package ssh import ( "fmt" "os" "path/filepath" "strings" cryptossh "golang.org/x/crypto/ssh" ) // getAuthMethods returns SSH authentication methods based on host config func (c *Client) getAuthMethods() ([]cryptossh.AuthMethod, error) { var authMethods []cryptossh.AuthMethod switch c.host.Auth.Type { case "password": if c.host.Auth.Password == "" { return nil, fmt.Errorf("password auth requires password") } authMethods = append(authMethods, cryptossh.Password(c.host.Auth.Password)) case "key": signer, err := c.getKeySigner() if err != nil { return nil, fmt.Errorf("failed to setup key authentication: %w", err) } authMethods = append(authMethods, cryptossh.PublicKeys(signer)) case "both": // Try password first if c.host.Auth.Password != "" { authMethods = append(authMethods, cryptossh.Password(c.host.Auth.Password)) } // Then try key signer, err := c.getKeySigner() if err == nil { authMethods = append(authMethods, cryptossh.PublicKeys(signer)) } default: return nil, fmt.Errorf("unsupported authentication type: %s", c.host.Auth.Type) } if len(authMethods) == 0 { return nil, fmt.Errorf("no authentication methods configured") } return authMethods, nil } // getKeySigner returns an SSH signer for key-based authentication // // Phase 1: Loads key from KeyID as a file path, or from common SSH key locations // Phase 2: Will integrate with the storage layer for encrypted key storage func (c *Client) getKeySigner() (cryptossh.Signer, error) { var keyPath string if c.host.Auth.KeyID != "" { // If KeyID looks like a path, use it directly if strings.HasPrefix(c.host.Auth.KeyID, "/") || strings.HasPrefix(c.host.Auth.KeyID, "~") { keyPath = expandPath(c.host.Auth.KeyID) } else { // Try ~/.ssh/ home, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get home directory: %w", err) } keyPath = filepath.Join(home, ".ssh", c.host.Auth.KeyID) } } else { // Fall back to default key locations keyPath = getDefaultKeyPath() } // Read the key file keyData, err := os.ReadFile(keyPath) //nolint:gosec if err != nil { return nil, fmt.Errorf("failed to read key file %s: %w", keyPath, err) } // Parse the key (support passphrase-protected keys in Phase 2) var signer cryptossh.Signer if c.host.Auth.Password != "" { signer, err = cryptossh.ParsePrivateKeyWithPassphrase(keyData, []byte(c.host.Auth.Password)) } else { signer, err = cryptossh.ParsePrivateKey(keyData) } if err != nil { return nil, fmt.Errorf("failed to parse private key: %w", err) } return signer, nil } // getDefaultKeyPath returns the first existing default SSH key path func getDefaultKeyPath() string { home, err := os.UserHomeDir() if err != nil { return "" } candidates := []string{ "id_ed25519", "id_rsa", "id_ecdsa", "id_dsa", } for _, name := range candidates { path := filepath.Join(home, ".ssh", name) if _, err := os.Stat(path); err == nil { return path } } return "" } // expandPath expands ~ to the home directory func expandPath(path string) string { if strings.HasPrefix(path, "~/") { home, err := os.UserHomeDir() if err == nil { return filepath.Join(home, path[2:]) } } return path }