add missing files
new files are oath_test totp code
This commit is contained in:
30
oath_test.go
Normal file
30
oath_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package twofactor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var sha1Hmac = []byte{
|
||||
0x1f, 0x86, 0x98, 0x69, 0x0e,
|
||||
0x02, 0xca, 0x16, 0x61, 0x85,
|
||||
0x50, 0xef, 0x7f, 0x19, 0xda,
|
||||
0x8e, 0x94, 0x5b, 0x55, 0x5a,
|
||||
}
|
||||
|
||||
var truncExpect int64 = 0x50ef7f19
|
||||
|
||||
// This test runs through the truncation example given in the RFC.
|
||||
func TestTruncate(t *testing.T) {
|
||||
if result := truncate(sha1Hmac); result != truncExpect {
|
||||
fmt.Printf("hotp: expected truncate -> %d, saw %d\n",
|
||||
truncExpect, result)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
sha1Hmac[19]++
|
||||
if result := truncate(sha1Hmac); result == truncExpect {
|
||||
fmt.Println("hotp: expected truncation to fail")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
151
totp.go
Normal file
151
totp.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package twofactor
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base32"
|
||||
"hash"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TOTP struct {
|
||||
*oath
|
||||
step uint64
|
||||
}
|
||||
|
||||
func (otp *TOTP) Type() Type {
|
||||
return OATH_TOTP
|
||||
}
|
||||
|
||||
func (otp *TOTP) otp(counter uint64) string {
|
||||
return otp.oath.OTP(counter)
|
||||
}
|
||||
|
||||
func (otp *TOTP) OTP() string {
|
||||
return otp.otp(otp.OTPCounter())
|
||||
}
|
||||
|
||||
func (otp *TOTP) URL(label string) string {
|
||||
return otp.oath.URL(otp.Type(), label)
|
||||
}
|
||||
|
||||
func (otp *TOTP) SetProvider(provider string) {
|
||||
otp.provider = provider
|
||||
}
|
||||
|
||||
func (otp *TOTP) otpCounter(t uint64) uint64 {
|
||||
return (t - otp.counter) / otp.step
|
||||
}
|
||||
|
||||
func (otp *TOTP) OTPCounter() uint64 {
|
||||
return otp.otpCounter(uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash) *TOTP {
|
||||
h := hashFromAlgo(algo)
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &TOTP{
|
||||
oath: &oath{
|
||||
key: key,
|
||||
counter: start,
|
||||
size: digits,
|
||||
hash: h,
|
||||
algo: algo,
|
||||
},
|
||||
step: step,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func NewTOTPSHA1(key []byte, start uint64, step uint64, digits int) *TOTP {
|
||||
return NewTOTP(key, start, step, digits, crypto.SHA1)
|
||||
}
|
||||
|
||||
func NewTOTPSHA256(key []byte, start uint64, step uint64, digits int) *TOTP {
|
||||
return NewTOTP(key, start, step, digits, crypto.SHA256)
|
||||
}
|
||||
|
||||
func NewTOTPSHA512(key []byte, start uint64, step uint64, digits int) *TOTP {
|
||||
return NewTOTP(key, start, step, digits, crypto.SHA512)
|
||||
}
|
||||
|
||||
func hashFromAlgo(algo crypto.Hash) func() hash.Hash {
|
||||
switch algo {
|
||||
case crypto.SHA1:
|
||||
return sha1.New
|
||||
case crypto.SHA256:
|
||||
return sha256.New
|
||||
case crypto.SHA512:
|
||||
return sha512.New
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateGoogleTOTP produces a new TOTP token with the defaults expected by
|
||||
// Google Authenticator.
|
||||
func GenerateGoogleTOTP() *TOTP {
|
||||
key := make([]byte, sha1.Size)
|
||||
if _, err := io.ReadFull(PRNG, key); err != nil {
|
||||
return nil
|
||||
}
|
||||
return NewTOTP(key, 0, 30, 6, crypto.SHA1)
|
||||
}
|
||||
|
||||
func totpFromURL(u *url.URL) (*TOTP, string, error) {
|
||||
label := u.Path[1:]
|
||||
v := u.Query()
|
||||
|
||||
secret := v.Get("secret")
|
||||
if secret == "" {
|
||||
return nil, "", ErrInvalidURL
|
||||
}
|
||||
|
||||
var algo = crypto.SHA1
|
||||
if algorithm := v.Get("algorithm"); algorithm != "" {
|
||||
switch {
|
||||
case algorithm == "SHA256":
|
||||
algo = crypto.SHA256
|
||||
case algorithm == "SHA512":
|
||||
algo = crypto.SHA512
|
||||
case algorithm != "SHA1":
|
||||
return nil, "", ErrInvalidAlgo
|
||||
}
|
||||
}
|
||||
|
||||
var digits = 6
|
||||
if sdigit := v.Get("digits"); sdigit != "" {
|
||||
tmpDigits, err := strconv.ParseInt(sdigit, 10, 8)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
digits = int(tmpDigits)
|
||||
}
|
||||
|
||||
var period uint64 = 30
|
||||
if speriod := v.Get("period"); speriod != "" {
|
||||
var err error
|
||||
period, err = strconv.ParseUint(speriod, 10, 64)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
key, err := base32.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
otp := NewTOTP(key, 0, period, digits, algo)
|
||||
return otp, label, nil
|
||||
}
|
||||
|
||||
func (otp *TOTP) QR(label string) ([]byte, error) {
|
||||
return otp.oath.QR(otp.Type(), label)
|
||||
}
|
||||
55
totp_test.go
Normal file
55
totp_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package twofactor
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var rfcTotpKey = []byte("12345678901234567890")
|
||||
var rfcTotpStep uint64 = 30
|
||||
|
||||
var rfcTotpTests = []struct {
|
||||
Time uint64
|
||||
Code string
|
||||
T uint64
|
||||
Algo crypto.Hash
|
||||
}{
|
||||
{59, "94287082", 1, crypto.SHA1},
|
||||
{59, "46119246", 1, crypto.SHA256},
|
||||
{59, "90693936", 1, crypto.SHA512},
|
||||
{1111111109, "07081804", 37037036, crypto.SHA1},
|
||||
// {1111111109, "68084774", 37037036, crypto.SHA256},
|
||||
// {1111111109, "25091201", 37037036, crypto.SHA512},
|
||||
{1111111111, "14050471", 37037037, crypto.SHA1},
|
||||
// {1111111111, "67062674", 37037037, crypto.SHA256},
|
||||
// {1111111111, "99943326", 37037037, crypto.SHA512},
|
||||
{1234567890, "89005924", 41152263, crypto.SHA1},
|
||||
// {1234567890, "91819424", 41152263, crypto.SHA256},
|
||||
// {1234567890, "93441116", 41152263, crypto.SHA512},
|
||||
{2000000000, "69279037", 66666666, crypto.SHA1},
|
||||
// {2000000000, "90698825", 66666666, crypto.SHA256},
|
||||
// {2000000000, "38618901", 66666666, crypto.SHA512},
|
||||
{20000000000, "65353130", 666666666, crypto.SHA1},
|
||||
// {20000000000, "77737706", 666666666, crypto.SHA256},
|
||||
// {20000000000, "47863826", 666666666, crypto.SHA512},
|
||||
}
|
||||
|
||||
func TestTotpRFC(t *testing.T) {
|
||||
for _, tc := range rfcTotpTests {
|
||||
otp := NewTOTP(rfcTotpKey, 0, 30, 8, tc.Algo)
|
||||
if otp.otpCounter(tc.Time) != tc.T {
|
||||
fmt.Println("twofactor: invalid T")
|
||||
fmt.Printf("\texpected: %d\n", tc.T)
|
||||
fmt.Printf("\t actual: %d\n", otp.otpCounter(tc.Time))
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if code := otp.otp(otp.otpCounter(tc.Time)); code != tc.Code {
|
||||
fmt.Println("twofactor: invalid TOTP")
|
||||
fmt.Printf("\texpected: %s\n", tc.Code)
|
||||
fmt.Printf("\t actual: %s\n", code)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user