From 9d95eb95fd8d6d0ad4072589997cc0c22f90493d Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Sun, 2 Jun 2019 09:00:37 -0700 Subject: [PATCH] Consolidate and simplify the generation of codes. Rework gauth.Code as gauth.Codes, which returns the previous, current, and next strings in one step. Remove authCodeOrDie, since there is now only one decode step to check. The implementation now uses the bitbucket.org/creachadair/otp package, which makes the code simpler and subsumes normalizeSecret. --- gauth.go | 35 ++++++----------------------------- gauth/gauth.go | 44 +++++++++++++------------------------------- gauth/gauth_test.go | 4 ++-- go.mod | 1 + go.sum | 2 ++ 5 files changed, 24 insertions(+), 62 deletions(-) diff --git a/gauth.go b/gauth.go index 9df98dc..59df65f 100644 --- a/gauth.go +++ b/gauth.go @@ -73,40 +73,17 @@ func main() { } currentTS, progress := gauth.IndexNow() - prevTS := currentTS - 1 - nextTS := currentTS + 1 tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0) fmt.Fprintln(tw, "\tprev\tcurr\tnext") for _, record := range cfg { - name := record[0] - secret := normalizeSecret(record[1]) - prevToken := authCodeOrDie(secret, prevTS) - currentToken := authCodeOrDie(secret, currentTS) - nextToken := authCodeOrDie(secret, nextTS) - fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", name, prevToken, currentToken, nextToken) + name, secret := record[0], record[1] + prev, curr, next, err := gauth.Codes(secret, currentTS) + if err != nil { + log.Fatalf("Code: %v", err) + } + fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", name, prev, curr, next) } tw.Flush() fmt.Printf("[%-29s]\n", strings.Repeat("=", progress)) } - -// normalizeSecret cleans up whitespace and adds any missing padding to sec to -// use it as an OTP seed. -func normalizeSecret(sec string) string { - noPadding := strings.ToUpper(strings.Replace(sec, " ", "", -1)) - padLength := 8 - (len(noPadding) % 8) - if padLength < 8 { - return noPadding + strings.Repeat("=", padLength) - } - return noPadding -} - -// authCodeOrDie returns a code for the specified parameters, or aborts if an -// error occurred while generating the code. -func authCodeOrDie(sec string, ts int64) string { - str, e := gauth.Code(sec, ts) - if e != nil { - log.Fatal(e) - } - return str -} diff --git a/gauth/gauth.go b/gauth/gauth.go index 5976ec8..34df302 100644 --- a/gauth/gauth.go +++ b/gauth/gauth.go @@ -3,12 +3,9 @@ package gauth import ( - "crypto/hmac" - "crypto/sha1" - "encoding/base32" - "fmt" - "math/big" "time" + + "bitbucket.org/creachadair/otp" ) // IndexNow returns the current 30-second time slice index, and the number of @@ -18,31 +15,16 @@ func IndexNow() (int64, int) { return time / 30, int(time % 30) } -// Code returns the OTP code for the given secret at the specified time slice -// index. It will report an error if the secret is not valid Base32 or if HMAC -// generation fails. -func Code(sec string, ts int64) (string, error) { - key, err := base32.StdEncoding.DecodeString(sec) - if err != nil { - return "", err +// Codes returns the OTP codes for the given secret at the specified time slice +// and one slice on either side of it. It will report an error if the secret is +// not valid Base32. +func Codes(sec string, ts int64) (prev, curr, next string, _ error) { + var cfg otp.Config + if err := cfg.ParseKey(sec); err != nil { + return "", "", "", err } - enc := hmac.New(sha1.New, key) - msg := make([]byte, 8) - msg[0] = (byte)(ts >> (7 * 8) & 0xff) - msg[1] = (byte)(ts >> (6 * 8) & 0xff) - msg[2] = (byte)(ts >> (5 * 8) & 0xff) - msg[3] = (byte)(ts >> (4 * 8) & 0xff) - msg[4] = (byte)(ts >> (3 * 8) & 0xff) - msg[5] = (byte)(ts >> (2 * 8) & 0xff) - msg[6] = (byte)(ts >> (1 * 8) & 0xff) - msg[7] = (byte)(ts >> (0 * 8) & 0xff) - if _, err := enc.Write(msg); err != nil { - return "", err - } - hash := enc.Sum(nil) - offset := hash[19] & 0x0f - trunc := hash[offset : offset+4] - trunc[0] &= 0x7F - res := new(big.Int).Mod(new(big.Int).SetBytes(trunc), big.NewInt(1000000)) - return fmt.Sprintf("%06d", res), nil + prev = cfg.HOTP(uint64(ts - 1)) + curr = cfg.HOTP(uint64(ts)) + next = cfg.HOTP(uint64(ts + 1)) + return } diff --git a/gauth/gauth_test.go b/gauth/gauth_test.go index 4bed176..ffe343e 100644 --- a/gauth/gauth_test.go +++ b/gauth/gauth_test.go @@ -6,7 +6,7 @@ import ( "github.com/pcarrier/gauth/gauth" ) -func TestCode(t *testing.T) { +func TestCodes(t *testing.T) { tests := []struct { secret string index int64 @@ -20,7 +20,7 @@ func TestCode(t *testing.T) { {"blargh!", 123, "", true}, } for _, test := range tests { - got, err := gauth.Code(test.secret, test.index) + _, got, _, err := gauth.Codes(test.secret, test.index) if err != nil && !test.fail { t.Errorf("Code(%q, %d): unexpected error: %v", test.secret, test.index, err) } else if got != test.want { diff --git a/go.mod b/go.mod index 35caacb..b0eaf0c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/pcarrier/gauth go 1.12 require ( + bitbucket.org/creachadair/otp v0.0.2 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 // indirect ) diff --git a/go.sum b/go.sum index 808e7ec..ecd4f93 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +bitbucket.org/creachadair/otp v0.0.2 h1:0IAI6yUiapmEHt/x6sdmv8BnPRVOyWT/mPxqYkcYbF8= +bitbucket.org/creachadair/otp v0.0.2/go.mod h1:1RXxCIhHl/bxTZlSWONJzpCOi5uHZuwrDAkomlYrUGg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=