From 2733a1b1c5aeeddd45a11ed1ba28c8c3b561f05c Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 21 Dec 2022 10:21:16 +0100 Subject: [PATCH 1/5] extract default user email from git config --- cmd/key_generate.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/cmd/key_generate.go b/cmd/key_generate.go index 87e3905..3d3623b 100644 --- a/cmd/key_generate.go +++ b/cmd/key_generate.go @@ -1,9 +1,11 @@ package cmd import ( + "bytes" stdcrypto "crypto" "errors" "fmt" + "os/exec" "os/user" "strings" "time" @@ -51,6 +53,11 @@ func (cmd KeyGenerate) run() error { if username == "" { return errors.New("--name is required") } + email := cmd.Email() + if email == "" { + return errors.New("--email is required") + } + alg := cmd.algorithm() if alg == 0 { return fmt.Errorf("unsupported key type: %v", cmd.ktype) @@ -68,7 +75,7 @@ func (cmd KeyGenerate) run() error { DefaultCompressionAlgo: packet.CompressionZLIB, } - entity, err := openpgp.NewEntity(username, cmd.comment, cmd.email, cfg) + entity, err := openpgp.NewEntity(username, cmd.comment, email, cfg) if err != nil { return fmt.Errorf("cannot create entity: %v", err) } @@ -96,6 +103,20 @@ func (cmd KeyGenerate) Username() string { return "" } +func (cmd KeyGenerate) Email() string { + if cmd.email != "" { + return cmd.email + } + c := exec.Command("git", "config", "user.email") + var stdout bytes.Buffer + c.Stdout = &stdout + err := c.Run() + if err == nil { + return strings.TrimSpace(stdout.String()) + } + return "" +} + func (cmd KeyGenerate) algorithm() packet.PublicKeyAlgorithm { switch strings.ToLower(cmd.ktype) { case "rsa": From c1ef1ee28974170cebe8b5d39ea07b9886e08c13 Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 21 Dec 2022 10:21:31 +0100 Subject: [PATCH 2/5] show bit size of key in key info --- cmd/key_info.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/key_info.go b/cmd/key_info.go index eaf025f..f785c2d 100644 --- a/cmd/key_info.go +++ b/cmd/key_info.go @@ -42,11 +42,13 @@ func (cmd KeyInfo) run() error { expiration := prim.CreationTime.Add(time.Duration(*ttl) * time.Second) expirationStr = expiration.Format(time.RFC3339) } + bits, _ := prim.BitLength() result := map[string]interface{}{ // basic key info "id": key.GetHexKeyID(), "fingerprint": key.GetFingerprint(), "algorithm": cmd.algorithm(key), + "bits": bits, "created_at": prim.CreationTime.Format(time.RFC3339), "expires_at": expirationStr, "fingerprints": key.GetSHA256Fingerprints(), From 2ad453b119ae2ad23a46c6fa88f2dbd11839ce59 Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 21 Dec 2022 11:46:24 +0100 Subject: [PATCH 3/5] support advanced duration --- cmd/key_generate.go | 61 ++++++++++++++++++++++++++++++++++++---- cmd/key_generate_test.go | 26 +++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/cmd/key_generate.go b/cmd/key_generate.go index 3d3623b..06b740f 100644 --- a/cmd/key_generate.go +++ b/cmd/key_generate.go @@ -7,6 +7,7 @@ import ( "fmt" "os/exec" "os/user" + "strconv" "strings" "time" @@ -23,9 +24,14 @@ type KeyGenerate struct { ktype string comment string bits int - ttl time.Duration + ttl string } +const ( + day = 24 * time.Hour + year = 8766 * time.Hour // average year is 365.25 days +) + func (cmd KeyGenerate) Command() *cobra.Command { c := &cobra.Command{ Use: "generate", @@ -42,9 +48,7 @@ func (cmd KeyGenerate) Command() *cobra.Command { c.Flags().StringVarP(&cmd.comment, "comment", "c", "", "a note to add to the key") c.Flags().StringVarP(&cmd.ktype, "type", "t", "rsa", "type of the key") c.Flags().IntVarP(&cmd.bits, "bits", "b", 4096, "size of RSA key in bits") - year := 24 * time.Hour * 365 * 2 - // TODO: use another duration parser to support days, months, and years - c.Flags().DurationVar(&cmd.ttl, "ttl", year, "validity period of the key") + c.Flags().StringVar(&cmd.ttl, "ttl", "1y", "validity period of the key") return c } @@ -57,6 +61,13 @@ func (cmd KeyGenerate) run() error { if email == "" { return errors.New("--email is required") } + if cmd.ttl == "" { + return errors.New("--ttl is required") + } + ttl, err := ParseDuration(cmd.ttl) + if err != nil { + return fmt.Errorf("cannot parse --ttl: %v", err) + } alg := cmd.algorithm() if alg == 0 { @@ -68,7 +79,7 @@ func (cmd KeyGenerate) run() error { cfg := &packet.Config{ Algorithm: alg, RSABits: cmd.bits, - KeyLifetimeSecs: uint32(cmd.ttl.Seconds()), + KeyLifetimeSecs: uint32(ttl.Seconds()), Time: crypto.GetTime, DefaultHash: stdcrypto.SHA256, DefaultCipher: packet.CipherAES256, @@ -135,3 +146,43 @@ func (cmd KeyGenerate) algorithm() packet.PublicKeyAlgorithm { return 0 } } + +func ParseDuration(ttl string) (time.Duration, error) { + t, err := time.Parse("2006-01-02", ttl) + if err == nil { + return t.Sub(time.Now()), nil + } + + var shift time.Duration + + // parse years + parts := strings.Split(ttl, "y") + if len(parts) == 2 { + years, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, fmt.Errorf("parse year: %v", err) + } + shift += time.Duration(years) * year + ttl = parts[1] + } + + // parse days + parts = strings.Split(ttl, "d") + if len(parts) == 2 { + days, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, fmt.Errorf("parse year: %v", err) + } + shift += time.Duration(days) * day + ttl = parts[1] + } + + if ttl == "" { + return shift, nil + } + d, err := time.ParseDuration(ttl) + if err != nil { + return 0, err + } + return d + shift, nil +} diff --git a/cmd/key_generate_test.go b/cmd/key_generate_test.go index a800028..e3965a0 100644 --- a/cmd/key_generate_test.go +++ b/cmd/key_generate_test.go @@ -3,7 +3,9 @@ package cmd_test import ( "bytes" "testing" + "time" + "github.com/life4/enc/cmd" "github.com/matryer/is" ) @@ -21,3 +23,27 @@ func TestKeyGenerate_IsRandom(t *testing.T) { out2 := CallHappy(t, "key generate", nil) is.True(!bytes.Equal(out1, out2)) } + +func TestParseDuration(t *testing.T) { + t.Parallel() + testCases := []struct { + given string + expected time.Duration + }{ + {"4h", 4 * time.Hour}, + {"5d", 5 * 24 * time.Hour}, + {"5d4h", (5*24 + 4) * time.Hour}, + {"2y", 2 * 8766 * time.Hour}, + {"4h20m", 4*time.Hour + 20*time.Minute}, + } + for _, tCase := range testCases { + tc := tCase + t.Run(tc.given, func(t *testing.T) { + is := is.New(t) + t.Parallel() + actual, err := cmd.ParseDuration(tc.given) + is.NoErr(err) + is.Equal(actual, tc.expected) + }) + } +} From 91273d1b3a8d105d1306b77644df817ce6b38e8e Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 21 Dec 2022 14:31:00 +0100 Subject: [PATCH 4/5] improve help text for ttl --- cmd/key_generate.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/key_generate.go b/cmd/key_generate.go index 06b740f..ce7d547 100644 --- a/cmd/key_generate.go +++ b/cmd/key_generate.go @@ -48,7 +48,10 @@ func (cmd KeyGenerate) Command() *cobra.Command { c.Flags().StringVarP(&cmd.comment, "comment", "c", "", "a note to add to the key") c.Flags().StringVarP(&cmd.ktype, "type", "t", "rsa", "type of the key") c.Flags().IntVarP(&cmd.bits, "bits", "b", 4096, "size of RSA key in bits") - c.Flags().StringVar(&cmd.ttl, "ttl", "1y", "validity period of the key") + c.Flags().StringVar( + &cmd.ttl, "ttl", "1y", + "validity period of the key. Can be a date (2020-12-30) or duration (4y30d, 24h)", + ) return c } From 328900cb4d445a5218c3c4416cbec244dda18acd Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 21 Dec 2022 14:36:12 +0100 Subject: [PATCH 5/5] allow empty name, email, and ttl --- cmd/key_generate.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/cmd/key_generate.go b/cmd/key_generate.go index ce7d547..0a8a3a3 100644 --- a/cmd/key_generate.go +++ b/cmd/key_generate.go @@ -57,16 +57,7 @@ func (cmd KeyGenerate) Command() *cobra.Command { func (cmd KeyGenerate) run() error { username := cmd.Username() - if username == "" { - return errors.New("--name is required") - } email := cmd.Email() - if email == "" { - return errors.New("--email is required") - } - if cmd.ttl == "" { - return errors.New("--ttl is required") - } ttl, err := ParseDuration(cmd.ttl) if err != nil { return fmt.Errorf("cannot parse --ttl: %v", err) @@ -151,6 +142,9 @@ func (cmd KeyGenerate) algorithm() packet.PublicKeyAlgorithm { } func ParseDuration(ttl string) (time.Duration, error) { + if ttl == "" { + return 0, nil + } t, err := time.Parse("2006-01-02", ttl) if err == nil { return t.Sub(time.Now()), nil