Host trust

SSHHostKeyPolicy determines how the server’s host key is validated. SSHKit will not silently trust new keys — you must pick a policy.

Policies

// 1. OpenSSH known_hosts file. Best for first connections during development.
let policy: SSHHostKeyPolicy = .knownHostsFile(
    ("~/.ssh/known_hosts" as NSString).expandingTildeInPath
)

// 2. SHA-256 fingerprint pinned by the application.
let policy: SSHHostKeyPolicy = .pinnedFingerprint(
    SSHHostKeyFingerprint("SHA256:abcd…")
)

// 3. Inject any trust store (see below). Requires a stored fingerprint already.
let policy: SSHHostKeyPolicy = .trustStore(SSHKeychainHostTrustStore())

// 4. Explicit insecure mode — emits a warning log event. Testing only.
let policy: SSHHostKeyPolicy = .insecureAcceptAnyHostKey

Trust stores

The SSHHostTrustStore protocol is dependency-injectable. Two implementations ship with the package:

A trust store with no entry for the target host fails the connection with hostKeyVerificationFailed and the message “Host trust store has no trusted fingerprint for this host and port.” That is by design: a brand-new keychain has nothing to compare against.

Discover a fingerprint

To collect a server’s host key without authenticating, use SSHClient.discoverHostKey. It runs an SSH transport-level handshake, reads the server’s SHA-256 fingerprint, and tears the session down before any credential exchange. The discovery configuration carries only transport-level fields — not username or authentication.

let discovery = SSHHostKeyDiscoveryConfiguration(
    host: "example.com",
    port: 22
)
let observed = try await SSHClient.discoverHostKey(configuration: discovery)
print(observed.host, observed.port, observed.fingerprint.rawValue)

SSHDiscoveredHostKey carries the host, port, and the canonical SSHHostKeyFingerprint. Use this as the input to a user-facing “trust this server?” prompt.

Keychain bootstrap

The recommended four-step flow for first-time Keychain users: discover → confirm → save → connect.

// 1. Discover the server's fingerprint without authenticating.
let discovery = SSHHostKeyDiscoveryConfiguration(
    host: configuration.host,
    port: configuration.port,
    timeout: configuration.timeout,
    logHandler: configuration.logHandler,
    proxyRoute: configuration.proxyRoute,
    algorithmProfile: configuration.algorithmProfile
)
let observed = try await SSHClient.discoverHostKey(configuration: discovery)

// 2. Ask the user. The fingerprint is what they would normally read off
// the server console, the cloud provider UI, or a deployment runbook.
guard userApprovedFingerprint(observed.fingerprint) else {
    throw MyError.userRejected
}

// 3. Persist the approved fingerprint.
let store = SSHKeychainHostTrustStore()
try store.saveFingerprint(
    observed.fingerprint,
    host: observed.host,
    port: observed.port
)

// 4. Connect with strict trust-store verification.
var production = configuration
production.hostKeyPolicy = .trustStore(SSHKeychainHostTrustStore())
let connection = try await SSHClient.connect(configuration: production)

Once the fingerprint is stored, .trustStore is strict: it requires the live server to present the same fingerprint that the store has on file. A missing or mismatched entry fails the connection with hostKeyVerificationFailed.

.insecureAcceptAnyHostKey is not the bootstrap entry point — it’s an explicit “skip verification on this connection” policy intended for tests. Use discoverHostKey for fingerprint collection.

Fingerprint format

SSHHostKeyFingerprint(_:) normalizes its input. If the raw string omits the SHA256: prefix, SSHKit adds it. Both forms are accepted:

SSHHostKeyFingerprint("SHA256:abcd…")  // canonical
SSHHostKeyFingerprint("abcd…")         // SSHKit adds "SHA256:"