diff --git a/README.md b/README.md index 665ad78..1ddf5fd 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,17 @@ The supported algorithms for signing and verifying JWTs are: Note: ECDSA and RSA-PSS algorithms require a minimum Swift version of 4.1. + +#### Using ASN1 encoding for ECDSA signature + +In some cases the ASN1 encoding for ECDSA signatures might be necessary. To use this, a `signatureType: ECSignatureType` parameter can be used for the `JWTSigner` and `JWTVerifier` like this: + +```swift +let jwtSigner = JWTSigner.es512(privateKey: privateKey, signatureType: .asn1) +let jwtVerifier = JWTVerifier.es512(publicKey: publicKey, signatureType: .asn1) +``` + + ### Validate claims The `validateClaims` function validates the standard `Date` claims of a JWT instance. diff --git a/Sources/SwiftJWT/BlueECDSA.swift b/Sources/SwiftJWT/BlueECDSA.swift index 4f7e0f4..07e85d8 100644 --- a/Sources/SwiftJWT/BlueECDSA.swift +++ b/Sources/SwiftJWT/BlueECDSA.swift @@ -25,11 +25,13 @@ class BlueECSigner: SignerAlgorithm { private let key: Data private let curve: EllipticCurve + private let signatureType: ECSignatureType // Initialize a signer using .utf8 encoded PEM private key. - init(key: Data, curve: EllipticCurve) { + init(key: Data, curve: EllipticCurve, signatureType: ECSignatureType = .rs) { self.key = key self.curve = curve + self.signatureType = signatureType } // Sign the header and claims to produce a signed JWT String @@ -53,7 +55,11 @@ class BlueECSigner: SignerAlgorithm { throw JWTError.invalidPrivateKey } let signedData = try data.sign(with: privateKey) - return signedData.r + signedData.s + if signatureType == .asn1 { + return signedData.asn1 + } else { + return signedData.r + signedData.s + } } } @@ -65,11 +71,13 @@ class BlueECVerifier: VerifierAlgorithm { private let key: Data private let curve: EllipticCurve + private let signatureType: ECSignatureType // Initialize a verifier using .utf8 encoded PEM public key. - init(key: Data, curve: EllipticCurve) { + init(key: Data, curve: EllipticCurve, signatureType: ECSignatureType = .rs) { self.key = key self.curve = curve + self.signatureType = signatureType } // Verify a signed JWT String @@ -93,14 +101,19 @@ class BlueECVerifier: VerifierAlgorithm { guard let keyString = String(data: key, encoding: .utf8) else { return false } - let r = signature.subdata(in: 0 ..< signature.count/2) - let s = signature.subdata(in: signature.count/2 ..< signature.count) - let signature = try ECSignature(r: r, s: s) + let ecSignature: ECSignature + if signatureType == .asn1 { + ecSignature = try ECSignature(asn1: signature) + } else { + let r = signature.subdata(in: 0 ..< signature.count/2) + let s = signature.subdata(in: signature.count/2 ..< signature.count) + ecSignature = try ECSignature(r: r, s: s) + } let publicKey = try ECPublicKey(key: keyString) guard publicKey.curve == curve else { return false } - return signature.verify(plaintext: data, using: publicKey) + return ecSignature.verify(plaintext: data, using: publicKey) } catch { Log.error("Verification failed: \(error)") @@ -108,3 +121,8 @@ class BlueECVerifier: VerifierAlgorithm { } } } + +public enum ECSignatureType { + case asn1 + case rs +} diff --git a/Sources/SwiftJWT/JWTSigner.swift b/Sources/SwiftJWT/JWTSigner.swift index 4af3bfd..633f685 100644 --- a/Sources/SwiftJWT/JWTSigner.swift +++ b/Sources/SwiftJWT/JWTSigner.swift @@ -123,22 +123,22 @@ public struct JWTSigner { /// Initialize a JWTSigner using the ECDSA SHA256 algorithm and the provided privateKey. /// - Parameter privateKey: The UTF8 encoded PEM private key, with either a "BEGIN EC PRIVATE KEY" or "BEGIN PRIVATE KEY" header. @available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *) - public static func es256(privateKey: Data) -> JWTSigner { - return JWTSigner(name: "ES256", signerAlgorithm: BlueECSigner(key: privateKey, curve: .prime256v1)) + public static func es256(privateKey: Data, signatureType: ECSignatureType = .rs) -> JWTSigner { + return JWTSigner(name: "ES256", signerAlgorithm: BlueECSigner(key: privateKey, curve: .prime256v1, signatureType: signatureType)) } /// Initialize a JWTSigner using the ECDSA SHA384 algorithm and the provided privateKey. /// - Parameter privateKey: The UTF8 encoded PEM private key, with either a "BEGIN EC PRIVATE KEY" or "BEGIN PRIVATE KEY" header. @available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *) - public static func es384(privateKey: Data) -> JWTSigner { - return JWTSigner(name: "ES384", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp384r1)) + public static func es384(privateKey: Data, signatureType: ECSignatureType = .rs) -> JWTSigner { + return JWTSigner(name: "ES384", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp384r1, signatureType: signatureType)) } /// Initialize a JWTSigner using the ECDSA SHA512 algorithm and the provided privateKey. /// - Parameter privateKey: The UTF8 encoded PEM private key, with either a "BEGIN EC PRIVATE KEY" or "BEGIN PRIVATE KEY" header. @available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *) - public static func es512(privateKey: Data) -> JWTSigner { - return JWTSigner(name: "ES512", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp521r1)) + public static func es512(privateKey: Data, signatureType: ECSignatureType = .rs) -> JWTSigner { + return JWTSigner(name: "ES512", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp521r1, signatureType: signatureType)) } /// Initialize a JWTSigner that will not sign the JWT. This is equivelent to using the "none" alg header. diff --git a/Sources/SwiftJWT/JWTVerifier.swift b/Sources/SwiftJWT/JWTVerifier.swift index 35df293..a0f2c9a 100644 --- a/Sources/SwiftJWT/JWTVerifier.swift +++ b/Sources/SwiftJWT/JWTVerifier.swift @@ -129,22 +129,22 @@ public struct JWTVerifier { /// Initialize a JWTVerifier using the ECDSA SHA 256 algorithm and the provided public key. /// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header. @available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *) - public static func es256(publicKey: Data) -> JWTVerifier { - return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .prime256v1)) + public static func es256(publicKey: Data, signatureType: ECSignatureType = .rs) -> JWTVerifier { + return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .prime256v1, signatureType: signatureType)) } /// Initialize a JWTVerifier using the ECDSA SHA 384 algorithm and the provided public key. /// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header. @available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *) - public static func es384(publicKey: Data) -> JWTVerifier { - return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp384r1)) + public static func es384(publicKey: Data, signatureType: ECSignatureType = .rs) -> JWTVerifier { + return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp384r1, signatureType: signatureType)) } /// Initialize a JWTVerifier using the ECDSA SHA 512 algorithm and the provided public key. /// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header. @available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *) - public static func es512(publicKey: Data) -> JWTVerifier { - return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp521r1)) + public static func es512(publicKey: Data, signatureType: ECSignatureType = .rs) -> JWTVerifier { + return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp521r1, signatureType: signatureType)) } /// Initialize a JWTVerifier that will always return true when verifying the JWT. This is equivelent to using the "none" alg header. diff --git a/Tests/SwiftJWTTests/TestJWT.swift b/Tests/SwiftJWTTests/TestJWT.swift index ea7b8cd..f47500c 100644 --- a/Tests/SwiftJWTTests/TestJWT.swift +++ b/Tests/SwiftJWTTests/TestJWT.swift @@ -295,6 +295,16 @@ class TestJWT: XCTestCase { } } + func testSignAndVerifyECDSA512ASN1() { + if #available(OSX 10.13, iOS 11, tvOS 11.0, *) { + do { + try signAndVerify(signer: .es512(privateKey: ec512PrivateKey, signatureType: .asn1), verifier: .es512(publicKey: ec512PublicKey, signatureType: .asn1)) + } catch { + XCTFail("testSignAndVerify failed: \(error)") + } + } + } + func signAndVerify(signer: JWTSigner, verifier: JWTVerifier) throws { var jwt = JWT(claims: TestClaims(name:"Kitura")) jwt.claims.name = "Kitura-JWT"