From 20bef573bfdf348845d7185202493ca0a24a1c8f Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Thu, 5 May 2022 16:37:49 -0300 Subject: [PATCH 1/7] feat: melt from pipe Signed-off-by: Carlos A Becker --- cmd/melt/main.go | 16 ++++++++++------ cmd/melt/main_test.go | 13 ++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index 64d8150..de2c090 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -120,7 +120,11 @@ be used to rebuild your public and private keys.`, return err } - if err := restore(maybeFile(mnemonic), args[0], askNewPassphrase); err != nil { + bts, err := maybeFile(mnemonic) + if err != nil { + return err + } + if err := restore(string(bts), args[0], askNewPassphrase); err != nil { return err } @@ -165,18 +169,18 @@ func main() { } } -func maybeFile(s string) string { +func maybeFile(s string) ([]byte, error) { if s == "-" { bts, err := io.ReadAll(os.Stdin) if err == nil { - return string(bts) + return bts, nil } } bts, err := os.ReadFile(s) if err != nil { - return s + return []byte(s), nil } - return string(bts) + return bts, nil } func parsePrivateKey(bts, pass []byte) (interface{}, error) { @@ -189,7 +193,7 @@ func parsePrivateKey(bts, pass []byte) (interface{}, error) { } func backup(path string, pass []byte) (string, error) { - bts, err := os.ReadFile(path) + bts, err := maybeFile(path) if err != nil { return "", fmt.Errorf("could not read key: %w", err) } diff --git a/cmd/melt/main_test.go b/cmd/melt/main_test.go index 949567c..3212ae6 100644 --- a/cmd/melt/main_test.go +++ b/cmd/melt/main_test.go @@ -181,17 +181,24 @@ func TestMaybeFile(t *testing.T) { path := filepath.Join(t.TempDir(), "f") content := "test content" is.NoErr(os.WriteFile(path, []byte(content), 0o644)) // nolint: gomnd - is.Equal(content, maybeFile(path)) + + bts, err := maybeFile(path) + is.NoErr(err) + is.Equal(content, string(bts)) }) t.Run("not a file", func(t *testing.T) { is := is.New(t) - is.Equal("strings", maybeFile("strings")) + bts, err := maybeFile("strings") + is.NoErr(err) + is.Equal("strings", string(bts)) }) t.Run("stdin", func(t *testing.T) { is := is.New(t) - is.Equal("", maybeFile("-")) + bts, err := maybeFile("-") + is.NoErr(err) + is.Equal(0, len(bts)) }) } From d0e1b9ddb8bed470d75bf0a8a1f8c8890473d826 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Thu, 5 May 2022 16:38:41 -0300 Subject: [PATCH 2/7] Revert "feat: melt from pipe" This reverts commit 20bef573bfdf348845d7185202493ca0a24a1c8f. --- cmd/melt/main.go | 16 ++++++---------- cmd/melt/main_test.go | 13 +++---------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index de2c090..64d8150 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -120,11 +120,7 @@ be used to rebuild your public and private keys.`, return err } - bts, err := maybeFile(mnemonic) - if err != nil { - return err - } - if err := restore(string(bts), args[0], askNewPassphrase); err != nil { + if err := restore(maybeFile(mnemonic), args[0], askNewPassphrase); err != nil { return err } @@ -169,18 +165,18 @@ func main() { } } -func maybeFile(s string) ([]byte, error) { +func maybeFile(s string) string { if s == "-" { bts, err := io.ReadAll(os.Stdin) if err == nil { - return bts, nil + return string(bts) } } bts, err := os.ReadFile(s) if err != nil { - return []byte(s), nil + return s } - return bts, nil + return string(bts) } func parsePrivateKey(bts, pass []byte) (interface{}, error) { @@ -193,7 +189,7 @@ func parsePrivateKey(bts, pass []byte) (interface{}, error) { } func backup(path string, pass []byte) (string, error) { - bts, err := maybeFile(path) + bts, err := os.ReadFile(path) if err != nil { return "", fmt.Errorf("could not read key: %w", err) } diff --git a/cmd/melt/main_test.go b/cmd/melt/main_test.go index 3212ae6..949567c 100644 --- a/cmd/melt/main_test.go +++ b/cmd/melt/main_test.go @@ -181,24 +181,17 @@ func TestMaybeFile(t *testing.T) { path := filepath.Join(t.TempDir(), "f") content := "test content" is.NoErr(os.WriteFile(path, []byte(content), 0o644)) // nolint: gomnd - - bts, err := maybeFile(path) - is.NoErr(err) - is.Equal(content, string(bts)) + is.Equal(content, maybeFile(path)) }) t.Run("not a file", func(t *testing.T) { is := is.New(t) - bts, err := maybeFile("strings") - is.NoErr(err) - is.Equal("strings", string(bts)) + is.Equal("strings", maybeFile("strings")) }) t.Run("stdin", func(t *testing.T) { is := is.New(t) - bts, err := maybeFile("-") - is.NoErr(err) - is.Equal(0, len(bts)) + is.Equal("", maybeFile("-")) }) } From 830b79ee3a7bb6e11d42f86ef15e133f4f567221 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Thu, 5 May 2022 16:41:55 -0300 Subject: [PATCH 3/7] fix: better impl Signed-off-by: Carlos A Becker --- cmd/melt/main.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index 64d8150..e78644f 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -189,7 +189,13 @@ func parsePrivateKey(bts, pass []byte) (interface{}, error) { } func backup(path string, pass []byte) (string, error) { - bts, err := os.ReadFile(path) + var bts []byte + var err error + if path == "-" { + bts, err = io.ReadAll(os.Stdin) + } else { + bts, err = os.ReadFile(path) + } if err != nil { return "", fmt.Errorf("could not read key: %w", err) } From 56dc4d2c83a48ff155aa6234c2bf07d83e0a78e3 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Thu, 5 May 2022 22:34:15 -0300 Subject: [PATCH 4/7] feat: backup from pipe Signed-off-by: Carlos A Becker --- cmd/melt/main.go | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index e78644f..69b42b8 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -57,14 +57,19 @@ var ( Short: "Generate a seed phrase from an SSH key", Long: `melt generates a seed phrase from an SSH key. That phrase can be used to rebuild your public and private keys.`, - Args: coral.ExactArgs(1), + Args: coral.MaximumNArgs(1), SilenceUsage: true, RunE: func(cmd *coral.Command, args []string) error { if err := setLanguage(language); err != nil { return err } - mnemonic, err := backup(args[0], nil) + var keyPath string + if len(args) > 0 { + keyPath = args[0] + } + + mnemonic, err := backup(keyPath, nil) if err != nil { return err } @@ -166,19 +171,30 @@ func main() { } func maybeFile(s string) string { - if s == "-" { - bts, err := io.ReadAll(os.Stdin) - if err == nil { - return string(bts) - } + f, err := openFileOrStdin(s) + if err != nil { + return s } - bts, err := os.ReadFile(s) + defer f.Close() //nolint:errcheck + bts, err := io.ReadAll(f) if err != nil { return s } return string(bts) } +func openFileOrStdin(path string) (*os.File, error) { + if path == "-" { + return os.Stdin, nil + } + + if fi, _ := os.Stdin.Stat(); (fi.Mode() & os.ModeNamedPipe) != 0 { + return os.Stdin, nil + } + + return os.Open(path) +} + func parsePrivateKey(bts, pass []byte) (interface{}, error) { if len(pass) == 0 { // nolint: wrapcheck @@ -189,13 +205,12 @@ func parsePrivateKey(bts, pass []byte) (interface{}, error) { } func backup(path string, pass []byte) (string, error) { - var bts []byte - var err error - if path == "-" { - bts, err = io.ReadAll(os.Stdin) - } else { - bts, err = os.ReadFile(path) + f, err := openFileOrStdin(path) + if err != nil { + return "", fmt.Errorf("could not read key: %w", err) } + defer f.Close() //nolint:errcheck + bts, err := io.ReadAll(f) if err != nil { return "", fmt.Errorf("could not read key: %w", err) } From af403046c3af4b2d538377e8f3648591fd8113d7 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 6 May 2022 12:08:40 -0300 Subject: [PATCH 5/7] feat: restore to stdout Signed-off-by: Carlos A Becker --- cmd/melt/main.go | 42 ++++++++++++++++++++++++++++++------------ cmd/melt/main_test.go | 19 ++++++++++++++++--- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index 55cfffe..76ef3bd 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -125,13 +125,18 @@ be used to rebuild your public and private keys.`, return err } - if err := restore(maybeFile(mnemonic), args[0], askNewPassphrase); err != nil { - return err - } + switch args[0] { + case "-": + return restore(maybeFile(mnemonic), askNewPassphrase, restoreToWriter(cmd.OutOrStdout())) + default: + if err := restore(maybeFile(mnemonic), askNewPassphrase, restoreToFiles(args[0])); err != nil { + return err + } - pub := keyPathStyle.Render(args[0]) - priv := keyPathStyle.Render(args[0] + ".pub") - fmt.Println(baseStyle.Render(fmt.Sprintf("\nSuccessfully restored keys to %s and %s", pub, priv))) + pub := keyPathStyle.Render(args[0]) + priv := keyPathStyle.Render(args[0] + ".pub") + fmt.Println(baseStyle.Render(fmt.Sprintf("\nSuccessfully restored keys to %s and %s", pub, priv))) + } return nil }, } @@ -245,7 +250,7 @@ func marshallPrivateKey(key ed25519.PrivateKey, pass []byte) (*pem.Block, error) return sshmarshal.MarshalPrivateKeyWithPassphrase(key, "", pass) } -func restore(mnemonic, path string, passFn func() ([]byte, error)) error { +func restore(mnemonic string, passFn func() ([]byte, error), outFn func(pem, pub []byte) error) error { pvtKey, err := melt.FromMnemonic(mnemonic) if err != nil { // nolint: wrapcheck @@ -267,14 +272,27 @@ func restore(mnemonic, path string, passFn func() ([]byte, error)) error { return fmt.Errorf("could not prepare public key: %w", err) } - if err := os.WriteFile(path, pem.EncodeToMemory(block), 0o600); err != nil { // nolint: gomnd - return fmt.Errorf("failed to write private key: %w", err) + return outFn(pem.EncodeToMemory(block), ssh.MarshalAuthorizedKey(pubkey)) +} + +func restoreToWriter(w io.Writer) func(pem, _ []byte) error { + return func(pem, _ []byte) error { + _, err := fmt.Fprint(w, string(pem)) + return err } +} + +func restoreToFiles(path string) func(pem, pub []byte) error { + return func(pem, pub []byte) error { + if err := os.WriteFile(path, pem, 0o600); err != nil { // nolint: gomnd + return fmt.Errorf("failed to write private key: %w", err) + } - if err := os.WriteFile(path+".pub", ssh.MarshalAuthorizedKey(pubkey), 0o600); err != nil { // nolint: gomnd - return fmt.Errorf("failed to write public key: %w", err) + if err := os.WriteFile(path+".pub", pub, 0o600); err != nil { // nolint: gomnd + return fmt.Errorf("failed to write public key: %w", err) + } + return nil } - return nil } func getWidth(max int) int { diff --git a/cmd/melt/main_test.go b/cmd/melt/main_test.go index 949567c..31085e9 100644 --- a/cmd/melt/main_test.go +++ b/cmd/melt/main_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/sha256" "encoding/hex" "os" @@ -68,7 +69,7 @@ func TestBackupRestoreKnownKey(t *testing.T) { t.Run("restore", func(t *testing.T) { is := is.New(t) path := filepath.Join(t.TempDir(), "key") - is.NoErr(restore(expectedMnemonic, path, staticPass(nil))) + is.NoErr(restore(expectedMnemonic, staticPass(nil), restoreToFiles(path))) is.Equal(expectedSum, sha256sum(t, path+".pub")) bts, err := os.ReadFile(path) @@ -80,11 +81,23 @@ func TestBackupRestoreKnownKey(t *testing.T) { is.Equal(expectedFingerprint, ssh.FingerprintSHA256(k.PublicKey())) }) + t.Run("restore to writer", func(t *testing.T) { + is := is.New(t) + + var b bytes.Buffer + is.NoErr(restore(expectedMnemonic, staticPass(nil), restoreToWriter(&b))) + + k, err := ssh.ParsePrivateKey([]byte(b.String())) + is.NoErr(err) + + is.Equal(expectedFingerprint, ssh.FingerprintSHA256(k.PublicKey())) + }) + t.Run("restore key with password", func(t *testing.T) { path := filepath.Join(t.TempDir(), "key") is := is.New(t) pass := staticPass([]byte("asd")) - is.NoErr(restore(expectedMnemonic, path, pass)) + is.NoErr(restore(expectedMnemonic, pass, restoreToFiles(path))) bts, err := os.ReadFile(path) is.NoErr(err) @@ -162,7 +175,7 @@ func TestBackupRestoreKnownKeyInJapanse(t *testing.T) { t.Run("restore", func(t *testing.T) { is := is.New(t) path := filepath.Join(t.TempDir(), "key") - is.NoErr(restore(expectedMnemonic, path, staticPass(nil))) + is.NoErr(restore(expectedMnemonic, staticPass(nil), restoreToFiles(path))) is.Equal(expectedSum, sha256sum(t, path+".pub")) bts, err := os.ReadFile(path) From d1f50d2be086142b0fe2447526066883265a082e Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 6 May 2022 12:11:17 -0300 Subject: [PATCH 6/7] chore: lint Signed-off-by: Carlos A Becker --- cmd/melt/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index 76ef3bd..6d7821c 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -197,7 +197,11 @@ func openFileOrStdin(path string) (*os.File, error) { return os.Stdin, nil } - return os.Open(path) + f, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("could not open %s: %w", path, err) + } + return f, nil } func parsePrivateKey(bts, pass []byte) (interface{}, error) { From 9964c2f50485acef53102080d8ed7cffbb4cd499 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 6 May 2022 12:12:21 -0300 Subject: [PATCH 7/7] chore: lint Signed-off-by: Carlos A Becker --- cmd/melt/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/melt/main.go b/cmd/melt/main.go index 6d7821c..3706a82 100644 --- a/cmd/melt/main.go +++ b/cmd/melt/main.go @@ -281,8 +281,10 @@ func restore(mnemonic string, passFn func() ([]byte, error), outFn func(pem, pub func restoreToWriter(w io.Writer) func(pem, _ []byte) error { return func(pem, _ []byte) error { - _, err := fmt.Fprint(w, string(pem)) - return err + if _, err := fmt.Fprint(w, string(pem)); err != nil { + return fmt.Errorf("could not write private key: %w", err) + } + return nil } }