109 lines
3.5 KiB
Go
109 lines
3.5 KiB
Go
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 должен отвергаться")
|
||
}
|
||
}
|