From 3537b84a2d56199ec54bed78a59a6c6448b5b156 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:09:46 -0700 Subject: [PATCH 01/10] Bumped minimum swift-libp2p-crypto version to 0.1.1 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index dd8d45c..fa1269e 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/swift-libp2p/swift-libp2p-crypto.git", .upToNextMajor(from: "0.0.1")), + .package(url: "https://github.com/swift-libp2p/swift-libp2p-crypto.git", .upToNextMinor(from: "0.1.1")), .package(url: "https://github.com/swift-libp2p/swift-multihash.git", .upToNextMajor(from: "0.0.1")), .package(url: "https://github.com/swift-libp2p/swift-cid.git", .upToNextMajor(from: "0.0.1")), .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.12.0")) From 4e9c22804114fbfe9e92d1f5e6a99749e7760ae0 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:11:17 -0700 Subject: [PATCH 02/10] Added example of PEM String export / import --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index b7c5a18..489497e 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,13 @@ peerID.keyPair?.keyType == .ed25519 // The type of Key peerID.keyPair?.privateKey // Access to the private key (for signing) peerID.keyPair?.publicKey // Access to the public key (for verifying signatures) +/// If you want to reuse the same PeerID between sessions, you can... + +/// Export a PeerID as an Encrypted PEM String that you can store... +let encryptedPEM = try peerID.exportKeyPair(as: .privatePEMString(encryptedWithPassword: "mypassword")) + +/// And then load the PeerID from and encrypted PEM String later +let peerID = try PeerID(pem: "ENCRYPTED_PEM_String", password: "mypassword") ``` ### API @@ -108,6 +115,8 @@ PeerID.init(marshaledPrivateKey str:String, base:BaseEncoding) throws /// Inits a `PeerID` from a marshaled private key PeerID.init(marshaledPrivateKey data:Data) throws +/// Inits a `PeerID` from a PEM String +PeerID.init(pem: String, withPassword: String? = nil) throws /// Properties /// Returns the PeerID's id as a base58 string (multihash/CIDv0). @@ -136,6 +145,8 @@ PeerID.toJSON(includingPrivateKey:Bool = false) throws -> Data /// Exports our PeerID as a JSON string PeerID.toJSONString(includingPrivateKey:Bool = false) throws -> String? +/// Exports our PeerID as a PEM String +PeerID.exportKeyPair(as: PeerID.ExportType) throws -> String /// Signing and Verifying // Signs data using this PeerID's private key. This signature can then be verified by a remote peer using this PeerID's public key From 076b4f2af38426e6c0fec9956ad34a513400da5e Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:36:31 -0700 Subject: [PATCH 03/10] PeerID Equatable Conformance --- Sources/PeerID/PeerID+Equatable.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Sources/PeerID/PeerID+Equatable.swift diff --git a/Sources/PeerID/PeerID+Equatable.swift b/Sources/PeerID/PeerID+Equatable.swift new file mode 100644 index 0000000..fdf193f --- /dev/null +++ b/Sources/PeerID/PeerID+Equatable.swift @@ -0,0 +1,20 @@ +// +// PeerID+Equatable.swift +// +// +// Created by Brandon Toms on 9/23/22. +// + +import Foundation + +extension PeerID:Equatable { + public static func == (lhs: PeerID, rhs: PeerID) -> Bool { + lhs.id == rhs.id + } + public static func == (lhs: [UInt8], rhs: PeerID) -> Bool { + lhs == rhs.id + } + public static func == (lhs: Data, rhs: PeerID) -> Bool { + lhs.bytes == rhs.id + } +} From 4abcd3b066d3314a57162b297b1f9bb7677cd035 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:36:52 -0700 Subject: [PATCH 04/10] PeerID and JSON imports and exports --- Sources/PeerID/PeerID+JSON.swift | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Sources/PeerID/PeerID+JSON.swift diff --git a/Sources/PeerID/PeerID+JSON.swift b/Sources/PeerID/PeerID+JSON.swift new file mode 100644 index 0000000..5250bb4 --- /dev/null +++ b/Sources/PeerID/PeerID+JSON.swift @@ -0,0 +1,86 @@ +// +// PeerID+JSON.swift +// +// +// Created by Brandon Toms on 9/23/22. +// + +import LibP2PCrypto +import Foundation +import Multihash + +/// - MARK: JSON Imports and Exports +public extension PeerID { + /// An Internal PeerID struct to facilitate JSON Encoding and Decoding + internal struct PeerIDJSON:Codable { + /// base58 encoded string + let id:String + /// base64 encoded publicKey protobuf + let pubKey:String? + /// base64 encoded privateKey protobuf + let privKey:String? + } + + /// Initialize a PeerID from JSON data + /// + /// Expects a JSON object of the form + /// ``` + /// { + /// obj.id: String - The multihash encoded in base58 + /// obj.pubKey: String? - The public key in protobuf format, encoded in 'base64' + /// obj.privKey: String? - The private key in protobuf format, encoded in 'base64' + /// } + /// ``` + convenience init(fromJSON json:Data) throws { + let data = try JSONDecoder().decode(PeerIDJSON.self, from: json) + + if data.privKey == nil && data.pubKey == nil { + /// Only ID Present... + try self.init(fromBytesID: Multihash(b58String: data.id).value) + } else if data.privKey == nil, let pubKey = data.pubKey { + /// Only Public Key and ID Present, lets init via the public key and derive the ID + /// TODO: Compare the provided ID and the Derived ID and throw an error if they dont match... + try self.init(marshaledPublicKey: pubKey, base: .base64) + } else if let privKey = data.privKey { + /// Private Key was provided. Lets init via the private key and derive both the public key and the ID + /// TODO: Compare the provided publicKey and ID to the ones derived from the private key and throw an error if they don't match... + try self.init(marshaledPrivateKey: privKey, base: .base64) + } else { + throw NSError(domain: "Failed to init PeerID from json", code: 0, userInfo: nil) + } + } + + /// Exports our PeerID as a JSON object + /// + /// Returns a JSON object of the form + /// ``` + /// { + /// id: String - The multihash encoded in base58 + /// pubKey: String? - The public key in protobuf format, encoded in 'base64' + /// privKey: String? - The private key in protobuf format, encoded in 'base64' + /// } + /// ``` + func toJSON(includingPrivateKey:Bool = false) throws -> Data { + let pidJSON = PeerIDJSON( + id: self.b58String, + pubKey: try? self.keyPair?.publicKey.marshal().asString(base: .base64), + privKey: includingPrivateKey ? try? self.keyPair?.privateKey?.marshal().asString(base: .base64) : nil + ) + + return try JSONEncoder().encode(pidJSON) + } + + /// Exports our PeerID as a JSON object + /// + /// Returns a JSON object as a String + /// ``` + /// { + /// id: String - The multihash encoded in base58 + /// pubKey: String? - The public key in protobuf format, encoded in 'base64' + /// privKey: String? - The private key in protobuf format, encoded in 'base64' + /// } + /// ``` + func toJSONString(includingPrivateKey:Bool = false) throws -> String? { + return try String(data: self.toJSON(), encoding: .utf8) + } +} From 084661fd785fda58e4b6de9e1ac8395ce957be74 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:37:09 -0700 Subject: [PATCH 05/10] PeerID and Marshaled imports and exports --- Sources/PeerID/PeerID+Marshaled.swift | 118 ++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 Sources/PeerID/PeerID+Marshaled.swift diff --git a/Sources/PeerID/PeerID+Marshaled.swift b/Sources/PeerID/PeerID+Marshaled.swift new file mode 100644 index 0000000..a468de1 --- /dev/null +++ b/Sources/PeerID/PeerID+Marshaled.swift @@ -0,0 +1,118 @@ +// +// PeerID+Marshaled.swift +// +// +// Created by Brandon Toms on 9/23/22. +// + +import LibP2PCrypto +import Foundation +import Multibase + +/// - MARK: Marshaled Imports and Exports +public extension PeerID { + /// Inits a `PeerID` from a marshaled `PeerID` string + /// - Note: `base` can be left `nil` if the marshaledPeerID String is `Multibase` compliant (includes the multibase prefix) otherwise, you must specify the ecoded base of the string... + convenience init(marshaledPeerID:String, base: BaseEncoding? = nil) throws { + let marshaledData:Data + if let base = base { + marshaledData = try BaseEncoding.decode(marshaledPeerID, as: base).data + } else { + marshaledData = try BaseEncoding.decode(marshaledPeerID).data + } + try self.init(marshaledPeerID: marshaledData) + } + + /// Inits a `PeerID` from a marshaled `PeerID` + convenience init(marshaledPeerID data:Data) throws { + // Attampt to instantiate a PeerIdProto with the raw, marshaled, data + let protoPeerID = try PeerIdProto(contiguousBytes: data) + + //print(protoPeerID.id.asString(base: .base64)) + //print("Has PubKey: \(protoPeerID.hasPubKey)") + //print(protoPeerID.pubKey.asString(base: .base64)) + //print("Has PrivKey: \(protoPeerID.hasPrivKey)") + //print(protoPeerID.privKey.asString(base: .base64)) + + // Enusre the Marshaled data included at least a public key (is this necessary, would we ever need to unmarshal an ID only?) + guard protoPeerID.hasPubKey || protoPeerID.hasPrivKey else { + throw NSError(domain: "No Public or Private Key Found in marshaled data", code: 0, userInfo: nil) + } + + // If we have a private key, attempt to instantiate the PeerID via the private key, otherwise, try the public key... + if protoPeerID.hasPrivKey { + try self.init(marshaledPrivateKey: protoPeerID.privKey) + } else if protoPeerID.hasPubKey { + try self.init(marshaledPublicKey: protoPeerID.pubKey) + } else { + throw NSError(domain: "No Public or Private Key Found in marshaled data", code: 0, userInfo: nil) + } + } + + /// Inits a `PeerID` from a marshaled public key string + convenience init(marshaledPublicKey str:String, base:BaseEncoding) throws { + try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: str, base: base)) + } + + /// Inits a `PeerID` from a marshaled public key + convenience init(marshaledPublicKey key:Data) throws { + try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: key)) + } + + /// Inits a `PeerID` from a marshaled private key string + convenience init(marshaledPrivateKey str:String, base:BaseEncoding) throws { + try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: str, base: base)) + } + + /// Inits a `PeerID` from a marshaled private key + convenience init(marshaledPrivateKey data:Data) throws { + try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: data)) + } + + +// private static func computeDigest(pubKey:SecKey) throws -> [UInt8] { +// let bytes = try pubKey.rawRepresentation() +// return try self.computeDigest(rawPubKey: bytes) +// } +// +// /// - Note: We need to marshal the raw public key before multihashing it.... +// private static func computeDigest(rawPubKey bytes:Data) throws -> [UInt8] { +// let marshaled = try LibP2PCrypto.Keys.marshalPublicKey(raw: bytes, keyType: .RSA(bits: .B1024)) +// //print(marshaled.asString(base: .base64Pad)) +// if marshaled.count <= 42 { +// return try Multihash(raw: marshaled, hashedWith: .identity).value +// } else { +// //let mh = try Multihash(raw: bytes, hashedWith: .sha2_256) +// //print("Value: \(mh.value.asString(base: .base16))") +// //print("Hex: \(mh.hexString)") +// //print("Digest: \(mh.digest?.asString(base: .base16) ?? "NIL")") +// return try Multihash(raw: marshaled, hashedWith: .sha2_256).value //pubKey.hash() +// } +// } + + + /// Returns a protocol-buffers encoded version of the id, public key and, if `includingPrivateKey` is set to `true`, the private key. + func marshal(includingPrivateKey:Bool = false) throws -> [UInt8] { + var pid = PeerIdProto() + pid.id = Data(self.id) + pid.pubKey = try self.keyPair?.publicKey.marshal() ?? Data() + if includingPrivateKey, let privKey = self.keyPair?.privateKey { + pid.privKey = try privKey.marshal() + } + return try pid.serializedData().bytes + } + + func marshalPrivateKey() throws -> [UInt8] { + guard let privKey = self.keyPair?.privateKey else { + throw NSError(domain: "This PeerID doesn't have a Private Key to Marshal", code: 0, userInfo: nil) + } + return try privKey.marshal().bytes + } + + func marshalPublicKey() throws -> [UInt8] { + guard let pubKey = self.keyPair?.publicKey else { + throw NSError(domain: "This PeerID doesn't have a Public Key to Marshal", code: 0, userInfo: nil) + } + return try pubKey.marshal().bytes + } +} From 06e557b6302a25920e4341435cc015d26c37dc1d Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:37:23 -0700 Subject: [PATCH 06/10] PeerID and PEM imports and exports --- Sources/PeerID/PeerID+PEM.swift | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Sources/PeerID/PeerID+PEM.swift diff --git a/Sources/PeerID/PeerID+PEM.swift b/Sources/PeerID/PeerID+PEM.swift new file mode 100644 index 0000000..495f5aa --- /dev/null +++ b/Sources/PeerID/PeerID+PEM.swift @@ -0,0 +1,37 @@ +// +// PeerID+PEM.swift +// +// +// Created by Brandon Toms on 9/23/22. +// + +import LibP2PCrypto +import Foundation + +/// - MARK: PEM Imports and Exports +public extension PeerID { + convenience init(pem: String, password: String?) throws { + try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(pem: pem, password: password)) + } + + enum ExportType { + case publicPEMString + case privatePEMString(encryptedWithPassword:String) + case unencrypredPrivatePEMString + } + + /// Exports the KeyPair as PEM structured String. Private Keys can be encrypted with a password before export. + func exportKeyPair(as exportType:ExportType) throws -> String { + guard let keyPair = self.keyPair else { throw NSError(domain: "No Underlying Key Pair to Export", code: 0, userInfo: nil) } + switch exportType { + case .publicPEMString: + return try keyPair.publicKey.exportPublicKeyPEMString(withHeaderAndFooter: true) + case .unencrypredPrivatePEMString: + guard keyPair.privateKey != nil else { throw NSError(domain: "No Private Key to Export", code: 0, userInfo: nil) } + return try keyPair.exportPrivatePEMString(withHeaderAndFooter: true) + case .privatePEMString(let password): + guard !password.isEmpty else { throw NSError(domain: "Password shouldn't be empty", code: 0, userInfo: nil) } + return try keyPair.exportEncryptedPrivatePEMString(withPassword: password) + } + } +} From b4f5baf1a8b995d74080b15c45ee648033c8c977 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:38:00 -0700 Subject: [PATCH 07/10] PeerID signatures and signature verification methods --- Sources/PeerID/PeerID+Signatures.swift | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Sources/PeerID/PeerID+Signatures.swift diff --git a/Sources/PeerID/PeerID+Signatures.swift b/Sources/PeerID/PeerID+Signatures.swift new file mode 100644 index 0000000..83f4383 --- /dev/null +++ b/Sources/PeerID/PeerID+Signatures.swift @@ -0,0 +1,30 @@ +// +// PeerID+Signature.swift +// +// +// Created by Brandon Toms on 9/23/22. +// + +import LibP2PCrypto +import Foundation + +/// - MARK: PeerID Signatures and Verification Methods +public extension PeerID { + // Signs data using this PeerID's private key. This signature can then be verified by a remote peer using this PeerID's public key + func signature(for msg:Data) throws -> Data { + guard let priv = keyPair?.privateKey else { + throw NSError(domain: "A private key is required for generating signature and this PeerID doesn't contain a private key.", code: 0, userInfo: nil) + } + + return try priv.sign(message: msg) + } + + // Using this PeerID's public key, this method checks to see if the signature data was in fact signed by this peer and is a valid signature for the expected data + func isValidSignature(_ signature:Data, for expectedData:Data) throws -> Bool { + guard let pub = keyPair?.publicKey else { + throw NSError(domain: "A public key is required for verifying signatures and this PeerID doesn't contain a public key.", code: 0, userInfo: nil) + } + + return try pub.verify(signature: signature, for: expectedData) + } +} From b8dfcca541c6d2589d0fbf6168a0b1ac5729c117 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:39:06 -0700 Subject: [PATCH 08/10] Separated various conformances into their own files --- Sources/PeerID/PeerID.swift | 214 ++---------------------------------- 1 file changed, 7 insertions(+), 207 deletions(-) diff --git a/Sources/PeerID/PeerID.swift b/Sources/PeerID/PeerID.swift index 1f52e29..9007c10 100644 --- a/Sources/PeerID/PeerID.swift +++ b/Sources/PeerID/PeerID.swift @@ -1,9 +1,14 @@ +// +// PeerID.swift +// +// +// Created by Brandon Toms on 5/1/22. +// +import LibP2PCrypto import Foundation import Multihash -import Multibase import CID -import LibP2PCrypto /// - Reference: https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#how-keys-are-encoded-and-messages-signed public class PeerID { @@ -103,178 +108,6 @@ public class PeerID { self.keyPair = nil } - /// Inits a `PeerID` from a marshaled `PeerID` string - /// - Note: `base` can be left `nil` if the marshaledPeerID String is `Multibase` compliant (includes the multibase prefix) otherwise, you must specify the ecoded base of the string... - public convenience init(marshaledPeerID:String, base: BaseEncoding? = nil) throws { - let marshaledData:Data - if let base = base { - marshaledData = try BaseEncoding.decode(marshaledPeerID, as: base).data - } else { - marshaledData = try BaseEncoding.decode(marshaledPeerID).data - } - try self.init(marshaledPeerID: marshaledData) - } - - /// Inits a `PeerID` from a marshaled `PeerID` - public convenience init(marshaledPeerID data:Data) throws { - // Attampt to instantiate a PeerIdProto with the raw, marshaled, data - let protoPeerID = try PeerIdProto(contiguousBytes: data) - - //print(protoPeerID.id.asString(base: .base64)) - //print("Has PubKey: \(protoPeerID.hasPubKey)") - //print(protoPeerID.pubKey.asString(base: .base64)) - //print("Has PrivKey: \(protoPeerID.hasPrivKey)") - //print(protoPeerID.privKey.asString(base: .base64)) - - // Enusre the Marshaled data included at least a public key (is this necessary, would we ever need to unmarshal an ID only?) - guard protoPeerID.hasPubKey || protoPeerID.hasPrivKey else { - throw NSError(domain: "No Public or Private Key Found in marshaled data", code: 0, userInfo: nil) - } - - // If we have a private key, attempt to instantiate the PeerID via the private key, otherwise, try the public key... - if protoPeerID.hasPrivKey { - try self.init(marshaledPrivateKey: protoPeerID.privKey) - } else if protoPeerID.hasPubKey { - try self.init(marshaledPublicKey: protoPeerID.pubKey) - } else { - throw NSError(domain: "No Public or Private Key Found in marshaled data", code: 0, userInfo: nil) - } - } - - /// Inits a `PeerID` from a marshaled public key string - public convenience init(marshaledPublicKey str:String, base:BaseEncoding) throws { - try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: str, base: base)) - } - - /// Inits a `PeerID` from a marshaled public key - public convenience init(marshaledPublicKey key:Data) throws { - try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: key)) - } - - /// Inits a `PeerID` from a marshaled private key string - public convenience init(marshaledPrivateKey str:String, base:BaseEncoding) throws { - try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: str, base: base)) - } - - /// Inits a `PeerID` from a marshaled private key - public convenience init(marshaledPrivateKey data:Data) throws { - try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: data)) - } - - /// An Internal PeerID struct to facilitate JSON Encoding and Decoding - internal struct PeerIDJSON:Codable { - /// base58 encoded string - let id:String - /// base64 encoded publicKey protobuf - let pubKey:String? - /// base64 encoded privateKey protobuf - let privKey:String? - } - - /// Initialize a PeerID from JSON data - /// - /// Expects a JSON object of the form - /// ``` - /// { - /// obj.id: String - The multihash encoded in base58 - /// obj.pubKey: String? - The public key in protobuf format, encoded in 'base64' - /// obj.privKey: String? - The private key in protobuf format, encoded in 'base64' - /// } - /// ``` - public convenience init(fromJSON json:Data) throws { - let data = try JSONDecoder().decode(PeerIDJSON.self, from: json) - - if data.privKey == nil && data.pubKey == nil { - /// Only ID Present... - try self.init(fromBytesID: Multihash(b58String: data.id).value) - } else if data.privKey == nil, let pubKey = data.pubKey { - /// Only Public Key and ID Present, lets init via the public key and derive the ID - /// TODO: Compare the provided ID and the Derived ID and throw an error if they dont match... - try self.init(marshaledPublicKey: pubKey, base: .base64) - } else if let privKey = data.privKey { - /// Private Key was provided. Lets init via the private key and derive both the public key and the ID - /// TODO: Compare the provided publicKey and ID to the ones derived from the private key and throw an error if they don't match... - try self.init(marshaledPrivateKey: privKey, base: .base64) - } else { - throw NSError(domain: "Failed to init PeerID from json", code: 0, userInfo: nil) - } - } - - /// Exports our PeerID as a JSON object - /// - /// Returns a JSON object of the form - /// ``` - /// { - /// obj.id: String - The multihash encoded in base58 - /// obj.pubKey: String? - The public key in protobuf format, encoded in 'base64' - /// obj.privKey: String? - The private key in protobuf format, encoded in 'base64' - /// } - /// ``` - public func toJSON(includingPrivateKey:Bool = false) throws -> Data { - let pidJSON = PeerIDJSON( - id: self.b58String, - pubKey: try? self.keyPair?.publicKey.marshal().asString(base: .base64), - privKey: includingPrivateKey ? try? self.keyPair?.privateKey?.marshal().asString(base: .base64) : nil - ) - - return try JSONEncoder().encode(pidJSON) - } - - public func toJSONString(includingPrivateKey:Bool = false) throws -> String? { - return try String(data: self.toJSON(), encoding: .utf8) - } - - -// private static func computeDigest(pubKey:SecKey) throws -> [UInt8] { -// let bytes = try pubKey.rawRepresentation() -// return try self.computeDigest(rawPubKey: bytes) -// } -// -// /// - Note: We need to marshal the raw public key before multihashing it.... -// private static func computeDigest(rawPubKey bytes:Data) throws -> [UInt8] { -// let marshaled = try LibP2PCrypto.Keys.marshalPublicKey(raw: bytes, keyType: .RSA(bits: .B1024)) -// //print(marshaled.asString(base: .base64Pad)) -// if marshaled.count <= 42 { -// return try Multihash(raw: marshaled, hashedWith: .identity).value -// } else { -// //let mh = try Multihash(raw: bytes, hashedWith: .sha2_256) -// //print("Value: \(mh.value.asString(base: .base16))") -// //print("Hex: \(mh.hexString)") -// //print("Digest: \(mh.digest?.asString(base: .base16) ?? "NIL")") -// return try Multihash(raw: marshaled, hashedWith: .sha2_256).value //pubKey.hash() -// } -// } - - - /// Returns a protocol-buffers encoded version of the id, public key and, if `includingPrivateKey` is set to `true`, the private key. - public func marshal(includingPrivateKey:Bool = false) throws -> [UInt8] { - var pid = PeerIdProto() - pid.id = Data(self.id) - pid.pubKey = try self.keyPair?.publicKey.marshal() ?? Data() - if includingPrivateKey, let privKey = self.keyPair?.privateKey { - pid.privKey = try privKey.marshal() - } - return try pid.serializedData().bytes - } - - public func marshalPrivateKey() throws -> [UInt8] { - guard let privKey = self.keyPair?.privateKey else { - throw NSError(domain: "This PeerID doesn't have a Private Key to Marshal", code: 0, userInfo: nil) - } - return try privKey.marshal().bytes - } - - public func marshalPublicKey() throws -> [UInt8] { - guard let pubKey = self.keyPair?.publicKey else { - throw NSError(domain: "This PeerID doesn't have a Public Key to Marshal", code: 0, userInfo: nil) - } - return try pubKey.marshal().bytes - } - -// public func marshalPublicKeyAsProtobuf() throws -> LibP2PCrypto.PublicKey { -// -// } - /// Returns the PeerID's id as a self-describing CIDv1 in Base32 (RFC 0001) /// return self-describing String representation /// in default format from RFC 0001: https://github.com/libp2p/specs/pull/209 @@ -292,31 +125,11 @@ public class PeerID { private func toBase64Pad(_ buf:[UInt8]) -> String { buf.asString(base: .base64Pad) } - - // Signs data using this PeerID's private key. This signature can then be verified by a remote peer using this PeerID's public key - public func signature(for msg:Data) throws -> Data { - guard let priv = keyPair?.privateKey else { - throw NSError(domain: "A private key is required for generating signature and this PeerID doesn't contain a private key.", code: 0, userInfo: nil) - } - - return try priv.sign(message: msg) - } - - // Using this PeerID's public key, this method checks to see if the signature data was in fact signed by this peer and is a valid signature for the expected data - public func isValidSignature(_ signature:Data, for expectedData:Data) throws -> Bool { - guard let pub = keyPair?.publicKey else { - throw NSError(domain: "A public key is required for verifying signatures and this PeerID doesn't contain a public key.", code: 0, userInfo: nil) - } - - return try pub.verify(signature: signature, for: expectedData) - } } extension PeerID:CustomStringConvertible { public var description: String { let pid = self.b58String - // All sha256 nodes start with Qm - // We can skip the Qm to make the peer.ID more useful var skip = 0 if pid.hasPrefix("Qm") { skip = 2 @@ -328,8 +141,6 @@ extension PeerID:CustomStringConvertible { public var shortDescription: String { let pid = self.b58String - // All sha256 nodes start with Qm - // We can skip the Qm to make the peer.ID more useful if pid.hasPrefix("Qm") { return String(pid.dropFirst(2).prefix(6)) } else if pid.hasPrefix("12D3KooW") { @@ -355,17 +166,6 @@ private extension Array where Element == UInt8 { } } -extension PeerID:Equatable { - public static func == (lhs: PeerID, rhs: PeerID) -> Bool { - lhs.id == rhs.id - } - public static func == (lhs: [UInt8], rhs: PeerID) -> Bool { - lhs == rhs.id - } - public static func == (lhs: Data, rhs: PeerID) -> Bool { - lhs.bytes == rhs.id - } -} //public func computeDigest(rawPubKey:Data) -> Data { // if rawPubKey.count <= 42 { // return Multihash(raw: rawPubKey, hashedWith: .identity) From 9fde232382e6620af03c8c3b6cfe7e153a9f54a0 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 09:39:23 -0700 Subject: [PATCH 09/10] Added PEM import and export tests --- Tests/PeerIDTests/PeerIDTests.swift | 119 ++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/Tests/PeerIDTests/PeerIDTests.swift b/Tests/PeerIDTests/PeerIDTests.swift index 1fc1b7f..a8b1392 100644 --- a/Tests/PeerIDTests/PeerIDTests.swift +++ b/Tests/PeerIDTests/PeerIDTests.swift @@ -274,7 +274,6 @@ final class PeerIDTests: XCTestCase { } func testFromMarshaledPrivateKey() throws { - let marshaledPeerIDData = Data(hex: PeerIDTests.samplePeerID.marshaled) //try Multihash(hexString: "f\(PeerIDTests.samplePeerID.marshaled)").value let protoPeerID = try PeerIdProto(contiguousBytes: marshaledPeerIDData) @@ -304,7 +303,6 @@ final class PeerIDTests: XCTestCase { /// Imports a SecKey from the raw data /// Extracts/derives a Public Key from the Private Key func testFromMarshaledPrivateKey_GO() throws { - let marshaledPrivateKey = try BaseEncoding.decode(PeerIDTests.goPeerID.privKey, as: .base64Pad) let peerID = try PeerID(marshaledPrivateKey: marshaledPrivateKey.data) @@ -316,10 +314,8 @@ final class PeerIDTests: XCTestCase { XCTAssertEqual(pid, PeerIDTests.goPeerID.id) } - /// Marshaling Private RSA Keys aren't supported yet on Linux - #if canImport(Security) + /// Marshaling Private RSA Keys func testFromMarshaledPrivateKey_GO_2() throws { - let peerID = try PeerID(marshaledPrivateKey: PeerIDTests.goPeerID.privKey, base: .base64Pad) XCTAssertEqual(peerID.b58String, PeerIDTests.goPeerID.id) @@ -332,13 +328,9 @@ final class PeerIDTests: XCTestCase { XCTAssertEqual(marshaledPrivKey.asString(base: .base64Pad), PeerIDTests.goPeerID.privKey) } - #endif - /// 3.052, 3.096 (using multibase library) - /// 0.135, 0.134 (using Data(hex: )) func testToJSONPublic() throws { - //let peerID = try PeerID(marshaledPeerID: PeerIDTests.samplePeerID.marshaled, base: .base16) - let peerID = try PeerID(marshaledPeerID: Data(hex: PeerIDTests.samplePeerID.marshaled)) + let peerID = try PeerID(marshaledPeerID: PeerIDTests.samplePeerID.marshaled, base: .base16) let publicJSON = try peerID.toJSON(includingPrivateKey: false) @@ -357,10 +349,8 @@ final class PeerIDTests: XCTestCase { XCTAssertNil(pubID.keyPair?.privateKey) } - #if canImport(Security) func testToJSONFull() throws { - //let peerID = try PeerID(marshaledPeerID: PeerIDTests.samplePeerID.marshaled, base: .base16) - let peerID = try PeerID(marshaledPeerID: Data(hex: PeerIDTests.samplePeerID.marshaled)) + let peerID = try PeerID(marshaledPeerID: PeerIDTests.samplePeerID.marshaled, base: .base16) let fullJSON = try peerID.toJSON(includingPrivateKey: true) let publicJSON = try peerID.toJSON(includingPrivateKey: false) @@ -400,12 +390,104 @@ final class PeerIDTests: XCTestCase { XCTAssertEqual(pubID.keyPair?.publicKey.asString(base: .base64), fullID.keyPair?.publicKey.asString(base: .base64)) XCTAssertNotEqual(pubID.keyPair?.privateKey?.asString(base: .base64), fullID.keyPair?.privateKey?.asString(base: .base64)) } - #endif + + func testImportExportEncryptedPEM() throws { + /// The encrypted version of an RSA 1024 Private Key + /// + /// Encrypted with + /// ``` + /// openssl pkcs8 + /// -in foo.pem + /// -topk8 + /// -v2 aes-128-cbc + /// -passout pass:mypassword + /// ``` + let ENCRYPTED = """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQI49PtP+7yJmgCAggA + MB0GCWCGSAFlAwQBAgQQYz/oWtq4qhWPNrAQiO3i5wSCAoCjWvOSqAMdA4qDF8BB + aaqGRnZ/Lvewsrs4keppFogFnYpeVkzEmeleQLIYkO2mnNvsjhfh2Vk1LW/qNPIl + NvwjXyNbP1E6TlLmTNEAgIfyViHOCuk+17tkgAtK98huFTi0U+LbMcaxSnJ7CsNY + 9JODko7fLXMpEaGy5qcuXWsMHG1iKcggYs0J1kmWSVw9ZQP7Uh9hs31zz60kFe+T + 1I8EOjC06EcKY2HmOhzS+p378nWD3Lxi49FWkHslx1OtQwAXqMG5xWSo+kTWgmUx + fB3Olmv7opDcQ5OtOSxRjM/6SCtrtIlPRjIS7Uu4foW2BpFS+mkkvaJR0lMiEFjA + qMdLu3MZzT8U9lEDpd+ki+OjIC2bOXkv/OgHFmHjrTrGTVnK+HP5B0XkcaN0kmi5 + ypd8/XB4zDqO/eSSTKnDe5cvw9Ruj5vt9cesUGjckTlVlZ7Sip2nqtngEAh0k7gc + p8p0LpNRyOM5edxNCsRLWj3Z9oskkbEFbL3INuVr6HZ5C1IpUHaxzdii1FBeLSqY + RYCC7iOgfqRILkBN2dsnWhdLLvcVpeQqSccnNCYSrXgr40T8BqZKLnuhHT7/iZaw + OiKp9MyygPf0wO5IFaSglpk02dohJpg/LYxFBZk+qJKPR9883NrtSPSzXxDogu2f + /tc8OCoH919cB8WAsU1cvKYMxsr9HTfoxS7itrJX9d7tE3J2Ky7fQrPWt247BXSE + FMUJ8BQpLL/2lNIxW9clLEuzr0RZKu3AhBU0V0o8KDucrsLPdbLvV9/J8+G8VJWB + DZjkXrHO2Oob0rOBtz0gnIF4TSwMWlI28OFWLwN3ByGeT0KcDN7SghLtDSyEQKNW + ZHiA + -----END ENCRYPTED PRIVATE KEY----- + """ + + let peerID = try PeerID(pem: ENCRYPTED, password: "mypassword") + XCTAssertNotNil(peerID.keyPair) + XCTAssertNotNil(peerID.keyPair?.publicKey) + XCTAssertNotNil(peerID.keyPair?.privateKey) + XCTAssertEqual(peerID.type, .isPrivate) + XCTAssertEqual(peerID.keyPair?.keyType, .rsa) + + /// Every time we export the encrypted PEM it should be unique (unless you manually set the IV using swift-libp2p-crypto) + let export1 = try peerID.exportKeyPair(as: .privatePEMString(encryptedWithPassword: "mypassword")) + let export2 = try peerID.exportKeyPair(as: .privatePEMString(encryptedWithPassword: "mypassword")) + + XCTAssertNotEqual(export1, ENCRYPTED) + XCTAssertNotEqual(export2, ENCRYPTED) + XCTAssertNotEqual(export1, export2) + XCTAssertEqual(export1.count, export2.count) + XCTAssertEqual(export1.count, ENCRYPTED.count) + + /// Ensure the wrong passwords throw errors + XCTAssertThrowsError(try PeerID(pem: ENCRYPTED, password: "")) + XCTAssertThrowsError(try PeerID(pem: ENCRYPTED, password: "MyPassword")) + XCTAssertThrowsError(try PeerID(pem: ENCRYPTED, password: nil)) + } + + func testImportExportED25519PeerID() throws { + let peerID = try PeerID(.Ed25519) + let export = try peerID.exportKeyPair(as: .privatePEMString(encryptedWithPassword: "mypassword")) + + print(export) + + let imported = try PeerID(pem: export, password: "mypassword") + + XCTAssertNotNil(imported.keyPair) + XCTAssertNotNil(imported.keyPair?.privateKey) + XCTAssertEqual(imported.keyPair?.privateKey?.rawRepresentation, peerID.keyPair?.privateKey?.rawRepresentation) + XCTAssertEqual(imported, peerID) + + /// Ensure the wrong passwords throw errors + XCTAssertThrowsError(try PeerID(pem: export, password: "")) + XCTAssertThrowsError(try PeerID(pem: export, password: "MyPassword")) + XCTAssertThrowsError(try PeerID(pem: export, password: nil)) + } + + func testImportExportSecp256k1PeerID() throws { + let peerID = try PeerID(.Secp256k1) + let export = try peerID.exportKeyPair(as: .privatePEMString(encryptedWithPassword: "mypassword")) + + print(export) + + let imported = try PeerID(pem: export, password: "mypassword") + + XCTAssertNotNil(imported.keyPair) + XCTAssertNotNil(imported.keyPair?.privateKey) + XCTAssertEqual(imported.keyPair?.privateKey?.rawRepresentation, peerID.keyPair?.privateKey?.rawRepresentation) + XCTAssertEqual(imported, peerID) + + /// Ensure the wrong passwords throw errors + XCTAssertThrowsError(try PeerID(pem: export, password: "")) + XCTAssertThrowsError(try PeerID(pem: export, password: "MyPassword")) + XCTAssertThrowsError(try PeerID(pem: export, password: nil)) + } static var allTests = [ ("testGeneratePeerID_Default_Params", testGeneratePeerID_Default_Params), ("testGeneratePeerID_RSA_1024", testGeneratePeerID_RSA_1024), - //("testGeneratePeerID_RSA_2048", testGeneratePeerID_RSA_2048), + ("testGeneratePeerID_RSA_2048", testGeneratePeerID_RSA_2048), //("testGeneratePeerID_RSA_3072", testGeneratePeerID_RSA_3072), //("testGeneratePeerID_RSA_4096", testGeneratePeerID_RSA_4096), ("testGenerate_Secp256k1_PeerID", testGenerate_Secp256k1_PeerID), @@ -421,8 +503,11 @@ final class PeerIDTests: XCTestCase { ("testFromMarshaledPublicKey", testFromMarshaledPublicKey), ("testFromMarshaledPrivateKey", testFromMarshaledPrivateKey), ("testFromMarshaledPrivateKey_GO", testFromMarshaledPrivateKey_GO), - //("testFromMarshaledPrivateKey_GO_2", testFromMarshaledPrivateKey_GO_2), + ("testFromMarshaledPrivateKey_GO_2", testFromMarshaledPrivateKey_GO_2), ("testToJSONPublic", testToJSONPublic), - //("testToJSONFull", testToJSONFull) + ("testToJSONFull", testToJSONFull), + ("testImportExportEncryptedPEM", testImportExportEncryptedPEM), + ("testImportExportED25519PeerID", testImportExportED25519PeerID), + ("testImportExportSecp256k1PeerID", testImportExportSecp256k1PeerID), ] } From 60876dbb9062a6182837340592c9192f6fd62402 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 23 Sep 2022 12:32:59 -0700 Subject: [PATCH 10/10] Reordered dependencies --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index fa1269e..c174398 100644 --- a/Package.swift +++ b/Package.swift @@ -29,8 +29,8 @@ let package = Package( name: "PeerID", dependencies: [ .product(name: "LibP2PCrypto", package: "swift-libp2p-crypto"), - .product(name: "CID", package: "swift-cid"), .product(name: "Multihash", package: "swift-multihash"), + .product(name: "CID", package: "swift-cid"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), ], resources: [