From 9cd6f35e9d4873946060e79cb50643bae8ba8e2c Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 27 May 2019 11:31:47 -0600 Subject: [PATCH] Separate out certificate selection --- modules/caddytls/connpolicy.go | 75 +++++++++++----------------------- modules/caddytls/tls.go | 8 ++-- 2 files changed, 28 insertions(+), 55 deletions(-) diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index fb999df3..43ddb239 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -5,7 +5,6 @@ import ( "crypto/x509" "encoding/json" "fmt" - "math/big" "strings" "bitbucket.org/lightcodelabs/caddy2" @@ -22,16 +21,27 @@ type ConnectionPolicies []*ConnectionPolicy // TLS configuration which selects the first matching policy based on // the ClientHello. func (cp ConnectionPolicies) TLSConfig(ctx caddy2.Context) (*tls.Config, error) { - // connection policy matchers + // set up each of the connection policies for i, pol := range cp { - for modName, rawMsg := range pol.MatchersRaw { + // matchers + for modName, rawMsg := range pol.Matchers { val, err := ctx.LoadModule("tls.handshake_match."+modName, rawMsg) if err != nil { return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err) } cp[i].matchers = append(cp[i].matchers, val.(ConnectionMatcher)) } - cp[i].MatchersRaw = nil // allow GC to deallocate - TODO: Does this help? + cp[i].Matchers = nil // allow GC to deallocate - TODO: Does this help? + + // certificate selector + if pol.CertSelection != nil { + val, err := ctx.LoadModuleInline("policy", "tls.certificate_selection", pol.CertSelection) + if err != nil { + return nil, fmt.Errorf("loading certificate selection module: %s", err) + } + cp[i].certSelector = val.(certmagic.CertificateSelector) + cp[i].CertSelection = nil // allow GC to deallocate - TODO: Does this help? + } } // pre-build standard TLS configs so we don't have to at handshake-time @@ -83,7 +93,8 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy2.Context) (*tls.Config, error) // ConnectionPolicy specifies the logic for handling a TLS handshake. type ConnectionPolicy struct { - MatchersRaw map[string]json.RawMessage `json:"match,omitempty"` + Matchers map[string]json.RawMessage `json:"match,omitempty"` + CertSelection json.RawMessage `json:"certificate_selection,omitempty"` CipherSuites []string `json:"cipher_suites,omitempty"` Curves []string `json:"curves,omitempty"` @@ -91,14 +102,14 @@ type ConnectionPolicy struct { ProtocolMin string `json:"protocol_min,omitempty"` ProtocolMax string `json:"protocol_max,omitempty"` - CertSelection *CertSelectionPolicy `json:"certificate_selection,omitempty"` - // TODO: Client auth // TODO: see if starlark could be useful here - enterprise only StarlarkHandshake string `json:"starlark_handshake,omitempty"` matchers []ConnectionMatcher + certSelector certmagic.CertificateSelector + stdTLSConfig *tls.Config } @@ -118,8 +129,8 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy2.Context) error { return nil, fmt.Errorf("getting config for name %s: %v", hello.ServerName, err) } newCfg := certmagic.New(tlsApp.certCache, cfgTpl) - if p.CertSelection != nil { - newCfg.CertSelector = makeCertSelector(p) + if p.certSelector != nil { + newCfg.CertSelection = p.certSelector } return newCfg.GetCertificate(hello) }, @@ -178,56 +189,18 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy2.Context) error { return nil } -// CertSelectionPolicy 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/mholt/caddy/issues/2588. -type CertSelectionPolicy struct { - SerialNumber *big.Int `json:"serial_number,omitempty"` - SubjectOrganization string `json:"subject.organization,omitempty"` - PublicKeyAlgorithm pkAlgorithm `json:"public_key_algorithm,omitempty"` -} - -func makeCertSelector(p *ConnectionPolicy) func(*tls.ClientHelloInfo, []certmagic.Certificate) (certmagic.Certificate, error) { - return func(hello *tls.ClientHelloInfo, choices []certmagic.Certificate) (certmagic.Certificate, error) { - for _, cert := range choices { - var matchOrg bool - if p.CertSelection.SubjectOrganization != "" { - for _, org := range cert.Subject.Organization { - if p.CertSelection.SubjectOrganization == org { - matchOrg = true - break - } - } - } - if !matchOrg { - continue - } - if p.CertSelection.PublicKeyAlgorithm != pkAlgorithm(x509.UnknownPublicKeyAlgorithm) && - pkAlgorithm(cert.PublicKeyAlgorithm) != p.CertSelection.PublicKeyAlgorithm { - continue - } - if p.CertSelection.SerialNumber != nil && - cert.SerialNumber.Cmp(p.CertSelection.SerialNumber) != 0 { - continue - } - return cert, nil - } - return certmagic.Certificate{}, fmt.Errorf("no certificates matched custom selection policy") - } -} - -type pkAlgorithm x509.PublicKeyAlgorithm +// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type. +type PublicKeyAlgorithm x509.PublicKeyAlgorithm // UnmarshalJSON satisfies json.Unmarshaler. -func (a *pkAlgorithm) UnmarshalJSON(b []byte) error { +func (a *PublicKeyAlgorithm) UnmarshalJSON(b []byte) error { algoStr := strings.ToLower(strings.Trim(string(b), `"`)) algo, ok := publicKeyAlgorithms[algoStr] if !ok { return fmt.Errorf("unrecognized public key algorithm: %s (expected one of %v)", algoStr, publicKeyAlgorithms) } - a = &algo + *a = PublicKeyAlgorithm(algo) return nil } diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 174d3e41..29ca88eb 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -318,10 +318,10 @@ var supportedProtocols = map[string]uint16{ } // publicKeyAlgorithms is the map of supported public key algorithms. -var publicKeyAlgorithms = map[string]pkAlgorithm{ - "rsa": pkAlgorithm(x509.RSA), - "dsa": pkAlgorithm(x509.DSA), - "ecdsa": pkAlgorithm(x509.ECDSA), +var publicKeyAlgorithms = map[string]x509.PublicKeyAlgorithm{ + "rsa": x509.RSA, + "dsa": x509.DSA, + "ecdsa": x509.ECDSA, } const automateKey = "automate"