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

feature(ES256K): add functionality to failsafe verification of signatures from bouncy castle #12

Merged
merged 1 commit into from
Apr 14, 2024
Merged
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,14 @@ JWS is a standard for digitally signing arbitrary content, as detailed in [RFC 7
- PS512 (RSA PSS with SHA-512)
- EdDSA (EdDSA using Ed25519) - [RFC 8037](https://datatracker.ietf.org/doc/html/rfc8037)

### Bouncy castle secp256k1 failsafe

There is a difference between the signatures given by Bouncy castle a prominent cryptographic Java library and used with Nimbus JWT and bitcoin secp256k1. The signatures are in DER format and for some reason the R and S are reverted.

To have signatures that are verifiable by Bouncy Castle you can set this flag `ES256KSigner.outputFormat = .der`, it will transform the signatures in DER format.

With this in mind this library provides a functionality to enable verification of Nimbus/Bouncy Castle signatures, this can be enabled by setting the flag `ES256KVerifier.bouncyCastleFailSafe = true`. This process requires manipualtion of the internal signature, and reverses the R and S bytes, use it at your own risk since it can add security flaw.

Example:

```swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct EdDSASigner: Signer {
var algorithm: String { SigningAlgorithm.EdDSA.rawValue }
public struct EdDSASigner: Signer {
public var algorithm: String { SigningAlgorithm.EdDSA.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let d = key.d else { throw CryptoError.notValidPrivateKey }
let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: d)
return try privateKey.signature(for: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct EdDSAVerifier: Verifier {
var algorithm: String { SigningAlgorithm.EdDSA.rawValue }
public struct EdDSAVerifier: Verifier {
public var algorithm: String { SigningAlgorithm.EdDSA.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard
let x = key?.x
else { throw CryptoError.notValidPublicKey }
Expand Down
22 changes: 18 additions & 4 deletions Sources/JSONWebAlgorithms/Signatures/EC/Signers/ES256KSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,27 @@ import Foundation
import JSONWebKey
import secp256k1

struct ES256KSigner: Signer {
var algorithm: String { SigningAlgorithm.ES256K.rawValue }
public struct ES256KSigner: Signer {
public enum SignatureFormat {
case raw
case der
}

public static var outputFormat = ES256KSigner.SignatureFormat.raw

public var algorithm: String { SigningAlgorithm.ES256K.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let d = key.d else { throw CryptoError.notValidPrivateKey }
let privateKey = try secp256k1.Signing.PrivateKey(dataRepresentation: d)
let hash = SHA256.hash(data: data)
return try privateKey.signature(for: hash).dataRepresentation
let signature = try privateKey.signature(for: hash)

switch Self.outputFormat {
case .raw:
return signature.dataRepresentation
case .der:
return try signature.derRepresentation
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct ES256Signer: Signer {
var algorithm: String { SigningAlgorithm.ES256.rawValue }
public struct ES256Signer: Signer {
public var algorithm: String { SigningAlgorithm.ES256.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let d = key.d else { throw CryptoError.notValidPrivateKey }
let privateKey = try P256.Signing.PrivateKey(rawRepresentation: d)
let hash = SHA256.hash(data: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct ES384Signer: Signer {
var algorithm: String { SigningAlgorithm.ES384.rawValue }
public struct ES384Signer: Signer {
public var algorithm: String { SigningAlgorithm.ES384.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let d = key.d else { throw CryptoError.notValidPrivateKey }
let privateKey = try P384.Signing.PrivateKey(rawRepresentation: d)
let hash = SHA384.hash(data: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct ES512Signer: Signer {
var algorithm: String { SigningAlgorithm.ES512.rawValue }
public struct ES512Signer: Signer {
public var algorithm: String { SigningAlgorithm.ES512.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let d = key.d else { throw CryptoError.notValidPrivateKey }
let privateKey = try P521.Signing.PrivateKey(rawRepresentation: d)
let hash = SHA512.hash(data: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,74 @@ import Foundation
import JSONWebKey
import secp256k1

struct ES256KVerifier: Verifier {
var algorithm: String { SigningAlgorithm.ES256K.rawValue }
public struct ES256KVerifier: Verifier {
public static var bouncyCastleFailSafe = false

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public var algorithm: String { SigningAlgorithm.ES256K.rawValue }

public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard
let x = key?.x,
let y = key?.y
else { throw CryptoError.notValidPublicKey }
let publicKey = try secp256k1.Signing.PublicKey(dataRepresentation: [0x04] + x + y, format: .uncompressed)
let hash = SHA256.hash(data: data)
return try publicKey.isValidSignature(getSignature(signature), for: hash)
guard try publicKey.isValidSignature(getSignature(signature), for: hash) else {
guard ES256KVerifier.bouncyCastleFailSafe else {
return false
}
let bcSignature = transcodeSignatureToDERBitcoin(derEncodedSig: signature)
return try publicKey.isValidSignature(getSignature(bcSignature), for: hash)
}
return true
}

// This function helps transcode the signature from bouncy castle to bitcoin
private func transcodeSignatureToDERBitcoin(derEncodedSig: Data) -> Data {
// Helper to extract integer components from DER format
func extractInteger(from data: Data, at offset: inout Int) -> Data {
guard data[offset] == 0x02 else {
fatalError("Expected integer")
}
offset += 1 // Move past the 0x02

let length = Int(data[offset])
offset += 1 // Move past the length byte

let integerData = data[offset..<(offset + length)]
offset += length // Move past the integer data
return Data(integerData.reversed()) // Reverse the bytes
}

var offset = 0

// Verify initial DER sequence byte and length
guard derEncodedSig[offset] == 0x30 else {
fatalError("Invalid DER encoding")
}
offset += 1

let _ = Int(derEncodedSig[offset]) // Total length (not used)
offset += 1

// Extract and reverse R and S
let reversedR = extractInteger(from: derEncodedSig, at: &offset)
let reversedS = extractInteger(from: derEncodedSig, at: &offset)

// Re-encode to DER format
var derEncoded = Data([0x30]) // Start of DER sequence
let totalLength = reversedR.count + reversedS.count + 4 // Total length of the content
derEncoded.append(contentsOf: [UInt8(totalLength)])

// Append R
derEncoded.append(contentsOf: [0x02, UInt8(reversedR.count)])
derEncoded.append(reversedR)

// Append S
derEncoded.append(contentsOf: [0x02, UInt8(reversedS.count)])
derEncoded.append(reversedS)

return derEncoded
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct ES256Verifier: Verifier {
var algorithm: String { SigningAlgorithm.ES256.rawValue }
public struct ES256Verifier: Verifier {
public var algorithm: String { SigningAlgorithm.ES256.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard
let x = key?.x,
let y = key?.y
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct ES384Verifier: Verifier {
var algorithm: String { SigningAlgorithm.ES384.rawValue }
public struct ES384Verifier: Verifier {
public var algorithm: String { SigningAlgorithm.ES384.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard
let x = key?.x,
let y = key?.y
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct ES521Verifier: Verifier {
var algorithm: String { SigningAlgorithm.ES512.rawValue }
public struct ES521Verifier: Verifier {
public var algorithm: String { SigningAlgorithm.ES512.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard
let x = key?.x,
let y = key?.y
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct HS256Signer: Signer {
var algorithm: String { SigningAlgorithm.HS256.rawValue }
public struct HS256Signer: Signer {
public var algorithm: String { SigningAlgorithm.HS256.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let k = key.key else { throw CryptoError.notValidPrivateKey }
let symmetryKey = SymmetricKey(data: k)
return Data(HMAC<SHA256>.authenticationCode(for: data, using: symmetryKey))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct HS384Signer: Signer {
var algorithm: String { SigningAlgorithm.HS384.rawValue }
public struct HS384Signer: Signer {
public var algorithm: String { SigningAlgorithm.HS384.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let k = key.key else { throw CryptoError.notValidPrivateKey }
let symmetryKey = SymmetricKey(data: k)
return Data(HMAC<SHA384>.authenticationCode(for: data, using: symmetryKey))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct HS512Signer: Signer {
var algorithm: String { SigningAlgorithm.HS512.rawValue }
public struct HS512Signer: Signer {
public var algorithm: String { SigningAlgorithm.HS512.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard let k = key.key else { throw CryptoError.notValidPrivateKey }
let symmetryKey = SymmetricKey(data: k)
return Data(HMAC<SHA512>.authenticationCode(for: data, using: symmetryKey))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct HS256Verifier: Verifier {
var algorithm: String { SigningAlgorithm.HS256.rawValue }
public struct HS256Verifier: Verifier {
public var algorithm: String { SigningAlgorithm.HS256.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard let k = key?.key else { throw CryptoError.notValidPrivateKey }
let symmetryKey = SymmetricKey(data: k)
return HMAC<SHA256>.isValidAuthenticationCode(signature, authenticating: data, using: symmetryKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct HS384Verifier: Verifier {
var algorithm: String { SigningAlgorithm.HS384.rawValue }
public struct HS384Verifier: Verifier {
public var algorithm: String { SigningAlgorithm.HS384.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard let k = key?.key else { throw CryptoError.notValidPrivateKey }
let symmetryKey = SymmetricKey(data: k)
return HMAC<SHA384>.isValidAuthenticationCode(signature, authenticating: data, using: symmetryKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoKit
import Foundation
import JSONWebKey

struct HS512Verifier: Verifier {
var algorithm: String { SigningAlgorithm.HS512.rawValue }
public struct HS512Verifier: Verifier {
public var algorithm: String { SigningAlgorithm.HS512.rawValue }

func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
public func verify(data: Data, signature: Data, key: JWK?) throws -> Bool {
guard let k = key?.key else { throw CryptoError.notValidPrivateKey }
let symmetryKey = SymmetricKey(data: k)
return HMAC<SHA512>.isValidAuthenticationCode(signature, authenticating: data, using: symmetryKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoSwift
import Foundation
import JSONWebKey

struct PS256Signer: Signer {
var algorithm: String { SigningAlgorithm.PS256.rawValue }
public struct PS256Signer: Signer {
public var algorithm: String { SigningAlgorithm.PS256.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard
let n = key.n,
let e = key.e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoSwift
import Foundation
import JSONWebKey

struct PS384Signer: Signer {
var algorithm: String { SigningAlgorithm.PS384.rawValue }
public struct PS384Signer: Signer {
public var algorithm: String { SigningAlgorithm.PS384.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard
let n = key.n,
let e = key.e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoSwift
import Foundation
import JSONWebKey

struct PS512Signer: Signer {
var algorithm: String { SigningAlgorithm.PS512.rawValue }
public struct PS512Signer: Signer {
public var algorithm: String { SigningAlgorithm.PS512.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard
let n = key.n,
let e = key.e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoSwift
import Foundation
import JSONWebKey

struct RS256Signer: Signer {
var algorithm: String { SigningAlgorithm.RS256.rawValue }
public struct RS256Signer: Signer {
public var algorithm: String { SigningAlgorithm.RS256.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard
let n = key.n,
let e = key.e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import CryptoSwift
import Foundation
import JSONWebKey

struct RS384Signer: Signer {
var algorithm: String { SigningAlgorithm.RS384.rawValue }
public struct RS384Signer: Signer {
public var algorithm: String { SigningAlgorithm.RS384.rawValue }

func sign(data: Data, key: JWK) throws -> Data {
public func sign(data: Data, key: JWK) throws -> Data {
guard
let n = key.n,
let e = key.e
Expand Down
Loading
Loading