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>
This commit is contained in:
kid
2026-07-24 18:06:39 +08:00
parent 7abe61a71f
commit 1a257fae7f
9 changed files with 321 additions and 5 deletions

View File

@@ -28,6 +28,15 @@ struct ContentView: View {
default: break
}
}
.alert("⚠️ Host Key 已变化", isPresented: Binding(
get: { model.hostKeyMismatch != nil },
set: { if !$0 { model.dismissHostKeyMismatch() } }
), presenting: model.hostKeyMismatch) { _ in
Button("信任新密钥", role: .destructive) { model.trustNewHostKey() }
Button("取消", role: .cancel) { model.dismissHostKeyMismatch() }
} message: { info in
Text("\(info.egress)/\(info.host):\(info.port) 的 host key 与已记住的不同,可能是中间人攻击。\n\n已记住:\(info.storedFp)\n本次:\(info.presentedFp)")
}
}
}
}