package totp import ( "strings" "testing" "time" ) // The SHA-1 test vectors of RFC 6238 appendix B, cut to 6 digits. func TestCodeRFC6238(t *testing.T) { secret := b32.EncodeToString([]byte("12345678901234567890")) for _, c := range []struct { unix int64 want string }{ {59, "287082"}, {1111111109, "081804"}, {1111111111, "050471"}, {1234567890, "005924"}, {2000000000, "279037"}, {20000000000, "353130"}, } { got, err := Code(secret, Step(time.Unix(c.unix, 0))) if err != nil || got != c.want { t.Errorf("T=%d: %q, %v; want %q", c.unix, got, err, c.want) } } } func TestVerify(t *testing.T) { secret := NewSecret() now := time.Unix(1_800_000_000, 0) cur := Step(now) code := func(step int64) string { c, _ := Code(secret, step); return c } if step, ok := Verify(secret, code(cur), now, 0); !ok || step != cur { t.Fatal("current code refused") } if _, ok := Verify(secret, code(cur - 1)[:3]+" "+code(cur - 1)[3:], now, 0); !ok { t.Error("previous step (with a space) refused") } if _, ok := Verify(secret, code(cur+1), now, 0); !ok { t.Error("next step refused") } if _, ok := Verify(secret, code(cur-2), now, 0); ok { t.Error("code from a minute ago accepted") } // Replay: a step at or before the last used one is refused. if _, ok := Verify(secret, code(cur), now, cur); ok { t.Error("reused code accepted") } if _, ok := Verify(secret, code(cur+1), now, cur); !ok { t.Error("newer code refused after a used one") } if _, ok := Verify(secret, "abcdef", now, 0); ok { t.Error("letters accepted") } } func TestURIAndQR(t *testing.T) { u := URI("Jarl ns1", "adam", "JBSWY3DPEHPK3PXP") if !strings.HasPrefix(u, "otpauth://totp/Jarl%20ns1:adam?") || !strings.Contains(u, "secret=JBSWY3DPEHPK3PXP") || !strings.Contains(u, "issuer=Jarl+ns1") { t.Errorf("uri = %s", u) } img, err := QRDataURI(u) if err != nil || !strings.HasPrefix(img, "data:image/svg+xml;base64,") { t.Errorf("qr = %.60s, %v", img, err) } } func TestRecoveryCodes(t *testing.T) { codes := NewRecoveryCodes(RecoveryCodes) seen := map[string]bool{} for _, c := range codes { if len(c) != 11 || c[5] != '-' || seen[c] || IsCode(c) { t.Errorf("bad code %q", c) } seen[c] = true } if NormalizeRecovery(" K7DM2-XQ9PW ") != "k7dm2xq9pw" { t.Error("normalize") } }