From 1bc7de770de063c3c03b7232f91b7440388000f6 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Sun, 29 Dec 2024 23:57:09 +0100 Subject: [PATCH 1/6] Start adding support for encrypted PEM keys --- Sources/_CryptoExtras/RSA/RSA.swift | 4 + Sources/_CryptoExtras/RSA/RSA_boring.swift | 2 +- Sources/_CryptoExtras/RSA/RSA_security.swift | 32 +- .../Util/EncryptedPEMDocument.swift | 284 ++++++++++++++++++ Tests/_CryptoExtrasTests/TestRSASigning.swift | 51 +++- 5 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift diff --git a/Sources/_CryptoExtras/RSA/RSA.swift b/Sources/_CryptoExtras/RSA/RSA.swift index a7d5652c0..3877f64c0 100644 --- a/Sources/_CryptoExtras/RSA/RSA.swift +++ b/Sources/_CryptoExtras/RSA/RSA.swift @@ -179,6 +179,10 @@ extension _RSA.Signing { throw CryptoKitError.incorrectParameterSize } } + + public init(encryptedPEMRepresentation: String, encryptionPassword: String) throws { + self.backing = try BackingPrivateKey(encryptedPEMRepresentation: encryptedPEMRepresentation, encryptionPassword: encryptionPassword) + } /// Construct an RSA private key from a DER representation. /// diff --git a/Sources/_CryptoExtras/RSA/RSA_boring.swift b/Sources/_CryptoExtras/RSA/RSA_boring.swift index 1baf9b400..9fb0306a2 100644 --- a/Sources/_CryptoExtras/RSA/RSA_boring.swift +++ b/Sources/_CryptoExtras/RSA/RSA_boring.swift @@ -63,7 +63,7 @@ internal struct BoringSSLRSAPublicKey: Sendable { } func getKeyPrimitives() throws -> (n: Data, e: Data) { - try self.backing.getKeyPrimitives() + self.backing.getKeyPrimitives() } } diff --git a/Sources/_CryptoExtras/RSA/RSA_security.swift b/Sources/_CryptoExtras/RSA/RSA_security.swift index 114012de6..705c633e5 100644 --- a/Sources/_CryptoExtras/RSA/RSA_security.swift +++ b/Sources/_CryptoExtras/RSA/RSA_security.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation import Crypto +import SwiftASN1 #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API @_implementationOnly import Security @@ -97,7 +98,24 @@ internal struct SecurityRSAPrivateKey: @unchecked Sendable { default: throw _CryptoRSAError.invalidPEMDocument } - + } + + init(encryptedPEMRepresentation: String, encryptionPassword: String) throws { + let document = try EncryptedPEMDocument(pemEncoded: encryptedPEMRepresentation) + let pem = try document.decrypt(withPassword: encryptionPassword) + + switch pem.discriminator { + case _RSA.PKCS1KeyType: + // This is what is expected by Security.framework + self = try .init(derRepresentation: pem.derBytes) + case _RSA.PKCS8KeyType: + guard let pkcs8Bytes = pem.derBytes.pkcs8RSAKeyBytes else { + throw _CryptoRSAError.invalidPEMDocument + } + self = try .init(derRepresentation: pkcs8Bytes) + default: + throw _CryptoRSAError.invalidPEMDocument + } } init(derRepresentation: Bytes) throws { @@ -154,7 +172,7 @@ internal struct SecurityRSAPrivateKey: @unchecked Sendable { } var pemRepresentation: String { - return ASN1.PEMDocument(type: _RSA.PKCS1KeyType, derBytes: self.derRepresentation).pemString + return SwiftASN1.PEMDocument(type: _RSA.PKCS1KeyType, derBytes: [UInt8](self.derRepresentation)).pemString } var pkcs8PEMRepresentation: String { @@ -457,6 +475,16 @@ extension Int { } } +extension [UInt8] { + var pkcs8RSAKeyBytes: [UInt8]? { + let bytes = Data(self).pkcs8RSAKeyBytes + guard let bytes else { + return nil + } + return [UInt8](bytes) + } +} + extension UInt { // Bytes needed to store a given integer in 7 bit bytes. fileprivate var neededBytes: Int { diff --git a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift new file mode 100644 index 000000000..cfc18bc06 --- /dev/null +++ b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift @@ -0,0 +1,284 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Crypto +import SwiftASN1 +import Foundation + +// EncryptedPrivateKeyInfo ::= SEQUENCE { +// encryptionAlgorithm EncryptionAlgorithmIdentifier, +// encryptedData EncryptedData +// } +// +// EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier +// +// EncryptedData ::= OCTET STRING +struct EncryptedPEMDocument: PEMRepresentable { + let algorithmIdentifier: RFC5480AlgorithmIdentifier + let encryptedData: ASN1OctetString + + init(algorithmIdentifier: RFC5480AlgorithmIdentifier, encryptedData: ASN1OctetString) { + self.algorithmIdentifier = algorithmIdentifier + self.encryptedData = encryptedData + } + + static var defaultPEMDiscriminator: String { + "ENCRYPTED PRIVATE KEY" + } + + init(derEncoded node: ASN1Node) throws { + self = try DER.sequence(node, identifier: .sequence) { nodes in + let algorithmIdentifier = try RFC5480AlgorithmIdentifier(derEncoded: &nodes) + let encryptedData = try ASN1OctetString(derEncoded: &nodes) + + return .init(algorithmIdentifier: algorithmIdentifier, encryptedData: encryptedData) + } + } + + func serialize(into coder: inout SwiftASN1.DER.Serializer) throws { + try coder.appendConstructedNode(identifier: .sequence) { coder in + try self.algorithmIdentifier.serialize(into: &coder) + try self.encryptedData.serialize(into: &coder) + } + } + + func decrypt(withPassword password: String) throws -> PEMDocument { + let algorithm = self.algorithmIdentifier.algorithm + + guard let parameters = self.algorithmIdentifier.parameters else { + throw _CryptoRSAError.invalidPEMDocument + } + + switch algorithm { + case .pkcs5PBES2: + let pbes2Params = try PBES2Parameters(asn1Any: parameters) + let pbkdf2Params = try PBKDF2Parameters(asn1Any: pbes2Params.keyDerivationFunction.parameters) + + let hashFunction = pbkdf2Params.hashFunction + + let derivedKey = try KDF.Insecure.PBKDF2.deriveKey( + from: [UInt8](password.utf8), + salt: pbkdf2Params.salt.bytes, + using: .from(objectIdentifier: hashFunction.objectIdentifer)!, + outputByteCount: pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes.count, + unsafeUncheckedRounds: pbkdf2Params.iterationCount as! Int + ) + + let decryption: Data? = switch pbes2Params.encryptionScheme.encryptionAlgorithm { + case .aes128_CBC, .aes192_CBC, .aes256_CBC: + try AES._CBC.decrypt( + encryptedData.bytes, + using: derivedKey, + iv: .init(ivBytes: pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes) + ) + case .des_EDE3_CBC: // We don't support 3DES, will have to call through to BoringSSL + nil + default: nil + } + + return PEMDocument(type: "PRIVATE KEY", derBytes: [UInt8](decryption!)) + default: + break + } + + return try PEMDocument(pemString: "") + } +} + +extension ASN1ObjectIdentifier { + static let pkcs5PBES2 = ASN1ObjectIdentifier("1.2.840.113549.1.5.13") + static let pkcs5PBKDF2 = ASN1ObjectIdentifier("1.2.840.113549.1.5.12") + static let pkcs5PBE_MD5_DES_CBC = ASN1ObjectIdentifier("1.2.840.113549.1.5.3") + static let pkcs5PBE_MD5_RC2_CBC = ASN1ObjectIdentifier("1.2.840.113549.1.5.6") + static let pkcs5PBE_SHA1_DES_CBC = ASN1ObjectIdentifier("1.2.840.113549.1.5.10") + static let pkcs5PBE_SHA1_RC2_CBC = ASN1ObjectIdentifier("1.2.840.113549.1.5.11") + + static let pkcs5Scrypt = ASN1ObjectIdentifier("1.3.6.1.4.1.11591.4.11") +} + +// Encryption schemes +extension ASN1ObjectIdentifier { + static let aes128_CBC = ASN1ObjectIdentifier("2.16.840.1.101.3.4.1.2") + static let aes192_CBC = ASN1ObjectIdentifier("2.16.840.1.101.3.4.1.22") + static let aes256_CBC = ASN1ObjectIdentifier("2.16.840.1.101.3.4.1.42") + static let des_EDE3_CBC = ASN1ObjectIdentifier("1.2.840.113549.3.7") +} + +extension KDF.Insecure.PBKDF2.HashFunction { + static func from(objectIdentifier: ASN1ObjectIdentifier) -> Self? { + switch objectIdentifier.oidComponents { + case [2, 16, 840, 1, 101, 3, 4, 2, 1], + [1, 2, 840, 113549, 2, 9]: // hmacWithSHA256 + .sha256 + case [2, 16, 840, 1, 101, 3, 4, 2, 2]: + .sha384 + case [2, 16, 840, 1, 101, 3, 4, 2, 3]: + .sha512 + default: nil + } + } +} + +struct PBES2Parameters: DERImplicitlyTaggable { + static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } + + let keyDerivationFunction: KeyDerivationFunction + let encryptionScheme: EncryptionScheme + + init(keyDerivationFunction: KeyDerivationFunction, encryptionScheme: EncryptionScheme) { + self.keyDerivationFunction = keyDerivationFunction + self.encryptionScheme = encryptionScheme + } + + init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let keyDerivationFunction = try KeyDerivationFunction(derEncoded: &nodes) + let encryptionScheme = try EncryptionScheme(derEncoded: &nodes) + + return .init(keyDerivationFunction: keyDerivationFunction, encryptionScheme: encryptionScheme) + } + } + + func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.keyDerivationFunction.serialize(into: &coder) + try self.encryptionScheme.serialize(into: &coder) + } + } +} + +struct KeyDerivationFunction: DERImplicitlyTaggable { + static var defaultIdentifier: ASN1Identifier { .sequence } + + let algorithm: ASN1ObjectIdentifier + let parameters: ASN1Any + + init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any) { + self.algorithm = algorithm + self.parameters = parameters + } + + init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let algorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) + let parameters = try ASN1Any(derEncoded: &nodes) + + return .init(algorithm: algorithm, parameters: parameters) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.algorithm.serialize(into: &coder) + try self.parameters.serialize(into: &coder) + } + } +} + +struct EncryptionScheme: DERImplicitlyTaggable { + static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } + + let encryptionAlgorithm: ASN1ObjectIdentifier + let encryptionAlgorithmParameters: ASN1OctetString + + init(encryptionAlgorithm: ASN1ObjectIdentifier, encryptionAlgorithmParameters: ASN1OctetString) { + self.encryptionAlgorithm = encryptionAlgorithm + self.encryptionAlgorithmParameters = encryptionAlgorithmParameters + } + + init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let encryptionAlgorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) + let encryptionAlgorithmParameters = try ASN1OctetString(derEncoded: &nodes) + + return .init(encryptionAlgorithm: encryptionAlgorithm, encryptionAlgorithmParameters: encryptionAlgorithmParameters) + } + } + + func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.encryptionAlgorithm.serialize(into: &coder) + try self.encryptionAlgorithmParameters.serialize(into: &coder) + } + } +} + +// PBKDF2-params ::= SEQUENCE { +// salt CHOICE { +// specified OCTET STRING, +// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}} +// }, +// iterationCount INTEGER (1..MAX), +// keyLength INTEGER (1..MAX) OPTIONAL, +// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 +// } +struct PBKDF2Parameters: DERImplicitlyTaggable { + struct HashFunction: DERImplicitlyTaggable { + static var defaultIdentifier: ASN1Identifier { .sequence } + + let objectIdentifer: ASN1ObjectIdentifier + let null: ASN1Null + + init(objectIdentifer: ASN1ObjectIdentifier, null: ASN1Null) { + self.objectIdentifer = objectIdentifer + self.null = null + } + + init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let objectIdentifer = try ASN1ObjectIdentifier(derEncoded: &nodes) + let null = try ASN1Null(derEncoded: &nodes) + + return .init(objectIdentifer: objectIdentifer, null: null) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.objectIdentifer.serialize(into: &coder) + try self.null.serialize(into: &coder) + } + } + } + + static var defaultIdentifier: ASN1Identifier { .sequence } + + let salt: ASN1OctetString + let iterationCount: any ASN1IntegerRepresentable + let hashFunction: HashFunction + + init(salt: ASN1OctetString, iterationCount: any ASN1IntegerRepresentable, hashFunction: HashFunction) { + self.salt = salt + self.iterationCount = iterationCount + self.hashFunction = hashFunction + } + + init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let salt = try ASN1OctetString(derEncoded: &nodes) + let iterationCount = try Int(derEncoded: &nodes) + let hashFunction = try HashFunction(derEncoded: &nodes) + + return .init(salt: salt, iterationCount: iterationCount, hashFunction: hashFunction) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.salt.serialize(into: &coder) + try self.iterationCount.serialize(into: &coder) + try self.hashFunction.serialize(into: &coder) + } + } +} diff --git a/Tests/_CryptoExtrasTests/TestRSASigning.swift b/Tests/_CryptoExtrasTests/TestRSASigning.swift index 07da7c127..ac3da0d22 100644 --- a/Tests/_CryptoExtrasTests/TestRSASigning.swift +++ b/Tests/_CryptoExtrasTests/TestRSASigning.swift @@ -730,12 +730,61 @@ final class TestRSASigning: XCTestCase { K8TpFCFPBP/Yv1Kngovn4O1MskoxTQraRBDjfC6O7OfcSCSMuVgB0Oofcp9iQjLA HV3KOnbYqvzmFv7OWnAszkTh -----END PRIVATE KEY----- - """ let key = try _RSA.Signing.PrivateKey(pemRepresentation: pemKey) XCTAssertEqual(pemKey, key.pkcs8PEMRepresentation) } + + func testParsingEncryptedRSAKey() throws { + let encryptedPEMKey = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + 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 + -----END ENCRYPTED PRIVATE KEY----- + """ + + XCTAssertNoThrow(try _RSA.Signing.PrivateKey(encryptedPEMRepresentation: encryptedPEMKey, encryptionPassword: "foobar")) + XCTAssertThrowsError(try _RSA.Signing.PrivateKey(encryptedPEMRepresentation: encryptedPEMKey, encryptionPassword: "wrong")) + } func testConstructKeyFromRSANumbers() throws { /// Check we can successfully construct keys from known valid values from a test vector. From 167cce47ebfb78b5443367ba0e467b0687f67e3c Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Mon, 30 Dec 2024 00:19:38 +0100 Subject: [PATCH 2/6] Rollback wrong change --- Sources/_CryptoExtras/RSA/RSA_security.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/_CryptoExtras/RSA/RSA_security.swift b/Sources/_CryptoExtras/RSA/RSA_security.swift index 705c633e5..3d86285cd 100644 --- a/Sources/_CryptoExtras/RSA/RSA_security.swift +++ b/Sources/_CryptoExtras/RSA/RSA_security.swift @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import Foundation import Crypto -import SwiftASN1 #if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API @_implementationOnly import Security @@ -172,7 +171,7 @@ internal struct SecurityRSAPrivateKey: @unchecked Sendable { } var pemRepresentation: String { - return SwiftASN1.PEMDocument(type: _RSA.PKCS1KeyType, derBytes: [UInt8](self.derRepresentation)).pemString + return ASN1.PEMDocument(type: _RSA.PKCS1KeyType, derBytes: self.derRepresentation).pemString } var pkcs8PEMRepresentation: String { From fe1a02dc7248bc60247780ae63759cd8624e504c Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Mon, 30 Dec 2024 16:25:50 +0100 Subject: [PATCH 3/6] Add PBES2 with 3DES encryption PEM support --- Sources/_CryptoExtras/AES/AES_CBC.swift | 4 +- .../Util/EncryptedPEMDocument.swift | 68 ++++++++++- .../EncryptedPEMTests.swift | 113 ++++++++++++++++++ Tests/_CryptoExtrasTests/TestRSASigning.swift | 50 -------- 4 files changed, 180 insertions(+), 55 deletions(-) create mode 100644 Tests/_CryptoExtrasTests/EncryptedPEMTests.swift diff --git a/Sources/_CryptoExtras/AES/AES_CBC.swift b/Sources/_CryptoExtras/AES/AES_CBC.swift index 3363ddbd5..2bebccca8 100644 --- a/Sources/_CryptoExtras/AES/AES_CBC.swift +++ b/Sources/_CryptoExtras/AES/AES_CBC.swift @@ -134,7 +134,7 @@ extension AES { } if !noPadding { - try plaintext.trimPadding() + try plaintext.trimCBCPadding() } return plaintext } @@ -193,7 +193,7 @@ extension AES._CBC { } extension Data { - fileprivate mutating func trimPadding() throws { + mutating func trimCBCPadding() throws { guard let paddingBytes = self.last else { // Degenerate case, empty string. This is forbidden: // we must always pad. diff --git a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift index cfc18bc06..8d31177c3 100644 --- a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift +++ b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift @@ -15,6 +15,7 @@ import Crypto import SwiftASN1 import Foundation +@_implementationOnly import CCryptoBoringSSL // EncryptedPrivateKeyInfo ::= SEQUENCE { // encryptionAlgorithm EncryptionAlgorithmIdentifier, @@ -71,7 +72,7 @@ struct EncryptedPEMDocument: PEMRepresentable { from: [UInt8](password.utf8), salt: pbkdf2Params.salt.bytes, using: .from(objectIdentifier: hashFunction.objectIdentifer)!, - outputByteCount: pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes.count, + outputByteCount: pbes2Params.encryptionScheme.encryptionAlgorithm.encryptionAlgorithmKeyLength, unsafeUncheckedRounds: pbkdf2Params.iterationCount as! Int ) @@ -82,8 +83,57 @@ struct EncryptedPEMDocument: PEMRepresentable { using: derivedKey, iv: .init(ivBytes: pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes) ) - case .des_EDE3_CBC: // We don't support 3DES, will have to call through to BoringSSL - nil + case .des_EDE3_CBC: + try encryptedData.bytes.withUnsafeBufferPointer { encryptedPtr in + func toDESBlock(_ bytes: UnsafeBufferPointer, paddedBy padding: Int = 0) throws -> DES_cblock { + guard let baseAddress = bytes.baseAddress else { + throw _CryptoRSAError.invalidPEMDocument + } + + let bytes = baseAddress.advanced(by: padding) + return DES_cblock(bytes: ( + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7] + )) + } + + var output = [UInt8](repeating: 0, count: encryptedData.bytes.count) + + var ks1 = DES_key_schedule(), ks2 = DES_key_schedule(), ks3 = DES_key_schedule() + try derivedKey.withUnsafeBytes { keyPtr in + guard keyPtr.count >= 24 else { throw _CryptoRSAError.invalidPEMDocument } + + let keyBytes = keyPtr.bindMemory(to: UInt8.self) + + var key1 = try toDESBlock(keyBytes) + var key2 = try toDESBlock(keyBytes, paddedBy: 8) + var key3 = try toDESBlock(keyBytes, paddedBy: 16) + + CCryptoBoringSSL_DES_set_key_unchecked(&key1, &ks1) + CCryptoBoringSSL_DES_set_key_unchecked(&key2, &ks2) + CCryptoBoringSSL_DES_set_key_unchecked(&key3, &ks3) + } + + var iv = try pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes.withUnsafeBytes { ivPtr -> DES_cblock in + let ivBytes = ivPtr.bindMemory(to: UInt8.self) + return try toDESBlock(ivBytes) + } + + CCryptoBoringSSL_DES_ede3_cbc_encrypt( + encryptedPtr.baseAddress!, + &output, + encryptedPtr.count, + &ks1, + &ks2, + &ks3, + &iv, + 0 + ) + + var result = Data(output) + try result.trimCBCPadding() + return result + } default: nil } @@ -115,6 +165,18 @@ extension ASN1ObjectIdentifier { static let des_EDE3_CBC = ASN1ObjectIdentifier("1.2.840.113549.3.7") } +extension ASN1ObjectIdentifier { + var encryptionAlgorithmKeyLength: Int { + switch self { + case .aes128_CBC: 16 + case .aes192_CBC: 24 + case .aes256_CBC: 32 + case .des_EDE3_CBC: 24 + default: fatalError("Not an encryption algorithm") + } + } +} + extension KDF.Insecure.PBKDF2.HashFunction { static func from(objectIdentifier: ASN1ObjectIdentifier) -> Self? { switch objectIdentifier.oidComponents { diff --git a/Tests/_CryptoExtrasTests/EncryptedPEMTests.swift b/Tests/_CryptoExtrasTests/EncryptedPEMTests.swift new file mode 100644 index 000000000..5c907eec5 --- /dev/null +++ b/Tests/_CryptoExtrasTests/EncryptedPEMTests.swift @@ -0,0 +1,113 @@ + +import XCTest +import _CryptoExtras + +final class EncryptedPEMTests: XCTestCase { + func testPBES2WithAES128EncryptedKeyInit() { + let pbes2WithAES128EncryptedPrivateKey = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + 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 + -----END ENCRYPTED PRIVATE KEY----- + """ + + XCTAssertNoThrow( + try _RSA.Signing.PrivateKey( + encryptedPEMRepresentation: pbes2WithAES128EncryptedPrivateKey, + encryptionPassword: "foobar" + ) + ) + XCTAssertThrowsError( + try _RSA.Signing.PrivateKey( + encryptedPEMRepresentation: pbes2WithAES128EncryptedPrivateKey, + encryptionPassword: "wrong" + ) + ) + } + + func testPBES2WithTripleDESKeyEncryptedKeyInit() { + let pbes2WithTripleDESEncryptedPrivateKey = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIIFJDBWBgkqhkiG9w0BBQ0wSTAxBgkqhkiG9w0BBQwwJAQQ8HZLW3BDKXdsGjxA + 5BM8GgICCAAwDAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIUo0QnIb9O+wEggTI + GqGG0X9OWxs8opGqJ6ynfJzCUy1TJh9CGJgBBVOMS8zqz7qAkBCKhT+VPCtn7W0g + GTf+OhOkj7YnmN/GSwbih/O33NFXoVQrP+kJOTRYFne2zVQ5KvG48oN3P7T4tHMP + zRqq7+qpz6Y0906z/6RmVZWEPryAb0xYEd2DhdX4wBMyHfTf28u10ivEsfTWa5/5 + /n4ENmwAce2MLUbvNGgtXvgbiDn5ITj17Reyal3hTzRoL3J6kLj6xFpBkaAAvvQP + O8FGaVuvi4seeWPVAwwuksRiCwA+wPi3eyREPwG8Q4tS2IKwJqUrbPjrIhxl7HwK + bb2iaQ+es+FZIHXHWvfWiEUyDs2OMcErlUqx8Qaf9K/3o8KFdyqZ7qOKNjK+Z0BC + AHelXjvO62N/sNoK8318LYOkCZ1Wd820JdSTac3AVy9BGQRu7GfhcpjjNbOxsjhz + HSnrZR8PIRNujTyLC8b2fzsTpDNLUE6KYiNzZWfUDOVMmm9xi64kwCMvsKsLd47n + 4VdaPHaqqSA3XkXIDyqAZUKo6r2CUkJH6CYKuVLl6GsA6lLFxVCHtdbQu6MopymO + 0+XkLTJrZItEB4ZIbtG88/ubnYOPqOn7Jvi7W8TEDBXw9inGO4osj7wSnWNEsTRx + 8P/uF9ygpKTANuR84welaJk6c3pxf96esfmxkp7XxGdRx9o0OWbSqB1C4LUjWmKs + LpPF8TvnzFlZyfyW5VyzOs8/4zNO7B0S6X5Ywwytobo0G0/6/eilFIPGZfLTz7gw + 2LPMYKgi+OjE27KGUS3fSDlVkcQqrfrADchtEM6bSYHU1B0K8QE2bkRVM97DRVTv + lngqxvr9yeE+ILCGOf/kTfqGqvoampUUUUMi8is80oSlSVApYiZ3uWJWOMsHlH7X + H1sONAARzhbm+BQ7QRFTH41mMmHIzNSuXVYItRxbC4VkbRqMzLCfZtsCCZ7Mupo7 + a9FdDMDsLeA28EDTESzWEPREk4i0wvJ1QRLdQFJ9GL+RP/YsV1GEwRqHu9lsZCAL + Oz83V41/NfSuTrykZFKaLA2D4DjVGbyinxxcThUL/3u3k98EjBKdfMj6wMF8hKCx + eYvowNOJUdMG3+i7Bo5rKhKZ5mIeRP3MGvelvSQ8gXm03pM255iDV441Ir4F/mpJ + TaMXySqhZef4Ls6tkxsq7E2mXhsPkJVy/hSmnbqZi3FltMGvkbiM3aqNJQcG67mO + NX66Zlbb8JHi6OG8o7H3u6i3BTeyvQkPO0n+sUWrYo1vqemykDUHAdqLdSIdC4pb + kCvRncCw729CD1B3IVkvSZ6NTqNxGCqmc1g/6bkqCaNOXXOqiT4Fzxxv+FCt2sGf + m2G8BvdBVILRRVSGgmG7ahvIY5O+911duS6vkzoxF39VJjrYXcqzKRh71zfAM3J0 + h9GLgrI+lZ7HYCi4eDsSMOdARfL6C7beA6Jaa4snHfGNNrwCECuV0zKrB61n33nN + wE1Nc+gPZ4rbYeYUa8EvdchNB5JdMTyKqOAHrHrM4EberwnZAZMk4Aal/PLAup5L + mrdalZF0qlLUetwUPmAGMuW34igiV084ecKxsuZWXvKtLTHhiTN4NYBgV2rvJ2LE + PRiLIoKv+M+qjywhjPeQbD70byOdIx5J + -----END ENCRYPTED PRIVATE KEY----- + """ + + XCTAssertNoThrow( + try _RSA.Signing.PrivateKey( + encryptedPEMRepresentation: pbes2WithTripleDESEncryptedPrivateKey, + encryptionPassword: "foobar" + ) + ) + XCTAssertThrowsError( + try _RSA.Signing.PrivateKey( + encryptedPEMRepresentation: pbes2WithTripleDESEncryptedPrivateKey, + encryptionPassword: "wrong" + ) + ) + } +} diff --git a/Tests/_CryptoExtrasTests/TestRSASigning.swift b/Tests/_CryptoExtrasTests/TestRSASigning.swift index ac3da0d22..2c8405bc9 100644 --- a/Tests/_CryptoExtrasTests/TestRSASigning.swift +++ b/Tests/_CryptoExtrasTests/TestRSASigning.swift @@ -735,56 +735,6 @@ final class TestRSASigning: XCTestCase { let key = try _RSA.Signing.PrivateKey(pemRepresentation: pemKey) XCTAssertEqual(pemKey, key.pkcs8PEMRepresentation) } - - func testParsingEncryptedRSAKey() throws { - let encryptedPEMKey = """ - -----BEGIN ENCRYPTED PRIVATE KEY----- - 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 - -----END ENCRYPTED PRIVATE KEY----- - """ - - XCTAssertNoThrow(try _RSA.Signing.PrivateKey(encryptedPEMRepresentation: encryptedPEMKey, encryptionPassword: "foobar")) - XCTAssertThrowsError(try _RSA.Signing.PrivateKey(encryptedPEMRepresentation: encryptedPEMKey, encryptionPassword: "wrong")) - } func testConstructKeyFromRSANumbers() throws { /// Check we can successfully construct keys from known valid values from a test vector. From 9c2cca0f5df26c3f716518fc751bb92e331e198c Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Mon, 30 Dec 2024 16:32:17 +0100 Subject: [PATCH 4/6] Cleanup 3DES into namespace --- .../Util/EncryptedPEMDocument.swift | 112 ++++++++++-------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift index 8d31177c3..19ef5295a 100644 --- a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift +++ b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift @@ -84,56 +84,11 @@ struct EncryptedPEMDocument: PEMRepresentable { iv: .init(ivBytes: pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes) ) case .des_EDE3_CBC: - try encryptedData.bytes.withUnsafeBufferPointer { encryptedPtr in - func toDESBlock(_ bytes: UnsafeBufferPointer, paddedBy padding: Int = 0) throws -> DES_cblock { - guard let baseAddress = bytes.baseAddress else { - throw _CryptoRSAError.invalidPEMDocument - } - - let bytes = baseAddress.advanced(by: padding) - return DES_cblock(bytes: ( - bytes[0], bytes[1], bytes[2], bytes[3], - bytes[4], bytes[5], bytes[6], bytes[7] - )) - } - - var output = [UInt8](repeating: 0, count: encryptedData.bytes.count) - - var ks1 = DES_key_schedule(), ks2 = DES_key_schedule(), ks3 = DES_key_schedule() - try derivedKey.withUnsafeBytes { keyPtr in - guard keyPtr.count >= 24 else { throw _CryptoRSAError.invalidPEMDocument } - - let keyBytes = keyPtr.bindMemory(to: UInt8.self) - - var key1 = try toDESBlock(keyBytes) - var key2 = try toDESBlock(keyBytes, paddedBy: 8) - var key3 = try toDESBlock(keyBytes, paddedBy: 16) - - CCryptoBoringSSL_DES_set_key_unchecked(&key1, &ks1) - CCryptoBoringSSL_DES_set_key_unchecked(&key2, &ks2) - CCryptoBoringSSL_DES_set_key_unchecked(&key3, &ks3) - } - - var iv = try pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes.withUnsafeBytes { ivPtr -> DES_cblock in - let ivBytes = ivPtr.bindMemory(to: UInt8.self) - return try toDESBlock(ivBytes) - } - - CCryptoBoringSSL_DES_ede3_cbc_encrypt( - encryptedPtr.baseAddress!, - &output, - encryptedPtr.count, - &ks1, - &ks2, - &ks3, - &iv, - 0 - ) - - var result = Data(output) - try result.trimCBCPadding() - return result - } + try TripleDES.CBC.decrypt( + encryptedData.bytes, + using: derivedKey, + iv: pbes2Params.encryptionScheme.encryptionAlgorithmParameters.bytes + ) default: nil } @@ -344,3 +299,60 @@ struct PBKDF2Parameters: DERImplicitlyTaggable { } } } + +fileprivate enum TripleDES { + fileprivate enum CBC { + static func decrypt(_ encryptedData: ArraySlice, using key: SymmetricKey, iv: ArraySlice) throws -> Data { + try encryptedData.withUnsafeBytes { encryptedPtr in + func toDESBlock(_ bytes: UnsafeBufferPointer, paddedBy padding: Int = 0) throws -> DES_cblock { + guard let baseAddress = bytes.baseAddress else { + throw _CryptoRSAError.invalidPEMDocument + } + + let bytes = baseAddress.advanced(by: padding) + return DES_cblock(bytes: ( + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7] + )) + } + + var output = [UInt8](repeating: 0, count: encryptedData.count) + + var ks1 = DES_key_schedule(), ks2 = DES_key_schedule(), ks3 = DES_key_schedule() + try key.withUnsafeBytes { keyPtr in + guard keyPtr.count >= 24 else { throw _CryptoRSAError.invalidPEMDocument } + + let keyBytes = keyPtr.bindMemory(to: UInt8.self) + + var key1 = try toDESBlock(keyBytes) + var key2 = try toDESBlock(keyBytes, paddedBy: 8) + var key3 = try toDESBlock(keyBytes, paddedBy: 16) + + CCryptoBoringSSL_DES_set_key_unchecked(&key1, &ks1) + CCryptoBoringSSL_DES_set_key_unchecked(&key2, &ks2) + CCryptoBoringSSL_DES_set_key_unchecked(&key3, &ks3) + } + + var iv = try iv.withUnsafeBytes { ivPtr -> DES_cblock in + let ivBytes = ivPtr.bindMemory(to: UInt8.self) + return try toDESBlock(ivBytes) + } + + CCryptoBoringSSL_DES_ede3_cbc_encrypt( + encryptedPtr.baseAddress!, + &output, + encryptedPtr.count, + &ks1, + &ks2, + &ks3, + &iv, + 0 + ) + + var result = Data(output) + try result.trimCBCPadding() + return result + } + } + } +} From 49c67a61d5987fcf233675603e2e7034501fa8d1 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Mon, 30 Dec 2024 17:05:02 +0100 Subject: [PATCH 5/6] Abstract ASN1 structures into different files --- ...ncryptedPEMDocument+EncryptionScheme.swift | 46 +++++ ...tedPEMDocument+KeyDerivationFunction.swift | 133 ++++++++++++++ ...EncryptedPEMDocument+PBES2Parameters.swift | 45 +++++ .../Util/EncryptedPEMDocument.swift | 170 +----------------- 4 files changed, 225 insertions(+), 169 deletions(-) create mode 100644 Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift create mode 100644 Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift create mode 100644 Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift diff --git a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift new file mode 100644 index 000000000..65b7287fd --- /dev/null +++ b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftASN1 + +extension EncryptedPEMDocument { + struct EncryptionScheme: DERImplicitlyTaggable { + static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } + + let encryptionAlgorithm: ASN1ObjectIdentifier + let encryptionAlgorithmParameters: ASN1OctetString + + init(encryptionAlgorithm: ASN1ObjectIdentifier, encryptionAlgorithmParameters: ASN1OctetString) { + self.encryptionAlgorithm = encryptionAlgorithm + self.encryptionAlgorithmParameters = encryptionAlgorithmParameters + } + + init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let encryptionAlgorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) + let encryptionAlgorithmParameters = try ASN1OctetString(derEncoded: &nodes) + + return .init(encryptionAlgorithm: encryptionAlgorithm, encryptionAlgorithmParameters: encryptionAlgorithmParameters) + } + } + + func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.encryptionAlgorithm.serialize(into: &coder) + try self.encryptionAlgorithmParameters.serialize(into: &coder) + } + } + } +} + diff --git a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift new file mode 100644 index 000000000..2510e1798 --- /dev/null +++ b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftASN1 + +extension EncryptedPEMDocument { + struct KeyDerivationFunction: DERImplicitlyTaggable { + static var defaultIdentifier: ASN1Identifier { .sequence } + + let algorithm: ASN1ObjectIdentifier + let parameters: ASN1Any + + init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any) { + self.algorithm = algorithm + self.parameters = parameters + } + + init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let algorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) + let parameters = try ASN1Any(derEncoded: &nodes) + + return .init(algorithm: algorithm, parameters: parameters) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.algorithm.serialize(into: &coder) + try self.parameters.serialize(into: &coder) + } + } + } +} + +extension EncryptedPEMDocument.KeyDerivationFunction { + // PBKDF2-params ::= SEQUENCE { + // salt CHOICE { + // specified OCTET STRING, + // otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}} + // }, + // iterationCount INTEGER (1..MAX), + // keyLength INTEGER (1..MAX) OPTIONAL, + // prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 + // } + struct PBKDF2Parameters: DERImplicitlyTaggable { + static var defaultIdentifier: ASN1Identifier { .sequence } + + let salt: ASN1OctetString + let iterationCount: any ASN1IntegerRepresentable + let hashFunction: HashFunction + + init(salt: ASN1OctetString, iterationCount: any ASN1IntegerRepresentable, hashFunction: HashFunction) { + self.salt = salt + self.iterationCount = iterationCount + self.hashFunction = hashFunction + } + + init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let salt = try ASN1OctetString(derEncoded: &nodes) + let iterationCount = try Int(derEncoded: &nodes) + let hashFunction = try HashFunction(derEncoded: &nodes) + + return .init(salt: salt, iterationCount: iterationCount, hashFunction: hashFunction) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.salt.serialize(into: &coder) + try self.iterationCount.serialize(into: &coder) + try self.hashFunction.serialize(into: &coder) + } + } + } +} + +extension EncryptedPEMDocument.KeyDerivationFunction.PBKDF2Parameters { + struct HashFunction: DERImplicitlyTaggable { + static var defaultIdentifier: ASN1Identifier { .sequence } + + let objectIdentifer: ASN1ObjectIdentifier + let null: ASN1Null + + init(objectIdentifer: ASN1ObjectIdentifier, null: ASN1Null) { + self.objectIdentifer = objectIdentifer + self.null = null + } + + init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let objectIdentifer = try ASN1ObjectIdentifier(derEncoded: &nodes) + let null = try ASN1Null(derEncoded: &nodes) + + return .init(objectIdentifer: objectIdentifer, null: null) + } + } + + func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.objectIdentifer.serialize(into: &coder) + try self.null.serialize(into: &coder) + } + } + } +} + +extension KDF.Insecure.PBKDF2.HashFunction { + static func from(objectIdentifier: ASN1ObjectIdentifier) -> Self? { + switch objectIdentifier.oidComponents { + case [2, 16, 840, 1, 101, 3, 4, 2, 1], + [1, 2, 840, 113549, 2, 9]: // hmacWithSHA256 + .sha256 + case [2, 16, 840, 1, 101, 3, 4, 2, 2]: + .sha384 + case [2, 16, 840, 1, 101, 3, 4, 2, 3]: + .sha512 + default: nil + } + } +} diff --git a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift new file mode 100644 index 000000000..42eca7f12 --- /dev/null +++ b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftASN1 + +extension EncryptedPEMDocument { + struct PBES2Parameters: DERImplicitlyTaggable { + static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } + + let keyDerivationFunction: KeyDerivationFunction + let encryptionScheme: EncryptionScheme + + init(keyDerivationFunction: KeyDerivationFunction, encryptionScheme: EncryptionScheme) { + self.keyDerivationFunction = keyDerivationFunction + self.encryptionScheme = encryptionScheme + } + + init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + let keyDerivationFunction = try KeyDerivationFunction(derEncoded: &nodes) + let encryptionScheme = try EncryptionScheme(derEncoded: &nodes) + + return .init(keyDerivationFunction: keyDerivationFunction, encryptionScheme: encryptionScheme) + } + } + + func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { + try coder.appendConstructedNode(identifier: identifier) { coder in + try self.keyDerivationFunction.serialize(into: &coder) + try self.encryptionScheme.serialize(into: &coder) + } + } + } +} diff --git a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift index 19ef5295a..11a0cde84 100644 --- a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift +++ b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift @@ -64,7 +64,7 @@ struct EncryptedPEMDocument: PEMRepresentable { switch algorithm { case .pkcs5PBES2: let pbes2Params = try PBES2Parameters(asn1Any: parameters) - let pbkdf2Params = try PBKDF2Parameters(asn1Any: pbes2Params.keyDerivationFunction.parameters) + let pbkdf2Params = try KeyDerivationFunction.PBKDF2Parameters(asn1Any: pbes2Params.keyDerivationFunction.parameters) let hashFunction = pbkdf2Params.hashFunction @@ -132,174 +132,6 @@ extension ASN1ObjectIdentifier { } } -extension KDF.Insecure.PBKDF2.HashFunction { - static func from(objectIdentifier: ASN1ObjectIdentifier) -> Self? { - switch objectIdentifier.oidComponents { - case [2, 16, 840, 1, 101, 3, 4, 2, 1], - [1, 2, 840, 113549, 2, 9]: // hmacWithSHA256 - .sha256 - case [2, 16, 840, 1, 101, 3, 4, 2, 2]: - .sha384 - case [2, 16, 840, 1, 101, 3, 4, 2, 3]: - .sha512 - default: nil - } - } -} - -struct PBES2Parameters: DERImplicitlyTaggable { - static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } - - let keyDerivationFunction: KeyDerivationFunction - let encryptionScheme: EncryptionScheme - - init(keyDerivationFunction: KeyDerivationFunction, encryptionScheme: EncryptionScheme) { - self.keyDerivationFunction = keyDerivationFunction - self.encryptionScheme = encryptionScheme - } - - init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in - let keyDerivationFunction = try KeyDerivationFunction(derEncoded: &nodes) - let encryptionScheme = try EncryptionScheme(derEncoded: &nodes) - - return .init(keyDerivationFunction: keyDerivationFunction, encryptionScheme: encryptionScheme) - } - } - - func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.keyDerivationFunction.serialize(into: &coder) - try self.encryptionScheme.serialize(into: &coder) - } - } -} - -struct KeyDerivationFunction: DERImplicitlyTaggable { - static var defaultIdentifier: ASN1Identifier { .sequence } - - let algorithm: ASN1ObjectIdentifier - let parameters: ASN1Any - - init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any) { - self.algorithm = algorithm - self.parameters = parameters - } - - init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in - let algorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) - let parameters = try ASN1Any(derEncoded: &nodes) - - return .init(algorithm: algorithm, parameters: parameters) - } - } - - func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.algorithm.serialize(into: &coder) - try self.parameters.serialize(into: &coder) - } - } -} - -struct EncryptionScheme: DERImplicitlyTaggable { - static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } - - let encryptionAlgorithm: ASN1ObjectIdentifier - let encryptionAlgorithmParameters: ASN1OctetString - - init(encryptionAlgorithm: ASN1ObjectIdentifier, encryptionAlgorithmParameters: ASN1OctetString) { - self.encryptionAlgorithm = encryptionAlgorithm - self.encryptionAlgorithmParameters = encryptionAlgorithmParameters - } - - init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in - let encryptionAlgorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) - let encryptionAlgorithmParameters = try ASN1OctetString(derEncoded: &nodes) - - return .init(encryptionAlgorithm: encryptionAlgorithm, encryptionAlgorithmParameters: encryptionAlgorithmParameters) - } - } - - func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.encryptionAlgorithm.serialize(into: &coder) - try self.encryptionAlgorithmParameters.serialize(into: &coder) - } - } -} - -// PBKDF2-params ::= SEQUENCE { -// salt CHOICE { -// specified OCTET STRING, -// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}} -// }, -// iterationCount INTEGER (1..MAX), -// keyLength INTEGER (1..MAX) OPTIONAL, -// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 -// } -struct PBKDF2Parameters: DERImplicitlyTaggable { - struct HashFunction: DERImplicitlyTaggable { - static var defaultIdentifier: ASN1Identifier { .sequence } - - let objectIdentifer: ASN1ObjectIdentifier - let null: ASN1Null - - init(objectIdentifer: ASN1ObjectIdentifier, null: ASN1Null) { - self.objectIdentifer = objectIdentifer - self.null = null - } - - init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in - let objectIdentifer = try ASN1ObjectIdentifier(derEncoded: &nodes) - let null = try ASN1Null(derEncoded: &nodes) - - return .init(objectIdentifer: objectIdentifer, null: null) - } - } - - func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.objectIdentifer.serialize(into: &coder) - try self.null.serialize(into: &coder) - } - } - } - - static var defaultIdentifier: ASN1Identifier { .sequence } - - let salt: ASN1OctetString - let iterationCount: any ASN1IntegerRepresentable - let hashFunction: HashFunction - - init(salt: ASN1OctetString, iterationCount: any ASN1IntegerRepresentable, hashFunction: HashFunction) { - self.salt = salt - self.iterationCount = iterationCount - self.hashFunction = hashFunction - } - - init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in - let salt = try ASN1OctetString(derEncoded: &nodes) - let iterationCount = try Int(derEncoded: &nodes) - let hashFunction = try HashFunction(derEncoded: &nodes) - - return .init(salt: salt, iterationCount: iterationCount, hashFunction: hashFunction) - } - } - - func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.salt.serialize(into: &coder) - try self.iterationCount.serialize(into: &coder) - try self.hashFunction.serialize(into: &coder) - } - } -} - fileprivate enum TripleDES { fileprivate enum CBC { static func decrypt(_ encryptedData: ArraySlice, using key: SymmetricKey, iv: ArraySlice) throws -> Data { From 978c10d87fbd9bfd1ecb37c775cb0b3ca37cc7e3 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Mon, 30 Dec 2024 18:09:38 +0100 Subject: [PATCH 6/6] Remove serialisation capabilities for now --- ...ncryptedPEMDocument+EncryptionScheme.swift | 13 ++---- ...tedPEMDocument+KeyDerivationFunction.swift | 40 +++++-------------- ...EncryptedPEMDocument+PBES2Parameters.swift | 15 ++----- .../Util/EncryptedPEMDocument.swift | 9 +---- 4 files changed, 17 insertions(+), 60 deletions(-) diff --git a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift index 65b7287fd..f3e260d8e 100644 --- a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift +++ b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+EncryptionScheme.swift @@ -15,7 +15,7 @@ import SwiftASN1 extension EncryptedPEMDocument { - struct EncryptionScheme: DERImplicitlyTaggable { + struct EncryptionScheme: DERParseable { static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } let encryptionAlgorithm: ASN1ObjectIdentifier @@ -26,21 +26,14 @@ extension EncryptedPEMDocument { self.encryptionAlgorithmParameters = encryptionAlgorithmParameters } - init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + init(derEncoded node: ASN1Node) throws { + self = try DER.sequence(node, identifier: .sequence) { nodes in let encryptionAlgorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) let encryptionAlgorithmParameters = try ASN1OctetString(derEncoded: &nodes) return .init(encryptionAlgorithm: encryptionAlgorithm, encryptionAlgorithmParameters: encryptionAlgorithmParameters) } } - - func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.encryptionAlgorithm.serialize(into: &coder) - try self.encryptionAlgorithmParameters.serialize(into: &coder) - } - } } } diff --git a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift index 2510e1798..54341cf96 100644 --- a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift +++ b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+KeyDerivationFunction.swift @@ -15,7 +15,7 @@ import SwiftASN1 extension EncryptedPEMDocument { - struct KeyDerivationFunction: DERImplicitlyTaggable { + struct KeyDerivationFunction: DERParseable { static var defaultIdentifier: ASN1Identifier { .sequence } let algorithm: ASN1ObjectIdentifier @@ -26,21 +26,14 @@ extension EncryptedPEMDocument { self.parameters = parameters } - init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + init(derEncoded node: ASN1Node) throws { + self = try DER.sequence(node, identifier: .sequence) { nodes in let algorithm = try ASN1ObjectIdentifier(derEncoded: &nodes) let parameters = try ASN1Any(derEncoded: &nodes) return .init(algorithm: algorithm, parameters: parameters) } } - - func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.algorithm.serialize(into: &coder) - try self.parameters.serialize(into: &coder) - } - } } } @@ -54,7 +47,7 @@ extension EncryptedPEMDocument.KeyDerivationFunction { // keyLength INTEGER (1..MAX) OPTIONAL, // prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 // } - struct PBKDF2Parameters: DERImplicitlyTaggable { + struct PBKDF2Parameters: DERParseable { static var defaultIdentifier: ASN1Identifier { .sequence } let salt: ASN1OctetString @@ -67,8 +60,8 @@ extension EncryptedPEMDocument.KeyDerivationFunction { self.hashFunction = hashFunction } - init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + init(derEncoded node: ASN1Node) throws { + self = try DER.sequence(node, identifier: .sequence) { nodes in let salt = try ASN1OctetString(derEncoded: &nodes) let iterationCount = try Int(derEncoded: &nodes) let hashFunction = try HashFunction(derEncoded: &nodes) @@ -76,19 +69,11 @@ extension EncryptedPEMDocument.KeyDerivationFunction { return .init(salt: salt, iterationCount: iterationCount, hashFunction: hashFunction) } } - - func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.salt.serialize(into: &coder) - try self.iterationCount.serialize(into: &coder) - try self.hashFunction.serialize(into: &coder) - } - } } } extension EncryptedPEMDocument.KeyDerivationFunction.PBKDF2Parameters { - struct HashFunction: DERImplicitlyTaggable { + struct HashFunction: DERParseable { static var defaultIdentifier: ASN1Identifier { .sequence } let objectIdentifer: ASN1ObjectIdentifier @@ -99,21 +84,14 @@ extension EncryptedPEMDocument.KeyDerivationFunction.PBKDF2Parameters { self.null = null } - init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + init(derEncoded node: ASN1Node) throws { + self = try DER.sequence(node, identifier: .sequence) { nodes in let objectIdentifer = try ASN1ObjectIdentifier(derEncoded: &nodes) let null = try ASN1Null(derEncoded: &nodes) return .init(objectIdentifer: objectIdentifer, null: null) } } - - func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.objectIdentifer.serialize(into: &coder) - try self.null.serialize(into: &coder) - } - } } } diff --git a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift index 42eca7f12..a2c1064a9 100644 --- a/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift +++ b/Sources/_CryptoExtras/Util/ASN1/EncryptedPEMDocument+PBES2Parameters.swift @@ -15,8 +15,8 @@ import SwiftASN1 extension EncryptedPEMDocument { - struct PBES2Parameters: DERImplicitlyTaggable { - static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence } + struct PBES2Parameters: DERParseable { + static var defaultIdentifier: ASN1Identifier { .sequence } let keyDerivationFunction: KeyDerivationFunction let encryptionScheme: EncryptionScheme @@ -26,20 +26,13 @@ extension EncryptedPEMDocument { self.encryptionScheme = encryptionScheme } - init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - self = try DER.sequence(derEncoded, identifier: identifier) { nodes in + init(derEncoded node: ASN1Node) throws { + self = try DER.sequence(node, identifier: .sequence) { nodes in let keyDerivationFunction = try KeyDerivationFunction(derEncoded: &nodes) let encryptionScheme = try EncryptionScheme(derEncoded: &nodes) return .init(keyDerivationFunction: keyDerivationFunction, encryptionScheme: encryptionScheme) } } - - func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws { - try coder.appendConstructedNode(identifier: identifier) { coder in - try self.keyDerivationFunction.serialize(into: &coder) - try self.encryptionScheme.serialize(into: &coder) - } - } } } diff --git a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift index 11a0cde84..ec52ad1ad 100644 --- a/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift +++ b/Sources/_CryptoExtras/Util/EncryptedPEMDocument.swift @@ -25,7 +25,7 @@ import Foundation // EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier // // EncryptedData ::= OCTET STRING -struct EncryptedPEMDocument: PEMRepresentable { +struct EncryptedPEMDocument: PEMParseable { let algorithmIdentifier: RFC5480AlgorithmIdentifier let encryptedData: ASN1OctetString @@ -47,13 +47,6 @@ struct EncryptedPEMDocument: PEMRepresentable { } } - func serialize(into coder: inout SwiftASN1.DER.Serializer) throws { - try coder.appendConstructedNode(identifier: .sequence) { coder in - try self.algorithmIdentifier.serialize(into: &coder) - try self.encryptedData.serialize(into: &coder) - } - } - func decrypt(withPassword password: String) throws -> PEMDocument { let algorithm = self.algorithmIdentifier.algorithm