1// Copyright 2025 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package runner
16
17import (
18	"strconv"
19	"strings"
20	"time"
21
22	"boringssl.googlesource.com/boringssl.git/ssl/test/runner/hpke"
23)
24
25type echCipher struct {
26	name   string
27	cipher HPKECipherSuite
28}
29
30var echCiphers = []echCipher{
31	{
32		name:   "HKDF-SHA256-AES-128-GCM",
33		cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.AES128GCM},
34	},
35	{
36		name:   "HKDF-SHA256-AES-256-GCM",
37		cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.AES256GCM},
38	},
39	{
40		name:   "HKDF-SHA256-ChaCha20-Poly1305",
41		cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.ChaCha20Poly1305},
42	},
43}
44
45// generateServerECHConfig constructs a ServerECHConfig with a fresh X25519
46// keypair and using |template| as a template for the ECHConfig. If fields are
47// omitted, defaults are used.
48func generateServerECHConfig(template *ECHConfig) ServerECHConfig {
49	publicKey, secretKey, err := hpke.GenerateKeyPairX25519()
50	if err != nil {
51		panic(err)
52	}
53	templateCopy := *template
54	if templateCopy.KEM == 0 {
55		templateCopy.KEM = hpke.X25519WithHKDFSHA256
56	}
57	if len(templateCopy.PublicKey) == 0 {
58		templateCopy.PublicKey = publicKey
59	}
60	if len(templateCopy.CipherSuites) == 0 {
61		templateCopy.CipherSuites = make([]HPKECipherSuite, len(echCiphers))
62		for i, cipher := range echCiphers {
63			templateCopy.CipherSuites[i] = cipher.cipher
64		}
65	}
66	if len(templateCopy.PublicName) == 0 {
67		templateCopy.PublicName = "public.example"
68	}
69	if templateCopy.MaxNameLen == 0 {
70		templateCopy.MaxNameLen = 64
71	}
72	return ServerECHConfig{ECHConfig: CreateECHConfig(&templateCopy), Key: secretKey}
73}
74
75func addEncryptedClientHelloTests() {
76	// echConfig's ConfigID should match the one used in ssl/test/fuzzer.h.
77	echConfig := generateServerECHConfig(&ECHConfig{ConfigID: 42})
78	echConfig1 := generateServerECHConfig(&ECHConfig{ConfigID: 43})
79	echConfig2 := generateServerECHConfig(&ECHConfig{ConfigID: 44})
80	echConfig3 := generateServerECHConfig(&ECHConfig{ConfigID: 45})
81	echConfigRepeatID := generateServerECHConfig(&ECHConfig{ConfigID: 42})
82
83	echSecretCertificate := rootCA.Issue(X509Info{
84		PrivateKey: &rsa2048Key,
85		DNSNames:   []string{"secret.example"},
86	}).ToCredential()
87	echPublicCertificate := rootCA.Issue(X509Info{
88		PrivateKey: &rsa2048Key,
89		DNSNames:   []string{"public.example"},
90	}).ToCredential()
91	echLongNameCertificate := rootCA.Issue(X509Info{
92		PrivateKey: &ecdsaP256Key,
93		DNSNames:   []string{"test0123456789.example"},
94	}).ToCredential()
95
96	for _, protocol := range []protocol{tls, quic, dtls} {
97		prefix := protocol.String() + "-"
98
99		// There are two ClientHellos, so many of our tests have
100		// HelloRetryRequest variations.
101		for _, hrr := range []bool{false, true} {
102			var suffix string
103			var defaultCurves []CurveID
104			if hrr {
105				suffix = "-HelloRetryRequest"
106				// Require a HelloRetryRequest for every curve.
107				defaultCurves = []CurveID{}
108			}
109
110			// Test the server can accept ECH.
111			testCases = append(testCases, testCase{
112				testType: serverTest,
113				protocol: protocol,
114				name:     prefix + "ECH-Server" + suffix,
115				config: Config{
116					ServerName:      "secret.example",
117					ClientECHConfig: echConfig.ECHConfig,
118					DefaultCurves:   defaultCurves,
119				},
120				resumeSession: true,
121				flags: []string{
122					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
123					"-ech-server-key", base64FlagValue(echConfig.Key),
124					"-ech-is-retry-config", "1",
125					"-expect-server-name", "secret.example",
126					"-expect-ech-accept",
127				},
128				expectations: connectionExpectations{
129					echAccepted: true,
130				},
131			})
132
133			// Test the server can accept ECH with a minimal ClientHelloOuter.
134			// This confirms that the server does not unexpectedly pick up
135			// fields from the wrong ClientHello.
136			testCases = append(testCases, testCase{
137				testType: serverTest,
138				protocol: protocol,
139				name:     prefix + "ECH-Server-MinimalClientHelloOuter" + suffix,
140				config: Config{
141					ServerName:      "secret.example",
142					ClientECHConfig: echConfig.ECHConfig,
143					DefaultCurves:   defaultCurves,
144					Bugs: ProtocolBugs{
145						MinimalClientHelloOuter: true,
146					},
147				},
148				resumeSession: true,
149				flags: []string{
150					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
151					"-ech-server-key", base64FlagValue(echConfig.Key),
152					"-ech-is-retry-config", "1",
153					"-expect-server-name", "secret.example",
154					"-expect-ech-accept",
155				},
156				expectations: connectionExpectations{
157					echAccepted: true,
158				},
159			})
160
161			// Test that the server can decline ECH. In particular, it must send
162			// retry configs.
163			testCases = append(testCases, testCase{
164				testType: serverTest,
165				protocol: protocol,
166				name:     prefix + "ECH-Server-Decline" + suffix,
167				config: Config{
168					ServerName:    "secret.example",
169					DefaultCurves: defaultCurves,
170					// The client uses an ECHConfig that the server does not understand
171					// so we can observe which retry configs the server sends back.
172					ClientECHConfig: echConfig.ECHConfig,
173					Bugs: ProtocolBugs{
174						OfferSessionInClientHelloOuter: true,
175						ExpectECHRetryConfigs:          CreateECHConfigList(echConfig2.ECHConfig.Raw, echConfig3.ECHConfig.Raw),
176					},
177				},
178				resumeSession: true,
179				flags: []string{
180					// Configure three ECHConfigs on the shim, only two of which
181					// should be sent in retry configs.
182					"-ech-server-config", base64FlagValue(echConfig1.ECHConfig.Raw),
183					"-ech-server-key", base64FlagValue(echConfig1.Key),
184					"-ech-is-retry-config", "0",
185					"-ech-server-config", base64FlagValue(echConfig2.ECHConfig.Raw),
186					"-ech-server-key", base64FlagValue(echConfig2.Key),
187					"-ech-is-retry-config", "1",
188					"-ech-server-config", base64FlagValue(echConfig3.ECHConfig.Raw),
189					"-ech-server-key", base64FlagValue(echConfig3.Key),
190					"-ech-is-retry-config", "1",
191					"-expect-server-name", "public.example",
192				},
193			})
194
195			// Test that the server considers a ClientHelloInner indicating TLS
196			// 1.2 to be a fatal error.
197			testCases = append(testCases, testCase{
198				testType: serverTest,
199				protocol: protocol,
200				name:     prefix + "ECH-Server-TLS12InInner" + suffix,
201				config: Config{
202					ServerName:      "secret.example",
203					DefaultCurves:   defaultCurves,
204					ClientECHConfig: echConfig.ECHConfig,
205					Bugs: ProtocolBugs{
206						AllowTLS12InClientHelloInner: true,
207					},
208				},
209				flags: []string{
210					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
211					"-ech-server-key", base64FlagValue(echConfig.Key),
212					"-ech-is-retry-config", "1",
213				},
214				shouldFail:         true,
215				expectedLocalError: "remote error: illegal parameter",
216				expectedError:      ":INVALID_CLIENT_HELLO_INNER:",
217			})
218
219			// When inner ECH extension is absent from the ClientHelloInner, the
220			// server should fail the connection.
221			testCases = append(testCases, testCase{
222				testType: serverTest,
223				protocol: protocol,
224				name:     prefix + "ECH-Server-MissingECHInner" + suffix,
225				config: Config{
226					ServerName:      "secret.example",
227					DefaultCurves:   defaultCurves,
228					ClientECHConfig: echConfig.ECHConfig,
229					Bugs: ProtocolBugs{
230						OmitECHInner:       !hrr,
231						OmitSecondECHInner: hrr,
232					},
233				},
234				flags: []string{
235					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
236					"-ech-server-key", base64FlagValue(echConfig.Key),
237					"-ech-is-retry-config", "1",
238				},
239				shouldFail:         true,
240				expectedLocalError: "remote error: illegal parameter",
241				expectedError:      ":INVALID_CLIENT_HELLO_INNER:",
242			})
243
244			// Test that the server can decode ech_outer_extensions.
245			testCases = append(testCases, testCase{
246				testType: serverTest,
247				protocol: protocol,
248				name:     prefix + "ECH-Server-OuterExtensions" + suffix,
249				config: Config{
250					ServerName:      "secret.example",
251					DefaultCurves:   defaultCurves,
252					ClientECHConfig: echConfig.ECHConfig,
253					ECHOuterExtensions: []uint16{
254						extensionKeyShare,
255						extensionSupportedCurves,
256						// Include a custom extension, to test that unrecognized
257						// extensions are also decoded.
258						extensionCustom,
259					},
260					Bugs: ProtocolBugs{
261						CustomExtension:                    "test",
262						OnlyCompressSecondClientHelloInner: hrr,
263					},
264				},
265				flags: []string{
266					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
267					"-ech-server-key", base64FlagValue(echConfig.Key),
268					"-ech-is-retry-config", "1",
269					"-expect-server-name", "secret.example",
270					"-expect-ech-accept",
271				},
272				expectations: connectionExpectations{
273					echAccepted: true,
274				},
275			})
276
277			// Test that the server allows referenced ClientHelloOuter
278			// extensions to be interleaved with other extensions. Only the
279			// relative order must match.
280			testCases = append(testCases, testCase{
281				testType: serverTest,
282				protocol: protocol,
283				name:     prefix + "ECH-Server-OuterExtensions-Interleaved" + suffix,
284				config: Config{
285					ServerName:      "secret.example",
286					DefaultCurves:   defaultCurves,
287					ClientECHConfig: echConfig.ECHConfig,
288					ECHOuterExtensions: []uint16{
289						extensionKeyShare,
290						extensionSupportedCurves,
291						extensionCustom,
292					},
293					Bugs: ProtocolBugs{
294						CustomExtension:                    "test",
295						OnlyCompressSecondClientHelloInner: hrr,
296						ECHOuterExtensionOrder: []uint16{
297							extensionServerName,
298							extensionKeyShare,
299							extensionSupportedVersions,
300							extensionPSKKeyExchangeModes,
301							extensionSupportedCurves,
302							extensionSignatureAlgorithms,
303							extensionCustom,
304						},
305					},
306				},
307				flags: []string{
308					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
309					"-ech-server-key", base64FlagValue(echConfig.Key),
310					"-ech-is-retry-config", "1",
311					"-expect-server-name", "secret.example",
312					"-expect-ech-accept",
313				},
314				expectations: connectionExpectations{
315					echAccepted: true,
316				},
317			})
318
319			// Test that the server rejects references to extensions in the
320			// wrong order.
321			testCases = append(testCases, testCase{
322				testType: serverTest,
323				protocol: protocol,
324				name:     prefix + "ECH-Server-OuterExtensions-WrongOrder" + suffix,
325				config: Config{
326					ServerName:      "secret.example",
327					DefaultCurves:   defaultCurves,
328					ClientECHConfig: echConfig.ECHConfig,
329					ECHOuterExtensions: []uint16{
330						extensionKeyShare,
331						extensionSupportedCurves,
332					},
333					Bugs: ProtocolBugs{
334						CustomExtension:                    "test",
335						OnlyCompressSecondClientHelloInner: hrr,
336						ECHOuterExtensionOrder: []uint16{
337							extensionSupportedCurves,
338							extensionKeyShare,
339						},
340					},
341				},
342				flags: []string{
343					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
344					"-ech-server-key", base64FlagValue(echConfig.Key),
345					"-ech-is-retry-config", "1",
346					"-expect-server-name", "secret.example",
347				},
348				shouldFail:         true,
349				expectedLocalError: "remote error: illegal parameter",
350				expectedError:      ":INVALID_OUTER_EXTENSION:",
351			})
352
353			// Test that the server rejects duplicated values in ech_outer_extensions.
354			// Besides causing the server to reconstruct an invalid ClientHelloInner
355			// with duplicated extensions, this behavior would be vulnerable to DoS
356			// attacks.
357			testCases = append(testCases, testCase{
358				testType: serverTest,
359				protocol: protocol,
360				name:     prefix + "ECH-Server-OuterExtensions-Duplicate" + suffix,
361				config: Config{
362					ServerName:      "secret.example",
363					DefaultCurves:   defaultCurves,
364					ClientECHConfig: echConfig.ECHConfig,
365					ECHOuterExtensions: []uint16{
366						extensionSupportedCurves,
367						extensionSupportedCurves,
368					},
369					Bugs: ProtocolBugs{
370						OnlyCompressSecondClientHelloInner: hrr,
371						// Don't duplicate the extension in ClientHelloOuter.
372						ECHOuterExtensionOrder: []uint16{
373							extensionSupportedCurves,
374						},
375					},
376				},
377				flags: []string{
378					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
379					"-ech-server-key", base64FlagValue(echConfig.Key),
380					"-ech-is-retry-config", "1",
381				},
382				shouldFail:         true,
383				expectedLocalError: "remote error: illegal parameter",
384				expectedError:      ":INVALID_OUTER_EXTENSION:",
385			})
386
387			// Test that the server rejects references to missing extensions in
388			// ech_outer_extensions.
389			testCases = append(testCases, testCase{
390				testType: serverTest,
391				protocol: protocol,
392				name:     prefix + "ECH-Server-OuterExtensions-Missing" + suffix,
393				config: Config{
394					ServerName:      "secret.example",
395					DefaultCurves:   defaultCurves,
396					ClientECHConfig: echConfig.ECHConfig,
397					ECHOuterExtensions: []uint16{
398						extensionCustom,
399					},
400					Bugs: ProtocolBugs{
401						OnlyCompressSecondClientHelloInner: hrr,
402					},
403				},
404				flags: []string{
405					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
406					"-ech-server-key", base64FlagValue(echConfig.Key),
407					"-ech-is-retry-config", "1",
408					"-expect-server-name", "secret.example",
409					"-expect-ech-accept",
410				},
411				shouldFail:         true,
412				expectedLocalError: "remote error: illegal parameter",
413				expectedError:      ":INVALID_OUTER_EXTENSION:",
414			})
415
416			// Test that the server rejects a references to the ECH extension in
417			// ech_outer_extensions. The ECH extension is not authenticated in the
418			// AAD and would result in an invalid ClientHelloInner.
419			testCases = append(testCases, testCase{
420				testType: serverTest,
421				protocol: protocol,
422				name:     prefix + "ECH-Server-OuterExtensions-SelfReference" + suffix,
423				config: Config{
424					ServerName:      "secret.example",
425					DefaultCurves:   defaultCurves,
426					ClientECHConfig: echConfig.ECHConfig,
427					ECHOuterExtensions: []uint16{
428						extensionEncryptedClientHello,
429					},
430					Bugs: ProtocolBugs{
431						OnlyCompressSecondClientHelloInner: hrr,
432					},
433				},
434				flags: []string{
435					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
436					"-ech-server-key", base64FlagValue(echConfig.Key),
437					"-ech-is-retry-config", "1",
438				},
439				shouldFail:         true,
440				expectedLocalError: "remote error: illegal parameter",
441				expectedError:      ":INVALID_OUTER_EXTENSION:",
442			})
443
444			// Test the message callback is correctly reported with ECH.
445			clientAndServerHello := "read hs 1\nread clienthelloinner\nwrite hs 2\n"
446			expectMsgCallback := clientAndServerHello
447			if protocol == tls {
448				expectMsgCallback += "write ccs\n"
449			}
450			if hrr {
451				expectMsgCallback += clientAndServerHello
452			}
453			// EncryptedExtensions onwards.
454			expectMsgCallback += `write hs 8
455write hs 11
456write hs 15
457write hs 20
458read hs 20
459write ack
460write hs 4
461write hs 4
462read ack
463read ack
464`
465			if protocol != dtls {
466				expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "write ack\n", "")
467				expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "read ack\n", "")
468			}
469			testCases = append(testCases, testCase{
470				testType: serverTest,
471				protocol: protocol,
472				name:     prefix + "ECH-Server-MessageCallback" + suffix,
473				config: Config{
474					ServerName:      "secret.example",
475					ClientECHConfig: echConfig.ECHConfig,
476					DefaultCurves:   defaultCurves,
477					Bugs: ProtocolBugs{
478						NoCloseNotify: true, // Align QUIC and TCP traces.
479					},
480				},
481				flags: []string{
482					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
483					"-ech-server-key", base64FlagValue(echConfig.Key),
484					"-ech-is-retry-config", "1",
485					"-expect-ech-accept",
486					"-expect-msg-callback", expectMsgCallback,
487				},
488				expectations: connectionExpectations{
489					echAccepted: true,
490				},
491			})
492		}
493
494		// Test that ECH, which runs before an async early callback, interacts
495		// correctly in the state machine.
496		testCases = append(testCases, testCase{
497			testType: serverTest,
498			protocol: protocol,
499			name:     prefix + "ECH-Server-AsyncEarlyCallback",
500			config: Config{
501				ServerName:      "secret.example",
502				ClientECHConfig: echConfig.ECHConfig,
503			},
504			flags: []string{
505				"-async",
506				"-use-early-callback",
507				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
508				"-ech-server-key", base64FlagValue(echConfig.Key),
509				"-ech-is-retry-config", "1",
510				"-expect-server-name", "secret.example",
511				"-expect-ech-accept",
512			},
513			expectations: connectionExpectations{
514				echAccepted: true,
515			},
516		})
517
518		// Test that we successfully rewind the TLS state machine and disable ECH in the
519		// case that the select_cert_cb signals that ECH is not possible for the SNI in
520		// ClientHelloInner.
521		testCases = append(testCases, testCase{
522			testType: serverTest,
523			protocol: protocol,
524			name:     prefix + "ECH-Server-FailCallbackNeedRewind",
525			config: Config{
526				ServerName:      "secret.example",
527				ClientECHConfig: echConfig.ECHConfig,
528			},
529			flags: []string{
530				"-async",
531				"-fail-early-callback-ech-rewind",
532				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
533				"-ech-server-key", base64FlagValue(echConfig.Key),
534				"-ech-is-retry-config", "1",
535				"-expect-server-name", "public.example",
536			},
537			expectations: connectionExpectations{
538				echAccepted: false,
539			},
540		})
541
542		// Test that we correctly handle falling back to a ClientHelloOuter with
543		// no SNI (public name).
544		testCases = append(testCases, testCase{
545			testType: serverTest,
546			protocol: protocol,
547			name:     prefix + "ECH-Server-RewindWithNoPublicName",
548			config: Config{
549				ServerName:      "secret.example",
550				ClientECHConfig: echConfig.ECHConfig,
551				Bugs: ProtocolBugs{
552					OmitPublicName: true,
553				},
554			},
555			flags: []string{
556				"-async",
557				"-fail-early-callback-ech-rewind",
558				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
559				"-ech-server-key", base64FlagValue(echConfig.Key),
560				"-ech-is-retry-config", "1",
561				"-expect-no-server-name",
562			},
563			expectations: connectionExpectations{
564				echAccepted: false,
565			},
566		})
567
568		// Test ECH-enabled server with two ECHConfigs can decrypt client's ECH when
569		// it uses the second ECHConfig.
570		testCases = append(testCases, testCase{
571			testType: serverTest,
572			protocol: protocol,
573			name:     prefix + "ECH-Server-SecondECHConfig",
574			config: Config{
575				ServerName:      "secret.example",
576				ClientECHConfig: echConfig1.ECHConfig,
577			},
578			flags: []string{
579				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
580				"-ech-server-key", base64FlagValue(echConfig.Key),
581				"-ech-is-retry-config", "1",
582				"-ech-server-config", base64FlagValue(echConfig1.ECHConfig.Raw),
583				"-ech-server-key", base64FlagValue(echConfig1.Key),
584				"-ech-is-retry-config", "1",
585				"-expect-server-name", "secret.example",
586				"-expect-ech-accept",
587			},
588			expectations: connectionExpectations{
589				echAccepted: true,
590			},
591		})
592
593		// Test ECH-enabled server with two ECHConfigs that have the same config
594		// ID can decrypt client's ECH when it uses the second ECHConfig.
595		testCases = append(testCases, testCase{
596			testType: serverTest,
597			protocol: protocol,
598			name:     prefix + "ECH-Server-RepeatedConfigID",
599			config: Config{
600				ServerName:      "secret.example",
601				ClientECHConfig: echConfigRepeatID.ECHConfig,
602			},
603			flags: []string{
604				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
605				"-ech-server-key", base64FlagValue(echConfig.Key),
606				"-ech-is-retry-config", "1",
607				"-ech-server-config", base64FlagValue(echConfigRepeatID.ECHConfig.Raw),
608				"-ech-server-key", base64FlagValue(echConfigRepeatID.Key),
609				"-ech-is-retry-config", "1",
610				"-expect-server-name", "secret.example",
611				"-expect-ech-accept",
612			},
613			expectations: connectionExpectations{
614				echAccepted: true,
615			},
616		})
617
618		// Test all supported ECH cipher suites.
619		for i, cipher := range echCiphers {
620			otherCipher := echCiphers[(i+1)%len(echCiphers)]
621
622			// Test the ECH server can handle the specified cipher.
623			testCases = append(testCases, testCase{
624				testType: serverTest,
625				protocol: protocol,
626				name:     prefix + "ECH-Server-Cipher-" + cipher.name,
627				config: Config{
628					ServerName:      "secret.example",
629					ClientECHConfig: echConfig.ECHConfig,
630					ECHCipherSuites: []HPKECipherSuite{cipher.cipher},
631				},
632				flags: []string{
633					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
634					"-ech-server-key", base64FlagValue(echConfig.Key),
635					"-ech-is-retry-config", "1",
636					"-expect-server-name", "secret.example",
637					"-expect-ech-accept",
638				},
639				expectations: connectionExpectations{
640					echAccepted: true,
641				},
642			})
643
644			// Test that client can offer the specified cipher and skip over
645			// unrecognized ones.
646			cipherConfig := generateServerECHConfig(&ECHConfig{
647				ConfigID: 42,
648				CipherSuites: []HPKECipherSuite{
649					{KDF: 0x1111, AEAD: 0x2222},
650					{KDF: cipher.cipher.KDF, AEAD: 0x2222},
651					{KDF: 0x1111, AEAD: cipher.cipher.AEAD},
652					cipher.cipher,
653				},
654			})
655			testCases = append(testCases, testCase{
656				testType: clientTest,
657				protocol: protocol,
658				name:     prefix + "ECH-Client-Cipher-" + cipher.name,
659				config: Config{
660					ServerECHConfigs: []ServerECHConfig{cipherConfig},
661					Credential:       &echSecretCertificate,
662				},
663				flags: []string{
664					"-ech-config-list", base64FlagValue(CreateECHConfigList(cipherConfig.ECHConfig.Raw)),
665					"-host-name", "secret.example",
666					"-expect-ech-accept",
667				},
668				expectations: connectionExpectations{
669					echAccepted: true,
670				},
671			})
672
673			// Test that the ECH server rejects the specified cipher if not
674			// listed in its ECHConfig.
675			otherCipherConfig := generateServerECHConfig(&ECHConfig{
676				ConfigID:     42,
677				CipherSuites: []HPKECipherSuite{otherCipher.cipher},
678			})
679			testCases = append(testCases, testCase{
680				testType: serverTest,
681				protocol: protocol,
682				name:     prefix + "ECH-Server-DisabledCipher-" + cipher.name,
683				config: Config{
684					ServerName:      "secret.example",
685					ClientECHConfig: echConfig.ECHConfig,
686					ECHCipherSuites: []HPKECipherSuite{cipher.cipher},
687					Bugs: ProtocolBugs{
688						ExpectECHRetryConfigs: CreateECHConfigList(otherCipherConfig.ECHConfig.Raw),
689					},
690				},
691				flags: []string{
692					"-ech-server-config", base64FlagValue(otherCipherConfig.ECHConfig.Raw),
693					"-ech-server-key", base64FlagValue(otherCipherConfig.Key),
694					"-ech-is-retry-config", "1",
695					"-expect-server-name", "public.example",
696				},
697			})
698		}
699
700		// Test that the ECH server handles a short enc value by falling back to
701		// ClientHelloOuter.
702		testCases = append(testCases, testCase{
703			testType: serverTest,
704			protocol: protocol,
705			name:     prefix + "ECH-Server-ShortEnc",
706			config: Config{
707				ServerName:      "secret.example",
708				ClientECHConfig: echConfig.ECHConfig,
709				Bugs: ProtocolBugs{
710					ExpectECHRetryConfigs: CreateECHConfigList(echConfig.ECHConfig.Raw),
711					TruncateClientECHEnc:  true,
712				},
713			},
714			flags: []string{
715				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
716				"-ech-server-key", base64FlagValue(echConfig.Key),
717				"-ech-is-retry-config", "1",
718				"-expect-server-name", "public.example",
719			},
720		})
721
722		// Test that the server handles decryption failure by falling back to
723		// ClientHelloOuter.
724		testCases = append(testCases, testCase{
725			testType: serverTest,
726			protocol: protocol,
727			name:     prefix + "ECH-Server-CorruptEncryptedClientHello",
728			config: Config{
729				ServerName:      "secret.example",
730				ClientECHConfig: echConfig.ECHConfig,
731				Bugs: ProtocolBugs{
732					ExpectECHRetryConfigs:       CreateECHConfigList(echConfig.ECHConfig.Raw),
733					CorruptEncryptedClientHello: true,
734				},
735			},
736			flags: []string{
737				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
738				"-ech-server-key", base64FlagValue(echConfig.Key),
739				"-ech-is-retry-config", "1",
740			},
741		})
742
743		// Test that the server treats decryption failure in the second
744		// ClientHello as fatal.
745		testCases = append(testCases, testCase{
746			testType: serverTest,
747			protocol: protocol,
748			name:     prefix + "ECH-Server-CorruptSecondEncryptedClientHello",
749			config: Config{
750				ServerName:      "secret.example",
751				ClientECHConfig: echConfig.ECHConfig,
752				// Force a HelloRetryRequest.
753				DefaultCurves: []CurveID{},
754				Bugs: ProtocolBugs{
755					CorruptSecondEncryptedClientHello: true,
756				},
757			},
758			flags: []string{
759				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
760				"-ech-server-key", base64FlagValue(echConfig.Key),
761				"-ech-is-retry-config", "1",
762			},
763			shouldFail:         true,
764			expectedError:      ":DECRYPTION_FAILED:",
765			expectedLocalError: "remote error: error decrypting message",
766		})
767
768		// Test that the server treats a missing second ECH extension as fatal.
769		testCases = append(testCases, testCase{
770			testType: serverTest,
771			protocol: protocol,
772			name:     prefix + "ECH-Server-OmitSecondEncryptedClientHello",
773			config: Config{
774				ServerName:      "secret.example",
775				ClientECHConfig: echConfig.ECHConfig,
776				// Force a HelloRetryRequest.
777				DefaultCurves: []CurveID{},
778				Bugs: ProtocolBugs{
779					OmitSecondEncryptedClientHello: true,
780				},
781			},
782			flags: []string{
783				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
784				"-ech-server-key", base64FlagValue(echConfig.Key),
785				"-ech-is-retry-config", "1",
786			},
787			shouldFail:         true,
788			expectedError:      ":MISSING_EXTENSION:",
789			expectedLocalError: "remote error: missing extension",
790		})
791
792		// Test that the server treats a mismatched config ID in the second ClientHello as fatal.
793		testCases = append(testCases, testCase{
794			testType: serverTest,
795			protocol: protocol,
796			name:     prefix + "ECH-Server-DifferentConfigIDSecondClientHello",
797			config: Config{
798				ServerName:      "secret.example",
799				ClientECHConfig: echConfig.ECHConfig,
800				// Force a HelloRetryRequest.
801				DefaultCurves: []CurveID{},
802				Bugs: ProtocolBugs{
803					CorruptSecondEncryptedClientHelloConfigID: true,
804				},
805			},
806			flags: []string{
807				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
808				"-ech-server-key", base64FlagValue(echConfig.Key),
809				"-ech-is-retry-config", "1",
810			},
811			shouldFail:         true,
812			expectedError:      ":DECODE_ERROR:",
813			expectedLocalError: "remote error: illegal parameter",
814		})
815
816		// Test early data works with ECH, in both accept and reject cases.
817		// TODO(crbug.com/381113363): Enable these tests for DTLS once we
818		// support early data in DTLS 1.3.
819		if protocol != dtls {
820			testCases = append(testCases, testCase{
821				testType: serverTest,
822				protocol: protocol,
823				name:     prefix + "ECH-Server-EarlyData",
824				config: Config{
825					ServerName:      "secret.example",
826					ClientECHConfig: echConfig.ECHConfig,
827				},
828				resumeSession: true,
829				earlyData:     true,
830				flags: []string{
831					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
832					"-ech-server-key", base64FlagValue(echConfig.Key),
833					"-ech-is-retry-config", "1",
834					"-expect-ech-accept",
835				},
836				expectations: connectionExpectations{
837					echAccepted: true,
838				},
839			})
840			testCases = append(testCases, testCase{
841				testType: serverTest,
842				protocol: protocol,
843				name:     prefix + "ECH-Server-EarlyDataRejected",
844				config: Config{
845					ServerName:      "secret.example",
846					ClientECHConfig: echConfig.ECHConfig,
847					Bugs: ProtocolBugs{
848						// Cause the server to reject 0-RTT with a bad ticket age.
849						SendTicketAge: 1 * time.Hour,
850					},
851				},
852				resumeSession:           true,
853				earlyData:               true,
854				expectEarlyDataRejected: true,
855				flags: []string{
856					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
857					"-ech-server-key", base64FlagValue(echConfig.Key),
858					"-ech-is-retry-config", "1",
859					"-expect-ech-accept",
860				},
861				expectations: connectionExpectations{
862					echAccepted: true,
863				},
864			})
865		}
866
867		// Test servers with ECH disabled correctly ignore the extension and
868		// handshake with the ClientHelloOuter.
869		testCases = append(testCases, testCase{
870			testType: serverTest,
871			protocol: protocol,
872			name:     prefix + "ECH-Server-Disabled",
873			config: Config{
874				ServerName:      "secret.example",
875				ClientECHConfig: echConfig.ECHConfig,
876			},
877			flags: []string{
878				"-expect-server-name", "public.example",
879			},
880		})
881
882		// Test that ECH can be used with client certificates. In particular,
883		// the name override logic should not interfere with the server.
884		// Test the server can accept ECH.
885		testCases = append(testCases, testCase{
886			testType: serverTest,
887			protocol: protocol,
888			name:     prefix + "ECH-Server-ClientAuth",
889			config: Config{
890				Credential:      &rsaCertificate,
891				ClientECHConfig: echConfig.ECHConfig,
892			},
893			flags: []string{
894				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
895				"-ech-server-key", base64FlagValue(echConfig.Key),
896				"-ech-is-retry-config", "1",
897				"-expect-ech-accept",
898				"-require-any-client-certificate",
899			},
900			expectations: connectionExpectations{
901				echAccepted: true,
902			},
903		})
904		testCases = append(testCases, testCase{
905			testType: serverTest,
906			protocol: protocol,
907			name:     prefix + "ECH-Server-Decline-ClientAuth",
908			config: Config{
909				Credential:      &rsaCertificate,
910				ClientECHConfig: echConfig.ECHConfig,
911				Bugs: ProtocolBugs{
912					ExpectECHRetryConfigs: CreateECHConfigList(echConfig1.ECHConfig.Raw),
913				},
914			},
915			flags: []string{
916				"-ech-server-config", base64FlagValue(echConfig1.ECHConfig.Raw),
917				"-ech-server-key", base64FlagValue(echConfig1.Key),
918				"-ech-is-retry-config", "1",
919				"-require-any-client-certificate",
920			},
921		})
922
923		// Test that the server accepts padding.
924		testCases = append(testCases, testCase{
925			testType: serverTest,
926			protocol: protocol,
927			name:     prefix + "ECH-Server-Padding",
928			config: Config{
929				ClientECHConfig: echConfig.ECHConfig,
930				Bugs: ProtocolBugs{
931					ClientECHPadding: 10,
932				},
933			},
934			flags: []string{
935				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
936				"-ech-server-key", base64FlagValue(echConfig.Key),
937				"-ech-is-retry-config", "1",
938				"-expect-ech-accept",
939			},
940			expectations: connectionExpectations{
941				echAccepted: true,
942			},
943		})
944
945		// Test that the server rejects bad padding.
946		testCases = append(testCases, testCase{
947			testType: serverTest,
948			protocol: protocol,
949			name:     prefix + "ECH-Server-BadPadding",
950			config: Config{
951				ClientECHConfig: echConfig.ECHConfig,
952				Bugs: ProtocolBugs{
953					ClientECHPadding:    10,
954					BadClientECHPadding: true,
955				},
956			},
957			flags: []string{
958				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
959				"-ech-server-key", base64FlagValue(echConfig.Key),
960				"-ech-is-retry-config", "1",
961				"-expect-ech-accept",
962			},
963			expectations: connectionExpectations{
964				echAccepted: true,
965			},
966			shouldFail:         true,
967			expectedError:      ":DECODE_ERROR",
968			expectedLocalError: "remote error: illegal parameter",
969		})
970
971		// Test the client's behavior when the server ignores ECH GREASE.
972		testCases = append(testCases, testCase{
973			testType: clientTest,
974			protocol: protocol,
975			name:     prefix + "ECH-GREASE-Client-TLS13",
976			config: Config{
977				MinVersion: VersionTLS13,
978				MaxVersion: VersionTLS13,
979				Bugs: ProtocolBugs{
980					ExpectClientECH: true,
981				},
982			},
983			flags: []string{"-enable-ech-grease"},
984		})
985
986		// Test the client's ECH GREASE behavior when responding to server's
987		// HelloRetryRequest. This test implicitly checks that the first and second
988		// ClientHello messages have identical ECH extensions.
989		testCases = append(testCases, testCase{
990			testType: clientTest,
991			protocol: protocol,
992			name:     prefix + "ECH-GREASE-Client-TLS13-HelloRetryRequest",
993			config: Config{
994				MaxVersion: VersionTLS13,
995				MinVersion: VersionTLS13,
996				// P-384 requires a HelloRetryRequest against BoringSSL's default
997				// configuration. Assert this with ExpectMissingKeyShare.
998				CurvePreferences: []CurveID{CurveP384},
999				Bugs: ProtocolBugs{
1000					ExpectMissingKeyShare: true,
1001					ExpectClientECH:       true,
1002				},
1003			},
1004			flags: []string{"-enable-ech-grease", "-expect-hrr"},
1005		})
1006
1007		unsupportedVersion := []byte{
1008			// version
1009			0xba, 0xdd,
1010			// length
1011			0x00, 0x05,
1012			// contents
1013			0x05, 0x04, 0x03, 0x02, 0x01,
1014		}
1015
1016		// Test that the client accepts a well-formed encrypted_client_hello
1017		// extension in response to ECH GREASE. The response includes one ECHConfig
1018		// with a supported version and one with an unsupported version.
1019		testCases = append(testCases, testCase{
1020			testType: clientTest,
1021			protocol: protocol,
1022			name:     prefix + "ECH-GREASE-Client-TLS13-Retry-Configs",
1023			config: Config{
1024				MinVersion: VersionTLS13,
1025				MaxVersion: VersionTLS13,
1026				Bugs: ProtocolBugs{
1027					ExpectClientECH: true,
1028					// Include an additional well-formed ECHConfig with an
1029					// unsupported version. This ensures the client can skip
1030					// unsupported configs.
1031					SendECHRetryConfigs: CreateECHConfigList(echConfig.ECHConfig.Raw, unsupportedVersion),
1032				},
1033			},
1034			flags: []string{"-enable-ech-grease"},
1035		})
1036
1037		// TLS 1.2 ServerHellos cannot contain retry configs.
1038		if protocol != quic {
1039			testCases = append(testCases, testCase{
1040				testType: clientTest,
1041				protocol: protocol,
1042				name:     prefix + "ECH-GREASE-Client-TLS12-RejectRetryConfigs",
1043				config: Config{
1044					MinVersion:       VersionTLS12,
1045					MaxVersion:       VersionTLS12,
1046					ServerECHConfigs: []ServerECHConfig{echConfig},
1047					Bugs: ProtocolBugs{
1048						ExpectClientECH:           true,
1049						AlwaysSendECHRetryConfigs: true,
1050					},
1051				},
1052				flags:              []string{"-enable-ech-grease"},
1053				shouldFail:         true,
1054				expectedLocalError: "remote error: unsupported extension",
1055				expectedError:      ":UNEXPECTED_EXTENSION:",
1056			})
1057			testCases = append(testCases, testCase{
1058				testType: clientTest,
1059				protocol: protocol,
1060				name:     prefix + "ECH-Client-TLS12-RejectRetryConfigs",
1061				config: Config{
1062					MinVersion:       VersionTLS12,
1063					MaxVersion:       VersionTLS12,
1064					ServerECHConfigs: []ServerECHConfig{echConfig},
1065					Bugs: ProtocolBugs{
1066						ExpectClientECH:           true,
1067						AlwaysSendECHRetryConfigs: true,
1068					},
1069				},
1070				flags: []string{
1071					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig1.ECHConfig.Raw)),
1072				},
1073				shouldFail:         true,
1074				expectedLocalError: "remote error: unsupported extension",
1075				expectedError:      ":UNEXPECTED_EXTENSION:",
1076			})
1077		}
1078
1079		// Retry configs must be rejected when ECH is accepted.
1080		testCases = append(testCases, testCase{
1081			testType: clientTest,
1082			protocol: protocol,
1083			name:     prefix + "ECH-Client-Accept-RejectRetryConfigs",
1084			config: Config{
1085				ServerECHConfigs: []ServerECHConfig{echConfig},
1086				Bugs: ProtocolBugs{
1087					ExpectClientECH:           true,
1088					AlwaysSendECHRetryConfigs: true,
1089				},
1090			},
1091			flags: []string{
1092				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1093			},
1094			shouldFail:         true,
1095			expectedLocalError: "remote error: unsupported extension",
1096			expectedError:      ":UNEXPECTED_EXTENSION:",
1097		})
1098
1099		// Unsolicited ECH HelloRetryRequest extensions should be rejected.
1100		testCases = append(testCases, testCase{
1101			testType: clientTest,
1102			protocol: protocol,
1103			name:     prefix + "ECH-Client-UnsolictedHRRExtension",
1104			config: Config{
1105				ServerECHConfigs: []ServerECHConfig{echConfig},
1106				CurvePreferences: []CurveID{CurveP384},
1107				Bugs: ProtocolBugs{
1108					AlwaysSendECHHelloRetryRequest: true,
1109					ExpectMissingKeyShare:          true, // Check we triggered HRR.
1110				},
1111			},
1112			shouldFail:         true,
1113			expectedLocalError: "remote error: unsupported extension",
1114			expectedError:      ":UNEXPECTED_EXTENSION:",
1115		})
1116
1117		// GREASE should ignore ECH HelloRetryRequest extensions.
1118		testCases = append(testCases, testCase{
1119			testType: clientTest,
1120			protocol: protocol,
1121			name:     prefix + "ECH-Client-GREASE-IgnoreHRRExtension",
1122			config: Config{
1123				CurvePreferences: []CurveID{CurveP384},
1124				Bugs: ProtocolBugs{
1125					AlwaysSendECHHelloRetryRequest: true,
1126					ExpectMissingKeyShare:          true, // Check we triggered HRR.
1127				},
1128			},
1129			flags: []string{"-enable-ech-grease"},
1130		})
1131
1132		// Random ECH HelloRetryRequest extensions also signal ECH reject.
1133		testCases = append(testCases, testCase{
1134			testType: clientTest,
1135			protocol: protocol,
1136			name:     prefix + "ECH-Client-Reject-RandomHRRExtension",
1137			config: Config{
1138				CurvePreferences: []CurveID{CurveP384},
1139				Bugs: ProtocolBugs{
1140					AlwaysSendECHHelloRetryRequest: true,
1141					ExpectMissingKeyShare:          true, // Check we triggered HRR.
1142				},
1143				Credential: &echPublicCertificate,
1144			},
1145			flags: []string{
1146				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1147			},
1148			shouldFail:         true,
1149			expectedLocalError: "remote error: ECH required",
1150			expectedError:      ":ECH_REJECTED:",
1151		})
1152
1153		// Test that the client aborts with a decode_error alert when it receives a
1154		// syntactically-invalid encrypted_client_hello extension from the server.
1155		testCases = append(testCases, testCase{
1156			testType: clientTest,
1157			protocol: protocol,
1158			name:     prefix + "ECH-GREASE-Client-TLS13-Invalid-Retry-Configs",
1159			config: Config{
1160				MinVersion: VersionTLS13,
1161				MaxVersion: VersionTLS13,
1162				Bugs: ProtocolBugs{
1163					ExpectClientECH:     true,
1164					SendECHRetryConfigs: []byte{0xba, 0xdd, 0xec, 0xcc},
1165				},
1166			},
1167			flags:              []string{"-enable-ech-grease"},
1168			shouldFail:         true,
1169			expectedLocalError: "remote error: error decoding message",
1170			expectedError:      ":ERROR_PARSING_EXTENSION:",
1171		})
1172
1173		// Test that the server responds to an inner ECH extension with the
1174		// acceptance confirmation.
1175		testCases = append(testCases, testCase{
1176			testType: serverTest,
1177			protocol: protocol,
1178			name:     prefix + "ECH-Server-ECHInner",
1179			config: Config{
1180				MinVersion: VersionTLS13,
1181				MaxVersion: VersionTLS13,
1182				Bugs: ProtocolBugs{
1183					AlwaysSendECHInner: true,
1184				},
1185			},
1186			resumeSession: true,
1187		})
1188		testCases = append(testCases, testCase{
1189			testType: serverTest,
1190			protocol: protocol,
1191			name:     prefix + "ECH-Server-ECHInner-HelloRetryRequest",
1192			config: Config{
1193				MinVersion: VersionTLS13,
1194				MaxVersion: VersionTLS13,
1195				// Force a HelloRetryRequest.
1196				DefaultCurves: []CurveID{},
1197				Bugs: ProtocolBugs{
1198					AlwaysSendECHInner: true,
1199				},
1200			},
1201			resumeSession: true,
1202		})
1203
1204		// Test that server fails the handshake when it sees a non-empty
1205		// inner ECH extension.
1206		testCases = append(testCases, testCase{
1207			testType: serverTest,
1208			protocol: protocol,
1209			name:     prefix + "ECH-Server-ECHInner-NotEmpty",
1210			config: Config{
1211				MinVersion: VersionTLS13,
1212				MaxVersion: VersionTLS13,
1213				Bugs: ProtocolBugs{
1214					AlwaysSendECHInner:  true,
1215					SendInvalidECHInner: []byte{42, 42, 42},
1216				},
1217			},
1218			shouldFail:         true,
1219			expectedLocalError: "remote error: error decoding message",
1220			expectedError:      ":ERROR_PARSING_EXTENSION:",
1221		})
1222
1223		// Test that a TLS 1.3 server that receives an inner ECH extension can
1224		// negotiate TLS 1.2 without clobbering the downgrade signal.
1225		if protocol != quic {
1226			testCases = append(testCases, testCase{
1227				testType: serverTest,
1228				protocol: protocol,
1229				name:     prefix + "ECH-Server-ECHInner-Absent-TLS12",
1230				config: Config{
1231					MinVersion: VersionTLS12,
1232					MaxVersion: VersionTLS13,
1233					Bugs: ProtocolBugs{
1234						// Omit supported_versions extension so the server negotiates
1235						// TLS 1.2.
1236						OmitSupportedVersions: true,
1237						AlwaysSendECHInner:    true,
1238					},
1239				},
1240				// Check that the client sees the TLS 1.3 downgrade signal in
1241				// ServerHello.random.
1242				shouldFail:         true,
1243				expectedLocalError: "tls: downgrade from TLS 1.3 detected",
1244			})
1245		}
1246
1247		// Test the client can negotiate ECH, with and without HelloRetryRequest.
1248		testCases = append(testCases, testCase{
1249			testType: clientTest,
1250			protocol: protocol,
1251			name:     prefix + "ECH-Client",
1252			config: Config{
1253				MinVersion:       VersionTLS13,
1254				MaxVersion:       VersionTLS13,
1255				ServerECHConfigs: []ServerECHConfig{echConfig},
1256				Bugs: ProtocolBugs{
1257					ExpectServerName:      "secret.example",
1258					ExpectOuterServerName: "public.example",
1259				},
1260				Credential: &echSecretCertificate,
1261			},
1262			flags: []string{
1263				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1264				"-host-name", "secret.example",
1265				"-expect-ech-accept",
1266			},
1267			resumeSession: true,
1268			expectations:  connectionExpectations{echAccepted: true},
1269		})
1270		testCases = append(testCases, testCase{
1271			testType: clientTest,
1272			protocol: protocol,
1273			name:     prefix + "ECH-Client-HelloRetryRequest",
1274			config: Config{
1275				MinVersion:       VersionTLS13,
1276				MaxVersion:       VersionTLS13,
1277				CurvePreferences: []CurveID{CurveP384},
1278				ServerECHConfigs: []ServerECHConfig{echConfig},
1279				Bugs: ProtocolBugs{
1280					ExpectServerName:      "secret.example",
1281					ExpectOuterServerName: "public.example",
1282					ExpectMissingKeyShare: true, // Check we triggered HRR.
1283				},
1284				Credential: &echSecretCertificate,
1285			},
1286			resumeSession: true,
1287			flags: []string{
1288				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1289				"-host-name", "secret.example",
1290				"-expect-ech-accept",
1291				"-expect-hrr", // Check we triggered HRR.
1292			},
1293			expectations: connectionExpectations{echAccepted: true},
1294		})
1295
1296		// Test the client can negotiate ECH with early data.
1297		// TODO(crbug.com/381113363): Enable these tests for DTLS once we
1298		// support early data in DTLS 1.3.
1299		if protocol != dtls {
1300			testCases = append(testCases, testCase{
1301				testType: clientTest,
1302				protocol: protocol,
1303				name:     prefix + "ECH-Client-EarlyData",
1304				config: Config{
1305					MinVersion:       VersionTLS13,
1306					MaxVersion:       VersionTLS13,
1307					ServerECHConfigs: []ServerECHConfig{echConfig},
1308					Bugs: ProtocolBugs{
1309						ExpectServerName: "secret.example",
1310					},
1311					Credential: &echSecretCertificate,
1312				},
1313				flags: []string{
1314					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1315					"-host-name", "secret.example",
1316					"-expect-ech-accept",
1317				},
1318				resumeSession: true,
1319				earlyData:     true,
1320				expectations:  connectionExpectations{echAccepted: true},
1321			})
1322			testCases = append(testCases, testCase{
1323				testType: clientTest,
1324				protocol: protocol,
1325				name:     prefix + "ECH-Client-EarlyDataRejected",
1326				config: Config{
1327					MinVersion:       VersionTLS13,
1328					MaxVersion:       VersionTLS13,
1329					ServerECHConfigs: []ServerECHConfig{echConfig},
1330					Bugs: ProtocolBugs{
1331						ExpectServerName:      "secret.example",
1332						AlwaysRejectEarlyData: true,
1333					},
1334					Credential: &echSecretCertificate,
1335				},
1336				flags: []string{
1337					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1338					"-host-name", "secret.example",
1339					"-expect-ech-accept",
1340				},
1341				resumeSession:           true,
1342				earlyData:               true,
1343				expectEarlyDataRejected: true,
1344				expectations:            connectionExpectations{echAccepted: true},
1345			})
1346		}
1347
1348		if protocol != quic {
1349			// Test that an ECH client does not offer a TLS 1.2 session.
1350			testCases = append(testCases, testCase{
1351				testType: clientTest,
1352				protocol: protocol,
1353				name:     prefix + "ECH-Client-TLS12SessionID",
1354				config: Config{
1355					MaxVersion:             VersionTLS12,
1356					SessionTicketsDisabled: true,
1357				},
1358				resumeConfig: &Config{
1359					ServerECHConfigs: []ServerECHConfig{echConfig},
1360					Bugs: ProtocolBugs{
1361						ExpectNoTLS12Session: true,
1362					},
1363				},
1364				flags: []string{
1365					"-on-resume-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1366					"-on-resume-expect-ech-accept",
1367				},
1368				resumeSession:        true,
1369				expectResumeRejected: true,
1370				resumeExpectations:   &connectionExpectations{echAccepted: true},
1371			})
1372			testCases = append(testCases, testCase{
1373				testType: clientTest,
1374				protocol: protocol,
1375				name:     prefix + "ECH-Client-TLS12SessionTicket",
1376				config: Config{
1377					MaxVersion: VersionTLS12,
1378				},
1379				resumeConfig: &Config{
1380					ServerECHConfigs: []ServerECHConfig{echConfig},
1381					Bugs: ProtocolBugs{
1382						ExpectNoTLS12Session: true,
1383					},
1384				},
1385				flags: []string{
1386					"-on-resume-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1387					"-on-resume-expect-ech-accept",
1388				},
1389				resumeSession:        true,
1390				expectResumeRejected: true,
1391				resumeExpectations:   &connectionExpectations{echAccepted: true},
1392			})
1393		}
1394
1395		// ClientHelloInner should not include NPN, which is a TLS 1.2-only
1396		// extensions. The Go server will enforce this, so this test only needs
1397		// to configure the feature on the shim. Other application extensions
1398		// are sent implicitly.
1399		testCases = append(testCases, testCase{
1400			testType: clientTest,
1401			protocol: protocol,
1402			name:     prefix + "ECH-Client-NoNPN",
1403			config: Config{
1404				ServerECHConfigs: []ServerECHConfig{echConfig},
1405			},
1406			flags: []string{
1407				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1408				"-expect-ech-accept",
1409				// Enable NPN.
1410				"-select-next-proto", "foo",
1411			},
1412			expectations: connectionExpectations{echAccepted: true},
1413		})
1414
1415		// Test that the client iterates over configurations in the
1416		// ECHConfigList and selects the first with supported parameters.
1417		unsupportedKEM := generateServerECHConfig(&ECHConfig{
1418			KEM:       0x6666,
1419			PublicKey: []byte{1, 2, 3, 4},
1420		}).ECHConfig
1421		unsupportedCipherSuites := generateServerECHConfig(&ECHConfig{
1422			CipherSuites: []HPKECipherSuite{{0x1111, 0x2222}},
1423		}).ECHConfig
1424		unsupportedMandatoryExtension := generateServerECHConfig(&ECHConfig{
1425			UnsupportedMandatoryExtension: true,
1426		}).ECHConfig
1427		testCases = append(testCases, testCase{
1428			testType: clientTest,
1429			protocol: protocol,
1430			name:     prefix + "ECH-Client-SelectECHConfig",
1431			config: Config{
1432				ServerECHConfigs: []ServerECHConfig{echConfig},
1433			},
1434			flags: []string{
1435				"-ech-config-list", base64FlagValue(CreateECHConfigList(
1436					unsupportedVersion,
1437					unsupportedKEM.Raw,
1438					unsupportedCipherSuites.Raw,
1439					unsupportedMandatoryExtension.Raw,
1440					echConfig.ECHConfig.Raw,
1441					// |echConfig1| is also supported, but the client should
1442					// select the first one.
1443					echConfig1.ECHConfig.Raw,
1444				)),
1445				"-expect-ech-accept",
1446			},
1447			expectations: connectionExpectations{
1448				echAccepted: true,
1449			},
1450		})
1451
1452		// Test that the client skips sending ECH if all ECHConfigs are
1453		// unsupported.
1454		testCases = append(testCases, testCase{
1455			testType: clientTest,
1456			protocol: protocol,
1457			name:     prefix + "ECH-Client-NoSupportedConfigs",
1458			config: Config{
1459				Bugs: ProtocolBugs{
1460					ExpectNoClientECH: true,
1461				},
1462			},
1463			flags: []string{
1464				"-ech-config-list", base64FlagValue(CreateECHConfigList(
1465					unsupportedVersion,
1466					unsupportedKEM.Raw,
1467					unsupportedCipherSuites.Raw,
1468					unsupportedMandatoryExtension.Raw,
1469				)),
1470			},
1471		})
1472
1473		// If ECH GREASE is enabled, the client should send ECH GREASE when no
1474		// configured ECHConfig is suitable.
1475		testCases = append(testCases, testCase{
1476			testType: clientTest,
1477			protocol: protocol,
1478			name:     prefix + "ECH-Client-NoSupportedConfigs-GREASE",
1479			config: Config{
1480				Bugs: ProtocolBugs{
1481					ExpectClientECH: true,
1482				},
1483			},
1484			flags: []string{
1485				"-ech-config-list", base64FlagValue(CreateECHConfigList(
1486					unsupportedVersion,
1487					unsupportedKEM.Raw,
1488					unsupportedCipherSuites.Raw,
1489					unsupportedMandatoryExtension.Raw,
1490				)),
1491				"-enable-ech-grease",
1492			},
1493		})
1494
1495		// If both ECH GREASE and suitable ECHConfigs are available, the
1496		// client should send normal ECH.
1497		testCases = append(testCases, testCase{
1498			testType: clientTest,
1499			protocol: protocol,
1500			name:     prefix + "ECH-Client-GREASE",
1501			config: Config{
1502				ServerECHConfigs: []ServerECHConfig{echConfig},
1503			},
1504			flags: []string{
1505				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1506				"-expect-ech-accept",
1507			},
1508			resumeSession: true,
1509			expectations:  connectionExpectations{echAccepted: true},
1510		})
1511
1512		// Test that GREASE extensions correctly interact with ECH. Both the
1513		// inner and outer ClientHellos should include GREASE extensions.
1514		testCases = append(testCases, testCase{
1515			testType: clientTest,
1516			protocol: protocol,
1517			name:     prefix + "ECH-Client-GREASEExtensions",
1518			config: Config{
1519				ServerECHConfigs: []ServerECHConfig{echConfig},
1520				Bugs: ProtocolBugs{
1521					ExpectGREASE: true,
1522				},
1523			},
1524			flags: []string{
1525				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1526				"-expect-ech-accept",
1527				"-enable-grease",
1528			},
1529			resumeSession: true,
1530			expectations:  connectionExpectations{echAccepted: true},
1531		})
1532
1533		// Test that the client tolerates unsupported extensions if the
1534		// mandatory bit is not set.
1535		unsupportedExtension := generateServerECHConfig(&ECHConfig{UnsupportedExtension: true})
1536		testCases = append(testCases, testCase{
1537			testType: clientTest,
1538			protocol: protocol,
1539			name:     prefix + "ECH-Client-UnsupportedExtension",
1540			config: Config{
1541				ServerECHConfigs: []ServerECHConfig{unsupportedExtension},
1542			},
1543			flags: []string{
1544				"-ech-config-list", base64FlagValue(CreateECHConfigList(unsupportedExtension.ECHConfig.Raw)),
1545				"-expect-ech-accept",
1546			},
1547			expectations: connectionExpectations{echAccepted: true},
1548		})
1549
1550		// Syntax errors in the ECHConfigList should be rejected.
1551		testCases = append(testCases, testCase{
1552			testType: clientTest,
1553			protocol: protocol,
1554			name:     prefix + "ECH-Client-InvalidECHConfigList",
1555			flags: []string{
1556				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw[1:])),
1557			},
1558			shouldFail:    true,
1559			expectedError: ":INVALID_ECH_CONFIG_LIST:",
1560		})
1561
1562		// If the ClientHelloInner has no server_name extension, while the
1563		// ClientHelloOuter has one, the client must check for unsolicited
1564		// extensions based on the selected ClientHello.
1565		testCases = append(testCases, testCase{
1566			testType: clientTest,
1567			protocol: protocol,
1568			name:     prefix + "ECH-Client-UnsolicitedInnerServerNameAck",
1569			config: Config{
1570				ServerECHConfigs: []ServerECHConfig{echConfig},
1571				Bugs: ProtocolBugs{
1572					// ClientHelloOuter should have a server name.
1573					ExpectOuterServerName: "public.example",
1574					// The server will acknowledge the server_name extension.
1575					// This option runs whether or not the client requested the
1576					// extension.
1577					SendServerNameAck: true,
1578				},
1579			},
1580			flags: []string{
1581				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1582				// No -host-name flag.
1583				"-expect-ech-accept",
1584			},
1585			shouldFail:         true,
1586			expectedError:      ":UNEXPECTED_EXTENSION:",
1587			expectedLocalError: "remote error: unsupported extension",
1588			expectations:       connectionExpectations{echAccepted: true},
1589		})
1590
1591		// Most extensions are the same between ClientHelloInner and
1592		// ClientHelloOuter and can be compressed.
1593		testCases = append(testCases, testCase{
1594			testType: clientTest,
1595			protocol: protocol,
1596			name:     prefix + "ECH-Client-ExpectECHOuterExtensions",
1597			config: Config{
1598				ServerECHConfigs: []ServerECHConfig{echConfig},
1599				NextProtos:       []string{"proto"},
1600				Bugs: ProtocolBugs{
1601					ExpectECHOuterExtensions: []uint16{
1602						extensionALPN,
1603						extensionKeyShare,
1604						extensionPSKKeyExchangeModes,
1605						extensionSignatureAlgorithms,
1606						extensionSupportedCurves,
1607					},
1608				},
1609				Credential: &echSecretCertificate,
1610			},
1611			flags: []string{
1612				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1613				"-expect-ech-accept",
1614				"-advertise-alpn", "\x05proto",
1615				"-expect-alpn", "proto",
1616				"-host-name", "secret.example",
1617			},
1618			expectations: connectionExpectations{
1619				echAccepted: true,
1620				nextProto:   "proto",
1621			},
1622			skipQUICALPNConfig: true,
1623		})
1624
1625		// If the server name happens to match the public name, it still should
1626		// not be compressed. It is not publicly known that they match.
1627		testCases = append(testCases, testCase{
1628			testType: clientTest,
1629			protocol: protocol,
1630			name:     prefix + "ECH-Client-NeverCompressServerName",
1631			config: Config{
1632				ServerECHConfigs: []ServerECHConfig{echConfig},
1633				NextProtos:       []string{"proto"},
1634				Bugs: ProtocolBugs{
1635					ExpectECHUncompressedExtensions: []uint16{extensionServerName},
1636					ExpectServerName:                "public.example",
1637					ExpectOuterServerName:           "public.example",
1638				},
1639				Credential: &echPublicCertificate,
1640			},
1641			flags: []string{
1642				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1643				"-expect-ech-accept",
1644				"-host-name", "public.example",
1645			},
1646			expectations: connectionExpectations{echAccepted: true},
1647		})
1648
1649		// If the ClientHelloOuter disables TLS 1.3, e.g. in QUIC, the client
1650		// should also compress supported_versions.
1651		tls13Vers := VersionTLS13
1652		if protocol == dtls {
1653			tls13Vers = VersionDTLS13
1654		}
1655		testCases = append(testCases, testCase{
1656			testType: clientTest,
1657			protocol: protocol,
1658			name:     prefix + "ECH-Client-CompressSupportedVersions",
1659			config: Config{
1660				ServerECHConfigs: []ServerECHConfig{echConfig},
1661				Bugs: ProtocolBugs{
1662					ExpectECHOuterExtensions: []uint16{
1663						extensionSupportedVersions,
1664					},
1665				},
1666				Credential: &echSecretCertificate,
1667			},
1668			flags: []string{
1669				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1670				"-host-name", "secret.example",
1671				"-expect-ech-accept",
1672				"-min-version", strconv.Itoa(int(tls13Vers)),
1673			},
1674			expectations: connectionExpectations{echAccepted: true},
1675		})
1676
1677		// Test that the client can still offer server names that exceed the
1678		// maximum name length. It is only a padding hint.
1679		maxNameLen10 := generateServerECHConfig(&ECHConfig{MaxNameLen: 10})
1680		testCases = append(testCases, testCase{
1681			testType: clientTest,
1682			protocol: protocol,
1683			name:     prefix + "ECH-Client-NameTooLong",
1684			config: Config{
1685				ServerECHConfigs: []ServerECHConfig{maxNameLen10},
1686				Bugs: ProtocolBugs{
1687					ExpectServerName: "test0123456789.example",
1688				},
1689				Credential: &echLongNameCertificate,
1690			},
1691			flags: []string{
1692				"-ech-config-list", base64FlagValue(CreateECHConfigList(maxNameLen10.ECHConfig.Raw)),
1693				"-host-name", "test0123456789.example",
1694				"-expect-ech-accept",
1695			},
1696			expectations: connectionExpectations{echAccepted: true},
1697		})
1698
1699		// Test the client can recognize when ECH is rejected.
1700		testCases = append(testCases, testCase{
1701			testType: clientTest,
1702			protocol: protocol,
1703			name:     prefix + "ECH-Client-Reject",
1704			config: Config{
1705				ServerECHConfigs: []ServerECHConfig{echConfig2, echConfig3},
1706				Bugs: ProtocolBugs{
1707					ExpectServerName: "public.example",
1708				},
1709				Credential: &echPublicCertificate,
1710			},
1711			flags: []string{
1712				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1713				"-expect-ech-retry-configs", base64FlagValue(CreateECHConfigList(echConfig2.ECHConfig.Raw, echConfig3.ECHConfig.Raw)),
1714			},
1715			shouldFail:         true,
1716			expectedLocalError: "remote error: ECH required",
1717			expectedError:      ":ECH_REJECTED:",
1718		})
1719		testCases = append(testCases, testCase{
1720			testType: clientTest,
1721			protocol: protocol,
1722			name:     prefix + "ECH-Client-Reject-HelloRetryRequest",
1723			config: Config{
1724				ServerECHConfigs: []ServerECHConfig{echConfig2, echConfig3},
1725				CurvePreferences: []CurveID{CurveP384},
1726				Bugs: ProtocolBugs{
1727					ExpectServerName:      "public.example",
1728					ExpectMissingKeyShare: true, // Check we triggered HRR.
1729				},
1730				Credential: &echPublicCertificate,
1731			},
1732			flags: []string{
1733				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1734				"-expect-ech-retry-configs", base64FlagValue(CreateECHConfigList(echConfig2.ECHConfig.Raw, echConfig3.ECHConfig.Raw)),
1735				"-expect-hrr", // Check we triggered HRR.
1736			},
1737			shouldFail:         true,
1738			expectedLocalError: "remote error: ECH required",
1739			expectedError:      ":ECH_REJECTED:",
1740		})
1741		testCases = append(testCases, testCase{
1742			testType: clientTest,
1743			protocol: protocol,
1744			name:     prefix + "ECH-Client-Reject-NoRetryConfigs",
1745			config: Config{
1746				Bugs: ProtocolBugs{
1747					ExpectServerName: "public.example",
1748				},
1749				Credential: &echPublicCertificate,
1750			},
1751			flags: []string{
1752				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1753				"-expect-no-ech-retry-configs",
1754			},
1755			shouldFail:         true,
1756			expectedLocalError: "remote error: ECH required",
1757			expectedError:      ":ECH_REJECTED:",
1758		})
1759		if protocol != quic {
1760			testCases = append(testCases, testCase{
1761				testType: clientTest,
1762				protocol: protocol,
1763				name:     prefix + "ECH-Client-Reject-TLS12",
1764				config: Config{
1765					MaxVersion: VersionTLS12,
1766					Bugs: ProtocolBugs{
1767						ExpectServerName: "public.example",
1768					},
1769					Credential: &echPublicCertificate,
1770				},
1771				flags: []string{
1772					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1773					// TLS 1.2 cannot provide retry configs.
1774					"-expect-no-ech-retry-configs",
1775				},
1776				shouldFail:         true,
1777				expectedLocalError: "remote error: ECH required",
1778				expectedError:      ":ECH_REJECTED:",
1779			})
1780
1781			// Test that the client disables False Start when ECH is rejected.
1782			testCases = append(testCases, testCase{
1783				protocol: protocol,
1784				name:     prefix + "ECH-Client-Reject-TLS12-NoFalseStart",
1785				config: Config{
1786					MaxVersion:   VersionTLS12,
1787					CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
1788					NextProtos:   []string{"foo"},
1789					Bugs: ProtocolBugs{
1790						// The options below cause the server to, immediately
1791						// after client Finished, send an alert and try to read
1792						// application data without sending server Finished.
1793						ExpectFalseStart:          true,
1794						AlertBeforeFalseStartTest: alertAccessDenied,
1795					},
1796					Credential: &echPublicCertificate,
1797				},
1798				flags: []string{
1799					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1800					"-false-start",
1801					"-advertise-alpn", "\x03foo",
1802					"-expect-alpn", "foo",
1803				},
1804				shimWritesFirst: true,
1805				shouldFail:      true,
1806				// Ensure the client does not send application data at the False
1807				// Start point. EOF comes from the client closing the connection
1808				// in response ot the alert.
1809				expectedLocalError: "tls: peer did not false start: EOF",
1810				// Ensures the client picks up the alert before reporting an
1811				// authenticated |SSL_R_ECH_REJECTED|.
1812				expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
1813			})
1814		}
1815
1816		// Test that unsupported retry configs in a valid ECHConfigList are
1817		// allowed. They will be skipped when configured in the retry.
1818		retryConfigs := CreateECHConfigList(
1819			unsupportedVersion,
1820			unsupportedKEM.Raw,
1821			unsupportedCipherSuites.Raw,
1822			unsupportedMandatoryExtension.Raw,
1823			echConfig2.ECHConfig.Raw)
1824		testCases = append(testCases, testCase{
1825			testType: clientTest,
1826			protocol: protocol,
1827			name:     prefix + "ECH-Client-Reject-UnsupportedRetryConfigs",
1828			config: Config{
1829				Bugs: ProtocolBugs{
1830					SendECHRetryConfigs: retryConfigs,
1831					ExpectServerName:    "public.example",
1832				},
1833				Credential: &echPublicCertificate,
1834			},
1835			flags: []string{
1836				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1837				"-expect-ech-retry-configs", base64FlagValue(retryConfigs),
1838			},
1839			shouldFail:         true,
1840			expectedLocalError: "remote error: ECH required",
1841			expectedError:      ":ECH_REJECTED:",
1842		})
1843
1844		// Test that the client rejects ClientHelloOuter handshakes that attempt
1845		// to resume the ClientHelloInner's ticket, at TLS 1.2 and TLS 1.3.
1846		testCases = append(testCases, testCase{
1847			testType: clientTest,
1848			protocol: protocol,
1849			name:     prefix + "ECH-Client-Reject-ResumeInnerSession-TLS13",
1850			config: Config{
1851				ServerECHConfigs: []ServerECHConfig{echConfig},
1852				Bugs: ProtocolBugs{
1853					ExpectServerName: "secret.example",
1854				},
1855				Credential: &echSecretCertificate,
1856			},
1857			resumeConfig: &Config{
1858				MaxVersion:       VersionTLS13,
1859				ServerECHConfigs: []ServerECHConfig{echConfig},
1860				Bugs: ProtocolBugs{
1861					ExpectServerName:                    "public.example",
1862					UseInnerSessionWithClientHelloOuter: true,
1863				},
1864				Credential: &echPublicCertificate,
1865			},
1866			resumeSession: true,
1867			flags: []string{
1868				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1869				"-host-name", "secret.example",
1870				"-on-initial-expect-ech-accept",
1871			},
1872			shouldFail:         true,
1873			expectedError:      ":UNEXPECTED_EXTENSION:",
1874			expectations:       connectionExpectations{echAccepted: true},
1875			resumeExpectations: &connectionExpectations{echAccepted: false},
1876		})
1877		if protocol == tls {
1878			// This is only syntactically possible with TLS. In DTLS, we don't
1879			// have middlebox compatibility mode, so the session ID will only
1880			// filled in if we are offering a DTLS 1.2 session. But a DTLS 1.2
1881			// would never be offered in ClientHelloInner. Without a session ID,
1882			// the server syntactically cannot express a resumption at DTLS 1.2.
1883			// In QUIC, the above is true, and 1.2 does not exist anyway.
1884			testCases = append(testCases, testCase{
1885				testType: clientTest,
1886				protocol: protocol,
1887				name:     prefix + "ECH-Client-Reject-ResumeInnerSession-TLS12",
1888				config: Config{
1889					ServerECHConfigs: []ServerECHConfig{echConfig},
1890					Bugs: ProtocolBugs{
1891						ExpectServerName: "secret.example",
1892					},
1893					Credential: &echSecretCertificate,
1894				},
1895				resumeConfig: &Config{
1896					MinVersion:       VersionTLS12,
1897					MaxVersion:       VersionTLS12,
1898					ServerECHConfigs: []ServerECHConfig{echConfig},
1899					Bugs: ProtocolBugs{
1900						ExpectServerName:                    "public.example",
1901						UseInnerSessionWithClientHelloOuter: true,
1902						// The client only ever offers TLS 1.3 sessions in
1903						// ClientHelloInner. AcceptAnySession allows them to be
1904						// resumed at TLS 1.2.
1905						AcceptAnySession: true,
1906					},
1907					Credential: &echPublicCertificate,
1908				},
1909				resumeSession: true,
1910				flags: []string{
1911					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1912					"-host-name", "secret.example",
1913					"-on-initial-expect-ech-accept",
1914				},
1915				// From the client's perspective, the server echoed a session ID to
1916				// signal resumption, but the selected ClientHello had nothing to
1917				// resume.
1918				shouldFail:         true,
1919				expectedError:      ":SERVER_ECHOED_INVALID_SESSION_ID:",
1920				expectedLocalError: "remote error: illegal parameter",
1921				expectations:       connectionExpectations{echAccepted: true},
1922				resumeExpectations: &connectionExpectations{echAccepted: false},
1923			})
1924		}
1925
1926		// Test that the client can process ECH rejects after an early data reject.
1927		testCases = append(testCases, testCase{
1928			testType: clientTest,
1929			protocol: protocol,
1930			name:     prefix + "ECH-Client-Reject-EarlyDataRejected",
1931			config: Config{
1932				ServerECHConfigs: []ServerECHConfig{echConfig},
1933				Bugs: ProtocolBugs{
1934					ExpectServerName: "secret.example",
1935				},
1936				Credential: &echSecretCertificate,
1937			},
1938			resumeConfig: &Config{
1939				ServerECHConfigs: []ServerECHConfig{echConfig2},
1940				Bugs: ProtocolBugs{
1941					ExpectServerName: "public.example",
1942				},
1943				Credential: &echPublicCertificate,
1944			},
1945			flags: []string{
1946				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1947				"-host-name", "secret.example",
1948				// Although the resumption connection does not accept ECH, the
1949				// API will report ECH was accepted at the 0-RTT point.
1950				"-expect-ech-accept",
1951				// -on-retry refers to the retried handshake after 0-RTT reject,
1952				// while ech-retry-configs refers to the ECHConfigs to use in
1953				// the next connection attempt.
1954				"-on-retry-expect-ech-retry-configs", base64FlagValue(CreateECHConfigList(echConfig2.ECHConfig.Raw)),
1955			},
1956			resumeSession:           true,
1957			expectResumeRejected:    true,
1958			earlyData:               true,
1959			expectEarlyDataRejected: true,
1960			expectations:            connectionExpectations{echAccepted: true},
1961			resumeExpectations:      &connectionExpectations{echAccepted: false},
1962			shouldFail:              true,
1963			expectedLocalError:      "remote error: ECH required",
1964			expectedError:           ":ECH_REJECTED:",
1965		})
1966		// TODO(crbug.com/381113363): Enable this test for DTLS once we
1967		// support early data in DTLS 1.3.
1968		if protocol != quic && protocol != dtls {
1969			testCases = append(testCases, testCase{
1970				testType: clientTest,
1971				protocol: protocol,
1972				name:     prefix + "ECH-Client-Reject-EarlyDataRejected-TLS12",
1973				config: Config{
1974					ServerECHConfigs: []ServerECHConfig{echConfig},
1975					Bugs: ProtocolBugs{
1976						ExpectServerName: "secret.example",
1977					},
1978					Credential: &echSecretCertificate,
1979				},
1980				resumeConfig: &Config{
1981					MaxVersion: VersionTLS12,
1982					Bugs: ProtocolBugs{
1983						ExpectServerName: "public.example",
1984					},
1985					Credential: &echPublicCertificate,
1986				},
1987				flags: []string{
1988					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
1989					"-host-name", "secret.example",
1990					// Although the resumption connection does not accept ECH, the
1991					// API will report ECH was accepted at the 0-RTT point.
1992					"-expect-ech-accept",
1993				},
1994				resumeSession:           true,
1995				expectResumeRejected:    true,
1996				earlyData:               true,
1997				expectEarlyDataRejected: true,
1998				expectations:            connectionExpectations{echAccepted: true},
1999				resumeExpectations:      &connectionExpectations{echAccepted: false},
2000				// ClientHellos with early data cannot negotiate TLS 1.2, with
2001				// or without ECH. The shim should first report
2002				// |SSL_R_WRONG_VERSION_ON_EARLY_DATA|. The caller will then
2003				// repair the first error by retrying without early data. That
2004				// will look like ECH-Client-Reject-TLS12 and select TLS 1.2
2005				// and ClientHelloOuter. The caller will then trigger a third
2006				// attempt, which will succeed.
2007				shouldFail:    true,
2008				expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
2009			})
2010		}
2011
2012		// Test that the client ignores ECHConfigs with invalid public names.
2013		invalidPublicName := generateServerECHConfig(&ECHConfig{PublicName: "dns_names_have_no_underscores.example"})
2014		testCases = append(testCases, testCase{
2015			testType: clientTest,
2016			protocol: protocol,
2017			name:     prefix + "ECH-Client-SkipInvalidPublicName",
2018			config: Config{
2019				Bugs: ProtocolBugs{
2020					// No ECHConfigs are supported, so the client should fall
2021					// back to cleartext.
2022					ExpectNoClientECH: true,
2023					ExpectServerName:  "secret.example",
2024				},
2025				Credential: &echSecretCertificate,
2026			},
2027			flags: []string{
2028				"-ech-config-list", base64FlagValue(CreateECHConfigList(invalidPublicName.ECHConfig.Raw)),
2029				"-host-name", "secret.example",
2030			},
2031		})
2032		testCases = append(testCases, testCase{
2033			testType: clientTest,
2034			protocol: protocol,
2035			name:     prefix + "ECH-Client-SkipInvalidPublicName-2",
2036			config: Config{
2037				// The client should skip |invalidPublicName| and use |echConfig|.
2038				ServerECHConfigs: []ServerECHConfig{echConfig},
2039				Bugs: ProtocolBugs{
2040					ExpectOuterServerName: "public.example",
2041					ExpectServerName:      "secret.example",
2042				},
2043				Credential: &echSecretCertificate,
2044			},
2045			flags: []string{
2046				"-ech-config-list", base64FlagValue(CreateECHConfigList(invalidPublicName.ECHConfig.Raw, echConfig.ECHConfig.Raw)),
2047				"-host-name", "secret.example",
2048				"-expect-ech-accept",
2049			},
2050			expectations: connectionExpectations{echAccepted: true},
2051		})
2052
2053		// Test both sync and async mode, to test both with and without the
2054		// client certificate callback.
2055		for _, async := range []bool{false, true} {
2056			var flags []string
2057			var suffix string
2058			if async {
2059				flags = []string{"-async"}
2060				suffix = "-Async"
2061			}
2062
2063			// Test that ECH and client certificates can be used together.
2064			testCases = append(testCases, testCase{
2065				testType: clientTest,
2066				protocol: protocol,
2067				name:     prefix + "ECH-Client-ClientCertificate" + suffix,
2068				config: Config{
2069					ServerECHConfigs: []ServerECHConfig{echConfig},
2070					ClientAuth:       RequireAnyClientCert,
2071				},
2072				shimCertificate: &rsaCertificate,
2073				flags: append([]string{
2074					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2075					"-expect-ech-accept",
2076				}, flags...),
2077				expectations: connectionExpectations{echAccepted: true},
2078			})
2079
2080			// Test that, when ECH is rejected, the client does not send a client
2081			// certificate.
2082			testCases = append(testCases, testCase{
2083				testType: clientTest,
2084				protocol: protocol,
2085				name:     prefix + "ECH-Client-Reject-NoClientCertificate-TLS13" + suffix,
2086				config: Config{
2087					MinVersion: VersionTLS13,
2088					MaxVersion: VersionTLS13,
2089					ClientAuth: RequireAnyClientCert,
2090					Credential: &echPublicCertificate,
2091				},
2092				shimCertificate: &rsaCertificate,
2093				flags: append([]string{
2094					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2095				}, flags...),
2096				shouldFail:         true,
2097				expectedLocalError: "tls: client didn't provide a certificate",
2098			})
2099			if protocol != quic {
2100				testCases = append(testCases, testCase{
2101					testType: clientTest,
2102					protocol: protocol,
2103					name:     prefix + "ECH-Client-Reject-NoClientCertificate-TLS12" + suffix,
2104					config: Config{
2105						MinVersion: VersionTLS12,
2106						MaxVersion: VersionTLS12,
2107						ClientAuth: RequireAnyClientCert,
2108						Credential: &echPublicCertificate,
2109					},
2110					shimCertificate: &rsaCertificate,
2111					flags: append([]string{
2112						"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2113					}, flags...),
2114					shouldFail:         true,
2115					expectedLocalError: "tls: client didn't provide a certificate",
2116				})
2117			}
2118		}
2119
2120		// Test that ECH and Channel ID can be used together. Channel ID does
2121		// not exist in DTLS.
2122		if protocol != dtls {
2123			testCases = append(testCases, testCase{
2124				testType: clientTest,
2125				protocol: protocol,
2126				name:     prefix + "ECH-Client-ChannelID",
2127				config: Config{
2128					ServerECHConfigs: []ServerECHConfig{echConfig},
2129					RequestChannelID: true,
2130				},
2131				flags: []string{
2132					"-send-channel-id", channelIDKeyPath,
2133					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2134					"-expect-ech-accept",
2135				},
2136				resumeSession: true,
2137				expectations: connectionExpectations{
2138					channelID:   true,
2139					echAccepted: true,
2140				},
2141			})
2142
2143			// Handshakes where ECH is rejected do not offer or accept Channel ID.
2144			testCases = append(testCases, testCase{
2145				testType: clientTest,
2146				protocol: protocol,
2147				name:     prefix + "ECH-Client-Reject-NoChannelID-TLS13",
2148				config: Config{
2149					MinVersion: VersionTLS13,
2150					MaxVersion: VersionTLS13,
2151					Bugs: ProtocolBugs{
2152						AlwaysNegotiateChannelID: true,
2153					},
2154					Credential: &echPublicCertificate,
2155				},
2156				flags: []string{
2157					"-send-channel-id", channelIDKeyPath,
2158					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2159				},
2160				shouldFail:         true,
2161				expectedLocalError: "remote error: unsupported extension",
2162				expectedError:      ":UNEXPECTED_EXTENSION:",
2163			})
2164			if protocol != quic {
2165				testCases = append(testCases, testCase{
2166					testType: clientTest,
2167					protocol: protocol,
2168					name:     prefix + "ECH-Client-Reject-NoChannelID-TLS12",
2169					config: Config{
2170						MinVersion: VersionTLS12,
2171						MaxVersion: VersionTLS12,
2172						Bugs: ProtocolBugs{
2173							AlwaysNegotiateChannelID: true,
2174						},
2175						Credential: &echPublicCertificate,
2176					},
2177					flags: []string{
2178						"-send-channel-id", channelIDKeyPath,
2179						"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2180					},
2181					shouldFail:         true,
2182					expectedLocalError: "remote error: unsupported extension",
2183					expectedError:      ":UNEXPECTED_EXTENSION:",
2184				})
2185			}
2186		}
2187
2188		// Test that ECH correctly overrides the host name for certificate
2189		// verification.
2190		testCases = append(testCases, testCase{
2191			testType: clientTest,
2192			protocol: protocol,
2193			name:     prefix + "ECH-Client-NotOffered-NoOverrideName",
2194			flags: []string{
2195				"-verify-peer",
2196				"-use-custom-verify-callback",
2197				// When not offering ECH, verify the usual name in both full
2198				// and resumption handshakes.
2199				"-reverify-on-resume",
2200				"-expect-no-ech-name-override",
2201			},
2202			resumeSession: true,
2203		})
2204		testCases = append(testCases, testCase{
2205			testType: clientTest,
2206			protocol: protocol,
2207			name:     prefix + "ECH-Client-GREASE-NoOverrideName",
2208			flags: []string{
2209				"-verify-peer",
2210				"-use-custom-verify-callback",
2211				"-enable-ech-grease",
2212				// When offering ECH GREASE, verify the usual name in both full
2213				// and resumption handshakes.
2214				"-reverify-on-resume",
2215				"-expect-no-ech-name-override",
2216			},
2217			resumeSession: true,
2218		})
2219		if protocol != quic {
2220			testCases = append(testCases, testCase{
2221				testType: clientTest,
2222				protocol: protocol,
2223				name:     prefix + "ECH-Client-Rejected-OverrideName-TLS12",
2224				config: Config{
2225					MinVersion: VersionTLS12,
2226					MaxVersion: VersionTLS12,
2227				},
2228				flags: []string{
2229					"-verify-peer",
2230					"-use-custom-verify-callback",
2231					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2232					// When ECH is rejected, verify the public name. This can
2233					// only happen in full handshakes.
2234					"-expect-ech-name-override", "public.example",
2235				},
2236				shouldFail:         true,
2237				expectedError:      ":ECH_REJECTED:",
2238				expectedLocalError: "remote error: ECH required",
2239			})
2240		}
2241		testCases = append(testCases, testCase{
2242			testType: clientTest,
2243			protocol: protocol,
2244			name:     prefix + "ECH-Client-Reject-OverrideName-TLS13",
2245			config: Config{
2246				MinVersion: VersionTLS13,
2247				MaxVersion: VersionTLS13,
2248				Credential: &echPublicCertificate,
2249			},
2250			flags: []string{
2251				"-verify-peer",
2252				"-use-custom-verify-callback",
2253				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2254				// When ECH is rejected, verify the public name. This can
2255				// only happen in full handshakes.
2256				"-expect-ech-name-override", "public.example",
2257			},
2258			shouldFail:         true,
2259			expectedError:      ":ECH_REJECTED:",
2260			expectedLocalError: "remote error: ECH required",
2261		})
2262		testCases = append(testCases, testCase{
2263			testType: clientTest,
2264			protocol: protocol,
2265			name:     prefix + "ECH-Client-Accept-NoOverrideName",
2266			config: Config{
2267				ServerECHConfigs: []ServerECHConfig{echConfig},
2268			},
2269			flags: []string{
2270				"-verify-peer",
2271				"-use-custom-verify-callback",
2272				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2273				"-expect-ech-accept",
2274				// When ECH is accepted, verify the usual name in both full and
2275				// resumption handshakes.
2276				"-reverify-on-resume",
2277				"-expect-no-ech-name-override",
2278			},
2279			resumeSession: true,
2280			expectations:  connectionExpectations{echAccepted: true},
2281		})
2282		// TODO(crbug.com/381113363): Enable this test for DTLS once we
2283		// support early data in DTLS 1.3.
2284		if protocol != dtls {
2285			testCases = append(testCases, testCase{
2286				testType: clientTest,
2287				protocol: protocol,
2288				name:     prefix + "ECH-Client-Reject-EarlyDataRejected-OverrideNameOnRetry",
2289				config: Config{
2290					ServerECHConfigs: []ServerECHConfig{echConfig},
2291					Credential:       &echPublicCertificate,
2292				},
2293				resumeConfig: &Config{
2294					Credential: &echPublicCertificate,
2295				},
2296				flags: []string{
2297					"-verify-peer",
2298					"-use-custom-verify-callback",
2299					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2300					// Although the resumption connection does not accept ECH, the
2301					// API will report ECH was accepted at the 0-RTT point.
2302					"-expect-ech-accept",
2303					// The resumption connection verifies certificates twice. First,
2304					// if reverification is enabled, we verify the 0-RTT certificate
2305					// as if ECH as accepted. There should be no name override.
2306					// Next, on the post-0-RTT-rejection retry, we verify the new
2307					// server certificate. This picks up the ECH reject, so it
2308					// should use public.example.
2309					"-reverify-on-resume",
2310					"-on-resume-expect-no-ech-name-override",
2311					"-on-retry-expect-ech-name-override", "public.example",
2312				},
2313				resumeSession:           true,
2314				expectResumeRejected:    true,
2315				earlyData:               true,
2316				expectEarlyDataRejected: true,
2317				expectations:            connectionExpectations{echAccepted: true},
2318				resumeExpectations:      &connectionExpectations{echAccepted: false},
2319				shouldFail:              true,
2320				expectedError:           ":ECH_REJECTED:",
2321				expectedLocalError:      "remote error: ECH required",
2322			})
2323		}
2324
2325		// Test that the client checks both HelloRetryRequest and ServerHello
2326		// for a confirmation signal.
2327		testCases = append(testCases, testCase{
2328			testType: clientTest,
2329			protocol: protocol,
2330			name:     prefix + "ECH-Client-HelloRetryRequest-MissingServerHelloConfirmation",
2331			config: Config{
2332				MinVersion:       VersionTLS13,
2333				MaxVersion:       VersionTLS13,
2334				CurvePreferences: []CurveID{CurveP384},
2335				ServerECHConfigs: []ServerECHConfig{echConfig},
2336				Bugs: ProtocolBugs{
2337					ExpectMissingKeyShare:          true, // Check we triggered HRR.
2338					OmitServerHelloECHConfirmation: true,
2339				},
2340			},
2341			resumeSession: true,
2342			flags: []string{
2343				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2344				"-expect-hrr", // Check we triggered HRR.
2345			},
2346			shouldFail:    true,
2347			expectedError: ":INCONSISTENT_ECH_NEGOTIATION:",
2348		})
2349
2350		// Test the message callback is correctly reported, with and without
2351		// HelloRetryRequest.
2352		clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n"
2353		clientAndServerHelloInitial := clientAndServerHello
2354		if protocol == tls {
2355			clientAndServerHelloInitial += "write ccs\n"
2356		}
2357		// EncryptedExtensions onwards.
2358		finishHandshake := `read hs 8
2359read hs 11
2360read hs 15
2361read hs 20
2362write hs 20
2363read ack
2364read hs 4
2365read hs 4
2366`
2367		if protocol != dtls {
2368			finishHandshake = strings.ReplaceAll(finishHandshake, "read ack\n", "")
2369		}
2370		testCases = append(testCases, testCase{
2371			testType: clientTest,
2372			protocol: protocol,
2373			name:     prefix + "ECH-Client-MessageCallback",
2374			config: Config{
2375				MinVersion:       VersionTLS13,
2376				MaxVersion:       VersionTLS13,
2377				ServerECHConfigs: []ServerECHConfig{echConfig},
2378				Bugs: ProtocolBugs{
2379					NoCloseNotify: true, // Align QUIC and TCP traces.
2380				},
2381			},
2382			flags: []string{
2383				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2384				"-expect-ech-accept",
2385				"-expect-msg-callback", clientAndServerHelloInitial + finishHandshake,
2386			},
2387			expectations: connectionExpectations{echAccepted: true},
2388		})
2389		testCases = append(testCases, testCase{
2390			testType: clientTest,
2391			protocol: protocol,
2392			name:     prefix + "ECH-Client-MessageCallback-HelloRetryRequest",
2393			config: Config{
2394				MinVersion:       VersionTLS13,
2395				MaxVersion:       VersionTLS13,
2396				CurvePreferences: []CurveID{CurveP384},
2397				ServerECHConfigs: []ServerECHConfig{echConfig},
2398				Bugs: ProtocolBugs{
2399					ExpectMissingKeyShare: true, // Check we triggered HRR.
2400					NoCloseNotify:         true, // Align QUIC and TCP traces.
2401				},
2402			},
2403			flags: []string{
2404				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
2405				"-expect-ech-accept",
2406				"-expect-hrr", // Check we triggered HRR.
2407				"-expect-msg-callback", clientAndServerHelloInitial + clientAndServerHello + finishHandshake,
2408			},
2409			expectations: connectionExpectations{echAccepted: true},
2410		})
2411	}
2412}
2413