Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
2436 lines
80 KiB
Go
2436 lines
80 KiB
Go
// Copyright 2025 The BoringSSL Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package runner
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"math/big"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"boringssl.googlesource.com/boringssl.git/ssl/test/runner/hpke"
|
|
)
|
|
|
|
type echCipher struct {
|
|
name string
|
|
cipher HPKECipherSuite
|
|
}
|
|
|
|
var echCiphers = []echCipher{
|
|
{
|
|
name: "HKDF-SHA256-AES-128-GCM",
|
|
cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.AES128GCM},
|
|
},
|
|
{
|
|
name: "HKDF-SHA256-AES-256-GCM",
|
|
cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.AES256GCM},
|
|
},
|
|
{
|
|
name: "HKDF-SHA256-ChaCha20-Poly1305",
|
|
cipher: HPKECipherSuite{KDF: hpke.HKDFSHA256, AEAD: hpke.ChaCha20Poly1305},
|
|
},
|
|
}
|
|
|
|
// generateServerECHConfig constructs a ServerECHConfig with a fresh X25519
|
|
// keypair and using |template| as a template for the ECHConfig. If fields are
|
|
// omitted, defaults are used.
|
|
func generateServerECHConfig(template *ECHConfig) ServerECHConfig {
|
|
publicKey, secretKey, err := hpke.GenerateKeyPairX25519()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
templateCopy := *template
|
|
if templateCopy.KEM == 0 {
|
|
templateCopy.KEM = hpke.X25519WithHKDFSHA256
|
|
}
|
|
if len(templateCopy.PublicKey) == 0 {
|
|
templateCopy.PublicKey = publicKey
|
|
}
|
|
if len(templateCopy.CipherSuites) == 0 {
|
|
templateCopy.CipherSuites = make([]HPKECipherSuite, len(echCiphers))
|
|
for i, cipher := range echCiphers {
|
|
templateCopy.CipherSuites[i] = cipher.cipher
|
|
}
|
|
}
|
|
if len(templateCopy.PublicName) == 0 {
|
|
templateCopy.PublicName = "public.example"
|
|
}
|
|
if templateCopy.MaxNameLen == 0 {
|
|
templateCopy.MaxNameLen = 64
|
|
}
|
|
return ServerECHConfig{ECHConfig: CreateECHConfig(&templateCopy), Key: secretKey}
|
|
}
|
|
|
|
func addEncryptedClientHelloTests() {
|
|
// echConfig's ConfigID should match the one used in ssl/test/fuzzer.h.
|
|
echConfig := generateServerECHConfig(&ECHConfig{ConfigID: 42})
|
|
echConfig1 := generateServerECHConfig(&ECHConfig{ConfigID: 43})
|
|
echConfig2 := generateServerECHConfig(&ECHConfig{ConfigID: 44})
|
|
echConfig3 := generateServerECHConfig(&ECHConfig{ConfigID: 45})
|
|
echConfigRepeatID := generateServerECHConfig(&ECHConfig{ConfigID: 42})
|
|
|
|
echSecretCertificate := generateSingleCertChain(&x509.Certificate{
|
|
SerialNumber: big.NewInt(57005),
|
|
Subject: pkix.Name{
|
|
CommonName: "test cert",
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
DNSNames: []string{"secret.example"},
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
}, &rsa2048Key)
|
|
echPublicCertificate := generateSingleCertChain(&x509.Certificate{
|
|
SerialNumber: big.NewInt(57005),
|
|
Subject: pkix.Name{
|
|
CommonName: "test cert",
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
DNSNames: []string{"public.example"},
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
}, &rsa2048Key)
|
|
echLongNameCertificate := generateSingleCertChain(&x509.Certificate{
|
|
SerialNumber: big.NewInt(57005),
|
|
Subject: pkix.Name{
|
|
CommonName: "test cert",
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
DNSNames: []string{"test0123456789.example"},
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
}, &ecdsaP256Key)
|
|
|
|
for _, protocol := range []protocol{tls, quic, dtls} {
|
|
prefix := protocol.String() + "-"
|
|
|
|
// There are two ClientHellos, so many of our tests have
|
|
// HelloRetryRequest variations.
|
|
for _, hrr := range []bool{false, true} {
|
|
var suffix string
|
|
var defaultCurves []CurveID
|
|
if hrr {
|
|
suffix = "-HelloRetryRequest"
|
|
// Require a HelloRetryRequest for every curve.
|
|
defaultCurves = []CurveID{}
|
|
}
|
|
|
|
// Test the server can accept ECH.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
DefaultCurves: defaultCurves,
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test the server can accept ECH with a minimal ClientHelloOuter.
|
|
// This confirms that the server does not unexpectedly pick up
|
|
// fields from the wrong ClientHello.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-MinimalClientHelloOuter" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
DefaultCurves: defaultCurves,
|
|
Bugs: ProtocolBugs{
|
|
MinimalClientHelloOuter: true,
|
|
},
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that the server can decline ECH. In particular, it must send
|
|
// retry configs.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-Decline" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
// The client uses an ECHConfig that the server does not understand
|
|
// so we can observe which retry configs the server sends back.
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
OfferSessionInClientHelloOuter: true,
|
|
ExpectECHRetryConfigs: CreateECHConfigList(echConfig2.ECHConfig.Raw, echConfig3.ECHConfig.Raw),
|
|
},
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
// Configure three ECHConfigs on the shim, only two of which
|
|
// should be sent in retry configs.
|
|
"-ech-server-config", base64FlagValue(echConfig1.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig1.Key),
|
|
"-ech-is-retry-config", "0",
|
|
"-ech-server-config", base64FlagValue(echConfig2.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig2.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-ech-server-config", base64FlagValue(echConfig3.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig3.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "public.example",
|
|
},
|
|
})
|
|
|
|
// Test that the server considers a ClientHelloInner indicating TLS
|
|
// 1.2 to be a fatal error.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-TLS12InInner" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
AllowTLS12InClientHelloInner: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectedError: ":INVALID_CLIENT_HELLO_INNER:",
|
|
})
|
|
|
|
// When inner ECH extension is absent from the ClientHelloInner, the
|
|
// server should fail the connection.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-MissingECHInner" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
OmitECHInner: !hrr,
|
|
OmitSecondECHInner: hrr,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectedError: ":INVALID_CLIENT_HELLO_INNER:",
|
|
})
|
|
|
|
// Test that the server can decode ech_outer_extensions.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OuterExtensions" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHOuterExtensions: []uint16{
|
|
extensionKeyShare,
|
|
extensionSupportedCurves,
|
|
// Include a custom extension, to test that unrecognized
|
|
// extensions are also decoded.
|
|
extensionCustom,
|
|
},
|
|
Bugs: ProtocolBugs{
|
|
CustomExtension: "test",
|
|
OnlyCompressSecondClientHelloInner: hrr,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that the server allows referenced ClientHelloOuter
|
|
// extensions to be interleaved with other extensions. Only the
|
|
// relative order must match.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OuterExtensions-Interleaved" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHOuterExtensions: []uint16{
|
|
extensionKeyShare,
|
|
extensionSupportedCurves,
|
|
extensionCustom,
|
|
},
|
|
Bugs: ProtocolBugs{
|
|
CustomExtension: "test",
|
|
OnlyCompressSecondClientHelloInner: hrr,
|
|
ECHOuterExtensionOrder: []uint16{
|
|
extensionServerName,
|
|
extensionKeyShare,
|
|
extensionSupportedVersions,
|
|
extensionPSKKeyExchangeModes,
|
|
extensionSupportedCurves,
|
|
extensionSignatureAlgorithms,
|
|
extensionCustom,
|
|
},
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that the server rejects references to extensions in the
|
|
// wrong order.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OuterExtensions-WrongOrder" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHOuterExtensions: []uint16{
|
|
extensionKeyShare,
|
|
extensionSupportedCurves,
|
|
},
|
|
Bugs: ProtocolBugs{
|
|
CustomExtension: "test",
|
|
OnlyCompressSecondClientHelloInner: hrr,
|
|
ECHOuterExtensionOrder: []uint16{
|
|
extensionSupportedCurves,
|
|
extensionKeyShare,
|
|
},
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectedError: ":INVALID_OUTER_EXTENSION:",
|
|
})
|
|
|
|
// Test that the server rejects duplicated values in ech_outer_extensions.
|
|
// Besides causing the server to reconstruct an invalid ClientHelloInner
|
|
// with duplicated extensions, this behavior would be vulnerable to DoS
|
|
// attacks.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OuterExtensions-Duplicate" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHOuterExtensions: []uint16{
|
|
extensionSupportedCurves,
|
|
extensionSupportedCurves,
|
|
},
|
|
Bugs: ProtocolBugs{
|
|
OnlyCompressSecondClientHelloInner: hrr,
|
|
// Don't duplicate the extension in ClientHelloOuter.
|
|
ECHOuterExtensionOrder: []uint16{
|
|
extensionSupportedCurves,
|
|
},
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectedError: ":INVALID_OUTER_EXTENSION:",
|
|
})
|
|
|
|
// Test that the server rejects references to missing extensions in
|
|
// ech_outer_extensions.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OuterExtensions-Missing" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHOuterExtensions: []uint16{
|
|
extensionCustom,
|
|
},
|
|
Bugs: ProtocolBugs{
|
|
OnlyCompressSecondClientHelloInner: hrr,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectedError: ":INVALID_OUTER_EXTENSION:",
|
|
})
|
|
|
|
// Test that the server rejects a references to the ECH extension in
|
|
// ech_outer_extensions. The ECH extension is not authenticated in the
|
|
// AAD and would result in an invalid ClientHelloInner.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OuterExtensions-SelfReference" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
DefaultCurves: defaultCurves,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHOuterExtensions: []uint16{
|
|
extensionEncryptedClientHello,
|
|
},
|
|
Bugs: ProtocolBugs{
|
|
OnlyCompressSecondClientHelloInner: hrr,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectedError: ":INVALID_OUTER_EXTENSION:",
|
|
})
|
|
|
|
// Test the message callback is correctly reported with ECH.
|
|
clientAndServerHello := "read hs 1\nread clienthelloinner\nwrite hs 2\n"
|
|
expectMsgCallback := clientAndServerHello
|
|
if protocol == tls {
|
|
expectMsgCallback += "write ccs\n"
|
|
}
|
|
if hrr {
|
|
expectMsgCallback += clientAndServerHello
|
|
}
|
|
// EncryptedExtensions onwards.
|
|
expectMsgCallback += `write hs 8
|
|
write hs 11
|
|
write hs 15
|
|
write hs 20
|
|
read hs 20
|
|
write ack
|
|
write hs 4
|
|
write hs 4
|
|
read ack
|
|
read ack
|
|
`
|
|
if protocol != dtls {
|
|
expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "write ack\n", "")
|
|
expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "read ack\n", "")
|
|
}
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-MessageCallback" + suffix,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
DefaultCurves: defaultCurves,
|
|
Bugs: ProtocolBugs{
|
|
NoCloseNotify: true, // Align QUIC and TCP traces.
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-ech-accept",
|
|
"-expect-msg-callback", expectMsgCallback,
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Test that ECH, which runs before an async early callback, interacts
|
|
// correctly in the state machine.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-AsyncEarlyCallback",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
},
|
|
flags: []string{
|
|
"-async",
|
|
"-use-early-callback",
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that we successfully rewind the TLS state machine and disable ECH in the
|
|
// case that the select_cert_cb signals that ECH is not possible for the SNI in
|
|
// ClientHelloInner.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-FailCallbackNeedRewind",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
},
|
|
flags: []string{
|
|
"-async",
|
|
"-fail-early-callback-ech-rewind",
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "public.example",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: false,
|
|
},
|
|
})
|
|
|
|
// Test that we correctly handle falling back to a ClientHelloOuter with
|
|
// no SNI (public name).
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-RewindWithNoPublicName",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
OmitPublicName: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-async",
|
|
"-fail-early-callback-ech-rewind",
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-no-server-name",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: false,
|
|
},
|
|
})
|
|
|
|
// Test ECH-enabled server with two ECHConfigs can decrypt client's ECH when
|
|
// it uses the second ECHConfig.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-SecondECHConfig",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig1.ECHConfig,
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-ech-server-config", base64FlagValue(echConfig1.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig1.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test ECH-enabled server with two ECHConfigs that have the same config
|
|
// ID can decrypt client's ECH when it uses the second ECHConfig.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-RepeatedConfigID",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfigRepeatID.ECHConfig,
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-ech-server-config", base64FlagValue(echConfigRepeatID.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfigRepeatID.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test all supported ECH cipher suites.
|
|
for i, cipher := range echCiphers {
|
|
otherCipher := echCiphers[(i+1)%len(echCiphers)]
|
|
|
|
// Test the ECH server can handle the specified cipher.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-Cipher-" + cipher.name,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHCipherSuites: []HPKECipherSuite{cipher.cipher},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that client can offer the specified cipher and skip over
|
|
// unrecognized ones.
|
|
cipherConfig := generateServerECHConfig(&ECHConfig{
|
|
ConfigID: 42,
|
|
CipherSuites: []HPKECipherSuite{
|
|
{KDF: 0x1111, AEAD: 0x2222},
|
|
{KDF: cipher.cipher.KDF, AEAD: 0x2222},
|
|
{KDF: 0x1111, AEAD: cipher.cipher.AEAD},
|
|
cipher.cipher,
|
|
},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Cipher-" + cipher.name,
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{cipherConfig},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(cipherConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that the ECH server rejects the specified cipher if not
|
|
// listed in its ECHConfig.
|
|
otherCipherConfig := generateServerECHConfig(&ECHConfig{
|
|
ConfigID: 42,
|
|
CipherSuites: []HPKECipherSuite{otherCipher.cipher},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-DisabledCipher-" + cipher.name,
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
ECHCipherSuites: []HPKECipherSuite{cipher.cipher},
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHRetryConfigs: CreateECHConfigList(otherCipherConfig.ECHConfig.Raw),
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(otherCipherConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(otherCipherConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "public.example",
|
|
},
|
|
})
|
|
}
|
|
|
|
// Test that the ECH server handles a short enc value by falling back to
|
|
// ClientHelloOuter.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-ShortEnc",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHRetryConfigs: CreateECHConfigList(echConfig.ECHConfig.Raw),
|
|
TruncateClientECHEnc: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-server-name", "public.example",
|
|
},
|
|
})
|
|
|
|
// Test that the server handles decryption failure by falling back to
|
|
// ClientHelloOuter.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-CorruptEncryptedClientHello",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHRetryConfigs: CreateECHConfigList(echConfig.ECHConfig.Raw),
|
|
CorruptEncryptedClientHello: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
})
|
|
|
|
// Test that the server treats decryption failure in the second
|
|
// ClientHello as fatal.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-CorruptSecondEncryptedClientHello",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
// Force a HelloRetryRequest.
|
|
DefaultCurves: []CurveID{},
|
|
Bugs: ProtocolBugs{
|
|
CorruptSecondEncryptedClientHello: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":DECRYPTION_FAILED:",
|
|
expectedLocalError: "remote error: error decrypting message",
|
|
})
|
|
|
|
// Test that the server treats a missing second ECH extension as fatal.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-OmitSecondEncryptedClientHello",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
// Force a HelloRetryRequest.
|
|
DefaultCurves: []CurveID{},
|
|
Bugs: ProtocolBugs{
|
|
OmitSecondEncryptedClientHello: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":MISSING_EXTENSION:",
|
|
expectedLocalError: "remote error: missing extension",
|
|
})
|
|
|
|
// Test that the server treats a mismatched config ID in the second ClientHello as fatal.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-DifferentConfigIDSecondClientHello",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
// Force a HelloRetryRequest.
|
|
DefaultCurves: []CurveID{},
|
|
Bugs: ProtocolBugs{
|
|
CorruptSecondEncryptedClientHelloConfigID: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":DECODE_ERROR:",
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
})
|
|
|
|
// Test early data works with ECH, in both accept and reject cases.
|
|
// TODO(crbug.com/381113363): Enable these tests for DTLS once we
|
|
// support early data in DTLS 1.3.
|
|
if protocol != dtls {
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-EarlyData",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
},
|
|
resumeSession: true,
|
|
earlyData: true,
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-EarlyDataRejected",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
// Cause the server to reject 0-RTT with a bad ticket age.
|
|
SendTicketAge: 1 * time.Hour,
|
|
},
|
|
},
|
|
resumeSession: true,
|
|
earlyData: true,
|
|
expectEarlyDataRejected: true,
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Test servers with ECH disabled correctly ignore the extension and
|
|
// handshake with the ClientHelloOuter.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-Disabled",
|
|
config: Config{
|
|
ServerName: "secret.example",
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
},
|
|
flags: []string{
|
|
"-expect-server-name", "public.example",
|
|
},
|
|
})
|
|
|
|
// Test that ECH can be used with client certificates. In particular,
|
|
// the name override logic should not interfere with the server.
|
|
// Test the server can accept ECH.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-ClientAuth",
|
|
config: Config{
|
|
Credential: &rsaCertificate,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-ech-accept",
|
|
"-require-any-client-certificate",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-Decline-ClientAuth",
|
|
config: Config{
|
|
Credential: &rsaCertificate,
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHRetryConfigs: CreateECHConfigList(echConfig1.ECHConfig.Raw),
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig1.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig1.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-require-any-client-certificate",
|
|
},
|
|
})
|
|
|
|
// Test that the server accepts padding.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-Padding",
|
|
config: Config{
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
ClientECHPadding: 10,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that the server rejects bad padding.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-BadPadding",
|
|
config: Config{
|
|
ClientECHConfig: echConfig.ECHConfig,
|
|
Bugs: ProtocolBugs{
|
|
ClientECHPadding: 10,
|
|
BadClientECHPadding: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
|
|
"-ech-server-key", base64FlagValue(echConfig.Key),
|
|
"-ech-is-retry-config", "1",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":DECODE_ERROR",
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
})
|
|
|
|
// Test the client's behavior when the server ignores ECH GREASE.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-GREASE-Client-TLS13",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
},
|
|
},
|
|
flags: []string{"-enable-ech-grease"},
|
|
})
|
|
|
|
// Test the client's ECH GREASE behavior when responding to server's
|
|
// HelloRetryRequest. This test implicitly checks that the first and second
|
|
// ClientHello messages have identical ECH extensions.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-GREASE-Client-TLS13-HelloRetryRequest",
|
|
config: Config{
|
|
MaxVersion: VersionTLS13,
|
|
MinVersion: VersionTLS13,
|
|
// P-384 requires a HelloRetryRequest against BoringSSL's default
|
|
// configuration. Assert this with ExpectMissingKeyShare.
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
Bugs: ProtocolBugs{
|
|
ExpectMissingKeyShare: true,
|
|
ExpectClientECH: true,
|
|
},
|
|
},
|
|
flags: []string{"-enable-ech-grease", "-expect-hrr"},
|
|
})
|
|
|
|
unsupportedVersion := []byte{
|
|
// version
|
|
0xba, 0xdd,
|
|
// length
|
|
0x00, 0x05,
|
|
// contents
|
|
0x05, 0x04, 0x03, 0x02, 0x01,
|
|
}
|
|
|
|
// Test that the client accepts a well-formed encrypted_client_hello
|
|
// extension in response to ECH GREASE. The response includes one ECHConfig
|
|
// with a supported version and one with an unsupported version.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-GREASE-Client-TLS13-Retry-Configs",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
// Include an additional well-formed ECHConfig with an
|
|
// unsupported version. This ensures the client can skip
|
|
// unsupported configs.
|
|
SendECHRetryConfigs: CreateECHConfigList(echConfig.ECHConfig.Raw, unsupportedVersion),
|
|
},
|
|
},
|
|
flags: []string{"-enable-ech-grease"},
|
|
})
|
|
|
|
// TLS 1.2 ServerHellos cannot contain retry configs.
|
|
if protocol != quic {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-GREASE-Client-TLS12-RejectRetryConfigs",
|
|
config: Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS12,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
AlwaysSendECHRetryConfigs: true,
|
|
},
|
|
},
|
|
flags: []string{"-enable-ech-grease"},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-TLS12-RejectRetryConfigs",
|
|
config: Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS12,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
AlwaysSendECHRetryConfigs: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig1.ECHConfig.Raw)),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
})
|
|
}
|
|
|
|
// Retry configs must be rejected when ECH is accepted.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Accept-RejectRetryConfigs",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
AlwaysSendECHRetryConfigs: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
})
|
|
|
|
// Unsolicited ECH HelloRetryRequest extensions should be rejected.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-UnsolictedHRRExtension",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
Bugs: ProtocolBugs{
|
|
AlwaysSendECHHelloRetryRequest: true,
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
},
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
})
|
|
|
|
// GREASE should ignore ECH HelloRetryRequest extensions.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-GREASE-IgnoreHRRExtension",
|
|
config: Config{
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
Bugs: ProtocolBugs{
|
|
AlwaysSendECHHelloRetryRequest: true,
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
},
|
|
},
|
|
flags: []string{"-enable-ech-grease"},
|
|
})
|
|
|
|
// Random ECH HelloRetryRequest extensions also signal ECH reject.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-RandomHRRExtension",
|
|
config: Config{
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
Bugs: ProtocolBugs{
|
|
AlwaysSendECHHelloRetryRequest: true,
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
|
|
// Test that the client aborts with a decode_error alert when it receives a
|
|
// syntactically-invalid encrypted_client_hello extension from the server.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-GREASE-Client-TLS13-Invalid-Retry-Configs",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
SendECHRetryConfigs: []byte{0xba, 0xdd, 0xec, 0xcc},
|
|
},
|
|
},
|
|
flags: []string{"-enable-ech-grease"},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: error decoding message",
|
|
expectedError: ":ERROR_PARSING_EXTENSION:",
|
|
})
|
|
|
|
// Test that the server responds to an inner ECH extension with the
|
|
// acceptance confirmation.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-ECHInner",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
AlwaysSendECHInner: true,
|
|
},
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-ECHInner-HelloRetryRequest",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
// Force a HelloRetryRequest.
|
|
DefaultCurves: []CurveID{},
|
|
Bugs: ProtocolBugs{
|
|
AlwaysSendECHInner: true,
|
|
},
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
|
|
// Test that server fails the handshake when it sees a non-empty
|
|
// inner ECH extension.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-ECHInner-NotEmpty",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
AlwaysSendECHInner: true,
|
|
SendInvalidECHInner: []byte{42, 42, 42},
|
|
},
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: error decoding message",
|
|
expectedError: ":ERROR_PARSING_EXTENSION:",
|
|
})
|
|
|
|
// Test that a TLS 1.3 server that receives an inner ECH extension can
|
|
// negotiate TLS 1.2 without clobbering the downgrade signal.
|
|
if protocol != quic {
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Server-ECHInner-Absent-TLS12",
|
|
config: Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
// Omit supported_versions extension so the server negotiates
|
|
// TLS 1.2.
|
|
OmitSupportedVersions: true,
|
|
AlwaysSendECHInner: true,
|
|
},
|
|
},
|
|
// Check that the client sees the TLS 1.3 downgrade signal in
|
|
// ServerHello.random.
|
|
shouldFail: true,
|
|
expectedLocalError: "tls: downgrade from TLS 1.3 detected",
|
|
})
|
|
}
|
|
|
|
// Test the client can negotiate ECH, with and without HelloRetryRequest.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
ExpectOuterServerName: "public.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-HelloRetryRequest",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
ExpectOuterServerName: "public.example",
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
"-expect-hrr", // Check we triggered HRR.
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test the client can negotiate ECH with early data.
|
|
// TODO(crbug.com/381113363): Enable these tests for DTLS once we
|
|
// support early data in DTLS 1.3.
|
|
if protocol != dtls {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-EarlyData",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
earlyData: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-EarlyDataRejected",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
AlwaysRejectEarlyData: true,
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
earlyData: true,
|
|
expectEarlyDataRejected: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
}
|
|
|
|
if protocol != quic {
|
|
// Test that an ECH client does not offer a TLS 1.2 session.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-TLS12SessionID",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
SessionTicketsDisabled: true,
|
|
},
|
|
resumeConfig: &Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectNoTLS12Session: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-on-resume-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-on-resume-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
resumeExpectations: &connectionExpectations{echAccepted: true},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-TLS12SessionTicket",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
},
|
|
resumeConfig: &Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectNoTLS12Session: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-on-resume-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-on-resume-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
resumeExpectations: &connectionExpectations{echAccepted: true},
|
|
})
|
|
}
|
|
|
|
// ClientHelloInner should not include NPN, which is a TLS 1.2-only
|
|
// extensions. The Go server will enforce this, so this test only needs
|
|
// to configure the feature on the shim. Other application extensions
|
|
// are sent implicitly.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-NoNPN",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
// Enable NPN.
|
|
"-select-next-proto", "foo",
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test that the client iterates over configurations in the
|
|
// ECHConfigList and selects the first with supported parameters.
|
|
unsupportedKEM := generateServerECHConfig(&ECHConfig{
|
|
KEM: 0x6666,
|
|
PublicKey: []byte{1, 2, 3, 4},
|
|
}).ECHConfig
|
|
unsupportedCipherSuites := generateServerECHConfig(&ECHConfig{
|
|
CipherSuites: []HPKECipherSuite{{0x1111, 0x2222}},
|
|
}).ECHConfig
|
|
unsupportedMandatoryExtension := generateServerECHConfig(&ECHConfig{
|
|
UnsupportedMandatoryExtension: true,
|
|
}).ECHConfig
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-SelectECHConfig",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(
|
|
unsupportedVersion,
|
|
unsupportedKEM.Raw,
|
|
unsupportedCipherSuites.Raw,
|
|
unsupportedMandatoryExtension.Raw,
|
|
echConfig.ECHConfig.Raw,
|
|
// |echConfig1| is also supported, but the client should
|
|
// select the first one.
|
|
echConfig1.ECHConfig.Raw,
|
|
)),
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Test that the client skips sending ECH if all ECHConfigs are
|
|
// unsupported.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-NoSupportedConfigs",
|
|
config: Config{
|
|
Bugs: ProtocolBugs{
|
|
ExpectNoClientECH: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(
|
|
unsupportedVersion,
|
|
unsupportedKEM.Raw,
|
|
unsupportedCipherSuites.Raw,
|
|
unsupportedMandatoryExtension.Raw,
|
|
)),
|
|
},
|
|
})
|
|
|
|
// If ECH GREASE is enabled, the client should send ECH GREASE when no
|
|
// configured ECHConfig is suitable.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-NoSupportedConfigs-GREASE",
|
|
config: Config{
|
|
Bugs: ProtocolBugs{
|
|
ExpectClientECH: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(
|
|
unsupportedVersion,
|
|
unsupportedKEM.Raw,
|
|
unsupportedCipherSuites.Raw,
|
|
unsupportedMandatoryExtension.Raw,
|
|
)),
|
|
"-enable-ech-grease",
|
|
},
|
|
})
|
|
|
|
// If both ECH GREASE and suitable ECHConfigs are available, the
|
|
// client should send normal ECH.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-GREASE",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test that GREASE extensions correctly interact with ECH. Both the
|
|
// inner and outer ClientHellos should include GREASE extensions.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-GREASEExtensions",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectGREASE: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
"-enable-grease",
|
|
},
|
|
resumeSession: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test that the client tolerates unsupported extensions if the
|
|
// mandatory bit is not set.
|
|
unsupportedExtension := generateServerECHConfig(&ECHConfig{UnsupportedExtension: true})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-UnsupportedExtension",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{unsupportedExtension},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(unsupportedExtension.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Syntax errors in the ECHConfigList should be rejected.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-InvalidECHConfigList",
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw[1:])),
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":INVALID_ECH_CONFIG_LIST:",
|
|
})
|
|
|
|
// If the ClientHelloInner has no server_name extension, while the
|
|
// ClientHelloOuter has one, the client must check for unsolicited
|
|
// extensions based on the selected ClientHello.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-UnsolicitedInnerServerNameAck",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
// ClientHelloOuter should have a server name.
|
|
ExpectOuterServerName: "public.example",
|
|
// The server will acknowledge the server_name extension.
|
|
// This option runs whether or not the client requested the
|
|
// extension.
|
|
SendServerNameAck: true,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
// No -host-name flag.
|
|
"-expect-ech-accept",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Most extensions are the same between ClientHelloInner and
|
|
// ClientHelloOuter and can be compressed.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-ExpectECHOuterExtensions",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
NextProtos: []string{"proto"},
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHOuterExtensions: []uint16{
|
|
extensionALPN,
|
|
extensionKeyShare,
|
|
extensionPSKKeyExchangeModes,
|
|
extensionSignatureAlgorithms,
|
|
extensionSupportedCurves,
|
|
},
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
"-advertise-alpn", "\x05proto",
|
|
"-expect-alpn", "proto",
|
|
"-host-name", "secret.example",
|
|
},
|
|
expectations: connectionExpectations{
|
|
echAccepted: true,
|
|
nextProto: "proto",
|
|
},
|
|
skipQUICALPNConfig: true,
|
|
})
|
|
|
|
// If the server name happens to match the public name, it still should
|
|
// not be compressed. It is not publicly known that they match.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-NeverCompressServerName",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
NextProtos: []string{"proto"},
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHUncompressedExtensions: []uint16{extensionServerName},
|
|
ExpectServerName: "public.example",
|
|
ExpectOuterServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
"-host-name", "public.example",
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// If the ClientHelloOuter disables TLS 1.3, e.g. in QUIC, the client
|
|
// should also compress supported_versions.
|
|
tls13Vers := VersionTLS13
|
|
if protocol == dtls {
|
|
tls13Vers = VersionDTLS13
|
|
}
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-CompressSupportedVersions",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectECHOuterExtensions: []uint16{
|
|
extensionSupportedVersions,
|
|
},
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
"-min-version", strconv.Itoa(int(tls13Vers)),
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test that the client can still offer server names that exceed the
|
|
// maximum name length. It is only a padding hint.
|
|
maxNameLen10 := generateServerECHConfig(&ECHConfig{MaxNameLen: 10})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-NameTooLong",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{maxNameLen10},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "test0123456789.example",
|
|
},
|
|
Credential: &echLongNameCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(maxNameLen10.ECHConfig.Raw)),
|
|
"-host-name", "test0123456789.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test the client can recognize when ECH is rejected.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig2, echConfig3},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-retry-configs", base64FlagValue(CreateECHConfigList(echConfig2.ECHConfig.Raw, echConfig3.ECHConfig.Raw)),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-HelloRetryRequest",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig2, echConfig3},
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-retry-configs", base64FlagValue(CreateECHConfigList(echConfig2.ECHConfig.Raw, echConfig3.ECHConfig.Raw)),
|
|
"-expect-hrr", // Check we triggered HRR.
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-NoRetryConfigs",
|
|
config: Config{
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-no-ech-retry-configs",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
if protocol != quic {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-TLS12",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
// TLS 1.2 cannot provide retry configs.
|
|
"-expect-no-ech-retry-configs",
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
|
|
// Test that the client disables False Start when ECH is rejected.
|
|
testCases = append(testCases, testCase{
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-TLS12-NoFalseStart",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
|
|
NextProtos: []string{"foo"},
|
|
Bugs: ProtocolBugs{
|
|
// The options below cause the server to, immediately
|
|
// after client Finished, send an alert and try to read
|
|
// application data without sending server Finished.
|
|
ExpectFalseStart: true,
|
|
AlertBeforeFalseStartTest: alertAccessDenied,
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-false-start",
|
|
"-advertise-alpn", "\x03foo",
|
|
"-expect-alpn", "foo",
|
|
},
|
|
shimWritesFirst: true,
|
|
shouldFail: true,
|
|
// Ensure the client does not send application data at the False
|
|
// Start point. EOF comes from the client closing the connection
|
|
// in response ot the alert.
|
|
expectedLocalError: "tls: peer did not false start: EOF",
|
|
// Ensures the client picks up the alert before reporting an
|
|
// authenticated |SSL_R_ECH_REJECTED|.
|
|
expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
|
|
})
|
|
}
|
|
|
|
// Test that unsupported retry configs in a valid ECHConfigList are
|
|
// allowed. They will be skipped when configured in the retry.
|
|
retryConfigs := CreateECHConfigList(
|
|
unsupportedVersion,
|
|
unsupportedKEM.Raw,
|
|
unsupportedCipherSuites.Raw,
|
|
unsupportedMandatoryExtension.Raw,
|
|
echConfig2.ECHConfig.Raw)
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-UnsupportedRetryConfigs",
|
|
config: Config{
|
|
Bugs: ProtocolBugs{
|
|
SendECHRetryConfigs: retryConfigs,
|
|
ExpectServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-retry-configs", base64FlagValue(retryConfigs),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
|
|
// Test that the client rejects ClientHelloOuter handshakes that attempt
|
|
// to resume the ClientHelloInner's ticket, at TLS 1.2 and TLS 1.3.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-ResumeInnerSession-TLS13",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
resumeConfig: &Config{
|
|
MaxVersion: VersionTLS13,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
UseInnerSessionWithClientHelloOuter: true,
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-on-initial-expect-ech-accept",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
resumeExpectations: &connectionExpectations{echAccepted: false},
|
|
})
|
|
if protocol == tls {
|
|
// This is only syntactically possible with TLS. In DTLS, we don't
|
|
// have middlebox compatibility mode, so the session ID will only
|
|
// filled in if we are offering a DTLS 1.2 session. But a DTLS 1.2
|
|
// would never be offered in ClientHelloInner. Without a session ID,
|
|
// the server syntactically cannot express a resumption at DTLS 1.2.
|
|
// In QUIC, the above is true, and 1.2 does not exist anyway.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-ResumeInnerSession-TLS12",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
resumeConfig: &Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS12,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
UseInnerSessionWithClientHelloOuter: true,
|
|
// The client only ever offers TLS 1.3 sessions in
|
|
// ClientHelloInner. AcceptAnySession allows them to be
|
|
// resumed at TLS 1.2.
|
|
AcceptAnySession: true,
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-on-initial-expect-ech-accept",
|
|
},
|
|
// From the client's perspective, the server echoed a session ID to
|
|
// signal resumption, but the selected ClientHello had nothing to
|
|
// resume.
|
|
shouldFail: true,
|
|
expectedError: ":SERVER_ECHOED_INVALID_SESSION_ID:",
|
|
expectedLocalError: "remote error: illegal parameter",
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
resumeExpectations: &connectionExpectations{echAccepted: false},
|
|
})
|
|
}
|
|
|
|
// Test that the client can process ECH rejects after an early data reject.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-EarlyDataRejected",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
resumeConfig: &Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig2},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
// Although the resumption connection does not accept ECH, the
|
|
// API will report ECH was accepted at the 0-RTT point.
|
|
"-expect-ech-accept",
|
|
// -on-retry refers to the retried handshake after 0-RTT reject,
|
|
// while ech-retry-configs refers to the ECHConfigs to use in
|
|
// the next connection attempt.
|
|
"-on-retry-expect-ech-retry-configs", base64FlagValue(CreateECHConfigList(echConfig2.ECHConfig.Raw)),
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
earlyData: true,
|
|
expectEarlyDataRejected: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
resumeExpectations: &connectionExpectations{echAccepted: false},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: ECH required",
|
|
expectedError: ":ECH_REJECTED:",
|
|
})
|
|
// TODO(crbug.com/381113363): Enable this test for DTLS once we
|
|
// support early data in DTLS 1.3.
|
|
if protocol != quic && protocol != dtls {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-EarlyDataRejected-TLS12",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
resumeConfig: &Config{
|
|
MaxVersion: VersionTLS12,
|
|
Bugs: ProtocolBugs{
|
|
ExpectServerName: "public.example",
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
// Although the resumption connection does not accept ECH, the
|
|
// API will report ECH was accepted at the 0-RTT point.
|
|
"-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
earlyData: true,
|
|
expectEarlyDataRejected: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
resumeExpectations: &connectionExpectations{echAccepted: false},
|
|
// ClientHellos with early data cannot negotiate TLS 1.2, with
|
|
// or without ECH. The shim should first report
|
|
// |SSL_R_WRONG_VERSION_ON_EARLY_DATA|. The caller will then
|
|
// repair the first error by retrying without early data. That
|
|
// will look like ECH-Client-Reject-TLS12 and select TLS 1.2
|
|
// and ClientHelloOuter. The caller will then trigger a third
|
|
// attempt, which will succeed.
|
|
shouldFail: true,
|
|
expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
|
|
})
|
|
}
|
|
|
|
// Test that the client ignores ECHConfigs with invalid public names.
|
|
invalidPublicName := generateServerECHConfig(&ECHConfig{PublicName: "dns_names_have_no_underscores.example"})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-SkipInvalidPublicName",
|
|
config: Config{
|
|
Bugs: ProtocolBugs{
|
|
// No ECHConfigs are supported, so the client should fall
|
|
// back to cleartext.
|
|
ExpectNoClientECH: true,
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(invalidPublicName.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-SkipInvalidPublicName-2",
|
|
config: Config{
|
|
// The client should skip |invalidPublicName| and use |echConfig|.
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectOuterServerName: "public.example",
|
|
ExpectServerName: "secret.example",
|
|
},
|
|
Credential: &echSecretCertificate,
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(invalidPublicName.ECHConfig.Raw, echConfig.ECHConfig.Raw)),
|
|
"-host-name", "secret.example",
|
|
"-expect-ech-accept",
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test both sync and async mode, to test both with and without the
|
|
// client certificate callback.
|
|
for _, async := range []bool{false, true} {
|
|
var flags []string
|
|
var suffix string
|
|
if async {
|
|
flags = []string{"-async"}
|
|
suffix = "-Async"
|
|
}
|
|
|
|
// Test that ECH and client certificates can be used together.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-ClientCertificate" + suffix,
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
ClientAuth: RequireAnyClientCert,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
flags: append([]string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
}, flags...),
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
|
|
// Test that, when ECH is rejected, the client does not send a client
|
|
// certificate.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-NoClientCertificate-TLS13" + suffix,
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
ClientAuth: RequireAnyClientCert,
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
flags: append([]string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
}, flags...),
|
|
shouldFail: true,
|
|
expectedLocalError: "tls: client didn't provide a certificate",
|
|
})
|
|
if protocol != quic {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-NoClientCertificate-TLS12" + suffix,
|
|
config: Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS12,
|
|
ClientAuth: RequireAnyClientCert,
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
flags: append([]string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
}, flags...),
|
|
shouldFail: true,
|
|
expectedLocalError: "tls: client didn't provide a certificate",
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that ECH and Channel ID can be used together. Channel ID does
|
|
// not exist in DTLS.
|
|
if protocol != dtls {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-ChannelID",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
RequestChannelID: true,
|
|
},
|
|
flags: []string{
|
|
"-send-channel-id", channelIDKeyPath,
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
},
|
|
resumeSession: true,
|
|
expectations: connectionExpectations{
|
|
channelID: true,
|
|
echAccepted: true,
|
|
},
|
|
})
|
|
|
|
// Handshakes where ECH is rejected do not offer or accept Channel ID.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-NoChannelID-TLS13",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Bugs: ProtocolBugs{
|
|
AlwaysNegotiateChannelID: true,
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-send-channel-id", channelIDKeyPath,
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
})
|
|
if protocol != quic {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-NoChannelID-TLS12",
|
|
config: Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS12,
|
|
Bugs: ProtocolBugs{
|
|
AlwaysNegotiateChannelID: true,
|
|
},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-send-channel-id", channelIDKeyPath,
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "remote error: unsupported extension",
|
|
expectedError: ":UNEXPECTED_EXTENSION:",
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that ECH correctly overrides the host name for certificate
|
|
// verification.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-NotOffered-NoOverrideName",
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-use-custom-verify-callback",
|
|
// When not offering ECH, verify the usual name in both full
|
|
// and resumption handshakes.
|
|
"-reverify-on-resume",
|
|
"-expect-no-ech-name-override",
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-GREASE-NoOverrideName",
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-use-custom-verify-callback",
|
|
"-enable-ech-grease",
|
|
// When offering ECH GREASE, verify the usual name in both full
|
|
// and resumption handshakes.
|
|
"-reverify-on-resume",
|
|
"-expect-no-ech-name-override",
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
if protocol != quic {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Rejected-OverrideName-TLS12",
|
|
config: Config{
|
|
MinVersion: VersionTLS12,
|
|
MaxVersion: VersionTLS12,
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-use-custom-verify-callback",
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
// When ECH is rejected, verify the public name. This can
|
|
// only happen in full handshakes.
|
|
"-expect-ech-name-override", "public.example",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":ECH_REJECTED:",
|
|
expectedLocalError: "remote error: ECH required",
|
|
})
|
|
}
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-OverrideName-TLS13",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-use-custom-verify-callback",
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
// When ECH is rejected, verify the public name. This can
|
|
// only happen in full handshakes.
|
|
"-expect-ech-name-override", "public.example",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":ECH_REJECTED:",
|
|
expectedLocalError: "remote error: ECH required",
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Accept-NoOverrideName",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-use-custom-verify-callback",
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
// When ECH is accepted, verify the usual name in both full and
|
|
// resumption handshakes.
|
|
"-reverify-on-resume",
|
|
"-expect-no-ech-name-override",
|
|
},
|
|
resumeSession: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
// TODO(crbug.com/381113363): Enable this test for DTLS once we
|
|
// support early data in DTLS 1.3.
|
|
if protocol != dtls {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-Reject-EarlyDataRejected-OverrideNameOnRetry",
|
|
config: Config{
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
resumeConfig: &Config{
|
|
Credential: &echPublicCertificate,
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-use-custom-verify-callback",
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
// Although the resumption connection does not accept ECH, the
|
|
// API will report ECH was accepted at the 0-RTT point.
|
|
"-expect-ech-accept",
|
|
// The resumption connection verifies certificates twice. First,
|
|
// if reverification is enabled, we verify the 0-RTT certificate
|
|
// as if ECH as accepted. There should be no name override.
|
|
// Next, on the post-0-RTT-rejection retry, we verify the new
|
|
// server certificate. This picks up the ECH reject, so it
|
|
// should use public.example.
|
|
"-reverify-on-resume",
|
|
"-on-resume-expect-no-ech-name-override",
|
|
"-on-retry-expect-ech-name-override", "public.example",
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
earlyData: true,
|
|
expectEarlyDataRejected: true,
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
resumeExpectations: &connectionExpectations{echAccepted: false},
|
|
shouldFail: true,
|
|
expectedError: ":ECH_REJECTED:",
|
|
expectedLocalError: "remote error: ECH required",
|
|
})
|
|
}
|
|
|
|
// Test that the client checks both HelloRetryRequest and ServerHello
|
|
// for a confirmation signal.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-HelloRetryRequest-MissingServerHelloConfirmation",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
OmitServerHelloECHConfirmation: true,
|
|
},
|
|
},
|
|
resumeSession: true,
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-hrr", // Check we triggered HRR.
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":INCONSISTENT_ECH_NEGOTIATION:",
|
|
})
|
|
|
|
// Test the message callback is correctly reported, with and without
|
|
// HelloRetryRequest.
|
|
clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n"
|
|
clientAndServerHelloInitial := clientAndServerHello
|
|
if protocol == tls {
|
|
clientAndServerHelloInitial += "write ccs\n"
|
|
}
|
|
// EncryptedExtensions onwards.
|
|
finishHandshake := `read hs 8
|
|
read hs 11
|
|
read hs 15
|
|
read hs 20
|
|
write hs 20
|
|
read ack
|
|
read hs 4
|
|
read hs 4
|
|
`
|
|
if protocol != dtls {
|
|
finishHandshake = strings.ReplaceAll(finishHandshake, "read ack\n", "")
|
|
}
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-MessageCallback",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
NoCloseNotify: true, // Align QUIC and TCP traces.
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
"-expect-msg-callback", clientAndServerHelloInitial + finishHandshake,
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
protocol: protocol,
|
|
name: prefix + "ECH-Client-MessageCallback-HelloRetryRequest",
|
|
config: Config{
|
|
MinVersion: VersionTLS13,
|
|
MaxVersion: VersionTLS13,
|
|
CurvePreferences: []CurveID{CurveP384},
|
|
ServerECHConfigs: []ServerECHConfig{echConfig},
|
|
Bugs: ProtocolBugs{
|
|
ExpectMissingKeyShare: true, // Check we triggered HRR.
|
|
NoCloseNotify: true, // Align QUIC and TCP traces.
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
|
|
"-expect-ech-accept",
|
|
"-expect-hrr", // Check we triggered HRR.
|
|
"-expect-msg-callback", clientAndServerHelloInitial + clientAndServerHello + finishHandshake,
|
|
},
|
|
expectations: connectionExpectations{echAccepted: true},
|
|
})
|
|
}
|
|
}
|