Host trust

SSHHostKeyPolicy

public enum SSHHostKeyPolicy: Sendable {
    case insecureAcceptAnyHostKey
    case knownHostsFile(String)
    case pinnedFingerprint(SSHHostKeyFingerprint)
    case trustStore(any SSHHostTrustStore)
}

SSHHostKeyFingerprint

public struct SSHHostKeyFingerprint: Equatable, Codable, Sendable {
    public var rawValue: String

    public init(_ rawValue: String) // prepends "SHA256:" if missing
}

SSHHostTrustStore

public protocol SSHHostTrustStore: Sendable {
    func fingerprint(host: String, port: UInt16) throws -> SSHHostKeyFingerprint?
    func saveFingerprint(_ fingerprint: SSHHostKeyFingerprint, host: String, port: UInt16) throws
    func removeFingerprint(host: String, port: UInt16) throws
}

Synchronous — no async or callback variants. Trust store calls happen on the worker queue during host-key verification.

SSHMemoryHostTrustStore

public final class SSHMemoryHostTrustStore: SSHHostTrustStore, @unchecked Sendable {
    public init(fingerprints: [String: SSHHostKeyFingerprint] = [:])
}

Keys are "\(host.lowercased()):\(port)" if you want to seed the dictionary directly.

SSHKeychainHostTrustStore

public struct SSHKeychainHostTrustStore: SSHHostTrustStore, @unchecked Sendable {
    public static let defaultService = "wiki.qaq.sshkit"

    public init(
        service: String = SSHKeychainHostTrustStore.defaultService,
        accessGroup: String? = nil
    )
}

Discovery

Transport-only API for collecting a server’s fingerprint before any authentication. See the Keychain bootstrap flow in the guide for usage.

SSHHostKeyDiscoveryConfiguration

public struct SSHHostKeyDiscoveryConfiguration: Sendable {
    public var host: String
    public var port: UInt16
    public var timeout: TimeInterval
    public var logHandler: SSHLogHandler?
    public var proxyRoute: SSHProxyRoute?
    public var algorithmProfile: SSHAlgorithmProfile

    public init(
        host: String,
        port: UInt16 = 22,
        timeout: TimeInterval = 30,
        logHandler: SSHLogHandler? = nil,
        proxyRoute: SSHProxyRoute? = nil,
        algorithmProfile: SSHAlgorithmProfile = .modern
    )
}

Carries the transport-level fields needed to reach the server. Notably absent: username and authentication — discovery never authenticates. The init preconditions reject an empty host, a zero port, and a non-positive timeout.

SSHDiscoveredHostKey

public struct SSHDiscoveredHostKey: Equatable, Sendable {
    public let host: String
    public let port: UInt16
    public let fingerprint: SSHHostKeyFingerprint

    public init(host: String, port: UInt16, fingerprint: SSHHostKeyFingerprint)
}

SSHClient.discoverHostKey

extension SSHClient {
    public static func discoverHostKey(
        configuration: SSHHostKeyDiscoveryConfiguration
    ) async throws -> SSHDiscoveredHostKey

    public static func discoverHostKey(
        configuration: SSHHostKeyDiscoveryConfiguration,
        callbackQueue: DispatchQueue = .main,
        completion: @escaping (Result<SSHDiscoveredHostKey, SSHKitError>) -> Void
    )
}

Connects, reads the SHA-256 host-key fingerprint, and tears the session down. The returned fingerprint is in the canonical SHA256:… form. Task cancellation maps to SSHKitError(.cancelled) through the same socket-shutdown path described in Architecture.