package otp import ( "testing" "time" ) // RFC 4226, Appendix D: ключ ASCII "12345678901234567890". var rfc4226Key = []byte("12345678901234567890") func TestHOTP_RFC4226(t *testing.T) { want := []string{ "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", "520489", } c := Config{Secret: rfc4226Key, Digits: 6, Alg: SHA1, Step: DefaultStep} for i, w := range want { if got := c.HOTP(uint64(i)); got != w { t.Errorf("HOTP(%d) = %s, ожидалось %s", i, got, w) } } } // RFC 6238, Appendix B. Ключи для SHA256/SHA512 — продолжение той же // последовательности "12345678901234567890..." до размера блока хеша. func seedRepeat(n int) []byte { base := "12345678901234567890" out := make([]byte, 0, n) for len(out) < n { out = append(out, base...) } return out[:n] } func TestTOTP_RFC6238(t *testing.T) { cases := []struct { unix int64 alg Algorithm key []byte want string }{ {59, SHA1, seedRepeat(20), "94287082"}, {59, SHA256, seedRepeat(32), "46119246"}, {59, SHA512, seedRepeat(64), "90693936"}, {1111111109, SHA1, seedRepeat(20), "07081804"}, {1111111111, SHA1, seedRepeat(20), "14050471"}, {1234567890, SHA1, seedRepeat(20), "89005924"}, {2000000000, SHA1, seedRepeat(20), "69279037"}, {20000000000, SHA1, seedRepeat(20), "65353130"}, {1111111109, SHA256, seedRepeat(32), "68084774"}, {20000000000, SHA256, seedRepeat(32), "77737706"}, {1111111109, SHA512, seedRepeat(64), "25091201"}, {20000000000, SHA512, seedRepeat(64), "47863826"}, } for _, tc := range cases { c := Config{Secret: tc.key, Digits: 8, Alg: tc.alg, Step: DefaultStep} got := c.TOTP(time.Unix(tc.unix, 0).UTC()) if got != tc.want { t.Errorf("TOTP(%d, %s) = %s, ожидалось %s", tc.unix, tc.alg, got, tc.want) } } } func TestHOTP_LeadingZeros(t *testing.T) { c := Config{Secret: rfc4226Key, Digits: 6, Alg: SHA1, Step: DefaultStep} for i := uint64(0); i < 5000; i++ { if got := c.HOTP(i); len(got) != 6 { t.Fatalf("HOTP(%d) = %q: длина %d вместо 6", i, got, len(got)) } } } func TestTimeStepAndStepStart(t *testing.T) { c := Config{Secret: rfc4226Key, Digits: 6, Alg: SHA1, Step: DefaultStep} if got := c.TimeStep(time.Unix(59, 0)); got != 1 { t.Errorf("TimeStep(59) = %d, ожидалось 1", got) } if got := c.StepStart(1).Unix(); got != 30 { t.Errorf("StepStart(1) = %d, ожидалось 30", got) } c60 := Config{Secret: rfc4226Key, Digits: 6, Alg: SHA1, Step: 60 * time.Second} if got := c60.TimeStep(time.Unix(119, 0)); got != 1 { t.Errorf("TimeStep(119, шаг 60с) = %d, ожидалось 1", got) } } func TestValidate(t *testing.T) { if err := (Config{Digits: 6, Step: DefaultStep}).Validate(); err == nil { t.Error("пустой seed должен отвергаться") } if err := (Config{Secret: rfc4226Key, Digits: 4, Step: DefaultStep}).Validate(); err == nil { t.Error("разрядность 4 должна отвергаться") } if err := (Config{Secret: rfc4226Key, Digits: 6}).Validate(); err == nil { t.Error("нулевой шаг должен отвергаться") } } func TestParseAlgorithm(t *testing.T) { for _, s := range []string{"sha1", "SHA-1", ""} { if a, err := ParseAlgorithm(s); err != nil || a != SHA1 { t.Errorf("ParseAlgorithm(%q) = %v, %v", s, a, err) } } if _, err := ParseAlgorithm("md5"); err == nil { t.Error("md5 должен отвергаться") } }