aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/invalid/lena/rsend/Keys.kt13
-rw-r--r--app/src/main/java/invalid/lena/rsend/MainActivity.kt21
-rw-r--r--rsh/main.go32
-rw-r--r--rsh/transport_test.go17
4 files changed, 41 insertions, 42 deletions
diff --git a/app/src/main/java/invalid/lena/rsend/Keys.kt b/app/src/main/java/invalid/lena/rsend/Keys.kt
index 0a5384d..fa233f7 100644
--- a/app/src/main/java/invalid/lena/rsend/Keys.kt
+++ b/app/src/main/java/invalid/lena/rsend/Keys.kt
@@ -15,14 +15,13 @@ object Keys {
fun exists(ctx: Context): Boolean = keyEnc(ctx).exists()
- // generate creates the key pair, stores the private key encrypted, and
- // returns the public key in authorized_keys format.
+ // generate creates a key pair via rsh -keygen (the plaintext key never
+ // touches disk), stores it encrypted, and returns the public key in
+ // authorized_keys format. Storage and validation reuse importKey.
fun generate(ctx: Context): String {
- val tmp = File(ctx.cacheDir, "keygen").apply { mkdirs() }
- Native.run(Native.rsh(ctx), listOf("-keygen", tmp.absolutePath))
- keyEnc(ctx).writeBytes(KeyVault.encrypt(File(tmp, "id_ed25519").readBytes()))
- File(tmp, "id_ed25519.pub").copyTo(publicKey(ctx), overwrite = true)
- tmp.deleteRecursively()
+ val r = Native.run(Native.rsh(ctx), listOf("-keygen"))
+ if (r.code != 0) throw IllegalStateException(r.output.trim().ifEmpty { "keygen failed" })
+ importKey(ctx, r.output.toByteArray())
return publicKeyText(ctx)
}
diff --git a/app/src/main/java/invalid/lena/rsend/MainActivity.kt b/app/src/main/java/invalid/lena/rsend/MainActivity.kt
index 6b51252..ce65c2e 100644
--- a/app/src/main/java/invalid/lena/rsend/MainActivity.kt
+++ b/app/src/main/java/invalid/lena/rsend/MainActivity.kt
@@ -295,11 +295,24 @@ class MainActivity : AppCompatActivity() {
.setMessage(msg)
.setPositiveButton("Generate") { _, _ ->
thread {
- Keys.generate(this)
+ val error = try {
+ Keys.generate(this)
+ null
+ } catch (e: Exception) {
+ e.message ?: "keygen failed"
+ }
runOnUiThread {
- refresh()
- Toast.makeText(this, "New key generated.", Toast.LENGTH_SHORT).show()
- showKey()
+ if (error == null) {
+ refresh()
+ Toast.makeText(this, "New key generated.", Toast.LENGTH_SHORT).show()
+ showKey()
+ } else {
+ AlertDialog.Builder(this)
+ .setTitle("Keygen failed")
+ .setMessage(error)
+ .setPositiveButton("OK", null)
+ .show()
+ }
}
}
}
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 {