Files
terminalX/apps/TerminalX/iOS/SigningKeyProvider.swift
kid 5dbe937222 feat: M4b publickey 认证(SE 优先/软件回退,sign_callback)
- TXTransport: SSHSigner 协议 + Authentication.publicKeyCallback;SSHSession 用
  @convention(c) sshSignCallback(abstract 桥 SignerBox + malloc 出参)接 libssh2_userauth_publickey
- app SigningKeyProvider: SE 优先(kSecAttrTokenIDSecureEnclave),未签名/无 SE 回退软件 P-256
  (导出落文件持久化),同一 SSHSigner 路径;SecKeyCreateSignature(ecdsa)→ECDSAConv.derToSSHSignature
- 公钥 blob=ecdsa-sha2-nistp256||nistp256||X963;-txPubkeyAuth 日志打 authorized_keys 行
- 验证(192.168.9.199):不给密码,公钥加 authorized_keys 后免密登录成功(whoami=kid);
  模拟器 SE 生成 hw=true 可达,真机 SE 硬件签名+keychain 持久化待补

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

99 lines
4.5 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 Security
import TXCore
import TXTransport
/// SSH publickey Secure Enclave
/// / SE 退 P-256** SSHSigner **
/// "SE /"
final class SigningKeyProvider: SSHSigner, @unchecked Sendable {
private let privateKey: SecKey
let publicKeyBlob: [UInt8]
let isHardwareBacked: Bool
/// authorized_keys key
var authorizedKeysLine: String {
"ecdsa-sha2-nistp256 " + Data(publicKeyBlob).base64EncodedString() + " terminalx@device"
}
private init(privateKey: SecKey, hardware: Bool) {
self.privateKey = privateKey
self.isHardwareBacked = hardware
let pub = SecKeyCopyPublicKey(privateKey)!
let x963 = SecKeyCopyExternalRepresentation(pub, nil)! as Data // 0x04||X||Y (65B)
self.publicKeyBlob = ECDSAConv.p256PublicKeyBlob(x963: [UInt8](x963))
}
// MARK: - SSHSigner
func signSSH(_ data: [UInt8]) -> [UInt8]? {
var err: Unmanaged<CFError>?
guard let der = SecKeyCreateSignature(
privateKey, .ecdsaSignatureMessageX962SHA256, Data(data) as CFData, &err) as Data? else {
NSLog("M4DBG SecKeyCreateSignature failed: \(String(describing: err?.takeRetainedValue()))")
return nil
}
return ECDSAConv.derToSSHSignature([UInt8](der)) // DER SEQUENCE{r,s} SSH mpint r||mpint s
}
// MARK: - /
private static var keyFileURL: URL {
let base = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0]
return URL(fileURLWithPath: base).appendingPathComponent("ssh-ecdsa-p256.key")
}
static func loadOrCreate() -> SigningKeyProvider? {
// 1)
if let data = try? Data(contentsOf: keyFileURL),
let key = SecKeyCreateWithData(data as CFData, [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits: 256,
] as CFDictionary, nil) {
return SigningKeyProvider(privateKey: key, hardware: false)
}
// 2) Secure Enclave
if let se = createSecureEnclaveKey() {
return SigningKeyProvider(privateKey: se, hardware: true)
}
// 3) 退 P-256/ SE
guard let sw = createSoftwareKey() else { return nil }
if let ext = SecKeyCopyExternalRepresentation(sw, nil) as Data? {
try? ext.write(to: keyFileURL, options: .atomic)
}
return SigningKeyProvider(privateKey: sw, hardware: false)
}
private static func createSecureEnclaveKey() -> SecKey? {
guard let access = SecAccessControlCreateWithFlags(
nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .privateKeyUsage, nil) else { return nil }
let attrs: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true, // keychain/(-34018)退+
kSecAttrApplicationTag as String: "ai.athom.terminalx.sshkey".data(using: .utf8)!,
kSecAttrAccessControl as String: access,
],
]
var err: Unmanaged<CFError>?
let k = SecKeyCreateRandomKey(attrs as CFDictionary, &err)
if k == nil { NSLog("M4DBG SE key unavailable (预期于模拟器): \(String(describing: err?.takeRetainedValue()))") }
return k
}
private static func createSoftwareKey() -> SecKey? {
let attrs: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false],
]
var err: Unmanaged<CFError>?
let k = SecKeyCreateRandomKey(attrs as CFDictionary, &err)
if k == nil { NSLog("M4DBG software key create failed: \(String(describing: err?.takeRetainedValue()))") }
return k
}
}