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.
This commit is contained in:
M. J. Fromberger
2019-06-02 09:00:37 -07:00
parent c217b384f0
commit 9d95eb95fd
5 changed files with 24 additions and 62 deletions

View File

@ -73,40 +73,17 @@ func main() {
} }
currentTS, progress := gauth.IndexNow() currentTS, progress := gauth.IndexNow()
prevTS := currentTS - 1
nextTS := currentTS + 1
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0) tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
fmt.Fprintln(tw, "\tprev\tcurr\tnext") fmt.Fprintln(tw, "\tprev\tcurr\tnext")
for _, record := range cfg { for _, record := range cfg {
name := record[0] name, secret := record[0], record[1]
secret := normalizeSecret(record[1]) prev, curr, next, err := gauth.Codes(secret, currentTS)
prevToken := authCodeOrDie(secret, prevTS) if err != nil {
currentToken := authCodeOrDie(secret, currentTS) log.Fatalf("Code: %v", err)
nextToken := authCodeOrDie(secret, nextTS) }
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", name, prevToken, currentToken, nextToken) fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", name, prev, curr, next)
} }
tw.Flush() tw.Flush()
fmt.Printf("[%-29s]\n", strings.Repeat("=", progress)) 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
}

View File

@ -3,12 +3,9 @@
package gauth package gauth
import ( import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"fmt"
"math/big"
"time" "time"
"bitbucket.org/creachadair/otp"
) )
// IndexNow returns the current 30-second time slice index, and the number of // 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) return time / 30, int(time % 30)
} }
// Code returns the OTP code for the given secret at the specified time slice // Codes returns the OTP codes 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 // and one slice on either side of it. It will report an error if the secret is
// generation fails. // not valid Base32.
func Code(sec string, ts int64) (string, error) { func Codes(sec string, ts int64) (prev, curr, next string, _ error) {
key, err := base32.StdEncoding.DecodeString(sec) var cfg otp.Config
if err != nil { if err := cfg.ParseKey(sec); err != nil {
return "", err return "", "", "", err
} }
enc := hmac.New(sha1.New, key) prev = cfg.HOTP(uint64(ts - 1))
msg := make([]byte, 8) curr = cfg.HOTP(uint64(ts))
msg[0] = (byte)(ts >> (7 * 8) & 0xff) next = cfg.HOTP(uint64(ts + 1))
msg[1] = (byte)(ts >> (6 * 8) & 0xff) return
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
} }

View File

@ -6,7 +6,7 @@ import (
"github.com/pcarrier/gauth/gauth" "github.com/pcarrier/gauth/gauth"
) )
func TestCode(t *testing.T) { func TestCodes(t *testing.T) {
tests := []struct { tests := []struct {
secret string secret string
index int64 index int64
@ -20,7 +20,7 @@ func TestCode(t *testing.T) {
{"blargh!", 123, "", true}, {"blargh!", 123, "", true},
} }
for _, test := range tests { 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 { if err != nil && !test.fail {
t.Errorf("Code(%q, %d): unexpected error: %v", test.secret, test.index, err) t.Errorf("Code(%q, %d): unexpected error: %v", test.secret, test.index, err)
} else if got != test.want { } else if got != test.want {

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/pcarrier/gauth
go 1.12 go 1.12
require ( require (
bitbucket.org/creachadair/otp v0.0.2
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 // indirect golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 // indirect
) )

2
go.sum
View File

@ -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-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 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=