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()
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
}

View File

@ -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
}

View File

@ -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 {

1
go.mod
View File

@ -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
)

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