From 1da5637997e7971cbde77dbf642ea734fbb2f4cc Mon Sep 17 00:00:00 2001 From: Lena Date: Wed, 1 Jul 2026 00:00:00 +0000 Subject: keys: never write the plaintext private key to disk rsh -keygen printed the pubkey but wrote the key pair into a directory, so generating a key briefly left the plaintext private key on flash, contradicting the documented invariant that it only ever exists in memory. -keygen now emits the private key PEM on stdout and nothing else; the app encrypts it immediately and derives the public key via -pubkey, reusing the validated import path. Keygen failures now surface as an error dialog instead of crashing the app from a bare thread. --- rsh/main.go | 32 ++++++++++---------------------- rsh/transport_test.go | 17 ++++++++--------- 2 files changed, 18 insertions(+), 31 deletions(-) (limited to 'rsh') diff --git a/rsh/main.go b/rsh/main.go index 31029cb..0204a6b 100644 --- a/rsh/main.go +++ b/rsh/main.go @@ -1,7 +1,7 @@ // rsh is rsend's SSH transport. It is invoked three ways: // // rsh [-l USER] [USER@]HOST CMD... rsync remote shell (rsync -e), strict -// rsh -keygen DIR generate an ed25519 key, print the pubkey +// rsh -keygen generate an ed25519 key, print the private key (PEM) // rsh -pubkey print the pubkey for RSH_KEY_DATA/RSH_KEY // rsh -scan USER@HOST connect, print host-key fingerprint + line // @@ -29,7 +29,6 @@ import ( "io" "net" "os" - "path/filepath" "strconv" "strings" "time" @@ -55,10 +54,10 @@ func run(args []string, in io.Reader, out, errw io.Writer) error { if len(args) >= 1 { switch args[0] { case "-keygen": - if len(args) != 2 { - return errors.New("usage: rsh -keygen DIR") + if len(args) != 1 { + return errors.New("usage: rsh -keygen") } - return keygen(args[1], out) + return keygen(out) case "-pubkey": if len(args) != 1 { return errors.New("usage: rsh -pubkey") @@ -138,10 +137,11 @@ func scan(target string, out io.Writer) error { return nil } -// keygen writes an ed25519 key pair into dir and prints the public key in -// authorized_keys format to out. -func keygen(dir string, out io.Writer) error { - pub, priv, err := ed25519.GenerateKey(rand.Reader) +// keygen generates an ed25519 key and prints the private key in PEM form to +// out. Nothing touches disk: the caller owns persistence (the app stores it +// encrypted) and derives the public key with -pubkey. +func keygen(out io.Writer) error { + _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return err } @@ -149,19 +149,7 @@ func keygen(dir string, out io.Writer) error { if err != nil { return err } - if err := os.WriteFile(filepath.Join(dir, "id_ed25519"), pem.EncodeToMemory(block), 0o600); err != nil { - return err - } - sshPub, err := ssh.NewPublicKey(pub) - if err != nil { - return err - } - authLine := ssh.MarshalAuthorizedKey(sshPub) - if err := os.WriteFile(filepath.Join(dir, "id_ed25519.pub"), authLine, 0o644); err != nil { - return err - } - _, err = out.Write(authLine) - return err + return pem.Encode(out, block) } // pubkey loads the private key (RSH_KEY_DATA or RSH_KEY) and prints its public diff --git a/rsh/transport_test.go b/rsh/transport_test.go index 562c4d2..7ff74b1 100644 --- a/rsh/transport_test.go +++ b/rsh/transport_test.go @@ -15,24 +15,23 @@ import ( "golang.org/x/crypto/ssh/knownhosts" ) -// genClientKey makes a client key via keygen and returns the private key path -// and the parsed public key. +// genClientKey makes a client key via keygen, writes it to a file for RSH_KEY, +// and returns the private key path and the corresponding public key. func genClientKey(t *testing.T) (keyPath string, pub ssh.PublicKey) { t.Helper() - dir := t.TempDir() - if err := keygen(dir, io.Discard); err != nil { + var buf bytes.Buffer + if err := keygen(&buf); err != nil { t.Fatal(err) } - keyPath = filepath.Join(dir, "id_ed25519") - pb, err := os.ReadFile(filepath.Join(dir, "id_ed25519.pub")) - if err != nil { + keyPath = filepath.Join(t.TempDir(), "id_ed25519") + if err := os.WriteFile(keyPath, buf.Bytes(), 0o600); err != nil { t.Fatal(err) } - pub, _, _, _, err = ssh.ParseAuthorizedKey(pb) + signer, err := ssh.ParsePrivateKey(buf.Bytes()) if err != nil { t.Fatal(err) } - return keyPath, pub + return keyPath, signer.PublicKey() } func writeKnownHosts(t *testing.T, host string, port int, hostKey ssh.PublicKey) string { -- cgit v1.2.3