-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
- Loading branch information
0 parents
commit 0387d3f
Showing
3 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |