import Foundation import TXTransport /// 凭据 ↔ SSH 认证 的桥。三条各自很薄的路径: /// - 密码 → `.password` /// - 生成的软件 P-256 → 重建 SecKey → `SSHSigner` → `.publicKeyCallback`(路径 A) /// - 粘贴/导入 的 PEM/PKCS#8 → `.privateKey`(libssh2 `publickey_frommemory`,路径 B) /// /// 不自造私钥格式解析器:导入的私钥直接透传 libssh2/mbedTLS。只做**轻量头部嗅探**推断类型/加密/警告。 enum CredentialAuth { // MARK: - 生成 /// 生成一把软件 P-256 私钥 → 返回可存入 CredentialStore 的 (kind, secret)。 static func generateECDSAP256() -> (kind: Credential.Kind, secret: CredentialSecret)? { guard let (provider, x963) = SigningKeyProvider.generateSoftwareP256() else { return nil } let kind = Credential.Kind.privateKey( keyType: .ecdsaP256, source: .generated, hasPassphrase: false, publicKeyBlob: Data(provider.publicKeyBlob)) return (kind, .rawECPrivateKey(x963: x963)) } // MARK: - 导入 / 粘贴 struct ParsedKey { var kind: Credential.Kind var secret: CredentialSecret /// 人类可读的格式标签(UI 实时提示,如 "PEM · EC 私钥")。 var formatLabel: String /// 非致命警告(如 openssh-key-v1 mbedTLS 不认)。nil = 无。 var warning: String? } /// 嗅探私钥文本头部,产出凭据 kind + 机密(路径 B)。识别不了也照存(交由 libssh2 定夺)。 static func parsePrivateKey(_ text: String, source: Credential.Source) -> ParsedKey { let t = text.trimmingCharacters(in: .whitespacesAndNewlines) let encrypted = t.contains("Proc-Type: 4,ENCRYPTED") || t.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----") var keyType: Credential.KeyType = .unknown var label = "SSH 私钥" var warning: String? if t.contains("-----BEGIN OPENSSH PRIVATE KEY-----") { label = "OpenSSH 新格式" warning = "mbedTLS 不识别 openssh-key-v1,可能认证失败。建议先 `ssh-keygen -p -m PEM` 转为 PEM。" } else if t.contains("-----BEGIN RSA PRIVATE KEY-----") { keyType = .rsa; label = "PEM · RSA (PKCS#1)" } else if t.contains("-----BEGIN EC PRIVATE KEY-----") { keyType = .ecdsaP256; label = "PEM · EC (SEC1)" } else if t.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----") { label = "PKCS#8(加密)" } else if t.contains("-----BEGIN PRIVATE KEY-----") { label = "PKCS#8" } else { warning = "未识别到 PEM 私钥头,请确认粘贴的是私钥内容。" } let kind = Credential.Kind.privateKey( keyType: keyType, source: source, hasPassphrase: encrypted, publicKeyBlob: Data()) let secret = CredentialSecret.pemPrivateKey(pem: Data(text.utf8), passphrase: nil) return ParsedKey(kind: kind, secret: secret, formatLabel: label, warning: warning) } // MARK: - 构造 Authentication /// 由凭据 meta + 机密构造 SSH 认证。返回 nil = 无法用于连接(如 SE 未签名建不出、机密缺失)。 /// `passphraseOverride` 供连接时按需补输加密私钥口令。 static func authentication(for cred: Credential, secret: CredentialSecret, passphraseOverride: String? = nil) -> SSHConfig.Authentication? { switch secret { case .password(let pw): return .password(pw) case .pemPrivateKey(let pem, let storedPass): let pem = String(decoding: pem, as: UTF8.self) return .privateKey(pem: pem, passphrase: passphraseOverride ?? storedPass) case .rawECPrivateKey(let x963): guard let signer = SigningKeyProvider.from(x963: x963) else { return nil } return .publicKeyCallback(signer) case .none: return nil // Secure Enclave 占位:未签名无法签名 } } }