Skip to content

Commit

Permalink
feat: Add ES256, ES384 and ES512 support (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-Lees11 authored Mar 5, 2019
1 parent 03fa06f commit 527e757
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 197 deletions.
7 changes: 4 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/IBM-Swift/BlueRSA.git", from:"1.0.0"),
.package(url: "https://github.com/IBM-Swift/BlueCryptor.git", from:"1.0.0"),
.package(url: "https://github.com/IBM-Swift/BlueRSA.git", from: "1.0.24"),
.package(url: "https://github.com/IBM-Swift/BlueCryptor.git", from: "1.0.0"),
.package(url: "https://github.com/IBM-Swift/BlueECC.git", from: "1.1.0"),
.package(url: "https://github.com/IBM-Swift/LoggerAPI.git", from: "1.7.0"),
.package(url: "https://github.com/IBM-Swift/KituraContracts.git", from: "1.1.0")
],
targets: [
.target(name: "SwiftJWT", dependencies: ["CryptorRSA", "LoggerAPI", "KituraContracts", "Cryptor"]),
.target(name: "SwiftJWT", dependencies: ["CryptorRSA", "LoggerAPI", "KituraContracts", "Cryptor", "CryptorECC"]),
.testTarget(name: "SwiftJWTTests", dependencies: ["SwiftJWT"])
]
)
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ let myJWT = JWT(header: myHeader, claims: myClaims)

### Signing and Verifying JSON web tokens

#### Creating the RSA public and private keys
#### Creating public and private keys

To sign and verify a JWT using an RSA algorithm, you must provide a public and private key. This could be the contents of a .key file generated via the following Terminal commands:

