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
+}