Skip to content

Commit

Permalink
Merge pull request #5 from swift-libp2p/feature/pem
Browse files Browse the repository at this point in the history
PEM + Private Marshaled Keys + CryptoSwift 1.6+
  • Loading branch information
btoms20 committed Sep 18, 2022
2 parents 2f3956a + 6945860 commit d98dcd4
Show file tree
Hide file tree
Showing 27 changed files with 2,917 additions and 2,631 deletions.
10 changes: 3 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"),
Expand Down
2 changes: 1 addition & 1 deletion Sources/LibP2PCrypto/Keys/CommonPrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Sources/LibP2PCrypto/Keys/CommonPublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
139 changes: 138 additions & 1 deletion Sources/LibP2PCrypto/Keys/KeyPair.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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<UInt8>, 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<UInt8> {
//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<UInt8> {
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<UInt8> {
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)!
}

}
102 changes: 102 additions & 0 deletions Sources/LibP2PCrypto/Keys/Types/Ed25519/Ed25519.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import Crypto
//import PEM

extension Curve25519.Signing.PublicKey:CommonPublicKey {
public static var keyType: LibP2PCrypto.Keys.GenericKeyType { .ed25519 }
Expand Down Expand Up @@ -128,3 +129,104 @@ extension Curve25519.Signing.PrivateKey:Equatable {
lhs.rawRepresentation == rhs.rawRepresentation
}
}

extension Curve25519.Signing.PublicKey:DERCodable {
public static var primaryObjectIdentifier: Array<UInt8> { [0x2B, 0x65, 0x70] }
public static var secondaryObjectIdentifier: Array<UInt8>? { nil }

public init(publicDER: Array<UInt8>) throws {
try self.init(rawRepresentation: publicDER)
}

public init(privateDER: Array<UInt8>) throws {
throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0)
}

public func publicKeyDER() throws -> Array<UInt8> {
self.rawRepresentation.bytes
}

public func privateKeyDER() throws -> Array<UInt8> {
throw NSError(domain: "Public Key doesn't have private DER representation", code: 0)
}

public func exportPublicKeyPEM(withHeaderAndFooter: Bool) throws -> Array<UInt8> {
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<UInt8> { [0x2B, 0x65, 0x70] }
public static var secondaryObjectIdentifier: Array<UInt8>? { nil }

public init(publicDER: Array<UInt8>) throws {
throw NSError(domain: "Can't instantiate private key from public DER representation", code: 0)
}

public init(privateDER: Array<UInt8>) 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<UInt8> {
try self.publicKey.publicKeyDER()
}

public func privateKeyDER() throws -> Array<UInt8> {
ASN1.Encoder.encode(
ASN1.Node.octetString(data: Data( self.rawRepresentation ))
)
}

public func exportPrivateKeyPEMRaw() throws -> Array<UInt8> {
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<UInt8> {
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
}
}
}

Loading

0 comments on commit d98dcd4

Please sign in to comment.