Expand All @@ -126,6 +126,8 @@ let publicKeyPath = URL(fileURLWithPath: getAbsolutePath(relativePath: "/path/to
let publicKey: Data = try Data(contentsOf: publicKeyPath, options: alwaysMapped)
```

For details on creating elliptic curve public and private keys, view the [BlueECC README.txt](https://github.com/IBM-Swift/BlueECC).

#### Sign a JWT using a JWTSigner

The struct JWTSigner contains the algorithms that can be used to sign a JWT.
Expand Down Expand Up @@ -174,8 +176,13 @@ The supported algorithms for signing and verifying JWTs are:
* HS256 - HMAC using using SHA-256
* HS384 - HMAC using using SHA-384
* HS512 - HMAC using using SHA-512
* ES256 - ECDSA using using SHA-256 and a P-256 curve
* ES384 - ECDSA using using SHA-384 and a P-384 curve
* ES512 - ECDSA using using SHA-512 and a P-521 curve
* none - Don't sign or verify the JWT

Note: ECDSA algorithms require a minimum Swift version of 4.1.

### Validate claims

The `validateClaims` function validates the standard `Date` claims of a JWT instance.
Expand Down
113 changes: 113 additions & 0 deletions Sources/SwiftJWT/BlueECDSA.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copyright IBM Corporation 2019
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import CryptorECC
import LoggerAPI
import Foundation

// Class for ECDSA signing using BlueECC
@available(OSX 10.13, *)
class BlueECSigner: SignerAlgorithm {
let name: String = "ECDSA"

private let key: Data
private let curve: EllipticCurve

// Initialize a signer using .utf8 encoded PEM private key.
init(key: Data, curve: EllipticCurve) {
self.key = key
self.curve = curve
}

// Sign the header and claims to produce a signed JWT String
func sign(header: String, claims: String) throws -> String {
let unsignedJWT = header + "." + claims
guard let unsignedData = unsignedJWT.data(using: .utf8) else {
throw JWTError.invalidJWTString
}
let signature = try sign(unsignedData)
let signatureString = signature.base64urlEncodedString()
return header + "." + claims + "." + signatureString
}

// send utf8 encoded `header.claims` to BlueECC for signing
private func sign(_ data: Data) throws -> Data {
guard let keyString = String(data: key, encoding: .utf8) else {
throw JWTError.invalidPrivateKey
}
let privateKey = try ECPrivateKey(key: keyString)
guard privateKey.curve == curve else {
throw JWTError.invalidPrivateKey
}
let signedData = try data.sign(with: privateKey)
return signedData.r + signedData.s
}
}

// Class for ECDSA verifying using BlueECC
@available(OSX 10.13, *)
class BlueECVerifier: VerifierAlgorithm {

let name: String = "ECDSA"

private let key: Data
private let curve: EllipticCurve

// Initialize a verifier using .utf8 encoded PEM public key.
init(key: Data, curve: EllipticCurve) {
self.key = key
self.curve = curve
}

// Verify a signed JWT String
func verify(jwt: String) -> Bool {
let components = jwt.components(separatedBy: ".")
if components.count == 3 {
guard let signature = Data(base64urlEncoded: components[2]),
let jwtData = (components[0] + "." + components[1]).data(using: .utf8)
else {
return false
}
return self.verify(signature: signature, for: jwtData)
} else {
return false
}
}

// Send the base64URLencoded signature and `header.claims` to BlueECC for verification.
private func verify(signature: Data, for data: Data) -> Bool {
guard #available(OSX 10.13, *) else {
return false
}
do {
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 publicKey = try ECPublicKey(key: keyString)
guard publicKey.curve == curve else {
return false
}
return signature.verify(plaintext: data, using: publicKey)
}
catch {
Log.error("Verification failed: \(error)")
return false
}
}
}
50 changes: 47 additions & 3 deletions Sources/SwiftJWT/JWTSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,33 @@ import Foundation

/**
A struct that will be used to sign the JWT `Header` and `Claims` and generate a signed JWT.
For RSA and ECDSA, the provided key should be a .utf8 encoded PEM String.
### Usage Example: ###
```swift
let privateKey = "<PrivateKey>".data(using: .utf8)!
let pemString = """
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS
3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp
uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE
2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0
GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K
Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY
6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5
fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
-----END RSA PRIVATE KEY-----
"""
let privateKey = pemString.data(using: .utf8)!
let jwtSigner = JWTSigner.rs256(privateKey: privateKey)
struct MyClaims: Claims {
var name: String
}
let jwt = JWT(claims: MyClaims(name: "Kitura"))
let signedJWT: String? = try? jwt.sign(using: jwtSigner)
let signedJWT = try? jwt.sign(using: jwtSigner)
```
*/
public struct JWTSigner {
Expand All @@ -49,35 +66,62 @@ public struct JWTSigner {
}

/// Initialize a JWTSigner using the RSA 256 bits algorithm and the provided privateKey.
/// - Parameter privateKey: The UTF8 encoded PEM private key, with a "BEGIN RSA PRIVATE KEY" header.
public static func rs256(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "RS256", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha256))
}

/// Initialize a JWTSigner using the RSA 384 bits algorithm and the provided privateKey.
/// - Parameter privateKey: The UTF8 encoded PEM private key, with a "BEGIN RSA PRIVATE KEY" header.
public static func rs384(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "RS384", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha384))
}

/// Initialize a JWTSigner using the RSA 512 bits algorithm and the provided privateKey.
/// - Parameter privateKey: The UTF8 encoded PEM private key, with a "BEGIN RSA PRIVATE KEY" header.
public static func rs512(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "RS512", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha512))
}

/// Initialize a JWTSigner using the HMAC 256 bits algorithm and the provided privateKey.
/// - Parameter key: The HMAC symmetric password data.
public static func hs256(key: Data) -> JWTSigner {
return JWTSigner(name: "HS256", signerAlgorithm: BlueHMAC(key: key, algorithm: .sha256))
}

/// Initialize a JWTSigner using the HMAC 384 bits algorithm and the provided privateKey.
/// - Parameter key: The HMAC symmetric password data.
public static func hs384(key: Data) -> JWTSigner {
return JWTSigner(name: "HS384", signerAlgorithm: BlueHMAC(key: key, algorithm: .sha384))
}

/// Initialize a JWTSigner using the HMAC 512 bits algorithm and the provided privateKey.
/// - Parameter key: The HMAC symmetric password data.
public static func hs512(key: Data) -> JWTSigner {
return JWTSigner(name: "HS512", signerAlgorithm: BlueHMAC(key: key, algorithm: .sha512))
}

/// 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, *)
public static func es256(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "ES256", signerAlgorithm: BlueECSigner(key: privateKey, curve: .prime256v1))
}

/// 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, *)
public static func es384(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "ES384", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp384r1))
}

/// 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, *)
public static func es512(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "ES512", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp521r1))
}

/// Initialize a JWTSigner that will not sign the JWT. This is equivelent to using the "none" alg header.
public static let none = JWTSigner(name: "none", signerAlgorithm: NoneAlgorithm())
}
Expand Down
44 changes: 41 additions & 3 deletions Sources/SwiftJWT/JWTVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@ import Foundation
/**
A struct that will be used to verify the signature of a JWT is valid for the provided `Header` and `Claims`.
For RSA and ECDSA, the provided key should be a .utf8 encoded PEM String.
### Usage Example: ###
```swift
let signedJWT = "<SignedJWTString>"
let pemString = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
o2kQ+X5xK9cipRgEKwIDAQAB
-----END PUBLIC KEY-----
"""
let signedJWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS2l0dXJhIn0.o2Rv_w1W6qfkldgb6FwzC3tAFEzo7WyYcLyykijCEqDbW8A7TwoFev85KGo_Bi7eNaSgZ6Q8jgkA31r8EDQWtSRg3_o5Zlq-ZCndyVeibgbyM2BMVUGcGzkUD2ikARfnb6GNGHr2waVeFSDehTN8WTLl0mGFxUE6wx5ZugR7My0"
struct MyClaims: Claims {
var name: String
}
let jwt = JWT(claims: MyClaims(name: "Kitura"))
let publicKey = "<PublicKey>".data(using: .utf8)!
let publicKey = pemString.data(using: .utf8)!
let jwtVerifier = JWTVerifier.rs256(publicKey: publicKey)
let verified: Bool = jwt.verify(signedJWT, using: jwtVerifier)
```
Expand All @@ -46,50 +54,80 @@ public struct JWTVerifier {
}

/// Initialize a JWTVerifier using the RSA 256 bits algorithm and the provided publicKey.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header.
public static func rs256(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueRSA(key: publicKey, keyType: .publicKey, algorithm: .sha256))
}

/// Initialize a JWTVerifier using the RSA 384 bits algorithm and the provided publicKey.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header.
public static func rs384(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueRSA(key: publicKey, keyType: .publicKey, algorithm: .sha384))
}

/// Initialize a JWTVerifier using the RSA 512 bits algorithm and the provided publicKey.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header.
public static func rs512(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueRSA(key: publicKey, keyType: .publicKey, algorithm: .sha512))
}

/// Initialize a JWTVerifier using the RSA 256 bits algorithm and the provided certificate.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN CERTIFICATE" header.
public static func rs256(certificate: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueRSA(key: certificate, keyType: .certificate, algorithm: .sha256))
}

/// Initialize a JWTVerifier using the RSA 384 bits algorithm and the provided certificate.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN CERTIFICATE" header.
public static func rs384(certificate: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueRSA(key: certificate, keyType: .certificate, algorithm: .sha384))
}

/// Initialize a JWTVerifier using the RSA 512 bits algorithm and the provided certificate.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN CERTIFICATE" header.
public static func rs512(certificate: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueRSA(key: certificate, keyType: .certificate, algorithm: .sha512))
}

/// Initialize a JWTSigner using the HMAC 256 bits algorithm and the provided privateKey.
/// - Parameter key: The HMAC symmetric password data.
public static func hs256(key: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueHMAC(key: key, algorithm: .sha256))
}

/// Initialize a JWTSigner using the HMAC 384 bits algorithm and the provided privateKey.
/// - Parameter key: The HMAC symmetric password data.
public static func hs384(key: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueHMAC(key: key, algorithm: .sha384))
}

/// Initialize a JWTSigner using the HMAC 512 bits algorithm and the provided privateKey.
/// - Parameter key: The HMAC symmetric password data.
public static func hs512(key: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueHMAC(key: key, algorithm: .sha512))
}

/// 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, *)
public static func es256(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .prime256v1))
}

/// 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, *)
public static func es384(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp384r1))
}

/// 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, *)
public static func es512(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp521r1))
}

/// Initialize a JWTVerifier that will always return true when verifying the JWT. This is equivelent to using the "none" alg header.
public static let none = JWTVerifier(verifierAlgorithm: NoneAlgorithm())
}
Loading

0 comments on commit 527e757

Please sign in to comment.