Smail/backend/services/setup/ssl/dns_request_security_test.go
smail d6b74bcaad
Some checks are pending
Security Checks / security (push) Waiting to run
修复DNS签名参数复用并完成云端回归与安全复扫
2026-09-21 21:55:02 +08:00

139 lines
4.8 KiB
Go

package ssl
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"sort"
"strings"
"testing"
"time"
)
type dnsSignatureTransport struct {
t *testing.T
nonces map[string]bool
}
// 独立按字节编码,避免验证器与生产编码函数共享同一种错误。
func referenceDNSEncode(value string) string {
var result strings.Builder
for _, value := range []byte(value) {
if value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z' || value >= '0' && value <= '9' || strings.ContainsRune("-_.~", rune(value)) {
result.WriteByte(value)
} else {
fmt.Fprintf(&result, "%%%02X", value)
}
}
return result.String()
}
func referenceDNSSignature(values url.Values) string {
keys := make([]string, 0, len(values))
for key := range values {
if key != "Signature" {
keys = append(keys, key)
}
}
sort.Strings(keys)
var parts []string
for _, key := range keys {
for _, value := range values[key] {
parts = append(parts, referenceDNSEncode(key)+"="+referenceDNSEncode(value))
}
}
mac := hmac.New(sha1.New, []byte("test-secret&"))
_, _ = mac.Write([]byte("POST&%2F&" + referenceDNSEncode(strings.Join(parts, "&"))))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
func (transport *dnsSignatureTransport) RoundTrip(request *http.Request) (*http.Response, error) {
t := transport.t
if request.Method != http.MethodPost || request.URL.Scheme != "https" || request.URL.Host != "alidns.aliyuncs.com" || request.URL.RawQuery != "" {
t.Error("DNS 签名请求没有使用预期的地址和方法")
}
defer request.Body.Close()
body, err := io.ReadAll(request.Body)
if err != nil {
return nil, err
}
values, err := url.ParseQuery(string(body))
if err != nil {
return nil, err
}
if values.Get("Signature") != referenceDNSSignature(values) {
t.Error("DNS 请求签名不匹配,可能包含上一次的签名")
}
for key, expected := range map[string]string{
"Action": "AddDomainRecord", "Version": "2015-01-09", "Format": "JSON",
"AccessKeyId": "test-access", "SignatureMethod": "HMAC-SHA1", "SignatureVersion": "1.0",
"Value": "中文 a b+*/~=&%", "DomainName": "example.com",
} {
if values.Get(key) != expected {
t.Errorf("DNS 请求字段不符合预期:%s", key)
}
}
nonce := values.Get("SignatureNonce")
decoded, err := hex.DecodeString(nonce)
if err != nil || len(decoded) != 16 || transport.nonces[nonce] {
t.Error("DNS 请求随机数无效或被重复使用")
}
transport.nonces[nonce] = true
timestamp, err := time.Parse("2006-01-02T15:04:05Z", values.Get("Timestamp"))
if err != nil || time.Since(timestamp) > time.Minute || time.Until(timestamp) > time.Minute {
t.Error("DNS 请求时间戳无效")
}
if strings.Contains(string(body), "test-secret") || request.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
t.Error("DNS 请求泄露测试密钥或使用了错误的内容类型")
}
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(`{"RecordId":"created"}`)), Header: make(http.Header)}, nil
}
func TestAutomaticDNSSigningPreservesReusableParameters(t *testing.T) {
for _, name := range []string{"fresh", "stale"} {
t.Run(name, func(t *testing.T) {
transport := &dnsSignatureTransport{t: t, nonces: make(map[string]bool)}
provider := &aliDNSChallenge{
accessKey: "test-access", secretKey: "test-secret", endpoint: "https://alidns.aliyuncs.com/",
client: &http.Client{Transport: transport},
}
values := url.Values{"DomainName": {"example.com"}, "Value": {"中文 a b+*/~=&%"}}
expected := url.Values{"DomainName": {"example.com"}, "Value": {"中文 a b+*/~=&%"}}
if name == "stale" {
values.Set("Signature", "stale-signature")
expected.Set("Signature", "stale-signature")
}
for i := 0; i < 3; i++ {
var result struct {
RecordID string `json:"RecordId"`
}
if err := provider.call("AddDomainRecord", values, &result); err != nil || result.RecordID != "created" {
t.Fatal("DNS 签名请求未正确返回")
}
if !reflect.DeepEqual(values, expected) {
t.Error("DNS 请求改写了调用者参数,重试会带入之前的签名")
}
}
})
}
}
func TestAutomaticDNSSignatureReferenceVector(t *testing.T) {
// 固定向量由独立 Python 标准库计算;只是测试样本,不是真实云凭据。
values := url.Values{
"Action": {"AddDomainRecord"}, "Version": {"2015-01-09"}, "Format": {"JSON"},
"AccessKeyId": {"test-access"}, "SignatureMethod": {"HMAC-SHA1"}, "SignatureVersion": {"1.0"},
"SignatureNonce": {"00000000000000000000000000000000"}, "Timestamp": {"2026-09-21T00:00:00Z"},
"DomainName": {"example.com"}, "RR": {"_acme-challenge"}, "Type": {"TXT"}, "Value": {"a b+*/~=&%"},
}
if referenceDNSSignature(values) != "s+e5Vs2kLbIFh5nFBykkdS2IrFU=" {
t.Fatal("独立签名验证器不符合固定向量")
}
}