Files
ThothII/tools/tht/internal/authconfig/password_test.go
T

110 lines
4.2 KiB
Go

package authconfig
import (
"bytes"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
type passwordVector struct {
Password string `json:"password"`
SaltHex string `json:"saltHex"`
MemoryKiB uint32 `json:"memoryKiB"`
Passes uint32 `json:"passes"`
Parallelism uint8 `json:"parallelism"`
KeyLength uint32 `json:"keyLength"`
PHC string `json:"phc"`
}
func TestHashPasswordMatchesSharedFixedArgon2idVector(t *testing.T) {
vector := loadPasswordVector(t)
salt, err := hex.DecodeString(vector.SaltHex)
if err != nil {
t.Fatal(err)
}
got, err := HashPassword([]byte(vector.Password), bytes.NewReader(salt))
if err != nil {
t.Fatal(err)
}
if got != vector.PHC {
t.Fatalf("HashPassword() = %q, want shared PHC vector %q", got, vector.PHC)
}
}
func TestVerifyPasswordAcceptsOnlyTheMatchingPassword(t *testing.T) {
vector := loadPasswordVector(t)
if !VerifyPassword([]byte(vector.Password), vector.PHC) {
t.Fatal("VerifyPassword() rejected the shared fixed vector")
}
if VerifyPassword([]byte(vector.Password+"!"), vector.PHC) {
t.Fatal("VerifyPassword() accepted a one-byte password change")
}
}
func TestVerifyPasswordRejectsMalformedAndOversizedPHCBeforeHashing(t *testing.T) {
password := []byte("correct horse battery staple")
for name, encoded := range map[string]string{
"wrong algorithm": "$argon2i$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"wrong version": "$argon2id$v=18$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"wrong parameter order": "$argon2id$v=19$t=3,m=65536,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"padded base64": "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw==$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"memory too large": "$argon2id$v=19$m=262145,t=1,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"passes too large": "$argon2id$v=19$m=65536,t=11,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"parallelism too large": "$argon2id$v=19$m=65536,t=3,p=5$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"weak bounded policy": "$argon2id$v=19$m=8,t=1,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"short salt": "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0O$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"long digest": "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"noncanonical salt": "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODx$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4",
"noncanonical digest": "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC5",
} {
t.Run(name, func(t *testing.T) {
if VerifyPassword(password, encoded) {
t.Fatal("VerifyPassword() accepted an invalid PHC string")
}
if err := validatePasswordHash(encoded); err == nil {
t.Fatal("validatePasswordHash() accepted an invalid PHC string")
}
})
}
}
func TestHashPasswordRejectsPasswordsOutsideUTF8ByteBounds(t *testing.T) {
for name, password := range map[string][]byte{
"too short": bytes.Repeat([]byte("a"), 11),
"too long": bytes.Repeat([]byte("a"), 1025),
} {
t.Run(name, func(t *testing.T) {
if _, err := HashPassword(password, bytes.NewReader(make([]byte, 16))); err == nil {
t.Fatal("HashPassword() accepted an out-of-bounds password")
}
})
}
}
func loadPasswordVector(t *testing.T) passwordVector {
t.Helper()
_, testFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("locate password test fixture")
}
path := filepath.Join(filepath.Dir(testFile), "..", "..", "..", "..", "backend", "test", "fixtures", "argon2id-vectors.json")
contents, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var vectors []passwordVector
if err := json.Unmarshal(contents, &vectors); err != nil {
t.Fatal(err)
}
if len(vectors) != 1 || vectors[0].Password == "" || strings.Contains(vectors[0].PHC, "\n") {
t.Fatal("shared Argon2id fixture is invalid")
}
return vectors[0]
}