@marcel91200 / caddy Mirror · read-only / commits / 872c864c
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 02:15 UTC

caddytls: skip client cert verifiers when no certificate is presented (#8251)

awss committed Oct 19, 2026 at 07:22 UTC 872c864c200b22dc5161942816be6409eafcb295
2 files changed +168 -2
modules/caddytls/connpolicy.go
+8 -1
@@ -918,6 +918,12 @@ func (clientauth *ClientAuthentication) verifyConnection(cs tls.ConnectionState)
918 return err
919 }
920 }
921 +
922 + // crypto/tls has already enforced whether the mode requires a
923 + // certificate, so the verifiers only check one that was presented
924 + if len(rawCerts) == 0 {
925 + return nil
926 + }
927 for _, verifier := range clientauth.verifiers {
928 if err := verifier.VerifyClientCertificate(rawCerts, cs.VerifiedChains); err != nil {
929 return err
@@ -1082,7 +1088,8 @@ type LeafCertificateLoader interface {
1088 }
1089
1090 // ClientCertificateVerifier is a type which verifies client certificates.
1085 -// It is called during verifyPeerCertificate in the TLS handshake.
1091 +// It is called during verifyConnection in the TLS handshake, only when
1092 +// the client presented a certificate.
1093 type ClientCertificateVerifier interface {
1094 VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
1095 }
modules/caddytls/connpolicy_verifyconnection_test.go
+160 -1
@@ -1,20 +1,30 @@
1 package caddytls
2
3 import (
4 + "crypto"
5 + "crypto/ecdsa"
6 + "crypto/elliptic"
7 + "crypto/rand"
8 "crypto/tls"
9 "crypto/x509"
10 + "crypto/x509/pkix"
11 "errors"
12 + "math/big"
13 + "net"
14 "reflect"
15 "testing"
16 + "time"
17 )
18
19 type testClientCertificateVerifier struct {
20 + called bool
21 rawCerts [][]byte
22 verifiedChains [][]*x509.Certificate
23 err error
24 }
25
26 func (v *testClientCertificateVerifier) VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
27 + v.called = true
28 v.rawCerts = rawCerts
29 v.verifiedChains = verifiedChains
30 return v.err
@@ -52,8 +62,157 @@ func TestClientAuthenticationVerifyConnectionReturnsVerifierError(t *testing.T)
62 verifiers: []ClientCertificateVerifier{verifier},
63 }
64
55 - err := clientauth.verifyConnection(tls.ConnectionState{})
65 + peerCert := &x509.Certificate{Raw: []byte("peer-cert-raw")}
66 + err := clientauth.verifyConnection(tls.ConnectionState{
67 + PeerCertificates: []*x509.Certificate{peerCert},
68 + })
69 if !errors.Is(err, wantErr) {
70 t.Fatalf("expected error %v, got %v", wantErr, err)
71 }
72 }
73 +
74 +func TestClientAuthenticationVerifyConnectionSkipsVerifiersWithoutPeerCert(t *testing.T) {
75 + verifier := &testClientCertificateVerifier{err: errors.New("verify failed")}
76 + clientauth := &ClientAuthentication{
77 + verifiers: []ClientCertificateVerifier{verifier},
78 + }
79 +
80 + if err := clientauth.verifyConnection(tls.ConnectionState{}); err != nil {
81 + t.Fatalf("verifyConnection failed: %v", err)
82 + }
83 + if verifier.called {
84 + t.Fatal("verifier was called without a peer certificate")
85 + }
86 +}
87 +
88 +func TestClientAuthenticationModeWithLeafVerifier(t *testing.T) {
89 + ca := issueTestCert(t, nil, 0)
90 + serverCert := issueTestCert(t, &ca, x509.ExtKeyUsageServerAuth)
91 + trusted := issueTestCert(t, &ca, x509.ExtKeyUsageClientAuth)
92 + untrusted := issueTestCert(t, &ca, x509.ExtKeyUsageClientAuth)
93 +
94 + caPool := x509.NewCertPool()
95 + caPool.AddCert(ca.Leaf)
96 +
97 + for _, tc := range []struct {
98 + mode string
99 + cert *tls.Certificate
100 + wantError bool
101 + }{
102 + {mode: "request", cert: nil, wantError: false},
103 + {mode: "request", cert: &trusted, wantError: false},
104 + {mode: "request", cert: &untrusted, wantError: true},
105 + {mode: "require", cert: nil, wantError: true},
106 + {mode: "require", cert: &trusted, wantError: false},
107 + {mode: "require", cert: &untrusted, wantError: true},
108 + {mode: "verify_if_given", cert: nil, wantError: false},
109 + {mode: "verify_if_given", cert: &trusted, wantError: false},
110 + {mode: "verify_if_given", cert: &untrusted, wantError: true},
111 + {mode: "require_and_verify", cert: nil, wantError: true},
112 + {mode: "require_and_verify", cert: &trusted, wantError: false},
113 + {mode: "require_and_verify", cert: &untrusted, wantError: true},
114 + } {
115 + for _, version := range []uint16{tls.VersionTLS12, tls.VersionTLS13} {
116 + clientauth := &ClientAuthentication{
117 + Mode: tc.mode,
118 + ca: InlineCAPool{pool: caPool},
119 + verifiers: []ClientCertificateVerifier{LeafCertClientAuth{trustedLeafCerts: []*x509.Certificate{trusted.Leaf}}},
120 + }
121 + serverConfig := &tls.Config{Certificates: []tls.Certificate{serverCert}}
122 + if err := clientauth.ConfigureTLSConfig(serverConfig); err != nil {
123 + t.Fatalf("configuring TLS: %v", err)
124 + }
125 + clientConfig := &tls.Config{
126 + InsecureSkipVerify: true,
127 + MinVersion: version,
128 + MaxVersion: version,
129 + }
130 + if tc.cert != nil {
131 + clientConfig.Certificates = []tls.Certificate{*tc.cert}
132 + }
133 +
134 + err := serverHandshake(t, serverConfig, clientConfig)
135 + certName := "no certificate"
136 + if tc.cert != nil {
137 + certName = tc.cert.Leaf.Subject.CommonName
138 + }
139 + if tc.wantError && err == nil {
140 + t.Errorf("mode %s, %s, %s: expected handshake error but got none", tc.mode, tls.VersionName(version), certName)
141 + }
142 + if !tc.wantError && err != nil {
143 + t.Errorf("mode %s, %s, %s: unexpected handshake error: %v", tc.mode, tls.VersionName(version), certName, err)
144 + }
145 + }
146 + }
147 +}
148 +
149 +// serverHandshake runs one TLS handshake over loopback TCP and returns
150 +// the server's handshake error.
151 +func serverHandshake(t *testing.T, serverConfig, clientConfig *tls.Config) error {
152 + t.Helper()
153 + ln, err := net.Listen("tcp", "127.0.0.1:0")
154 + if err != nil {
155 + t.Fatal(err)
156 + }
157 + defer ln.Close()
158 +
159 + errCh := make(chan error, 1)
160 + go func() {
161 + conn, err := ln.Accept()
162 + if err != nil {
163 + errCh <- err
164 + return
165 + }
166 + tlsConn := tls.Server(conn, serverConfig)
167 + errCh <- tlsConn.Handshake()
168 + tlsConn.Close()
169 + }()
170 +
171 + conn, err := tls.Dial("tcp", ln.Addr().String(), clientConfig)
172 + if err == nil {
173 + // wait for the server to finish before closing
174 + _, _ = conn.Read(make([]byte, 1))
175 + conn.Close()
176 + }
177 + return <-errCh
178 +}
179 +
180 +// issueTestCert returns a certificate signed by parent, or a self-signed
181 +// CA certificate if parent is nil.
182 +func issueTestCert(t *testing.T, parent *tls.Certificate, extKeyUsage x509.ExtKeyUsage) tls.Certificate {
183 + t.Helper()
184 + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
185 + if err != nil {
186 + t.Fatal(err)
187 + }
188 + serial, err := rand.Int(rand.Reader, big.NewInt(1<<62))
189 + if err != nil {
190 + t.Fatal(err)
191 + }
192 + template := &x509.Certificate{
193 + SerialNumber: serial,
194 + Subject: pkix.Name{CommonName: "test " + serial.String()},
195 + DNSNames: []string{"localhost"},
196 + NotBefore: time.Now().Add(-time.Hour),
197 + NotAfter: time.Now().Add(time.Hour),
198 + KeyUsage: x509.KeyUsageDigitalSignature,
199 + }
200 + signer, signerKey := template, crypto.Signer(key)
201 + if parent == nil {
202 + template.IsCA = true
203 + template.BasicConstraintsValid = true
204 + template.KeyUsage |= x509.KeyUsageCertSign
205 + } else {
206 + template.ExtKeyUsage = []x509.ExtKeyUsage{extKeyUsage}
207 + signer, signerKey = parent.Leaf, parent.PrivateKey.(crypto.Signer)
208 + }
209 + der, err := x509.CreateCertificate(rand.Reader, template, signer, &key.PublicKey, signerKey)
210 + if err != nil {
211 + t.Fatal(err)
212 + }
213 + leaf, err := x509.ParseCertificate(der)
214 + if err != nil {
215 + t.Fatal(err)
216 + }
217 + return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key, Leaf: leaf}
218 +}