Skip to content

Commit

Permalink
feature(ES256K): add functionality to failsafe verification of signat…
Browse files Browse the repository at this point in the history
…ures from bouncy castle. (#12)

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.
  • Loading branch information
beatt83 authored Apr 14, 2024
1 parent 94bfa88 commit bc86d15
Show file tree
Hide file tree
Showing 30 changed files with 237 additions and 86 deletions.
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

0 comments on commit bc86d15

Please sign in to comment.