1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
package otp
import (
"bytes"
"fmt"
"image/png"
"strings"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"github.com/smallstep/cli/command"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/flags"
"github.com/smallstep/cli/utils"
"github.com/urfave/cli"
)
func generateCommand() cli.Command {
return cli.Command{
Name: "generate",
Action: command.ActionFunc(generateAction),
Usage: "generate a one-time password",
UsageText: `**step crypto otp generate** [**--issuer**=<name>]
[**--account**=<user-name>] [**--period**=<seconds>] [**--length**=<size>]
[**--alg**=<alg>] [**--url**] [**--qr**]`,
Description: `**step crypto otp generate** does TOTP and HTOP`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "issuer, iss",
Usage: `Name of the issuing organization (e.g., smallstep.com)`,
},
cli.StringFlag{
Name: "account",
Usage: `Name of the user's account (e.g., a username or email
address)`,
},
cli.IntFlag{
Name: "period",
Usage: `Number of seconds a TOTP hash is valid. Defaults to 30
seconds.`,
Value: 30,
},
cli.IntFlag{
Name: "length, digits",
Usage: `Length of one-time passwords. Defaults to 6.`,
Value: 6,
},
cli.IntFlag{
Name: "secret-size",
Usage: `Size of generated TOTP secret. Defaults to 20.`,
Value: 20,
},
cli.StringFlag{
Name: "alg, algorithm",
Usage: `Algorithm to use for HMAC. Defaults to SHA1. Must be
one of: SHA1, SHA256, SHA512`,
Value: "SHA1",
},
cli.BoolFlag{
Name: "url",
Usage: `Output a TOTP Key URI. See
https://github.com/google/google-authenticator/wiki/Key-Uri-Format`,
},
cli.StringFlag{
Name: "qr",
Usage: `Write a QR code to the specified path`,
},
flags.Force,
},
}
}
func generateAction(ctx *cli.Context) error {
switch {
case len(ctx.String("issuer")) == 0:
return errs.RequiredFlag(ctx, "issuer")
case len(ctx.String("account")) == 0:
return errs.RequiredFlag(ctx, "account")
}
key, err := generate(ctx)
if err != nil {
return err
}
if ctx.IsSet("qr") {
filename := ctx.String("qr")
// Convert TOTP key into a PNG
var buf bytes.Buffer
img, err := key.Image(200, 200)
if err != nil {
return err
}
png.Encode(&buf, img)
if err := utils.WriteFile(filename, buf.Bytes(), 0644); err != nil {
return errs.FileError(err, filename)
}
}
if ctx.Bool("url") {
fmt.Println(key.String())
} else {
fmt.Println(key.Secret())
}
return nil
}
func algFromString(ctx *cli.Context, alg string) (otp.Algorithm, error) {
switch strings.ToUpper(alg) {
case "SHA1":
return otp.AlgorithmSHA1, nil
case "SHA256":
return otp.AlgorithmSHA256, nil
case "SHA512":
return otp.AlgorithmSHA512, nil
default:
return 0, errs.InvalidFlagValue(ctx, "alg", alg, "SHA1, SHA256, or SHA512")
}
}
func generate(ctx *cli.Context) (*otp.Key, error) {
alg, err := algFromString(ctx, ctx.String("alg"))
if err != nil {
return nil, err
}
return totp.Generate(totp.GenerateOpts{
Issuer: ctx.String("issuer"),
AccountName: ctx.String("account"),
Period: uint(ctx.Int("period")),
SecretSize: uint(ctx.Int("secret-size")),
Digits: otp.Digits(ctx.Int("length")),
Algorithm: alg,
})
}
|