Files
ssh-access-tui/internal/keymgr/keygen.go
T
2026-07-22 14:14:53 +03:00

67 lines
2.0 KiB
Go

// Package keymgr generates per-user SSH keypairs and packages them, together
// with a ready-to-use client config and Windows-oriented instructions, into
// a password-protected 7z archive for delivery.
//
// The private key only ever exists on disk for the lifetime of one export
// operation: generated into a temp directory, packed into the archive, then
// securely wiped (see Wipe in pack.go). It is never written into the state
// store and therefore never ends up in a backup.
package keymgr
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
// KeyPair is the result of generating one user's SSH keypair.
type KeyPair struct {
PrivateKeyPath string
PublicKeyLine string // full "ssh-ed25519 AAAA... comment" line for authorized_keys
Fingerprint string // "SHA256:xxxxx" as reported by ssh-keygen -lf
}
// GeneratePair creates a new passphrase-less ed25519 keypair for username in
// dir. The key itself has no passphrase because the archive it ships in is
// already encrypted (see pack.go); a second passphrase would only add
// friction without a corresponding threat it defends against once the
// archive's own encryption and the server-side no-shell restriction are in
// place.
func GeneratePair(dir, username string) (*KeyPair, error) {
priv := filepath.Join(dir, username+"_jump_key")
pub := priv + ".pub"
cmd := exec.Command("ssh-keygen",
"-t", "ed25519",
"-N", "", // no passphrase
"-C", username+"@jump",
"-f", priv,
)
if out, err := cmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("ssh-keygen: %w: %s", err, out)
}
pubData, err := os.ReadFile(pub)
if err != nil {
return nil, fmt.Errorf("read public key: %w", err)
}
fpOut, err := exec.Command("ssh-keygen", "-lf", pub).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("fingerprint: %w: %s", err, fpOut)
}
fields := strings.Fields(string(fpOut))
fingerprint := ""
if len(fields) >= 2 {
fingerprint = fields[1]
}
return &KeyPair{
PrivateKeyPath: priv,
PublicKeyLine: strings.TrimSpace(string(pubData)),
Fingerprint: fingerprint,
}, nil
}