Skip to content

Commit

Permalink
Use github.com/pquerna/otp to allow using the key period
Browse files Browse the repository at this point in the history
RELEASE_NOTES=[BUGFIX] Use OTP key period

Fixes #2276

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz committed Jul 18, 2022
1 parent 832d2cd commit b82cf9e
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 37 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ require (

require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/otp v1.3.0 // indirect
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 // indirect
github.com/rs/zerolog v1.27.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caspr-io/yamlpath v0.0.0-20200722075116-502e8d113a9b h1:2K3B6Xm7/lnhOugeGB3nIk50bZ9zhuJvXCEfUuL68ik=
github.com/caspr-io/yamlpath v0.0.0-20200722075116-502e8d113a9b/go.mod h1:4rP9T6iHCuPAIDKdNaZfTuuqSIoQQvFctNWIAUI1rlg=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
Expand Down Expand Up @@ -102,6 +104,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
Expand Down
12 changes: 9 additions & 3 deletions internal/action/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/gopasspw/gopass/pkg/otp"
"github.com/gopasspw/gopass/pkg/termio"
"github.com/mattn/go-tty"
"github.com/pquerna/otp/totp"
"github.com/urfave/cli/v2"
)

Expand Down Expand Up @@ -108,11 +109,16 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bo
return nil
default:
}
two, label, err := otp.Calculate(name, sec)

two, err := otp.Calculate(name, sec)
if err != nil {
return exit.Error(exit.Unknown, err, "No OTP entry found for %s: %s", name, err)
}
token := two.OTP()

token, err := totp.GenerateCode(two.Secret(), time.Now())
if err != nil {
return exit.Error(exit.Unknown, err, "Failed to compute OTP token for %s: %s", name, err)
}

now := time.Now()
expiresAt := now.Add(otpPeriod * time.Second).Truncate(otpPeriod * time.Second)
Expand Down Expand Up @@ -145,7 +151,7 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bo
}

if qrf != "" {
return otp.WriteQRFile(two, label, qrf)
return otp.WriteQRFile(two, qrf)
}

// let us wait until next OTP code:.
Expand Down
53 changes: 19 additions & 34 deletions pkg/otp/otp.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package otp

import (
"bytes"
"fmt"
"image/png"
"os"
"strings"

"github.com/gokyle/twofactor"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/gopass"
"github.com/pquerna/otp"
)

// Calculate will compute a OTP code from a given secret.
//nolint:ireturn
func Calculate(name string, sec gopass.Secret) (twofactor.OTP, string, error) {
func Calculate(name string, sec gopass.Secret) (*otp.Key, error) {
otpURL, found := sec.Get("otpauth")
if found && strings.HasPrefix(otpURL, "//") {
otpURL = "otpauth:" + otpURL
Expand All @@ -31,12 +33,10 @@ func Calculate(name string, sec gopass.Secret) (twofactor.OTP, string, error) {
if otpURL != "" {
debug.Log("found otpauth url: %s", out.Secret(otpURL))

return twofactor.FromURL(otpURL) //nolint:wrapcheck
return otp.NewKeyFromURL(otpURL) //nolint:wrapcheck
}

// check yaml entry and fall back to password if we don't have one
label := name

secKey, found := sec.Get("totp")
if !found {
debug.Log("no totp secret found, falling back to password")
Expand All @@ -45,47 +45,32 @@ func Calculate(name string, sec gopass.Secret) (twofactor.OTP, string, error) {
}

if strings.HasPrefix(secKey, "otpauth://") {
return twofactor.FromURL(secKey) //nolint:wrapcheck
return otp.NewKeyFromURL(otpURL) //nolint:wrapcheck
}

otp, err := twofactor.NewGoogleTOTP(twofactor.Pad(secKey))
// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
key, err := otp.NewKeyFromURL(fmt.Sprintf("otpauth://totp/new?secret=%s&issuer=gopass", secKey))
if err != nil {
return otp, label, fmt.Errorf("invalid OTP secret %q: %w", secKey, err)
return nil, fmt.Errorf("invalid OTP secret %q: %w", secKey, err)
}

return otp, label, nil
return key, nil
}

// WriteQRFile writes the given OTP code as a QR image to disk.
func WriteQRFile(otp twofactor.OTP, label, file string) error {
var qr []byte

var err error

switch otp.Type() {
case twofactor.OATH_HOTP:
hotp, ok := otp.(*twofactor.HOTP)
if !ok {
return fmt.Errorf("Type assertion failed on twofactor.HOTP: %w", ErrType)
}

qr, err = hotp.QR(label)
case twofactor.OATH_TOTP:
totp, ok := otp.(*twofactor.TOTP)
if !ok {
return fmt.Errorf("Type assertion failed on twofactor.TOTP: %w", ErrType)
}

qr, err = totp.QR(label)
default:
err = ErrOathOTP
func WriteQRFile(key *otp.Key, file string) error {
// Convert TOTP key into a QR code encoded as a PNG image.
var buf bytes.Buffer
img, err := key.Image(200, 200)
if err != nil {
return fmt.Errorf("failed to encode qr code: %w", err)
}

if err != nil {
return fmt.Errorf("failed to write qr file: %w", err)
if err := png.Encode(&buf, img); err != nil {
return fmt.Errorf("failed to encode as png: %w", err)
}

if err := os.WriteFile(file, qr, 0o600); err != nil {
if err := os.WriteFile(file, buf.Bytes(), 0o600); err != nil {
return fmt.Errorf("failed to write QR code: %w", err)
}

Expand Down

0 comments on commit b82cf9e

Please sign in to comment.