Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add asn1 signature encoding to signer and verifier #81

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 25 additions & 7 deletions Sources/SwiftJWT/BlueECDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
}

Expand All @@ -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
Expand All @@ -93,18 +101,28 @@ 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)")
return false
}
}
}

public enum ECSignatureType {
case asn1
case rs
}
12 changes: 6 additions & 6 deletions Sources/SwiftJWT/JWTSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions Sources/SwiftJWT/JWTVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions Tests/SwiftJWTTests/TestJWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down