From 89876ab72cd19c22ce8d4dca7b3360a9fba41fd0 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni <69189821+ptoffy@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:42:29 +0100 Subject: [PATCH] Update lower bound for RSA key sizes (#218) * Update lower bound for RSA key sizes * Add unsafe initialiser for smaller keys --------- Co-authored-by: Cory Benfield --- Sources/_CryptoExtras/RSA/RSA.swift | 137 ++++++++++++++++-- .../TestRSAEncryption.swift | 19 ++- Tests/_CryptoExtrasTests/TestRSASigning.swift | 21 ++- 3 files changed, 156 insertions(+), 21 deletions(-) diff --git a/Sources/_CryptoExtras/RSA/RSA.swift b/Sources/_CryptoExtras/RSA/RSA.swift index 8fe7e10c..fbcec8ca 100644 --- a/Sources/_CryptoExtras/RSA/RSA.swift +++ b/Sources/_CryptoExtras/RSA/RSA.swift @@ -49,23 +49,50 @@ extension _RSA.Signing { /// 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 + /// 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 { self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// 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. + /// - 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 } + } /// Construct an RSA public key from a DER representation. /// - /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// 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 { self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA public key from a DER representation. + /// + /// 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 { + self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize } @@ -103,11 +130,24 @@ extension _RSA.Signing { /// Construct an RSA private key from a PEM representation. /// - /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// 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 { self.backing = try BackingPrivateKey(pemRepresentation: pemRepresentation) + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// 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. + /// - 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 BackingPrivateKey(pemRepresentation: pemRepresentation) + guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize } @@ -115,11 +155,24 @@ extension _RSA.Signing { /// Construct an RSA private key from a DER representation. /// - /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// 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 { self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA public key from a DER representation. + /// + /// 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 { + self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + guard self.keySizeInBits >= 1024 else { throw CryptoKitError.incorrectParameterSize } @@ -127,9 +180,21 @@ extension _RSA.Signing { /// Randomly generate a new RSA private key of a given size. /// - /// This constructor will refuse to generate keys smaller than 1024 bits. Callers that want to enforce minimum + /// 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 { + guard keySize.bitCount >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + self.backing = try BackingPrivateKey(keySize: keySize) + } + + /// Randomly generate a new RSA private key of a given size. + /// + /// 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 { guard keySize.bitCount >= 1024 else { throw CryptoKitError.incorrectParameterSize } @@ -335,18 +400,38 @@ extension _RSA.Encryption { /// 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 + /// 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 { self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) - guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } + guard self.keySizeInBits >= 2048, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } + } + + /// 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. + /// - 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 >= 2048, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } } /// Construct an RSA public key from a DER representation. /// - /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// 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 { + self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + guard self.keySizeInBits >= 2048, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } + } + + /// Construct an RSA public key from a DER representation. + /// + /// 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 { self.backing = try BackingPublicKey(derRepresentation: derRepresentation) guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } } @@ -365,27 +450,57 @@ extension _RSA.Encryption { /// Construct an RSA private key from a PEM representation. /// - /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// 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 { + self.backing = try BackingPrivateKey(pemRepresentation: pemRepresentation) + guard self.keySizeInBits >= 2048, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } + } + + /// 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. + /// - 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 BackingPrivateKey(pemRepresentation: pemRepresentation) guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } } /// Construct an RSA private key from a DER representation. /// - /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// 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 { + self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + guard self.keySizeInBits >= 2048, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } + } + + /// Construct an RSA public key from a DER representation. + /// + /// 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 { self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize } } /// Randomly generate a new RSA private key of a given size. /// - /// This constructor will refuse to generate keys smaller than 1024 bits. Callers that want to enforce minimum + /// 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 { + guard keySize.bitCount >= 2048 else { throw CryptoKitError.incorrectParameterSize } + self.backing = try BackingPrivateKey(keySize: keySize) + } + + /// Randomly generate a new RSA private key of a given size. + /// + /// This constructor will refuse to generate keys smaller than 1024 bits. Callers that want to enforce minimum + /// key size requirements should validate `keySize` 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 { guard keySize.bitCount >= 1024 else { throw CryptoKitError.incorrectParameterSize } self.backing = try BackingPrivateKey(keySize: keySize) } diff --git a/Tests/_CryptoExtrasTests/TestRSAEncryption.swift b/Tests/_CryptoExtrasTests/TestRSAEncryption.swift index 3ff65ac2..84d63e57 100644 --- a/Tests/_CryptoExtrasTests/TestRSAEncryption.swift +++ b/Tests/_CryptoExtrasTests/TestRSAEncryption.swift @@ -20,19 +20,27 @@ final class TestRSAEncryption: XCTestCase { func test_wycheproofOAEPVectors() throws { try wycheproofTest( - jsonName: "rsa_oaep_2048_sha1_mgf1sha1_test", + jsonName: "rsa_oaep_misc_test", testFunction: self.testOAEPGroup) try wycheproofTest( - jsonName: "rsa_oaep_2048_sha256_mgf1sha256_test", + jsonName: "rsa_oaep_2048_sha1_mgf1sha1_test", testFunction: self.testOAEPGroup) try wycheproofTest( - jsonName: "rsa_oaep_misc_test", + jsonName: "rsa_oaep_2048_sha256_mgf1sha256_test", testFunction: self.testOAEPGroup) } private func testOAEPGroup(_ group: RSAEncryptionOAEPTestGroup) throws { - let derPrivKey = try _RSA.Encryption.PrivateKey(derRepresentation: group.privateKeyDerBytes) - let pemPrivKey = try _RSA.Encryption.PrivateKey(pemRepresentation: group.privateKeyPem) + let derPrivKey: _RSA.Encryption.PrivateKey + let pemPrivKey: _RSA.Encryption.PrivateKey + + if group.keysize < 2048 { + derPrivKey = try _RSA.Encryption.PrivateKey(unsafeDERRepresentation: group.privateKeyDerBytes) + pemPrivKey = try _RSA.Encryption.PrivateKey(unsafePEMRepresentation: group.privateKeyPem) + } else { + derPrivKey = try _RSA.Encryption.PrivateKey(derRepresentation: group.privateKeyDerBytes) + pemPrivKey = try _RSA.Encryption.PrivateKey(pemRepresentation: group.privateKeyPem) + } XCTAssertEqual(derPrivKey.derRepresentation, pemPrivKey.derRepresentation) XCTAssertEqual(derPrivKey.pemRepresentation, pemPrivKey.pemRepresentation) @@ -77,6 +85,7 @@ struct RSAEncryptionOAEPTestGroup: Codable { var sha: String var tests: [RSAEncryptionTest] var mgfSha: String + var keysize: Int var privateKeyDerBytes: Data { return try! Data(hexString: self.privateKeyPkcs8) diff --git a/Tests/_CryptoExtrasTests/TestRSASigning.swift b/Tests/_CryptoExtrasTests/TestRSASigning.swift index c89c8256..cc18b576 100644 --- a/Tests/_CryptoExtrasTests/TestRSASigning.swift +++ b/Tests/_CryptoExtrasTests/TestRSASigning.swift @@ -21,7 +21,7 @@ final class TestRSASigning: XCTestCase { try wycheproofTest( jsonName: "rsa_signature_test", testFunction: self.testPKCS1Group) - + try wycheproofTest( jsonName: "rsa_signature_2048_sha256_test", testFunction: self.testPKCS1Group) @@ -286,13 +286,15 @@ final class TestRSASigning: XCTestCase { (_RSA.Signing.PrivateKey(keySize: .bits2048), 2048), (_RSA.Signing.PrivateKey(keySize: .bits3072), 3072), (_RSA.Signing.PrivateKey(keySize: .bits4096), 4096), - (_RSA.Signing.PrivateKey(keySize: .init(bitCount: 1024)), 1024), + (_RSA.Signing.PrivateKey(unsafeKeySize: .init(bitCount: 1024)), 1024), ] - + for (key, size) in keysAndSizes { XCTAssertEqual(size, key.keySizeInBits) XCTAssertEqual(size, key.publicKey.keySizeInBits) } + + try XCTAssertThrowsError((_RSA.Signing.PrivateKey(keySize: .init(bitCount: 1024)), 1024)) } func testRejectSmallKeys() throws { @@ -676,8 +678,16 @@ final class TestRSASigning: XCTestCase { } private func testPKCS1Group(_ group: RSAPKCS1TestGroup) throws { - let derKey = try _RSA.Signing.PublicKey(derRepresentation: group.keyDerBytes) - let pemKey = try _RSA.Signing.PublicKey(pemRepresentation: group.keyPem) + let derKey: _RSA.Signing.PublicKey + let pemKey: _RSA.Signing.PublicKey + + if group.keysize < 2048 { + derKey = try _RSA.Signing.PublicKey(unsafeDERRepresentation: group.keyDerBytes) + pemKey = try _RSA.Signing.PublicKey(unsafePEMRepresentation: group.keyPem) + } else { + derKey = try _RSA.Signing.PublicKey(derRepresentation: group.keyDerBytes) + pemKey = try _RSA.Signing.PublicKey(pemRepresentation: group.keyPem) + } XCTAssertEqual(derKey.derRepresentation, pemKey.derRepresentation) XCTAssertEqual(derKey.pemRepresentation, pemKey.pemRepresentation) @@ -777,6 +787,7 @@ struct RSAPKCS1TestGroup: Codable { var keyPem: String var sha: String var tests: [RSATest] + var keysize: Int var keyDerBytes: Data { return try! Data(hexString: self.keyDer)