Files

62 lines
2.6 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
import TermindApplication
import TermindDomain
nonisolated struct SSHKeyExportSession: KeyExportSession {
let connection: any SSHConnectionProtocol
func install(publicKey: String, options: KeyExportOptions) async throws {
let command = try Self.command(publicKey: publicKey, options: options)
let result = try await connection.execute(
command, workingDirectory: nil, stdin: nil, timeout: 20, outputLimit: 64 * 1024, onOutput: nil)
guard result.exitCode == 0 else {
throw TermindSSHError.library(
String(localized: "The export script exited with status \(result.exitCode).")
+ "\n" + result.stderr.trimmingCharacters(in: .whitespacesAndNewlines))
}
}
func disconnect() async { await connection.disconnect() }
static func command(publicKey: String, options: KeyExportOptions) throws -> String {
guard options.isValid else { throw CocoaError(.coderInvalidValue) }
let key = try SSHKeyMaterial.publicMaterial(publicKey).publicKey
let launcher = """
case "$1" in
/*) directory=$1 ;;
'~') directory=$HOME ;;
'~/'*) directory="$HOME/${1#??}" ;;
*) directory="$HOME/$1" ;;
esac
exec sh -c "$4" termind-key-export "$directory" "$2" "$3"
"""
return "sh -c "
+ [launcher, "termind-key-export", options.directory, options.filename, key, options.script]
.map(RemoteShellCommand.escapeArgument).joined(separator: " ")
}
static func derivePublicKey(
key: SSHKeyRef, credential: SSHResolvedKeyCredential, passphrase: SSHSecret?
) async throws -> String {
try await Task.detached(priority: .userInitiated) {
defer {
credential.clearSecrets()
passphrase?.clear()
}
guard case .software(let privateKey, let saved) = credential else {
throw TermindSSHError.missingCredential
}
let password = passphrase ?? saved
let passwordText = try password?.withUnsafeCStringAndConsume { bytes, _ in String(cString: bytes) }
return try privateKey.withUnsafeCStringAndConsume { bytes, _ in
try SSHKeyMaterial.inspect(
privateKey: String(cString: bytes), passphrase: passwordText, comment: key.label
).publicKey
}
}.value
}
}