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:
35
gauth.go
35
gauth.go
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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
1
go.mod
@ -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
2
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-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=
|
||||||
|
Reference in New Issue
Block a user