diff options
| author | Shulhan <ms@kilabit.info> | 2023-05-16 00:06:39 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2023-05-16 00:06:39 +0700 |
| commit | 6a9e476354d23da316a94d0d5a7f8b14a273910e (patch) | |
| tree | 2b3cd9df782fa41edde4b3282b7375825684be40 | |
| parent | 6fd1ef57498aad5b73808f2b2260ccfe76ae649c (diff) | |
| download | gotp-6a9e476354d23da316a94d0d5a7f8b14a273910e.tar.xz | |
all: add command to set private key
The set-private-key command encrypt the issuer's value (hash:secret...)
in the file using private key.
The supported private key is RSA.
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | _sys/etc/bash_completion.d/gotp | 4 | ||||
| -rw-r--r-- | cli.go | 46 | ||||
| -rw-r--r-- | cli_test.go | 72 | ||||
| -rw-r--r-- | cmd/gotp/main.go | 32 | ||||
| -rw-r--r-- | testdata/cli_SetPrivateKey_test.txt | 22 | ||||
| -rw-r--r-- | testdata/keys/rsa-openssh.pem | 38 | ||||
| -rw-r--r-- | testdata/keys/rsa-openssh.pem.pub | 1 |
8 files changed, 211 insertions, 9 deletions
@@ -47,6 +47,11 @@ remove <LABEL> rename <LABEL> <NEW-LABEL> Rename a LABEL into NEW-LABEL. + +set-private-key <PRIVATE-KEY-FILE> + + Encrypt the issuer's value (hash:secret...) in the file using private key. + The supported private key is RSA. ``` ## ENCRYPTION diff --git a/_sys/etc/bash_completion.d/gotp b/_sys/etc/bash_completion.d/gotp index b330a20..3e4bdf2 100644 --- a/_sys/etc/bash_completion.d/gotp +++ b/_sys/etc/bash_completion.d/gotp @@ -15,7 +15,7 @@ suggest_key() { _gotp_completions() { - commands=("add" "gen" "import" "list" "remove" "rename") + commands=("add" "gen" "import" "list" "remove" "rename" "set-private-key") local len=${#COMP_WORDS[@]} local cmd=${COMP_WORDS[1]} @@ -43,6 +43,8 @@ _gotp_completions() suggest_key "$key" fi ;; + set-private-key) + ;; *) if [[ -z $cmd ]]; then COMPREPLY=("${commands[@]}") @@ -306,6 +306,52 @@ func (cli *Cli) Rename(label, newLabel string) (err error) { return nil } +// SetPrivateKey encrypt all the OTP secret using the private key. +// The only accepted private key is RSA. +func (cli *Cli) SetPrivateKey(privateKeyFile string) (err error) { + var ( + logp = `SetPrivateKey` + oldIssuers = cli.cfg.Issuers + oldPrivateKey = cli.cfg.privateKey + ) + + cli.cfg.privateKey, err = loadPrivateKey(privateKeyFile, nil) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + var ( + issuer *Issuer + label string + raw string + ) + + cli.cfg.Issuers = map[string]string{} + + for label, raw = range oldIssuers { + // Decrypt the old issuer using old private key. + issuer, err = NewIssuer(label, raw, oldPrivateKey) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + // Add it to the config back using new private key. + err = cli.cfg.add(issuer) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + } + + cli.cfg.PrivateKey = privateKeyFile + + err = cli.cfg.save() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + return nil +} + func (cli *Cli) add(issuer *Issuer) (err error) { err = issuer.validate() if err != nil { diff --git a/cli_test.go b/cli_test.go index c91d11e..a900ede 100644 --- a/cli_test.go +++ b/cli_test.go @@ -4,6 +4,7 @@ package gotp import ( + "bytes" "fmt" "os" "testing" @@ -137,3 +138,74 @@ private_key = test.Assert(t, cli.cfg.file, c.expConfig, string(got)) } } + +func TestCli_SetPrivateKey(t *testing.T) { + var ( + tdata *test.Data + err error + ) + + tdata, err = test.LoadData(`testdata/cli_SetPrivateKey_test.txt`) + if err != nil { + t.Fatal(err) + } + + var ( + cli = &Cli{} + rawConfig []byte + cfg *config + ) + + rawConfig = tdata.Input[`config.ini.before`] + + cfg, err = loadConfig(rawConfig) + if err != nil { + t.Fatal(err) + } + cli.cfg = cfg + + // Set the private key. + + err = cli.SetPrivateKey(tdata.Flag[`private_key_openssl`]) + if err != nil { + t.Fatal(err) + } + + // Change the private key. + + err = cli.SetPrivateKey(tdata.Flag[`private_key_openssl`]) + if err != nil { + t.Fatal(err) + } + + rawConfig, err = cli.cfg.MarshalText() + if err != nil { + t.Fatal(err) + } + + // Load the encrypted raw config and compare the issuer. + + cfg, err = loadConfig(rawConfig) + if err != nil { + t.Fatal(err) + } + cli.cfg = cfg + + var ( + gotLabels []string = cli.List() + label string + issuer *Issuer + got bytes.Buffer + ) + + for _, label = range gotLabels { + issuer, err = cli.cfg.get(label) + if err != nil { + t.Fatal(err) + } + + fmt.Fprintf(&got, "%s = %s\n", label, issuer.String()) + } + + test.Assert(t, `get all labels`, string(tdata.Output[`issuers`]), got.String()) +} diff --git a/cmd/gotp/main.go b/cmd/gotp/main.go index cf7a2a4..466818f 100644 --- a/cmd/gotp/main.go +++ b/cmd/gotp/main.go @@ -17,14 +17,15 @@ import ( ) const ( - cmdName = `gotp` - cmdAdd = `add` - cmdGenerate = `gen` - cmdImport = `import` - cmdList = `list` - cmdRemove = `remove` - cmdRename = `rename` - cmdVersion = `version` + cmdName = `gotp` + cmdAdd = `add` + cmdGenerate = `gen` + cmdImport = `import` + cmdList = `list` + cmdRemove = `remove` + cmdRename = `rename` + cmdSetPrivateKey = `set-private-key` + cmdVersion = `version` ) func main() { @@ -80,6 +81,12 @@ func main() { os.Exit(1) } + case cmdSetPrivateKey: + if len(args) <= 1 { + log.Printf(`%s %s: missing parameters`, cmdName, cmd) + os.Exit(1) + } + case cmdVersion: fmt.Println(`gotp v`, gotp.Version) return @@ -108,6 +115,8 @@ func main() { doRemove(cli, args) case cmdRename: doRename(cli, args) + case cmdSetPrivateKey: + doSetPrivateKey(cli, args) } } @@ -219,3 +228,10 @@ func doRename(cli *gotp.Cli, args []string) { } fmt.Println(`OK`) } + +func doSetPrivateKey(cli *gotp.Cli, args []string) { + var err = cli.SetPrivateKey(args[1]) + if err != nil { + log.Fatalf(`%s: %s`, cmdName, err) + } +} diff --git a/testdata/cli_SetPrivateKey_test.txt b/testdata/cli_SetPrivateKey_test.txt new file mode 100644 index 0000000..1162ead --- /dev/null +++ b/testdata/cli_SetPrivateKey_test.txt @@ -0,0 +1,22 @@ +private_key_openssl: testdata/keys/rsa-openssl.pem +private_key_openssh: testdata/keys/rsa-openssh.pem + +Test setting private key from unencrypted configuration. + +>>> config.ini.before +[gotp "issuer"] +test1 = SHA1:a:6:30: +test2 = SHA1:b:6:30: +test3 = SHA1:c:6:30: +test4 = SHA1:d:6:30: +test5 = SHA1:e:6:30: + +[gotp] +private_key = + +<<< issuers +test1 = SHA1:a:6:30: +test2 = SHA1:b:6:30: +test3 = SHA1:c:6:30: +test4 = SHA1:d:6:30: +test5 = SHA1:e:6:30: diff --git a/testdata/keys/rsa-openssh.pem b/testdata/keys/rsa-openssh.pem new file mode 100644 index 0000000..d850f2d --- /dev/null +++ b/testdata/keys/rsa-openssh.pem @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +i2fy4+BK+aefXXAAAACm1zQGluc3Bpcm8= +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/rsa-openssh.pem.pub b/testdata/keys/rsa-openssh.pem.pub new file mode 100644 index 0000000..14edb2f --- /dev/null +++ b/testdata/keys/rsa-openssh.pem.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCncxSh09rltoGEjjo1lA6+3PE4BLEAxu/WJAE6D3TfVn+aQFV6KvURqa9ngRGQvvkleqCqczbeMvyrAR5Ai9utk6Mrpcy1WzKqVt7clbhAPC078vdVlo/6GILJ2JgOUREy6ZaqJzPzXSw1zsLbIwDDrixSkiRaTSm9yrGmSIEhPGYjBGeAFoSeJPe69VcChs47PBJVT2UemiMb/3wVL2Kl0VjVA4EOK+aLx6LCh6x90vkvaWDDWWXWW1Rt9yFcd224wbKavvv6DxKK9KJ8ZDsVts/ByZPkEbDfwT3IwAgAdjrKcKvOuxRE9hb5vLfA28p9VlgRKOpqc1UtYQyTYUnhSP+inKm7xg55bk+vhdkJ+PAqqWeMSI/oGyxsekY/E8A0IFEqXkXqp7Mxj8Zn+sAS7L6k/E7JYHCP/rETpjaEurtq+KkulSRxm2JxLT0nVPdZDPckZsHLnZ/d468o4Yh8WPXY5zxddl5chkjBZc8kfEUEtpOBb36CXNfptlgCOfs= ms@inspiro |
