Skip to content

Commit

Permalink
caddytls: Make peer certificate verification pluggable (#4389)
Browse files Browse the repository at this point in the history
* caddytls: Adding ClientCertValidator for custom client cert validations

* caddytls: Cleanups for ClientCertValidator changes

caddytls: Cleanups for ClientCertValidator changes

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

* Unexported field Validators, corrected renaming of LeafVerificationValidator to LeafCertClientAuth

* admin: Write proper status on invalid requests (#4569) (fix #4561)

* Apply suggestions from code review

* Register module; fix compilation

* Add log for deprecation notice

Co-authored-by: Roettges Florian <roettges.florian@scheidt-bachmann.de>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Co-authored-by: Alok Naushad <alokme123@gmail.com>
  • Loading branch information
5 people committed Jun 2, 2022
1 parent 9864b13 commit 0a14f97
Showing 1 changed file with 82 additions and 26 deletions.
108 changes: 82 additions & 26 deletions modules/caddytls/connpolicy.go
Expand Up @@ -18,6 +18,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
Expand All @@ -26,6 +27,10 @@ import (
"github.com/mholt/acmez"
)

func init() {
caddy.RegisterModule(LeafCertClientAuth{})
}

// ConnectionPolicies govern the establishment of TLS connections. It is
// an ordered group of connection policies; the first matching policy will
// be used to configure TLS connections at handshake-time.
Expand Down Expand Up @@ -55,14 +60,24 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
if err != nil {
return fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
}

if pol.ClientAuthentication != nil && len(pol.ClientAuthentication.VerifiersRaw) > 0 {
clientCertValidations, err := ctx.LoadModule(pol.ClientAuthentication, "VerifiersRaw")
if err != nil {
return fmt.Errorf("loading client cert verifiers: %v", err)
}
for _, validator := range clientCertValidations.([]interface{}) {
cp[i].ClientAuthentication.verifiers = append(cp[i].ClientAuthentication.verifiers, validator.(ClientCertificateVerifier))
}
}
}

return nil
}

// TLSConfig returns a standard-lib-compatible TLS configuration which
// selects the first matching policy based on the ClientHello.
func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) *tls.Config {
func (cp ConnectionPolicies) TLSConfig(_ caddy.Context) *tls.Config {
// using ServerName to match policies is extremely common, especially in configs
// with lots and lots of different policies; we can fast-track those by indexing
// them by SNI, so we don't have to iterate potentially thousands of policies
Expand Down Expand Up @@ -293,11 +308,22 @@ type ClientAuthentication struct {
// these CA certificates will be rejected.
TrustedCACertPEMFiles []string `json:"trusted_ca_certs_pem_files,omitempty"`

// DEPRECATED: This field is deprecated and will be removed in
// a future version. Please use the `validators` field instead
// with the tls.client_auth.leaf module instead.
//
// A list of base64 DER-encoded client leaf certs
// to accept. If this list is not empty, client certs
// which are not in this list will be rejected.
TrustedLeafCerts []string `json:"trusted_leaf_certs,omitempty"`

// Client certificate verification modules. These can perform
// custom client authentication checks, such as ensuring the
// certificate is not revoked.
VerifiersRaw []json.RawMessage `json:"verifiers,omitempty" caddy:"namespace=tls.client_auth inline_key=verifier"`

verifiers []ClientCertificateVerifier

// The mode for authenticating the client. Allowed values are:
//
// Mode | Description
Expand All @@ -312,16 +338,15 @@ type ClientAuthentication struct {
// are provided; otherwise, the default mode is `require`.
Mode string `json:"mode,omitempty"`

// state established with the last call to ConfigureTLSConfig
trustedLeafCerts []*x509.Certificate
existingVerifyPeerCert func([][]byte, [][]*x509.Certificate) error
}

// Active returns true if clientauth has an actionable configuration.
func (clientauth ClientAuthentication) Active() bool {
return len(clientauth.TrustedCACerts) > 0 ||
len(clientauth.TrustedCACertPEMFiles) > 0 ||
len(clientauth.TrustedLeafCerts) > 0 ||
len(clientauth.TrustedLeafCerts) > 0 || // TODO: DEPRECATED
len(clientauth.VerifiersRaw) > 0 ||
len(clientauth.Mode) > 0
}

Expand Down Expand Up @@ -378,52 +403,45 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
cfg.ClientCAs = caPool
}

// enforce leaf verification by writing our own verify function
// TODO: DEPRECATED: Only here for backwards compatibility.
// If leaf cert is specified, enforce by adding a client auth module
if len(clientauth.TrustedLeafCerts) > 0 {
clientauth.trustedLeafCerts = []*x509.Certificate{}
caddy.Log().Named("tls.connection_policy").Warn("trusted_leaf_certs is deprecated; use leaf verifier module instead")
var trustedLeafCerts []*x509.Certificate
for _, clientCertString := range clientauth.TrustedLeafCerts {
clientCert, err := decodeBase64DERCert(clientCertString)
if err != nil {
return fmt.Errorf("parsing certificate: %v", err)
}
clientauth.trustedLeafCerts = append(clientauth.trustedLeafCerts, clientCert)
trustedLeafCerts = append(trustedLeafCerts, clientCert)
}
// if a custom verification function already exists, wrap it
clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate
cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate
clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{TrustedLeafCerts: trustedLeafCerts})
}

// if a custom verification function already exists, wrap it
clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate
cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate
return nil
}

