75 lines
2.4 KiB
Swift
75 lines
2.4 KiB
Swift
import CryptoKit
|
|
import Foundation
|
|
import Network
|
|
import Security
|
|
|
|
struct MacNodeBridgeTLSParams: Sendable {
|
|
let required: Bool
|
|
let expectedFingerprint: String?
|
|
let allowTOFU: Bool
|
|
let storeKey: String?
|
|
}
|
|
|
|
enum MacNodeBridgeTLSStore {
|
|
private static let suiteName = "com.clawdbot.shared"
|
|
private static let keyPrefix = "mac.node.bridge.tls."
|
|
|
|
private static var defaults: UserDefaults {
|
|
UserDefaults(suiteName: suiteName) ?? .standard
|
|
}
|
|
|
|
static func loadFingerprint(stableID: String) -> String? {
|
|
let key = self.keyPrefix + stableID
|
|
let raw = self.defaults.string(forKey: key)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return raw?.isEmpty == false ? raw : nil
|
|
}
|
|
|
|
static func saveFingerprint(_ value: String, stableID: String) {
|
|
let key = self.keyPrefix + stableID
|
|
self.defaults.set(value, forKey: key)
|
|
}
|
|
}
|
|
|
|
func makeMacNodeTLSOptions(_ params: MacNodeBridgeTLSParams?) -> NWProtocolTLS.Options? {
|
|
guard let params else { return nil }
|
|
let options = NWProtocolTLS.Options()
|
|
let expected = params.expectedFingerprint.map(normalizeMacNodeFingerprint)
|
|
let allowTOFU = params.allowTOFU
|
|
let storeKey = params.storeKey
|
|
|
|
sec_protocol_options_set_verify_block(
|
|
options.securityProtocolOptions,
|
|
{ _, trust, complete in
|
|
let trustRef = sec_trust_copy_ref(trust).takeRetainedValue()
|
|
if let chain = SecTrustCopyCertificateChain(trustRef) as? [SecCertificate],
|
|
let cert = chain.first
|
|
{
|
|
let data = SecCertificateCopyData(cert) as Data
|
|
let fingerprint = sha256Hex(data)
|
|
if let expected {
|
|
complete(fingerprint == expected)
|
|
return
|
|
}
|
|
if allowTOFU {
|
|
if let storeKey { MacNodeBridgeTLSStore.saveFingerprint(fingerprint, stableID: storeKey) }
|
|
complete(true)
|
|
return
|
|
}
|
|
}
|
|
let ok = SecTrustEvaluateWithError(trustRef, nil)
|
|
complete(ok)
|
|
},
|
|
DispatchQueue(label: "com.clawdbot.macos.bridge.tls.verify"))
|
|
|
|
return options
|
|
}
|
|
|
|
private func sha256Hex(_ data: Data) -> String {
|
|
let digest = SHA256.hash(data: data)
|
|
return digest.map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
|
|
private func normalizeMacNodeFingerprint(_ raw: String) -> String {
|
|
raw.lowercased().filter(\.isHexDigit)
|
|
}
|