mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
caddytls: Make peer certificate verification pluggable (#4389)
* 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>
This commit is contained in:
parent
9864b138fb
commit
0a14f97e49
1 changed files with 82 additions and 26 deletions
|
@ -18,6 +18,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -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.
|
||||
|
@ -55,6 +60,16 @@ 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
|
||||
|
@ -62,7 +77,7 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
|||
|
||||
// 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
|
||||
|
@ -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
|
||||
|
@ -312,8 +338,6 @@ 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
|
||||
}
|
||||
|
||||
|
@ -321,7 +345,8 @@ type ClientAuthentication struct {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -378,28 +403,31 @@ 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)
|
||||
|
@ -407,23 +435,13 @@ func (clientauth ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte,
|
|||
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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"}
|
||||
|
|
Loading…
Reference in a new issue