Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

caddytls: Make peer certificate verification pluggable #4389

Merged
merged 35 commits into from Jun 2, 2022
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
04a51f9
caddytls: Adding ClientCertValidator for custom client cert validations
FlorianRoettges Oct 18, 2021
1217243
Merge branch 'master' of https://github.com/caddyserver/caddy
FlorianRoettges Oct 18, 2021
6c9d4b7
Merge branch 'master' into master
Gr33nbl00d Oct 19, 2021
c28d53d
caddytls: Cleanups for ClientCertValidator changes
FlorianRoettges Oct 19, 2021
ff28565
Merge branch 'master' of https://github.com/Gr33nbl00d/caddy
FlorianRoettges Oct 19, 2021
d40dd1b
Merge branch 'master' of https://github.com/caddyserver/caddy
Gr33nbl00d Oct 20, 2021
94cded5
Merge branch 'master' of https://github.com/caddyserver/caddy
Gr33nbl00d Oct 21, 2021
f8ddbdc
Merge branch 'master' into master
Gr33nbl00d Oct 22, 2021
4bb1a7b
Merge branch 'master' into master
Gr33nbl00d Oct 27, 2021
8ed75ec
Merge branch 'master' into master
Gr33nbl00d Oct 28, 2021
845a74b
Merge branch 'master' into master
Gr33nbl00d Nov 4, 2021
99698b1
Merge branch 'master' into master
Gr33nbl00d Nov 9, 2021
b8cdd3c
Merge branch 'master' into master
Gr33nbl00d Nov 18, 2021
3508007
Update modules/caddytls/connpolicy.go
Gr33nbl00d Jan 21, 2022
b25dc33
Update modules/caddytls/connpolicy.go
Gr33nbl00d Jan 21, 2022
cbf5cd3
Update modules/caddytls/connpolicy.go
Gr33nbl00d Jan 21, 2022
8e5ae59
Update modules/caddytls/connpolicy.go
Gr33nbl00d Jan 21, 2022
995fce7
Merge branch 'master' into master
Gr33nbl00d Jan 21, 2022
f411ede
Merge branch 'master' into master
Gr33nbl00d Jan 25, 2022
5add9eb
Merge branch 'master' of https://github.com/caddyserver/caddy
Gr33nbl00d Jan 25, 2022
5f3dd0f
Merge remote-tracking branch 'origin/master'
Gr33nbl00d Jan 25, 2022
ade1845
Merge branch 'master' into master
Gr33nbl00d Feb 1, 2022
74d1539
Update modules/caddytls/connpolicy.go
Gr33nbl00d Feb 4, 2022
ffe10fe
Update modules/caddytls/connpolicy.go
Gr33nbl00d Feb 4, 2022
1895494
Merge remote-tracking branch 'origin/master'
Gr33nbl00d Feb 4, 2022
428cbb8
Unexported field Validators, corrected renaming of LeafVerificationVa…
Gr33nbl00d Feb 4, 2022
d3780ea
Merge branch 'master' of https://github.com/caddyserver/caddy
Gr33nbl00d Feb 4, 2022
07c7dc6
admin: Write proper status on invalid requests (#4569) (fix #4561)
mintbomb27 Feb 11, 2022
738f730
Merge branch 'master' into master
Gr33nbl00d Feb 14, 2022
cd1e826
Merge branch 'master' into master
Gr33nbl00d Feb 21, 2022
5f506a6
Merge branch 'master' into master
Gr33nbl00d Mar 3, 2022
99db241
Merge branch 'master' into master
Gr33nbl00d Mar 30, 2022
71f5f1e
Apply suggestions from code review
mholt Jun 2, 2022
ff1d614
Register module; fix compilation
mholt Jun 2, 2022
7d2e450
Add log for deprecation notice
mholt Jun 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"`
mholt marked this conversation as resolved.
Show resolved Hide resolved

// 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 {
Gr33nbl00d marked this conversation as resolved.
Show resolved Hide resolved
// 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"}