import Foundation import TXCore /// known_hosts pin 存储(文件后端)。 /// /// 为何用文件而非 Keychain:本 app 当前未签名(CODE_SIGNING_ALLOWED=NO),无 keychain-access-group /// 权限 → SecItem 全返 errSecMissingEntitlement(-34018)。而 host key pin 是**公钥完整性数据、非机密**, /// 存于 app 沙盒文件对 TOFU 足够(威胁模型是 MITM 换 key,沙盒文件完整性对非越狱设备与 Keychain 相当)。 /// 生产签名构建可换 Keychain 加固(另留 KnownHostsStore 协议便于替换)。 /// /// 格式:JSON `{ "egress|host|port": { "blob": "", "type": } }`, /// 落 Application Support/known-hosts.json。 final class KeychainKnownHostsStore: KnownHostsStore, @unchecked Sendable { private let lock = NSLock() private let url: URL private var cache: [String: [String: Any]] init() { let base = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] try? FileManager.default.createDirectory(atPath: base, withIntermediateDirectories: true) url = URL(fileURLWithPath: base).appendingPathComponent("known-hosts.json") if let data = try? Data(contentsOf: url), let obj = try? JSONSerialization.jsonObject(with: data) as? [String: [String: Any]] { cache = obj } else { cache = [:] } } func lookup(_ triple: HostTriple) -> HostKeyRecord? { lock.lock(); defer { lock.unlock() } guard let entry = cache[triple.accountKey], let b64 = entry["blob"] as? String, let blob = Data(base64Encoded: b64), let type = entry["type"] as? Int else { return nil } return HostKeyRecord(blob: blob, keyType: Int32(type)) } func pin(_ triple: HostTriple, record: HostKeyRecord) { lock.lock(); defer { lock.unlock() } cache[triple.accountKey] = ["blob": record.blob.base64EncodedString(), "type": Int(record.keyType)] persist() } func remove(_ triple: HostTriple) { lock.lock(); defer { lock.unlock() } cache[triple.accountKey] = nil persist() } private func persist() { guard let data = try? JSONSerialization.data(withJSONObject: cache) else { return } try? data.write(to: url, options: .atomic) } }