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