Skip to content

Commit

Permalink
feat(JWT): support for ES256K
Browse files Browse the repository at this point in the history
  • Loading branch information
goncalo-frade-iohk committed Mar 9, 2023
1 parent ec3954d commit fe2a464
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 2 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ let package = Package(
.package(name: "Cryptor", url: "https://github.com/Kitura/BlueCryptor.git", from: "2.0.1"),
.package(name: "CryptorECC", url: "https://github.com/Kitura/BlueECC.git", from: "1.2.200"),
.package(url: "https://github.com/Kitura/LoggerAPI.git", from: "2.0.0"),
.package(url: "https://github.com/Kitura/KituraContracts.git", from: "2.0.1")
.package(url: "https://github.com/Kitura/KituraContracts.git", from: "2.0.1"),
.package(url: "[email protected]:GigaBitcoin/secp256k1.swift.git", from: "0.5.0")
],
targets: [
.target(name: "SwiftJWT", dependencies: [
Expand All @@ -44,6 +45,7 @@ let package = Package(
"CryptorRSA",
"Cryptor",
"CryptorECC",
.product(name: "secp256k1", package: "secp256k1.swift")
]),
.testTarget(name: "SwiftJWTTests", dependencies: ["SwiftJWT"])
]
Expand Down
92 changes: 92 additions & 0 deletions Sources/SwiftJWT/ES256K.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Foundation
import LoggerAPI
import secp256k1

class ES256KSigner: SignerAlgorithm {
let name: String = "ES256K"
private let key: Data

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

// 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 = JWTEncoder.base64urlEncodedString(data: signature)
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 secp256k1
.Signing
.PrivateKey(rawRepresentation: key)

let signedData = try privateKey.ecdsa.signature(for: data)
return signedData.rawRepresentation
}
}

// Class for ECDSA verifying using BlueECC
@available(OSX 10.13, iOS 11, tvOS 11.0, watchOS 4.0, *)
class ES256KVerifier: VerifierAlgorithm {
let name: String = "ES256K"
private let key: Data

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

// Verify a signed JWT String
func verify(jwt: String) -> Bool {
let components = jwt.components(separatedBy: ".")
if components.count == 3 {
guard let signature = JWTDecoder.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 {
do {
let format: secp256k1.Format
switch key[0] {
case 0x02, 0x03:
format = .compressed
case 0x04:
format = .uncompressed
default:
throw JWTError.failedVerification
}
let publicKey = try secp256k1
.Signing
.PublicKey(rawRepresentation: key, format: format)
return publicKey
.ecdsa
.isValidSignature(
try .init(rawRepresentation: signature),
for: data
)
}
catch {
Log.error("Verification failed: \(error)")
return false
}
}
}
8 changes: 7 additions & 1 deletion Sources/SwiftJWT/JWTSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,13 @@ public struct JWTSigner {
public static func es512(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "ES512", signerAlgorithm: BlueECSigner(key: privateKey, curve: .secp521r1))
}


/// Initialize a JWTSigner using the ECDSA SHA512 algorithm and the provided a secp256k1 privateKey.
/// - Parameter privateKey: The UTF8 encoded PEM private key, with either a "BEGIN EC PRIVATE KEY" or "BEGIN PRIVATE KEY" header.
public static func es256k(privateKey: Data) -> JWTSigner {
return JWTSigner(name: "ES256K", signerAlgorithm: ES256KSigner(key: privateKey))
}

/// 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
6 changes: 6 additions & 0 deletions Sources/SwiftJWT/JWTVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ public struct JWTVerifier {
public static func es512(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: BlueECVerifier(key: publicKey, curve: .secp521r1))
}

/// Initialize a JWTVerifier using the ECDSA SHA 256 algorithm and the provided secp256k1 public key.
/// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header.
public static func es256k(publicKey: Data) -> JWTVerifier {
return JWTVerifier(verifierAlgorithm: ES256KVerifier(key: publicKey))
}

/// 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())
Expand Down

0 comments on commit fe2a464

Please sign in to comment.