diff --git a/Package.swift b/Package.swift index 3fe0944..bd33dac 100644 --- a/Package.swift +++ b/Package.swift @@ -17,17 +17,15 @@ let package = Package( ], dependencies: [ // Multibase Support - .package(url: "https://github.com/swift-libp2p/swift-multibase.git", .upToNextMajor(from: "0.0.1")), + .package(url: "https://github.com/swift-libp2p/swift-multibase.git", .upToNextMinor(from: "0.0.1")), // Protobuf Marshaling .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.12.0")), - // RSA Import / Export Support - .package(url: "https://github.com/nextincrement/rsa-public-key-importer-exporter.git", .upToNextMajor(from: "0.1.0")), // Secp256k1 Support .package(url: "https://github.com/Boilertalk/secp256k1.swift.git", .exact("0.1.6")), // 🔑 Hashing (BCrypt, SHA2, HMAC), encryption (AES), public-key (RSA), PEM and DER file handling, and random data generation. .package(url: "https://github.com/apple/swift-crypto.git", .upToNextMajor(from: "1.0.0")), - .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .exact("1.5.1")), - .package(url: "https://github.com/swift-libp2p/swift-multihash.git", .upToNextMajor(from: "0.0.1")), + .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.6.0")), + .package(url: "https://github.com/swift-libp2p/swift-multihash.git", .upToNextMinor(from: "0.0.1")), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -38,8 +36,6 @@ let package = Package( .product(name: "Multibase", package: "swift-multibase"), .product(name: "Multihash", package: "swift-multihash"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), - .product(name: "RSAPublicKeyExporter", package: "rsa-public-key-importer-exporter"), - .product(name: "RSAPublicKeyImporter", package: "rsa-public-key-importer-exporter"), .product(name: "secp256k1", package: "secp256k1.swift"), .product(name: "Crypto", package: "swift-crypto"), .product(name: "CryptoSwift", package: "CryptoSwift"), diff --git a/Sources/LibP2PCrypto/Keys/CommonPrivateKey.swift b/Sources/LibP2PCrypto/Keys/CommonPrivateKey.swift index d020ca3..424df9c 100644 --- a/Sources/LibP2PCrypto/Keys/CommonPrivateKey.swift +++ b/Sources/LibP2PCrypto/Keys/CommonPrivateKey.swift @@ -8,7 +8,7 @@ import Foundation import Multibase -public protocol CommonPrivateKey { +public protocol CommonPrivateKey:DERCodable { static var keyType:LibP2PCrypto.Keys.GenericKeyType { get } /// Init from raw representation diff --git a/Sources/LibP2PCrypto/Keys/CommonPublicKey.swift b/Sources/LibP2PCrypto/Keys/CommonPublicKey.swift index 9eb96ef..6d5045d 100644 --- a/Sources/LibP2PCrypto/Keys/CommonPublicKey.swift +++ b/Sources/LibP2PCrypto/Keys/CommonPublicKey.swift @@ -9,7 +9,7 @@ import Foundation import Multihash import Multibase -public protocol CommonPublicKey { +public protocol CommonPublicKey:DERCodable { static var keyType:LibP2PCrypto.Keys.GenericKeyType { get } /// Init from raw representation diff --git a/Sources/LibP2PCrypto/Keys/KeyPair.swift b/Sources/LibP2PCrypto/Keys/KeyPair.swift index 1a15bdc..e414004 100644 --- a/Sources/LibP2PCrypto/Keys/KeyPair.swift +++ b/Sources/LibP2PCrypto/Keys/KeyPair.swift @@ -89,7 +89,7 @@ extension LibP2PCrypto.Keys { case .rsa: let count = self.publicKey.rawRepresentation.count switch self.publicKey.rawRepresentation.count { - case 140, 162: + case 140, 161, 162: return Attributes(type: .RSA(bits: .B1024), size: 1024, isPrivate: (self.privateKey != nil)) case 270, 294: return Attributes(type: .RSA(bits: .B2048), size: 2048, isPrivate: (self.privateKey != nil)) @@ -233,3 +233,140 @@ extension LibP2PCrypto.Keys { } } } + + +extension LibP2PCrypto.Keys.KeyPair { + init(pem:String, password:String? = nil) throws { + try self.init(pem: pem.bytes, password: password) + } + + init(pem:Data, password:String? = nil) throws { + try self.init(pem: pem.bytes, password: password) + } + + init(pem pemBytes:Array, password:String? = nil) throws { + + let (type, bytes, ids) = try PEM.pemToData(pemBytes) + + if password != nil { + guard type == .encryptedPrivateKey else { throw PEM.Error.invalidParameters } + } + + switch type { + case .publicRSAKeyDER: + // Ensure the objectIdentifier is rsaEncryption + try self.init(publicKey: RSAPublicKey(publicDER: bytes)) + + case .privateRSAKeyDER: + // Ensure the objectIdentifier is rsaEncryption + try self.init(privateKey: RSAPrivateKey(privateDER: bytes)) + + case .publicKey: + // Attempt to further classify the pem into it's exact key type + if ids.contains(RSAPublicKey.primaryObjectIdentifier) { + try self.init(publicKey: RSAPublicKey(pem: pemBytes, asType: RSAPublicKey.self)) + } else if ids.contains(Curve25519.Signing.PublicKey.primaryObjectIdentifier) { + try self.init(publicKey: Curve25519.Signing.PublicKey(pem: pemBytes, asType: Curve25519.Signing.PublicKey.self)) + } else if ids.contains(Secp256k1PublicKey.primaryObjectIdentifier) { + try self.init(publicKey: Secp256k1PublicKey(pem: pemBytes, asType: Secp256k1PublicKey.self)) + } else { + throw PEM.Error.unsupportedPEMType + } + + case .privateKey, .ecPrivateKey: + // Attempt to further classify the pem into it's exact key type + if ids.contains(RSAPrivateKey.primaryObjectIdentifier) { + try self.init(privateKey: RSAPrivateKey(pem: pemBytes, asType: RSAPrivateKey.self)) + } else if ids.contains(Curve25519.Signing.PrivateKey.primaryObjectIdentifier) { + try self.init(privateKey: Curve25519.Signing.PrivateKey(pem: pemBytes, asType: Curve25519.Signing.PrivateKey.self)) + } else if ids.contains(Secp256k1PrivateKey.primaryObjectIdentifier) { + try self.init(privateKey: Secp256k1PrivateKey(pem: pemBytes, asType: Secp256k1PrivateKey.self)) + } else { + throw PEM.Error.unsupportedPEMType + } + + case .encryptedPrivateKey: + // Decrypt the encrypted PEM and attempt to instantiate it again... + + // Ensure we were provided a password + guard let password = password else { throw PEM.Error.invalidParameters } + + // Parse out Encryption Strategy and CipherText + let decryptionStategy = try PEM.decodeEncryptedPEM(Data(bytes)) // RSA.decodeEncryptedPEM(Data(bytes)) + + // Derive Encryption Key from Password + let key = try decryptionStategy.pbkdfAlgorithm.deriveKey(password: password, ofLength: decryptionStategy.cipherAlgorithm.desiredKeyLength) + + // Decrypt CipherText + let decryptedPEM = try decryptionStategy.cipherAlgorithm.decrypt(bytes: decryptionStategy.ciphertext, withKey: key) + + // Extract out the objectIdentifiers from the decrypted pem + let ids = try PEM.objIdsInSequence(ASN1.Decoder.decode(data: Data(decryptedPEM))).map { $0.bytes } + + // Attempt to classify the Key Type + if ids.contains(RSAPrivateKey.primaryObjectIdentifier) { + let der = try PEM.decodePrivateKeyPEM( + Data(decryptedPEM), + expectedPrimaryObjectIdentifier: RSAPrivateKey.primaryObjectIdentifier, + expectedSecondaryObjectIdentifier: RSAPrivateKey.secondaryObjectIdentifier + ) + try self.init(privateKey: RSAPrivateKey(privateDER: der)) + } else if ids.contains(Curve25519.Signing.PrivateKey.primaryObjectIdentifier) { + let der = try PEM.decodePrivateKeyPEM( + Data(decryptedPEM), + expectedPrimaryObjectIdentifier: Curve25519.Signing.PrivateKey.primaryObjectIdentifier, + expectedSecondaryObjectIdentifier: Curve25519.Signing.PrivateKey.secondaryObjectIdentifier + ) + try self.init(privateKey: Curve25519.Signing.PrivateKey(privateDER: der)) + } else if ids.contains(Secp256k1PrivateKey.primaryObjectIdentifier) { + let der = try PEM.decodePrivateKeyPEM( + Data(decryptedPEM), + expectedPrimaryObjectIdentifier: Secp256k1PrivateKey.primaryObjectIdentifier, + expectedSecondaryObjectIdentifier: Secp256k1PrivateKey.secondaryObjectIdentifier + ) + try self.init(privateKey: Secp256k1PrivateKey(privateDER: der)) + } else { + print(ids) + throw PEM.Error.unsupportedPEMType + } + } + } +} + +extension LibP2PCrypto.Keys.KeyPair { + + func exportPublicPEM(withHeaderAndFooter:Bool = true) throws -> Array { + //guard let der = publicKey as? DEREncodable else { throw NSError(domain: "Unknown private key type", code: 0) } + return try publicKey.exportPublicKeyPEM(withHeaderAndFooter: withHeaderAndFooter) + } + + func exportPrivatePEM(withHeaderAndFooter:Bool = true) throws -> Array { + guard let privKey = self.privateKey else { throw NSError(domain: "No private key available to export", code: 0) } + //guard let der = privKey as? DEREncodable else { throw NSError(domain: "Unknown private key type", code: 0) } + return try privKey.exportPrivateKeyPEM(withHeaderAndFooter: withHeaderAndFooter) + } + + func exportPublicPEMString(withHeaderAndFooter:Bool = true) throws -> String { + //guard let der = publicKey as? DEREncodable else { throw NSError(domain: "Unknown private key type", code: 0) } + return try publicKey.exportPublicKeyPEMString(withHeaderAndFooter: withHeaderAndFooter) + } + + func exportPrivatePEMString(withHeaderAndFooter:Bool = true) throws -> String { + guard let privKey = self.privateKey else { throw NSError(domain: "No private key available to export", code: 0) } + //guard let der = privKey as? DEREncodable else { throw NSError(domain: "Unknown private key type", code: 0) } + return try privKey.exportPrivateKeyPEMString(withHeaderAndFooter: withHeaderAndFooter) + } + + func exportEncryptedPrivatePEM(withPassword password:String, usingPBKDF pbkdf:PEM.PBKDFAlgorithm? = nil, andCipher cipher:PEM.CipherAlgorithm? = nil) throws -> Array { + let cipher = try cipher ?? .aes_128_cbc(iv: LibP2PCrypto.randomBytes(length: 16)) + let pbkdf = try pbkdf ?? .pbkdf2(salt: LibP2PCrypto.randomBytes(length: 8), iterations: 2048) + + return try PEM.encryptPEM(Data(self.privateKey!.exportPrivateKeyPEMRaw()), withPassword: password, usingPBKDF: pbkdf, andCipher: cipher).bytes + } + + func exportEncryptedPrivatePEMString(withPassword password:String, usingPBKDF pbkdf:PEM.PBKDFAlgorithm? = nil, andCipher cipher:PEM.CipherAlgorithm? = nil) throws -> String { + let data = try self.exportEncryptedPrivatePEM(withPassword: password, usingPBKDF: pbkdf, andCipher: cipher) + return String(data: Data(data), encoding: .utf8)! + } + +} diff --git a/Sources/LibP2PCrypto/Keys/Types/Ed25519/Ed25519.swift b/Sources/LibP2PCrypto/Keys/Types/Ed25519/Ed25519.swift index f9dfab3..4bd9cf7 100644 --- a/Sources/LibP2PCrypto/Keys/Types/Ed25519/Ed25519.swift +++ b/Sources/LibP2PCrypto/Keys/Types/Ed25519/Ed25519.swift @@ -7,6 +7,7 @@ import Foundation import Crypto +//import PEM extension Curve25519.Signing.PublicKey:CommonPublicKey { public static var keyType: LibP2PCrypto.Keys.GenericKeyType { .ed25519 } @@ -128,3 +129,104 @@ extension Curve25519.Signing.PrivateKey:Equatable { lhs.rawRepresentation == rhs.rawRepresentation } } + +extension Curve25519.Signing.PublicKey:DERCodable { + public static var primaryObjectIdentifier: Array { [0x2B, 0x65, 0x70] } + public static var secondaryObjectIdentifier: Array? { nil } + + public init(publicDER: Array) throws { + try self.init(rawRepresentation: publicDER) + } + + public init(privateDER: Array) throws { + throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0) + } + + public func publicKeyDER() throws -> Array { + self.rawRepresentation.bytes + } + + public func privateKeyDER() throws -> Array { + throw NSError(domain: "Public Key doesn't have private DER representation", code: 0) + } + + public func exportPublicKeyPEM(withHeaderAndFooter: Bool) throws -> Array { + let publicDER = try self.publicKeyDER() + + let asnNodes:ASN1.Node = .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier)), + ]), + .bitString(data: Data( publicDER )) + ]) + + let base64String = ASN1.Encoder.encode(asnNodes).toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.publicKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.publicKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } +} + +extension Curve25519.Signing.PrivateKey:DERCodable { + public static var primaryObjectIdentifier: Array { [0x2B, 0x65, 0x70] } + public static var secondaryObjectIdentifier: Array? { nil } + + public init(publicDER: Array) throws { + throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0) + } + + public init(privateDER: Array) throws { + guard case .octetString(let rawData) = try ASN1.Decoder.decode(data: Data(privateDER)) else { + throw PEM.Error.invalidParameters + } + try self.init(rawRepresentation: rawData) + } + + public func publicKeyDER() throws -> Array { + try self.publicKey.publicKeyDER() + } + + public func privateKeyDER() throws -> Array { + ASN1.Encoder.encode( + ASN1.Node.octetString(data: Data( self.rawRepresentation )) + ) + } + + public func exportPrivateKeyPEMRaw() throws -> Array { + let privKey = try privateKeyDER() + + let asnNodes:ASN1.Node = .sequence(nodes: [ + .integer(data: Data(hex: "0x00")), + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier) ) + ]), + .octetString(data: Data(privKey) ) + ]) + + return ASN1.Encoder.encode(asnNodes) + } + + public func exportPrivateKeyPEM(withHeaderAndFooter: Bool) throws -> Array { + let base64String = try self.exportPrivateKeyPEMRaw().toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.privateKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.privateKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } +} + diff --git a/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+CryptoSwift.swift b/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+CryptoSwift.swift index 1f00b68..86ace21 100644 --- a/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+CryptoSwift.swift +++ b/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+CryptoSwift.swift @@ -15,7 +15,7 @@ struct RSAPublicKey:CommonPublicKey { /// RSA Object Identifier Bytes private static var RSA_OBJECT_IDENTIFIER = Array(arrayLiteral: 42, 134, 72, 134, 247, 13, 1, 1, 1) - /// The underlying SecKey that backs this struct + /// The underlying CryptoSwift RSA Key that backs this struct private let key:RSA fileprivate init(_ rsa:RSA) { @@ -23,26 +23,22 @@ struct RSAPublicKey:CommonPublicKey { } init(rawRepresentation raw: Data) throws { - let asn1 = try Asn1Parser.parse(data: raw) - + let asn1 = try ASN1.Decoder.decode(data: raw) + guard case .sequence(let params) = asn1 else { throw NSError(domain: "Invalid ASN1 Encoding -> \(asn1)", code: 0) } - + /// We have an objectID header.... if case .sequence(let objectID) = params.first { guard case .objectIdentifier(let oid) = objectID.first else { throw NSError(domain: "Invalid ASN1 Encoding -> No ObjectID", code: 0) } guard oid.bytes == RSAPublicKey.RSA_OBJECT_IDENTIFIER else { throw NSError(domain: "Invalid ASN1 Encoding -> ObjectID != Public RSA Key ID", code: 0) } guard case .bitString(let bits) = params.last else { throw NSError(domain: "Invalid ASN1 Encoding -> No BitString", code: 0) } - - guard case .sequence(let params2) = try Asn1Parser.parse(data: bits) else { throw NSError(domain: "Invalid ASN1 Encoding -> No PubKey Sequence", code: 0) } - guard case .integer(let n) = params2.first else { throw NSError(domain: "Invalid ASN1 Encoding -> No Modulus", code: 0) } - guard case .integer(let e) = params2.last else { throw NSError(domain: "Invalid ASN1 Encoding -> No Public Exponent", code: 0) } - - self.key = CryptoSwift.RSA(n: n.bytes, e: e.bytes) + + self.key = try CryptoSwift.RSA(rawRepresentation: bits) } else if params.count == 2, case .integer = params.first { /// We have a direct sequence of integers guard case .integer(let n) = params.first else { throw NSError(domain: "Invalid ASN1 Encoding -> No Modulus", code: 0) } guard case .integer(let e) = params.last else { throw NSError(domain: "Invalid ASN1 Encoding -> No Public Exponent", code: 0) } - + self.key = CryptoSwift.RSA(n: n.bytes, e: e.bytes) } else { throw NSError(domain: "Invalid RSA rawRepresentation", code: 0) @@ -53,24 +49,17 @@ struct RSAPublicKey:CommonPublicKey { try self.init(rawRepresentation: data) } - /// We return the ASN1 Encoded DER Representation of the public key because that's what the rewRepresentation of RSA SecKey return + /// We return the ASN1 Encoded DER Representation of the public key because that's what the rawRepresentation of RSA SecKey return var rawRepresentation: Data { - let mod = key.n.serialize() - let pubkeyAsnNode:Asn1Parser.Node = + let asnNodes:ASN1.Node = try! .sequence(nodes: [ .sequence(nodes: [ - .integer(data: Data(CryptoSwift.RSA.zeroPad(n: mod.bytes, to: mod.count + 1))), - .integer(data: key.e.serialize()) - ]) - - let asnNodes:Asn1Parser.Node = .sequence(nodes: [ - .sequence(nodes: [ - .objectIdentifier(data: Data(RSAPublicKey.RSA_OBJECT_IDENTIFIER)), + .objectIdentifier(data: Data(RSAPublicKey.primaryObjectIdentifier)), .null ]), - .bitString(data: Data(ASN1Encoder.encode(pubkeyAsnNode))) + .bitString(data: self.key.externalRepresentation()) ]) - return Data(ASN1Encoder.encode(asnNodes)) + return Data(ASN1.Encoder.encode(asnNodes)) } func encrypt(data: Data) throws -> Data { @@ -107,31 +96,29 @@ struct RSAPrivateKey:CommonPrivateKey { } /// Initializes a new RSA key (backed by CryptoSwift) of the specified bit size - init(keySize: Int) throws { + internal init(keySize: Int) throws { switch keySize { case 1024: - self.key = CryptoSwift.RSA(keySize: keySize) + self.key = try CryptoSwift.RSA(keySize: keySize) case 2048: - self.key = CryptoSwift.RSA(keySize: keySize) + self.key = try CryptoSwift.RSA(keySize: keySize) case 3072: - self.key = CryptoSwift.RSA(keySize: keySize) + self.key = try CryptoSwift.RSA(keySize: keySize) case 4096: - self.key = CryptoSwift.RSA(keySize: keySize) + self.key = try CryptoSwift.RSA(keySize: keySize) default: throw NSError(domain: "Invalid RSA Key Bit Length. (Use one of 2048, 3072 or 4096)", code: 0) } } + init(keySize: LibP2PCrypto.Keys.RSABitLength) throws { + try self.init(keySize: keySize.bits) + } + /// Expects the ASN1 Encoding of the DER formatted RSA Private Key init(rawRepresentation raw: Data) throws { - guard case .sequence(let params) = try Asn1Parser.parse(data: raw) else { throw NSError(domain: "Invalid ASN1 Encoding -> No PrivKey Sequence", code: 0) } - // We check for 4 here because internally we can only marshal the first 4 integers at the moment... - guard params.count == 4 || params.count == 9 else { throw NSError(domain: "Invalid ASN1 Encoding -> Invalid Private RSA param count. Expected 9 got \(params.count)", code: 0) } - guard case .integer(let n) = params[1] else { throw NSError(domain: "Invalid ASN1 Encoding -> PrivKey No Modulus", code: 0) } - guard case .integer(let e) = params[2] else { throw NSError(domain: "Invalid ASN1 Encoding -> PrivKey No Public Exponent", code: 0) } - guard case .integer(let d) = params[3] else { throw NSError(domain: "Invalid ASN1 Encoding -> PrivKey No Private Exponent", code: 0) } - - self.key = RSA(n: n.bytes, e: e.bytes, d: d.bytes) + self.key = try RSA(rawRepresentation: raw) + guard self.key.d != nil else { throw NSError(domain: "Invalid Private Key", code: 0) } } init(marshaledData data: Data) throws { @@ -139,16 +126,8 @@ struct RSAPrivateKey:CommonPrivateKey { } var rawRepresentation: Data { - guard let d = key.d else { /*throw NSError(domain: "Not a valid private RSA Key", code: 0)*/ return Data() } - let mod = key.n.serialize() - let privkeyAsnNode:Asn1Parser.Node = - .sequence(nodes: [ - .integer(data: Data( Array(arrayLiteral: 0x00) )), - .integer(data: Data(CryptoSwift.RSA.zeroPad(n: mod.bytes, to: mod.count + 1))), - .integer(data: key.e.serialize()), - .integer(data: d.serialize()) - ]) - return Data(ASN1Encoder.encode(privkeyAsnNode)) + guard key.d != nil, let raw = try? self.key.externalRepresentation() else { return Data() } + return raw } func derivePublicKey() throws -> CommonPublicKey { @@ -165,11 +144,10 @@ struct RSAPrivateKey:CommonPrivateKey { } public func marshal() throws -> Data { - throw NSError(domain: "CryptoSwift based RSA private keys don't support marshaling", code: 0) - //var privateKey = PrivateKey() - //privateKey.type = .rsa - //privateKey.data = self.rawRepresentation - //return try privateKey.serializedData() + var privateKey = PrivateKey() + privateKey.type = .rsa + privateKey.data = self.rawRepresentation + return try privateKey.serializedData() } } @@ -192,14 +170,7 @@ extension CryptoSwift.RSA { /// - Note: The signature uses the SHA256 PKCS#1v15 Padding Scheme /// - Note: [EMSA-PKCS1-v1_5](https://datatracker.ietf.org/doc/html/rfc8017#section-9.2) fileprivate static func sign(message:Data, withKey key:RSA) throws -> Data { - guard let d = key.d else { throw NSError(domain: "Signing data requires a Private RSA key", code: 0) } - let encodedMessage = try CryptoSwift.RSA.hashedAndPKCSEncoded(message.bytes, modLength: key.n.serialize().count) - - let n = BigUInteger(Data(encodedMessage)) - let e = d - let m = key.n - let signedData_RsaKey = CryptoSwift.RSA.modPow(n: n, e: e, m: m).serialize() - return signedData_RsaKey + return try Data(key.sign(message.bytes, variant: .message_pkcs1v15_SHA256)) } /// Verifies a signature for the expected data @@ -207,93 +178,7 @@ extension CryptoSwift.RSA { /// - Note: This method assumes the signature was generated using the SHA256 PKCS#1v15 Padding Scheme /// - Note: [EMSA-PKCS1-v1_5](https://datatracker.ietf.org/doc/html/rfc8017#section-9.2) fileprivate static func verify(signature:Data, fromMessage message:Data, usingKey key:RSA) throws -> Bool { - let modLength = key.n.serialize().count - /// Step 1: Ensure the signature is the same length as the key's modulus - guard signature.count == modLength else { throw NSError(domain: "Invalid Signature Length", code: 0) } - - /// Step 2: 'Decrypt' the signature - let n = BigUInteger(signature) - let e = key.e - let m = key.n - let pkcsEncodedSHA256HashedMessage = CryptoSwift.RSA.modPow(n: n, e: e, m: m).serialize() - - /// Step 3: Compare the 'decrypted' signature with the prepared / encoded expected message.... - let preparedExpectedMessage = try CryptoSwift.RSA.hashedAndPKCSEncoded(message.bytes, modLength: modLength).dropFirst() - - guard pkcsEncodedSHA256HashedMessage == preparedExpectedMessage else { return false } - - return true - } - - /// prepends the data with zero's until it reaches the specified length - fileprivate static func zeroPad(n:[UInt8], to:Int) -> [UInt8] { - var modulus = n - while modulus.count < to { - modulus.insert(0x00, at: 0) - } - return modulus - } - - /// Modular exponentiation - /// - /// - Credit: AttaSwift BigInt - /// - Source: https://rosettacode.org/wiki/Modular_exponentiation#Swift - fileprivate static func modPow(n: T, e: T, m: T) -> T { - guard e != 0 else { - return 1 - } - - var res = T(1) - var base = n % m - var exp = e - - while true { - if exp & 1 == 1 { - res *= base - res %= m - } - - if exp == 1 { - return res - } - - exp /= 2 - base *= base - base %= m - } - } - - /// Hashes and Encodes a message for signing and verifying - /// - /// - Note: [EMSA-PKCS1-v1_5](https://datatracker.ietf.org/doc/html/rfc8017#section-9.2) - fileprivate static func hashedAndPKCSEncoded(_ message:[UInt8], modLength:Int) throws -> Data { - /// 1. Apply the hash function to the message M to produce a hash - let hashedMessage = SHA2(variant: .sha256).calculate(for: message) - - /// 2. Encode the algorithm ID for the hash function and the hash value into an ASN.1 value of type DigestInfo - /// PKCS#1_15 DER Structure (OID == sha256WithRSAEncryption) - let asn:Asn1Parser.Node = .sequence(nodes: [ - .sequence(nodes: [ - .objectIdentifier(data: Data(Array(arrayLiteral: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01))), - .null - ]), - .octetString(data: Data(hashedMessage)) - ]) - - let t = ASN1Encoder.encode(asn) - - /// 3. If emLen < tLen + 11, output "intended encoded message lengthtoo short" and stop - if modLength < t.count + 11 { throw NSError(domain: "intended encoded message length too short", code: 0) } - - /// 4. Generate an octet string PS consisting of emLen - tLen - 3 - /// octets with hexadecimal value 0xff. The length of PS will be - /// at least 8 octets. - let r = modLength - t.count - 3 - let padding = [0x00, 0x01] + Array(repeating: 0xFF, count: r) + [0x00] - - /// 5. Concatenate PS, the DER encoding T, and other padding to form - /// the encoded message EM as EM = 0x00 || 0x01 || PS || 0x00 || T. - return Data(padding + t) + return try key.verify(signature: signature.bytes, for: message.bytes, variant: .message_pkcs1v15_SHA256) } } diff --git a/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+DER.swift b/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+DER.swift new file mode 100644 index 0000000..81fc80d --- /dev/null +++ b/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+DER.swift @@ -0,0 +1,100 @@ +// +// RSA+DER.swift +// +// +// Created by Brandon Toms on 6/5/22. +// + +import Foundation + +extension RSAPublicKey:DERCodable { + /// RSA Object Identifier Bytes + public static var primaryObjectIdentifier: Array { [42, 134, 72, 134, 247, 13, 1, 1, 1] } + + public static var secondaryObjectIdentifier: Array? { nil } + + public func publicKeyDER() throws -> Array { + return self.rawRepresentation.bytes + } + + public func privateKeyDER() throws -> Array { + throw NSError(domain: "Public Key doesn't have private DER representation", code: 0) + } + + init(publicDER: Array) throws { + try self.init(rawRepresentation: Data(publicDER)) + } + + init(privateDER: Array) throws { + throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0) + } + + public func exportPublicKeyPEM(withHeaderAndFooter: Bool) throws -> Array { + let publicDER = try self.publicKeyDER() + + let base64String = publicDER.toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.publicKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.publicKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } +} + +extension RSAPrivateKey: DERCodable { + /// RSA Object Identifier Bytes + public static var primaryObjectIdentifier: Array { [42, 134, 72, 134, 247, 13, 1, 1, 1] } + + static var secondaryObjectIdentifier: Array? { nil } + + func publicKeyDER() throws -> Array { + try self.derivePublicKey().rawRepresentation.bytes + } + + func privateKeyDER() throws -> Array { + self.rawRepresentation.bytes + } + + init(publicDER: Array) throws { + throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0) + } + + init(privateDER: Array) throws { + try self.init(rawRepresentation: Data(privateDER)) + } + + public func exportPrivateKeyPEMRaw() throws -> Array { + let privateDER = try self.privateKeyDER() + let asnNodes:ASN1.Node = .sequence(nodes: [ + .integer(data: Data(hex: "0x00")), + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier)), + .null + ]), + .octetString(data: Data( privateDER )) + ]) + + return ASN1.Encoder.encode(asnNodes) + } + + public func exportPrivateKeyPEM(withHeaderAndFooter: Bool) throws -> Array { + let base64String = try self.exportPrivateKeyPEMRaw().toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.privateKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.privateKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } +} diff --git a/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+SecKey.swift b/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+SecKey.swift index 6b22a19..f89153d 100644 --- a/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+SecKey.swift +++ b/Sources/LibP2PCrypto/Keys/Types/RSA/RSA+SecKey.swift @@ -9,8 +9,6 @@ import Foundation import Multibase import Security -import RSAPublicKeyImporter -import RSAPublicKeyExporter struct RSAPublicKey:CommonPublicKey { static var keyType: LibP2PCrypto.Keys.GenericKeyType { .rsa } @@ -39,12 +37,25 @@ struct RSAPublicKey:CommonPublicKey { } init(marshaledData data: Data) throws { - try self.init(rawRepresentation: try RSAPublicKeyImporter().fromSubjectPublicKeyInfo(data)) + let asn = try ASN1.Decoder.decode(data: data) + guard case .sequence(let nodes) = asn else { throw NSError(domain: "RSAPublicKey Invalid marshaled data", code: 0) } + guard case .sequence(let subjectInfo) = nodes[0] else { throw NSError(domain: "RSAPublicKey Invalid marshaled data", code: 0) } + guard case .objectIdentifier(let objID) = subjectInfo.first else { throw NSError(domain: "RSAPublicKey Invalid marshaled data", code: 0) } + guard objID.bytes == RSAPublicKey.primaryObjectIdentifier else { throw NSError(domain: "RSAPublicKey Invalid marshaled data", code: 0) } + guard case .bitString(let bits) = nodes[1] else { throw NSError(domain: "RSAPublicKey Invalid marshaled data", code: 0) } + try self.init(rawRepresentation: bits) } var rawRepresentation: Data { - try! RSAPublicKeyExporter().toSubjectPublicKeyInfo(self.key.rawRepresentation()) - //try! self.key.rawRepresentation() + let asnNodes:ASN1.Node = try! .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(RSAPublicKey.primaryObjectIdentifier)), + .null + ]), + .bitString(data: self.key.rawRepresentation()) + ]) + + return Data(ASN1.Encoder.encode(asnNodes)) } func encrypt(data: Data) throws -> Data { @@ -216,36 +227,10 @@ internal extension SecKey { return cfdata as Data } else { throw NSError(domain: "RawKeyError: \(error.debugDescription)", code: 0, userInfo: nil) } } -} - -internal extension SecKey { - - /// Gets the ID of the key. - /// - /// The key id is the base58 encoding of the SHA-256 multihash of its public key. - /// The public key is a protobuf encoding containing a type and the DER encoding - /// of the PKCS SubjectPublicKeyInfo. -// func id(keyType: LibP2PCrypto.Keys.KeyPairType, withMultibasePrefix:Bool = true) throws -> String { -// -// guard let pubKey = SecKeyCopyPublicKey(self) else { -// throw NSError(domain: "Public Key Extraction Error", code: 0, userInfo: nil) -// } -// -// /// The key id is the base58 encoding of the SHA-256 multihash of its public key. -// /// The public key is a protobuf encoding containing a type and the DER encoding -// /// of the PKCS SubjectPublicKeyInfo. -// let marshaledPubKey = try LibP2PCrypto.Keys.marshalPublicKey(pubKey, keyType: keyType) -// let mh = try Multihash(raw: marshaledPubKey, hashedWith: .sha2_256) -// return withMultibasePrefix ? mh.asMultibase(.base58btc) : mh.asString(base: .base58btc) -// } var attributes:CFDictionary? { return SecKeyCopyAttributes(self) } - - func subjectKeyInfo() throws -> Data { - return try RSAPublicKeyExporter().toSubjectPublicKeyInfo(self.rawRepresentation()) - } } #endif diff --git a/Sources/LibP2PCrypto/Keys/Types/Secp256k1/Secp256k1.swift b/Sources/LibP2PCrypto/Keys/Types/Secp256k1/Secp256k1.swift index d50aaaa..4940d3c 100644 --- a/Sources/LibP2PCrypto/Keys/Types/Secp256k1/Secp256k1.swift +++ b/Sources/LibP2PCrypto/Keys/Types/Secp256k1/Secp256k1.swift @@ -110,3 +110,103 @@ extension Secp256k1PrivateKey:CommonPrivateKey { } } + + +extension Secp256k1PublicKey:DERCodable { + public static var primaryObjectIdentifier: Array { [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01] } + public static var secondaryObjectIdentifier: Array? { [0x2B, 0x81, 0x04, 0x00, 0x0A] } + + public convenience init(publicDER: Array) throws { + /// Expects a 0x0422 32byte long octetString as the rawRepresentation + try self.init(rawRepresentation: Data(publicDER)) + } + + public convenience init(privateDER: Array) throws { + throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0) + } + + public func publicKeyDER() throws -> Array { + [0x04] + self.rawRepresentation + } + + public func privateKeyDER() throws -> Array { + throw NSError(domain: "Public Key doesn't have private DER representation", code: 0) + } + + public func exportPublicKeyPEM(withHeaderAndFooter: Bool) throws -> Array { + let publicDER = try self.publicKeyDER() + + let asnNodes:ASN1.Node = .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier)), + .objectIdentifier(data: Data(Self.secondaryObjectIdentifier!)) + ]), + .bitString(data: Data( publicDER )) + ]) + + let base64String = ASN1.Encoder.encode(asnNodes).toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.publicKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.publicKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } +} + +extension Secp256k1PrivateKey:DERCodable { + public static var primaryObjectIdentifier: Array { [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A] } + public static var secondaryObjectIdentifier: Array? { nil } + + public convenience init(publicDER: Array) throws { + throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0) + } + + public convenience init(privateDER: Array) throws { + try self.init(rawRepresentation: Data(privateDER)) + } + + public func publicKeyDER() throws -> Array { + try self.publicKey.publicKeyDER() + } + + public func privateKeyDER() throws -> Array { + self.rawRepresentation.bytes + } + + public func exportPrivateKeyPEMRaw() throws -> Array { + let publicDER = try self.publicKeyDER() + + let pubKeyBitString:ASN1.Node = .bitString(data: Data( publicDER )) + + let asnNodes:ASN1.Node = .sequence(nodes: [ + .integer(data: Data(hex: "0x01")), + .octetString(data: self.rawRepresentation), + .ecObject(data: Data(Self.primaryObjectIdentifier)), + .ecBits(data: Data(ASN1.Encoder.encode(pubKeyBitString))) + ]) + + return ASN1.Encoder.encode(asnNodes) + } + + public func exportPrivateKeyPEM(withHeaderAndFooter: Bool) throws -> Array { + let base64String = try self.exportPrivateKeyPEMRaw().toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.ecPrivateKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.ecPrivateKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } + +} diff --git a/Sources/LibP2PCrypto/PEM/ASN1/ASN1.swift b/Sources/LibP2PCrypto/PEM/ASN1/ASN1.swift new file mode 100644 index 0000000..1455ae1 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/ASN1/ASN1.swift @@ -0,0 +1,92 @@ +// +// Original Asn1Parser.swift from SwiftyRSA +// +// Created by Lois Di Qual on 5/9/17. +// Copyright © 2017 Scoop. All rights reserved. +// +// Modified by Brandon Toms on 5/1/22 +// + +import Foundation + +enum ASN1 { + internal enum IDENTIFIERS:UInt8, Equatable { + case SEQUENCE = 0x30 + case INTERGER = 0x02 + case OBJECTID = 0x06 + case NULL = 0x05 + case BITSTRING = 0x03 + case OCTETSTRING = 0x04 + case EC_OBJECT = 0xA0 + case EC_BITS = 0xA1 + + static func == (lhs:UInt8, rhs:IDENTIFIERS) -> Bool { + lhs == rhs.rawValue + } + + var bytes:[UInt8] { + switch self { + case .NULL: + return [self.rawValue, 0x00] + default: + return [self.rawValue] + } + } + } + + /// An ASN1 node + enum Node:CustomStringConvertible { + /// An array of more `ASN1.Node`s + case sequence(nodes: [Node]) + /// An integer + case integer(data: Data) + /// An objectIdentifier + case objectIdentifier(data: Data) + /// A null object + case null + /// A bitString + case bitString(data: Data) + /// An octetString + case octetString(data: Data) + + //Exteneded Params + + /// Elliptic Curve specific objectIdentifier + case ecObject(data: Data) + /// Elliptic Curve specific bitString + case ecBits(data: Data) + + var description: String { + ASN1.printNode(self, level: 0) + } + + } + + internal static func printNode(_ node:ASN1.Node, level:Int) -> String { + var str:[String] = [] + let prefix = String(repeating: "\t", count: level) + switch node { + case .integer(let int): + str.append("\(prefix)Integer: \(int.toHexString())") + case .bitString(let bs): + str.append("\(prefix)BitString: \(bs.toHexString())") + case .null: + str.append("\(prefix)NULL") + case .objectIdentifier(let oid): + str.append("\(prefix)ObjectID: \(oid.toHexString())") + case .octetString(let os): + str.append("\(prefix)OctetString: \(os.toHexString())") + case .ecObject(let ecObj): + str.append("\(prefix)EC Object: \(ecObj.toHexString())") + case .ecBits(let ecBits): + str.append("\(prefix)EC Bits: \(ecBits.toHexString())") + case .sequence(let nodes): + str.append("\(prefix)Sequence:") + nodes.forEach { str.append(printNode($0, level: level + 1)) } + } + return str.joined(separator: "\n") + } +} + + + diff --git a/Sources/LibP2PCrypto/PEM/ASN1/ASN1Decoder.swift b/Sources/LibP2PCrypto/PEM/ASN1/ASN1Decoder.swift new file mode 100644 index 0000000..86f0221 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/ASN1/ASN1Decoder.swift @@ -0,0 +1,130 @@ +// +// File.swift +// +// +// Created by Brandon Toms on 5/28/22. +// + +import Foundation + +extension ASN1 { + /// A simple ASN1 parser that will recursively iterate over a root node and return a Node tree. + /// The root node can be any of the supported nodes described in `Node`. If the parser encounters a sequence + /// it will recursively parse its children. + enum Decoder { + + enum DecodingError: Error { + case noType + case invalidType(value: UInt8) + } + + /// Parses ASN1 data and returns its root node. + /// + /// - Parameter data: ASN1 data to parse + /// - Returns: Root ASN1 Node + /// - Throws: A DecodingError if anything goes wrong, or if an unknown node was encountered + static func decode(data: Data) throws -> Node { + let scanner = Scanner(data: data) + let node = try decodeNode(scanner: scanner) + return node + } + + /// Parses an ASN1 given an existing scanner. + /// @warning: this will modify the state (ie: position) of the provided scanner. + /// + /// - Parameter scanner: Scanner to use to consume the data + /// - Returns: Parsed node + /// - Throws: A DecodingError if anything goes wrong, or if an unknown node was encountered + private static func decodeNode(scanner: Scanner) throws -> Node { + + let firstByte = try scanner.consume(length: 1).firstByte + + // Sequence + if firstByte == IDENTIFIERS.SEQUENCE { + let length = try scanner.consumeLength() + let data = try scanner.consume(length: length) + let nodes = try decodeSequence(data: data) + return .sequence(nodes: nodes) + } + + // Integer + if firstByte == IDENTIFIERS.INTERGER { + let length = try scanner.consumeLength() + let data = try scanner.consume(length: length) + return .integer(data: data) + } + + // Object identifier + if firstByte == IDENTIFIERS.OBJECTID { + let length = try scanner.consumeLength() + let data = try scanner.consume(length: length) + return .objectIdentifier(data: data) + } + + // Null + if firstByte == IDENTIFIERS.NULL { + _ = try scanner.consume(length: 1) + return .null + } + + // Bit String + if firstByte == IDENTIFIERS.BITSTRING { + let length = try scanner.consumeLength() + + // There's an extra byte (0x00) after the bit string length in all the keys I've encountered. + // I couldn't find a specification that referenced this extra byte, but let's consume it and discard it. + _ = try scanner.consume(length: 1) + + let data = try scanner.consume(length: length - 1) + return .bitString(data: data) + } + + // Octet String + if firstByte == IDENTIFIERS.OCTETSTRING { + let length = try scanner.consumeLength() + let data = try scanner.consume(length: length) + return .octetString(data: data) + } + + // EC Curves Cont 0 identifier (obj id) + if firstByte == IDENTIFIERS.EC_OBJECT { + let length = try scanner.consumeLength() + let data = try scanner.consume(length: length) + //print("Found an EC Curve Obj ID: [\(data.map { "\($0)" }.joined(separator: ","))]") + //return .ecObject(data: data) + return .objectIdentifier(data: data) + } + + // EC Curves Cont 1 identifier (bit string) + if firstByte == IDENTIFIERS.EC_BITS { + let length = try scanner.consumeLength() + + // There's an extra byte (0x00) after the bit string length in all the keys I've encountered. + // I couldn't find a specification that referenced this extra byte, but let's consume it and discard it. + _ = try scanner.consume(length: 1) + + let data = try scanner.consume(length: length - 1) + //print("Found an EC Curve Bit String: [\(data.map { "\($0)" }.joined(separator: ","))]") + //return .bitString(data: data) + return .ecBits(data: data) + } + + throw DecodingError.invalidType(value: firstByte) + } + + /// Parses an ASN1 sequence and returns its child nodes + /// + /// - Parameter data: ASN1 data + /// - Returns: A list of ASN1 nodes + /// - Throws: A DecodingError if anything goes wrong, or if an unknown node was encountered + private static func decodeSequence(data: Data) throws -> [Node] { + let scanner = Scanner(data: data) + var nodes: [Node] = [] + while !scanner.isComplete { + let node = try decodeNode(scanner: scanner) + nodes.append(node) + } + return nodes + } + } +} diff --git a/Sources/LibP2PCrypto/PEM/ASN1/ASN1Encoder.swift b/Sources/LibP2PCrypto/PEM/ASN1/ASN1Encoder.swift new file mode 100644 index 0000000..2e14fd7 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/ASN1/ASN1Encoder.swift @@ -0,0 +1,55 @@ +// +// File.swift +// +// +// Created by Brandon Toms on 5/28/22. +// + +import Foundation + +extension ASN1 { + enum Encoder { + /// Encodes an ASN1Node into it's byte representation + /// + /// - Parameter node: The Node to encode + /// - Returns: The encoded bytes as a UInt8 array + public static func encode(_ node:ASN1.Node) -> [UInt8] { + switch node { + case .integer(let integer): + return IDENTIFIERS.INTERGER.bytes + asn1LengthPrefixed(integer.bytes) + case .bitString(let bits): + return IDENTIFIERS.BITSTRING.bytes + asn1LengthPrefixed([0x00] + bits.bytes) + case .octetString(let octet): + return IDENTIFIERS.OCTETSTRING.bytes + asn1LengthPrefixed(octet.bytes) + case .null: + return IDENTIFIERS.NULL.bytes + case .objectIdentifier(let oid): + return IDENTIFIERS.OBJECTID.bytes + asn1LengthPrefixed(oid.bytes) + case .ecObject(let ecObj): + return IDENTIFIERS.EC_OBJECT.bytes + asn1LengthPrefixed(ecObj.bytes) + case .ecBits(let ecBits): + return IDENTIFIERS.EC_BITS.bytes + asn1LengthPrefixed(ecBits.bytes) + case .sequence(let nodes): + return IDENTIFIERS.SEQUENCE.bytes + asn1LengthPrefixed( nodes.reduce(into: Array(), { partialResult, node in + partialResult += encode(node) + })) + } + } + + /// Calculates and returns the ASN.1 length Prefix for a chunk of data + private static func asn1LengthPrefix(_ bytes:[UInt8]) -> [UInt8] { + if bytes.count >= 0x80 { + var lengthAsBytes = withUnsafeBytes(of: bytes.count.bigEndian, Array.init) + while lengthAsBytes.first == 0 { lengthAsBytes.removeFirst() } + return [(0x80 + UInt8(lengthAsBytes.count))] + lengthAsBytes + } else { + return [UInt8(bytes.count)] + } + } + + /// Returns the provided bytes with the appropriate ASN.1 length prefix prepended + private static func asn1LengthPrefixed(_ bytes:[UInt8]) -> [UInt8] { + asn1LengthPrefix(bytes) + bytes + } + } +} diff --git a/Sources/LibP2PCrypto/PEM/ASN1/ASN1Scanner.swift b/Sources/LibP2PCrypto/PEM/ASN1/ASN1Scanner.swift new file mode 100644 index 0000000..2dd820e --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/ASN1/ASN1Scanner.swift @@ -0,0 +1,113 @@ +// +// ASN1Scanner.swift +// +// +// Created by Brandon Toms on 5/28/22. +// + +import Foundation + + +/// Simple data scanner that consumes bytes from a raw data and keeps an updated position. +internal class Scanner { + + enum ScannerError: Error { + case outOfBounds + } + + let data: Data + var index: Int = 0 + + /// Returns whether there is no more data to consume + var isComplete: Bool { + return index >= data.count + } + + /// Creates a scanner with provided data + /// + /// - Parameter data: Data to consume + init(data: Data) { + self.data = data + } + + /// Consumes data of provided length and returns it + /// + /// - Parameter length: length of the data to consume + /// - Returns: data consumed + /// - Throws: ScannerError.outOfBounds error if asked to consume too many bytes + func consume(length: Int) throws -> Data { + + guard length > 0 else { + return Data() + } + + guard index + length <= data.count else { + throw ScannerError.outOfBounds + } + + let subdata = data.subdata(in: index.. Int { + + let lengthByte = try consume(length: 1).firstByte + + // If the first byte's value is less than 0x80, it directly contains the length + // so we can return it + guard lengthByte >= 0x80 else { + return Int(lengthByte) + } + + // If the first byte's value is more than 0x80, it indicates how many following bytes + // will describe the length. For instance, 0x85 indicates that 0x85 - 0x80 = 0x05 = 5 + // bytes will describe the length, so we need to read the 5 next bytes and get their integer + // value to determine the length. + let nextByteCount = lengthByte - 0x80 + let length = try consume(length: Int(nextByteCount)) + + return length.integer + } +} + +internal extension Data { + + /// Returns the first byte of the current data + var firstByte: UInt8 { + var byte: UInt8 = 0 + copyBytes(to: &byte, count: MemoryLayout.size) + return byte + } + + /// Returns the integer value of the current data. + /// @warning: this only supports data up to 4 bytes, as we can only extract 32-bit integers. + var integer: Int { + + guard count > 0 else { + return 0 + } + + var int: UInt32 = 0 + var offset: Int32 = Int32(count - 1) + forEach { byte in + let byte32 = UInt32(byte) + let shifted = byte32 << (UInt32(offset) * 8) + int = int | shifted + offset -= 1 + } + + return Int(int) + } +} diff --git a/Sources/LibP2PCrypto/PEM/DER.swift b/Sources/LibP2PCrypto/PEM/DER.swift new file mode 100644 index 0000000..74f466a --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/DER.swift @@ -0,0 +1,240 @@ +// +// DER.swift +// +// +// Created by Brandon Toms on 5/28/22. +// + +import Foundation + +/// Conform to this protocol if your type can be instantiated from a ASN1 DER representation +public protocol DERDecodable { + /// The keys ASN1 object identifier (ex: RSA --> rsaEncryption --> [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]) + static var primaryObjectIdentifier:Array { get } + /// The keys ASN1 object identifier (ex: RSA --> rsaEncryption --> [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]) + static var secondaryObjectIdentifier:Array? { get } + /// Instantiates an instance of your Public Key when given a DER representation of your Public Key + init(publicDER: Array) throws + /// Instantiates an instance of your Private Key when given a DER representation of your Private Key + init(privateDER: Array) throws + /// Instantiates a DERDecodable Key from a PEM string + init(pem: String, password: String?, asType:Key.Type) throws + /// Instantiates a DERDecodable Key from ut8 decoded PEM data + init(pem: Data, password: String?, asType:Key.Type) throws +} + +public extension DERDecodable { + /// Instantiates a DERDecodable Key from a PEM string + /// - Parameters: + /// - pem: The PEM file to import + /// - password: A password to use to decrypt an encrypted PEM file + /// - asType: The underlying DERDecodable Key Type (ex: RSA.self) + init(pem: String, password: String? = nil, asType:Key.Type = Key.self) throws { + try self.init(pem: pem.bytes, password: password, asType: Key.self) + } + + /// Instantiates a DERDecodable Key from ut8 decoded PEM data + /// - Parameters: + /// - pem: The PEM file to import + /// - password: A password to use to decrypt an encrypted PEM file + /// - asType: The underlying DERDecodable Key Type (ex: RSA.self) + init(pem: Data, password: String? = nil, asType:Key.Type = Key.self) throws { + try self.init(pem: pem.bytes, password: password, asType: Key.self) + } + + /// Instantiates a DERDecodable Key from ut8 decoded PEM bytes + /// - Parameters: + /// - pem: The PEM file to import + /// - password: A password to use to decrypt an encrypted PEM file + /// - asType: The underlying DERDecodable Key Type (ex: RSA.self) + init(pem: Array, password: String? = nil, asType:Key.Type = Key.self) throws { + let (type, bytes, _) = try PEM.pemToData(pem) + + if password != nil { + guard type == .encryptedPrivateKey else { throw PEM.Error.invalidParameters } + } + + switch type { + case .publicRSAKeyDER: + // Ensure the objectIdentifier is rsaEncryption + try self.init(publicDER: bytes) + case .privateRSAKeyDER: + // Ensure the objectIdentifier is rsaEncryption + try self.init(privateDER: bytes) + case .publicKey: + let der = try PEM.decodePublicKeyPEM(Data(bytes), expectedPrimaryObjectIdentifier: Key.primaryObjectIdentifier, expectedSecondaryObjectIdentifier: Key.secondaryObjectIdentifier) + try self.init(publicDER: der) + case .privateKey, .ecPrivateKey: + let der = try PEM.decodePrivateKeyPEM(Data(bytes), expectedPrimaryObjectIdentifier: Key.primaryObjectIdentifier, expectedSecondaryObjectIdentifier: Key.secondaryObjectIdentifier) + try self.init(privateDER: der) + case .encryptedPrivateKey: + // Decrypt the encrypted PEM and attempt to instantiate it again... + + // Ensure we were provided a password + guard let password = password else { throw PEM.Error.invalidParameters } + + // Parse out Encryption Strategy and CipherText + let decryptionStategy = try PEM.decodeEncryptedPEM(Data(bytes)) // RSA.decodeEncryptedPEM(Data(bytes)) + + // Derive Encryption Key from Password + let key = try decryptionStategy.pbkdfAlgorithm.deriveKey(password: password, ofLength: decryptionStategy.cipherAlgorithm.desiredKeyLength) + + // Decrypt CipherText + let decryptedPEM = try decryptionStategy.cipherAlgorithm.decrypt(bytes: decryptionStategy.ciphertext, withKey: key) + + // Proceed with the unencrypted PEM (can public PEM keys be encrypted as well, wouldn't really make sense but idk if we should support it)? + let der = try PEM.decodePrivateKeyPEM(Data(decryptedPEM), expectedPrimaryObjectIdentifier: Key.primaryObjectIdentifier, expectedSecondaryObjectIdentifier: Key.secondaryObjectIdentifier) + + try self.init(privateDER: der) + } + } +} + +/// Conform to this protocol if your type can be described in an ASN1 DER representation +public protocol DEREncodable { + /// The keys ASN1 object identifier (ex: RSA --> rsaEncryption --> [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]) + static var primaryObjectIdentifier:Array { get } + /// The keys ASN1 object identifier (ex: RSA --> null --> nil) + static var secondaryObjectIdentifier:Array? { get } + + func publicKeyDER() throws -> Array + func privateKeyDER() throws -> Array + + /// The raw ASN1 Encoded PEM data without headers, footers and line breaks + func exportPrivateKeyPEMRaw() throws -> Array + + /// PublicKey PEM Export Functions + func exportPublicKeyPEM(withHeaderAndFooter:Bool) throws -> Array + func exportPublicKeyPEMString(withHeaderAndFooter:Bool) throws -> String + + /// PrivateKey PEM Export Functions + func exportPrivateKeyPEM(withHeaderAndFooter:Bool) throws -> Array + func exportPrivateKeyPEMString(withHeaderAndFooter:Bool) throws -> String +} + +public extension DEREncodable { + + internal func exportPublicKeyPEMRaw() throws -> Array { + let publicDER = try self.publicKeyDER() + let secondaryObject:ASN1.Node? + if Self.primaryObjectIdentifier == RSAPublicKey.primaryObjectIdentifier { + secondaryObject = .null + } else if Self.primaryObjectIdentifier == Secp256k1PublicKey.primaryObjectIdentifier { + secondaryObject = .objectIdentifier(data: Data(Self.secondaryObjectIdentifier!)) + } else { + secondaryObject = nil + } + + let asnNodes:ASN1.Node + if let secObj = secondaryObject { + asnNodes = .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier)), + secObj + ]), + .bitString(data: Data( publicDER )) + ]) + } else { + asnNodes = .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier)) + ]), + .bitString(data: Data( publicDER )) + ]) + } + + return ASN1.Encoder.encode(asnNodes) + } + + func exportPublicKeyPEM(withHeaderAndFooter:Bool = true) throws -> Array { + let base64String = try self.exportPublicKeyPEMRaw().toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.publicKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.publicKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } + + func exportPublicKeyPEMString(withHeaderAndFooter:Bool = true) throws -> String { + let publicPEMData = try exportPublicKeyPEM(withHeaderAndFooter: withHeaderAndFooter) + guard let pemAsString = String(data: Data(publicPEMData), encoding: .utf8) else { + throw PEM.Error.encodingError + } + return pemAsString + } + + func exportPrivateKeyPEMRaw() throws -> Array { + let privateDER = try self.privateKeyDER() + let asnNodes:ASN1.Node = .sequence(nodes: [ + .integer(data: Data(hex: "0x00")), + .sequence(nodes: [ + .objectIdentifier(data: Data(Self.primaryObjectIdentifier)), + //.null + ]), + .octetString(data: Data( privateDER )) + ]) + + return ASN1.Encoder.encode(asnNodes) + } + + func exportPrivateKeyPEM(withHeaderAndFooter:Bool = true) throws -> Array { + let base64String = try self.exportPrivateKeyPEMRaw().toBase64() + let bodyString = base64String.chunks(ofCount: 64).joined(separator: "\n") + let bodyUTF8Bytes = bodyString.bytes + + if withHeaderAndFooter { + let header = PEM.PEMType.privateKey.headerBytes + [0x0a] + let footer = [0x0a] + PEM.PEMType.privateKey.footerBytes + + return header + bodyUTF8Bytes + footer + } else { + return bodyUTF8Bytes + } + } + + func exportPrivateKeyPEMString(withHeaderAndFooter:Bool = true) throws -> String { + let privatePEMData = try exportPrivateKeyPEM(withHeaderAndFooter: withHeaderAndFooter) + guard let pemAsString = String(data: Data(privatePEMData), encoding: .utf8) else { + throw PEM.Error.encodingError + } + return pemAsString + } +} + +/// Conform to this protocol if your type can both be instantiated and expressed as an ASN1 DER representation. +public protocol DERCodable: DERDecodable, DEREncodable { } + +struct DER { + /// Integer to Octet String Primitive + /// - Parameters: + /// - x: nonnegative integer to be converted + /// - size: intended length of the resulting octet string + /// - Returns: corresponding octet string of length xLen + /// - Note: https://datatracker.ietf.org/doc/html/rfc3447#section-4.1 + internal static func i2osp(x:[UInt8], size:Int) -> [UInt8] { + var modulus = x + while modulus.count < size { + modulus.insert(0x00, at: 0) + } + if modulus[0] >= 0x80 { + modulus.insert(0x00, at: 0) + } + return modulus + } + + /// Integer to Octet String Primitive + /// - Parameters: + /// - x: nonnegative integer to be converted + /// - size: intended length of the resulting octet string + /// - Returns: corresponding octet string of length xLen + /// - Note: https://datatracker.ietf.org/doc/html/rfc3447#section-4.1 + internal static func i2ospData(x:[UInt8], size:Int) -> Data { + return Data(DER.i2osp(x: x, size: size)) + } +} diff --git a/Sources/LibP2PCrypto/PEM/PEM+Cipher.swift b/Sources/LibP2PCrypto/PEM/PEM+Cipher.swift new file mode 100644 index 0000000..f3fdda9 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/PEM+Cipher.swift @@ -0,0 +1,109 @@ +// +// PEM+Cipher.swift +// +// +// Created by Brandon Toms on 7/1/22. +// + +import Foundation +import CryptoSwift + +// MARK: Encrypted PEM Cipher Algorithms + +extension PEM { + // MARK: Add support for new Cipher Algorithms here... + internal enum CipherAlgorithm { + case aes_128_cbc(iv:[UInt8]) + case aes_256_cbc(iv:[UInt8]) + //case des3(iv: [UInt8]) + + init(objID:[UInt8], iv:[UInt8]) throws { + switch objID { + case [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02]: // aes-128-cbc + self = .aes_128_cbc(iv: iv) + case [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a]: // aes-256-cbc + self = .aes_256_cbc(iv: iv) + //case [42, 134, 72, 134, 247, 13, 3, 7]: + // self = .des3(iv: iv) + default: + throw Error.unsupportedCipherAlgorithm(objID) + } + } + + func decrypt(bytes: [UInt8], withKey key:[UInt8]) throws -> [UInt8] { + switch self { + case .aes_128_cbc(let iv): + //print("128 IV: \(iv)") + return try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt(bytes) + case .aes_256_cbc(let iv): + //print("256 IV: \(iv)") + return try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt(bytes) + //default: + //throw Error.invalidPEMFormat + } + } + + func encrypt(bytes: [UInt8], withKey key:[UInt8]) throws -> [UInt8] { + switch self { + case .aes_128_cbc(let iv): + return try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).encrypt(bytes) + case .aes_256_cbc(let iv): + return try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).encrypt(bytes) + } + } + + /// The key length used for this Cipher strategy + /// - Note: we need this information when deriving the key using our PBKDF strategy + var desiredKeyLength:Int { + switch self { + case .aes_128_cbc: return 16 + case .aes_256_cbc: return 32 + } + } + + var objectIdentifier:[UInt8] { + switch self { + case .aes_128_cbc: + return [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02] + case .aes_256_cbc: + return [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a] + } + } + + var iv:[UInt8] { + switch self { + case .aes_128_cbc(let iv): + return iv + case .aes_256_cbc(let iv): + return iv + } + } + + func encodeCipher() throws -> ASN1.Node { + return .sequence(nodes: [ + .objectIdentifier(data: Data(self.objectIdentifier)), + .octetString(data: Data(self.iv)) + ]) + } + } + + /// Decodes the Cipher ASN1 Block in an Encrypted Private Key PEM file + /// - Parameter node: The ASN1 sequence node containing the cipher parameters + /// - Returns: The CipherAlogrithm if supported + /// + /// Expects an ASN1.Node with the following structure + /// ``` + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.objectIdentifier(data: 9 bytes), //des-ede3-cbc + /// ASN1.Parser.Node.octetString(data: 16 bytes) //IV + /// ]) + /// ``` + internal static func decodeCipher(_ node:ASN1.Node) throws -> CipherAlgorithm { + guard case .sequence(let params) = node else { throw Error.invalidPEMFormat("EncryptedPrivateKey::CIPHER") } + guard params.count == 2 else { throw Error.invalidPEMFormat("EncryptedPrivateKey::CIPHER") } + guard case .objectIdentifier(let objID) = params.first else { throw Error.invalidPEMFormat("EncryptedPrivateKey::CIPHER") } + guard case .octetString(let initialVector) = params.last else { throw Error.invalidPEMFormat("EncryptedPrivateKey::CIPHER") } + + return try CipherAlgorithm(objID: objID.bytes, iv: initialVector.bytes) + } +} diff --git a/Sources/LibP2PCrypto/PEM/PEM+Encrypted.swift b/Sources/LibP2PCrypto/PEM/PEM+Encrypted.swift new file mode 100644 index 0000000..da9ffa8 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/PEM+Encrypted.swift @@ -0,0 +1,103 @@ +// +// PEM+Encrypted.swift +// +// +// Created by Brandon Toms on 7/1/22. +// + +import Foundation +import CryptoSwift + +// MARK: Encrypted PEM + +extension PEM { + + internal struct EncryptedPEM { + let objectIdentifer:[UInt8] + let ciphertext:[UInt8] + let pbkdfAlgorithm:PBKDFAlgorithm + let cipherAlgorithm:CipherAlgorithm + } + + /// Attempts to decode an encrypted Private Key PEM, returning all of the information necessary to decrypt the encrypted PEM + /// - Parameter encryptedPEM: The raw base64 decoded PEM data + /// - Returns: An `EncryptedPEM` Struct containing the ciphertext, the pbkdf alogrithm for key derivation, the cipher algorithm for decrypting and the objectIdentifier describing the contents of this PEM data + /// + /// To decrypt an encrypted PEM Private Key... + /// 1) Strip the headers of the PEM and base64 decode the data + /// 2) Parse the data via ASN1 looking for both the pbkdf and cipher algorithms, their respective parameters (salt, iv and itterations) and the ciphertext (aka octet string)) + /// 3) Derive the encryption key using the appropriate pbkdf alogorithm, found in step 2 + /// 4) Use the encryption key to instantiate the appropriate cipher algorithm, also found in step 2 + /// 5) Decrypt the encrypted ciphertext (the contents of the octetString node) + /// 6) The decrypted octet string can now be handled like any other Private Key PEM + /// + /// ``` + /// sequence(nodes: [ + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.objectIdentifier(data: 9 bytes), // PEM's ObjectIdentifier + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.objectIdentifier(data: 9 bytes), // PBKDF Algorithm + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.octetString(data: 8 bytes), // SALT + /// ASN1.Parser.Node.integer(data: 2 bytes) // ITERATIONS + /// ]) + /// ]), + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.objectIdentifier(data: 9 bytes), // Cipher Algorithm (ex: des-ede3-cbc) + /// ASN1.Parser.Node.octetString(data: 16 bytes) // Initial Vector (IV) + /// ]) + /// ]) + /// ]), + /// ASN1.Parser.Node.octetString(data: 640 bytes) + /// ]) + /// ``` + internal static func decodeEncryptedPEM(_ encryptedPEM:Data) throws -> EncryptedPEM { + let asn = try ASN1.Decoder.decode(data: encryptedPEM) + + guard case .sequence(let encryptedPEMWrapper) = asn else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + guard encryptedPEMWrapper.count == 2 else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + guard case .sequence(let encryptionInfoWrapper) = encryptedPEMWrapper.first else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + guard encryptionInfoWrapper.count == 2 else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + guard case .objectIdentifier(let objID) = encryptionInfoWrapper.first else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + guard case .sequence(let encryptionAlgorithmsWrapper) = encryptionInfoWrapper.last else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + guard encryptionAlgorithmsWrapper.count == 2 else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + let pbkdf = try decodePBKFD(encryptionAlgorithmsWrapper.first!) + let cipher = try decodeCipher(encryptionAlgorithmsWrapper.last!) + guard case .octetString(let octets) = encryptedPEMWrapper.last else { throw Error.invalidPEMFormat("EncryptedPrivateKey::") } + + return EncryptedPEM(objectIdentifer: objID.bytes, ciphertext: octets.bytes, pbkdfAlgorithm: pbkdf, cipherAlgorithm: cipher) + } + + internal static func encryptPEM(_ pem:Data, withPassword password:String, usingPBKDF pbkdf:PBKDFAlgorithm = .pbkdf2(salt: try! LibP2PCrypto.randomBytes(length: 8), iterations: 2048), andCipher cipher:CipherAlgorithm = .aes_128_cbc(iv: try! LibP2PCrypto.randomBytes(length: 16))) throws -> Data { + + // Generate Encryption Key from Password + let key = try pbkdf.deriveKey(password: password, ofLength: cipher.desiredKeyLength) + + // Encrypt Plaintext + let ciphertext = try cipher.encrypt(bytes: pem.bytes, withKey: key) + + // Encode Encrypted PEM (including pbkdf and cipher algos used) + let nodes:ASN1.Node = .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(hex: "2a864886f70d01050d")), + .sequence(nodes: [ + try pbkdf.encodePBKDF(), + try cipher.encodeCipher() + ]) + ]), + .octetString(data: Data(ciphertext)) + ]) + + let encoded = ASN1.Encoder.encode(nodes) + + let base64 = "\n" + encoded.toBase64().split(intoChunksOfLength: 64).joined(separator: "\n") + "\n" + + return Data(PEM.PEMType.encryptedPrivateKey.headerBytes + base64.bytes + PEM.PEMType.encryptedPrivateKey.footerBytes) + } + + internal static func encryptPEMString(_ pem:Data, withPassword password:String, usingPBKDF pbkdf:PBKDFAlgorithm = .pbkdf2(salt: try! LibP2PCrypto.randomBytes(length: 8), iterations: 2048), andCipher cipher:CipherAlgorithm = .aes_128_cbc(iv: try! LibP2PCrypto.randomBytes(length: 16))) throws -> String { + let data = try PEM.encryptPEM(pem, withPassword: password, usingPBKDF: pbkdf, andCipher: cipher) + return String(data: data, encoding: .utf8)! + } +} diff --git a/Sources/LibP2PCrypto/PEM/PEM+PBKDF.swift b/Sources/LibP2PCrypto/PEM/PEM+PBKDF.swift new file mode 100644 index 0000000..d2d69d1 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/PEM+PBKDF.swift @@ -0,0 +1,97 @@ +// +// PEM+PBKDF.swift +// +// +// Created by Brandon Toms on 7/1/22. +// + +import Foundation +import CryptoSwift + +// MARK: Encrypted PEM PBKDF Algorithms + +extension PEM { + // MARK: Add support for new PBKDF Algorithms here... + internal enum PBKDFAlgorithm { + case pbkdf2(salt: [UInt8], iterations: Int) + + init(objID:[UInt8], salt:[UInt8], iterations:[UInt8]) throws { + guard let iterations = Int(iterations.toHexString(), radix: 16) else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + switch objID { + case [42, 134, 72, 134, 247, 13, 1, 5, 12]: // pbkdf2 + self = .pbkdf2(salt: salt, iterations: iterations) + default: + throw Error.unsupportedPBKDFAlgorithm(objID) + } + } + + func deriveKey(password:String, ofLength keyLength:Int, usingHashVarient variant:HMAC.Variant = .sha1) throws -> [UInt8] { + switch self { + case .pbkdf2(let salt, let iterations): + //print("Salt: \(salt), Iterations: \(iterations)") + let key = try PKCS5.PBKDF2(password: password.bytes, salt: salt, iterations: iterations, keyLength: keyLength, variant: variant).calculate() + //print(key) + return key + //default: + // throw Error.invalidPEMFormat + } + } + + var objectIdentifier:[UInt8] { + switch self { + case .pbkdf2: + return [42, 134, 72, 134, 247, 13, 1, 5, 12] + } + } + + var salt:[UInt8] { + switch self { + case .pbkdf2(let salt, _): + return salt + } + } + + var iterations:Int { + switch self { + case .pbkdf2(_, let iterations): + return iterations + } + } + + func encodePBKDF() throws -> ASN1.Node { + return .sequence(nodes: [ + .objectIdentifier(data: Data(self.objectIdentifier)), + .sequence(nodes: [ + .octetString(data: Data(self.salt)), + .integer(data: Data(self.iterations.bytes(totalBytes: 2))) + ]) + ]) + } + } + + /// Decodes the PBKDF ASN1 Block in an Encrypted Private Key PEM file + /// - Parameter node: The ASN1 sequence node containing the pbkdf parameters + /// - Returns: The PBKDFAlogrithm if supported + /// + /// Expects an ASN1.Node with the following structure + /// ``` + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.objectIdentifier(data: 9 bytes), //PBKDF2 //[42,134,72,134,247,13,1,5,12] + /// ASN1.Parser.Node.sequence(nodes: [ + /// ASN1.Parser.Node.octetString(data: 8 bytes), //SALT + /// ASN1.Parser.Node.integer(data: 2 bytes) //ITTERATIONS + /// ]) + /// ]) + /// ``` + internal static func decodePBKFD(_ node:ASN1.Node) throws -> PBKDFAlgorithm { + guard case .sequence(let wrapper) = node else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + guard wrapper.count == 2 else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + guard case .objectIdentifier(let objID) = wrapper.first else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + guard case .sequence(let params) = wrapper.last else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + guard params.count == 2 else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + guard case .octetString(let salt) = params.first else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + guard case .integer(let iterations) = params.last else { throw Error.invalidPEMFormat("EncryptedPrivateKey::PBKDF") } + + return try PBKDFAlgorithm(objID: objID.bytes, salt: salt.bytes, iterations: iterations.bytes) + } +} diff --git a/Sources/LibP2PCrypto/PEM/PEM.swift b/Sources/LibP2PCrypto/PEM/PEM.swift new file mode 100644 index 0000000..9166f09 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/PEM.swift @@ -0,0 +1,324 @@ +// +// PEM.swift +// +// +// Created by Brandon Toms on 5/28/22. +// + +import Foundation +import CryptoSwift + +struct PEM { + + public enum Error: Swift.Error { + /// An error occured while encoding the PEM file + case encodingError + /// An error occured while decoding the PEM file + case decodingError + /// Encountered an unsupported PEM type + case unsupportedPEMType + /// Encountered an invalid/unexpected PEM format + case invalidPEMFormat(String? = nil) + /// Encountered an invalid/unexpected PEM header string/delimiter + case invalidPEMHeader + /// Encountered an invalid/unexpected PEM footer string/delimiter + case invalidPEMFooter + /// Encountered a invalid/unexpected parameters while attempting to decode a PEM file + case invalidParameters + /// Encountered an unsupported Cipher algorithm while attempting to decrypt an encrypted PEM file + case unsupportedCipherAlgorithm([UInt8]) + /// Encountered an unsupported Password Derivation algorithm while attempting to decrypt an encrypted PEM file + case unsupportedPBKDFAlgorithm([UInt8]) + /// The instiating types objectIdentifier does not match that of the PEM file + case objectIdentifierMismatch(got:[UInt8], expected:[UInt8]) + } + + // MARK: Add support for additional PEM types here + + /// General PEM Classification + internal enum PEMType { + // Direct DER Exports for RSA Keys (special case) + case publicRSAKeyDER + case privateRSAKeyDER + + // Generale PEM Headers + case publicKey + case privateKey + case encryptedPrivateKey + case ecPrivateKey + + // Others + //case certificate + + init(headerBytes: ArraySlice) throws { + guard headerBytes.count > 10 else { throw PEM.Error.unsupportedPEMType } + let bytes = headerBytes.dropFirst(5).dropLast(5) + switch bytes { + //"BEGIN RSA PUBLIC KEY" + case [0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x52, 0x53, 0x41, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59]: + self = .publicRSAKeyDER + + //"BEGIN RSA PRIVATE KEY" + case [0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x52, 0x53, 0x41, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59]: + self = .privateRSAKeyDER + + //"BEGIN PUBLIC KEY" + case [0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59]: + self = .publicKey + + //"BEGIN PRIVATE KEY" + case [0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59]: + self = .privateKey + + //"BEGIN ENCRYPTED PRIVATE KEY" + case [0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x45, 0x44, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59]: + self = .encryptedPrivateKey + + //"BEGIN EC PRIVATE KEY" + case [0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x45, 0x43, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59]: + self = .ecPrivateKey + + default: + print("Unsupported PEM Type: \(Data(bytes).toHexString())") + throw PEM.Error.unsupportedPEMType + } + } + + /// This PEM type's header string (expressed as the utf8 decoded byte representation) + var headerBytes:Array { + switch self { + case .publicRSAKeyDER: + return "-----BEGIN RSA PUBLIC KEY-----".bytes + case .privateRSAKeyDER: + return "-----BEGIN RSA PRIVATE KEY-----".bytes + case .publicKey: + return "-----BEGIN PUBLIC KEY-----".bytes + case .privateKey: + return "-----BEGIN PRIVATE KEY-----".bytes + case .encryptedPrivateKey: + return "-----BEGIN ENCRYPTED PRIVATE KEY-----".bytes + case .ecPrivateKey: + return "-----BEGIN EC PRIVATE KEY-----".bytes + } + } + + /// This PEM type's footer string (expressed as the utf8 decoded byte representation) + var footerBytes:Array { + switch self { + case .publicRSAKeyDER: + return "-----END RSA PUBLIC KEY-----".bytes + case .privateRSAKeyDER: + return "-----END RSA PRIVATE KEY-----".bytes + case .publicKey: + return "-----END PUBLIC KEY-----".bytes + case .privateKey: + return "-----END PRIVATE KEY-----".bytes + case .encryptedPrivateKey: + return "-----END ENCRYPTED PRIVATE KEY-----".bytes + case .ecPrivateKey: + return "-----END EC PRIVATE KEY-----".bytes + } + } + } + + /// Converts UTF8 Encoding of PEM file into a PEMType and the base64 decoded key data + /// - Parameter data: The `UTF8` encoding of the PEM file + /// - Returns: A tuple containing the PEMType, and the actual base64 decoded PEM data (with the headers and footers removed). + internal static func pemToData(_ data:Array) throws -> (type: PEMType, bytes: Array, objectIdentifiers:[Array]) { + let fiveDashes = ArraySlice(repeating: 0x2D, count: 5) // "-----".bytes.toHexString() + let chunks = data.split(separator: 0x0a) // 0x0a == "\n" `new line` char + guard chunks.count > 2 else { throw PEM.Error.invalidPEMFormat("expected at least 3 chunks, a header, body and footer, but got \(chunks.count)") } + + // Enforce a valid PEM header + guard let header = chunks.first, + header.count > 10, + header.prefix(5) == fiveDashes, + header.suffix(5) == fiveDashes else { + throw PEM.Error.invalidPEMHeader + } + + // Enforce a valid PEM footer + guard let footer = chunks.last, + footer.count > 10, + footer.prefix(5) == fiveDashes, + footer.suffix(5) == fiveDashes else { + throw PEM.Error.invalidPEMFooter + } + + // Attempt to classify the PEMType based on the header + // + // - Note: This just gives us a general idea of what direction to head in. Headers that don't match the underlying data will end up throwing an Error later + let pemType:PEMType = try PEMType(headerBytes: header) + + guard let base64 = String(data: Data(chunks[1.. [Data] { + if case .objectIdentifier(let id) = node { return [id] } + else if case .sequence(let nodes) = node { + return objIdsInSequence(nodes) + } + return [] + } + + /// Traverses a Node tree and returns all instances of objectIds + internal static func objIdsInSequence(_ nodes:[ASN1.Node]) -> [Data] { + var objs:[Data] = [] + + nodes.forEach { + if case .objectIdentifier(let id) = $0 { objs.append(id) } + else if case .sequence(let nodes) = $0 { + return objs.append(contentsOf: objIdsInSequence(nodes) ) + } + } + + return objs + } + + /// Decodes an ASN1 formatted Public Key into it's raw DER representation + /// - Parameters: + /// - pem: The ASN1 encoded Public Key representation + /// - expectedObjectIdentifier: The expected objectIdentifier for the particular key type + /// - Returns: The raw bitString data (Public Key DER) + /// + /// ``` + /// 0:d=0 hl=4 l= 546 cons: SEQUENCE + /// 4:d=1 hl=2 l= 13 cons: SEQUENCE + /// 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + /// 17:d=2 hl=2 l= 0 prim: NULL + /// 19:d=1 hl=4 l= 527 prim: BIT STRING + /// ``` + internal static func decodePublicKeyPEM(_ pem:Data, expectedPrimaryObjectIdentifier:Array, expectedSecondaryObjectIdentifier:Array?) throws -> Array { + let asn = try ASN1.Decoder.decode(data: pem) + + //print("PublicKey") + //print(asn) + + // Enforce the above ASN1 Structure + guard case .sequence(let sequence) = asn else { throw Error.invalidPEMFormat("PublicKey::No top level sequence for PublicKey PEM") } + guard sequence.count == 2 else { throw Error.invalidPEMFormat("PublicKey::Top level sequnce should contain two nodes but we got \(sequence.count) isntead") } + guard case .sequence(let params) = sequence.first else { throw Error.invalidPEMFormat("PublicKey::Expected the first node of the top level to be a sequence node, but we got \(sequence.first?.description ?? "NIL") instead") } + guard params.count >= 1 else { throw Error.invalidPEMFormat("PublicKey::Expected at least one param within the secondary sequence") } + guard case .objectIdentifier(let objectID) = params.first else { throw Error.invalidPEMFormat("PublicKey::Expected first param of secondary sequence to be an objectIndentifier") } + + // Ensure the ObjectID specified in the PEM matches that of the Key.Type we're attempting to instantiate + guard objectID.bytes == expectedPrimaryObjectIdentifier else { throw Error.objectIdentifierMismatch(got: objectID.bytes, expected: expectedPrimaryObjectIdentifier) } + + // If the key supports a secondary objectIdentifier (ensure one is present and that they match) + if let expectedSecondaryObjectIdentifier = expectedSecondaryObjectIdentifier { + guard params.count >= 2 else { throw Error.invalidPEMFormat("PrivateKey::") } + guard case .objectIdentifier(let objectIDSecondary) = params[1] else { throw Error.invalidPEMFormat("PrivateKey::") } + guard objectIDSecondary.bytes == expectedSecondaryObjectIdentifier else { throw Error.objectIdentifierMismatch(got: objectIDSecondary.bytes, expected: expectedSecondaryObjectIdentifier) } + } + + guard case .bitString(let bits) = sequence.last else { throw Error.invalidPEMFormat("Expected the last element of the top level sequence to be a bitString") } + + return bits.bytes + } + + /// Decodes an ASN1 formatted Private Key into it's raw DER representation + /// - Parameters: + /// - pem: The ASN1 encoded Private Key representation + /// - expectedObjectIdentifier: The expected objectIdentifier for the particular key type + /// - Returns: The raw octetString data (Private Key DER) + internal static func decodePrivateKeyPEM(_ pem:Data, expectedPrimaryObjectIdentifier:Array, expectedSecondaryObjectIdentifier:Array?) throws -> Array { + let asn = try ASN1.Decoder.decode(data: pem) + + //print("PrivateKey") + //print(asn) + + // Enforce the above ASN1 Structure + guard case .sequence(let sequence) = asn else { throw Error.invalidPEMFormat("PrivateKey::Top level node is not a sequence") } + // Enforce the integer/version param as the first param in our top level sequence + guard case .integer(let integer) = sequence.first else { throw Error.invalidPEMFormat("PrivateKey::First item in top level sequence wasn't an integer") } + //print("PEM Version: \(integer.bytes)") + switch integer { + case Data(hex: "0x00"): + //Proceed with standard pkcs1 private key format + return try decodePrivateKey(sequence, expectedPrimaryObjectIdentifier: expectedPrimaryObjectIdentifier, expectedSecondaryObjectIdentifier: expectedSecondaryObjectIdentifier) + case Data(hex: "0x01"): + //Proceed with EC private key format + return try decodePrivateECKey(sequence, expectedPrimaryObjectIdentifier: expectedPrimaryObjectIdentifier) + default: + throw Error.invalidPEMFormat("Unknown version identifier") + } + } + + /// Decodes a standard (RSA) Private Key PEM file + /// - Parameters: + /// - sequence: The contents of the top level ASN1 Sequence node + /// - expectedPrimaryObjectIdentifier: The expected primary object identifier key to compare the PEM contents against + /// - expectedSecondaryObjectIdentifier: The expected secondary object identifier key to compare the PEM contents against + /// - Returns: The private key bytes + /// + /// [Private key format]() + /// ``` + /// 0:d=0 hl=4 l= 630 cons: SEQUENCE + /// 4:d=1 hl=2 l= 1 prim: INTEGER :00 + /// 7:d=1 hl=2 l= 13 cons: SEQUENCE + /// 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + /// 20:d=2 hl=2 l= 0 prim: NULL + /// 22:d=1 hl=4 l= 608 prim: OCTET STRING [HEX DUMP]:3082...AA50 + /// ``` + private static func decodePrivateKey(_ sequence:[ASN1.Node], expectedPrimaryObjectIdentifier:Array, expectedSecondaryObjectIdentifier:Array?) throws -> Array { + guard sequence.count == 3 else { throw Error.invalidPEMFormat("PrivateKey::Top level sequence doesn't contain 3 items") } + guard case .sequence(let params) = sequence[1] else { throw Error.invalidPEMFormat("PrivateKey::Second item wasn't a sequence") } + guard params.count >= 1 else { throw Error.invalidPEMFormat("PrivateKey::Second sequence contained fewer than expected parameters") } + guard case .objectIdentifier(let objectID) = params.first else { throw Error.invalidPEMFormat("PrivateKey::") } + + // Ensure the ObjectID specified in the PEM matches that of the Key.Type we're attempting to instantiate + guard objectID.bytes == expectedPrimaryObjectIdentifier else { throw Error.objectIdentifierMismatch(got: objectID.bytes, expected: expectedPrimaryObjectIdentifier) } + + // If the key supports a secondary objectIdentifier (ensure one is present and that they match) + if let expectedSecondaryObjectIdentifier = expectedSecondaryObjectIdentifier { + guard params.count >= 2 else { throw Error.invalidPEMFormat("PrivateKey::") } + guard case .objectIdentifier(let objectIDSecondary) = params[1] else { throw Error.invalidPEMFormat("PrivateKey::") } + guard objectIDSecondary.bytes == expectedSecondaryObjectIdentifier else { throw Error.objectIdentifierMismatch(got: objectIDSecondary.bytes, expected: expectedSecondaryObjectIdentifier) } + } + + guard case .octetString(let octet) = sequence[2] else { throw Error.invalidPEMFormat("PrivateKey::") } + + return octet.bytes + } + + /// Decodes an Eliptic Curve Private Key PEM that conforms to the IETF RFC5915 structure + /// - Parameters: + /// - node: The contents of the top level ASN1 Sequence node + /// - expectedPrimaryObjectIdentifier: The expected primary object identifier key to compare the PEM contents against + /// - Returns: The EC private key bytes + /// + /// [EC private key format](https://datatracker.ietf.org/doc/html/rfc5915#section-3) + /// ``` + /// ECPrivateKey ::= SEQUENCE { + /// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + /// privateKey OCTET STRING, + /// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + /// publicKey [1] BIT STRING OPTIONAL + /// } + /// ``` + private static func decodePrivateECKey(_ sequence:[ASN1.Node], expectedPrimaryObjectIdentifier:Array) throws -> Array { + guard sequence.count >= 2 else { throw Error.invalidPEMFormat("PrivateKey::EC::Top level sequence doesn't contain at least 2 items") } + guard case .octetString(let octet) = sequence[1] else { throw Error.invalidPEMFormat("PrivateKey::EC::Second item wasn't an octetString") } + + // Remaining parameters are optional... + if sequence.count > 2 { + guard case .objectIdentifier(let objectID) = sequence[2] else { throw Error.invalidPEMFormat("PrivateKey::EC::Missing objectIdentifier in top level sequence") } + // Ensure the ObjectID specified in the PEM matches that of the Key.Type we're attempting to instantiate + guard objectID.bytes == expectedPrimaryObjectIdentifier else { throw Error.objectIdentifierMismatch(got: objectID.bytes, expected: expectedPrimaryObjectIdentifier) } + } + + //if sequence.count > 3 { + // // Optional Public Key + // guard case .bitString(let _) = sequence[3] else { throw Error.invalidPEMFormat("PrivateKey::EC::") } + //} + + return octet.bytes + } +} diff --git a/Sources/LibP2PCrypto/PEM/Utilities/Collection+chunks.swift b/Sources/LibP2PCrypto/PEM/Utilities/Collection+chunks.swift new file mode 100644 index 0000000..2368ab5 --- /dev/null +++ b/Sources/LibP2PCrypto/PEM/Utilities/Collection+chunks.swift @@ -0,0 +1,120 @@ +// +// Collection+chunks.swift +// +// +// Swift-Algorithms +// + +// MARK: Chunks of Collection (used in exporting PEM strings) +public struct ChunksOfCountCollection { + public typealias Element = Base.SubSequence + + @usableFromInline + internal let base: Base + + @usableFromInline + internal let chunkCount: Int + + @usableFromInline + internal var endOfFirstChunk: Base.Index + + /// Creates a view instance that presents the elements of `base` in + /// `SubSequence` chunks of the given count. + /// + /// - Complexity: O(*n*), because the start index is pre-computed. + @inlinable + internal init(_base: Base, _chunkCount: Int) { + self.base = _base + self.chunkCount = _chunkCount + + // Compute the start index upfront in order to make start index a O(1) + // lookup. + self.endOfFirstChunk = _base.index( + _base.startIndex, offsetBy: _chunkCount, + limitedBy: _base.endIndex + ) ?? _base.endIndex + } +} + +extension Collection { + /// Returns a `ChunksOfCountCollection` view presenting the elements in + /// chunks with count of the given count parameter. + /// + /// - Parameter count: The size of the chunks. If the `count` parameter is + /// evenly divided by the count of the base `Collection` all the chunks will + /// have the count equals to size. Otherwise, the last chunk will contain + /// the remaining elements. + /// + /// let c = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + /// print(c.chunks(ofCount: 5).map(Array.init)) + /// // [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] + /// + /// print(c.chunks(ofCount: 3).map(Array.init)) + /// // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] + /// + /// - Complexity: O(*n*), because the start index is pre-computed. + @inlinable + public func chunks(ofCount count: Int) -> ChunksOfCountCollection { + precondition(count > 0, "Cannot chunk with count <= 0!") + return ChunksOfCountCollection(_base: self, _chunkCount: count) + } +} + +extension ChunksOfCountCollection: Collection { + public struct Index { + @usableFromInline + internal let baseRange: Range + + @inlinable + internal init(_baseRange: Range) { + self.baseRange = _baseRange + } + } + + /// - Complexity: O(1) + @inlinable + public var startIndex: Index { + Index(_baseRange: base.startIndex.. Element { + precondition(i != endIndex, "Index out of range") + return base[i.baseRange] + } + + @inlinable + public func index(after i: Index) -> Index { + precondition(i != endIndex, "Advancing past end index") + let baseIdx = base.index( + i.baseRange.upperBound, offsetBy: chunkCount, + limitedBy: base.endIndex + ) ?? base.endIndex + return Index(_baseRange: i.baseRange.upperBound.. Bool { + lhs.baseRange.lowerBound == rhs.baseRange.lowerBound + } + + @inlinable + public static func < (lhs: ChunksOfCountCollection.Index, + rhs: ChunksOfCountCollection.Index) -> Bool { + lhs.baseRange.lowerBound < rhs.baseRange.lowerBound + } +} + +extension ChunksOfCountCollection.Index: Hashable where Base.Index: Hashable {} + +extension ChunksOfCountCollection: LazySequenceProtocol, LazyCollectionProtocol + where Base: LazySequenceProtocol {} diff --git a/Sources/LibP2PCrypto/Utils/ASN1Parser.swift b/Sources/LibP2PCrypto/Utils/ASN1Parser.swift deleted file mode 100644 index c1799de..0000000 --- a/Sources/LibP2PCrypto/Utils/ASN1Parser.swift +++ /dev/null @@ -1,700 +0,0 @@ -// -// Asn1Parser.swift -// SwiftyRSA -// -// Created by Lois Di Qual on 5/9/17. -// Copyright © 2017 Scoop. All rights reserved. -// Modified by Brandon Toms on 5/1/22 -// - -import Foundation -import Multibase - -/// Object Identifiers -/// [6,8,42,134,72,206,61,3,1,7] -> EC Curve 256 ':prime256v1' -/// [6,5,43,129,4,0,34] -> EC Curve 384 'secp384r1' -/// [6,5,43,129,4,0,35] -> EC Curve 521 ':secp521r1' -/// [42,134,72,206,61,2,1] -> EC Pub ':id-ecPublicKey' -/// [42,134,72,206,61,3,1,7] -> EC Pub 256 ':prime256v1' -/// [43,129,4,0,34] -> EC Pub 384 ':secp384r1' -/// [43,129,4,0,35] -> EC Pub 521 ':secp521r1' -/// [6,5,43,129,4,0,10] -> EC Secp256k1 Private - -/// Simple data scanner that consumes bytes from a raw data and keeps an updated position. -private class Scanner { - - enum ScannerError: Error { - case outOfBounds - } - - let data: Data - var index: Int = 0 - - /// Returns whether there is no more data to consume - var isComplete: Bool { - return index >= data.count - } - - /// Creates a scanner with provided data - /// - /// - Parameter data: Data to consume - init(data: Data) { - self.data = data - } - - /// Consumes data of provided length and returns it - /// - /// - Parameter length: length of the data to consume - /// - Returns: data consumed - /// - Throws: ScannerError.outOfBounds error if asked to consume too many bytes - func consume(length: Int) throws -> Data { - - guard length > 0 else { - return Data() - } - - guard index + length <= data.count else { - throw ScannerError.outOfBounds - } - - let subdata = data.subdata(in: index.. Int { - - let lengthByte = try consume(length: 1).firstByte - - // If the first byte's value is less than 0x80, it directly contains the length - // so we can return it - guard lengthByte >= 0x80 else { - return Int(lengthByte) - } - - // If the first byte's value is more than 0x80, it indicates how many following bytes - // will describe the length. For instance, 0x85 indicates that 0x85 - 0x80 = 0x05 = 5 - // bytes will describe the length, so we need to read the 5 next bytes and get their integer - // value to determine the length. - let nextByteCount = lengthByte - 0x80 - let length = try consume(length: Int(nextByteCount)) - - return length.integer - } -} - -private extension Data { - - /// Returns the first byte of the current data - var firstByte: UInt8 { - var byte: UInt8 = 0 - copyBytes(to: &byte, count: MemoryLayout.size) - return byte - } - - /// Returns the integer value of the current data. - /// @warning: this only supports data up to 4 bytes, as we can only extract 32-bit integers. - var integer: Int { - - guard count > 0 else { - return 0 - } - - var int: UInt32 = 0 - var offset: Int32 = Int32(count - 1) - forEach { byte in - let byte32 = UInt32(byte) - let shifted = byte32 << (UInt32(offset) * 8) - int = int | shifted - offset -= 1 - } - - return Int(int) - } -} - -/// A simple ASN1 parser that will recursively iterate over a root node and return a Node tree. -/// The root node can be any of the supported nodes described in `Node`. If the parser encounters a sequence -/// it will recursively parse its children. -enum Asn1Parser { - - /// An ASN1 node - enum Node:CustomStringConvertible { - case sequence(nodes: [Node]) - case integer(data: Data) - case objectIdentifier(data: Data) - case null - case bitString(data: Data) - case octetString(data: Data) - - var description: String { - printNode(self, level: 0) - } - } - - enum ParserError: Error { - case noType - case invalidType(value: UInt8) - } - - /// Parses ASN1 data and returns its root node. - /// - /// - Parameter data: ASN1 data to parse - /// - Returns: Root ASN1 Node - /// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered - static func parse(data: Data) throws -> Node { - let scanner = Scanner(data: data) - let node = try parseNode(scanner: scanner) - return node - } - - /// Parses an ASN1 given an existing scanne. - /// @warning: this will modify the state (ie: position) of the provided scanner. - /// - /// - Parameter scanner: Scanner to use to consume the data - /// - Returns: Parsed node - /// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered - private static func parseNode(scanner: Scanner) throws -> Node { - - let firstByte = try scanner.consume(length: 1).firstByte - -// print([firstByte].asString(base: .base16)) - - // Sequence - if firstByte == 0x30 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - let nodes = try parseSequence(data: data) - return .sequence(nodes: nodes) - } - - // Integer - if firstByte == 0x02 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - //print(Int(data.asString(base: .base16), radix: 16) ?? -1) - return .integer(data: data) - } - - // Object identifier - if firstByte == 0x06 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - //print(String(data: data, encoding: .ascii)) - //print("Object ID: [\(data.map { "\($0)" }.joined(separator: ","))]") - return .objectIdentifier(data: data) - } - - // Null - if firstByte == 0x05 { - _ = try scanner.consume(length: 1) - return .null - } - - // Bit String - if firstByte == 0x03 { - let length = try scanner.consumeLength() - - // There's an extra byte (0x00) after the bit string length in all the keys I've encountered. - // I couldn't find a specification that referenced this extra byte, but let's consume it and discard it. - _ = try scanner.consume(length: 1) - - let data = try scanner.consume(length: length - 1) - return .bitString(data: data) - } - - // Octet String - if firstByte == 0x04 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) -// print(data.asString(base: .base64)) -// print() -// print(data.bytes) -// print() - return .octetString(data: data) - } - - throw ParserError.invalidType(value: firstByte) - } - - /// Parses an ASN1 sequence and returns its child nodes - /// - /// - Parameter data: ASN1 data - /// - Returns: A list of ASN1 nodes - /// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered - private static func parseSequence(data: Data) throws -> [Node] { - let scanner = Scanner(data: data) - var nodes: [Node] = [] - while !scanner.isComplete { - let node = try parseNode(scanner: scanner) - nodes.append(node) - } - return nodes - } -} - - -private let Mappings:[Array:String] = [ - [6,8,42,134,72,206,61,3,1,7]: "prime256v1", - [6,5,43,129,4,0,34]: "secp384r1", - [6,5,43,129,4,0,35]: "secp521r1", - [42,134,72,206,61,2,1]: "id-ecPublicKey", - [42,134,72,206,61,3,1,7]: "prime256v1", - [43,129,4,0,34]: "secp384r1", - [43,129,4,0,35]: "secp521r1", - [6,5,43,129,4,0,10]: "secp256k1", - [43,101,112]: "Ed25519", - [42,134,72,134,247,13,1,1,1]: "rsaEncryption" -] - -/// A simple ASN1 parser that will recursively iterate over a root node and return a Node tree. -/// The root node can be any of the supported nodes described in `Node`. If the parser encounters a sequence -/// it will recursively parse its children. -enum Asn1ParserECPrivate { - - enum ObjectIdentifier:CustomStringConvertible { - case prime256v1 - case secp384r1 - case secp521r1 - case id_ecPublicKey - case secp256k1 - case Ed25519 - case rsaEncryption - /// Encryption Tags and Ciphers - case aes_128_cbc // The cipher used to encrypt an encrypted key // des_ede3_cbc - case PBKDF2 //An Encrypted PEM Key that uses PBKDF2 to derive a the AES key - case PBES2 //An Encrypted PEM Key - case unknown(Data) - - init(data:Data) { - /// Often times Object Identifiers in private keys begin with an additional 6,5 or 6,8. - /// If the objID has this prefix, we drop the first two bytes before attempting to classify... - let d = data.first == 6 ? data.dropFirst(2) : data - switch d.bytes { - case [42,134,72,206,61,2,1]: - self = .id_ecPublicKey - - case [42,134,72,206,61,3,1,7]: - self = .prime256v1 - - case [43,129,4,0,34]: - self = .secp384r1 - - case [43,129,4,0,35]: - self = .secp521r1 - - case [43,129,4,0,10]: - self = .secp256k1 - - case [43,101,112]: - self = .Ed25519 - - case [42,134,72,134,247,13,1,1,1]: - self = .rsaEncryption - - case [96,134,72,1,101,3,4,1,2]: - self = .aes_128_cbc - - case [42,134,72,134,247,13,1,5,12]: - self = .PBKDF2 - - case [42,134,72,134,247,13,1,5,13]: - self = .PBES2 - - default: - print("Found an unknown Obj ID: [\(data.map { "\($0)" }.joined(separator: ","))]") - self = .unknown(data) - } - } - - var keyType:KeyType? { - switch self { - case .secp256k1: - return .secp256K1 - case .Ed25519: - return .ed25519 - case .rsaEncryption: - return .rsa - default: //Generic EC Curves aren't supported yet... - return nil - } - } - - var description:String { - switch self { - case .prime256v1: return "prime256v1" - case .secp384r1: return "secp384r1" - case .secp521r1: return "secp521r1" - case .id_ecPublicKey: return "id_ecPublicKey" - case .secp256k1: return "secp256k1" - case .Ed25519: return "Ed25519" - case .rsaEncryption: return "rsaEncryption" - /// Encryption Tags and Ciphers.... - case .PBES2: return "PBES2" - case .PBKDF2: return "PBKDF2" - case .aes_128_cbc: return "aes_128_cbc" - case .unknown(let data): - return "Unknown Obj ID: [\(data.map { "\($0)" }.joined(separator: ","))]" - } - } - } - - /// An ASN1 node - enum Node { - case sequence(nodes: [Node]) - case integer(data: Data) - case objectIdentifier(data: ObjectIdentifier) - case null - case bitString(data: Data) - case octetString(data: Data) - } - - enum ParserError: Error { - case noType - case invalidType(value: UInt8) - } - - /// Parses ASN1 data and returns its root node. - /// - /// - Parameter data: ASN1 data to parse - /// - Returns: Root ASN1 Node - /// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered - static func parse(data: Data) throws -> Node { - let scanner = Scanner(data: data) - let node = try parseNode(scanner: scanner) - return node - } - - /// Parses an ASN1 given an existing scanne. - /// @warning: this will modify the state (ie: position) of the provided scanner. - /// - /// - Parameter scanner: Scanner to use to consume the data - /// - Returns: Parsed node - /// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered - private static func parseNode(scanner: Scanner) throws -> Node { - - let firstByte = try scanner.consume(length: 1).firstByte - - // Sequence - if firstByte == 0x30 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - let nodes = try parseSequence(data: data) - return .sequence(nodes: nodes) - } - - // Integer - if firstByte == 0x02 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - return .integer(data: data) - } - - // Object identifier - if firstByte == 0x06 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - return .objectIdentifier(data: ObjectIdentifier(data: data)) - } - - // Null - if firstByte == 0x05 { - _ = try scanner.consume(length: 1) - return .null - } - - // Bit String - if firstByte == 0x03 { - let length = try scanner.consumeLength() - - // There's an extra byte (0x00) after the bit string length in all the keys I've encountered. - // I couldn't find a specification that referenced this extra byte, but let's consume it and discard it. - _ = try scanner.consume(length: 1) - - let data = try scanner.consume(length: length - 1) - return .bitString(data: data) - } - - // Octet String - if firstByte == 0x04 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - return .octetString(data: data) - } - - // EC Curves Cont 0 identifier (obj id) - if firstByte == 0xa0 { - let length = try scanner.consumeLength() - let data = try scanner.consume(length: length) - //print("Found an EC Curve Obj ID: [\(data.map { "\($0)" }.joined(separator: ","))]") - return .objectIdentifier(data: ObjectIdentifier(data: data)) - } - - // EC Curves Cont 1 identifier (bit string) - if firstByte == 0xa1 { - let length = try scanner.consumeLength() - - // There's an extra byte (0x00) after the bit string length in all the keys I've encountered. - // I couldn't find a specification that referenced this extra byte, but let's consume it and discard it. - _ = try scanner.consume(length: 1) - - let data = try scanner.consume(length: length - 1) - //print("Found an EC Curve Bit String: [\(data.map { "\($0)" }.joined(separator: ","))]") - return .bitString(data: data) - } - - print("Unknown byte: \([firstByte].asString(base: .base16))") - - throw ParserError.invalidType(value: firstByte) - } - - /// Parses an ASN1 sequence and returns its child nodes - /// - /// - Parameter data: ASN1 data - /// - Returns: A list of ASN1 nodes - /// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered - private static func parseSequence(data: Data) throws -> [Node] { - let scanner = Scanner(data: data) - var nodes: [Node] = [] - do { - while !scanner.isComplete { - let node = try parseNode(scanner: scanner) - nodes.append(node) - } - } catch { - return nodes - } - return nodes - } -} - -extension LibP2PCrypto.Keys { - - // 256 objId -> 2a8648ce3d0301 - // 384 objId -> 2a8648ce3d0201 - // 521 objId -> 2a8648ce3d0201 - public struct ASN1Parts { - let isPrivateKey:Bool - let keyBits:Data - let objectIdentifier:Data - } - - public static func parseASN1(pemData:Data) throws -> ASN1Parts { - let asn = try Asn1Parser.parse(data: pemData) - - var bitString:Data? = nil - var objId:Data? = nil - var isPrivate:Bool = false - if case .sequence(let nodes) = asn { - nodes.forEach { - switch $0 { - case .objectIdentifier(let data): - if data.first == 0x2a { - //print("Got our obj id: \(data.asString(base: .base64))") - objId = data - } - case .bitString(let data): - //print("Got our bit string: \(data.asString(base: .base64))") - bitString = data - case .sequence(let nodes): - nodes.forEach { n in - switch n { - case .objectIdentifier(let data): - if data.first == 0x2a { - //print("Got our obj id: \(data.asString(base: .base64))") - objId = data - } - case .bitString(let data): - //print("Got our bit string: \(data.asString(base: .base64))") - bitString = data - case .octetString(let data): - //Private Keys trigger - bitString = data - isPrivate = true - default: - return - } - } - case .octetString(let data): - //Private Keys trigger - bitString = data - isPrivate = true - default: - return - } - } - } - - guard let id = objId, let bits = bitString else { - throw NSError(domain: "Unsupported asn1 format", code: 0, userInfo: nil) - } - - return ASN1Parts(isPrivateKey: isPrivate, keyBits: bits, objectIdentifier: id) - - } - - public static func parseASN1ECPrivate(pemData:Data) throws -> Data { - let asn = try Asn1ParserECPrivate.parse(data: pemData) - - var octetString:Data? = nil - if case .sequence(let nodes) = asn { - nodes.forEach { - switch $0 { - case .sequence(let nodes): - nodes.forEach { n in - switch n { - case .octetString(let data): - octetString = data - default: - return - } - } - case .octetString(let data): - octetString = data - default: - return - } - } - } else if case .octetString(let data) = asn { - octetString = data - } - - guard let bits = octetString else { - throw NSError(domain: "Unsupported asn1 format", code: 0, userInfo: nil) - } - - return bits - } - - /// This method strips the x509 header from a provided ASN.1 DER key. - /// If the key doesn't contain a header, the DER data is returned as is. - /// - /// Supported formats are: - /// - /// Headerless: - /// SEQUENCE - /// INTEGER (1024 or 2048 bit) -- modulo - /// INTEGER -- public exponent - /// - /// With x509 header: - /// SEQUENCE - /// SEQUENCE - /// OBJECT IDENTIFIER 1.2.840.113549.1.1.1 - /// NULL - /// BIT STRING - /// SEQUENCE - /// INTEGER (1024 or 2048 bit) -- modulo - /// INTEGER -- public exponent - /// - /// Example of headerless key: - ///https://lapo.it/asn1js/#3082010A0282010100C1A0DFA367FBC2A5FD6ED5A071E02A4B0617E19C6B5AD11BB61192E78D212F10A7620084A3CED660894134D4E475BAD7786FA1D40878683FD1B7A1AD9C0542B7A666457A270159DAC40CE25B2EAE7CCD807D31AE725CA394F90FBB5C5BA500545B99C545A9FE08EFF00A5F23457633E1DB84ED5E908EF748A90F8DFCCAFF319CB0334705EA012AF15AA090D17A9330159C9AFC9275C610BB9B7C61317876DC7386C723885C100F774C19830F475AD1E9A9925F9CA9A69CE0181A214DF2EB75FD13E6A546B8C8ED699E33A8521242B7E42711066AEC22D25DD45D56F94D3170D6F2C25164D2DACED31C73963BA885ADCB706F40866B8266433ED5161DC50E4B3B0203010001 - /// - /// Example of key with X509 header (notice the additional ASN.1 sequence): - ///https://lapo.it/asn1js/#30819F300D06092A864886F70D010101050003818D0030818902818100D0674615A252ED3D75D2A3073A0A8A445F3188FD3BEB8BA8584F7299E391BDEC3427F287327414174997D147DD8CA62647427D73C9DA5504E0A3EED5274A1D50A1237D688486FADB8B82061675ABFA5E55B624095DB8790C6DBCAE83D6A8588C9A6635D7CF257ED1EDE18F04217D37908FD0CBB86B2C58D5F762E6207FF7B92D0203010001 - public static func stripX509HeaderFromDER(keyData: Data) throws -> Data { - - let node: Asn1Parser.Node - do { - node = try Asn1Parser.parse(data: keyData) - } catch { - throw NSError(domain: "asn1ParsingFailed", code: 0, userInfo: nil) - } - - // Ensure the raw data is an ASN1 sequence - guard case .sequence(let nodes) = node else { - throw NSError(domain: "invalidAsn1RootNode", code: 0, userInfo: nil) - } - - // Detect whether the sequence only has integers, in which case it's a headerless key - let onlyHasIntegers = nodes.filter { node -> Bool in - if case .integer = node { - return false - } - return true - }.isEmpty - - // Headerless key - if onlyHasIntegers { - return keyData - } - - // If last element of the sequence is a bit string, return its data - if let last = nodes.last, case .bitString(let data) = last { - return data - } - - // If last element of the sequence is an octet string, return its data - if let last = nodes.last, case .octetString(let data) = last { - return data - } - - // Unable to extract bit/octet string or raw integer sequence - throw NSError(domain: "invalidAsn1Structure", code: 0, userInfo: nil) - } -} - -enum ASN1Encoder { - private static func asn1LengthPrefix(_ bytes:[UInt8]) -> [UInt8] { - if bytes.count >= 0x80 { - var lengthAsBytes = withUnsafeBytes(of: bytes.count.bigEndian, Array.init) - while lengthAsBytes.first == 0 { lengthAsBytes.removeFirst() } - return [(0x80 + UInt8(lengthAsBytes.count))] + lengthAsBytes - } else { - return [UInt8(bytes.count)] - } - } - - private static func asn1LengthPrefixed(_ bytes:[UInt8]) -> [UInt8] { - asn1LengthPrefix(bytes) + bytes - } - - public static func encode(_ node:Asn1Parser.Node) -> [UInt8] { - switch node { - case .integer(let integer): - return [0x02] + asn1LengthPrefixed(integer.bytes) - case .bitString(let bits): - return [0x03] + asn1LengthPrefixed([0x00] + bits.bytes) - case .octetString(let octet): - return [0x04] + asn1LengthPrefixed(octet.bytes) - case .null: - return [0x05, 0x00] - case .objectIdentifier(let oid): - return [0x06] + asn1LengthPrefixed(oid.bytes) - case .sequence(let nodes): - return [0x30] + asn1LengthPrefixed( nodes.reduce(into: Array(), { partialResult, node in - partialResult += encode(node) - }) ) - } - } -} - -fileprivate func printNode(_ node:Asn1Parser.Node, level:Int) -> String { - var str:[String] = [] - let prefix = String(repeating: "\t", count: level) - switch node { - case .integer(let int): - str.append("\(prefix)Integer: \(int.asString(base: .base16))") - case .bitString(let bs): - str.append("\(prefix)BitString: \(bs.asString(base: .base16))") - case .null: - str.append("\(prefix)NULL") - case .objectIdentifier(let oid): - str.append("\(prefix)ObjectID: \(oid.asString(base: .base16))") - case .octetString(let os): - str.append("\(prefix)OctetString: \(os.asString(base: .base16))") - case .sequence(let nodes): - nodes.forEach { str.append(printNode($0, level: level + 1)) } - } - return str.joined(separator: "\n") -} diff --git a/Sources/LibP2PCrypto/Utils/Integer+Bytes.swift b/Sources/LibP2PCrypto/Utils/Integer+Bytes.swift new file mode 100644 index 0000000..3958ee6 --- /dev/null +++ b/Sources/LibP2PCrypto/Utils/Integer+Bytes.swift @@ -0,0 +1,47 @@ +// +// Integer+Bytes.swift +// +// +// CryptoSwift +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +extension FixedWidthInteger { + @inlinable + func bytes(totalBytes: Int = MemoryLayout.size) -> Array { + arrayOfBytes(value: self.littleEndian, length: totalBytes) + // TODO: adjust bytes order + // var value = self.littleEndian + // return withUnsafeBytes(of: &value, Array.init).reversed() + } +} + +@_specialize(where T == Int) +@_specialize(where T == UInt) +@_specialize(where T == UInt8) +@_specialize(where T == UInt16) +@_specialize(where T == UInt32) +@_specialize(where T == UInt64) +@inlinable +func arrayOfBytes(value: T, length totalBytes: Int = MemoryLayout.size) -> Array { + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value + + let bytesPointer = UnsafeMutablePointer(OpaquePointer(valuePointer)) + var bytes = Array(repeating: 0, count: totalBytes) + for j in 0...size, totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } + + valuePointer.deinitialize(count: 1) + valuePointer.deallocate() + + return bytes +} diff --git a/Sources/LibP2PCrypto/Utils/PEM+DER/DER.swift b/Sources/LibP2PCrypto/Utils/PEM+DER/DER.swift deleted file mode 100644 index 7429653..0000000 --- a/Sources/LibP2PCrypto/Utils/PEM+DER/DER.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// File.swift -// -// -// Created by Brandon Toms on 5/22/22. -// - -import Foundation -import Multibase - -extension LibP2PCrypto.Keys { - /// Expects a PEM Public Key with the x509 header information included (object identifier) - /// - /// - Note: Handles RSA and EC Public Keys - public static func importPublicDER(_ der:String) throws -> KeyPair { - let chunks = der.split(separator: "\n") - guard chunks.count > 3, - let f = chunks.first, f.hasPrefix("-----BEGIN RSA PUBLIC"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid DER Format", code: 0, userInfo: nil) - } - - let raw = try BaseEncoding.decode(chunks[1.. KeyPair { - let chunks = der.split(separator: "\n") - guard chunks.count > 3, - let f = chunks.first, f.hasPrefix("-----BEGIN RSA PRIVATE"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid DER Format", code: 0, userInfo: nil) - } - - var raw = try BaseEncoding.decode(chunks[1.. PrivKey { -// -// let chunks = der.split(separator: "\n") -// guard chunks.count > 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// let raw = try BaseEncoding.decode(chunks[1..? -// //let privKey = SecKeyCreateWithData(d as CFData, attributes as CFDictionary, &error) -// let privKey = SecKeyCreateFromData([:] as CFDictionary, d as CFData, &error) -// -// guard let key = privKey else { -// -// throw NSError(domain: "Failed to gen priv key... \(error.debugDescription)", code: 0, userInfo: nil) -// } -// -// if let pubkey = try? key.extractPubKey() { -// print("Got the pub key...") -// print((try? pubkey.asString(base: .base16)) ?? "nil") -// } -// -// print(key.attributes) -// -// print((try? key.rawRepresentation()) ?? "Failed to get raw rep...") -// -// return key -// -// //return try LibP2PCrypto.Keys.secKeyFrom(data: d, isPrivateKey: true, keyType: .EC(curve: .P256)) -// } - - /// Appends PEM Header and Footer - /// Base64 encodes DER PubKey - /// MacOS -> Use - /// iOS roll our own - /// https://github.com/ibm-cloud-security/Swift-JWK-to-PEM - /// https://github.com/Kitura/OpenSSL -// private func toDER(keyPair:LibP2PCrypto.Keys.KeyPair) throws -> String { -// -// // Line length is typically 64 characters, except the last line. -// // See https://tools.ietf.org/html/rfc7468#page-6 (64base64char) -// // See https://tools.ietf.org/html/rfc7468#page-11 (example) -// let keyData = try keyPair.publicKey.rawRepresentation() -// let chunks = keyData.base64EncodedString().split(intoChunksOfLength: 64) -// -// let pem = [ -// "-----BEGIN \(keyPair.keyType.name)-----", -// chunks.joined(separator: "\n"), -// "-----END \(keyPair.keyType.name)-----" -// ] -// -// return pem.joined(separator: "\n") -// } - - -} diff --git a/Sources/LibP2PCrypto/Utils/PEM+DER/PEM.swift b/Sources/LibP2PCrypto/Utils/PEM+DER/PEM.swift deleted file mode 100644 index e171376..0000000 --- a/Sources/LibP2PCrypto/Utils/PEM+DER/PEM.swift +++ /dev/null @@ -1,524 +0,0 @@ -// -// PEM.swift -// -// -// Created by Brandon Toms on 5/22/22. -// - -import Foundation -import Multibase -import Crypto - -extension LibP2PCrypto.Keys { - public struct ParsedPem { - let isPrivate:Bool - let type:KeyPairType - let rawKey:Data - } - - /// Parse the pem file into ASN1 bits... - /// Scan the bits for Object Identifiers and classify the key type - /// Based on the key type... scan the bits for the key data - /// Return a ParsedPem struct that we can use to instantiate any of our supported KeyPairTypes... - public static func parsePem(_ pem:String) throws -> KeyPair { - let chunks = pem.split(separator: "\n") - guard chunks.count >= 3, - let f = chunks.first, f.hasPrefix("-----BEGIN"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) - } - - /// If its a DER re route it... - if f.contains("-----BEGIN RSA PUBLIC") { return try LibP2PCrypto.Keys.importPublicDER(pem) } - else if f.contains("-----BEGIN RSA PRIVATE") { return try LibP2PCrypto.Keys.importPrivateDER(pem) } - - let isPrivate:Bool = f.contains("PRIVATE") - - let rawPem = try BaseEncoding.decode(chunks[1.. KeyPair { - var type:KeyPairType? = nil - let asn = try Asn1ParserECPrivate.parse(data: rawPem) - - print("ASN1 Nodes") - print(asn) - print("----------") - - guard case .sequence(let nodes) = asn else { throw NSError(domain: "Failed to parse PEM", code: 0, userInfo: nil) } - let ids = objIdsInSequence(nodes) - - if ids.contains(where: { (id) -> Bool in - if case .rsaEncryption = id { return true } else { return false } - }) { - type = .RSA(bits: .B1024) //Bit length doesn't matter here, we're just broadly classifying it... - } else if ids.contains(where: { (id) -> Bool in - if case .secp256k1 = id { return true } else { return false } - }) { - type = .Secp256k1 - } else if ids.contains(where: { (id) -> Bool in - if case .Ed25519 = id { return true } else { return false } - }) { - type = .Ed25519 - } else if ids.contains(where: { (id) -> Bool in - switch id { - case .prime256v1, .secp384r1, .secp521r1: return true - default: return false - } - }) { - throw NSError(domain: "No EC Key Support Yet", code: 0, userInfo: nil) - //type = .EC(curve: .P256) //Curve bits dont matter here, we're just broadly classifying it... - } - - guard let keyType = type else { throw NSError(domain: "Failed to classify key", code: 0, userInfo: nil) } - - guard case .sequence(let top) = asn else { - throw NSError(domain: "Failed to parse Asn1", code: 0, userInfo: nil) - } - - var rawKeyData:Data? = nil - - if isPrivate { - // First Octet - guard let octet = octetsInSequence(top).first else { - throw NSError(domain: "Failed to extract \(keyType.name) \(isPrivate ? "Private" : "Public") key", code: 0, userInfo: nil) - } - rawKeyData = octet - } else { - // First Bit String... - guard let bitString = bitStringsInSequence(top).first else { - throw NSError(domain: "Failed to extract \(keyType.name) \(isPrivate ? "Private" : "Public") key", code: 0, userInfo: nil) - } - rawKeyData = bitString - } - - // ED25519 Private Keys are wrapped in an additional octetString node, lets remove it... - if isPrivate, case .Ed25519 = keyType, rawKeyData?.count == 34 { - rawKeyData?.removeFirst(2) - } - - guard let keyData = rawKeyData else { - throw NSError(domain: "Failed to extract key data from asn1 nodes", code: 0, userInfo: nil) - } - - //return ParsedPem(isPrivate: isPrivate, type: keyType, rawKey: keyData) - - // At this point we know if its a public or private key, the type of key, and the raw bits of the key. - // We can instantiate the key, ensure it's valid, then create a return a PublicKey or PrivateKey - switch keyType { - case .RSA: - if isPrivate { - return try KeyPair(privateKey: RSAPrivateKey(rawRepresentation: keyData)) - } else { - return try KeyPair(publicKey: RSAPublicKey(rawRepresentation: keyData)) - } - case .Ed25519: - if isPrivate { - return try KeyPair(privateKey: Curve25519.Signing.PrivateKey(rawRepresentation: keyData)) - } else { - return try KeyPair(publicKey: Curve25519.Signing.PublicKey(rawRepresentation: keyData)) - } - case .Secp256k1: - if isPrivate { - return try KeyPair(privateKey: Secp256k1PrivateKey(keyData.bytes)) - } else { - return try KeyPair(publicKey: Secp256k1PublicKey(keyData.bytes)) - } - //default: - /// - TODO: Internal Support For EC Keys (without support for marshaling) - // throw NSError(domain: "Unsupported Key Type \(keyType.description)", code: 0, userInfo: nil) - } - } - - /// Importes an Encrypted PEM Key File - /// - /// An ASN1 Node Tree of an Encrypted RSA PEM Key (PBKDF2 and AES_CBC_128) - /// ``` - /// sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.objectIdentifier(data: 9 bytes), //[42,134,72,134,247,13,1,5,13] - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.objectIdentifier(data: 9 bytes), //PBKDF2 //[42,134,72,134,247,13,1,5,12] - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.octetString(data: 8 bytes), //SALT - /// libp2p_crypto.Asn1Parser.Node.integer(data: 2 bytes) //ITTERATIONS - /// ]) - /// ]), - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.objectIdentifier(data: 9 bytes), //des-ede3-cbc [96,134,72,1,101,3,4,1,2] - /// libp2p_crypto.Asn1Parser.Node.octetString(data: 16 bytes) //IV - /// ]) - /// ]) - /// ]), - /// libp2p_crypto.Asn1Parser.Node.octetString(data: 640 bytes) - /// ]) - /// ``` - static func parseEncryptedPem(_ pem:String, password:String) throws -> KeyPair { - let chunks = pem.split(separator: "\n") - guard chunks.count >= 3, - let f = chunks.first, f.hasPrefix("-----BEGIN ENCRYPTED"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid Encrypted PEM Format", code: 0, userInfo: nil) - } - - let isPrivate:Bool = f.contains("PRIVATE") - - let rawPem = try BaseEncoding.decode(chunks[1.. 100 { - ciphertextData = $0 - } else { - saltData = $0 - } - } - - /// There should be only one integer, the itteration count... - itterationsData = integersInSequence(nodes).first - - guard let salt = saltData, let iv = ivData, let itterations = itterationsData, let ciphertext = ciphertextData else { - throw NSError(domain: "Failed to parse our pcks#8 key", code: 0, userInfo: nil) - } - - // Attempt to derive the aes encryption key from the password and salt - // PBKDF2-SHA1 - guard let key = PBKDF2.SHA1(password: password, salt: salt, keyByteCount: iv.count, rounds: itterations) else { - throw NSError(domain: "Failed to derive key from password and salt", code: 0, userInfo: nil) - } - - //print("Key 1 -> \(key.asString(base: .base16))") - - //Create our CBC AES Cipher - let aes = try LibP2PCrypto.AES.createKey(key: key, iv: iv) - //let aes = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes), padding: .noPadding) - - // GCM Doesn't work on OPENSSL Encrypted PEM Files but I saw mention of it in libp2p-crypto-js so perhaps we'll need it later... - //let aes = try AES(key: key.bytes, blockMode: GCM(iv: iv.bytes, mode: .detached), padding: .noPadding) - - let decryptedKey = try aes.decrypt(ciphertext.bytes) - - // At this point we have regular unencrypted PEM data rep of a key, lets parse it... - return try self.parsePem(decryptedKey, isPrivate: isPrivate) - } - - /// Traverses a Node tree and returns all instances of integers - private static func integersInSequence(_ nodes:[Asn1ParserECPrivate.Node]) -> [Int] { - var integers:[Int?] = [] - - nodes.forEach { - if case .integer(let data) = $0 { integers.append(Int(data.asString(base: .base16), radix: 16)) } - else if case .sequence(let nodes) = $0 { - return integers.append(contentsOf: integersInSequence(nodes) ) - } - } - - return integers.compactMap { $0 } - } - - /// Traverses a Node tree and returns all instances of bitStrings - private static func bitStringsInSequence(_ nodes:[Asn1ParserECPrivate.Node]) -> [Data] { - var bitString:[Data] = [] - - nodes.forEach { - if case .bitString(let data) = $0 { bitString.append(data) } - else if case .sequence(let nodes) = $0 { - return bitString.append(contentsOf: bitStringsInSequence(nodes) ) - } - } - - return bitString - } - - /// Traverses a Node tree and returns all instances of bitStrings - private static func octetsInSequence(_ nodes:[Asn1ParserECPrivate.Node]) -> [Data] { - var octets:[Data] = [] - - nodes.forEach { - if case .octetString(let data) = $0 { octets.append(data) } - else if case .sequence(let nodes) = $0 { - return octets.append(contentsOf: octetsInSequence(nodes) ) - } - } - - return octets - } - - /// Traverses a Node tree and returns all instances of objectIds - private static func objIdsInSequence(_ nodes:[Asn1ParserECPrivate.Node]) -> [Asn1ParserECPrivate.ObjectIdentifier] { - var objs:[Asn1ParserECPrivate.ObjectIdentifier] = [] - - nodes.forEach { - if case .objectIdentifier(let id) = $0 { objs.append(id) } - else if case .sequence(let nodes) = $0 { - return objs.append(contentsOf: objIdsInSequence(nodes) ) - } - } - - return objs - } - - /// Expects a PEM Public Key with the x509 header information included (object identifier) - /// - /// - Note: Handles RSA Public Keys - public static func importPublicPem(_ pem:String) throws -> CommonPublicKey { - let chunks = pem.split(separator: "\n") - guard chunks.count > 3, - let f = chunks.first, f.hasPrefix("-----BEGIN"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) - } - - let raw = try BaseEncoding.decode(chunks[1.. Data { -// let chunks = pem.split(separator: "\n") -// guard chunks.count > 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// //print("Attempting to decode: \(chunks[1.. CommonPrivateKey { - let chunks = pem.split(separator: "\n") - guard chunks.count > 3, - let f = chunks.first, f.hasPrefix("-----BEGIN PRIVATE"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) - } - - //print("Attempting to decode: \(chunks[1.. PrivKey { -// let chunks = pem.split(separator: "\n") -// guard chunks.count > 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN PRIVATE"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// //print("Attempting to decode: \(chunks[1.. PubKey { -// var pubKey:Data? = nil -// switch keyType { -// case .ECDSA(curve: .P256): -// pubKey = try P256.Signing.PublicKey(pemRepresentation: pem).rawRepresentation -// case .ECDSA(curve: .P384): -// pubKey = try P384.Signing.PublicKey(pemRepresentation: pem).rawRepresentation -// case .ECDSA(curve: .P521): -// pubKey = try P521.Signing.PublicKey(pemRepresentation: pem).rawRepresentation -// default: -// print("Unsupported KeyType") -// } -// -// guard let pubKeyData = pubKey else { -// throw NSError(domain: "Unable to parse PEM into Public Key", code: 0, userInfo: nil) -// } -// -// let attributes: [String:Any] = [ -// kSecAttrKeyType as String: keyType.secKey, -// kSecAttrKeyClass as String: kSecAttrKeyClassPublic, -// kSecAttrKeySizeInBits as String: keyType.bits, -// kSecAttrIsPermanent as String: false -// ] -// -// return try LibP2PCrypto.Keys.secKeyFrom(data: pubKeyData, attributes: attributes) -// } - -// public static func initPrivKeyFromPem(_ pem:String, keyType:LibP2PCrypto.Keys.KeyPairType) throws -> PubKey { -// var pubKey:Data? = nil -// switch keyType { -// case .ECDSA(curve: .P256): -// pubKey = try P256.Signing.PrivateKey(pemRepresentation: pem).rawRepresentation -// case .ECDSA(curve: .P384): -// pubKey = try P384.Signing.PrivateKey(pemRepresentation: pem).rawRepresentation -// case .ECDSA(curve: .P521): -// pubKey = try P521.Signing.PrivateKey(pemRepresentation: pem).rawRepresentation -// default: -// print("Unsupported KeyType") -// } -// -// guard let pubKeyData = pubKey else { -// throw NSError(domain: "Unable to parse PEM into Private Key", code: 0, userInfo: nil) -// } -// -// let attributes: [String:Any] = [ -// kSecAttrKeyType as String: keyType.secKey, -// kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, -// kSecAttrKeySizeInBits as String: keyType.bits, -// kSecAttrIsPermanent as String: false -// ] -// -// return try LibP2PCrypto.Keys.secKeyFrom(data: pubKeyData, attributes: attributes) -// } - - -// public static func importPem(_ str:String) throws -> KeyPair { -// -// let pemData = str.data(using: .utf8) -// -// } - -// public static func fromPEM(_ str:String, keyType:LibP2PCrypto.Keys.KeyPairType) throws -> SecKey { -// -// guard str.hasPrefix("-----BEGIN"), str.hasSuffix("-----") else { throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) } -// let chunks = str.split(separator: "\n") -// guard chunks.count > 3 else { throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) } -// //print(chunks) -// print("Attempting to decode: \(chunks[1..? -//// let key = SecItemImport(raw.data as CFData, nil, nil, nil, .pemArmour, nil, nil, out) -//// print(key) -//// print(out) -// let attributesRSAPriv: [String:Any] = [ -// kSecAttrKeyType as String: kSecAttrKeyTypeRSA, -// kSecAttrKeyClass as String: kSecAttrKeyClassPublic, -// kSecAttrKeySizeInBits as String: keyType.bits, -// kSecAttrIsPermanent as String: false -// ] -// -// var error:Unmanaged? = nil -// guard let secKey = SecKeyCreateWithData(key as CFData, attributesRSAPriv as CFDictionary, &error) else { -// //guard let secKey = SecKeyCreateFromData(keyType.params! as CFDictionary, key as CFData, &error) else { -// throw NSError(domain: "Error constructing SecKey from raw key data: \(error.debugDescription)", code: 0, userInfo: nil) -// } -// -// return secKey -// } -} diff --git a/Tests/LibP2PCryptoTests/LibP2PCryptoTests.swift b/Tests/LibP2PCryptoTests/LibP2PCryptoTests.swift index e50d6c4..f3d14c5 100644 --- a/Tests/LibP2PCryptoTests/LibP2PCryptoTests.swift +++ b/Tests/LibP2PCryptoTests/LibP2PCryptoTests.swift @@ -5,6 +5,11 @@ import Crypto import CryptoSwift import Multihash +/// Secp - https://techdocs.akamai.com/iot-token-access-control/docs/generate-ecdsa-keys +/// JWT - https://techdocs.akamai.com/iot-token-access-control/docs/generate-jwt-ecdsa-keys +/// Fixtures - http://cryptomanager.com/tv.html +/// RSA (Sign+Verify) - https://cryptobook.nakov.com/digital-signatures/rsa-sign-verify-examples +/// PEM+DER (PKCS1&8) - https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem final class libp2p_cryptoTests: XCTestCase { /// RSA @@ -24,6 +29,7 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertEqual(attributes?.isPrivate, true) } + /// These tests are skipped on Linux when using CryptoSwift due to very slow key generation times. #if canImport(Security) func testRSA2048() throws { let keyPair = try LibP2PCrypto.Keys.generateKeyPair(.RSA(bits: .B2048)) @@ -66,6 +72,63 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertEqual(attributes?.size, 4096) XCTAssertEqual(attributes?.isPrivate, true) } + + /// This test ensures that SecKey's CopyExternalRepresentation outputs the same data as our CryptoSwift RSA Implementation + func testRSAExternalRepresentation() throws { + /// Generate a SecKey RSA Key + let parameters:[CFString:Any] = [ + kSecAttrKeyType: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits: 1024 + ] + + var error:Unmanaged? = nil + + guard let privKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else { + print(error.debugDescription) + throw NSError(domain: "Key Generation Error: \(error.debugDescription)", code: 0, userInfo: nil) + } + + let rsaSecKey = privKey + + /// Lets grab the external representation + var externalRepError:Unmanaged? + guard let cfdata = SecKeyCopyExternalRepresentation(rsaSecKey, &externalRepError) else { + XCTFail("Failed to copy external representation for RSA SecKey") + return + } + + let rsaSecKeyRawRep = cfdata as Data + + print(rsaSecKeyRawRep.asString(base: .base16)) + + + /// Ensure we can import the RSA key as a CryptoSwift RSA Key + guard case .sequence(let params) = try ASN1.Decoder.decode(data: rsaSecKeyRawRep) else { throw NSError(domain: "Invalid ASN1 Encoding -> No PrivKey Sequence", code: 0) } + // We check for 4 here because internally we can only marshal the first 4 integers at the moment... + guard params.count == 4 || params.count == 9 else { throw NSError(domain: "Invalid ASN1 Encoding -> Invalid Private RSA param count. Expected 9 got \(params.count)", code: 0) } + guard case .integer(let n) = params[1] else { throw NSError(domain: "Invalid ASN1 Encoding -> PrivKey No Modulus", code: 0) } + guard case .integer(let e) = params[2] else { throw NSError(domain: "Invalid ASN1 Encoding -> PrivKey No Public Exponent", code: 0) } + guard case .integer(let d) = params[3] else { throw NSError(domain: "Invalid ASN1 Encoding -> PrivKey No Private Exponent", code: 0) } + + let rsaCryptoSwift = RSA(n: n.bytes, e: e.bytes, d: d.bytes) + + // Raw Rep + guard let d = rsaCryptoSwift.d else { XCTFail("Failed to import RSA SecKey as private CryptoSwift Key"); return } + let mod = rsaCryptoSwift.n.serialize() + let privkeyAsnNode:ASN1.Node = + .sequence(nodes: [ + .integer(data: Data( Array(arrayLiteral: 0x00) )), + .integer(data: Data(DER.i2osp(x: mod.bytes, size: mod.count + 1))), + .integer(data: rsaCryptoSwift.e.serialize()), + .integer(data: d.serialize()) + ]) + + let rsaCryptoSwiftRawRep = Data(ASN1.Encoder.encode(privkeyAsnNode)) + + + print(rsaCryptoSwiftRawRep.asString(base: .base16)) + + } #endif func testED25519() throws { @@ -102,6 +165,29 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertEqual(try keyPair.rawID(), try recoveredPrivateKeyPair.rawID()) } + /// RSA CryptoSwift + func testCryptoSwiftRawRepresentationRoundTrip() throws { + let rsa = try CryptoSwift.RSA(keySize: 1024) + + let extRep = try rsa.externalRepresentation() + print(extRep.toHexString()) + + let pubKeyExtRep = try rsa.publicKeyExternalRepresentation() + print(pubKeyExtRep.toHexString()) + + let recoveredPrivate = try CryptoSwift.RSA(rawRepresentation: rsa.externalRepresentation()) + + XCTAssertEqual(rsa.n, recoveredPrivate.n) + XCTAssertEqual(rsa.e, recoveredPrivate.e) + XCTAssertEqual(rsa.d, recoveredPrivate.d) + + let recoveredPublic = try CryptoSwift.RSA(rawRepresentation: rsa.publicKeyExternalRepresentation()) + + XCTAssertEqual(rsa.n, recoveredPublic.n) + XCTAssertEqual(rsa.e, recoveredPublic.e) + XCTAssertNil(recoveredPublic.d) + } + func testEd25519RawRepresentationRoundTrip() throws { let keyPair = try LibP2PCrypto.Keys.KeyPair(.Ed25519) @@ -202,54 +288,6 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertEqual(pubKey.data, keyPair.publicKey.data) } -// /// Eliptic Curves -// func testEC256() throws { -// let keyPair = try LibP2PCrypto.Keys.generateRawKeyPair(.EC(curve: .P256)) -// print(keyPair) -// } -// -// func testEC256KeyPair() throws { -// let keyPair = try LibP2PCrypto.Keys.generateKeyPair(.EC(curve: .P256)) -// XCTAssertFalse(keyPair.publicKey.isRSAKey) -// XCTAssertTrue(keyPair.publicKey.isPublicKey) -// XCTAssertFalse(keyPair.privateKey.isPublicKey) -// } -// -// func testECDSA256() throws { -// let keyPair = try LibP2PCrypto.Keys.generateRawKeyPair(.ECDSA(curve: .P256)) -// print(keyPair) -// } -// -// func testECDSA384() throws { -// let keyPair = try LibP2PCrypto.Keys.generateRawKeyPair(.ECDSA(curve: .P384)) -// print(keyPair) -// } -// -// func testECDSA521() throws { -// let keyPair = try LibP2PCrypto.Keys.generateRawKeyPair(.ECDSA(curve: .P521)) -// print(keyPair) -// } -// -// func testECSecPrimeRandom() throws { -// let keyPair = try LibP2PCrypto.Keys.generateRawKeyPair(.ECSECPrimeRandom()) -// print(keyPair) -// } - -// func testEphemeralMashallingRoundTrip() throws { -// let keyPair = try LibP2PCrypto.Keys.generateRawEphemeralKeyPair(curve: .P256) -// -// print("Public Key: \(keyPair.publicKey.asString(base: .base16))") -// -// let marshaledPubKey = try LibP2PCrypto.Keys.marshalPublicKey(raw: keyPair.publicKey, keyType: .ECDSA(curve: .P256)) -// print("Marshaled PubKey Bytes: \(marshaledPubKey)") -// -// let unmarshaledPubKey = try LibP2PCrypto.Keys.unmarshalPublicKey(buf: marshaledPubKey, into: .base16) -// -// print("Public Key: \(unmarshaledPubKey)") -// XCTAssertEqual(unmarshaledPubKey, keyPair.publicKey.asString(base: .base16)) -// } -// - // - MARK: Marshaling // Manual @@ -269,18 +307,6 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertEqual(base64MarshaledPublicKey, MarshaledData.PUBLIC_RSA_KEY_1024) } -// func testImportFromMarshalledPublicKey() throws { -// let pubKey = try RawPublicKey(marshaledKey: MarshaledData.PUBLIC_KEY, base: .base64Pad) -// -// /// We've imported a Public Key! 🥳 -// print(pubKey) -// -// /// Now lets try and re-marshal the imported key and make sure it matches the original data... -// let base64MarshaledPublicKey = try pubKey.marshalPublicKey().asString(base: .base64Pad) -// -// XCTAssertEqual(base64MarshaledPublicKey, MarshaledData.PUBLIC_KEY) -// } - func testCreateKeyPairFromMarshalledPublicKey_1024() throws { let keyPair = try LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: MarshaledData.PUBLIC_RSA_KEY_1024, base: .base64Pad) @@ -328,7 +354,7 @@ final class libp2p_cryptoTests: XCTestCase { print(privKey) } - func testImportFromMarshalledPrivateKey() throws { + func testImportFromMarshalledPrivateKey_1024() throws { let keyPair = try LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: MarshaledData.PRIVATE_RSA_KEY_1024, base: .base64Pad) //RawPrivateKey(marshaledKey: MarshaledData.PRIVATE_KEY, base: .base64Pad) print(keyPair) @@ -336,14 +362,101 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertNotNil(keyPair.privateKey) XCTAssertNotNil(keyPair.publicKey) XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertTrue(keyPair.hasPrivateKey) + + /// Ensure that when we marshal the private key, we end up with that same data we imported. + XCTAssertEqual(try keyPair.privateKey?.marshal().asString(base: .base64Pad), MarshaledData.PRIVATE_RSA_KEY_1024) - /// Ensures that the private key was instantiated properly and then we can derive the public key from it and then marshal it + /// Ensures that the public key derived from the private key is correct and the marshaled version matches that of the fixture. XCTAssertEqual(try keyPair.publicKey.marshal().asString(base: .base64Pad), MarshaledData.PUBLIC_RSA_KEY_1024) } + + func testImportFromMarshalledPrivateKey_2048() throws { + let keyPair = try LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: MarshaledData.PRIVATE_RSA_KEY_2048, base: .base64Pad) //RawPrivateKey(marshaledKey: MarshaledData.PRIVATE_KEY, base: .base64Pad) + + print(keyPair) + + XCTAssertNotNil(keyPair.privateKey) + XCTAssertNotNil(keyPair.publicKey) + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertTrue(keyPair.hasPrivateKey) + + /// Ensure that when we marshal the private key, we end up with that same data we imported. + XCTAssertEqual(try keyPair.privateKey?.marshal().asString(base: .base64Pad), MarshaledData.PRIVATE_RSA_KEY_2048) + + /// Ensures that the public key derived from the private key is correct and the marshaled version matches that of the fixture. + XCTAssertEqual(try keyPair.publicKey.marshal().asString(base: .base64Pad), MarshaledData.PUBLIC_RSA_KEY_2048) + } + + func testImportFromMarshalledPrivateKey_3072() throws { + let keyPair = try LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: MarshaledData.PRIVATE_RSA_KEY_3072, base: .base64Pad) //RawPrivateKey(marshaledKey: MarshaledData.PRIVATE_KEY, base: .base64Pad) + + print(keyPair) + + XCTAssertNotNil(keyPair.privateKey) + XCTAssertNotNil(keyPair.publicKey) + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertTrue(keyPair.hasPrivateKey) + + /// Ensure that when we marshal the private key, we end up with that same data we imported. + XCTAssertEqual(try keyPair.privateKey?.marshal().asString(base: .base64Pad), MarshaledData.PRIVATE_RSA_KEY_3072) + + /// Ensures that the public key derived from the private key is correct and the marshaled version matches that of the fixture. + XCTAssertEqual(try keyPair.publicKey.marshal().asString(base: .base64Pad), MarshaledData.PUBLIC_RSA_KEY_3072) + } + + func testImportFromMarshalledPrivateKey_4096() throws { + let keyPair = try LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: MarshaledData.PRIVATE_RSA_KEY_4096, base: .base64Pad) //RawPrivateKey(marshaledKey: MarshaledData.PRIVATE_KEY, base: .base64Pad) + + print(keyPair) + + XCTAssertNotNil(keyPair.privateKey) + XCTAssertNotNil(keyPair.publicKey) + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertTrue(keyPair.hasPrivateKey) + + /// Ensure that when we marshal the private key, we end up with that same data we imported. + XCTAssertEqual(try keyPair.privateKey?.marshal().asString(base: .base64Pad), MarshaledData.PRIVATE_RSA_KEY_4096) + + /// Ensures that the public key derived from the private key is correct and the marshaled version matches that of the fixture. + XCTAssertEqual(try keyPair.publicKey.marshal().asString(base: .base64Pad), MarshaledData.PUBLIC_RSA_KEY_4096) + } // - MARK: SIGN & VERIFY - func testRSAMessageSignVerify() throws { + func testRSAMessageSignVerify_StaticKey() throws { + let message = TestFixtures.RSA_1024.rawMessage.data(using: .utf8)! + + let rsa = try LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: TestFixtures.RSA_1024.privateMarshaled, base: .base64Pad) + + let signedData = try rsa.privateKey!.sign(message: message) + + print(signedData.asString(base: .base64Pad)) + + XCTAssertEqual(signedData.asString(base: .base64Pad), TestFixtures.RSA_1024.signedMessages["algid:sign:RSA:message-PKCS1v15:SHA256"]) + XCTAssertNotEqual(message, signedData) + + // This just ensures that a newly instantiated Pub SecKey matches the derived pubkey from the keypair... + let recoveredPubKey:RSAPublicKey = try RSAPublicKey(rawRepresentation: rsa.publicKey.data) + XCTAssertEqual(rsa.publicKey.data, recoveredPubKey.rawRepresentation) + XCTAssertEqual(try rsa.marshalPublicKey().asString(base: .base64Pad), TestFixtures.RSA_1024.publicMarshaled) + + // Ensure the rsaSignatureMessagePKCS1v15SHA256 algorithm works with our RSA KeyPair + //XCTAssertTrue(SecKeyIsAlgorithmSupported(recoveredPubKey, .verify, .rsaSignatureMessagePKCS1v15SHA256)) + + // Ensure the Signed Data is Valid for the given message + XCTAssertTrue(try rsa.publicKey.verify(signature: signedData, for: message)) + + // Ensure that the signature is no longer valid if it is tweaked in any way + XCTAssertThrowsError(try rsa.publicKey.verify(signature: Data(signedData.shuffled()), for: message)) + XCTAssertThrowsError(try rsa.publicKey.verify(signature: Data(signedData.dropFirst()), for: message)) + + // Ensure that the signature is no longer valid if the message is tweaked in any way + XCTAssertThrowsError(try rsa.publicKey.verify(signature: signedData, for: Data(message.shuffled()))) + XCTAssertThrowsError(try rsa.publicKey.verify(signature: signedData, for: Data(message.dropFirst()))) + } + + func testRSAMessageSignVerify_DynamicKey() throws { let message = "Hello, swift-libp2p-crypto!".data(using: .utf8)! let rsa = try LibP2PCrypto.Keys.generateKeyPair(.RSA(bits: .B1024)) @@ -569,178 +682,6 @@ final class libp2p_cryptoTests: XCTestCase { // print(keyPair) // } - /// RSA Object Identifier --> 2a 86 48 86 f7 0d 01 01 01 (bit length independent, pub/priv key independent) - /// ECDSA P384 --> 2a 86 48 ce 3d 02 01 - func testPemParsing() throws { - -// let pem = """ -// -----BEGIN PUBLIC KEY----- -// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcZ/r0nvJyHUUstIB5BqCUJ1CC -// Cd1nzle4bEpPQJ/S0Wn7mV2FDAeh+UcbVhZu9n+5zypYNjeZKapPqBLoT8eCK51y -// Kpzeb8LuEm3P8PK4xN18XBrIF1GprN8IIgSdK9f5SwnemutcURrY+PlWnvj7N5s/ -// 03RlJA3/NHVXpPW/VQIDAQAB -// -----END PUBLIC KEY----- -// """ - - let pem = """ - -----BEGIN PRIVATE KEY----- - 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 - 4pMwXeXP+LO8NIfRXV8mgrm86g== - -----END PRIVATE KEY----- - """ - -// /// EC P256 Public Key -// let pem = """ -// -----BEGIN PUBLIC KEY----- -// MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb4nB0k8CBVnKCHVHkxuXAkSlZuO5 -// Nsev1rzcRv5QHiJuWUKomFGadQlMSGwoDOHEDdW3ujcA6t0ADteHw6KrZg== -// -----END PUBLIC KEY----- -// """ - -// /// EC P384 Public Key -// let pem = """ -// -----BEGIN PUBLIC KEY----- -// MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEBwY0l7mq7hSBEZRld5ISWfSoFsYN3wwM -// hdD3cMU95DmYXzbqVHB4dCfsy7bexm4h9c0zs4CyTPzy3DV3vfmv1akQJIQv7l08 -// lx/YXNeGXTN4Gr9r4rwA5GvRl1p6plPL -// -----END PUBLIC KEY----- -// """ - -// /// EC P521 Public Key -// let pem = """ -// -----BEGIN PUBLIC KEY----- -// MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAp3v1UQWvSyQnkAUEBu+x/7ZrPtNJ -// SCUk9kMvuZMyGP1idwvspALuJjzrSFFlXObjlOjxucSbWhTYF/o3nc0XzpAA3dxA -// BYiMqH9vrVePoJMpv+DMdkUiUJ/WqHSOu9bJEi1h4fdqh5HHx4QZJY/iX/59VAi1 -// uSbAhALvbdGFbVpkcOs= -// -----END PUBLIC KEY----- -// """ - - /// EC P256 Private Key -// let pem = """ -// -----BEGIN PRIVATE KEY----- -// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZjQLlzempZx7YF1F -// +MK1HWZTNgLcC1MAufb/2/YZYk6hRANCAAQwgn0PfkIHiZ/K+3zA//CoDqU2PqDc -// aA3U5R68jmlZQITvMyBlMJl9Mjh0biIe88dAfRKeUm9FVMD2ErJ/006V -// -----END PRIVATE KEY----- -// """ - - /// PEMP384PKCS8 -// let pem = """ -// -----BEGIN PRIVATE KEY----- -// MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB7ERKhMR+mvz1NQ+oL -// i6ZJMACOcwbUetWcNnB4Mnx3j4XuhpkkHEW8E1+rXyjZ3UmhZANiAASYH+emlyXM -// kBSFJl0BiopDVuIIR47M4pLl00YNnuu/Rp5VHeVAHrP67i2Q92u5fk34eOSwQvkO -// VvktWsgtzAomIam4SHqE9bhvrHy6kW6QzxlERHTL+YkXEX8c6t8VOxk= -// -----END PRIVATE KEY----- -// """ - - let chunks = pem.split(separator: "\n") - guard chunks.count > 3, - let f = chunks.first, f.hasPrefix("-----BEGIN"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) - } - - //print("Attempting to decode: \(chunks[1.. 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// let raw = try BaseEncoding.decode(chunks[1.. 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// let raw = try BaseEncoding.decode(chunks[1.. 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// let raw = try BaseEncoding.decode(chunks[1.. 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// let raw = try BaseEncoding.decode(chunks[1.. \(key.asString(base: .base16))") - //print("Key 2 -> \(key2.asString(base: .base16))") - - //Create our CBC AES Cipher - let aes = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes), padding: .noPadding) - - // Try GCM - //let aes = try AES(key: key.bytes, blockMode: GCM(iv: iv.bytes, mode: .detached), padding: .noPadding) - - let decryptedKey = try aes.decrypt(ciphertext.bytes) - - print(decryptedKey.asString(base: .base64)) - - let deASN = try Asn1Parser.parse(data: Data(decryptedKey)) - print(deASN) - print("-----") - let unASN = try Asn1Parser.parse(data: pemToData(unencryptedPem)) - print(unASN) - - /// sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.objectIdentifier(data: 9 bytes), // [42,134,72,134,247,13,1,1,1] => RSA Private Key - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.objectIdentifier(data: 9 bytes), - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.octetString(data: 8 bytes), - /// libp2p_crypto.Asn1Parser.Node.integer(data: 2 bytes) - /// ]) - /// ]), - /// libp2p_crypto.Asn1Parser.Node.sequence(nodes: [ - /// libp2p_crypto.Asn1Parser.Node.objectIdentifier(data: 9 bytes), - /// libp2p_crypto.Asn1Parser.Node.octetString(data: 16 bytes) - /// ]) - /// ]), - /// libp2p_crypto.Asn1Parser.Node.octetString(data: 640 bytes) - /// ]) - /// ]) - - var unencRawPrivateKeyData:Data? = nil - if case .sequence(let top) = unASN { - if case .octetString(let d) = top.last { - print("Found our unenc octetString") - unencRawPrivateKeyData = d - } - } - - var decRawPrivateKeyData:Data? = nil - if case .sequence(let top) = deASN { - if case .octetString(let d) = top.last { - print("Found our dec octetString") - decRawPrivateKeyData = d - } - } - - guard let uRawPrivKeyData = unencRawPrivateKeyData else { - return XCTFail("Failed to parse our unencrypted private pem key...") - } - - guard let dRawPrivKeyData = decRawPrivateKeyData else { - return XCTFail("Failed to parse our decrypted private pem key...") - } - - print(uRawPrivKeyData.asString(base: .base64)) - print(dRawPrivKeyData.asString(base: .base64)) - - print(dRawPrivKeyData.count) - print(uRawPrivKeyData.count) - - let og = try RSAPrivateKey(rawRepresentation: uRawPrivKeyData) - let de = try RSAPrivateKey(rawRepresentation: dRawPrivKeyData) - - print(og) - - print(de) - - XCTAssertEqual(uRawPrivKeyData, dRawPrivKeyData) - XCTAssertEqual(og, de) - - - //XCTAssertEqual(uRawPrivKeyData.bytes, decryptedKey, "Not Equal") - + let keyPair = try LibP2PCrypto.Keys.KeyPair(pem: pem, password: "mypassword") + + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertEqual(keyPair.hasPrivateKey, true) + } + + func testImportEncryptedPemKey() throws { + /* + * Generated with + * openssl genpkey -algorithm RSA + * -pkeyopt rsa_keygen_bits:1024 + * -pkeyopt rsa_keygen_pubexp:65537 + * -out foo.pem + * openssl pkcs8 -in foo.pem -topk8 -v2 des3 -passout pass:mypassword + */ + let pem = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQISznrfHd+D58CAggA + MBQGCCqGSIb3DQMHBAhx0DnnUvDiHASCAoCceplm+Cmwlgvn4hNsv6e4c/S1iA7w + 2hU7Jt8JgRCIMWjP2FthXOAFLa2fD4g3qncYXcDAFBXNyoh25OgOwstO14YkxhDi + wG4TeppGUt9IlyyCol6Z4WhQs1TGm5OcD5xDta+zBXsBnlgmKLD5ZXPEYB+3v/Dg + SvM4sQz6NgkVHN52hchERsnknwSOghiK9mIBH0RZU5LgzlDy2VoBCiEPVdZ7m4F2 + dft5e82zFS58vwDeNN/0r7fC54TyJf/8k3q94+4Hp0mseZ67LR39cvnEKuDuFROm + kLPLekWt5R2NGdunSQlA79BkrNB1ADruO8hQOOHMO9Y3/gNPWLKk+qrfHcUni+w3 + Ofq+rdfakHRb8D6PUmsp3wQj6fSOwOyq3S50VwP4P02gKcZ1om1RvEzTbVMyL3sh + hZcVB3vViu3DO2/56wo29lPVTpj9bSYjw/CO5jNpPBab0B/Gv7JAR0z4Q8gn6OPy + qf+ddyW4Kcb6QUtMrYepghDthOiS3YJV/zCNdL3gTtVs5Ku9QwQ8FeM0/5oJZPlC + TxGuOFEJnYRWqIdByCP8mp/qXS5alSR4uoYQSd7vZG4vkhkPNSAwux/qK1IWfqiW + 3XlZzrbD//9IzFVqGRs4nRIFq85ULK0zAR57HEKIwGyn2brEJzrxpV6xsHBp+m4w + 6r0+PtwuWA0NauTCUzJ1biUdH8t0TgBL6YLaMjlrfU7JstH3TpcZzhJzsjfy0+zV + NT2TO3kSzXpQ5M2VjOoHPm2fqxD/js+ThDB3QLi4+C7HqakfiTY1lYzXl9/vayt6 + DUD29r9pYL9ErB9tYko2rat54EY7k7Ts6S5jf+8G7Zz234We1APhvqaG + -----END ENCRYPTED PRIVATE KEY----- + """ + + /// We don't support the DES3 Cipher yet + XCTAssertThrowsError(try LibP2PCrypto.Keys.KeyPair(pem: pem, password: "mypassword")) } + func testRSAEncryptedPrivateKeyPemExportManual() throws { + /* + * Generated with + * openssl genpkey -algorithm RSA + * -pkeyopt rsa_keygen_bits:1024 + * -pkeyopt rsa_keygen_pubexp:65537 + * -out foo.pem + * openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword + */ + let pem = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIP5QK2RfqUl4CAggA + MB0GCWCGSAFlAwQBAgQQj3OyM9gnW2dd/eRHkxjGrgSCAoCpM5GZB0v27cxzZsGc + O4/xqgwB0c/bSJ6QogtYU2KVoc7ZNQ5q9jtzn3I4ONvneOkpm9arzYz0FWnJi2C3 + BPiF0D1NkfvjvMLv56bwiG2A1oBECacyAb2pXYeJY7SdtYKvcbgs3jx65uCm6TF2 + BylteH+n1ewTQN9DLfASp1n81Ajq9lQGaK03SN2MUtcAPp7N9gnxJrlmDGeqlPRs + KpQYRcot+kE6Ew8a5jAr7mAxwpqvr3SM4dMvADZmRQsM4Uc/9+YMUdI52DG87EWc + 0OUB+fnQ8jw4DZgOE9KKM5/QTWc3aEw/dzXr/YJsrv01oLazhqVHnEMG0Nfr0+DP + q+qac1AsCsOb71VxaRlRZcVEkEfAq3gidSPD93qmlDrCnmLYTilcLanXUepda7ez + qhjkHtpwBLN5xRZxOn3oUuLGjk8VRwfmFX+RIMYCyihjdmbEDYpNUVkQVYFGi/F/ + 1hxOyl9yhGdL0hb9pKHH10GGIgoqo4jSTLlb4ennihGMHCjehAjLdx/GKJkOWShy + V9hj8rAuYnRNb+tUW7ChXm1nLq14x9x1tX0ciVVn3ap/NoMkbFTr8M3pJ4bQlpAn + wCT2erYqwQtgSpOJcrFeph9TjIrNRVE7Zlmr7vayJrB/8/oPssVdhf82TXkna4fB + PcmO0YWLa117rfdeNM/Duy0ThSdTl39Qd+4FxqRZiHjbt+l0iSa/nOjTv1TZ/QqF + wqrO6EtcM45fbFJ1Y79o2ptC2D6MB4HKJq9WCt064/8zQCVx3XPbb3X8Z5o/6koy + ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj + nPyn + -----END ENCRYPTED PRIVATE KEY----- + """ + + let keyPair = try LibP2PCrypto.Keys.KeyPair(pem: pem, password: "mypassword") + + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertEqual(keyPair.hasPrivateKey, true) + + /// Now lets try and reverse the process and export the encrypted private key... + + // Salt: [63, 148, 10, 217, 23, 234, 82, 94] + // Iterations: 2048 + // Password: mypassword + // Cipher IV: 128 - [143, 115, 178, 51, 216, 39, 91, 103, 93, 253, 228, 71, 147, 24, 198, 174] + + let cipher = PEM.CipherAlgorithm.aes_128_cbc(iv: [143, 115, 178, 51, 216, 39, 91, 103, 93, 253, 228, 71, 147, 24, 198, 174]) + + let pbkdf = PEM.PBKDFAlgorithm.pbkdf2(salt: [63, 148, 10, 217, 23, 234, 82, 94], iterations: 2048) + + // Generate Encryption Key from Password (confirmed key is same) + let key = try pbkdf.deriveKey(password: "mypassword", ofLength: cipher.desiredKeyLength) + + let pemData:ASN1.Node = .sequence(nodes: [ + .integer(data: Data(hex: "0x00")), + .sequence(nodes: [ + .objectIdentifier(data: Data(hex: "2a864886f70d010101")), + .null + ]), + .octetString(data: Data(hex: "3082025d02010002818100cac3f636b7733cf98fbe26aad1a6578f9889995e87b820bb729b798f5178311eb1145b9b05a8384193c7b594d03ff626b3b94f79220bbd2ad6f4e688d6d8afd3744a34afcd484809c35bdf31b9b8d2e0ebac5671f9e6eae68766c6803b074c53f663b5f689e9505d672724904a7d6ab4d1fc31cda4a169206f8f772339c9716f02030100010281807b726b084d101fe3609c48365f858271ae50b7cb519dcc6fd30acd2b705258b572e20e1387922f0dddc70cca192f97d16042461c5d9a000580f1811976945e16ad180666399cbe2e42d6a2c07a77fc8aaad950dedeec5d6576eb8fb07bb70989d273dc22e892b3df04982ba6d597ef8238b84fed5b84e493512554e43723f1c1024100f24805a6fcf6f4324f6248d6646538474c790d4111dbb2b816972a0164ea94fc3a209afe5b38d8c7ee1661610e94727669fe3261b4c112fc5c6629477e380687024100d63f23ad2abd389127009bee7bd872acdfb85b3b53d0029bcb2afc11895a8f0b6273d331d85ed39ac1b9d61afa1d72227b6dea3cec7ff78a9e277e9c3d460fd9024100bc2c8e1f4d782cdfea6226ba454d8c716c06d4f186024203d29fe3a3239342d5c7fbcd05e329facd05b1623eb4c93d41953f363846e0727388fc5bf1482a117f0240543566f1664e0f50c612b037514826f299d05d537942d5f3a42c55fd128e9c90adf6b678ee017f8c613e88cffba4dd3a7e671a5d2ddbb251328e756e358b37290241008acc81787457ab32ae0a939e13805651da3403b9ae46b7d31f1580b3fb7ca4ba109ac9b624e24d6c5ca5765c0ea09c00173eebabc283e29b25a281744dcbbbd6")) + ]) + + // Encrypt Plaintext + let ciphertext = try cipher.encrypt(bytes: ASN1.Encoder.encode(pemData), withKey: key) + + // Ensure the ciphertext is the same... + XCTAssertEqual(Data(ciphertext), Data(hex: "a9339199074bf6edcc7366c19c3b8ff1aa0c01d1cfdb489e90a20b58536295a1ced9350e6af63b739f723838dbe778e9299bd6abcd8cf41569c98b60b704f885d03d4d91fbe3bcc2efe7a6f0886d80d6804409a73201bda95d878963b49db582af71b82cde3c7ae6e0a6e9317607296d787fa7d5ec1340df432df012a759fcd408eaf6540668ad3748dd8c52d7003e9ecdf609f126b9660c67aa94f46c2a941845ca2dfa413a130f1ae6302bee6031c29aafaf748ce1d32f003666450b0ce1473ff7e60c51d239d831bcec459cd0e501f9f9d0f23c380d980e13d28a339fd04d6737684c3f7735ebfd826caefd35a0b6b386a5479c4306d0d7ebd3e0cfabea9a73502c0ac39bef557169195165c5449047c0ab78227523c3f77aa6943ac29e62d84e295c2da9d751ea5d6bb7b3aa18e41eda7004b379c516713a7de852e2c68e4f154707e6157f9120c602ca28637666c40d8a4d5159105581468bf17fd61c4eca5f7284674bd216fda4a1c7d74186220a2aa388d24cb95be1e9e78a118c1c28de8408cb771fc628990e59287257d863f2b02e62744d6feb545bb0a15e6d672ead78c7dc75b57d1c895567ddaa7f3683246c54ebf0cde92786d0969027c024f67ab62ac10b604a938972b15ea61f538c8acd45513b6659abeef6b226b07ff3fa0fb2c55d85ff364d79276b87c13dc98ed1858b6b5d7badf75e34cfc3bb2d13852753977f5077ee05c6a4598878dbb7e9748926bf9ce8d3bf54d9fd0a85c2aacee84b5c338e5f6c527563bf68da9b42d83e8c0781ca26af560add3ae3ff33402571dd73db6f75fc679a3fea4a3278f19bcfe52d49bf71733beaa5108e8852e1d8c1b5754816b876b38ceb547155af8a52a792308ccd9f6cd6a1b4b140e39cfca7")) + + //print(pbkdf.iterations.bytes(totalBytes: 2)) + +// print("*** DER ***") +// //print(try ASN1.Decoder.decode(data: Data(keyPair.privateKey!.exportPrivateKeyPEM(withHeaderAndFooter: false)))) +// print("***********") +// + // Encode Encrypted PEM (including pbkdf and cipher algos used) + let nodes:ASN1.Node = .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(hex: "2a864886f70d01050d")), + .sequence(nodes: [ + .sequence(nodes: [ + .objectIdentifier(data: Data(pbkdf.objectIdentifier)), + .sequence(nodes: [ + .octetString(data: Data(pbkdf.salt)), + .integer(data: Data( pbkdf.iterations.bytes(totalBytes: 2) )) + ]) + ]), + .sequence(nodes: [ + .objectIdentifier(data: Data(cipher.objectIdentifier)), + .octetString(data: Data(cipher.iv)) + ]) + ]) + ]), + .octetString(data: Data(ciphertext)) + ]) + + let encoded = ASN1.Encoder.encode(nodes) + + //print(encoded.asString(base: .base16)) + + // Ensure the raw ASN data is equivalent + XCTAssertEqual(Data(encoded).asString(base: .base16), "308202cf304906092a864886f70d01050d303c301b06092a864886f70d01050c300e04083f940ad917ea525e02020800301d060960864801650304010204108f73b233d8275b675dfde4479318c6ae04820280a9339199074bf6edcc7366c19c3b8ff1aa0c01d1cfdb489e90a20b58536295a1ced9350e6af63b739f723838dbe778e9299bd6abcd8cf41569c98b60b704f885d03d4d91fbe3bcc2efe7a6f0886d80d6804409a73201bda95d878963b49db582af71b82cde3c7ae6e0a6e9317607296d787fa7d5ec1340df432df012a759fcd408eaf6540668ad3748dd8c52d7003e9ecdf609f126b9660c67aa94f46c2a941845ca2dfa413a130f1ae6302bee6031c29aafaf748ce1d32f003666450b0ce1473ff7e60c51d239d831bcec459cd0e501f9f9d0f23c380d980e13d28a339fd04d6737684c3f7735ebfd826caefd35a0b6b386a5479c4306d0d7ebd3e0cfabea9a73502c0ac39bef557169195165c5449047c0ab78227523c3f77aa6943ac29e62d84e295c2da9d751ea5d6bb7b3aa18e41eda7004b379c516713a7de852e2c68e4f154707e6157f9120c602ca28637666c40d8a4d5159105581468bf17fd61c4eca5f7284674bd216fda4a1c7d74186220a2aa388d24cb95be1e9e78a118c1c28de8408cb771fc628990e59287257d863f2b02e62744d6feb545bb0a15e6d672ead78c7dc75b57d1c895567ddaa7f3683246c54ebf0cde92786d0969027c024f67ab62ac10b604a938972b15ea61f538c8acd45513b6659abeef6b226b07ff3fa0fb2c55d85ff364d79276b87c13dc98ed1858b6b5d7badf75e34cfc3bb2d13852753977f5077ee05c6a4598878dbb7e9748926bf9ce8d3bf54d9fd0a85c2aacee84b5c338e5f6c527563bf68da9b42d83e8c0781ca26af560add3ae3ff33402571dd73db6f75fc679a3fea4a3278f19bcfe52d49bf71733beaa5108e8852e1d8c1b5754816b876b38ceb547155af8a52a792308ccd9f6cd6a1b4b140e39cfca7") + + let exportedPEM = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + encoded.toBase64().split(intoChunksOfLength: 64).joined(separator: "\n") + "\n-----END ENCRYPTED PRIVATE KEY-----" + + XCTAssertEqual(exportedPEM, pem) + } + + func testRSAEncryptedPrivateKeyPemExport() throws { + /* + * Generated with + * openssl genpkey -algorithm RSA + * -pkeyopt rsa_keygen_bits:1024 + * -pkeyopt rsa_keygen_pubexp:65537 + * -out foo.pem + * openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword + */ + let pem = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIP5QK2RfqUl4CAggA + MB0GCWCGSAFlAwQBAgQQj3OyM9gnW2dd/eRHkxjGrgSCAoCpM5GZB0v27cxzZsGc + O4/xqgwB0c/bSJ6QogtYU2KVoc7ZNQ5q9jtzn3I4ONvneOkpm9arzYz0FWnJi2C3 + BPiF0D1NkfvjvMLv56bwiG2A1oBECacyAb2pXYeJY7SdtYKvcbgs3jx65uCm6TF2 + BylteH+n1ewTQN9DLfASp1n81Ajq9lQGaK03SN2MUtcAPp7N9gnxJrlmDGeqlPRs + KpQYRcot+kE6Ew8a5jAr7mAxwpqvr3SM4dMvADZmRQsM4Uc/9+YMUdI52DG87EWc + 0OUB+fnQ8jw4DZgOE9KKM5/QTWc3aEw/dzXr/YJsrv01oLazhqVHnEMG0Nfr0+DP + q+qac1AsCsOb71VxaRlRZcVEkEfAq3gidSPD93qmlDrCnmLYTilcLanXUepda7ez + qhjkHtpwBLN5xRZxOn3oUuLGjk8VRwfmFX+RIMYCyihjdmbEDYpNUVkQVYFGi/F/ + 1hxOyl9yhGdL0hb9pKHH10GGIgoqo4jSTLlb4ennihGMHCjehAjLdx/GKJkOWShy + V9hj8rAuYnRNb+tUW7ChXm1nLq14x9x1tX0ciVVn3ap/NoMkbFTr8M3pJ4bQlpAn + wCT2erYqwQtgSpOJcrFeph9TjIrNRVE7Zlmr7vayJrB/8/oPssVdhf82TXkna4fB + PcmO0YWLa117rfdeNM/Duy0ThSdTl39Qd+4FxqRZiHjbt+l0iSa/nOjTv1TZ/QqF + wqrO6EtcM45fbFJ1Y79o2ptC2D6MB4HKJq9WCt064/8zQCVx3XPbb3X8Z5o/6koy + ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj + nPyn + -----END ENCRYPTED PRIVATE KEY----- + """ + + let keyPair = try LibP2PCrypto.Keys.KeyPair(pem: pem, password: "mypassword") + + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertEqual(keyPair.hasPrivateKey, true) + + XCTAssertThrowsError(try LibP2PCrypto.Keys.KeyPair(pem: pem, password: "wrongpassword")) + + /// Now lets try and reverse the process and export the encrypted private key... + + // Salt: [63, 148, 10, 217, 23, 234, 82, 94] + // Iterations: 2048 + // Password: mypassword + // Cipher IV: 128 - [143, 115, 178, 51, 216, 39, 91, 103, 93, 253, 228, 71, 147, 24, 198, 174] + + let cipher = PEM.CipherAlgorithm.aes_128_cbc(iv: [143, 115, 178, 51, 216, 39, 91, 103, 93, 253, 228, 71, 147, 24, 198, 174]) + + let pbkdf = PEM.PBKDFAlgorithm.pbkdf2(salt: [63, 148, 10, 217, 23, 234, 82, 94], iterations: 2048) + + let exportedPEM = try keyPair.exportEncryptedPrivatePEMString(withPassword: "mypassword", usingPBKDF: pbkdf, andCipher: cipher) + + XCTAssertEqual(exportedPEM, pem) + + let differentPassword = try keyPair.exportEncryptedPrivatePEMString(withPassword: "wrongpassword", usingPBKDF: pbkdf, andCipher: cipher) + + XCTAssertNotEqual(differentPassword, pem) + XCTAssertEqual(differentPassword.count, pem.count) + } + + func testRSAEncryptedPrivateKeyPemExport2() throws { + let keyPair = try LibP2PCrypto.Keys.KeyPair(pem: TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED, password: "mypassword") + + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertEqual(keyPair.hasPrivateKey, true) + + XCTAssertThrowsError(try LibP2PCrypto.Keys.KeyPair(pem: TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED, password: "wrongpassword")) + + /// Now lets try and reverse the process and export the encrypted private key... + + // Salt: [227, 211, 237, 63, 238, 242, 38, 104] + // Iterations: 2048 + // Password: mypassword + // Cipher IV: 128 - [99, 63, 232, 90, 218, 184, 170, 21, 143, 54, 176, 16, 136, 237, 226, 231] + + let cipher = PEM.CipherAlgorithm.aes_128_cbc(iv: [99, 63, 232, 90, 218, 184, 170, 21, 143, 54, 176, 16, 136, 237, 226, 231]) + + let pbkdf = PEM.PBKDFAlgorithm.pbkdf2(salt: [227, 211, 237, 63, 238, 242, 38, 104], iterations: 2048) + + let exportedPEM = try keyPair.exportEncryptedPrivatePEMString(withPassword: "mypassword", usingPBKDF: pbkdf, andCipher: cipher) + + XCTAssertEqual(exportedPEM, TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED) + + //let differentPassword = try keyPair.exportEncryptedPrivatePEMString(withPassword: "wrongpassword", usingPBKDF: pbkdf, andCipher: cipher) + + //XCTAssertNotEqual(differentPassword, TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED) + //XCTAssertEqual(differentPassword.count, TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED.count) + } + + func testRSAEncryptedPrivateKeyPemRoundTrip() throws { + let keyPair = try LibP2PCrypto.Keys.KeyPair(.RSA(bits: .B1024)) + + XCTAssertEqual(keyPair.keyType, .rsa) + XCTAssertEqual(keyPair.hasPrivateKey, true) + + let exportedPEM = try keyPair.exportEncryptedPrivatePEMString(withPassword: "mypassword") + + + let recoveredKey = try LibP2PCrypto.Keys.KeyPair(pem: exportedPEM, password: "mypassword") + + XCTAssertEqual(recoveredKey.keyType, .rsa) + XCTAssertEqual(recoveredKey.hasPrivateKey, true) + + XCTAssertEqual(keyPair.privateKey?.rawRepresentation, recoveredKey.privateKey?.rawRepresentation) + XCTAssertEqual(keyPair.publicKey.rawRepresentation, recoveredKey.publicKey.rawRepresentation) + XCTAssertEqual(try keyPair.id(), try recoveredKey.id()) + //let differentPassword = try keyPair.exportEncryptedPrivatePEMString(withPassword: "wrongpassword", usingPBKDF: pbkdf, andCipher: cipher) + + //XCTAssertNotEqual(differentPassword, TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED) + //XCTAssertEqual(differentPassword.count, TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED.count) + } + func testRSAEncryptedPrivateKeyPem2() throws { // Generated with @@ -1554,8 +1427,8 @@ final class libp2p_cryptoTests: XCTestCase { // openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword let encryptedPem = TestPEMKeys.RSA_1024_PRIVATE_ENCRYPTED_PAIR.ENCRYPTED - let fromDecrypted = try LibP2PCrypto.Keys.parsePem(unencryptedPem) - let fromEncrypted = try LibP2PCrypto.Keys.parseEncryptedPem(encryptedPem, password: "mypassword") + let fromDecrypted = try LibP2PCrypto.Keys.KeyPair(pem: unencryptedPem) + let fromEncrypted = try LibP2PCrypto.Keys.KeyPair(pem: encryptedPem, password: "mypassword") XCTAssertNotNil(fromDecrypted.privateKey) XCTAssertNotNil(fromEncrypted.privateKey) @@ -1570,270 +1443,18 @@ final class libp2p_cryptoTests: XCTestCase { XCTAssertEqual(attributes!.size, 1024) XCTAssertTrue(attributes!.isPrivate) } - - private func pemToData(_ str:String) throws -> Data { - let chunks = str.split(separator: "\n") - guard chunks.count > 2, - let f = chunks.first, f.hasPrefix("-----BEGIN"), - let l = chunks.last, l.hasSuffix("-----") else { - throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) - } - - return try BaseEncoding.decode(chunks[1..} The private key protobuf -// */ -// import: async function (privateKey, password) { -// const base64 = multibase.names.base64 -// const encryptedKey = base64.decode(privateKey) -// const cipher = ciphers.create() -// return await cipher.decrypt(encryptedKey, password) -// } -// -// function create ({ -// algorithmTagLength = 16, -// nonceLength = 12, -// keyLength = 16, -// digest = 'sha256', -// saltLength = 16, -// iterations = 32767 -// } = {}) { -// const algorithm = 'aes-128-gcm' -// -// /** -// * Decrypts the given cipher text with the provided key. The `key` should -// * be a cryptographically safe key and not a plaintext password. To use -// * a plaintext password, use `decrypt`. The options used to create -// * this decryption cipher must be the same as those used to create -// * the encryption cipher. -// * -// * @private -// * @param {Uint8Array} ciphertextAndNonce The data to decrypt -// * @param {Uint8Array} key -// * @returns {Promise} -// */ -// async function decryptWithKey (ciphertextAndNonce, key) { // eslint-disable-line require-await -// // Create Uint8Arrays of nonce, ciphertext and tag. -// const nonce = ciphertextAndNonce.slice(0, nonceLength) -// const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength) -// const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength) -// -// // Create the cipher instance. -// const cipher = crypto.createDecipheriv(algorithm, key, nonce) -// -// // Decrypt and return result. -// cipher.setAuthTag(tag) -// return uint8ArrayConcat([cipher.update(ciphertext), cipher.final()]) -// } -// -// /** -// * Uses the provided password to derive a pbkdf2 key. The key -// * will then be used to decrypt the data. The options used to create -// * this decryption cipher must be the same as those used to create -// * the encryption cipher. -// * -// * @param {Uint8Array} data The data to decrypt -// * @param {string|Uint8Array} password A plain password -// */ -// async function decrypt (data, password) { // eslint-disable-line require-await -// // Create Uint8Arrays of salt and ciphertextAndNonce. -// const salt = data.slice(0, saltLength) -// const ciphertextAndNonce = data.slice(saltLength) -// -// if (typeof password === 'string' || password instanceof String) { -// password = uint8ArrayFromString(password) -// } -// -// // Derive the key using PBKDF2. -// const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest) -// -// // Decrypt and return result. -// return decryptWithKey(ciphertextAndNonce, key) -// } -// */ -// -// -// /// Private Key -// /// Raw Bytes: [1, 187, 116, 255, 157, 152, 71, 218, 87, 128, 62, 200, 148, 52, 164, 109, 237, 133, 89, 216, 240, 207, 80, 244, 60, 41, 32, 117, 184, 2, 231, 7, 9, 237, 110, 180, 86, 120, 103, 133, 84, 215, 104, 137, 101, 171, 127, 154, 54, 153, 229, 201, 46, 20, 1, 221, 211, 59, 129, 102, 129, 5, 76, 249, 30, 182] -// /// Bytes: 66 -// /// --- -// /// Public Key -// /// Raw Bytes: [0, 110, 196, 197, 185, 248, 73, 76, 67, 3, 127, 38, 67, 168, 163, 6, 20, 223, 146, 233, 198, 22, 77, 105, 120, 172, 14, 6, 95, 144, 206, 161, 48, 16, 46, 29, 26, 53, 177, 60, 132, 212, 146, 37, 203, 104, 104, 81, 129, 246, 149, 222, 98, 0, 249, 7, 134, 50, 83, 122, 75, 74, 242, 216, 234, 152, 0, 196, 255, 251, 57, 249, 20, 79, 95, 72, 156, 153, 174, 189, 153, 145, 253, 72, 69, 57, 114, 180, 179, 100, 173, 183, 100, 235, 84, 42, 66, 116, 93, 139, 64, 190, 225, 15, 90, 159, 178, 212, 204, 25, 174, 159, 36, 177, 45, 227, 230, 147, 191, 167, 141, 103, 47, 96, 183, 159, 143, 89, 155, 144, 199, 38] -// /// Bytes: 132 -// /// --- -// func testECRawRep() throws { -// let key = P521.Signing.PrivateKey() -// -// let rawPrivKey = key.rawRepresentation -// let rawPubKey = key.publicKey.rawRepresentation -// -// //print(key.x963Representation.asString(base: .base64)) -// print("Private Key") -// print("Raw Bytes: \(rawPrivKey.bytes)") -// print("Bytes: \(rawPrivKey.bytes.count)") -// print("---") -// //print(rawRep.asString(base: .base64)) -// print("Public Key") -// print("Raw Bytes: \(rawPubKey.bytes)") -// print("Bytes: \(rawPubKey.bytes.count)") -// print("---") -// -// let importedKey = try P521.Signing.PrivateKey(rawRepresentation: rawPrivKey) -// -// print(importedKey) -// } -// -// func testImportEncryptedPemKey() throws { -// /* -// * Generated with -// * openssl genpkey -algorithm RSA -// * -pkeyopt rsa_keygen_bits:1024 -// * -pkeyopt rsa_keygen_pubexp:65537 -// * -out foo.pem -// * openssl pkcs8 -in foo.pem -topk8 -v2 des3 -passout pass:mypassword -// */ -// let pem = """ -// -----BEGIN ENCRYPTED PRIVATE KEY----- -// MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQISznrfHd+D58CAggA -// MBQGCCqGSIb3DQMHBAhx0DnnUvDiHASCAoCceplm+Cmwlgvn4hNsv6e4c/S1iA7w -// 2hU7Jt8JgRCIMWjP2FthXOAFLa2fD4g3qncYXcDAFBXNyoh25OgOwstO14YkxhDi -// wG4TeppGUt9IlyyCol6Z4WhQs1TGm5OcD5xDta+zBXsBnlgmKLD5ZXPEYB+3v/Dg -// SvM4sQz6NgkVHN52hchERsnknwSOghiK9mIBH0RZU5LgzlDy2VoBCiEPVdZ7m4F2 -// dft5e82zFS58vwDeNN/0r7fC54TyJf/8k3q94+4Hp0mseZ67LR39cvnEKuDuFROm -// kLPLekWt5R2NGdunSQlA79BkrNB1ADruO8hQOOHMO9Y3/gNPWLKk+qrfHcUni+w3 -// Ofq+rdfakHRb8D6PUmsp3wQj6fSOwOyq3S50VwP4P02gKcZ1om1RvEzTbVMyL3sh -// hZcVB3vViu3DO2/56wo29lPVTpj9bSYjw/CO5jNpPBab0B/Gv7JAR0z4Q8gn6OPy -// qf+ddyW4Kcb6QUtMrYepghDthOiS3YJV/zCNdL3gTtVs5Ku9QwQ8FeM0/5oJZPlC -// TxGuOFEJnYRWqIdByCP8mp/qXS5alSR4uoYQSd7vZG4vkhkPNSAwux/qK1IWfqiW -// 3XlZzrbD//9IzFVqGRs4nRIFq85ULK0zAR57HEKIwGyn2brEJzrxpV6xsHBp+m4w -// 6r0+PtwuWA0NauTCUzJ1biUdH8t0TgBL6YLaMjlrfU7JstH3TpcZzhJzsjfy0+zV -// NT2TO3kSzXpQ5M2VjOoHPm2fqxD/js+ThDB3QLi4+C7HqakfiTY1lYzXl9/vayt6 -// DUD29r9pYL9ErB9tYko2rat54EY7k7Ts6S5jf+8G7Zz234We1APhvqaG -// -----END ENCRYPTED PRIVATE KEY----- -// """ -// -// let chunks = pem.split(separator: "\n") -// guard chunks.count > 3, -// let f = chunks.first, f.hasPrefix("-----BEGIN"), -// let l = chunks.last, l.hasSuffix("-----") else { -// throw NSError(domain: "Invalid PEM Format", code: 0, userInfo: nil) -// } -// -// let raw = try BaseEncoding.decode(chunks[1.. - /// - /// xsDNBGH7EL8BDADovR5cjh9P26RJ2uNxHuaEmdSFTY6q2uE5s4C6G+JEmtyuqhC9 - /// HEHgl7hv9LbsskLs50J0cCH9KQzMSl2OxztVGR8ABV06oDB+7fhHEPXNA4m1cLmQ - /// zGCp9uDxCs3tuDJRkEMSo97T6AnQwDsl5rBBMqR9c/B7Ozml1aER6ehtxSQt7tuu - /// x/9oD+9zFyUsBOuO20d/Km629h6IHfF+BadbJpzAqHunq+w2P4ks7XYlhFJdhMIq - /// W0h31rI1CO4tM+DG7+6Osz6EJeHWZOc9tWM/5YxmJOA7SZU4t+yedif4bzC4+aCH - /// W2AYlrWKivNOe4n46u1J+xlCtnLyK9Si39ylDuem118diaW1Qs99PkTbWawagYyc - /// c+0flTYeWugLxc2LDUfq9b6r7tfu9hyez0TghdRwlvrQlnZCmEt/Ndg0YF0N3BFE - /// dAzdpsiddEytH28L7hwHE5lECam4mGxBe8uNGYlDu8Viq7IYhyfCM0CNGlEwUQIW - /// I7ahSbKTX66aZxcAEQEAAc0lTG93ZWxsIFRvbXMgPGxvd2VsbHMudmF1bHRAZ21h - /// aWwuY29tPsLBFAQTAQoAPhYhBMRZ5UKAhHyTeb6a7dow5ilh9gp1BQJh+xC/AhsD - /// BQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJENow5ilh9gp1sNoMAItC - /// peoNv5MLrf5XJzP+bi+qmyAkiQSQ+0Jd17G1bUgrmpjeWXUqxu3fWgdxC6+CQLM9 - /// Fj69v11G1nrNlII0vyUN0nxPf9LS5qPMQKXZeAvT+lYrPaNWoE4ShI7MnJhxbME9 - /// TlQNikjOYgWt5dNlO+XqWAqP9ECQ0D2x2ulypVvhYPpvslp0+dQJGfIteTzqHHIY - /// Cu7drWh5Sjy2pvIKOIjjrKapjwqidy6laDi4NDhbu/JWp4im1ZRpIU6qTZgH4b+U - /// kCiAScNbnkYWtlIJ73j+Mh62FzZFQ+lSlVI8Fe/POyCwphVUw9smaEMO1WCsg0zP - /// LLAfnVLhWXdaB1QfchlWU1C/zVtnyCZvkVzDTFCa4IyBrAqUCnXfJHOgivtUU4rl - /// FvgynrDmeKjUqxJsVpEDS524nxR7aVdnfj34879SlJdliHeuwgpYF1q3eQYJMrR/ - /// wDCRBxbGpHTCOVjtU6GEeE9mV93SwVFAFHokD/iJMNTmGiUxFubHQXqCuAKuCc7A - /// zQRh+xC/AQwA0NZs9RjrGAicIFIp6C7NHheyFDL8tet5ZDPO2Lya22AXgnWo3/bs - /// QGXOlxhHJB7TK3Ma5wHAUKJ4BEvxpMtSwHnkFf/EAFZug2AXIvhJpdT8j/Lct6er - /// VusrPxfDmlTH0QeKl3IklnvowyO3r6VeI2Lxga0gWlV+/gRq0vzwXM4vqeXzjNFo - /// cCuNq4jSFT+zztYkud8GbAULBB5oeAp1Dhp+Uk84tu2Lg8rbBhK/H/ax4ozUrWi2 - /// G6azGx9psW9yq+LnQXaCSeGTn8XMqXzuwiD87TZgvuih/8iyMDoaFnuSKCw4v1WQ - /// 8wWusZ2e0a05SCiRpYDlv+CJ93J5F1ApZXjD9NpYcU0O53zl+wqqNbj1HtxoGPrT - /// EHvKPYZru/Dtea+sAxEXpYpUXFamQq4zIaI+dagD3vzSmFLRuCtaZElHhsS3t91K - /// tQBKon1ZEN/VwdO7tix6gfDMMa3BpnllI2eaw2X+Ucdkm/jJHuPoxfy1lbFP8byy - /// fDXvhR8Pnk8TABEBAAHCwPwEGAEKACYWIQTEWeVCgIR8k3m+mu3aMOYpYfYKdQUC - /// YfsQvwIbDAUJA8JnAAAKCRDaMOYpYfYKddh5DADGLKm9AmtCh7bUR+r+MKdCyjTO - /// LSiu0WACRUHIw+RLu5J5/haa7xZKPo6iN955oUS3j9CK0NTr/+ZxdDFwVc7kAjOn - /// eoZefac0CFRtLO26LQcVh5jc6YV0ZVtchvbnWCOfwtsvVfsQUCo7oIfP2CacEGq2 - /// EIqYLTJ7leVgo3AqlR5MmPA+umMKVcb/KDkesMnhgThw3F+81cocRhxWVlNQam0/ - /// PAj2uprXt+yWU8K94gojmolBuSdBBMZGArjBr/oCAhBrWarg4gEnC0tGQBBLU1Us - /// YEdv+2OYokrlIU96P2mtCDX+9GId+idUlgEPc+CQr3Um9W/syyxGI2e2wSlAlkjh - /// +h84GvLvZpM9HU4HrHF5AxmXoEe2BF7ICHHju0vdignpEdtQ/XHckSLlxcJz8i2S - /// XkMi7m1i5U0OWEXVr+Z3jeW2HVVEvEJ7aXL3oLLAVz3TROFY0M41zjrstyjg0sg5 - /// 5H93wgN5hgZ8MeDFEWbEh3p7e/MP9I/lo70bkA0= - /// =n56n - /// -----END PGP PUBLIC KEY BLOCK----- -// func testOpen_ASC_PGP_PublicKey() throws { -// let urls = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask) -// let downloadsFolder = urls[0] -// -// print(downloadsFolder) -// let fileURL = downloadsFolder.appendingPathComponent("C459E54280847C9379BE9AEDDA30E62961F60A75.asc") -// -// let pubKey = try String(contentsOf: fileURL, encoding: .utf8) -// -// print(pubKey) -// -// //let keyPair = try LibP2PCrypto.Keys.parsePem(pubKey) -// let keyPair = try LibP2PCrypto.Keys.importPublicPem(pubKey) -// -// print(keyPair) -// } - -// func testOpen_PGP_PublicKey() throws { -// -// let pubKey = """ -// xsDNBGH7EL8BDADovR5cjh9P26RJ2uNxHuaEmdSFTY6q2uE5s4C6G+JEmtyuqhC9 -// HEHgl7hv9LbsskLs50J0cCH9KQzMSl2OxztVGR8ABV06oDB+7fhHEPXNA4m1cLmQ -// zGCp9uDxCs3tuDJRkEMSo97T6AnQwDsl5rBBMqR9c/B7Ozml1aER6ehtxSQt7tuu -// x/9oD+9zFyUsBOuO20d/Km629h6IHfF+BadbJpzAqHunq+w2P4ks7XYlhFJdhMIq -// W0h31rI1CO4tM+DG7+6Osz6EJeHWZOc9tWM/5YxmJOA7SZU4t+yedif4bzC4+aCH -// W2AYlrWKivNOe4n46u1J+xlCtnLyK9Si39ylDuem118diaW1Qs99PkTbWawagYyc -// c+0flTYeWugLxc2LDUfq9b6r7tfu9hyez0TghdRwlvrQlnZCmEt/Ndg0YF0N3BFE -// dAzdpsiddEytH28L7hwHE5lECam4mGxBe8uNGYlDu8Viq7IYhyfCM0CNGlEwUQIW -// I7ahSbKTX66aZxcAEQEAAc0lTG93ZWxsIFRvbXMgPGxvd2VsbHMudmF1bHRAZ21h -// aWwuY29tPsLBFAQTAQoAPhYhBMRZ5UKAhHyTeb6a7dow5ilh9gp1BQJh+xC/AhsD -// BQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJENow5ilh9gp1sNoMAItC -// peoNv5MLrf5XJzP+bi+qmyAkiQSQ+0Jd17G1bUgrmpjeWXUqxu3fWgdxC6+CQLM9 -// Fj69v11G1nrNlII0vyUN0nxPf9LS5qPMQKXZeAvT+lYrPaNWoE4ShI7MnJhxbME9 -// TlQNikjOYgWt5dNlO+XqWAqP9ECQ0D2x2ulypVvhYPpvslp0+dQJGfIteTzqHHIY -// Cu7drWh5Sjy2pvIKOIjjrKapjwqidy6laDi4NDhbu/JWp4im1ZRpIU6qTZgH4b+U -// kCiAScNbnkYWtlIJ73j+Mh62FzZFQ+lSlVI8Fe/POyCwphVUw9smaEMO1WCsg0zP -// LLAfnVLhWXdaB1QfchlWU1C/zVtnyCZvkVzDTFCa4IyBrAqUCnXfJHOgivtUU4rl -// FvgynrDmeKjUqxJsVpEDS524nxR7aVdnfj34879SlJdliHeuwgpYF1q3eQYJMrR/ -// wDCRBxbGpHTCOVjtU6GEeE9mV93SwVFAFHokD/iJMNTmGiUxFubHQXqCuAKuCc7A -// zQRh+xC/AQwA0NZs9RjrGAicIFIp6C7NHheyFDL8tet5ZDPO2Lya22AXgnWo3/bs -// QGXOlxhHJB7TK3Ma5wHAUKJ4BEvxpMtSwHnkFf/EAFZug2AXIvhJpdT8j/Lct6er -// VusrPxfDmlTH0QeKl3IklnvowyO3r6VeI2Lxga0gWlV+/gRq0vzwXM4vqeXzjNFo -// cCuNq4jSFT+zztYkud8GbAULBB5oeAp1Dhp+Uk84tu2Lg8rbBhK/H/ax4ozUrWi2 -// G6azGx9psW9yq+LnQXaCSeGTn8XMqXzuwiD87TZgvuih/8iyMDoaFnuSKCw4v1WQ -// 8wWusZ2e0a05SCiRpYDlv+CJ93J5F1ApZXjD9NpYcU0O53zl+wqqNbj1HtxoGPrT -// EHvKPYZru/Dtea+sAxEXpYpUXFamQq4zIaI+dagD3vzSmFLRuCtaZElHhsS3t91K -// tQBKon1ZEN/VwdO7tix6gfDMMa3BpnllI2eaw2X+Ucdkm/jJHuPoxfy1lbFP8byy -// fDXvhR8Pnk8TABEBAAHCwPwEGAEKACYWIQTEWeVCgIR8k3m+mu3aMOYpYfYKdQUC -// YfsQvwIbDAUJA8JnAAAKCRDaMOYpYfYKddh5DADGLKm9AmtCh7bUR+r+MKdCyjTO -// LSiu0WACRUHIw+RLu5J5/haa7xZKPo6iN955oUS3j9CK0NTr/+ZxdDFwVc7kAjOn -// eoZefac0CFRtLO26LQcVh5jc6YV0ZVtchvbnWCOfwtsvVfsQUCo7oIfP2CacEGq2 -// EIqYLTJ7leVgo3AqlR5MmPA+umMKVcb/KDkesMnhgThw3F+81cocRhxWVlNQam0/ -// PAj2uprXt+yWU8K94gojmolBuSdBBMZGArjBr/oCAhBrWarg4gEnC0tGQBBLU1Us -// YEdv+2OYokrlIU96P2mtCDX+9GId+idUlgEPc+CQr3Um9W/syyxGI2e2wSlAlkjh -// +h84GvLvZpM9HU4HrHF5AxmXoEe2BF7ICHHju0vdignpEdtQ/XHckSLlxcJz8i2S -// XkMi7m1i5U0OWEXVr+Z3jeW2HVVEvEJ7aXL3oLLAVz3TROFY0M41zjrstyjg0sg5 -// 5H93wgN5hgZ8MeDFEWbEh3p7e/MP9I/lo70bkA0= -// =n56n -// """ -// -// let data = try BaseEncoding.decode(pubKey, as: .base64) -// print(data) -// -// //let keyPair = try LibP2PCrypto.Keys.parsePem(pubKey) -// //let keyPair = try LibP2PCrypto.Keys.importPublicPem(pubKey) -// //let keyPair = try LibP2PCrypto.Keys.importPublicDER(pubKey) -// //let keyPair = try LibP2PCrypto.Keys. -// -// //print(keyPair) -// } + func testImportSecp256k1PEM() throws { + let secp256k1Public = try Secp256k1PublicKey(pem: TestPEMKeys.SECP256k1_KeyPair.PUBLIC, asType: Secp256k1PublicKey.self) + + print(secp256k1Public) + + let secp256k1Private = try Secp256k1PrivateKey(pem: TestPEMKeys.SECP256k1_KeyPair.PRIVATE, asType: Secp256k1PrivateKey.self) + + print(secp256k1Private) + + XCTAssertEqual(secp256k1Private.publicKey, secp256k1Public) + } - /// Is the public key embedded in these IDs?? + /// The public key is embedded in certain PeerID's /// Dialed: 12D3KooWAfPDpPRRRBrmqy9is2zjU5srQ4hKuZitiGmh4NTTpS2d /// Provided: QmPoHmYtUt8BU9eiwMYdBfT6rooBnna5fdAZHUaZASGQY8 /// QmPoHmYtUt8BU9eiwMYdBfT6rooBnna5fdAZHUaZASGQY8 @@ -1995,21 +1518,23 @@ final class libp2p_cryptoTests: XCTestCase { /// Provided: Qmbp3SxL2SYcH6Ly4r5SGQwfxkDCJPuhJG35GCZimcTiBc /// Qmbp3SxL2SYcH6Ly4r5SGQwfxkDCJPuhJG35GCZimcTiBc func testEmbeddedEd25519PublicKey() throws { - let multi = try Multihash(b58String: "12D3KooWF5Qbrbvhhha1AcqRULWAfYzFEnKvWVGBUjw489hpo5La") print(multi) print("\(multi.value) (\(multi.value.count))") print("\(multi.digest!) (\(multi.digest!.count))") + /// Ensure we can instantiate a ED25519 Public Key from the multihash's digest (identity) let key = try Curve25519.Signing.PublicKey(rawRepresentation: multi.digest!.dropFirst(4)) print(key) + /// Ensure we can instantiate a KeyPair with the public key let kp = try LibP2PCrypto.Keys.KeyPair(publicKey: key) - print(kp) + XCTAssertEqual(kp.keyType, .ed25519) print(try kp.id(withMultibasePrefix: false)) + /// Ensure we can instantiate a key pair directly from the Multihash's Digest (Identity) let marshed = try LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: Data(multi.digest!)) print(marshed) @@ -2017,6 +1542,7 @@ final class libp2p_cryptoTests: XCTestCase { print(marshed.publicKey) print(try marshed.id(withMultibasePrefix: false)) + XCTAssertEqual(try marshed.id(withMultibasePrefix: false), "12D3KooWF5Qbrbvhhha1AcqRULWAfYzFEnKvWVGBUjw489hpo5La") } static var allTests = [ @@ -2026,6 +1552,7 @@ final class libp2p_cryptoTests: XCTestCase { ("testRSARawRepresentationRoundTrip", testRSARawRepresentationRoundTrip), ("testEd25519RawRepresentationRoundTrip", testEd25519RawRepresentationRoundTrip), ("testSecP256k1RawRepresentationRoundTrip", testSecP256k1RawRepresentationRoundTrip), + ("testCryptoSwiftRawRepresentationRoundTrip", testCryptoSwiftRawRepresentationRoundTrip), ("testRSAMarshaledRoundTrip", testRSAMarshaledRoundTrip), ("testRSAMashallingRoundTrip", testRSAMashallingRoundTrip), ("testED25519MarshallingRoundTrip", testED25519MarshallingRoundTrip), @@ -2036,8 +1563,12 @@ final class libp2p_cryptoTests: XCTestCase { ("testCreateKeyPairFromMarshalledPublicKey_3072", testCreateKeyPairFromMarshalledPublicKey_3072), ("testCreateKeyPairFromMarshalledPublicKey_4096", testCreateKeyPairFromMarshalledPublicKey_4096), ("testImportFromMarshalledPrivateKey_Manual", testImportFromMarshalledPrivateKey_Manual), - ("testImportFromMarshalledPrivateKey", testImportFromMarshalledPrivateKey), - ("testRSAMessageSignVerify", testRSAMessageSignVerify), + ("testImportFromMarshalledPrivateKey_1024", testImportFromMarshalledPrivateKey_1024), + ("testImportFromMarshalledPrivateKey_2048", testImportFromMarshalledPrivateKey_2048), + ("testImportFromMarshalledPrivateKey_3072", testImportFromMarshalledPrivateKey_3072), + ("testImportFromMarshalledPrivateKey_4096", testImportFromMarshalledPrivateKey_4096), + ("testRSAMessageSignVerify_StaticKey", testRSAMessageSignVerify_StaticKey), + ("testRSAMessageSignVerify_DynamicKey", testRSAMessageSignVerify_DynamicKey), ("testED25519MessageSignVerify", testED25519MessageSignVerify), ("testSecp256k1MessageSignVerify", testSecp256k1MessageSignVerify), ("testAESEncryption128", testAESEncryption128), @@ -2049,7 +1580,7 @@ final class libp2p_cryptoTests: XCTestCase { ("testHMACKey", testHMACKey), ("testHMACBaseEncoded", testHMACBaseEncoded), ("testHMACVerify", testHMACVerify), - ("testPemParsing", testPemParsing), + //("testPemParsing", testPemParsing), ("testPemParsing_RSA_1024_Public", testPemParsing_RSA_1024_Public), ("testPemParsing_RSA_1024_Public_2", testPemParsing_RSA_1024_Public_2), ("testPemParsing_RSA_2048_Public", testPemParsing_RSA_2048_Public), @@ -2064,13 +1595,17 @@ final class libp2p_cryptoTests: XCTestCase { ("testSecp256k1PemImport_Public", testSecp256k1PemImport_Public), ("testSecp256k1PemImport_Private_Manual", testSecp256k1PemImport_Private_Manual), ("testSecp256k1PemImport_Private", testSecp256k1PemImport_Private), - ("testRSAEncryptedPrivateKeyPem2_Manual", testRSAEncryptedPrivateKeyPem2_Manual), + //("testRSAEncryptedPrivateKeyPem2_Manual", testRSAEncryptedPrivateKeyPem2_Manual), + ("testImportEncryptedPemKey", testImportEncryptedPemKey), + ("testRSAEncryptedPrivateKeyPemExportManual", testRSAEncryptedPrivateKeyPemExportManual), + ("testRSAEncryptedPrivateKeyPemExport", testRSAEncryptedPrivateKeyPemExport), + ("testRSAEncryptedPrivateKeyPem", testRSAEncryptedPrivateKeyPem), ("testRSAEncryptedPrivateKeyPem2", testRSAEncryptedPrivateKeyPem2), ("testRSA_Pem_Parsing_Public", testRSA_Pem_Parsing_Public), ("testRSA_Pem_Parsing_Private", testRSA_Pem_Parsing_Private), ("testEd25519_Pem_Parsing_Public", testEd25519_Pem_Parsing_Public), - ("testEd25519_Pem_Parsing_Private", testEd25519_Pem_Parsing_Private), - ("testSecp256k1_Pem_Parsing_Public", testSecp256k1_Pem_Parsing_Public), + //("testEd25519_Pem_Parsing_Private", testEd25519_Pem_Parsing_Private), + //("testSecp256k1_Pem_Parsing_Public", testSecp256k1_Pem_Parsing_Public), ("testSecp256k1_Pem_Parsing_Private", testSecp256k1_Pem_Parsing_Private), ("testEmbeddedEd25519PublicKey", testEmbeddedEd25519PublicKey) ] diff --git a/Tests/LibP2PCryptoTests/marshaled.swift b/Tests/LibP2PCryptoTests/Marshaled.swift similarity index 100% rename from Tests/LibP2PCryptoTests/marshaled.swift rename to Tests/LibP2PCryptoTests/Marshaled.swift diff --git a/Tests/LibP2PCryptoTests/pem.swift b/Tests/LibP2PCryptoTests/Pem.swift similarity index 99% rename from Tests/LibP2PCryptoTests/pem.swift rename to Tests/LibP2PCryptoTests/Pem.swift index 49dae78..dc136fc 100644 --- a/Tests/LibP2PCryptoTests/pem.swift +++ b/Tests/LibP2PCryptoTests/Pem.swift @@ -583,3 +583,4 @@ struct TestPEMKeys { -----END CERTIFICATE----- """ } + diff --git a/Tests/LibP2PCryptoTests/fixtures.swift b/Tests/LibP2PCryptoTests/fixtures.swift new file mode 100644 index 0000000..cd2b276 --- /dev/null +++ b/Tests/LibP2PCryptoTests/fixtures.swift @@ -0,0 +1,365 @@ +// +// Fixtures.swift +// +// +// Created by Brandon Toms on 6/6/22. +// + +import Foundation + +struct TestFixtures { + struct Fixture { + let keySize: Int + let publicDER: String + let privateDER: String + let publicPEM:String + let privatePEM:String + let encryptedPEM:[String:String] + let encryptionPassword:String + let publicMarshaled:String + let privateMarshaled:String + let rawMessage: String + let encryptedMessage: [String: String] + let signedMessages: [String: String] + } +} + +extension TestFixtures { + static let RSA_1024 = Fixture( + keySize: 1024, + publicDER: """ + MIGJAoGBALs3YIii1qV6Q01m2CZ5I+4ZJUttX13AbzK+QbWTNgRhwOneo7ZoWXtLwGfmyDYv+B0aOzjwC7Nh3+iKhLD4BQasZxBjzTirjrLib4TMMruWVCkmIWft5fLnoW7Q0gu/yIlRlKw1r/fqVWgVCLezZCJ+v5LJvbUfjdo6ffaqcKoRAgMBAAE= + """, + privateDER: """ + MIICXQIBAAKBgQC7N2CIotalekNNZtgmeSPuGSVLbV9dwG8yvkG1kzYEYcDp3qO2aFl7S8Bn5sg2L/gdGjs48AuzYd/oioSw+AUGrGcQY804q46y4m+EzDK7llQpJiFn7eXy56Fu0NILv8iJUZSsNa/36lVoFQi3s2Qifr+Syb21H43aOn32qnCqEQIDAQABAoGBAJ6hO3BK2aj4wZIR9FAVEPar48fXcpjTduT+BFs/0uM/mOAQv5LNNBSeiPcAut//ITI3ibqi2qcx5TD6PZhdbpNXtIvPf1/DtjsZiYupTd61KVBnxDn2v0z6LsSDfTe3rhXMmgPxfNQtNKxMhvM0d0vRwxSFA+OleG8PFXFuAczpAkEA47azreYAfdmMcD52Mvam0N7BpPlMEv3Tk6eMawy7aWKIqvE5FYHAZufMNO4bX9Jd0EoOChYKHa2g4ZewVXwajwJBANJ428iiHfzxGLDGkSKEW8pDIzbk2TAyhZLb/TfRfPpOvgW1kKH9HuByf3ZZ6wYd+E9ZHnC/CctbMSJeYmtTwV8CQCnAJdGMiiqI6KbrzOArOQqyzO5ihwA0acZ4wdYez33TAxvUfpLi51P2zAooXfyDpY+7BDf1MoWegBDcrwf9aSECQQCTkyqIAyQDtwkY6iHZkfTKXUjTtKKUqNf/oUBrYve+ineyiRxgeJqtxZqZ4XJpV5pECLjPVSQI8mgBMSzRFGkBAkAXssWCIWVbBqcKO5xQM9juuqjmFDf2x9opay0C9MOASo7Af8LIM4moz1sVwU/H8PLKvUhlmVCX9kaF7b0PWBjl + """, + publicPEM: """ + -----BEGIN PUBLIC KEY----- + MIG1MA0GCSqGSIb3DQEBAQUAA4GjADCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC + gYEAuzdgiKLWpXpDTWbYJnkj7hklS21fXcBvMr5BtZM2BGHA6d6jtmhZe0vAZ+bI + Ni/4HRo7OPALs2Hf6IqEsPgFBqxnEGPNOKuOsuJvhMwyu5ZUKSYhZ+3l8uehbtDS + C7/IiVGUrDWv9+pVaBUIt7NkIn6/ksm9tR+N2jp99qpwqhECAwEAAQ== + -----END PUBLIC KEY----- + """, + privatePEM: """ + -----BEGIN PRIVATE KEY----- + MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALs3YIii1qV6Q01m + 2CZ5I+4ZJUttX13AbzK+QbWTNgRhwOneo7ZoWXtLwGfmyDYv+B0aOzjwC7Nh3+iK + hLD4BQasZxBjzTirjrLib4TMMruWVCkmIWft5fLnoW7Q0gu/yIlRlKw1r/fqVWgV + CLezZCJ+v5LJvbUfjdo6ffaqcKoRAgMBAAECgYEAnqE7cErZqPjBkhH0UBUQ9qvj + x9dymNN25P4EWz/S4z+Y4BC/ks00FJ6I9wC63/8hMjeJuqLapzHlMPo9mF1uk1e0 + i89/X8O2OxmJi6lN3rUpUGfEOfa/TPouxIN9N7euFcyaA/F81C00rEyG8zR3S9HD + FIUD46V4bw8VcW4BzOkCQQDjtrOt5gB92YxwPnYy9qbQ3sGk+UwS/dOTp4xrDLtp + Yoiq8TkVgcBm58w07htf0l3QSg4KFgodraDhl7BVfBqPAkEA0njbyKId/PEYsMaR + IoRbykMjNuTZMDKFktv9N9F8+k6+BbWQof0e4HJ/dlnrBh34T1kecL8Jy1sxIl5i + a1PBXwJAKcAl0YyKKojopuvM4Cs5CrLM7mKHADRpxnjB1h7PfdMDG9R+kuLnU/bM + Cihd/IOlj7sEN/UyhZ6AENyvB/1pIQJBAJOTKogDJAO3CRjqIdmR9MpdSNO0opSo + 1/+hQGti976Kd7KJHGB4mq3FmpnhcmlXmkQIuM9VJAjyaAExLNEUaQECQBeyxYIh + ZVsGpwo7nFAz2O66qOYUN/bH2ilrLQL0w4BKjsB/wsgziajPWxXBT8fw8sq9SGWZ + UJf2RoXtvQ9YGOU= + -----END PRIVATE KEY----- + """, + encryptedPEM: [:], + encryptionPassword: "", + publicMarshaled: """ + CAASogEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALs3YIii1qV6Q01m2CZ5I+4ZJUttX13AbzK+QbWTNgRhwOneo7ZoWXtLwGfmyDYv+B0aOzjwC7Nh3+iKhLD4BQasZxBjzTirjrLib4TMMruWVCkmIWft5fLnoW7Q0gu/yIlRlKw1r/fqVWgVCLezZCJ+v5LJvbUfjdo6ffaqcKoRAgMBAAE= + """, + privateMarshaled: """ + CAAS4QQwggJdAgEAAoGBALs3YIii1qV6Q01m2CZ5I+4ZJUttX13AbzK+QbWTNgRhwOneo7ZoWXtLwGfmyDYv+B0aOzjwC7Nh3+iKhLD4BQasZxBjzTirjrLib4TMMruWVCkmIWft5fLnoW7Q0gu/yIlRlKw1r/fqVWgVCLezZCJ+v5LJvbUfjdo6ffaqcKoRAgMBAAECgYEAnqE7cErZqPjBkhH0UBUQ9qvjx9dymNN25P4EWz/S4z+Y4BC/ks00FJ6I9wC63/8hMjeJuqLapzHlMPo9mF1uk1e0i89/X8O2OxmJi6lN3rUpUGfEOfa/TPouxIN9N7euFcyaA/F81C00rEyG8zR3S9HDFIUD46V4bw8VcW4BzOkCQQDjtrOt5gB92YxwPnYy9qbQ3sGk+UwS/dOTp4xrDLtpYoiq8TkVgcBm58w07htf0l3QSg4KFgodraDhl7BVfBqPAkEA0njbyKId/PEYsMaRIoRbykMjNuTZMDKFktv9N9F8+k6+BbWQof0e4HJ/dlnrBh34T1kecL8Jy1sxIl5ia1PBXwJAKcAl0YyKKojopuvM4Cs5CrLM7mKHADRpxnjB1h7PfdMDG9R+kuLnU/bMCihd/IOlj7sEN/UyhZ6AENyvB/1pIQJBAJOTKogDJAO3CRjqIdmR9MpdSNO0opSo1/+hQGti976Kd7KJHGB4mq3FmpnhcmlXmkQIuM9VJAjyaAExLNEUaQECQBeyxYIhZVsGpwo7nFAz2O66qOYUN/bH2ilrLQL0w4BKjsB/wsgziajPWxXBT8fw8sq9SGWZUJf2RoXtvQ9YGOU= + """, + rawMessage: "LibP2P RSA Keys!", + encryptedMessage: [ + "algid:encrypt:RSA:raw": "XEknpMZTaFgRf1E4QcpTbZVAea8rMSqe4XSM/UMkcqq5N3XV9nXJk0LKHN/ffr4O5ZHMO84q24bXFMpGJFbm2noBcepVIOoP+V6eLrxWbTadEP0IZGPEq/yvywchDYx9KWE4HVEdjZHCXgGBbsu1TZBj4myvmExaSQi26fKzQnM=", + "algid:encrypt:RSA:PKCS1": "Won/LQrLJHoqCWNJhEw5fqI3gxcCWpIOuHhn428RYgcyYIWEVOpM0hLJd3O+sBujNqYwqlEIOZ8odtZQMStrsqqhz6HyqIAP7LVT1ZGDEEEI26Nth6OS0WGsY4tL1TfaDuZ1fm6c82w7GUzZPfNfv3+2bsckGC/3ZvE6uMr4ybA=" + ], + signedMessages: [ + "algid:sign:RSA:raw": "KIelsfGQa2HB4sIcj/YNwkgNVnv/j7LI3EEReISXbzteEHmF3lolDFB0MVS2Y0yrKtTnk+WHWUQx4iYxOON180tbL6JoDQh+Ut3KucaTpd+Pfue248EvZpu45jFQdXXlqTx9+BgOeyfAT5whxypaE1v98Dj9eo6gsfR1rPvLVvg=", + "algid:sign:RSA:digest-PKCS1v15": "usfkT3z+Uu2mTpu9Qjs5+Q/rtLgy0VQUccI/J5uc6WdpUvscvxCEFTgbMWbXk0FFR9Uj4q3K29rq/oRnBYrBoVBA6nUZUXlaqjR9X+T+BUQxro3rXXAQSImxga02e4r6bIbFSgI86RkQVPYYZssaNr37+XhHwM78SMuGGzJuZSQ=", + "algid:sign:RSA:digest-PKCS1v15:SHA1": "RpTfRiTK8Eey2FQ+IHRQbbrDh+WaEv+ioB3VkpoGTKabbr6VaBc/dP5rTTzlyobsPFZLCyLHfL/VuN20nqWqKc3R+LX81wDCiduqf/Q3UibglUomUrIwB62qxY/3m/xhv6raVM+HDAs+hi6GHBMSU97dtJSrV4UrL0H1y8vg4kk=", + "algid:sign:RSA:digest-PKCS1v15:SHA224": "ZPxZ0pX2C2bH+1+GRe+v7pTXWGuhMp3fORFE9qMFh/o8EBL/+Wh4z6WdnIR1P+TA2FE4g0Pyl1vSv4EMs6lHSyOqJ7lcnq83VVKyrqZXev8TlaYnacU61GRbDfV0xssD5CYodW1hlkB67qjir2UL8HZnZJCALWK89JFRPsCYoUM=", + "algid:sign:RSA:digest-PKCS1v15:SHA256": "NS882+s/9pSMOgIvAxLZzRnO/zIpx0uBqev37LR3ylM+1m5KDw/QKg0H/Md6x60qSWIQ1tgp1GLCTO7qyov2l9B+s2WRFGBoq60zjLVSzz76NOdiXq3LOaNll8oT1t9nKoXo6ZaTKFMFQE5ljk2cM4MyGskHysveAcsULRhDLZg=", + "algid:sign:RSA:digest-PKCS1v15:SHA384": "PIX+JagbtZI093xYwDPLVrNS+j8rKObxwu6JDN9rowD/icSkl70pEQekFbxJVLSVrK1BT/WeoEXxF4h6iIpHmxQuUv9+a2iPKtmWx6rKCiFmMfvP4ssd11JDW5tOfa1mJeaQL1GmUgwlte2lYV8GQGn3ys83EIbJgCM5rKZwgCA=", + "algid:sign:RSA:digest-PKCS1v15:SHA512": "Crw/AtIwICiM5Eex7Sn1pgpOfjDN4sxc7cqCWAKjBzJStrxvCQg8pwnTuE/J3zPrJ/wPJIKXlfdOSOzW1h/VJ32fpoj8UiK1OJt4/psSWJZuzDbqpBm83uFqctI/WdI4f1AgUaCauu+eu04C8viN17ljPHJdTYhsjAOgBCnZnqs=", + "algid:sign:RSA:message-PKCS1v15:SHA1": "qfLYtrDiipLkL4BM3jb6AHq5vABxrEd6hAk4aAhMs88fBtHAuMHpyreozI1DorX8VCpoyOmFm5EviX30tReGur6++YICw0r3ySL5Jx3mn2upu79zPEMiD3PYg25MMQFOq94A6/u7U46rw+DFMqmQR6lO/0JndhOA3/m+n53qGXU=", + "algid:sign:RSA:message-PKCS1v15:SHA224": "hZZqdej0F818bR/j9IgYeufE04jCS3rTOgxDox/gUM1DD89vu7og+dHdGbZlk4ijV+kJO/QRee25nm1v//oihUz4jWTBKdGv50Jiqg3MJMo/qmq44hZEeit0LL0F0yOoAXqlbHhoJweEaSuWFCq424qjFMCV8dK02/3ToQ/O4n8=", + "algid:sign:RSA:message-PKCS1v15:SHA256": "gLpu6xrAzv+zVagq4opnnPkC7LsvsvCUMxUlp62E5o8XV2/4gf6IgJgcbrcmdMszHMGH7IBnJ+9NAL5XwE3GZAbIBLAdTYy4UwmrdOKBDguszmtW5sITWLlFyjjPoDR2NiZcOsJAjhRzBLqFOvjOwHK1S5t0wzYJLYUUMZuc8PE=", + "algid:sign:RSA:message-PKCS1v15:SHA384": "Oh8ruSarZ8IoY5JQgZyckTk77Fw/9K3HYl4JJUv6eqTnJnCK8d7K2IPSfdaNJbzw+CF8rMc5w7EFL3zcgd+7aueHK7ggMrrkMFzrKZMTbx9WooryvcKBhQKggT05/VwaB3nkwyPXFwLpBhMNw6/mE+vG+SZtJNlrAyCuiebeL2Q=", + "algid:sign:RSA:message-PKCS1v15:SHA512": "IED27MAOGl79/9EncYzx+V0nCa5MlsUmj5pAZEHes6X7W8kQaF5Yof3psDsp5QvDiXJNFjoDHGL/y99mCGiw/jRL6bAr0ni3889z4/4zhQPZZ1n9J03yBxmg2KhPL1K9X/yC4EkJQ2rdHTQPwjhoa7H7rIEB5dyiKUWgxBujgD0=" + ] + ) + static let RSA_2048 = Fixture( + keySize: 2048, + publicDER: """ + MIIBCgKCAQEA1T2+nRi/gUmgIbRwECPXzrjHwV1i+SenaIZTK2v76QoTAj9DVTTXbpNJ49goYb+P9PHFubCof+Lf7PXQ5w3370GoB2Ypl200d4NPaymN4hn3nv8th61864Yh96wHDwtSRF+3NpeJa00PKcg4Ghgt5plgyctpzDtllY1zi1MN1kIwCJxoTSy+Z4WtdYIvIcGkVl7SJkNg3ZzGiwpFMPtH6j5R4MNzGgn/Oy7A2x2AQYbndS3qbMr9ftXQEP8eVTxeHnpOffK7C0L0cmMZqlV1uDQaDAXj7R1sMpbNFw+gNSR3clBix2UL4wZAs0bZ3nyEbJVjczzVCxGArbpq2ozFWwIDAQAB + """, + privateDER: """ + MIIEowIBAAKCAQEA1T2+nRi/gUmgIbRwECPXzrjHwV1i+SenaIZTK2v76QoTAj9DVTTXbpNJ49goYb+P9PHFubCof+Lf7PXQ5w3370GoB2Ypl200d4NPaymN4hn3nv8th61864Yh96wHDwtSRF+3NpeJa00PKcg4Ghgt5plgyctpzDtllY1zi1MN1kIwCJxoTSy+Z4WtdYIvIcGkVl7SJkNg3ZzGiwpFMPtH6j5R4MNzGgn/Oy7A2x2AQYbndS3qbMr9ftXQEP8eVTxeHnpOffK7C0L0cmMZqlV1uDQaDAXj7R1sMpbNFw+gNSR3clBix2UL4wZAs0bZ3nyEbJVjczzVCxGArbpq2ozFWwIDAQABAoIBADZVnEs1Mh7EXtwXuPIz39pZtPRtUjnAQ+TbTTfkNPUFTyCkdAizBS20tAAtZOS7RfgY3tPY0qZ7balYXVlycrlxFlqESpa+Cb9mIwdgODnjeff2d2h56TmuHNuZ5taLgPPRG8L6S9aedP2leb4UaSW38TSZ8yRKAjFgMI/Qotbz4SpSY8VE9QODzrGqFVc0HU/M4GrMVqptPAA4OEneb//tCOK/ATrQIsIyBN4Mb+dqCV6tRZKNP9nYRX/yVRI3aD8rAEA4daa4KDelXdTNoVHONTPYTFjAhXEHDldxXS1nIVAVwHaIYGbCgXGveboZLeGcibZVKHCMvHhHraqldykCgYEA+bqgtEcBTgZXrqefJnf375bH2b9zUjYhzI5Il+3fe7k/LwdYNVK/HzUZqKuwO1N6bah2P/9NECrO4IinIAMNAocCfYgfK5l8XmenftA65ek3lzbj2fpVihYgaHfJA5I1LkUbUIbmVv5gQndWZRDcg9Hv7k+q+5AaeP4E5OO6Sp8CgYEA2piOcWtag4zvWkm6ZKvKCRDt380xIvxZqEUkhL4vIY9egjEIdBis23sd9FOlxv570qCp7Q5DWA5ZZFCun9zms1k9HDp60L+kG/xEd1RDdgQwirYkkL8eriIYrGvksB/ywT1pECmj1mcr8h7MNY6efCdjEpUrP2KSYvZlBmF5B8UCgYEA38WYnRIHHEhYp3syBAF6HKlKmVaRWniBHs/cQq93E2FyOYzmQJnOAoPNYzO9Ldvml35dv4jgH/2L9OzefLPfI4Wg+KVR8PqO0/UjxGGIdV3eX1RjJX7IyXx8O8AiUl3f438vM6A9pHQ6AzT2KIfMYR5sVWnz94kv/3z3G7bnxlcCgYBoI8HIuvI2NdBZ3UIVb9oik5QfyOud1UcJaVdKfiiJ/nlx4NY8KP1A2tica7VQpjBrWetaai8fJkbkCaQHuP+Xde4tIpccGBCg3H/psZUqBjjx/HBTHRoKr2e9zPD4D2BhO1ZwQsYxAJnpEU8MPNO4JjOGyNX/roA68VOTxKAaWQKBgAO9iPiQqAff3AtICPI1rMZO+f3kzxD70Dxl2OVRodKcQ13QQXyNfhN/2f5KbEQmAElYrSazaLhYYxTVaSDfkxZUK5xTYSRlAUKqxyTzPQ1f8Cg73GmlpK/cJJSoPorJbYSQj1JXQnfaSh8w4X18KpD/VKMb4IGgygHWAnigTO46 + """, + publicPEM: """ + -----BEGIN PUBLIC KEY----- + MIIBOjANBgkqhkiG9w0BAQEFAAOCAScAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + MIIBCgKCAQEA1T2+nRi/gUmgIbRwECPXzrjHwV1i+SenaIZTK2v76QoTAj9DVTTX + bpNJ49goYb+P9PHFubCof+Lf7PXQ5w3370GoB2Ypl200d4NPaymN4hn3nv8th618 + 64Yh96wHDwtSRF+3NpeJa00PKcg4Ghgt5plgyctpzDtllY1zi1MN1kIwCJxoTSy+ + Z4WtdYIvIcGkVl7SJkNg3ZzGiwpFMPtH6j5R4MNzGgn/Oy7A2x2AQYbndS3qbMr9 + ftXQEP8eVTxeHnpOffK7C0L0cmMZqlV1uDQaDAXj7R1sMpbNFw+gNSR3clBix2UL + 4wZAs0bZ3nyEbJVjczzVCxGArbpq2ozFWwIDAQAB + -----END PUBLIC KEY----- + """, + privatePEM: """ + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVPb6dGL+BSaAh + tHAQI9fOuMfBXWL5J6dohlMra/vpChMCP0NVNNduk0nj2Chhv4/08cW5sKh/4t/s + 9dDnDffvQagHZimXbTR3g09rKY3iGfee/y2HrXzrhiH3rAcPC1JEX7c2l4lrTQ8p + yDgaGC3mmWDJy2nMO2WVjXOLUw3WQjAInGhNLL5nha11gi8hwaRWXtImQ2DdnMaL + CkUw+0fqPlHgw3MaCf87LsDbHYBBhud1Lepsyv1+1dAQ/x5VPF4eek598rsLQvRy + YxmqVXW4NBoMBePtHWwyls0XD6A1JHdyUGLHZQvjBkCzRtnefIRslWNzPNULEYCt + umrajMVbAgMBAAECggEANlWcSzUyHsRe3Be48jPf2lm09G1SOcBD5NtNN+Q09QVP + IKR0CLMFLbS0AC1k5LtF+Bje09jSpnttqVhdWXJyuXEWWoRKlr4Jv2YjB2A4OeN5 + 9/Z3aHnpOa4c25nm1ouA89EbwvpL1p50/aV5vhRpJbfxNJnzJEoCMWAwj9Ci1vPh + KlJjxUT1A4POsaoVVzQdT8zgasxWqm08ADg4Sd5v/+0I4r8BOtAiwjIE3gxv52oJ + Xq1Fko0/2dhFf/JVEjdoPysAQDh1prgoN6Vd1M2hUc41M9hMWMCFcQcOV3FdLWch + UBXAdohgZsKBca95uhkt4ZyJtlUocIy8eEetqqV3KQKBgQD5uqC0RwFOBleup58m + d/fvlsfZv3NSNiHMjkiX7d97uT8vB1g1Ur8fNRmoq7A7U3ptqHY//00QKs7giKcg + Aw0ChwJ9iB8rmXxeZ6d+0Drl6TeXNuPZ+lWKFiBod8kDkjUuRRtQhuZW/mBCd1Zl + ENyD0e/uT6r7kBp4/gTk47pKnwKBgQDamI5xa1qDjO9aSbpkq8oJEO3fzTEi/Fmo + RSSEvi8hj16CMQh0GKzbex30U6XG/nvSoKntDkNYDllkUK6f3OazWT0cOnrQv6Qb + /ER3VEN2BDCKtiSQvx6uIhisa+SwH/LBPWkQKaPWZyvyHsw1jp58J2MSlSs/YpJi + 9mUGYXkHxQKBgQDfxZidEgccSFinezIEAXocqUqZVpFaeIEez9xCr3cTYXI5jOZA + mc4Cg81jM70t2+aXfl2/iOAf/Yv07N58s98jhaD4pVHw+o7T9SPEYYh1Xd5fVGMl + fsjJfHw7wCJSXd/jfy8zoD2kdDoDNPYoh8xhHmxVafP3iS//fPcbtufGVwKBgGgj + wci68jY10FndQhVv2iKTlB/I653VRwlpV0p+KIn+eXHg1jwo/UDa2JxrtVCmMGtZ + 61pqLx8mRuQJpAe4/5d17i0ilxwYEKDcf+mxlSoGOPH8cFMdGgqvZ73M8PgPYGE7 + VnBCxjEAmekRTww807gmM4bI1f+ugDrxU5PEoBpZAoGAA72I+JCoB9/cC0gI8jWs + xk75/eTPEPvQPGXY5VGh0pxDXdBBfI1+E3/Z/kpsRCYASVitJrNouFhjFNVpIN+T + FlQrnFNhJGUBQqrHJPM9DV/wKDvcaaWkr9wklKg+islthJCPUldCd9pKHzDhfXwq + kP9UoxvggaDKAdYCeKBM7jo= + -----END PRIVATE KEY----- + """, + encryptedPEM: [:], + encryptionPassword: "", + publicMarshaled: """ + CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVPb6dGL+BSaAhtHAQI9fOuMfBXWL5J6dohlMra/vpChMCP0NVNNduk0nj2Chhv4/08cW5sKh/4t/s9dDnDffvQagHZimXbTR3g09rKY3iGfee/y2HrXzrhiH3rAcPC1JEX7c2l4lrTQ8pyDgaGC3mmWDJy2nMO2WVjXOLUw3WQjAInGhNLL5nha11gi8hwaRWXtImQ2DdnMaLCkUw+0fqPlHgw3MaCf87LsDbHYBBhud1Lepsyv1+1dAQ/x5VPF4eek598rsLQvRyYxmqVXW4NBoMBePtHWwyls0XD6A1JHdyUGLHZQvjBkCzRtnefIRslWNzPNULEYCtumrajMVbAgMBAAE= + """, + privateMarshaled: """ + CAASpwkwggSjAgEAAoIBAQDVPb6dGL+BSaAhtHAQI9fOuMfBXWL5J6dohlMra/vpChMCP0NVNNduk0nj2Chhv4/08cW5sKh/4t/s9dDnDffvQagHZimXbTR3g09rKY3iGfee/y2HrXzrhiH3rAcPC1JEX7c2l4lrTQ8pyDgaGC3mmWDJy2nMO2WVjXOLUw3WQjAInGhNLL5nha11gi8hwaRWXtImQ2DdnMaLCkUw+0fqPlHgw3MaCf87LsDbHYBBhud1Lepsyv1+1dAQ/x5VPF4eek598rsLQvRyYxmqVXW4NBoMBePtHWwyls0XD6A1JHdyUGLHZQvjBkCzRtnefIRslWNzPNULEYCtumrajMVbAgMBAAECggEANlWcSzUyHsRe3Be48jPf2lm09G1SOcBD5NtNN+Q09QVPIKR0CLMFLbS0AC1k5LtF+Bje09jSpnttqVhdWXJyuXEWWoRKlr4Jv2YjB2A4OeN59/Z3aHnpOa4c25nm1ouA89EbwvpL1p50/aV5vhRpJbfxNJnzJEoCMWAwj9Ci1vPhKlJjxUT1A4POsaoVVzQdT8zgasxWqm08ADg4Sd5v/+0I4r8BOtAiwjIE3gxv52oJXq1Fko0/2dhFf/JVEjdoPysAQDh1prgoN6Vd1M2hUc41M9hMWMCFcQcOV3FdLWchUBXAdohgZsKBca95uhkt4ZyJtlUocIy8eEetqqV3KQKBgQD5uqC0RwFOBleup58md/fvlsfZv3NSNiHMjkiX7d97uT8vB1g1Ur8fNRmoq7A7U3ptqHY//00QKs7giKcgAw0ChwJ9iB8rmXxeZ6d+0Drl6TeXNuPZ+lWKFiBod8kDkjUuRRtQhuZW/mBCd1ZlENyD0e/uT6r7kBp4/gTk47pKnwKBgQDamI5xa1qDjO9aSbpkq8oJEO3fzTEi/FmoRSSEvi8hj16CMQh0GKzbex30U6XG/nvSoKntDkNYDllkUK6f3OazWT0cOnrQv6Qb/ER3VEN2BDCKtiSQvx6uIhisa+SwH/LBPWkQKaPWZyvyHsw1jp58J2MSlSs/YpJi9mUGYXkHxQKBgQDfxZidEgccSFinezIEAXocqUqZVpFaeIEez9xCr3cTYXI5jOZAmc4Cg81jM70t2+aXfl2/iOAf/Yv07N58s98jhaD4pVHw+o7T9SPEYYh1Xd5fVGMlfsjJfHw7wCJSXd/jfy8zoD2kdDoDNPYoh8xhHmxVafP3iS//fPcbtufGVwKBgGgjwci68jY10FndQhVv2iKTlB/I653VRwlpV0p+KIn+eXHg1jwo/UDa2JxrtVCmMGtZ61pqLx8mRuQJpAe4/5d17i0ilxwYEKDcf+mxlSoGOPH8cFMdGgqvZ73M8PgPYGE7VnBCxjEAmekRTww807gmM4bI1f+ugDrxU5PEoBpZAoGAA72I+JCoB9/cC0gI8jWsxk75/eTPEPvQPGXY5VGh0pxDXdBBfI1+E3/Z/kpsRCYASVitJrNouFhjFNVpIN+TFlQrnFNhJGUBQqrHJPM9DV/wKDvcaaWkr9wklKg+islthJCPUldCd9pKHzDhfXwqkP9UoxvggaDKAdYCeKBM7jo= + """, + rawMessage: "LibP2P RSA Keys!", + encryptedMessage: [ + "algid:encrypt:RSA:raw": "iTCnmwcO0DT+/X53IyA9kT6JqLt66psHOy4nMa0Jss1JONHvAn1DOFOQWCORVg1pXGghpzgUE1ZdpG8kP22wU3g3Uq9F7vreGzgJNrSSpiOa+C2eqKYBYS9WgXAnLIetoDZSRhrlndc/XJ2wpJk74pVEEl6LGh+iWzUarjrK2Lm2brb1UvOO7q4agJfyPBl/+u0MEMgXkT0DsGzZmsqJWU6s8PPx8ZJiHpa24gFL301sZoiABMpUZubhdANw9Rf7g4uRBJlHFZWc2g5VKCRoAZ0KJT4AaN+ghQe4ttkn1xWYBJ1MjulShNMm8fvf2XxX/3zh7yTKDQmsRAVjHvHezQ==", + "algid:encrypt:RSA:PKCS1": "d68lvqxdu3sxiYncI9KKGkpud+2vsiSmVF/hGvLL20S9lfFbw0seXT/AkRSqk+si7YvLzTfk9vWMP3jALNJtALnuIZT/0tkLJFozgS1Xs+6AfG+f/eplNDYgpkvIPnzWlorItERuAYzgp/8TRQrA0plf07EkR4AQoyy6q9zob+8ppUnp4GHuuuhrf53H1ITqP+1o/gkVdIprSG01ivL2oa1yN6KKwXvN3BPjWWz8K9eNQ20MapmKOEoz6Z7qYgRfX9wh7VNTkKNQrwtkwVcuzIn31ISlgYd7vF0s6GtnjqoJoWELhatWDRBEIspzG4VND8tl0fJkZX5noUYow83BJg==" + ], + signedMessages: [ + "algid:sign:RSA:raw": "Nc1q2PkJEoIpCO8Hpk+fThlQzqSh6UiR4DScA97Bqu0r2q9zZCp72/wsNBDC3JzBt6AafL2rXdDs9/fGCt1P8mH7uwbTHWYgBgx+iuS/pUUNkBD8CQN129VICE/eQq8tiv5qU6fAGZHOFSRtsezJJbX76FLOtdzaXNVeMW5VLaGtTCqYXBqTuuuFZ1IX/Jx0h3D1ZXHq3InSU3RuR5Dc754gZdf4I4pflBLI4He6kjCFlHDBAAiuYD3NznQaLFlp/kpX8uRqWtM8xjhvy9LdTBciWOmx1EiyrDtw+90QoSAyy2Bk6HJchFX9kIMJXk+g8Si1saYiAbMe5INsvdSmZw==", + "algid:sign:RSA:digest-PKCS1v15": "bNZTmxqjUS2N4IVxbCZa81KKX8FeGU6Fg4XKB6MeEHFVRg715d+7++77jbuCKyL4MBWZNVejb5Gb3OA+BqVyGk2PuzdIMnJexpwVmbOMReK+2rDgSMewC8aUNw33VTbIcXvjqmBpHLERPJ2JnDbC/Lyua9h3IAZ1NTosqWCAuReqOhvx6nZtNg9c/Kc5BCUvW02fZDWmxl9Gh3UrQI53uyKayDdYW6oXHPPSy8CwminKMgY827XVQtSHEUcHnvxH9sInmAFTcrgd8/ohE8swdhYzcuesmI9X3vAuAUQ69nPdXQIyf2IKv7bNhbZqj3qXmaeOW4IwJFPnKzCZLHQocQ==", + "algid:sign:RSA:digest-PKCS1v15:SHA1": "oqJ8TYXX/kjSJteyrlEza2sh6CKQ/mvZ/w8bNpIqagnNIZN/rGwFAESEJOVo8/XZ1a+uPsIs7WFJPKuUhaQRe8K2hAkkuWEoaQZ9wTPoMeKrRXImXXO6sx1YVMdAZqlPVkK1nUU+Dm3BuKqfVRPYkk6xjOCqQvLi3kiTkllNROS9wiry+6peLzH8kGJZ4d+pOEM+Yp7Ik6Zho2Qf8dbiCLjmZf4fHdKZEH85Hi0/ZQg27Ublvoruz4P8L43uLM+oqo9G0xL4k1fLnar0krmU9PZhnU0/1j0OF8bbJypzHCgiGAMYgMiAU3yESaxNN5vQTyEPbxjRkrtJ+1pyhZ2ANg==", + "algid:sign:RSA:digest-PKCS1v15:SHA224": "fWnLlUGp8ZVRtisoSzfLmRFqsNK0Pqa/mlHsBcxJ0HxKuruJzkEVKjcelLALyDZIKMUUaPmvKgrnLN7dFNFK1/YUBcMfAr555LeNHh/TVC+j1IUOlxknFSnJDIEc5AjT4GcxvklhD45dDWafC67gb+4M/2A6h70OXun651whOGaVMEztQShqKsHEvLYfFrgjjueHz7tl3J9LjLqKJBC/dsVN1JXLJuyqnHiJOtZ1WwX1foT0bLoAAiFA6f86l75Q1iHdiBSrsNrQI6Aa2JomV8Bb2q0fwuNiMH12x06uh+SWPVCh+oh4bYJvUNXMus49GX+BOwTQZktFqvbqgcDyMA==", + "algid:sign:RSA:digest-PKCS1v15:SHA256": "PqiHMiZWKgJK9Zp2bi4yLdpQgXSn9lrgHHGIv8bbLEi6U9ASlHbCvyYlqk+rqTOkK5+K9O5gIXlq8/MY+fFIb49xmRgYq+LB/MLrBla9eWAfPkX/H2FZfKR0KHHM6IaGYyjhbHLji949B2i9D8TNOmNL7YDP5AT1LrlxL3r0mx6JFZ2BpZd+HugShA+fK0fzsu1NYoiZyhwSFGOuFKf/ejIx/4jtMYX8NiStBRAu/1/zHumKMS0vOPSYzZh1XFmIEsfuF1yXAqRa/QtMFE6rZ9MXhdqeIvNxQrmcVOMpWvOSr3p1YgQW2xZf+TBLIz123rNlt+oS1wtEHH0GyTkgTg==", + "algid:sign:RSA:digest-PKCS1v15:SHA384": "dSokzmcHo8Obo47aOPozp9v2cufcXr2OGRxJl2W/E3o5YcQIps1xNYvyCXw2h3plD91BsWDTE7j4PyYbUmtHtKoDiq2dC6PL07wM2uf8fcXxusIHJOvGv3/lA1wbUik0vLZMO4RPiiBH4yY8OVD9PXCh34yvtxxPorvsOzkEjYadvAwXElBcbXDuOoMTGbAHgaShXsH9Ft+X5rKEtQl0neqyxwr61cTnKIJxxXWlIKT9SDMj2iu0tT6W8BEKLzF9Sl+KuR4sPlFBfJAll38gx/0OM3iy6/avxldIJgKvOFVoMQ0kpSkg/sotXR3ZJtiNVE0W+QYKu0fOSv+q7JwKiw==", + "algid:sign:RSA:digest-PKCS1v15:SHA512": "avuTXRpjQsh1QOR/FFwDp3SOPzo90hCeeVqpUHMhXcdvy1AJmmTL86Y5Gwn3fWWiweFxnXcqfaJ3rQk1bBcLmANP3F29uEajdhecuq+7ma2ieshASsWp/cAq5mg8uIdVEldtcmYO8u5tIqmruOFw2plrgk6ZgKWVPvfSNTcoovZx9DxGNrsfHF+Z1SUpbnDIYVESniDhsfTsv2xtCJjz1100SfKGyKyty9PhPTSDJATJrPjaGmEIcD/nBbiqEf1RhEv7tb9qp8DdkikhYhtDKSIMxLd04dmeyeb412I4uW4gdidv+F+Jqus6DQPmo/Pg8upL3So1A2P006OAgFvH7Q==", + "algid:sign:RSA:message-PKCS1v15:SHA1": "fOdsPPhBGL2MmzaZt5fsH4Ih4FuYeRx1Vse/+F4YeRsMZqNrhOiPyXWqB8j5LS2ggROYVD6J3G4zubYIJ/T5jwqBgftwJ18GxYaA7oWDKbcJVrHK3RsMv0kQkD/Fl+yunf/LMUANomieA/OyAxHGjMXZuT3m//3OruH/j3ck+h47eVY5U6aeNyor8cFXDlo7ljm9xCNpzhrEvbFT0AD7gfv4q4riKiT4cw+h7c9k0a2LyoUAYdS+wDUpG8hJbQ05NScyhCoEB0YquM1qB6R0xgSzEi33kTFMahGQdBlOg1ILmsBf23FfyJxZC4ur6JYEGAB3+WdjDXHE945f+vq3Bw==", + "algid:sign:RSA:message-PKCS1v15:SHA224": "M9xryRG5ScZhfEXt31Lc5hTbwz8Gh1JYC6zWO+yV8skq6FTIFH5KjWCGWgQCXEt8GdHJjKAw8z9qe5cc7qXUuLN6YB+QuKUZs0prqKAru6nLI1dzJ501eHfxQPSBgQKLNTrx4sHfXBUDEvBPOpEgC8Xw8Ye18VrhM7xGoxeUD/8KYh7eG2+pbnDKn1G2+aEJdpvZs5r78dr1cHggSSCaQnD1AjRufnH0vGz/qr8OOxvuZDyLXlQxbASas245VXrth3/Je8xFeOFoWNSGbh/jo6u/mZZh57WeSEBz3+8806Ge70E09yAAC+4Cub/04mBttPstRYFV1DaUjM7MaqyVLg==", + "algid:sign:RSA:message-PKCS1v15:SHA256": "h0bEBt1D3W4VGn4zFNqF4OCdgrGdMWRlZnehKhNiQjhDG4GO8iJlHAtXpoZffHYnC0Sv/TfH1UpDIEAKj/2LG+kH97WFDC0TlBCxh11tRPvfVCs2x1U11zMe9df4j4f5O0iZNic4nPwhvaFTDjmzPXXPepy3XHrknf0w/ocjKhhF2DAQmPu9910N+cOjjq6f2HZ1i7wrmFLqWAMwt8rdoNJZ3VHpPi+PtU5zcE9to8HzG4GaY7X6CHC9eTgnxYdRUa+gNhvjwSr7jngRWD9zvPBoWLBQ5IS6UdzxJi474MojPuKEXBDw4Yj4ZfywlbAoCrcle6UeiaBO8/6gkqudaQ==", + "algid:sign:RSA:message-PKCS1v15:SHA384": "SnFSQhsAzjr7y7lpEfbP/F5gnNu2pNpvQQxjwktfM0i0HACDhIJLKmOpTgRgieo22cYkeP/+HxmGrzlhil7Ie8rOT/yL183X9tSrS8605USsO7AxvdVHLGcggyglc8vTHcPz7lrIhB9nbXVQo3bqMy4hS2tyOxUaZdiBHofpuaDkxer2OtzJGTbzKH3ImwfAQEBuUM6KIf790Z2eiX6bW/T795eyPFwM2TsBKK6U3GmEgm/xFNf5GEeTwEhOa0Antv3DOyu3MCo7u2EjriKrvSs+oJsmWoS28TjtVVhDR09hUcXm9nR0xszZQFHJSwzOAOzm8LW5xtqYuud7Q6niiw==", + "algid:sign:RSA:message-PKCS1v15:SHA512": "AGCa30WjEHkcHkSfRKJ7x4ZbAe+Be1XZ+W4823fke3sS9ya/TIHu29dfNgoPYh3r4C+WHCxsw3NvHNx1lOqZaJke55ftBA6NlV/Cp1e1fXvSBU7x79w4e8CfBG0AX1wbrBw2TexMu5yLkSEfqr8tSsxk7XOwDK9pytJOj9mCVizaTkpwmR70PjrRhsSgKqovwG8fpJD3si/PBiWM2B1DUgSWS4Doa4wl6qPvtHoTeraMnKBiy/K0ODXnCnMwcKo7Ke0pgoSZoXFsyU/ojXuTOTB/rWfVoJHKbAnG+1XwR+Ydxn1db3iFOd5KlNArFkmw+oF+ioKRgkzq5fhBfsZTcw==" + ] + ) + static let RSA_3072 = Fixture( + keySize: 3072, + publicDER: """ + MIIBigKCAYEAzsersX7f5oUheLRaN4CGOWEhJcWJXKqQ+ZrHW8kSqCbMvohtPh7O6/Tjg5smkEM/OE2MvkPBbe04AAg9xPwrcCg/yY8NkHGTe05fAsuxa4a2pU5OP401DH6qwzZLPhGlm0Nrn9cKaDul6iwBi54U79kXj5JveHgb2NGWZqb09xAhdFUFhIqAAzRFtw2JPICdrySudBYfqoTr58EAIb+UNORlT9xuM6Q3LgH8LzkexZV3O9mYAOYgEL4pD5yYHXQL+IkqpnHMCujZ+lXMyfGPlvm3bSUmaEXXZ2DfgmHUz3yTTA7Y/r+U7qi8zAaAtTwbNbxyDP48vurbf6dGE6ojkhYVVvk00oOCMpbPpl0po+I8Kre4HPAqqJzAZg0i1DiTjTy2J3i7e4K45Hp8VObLUVkNcqaOFDh3v1liZdK9jLW6JvMD/f+Zmes8mNF5pTzO5WERKqqdtvI0Ml/VFZFV7NPt9KfW+21ThmR9fXrU6A5Jiiq4pWBtkfBMPlKITMoBAgMBAAE= + """, + privateDER: """ + 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 + """, + publicPEM: """ + -----BEGIN PUBLIC KEY----- + MIIBujANBgkqhkiG9w0BAQEFAAOCAacAMIIBojANBgkqhkiG9w0BAQEFAAOCAY8A + MIIBigKCAYEAzsersX7f5oUheLRaN4CGOWEhJcWJXKqQ+ZrHW8kSqCbMvohtPh7O + 6/Tjg5smkEM/OE2MvkPBbe04AAg9xPwrcCg/yY8NkHGTe05fAsuxa4a2pU5OP401 + DH6qwzZLPhGlm0Nrn9cKaDul6iwBi54U79kXj5JveHgb2NGWZqb09xAhdFUFhIqA + AzRFtw2JPICdrySudBYfqoTr58EAIb+UNORlT9xuM6Q3LgH8LzkexZV3O9mYAOYg + EL4pD5yYHXQL+IkqpnHMCujZ+lXMyfGPlvm3bSUmaEXXZ2DfgmHUz3yTTA7Y/r+U + 7qi8zAaAtTwbNbxyDP48vurbf6dGE6ojkhYVVvk00oOCMpbPpl0po+I8Kre4HPAq + qJzAZg0i1DiTjTy2J3i7e4K45Hp8VObLUVkNcqaOFDh3v1liZdK9jLW6JvMD/f+Z + mes8mNF5pTzO5WERKqqdtvI0Ml/VFZFV7NPt9KfW+21ThmR9fXrU6A5Jiiq4pWBt + kfBMPlKITMoBAgMBAAE= + -----END PUBLIC KEY----- + """, + privatePEM: """ + -----BEGIN PRIVATE KEY----- + MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQDOx6uxft/mhSF4 + tFo3gIY5YSElxYlcqpD5msdbyRKoJsy+iG0+Hs7r9OODmyaQQz84TYy+Q8Ft7TgA + CD3E/CtwKD/Jjw2QcZN7Tl8Cy7FrhralTk4/jTUMfqrDNks+EaWbQ2uf1wpoO6Xq + LAGLnhTv2RePkm94eBvY0ZZmpvT3ECF0VQWEioADNEW3DYk8gJ2vJK50Fh+qhOvn + wQAhv5Q05GVP3G4zpDcuAfwvOR7FlXc72ZgA5iAQvikPnJgddAv4iSqmccwK6Nn6 + VczJ8Y+W+bdtJSZoRddnYN+CYdTPfJNMDtj+v5TuqLzMBoC1PBs1vHIM/jy+6tt/ + p0YTqiOSFhVW+TTSg4Iyls+mXSmj4jwqt7gc8CqonMBmDSLUOJONPLYneLt7grjk + enxU5stRWQ1ypo4UOHe/WWJl0r2Mtbom8wP9/5mZ6zyY0XmlPM7lYREqqp228jQy + X9UVkVXs0+30p9b7bVOGZH19etToDkmKKrilYG2R8Ew+UohMygECAwEAAQKCAYEA + mXxwn1Gz1KJhU/1bMfN+rjzApA7ahSQ+dGhFLoY0CQiz1YHixMeNhGvELT4PA+pG + lP5LSDd27qe0d6GmZ/9eJuRZZL2ArgUAGfj0YVjdGxllnZJgVp/0DpA/JKO6ehoT + OQp0arLuEKyl3QpJI1913wrfiDyD7YIJ+YipAQ1Qq8sXaghqDhXYK4izbsrnYTd+ + 1DCAJb7+Rk5f5SjHdkWENzaJVRwj2MU+CDKFBHB7vOHTHWPIhnJKvuLIC8SKzOID + Yw8toucbjKZu9ZFnwhOj8okn4K4QirQMCWzZz6aybUXIb97hohc+W9n2Y1Ra457e + ofw6q5dwXBIwhS7Wa+Fk4SK/C+uSoKAM9IGQQBHbD4JQIl3f6DfN0S3BSr5p4LqH + 0QdmaAIN9wO3T3chiasSlyKBjf6Uq0SnIh+uvkFV37e86Hxph/P2PSJibmxQcND8 + oqN5oDcwMN5wpJasnbjnILuvsz0DXtPohtATX+DRutD5d4XSUJJEs9aynv4ekOD5 + AoHBAOvYvk490NofXGTChNr69T5yTB3wsvU9rniVnYuIGYwOptg21MBrRvDJ9rwx + 1FyPJIeTOjWHmq/h6pqI4/Usm3yoZ0NDxMxRB5fc90BB3+eP5g9yIZZym7tkZmez + tyk+t/r2EvAZG7b/UJ483rGDkCmgWPHWZk3nynnl0yeZwYXKY9a7lPrSQFthgGFQ + nlQrvl8EBD5/cnydSXhPL6G9PISm0eegoZFtLczpwI3p5yDsm/smZcfbxEtXd1Lp + 9LCiuwKBwQDgcxRrCfaEzEALY/Qaur88LebXpsnQR8pOHUh8tHupLyS4mMjuDOaL + ybDY3X32d8zm6vrx4XDw6NDGpEDIWBRebpLbFLUJI/+rVOq8QNiK23qtaWHTe4Qq + NwB5xGdzrOgLvR7jbG7k8/3hm9HmuTVlmlOF5V7M3YKOgwQnwFMzQLEXJ0nF4dHw + feLnC9MR0yjGxqg4hfoAhmXHqArWfhhIXYW6TDp/nLs/6Y4RcfSsdRu4rbw3+kg5 + WWJuF//YEHMCgcA1n7ldZta20YybaFL2yzq5sRk3cYPvGNAzoKkKka5lVut+m7Dx + 8zJVC0arcZT8WpuOq93/T87PEMnbyyYxl6vewxd/hsgXQJE+aUJafN/g8an4dxXF + 9FcOW3yhNY5+tUD1sb+KkdroV9LHt8VWjThZDplKm1n1eq9h2eyRw3bp6aObrljo + UqfJXzjmZ4MoVi3ZwrsITsgEJ2sODMsitjktXBS72p0ByNYyEi/y1Gz14nZwu0FH + GOPf0YpS8tjs1IMCgcEAu923HmzlaXwosyADpwfen3lwOdsCGxKr87ww+yh6RT8P + hm9j51Tm9+Rm4O5ry4u09yeBykm/pfXJIitDii2nRu9YfoWTjkYSbw8Rt2azmW5h + /ZoyZriBBHCrNkC9sqXsSD8RBpyxKXRMcbVh+LPXTObiuDUnp2RVLgHdzYP+X+zr + DY02A6qjdZ9495j/H3FWNSL8lOsXOa9Eq9wsziIXIyMclrXNVRkT5kgQQOvYNy2C + eY759wTHxTDa4Xm3DNufAoHBAOik7K6gngvzxCX0Ksu4nQc/pFLQEgam157sMv0h + cuNcvCZkA8ADo5DQEbPTCqz61pGGOzHYZruICc+2X+Y+Uw1Wf4o06G4lrDDkuZ9k + 17MXBJBgSTEUGmi3/P4txyMdPl5/cs3SWy9dVWp6jBscixb6bdpIeI9yYlxhs2mu + MVybhcfPGMfrMVOdaBEAMKixO/COOq9x2SCBOrtbVvcXlc7hQyVFzM2+P+LTwRhL + ohNsKDeoGvudaaBatZe6Q5+QJw== + -----END PRIVATE KEY----- + """, + encryptedPEM: [:], + encryptionPassword: "", + publicMarshaled: """ + CAASpgMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDOx6uxft/mhSF4tFo3gIY5YSElxYlcqpD5msdbyRKoJsy+iG0+Hs7r9OODmyaQQz84TYy+Q8Ft7TgACD3E/CtwKD/Jjw2QcZN7Tl8Cy7FrhralTk4/jTUMfqrDNks+EaWbQ2uf1wpoO6XqLAGLnhTv2RePkm94eBvY0ZZmpvT3ECF0VQWEioADNEW3DYk8gJ2vJK50Fh+qhOvnwQAhv5Q05GVP3G4zpDcuAfwvOR7FlXc72ZgA5iAQvikPnJgddAv4iSqmccwK6Nn6VczJ8Y+W+bdtJSZoRddnYN+CYdTPfJNMDtj+v5TuqLzMBoC1PBs1vHIM/jy+6tt/p0YTqiOSFhVW+TTSg4Iyls+mXSmj4jwqt7gc8CqonMBmDSLUOJONPLYneLt7grjkenxU5stRWQ1ypo4UOHe/WWJl0r2Mtbom8wP9/5mZ6zyY0XmlPM7lYREqqp228jQyX9UVkVXs0+30p9b7bVOGZH19etToDkmKKrilYG2R8Ew+UohMygECAwEAAQ== + """, + privateMarshaled: """ + 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 + """, + rawMessage: "LibP2P RSA Keys!", + encryptedMessage: [ + "algid:encrypt:RSA:raw": "wlT1qJE2EofoTNS/Tj/A98ehqJLwVVENKQ/ytQMV2mbzjkFs/kVBMtATmYuyFEYBTkZ59nlqRcrOcPew03A6dk07Sir2HdG65dsDmfOCuW1iZcnhBMNkMKIk38YwtRyPkrjthSh5II0UowEUE2D85L4+Ygf7bWk/7WJRxVhmFUd99inWMpodv05YvQJtyH7TzyjUEipjon37d5Aq2DfxUlRQJ65+ZHk1yakT+qdc+/TvjH7SCgcMHyB36gAOwyer/Odp5spofIl0//8iCnKK9zqqrskjfEEoiYkYpNrs1lrrzWE/kCXcxZ14nCEqBBYG4Y5P3U9NL85NwPaB93rxWyenJtJ0kDcBXX3Sa6BENLkwc33Ma2wMsKXxLWSHP9xsIOlHjeW/eR6IbFuhgDzojAqAwnKN5APZMieANToaYVzpgBs/QrUIVLMNi0Ua1xjpQX9xmxNIClzzHa8Uj22lDbs+hpSJIX+5EgNe+INlBiQRsQHJhFUuTkbZeVvfhQOx", + "algid:encrypt:RSA:PKCS1": "FQPy8WaNWSZYECiv4afrteLxH9s0p2MMSRxpYjEg2y6wGfwQLjhikZSnt+guHQmq1q3Enj0INVzAZ0kiEYywbycfYL8GAXZIsDE8bmQBqDX/Fcib17wBnLCm4GmFNaf7j6PS6itYrGfe6qulwxL7MzB8IhZdkPz9vvdBoC2oMo58wUHPlFljoADu+lIH92/0s2JPMEgNKu0qlVBmx2TnjuHaJBgXThCcrErDFpAxqBAGxKVhRMJuAO3WtHCQBLUWRSxzfYR6sygCcBrHhLV7foUiNa6aU3Uj0nr0vJXmk89JumeYmvkk/BT0uYFf/UdBtQZTt6fvFlcTX+AeU12W1Ez2PexfsiHLqfibP/nGE1UrleSpx9EarT73lahPhOHGCXosgxEjwT+SPLf8ZKDE8M+1GYOU/03A9btjoB8guBw+nfbdhcKJpHRA7Hgd/UzKdKRvaORtIaA80dseQK7cSsE3laaRncn0A+Z+UMpBaYqH2IBdENOx9xp8StFz77Iq" + ], + signedMessages: [ + "algid:sign:RSA:raw": "SIqqOlao51DQeCxN+Od+ENTmRFIIQLMs8WmQz8jJ4OScmDPSvV/EGGNk37JqQdR3ydN7Cp3Nb0YECWIkQPzaciuRR3jItfesQHx4pucv+4jd2ir2Et9NbzhTnpowHZ8Ui7Pb2Yea9RAITkf8+dTvmYWiJLvUuGHaXz1KkB1TMSbMcI2ElfsCYRN6ofs18WhcifSL3OI+4jTdK8V38JEYJZRlmuEQ9IXLE6yWJ3+0ZBDNZyQ+P3N7tNvV0AmlBqt0euD+EK36TvNpXVLS61nYMD44d2vc7OpTbkCjuzLVmMx9gF9cIhCYud2WtXU9z640T9YgD4zVGBAGMMsHOK6RF7yR1G0W8itmfJp0EdShVFh0p3Gfh2+FSdIFKV6xZdQ9Qlyd1NzFLNgK7299wBjPY8VU4++PhMbXipMYiA0xUkmmiXpbneLoPDfaIRpsKC72NGcTC5uo47nf6bJ3mH32IX7TuS31tPRjCxrC2c7EJEHUZdjSPGiHaFlDsnMSzgtj", + "algid:sign:RSA:digest-PKCS1v15": "ibLBssBCod+XN4yvqtopucRVd7j7WYBuAc6m6z61B0WclE1BPFAezPz+Mrr7QipKfSXYvH+kTjmyEJx+j+egdJ/w6WjIOibmLyBgQ1/936iR2O8OALREdXJLQ/RTVJ2ZEQOrbKaCKXRcSo34oHrv4IiagEAaDB243omlT4zlNSaaKSMhvb4RiujzUUxl//r/u4OyuDFSxhM5UchizbsdH3w72pjOJInGFuAfrIRXF0yYKgIFpsGXh77go7K6imU0I6ZwFSUeVDJYBos5+3eWAq2kboAYoyQscpyyfyfxzwXooO70y3OqjHDt64IMjYpqQpGFXBpbp97xNSiYO+44aYktbvhT+LqL+g7EcG4qgF+0D4eWbJAcpKLq2LCSAwnm9bieMDuRfcdoZa/REi9XLAfcXbjQ5q4UzOr0KUejU6U4Mkv/9tdg/LUufzNtcukh/+ADv3p0+42iatrXMuEj7yPpdf+wKNDusscEkGL0kOldNYyODgI/GEXwPWLi/rAJ", + "algid:sign:RSA:digest-PKCS1v15:SHA1": "ww9LTKFkuIjZWLeEKEx6Q2cQ3L5em3vmjj54yvIi+P4OXb4MZdq+Xdvyr6lm5M0WQ4Q36T/Sl4qtvMEItIIplNmQ4ah8/H45+e2b6RYcDlR8pWIMXo9AnzGsM6WIFDsS5xcqLNEB+u1OC1Ukb0BanEdmG3FgzIZrCB3oaHIyzmKeRkimcSqjevLfJsxeJ6mpxsYlYKynEhCmP0GIlp07+oaPEVDZCCcIbxLS6/gNgywiWsSxS9DaJhQRU0EqNMBpQWK0vjxwd9guFdcXAw23LQbA2KxLbqeaEY0j47eOQ728f1Qi5Me5Drdh5eMxDFc6tw6zBCqSGPVG84JM6yeckOXXQrYTyTRRJZmo6msQCi9SQBgxKDEyVD5XqPuI/mSuiwcAHHGdD+OBTNpCKAahnq6CzAywbR5jWNTDKDBeJtCPLRaQUg/c0JhvWZnKbZhBF/fI7/UQjkcRZsAFP/D03JZ6JhynVIWYIHC0OpohdyeknyjDe3CyZBZQpIxo9BRw", + "algid:sign:RSA:digest-PKCS1v15:SHA224": "P22CtZun4BVEbTngy469qiw19SEYSnnhMcEq/msHZioEGXkqo4n1/0pUT0YbPQ+VSZg/KN8Xuqi/HL09BRqbU07k7Sx31pr/Lh5TdII87DGg6MV59u0UpzzA9zINyXgbNCc29d78RkZACLrULZXVeg/8+iDmfG+QuEvITBjCjJ2Uc8gjRUIJHpKulLN06oKNINjbJ7EZB6mTdXpWS4MK+VH2quTRhg+VBK9I5UbCwXM32lSaUbDr9iylUHEJTjbsfH+W9eus3ywTo+/I5Ve2hgSZLg4SkzSL9JxGgyUECuxEoVU1uImXjgJDZ0VKO9iZEJ4qhwkPpQP60ew5bXQAPlu1PsVCs1R+1mlPZGXr+YO7WHfs2ZgJ1pU/Z/mY8tAStcIaPCWiwiU/tjul0eTX5JVzbRN8vRzAGAtf8LONEjbGV5OfQPsfCwHoJFkSBoZobHasTiHUpNW3Qnmz4XhOaImxte+oPsL4GOq4ORvRvn5Pe9mg92pZ4uWR/2Ls8kzf", + "algid:sign:RSA:digest-PKCS1v15:SHA256": "LO/q9HjRLDD/t94BSm491M3zC2oRai1MT97CtPZenJ2dI+dRTmoebv7C8LWoKQDVXnmZX3xjhCQHhW8nmYabfxYD1SZuFDrZE5hvQ5X993w8DbN8kXrr7vA/+19zZMAxcKdx33wtokCH+Q3KWVgr09X9BjAIWtNx1w4guMFKOwHurvBVS2KItjgZ7JkdZ5jX5raQVxj/K3c3YifMhlbw0M9CORr93KAYfmYohY3XO70NVSpUPtwJ5UOGj9aUrtf76UCGaIzqznn9R+mPnQLzvh2NXFbJPWI7Ry7QVWaP114oAXSq7LiWGRRtb5nkWe3fX5afJ3uF5iz2FpvGLmGbCHyXuY4l1SflxgEz8CE38c69lxJpaVi46wlENoCvy1hctNVkJj24zzRjvMP/k+v9KN7fKoL8lG86yCJQPQdkMcj5n3z6/ib5W01+5/DmQVP/SdVhndqiem2NX6hq22y/TGPUEsU2a/oRI8/iJVv0IMy1IkWWCiXjwqgOvuUF0ErF", + "algid:sign:RSA:digest-PKCS1v15:SHA384": "o6QwBqhODSTZ/S6QjzWzENb9oGmU2C9MFAJ1BVnU0rstl301EjuRbiKqfQKGdUiHDICrcQF1Yo8hntlIPnRlUSRWTjGfSLSSqox8D/PGzNyoF4wMX0fvrgMfjfDgDddMwTaD5cx5WVTA/c/aKvo+hFhMAGt2EsUiEq1QUgOHoxB4jIz/PjuP1b4BTRnMLe7QHNXDqM2x1g+Nqpqkk6GhE1kLDrZofBoP5Kc37oK0BGlZgnb+5mxQ8+fOZxacZiAdpkcpvhhWfk79OtlQfi5ayeE53cnUBYRKysIc/pqhNdAwxXbLUKGcOieJtkYZ/Ei5LY7lBh//11MX6oxSNV0mdmy+zmFY0QK504Xph1ffvJ9mtYUSscfd/cILbYPxTzguHkxn2DxNauALEvjg2LO3JJ7bP9R8mc0m4bzgHk24iuGIKnVQW3riHeuyhFb6BKsXKKDPW4xgMhb2rANkwIcC78n6yzwVgzUfPCdiXPnpCMwGcRgyQZ+7EXsNM6faofX/", + "algid:sign:RSA:digest-PKCS1v15:SHA512": "Z0Hi9LVrlBt17g9QFp+kAFBVAmshIz28RQl0HNugrIyAN0uWs9JYmchR6IyZ45J1zE2jI3Utb5wAJoqha1Ej6oVN0GTTuA1qdmdpHNnbQbCrQryMPskSAeX11usd/1aPsWNtQOj052HylkStkV9Wsp3pRMpdKjtSj9Mgjl9JAPlWQlbxX6a5+ScjVfQCMpt9o931ptg3zD0jPLeAsRsrLVJydofjwUMEkS2djRdzIMQKGgvt2xvyYDxlV27WrdAlqpOC2WGYY2tkSAP7FAxHS8OluvZ6fA3+rZmag6EajxRLU9jmgwE83YfndS77LgjuVrNM3J4Gwx4bjqLVl2ploEYL2wLjm8I120wRKwRxXI6E1UfiwCjffLetg67GIBc9kmPtZPAcWGIxaW8FKvX4Ir4sNYkuMGyfMXyzChUtmpJGmwY9dstEw34p7jPIdt+Q0NNbSbYo36lwLSTAW1XKUE41ix7ufC8S095d2XKgPsc2f77NUXcMgXQGW3ly8H5g", + "algid:sign:RSA:message-PKCS1v15:SHA1": "Tn/qW8AnjQJGGGHZRWCL6BrEzl+eG03E7qKh3lsngDPmqCMJBtcKk4rjTAWBNok+ldr1WNwJSYZvFk8udQ9LPxd96nkWJYio9+MG8y+ZY5nkrjDacWRELlnchHv2Cwr3gK7qFGT3EqVEuOAarVGxIBO/oOZmajawk3mBX3v1nEKPc94BT+qIsFTOJ/T+ATnksJVQOC/uw0ON+CsmDbhrpMrwccExUKVqUZ4EUusHwDyQAyU9tIIBNSGVyljGHhzpcWWQxClixvoMDD6SL6XrhdxoyzjONkqop+/Vw7jUogsLd00FjyhgcnahcXwFVQ71nkD22aSrPdXAp1LZCsQE3tUZcqc3eU2R3TM8JSsrovqwSYK33B/5dav84qBBCVqPEjjPcCTLgP/W5jzMX+3CTjYnRswMGhmHCXMNsEt5Sk23g6AlXUma+uGs3BlE4ChsZ9dGgoHQB4LroUcjUpraAXoG2M/jbX3si4l2MJqoVBulkGVC6TrcCCLfQMqI7wDR", + "algid:sign:RSA:message-PKCS1v15:SHA224": "WT1S9NHq6TAPaSCn8d+h0PjS1aOHC39h/8iZBBWVK8nCYOWkjmBBwt3yFrZTLbJdoNZsf+86bbGBFPvMta+rZ7dkDfWHVA4Ufh9NfG4k/Urw0cx0yEw7uTgieZZOGVVMt5eQmXEZMjlWtxsbThJIugRSCVbKILUkv9Ja+sLLg4vncLgQQqpJ/aDXG3gCiw69BQtHzbYKqaN3AAVD1/jfQjFYD5o/zajFbh9WWZ7ULkne+xK/8fh0k106WdQM5Gh5qazBxI9d3knMqGZuXiTZczZrfRwzKhkZgb9BqTvGQWtvt8ZbMw8trqKcTCovZFR/EAT21++kdJBoDZCIb1o8wCEPFddVjhx/B+HhmYG0UkQWJvNSvJsOAeGO7/PKg0J8X5vt26UUs9nLWEZ8x7gHwT21TSFeKBeNJ+p+Q6bitEPYMTADdpMXQfFdJL4HdTaIOYJ5qWaPcuMAID80uBrWrQXsi225Uk0yBas2qDOY23TWxI5v7D9JXjqA5KqfeKde", + "algid:sign:RSA:message-PKCS1v15:SHA256": "r47sYLdGPYjUcxDR9NvfVyx14oihkCzZ3HmrvRInvKZb88DEe+tbcCDhm1v8SvZkjyRIabDzK2UbQzfmnDWGFHlrfWpjQ7uNZY5vYQkiEhc/qun0a1/eAqN23FgDMgBXxUZMZTkN6mKmdmvp19p6mGamngTAFfni6MBO9VshOKE1oRk34V7T4NhdmmqjyGTcrHJKpP5JvVTfKnQidoojqzwD1tKQGwl056BQ93qzXjrqfxvlVRogN5AsT8zV+b0cPDl4QJKr8Oj66ZYdjVOG5lk2b4UzOXo+Oy4dbIcitd3s2yIzkVasNA2L88VE+P7W/lBXEWOTHqYku4SmdD8YFEHp5F1iqjKi/cZY2BmNevtcdrNszu03BA0MnxksIysFV71VNossaVXfT/i+ckzNwGLf+WIwzg15yCOIJt12fDuTNZzFRfT0tDURVDNuDov8aRq0PA7wQLs00plOgAUBZ3XZS0gZOS8hB4UcacNQewYqbqPrkaiVlTE89JE+sgki", + "algid:sign:RSA:message-PKCS1v15:SHA384": "kxkBL/754HXu4A7GsVcs23uk/qj4sR2Y8vVLKJD6Aa7SE1gCbTeQN1MBcYF0nmNq1IJm+8hsRBKpXsR6q8dU6rPXI2RiqkGyS+qEy5f87T4MMqz7SdVqWxbL0n/rzRiGwGEhAyxYqlheDIVzMEhLlc2vHP4C/fyWWdnlVjOeboBiL3iChXiIHt8y1IsOHOs3pHKFeHS+DCD6zK48m1NolcQaJLcvwEme6JJx3eZNSnZR/bOaZTygpJn48PMC35EbG5eOBc2a9eDq2uX8mFN1e0tS+EbdQX4wYXHfJNaB/kiBA8Z5vTDnPoslKBIkLwlcvXpz7i0O0KyTbUloZGffRZqxQTKb+eJuRKzovj9FgoIuxnjnE7+HcFhNOKFrBi/mqY2oC2Hx1aPMISIJ+Ai038dkOS6otlfnkP6m043cRofNlorM0d9CSuUYMLQwjonAHIW3a10K9RvHdZcAbPHXIeUuWhmTLk6f0I2MoOVrcY+ZDhhsKgVVEHUlibfQ7MZ/", + "algid:sign:RSA:message-PKCS1v15:SHA512": "N0MY0xXY9sj4gpvniGaD/mErLhtYMzUcseBVBzMwSz1FUFymZp0z2JmmjV/gVGewKY8DzGgkVXS0vpj7tJL3F+x2KOlz0hUhAn7K/G/SLwd3f9acMIq7NdxeEh8y+nlB/BwzRea2hnb4sPq0/TqlcNrsipgliBD14l7vgdqTgmiM4WRhA2h93CARR6mcoRiU1xSkjGEVKaHdeKRFCe43kRY/dmpVBW0Vva7MS+lPT3RGHY8rAzlbSCq/fK037ljMsByqv2fVwoIOLC9G/EN8liX0P5Xo1BjuqP6PSUZOw+8d4TbD1KtesnNlQH94usoQI/XCNtQSzOiQDvygA1fEGjnqj/+z4PGyjcot6r1VeO5noAsZI/JmhdlK418q6FsAH78oeGlMYLguhGErwu4pRUJKUb5tolQCklMC3wofSncvxiyAje4RqWJltbY2pddko9qNs7Efx/oNGZfBZ8S9b1vzYm6wcd1OMUQzymqf9JU9orNL8GyASk28/46vWOx1" + ] + ) + static let RSA_4096 = Fixture( + keySize: 4096, + publicDER: """ + MIICCgKCAgEA7UWD2/7gw3e328OzBLjcfz4LEgg57GMYX8kliiVv1MvIh265F+wGbBmlCKM25BFA6Fkjk6XGTIFIk1CyLAluVNCDbcDuKiwgAFRc+DmjBBXKAKUtBWY4LtMtEoqJnayIfukho0wrIIgspN8fzmJIVPmU2e5aptDa/6y/1PUQSGnqm+PGX9/QlQ+0fop9YonlpKgxLYkoszEr96nvEOuM4otAwGCiCgdygQTzrU1xXyK2S7iqh2Vi2B2Cjmp2nGmTAMUnrML0A3Ld6Azc5qVMF59n4icyzc1RmLl9imjUfhWzfg9lOaq883WVdlRK6rg1nxuhqV06sS+W1QpH288rvr3JQlM43LOArQHtLjABJVyXhkXVeZzAmL6hYw44AC71NFLzbGltHkaedQMXMBO81HPJouVGyQlekYevG+efjrQ1oUA7BlZywoqF61IpULpLbLUmgbf+P36TxyFvXhJS1tbWVQ7D+a4cuZ6M+p3hlYqostUbcytTUdTr3G3uBedq+SnSiObx5uQUS9B2fYU/k3C6UC/yhasXAR5hvFojed8G2g86njzq14VIKofMOsMG0QkpBrO9bdlMfT43ZjYEs2SuUVnem/8UMv9Q5TLtjwLDmgXXtjocascuNpIWTsUfaiKRiwey3o1iK9axqvvOFvYQuXFXj8GH6aKdKWejpd8CAwEAAQ== + """, + privateDER: """ + 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 + """, + publicPEM: """ + -----BEGIN PUBLIC KEY----- + MIICOjANBgkqhkiG9w0BAQEFAAOCAicAMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A + MIICCgKCAgEA7UWD2/7gw3e328OzBLjcfz4LEgg57GMYX8kliiVv1MvIh265F+wG + bBmlCKM25BFA6Fkjk6XGTIFIk1CyLAluVNCDbcDuKiwgAFRc+DmjBBXKAKUtBWY4 + LtMtEoqJnayIfukho0wrIIgspN8fzmJIVPmU2e5aptDa/6y/1PUQSGnqm+PGX9/Q + lQ+0fop9YonlpKgxLYkoszEr96nvEOuM4otAwGCiCgdygQTzrU1xXyK2S7iqh2Vi + 2B2Cjmp2nGmTAMUnrML0A3Ld6Azc5qVMF59n4icyzc1RmLl9imjUfhWzfg9lOaq8 + 83WVdlRK6rg1nxuhqV06sS+W1QpH288rvr3JQlM43LOArQHtLjABJVyXhkXVeZzA + mL6hYw44AC71NFLzbGltHkaedQMXMBO81HPJouVGyQlekYevG+efjrQ1oUA7BlZy + woqF61IpULpLbLUmgbf+P36TxyFvXhJS1tbWVQ7D+a4cuZ6M+p3hlYqostUbcytT + UdTr3G3uBedq+SnSiObx5uQUS9B2fYU/k3C6UC/yhasXAR5hvFojed8G2g86njzq + 14VIKofMOsMG0QkpBrO9bdlMfT43ZjYEs2SuUVnem/8UMv9Q5TLtjwLDmgXXtjoc + ascuNpIWTsUfaiKRiwey3o1iK9axqvvOFvYQuXFXj8GH6aKdKWejpd8CAwEAAQ== + -----END PUBLIC KEY----- + """, + privatePEM: """ + -----BEGIN PRIVATE KEY----- + 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 + YMm4CrozLBthDCm6ApV8muQxGMZOVQ== + -----END PRIVATE KEY----- + """, + encryptedPEM: [:], + encryptionPassword: "", + publicMarshaled: """ + CAASpgQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDtRYPb/uDDd7fbw7MEuNx/PgsSCDnsYxhfySWKJW/Uy8iHbrkX7AZsGaUIozbkEUDoWSOTpcZMgUiTULIsCW5U0INtwO4qLCAAVFz4OaMEFcoApS0FZjgu0y0SiomdrIh+6SGjTCsgiCyk3x/OYkhU+ZTZ7lqm0Nr/rL/U9RBIaeqb48Zf39CVD7R+in1iieWkqDEtiSizMSv3qe8Q64zii0DAYKIKB3KBBPOtTXFfIrZLuKqHZWLYHYKOanacaZMAxSeswvQDct3oDNzmpUwXn2fiJzLNzVGYuX2KaNR+FbN+D2U5qrzzdZV2VErquDWfG6GpXTqxL5bVCkfbzyu+vclCUzjcs4CtAe0uMAElXJeGRdV5nMCYvqFjDjgALvU0UvNsaW0eRp51AxcwE7zUc8mi5UbJCV6Rh68b55+OtDWhQDsGVnLCioXrUilQuktstSaBt/4/fpPHIW9eElLW1tZVDsP5rhy5noz6neGViqiy1RtzK1NR1Ovcbe4F52r5KdKI5vHm5BRL0HZ9hT+TcLpQL/KFqxcBHmG8WiN53wbaDzqePOrXhUgqh8w6wwbRCSkGs71t2Ux9PjdmNgSzZK5RWd6b/xQy/1DlMu2PAsOaBde2Ohxqxy42khZOxR9qIpGLB7LejWIr1rGq+84W9hC5cVePwYfpop0pZ6Ol3wIDAQAB + """, + privateMarshaled: """ + CAASrBIwggkoAgEAAoICAQDtRYPb/uDDd7fbw7MEuNx/PgsSCDnsYxhfySWKJW/Uy8iHbrkX7AZsGaUIozbkEUDoWSOTpcZMgUiTULIsCW5U0INtwO4qLCAAVFz4OaMEFcoApS0FZjgu0y0SiomdrIh+6SGjTCsgiCyk3x/OYkhU+ZTZ7lqm0Nr/rL/U9RBIaeqb48Zf39CVD7R+in1iieWkqDEtiSizMSv3qe8Q64zii0DAYKIKB3KBBPOtTXFfIrZLuKqHZWLYHYKOanacaZMAxSeswvQDct3oDNzmpUwXn2fiJzLNzVGYuX2KaNR+FbN+D2U5qrzzdZV2VErquDWfG6GpXTqxL5bVCkfbzyu+vclCUzjcs4CtAe0uMAElXJeGRdV5nMCYvqFjDjgALvU0UvNsaW0eRp51AxcwE7zUc8mi5UbJCV6Rh68b55+OtDWhQDsGVnLCioXrUilQuktstSaBt/4/fpPHIW9eElLW1tZVDsP5rhy5noz6neGViqiy1RtzK1NR1Ovcbe4F52r5KdKI5vHm5BRL0HZ9hT+TcLpQL/KFqxcBHmG8WiN53wbaDzqePOrXhUgqh8w6wwbRCSkGs71t2Ux9PjdmNgSzZK5RWd6b/xQy/1DlMu2PAsOaBde2Ohxqxy42khZOxR9qIpGLB7LejWIr1rGq+84W9hC5cVePwYfpop0pZ6Ol3wIDAQABAoICAHmAV32Yj1CJRcVr+Yg5gfBGvTSGseKy/BBQ2MBdW18E27M+p9nwK3m/yBscOUeg7jQNMYKyKVGUAq4tbI/QZIdaH3NckJlX7ys8CD+0hB3bao3aB6v0JGEWEsaj2sS7TUpv56HPp2YJVZM4DvmIqrGY/Ph8bj9uRDcZJ0LOG4B5fIJHqZX+7rjmSe9iy8JiCw6+cjB3RDg+vd0dUCbB+GsqFAZvdYrdNW2MM5xTQ/MLu34bGngrGurAcdKsAgEmrvh9/UsiqgH6GFhz/cq+arDOGExe9k0dDuO+WOM/u1C1nCy5hYR6QjgJxnfJwnNf0VWguLAZ5axGSl7d3N5awHL3+w4/wTiaJvorKlmnzgtVpAZNHRtOJ5LY5ZpsPmlAISF9JA9ewRmuXmz5ICG1BbkvzQJSfCu2CF34/W6n4MRpjCFxnTE9fDTUwrKBQEWcltAyllz3e3ggeSGAImE+d46QUOfOpHwlnWbtru9w6Bzcp87tbDc/d3c+jVFgemMzy0FrGClHv1TgQR/rAPva/SrQZsdj3c3WiPgdoyTk3imXH22Tg42JugKJ6SnrGS0UeP1E13Hmf5zIX/9+BPjxtCdWBsp6v9ASHO2/Bt0iXc7X6GqKQm4pzFpddK/VaB2DXOe/4qA39M7W4yxnC/sM/GrhzEwMLO8EfmdlnNlHEpjBAoIBAQD/PBn9LPuQH5BjfXhIXjkhw5YbiFCV4pTrEEZ9iMAfkj4oC+HN58LR/scJr6KXcNq6znAcpSSSnb45Qd/aQ1vZfP1829qXLLFisF29zzqz2EkBQXDZlvTqOJOWmpg3qEd1iCppecIItptdGPMibECvJ5vPHA+/wSunqG9vdCAYYCyRGvJfl3K5+XIkdqfPrwEuhgo6558jR41SHlTxj3Mol/u361cF4mxQ5MwA7RWEy9pK7oZPEwiK3ahe7b6Qbe0DduVDWcSx201B5/bZdk59PWwJOGM8ekWgy62LhDdZiJh2ydnGu4Rig3bG/7OFOY93VtGEI+05iC9sEEmga6MzAoIBAQDt+6BZ1YDgQUjoHsEtksnImArisWvTCPgLhfrN4uWClWq9E/6ImX3/8W15ZWUrcWXBejx9lQYl7Ms9nmxnjz5udW2ms015PIeJarQtoCbFHoWyvwz6xtwIei/fBB4mouTBTUARoztKpJK6mq4U8I7bGjxFEtHY8AP09qgk5KnVxEaJgoYdGqW4TvbfEgdqEafw6B2ZdyVaarXWbfrn6gWyJ0+z/4Kskg8G5XIFuBaN0lqJGc09Sv0TYqCmYNovhxVj+haUhKLtb5OxK3J1k2+2Gw/hjOQytLnaqPqTyHFImnTv/OfKWzLHM2KJ3UdGzTj4LBmoioKasTgy+nvKDrKlAoIBAHrESu/nSh1ZwVbJjCirAaoG7NXTbIeb0+t5CZ7qPxcwfas/g95JowDlHpG/JGLZiqweDfkJSh0nuM7Lq8PNcImdQERlb7AVdEE2+fAJlH/bAcqT0pcmsClVC1B+jcb/r1eOJnBTe4w7YWGXVaNyFG7ri1tEcSLX5IqhTvB6F9IBuI4XOwvVyf5ASLHXTQJteBELsjIBJR+QFB6yrkL+r/KPoCkoDtSqw1TpqGKtpxMYBqjSMFIqoopA8fKvQmEwLrfi4YXS0PSHupJ/49FXwYsmFygcMkdtvL4KGjCoaQMs28EPeCvKGHRWLbSsW4oyVJqfbOm1ai6pHb5J21BJVdcCggEBAKRTfIyrXTr8OWB2ZAOY+RY2zkVsJzzoU4Z5dA4YX0wE6lg25ynhetxxE5ughPQzcATh58T7Uux7Dne03bvAcGaLVTSnWJafUiNz94gNrter1LvxKgA7shF0dXuz9zHMhtJnChdjs/w9mUeaqRuyY6ItAnLbpOT8Dm9mpgIblp4R0nb/FqYgEVArzlGHvHDkUieJz255k+RUDM1M0dSzhk1W0bVDeDK7A/9KDaqfS4uQNY23sD1je06Yar+X3vC0gPOw60iQMYRr5/qNOpKvRJLlKtkelS/iFPdEeQn0fSyZ4qcX5eGXiX99Hsx9a8sax/ytTuZPBhHUzy58QV/rODUCggEAC4fMPd5xVQq5LVlMGuHybjYdFWGhBpWWhUF8227yQnGuU6H+2b6wsI+BCdzI0tNw1SnrnN5liT6DQB02jv+YvqEN9yxxpyDEJ96soWUrYfTP5nPCl+5fpvlRdggs0oGeIl2nw9vMq7R0Wlw4jZ9OV0jkRItAmm0++LoUH+ZVcmKmzit5xHkF1ZKG1bjZXRpEIyLIuf0Zr7HW4zelmKCXcZqAgtvMOrFReVnaxFPoDUyvN78mKUGS515wRg30Dc+ToAstFb4NC5WnP/1iEr0DQha0q9viW3EoRBzM7yfwd1kzahWRKo7nDceEYMm4CrozLBthDCm6ApV8muQxGMZOVQ== + """, + rawMessage: "LibP2P RSA Keys!", + encryptedMessage: [ + "algid:encrypt:RSA:raw": "FSE6NautB+EYA28dmfFGJfq7LMeZ+8rMR401Ai2J+s3ruk8xEzETtioQRkAUN2rxJJ9I37uLG62sH1GRYqWVWeyC9Aqdd9XOEj0bmerHsHHiFaEuKneM1rpkYkujyQHJNl/DbAp4LRvgVrVbwOtc/9DBO5PIyyShfv8UeVeA/y37KnSk11mWNKWGn/qBir91eQNEhJSqvnyvdvs0WdDuQsMNHyYvcxvmA4BTmPSAIN7839yuOX27npCmajNbjTCxvf8s5ZIyGRBo5UYbbGM4BfYcR2ogmhIQ0YyikrlDiMj9Q8fw8ih6bg7iLnWbl+N8qM7Oz84Z1K41LWUx0oQ4TLkvkXPdCL0TiR7jkCU3s2OT2jPkfMq07s8A3NosDy5HTJC01JflfsMr0K1UrSeMDWmWLbRm4dfh2gGx7nicQ8/mlUoL5p+wWRc63u2ZfTB+aR+ueic5ie1bL+eBIsYcbDOmoMNuW3mMEK6efpNxraN3KuTLIcM8jsYACqm4Ry/podKLY9p6s7iwoko3duL4DakUCMnlP83AOdixDYFc3LO+s9Hy6TkPN+feK+lkTgP1QyG9frlH8Ox6NU8iaHF+Xe2I5Z0RCWxQgFlYiwBYS31mM8yqo3+mONDSZtFBUoPiShGT2wZcBEXDlvHOYQ+Lx/3xqSov+DBs4/a2Wujaetw=", + "algid:encrypt:RSA:PKCS1": "DyDJazmOoNG97+Fe7sScsaHjI9K0EzmikpTqhYIGPAM+RdqAnC2lFqesLj0VLZwd4ayFok9bmMabjhTsh3HdQxoJ12VZN8NZtmF+6D7zPQaZrqMqZD8iuRupVPIT3QGWKXXOBfrvdWavCqEmS7dEdlg+A7PS+jEauuLN/AtvvrJIDzRUWp37A4lGfTjg2qPFACL8gJ+C9Nma3aOeqv/Qi+t9YGc5CRuimFqy2d2T5ingWIV4eVrmZKGSmHaDZDmb3U1CwFVQNvG/cv2DPM/WflMqz9vDxTUWTofCKPas4lYyXgIeR8Xgluhq+xFHNNb8za4ILXSJ9WZEXJTJMo1G0/ihxYbjGk9yLJA0JORw15V7fVKTDk9qbtcn8aT+JTKPX0tBBaWfqgB3GO5a3oqGs/UTLDK60Ev7UAycHlgBxxAJ512HwuFXgmcQnCLBW/EWs6w2ugHRasn6WBjI7utbPk1E0nBfudZpUnmRyAGtGIT6vOEyDnfzUEBeqgGbZ2WY38z6NqQKVz9IKXIvR7twPQL4IDg/IeY6ZkxB5x1gnMNf1ppO9LonamucH3hy57rHUZ4GDpenY1sTZEw4tCWXraOanhYVtPXEjblATV5/IHv53qNz38gmOQ9Di1Ib8hbxonSQnS2IMpmiUkgvaUhTn46CJynlk47T5UttVKupdlU=" + ], + signedMessages: [ + "algid:sign:RSA:raw": "SGskSQzPDN9KQMTOCXBMvh17y31w7KnXiVoOFyx9+HREa4zieP1N0Rwrygp2dnBtLl+1UqSd7wfthx9xy4JVVOiWUslxaYfF0MDP+OMsh34CIrt7MUmBL2waXk/5Ecuz6ijojjy86IO8TPfsiWgBeLa4GHrwtI73EP4NW8Di6lpoLcD9rzFklldu7kEvszOqfAvgLEwMGPxMLjQ+pzLePNmdO0uoOqdEfnkVA3dcFOW62LwHTGn6JaQsiL/98afjIscVViWDD2Lr6ydfZfF5Rvf7YXFlwPMqitmSZuNI0pXcJxkWBMzZ4/4a3uHw2WbVK1b5ZCrGgCdc1CN6XPG33aL4PN6G4+UMwJGmQ9JkyB3Ecma18BTERJCgJY1jng277alqyNKpuZIT6DrwdMleJQt3VvVgSM1sJZcOvEv20R9rze0jphk7zWhPO70B85Lr7GQns/8qL+a8iFAbV9Nra8mA+T7vsNr0t0UXOSjnJPMjoR7f24Bt1FzH+X37q6HigirUrjvCWK2tNcRqF5Le1TL+mHinVzdUouJ/X/6u8oaqgfwuDi9aGncwSxkSkyycPQ14x1T1imEbRQO+iCr0GHyyPgVEDNk5ycy77wPegh4R3EY09nBTa+G9AhYZHq5dWylcfObwsXZMss6i6GN3FA14zgA2KixXEzRG8ZEi6ic=", + "algid:sign:RSA:digest-PKCS1v15": "DyKll2Qr2pe+XOII07+RBij4Gf5MXWSbVKjoH/C9IiGLySUS4V3kJNBukEtnVKsAj7DiA/oPgZmMKmy2xakMQF7OcSQtq8sUwxUQDjI1J7WU81DrRUlT5rW/3Vpf/fwS2zGmzV7gN2S5f55VT4KpvcfFJZwVKzD/cMXTKkLOiBo/kvIg9+KG/4XWWHjzHyJlMxEa3I7g0epz50VuBlTLKhRnmbVzdae4kftbqM82luoyV2ES7d97I3oAtZjhcelCiTzLxEFfkbAh9mPJB9lDB1yRAdEwKmSM6/pG1wD2jjdxgkxulnQmUaYdEs9WQ113nTqF48YRiLKr3jTFB8CvCEnDtrXFK6pLSZZMJGne3l2ZIef7YDeDC5102k8Vy9/zjJ9oLhEbhUVurUA7QG9rx/rarMYTVmEOOzd7pG36FSs8PLcV/1rpnd3IbYFHfIc8bMGoSwGnE0hvHDaF3v5FnRESIK02yCIXppRKnKVMcSTcahCn021QWTW9j9u0hvAj6+G0uGMY0C/RxNV/SZ1tOfuaja54xoY0O3YhRNwq11PNw3eWPkGxQdOBZym+qmBE5HzyhscIXM3dVTBQny9CcOtD3AZUmcjNKGXtmxl+IfMRJOAtvN+GONa9O9pAElBY8wIBP9+RRB0DCBKPY111zwDGPDFJZasqNrNslphUhCI=", + "algid:sign:RSA:digest-PKCS1v15:SHA1": "ecoYyYxSvdfmSEJ6UiPI6oZjZ/SINTd2irgCsX8thIXXbFjLkdRm2owfBdke3l1LnSiqB/8WT9B5HeDdSdYXwNNvEucIlWi4vlKb7hv+hcV67jXvjAWxnCetui1xYZYHniwX/2medzlP0aHHXngQEWoPdLfiijJbNnAAWurAJUsl4aik6MAkQoqiY/myiihtLPuTLcU6Is1f7fdpcvRdrnamCklWi61S2/E2RIRJ/fIKKflNLR5plhE369/9F9cxCi8egomSC2qw0Uz02AexmQ01Uj6XRYdBCNyR3mtgozzZKIQPjm5U8pKqmOR5rvNU2idKnalbm7rwRYFvjuQ+4SEWI7f3YlLPeR8gMKF5sousID0qEwV6c7Hl3K5Tb8zrv6fYUiPZLVoQNvjg3KJynjAB7ciYuEGU7BZj74KUkMVxi6JLyyxGEHxXEoN2uWFwGnaBa+xjknL0TJ1P+4G6kQQLk5HsQe/cEa5ZkFJYQdQA8Sh/tB3BFrjKCO7OYolTwAAS9krsi6wSSGALDEH8dHLb9D6mZyNBARXw/yCoyfdKgtis46NkQS+pHOcy59N9l9WDmaP8ZRDBo+RjyIbgKaxqnZvKSihmMLOdUlbg7ZYWAHJPDCLpyYZwljwuJluP0NgxDGEBUzAASo+RKUCVh040mwRqpeNJ7vKfs0HKkig=", + "algid:sign:RSA:digest-PKCS1v15:SHA224": "TuBls8VjkpTCa8oJHW8/K/X+Niqn7nNx4Xk5TK4P2KxrZLGEwEaVvPkk4XF4JAkbVBa/g6123IIFHOXpe+Ut2S9KLrmTx3uUULM9yXVCHhHqTsqC+5doG0zr/fQmvxKHQbAxhzvmBTzdGqIQIkMuiilHFLZdm2zYiLVSwEC0EHq/RtvplGLMiiViS8jrfJlhblW8r+fnzti/jzD22oh7u6QO2uzI6bWlV5j3W5vKETPJnSq0TRDdrRVUAOag0S209dNHlZd+TIYrLAWRqnAzvTVRcp4v+UpOp0yPTxn6YSOgbOX1wL/xmwVsQc3RweLL+mr91F35iWhR+JMQr++vR1xLLzvoU/jFYfQ3OlcZZ6yt5hLorzEKTDryqiLcBqYe0atFGPROQqhD3X5vMoOvOLSa3wRKwABKgqUzpfWVqswgo4T5GpYJkU6yd74quzhXB2W5aFRSH3HzahO+RCND3nH3BZ8whqq/QTJ/yxgLWr4eUtzssyxa/5W5ojylByupkjq1qrJaP2TtxNrzffvt9kcqFexQWRHXRVwDWMeuKqITbWetZsoXLa/CeXPpPGMFCKzJmDrswKBKN6URRV9c4g5ioor1upikfjSJTBXWggrH3N3Yd403NNArRZFkB28pHYsaM5Yrnq3jj5VULCLjIbv89zfOtmY4+fAB6VCNdmg=", + "algid:sign:RSA:digest-PKCS1v15:SHA256": "Y//zjQxsPKxGWeQwIrxrb6dLtAPLQ/u6GhHB/+FLO7XA3Dz37w6k9jbBMHPoKwL0sSZjG2c7tSqWn03chavfDpIz4nxiYBn8zp0C/TYQ0etHMUIrRyteMDuQaOkRCulcYmZ/SDtMGIpB7+qrUqI3LBfHVWA9X45fblk8/JOHNtlwIFOQjCbLhiDgBBbPvJ9uf4Tbq5EIlyf1z6Sr+atNYOUR1Uh/Tf9eS2eTPa13sRgA1I88U2eOYv1EE2sHAKW4sqyogXsWQLielKbKGg9yWt7bfXPt8lXKL6UkCNt8LDb8SUISw/pEW37lpGH7VOMe2NZYiUMKTmqfwuu+7XtKCXVSyolQ+C5qytdOJ7xQOaffeB6tz8u94hE8ltKt1XkuNsS2g1KRJj2Anjd5qAk4hdHMiGpKUCRxEVe0W9D/iSMSYLIlFJSzjPBuDRV2kz80rKO3t00IJAQz18418j/xxClgWcZHHBMDWy9fYWLRYN10RPFhUgwH4r7vp5yjzRLqBy0OD2iXMy40SJkojmmgrTawYa5CGjaIgsP0MbcVrg3RAtla9uh7HLaL/2fP2OSmk8K9x71e8JuWohcRrD5HJMG88t1PBCeHhx1PytZPzx6tI4X3Cs4Q5yTAQLYry53nlf6ZJDc1VE1XJy9ZZEkJ5wwNYK/1OH6TTS0yOA4Yy6Q=", + "algid:sign:RSA:digest-PKCS1v15:SHA384": "rdQyl3D7gaBdXMvTZ4zYzcALr0GL5FWXeDljtEBC8ZgGN0/a3OZMKAnHn5Jp5TX1Flhq1vfRyx+C8vQacOkoXhcbnImGu3A7OJXpuCVO/FC0y2m3TUr+6MnjjkAHZ3sFiNJ+27nd7WV65iTlp7Yka0Yc2Jvqc1+8N15s9m8N0cUkcIRx66NfpNKjHXepK/nqj85r5+8ow3Siolbk7dDIzjPYKo+o27k9uNGGac36Ts+32yGuoCx+zQGzjxE6m9afN/2UQrwNVFk/R0aXHfYf27EESQjCiCdmnCjk2BV1QwCdgrKpbqreYoKL4X9Z7+LaKEXivjxnzov9hfFgeijmlrvCBh5YF59zjWWiEcG8DH+hsxk4Jpn7eas+u4hK+8lIdPtTuNd+Qf9Wc4soJSISNxwKEn9+Gn2KvThxfOW92ZkOs17ffdGGVNndYt0xvh+xhnHWIa4GLVpyE8YLWHgEjk7zXUyGMjrsFziMJeLPvJETi2E4jq2XLyVrydXsVQiCl7pxIKcn69tmkeLwa2kEKCSVwI1opNe+euDoJejU/3ezYcNULrqxeFcsPYx07CDbkioSbJ0/hzAkyLJeZcN+D7LWLbbex866wUFyHXIxuKewWi5gUwGoArelf2Rt/bGTeuEY+5EHae80qLWp55TCTLZpEOXZKyPZ+Q8gWRvWufw=", + "algid:sign:RSA:digest-PKCS1v15:SHA512": "wkUhun+23sf7YnDNnu3FlrFA+GKJpvyrYFrpam0l80AeEvtlc6B6/eBgUKNc/NYnYDtLNMOILromOb6ZVHefpz1nnB8A0Safjf9+URBYeqIILAplRD4WTgFwS8aqENdgF2w51LZwvqIyFXKy6A4Q1EbKkm6/X4VHn6foLz3ELo1lOiEVBv9dcqVlM8onKUtUxRXuY4J3PeNTciUwigdn80hM3bJ1p8GR2dgG89bM5vLXb4MX6vFeXTm1Xyb5oWwaS4XSvbnhbrueTJnBLcCWV3+ILYVgYot99D0gVWI06KH5EPe53SPPBe3Eb3TudqS1sbnmjVeX8avfMfSlbwDNHqADo6z0AFKUQO6/mX6aNb0ei7EcqSSeVRl0IO6vXbB7tVTuJk5/sdW8CGwIybWJGNwxp4b/vTuQvu2BhFzqfnmiqvVln6D6X5latGWAk+6kJPZpayoCXAnsN41dS3Si435h/0LK+/D0uqBaNBG5WUntKqMNibiW9WQpi3NV0T6AuAOp0zBxVih7OYyz5f9D+y0Gvhulg+j9zM0hfx2KKkqEOZfViBLFMZx3nFDCyDpV8ytt08qRjdhozoe5WXy5wUKQ9RZ6fmQWa5zpFZjr5KxwX84Ev9FfAA5l3zt2pLEkIOlH5ubnCCvF7KqJjoR9QhuxJnvVP9pWFg3dhHn+BG8=", + "algid:sign:RSA:message-PKCS1v15:SHA1": "RjcmVMLG3o3FnAH8oYdiFPu+Oy5LsoBEQP2yiHAmSllm3L3bjNt7b8WI8PtZqV4P1XFLqxCWUvbg5+cRo9FHD15+3+5PyQWBdut0LPecrsq/78t9BIXPFWYt2ZPdOhYAV0VxBxd9IGWZ92V8Dtw0xBNoF/nIojWABJne5xOBISeInnSDXATTXp9SUkNSsjyzgHX41W7Y8Ewac84Vfk+ITMvZhl9JZM7+TlL0ydWmf4Rz8EbUy/QZGuqBhfiaddyeRkrhJ6+yiiuLim6oKzUnTWb+UW4Vit3CiExG/OtGYB6Df1HCUZybWhN3N/ovMiTig5iO5z3UAKlWOt6eeB3d9uAspb6dkKmiWvTDqZGM4ldLlI8vm8AkaM3nYx9vLP6fY8KK0CAPeOsJEkx7gcHQSPUac/Pn37LLG4itg1LXG8ceU/hR1VA1YwwMCpGUY4kgoPHXDlqmojBHkNfk99ESgY8WoHe1uWQwo041C8MVFXdRixV5yHMtPpvr2SgRT0HoaxXGxD57EYvP+Kh20O/RvYRsp9aJovWYeuM38SAJB0S07O62WvUweR1KtfmKsNK8Qb2oW1jPjjiZJlAKMp9Ildct0EFqWXucb0RMpUABQeU4vpquYJmUoFnuyi8AB3DD1/As09Ql19Z8f5+QIo3t9X3jkGj8do+dEbwfdpVME/g=", + "algid:sign:RSA:message-PKCS1v15:SHA224": "p3yUPCZDfHDSkalZURc0wUxJajkbf/UdTFJ/bXJznuSQm/wtn88gw9/pDTXdaLrX7yo9JoIX5IloYcpqvPLlflO1jI6HYSTyS9/420qmrL5fzWuFXbmqhHZVQcCtYNntD20JUaLVwm+6ZC7d2FFf7qPJ6K+6Y+Ap6eY5D0YydD9twARI60G7Y5ZTMEc7Q4PkyT08mnFMv3WDc4R0VoU43r3ovk1YT4RGOQwQ22BNs+eSk6ZSaKpWX+vefwoCFjXU5WZ5ycUdmo/x/Qi4+zcNQhK2UifWC35m8ifr/D9/kVaOgLlhgGIjhEEjipNuS9VgCUwjXSJqrHUOygp7woudxAladQdpAwlDJ0a4RBA/0tVjmGPLnNeCXvperAxSYEHUHHlgy9Gr7DDUHYxLuHAbcLyffEggZSi/yPK2dX3eFGNgCeUdQVZPYvr7mviKKxn/klqT3CPPu6wHZJI84IFVHc3NkkH5DSsRdrfKwdGcBRiqW5ZF8rfgHijdj7IR641ZSX01FxBOz5jl2RJZhBYYETS9dtU4b3UGNrNFnwCPE0DbCRBGkoX2wJC9VQDkXDd/n3uXV9quRSNErr6JJSYtZ0oopsMt0ghtRiVzXicp/EmAHeYw5zdc4WI/6ZUQ5MYJrB3HPkMvwbHNpgtu3DNAn9hAJRyzy2DbJbHuNym9QEE=", + "algid:sign:RSA:message-PKCS1v15:SHA256": "v+CUbIaUyg8YrLyrpWzz9mbHWtfcSF2IV8wMEzXj557BQBTzjhVF4kfwbAR2VmhxuuwHOgxi/gxTCBMto2BxPv3VbYPGprBikfXpii5EcoZ/5q7hZes9fw55QPg5z7D0xskQonQ33obcajTfjeaT2p4xRwvBG6BYw8WDifSj/JqGxkXAf1lnuZUTAq6Djw0ZB+kIYdUzBoUSUYrI29nY+uSKH1xLgKbc9UuZt7n/VY9BiygX6WKGoRVsfW74SHHWjBmbaKANcPPVoYAscYVU3toIZOe1nae5ZDxBGliTuoesWwkVSWjaNytfgYRhRE/uo0zQOPK9j5txDQ+mFlo6eheym0v0PF/MvvA+H/XSWHWrZQ4HWmnPIuxsKprLdf4kroSqBb5reM/krsgLsqnDxybOmrlC/WG7fHSYHSfFm9eot1vD2pk7ZnYJHiTFQ2XbdXPMWOIvmGcE1wyGS6zwEEdvyofUopt7NU0hKalsQdfZPAXoOMClEw7ycIhoXy3qVTSyD+PuN63W6jYlHvQDXlbMZWtnneBNtKT/3NMuQk+ff9AZ3YhENUuwFVsT2WfdVF4boNAP3EsP3CjF02K7iGbeG9m+KmOX68+QKHZadm91N67vrjHjh9kx41zOJ5zEirmNW2rBqeqq5wXRIlH1eGTENxgUoo9fi/DYg6zFQaI=", + "algid:sign:RSA:message-PKCS1v15:SHA384": "qCz5z4vf3oeREXrYwx7ii5vGUSzlVxIj/xJbGAnPiS8p1ffOQGbtJXdrzFGiGeje6O+ILqdfOU5mISTUqaH+qh8z14ta7whf3vnis+dEMGv4Re6cBfq5lVu+xHgeScBOaz4WLeY8hs0WsPHCMOkcl6tjs0XujnCODob1yTeRTxnZU582MjDmZheM3Rlc+3aXhgKuYprmgwEcsoqoPlYqd8m3DumXLggL3r9v+nP2gH/8kZNyBrTyjJRf+NQ+Mk/5dkJzNJ87lf6oUR5PGHAdB3ZvLGMQUF42M39ev61sod8Emlbw9vUUdVmdzP86HIs5d3EkWSI0ojR/szmDoKvAI6a9BJvgd/dvNwNrh9KmmQmurGpNbL8fV1Ceoua5wdJ20BPNxmL0H8cnj+iNaSPmcTWlpxEhsieiedIZpRSqDrTqXIJfhUaJSGHLinXrkZ7kR7Et4AVt1Nw+8wujLM2TgLPVLoTNxXXDdOGNqeXCUn0Je9DZJ/VA8FEMT1SNJhkITsTtjMwcc8060NDvGfDrGMnkwK0lIdCR+HDTb+YIzF0vKuYhvE3yOVNT7RHRnQWfM4i06NDoZkuetz8ih2LYBvehh4xjusjo9EGgilogsdvlFMJJSW8iNZ1rNr7/jcbP44iUJVh6Q7xK+X44Fwq1k3cRP63opAFzV4b9Vi8HbZY=", + "algid:sign:RSA:message-PKCS1v15:SHA512": "KBLoU88ueGIlb+eho9Hj7nlO3Lz6gWeqnTArxGs/AOYIIc+/kxC6sEf2r0DrnfGXDWqIBrH6XmjZhPHTbs/N7lamXegBkCeQfk+Qkwu32CX7CZag81D6Qev1ZISM0eKjRT0tNnEBKeebrYukqrUeSe1LR9jbq+y8Y9txpX6/eMgOlm48qsKEm5Z7VOsZCebj+8JTZUYNKqOaxjEvOkVl51MgGj2thaWvPgC9KURywEiML6nIsY29R/9c5gv0bs6Ui7eEdWATcTk+Vahpah5yupTvrqdvZLXE9OUP7eHHN9A0qAOyYT/g5pKef29ydPkA+JSJXeER9oK6OttV5iAJ1bJ4tkVS+WhdZ7DDsUXyj4NOmoPm4DbgzFawFMzlyppKLz/vRTCemiW5S0GYXYf93vd8uTeUAaTq1Ka+i5m5VMRd6kGdpmIHY7OZSZ9Ult1nnBTPCUQFps0vz+i22Y6h9zY1RpAFlca7u/8CyjlSRjaMJNWSNMvrF7bY253mo8oWYIPcYlWx5wsQ7eD2iaS3ac+EimBITJYWEs08vW6GE6venbW2NS1qwwSbXEKeDbLukWXd+B9+MvfayInW538E1C8TlKu5tgpFahWUFefMOdaSbqnePI0Ft52K5MgHWPwzc63e4W/ZxoULl5+MV81S2MUfD+WO9nZ5HM9W2BJUuxs=" + ] + ) +}