67 lines
2.2 KiB
Swift
67 lines
2.2 KiB
Swift
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
|
|
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 { BridgeTLSStore.saveFingerprint(fingerprint, stableID: storeKey) }
|
|
complete(true)
|
|
return
|
|
}
|
|
}
|
|
let ok = SecTrustEvaluateWithError(trustRef, 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 }
|
|
}
|