package otp import ( "testing" "time" ) var rfc6238Secret = "12345678901234567890" var rfc6238CounterCodes = map[uint64]string{ 59: "94287082", 1111111109: "07081804", 1111111111: "14050471", 1234567890: "89005924", 2000000000: "69279037", 20000000000: "65353130", } var baseRFC6238URL = "otpauth://totp/IETF:ReferenceImplementation?digits=6&issuer=IETF&period=30&secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ" func TestRFC6238CustomCode(t *testing.T) { secret := []byte(rfc6238Secret) for counter, code := range rfc6238CounterCodes { generated := CustomCode(secret, counter/DefaultPeriod, uint(len(code))) if code != generated { t.Logf("Invalid TOTP code for counter %d", counter) t.Logf("\tExpected:\t%s\n", code) t.Logf("\tActual: \t%s\n", generated) t.FailNow() } } } func TestRFC6238Verify(t *testing.T) { o := NewTOTPKeyOptions() o.Secret = []byte(rfc6238Secret) o.Length = 8 k := NewTOTPKey(o) for counter, code := range rfc6238CounterCodes { if !k.Verify(code, counter) { t.Logf("Invalid TOTP code for counter %d", counter) t.Logf("\tExpected:\t%s\n", code) t.FailNow() } } tKey := k.(*TOTPKey) code, counter := k.IntegrityCheck() expectedCounter := uint64(time.Now().Unix()) / tKey.Period() if expectedCounter-counter > 1 { t.Logf("Invalid time returned for TOTP Integrity check") t.Logf("\tExpected:\t%s\n", expectedCounter) t.Logf("\tActual: \t%s\n", counter) t.FailNow() } if !k.Verify(code, 0) { t.Logf("Invalid initial code returned for TOTP Integrity check") t.Logf("\tExpected:\t%s\n", code) t.FailNow() } } func TestRFC6238CustomValidate(t *testing.T) { secret := []byte(rfc6238Secret) for counter, code := range rfc6238CounterCodes { if !ValidateCustom(secret, counter/DefaultPeriod, code) { t.Logf("Invalid HOTP code for counter %d", counter) t.Logf("\tExpected:\t%s\n", code) t.FailNow() } } } func TestRFC6238FromURL(t *testing.T) { k, err := FromURL(baseRFC6238URL) if err != nil { t.Log(err.Error()) t.FailNow() } if k.URL() != baseRFC6238URL { t.Log("Invalid URL from RFC 6238 reference TOTP key") t.Logf("\tExpected:\t%s\n", baseRFC6238URL) t.Logf("\tActual: \t%s\n", k.URL()) t.FailNow() } } func TestRFC6238ToURL(t *testing.T) { o := NewTOTPKeyOptions() o.Secret = []byte(rfc6238Secret) o.Issuer = "IETF" o.Label = "ReferenceImplementation" k := NewTOTPKey(o) if k.URL() != baseRFC6238URL { t.Log("Invalid URL from RFC 6238 reference TOTP key") t.Logf("\tExpected:\t%s\n", baseRFC6238URL) t.Logf("\tActual: \t%s\n", k.URL()) t.FailNow() } } func TestTOTPVerify(t *testing.T) { k := NewTOTPKey() first := k.OTP() if !k.Verify(first) { t.Logf("TOTP key %s failed to properly verify the first code - %s", k.URL(), first) t.FailNow() } // Need to wait a full period newTime := uint64(time.Now().Add(time.Duration(DefaultPeriod) * time.Second).Unix()) // Should still validate because of Allowed Skew if !k.Verify(first) { t.Logf("TOTP key %s failed to properly verify the first code - %s", k.URL(), first) t.FailNow() } // wait another period newTime = uint64(time.Now().Add(time.Duration(DefaultPeriod) * time.Second * 2).Unix()) if k.Verify(first, newTime) { //Key should have advanced to the next code t.Logf("TOTP key %s should not still verify the first code - %s", k.URL(), first) t.FailNow() } }