mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
refactor: enhance certificate domain matching logic
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user