Files
Havline/internal/agent/apr1_test.go
T
mrlee 166449871f feat(agent): Basic Auth 改为纯 Go apr1 并去掉 VPS openssl 依赖
- 新增 internal/agent/apr1.go(Apache apr_md5_encode 移植)+ 参考向量单元测试(与 openssl 输出逐字节一致)
- ensureBasicAuthSecret / writeBasicAuthSecret 不再调用 openssl passwd -apr1
- 安装脚本 token 改用 /dev/urandom(内嵌副本与 scripts/agent-install.sh 同步)
- AgentVersion 0.9.2;重编 amd64/arm64 内嵌二进制
- CHANGELOG v1.2.0 补记该改动
2026-09-23 11:22:11 +08:00

59 lines
1.8 KiB
Go

package agent
import "testing"
// 参考向量由本机 OpenSSL 3.5.4 生成:
//
// openssl passwd -apr1 -salt <salt> <password>
//
// 自研哈希必须与 openssl 完全一致,否则 Nginx Basic Auth 会永远认证失败。
func TestApr1CryptMatchesOpenSSLVectors(t *testing.T) {
cases := []struct {
password string
salt string
want string
}{
{"password", "xxxxxxxx", "$apr1$xxxxxxxx$dxHfLAsjHkDRmG83UXe8K0"},
{"hello", "01234567", "$apr1$01234567$JGzOENmOOkvGYFfJQyG9X/"},
{"P@ssw0rd!", "abcdefgh", "$apr1$abcdefgh$T9Ta7XuM2Yltpi/4uviFw0"},
}
for _, c := range cases {
if got := apr1CryptWithSalt(c.password, c.salt); got != c.want {
t.Errorf("apr1CryptWithSalt(%q, %q)\n got: %s\nwant: %s", c.password, c.salt, got, c.want)
}
}
}
func TestApr1CryptSaltHandling(t *testing.T) {
// 超过 8 字符的盐只取前 8 位(与 openssl / apr 行为一致)
long := apr1CryptWithSalt("password", "xxxxxxxxyyyy")
short := apr1CryptWithSalt("password", "xxxxxxxx")
if long != short {
t.Errorf("盐应截断到 8 字符: %s vs %s", long, short)
}
}
func TestApr1CryptRandomSalt(t *testing.T) {
first, err := apr1Crypt("password")
if err != nil {
t.Fatalf("apr1Crypt 失败: %v", err)
}
second, err := apr1Crypt("password")
if err != nil {
t.Fatalf("apr1Crypt 失败: %v", err)
}
if first == second {
t.Error("随机盐应产生不同哈希")
}
// 结构:$apr1$<8 位盐>$<22 位编码> = 37 字符
if len(first) != 37 {
t.Errorf("哈希长度应为 37,实际 %d: %s", len(first), first)
}
if first[:6] != "$apr1$" || first[14] != '$' {
t.Errorf("哈希结构异常: %s", first)
}
if same := apr1CryptWithSalt("password", first[6:14]); same != first {
t.Errorf("从哈希中取出的盐应能复现同一哈希: %s vs %s", same, first)
}
}