@marcel91200 / caddy Mirror · read-only / commits / 41aee973
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, 12:15 UTC

core: propagate ECH keys to the QUIC listener (#7670)

Zen Dodd committed Apr 24, 2026 at 05:33 UTC 41aee97386ae8a52231ab8ff7790e49cff802d77
2 files changed +72 -1
listeners.go
+14 -1
@@ -462,7 +462,10 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
462 sqs := newSharedQUICState(tlsConf)
463 // http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
464 //nolint:gosec
465 - quicTlsConfig := &tls.Config{GetConfigForClient: sqs.getConfigForClient}
465 + quicTlsConfig := &tls.Config{
466 + GetConfigForClient: sqs.getConfigForClient,
467 + GetEncryptedClientHelloKeys: sqs.getEncryptedClientHelloKeys,
468 + }
469 // Require clients to verify their source address when we're handling more than 1000 handshakes per second.
470 // TODO: make tunable?
471 limiter := rate.NewLimiter(1000, 1000)
@@ -540,6 +543,16 @@ func (sqs *sharedQUICState) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Co
543 return sqs.activeTlsConf.GetConfigForClient(ch)
544 }
545
546 +// getEncryptedClientHelloKeys is used as tls.Config's GetEncryptedClientHelloKeys field.
547 +func (sqs *sharedQUICState) getEncryptedClientHelloKeys(ch *tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) {
548 + sqs.rmu.RLock()
549 + defer sqs.rmu.RUnlock()
550 + if sqs.activeTlsConf.GetEncryptedClientHelloKeys == nil {
551 + return nil, nil
552 + }
553 + return sqs.activeTlsConf.GetEncryptedClientHelloKeys(ch)
554 +}
555 +
556 // addState adds tls.Config and activeRequests to the map if not present and returns the corresponding context and its cancelFunc
557 // so that when cancelled, the active tls.Config will change
558 func (sqs *sharedQUICState) addState(tlsConfig *tls.Config) (context.Context, context.CancelCauseFunc) {
listeners_test.go
+58
@@ -15,6 +15,7 @@
15 package caddy
16
17 import (
18 + "crypto/tls"
19 "reflect"
20 "testing"
21
@@ -175,6 +176,63 @@ func TestJoinNetworkAddress(t *testing.T) {
176 }
177 }
178
179 +func TestSharedQUICStateGetEncryptedClientHelloKeys(t *testing.T) {
180 + hello := &tls.ClientHelloInfo{ServerName: "example.com"}
181 + initialKeys := []tls.EncryptedClientHelloKey{{Config: []byte("initial"), PrivateKey: []byte("initial-key")}}
182 + updatedKeys := []tls.EncryptedClientHelloKey{{Config: []byte("updated"), PrivateKey: []byte("updated-key")}}
183 +
184 + initialConfig := &tls.Config{
185 + GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
186 + return nil, nil
187 + },
188 + GetEncryptedClientHelloKeys: func(*tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) {
189 + return initialKeys, nil
190 + },
191 + }
192 +
193 + sqs := newSharedQUICState(initialConfig)
194 +
195 + keys, err := sqs.getEncryptedClientHelloKeys(hello)
196 + if err != nil {
197 + t.Fatalf("getting initial ECH keys: %v", err)
198 + }
199 + if !reflect.DeepEqual(keys, initialKeys) {
200 + t.Fatalf("unexpected initial ECH keys: got %#v, want %#v", keys, initialKeys)
201 + }
202 +
203 + updatedConfig := &tls.Config{
204 + GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
205 + return nil, nil
206 + },
207 + GetEncryptedClientHelloKeys: func(*tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) {
208 + return updatedKeys, nil
209 + },
210 + }
211 +
212 + _, cancel := sqs.addState(updatedConfig)
213 + sqs.rmu.Lock()
214 + sqs.activeTlsConf = updatedConfig
215 + sqs.rmu.Unlock()
216 +
217 + keys, err = sqs.getEncryptedClientHelloKeys(hello)
218 + if err != nil {
219 + t.Fatalf("getting updated ECH keys: %v", err)
220 + }
221 + if !reflect.DeepEqual(keys, updatedKeys) {
222 + t.Fatalf("unexpected updated ECH keys: got %#v, want %#v", keys, updatedKeys)
223 + }
224 +
225 + cancel(nil)
226 +
227 + keys, err = sqs.getEncryptedClientHelloKeys(hello)
228 + if err != nil {
229 + t.Fatalf("getting restored ECH keys: %v", err)
230 + }
231 + if !reflect.DeepEqual(keys, initialKeys) {
232 + t.Fatalf("unexpected restored ECH keys: got %#v, want %#v", keys, initialKeys)
233 + }
234 +}
235 +
236 func TestParseNetworkAddress(t *testing.T) {
237 for i, tc := range []struct {
238 input string