From 02b7520a9aeae72409f4e52eeba09c4cad6fcb2c Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 1 Jul 2019 11:17:11 +0100 Subject: [PATCH 1/2] feat: Ignore newlines in key generation and allow DER formatting --- Sources/SwiftJWT/BlueRSA.swift | 36 ++++++++++++++++++++++++----- Sources/SwiftJWT/JWTError.swift | 4 +++- Tests/SwiftJWTTests/TestJWT.swift | 10 ++++++++ Tests/SwiftJWTTests/privateRSA.der | Bin 0 -> 1194 bytes Tests/SwiftJWTTests/publicRSA.der | Bin 0 -> 294 bytes 5 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 Tests/SwiftJWTTests/privateRSA.der create mode 100644 Tests/SwiftJWTTests/publicRSA.der diff --git a/Sources/SwiftJWT/BlueRSA.swift b/Sources/SwiftJWT/BlueRSA.swift index 7dee825..16fbd18 100644 --- a/Sources/SwiftJWT/BlueRSA.swift +++ b/Sources/SwiftJWT/BlueRSA.swift @@ -49,10 +49,22 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm { Log.error("macOS 10.12.0 (Sierra) or higher or iOS 10.0 or higher is required by CryptorRSA") throw JWTError.osVersionToLow } - guard let keyString = String(data: key, encoding: .utf8) else { - throw JWTError.invalidPrivateKey + // Convert PEM format to DER + let keyDer: Data + if let keyString = String(data: key, encoding: .utf8) { + let strippedKey = String(keyString.filter { !" \n\t\r".contains($0) }) + var pemComponents = strippedKey.components(separatedBy: "-----") + guard pemComponents.count >= 5 else { + throw JWTError.missingPEMHeaders + } + guard let der = Data(base64Encoded: pemComponents[2]) else { + throw JWTError.invalidPrivateKey + } + keyDer = der + } else { + keyDer = key } - let privateKey = try CryptorRSA.createPrivateKey(withPEM: keyString) + let privateKey = try CryptorRSA.createPrivateKey(with: keyDer) let myPlaintext = CryptorRSA.createPlaintext(with: data) guard let signedData = try myPlaintext.signed(with: privateKey, algorithm: algorithm, usePSS: usePSS) else { throw JWTError.invalidPrivateKey @@ -85,10 +97,22 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm { case .privateKey: return false case .publicKey: - guard let keyString = String(data: key, encoding: .utf8) else { - return false + // Convert PEM format to DER + let keyDer: Data + if let keyString = String(data: key, encoding: .utf8) { + let strippedKey = String(keyString.filter { !" \n\t\r".contains($0) }) + var pemComponents = strippedKey.components(separatedBy: "-----") + guard pemComponents.count >= 5 else { + return false + } + guard let der = Data(base64Encoded: pemComponents[2]) else { + return false + } + keyDer = der + } else { + keyDer = key } - publicKey = try CryptorRSA.createPublicKey(withPEM: keyString) + publicKey = try CryptorRSA.createPublicKey(with: keyDer) case .certificate: publicKey = try CryptorRSA.createPublicKey(extractingFrom: key) } diff --git a/Sources/SwiftJWT/JWTError.swift b/Sources/SwiftJWT/JWTError.swift index d26be22..cccff49 100644 --- a/Sources/SwiftJWT/JWTError.swift +++ b/Sources/SwiftJWT/JWTError.swift @@ -27,7 +27,7 @@ public struct JWTError: Error, Equatable { private let internalError: InternalError private enum InternalError { - case invalidJWTString, failedVerification, osVersionToLow, invalidPrivateKey, invalidData, invalidKeyID + case invalidJWTString, failedVerification, osVersionToLow, invalidPrivateKey, invalidData, invalidKeyID, missingPEMHeaders } /// Error when an invalid JWT String is provided @@ -48,6 +48,8 @@ public struct JWTError: Error, Equatable { /// Error when the KeyID field `kid` in the JWT header fails to generate a JWTSigner or JWTVerifier public static let invalidKeyID = JWTError(localizedDescription: "The JWT KeyID `kid` header failed to generate a JWTSigner/JWTVerifier", internalError: .invalidKeyID) + public static let missingPEMHeaders = JWTError(localizedDescription: "The provided key did not have the expected PEM headers/footers. (e.g. -----BEGIN PRIVATE KEY-----)", internalError: .missingPEMHeaders) + /// Function to check if JWTErrors are equal. Required for equatable protocol. public static func == (lhs: JWTError, rhs: JWTError) -> Bool { return lhs.internalError == rhs.internalError diff --git a/Tests/SwiftJWTTests/TestJWT.swift b/Tests/SwiftJWTTests/TestJWT.swift index 57e6f5e..3cfd19a 100644 --- a/Tests/SwiftJWTTests/TestJWT.swift +++ b/Tests/SwiftJWTTests/TestJWT.swift @@ -21,6 +21,8 @@ import Foundation let rsaPrivateKey = read(fileName: "rsa_private_key") let rsaPublicKey = read(fileName: "rsa_public_key") +let rsaDERPrivateKey = read(fileName: "privateRSA.der") +let rsaDERPublicKey = read(fileName: "publicRSA.der") let ecdsaPrivateKey = read(fileName: "ecdsa_private_key") let ecdsaPublicKey = read(fileName: "ecdsa_public_key") let ec384PrivateKey = read(fileName: "ec384_private_key") @@ -158,6 +160,14 @@ class TestJWT: XCTestCase { } } + func testSignAndVerifyRSADERKey() { + do { + try signAndVerify(signer: .rs256(privateKey: rsaDERPrivateKey), verifier: .rs256(publicKey: rsaDERPublicKey)) + } catch { + XCTFail("testSignAndVerify failed: \(error)") + } + } + func testSignAndVerifyRSAPSS() { if #available(OSX 10.13, *) { do { diff --git a/Tests/SwiftJWTTests/privateRSA.der b/Tests/SwiftJWTTests/privateRSA.der new file mode 100644 index 0000000000000000000000000000000000000000..15f577cf9ed37228a444eda8bfd43861c6589159 GIT binary patch literal 1194 zcmV;b1XcSmf&``l0RRGm0RaH5jXgUFDD$vbsRDsv@)DA%Dw z-sgAGTP|s{IuvJY>59}X@KBtZz>?y}t(!$uG!HbZ8PQ0(?`>SU}d8yVwK~q8PzEb`xoCG|%rt<);?d>OhCWf1IwVO*gzU`)%VU7L; zMsd>pQQPwadYZK^;=jAUW@&#ia6!2P20U}fDRhW2kf91Rbj1j`O)|PKZm-|HFPH>G zOM^VbS2Xf4hxcGGMX2op0|5X50)hbn0GrV(AWh*8sZDUOXDL-rtFpN`-Fo*hPUCjp zO#5gb@e$USO5_w#Uu7c59`ljfs}*ylY;`1HylXmJC9A9P+W(_=TP`}dou%T$I zRYc-xMcF=uAPy7{s#$Pt20>XZJ~Cw5v=#RY+f6%cC$d4>5f?ttLl6~meM&1a^SprZ z8DWOu8Z~;cX$;e%5z@Z8XdKF$SQXIc zebs<^(mSE$lUDcW0W()fNCjaQ@D*Z73-n0rgBbC+5*N#RqK^Y-bM2fHm*uNw)};-C zMn@}B3nD%J!(r;pT6AwSIK{p-uI#IsYFxmEoOg}tw*SoTnuy9BKoP_tRU&aW2LgeC z0L2&<&QtTrE)RP!E{Ls8hqS@cr8kVNJ}*xD$XSwk_QE6l*1PVW8qHe*w2KlLtw|L} z`aQ5PXf-uPXYI63&D@zWQ?F0`FXx5MJy>ptEnIXOXfol5w~PZR=1J`cwp>DK^x!o{ zRX1`?)o}R-D1t_$)c^$y)DpQT_nM>6YXX6R0KGZ-?*Pu56j81f!>tLjXKUSP$MuAC zlCaH+WuYOK_Ba`L?AOz#}}_99Vp zWdebL0Hks;?HfGj*{6>fCu?AKiedHYByrKrW_lolk*Tu2QgyF`F++lvrZYQ1X_PaV zB~>~NVJv}45?Ff2@Wk^`VGuJtW!`&r`?p8@Nl^5FXrQ1vyg%R1tDS|iI`ips0DQmc z8Ys*vkHvs^lA`?-%;wF!kN?3o@8@GzhL0a8TLOWB0H6wZxFni;B_s_=l-;~B^qoE& zUh{k?z#s7TXz7>Y1Hq0L+q)g~&;Pk#iktYkN}oI=n@z3@I8yBfXH&*+U-tt-qtTXs z+V3Ca>TG6RvD2J9ig_wt1*~1(S20ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>lt&Kf92`KZhSg8Vm zVdV#ou4b0<_T9tJ#$w9MN+{Q%L*D0i(OWKQvpN)KZ0U;BE$~pBn!u9c$E}-1R5TAX zs~OQqy6g}E1U#8xu){~ zt?lh6eI|yRbhVpHIKJ(sm|>0n1x9hw{ZZTV1A3aZF5E$SHJ) sF_57OG<3xXxJ@#;FK(~jy)T#qL`#D_#8))(Fo*YGFh!{C0s{d60cm@PS^xk5 literal 0 HcmV?d00001 From 7d5d4f8b0c7e4da57654922048b34e3fb5c21510 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 1 Jul 2019 11:20:29 +0100 Subject: [PATCH 2/2] Add Jazzy docs for missingPEMHeaders --- Sources/SwiftJWT/JWTError.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftJWT/JWTError.swift b/Sources/SwiftJWT/JWTError.swift index cccff49..02765aa 100644 --- a/Sources/SwiftJWT/JWTError.swift +++ b/Sources/SwiftJWT/JWTError.swift @@ -48,7 +48,8 @@ public struct JWTError: Error, Equatable { /// Error when the KeyID field `kid` in the JWT header fails to generate a JWTSigner or JWTVerifier public static let invalidKeyID = JWTError(localizedDescription: "The JWT KeyID `kid` header failed to generate a JWTSigner/JWTVerifier", internalError: .invalidKeyID) - public static let missingPEMHeaders = JWTError(localizedDescription: "The provided key did not have the expected PEM headers/footers. (e.g. -----BEGIN PRIVATE KEY-----)", internalError: .missingPEMHeaders) + /// Error when a PEM string is provided without the expected PEM headers/footers. (e.g. -----BEGIN PRIVATE KEY-----) + public static let missingPEMHeaders = JWTError(localizedDescription: "The provided key did not have the expected PEM headers/footers", internalError: .missingPEMHeaders) /// Function to check if JWTErrors are equal. Required for equatable protocol. public static func == (lhs: JWTError, rhs: JWTError) -> Bool {