diff --git a/Package.swift b/Package.swift index 689e6c83..bdc9d6b1 100644 --- a/Package.swift +++ b/Package.swift @@ -84,7 +84,9 @@ let package = Package( .library(name: "CCryptoBoringSSL", type: .static, targets: ["CCryptoBoringSSL"]), MANGLE_END */ ], - dependencies: [], + dependencies: [ + .package(url: "https://github.com/apple/swift-asn1.git", .upToNextMajor(from: "1.2.0")) + ], targets: [ .target( name: "CCryptoBoringSSL", @@ -141,7 +143,8 @@ let package = Package( "CCryptoBoringSSL", "CCryptoBoringSSLShims", "CryptoBoringWrapper", - "Crypto" + "Crypto", + .product(name: "SwiftASN1", package: "swift-asn1") ], exclude: privacyManifestExclude + [ "CMakeLists.txt", diff --git a/Sources/_CryptoExtras/RSA/RSA.swift b/Sources/_CryptoExtras/RSA/RSA.swift index 00b98e86..03b1006d 100644 --- a/Sources/_CryptoExtras/RSA/RSA.swift +++ b/Sources/_CryptoExtras/RSA/RSA.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation import Crypto +import SwiftASN1 #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API fileprivate typealias BackingPublicKey = SecurityRSAPublicKey @@ -57,34 +58,34 @@ extension _RSA.Signing { /// /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate /// for their use-case. + /// Parameters from RSA PSS keys will be stripped. public init(pemRepresentation: String) throws { - self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) + let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes - guard self.keySizeInBits >= 2048 else { - throw CryptoKitError.incorrectParameterSize - } + try self.init(derRepresentation: derBytes) } /// Construct an RSA public key from a PEM representation. /// /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate /// for their use-case. + /// Parameters from RSA PSS keys will be stripped. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. public init(unsafePEMRepresentation pemRepresentation: String) throws { - self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) - - guard self.keySizeInBits >= 1024 else { - throw CryptoKitError.incorrectParameterSize - } + let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes + try self.init(unsafeDERRepresentation: derBytes) } /// 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. + /// Parameters from RSA PSS keys will be stripped. public init(derRepresentation: Bytes) throws { - self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + let sanitizedDer = try SubjectPublicKeyInfo.stripRsaPssParameters(derEncoded: [UInt8](derRepresentation)) + + self.backing = try BackingPublicKey(derRepresentation: sanitizedDer) guard self.keySizeInBits >= 2048 else { throw CryptoKitError.incorrectParameterSize @@ -95,9 +96,12 @@ extension _RSA.Signing { /// /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate /// for their use-case. + /// Parameters from RSA PSS keys will be stripped. /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. public init(unsafeDERRepresentation derRepresentation: Bytes) throws { - self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + let sanitizedDer = try SubjectPublicKeyInfo.stripRsaPssParameters(derEncoded: [UInt8](derRepresentation)) + + self.backing = try BackingPublicKey(derRepresentation: sanitizedDer) guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize diff --git a/Sources/_CryptoExtras/Util/SubjectPublicKeyInfo.swift b/Sources/_CryptoExtras/Util/SubjectPublicKeyInfo.swift new file mode 100644 index 00000000..4d4dc40c --- /dev/null +++ b/Sources/_CryptoExtras/Util/SubjectPublicKeyInfo.swift @@ -0,0 +1,116 @@ +// +// SubjectPublicKeyInfo.swift +// +// +// Created by Gautier Delorme on 24/09/2024. +// + +import SwiftASN1 + +struct SubjectPublicKeyInfo: DERImplicitlyTaggable, Hashable { + static var defaultIdentifier: ASN1Identifier { + .sequence + } + + var algorithmIdentifier: RFC5480AlgorithmIdentifier + + var key: ASN1BitString + + init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + // The SPKI block looks like this: + // + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING + // } + self = try DER.sequence(rootNode, identifier: identifier) { nodes in + let algorithmIdentifier = try RFC5480AlgorithmIdentifier(derEncoded: &nodes) + let key = try ASN1BitString(derEncoded: &nodes) + + return SubjectPublicKeyInfo(algorithmIdentifier: algorithmIdentifier, key: key) + } + } + + private init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: ASN1BitString) { + self.algorithmIdentifier = algorithmIdentifier + self.key = key + } + + internal init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: [UInt8]) { + self.algorithmIdentifier = algorithmIdentifier + self.key = ASN1BitString(bytes: key[...]) + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try coder.serialize(self.algorithmIdentifier) + try coder.serialize(self.key) + } + } +} + +struct RFC5480AlgorithmIdentifier: DERImplicitlyTaggable, Hashable { + static var defaultIdentifier: ASN1Identifier { + .sequence + } + + var algorithm: ASN1ObjectIdentifier + + var parameters: ASN1Any? + + init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any?) { + self.algorithm = algorithm + self.parameters = parameters + } + + init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + // The AlgorithmIdentifier block looks like this. + // + // AlgorithmIdentifier ::= SEQUENCE { + // algorithm OBJECT IDENTIFIER, + // parameters ANY DEFINED BY algorithm OPTIONAL + // } + // + // ECParameters ::= CHOICE { + // namedCurve OBJECT IDENTIFIER + // -- implicitCurve NULL + // -- specifiedCurve SpecifiedECDomain + // } + // + // We don't bother with helpers: we just try to decode it directly. + self = try DER.sequence(rootNode, identifier: identifier) { nodes in + let algorithmOID = try ASN1ObjectIdentifier(derEncoded: &nodes) + + let parameters = nodes.next().map { ASN1Any(derEncoded: $0) } + + return .init(algorithm: algorithmOID, parameters: parameters) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try coder.serialize(self.algorithm) + if let parameters = self.parameters { + try coder.serialize(parameters) + } + } + } +} + +extension SubjectPublicKeyInfo { + static func stripRsaPssParameters(derEncoded: [UInt8]) throws -> [UInt8] { + guard var spki = try? SubjectPublicKeyInfo(derEncoded: derEncoded) else { + // If it's not a SPKI then it can't be a PSS key so we just return it. + return derEncoded + } + + if spki.algorithmIdentifier.algorithm == .AlgorithmIdentifier.rsaPSS { + spki.algorithmIdentifier.parameters = try ASN1Any(erasing: ASN1Null()) + } + + var serializer = DER.Serializer() + try serializer.serialize(spki) + + return serializer.serializedBytes + } +} diff --git a/Tests/_CryptoExtrasTests/TestRSASigning.swift b/Tests/_CryptoExtrasTests/TestRSASigning.swift index 505e55ac..8ca810ee 100644 --- a/Tests/_CryptoExtrasTests/TestRSASigning.swift +++ b/Tests/_CryptoExtrasTests/TestRSASigning.swift @@ -17,6 +17,56 @@ import Crypto @testable import _CryptoExtras final class TestRSASigning: XCTestCase { + + func test_rsaPssParameters() throws { + let rsaPssPublicKeyPEM = """ + -----BEGIN PUBLIC KEY----- + MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB + CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAvcOaxSJoSiiXIQme6HEF + d0/QHjtk5+U1RbeejxeUR80Q1f8E5v7+uIBEFVbwZpIJZtmSB3bxbS31rOBGVcrI + IAfCnUlq6DK1fEL1fgn61XMiSSyKr75L5ZXv9Rib95h3lrNbhW0DUaXzf61kw3+Z + 4KV1btD7C+fdiLzPm18UQv8jJSbCE6hv3MWdkG3NcwgZC+iXwz3DFcsclyYg/+Om + 0hx8UJ/34vNpeE+0MHwyl0j/eO7izrzTZnfsm4ZRaU3mw0ORDQmo8MyIDFa55R/v + 30otk9y3LFkaeEyl1+7VFjJzoOEtze6VkTEzV8e/BTu4eXlKQ6CEYvHhUkNmHGC+ + mwIDAQAB + -----END PUBLIC KEY----- + """ + + let rsaPssPublicKeyDER = Data(base64Encoded: + "MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB" + + "CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAxPJvJDGPzb2rBWfE5JCB" + + "p2OAmR46zIbaVjIR1lUabKCdb5CxdnHvQBymp3AlvOGTNzSLxTXOaYn7MzeFvAVI" + + "mpRRzXzalG0ZfM4AkPBtjPz93pPLWEfgk+/i+JLWlWUStUGgGKNbJn4yJ8cJ8n+E" + + "/5+ry+tUYHEJm9A4/HwH4Agg78kPtnEvIvdC/aIw4TEpjZDewVNAEW2rBuQNd01r" + + "fAo2CSzbH76gL02mnLuvh1xyrKz+v9gyo9Taw273KU+83HPs91obgX4WpEfWOnd6" + + "LMJHRZo92FXnW6IHkCdz12khyS1TVIq4ONwjvmS6q3V9UwQg/uuyoSNnRfWXvZXQ" + + "aQIDAQAB" + )! + + let rsaPssPublicKey1024PEM = """ + -----BEGIN PUBLIC KEY----- + MIHPMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEI + MAsGCWCGSAFlAwQCAaIDAgEgA4GNADCBiQKBgQDGv67JltnwgkFxQOI8YUldC1LG + rCLOpyAN/Vq4WyLQ6TKcPevcYA8XmuXL8tC85rMQQG1GMwMWKcf/kf0NDKblUFjZ + BevUPmQF3Jadsn9ST+RMn8D+kq31Hdc0UG/WjZSpMHTkc8SWIjr2E6DIILn/OA/w + G3jVOeTsEfUeGExhVwIDAQAB + -----END PUBLIC KEY----- + """ + + let rsaPssPublicKey1024DER = Data(base64Encoded: + "MIHPMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEI" + + "MAsGCWCGSAFlAwQCAaIDAgEgA4GNADCBiQKBgQC7LZLbFhzOCoTmXEABRsyOkRiB" + + "18XkkJBwTkn2JES1jVZogXtcq5ZV+KmPulOrzLuaC45IliS5OZ1hJuC7m8/devXk" + + "HaNId+y2cZxRYnfNCsEzvTryxt+01VMQJA4VHsdmhJO6TEIUzDIfj3BlahZuoU11" + + "VZ4wgVIpYymQidJigQIDAQAB" + )! + + XCTAssertEqual(try _RSA.Signing.PublicKey(pemRepresentation: rsaPssPublicKeyPEM).keySizeInBits, 2048) + XCTAssertEqual(try _RSA.Signing.PublicKey(derRepresentation: rsaPssPublicKeyDER).keySizeInBits, 2048) + XCTAssertEqual(try _RSA.Signing.PublicKey(unsafePEMRepresentation: rsaPssPublicKey1024PEM).keySizeInBits, 1024) + XCTAssertEqual(try _RSA.Signing.PublicKey(unsafeDERRepresentation: rsaPssPublicKey1024DER).keySizeInBits, 1024) + } + func test_wycheproofPKCS1Vectors() throws { try wycheproofTest( jsonName: "rsa_signature_test",