inital commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
public final class NodeIdentity: @unchecked Sendable {
|
||||
public static let currentVersion = 1
|
||||
public let signingKey: Curve25519.Signing.PrivateKey
|
||||
public let agreementKey: Curve25519.KeyAgreement.PrivateKey
|
||||
public let record: NodeRecord
|
||||
|
||||
public init(signingKey: Curve25519.Signing.PrivateKey = .init(),
|
||||
agreementKey: Curve25519.KeyAgreement.PrivateKey = .init()) throws {
|
||||
self.signingKey = signingKey; self.agreementKey = agreementKey
|
||||
let signing = signingKey.publicKey.rawRepresentation
|
||||
self.record = NodeRecord(address: try MeshAddress.derive(from: signing),
|
||||
signingPublicKey: signing,
|
||||
agreementPublicKey: agreementKey.publicKey.rawRepresentation)
|
||||
}
|
||||
|
||||
public static func loadOrCreate(at url: URL) throws -> NodeIdentity {
|
||||
struct Stored: Codable { let version: Int; let signing: Data; let agreement: Data }
|
||||
let decoder = PropertyListDecoder()
|
||||
if let data = try? Data(contentsOf: url) {
|
||||
let value = try decoder.decode(Stored.self, from: data)
|
||||
guard value.version == currentVersion else { throw UMNError.invalidIdentity }
|
||||
return try NodeIdentity(signingKey: .init(rawRepresentation: value.signing),
|
||||
agreementKey: .init(rawRepresentation: value.agreement))
|
||||
}
|
||||
let identity = try NodeIdentity()
|
||||
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
let encoder = PropertyListEncoder(); encoder.outputFormat = .binary
|
||||
let data = try encoder.encode(Stored(version: currentVersion,
|
||||
signing: identity.signingKey.rawRepresentation,
|
||||
agreement: identity.agreementKey.rawRepresentation))
|
||||
try data.write(to: url, options: .atomic)
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
|
||||
return identity
|
||||
}
|
||||
|
||||
public func sign(_ data: Data) throws -> Data { try signingKey.signature(for: data) }
|
||||
|
||||
public func makeLinkState(sequence: UInt64, neighbors: [MeshAddress]) throws -> LinkState {
|
||||
let unsigned = LinkState(origin: record, sequence: sequence, neighbors: neighbors, signature: Data())
|
||||
return LinkState(origin: record, sequence: sequence, neighbors: neighbors,
|
||||
signature: try sign(unsigned.signingBytes()))
|
||||
}
|
||||
|
||||
public func seal(_ inner: InnerFrame, to destination: NodeRecord) throws -> SealedPayload {
|
||||
guard destination.validate() else { throw UMNError.invalidIdentity }
|
||||
let encoder = PropertyListEncoder(); encoder.outputFormat = .binary
|
||||
let innerData = try encoder.encode(inner)
|
||||
struct Signed: Codable { let inner: Data; let signature: Data }
|
||||
let signed = try encoder.encode(Signed(inner: innerData, signature: sign(innerData)))
|
||||
let ephemeral = Curve25519.KeyAgreement.PrivateKey()
|
||||
let remote = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: destination.agreementPublicKey)
|
||||
let secret = try ephemeral.sharedSecretFromKeyAgreement(with: remote)
|
||||
let key = secret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: Data("umn-e2e-v1".utf8),
|
||||
sharedInfo: destination.address.bytes, outputByteCount: 32)
|
||||
let box = try ChaChaPoly.seal(signed, using: key)
|
||||
return SealedPayload(ephemeralPublicKey: ephemeral.publicKey.rawRepresentation,
|
||||
combinedCiphertext: box.combined)
|
||||
}
|
||||
|
||||
public func open(_ payload: SealedPayload, expectedSource: MeshAddress) throws -> InnerFrame {
|
||||
struct Signed: Codable { let inner: Data; let signature: Data }
|
||||
let ephemeral = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: payload.ephemeralPublicKey)
|
||||
let secret = try agreementKey.sharedSecretFromKeyAgreement(with: ephemeral)
|
||||
let key = secret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: Data("umn-e2e-v1".utf8),
|
||||
sharedInfo: record.address.bytes, outputByteCount: 32)
|
||||
let box = try ChaChaPoly.SealedBox(combined: payload.combinedCiphertext)
|
||||
let data = try ChaChaPoly.open(box, using: key)
|
||||
let decoder = PropertyListDecoder()
|
||||
let signed = try decoder.decode(Signed.self, from: data)
|
||||
let inner = try decoder.decode(InnerFrame.self, from: signed.inner)
|
||||
guard inner.sourceRecord.address == expectedSource, inner.sourceRecord.validate() else { throw UMNError.invalidIdentity }
|
||||
let keyVerify = try Curve25519.Signing.PublicKey(rawRepresentation: inner.sourceRecord.signingPublicKey)
|
||||
guard keyVerify.isValidSignature(signed.signature, for: signed.inner) else { throw UMNError.invalidIdentity }
|
||||
return inner
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user