import CryptoKit import Foundation import Network import Security struct BridgeTLSParams: Sendable { let required: Bool let expectedFingerprint: String? let allowTOFU: Bool let storeKey: String? } enum BridgeTLSStore { private static let service = "com.clawdbot.bridge.tls" static func loadFingerprint(stableID: String) -> String? { KeychainStore.loadString(service: service, account: stableID)?.trimmingCharacters(in: .whitespacesAndNewlines) } static func saveFingerprint(_ value: String, stableID: String) { _ = KeychainStore.saveString(value, service: service, account: stableID) } } func makeBridgeTLSOptions(_ params: BridgeTLSParams?) -> NWProtocolTLS.Options? { guard let params else { return nil } let options = NWProtocolTLS.Options() let expected = params.expectedFingerprint.map(normalizeBridgeFingerprint) let allowTOFU = params.allowTOFU let storeKey = params.storeKey sec_protocol_options_set_verify_block( options.securityProtocolOptions, { _, trust, complete in guard let trust else { complete(false) return } if let cert = SecTrustGetCertificateAtIndex(trust, 0) { let data = SecCertificateCopyData(cert) as Data let fingerprint = sha256Hex(data) if let expected { complete(fingerprint == expected) return } if allowTOFU { if let storeKey { BridgeTLSStore.saveFingerprint(fingerprint, stableID: storeKey) } complete(true) return } } let ok = SecTrustEvaluateWithError(trust, nil) complete(ok) }, DispatchQueue(label: "com.clawdbot.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 normalizeBridgeFingerprint(_ raw: String) -> String { raw.lowercased().filter { $0.isHexDigit } }