Files
hope-voice-api/Sources/Networking/TLSPinning.swift
T
snowspeeder 378b8cb36f feat: server config, token client, TLS pinning, connection test (Phase 1)
ServerConfig parses https base URL and derives wss livekit URL;
TokenClient POSTs /token with clean 503/offline error states;
TLSPinning holds optional cert SHA-384 pins (empty = system trust);
Settings gains a Connection test screen proving the endpoint works.
2026-08-29 17:18:38 -04:00

37 lines
1.5 KiB
Swift

import Foundation
import CommonCrypto
import Security
/// ATS exception for the LAN host is declared in Info.plist. If the deployment
/// uses a self-signed certificate, pin its SHA-384 here (extract with:
/// `openssl s_client -connect 192.168.86.2:9178 </dev/null 2>/dev/null | openssl x509 -outform der | shasum -a 384`).
/// While empty, standard system trust is used (works for the current deployment).
enum TLSPinning {
static let pinnedCertSHA384: [String] = []
static func validate(_ trust: SecTrust) -> Bool {
guard !pinnedCertSHA384.isEmpty else { return true }
var error: CFError?
guard SecTrustEvaluateWithError(trust, &error) else { return false }
let count = SecTrustGetCertificateCount(trust)
guard count > 0 else { return false }
guard let chain = SecTrustCopyCertificateChain(trust) as? [SecCertificate] else {
return false
}
for cert in chain {
guard let derData = SecCertificateCopyData(cert) as Data? else { continue }
if pinnedCertSHA384.contains(sha384Hex(derData)) { return true }
}
return false
}
private static func sha384Hex(_ data: Data) -> String {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH))
data.withUnsafeBytes { buffer in
guard let base = buffer.baseAddress else { return }
CC_SHA384(base, CC_LONG(data.count), &hash)
}
return hash.map { String(format: "%02x", $0) }.joined()
}
}