Skip to content

Commit

Permalink
Implement digits and algorithm parameter parsing
Browse files Browse the repository at this point in the history
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz committed Aug 2, 2022
1 parent 2a136c8 commit d19479a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 11 deletions.
81 changes: 75 additions & 6 deletions internal/action/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"

"github.com/gopasspw/gopass/internal/action/exit"
Expand Down Expand Up @@ -100,6 +103,13 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bo
go waitForKeyPress(ctx, cancel)
}

// only used for the HOTP case as a fallback
var counter uint64 = 1
if sv, found := sec.Get("counter"); found && sv != "" {
if iv, err := strconv.ParseUint(sv, 10, 64); iv != 0 && err == nil {
counter = iv
}
}
for {
select {
case <-ctx.Done():
Expand All @@ -118,21 +128,25 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bo
token, err = totp.GenerateCodeCustom(two.Secret(), time.Now(), totp.ValidateOpts{
Period: uint(two.Period()),
Skew: 1,
Digits: potp.DigitsSix,
Algorithm: potp.AlgorithmSHA1,
Digits: parseDigits(two.URL()),
Algorithm: parseAlgorithm(two.URL()),
})
if err != nil {
return exit.Error(exit.Unknown, err, "Failed to compute OTP token for %s: %s", name, err)
}
case "hotp":
// TODO: Counter shouldn't be fixed.
token, err = hotp.GenerateCodeCustom(two.Secret(), 1, hotp.ValidateOpts{
Digits: potp.DigitsSix,
Algorithm: potp.AlgorithmSHA1,
token, err = hotp.GenerateCodeCustom(two.Secret(), counter, hotp.ValidateOpts{
Digits: parseDigits(two.URL()),
Algorithm: parseAlgorithm(two.URL()),
})
if err != nil {
return exit.Error(exit.Unknown, err, "Failed to compute OTP token for %s: %s", name, err)
}
counter++
sec.Set("counter", counter)
if err := s.Store.Set(ctx, name, sec); err != nil {
out.Errorf(ctx, "Failed to persist counter value: %s", err)
}
}

now := time.Now()
Expand Down Expand Up @@ -205,3 +219,58 @@ func (s *Action) otpHandleError(ctx context.Context, name, qrf string, clip, pw,

return nil
}

// parseDigits and parseAlgorithm can be replaced if https://github.com/pquerna/otp/pull/74 is merged.
func parseDigits(ku string) potp.Digits {
u, err := url.Parse(ku)
if err != nil {
debug.Log("Failed to parse key URL: %s", err)

// return the most common value
return potp.DigitsSix
}

q := u.Query()
iv, err := strconv.ParseUint(q.Get("digits"), 10, 64)
if err != nil {
debug.Log("Failed to parse digits param: %s", err)

// return the most common value
return potp.DigitsSix
}

switch iv {
case 6:
return potp.DigitsSix
case 8:
return potp.DigitsEight
default:
debug.Log("Unsupported digits value: %d", iv)

// return the most common value
return potp.DigitsSix
}
}

func parseAlgorithm(ku string) potp.Algorithm {
u, err := url.Parse(ku)
if err != nil {
debug.Log("Failed to parse key URL: %s", err)

// return the most common value
return potp.AlgorithmSHA1
}

q := u.Query()
a := strings.ToLower(q.Get("algorithm"))
switch a {
case "md5":
return potp.AlgorithmMD5
case "sha256":
return potp.AlgorithmSHA256
case "sha512":
return potp.AlgorithmSHA512
default:
return potp.AlgorithmSHA1
}
}
19 changes: 14 additions & 5 deletions pkg/otp/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ func Calculate(name string, sec gopass.Secret) (*otp.Key, error) {
}

// check yaml entry and fall back to password if we don't have one
secKey, found := sec.Get("totp")
if !found {
debug.Log("no totp secret found, falling back to password")

secKey = sec.Password()
// TOTP
if secKey, found := sec.Get("totp"); found {
return parseOTP("totp", secKey)
}

// HOTP
if secKey, found := sec.Get("hotp"); found {
return parseOTP("hotp", secKey)
}

debug.Log("no totp secret found, falling back to password")
return parseOTP("totp", sec.Password())
}

func parseOTP(typ string, secKey string) (*otp.Key, error) {
if strings.HasPrefix(secKey, "otpauth://") {
debug.Log("parsing otpauth:// URL %q", out.Secret(secKey))

Expand All @@ -58,7 +67,7 @@ func Calculate(name string, sec gopass.Secret) (*otp.Key, error) {
debug.Log("assembling otpauth URL from secret only (%q), using defaults", out.Secret(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))
key, err := otp.NewKeyFromURL(fmt.Sprintf("otpauth://%s/new?secret=%s&issuer=gopass", typ, secKey))
if err != nil {
debug.Log("failed to parse OTP: %s", out.Secret(secKey))

Expand Down

0 comments on commit d19479a

Please sign in to comment.