Add Go project scaffold for Wails v2 SSH/Telnet/RAW TCP client
- go.mod with Wails v2.9.2, x/crypto, go-sqlite3, go-keyring, uuid - cmd/app/main.go: Wails entry point (Nexus Terminal, 1400x900, #1a1a2e) - internal/app/app.go: lifecycle (Startup/Shutdown/DomReady) - internal/models/models.go: full model definitions (Profile, SSHConfig, TelnetConfig, RAWConfig, SessionInfo, KnownHost, AppPreferences, etc.) - internal/models/defaults.go: default values and NewProfile factory - internal/validation/validation.go: thorough field-level validation - frontend/embed.go: embedded frontend assets - Updated existing storage, secrets, config to match new model structure
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// Package knownhosts manages SSH known host entries, providing verification,
|
||||
// storage, and management of host key fingerprints.
|
||||
package knownhosts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/copilot-ssh-client/app/internal/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// KnownHostStorage is the persistence interface used by the Store.
|
||||
type KnownHostStorage interface {
|
||||
GetKnownHost(host string, port int) (*models.KnownHost, error)
|
||||
SaveKnownHost(kh *models.KnownHost) error
|
||||
ListKnownHosts() ([]*models.KnownHost, error)
|
||||
DeleteKnownHost(id string) error
|
||||
}
|
||||
|
||||
// Store manages SSH known host entries. It provides verification against
|
||||
// previously accepted host keys and allows new keys to be trusted.
|
||||
type Store struct {
|
||||
storage KnownHostStorage
|
||||
}
|
||||
|
||||
// NewStore creates a Store backed by the given storage implementation.
|
||||
func NewStore(storage KnownHostStorage) *Store {
|
||||
return &Store{storage: storage}
|
||||
}
|
||||
|
||||
// Verify checks whether the presented host key matches a previously accepted
|
||||
// entry. Returns true if the fingerprint matches, false if there is no record,
|
||||
// and an error if the key has changed (possible MITM).
|
||||
func (s *Store) Verify(host string, port int, keyType string, fingerprint string, publicKey string) (bool, error) {
|
||||
existing, err := s.storage.GetKnownHost(host, port)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("knownhosts: lookup failed: %w", err)
|
||||
}
|
||||
if existing == nil {
|
||||
// No prior record — the caller should prompt the user.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if existing.Fingerprint == fingerprint {
|
||||
// Key matches — update the last-seen timestamp.
|
||||
existing.LastSeen = time.Now()
|
||||
_ = s.storage.SaveKnownHost(existing)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Fingerprint mismatch — the host key has changed.
|
||||
return false, fmt.Errorf(
|
||||
"knownhosts: host key for %s:%d has changed (was %s %s, now %s %s); "+
|
||||
"this could indicate a man-in-the-middle attack",
|
||||
host, port,
|
||||
existing.KeyType, existing.Fingerprint,
|
||||
keyType, fingerprint,
|
||||
)
|
||||
}
|
||||
|
||||
// Accept stores a new host key as trusted. If an entry already exists for the
|
||||
// host/port combination it is overwritten.
|
||||
func (s *Store) Accept(host string, port int, keyType string, fingerprint string, publicKey string) error {
|
||||
now := time.Now()
|
||||
kh := &models.KnownHost{
|
||||
ID: uuid.New().String(),
|
||||
Host: host,
|
||||
Port: port,
|
||||
KeyType: keyType,
|
||||
Fingerprint: fingerprint,
|
||||
PublicKey: publicKey,
|
||||
FirstSeen: now,
|
||||
LastSeen: now,
|
||||
Trusted: true,
|
||||
}
|
||||
if err := s.storage.SaveKnownHost(kh); err != nil {
|
||||
return fmt.Errorf("knownhosts: failed to accept key: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns all stored known host entries.
|
||||
func (s *Store) List() ([]*models.KnownHost, error) {
|
||||
entries, err := s.storage.ListKnownHosts()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("knownhosts: failed to list entries: %w", err)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// Delete removes a known host entry by ID.
|
||||
func (s *Store) Delete(id string) error {
|
||||
if err := s.storage.DeleteKnownHost(id); err != nil {
|
||||
return fmt.Errorf("knownhosts: failed to delete entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user