refactor: enhance certificate domain matching logic

This commit is contained in:
Fu Diwei
2026-04-07 17:14:55 +08:00
parent dbe97b19bc
commit 871603537d
34 changed files with 162 additions and 273 deletions
+58 -8
View File
@@ -4,29 +4,79 @@ import (
"crypto/x509"
"net"
"strings"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
// 检查目标主机名是否匹配待匹配主机名。
// 兼容目标主机名开头是 "." 的情况(视为泛域名)。
//
// 入参:
// - match: 待匹配主机名。可以是泛域名,如 "*.example.com"。
// - candidate: 目标主机名。如 "sub.example.com"。
// - pattern: 待匹配主机名,可以是泛域名。如 "*.example.com"。
// - hostname: 目标主机名。如 "sub.example.com"。
//
// 出参:
// - 是否匹配。
func IsMatch(match, candidate string) bool {
if match == "" || candidate == "" {
func IsMatch(pattern, hostname string) bool {
if pattern == "" || hostname == "" {
return false
}
mockCert := &x509.Certificate{}
if ip := net.ParseIP(match); ip != nil {
if ip := net.ParseIP(pattern); ip != nil {
mockCert.IPAddresses = []net.IP{ip}
} else {
if strings.EqualFold(match, candidate) {
if strings.EqualFold(pattern, hostname) {
return true
}
mockCert.DNSNames = []string{match}
mockCert.DNSNames = []string{pattern}
}
return mockCert.VerifyHostname(candidate) == nil
return IsMatchByCertificate(mockCert, hostname)
}
// 检查目标主机名是否匹配证书。
// 兼容目标主机名开头是 "." 的情况(视为泛域名)。
//
// 入参:
// - certPEM: 证书 PEM 内容。
// - hostname: 目标主机名。如 "sub.example.com"。
//
// 出参:
// - 是否匹配。
func IsMatchByCertificatePEM(certPEM string, hostname string) bool {
if certPEM == "" || hostname == "" {
return false
}
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return false
}
return IsMatchByCertificate(certX509, hostname)
}
// 检查目标主机名是否匹配证书。
// 兼容目标主机名开头是 "." 的情况(视为泛域名)。
//
// 入参:
// - certX509: 证书 X509 对象。
// - hostname: 目标主机名。如 "sub.example.com"。
//
// 出参:
// - 是否匹配。
func IsMatchByCertificate(certX509 *x509.Certificate, hostname string) bool {
if certX509 == nil || hostname == "" {
return false
}
if strings.HasPrefix(hostname, "*.") || strings.HasPrefix(hostname, ".") {
for _, dn := range certX509.DNSNames {
if strings.EqualFold(strings.TrimPrefix(dn, "*"), strings.TrimPrefix(hostname, "*")) {
return true
}
}
}
return certX509.VerifyHostname(hostname) == nil
}
+51 -4
View File
@@ -9,12 +9,14 @@ import (
func TestCertHostnameUtil_IsMatch(t *testing.T) {
t.Run("IsMatch", func(t *testing.T) {
testCases := []struct {
wildcard string
target string
pattern string
hostname string
expected bool
}{
{"*.example.com", "sub.example.com", true},
{"*.example.com", "sub.sub.example.com", false},
{"*.example.com", "*.example.com", true},
{"*.example.com", ".example.com", true},
{"*.example.com", "example.com", false},
{"*.*.example.com", "a.b.example.com", false},
@@ -36,7 +38,7 @@ func TestCertHostnameUtil_IsMatch(t *testing.T) {
}
for _, tc := range testCases {
result := xcerthostname.IsMatch(tc.wildcard, tc.target)
result := xcerthostname.IsMatch(tc.pattern, tc.hostname)
status := "✓"
pf := t.Logf
if result != tc.expected {
@@ -44,7 +46,52 @@ func TestCertHostnameUtil_IsMatch(t *testing.T) {
pf = t.Errorf
}
pf("%s Wildcard: %-20s Target: %-20s Expected: %-5v Got: %-5v\n", status, tc.wildcard, tc.target, tc.expected, result)
pf("%s Pattern: %-20s Hostname: %-20s Expected: %-5v Got: %-5v\n", status, tc.pattern, tc.hostname, tc.expected, result)
}
})
}
func TestCertHostnameUtil_IsMatch(t *testing.T) {
t.Run("IsMatch", func(t *testing.T) {
testCases := []struct {
pattern string
hostname string
expected bool
}{
{"*.example.com", "sub.example.com", true},
{"*.example.com", "sub.sub.example.com", false},
{"*.example.com", "*.example.com", true},
{"*.example.com", ".example.com", true},
{"*.example.com", "example.com", false},
{"*.*.example.com", "a.b.example.com", false},
{"*.*.example.com", "a.example.com", false},
{"*.*.example.com", "a.b.c.example.com", false},
{"example.com", "example.com", true},
{"example.com", "wrong.com", false},
{"", "example.com", false},
{"*.example.com", "", false},
{"*.sub.example.com", "a.sub.example.com", true},
{"*.sub.example.com", "a.b.sub.example.com", false},
{"*.sub.example.com", "sub.example.com", false},
{"*.Example.COM", "sub.example.com", true},
{"*.EXAMPLE.COM", "SUB.EXAMPLE.COM", true},
}
for _, tc := range testCases {
result := xcerthostname.IsMatch(tc.pattern, tc.hostname)
status := "✓"
pf := t.Logf
if result != tc.expected {
status = "✗"
pf = t.Errorf
}
pf("%s Pattern: %-20s Hostname: %-20s Expected: %-5v Got: %-5v\n", status, tc.pattern, tc.hostname, tc.expected, result)
}
})
}