From dbe6e6088d967c5344766fa1b5c60fdd7634ab80 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Thu, 30 May 2024 11:04:21 +0100 Subject: [PATCH] Update API: bake params into key, use wrappers for intermidiate values --- .../_CryptoExtras/RSA/RSA+BlindSigning.swift | 121 +++++++++++------- .../_CryptoExtras/Util/BoringSSLHelpers.swift | 8 +- .../TestRSABlindSigning.swift | 63 +++++++-- 3 files changed, 134 insertions(+), 58 deletions(-) diff --git a/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift b/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift index 5f8e42b3..5f51fbb0 100644 --- a/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift +++ b/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift @@ -27,15 +27,19 @@ extension _RSA { } extension _RSA.BlindSigning { - public struct PublicKey: Sendable { + public struct PublicKey: Sendable where H: Sendable { + public typealias Parameters = _RSA.BlindSigning.Parameters + private var backing: BackingPublicKey + private let parameters: Parameters /// Construct an RSA public key from a PEM representation. /// /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate /// for their use-case. - public init(pemRepresentation: String) throws { + public init(pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 2048 else { throw CryptoKitError.incorrectParameterSize @@ -47,21 +51,22 @@ extension _RSA.BlindSigning { /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate /// for their use-case. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. - public init(unsafePEMRepresentation pemRepresentation: String) throws { + public init(unsafePEMRepresentation pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize } - } /// Construct an RSA public key from a DER representation. /// /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate /// for their use-case. - public init(derRepresentation: Bytes) throws { + public init(derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 2048 else { throw CryptoKitError.incorrectParameterSize @@ -73,8 +78,9 @@ extension _RSA.BlindSigning { /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate /// for their use-case. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. - public init(unsafeDERRepresentation derRepresentation: Bytes) throws { + public init(unsafeDERRepresentation derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize @@ -101,22 +107,27 @@ extension _RSA.BlindSigning { self.backing.keySizeInBits } - fileprivate init(_ backing: BackingPublicKey) { + fileprivate init(_ backing: BackingPublicKey, _ parameters: Parameters) { self.backing = backing + self.parameters = parameters } } } extension _RSA.BlindSigning { - public struct PrivateKey: Sendable { + public struct PrivateKey: Sendable where H: Sendable { + public typealias Parameters = _RSA.BlindSigning.Parameters + private var backing: BackingPrivateKey + private let parameters: Parameters /// Construct an RSA private key from a PEM representation. /// /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate /// for their use-case. - public init(pemRepresentation: String) throws { + public init(pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPrivateKey(pemRepresentation: pemRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 2048 else { throw CryptoKitError.incorrectParameterSize @@ -128,8 +139,9 @@ extension _RSA.BlindSigning { /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate /// for their use-case. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. - public init(unsafePEMRepresentation pemRepresentation: String) throws { + public init(unsafePEMRepresentation pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPrivateKey(pemRepresentation: pemRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize @@ -140,8 +152,9 @@ extension _RSA.BlindSigning { /// /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate /// for their use-case. - public init(derRepresentation: Bytes) throws { + public init(derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 2048 else { throw CryptoKitError.incorrectParameterSize @@ -153,8 +166,9 @@ extension _RSA.BlindSigning { /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate /// for their use-case. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. - public init(unsafeDERRepresentation derRepresentation: Bytes) throws { + public init(unsafeDERRepresentation derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + self.parameters = parameters guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize @@ -165,11 +179,12 @@ extension _RSA.BlindSigning { /// /// This constructor will refuse to generate keys smaller than 2048 bits. Callers that want to enforce minimum /// key size requirements should validate `keySize` before use. - public init(keySize: _RSA.Signing.KeySize) throws { + public init(keySize: _RSA.Signing.KeySize, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { guard keySize.bitCount >= 2048 else { throw CryptoKitError.incorrectParameterSize } self.backing = try BackingPrivateKey(keySize: keySize) + self.parameters = parameters } /// Randomly generate a new RSA private key of a given size. @@ -177,11 +192,12 @@ extension _RSA.BlindSigning { /// This constructor will refuse to generate keys smaller than 1024 bits. Callers that want to enforce minimum /// key size requirements should validate `unsafekeySize` before use. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. - public init(unsafeKeySize keySize: _RSA.Signing.KeySize) throws { + public init(unsafeKeySize keySize: _RSA.Signing.KeySize, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { guard keySize.bitCount >= 1024 else { throw CryptoKitError.incorrectParameterSize } self.backing = try BackingPrivateKey(keySize: keySize) + self.parameters = parameters } public var derRepresentation: Data { @@ -200,8 +216,8 @@ extension _RSA.BlindSigning { self.backing.keySizeInBits } - public var publicKey: _RSA.BlindSigning.PublicKey { - _RSA.BlindSigning.PublicKey(self.backing.publicKey) + public var publicKey: _RSA.BlindSigning.PublicKey { + _RSA.BlindSigning.PublicKey(self.backing.publicKey, self.parameters) } } } @@ -233,7 +249,7 @@ extension _RSA.BlindSigning { /// The RECOMMENDED variants are RSABSSA-SHA384-PSS-Randomized or RSABSSA-SHA384-PSSZERO-Randomized. /// /// - Seealso: [RFC 9474: RSABSSA Variants](https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants). - public struct Parameters { + public struct Parameters: Sendable where H: Sendable { enum Preparation { case identity, randomized } var padding: _RSA.Signing.Padding var preparation: Preparation @@ -293,6 +309,38 @@ extension _RSA.BlindSigning.Parameters where H == SHA384 { internal static let RSABSSA_SHA384_PSSZERO_Deterministic = Self(padding: .PSSZERO, preparation: .identity) } +extension _RSA.BlindSigning { + /// An input ready to be blinded, possibly prepended with random bytes. + /* public when ready */ struct PreparedMessage { + var rawRepresentation: Data + } +} + +extension _RSA.BlindSigning { + /// The blinding inverse for a blinded messaeg, used to unblind a blind signature. + /* public when ready */ struct BlindInverse { + var inverse: /* ArbitraryPrecisionInteger when SPI */ Any + + init() { fatalError("not yet implemented") } + } +} + +extension _RSA.BlindSigning { + /// An encoded, blinded message ready to be signed. + public struct BlindedMessage { + /// The raw representation of the key as a collection of contiguous bytes. + public var rawRepresentation: Data + + /// Creates a blinded message for signing from its representation in bytes. + /// + /// - Parameters: + /// - data: The bytes from which to create the blinded message. + public init(rawRepresentation: Data) { + self.rawRepresentation = rawRepresentation + } + } +} + extension _RSA.BlindSigning.PrivateKey { /// Generate a blind signature with the given key for a blinded message. /// @@ -301,9 +349,8 @@ extension _RSA.BlindSigning.PrivateKey { /// - Throws: If there is a failure producing the signature. /// /// - Seealso: [RFC 9474: BlindSign](https://www.rfc-editor.org/rfc/rfc9474.html#name-blindsign). - /// - TODO: Should this accept a new `BlindedMesage` type to help guide protocol usage? - public func blindSignature(for blindedMessage: D) throws -> _RSA.BlindSigning.BlindSignature { - try self.backing.blindSignature(blindedMessage) + public func blindSignature(for blindedMessage: _RSA.BlindSigning.BlindedMessage) throws -> _RSA.BlindSigning.BlindSignature { + try self.backing.blindSignature(blindedMessage.rawRepresentation) } } @@ -318,13 +365,12 @@ extension _RSA.BlindSigning { /// - Seealso: [RFC 9474: Prepare](https://www.rfc-editor.org/rfc/rfc9474.html#name-prepare). /// /// - TODO: Needs `SecureBytes` SPI from `Crypto`. - /// - TODO: Should this return a new `PreparedMessage` type to help guide protocol usage? /* public when ready */ static func prepare( _ message: D, parameters: _RSA.BlindSigning.Parameters = .RSABSSA_SHA384_PSS_Randomized - ) -> some DataProtocol { + ) -> _RSA.BlindSigning.PreparedMessage { switch parameters.preparation { - case .identity: return Data(message) + case .identity: return PreparedMessage(rawRepresentation: Data(message)) case .randomized: // return Data(SecureBytes(count: 32) + message) fatalError("not yet implemented") } @@ -339,14 +385,9 @@ extension _RSA.BlindSigning.PublicKey { /// - Returns: The blinded message, and its inverse for unblinding its blind signature. /// /// - Seealso: [RFC 9474: Blind](https://www.rfc-editor.org/rfc/rfc9474.html#name-blind). - /// - /// - TODO: Needs `ArbitraryPrecisionInteger` SPI from `Crypto`. - /// - TODO: Should this accept a new `PreparedMessage` type to help guide protocol usage? - /// - TODO: Should this return a new `BlindedMessage` type to help guide protocol usage? - /* public when ready */ func blind( - _ message: D, - parameters: _RSA.BlindSigning.Parameters = .RSABSSA_SHA384_PSS_Randomized - ) throws -> (blindedMessage: /* some DataProtocol */ Any, blindInverse: /* ArbitraryPrecisionInteger when available */ Any) { + /* public when ready */ func blind( + _ preparedMessage: _RSA.BlindSigning.PreparedMessage + ) throws -> (blindedMessage: _RSA.BlindSigning.BlindedMessage, blindInverse: _RSA.BlindSigning.BlindInverse) { fatalError("not yet implemented") } @@ -359,14 +400,10 @@ extension _RSA.BlindSigning.PublicKey { /// - Returns: The signature of the message. /// /// - Seealso: [RFC 9474: Finalize](https://www.rfc-editor.org/rfc/rfc9474.html#name-finalize). - /// - /// - TODO: Needs `ArbitraryPrecisionInteger` SPI from `Crypto`. - /// - TODO: Should this accept a new `PreparedMessage` type to help guide protocol usage? - /* public when ready */ func finalize( + /* public when ready */ func finalize( _ blindSignature: _RSA.BlindSigning.BlindSignature, - for message: D, - blindInverse: /* ArbitraryPrecisionInteger when available */ Any, - parameters: _RSA.BlindSigning.Parameters = .RSABSSA_SHA384_PSS_Randomized + for preparedMessage: _RSA.BlindSigning.PreparedMessage, + blindInverse: _RSA.BlindSigning.BlindInverse ) throws -> _RSA.Signing.RSASignature { fatalError("not yet implemented") } @@ -382,12 +419,10 @@ extension _RSA.BlindSigning.PublicKey { /// /// - TODO: Needs `ArbitraryPrecisionInteger` SPI from `Crypto`. /// - TODO: Should this accept a new `PreparedMessage` type to help guide protocol usage? - public func isValidSignature( + public func isValidSignature( _ signature: _RSA.Signing.RSASignature, - for message: D, - parameters: _RSA.BlindSigning.Parameters = .RSABSSA_SHA384_PSS_Randomized + for message: D ) -> Bool { - let digest = H.hash(data: message) - return self.backing.isValidSignature(signature, for: digest, padding: parameters.padding) + self.backing.isValidSignature(signature, for: H.hash(data: message), padding: parameters.padding) } } diff --git a/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift b/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift index 5a37fa89..3a925a92 100644 --- a/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift +++ b/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift @@ -182,7 +182,7 @@ extension _RSA.BlindSigning.PublicKey { /// /// Only the BoringSSL backend provides APIs to create the key from its parameters so we first create a BoringSSL /// key, serialize it to PEM format, and then construct a platform specific key from the PEM representation. - internal init(nHexString: String, eHexString: String) throws { + internal init(nHexString: String, eHexString: String, parameters: Parameters) throws { var n = try BIGNUM(hexString: nHexString) defer { CCryptoBoringSSL_BN_clear_free(&n) } var e = try BIGNUM(hexString: eHexString) @@ -199,7 +199,7 @@ extension _RSA.BlindSigning.PublicKey { } // Create a key (which might be backed by Security framework) from PEM representation. - try self.init(pemRepresentation: pemRepresentation) + try self.init(pemRepresentation: pemRepresentation, parameters: parameters) } } @@ -211,7 +211,7 @@ extension _RSA.BlindSigning.PrivateKey { /// /// Only the BoringSSL backend provides APIs to create the key from its parameters so we first create a BoringSSL /// key, serialize it to PEM format, and then construct a platform specific key from the PEM representation. - internal init(nHexString: String, eHexString: String, dHexString: String, pHexString: String, qHexString: String) throws { + internal init(nHexString: String, eHexString: String, dHexString: String, pHexString: String, qHexString: String, parameters: Parameters) throws { var n = try BIGNUM(hexString: nHexString) defer { CCryptoBoringSSL_BN_clear_free(&n) } var e = try BIGNUM(hexString: eHexString) @@ -244,6 +244,6 @@ extension _RSA.BlindSigning.PrivateKey { } // Create a key (which might be backed by Security framework) from PEM representation. - try self.init(pemRepresentation: pemRepresentation) + try self.init(pemRepresentation: pemRepresentation, parameters: parameters) } } diff --git a/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift b/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift index 216bf76b..4d8bd1ea 100644 --- a/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift +++ b/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift @@ -20,10 +20,20 @@ fileprivate struct TestVector: Codable { var name, p, q, n, e, d, msg, msg_prefix, prepared_msg, salt, inv, blinded_msg, blind_sig, sig: String var parameters: _RSA.BlindSigning.Parameters { - .init( - padding: self.salt.count == 0 ? .PSSZERO : .PSS, - preparation: self.msg == self.prepared_msg ? .identity : .randomized - ) + let messagePaddingByteCount = (self.prepared_msg.count - self.msg.count) / 2 + let saltByteCount = self.salt.count / 2 + switch (saltByteCount, messagePaddingByteCount) { + case (0, 0): + return .RSABSSA_SHA384_PSSZERO_Deterministic + case (0, 32): + return .RSABSSA_SHA384_PSSZERO_Randomized + case (SHA384.byteCount, 0): + return .RSABSSA_SHA384_PSS_Deterministic + case (SHA384.byteCount, 32): + return .RSABSSA_SHA384_PSS_Randomized + default: + fatalError("Unsupported test vector; salt length: \(saltByteCount); message padding: \(messagePaddingByteCount).") + } } static func load(from fileURL: URL) throws -> [Self] { @@ -54,11 +64,11 @@ final class TestRSABlindSigning: XCTestCase { switch testVector.parameters.preparation { case .identity: let preparedMessage = _RSA.BlindSigning.prepare(message, parameters: testVector.parameters) - XCTAssertEqual(Data(preparedMessage), message) + XCTAssertEqual(preparedMessage.rawRepresentation, message) case .randomized: break // Until we have SPI secure bytes. let preparedMessage = _RSA.BlindSigning.prepare(message, parameters: testVector.parameters) - XCTAssertEqual(Data(preparedMessage.dropFirst(32)), message) + XCTAssertEqual(preparedMessage.rawRepresentation.dropFirst(32), message) } } @@ -73,9 +83,10 @@ final class TestRSABlindSigning: XCTestCase { eHexString: testVector.e, dHexString: testVector.d, pHexString: testVector.p, - qHexString: testVector.q + qHexString: testVector.q, + parameters: testVector.parameters ) - let blindedMessage = try Data(hexString: testVector.blinded_msg) + let blindedMessage = try _RSA.BlindSigning.BlindedMessage(rawRepresentation: Data(hexString: testVector.blinded_msg)) let blindSignature = try privateKey.blindSignature(for: blindedMessage) XCTAssertEqual( blindSignature.rawRepresentation.hexString, @@ -89,11 +100,41 @@ final class TestRSABlindSigning: XCTestCase { // Verification do { - let publicKey = try _RSA.BlindSigning.PublicKey(nHexString: testVector.n, eHexString: testVector.e) + let publicKey = try _RSA.BlindSigning.PublicKey(nHexString: testVector.n, eHexString: testVector.e, parameters: testVector.parameters) let signature = try _RSA.Signing.RSASignature(rawRepresentation: Data(hexString: testVector.sig)) - let preparedMessage = try Data(hexString: testVector.prepared_msg) - XCTAssert(publicKey.isValidSignature(signature, for: preparedMessage, parameters: testVector.parameters)) + let preparedMessage = try _RSA.BlindSigning.PreparedMessage(rawRepresentation: Data(hexString: testVector.prepared_msg)) + XCTAssert(publicKey.isValidSignature(signature, for: preparedMessage.rawRepresentation)) } } } + + func testEndToEndAPIUsage() throws { + try XCTSkipIf(true, "Until the client operations are implemented, this is just here to check the types compose") + + // 1. [Issuer] Create private key (other initializers are available). + let privateKeyPEM = "This will not work, just here to test the API." + let privateKey = try _RSA.BlindSigning.PrivateKey(pemRepresentation: privateKeyPEM, parameters: .RSABSSA_SHA384_PSS_Randomized) + + // 2. [Client] Create public key (other initializers are available). + let publicKeyPEM = "This will not work, just here to test the API." + let publicKey = try _RSA.BlindSigning.PublicKey(pemRepresentation: publicKeyPEM, parameters: .RSABSSA_SHA384_PSS_Randomized) + + // 3. [Client] Have a message they wish to use. + let message = Data("This is some input data".utf8) + + // 4. [Client] Prepare the message. + let preparedMessage = _RSA.BlindSigning.prepare(message, parameters: .RSABSSA_SHA384_PSS_Randomized) + + // 5. [Client] Blind the message. + let (blindedMessage, blindInverse) = try publicKey.blind(preparedMessage) + + // 6. [Issuer] Blind sign. + let blindSignature = try privateKey.blindSignature(for: blindedMessage) + + // 7. [Client] Finalize. + let unblindedSignature = try publicKey.finalize(blindSignature, for: preparedMessage, blindInverse: blindInverse) + + // 8. [Verifier] Verify. + _ = publicKey.isValidSignature(unblindedSignature, for: preparedMessage.rawRepresentation) + } }