// verifyPeerCertificate is for use as a tls.Config.VerifyPeerCertificate
// callback to do custom client certificate verification. It is intended
// for installation only by clientauth.ConfigureTLSConfig().
func (clientauth ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
func (clientauth *ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// first use any pre-existing custom verification function
if clientauth.existingVerifyPeerCert != nil {
err := clientauth.existingVerifyPeerCert(rawCerts, verifiedChains)
if err != nil {
return err
}
}

if len(rawCerts) == 0 {
return fmt.Errorf("no client certificate provided")
}

remoteLeafCert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
}

for _, trustedLeafCert := range clientauth.trustedLeafCerts {
if remoteLeafCert.Equal(trustedLeafCert) {
return nil
for _, verifier := range clientauth.verifiers {
err := verifier.VerifyClientCertificate(rawCerts, verifiedChains)
if err != nil {
return err
}
}

return fmt.Errorf("client leaf certificate failed validation")
return nil
}

// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
Expand Down Expand Up @@ -461,6 +479,38 @@ func setDefaultTLSParams(cfg *tls.Config) {
cfg.PreferServerCipherSuites = true
}

// LeafCertClientAuth verifies the client's leaf certificate.
type LeafCertClientAuth struct {
TrustedLeafCerts []*x509.Certificate
}

// CaddyModule returns the Caddy module information.
func (LeafCertClientAuth) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "tls.client_auth.leaf",
New: func() caddy.Module { return new(LeafCertClientAuth) },
}
}

func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return fmt.Errorf("no client certificate provided")
}

remoteLeafCert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
}

for _, trustedLeafCert := range l.TrustedLeafCerts {
if remoteLeafCert.Equal(trustedLeafCert) {
return nil
}
}

return fmt.Errorf("client leaf certificate failed validation")
}

// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type.
type PublicKeyAlgorithm x509.PublicKeyAlgorithm

Expand All @@ -481,4 +531,10 @@ type ConnectionMatcher interface {
Match(*tls.ClientHelloInfo) bool
}

// ClientCertificateVerifier is a type which verifies client certificates.
// It is called during verifyPeerCertificate in the TLS handshake.
type ClientCertificateVerifier interface {
VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
}

var defaultALPN = []string{"h2", "http/1.1"}

0 comments on commit 0a14f97

Please sign in to comment.