aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--_sys/etc/bash_completion.d/gotp4
-rw-r--r--cli.go46
-rw-r--r--cli_test.go72
-rw-r--r--cmd/gotp/main.go32
-rw-r--r--testdata/cli_SetPrivateKey_test.txt22
-rw-r--r--testdata/keys/rsa-openssh.pem38
-rw-r--r--testdata/keys/rsa-openssh.pem.pub1
8 files changed, 211 insertions, 9 deletions
diff --git a/README.md b/README.md
index 1d5de60..6c977bc 100644
--- a/README.md
+++ b/README.md
@@ -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[@]}")
diff --git a/cli.go b/cli.go
index a381b85..46a6d7e 100644
--- a/cli.go
+++ b/cli.go
@@ -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