110 lines
4.2 KiB
Go
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]
|
|
}
|