gosh/utils.go
2026-01-11 18:47:54 -05:00

131 lines
3 KiB
Go

package main
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
)
func parseDestination(args []string) (user, host string) {
argsWithParams := map[string]bool{
"-b": true, "-c": true, "-D": true, "-E": true, "-e": true,
"-F": true, "-I": true, "-i": true, "-L": true, "-l": true,
"-m": true, "-O": true, "-o": true, "-p": true, "-R": true,
"-S": true, "-W": true, "-w": true,
}
skipNext := false
for _, arg := range args {
if skipNext {
skipNext = false
continue
}
if strings.HasPrefix(arg, "-") {
flag := arg
if len(arg) > 2 && !strings.Contains(arg, "=") {
flag = "-" + string(arg[len(arg)-1])
}
if argsWithParams[flag] {
skipNext = true
}
continue
}
parts := strings.SplitN(arg, "@", 2)
if len(parts) == 2 {
return parts[0], parts[1]
}
return "", parts[0] // No user specified, just host
}
return "", ""
}
func checkAndEncryptKey(pemData []byte) ([]byte, error) {
_, err := ssh.ParseRawPrivateKey(pemData)
if err != nil {
// Key is encrypted, so there is no more further action needed
if _, ok := err.(*ssh.PassphraseMissingError); ok {
return pemData, nil
}
return nil, err
}
tty, err := getTTY()
if err != nil {
fmt.Println("Warning: cannot open TTY for encryption prompt. Storing unencrypted.")
return pemData, nil
}
defer tty.Close()
ttyFd := int(tty.Fd())
fmt.Fprintln(tty, "Warning: This key is unencrypted.")
fmt.Fprint(tty, "Would you like to encrypt it before storing? (y/N): ")
var response string
fmt.Fscanln(tty, &response)
if strings.ToLower(response) != "y" {
return pemData, nil
}
fmt.Fprint(tty, "Enter new passphrase: ")
bytePass, err := term.ReadPassword(ttyFd)
fmt.Fprintln(tty)
if err != nil {
return nil, fmt.Errorf("failed to read passphrase: %w", err)
}
passphrase := string(bytePass)
fmt.Fprint(tty, "Confirm passphrase: ")
bytePassConfirm, err := term.ReadPassword(ttyFd)
if err != nil {
return nil, fmt.Errorf("fialed to read passphrase: %w", err)
}
fmt.Fprintln(tty)
if passphrase != string(bytePassConfirm) {
return nil, fmt.Errorf("passphrases do not match")
}
tempDir, err := os.MkdirTemp("", "gosh-encrypt")
if err != nil {
return nil, err
}
defer os.RemoveAll(tempDir)
tempKeyPath := filepath.Join(tempDir, "gosh_temp_key")
if err := os.WriteFile(tempKeyPath, pemData, 0600); err != nil {
return nil, err
}
cmd := exec.Command("ssh-keygen", "-p", "-f", tempKeyPath, "-P", "", "-N", passphrase, "-Z", "aes256-ctr")
if output, err := cmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("ssh-keygen failed: %s: %s", err, string(output))
}
encryptedData, err := os.ReadFile(tempKeyPath)
if err != nil {
return nil, err
}
fmt.Println("Key encrypted successfully (AES-256-CTR).")
return encryptedData, nil
}
func getTTY() (*os.File, error) {
return os.OpenFile("/dev/tty", os.O_RDWR, 0)
}
func readKeyFile(path string) ([]byte, error) {
if path == "-" {
return io.ReadAll(os.Stdin)
}
return os.ReadFile(path)
}