mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +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/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -26,6 +27,10 @@ import (
|
||||||
"github.com/mholt/acmez"
|
"github.com/mholt/acmez"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(LeafCertClientAuth{})
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectionPolicies govern the establishment of TLS connections. It is
|
// ConnectionPolicies govern the establishment of TLS connections. It is
|
||||||
// an ordered group of connection policies; the first matching policy will
|
// an ordered group of connection policies; the first matching policy will
|
||||||
// be used to configure TLS connections at handshake-time.
|
// be used to configure TLS connections at handshake-time.
|
||||||
|
@ -55,6 +60,16 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
|
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
|
return nil
|
||||||
|
@ -62,7 +77,7 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// TLSConfig returns a standard-lib-compatible TLS configuration which
|
// TLSConfig returns a standard-lib-compatible TLS configuration which
|
||||||
// selects the first matching policy based on the ClientHello.
|
// 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
|
// 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
|
// 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
|
// 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.
|
// these CA certificates will be rejected.
|
||||||
TrustedCACertPEMFiles []string `json:"trusted_ca_certs_pem_files,omitempty"`
|
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
|
// A list of base64 DER-encoded client leaf certs
|
||||||
// to accept. If this list is not empty, client certs
|
// to accept. If this list is not empty, client certs
|
||||||
// which are not in this list will be rejected.
|
// which are not in this list will be rejected.
|
||||||
TrustedLeafCerts []string `json:"trusted_leaf_certs,omitempty"`
|
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:
|
// The mode for authenticating the client. Allowed values are:
|
||||||
//
|
//
|
||||||
// Mode | Description
|
// Mode | Description
|
||||||
|
@ -312,8 +338,6 @@ type ClientAuthentication struct {
|
||||||
// are provided; otherwise, the default mode is `require`.
|
// are provided; otherwise, the default mode is `require`.
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty"`
|
||||||
|
|
||||||
// state established with the last call to ConfigureTLSConfig
|
|
||||||
trustedLeafCerts []*x509.Certificate
|
|
||||||
existingVerifyPeerCert func([][]byte, [][]*x509.Certificate) error
|
existingVerifyPeerCert func([][]byte, [][]*x509.Certificate) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +345,8 @@ type ClientAuthentication struct {
|
||||||
func (clientauth ClientAuthentication) Active() bool {
|
func (clientauth ClientAuthentication) Active() bool {
|
||||||
return len(clientauth.TrustedCACerts) > 0 ||
|
return len(clientauth.TrustedCACerts) > 0 ||
|
||||||
len(clientauth.TrustedCACertPEMFiles) > 0 ||
|
len(clientauth.TrustedCACertPEMFiles) > 0 ||
|
||||||
len(clientauth.TrustedLeafCerts) > 0 ||
|
len(clientauth.TrustedLeafCerts) > 0 || // TODO: DEPRECATED
|
||||||
|
len(clientauth.VerifiersRaw) > 0 ||
|
||||||
len(clientauth.Mode) > 0
|
len(clientauth.Mode) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,28 +403,31 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
|
||||||
cfg.ClientCAs = caPool
|
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 {
|
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 {
|
for _, clientCertString := range clientauth.TrustedLeafCerts {
|
||||||
clientCert, err := decodeBase64DERCert(clientCertString)
|
clientCert, err := decodeBase64DERCert(clientCertString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing certificate: %v", err)
|
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.verifiers = append(clientauth.verifiers, LeafCertClientAuth{TrustedLeafCerts: trustedLeafCerts})
|
||||||
clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate
|
|
||||||
cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if a custom verification function already exists, wrap it
|
||||||
|
clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate
|
||||||
|
cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyPeerCertificate is for use as a tls.Config.VerifyPeerCertificate
|
// verifyPeerCertificate is for use as a tls.Config.VerifyPeerCertificate
|
||||||
// callback to do custom client certificate verification. It is intended
|
// callback to do custom client certificate verification. It is intended
|
||||||
// for installation only by clientauth.ConfigureTLSConfig().
|
// 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
|
// first use any pre-existing custom verification function
|
||||||
if clientauth.existingVerifyPeerCert != nil {
|
if clientauth.existingVerifyPeerCert != nil {
|
||||||
err := clientauth.existingVerifyPeerCert(rawCerts, verifiedChains)
|
err := clientauth.existingVerifyPeerCert(rawCerts, verifiedChains)
|
||||||
|
@ -407,23 +435,13 @@ func (clientauth ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, verifier := range clientauth.verifiers {
|
||||||
if len(rawCerts) == 0 {
|
err := verifier.VerifyClientCertificate(rawCerts, verifiedChains)
|
||||||
return fmt.Errorf("no client certificate provided")
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return fmt.Errorf("client leaf certificate failed validation")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
|
// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
|
||||||
|
@ -461,6 +479,38 @@ func setDefaultTLSParams(cfg *tls.Config) {
|
||||||
cfg.PreferServerCipherSuites = true
|
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.
|
// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type.
|
||||||
type PublicKeyAlgorithm x509.PublicKeyAlgorithm
|
type PublicKeyAlgorithm x509.PublicKeyAlgorithm
|
||||||
|
|
||||||
|
@ -481,4 +531,10 @@ type ConnectionMatcher interface {
|
||||||
Match(*tls.ClientHelloInfo) bool
|
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"}
|
var defaultALPN = []string{"h2", "http/1.1"}
|
||||||
|
|
Loading…
Reference in a new issue