Skip to content

Commit

Permalink
Fix Base64URL JWK conversion for ECDSA keys (#73)
Browse files Browse the repository at this point in the history
* fix conversion from base64Urlencoded string

* add test for the base64url fix

* changed the fuction of BigNumber to return a base64UrlEncoded string

* add function to explicitly handle base64URL string

* change init assuming the input strings are in base64URLformat

* change tests to convert and verify with base64url input

* remove extra space

* remove extra space
  • Loading branch information
MFranceschi6 committed Jun 14, 2022
1 parent 3537dd3 commit 718e94f
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 4 deletions.
8 changes: 4 additions & 4 deletions Sources/JWTKit/ECDSA/ECDSAKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public final class ECDSAKey: OpenSSLKey {
throw JWTError.signingAlgorithmFailure(ECDSAError.newKeyByCurveFailure)
}

guard let bnX = BigNumber.convert(parameters.x) else {
guard let bnX = BigNumber(base64URL: parameters.x) else {
throw JWTError.generic(identifier: "ecCoordinates", reason: "Unable to interpret x as BN")
}
guard let bnY = BigNumber.convert(parameters.y) else {
guard let bnY = BigNumber(base64URL: parameters.y) else {
throw JWTError.generic(identifier: "ecCoordinates", reason: "Unable to interpret y as BN")
}

Expand All @@ -90,7 +90,7 @@ public final class ECDSAKey: OpenSSLKey {
}

if let privateKey = privateKey {
guard let bnPrivate = BigNumber.convert(privateKey) else {
guard let bnPrivate = BigNumber(base64URL: privateKey) else {
throw JWTError.generic(identifier: "ecPrivateKey", reason: "Unable to interpret privateKey as BN")
}
if CJWTKitBoringSSL_EC_KEY_set_private_key(c, bnPrivate.c) != 1 {
Expand Down Expand Up @@ -121,7 +121,7 @@ public final class ECDSAKey: OpenSSLKey {
return nil
}

return Parameters(x: bnX.toBase64(), y: bnY.toBase64())
return Parameters(x: bnX.toBase64URL(), y: bnY.toBase64URL())
}

public struct Parameters {
Expand Down
21 changes: 21 additions & 0 deletions Sources/JWTKit/Utilities/BigNumber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ class BigNumber {
return BigNumber(c)
}

public convenience init?(base64URL: String) {
guard let data = base64URL.data(using: .utf8)?.base64URLDecodedBytes() else {
return nil
}

let c = data.withUnsafeBytes { (p: UnsafeRawBufferPointer) -> OpaquePointer in
return OpaquePointer(CJWTKitBoringSSL_BN_bin2bn(p.baseAddress?.assumingMemoryBound(to: UInt8.self), p.count, nil))
}
self.init(c)
}

public func toBase64(_ size: Int = 1000) -> String {
let pBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
defer { pBuffer.deallocate() }
Expand All @@ -35,4 +46,14 @@ class BigNumber {
let data = Data(bytes: pBuffer, count: actualBytes)
return data.base64EncodedString()
}

public func toBase64URL() -> String {
let bytes = CJWTKitBoringSSL_BN_num_bytes(self.c)
let pBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(bytes))
defer { pBuffer.deallocate() }

let actualBytes = Int(CJWTKitBoringSSL_BN_bn2bin(self.c, pBuffer))
let data = Data(bytes: pBuffer, count: actualBytes)
return String.init(decoding: data.base64URLEncodedBytes(), as: UTF8.self)
}
}
77 changes: 77 additions & 0 deletions Tests/JWTKitTests/JWTKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,83 @@ class JWTKitTests: XCTestCase {
let foo = try signers.verify(jwt, as: Foo.self)
XCTAssertEqual(foo.bar, 42)
}

func testVerifyingECDSAKeyUsingJWKBase64URL() throws {
struct Foo: JWTPayload {
var bar: Int
func verify(using signer: JWTSigner) throws { }
}

// ecdsa key in base64url format
let x = "0tu_H2ShuV8RIgoOxFneTdxmQQYsSk5LdCPuEIBXT-hHd0ufc_OwjEbqilsYnTdm"
let y = "RWRZz-tP83N0CGwroGyFVgH3PYAO6Oewpu4Xf6EXCp4-sU8uWegwjd72sBK6axj7"

let privateKey = "k-1LAHQRSSMcyaouYK0YOzRbUKj6ISnvihO2XdLQZHQgMt9BkuCT0-539FSHmJxg"

// sign jwt
let privateSigner = JWTSigner.es384(key: try ECDSAKey(parameters: .init(x: x, y: y), curve: .p384, privateKey: privateKey))

let jwt = try privateSigner.sign(Foo(bar: 42), kid: "vapor")

// verify using jwks without alg
let jwksString = """
{
"keys": [
{
"kty": "EC",
"use": "sig",
"kid": "vapor",
"x": "\(x)",
"y": "\(y)"
}
]
}
"""

let signers = JWTSigners()
try signers.use(jwksJSON: jwksString)
let foo = try signers.verify(jwt, as: Foo.self)
XCTAssertEqual(foo.bar, 42)
}

func testVerifyingECDSAKeyUsingJWKWithMixedBase64Formats() throws {
struct Foo: JWTPayload {
var bar: Int
func verify(using signer: JWTSigner) throws { }
}

// ecdsa key in base64url format
let x = "0tu_H2ShuV8RIgoOxFneTdxmQQYsSk5LdCPuEIBXT-hHd0ufc_OwjEbqilsYnTdm"
let y = "RWRZz-tP83N0CGwroGyFVgH3PYAO6Oewpu4Xf6EXCp4-sU8uWegwjd72sBK6axj7"

// private key in base64 format
let privateKey = "k+1LAHQRSSMcyaouYK0YOzRbUKj6ISnvihO2XdLQZHQgMt9BkuCT0+539FSHmJxg"

// sign jwt
let privateSigner = JWTSigner.es384(key: try ECDSAKey(parameters: .init(x: x, y: y), curve: .p384, privateKey: privateKey))

let jwt = try privateSigner.sign(Foo(bar: 42), kid: "vapor")

// verify using jwks without alg
let jwksString = """
{
"keys": [
{
"kty": "EC",
"use": "sig",
"kid": "vapor",
"x": "\(x)",
"y": "\(y)"
}
]
}
"""

let signers = JWTSigners()
try signers.use(jwksJSON: jwksString)
let foo = try signers.verify(jwt, as: Foo.self)
XCTAssertEqual(foo.bar, 42)
}

func testJWTioExample() throws {
let token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA"
Expand Down

0 comments on commit 718e94f

Please sign in to comment.