Skip to content

Commit

Permalink
first
Browse files Browse the repository at this point in the history
Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
  • Loading branch information
caarlos0 committed Mar 7, 2022
0 parents commit 0387d3f
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 0 deletions.
23 changes: 23 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module github.com/caarlos0/keybackup

go 1.17

require (
github.com/charmbracelet/lipgloss v0.5.0
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
github.com/muesli/coral v1.0.0
github.com/tyler-smith/go-bip39 v1.1.0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
)

require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)
47 changes: 47 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
github.com/muesli/coral v1.0.0 h1:odyqkoEg4aJAINOzvnjN4tUsdp+Zleccs7tRIAkkYzU=
github.com/muesli/coral v1.0.0/go.mod h1:bf91M/dkp7iHQw73HOoR9PekdTJMTD6ihJgWoDitde8=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
159 changes: 159 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package main

import (
"crypto/ed25519"
"encoding/pem"
"fmt"
"os"

"github.com/charmbracelet/lipgloss"
"github.com/mikesmitty/edkey"
"github.com/muesli/coral"
"github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/ssh"
)

var (
rootCmd = &coral.Command{
Use: "keys",
Short: "Backup and restore SSH keys using a mnemonic",
SilenceUsage: true,
}
keypath string
backupCmd = &coral.Command{
Use: "backup",
Short: "Backup a SSH private key",
RunE: func(cmd *coral.Command, args []string) error {
bts, err := os.ReadFile(keypath)
if err != nil {
return fmt.Errorf("could not read key: %w", err)
}

key, err := ssh.ParseRawPrivateKey(bts)
if err != nil {
return fmt.Errorf("could not parse key: %w", err)
}

var seed []byte
switch key := key.(type) {
case *ed25519.PrivateKey:
seed = key.Seed()
default:
return fmt.Errorf("unknown key type: %v", key)
}

words, err := bip39.NewMnemonic(seed)
if err != nil {
return fmt.Errorf("could not create a mnemonic for %s: %w", keypath, err)
}

fmt.Println(
lipgloss.NewStyle().
Align(lipgloss.Center).
Italic(true).
Render(`Success!
You can now use the words bellow to recreate your key usint the 'keys restore' command.
Store them somewhere safe, print or memorize them.`),
)
fmt.Println(
lipgloss.NewStyle().
Align(lipgloss.Center).
Bold(true).
Foreground(lipgloss.Color("63")).
Margin(1).
Width(60).
Render(words),
)

return nil
},
}

words string
restoreCmd = &coral.Command{
Use: "restore",
Short: "Recreate a key using the given mnemonic words",
RunE: func(cmd *coral.Command, args []string) error {
seed, err := bip39.EntropyFromMnemonic(words)
if err != nil {
return err
}

pvtKey := ed25519.NewKeyFromSeed(seed)
if err := os.WriteFile(
keypath,
pem.EncodeToMemory(&pem.Block{
Type: "OPENSSH PRIVATE KEY",
Bytes: edkey.MarshalED25519PrivateKey(pvtKey),
}),
0o600,
); err != nil {
return fmt.Errorf("failed to write private key: %w", err)
}

pubKey, err := ssh.NewPublicKey(pvtKey.Public())
if err != nil {
return fmt.Errorf("could not prepare public key: %w", err)
}

if err := os.WriteFile(
keypath+".pub",
ssh.MarshalAuthorizedKey(pubKey),
0o655,
); err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}

fmt.Println(lipgloss.NewStyle().Bold(true).Render(fmt.Sprintf("Written keys to %s and %[1]s.pub", keypath)))

return nil
},
}
)

func init() {
rootCmd.AddCommand(backupCmd, restoreCmd)

backupCmd.PersistentFlags().StringVarP(&keypath, "key", "k", "", "Path to the key you want to backup")
_ = backupCmd.MarkFlagRequired("key")

restoreCmd.PersistentFlags().StringVarP(&keypath, "key", "k", "", "Path to where you want to save the key")
restoreCmd.PersistentFlags().StringVarP(&words, "words", "w", "", "Mnemonic words given by the backup command")
_ = restoreCmd.MarkFlagRequired("key")
_ = restoreCmd.MarkFlagRequired("words")
}

func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// srcPem, err := os.ReadFile("test_ed25519")
// if err != nil {
// log.Fatalln(err)
// }
//
// srcKey, err := ssh.ParseRawPrivateKey(srcPem)
// if err != nil {
// log.Fatalln(err)
// }
//
// words, err := bip39.NewMnemonic((srcKey.(*ed25519.PrivateKey)).Seed())
// if err != nil {
// log.Fatalln(err)
// }
// log.Println("words:", words)
//
// recovSeed, err := bip39.EntropyFromMnemonic(words)
// if err != nil {
// log.Fatalln(err)
// }
//
// recovKey := ed25519.NewKeyFromSeed(recovSeed)
// recovPem := pem.EncodeToMemory(&pem.Block{
// Type: "OPENSSH PRIVATE KEY",
// Bytes: edkey.MarshalED25519PrivateKey(recovKey),
// })
//
// log.Println("keys match?", string(recovPem) == string(srcPem))
}

0 comments on commit 0387d3f

Please sign in to comment.