mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-29 05:11:51 +02:00
Multi-certificate, SNI matching with exact map and suffix tree Add support for multiple TLS certificates with SNI-based selection. The root provider maintains a single centralized SNI matcher that uses an exact match map for O(1) lookups, falling back to a suffix tree for wildcard matching. Key features: - Add `Extra []Config` field to autocert.Config for additional certificates - Each extra entry must specify unique `cert_path` and `key_path` - Extra certs inherit main config (except `email` and `extra` fields) - Extra certs participate in ACME obtain/renew cycles independently - SNI selection precedence: exact match > wildcard match, main > extra - Single centralized SNI matcher on root provider rebuilt after cert changes The SNI matcher structure: - Exact match map: O(1) lookup for exact domain matches - Suffix tree: Efficient wildcard matching (e.g., *.example.com) Implementation details: - Provider.GetCert() now uses SNI from ClientHelloInfo for selection - Main cert is returned as fallback when no SNI match is found - Extra providers are created as child providers with merged configs - SNI matcher is rebuilt after Setup() and after ObtainCert() completes
130 lines
2.4 KiB
Go
130 lines
2.4 KiB
Go
package autocert
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"strings"
|
|
)
|
|
|
|
type sniMatcher struct {
|
|
exact map[string]*Provider
|
|
root sniTreeNode
|
|
}
|
|
|
|
type sniTreeNode struct {
|
|
children map[string]*sniTreeNode
|
|
wildcard *Provider
|
|
}
|
|
|
|
func (m *sniMatcher) match(serverName string) *Provider {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
serverName = normalizeServerName(serverName)
|
|
if serverName == "" {
|
|
return nil
|
|
}
|
|
if m.exact != nil {
|
|
if p, ok := m.exact[serverName]; ok {
|
|
return p
|
|
}
|
|
}
|
|
return m.matchSuffixTree(serverName)
|
|
}
|
|
|
|
func (m *sniMatcher) matchSuffixTree(serverName string) *Provider {
|
|
n := &m.root
|
|
labels := strings.Split(serverName, ".")
|
|
|
|
var best *Provider
|
|
for i := len(labels) - 1; i >= 0; i-- {
|
|
if n.children == nil {
|
|
break
|
|
}
|
|
next := n.children[labels[i]]
|
|
if next == nil {
|
|
break
|
|
}
|
|
n = next
|
|
|
|
consumed := len(labels) - i
|
|
remaining := len(labels) - consumed
|
|
if remaining == 1 && n.wildcard != nil {
|
|
best = n.wildcard
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func normalizeServerName(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
s = strings.TrimSuffix(s, ".")
|
|
return strings.ToLower(s)
|
|
}
|
|
|
|
func (m *sniMatcher) addProvider(p *Provider) {
|
|
if p == nil || p.tlsCert == nil || len(p.tlsCert.Certificate) == 0 {
|
|
return
|
|
}
|
|
leaf, err := x509.ParseCertificate(p.tlsCert.Certificate[0])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
addName := func(name string) {
|
|
name = normalizeServerName(name)
|
|
if name == "" {
|
|
return
|
|
}
|
|
if after, ok := strings.CutPrefix(name, "*."); ok {
|
|
suffix := after
|
|
if suffix == "" {
|
|
return
|
|
}
|
|
m.insertWildcardSuffix(suffix, p)
|
|
return
|
|
}
|
|
m.insertExact(name, p)
|
|
}
|
|
|
|
if leaf.Subject.CommonName != "" {
|
|
addName(leaf.Subject.CommonName)
|
|
}
|
|
for _, n := range leaf.DNSNames {
|
|
addName(n)
|
|
}
|
|
}
|
|
|
|
func (m *sniMatcher) insertExact(name string, p *Provider) {
|
|
if name == "" || p == nil {
|
|
return
|
|
}
|
|
if m.exact == nil {
|
|
m.exact = make(map[string]*Provider)
|
|
}
|
|
if _, exists := m.exact[name]; !exists {
|
|
m.exact[name] = p
|
|
}
|
|
}
|
|
|
|
func (m *sniMatcher) insertWildcardSuffix(suffix string, p *Provider) {
|
|
if suffix == "" || p == nil {
|
|
return
|
|
}
|
|
n := &m.root
|
|
labels := strings.Split(suffix, ".")
|
|
for i := len(labels) - 1; i >= 0; i-- {
|
|
if n.children == nil {
|
|
n.children = make(map[string]*sniTreeNode)
|
|
}
|
|
next := n.children[labels[i]]
|
|
if next == nil {
|
|
next = &sniTreeNode{}
|
|
n.children[labels[i]] = next
|
|
}
|
|
n = next
|
|
}
|
|
if n.wildcard == nil {
|
|
n.wildcard = p
|
|
}
|
|
}
|