package totp import ( "testing" "time" ) // rfc6238Secret is the Base32 encoding of the ASCII seed "12345678901234567890" // from RFC 6238 Appendix B (the SHA-1 test vector). const rfc6238Secret = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ" func TestCode_RFC6238Vectors(t *testing.T) { // 8-digit reference values from RFC 6238 truncated to our 6 digits. cases := []struct { unix int64 want string }{ {59, "287082"}, {1111111109, "081804"}, {1111111111, "050471"}, {1234567890, "005924"}, {2000000000, "279037"}, {20000000000, "353130"}, } for _, c := range cases { got, err := Code(rfc6238Secret, time.Unix(c.unix, 0).UTC()) if err != nil { t.Fatalf("Code(%d): %v", c.unix, err) } if got != c.want { t.Errorf("Code(%d) = %s; want %s", c.unix, got, c.want) } } } func TestValidate_SkewWindow(t *testing.T) { secret, err := GenerateSecret() if err != nil { t.Fatal(err) } now := time.Now().UTC() code, err := Code(secret, now) if err != nil { t.Fatal(err) } if !Validate(secret, code, now, 1) { t.Error("current code rejected") } // Previous window must be accepted with skew=1. if !Validate(secret, code, now.Add(Period), 1) { t.Error("code from previous step rejected with skew=1") } // Two steps away must be rejected. if Validate(secret, code, now.Add(2*Period+time.Second), 1) { t.Error("stale code accepted outside skew window") } // Wrong code rejected. if Validate(secret, "000000", now, 1) && code != "000000" { t.Error("validate accepted obviously wrong code") } } func TestValidate_BadInput(t *testing.T) { secret, _ := GenerateSecret() now := time.Now().UTC() if Validate(secret, "12345", now, 1) { // too short t.Error("accepted 5-digit code") } if Validate("not-base32!!", "123456", now, 1) { t.Error("accepted invalid secret") } } func TestProvisioningURI(t *testing.T) { uri := ProvisioningURI(rfc6238Secret, "admin", "Pangolin") if uri == "" { t.Fatal("empty URI") } for _, sub := range []string{"otpauth://totp/", "secret=" + rfc6238Secret, "issuer=Pangolin"} { if !contains(uri, sub) { t.Errorf("URI %q missing %q", uri, sub) } } } func contains(s, sub string) bool { for i := 0; i+len(sub) <= len(s); i++ { if s[i:i+len(sub)] == sub { return true } } return false }