129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package crypt
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"errors"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// testKey — 32 байта в base64 (та же строка ниже и в hex).
|
||
var (
|
||
testKeyB64 = base64.StdEncoding.EncodeToString(make([]byte, 32))
|
||
testKeyHex = hex.EncodeToString(make([]byte, 32))
|
||
)
|
||
|
||
// TestNewKeyFormat — принимаются base64 и hex, отклоняется прочее:
|
||
// пустой ключ, короткий, мусор и неверный размер.
|
||
func TestNewKeyFormat(t *testing.T) {
|
||
for _, key := range []string{testKeyB64, testKeyHex, strings.TrimRight(testKeyB64, "=")} {
|
||
if _, err := New(key); err != nil {
|
||
t.Errorf("New(%q): %v", key[:12], err)
|
||
}
|
||
}
|
||
for _, key := range []string{
|
||
"",
|
||
"short",
|
||
base64.StdEncoding.EncodeToString(make([]byte, 16)), // 16 байт — AES-128, не наш формат
|
||
strings.Repeat("z", 44), // base64 от 33 байт
|
||
} {
|
||
if _, err := New(key); !errors.Is(err, ErrKeySize) {
|
||
t.Errorf("New(%q): err = %v, want ErrKeySize", key, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestSealOpen — основной путь: шифрование обратимо, ciphertext не
|
||
// содержит исходный текст и меняется от прогона к прогону (nonce).
|
||
func TestSealOpen(t *testing.T) {
|
||
c, err := New(testKeyB64)
|
||
if err != nil {
|
||
t.Fatalf("New: %v", err)
|
||
}
|
||
const secret = "123456789:AAFakeBotTokenForTests0123456789"
|
||
|
||
enc, err := c.Seal(secret)
|
||
if err != nil {
|
||
t.Fatalf("Seal: %v", err)
|
||
}
|
||
if strings.Contains(enc, secret) || strings.Contains(enc, "AAFake") {
|
||
t.Errorf("ciphertext leaks plaintext: %q", enc)
|
||
}
|
||
got, err := c.Open(enc)
|
||
if err != nil {
|
||
t.Fatalf("Open: %v", err)
|
||
}
|
||
if got != secret {
|
||
t.Errorf("roundtrip = %q, want %q", got, secret)
|
||
}
|
||
|
||
// Та же строка шифруется заново: nonce другой, результат другой —
|
||
// иначе два пользователя с одинаковым токеном дали бы одинаковый blob.
|
||
again, err := c.Seal(secret)
|
||
if err != nil {
|
||
t.Fatalf("Seal again: %v", err)
|
||
}
|
||
if again == enc {
|
||
t.Error("two seals of the same value are equal (nonce reuse?)")
|
||
}
|
||
}
|
||
|
||
// TestOpenRejects — битые и чужие данные: обрезанный результат, битый
|
||
// base64, подделанный байт (GCM поймает), другой ключ.
|
||
func TestOpenRejects(t *testing.T) {
|
||
c, err := New(testKeyB64)
|
||
if err != nil {
|
||
t.Fatalf("New: %v", err)
|
||
}
|
||
other, err := New(hex.EncodeToString(append(make([]byte, 31), 1)))
|
||
if err != nil {
|
||
t.Fatalf("New other: %v", err)
|
||
}
|
||
enc, err := c.Seal("topsecret")
|
||
if err != nil {
|
||
t.Fatalf("Seal: %v", err)
|
||
}
|
||
|
||
raw, err := base64.StdEncoding.DecodeString(enc)
|
||
if err != nil {
|
||
t.Fatalf("decode: %v", err)
|
||
}
|
||
raw[len(raw)-1] ^= 0xFF // битый тэг GCM
|
||
tampered := base64.StdEncoding.EncodeToString(raw)
|
||
|
||
for name, tc := range map[string]struct {
|
||
c *Cipher
|
||
in string
|
||
}{
|
||
"not base64": {c, "не base64"},
|
||
"too short": {c, base64.StdEncoding.EncodeToString([]byte("abc"))},
|
||
"tampered": {c, tampered},
|
||
"another key": {other, enc},
|
||
"empty": {c, ""},
|
||
} {
|
||
t.Run(name, func(t *testing.T) {
|
||
if _, err := tc.c.Open(tc.in); !errors.Is(err, ErrCorrupt) {
|
||
t.Errorf("err = %v, want ErrCorrupt", err)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestSealEmpty — пустая строка шифруется и расшифровывается (нужно,
|
||
// когда пустое значение хранят сознательно).
|
||
func TestSealEmpty(t *testing.T) {
|
||
c, err := New(testKeyHex)
|
||
if err != nil {
|
||
t.Fatalf("New: %v", err)
|
||
}
|
||
enc, err := c.Seal("")
|
||
if err != nil {
|
||
t.Fatalf("Seal: %v", err)
|
||
}
|
||
got, err := c.Open(enc)
|
||
if err != nil || got != "" {
|
||
t.Errorf("got %q, err %v, want empty", got, err)
|
||
}
|
||
}
|