Skip to content

Commit 7f85efe

Browse files
authored
Merge pull request #1 from swift-libp2p/dev/pem+api
PEM Imports and Exports + Code Refactoring
2 parents 6d44cf2 + 60876db commit 7f85efe

File tree

9 files changed

+413
-226
lines changed

9 files changed

+413
-226
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
],
1818
dependencies: [
1919
// Dependencies declare other packages that this package depends on.
20-
.package(url: "https://github.com/swift-libp2p/swift-libp2p-crypto.git", .upToNextMajor(from: "0.0.1")),
20+
.package(url: "https://github.com/swift-libp2p/swift-libp2p-crypto.git", .upToNextMinor(from: "0.1.1")),
2121
.package(url: "https://github.com/swift-libp2p/swift-multihash.git", .upToNextMajor(from: "0.0.1")),
2222
.package(url: "https://github.com/swift-libp2p/swift-cid.git", .upToNextMajor(from: "0.0.1")),
2323
.package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.12.0"))
@@ -29,8 +29,8 @@ let package = Package(
2929
name: "PeerID",
3030
dependencies: [
3131
.product(name: "LibP2PCrypto", package: "swift-libp2p-crypto"),
32-
.product(name: "CID", package: "swift-cid"),
3332
.product(name: "Multihash", package: "swift-multihash"),
33+
.product(name: "CID", package: "swift-cid"),
3434
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
3535
],
3636
resources: [

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ peerID.keyPair?.keyType == .ed25519 // The type of Key
6767
peerID.keyPair?.privateKey // Access to the private key (for signing)
6868
peerID.keyPair?.publicKey // Access to the public key (for verifying signatures)
6969

70+
/// If you want to reuse the same PeerID between sessions, you can...
71+
72+
/// Export a PeerID as an Encrypted PEM String that you can store...
73+
let encryptedPEM = try peerID.exportKeyPair(as: .privatePEMString(encryptedWithPassword: "mypassword"))
74+
75+
/// And then load the PeerID from and encrypted PEM String later
76+
let peerID = try PeerID(pem: "ENCRYPTED_PEM_String", password: "mypassword")
7077
```
7178

7279
### API
@@ -108,6 +115,8 @@ PeerID.init(marshaledPrivateKey str:String, base:BaseEncoding) throws
108115
/// Inits a `PeerID` from a marshaled private key
109116
PeerID.init(marshaledPrivateKey data:Data) throws
110117

118+
/// Inits a `PeerID` from a PEM String
119+
PeerID.init(pem: String, withPassword: String? = nil) throws
111120

112121
/// Properties
113122
/// Returns the PeerID's id as a base58 string (multihash/CIDv0).
@@ -136,6 +145,8 @@ PeerID.toJSON(includingPrivateKey:Bool = false) throws -> Data
136145
/// Exports our PeerID as a JSON string
137146
PeerID.toJSONString(includingPrivateKey:Bool = false) throws -> String?
138147

148+
/// Exports our PeerID as a PEM String
149+
PeerID.exportKeyPair(as: PeerID.ExportType) throws -> String
139150

140151
/// Signing and Verifying
141152
// Signs data using this PeerID's private key. This signature can then be verified by a remote peer using this PeerID's public key

Sources/PeerID/PeerID+Equatable.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// PeerID+Equatable.swift
3+
//
4+
//
5+
// Created by Brandon Toms on 9/23/22.
6+
//
7+
8+
import Foundation
9+
10+
extension PeerID:Equatable {
11+
public static func == (lhs: PeerID, rhs: PeerID) -> Bool {
12+
lhs.id == rhs.id
13+
}
14+
public static func == (lhs: [UInt8], rhs: PeerID) -> Bool {
15+
lhs == rhs.id
16+
}
17+
public static func == (lhs: Data, rhs: PeerID) -> Bool {
18+
lhs.bytes == rhs.id
19+
}
20+
}

Sources/PeerID/PeerID+JSON.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// PeerID+JSON.swift
3+
//
4+
//
5+
// Created by Brandon Toms on 9/23/22.
6+
//
7+
8+
import LibP2PCrypto
9+
import Foundation
10+
import Multihash
11+
12+
/// - MARK: JSON Imports and Exports
13+
public extension PeerID {
14+
/// An Internal PeerID struct to facilitate JSON Encoding and Decoding
15+
internal struct PeerIDJSON:Codable {
16+
/// base58 encoded string
17+
let id:String
18+
/// base64 encoded publicKey protobuf
19+
let pubKey:String?
20+
/// base64 encoded privateKey protobuf
21+
let privKey:String?
22+
}
23+
24+
/// Initialize a PeerID from JSON data
25+
///
26+
/// Expects a JSON object of the form
27+
/// ```
28+
/// {
29+
/// obj.id: String - The multihash encoded in base58
30+
/// obj.pubKey: String? - The public key in protobuf format, encoded in 'base64'
31+
/// obj.privKey: String? - The private key in protobuf format, encoded in 'base64'
32+
/// }
33+
/// ```
34+
convenience init(fromJSON json:Data) throws {
35+
let data = try JSONDecoder().decode(PeerIDJSON.self, from: json)
36+
37+
if data.privKey == nil && data.pubKey == nil {
38+
/// Only ID Present...
39+
try self.init(fromBytesID: Multihash(b58String: data.id).value)
40+
} else if data.privKey == nil, let pubKey = data.pubKey {
41+
/// Only Public Key and ID Present, lets init via the public key and derive the ID
42+
/// TODO: Compare the provided ID and the Derived ID and throw an error if they dont match...
43+
try self.init(marshaledPublicKey: pubKey, base: .base64)
44+
} else if let privKey = data.privKey {
45+
/// Private Key was provided. Lets init via the private key and derive both the public key and the ID
46+
/// TODO: Compare the provided publicKey and ID to the ones derived from the private key and throw an error if they don't match...
47+
try self.init(marshaledPrivateKey: privKey, base: .base64)
48+
} else {
49+
throw NSError(domain: "Failed to init PeerID from json", code: 0, userInfo: nil)
50+
}
51+
}
52+
53+
/// Exports our PeerID as a JSON object
54+
///
55+
/// Returns a JSON object of the form
56+
/// ```
57+
/// {
58+
/// id: String - The multihash encoded in base58
59+
/// pubKey: String? - The public key in protobuf format, encoded in 'base64'
60+
/// privKey: String? - The private key in protobuf format, encoded in 'base64'
61+
/// }
62+
/// ```
63+
func toJSON(includingPrivateKey:Bool = false) throws -> Data {
64+
let pidJSON = PeerIDJSON(
65+
id: self.b58String,
66+
pubKey: try? self.keyPair?.publicKey.marshal().asString(base: .base64),
67+
privKey: includingPrivateKey ? try? self.keyPair?.privateKey?.marshal().asString(base: .base64) : nil
68+
)
69+
70+
return try JSONEncoder().encode(pidJSON)
71+
}
72+
73+
/// Exports our PeerID as a JSON object
74+
///
75+
/// Returns a JSON object as a String
76+
/// ```
77+
/// {
78+
/// id: String - The multihash encoded in base58
79+
/// pubKey: String? - The public key in protobuf format, encoded in 'base64'
80+
/// privKey: String? - The private key in protobuf format, encoded in 'base64'
81+
/// }
82+
/// ```
83+
func toJSONString(includingPrivateKey:Bool = false) throws -> String? {
84+
return try String(data: self.toJSON(), encoding: .utf8)
85+
}
86+
}

Sources/PeerID/PeerID+Marshaled.swift

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//
2+
// PeerID+Marshaled.swift
3+
//
4+
//
5+
// Created by Brandon Toms on 9/23/22.
6+
//
7+
8+
import LibP2PCrypto
9+
import Foundation
10+
import Multibase
11+
12+
/// - MARK: Marshaled Imports and Exports
13+
public extension PeerID {
14+
/// Inits a `PeerID` from a marshaled `PeerID` string
15+
/// - 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...
16+
convenience init(marshaledPeerID:String, base: BaseEncoding? = nil) throws {
17+
let marshaledData:Data
18+
if let base = base {
19+
marshaledData = try BaseEncoding.decode(marshaledPeerID, as: base).data
20+
} else {
21+
marshaledData = try BaseEncoding.decode(marshaledPeerID).data
22+
}
23+
try self.init(marshaledPeerID: marshaledData)
24+
}
25+
26+
/// Inits a `PeerID` from a marshaled `PeerID`
27+
convenience init(marshaledPeerID data:Data) throws {
28+
// Attampt to instantiate a PeerIdProto with the raw, marshaled, data
29+
let protoPeerID = try PeerIdProto(contiguousBytes: data)
30+
31+
//print(protoPeerID.id.asString(base: .base64))
32+
//print("Has PubKey: \(protoPeerID.hasPubKey)")
33+
//print(protoPeerID.pubKey.asString(base: .base64))
34+
//print("Has PrivKey: \(protoPeerID.hasPrivKey)")
35+
//print(protoPeerID.privKey.asString(base: .base64))
36+
37+
// Enusre the Marshaled data included at least a public key (is this necessary, would we ever need to unmarshal an ID only?)
38+
guard protoPeerID.hasPubKey || protoPeerID.hasPrivKey else {
39+
throw NSError(domain: "No Public or Private Key Found in marshaled data", code: 0, userInfo: nil)
40+
}
41+
42+
// If we have a private key, attempt to instantiate the PeerID via the private key, otherwise, try the public key...
43+
if protoPeerID.hasPrivKey {
44+
try self.init(marshaledPrivateKey: protoPeerID.privKey)
45+
} else if protoPeerID.hasPubKey {
46+
try self.init(marshaledPublicKey: protoPeerID.pubKey)
47+
} else {
48+
throw NSError(domain: "No Public or Private Key Found in marshaled data", code: 0, userInfo: nil)
49+
}
50+
}
51+
52+
/// Inits a `PeerID` from a marshaled public key string
53+
convenience init(marshaledPublicKey str:String, base:BaseEncoding) throws {
54+
try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: str, base: base))
55+
}
56+
57+
/// Inits a `PeerID` from a marshaled public key
58+
convenience init(marshaledPublicKey key:Data) throws {
59+
try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPublicKey: key))
60+
}
61+
62+
/// Inits a `PeerID` from a marshaled private key string
63+
convenience init(marshaledPrivateKey str:String, base:BaseEncoding) throws {
64+
try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: str, base: base))
65+
}
66+
67+
/// Inits a `PeerID` from a marshaled private key
68+
convenience init(marshaledPrivateKey data:Data) throws {
69+
try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(marshaledPrivateKey: data))
70+
}
71+
72+
73+
// private static func computeDigest(pubKey:SecKey) throws -> [UInt8] {
74+
// let bytes = try pubKey.rawRepresentation()
75+
// return try self.computeDigest(rawPubKey: bytes)
76+
// }
77+
//
78+
// /// - Note: We need to marshal the raw public key before multihashing it....
79+
// private static func computeDigest(rawPubKey bytes:Data) throws -> [UInt8] {
80+
// let marshaled = try LibP2PCrypto.Keys.marshalPublicKey(raw: bytes, keyType: .RSA(bits: .B1024))
81+
// //print(marshaled.asString(base: .base64Pad))
82+
// if marshaled.count <= 42 {
83+
// return try Multihash(raw: marshaled, hashedWith: .identity).value
84+
// } else {
85+
// //let mh = try Multihash(raw: bytes, hashedWith: .sha2_256)
86+
// //print("Value: \(mh.value.asString(base: .base16))")
87+
// //print("Hex: \(mh.hexString)")
88+
// //print("Digest: \(mh.digest?.asString(base: .base16) ?? "NIL")")
89+
// return try Multihash(raw: marshaled, hashedWith: .sha2_256).value //pubKey.hash()
90+
// }
91+
// }
92+
93+
94+
/// Returns a protocol-buffers encoded version of the id, public key and, if `includingPrivateKey` is set to `true`, the private key.
95+
func marshal(includingPrivateKey:Bool = false) throws -> [UInt8] {
96+
var pid = PeerIdProto()
97+
pid.id = Data(self.id)
98+
pid.pubKey = try self.keyPair?.publicKey.marshal() ?? Data()
99+
if includingPrivateKey, let privKey = self.keyPair?.privateKey {
100+
pid.privKey = try privKey.marshal()
101+
}
102+
return try pid.serializedData().bytes
103+
}
104+
105+
func marshalPrivateKey() throws -> [UInt8] {
106+
guard let privKey = self.keyPair?.privateKey else {
107+
throw NSError(domain: "This PeerID doesn't have a Private Key to Marshal", code: 0, userInfo: nil)
108+
}
109+
return try privKey.marshal().bytes
110+
}
111+
112+
func marshalPublicKey() throws -> [UInt8] {
113+
guard let pubKey = self.keyPair?.publicKey else {
114+
throw NSError(domain: "This PeerID doesn't have a Public Key to Marshal", code: 0, userInfo: nil)
115+
}
116+
return try pubKey.marshal().bytes
117+
}
118+
}

