Files
terminalX/apps/TerminalX/iOS/KeychainKnownHosts.swift
kid 1a257fae7f feat: M4a host key 校验(TOFU pin,关闭 MITM 敞口)
- TXCore/SSH: SSHWire(string/mpint 编码)、ECDSAConv(DER→SSH 签名/P256 blob)、
  HostKey(opensshFingerprint/evaluate/HostTriple/KnownHostsStore),+10 单测(含真实指纹向量、高位 DER)
- TXTransport: SSHConfig.hostKeyVerifier 闭包 + SSHError.hostKeyMismatch;
  SSHSession 握手后 libssh2_session_hostkey 取 blob 交 verifier,拒绝则断开抛错
- app: 文件后端 KnownHostsStore(未签名 app 无 keychain 权限 SecItem -34018,host key 是
  公钥非机密,沙盒文件对 TOFU 足够)+ TOFU verifier + firstUse 横幅 + mismatch alert(信任/取消)
- 验证(192.168.9.199):首次信任 pin+接受连上;假 pin→不符→断开+告警,本次指纹与 ssh-keygen 逐字符一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-24 18:06:39 +08:00

55 lines
2.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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": "<base64>", "type": <int> } }`
/// 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)
}
}