Initial commit: hotpcalc CLI
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package otp
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Seed из примера задачи: eToken PASS, 20 байт в hex.
|
||||
const exampleSeed = "765D7268D012090C28172A729BC9CC1AD82B382D"
|
||||
|
||||
func TestParseSeed_Hex(t *testing.T) {
|
||||
for _, in := range []string{
|
||||
exampleSeed,
|
||||
strings.ToLower(exampleSeed),
|
||||
"765D 7268 D012 090C 2817 2A72 9BC9 CC1A D82B 382D",
|
||||
"76-5D-72-68-D0-12-09-0C-28-17-2A-72-9B-C9-CC-1A-D8-2B-38-2D",
|
||||
} {
|
||||
s, err := ParseSeed(in, FormatAuto)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseSeed(%q): %v", in, err)
|
||||
}
|
||||
if s.Format != FormatHex {
|
||||
t.Errorf("ParseSeed(%q): формат %s, ожидался hex", in, s.Format)
|
||||
}
|
||||
if got := strings.ToUpper(hex.EncodeToString(s.Secret)); got != exampleSeed {
|
||||
t.Errorf("ParseSeed(%q) = %s", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSeed_Base32(t *testing.T) {
|
||||
// Base32 от ASCII-ключа "12345678901234567890" из RFC 4226.
|
||||
const b32 = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"
|
||||
s, err := ParseSeed(b32, FormatAuto)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseSeed: %v", err)
|
||||
}
|
||||
if s.Format != FormatBase32 {
|
||||
t.Errorf("формат %s, ожидался base32", s.Format)
|
||||
}
|
||||
if string(s.Secret) != string(rfc4226Key) {
|
||||
t.Errorf("secret = %q, ожидалось %q", s.Secret, rfc4226Key)
|
||||
}
|
||||
// Без padding и в нижнем регистре тоже должно работать.
|
||||
if s2, err := ParseSeed("gezdgnbvgy3tqojq", FormatAuto); err != nil || len(s2.Secret) != 10 {
|
||||
t.Errorf("Base32 без padding: %v, %d байт", err, len(s2.Secret))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSeed_ForcedFormat(t *testing.T) {
|
||||
// "12345678" — валидный hex и валидный набор символов не для Base32.
|
||||
if _, err := ParseSeed(exampleSeed, FormatBase32); err == nil {
|
||||
t.Error("hex-строка с цифрами 0/1 не должна разбираться как Base32")
|
||||
}
|
||||
if s, err := ParseSeed(exampleSeed, FormatHex); err != nil || len(s.Secret) != 20 {
|
||||
t.Errorf("принудительный hex: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSeed_Errors(t *testing.T) {
|
||||
for _, in := range []string{"", " ", "zzzz!!!", "765D7268D"} {
|
||||
if _, err := ParseSeed(in, FormatAuto); err == nil {
|
||||
t.Errorf("ParseSeed(%q) должен возвращать ошибку", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSeed_OtpauthURI(t *testing.T) {
|
||||
uri := "otpauth://totp/ACME:user@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ" +
|
||||
"&issuer=ACME&digits=8&algorithm=SHA256&period=60"
|
||||
s, err := ParseSeed(uri, FormatAuto)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseSeed: %v", err)
|
||||
}
|
||||
if !s.FromURI || !s.TOTP {
|
||||
t.Error("URI должен распознаваться как TOTP")
|
||||
}
|
||||
if s.Digits == nil || *s.Digits != 8 {
|
||||
t.Error("digits из URI не подхвачен")
|
||||
}
|
||||
if s.Alg == nil || *s.Alg != SHA256 {
|
||||
t.Error("algorithm из URI не подхвачен")
|
||||
}
|
||||
if s.Step == nil || *s.Step != 60*time.Second {
|
||||
t.Error("period из URI не подхвачен")
|
||||
}
|
||||
if s.Label != "ACME:user@example.com" {
|
||||
t.Errorf("label = %q", s.Label)
|
||||
}
|
||||
|
||||
hotpURI := "otpauth://hotp/tok?secret=GEZDGNBVGY3TQOJQ&counter=42"
|
||||
h, err := ParseSeed(hotpURI, FormatAuto)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseSeed(hotp): %v", err)
|
||||
}
|
||||
if h.TOTP {
|
||||
t.Error("hotp URI не должен помечаться как TOTP")
|
||||
}
|
||||
if h.Counter == nil || *h.Counter != 42 {
|
||||
t.Error("counter из URI не подхвачен")
|
||||
}
|
||||
if _, err := ParseSeed("otpauth://totp/x?issuer=ACME", FormatAuto); err == nil {
|
||||
t.Error("URI без secret должен отвергаться")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprint(t *testing.T) {
|
||||
s, _ := ParseSeed(exampleSeed, FormatAuto)
|
||||
if got := Fingerprint(s.Secret); got != "765D7268…382D" {
|
||||
t.Errorf("Fingerprint = %q", got)
|
||||
}
|
||||
if got := Fingerprint([]byte{0x01, 0x02}); got != "0102" {
|
||||
t.Errorf("короткий Fingerprint = %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user