Skip to content

Commit 88dbad6

Browse files
Andrew-Lees11ianpartridge
authored andcommitted
feat: Ignore newlines in key generation and allow DER formatting (#68)
* feat: Ignore newlines in key generation and allow DER formatting * Add Jazzy docs for missingPEMHeaders
1 parent 404308b commit 88dbad6

File tree

5 files changed

+44
-7
lines changed

5 files changed

+44
-7
lines changed

Sources/SwiftJWT/BlueRSA.swift

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,22 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm {
4949
Log.error("macOS 10.12.0 (Sierra) or higher or iOS 10.0 or higher is required by CryptorRSA")
5050
throw JWTError.osVersionToLow
5151
}
52-
guard let keyString = String(data: key, encoding: .utf8) else {
53-
throw JWTError.invalidPrivateKey
52+
// Convert PEM format to DER
53+
let keyDer: Data
54+
if let keyString = String(data: key, encoding: .utf8) {
55+
let strippedKey = String(keyString.filter { !" \n\t\r".contains($0) })
56+
var pemComponents = strippedKey.components(separatedBy: "-----")
57+
guard pemComponents.count >= 5 else {
58+
throw JWTError.missingPEMHeaders
59+
}
60+
guard let der = Data(base64Encoded: pemComponents[2]) else {
61+
throw JWTError.invalidPrivateKey
62+
}
63+
keyDer = der
64+
} else {
65+
keyDer = key
5466
}
55-
let privateKey = try CryptorRSA.createPrivateKey(withPEM: keyString)
67+
let privateKey = try CryptorRSA.createPrivateKey(with: keyDer)
5668
let myPlaintext = CryptorRSA.createPlaintext(with: data)
5769
guard let signedData = try myPlaintext.signed(with: privateKey, algorithm: algorithm, usePSS: usePSS) else {
5870
throw JWTError.invalidPrivateKey
@@ -85,10 +97,22 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm {
8597
case .privateKey:
8698
return false
8799
case .publicKey:
88-
guard let keyString = String(data: key, encoding: .utf8) else {
89-
return false
100+
// Convert PEM format to DER
101+
let keyDer: Data
102+
if let keyString = String(data: key, encoding: .utf8) {
103+
let strippedKey = String(keyString.filter { !" \n\t\r".contains($0) })
104+
var pemComponents = strippedKey.components(separatedBy: "-----")
105+
guard pemComponents.count >= 5 else {
106+
return false
107+
}
108+
guard let der = Data(base64Encoded: pemComponents[2]) else {
109+
return false
110+
}
111+
keyDer = der
112+
} else {
113+
keyDer = key
90114
}
91-
publicKey = try CryptorRSA.createPublicKey(withPEM: keyString)
115+
publicKey = try CryptorRSA.createPublicKey(with: keyDer)
92116
case .certificate:
93117
publicKey = try CryptorRSA.createPublicKey(extractingFrom: key)
94118
}

Sources/SwiftJWT/JWTError.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public struct JWTError: Error, Equatable {
2727
private let internalError: InternalError
2828

2929
private enum InternalError {
30-
case invalidJWTString, failedVerification, osVersionToLow, invalidPrivateKey, invalidData, invalidKeyID
30+
case invalidJWTString, failedVerification, osVersionToLow, invalidPrivateKey, invalidData, invalidKeyID, missingPEMHeaders
3131
}
3232

3333
/// Error when an invalid JWT String is provided
@@ -48,6 +48,9 @@ public struct JWTError: Error, Equatable {
4848
/// Error when the KeyID field `kid` in the JWT header fails to generate a JWTSigner or JWTVerifier
4949
public static let invalidKeyID = JWTError(localizedDescription: "The JWT KeyID `kid` header failed to generate a JWTSigner/JWTVerifier", internalError: .invalidKeyID)
5050

51+
/// Error when a PEM string is provided without the expected PEM headers/footers. (e.g. -----BEGIN PRIVATE KEY-----)
52+
public static let missingPEMHeaders = JWTError(localizedDescription: "The provided key did not have the expected PEM headers/footers", internalError: .missingPEMHeaders)
53+
5154
/// Function to check if JWTErrors are equal. Required for equatable protocol.
5255
public static func == (lhs: JWTError, rhs: JWTError) -> Bool {
5356
return lhs.internalError == rhs.internalError

Tests/SwiftJWTTests/TestJWT.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import Foundation
2121

2222
let rsaPrivateKey = read(fileName: "rsa_private_key")
2323
let rsaPublicKey = read(fileName: "rsa_public_key")
24+
let rsaDERPrivateKey = read(fileName: "privateRSA.der")
25+
let rsaDERPublicKey = read(fileName: "publicRSA.der")
2426
let ecdsaPrivateKey = read(fileName: "ecdsa_private_key")
2527
let ecdsaPublicKey = read(fileName: "ecdsa_public_key")
2628
let ec384PrivateKey = read(fileName: "ec384_private_key")
@@ -158,6 +160,14 @@ class TestJWT: XCTestCase {
158160
}
159161
}
160162

163+
func testSignAndVerifyRSADERKey() {
164+
do {
165+
try signAndVerify(signer: .rs256(privateKey: rsaDERPrivateKey), verifier: .rs256(publicKey: rsaDERPublicKey))
166+
} catch {
167+
XCTFail("testSignAndVerify failed: \(error)")
168+
}
169+
}
170+
161171
func testSignAndVerifyRSAPSS() {
162172
if #available(OSX 10.13, *) {
163173
do {

Tests/SwiftJWTTests/privateRSA.der

1.17 KB
Binary file not shown.

Tests/SwiftJWTTests/publicRSA.der

294 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)