diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b31bf8d..3d61e56 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: nixPath: - - nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-22.05.tar.gz + - nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-22.11.tar.gz - nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixpkgs-unstable.tar.gz os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 0407f86..ca43f2d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,13 @@ $ cat key.txt AGE-SECRET-KEY-1K3VN4N03PTHJWSJSCCMQCN33RY5FSKQPJ4KRRTG3JMQUYE0TUSEQEDH6V8 ``` +If you private key is encrypted, you can export the password in `SSH_TO_AGE_PASSPHRASE` + +``` console +$ read -s SSH_TO_AGE_PASSPHRASE; export SSH_TO_AGE_PASSPHRASE +$ ssh-to-age -private-key -i $HOME/.ssh/id_ed25519 -o key.txt +``` + - Exports the public key: ```console diff --git a/cmd/ssh-to-age/main.go b/cmd/ssh-to-age/main.go index 674cbf3..b034ee9 100644 --- a/cmd/ssh-to-age/main.go +++ b/cmd/ssh-to-age/main.go @@ -4,11 +4,12 @@ import ( "errors" "flag" "fmt" - sshage "github.com/Mic92/ssh-to-age" "io" "io/ioutil" "os" "strings" + + sshage "github.com/Mic92/ssh-to-age" ) type options struct { @@ -63,9 +64,15 @@ func convertKeys(args []string) error { } defer writer.Close() } - if opts.privateKey { - key, _, err := sshage.SSHPrivateKeyToAge(sshKey) + var ( + key *string + err error + ) + + keyPassphrase := os.Getenv("SSH_TO_AGE_PASSPHRASE") + + key, _, err = sshage.SSHPrivateKeyToAge(sshKey, []byte(keyPassphrase)) if err != nil { return fmt.Errorf("failed to convert '%s': %w", sshKey, err) } diff --git a/cmd/ssh-to-age/main_test.go b/cmd/ssh-to-age/main_test.go index 70bdf29..de83d6e 100644 --- a/cmd/ssh-to-age/main_test.go +++ b/cmd/ssh-to-age/main_test.go @@ -64,15 +64,15 @@ func TestSshKeyScan(t *testing.T) { file, err := os.Open(out) ok(t, err) - defer file.Close() + defer file.Close() scanner := bufio.NewScanner(file) - for scanner.Scan() { + for scanner.Scan() { pubKey := strings.TrimSuffix(scanner.Text(), "\n") fmt.Printf("scanned key: %s\n", pubKey) _, err = age.ParseX25519Recipient(pubKey) ok(t, err) - } + } ok(t, scanner.Err()) } @@ -92,3 +92,25 @@ func TestPrivateKey(t *testing.T) { _, err = age.ParseX25519Identity(privateKey) ok(t, err) } + +func TestPrivateKeyWithPassphrase(t *testing.T) { + tempdir := TempDir(t) + defer os.RemoveAll(tempdir) + out := path.Join(tempdir, "out") + + passphrase := "test" + + os.Setenv("SSH_TO_AGE_PASSPHRASE", passphrase) + defer os.Unsetenv("SSH_TO_AGE_PASSPHRASE") + + err := convertKeys([]string{"ssh-to-age", "-private-key", "-i", Asset("id_ed25519_passphrase"), "-o", out}) + ok(t, err) + + rawPrivateKey, err := ioutil.ReadFile(out) + privateKey := strings.TrimSuffix(string(rawPrivateKey), "\n") + ok(t, err) + + fmt.Printf("private key: %s\n", privateKey) + _, err = age.ParseX25519Identity(privateKey) + ok(t, err) +} diff --git a/cmd/ssh-to-age/test-assets/id_ed25519_passphrase b/cmd/ssh-to-age/test-assets/id_ed25519_passphrase new file mode 100644 index 0000000..9d08ed2 --- /dev/null +++ b/cmd/ssh-to-age/test-assets/id_ed25519_passphrase @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABC83l/B2p +MGlU+7xBT7wzeuAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAID/+AcTjBdIG6Xwk +4ZiuckvG5xNDlaqX316bKGo6D3a5AAAAkJA09klC9kTXa4VO1n4p3/J0ugw89MNS4eUn2b +4vbCPGrqZGZBU/Byu4A5g/Z03sGxGJj0GqnkC6I8aS2aTeQriNpdm10NaPVRL9dtL0//rp +NT/WAPFTUavHyBT16tmKKabyKHHf83QdtpbjckXkk8q1Xf8tBKYooZJcieo+22mrmq1Hha +JxU9TKx2Tc2RMymQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/cmd/ssh-to-age/test-assets/id_ed25519_passphrase.pub b/cmd/ssh-to-age/test-assets/id_ed25519_passphrase.pub new file mode 100644 index 0000000..faa863c --- /dev/null +++ b/cmd/ssh-to-age/test-assets/id_ed25519_passphrase.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID/+AcTjBdIG6Xwk4ZiuckvG5xNDlaqX316bKGo6D3a5 diff --git a/convert.go b/convert.go index 9624754..92478fd 100644 --- a/convert.go +++ b/convert.go @@ -1,9 +1,9 @@ package agessh import ( + "crypto" "crypto/ed25519" "crypto/sha512" - "crypto" "errors" "fmt" "reflect" @@ -57,8 +57,16 @@ func encodePublicKey(key crypto.PublicKey) (*string, error) { return &s, nil } -func SSHPrivateKeyToAge(sshKey []byte) (*string, *string, error) { - privateKey, err := ssh.ParseRawPrivateKey(sshKey) +func SSHPrivateKeyToAge(sshKey, passphrase []byte) (*string, *string, error) { + var ( + privateKey interface{} + err error + ) + if len(passphrase) > 0 { + privateKey, err = ssh.ParseRawPrivateKeyWithPassphrase(sshKey, passphrase) + } else { + privateKey, err = ssh.ParseRawPrivateKey(sshKey) + } if err != nil { return nil, nil, fmt.Errorf("failed to parse ssh private key: %w", err) } @@ -85,7 +93,6 @@ func SSHPrivateKeyToAge(sshKey []byte) (*string, *string, error) { return &s, pubKey, nil } - func SSHPublicKeyToAge(sshKey []byte) (*string, error) { var err error var pk ssh.PublicKey