Skip to content

Commit

Permalink
Merge pull request #2 from life4/detect-email
Browse files Browse the repository at this point in the history
Detect email when generating key
  • Loading branch information
orsinium authored Dec 21, 2022
2 parents aabc9a9 + 328900c commit 4b36bb9
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 8 deletions.
85 changes: 77 additions & 8 deletions cmd/key_generate.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cmd

import (
"bytes"
stdcrypto "crypto"
"errors"
"fmt"
"os/exec"
"os/user"
"strconv"
"strings"
"time"

Expand All @@ -21,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",
Expand All @@ -40,17 +48,21 @@ 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. Can be a date (2020-12-30) or duration (4y30d, 24h)",
)
return c
}

func (cmd KeyGenerate) run() error {
username := cmd.Username()
if username == "" {
return errors.New("--name is required")
email := cmd.Email()
ttl, err := ParseDuration(cmd.ttl)
if err != nil {
return fmt.Errorf("cannot parse --ttl: %v", err)
}

alg := cmd.algorithm()
if alg == 0 {
return fmt.Errorf("unsupported key type: %v", cmd.ktype)
Expand All @@ -61,14 +73,14 @@ 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,
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)
}
Expand Down Expand Up @@ -96,6 +108,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":
Expand All @@ -114,3 +140,46 @@ func (cmd KeyGenerate) algorithm() packet.PublicKeyAlgorithm {
return 0
}
}

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
}

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
}
26 changes: 26 additions & 0 deletions cmd/key_generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cmd_test
import (
"bytes"
"testing"
"time"

"github.com/life4/enc/cmd"
"github.com/matryer/is"
)

Expand All @@ -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)
})
}
}
2 changes: 2 additions & 0 deletions cmd/key_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit 4b36bb9

Please sign in to comment.