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? 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") } /// 从 SecKey 外部表示(x9.63 私钥,生成时存入凭据机密)重建签名器(路径 A)。 static func from(x963: Data) -> SigningKeyProvider? { guard let key = SecKeyCreateWithData(x963 as CFData, [ kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits: 256, ] as CFDictionary, nil) else { return nil } return SigningKeyProvider(privateKey: key, hardware: false) } /// 生成一把软件 P-256 私钥,返回签名器 + 其外部表示(供凭据机密持久化)。 static func generateSoftwareP256() -> (provider: SigningKeyProvider, x963: Data)? { guard let sw = createSoftwareKey(), let ext = SecKeyCopyExternalRepresentation(sw, nil) as Data? else { return nil } return (SigningKeyProvider(privateKey: sw, hardware: false), ext) } 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) } /// 运行时探测 Secure Enclave 是否可用(建一把临时非持久 SE key)。 /// 未签名/模拟器返回 false(驱动 UI 灰化 Face ID 选项);签名真机自动点亮,无需改码。 static func secureEnclaveAvailable() -> Bool { guard let access = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .privateKeyUsage, nil) else { return false } let attrs: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256, kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: false, kSecAttrAccessControl as String: access, ], ] return SecKeyCreateRandomKey(attrs as CFDictionary, nil) != nil } 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? 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? let k = SecKeyCreateRandomKey(attrs as CFDictionary, &err) if k == nil { NSLog("M4DBG software key create failed: \(String(describing: err?.takeRetainedValue()))") } return k } }