package ssh import ( "database/sql" "fmt" ) // HostKeyResult represents the result of a host key verification. type HostKeyResult int const ( HostKeyNew HostKeyResult = iota // never seen this host before HostKeyMatch // fingerprint matches stored HostKeyChanged // fingerprint CHANGED — possible MITM ) // HostKeyStore stores and verifies SSH host key fingerprints in the host_keys SQLite table. type HostKeyStore struct { db *sql.DB } // NewHostKeyStore creates a new HostKeyStore backed by the given database. func NewHostKeyStore(db *sql.DB) *HostKeyStore { return &HostKeyStore{db: db} } // Verify checks whether the given fingerprint matches any stored host key for the // specified hostname, port, and key type. It returns HostKeyNew if no key is stored, // HostKeyMatch if the fingerprint matches, or HostKeyChanged if it differs. func (s *HostKeyStore) Verify(hostname string, port int, keyType string, fingerprint string) (HostKeyResult, error) { var storedFingerprint string err := s.db.QueryRow( "SELECT fingerprint FROM host_keys WHERE hostname = ? AND port = ? AND key_type = ?", hostname, port, keyType, ).Scan(&storedFingerprint) if err == sql.ErrNoRows { return HostKeyNew, nil } if err != nil { return 0, fmt.Errorf("query host key: %w", err) } if storedFingerprint == fingerprint { return HostKeyMatch, nil } return HostKeyChanged, nil } // Store inserts or replaces a host key fingerprint for the given hostname, port, and key type. func (s *HostKeyStore) Store(hostname string, port int, keyType string, fingerprint string, rawKey string) error { _, err := s.db.Exec( `INSERT INTO host_keys (hostname, port, key_type, fingerprint, raw_key) VALUES (?, ?, ?, ?, ?) ON CONFLICT (hostname, port, key_type) DO UPDATE SET fingerprint = excluded.fingerprint, raw_key = excluded.raw_key`, hostname, port, keyType, fingerprint, rawKey, ) if err != nil { return fmt.Errorf("store host key: %w", err) } return nil } // Delete removes all stored host keys for the given hostname and port. func (s *HostKeyStore) Delete(hostname string, port int) error { _, err := s.db.Exec( "DELETE FROM host_keys WHERE hostname = ? AND port = ?", hostname, port, ) if err != nil { return fmt.Errorf("delete host key: %w", err) } return nil } // GetFingerprint returns the stored fingerprint for the given hostname and port. // It returns an empty string and sql.ErrNoRows if no key is stored. func (s *HostKeyStore) GetFingerprint(hostname string, port int) (string, error) { var fingerprint string err := s.db.QueryRow( "SELECT fingerprint FROM host_keys WHERE hostname = ? AND port = ?", hostname, port, ).Scan(&fingerprint) if err != nil { return "", fmt.Errorf("get fingerprint: %w", err) } return fingerprint, nil }