diff options
Diffstat (limited to 'modules/caddytls')
-rw-r--r-- | modules/caddytls/certselection.go | 101 | ||||
-rw-r--r-- | modules/caddytls/connpolicy.go | 19 |
2 files changed, 72 insertions, 48 deletions
diff --git a/modules/caddytls/certselection.go b/modules/caddytls/certselection.go index 88b7e0b..8a24034 100644 --- a/modules/caddytls/certselection.go +++ b/modules/caddytls/certselection.go @@ -20,65 +20,104 @@ import ( "fmt" "math/big" - "github.com/caddyserver/caddy/v2" "github.com/caddyserver/certmagic" ) -func init() { - caddy.RegisterModule(CustomCertSelectionPolicy{}) -} - // CustomCertSelectionPolicy represents a policy for selecting the certificate // used to complete a handshake when there may be multiple options. All fields // specified must match the candidate certificate for it to be chosen. // This was needed to solve https://github.com/caddyserver/caddy/issues/2588. type CustomCertSelectionPolicy struct { - SerialNumber *big.Int `json:"serial_number,omitempty"` - SubjectOrganization string `json:"subject_organization,omitempty"` - PublicKeyAlgorithm PublicKeyAlgorithm `json:"public_key_algorithm,omitempty"` - Tag string `json:"tag,omitempty"` -} + // The certificate must have one of these serial numbers. + SerialNumber []*big.Int `json:"serial_number,omitempty"` -// CaddyModule returns the Caddy module information. -func (CustomCertSelectionPolicy) CaddyModule() caddy.ModuleInfo { - return caddy.ModuleInfo{ - ID: "tls.certificate_selection.custom", - New: func() caddy.Module { return new(CustomCertSelectionPolicy) }, - } + // The certificate must have one of these organization names. + SubjectOrganization []string `json:"subject_organization,omitempty"` + + // The certificate must use this public key algorithm. + PublicKeyAlgorithm PublicKeyAlgorithm `json:"public_key_algorithm,omitempty"` + + // The certificate must have at least one of the tags in the list. + AnyTag []string `json:"any_tag,omitempty"` + + // The certificate must have all of the tags in the list. + AllTags []string `json:"all_tags,omitempty"` } -// SelectCertificate implements certmagic.CertificateSelector. -func (p CustomCertSelectionPolicy) SelectCertificate(_ *tls.ClientHelloInfo, choices []certmagic.Certificate) (certmagic.Certificate, error) { +// SelectCertificate implements certmagic.CertificateSelector. It +// only chooses a certificate that at least meets the criteria in +// p. It then chooses the first non-expired certificate that is +// compatible with the client. If none are valid, it chooses the +// first viable candidate anyway. +func (p CustomCertSelectionPolicy) SelectCertificate(hello *tls.ClientHelloInfo, choices []certmagic.Certificate) (certmagic.Certificate, error) { + viable := make([]certmagic.Certificate, 0, len(choices)) + +nextChoice: for _, cert := range choices { - if p.SerialNumber != nil && cert.SerialNumber.Cmp(p.SerialNumber) != 0 { - continue + if len(p.SerialNumber) > 0 { + var found bool + for _, sn := range p.SerialNumber { + if cert.Leaf.SerialNumber.Cmp(sn) == 0 { + found = true + break + } + } + if !found { + continue + } + } + + if len(p.SubjectOrganization) > 0 { + var found bool + for _, subjOrg := range p.SubjectOrganization { + for _, org := range cert.Leaf.Subject.Organization { + if subjOrg == org { + found = true + break + } + } + } + if !found { + continue + } } if p.PublicKeyAlgorithm != PublicKeyAlgorithm(x509.UnknownPublicKeyAlgorithm) && - PublicKeyAlgorithm(cert.PublicKeyAlgorithm) != p.PublicKeyAlgorithm { + PublicKeyAlgorithm(cert.Leaf.PublicKeyAlgorithm) != p.PublicKeyAlgorithm { continue } - if p.SubjectOrganization != "" { - var matchOrg bool - for _, org := range cert.Subject.Organization { - if p.SubjectOrganization == org { - matchOrg = true + if len(p.AnyTag) > 0 { + var found bool + for _, tag := range p.AnyTag { + if cert.HasTag(tag) { + found = true break } } - if !matchOrg { + if !found { continue } } - if p.Tag != "" && !cert.HasTag(p.Tag) { - continue + if len(p.AllTags) > 0 { + for _, tag := range p.AllTags { + if !cert.HasTag(tag) { + continue nextChoice + } + } } - return cert, nil + // this certificate at least meets the policy's requirements, + // but we still have to check expiration and compatibility + viable = append(viable, cert) } - return certmagic.Certificate{}, fmt.Errorf("no certificates matched custom selection policy") + + if len(viable) == 0 { + return certmagic.Certificate{}, fmt.Errorf("no certificates matched custom selection policy") + } + + return certmagic.DefaultCertificateSelector(hello, viable) } // Interface guard diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index 52ccdd9..4fd8112 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -18,12 +18,10 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "encoding/json" "fmt" "strings" "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/certmagic" "github.com/go-acme/lego/v3/challenge/tlsalpn01" ) @@ -46,15 +44,6 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error { cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher)) } - // certificate selector - if pol.CertSelection != nil { - val, err := ctx.LoadModule(pol, "CertSelection") - if err != nil { - return fmt.Errorf("loading certificate selection module: %s", err) - } - cp[i].certSelector = val.(certmagic.CertificateSelector) - } - // enable HTTP/2 by default if len(pol.ALPN) == 0 { pol.ALPN = append(pol.ALPN, defaultALPN...) @@ -123,7 +112,7 @@ type ConnectionPolicy struct { // How to choose a certificate if more than one matched // the given ServerName (SNI) value. - CertSelection json.RawMessage `json:"certificate_selection,omitempty" caddy:"namespace=tls.certificate_selection inline_key=policy"` + CertSelection *CustomCertSelectionPolicy `json:"certificate_selection,omitempty"` // The list of cipher suites to support. Caddy's // defaults are modern and secure. @@ -151,8 +140,6 @@ type ConnectionPolicy struct { DefaultSNI string `json:"default_sni,omitempty"` matchers []ConnectionMatcher - certSelector certmagic.CertificateSelector - stdTLSConfig *tls.Config } @@ -184,9 +171,7 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { // more at handshake-time, but I don't know how to practically pre-build // a certmagic config for each combination of conn policy + automation policy... cfg := *tlsApp.getConfigForName(hello.ServerName) - if p.certSelector != nil { - cfg.CertSelection = p.certSelector - } + cfg.CertSelection = p.CertSelection cfg.DefaultServerName = p.DefaultSNI return cfg.GetCertificate(hello) }, |