Sources/PeerID/PeerID+PEM.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// PeerID+PEM.swift
3+
//
4+
//
5+
// Created by Brandon Toms on 9/23/22.
6+
//
7+
8+
import LibP2PCrypto
9+
import Foundation
10+
11+
/// - MARK: PEM Imports and Exports
12+
public extension PeerID {
13+
convenience init(pem: String, password: String?) throws {
14+
try self.init(keyPair: LibP2PCrypto.Keys.KeyPair(pem: pem, password: password))
15+
}
16+
17+
enum ExportType {
18+
case publicPEMString
19+
case privatePEMString(encryptedWithPassword:String)
20+
case unencrypredPrivatePEMString
21+
}
22+
23+
/// Exports the KeyPair as PEM structured String. Private Keys can be encrypted with a password before export.
24+
func exportKeyPair(as exportType:ExportType) throws -> String {
25+
guard let keyPair = self.keyPair else { throw NSError(domain: "No Underlying Key Pair to Export", code: 0, userInfo: nil) }
26+
switch exportType {
27+
case .publicPEMString:
28+
return try keyPair.publicKey.exportPublicKeyPEMString(withHeaderAndFooter: true)
29+
case .unencrypredPrivatePEMString:
30+
guard keyPair.privateKey != nil else { throw NSError(domain: "No Private Key to Export", code: 0, userInfo: nil) }
31+
return try keyPair.exportPrivatePEMString(withHeaderAndFooter: true)
32+
case .privatePEMString(let password):
33+
guard !password.isEmpty else { throw NSError(domain: "Password shouldn't be empty", code: 0, userInfo: nil) }
34+
return try keyPair.exportEncryptedPrivatePEMString(withPassword: password)
35+
}
36+
}
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// PeerID+Signature.swift
3+
//
4+
//
5+
// Created by Brandon Toms on 9/23/22.
6+
//
7+
8+
import LibP2PCrypto
9+
import Foundation
10+
11+
/// - MARK: PeerID Signatures and Verification Methods
12+
public extension PeerID {
13+
// Signs data using this PeerID's private key. This signature can then be verified by a remote peer using this PeerID's public key
14+
func signature(for msg:Data) throws -> Data {
15+
guard let priv = keyPair?.privateKey else {
16+
throw NSError(domain: "A private key is required for generating signature and this PeerID doesn't contain a private key.", code: 0, userInfo: nil)
17+
}
18+
19+
return try priv.sign(message: msg)
20+
}
21+
22+
// 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
23+
func isValidSignature(_ signature:Data, for expectedData:Data) throws -> Bool {
24+
guard let pub = keyPair?.publicKey else {
25+
throw NSError(domain: "A public key is required for verifying signatures and this PeerID doesn't contain a public key.", code: 0, userInfo: nil)
26+
}
27+
28+
return try pub.verify(signature: signature, for: expectedData)
29+
}
30+
}

0 commit comments

Comments
 (0)