Split runner.go into a bunch of different files
Now runner.go contains only the test runner, while the various test
suites are moved into their own files, named foo_tests.go. (foo_test.go
would be treated as a Go test.)
I broadly just split by the addFooTests functions, but in a few cases I
grouped them together.
Now we no longer have a single 24,000 line file with all the tests. That
was getting unwieldy.
Change-Id: I76f372f60f5f0de5f1ba0913317918a4053372a3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/77107
Reviewed-by: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/ssl/test/runner/basic_tests.go b/ssl/test/runner/basic_tests.go
new file mode 100644
index 0000000..08de8fa
--- /dev/null
+++ b/ssl/test/runner/basic_tests.go
@@ -0,0 +1,1971 @@
+// 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 "strconv"
+
+func addBasicTests() {
+ basicTests := []testCase{
+ {
+ name: "NoFallbackSCSV",
+ config: Config{
+ Bugs: ProtocolBugs{
+ FailIfNotFallbackSCSV: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "no fallback SCSV found",
+ },
+ {
+ name: "SendFallbackSCSV",
+ config: Config{
+ Bugs: ProtocolBugs{
+ FailIfNotFallbackSCSV: true,
+ },
+ },
+ flags: []string{"-fallback-scsv"},
+ },
+ {
+ name: "ClientCertificateTypes",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequestClientCert,
+ ClientCertificateTypes: []byte{
+ CertTypeDSSSign,
+ CertTypeRSASign,
+ CertTypeECDSASign,
+ },
+ },
+ flags: []string{
+ "-expect-certificate-types",
+ base64FlagValue([]byte{
+ CertTypeDSSSign,
+ CertTypeRSASign,
+ CertTypeECDSASign,
+ }),
+ },
+ },
+ {
+ name: "CheckClientCertificateTypes",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequestClientCert,
+ ClientCertificateTypes: []byte{CertTypeECDSASign},
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+ },
+ {
+ name: "UnauthenticatedECDH",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ UnauthenticatedECDH: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ },
+ {
+ name: "SkipCertificateStatus",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SkipCertificateStatus: true,
+ },
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ // This test involves an optional message. Test the message callback
+ // trace to ensure we do not miss or double-report any.
+ "-expect-msg-callback",
+ `write hs 1
+read hs 2
+read hs 11
+read hs 12
+read hs 14
+write hs 16
+write ccs
+write hs 20
+read hs 4
+read ccs
+read hs 20
+read alert 1 0
+`,
+ },
+ },
+ {
+ protocol: dtls,
+ name: "SkipCertificateStatus-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SkipCertificateStatus: true,
+ },
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ // This test involves an optional message. Test the message callback
+ // trace to ensure we do not miss or double-report any.
+ "-expect-msg-callback",
+ `write hs 1
+read hs 3
+write hs 1
+read hs 2
+read hs 11
+read hs 12
+read hs 14
+write hs 16
+write ccs
+write hs 20
+read hs 4
+read ccs
+read hs 20
+read alert 1 0
+`,
+ },
+ },
+ {
+ name: "SkipServerKeyExchange",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SkipServerKeyExchange: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ },
+ {
+ testType: serverTest,
+ name: "ServerSkipCertificateVerify",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ SkipCertificateVerify: true,
+ },
+ },
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ flags: []string{
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ testType: serverTest,
+ name: "Alert",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "Alert-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
+ },
+ {
+ testType: serverTest,
+ name: "FragmentAlert",
+ config: Config{
+ Bugs: ProtocolBugs{
+ FragmentAlert: true,
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_ALERT:",
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "FragmentAlert-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ FragmentAlert: true,
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_ALERT:",
+ },
+ {
+ testType: serverTest,
+ name: "DoubleAlert",
+ config: Config{
+ Bugs: ProtocolBugs{
+ DoubleAlert: true,
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_ALERT:",
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "DoubleAlert-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ DoubleAlert: true,
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_ALERT:",
+ },
+ {
+ name: "SkipNewSessionTicket",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SkipNewSessionTicket: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ testType: serverTest,
+ name: "FallbackSCSV",
+ config: Config{
+ MaxVersion: VersionTLS11,
+ Bugs: ProtocolBugs{
+ SendFallbackSCSV: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INAPPROPRIATE_FALLBACK:",
+ expectedLocalError: "remote error: inappropriate fallback",
+ },
+ {
+ testType: serverTest,
+ name: "FallbackSCSV-VersionMatch-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFallbackSCSV: true,
+ },
+ },
+ },
+ {
+ testType: serverTest,
+ name: "FallbackSCSV-VersionMatch-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendFallbackSCSV: true,
+ },
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ },
+ // Regression test for CVE-2014-3511. Even when the ClientHello is
+ // maximally fragmented, version negotiation works correctly.
+ {
+ testType: serverTest,
+ name: "FragmentedClientVersion",
+ config: Config{
+ Bugs: ProtocolBugs{
+ MaxHandshakeRecordLength: 1,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS13,
+ },
+ },
+ {
+ testType: serverTest,
+ name: "HttpGET",
+ sendPrefix: "GET / HTTP/1.0\n",
+ shouldFail: true,
+ expectedError: ":HTTP_REQUEST:",
+ },
+ {
+ testType: serverTest,
+ name: "HttpPOST",
+ sendPrefix: "POST / HTTP/1.0\n",
+ shouldFail: true,
+ expectedError: ":HTTP_REQUEST:",
+ },
+ {
+ testType: serverTest,
+ name: "HttpHEAD",
+ sendPrefix: "HEAD / HTTP/1.0\n",
+ shouldFail: true,
+ expectedError: ":HTTP_REQUEST:",
+ },
+ {
+ testType: serverTest,
+ name: "HttpPUT",
+ sendPrefix: "PUT / HTTP/1.0\n",
+ shouldFail: true,
+ expectedError: ":HTTP_REQUEST:",
+ },
+ {
+ testType: serverTest,
+ name: "HttpCONNECT",
+ sendPrefix: "CONNECT www.google.com:443 HTTP/1.0\n",
+ shouldFail: true,
+ expectedError: ":HTTPS_PROXY_REQUEST:",
+ },
+ {
+ testType: serverTest,
+ name: "Garbage",
+ sendPrefix: "blah",
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_NUMBER:",
+ },
+ {
+ name: "RSAEphemeralKey",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ RSAEphemeralKey: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ },
+ {
+ name: "DisableEverything",
+ flags: []string{"-no-tls13", "-no-tls12", "-no-tls11", "-no-tls1"},
+ shouldFail: true,
+ expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
+ },
+ {
+ protocol: dtls,
+ name: "DisableEverything-DTLS",
+ flags: []string{"-no-tls13", "-no-tls12", "-no-tls1"},
+ shouldFail: true,
+ expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "MTU-DTLS12-AEAD",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 256,
+ },
+ },
+ flags: []string{"-mtu", "256"},
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "MTU-DTLS12-AES-CBC",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 256,
+ },
+ },
+ flags: []string{"-mtu", "256"},
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "MTU-DTLS12-3DES-CBC",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 256,
+ },
+ },
+ flags: []string{"-mtu", "256", "-cipher", "TLS_RSA_WITH_3DES_EDE_CBC_SHA"},
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "MTU-DTLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 256,
+ },
+ },
+ flags: []string{"-mtu", "256"},
+ },
+ {
+ name: "EmptyCertificateList",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ EmptyCertificateList: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ },
+ {
+ name: "EmptyCertificateList-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ EmptyCertificateList: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
+ },
+ {
+ name: "TLSFatalBadPackets",
+ damageFirstWrite: true,
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ },
+ {
+ protocol: dtls,
+ name: "DTLSIgnoreBadPackets",
+ damageFirstWrite: true,
+ },
+ {
+ protocol: dtls,
+ name: "DTLSIgnoreBadPackets-Async",
+ damageFirstWrite: true,
+ flags: []string{"-async"},
+ },
+ {
+ name: "AppDataBeforeHandshake",
+ config: Config{
+ Bugs: ProtocolBugs{
+ AppDataBeforeHandshake: []byte("TEST MESSAGE"),
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ name: "AppDataBeforeHandshake-Empty",
+ config: Config{
+ Bugs: ProtocolBugs{
+ AppDataBeforeHandshake: []byte{},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "AppDataBeforeHandshake-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ AppDataBeforeHandshake: []byte("TEST MESSAGE"),
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "AppDataBeforeHandshake-DTLS-Empty",
+ config: Config{
+ Bugs: ProtocolBugs{
+ AppDataBeforeHandshake: []byte{},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ name: "AppDataBeforeTLS13KeyChange",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AppDataBeforeTLS13KeyChange: []byte("TEST MESSAGE"),
+ },
+ },
+ // The shim should fail to decrypt this record.
+ shouldFail: true,
+ expectedError: ":BAD_DECRYPT:",
+ expectedLocalError: "remote error: bad record MAC",
+ },
+ {
+ name: "AppDataBeforeTLS13KeyChange-Empty",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AppDataBeforeTLS13KeyChange: []byte{},
+ },
+ },
+ // The shim should fail to decrypt this record.
+ shouldFail: true,
+ expectedError: ":BAD_DECRYPT:",
+ expectedLocalError: "remote error: bad record MAC",
+ },
+ {
+ protocol: dtls,
+ name: "AppDataBeforeTLS13KeyChange-DTLS",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AppDataBeforeTLS13KeyChange: []byte("TEST MESSAGE"),
+ },
+ },
+ // The shim will decrypt the record, because it has not
+ // yet applied the key change, but it should know to
+ // reject the record.
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ name: "AppDataBeforeTLS13KeyChange-DTLS-Empty",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AppDataBeforeTLS13KeyChange: []byte{},
+ },
+ },
+ // The shim will decrypt the record, because it has not
+ // yet applied the key change, but it should know to
+ // reject the record.
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ name: "UnencryptedEncryptedExtensions",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ UnencryptedEncryptedExtensions: true,
+ },
+ },
+ // The shim should fail to decrypt this record.
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ expectedLocalError: "remote error: bad record MAC",
+ },
+ {
+ protocol: dtls,
+ name: "UnencryptedEncryptedExtensions-DTLS",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ UnencryptedEncryptedExtensions: true,
+ },
+ },
+ // The shim will decrypt the record, because it has not
+ // yet applied the key change, but it should know to
+ // reject new handshake data on the previous epoch.
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ name: "AppDataAfterChangeCipherSpec",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ AppDataAfterChangeCipherSpec: []byte("TEST MESSAGE"),
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ name: "AppDataAfterChangeCipherSpec-Empty",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ AppDataAfterChangeCipherSpec: []byte{},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "AppDataAfterChangeCipherSpec-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ AppDataAfterChangeCipherSpec: []byte("TEST MESSAGE"),
+ },
+ },
+ // BoringSSL's DTLS implementation will drop the out-of-order
+ // application data.
+ },
+ {
+ protocol: dtls,
+ name: "AppDataAfterChangeCipherSpec-DTLS-Empty",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ AppDataAfterChangeCipherSpec: []byte{},
+ },
+ },
+ // BoringSSL's DTLS implementation will drop the out-of-order
+ // application data.
+ },
+ {
+ name: "AlertAfterChangeCipherSpec",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ AlertAfterChangeCipherSpec: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
+ },
+ {
+ protocol: dtls,
+ name: "AlertAfterChangeCipherSpec-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ AlertAfterChangeCipherSpec: alertRecordOverflow,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
+ },
+ {
+ name: "SendInvalidRecordType",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendInvalidRecordType: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "SendInvalidRecordType-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendInvalidRecordType: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ name: "FalseStart-SkipServerSecondLeg",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ SkipNewSessionTicket: true,
+ SkipChangeCipherSpec: true,
+ SkipFinished: true,
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-handshake-never-done",
+ "-advertise-alpn", "\x03foo",
+ "-expect-alpn", "foo",
+ },
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ name: "FalseStart-SkipServerSecondLeg-Implicit",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ SkipNewSessionTicket: true,
+ SkipChangeCipherSpec: true,
+ SkipFinished: true,
+ },
+ },
+ flags: []string{
+ "-implicit-handshake",
+ "-false-start",
+ "-handshake-never-done",
+ "-advertise-alpn", "\x03foo",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ },
+ {
+ testType: serverTest,
+ name: "FailEarlyCallback",
+ flags: []string{"-fail-early-callback"},
+ shouldFail: true,
+ expectedError: ":CONNECTION_REJECTED:",
+ expectedLocalError: "remote error: handshake failure",
+ },
+ {
+ name: "FailCertCallback-Client-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequestClientCert,
+ },
+ flags: []string{"-fail-cert-callback"},
+ shouldFail: true,
+ expectedError: ":CERT_CB_ERROR:",
+ expectedLocalError: "remote error: internal error",
+ },
+ {
+ testType: serverTest,
+ name: "FailCertCallback-Server-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{"-fail-cert-callback"},
+ shouldFail: true,
+ expectedError: ":CERT_CB_ERROR:",
+ expectedLocalError: "remote error: internal error",
+ },
+ {
+ name: "FailCertCallback-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequestClientCert,
+ },
+ flags: []string{"-fail-cert-callback"},
+ shouldFail: true,
+ expectedError: ":CERT_CB_ERROR:",
+ expectedLocalError: "remote error: internal error",
+ },
+ {
+ testType: serverTest,
+ name: "FailCertCallback-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{"-fail-cert-callback"},
+ shouldFail: true,
+ expectedError: ":CERT_CB_ERROR:",
+ expectedLocalError: "remote error: internal error",
+ },
+ {
+ protocol: dtls,
+ name: "FragmentMessageTypeMismatch-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ f1 := next[0].Fragment(0, 1)
+ f2 := next[0].Fragment(1, 1)
+ f2.Type++
+ c.WriteFragments([]DTLSFragment{f1, f2})
+ },
+ },
+ },
+ shouldFail: true,
+ expectedError: ":FRAGMENT_MISMATCH:",
+ },
+ {
+ protocol: dtls,
+ name: "FragmentMessageLengthMismatch-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ f1 := next[0].Fragment(0, 1)
+ f2 := next[0].Fragment(1, 1)
+ f2.TotalLength++
+ c.WriteFragments([]DTLSFragment{f1, f2})
+ },
+ },
+ },
+ shouldFail: true,
+ expectedError: ":FRAGMENT_MISMATCH:",
+ },
+ {
+ protocol: dtls,
+ name: "SplitFragments-Header-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SplitFragments: 2,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_HANDSHAKE_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "SplitFragments-Boundary-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SplitFragments: dtlsMaxRecordHeaderLen,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_HANDSHAKE_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "SplitFragments-Body-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SplitFragments: dtlsMaxRecordHeaderLen + 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_HANDSHAKE_RECORD:",
+ },
+ {
+ protocol: dtls,
+ name: "SendEmptyFragments-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendEmptyFragments: true,
+ },
+ },
+ },
+ {
+ testType: serverTest,
+ protocol: dtls,
+ name: "SendEmptyFragments-Padded-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ // Test empty fragments for a message with a
+ // nice power-of-two length.
+ PadClientHello: 64,
+ SendEmptyFragments: true,
+ },
+ },
+ },
+ {
+ name: "BadFinished-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ },
+ {
+ name: "BadFinished-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ },
+ {
+ testType: serverTest,
+ name: "BadFinished-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ },
+ {
+ testType: serverTest,
+ name: "BadFinished-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ },
+ {
+ name: "FalseStart-BadFinished",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-handshake-never-done",
+ "-advertise-alpn", "\x03foo",
+ "-expect-alpn", "foo",
+ },
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ },
+ {
+ name: "NoFalseStart-NoALPN",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ AlertBeforeFalseStartTest: alertAccessDenied,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ },
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
+ expectedLocalError: "tls: peer did not false start: EOF",
+ },
+ {
+ name: "FalseStart-NoALPNAllowed",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-allow-false-start-without-alpn",
+ },
+ shimWritesFirst: true,
+ },
+ {
+ name: "NoFalseStart-NoAEAD",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ AlertBeforeFalseStartTest: alertAccessDenied,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-advertise-alpn", "\x03foo",
+ },
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
+ expectedLocalError: "tls: peer did not false start: EOF",
+ },
+ {
+ name: "NoFalseStart-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ AlertBeforeFalseStartTest: alertAccessDenied,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-advertise-alpn", "\x03foo",
+ },
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
+ expectedLocalError: "tls: peer did not false start: EOF",
+ },
+ {
+ protocol: dtls,
+ name: "SendSplitAlert-Sync",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSplitAlert: true,
+ },
+ },
+ },
+ {
+ protocol: dtls,
+ name: "SendSplitAlert-Async",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSplitAlert: true,
+ },
+ },
+ flags: []string{"-async"},
+ },
+ {
+ name: "SendEmptyRecords-Pass",
+ sendEmptyRecords: 32,
+ },
+ {
+ name: "SendEmptyRecords",
+ sendEmptyRecords: 33,
+ shouldFail: true,
+ expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
+ },
+ {
+ name: "SendEmptyRecords-Async",
+ sendEmptyRecords: 33,
+ flags: []string{"-async"},
+ shouldFail: true,
+ expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
+ },
+ {
+ name: "SendWarningAlerts-Pass",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ sendWarningAlerts: 4,
+ },
+ {
+ protocol: dtls,
+ name: "SendWarningAlerts-DTLS-Pass",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ sendWarningAlerts: 4,
+ },
+ {
+ name: "SendWarningAlerts-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendWarningAlerts: 4,
+ shouldFail: true,
+ expectedError: ":BAD_ALERT:",
+ expectedLocalError: "remote error: error decoding message",
+ },
+ // Although TLS 1.3 intended to remove warning alerts, it left in
+ // user_canceled. JDK11 misuses this alert as a post-handshake
+ // full-duplex signal. As a workaround, skip user_canceled as in
+ // TLS 1.2, which is consistent with NSS and OpenSSL.
+ {
+ name: "SendUserCanceledAlerts-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendUserCanceledAlerts: 4,
+ },
+ {
+ name: "SendUserCanceledAlerts-TooMany-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendUserCanceledAlerts: 5,
+ shouldFail: true,
+ expectedError: ":TOO_MANY_WARNING_ALERTS:",
+ },
+ {
+ name: "SendWarningAlerts-TooMany",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ sendWarningAlerts: 5,
+ shouldFail: true,
+ expectedError: ":TOO_MANY_WARNING_ALERTS:",
+ },
+ {
+ name: "SendWarningAlerts-TooMany-Async",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ sendWarningAlerts: 5,
+ flags: []string{"-async"},
+ shouldFail: true,
+ expectedError: ":TOO_MANY_WARNING_ALERTS:",
+ },
+ {
+ name: "SendBogusAlertType",
+ sendBogusAlertType: true,
+ shouldFail: true,
+ expectedError: ":UNKNOWN_ALERT_TYPE:",
+ expectedLocalError: "remote error: illegal parameter",
+ },
+ {
+ protocol: dtls,
+ name: "SendBogusAlertType-DTLS",
+ sendBogusAlertType: true,
+ shouldFail: true,
+ expectedError: ":UNKNOWN_ALERT_TYPE:",
+ expectedLocalError: "remote error: illegal parameter",
+ },
+ {
+ name: "TooManyKeyUpdates",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendKeyUpdates: 33,
+ keyUpdateRequest: keyUpdateNotRequested,
+ shouldFail: true,
+ expectedError: ":TOO_MANY_KEY_UPDATES:",
+ },
+ {
+ name: "EmptySessionID",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ noSessionCache: true,
+ flags: []string{"-expect-no-session"},
+ },
+ {
+ name: "Unclean-Shutdown",
+ config: Config{
+ Bugs: ProtocolBugs{
+ NoCloseNotify: true,
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ flags: []string{"-check-close-notify"},
+ shouldFail: true,
+ expectedError: "Unexpected SSL_shutdown result: -1 != 1",
+ },
+ {
+ name: "Unclean-Shutdown-Ignored",
+ config: Config{
+ Bugs: ProtocolBugs{
+ NoCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ },
+ {
+ name: "Unclean-Shutdown-Alert",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendAlertOnShutdown: alertDecompressionFailure,
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ flags: []string{"-check-close-notify"},
+ shouldFail: true,
+ expectedError: ":SSLV3_ALERT_DECOMPRESSION_FAILURE:",
+ },
+ {
+ name: "LargePlaintext",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: maxPlaintext + 1,
+ shouldFail: true,
+ expectedError: ":DATA_LENGTH_TOO_LONG:",
+ expectedLocalError: "remote error: record overflow",
+ },
+ {
+ protocol: dtls,
+ name: "LargePlaintext-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: maxPlaintext + 1,
+ shouldFail: true,
+ expectedError: ":DATA_LENGTH_TOO_LONG:",
+ expectedLocalError: "remote error: record overflow",
+ },
+ {
+ name: "LargePlaintext-TLS13-Padded-8192-8192",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RecordPadding: 8192,
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: 8192,
+ },
+ {
+ name: "LargePlaintext-TLS13-Padded-8193-8192",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RecordPadding: 8193,
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: 8192,
+ shouldFail: true,
+ expectedError: ":DATA_LENGTH_TOO_LONG:",
+ expectedLocalError: "remote error: record overflow",
+ },
+ {
+ name: "LargePlaintext-TLS13-Padded-16383-1",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RecordPadding: 1,
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: 16383,
+ },
+ {
+ name: "LargePlaintext-TLS13-Padded-16384-1",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RecordPadding: 1,
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: 16384,
+ shouldFail: true,
+ expectedError: ":DATA_LENGTH_TOO_LONG:",
+ expectedLocalError: "remote error: record overflow",
+ },
+ {
+ name: "LargeCiphertext",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: maxPlaintext * 2,
+ shouldFail: true,
+ expectedError: ":ENCRYPTED_LENGTH_TOO_LONG:",
+ },
+ {
+ protocol: dtls,
+ name: "LargeCiphertext-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendLargeRecords: true,
+ },
+ },
+ messageLen: maxPlaintext * 2,
+ // Unlike the other four cases, DTLS drops records which
+ // are invalid before authentication, so the connection
+ // does not fail.
+ expectMessageDropped: true,
+ },
+ {
+ name: "BadHelloRequest-1",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadHelloRequest: []byte{typeHelloRequest, 0, 0, 1, 1},
+ },
+ },
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shouldFail: true,
+ expectedError: ":BAD_HELLO_REQUEST:",
+ },
+ {
+ name: "BadHelloRequest-2",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadHelloRequest: []byte{typeServerKeyExchange, 0, 0, 0},
+ },
+ },
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shouldFail: true,
+ expectedError: ":BAD_HELLO_REQUEST:",
+ },
+ {
+ testType: serverTest,
+ name: "SupportTicketsWithSessionID",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ },
+ {
+ protocol: dtls,
+ name: "DTLS12-SendExtraFinished",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendExtraFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ name: "DTLS12-SendExtraFinished-Reordered",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ MaxHandshakeRecordLength: 2,
+ ReorderHandshakeFragments: true,
+ SendExtraFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ name: "DTLS12-SendExtraFinished-Packed",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendExtraFinished: true,
+ PackHandshakeFragments: 1000,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ name: "DTLS13-SendExtraFinished",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtraFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ name: "DTLS13-SendExtraFinished-Reordered",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ MaxHandshakeRecordLength: 2,
+ ReorderHandshakeFragments: true,
+ SendExtraFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ name: "DTLS13-SendExtraFinished-Packed",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtraFinished: true,
+ PackHandshakeFragments: 1000,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ },
+ {
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS13-SendExtraFinished-AfterAppData",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SkipImplicitACKRead: true,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[len(next)-1].Type != typeFinished {
+ c.WriteFlight(next)
+ return
+ }
+
+ // Complete the handshake.
+ c.WriteFlight(next)
+ c.ReadACK(c.InEpoch())
+
+ // Send some application data. The shim is now on epoch 3.
+ msg := []byte("hello")
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // The shim is still accepting data from epoch 2, so it can
+ // ACK a retransmit if needed, but it should not accept new
+ // messages at epoch three.
+ extraFinished := next[len(next)-1]
+ extraFinished.Sequence++
+ c.WriteFlight([]DTLSMessage{extraFinished})
+ },
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ // Disable tickets on the shim to avoid NewSessionTicket
+ // interfering with the test callback.
+ flags: []string{"-no-ticket"},
+ },
+ {
+ testType: serverTest,
+ name: "V2ClientHello-EmptyRecordPrefix",
+ config: Config{
+ // Choose a cipher suite that does not involve
+ // elliptic curves, so no extensions are
+ // involved.
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ SendV2ClientHello: true,
+ },
+ },
+ sendPrefix: string([]byte{
+ byte(recordTypeHandshake),
+ 3, 1, // version
+ 0, 0, // length
+ }),
+ // A no-op empty record may not be sent before V2ClientHello.
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_NUMBER:",
+ },
+ {
+ testType: serverTest,
+ name: "V2ClientHello-WarningAlertPrefix",
+ config: Config{
+ // Choose a cipher suite that does not involve
+ // elliptic curves, so no extensions are
+ // involved.
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ SendV2ClientHello: true,
+ },
+ },
+ sendPrefix: string([]byte{
+ byte(recordTypeAlert),
+ 3, 1, // version
+ 0, 2, // length
+ alertLevelWarning, byte(alertDecompressionFailure),
+ }),
+ // A no-op warning alert may not be sent before V2ClientHello.
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_NUMBER:",
+ },
+ {
+ name: "SendSNIWarningAlert",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendSNIWarningAlert: true,
+ },
+ },
+ },
+ {
+ testType: serverTest,
+ name: "ExtraCompressionMethods-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendCompressionMethods: []byte{1, 2, 3, compressionNone, 4, 5, 6},
+ },
+ },
+ },
+ {
+ testType: serverTest,
+ name: "ExtraCompressionMethods-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendCompressionMethods: []byte{1, 2, 3, compressionNone, 4, 5, 6},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_COMPRESSION_LIST:",
+ expectedLocalError: "remote error: illegal parameter",
+ },
+ {
+ testType: serverTest,
+ name: "NoNullCompression-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendCompressionMethods: []byte{1, 2, 3, 4, 5, 6},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_COMPRESSION_LIST:",
+ expectedLocalError: "remote error: illegal parameter",
+ },
+ {
+ testType: serverTest,
+ name: "NoNullCompression-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendCompressionMethods: []byte{1, 2, 3, 4, 5, 6},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_COMPRESSION_LIST:",
+ expectedLocalError: "remote error: illegal parameter",
+ },
+ // Test that the client rejects invalid compression methods
+ // from the server.
+ {
+ testType: clientTest,
+ name: "InvalidCompressionMethod",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendCompressionMethod: 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_COMPRESSION_ALGORITHM:",
+ expectedLocalError: "remote error: illegal parameter",
+ },
+ {
+ testType: clientTest,
+ name: "TLS13-InvalidCompressionMethod",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendCompressionMethod: 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ },
+ {
+ testType: clientTest,
+ name: "TLS13-HRR-InvalidCompressionMethod",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendCompressionMethod: 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: error decoding message",
+ },
+ {
+ name: "GREASE-Client-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectGREASE: true,
+ },
+ },
+ flags: []string{"-enable-grease"},
+ },
+ {
+ name: "GREASE-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectGREASE: true,
+ },
+ },
+ flags: []string{"-enable-grease"},
+ },
+ {
+ testType: serverTest,
+ name: "GREASE-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // TLS 1.3 servers are expected to
+ // always enable GREASE. TLS 1.3 is new,
+ // so there is no existing ecosystem to
+ // worry about.
+ ExpectGREASE: true,
+ },
+ },
+ },
+ {
+ // Test the TLS 1.2 server so there is a large
+ // unencrypted certificate as well as application data.
+ testType: serverTest,
+ name: "MaxSendFragment-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ MaxReceivePlaintext: 512,
+ },
+ },
+ messageLen: 1024,
+ flags: []string{
+ "-max-send-fragment", "512",
+ "-read-size", "1024",
+ },
+ },
+ {
+ // Test the TLS 1.2 server so there is a large
+ // unencrypted certificate as well as application data.
+ testType: serverTest,
+ name: "MaxSendFragment-TLS12-TooLarge",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ // Ensure that some of the records are
+ // 512.
+ MaxReceivePlaintext: 511,
+ },
+ },
+ messageLen: 1024,
+ flags: []string{
+ "-max-send-fragment", "512",
+ "-read-size", "1024",
+ },
+ shouldFail: true,
+ expectedLocalError: "local error: record overflow",
+ },
+ {
+ // Test the TLS 1.3 server so there is a large encrypted
+ // certificate as well as application data.
+ testType: serverTest,
+ name: "MaxSendFragment-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ MaxReceivePlaintext: 512,
+ ExpectPackedEncryptedHandshake: 512,
+ },
+ },
+ messageLen: 1024,
+ flags: []string{
+ "-max-send-fragment", "512",
+ "-read-size", "1024",
+ },
+ },
+ {
+ // Test the TLS 1.3 server so there is a large encrypted
+ // certificate as well as application data.
+ testType: serverTest,
+ name: "MaxSendFragment-TLS13-TooLarge",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Ensure that some of the records are
+ // 512.
+ MaxReceivePlaintext: 511,
+ },
+ },
+ messageLen: 1024,
+ flags: []string{
+ "-max-send-fragment", "512",
+ "-read-size", "1024",
+ },
+ shouldFail: true,
+ expectedLocalError: "local error: record overflow",
+ },
+ {
+ // Test that handshake data is tightly packed in TLS 1.3.
+ testType: serverTest,
+ name: "PackedEncryptedHandshake-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectPackedEncryptedHandshake: 16384,
+ },
+ },
+ },
+ {
+ // Test that DTLS can handle multiple application data
+ // records in a single packet.
+ protocol: dtls,
+ name: "SplitAndPackAppData-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SplitAndPackAppData: true,
+ },
+ },
+ },
+ {
+ protocol: dtls,
+ name: "SplitAndPackAppData-DTLS-Async",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SplitAndPackAppData: true,
+ },
+ },
+ flags: []string{"-async"},
+ },
+ {
+ // DTLS 1.2 allows up to a 255-byte HelloVerifyRequest cookie, which
+ // is the largest encodable value.
+ protocol: dtls,
+ name: "DTLS-HelloVerifyRequest-255",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ HelloVerifyRequestCookieLength: 255,
+ },
+ },
+ },
+ {
+ // DTLS 1.2 allows up to a 0-byte HelloVerifyRequest cookie, which
+ // was probably a mistake in the spec but test that it works
+ // nonetheless.
+ protocol: dtls,
+ name: "DTLS-HelloVerifyRequest-0",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ EmptyHelloVerifyRequestCookie: true,
+ },
+ },
+ },
+ }
+ testCases = append(testCases, basicTests...)
+
+ // Test that very large messages can be received.
+ cert := rsaCertificate
+ for i := 0; i < 50; i++ {
+ cert.Certificate = append(cert.Certificate, cert.Certificate[0])
+ }
+ testCases = append(testCases, testCase{
+ name: "LargeMessage",
+ config: Config{
+ Credential: &cert,
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "LargeMessage-DTLS",
+ config: Config{
+ Credential: &cert,
+ },
+ })
+
+ // They are rejected if the maximum certificate chain length is capped.
+ testCases = append(testCases, testCase{
+ name: "LargeMessage-Reject",
+ config: Config{
+ Credential: &cert,
+ },
+ flags: []string{"-max-cert-list", "16384"},
+ shouldFail: true,
+ expectedError: ":EXCESSIVE_MESSAGE_SIZE:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "LargeMessage-Reject-DTLS",
+ config: Config{
+ Credential: &cert,
+ },
+ flags: []string{"-max-cert-list", "16384"},
+ shouldFail: true,
+ expectedError: ":EXCESSIVE_MESSAGE_SIZE:",
+ })
+
+ // Servers echoing the TLS 1.3 compatibility mode session ID should be
+ // rejected.
+ testCases = append(testCases, testCase{
+ name: "EchoTLS13CompatibilitySessionID",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ EchoSessionIDInFullHandshake: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":SERVER_ECHOED_INVALID_SESSION_ID:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ // Servers should reject QUIC client hellos that have a legacy
+ // session ID.
+ testCases = append(testCases, testCase{
+ name: "QUICCompatibilityMode",
+ testType: serverTest,
+ protocol: quic,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ CompatModeWithQUIC: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_COMPATIBILITY_MODE:",
+ })
+
+ // Clients should reject DTLS 1.3 ServerHellos that echo the legacy
+ // session ID.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS13CompatibilityMode-EchoSessionID",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeConfig: &Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ DTLS13EchoSessionID: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ // DTLS 1.3 should work with record headers that don't set the
+ // length bit or that use the short sequence number format.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: dtls,
+ name: "DTLS13RecordHeader-NoLength-Client",
+ config: Config{
+ MinVersion: VersionTLS13,
+ DTLSRecordHeaderOmitLength: true,
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "DTLS13RecordHeader-NoLength-Server",
+ config: Config{
+ MinVersion: VersionTLS13,
+ DTLSRecordHeaderOmitLength: true,
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: dtls,
+ name: "DTLS13RecordHeader-ShortSeqNums-Client",
+ config: Config{
+ MinVersion: VersionTLS13,
+ DTLSUseShortSeqNums: true,
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "DTLS13RecordHeader-ShortSeqNums-Server",
+ config: Config{
+ MinVersion: VersionTLS13,
+ DTLSUseShortSeqNums: true,
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS13RecordHeader-OldHeader",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ DTLSUsePlaintextRecordHeader: true,
+ },
+ },
+ expectMessageDropped: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS13RecordHeader-CIDBit",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ DTLS13RecordHeaderSetCIDBit: true,
+ },
+ },
+ expectMessageDropped: true,
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS13-MessageCallback-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-expect-msg-callback",
+ `write hs 1
+read hs 2
+read hs 8
+read hs 11
+read hs 15
+read hs 20
+write hs 20
+read ack
+read hs 4
+read hs 4
+read alert 1 0
+`,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "DTLS13-MessageCallback-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-expect-msg-callback",
+ `read hs 1
+write hs 2
+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
+read alert 1 0
+`,
+ },
+ })
+}
diff --git a/ssl/test/runner/cbc_tests.go b/ssl/test/runner/cbc_tests.go
new file mode 100644
index 0000000..6f49d12
--- /dev/null
+++ b/ssl/test/runner/cbc_tests.go
@@ -0,0 +1,110 @@
+// 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
+
+func addCBCPaddingTests() {
+ testCases = append(testCases, testCase{
+ name: "MaxCBCPadding",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ MaxPadding: true,
+ },
+ },
+ messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
+ })
+ testCases = append(testCases, testCase{
+ name: "BadCBCPadding",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ PaddingFirstByteBad: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+ // OpenSSL previously had an issue where the first byte of padding in
+ // 255 bytes of padding wasn't checked.
+ testCases = append(testCases, testCase{
+ name: "BadCBCPadding255",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ MaxPadding: true,
+ PaddingFirstByteBadIf255: true,
+ },
+ },
+ messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+}
+
+func addCBCSplittingTests() {
+ cbcCiphers := []struct {
+ name string
+ cipher uint16
+ }{
+ {"3DES", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+ {"AES128", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ {"AES256", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
+ }
+ for _, t := range cbcCiphers {
+ testCases = append(testCases, testCase{
+ name: "CBCRecordSplitting-" + t.name,
+ config: Config{
+ MaxVersion: VersionTLS10,
+ MinVersion: VersionTLS10,
+ CipherSuites: []uint16{t.cipher},
+ Bugs: ProtocolBugs{
+ ExpectRecordSplitting: true,
+ },
+ },
+ messageLen: -1, // read until EOF
+ resumeSession: true,
+ flags: []string{
+ "-async",
+ "-write-different-record-sizes",
+ "-cbc-record-splitting",
+ // BoringSSL disables 3DES by default.
+ "-cipher", "ALL:3DES",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "CBCRecordSplittingPartialWrite-" + t.name,
+ config: Config{
+ MaxVersion: VersionTLS10,
+ MinVersion: VersionTLS10,
+ CipherSuites: []uint16{t.cipher},
+ Bugs: ProtocolBugs{
+ ExpectRecordSplitting: true,
+ },
+ },
+ messageLen: -1, // read until EOF
+ flags: []string{
+ "-async",
+ "-write-different-record-sizes",
+ "-cbc-record-splitting",
+ "-partial-write",
+ // BoringSSL disables 3DES by default.
+ "-cipher", "ALL:3DES",
+ },
+ })
+ }
+}
diff --git a/ssl/test/runner/cert_compression_tests.go b/ssl/test/runner/cert_compression_tests.go
new file mode 100644
index 0000000..e74dedc
--- /dev/null
+++ b/ssl/test/runner/cert_compression_tests.go
@@ -0,0 +1,323 @@
+// 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 (
+ "bytes"
+ "crypto/rand"
+ "fmt"
+ "strconv"
+)
+
+const (
+ shrinkingCompressionAlgID = 0xff01
+ expandingCompressionAlgID = 0xff02
+ randomCompressionAlgID = 0xff03
+)
+
+var (
+ // shrinkingPrefix is the first two bytes of a Certificate message.
+ shrinkingPrefix = []byte{0, 0}
+ // expandingPrefix is just some arbitrary byte string. This has to match the
+ // value in the shim.
+ expandingPrefix = []byte{1, 2, 3, 4}
+)
+
+var shrinkingCompression = CertCompressionAlg{
+ Compress: func(uncompressed []byte) []byte {
+ if !bytes.HasPrefix(uncompressed, shrinkingPrefix) {
+ panic(fmt.Sprintf("cannot compress certificate message %x", uncompressed))
+ }
+ return uncompressed[len(shrinkingPrefix):]
+ },
+ Decompress: func(out []byte, compressed []byte) bool {
+ if len(out) != len(shrinkingPrefix)+len(compressed) {
+ return false
+ }
+
+ copy(out, shrinkingPrefix)
+ copy(out[len(shrinkingPrefix):], compressed)
+ return true
+ },
+}
+
+var expandingCompression = CertCompressionAlg{
+ Compress: func(uncompressed []byte) []byte {
+ ret := make([]byte, 0, len(expandingPrefix)+len(uncompressed))
+ ret = append(ret, expandingPrefix...)
+ return append(ret, uncompressed...)
+ },
+ Decompress: func(out []byte, compressed []byte) bool {
+ if !bytes.HasPrefix(compressed, expandingPrefix) {
+ return false
+ }
+ copy(out, compressed[len(expandingPrefix):])
+ return true
+ },
+}
+
+var randomCompression = CertCompressionAlg{
+ Compress: func(uncompressed []byte) []byte {
+ ret := make([]byte, 1+len(uncompressed))
+ if _, err := rand.Read(ret[:1]); err != nil {
+ panic(err)
+ }
+ copy(ret[1:], uncompressed)
+ return ret
+ },
+ Decompress: func(out []byte, compressed []byte) bool {
+ if len(compressed) != 1+len(out) {
+ return false
+ }
+ copy(out, compressed[1:])
+ return true
+ },
+}
+
+func addCertCompressionTests() {
+ for _, ver := range tlsVersions {
+ if ver.version < VersionTLS12 {
+ continue
+ }
+
+ // Duplicate compression algorithms is an error, even if nothing is
+ // configured.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DuplicateCertCompressionExt-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ DuplicateCompressedCertAlgs: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ // With compression algorithms configured, an duplicate values should still
+ // be an error.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DuplicateCertCompressionExt2-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ DuplicateCompressedCertAlgs: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ if ver.version < VersionTLS13 {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CertCompressionIgnoredBefore13-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ expandingCompressionAlgID: expandingCompression,
+ },
+ },
+ })
+
+ continue
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CertCompressionExpands-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ expandingCompressionAlgID: expandingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: expandingCompressionAlgID,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CertCompressionShrinks-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ },
+ },
+ })
+
+ // Test that the shim behaves consistently if the compression function
+ // is non-deterministic. This is intended to model version differences
+ // between the shim and handshaker with handshake hints, but it is also
+ // useful in confirming we only call the callbacks once.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CertCompressionRandom-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ randomCompressionAlgID: randomCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: randomCompressionAlgID,
+ },
+ },
+ })
+
+ // With both algorithms configured, the server should pick its most
+ // preferable. (Which is expandingCompressionAlgID.)
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CertCompressionPriority-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ expandingCompressionAlgID: expandingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: expandingCompressionAlgID,
+ },
+ },
+ })
+
+ // With no common algorithms configured, the server should decline
+ // compression.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CertCompressionNoCommonAlgs-" + ver.name,
+ flags: []string{"-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID)},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ expandingCompressionAlgID: expandingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectUncompressedCert: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertCompressionExpandsClient-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ expandingCompressionAlgID: expandingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: expandingCompressionAlgID,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertCompressionShrinksClient-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertCompressionBadAlgIDClient-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ SendCertCompressionAlgID: 1234,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNKNOWN_CERT_COMPRESSION_ALG:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertCompressionTooSmallClient-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ SendCertUncompressedLength: 12,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":CERT_DECOMPRESSION_FAILED:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertCompressionTooLargeClient-" + ver.name,
+ flags: []string{"-install-cert-compression-algs"},
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ SendCertUncompressedLength: 1 << 20,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNCOMPRESSED_CERT_TOO_LARGE:",
+ })
+ }
+}
diff --git a/ssl/test/runner/certificate_selection_tests.go b/ssl/test/runner/certificate_selection_tests.go
new file mode 100644
index 0000000..ea64ae9
--- /dev/null
+++ b/ssl/test/runner/certificate_selection_tests.go
@@ -0,0 +1,688 @@
+// 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 (
+ "fmt"
+ "strconv"
+)
+
+func canBeShimCertificate(c *Credential) bool {
+ // Some options can only be set with the credentials API.
+ return c.Type == CredentialTypeX509 && !c.MustMatchIssuer && c.TrustAnchorID == nil
+}
+
+func addCertificateSelectionTests() {
+ // Combinatorially test each selection criteria at different versions,
+ // protocols, and with the matching certificate before and after the
+ // mismatching one.
+ type certSelectTest struct {
+ name string
+ testType testType
+ minVersion uint16
+ maxVersion uint16
+ config Config
+ match *Credential
+ mismatch *Credential
+ flags []string
+ expectedError string
+ }
+ certSelectTests := []certSelectTest{
+ // TLS 1.0 through TLS 1.2 servers should incorporate TLS cipher suites
+ // into certificate selection.
+ {
+ name: "Server-CipherSuite-ECDHE_ECDSA",
+ testType: serverTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CipherSuites: []uint16{
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+ {
+ name: "Server-CipherSuite-ECDHE_RSA",
+ testType: serverTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CipherSuites: []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &rsaCertificate,
+ mismatch: &ecdsaP256Certificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+ {
+ name: "Server-CipherSuite-RSA",
+ testType: serverTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CipherSuites: []uint16{
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &rsaCertificate,
+ mismatch: &ecdsaP256Certificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+
+ // Ed25519 counts as ECDSA for purposes of cipher suite matching.
+ {
+ name: "Server-CipherSuite-ECDHE_ECDSA-Ed25519",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CipherSuites: []uint16{
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &ed25519Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+ {
+ name: "Server-CipherSuite-ECDHE_RSA-Ed25519",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CipherSuites: []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &rsaCertificate,
+ mismatch: &ed25519Certificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+
+ // If there is no ECDHE curve match, ECDHE cipher suites are
+ // disqualified in TLS 1.2 and below. This, in turn, impacts the
+ // available cipher suites for each credential.
+ {
+ name: "Server-CipherSuite-NoECDHE",
+ testType: serverTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CurvePreferences: []CurveID{CurveP256},
+ },
+ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))},
+ match: &rsaCertificate,
+ mismatch: &ecdsaP256Certificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+
+ // If the client offered a cipher that would allow a certificate, but it
+ // wasn't one of the ones we configured, the certificate should be
+ // skipped in favor of another one.
+ {
+ name: "Server-CipherSuite-Prefs",
+ testType: serverTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CipherSuites: []uint16{
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ flags: []string{"-cipher", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"},
+ match: &rsaCertificate,
+ mismatch: &ecdsaP256Certificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+
+ // TLS 1.0 through 1.2 servers should incorporate the curve list into
+ // ECDSA certificate selection.
+ {
+ name: "Server-Curve",
+ testType: serverTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ CurvePreferences: []CurveID{CurveP256},
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &ecdsaP384Certificate,
+ expectedError: ":WRONG_CURVE:",
+ },
+
+ // TLS 1.3 servers ignore the curve list. ECDSA certificate selection is
+ // solely determined by the signature algorithm list.
+ {
+ name: "Server-IgnoreCurve",
+ testType: serverTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ CurvePreferences: []CurveID{CurveP256},
+ },
+ match: &ecdsaP384Certificate,
+ },
+
+ // TLS 1.2 servers also ignore the curve list for Ed25519. The signature
+ // algorithm list is sufficient for Ed25519.
+ {
+ name: "Server-IgnoreCurveEd25519",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ config: Config{
+ CurvePreferences: []CurveID{CurveP256},
+ },
+ match: &ed25519Certificate,
+ },
+
+ // Without signature algorithm negotiation, Ed25519 is not usable in TLS
+ // 1.1 and below.
+ {
+ name: "Server-NoEd25519",
+ testType: serverTest,
+ maxVersion: VersionTLS11,
+ match: &rsaCertificate,
+ mismatch: &ed25519Certificate,
+ },
+
+ // TLS 1.2 and up should incorporate the signature algorithm list into
+ // certificate selection.
+ {
+ name: "Server-SignatureAlgorithm",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ CipherSuites: []uint16{
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+ {
+ name: "Server-SignatureAlgorithm",
+ testType: serverTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ },
+
+ // TLS 1.2's use of the signature algorithm list only disables the
+ // signing-based algorithms. If an RSA key exchange cipher suite is
+ // eligible, that is fine. (This is not a realistic configuration,
+ // however. No one would configure RSA before ECDSA.)
+ {
+ name: "Server-SignatureAlgorithmImpactsECDHEOnly",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ CipherSuites: []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ match: &rsaCertificate,
+ },
+
+ // TLS 1.3's use of the signature algorithm looks at the ECDSA curve
+ // embedded in the signature algorithm.
+ {
+ name: "Server-SignatureAlgorithmECDSACurve",
+ testType: serverTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &ecdsaP384Certificate,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ },
+
+ // TLS 1.2's use does not.
+ {
+ name: "Server-SignatureAlgorithmECDSACurve",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &ecdsaP384Certificate,
+ },
+
+ // TLS 1.0 and 1.1 do not look at the signature algorithm.
+ {
+ name: "Server-IgnoreSignatureAlgorithm",
+ testType: serverTest,
+ maxVersion: VersionTLS11,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &rsaCertificate,
+ },
+
+ // Signature algorithm matches take preferences on the keys into
+ // consideration.
+ {
+ name: "Server-SignatureAlgorithmKeyPrefs",
+ testType: serverTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+ expectedError: ":NO_SHARED_CIPHER:",
+ },
+ {
+ name: "Server-SignatureAlgorithmKeyPrefs",
+ testType: serverTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+ },
+ match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ },
+
+ // TLS 1.2 clients and below check the certificate against the old
+ // client certificate types field.
+ {
+ name: "Client-ClientCertificateTypes-RSA",
+ testType: clientTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCertificateTypes: []uint8{CertTypeRSASign},
+ },
+ match: &rsaCertificate,
+ mismatch: &ecdsaP256Certificate,
+ expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+ },
+ {
+ name: "Client-ClientCertificateTypes-ECDSA",
+ testType: clientTest,
+ maxVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCertificateTypes: []uint8{CertTypeECDSASign},
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+ },
+
+ // Ed25519 is considered ECDSA for purposes of client certificate types.
+ {
+ name: "Client-ClientCertificateTypes-RSA-Ed25519",
+ testType: clientTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCertificateTypes: []uint8{CertTypeRSASign},
+ },
+ match: &rsaCertificate,
+ mismatch: &ed25519Certificate,
+ expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+ },
+ {
+ name: "Client-ClientCertificateTypes-ECDSA-Ed25519",
+ testType: clientTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCertificateTypes: []uint8{CertTypeECDSASign},
+ },
+ match: &ed25519Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+ },
+
+ // TLS 1.2 and up should incorporate the signature algorithm list into
+ // certificate selection. (There is no signature algorithm list to look
+ // at in TLS 1.0 and 1.1.)
+ {
+ name: "Client-SignatureAlgorithm",
+ testType: clientTest,
+ minVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &rsaCertificate,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ },
+
+ // TLS 1.3's use of the signature algorithm looks at the ECDSA curve
+ // embedded in the signature algorithm.
+ {
+ name: "Client-SignatureAlgorithmECDSACurve",
+ testType: clientTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &ecdsaP256Certificate,
+ mismatch: &ecdsaP384Certificate,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ },
+
+ // TLS 1.2's use does not. It is not possible to determine what ECDSA
+ // curves are allowed by the server.
+ {
+ name: "Client-SignatureAlgorithmECDSACurve",
+ testType: clientTest,
+ minVersion: VersionTLS12,
+ maxVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ match: &ecdsaP384Certificate,
+ },
+
+ // Signature algorithm matches take preferences on the keys into
+ // consideration.
+ {
+ name: "Client-SignatureAlgorithmKeyPrefs",
+ testType: clientTest,
+ minVersion: VersionTLS12,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+ },
+ match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ },
+
+ // By default, certificate selection does not take issuers
+ // into account.
+ {
+ name: "Client-DontCheckIssuer",
+ testType: clientTest,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
+ },
+ match: &ecdsaP256Certificate,
+ },
+ {
+ name: "Server-DontCheckIssuer",
+ testType: serverTest,
+ config: Config{
+ RootCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
+ SendRootCAs: true,
+ },
+ match: &ecdsaP256Certificate,
+ },
+
+ // If requested, certificate selection will match against the
+ // requested issuers.
+ {
+ name: "Client-CheckIssuer",
+ testType: clientTest,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
+ },
+ match: rsaChainCertificate.WithMustMatchIssuer(true),
+ mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
+ expectedError: ":NO_MATCHING_ISSUER:",
+ },
+ {
+ name: "Server-CheckIssuer",
+ testType: serverTest,
+ config: Config{
+ RootCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
+ SendRootCAs: true,
+ },
+ match: rsaChainCertificate.WithMustMatchIssuer(true),
+ mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
+ expectedError: ":NO_MATCHING_ISSUER:",
+ },
+
+ // Trust anchor IDs can also be used to match issuers.
+ // TODO(crbug.com/398275713): Implement this for client certificates.
+ {
+ name: "Server-CheckIssuer-TrustAnchorIDs",
+ testType: serverTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ RequestTrustAnchors: [][]byte{{1, 1, 1}},
+ },
+ match: rsaChainCertificate.WithTrustAnchorID([]byte{1, 1, 1}),
+ mismatch: ecdsaP256Certificate.WithTrustAnchorID([]byte{2, 2, 2}),
+ expectedError: ":NO_MATCHING_ISSUER:",
+ },
+
+ // When an issuer-gated credential fails, a normal credential may be
+ // selected instead.
+ {
+ name: "Client-CheckIssuerFallback",
+ testType: clientTest,
+ config: Config{
+ ClientAuth: RequestClientCert,
+ ClientCAs: makeCertPoolFromRoots(&ecdsaP384Certificate),
+ },
+ match: &rsaChainCertificate,
+ mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
+ expectedError: ":NO_MATCHING_ISSUER:",
+ },
+ {
+ name: "Server-CheckIssuerFallback",
+ testType: serverTest,
+ config: Config{
+ RootCAs: makeCertPoolFromRoots(&ecdsaP384Certificate),
+ SendRootCAs: true,
+ },
+ match: &rsaChainCertificate,
+ mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
+ expectedError: ":NO_MATCHING_ISSUER:",
+ },
+ {
+ name: "Server-CheckIssuerFallback-TrustAnchorIDs",
+ testType: serverTest,
+ minVersion: VersionTLS13,
+ config: Config{
+ RequestTrustAnchors: [][]byte{{1, 1, 1}},
+ },
+ match: &rsaChainCertificate,
+ mismatch: ecdsaP256Certificate.WithTrustAnchorID([]byte{2, 2, 2}),
+ expectedError: ":NO_MATCHING_ISSUER:",
+ },
+ }
+
+ for _, protocol := range []protocol{tls, dtls} {
+ for _, vers := range allVersions(protocol) {
+ suffix := fmt.Sprintf("%s-%s", protocol, vers)
+
+ // Test that the credential list is interpreted in preference order,
+ // with the default credential, if any, at the end.
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-Client-PreferenceOrder-%s", suffix),
+ testType: clientTest,
+ protocol: protocol,
+ config: Config{
+ MinVersion: vers.version,
+ MaxVersion: vers.version,
+ ClientAuth: RequestClientCert,
+ },
+ shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
+ shimCertificate: &rsaCertificate,
+ flags: []string{"-expect-selected-credential", "0"},
+ expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+ })
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-Server-PreferenceOrder-%s", suffix),
+ testType: serverTest,
+ protocol: protocol,
+ config: Config{
+ MinVersion: vers.version,
+ MaxVersion: vers.version,
+ },
+ shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
+ shimCertificate: &rsaCertificate,
+ flags: []string{"-expect-selected-credential", "0"},
+ expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+ })
+
+ // Test that the selected credential contributes the certificate chain, OCSP response,
+ // and SCT list.
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-Server-OCSP-SCT-%s", suffix),
+ testType: serverTest,
+ protocol: protocol,
+ config: Config{
+ MinVersion: vers.version,
+ MaxVersion: vers.version,
+ // Configure enough options so that, at all TLS versions, only an RSA
+ // certificate will be accepted.
+ CipherSuites: []uint16{
+ TLS_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ },
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+ },
+ shimCredentials: []*Credential{
+ ecdsaP256Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
+ rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ },
+ shimCertificate: ecdsaP384Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ },
+ })
+
+ // Test that the credentials API works asynchronously. This tests both deferring the
+ // configuration to the certificate callback, and using a custom, async private key.
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-Client-Async-%s", suffix),
+ testType: clientTest,
+ protocol: protocol,
+ config: Config{
+ MinVersion: vers.version,
+ MaxVersion: vers.version,
+ ClientAuth: RequestClientCert,
+ },
+ shimCredentials: []*Credential{&ecdsaP256Certificate},
+ shimCertificate: &rsaCertificate,
+ flags: []string{"-async", "-expect-selected-credential", "0"},
+ expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+ })
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-Server-Async-%s", suffix),
+ testType: serverTest,
+ protocol: protocol,
+ config: Config{
+ MinVersion: vers.version,
+ MaxVersion: vers.version,
+ },
+ shimCredentials: []*Credential{&ecdsaP256Certificate},
+ shimCertificate: &rsaCertificate,
+ flags: []string{"-async", "-expect-selected-credential", "0"},
+ expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+ })
+
+ for _, test := range certSelectTests {
+ if test.minVersion != 0 && vers.version < test.minVersion {
+ continue
+ }
+ if test.maxVersion != 0 && vers.version > test.maxVersion {
+ continue
+ }
+
+ config := test.config
+ config.MinVersion = vers.version
+ config.MaxVersion = vers.version
+
+ // If the mismatch field is omitted, this is a positive test,
+ // just to confirm that the selection logic does not block a
+ // particular certificate.
+ if test.mismatch == nil {
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-%s-%s", test.name, suffix),
+ protocol: protocol,
+ testType: test.testType,
+ config: config,
+ shimCredentials: []*Credential{test.match},
+ flags: append([]string{"-expect-selected-credential", "0"}, test.flags...),
+ expectations: connectionExpectations{peerCertificate: test.match},
+ })
+ continue
+ }
+
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-%s-MatchFirst-%s", test.name, suffix),
+ protocol: protocol,
+ testType: test.testType,
+ config: config,
+ shimCredentials: []*Credential{test.match, test.mismatch},
+ flags: append([]string{"-expect-selected-credential", "0"}, test.flags...),
+ expectations: connectionExpectations{peerCertificate: test.match},
+ })
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-%s-MatchSecond-%s", test.name, suffix),
+ protocol: protocol,
+ testType: test.testType,
+ config: config,
+ shimCredentials: []*Credential{test.mismatch, test.match},
+ flags: append([]string{"-expect-selected-credential", "1"}, test.flags...),
+ expectations: connectionExpectations{peerCertificate: test.match},
+ })
+ if canBeShimCertificate(test.match) {
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-%s-MatchDefault-%s", test.name, suffix),
+ protocol: protocol,
+ testType: test.testType,
+ config: config,
+ shimCredentials: []*Credential{test.mismatch},
+ shimCertificate: test.match,
+ flags: append([]string{"-expect-selected-credential", "-1"}, test.flags...),
+ expectations: connectionExpectations{peerCertificate: test.match},
+ })
+ }
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("CertificateSelection-%s-MatchNone-%s", test.name, suffix),
+ protocol: protocol,
+ testType: test.testType,
+ config: config,
+ shimCredentials: []*Credential{test.mismatch, test.mismatch, test.mismatch},
+ flags: test.flags,
+ shouldFail: true,
+ expectedLocalError: "remote error: handshake failure",
+ expectedError: test.expectedError,
+ })
+ }
+ }
+ }
+}
diff --git a/ssl/test/runner/certificate_tests.go b/ssl/test/runner/certificate_tests.go
new file mode 100644
index 0000000..7f6c4b8
--- /dev/null
+++ b/ssl/test/runner/certificate_tests.go
@@ -0,0 +1,409 @@
+// 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"
+
+func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool {
+ certPool := x509.NewCertPool()
+ for _, cred := range creds {
+ cert, err := x509.ParseCertificate(cred.RootCertificate)
+ if err != nil {
+ panic(err)
+ }
+ certPool.AddCert(cert)
+ }
+ return certPool
+}
+
+func addClientAuthTests() {
+ // Add a dummy cert pool to stress certificate authority parsing.
+ certPool := makeCertPoolFromRoots(&rsaCertificate, &rsa1024Certificate)
+ caNames := certPool.Subjects()
+
+ for _, ver := range tlsVersions {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: ver.name + "-Client-ClientAuth-RSA",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ ClientAuth: RequireAnyClientCert,
+ ClientCAs: certPool,
+ },
+ shimCertificate: &rsaCertificate,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: ver.name + "-Server-ClientAuth-RSA",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: ver.name + "-Server-ClientAuth-ECDSA",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &ecdsaP256Certificate,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: ver.name + "-Client-ClientAuth-ECDSA",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ ClientAuth: RequireAnyClientCert,
+ ClientCAs: certPool,
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ })
+
+ testCases = append(testCases, testCase{
+ name: "NoClientCertificate-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shouldFail: true,
+ expectedLocalError: "client didn't provide a certificate",
+ })
+
+ testCases = append(testCases, testCase{
+ // Even if not configured to expect a certificate, OpenSSL will
+ // return X509_V_OK as the verify_result.
+ testType: serverTest,
+ name: "NoClientCertificateRequested-Server-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ flags: []string{
+ "-expect-verify-result",
+ },
+ resumeSession: true,
+ })
+
+ testCases = append(testCases, testCase{
+ // If a client certificate is not provided, OpenSSL will still
+ // return X509_V_OK as the verify_result.
+ testType: serverTest,
+ name: "NoClientCertificate-Server-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ flags: []string{
+ "-expect-verify-result",
+ "-verify-peer",
+ },
+ resumeSession: true,
+ })
+
+ certificateRequired := "remote error: certificate required"
+ if ver.version < VersionTLS13 {
+ // Prior to TLS 1.3, the generic handshake_failure alert
+ // was used.
+ certificateRequired = "remote error: handshake failure"
+ }
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RequireAnyClientCertificate-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ shouldFail: true,
+ expectedError: ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
+ expectedLocalError: certificateRequired,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipClientCertificate-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SkipClientCertificate: true,
+ },
+ },
+ // Setting SSL_VERIFY_PEER allows anonymous clients.
+ flags: []string{"-verify-peer"},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: ver.name + "-Server-CertReq-CA-List",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ ExpectCertificateReqNames: caNames,
+ },
+ },
+ flags: []string{
+ "-require-any-client-certificate",
+ "-use-client-ca-list", encodeDERValues(caNames),
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: ver.name + "-Client-CertReq-CA-List",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ ClientAuth: RequireAnyClientCert,
+ ClientCAs: certPool,
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-expect-client-ca-list", encodeDERValues(caNames),
+ },
+ })
+ }
+
+ // Client auth is only legal in certificate-based ciphers.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ClientAuth-PSK",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
+ PreSharedKey: []byte("secret"),
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-psk", "secret",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ClientAuth-ECDHE_PSK",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
+ PreSharedKey: []byte("secret"),
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-psk", "secret",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+
+ // Regression test for a bug where the client CA list, if explicitly
+ // set to NULL, was mis-encoded.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Null-Client-CA-List",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ ExpectCertificateReqNames: [][]byte{},
+ },
+ },
+ flags: []string{
+ "-require-any-client-certificate",
+ "-use-client-ca-list", "<NULL>",
+ },
+ })
+
+ // Test that an empty client CA list doesn't send a CA extension.
+ // (This is implicitly tested by the parser. An empty CA extension is
+ // a syntax error.)
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-Empty-Client-CA-List",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-require-any-client-certificate",
+ "-use-client-ca-list", "<EMPTY>",
+ },
+ })
+}
+
+func addCertificateTests() {
+ for _, ver := range tlsVersions {
+ // Test that a certificate chain with intermediate may be sent
+ // and received as both client and server.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "SendReceiveIntermediate-Client-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaChainCertificate,
+ ClientAuth: RequireAnyClientCert,
+ },
+ expectations: connectionExpectations{
+ peerCertificate: &rsaChainCertificate,
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: []string{
+ "-expect-peer-cert-file", rsaChainCertificate.ChainPath,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendReceiveIntermediate-Server-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaChainCertificate,
+ },
+ expectations: connectionExpectations{
+ peerCertificate: &rsaChainCertificate,
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: []string{
+ "-require-any-client-certificate",
+ "-expect-peer-cert-file", rsaChainCertificate.ChainPath,
+ },
+ })
+
+ // Test that garbage leaf certificates are properly rejected.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "GarbageCertificate-Client-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &garbageCertificate,
+ },
+ shouldFail: true,
+ expectedError: ":CANNOT_PARSE_LEAF_CERT:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "GarbageCertificate-Server-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &garbageCertificate,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ shouldFail: true,
+ expectedError: ":CANNOT_PARSE_LEAF_CERT:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+ }
+}
+
+func addRetainOnlySHA256ClientCertTests() {
+ for _, ver := range tlsVersions {
+ // Test that enabling
+ // SSL_CTX_set_retain_only_sha256_of_client_certs without
+ // actually requesting a client certificate is a no-op.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RetainOnlySHA256-NoCert-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ flags: []string{
+ "-on-initial-retain-only-sha256-client-cert",
+ "-on-resume-retain-only-sha256-client-cert",
+ },
+ resumeSession: true,
+ })
+
+ // Test that when retaining only a SHA-256 certificate is
+ // enabled, the hash appears as expected.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RetainOnlySHA256-Cert-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-verify-peer",
+ "-on-initial-retain-only-sha256-client-cert",
+ "-on-resume-retain-only-sha256-client-cert",
+ "-on-initial-expect-sha256-client-cert",
+ "-on-resume-expect-sha256-client-cert",
+ },
+ resumeSession: true,
+ })
+
+ // Test that when the config changes from on to off, a
+ // resumption is rejected because the server now wants the full
+ // certificate chain.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RetainOnlySHA256-OnOff-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-verify-peer",
+ "-on-initial-retain-only-sha256-client-cert",
+ "-on-initial-expect-sha256-client-cert",
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+
+ // Test that when the config changes from off to on, a
+ // resumption is rejected because the server now wants just the
+ // hash.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RetainOnlySHA256-OffOn-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-verify-peer",
+ "-on-resume-retain-only-sha256-client-cert",
+ "-on-resume-expect-sha256-client-cert",
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+ }
+}
diff --git a/ssl/test/runner/change_cipher_spec_tests.go b/ssl/test/runner/change_cipher_spec_tests.go
new file mode 100644
index 0000000..0b28525
--- /dev/null
+++ b/ssl/test/runner/change_cipher_spec_tests.go
@@ -0,0 +1,360 @@
+// 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 "slices"
+
+func addChangeCipherSpecTests() {
+ // Test missing ChangeCipherSpecs.
+ testCases = append(testCases, testCase{
+ name: "SkipChangeCipherSpec-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SkipChangeCipherSpec: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipChangeCipherSpec-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SkipChangeCipherSpec: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipChangeCipherSpec-Server-NPN",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"bar"},
+ Bugs: ProtocolBugs{
+ SkipChangeCipherSpec: true,
+ },
+ },
+ flags: []string{
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+
+ // Test synchronization between the handshake and ChangeCipherSpec.
+ // Partial post-CCS handshake messages before ChangeCipherSpec should be
+ // rejected. Test both with and without handshake packing to handle both
+ // when the partial post-CCS message is in its own record and when it is
+ // attached to the pre-CCS message.
+ for _, packed := range []bool{false, true} {
+ var suffix string
+ if packed {
+ suffix = "-Packed"
+ }
+
+ testCases = append(testCases, testCase{
+ name: "FragmentAcrossChangeCipherSpec-Client" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ FragmentAcrossChangeCipherSpec: true,
+ PackHandshakeFlight: packed,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ name: "FragmentAcrossChangeCipherSpec-Client-Resume" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ FragmentAcrossChangeCipherSpec: true,
+ PackHandshakeFlight: packed,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "FragmentAcrossChangeCipherSpec-Server" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ FragmentAcrossChangeCipherSpec: true,
+ PackHandshakeFlight: packed,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "FragmentAcrossChangeCipherSpec-Server-Resume" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ FragmentAcrossChangeCipherSpec: true,
+ PackHandshakeFlight: packed,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "FragmentAcrossChangeCipherSpec-Server-NPN" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"bar"},
+ Bugs: ProtocolBugs{
+ FragmentAcrossChangeCipherSpec: true,
+ PackHandshakeFlight: packed,
+ },
+ },
+ flags: []string{
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ }
+
+ // In TLS 1.2 resumptions, the client sends ClientHello in the first flight
+ // and ChangeCipherSpec + Finished in the second flight. Test the server's
+ // behavior when the Finished message is fragmented across not only
+ // ChangeCipherSpec but also the flight boundary.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PartialClientFinishedWithClientHello-TLS12-Resume",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ PartialClientFinishedWithClientHello: true,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // In TLS 1.2 full handshakes without tickets, the server's first flight ends
+ // with ServerHelloDone and the second flight is ChangeCipherSpec + Finished.
+ // Test the client's behavior when the Finished message is fragmented across
+ // not only ChangeCipherSpec but also the flight boundary.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "PartialFinishedWithServerHelloDone",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ PartialFinishedWithServerHelloDone: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Test that, in DTLS 1.2, key changes are not allowed when there are
+ // buffered messages. Do this sending all messages in reverse, so that later
+ // ones are buffered, and leaving Finished unencrypted.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "KeyChangeWithBufferedMessages-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ next = slices.Clone(next)
+ slices.Reverse(next)
+ for i := range next {
+ next[i].Epoch = 0
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ })
+
+ // Test synchronization between encryption changes and the handshake in
+ // TLS 1.3, where ChangeCipherSpec is implicit.
+ testCases = append(testCases, testCase{
+ name: "PartialEncryptedExtensionsWithServerHello",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ PartialEncryptedExtensionsWithServerHello: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PartialClientFinishedWithClientHello",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ PartialClientFinishedWithClientHello: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PartialClientFinishedWithSecondClientHello",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Trigger a curve-based HelloRetryRequest.
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ PartialClientFinishedWithSecondClientHello: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PartialEndOfEarlyDataWithClientHello",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ PartialEndOfEarlyDataWithClientHello: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ })
+
+ // Test that early ChangeCipherSpecs are handled correctly.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyChangeCipherSpec-server-1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ EarlyChangeCipherSpec: 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyChangeCipherSpec-server-2",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ EarlyChangeCipherSpec: 2,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "StrayChangeCipherSpec",
+ config: Config{
+ // TODO(davidben): Once DTLS 1.3 exists, test
+ // that stray ChangeCipherSpec messages are
+ // rejected.
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ c.WriteFragments([]DTLSFragment{{IsChangeCipherSpec: true, Data: []byte{1}}})
+ c.WriteFlight(next)
+ },
+ },
+ },
+ })
+
+ // Test that the contents of ChangeCipherSpec are checked.
+ testCases = append(testCases, testCase{
+ name: "BadChangeCipherSpec-1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadChangeCipherSpec: []byte{2},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
+ })
+ testCases = append(testCases, testCase{
+ name: "BadChangeCipherSpec-2",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadChangeCipherSpec: []byte{1, 1},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "BadChangeCipherSpec-DTLS-1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadChangeCipherSpec: []byte{2},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "BadChangeCipherSpec-DTLS-2",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadChangeCipherSpec: []byte{1, 1},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
+ })
+}
diff --git a/ssl/test/runner/cipher_suite_tests.go b/ssl/test/runner/cipher_suite_tests.go
new file mode 100644
index 0000000..5ae63ca
--- /dev/null
+++ b/ssl/test/runner/cipher_suite_tests.go
@@ -0,0 +1,575 @@
+// 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 (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type testCipherSuite struct {
+ name string
+ id uint16
+}
+
+var testCipherSuites = []testCipherSuite{
+ {"RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+ {"RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ {"ECDHE_ECDSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
+ {"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
+ {"ECDHE_ECDSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
+ {"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
+ {"ECDHE_RSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"ECDHE_RSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ {"ECDHE_RSA_WITH_AES_128_CBC_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"ECDHE_RSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"ECDHE_RSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
+ {"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ {"PSK_WITH_AES_128_CBC_SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
+ {"PSK_WITH_AES_256_CBC_SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
+ {"ECDHE_PSK_WITH_AES_128_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
+ {"ECDHE_PSK_WITH_AES_256_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA},
+ {"ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256},
+ {"CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {"AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+}
+
+func hasComponent(suiteName, component string) bool {
+ return strings.Contains("_"+suiteName+"_", "_"+component+"_")
+}
+
+func isTLS12Only(suiteName string) bool {
+ return hasComponent(suiteName, "GCM") ||
+ hasComponent(suiteName, "SHA256") ||
+ hasComponent(suiteName, "SHA384") ||
+ hasComponent(suiteName, "POLY1305")
+}
+
+func isTLS13Suite(suiteName string) bool {
+ return !hasComponent(suiteName, "WITH")
+}
+
+func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol protocol) {
+ const psk = "12345"
+ const pskIdentity = "luggage combo"
+
+ if !ver.supportsProtocol(protocol) {
+ return
+ }
+ prefix := protocol.String() + "-"
+
+ var cert *Credential
+ if isTLS13Suite(suite.name) {
+ cert = &rsaCertificate
+ } else if hasComponent(suite.name, "ECDSA") {
+ cert = &ecdsaP256Certificate
+ } else if hasComponent(suite.name, "RSA") {
+ cert = &rsaCertificate
+ }
+
+ var flags []string
+ if hasComponent(suite.name, "PSK") {
+ flags = append(flags,
+ "-psk", psk,
+ "-psk-identity", pskIdentity)
+ }
+
+ if hasComponent(suite.name, "3DES") {
+ // BoringSSL disables 3DES ciphers by default.
+ flags = append(flags, "-cipher", "3DES")
+ }
+
+ var shouldFail bool
+ if isTLS12Only(suite.name) && ver.version < VersionTLS12 {
+ shouldFail = true
+ }
+ if !isTLS13Suite(suite.name) && ver.version >= VersionTLS13 {
+ shouldFail = true
+ }
+ if isTLS13Suite(suite.name) && ver.version < VersionTLS13 {
+ shouldFail = true
+ }
+
+ var sendCipherSuite uint16
+ var expectedServerError, expectedClientError string
+ serverCipherSuites := []uint16{suite.id}
+ if shouldFail {
+ expectedServerError = ":NO_SHARED_CIPHER:"
+ if ver.version >= VersionTLS13 && cert == nil {
+ // TLS 1.2 PSK ciphers won't configure a server certificate, but we
+ // require one in TLS 1.3.
+ expectedServerError = ":NO_CERTIFICATE_SET:"
+ }
+ expectedClientError = ":WRONG_CIPHER_RETURNED:"
+ // Configure the server to select ciphers as normal but
+ // select an incompatible cipher in ServerHello.
+ serverCipherSuites = nil
+ sendCipherSuite = suite.id
+ }
+
+ // Verify exporters interoperate.
+ exportKeyingMaterial := 1024
+
+ if ver.version != VersionTLS13 || !ver.hasDTLS {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: prefix + ver.name + "-" + suite.name + "-server",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CipherSuites: []uint16{suite.id},
+ Credential: cert,
+ PreSharedKey: []byte(psk),
+ PreSharedKeyIdentity: pskIdentity,
+ Bugs: ProtocolBugs{
+ AdvertiseAllConfiguredCiphers: true,
+ },
+ },
+ shimCertificate: cert,
+ flags: flags,
+ resumeSession: true,
+ shouldFail: shouldFail,
+ expectedError: expectedServerError,
+ exportKeyingMaterial: exportKeyingMaterial,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: prefix + ver.name + "-" + suite.name + "-client",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CipherSuites: serverCipherSuites,
+ Credential: cert,
+ PreSharedKey: []byte(psk),
+ PreSharedKeyIdentity: pskIdentity,
+ Bugs: ProtocolBugs{
+ IgnorePeerCipherPreferences: shouldFail,
+ SendCipherSuite: sendCipherSuite,
+ },
+ },
+ flags: flags,
+ resumeSession: true,
+ shouldFail: shouldFail,
+ expectedError: expectedClientError,
+ exportKeyingMaterial: exportKeyingMaterial,
+ })
+ }
+
+ if shouldFail {
+ return
+ }
+
+ // Ensure the maximum record size is accepted.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: prefix + ver.name + "-" + suite.name + "-LargeRecord",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CipherSuites: []uint16{suite.id},
+ Credential: cert,
+ PreSharedKey: []byte(psk),
+ PreSharedKeyIdentity: pskIdentity,
+ },
+ flags: flags,
+ messageLen: maxPlaintext,
+ })
+
+ // Test bad records for all ciphers. Bad records are fatal in TLS
+ // and ignored in DTLS.
+ shouldFail = protocol == tls
+ var expectedError string
+ if shouldFail {
+ expectedError = ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"
+ }
+
+ // When QUIC is used, the QUIC stack handles record encryption/decryption.
+ // Thus it is not possible for the TLS stack in QUIC mode to receive a
+ // bad record (i.e. one that fails to decrypt).
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: prefix + ver.name + "-" + suite.name + "-BadRecord",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ CipherSuites: []uint16{suite.id},
+ Credential: cert,
+ PreSharedKey: []byte(psk),
+ PreSharedKeyIdentity: pskIdentity,
+ },
+ flags: flags,
+ damageFirstWrite: true,
+ messageLen: maxPlaintext,
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ })
+ }
+}
+
+func addCipherSuiteTests() {
+ const bogusCipher = 0xfe00
+
+ for _, suite := range testCipherSuites {
+ for _, ver := range tlsVersions {
+ for _, protocol := range []protocol{tls, dtls, quic} {
+ addTestForCipherSuite(suite, ver, protocol)
+ }
+ }
+ }
+
+ testCases = append(testCases, testCase{
+ name: "NoSharedCipher",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{},
+ },
+ shouldFail: true,
+ expectedError: ":HANDSHAKE_FAILURE_ON_CLIENT_HELLO:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "NoSharedCipher-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{},
+ },
+ shouldFail: true,
+ expectedError: ":HANDSHAKE_FAILURE_ON_CLIENT_HELLO:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnsupportedCipherSuite",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ IgnorePeerCipherPreferences: true,
+ },
+ },
+ flags: []string{"-cipher", "DEFAULT:!AES"},
+ shouldFail: true,
+ expectedError: ":WRONG_CIPHER_RETURNED:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ServerHelloBogusCipher",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: bogusCipher,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CIPHER_RETURNED:",
+ })
+ testCases = append(testCases, testCase{
+ name: "ServerHelloBogusCipher-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: bogusCipher,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CIPHER_RETURNED:",
+ })
+
+ // The server must be tolerant to bogus ciphers.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "UnknownCipher",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{bogusCipher, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ AdvertiseAllConfiguredCiphers: true,
+ },
+ },
+ })
+
+ // The server must be tolerant to bogus ciphers.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "UnknownCipher-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{bogusCipher, TLS_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ AdvertiseAllConfiguredCiphers: true,
+ },
+ },
+ })
+
+ // Test empty ECDHE_PSK identity hints work as expected.
+ testCases = append(testCases, testCase{
+ name: "EmptyECDHEPSKHint",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
+ PreSharedKey: []byte("secret"),
+ },
+ flags: []string{"-psk", "secret"},
+ })
+
+ // Test empty PSK identity hints work as expected, even if an explicit
+ // ServerKeyExchange is sent.
+ testCases = append(testCases, testCase{
+ name: "ExplicitEmptyPSKHint",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
+ PreSharedKey: []byte("secret"),
+ Bugs: ProtocolBugs{
+ AlwaysSendPreSharedKeyIdentityHint: true,
+ },
+ },
+ flags: []string{"-psk", "secret"},
+ })
+
+ // Test that clients enforce that the server-sent certificate and cipher
+ // suite match in TLS 1.2.
+ testCases = append(testCases, testCase{
+ name: "CertificateCipherMismatch-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CERTIFICATE_TYPE:",
+ })
+ testCases = append(testCases, testCase{
+ name: "CertificateCipherMismatch-ECDSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ Credential: &ecdsaP256Certificate,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CERTIFICATE_TYPE:",
+ })
+ testCases = append(testCases, testCase{
+ name: "CertificateCipherMismatch-Ed25519",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ Credential: &ed25519Certificate,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CERTIFICATE_TYPE:",
+ })
+
+ // Test that servers decline to select a cipher suite which is
+ // inconsistent with their configured certificate.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerCipherFilter-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":NO_SHARED_CIPHER:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerCipherFilter-ECDSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ shouldFail: true,
+ expectedError: ":NO_SHARED_CIPHER:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerCipherFilter-Ed25519",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &ed25519Certificate,
+ shouldFail: true,
+ expectedError: ":NO_SHARED_CIPHER:",
+ })
+
+ // Test cipher suite negotiation works as expected. Configure a
+ // complicated cipher suite configuration.
+ const negotiationTestCiphers = "" +
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:" +
+ "[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384|TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]:" +
+ "TLS_RSA_WITH_AES_128_GCM_SHA256:" +
+ "TLS_RSA_WITH_AES_128_CBC_SHA:" +
+ "[TLS_RSA_WITH_AES_256_GCM_SHA384|TLS_RSA_WITH_AES_256_CBC_SHA]"
+ negotiationTests := []struct {
+ ciphers []uint16
+ expected uint16
+ }{
+ // Server preferences are honored, including when
+ // equipreference groups are involved.
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ },
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ },
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ },
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ },
+ // Equipreference groups use the client preference.
+ {
+ []uint16{
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ },
+ {
+ []uint16{
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+ },
+ {
+ []uint16{
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+ },
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_256_CBC_SHA,
+ },
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_CBC_SHA,
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ TLS_RSA_WITH_AES_256_CBC_SHA,
+ },
+ // If there are two equipreference groups, the preferred one
+ // takes precedence.
+ {
+ []uint16{
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+ },
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ },
+ }
+ for i, t := range negotiationTests {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CipherNegotiation-" + strconv.Itoa(i),
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: t.ciphers,
+ },
+ flags: []string{"-cipher", negotiationTestCiphers},
+ expectations: connectionExpectations{
+ cipher: t.expected,
+ },
+ })
+ }
+}
+
+func addRSAClientKeyExchangeTests() {
+ for bad := RSABadValue(1); bad < NumRSABadValues; bad++ {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: fmt.Sprintf("BadRSAClientKeyExchange-%d", bad),
+ config: Config{
+ // Ensure the ClientHello version and final
+ // version are different, to detect if the
+ // server uses the wrong one.
+ MaxVersion: VersionTLS11,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ BadRSAClientKeyExchange: bad,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+ }
+
+ // The server must compare whatever was in ClientHello.version for the
+ // RSA premaster.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendClientVersion-RSA",
+ config: Config{
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0x1234,
+ },
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+}
diff --git a/ssl/test/runner/compliance_policy_tests.go b/ssl/test/runner/compliance_policy_tests.go
new file mode 100644
index 0000000..f0ccf9e
--- /dev/null
+++ b/ssl/test/runner/compliance_policy_tests.go
@@ -0,0 +1,290 @@
+// 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
+
+func addCompliancePolicyTests() {
+ for _, protocol := range []protocol{tls, quic} {
+ for _, suite := range testCipherSuites {
+ var isFIPSCipherSuite bool
+ switch suite.id {
+ case TLS_AES_128_GCM_SHA256,
+ TLS_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ isFIPSCipherSuite = true
+ }
+
+ var isWPACipherSuite bool
+ switch suite.id {
+ case TLS_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ isWPACipherSuite = true
+ }
+
+ var cert Credential
+ if hasComponent(suite.name, "ECDSA") {
+ cert = ecdsaP384Certificate
+ } else {
+ cert = rsaCertificate
+ }
+
+ maxVersion := uint16(VersionTLS13)
+ if !isTLS13Suite(suite.name) {
+ if protocol == quic {
+ continue
+ }
+ maxVersion = VersionTLS12
+ }
+
+ policies := []struct {
+ flag string
+ cipherSuiteOk bool
+ }{
+ {"-fips-202205", isFIPSCipherSuite},
+ {"-wpa-202304", isWPACipherSuite},
+ }
+
+ for _, policy := range policies {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + suite.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ CipherSuites: []uint16{suite.id},
+ },
+ shimCertificate: &cert,
+ flags: []string{
+ policy.flag,
+ },
+ shouldFail: !policy.cipherSuiteOk,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + suite.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ CipherSuites: []uint16{suite.id},
+ Credential: &cert,
+ },
+ flags: []string{
+ policy.flag,
+ },
+ shouldFail: !policy.cipherSuiteOk,
+ })
+ }
+ }
+
+ // Check that a TLS 1.3 client won't accept ChaCha20 even if the server
+ // picks it without it being in the client's cipher list.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Client-ReallyWontAcceptChaCha",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
+ },
+ },
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CIPHER_RETURNED:",
+ })
+
+ for _, curve := range testCurves {
+ var isFIPSCurve bool
+ switch curve.id {
+ case CurveP256, CurveP384:
+ isFIPSCurve = true
+ }
+
+ var isWPACurve bool
+ switch curve.id {
+ case CurveP384:
+ isWPACurve = true
+ }
+
+ policies := []struct {
+ flag string
+ curveOk bool
+ }{
+ {"-fips-202205", isFIPSCurve},
+ {"-wpa-202304", isWPACurve},
+ }
+
+ for _, policy := range policies {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + curve.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{curve.id},
+ },
+ flags: []string{
+ policy.flag,
+ },
+ shouldFail: !policy.curveOk,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + curve.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{curve.id},
+ },
+ flags: []string{
+ policy.flag,
+ },
+ shouldFail: !policy.curveOk,
+ })
+ }
+ }
+
+ for _, sigalg := range testSignatureAlgorithms {
+ // The TLS 1.0 and TLS 1.1 default signature algorithm does not
+ // apply to these tests.
+ if sigalg.id == 0 {
+ continue
+ }
+
+ var isFIPSSigAlg bool
+ switch sigalg.id {
+ case signatureRSAPKCS1WithSHA256,
+ signatureRSAPKCS1WithSHA384,
+ signatureRSAPKCS1WithSHA512,
+ signatureECDSAWithP256AndSHA256,
+ signatureECDSAWithP384AndSHA384,
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA384,
+ signatureRSAPSSWithSHA512:
+ isFIPSSigAlg = true
+ }
+
+ var isWPASigAlg bool
+ switch sigalg.id {
+ case signatureRSAPKCS1WithSHA384,
+ signatureRSAPKCS1WithSHA512,
+ signatureECDSAWithP384AndSHA384,
+ signatureRSAPSSWithSHA384,
+ signatureRSAPSSWithSHA512:
+ isWPASigAlg = true
+ }
+
+ if sigalg.curve == CurveP224 {
+ // This can work in TLS 1.2, but not with TLS 1.3.
+ // For consistency it's not permitted in FIPS mode.
+ isFIPSSigAlg = false
+ }
+
+ maxVersion := uint16(VersionTLS13)
+ if hasComponent(sigalg.name, "PKCS1") {
+ if protocol == quic {
+ continue
+ }
+ maxVersion = VersionTLS12
+ }
+
+ policies := []struct {
+ flag string
+ sigAlgOk bool
+ }{
+ {"-fips-202205", isFIPSSigAlg},
+ {"-wpa-202304", isWPASigAlg},
+ }
+
+ cert := sigalg.baseCert.WithSignatureAlgorithms(sigalg.id)
+ for _, policy := range policies {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + sigalg.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ VerifySignatureAlgorithms: []signatureAlgorithm{sigalg.id},
+ },
+ // Use the base certificate. We wish to pick up the signature algorithm
+ // preferences from the FIPS policy.
+ shimCertificate: sigalg.baseCert,
+ flags: []string{policy.flag},
+ shouldFail: !policy.sigAlgOk,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + sigalg.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ Credential: cert,
+ },
+ flags: []string{
+ policy.flag,
+ },
+ shouldFail: !policy.sigAlgOk,
+ })
+ }
+ }
+
+ // AES-256-GCM is the most preferred.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance-cnsa202407-" + protocol.String() + "-AES-256-preferred",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384},
+ },
+ flags: []string{
+ "-cnsa-202407",
+ },
+ expectations: connectionExpectations{cipher: TLS_AES_256_GCM_SHA384},
+ })
+
+ // AES-128-GCM is preferred over ChaCha20-Poly1305.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance-cnsa202407-" + protocol.String() + "-AES-128-preferred",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
+ },
+ flags: []string{
+ "-cnsa-202407",
+ },
+ expectations: connectionExpectations{cipher: TLS_AES_128_GCM_SHA256},
+ })
+ }
+}
diff --git a/ssl/test/runner/curve_tests.go b/ssl/test/runner/curve_tests.go
new file mode 100644
index 0000000..5afad08
--- /dev/null
+++ b/ssl/test/runner/curve_tests.go
@@ -0,0 +1,736 @@
+// 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 (
+ "fmt"
+ "strconv"
+)
+
+var testCurves = []struct {
+ name string
+ id CurveID
+}{
+ {"P-224", CurveP224},
+ {"P-256", CurveP256},
+ {"P-384", CurveP384},
+ {"P-521", CurveP521},
+ {"X25519", CurveX25519},
+ {"Kyber", CurveX25519Kyber768},
+ {"MLKEM", CurveX25519MLKEM768},
+}
+
+const bogusCurve = 0x1234
+
+func isPqGroup(r CurveID) bool {
+ return r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
+}
+
+func isECDHGroup(r CurveID) bool {
+ return r == CurveP224 || r == CurveP256 || r == CurveP384 || r == CurveP521
+}
+
+func isX25519Group(r CurveID) bool {
+ return r == CurveX25519 || r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
+}
+
+func addCurveTests() {
+ // A set of cipher suites that ensures some curve-using mode is used.
+ // Without this, servers may fall back to RSA key exchange.
+ ecdheCiphers := []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_AES_256_GCM_SHA384,
+ }
+
+ for _, curve := range testCurves {
+ for _, ver := range tlsVersions {
+ if isPqGroup(curve.id) && ver.version < VersionTLS13 {
+ continue
+ }
+ for _, testType := range []testType{clientTest, serverTest} {
+ suffix := fmt.Sprintf("%s-%s-%s", testType, curve.name, ver.name)
+
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ },
+ flags: append(
+ []string{"-expect-curve-id", strconv.Itoa(int(curve.id))},
+ flagInts("-curves", shimConfig.AllCurves)...,
+ ),
+ expectations: connectionExpectations{
+ curveID: curve.id,
+ },
+ })
+
+ badKeyShareLocalError := "remote error: illegal parameter"
+ if testType == clientTest && ver.version >= VersionTLS13 {
+ // If the shim is a TLS 1.3 client and the runner sends a bad
+ // key share, the runner never reads the client's cleartext
+ // alert because the runner has already started encrypting by
+ // the time the client sees it.
+ badKeyShareLocalError = "local error: bad record MAC"
+ }
+
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-TruncateKeyShare-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ TruncateKeyShare: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-PadKeyShare-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ PadKeyShare: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+
+ if isECDHGroup(curve.id) {
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-Compressed-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ SendCompressedCoordinates: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-NotOnCurve-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ ECDHPointNotOnCurve: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ }
+
+ if isX25519Group(curve.id) {
+ // Implementations should mask off the high order bit in X25519.
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-SetX25519HighBit-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ SetX25519HighBit: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ expectations: connectionExpectations{
+ curveID: curve.id,
+ },
+ })
+
+ // Implementations should reject low order points.
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-LowOrderX25519Point-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ LowOrderX25519Point: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ }
+
+ if curve.id == CurveX25519MLKEM768 && testType == serverTest {
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-MLKEMEncapKeyNotReduced-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ MLKEMEncapKeyNotReduced: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ }
+ }
+ }
+ }
+
+ // The server must be tolerant to bogus curves.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "UnknownCurve",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ CurvePreferences: []CurveID{bogusCurve, CurveP256},
+ },
+ })
+
+ // The server must be tolerant to bogus curves.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "UnknownCurve-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{bogusCurve, CurveP256},
+ },
+ })
+
+ // The server must not consider ECDHE ciphers when there are no
+ // supported curves.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoSupportedCurves",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ NoSupportedCurves: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":NO_SHARED_CIPHER:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoSupportedCurves-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ NoSupportedCurves: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":NO_SHARED_GROUP:",
+ })
+
+ // The server must fall back to another cipher when there are no
+ // supported curves.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoCommonCurves",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ CurvePreferences: []CurveID{CurveP224},
+ },
+ expectations: connectionExpectations{
+ cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ })
+
+ // The client must reject bogus curves and disabled curves.
+ testCases = append(testCases, testCase{
+ name: "BadECDHECurve",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SendCurve: bogusCurve,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+ testCases = append(testCases, testCase{
+ name: "BadECDHECurve-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendCurve: bogusCurve,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnsupportedCurve",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ CurvePreferences: []CurveID{CurveP256},
+ Bugs: ProtocolBugs{
+ IgnorePeerCurvePreferences: true,
+ },
+ },
+ flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ // TODO(davidben): Add a TLS 1.3 version where
+ // HelloRetryRequest requests an unsupported curve.
+ name: "UnsupportedCurve-ServerHello-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendCurve: CurveP256,
+ },
+ },
+ flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ // The previous curve ID should be reported on TLS 1.2 resumption.
+ testCases = append(testCases, testCase{
+ name: "CurveID-Resume-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ flags: []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))},
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CurveID-Resume-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ flags: []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))},
+ resumeSession: true,
+ })
+
+ // TLS 1.3 allows resuming at a differet curve. If this happens, the new
+ // one should be reported.
+ testCases = append(testCases, testCase{
+ name: "CurveID-Resume-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP256},
+ },
+ flags: []string{
+ "-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+ "-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)),
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CurveID-Resume-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP256},
+ },
+ flags: []string{
+ "-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+ "-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)),
+ },
+ resumeSession: true,
+ })
+
+ // Server-sent point formats are legal in TLS 1.2, but not in TLS 1.3.
+ testCases = append(testCases, testCase{
+ name: "PointFormat-ServerHello-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{pointFormatUncompressed},
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "PointFormat-EncryptedExtensions-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{pointFormatUncompressed},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ // Server-sent supported groups/curves are legal in TLS 1.3. They are
+ // illegal in TLS 1.2, but some servers send them anyway, so we must
+ // tolerate them.
+ testCases = append(testCases, testCase{
+ name: "SupportedCurves-ServerHello-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendServerSupportedCurves: true,
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "SupportedCurves-EncryptedExtensions-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendServerSupportedCurves: true,
+ },
+ },
+ })
+
+ // Test that we tolerate unknown point formats, as long as
+ // pointFormatUncompressed is present. Limit ciphers to ECDHE ciphers to
+ // check they are still functional.
+ testCases = append(testCases, testCase{
+ name: "PointFormat-Client-Tolerance",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime},
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PointFormat-Server-Tolerance",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime},
+ },
+ },
+ })
+
+ // Test TLS 1.2 does not require the point format extension to be
+ // present.
+ testCases = append(testCases, testCase{
+ name: "PointFormat-Client-Missing",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{},
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PointFormat-Server-Missing",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{},
+ },
+ },
+ })
+
+ // If the point format extension is present, uncompressed points must be
+ // offered. BoringSSL requires this whether or not ECDHE is used.
+ testCases = append(testCases, testCase{
+ name: "PointFormat-Client-MissingUncompressed",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{pointFormatCompressedPrime},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PointFormat-Server-MissingUncompressed",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendSupportedPointFormats: []byte{pointFormatCompressedPrime},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ // Post-quantum groups require TLS 1.3.
+ for _, curve := range testCurves {
+ if !isPqGroup(curve.id) {
+ continue
+ }
+
+ // Post-quantum groups should not be offered by a TLS 1.2 client.
+ testCases = append(testCases, testCase{
+ name: "TLS12ClientShouldNotOffer-" + curve.name,
+ config: Config{
+ Bugs: ProtocolBugs{
+ FailIfPostQuantumOffered: true,
+ },
+ },
+ flags: []string{
+ "-max-version", strconv.Itoa(VersionTLS12),
+ "-curves", strconv.Itoa(int(curve.id)),
+ "-curves", strconv.Itoa(int(CurveX25519)),
+ },
+ })
+
+ // Post-quantum groups should not be selected by a TLS 1.2 server.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS12ServerShouldNotSelect-" + curve.name,
+ flags: []string{
+ "-max-version", strconv.Itoa(VersionTLS12),
+ "-curves", strconv.Itoa(int(curve.id)),
+ "-curves", strconv.Itoa(int(CurveX25519)),
+ },
+ expectations: connectionExpectations{
+ curveID: CurveX25519,
+ },
+ })
+
+ // If a TLS 1.2 server selects a post-quantum group anyway, the client
+ // should not accept it.
+ testCases = append(testCases, testCase{
+ name: "ClientShouldNotAllowInTLS12-" + curve.name,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendCurve: curve.id,
+ },
+ },
+ flags: []string{
+ "-curves", strconv.Itoa(int(curve.id)),
+ "-curves", strconv.Itoa(int(CurveX25519)),
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+ }
+
+ // ML-KEM and Kyber should not be offered by default as a client.
+ testCases = append(testCases, testCase{
+ name: "PostQuantumNotEnabledByDefaultInClients",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ FailIfPostQuantumOffered: true,
+ },
+ },
+ })
+
+ // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share.
+ testCases = append(testCases, testCase{
+ name: "NotJustMLKEMKeyShare",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519},
+ },
+ },
+ flags: []string{
+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
+ "-curves", strconv.Itoa(int(CurveX25519)),
+ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
+ },
+ })
+
+ // ... and the other way around
+ testCases = append(testCases, testCase{
+ name: "MLKEMKeyShareIncludedSecond",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768},
+ },
+ },
+ flags: []string{
+ "-curves", strconv.Itoa(int(CurveX25519)),
+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
+ "-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+ },
+ })
+
+ // ... and even if there's another curve in the middle because it's the
+ // first classical and first post-quantum "curves" that get key shares
+ // included.
+ testCases = append(testCases, testCase{
+ name: "MLKEMKeyShareIncludedThird",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768},
+ },
+ },
+ flags: []string{
+ "-curves", strconv.Itoa(int(CurveX25519)),
+ "-curves", strconv.Itoa(int(CurveP256)),
+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
+ "-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+ },
+ })
+
+ // If ML-KEM is the only configured curve, the key share is sent.
+ testCases = append(testCases, testCase{
+ name: "JustConfiguringMLKEMWorks",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768},
+ },
+ },
+ flags: []string{
+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
+ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
+ },
+ })
+
+ // If both ML-KEM and Kyber are configured, only the preferred one's
+ // key share should be sent.
+ testCases = append(testCases, testCase{
+ name: "BothMLKEMAndKyber",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768},
+ },
+ },
+ flags: []string{
+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
+ "-curves", strconv.Itoa(int(CurveX25519Kyber768)),
+ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
+ },
+ })
+
+ // As a server, ML-KEM is not yet supported by default.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PostQuantumNotEnabledByDefaultForAServer",
+ config: Config{
+ MinVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768, CurveX25519},
+ DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768},
+ },
+ flags: []string{
+ "-server-preference",
+ "-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+ },
+ })
+
+ // In TLS 1.2, the curve list is also used to signal ECDSA curves.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CheckECDSACurve-TLS12",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ CurvePreferences: []CurveID{CurveP384},
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ // If the ECDSA certificate is ineligible due to a curve mismatch, the
+ // server may still consider a PSK cipher suite.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CheckECDSACurve-PSK-TLS12",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
+ },
+ CurvePreferences: []CurveID{CurveP384},
+ PreSharedKey: []byte("12345"),
+ PreSharedKeyIdentity: "luggage combo",
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ expectations: connectionExpectations{
+ cipher: TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
+ },
+ flags: []string{
+ "-psk", "12345",
+ "-psk-identity", "luggage combo",
+ },
+ })
+
+ // In TLS 1.3, the curve list only controls ECDH.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "CheckECDSACurve-NotApplicable-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP384},
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ })
+}
diff --git a/ssl/test/runner/ddos_callback_tests.go b/ssl/test/runner/ddos_callback_tests.go
new file mode 100644
index 0000000..ad292de
--- /dev/null
+++ b/ssl/test/runner/ddos_callback_tests.go
@@ -0,0 +1,73 @@
+// 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
+
+func addDDoSCallbackTests() {
+ // DDoS callback.
+ for _, resume := range []bool{false, true} {
+ suffix := "Resume"
+ if resume {
+ suffix = "No" + suffix
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-DDoS-OK-" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{"-install-ddos-callback"},
+ resumeSession: resume,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-DDoS-OK-" + suffix + "-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{"-install-ddos-callback"},
+ resumeSession: resume,
+ })
+
+ failFlag := "-fail-ddos-callback"
+ if resume {
+ failFlag = "-on-resume-fail-ddos-callback"
+ }
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-DDoS-Reject-" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{"-install-ddos-callback", failFlag},
+ resumeSession: resume,
+ shouldFail: true,
+ expectedError: ":CONNECTION_REJECTED:",
+ expectedLocalError: "remote error: internal error",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-DDoS-Reject-" + suffix + "-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{"-install-ddos-callback", failFlag},
+ resumeSession: resume,
+ shouldFail: true,
+ expectedError: ":CONNECTION_REJECTED:",
+ expectedLocalError: "remote error: internal error",
+ })
+ }
+}
diff --git a/ssl/test/runner/delegated_credential_tests.go b/ssl/test/runner/delegated_credential_tests.go
new file mode 100644
index 0000000..a39c3ed
--- /dev/null
+++ b/ssl/test/runner/delegated_credential_tests.go
@@ -0,0 +1,300 @@
+// 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"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/x509"
+ "fmt"
+ "time"
+
+ "golang.org/x/crypto/cryptobyte"
+)
+
+// delegatedCredentialConfig specifies the shape of a delegated credential, not
+// including the keys themselves.
+type delegatedCredentialConfig struct {
+ // lifetime is the amount of time, from the notBefore of the parent
+ // certificate, that the delegated credential is valid for. If zero, then 24
+ // hours is assumed.
+ lifetime time.Duration
+ // dcAlgo is the signature scheme that should be used with this delegated
+ // credential. If zero, ECDSA with P-256 is assumed.
+ dcAlgo signatureAlgorithm
+ // algo is the signature algorithm that the delegated credential itself is
+ // signed with. Cannot be zero.
+ algo signatureAlgorithm
+}
+
+func createDelegatedCredential(parent *Credential, config delegatedCredentialConfig) *Credential {
+ if parent.Type != CredentialTypeX509 {
+ panic("delegated credentials must be issued by X.509 credentials")
+ }
+
+ dcAlgo := config.dcAlgo
+ if dcAlgo == 0 {
+ dcAlgo = signatureECDSAWithP256AndSHA256
+ }
+
+ var dcPriv crypto.Signer
+ switch dcAlgo {
+ case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512:
+ dcPriv = &rsa2048Key
+
+ case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512:
+ var curve elliptic.Curve
+ switch dcAlgo {
+ case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256:
+ curve = elliptic.P256()
+ case signatureECDSAWithP384AndSHA384:
+ curve = elliptic.P384()
+ case signatureECDSAWithP521AndSHA512:
+ curve = elliptic.P521()
+ default:
+ panic("internal error")
+ }
+
+ priv, err := ecdsa.GenerateKey(curve, rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+ dcPriv = priv
+
+ default:
+ panic(fmt.Errorf("unsupported DC signature algorithm: %x", dcAlgo))
+ }
+
+ lifetime := config.lifetime
+ if lifetime == 0 {
+ lifetime = 24 * time.Hour
+ }
+ lifetimeSecs := int64(lifetime.Seconds())
+ if lifetimeSecs < 0 || lifetimeSecs > 1<<32 {
+ panic(fmt.Errorf("lifetime %s is too long to be expressed", lifetime))
+ }
+
+ // https://www.rfc-editor.org/rfc/rfc9345.html#section-4
+ dc := cryptobyte.NewBuilder(nil)
+ dc.AddUint32(uint32(lifetimeSecs))
+ dc.AddUint16(uint16(dcAlgo))
+
+ pubBytes, err := x509.MarshalPKIXPublicKey(dcPriv.Public())
+ if err != nil {
+ panic(err)
+ }
+ addUint24LengthPrefixedBytes(dc, pubBytes)
+
+ var dummyConfig Config
+ parentSignature, err := signMessage(false /* server */, VersionTLS13, parent.PrivateKey, &dummyConfig, config.algo, delegatedCredentialSignedMessage(dc.BytesOrPanic(), config.algo, parent.Leaf.Raw))
+ if err != nil {
+ panic(err)
+ }
+
+ dc.AddUint16(uint16(config.algo))
+ addUint16LengthPrefixedBytes(dc, parentSignature)
+
+ dcCred := *parent
+ dcCred.Type = CredentialTypeDelegated
+ dcCred.DelegatedCredential = dc.BytesOrPanic()
+ dcCred.PrivateKey = dcPriv
+ dcCred.KeyPath = writeTempKeyFile(dcPriv)
+ return &dcCred
+}
+
+func addDelegatedCredentialTests() {
+ p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
+ dcAlgo: signatureECDSAWithP256AndSHA256,
+ algo: signatureRSAPSSWithSHA256,
+ })
+ p256DCFromECDSA := createDelegatedCredential(&ecdsaP256Certificate, delegatedCredentialConfig{
+ dcAlgo: signatureECDSAWithP256AndSHA256,
+ algo: signatureECDSAWithP256AndSHA256,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-NoClientSupport",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ },
+ shimCredentials: []*Credential{p256DC, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-Basic",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ shimCredentials: []*Credential{p256DC, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "0"},
+ expectations: connectionExpectations{
+ peerCertificate: p256DC,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-ExactAlgorithmMatch",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ // Test that the server doesn't mix up the two signature algorithm
+ // fields. These options are a match because the signature_algorithms
+ // extension matches against the signature on the delegated
+ // credential, while the delegated_credential extension matches
+ // against the signature made by the delegated credential.
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ shimCredentials: []*Credential{p256DC, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "0"},
+ expectations: connectionExpectations{
+ peerCertificate: p256DC,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-SigAlgoMissing",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ // If the client doesn't support the signature in the delegated credential,
+ // the server should not use delegated credentials.
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA384},
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ shimCredentials: []*Credential{p256DC, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-CertVerifySigAlgoMissing",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ // If the client doesn't support the delegated credential's
+ // CertificateVerify algorithm, the server should not use delegated
+ // credentials.
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
+ },
+ shimCredentials: []*Credential{p256DC, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ })
+
+ // Delegated credentials are not supported at TLS 1.2, even if the client
+ // sends the extension.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-TLS12-Forbidden",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ },
+ shimCredentials: []*Credential{p256DC, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ })
+
+ // Generate another delegated credential, so we can get the keys out of sync.
+ dcWrongKey := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
+ algo: signatureRSAPSSWithSHA256,
+ })
+ dcWrongKey.DelegatedCredential = p256DC.DelegatedCredential
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-KeyMismatch",
+ // The handshake hints version of the test will, as a side effect, use a
+ // custom private key. Custom private keys can't be checked for key
+ // mismatches.
+ skipHints: true,
+ shimCredentials: []*Credential{dcWrongKey},
+ shouldFail: true,
+ expectedError: ":KEY_VALUES_MISMATCH:",
+ })
+
+ // RSA delegated credentials should be rejected at configuration time.
+ rsaDC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
+ algo: signatureRSAPSSWithSHA256,
+ dcAlgo: signatureRSAPSSWithSHA256,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-NoRSA",
+ shimCredentials: []*Credential{rsaDC},
+ shouldFail: true,
+ expectedError: ":INVALID_SIGNATURE_ALGORITHM:",
+ })
+
+ // If configured with multiple delegated credentials, the server can cleanly
+ // select the first one that works.
+ p384DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
+ dcAlgo: signatureECDSAWithP384AndSHA384,
+ algo: signatureRSAPSSWithSHA256,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-Multiple",
+ config: Config{
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
+ },
+ shimCredentials: []*Credential{p256DC, p384DC},
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: p384DC,
+ },
+ })
+
+ // Delegated credentials participate in issuer-based certificate selection.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DelegatedCredentials-MatchIssuer",
+ config: Config{
+ DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+ // The client requested p256DCFromECDSA's issuer.
+ RootCAs: makeCertPoolFromRoots(p256DCFromECDSA),
+ SendRootCAs: true,
+ },
+ shimCredentials: []*Credential{
+ p256DC.WithMustMatchIssuer(true), p256DCFromECDSA.WithMustMatchIssuer(true)},
+ flags: []string{"-expect-selected-credential", "1"},
+ expectations: connectionExpectations{
+ peerCertificate: p256DCFromECDSA,
+ },
+ })
+
+}
diff --git a/ssl/test/runner/dtls_tests.go b/ssl/test/runner/dtls_tests.go
new file mode 100644
index 0000000..8ad208d
--- /dev/null
+++ b/ssl/test/runner/dtls_tests.go
@@ -0,0 +1,1416 @@
+// 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 (
+ "slices"
+ "strconv"
+ "time"
+)
+
+func addDTLSReplayTests() {
+ for _, vers := range allVersions(dtls) {
+ // Test that sequence number replays are detected.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Replay-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ messageCount: 200,
+ replayWrites: true,
+ })
+
+ // Test the incoming sequence number skipping by values larger
+ // than the retransmit window.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Replay-LargeGaps-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ SequenceNumberMapping: func(in uint64) uint64 {
+ return in * 1023
+ },
+ },
+ },
+ messageCount: 200,
+ replayWrites: true,
+ })
+
+ // Test the incoming sequence number changing non-monotonically.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Replay-NonMonotonic-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ SequenceNumberMapping: func(in uint64) uint64 {
+ // This mapping has numbers counting backwards in groups
+ // of 256, and then jumping forwards 511 numbers.
+ return in ^ 255
+ },
+ },
+ },
+ // This messageCount is large enough to make sure that the SequenceNumberMapping
+ // will reach the point where it jumps forwards after stepping backwards.
+ messageCount: 500,
+ replayWrites: true,
+ })
+ }
+}
+
+// timeouts is the default retransmit schedule for BoringSSL. It doubles and
+// caps at 60 seconds. On the 13th timeout, it gives up.
+var timeouts = []time.Duration{
+ 400 * time.Millisecond,
+ 800 * time.Millisecond,
+ 1600 * time.Millisecond,
+ 3200 * time.Millisecond,
+ 6400 * time.Millisecond,
+ 12800 * time.Millisecond,
+ 25600 * time.Millisecond,
+ 51200 * time.Millisecond,
+ 60 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+}
+
+// shortTimeouts is an alternate set of timeouts which would occur if the
+// initial timeout duration was set to 250ms.
+var shortTimeouts = []time.Duration{
+ 250 * time.Millisecond,
+ 500 * time.Millisecond,
+ 1 * time.Second,
+ 2 * time.Second,
+ 4 * time.Second,
+ 8 * time.Second,
+ 16 * time.Second,
+ 32 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+ 60 * time.Second,
+}
+
+// dtlsPrevEpochExpiration is how long before the shim releases old epochs. Add
+// an extra second to allow the shim to be less precise.
+const dtlsPrevEpochExpiration = 4*time.Minute + 1*time.Second
+
+func addDTLSRetransmitTests() {
+ for _, shortTimeout := range []bool{false, true} {
+ for _, vers := range allVersions(dtls) {
+ suffix := "-" + vers.name
+ flags := []string{"-async"} // Retransmit tests require async.
+ useTimeouts := timeouts
+ if shortTimeout {
+ suffix += "-Short"
+ flags = append(flags, "-initial-timeout-duration-ms", "250")
+ useTimeouts = shortTimeouts
+ }
+
+ // Testing NewSessionTicket is tricky. First, BoringSSL sends two
+ // tickets in a row. These are conceptually separate flights, but we
+ // test them as one flight. Second, these tickets are sent
+ // concurrently with the runner's first test message. The shim's
+ // reply will come in before any retransmit challenges.
+ // handleNewSessionTicket corrects for both effects.
+ handleNewSessionTicket := func(f ACKFlightFunc) ACKFlightFunc {
+ if vers.version < VersionTLS13 {
+ return f
+ }
+ return func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // BoringSSL sends two NewSessionTickets in a row.
+ if received[0].Type == typeNewSessionTicket && len(received) < 2 {
+ c.MergeIntoNextFlight()
+ return
+ }
+ // NewSessionTicket is sent in parallel with the runner's
+ // first application data. Consume the shim's reply.
+ testMessage := makeTestMessage(0, 32)
+ if received[0].Type == typeNewSessionTicket {
+ c.ReadAppData(c.InEpoch(), expectedReply(testMessage))
+ }
+ // Run the test, without any stray messages in the way.
+ f(c, prev, received, records)
+ // The test loop is expecting a reply to the first message.
+ // Prime the shim to send it again.
+ if received[0].Type == typeNewSessionTicket {
+ c.WriteAppData(c.OutEpoch(), testMessage)
+ }
+ }
+ }
+
+ // In all versions, the sender will retransmit the whole flight if
+ // it times out and hears nothing.
+ writeFlightBasic := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ // Exercise every timeout but the last one (which would fail the
+ // connection).
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ c.ExpectNextTimeout(t)
+ c.AdvanceClock(t)
+ c.ReadRetransmit()
+ }
+ c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
+ }
+ // Finally release the whole flight to the shim.
+ c.WriteFlight(next)
+ }
+ ackFlightBasic := handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if vers.version >= VersionTLS13 {
+ // In DTLS 1.3, final flights (either handshake or post-handshake)
+ // are retransmited until ACKed. Exercise every timeout but
+ // the last one (which would fail the connection).
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ c.ExpectNextTimeout(t)
+ c.AdvanceClock(t)
+ c.ReadRetransmit()
+ }
+ c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
+ // Finally ACK the flight.
+ c.WriteACK(c.OutEpoch(), records)
+ return
+ }
+ // In DTLS 1.2, the final flight is retransmitted on receipt of
+ // the previous flight. Test the peer is willing to retransmit
+ // it several times.
+ for i := 0; i < 5; i++ {
+ c.WriteFlight(prev)
+ c.ReadRetransmit()
+ }
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Client-Basic" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: writeFlightBasic,
+ ACKFlightDTLS: ackFlightBasic,
+ },
+ },
+ resumeSession: true,
+ flags: flags,
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-Basic" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: writeFlightBasic,
+ ACKFlightDTLS: ackFlightBasic,
+ },
+ },
+ resumeSession: true,
+ flags: flags,
+ })
+
+ if vers.version <= VersionTLS12 {
+ // In DTLS 1.2, receiving a part of the next flight should not stop
+ // the retransmission timer.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-PartialProgress" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // Send a portion of the first message. The rest was lost.
+ msg := next[0]
+ split := len(msg.Data) / 2
+ c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
+ // If we time out, the shim should still retransmit. It knows
+ // we received the whole flight, but the shim should use a
+ // retransmit to request the runner try again.
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+ // "Retransmit" the rest of the flight. The shim should remember
+ // the portion that was already sent.
+ rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}
+ for _, m := range next[1:] {
+ rest = append(rest, m.Fragment(0, len(m.Data)))
+ }
+ c.WriteFragments(rest)
+ },
+ },
+ },
+ flags: flags,
+ })
+ } else {
+ // In DTLS 1.3, receiving a part of the next flight implicitly ACKs
+ // the previous flight.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "DTLS-Retransmit-PartialProgress-Server" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) == 0 && next[0].Type == typeClientHello {
+ // Send the initial ClientHello as-is.
+ c.WriteFlight(next)
+ return
+ }
+
+ // Send a portion of the first message. The rest was lost.
+ msg := next[0]
+ split := len(msg.Data) / 2
+ c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
+ // After waiting the current timeout, the shim should ACK
+ // the partial flight.
+ c.ExpectNextTimeout(useTimeouts[0] / 4)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+ // The partial flight is enough to ACK the previous flight.
+ // The shim should stop retransmitting and even stop the
+ // retransmit timer.
+ c.ExpectNoNextTimeout()
+ for _, t := range useTimeouts {
+ c.AdvanceClock(t)
+ }
+ // "Retransmit" the rest of the flight. The shim should remember
+ // the portion that was already sent.
+ rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}
+ for _, m := range next[1:] {
+ rest = append(rest, m.Fragment(0, len(m.Data)))
+ }
+ c.WriteFragments(rest)
+ },
+ },
+ },
+ flags: flags,
+ })
+
+ // When the shim is a client, receiving fragments before the version is
+ // known does not trigger this behavior.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-PartialProgress-Client" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ msg := next[0]
+ if msg.Type != typeServerHello {
+ // Post-handshake is tested separately.
+ c.WriteFlight(next)
+ return
+ }
+ // Send a portion of the ServerHello. The rest was lost.
+ split := len(msg.Data) / 2
+ c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
+
+ // The shim did not know this was DTLS 1.3, so it still
+ // retransmits ClientHello.
+ c.ExpectNextTimeout(useTimeouts[0])
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+
+ // Finish the ServerHello. The version is still not known,
+ // at the time the ServerHello fragment is processed, This
+ // is not as efficient as we could be; we could go back and
+ // implicitly ACK once the version is known. But the last
+ // byte of ServerHello will almost certainly be in the same
+ // packet as EncryptedExtensions, which will trigger the case
+ // below.
+ c.WriteFragments([]DTLSFragment{msg.Fragment(split, len(msg.Data)-split)})
+ c.ExpectNextTimeout(useTimeouts[1])
+ c.AdvanceClock(useTimeouts[1])
+ c.ReadRetransmit()
+
+ // Send EncryptedExtensions. The shim now knows the version.
+ c.WriteFragments([]DTLSFragment{next[1].Fragment(0, len(next[1].Data))})
+
+ // The shim should ACK the partial flight. The shim hasn't
+ // gotten to epoch 3 yet, so the ACK will come in epoch 2.
+ c.AdvanceClock(useTimeouts[2] / 4)
+ c.ReadACK(uint16(encryptionHandshake))
+
+ // This is enough to ACK the previous flight. The shim
+ // should stop retransmitting and even stop the timer.
+ c.ExpectNoNextTimeout()
+ for _, t := range useTimeouts[2:] {
+ c.AdvanceClock(t)
+ }
+
+ // "Retransmit" the rest of the flight. The shim should remember
+ // the portion that was already sent.
+ var rest []DTLSFragment
+ for _, m := range next[2:] {
+ rest = append(rest, m.Fragment(0, len(m.Data)))
+ }
+ c.WriteFragments(rest)
+ },
+ },
+ },
+ flags: flags,
+ })
+ }
+
+ // Test that exceeding the timeout schedule hits a read
+ // timeout.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Timeout" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ c.ExpectNextTimeout(t)
+ c.AdvanceClock(t)
+ c.ReadRetransmit()
+ }
+ c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
+ c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
+ // The shim should give up at this point.
+ },
+ },
+ },
+ resumeSession: true,
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":READ_TIMEOUT_EXPIRED:",
+ })
+
+ // Test that timeout handling has a fudge factor, due to API
+ // problems.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Fudge" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ c.ExpectNextTimeout(useTimeouts[0])
+ c.AdvanceClock(useTimeouts[0] - 10*time.Millisecond)
+ c.ReadRetransmit()
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ resumeSession: true,
+ flags: flags,
+ })
+
+ // Test that the shim can retransmit at different MTUs.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-ChangeMTU" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ // Request a client certificate, so the shim has more to send.
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ for i, mtu := range []int{300, 301, 302, 303, 299, 298, 297} {
+ c.SetMTU(mtu)
+ c.AdvanceClock(useTimeouts[i])
+ c.ReadRetransmit()
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: flags,
+ })
+
+ // DTLS 1.3 uses explicit ACKs.
+ if vers.version >= VersionTLS13 {
+ // The two server flights (HelloRetryRequest and ServerHello..Finished)
+ // happen after the shim has learned the version, so they are more
+ // straightforward. In these tests, we trigger HelloRetryRequest,
+ // and also use ML-KEM with rsaChainCertificate and a limited MTU,
+ // to increase the number of records and exercise more complex
+ // ACK patterns.
+
+ // After ACKing everything, the shim should stop retransmitting.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKEverything" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaChainCertificate,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768},
+ DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ // Send smaller packets to exercise more ACK cases.
+ MaxPacketLength: 512,
+ MaxHandshakeRecordLength: 512,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ ackEpoch := received[len(received)-1].Epoch
+ c.ExpectNextTimeout(useTimeouts[0])
+ c.WriteACK(ackEpoch, records)
+ // After everything is ACKed, the shim should stop the timer
+ // and wait for the next flight.
+ c.ExpectNoNextTimeout()
+ for _, t := range useTimeouts {
+ c.AdvanceClock(t)
+ }
+ }
+ c.WriteFlight(next)
+ },
+ ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ ackEpoch := received[len(received)-1].Epoch
+ c.ExpectNextTimeout(useTimeouts[0])
+ c.WriteACK(ackEpoch, records)
+ // After everything is ACKed, the shim should stop the timer.
+ c.ExpectNoNextTimeout()
+ for _, t := range useTimeouts {
+ c.AdvanceClock(t)
+ }
+ }),
+ SequenceNumberMapping: func(in uint64) uint64 {
+ // Perturb sequence numbers to test that ACKs are sorted.
+ return in ^ 63
+ },
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: slices.Concat(flags, []string{
+ "-mtu", "512",
+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
+ // Request a client certificate so the client final flight is
+ // larger.
+ "-require-any-client-certificate",
+ }),
+ })
+
+ // ACK packets one by one, in reverse.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKReverse" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768},
+ DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 512,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ ackEpoch := received[len(received)-1].Epoch
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]})
+ }
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }
+ c.WriteFlight(next)
+ },
+ ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ ackEpoch := received[len(received)-1].Epoch
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]})
+ }
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }),
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
+ })
+
+ // ACK packets one by one, forwards.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKForwards" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768},
+ DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 512,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ ackEpoch := received[len(received)-1].Epoch
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
+ }
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }
+ c.WriteFlight(next)
+ },
+ ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ ackEpoch := received[len(received)-1].Epoch
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
+ }
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }),
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
+ })
+
+ // ACK 1/3 the packets each time.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKIterate" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768},
+ DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 512,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ ackEpoch := received[len(received)-1].Epoch
+ for i, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ ack := make([]DTLSRecordNumberInfo, 0, (len(records)+2)/3)
+ for i := 0; i < len(records); i += 3 {
+ ack = append(ack, records[i])
+ }
+ c.WriteACK(ackEpoch, ack)
+ }
+ // Change the MTU every iteration, to make the fragment
+ // patterns more complex.
+ c.SetMTU(512 + i)
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }
+ c.WriteFlight(next)
+ },
+ ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ ackEpoch := received[len(received)-1].Epoch
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
+ }
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }),
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
+ })
+
+ // ACKing packets that have already been ACKed is a no-op.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKDuplicate" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte("cookie"),
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ ackEpoch := received[len(received)-1].Epoch
+ // Keep ACKing the same record over and over.
+ c.WriteACK(ackEpoch, records[:1])
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+ c.WriteACK(ackEpoch, records[:1])
+ c.AdvanceClock(useTimeouts[1])
+ c.ReadRetransmit()
+ }
+ c.WriteFlight(next)
+ },
+ ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ ackEpoch := received[len(received)-1].Epoch
+ // Keep ACKing the same record over and over.
+ c.WriteACK(ackEpoch, records[:1])
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+ c.WriteACK(ackEpoch, records[:1])
+ c.AdvanceClock(useTimeouts[1])
+ c.ReadRetransmit()
+ // ACK everything to clear the timer.
+ c.WriteACK(ackEpoch, records)
+ }),
+ },
+ },
+ flags: flags,
+ })
+
+ // When ACKing ServerHello..Finished, the ServerHello might be
+ // ACKed at epoch 0 or epoch 2, depending on how far the client
+ // received. Test that epoch 0 is allowed by ACKing each packet
+ // at the record it was received.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKMatchingEpoch" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ if len(records) > 0 {
+ c.WriteACK(uint16(records[0].Epoch), []DTLSRecordNumberInfo{records[0]})
+ }
+ c.AdvanceClock(t)
+ records = c.ReadRetransmit()
+ }
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ flags: flags,
+ })
+
+ // However, records in the handshake may not be ACKed at lower
+ // epoch than they were received.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKBadEpoch" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) == 0 {
+ // Send the ClientHello.
+ c.WriteFlight(next)
+ } else {
+ // Try to ACK ServerHello..Finished at epoch 0. The shim should reject this.
+ c.WriteACK(0, records)
+ }
+ },
+ },
+ },
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ // The bad epoch check should notice when the epoch number
+ // would overflow 2^16.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKEpochOverflow" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) == 0 {
+ // Send the ClientHello.
+ c.WriteFlight(next)
+ } else {
+ r := records[0]
+ r.Epoch += 1 << 63
+ c.WriteACK(0, []DTLSRecordNumberInfo{r})
+ }
+ },
+ },
+ },
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ // ACK some records from the first transmission, trigger a
+ // retransmit, but then ACK the rest of the first transmission.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKOldRecords" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768},
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 512,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ ackEpoch := received[len(received)-1].Epoch
+ c.WriteACK(ackEpoch, records[len(records)/2:])
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+ c.WriteACK(ackEpoch, records[:len(records)/2])
+ // Everything should be ACKed now. The shim should not
+ // retransmit anything.
+ c.AdvanceClock(useTimeouts[1])
+ c.AdvanceClock(useTimeouts[2])
+ c.AdvanceClock(useTimeouts[3])
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
+ })
+
+ // If the shim sends too many records, it will eventually forget them.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKForgottenRecords" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ CurvePreferences: []CurveID{CurveX25519MLKEM768},
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 256,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) > 0 {
+ // Make the peer retransmit many times, with a small MTU.
+ for _, t := range useTimeouts[:len(useTimeouts)-2] {
+ c.AdvanceClock(t)
+ c.ReadRetransmit()
+ }
+ // ACK the first record the shim ever sent. It will have
+ // fallen off the queue by now, so it is expected to not
+ // impact the shim's retransmissions.
+ c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: records[0].DTLSRecordNumber}})
+ c.AdvanceClock(useTimeouts[len(useTimeouts)-2])
+ c.ReadRetransmit()
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ flags: slices.Concat(flags, []string{"-mtu", "256", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
+ })
+
+ // The shim should ignore ACKs for a previous flight, and not get its
+ // internal state confused.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKPreviousFlight" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ DefaultCurves: []CurveID{}, // Force a HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[len(next)-1].Type == typeFinished {
+ // We are now sending client Finished, in response
+ // to the shim's ServerHello. ACK the shim's first
+ // record, which would have been part of
+ // HelloRetryRequest. This should not impact retransmit.
+ c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: DTLSRecordNumber{Epoch: 0, Sequence: 0}}})
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+ }
+ c.WriteFlight(next)
+ },
+ },
+ },
+ flags: flags,
+ })
+
+ // Records that contain a mix of discarded and processed fragments should
+ // not be ACKed.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-DoNotACKDiscardedFragments" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ DefaultCurves: []CurveID{}, // Force a HelloRetryRequest.
+ Bugs: ProtocolBugs{
+ PackHandshakeFragments: 4096,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // Send the flight, but combine every fragment with a far future
+ // fragment, which the shim will discard. During the handshake,
+ // the shim has enough information to reject this entirely, but
+ // that would require coordinating with the handshake state
+ // machine. Instead, BoringSSL discards the fragment and skips
+ // ACKing the packet.
+ //
+ // runner implicitly tests that the shim ACKs the Finished flight
+ // (or, in case, that it is does not), so this exercises the final
+ // ACK.
+ for _, msg := range next {
+ shouldDiscard := DTLSFragment{Epoch: msg.Epoch, Sequence: 1000, ShouldDiscard: true}
+ c.WriteFragments([]DTLSFragment{shouldDiscard, msg.Fragment(0, len(msg.Data))})
+ // The shim has nothing to ACK and thus no ACK timer (which
+ // would be 1/4 of this value).
+ c.ExpectNextTimeout(useTimeouts[0])
+ }
+ },
+ },
+ },
+ flags: flags,
+ })
+
+ // The server must continue to ACK the Finished flight even after
+ // receiving application data from the client.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-Retransmit-Server-ACKFinishedAfterAppData" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ // WriteFlightDTLS will handle consuming ACKs.
+ SkipImplicitACKRead: true,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[len(next)-1].Type != typeFinished {
+ c.WriteFlight(next)
+ return
+ }
+
+ // Write Finished. The shim should ACK it immediately.
+ c.WriteFlight(next)
+ c.ReadACK(c.InEpoch())
+
+ // Exchange some application data.
+ msg := []byte("hello")
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // Act as if the ACK was dropped and retransmit Finished.
+ // The shim should process the retransmit from epoch 2 and
+ // ACK, although it has already received data at epoch 3.
+ c.WriteFlight(next)
+ ackTimeout := useTimeouts[0] / 4
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // Partially retransmit Finished. The shim should continue
+ // to ACK.
+ c.WriteFragments([]DTLSFragment{next[0].Fragment(0, 1)})
+ c.WriteFragments([]DTLSFragment{next[0].Fragment(1, 1)})
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // Eventually, the shim assumes we have received the ACK
+ // and drops epoch 2. Retransmits now go unanswered.
+ c.AdvanceClock(dtlsPrevEpochExpiration)
+ c.WriteFlight(next)
+ },
+ },
+ },
+ // Disable tickets on the shim to avoid NewSessionTicket
+ // interfering with the test callback.
+ flags: slices.Concat(flags, []string{"-no-ticket"}),
+ })
+
+ // As a client, the shim must tolerate ACKs in response to its
+ // initial ClientHello, but it will not process them because the
+ // version is not yet known. The second ClientHello, in response
+ // to HelloRetryRequest, however, is ACKed.
+ //
+ // The shim must additionally process ACKs and retransmit its
+ // Finished flight, possibly interleaved with application data.
+ // (The server may send half-RTT data without Finished.)
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Client" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ // Require a client certificate, so the Finished flight
+ // is large.
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte("cookie"), // Send HelloRetryRequest
+ MaxPacketLength: 512,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if len(received) == 0 || received[0].Type != typeClientHello {
+ // We test post-handshake flights separately.
+ c.WriteFlight(next)
+ return
+ }
+
+ // This is either HelloRetryRequest in response to ClientHello1,
+ // or ServerHello..Finished in response to ClientHello2.
+ first := records[0]
+ if len(prev) == 0 {
+ // This is HelloRetryRequest in response to ClientHello1. The client
+ // will accept the ACK, but it will ignore it. Do not expect
+ // retransmits to be impacted.
+ first.MessageStartSequence = 0
+ first.MessageStartOffset = 0
+ first.MessageEndSequence = 0
+ first.MessageEndOffset = 0
+ }
+ c.WriteACK(0, []DTLSRecordNumberInfo{first})
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+ c.WriteFlight(next)
+ },
+ ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // The shim will process application data without an ACK.
+ msg := []byte("hello")
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // After a timeout, the shim will retransmit Finished.
+ c.AdvanceClock(useTimeouts[0])
+ c.ReadRetransmit()
+
+ // Application data still flows.
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // ACK part of the flight and check that retransmits
+ // are updated.
+ c.WriteACK(c.OutEpoch(), records[len(records)/3:2*len(records)/3])
+ c.AdvanceClock(useTimeouts[1])
+ records = c.ReadRetransmit()
+
+ // ACK the rest. Retransmits should stop.
+ c.WriteACK(c.OutEpoch(), records)
+ for _, t := range useTimeouts[2:] {
+ c.AdvanceClock(t)
+ }
+ },
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
+ })
+
+ // If the client never receives an ACK for the Finished flight, it
+ // is eventually fatal.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Client-FinishedTimeout" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ c.AdvanceClock(t)
+ c.ReadRetransmit()
+ }
+ c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
+ },
+ },
+ },
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":READ_TIMEOUT_EXPIRED:",
+ })
+
+ // Neither post-handshake messages nor application data implicitly
+ // ACK the Finished flight. The server may have sent either in
+ // half-RTT data. Test that the client continues to retransmit
+ // despite this.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Client-NoImplictACKFinished" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // Merge the Finished flight into the NewSessionTicket.
+ c.MergeIntoNextFlight()
+ },
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[0].Type != typeNewSessionTicket {
+ c.WriteFlight(next)
+ return
+ }
+ if len(received) == 0 || received[0].Type != typeFinished {
+ panic("Finished should be merged with NewSessionTicket")
+ }
+ // Merge NewSessionTicket into the KeyUpdate.
+ if next[len(next)-1].Type != typeKeyUpdate {
+ c.MergeIntoNextFlight()
+ return
+ }
+
+ // Write NewSessionTicket and the KeyUpdate and
+ // read the ACK.
+ c.WriteFlight(next)
+ ackTimeout := useTimeouts[0] / 4
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // The retransmit timer is still running.
+ c.AdvanceClock(useTimeouts[0] - ackTimeout)
+ c.ReadRetransmit()
+
+ // Application data can flow at the old epoch.
+ msg := []byte("test")
+ c.WriteAppData(c.OutEpoch()-1, msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // The retransmit timer is still running.
+ c.AdvanceClock(useTimeouts[1])
+ c.ReadRetransmit()
+
+ // Advance the shim to the next epoch.
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // The retransmit timer is still running. The shim
+ // actually could implicitly ACK at this point, but
+ // RFC 9147 does not list this as an implicit ACK.
+ c.AdvanceClock(useTimeouts[2])
+ c.ReadRetransmit()
+
+ // Finally ACK the final flight. Now the shim will
+ // stop the timer.
+ c.WriteACK(c.OutEpoch(), records)
+ c.ExpectNoNextTimeout()
+ },
+ },
+ },
+ sendKeyUpdates: 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ flags: flags,
+ })
+
+ // If the server never receives an ACK for NewSessionTicket, it
+ // is eventually fatal.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "DTLS-Retransmit-Server-NewSessionTicketTimeout" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if received[0].Type != typeNewSessionTicket {
+ c.WriteACK(c.OutEpoch(), records)
+ return
+ }
+ // Time the peer out.
+ for _, t := range useTimeouts[:len(useTimeouts)-1] {
+ c.AdvanceClock(t)
+ c.ReadRetransmit()
+ }
+ c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
+ }),
+ },
+ },
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":READ_TIMEOUT_EXPIRED:",
+ })
+
+ // If generating the reply to a flight takes time (generating a
+ // CertificateVerify for a client certificate), the shim should
+ // send an ACK.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-SlowReplyGeneration" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ c.WriteFlight(next)
+ if next[0].Type == typeServerHello {
+ // The shim will reply with Certificate..Finished, but
+ // take time to do so. In that time, it should schedule
+ // an ACK so the runner knows not to retransmit.
+ c.ReadACK(c.InEpoch())
+ }
+ },
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ // Simulate it taking time to generate the reply.
+ flags: slices.Concat(flags, []string{"-private-key-delay-ms", strconv.Itoa(int(useTimeouts[0].Milliseconds()))}),
+ })
+
+ // BoringSSL's ACK policy may schedule both retransmit and ACK
+ // timers in parallel.
+ //
+ // TODO(crbug.com/42290594): This is only possible during the
+ // handshake because we're willing to ACK old flights without
+ // trying to distinguish these cases. However, post-handshake
+ // messages will exercise this, so that may be a better version
+ // of this test. In-handshake, it's kind of a waste to ACK this,
+ // so maybe we should stop.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-BothTimers" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ // Arrange for there to be two server flights.
+ SendHelloRetryRequestCookie: []byte("cookie"),
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[0].Sequence == 0 || next[0].Type != typeServerHello {
+ // Send the first flight (HelloRetryRequest) as-is,
+ // as well as any post-handshake flights.
+ c.WriteFlight(next)
+ return
+ }
+
+ // The shim just send the ClientHello2 and is
+ // waiting for ServerHello..Finished. If it hears
+ // nothing, it will retransmit ClientHello2 on the
+ // assumption the packet was lost.
+ c.ExpectNextTimeout(useTimeouts[0])
+
+ // Retransmit a portion of HelloRetryRequest.
+ c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)})
+
+ // The shim does not actually need to ACK this,
+ // but BoringSSL does. Now both timers are active.
+ // Fire the first...
+ c.ExpectNextTimeout(useTimeouts[0] / 4)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(0)
+
+ // ...followed by the second.
+ c.ExpectNextTimeout(3 * useTimeouts[0] / 4)
+ c.AdvanceClock(3 * useTimeouts[0] / 4)
+ c.ReadRetransmit()
+
+ // The shim is now set for the next retransmit.
+ c.ExpectNextTimeout(useTimeouts[1])
+
+ // Start the ACK timer again.
+ c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)})
+ c.ExpectNextTimeout(useTimeouts[1] / 4)
+
+ // Expire both timers at once.
+ c.AdvanceClock(useTimeouts[1])
+ c.ReadACK(0)
+ c.ReadRetransmit()
+
+ c.WriteFlight(next)
+ },
+ },
+ },
+ flags: flags,
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Client-ACKPostHandshake" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[0].Type != typeNewSessionTicket {
+ c.WriteFlight(next)
+ return
+ }
+
+ // The test should try to send two NewSessionTickets in a row.
+ if len(next) != 2 {
+ panic("unexpected message count")
+ }
+
+ // Send part of first ticket post-handshake message.
+ first0, second0 := next[0].Split(len(next[0].Data) / 2)
+ first1, second1 := next[1].Split(len(next[1].Data) / 2)
+ c.WriteFragments([]DTLSFragment{first0})
+
+ // The shim should ACK on a timer.
+ c.ExpectNextTimeout(useTimeouts[0] / 4)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+
+ // The shim is just waiting for us to retransmit.
+ c.ExpectNoNextTimeout()
+
+ // Send some more fragments.
+ c.WriteFragments([]DTLSFragment{first0, second1})
+
+ // The shim should ACK, again on a timer.
+ c.ExpectNextTimeout(useTimeouts[0] / 4)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+ c.ExpectNoNextTimeout()
+
+ // Finish up both messages. We implicitly test if shim
+ // processed these messages by checking that it returned a new
+ // session.
+ c.WriteFragments([]DTLSFragment{first1, second0})
+
+ // The shim should ACK again, once the timer expires.
+ //
+ // TODO(crbug.com/42290594): Should the shim ACK immediately?
+ // Otherwise KeyUpdates are delayed, which will complicated
+ // downstream testing.
+ c.ExpectNextTimeout(useTimeouts[0] / 4)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+ c.ExpectNoNextTimeout()
+ },
+ },
+ },
+ flags: flags,
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS-Retransmit-Client-ACKPostHandshakeTwice" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[0].Type != typeNewSessionTicket {
+ c.WriteFlight(next)
+ return
+ }
+
+ // The test should try to send two NewSessionTickets in a row.
+ if len(next) != 2 {
+ panic("unexpected message count")
+ }
+
+ // Send the flight. The shim should ACK it.
+ c.WriteFlight(next)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+ c.ExpectNoNextTimeout()
+
+ // Retransmit the flight, as if we lost the ACK. The shim should
+ // ACK again.
+ c.WriteFlight(next)
+ c.AdvanceClock(useTimeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+ c.ExpectNoNextTimeout()
+ },
+ },
+ },
+ flags: flags,
+ })
+ }
+ }
+ }
+
+ // Test that the final Finished retransmitting isn't
+ // duplicated if the peer badly fragments everything.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "DTLS-RetransmitFinished-Fragmented",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ MaxHandshakeRecordLength: 2,
+ ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ c.WriteFlight(prev)
+ c.ReadRetransmit()
+ },
+ },
+ },
+ flags: []string{"-async"},
+ })
+
+ // If the shim sends the last Finished (server full or client resume
+ // handshakes), it must retransmit that Finished when it sees a
+ // post-handshake penultimate Finished from the runner. The above tests
+ // cover this. Conversely, if the shim sends the penultimate Finished
+ // (client full or server resume), test that it does not retransmit.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: clientTest,
+ name: "DTLS-StrayRetransmitFinished-ClientFull",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ c.WriteFlight(next)
+ for _, msg := range next {
+ if msg.Type == typeFinished {
+ c.WriteFlight([]DTLSMessage{msg})
+ }
+ }
+ },
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "DTLS-StrayRetransmitFinished-ServerResume",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ c.WriteFlight(next)
+ for _, msg := range next {
+ if msg.Type == typeFinished {
+ c.WriteFlight([]DTLSMessage{msg})
+ }
+ }
+ },
+ },
+ },
+ resumeSession: true,
+ })
+}
+
+func addDTLSReorderTests() {
+ for _, vers := range allVersions(dtls) {
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "ReorderHandshakeFragments-Small-DTLS-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ ReorderHandshakeFragments: true,
+ // Small enough that every handshake message is
+ // fragmented.
+ MaxHandshakeRecordLength: 2,
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "ReorderHandshakeFragments-Large-DTLS-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ ReorderHandshakeFragments: true,
+ // Large enough that no handshake message is
+ // fragmented.
+ MaxHandshakeRecordLength: 2048,
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "MixCompleteMessageWithFragments-DTLS-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ ReorderHandshakeFragments: true,
+ MixCompleteMessageWithFragments: true,
+ MaxHandshakeRecordLength: 2,
+ },
+ },
+ })
+ }
+}
diff --git a/ssl/test/runner/ech_tests.go b/ssl/test/runner/ech_tests.go
new file mode 100644
index 0000000..0b7eb64
--- /dev/null
+++ b/ssl/test/runner/ech_tests.go
@@ -0,0 +1,2436 @@
+// 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},
+ })
+ }
+}
diff --git a/ssl/test/runner/ems_tests.go b/ssl/test/runner/ems_tests.go
new file mode 100644
index 0000000..16ce0b6
--- /dev/null
+++ b/ssl/test/runner/ems_tests.go
@@ -0,0 +1,199 @@
+// 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
+
+func addExtendedMasterSecretTests() {
+ const expectEMSFlag = "-expect-extended-master-secret"
+
+ for _, with := range []bool{false, true} {
+ prefix := "No"
+ if with {
+ prefix = ""
+ }
+
+ for _, isClient := range []bool{false, true} {
+ suffix := "-Server"
+ testType := serverTest
+ if isClient {
+ suffix = "-Client"
+ testType = clientTest
+ }
+
+ for _, ver := range tlsVersions {
+ // In TLS 1.3, the extension is irrelevant and
+ // always reports as enabled.
+ var flags []string
+ if with || ver.version >= VersionTLS13 {
+ flags = []string{expectEMSFlag}
+ }
+
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: prefix + "ExtendedMasterSecret-" + ver.name + suffix,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: !with,
+ RequireExtendedMasterSecret: with,
+ },
+ },
+ flags: flags,
+ })
+ }
+ }
+ }
+
+ for _, isClient := range []bool{false, true} {
+ for _, supportedInFirstConnection := range []bool{false, true} {
+ for _, supportedInResumeConnection := range []bool{false, true} {
+ boolToWord := func(b bool) string {
+ if b {
+ return "Yes"
+ }
+ return "No"
+ }
+ suffix := boolToWord(supportedInFirstConnection) + "To" + boolToWord(supportedInResumeConnection) + "-"
+ if isClient {
+ suffix += "Client"
+ } else {
+ suffix += "Server"
+ }
+
+ supportedConfig := Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ RequireExtendedMasterSecret: true,
+ },
+ }
+
+ noSupportConfig := Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: true,
+ },
+ }
+
+ test := testCase{
+ name: "ExtendedMasterSecret-" + suffix,
+ resumeSession: true,
+ }
+
+ if !isClient {
+ test.testType = serverTest
+ }
+
+ if supportedInFirstConnection {
+ test.config = supportedConfig
+ } else {
+ test.config = noSupportConfig
+ }
+
+ if supportedInResumeConnection {
+ test.resumeConfig = &supportedConfig
+ } else {
+ test.resumeConfig = &noSupportConfig
+ }
+
+ switch suffix {
+ case "YesToYes-Client", "YesToYes-Server":
+ // When a session is resumed, it should
+ // still be aware that its master
+ // secret was generated via EMS and
+ // thus it's safe to use tls-unique.
+ test.flags = []string{expectEMSFlag}
+ case "NoToYes-Server":
+ // If an original connection did not
+ // contain EMS, but a resumption
+ // handshake does, then a server should
+ // not resume the session.
+ test.expectResumeRejected = true
+ case "YesToNo-Server":
+ // Resuming an EMS session without the
+ // EMS extension should cause the
+ // server to abort the connection.
+ test.shouldFail = true
+ test.expectedError = ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"
+ case "NoToYes-Client":
+ // A client should abort a connection
+ // where the server resumed a non-EMS
+ // session but echoed the EMS
+ // extension.
+ test.shouldFail = true
+ test.expectedError = ":RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION:"
+ case "YesToNo-Client":
+ // A client should abort a connection
+ // where the server didn't echo EMS
+ // when the session used it.
+ test.shouldFail = true
+ test.expectedError = ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"
+ }
+
+ testCases = append(testCases, test)
+ }
+ }
+ }
+
+ // Switching EMS on renegotiation is forbidden.
+ testCases = append(testCases, testCase{
+ name: "ExtendedMasterSecret-Renego-NoEMS",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: true,
+ NoExtendedMasterSecretOnRenegotiation: true,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ExtendedMasterSecret-Renego-Upgrade",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: true,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_EMS_MISMATCH:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ExtendedMasterSecret-Renego-Downgrade",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecretOnRenegotiation: true,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_EMS_MISMATCH:",
+ })
+}
diff --git a/ssl/test/runner/end_of_flight_tests.go b/ssl/test/runner/end_of_flight_tests.go
new file mode 100644
index 0000000..f563b1f
--- /dev/null
+++ b/ssl/test/runner/end_of_flight_tests.go
@@ -0,0 +1,138 @@
+// 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
+
+// addEndOfFlightTests adds tests where the runner adds extra data in the final
+// record of each handshake flight. Depending on the implementation strategy,
+// this data may be carried over to the next flight (assuming no key change) or
+// may be rejected. To avoid differences with split handshakes and generally
+// reject misbehavior, BoringSSL treats this as an error. When possible, these
+// tests pull the extra data from the subsequent flight to distinguish the data
+// being carried over from a general syntax error.
+//
+// These tests are similar to tests in |addChangeCipherSpecTests| that send
+// extra data at key changes. Not all key changes are at the end of a flight and
+// not all flights end at a key change.
+func addEndOfFlightTests() {
+ // TLS 1.3 client handshakes.
+ //
+ // Data following the second TLS 1.3 ClientHello is covered by
+ // PartialClientFinishedWithClientHello,
+ // PartialClientFinishedWithSecondClientHello, and
+ // PartialEndOfEarlyDataWithClientHello in |addChangeCipherSpecTests|.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PartialSecondClientHelloAfterFirst",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Trigger a curve-based HelloRetryRequest.
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ PartialSecondClientHelloAfterFirst: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // TLS 1.3 server handshakes.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "PartialServerHelloWithHelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ PartialServerHelloWithHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // TLS 1.2 client handshakes.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "PartialClientKeyExchangeWithClientHello",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ PartialClientKeyExchangeWithClientHello: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // TLS 1.2 server handshakes.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "PartialNewSessionTicketWithServerHelloDone",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ PartialNewSessionTicketWithServerHelloDone: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ for _, vers := range tlsVersions {
+ for _, testType := range []testType{clientTest, serverTest} {
+ suffix := "-Client"
+ if testType == serverTest {
+ suffix = "-Server"
+ }
+ suffix += "-" + vers.name
+
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "TrailingDataWithFinished" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ TrailingDataWithFinished: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "TrailingDataWithFinished-Resume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ TrailingDataWithFinished: true,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ }
+ }
+}
diff --git a/ssl/test/runner/export_tests.go b/ssl/test/runner/export_tests.go
new file mode 100644
index 0000000..a790f06
--- /dev/null
+++ b/ssl/test/runner/export_tests.go
@@ -0,0 +1,214 @@
+// 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
+
+func addExportKeyingMaterialTests() {
+ for _, vers := range tlsVersions {
+ testCases = append(testCases, testCase{
+ name: "ExportKeyingMaterial-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ // Test the exporter in both initial and resumption
+ // handshakes.
+ resumeSession: true,
+ exportKeyingMaterial: 1024,
+ exportLabel: "label",
+ exportContext: "context",
+ useExportContext: true,
+ })
+ testCases = append(testCases, testCase{
+ name: "ExportKeyingMaterial-NoContext-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ exportKeyingMaterial: 1024,
+ })
+ testCases = append(testCases, testCase{
+ name: "ExportKeyingMaterial-EmptyContext-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ exportKeyingMaterial: 1024,
+ useExportContext: true,
+ })
+ testCases = append(testCases, testCase{
+ name: "ExportKeyingMaterial-Small-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ exportKeyingMaterial: 1,
+ exportLabel: "label",
+ exportContext: "context",
+ useExportContext: true,
+ })
+
+ if vers.version >= VersionTLS13 {
+ // Test the exporters do not work while the client is
+ // sending 0-RTT data.
+ testCases = append(testCases, testCase{
+ name: "NoEarlyKeyingMaterial-Client-InEarlyData-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-on-resume-export-keying-material", "1024",
+ "-on-resume-export-label", "label",
+ "-on-resume-export-context", "context",
+ },
+ shouldFail: true,
+ expectedError: ":HANDSHAKE_NOT_COMPLETE:",
+ })
+
+ // Test the normal exporter on the server in half-RTT.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ExportKeyingMaterial-Server-HalfRTT-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ // The shim writes exported data immediately after
+ // the handshake returns, so disable the built-in
+ // early data test.
+ SendEarlyData: [][]byte{},
+ ExpectHalfRTTData: [][]byte{},
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ exportKeyingMaterial: 1024,
+ exportLabel: "label",
+ exportContext: "context",
+ useExportContext: true,
+ })
+ }
+ }
+
+ // Exporters work during a False Start.
+ testCases = append(testCases, testCase{
+ name: "ExportKeyingMaterial-FalseStart",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-advertise-alpn", "\x03foo",
+ "-expect-alpn", "foo",
+ },
+ shimWritesFirst: true,
+ exportKeyingMaterial: 1024,
+ exportLabel: "label",
+ exportContext: "context",
+ useExportContext: true,
+ })
+
+ // Exporters do not work in the middle of a renegotiation. Test this by
+ // triggering the exporter after every SSL_read call and configuring the
+ // shim to run asynchronously.
+ testCases = append(testCases, testCase{
+ name: "ExportKeyingMaterial-Renegotiate",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-async",
+ "-use-exporter-between-reads",
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shouldFail: true,
+ expectedError: "failed to export keying material",
+ })
+}
+
+func addExportTrafficSecretsTests() {
+ for _, cipherSuite := range []testCipherSuite{
+ // Test a SHA-256 and SHA-384 based cipher suite.
+ {"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256},
+ {"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384},
+ } {
+ testCases = append(testCases, testCase{
+ name: "ExportTrafficSecrets-" + cipherSuite.name,
+ config: Config{
+ MinVersion: VersionTLS13,
+ CipherSuites: []uint16{cipherSuite.id},
+ },
+ exportTrafficSecrets: true,
+ })
+ }
+}
+
+func addTLSUniqueTests() {
+ for _, isClient := range []bool{false, true} {
+ for _, isResumption := range []bool{false, true} {
+ for _, hasEMS := range []bool{false, true} {
+ var suffix string
+ if isResumption {
+ suffix = "Resume-"
+ } else {
+ suffix = "Full-"
+ }
+
+ if hasEMS {
+ suffix += "EMS-"
+ } else {
+ suffix += "NoEMS-"
+ }
+
+ if isClient {
+ suffix += "Client"
+ } else {
+ suffix += "Server"
+ }
+
+ test := testCase{
+ name: "TLSUnique-" + suffix,
+ testTLSUnique: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: !hasEMS,
+ },
+ },
+ }
+
+ if isResumption {
+ test.resumeSession = true
+ test.resumeConfig = &Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: !hasEMS,
+ },
+ }
+ }
+
+ if isResumption && !hasEMS {
+ test.shouldFail = true
+ test.expectedError = "failed to get tls-unique"
+ }
+
+ testCases = append(testCases, test)
+ }
+ }
+ }
+}
diff --git a/ssl/test/runner/extension_tests.go b/ssl/test/runner/extension_tests.go
new file mode 100644
index 0000000..6ea70b5
--- /dev/null
+++ b/ssl/test/runner/extension_tests.go
@@ -0,0 +1,2342 @@
+// 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"
+ "fmt"
+ "math/big"
+ "time"
+)
+
+func addExtensionTests() {
+ exampleCertificate := 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{"example.com"},
+ IsCA: true,
+ BasicConstraintsValid: true,
+ }, &ecdsaP256Key)
+
+ // Repeat extensions tests at all versions.
+ for _, protocol := range []protocol{tls, dtls, quic} {
+ for _, ver := range allVersions(protocol) {
+ suffix := fmt.Sprintf("%s-%s", protocol.String(), ver.name)
+
+ // Test that duplicate extensions are rejected.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "DuplicateExtensionClient-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ DuplicateExtension: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decoding message",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "DuplicateExtensionServer-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ DuplicateExtension: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ // Test SNI.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "ServerNameExtensionClient-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectServerName: "example.com",
+ },
+ Credential: &exampleCertificate,
+ },
+ flags: []string{"-host-name", "example.com"},
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "ServerNameExtensionClientMismatch-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectServerName: "mismatch.com",
+ },
+ },
+ flags: []string{"-host-name", "example.com"},
+ shouldFail: true,
+ expectedLocalError: "tls: unexpected server name",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "ServerNameExtensionClientMissing-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectServerName: "missing.com",
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "tls: unexpected server name",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "TolerateServerNameAck-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendServerNameAck: true,
+ },
+ Credential: &exampleCertificate,
+ },
+ flags: []string{"-host-name", "example.com"},
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "UnsolicitedServerNameAck-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendServerNameAck: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "ServerNameExtensionServer-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ ServerName: "example.com",
+ },
+ flags: []string{"-expect-server-name", "example.com"},
+ resumeSession: true,
+ })
+
+ // Test ALPN.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNClient-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar\x03baz",
+ "-expect-alpn", "foo",
+ },
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: alpn,
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNClient-RejectUnknown-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendALPN: "baz",
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_ALPN_PROTOCOL:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNClient-AllowUnknown-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendALPN: "baz",
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-allow-unknown-alpn-protos",
+ "-expect-alpn", "baz",
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNServer-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ },
+ flags: []string{
+ "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+ "-select-alpn", "foo",
+ },
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: alpn,
+ },
+ resumeSession: true,
+ })
+
+ var shouldDeclineALPNFail bool
+ var declineALPNError, declineALPNLocalError string
+ if protocol == quic {
+ // ALPN is mandatory in QUIC.
+ shouldDeclineALPNFail = true
+ declineALPNError = ":NO_APPLICATION_PROTOCOL:"
+ declineALPNLocalError = "remote error: no application protocol"
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNServer-Decline-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ },
+ flags: []string{"-decline-alpn"},
+ expectations: connectionExpectations{
+ noNextProto: true,
+ },
+ resumeSession: true,
+ shouldFail: shouldDeclineALPNFail,
+ expectedError: declineALPNError,
+ expectedLocalError: declineALPNLocalError,
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNServer-Reject-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ },
+ flags: []string{"-reject-alpn"},
+ shouldFail: true,
+ expectedError: ":NO_APPLICATION_PROTOCOL:",
+ expectedLocalError: "remote error: no application protocol",
+ })
+
+ // Test that the server implementation catches itself if the
+ // callback tries to return an invalid empty ALPN protocol.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNServer-SelectEmpty-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ },
+ flags: []string{
+ "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+ "-select-empty-alpn",
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: internal error",
+ expectedError: ":INVALID_ALPN_PROTOCOL:",
+ })
+
+ // Test ALPN in async mode as well to ensure that extensions callbacks are only
+ // called once.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNServer-Async-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ // Prior to TLS 1.3, exercise the asynchronous session callback.
+ SessionTicketsDisabled: ver.version < VersionTLS13,
+ },
+ flags: []string{
+ "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+ "-select-alpn", "foo",
+ "-async",
+ },
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: alpn,
+ },
+ resumeSession: true,
+ })
+
+ var emptyString string
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNClient-EmptyProtocolName-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{""},
+ Bugs: ProtocolBugs{
+ // A server returning an empty ALPN protocol
+ // should be rejected.
+ ALPNProtocol: &emptyString,
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo",
+ },
+ shouldFail: true,
+ expectedError: ":PARSE_TLSEXT:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ skipQUICALPNConfig: true,
+ name: "ALPNServer-EmptyProtocolName-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ // A ClientHello containing an empty ALPN protocol
+ // should be rejected.
+ NextProtos: []string{"foo", "", "baz"},
+ },
+ flags: []string{
+ "-select-alpn", "foo",
+ },
+ shouldFail: true,
+ expectedError: ":PARSE_TLSEXT:",
+ })
+
+ // Test NPN and the interaction with ALPN.
+ if ver.version < VersionTLS13 && protocol == tls {
+ // Test that the server prefers ALPN over NPN.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "ALPNServer-Preferred-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ },
+ flags: []string{
+ "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+ "-select-alpn", "foo",
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ },
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: alpn,
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "ALPNServer-Preferred-Swapped-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ Bugs: ProtocolBugs{
+ SwapNPNAndALPN: true,
+ },
+ },
+ flags: []string{
+ "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+ "-select-alpn", "foo",
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ },
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: alpn,
+ },
+ resumeSession: true,
+ })
+
+ // Test that negotiating both NPN and ALPN is forbidden.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "NegotiateALPNAndNPN-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ Bugs: ProtocolBugs{
+ NegotiateALPNAndNPN: true,
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo",
+ "-select-next-proto", "foo",
+ },
+ shouldFail: true,
+ expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "NegotiateALPNAndNPN-Swapped-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo", "bar", "baz"},
+ Bugs: ProtocolBugs{
+ NegotiateALPNAndNPN: true,
+ SwapNPNAndALPN: true,
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo",
+ "-select-next-proto", "foo",
+ },
+ shouldFail: true,
+ expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
+ })
+ }
+
+ // Test missing ALPN in QUIC
+ if protocol == quic {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Client-ALPNMissingFromConfig-" + suffix,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ skipQUICALPNConfig: true,
+ shouldFail: true,
+ expectedError: ":NO_APPLICATION_PROTOCOL:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Client-ALPNMissing-" + suffix,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo",
+ },
+ skipQUICALPNConfig: true,
+ shouldFail: true,
+ expectedError: ":NO_APPLICATION_PROTOCOL:",
+ expectedLocalError: "remote error: no application protocol",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Server-ALPNMissing-" + suffix,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ skipQUICALPNConfig: true,
+ shouldFail: true,
+ expectedError: ":NO_APPLICATION_PROTOCOL:",
+ expectedLocalError: "remote error: no application protocol",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Server-ALPNMismatch-" + suffix,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{
+ "-decline-alpn",
+ },
+ skipQUICALPNConfig: true,
+ shouldFail: true,
+ expectedError: ":NO_APPLICATION_PROTOCOL:",
+ expectedLocalError: "remote error: no application protocol",
+ })
+ }
+
+ // Test ALPS.
+ if ver.version >= VersionTLS13 {
+ // Test basic client with different ALPS codepoint.
+ for _, alpsCodePoint := range []ALPSUseCodepoint{ALPSUseCodepointNew, ALPSUseCodepointOld} {
+ flags := []string{}
+ expectations := connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim1"),
+ }
+ resumeExpectations := &connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim2"),
+ }
+
+ if alpsCodePoint == ALPSUseCodepointNew {
+ flags = append(flags, "-alps-use-new-codepoint")
+ expectations = connectionExpectations{
+ peerApplicationSettings: []byte("shim1"),
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettings: []byte("shim2"),
+ }
+ }
+
+ flags = append(flags,
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-on-initial-application-settings", "proto,shim1",
+ "-on-initial-expect-peer-application-settings", "runner1",
+ "-on-resume-application-settings", "proto,shim2",
+ "-on-resume-expect-peer-application-settings", "runner2")
+
+ // Test that server can negotiate ALPS, including different values
+ // on resumption.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-Basic-Client-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeConfig: &Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ expectations: expectations,
+ resumeExpectations: resumeExpectations,
+ flags: flags,
+ })
+
+ // Test basic server with different ALPS codepoint.
+ flags = []string{}
+ expectations = connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim1"),
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim2"),
+ }
+
+ if alpsCodePoint == ALPSUseCodepointNew {
+ flags = append(flags, "-alps-use-new-codepoint")
+ expectations = connectionExpectations{
+ peerApplicationSettings: []byte("shim1"),
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettings: []byte("shim2"),
+ }
+ }
+
+ flags = append(flags,
+ "-select-alpn", "proto",
+ "-on-initial-application-settings", "proto,shim1",
+ "-on-initial-expect-peer-application-settings", "runner1",
+ "-on-resume-application-settings", "proto,shim2",
+ "-on-resume-expect-peer-application-settings", "runner2")
+
+ // Test that server can negotiate ALPS, including different values
+ // on resumption.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-Basic-Server-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeConfig: &Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ expectations: expectations,
+ resumeExpectations: resumeExpectations,
+ flags: flags,
+ })
+
+ // Try different ALPS codepoint for all the existing tests.
+ alpsFlags := []string{}
+ expectations = connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim1"),
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim2"),
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ alpsFlags = append(alpsFlags, "-alps-use-new-codepoint")
+ expectations = connectionExpectations{
+ peerApplicationSettings: []byte("shim1"),
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettings: []byte("shim2"),
+ }
+ }
+
+ // Test that the server can defer its ALPS configuration to the ALPN
+ // selection callback.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-Basic-Server-Defer-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeConfig: &Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ expectations: expectations,
+ resumeExpectations: resumeExpectations,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-defer-alps",
+ "-on-initial-application-settings", "proto,shim1",
+ "-on-initial-expect-peer-application-settings", "runner1",
+ "-on-resume-application-settings", "proto,shim2",
+ "-on-resume-expect-peer-application-settings", "runner2",
+ }, alpsFlags...),
+ })
+
+ expectations = connectionExpectations{
+ peerApplicationSettingsOld: []byte{},
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ expectations = connectionExpectations{
+ peerApplicationSettings: []byte{},
+ }
+ }
+ // Test the client and server correctly handle empty settings.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-Empty-Client-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": {}},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ expectations: expectations,
+ flags: append([]string{
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-application-settings", "proto,",
+ "-expect-peer-application-settings", "",
+ }, alpsFlags...),
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-Empty-Server-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": {}},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ expectations: expectations,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto,",
+ "-expect-peer-application-settings", "",
+ }, alpsFlags...),
+ })
+
+ bugs := ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsOld: true,
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ bugs = ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsNew: true,
+ }
+ }
+ // Test the client rejects application settings from the server on
+ // protocols it doesn't have them.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-UnsupportedProtocol-Client-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto1"},
+ ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
+ Bugs: bugs,
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ // The client supports ALPS with "proto2", but not "proto1".
+ flags: append([]string{
+ "-advertise-alpn", "\x06proto1\x06proto2",
+ "-application-settings", "proto2,shim",
+ "-expect-alpn", "proto1",
+ }, alpsFlags...),
+ // The server sends ALPS with "proto1", which is invalid.
+ shouldFail: true,
+ expectedError: ":INVALID_ALPN_PROTOCOL:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ // Test client rejects application settings from the server when
+ // server sends the wrong ALPS codepoint.
+ bugs = ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsOld: true,
+ }
+ if alpsCodePoint == ALPSUseCodepointOld {
+ bugs = ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsNew: true,
+ }
+ }
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-WrongServerCodepoint-Client-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": {}},
+ Bugs: bugs,
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ flags: append([]string{
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-application-settings", "proto,",
+ "-expect-peer-application-settings", "",
+ }, alpsFlags...),
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ // Test server ignore wrong codepoint from client.
+ clientSends := ALPSUseCodepointNew
+ if alpsCodePoint == ALPSUseCodepointNew {
+ clientSends = ALPSUseCodepointOld
+ }
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-IgnoreClientWrongCodepoint-Server-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+ ALPSUseNewCodepoint: clientSends,
+ },
+ resumeConfig: &Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
+ ALPSUseNewCodepoint: clientSends,
+ },
+ resumeSession: true,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-on-initial-application-settings", "proto,shim1",
+ "-on-resume-application-settings", "proto,shim2",
+ }, alpsFlags...),
+ })
+
+ // Test the server declines ALPS if it doesn't support it for the
+ // specified protocol.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-UnsupportedProtocol-Server-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto1"},
+ ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ // The server supports ALPS with "proto2", but not "proto1".
+ flags: append([]string{
+ "-select-alpn", "proto1",
+ "-application-settings", "proto2,shim",
+ }, alpsFlags...),
+ })
+
+ // Test the client rejects application settings from the server when
+ // it always negotiate both codepoint.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-UnsupportedProtocol-Client-ServerBoth-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto1"},
+ ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
+ Bugs: ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsBoth: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ flags: append([]string{
+ "-advertise-alpn", "\x06proto1\x06proto2",
+ "-application-settings", "proto1,shim",
+ "-expect-alpn", "proto1",
+ }, alpsFlags...),
+ // The server sends ALPS with both application settings, which is invalid.
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ expectations = connectionExpectations{
+ peerApplicationSettingsOld: []byte("shim"),
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ expectations = connectionExpectations{
+ peerApplicationSettings: []byte("shim"),
+ }
+ }
+
+ // Test that the server rejects a missing application_settings extension.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-OmitClientApplicationSettings-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+ Bugs: ProtocolBugs{
+ OmitClientApplicationSettings: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto,shim",
+ }, alpsFlags...),
+ // The runner is a client, so it only processes the shim's alert
+ // after checking connection state.
+ expectations: expectations,
+ shouldFail: true,
+ expectedError: ":MISSING_EXTENSION:",
+ expectedLocalError: "remote error: missing extension",
+ })
+
+ // Test that the server rejects a missing EncryptedExtensions message.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-OmitClientEncryptedExtensions-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+ Bugs: ProtocolBugs{
+ OmitClientEncryptedExtensions: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto,shim",
+ }, alpsFlags...),
+ // The runner is a client, so it only processes the shim's alert
+ // after checking connection state.
+ expectations: expectations,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Test that the server rejects an unexpected EncryptedExtensions message.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("UnexpectedClientEncryptedExtensions-%s-%s", alpsCodePoint, suffix),
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ AlwaysSendClientEncryptedExtensions: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Test that the server rejects an unexpected extension in an
+ // expected EncryptedExtensions message.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ExtraClientEncryptedExtension-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+ Bugs: ProtocolBugs{
+ SendExtraClientEncryptedExtension: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto,shim",
+ }, alpsFlags...),
+ // The runner is a client, so it only processes the shim's alert
+ // after checking connection state.
+ expectations: expectations,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ // Test that ALPS is carried over on 0-RTT.
+ // TODO(crbug.com/381113363): Support 0-RTT in DTLS 1.3.
+ if protocol != dtls {
+ for _, empty := range []bool{false, true} {
+ maybeEmpty := ""
+ runnerSettings := "runner"
+ shimSettings := "shim"
+ if empty {
+ maybeEmpty = "Empty-"
+ runnerSettings = ""
+ shimSettings = ""
+ }
+
+ expectations = connectionExpectations{
+ peerApplicationSettingsOld: []byte(shimSettings),
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ expectations = connectionExpectations{
+ peerApplicationSettings: []byte(shimSettings),
+ }
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-EarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-application-settings", "proto," + shimSettings,
+ "-expect-peer-application-settings", runnerSettings,
+ }, alpsFlags...),
+ expectations: expectations,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-EarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto," + shimSettings,
+ "-expect-peer-application-settings", runnerSettings,
+ }, alpsFlags...),
+ expectations: expectations,
+ })
+
+ // Sending application settings in 0-RTT handshakes is forbidden.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+ Bugs: ProtocolBugs{
+ SendApplicationSettingsWithEarlyData: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-application-settings", "proto," + shimSettings,
+ "-expect-peer-application-settings", runnerSettings,
+ }, alpsFlags...),
+ expectations: expectations,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+ Bugs: ProtocolBugs{
+ SendApplicationSettingsWithEarlyData: true,
+ },
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto," + shimSettings,
+ "-expect-peer-application-settings", runnerSettings,
+ }, alpsFlags...),
+ expectations: expectations,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ }
+
+ // Test that the client and server each decline early data if local
+ // ALPS preferences has changed for the current connection.
+ alpsMismatchTests := []struct {
+ name string
+ initialSettings, resumeSettings []byte
+ }{
+ {"DifferentValues", []byte("settings1"), []byte("settings2")},
+ {"OnOff", []byte("settings"), nil},
+ {"OffOn", nil, []byte("settings")},
+ // The empty settings value should not be mistaken for ALPS not
+ // being negotiated.
+ {"OnEmpty", []byte("settings"), []byte{}},
+ {"EmptyOn", []byte{}, []byte("settings")},
+ {"EmptyOff", []byte{}, nil},
+ {"OffEmpty", nil, []byte{}},
+ }
+ for _, test := range alpsMismatchTests {
+ flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
+ flags = append(flags, alpsFlags...)
+ if test.initialSettings != nil {
+ flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
+ flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
+ }
+ if test.resumeSettings != nil {
+ flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
+ flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
+ }
+
+ expectations = connectionExpectations{
+ peerApplicationSettingsOld: test.initialSettings,
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettingsOld: test.resumeSettings,
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ expectations = connectionExpectations{
+ peerApplicationSettings: test.initialSettings,
+ }
+ resumeExpectations = &connectionExpectations{
+ peerApplicationSettings: test.resumeSettings,
+ }
+ }
+ // The client should not offer early data if the session is
+ // inconsistent with the new configuration. Note that if
+ // the session did not negotiate ALPS (test.initialSettings
+ // is nil), the client always offers early data.
+ if test.initialSettings != nil {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s-%s", test.name, alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ MaxEarlyDataSize: 16384,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ flags: append([]string{
+ "-enable-early-data",
+ "-expect-ticket-supports-early-data",
+ "-expect-no-offer-early-data",
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ }, flags...),
+ expectations: expectations,
+ resumeExpectations: resumeExpectations,
+ })
+ }
+
+ // The server should reject early data if the session is
+ // inconsistent with the new selection.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s-%s", test.name, alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ }, flags...),
+ expectations: expectations,
+ resumeExpectations: resumeExpectations,
+ })
+ }
+
+ // Test that 0-RTT continues working when the shim configures
+ // ALPS but the peer does not.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-EarlyData-Client-ServerDecline-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-application-settings", "proto,shim",
+ }, alpsFlags...),
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-EarlyData-Server-ClientNoOffe-%s-%s", alpsCodePoint, suffix),
+ skipQUICALPNConfig: true,
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"proto"},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-select-alpn", "proto",
+ "-application-settings", "proto,shim",
+ }, alpsFlags...),
+ })
+ }
+ }
+ } else {
+ // Test the client rejects the ALPS extension if the server
+ // negotiated TLS 1.2 or below.
+ for _, alpsCodePoint := range []ALPSUseCodepoint{ALPSUseCodepointNew, ALPSUseCodepointOld} {
+ flags := []string{
+ "-advertise-alpn", "\x03foo",
+ "-expect-alpn", "foo",
+ "-application-settings", "foo,shim",
+ }
+ bugs := ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsOld: true,
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ flags = append(flags, "-alps-use-new-codepoint")
+ bugs = ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsNew: true,
+ }
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-Reject-Client-%s-%s", alpsCodePoint, suffix),
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo"},
+ ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+ Bugs: bugs,
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ flags = []string{
+ "-on-resume-advertise-alpn", "\x03foo",
+ "-on-resume-expect-alpn", "foo",
+ "-on-resume-application-settings", "foo,shim",
+ }
+ bugs = ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsOld: true,
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ flags = append(flags, "-alps-use-new-codepoint")
+ bugs = ProtocolBugs{
+ AlwaysNegotiateApplicationSettingsNew: true,
+ }
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("ALPS-Reject-Client-Resume-%s-%s", alpsCodePoint, suffix),
+ config: Config{
+ MaxVersion: ver.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo"},
+ ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+ Bugs: bugs,
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ resumeSession: true,
+ flags: flags,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ // Test the server declines ALPS if it negotiates TLS 1.2 or below.
+ flags = []string{
+ "-select-alpn", "foo",
+ "-application-settings", "foo,shim",
+ }
+ if alpsCodePoint == ALPSUseCodepointNew {
+ flags = append(flags, "-alps-use-new-codepoint")
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("ALPS-Decline-Server-%s-%s", alpsCodePoint, suffix),
+ config: Config{
+ MaxVersion: ver.version,
+ NextProtos: []string{"foo"},
+ ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+ ALPSUseNewCodepoint: alpsCodePoint,
+ },
+ // Test both TLS 1.2 full and resumption handshakes.
+ resumeSession: true,
+ flags: flags,
+ // If not specified, runner and shim both implicitly expect ALPS
+ // is not negotiated.
+ })
+ }
+ }
+
+ // Test QUIC transport params
+ if protocol == quic {
+ // Client sends params
+ for _, clientConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
+ for _, serverSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} {
+ useCodepointFlag := "0"
+ if clientConfig == QUICUseCodepointLegacy {
+ useCodepointFlag = "1"
+ }
+ flags := []string{
+ "-quic-transport-params",
+ base64FlagValue([]byte{1, 2}),
+ "-quic-use-legacy-codepoint", useCodepointFlag,
+ }
+ expectations := connectionExpectations{
+ quicTransportParams: []byte{1, 2},
+ }
+ shouldFail := false
+ expectedError := ""
+ expectedLocalError := ""
+ if clientConfig == QUICUseCodepointLegacy {
+ expectations = connectionExpectations{
+ quicTransportParamsLegacy: []byte{1, 2},
+ }
+ }
+ if serverSends != clientConfig {
+ expectations = connectionExpectations{}
+ shouldFail = true
+ if serverSends == QUICUseCodepointNeither {
+ expectedError = ":MISSING_EXTENSION:"
+ } else {
+ expectedLocalError = "remote error: unsupported extension"
+ }
+ } else {
+ flags = append(flags,
+ "-expect-quic-transport-params",
+ base64FlagValue([]byte{3, 4}))
+ }
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: fmt.Sprintf("QUICTransportParams-Client-Client%s-Server%s-%s", clientConfig, serverSends, suffix),
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ QUICTransportParams: []byte{3, 4},
+ QUICTransportParamsUseLegacyCodepoint: serverSends,
+ },
+ flags: flags,
+ expectations: expectations,
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ expectedLocalError: expectedLocalError,
+ skipTransportParamsConfig: true,
+ })
+ }
+ }
+ // Server sends params
+ for _, clientSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} {
+ for _, serverConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
+ expectations := connectionExpectations{
+ quicTransportParams: []byte{3, 4},
+ }
+ shouldFail := false
+ expectedError := ""
+ useCodepointFlag := "0"
+ if serverConfig == QUICUseCodepointLegacy {
+ useCodepointFlag = "1"
+ expectations = connectionExpectations{
+ quicTransportParamsLegacy: []byte{3, 4},
+ }
+ }
+ flags := []string{
+ "-quic-transport-params",
+ base64FlagValue([]byte{3, 4}),
+ "-quic-use-legacy-codepoint", useCodepointFlag,
+ }
+ if clientSends != QUICUseCodepointBoth && clientSends != serverConfig {
+ expectations = connectionExpectations{}
+ shouldFail = true
+ expectedError = ":MISSING_EXTENSION:"
+ } else {
+ flags = append(flags,
+ "-expect-quic-transport-params",
+ base64FlagValue([]byte{1, 2}),
+ )
+ }
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: fmt.Sprintf("QUICTransportParams-Server-Client%s-Server%s-%s", clientSends, serverConfig, suffix),
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ QUICTransportParams: []byte{1, 2},
+ QUICTransportParamsUseLegacyCodepoint: clientSends,
+ },
+ flags: flags,
+ expectations: expectations,
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ skipTransportParamsConfig: true,
+ })
+ }
+ }
+ } else {
+ // Ensure non-QUIC client doesn't send QUIC transport parameters.
+ for _, clientConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
+ useCodepointFlag := "0"
+ if clientConfig == QUICUseCodepointLegacy {
+ useCodepointFlag = "1"
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: fmt.Sprintf("QUICTransportParams-Client-NotSentInNonQUIC-%s-%s", clientConfig, suffix),
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ QUICTransportParamsUseLegacyCodepoint: clientConfig,
+ },
+ flags: []string{
+ "-max-version",
+ ver.shimFlag(protocol),
+ "-quic-transport-params",
+ base64FlagValue([]byte{3, 4}),
+ "-quic-use-legacy-codepoint", useCodepointFlag,
+ },
+ shouldFail: true,
+ expectedError: ":QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED:",
+ skipTransportParamsConfig: true,
+ })
+ }
+ // Ensure non-QUIC server rejects codepoint 57 but ignores legacy 0xffa5.
+ for _, clientSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} {
+ for _, serverConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
+ shouldFail := false
+ expectedLocalError := ""
+ useCodepointFlag := "0"
+ if serverConfig == QUICUseCodepointLegacy {
+ useCodepointFlag = "1"
+ }
+ if clientSends == QUICUseCodepointStandard || clientSends == QUICUseCodepointBoth {
+ shouldFail = true
+ expectedLocalError = "remote error: unsupported extension"
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: fmt.Sprintf("QUICTransportParams-NonQUICServer-Client%s-Server%s-%s", clientSends, serverConfig, suffix),
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ QUICTransportParams: []byte{1, 2},
+ QUICTransportParamsUseLegacyCodepoint: clientSends,
+ },
+ flags: []string{
+ "-quic-use-legacy-codepoint", useCodepointFlag,
+ },
+ shouldFail: shouldFail,
+ expectedLocalError: expectedLocalError,
+ skipTransportParamsConfig: true,
+ })
+ }
+ }
+
+ }
+
+ // Test ticket behavior.
+
+ // Resume with a corrupt ticket.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "CorruptTicket-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ FilterTicket: func(in []byte) ([]byte, error) {
+ in[len(in)-1] ^= 1
+ return in, nil
+ },
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+ // Test the ticket callbacks.
+ for _, aeadCallback := range []bool{false, true} {
+ flag := "-use-ticket-callback"
+ callbackSuffix := suffix
+ if aeadCallback {
+ flag = "-use-ticket-aead-callback"
+ callbackSuffix += "-AEAD"
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketCallback-" + callbackSuffix,
+ config: Config{
+ MaxVersion: ver.version,
+ },
+ resumeSession: true,
+ flags: []string{flag},
+ })
+ // Only the old callback supports renewal.
+ if !aeadCallback {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketCallback-Renew-" + callbackSuffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
+ },
+ },
+ flags: []string{flag, "-renew-ticket"},
+ resumeSession: true,
+ })
+ }
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketCallback-Skip-" + callbackSuffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectNoNonEmptyNewSessionTicket: true,
+ },
+ },
+ flags: []string{flag, "-skip-ticket"},
+ })
+
+ // Test that the ticket callback is only called once when everything before
+ // it in the ClientHello is asynchronous. This corrupts the ticket so
+ // certificate selection callbacks run.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketCallback-SingleCall-" + callbackSuffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ FilterTicket: func(in []byte) ([]byte, error) {
+ in[len(in)-1] ^= 1
+ return in, nil
+ },
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ flags: []string{
+ flag,
+ "-async",
+ },
+ })
+ }
+
+ // Resume with various lengths of ticket session id.
+ if ver.version < VersionTLS13 {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketSessionIDLength-0-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ EmptyTicketSessionID: true,
+ },
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketSessionIDLength-16-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ TicketSessionIDLength: 16,
+ },
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketSessionIDLength-32-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ TicketSessionIDLength: 32,
+ },
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "TicketSessionIDLength-33-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ TicketSessionIDLength: 33,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ // The maximum session ID length is 32.
+ expectedError: ":CLIENTHELLO_PARSE_FAILED:",
+ })
+ }
+
+ // Basic DTLS-SRTP tests. Include fake profiles to ensure they
+ // are ignored.
+ if protocol == dtls {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SRTP-Client-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+ },
+ expectations: connectionExpectations{
+ srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "SRTP-Server-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+ },
+ expectations: connectionExpectations{
+ srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+ },
+ })
+ // Test that the MKI is ignored.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "SRTP-Server-IgnoreMKI-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80},
+ Bugs: ProtocolBugs{
+ SRTPMasterKeyIdentifier: "bogus",
+ },
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+ },
+ expectations: connectionExpectations{
+ srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+ },
+ })
+ // Test that SRTP isn't negotiated on the server if there were
+ // no matching profiles.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "SRTP-Server-NoMatch-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ SRTPProtectionProfiles: []uint16{100, 101, 102},
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+ },
+ expectations: connectionExpectations{
+ srtpProtectionProfile: 0,
+ },
+ })
+ // Test that the server returning an invalid SRTP profile is
+ // flagged as an error by the client.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SRTP-Client-NoMatch-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32,
+ },
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80",
+ },
+ shouldFail: true,
+ expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:",
+ })
+ } else {
+ // DTLS-SRTP is not defined for other protocols. Configuring it
+ // on the client and server should ignore the extension.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SRTP-Client-Ignore-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+ },
+ expectations: connectionExpectations{
+ srtpProtectionProfile: 0,
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "SRTP-Server-Ignore-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
+ },
+ flags: []string{
+ "-srtp-profiles",
+ "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+ },
+ expectations: connectionExpectations{
+ srtpProtectionProfile: 0,
+ },
+ })
+ }
+
+ // Test SCT list.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SignedCertificateTimestampList-Client-" + suffix,
+ testType: clientTest,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: rsaCertificate.WithSCTList(testSCTList),
+ },
+ flags: []string{
+ "-enable-signed-cert-timestamps",
+ "-expect-signed-cert-timestamps",
+ base64FlagValue(testSCTList),
+ },
+ resumeSession: true,
+ })
+
+ var differentSCTList []byte
+ differentSCTList = append(differentSCTList, testSCTList...)
+ differentSCTList[len(differentSCTList)-1] ^= 1
+
+ // The SCT extension did not specify that it must only be sent on the inital handshake as it
+ // should have, so test that we tolerate but ignore it. This is only an issue pre-1.3, since
+ // SCTs are sent in the CertificateEntry message in 1.3, whereas they were previously sent
+ // in an extension in the ServerHello pre-1.3.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SendSCTListOnResume-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: rsaCertificate.WithSCTList(testSCTList),
+ Bugs: ProtocolBugs{
+ SendSCTListOnResume: differentSCTList,
+ },
+ },
+ flags: []string{
+ "-enable-signed-cert-timestamps",
+ "-expect-signed-cert-timestamps",
+ base64FlagValue(testSCTList),
+ },
+ resumeSession: true,
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SignedCertificateTimestampList-Server-" + suffix,
+ testType: serverTest,
+ config: Config{
+ MaxVersion: ver.version,
+ },
+ shimCertificate: rsaCertificate.WithSCTList(testSCTList),
+ expectations: connectionExpectations{
+ peerCertificate: rsaCertificate.WithSCTList(testSCTList),
+ },
+ resumeSession: true,
+ })
+
+ // Test empty SCT list.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SignedCertificateTimestampListEmpty-Client-" + suffix,
+ testType: clientTest,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: rsaCertificate.WithSCTList([]byte{0, 0}),
+ },
+ flags: []string{
+ "-enable-signed-cert-timestamps",
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ // Test empty SCT in non-empty list.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "SignedCertificateTimestampListEmptySCT-Client-" + suffix,
+ testType: clientTest,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: rsaCertificate.WithSCTList([]byte{0, 6, 0, 2, 1, 2, 0, 0}),
+ },
+ flags: []string{
+ "-enable-signed-cert-timestamps",
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ // Test that certificate-related extensions are not sent unsolicited.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "UnsolicitedCertificateExtensions-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ NoOCSPStapling: true,
+ NoSignedCertificateTimestamps: true,
+ },
+ },
+ shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ })
+
+ // Extension permutation should interact correctly with other extensions,
+ // HelloVerifyRequest, HelloRetryRequest, and ECH. SSLTest.PermuteExtensions
+ // in ssl_test.cc tests that the extensions are actually permuted. This
+ // tests the handshake still works.
+ //
+ // This test also tests that all our extensions interact with each other.
+ for _, ech := range []bool{false, true} {
+ if ech && ver.version < VersionTLS13 {
+ continue
+ }
+
+ test := testCase{
+ protocol: protocol,
+ name: "AllExtensions-Client-Permute",
+ skipQUICALPNConfig: true,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ NextProtos: []string{"proto"},
+ ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+ Bugs: ProtocolBugs{
+ SendServerNameAck: true,
+ ExpectServerName: "example.com",
+ ExpectGREASE: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-permute-extensions",
+ "-enable-grease",
+ "-enable-ocsp-stapling",
+ "-enable-signed-cert-timestamps",
+ "-advertise-alpn", "\x05proto",
+ "-expect-alpn", "proto",
+ "-host-name", "example.com",
+ },
+ }
+
+ if ech {
+ test.name += "-ECH"
+ echConfig := generateServerECHConfig(&ECHConfig{ConfigID: 42})
+ test.config.ServerECHConfigs = []ServerECHConfig{echConfig}
+ test.flags = append(test.flags,
+ "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
+ "-expect-ech-accept",
+ )
+ test.expectations.echAccepted = true
+ }
+
+ if ver.version >= VersionTLS13 {
+ // Trigger a HelloRetryRequest to test both ClientHellos. Note
+ // our DTLS tests always enable HelloVerifyRequest.
+ test.name += "-HelloRetryRequest"
+
+ // ALPS is only available on TLS 1.3.
+ test.config.ApplicationSettings = map[string][]byte{"proto": []byte("runner")}
+ test.flags = append(test.flags,
+ "-application-settings", "proto,shim",
+ "-alps-use-new-codepoint",
+ "-expect-peer-application-settings", "runner")
+ test.expectations.peerApplicationSettings = []byte("shim")
+ }
+
+ if protocol == dtls {
+ test.config.SRTPProtectionProfiles = []uint16{SRTP_AES128_CM_HMAC_SHA1_80}
+ test.flags = append(test.flags, "-srtp-profiles", "SRTP_AES128_CM_SHA1_80")
+ test.expectations.srtpProtectionProfile = SRTP_AES128_CM_HMAC_SHA1_80
+ }
+
+ test.name += "-" + suffix
+ testCases = append(testCases, test)
+ }
+ }
+ }
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ClientHelloPadding",
+ config: Config{
+ Bugs: ProtocolBugs{
+ RequireClientHelloSize: 512,
+ },
+ },
+ // This hostname just needs to be long enough to push the
+ // ClientHello into F5's danger zone between 256 and 511 bytes
+ // long.
+ flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"},
+ })
+
+ // Test that illegal extensions in TLS 1.3 are rejected by the client if
+ // in ServerHello.
+ testCases = append(testCases, testCase{
+ name: "NPN-Forbidden-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ NegotiateNPNAtAllVersions: true,
+ },
+ },
+ flags: []string{"-select-next-proto", "foo"},
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ name: "EMS-Forbidden-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ NegotiateEMSAtAllVersions: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ name: "RenegotiationInfo-Forbidden-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ NegotiateRenegotiationInfoAtAllVersions: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ name: "Ticket-Forbidden-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AdvertiseTicketExtension: true,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+
+ // Test that illegal extensions in TLS 1.3 are declined by the server if
+ // offered in ClientHello. The runner's server will fail if this occurs,
+ // so we exercise the offering path. (EMS and Renegotiation Info are
+ // implicit in every test.)
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NPN-Declined-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"bar"},
+ },
+ flags: []string{"-advertise-npn", "\x03foo\x03bar\x03baz"},
+ })
+
+ // OpenSSL sends the status_request extension on resumption in TLS 1.2. Test that this is
+ // tolerated.
+ testCases = append(testCases, testCase{
+ name: "SendOCSPResponseOnResume-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ Bugs: ProtocolBugs{
+ SendOCSPResponseOnResume: []byte("bogus"),
+ },
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-expect-ocsp-response",
+ base64FlagValue(testOCSPResponse),
+ },
+ resumeSession: true,
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SendUnsolicitedOCSPOnCertificate-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtensionOnCertificate: testOCSPExtension,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SendUnsolicitedSCTOnCertificate-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtensionOnCertificate: testSCTExtension,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // Test that extensions on client certificates are never accepted.
+ testCases = append(testCases, testCase{
+ name: "SendExtensionOnClientCertificate-TLS13",
+ testType: serverTest,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ SendExtensionOnCertificate: testOCSPExtension,
+ },
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SendUnknownExtensionOnCertificate-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtensionOnCertificate: []byte{0x00, 0x7f, 0, 0},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // Test that extensions on intermediates are allowed but ignored.
+ testCases = append(testCases, testCase{
+ name: "IgnoreExtensionsOnIntermediates-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ Bugs: ProtocolBugs{
+ // Send different values on the intermediate. This tests
+ // the intermediate's extensions do not override the
+ // leaf's.
+ SendOCSPOnIntermediates: testOCSPResponse2,
+ SendSCTOnIntermediates: testSCTList2,
+ },
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-expect-ocsp-response",
+ base64FlagValue(testOCSPResponse),
+ "-enable-signed-cert-timestamps",
+ "-expect-signed-cert-timestamps",
+ base64FlagValue(testSCTList),
+ },
+ resumeSession: true,
+ })
+
+ // Test that extensions are not sent on intermediates when configured
+ // only for a leaf.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendNoExtensionsOnIntermediate-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectNoExtensionsOnIntermediate: true,
+ },
+ },
+ shimCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ })
+
+ // Test that extensions are not sent on client certificates.
+ testCases = append(testCases, testCase{
+ name: "SendNoClientCertificateExtensions-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SendDuplicateExtensionsOnCerts-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ Bugs: ProtocolBugs{
+ SendDuplicateCertExtensions: true,
+ },
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-enable-signed-cert-timestamps",
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":DUPLICATE_EXTENSION:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SignedCertificateTimestampListInvalid-Server",
+ testType: serverTest,
+ shimCertificate: rsaCertificate.WithSCTList([]byte{0, 0}),
+ shouldFail: true,
+ expectedError: ":INVALID_SCT_LIST:",
+ })
+}
+
+func addUnknownExtensionTests() {
+ // Test an unknown extension from the server.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "UnknownExtension-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ CustomExtension: "custom extension",
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "UnknownExtension-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ CustomExtension: "custom extension",
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "UnknownUnencryptedExtension-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ CustomUnencryptedExtension: "custom extension",
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ // The shim must send an alert, but alerts at this point do not
+ // get successfully decrypted by the runner.
+ expectedLocalError: "local error: bad record MAC",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "UnexpectedUnencryptedExtension-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendUnencryptedALPN: "foo",
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ // The shim must send an alert, but alerts at this point do not
+ // get successfully decrypted by the runner.
+ expectedLocalError: "local error: bad record MAC",
+ })
+
+ // Test a known but unoffered extension from the server.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "UnofferedExtension-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendALPN: "alpn",
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "UnofferedExtension-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendALPN: "alpn",
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+}
+
+// Test that omitted and empty extensions blocks are tolerated.
+func addOmitExtensionsTests() {
+ // Check the ExpectOmitExtensions setting works.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ExpectOmitExtensions",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectOmitExtensions: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "tls: ServerHello did not omit extensions",
+ })
+
+ for _, ver := range tlsVersions {
+ if ver.version > VersionTLS12 {
+ continue
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "OmitExtensions-ClientHello-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ OmitExtensions: true,
+ // With no client extensions, the ServerHello must not have
+ // extensions. It should then omit the extensions field.
+ ExpectOmitExtensions: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EmptyExtensions-ClientHello-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ EmptyExtensions: true,
+ // With no client extensions, the ServerHello must not have
+ // extensions. It should then omit the extensions field.
+ ExpectOmitExtensions: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "OmitExtensions-ServerHello-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ OmitExtensions: true,
+ // Disable all ServerHello extensions so
+ // OmitExtensions works.
+ NoExtendedMasterSecret: true,
+ NoRenegotiationInfo: true,
+ NoOCSPStapling: true,
+ NoSignedCertificateTimestamps: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EmptyExtensions-ServerHello-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ EmptyExtensions: true,
+ // Disable all ServerHello extensions so
+ // EmptyExtensions works.
+ NoExtendedMasterSecret: true,
+ NoRenegotiationInfo: true,
+ NoOCSPStapling: true,
+ NoSignedCertificateTimestamps: true,
+ },
+ },
+ })
+ }
+}
diff --git a/ssl/test/runner/extra_handshake_tests.go b/ssl/test/runner/extra_handshake_tests.go
new file mode 100644
index 0000000..911ff3e
--- /dev/null
+++ b/ssl/test/runner/extra_handshake_tests.go
@@ -0,0 +1,113 @@
+// 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
+
+func addExtraHandshakeTests() {
+ // An extra SSL_do_handshake is normally a no-op. These tests use -async
+ // to ensure there is no transport I/O.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ExtraHandshake-Client-TLS12",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{
+ "-async",
+ "-no-op-extra-handshake",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ExtraHandshake-Server-TLS12",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{
+ "-async",
+ "-no-op-extra-handshake",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ExtraHandshake-Client-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-async",
+ "-no-op-extra-handshake",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ExtraHandshake-Server-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-async",
+ "-no-op-extra-handshake",
+ },
+ })
+
+ // An extra SSL_do_handshake is a no-op in server 0-RTT.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ExtraHandshake-Server-EarlyData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ messageCount: 2,
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-async",
+ "-no-op-extra-handshake",
+ },
+ })
+
+ // An extra SSL_do_handshake drives the handshake to completion in False
+ // Start. We test this by handshaking twice and asserting the False
+ // Start does not appear to happen. See AlertBeforeFalseStartTest for
+ // how the test works.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ExtraHandshake-FalseStart",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ AlertBeforeFalseStartTest: alertAccessDenied,
+ },
+ },
+ flags: []string{
+ "-handshake-twice",
+ "-false-start",
+ "-advertise-alpn", "\x03foo",
+ "-expect-alpn", "foo",
+ },
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
+ expectedLocalError: "tls: peer did not false start: EOF",
+ })
+}
diff --git a/ssl/test/runner/hint_mismatch_tests.go b/ssl/test/runner/hint_mismatch_tests.go
new file mode 100644
index 0000000..db96716
--- /dev/null
+++ b/ssl/test/runner/hint_mismatch_tests.go
@@ -0,0 +1,491 @@
+// 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 "strconv"
+
+func addHintMismatchTests() {
+ // Each of these tests skips split handshakes because split handshakes does
+ // not handle a mismatch between shim and handshaker. Handshake hints,
+ // however, are designed to tolerate the mismatch.
+ //
+ // Note also these tests do not specify -handshake-hints directly. Instead,
+ // we define normal tests, that run even without a handshaker, and rely on
+ // convertToSplitHandshakeTests to generate a handshaker hints variant. This
+ // avoids repeating the -is-handshaker-supported and -handshaker-path logic.
+ // (While not useful, the tests will still pass without a handshaker.)
+ for _, protocol := range []protocol{tls, quic} {
+ // If the signing payload is different, the handshake still completes
+ // successfully. Different ALPN preferences will trigger a mismatch.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-SignatureInput",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo", "bar"},
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-select-alpn", "foo",
+ "-on-handshaker-select-alpn", "bar",
+ },
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: alpn,
+ },
+ })
+
+ // The shim and handshaker may have different curve preferences.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-KeyShare",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ // Send both curves in the key share list, to avoid getting
+ // mixed up with HelloRetryRequest.
+ DefaultCurves: []CurveID{CurveX25519, CurveP256},
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-curves", strconv.Itoa(int(CurveX25519)),
+ "-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
+ },
+ expectations: connectionExpectations{
+ curveID: CurveX25519,
+ },
+ })
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-ECDHE-Group",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ DefaultCurves: []CurveID{CurveX25519, CurveP256},
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-curves", strconv.Itoa(int(CurveX25519)),
+ "-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
+ },
+ expectations: connectionExpectations{
+ curveID: CurveX25519,
+ },
+ })
+ }
+
+ // If the handshaker does HelloRetryRequest, it will omit most hints.
+ // The shim should still work.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-HandshakerHelloRetryRequest",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveX25519},
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-curves", strconv.Itoa(int(CurveX25519)),
+ "-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
+ },
+ expectations: connectionExpectations{
+ curveID: CurveX25519,
+ },
+ })
+
+ // If the shim does HelloRetryRequest, the hints from the handshaker
+ // will be ignored. This is not reported as a mismatch because hints
+ // would not have helped the shim anyway.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-ShimHelloRetryRequest",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveX25519},
+ },
+ flags: []string{
+ "-on-shim-curves", strconv.Itoa(int(CurveP256)),
+ "-on-handshaker-curves", strconv.Itoa(int(CurveX25519)),
+ },
+ expectations: connectionExpectations{
+ curveID: CurveP256,
+ },
+ })
+
+ // The shim and handshaker may have different signature algorithm
+ // preferences.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS13",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA384,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ handshakerCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+ flags: []string{"-allow-hint-mismatch"},
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
+ },
+ })
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS12",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA384,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ handshakerCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+ flags: []string{"-allow-hint-mismatch"},
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
+ },
+ })
+ }
+
+ // The shim and handshaker may use different certificates. In TLS 1.3,
+ // the signature input includes the certificate, so we do not need to
+ // explicitly check for a public key match. In TLS 1.2, it does not.
+ ecdsaP256Certificate2 := generateSingleCertChain(nil, &channelIDKey)
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-Certificate-TLS13",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ handshakerCertificate: &ecdsaP256Certificate2,
+ flags: []string{"-allow-hint-mismatch"},
+ expectations: connectionExpectations{
+ peerCertificate: &ecdsaP256Certificate,
+ },
+ })
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-Certificate-TLS12",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ handshakerCertificate: &ecdsaP256Certificate2,
+ flags: []string{"-allow-hint-mismatch"},
+ expectations: connectionExpectations{
+ peerCertificate: &ecdsaP256Certificate,
+ },
+ })
+ }
+
+ // The shim and handshaker may disagree on whether resumption is allowed.
+ // We run the first connection with tickets enabled, so the client is
+ // issued a ticket, then disable tickets on the second connection.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-NoTickets1-TLS13",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-on-resume-allow-hint-mismatch",
+ "-on-shim-on-resume-no-ticket",
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-NoTickets2-TLS13",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-on-resume-allow-hint-mismatch",
+ "-on-handshaker-on-resume-no-ticket",
+ },
+ resumeSession: true,
+ })
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-NoTickets1-TLS12",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{
+ "-on-resume-allow-hint-mismatch",
+ "-on-shim-on-resume-no-ticket",
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-NoTickets2-TLS12",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{
+ "-on-resume-allow-hint-mismatch",
+ "-on-handshaker-on-resume-no-ticket",
+ },
+ resumeSession: true,
+ })
+ }
+
+ // The shim and handshaker may disagree on whether to request a client
+ // certificate.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-CertificateRequest",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-require-any-client-certificate",
+ },
+ })
+
+ // The shim and handshaker may negotiate different versions altogether.
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-Version1",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-max-version", strconv.Itoa(VersionTLS12),
+ "-on-handshaker-max-version", strconv.Itoa(VersionTLS13),
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-Version2",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-max-version", strconv.Itoa(VersionTLS13),
+ "-on-handshaker-max-version", strconv.Itoa(VersionTLS12),
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS13,
+ },
+ })
+ }
+
+ // The shim and handshaker may disagree on the certificate compression
+ // algorithm, whether to enable certificate compression, or certificate
+ // compression inputs.
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-CertificateCompression-ShimOnly",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ },
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-install-cert-compression-algs",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: protocol.String() + "-HintMismatch-CertificateCompression-HandshakerOnly",
+ testType: serverTest,
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectUncompressedCert: true,
+ },
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-handshaker-install-cert-compression-algs",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: protocol.String() + "-HintMismatch-CertificateCompression-AlgorithmMismatch",
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ expandingCompressionAlgID: expandingCompression,
+ },
+ Bugs: ProtocolBugs{
+ // The shim's preferences should take effect.
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ },
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID),
+ "-on-handshaker-install-one-cert-compression-alg", strconv.Itoa(expandingCompressionAlgID),
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: protocol.String() + "-HintMismatch-CertificateCompression-InputMismatch",
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ CertCompressionAlgs: map[uint16]CertCompressionAlg{
+ shrinkingCompressionAlgID: shrinkingCompression,
+ },
+ Bugs: ProtocolBugs{
+ ExpectedCompressedCert: shrinkingCompressionAlgID,
+ },
+ },
+ // Configure the shim and handshaker with different OCSP responses,
+ // so the compression inputs do not match.
+ shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ handshakerCertificate: rsaCertificate.WithOCSP(testOCSPResponse2),
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-install-cert-compression-algs",
+ },
+ expectations: connectionExpectations{
+ // The shim's configuration should take precendence.
+ peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ })
+
+ // The shim and handshaker may disagree on cipher suite, to the point
+ // that one selects RSA key exchange (no applicable hint) and the other
+ // selects ECDHE_RSA (hints are useful).
+ if protocol != quic {
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: protocol.String() + "-HintMismatch-CipherMismatch1",
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{
+ "-allow-hint-mismatch",
+ "-on-shim-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ "-on-handshaker-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
+ },
+ expectations: connectionExpectations{
+ cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: protocol.String() + "-HintMismatch-CipherMismatch2",
+ protocol: protocol,
+ skipSplitHandshake: true,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{
+ // There is no need to pass -allow-hint-mismatch. The
+ // handshaker will unnecessarily generate a signature hints.
+ // This is not reported as a mismatch because hints would
+ // not have helped the shim anyway.
+ "-on-shim-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
+ "-on-handshaker-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ },
+ expectations: connectionExpectations{
+ cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ })
+ }
+ }
+}
diff --git a/ssl/test/runner/jdk11_workaround_tests.go b/ssl/test/runner/jdk11_workaround_tests.go
new file mode 100644
index 0000000..b43baf3
--- /dev/null
+++ b/ssl/test/runner/jdk11_workaround_tests.go
@@ -0,0 +1,182 @@
+// 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 (
+ "fmt"
+ "strconv"
+)
+
+func addJDK11WorkaroundTests() {
+ // Test the client treats the JDK 11 downgrade random like the usual one.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "Client-RejectJDK11DowngradeRandom",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendJDK11DowngradeRandom: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TLS13_DOWNGRADE:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "Client-AcceptJDK11DowngradeRandom",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendJDK11DowngradeRandom: true,
+ },
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+
+ clientHelloTests := []struct {
+ clientHello []byte
+ isJDK11 bool
+ }{
+ {
+ // A default JDK 11 ClientHello.
+ decodeHexOrPanic("010001a9030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b5"),
+ true,
+ },
+ {
+ // The above with supported_versions and
+ // psk_key_exchange_modes in the wrong order.
+ decodeHexOrPanic("010001a9030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002d00020101002b00090803040303030203010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b5"),
+ false,
+ },
+ {
+ // The above with a padding extension added at the end.
+ decodeHexOrPanic("010001b4030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000111000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b50015000700000000000000"),
+ false,
+ },
+ {
+ // A JDK 11 ClientHello offering a TLS 1.3 PSK.
+ decodeHexOrPanic("0100024c0303a8d71b20f060545a398226e807d21371a7a02b7ca2f96f476c2dea7e5860c5a400005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff010001c9000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104aaec585ea9e121b24710a23560571322b2cf8ab8cd14e5762ef0486d8a6d0ecd721d8f2abda2eb8ed5ab7195505660450f49bba94bbf0c3f0070a531d9a1be4f002900cb00a600a0e6f7586d9a2bf64a54c1adf55a2f76657047e8e88e26629e2e7b9d630941e06fd87792770f6834e159a70b252157a9b4b082183f24629c8ff5049088b07ce37c49de8cf752a2ed7a545aff63bdc7a1b18e1bc201f23f159ee75d4987a04e00f840824f764691ab83a20e3032646e793065874cdb46138a52f50ed71406f399f96f9309eba4e5b1966148c22a63dc4aa1364269dd41dd5cc0e848d07af0095622c52cfcfc00212009cc315259e2328d65ad17a3de7c182c7874140a9356fecdd4614657806cd659"),
+ true,
+ },
+ {
+ // A JDK 11 ClientHello offering a TLS 1.2 session.
+ decodeHexOrPanic("010001a903038cdec49f4836d064a75046c93f22d0b9c2cf4900917332e6f0e1f41d692d3146201a3e99047492285ec65ab4e0eeee59f8f9d1eb7687398887bcd7b81353e93923005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d0002010100330047004500170041041c83c42fcd8fc06265b9f6e4f076f7e7ee17ace915c587845c0e1bc8cd177f904befeb611b682cae4702509a5f5d0c7162a282b8152d843169b91136e7c6f3e7"),
+ true,
+ },
+ {
+ // A JDK 11 ClientHello with EMS disabled.
+ decodeHexOrPanic("010001a50303323a857c324a9ef57d6e2544d129073830385cb1dc75ea79f6a2ec8ae09d2e7320f85fdd081678874c67ebab235e6d6a81d947f690bc0af9be4d39854ed67d9ef9005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000102000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b040105010601040203030301030202030201020200110009000702000400000000002b0009080304030303020301002d0002010100330047004500170041049c904c4850b495d75522f955d79e9cabea065c90279d6037a101a4c4ee712afc93ad0df5d12d287d53e458c7075d9a3ce3969c939bb62222bda779cecf54a603"),
+ true,
+ },
+ {
+ // A JDK 11 ClientHello with OCSP stapling disabled.
+ decodeHexOrPanic("0100019303038a50481dc85ee4f6581670821c50f2b3d34ac3251dc6e9b751bfd2521ab47ab02069a963c5486034c37ae0577ddb4c2db28cab592380ef8e4599d1305148712112005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff010000f0000000080006000003736e69000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b040105010601040203030301030202030201020200170000002b0009080304030303020301002d00020101003300470045001700410438a97824f842c549e3c339322d8b2dbaa85d10bd7bca9c969376cb0c60b1e929eb4d13db38dcb0082ad8c637b24f55466a9acbb0b63634c1f431ec8342cf720d"),
+ true,
+ },
+ {
+ // A JDK 11 ClientHello configured with a smaller set of
+ // ciphers.
+ decodeHexOrPanic("0100015603036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f4811800061301c02bc02f01000107000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
+ true,
+ },
+ {
+ // The above with TLS_CHACHA20_POLY1305_SHA256 added,
+ // which JDK 11 does not support.
+ decodeHexOrPanic("0100015803036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f48118000813011303c02bc02f01000107000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
+ false,
+ },
+ {
+ // The above with X25519 added, which JDK 11 does not
+ // support.
+ decodeHexOrPanic("0100015803036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f4811800061301c02bc02f01000109000000080006000003736e69000500050100000000000a00220020001d0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
+ false,
+ },
+ {
+ // A JDK 11 ClientHello with ALPN protocols configured.
+ decodeHexOrPanic("010001bb0303c0e0ea707b00c5311eb09cabd58626692cebfaefaef7265637e4550811dae16220da86d6eea04e214e873675223f08a6926bcf79f16d866280bdbab85e9e09c3ff005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000118000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020010000e000c02683208687474702f312e310011000900070200040000000000170000002b0009080304030303020301002d00020101003300470045001700410416def07c1d66ddde5fc9dcc328c8e77022d321c590c0d30cb41d515b38dca34540819a216c6c053bd47b9068f4f6b960f03647de4e36e8b7ffeea78f7252e3d9"),
+ true,
+ },
+ }
+ for i, t := range clientHelloTests {
+ expectedVersion := uint16(VersionTLS13)
+ if t.isJDK11 {
+ expectedVersion = VersionTLS12
+ }
+
+ // In each of these tests, we set DefaultCurves to P-256 to
+ // match the test inputs. SendClientHelloWithFixes requires the
+ // key_shares extension to match in type.
+
+ // With the workaround enabled, we should negotiate TLS 1.2 on
+ // JDK 11 ClientHellos.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: fmt.Sprintf("Server-JDK11-%d", i),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveP256},
+ Bugs: ProtocolBugs{
+ SendClientHelloWithFixes: t.clientHello,
+ ExpectJDK11DowngradeRandom: t.isJDK11,
+ },
+ },
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ flags: []string{"-jdk11-workaround"},
+ })
+
+ // With the workaround disabled, we always negotiate TLS 1.3.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: fmt.Sprintf("Server-JDK11-NoWorkaround-%d", i),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveP256},
+ Bugs: ProtocolBugs{
+ SendClientHelloWithFixes: t.clientHello,
+ ExpectJDK11DowngradeRandom: false,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS13,
+ },
+ })
+
+ // If the server does not support TLS 1.3, the workaround should
+ // be a no-op. In particular, it should not send the downgrade
+ // signal.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: fmt.Sprintf("Server-JDK11-TLS12-%d", i),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveP256},
+ Bugs: ProtocolBugs{
+ SendClientHelloWithFixes: t.clientHello,
+ ExpectJDK11DowngradeRandom: false,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ flags: []string{
+ "-jdk11-workaround",
+ "-max-version", strconv.Itoa(VersionTLS12),
+ },
+ })
+ }
+}
diff --git a/ssl/test/runner/key_update_tests.go b/ssl/test/runner/key_update_tests.go
new file mode 100644
index 0000000..0a90530
--- /dev/null
+++ b/ssl/test/runner/key_update_tests.go
@@ -0,0 +1,597 @@
+// 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 "slices"
+
+func addKeyUpdateTests() {
+ // TLS tests.
+ testCases = append(testCases, testCase{
+ name: "KeyUpdate-ToClient",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendKeyUpdates: 10,
+ keyUpdateRequest: keyUpdateNotRequested,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "KeyUpdate-ToServer",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendKeyUpdates: 10,
+ keyUpdateRequest: keyUpdateNotRequested,
+ })
+ testCases = append(testCases, testCase{
+ name: "KeyUpdate-FromClient",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ expectUnsolicitedKeyUpdate: true,
+ flags: []string{"-key-update"},
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "KeyUpdate-FromServer",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ expectUnsolicitedKeyUpdate: true,
+ flags: []string{"-key-update"},
+ })
+ testCases = append(testCases, testCase{
+ name: "KeyUpdate-InvalidRequestMode",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendKeyUpdates: 1,
+ keyUpdateRequest: 42,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+ testCases = append(testCases, testCase{
+ // Test that shim responds to KeyUpdate requests.
+ name: "KeyUpdate-Requested",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RejectUnsolicitedKeyUpdate: true,
+ },
+ },
+ // Test the shim receiving many KeyUpdates in a row.
+ sendKeyUpdates: 5,
+ messageCount: 5,
+ keyUpdateRequest: keyUpdateRequested,
+ })
+ testCases = append(testCases, testCase{
+ // Test that shim responds to KeyUpdate requests if peer's KeyUpdate is
+ // discovered while a write is pending.
+ name: "KeyUpdate-Requested-UnfinishedWrite",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RejectUnsolicitedKeyUpdate: true,
+ },
+ },
+ // Test the shim receiving many KeyUpdates in a row.
+ sendKeyUpdates: 5,
+ messageCount: 5,
+ keyUpdateRequest: keyUpdateRequested,
+ readWithUnfinishedWrite: true,
+ flags: []string{"-async"},
+ })
+
+ // DTLS tests.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-ToClient-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ // Send many KeyUpdates to make sure record reassembly can handle it.
+ sendKeyUpdates: 10,
+ keyUpdateRequest: keyUpdateNotRequested,
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "KeyUpdate-ToServer-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendKeyUpdates: 10,
+ keyUpdateRequest: keyUpdateNotRequested,
+ })
+
+ // Test that the shim accounts for packet loss when processing KeyUpdate.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-ToClient-PacketLoss-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[0].Type != typeKeyUpdate {
+ c.WriteFlight(next)
+ return
+ }
+
+ // Send the KeyUpdate. The shim should ACK it.
+ c.WriteFlight(next)
+ ackTimeout := timeouts[0] / 4
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // The shim should continue reading data at the old epoch.
+ // The ACK may not have come through.
+ msg := []byte("test")
+ c.WriteAppData(c.OutEpoch()-1, msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // Re-send KeyUpdate. The shim should ACK it again. The ACK
+ // may not have come through.
+ c.WriteFlight(next)
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // The shim should be able to read data at the new epoch.
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // The shim continues to accept application data at the old
+ // epoch, for a period of time.
+ c.WriteAppData(c.OutEpoch()-1, msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+
+ // It will even ACK the retransmission, though it knows the
+ // shim has seen the ACK.
+ c.WriteFlight(next)
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // After some time has passed, the shim will discard the old
+ // epoch. The following writes should be ignored.
+ c.AdvanceClock(dtlsPrevEpochExpiration)
+ f := next[0].Fragment(0, len(next[0].Data))
+ f.ShouldDiscard = true
+ c.WriteFragments([]DTLSFragment{f})
+ c.WriteAppData(c.OutEpoch()-1, msg)
+ },
+ },
+ },
+ sendKeyUpdates: 10,
+ keyUpdateRequest: keyUpdateNotRequested,
+ flags: []string{"-async"},
+ })
+
+ // In DTLS, we KeyUpdate before read, rather than write, because the
+ // KeyUpdate will not be applied before the shim reads the ACK.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-FromClient-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ // Perform several message exchanges to update keys several times.
+ messageCount: 10,
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "KeyUpdate-FromServer-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ // Perform several message exchanges to update keys several times.
+ messageCount: 10,
+ // Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
+ flags: []string{"-no-ticket"},
+ })
+
+ // If the shim has a pending unACKed flight, it defers sending KeyUpdate.
+ // BoringSSL does not support multiple outgoing flights at once.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-DeferredSend-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Request a client certificate, so the shim has more to send.
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 512,
+ ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if received[len(received)-1].Type != typeFinished {
+ c.WriteACK(c.OutEpoch(), records)
+ return
+ }
+
+ // This test relies on the Finished flight being multiple
+ // records.
+ if len(records) <= 1 {
+ panic("shim sent Finished flight in one record")
+ }
+
+ // Before ACKing Finished, do some rounds of exchanging
+ // application data. Although the shim has already scheduled
+ // KeyUpdate, it should not send the KeyUpdate until it gets
+ // an ACK. (If it sent KeyUpdate, ReadAppData would report
+ // an unexpected record.)
+ msg := []byte("test")
+ for i := 0; i < 10; i++ {
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+ }
+
+ // ACK some of the Finished flight, but not all of it.
+ c.WriteACK(c.OutEpoch(), records[:1])
+
+ // The shim continues to defer KeyUpdate.
+ for i := 0; i < 10; i++ {
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+ }
+
+ // ACK the remainder.
+ c.WriteACK(c.OutEpoch(), records[1:])
+
+ // The shim should now send KeyUpdate. Return to the test
+ // harness, which will look for it.
+ },
+ },
+ },
+ shimCertificate: &rsaChainCertificate,
+ shimSendsKeyUpdateBeforeRead: true,
+ flags: []string{"-mtu", "512"},
+ })
+
+ // The shim should not switch keys until it receives an ACK.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-WaitForACK-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ MaxPacketLength: 512,
+ ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if received[0].Type != typeKeyUpdate {
+ c.WriteACK(c.OutEpoch(), records)
+ return
+ }
+
+ // Make the shim send application data. We have not yet
+ // ACKed KeyUpdate, so the shim should send at the previous
+ // epoch. Through each of these rounds, the shim will also
+ // try to KeyUpdate again. These calls will be suppressed
+ // because there is still an outstanding KeyUpdate.
+ msg := []byte("test")
+ for i := 0; i < 10; i++ {
+ c.WriteAppData(c.OutEpoch(), msg)
+ c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
+ }
+
+ // ACK the KeyUpdate. Ideally we'd test a partial ACK, but
+ // BoringSSL's minimum MTU is such that KeyUpdate always
+ // fits in one record.
+ c.WriteACK(c.OutEpoch(), records)
+
+ // The shim should now send at the new epoch. Return to the
+ // test harness, which will enforce this.
+ },
+ },
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ })
+
+ // Test that shim responds to KeyUpdate requests.
+ fixKeyUpdateReply := func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+ c.WriteACK(c.OutEpoch(), records)
+ if received[0].Type != typeKeyUpdate {
+ return
+ }
+ // This works around an awkward testing mismatch. The test
+ // harness expects the shim to immediately change keys, but
+ // the shim writes app data before seeing the ACK. The app
+ // data will be sent at the previous epoch. Consume this and
+ // prime the shim to resend its reply at the new epoch.
+ msg := makeTestMessage(int(received[0].Sequence)-2, 32)
+ c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
+ c.WriteAppData(c.OutEpoch(), msg)
+ }
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-Requested-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RejectUnsolicitedKeyUpdate: true,
+ ACKFlightDTLS: fixKeyUpdateReply,
+ },
+ },
+ // Test the shim receiving many KeyUpdates in a row. They will be
+ // combined into one reply KeyUpdate.
+ sendKeyUpdates: 5,
+ messageLen: 32,
+ messageCount: 5,
+ keyUpdateRequest: keyUpdateRequested,
+ })
+
+ mergeNewSessionTicketAndKeyUpdate := func(f WriteFlightFunc) WriteFlightFunc {
+ return func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // Send NewSessionTicket and the first KeyUpdate all together.
+ if next[0].Type == typeKeyUpdate {
+ panic("key update should have been merged into NewSessionTicket")
+ }
+ if next[0].Type != typeNewSessionTicket {
+ c.WriteFlight(next)
+ return
+ }
+ if next[0].Type == typeNewSessionTicket && next[len(next)-1].Type != typeKeyUpdate {
+ c.MergeIntoNextFlight()
+ return
+ }
+
+ f(c, prev, received, next, records)
+ }
+ }
+
+ // Test that the shim does not process KeyUpdate until it has processed all
+ // preceding messages.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-ProcessInOrder-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // Write the KeyUpdate. The shim should buffer and ACK it.
+ keyUpdate := next[len(next)-1]
+ c.WriteFlight([]DTLSMessage{keyUpdate})
+ ackTimeout := timeouts[0] / 4
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // The shim should not process KeyUpdate yet. It should not
+ // read from the new epoch.
+ msg1, msg2 := []byte("aaaa"), []byte("bbbb")
+ c.WriteAppData(c.OutEpoch(), msg1)
+ c.AdvanceClock(0) // Check there are no messages.
+
+ // It can read from the old epoch, however.
+ c.WriteAppData(c.OutEpoch()-1, msg2)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg2))
+
+ // Write the rest of the flight.
+ c.WriteFlight(next[:len(next)-1])
+ c.AdvanceClock(ackTimeout)
+ c.ReadACK(c.InEpoch())
+
+ // Now the new epoch is functional.
+ c.WriteAppData(c.OutEpoch(), msg1)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg1))
+ }),
+ },
+ },
+ sendKeyUpdates: 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ flags: []string{"-async"},
+ })
+
+ // Messages after a KeyUpdate are not allowed.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-ExtraMessage-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ extra := next[0]
+ extra.Sequence = next[len(next)-1].Sequence + 1
+ next = append(slices.Clip(next), extra)
+ c.WriteFlight(next)
+ }),
+ },
+ },
+ sendKeyUpdates: 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-ExtraMessageBuffered-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ // Send the extra message first. The shim should accept and
+ // buffer it.
+ extra := next[0]
+ extra.Sequence = next[len(next)-1].Sequence + 1
+ c.WriteFlight([]DTLSMessage{extra})
+
+ // Now send the flight, including a KeyUpdate. The shim
+ // should now notice the extra message and reject.
+ c.WriteFlight(next)
+ }),
+ },
+ },
+ sendKeyUpdates: 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Test KeyUpdate overflow conditions. Both the epoch number and the message
+ // number may overflow, in either the read or write direction.
+
+ // When the sender is the client, the first KeyUpdate is message 2 at epoch
+ // 3, so the epoch number overflows first.
+ const maxClientKeyUpdates = 0xffff - 3
+
+ // Test that the shim, as a server, rejects KeyUpdates at epoch 0xffff. RFC
+ // 9147 does not prescribe this limit, but we enforce it. See
+ // https://mailarchive.ietf.org/arch/msg/tls/6y8wTv8Q_IPM-PCcbCAmDOYg6bM/
+ // and https://www.rfc-editor.org/errata/eid8050
+ writeFlightKeyUpdate := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ if next[0].Type == typeKeyUpdate {
+ // Exchange some data to avoid tripping KeyUpdate DoS limits.
+ msg := []byte("test")
+ c.WriteAppData(c.OutEpoch()-1, msg)
+ c.ReadAppData(c.InEpoch(), expectedReply(msg))
+ }
+ c.WriteFlight(next)
+ }
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "KeyUpdate-MaxReadEpoch-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AllowEpochOverflow: true,
+ WriteFlightDTLS: writeFlightKeyUpdate,
+ },
+ },
+ // Avoid the NewSessionTicket messages interfering with the callback.
+ flags: []string{"-no-ticket"},
+ sendKeyUpdates: maxClientKeyUpdates,
+ keyUpdateRequest: keyUpdateNotRequested,
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: dtls,
+ name: "KeyUpdate-ReadEpochOverflow-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AllowEpochOverflow: true,
+ WriteFlightDTLS: writeFlightKeyUpdate,
+ },
+ },
+ // Avoid the NewSessionTicket messages interfering with the callback.
+ flags: []string{"-no-ticket"},
+ sendKeyUpdates: maxClientKeyUpdates + 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ shouldFail: true,
+ expectedError: ":TOO_MANY_KEY_UPDATES:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Test that the shim, as a client, notices its epoch overflow condition
+ // when asked to send too many KeyUpdates. The shim sends KeyUpdate before
+ // every read, including reading connection close, so the number of
+ // KeyUpdates is one more than the message count.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-MaxWriteEpoch-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ messageCount: maxClientKeyUpdates - 1,
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-WriteEpochOverflow-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // The shim does not notice the overflow until immediately after
+ // sending KeyUpdate, so tolerate the overflow on the runner.
+ AllowEpochOverflow: true,
+ },
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ messageCount: maxClientKeyUpdates,
+ shouldFail: true,
+ expectedError: ":TOO_MANY_KEY_UPDATES:",
+ })
+
+ // When the sender is a server that doesn't send tickets, the first
+ // KeyUpdate is message 5 (SH, EE, C, CV, Fin) at epoch 3, so the message
+ // number overflows first.
+ const maxServerKeyUpdates = 0xffff - 5
+
+ // Test that the shim, as a client, does not allow the value to wraparound.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "KeyUpdate-ReadMessageOverflow-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ AllowEpochOverflow: true,
+ WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
+ writeFlightKeyUpdate(c, prev, received, next, records)
+ if next[0].Type == typeKeyUpdate && next[0].Sequence == 0xffff {
+ // At this point, the shim has accepted message 0xffff.
+ // Check the shim does not now accept message 0 as the
+ // current message. Test this by sending a garbage
+ // message 0. A shim that overflows and processes the
+ // message will notice the syntax error. A shim that
+ // correctly interprets this as an old message will drop
+ // the record and simply ACK it.
+ //
+ // We do this rather than send a valid KeyUpdate because
+ // the shim will keep the old epoch active and drop
+ // decryption failures. Looking for the lack of an error
+ // is more straightforward.
+ c.WriteFlight([]DTLSMessage{{Epoch: c.OutEpoch(), Sequence: 0, Type: typeKeyUpdate, Data: []byte("INVALID")}})
+ c.ExpectNextTimeout(timeouts[0] / 4)
+ c.AdvanceClock(timeouts[0] / 4)
+ c.ReadACK(c.InEpoch())
+ }
+ },
+ },
+ },
+ sendKeyUpdates: maxServerKeyUpdates + 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ flags: []string{"-async", "-expect-no-session"},
+ })
+
+ // Test that the shim, as a server, notices its message overflow condition,
+ // when asked to send too many KeyUpdates.
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "KeyUpdate-MaxWriteMessage-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ messageCount: maxServerKeyUpdates,
+ // Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
+ flags: []string{"-no-ticket"},
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "KeyUpdate-WriteMessageOverflow-DTLS",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ shimSendsKeyUpdateBeforeRead: true,
+ messageCount: maxServerKeyUpdates + 1,
+ shouldFail: true,
+ expectedError: ":overflow:",
+ // Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
+ flags: []string{"-no-ticket"},
+ })
+}
diff --git a/ssl/test/runner/key_usage_tests.go b/ssl/test/runner/key_usage_tests.go
new file mode 100644
index 0000000..8c1df84
--- /dev/null
+++ b/ssl/test/runner/key_usage_tests.go
@@ -0,0 +1,249 @@
+// 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/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "math/big"
+ "time"
+)
+
+func addECDSAKeyUsageTests() {
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ panic(err)
+ }
+
+ template := &x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"Acme Co"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now(),
+
+ // An ECC certificate with only the keyAgreement key usgae may
+ // be used with ECDH, but not ECDSA.
+ KeyUsage: x509.KeyUsageKeyAgreement,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ BasicConstraintsValid: true,
+ }
+
+ cert := generateSingleCertChain(template, &ecdsaP256Key)
+
+ for _, ver := range tlsVersions {
+ if ver.version < VersionTLS12 {
+ continue
+ }
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ECDSAKeyUsage-Client-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &cert,
+ },
+ shouldFail: true,
+ expectedError: ":KEY_USAGE_BIT_INCORRECT:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ECDSAKeyUsage-Server-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &cert,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ shouldFail: true,
+ expectedError: ":KEY_USAGE_BIT_INCORRECT:",
+ })
+ }
+}
+
+func addRSAKeyUsageTests() {
+ priv := rsaCertificate.PrivateKey.(*rsa.PrivateKey)
+
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ panic(err)
+ }
+
+ dsTemplate := x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"Acme Co"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now(),
+
+ KeyUsage: x509.KeyUsageDigitalSignature,
+ BasicConstraintsValid: true,
+ }
+
+ encTemplate := x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"Acme Co"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now(),
+
+ KeyUsage: x509.KeyUsageKeyEncipherment,
+ BasicConstraintsValid: true,
+ }
+
+ dsCert := generateSingleCertChain(&dsTemplate, priv)
+
+ encCert := generateSingleCertChain(&encTemplate, priv)
+
+ dsSuites := []uint16{
+ TLS_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ }
+ encSuites := []uint16{
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ }
+
+ for _, ver := range tlsVersions {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &encCert,
+ CipherSuites: dsSuites,
+ },
+ shouldFail: true,
+ expectedError: ":KEY_USAGE_BIT_INCORRECT:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantSignature-GotSignature-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &dsCert,
+ CipherSuites: dsSuites,
+ },
+ })
+
+ // TLS 1.3 removes the encipherment suites.
+ if ver.version < VersionTLS13 {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantEncipherment-GotEncipherment" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &encCert,
+ CipherSuites: encSuites,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantEncipherment-GotSignature-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &dsCert,
+ CipherSuites: encSuites,
+ },
+ shouldFail: true,
+ expectedError: ":KEY_USAGE_BIT_INCORRECT:",
+ })
+
+ // In 1.2 and below, we should not enforce without the enforce-rsa-key-usage flag.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-Unenforced-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &dsCert,
+ CipherSuites: encSuites,
+ },
+ flags: []string{"-expect-key-usage-invalid", "-ignore-rsa-key-usage"},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantEncipherment-GotSignature-Unenforced-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &encCert,
+ CipherSuites: dsSuites,
+ },
+ flags: []string{"-expect-key-usage-invalid", "-ignore-rsa-key-usage"},
+ })
+ }
+
+ if ver.version >= VersionTLS13 {
+ // In 1.3 and above, we enforce keyUsage even when disabled.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-AlwaysEnforced-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &encCert,
+ CipherSuites: dsSuites,
+ },
+ flags: []string{"-ignore-rsa-key-usage"},
+ shouldFail: true,
+ expectedError: ":KEY_USAGE_BIT_INCORRECT:",
+ })
+ }
+
+ // The server only uses signatures and always enforces it.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RSAKeyUsage-Server-WantSignature-GotEncipherment-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &encCert,
+ },
+ shouldFail: true,
+ expectedError: ":KEY_USAGE_BIT_INCORRECT:",
+ flags: []string{"-require-any-client-certificate"},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RSAKeyUsage-Server-WantSignature-GotSignature-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Credential: &dsCert,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ })
+
+ }
+}
diff --git a/ssl/test/runner/pake_tests.go b/ssl/test/runner/pake_tests.go
new file mode 100644
index 0000000..ddd2eb3
--- /dev/null
+++ b/ssl/test/runner/pake_tests.go
@@ -0,0 +1,513 @@
+// 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 "errors"
+
+func addPAKETests() {
+ spakeCredential := Credential{
+ Type: CredentialTypeSPAKE2PlusV1,
+ PAKEContext: []byte("context"),
+ PAKEClientID: []byte("client"),
+ PAKEServerID: []byte("server"),
+ PAKEPassword: []byte("password"),
+ }
+
+ spakeWrongClientID := spakeCredential
+ spakeWrongClientID.PAKEClientID = []byte("wrong")
+
+ spakeWrongServerID := spakeCredential
+ spakeWrongServerID.PAKEServerID = []byte("wrong")
+
+ spakeWrongPassword := spakeCredential
+ spakeWrongPassword.PAKEPassword = []byte("wrong")
+
+ spakeWrongRole := spakeCredential
+ spakeWrongRole.WrongPAKERole = true
+
+ spakeWrongCodepoint := spakeCredential
+ spakeWrongCodepoint.OverridePAKECodepoint = 1234
+
+ testCases = append(testCases, testCase{
+ name: "PAKE-No-Server-Support",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ },
+ shouldFail: true,
+ expectedError: ":MISSING_KEY_SHARE:",
+ })
+ testCases = append(testCases, testCase{
+ name: "PAKE-Server",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ // We do not currently support resumption with PAKE, so PAKE
+ // servers should not issue session tickets.
+ ExpectNoNewSessionTicket: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ })
+ testCases = append(testCases, testCase{
+ // Send a ClientHello with the wrong PAKE client ID.
+ name: "PAKE-Server-WrongClientID",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeWrongClientID,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":PEER_PAKE_MISMATCH:",
+ expectedLocalError: "remote error: handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ // Send a ClientHello with the wrong PAKE server ID.
+ name: "PAKE-Server-WrongServerID",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeWrongServerID,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":PEER_PAKE_MISMATCH:",
+ expectedLocalError: "remote error: handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ // Send a ClientHello with the wrong PAKE codepoint.
+ name: "PAKE-Server-WrongCodepoint",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeWrongCodepoint,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":PEER_PAKE_MISMATCH:",
+ expectedLocalError: "remote error: handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ // A server configured with a mix of PAKE and non-PAKE
+ // credentials will select the first that matches what the
+ // client offered. In doing so, it should skip unsupported
+ // PAKE algorithms.
+ name: "PAKE-Server-MultiplePAKEs",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ OfferExtraPAKEs: []uint16{1, 2, 3, 4, 5},
+ },
+ },
+ shimCredentials: []*Credential{&spakeWrongClientID, &spakeWrongServerID, &spakeWrongRole, &spakeCredential, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "3"},
+ })
+ testCases = append(testCases, testCase{
+ // A server configured with a certificate credential before a
+ // PAKE credential will consider the certificate credential first.
+ name: "PAKE-Server-CertificateBeforePAKE",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Pretend to offer a matching PAKE share, but expect the
+ // shim to select the credential first and negotiate a
+ // normal handshake.
+ OfferExtraPAKEClientID: spakeCredential.PAKEClientID,
+ OfferExtraPAKEServerID: spakeCredential.PAKEServerID,
+ OfferExtraPAKEs: []uint16{spakeID},
+ },
+ },
+ shimCredentials: []*Credential{&rsaCertificate, &spakeCredential},
+ flags: []string{"-expect-selected-credential", "0"},
+ })
+ testCases = append(testCases, testCase{
+ // A server configured with just a PAKE credential should reject normal
+ // clients.
+ name: "PAKE-Server-NormalClient",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":PEER_PAKE_MISMATCH:",
+ expectedLocalError: "remote error: handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ // ... and TLS 1.2 clients.
+ name: "PAKE-Server-NormalTLS12Client",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":NO_SHARED_CIPHER:",
+ expectedLocalError: "remote error: handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ // ... but you can configure a server with both PAKE and certificate-based
+ // SSL_CREDENTIALs and that works.
+ name: "PAKE-ServerWithCertsToo-NormalClient",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ },
+ shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "1"},
+ })
+ testCases = append(testCases, testCase{
+ // ... and for older clients.
+ name: "PAKE-ServerWithCertsToo-NormalTLS12Client",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
+ flags: []string{"-expect-selected-credential", "1"},
+ })
+ testCases = append(testCases, testCase{
+ name: "PAKE-Client",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ CheckClientHello: func(c *clientHelloMsg) error {
+ // PAKE connections don't use the key_share / supported_groups mechanism.
+ if c.hasKeyShares {
+ return errors.New("unexpected key_share extension")
+ }
+ if len(c.supportedCurves) != 0 {
+ return errors.New("unexpected supported_groups extension")
+ }
+ // PAKE connections don't use signature algorithms.
+ if len(c.signatureAlgorithms) != 0 {
+ return errors.New("unexpected signature_algorithms extension")
+ }
+ // We don't support resumption with PAKEs.
+ if len(c.pskKEModes) != 0 {
+ return errors.New("unexpected psk_key_exchange_modes extension")
+ }
+ return nil
+ },
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ })
+ testCases = append(testCases, testCase{
+ // Although there is no reason to request new key shares, the PAKE
+ // client should handle cookie requests.
+ name: "PAKE-Client-HRRCookie",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte("cookie"),
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ })
+ testCases = append(testCases, testCase{
+ // A PAKE client will not offer key shares, so the client should
+ // reject a HelloRetryRequest requesting a different key share.
+ name: "PAKE-Client-HRRKeyShare",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCurve: CurveX25519,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+ testCases = append(testCases, testCase{
+ // A server cannot reply with an HRR asking for a PAKE if the client didn't
+ // offer a PAKE in the ClientHello.
+ name: "PAKE-NormalClient-PAKEInHRR",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ AlwaysSendHelloRetryRequest: true,
+ SendPAKEInHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ // A PAKE client should not accept an empty ServerHello.
+ name: "PAKE-Client-EmptyServerHello",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Trigger an empty ServerHello by making a normal server skip
+ // the key_share extension.
+ MissingKeyShare: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":MISSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ // A PAKE client should not accept a key_share ServerHello.
+ name: "PAKE-Client-KeyShareServerHello",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Trigger a key_share ServerHello by making a normal server
+ // skip the HelloRetryRequest it would otherwise send in
+ // response to the shim's key_share-less ClientHello.
+ SkipHelloRetryRequest: true,
+ // Ignore the client's lack of supported_groups.
+ IgnorePeerCurvePreferences: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ // A PAKE client should not accept a TLS 1.2 ServerHello.
+ name: "PAKE-Client-TLS12ServerHello",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+ testCases = append(testCases, testCase{
+ // A server cannot send the PAKE extension to a non-PAKE client.
+ name: "PAKE-NormalClient-UnsolicitedPAKEInServerHello",
+ testType: clientTest,
+ config: Config{
+ Bugs: ProtocolBugs{
+ UnsolicitedPAKE: spakeID,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ // A server cannot reply with a PAKE that the client did not offer.
+ name: "PAKE-Client-WrongPAKEInServerHello",
+ testType: clientTest,
+ config: Config{
+ Bugs: ProtocolBugs{
+ UnsolicitedPAKE: 1234,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+ testCases = append(testCases, testCase{
+ name: "PAKE-Extension-Duplicate",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ OfferExtraPAKEClientID: []byte("client"),
+ OfferExtraPAKEServerID: []byte("server"),
+ OfferExtraPAKEs: []uint16{1234, 1234},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ // If the client sees a server with a wrong password, it should
+ // reject the confirmV value in the ServerHello.
+ name: "PAKE-Client-WrongPassword",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeWrongPassword,
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+ testCases = append(testCases, testCase{
+ name: "PAKE-Client-Truncate",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ TruncatePAKEMessage: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+ testCases = append(testCases, testCase{
+ name: "PAKE-Server-Truncate",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ TruncatePAKEMessage: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+ testCases = append(testCases, testCase{
+ // Servers may not send CertificateRequest in a PAKE handshake.
+ name: "PAKE-Client-UnexpectedCertificateRequest",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ AlwaysSendCertificateRequest: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ testCases = append(testCases, testCase{
+ // Servers may not send Certificate in a PAKE handshake.
+ name: "PAKE-Client-UnexpectedCertificate",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ AlwaysSendCertificate: true,
+ UseCertificateCredential: &rsaCertificate,
+ // Ignore the client's lack of signature_algorithms.
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ },
+ },
+ shimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ testCases = append(testCases, testCase{
+ // If a server is configured to request client certificates, it should
+ // still not do so when negotiating a PAKE.
+ name: "PAKE-Server-DoNotRequestClientCertificate",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ },
+ shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
+ flags: []string{"-require-any-client-certificate"},
+ })
+ testCases = append(testCases, testCase{
+ // Clients should ignore server PAKE credentials.
+ name: "PAKE-Client-WrongRole",
+ testType: clientTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ },
+ shimCredentials: []*Credential{&spakeWrongRole},
+ shouldFail: true,
+ // The shim will send a non-PAKE ClientHello.
+ expectedLocalError: "tls: client not configured with PAKE",
+ })
+ testCases = append(testCases, testCase{
+ // Servers should ignore client PAKE credentials.
+ name: "PAKE-Server-WrongRole",
+ testType: serverTest,
+ config: Config{
+ MinVersion: VersionTLS13,
+ Credential: &spakeCredential,
+ },
+ shimCredentials: []*Credential{&spakeWrongRole},
+ shouldFail: true,
+ // The shim will fail the handshake because it has no usable credentials
+ // available.
+ expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+ expectedLocalError: "remote error: handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ // On the client, we only support a single PAKE credential.
+ name: "PAKE-Client-MultiplePAKEs",
+ testType: clientTest,
+ shimCredentials: []*Credential{&spakeCredential, &spakeWrongPassword},
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
+ })
+ testCases = append(testCases, testCase{
+ // On the client, we only support a single PAKE credential.
+ name: "PAKE-Client-PAKEAndCertificate",
+ testType: clientTest,
+ shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
+ })
+ testCases = append(testCases, testCase{
+ // We currently do not support resumption with PAKE. Even if configured
+ // with a session, the client should not offer the session with PAKEs.
+ name: "PAKE-Client-NoResume",
+ testType: clientTest,
+ // Make two connections. For the first connection, just establish a
+ // session without PAKE, to pick up a session.
+ config: Config{
+ Credential: &rsaCertificate,
+ },
+ // For the second connection, use SPAKE.
+ resumeSession: true,
+ resumeConfig: &Config{
+ Credential: &spakeCredential,
+ Bugs: ProtocolBugs{
+ // Check that the ClientHello does not offer a session, even
+ // though one was configured.
+ ExpectNoTLS13PSK: true,
+ // Respond with an unsolicted PSK extension in ServerHello, to
+ // check that the client rejects it.
+ AlwaysSelectPSKIdentity: true,
+ },
+ },
+ resumeShimCredentials: []*Credential{&spakeCredential},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+}
diff --git a/ssl/test/runner/peek_tests.go b/ssl/test/runner/peek_tests.go
new file mode 100644
index 0000000..368903a
--- /dev/null
+++ b/ssl/test/runner/peek_tests.go
@@ -0,0 +1,85 @@
+// 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
+
+func addPeekTests() {
+ // Test SSL_peek works, including on empty records.
+ testCases = append(testCases, testCase{
+ name: "Peek-Basic",
+ sendEmptyRecords: 1,
+ flags: []string{"-peek-then-read"},
+ })
+
+ // Test SSL_peek can drive the initial handshake.
+ testCases = append(testCases, testCase{
+ name: "Peek-ImplicitHandshake",
+ flags: []string{
+ "-peek-then-read",
+ "-implicit-handshake",
+ },
+ })
+
+ // Test SSL_peek can discover and drive a renegotiation.
+ testCases = append(testCases, testCase{
+ name: "Peek-Renegotiate",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-peek-then-read",
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+
+ // Test SSL_peek can discover a close_notify.
+ testCases = append(testCases, testCase{
+ name: "Peek-Shutdown",
+ config: Config{
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ flags: []string{
+ "-peek-then-read",
+ "-check-close-notify",
+ },
+ })
+
+ // Test SSL_peek can discover an alert.
+ testCases = append(testCases, testCase{
+ name: "Peek-Alert",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSpuriousAlert: alertRecordOverflow,
+ },
+ },
+ flags: []string{"-peek-then-read"},
+ shouldFail: true,
+ expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
+ })
+
+ // Test SSL_peek can handle KeyUpdate.
+ testCases = append(testCases, testCase{
+ name: "Peek-KeyUpdate",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ sendKeyUpdates: 1,
+ keyUpdateRequest: keyUpdateNotRequested,
+ flags: []string{"-peek-then-read"},
+ })
+}
diff --git a/ssl/test/runner/per_message_tests.go b/ssl/test/runner/per_message_tests.go
new file mode 100644
index 0000000..5ef58d5
--- /dev/null
+++ b/ssl/test/runner/per_message_tests.go
@@ -0,0 +1,437 @@
+// 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
+
+type perMessageTest struct {
+ messageType uint8
+ test testCase
+}
+
+// makePerMessageTests returns a series of test templates which cover each
+// message in the TLS handshake. These may be used with bugs like
+// WrongMessageType to fully test a per-message bug.
+func makePerMessageTests() []perMessageTest {
+ var ret []perMessageTest
+ // The following tests are limited to TLS 1.2, so QUIC is not tested.
+ for _, protocol := range []protocol{tls, dtls} {
+ suffix := "-" + protocol.String()
+
+ ret = append(ret, perMessageTest{
+ messageType: typeClientHello,
+ test: testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "ClientHello" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ if protocol == dtls {
+ ret = append(ret, perMessageTest{
+ messageType: typeHelloVerifyRequest,
+ test: testCase{
+ protocol: protocol,
+ name: "HelloVerifyRequest" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+ }
+
+ ret = append(ret, perMessageTest{
+ messageType: typeServerHello,
+ test: testCase{
+ protocol: protocol,
+ name: "ServerHello" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificate,
+ test: testCase{
+ protocol: protocol,
+ name: "ServerCertificate" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificateStatus,
+ test: testCase{
+ protocol: protocol,
+ name: "CertificateStatus" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ flags: []string{"-enable-ocsp-stapling"},
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeServerKeyExchange,
+ test: testCase{
+ protocol: protocol,
+ name: "ServerKeyExchange" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificateRequest,
+ test: testCase{
+ protocol: protocol,
+ name: "CertificateRequest" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeServerHelloDone,
+ test: testCase{
+ protocol: protocol,
+ name: "ServerHelloDone" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificate,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "ClientCertificate" + suffix,
+ config: Config{
+ Credential: &rsaCertificate,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificateVerify,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "CertificateVerify" + suffix,
+ config: Config{
+ Credential: &rsaCertificate,
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeClientKeyExchange,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "ClientKeyExchange" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ if protocol != dtls {
+ ret = append(ret, perMessageTest{
+ messageType: typeNextProtocol,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "NextProtocol" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"bar"},
+ },
+ flags: []string{"-advertise-npn", "\x03foo\x03bar\x03baz"},
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeChannelID,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "ChannelID" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ChannelID: &channelIDKey,
+ },
+ flags: []string{
+ "-expect-channel-id",
+ base64FlagValue(channelIDBytes),
+ },
+ },
+ })
+ }
+
+ ret = append(ret, perMessageTest{
+ messageType: typeFinished,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "ClientFinished" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeNewSessionTicket,
+ test: testCase{
+ protocol: protocol,
+ name: "NewSessionTicket" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeFinished,
+ test: testCase{
+ protocol: protocol,
+ name: "ServerFinished" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ },
+ })
+
+ }
+
+ for _, protocol := range []protocol{tls, quic, dtls} {
+ suffix := "-" + protocol.String()
+ ret = append(ret, perMessageTest{
+ messageType: typeClientHello,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "TLS13-ClientHello" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeServerHello,
+ test: testCase{
+ name: "TLS13-ServerHello" + suffix,
+ protocol: protocol,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeEncryptedExtensions,
+ test: testCase{
+ name: "TLS13-EncryptedExtensions" + suffix,
+ protocol: protocol,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificateRequest,
+ test: testCase{
+ name: "TLS13-CertificateRequest" + suffix,
+ protocol: protocol,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificate,
+ test: testCase{
+ name: "TLS13-ServerCertificate" + suffix,
+ protocol: protocol,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificateVerify,
+ test: testCase{
+ name: "TLS13-ServerCertificateVerify" + suffix,
+ protocol: protocol,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeFinished,
+ test: testCase{
+ name: "TLS13-ServerFinished" + suffix,
+ protocol: protocol,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificate,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "TLS13-ClientCertificate" + suffix,
+ config: Config{
+ Credential: &rsaCertificate,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeCertificateVerify,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "TLS13-ClientCertificateVerify" + suffix,
+ config: Config{
+ Credential: &rsaCertificate,
+ MaxVersion: VersionTLS13,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ },
+ })
+
+ ret = append(ret, perMessageTest{
+ messageType: typeFinished,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "TLS13-ClientFinished" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ },
+ })
+
+ // Only TLS uses EndOfEarlyData.
+ if protocol == tls {
+ ret = append(ret, perMessageTest{
+ messageType: typeEndOfEarlyData,
+ test: testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "TLS13-EndOfEarlyData" + suffix,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ },
+ })
+ }
+ }
+
+ return ret
+}
+
+func addWrongMessageTypeTests() {
+ for _, t := range makePerMessageTests() {
+ t.test.name = "WrongMessageType-" + t.test.name
+ if t.test.resumeConfig != nil {
+ t.test.resumeConfig.Bugs.SendWrongMessageType = t.messageType
+ } else {
+ t.test.config.Bugs.SendWrongMessageType = t.messageType
+ }
+ t.test.shouldFail = true
+ t.test.expectedError = ":UNEXPECTED_MESSAGE:"
+ t.test.expectedLocalError = "remote error: unexpected message"
+
+ if t.test.config.MaxVersion >= VersionTLS13 && t.messageType == typeServerHello {
+ // In TLS 1.3, if the server believes it has sent ServerHello,
+ // but the client cannot process it, the client will send an
+ // unencrypted alert while the server expects encryption. This
+ // decryption failure is reported differently for each protocol, so
+ // leave it unchecked.
+ t.test.expectedLocalError = ""
+ }
+
+ testCases = append(testCases, t.test)
+ }
+}
+
+func addTrailingMessageDataTests() {
+ for _, t := range makePerMessageTests() {
+ t.test.name = "TrailingMessageData-" + t.test.name
+ if t.test.resumeConfig != nil {
+ t.test.resumeConfig.Bugs.SendTrailingMessageData = t.messageType
+ } else {
+ t.test.config.Bugs.SendTrailingMessageData = t.messageType
+ }
+ t.test.shouldFail = true
+ t.test.expectedError = ":DECODE_ERROR:"
+ t.test.expectedLocalError = "remote error: error decoding message"
+
+ if t.test.config.MaxVersion >= VersionTLS13 && t.messageType == typeServerHello {
+ // In TLS 1.3, if the server believes it has sent ServerHello,
+ // but the client cannot process it, the client will send an
+ // unencrypted alert while the server expects encryption. This
+ // decryption failure is reported differently for each protocol, so
+ // leave it unchecked.
+ t.test.expectedLocalError = ""
+ }
+
+ if t.messageType == typeClientHello {
+ // We have a different error for ClientHello parsing.
+ t.test.expectedError = ":CLIENTHELLO_PARSE_FAILED:"
+ }
+
+ if t.messageType == typeFinished {
+ // Bad Finished messages read as the verify data having
+ // the wrong length.
+ t.test.expectedError = ":DIGEST_CHECK_FAILED:"
+ t.test.expectedLocalError = "remote error: error decrypting message"
+ }
+
+ testCases = append(testCases, t.test)
+ }
+}
diff --git a/ssl/test/runner/renegotiation_tests.go b/ssl/test/runner/renegotiation_tests.go
new file mode 100644
index 0000000..02dbc19
--- /dev/null
+++ b/ssl/test/runner/renegotiation_tests.go
@@ -0,0 +1,546 @@
+// 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
+
+func addRenegotiationTests() {
+ // Servers cannot renegotiate.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Renegotiate-Server-Forbidden",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ expectedLocalError: "remote error: no renegotiation",
+ })
+ // The server shouldn't echo the renegotiation extension unless
+ // requested by the client.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Renegotiate-Server-NoExt",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfo: true,
+ RequireRenegotiationInfo: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "renegotiation extension missing",
+ })
+ // The renegotiation SCSV should be sufficient for the server to echo
+ // the extension.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Renegotiate-Server-NoExt-SCSV",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfo: true,
+ SendRenegotiationSCSV: true,
+ RequireRenegotiationInfo: true,
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ FailIfResumeOnRenego: true,
+ },
+ },
+ renegotiate: 1,
+ // Test renegotiation after both an initial and resumption
+ // handshake.
+ resumeSession: true,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ "-expect-secure-renegotiation",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ FailIfResumeOnRenego: true,
+ },
+ },
+ renegotiate: 1,
+ // Test renegotiation after both an initial and resumption
+ // handshake.
+ resumeSession: true,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ "-expect-secure-renegotiation",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-EmptyExt",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ EmptyRenegotiationInfo: true,
+ },
+ },
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_MISMATCH:",
+ expectedLocalError: "handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-BadExt",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadRenegotiationInfo: true,
+ },
+ },
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_MISMATCH:",
+ expectedLocalError: "handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-BadExt2",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ BadRenegotiationInfoEnd: true,
+ },
+ },
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_MISMATCH:",
+ expectedLocalError: "handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Downgrade",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfoAfterInitial: true,
+ },
+ },
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_MISMATCH:",
+ expectedLocalError: "handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Upgrade",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfoInInitial: true,
+ },
+ },
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":RENEGOTIATION_MISMATCH:",
+ expectedLocalError: "handshake failure",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-NoExt-Allowed",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfo: true,
+ },
+ },
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ "-expect-no-secure-renegotiation",
+ },
+ })
+
+ // Test that the server may switch ciphers on renegotiation without
+ // problems.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-SwitchCiphers",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ },
+ renegotiateCiphers: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-SwitchCiphers2",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ renegotiateCiphers: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+
+ // Test that the server may not switch versions on renegotiation.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-SwitchVersion",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ // Pick a cipher which exists at both versions.
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ NegotiateVersionOnRenego: VersionTLS11,
+ // Avoid failing early at the record layer.
+ SendRecordVersion: VersionTLS12,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SSL_VERSION:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-SameClientVersion",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS10,
+ Bugs: ProtocolBugs{
+ RequireSameRenegoClientVersion: true,
+ },
+ },
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-FalseStart",
+ renegotiate: 1,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{
+ "-false-start",
+ "-select-next-proto", "foo",
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ shimWritesFirst: true,
+ })
+
+ // Client-side renegotiation controls.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Forbidden-1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ expectedLocalError: "remote error: no renegotiation",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Once-1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-once",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Freely-1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Once-2",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 2,
+ flags: []string{"-renegotiate-once"},
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ expectedLocalError: "remote error: no renegotiation",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Freely-2",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 2,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "2",
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-NoIgnore",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendHelloRequestBeforeEveryAppDataRecord: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Ignore",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendHelloRequestBeforeEveryAppDataRecord: true,
+ },
+ },
+ flags: []string{
+ "-renegotiate-ignore",
+ "-expect-total-renegotiations", "0",
+ },
+ })
+
+ // Renegotiation may be enabled and then disabled immediately after the
+ // handshake.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-ForbidAfterHandshake",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{"-forbid-renegotiation-after-handshake"},
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ expectedLocalError: "remote error: no renegotiation",
+ })
+
+ // Renegotiation is not allowed when there is an unfinished write.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-UnfinishedWrite",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ readWithUnfinishedWrite: true,
+ flags: []string{
+ "-async",
+ "-renegotiate-freely",
+ },
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ // We do not successfully send the no_renegotiation alert in
+ // this case. https://crbug.com/boringssl/130
+ })
+
+ // We reject stray HelloRequests during the handshake in TLS 1.2.
+ testCases = append(testCases, testCase{
+ name: "StrayHelloRequest",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendHelloRequestBeforeEveryHandshakeMessage: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+ testCases = append(testCases, testCase{
+ name: "StrayHelloRequest-Packed",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ PackHandshakeFlight: true,
+ SendHelloRequestBeforeEveryHandshakeMessage: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+
+ // Test that HelloRequest is rejected if it comes in the same record as the
+ // server Finished.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-Packed",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ PackHandshakeFlight: true,
+ PackHelloRequestWithFinished: true,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":EXCESS_HANDSHAKE_DATA:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Renegotiation is forbidden in TLS 1.3.
+ testCases = append(testCases, testCase{
+ name: "Renegotiate-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRequestBeforeEveryAppDataRecord: true,
+ },
+ },
+ flags: []string{
+ "-renegotiate-freely",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+
+ // Stray HelloRequests during the handshake are forbidden in TLS 1.3.
+ testCases = append(testCases, testCase{
+ name: "StrayHelloRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRequestBeforeEveryHandshakeMessage: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+
+ // The renegotiation_info extension is not sent in TLS 1.3, but TLS 1.3
+ // always reads as supporting it, regardless of whether it was
+ // negotiated.
+ testCases = append(testCases, testCase{
+ name: "AlwaysReportRenegotiationInfo-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfo: true,
+ },
+ },
+ flags: []string{
+ "-expect-secure-renegotiation",
+ },
+ })
+
+ // Certificates may not change on renegotiation.
+ testCases = append(testCases, testCase{
+ name: "Renegotiation-CertificateChange",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ RenegotiationCertificate: &rsaChainCertificate,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":SERVER_CERT_CHANGED:",
+ })
+ testCases = append(testCases, testCase{
+ name: "Renegotiation-CertificateChange-2",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ RenegotiationCertificate: &rsa1024Certificate,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{"-renegotiate-freely"},
+ shouldFail: true,
+ expectedError: ":SERVER_CERT_CHANGED:",
+ })
+
+ // We do not negotiate ALPN after the initial handshake. This is
+ // error-prone and only risks bugs in consumers.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "Renegotiation-ForbidALPN",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ // Forcibly negotiate ALPN on both initial and
+ // renegotiation handshakes. The test stack will
+ // internally check the client does not offer
+ // it.
+ SendALPN: "foo",
+ },
+ },
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar\x03baz",
+ "-expect-alpn", "foo",
+ "-renegotiate-freely",
+ },
+ renegotiate: 1,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // The server may send different stapled OCSP responses or SCT lists on
+ // renegotiation, but BoringSSL ignores this and reports the old values.
+ // Also test that non-fatal verify results are preserved.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "Renegotiation-ChangeAuthProperties",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+ Bugs: ProtocolBugs{
+ SendOCSPResponseOnRenegotiation: testOCSPResponse2,
+ SendSCTListOnRenegotiation: testSCTList2,
+ },
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ "-enable-ocsp-stapling",
+ "-expect-ocsp-response",
+ base64FlagValue(testOCSPResponse),
+ "-enable-signed-cert-timestamps",
+ "-expect-signed-cert-timestamps",
+ base64FlagValue(testSCTList),
+ "-verify-fail",
+ "-expect-verify-result",
+ },
+ })
+}
diff --git a/ssl/test/runner/resumption_tests.go b/ssl/test/runner/resumption_tests.go
new file mode 100644
index 0000000..eacf964
--- /dev/null
+++ b/ssl/test/runner/resumption_tests.go
@@ -0,0 +1,976 @@
+// 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 "time"
+
+func addResumptionVersionTests() {
+ for _, sessionVers := range tlsVersions {
+ for _, resumeVers := range tlsVersions {
+ protocols := []protocol{tls}
+ if sessionVers.hasDTLS && resumeVers.hasDTLS {
+ protocols = append(protocols, dtls)
+ }
+ if sessionVers.hasQUIC && resumeVers.hasQUIC {
+ protocols = append(protocols, quic)
+ }
+ for _, protocol := range protocols {
+ suffix := "-" + sessionVers.name + "-" + resumeVers.name
+ suffix += "-" + protocol.String()
+
+ if sessionVers.version == resumeVers.version {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "Resume-Client" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: sessionVers.version,
+ Bugs: ProtocolBugs{
+ ExpectNoTLS13PSK: sessionVers.version < VersionTLS13,
+ },
+ },
+ expectations: connectionExpectations{
+ version: sessionVers.version,
+ },
+ resumeExpectations: &connectionExpectations{
+ version: resumeVers.version,
+ },
+ })
+ } else if protocol != tls && sessionVers.version >= VersionTLS13 && resumeVers.version < VersionTLS13 {
+ // In TLS 1.2 and below, the server indicates resumption by echoing
+ // the client's session ID, which is impossible if the client did
+ // not send a session ID. If the client offers a TLS 1.3 session, it
+ // only fills in session ID in TLS (not DTLS or QUIC) for middlebox
+ // compatibility mode. So, instead, test that the session ID was
+ // empty and it was indeed impossible to hit this path
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "Resume-Client-Impossible" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: sessionVers.version,
+ },
+ expectations: connectionExpectations{
+ version: sessionVers.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: resumeVers.version,
+ Bugs: ProtocolBugs{
+ ExpectNoSessionID: true,
+ },
+ },
+ resumeExpectations: &connectionExpectations{
+ version: resumeVers.version,
+ },
+ expectResumeRejected: true,
+ })
+ } else {
+ // Test that the client rejects ServerHellos which resume
+ // sessions at inconsistent versions.
+ expectedError := ":OLD_SESSION_VERSION_NOT_RETURNED:"
+ if sessionVers.version < VersionTLS13 && resumeVers.version >= VersionTLS13 {
+ // The server will "resume" the session by sending pre_shared_key,
+ // but the shim will not have sent pre_shared_key at all. The shim
+ // should reject this because the extension was not allowed at all.
+ expectedError = ":UNEXPECTED_EXTENSION:"
+ }
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "Resume-Client-Mismatch" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: sessionVers.version,
+ },
+ expectations: connectionExpectations{
+ version: sessionVers.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: resumeVers.version,
+ Bugs: ProtocolBugs{
+ AcceptAnySession: true,
+ },
+ },
+ resumeExpectations: &connectionExpectations{
+ version: resumeVers.version,
+ },
+ shouldFail: true,
+ expectedError: expectedError,
+ })
+ }
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "Resume-Client-NoResume" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: sessionVers.version,
+ },
+ expectations: connectionExpectations{
+ version: sessionVers.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: resumeVers.version,
+ },
+ newSessionsOnResume: true,
+ expectResumeRejected: true,
+ resumeExpectations: &connectionExpectations{
+ version: resumeVers.version,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "Resume-Server" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: sessionVers.version,
+ },
+ expectations: connectionExpectations{
+ version: sessionVers.version,
+ },
+ expectResumeRejected: sessionVers != resumeVers,
+ resumeConfig: &Config{
+ MaxVersion: resumeVers.version,
+ Bugs: ProtocolBugs{
+ SendBothTickets: true,
+ },
+ },
+ resumeExpectations: &connectionExpectations{
+ version: resumeVers.version,
+ },
+ })
+
+ // Repeat the test using session IDs, rather than tickets.
+ if sessionVers.version < VersionTLS13 && resumeVers.version < VersionTLS13 {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "Resume-Server-NoTickets" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: sessionVers.version,
+ SessionTicketsDisabled: true,
+ },
+ expectations: connectionExpectations{
+ version: sessionVers.version,
+ },
+ expectResumeRejected: sessionVers != resumeVers,
+ resumeConfig: &Config{
+ MaxVersion: resumeVers.version,
+ SessionTicketsDisabled: true,
+ },
+ resumeExpectations: &connectionExpectations{
+ version: resumeVers.version,
+ },
+ })
+ }
+ }
+ }
+ }
+
+ // Make sure shim ticket mutations are functional.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ShimTicketRewritable",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ FilterTicket: func(in []byte) ([]byte, error) {
+ in, err := SetShimTicketVersion(in, VersionTLS12)
+ if err != nil {
+ return nil, err
+ }
+ return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ })
+
+ // Resumptions are declined if the version does not match.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-DeclineCrossVersion",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
+ FilterTicket: func(in []byte) ([]byte, error) {
+ return SetShimTicketVersion(in, VersionTLS13)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ expectResumeRejected: true,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-DeclineCrossVersion-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ FilterTicket: func(in []byte) ([]byte, error) {
+ return SetShimTicketVersion(in, VersionTLS12)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ expectResumeRejected: true,
+ })
+
+ // Resumptions are declined if the cipher is invalid or disabled.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-DeclineBadCipher",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
+ FilterTicket: func(in []byte) ([]byte, error) {
+ return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ expectResumeRejected: true,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-DeclineBadCipher-2",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
+ FilterTicket: func(in []byte) ([]byte, error) {
+ return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
+ },
+ },
+ },
+ flags: []string{
+ "-cipher", "AES128",
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ expectResumeRejected: true,
+ })
+
+ // Sessions are not resumed if they do not use the preferred cipher.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-CipherNotPreferred",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
+ FilterTicket: func(in []byte) ([]byte, error) {
+ return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ shouldFail: false,
+ expectResumeRejected: true,
+ })
+
+ // TLS 1.3 allows sessions to be resumed at a different cipher if their
+ // PRF hashes match, but BoringSSL will always decline such resumptions.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-CipherNotPreferred-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ FilterTicket: func(in []byte) ([]byte, error) {
+ // If the client (runner) offers ChaCha20-Poly1305 first, the
+ // server (shim) always prefers it. Switch it to AES-GCM.
+ return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ shouldFail: false,
+ expectResumeRejected: true,
+ })
+
+ // Sessions may not be resumed if they contain another version's cipher.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-DeclineBadCipher-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ FilterTicket: func(in []byte) ([]byte, error) {
+ return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
+ },
+ },
+ },
+ flags: []string{
+ "-ticket-key",
+ base64FlagValue(TestShimTicketKey),
+ },
+ expectResumeRejected: true,
+ })
+
+ // If the client does not offer the cipher from the session, decline to
+ // resume. Clients are forbidden from doing this, but BoringSSL selects
+ // the cipher first, so we only decline.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-UnofferedCipher",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendCipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ },
+ expectResumeRejected: true,
+ })
+
+ // In TLS 1.3, clients may advertise a cipher list which does not
+ // include the selected cipher. Test that we tolerate this. Servers may
+ // resume at another cipher if the PRF matches and are not doing 0-RTT, but
+ // BoringSSL will always decline.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-UnofferedCipher-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendCipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ },
+ expectResumeRejected: true,
+ })
+
+ // Sessions may not be resumed at a different cipher.
+ testCases = append(testCases, testCase{
+ name: "Resume-Client-CipherMismatch",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_RSA_WITH_AES_128_CBC_SHA,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:",
+ })
+
+ // Session resumption in TLS 1.3 may change the cipher suite if the PRF
+ // matches.
+ testCases = append(testCases, testCase{
+ name: "Resume-Client-CipherMismatch-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
+ },
+ })
+
+ // Session resumption in TLS 1.3 is forbidden if the PRF does not match.
+ testCases = append(testCases, testCase{
+ name: "Resume-Client-PRFMismatch-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_AES_256_GCM_SHA384,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":OLD_SESSION_PRF_HASH_MISMATCH:",
+ })
+
+ for _, secondBinder := range []bool{false, true} {
+ var suffix string
+ var defaultCurves []CurveID
+ if secondBinder {
+ suffix = "-SecondBinder"
+ // Force a HelloRetryRequest by predicting an empty curve list.
+ defaultCurves = []CurveID{}
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-BinderWrongLength" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: defaultCurves,
+ Bugs: ProtocolBugs{
+ SendShortPSKBinder: true,
+ OnlyCorruptSecondPSKBinder: secondBinder,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decrypting message",
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-NoPSKBinder" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: defaultCurves,
+ Bugs: ProtocolBugs{
+ SendNoPSKBinder: true,
+ OnlyCorruptSecondPSKBinder: secondBinder,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decoding message",
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-ExtraPSKBinder" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: defaultCurves,
+ Bugs: ProtocolBugs{
+ SendExtraPSKBinder: true,
+ OnlyCorruptSecondPSKBinder: secondBinder,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: illegal parameter",
+ expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-ExtraIdentityNoBinder" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: defaultCurves,
+ Bugs: ProtocolBugs{
+ ExtraPSKIdentity: true,
+ OnlyCorruptSecondPSKBinder: secondBinder,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: illegal parameter",
+ expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-InvalidPSKBinder" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: defaultCurves,
+ Bugs: ProtocolBugs{
+ SendInvalidPSKBinder: true,
+ OnlyCorruptSecondPSKBinder: secondBinder,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decrypting message",
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-PSKBinderFirstExtension" + suffix,
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: defaultCurves,
+ Bugs: ProtocolBugs{
+ PSKBinderFirst: true,
+ OnlyCorruptSecondPSKBinder: secondBinder,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: illegal parameter",
+ expectedError: ":PRE_SHARED_KEY_MUST_BE_LAST:",
+ })
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-OmitPSKsOnSecondClientHello",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ OmitPSKsOnSecondClientHello: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: illegal parameter",
+ expectedError: ":INCONSISTENT_CLIENT_HELLO:",
+ })
+}
+
+func addSessionTicketTests() {
+ testCases = append(testCases, testCase{
+ // In TLS 1.2 and below, empty NewSessionTicket messages
+ // mean the server changed its mind on sending a ticket.
+ name: "SendEmptySessionTicket-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendEmptySessionTicket: true,
+ },
+ },
+ flags: []string{"-expect-no-session"},
+ })
+
+ testCases = append(testCases, testCase{
+ // In TLS 1.3, empty NewSessionTicket messages are not
+ // allowed.
+ name: "SendEmptySessionTicket-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEmptySessionTicket: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ // Test that the server ignores unknown PSK modes.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-SendUnknownModeSessionTicket-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendPSKKeyExchangeModes: []byte{0x1a, pskDHEKEMode, 0x2a},
+ },
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ version: VersionTLS13,
+ },
+ })
+
+ // Test that the server does not send session tickets with no matching key exchange mode.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-ExpectNoSessionTicketOnBadKEMode-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendPSKKeyExchangeModes: []byte{0x1a},
+ ExpectNoNewSessionTicket: true,
+ },
+ },
+ })
+
+ // Test that the server does not accept a session with no matching key exchange mode.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-SendBadKEModeSessionTicket-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendPSKKeyExchangeModes: []byte{0x1a},
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+
+ // Test that the server rejects ClientHellos with pre_shared_key but without
+ // psk_key_exchange_modes.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-SendNoKEMModesWithPSK-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendPSKKeyExchangeModes: []byte{},
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedLocalError: "remote error: missing extension",
+ expectedError: ":MISSING_EXTENSION:",
+ })
+
+ // Test that the client ticket age is sent correctly.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-TestValidTicketAge-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectTicketAge: 10 * time.Second,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ },
+ })
+
+ // Test that the client ticket age is enforced.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-TestBadTicketAge-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectTicketAge: 1000 * time.Second,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedLocalError: "tls: invalid ticket age",
+ })
+
+ // Test that the server's ticket age skew reporting works.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Forward",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 15 * time.Second,
+ },
+ },
+ resumeSession: true,
+ resumeRenewedSession: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ "-expect-ticket-age-skew", "5",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Backward",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 5 * time.Second,
+ },
+ },
+ resumeSession: true,
+ resumeRenewedSession: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ "-expect-ticket-age-skew", "-5",
+ },
+ })
+
+ // Test that ticket age skew up to 60 seconds in either direction is accepted.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Forward-60-Accept",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 70 * time.Second,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ "-expect-ticket-age-skew", "60",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Backward-60-Accept",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 10 * time.Second,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-resumption-delay", "70",
+ "-expect-ticket-age-skew", "-60",
+ },
+ })
+
+ // Test that ticket age skew beyond 60 seconds in either direction is rejected.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Forward-61-Reject",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 71 * time.Second,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ "-expect-ticket-age-skew", "61",
+ "-on-resume-expect-early-data-reason", "ticket_age_skew",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Backward-61-Reject",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 10 * time.Second,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-resumption-delay", "71",
+ "-expect-ticket-age-skew", "-61",
+ "-on-resume-expect-early-data-reason", "ticket_age_skew",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-SendTicketEarlyDataSupport",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ flags: []string{
+ "-enable-early-data",
+ "-expect-ticket-supports-early-data",
+ },
+ })
+
+ // Test that 0-RTT tickets are still recorded as such when early data is disabled overall.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-SendTicketEarlyDataSupport-Disabled",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ flags: []string{
+ "-expect-ticket-supports-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DuplicateTicketEarlyDataSupport",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ DuplicateTicketEarlyData: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DUPLICATE_EXTENSION:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-ExpectTicketEarlyDataSupport",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectTicketEarlyData: true,
+ },
+ },
+ flags: []string{
+ "-enable-early-data",
+ },
+ })
+
+ // Test that, in TLS 1.3, the server-offered NewSessionTicket lifetime
+ // is honored.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-HonorServerSessionTicketLifetime",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketLifetime: 20 * time.Second,
+ },
+ },
+ flags: []string{
+ "-resumption-delay", "19",
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-HonorServerSessionTicketLifetime-2",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketLifetime: 20 * time.Second,
+ // The client should not offer the expired session.
+ ExpectNoTLS13PSK: true,
+ },
+ },
+ flags: []string{
+ "-resumption-delay", "21",
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+
+ for _, ver := range tlsVersions {
+ // Prior to TLS 1.3, disabling session tickets enables session IDs.
+ useStatefulResumption := ver.version < VersionTLS13
+
+ // SSL_OP_NO_TICKET implies the server must not mint any tickets.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: ver.name + "-NoTicket-NoMint",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectNoNewSessionTicket: true,
+ RequireSessionIDs: useStatefulResumption,
+ },
+ },
+ resumeSession: useStatefulResumption,
+ flags: []string{"-no-ticket"},
+ })
+
+ // SSL_OP_NO_TICKET implies the server must not accept any tickets.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: ver.name + "-NoTicket-NoAccept",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ // Set SSL_OP_NO_TICKET on the second connection, after the first
+ // has established tickets.
+ flags: []string{"-on-resume-no-ticket"},
+ })
+
+ // SSL_OP_NO_TICKET implies the client must not offer ticket-based
+ // sessions. The client not only should not send the session ticket
+ // extension, but if the server echos the session ID, the client should
+ // reject this.
+ if ver.version < VersionTLS13 {
+ testCases = append(testCases, testCase{
+ name: ver.name + "-NoTicket-NoOffer",
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ },
+ resumeConfig: &Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ ExpectNoTLS12TicketSupport: true,
+ // Pretend to accept the session, even though the client
+ // did not offer it. The client should reject this as
+ // invalid. A buggy client will still fail because it
+ // expects resumption, but with a different error.
+ // Ideally, we would test this by actually resuming the
+ // previous session, even though the client did not
+ // provide a ticket.
+ EchoSessionIDInFullHandshake: true,
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ // Set SSL_OP_NO_TICKET on the second connection, after the first
+ // has established tickets.
+ flags: []string{"-on-resume-no-ticket"},
+ shouldFail: true,
+ expectedError: ":SERVER_ECHOED_INVALID_SESSION_ID:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+ }
+ }
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 8cbb46b..79866d9 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -19,11 +19,8 @@
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
- "crypto/elliptic"
- "crypto/rand"
"crypto/rsa"
"crypto/x509"
- "crypto/x509/pkix"
_ "embed"
"encoding/base64"
"encoding/binary"
@@ -34,7 +31,6 @@
"flag"
"fmt"
"io"
- "math/big"
"net"
"os"
"os/exec"
@@ -47,7 +43,6 @@
"syscall"
"time"
- "boringssl.googlesource.com/boringssl.git/ssl/test/runner/hpke"
"boringssl.googlesource.com/boringssl.git/util/testresult"
"golang.org/x/crypto/cryptobyte"
)
@@ -296,21 +291,6 @@
return *useGDB || *useLLDB || *useRR || *waitForDebugger
}
-// delegatedCredentialConfig specifies the shape of a delegated credential, not
-// including the keys themselves.
-type delegatedCredentialConfig struct {
- // lifetime is the amount of time, from the notBefore of the parent
- // certificate, that the delegated credential is valid for. If zero, then 24
- // hours is assumed.
- lifetime time.Duration
- // dcAlgo is the signature scheme that should be used with this delegated
- // credential. If zero, ECDSA with P-256 is assumed.
- dcAlgo signatureAlgorithm
- // algo is the signature algorithm that the delegated credential itself is
- // signed with. Cannot be zero.
- algo signatureAlgorithm
-}
-
func loadPEMKey(pemBytes []byte) (crypto.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
@@ -329,81 +309,6 @@
return k, nil
}
-func createDelegatedCredential(parent *Credential, config delegatedCredentialConfig) *Credential {
- if parent.Type != CredentialTypeX509 {
- panic("delegated credentials must be issued by X.509 credentials")
- }
-
- dcAlgo := config.dcAlgo
- if dcAlgo == 0 {
- dcAlgo = signatureECDSAWithP256AndSHA256
- }
-
- var dcPriv crypto.Signer
- switch dcAlgo {
- case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512:
- dcPriv = &rsa2048Key
-
- case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512:
- var curve elliptic.Curve
- switch dcAlgo {
- case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256:
- curve = elliptic.P256()
- case signatureECDSAWithP384AndSHA384:
- curve = elliptic.P384()
- case signatureECDSAWithP521AndSHA512:
- curve = elliptic.P521()
- default:
- panic("internal error")
- }
-
- priv, err := ecdsa.GenerateKey(curve, rand.Reader)
- if err != nil {
- panic(err)
- }
- dcPriv = priv
-
- default:
- panic(fmt.Errorf("unsupported DC signature algorithm: %x", dcAlgo))
- }
-
- lifetime := config.lifetime
- if lifetime == 0 {
- lifetime = 24 * time.Hour
- }
- lifetimeSecs := int64(lifetime.Seconds())
- if lifetimeSecs < 0 || lifetimeSecs > 1<<32 {
- panic(fmt.Errorf("lifetime %s is too long to be expressed", lifetime))
- }
-
- // https://www.rfc-editor.org/rfc/rfc9345.html#section-4
- dc := cryptobyte.NewBuilder(nil)
- dc.AddUint32(uint32(lifetimeSecs))
- dc.AddUint16(uint16(dcAlgo))
-
- pubBytes, err := x509.MarshalPKIXPublicKey(dcPriv.Public())
- if err != nil {
- panic(err)
- }
- addUint24LengthPrefixedBytes(dc, pubBytes)
-
- var dummyConfig Config
- parentSignature, err := signMessage(false /* server */, VersionTLS13, parent.PrivateKey, &dummyConfig, config.algo, delegatedCredentialSignedMessage(dc.BytesOrPanic(), config.algo, parent.Leaf.Raw))
- if err != nil {
- panic(err)
- }
-
- dc.AddUint16(uint16(config.algo))
- addUint16LengthPrefixedBytes(dc, parentSignature)
-
- dcCred := *parent
- dcCred.Type = CredentialTypeDelegated
- dcCred.DelegatedCredential = dc.BytesOrPanic()
- dcCred.PrivateKey = dcPriv
- dcCred.KeyPath = writeTempKeyFile(dcPriv)
- return &dcCred
-}
-
// recordVersionToWire maps a record-layer protocol version to its wire
// representation.
func recordVersionToWire(vers uint16, protocol protocol) uint16 {
@@ -1977,61 +1882,6 @@
return ret
}
-type testCipherSuite struct {
- name string
- id uint16
-}
-
-var testCipherSuites = []testCipherSuite{
- {"RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
- {"RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
- {"RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
- {"RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
- {"RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
- {"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- {"ECDHE_ECDSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
- {"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
- {"ECDHE_ECDSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
- {"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
- {"ECDHE_RSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- {"ECDHE_RSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- {"ECDHE_RSA_WITH_AES_128_CBC_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
- {"ECDHE_RSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
- {"ECDHE_RSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
- {"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
- {"PSK_WITH_AES_128_CBC_SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
- {"PSK_WITH_AES_256_CBC_SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
- {"ECDHE_PSK_WITH_AES_128_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
- {"ECDHE_PSK_WITH_AES_256_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA},
- {"ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256},
- {"CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
- {"AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
- {"AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
-}
-
-func hasComponent(suiteName, component string) bool {
- return strings.Contains("_"+suiteName+"_", "_"+component+"_")
-}
-
-func isTLS12Only(suiteName string) bool {
- return hasComponent(suiteName, "GCM") ||
- hasComponent(suiteName, "SHA256") ||
- hasComponent(suiteName, "SHA384") ||
- hasComponent(suiteName, "POLY1305")
-}
-
-func isTLS13Suite(suiteName string) bool {
- return !hasComponent(suiteName, "WITH")
-}
-
-func bigFromHex(hex string) *big.Int {
- ret, ok := new(big.Int).SetString(hex, 16)
- if !ok {
- panic("failed to parse hex number 0x" + hex)
- }
- return ret
-}
-
func convertToSplitHandshakeTests(tests []testCase) (splitHandshakeTests []testCase, err error) {
var stdout bytes.Buffer
var flags []string
@@ -2115,21669 +1965,6 @@
return splitHandshakeTests, nil
}
-func addBasicTests() {
- basicTests := []testCase{
- {
- name: "NoFallbackSCSV",
- config: Config{
- Bugs: ProtocolBugs{
- FailIfNotFallbackSCSV: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "no fallback SCSV found",
- },
- {
- name: "SendFallbackSCSV",
- config: Config{
- Bugs: ProtocolBugs{
- FailIfNotFallbackSCSV: true,
- },
- },
- flags: []string{"-fallback-scsv"},
- },
- {
- name: "ClientCertificateTypes",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequestClientCert,
- ClientCertificateTypes: []byte{
- CertTypeDSSSign,
- CertTypeRSASign,
- CertTypeECDSASign,
- },
- },
- flags: []string{
- "-expect-certificate-types",
- base64FlagValue([]byte{
- CertTypeDSSSign,
- CertTypeRSASign,
- CertTypeECDSASign,
- }),
- },
- },
- {
- name: "CheckClientCertificateTypes",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequestClientCert,
- ClientCertificateTypes: []byte{CertTypeECDSASign},
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
- },
- {
- name: "UnauthenticatedECDH",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- UnauthenticatedECDH: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- },
- {
- name: "SkipCertificateStatus",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SkipCertificateStatus: true,
- },
- },
- flags: []string{
- "-enable-ocsp-stapling",
- // This test involves an optional message. Test the message callback
- // trace to ensure we do not miss or double-report any.
- "-expect-msg-callback",
- `write hs 1
-read hs 2
-read hs 11
-read hs 12
-read hs 14
-write hs 16
-write ccs
-write hs 20
-read hs 4
-read ccs
-read hs 20
-read alert 1 0
-`,
- },
- },
- {
- protocol: dtls,
- name: "SkipCertificateStatus-DTLS",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SkipCertificateStatus: true,
- },
- },
- flags: []string{
- "-enable-ocsp-stapling",
- // This test involves an optional message. Test the message callback
- // trace to ensure we do not miss or double-report any.
- "-expect-msg-callback",
- `write hs 1
-read hs 3
-write hs 1
-read hs 2
-read hs 11
-read hs 12
-read hs 14
-write hs 16
-write ccs
-write hs 20
-read hs 4
-read ccs
-read hs 20
-read alert 1 0
-`,
- },
- },
- {
- name: "SkipServerKeyExchange",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SkipServerKeyExchange: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- },
- {
- testType: serverTest,
- name: "ServerSkipCertificateVerify",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- SkipCertificateVerify: true,
- },
- },
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- flags: []string{
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- testType: serverTest,
- name: "Alert",
- config: Config{
- Bugs: ProtocolBugs{
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "Alert-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
- },
- {
- testType: serverTest,
- name: "FragmentAlert",
- config: Config{
- Bugs: ProtocolBugs{
- FragmentAlert: true,
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ALERT:",
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "FragmentAlert-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- FragmentAlert: true,
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ALERT:",
- },
- {
- testType: serverTest,
- name: "DoubleAlert",
- config: Config{
- Bugs: ProtocolBugs{
- DoubleAlert: true,
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ALERT:",
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "DoubleAlert-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- DoubleAlert: true,
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ALERT:",
- },
- {
- name: "SkipNewSessionTicket",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SkipNewSessionTicket: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- testType: serverTest,
- name: "FallbackSCSV",
- config: Config{
- MaxVersion: VersionTLS11,
- Bugs: ProtocolBugs{
- SendFallbackSCSV: true,
- },
- },
- shouldFail: true,
- expectedError: ":INAPPROPRIATE_FALLBACK:",
- expectedLocalError: "remote error: inappropriate fallback",
- },
- {
- testType: serverTest,
- name: "FallbackSCSV-VersionMatch-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFallbackSCSV: true,
- },
- },
- },
- {
- testType: serverTest,
- name: "FallbackSCSV-VersionMatch-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendFallbackSCSV: true,
- },
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- },
- // Regression test for CVE-2014-3511. Even when the ClientHello is
- // maximally fragmented, version negotiation works correctly.
- {
- testType: serverTest,
- name: "FragmentedClientVersion",
- config: Config{
- Bugs: ProtocolBugs{
- MaxHandshakeRecordLength: 1,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS13,
- },
- },
- {
- testType: serverTest,
- name: "HttpGET",
- sendPrefix: "GET / HTTP/1.0\n",
- shouldFail: true,
- expectedError: ":HTTP_REQUEST:",
- },
- {
- testType: serverTest,
- name: "HttpPOST",
- sendPrefix: "POST / HTTP/1.0\n",
- shouldFail: true,
- expectedError: ":HTTP_REQUEST:",
- },
- {
- testType: serverTest,
- name: "HttpHEAD",
- sendPrefix: "HEAD / HTTP/1.0\n",
- shouldFail: true,
- expectedError: ":HTTP_REQUEST:",
- },
- {
- testType: serverTest,
- name: "HttpPUT",
- sendPrefix: "PUT / HTTP/1.0\n",
- shouldFail: true,
- expectedError: ":HTTP_REQUEST:",
- },
- {
- testType: serverTest,
- name: "HttpCONNECT",
- sendPrefix: "CONNECT www.google.com:443 HTTP/1.0\n",
- shouldFail: true,
- expectedError: ":HTTPS_PROXY_REQUEST:",
- },
- {
- testType: serverTest,
- name: "Garbage",
- sendPrefix: "blah",
- shouldFail: true,
- expectedError: ":WRONG_VERSION_NUMBER:",
- },
- {
- name: "RSAEphemeralKey",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- RSAEphemeralKey: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- },
- {
- name: "DisableEverything",
- flags: []string{"-no-tls13", "-no-tls12", "-no-tls11", "-no-tls1"},
- shouldFail: true,
- expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
- },
- {
- protocol: dtls,
- name: "DisableEverything-DTLS",
- flags: []string{"-no-tls13", "-no-tls12", "-no-tls1"},
- shouldFail: true,
- expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "MTU-DTLS12-AEAD",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- MaxPacketLength: 256,
- },
- },
- flags: []string{"-mtu", "256"},
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "MTU-DTLS12-AES-CBC",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
- Bugs: ProtocolBugs{
- MaxPacketLength: 256,
- },
- },
- flags: []string{"-mtu", "256"},
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "MTU-DTLS12-3DES-CBC",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_3DES_EDE_CBC_SHA},
- Bugs: ProtocolBugs{
- MaxPacketLength: 256,
- },
- },
- flags: []string{"-mtu", "256", "-cipher", "TLS_RSA_WITH_3DES_EDE_CBC_SHA"},
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "MTU-DTLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- MaxPacketLength: 256,
- },
- },
- flags: []string{"-mtu", "256"},
- },
- {
- name: "EmptyCertificateList",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- EmptyCertificateList: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- },
- {
- name: "EmptyCertificateList-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- EmptyCertificateList: true,
- },
- },
- shouldFail: true,
- expectedError: ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
- },
- {
- name: "TLSFatalBadPackets",
- damageFirstWrite: true,
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- },
- {
- protocol: dtls,
- name: "DTLSIgnoreBadPackets",
- damageFirstWrite: true,
- },
- {
- protocol: dtls,
- name: "DTLSIgnoreBadPackets-Async",
- damageFirstWrite: true,
- flags: []string{"-async"},
- },
- {
- name: "AppDataBeforeHandshake",
- config: Config{
- Bugs: ProtocolBugs{
- AppDataBeforeHandshake: []byte("TEST MESSAGE"),
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- name: "AppDataBeforeHandshake-Empty",
- config: Config{
- Bugs: ProtocolBugs{
- AppDataBeforeHandshake: []byte{},
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- protocol: dtls,
- name: "AppDataBeforeHandshake-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- AppDataBeforeHandshake: []byte("TEST MESSAGE"),
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- protocol: dtls,
- name: "AppDataBeforeHandshake-DTLS-Empty",
- config: Config{
- Bugs: ProtocolBugs{
- AppDataBeforeHandshake: []byte{},
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- name: "AppDataBeforeTLS13KeyChange",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AppDataBeforeTLS13KeyChange: []byte("TEST MESSAGE"),
- },
- },
- // The shim should fail to decrypt this record.
- shouldFail: true,
- expectedError: ":BAD_DECRYPT:",
- expectedLocalError: "remote error: bad record MAC",
- },
- {
- name: "AppDataBeforeTLS13KeyChange-Empty",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AppDataBeforeTLS13KeyChange: []byte{},
- },
- },
- // The shim should fail to decrypt this record.
- shouldFail: true,
- expectedError: ":BAD_DECRYPT:",
- expectedLocalError: "remote error: bad record MAC",
- },
- {
- protocol: dtls,
- name: "AppDataBeforeTLS13KeyChange-DTLS",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AppDataBeforeTLS13KeyChange: []byte("TEST MESSAGE"),
- },
- },
- // The shim will decrypt the record, because it has not
- // yet applied the key change, but it should know to
- // reject the record.
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- name: "AppDataBeforeTLS13KeyChange-DTLS-Empty",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AppDataBeforeTLS13KeyChange: []byte{},
- },
- },
- // The shim will decrypt the record, because it has not
- // yet applied the key change, but it should know to
- // reject the record.
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- name: "UnencryptedEncryptedExtensions",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- UnencryptedEncryptedExtensions: true,
- },
- },
- // The shim should fail to decrypt this record.
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- expectedLocalError: "remote error: bad record MAC",
- },
- {
- protocol: dtls,
- name: "UnencryptedEncryptedExtensions-DTLS",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- UnencryptedEncryptedExtensions: true,
- },
- },
- // The shim will decrypt the record, because it has not
- // yet applied the key change, but it should know to
- // reject new handshake data on the previous epoch.
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- name: "AppDataAfterChangeCipherSpec",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- AppDataAfterChangeCipherSpec: []byte("TEST MESSAGE"),
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- name: "AppDataAfterChangeCipherSpec-Empty",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- AppDataAfterChangeCipherSpec: []byte{},
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- protocol: dtls,
- name: "AppDataAfterChangeCipherSpec-DTLS",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- AppDataAfterChangeCipherSpec: []byte("TEST MESSAGE"),
- },
- },
- // BoringSSL's DTLS implementation will drop the out-of-order
- // application data.
- },
- {
- protocol: dtls,
- name: "AppDataAfterChangeCipherSpec-DTLS-Empty",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- AppDataAfterChangeCipherSpec: []byte{},
- },
- },
- // BoringSSL's DTLS implementation will drop the out-of-order
- // application data.
- },
- {
- name: "AlertAfterChangeCipherSpec",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- AlertAfterChangeCipherSpec: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
- },
- {
- protocol: dtls,
- name: "AlertAfterChangeCipherSpec-DTLS",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- AlertAfterChangeCipherSpec: alertRecordOverflow,
- },
- },
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
- },
- {
- name: "SendInvalidRecordType",
- config: Config{
- Bugs: ProtocolBugs{
- SendInvalidRecordType: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- protocol: dtls,
- name: "SendInvalidRecordType-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendInvalidRecordType: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- name: "FalseStart-SkipServerSecondLeg",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- SkipNewSessionTicket: true,
- SkipChangeCipherSpec: true,
- SkipFinished: true,
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-handshake-never-done",
- "-advertise-alpn", "\x03foo",
- "-expect-alpn", "foo",
- },
- shimWritesFirst: true,
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- name: "FalseStart-SkipServerSecondLeg-Implicit",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- SkipNewSessionTicket: true,
- SkipChangeCipherSpec: true,
- SkipFinished: true,
- },
- },
- flags: []string{
- "-implicit-handshake",
- "-false-start",
- "-handshake-never-done",
- "-advertise-alpn", "\x03foo",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- },
- {
- testType: serverTest,
- name: "FailEarlyCallback",
- flags: []string{"-fail-early-callback"},
- shouldFail: true,
- expectedError: ":CONNECTION_REJECTED:",
- expectedLocalError: "remote error: handshake failure",
- },
- {
- name: "FailCertCallback-Client-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequestClientCert,
- },
- flags: []string{"-fail-cert-callback"},
- shouldFail: true,
- expectedError: ":CERT_CB_ERROR:",
- expectedLocalError: "remote error: internal error",
- },
- {
- testType: serverTest,
- name: "FailCertCallback-Server-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- flags: []string{"-fail-cert-callback"},
- shouldFail: true,
- expectedError: ":CERT_CB_ERROR:",
- expectedLocalError: "remote error: internal error",
- },
- {
- name: "FailCertCallback-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequestClientCert,
- },
- flags: []string{"-fail-cert-callback"},
- shouldFail: true,
- expectedError: ":CERT_CB_ERROR:",
- expectedLocalError: "remote error: internal error",
- },
- {
- testType: serverTest,
- name: "FailCertCallback-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- flags: []string{"-fail-cert-callback"},
- shouldFail: true,
- expectedError: ":CERT_CB_ERROR:",
- expectedLocalError: "remote error: internal error",
- },
- {
- protocol: dtls,
- name: "FragmentMessageTypeMismatch-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- f1 := next[0].Fragment(0, 1)
- f2 := next[0].Fragment(1, 1)
- f2.Type++
- c.WriteFragments([]DTLSFragment{f1, f2})
- },
- },
- },
- shouldFail: true,
- expectedError: ":FRAGMENT_MISMATCH:",
- },
- {
- protocol: dtls,
- name: "FragmentMessageLengthMismatch-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- f1 := next[0].Fragment(0, 1)
- f2 := next[0].Fragment(1, 1)
- f2.TotalLength++
- c.WriteFragments([]DTLSFragment{f1, f2})
- },
- },
- },
- shouldFail: true,
- expectedError: ":FRAGMENT_MISMATCH:",
- },
- {
- protocol: dtls,
- name: "SplitFragments-Header-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SplitFragments: 2,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_HANDSHAKE_RECORD:",
- },
- {
- protocol: dtls,
- name: "SplitFragments-Boundary-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SplitFragments: dtlsMaxRecordHeaderLen,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_HANDSHAKE_RECORD:",
- },
- {
- protocol: dtls,
- name: "SplitFragments-Body-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SplitFragments: dtlsMaxRecordHeaderLen + 1,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_HANDSHAKE_RECORD:",
- },
- {
- protocol: dtls,
- name: "SendEmptyFragments-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendEmptyFragments: true,
- },
- },
- },
- {
- testType: serverTest,
- protocol: dtls,
- name: "SendEmptyFragments-Padded-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- // Test empty fragments for a message with a
- // nice power-of-two length.
- PadClientHello: 64,
- SendEmptyFragments: true,
- },
- },
- },
- {
- name: "BadFinished-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- },
- {
- name: "BadFinished-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- BadFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- },
- {
- testType: serverTest,
- name: "BadFinished-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- },
- {
- testType: serverTest,
- name: "BadFinished-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- BadFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- },
- {
- name: "FalseStart-BadFinished",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- BadFinished: true,
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-handshake-never-done",
- "-advertise-alpn", "\x03foo",
- "-expect-alpn", "foo",
- },
- shimWritesFirst: true,
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- },
- {
- name: "NoFalseStart-NoALPN",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- AlertBeforeFalseStartTest: alertAccessDenied,
- },
- },
- flags: []string{
- "-false-start",
- },
- shimWritesFirst: true,
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
- expectedLocalError: "tls: peer did not false start: EOF",
- },
- {
- name: "FalseStart-NoALPNAllowed",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-allow-false-start-without-alpn",
- },
- shimWritesFirst: true,
- },
- {
- name: "NoFalseStart-NoAEAD",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- AlertBeforeFalseStartTest: alertAccessDenied,
- },
- },
- flags: []string{
- "-false-start",
- "-advertise-alpn", "\x03foo",
- },
- shimWritesFirst: true,
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
- expectedLocalError: "tls: peer did not false start: EOF",
- },
- {
- name: "NoFalseStart-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- AlertBeforeFalseStartTest: alertAccessDenied,
- },
- },
- flags: []string{
- "-false-start",
- "-advertise-alpn", "\x03foo",
- },
- shimWritesFirst: true,
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
- expectedLocalError: "tls: peer did not false start: EOF",
- },
- {
- protocol: dtls,
- name: "SendSplitAlert-Sync",
- config: Config{
- Bugs: ProtocolBugs{
- SendSplitAlert: true,
- },
- },
- },
- {
- protocol: dtls,
- name: "SendSplitAlert-Async",
- config: Config{
- Bugs: ProtocolBugs{
- SendSplitAlert: true,
- },
- },
- flags: []string{"-async"},
- },
- {
- name: "SendEmptyRecords-Pass",
- sendEmptyRecords: 32,
- },
- {
- name: "SendEmptyRecords",
- sendEmptyRecords: 33,
- shouldFail: true,
- expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
- },
- {
- name: "SendEmptyRecords-Async",
- sendEmptyRecords: 33,
- flags: []string{"-async"},
- shouldFail: true,
- expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
- },
- {
- name: "SendWarningAlerts-Pass",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- sendWarningAlerts: 4,
- },
- {
- protocol: dtls,
- name: "SendWarningAlerts-DTLS-Pass",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- sendWarningAlerts: 4,
- },
- {
- name: "SendWarningAlerts-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendWarningAlerts: 4,
- shouldFail: true,
- expectedError: ":BAD_ALERT:",
- expectedLocalError: "remote error: error decoding message",
- },
- // Although TLS 1.3 intended to remove warning alerts, it left in
- // user_canceled. JDK11 misuses this alert as a post-handshake
- // full-duplex signal. As a workaround, skip user_canceled as in
- // TLS 1.2, which is consistent with NSS and OpenSSL.
- {
- name: "SendUserCanceledAlerts-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendUserCanceledAlerts: 4,
- },
- {
- name: "SendUserCanceledAlerts-TooMany-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendUserCanceledAlerts: 5,
- shouldFail: true,
- expectedError: ":TOO_MANY_WARNING_ALERTS:",
- },
- {
- name: "SendWarningAlerts-TooMany",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- sendWarningAlerts: 5,
- shouldFail: true,
- expectedError: ":TOO_MANY_WARNING_ALERTS:",
- },
- {
- name: "SendWarningAlerts-TooMany-Async",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- sendWarningAlerts: 5,
- flags: []string{"-async"},
- shouldFail: true,
- expectedError: ":TOO_MANY_WARNING_ALERTS:",
- },
- {
- name: "SendBogusAlertType",
- sendBogusAlertType: true,
- shouldFail: true,
- expectedError: ":UNKNOWN_ALERT_TYPE:",
- expectedLocalError: "remote error: illegal parameter",
- },
- {
- protocol: dtls,
- name: "SendBogusAlertType-DTLS",
- sendBogusAlertType: true,
- shouldFail: true,
- expectedError: ":UNKNOWN_ALERT_TYPE:",
- expectedLocalError: "remote error: illegal parameter",
- },
- {
- name: "TooManyKeyUpdates",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendKeyUpdates: 33,
- keyUpdateRequest: keyUpdateNotRequested,
- shouldFail: true,
- expectedError: ":TOO_MANY_KEY_UPDATES:",
- },
- {
- name: "EmptySessionID",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- noSessionCache: true,
- flags: []string{"-expect-no-session"},
- },
- {
- name: "Unclean-Shutdown",
- config: Config{
- Bugs: ProtocolBugs{
- NoCloseNotify: true,
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- flags: []string{"-check-close-notify"},
- shouldFail: true,
- expectedError: "Unexpected SSL_shutdown result: -1 != 1",
- },
- {
- name: "Unclean-Shutdown-Ignored",
- config: Config{
- Bugs: ProtocolBugs{
- NoCloseNotify: true,
- },
- },
- shimShutsDown: true,
- },
- {
- name: "Unclean-Shutdown-Alert",
- config: Config{
- Bugs: ProtocolBugs{
- SendAlertOnShutdown: alertDecompressionFailure,
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- flags: []string{"-check-close-notify"},
- shouldFail: true,
- expectedError: ":SSLV3_ALERT_DECOMPRESSION_FAILURE:",
- },
- {
- name: "LargePlaintext",
- config: Config{
- Bugs: ProtocolBugs{
- SendLargeRecords: true,
- },
- },
- messageLen: maxPlaintext + 1,
- shouldFail: true,
- expectedError: ":DATA_LENGTH_TOO_LONG:",
- expectedLocalError: "remote error: record overflow",
- },
- {
- protocol: dtls,
- name: "LargePlaintext-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendLargeRecords: true,
- },
- },
- messageLen: maxPlaintext + 1,
- shouldFail: true,
- expectedError: ":DATA_LENGTH_TOO_LONG:",
- expectedLocalError: "remote error: record overflow",
- },
- {
- name: "LargePlaintext-TLS13-Padded-8192-8192",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RecordPadding: 8192,
- SendLargeRecords: true,
- },
- },
- messageLen: 8192,
- },
- {
- name: "LargePlaintext-TLS13-Padded-8193-8192",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RecordPadding: 8193,
- SendLargeRecords: true,
- },
- },
- messageLen: 8192,
- shouldFail: true,
- expectedError: ":DATA_LENGTH_TOO_LONG:",
- expectedLocalError: "remote error: record overflow",
- },
- {
- name: "LargePlaintext-TLS13-Padded-16383-1",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RecordPadding: 1,
- SendLargeRecords: true,
- },
- },
- messageLen: 16383,
- },
- {
- name: "LargePlaintext-TLS13-Padded-16384-1",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RecordPadding: 1,
- SendLargeRecords: true,
- },
- },
- messageLen: 16384,
- shouldFail: true,
- expectedError: ":DATA_LENGTH_TOO_LONG:",
- expectedLocalError: "remote error: record overflow",
- },
- {
- name: "LargeCiphertext",
- config: Config{
- Bugs: ProtocolBugs{
- SendLargeRecords: true,
- },
- },
- messageLen: maxPlaintext * 2,
- shouldFail: true,
- expectedError: ":ENCRYPTED_LENGTH_TOO_LONG:",
- },
- {
- protocol: dtls,
- name: "LargeCiphertext-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendLargeRecords: true,
- },
- },
- messageLen: maxPlaintext * 2,
- // Unlike the other four cases, DTLS drops records which
- // are invalid before authentication, so the connection
- // does not fail.
- expectMessageDropped: true,
- },
- {
- name: "BadHelloRequest-1",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadHelloRequest: []byte{typeHelloRequest, 0, 0, 1, 1},
- },
- },
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shouldFail: true,
- expectedError: ":BAD_HELLO_REQUEST:",
- },
- {
- name: "BadHelloRequest-2",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadHelloRequest: []byte{typeServerKeyExchange, 0, 0, 0},
- },
- },
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shouldFail: true,
- expectedError: ":BAD_HELLO_REQUEST:",
- },
- {
- testType: serverTest,
- name: "SupportTicketsWithSessionID",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- },
- resumeSession: true,
- },
- {
- protocol: dtls,
- name: "DTLS12-SendExtraFinished",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendExtraFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- name: "DTLS12-SendExtraFinished-Reordered",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- MaxHandshakeRecordLength: 2,
- ReorderHandshakeFragments: true,
- SendExtraFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- name: "DTLS12-SendExtraFinished-Packed",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendExtraFinished: true,
- PackHandshakeFragments: 1000,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- name: "DTLS13-SendExtraFinished",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtraFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- name: "DTLS13-SendExtraFinished-Reordered",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- MaxHandshakeRecordLength: 2,
- ReorderHandshakeFragments: true,
- SendExtraFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- name: "DTLS13-SendExtraFinished-Packed",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtraFinished: true,
- PackHandshakeFragments: 1000,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- },
- {
- protocol: dtls,
- testType: serverTest,
- name: "DTLS13-SendExtraFinished-AfterAppData",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SkipImplicitACKRead: true,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[len(next)-1].Type != typeFinished {
- c.WriteFlight(next)
- return
- }
-
- // Complete the handshake.
- c.WriteFlight(next)
- c.ReadACK(c.InEpoch())
-
- // Send some application data. The shim is now on epoch 3.
- msg := []byte("hello")
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // The shim is still accepting data from epoch 2, so it can
- // ACK a retransmit if needed, but it should not accept new
- // messages at epoch three.
- extraFinished := next[len(next)-1]
- extraFinished.Sequence++
- c.WriteFlight([]DTLSMessage{extraFinished})
- },
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- // Disable tickets on the shim to avoid NewSessionTicket
- // interfering with the test callback.
- flags: []string{"-no-ticket"},
- },
- {
- testType: serverTest,
- name: "V2ClientHello-EmptyRecordPrefix",
- config: Config{
- // Choose a cipher suite that does not involve
- // elliptic curves, so no extensions are
- // involved.
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- SendV2ClientHello: true,
- },
- },
- sendPrefix: string([]byte{
- byte(recordTypeHandshake),
- 3, 1, // version
- 0, 0, // length
- }),
- // A no-op empty record may not be sent before V2ClientHello.
- shouldFail: true,
- expectedError: ":WRONG_VERSION_NUMBER:",
- },
- {
- testType: serverTest,
- name: "V2ClientHello-WarningAlertPrefix",
- config: Config{
- // Choose a cipher suite that does not involve
- // elliptic curves, so no extensions are
- // involved.
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- SendV2ClientHello: true,
- },
- },
- sendPrefix: string([]byte{
- byte(recordTypeAlert),
- 3, 1, // version
- 0, 2, // length
- alertLevelWarning, byte(alertDecompressionFailure),
- }),
- // A no-op warning alert may not be sent before V2ClientHello.
- shouldFail: true,
- expectedError: ":WRONG_VERSION_NUMBER:",
- },
- {
- name: "SendSNIWarningAlert",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendSNIWarningAlert: true,
- },
- },
- },
- {
- testType: serverTest,
- name: "ExtraCompressionMethods-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendCompressionMethods: []byte{1, 2, 3, compressionNone, 4, 5, 6},
- },
- },
- },
- {
- testType: serverTest,
- name: "ExtraCompressionMethods-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendCompressionMethods: []byte{1, 2, 3, compressionNone, 4, 5, 6},
- },
- },
- shouldFail: true,
- expectedError: ":INVALID_COMPRESSION_LIST:",
- expectedLocalError: "remote error: illegal parameter",
- },
- {
- testType: serverTest,
- name: "NoNullCompression-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendCompressionMethods: []byte{1, 2, 3, 4, 5, 6},
- },
- },
- shouldFail: true,
- expectedError: ":INVALID_COMPRESSION_LIST:",
- expectedLocalError: "remote error: illegal parameter",
- },
- {
- testType: serverTest,
- name: "NoNullCompression-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendCompressionMethods: []byte{1, 2, 3, 4, 5, 6},
- },
- },
- shouldFail: true,
- expectedError: ":INVALID_COMPRESSION_LIST:",
- expectedLocalError: "remote error: illegal parameter",
- },
- // Test that the client rejects invalid compression methods
- // from the server.
- {
- testType: clientTest,
- name: "InvalidCompressionMethod",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendCompressionMethod: 1,
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_COMPRESSION_ALGORITHM:",
- expectedLocalError: "remote error: illegal parameter",
- },
- {
- testType: clientTest,
- name: "TLS13-InvalidCompressionMethod",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendCompressionMethod: 1,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- },
- {
- testType: clientTest,
- name: "TLS13-HRR-InvalidCompressionMethod",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendCompressionMethod: 1,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: error decoding message",
- },
- {
- name: "GREASE-Client-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectGREASE: true,
- },
- },
- flags: []string{"-enable-grease"},
- },
- {
- name: "GREASE-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectGREASE: true,
- },
- },
- flags: []string{"-enable-grease"},
- },
- {
- testType: serverTest,
- name: "GREASE-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // TLS 1.3 servers are expected to
- // always enable GREASE. TLS 1.3 is new,
- // so there is no existing ecosystem to
- // worry about.
- ExpectGREASE: true,
- },
- },
- },
- {
- // Test the TLS 1.2 server so there is a large
- // unencrypted certificate as well as application data.
- testType: serverTest,
- name: "MaxSendFragment-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- MaxReceivePlaintext: 512,
- },
- },
- messageLen: 1024,
- flags: []string{
- "-max-send-fragment", "512",
- "-read-size", "1024",
- },
- },
- {
- // Test the TLS 1.2 server so there is a large
- // unencrypted certificate as well as application data.
- testType: serverTest,
- name: "MaxSendFragment-TLS12-TooLarge",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- // Ensure that some of the records are
- // 512.
- MaxReceivePlaintext: 511,
- },
- },
- messageLen: 1024,
- flags: []string{
- "-max-send-fragment", "512",
- "-read-size", "1024",
- },
- shouldFail: true,
- expectedLocalError: "local error: record overflow",
- },
- {
- // Test the TLS 1.3 server so there is a large encrypted
- // certificate as well as application data.
- testType: serverTest,
- name: "MaxSendFragment-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- MaxReceivePlaintext: 512,
- ExpectPackedEncryptedHandshake: 512,
- },
- },
- messageLen: 1024,
- flags: []string{
- "-max-send-fragment", "512",
- "-read-size", "1024",
- },
- },
- {
- // Test the TLS 1.3 server so there is a large encrypted
- // certificate as well as application data.
- testType: serverTest,
- name: "MaxSendFragment-TLS13-TooLarge",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Ensure that some of the records are
- // 512.
- MaxReceivePlaintext: 511,
- },
- },
- messageLen: 1024,
- flags: []string{
- "-max-send-fragment", "512",
- "-read-size", "1024",
- },
- shouldFail: true,
- expectedLocalError: "local error: record overflow",
- },
- {
- // Test that handshake data is tightly packed in TLS 1.3.
- testType: serverTest,
- name: "PackedEncryptedHandshake-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectPackedEncryptedHandshake: 16384,
- },
- },
- },
- {
- // Test that DTLS can handle multiple application data
- // records in a single packet.
- protocol: dtls,
- name: "SplitAndPackAppData-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SplitAndPackAppData: true,
- },
- },
- },
- {
- protocol: dtls,
- name: "SplitAndPackAppData-DTLS-Async",
- config: Config{
- Bugs: ProtocolBugs{
- SplitAndPackAppData: true,
- },
- },
- flags: []string{"-async"},
- },
- {
- // DTLS 1.2 allows up to a 255-byte HelloVerifyRequest cookie, which
- // is the largest encodable value.
- protocol: dtls,
- name: "DTLS-HelloVerifyRequest-255",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- HelloVerifyRequestCookieLength: 255,
- },
- },
- },
- {
- // DTLS 1.2 allows up to a 0-byte HelloVerifyRequest cookie, which
- // was probably a mistake in the spec but test that it works
- // nonetheless.
- protocol: dtls,
- name: "DTLS-HelloVerifyRequest-0",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- EmptyHelloVerifyRequestCookie: true,
- },
- },
- },
- }
- testCases = append(testCases, basicTests...)
-
- // Test that very large messages can be received.
- cert := rsaCertificate
- for i := 0; i < 50; i++ {
- cert.Certificate = append(cert.Certificate, cert.Certificate[0])
- }
- testCases = append(testCases, testCase{
- name: "LargeMessage",
- config: Config{
- Credential: &cert,
- },
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "LargeMessage-DTLS",
- config: Config{
- Credential: &cert,
- },
- })
-
- // They are rejected if the maximum certificate chain length is capped.
- testCases = append(testCases, testCase{
- name: "LargeMessage-Reject",
- config: Config{
- Credential: &cert,
- },
- flags: []string{"-max-cert-list", "16384"},
- shouldFail: true,
- expectedError: ":EXCESSIVE_MESSAGE_SIZE:",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "LargeMessage-Reject-DTLS",
- config: Config{
- Credential: &cert,
- },
- flags: []string{"-max-cert-list", "16384"},
- shouldFail: true,
- expectedError: ":EXCESSIVE_MESSAGE_SIZE:",
- })
-
- // Servers echoing the TLS 1.3 compatibility mode session ID should be
- // rejected.
- testCases = append(testCases, testCase{
- name: "EchoTLS13CompatibilitySessionID",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- EchoSessionIDInFullHandshake: true,
- },
- },
- shouldFail: true,
- expectedError: ":SERVER_ECHOED_INVALID_SESSION_ID:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- // Servers should reject QUIC client hellos that have a legacy
- // session ID.
- testCases = append(testCases, testCase{
- name: "QUICCompatibilityMode",
- testType: serverTest,
- protocol: quic,
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- CompatModeWithQUIC: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_COMPATIBILITY_MODE:",
- })
-
- // Clients should reject DTLS 1.3 ServerHellos that echo the legacy
- // session ID.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS13CompatibilityMode-EchoSessionID",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeConfig: &Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- DTLS13EchoSessionID: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- // DTLS 1.3 should work with record headers that don't set the
- // length bit or that use the short sequence number format.
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: dtls,
- name: "DTLS13RecordHeader-NoLength-Client",
- config: Config{
- MinVersion: VersionTLS13,
- DTLSRecordHeaderOmitLength: true,
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "DTLS13RecordHeader-NoLength-Server",
- config: Config{
- MinVersion: VersionTLS13,
- DTLSRecordHeaderOmitLength: true,
- },
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: dtls,
- name: "DTLS13RecordHeader-ShortSeqNums-Client",
- config: Config{
- MinVersion: VersionTLS13,
- DTLSUseShortSeqNums: true,
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "DTLS13RecordHeader-ShortSeqNums-Server",
- config: Config{
- MinVersion: VersionTLS13,
- DTLSUseShortSeqNums: true,
- },
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS13RecordHeader-OldHeader",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- DTLSUsePlaintextRecordHeader: true,
- },
- },
- expectMessageDropped: true,
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS13RecordHeader-CIDBit",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- DTLS13RecordHeaderSetCIDBit: true,
- },
- },
- expectMessageDropped: true,
- })
-
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS13-MessageCallback-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- flags: []string{
- "-expect-msg-callback",
- `write hs 1
-read hs 2
-read hs 8
-read hs 11
-read hs 15
-read hs 20
-write hs 20
-read ack
-read hs 4
-read hs 4
-read alert 1 0
-`,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "DTLS13-MessageCallback-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- flags: []string{
- "-expect-msg-callback",
- `read hs 1
-write hs 2
-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
-read alert 1 0
-`,
- },
- })
-}
-
-func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol protocol) {
- const psk = "12345"
- const pskIdentity = "luggage combo"
-
- if !ver.supportsProtocol(protocol) {
- return
- }
- prefix := protocol.String() + "-"
-
- var cert *Credential
- if isTLS13Suite(suite.name) {
- cert = &rsaCertificate
- } else if hasComponent(suite.name, "ECDSA") {
- cert = &ecdsaP256Certificate
- } else if hasComponent(suite.name, "RSA") {
- cert = &rsaCertificate
- }
-
- var flags []string
- if hasComponent(suite.name, "PSK") {
- flags = append(flags,
- "-psk", psk,
- "-psk-identity", pskIdentity)
- }
-
- if hasComponent(suite.name, "3DES") {
- // BoringSSL disables 3DES ciphers by default.
- flags = append(flags, "-cipher", "3DES")
- }
-
- var shouldFail bool
- if isTLS12Only(suite.name) && ver.version < VersionTLS12 {
- shouldFail = true
- }
- if !isTLS13Suite(suite.name) && ver.version >= VersionTLS13 {
- shouldFail = true
- }
- if isTLS13Suite(suite.name) && ver.version < VersionTLS13 {
- shouldFail = true
- }
-
- var sendCipherSuite uint16
- var expectedServerError, expectedClientError string
- serverCipherSuites := []uint16{suite.id}
- if shouldFail {
- expectedServerError = ":NO_SHARED_CIPHER:"
- if ver.version >= VersionTLS13 && cert == nil {
- // TLS 1.2 PSK ciphers won't configure a server certificate, but we
- // require one in TLS 1.3.
- expectedServerError = ":NO_CERTIFICATE_SET:"
- }
- expectedClientError = ":WRONG_CIPHER_RETURNED:"
- // Configure the server to select ciphers as normal but
- // select an incompatible cipher in ServerHello.
- serverCipherSuites = nil
- sendCipherSuite = suite.id
- }
-
- // Verify exporters interoperate.
- exportKeyingMaterial := 1024
-
- if ver.version != VersionTLS13 || !ver.hasDTLS {
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: prefix + ver.name + "-" + suite.name + "-server",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CipherSuites: []uint16{suite.id},
- Credential: cert,
- PreSharedKey: []byte(psk),
- PreSharedKeyIdentity: pskIdentity,
- Bugs: ProtocolBugs{
- AdvertiseAllConfiguredCiphers: true,
- },
- },
- shimCertificate: cert,
- flags: flags,
- resumeSession: true,
- shouldFail: shouldFail,
- expectedError: expectedServerError,
- exportKeyingMaterial: exportKeyingMaterial,
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: prefix + ver.name + "-" + suite.name + "-client",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CipherSuites: serverCipherSuites,
- Credential: cert,
- PreSharedKey: []byte(psk),
- PreSharedKeyIdentity: pskIdentity,
- Bugs: ProtocolBugs{
- IgnorePeerCipherPreferences: shouldFail,
- SendCipherSuite: sendCipherSuite,
- },
- },
- flags: flags,
- resumeSession: true,
- shouldFail: shouldFail,
- expectedError: expectedClientError,
- exportKeyingMaterial: exportKeyingMaterial,
- })
- }
-
- if shouldFail {
- return
- }
-
- // Ensure the maximum record size is accepted.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: prefix + ver.name + "-" + suite.name + "-LargeRecord",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CipherSuites: []uint16{suite.id},
- Credential: cert,
- PreSharedKey: []byte(psk),
- PreSharedKeyIdentity: pskIdentity,
- },
- flags: flags,
- messageLen: maxPlaintext,
- })
-
- // Test bad records for all ciphers. Bad records are fatal in TLS
- // and ignored in DTLS.
- shouldFail = protocol == tls
- var expectedError string
- if shouldFail {
- expectedError = ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"
- }
-
- // When QUIC is used, the QUIC stack handles record encryption/decryption.
- // Thus it is not possible for the TLS stack in QUIC mode to receive a
- // bad record (i.e. one that fails to decrypt).
- if protocol != quic {
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: prefix + ver.name + "-" + suite.name + "-BadRecord",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CipherSuites: []uint16{suite.id},
- Credential: cert,
- PreSharedKey: []byte(psk),
- PreSharedKeyIdentity: pskIdentity,
- },
- flags: flags,
- damageFirstWrite: true,
- messageLen: maxPlaintext,
- shouldFail: shouldFail,
- expectedError: expectedError,
- })
- }
-}
-
-func addCipherSuiteTests() {
- const bogusCipher = 0xfe00
-
- for _, suite := range testCipherSuites {
- for _, ver := range tlsVersions {
- for _, protocol := range []protocol{tls, dtls, quic} {
- addTestForCipherSuite(suite, ver, protocol)
- }
- }
- }
-
- testCases = append(testCases, testCase{
- name: "NoSharedCipher",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{},
- },
- shouldFail: true,
- expectedError: ":HANDSHAKE_FAILURE_ON_CLIENT_HELLO:",
- })
-
- testCases = append(testCases, testCase{
- name: "NoSharedCipher-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{},
- },
- shouldFail: true,
- expectedError: ":HANDSHAKE_FAILURE_ON_CLIENT_HELLO:",
- })
-
- testCases = append(testCases, testCase{
- name: "UnsupportedCipherSuite",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- IgnorePeerCipherPreferences: true,
- },
- },
- flags: []string{"-cipher", "DEFAULT:!AES"},
- shouldFail: true,
- expectedError: ":WRONG_CIPHER_RETURNED:",
- })
-
- testCases = append(testCases, testCase{
- name: "ServerHelloBogusCipher",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendCipherSuite: bogusCipher,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CIPHER_RETURNED:",
- })
- testCases = append(testCases, testCase{
- name: "ServerHelloBogusCipher-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendCipherSuite: bogusCipher,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CIPHER_RETURNED:",
- })
-
- // The server must be tolerant to bogus ciphers.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "UnknownCipher",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{bogusCipher, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- AdvertiseAllConfiguredCiphers: true,
- },
- },
- })
-
- // The server must be tolerant to bogus ciphers.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "UnknownCipher-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{bogusCipher, TLS_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- AdvertiseAllConfiguredCiphers: true,
- },
- },
- })
-
- // Test empty ECDHE_PSK identity hints work as expected.
- testCases = append(testCases, testCase{
- name: "EmptyECDHEPSKHint",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
- PreSharedKey: []byte("secret"),
- },
- flags: []string{"-psk", "secret"},
- })
-
- // Test empty PSK identity hints work as expected, even if an explicit
- // ServerKeyExchange is sent.
- testCases = append(testCases, testCase{
- name: "ExplicitEmptyPSKHint",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
- PreSharedKey: []byte("secret"),
- Bugs: ProtocolBugs{
- AlwaysSendPreSharedKeyIdentityHint: true,
- },
- },
- flags: []string{"-psk", "secret"},
- })
-
- // Test that clients enforce that the server-sent certificate and cipher
- // suite match in TLS 1.2.
- testCases = append(testCases, testCase{
- name: "CertificateCipherMismatch-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CERTIFICATE_TYPE:",
- })
- testCases = append(testCases, testCase{
- name: "CertificateCipherMismatch-ECDSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- Credential: &ecdsaP256Certificate,
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CERTIFICATE_TYPE:",
- })
- testCases = append(testCases, testCase{
- name: "CertificateCipherMismatch-Ed25519",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- Credential: &ed25519Certificate,
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CERTIFICATE_TYPE:",
- })
-
- // Test that servers decline to select a cipher suite which is
- // inconsistent with their configured certificate.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerCipherFilter-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":NO_SHARED_CIPHER:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerCipherFilter-ECDSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &ecdsaP256Certificate,
- shouldFail: true,
- expectedError: ":NO_SHARED_CIPHER:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerCipherFilter-Ed25519",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &ed25519Certificate,
- shouldFail: true,
- expectedError: ":NO_SHARED_CIPHER:",
- })
-
- // Test cipher suite negotiation works as expected. Configure a
- // complicated cipher suite configuration.
- const negotiationTestCiphers = "" +
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:" +
- "[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384|TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]:" +
- "TLS_RSA_WITH_AES_128_GCM_SHA256:" +
- "TLS_RSA_WITH_AES_128_CBC_SHA:" +
- "[TLS_RSA_WITH_AES_256_GCM_SHA384|TLS_RSA_WITH_AES_256_CBC_SHA]"
- negotiationTests := []struct {
- ciphers []uint16
- expected uint16
- }{
- // Server preferences are honored, including when
- // equipreference groups are involved.
- {
- []uint16{
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- TLS_RSA_WITH_AES_128_CBC_SHA,
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- },
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- },
- {
- []uint16{
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- TLS_RSA_WITH_AES_128_CBC_SHA,
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- },
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- },
- {
- []uint16{
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- TLS_RSA_WITH_AES_128_CBC_SHA,
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- {
- []uint16{
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- TLS_RSA_WITH_AES_128_CBC_SHA,
- },
- TLS_RSA_WITH_AES_128_CBC_SHA,
- },
- // Equipreference groups use the client preference.
- {
- []uint16{
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- },
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- },
- {
- []uint16{
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- },
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- },
- {
- []uint16{
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- },
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- },
- {
- []uint16{
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- TLS_RSA_WITH_AES_256_CBC_SHA,
- },
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- },
- {
- []uint16{
- TLS_RSA_WITH_AES_256_CBC_SHA,
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- },
- TLS_RSA_WITH_AES_256_CBC_SHA,
- },
- // If there are two equipreference groups, the preferred one
- // takes precedence.
- {
- []uint16{
- TLS_RSA_WITH_AES_256_GCM_SHA384,
- TLS_RSA_WITH_AES_256_CBC_SHA,
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- },
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- },
- }
- for i, t := range negotiationTests {
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CipherNegotiation-" + strconv.Itoa(i),
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: t.ciphers,
- },
- flags: []string{"-cipher", negotiationTestCiphers},
- expectations: connectionExpectations{
- cipher: t.expected,
- },
- })
- }
-}
-
-func addBadECDSASignatureTests() {
- for badR := BadValue(1); badR < NumBadValues; badR++ {
- for badS := BadValue(1); badS < NumBadValues; badS++ {
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("BadECDSA-%d-%d", badR, badS),
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- Credential: &ecdsaP256Certificate,
- Bugs: ProtocolBugs{
- BadECDSAR: badR,
- BadECDSAS: badS,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_SIGNATURE:",
- })
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("BadECDSA-%d-%d-TLS13", badR, badS),
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &ecdsaP256Certificate,
- Bugs: ProtocolBugs{
- BadECDSAR: badR,
- BadECDSAS: badS,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_SIGNATURE:",
- })
- }
- }
-}
-
-func addCBCPaddingTests() {
- testCases = append(testCases, testCase{
- name: "MaxCBCPadding",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- MaxPadding: true,
- },
- },
- messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
- })
- testCases = append(testCases, testCase{
- name: "BadCBCPadding",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- PaddingFirstByteBad: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
- // OpenSSL previously had an issue where the first byte of padding in
- // 255 bytes of padding wasn't checked.
- testCases = append(testCases, testCase{
- name: "BadCBCPadding255",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- MaxPadding: true,
- PaddingFirstByteBadIf255: true,
- },
- },
- messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
-}
-
-func addCBCSplittingTests() {
- cbcCiphers := []struct {
- name string
- cipher uint16
- }{
- {"3DES", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
- {"AES128", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- {"AES256", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
- }
- for _, t := range cbcCiphers {
- testCases = append(testCases, testCase{
- name: "CBCRecordSplitting-" + t.name,
- config: Config{
- MaxVersion: VersionTLS10,
- MinVersion: VersionTLS10,
- CipherSuites: []uint16{t.cipher},
- Bugs: ProtocolBugs{
- ExpectRecordSplitting: true,
- },
- },
- messageLen: -1, // read until EOF
- resumeSession: true,
- flags: []string{
- "-async",
- "-write-different-record-sizes",
- "-cbc-record-splitting",
- // BoringSSL disables 3DES by default.
- "-cipher", "ALL:3DES",
- },
- })
- testCases = append(testCases, testCase{
- name: "CBCRecordSplittingPartialWrite-" + t.name,
- config: Config{
- MaxVersion: VersionTLS10,
- MinVersion: VersionTLS10,
- CipherSuites: []uint16{t.cipher},
- Bugs: ProtocolBugs{
- ExpectRecordSplitting: true,
- },
- },
- messageLen: -1, // read until EOF
- flags: []string{
- "-async",
- "-write-different-record-sizes",
- "-cbc-record-splitting",
- "-partial-write",
- // BoringSSL disables 3DES by default.
- "-cipher", "ALL:3DES",
- },
- })
- }
-}
-
-func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool {
- certPool := x509.NewCertPool()
- for _, cred := range creds {
- cert, err := x509.ParseCertificate(cred.RootCertificate)
- if err != nil {
- panic(err)
- }
- certPool.AddCert(cert)
- }
- return certPool
-}
-
-func addClientAuthTests() {
- // Add a dummy cert pool to stress certificate authority parsing.
- certPool := makeCertPoolFromRoots(&rsaCertificate, &rsa1024Certificate)
- caNames := certPool.Subjects()
-
- for _, ver := range tlsVersions {
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: ver.name + "-Client-ClientAuth-RSA",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- ClientAuth: RequireAnyClientCert,
- ClientCAs: certPool,
- },
- shimCertificate: &rsaCertificate,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: ver.name + "-Server-ClientAuth-RSA",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- },
- flags: []string{"-require-any-client-certificate"},
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: ver.name + "-Server-ClientAuth-ECDSA",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &ecdsaP256Certificate,
- },
- flags: []string{"-require-any-client-certificate"},
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: ver.name + "-Client-ClientAuth-ECDSA",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- ClientAuth: RequireAnyClientCert,
- ClientCAs: certPool,
- },
- shimCertificate: &ecdsaP256Certificate,
- })
-
- testCases = append(testCases, testCase{
- name: "NoClientCertificate-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- ClientAuth: RequireAnyClientCert,
- },
- shouldFail: true,
- expectedLocalError: "client didn't provide a certificate",
- })
-
- testCases = append(testCases, testCase{
- // Even if not configured to expect a certificate, OpenSSL will
- // return X509_V_OK as the verify_result.
- testType: serverTest,
- name: "NoClientCertificateRequested-Server-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- flags: []string{
- "-expect-verify-result",
- },
- resumeSession: true,
- })
-
- testCases = append(testCases, testCase{
- // If a client certificate is not provided, OpenSSL will still
- // return X509_V_OK as the verify_result.
- testType: serverTest,
- name: "NoClientCertificate-Server-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- flags: []string{
- "-expect-verify-result",
- "-verify-peer",
- },
- resumeSession: true,
- })
-
- certificateRequired := "remote error: certificate required"
- if ver.version < VersionTLS13 {
- // Prior to TLS 1.3, the generic handshake_failure alert
- // was used.
- certificateRequired = "remote error: handshake failure"
- }
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RequireAnyClientCertificate-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- flags: []string{"-require-any-client-certificate"},
- shouldFail: true,
- expectedError: ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
- expectedLocalError: certificateRequired,
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipClientCertificate-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SkipClientCertificate: true,
- },
- },
- // Setting SSL_VERIFY_PEER allows anonymous clients.
- flags: []string{"-verify-peer"},
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: ver.name + "-Server-CertReq-CA-List",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- ExpectCertificateReqNames: caNames,
- },
- },
- flags: []string{
- "-require-any-client-certificate",
- "-use-client-ca-list", encodeDERValues(caNames),
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: ver.name + "-Client-CertReq-CA-List",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- ClientAuth: RequireAnyClientCert,
- ClientCAs: certPool,
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-expect-client-ca-list", encodeDERValues(caNames),
- },
- })
- }
-
- // Client auth is only legal in certificate-based ciphers.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ClientAuth-PSK",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
- PreSharedKey: []byte("secret"),
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-psk", "secret",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ClientAuth-ECDHE_PSK",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
- PreSharedKey: []byte("secret"),
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-psk", "secret",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
-
- // Regression test for a bug where the client CA list, if explicitly
- // set to NULL, was mis-encoded.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Null-Client-CA-List",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- ExpectCertificateReqNames: [][]byte{},
- },
- },
- flags: []string{
- "-require-any-client-certificate",
- "-use-client-ca-list", "<NULL>",
- },
- })
-
- // Test that an empty client CA list doesn't send a CA extension.
- // (This is implicitly tested by the parser. An empty CA extension is
- // a syntax error.)
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-Empty-Client-CA-List",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-require-any-client-certificate",
- "-use-client-ca-list", "<EMPTY>",
- },
- })
-}
-
-func addExtendedMasterSecretTests() {
- const expectEMSFlag = "-expect-extended-master-secret"
-
- for _, with := range []bool{false, true} {
- prefix := "No"
- if with {
- prefix = ""
- }
-
- for _, isClient := range []bool{false, true} {
- suffix := "-Server"
- testType := serverTest
- if isClient {
- suffix = "-Client"
- testType = clientTest
- }
-
- for _, ver := range tlsVersions {
- // In TLS 1.3, the extension is irrelevant and
- // always reports as enabled.
- var flags []string
- if with || ver.version >= VersionTLS13 {
- flags = []string{expectEMSFlag}
- }
-
- testCases = append(testCases, testCase{
- testType: testType,
- name: prefix + "ExtendedMasterSecret-" + ver.name + suffix,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecret: !with,
- RequireExtendedMasterSecret: with,
- },
- },
- flags: flags,
- })
- }
- }
- }
-
- for _, isClient := range []bool{false, true} {
- for _, supportedInFirstConnection := range []bool{false, true} {
- for _, supportedInResumeConnection := range []bool{false, true} {
- boolToWord := func(b bool) string {
- if b {
- return "Yes"
- }
- return "No"
- }
- suffix := boolToWord(supportedInFirstConnection) + "To" + boolToWord(supportedInResumeConnection) + "-"
- if isClient {
- suffix += "Client"
- } else {
- suffix += "Server"
- }
-
- supportedConfig := Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- RequireExtendedMasterSecret: true,
- },
- }
-
- noSupportConfig := Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecret: true,
- },
- }
-
- test := testCase{
- name: "ExtendedMasterSecret-" + suffix,
- resumeSession: true,
- }
-
- if !isClient {
- test.testType = serverTest
- }
-
- if supportedInFirstConnection {
- test.config = supportedConfig
- } else {
- test.config = noSupportConfig
- }
-
- if supportedInResumeConnection {
- test.resumeConfig = &supportedConfig
- } else {
- test.resumeConfig = &noSupportConfig
- }
-
- switch suffix {
- case "YesToYes-Client", "YesToYes-Server":
- // When a session is resumed, it should
- // still be aware that its master
- // secret was generated via EMS and
- // thus it's safe to use tls-unique.
- test.flags = []string{expectEMSFlag}
- case "NoToYes-Server":
- // If an original connection did not
- // contain EMS, but a resumption
- // handshake does, then a server should
- // not resume the session.
- test.expectResumeRejected = true
- case "YesToNo-Server":
- // Resuming an EMS session without the
- // EMS extension should cause the
- // server to abort the connection.
- test.shouldFail = true
- test.expectedError = ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"
- case "NoToYes-Client":
- // A client should abort a connection
- // where the server resumed a non-EMS
- // session but echoed the EMS
- // extension.
- test.shouldFail = true
- test.expectedError = ":RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION:"
- case "YesToNo-Client":
- // A client should abort a connection
- // where the server didn't echo EMS
- // when the session used it.
- test.shouldFail = true
- test.expectedError = ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"
- }
-
- testCases = append(testCases, test)
- }
- }
- }
-
- // Switching EMS on renegotiation is forbidden.
- testCases = append(testCases, testCase{
- name: "ExtendedMasterSecret-Renego-NoEMS",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecret: true,
- NoExtendedMasterSecretOnRenegotiation: true,
- },
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
-
- testCases = append(testCases, testCase{
- name: "ExtendedMasterSecret-Renego-Upgrade",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecret: true,
- },
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shouldFail: true,
- expectedError: ":RENEGOTIATION_EMS_MISMATCH:",
- })
-
- testCases = append(testCases, testCase{
- name: "ExtendedMasterSecret-Renego-Downgrade",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecretOnRenegotiation: true,
- },
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shouldFail: true,
- expectedError: ":RENEGOTIATION_EMS_MISMATCH:",
- })
-}
-
-type stateMachineTestConfig struct {
- protocol protocol
- async bool
- splitHandshake bool
- packHandshake bool
- implicitHandshake bool
-}
-
-// Adds tests that try to cover the range of the handshake state machine, under
-// various conditions. Some of these are redundant with other tests, but they
-// only cover the synchronous case.
-func addAllStateMachineCoverageTests() {
- for _, async := range []bool{false, true} {
- for _, protocol := range []protocol{tls, dtls, quic} {
- addStateMachineCoverageTests(stateMachineTestConfig{
- protocol: protocol,
- async: async,
- })
- // QUIC doesn't work with the implicit handshake API. Additionally,
- // splitting or packing handshake records is meaningless in QUIC.
- if protocol != quic {
- addStateMachineCoverageTests(stateMachineTestConfig{
- protocol: protocol,
- async: async,
- implicitHandshake: true,
- })
- addStateMachineCoverageTests(stateMachineTestConfig{
- protocol: protocol,
- async: async,
- splitHandshake: true,
- })
- addStateMachineCoverageTests(stateMachineTestConfig{
- protocol: protocol,
- async: async,
- packHandshake: true,
- })
- }
- }
- }
-}
-
-func addStateMachineCoverageTests(config stateMachineTestConfig) {
- var tests []testCase
-
- // Basic handshake, with resumption. Client and server,
- // session ID and session ticket.
- // The following tests have a max version of 1.2, so they are not suitable
- // for use with QUIC.
- if config.protocol != quic {
- tests = append(tests, testCase{
- name: "Basic-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeSession: true,
- // Ensure session tickets are used, not session IDs.
- noSessionCache: true,
- flags: []string{"-expect-no-hrr"},
- })
- tests = append(tests, testCase{
- name: "Basic-Client-RenewTicket",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- RenewTicketOnResume: true,
- },
- },
- flags: []string{"-expect-ticket-renewal"},
- resumeSession: true,
- resumeRenewedSession: true,
- })
- tests = append(tests, testCase{
- name: "Basic-Client-NoTicket",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- resumeSession: true,
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- RequireSessionTickets: true,
- },
- },
- resumeSession: true,
- flags: []string{
- "-expect-no-session-id",
- "-expect-no-hrr",
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server-NoTickets",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- resumeSession: true,
- flags: []string{"-expect-session-id"},
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server-EarlyCallback",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- flags: []string{"-use-early-callback"},
- resumeSession: true,
- })
- }
-
- // TLS 1.3 basic handshake shapes.
- tests = append(tests, testCase{
- name: "TLS13-1RTT-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- resumeSession: true,
- resumeRenewedSession: true,
- // 0-RTT being disabled overrides all other 0-RTT reasons.
- flags: []string{"-expect-early-data-reason", "disabled"},
- })
-
- tests = append(tests, testCase{
- testType: serverTest,
- name: "TLS13-1RTT-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- resumeSession: true,
- resumeRenewedSession: true,
- flags: []string{
- // TLS 1.3 uses tickets, so the session should not be
- // cached statefully.
- "-expect-no-session-id",
- // 0-RTT being disabled overrides all other 0-RTT reasons.
- "-expect-early-data-reason", "disabled",
- },
- })
-
- tests = append(tests, testCase{
- name: "TLS13-HelloRetryRequest-Client",
- 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,
- },
- },
- // Cover HelloRetryRequest during an ECDHE-PSK resumption.
- resumeSession: true,
- flags: []string{"-expect-hrr"},
- })
-
- tests = append(tests, testCase{
- testType: serverTest,
- name: "TLS13-HelloRetryRequest-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- // Require a HelloRetryRequest for every curve.
- DefaultCurves: []CurveID{},
- },
- // Cover HelloRetryRequest during an ECDHE-PSK resumption.
- resumeSession: true,
- flags: []string{"-expect-hrr"},
- })
-
- // TLS 1.3 early data tests. DTLS 1.3 doesn't support early data yet.
- // These tests are disabled for QUIC as well because they test features
- // that do not apply to QUIC's use of TLS 1.3.
- //
- // TODO(crbug.com/381113363): Enable these tests for DTLS once we
- // support early data in DTLS 1.3.
- if config.protocol != dtls && config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "TLS13-EarlyData-TooMuchData-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- MaxEarlyDataSize: 2,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- MaxEarlyDataSize: 2,
- Bugs: ProtocolBugs{
- ExpectEarlyData: [][]byte{[]byte(shimInitialWrite[:2])},
- },
- },
- resumeShimPrefix: shimInitialWrite[2:],
- resumeSession: true,
- earlyData: true,
- })
-
- // Unfinished writes can only be tested when operations are async. EarlyData
- // can't be tested as part of an ImplicitHandshake in this case since
- // otherwise the early data will be sent as normal data.
- if config.async && !config.implicitHandshake {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "TLS13-EarlyData-UnfinishedWrite-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Write the server response before expecting early data.
- ExpectEarlyData: [][]byte{},
- ExpectLateEarlyData: [][]byte{[]byte(shimInitialWrite)},
- },
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{"-on-resume-read-with-unfinished-write"},
- })
-
- // Rejected unfinished writes are discarded (from the
- // perspective of the calling application) on 0-RTT
- // reject.
- tests = append(tests, testCase{
- testType: clientTest,
- name: "TLS13-EarlyData-RejectUnfinishedWrite-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysRejectEarlyData: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{"-on-resume-read-with-unfinished-write"},
- })
- }
-
- // Early data has no size limit in QUIC.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "TLS13-MaxEarlyData-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEarlyData: [][]byte{bytes.Repeat([]byte{1}, 14336+1)},
- ExpectEarlyDataAccepted: true,
- },
- },
- messageCount: 2,
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":TOO_MUCH_READ_EARLY_DATA:",
- })
- }
-
- // Test that early data is disabled for DTLS 1.3.
- if config.protocol == dtls {
- tests = append(tests, testCase{
- testType: clientTest,
- protocol: dtls,
- name: "DTLS13-EarlyData",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- })
- }
-
- // TLS client auth.
- // The following tests have a max version of 1.2, so they are not suitable
- // for use with QUIC.
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-NoCertificate-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequestClientCert,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ClientAuth-NoCertificate-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- // Setting SSL_VERIFY_PEER allows anonymous clients.
- flags: []string{"-verify-peer"},
- })
- }
- if config.protocol != dtls {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-NoCertificate-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequestClientCert,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ClientAuth-NoCertificate-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- // Setting SSL_VERIFY_PEER allows anonymous clients.
- flags: []string{"-verify-peer"},
- })
- }
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-RSA-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &rsaCertificate,
- })
- }
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-RSA-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &rsaCertificate,
- })
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-ECDSA-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &ecdsaP256Certificate,
- })
- }
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-ECDSA-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &ecdsaP256Certificate,
- })
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-NoCertificate-OldCallback",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequestClientCert,
- },
- flags: []string{"-use-old-client-cert-callback"},
- })
- }
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-NoCertificate-OldCallback-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequestClientCert,
- },
- flags: []string{"-use-old-client-cert-callback"},
- })
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-OldCallback",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-use-old-client-cert-callback",
- },
- })
- }
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-OldCallback-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-use-old-client-cert-callback",
- },
- })
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ClientAuth-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: &rsaCertificate,
- },
- flags: []string{"-require-any-client-certificate"},
- })
- }
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ClientAuth-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &rsaCertificate,
- },
- flags: []string{"-require-any-client-certificate"},
- })
-
- // Test each key exchange on the server side for async keys.
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &rsaCertificate,
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server-ECDHE-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &rsaCertificate,
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server-ECDHE-ECDSA",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &ecdsaP256Certificate,
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Basic-Server-Ed25519",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- },
- shimCertificate: &ed25519Certificate,
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureEd25519)),
- },
- })
-
- // No session ticket support; server doesn't send NewSessionTicket.
- tests = append(tests, testCase{
- name: "SessionTicketsDisabled-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "SessionTicketsDisabled-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- })
-
- // Skip ServerKeyExchange in PSK key exchange if there's no
- // identity hint.
- tests = append(tests, testCase{
- name: "EmptyPSKHint-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
- PreSharedKey: []byte("secret"),
- },
- flags: []string{"-psk", "secret"},
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "EmptyPSKHint-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
- PreSharedKey: []byte("secret"),
- },
- flags: []string{"-psk", "secret"},
- })
- }
-
- // OCSP stapling tests.
- for _, vers := range allVersions(config.protocol) {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "OCSPStapling-Client-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-expect-ocsp-response",
- base64FlagValue(testOCSPResponse),
- "-verify-peer",
- },
- resumeSession: true,
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "OCSPStapling-Server-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- expectations: connectionExpectations{
- peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- resumeSession: true,
- })
-
- // The client OCSP callback is an alternate certificate
- // verification callback.
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientOCSPCallback-Pass-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-use-ocsp-callback",
- },
- })
- var expectedLocalError string
- if !config.async {
- // TODO(davidben): Asynchronous fatal alerts are never
- // sent. https://crbug.com/boringssl/130.
- expectedLocalError = "remote error: bad certificate status response"
- }
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientOCSPCallback-Fail-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-use-ocsp-callback",
- "-fail-ocsp-callback",
- },
- shouldFail: true,
- expectedLocalError: expectedLocalError,
- expectedError: ":OCSP_CB_ERROR:",
- })
- // The callback still runs if the server does not send an OCSP
- // response.
- tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientOCSPCallback-FailNoStaple-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-use-ocsp-callback",
- "-fail-ocsp-callback",
- },
- shouldFail: true,
- expectedLocalError: expectedLocalError,
- expectedError: ":OCSP_CB_ERROR:",
- })
-
- // The server OCSP callback is a legacy mechanism for
- // configuring OCSP, used by unreliable server software.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ServerOCSPCallback-SetInCallback-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- expectations: connectionExpectations{
- peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- flags: []string{
- "-use-ocsp-callback",
- "-set-ocsp-in-callback",
- },
- resumeSession: true,
- })
-
- // The callback may decline OCSP, in which case we act as if
- // the client did not support it, even if a response was
- // configured.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ServerOCSPCallback-Decline-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- expectations: connectionExpectations{
- // There should be no OCSP response from the peer.
- peerCertificate: &rsaCertificate,
- },
- flags: []string{
- "-use-ocsp-callback",
- "-decline-ocsp-callback",
- },
- resumeSession: true,
- })
-
- // The callback may also signal an internal error.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ServerOCSPCallback-Fail-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- flags: []string{
- "-use-ocsp-callback",
- "-fail-ocsp-callback",
- },
- shouldFail: true,
- expectedError: ":OCSP_CB_ERROR:",
- })
- }
-
- // Certificate verification tests.
- for _, vers := range allVersions(config.protocol) {
- for _, useCustomCallback := range []bool{false, true} {
- for _, testType := range []testType{clientTest, serverTest} {
- suffix := "-Client"
- if testType == serverTest {
- suffix = "-Server"
- }
- suffix += "-" + vers.name
- if useCustomCallback {
- suffix += "-CustomCallback"
- }
-
- // The custom callback and legacy callback have different default
- // alerts.
- verifyFailLocalError := "remote error: handshake failure"
- if useCustomCallback {
- verifyFailLocalError = "remote error: unknown certificate"
- }
-
- // We do not reliably send asynchronous fatal alerts. See
- // https://crbug.com/boringssl/130.
- if config.async {
- verifyFailLocalError = ""
- }
-
- flags := []string{"-verify-peer"}
- if testType == serverTest {
- flags = append(flags, "-require-any-client-certificate")
- }
- if useCustomCallback {
- flags = append(flags, "-use-custom-verify-callback")
- }
-
- tests = append(tests, testCase{
- testType: testType,
- name: "CertificateVerificationSucceed" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: append([]string{"-expect-verify-result"}, flags...),
- resumeSession: true,
- })
- tests = append(tests, testCase{
- testType: testType,
- name: "CertificateVerificationFail" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: append([]string{"-verify-fail"}, flags...),
- shouldFail: true,
- expectedError: ":CERTIFICATE_VERIFY_FAILED:",
- expectedLocalError: verifyFailLocalError,
- })
- // Tests that although the verify callback fails on resumption, by default we don't call it.
- tests = append(tests, testCase{
- testType: testType,
- name: "CertificateVerificationDoesNotFailOnResume" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: append([]string{"-on-resume-verify-fail"}, flags...),
- resumeSession: true,
- })
- if testType == clientTest && useCustomCallback {
- tests = append(tests, testCase{
- testType: testType,
- name: "CertificateVerificationFailsOnResume" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: append([]string{
- "-on-resume-verify-fail",
- "-reverify-on-resume",
- }, flags...),
- resumeSession: true,
- shouldFail: true,
- expectedError: ":CERTIFICATE_VERIFY_FAILED:",
- expectedLocalError: verifyFailLocalError,
- })
- tests = append(tests, testCase{
- testType: testType,
- name: "CertificateVerificationPassesOnResume" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: append([]string{
- "-reverify-on-resume",
- }, flags...),
- resumeSession: true,
- })
- // TODO(crbug.com/381113363): Support 0-RTT in DTLS 1.3.
- if vers.version >= VersionTLS13 && config.protocol != dtls {
- tests = append(tests, testCase{
- testType: testType,
- name: "EarlyData-RejectTicket-Client-Reverify" + suffix,
- config: Config{
- MaxVersion: vers.version,
- },
- resumeConfig: &Config{
- MaxVersion: vers.version,
- SessionTicketsDisabled: true,
- },
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: append([]string{
- "-reverify-on-resume",
- // Session tickets are disabled, so the runner will not send a ticket.
- "-on-retry-expect-no-session",
- }, flags...),
- })
- tests = append(tests, testCase{
- testType: testType,
- name: "EarlyData-Reject0RTT-Client-Reverify" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- AlwaysRejectEarlyData: true,
- },
- },
- resumeSession: true,
- expectResumeRejected: false,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: append([]string{
- "-reverify-on-resume",
- }, flags...),
- })
- tests = append(tests, testCase{
- testType: testType,
- name: "EarlyData-RejectTicket-Client-ReverifyFails" + suffix,
- config: Config{
- MaxVersion: vers.version,
- },
- resumeConfig: &Config{
- MaxVersion: vers.version,
- SessionTicketsDisabled: true,
- },
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- shouldFail: true,
- expectedError: ":CERTIFICATE_VERIFY_FAILED:",
- flags: append([]string{
- "-reverify-on-resume",
- // Session tickets are disabled, so the runner will not send a ticket.
- "-on-retry-expect-no-session",
- "-on-retry-verify-fail",
- }, flags...),
- })
- tests = append(tests, testCase{
- testType: testType,
- name: "EarlyData-Reject0RTT-Client-ReverifyFails" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- AlwaysRejectEarlyData: true,
- },
- },
- resumeSession: true,
- expectResumeRejected: false,
- earlyData: true,
- expectEarlyDataRejected: true,
- shouldFail: true,
- expectedError: ":CERTIFICATE_VERIFY_FAILED:",
- expectedLocalError: verifyFailLocalError,
- flags: append([]string{
- "-reverify-on-resume",
- "-on-retry-verify-fail",
- }, flags...),
- })
- // This tests that we only call the verify callback once.
- tests = append(tests, testCase{
- testType: testType,
- name: "EarlyData-Accept0RTT-Client-Reverify" + suffix,
- config: Config{
- MaxVersion: vers.version,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-reverify-on-resume",
- }, flags...),
- })
- tests = append(tests, testCase{
- testType: testType,
- name: "EarlyData-Accept0RTT-Client-ReverifyFails" + suffix,
- config: Config{
- MaxVersion: vers.version,
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":CERTIFICATE_VERIFY_FAILED:",
- // We do not set expectedLocalError here because the shim rejects
- // the connection without an alert.
- flags: append([]string{
- "-reverify-on-resume",
- "-on-resume-verify-fail",
- }, flags...),
- })
- }
- }
- }
- }
-
- // By default, the client is in a soft fail mode where the peer
- // certificate is verified but failures are non-fatal.
- tests = append(tests, testCase{
- testType: clientTest,
- name: "CertificateVerificationSoftFail-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-verify-fail",
- "-expect-verify-result",
- },
- resumeSession: true,
- })
- }
-
- tests = append(tests, testCase{
- name: "ShimSendAlert",
- flags: []string{"-send-alert"},
- shimWritesFirst: true,
- shouldFail: true,
- expectedLocalError: "remote error: decompression failure",
- })
-
- if config.protocol == tls {
- tests = append(tests, testCase{
- name: "Renegotiate-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
-
- tests = append(tests, testCase{
- name: "Renegotiate-Client-Explicit",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-explicit",
- "-expect-total-renegotiations", "1",
- },
- })
-
- halfHelloRequestError := ":UNEXPECTED_RECORD:"
- if config.packHandshake {
- // If the HelloRequest is sent in the same record as the server Finished,
- // BoringSSL rejects it before the handshake completes.
- halfHelloRequestError = ":EXCESS_HANDSHAKE_DATA:"
- }
- tests = append(tests, testCase{
- name: "SendHalfHelloRequest",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- PackHelloRequestWithFinished: config.packHandshake,
- },
- },
- sendHalfHelloRequest: true,
- flags: []string{"-renegotiate-ignore"},
- shouldFail: true,
- expectedError: halfHelloRequestError,
- })
-
- // NPN on client and server; results in post-ChangeCipherSpec message.
- tests = append(tests, testCase{
- name: "NPN-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"foo"},
- },
- flags: []string{"-select-next-proto", "foo"},
- resumeSession: true,
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: npn,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "NPN-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"bar"},
- },
- flags: []string{
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- "-expect-next-proto", "bar",
- },
- resumeSession: true,
- expectations: connectionExpectations{
- nextProto: "bar",
- nextProtoType: npn,
- },
- })
-
- // The client may select no protocol after seeing the server list.
- tests = append(tests, testCase{
- name: "NPN-Client-ClientSelectEmpty",
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"foo"},
- },
- flags: []string{"-select-empty-next-proto"},
- resumeSession: true,
- expectations: connectionExpectations{
- noNextProto: true,
- nextProtoType: npn,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "NPN-Server-ClientSelectEmpty",
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"no-match"},
- NoFallbackNextProto: true,
- },
- flags: []string{
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- "-expect-no-next-proto",
- },
- resumeSession: true,
- expectations: connectionExpectations{
- noNextProto: true,
- nextProtoType: npn,
- },
- })
-
- // The server may negotiate NPN, despite offering no protocols. In this
- // case, the server must still be prepared for the client to select a
- // fallback protocol.
- tests = append(tests, testCase{
- name: "NPN-Client-ServerAdvertiseEmpty",
- config: Config{
- MaxVersion: VersionTLS12,
- NegotiateNPNWithNoProtos: true,
- },
- flags: []string{"-select-next-proto", "foo"},
- resumeSession: true,
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: npn,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "NPN-Server-ServerAdvertiseEmpty",
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"foo"},
- },
- flags: []string{
- "-advertise-empty-npn",
- "-expect-next-proto", "foo",
- },
- resumeSession: true,
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: npn,
- },
- })
-
- // Client does False Start and negotiates NPN.
- tests = append(tests, testCase{
- name: "FalseStart",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-select-next-proto", "foo",
- },
- shimWritesFirst: true,
- resumeSession: true,
- })
-
- // Client does False Start and negotiates ALPN.
- tests = append(tests, testCase{
- name: "FalseStart-ALPN",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-advertise-alpn", "\x03foo",
- "-expect-alpn", "foo",
- },
- shimWritesFirst: true,
- resumeSession: true,
- })
-
- // False Start without session tickets.
- tests = append(tests, testCase{
- name: "FalseStart-SessionTicketsDisabled",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-select-next-proto", "foo",
- },
- shimWritesFirst: true,
- })
-
- // Server parses a V2ClientHello. Test different lengths for the
- // challenge field.
- for _, challengeLength := range []int{16, 31, 32, 33, 48} {
- tests = append(tests, testCase{
- testType: serverTest,
- name: fmt.Sprintf("SendV2ClientHello-%d", challengeLength),
- config: Config{
- // Choose a cipher suite that does not involve
- // elliptic curves, so no extensions are
- // involved.
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- SendV2ClientHello: true,
- V2ClientHelloChallengeLength: challengeLength,
- },
- },
- flags: []string{
- "-expect-msg-callback",
- `read v2clienthello
-write hs 2
-write hs 11
-write hs 14
-read hs 16
-read ccs
-read hs 20
-write ccs
-write hs 20
-read alert 1 0
-`,
- },
- })
- }
-
- // Channel ID and NPN at the same time, to ensure their relative
- // ordering is correct.
- tests = append(tests, testCase{
- name: "ChannelID-NPN-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- RequestChannelID: true,
- NextProtos: []string{"foo"},
- },
- flags: []string{
- "-send-channel-id", channelIDKeyPath,
- "-select-next-proto", "foo",
- },
- resumeSession: true,
- expectations: connectionExpectations{
- channelID: true,
- nextProto: "foo",
- nextProtoType: npn,
- },
- })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ChannelID-NPN-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- ChannelID: &channelIDKey,
- NextProtos: []string{"bar"},
- },
- flags: []string{
- "-expect-channel-id",
- base64FlagValue(channelIDBytes),
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- "-expect-next-proto", "bar",
- },
- resumeSession: true,
- expectations: connectionExpectations{
- channelID: true,
- nextProto: "bar",
- nextProtoType: npn,
- },
- })
-
- // Bidirectional shutdown with the runner initiating.
- tests = append(tests, testCase{
- name: "Shutdown-Runner",
- config: Config{
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- flags: []string{"-check-close-notify"},
- })
- }
- if config.protocol != dtls {
- // Test Channel ID
- for _, ver := range allVersions(config.protocol) {
- if ver.version < VersionTLS10 {
- continue
- }
- // Client sends a Channel ID.
- tests = append(tests, testCase{
- name: "ChannelID-Client-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- RequestChannelID: true,
- },
- flags: []string{"-send-channel-id", channelIDKeyPath},
- resumeSession: true,
- expectations: connectionExpectations{
- channelID: true,
- },
- })
-
- // Server accepts a Channel ID.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ChannelID-Server-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- ChannelID: &channelIDKey,
- },
- flags: []string{
- "-expect-channel-id",
- base64FlagValue(channelIDBytes),
- },
- resumeSession: true,
- expectations: connectionExpectations{
- channelID: true,
- },
- })
-
- tests = append(tests, testCase{
- testType: serverTest,
- name: "InvalidChannelIDSignature-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- ChannelID: &channelIDKey,
- Bugs: ProtocolBugs{
- InvalidChannelIDSignature: true,
- },
- },
- flags: []string{"-enable-channel-id"},
- shouldFail: true,
- expectedError: ":CHANNEL_ID_SIGNATURE_INVALID:",
- })
-
- if ver.version < VersionTLS13 {
- // Channel ID requires ECDHE ciphers.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ChannelID-NoECDHE-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- ChannelID: &channelIDKey,
- },
- expectations: connectionExpectations{
- channelID: false,
- },
- flags: []string{"-enable-channel-id"},
- })
-
- // Sanity-check setting expectations.channelID false works.
- tests = append(tests, testCase{
- testType: serverTest,
- name: "ChannelID-ECDHE-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- ChannelID: &channelIDKey,
- },
- expectations: connectionExpectations{
- channelID: false,
- },
- flags: []string{"-enable-channel-id"},
- shouldFail: true,
- expectedLocalError: "channel ID unexpectedly negotiated",
- })
- }
- }
-
- if !config.implicitHandshake {
- // Bidirectional shutdown with the shim initiating. The runner,
- // in the meantime, sends garbage before the close_notify which
- // the shim must ignore. This test is disabled under implicit
- // handshake tests because the shim never reads or writes.
-
- // Tests that require checking for a close notify alert don't work with
- // QUIC because alerts are handled outside of the TLS stack in QUIC.
- if config.protocol != quic {
- tests = append(tests, testCase{
- name: "Shutdown-Shim",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- sendEmptyRecords: 1,
- sendWarningAlerts: 1,
- flags: []string{"-check-close-notify"},
- })
-
- // The shim should reject unexpected application data
- // when shutting down.
- tests = append(tests, testCase{
- name: "Shutdown-Shim-ApplicationData",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- messageCount: 1,
- sendEmptyRecords: 1,
- sendWarningAlerts: 1,
- flags: []string{"-check-close-notify"},
- shouldFail: true,
- expectedError: ":APPLICATION_DATA_ON_SHUTDOWN:",
- })
-
- // Test that SSL_shutdown still processes KeyUpdate.
- tests = append(tests, testCase{
- name: "Shutdown-Shim-KeyUpdate",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- sendKeyUpdates: 1,
- keyUpdateRequest: keyUpdateRequested,
- flags: []string{"-check-close-notify"},
- })
-
- // Test that SSL_shutdown processes HelloRequest
- // correctly.
- tests = append(tests, testCase{
- name: "Shutdown-Shim-HelloRequest-Ignore",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendHelloRequestBeforeEveryAppDataRecord: true,
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- flags: []string{
- "-renegotiate-ignore",
- "-check-close-notify",
- },
- })
- tests = append(tests, testCase{
- name: "Shutdown-Shim-HelloRequest-Reject",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- renegotiate: 1,
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- flags: []string{"-check-close-notify"},
- })
- tests = append(tests, testCase{
- name: "Shutdown-Shim-HelloRequest-CannotHandshake",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- renegotiate: 1,
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- flags: []string{
- "-check-close-notify",
- "-renegotiate-freely",
- },
- })
-
- tests = append(tests, testCase{
- testType: serverTest,
- name: "Shutdown-Shim-Renegotiate-Server-Forbidden",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- shimShutsDown: true,
- renegotiate: 1,
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- flags: []string{
- "-check-close-notify",
- },
- })
- }
- }
- }
- if config.protocol == dtls {
- tests = append(tests, testCase{
- name: "SkipHelloVerifyRequest",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SkipHelloVerifyRequest: true,
- },
- },
- })
- tests = append(tests, testCase{
- name: "DTLS13-HelloVerifyRequest",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ForceHelloVerifyRequest: true,
- },
- },
- shouldFail: true,
- expectedError: ":INVALID_MESSAGE:",
- })
- tests = append(tests, testCase{
- name: "DTLS13-HelloVerifyRequestEmptyCookie",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ForceHelloVerifyRequest: true,
- EmptyHelloVerifyRequestCookie: true,
- },
- },
- shouldFail: true,
- expectedError: ":INVALID_MESSAGE:",
- })
- }
-
- for _, test := range tests {
- test.protocol = config.protocol
- test.name += "-" + config.protocol.String()
- if config.async {
- test.name += "-Async"
- test.flags = append(test.flags, "-async")
- } else {
- test.name += "-Sync"
- }
- if config.splitHandshake {
- test.name += "-SplitHandshakeRecords"
- test.config.Bugs.MaxHandshakeRecordLength = 1
- if config.protocol == dtls {
- test.config.Bugs.MaxPacketLength = 256
- test.flags = append(test.flags, "-mtu", "256")
- }
- }
- if config.packHandshake {
- test.name += "-PackHandshake"
- if config.protocol == dtls {
- test.config.Bugs.MaxHandshakeRecordLength = 2
- test.config.Bugs.PackHandshakeFragments = 20
- test.config.Bugs.PackHandshakeRecords = 1500
- test.config.Bugs.PackAppDataWithHandshake = true
- } else {
- test.config.Bugs.PackHandshakeFlight = true
- }
- }
- if config.implicitHandshake {
- test.name += "-ImplicitHandshake"
- test.flags = append(test.flags, "-implicit-handshake")
- }
- testCases = append(testCases, test)
- }
-}
-
-func addDDoSCallbackTests() {
- // DDoS callback.
- for _, resume := range []bool{false, true} {
- suffix := "Resume"
- if resume {
- suffix = "No" + suffix
- }
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-DDoS-OK-" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- flags: []string{"-install-ddos-callback"},
- resumeSession: resume,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-DDoS-OK-" + suffix + "-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- flags: []string{"-install-ddos-callback"},
- resumeSession: resume,
- })
-
- failFlag := "-fail-ddos-callback"
- if resume {
- failFlag = "-on-resume-fail-ddos-callback"
- }
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-DDoS-Reject-" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- flags: []string{"-install-ddos-callback", failFlag},
- resumeSession: resume,
- shouldFail: true,
- expectedError: ":CONNECTION_REJECTED:",
- expectedLocalError: "remote error: internal error",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-DDoS-Reject-" + suffix + "-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- flags: []string{"-install-ddos-callback", failFlag},
- resumeSession: resume,
- shouldFail: true,
- expectedError: ":CONNECTION_REJECTED:",
- expectedLocalError: "remote error: internal error",
- })
- }
-}
-
-func addVersionNegotiationTests() {
- for _, protocol := range []protocol{tls, dtls, quic} {
- for _, shimVers := range allVersions(protocol) {
- // Assemble flags to disable all newer versions on the shim.
- var flags []string
- for _, vers := range allVersions(protocol) {
- if vers.version > shimVers.version {
- flags = append(flags, vers.excludeFlag)
- }
- }
-
- flags2 := []string{"-max-version", shimVers.shimFlag(protocol)}
-
- // Test configuring the runner's maximum version.
- for _, runnerVers := range allVersions(protocol) {
- expectedVersion := shimVers.version
- if runnerVers.version < shimVers.version {
- expectedVersion = runnerVers.version
- }
-
- suffix := shimVers.name + "-" + runnerVers.name
- suffix += "-" + protocol.String()
-
- // Determine the expected initial record-layer versions.
- clientVers := shimVers.version
- if clientVers > VersionTLS10 {
- clientVers = VersionTLS10
- }
- clientVers = recordVersionToWire(clientVers, protocol)
- serverVers := expectedVersion
- if expectedVersion >= VersionTLS13 {
- serverVers = VersionTLS12
- }
- serverVers = recordVersionToWire(serverVers, protocol)
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "VersionNegotiation-Client-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- Bugs: ProtocolBugs{
- ExpectInitialRecordVersion: clientVers,
- },
- },
- flags: flags,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- // The version name check does not recognize the
- // |excludeFlag| construction in |flags|.
- skipVersionNameCheck: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "VersionNegotiation-Client2-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- Bugs: ProtocolBugs{
- ExpectInitialRecordVersion: clientVers,
- },
- },
- flags: flags2,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "VersionNegotiation-Server-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- Bugs: ProtocolBugs{
- ExpectInitialRecordVersion: serverVers,
- },
- },
- flags: flags,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- // The version name check does not recognize the
- // |excludeFlag| construction in |flags|.
- skipVersionNameCheck: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "VersionNegotiation-Server2-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- Bugs: ProtocolBugs{
- ExpectInitialRecordVersion: serverVers,
- },
- },
- flags: flags2,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- })
- }
- }
- }
-
- // Test the version extension at all versions.
- for _, protocol := range []protocol{tls, dtls, quic} {
- for _, vers := range allVersions(protocol) {
- suffix := vers.name + "-" + protocol.String()
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "VersionNegotiationExtension-" + suffix,
- config: Config{
- Bugs: ProtocolBugs{
- SendSupportedVersions: []uint16{0x1111, vers.wire(protocol), 0x2222},
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- expectations: connectionExpectations{
- version: vers.version,
- },
- })
- }
- }
-
- // If all versions are unknown, negotiation fails.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoSupportedVersions",
- config: Config{
- Bugs: ProtocolBugs{
- SendSupportedVersions: []uint16{0x1111},
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "NoSupportedVersions-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendSupportedVersions: []uint16{0x1111},
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ClientHelloVersionTooHigh",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendClientVersion: 0x0304,
- OmitSupportedVersions: true,
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ConflictingVersionNegotiation",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: VersionTLS12,
- SendSupportedVersions: []uint16{VersionTLS11},
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- // The extension takes precedence over the ClientHello version.
- expectations: connectionExpectations{
- version: VersionTLS11,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ConflictingVersionNegotiation-2",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: VersionTLS11,
- SendSupportedVersions: []uint16{VersionTLS12},
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- // The extension takes precedence over the ClientHello version.
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
-
- // Test that TLS 1.2 isn't negotiated by the supported_versions extension in
- // the ServerHello.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "SupportedVersionSelection-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendServerSupportedVersionExtension: VersionTLS12,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- // Test that the maximum version is selected regardless of the
- // client-sent order.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "IgnoreClientVersionOrder",
- config: Config{
- Bugs: ProtocolBugs{
- SendSupportedVersions: []uint16{VersionTLS12, VersionTLS13},
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS13,
- },
- })
-
- // Test for version tolerance.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "MinorVersionTolerance",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: 0x03ff,
- OmitSupportedVersions: true,
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "MajorVersionTolerance",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: 0x0400,
- OmitSupportedVersions: true,
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- // TLS 1.3 must be negotiated with the supported_versions
- // extension, not ClientHello.version.
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "VersionTolerance-TLS13",
- config: Config{
- Bugs: ProtocolBugs{
- // Although TLS 1.3 does not use
- // ClientHello.version, it still tolerates high
- // values there.
- SendClientVersion: 0x0400,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS13,
- },
- })
-
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "MinorVersionTolerance-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: 0xfe00,
- OmitSupportedVersions: true,
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "MajorVersionTolerance-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: 0xfdff,
- OmitSupportedVersions: true,
- IgnoreTLS13DowngradeRandom: true,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
-
- // Test that versions below 3.0 are rejected.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "VersionTooLow",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: 0x0200,
- OmitSupportedVersions: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "VersionTooLow-DTLS",
- config: Config{
- Bugs: ProtocolBugs{
- SendClientVersion: 0xffff,
- OmitSupportedVersions: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- })
-
- testCases = append(testCases, testCase{
- name: "ServerBogusVersion",
- config: Config{
- Bugs: ProtocolBugs{
- SendServerHelloVersion: 0x1234,
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- })
-
- // Test TLS 1.3's downgrade signal.
- for _, protocol := range []protocol{tls, dtls} {
- for _, vers := range allVersions(protocol) {
- if vers.version >= VersionTLS13 {
- continue
- }
- clientShimError := "tls: downgrade from TLS 1.3 detected"
- if vers.version < VersionTLS12 {
- clientShimError = "tls: downgrade from TLS 1.2 detected"
- }
- // for _, test := range downgradeTests {
- // The client should enforce the downgrade sentinel.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "Downgrade-" + vers.name + "-Client-" + protocol.String(),
- config: Config{
- Bugs: ProtocolBugs{
- NegotiateVersion: vers.wire(protocol),
- },
- },
- expectations: connectionExpectations{
- version: vers.version,
- },
- shouldFail: true,
- expectedError: ":TLS13_DOWNGRADE:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- // The server should emit the downgrade signal.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "Downgrade-" + vers.name + "-Server-" + protocol.String(),
- config: Config{
- Bugs: ProtocolBugs{
- SendSupportedVersions: []uint16{vers.wire(protocol)},
- },
- },
- expectations: connectionExpectations{
- version: vers.version,
- },
- shouldFail: true,
- expectedLocalError: clientShimError,
- })
- }
- }
-
- // SSL 3.0 support has been removed. Test that the shim does not
- // support it.
- testCases = append(testCases, testCase{
- name: "NoSSL3-Client",
- config: Config{
- MinVersion: VersionSSL30,
- MaxVersion: VersionSSL30,
- },
- shouldFail: true,
- expectedLocalError: "tls: client did not offer any supported protocol versions",
- })
- testCases = append(testCases, testCase{
- name: "NoSSL3-Client-Unsolicited",
- config: Config{
- MinVersion: VersionSSL30,
- MaxVersion: VersionSSL30,
- Bugs: ProtocolBugs{
- // The above test asserts the client does not
- // offer SSL 3.0 in the supported_versions
- // list. Additionally assert that it rejects an
- // unsolicited SSL 3.0 ServerHello.
- NegotiateVersion: VersionSSL30,
- },
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- expectedLocalError: "remote error: protocol version not supported",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoSSL3-Server",
- config: Config{
- MinVersion: VersionSSL30,
- MaxVersion: VersionSSL30,
- },
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- expectedLocalError: "remote error: protocol version not supported",
- })
-}
-
-func addMinimumVersionTests() {
- for _, protocol := range []protocol{tls, dtls, quic} {
- for _, shimVers := range allVersions(protocol) {
- // Assemble flags to disable all older versions on the shim.
- var flags []string
- for _, vers := range allVersions(protocol) {
- if vers.version < shimVers.version {
- flags = append(flags, vers.excludeFlag)
- }
- }
-
- flags2 := []string{"-min-version", shimVers.shimFlag(protocol)}
-
- for _, runnerVers := range allVersions(protocol) {
- suffix := shimVers.name + "-" + runnerVers.name
- suffix += "-" + protocol.String()
-
- var expectedVersion uint16
- var shouldFail bool
- var expectedError, expectedLocalError string
- if runnerVers.version >= shimVers.version {
- expectedVersion = runnerVers.version
- } else {
- shouldFail = true
- expectedError = ":UNSUPPORTED_PROTOCOL:"
- expectedLocalError = "remote error: protocol version not supported"
- }
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "MinimumVersion-Client-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- Bugs: ProtocolBugs{
- // Ensure the server does not decline to
- // select a version (versions extension) or
- // cipher (some ciphers depend on versions).
- NegotiateVersion: runnerVers.wire(protocol),
- IgnorePeerCipherPreferences: shouldFail,
- },
- },
- flags: flags,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- shouldFail: shouldFail,
- expectedError: expectedError,
- expectedLocalError: expectedLocalError,
- // The version name check does not recognize the
- // |excludeFlag| construction in |flags|.
- skipVersionNameCheck: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "MinimumVersion-Client2-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- Bugs: ProtocolBugs{
- // Ensure the server does not decline to
- // select a version (versions extension) or
- // cipher (some ciphers depend on versions).
- NegotiateVersion: runnerVers.wire(protocol),
- IgnorePeerCipherPreferences: shouldFail,
- },
- },
- flags: flags2,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- shouldFail: shouldFail,
- expectedError: expectedError,
- expectedLocalError: expectedLocalError,
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "MinimumVersion-Server-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- },
- flags: flags,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- shouldFail: shouldFail,
- expectedError: expectedError,
- expectedLocalError: expectedLocalError,
- // The version name check does not recognize the
- // |excludeFlag| construction in |flags|.
- skipVersionNameCheck: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "MinimumVersion-Server2-" + suffix,
- config: Config{
- MaxVersion: runnerVers.version,
- },
- flags: flags2,
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- shouldFail: shouldFail,
- expectedError: expectedError,
- expectedLocalError: expectedLocalError,
- })
- }
- }
- }
-}
-
-func addExtensionTests() {
- exampleCertificate := 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{"example.com"},
- IsCA: true,
- BasicConstraintsValid: true,
- }, &ecdsaP256Key)
-
- // Repeat extensions tests at all versions.
- for _, protocol := range []protocol{tls, dtls, quic} {
- for _, ver := range allVersions(protocol) {
- suffix := fmt.Sprintf("%s-%s", protocol.String(), ver.name)
-
- // Test that duplicate extensions are rejected.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "DuplicateExtensionClient-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- DuplicateExtension: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "DuplicateExtensionServer-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- DuplicateExtension: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- })
-
- // Test SNI.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "ServerNameExtensionClient-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectServerName: "example.com",
- },
- Credential: &exampleCertificate,
- },
- flags: []string{"-host-name", "example.com"},
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "ServerNameExtensionClientMismatch-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectServerName: "mismatch.com",
- },
- },
- flags: []string{"-host-name", "example.com"},
- shouldFail: true,
- expectedLocalError: "tls: unexpected server name",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "ServerNameExtensionClientMissing-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectServerName: "missing.com",
- },
- },
- shouldFail: true,
- expectedLocalError: "tls: unexpected server name",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "TolerateServerNameAck-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendServerNameAck: true,
- },
- Credential: &exampleCertificate,
- },
- flags: []string{"-host-name", "example.com"},
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: "UnsolicitedServerNameAck-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendServerNameAck: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "ServerNameExtensionServer-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- ServerName: "example.com",
- },
- flags: []string{"-expect-server-name", "example.com"},
- resumeSession: true,
- })
-
- // Test ALPN.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- skipQUICALPNConfig: true,
- name: "ALPNClient-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo"},
- },
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar\x03baz",
- "-expect-alpn", "foo",
- },
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: alpn,
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- skipQUICALPNConfig: true,
- name: "ALPNClient-RejectUnknown-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendALPN: "baz",
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- },
- shouldFail: true,
- expectedError: ":INVALID_ALPN_PROTOCOL:",
- expectedLocalError: "remote error: illegal parameter",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- skipQUICALPNConfig: true,
- name: "ALPNClient-AllowUnknown-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendALPN: "baz",
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- "-allow-unknown-alpn-protos",
- "-expect-alpn", "baz",
- },
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- skipQUICALPNConfig: true,
- name: "ALPNServer-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- },
- flags: []string{
- "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
- "-select-alpn", "foo",
- },
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: alpn,
- },
- resumeSession: true,
- })
-
- var shouldDeclineALPNFail bool
- var declineALPNError, declineALPNLocalError string
- if protocol == quic {
- // ALPN is mandatory in QUIC.
- shouldDeclineALPNFail = true
- declineALPNError = ":NO_APPLICATION_PROTOCOL:"
- declineALPNLocalError = "remote error: no application protocol"
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- skipQUICALPNConfig: true,
- name: "ALPNServer-Decline-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- },
- flags: []string{"-decline-alpn"},
- expectations: connectionExpectations{
- noNextProto: true,
- },
- resumeSession: true,
- shouldFail: shouldDeclineALPNFail,
- expectedError: declineALPNError,
- expectedLocalError: declineALPNLocalError,
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- skipQUICALPNConfig: true,
- name: "ALPNServer-Reject-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- },
- flags: []string{"-reject-alpn"},
- shouldFail: true,
- expectedError: ":NO_APPLICATION_PROTOCOL:",
- expectedLocalError: "remote error: no application protocol",
- })
-
- // Test that the server implementation catches itself if the
- // callback tries to return an invalid empty ALPN protocol.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- skipQUICALPNConfig: true,
- name: "ALPNServer-SelectEmpty-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- },
- flags: []string{
- "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
- "-select-empty-alpn",
- },
- shouldFail: true,
- expectedLocalError: "remote error: internal error",
- expectedError: ":INVALID_ALPN_PROTOCOL:",
- })
-
- // Test ALPN in async mode as well to ensure that extensions callbacks are only
- // called once.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- skipQUICALPNConfig: true,
- name: "ALPNServer-Async-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- // Prior to TLS 1.3, exercise the asynchronous session callback.
- SessionTicketsDisabled: ver.version < VersionTLS13,
- },
- flags: []string{
- "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
- "-select-alpn", "foo",
- "-async",
- },
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: alpn,
- },
- resumeSession: true,
- })
-
- var emptyString string
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- skipQUICALPNConfig: true,
- name: "ALPNClient-EmptyProtocolName-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{""},
- Bugs: ProtocolBugs{
- // A server returning an empty ALPN protocol
- // should be rejected.
- ALPNProtocol: &emptyString,
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo",
- },
- shouldFail: true,
- expectedError: ":PARSE_TLSEXT:",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- skipQUICALPNConfig: true,
- name: "ALPNServer-EmptyProtocolName-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- // A ClientHello containing an empty ALPN protocol
- // should be rejected.
- NextProtos: []string{"foo", "", "baz"},
- },
- flags: []string{
- "-select-alpn", "foo",
- },
- shouldFail: true,
- expectedError: ":PARSE_TLSEXT:",
- })
-
- // Test NPN and the interaction with ALPN.
- if ver.version < VersionTLS13 && protocol == tls {
- // Test that the server prefers ALPN over NPN.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "ALPNServer-Preferred-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- },
- flags: []string{
- "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
- "-select-alpn", "foo",
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- },
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: alpn,
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "ALPNServer-Preferred-Swapped-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- Bugs: ProtocolBugs{
- SwapNPNAndALPN: true,
- },
- },
- flags: []string{
- "-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
- "-select-alpn", "foo",
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- },
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: alpn,
- },
- resumeSession: true,
- })
-
- // Test that negotiating both NPN and ALPN is forbidden.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "NegotiateALPNAndNPN-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- Bugs: ProtocolBugs{
- NegotiateALPNAndNPN: true,
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo",
- "-select-next-proto", "foo",
- },
- shouldFail: true,
- expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "NegotiateALPNAndNPN-Swapped-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo", "bar", "baz"},
- Bugs: ProtocolBugs{
- NegotiateALPNAndNPN: true,
- SwapNPNAndALPN: true,
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo",
- "-select-next-proto", "foo",
- },
- shouldFail: true,
- expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
- })
- }
-
- // Test missing ALPN in QUIC
- if protocol == quic {
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: "Client-ALPNMissingFromConfig-" + suffix,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- skipQUICALPNConfig: true,
- shouldFail: true,
- expectedError: ":NO_APPLICATION_PROTOCOL:",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: "Client-ALPNMissing-" + suffix,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- flags: []string{
- "-advertise-alpn", "\x03foo",
- },
- skipQUICALPNConfig: true,
- shouldFail: true,
- expectedError: ":NO_APPLICATION_PROTOCOL:",
- expectedLocalError: "remote error: no application protocol",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Server-ALPNMissing-" + suffix,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- skipQUICALPNConfig: true,
- shouldFail: true,
- expectedError: ":NO_APPLICATION_PROTOCOL:",
- expectedLocalError: "remote error: no application protocol",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Server-ALPNMismatch-" + suffix,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- NextProtos: []string{"foo"},
- },
- flags: []string{
- "-decline-alpn",
- },
- skipQUICALPNConfig: true,
- shouldFail: true,
- expectedError: ":NO_APPLICATION_PROTOCOL:",
- expectedLocalError: "remote error: no application protocol",
- })
- }
-
- // Test ALPS.
- if ver.version >= VersionTLS13 {
- // Test basic client with different ALPS codepoint.
- for _, alpsCodePoint := range []ALPSUseCodepoint{ALPSUseCodepointNew, ALPSUseCodepointOld} {
- flags := []string{}
- expectations := connectionExpectations{
- peerApplicationSettingsOld: []byte("shim1"),
- }
- resumeExpectations := &connectionExpectations{
- peerApplicationSettingsOld: []byte("shim2"),
- }
-
- if alpsCodePoint == ALPSUseCodepointNew {
- flags = append(flags, "-alps-use-new-codepoint")
- expectations = connectionExpectations{
- peerApplicationSettings: []byte("shim1"),
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettings: []byte("shim2"),
- }
- }
-
- flags = append(flags,
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-on-initial-application-settings", "proto,shim1",
- "-on-initial-expect-peer-application-settings", "runner1",
- "-on-resume-application-settings", "proto,shim2",
- "-on-resume-expect-peer-application-settings", "runner2")
-
- // Test that server can negotiate ALPS, including different values
- // on resumption.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-Basic-Client-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeConfig: &Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- expectations: expectations,
- resumeExpectations: resumeExpectations,
- flags: flags,
- })
-
- // Test basic server with different ALPS codepoint.
- flags = []string{}
- expectations = connectionExpectations{
- peerApplicationSettingsOld: []byte("shim1"),
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettingsOld: []byte("shim2"),
- }
-
- if alpsCodePoint == ALPSUseCodepointNew {
- flags = append(flags, "-alps-use-new-codepoint")
- expectations = connectionExpectations{
- peerApplicationSettings: []byte("shim1"),
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettings: []byte("shim2"),
- }
- }
-
- flags = append(flags,
- "-select-alpn", "proto",
- "-on-initial-application-settings", "proto,shim1",
- "-on-initial-expect-peer-application-settings", "runner1",
- "-on-resume-application-settings", "proto,shim2",
- "-on-resume-expect-peer-application-settings", "runner2")
-
- // Test that server can negotiate ALPS, including different values
- // on resumption.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-Basic-Server-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeConfig: &Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- expectations: expectations,
- resumeExpectations: resumeExpectations,
- flags: flags,
- })
-
- // Try different ALPS codepoint for all the existing tests.
- alpsFlags := []string{}
- expectations = connectionExpectations{
- peerApplicationSettingsOld: []byte("shim1"),
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettingsOld: []byte("shim2"),
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- alpsFlags = append(alpsFlags, "-alps-use-new-codepoint")
- expectations = connectionExpectations{
- peerApplicationSettings: []byte("shim1"),
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettings: []byte("shim2"),
- }
- }
-
- // Test that the server can defer its ALPS configuration to the ALPN
- // selection callback.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-Basic-Server-Defer-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeConfig: &Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- expectations: expectations,
- resumeExpectations: resumeExpectations,
- flags: append([]string{
- "-select-alpn", "proto",
- "-defer-alps",
- "-on-initial-application-settings", "proto,shim1",
- "-on-initial-expect-peer-application-settings", "runner1",
- "-on-resume-application-settings", "proto,shim2",
- "-on-resume-expect-peer-application-settings", "runner2",
- }, alpsFlags...),
- })
-
- expectations = connectionExpectations{
- peerApplicationSettingsOld: []byte{},
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- expectations = connectionExpectations{
- peerApplicationSettings: []byte{},
- }
- }
- // Test the client and server correctly handle empty settings.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-Empty-Client-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": {}},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- expectations: expectations,
- flags: append([]string{
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-application-settings", "proto,",
- "-expect-peer-application-settings", "",
- }, alpsFlags...),
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-Empty-Server-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": {}},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- expectations: expectations,
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto,",
- "-expect-peer-application-settings", "",
- }, alpsFlags...),
- })
-
- bugs := ProtocolBugs{
- AlwaysNegotiateApplicationSettingsOld: true,
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- bugs = ProtocolBugs{
- AlwaysNegotiateApplicationSettingsNew: true,
- }
- }
- // Test the client rejects application settings from the server on
- // protocols it doesn't have them.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-UnsupportedProtocol-Client-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto1"},
- ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
- Bugs: bugs,
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- // The client supports ALPS with "proto2", but not "proto1".
- flags: append([]string{
- "-advertise-alpn", "\x06proto1\x06proto2",
- "-application-settings", "proto2,shim",
- "-expect-alpn", "proto1",
- }, alpsFlags...),
- // The server sends ALPS with "proto1", which is invalid.
- shouldFail: true,
- expectedError: ":INVALID_ALPN_PROTOCOL:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- // Test client rejects application settings from the server when
- // server sends the wrong ALPS codepoint.
- bugs = ProtocolBugs{
- AlwaysNegotiateApplicationSettingsOld: true,
- }
- if alpsCodePoint == ALPSUseCodepointOld {
- bugs = ProtocolBugs{
- AlwaysNegotiateApplicationSettingsNew: true,
- }
- }
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-WrongServerCodepoint-Client-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": {}},
- Bugs: bugs,
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- flags: append([]string{
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-application-settings", "proto,",
- "-expect-peer-application-settings", "",
- }, alpsFlags...),
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-
- // Test server ignore wrong codepoint from client.
- clientSends := ALPSUseCodepointNew
- if alpsCodePoint == ALPSUseCodepointNew {
- clientSends = ALPSUseCodepointOld
- }
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-IgnoreClientWrongCodepoint-Server-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
- ALPSUseNewCodepoint: clientSends,
- },
- resumeConfig: &Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
- ALPSUseNewCodepoint: clientSends,
- },
- resumeSession: true,
- flags: append([]string{
- "-select-alpn", "proto",
- "-on-initial-application-settings", "proto,shim1",
- "-on-resume-application-settings", "proto,shim2",
- }, alpsFlags...),
- })
-
- // Test the server declines ALPS if it doesn't support it for the
- // specified protocol.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-UnsupportedProtocol-Server-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto1"},
- ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- // The server supports ALPS with "proto2", but not "proto1".
- flags: append([]string{
- "-select-alpn", "proto1",
- "-application-settings", "proto2,shim",
- }, alpsFlags...),
- })
-
- // Test the client rejects application settings from the server when
- // it always negotiate both codepoint.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-UnsupportedProtocol-Client-ServerBoth-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto1"},
- ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
- Bugs: ProtocolBugs{
- AlwaysNegotiateApplicationSettingsBoth: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- flags: append([]string{
- "-advertise-alpn", "\x06proto1\x06proto2",
- "-application-settings", "proto1,shim",
- "-expect-alpn", "proto1",
- }, alpsFlags...),
- // The server sends ALPS with both application settings, which is invalid.
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-
- expectations = connectionExpectations{
- peerApplicationSettingsOld: []byte("shim"),
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- expectations = connectionExpectations{
- peerApplicationSettings: []byte("shim"),
- }
- }
-
- // Test that the server rejects a missing application_settings extension.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-OmitClientApplicationSettings-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
- Bugs: ProtocolBugs{
- OmitClientApplicationSettings: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto,shim",
- }, alpsFlags...),
- // The runner is a client, so it only processes the shim's alert
- // after checking connection state.
- expectations: expectations,
- shouldFail: true,
- expectedError: ":MISSING_EXTENSION:",
- expectedLocalError: "remote error: missing extension",
- })
-
- // Test that the server rejects a missing EncryptedExtensions message.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-OmitClientEncryptedExtensions-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
- Bugs: ProtocolBugs{
- OmitClientEncryptedExtensions: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto,shim",
- }, alpsFlags...),
- // The runner is a client, so it only processes the shim's alert
- // after checking connection state.
- expectations: expectations,
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Test that the server rejects an unexpected EncryptedExtensions message.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("UnexpectedClientEncryptedExtensions-%s-%s", alpsCodePoint, suffix),
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- AlwaysSendClientEncryptedExtensions: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Test that the server rejects an unexpected extension in an
- // expected EncryptedExtensions message.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ExtraClientEncryptedExtension-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
- Bugs: ProtocolBugs{
- SendExtraClientEncryptedExtension: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto,shim",
- }, alpsFlags...),
- // The runner is a client, so it only processes the shim's alert
- // after checking connection state.
- expectations: expectations,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-
- // Test that ALPS is carried over on 0-RTT.
- // TODO(crbug.com/381113363): Support 0-RTT in DTLS 1.3.
- if protocol != dtls {
- for _, empty := range []bool{false, true} {
- maybeEmpty := ""
- runnerSettings := "runner"
- shimSettings := "shim"
- if empty {
- maybeEmpty = "Empty-"
- runnerSettings = ""
- shimSettings = ""
- }
-
- expectations = connectionExpectations{
- peerApplicationSettingsOld: []byte(shimSettings),
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- expectations = connectionExpectations{
- peerApplicationSettings: []byte(shimSettings),
- }
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-EarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-application-settings", "proto," + shimSettings,
- "-expect-peer-application-settings", runnerSettings,
- }, alpsFlags...),
- expectations: expectations,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-EarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto," + shimSettings,
- "-expect-peer-application-settings", runnerSettings,
- }, alpsFlags...),
- expectations: expectations,
- })
-
- // Sending application settings in 0-RTT handshakes is forbidden.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
- Bugs: ProtocolBugs{
- SendApplicationSettingsWithEarlyData: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-application-settings", "proto," + shimSettings,
- "-expect-peer-application-settings", runnerSettings,
- }, alpsFlags...),
- expectations: expectations,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
- expectedLocalError: "remote error: illegal parameter",
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
- Bugs: ProtocolBugs{
- SendApplicationSettingsWithEarlyData: true,
- },
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto," + shimSettings,
- "-expect-peer-application-settings", runnerSettings,
- }, alpsFlags...),
- expectations: expectations,
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
- }
-
- // Test that the client and server each decline early data if local
- // ALPS preferences has changed for the current connection.
- alpsMismatchTests := []struct {
- name string
- initialSettings, resumeSettings []byte
- }{
- {"DifferentValues", []byte("settings1"), []byte("settings2")},
- {"OnOff", []byte("settings"), nil},
- {"OffOn", nil, []byte("settings")},
- // The empty settings value should not be mistaken for ALPS not
- // being negotiated.
- {"OnEmpty", []byte("settings"), []byte{}},
- {"EmptyOn", []byte{}, []byte("settings")},
- {"EmptyOff", []byte{}, nil},
- {"OffEmpty", nil, []byte{}},
- }
- for _, test := range alpsMismatchTests {
- flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
- flags = append(flags, alpsFlags...)
- if test.initialSettings != nil {
- flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
- flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
- }
- if test.resumeSettings != nil {
- flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
- flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
- }
-
- expectations = connectionExpectations{
- peerApplicationSettingsOld: test.initialSettings,
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettingsOld: test.resumeSettings,
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- expectations = connectionExpectations{
- peerApplicationSettings: test.initialSettings,
- }
- resumeExpectations = &connectionExpectations{
- peerApplicationSettings: test.resumeSettings,
- }
- }
- // The client should not offer early data if the session is
- // inconsistent with the new configuration. Note that if
- // the session did not negotiate ALPS (test.initialSettings
- // is nil), the client always offers early data.
- if test.initialSettings != nil {
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s-%s", test.name, alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- MaxEarlyDataSize: 16384,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- flags: append([]string{
- "-enable-early-data",
- "-expect-ticket-supports-early-data",
- "-expect-no-offer-early-data",
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- }, flags...),
- expectations: expectations,
- resumeExpectations: resumeExpectations,
- })
- }
-
- // The server should reject early data if the session is
- // inconsistent with the new selection.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s-%s", test.name, alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: append([]string{
- "-select-alpn", "proto",
- }, flags...),
- expectations: expectations,
- resumeExpectations: resumeExpectations,
- })
- }
-
- // Test that 0-RTT continues working when the shim configures
- // ALPS but the peer does not.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-EarlyData-Client-ServerDecline-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-application-settings", "proto,shim",
- }, alpsFlags...),
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-EarlyData-Server-ClientNoOffe-%s-%s", alpsCodePoint, suffix),
- skipQUICALPNConfig: true,
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"proto"},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- earlyData: true,
- flags: append([]string{
- "-select-alpn", "proto",
- "-application-settings", "proto,shim",
- }, alpsFlags...),
- })
- }
- }
- } else {
- // Test the client rejects the ALPS extension if the server
- // negotiated TLS 1.2 or below.
- for _, alpsCodePoint := range []ALPSUseCodepoint{ALPSUseCodepointNew, ALPSUseCodepointOld} {
- flags := []string{
- "-advertise-alpn", "\x03foo",
- "-expect-alpn", "foo",
- "-application-settings", "foo,shim",
- }
- bugs := ProtocolBugs{
- AlwaysNegotiateApplicationSettingsOld: true,
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- flags = append(flags, "-alps-use-new-codepoint")
- bugs = ProtocolBugs{
- AlwaysNegotiateApplicationSettingsNew: true,
- }
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-Reject-Client-%s-%s", alpsCodePoint, suffix),
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo"},
- ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
- Bugs: bugs,
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- flags: flags,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-
- flags = []string{
- "-on-resume-advertise-alpn", "\x03foo",
- "-on-resume-expect-alpn", "foo",
- "-on-resume-application-settings", "foo,shim",
- }
- bugs = ProtocolBugs{
- AlwaysNegotiateApplicationSettingsOld: true,
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- flags = append(flags, "-alps-use-new-codepoint")
- bugs = ProtocolBugs{
- AlwaysNegotiateApplicationSettingsNew: true,
- }
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("ALPS-Reject-Client-Resume-%s-%s", alpsCodePoint, suffix),
- config: Config{
- MaxVersion: ver.version,
- },
- resumeConfig: &Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo"},
- ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
- Bugs: bugs,
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- resumeSession: true,
- flags: flags,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-
- // Test the server declines ALPS if it negotiates TLS 1.2 or below.
- flags = []string{
- "-select-alpn", "foo",
- "-application-settings", "foo,shim",
- }
- if alpsCodePoint == ALPSUseCodepointNew {
- flags = append(flags, "-alps-use-new-codepoint")
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("ALPS-Decline-Server-%s-%s", alpsCodePoint, suffix),
- config: Config{
- MaxVersion: ver.version,
- NextProtos: []string{"foo"},
- ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
- ALPSUseNewCodepoint: alpsCodePoint,
- },
- // Test both TLS 1.2 full and resumption handshakes.
- resumeSession: true,
- flags: flags,
- // If not specified, runner and shim both implicitly expect ALPS
- // is not negotiated.
- })
- }
- }
-
- // Test QUIC transport params
- if protocol == quic {
- // Client sends params
- for _, clientConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
- for _, serverSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} {
- useCodepointFlag := "0"
- if clientConfig == QUICUseCodepointLegacy {
- useCodepointFlag = "1"
- }
- flags := []string{
- "-quic-transport-params",
- base64FlagValue([]byte{1, 2}),
- "-quic-use-legacy-codepoint", useCodepointFlag,
- }
- expectations := connectionExpectations{
- quicTransportParams: []byte{1, 2},
- }
- shouldFail := false
- expectedError := ""
- expectedLocalError := ""
- if clientConfig == QUICUseCodepointLegacy {
- expectations = connectionExpectations{
- quicTransportParamsLegacy: []byte{1, 2},
- }
- }
- if serverSends != clientConfig {
- expectations = connectionExpectations{}
- shouldFail = true
- if serverSends == QUICUseCodepointNeither {
- expectedError = ":MISSING_EXTENSION:"
- } else {
- expectedLocalError = "remote error: unsupported extension"
- }
- } else {
- flags = append(flags,
- "-expect-quic-transport-params",
- base64FlagValue([]byte{3, 4}))
- }
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: fmt.Sprintf("QUICTransportParams-Client-Client%s-Server%s-%s", clientConfig, serverSends, suffix),
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- QUICTransportParams: []byte{3, 4},
- QUICTransportParamsUseLegacyCodepoint: serverSends,
- },
- flags: flags,
- expectations: expectations,
- shouldFail: shouldFail,
- expectedError: expectedError,
- expectedLocalError: expectedLocalError,
- skipTransportParamsConfig: true,
- })
- }
- }
- // Server sends params
- for _, clientSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} {
- for _, serverConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
- expectations := connectionExpectations{
- quicTransportParams: []byte{3, 4},
- }
- shouldFail := false
- expectedError := ""
- useCodepointFlag := "0"
- if serverConfig == QUICUseCodepointLegacy {
- useCodepointFlag = "1"
- expectations = connectionExpectations{
- quicTransportParamsLegacy: []byte{3, 4},
- }
- }
- flags := []string{
- "-quic-transport-params",
- base64FlagValue([]byte{3, 4}),
- "-quic-use-legacy-codepoint", useCodepointFlag,
- }
- if clientSends != QUICUseCodepointBoth && clientSends != serverConfig {
- expectations = connectionExpectations{}
- shouldFail = true
- expectedError = ":MISSING_EXTENSION:"
- } else {
- flags = append(flags,
- "-expect-quic-transport-params",
- base64FlagValue([]byte{1, 2}),
- )
- }
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: fmt.Sprintf("QUICTransportParams-Server-Client%s-Server%s-%s", clientSends, serverConfig, suffix),
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- QUICTransportParams: []byte{1, 2},
- QUICTransportParamsUseLegacyCodepoint: clientSends,
- },
- flags: flags,
- expectations: expectations,
- shouldFail: shouldFail,
- expectedError: expectedError,
- skipTransportParamsConfig: true,
- })
- }
- }
- } else {
- // Ensure non-QUIC client doesn't send QUIC transport parameters.
- for _, clientConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
- useCodepointFlag := "0"
- if clientConfig == QUICUseCodepointLegacy {
- useCodepointFlag = "1"
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: clientTest,
- name: fmt.Sprintf("QUICTransportParams-Client-NotSentInNonQUIC-%s-%s", clientConfig, suffix),
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- QUICTransportParamsUseLegacyCodepoint: clientConfig,
- },
- flags: []string{
- "-max-version",
- ver.shimFlag(protocol),
- "-quic-transport-params",
- base64FlagValue([]byte{3, 4}),
- "-quic-use-legacy-codepoint", useCodepointFlag,
- },
- shouldFail: true,
- expectedError: ":QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED:",
- skipTransportParamsConfig: true,
- })
- }
- // Ensure non-QUIC server rejects codepoint 57 but ignores legacy 0xffa5.
- for _, clientSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} {
- for _, serverConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} {
- shouldFail := false
- expectedLocalError := ""
- useCodepointFlag := "0"
- if serverConfig == QUICUseCodepointLegacy {
- useCodepointFlag = "1"
- }
- if clientSends == QUICUseCodepointStandard || clientSends == QUICUseCodepointBoth {
- shouldFail = true
- expectedLocalError = "remote error: unsupported extension"
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: fmt.Sprintf("QUICTransportParams-NonQUICServer-Client%s-Server%s-%s", clientSends, serverConfig, suffix),
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- QUICTransportParams: []byte{1, 2},
- QUICTransportParamsUseLegacyCodepoint: clientSends,
- },
- flags: []string{
- "-quic-use-legacy-codepoint", useCodepointFlag,
- },
- shouldFail: shouldFail,
- expectedLocalError: expectedLocalError,
- skipTransportParamsConfig: true,
- })
- }
- }
-
- }
-
- // Test ticket behavior.
-
- // Resume with a corrupt ticket.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "CorruptTicket-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- FilterTicket: func(in []byte) ([]byte, error) {
- in[len(in)-1] ^= 1
- return in, nil
- },
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
- // Test the ticket callbacks.
- for _, aeadCallback := range []bool{false, true} {
- flag := "-use-ticket-callback"
- callbackSuffix := suffix
- if aeadCallback {
- flag = "-use-ticket-aead-callback"
- callbackSuffix += "-AEAD"
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketCallback-" + callbackSuffix,
- config: Config{
- MaxVersion: ver.version,
- },
- resumeSession: true,
- flags: []string{flag},
- })
- // Only the old callback supports renewal.
- if !aeadCallback {
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketCallback-Renew-" + callbackSuffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectNewTicket: true,
- },
- },
- flags: []string{flag, "-renew-ticket"},
- resumeSession: true,
- })
- }
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketCallback-Skip-" + callbackSuffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectNoNonEmptyNewSessionTicket: true,
- },
- },
- flags: []string{flag, "-skip-ticket"},
- })
-
- // Test that the ticket callback is only called once when everything before
- // it in the ClientHello is asynchronous. This corrupts the ticket so
- // certificate selection callbacks run.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketCallback-SingleCall-" + callbackSuffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- FilterTicket: func(in []byte) ([]byte, error) {
- in[len(in)-1] ^= 1
- return in, nil
- },
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- flags: []string{
- flag,
- "-async",
- },
- })
- }
-
- // Resume with various lengths of ticket session id.
- if ver.version < VersionTLS13 {
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketSessionIDLength-0-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- EmptyTicketSessionID: true,
- },
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketSessionIDLength-16-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- TicketSessionIDLength: 16,
- },
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketSessionIDLength-32-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- TicketSessionIDLength: 32,
- },
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "TicketSessionIDLength-33-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- TicketSessionIDLength: 33,
- },
- },
- resumeSession: true,
- shouldFail: true,
- // The maximum session ID length is 32.
- expectedError: ":CLIENTHELLO_PARSE_FAILED:",
- })
- }
-
- // Basic DTLS-SRTP tests. Include fake profiles to ensure they
- // are ignored.
- if protocol == dtls {
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SRTP-Client-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
- },
- expectations: connectionExpectations{
- srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
- },
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "SRTP-Server-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
- },
- expectations: connectionExpectations{
- srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
- },
- })
- // Test that the MKI is ignored.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "SRTP-Server-IgnoreMKI-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80},
- Bugs: ProtocolBugs{
- SRTPMasterKeyIdentifier: "bogus",
- },
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
- },
- expectations: connectionExpectations{
- srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
- },
- })
- // Test that SRTP isn't negotiated on the server if there were
- // no matching profiles.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "SRTP-Server-NoMatch-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- SRTPProtectionProfiles: []uint16{100, 101, 102},
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
- },
- expectations: connectionExpectations{
- srtpProtectionProfile: 0,
- },
- })
- // Test that the server returning an invalid SRTP profile is
- // flagged as an error by the client.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SRTP-Client-NoMatch-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32,
- },
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80",
- },
- shouldFail: true,
- expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:",
- })
- } else {
- // DTLS-SRTP is not defined for other protocols. Configuring it
- // on the client and server should ignore the extension.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SRTP-Client-Ignore-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
- },
- expectations: connectionExpectations{
- srtpProtectionProfile: 0,
- },
- })
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "SRTP-Server-Ignore-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
- },
- flags: []string{
- "-srtp-profiles",
- "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
- },
- expectations: connectionExpectations{
- srtpProtectionProfile: 0,
- },
- })
- }
-
- // Test SCT list.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SignedCertificateTimestampList-Client-" + suffix,
- testType: clientTest,
- config: Config{
- MaxVersion: ver.version,
- Credential: rsaCertificate.WithSCTList(testSCTList),
- },
- flags: []string{
- "-enable-signed-cert-timestamps",
- "-expect-signed-cert-timestamps",
- base64FlagValue(testSCTList),
- },
- resumeSession: true,
- })
-
- var differentSCTList []byte
- differentSCTList = append(differentSCTList, testSCTList...)
- differentSCTList[len(differentSCTList)-1] ^= 1
-
- // The SCT extension did not specify that it must only be sent on the inital handshake as it
- // should have, so test that we tolerate but ignore it. This is only an issue pre-1.3, since
- // SCTs are sent in the CertificateEntry message in 1.3, whereas they were previously sent
- // in an extension in the ServerHello pre-1.3.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SendSCTListOnResume-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Credential: rsaCertificate.WithSCTList(testSCTList),
- Bugs: ProtocolBugs{
- SendSCTListOnResume: differentSCTList,
- },
- },
- flags: []string{
- "-enable-signed-cert-timestamps",
- "-expect-signed-cert-timestamps",
- base64FlagValue(testSCTList),
- },
- resumeSession: true,
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SignedCertificateTimestampList-Server-" + suffix,
- testType: serverTest,
- config: Config{
- MaxVersion: ver.version,
- },
- shimCertificate: rsaCertificate.WithSCTList(testSCTList),
- expectations: connectionExpectations{
- peerCertificate: rsaCertificate.WithSCTList(testSCTList),
- },
- resumeSession: true,
- })
-
- // Test empty SCT list.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SignedCertificateTimestampListEmpty-Client-" + suffix,
- testType: clientTest,
- config: Config{
- MaxVersion: ver.version,
- Credential: rsaCertificate.WithSCTList([]byte{0, 0}),
- },
- flags: []string{
- "-enable-signed-cert-timestamps",
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- // Test empty SCT in non-empty list.
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "SignedCertificateTimestampListEmptySCT-Client-" + suffix,
- testType: clientTest,
- config: Config{
- MaxVersion: ver.version,
- Credential: rsaCertificate.WithSCTList([]byte{0, 6, 0, 2, 1, 2, 0, 0}),
- },
- flags: []string{
- "-enable-signed-cert-timestamps",
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- // Test that certificate-related extensions are not sent unsolicited.
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "UnsolicitedCertificateExtensions-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- NoOCSPStapling: true,
- NoSignedCertificateTimestamps: true,
- },
- },
- shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- })
-
- // Extension permutation should interact correctly with other extensions,
- // HelloVerifyRequest, HelloRetryRequest, and ECH. SSLTest.PermuteExtensions
- // in ssl_test.cc tests that the extensions are actually permuted. This
- // tests the handshake still works.
- //
- // This test also tests that all our extensions interact with each other.
- for _, ech := range []bool{false, true} {
- if ech && ver.version < VersionTLS13 {
- continue
- }
-
- test := testCase{
- protocol: protocol,
- name: "AllExtensions-Client-Permute",
- skipQUICALPNConfig: true,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- NextProtos: []string{"proto"},
- ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
- Bugs: ProtocolBugs{
- SendServerNameAck: true,
- ExpectServerName: "example.com",
- ExpectGREASE: true,
- },
- },
- resumeSession: true,
- flags: []string{
- "-permute-extensions",
- "-enable-grease",
- "-enable-ocsp-stapling",
- "-enable-signed-cert-timestamps",
- "-advertise-alpn", "\x05proto",
- "-expect-alpn", "proto",
- "-host-name", "example.com",
- },
- }
-
- if ech {
- test.name += "-ECH"
- echConfig := generateServerECHConfig(&ECHConfig{ConfigID: 42})
- test.config.ServerECHConfigs = []ServerECHConfig{echConfig}
- test.flags = append(test.flags,
- "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
- "-expect-ech-accept",
- )
- test.expectations.echAccepted = true
- }
-
- if ver.version >= VersionTLS13 {
- // Trigger a HelloRetryRequest to test both ClientHellos. Note
- // our DTLS tests always enable HelloVerifyRequest.
- test.name += "-HelloRetryRequest"
-
- // ALPS is only available on TLS 1.3.
- test.config.ApplicationSettings = map[string][]byte{"proto": []byte("runner")}
- test.flags = append(test.flags,
- "-application-settings", "proto,shim",
- "-alps-use-new-codepoint",
- "-expect-peer-application-settings", "runner")
- test.expectations.peerApplicationSettings = []byte("shim")
- }
-
- if protocol == dtls {
- test.config.SRTPProtectionProfiles = []uint16{SRTP_AES128_CM_HMAC_SHA1_80}
- test.flags = append(test.flags, "-srtp-profiles", "SRTP_AES128_CM_SHA1_80")
- test.expectations.srtpProtectionProfile = SRTP_AES128_CM_HMAC_SHA1_80
- }
-
- test.name += "-" + suffix
- testCases = append(testCases, test)
- }
- }
- }
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ClientHelloPadding",
- config: Config{
- Bugs: ProtocolBugs{
- RequireClientHelloSize: 512,
- },
- },
- // This hostname just needs to be long enough to push the
- // ClientHello into F5's danger zone between 256 and 511 bytes
- // long.
- flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"},
- })
-
- // Test that illegal extensions in TLS 1.3 are rejected by the client if
- // in ServerHello.
- testCases = append(testCases, testCase{
- name: "NPN-Forbidden-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- NegotiateNPNAtAllVersions: true,
- },
- },
- flags: []string{"-select-next-proto", "foo"},
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- name: "EMS-Forbidden-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- NegotiateEMSAtAllVersions: true,
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- name: "RenegotiationInfo-Forbidden-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- NegotiateRenegotiationInfoAtAllVersions: true,
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- name: "Ticket-Forbidden-TLS13",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AdvertiseTicketExtension: true,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- // Test that illegal extensions in TLS 1.3 are declined by the server if
- // offered in ClientHello. The runner's server will fail if this occurs,
- // so we exercise the offering path. (EMS and Renegotiation Info are
- // implicit in every test.)
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NPN-Declined-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"bar"},
- },
- flags: []string{"-advertise-npn", "\x03foo\x03bar\x03baz"},
- })
-
- // OpenSSL sends the status_request extension on resumption in TLS 1.2. Test that this is
- // tolerated.
- testCases = append(testCases, testCase{
- name: "SendOCSPResponseOnResume-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- Bugs: ProtocolBugs{
- SendOCSPResponseOnResume: []byte("bogus"),
- },
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-expect-ocsp-response",
- base64FlagValue(testOCSPResponse),
- },
- resumeSession: true,
- })
-
- testCases = append(testCases, testCase{
- name: "SendUnsolicitedOCSPOnCertificate-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtensionOnCertificate: testOCSPExtension,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- testCases = append(testCases, testCase{
- name: "SendUnsolicitedSCTOnCertificate-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtensionOnCertificate: testSCTExtension,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- // Test that extensions on client certificates are never accepted.
- testCases = append(testCases, testCase{
- name: "SendExtensionOnClientCertificate-TLS13",
- testType: serverTest,
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- SendExtensionOnCertificate: testOCSPExtension,
- },
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- testCases = append(testCases, testCase{
- name: "SendUnknownExtensionOnCertificate-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtensionOnCertificate: []byte{0x00, 0x7f, 0, 0},
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- // Test that extensions on intermediates are allowed but ignored.
- testCases = append(testCases, testCase{
- name: "IgnoreExtensionsOnIntermediates-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- Bugs: ProtocolBugs{
- // Send different values on the intermediate. This tests
- // the intermediate's extensions do not override the
- // leaf's.
- SendOCSPOnIntermediates: testOCSPResponse2,
- SendSCTOnIntermediates: testSCTList2,
- },
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-expect-ocsp-response",
- base64FlagValue(testOCSPResponse),
- "-enable-signed-cert-timestamps",
- "-expect-signed-cert-timestamps",
- base64FlagValue(testSCTList),
- },
- resumeSession: true,
- })
-
- // Test that extensions are not sent on intermediates when configured
- // only for a leaf.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SendNoExtensionsOnIntermediate-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectNoExtensionsOnIntermediate: true,
- },
- },
- shimCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- })
-
- // Test that extensions are not sent on client certificates.
- testCases = append(testCases, testCase{
- name: "SendNoClientCertificateExtensions-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- })
-
- testCases = append(testCases, testCase{
- name: "SendDuplicateExtensionsOnCerts-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- Bugs: ProtocolBugs{
- SendDuplicateCertExtensions: true,
- },
- },
- flags: []string{
- "-enable-ocsp-stapling",
- "-enable-signed-cert-timestamps",
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":DUPLICATE_EXTENSION:",
- })
-
- testCases = append(testCases, testCase{
- name: "SignedCertificateTimestampListInvalid-Server",
- testType: serverTest,
- shimCertificate: rsaCertificate.WithSCTList([]byte{0, 0}),
- shouldFail: true,
- expectedError: ":INVALID_SCT_LIST:",
- })
-}
-
-func addResumptionVersionTests() {
- for _, sessionVers := range tlsVersions {
- for _, resumeVers := range tlsVersions {
- protocols := []protocol{tls}
- if sessionVers.hasDTLS && resumeVers.hasDTLS {
- protocols = append(protocols, dtls)
- }
- if sessionVers.hasQUIC && resumeVers.hasQUIC {
- protocols = append(protocols, quic)
- }
- for _, protocol := range protocols {
- suffix := "-" + sessionVers.name + "-" + resumeVers.name
- suffix += "-" + protocol.String()
-
- if sessionVers.version == resumeVers.version {
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "Resume-Client" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: sessionVers.version,
- Bugs: ProtocolBugs{
- ExpectNoTLS13PSK: sessionVers.version < VersionTLS13,
- },
- },
- expectations: connectionExpectations{
- version: sessionVers.version,
- },
- resumeExpectations: &connectionExpectations{
- version: resumeVers.version,
- },
- })
- } else if protocol != tls && sessionVers.version >= VersionTLS13 && resumeVers.version < VersionTLS13 {
- // In TLS 1.2 and below, the server indicates resumption by echoing
- // the client's session ID, which is impossible if the client did
- // not send a session ID. If the client offers a TLS 1.3 session, it
- // only fills in session ID in TLS (not DTLS or QUIC) for middlebox
- // compatibility mode. So, instead, test that the session ID was
- // empty and it was indeed impossible to hit this path
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "Resume-Client-Impossible" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: sessionVers.version,
- },
- expectations: connectionExpectations{
- version: sessionVers.version,
- },
- resumeConfig: &Config{
- MaxVersion: resumeVers.version,
- Bugs: ProtocolBugs{
- ExpectNoSessionID: true,
- },
- },
- resumeExpectations: &connectionExpectations{
- version: resumeVers.version,
- },
- expectResumeRejected: true,
- })
- } else {
- // Test that the client rejects ServerHellos which resume
- // sessions at inconsistent versions.
- expectedError := ":OLD_SESSION_VERSION_NOT_RETURNED:"
- if sessionVers.version < VersionTLS13 && resumeVers.version >= VersionTLS13 {
- // The server will "resume" the session by sending pre_shared_key,
- // but the shim will not have sent pre_shared_key at all. The shim
- // should reject this because the extension was not allowed at all.
- expectedError = ":UNEXPECTED_EXTENSION:"
- }
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "Resume-Client-Mismatch" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: sessionVers.version,
- },
- expectations: connectionExpectations{
- version: sessionVers.version,
- },
- resumeConfig: &Config{
- MaxVersion: resumeVers.version,
- Bugs: ProtocolBugs{
- AcceptAnySession: true,
- },
- },
- resumeExpectations: &connectionExpectations{
- version: resumeVers.version,
- },
- shouldFail: true,
- expectedError: expectedError,
- })
- }
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "Resume-Client-NoResume" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: sessionVers.version,
- },
- expectations: connectionExpectations{
- version: sessionVers.version,
- },
- resumeConfig: &Config{
- MaxVersion: resumeVers.version,
- },
- newSessionsOnResume: true,
- expectResumeRejected: true,
- resumeExpectations: &connectionExpectations{
- version: resumeVers.version,
- },
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "Resume-Server" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: sessionVers.version,
- },
- expectations: connectionExpectations{
- version: sessionVers.version,
- },
- expectResumeRejected: sessionVers != resumeVers,
- resumeConfig: &Config{
- MaxVersion: resumeVers.version,
- Bugs: ProtocolBugs{
- SendBothTickets: true,
- },
- },
- resumeExpectations: &connectionExpectations{
- version: resumeVers.version,
- },
- })
-
- // Repeat the test using session IDs, rather than tickets.
- if sessionVers.version < VersionTLS13 && resumeVers.version < VersionTLS13 {
- testCases = append(testCases, testCase{
- protocol: protocol,
- testType: serverTest,
- name: "Resume-Server-NoTickets" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: sessionVers.version,
- SessionTicketsDisabled: true,
- },
- expectations: connectionExpectations{
- version: sessionVers.version,
- },
- expectResumeRejected: sessionVers != resumeVers,
- resumeConfig: &Config{
- MaxVersion: resumeVers.version,
- SessionTicketsDisabled: true,
- },
- resumeExpectations: &connectionExpectations{
- version: resumeVers.version,
- },
- })
- }
- }
- }
- }
-
- // Make sure shim ticket mutations are functional.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ShimTicketRewritable",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- FilterTicket: func(in []byte) ([]byte, error) {
- in, err := SetShimTicketVersion(in, VersionTLS12)
- if err != nil {
- return nil, err
- }
- return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- })
-
- // Resumptions are declined if the version does not match.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-DeclineCrossVersion",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectNewTicket: true,
- FilterTicket: func(in []byte) ([]byte, error) {
- return SetShimTicketVersion(in, VersionTLS13)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- expectResumeRejected: true,
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-DeclineCrossVersion-TLS13",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- FilterTicket: func(in []byte) ([]byte, error) {
- return SetShimTicketVersion(in, VersionTLS12)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- expectResumeRejected: true,
- })
-
- // Resumptions are declined if the cipher is invalid or disabled.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-DeclineBadCipher",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectNewTicket: true,
- FilterTicket: func(in []byte) ([]byte, error) {
- return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- expectResumeRejected: true,
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-DeclineBadCipher-2",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectNewTicket: true,
- FilterTicket: func(in []byte) ([]byte, error) {
- return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
- },
- },
- },
- flags: []string{
- "-cipher", "AES128",
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- expectResumeRejected: true,
- })
-
- // Sessions are not resumed if they do not use the preferred cipher.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-CipherNotPreferred",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectNewTicket: true,
- FilterTicket: func(in []byte) ([]byte, error) {
- return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- shouldFail: false,
- expectResumeRejected: true,
- })
-
- // TLS 1.3 allows sessions to be resumed at a different cipher if their
- // PRF hashes match, but BoringSSL will always decline such resumptions.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-CipherNotPreferred-TLS13",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- FilterTicket: func(in []byte) ([]byte, error) {
- // If the client (runner) offers ChaCha20-Poly1305 first, the
- // server (shim) always prefers it. Switch it to AES-GCM.
- return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- shouldFail: false,
- expectResumeRejected: true,
- })
-
- // Sessions may not be resumed if they contain another version's cipher.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-DeclineBadCipher-TLS13",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- FilterTicket: func(in []byte) ([]byte, error) {
- return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
- },
- },
- },
- flags: []string{
- "-ticket-key",
- base64FlagValue(TestShimTicketKey),
- },
- expectResumeRejected: true,
- })
-
- // If the client does not offer the cipher from the session, decline to
- // resume. Clients are forbidden from doing this, but BoringSSL selects
- // the cipher first, so we only decline.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-UnofferedCipher",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
- Bugs: ProtocolBugs{
- SendCipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- },
- expectResumeRejected: true,
- })
-
- // In TLS 1.3, clients may advertise a cipher list which does not
- // include the selected cipher. Test that we tolerate this. Servers may
- // resume at another cipher if the PRF matches and are not doing 0-RTT, but
- // BoringSSL will always decline.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-UnofferedCipher-TLS13",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
- Bugs: ProtocolBugs{
- SendCipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- },
- },
- expectResumeRejected: true,
- })
-
- // Sessions may not be resumed at a different cipher.
- testCases = append(testCases, testCase{
- name: "Resume-Client-CipherMismatch",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- shouldFail: true,
- expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:",
- })
-
- // Session resumption in TLS 1.3 may change the cipher suite if the PRF
- // matches.
- testCases = append(testCases, testCase{
- name: "Resume-Client-CipherMismatch-TLS13",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
- },
- })
-
- // Session resumption in TLS 1.3 is forbidden if the PRF does not match.
- testCases = append(testCases, testCase{
- name: "Resume-Client-PRFMismatch-TLS13",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_AES_256_GCM_SHA384,
- },
- },
- shouldFail: true,
- expectedError: ":OLD_SESSION_PRF_HASH_MISMATCH:",
- })
-
- for _, secondBinder := range []bool{false, true} {
- var suffix string
- var defaultCurves []CurveID
- if secondBinder {
- suffix = "-SecondBinder"
- // Force a HelloRetryRequest by predicting an empty curve list.
- defaultCurves = []CurveID{}
- }
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-BinderWrongLength" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: defaultCurves,
- Bugs: ProtocolBugs{
- SendShortPSKBinder: true,
- OnlyCorruptSecondPSKBinder: secondBinder,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decrypting message",
- expectedError: ":DIGEST_CHECK_FAILED:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-NoPSKBinder" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: defaultCurves,
- Bugs: ProtocolBugs{
- SendNoPSKBinder: true,
- OnlyCorruptSecondPSKBinder: secondBinder,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-ExtraPSKBinder" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: defaultCurves,
- Bugs: ProtocolBugs{
- SendExtraPSKBinder: true,
- OnlyCorruptSecondPSKBinder: secondBinder,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: illegal parameter",
- expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-ExtraIdentityNoBinder" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: defaultCurves,
- Bugs: ProtocolBugs{
- ExtraPSKIdentity: true,
- OnlyCorruptSecondPSKBinder: secondBinder,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: illegal parameter",
- expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-InvalidPSKBinder" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: defaultCurves,
- Bugs: ProtocolBugs{
- SendInvalidPSKBinder: true,
- OnlyCorruptSecondPSKBinder: secondBinder,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decrypting message",
- expectedError: ":DIGEST_CHECK_FAILED:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-PSKBinderFirstExtension" + suffix,
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: defaultCurves,
- Bugs: ProtocolBugs{
- PSKBinderFirst: true,
- OnlyCorruptSecondPSKBinder: secondBinder,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: illegal parameter",
- expectedError: ":PRE_SHARED_KEY_MUST_BE_LAST:",
- })
- }
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Resume-Server-OmitPSKsOnSecondClientHello",
- resumeSession: true,
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- OmitPSKsOnSecondClientHello: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: illegal parameter",
- expectedError: ":INCONSISTENT_CLIENT_HELLO:",
- })
-}
-
-func addRenegotiationTests() {
- // Servers cannot renegotiate.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Renegotiate-Server-Forbidden",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- expectedLocalError: "remote error: no renegotiation",
- })
- // The server shouldn't echo the renegotiation extension unless
- // requested by the client.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Renegotiate-Server-NoExt",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoRenegotiationInfo: true,
- RequireRenegotiationInfo: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "renegotiation extension missing",
- })
- // The renegotiation SCSV should be sufficient for the server to echo
- // the extension.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Renegotiate-Server-NoExt-SCSV",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoRenegotiationInfo: true,
- SendRenegotiationSCSV: true,
- RequireRenegotiationInfo: true,
- },
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- FailIfResumeOnRenego: true,
- },
- },
- renegotiate: 1,
- // Test renegotiation after both an initial and resumption
- // handshake.
- resumeSession: true,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- "-expect-secure-renegotiation",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- FailIfResumeOnRenego: true,
- },
- },
- renegotiate: 1,
- // Test renegotiation after both an initial and resumption
- // handshake.
- resumeSession: true,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- "-expect-secure-renegotiation",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-EmptyExt",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- EmptyRenegotiationInfo: true,
- },
- },
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":RENEGOTIATION_MISMATCH:",
- expectedLocalError: "handshake failure",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-BadExt",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadRenegotiationInfo: true,
- },
- },
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":RENEGOTIATION_MISMATCH:",
- expectedLocalError: "handshake failure",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-BadExt2",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadRenegotiationInfoEnd: true,
- },
- },
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":RENEGOTIATION_MISMATCH:",
- expectedLocalError: "handshake failure",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Downgrade",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoRenegotiationInfoAfterInitial: true,
- },
- },
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":RENEGOTIATION_MISMATCH:",
- expectedLocalError: "handshake failure",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Upgrade",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoRenegotiationInfoInInitial: true,
- },
- },
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":RENEGOTIATION_MISMATCH:",
- expectedLocalError: "handshake failure",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-NoExt-Allowed",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoRenegotiationInfo: true,
- },
- },
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- "-expect-no-secure-renegotiation",
- },
- })
-
- // Test that the server may switch ciphers on renegotiation without
- // problems.
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-SwitchCiphers",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- },
- renegotiateCiphers: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-SwitchCiphers2",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- renegotiateCiphers: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
-
- // Test that the server may not switch versions on renegotiation.
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-SwitchVersion",
- config: Config{
- MaxVersion: VersionTLS12,
- // Pick a cipher which exists at both versions.
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- NegotiateVersionOnRenego: VersionTLS11,
- // Avoid failing early at the record layer.
- SendRecordVersion: VersionTLS12,
- },
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shouldFail: true,
- expectedError: ":WRONG_SSL_VERSION:",
- })
-
- testCases = append(testCases, testCase{
- name: "Renegotiate-SameClientVersion",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS10,
- Bugs: ProtocolBugs{
- RequireSameRenegoClientVersion: true,
- },
- },
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-FalseStart",
- renegotiate: 1,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- },
- flags: []string{
- "-false-start",
- "-select-next-proto", "foo",
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shimWritesFirst: true,
- })
-
- // Client-side renegotiation controls.
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Forbidden-1",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- expectedLocalError: "remote error: no renegotiation",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Once-1",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-once",
- "-expect-total-renegotiations", "1",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Freely-1",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Once-2",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 2,
- flags: []string{"-renegotiate-once"},
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- expectedLocalError: "remote error: no renegotiation",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Freely-2",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 2,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "2",
- },
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-NoIgnore",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendHelloRequestBeforeEveryAppDataRecord: true,
- },
- },
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Ignore",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendHelloRequestBeforeEveryAppDataRecord: true,
- },
- },
- flags: []string{
- "-renegotiate-ignore",
- "-expect-total-renegotiations", "0",
- },
- })
-
- // Renegotiation may be enabled and then disabled immediately after the
- // handshake.
- testCases = append(testCases, testCase{
- name: "Renegotiate-ForbidAfterHandshake",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{"-forbid-renegotiation-after-handshake"},
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- expectedLocalError: "remote error: no renegotiation",
- })
-
- // Renegotiation is not allowed when there is an unfinished write.
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-UnfinishedWrite",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- readWithUnfinishedWrite: true,
- flags: []string{
- "-async",
- "-renegotiate-freely",
- },
- shouldFail: true,
- expectedError: ":NO_RENEGOTIATION:",
- // We do not successfully send the no_renegotiation alert in
- // this case. https://crbug.com/boringssl/130
- })
-
- // We reject stray HelloRequests during the handshake in TLS 1.2.
- testCases = append(testCases, testCase{
- name: "StrayHelloRequest",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendHelloRequestBeforeEveryHandshakeMessage: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
- testCases = append(testCases, testCase{
- name: "StrayHelloRequest-Packed",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- PackHandshakeFlight: true,
- SendHelloRequestBeforeEveryHandshakeMessage: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
-
- // Test that HelloRequest is rejected if it comes in the same record as the
- // server Finished.
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-Packed",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- PackHandshakeFlight: true,
- PackHelloRequestWithFinished: true,
- },
- },
- renegotiate: 1,
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Renegotiation is forbidden in TLS 1.3.
- testCases = append(testCases, testCase{
- name: "Renegotiate-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRequestBeforeEveryAppDataRecord: true,
- },
- },
- flags: []string{
- "-renegotiate-freely",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
-
- // Stray HelloRequests during the handshake are forbidden in TLS 1.3.
- testCases = append(testCases, testCase{
- name: "StrayHelloRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRequestBeforeEveryHandshakeMessage: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- })
-
- // The renegotiation_info extension is not sent in TLS 1.3, but TLS 1.3
- // always reads as supporting it, regardless of whether it was
- // negotiated.
- testCases = append(testCases, testCase{
- name: "AlwaysReportRenegotiationInfo-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- NoRenegotiationInfo: true,
- },
- },
- flags: []string{
- "-expect-secure-renegotiation",
- },
- })
-
- // Certificates may not change on renegotiation.
- testCases = append(testCases, testCase{
- name: "Renegotiation-CertificateChange",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- RenegotiationCertificate: &rsaChainCertificate,
- },
- },
- renegotiate: 1,
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":SERVER_CERT_CHANGED:",
- })
- testCases = append(testCases, testCase{
- name: "Renegotiation-CertificateChange-2",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- RenegotiationCertificate: &rsa1024Certificate,
- },
- },
- renegotiate: 1,
- flags: []string{"-renegotiate-freely"},
- shouldFail: true,
- expectedError: ":SERVER_CERT_CHANGED:",
- })
-
- // We do not negotiate ALPN after the initial handshake. This is
- // error-prone and only risks bugs in consumers.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "Renegotiation-ForbidALPN",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- // Forcibly negotiate ALPN on both initial and
- // renegotiation handshakes. The test stack will
- // internally check the client does not offer
- // it.
- SendALPN: "foo",
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar\x03baz",
- "-expect-alpn", "foo",
- "-renegotiate-freely",
- },
- renegotiate: 1,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- // The server may send different stapled OCSP responses or SCT lists on
- // renegotiation, but BoringSSL ignores this and reports the old values.
- // Also test that non-fatal verify results are preserved.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "Renegotiation-ChangeAuthProperties",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- Bugs: ProtocolBugs{
- SendOCSPResponseOnRenegotiation: testOCSPResponse2,
- SendSCTListOnRenegotiation: testSCTList2,
- },
- },
- renegotiate: 1,
- flags: []string{
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- "-enable-ocsp-stapling",
- "-expect-ocsp-response",
- base64FlagValue(testOCSPResponse),
- "-enable-signed-cert-timestamps",
- "-expect-signed-cert-timestamps",
- base64FlagValue(testSCTList),
- "-verify-fail",
- "-expect-verify-result",
- },
- })
-}
-
-func addDTLSReplayTests() {
- for _, vers := range allVersions(dtls) {
- // Test that sequence number replays are detected.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Replay-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- messageCount: 200,
- replayWrites: true,
- })
-
- // Test the incoming sequence number skipping by values larger
- // than the retransmit window.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Replay-LargeGaps-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- SequenceNumberMapping: func(in uint64) uint64 {
- return in * 1023
- },
- },
- },
- messageCount: 200,
- replayWrites: true,
- })
-
- // Test the incoming sequence number changing non-monotonically.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Replay-NonMonotonic-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- SequenceNumberMapping: func(in uint64) uint64 {
- // This mapping has numbers counting backwards in groups
- // of 256, and then jumping forwards 511 numbers.
- return in ^ 255
- },
- },
- },
- // This messageCount is large enough to make sure that the SequenceNumberMapping
- // will reach the point where it jumps forwards after stepping backwards.
- messageCount: 500,
- replayWrites: true,
- })
- }
-}
-
-var testSignatureAlgorithms = []struct {
- name string
- id signatureAlgorithm
- baseCert *Credential
- // If non-zero, the curve that must be supported in TLS 1.2 for cert to be
- // accepted.
- curve CurveID
-}{
- {"RSA_PKCS1_SHA1", signatureRSAPKCS1WithSHA1, &rsaCertificate, 0},
- {"RSA_PKCS1_SHA256", signatureRSAPKCS1WithSHA256, &rsaCertificate, 0},
- {"RSA_PKCS1_SHA256_LEGACY", signatureRSAPKCS1WithSHA256Legacy, &rsaCertificate, 0},
- {"RSA_PKCS1_SHA384", signatureRSAPKCS1WithSHA384, &rsaCertificate, 0},
- {"RSA_PKCS1_SHA512", signatureRSAPKCS1WithSHA512, &rsaCertificate, 0},
- {"ECDSA_SHA1", signatureECDSAWithSHA1, &ecdsaP256Certificate, CurveP256},
- // The “P256” in the following line is not a mistake. In TLS 1.2 the
- // hash function doesn't have to match the curve and so the same
- // signature algorithm works with P-224.
- {"ECDSA_P224_SHA256", signatureECDSAWithP256AndSHA256, &ecdsaP224Certificate, CurveP224},
- {"ECDSA_P256_SHA256", signatureECDSAWithP256AndSHA256, &ecdsaP256Certificate, CurveP256},
- {"ECDSA_P384_SHA384", signatureECDSAWithP384AndSHA384, &ecdsaP384Certificate, CurveP384},
- {"ECDSA_P521_SHA512", signatureECDSAWithP521AndSHA512, &ecdsaP521Certificate, CurveP521},
- {"RSA_PSS_SHA256", signatureRSAPSSWithSHA256, &rsaCertificate, 0},
- {"RSA_PSS_SHA384", signatureRSAPSSWithSHA384, &rsaCertificate, 0},
- {"RSA_PSS_SHA512", signatureRSAPSSWithSHA512, &rsaCertificate, 0},
- {"Ed25519", signatureEd25519, &ed25519Certificate, 0},
- // Tests for key types prior to TLS 1.2.
- {"RSA", 0, &rsaCertificate, 0},
- {"ECDSA", 0, &ecdsaP256Certificate, CurveP256},
-}
-
-const (
- fakeSigAlg1 signatureAlgorithm = 0x2a01
- fakeSigAlg2 signatureAlgorithm = 0xff01
-)
-
-func addSignatureAlgorithmTests() {
- // Not all ciphers involve a signature. Advertise a list which gives all
- // versions a signing cipher.
- signingCiphers := []uint16{
- TLS_AES_256_GCM_SHA384,
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- }
-
- var allAlgorithms []signatureAlgorithm
- for _, alg := range testSignatureAlgorithms {
- if alg.id != 0 {
- allAlgorithms = append(allAlgorithms, alg.id)
- }
- }
-
- // Make sure each signature algorithm works. Include some fake values in
- // the list and ensure they're ignored.
- for _, alg := range testSignatureAlgorithms {
- // Make a version of the certificate that will not sign any other algorithm.
- cert := alg.baseCert
- if alg.id != 0 {
- cert = cert.WithSignatureAlgorithms(alg.id)
- }
-
- for _, ver := range tlsVersions {
- if (ver.version < VersionTLS12) != (alg.id == 0) {
- continue
- }
-
- suffix := "-" + alg.name + "-" + ver.name
- for _, signTestType := range []testType{clientTest, serverTest} {
- signPrefix := "Client-"
- verifyPrefix := "Server-"
- verifyTestType := serverTest
- if signTestType == serverTest {
- verifyTestType = clientTest
- signPrefix, verifyPrefix = verifyPrefix, signPrefix
- }
-
- var shouldFail bool
- isTLS12PKCS1 := hasComponent(alg.name, "PKCS1") && !hasComponent(alg.name, "LEGACY")
- isTLS13PKCS1 := hasComponent(alg.name, "PKCS1") && hasComponent(alg.name, "LEGACY")
-
- // TLS 1.3 removes a number of signature algorithms.
- if ver.version >= VersionTLS13 && (alg.curve == CurveP224 || alg.id == signatureECDSAWithSHA1 || isTLS12PKCS1) {
- shouldFail = true
- }
-
- // The backported RSA-PKCS1 code points only exist for TLS 1.3
- // client certificates.
- if (ver.version < VersionTLS13 || signTestType == serverTest) && isTLS13PKCS1 {
- shouldFail = true
- }
-
- // By default, BoringSSL does not sign with these algorithms.
- signDefault := !shouldFail
- if isTLS13PKCS1 {
- signDefault = false
- }
-
- // By default, BoringSSL does not accept these algorithms.
- verifyDefault := !shouldFail
- if alg.id == signatureECDSAWithSHA1 || alg.id == signatureECDSAWithP521AndSHA512 || alg.id == signatureEd25519 || isTLS13PKCS1 {
- verifyDefault = false
- }
-
- var curveFlags []string
- var runnerCurves []CurveID
- if alg.curve != 0 && ver.version <= VersionTLS12 {
- // In TLS 1.2, the ECDH curve list also constrains ECDSA keys. Ensure the
- // corresponding curve is enabled. Also include X25519 to ensure the shim
- // and runner have something in common for ECDH.
- curveFlags = flagInts("-curves", []int{int(CurveX25519), int(alg.curve)})
- runnerCurves = []CurveID{CurveX25519, alg.curve}
- }
-
- signError := func(shouldFail bool) string {
- if !shouldFail {
- return ""
- }
- // In TLS 1.3, the shim should report no common signature algorithms if
- // it cannot generate a signature. In TLS 1.2 servers, signature
- // algorithm and cipher selection are integrated, so it is reported as
- // no shared cipher.
- if ver.version <= VersionTLS12 && signTestType == serverTest {
- return ":NO_SHARED_CIPHER:"
- }
- return ":NO_COMMON_SIGNATURE_ALGORITHMS:"
- }
- signLocalError := func(shouldFail bool) string {
- if !shouldFail {
- return ""
- }
- // The shim should send handshake_failure when it cannot
- // negotiate parameters.
- return "remote error: handshake failure"
- }
- verifyError := func(shouldFail bool) string {
- if !shouldFail {
- return ""
- }
- // If the shim rejects the signature algorithm, but the
- // runner forcibly selects it anyway, the shim should notice.
- return ":WRONG_SIGNATURE_TYPE:"
- }
- verifyLocalError := func(shouldFail bool) string {
- if !shouldFail {
- return ""
- }
- // The shim should send an illegal_parameter alert if the runner
- // uses a signature algorithm it isn't allowed to use.
- return "remote error: illegal parameter"
- }
-
- // Test the shim using the algorithm for signing.
- signTest := testCase{
- testType: signTestType,
- name: signPrefix + "Sign" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CurvePreferences: runnerCurves,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- fakeSigAlg1,
- alg.id,
- fakeSigAlg2,
- },
- },
- shimCertificate: cert,
- flags: curveFlags,
- shouldFail: shouldFail,
- expectedError: signError(shouldFail),
- expectedLocalError: signLocalError(shouldFail),
- expectations: connectionExpectations{
- peerSignatureAlgorithm: alg.id,
- },
- }
-
- // Test whether the shim enables the algorithm by default.
- signDefaultTest := testCase{
- testType: signTestType,
- name: signPrefix + "SignDefault" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CurvePreferences: runnerCurves,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- fakeSigAlg1,
- alg.id,
- fakeSigAlg2,
- },
- },
- // cert has been configured with the specified algorithm,
- // while alg.baseCert uses the defaults.
- shimCertificate: alg.baseCert,
- flags: curveFlags,
- shouldFail: !signDefault,
- expectedError: signError(!signDefault),
- expectedLocalError: signLocalError(!signDefault),
- expectations: connectionExpectations{
- peerSignatureAlgorithm: alg.id,
- },
- }
-
- // Test that the shim will select the algorithm when configured to only
- // support it.
- negotiateTest := testCase{
- testType: signTestType,
- name: signPrefix + "Sign-Negotiate" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CurvePreferences: runnerCurves,
- VerifySignatureAlgorithms: allAlgorithms,
- },
- shimCertificate: cert,
- flags: curveFlags,
- expectations: connectionExpectations{
- peerSignatureAlgorithm: alg.id,
- },
- }
-
- if signTestType == serverTest {
- // TLS 1.2 servers only sign on some cipher suites.
- signTest.config.CipherSuites = signingCiphers
- signDefaultTest.config.CipherSuites = signingCiphers
- negotiateTest.config.CipherSuites = signingCiphers
- } else {
- // TLS 1.2 clients only sign when the server requests certificates.
- signTest.config.ClientAuth = RequireAnyClientCert
- signDefaultTest.config.ClientAuth = RequireAnyClientCert
- negotiateTest.config.ClientAuth = RequireAnyClientCert
- }
- testCases = append(testCases, signTest, signDefaultTest)
- if ver.version >= VersionTLS12 && !shouldFail {
- testCases = append(testCases, negotiateTest)
- }
-
- // Test the shim using the algorithm for verifying.
- verifyTest := testCase{
- testType: verifyTestType,
- name: verifyPrefix + "Verify" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Credential: cert,
- Bugs: ProtocolBugs{
- SkipECDSACurveCheck: shouldFail,
- IgnoreSignatureVersionChecks: shouldFail,
- // Some signature algorithms may not be advertised.
- IgnorePeerSignatureAlgorithmPreferences: shouldFail,
- },
- },
- flags: curveFlags,
- // Resume the session to assert the peer signature
- // algorithm is reported on both handshakes.
- resumeSession: !shouldFail,
- shouldFail: shouldFail,
- expectedError: verifyError(shouldFail),
- expectedLocalError: verifyLocalError(shouldFail),
- }
- if alg.id != 0 {
- verifyTest.flags = append(verifyTest.flags, "-expect-peer-signature-algorithm", strconv.Itoa(int(alg.id)))
- // The algorithm may be disabled by default, so explicitly enable it.
- verifyTest.flags = append(verifyTest.flags, "-verify-prefs", strconv.Itoa(int(alg.id)))
- }
-
- // Test whether the shim expects the algorithm enabled by default.
- defaultTest := testCase{
- testType: verifyTestType,
- name: verifyPrefix + "VerifyDefault" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Credential: cert,
- Bugs: ProtocolBugs{
- SkipECDSACurveCheck: !verifyDefault,
- IgnoreSignatureVersionChecks: !verifyDefault,
- // Some signature algorithms may not be advertised.
- IgnorePeerSignatureAlgorithmPreferences: !verifyDefault,
- },
- },
- flags: append(
- []string{"-expect-peer-signature-algorithm", strconv.Itoa(int(alg.id))},
- curveFlags...,
- ),
- // Resume the session to assert the peer signature
- // algorithm is reported on both handshakes.
- resumeSession: verifyDefault,
- shouldFail: !verifyDefault,
- expectedError: verifyError(!verifyDefault),
- expectedLocalError: verifyLocalError(!verifyDefault),
- }
-
- // Test whether the shim handles invalid signatures for this algorithm.
- invalidTest := testCase{
- testType: verifyTestType,
- name: verifyPrefix + "InvalidSignature" + suffix,
- config: Config{
- MaxVersion: ver.version,
- Credential: cert,
- Bugs: ProtocolBugs{
- InvalidSignature: true,
- },
- },
- flags: curveFlags,
- shouldFail: true,
- expectedError: ":BAD_SIGNATURE:",
- }
- if alg.id != 0 {
- // The algorithm may be disabled by default, so explicitly enable it.
- invalidTest.flags = append(invalidTest.flags, "-verify-prefs", strconv.Itoa(int(alg.id)))
- }
-
- if verifyTestType == serverTest {
- // TLS 1.2 servers only verify when they request client certificates.
- verifyTest.flags = append(verifyTest.flags, "-require-any-client-certificate")
- defaultTest.flags = append(defaultTest.flags, "-require-any-client-certificate")
- invalidTest.flags = append(invalidTest.flags, "-require-any-client-certificate")
- } else {
- // TLS 1.2 clients only verify on some cipher suites.
- verifyTest.config.CipherSuites = signingCiphers
- defaultTest.config.CipherSuites = signingCiphers
- invalidTest.config.CipherSuites = signingCiphers
- }
- testCases = append(testCases, verifyTest, defaultTest)
- if !shouldFail {
- testCases = append(testCases, invalidTest)
- }
- }
- }
- }
-
- // Test the peer's verify preferences are available.
- for _, ver := range tlsVersions {
- if ver.version < VersionTLS12 {
- continue
- }
- testCases = append(testCases, testCase{
- name: "ClientAuth-PeerVerifyPrefs-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA256,
- signatureEd25519,
- signatureECDSAWithP256AndSHA256,
- },
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
- "-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)),
- "-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)),
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerAuth-PeerVerifyPrefs-" + ver.name,
- config: Config{
- MaxVersion: ver.version,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA256,
- signatureEd25519,
- signatureECDSAWithP256AndSHA256,
- },
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
- "-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)),
- "-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)),
- },
- })
-
- }
-
- // Test that algorithm selection takes the key type into account.
- testCases = append(testCases, testCase{
- name: "ClientAuth-SignatureType",
- config: Config{
- ClientAuth: RequireAnyClientCert,
- MaxVersion: VersionTLS12,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithP521AndSHA512,
- signatureRSAPKCS1WithSHA384,
- signatureECDSAWithSHA1,
- },
- },
- shimCertificate: &rsaCertificate,
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPKCS1WithSHA384,
- },
- })
-
- testCases = append(testCases, testCase{
- name: "ClientAuth-SignatureType-TLS13",
- config: Config{
- ClientAuth: RequireAnyClientCert,
- MaxVersion: VersionTLS13,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithP521AndSHA512,
- signatureRSAPKCS1WithSHA384,
- signatureRSAPSSWithSHA384,
- signatureECDSAWithSHA1,
- },
- },
- shimCertificate: &rsaCertificate,
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPSSWithSHA384,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerAuth-SignatureType",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithP521AndSHA512,
- signatureRSAPKCS1WithSHA384,
- signatureECDSAWithSHA1,
- },
- },
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPKCS1WithSHA384,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerAuth-SignatureType-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithP521AndSHA512,
- signatureRSAPKCS1WithSHA384,
- signatureRSAPSSWithSHA384,
- signatureECDSAWithSHA1,
- },
- },
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPSSWithSHA384,
- },
- })
-
- // Test that signature verification takes the key type into account.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Verify-ClientAuth-SignatureType",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithSHA256),
- Bugs: ProtocolBugs{
- SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
- },
- },
- flags: []string{
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Verify-ClientAuth-SignatureType-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- Bugs: ProtocolBugs{
- SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
- },
- },
- flags: []string{
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- testCases = append(testCases, testCase{
- name: "Verify-ServerAuth-SignatureType",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithSHA256),
- Bugs: ProtocolBugs{
- SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- testCases = append(testCases, testCase{
- name: "Verify-ServerAuth-SignatureType-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- Bugs: ProtocolBugs{
- SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- // Test that, if the ClientHello list is missing, the server falls back
- // to SHA-1 in TLS 1.2, but not TLS 1.3.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerAuth-SHA1-Fallback-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA1,
- },
- Bugs: ProtocolBugs{
- NoSignatureAlgorithms: true,
- },
- },
- shimCertificate: &rsaCertificate,
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerAuth-SHA1-Fallback-ECDSA",
- config: Config{
- MaxVersion: VersionTLS12,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithSHA1,
- },
- Bugs: ProtocolBugs{
- NoSignatureAlgorithms: true,
- },
- },
- shimCertificate: &ecdsaP256Certificate,
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerAuth-NoFallback-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA1,
- },
- Bugs: ProtocolBugs{
- NoSignatureAlgorithms: true,
- },
- },
- shouldFail: true,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- })
-
- // The CertificateRequest list, however, may never be omitted. It is a
- // syntax error for it to be empty.
- testCases = append(testCases, testCase{
- name: "ClientAuth-NoFallback-RSA",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA1,
- },
- Bugs: ProtocolBugs{
- NoSignatureAlgorithms: true,
- },
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- testCases = append(testCases, testCase{
- name: "ClientAuth-NoFallback-ECDSA",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithSHA1,
- },
- Bugs: ProtocolBugs{
- NoSignatureAlgorithms: true,
- },
- },
- shimCertificate: &ecdsaP256Certificate,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- testCases = append(testCases, testCase{
- name: "ClientAuth-NoFallback-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA1,
- },
- Bugs: ProtocolBugs{
- NoSignatureAlgorithms: true,
- },
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- // Test that signature preferences are enforced. BoringSSL does not
- // implement MD5 signatures.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ClientAuth-Enforced",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- },
- },
- flags: []string{"-require-any-client-certificate"},
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- testCases = append(testCases, testCase{
- name: "ServerAuth-Enforced",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ClientAuth-Enforced-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- IgnoreSignatureVersionChecks: true,
- },
- },
- flags: []string{"-require-any-client-certificate"},
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- testCases = append(testCases, testCase{
- name: "ServerAuth-Enforced-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- IgnoreSignatureVersionChecks: true,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- // Test that the negotiated signature algorithm respects the client and
- // server preferences.
- testCases = append(testCases, testCase{
- name: "NoCommonAlgorithms",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA512,
- signatureRSAPKCS1WithSHA1,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithSHA256),
- shouldFail: true,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- })
- testCases = append(testCases, testCase{
- name: "NoCommonAlgorithms-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA512,
- signatureRSAPSSWithSHA384,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- shouldFail: true,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- })
- testCases = append(testCases, testCase{
- name: "Agree-Digest-SHA256",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA1,
- signatureRSAPKCS1WithSHA256,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(
- signatureRSAPKCS1WithSHA256,
- signatureRSAPKCS1WithSHA1,
- ),
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
- },
- })
- testCases = append(testCases, testCase{
- name: "Agree-Digest-SHA1",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA1,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(
- signatureRSAPKCS1WithSHA512,
- signatureRSAPKCS1WithSHA256,
- signatureRSAPKCS1WithSHA1,
- ),
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPKCS1WithSHA1,
- },
- })
- testCases = append(testCases, testCase{
- name: "Agree-Digest-Default",
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA256,
- signatureECDSAWithP256AndSHA256,
- signatureRSAPKCS1WithSHA1,
- signatureECDSAWithSHA1,
- },
- },
- shimCertificate: &rsaCertificate,
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
- },
- })
-
- // Test that the signing preference list may include extra algorithms
- // without negotiation problems.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "FilterExtraAlgorithms",
- config: Config{
- MaxVersion: VersionTLS12,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPKCS1WithSHA256,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(
- signatureECDSAWithP256AndSHA256,
- signatureRSAPKCS1WithSHA256,
- ),
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
- },
- })
-
- // In TLS 1.2 and below, ECDSA uses the curve list rather than the
- // signature algorithms.
- testCases = append(testCases, testCase{
- name: "CheckLeafCurve",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- Credential: &ecdsaP256Certificate,
- },
- flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
- shouldFail: true,
- expectedError: ":BAD_ECC_CERT:",
- })
-
- // In TLS 1.3, ECDSA does not use the ECDHE curve list.
- testCases = append(testCases, testCase{
- name: "CheckLeafCurve-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &ecdsaP256Certificate,
- },
- flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
- })
-
- // In TLS 1.2, the ECDSA curve is not in the signature algorithm, so the
- // shim should accept P-256 with SHA-384.
- testCases = append(testCases, testCase{
- name: "ECDSACurveMismatch-Verify-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
- Credential: ecdsaP256Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384),
- },
- })
-
- // In TLS 1.3, the ECDSA curve comes from the signature algorithm, so the
- // shim should reject P-256 with SHA-384.
- testCases = append(testCases, testCase{
- name: "ECDSACurveMismatch-Verify-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: ecdsaP256Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384),
- Bugs: ProtocolBugs{
- SkipECDSACurveCheck: true,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- // Signature algorithm selection in TLS 1.3 should take the curve into
- // account.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ECDSACurveMismatch-Sign-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithP384AndSHA384,
- signatureECDSAWithP256AndSHA256,
- },
- },
- shimCertificate: &ecdsaP256Certificate,
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
- },
- })
-
- // RSASSA-PSS with SHA-512 is too large for 1024-bit RSA. Test that the
- // server does not attempt to sign in that case.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RSA-PSS-Large",
- config: Config{
- MaxVersion: VersionTLS13,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA512,
- },
- },
- shimCertificate: &rsa1024Certificate,
- shouldFail: true,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- })
-
- // Test that RSA-PSS is enabled by default for TLS 1.2.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSA-PSS-Default-Verify",
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RSA-PSS-Default-Sign",
- config: Config{
- MaxVersion: VersionTLS12,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA256,
- },
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- })
-
- // TLS 1.1 and below has no way to advertise support for or negotiate
- // Ed25519's signature algorithm.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "NoEd25519-TLS11-ServerAuth-Verify",
- config: Config{
- MaxVersion: VersionTLS11,
- Credential: &ed25519Certificate,
- Bugs: ProtocolBugs{
- // Sign with Ed25519 even though it is TLS 1.1.
- SigningAlgorithmForLegacyVersions: signatureEd25519,
- },
- },
- flags: []string{"-verify-prefs", strconv.Itoa(int(signatureEd25519))},
- shouldFail: true,
- expectedError: ":PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoEd25519-TLS11-ServerAuth-Sign",
- config: Config{
- MaxVersion: VersionTLS11,
- },
- shimCertificate: &ed25519Certificate,
- shouldFail: true,
- expectedError: ":NO_SHARED_CIPHER:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoEd25519-TLS11-ClientAuth-Verify",
- config: Config{
- MaxVersion: VersionTLS11,
- Credential: &ed25519Certificate,
- Bugs: ProtocolBugs{
- // Sign with Ed25519 even though it is TLS 1.1.
- SigningAlgorithmForLegacyVersions: signatureEd25519,
- },
- },
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureEd25519)),
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "NoEd25519-TLS11-ClientAuth-Sign",
- config: Config{
- MaxVersion: VersionTLS11,
- ClientAuth: RequireAnyClientCert,
- },
- shimCertificate: &ed25519Certificate,
- shouldFail: true,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- })
-
- // Test Ed25519 is not advertised by default.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "Ed25519DefaultDisable-NoAdvertise",
- config: Config{
- Credential: &ed25519Certificate,
- },
- shouldFail: true,
- expectedLocalError: "tls: no common signature algorithms",
- })
-
- // Test Ed25519, when disabled, is not accepted if the peer ignores our
- // preferences.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "Ed25519DefaultDisable-NoAccept",
- config: Config{
- Credential: &ed25519Certificate,
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: illegal parameter",
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- // Test that configuring verify preferences changes what the client
- // advertises.
- testCases = append(testCases, testCase{
- name: "VerifyPreferences-Advertised",
- config: Config{
- Credential: rsaCertificate.WithSignatureAlgorithms(
- signatureRSAPSSWithSHA256,
- signatureRSAPSSWithSHA384,
- signatureRSAPSSWithSHA512,
- ),
- },
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
- "-expect-peer-signature-algorithm", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
- },
- })
-
- // Test that the client advertises a set which the runner can find
- // nothing in common with.
- testCases = append(testCases, testCase{
- name: "VerifyPreferences-NoCommonAlgorithms",
- config: Config{
- Credential: rsaCertificate.WithSignatureAlgorithms(
- signatureRSAPSSWithSHA256,
- signatureRSAPSSWithSHA512,
- ),
- },
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
- },
- shouldFail: true,
- expectedLocalError: "tls: no common signature algorithms",
- })
-
- // Test that the client enforces its preferences when configured.
- testCases = append(testCases, testCase{
- name: "VerifyPreferences-Enforced",
- config: Config{
- Credential: rsaCertificate.WithSignatureAlgorithms(
- signatureRSAPSSWithSHA256,
- signatureRSAPSSWithSHA512,
- ),
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- },
- },
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
- },
- shouldFail: true,
- expectedLocalError: "remote error: illegal parameter",
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
-
- // Test that explicitly configuring Ed25519 is as good as changing the
- // boolean toggle.
- testCases = append(testCases, testCase{
- name: "VerifyPreferences-Ed25519",
- config: Config{
- Credential: &ed25519Certificate,
- },
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureEd25519)),
- },
- })
-
- for _, testType := range []testType{clientTest, serverTest} {
- for _, ver := range tlsVersions {
- if ver.version < VersionTLS12 {
- continue
- }
-
- prefix := "Client-" + ver.name + "-"
- noCommonAlgorithmsError := ":NO_COMMON_SIGNATURE_ALGORITHMS:"
- if testType == serverTest {
- prefix = "Server-" + ver.name + "-"
- // In TLS 1.2 servers, cipher selection and algorithm
- // selection are linked.
- if ver.version <= VersionTLS12 {
- noCommonAlgorithmsError = ":NO_SHARED_CIPHER:"
- }
- }
-
- // Test that the shim will not sign MD5/SHA1 with RSA at TLS 1.2,
- // even if specified in signing preferences.
- testCases = append(testCases, testCase{
- testType: testType,
- name: prefix + "NoSign-RSA_PKCS1_MD5_SHA1",
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: signingCiphers,
- ClientAuth: RequireAnyClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPKCS1WithMD5AndSHA1},
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(
- signatureRSAPKCS1WithMD5AndSHA1,
- // Include a valid algorithm as well, to avoid an empty list
- // if filtered out.
- signatureRSAPKCS1WithSHA256,
- ),
- shouldFail: true,
- expectedError: noCommonAlgorithmsError,
- })
-
- // Test that the shim will not accept MD5/SHA1 with RSA at TLS 1.2,
- // even if specified in verify preferences.
- testCases = append(testCases, testCase{
- testType: testType,
- name: prefix + "NoVerify-RSA_PKCS1_MD5_SHA1",
- config: Config{
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- Bugs: ProtocolBugs{
- IgnorePeerSignatureAlgorithmPreferences: true,
- AlwaysSignAsLegacyVersion: true,
- SendSignatureAlgorithm: signatureRSAPKCS1WithMD5AndSHA1,
- },
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-verify-prefs", strconv.Itoa(int(signatureRSAPKCS1WithMD5AndSHA1)),
- // Include a valid algorithm as well, to avoid an empty list
- // if filtered out.
- "-verify-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)),
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":WRONG_SIGNATURE_TYPE:",
- })
- }
- }
-
- // Test that, when there are no signature algorithms in common in TLS
- // 1.2, the server will still consider the legacy RSA key exchange.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoCommonSignatureAlgorithms-TLS12-Fallback",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureECDSAWithP256AndSHA256,
- },
- },
- expectations: connectionExpectations{
- cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- })
-}
-
-// timeouts is the default retransmit schedule for BoringSSL. It doubles and
-// caps at 60 seconds. On the 13th timeout, it gives up.
-var timeouts = []time.Duration{
- 400 * time.Millisecond,
- 800 * time.Millisecond,
- 1600 * time.Millisecond,
- 3200 * time.Millisecond,
- 6400 * time.Millisecond,
- 12800 * time.Millisecond,
- 25600 * time.Millisecond,
- 51200 * time.Millisecond,
- 60 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
-}
-
-// shortTimeouts is an alternate set of timeouts which would occur if the
-// initial timeout duration was set to 250ms.
-var shortTimeouts = []time.Duration{
- 250 * time.Millisecond,
- 500 * time.Millisecond,
- 1 * time.Second,
- 2 * time.Second,
- 4 * time.Second,
- 8 * time.Second,
- 16 * time.Second,
- 32 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
- 60 * time.Second,
-}
-
-// dtlsPrevEpochExpiration is how long before the shim releases old epochs. Add
-// an extra second to allow the shim to be less precise.
-const dtlsPrevEpochExpiration = 4*time.Minute + 1*time.Second
-
-func addDTLSRetransmitTests() {
- for _, shortTimeout := range []bool{false, true} {
- for _, vers := range allVersions(dtls) {
- suffix := "-" + vers.name
- flags := []string{"-async"} // Retransmit tests require async.
- useTimeouts := timeouts
- if shortTimeout {
- suffix += "-Short"
- flags = append(flags, "-initial-timeout-duration-ms", "250")
- useTimeouts = shortTimeouts
- }
-
- // Testing NewSessionTicket is tricky. First, BoringSSL sends two
- // tickets in a row. These are conceptually separate flights, but we
- // test them as one flight. Second, these tickets are sent
- // concurrently with the runner's first test message. The shim's
- // reply will come in before any retransmit challenges.
- // handleNewSessionTicket corrects for both effects.
- handleNewSessionTicket := func(f ACKFlightFunc) ACKFlightFunc {
- if vers.version < VersionTLS13 {
- return f
- }
- return func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- // BoringSSL sends two NewSessionTickets in a row.
- if received[0].Type == typeNewSessionTicket && len(received) < 2 {
- c.MergeIntoNextFlight()
- return
- }
- // NewSessionTicket is sent in parallel with the runner's
- // first application data. Consume the shim's reply.
- testMessage := makeTestMessage(0, 32)
- if received[0].Type == typeNewSessionTicket {
- c.ReadAppData(c.InEpoch(), expectedReply(testMessage))
- }
- // Run the test, without any stray messages in the way.
- f(c, prev, received, records)
- // The test loop is expecting a reply to the first message.
- // Prime the shim to send it again.
- if received[0].Type == typeNewSessionTicket {
- c.WriteAppData(c.OutEpoch(), testMessage)
- }
- }
- }
-
- // In all versions, the sender will retransmit the whole flight if
- // it times out and hears nothing.
- writeFlightBasic := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- // Exercise every timeout but the last one (which would fail the
- // connection).
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- c.ExpectNextTimeout(t)
- c.AdvanceClock(t)
- c.ReadRetransmit()
- }
- c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
- }
- // Finally release the whole flight to the shim.
- c.WriteFlight(next)
- }
- ackFlightBasic := handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- if vers.version >= VersionTLS13 {
- // In DTLS 1.3, final flights (either handshake or post-handshake)
- // are retransmited until ACKed. Exercise every timeout but
- // the last one (which would fail the connection).
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- c.ExpectNextTimeout(t)
- c.AdvanceClock(t)
- c.ReadRetransmit()
- }
- c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
- // Finally ACK the flight.
- c.WriteACK(c.OutEpoch(), records)
- return
- }
- // In DTLS 1.2, the final flight is retransmitted on receipt of
- // the previous flight. Test the peer is willing to retransmit
- // it several times.
- for i := 0; i < 5; i++ {
- c.WriteFlight(prev)
- c.ReadRetransmit()
- }
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Client-Basic" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: writeFlightBasic,
- ACKFlightDTLS: ackFlightBasic,
- },
- },
- resumeSession: true,
- flags: flags,
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-Basic" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: writeFlightBasic,
- ACKFlightDTLS: ackFlightBasic,
- },
- },
- resumeSession: true,
- flags: flags,
- })
-
- if vers.version <= VersionTLS12 {
- // In DTLS 1.2, receiving a part of the next flight should not stop
- // the retransmission timer.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-PartialProgress" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- // Send a portion of the first message. The rest was lost.
- msg := next[0]
- split := len(msg.Data) / 2
- c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
- // If we time out, the shim should still retransmit. It knows
- // we received the whole flight, but the shim should use a
- // retransmit to request the runner try again.
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
- // "Retransmit" the rest of the flight. The shim should remember
- // the portion that was already sent.
- rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}
- for _, m := range next[1:] {
- rest = append(rest, m.Fragment(0, len(m.Data)))
- }
- c.WriteFragments(rest)
- },
- },
- },
- flags: flags,
- })
- } else {
- // In DTLS 1.3, receiving a part of the next flight implicitly ACKs
- // the previous flight.
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "DTLS-Retransmit-PartialProgress-Server" + suffix,
- config: Config{
- MaxVersion: vers.version,
- DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) == 0 && next[0].Type == typeClientHello {
- // Send the initial ClientHello as-is.
- c.WriteFlight(next)
- return
- }
-
- // Send a portion of the first message. The rest was lost.
- msg := next[0]
- split := len(msg.Data) / 2
- c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
- // After waiting the current timeout, the shim should ACK
- // the partial flight.
- c.ExpectNextTimeout(useTimeouts[0] / 4)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(c.InEpoch())
- // The partial flight is enough to ACK the previous flight.
- // The shim should stop retransmitting and even stop the
- // retransmit timer.
- c.ExpectNoNextTimeout()
- for _, t := range useTimeouts {
- c.AdvanceClock(t)
- }
- // "Retransmit" the rest of the flight. The shim should remember
- // the portion that was already sent.
- rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}
- for _, m := range next[1:] {
- rest = append(rest, m.Fragment(0, len(m.Data)))
- }
- c.WriteFragments(rest)
- },
- },
- },
- flags: flags,
- })
-
- // When the shim is a client, receiving fragments before the version is
- // known does not trigger this behavior.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-PartialProgress-Client" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- msg := next[0]
- if msg.Type != typeServerHello {
- // Post-handshake is tested separately.
- c.WriteFlight(next)
- return
- }
- // Send a portion of the ServerHello. The rest was lost.
- split := len(msg.Data) / 2
- c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
-
- // The shim did not know this was DTLS 1.3, so it still
- // retransmits ClientHello.
- c.ExpectNextTimeout(useTimeouts[0])
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
-
- // Finish the ServerHello. The version is still not known,
- // at the time the ServerHello fragment is processed, This
- // is not as efficient as we could be; we could go back and
- // implicitly ACK once the version is known. But the last
- // byte of ServerHello will almost certainly be in the same
- // packet as EncryptedExtensions, which will trigger the case
- // below.
- c.WriteFragments([]DTLSFragment{msg.Fragment(split, len(msg.Data)-split)})
- c.ExpectNextTimeout(useTimeouts[1])
- c.AdvanceClock(useTimeouts[1])
- c.ReadRetransmit()
-
- // Send EncryptedExtensions. The shim now knows the version.
- c.WriteFragments([]DTLSFragment{next[1].Fragment(0, len(next[1].Data))})
-
- // The shim should ACK the partial flight. The shim hasn't
- // gotten to epoch 3 yet, so the ACK will come in epoch 2.
- c.AdvanceClock(useTimeouts[2] / 4)
- c.ReadACK(uint16(encryptionHandshake))
-
- // This is enough to ACK the previous flight. The shim
- // should stop retransmitting and even stop the timer.
- c.ExpectNoNextTimeout()
- for _, t := range useTimeouts[2:] {
- c.AdvanceClock(t)
- }
-
- // "Retransmit" the rest of the flight. The shim should remember
- // the portion that was already sent.
- var rest []DTLSFragment
- for _, m := range next[2:] {
- rest = append(rest, m.Fragment(0, len(m.Data)))
- }
- c.WriteFragments(rest)
- },
- },
- },
- flags: flags,
- })
- }
-
- // Test that exceeding the timeout schedule hits a read
- // timeout.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Timeout" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- c.ExpectNextTimeout(t)
- c.AdvanceClock(t)
- c.ReadRetransmit()
- }
- c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
- c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
- // The shim should give up at this point.
- },
- },
- },
- resumeSession: true,
- flags: flags,
- shouldFail: true,
- expectedError: ":READ_TIMEOUT_EXPIRED:",
- })
-
- // Test that timeout handling has a fudge factor, due to API
- // problems.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Fudge" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- c.ExpectNextTimeout(useTimeouts[0])
- c.AdvanceClock(useTimeouts[0] - 10*time.Millisecond)
- c.ReadRetransmit()
- }
- c.WriteFlight(next)
- },
- },
- },
- resumeSession: true,
- flags: flags,
- })
-
- // Test that the shim can retransmit at different MTUs.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-ChangeMTU" + suffix,
- config: Config{
- MaxVersion: vers.version,
- // Request a client certificate, so the shim has more to send.
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- for i, mtu := range []int{300, 301, 302, 303, 299, 298, 297} {
- c.SetMTU(mtu)
- c.AdvanceClock(useTimeouts[i])
- c.ReadRetransmit()
- }
- c.WriteFlight(next)
- },
- },
- },
- shimCertificate: &rsaChainCertificate,
- flags: flags,
- })
-
- // DTLS 1.3 uses explicit ACKs.
- if vers.version >= VersionTLS13 {
- // The two server flights (HelloRetryRequest and ServerHello..Finished)
- // happen after the shim has learned the version, so they are more
- // straightforward. In these tests, we trigger HelloRetryRequest,
- // and also use ML-KEM with rsaChainCertificate and a limited MTU,
- // to increase the number of records and exercise more complex
- // ACK patterns.
-
- // After ACKing everything, the shim should stop retransmitting.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKEverything" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Credential: &rsaChainCertificate,
- CurvePreferences: []CurveID{CurveX25519MLKEM768},
- DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
- Bugs: ProtocolBugs{
- // Send smaller packets to exercise more ACK cases.
- MaxPacketLength: 512,
- MaxHandshakeRecordLength: 512,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- ackEpoch := received[len(received)-1].Epoch
- c.ExpectNextTimeout(useTimeouts[0])
- c.WriteACK(ackEpoch, records)
- // After everything is ACKed, the shim should stop the timer
- // and wait for the next flight.
- c.ExpectNoNextTimeout()
- for _, t := range useTimeouts {
- c.AdvanceClock(t)
- }
- }
- c.WriteFlight(next)
- },
- ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- ackEpoch := received[len(received)-1].Epoch
- c.ExpectNextTimeout(useTimeouts[0])
- c.WriteACK(ackEpoch, records)
- // After everything is ACKed, the shim should stop the timer.
- c.ExpectNoNextTimeout()
- for _, t := range useTimeouts {
- c.AdvanceClock(t)
- }
- }),
- SequenceNumberMapping: func(in uint64) uint64 {
- // Perturb sequence numbers to test that ACKs are sorted.
- return in ^ 63
- },
- },
- },
- shimCertificate: &rsaChainCertificate,
- flags: slices.Concat(flags, []string{
- "-mtu", "512",
- "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
- // Request a client certificate so the client final flight is
- // larger.
- "-require-any-client-certificate",
- }),
- })
-
- // ACK packets one by one, in reverse.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKReverse" + suffix,
- config: Config{
- MaxVersion: vers.version,
- CurvePreferences: []CurveID{CurveX25519MLKEM768},
- DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
- Bugs: ProtocolBugs{
- MaxPacketLength: 512,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- ackEpoch := received[len(received)-1].Epoch
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]})
- }
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }
- c.WriteFlight(next)
- },
- ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- ackEpoch := received[len(received)-1].Epoch
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]})
- }
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }),
- },
- },
- shimCertificate: &rsaChainCertificate,
- flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
- })
-
- // ACK packets one by one, forwards.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKForwards" + suffix,
- config: Config{
- MaxVersion: vers.version,
- CurvePreferences: []CurveID{CurveX25519MLKEM768},
- DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
- Bugs: ProtocolBugs{
- MaxPacketLength: 512,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- ackEpoch := received[len(received)-1].Epoch
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
- }
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }
- c.WriteFlight(next)
- },
- ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- ackEpoch := received[len(received)-1].Epoch
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
- }
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }),
- },
- },
- shimCertificate: &rsaChainCertificate,
- flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
- })
-
- // ACK 1/3 the packets each time.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKIterate" + suffix,
- config: Config{
- MaxVersion: vers.version,
- CurvePreferences: []CurveID{CurveX25519MLKEM768},
- DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
- Bugs: ProtocolBugs{
- MaxPacketLength: 512,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- ackEpoch := received[len(received)-1].Epoch
- for i, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- ack := make([]DTLSRecordNumberInfo, 0, (len(records)+2)/3)
- for i := 0; i < len(records); i += 3 {
- ack = append(ack, records[i])
- }
- c.WriteACK(ackEpoch, ack)
- }
- // Change the MTU every iteration, to make the fragment
- // patterns more complex.
- c.SetMTU(512 + i)
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }
- c.WriteFlight(next)
- },
- ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- ackEpoch := received[len(received)-1].Epoch
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
- }
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }),
- },
- },
- shimCertificate: &rsaChainCertificate,
- flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
- })
-
- // ACKing packets that have already been ACKed is a no-op.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKDuplicate" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte("cookie"),
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- ackEpoch := received[len(received)-1].Epoch
- // Keep ACKing the same record over and over.
- c.WriteACK(ackEpoch, records[:1])
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
- c.WriteACK(ackEpoch, records[:1])
- c.AdvanceClock(useTimeouts[1])
- c.ReadRetransmit()
- }
- c.WriteFlight(next)
- },
- ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- ackEpoch := received[len(received)-1].Epoch
- // Keep ACKing the same record over and over.
- c.WriteACK(ackEpoch, records[:1])
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
- c.WriteACK(ackEpoch, records[:1])
- c.AdvanceClock(useTimeouts[1])
- c.ReadRetransmit()
- // ACK everything to clear the timer.
- c.WriteACK(ackEpoch, records)
- }),
- },
- },
- flags: flags,
- })
-
- // When ACKing ServerHello..Finished, the ServerHello might be
- // ACKed at epoch 0 or epoch 2, depending on how far the client
- // received. Test that epoch 0 is allowed by ACKing each packet
- // at the record it was received.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKMatchingEpoch" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- if len(records) > 0 {
- c.WriteACK(uint16(records[0].Epoch), []DTLSRecordNumberInfo{records[0]})
- }
- c.AdvanceClock(t)
- records = c.ReadRetransmit()
- }
- }
- c.WriteFlight(next)
- },
- },
- },
- flags: flags,
- })
-
- // However, records in the handshake may not be ACKed at lower
- // epoch than they were received.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKBadEpoch" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) == 0 {
- // Send the ClientHello.
- c.WriteFlight(next)
- } else {
- // Try to ACK ServerHello..Finished at epoch 0. The shim should reject this.
- c.WriteACK(0, records)
- }
- },
- },
- },
- flags: flags,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- // The bad epoch check should notice when the epoch number
- // would overflow 2^16.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKEpochOverflow" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) == 0 {
- // Send the ClientHello.
- c.WriteFlight(next)
- } else {
- r := records[0]
- r.Epoch += 1 << 63
- c.WriteACK(0, []DTLSRecordNumberInfo{r})
- }
- },
- },
- },
- flags: flags,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- // ACK some records from the first transmission, trigger a
- // retransmit, but then ACK the rest of the first transmission.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKOldRecords" + suffix,
- config: Config{
- MaxVersion: vers.version,
- CurvePreferences: []CurveID{CurveX25519MLKEM768},
- Bugs: ProtocolBugs{
- MaxPacketLength: 512,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- ackEpoch := received[len(received)-1].Epoch
- c.WriteACK(ackEpoch, records[len(records)/2:])
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
- c.WriteACK(ackEpoch, records[:len(records)/2])
- // Everything should be ACKed now. The shim should not
- // retransmit anything.
- c.AdvanceClock(useTimeouts[1])
- c.AdvanceClock(useTimeouts[2])
- c.AdvanceClock(useTimeouts[3])
- }
- c.WriteFlight(next)
- },
- },
- },
- flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
- })
-
- // If the shim sends too many records, it will eventually forget them.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKForgottenRecords" + suffix,
- config: Config{
- MaxVersion: vers.version,
- CurvePreferences: []CurveID{CurveX25519MLKEM768},
- Bugs: ProtocolBugs{
- MaxPacketLength: 256,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) > 0 {
- // Make the peer retransmit many times, with a small MTU.
- for _, t := range useTimeouts[:len(useTimeouts)-2] {
- c.AdvanceClock(t)
- c.ReadRetransmit()
- }
- // ACK the first record the shim ever sent. It will have
- // fallen off the queue by now, so it is expected to not
- // impact the shim's retransmissions.
- c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: records[0].DTLSRecordNumber}})
- c.AdvanceClock(useTimeouts[len(useTimeouts)-2])
- c.ReadRetransmit()
- }
- c.WriteFlight(next)
- },
- },
- },
- flags: slices.Concat(flags, []string{"-mtu", "256", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
- })
-
- // The shim should ignore ACKs for a previous flight, and not get its
- // internal state confused.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKPreviousFlight" + suffix,
- config: Config{
- MaxVersion: vers.version,
- DefaultCurves: []CurveID{}, // Force a HelloRetryRequest.
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[len(next)-1].Type == typeFinished {
- // We are now sending client Finished, in response
- // to the shim's ServerHello. ACK the shim's first
- // record, which would have been part of
- // HelloRetryRequest. This should not impact retransmit.
- c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: DTLSRecordNumber{Epoch: 0, Sequence: 0}}})
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
- }
- c.WriteFlight(next)
- },
- },
- },
- flags: flags,
- })
-
- // Records that contain a mix of discarded and processed fragments should
- // not be ACKed.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-DoNotACKDiscardedFragments" + suffix,
- config: Config{
- MaxVersion: vers.version,
- DefaultCurves: []CurveID{}, // Force a HelloRetryRequest.
- Bugs: ProtocolBugs{
- PackHandshakeFragments: 4096,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- // Send the flight, but combine every fragment with a far future
- // fragment, which the shim will discard. During the handshake,
- // the shim has enough information to reject this entirely, but
- // that would require coordinating with the handshake state
- // machine. Instead, BoringSSL discards the fragment and skips
- // ACKing the packet.
- //
- // runner implicitly tests that the shim ACKs the Finished flight
- // (or, in case, that it is does not), so this exercises the final
- // ACK.
- for _, msg := range next {
- shouldDiscard := DTLSFragment{Epoch: msg.Epoch, Sequence: 1000, ShouldDiscard: true}
- c.WriteFragments([]DTLSFragment{shouldDiscard, msg.Fragment(0, len(msg.Data))})
- // The shim has nothing to ACK and thus no ACK timer (which
- // would be 1/4 of this value).
- c.ExpectNextTimeout(useTimeouts[0])
- }
- },
- },
- },
- flags: flags,
- })
-
- // The server must continue to ACK the Finished flight even after
- // receiving application data from the client.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-Retransmit-Server-ACKFinishedAfterAppData" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- // WriteFlightDTLS will handle consuming ACKs.
- SkipImplicitACKRead: true,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[len(next)-1].Type != typeFinished {
- c.WriteFlight(next)
- return
- }
-
- // Write Finished. The shim should ACK it immediately.
- c.WriteFlight(next)
- c.ReadACK(c.InEpoch())
-
- // Exchange some application data.
- msg := []byte("hello")
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // Act as if the ACK was dropped and retransmit Finished.
- // The shim should process the retransmit from epoch 2 and
- // ACK, although it has already received data at epoch 3.
- c.WriteFlight(next)
- ackTimeout := useTimeouts[0] / 4
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // Partially retransmit Finished. The shim should continue
- // to ACK.
- c.WriteFragments([]DTLSFragment{next[0].Fragment(0, 1)})
- c.WriteFragments([]DTLSFragment{next[0].Fragment(1, 1)})
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // Eventually, the shim assumes we have received the ACK
- // and drops epoch 2. Retransmits now go unanswered.
- c.AdvanceClock(dtlsPrevEpochExpiration)
- c.WriteFlight(next)
- },
- },
- },
- // Disable tickets on the shim to avoid NewSessionTicket
- // interfering with the test callback.
- flags: slices.Concat(flags, []string{"-no-ticket"}),
- })
-
- // As a client, the shim must tolerate ACKs in response to its
- // initial ClientHello, but it will not process them because the
- // version is not yet known. The second ClientHello, in response
- // to HelloRetryRequest, however, is ACKed.
- //
- // The shim must additionally process ACKs and retransmit its
- // Finished flight, possibly interleaved with application data.
- // (The server may send half-RTT data without Finished.)
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Client" + suffix,
- config: Config{
- MaxVersion: vers.version,
- // Require a client certificate, so the Finished flight
- // is large.
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte("cookie"), // Send HelloRetryRequest
- MaxPacketLength: 512,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if len(received) == 0 || received[0].Type != typeClientHello {
- // We test post-handshake flights separately.
- c.WriteFlight(next)
- return
- }
-
- // This is either HelloRetryRequest in response to ClientHello1,
- // or ServerHello..Finished in response to ClientHello2.
- first := records[0]
- if len(prev) == 0 {
- // This is HelloRetryRequest in response to ClientHello1. The client
- // will accept the ACK, but it will ignore it. Do not expect
- // retransmits to be impacted.
- first.MessageStartSequence = 0
- first.MessageStartOffset = 0
- first.MessageEndSequence = 0
- first.MessageEndOffset = 0
- }
- c.WriteACK(0, []DTLSRecordNumberInfo{first})
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
- c.WriteFlight(next)
- },
- ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- // The shim will process application data without an ACK.
- msg := []byte("hello")
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // After a timeout, the shim will retransmit Finished.
- c.AdvanceClock(useTimeouts[0])
- c.ReadRetransmit()
-
- // Application data still flows.
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // ACK part of the flight and check that retransmits
- // are updated.
- c.WriteACK(c.OutEpoch(), records[len(records)/3:2*len(records)/3])
- c.AdvanceClock(useTimeouts[1])
- records = c.ReadRetransmit()
-
- // ACK the rest. Retransmits should stop.
- c.WriteACK(c.OutEpoch(), records)
- for _, t := range useTimeouts[2:] {
- c.AdvanceClock(t)
- }
- },
- },
- },
- shimCertificate: &rsaChainCertificate,
- flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
- })
-
- // If the client never receives an ACK for the Finished flight, it
- // is eventually fatal.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Client-FinishedTimeout" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- c.AdvanceClock(t)
- c.ReadRetransmit()
- }
- c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
- },
- },
- },
- flags: flags,
- shouldFail: true,
- expectedError: ":READ_TIMEOUT_EXPIRED:",
- })
-
- // Neither post-handshake messages nor application data implicitly
- // ACK the Finished flight. The server may have sent either in
- // half-RTT data. Test that the client continues to retransmit
- // despite this.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Client-NoImplictACKFinished" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- // Merge the Finished flight into the NewSessionTicket.
- c.MergeIntoNextFlight()
- },
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[0].Type != typeNewSessionTicket {
- c.WriteFlight(next)
- return
- }
- if len(received) == 0 || received[0].Type != typeFinished {
- panic("Finished should be merged with NewSessionTicket")
- }
- // Merge NewSessionTicket into the KeyUpdate.
- if next[len(next)-1].Type != typeKeyUpdate {
- c.MergeIntoNextFlight()
- return
- }
-
- // Write NewSessionTicket and the KeyUpdate and
- // read the ACK.
- c.WriteFlight(next)
- ackTimeout := useTimeouts[0] / 4
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // The retransmit timer is still running.
- c.AdvanceClock(useTimeouts[0] - ackTimeout)
- c.ReadRetransmit()
-
- // Application data can flow at the old epoch.
- msg := []byte("test")
- c.WriteAppData(c.OutEpoch()-1, msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // The retransmit timer is still running.
- c.AdvanceClock(useTimeouts[1])
- c.ReadRetransmit()
-
- // Advance the shim to the next epoch.
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // The retransmit timer is still running. The shim
- // actually could implicitly ACK at this point, but
- // RFC 9147 does not list this as an implicit ACK.
- c.AdvanceClock(useTimeouts[2])
- c.ReadRetransmit()
-
- // Finally ACK the final flight. Now the shim will
- // stop the timer.
- c.WriteACK(c.OutEpoch(), records)
- c.ExpectNoNextTimeout()
- },
- },
- },
- sendKeyUpdates: 1,
- keyUpdateRequest: keyUpdateNotRequested,
- flags: flags,
- })
-
- // If the server never receives an ACK for NewSessionTicket, it
- // is eventually fatal.
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "DTLS-Retransmit-Server-NewSessionTicketTimeout" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- if received[0].Type != typeNewSessionTicket {
- c.WriteACK(c.OutEpoch(), records)
- return
- }
- // Time the peer out.
- for _, t := range useTimeouts[:len(useTimeouts)-1] {
- c.AdvanceClock(t)
- c.ReadRetransmit()
- }
- c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
- }),
- },
- },
- flags: flags,
- shouldFail: true,
- expectedError: ":READ_TIMEOUT_EXPIRED:",
- })
-
- // If generating the reply to a flight takes time (generating a
- // CertificateVerify for a client certificate), the shim should
- // send an ACK.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-SlowReplyGeneration" + suffix,
- config: Config{
- MaxVersion: vers.version,
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- c.WriteFlight(next)
- if next[0].Type == typeServerHello {
- // The shim will reply with Certificate..Finished, but
- // take time to do so. In that time, it should schedule
- // an ACK so the runner knows not to retransmit.
- c.ReadACK(c.InEpoch())
- }
- },
- },
- },
- shimCertificate: &rsaCertificate,
- // Simulate it taking time to generate the reply.
- flags: slices.Concat(flags, []string{"-private-key-delay-ms", strconv.Itoa(int(useTimeouts[0].Milliseconds()))}),
- })
-
- // BoringSSL's ACK policy may schedule both retransmit and ACK
- // timers in parallel.
- //
- // TODO(crbug.com/42290594): This is only possible during the
- // handshake because we're willing to ACK old flights without
- // trying to distinguish these cases. However, post-handshake
- // messages will exercise this, so that may be a better version
- // of this test. In-handshake, it's kind of a waste to ACK this,
- // so maybe we should stop.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-BothTimers" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- // Arrange for there to be two server flights.
- SendHelloRetryRequestCookie: []byte("cookie"),
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[0].Sequence == 0 || next[0].Type != typeServerHello {
- // Send the first flight (HelloRetryRequest) as-is,
- // as well as any post-handshake flights.
- c.WriteFlight(next)
- return
- }
-
- // The shim just send the ClientHello2 and is
- // waiting for ServerHello..Finished. If it hears
- // nothing, it will retransmit ClientHello2 on the
- // assumption the packet was lost.
- c.ExpectNextTimeout(useTimeouts[0])
-
- // Retransmit a portion of HelloRetryRequest.
- c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)})
-
- // The shim does not actually need to ACK this,
- // but BoringSSL does. Now both timers are active.
- // Fire the first...
- c.ExpectNextTimeout(useTimeouts[0] / 4)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(0)
-
- // ...followed by the second.
- c.ExpectNextTimeout(3 * useTimeouts[0] / 4)
- c.AdvanceClock(3 * useTimeouts[0] / 4)
- c.ReadRetransmit()
-
- // The shim is now set for the next retransmit.
- c.ExpectNextTimeout(useTimeouts[1])
-
- // Start the ACK timer again.
- c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)})
- c.ExpectNextTimeout(useTimeouts[1] / 4)
-
- // Expire both timers at once.
- c.AdvanceClock(useTimeouts[1])
- c.ReadACK(0)
- c.ReadRetransmit()
-
- c.WriteFlight(next)
- },
- },
- },
- flags: flags,
- })
-
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Client-ACKPostHandshake" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[0].Type != typeNewSessionTicket {
- c.WriteFlight(next)
- return
- }
-
- // The test should try to send two NewSessionTickets in a row.
- if len(next) != 2 {
- panic("unexpected message count")
- }
-
- // Send part of first ticket post-handshake message.
- first0, second0 := next[0].Split(len(next[0].Data) / 2)
- first1, second1 := next[1].Split(len(next[1].Data) / 2)
- c.WriteFragments([]DTLSFragment{first0})
-
- // The shim should ACK on a timer.
- c.ExpectNextTimeout(useTimeouts[0] / 4)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(c.InEpoch())
-
- // The shim is just waiting for us to retransmit.
- c.ExpectNoNextTimeout()
-
- // Send some more fragments.
- c.WriteFragments([]DTLSFragment{first0, second1})
-
- // The shim should ACK, again on a timer.
- c.ExpectNextTimeout(useTimeouts[0] / 4)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(c.InEpoch())
- c.ExpectNoNextTimeout()
-
- // Finish up both messages. We implicitly test if shim
- // processed these messages by checking that it returned a new
- // session.
- c.WriteFragments([]DTLSFragment{first1, second0})
-
- // The shim should ACK again, once the timer expires.
- //
- // TODO(crbug.com/42290594): Should the shim ACK immediately?
- // Otherwise KeyUpdates are delayed, which will complicated
- // downstream testing.
- c.ExpectNextTimeout(useTimeouts[0] / 4)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(c.InEpoch())
- c.ExpectNoNextTimeout()
- },
- },
- },
- flags: flags,
- })
-
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "DTLS-Retransmit-Client-ACKPostHandshakeTwice" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[0].Type != typeNewSessionTicket {
- c.WriteFlight(next)
- return
- }
-
- // The test should try to send two NewSessionTickets in a row.
- if len(next) != 2 {
- panic("unexpected message count")
- }
-
- // Send the flight. The shim should ACK it.
- c.WriteFlight(next)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(c.InEpoch())
- c.ExpectNoNextTimeout()
-
- // Retransmit the flight, as if we lost the ACK. The shim should
- // ACK again.
- c.WriteFlight(next)
- c.AdvanceClock(useTimeouts[0] / 4)
- c.ReadACK(c.InEpoch())
- c.ExpectNoNextTimeout()
- },
- },
- },
- flags: flags,
- })
- }
- }
- }
-
- // Test that the final Finished retransmitting isn't
- // duplicated if the peer badly fragments everything.
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "DTLS-RetransmitFinished-Fragmented",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- MaxHandshakeRecordLength: 2,
- ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- c.WriteFlight(prev)
- c.ReadRetransmit()
- },
- },
- },
- flags: []string{"-async"},
- })
-
- // If the shim sends the last Finished (server full or client resume
- // handshakes), it must retransmit that Finished when it sees a
- // post-handshake penultimate Finished from the runner. The above tests
- // cover this. Conversely, if the shim sends the penultimate Finished
- // (client full or server resume), test that it does not retransmit.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: clientTest,
- name: "DTLS-StrayRetransmitFinished-ClientFull",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- c.WriteFlight(next)
- for _, msg := range next {
- if msg.Type == typeFinished {
- c.WriteFlight([]DTLSMessage{msg})
- }
- }
- },
- },
- },
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "DTLS-StrayRetransmitFinished-ServerResume",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- c.WriteFlight(next)
- for _, msg := range next {
- if msg.Type == typeFinished {
- c.WriteFlight([]DTLSMessage{msg})
- }
- }
- },
- },
- },
- resumeSession: true,
- })
-}
-
-func addDTLSReorderTests() {
- for _, vers := range allVersions(dtls) {
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "ReorderHandshakeFragments-Small-DTLS-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- ReorderHandshakeFragments: true,
- // Small enough that every handshake message is
- // fragmented.
- MaxHandshakeRecordLength: 2,
- },
- },
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "ReorderHandshakeFragments-Large-DTLS-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- ReorderHandshakeFragments: true,
- // Large enough that no handshake message is
- // fragmented.
- MaxHandshakeRecordLength: 2048,
- },
- },
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "MixCompleteMessageWithFragments-DTLS-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- ReorderHandshakeFragments: true,
- MixCompleteMessageWithFragments: true,
- MaxHandshakeRecordLength: 2,
- },
- },
- })
- }
-}
-
-func addExportKeyingMaterialTests() {
- for _, vers := range tlsVersions {
- testCases = append(testCases, testCase{
- name: "ExportKeyingMaterial-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- // Test the exporter in both initial and resumption
- // handshakes.
- resumeSession: true,
- exportKeyingMaterial: 1024,
- exportLabel: "label",
- exportContext: "context",
- useExportContext: true,
- })
- testCases = append(testCases, testCase{
- name: "ExportKeyingMaterial-NoContext-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- exportKeyingMaterial: 1024,
- })
- testCases = append(testCases, testCase{
- name: "ExportKeyingMaterial-EmptyContext-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- exportKeyingMaterial: 1024,
- useExportContext: true,
- })
- testCases = append(testCases, testCase{
- name: "ExportKeyingMaterial-Small-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- exportKeyingMaterial: 1,
- exportLabel: "label",
- exportContext: "context",
- useExportContext: true,
- })
-
- if vers.version >= VersionTLS13 {
- // Test the exporters do not work while the client is
- // sending 0-RTT data.
- testCases = append(testCases, testCase{
- name: "NoEarlyKeyingMaterial-Client-InEarlyData-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-on-resume-export-keying-material", "1024",
- "-on-resume-export-label", "label",
- "-on-resume-export-context", "context",
- },
- shouldFail: true,
- expectedError: ":HANDSHAKE_NOT_COMPLETE:",
- })
-
- // Test the normal exporter on the server in half-RTT.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ExportKeyingMaterial-Server-HalfRTT-" + vers.name,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- // The shim writes exported data immediately after
- // the handshake returns, so disable the built-in
- // early data test.
- SendEarlyData: [][]byte{},
- ExpectHalfRTTData: [][]byte{},
- },
- },
- resumeSession: true,
- earlyData: true,
- exportKeyingMaterial: 1024,
- exportLabel: "label",
- exportContext: "context",
- useExportContext: true,
- })
- }
- }
-
- // Exporters work during a False Start.
- testCases = append(testCases, testCase{
- name: "ExportKeyingMaterial-FalseStart",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- },
- },
- flags: []string{
- "-false-start",
- "-advertise-alpn", "\x03foo",
- "-expect-alpn", "foo",
- },
- shimWritesFirst: true,
- exportKeyingMaterial: 1024,
- exportLabel: "label",
- exportContext: "context",
- useExportContext: true,
- })
-
- // Exporters do not work in the middle of a renegotiation. Test this by
- // triggering the exporter after every SSL_read call and configuring the
- // shim to run asynchronously.
- testCases = append(testCases, testCase{
- name: "ExportKeyingMaterial-Renegotiate",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{
- "-async",
- "-use-exporter-between-reads",
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- shouldFail: true,
- expectedError: "failed to export keying material",
- })
-}
-
-func addExportTrafficSecretsTests() {
- for _, cipherSuite := range []testCipherSuite{
- // Test a SHA-256 and SHA-384 based cipher suite.
- {"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256},
- {"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384},
- } {
- testCases = append(testCases, testCase{
- name: "ExportTrafficSecrets-" + cipherSuite.name,
- config: Config{
- MinVersion: VersionTLS13,
- CipherSuites: []uint16{cipherSuite.id},
- },
- exportTrafficSecrets: true,
- })
- }
-}
-
-func addTLSUniqueTests() {
- for _, isClient := range []bool{false, true} {
- for _, isResumption := range []bool{false, true} {
- for _, hasEMS := range []bool{false, true} {
- var suffix string
- if isResumption {
- suffix = "Resume-"
- } else {
- suffix = "Full-"
- }
-
- if hasEMS {
- suffix += "EMS-"
- } else {
- suffix += "NoEMS-"
- }
-
- if isClient {
- suffix += "Client"
- } else {
- suffix += "Server"
- }
-
- test := testCase{
- name: "TLSUnique-" + suffix,
- testTLSUnique: true,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecret: !hasEMS,
- },
- },
- }
-
- if isResumption {
- test.resumeSession = true
- test.resumeConfig = &Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- NoExtendedMasterSecret: !hasEMS,
- },
- }
- }
-
- if isResumption && !hasEMS {
- test.shouldFail = true
- test.expectedError = "failed to get tls-unique"
- }
-
- testCases = append(testCases, test)
- }
- }
- }
-}
-
-func addCustomExtensionTests() {
- // Test an unknown extension from the server.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "UnknownExtension-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- CustomExtension: "custom extension",
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "UnknownExtension-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- CustomExtension: "custom extension",
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "UnknownUnencryptedExtension-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- CustomUnencryptedExtension: "custom extension",
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- // The shim must send an alert, but alerts at this point do not
- // get successfully decrypted by the runner.
- expectedLocalError: "local error: bad record MAC",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "UnexpectedUnencryptedExtension-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendUnencryptedALPN: "foo",
- },
- },
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- // The shim must send an alert, but alerts at this point do not
- // get successfully decrypted by the runner.
- expectedLocalError: "local error: bad record MAC",
- })
-
- // Test a known but unoffered extension from the server.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "UnofferedExtension-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendALPN: "alpn",
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "UnofferedExtension-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendALPN: "alpn",
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-}
-
-func addRSAClientKeyExchangeTests() {
- for bad := RSABadValue(1); bad < NumRSABadValues; bad++ {
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: fmt.Sprintf("BadRSAClientKeyExchange-%d", bad),
- config: Config{
- // Ensure the ClientHello version and final
- // version are different, to detect if the
- // server uses the wrong one.
- MaxVersion: VersionTLS11,
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
- Bugs: ProtocolBugs{
- BadRSAClientKeyExchange: bad,
- },
- },
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
- }
-
- // The server must compare whatever was in ClientHello.version for the
- // RSA premaster.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SendClientVersion-RSA",
- config: Config{
- CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SendClientVersion: 0x1234,
- },
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- })
-}
-
-var testCurves = []struct {
- name string
- id CurveID
-}{
- {"P-224", CurveP224},
- {"P-256", CurveP256},
- {"P-384", CurveP384},
- {"P-521", CurveP521},
- {"X25519", CurveX25519},
- {"Kyber", CurveX25519Kyber768},
- {"MLKEM", CurveX25519MLKEM768},
-}
-
-const bogusCurve = 0x1234
-
-func isPqGroup(r CurveID) bool {
- return r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
-}
-
-func isECDHGroup(r CurveID) bool {
- return r == CurveP224 || r == CurveP256 || r == CurveP384 || r == CurveP521
-}
-
-func isX25519Group(r CurveID) bool {
- return r == CurveX25519 || r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
-}
-
-func addCurveTests() {
- // A set of cipher suites that ensures some curve-using mode is used.
- // Without this, servers may fall back to RSA key exchange.
- ecdheCiphers := []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_AES_256_GCM_SHA384,
- }
-
- for _, curve := range testCurves {
- for _, ver := range tlsVersions {
- if isPqGroup(curve.id) && ver.version < VersionTLS13 {
- continue
- }
- for _, testType := range []testType{clientTest, serverTest} {
- suffix := fmt.Sprintf("%s-%s-%s", testType, curve.name, ver.name)
-
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- },
- flags: append(
- []string{"-expect-curve-id", strconv.Itoa(int(curve.id))},
- flagInts("-curves", shimConfig.AllCurves)...,
- ),
- expectations: connectionExpectations{
- curveID: curve.id,
- },
- })
-
- badKeyShareLocalError := "remote error: illegal parameter"
- if testType == clientTest && ver.version >= VersionTLS13 {
- // If the shim is a TLS 1.3 client and the runner sends a bad
- // key share, the runner never reads the client's cleartext
- // alert because the runner has already started encrypting by
- // the time the client sees it.
- badKeyShareLocalError = "local error: bad record MAC"
- }
-
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-Invalid-TruncateKeyShare-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- TruncateKeyShare: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- expectedLocalError: badKeyShareLocalError,
- })
-
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-Invalid-PadKeyShare-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- PadKeyShare: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- expectedLocalError: badKeyShareLocalError,
- })
-
- if isECDHGroup(curve.id) {
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-Invalid-Compressed-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- SendCompressedCoordinates: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- expectedLocalError: badKeyShareLocalError,
- })
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-Invalid-NotOnCurve-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- ECDHPointNotOnCurve: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- expectedLocalError: badKeyShareLocalError,
- })
- }
-
- if isX25519Group(curve.id) {
- // Implementations should mask off the high order bit in X25519.
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-SetX25519HighBit-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- SetX25519HighBit: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- expectations: connectionExpectations{
- curveID: curve.id,
- },
- })
-
- // Implementations should reject low order points.
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-Invalid-LowOrderX25519Point-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- LowOrderX25519Point: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- expectedLocalError: badKeyShareLocalError,
- })
- }
-
- if curve.id == CurveX25519MLKEM768 && testType == serverTest {
- testCases = append(testCases, testCase{
- testType: testType,
- name: "CurveTest-Invalid-MLKEMEncapKeyNotReduced-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: ecdheCiphers,
- CurvePreferences: []CurveID{curve.id},
- Bugs: ProtocolBugs{
- MLKEMEncapKeyNotReduced: true,
- },
- },
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- expectedLocalError: badKeyShareLocalError,
- })
- }
- }
- }
- }
-
- // The server must be tolerant to bogus curves.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "UnknownCurve",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- CurvePreferences: []CurveID{bogusCurve, CurveP256},
- },
- })
-
- // The server must be tolerant to bogus curves.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "UnknownCurve-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{bogusCurve, CurveP256},
- },
- })
-
- // The server must not consider ECDHE ciphers when there are no
- // supported curves.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoSupportedCurves",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- NoSupportedCurves: true,
- },
- },
- shouldFail: true,
- expectedError: ":NO_SHARED_CIPHER:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoSupportedCurves-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- NoSupportedCurves: true,
- },
- },
- shouldFail: true,
- expectedError: ":NO_SHARED_GROUP:",
- })
-
- // The server must fall back to another cipher when there are no
- // supported curves.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "NoCommonCurves",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- CurvePreferences: []CurveID{CurveP224},
- },
- expectations: connectionExpectations{
- cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- })
-
- // The client must reject bogus curves and disabled curves.
- testCases = append(testCases, testCase{
- name: "BadECDHECurve",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- Bugs: ProtocolBugs{
- SendCurve: bogusCurve,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
- testCases = append(testCases, testCase{
- name: "BadECDHECurve-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendCurve: bogusCurve,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- name: "UnsupportedCurve",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- CurvePreferences: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- IgnorePeerCurvePreferences: true,
- },
- },
- flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- // TODO(davidben): Add a TLS 1.3 version where
- // HelloRetryRequest requests an unsupported curve.
- name: "UnsupportedCurve-ServerHello-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendCurve: CurveP256,
- },
- },
- flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- // The previous curve ID should be reported on TLS 1.2 resumption.
- testCases = append(testCases, testCase{
- name: "CurveID-Resume-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- CurvePreferences: []CurveID{CurveX25519},
- },
- flags: []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))},
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CurveID-Resume-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- CurvePreferences: []CurveID{CurveX25519},
- },
- flags: []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))},
- resumeSession: true,
- })
-
- // TLS 1.3 allows resuming at a differet curve. If this happens, the new
- // one should be reported.
- testCases = append(testCases, testCase{
- name: "CurveID-Resume-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveX25519},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP256},
- },
- flags: []string{
- "-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)),
- "-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)),
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CurveID-Resume-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveX25519},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP256},
- },
- flags: []string{
- "-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)),
- "-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)),
- },
- resumeSession: true,
- })
-
- // Server-sent point formats are legal in TLS 1.2, but not in TLS 1.3.
- testCases = append(testCases, testCase{
- name: "PointFormat-ServerHello-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{pointFormatUncompressed},
- },
- },
- })
- testCases = append(testCases, testCase{
- name: "PointFormat-EncryptedExtensions-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{pointFormatUncompressed},
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- // Server-sent supported groups/curves are legal in TLS 1.3. They are
- // illegal in TLS 1.2, but some servers send them anyway, so we must
- // tolerate them.
- testCases = append(testCases, testCase{
- name: "SupportedCurves-ServerHello-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendServerSupportedCurves: true,
- },
- },
- })
- testCases = append(testCases, testCase{
- name: "SupportedCurves-EncryptedExtensions-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendServerSupportedCurves: true,
- },
- },
- })
-
- // Test that we tolerate unknown point formats, as long as
- // pointFormatUncompressed is present. Limit ciphers to ECDHE ciphers to
- // check they are still functional.
- testCases = append(testCases, testCase{
- name: "PointFormat-Client-Tolerance",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime},
- },
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PointFormat-Server-Tolerance",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime},
- },
- },
- })
-
- // Test TLS 1.2 does not require the point format extension to be
- // present.
- testCases = append(testCases, testCase{
- name: "PointFormat-Client-Missing",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{},
- },
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PointFormat-Server-Missing",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{},
- },
- },
- })
-
- // If the point format extension is present, uncompressed points must be
- // offered. BoringSSL requires this whether or not ECDHE is used.
- testCases = append(testCases, testCase{
- name: "PointFormat-Client-MissingUncompressed",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{pointFormatCompressedPrime},
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PointFormat-Server-MissingUncompressed",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendSupportedPointFormats: []byte{pointFormatCompressedPrime},
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- // Post-quantum groups require TLS 1.3.
- for _, curve := range testCurves {
- if !isPqGroup(curve.id) {
- continue
- }
-
- // Post-quantum groups should not be offered by a TLS 1.2 client.
- testCases = append(testCases, testCase{
- name: "TLS12ClientShouldNotOffer-" + curve.name,
- config: Config{
- Bugs: ProtocolBugs{
- FailIfPostQuantumOffered: true,
- },
- },
- flags: []string{
- "-max-version", strconv.Itoa(VersionTLS12),
- "-curves", strconv.Itoa(int(curve.id)),
- "-curves", strconv.Itoa(int(CurveX25519)),
- },
- })
-
- // Post-quantum groups should not be selected by a TLS 1.2 server.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS12ServerShouldNotSelect-" + curve.name,
- flags: []string{
- "-max-version", strconv.Itoa(VersionTLS12),
- "-curves", strconv.Itoa(int(curve.id)),
- "-curves", strconv.Itoa(int(CurveX25519)),
- },
- expectations: connectionExpectations{
- curveID: CurveX25519,
- },
- })
-
- // If a TLS 1.2 server selects a post-quantum group anyway, the client
- // should not accept it.
- testCases = append(testCases, testCase{
- name: "ClientShouldNotAllowInTLS12-" + curve.name,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendCurve: curve.id,
- },
- },
- flags: []string{
- "-curves", strconv.Itoa(int(curve.id)),
- "-curves", strconv.Itoa(int(CurveX25519)),
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- expectedLocalError: "remote error: illegal parameter",
- })
- }
-
- // ML-KEM and Kyber should not be offered by default as a client.
- testCases = append(testCases, testCase{
- name: "PostQuantumNotEnabledByDefaultInClients",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- FailIfPostQuantumOffered: true,
- },
- },
- })
-
- // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share.
- testCases = append(testCases, testCase{
- name: "NotJustMLKEMKeyShare",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519},
- },
- },
- flags: []string{
- "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
- "-curves", strconv.Itoa(int(CurveX25519)),
- "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
- },
- })
-
- // ... and the other way around
- testCases = append(testCases, testCase{
- name: "MLKEMKeyShareIncludedSecond",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768},
- },
- },
- flags: []string{
- "-curves", strconv.Itoa(int(CurveX25519)),
- "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
- "-expect-curve-id", strconv.Itoa(int(CurveX25519)),
- },
- })
-
- // ... and even if there's another curve in the middle because it's the
- // first classical and first post-quantum "curves" that get key shares
- // included.
- testCases = append(testCases, testCase{
- name: "MLKEMKeyShareIncludedThird",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768},
- },
- },
- flags: []string{
- "-curves", strconv.Itoa(int(CurveX25519)),
- "-curves", strconv.Itoa(int(CurveP256)),
- "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
- "-expect-curve-id", strconv.Itoa(int(CurveX25519)),
- },
- })
-
- // If ML-KEM is the only configured curve, the key share is sent.
- testCases = append(testCases, testCase{
- name: "JustConfiguringMLKEMWorks",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectedKeyShares: []CurveID{CurveX25519MLKEM768},
- },
- },
- flags: []string{
- "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
- "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
- },
- })
-
- // If both ML-KEM and Kyber are configured, only the preferred one's
- // key share should be sent.
- testCases = append(testCases, testCase{
- name: "BothMLKEMAndKyber",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectedKeyShares: []CurveID{CurveX25519MLKEM768},
- },
- },
- flags: []string{
- "-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
- "-curves", strconv.Itoa(int(CurveX25519Kyber768)),
- "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
- },
- })
-
- // As a server, ML-KEM is not yet supported by default.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PostQuantumNotEnabledByDefaultForAServer",
- config: Config{
- MinVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768, CurveX25519},
- DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768},
- },
- flags: []string{
- "-server-preference",
- "-expect-curve-id", strconv.Itoa(int(CurveX25519)),
- },
- })
-
- // In TLS 1.2, the curve list is also used to signal ECDSA curves.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CheckECDSACurve-TLS12",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- CurvePreferences: []CurveID{CurveP384},
- },
- shimCertificate: &ecdsaP256Certificate,
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- // If the ECDSA certificate is ineligible due to a curve mismatch, the
- // server may still consider a PSK cipher suite.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CheckECDSACurve-PSK-TLS12",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
- },
- CurvePreferences: []CurveID{CurveP384},
- PreSharedKey: []byte("12345"),
- PreSharedKeyIdentity: "luggage combo",
- },
- shimCertificate: &ecdsaP256Certificate,
- expectations: connectionExpectations{
- cipher: TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
- },
- flags: []string{
- "-psk", "12345",
- "-psk-identity", "luggage combo",
- },
- })
-
- // In TLS 1.3, the curve list only controls ECDH.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CheckECDSACurve-NotApplicable-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP384},
- },
- shimCertificate: &ecdsaP256Certificate,
- })
-}
-
-func addTLS13RecordTests() {
- for _, protocol := range []protocol{tls, dtls} {
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "TLS13-RecordPadding-" + protocol.String(),
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RecordPadding: 10,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "TLS13-EmptyRecords-" + protocol.String(),
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- OmitRecordContents: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
-
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "TLS13-OnlyPadding-" + protocol.String(),
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- OmitRecordContents: true,
- RecordPadding: 10,
- },
- },
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
-
- if protocol == tls {
- testCases = append(testCases, testCase{
- protocol: protocol,
- name: "TLS13-WrongOuterRecord-" + protocol.String(),
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- OuterRecordType: recordTypeHandshake,
- },
- },
- shouldFail: true,
- expectedError: ":INVALID_OUTER_RECORD_TYPE:",
- })
- }
- }
-}
-
-func addSessionTicketTests() {
- testCases = append(testCases, testCase{
- // In TLS 1.2 and below, empty NewSessionTicket messages
- // mean the server changed its mind on sending a ticket.
- name: "SendEmptySessionTicket-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendEmptySessionTicket: true,
- },
- },
- flags: []string{"-expect-no-session"},
- })
-
- testCases = append(testCases, testCase{
- // In TLS 1.3, empty NewSessionTicket messages are not
- // allowed.
- name: "SendEmptySessionTicket-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEmptySessionTicket: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- // Test that the server ignores unknown PSK modes.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-SendUnknownModeSessionTicket-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendPSKKeyExchangeModes: []byte{0x1a, pskDHEKEMode, 0x2a},
- },
- },
- resumeSession: true,
- expectations: connectionExpectations{
- version: VersionTLS13,
- },
- })
-
- // Test that the server does not send session tickets with no matching key exchange mode.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-ExpectNoSessionTicketOnBadKEMode-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendPSKKeyExchangeModes: []byte{0x1a},
- ExpectNoNewSessionTicket: true,
- },
- },
- })
-
- // Test that the server does not accept a session with no matching key exchange mode.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-SendBadKEModeSessionTicket-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendPSKKeyExchangeModes: []byte{0x1a},
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
-
- // Test that the server rejects ClientHellos with pre_shared_key but without
- // psk_key_exchange_modes.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-SendNoKEMModesWithPSK-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendPSKKeyExchangeModes: []byte{},
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedLocalError: "remote error: missing extension",
- expectedError: ":MISSING_EXTENSION:",
- })
-
- // Test that the client ticket age is sent correctly.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-TestValidTicketAge-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectTicketAge: 10 * time.Second,
- },
- },
- resumeSession: true,
- flags: []string{
- "-resumption-delay", "10",
- },
- })
-
- // Test that the client ticket age is enforced.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-TestBadTicketAge-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectTicketAge: 1000 * time.Second,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedLocalError: "tls: invalid ticket age",
- })
-
- // Test that the server's ticket age skew reporting works.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-TicketAgeSkew-Forward",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketAge: 15 * time.Second,
- },
- },
- resumeSession: true,
- resumeRenewedSession: true,
- flags: []string{
- "-resumption-delay", "10",
- "-expect-ticket-age-skew", "5",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-TicketAgeSkew-Backward",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketAge: 5 * time.Second,
- },
- },
- resumeSession: true,
- resumeRenewedSession: true,
- flags: []string{
- "-resumption-delay", "10",
- "-expect-ticket-age-skew", "-5",
- },
- })
-
- // Test that ticket age skew up to 60 seconds in either direction is accepted.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-TicketAgeSkew-Forward-60-Accept",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketAge: 70 * time.Second,
- },
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-resumption-delay", "10",
- "-expect-ticket-age-skew", "60",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-TicketAgeSkew-Backward-60-Accept",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketAge: 10 * time.Second,
- },
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-resumption-delay", "70",
- "-expect-ticket-age-skew", "-60",
- },
- })
-
- // Test that ticket age skew beyond 60 seconds in either direction is rejected.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-TicketAgeSkew-Forward-61-Reject",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketAge: 71 * time.Second,
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-resumption-delay", "10",
- "-expect-ticket-age-skew", "61",
- "-on-resume-expect-early-data-reason", "ticket_age_skew",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-TicketAgeSkew-Backward-61-Reject",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketAge: 10 * time.Second,
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-resumption-delay", "71",
- "-expect-ticket-age-skew", "-61",
- "-on-resume-expect-early-data-reason", "ticket_age_skew",
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-SendTicketEarlyDataSupport",
- config: Config{
- MaxVersion: VersionTLS13,
- MaxEarlyDataSize: 16384,
- },
- flags: []string{
- "-enable-early-data",
- "-expect-ticket-supports-early-data",
- },
- })
-
- // Test that 0-RTT tickets are still recorded as such when early data is disabled overall.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-SendTicketEarlyDataSupport-Disabled",
- config: Config{
- MaxVersion: VersionTLS13,
- MaxEarlyDataSize: 16384,
- },
- flags: []string{
- "-expect-ticket-supports-early-data",
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-DuplicateTicketEarlyDataSupport",
- config: Config{
- MaxVersion: VersionTLS13,
- MaxEarlyDataSize: 16384,
- Bugs: ProtocolBugs{
- DuplicateTicketEarlyData: true,
- },
- },
- shouldFail: true,
- expectedError: ":DUPLICATE_EXTENSION:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-ExpectTicketEarlyDataSupport",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectTicketEarlyData: true,
- },
- },
- flags: []string{
- "-enable-early-data",
- },
- })
-
- // Test that, in TLS 1.3, the server-offered NewSessionTicket lifetime
- // is honored.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-HonorServerSessionTicketLifetime",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketLifetime: 20 * time.Second,
- },
- },
- flags: []string{
- "-resumption-delay", "19",
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13-HonorServerSessionTicketLifetime-2",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendTicketLifetime: 20 * time.Second,
- // The client should not offer the expired session.
- ExpectNoTLS13PSK: true,
- },
- },
- flags: []string{
- "-resumption-delay", "21",
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
-
- for _, ver := range tlsVersions {
- // Prior to TLS 1.3, disabling session tickets enables session IDs.
- useStatefulResumption := ver.version < VersionTLS13
-
- // SSL_OP_NO_TICKET implies the server must not mint any tickets.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: ver.name + "-NoTicket-NoMint",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectNoNewSessionTicket: true,
- RequireSessionIDs: useStatefulResumption,
- },
- },
- resumeSession: useStatefulResumption,
- flags: []string{"-no-ticket"},
- })
-
- // SSL_OP_NO_TICKET implies the server must not accept any tickets.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: ver.name + "-NoTicket-NoAccept",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- resumeSession: true,
- expectResumeRejected: true,
- // Set SSL_OP_NO_TICKET on the second connection, after the first
- // has established tickets.
- flags: []string{"-on-resume-no-ticket"},
- })
-
- // SSL_OP_NO_TICKET implies the client must not offer ticket-based
- // sessions. The client not only should not send the session ticket
- // extension, but if the server echos the session ID, the client should
- // reject this.
- if ver.version < VersionTLS13 {
- testCases = append(testCases, testCase{
- name: ver.name + "-NoTicket-NoOffer",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- resumeConfig: &Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- ExpectNoTLS12TicketSupport: true,
- // Pretend to accept the session, even though the client
- // did not offer it. The client should reject this as
- // invalid. A buggy client will still fail because it
- // expects resumption, but with a different error.
- // Ideally, we would test this by actually resuming the
- // previous session, even though the client did not
- // provide a ticket.
- EchoSessionIDInFullHandshake: true,
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- // Set SSL_OP_NO_TICKET on the second connection, after the first
- // has established tickets.
- flags: []string{"-on-resume-no-ticket"},
- shouldFail: true,
- expectedError: ":SERVER_ECHOED_INVALID_SESSION_ID:",
- expectedLocalError: "remote error: illegal parameter",
- })
- }
- }
-}
-
-func addChangeCipherSpecTests() {
- // Test missing ChangeCipherSpecs.
- testCases = append(testCases, testCase{
- name: "SkipChangeCipherSpec-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SkipChangeCipherSpec: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipChangeCipherSpec-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SkipChangeCipherSpec: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipChangeCipherSpec-Server-NPN",
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"bar"},
- Bugs: ProtocolBugs{
- SkipChangeCipherSpec: true,
- },
- },
- flags: []string{
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
-
- // Test synchronization between the handshake and ChangeCipherSpec.
- // Partial post-CCS handshake messages before ChangeCipherSpec should be
- // rejected. Test both with and without handshake packing to handle both
- // when the partial post-CCS message is in its own record and when it is
- // attached to the pre-CCS message.
- for _, packed := range []bool{false, true} {
- var suffix string
- if packed {
- suffix = "-Packed"
- }
-
- testCases = append(testCases, testCase{
- name: "FragmentAcrossChangeCipherSpec-Client" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- FragmentAcrossChangeCipherSpec: true,
- PackHandshakeFlight: packed,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- name: "FragmentAcrossChangeCipherSpec-Client-Resume" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeSession: true,
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- FragmentAcrossChangeCipherSpec: true,
- PackHandshakeFlight: packed,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "FragmentAcrossChangeCipherSpec-Server" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- FragmentAcrossChangeCipherSpec: true,
- PackHandshakeFlight: packed,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "FragmentAcrossChangeCipherSpec-Server-Resume" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeSession: true,
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- FragmentAcrossChangeCipherSpec: true,
- PackHandshakeFlight: packed,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "FragmentAcrossChangeCipherSpec-Server-NPN" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"bar"},
- Bugs: ProtocolBugs{
- FragmentAcrossChangeCipherSpec: true,
- PackHandshakeFlight: packed,
- },
- },
- flags: []string{
- "-advertise-npn", "\x03foo\x03bar\x03baz",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- }
-
- // In TLS 1.2 resumptions, the client sends ClientHello in the first flight
- // and ChangeCipherSpec + Finished in the second flight. Test the server's
- // behavior when the Finished message is fragmented across not only
- // ChangeCipherSpec but also the flight boundary.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PartialClientFinishedWithClientHello-TLS12-Resume",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- PartialClientFinishedWithClientHello: true,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // In TLS 1.2 full handshakes without tickets, the server's first flight ends
- // with ServerHelloDone and the second flight is ChangeCipherSpec + Finished.
- // Test the client's behavior when the Finished message is fragmented across
- // not only ChangeCipherSpec but also the flight boundary.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "PartialFinishedWithServerHelloDone",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- PartialFinishedWithServerHelloDone: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Test that, in DTLS 1.2, key changes are not allowed when there are
- // buffered messages. Do this sending all messages in reverse, so that later
- // ones are buffered, and leaving Finished unencrypted.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "KeyChangeWithBufferedMessages-DTLS",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- next = slices.Clone(next)
- slices.Reverse(next)
- for i := range next {
- next[i].Epoch = 0
- }
- c.WriteFlight(next)
- },
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- })
-
- // Test synchronization between encryption changes and the handshake in
- // TLS 1.3, where ChangeCipherSpec is implicit.
- testCases = append(testCases, testCase{
- name: "PartialEncryptedExtensionsWithServerHello",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- PartialEncryptedExtensionsWithServerHello: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PartialClientFinishedWithClientHello",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- PartialClientFinishedWithClientHello: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PartialClientFinishedWithSecondClientHello",
- config: Config{
- MaxVersion: VersionTLS13,
- // Trigger a curve-based HelloRetryRequest.
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- PartialClientFinishedWithSecondClientHello: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PartialEndOfEarlyDataWithClientHello",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- PartialEndOfEarlyDataWithClientHello: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- })
-
- // Test that early ChangeCipherSpecs are handled correctly.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyChangeCipherSpec-server-1",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- EarlyChangeCipherSpec: 1,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyChangeCipherSpec-server-2",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- EarlyChangeCipherSpec: 2,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "StrayChangeCipherSpec",
- config: Config{
- // TODO(davidben): Once DTLS 1.3 exists, test
- // that stray ChangeCipherSpec messages are
- // rejected.
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- c.WriteFragments([]DTLSFragment{{IsChangeCipherSpec: true, Data: []byte{1}}})
- c.WriteFlight(next)
- },
- },
- },
- })
-
- // Test that the contents of ChangeCipherSpec are checked.
- testCases = append(testCases, testCase{
- name: "BadChangeCipherSpec-1",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadChangeCipherSpec: []byte{2},
- },
- },
- shouldFail: true,
- expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
- })
- testCases = append(testCases, testCase{
- name: "BadChangeCipherSpec-2",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadChangeCipherSpec: []byte{1, 1},
- },
- },
- shouldFail: true,
- expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "BadChangeCipherSpec-DTLS-1",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadChangeCipherSpec: []byte{2},
- },
- },
- shouldFail: true,
- expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "BadChangeCipherSpec-DTLS-2",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- BadChangeCipherSpec: []byte{1, 1},
- },
- },
- shouldFail: true,
- expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
- })
-}
-
-// addEndOfFlightTests adds tests where the runner adds extra data in the final
-// record of each handshake flight. Depending on the implementation strategy,
-// this data may be carried over to the next flight (assuming no key change) or
-// may be rejected. To avoid differences with split handshakes and generally
-// reject misbehavior, BoringSSL treats this as an error. When possible, these
-// tests pull the extra data from the subsequent flight to distinguish the data
-// being carried over from a general syntax error.
-//
-// These tests are similar to tests in |addChangeCipherSpecTests| that send
-// extra data at key changes. Not all key changes are at the end of a flight and
-// not all flights end at a key change.
-func addEndOfFlightTests() {
- // TLS 1.3 client handshakes.
- //
- // Data following the second TLS 1.3 ClientHello is covered by
- // PartialClientFinishedWithClientHello,
- // PartialClientFinishedWithSecondClientHello, and
- // PartialEndOfEarlyDataWithClientHello in |addChangeCipherSpecTests|.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PartialSecondClientHelloAfterFirst",
- config: Config{
- MaxVersion: VersionTLS13,
- // Trigger a curve-based HelloRetryRequest.
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- PartialSecondClientHelloAfterFirst: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // TLS 1.3 server handshakes.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "PartialServerHelloWithHelloRetryRequest",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- PartialServerHelloWithHelloRetryRequest: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // TLS 1.2 client handshakes.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "PartialClientKeyExchangeWithClientHello",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- PartialClientKeyExchangeWithClientHello: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // TLS 1.2 server handshakes.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "PartialNewSessionTicketWithServerHelloDone",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- PartialNewSessionTicketWithServerHelloDone: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- for _, vers := range tlsVersions {
- for _, testType := range []testType{clientTest, serverTest} {
- suffix := "-Client"
- if testType == serverTest {
- suffix = "-Server"
- }
- suffix += "-" + vers.name
-
- testCases = append(testCases, testCase{
- testType: testType,
- name: "TrailingDataWithFinished" + suffix,
- config: Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- TrailingDataWithFinished: true,
- },
- },
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
- testCases = append(testCases, testCase{
- testType: testType,
- name: "TrailingDataWithFinished-Resume" + suffix,
- config: Config{
- MaxVersion: vers.version,
- },
- resumeConfig: &Config{
- MaxVersion: vers.version,
- Bugs: ProtocolBugs{
- TrailingDataWithFinished: true,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
- }
- }
-}
-
-type perMessageTest struct {
- messageType uint8
- test testCase
-}
-
-// makePerMessageTests returns a series of test templates which cover each
-// message in the TLS handshake. These may be used with bugs like
-// WrongMessageType to fully test a per-message bug.
-func makePerMessageTests() []perMessageTest {
- var ret []perMessageTest
- // The following tests are limited to TLS 1.2, so QUIC is not tested.
- for _, protocol := range []protocol{tls, dtls} {
- suffix := "-" + protocol.String()
-
- ret = append(ret, perMessageTest{
- messageType: typeClientHello,
- test: testCase{
- protocol: protocol,
- testType: serverTest,
- name: "ClientHello" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- if protocol == dtls {
- ret = append(ret, perMessageTest{
- messageType: typeHelloVerifyRequest,
- test: testCase{
- protocol: protocol,
- name: "HelloVerifyRequest" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
- }
-
- ret = append(ret, perMessageTest{
- messageType: typeServerHello,
- test: testCase{
- protocol: protocol,
- name: "ServerHello" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificate,
- test: testCase{
- protocol: protocol,
- name: "ServerCertificate" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificateStatus,
- test: testCase{
- protocol: protocol,
- name: "CertificateStatus" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- Credential: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- flags: []string{"-enable-ocsp-stapling"},
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeServerKeyExchange,
- test: testCase{
- protocol: protocol,
- name: "ServerKeyExchange" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificateRequest,
- test: testCase{
- protocol: protocol,
- name: "CertificateRequest" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- ClientAuth: RequireAnyClientCert,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeServerHelloDone,
- test: testCase{
- protocol: protocol,
- name: "ServerHelloDone" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificate,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "ClientCertificate" + suffix,
- config: Config{
- Credential: &rsaCertificate,
- MaxVersion: VersionTLS12,
- },
- flags: []string{"-require-any-client-certificate"},
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificateVerify,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "CertificateVerify" + suffix,
- config: Config{
- Credential: &rsaCertificate,
- MaxVersion: VersionTLS12,
- },
- flags: []string{"-require-any-client-certificate"},
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeClientKeyExchange,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "ClientKeyExchange" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- if protocol != dtls {
- ret = append(ret, perMessageTest{
- messageType: typeNextProtocol,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "NextProtocol" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- NextProtos: []string{"bar"},
- },
- flags: []string{"-advertise-npn", "\x03foo\x03bar\x03baz"},
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeChannelID,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "ChannelID" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- ChannelID: &channelIDKey,
- },
- flags: []string{
- "-expect-channel-id",
- base64FlagValue(channelIDBytes),
- },
- },
- })
- }
-
- ret = append(ret, perMessageTest{
- messageType: typeFinished,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "ClientFinished" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeNewSessionTicket,
- test: testCase{
- protocol: protocol,
- name: "NewSessionTicket" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeFinished,
- test: testCase{
- protocol: protocol,
- name: "ServerFinished" + suffix,
- config: Config{
- MaxVersion: VersionTLS12,
- },
- },
- })
-
- }
-
- for _, protocol := range []protocol{tls, quic, dtls} {
- suffix := "-" + protocol.String()
- ret = append(ret, perMessageTest{
- messageType: typeClientHello,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "TLS13-ClientHello" + suffix,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeServerHello,
- test: testCase{
- name: "TLS13-ServerHello" + suffix,
- protocol: protocol,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeEncryptedExtensions,
- test: testCase{
- name: "TLS13-EncryptedExtensions" + suffix,
- protocol: protocol,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificateRequest,
- test: testCase{
- name: "TLS13-CertificateRequest" + suffix,
- protocol: protocol,
- config: Config{
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificate,
- test: testCase{
- name: "TLS13-ServerCertificate" + suffix,
- protocol: protocol,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificateVerify,
- test: testCase{
- name: "TLS13-ServerCertificateVerify" + suffix,
- protocol: protocol,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeFinished,
- test: testCase{
- name: "TLS13-ServerFinished" + suffix,
- protocol: protocol,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificate,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "TLS13-ClientCertificate" + suffix,
- config: Config{
- Credential: &rsaCertificate,
- MaxVersion: VersionTLS13,
- },
- flags: []string{"-require-any-client-certificate"},
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeCertificateVerify,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "TLS13-ClientCertificateVerify" + suffix,
- config: Config{
- Credential: &rsaCertificate,
- MaxVersion: VersionTLS13,
- },
- flags: []string{"-require-any-client-certificate"},
- },
- })
-
- ret = append(ret, perMessageTest{
- messageType: typeFinished,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "TLS13-ClientFinished" + suffix,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- },
- })
-
- // Only TLS uses EndOfEarlyData.
- if protocol == tls {
- ret = append(ret, perMessageTest{
- messageType: typeEndOfEarlyData,
- test: testCase{
- testType: serverTest,
- protocol: protocol,
- name: "TLS13-EndOfEarlyData" + suffix,
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- },
- })
- }
- }
-
- return ret
-}
-
-func addWrongMessageTypeTests() {
- for _, t := range makePerMessageTests() {
- t.test.name = "WrongMessageType-" + t.test.name
- if t.test.resumeConfig != nil {
- t.test.resumeConfig.Bugs.SendWrongMessageType = t.messageType
- } else {
- t.test.config.Bugs.SendWrongMessageType = t.messageType
- }
- t.test.shouldFail = true
- t.test.expectedError = ":UNEXPECTED_MESSAGE:"
- t.test.expectedLocalError = "remote error: unexpected message"
-
- if t.test.config.MaxVersion >= VersionTLS13 && t.messageType == typeServerHello {
- // In TLS 1.3, if the server believes it has sent ServerHello,
- // but the client cannot process it, the client will send an
- // unencrypted alert while the server expects encryption. This
- // decryption failure is reported differently for each protocol, so
- // leave it unchecked.
- t.test.expectedLocalError = ""
- }
-
- testCases = append(testCases, t.test)
- }
-}
-
-func addTrailingMessageDataTests() {
- for _, t := range makePerMessageTests() {
- t.test.name = "TrailingMessageData-" + t.test.name
- if t.test.resumeConfig != nil {
- t.test.resumeConfig.Bugs.SendTrailingMessageData = t.messageType
- } else {
- t.test.config.Bugs.SendTrailingMessageData = t.messageType
- }
- t.test.shouldFail = true
- t.test.expectedError = ":DECODE_ERROR:"
- t.test.expectedLocalError = "remote error: error decoding message"
-
- if t.test.config.MaxVersion >= VersionTLS13 && t.messageType == typeServerHello {
- // In TLS 1.3, if the server believes it has sent ServerHello,
- // but the client cannot process it, the client will send an
- // unencrypted alert while the server expects encryption. This
- // decryption failure is reported differently for each protocol, so
- // leave it unchecked.
- t.test.expectedLocalError = ""
- }
-
- if t.messageType == typeClientHello {
- // We have a different error for ClientHello parsing.
- t.test.expectedError = ":CLIENTHELLO_PARSE_FAILED:"
- }
-
- if t.messageType == typeFinished {
- // Bad Finished messages read as the verify data having
- // the wrong length.
- t.test.expectedError = ":DIGEST_CHECK_FAILED:"
- t.test.expectedLocalError = "remote error: error decrypting message"
- }
-
- testCases = append(testCases, t.test)
- }
-}
-
-func addTLS13HandshakeTests() {
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "NegotiatePSKResumption-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- NegotiatePSKResumption: true,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":MISSING_KEY_SHARE:",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "MissingKeyShare-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- MissingKeyShare: true,
- },
- },
- shouldFail: true,
- expectedError: ":MISSING_KEY_SHARE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "MissingKeyShare-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- MissingKeyShare: true,
- },
- },
- shouldFail: true,
- expectedError: ":MISSING_KEY_SHARE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DuplicateKeyShares-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- DuplicateKeyShares: true,
- },
- },
- shouldFail: true,
- expectedError: ":DUPLICATE_KEY_SHARE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- },
- },
- })
-
- // Test that enabling TLS 1.3 does not interfere with TLS 1.2 session ID
- // resumption.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ResumeTLS12SessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS13)},
- resumeSession: true,
- })
-
- // Test that the client correctly handles a TLS 1.3 ServerHello which echoes
- // a TLS 1.2 session ID.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS12SessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
-
- // Test that the server correctly echoes back session IDs of
- // various lengths. The first test additionally asserts that
- // BoringSSL always sends the ChangeCipherSpec messages for
- // compatibility mode, rather than negotiating it based on the
- // ClientHello.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EmptySessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendClientHelloSessionID: []byte{},
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-ShortSessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendClientHelloSessionID: make([]byte, 16),
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-FullSessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendClientHelloSessionID: make([]byte, 32),
- },
- },
- })
-
- // The server should reject ClientHellos whose session IDs are too long.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-TooLongSessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendClientHelloSessionID: make([]byte, 33),
- },
- },
- shouldFail: true,
- expectedError: ":CLIENTHELLO_PARSE_FAILED:",
- expectedLocalError: "remote error: error decoding message",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-TooLongSessionID-TLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendClientHelloSessionID: make([]byte, 33),
- },
- },
- shouldFail: true,
- expectedError: ":CLIENTHELLO_PARSE_FAILED:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- // Test that the client correctly accepts or rejects short session IDs from
- // the server. Our tests use 32 bytes by default, so the boundary condition
- // is already covered.
- testCases = append(testCases, testCase{
- name: "Client-ShortSessionID",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- NewSessionIDLength: 1,
- },
- },
- resumeSession: true,
- })
- testCases = append(testCases, testCase{
- name: "Client-TooLongSessionID",
- config: Config{
- MaxVersion: VersionTLS12,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- NewSessionIDLength: 33,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- // Test that the client sends a fake session ID in TLS 1.3. We cover both
- // normal and resumption handshakes to capture interactions with the
- // session resumption path.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS13SessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectClientHelloSessionID: true,
- },
- },
- resumeSession: true,
- })
-
- // Test that the client omits the fake session ID when the max version is TLS 1.2 and below.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TLS12NoSessionID-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExpectNoSessionID: true,
- },
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-on-initial-expect-early-data-reason", "no_session_offered",
- "-on-resume-expect-early-data-reason", "accept",
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-Reject-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysRejectEarlyData: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-retry-expect-early-data-reason", "peer_declined",
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- messageCount: 2,
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-on-initial-expect-early-data-reason", "no_session_offered",
- "-on-resume-expect-early-data-reason", "accept",
- },
- })
-
- // The above tests the most recent ticket. Additionally test that 0-RTT
- // works on the first ticket issued by the server.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-FirstTicket-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- UseFirstSessionTicket: true,
- },
- },
- messageCount: 2,
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-on-resume-expect-early-data-reason", "accept",
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-OmitEarlyDataExtension-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- OmitEarlyDataExtension: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-OmitEarlyDataExtension-HelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // Require a HelloRetryRequest for every curve.
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- OmitEarlyDataExtension: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-TooMuchData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 16384 + 1,
- },
- },
- shouldFail: true,
- expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-Interleaved-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- InterleaveEarlyData: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-EarlyDataInTLS12-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-HRR-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- },
- DefaultCurves: []CurveID{},
- },
- // Though the session is not resumed and we send HelloRetryRequest,
- // early data being disabled takes priority as the reject reason.
- flags: []string{"-expect-early-data-reason", "disabled"},
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-HRR-Interleaved-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 4,
- InterleaveEarlyData: true,
- },
- DefaultCurves: []CurveID{},
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-HRR-TooMuchData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendFakeEarlyDataLength: 16384 + 1,
- },
- DefaultCurves: []CurveID{},
- },
- shouldFail: true,
- expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
- })
-
- // Test that skipping early data looking for cleartext correctly
- // processes an alert record.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-HRR-FatalAlert-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEarlyAlert: true,
- SendFakeEarlyDataLength: 4,
- },
- DefaultCurves: []CurveID{},
- },
- shouldFail: true,
- expectedError: ":SSLV3_ALERT_HANDSHAKE_FAILURE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipEarlyData-SecondClientHelloEarlyData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEarlyDataOnSecondClientHello: true,
- },
- DefaultCurves: []CurveID{},
- },
- shouldFail: true,
- expectedLocalError: "remote error: bad record MAC",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EmptyEncryptedExtensions-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- EmptyEncryptedExtensions: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EncryptedExtensionsWithKeyShare-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- EncryptedExtensionsWithKeyShare: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: unsupported extension",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SendHelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // Require a HelloRetryRequest for every curve.
- DefaultCurves: []CurveID{},
- CurvePreferences: []CurveID{CurveX25519},
- },
- expectations: connectionExpectations{
- curveID: CurveX25519,
- },
- flags: []string{"-expect-hrr"},
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SendHelloRetryRequest-2-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{CurveP384},
- CurvePreferences: []CurveID{CurveX25519, CurveP384},
- },
- // Although the ClientHello did not predict our preferred curve,
- // we always select it whether it is predicted or not.
- expectations: connectionExpectations{
- curveID: CurveX25519,
- },
- flags: []string{"-expect-hrr"},
- })
-
- testCases = append(testCases, testCase{
- name: "UnknownCurve-HelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCurve: bogusCurve,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-CipherChange-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_AES_128_GCM_SHA256,
- SendHelloRetryRequestCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CIPHER_RETURNED:",
- })
-
- // Test that the client does not offer a PSK in the second ClientHello if the
- // HelloRetryRequest is incompatible with it.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "HelloRetryRequest-NonResumableCipher-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{
- TLS_AES_128_GCM_SHA256,
- },
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- ExpectNoTLS13PSKAfterHRR: true,
- },
- CipherSuites: []uint16{
- TLS_AES_256_GCM_SHA384,
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
-
- testCases = append(testCases, testCase{
- name: "DisabledCurve-HelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- IgnorePeerCurvePreferences: true,
- },
- },
- flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- name: "UnnecessaryHelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveX25519},
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCurve: CurveX25519,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- name: "SecondHelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SecondHelloRetryRequest: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-Empty-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysSendHelloRetryRequest: true,
- },
- },
- shouldFail: true,
- expectedError: ":EMPTY_HELLO_RETRY_REQUEST:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-DuplicateCurve-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires a HelloRetryRequest against BoringSSL's default
- // configuration. Assert this ExpectMissingKeyShare.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- ExpectMissingKeyShare: true,
- DuplicateHelloRetryRequestExtensions: true,
- },
- },
- shouldFail: true,
- expectedError: ":DUPLICATE_EXTENSION:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-Cookie-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte("cookie"),
- },
- },
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-DuplicateCookie-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte("cookie"),
- DuplicateHelloRetryRequestExtensions: true,
- },
- },
- shouldFail: true,
- expectedError: ":DUPLICATE_EXTENSION:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-EmptyCookie-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte{},
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-Cookie-Curve-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte("cookie"),
- ExpectMissingKeyShare: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequest-Unknown-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- CustomHelloRetryRequestExtension: "extension",
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SecondClientHelloMissingKeyShare-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- SecondClientHelloMissingKeyShare: true,
- },
- },
- shouldFail: true,
- expectedError: ":MISSING_KEY_SHARE:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SecondClientHelloWrongCurve-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- MisinterpretHelloRetryRequestCurve: CurveP521,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequestVersionMismatch-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendServerHelloVersion: 0x0305,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- name: "HelloRetryRequestCurveMismatch-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- // Send P-384 (correct) in the HelloRetryRequest.
- SendHelloRetryRequestCurve: CurveP384,
- // But send P-256 in the ServerHello.
- SendCurve: CurveP256,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- // Test the server selecting a curve that requires a HelloRetryRequest
- // without sending it.
- testCases = append(testCases, testCase{
- name: "SkipHelloRetryRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SkipHelloRetryRequest: true,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_CURVE:",
- })
-
- testCases = append(testCases, testCase{
- name: "SecondServerHelloNoVersion-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- OmitServerSupportedVersionExtension: true,
- },
- },
- shouldFail: true,
- expectedError: ":SECOND_SERVERHELLO_VERSION_MISMATCH:",
- })
- testCases = append(testCases, testCase{
- name: "SecondServerHelloWrongVersion-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- // P-384 requires HelloRetryRequest in BoringSSL.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- SendServerSupportedVersionExtension: 0x1234,
- },
- },
- shouldFail: true,
- expectedError: ":SECOND_SERVERHELLO_VERSION_MISMATCH:",
- })
-
- testCases = append(testCases, testCase{
- name: "RequestContextInHandshake-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- SendRequestContext: []byte("request context"),
- },
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- name: "UnknownExtensionInCertificateRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- SendCustomCertificateRequest: 0x1212,
- },
- },
- shimCertificate: &rsaCertificate,
- })
-
- testCases = append(testCases, testCase{
- name: "MissingSignatureAlgorithmsInCertificateRequest-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- OmitCertificateRequestAlgorithms: true,
- },
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrailingKeyShareData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- TrailingKeyShareData: true,
- },
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- name: "AlwaysSelectPSKIdentity-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysSelectPSKIdentity: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- testCases = append(testCases, testCase{
- name: "InvalidPSKIdentity-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SelectPSKIdentityOnResume: 1,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":PSK_IDENTITY_NOT_FOUND:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ExtraPSKIdentity-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ExtraPSKIdentity: true,
- SendExtraPSKBinder: true,
- },
- },
- resumeSession: true,
- })
-
- // Test that unknown NewSessionTicket extensions are tolerated.
- testCases = append(testCases, testCase{
- name: "CustomTicketExtension-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- CustomTicketExtension: "1234",
- },
- },
- })
-
- // Test the client handles 0-RTT being rejected by a full handshake
- // and correctly reports a certificate change.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-RejectTicket-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &rsaCertificate,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Credential: &ecdsaP256Certificate,
- SessionTicketsDisabled: true,
- },
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-retry-expect-early-data-reason", "session_not_resumed",
- // Test the peer certificate is reported correctly in each of the
- // three logical connections.
- "-on-initial-expect-peer-cert-file", rsaCertificate.ChainPath,
- "-on-resume-expect-peer-cert-file", rsaCertificate.ChainPath,
- "-on-retry-expect-peer-cert-file", ecdsaP256Certificate.ChainPath,
- // Session tickets are disabled, so the runner will not send a ticket.
- "-on-retry-expect-no-session",
- },
- })
-
- // Test the server rejects 0-RTT if it does not recognize the ticket.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-RejectTicket-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Corrupt the ticket.
- FilterTicket: func(in []byte) ([]byte, error) {
- in[len(in)-1] ^= 1
- return in, nil
- },
- },
- },
- messageCount: 2,
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-resume-expect-early-data-reason", "session_not_resumed",
- },
- })
-
- // Test the client handles 0-RTT being rejected via a HelloRetryRequest.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-HRR-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-retry-expect-early-data-reason", "hello_retry_request",
- },
- })
-
- // Test the server rejects 0-RTT if it needs to send a HelloRetryRequest.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-HRR-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- // Require a HelloRetryRequest for every curve.
- DefaultCurves: []CurveID{},
- },
- messageCount: 2,
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-resume-expect-early-data-reason", "hello_retry_request",
- },
- })
-
- // Test the client handles a 0-RTT reject from both ticket rejection and
- // HelloRetryRequest.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-HRR-RejectTicket-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Credential: &rsaCertificate,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Credential: &ecdsaP256Certificate,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- // The client sees HelloRetryRequest before the resumption result,
- // though neither value is inherently preferable.
- "-on-retry-expect-early-data-reason", "hello_retry_request",
- // Test the peer certificate is reported correctly in each of the
- // three logical connections.
- "-on-initial-expect-peer-cert-file", rsaCertificate.ChainPath,
- "-on-resume-expect-peer-cert-file", rsaCertificate.ChainPath,
- "-on-retry-expect-peer-cert-file", ecdsaP256Certificate.ChainPath,
- // Session tickets are disabled, so the runner will not send a ticket.
- "-on-retry-expect-no-session",
- },
- })
-
- // Test the server rejects 0-RTT if it needs to send a HelloRetryRequest.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-HRR-RejectTicket-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- // Require a HelloRetryRequest for every curve.
- DefaultCurves: []CurveID{},
- Bugs: ProtocolBugs{
- // Corrupt the ticket.
- FilterTicket: func(in []byte) ([]byte, error) {
- in[len(in)-1] ^= 1
- return in, nil
- },
- },
- },
- messageCount: 2,
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- // The server sees the missed resumption before HelloRetryRequest,
- // though neither value is inherently preferable.
- "-on-resume-expect-early-data-reason", "session_not_resumed",
- },
- })
-
- // The client must check the server does not send the early_data
- // extension while rejecting the session.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataWithoutResume-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MaxEarlyDataSize: 16384,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- SendEarlyDataExtension: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- // The client must fail with a dedicated error code if the server
- // responds with TLS 1.2 when offering 0-RTT.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataVersionDowngrade-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
- })
-
- // Same as above, but the server also sends a warning alert before the
- // ServerHello. Although the shim predicts TLS 1.3 for 0-RTT, it should
- // still interpret data before ServerHello in a TLS-1.2-compatible way.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataVersionDowngrade-Client-TLS13-WarningAlert",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendSNIWarningAlert: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
- })
-
- // Test that the client rejects an (unsolicited) early_data extension if
- // the server sent an HRR.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ServerAcceptsEarlyDataOnHRR-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
- SendEarlyDataExtension: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- // The client will first process an early data reject from the HRR.
- expectEarlyDataRejected: true,
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "SkipChangeCipherSpec-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SkipChangeCipherSpec: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SkipChangeCipherSpec-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SkipChangeCipherSpec: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "TooManyChangeCipherSpec-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtraChangeCipherSpec: 33,
- },
- },
- shouldFail: true,
- expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TooManyChangeCipherSpec-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendExtraChangeCipherSpec: 33,
- },
- },
- shouldFail: true,
- expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
- })
-
- testCases = append(testCases, testCase{
- name: "SendPostHandshakeChangeCipherSpec-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendPostHandshakeChangeCipherSpec: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_RECORD:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- fooString := "foo"
- barString := "bar"
-
- // Test that the client reports the correct ALPN after a 0-RTT reject
- // that changed it.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-ALPNMismatch-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ALPNProtocol: &fooString,
- },
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ALPNProtocol: &barString,
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- // The client does not learn ALPN was the cause.
- "-on-retry-expect-early-data-reason", "peer_declined",
- // In the 0-RTT state, we surface the predicted ALPN. After
- // processing the reject, we surface the real one.
- "-on-initial-expect-alpn", "foo",
- "-on-resume-expect-alpn", "foo",
- "-on-retry-expect-alpn", "bar",
- },
- })
-
- // Test that the client reports the correct ALPN after a 0-RTT reject if
- // ALPN was omitted from the first connection.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-ALPNOmitted1-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo"},
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- // The client does not learn ALPN was the cause.
- "-on-retry-expect-early-data-reason", "peer_declined",
- // In the 0-RTT state, we surface the predicted ALPN. After
- // processing the reject, we surface the real one.
- "-on-initial-expect-alpn", "",
- "-on-resume-expect-alpn", "",
- "-on-retry-expect-alpn", "foo",
- },
- })
-
- // Test that the client reports the correct ALPN after a 0-RTT reject if
- // ALPN was omitted from the second connection.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-ALPNOmitted2-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo"},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- // The client does not learn ALPN was the cause.
- "-on-retry-expect-early-data-reason", "peer_declined",
- // In the 0-RTT state, we surface the predicted ALPN. After
- // processing the reject, we surface the real one.
- "-on-initial-expect-alpn", "foo",
- "-on-resume-expect-alpn", "foo",
- "-on-retry-expect-alpn", "",
- },
- })
-
- // Test that the client enforces ALPN match on 0-RTT accept.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-BadALPNMismatch-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- ALPNProtocol: &fooString,
- },
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysAcceptEarlyData: true,
- ALPNProtocol: &barString,
- },
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-advertise-alpn", "\x03foo\x03bar",
- "-on-initial-expect-alpn", "foo",
- "-on-resume-expect-alpn", "foo",
- "-on-retry-expect-alpn", "bar",
- },
- shouldFail: true,
- expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- // Test that the client does not offer early data if it is incompatible
- // with ALPN preferences.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-ALPNPreferenceChanged-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MaxEarlyDataSize: 16384,
- NextProtos: []string{"foo", "bar"},
- },
- resumeSession: true,
- flags: []string{
- "-enable-early-data",
- "-expect-ticket-supports-early-data",
- "-expect-no-offer-early-data",
- // Offer different ALPN values in the initial and resumption.
- "-on-initial-advertise-alpn", "\x03foo",
- "-on-initial-expect-alpn", "foo",
- "-on-resume-advertise-alpn", "\x03bar",
- "-on-resume-expect-alpn", "bar",
- // The ALPN mismatch comes from the client, so it reports it as the
- // reason.
- "-on-resume-expect-early-data-reason", "alpn_mismatch",
- },
- })
-
- // Test that the client does not offer 0-RTT to servers which never
- // advertise it.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-NonZeroRTTSession-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- flags: []string{
- "-enable-early-data",
- "-on-resume-expect-no-offer-early-data",
- // The client declines to offer 0-RTT because of the session.
- "-on-resume-expect-early-data-reason", "unsupported_for_session",
- },
- })
-
- // Test that the server correctly rejects 0-RTT when the previous
- // session did not allow early data on resumption.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-NonZeroRTTSession-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEarlyData: [][]byte{{1, 2, 3, 4}},
- ExpectEarlyDataAccepted: false,
- },
- },
- resumeSession: true,
- // This test configures early data manually instead of the earlyData
- // option, to customize the -enable-early-data flag.
- flags: []string{
- "-on-resume-enable-early-data",
- "-expect-reject-early-data",
- // The server rejects 0-RTT because of the session.
- "-on-resume-expect-early-data-reason", "unsupported_for_session",
- },
- })
-
- // Test that we reject early data where ALPN is omitted from the first
- // connection, but negotiated in the second.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-ALPNOmitted1-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo"},
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-initial-select-alpn", "",
- "-on-resume-select-alpn", "foo",
- "-on-resume-expect-early-data-reason", "alpn_mismatch",
- },
- })
-
- // Test that we reject early data where ALPN is omitted from the second
- // connection, but negotiated in the first.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-ALPNOmitted2-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo"},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{},
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-initial-select-alpn", "foo",
- "-on-resume-select-alpn", "",
- "-on-resume-expect-early-data-reason", "alpn_mismatch",
- },
- })
-
- // Test that we reject early data with mismatched ALPN.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-ALPNMismatch-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo"},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- NextProtos: []string{"bar"},
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-initial-select-alpn", "foo",
- "-on-resume-select-alpn", "bar",
- "-on-resume-expect-early-data-reason", "alpn_mismatch",
- },
- })
-
- // Test that the client offering 0-RTT and Channel ID forbids the server
- // from accepting both.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataChannelID-AcceptBoth-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- RequestChannelID: true,
- },
- resumeSession: true,
- earlyData: true,
- expectations: connectionExpectations{
- channelID: true,
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
- expectedLocalError: "remote error: illegal parameter",
- flags: []string{
- "-send-channel-id", channelIDKeyPath,
- },
- })
-
- // Test that the client offering Channel ID and 0-RTT allows the server
- // to decline 0-RTT.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataChannelID-AcceptChannelID-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- RequestChannelID: true,
- Bugs: ProtocolBugs{
- AlwaysRejectEarlyData: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- expectations: connectionExpectations{
- channelID: true,
- },
- flags: []string{
- "-send-channel-id", channelIDKeyPath,
- // The client never learns the reason was Channel ID.
- "-on-retry-expect-early-data-reason", "peer_declined",
- },
- })
-
- // Test that the client offering Channel ID and 0-RTT allows the server
- // to decline Channel ID.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataChannelID-AcceptEarlyData-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-send-channel-id", channelIDKeyPath,
- },
- })
-
- // Test that the server supporting Channel ID and 0-RTT declines 0-RTT
- // if it would negotiate Channel ID.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyDataChannelID-OfferBoth-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- ChannelID: &channelIDKey,
- },
- resumeSession: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- expectations: connectionExpectations{
- channelID: true,
- },
- flags: []string{
- "-expect-channel-id",
- base64FlagValue(channelIDBytes),
- "-on-resume-expect-early-data-reason", "channel_id",
- },
- })
-
- // Test that the server supporting Channel ID and 0-RTT accepts 0-RTT
- // if not offered Channel ID.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyDataChannelID-OfferEarlyData-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- expectations: connectionExpectations{
- channelID: false,
- },
- flags: []string{
- "-enable-channel-id",
- "-on-resume-expect-early-data-reason", "accept",
- },
- })
-
- // Test that the server errors on 0-RTT streams without EndOfEarlyData.
- // The subsequent records should fail to decrypt.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-SkipEndOfEarlyData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SkipEndOfEarlyData: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedLocalError: "remote error: bad record MAC",
- expectedError: ":BAD_DECRYPT:",
- })
-
- // Test that EndOfEarlyData is rejected in QUIC. Since we leave application
- // data to the QUIC implementation, we never accept any data at all in
- // the 0-RTT epoch, so the error is that the encryption level is rejected
- // outright.
- //
- // TODO(crbug.com/381113363): Test this for DTLS 1.3 as well.
- testCases = append(testCases, testCase{
- protocol: quic,
- testType: serverTest,
- name: "EarlyData-UnexpectedEndOfEarlyData-QUIC",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEndOfEarlyDataInQUICAndDTLS: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":WRONG_ENCRYPTION_LEVEL_RECEIVED:",
- })
-
- // Test that the server errors on 0-RTT streams with a stray handshake
- // message in them.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-UnexpectedHandshake-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendStrayEarlyHandshake: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Test that the client reports TLS 1.3 as the version while sending
- // early data.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-Client-VersionAPI-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-expect-version", strconv.Itoa(VersionTLS13),
- // EMS and RI are always reported as supported when we report
- // TLS 1.3.
- "-expect-extended-master-secret",
- "-expect-secure-renegotiation",
- },
- })
-
- // Test that client and server both notice handshake errors after data
- // has started flowing.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-Client-BadFinished-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- BadFinished: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- expectedLocalError: "remote error: error decrypting message",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyData-Server-BadFinished-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- BadFinished: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":DIGEST_CHECK_FAILED:",
- expectedLocalError: "remote error: error decrypting message",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "Server-NonEmptyEndOfEarlyData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- NonEmptyEndOfEarlyData: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ServerSkipCertificateVerify-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Credential: &rsaChainCertificate,
- Bugs: ProtocolBugs{
- SkipCertificateVerify: true,
- },
- },
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- shimCertificate: &rsaCertificate,
- flags: []string{
- "-require-any-client-certificate",
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ClientSkipCertificateVerify-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Credential: &rsaChainCertificate,
- Bugs: ProtocolBugs{
- SkipCertificateVerify: true,
- },
- },
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- shimCertificate: &rsaCertificate,
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // PSK/resumption handshakes should not accept CertificateRequest or
- // Certificate messages.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertificateInResumption-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysSendCertificate: true,
- },
- },
- resumeSession: true,
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertificateRequestInResumption-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- AlwaysSendCertificateRequest: true,
- },
- },
- shimCertificate: &rsaCertificate,
- resumeSession: true,
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // If the client or server has 0-RTT enabled but disabled TLS 1.3, it should
- // report a reason of protocol_version.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyDataEnabled-Client-MaxTLS12",
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- flags: []string{
- "-enable-early-data",
- "-max-version", strconv.Itoa(VersionTLS12),
- "-expect-early-data-reason", "protocol_version",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyDataEnabled-Server-MaxTLS12",
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- flags: []string{
- "-enable-early-data",
- "-max-version", strconv.Itoa(VersionTLS12),
- "-expect-early-data-reason", "protocol_version",
- },
- })
-
- // The server additionally reports protocol_version if it enabled TLS 1.3,
- // but the peer negotiated TLS 1.2. (The corresponding situation does not
- // exist on the client because negotiating TLS 1.2 with a 0-RTT ClientHello
- // is a fatal error.)
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EarlyDataEnabled-Server-NegotiateTLS12",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- flags: []string{
- "-enable-early-data",
- "-expect-early-data-reason", "protocol_version",
- },
- })
-
- // On 0-RTT reject, the server may end up negotiating a cipher suite with a
- // different PRF hash. Test that the client handles this correctly.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-Reject0RTT-DifferentPRF-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_256_GCM_SHA384},
- },
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-initial-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
- // The client initially reports the old cipher suite while sending
- // early data. After processing the 0-RTT reject, it reports the
- // true cipher suite.
- "-on-resume-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
- "-on-retry-expect-cipher", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)),
- },
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-Reject0RTT-DifferentPRF-HRR-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_256_GCM_SHA384},
- // P-384 requires a HelloRetryRequest against BoringSSL's default
- // configuration. Assert this with ExpectMissingKeyShare.
- CurvePreferences: []CurveID{CurveP384},
- Bugs: ProtocolBugs{
- ExpectMissingKeyShare: true,
- },
- },
- resumeSession: true,
- expectResumeRejected: true,
- earlyData: true,
- expectEarlyDataRejected: true,
- flags: []string{
- "-on-initial-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
- // The client initially reports the old cipher suite while sending
- // early data. After processing the 0-RTT reject, it reports the
- // true cipher suite.
- "-on-resume-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
- "-on-retry-expect-cipher", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)),
- },
- })
-
- // Test that the client enforces cipher suite match on 0-RTT accept.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-CipherMismatch-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
- },
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
- Bugs: ProtocolBugs{
- AlwaysAcceptEarlyData: true,
- },
- },
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":CIPHER_MISMATCH_ON_EARLY_DATA:",
- expectedLocalError: "remote error: illegal parameter",
- })
-
- // Test that the client can write early data when it has received a partial
- // ServerHello..Finished flight. See https://crbug.com/1208784. Note the
- // EncryptedExtensions test assumes EncryptedExtensions and Finished are in
- // separate records, i.e. that PackHandshakeFlight is disabled.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-WriteAfterServerHello",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Write the server response before expecting early data.
- ExpectEarlyData: [][]byte{},
- ExpectLateEarlyData: [][]byte{[]byte(shimInitialWrite)},
- },
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-async",
- "-on-resume-early-write-after-message",
- strconv.Itoa(int(typeServerHello)),
- },
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EarlyData-WriteAfterEncryptedExtensions",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Write the server response before expecting early data.
- ExpectEarlyData: [][]byte{},
- ExpectLateEarlyData: [][]byte{[]byte(shimInitialWrite)},
- },
- },
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-async",
- "-on-resume-early-write-after-message",
- strconv.Itoa(int(typeEncryptedExtensions)),
- },
- })
-}
-
-func addTLS13CipherPreferenceTests() {
- // Test that client preference is honored if the shim has AES hardware
- // and ChaCha20-Poly1305 is preferred otherwise.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-CipherPreference-Server-ChaCha20-AES",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{
- TLS_CHACHA20_POLY1305_SHA256,
- TLS_AES_128_GCM_SHA256,
- },
- CurvePreferences: []CurveID{CurveX25519},
- },
- flags: []string{
- "-expect-cipher-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
- "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TLS13-CipherPreference-Server-AES-ChaCha20",
- config: Config{
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{
- TLS_AES_128_GCM_SHA256,
- TLS_CHACHA20_POLY1305_SHA256,
- },
- CurvePreferences: []CurveID{CurveX25519},
- },
- flags: []string{
- "-expect-cipher-aes", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
- "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
- },
- })
-
- // Test that the client orders ChaCha20-Poly1305 and AES-GCM based on
- // whether it has AES hardware.
- testCases = append(testCases, testCase{
- name: "TLS13-CipherPreference-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- // Use the client cipher order. (This is the default but
- // is listed to be explicit.)
- PreferServerCipherSuites: false,
- },
- flags: []string{
- "-expect-cipher-aes", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
- "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
- },
- })
-}
-
-func addPeekTests() {
- // Test SSL_peek works, including on empty records.
- testCases = append(testCases, testCase{
- name: "Peek-Basic",
- sendEmptyRecords: 1,
- flags: []string{"-peek-then-read"},
- })
-
- // Test SSL_peek can drive the initial handshake.
- testCases = append(testCases, testCase{
- name: "Peek-ImplicitHandshake",
- flags: []string{
- "-peek-then-read",
- "-implicit-handshake",
- },
- })
-
- // Test SSL_peek can discover and drive a renegotiation.
- testCases = append(testCases, testCase{
- name: "Peek-Renegotiate",
- config: Config{
- MaxVersion: VersionTLS12,
- },
- renegotiate: 1,
- flags: []string{
- "-peek-then-read",
- "-renegotiate-freely",
- "-expect-total-renegotiations", "1",
- },
- })
-
- // Test SSL_peek can discover a close_notify.
- testCases = append(testCases, testCase{
- name: "Peek-Shutdown",
- config: Config{
- Bugs: ProtocolBugs{
- ExpectCloseNotify: true,
- },
- },
- flags: []string{
- "-peek-then-read",
- "-check-close-notify",
- },
- })
-
- // Test SSL_peek can discover an alert.
- testCases = append(testCases, testCase{
- name: "Peek-Alert",
- config: Config{
- Bugs: ProtocolBugs{
- SendSpuriousAlert: alertRecordOverflow,
- },
- },
- flags: []string{"-peek-then-read"},
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
- })
-
- // Test SSL_peek can handle KeyUpdate.
- testCases = append(testCases, testCase{
- name: "Peek-KeyUpdate",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendKeyUpdates: 1,
- keyUpdateRequest: keyUpdateNotRequested,
- flags: []string{"-peek-then-read"},
- })
-}
-
-func addRecordVersionTests() {
- for _, ver := range tlsVersions {
- // Test that the record version is enforced.
- testCases = append(testCases, testCase{
- name: "CheckRecordVersion-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendRecordVersion: 0x03ff,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_VERSION_NUMBER:",
- })
-
- // Test that the ClientHello may use any record version, for
- // compatibility reasons.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "LooseInitialRecordVersion-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendInitialRecordVersion: 0x03ff,
- },
- },
- })
-
- // Test that garbage ClientHello record versions are rejected.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "GarbageInitialRecordVersion-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- SendInitialRecordVersion: 0xffff,
- },
- },
- shouldFail: true,
- expectedError: ":WRONG_VERSION_NUMBER:",
- })
- }
-}
-
-func addCertificateTests() {
- for _, ver := range tlsVersions {
- // Test that a certificate chain with intermediate may be sent
- // and received as both client and server.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "SendReceiveIntermediate-Client-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaChainCertificate,
- ClientAuth: RequireAnyClientCert,
- },
- expectations: connectionExpectations{
- peerCertificate: &rsaChainCertificate,
- },
- shimCertificate: &rsaChainCertificate,
- flags: []string{
- "-expect-peer-cert-file", rsaChainCertificate.ChainPath,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "SendReceiveIntermediate-Server-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaChainCertificate,
- },
- expectations: connectionExpectations{
- peerCertificate: &rsaChainCertificate,
- },
- shimCertificate: &rsaChainCertificate,
- flags: []string{
- "-require-any-client-certificate",
- "-expect-peer-cert-file", rsaChainCertificate.ChainPath,
- },
- })
-
- // Test that garbage leaf certificates are properly rejected.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "GarbageCertificate-Client-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &garbageCertificate,
- },
- shouldFail: true,
- expectedError: ":CANNOT_PARSE_LEAF_CERT:",
- expectedLocalError: "remote error: error decoding message",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "GarbageCertificate-Server-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &garbageCertificate,
- },
- flags: []string{"-require-any-client-certificate"},
- shouldFail: true,
- expectedError: ":CANNOT_PARSE_LEAF_CERT:",
- expectedLocalError: "remote error: error decoding message",
- })
- }
-}
-
-func addRetainOnlySHA256ClientCertTests() {
- for _, ver := range tlsVersions {
- // Test that enabling
- // SSL_CTX_set_retain_only_sha256_of_client_certs without
- // actually requesting a client certificate is a no-op.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RetainOnlySHA256-NoCert-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- },
- flags: []string{
- "-on-initial-retain-only-sha256-client-cert",
- "-on-resume-retain-only-sha256-client-cert",
- },
- resumeSession: true,
- })
-
- // Test that when retaining only a SHA-256 certificate is
- // enabled, the hash appears as expected.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RetainOnlySHA256-Cert-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-verify-peer",
- "-on-initial-retain-only-sha256-client-cert",
- "-on-resume-retain-only-sha256-client-cert",
- "-on-initial-expect-sha256-client-cert",
- "-on-resume-expect-sha256-client-cert",
- },
- resumeSession: true,
- })
-
- // Test that when the config changes from on to off, a
- // resumption is rejected because the server now wants the full
- // certificate chain.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RetainOnlySHA256-OnOff-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-verify-peer",
- "-on-initial-retain-only-sha256-client-cert",
- "-on-initial-expect-sha256-client-cert",
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
-
- // Test that when the config changes from off to on, a
- // resumption is rejected because the server now wants just the
- // hash.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RetainOnlySHA256-OffOn-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-verify-peer",
- "-on-resume-retain-only-sha256-client-cert",
- "-on-resume-expect-sha256-client-cert",
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
- }
-}
-
-func addECDSAKeyUsageTests() {
- serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
- serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
- if err != nil {
- panic(err)
- }
-
- template := &x509.Certificate{
- SerialNumber: serialNumber,
- Subject: pkix.Name{
- Organization: []string{"Acme Co"},
- },
- NotBefore: time.Now(),
- NotAfter: time.Now(),
-
- // An ECC certificate with only the keyAgreement key usgae may
- // be used with ECDH, but not ECDSA.
- KeyUsage: x509.KeyUsageKeyAgreement,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- BasicConstraintsValid: true,
- }
-
- cert := generateSingleCertChain(template, &ecdsaP256Key)
-
- for _, ver := range tlsVersions {
- if ver.version < VersionTLS12 {
- continue
- }
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ECDSAKeyUsage-Client-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &cert,
- },
- shouldFail: true,
- expectedError: ":KEY_USAGE_BIT_INCORRECT:",
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ECDSAKeyUsage-Server-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &cert,
- },
- flags: []string{"-require-any-client-certificate"},
- shouldFail: true,
- expectedError: ":KEY_USAGE_BIT_INCORRECT:",
- })
- }
-}
-
-func addRSAKeyUsageTests() {
- priv := rsaCertificate.PrivateKey.(*rsa.PrivateKey)
-
- serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
- serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
- if err != nil {
- panic(err)
- }
-
- dsTemplate := x509.Certificate{
- SerialNumber: serialNumber,
- Subject: pkix.Name{
- Organization: []string{"Acme Co"},
- },
- NotBefore: time.Now(),
- NotAfter: time.Now(),
-
- KeyUsage: x509.KeyUsageDigitalSignature,
- BasicConstraintsValid: true,
- }
-
- encTemplate := x509.Certificate{
- SerialNumber: serialNumber,
- Subject: pkix.Name{
- Organization: []string{"Acme Co"},
- },
- NotBefore: time.Now(),
- NotAfter: time.Now(),
-
- KeyUsage: x509.KeyUsageKeyEncipherment,
- BasicConstraintsValid: true,
- }
-
- dsCert := generateSingleCertChain(&dsTemplate, priv)
-
- encCert := generateSingleCertChain(&encTemplate, priv)
-
- dsSuites := []uint16{
- TLS_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- }
- encSuites := []uint16{
- TLS_RSA_WITH_AES_128_GCM_SHA256,
- TLS_RSA_WITH_AES_128_CBC_SHA,
- }
-
- for _, ver := range tlsVersions {
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &encCert,
- CipherSuites: dsSuites,
- },
- shouldFail: true,
- expectedError: ":KEY_USAGE_BIT_INCORRECT:",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantSignature-GotSignature-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &dsCert,
- CipherSuites: dsSuites,
- },
- })
-
- // TLS 1.3 removes the encipherment suites.
- if ver.version < VersionTLS13 {
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantEncipherment-GotEncipherment" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &encCert,
- CipherSuites: encSuites,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantEncipherment-GotSignature-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &dsCert,
- CipherSuites: encSuites,
- },
- shouldFail: true,
- expectedError: ":KEY_USAGE_BIT_INCORRECT:",
- })
-
- // In 1.2 and below, we should not enforce without the enforce-rsa-key-usage flag.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-Unenforced-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &dsCert,
- CipherSuites: encSuites,
- },
- flags: []string{"-expect-key-usage-invalid", "-ignore-rsa-key-usage"},
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantEncipherment-GotSignature-Unenforced-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &encCert,
- CipherSuites: dsSuites,
- },
- flags: []string{"-expect-key-usage-invalid", "-ignore-rsa-key-usage"},
- })
- }
-
- if ver.version >= VersionTLS13 {
- // In 1.3 and above, we enforce keyUsage even when disabled.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-AlwaysEnforced-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &encCert,
- CipherSuites: dsSuites,
- },
- flags: []string{"-ignore-rsa-key-usage"},
- shouldFail: true,
- expectedError: ":KEY_USAGE_BIT_INCORRECT:",
- })
- }
-
- // The server only uses signatures and always enforces it.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RSAKeyUsage-Server-WantSignature-GotEncipherment-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &encCert,
- },
- shouldFail: true,
- expectedError: ":KEY_USAGE_BIT_INCORRECT:",
- flags: []string{"-require-any-client-certificate"},
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "RSAKeyUsage-Server-WantSignature-GotSignature-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Credential: &dsCert,
- },
- flags: []string{"-require-any-client-certificate"},
- })
-
- }
-}
-
-func addExtraHandshakeTests() {
- // An extra SSL_do_handshake is normally a no-op. These tests use -async
- // to ensure there is no transport I/O.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ExtraHandshake-Client-TLS12",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- flags: []string{
- "-async",
- "-no-op-extra-handshake",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ExtraHandshake-Server-TLS12",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- flags: []string{
- "-async",
- "-no-op-extra-handshake",
- },
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ExtraHandshake-Client-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- },
- flags: []string{
- "-async",
- "-no-op-extra-handshake",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ExtraHandshake-Server-TLS13",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- },
- flags: []string{
- "-async",
- "-no-op-extra-handshake",
- },
- })
-
- // An extra SSL_do_handshake is a no-op in server 0-RTT.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ExtraHandshake-Server-EarlyData-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- messageCount: 2,
- resumeSession: true,
- earlyData: true,
- flags: []string{
- "-async",
- "-no-op-extra-handshake",
- },
- })
-
- // An extra SSL_do_handshake drives the handshake to completion in False
- // Start. We test this by handshaking twice and asserting the False
- // Start does not appear to happen. See AlertBeforeFalseStartTest for
- // how the test works.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "ExtraHandshake-FalseStart",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
- Bugs: ProtocolBugs{
- ExpectFalseStart: true,
- AlertBeforeFalseStartTest: alertAccessDenied,
- },
- },
- flags: []string{
- "-handshake-twice",
- "-false-start",
- "-advertise-alpn", "\x03foo",
- "-expect-alpn", "foo",
- },
- shimWritesFirst: true,
- shouldFail: true,
- expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
- expectedLocalError: "tls: peer did not false start: EOF",
- })
-}
-
-// Test that omitted and empty extensions blocks are tolerated.
-func addOmitExtensionsTests() {
- // Check the ExpectOmitExtensions setting works.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "ExpectOmitExtensions",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- ExpectOmitExtensions: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "tls: ServerHello did not omit extensions",
- })
-
- for _, ver := range tlsVersions {
- if ver.version > VersionTLS12 {
- continue
- }
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "OmitExtensions-ClientHello-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- OmitExtensions: true,
- // With no client extensions, the ServerHello must not have
- // extensions. It should then omit the extensions field.
- ExpectOmitExtensions: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "EmptyExtensions-ClientHello-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- EmptyExtensions: true,
- // With no client extensions, the ServerHello must not have
- // extensions. It should then omit the extensions field.
- ExpectOmitExtensions: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "OmitExtensions-ServerHello-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- OmitExtensions: true,
- // Disable all ServerHello extensions so
- // OmitExtensions works.
- NoExtendedMasterSecret: true,
- NoRenegotiationInfo: true,
- NoOCSPStapling: true,
- NoSignedCertificateTimestamps: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "EmptyExtensions-ServerHello-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- EmptyExtensions: true,
- // Disable all ServerHello extensions so
- // EmptyExtensions works.
- NoExtendedMasterSecret: true,
- NoRenegotiationInfo: true,
- NoOCSPStapling: true,
- NoSignedCertificateTimestamps: true,
- },
- },
- })
- }
-}
-
-const (
- shrinkingCompressionAlgID = 0xff01
- expandingCompressionAlgID = 0xff02
- randomCompressionAlgID = 0xff03
-)
-
-var (
- // shrinkingPrefix is the first two bytes of a Certificate message.
- shrinkingPrefix = []byte{0, 0}
- // expandingPrefix is just some arbitrary byte string. This has to match the
- // value in the shim.
- expandingPrefix = []byte{1, 2, 3, 4}
-)
-
-var shrinkingCompression = CertCompressionAlg{
- Compress: func(uncompressed []byte) []byte {
- if !bytes.HasPrefix(uncompressed, shrinkingPrefix) {
- panic(fmt.Sprintf("cannot compress certificate message %x", uncompressed))
- }
- return uncompressed[len(shrinkingPrefix):]
- },
- Decompress: func(out []byte, compressed []byte) bool {
- if len(out) != len(shrinkingPrefix)+len(compressed) {
- return false
- }
-
- copy(out, shrinkingPrefix)
- copy(out[len(shrinkingPrefix):], compressed)
- return true
- },
-}
-
-var expandingCompression = CertCompressionAlg{
- Compress: func(uncompressed []byte) []byte {
- ret := make([]byte, 0, len(expandingPrefix)+len(uncompressed))
- ret = append(ret, expandingPrefix...)
- return append(ret, uncompressed...)
- },
- Decompress: func(out []byte, compressed []byte) bool {
- if !bytes.HasPrefix(compressed, expandingPrefix) {
- return false
- }
- copy(out, compressed[len(expandingPrefix):])
- return true
- },
-}
-
-var randomCompression = CertCompressionAlg{
- Compress: func(uncompressed []byte) []byte {
- ret := make([]byte, 1+len(uncompressed))
- if _, err := rand.Read(ret[:1]); err != nil {
- panic(err)
- }
- copy(ret[1:], uncompressed)
- return ret
- },
- Decompress: func(out []byte, compressed []byte) bool {
- if len(compressed) != 1+len(out) {
- return false
- }
- copy(out, compressed[1:])
- return true
- },
-}
-
-func addCertCompressionTests() {
- for _, ver := range tlsVersions {
- if ver.version < VersionTLS12 {
- continue
- }
-
- // Duplicate compression algorithms is an error, even if nothing is
- // configured.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DuplicateCertCompressionExt-" + ver.name,
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- DuplicateCompressedCertAlgs: true,
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- // With compression algorithms configured, an duplicate values should still
- // be an error.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DuplicateCertCompressionExt2-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- Bugs: ProtocolBugs{
- DuplicateCompressedCertAlgs: true,
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
-
- if ver.version < VersionTLS13 {
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CertCompressionIgnoredBefore13-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- expandingCompressionAlgID: expandingCompression,
- },
- },
- })
-
- continue
- }
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CertCompressionExpands-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- expandingCompressionAlgID: expandingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: expandingCompressionAlgID,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CertCompressionShrinks-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- },
- },
- })
-
- // Test that the shim behaves consistently if the compression function
- // is non-deterministic. This is intended to model version differences
- // between the shim and handshaker with handshake hints, but it is also
- // useful in confirming we only call the callbacks once.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CertCompressionRandom-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- randomCompressionAlgID: randomCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: randomCompressionAlgID,
- },
- },
- })
-
- // With both algorithms configured, the server should pick its most
- // preferable. (Which is expandingCompressionAlgID.)
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CertCompressionPriority-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- expandingCompressionAlgID: expandingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: expandingCompressionAlgID,
- },
- },
- })
-
- // With no common algorithms configured, the server should decline
- // compression.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CertCompressionNoCommonAlgs-" + ver.name,
- flags: []string{"-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID)},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- expandingCompressionAlgID: expandingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectUncompressedCert: true,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertCompressionExpandsClient-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- expandingCompressionAlgID: expandingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: expandingCompressionAlgID,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertCompressionShrinksClient-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- },
- },
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertCompressionBadAlgIDClient-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- SendCertCompressionAlgID: 1234,
- },
- },
- shouldFail: true,
- expectedError: ":UNKNOWN_CERT_COMPRESSION_ALG:",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertCompressionTooSmallClient-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- SendCertUncompressedLength: 12,
- },
- },
- shouldFail: true,
- expectedError: ":CERT_DECOMPRESSION_FAILED:",
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "CertCompressionTooLargeClient-" + ver.name,
- flags: []string{"-install-cert-compression-algs"},
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- SendCertUncompressedLength: 1 << 20,
- },
- },
- shouldFail: true,
- expectedError: ":UNCOMPRESSED_CERT_TOO_LARGE:",
- })
- }
-}
-
-func addJDK11WorkaroundTests() {
- // Test the client treats the JDK 11 downgrade random like the usual one.
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "Client-RejectJDK11DowngradeRandom",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendJDK11DowngradeRandom: true,
- },
- },
- shouldFail: true,
- expectedError: ":TLS13_DOWNGRADE:",
- expectedLocalError: "remote error: illegal parameter",
- })
- testCases = append(testCases, testCase{
- testType: clientTest,
- name: "Client-AcceptJDK11DowngradeRandom",
- config: Config{
- MaxVersion: VersionTLS12,
- Bugs: ProtocolBugs{
- SendJDK11DowngradeRandom: true,
- },
- },
- flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
- })
-
- clientHelloTests := []struct {
- clientHello []byte
- isJDK11 bool
- }{
- {
- // A default JDK 11 ClientHello.
- decodeHexOrPanic("010001a9030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b5"),
- true,
- },
- {
- // The above with supported_versions and
- // psk_key_exchange_modes in the wrong order.
- decodeHexOrPanic("010001a9030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002d00020101002b00090803040303030203010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b5"),
- false,
- },
- {
- // The above with a padding extension added at the end.
- decodeHexOrPanic("010001b4030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000111000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b50015000700000000000000"),
- false,
- },
- {
- // A JDK 11 ClientHello offering a TLS 1.3 PSK.
- decodeHexOrPanic("0100024c0303a8d71b20f060545a398226e807d21371a7a02b7ca2f96f476c2dea7e5860c5a400005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff010001c9000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104aaec585ea9e121b24710a23560571322b2cf8ab8cd14e5762ef0486d8a6d0ecd721d8f2abda2eb8ed5ab7195505660450f49bba94bbf0c3f0070a531d9a1be4f002900cb00a600a0e6f7586d9a2bf64a54c1adf55a2f76657047e8e88e26629e2e7b9d630941e06fd87792770f6834e159a70b252157a9b4b082183f24629c8ff5049088b07ce37c49de8cf752a2ed7a545aff63bdc7a1b18e1bc201f23f159ee75d4987a04e00f840824f764691ab83a20e3032646e793065874cdb46138a52f50ed71406f399f96f9309eba4e5b1966148c22a63dc4aa1364269dd41dd5cc0e848d07af0095622c52cfcfc00212009cc315259e2328d65ad17a3de7c182c7874140a9356fecdd4614657806cd659"),
- true,
- },
- {
- // A JDK 11 ClientHello offering a TLS 1.2 session.
- decodeHexOrPanic("010001a903038cdec49f4836d064a75046c93f22d0b9c2cf4900917332e6f0e1f41d692d3146201a3e99047492285ec65ab4e0eeee59f8f9d1eb7687398887bcd7b81353e93923005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d0002010100330047004500170041041c83c42fcd8fc06265b9f6e4f076f7e7ee17ace915c587845c0e1bc8cd177f904befeb611b682cae4702509a5f5d0c7162a282b8152d843169b91136e7c6f3e7"),
- true,
- },
- {
- // A JDK 11 ClientHello with EMS disabled.
- decodeHexOrPanic("010001a50303323a857c324a9ef57d6e2544d129073830385cb1dc75ea79f6a2ec8ae09d2e7320f85fdd081678874c67ebab235e6d6a81d947f690bc0af9be4d39854ed67d9ef9005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000102000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b040105010601040203030301030202030201020200110009000702000400000000002b0009080304030303020301002d0002010100330047004500170041049c904c4850b495d75522f955d79e9cabea065c90279d6037a101a4c4ee712afc93ad0df5d12d287d53e458c7075d9a3ce3969c939bb62222bda779cecf54a603"),
- true,
- },
- {
- // A JDK 11 ClientHello with OCSP stapling disabled.
- decodeHexOrPanic("0100019303038a50481dc85ee4f6581670821c50f2b3d34ac3251dc6e9b751bfd2521ab47ab02069a963c5486034c37ae0577ddb4c2db28cab592380ef8e4599d1305148712112005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff010000f0000000080006000003736e69000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b040105010601040203030301030202030201020200170000002b0009080304030303020301002d00020101003300470045001700410438a97824f842c549e3c339322d8b2dbaa85d10bd7bca9c969376cb0c60b1e929eb4d13db38dcb0082ad8c637b24f55466a9acbb0b63634c1f431ec8342cf720d"),
- true,
- },
- {
- // A JDK 11 ClientHello configured with a smaller set of
- // ciphers.
- decodeHexOrPanic("0100015603036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f4811800061301c02bc02f01000107000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
- true,
- },
- {
- // The above with TLS_CHACHA20_POLY1305_SHA256 added,
- // which JDK 11 does not support.
- decodeHexOrPanic("0100015803036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f48118000813011303c02bc02f01000107000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
- false,
- },
- {
- // The above with X25519 added, which JDK 11 does not
- // support.
- decodeHexOrPanic("0100015803036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f4811800061301c02bc02f01000109000000080006000003736e69000500050100000000000a00220020001d0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
- false,
- },
- {
- // A JDK 11 ClientHello with ALPN protocols configured.
- decodeHexOrPanic("010001bb0303c0e0ea707b00c5311eb09cabd58626692cebfaefaef7265637e4550811dae16220da86d6eea04e214e873675223f08a6926bcf79f16d866280bdbab85e9e09c3ff005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000118000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020010000e000c02683208687474702f312e310011000900070200040000000000170000002b0009080304030303020301002d00020101003300470045001700410416def07c1d66ddde5fc9dcc328c8e77022d321c590c0d30cb41d515b38dca34540819a216c6c053bd47b9068f4f6b960f03647de4e36e8b7ffeea78f7252e3d9"),
- true,
- },
- }
- for i, t := range clientHelloTests {
- expectedVersion := uint16(VersionTLS13)
- if t.isJDK11 {
- expectedVersion = VersionTLS12
- }
-
- // In each of these tests, we set DefaultCurves to P-256 to
- // match the test inputs. SendClientHelloWithFixes requires the
- // key_shares extension to match in type.
-
- // With the workaround enabled, we should negotiate TLS 1.2 on
- // JDK 11 ClientHellos.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: fmt.Sprintf("Server-JDK11-%d", i),
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- SendClientHelloWithFixes: t.clientHello,
- ExpectJDK11DowngradeRandom: t.isJDK11,
- },
- },
- expectations: connectionExpectations{
- version: expectedVersion,
- },
- flags: []string{"-jdk11-workaround"},
- })
-
- // With the workaround disabled, we always negotiate TLS 1.3.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: fmt.Sprintf("Server-JDK11-NoWorkaround-%d", i),
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- SendClientHelloWithFixes: t.clientHello,
- ExpectJDK11DowngradeRandom: false,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS13,
- },
- })
-
- // If the server does not support TLS 1.3, the workaround should
- // be a no-op. In particular, it should not send the downgrade
- // signal.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: fmt.Sprintf("Server-JDK11-TLS12-%d", i),
- config: Config{
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- SendClientHelloWithFixes: t.clientHello,
- ExpectJDK11DowngradeRandom: false,
- },
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- flags: []string{
- "-jdk11-workaround",
- "-max-version", strconv.Itoa(VersionTLS12),
- },
- })
- }
-}
-
-func trustAnchorListFlagValue(ids ...[]byte) string {
- b := cryptobyte.NewBuilder(nil)
- for _, id := range ids {
- addUint8LengthPrefixedBytes(b, id)
- }
- return base64FlagValue(b.BytesOrPanic())
-}
-
-func addTrustAnchorTests() {
- id1 := []byte{1}
- id2 := []byte{2, 2}
- id3 := []byte{3, 3, 3}
-
- // Unsolicited trust_anchors extensions should be rejected.
- testCases = append(testCases, testCase{
- name: "TrustAnchors-Unsolicited-Certificate",
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AlwaysMatchTrustAnchorID: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: unsupported extension",
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- name: "TrustAnchors-Unsolicited-EncryptedExtensions",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{id1, id2},
- Bugs: ProtocolBugs{
- AlwaysSendAvailableTrustAnchors: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: unsupported extension",
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-
- // Test that the client sends trust anchors when configured, and correctly
- // reports the server's response.
- testCases = append(testCases, testCase{
- name: "TrustAnchors-ClientRequest-Match",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{id1, id2},
- Credential: rsaChainCertificate.WithTrustAnchorID(id1),
- Bugs: ProtocolBugs{
- ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
- },
- },
- flags: []string{
- "-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
- "-expect-peer-match-trust-anchor",
- "-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
- },
- })
- // The client should not like it if the server indicates the match with a non-empty
- // extension.
- testCases = append(testCases, testCase{
- name: "TrustAnchors-ClientRequest-Match-Non-Empty-Extension",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{id1, id2},
- Credential: rsaChainCertificate.WithTrustAnchorID(id1),
- Bugs: ProtocolBugs{
- SendNonEmptyTrustAnchorMatch: true,
- ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
- },
- },
- flags: []string{
- "-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
- // The client should not like it if the server indicates the match on the incorrect
- // certificate in the Certificate message.
- testCases = append(testCases, testCase{
- name: "TrustAnchors-ClientRequest-Match-On-Incorrect-Certificate",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{id1, id2},
- Credential: rsaChainCertificate.WithTrustAnchorID(id1),
- Bugs: ProtocolBugs{
- SendTrustAnchorWrongCertificate: true,
- ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
- },
- },
- flags: []string{
- "-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
- },
- shouldFail: true,
- expectedLocalError: "remote error: unsupported extension",
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- name: "TrustAnchors-ClientRequest-NoMatch",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{id1, id2},
- Bugs: ProtocolBugs{
- ExpectPeerRequestedTrustAnchors: [][]byte{id3},
- },
- },
- flags: []string{
- "-requested-trust-anchors", trustAnchorListFlagValue(id3),
- "-expect-no-peer-match-trust-anchor",
- "-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
- },
- })
-
- // An empty trust anchor ID is a syntax error, so most be rejected in both
- // ClientHello and EncryptedExtensions.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrustAnchors-EmptyID-ClientHello",
- config: Config{
- MinVersion: VersionTLS13,
- RequestTrustAnchors: [][]byte{{}},
- },
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
- testCases = append(testCases, testCase{
- name: "TrustAnchors-EmptyID-EncryptedExtensions",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{{}},
- },
- flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
-
- // Test the server selection logic, as well as whether it correctly reports
- // available trust anchors and the match status. (The general selection flow
- // is covered in addCertificateSelectionTests.)
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrustAnchors-ServerSelect-Match",
- config: Config{
- MinVersion: VersionTLS13,
- RequestTrustAnchors: [][]byte{id2},
- Bugs: ProtocolBugs{
- ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
- ExpectPeerMatchTrustAnchor: ptrTo(true),
- },
- },
- shimCredentials: []*Credential{
- rsaCertificate.WithTrustAnchorID(id1),
- rsaCertificate.WithTrustAnchorID(id2),
- },
- flags: []string{"-expect-selected-credential", "1"},
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrustAnchors-ServerSelect-None",
- config: Config{
- MinVersion: VersionTLS13,
- RequestTrustAnchors: [][]byte{id1},
- },
- shimCredentials: []*Credential{
- rsaCertificate.WithTrustAnchorID(id2),
- rsaCertificate.WithTrustAnchorID(id3),
- },
- shouldFail: true,
- expectedError: ":NO_MATCHING_ISSUER:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrustAnchors-ServerSelect-Fallback",
- config: Config{
- MinVersion: VersionTLS13,
- RequestTrustAnchors: [][]byte{id1},
- Bugs: ProtocolBugs{
- ExpectPeerAvailableTrustAnchors: [][]byte{id2, id3},
- ExpectPeerMatchTrustAnchor: ptrTo(false),
- },
- },
- shimCredentials: []*Credential{
- rsaCertificate.WithTrustAnchorID(id2),
- rsaCertificate.WithTrustAnchorID(id3),
- &rsaCertificate,
- },
- flags: []string{"-expect-selected-credential", "2"},
- })
-
- // The ClientHello list may be empty. The client must be able to send it and
- // receive available trust anchors.
- testCases = append(testCases, testCase{
- name: "TrustAnchors-ClientRequestEmpty",
- config: Config{
- MinVersion: VersionTLS13,
- AvailableTrustAnchors: [][]byte{id1, id2},
- Bugs: ProtocolBugs{
- ExpectPeerRequestedTrustAnchors: [][]byte{},
- },
- },
- flags: []string{
- "-requested-trust-anchors", trustAnchorListFlagValue(),
- "-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
- },
- })
- // The server must be able to process it, and send available trust anchors.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrustAnchors-ServerReceiveEmptyRequest",
- config: Config{
- MinVersion: VersionTLS13,
- RequestTrustAnchors: [][]byte{},
- Bugs: ProtocolBugs{
- ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
- ExpectPeerMatchTrustAnchor: ptrTo(false),
- },
- },
- shimCredentials: []*Credential{
- rsaCertificate.WithTrustAnchorID(id1),
- rsaCertificate.WithTrustAnchorID(id2),
- &rsaCertificate,
- },
- flags: []string{"-expect-selected-credential", "2"},
- })
-
- // This extension requires TLS 1.3. If a server receives this and negotiates
- // TLS 1.2, it should ignore the extension and not accidentally send
- // something in ServerHello (implicitly checked by runner).
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "TrustAnchors-TLS12-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- RequestTrustAnchors: [][]byte{id1},
- },
- shimCredentials: []*Credential{
- rsaCertificate.WithTrustAnchorID(id1),
- &rsaCertificate,
- },
- // The first credential is skipped because the extension is ignored.
- flags: []string{"-expect-selected-credential", "1"},
- })
- // The client should reject the extension in TLS 1.2 ServerHello.
- testCases = append(testCases, testCase{
- name: "TrustAnchors-TLS12-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- AvailableTrustAnchors: [][]byte{id1},
- Bugs: ProtocolBugs{
- AlwaysSendAvailableTrustAnchors: true,
- },
- },
- flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
-}
-
-func addDelegatedCredentialTests() {
- p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
- dcAlgo: signatureECDSAWithP256AndSHA256,
- algo: signatureRSAPSSWithSHA256,
- })
- p256DCFromECDSA := createDelegatedCredential(&ecdsaP256Certificate, delegatedCredentialConfig{
- dcAlgo: signatureECDSAWithP256AndSHA256,
- algo: signatureECDSAWithP256AndSHA256,
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-NoClientSupport",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- },
- shimCredentials: []*Credential{p256DC, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-Basic",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- shimCredentials: []*Credential{p256DC, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "0"},
- expectations: connectionExpectations{
- peerCertificate: p256DC,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-ExactAlgorithmMatch",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- // Test that the server doesn't mix up the two signature algorithm
- // fields. These options are a match because the signature_algorithms
- // extension matches against the signature on the delegated
- // credential, while the delegated_credential extension matches
- // against the signature made by the delegated credential.
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- shimCredentials: []*Credential{p256DC, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "0"},
- expectations: connectionExpectations{
- peerCertificate: p256DC,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-SigAlgoMissing",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- // If the client doesn't support the signature in the delegated credential,
- // the server should not use delegated credentials.
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA384},
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- shimCredentials: []*Credential{p256DC, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- })
-
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-CertVerifySigAlgoMissing",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- // If the client doesn't support the delegated credential's
- // CertificateVerify algorithm, the server should not use delegated
- // credentials.
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
- },
- shimCredentials: []*Credential{p256DC, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- })
-
- // Delegated credentials are not supported at TLS 1.2, even if the client
- // sends the extension.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-TLS12-Forbidden",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- shimCredentials: []*Credential{p256DC, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: &rsaCertificate,
- },
- })
-
- // Generate another delegated credential, so we can get the keys out of sync.
- dcWrongKey := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
- algo: signatureRSAPSSWithSHA256,
- })
- dcWrongKey.DelegatedCredential = p256DC.DelegatedCredential
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-KeyMismatch",
- // The handshake hints version of the test will, as a side effect, use a
- // custom private key. Custom private keys can't be checked for key
- // mismatches.
- skipHints: true,
- shimCredentials: []*Credential{dcWrongKey},
- shouldFail: true,
- expectedError: ":KEY_VALUES_MISMATCH:",
- })
-
- // RSA delegated credentials should be rejected at configuration time.
- rsaDC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
- algo: signatureRSAPSSWithSHA256,
- dcAlgo: signatureRSAPSSWithSHA256,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-NoRSA",
- shimCredentials: []*Credential{rsaDC},
- shouldFail: true,
- expectedError: ":INVALID_SIGNATURE_ALGORITHM:",
- })
-
- // If configured with multiple delegated credentials, the server can cleanly
- // select the first one that works.
- p384DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
- dcAlgo: signatureECDSAWithP384AndSHA384,
- algo: signatureRSAPSSWithSHA256,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-Multiple",
- config: Config{
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
- },
- shimCredentials: []*Credential{p256DC, p384DC},
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: p384DC,
- },
- })
-
- // Delegated credentials participate in issuer-based certificate selection.
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "DelegatedCredentials-MatchIssuer",
- config: Config{
- DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- // The client requested p256DCFromECDSA's issuer.
- RootCAs: makeCertPoolFromRoots(p256DCFromECDSA),
- SendRootCAs: true,
- },
- shimCredentials: []*Credential{
- p256DC.WithMustMatchIssuer(true), p256DCFromECDSA.WithMustMatchIssuer(true)},
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: p256DCFromECDSA,
- },
- })
-
-}
-
-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},
- })
- }
-}
-
-func addHintMismatchTests() {
- // Each of these tests skips split handshakes because split handshakes does
- // not handle a mismatch between shim and handshaker. Handshake hints,
- // however, are designed to tolerate the mismatch.
- //
- // Note also these tests do not specify -handshake-hints directly. Instead,
- // we define normal tests, that run even without a handshaker, and rely on
- // convertToSplitHandshakeTests to generate a handshaker hints variant. This
- // avoids repeating the -is-handshaker-supported and -handshaker-path logic.
- // (While not useful, the tests will still pass without a handshaker.)
- for _, protocol := range []protocol{tls, quic} {
- // If the signing payload is different, the handshake still completes
- // successfully. Different ALPN preferences will trigger a mismatch.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-SignatureInput",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- NextProtos: []string{"foo", "bar"},
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-select-alpn", "foo",
- "-on-handshaker-select-alpn", "bar",
- },
- expectations: connectionExpectations{
- nextProto: "foo",
- nextProtoType: alpn,
- },
- })
-
- // The shim and handshaker may have different curve preferences.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-KeyShare",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- // Send both curves in the key share list, to avoid getting
- // mixed up with HelloRetryRequest.
- DefaultCurves: []CurveID{CurveX25519, CurveP256},
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-curves", strconv.Itoa(int(CurveX25519)),
- "-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
- },
- expectations: connectionExpectations{
- curveID: CurveX25519,
- },
- })
- if protocol != quic {
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-ECDHE-Group",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- DefaultCurves: []CurveID{CurveX25519, CurveP256},
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-curves", strconv.Itoa(int(CurveX25519)),
- "-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
- },
- expectations: connectionExpectations{
- curveID: CurveX25519,
- },
- })
- }
-
- // If the handshaker does HelloRetryRequest, it will omit most hints.
- // The shim should still work.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-HandshakerHelloRetryRequest",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{CurveX25519},
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-curves", strconv.Itoa(int(CurveX25519)),
- "-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
- },
- expectations: connectionExpectations{
- curveID: CurveX25519,
- },
- })
-
- // If the shim does HelloRetryRequest, the hints from the handshaker
- // will be ignored. This is not reported as a mismatch because hints
- // would not have helped the shim anyway.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-ShimHelloRetryRequest",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- DefaultCurves: []CurveID{CurveX25519},
- },
- flags: []string{
- "-on-shim-curves", strconv.Itoa(int(CurveP256)),
- "-on-handshaker-curves", strconv.Itoa(int(CurveX25519)),
- },
- expectations: connectionExpectations{
- curveID: CurveP256,
- },
- })
-
- // The shim and handshaker may have different signature algorithm
- // preferences.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS13",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA256,
- signatureRSAPSSWithSHA384,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- handshakerCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
- flags: []string{"-allow-hint-mismatch"},
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
- },
- })
- if protocol != quic {
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS12",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- VerifySignatureAlgorithms: []signatureAlgorithm{
- signatureRSAPSSWithSHA256,
- signatureRSAPSSWithSHA384,
- },
- },
- shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- handshakerCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
- flags: []string{"-allow-hint-mismatch"},
- expectations: connectionExpectations{
- peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
- },
- })
- }
-
- // The shim and handshaker may use different certificates. In TLS 1.3,
- // the signature input includes the certificate, so we do not need to
- // explicitly check for a public key match. In TLS 1.2, it does not.
- ecdsaP256Certificate2 := generateSingleCertChain(nil, &channelIDKey)
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-Certificate-TLS13",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- },
- shimCertificate: &ecdsaP256Certificate,
- handshakerCertificate: &ecdsaP256Certificate2,
- flags: []string{"-allow-hint-mismatch"},
- expectations: connectionExpectations{
- peerCertificate: &ecdsaP256Certificate,
- },
- })
- if protocol != quic {
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-Certificate-TLS12",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- shimCertificate: &ecdsaP256Certificate,
- handshakerCertificate: &ecdsaP256Certificate2,
- flags: []string{"-allow-hint-mismatch"},
- expectations: connectionExpectations{
- peerCertificate: &ecdsaP256Certificate,
- },
- })
- }
-
- // The shim and handshaker may disagree on whether resumption is allowed.
- // We run the first connection with tickets enabled, so the client is
- // issued a ticket, then disable tickets on the second connection.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-NoTickets1-TLS13",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- },
- flags: []string{
- "-on-resume-allow-hint-mismatch",
- "-on-shim-on-resume-no-ticket",
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-NoTickets2-TLS13",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- },
- flags: []string{
- "-on-resume-allow-hint-mismatch",
- "-on-handshaker-on-resume-no-ticket",
- },
- resumeSession: true,
- })
- if protocol != quic {
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-NoTickets1-TLS12",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- flags: []string{
- "-on-resume-allow-hint-mismatch",
- "-on-shim-on-resume-no-ticket",
- },
- resumeSession: true,
- expectResumeRejected: true,
- })
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-NoTickets2-TLS12",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- flags: []string{
- "-on-resume-allow-hint-mismatch",
- "-on-handshaker-on-resume-no-ticket",
- },
- resumeSession: true,
- })
- }
-
- // The shim and handshaker may disagree on whether to request a client
- // certificate.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-CertificateRequest",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- Credential: &rsaCertificate,
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-require-any-client-certificate",
- },
- })
-
- // The shim and handshaker may negotiate different versions altogether.
- if protocol != quic {
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-Version1",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS13,
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-max-version", strconv.Itoa(VersionTLS12),
- "-on-handshaker-max-version", strconv.Itoa(VersionTLS13),
- },
- expectations: connectionExpectations{
- version: VersionTLS12,
- },
- })
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-Version2",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS13,
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-max-version", strconv.Itoa(VersionTLS13),
- "-on-handshaker-max-version", strconv.Itoa(VersionTLS12),
- },
- expectations: connectionExpectations{
- version: VersionTLS13,
- },
- })
- }
-
- // The shim and handshaker may disagree on the certificate compression
- // algorithm, whether to enable certificate compression, or certificate
- // compression inputs.
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-CertificateCompression-ShimOnly",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- },
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-install-cert-compression-algs",
- },
- })
- testCases = append(testCases, testCase{
- name: protocol.String() + "-HintMismatch-CertificateCompression-HandshakerOnly",
- testType: serverTest,
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectUncompressedCert: true,
- },
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-handshaker-install-cert-compression-algs",
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: protocol.String() + "-HintMismatch-CertificateCompression-AlgorithmMismatch",
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- expandingCompressionAlgID: expandingCompression,
- },
- Bugs: ProtocolBugs{
- // The shim's preferences should take effect.
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- },
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID),
- "-on-handshaker-install-one-cert-compression-alg", strconv.Itoa(expandingCompressionAlgID),
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: protocol.String() + "-HintMismatch-CertificateCompression-InputMismatch",
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CertCompressionAlgs: map[uint16]CertCompressionAlg{
- shrinkingCompressionAlgID: shrinkingCompression,
- },
- Bugs: ProtocolBugs{
- ExpectedCompressedCert: shrinkingCompressionAlgID,
- },
- },
- // Configure the shim and handshaker with different OCSP responses,
- // so the compression inputs do not match.
- shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- handshakerCertificate: rsaCertificate.WithOCSP(testOCSPResponse2),
- flags: []string{
- "-allow-hint-mismatch",
- "-install-cert-compression-algs",
- },
- expectations: connectionExpectations{
- // The shim's configuration should take precendence.
- peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
- },
- })
-
- // The shim and handshaker may disagree on cipher suite, to the point
- // that one selects RSA key exchange (no applicable hint) and the other
- // selects ECDHE_RSA (hints are useful).
- if protocol != quic {
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: protocol.String() + "-HintMismatch-CipherMismatch1",
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- flags: []string{
- "-allow-hint-mismatch",
- "-on-shim-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- "-on-handshaker-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
- },
- expectations: connectionExpectations{
- cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: protocol.String() + "-HintMismatch-CipherMismatch2",
- protocol: protocol,
- skipSplitHandshake: true,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- flags: []string{
- // There is no need to pass -allow-hint-mismatch. The
- // handshaker will unnecessarily generate a signature hints.
- // This is not reported as a mismatch because hints would
- // not have helped the shim anyway.
- "-on-shim-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
- "-on-handshaker-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- },
- expectations: connectionExpectations{
- cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
- },
- })
- }
- }
-}
-
-func addCompliancePolicyTests() {
- for _, protocol := range []protocol{tls, quic} {
- for _, suite := range testCipherSuites {
- var isFIPSCipherSuite bool
- switch suite.id {
- case TLS_AES_128_GCM_SHA256,
- TLS_AES_256_GCM_SHA384,
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
- isFIPSCipherSuite = true
- }
-
- var isWPACipherSuite bool
- switch suite.id {
- case TLS_AES_256_GCM_SHA384,
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
- isWPACipherSuite = true
- }
-
- var cert Credential
- if hasComponent(suite.name, "ECDSA") {
- cert = ecdsaP384Certificate
- } else {
- cert = rsaCertificate
- }
-
- maxVersion := uint16(VersionTLS13)
- if !isTLS13Suite(suite.name) {
- if protocol == quic {
- continue
- }
- maxVersion = VersionTLS12
- }
-
- policies := []struct {
- flag string
- cipherSuiteOk bool
- }{
- {"-fips-202205", isFIPSCipherSuite},
- {"-wpa-202304", isWPACipherSuite},
- }
-
- for _, policy := range policies {
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + suite.name,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: maxVersion,
- CipherSuites: []uint16{suite.id},
- },
- shimCertificate: &cert,
- flags: []string{
- policy.flag,
- },
- shouldFail: !policy.cipherSuiteOk,
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + suite.name,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: maxVersion,
- CipherSuites: []uint16{suite.id},
- Credential: &cert,
- },
- flags: []string{
- policy.flag,
- },
- shouldFail: !policy.cipherSuiteOk,
- })
- }
- }
-
- // Check that a TLS 1.3 client won't accept ChaCha20 even if the server
- // picks it without it being in the client's cipher list.
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: "Compliance-fips202205-" + protocol.String() + "-Client-ReallyWontAcceptChaCha",
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: maxVersion,
- Bugs: ProtocolBugs{
- SendCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
- },
- },
- flags: []string{
- "-fips-202205",
- },
- shouldFail: true,
- expectedError: ":WRONG_CIPHER_RETURNED:",
- })
-
- for _, curve := range testCurves {
- var isFIPSCurve bool
- switch curve.id {
- case CurveP256, CurveP384:
- isFIPSCurve = true
- }
-
- var isWPACurve bool
- switch curve.id {
- case CurveP384:
- isWPACurve = true
- }
-
- policies := []struct {
- flag string
- curveOk bool
- }{
- {"-fips-202205", isFIPSCurve},
- {"-wpa-202304", isWPACurve},
- }
-
- for _, policy := range policies {
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + curve.name,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{curve.id},
- },
- flags: []string{
- policy.flag,
- },
- shouldFail: !policy.curveOk,
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + curve.name,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{curve.id},
- },
- flags: []string{
- policy.flag,
- },
- shouldFail: !policy.curveOk,
- })
- }
- }
-
- for _, sigalg := range testSignatureAlgorithms {
- // The TLS 1.0 and TLS 1.1 default signature algorithm does not
- // apply to these tests.
- if sigalg.id == 0 {
- continue
- }
-
- var isFIPSSigAlg bool
- switch sigalg.id {
- case signatureRSAPKCS1WithSHA256,
- signatureRSAPKCS1WithSHA384,
- signatureRSAPKCS1WithSHA512,
- signatureECDSAWithP256AndSHA256,
- signatureECDSAWithP384AndSHA384,
- signatureRSAPSSWithSHA256,
- signatureRSAPSSWithSHA384,
- signatureRSAPSSWithSHA512:
- isFIPSSigAlg = true
- }
-
- var isWPASigAlg bool
- switch sigalg.id {
- case signatureRSAPKCS1WithSHA384,
- signatureRSAPKCS1WithSHA512,
- signatureECDSAWithP384AndSHA384,
- signatureRSAPSSWithSHA384,
- signatureRSAPSSWithSHA512:
- isWPASigAlg = true
- }
-
- if sigalg.curve == CurveP224 {
- // This can work in TLS 1.2, but not with TLS 1.3.
- // For consistency it's not permitted in FIPS mode.
- isFIPSSigAlg = false
- }
-
- maxVersion := uint16(VersionTLS13)
- if hasComponent(sigalg.name, "PKCS1") {
- if protocol == quic {
- continue
- }
- maxVersion = VersionTLS12
- }
-
- policies := []struct {
- flag string
- sigAlgOk bool
- }{
- {"-fips-202205", isFIPSSigAlg},
- {"-wpa-202304", isWPASigAlg},
- }
-
- cert := sigalg.baseCert.WithSignatureAlgorithms(sigalg.id)
- for _, policy := range policies {
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + sigalg.name,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: maxVersion,
- VerifySignatureAlgorithms: []signatureAlgorithm{sigalg.id},
- },
- // Use the base certificate. We wish to pick up the signature algorithm
- // preferences from the FIPS policy.
- shimCertificate: sigalg.baseCert,
- flags: []string{policy.flag},
- shouldFail: !policy.sigAlgOk,
- })
-
- testCases = append(testCases, testCase{
- testType: clientTest,
- protocol: protocol,
- name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + sigalg.name,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: maxVersion,
- Credential: cert,
- },
- flags: []string{
- policy.flag,
- },
- shouldFail: !policy.sigAlgOk,
- })
- }
- }
-
- // AES-256-GCM is the most preferred.
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Compliance-cnsa202407-" + protocol.String() + "-AES-256-preferred",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384},
- },
- flags: []string{
- "-cnsa-202407",
- },
- expectations: connectionExpectations{cipher: TLS_AES_256_GCM_SHA384},
- })
-
- // AES-128-GCM is preferred over ChaCha20-Poly1305.
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: protocol,
- name: "Compliance-cnsa202407-" + protocol.String() + "-AES-128-preferred",
- config: Config{
- MinVersion: VersionTLS13,
- MaxVersion: VersionTLS13,
- CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
- },
- flags: []string{
- "-cnsa-202407",
- },
- expectations: connectionExpectations{cipher: TLS_AES_128_GCM_SHA256},
- })
- }
-}
-
-func canBeShimCertificate(c *Credential) bool {
- // Some options can only be set with the credentials API.
- return c.Type == CredentialTypeX509 && !c.MustMatchIssuer && c.TrustAnchorID == nil
-}
-
-func addCertificateSelectionTests() {
- // Combinatorially test each selection criteria at different versions,
- // protocols, and with the matching certificate before and after the
- // mismatching one.
- type certSelectTest struct {
- name string
- testType testType
- minVersion uint16
- maxVersion uint16
- config Config
- match *Credential
- mismatch *Credential
- flags []string
- expectedError string
- }
- certSelectTests := []certSelectTest{
- // TLS 1.0 through TLS 1.2 servers should incorporate TLS cipher suites
- // into certificate selection.
- {
- name: "Server-CipherSuite-ECDHE_ECDSA",
- testType: serverTest,
- maxVersion: VersionTLS12,
- config: Config{
- CipherSuites: []uint16{
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &ecdsaP256Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
- {
- name: "Server-CipherSuite-ECDHE_RSA",
- testType: serverTest,
- maxVersion: VersionTLS12,
- config: Config{
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &rsaCertificate,
- mismatch: &ecdsaP256Certificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
- {
- name: "Server-CipherSuite-RSA",
- testType: serverTest,
- maxVersion: VersionTLS12,
- config: Config{
- CipherSuites: []uint16{
- TLS_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &rsaCertificate,
- mismatch: &ecdsaP256Certificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
-
- // Ed25519 counts as ECDSA for purposes of cipher suite matching.
- {
- name: "Server-CipherSuite-ECDHE_ECDSA-Ed25519",
- testType: serverTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- CipherSuites: []uint16{
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &ed25519Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
- {
- name: "Server-CipherSuite-ECDHE_RSA-Ed25519",
- testType: serverTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &rsaCertificate,
- mismatch: &ed25519Certificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
-
- // If there is no ECDHE curve match, ECDHE cipher suites are
- // disqualified in TLS 1.2 and below. This, in turn, impacts the
- // available cipher suites for each credential.
- {
- name: "Server-CipherSuite-NoECDHE",
- testType: serverTest,
- maxVersion: VersionTLS12,
- config: Config{
- CurvePreferences: []CurveID{CurveP256},
- },
- flags: []string{"-curves", strconv.Itoa(int(CurveX25519))},
- match: &rsaCertificate,
- mismatch: &ecdsaP256Certificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
-
- // If the client offered a cipher that would allow a certificate, but it
- // wasn't one of the ones we configured, the certificate should be
- // skipped in favor of another one.
- {
- name: "Server-CipherSuite-Prefs",
- testType: serverTest,
- maxVersion: VersionTLS12,
- config: Config{
- CipherSuites: []uint16{
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- flags: []string{"-cipher", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"},
- match: &rsaCertificate,
- mismatch: &ecdsaP256Certificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
-
- // TLS 1.0 through 1.2 servers should incorporate the curve list into
- // ECDSA certificate selection.
- {
- name: "Server-Curve",
- testType: serverTest,
- maxVersion: VersionTLS12,
- config: Config{
- CurvePreferences: []CurveID{CurveP256},
- },
- match: &ecdsaP256Certificate,
- mismatch: &ecdsaP384Certificate,
- expectedError: ":WRONG_CURVE:",
- },
-
- // TLS 1.3 servers ignore the curve list. ECDSA certificate selection is
- // solely determined by the signature algorithm list.
- {
- name: "Server-IgnoreCurve",
- testType: serverTest,
- minVersion: VersionTLS13,
- config: Config{
- CurvePreferences: []CurveID{CurveP256},
- },
- match: &ecdsaP384Certificate,
- },
-
- // TLS 1.2 servers also ignore the curve list for Ed25519. The signature
- // algorithm list is sufficient for Ed25519.
- {
- name: "Server-IgnoreCurveEd25519",
- testType: serverTest,
- minVersion: VersionTLS12,
- config: Config{
- CurvePreferences: []CurveID{CurveP256},
- },
- match: &ed25519Certificate,
- },
-
- // Without signature algorithm negotiation, Ed25519 is not usable in TLS
- // 1.1 and below.
- {
- name: "Server-NoEd25519",
- testType: serverTest,
- maxVersion: VersionTLS11,
- match: &rsaCertificate,
- mismatch: &ed25519Certificate,
- },
-
- // TLS 1.2 and up should incorporate the signature algorithm list into
- // certificate selection.
- {
- name: "Server-SignatureAlgorithm",
- testType: serverTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- CipherSuites: []uint16{
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &ecdsaP256Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":NO_SHARED_CIPHER:",
- },
- {
- name: "Server-SignatureAlgorithm",
- testType: serverTest,
- minVersion: VersionTLS13,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &ecdsaP256Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- },
-
- // TLS 1.2's use of the signature algorithm list only disables the
- // signing-based algorithms. If an RSA key exchange cipher suite is
- // eligible, that is fine. (This is not a realistic configuration,
- // however. No one would configure RSA before ECDSA.)
- {
- name: "Server-SignatureAlgorithmImpactsECDHEOnly",
- testType: serverTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_RSA_WITH_AES_128_CBC_SHA,
- },
- },
- match: &rsaCertificate,
- },
-
- // TLS 1.3's use of the signature algorithm looks at the ECDSA curve
- // embedded in the signature algorithm.
- {
- name: "Server-SignatureAlgorithmECDSACurve",
- testType: serverTest,
- minVersion: VersionTLS13,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &ecdsaP256Certificate,
- mismatch: &ecdsaP384Certificate,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- },
-
- // TLS 1.2's use does not.
- {
- name: "Server-SignatureAlgorithmECDSACurve",
- testType: serverTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &ecdsaP384Certificate,
- },
-
- // TLS 1.0 and 1.1 do not look at the signature algorithm.
- {
- name: "Server-IgnoreSignatureAlgorithm",
- testType: serverTest,
- maxVersion: VersionTLS11,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &rsaCertificate,
- },
-
- // Signature algorithm matches take preferences on the keys into
- // consideration.
- {
- name: "Server-SignatureAlgorithmKeyPrefs",
- testType: serverTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- },
- match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
- expectedError: ":NO_SHARED_CIPHER:",
- },
- {
- name: "Server-SignatureAlgorithmKeyPrefs",
- testType: serverTest,
- minVersion: VersionTLS13,
- config: Config{
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
- },
- match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- },
-
- // TLS 1.2 clients and below check the certificate against the old
- // client certificate types field.
- {
- name: "Client-ClientCertificateTypes-RSA",
- testType: clientTest,
- maxVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCertificateTypes: []uint8{CertTypeRSASign},
- },
- match: &rsaCertificate,
- mismatch: &ecdsaP256Certificate,
- expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
- },
- {
- name: "Client-ClientCertificateTypes-ECDSA",
- testType: clientTest,
- maxVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCertificateTypes: []uint8{CertTypeECDSASign},
- },
- match: &ecdsaP256Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
- },
-
- // Ed25519 is considered ECDSA for purposes of client certificate types.
- {
- name: "Client-ClientCertificateTypes-RSA-Ed25519",
- testType: clientTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCertificateTypes: []uint8{CertTypeRSASign},
- },
- match: &rsaCertificate,
- mismatch: &ed25519Certificate,
- expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
- },
- {
- name: "Client-ClientCertificateTypes-ECDSA-Ed25519",
- testType: clientTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCertificateTypes: []uint8{CertTypeECDSASign},
- },
- match: &ed25519Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
- },
-
- // TLS 1.2 and up should incorporate the signature algorithm list into
- // certificate selection. (There is no signature algorithm list to look
- // at in TLS 1.0 and 1.1.)
- {
- name: "Client-SignatureAlgorithm",
- testType: clientTest,
- minVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &ecdsaP256Certificate,
- mismatch: &rsaCertificate,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- },
-
- // TLS 1.3's use of the signature algorithm looks at the ECDSA curve
- // embedded in the signature algorithm.
- {
- name: "Client-SignatureAlgorithmECDSACurve",
- testType: clientTest,
- minVersion: VersionTLS13,
- config: Config{
- ClientAuth: RequestClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &ecdsaP256Certificate,
- mismatch: &ecdsaP384Certificate,
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- },
-
- // TLS 1.2's use does not. It is not possible to determine what ECDSA
- // curves are allowed by the server.
- {
- name: "Client-SignatureAlgorithmECDSACurve",
- testType: clientTest,
- minVersion: VersionTLS12,
- maxVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
- },
- match: &ecdsaP384Certificate,
- },
-
- // Signature algorithm matches take preferences on the keys into
- // consideration.
- {
- name: "Client-SignatureAlgorithmKeyPrefs",
- testType: clientTest,
- minVersion: VersionTLS12,
- config: Config{
- ClientAuth: RequestClientCert,
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
- },
- match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
- mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
- expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
- },
-
- // By default, certificate selection does not take issuers
- // into account.
- {
- name: "Client-DontCheckIssuer",
- testType: clientTest,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
- },
- match: &ecdsaP256Certificate,
- },
- {
- name: "Server-DontCheckIssuer",
- testType: serverTest,
- config: Config{
- RootCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
- SendRootCAs: true,
- },
- match: &ecdsaP256Certificate,
- },
-
- // If requested, certificate selection will match against the
- // requested issuers.
- {
- name: "Client-CheckIssuer",
- testType: clientTest,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
- },
- match: rsaChainCertificate.WithMustMatchIssuer(true),
- mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
- expectedError: ":NO_MATCHING_ISSUER:",
- },
- {
- name: "Server-CheckIssuer",
- testType: serverTest,
- config: Config{
- RootCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
- SendRootCAs: true,
- },
- match: rsaChainCertificate.WithMustMatchIssuer(true),
- mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
- expectedError: ":NO_MATCHING_ISSUER:",
- },
-
- // Trust anchor IDs can also be used to match issuers.
- // TODO(crbug.com/398275713): Implement this for client certificates.
- {
- name: "Server-CheckIssuer-TrustAnchorIDs",
- testType: serverTest,
- minVersion: VersionTLS13,
- config: Config{
- RequestTrustAnchors: [][]byte{{1, 1, 1}},
- },
- match: rsaChainCertificate.WithTrustAnchorID([]byte{1, 1, 1}),
- mismatch: ecdsaP256Certificate.WithTrustAnchorID([]byte{2, 2, 2}),
- expectedError: ":NO_MATCHING_ISSUER:",
- },
-
- // When an issuer-gated credential fails, a normal credential may be
- // selected instead.
- {
- name: "Client-CheckIssuerFallback",
- testType: clientTest,
- config: Config{
- ClientAuth: RequestClientCert,
- ClientCAs: makeCertPoolFromRoots(&ecdsaP384Certificate),
- },
- match: &rsaChainCertificate,
- mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
- expectedError: ":NO_MATCHING_ISSUER:",
- },
- {
- name: "Server-CheckIssuerFallback",
- testType: serverTest,
- config: Config{
- RootCAs: makeCertPoolFromRoots(&ecdsaP384Certificate),
- SendRootCAs: true,
- },
- match: &rsaChainCertificate,
- mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
- expectedError: ":NO_MATCHING_ISSUER:",
- },
- {
- name: "Server-CheckIssuerFallback-TrustAnchorIDs",
- testType: serverTest,
- minVersion: VersionTLS13,
- config: Config{
- RequestTrustAnchors: [][]byte{{1, 1, 1}},
- },
- match: &rsaChainCertificate,
- mismatch: ecdsaP256Certificate.WithTrustAnchorID([]byte{2, 2, 2}),
- expectedError: ":NO_MATCHING_ISSUER:",
- },
- }
-
- for _, protocol := range []protocol{tls, dtls} {
- for _, vers := range allVersions(protocol) {
- suffix := fmt.Sprintf("%s-%s", protocol, vers)
-
- // Test that the credential list is interpreted in preference order,
- // with the default credential, if any, at the end.
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-Client-PreferenceOrder-%s", suffix),
- testType: clientTest,
- protocol: protocol,
- config: Config{
- MinVersion: vers.version,
- MaxVersion: vers.version,
- ClientAuth: RequestClientCert,
- },
- shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
- shimCertificate: &rsaCertificate,
- flags: []string{"-expect-selected-credential", "0"},
- expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
- })
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-Server-PreferenceOrder-%s", suffix),
- testType: serverTest,
- protocol: protocol,
- config: Config{
- MinVersion: vers.version,
- MaxVersion: vers.version,
- },
- shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
- shimCertificate: &rsaCertificate,
- flags: []string{"-expect-selected-credential", "0"},
- expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
- })
-
- // Test that the selected credential contributes the certificate chain, OCSP response,
- // and SCT list.
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-Server-OCSP-SCT-%s", suffix),
- testType: serverTest,
- protocol: protocol,
- config: Config{
- MinVersion: vers.version,
- MaxVersion: vers.version,
- // Configure enough options so that, at all TLS versions, only an RSA
- // certificate will be accepted.
- CipherSuites: []uint16{
- TLS_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- },
- VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
- },
- shimCredentials: []*Credential{
- ecdsaP256Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
- rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- },
- shimCertificate: ecdsaP384Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
- flags: []string{"-expect-selected-credential", "1"},
- expectations: connectionExpectations{
- peerCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
- },
- })
-
- // Test that the credentials API works asynchronously. This tests both deferring the
- // configuration to the certificate callback, and using a custom, async private key.
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-Client-Async-%s", suffix),
- testType: clientTest,
- protocol: protocol,
- config: Config{
- MinVersion: vers.version,
- MaxVersion: vers.version,
- ClientAuth: RequestClientCert,
- },
- shimCredentials: []*Credential{&ecdsaP256Certificate},
- shimCertificate: &rsaCertificate,
- flags: []string{"-async", "-expect-selected-credential", "0"},
- expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
- })
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-Server-Async-%s", suffix),
- testType: serverTest,
- protocol: protocol,
- config: Config{
- MinVersion: vers.version,
- MaxVersion: vers.version,
- },
- shimCredentials: []*Credential{&ecdsaP256Certificate},
- shimCertificate: &rsaCertificate,
- flags: []string{"-async", "-expect-selected-credential", "0"},
- expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
- })
-
- for _, test := range certSelectTests {
- if test.minVersion != 0 && vers.version < test.minVersion {
- continue
- }
- if test.maxVersion != 0 && vers.version > test.maxVersion {
- continue
- }
-
- config := test.config
- config.MinVersion = vers.version
- config.MaxVersion = vers.version
-
- // If the mismatch field is omitted, this is a positive test,
- // just to confirm that the selection logic does not block a
- // particular certificate.
- if test.mismatch == nil {
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-%s-%s", test.name, suffix),
- protocol: protocol,
- testType: test.testType,
- config: config,
- shimCredentials: []*Credential{test.match},
- flags: append([]string{"-expect-selected-credential", "0"}, test.flags...),
- expectations: connectionExpectations{peerCertificate: test.match},
- })
- continue
- }
-
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-%s-MatchFirst-%s", test.name, suffix),
- protocol: protocol,
- testType: test.testType,
- config: config,
- shimCredentials: []*Credential{test.match, test.mismatch},
- flags: append([]string{"-expect-selected-credential", "0"}, test.flags...),
- expectations: connectionExpectations{peerCertificate: test.match},
- })
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-%s-MatchSecond-%s", test.name, suffix),
- protocol: protocol,
- testType: test.testType,
- config: config,
- shimCredentials: []*Credential{test.mismatch, test.match},
- flags: append([]string{"-expect-selected-credential", "1"}, test.flags...),
- expectations: connectionExpectations{peerCertificate: test.match},
- })
- if canBeShimCertificate(test.match) {
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-%s-MatchDefault-%s", test.name, suffix),
- protocol: protocol,
- testType: test.testType,
- config: config,
- shimCredentials: []*Credential{test.mismatch},
- shimCertificate: test.match,
- flags: append([]string{"-expect-selected-credential", "-1"}, test.flags...),
- expectations: connectionExpectations{peerCertificate: test.match},
- })
- }
- testCases = append(testCases, testCase{
- name: fmt.Sprintf("CertificateSelection-%s-MatchNone-%s", test.name, suffix),
- protocol: protocol,
- testType: test.testType,
- config: config,
- shimCredentials: []*Credential{test.mismatch, test.mismatch, test.mismatch},
- flags: test.flags,
- shouldFail: true,
- expectedLocalError: "remote error: handshake failure",
- expectedError: test.expectedError,
- })
- }
- }
- }
-}
-
-func addKeyUpdateTests() {
- // TLS tests.
- testCases = append(testCases, testCase{
- name: "KeyUpdate-ToClient",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendKeyUpdates: 10,
- keyUpdateRequest: keyUpdateNotRequested,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "KeyUpdate-ToServer",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendKeyUpdates: 10,
- keyUpdateRequest: keyUpdateNotRequested,
- })
- testCases = append(testCases, testCase{
- name: "KeyUpdate-FromClient",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- expectUnsolicitedKeyUpdate: true,
- flags: []string{"-key-update"},
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "KeyUpdate-FromServer",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- expectUnsolicitedKeyUpdate: true,
- flags: []string{"-key-update"},
- })
- testCases = append(testCases, testCase{
- name: "KeyUpdate-InvalidRequestMode",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendKeyUpdates: 1,
- keyUpdateRequest: 42,
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
- testCases = append(testCases, testCase{
- // Test that shim responds to KeyUpdate requests.
- name: "KeyUpdate-Requested",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RejectUnsolicitedKeyUpdate: true,
- },
- },
- // Test the shim receiving many KeyUpdates in a row.
- sendKeyUpdates: 5,
- messageCount: 5,
- keyUpdateRequest: keyUpdateRequested,
- })
- testCases = append(testCases, testCase{
- // Test that shim responds to KeyUpdate requests if peer's KeyUpdate is
- // discovered while a write is pending.
- name: "KeyUpdate-Requested-UnfinishedWrite",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RejectUnsolicitedKeyUpdate: true,
- },
- },
- // Test the shim receiving many KeyUpdates in a row.
- sendKeyUpdates: 5,
- messageCount: 5,
- keyUpdateRequest: keyUpdateRequested,
- readWithUnfinishedWrite: true,
- flags: []string{"-async"},
- })
-
- // DTLS tests.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-ToClient-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- // Send many KeyUpdates to make sure record reassembly can handle it.
- sendKeyUpdates: 10,
- keyUpdateRequest: keyUpdateNotRequested,
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "KeyUpdate-ToServer-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- sendKeyUpdates: 10,
- keyUpdateRequest: keyUpdateNotRequested,
- })
-
- // Test that the shim accounts for packet loss when processing KeyUpdate.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-ToClient-PacketLoss-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[0].Type != typeKeyUpdate {
- c.WriteFlight(next)
- return
- }
-
- // Send the KeyUpdate. The shim should ACK it.
- c.WriteFlight(next)
- ackTimeout := timeouts[0] / 4
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // The shim should continue reading data at the old epoch.
- // The ACK may not have come through.
- msg := []byte("test")
- c.WriteAppData(c.OutEpoch()-1, msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // Re-send KeyUpdate. The shim should ACK it again. The ACK
- // may not have come through.
- c.WriteFlight(next)
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // The shim should be able to read data at the new epoch.
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // The shim continues to accept application data at the old
- // epoch, for a period of time.
- c.WriteAppData(c.OutEpoch()-1, msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
-
- // It will even ACK the retransmission, though it knows the
- // shim has seen the ACK.
- c.WriteFlight(next)
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // After some time has passed, the shim will discard the old
- // epoch. The following writes should be ignored.
- c.AdvanceClock(dtlsPrevEpochExpiration)
- f := next[0].Fragment(0, len(next[0].Data))
- f.ShouldDiscard = true
- c.WriteFragments([]DTLSFragment{f})
- c.WriteAppData(c.OutEpoch()-1, msg)
- },
- },
- },
- sendKeyUpdates: 10,
- keyUpdateRequest: keyUpdateNotRequested,
- flags: []string{"-async"},
- })
-
- // In DTLS, we KeyUpdate before read, rather than write, because the
- // KeyUpdate will not be applied before the shim reads the ACK.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-FromClient-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- shimSendsKeyUpdateBeforeRead: true,
- // Perform several message exchanges to update keys several times.
- messageCount: 10,
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "KeyUpdate-FromServer-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- shimSendsKeyUpdateBeforeRead: true,
- // Perform several message exchanges to update keys several times.
- messageCount: 10,
- // Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
- flags: []string{"-no-ticket"},
- })
-
- // If the shim has a pending unACKed flight, it defers sending KeyUpdate.
- // BoringSSL does not support multiple outgoing flights at once.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-DeferredSend-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- // Request a client certificate, so the shim has more to send.
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- MaxPacketLength: 512,
- ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- if received[len(received)-1].Type != typeFinished {
- c.WriteACK(c.OutEpoch(), records)
- return
- }
-
- // This test relies on the Finished flight being multiple
- // records.
- if len(records) <= 1 {
- panic("shim sent Finished flight in one record")
- }
-
- // Before ACKing Finished, do some rounds of exchanging
- // application data. Although the shim has already scheduled
- // KeyUpdate, it should not send the KeyUpdate until it gets
- // an ACK. (If it sent KeyUpdate, ReadAppData would report
- // an unexpected record.)
- msg := []byte("test")
- for i := 0; i < 10; i++ {
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
- }
-
- // ACK some of the Finished flight, but not all of it.
- c.WriteACK(c.OutEpoch(), records[:1])
-
- // The shim continues to defer KeyUpdate.
- for i := 0; i < 10; i++ {
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
- }
-
- // ACK the remainder.
- c.WriteACK(c.OutEpoch(), records[1:])
-
- // The shim should now send KeyUpdate. Return to the test
- // harness, which will look for it.
- },
- },
- },
- shimCertificate: &rsaChainCertificate,
- shimSendsKeyUpdateBeforeRead: true,
- flags: []string{"-mtu", "512"},
- })
-
- // The shim should not switch keys until it receives an ACK.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-WaitForACK-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- MaxPacketLength: 512,
- ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- if received[0].Type != typeKeyUpdate {
- c.WriteACK(c.OutEpoch(), records)
- return
- }
-
- // Make the shim send application data. We have not yet
- // ACKed KeyUpdate, so the shim should send at the previous
- // epoch. Through each of these rounds, the shim will also
- // try to KeyUpdate again. These calls will be suppressed
- // because there is still an outstanding KeyUpdate.
- msg := []byte("test")
- for i := 0; i < 10; i++ {
- c.WriteAppData(c.OutEpoch(), msg)
- c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
- }
-
- // ACK the KeyUpdate. Ideally we'd test a partial ACK, but
- // BoringSSL's minimum MTU is such that KeyUpdate always
- // fits in one record.
- c.WriteACK(c.OutEpoch(), records)
-
- // The shim should now send at the new epoch. Return to the
- // test harness, which will enforce this.
- },
- },
- },
- shimSendsKeyUpdateBeforeRead: true,
- })
-
- // Test that shim responds to KeyUpdate requests.
- fixKeyUpdateReply := func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
- c.WriteACK(c.OutEpoch(), records)
- if received[0].Type != typeKeyUpdate {
- return
- }
- // This works around an awkward testing mismatch. The test
- // harness expects the shim to immediately change keys, but
- // the shim writes app data before seeing the ACK. The app
- // data will be sent at the previous epoch. Consume this and
- // prime the shim to resend its reply at the new epoch.
- msg := makeTestMessage(int(received[0].Sequence)-2, 32)
- c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
- c.WriteAppData(c.OutEpoch(), msg)
- }
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-Requested-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- RejectUnsolicitedKeyUpdate: true,
- ACKFlightDTLS: fixKeyUpdateReply,
- },
- },
- // Test the shim receiving many KeyUpdates in a row. They will be
- // combined into one reply KeyUpdate.
- sendKeyUpdates: 5,
- messageLen: 32,
- messageCount: 5,
- keyUpdateRequest: keyUpdateRequested,
- })
-
- mergeNewSessionTicketAndKeyUpdate := func(f WriteFlightFunc) WriteFlightFunc {
- return func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- // Send NewSessionTicket and the first KeyUpdate all together.
- if next[0].Type == typeKeyUpdate {
- panic("key update should have been merged into NewSessionTicket")
- }
- if next[0].Type != typeNewSessionTicket {
- c.WriteFlight(next)
- return
- }
- if next[0].Type == typeNewSessionTicket && next[len(next)-1].Type != typeKeyUpdate {
- c.MergeIntoNextFlight()
- return
- }
-
- f(c, prev, received, next, records)
- }
- }
-
- // Test that the shim does not process KeyUpdate until it has processed all
- // preceding messages.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-ProcessInOrder-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- // Write the KeyUpdate. The shim should buffer and ACK it.
- keyUpdate := next[len(next)-1]
- c.WriteFlight([]DTLSMessage{keyUpdate})
- ackTimeout := timeouts[0] / 4
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // The shim should not process KeyUpdate yet. It should not
- // read from the new epoch.
- msg1, msg2 := []byte("aaaa"), []byte("bbbb")
- c.WriteAppData(c.OutEpoch(), msg1)
- c.AdvanceClock(0) // Check there are no messages.
-
- // It can read from the old epoch, however.
- c.WriteAppData(c.OutEpoch()-1, msg2)
- c.ReadAppData(c.InEpoch(), expectedReply(msg2))
-
- // Write the rest of the flight.
- c.WriteFlight(next[:len(next)-1])
- c.AdvanceClock(ackTimeout)
- c.ReadACK(c.InEpoch())
-
- // Now the new epoch is functional.
- c.WriteAppData(c.OutEpoch(), msg1)
- c.ReadAppData(c.InEpoch(), expectedReply(msg1))
- }),
- },
- },
- sendKeyUpdates: 1,
- keyUpdateRequest: keyUpdateNotRequested,
- flags: []string{"-async"},
- })
-
- // Messages after a KeyUpdate are not allowed.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-ExtraMessage-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- extra := next[0]
- extra.Sequence = next[len(next)-1].Sequence + 1
- next = append(slices.Clip(next), extra)
- c.WriteFlight(next)
- }),
- },
- },
- sendKeyUpdates: 1,
- keyUpdateRequest: keyUpdateNotRequested,
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-ExtraMessageBuffered-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- // Send the extra message first. The shim should accept and
- // buffer it.
- extra := next[0]
- extra.Sequence = next[len(next)-1].Sequence + 1
- c.WriteFlight([]DTLSMessage{extra})
-
- // Now send the flight, including a KeyUpdate. The shim
- // should now notice the extra message and reject.
- c.WriteFlight(next)
- }),
- },
- },
- sendKeyUpdates: 1,
- keyUpdateRequest: keyUpdateNotRequested,
- shouldFail: true,
- expectedError: ":EXCESS_HANDSHAKE_DATA:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Test KeyUpdate overflow conditions. Both the epoch number and the message
- // number may overflow, in either the read or write direction.
-
- // When the sender is the client, the first KeyUpdate is message 2 at epoch
- // 3, so the epoch number overflows first.
- const maxClientKeyUpdates = 0xffff - 3
-
- // Test that the shim, as a server, rejects KeyUpdates at epoch 0xffff. RFC
- // 9147 does not prescribe this limit, but we enforce it. See
- // https://mailarchive.ietf.org/arch/msg/tls/6y8wTv8Q_IPM-PCcbCAmDOYg6bM/
- // and https://www.rfc-editor.org/errata/eid8050
- writeFlightKeyUpdate := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- if next[0].Type == typeKeyUpdate {
- // Exchange some data to avoid tripping KeyUpdate DoS limits.
- msg := []byte("test")
- c.WriteAppData(c.OutEpoch()-1, msg)
- c.ReadAppData(c.InEpoch(), expectedReply(msg))
- }
- c.WriteFlight(next)
- }
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "KeyUpdate-MaxReadEpoch-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AllowEpochOverflow: true,
- WriteFlightDTLS: writeFlightKeyUpdate,
- },
- },
- // Avoid the NewSessionTicket messages interfering with the callback.
- flags: []string{"-no-ticket"},
- sendKeyUpdates: maxClientKeyUpdates,
- keyUpdateRequest: keyUpdateNotRequested,
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- protocol: dtls,
- name: "KeyUpdate-ReadEpochOverflow-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- AllowEpochOverflow: true,
- WriteFlightDTLS: writeFlightKeyUpdate,
- },
- },
- // Avoid the NewSessionTicket messages interfering with the callback.
- flags: []string{"-no-ticket"},
- sendKeyUpdates: maxClientKeyUpdates + 1,
- keyUpdateRequest: keyUpdateNotRequested,
- shouldFail: true,
- expectedError: ":TOO_MANY_KEY_UPDATES:",
- expectedLocalError: "remote error: unexpected message",
- })
-
- // Test that the shim, as a client, notices its epoch overflow condition
- // when asked to send too many KeyUpdates. The shim sends KeyUpdate before
- // every read, including reading connection close, so the number of
- // KeyUpdates is one more than the message count.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-MaxWriteEpoch-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- shimSendsKeyUpdateBeforeRead: true,
- messageCount: maxClientKeyUpdates - 1,
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-WriteEpochOverflow-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // The shim does not notice the overflow until immediately after
- // sending KeyUpdate, so tolerate the overflow on the runner.
- AllowEpochOverflow: true,
- },
- },
- shimSendsKeyUpdateBeforeRead: true,
- messageCount: maxClientKeyUpdates,
- shouldFail: true,
- expectedError: ":TOO_MANY_KEY_UPDATES:",
- })
-
- // When the sender is a server that doesn't send tickets, the first
- // KeyUpdate is message 5 (SH, EE, C, CV, Fin) at epoch 3, so the message
- // number overflows first.
- const maxServerKeyUpdates = 0xffff - 5
-
- // Test that the shim, as a client, does not allow the value to wraparound.
- testCases = append(testCases, testCase{
- protocol: dtls,
- name: "KeyUpdate-ReadMessageOverflow-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- SessionTicketsDisabled: true,
- Bugs: ProtocolBugs{
- AllowEpochOverflow: true,
- WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
- writeFlightKeyUpdate(c, prev, received, next, records)
- if next[0].Type == typeKeyUpdate && next[0].Sequence == 0xffff {
- // At this point, the shim has accepted message 0xffff.
- // Check the shim does not now accept message 0 as the
- // current message. Test this by sending a garbage
- // message 0. A shim that overflows and processes the
- // message will notice the syntax error. A shim that
- // correctly interprets this as an old message will drop
- // the record and simply ACK it.
- //
- // We do this rather than send a valid KeyUpdate because
- // the shim will keep the old epoch active and drop
- // decryption failures. Looking for the lack of an error
- // is more straightforward.
- c.WriteFlight([]DTLSMessage{{Epoch: c.OutEpoch(), Sequence: 0, Type: typeKeyUpdate, Data: []byte("INVALID")}})
- c.ExpectNextTimeout(timeouts[0] / 4)
- c.AdvanceClock(timeouts[0] / 4)
- c.ReadACK(c.InEpoch())
- }
- },
- },
- },
- sendKeyUpdates: maxServerKeyUpdates + 1,
- keyUpdateRequest: keyUpdateNotRequested,
- flags: []string{"-async", "-expect-no-session"},
- })
-
- // Test that the shim, as a server, notices its message overflow condition,
- // when asked to send too many KeyUpdates.
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "KeyUpdate-MaxWriteMessage-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- shimSendsKeyUpdateBeforeRead: true,
- messageCount: maxServerKeyUpdates,
- // Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
- flags: []string{"-no-ticket"},
- })
- testCases = append(testCases, testCase{
- protocol: dtls,
- testType: serverTest,
- name: "KeyUpdate-WriteMessageOverflow-DTLS",
- config: Config{
- MaxVersion: VersionTLS13,
- },
- shimSendsKeyUpdateBeforeRead: true,
- messageCount: maxServerKeyUpdates + 1,
- shouldFail: true,
- expectedError: ":overflow:",
- // Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
- flags: []string{"-no-ticket"},
- })
-}
-
-func addPAKETests() {
- spakeCredential := Credential{
- Type: CredentialTypeSPAKE2PlusV1,
- PAKEContext: []byte("context"),
- PAKEClientID: []byte("client"),
- PAKEServerID: []byte("server"),
- PAKEPassword: []byte("password"),
- }
-
- spakeWrongClientID := spakeCredential
- spakeWrongClientID.PAKEClientID = []byte("wrong")
-
- spakeWrongServerID := spakeCredential
- spakeWrongServerID.PAKEServerID = []byte("wrong")
-
- spakeWrongPassword := spakeCredential
- spakeWrongPassword.PAKEPassword = []byte("wrong")
-
- spakeWrongRole := spakeCredential
- spakeWrongRole.WrongPAKERole = true
-
- spakeWrongCodepoint := spakeCredential
- spakeWrongCodepoint.OverridePAKECodepoint = 1234
-
- testCases = append(testCases, testCase{
- name: "PAKE-No-Server-Support",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- },
- shouldFail: true,
- expectedError: ":MISSING_KEY_SHARE:",
- })
- testCases = append(testCases, testCase{
- name: "PAKE-Server",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- // We do not currently support resumption with PAKE, so PAKE
- // servers should not issue session tickets.
- ExpectNoNewSessionTicket: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- })
- testCases = append(testCases, testCase{
- // Send a ClientHello with the wrong PAKE client ID.
- name: "PAKE-Server-WrongClientID",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeWrongClientID,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":PEER_PAKE_MISMATCH:",
- expectedLocalError: "remote error: handshake failure",
- })
- testCases = append(testCases, testCase{
- // Send a ClientHello with the wrong PAKE server ID.
- name: "PAKE-Server-WrongServerID",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeWrongServerID,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":PEER_PAKE_MISMATCH:",
- expectedLocalError: "remote error: handshake failure",
- })
- testCases = append(testCases, testCase{
- // Send a ClientHello with the wrong PAKE codepoint.
- name: "PAKE-Server-WrongCodepoint",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeWrongCodepoint,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":PEER_PAKE_MISMATCH:",
- expectedLocalError: "remote error: handshake failure",
- })
- testCases = append(testCases, testCase{
- // A server configured with a mix of PAKE and non-PAKE
- // credentials will select the first that matches what the
- // client offered. In doing so, it should skip unsupported
- // PAKE algorithms.
- name: "PAKE-Server-MultiplePAKEs",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- OfferExtraPAKEs: []uint16{1, 2, 3, 4, 5},
- },
- },
- shimCredentials: []*Credential{&spakeWrongClientID, &spakeWrongServerID, &spakeWrongRole, &spakeCredential, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "3"},
- })
- testCases = append(testCases, testCase{
- // A server configured with a certificate credential before a
- // PAKE credential will consider the certificate credential first.
- name: "PAKE-Server-CertificateBeforePAKE",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Pretend to offer a matching PAKE share, but expect the
- // shim to select the credential first and negotiate a
- // normal handshake.
- OfferExtraPAKEClientID: spakeCredential.PAKEClientID,
- OfferExtraPAKEServerID: spakeCredential.PAKEServerID,
- OfferExtraPAKEs: []uint16{spakeID},
- },
- },
- shimCredentials: []*Credential{&rsaCertificate, &spakeCredential},
- flags: []string{"-expect-selected-credential", "0"},
- })
- testCases = append(testCases, testCase{
- // A server configured with just a PAKE credential should reject normal
- // clients.
- name: "PAKE-Server-NormalClient",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":PEER_PAKE_MISMATCH:",
- expectedLocalError: "remote error: handshake failure",
- })
- testCases = append(testCases, testCase{
- // ... and TLS 1.2 clients.
- name: "PAKE-Server-NormalTLS12Client",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":NO_SHARED_CIPHER:",
- expectedLocalError: "remote error: handshake failure",
- })
- testCases = append(testCases, testCase{
- // ... but you can configure a server with both PAKE and certificate-based
- // SSL_CREDENTIALs and that works.
- name: "PAKE-ServerWithCertsToo-NormalClient",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- },
- shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "1"},
- })
- testCases = append(testCases, testCase{
- // ... and for older clients.
- name: "PAKE-ServerWithCertsToo-NormalTLS12Client",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
- flags: []string{"-expect-selected-credential", "1"},
- })
- testCases = append(testCases, testCase{
- name: "PAKE-Client",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- CheckClientHello: func(c *clientHelloMsg) error {
- // PAKE connections don't use the key_share / supported_groups mechanism.
- if c.hasKeyShares {
- return errors.New("unexpected key_share extension")
- }
- if len(c.supportedCurves) != 0 {
- return errors.New("unexpected supported_groups extension")
- }
- // PAKE connections don't use signature algorithms.
- if len(c.signatureAlgorithms) != 0 {
- return errors.New("unexpected signature_algorithms extension")
- }
- // We don't support resumption with PAKEs.
- if len(c.pskKEModes) != 0 {
- return errors.New("unexpected psk_key_exchange_modes extension")
- }
- return nil
- },
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- })
- testCases = append(testCases, testCase{
- // Although there is no reason to request new key shares, the PAKE
- // client should handle cookie requests.
- name: "PAKE-Client-HRRCookie",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCookie: []byte("cookie"),
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- })
- testCases = append(testCases, testCase{
- // A PAKE client will not offer key shares, so the client should
- // reject a HelloRetryRequest requesting a different key share.
- name: "PAKE-Client-HRRKeyShare",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- SendHelloRetryRequestCurve: CurveX25519,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- expectedLocalError: "remote error: unsupported extension",
- })
- testCases = append(testCases, testCase{
- // A server cannot reply with an HRR asking for a PAKE if the client didn't
- // offer a PAKE in the ClientHello.
- name: "PAKE-NormalClient-PAKEInHRR",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- AlwaysSendHelloRetryRequest: true,
- SendPAKEInHelloRetryRequest: true,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- // A PAKE client should not accept an empty ServerHello.
- name: "PAKE-Client-EmptyServerHello",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Trigger an empty ServerHello by making a normal server skip
- // the key_share extension.
- MissingKeyShare: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":MISSING_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- // A PAKE client should not accept a key_share ServerHello.
- name: "PAKE-Client-KeyShareServerHello",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- // Trigger a key_share ServerHello by making a normal server
- // skip the HelloRetryRequest it would otherwise send in
- // response to the shim's key_share-less ClientHello.
- SkipHelloRetryRequest: true,
- // Ignore the client's lack of supported_groups.
- IgnorePeerCurvePreferences: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- // A PAKE client should not accept a TLS 1.2 ServerHello.
- name: "PAKE-Client-TLS12ServerHello",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS12,
- MaxVersion: VersionTLS12,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":UNSUPPORTED_PROTOCOL:",
- })
- testCases = append(testCases, testCase{
- // A server cannot send the PAKE extension to a non-PAKE client.
- name: "PAKE-NormalClient-UnsolicitedPAKEInServerHello",
- testType: clientTest,
- config: Config{
- Bugs: ProtocolBugs{
- UnsolicitedPAKE: spakeID,
- },
- },
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- // A server cannot reply with a PAKE that the client did not offer.
- name: "PAKE-Client-WrongPAKEInServerHello",
- testType: clientTest,
- config: Config{
- Bugs: ProtocolBugs{
- UnsolicitedPAKE: 1234,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
- testCases = append(testCases, testCase{
- name: "PAKE-Extension-Duplicate",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- OfferExtraPAKEClientID: []byte("client"),
- OfferExtraPAKEServerID: []byte("server"),
- OfferExtraPAKEs: []uint16{1234, 1234},
- },
- },
- shouldFail: true,
- expectedError: ":ERROR_PARSING_EXTENSION:",
- })
- testCases = append(testCases, testCase{
- // If the client sees a server with a wrong password, it should
- // reject the confirmV value in the ServerHello.
- name: "PAKE-Client-WrongPassword",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeWrongPassword,
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
- testCases = append(testCases, testCase{
- name: "PAKE-Client-Truncate",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- TruncatePAKEMessage: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- })
- testCases = append(testCases, testCase{
- name: "PAKE-Server-Truncate",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- TruncatePAKEMessage: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":DECODE_ERROR:",
- expectedLocalError: "remote error: illegal parameter",
- })
- testCases = append(testCases, testCase{
- // Servers may not send CertificateRequest in a PAKE handshake.
- name: "PAKE-Client-UnexpectedCertificateRequest",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- ClientAuth: RequireAnyClientCert,
- Bugs: ProtocolBugs{
- AlwaysSendCertificateRequest: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
- testCases = append(testCases, testCase{
- // Servers may not send Certificate in a PAKE handshake.
- name: "PAKE-Client-UnexpectedCertificate",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- AlwaysSendCertificate: true,
- UseCertificateCredential: &rsaCertificate,
- // Ignore the client's lack of signature_algorithms.
- IgnorePeerSignatureAlgorithmPreferences: true,
- },
- },
- shimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":UNEXPECTED_MESSAGE:",
- expectedLocalError: "remote error: unexpected message",
- })
- testCases = append(testCases, testCase{
- // If a server is configured to request client certificates, it should
- // still not do so when negotiating a PAKE.
- name: "PAKE-Server-DoNotRequestClientCertificate",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- },
- shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
- flags: []string{"-require-any-client-certificate"},
- })
- testCases = append(testCases, testCase{
- // Clients should ignore server PAKE credentials.
- name: "PAKE-Client-WrongRole",
- testType: clientTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- },
- shimCredentials: []*Credential{&spakeWrongRole},
- shouldFail: true,
- // The shim will send a non-PAKE ClientHello.
- expectedLocalError: "tls: client not configured with PAKE",
- })
- testCases = append(testCases, testCase{
- // Servers should ignore client PAKE credentials.
- name: "PAKE-Server-WrongRole",
- testType: serverTest,
- config: Config{
- MinVersion: VersionTLS13,
- Credential: &spakeCredential,
- },
- shimCredentials: []*Credential{&spakeWrongRole},
- shouldFail: true,
- // The shim will fail the handshake because it has no usable credentials
- // available.
- expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
- expectedLocalError: "remote error: handshake failure",
- })
- testCases = append(testCases, testCase{
- // On the client, we only support a single PAKE credential.
- name: "PAKE-Client-MultiplePAKEs",
- testType: clientTest,
- shimCredentials: []*Credential{&spakeCredential, &spakeWrongPassword},
- shouldFail: true,
- expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
- })
- testCases = append(testCases, testCase{
- // On the client, we only support a single PAKE credential.
- name: "PAKE-Client-PAKEAndCertificate",
- testType: clientTest,
- shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
- shouldFail: true,
- expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
- })
- testCases = append(testCases, testCase{
- // We currently do not support resumption with PAKE. Even if configured
- // with a session, the client should not offer the session with PAKEs.
- name: "PAKE-Client-NoResume",
- testType: clientTest,
- // Make two connections. For the first connection, just establish a
- // session without PAKE, to pick up a session.
- config: Config{
- Credential: &rsaCertificate,
- },
- // For the second connection, use SPAKE.
- resumeSession: true,
- resumeConfig: &Config{
- Credential: &spakeCredential,
- Bugs: ProtocolBugs{
- // Check that the ClientHello does not offer a session, even
- // though one was configured.
- ExpectNoTLS13PSK: true,
- // Respond with an unsolicted PSK extension in ServerHello, to
- // check that the client rejects it.
- AlwaysSelectPSKIdentity: true,
- },
- },
- resumeShimCredentials: []*Credential{&spakeCredential},
- shouldFail: true,
- expectedError: ":UNEXPECTED_EXTENSION:",
- })
-}
-
func worker(dispatcher *shimDispatcher, statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
defer wg.Done()
@@ -24002,7 +2189,7 @@
addExportKeyingMaterialTests()
addExportTrafficSecretsTests()
addTLSUniqueTests()
- addCustomExtensionTests()
+ addUnknownExtensionTests()
addRSAClientKeyExchangeTests()
addCurveTests()
addSessionTicketTests()
diff --git a/ssl/test/runner/signature_algorithm_tests.go b/ssl/test/runner/signature_algorithm_tests.go
new file mode 100644
index 0000000..d27d912
--- /dev/null
+++ b/ssl/test/runner/signature_algorithm_tests.go
@@ -0,0 +1,1169 @@
+// 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 (
+ "fmt"
+ "strconv"
+)
+
+var testSignatureAlgorithms = []struct {
+ name string
+ id signatureAlgorithm
+ baseCert *Credential
+ // If non-zero, the curve that must be supported in TLS 1.2 for cert to be
+ // accepted.
+ curve CurveID
+}{
+ {"RSA_PKCS1_SHA1", signatureRSAPKCS1WithSHA1, &rsaCertificate, 0},
+ {"RSA_PKCS1_SHA256", signatureRSAPKCS1WithSHA256, &rsaCertificate, 0},
+ {"RSA_PKCS1_SHA256_LEGACY", signatureRSAPKCS1WithSHA256Legacy, &rsaCertificate, 0},
+ {"RSA_PKCS1_SHA384", signatureRSAPKCS1WithSHA384, &rsaCertificate, 0},
+ {"RSA_PKCS1_SHA512", signatureRSAPKCS1WithSHA512, &rsaCertificate, 0},
+ {"ECDSA_SHA1", signatureECDSAWithSHA1, &ecdsaP256Certificate, CurveP256},
+ // The “P256” in the following line is not a mistake. In TLS 1.2 the
+ // hash function doesn't have to match the curve and so the same
+ // signature algorithm works with P-224.
+ {"ECDSA_P224_SHA256", signatureECDSAWithP256AndSHA256, &ecdsaP224Certificate, CurveP224},
+ {"ECDSA_P256_SHA256", signatureECDSAWithP256AndSHA256, &ecdsaP256Certificate, CurveP256},
+ {"ECDSA_P384_SHA384", signatureECDSAWithP384AndSHA384, &ecdsaP384Certificate, CurveP384},
+ {"ECDSA_P521_SHA512", signatureECDSAWithP521AndSHA512, &ecdsaP521Certificate, CurveP521},
+ {"RSA_PSS_SHA256", signatureRSAPSSWithSHA256, &rsaCertificate, 0},
+ {"RSA_PSS_SHA384", signatureRSAPSSWithSHA384, &rsaCertificate, 0},
+ {"RSA_PSS_SHA512", signatureRSAPSSWithSHA512, &rsaCertificate, 0},
+ {"Ed25519", signatureEd25519, &ed25519Certificate, 0},
+ // Tests for key types prior to TLS 1.2.
+ {"RSA", 0, &rsaCertificate, 0},
+ {"ECDSA", 0, &ecdsaP256Certificate, CurveP256},
+}
+
+const (
+ fakeSigAlg1 signatureAlgorithm = 0x2a01
+ fakeSigAlg2 signatureAlgorithm = 0xff01
+)
+
+func addSignatureAlgorithmTests() {
+ // Not all ciphers involve a signature. Advertise a list which gives all
+ // versions a signing cipher.
+ signingCiphers := []uint16{
+ TLS_AES_256_GCM_SHA384,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ }
+
+ var allAlgorithms []signatureAlgorithm
+ for _, alg := range testSignatureAlgorithms {
+ if alg.id != 0 {
+ allAlgorithms = append(allAlgorithms, alg.id)
+ }
+ }
+
+ // Make sure each signature algorithm works. Include some fake values in
+ // the list and ensure they're ignored.
+ for _, alg := range testSignatureAlgorithms {
+ // Make a version of the certificate that will not sign any other algorithm.
+ cert := alg.baseCert
+ if alg.id != 0 {
+ cert = cert.WithSignatureAlgorithms(alg.id)
+ }
+
+ for _, ver := range tlsVersions {
+ if (ver.version < VersionTLS12) != (alg.id == 0) {
+ continue
+ }
+
+ suffix := "-" + alg.name + "-" + ver.name
+ for _, signTestType := range []testType{clientTest, serverTest} {
+ signPrefix := "Client-"
+ verifyPrefix := "Server-"
+ verifyTestType := serverTest
+ if signTestType == serverTest {
+ verifyTestType = clientTest
+ signPrefix, verifyPrefix = verifyPrefix, signPrefix
+ }
+
+ var shouldFail bool
+ isTLS12PKCS1 := hasComponent(alg.name, "PKCS1") && !hasComponent(alg.name, "LEGACY")
+ isTLS13PKCS1 := hasComponent(alg.name, "PKCS1") && hasComponent(alg.name, "LEGACY")
+
+ // TLS 1.3 removes a number of signature algorithms.
+ if ver.version >= VersionTLS13 && (alg.curve == CurveP224 || alg.id == signatureECDSAWithSHA1 || isTLS12PKCS1) {
+ shouldFail = true
+ }
+
+ // The backported RSA-PKCS1 code points only exist for TLS 1.3
+ // client certificates.
+ if (ver.version < VersionTLS13 || signTestType == serverTest) && isTLS13PKCS1 {
+ shouldFail = true
+ }
+
+ // By default, BoringSSL does not sign with these algorithms.
+ signDefault := !shouldFail
+ if isTLS13PKCS1 {
+ signDefault = false
+ }
+
+ // By default, BoringSSL does not accept these algorithms.
+ verifyDefault := !shouldFail
+ if alg.id == signatureECDSAWithSHA1 || alg.id == signatureECDSAWithP521AndSHA512 || alg.id == signatureEd25519 || isTLS13PKCS1 {
+ verifyDefault = false
+ }
+
+ var curveFlags []string
+ var runnerCurves []CurveID
+ if alg.curve != 0 && ver.version <= VersionTLS12 {
+ // In TLS 1.2, the ECDH curve list also constrains ECDSA keys. Ensure the
+ // corresponding curve is enabled. Also include X25519 to ensure the shim
+ // and runner have something in common for ECDH.
+ curveFlags = flagInts("-curves", []int{int(CurveX25519), int(alg.curve)})
+ runnerCurves = []CurveID{CurveX25519, alg.curve}
+ }
+
+ signError := func(shouldFail bool) string {
+ if !shouldFail {
+ return ""
+ }
+ // In TLS 1.3, the shim should report no common signature algorithms if
+ // it cannot generate a signature. In TLS 1.2 servers, signature
+ // algorithm and cipher selection are integrated, so it is reported as
+ // no shared cipher.
+ if ver.version <= VersionTLS12 && signTestType == serverTest {
+ return ":NO_SHARED_CIPHER:"
+ }
+ return ":NO_COMMON_SIGNATURE_ALGORITHMS:"
+ }
+ signLocalError := func(shouldFail bool) string {
+ if !shouldFail {
+ return ""
+ }
+ // The shim should send handshake_failure when it cannot
+ // negotiate parameters.
+ return "remote error: handshake failure"
+ }
+ verifyError := func(shouldFail bool) string {
+ if !shouldFail {
+ return ""
+ }
+ // If the shim rejects the signature algorithm, but the
+ // runner forcibly selects it anyway, the shim should notice.
+ return ":WRONG_SIGNATURE_TYPE:"
+ }
+ verifyLocalError := func(shouldFail bool) string {
+ if !shouldFail {
+ return ""
+ }
+ // The shim should send an illegal_parameter alert if the runner
+ // uses a signature algorithm it isn't allowed to use.
+ return "remote error: illegal parameter"
+ }
+
+ // Test the shim using the algorithm for signing.
+ signTest := testCase{
+ testType: signTestType,
+ name: signPrefix + "Sign" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CurvePreferences: runnerCurves,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ fakeSigAlg1,
+ alg.id,
+ fakeSigAlg2,
+ },
+ },
+ shimCertificate: cert,
+ flags: curveFlags,
+ shouldFail: shouldFail,
+ expectedError: signError(shouldFail),
+ expectedLocalError: signLocalError(shouldFail),
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: alg.id,
+ },
+ }
+
+ // Test whether the shim enables the algorithm by default.
+ signDefaultTest := testCase{
+ testType: signTestType,
+ name: signPrefix + "SignDefault" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CurvePreferences: runnerCurves,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ fakeSigAlg1,
+ alg.id,
+ fakeSigAlg2,
+ },
+ },
+ // cert has been configured with the specified algorithm,
+ // while alg.baseCert uses the defaults.
+ shimCertificate: alg.baseCert,
+ flags: curveFlags,
+ shouldFail: !signDefault,
+ expectedError: signError(!signDefault),
+ expectedLocalError: signLocalError(!signDefault),
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: alg.id,
+ },
+ }
+
+ // Test that the shim will select the algorithm when configured to only
+ // support it.
+ negotiateTest := testCase{
+ testType: signTestType,
+ name: signPrefix + "Sign-Negotiate" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CurvePreferences: runnerCurves,
+ VerifySignatureAlgorithms: allAlgorithms,
+ },
+ shimCertificate: cert,
+ flags: curveFlags,
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: alg.id,
+ },
+ }
+
+ if signTestType == serverTest {
+ // TLS 1.2 servers only sign on some cipher suites.
+ signTest.config.CipherSuites = signingCiphers
+ signDefaultTest.config.CipherSuites = signingCiphers
+ negotiateTest.config.CipherSuites = signingCiphers
+ } else {
+ // TLS 1.2 clients only sign when the server requests certificates.
+ signTest.config.ClientAuth = RequireAnyClientCert
+ signDefaultTest.config.ClientAuth = RequireAnyClientCert
+ negotiateTest.config.ClientAuth = RequireAnyClientCert
+ }
+ testCases = append(testCases, signTest, signDefaultTest)
+ if ver.version >= VersionTLS12 && !shouldFail {
+ testCases = append(testCases, negotiateTest)
+ }
+
+ // Test the shim using the algorithm for verifying.
+ verifyTest := testCase{
+ testType: verifyTestType,
+ name: verifyPrefix + "Verify" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: cert,
+ Bugs: ProtocolBugs{
+ SkipECDSACurveCheck: shouldFail,
+ IgnoreSignatureVersionChecks: shouldFail,
+ // Some signature algorithms may not be advertised.
+ IgnorePeerSignatureAlgorithmPreferences: shouldFail,
+ },
+ },
+ flags: curveFlags,
+ // Resume the session to assert the peer signature
+ // algorithm is reported on both handshakes.
+ resumeSession: !shouldFail,
+ shouldFail: shouldFail,
+ expectedError: verifyError(shouldFail),
+ expectedLocalError: verifyLocalError(shouldFail),
+ }
+ if alg.id != 0 {
+ verifyTest.flags = append(verifyTest.flags, "-expect-peer-signature-algorithm", strconv.Itoa(int(alg.id)))
+ // The algorithm may be disabled by default, so explicitly enable it.
+ verifyTest.flags = append(verifyTest.flags, "-verify-prefs", strconv.Itoa(int(alg.id)))
+ }
+
+ // Test whether the shim expects the algorithm enabled by default.
+ defaultTest := testCase{
+ testType: verifyTestType,
+ name: verifyPrefix + "VerifyDefault" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: cert,
+ Bugs: ProtocolBugs{
+ SkipECDSACurveCheck: !verifyDefault,
+ IgnoreSignatureVersionChecks: !verifyDefault,
+ // Some signature algorithms may not be advertised.
+ IgnorePeerSignatureAlgorithmPreferences: !verifyDefault,
+ },
+ },
+ flags: append(
+ []string{"-expect-peer-signature-algorithm", strconv.Itoa(int(alg.id))},
+ curveFlags...,
+ ),
+ // Resume the session to assert the peer signature
+ // algorithm is reported on both handshakes.
+ resumeSession: verifyDefault,
+ shouldFail: !verifyDefault,
+ expectedError: verifyError(!verifyDefault),
+ expectedLocalError: verifyLocalError(!verifyDefault),
+ }
+
+ // Test whether the shim handles invalid signatures for this algorithm.
+ invalidTest := testCase{
+ testType: verifyTestType,
+ name: verifyPrefix + "InvalidSignature" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: cert,
+ Bugs: ProtocolBugs{
+ InvalidSignature: true,
+ },
+ },
+ flags: curveFlags,
+ shouldFail: true,
+ expectedError: ":BAD_SIGNATURE:",
+ }
+ if alg.id != 0 {
+ // The algorithm may be disabled by default, so explicitly enable it.
+ invalidTest.flags = append(invalidTest.flags, "-verify-prefs", strconv.Itoa(int(alg.id)))
+ }
+
+ if verifyTestType == serverTest {
+ // TLS 1.2 servers only verify when they request client certificates.
+ verifyTest.flags = append(verifyTest.flags, "-require-any-client-certificate")
+ defaultTest.flags = append(defaultTest.flags, "-require-any-client-certificate")
+ invalidTest.flags = append(invalidTest.flags, "-require-any-client-certificate")
+ } else {
+ // TLS 1.2 clients only verify on some cipher suites.
+ verifyTest.config.CipherSuites = signingCiphers
+ defaultTest.config.CipherSuites = signingCiphers
+ invalidTest.config.CipherSuites = signingCiphers
+ }
+ testCases = append(testCases, verifyTest, defaultTest)
+ if !shouldFail {
+ testCases = append(testCases, invalidTest)
+ }
+ }
+ }
+ }
+
+ // Test the peer's verify preferences are available.
+ for _, ver := range tlsVersions {
+ if ver.version < VersionTLS12 {
+ continue
+ }
+ testCases = append(testCases, testCase{
+ name: "ClientAuth-PeerVerifyPrefs-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA256,
+ signatureEd25519,
+ signatureECDSAWithP256AndSHA256,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
+ "-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)),
+ "-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)),
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerAuth-PeerVerifyPrefs-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA256,
+ signatureEd25519,
+ signatureECDSAWithP256AndSHA256,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
+ "-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)),
+ "-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)),
+ },
+ })
+
+ }
+
+ // Test that algorithm selection takes the key type into account.
+ testCases = append(testCases, testCase{
+ name: "ClientAuth-SignatureType",
+ config: Config{
+ ClientAuth: RequireAnyClientCert,
+ MaxVersion: VersionTLS12,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithP521AndSHA512,
+ signatureRSAPKCS1WithSHA384,
+ signatureECDSAWithSHA1,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA384,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ClientAuth-SignatureType-TLS13",
+ config: Config{
+ ClientAuth: RequireAnyClientCert,
+ MaxVersion: VersionTLS13,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithP521AndSHA512,
+ signatureRSAPKCS1WithSHA384,
+ signatureRSAPSSWithSHA384,
+ signatureECDSAWithSHA1,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPSSWithSHA384,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerAuth-SignatureType",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithP521AndSHA512,
+ signatureRSAPKCS1WithSHA384,
+ signatureECDSAWithSHA1,
+ },
+ },
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA384,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerAuth-SignatureType-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithP521AndSHA512,
+ signatureRSAPKCS1WithSHA384,
+ signatureRSAPSSWithSHA384,
+ signatureECDSAWithSHA1,
+ },
+ },
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPSSWithSHA384,
+ },
+ })
+
+ // Test that signature verification takes the key type into account.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Verify-ClientAuth-SignatureType",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithSHA256),
+ Bugs: ProtocolBugs{
+ SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
+ },
+ },
+ flags: []string{
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Verify-ClientAuth-SignatureType-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ Bugs: ProtocolBugs{
+ SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
+ },
+ },
+ flags: []string{
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "Verify-ServerAuth-SignatureType",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithSHA256),
+ Bugs: ProtocolBugs{
+ SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "Verify-ServerAuth-SignatureType-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ Bugs: ProtocolBugs{
+ SendSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ // Test that, if the ClientHello list is missing, the server falls back
+ // to SHA-1 in TLS 1.2, but not TLS 1.3.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerAuth-SHA1-Fallback-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA1,
+ },
+ Bugs: ProtocolBugs{
+ NoSignatureAlgorithms: true,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerAuth-SHA1-Fallback-ECDSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithSHA1,
+ },
+ Bugs: ProtocolBugs{
+ NoSignatureAlgorithms: true,
+ },
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerAuth-NoFallback-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA1,
+ },
+ Bugs: ProtocolBugs{
+ NoSignatureAlgorithms: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ })
+
+ // The CertificateRequest list, however, may never be omitted. It is a
+ // syntax error for it to be empty.
+ testCases = append(testCases, testCase{
+ name: "ClientAuth-NoFallback-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA1,
+ },
+ Bugs: ProtocolBugs{
+ NoSignatureAlgorithms: true,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ClientAuth-NoFallback-ECDSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithSHA1,
+ },
+ Bugs: ProtocolBugs{
+ NoSignatureAlgorithms: true,
+ },
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ClientAuth-NoFallback-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA1,
+ },
+ Bugs: ProtocolBugs{
+ NoSignatureAlgorithms: true,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ // Test that signature preferences are enforced. BoringSSL does not
+ // implement MD5 signatures.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ClientAuth-Enforced",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ },
+ },
+ flags: []string{"-require-any-client-certificate"},
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ServerAuth-Enforced",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ClientAuth-Enforced-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ IgnoreSignatureVersionChecks: true,
+ },
+ },
+ flags: []string{"-require-any-client-certificate"},
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ServerAuth-Enforced-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithMD5),
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ IgnoreSignatureVersionChecks: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ // Test that the negotiated signature algorithm respects the client and
+ // server preferences.
+ testCases = append(testCases, testCase{
+ name: "NoCommonAlgorithms",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA512,
+ signatureRSAPKCS1WithSHA1,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPKCS1WithSHA256),
+ shouldFail: true,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ })
+ testCases = append(testCases, testCase{
+ name: "NoCommonAlgorithms-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA512,
+ signatureRSAPSSWithSHA384,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ shouldFail: true,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ })
+ testCases = append(testCases, testCase{
+ name: "Agree-Digest-SHA256",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA1,
+ signatureRSAPKCS1WithSHA256,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(
+ signatureRSAPKCS1WithSHA256,
+ signatureRSAPKCS1WithSHA1,
+ ),
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Agree-Digest-SHA1",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA1,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(
+ signatureRSAPKCS1WithSHA512,
+ signatureRSAPKCS1WithSHA256,
+ signatureRSAPKCS1WithSHA1,
+ ),
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA1,
+ },
+ })
+ testCases = append(testCases, testCase{
+ name: "Agree-Digest-Default",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA256,
+ signatureECDSAWithP256AndSHA256,
+ signatureRSAPKCS1WithSHA1,
+ signatureECDSAWithSHA1,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
+ },
+ })
+
+ // Test that the signing preference list may include extra algorithms
+ // without negotiation problems.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "FilterExtraAlgorithms",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPKCS1WithSHA256,
+ },
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(
+ signatureECDSAWithP256AndSHA256,
+ signatureRSAPKCS1WithSHA256,
+ ),
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
+ },
+ })
+
+ // In TLS 1.2 and below, ECDSA uses the curve list rather than the
+ // signature algorithms.
+ testCases = append(testCases, testCase{
+ name: "CheckLeafCurve",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ Credential: &ecdsaP256Certificate,
+ },
+ flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
+ shouldFail: true,
+ expectedError: ":BAD_ECC_CERT:",
+ })
+
+ // In TLS 1.3, ECDSA does not use the ECDHE curve list.
+ testCases = append(testCases, testCase{
+ name: "CheckLeafCurve-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &ecdsaP256Certificate,
+ },
+ flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
+ })
+
+ // In TLS 1.2, the ECDSA curve is not in the signature algorithm, so the
+ // shim should accept P-256 with SHA-384.
+ testCases = append(testCases, testCase{
+ name: "ECDSACurveMismatch-Verify-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ Credential: ecdsaP256Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384),
+ },
+ })
+
+ // In TLS 1.3, the ECDSA curve comes from the signature algorithm, so the
+ // shim should reject P-256 with SHA-384.
+ testCases = append(testCases, testCase{
+ name: "ECDSACurveMismatch-Verify-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: ecdsaP256Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384),
+ Bugs: ProtocolBugs{
+ SkipECDSACurveCheck: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ // Signature algorithm selection in TLS 1.3 should take the curve into
+ // account.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ECDSACurveMismatch-Sign-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithP384AndSHA384,
+ signatureECDSAWithP256AndSHA256,
+ },
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ expectations: connectionExpectations{
+ peerSignatureAlgorithm: signatureECDSAWithP256AndSHA256,
+ },
+ })
+
+ // RSASSA-PSS with SHA-512 is too large for 1024-bit RSA. Test that the
+ // server does not attempt to sign in that case.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RSA-PSS-Large",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA512,
+ },
+ },
+ shimCertificate: &rsa1024Certificate,
+ shouldFail: true,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ })
+
+ // Test that RSA-PSS is enabled by default for TLS 1.2.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "RSA-PSS-Default-Verify",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "RSA-PSS-Default-Sign",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureRSAPSSWithSHA256,
+ },
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+
+ // TLS 1.1 and below has no way to advertise support for or negotiate
+ // Ed25519's signature algorithm.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "NoEd25519-TLS11-ServerAuth-Verify",
+ config: Config{
+ MaxVersion: VersionTLS11,
+ Credential: &ed25519Certificate,
+ Bugs: ProtocolBugs{
+ // Sign with Ed25519 even though it is TLS 1.1.
+ SigningAlgorithmForLegacyVersions: signatureEd25519,
+ },
+ },
+ flags: []string{"-verify-prefs", strconv.Itoa(int(signatureEd25519))},
+ shouldFail: true,
+ expectedError: ":PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoEd25519-TLS11-ServerAuth-Sign",
+ config: Config{
+ MaxVersion: VersionTLS11,
+ },
+ shimCertificate: &ed25519Certificate,
+ shouldFail: true,
+ expectedError: ":NO_SHARED_CIPHER:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoEd25519-TLS11-ClientAuth-Verify",
+ config: Config{
+ MaxVersion: VersionTLS11,
+ Credential: &ed25519Certificate,
+ Bugs: ProtocolBugs{
+ // Sign with Ed25519 even though it is TLS 1.1.
+ SigningAlgorithmForLegacyVersions: signatureEd25519,
+ },
+ },
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureEd25519)),
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "NoEd25519-TLS11-ClientAuth-Sign",
+ config: Config{
+ MaxVersion: VersionTLS11,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &ed25519Certificate,
+ shouldFail: true,
+ expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+ })
+
+ // Test Ed25519 is not advertised by default.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "Ed25519DefaultDisable-NoAdvertise",
+ config: Config{
+ Credential: &ed25519Certificate,
+ },
+ shouldFail: true,
+ expectedLocalError: "tls: no common signature algorithms",
+ })
+
+ // Test Ed25519, when disabled, is not accepted if the peer ignores our
+ // preferences.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "Ed25519DefaultDisable-NoAccept",
+ config: Config{
+ Credential: &ed25519Certificate,
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: illegal parameter",
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ // Test that configuring verify preferences changes what the client
+ // advertises.
+ testCases = append(testCases, testCase{
+ name: "VerifyPreferences-Advertised",
+ config: Config{
+ Credential: rsaCertificate.WithSignatureAlgorithms(
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA384,
+ signatureRSAPSSWithSHA512,
+ ),
+ },
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
+ "-expect-peer-signature-algorithm", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
+ },
+ })
+
+ // Test that the client advertises a set which the runner can find
+ // nothing in common with.
+ testCases = append(testCases, testCase{
+ name: "VerifyPreferences-NoCommonAlgorithms",
+ config: Config{
+ Credential: rsaCertificate.WithSignatureAlgorithms(
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA512,
+ ),
+ },
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
+ },
+ shouldFail: true,
+ expectedLocalError: "tls: no common signature algorithms",
+ })
+
+ // Test that the client enforces its preferences when configured.
+ testCases = append(testCases, testCase{
+ name: "VerifyPreferences-Enforced",
+ config: Config{
+ Credential: rsaCertificate.WithSignatureAlgorithms(
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA512,
+ ),
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ },
+ },
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)),
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: illegal parameter",
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+
+ // Test that explicitly configuring Ed25519 is as good as changing the
+ // boolean toggle.
+ testCases = append(testCases, testCase{
+ name: "VerifyPreferences-Ed25519",
+ config: Config{
+ Credential: &ed25519Certificate,
+ },
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureEd25519)),
+ },
+ })
+
+ for _, testType := range []testType{clientTest, serverTest} {
+ for _, ver := range tlsVersions {
+ if ver.version < VersionTLS12 {
+ continue
+ }
+
+ prefix := "Client-" + ver.name + "-"
+ noCommonAlgorithmsError := ":NO_COMMON_SIGNATURE_ALGORITHMS:"
+ if testType == serverTest {
+ prefix = "Server-" + ver.name + "-"
+ // In TLS 1.2 servers, cipher selection and algorithm
+ // selection are linked.
+ if ver.version <= VersionTLS12 {
+ noCommonAlgorithmsError = ":NO_SHARED_CIPHER:"
+ }
+ }
+
+ // Test that the shim will not sign MD5/SHA1 with RSA at TLS 1.2,
+ // even if specified in signing preferences.
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: prefix + "NoSign-RSA_PKCS1_MD5_SHA1",
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: signingCiphers,
+ ClientAuth: RequireAnyClientCert,
+ VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPKCS1WithMD5AndSHA1},
+ },
+ shimCertificate: rsaCertificate.WithSignatureAlgorithms(
+ signatureRSAPKCS1WithMD5AndSHA1,
+ // Include a valid algorithm as well, to avoid an empty list
+ // if filtered out.
+ signatureRSAPKCS1WithSHA256,
+ ),
+ shouldFail: true,
+ expectedError: noCommonAlgorithmsError,
+ })
+
+ // Test that the shim will not accept MD5/SHA1 with RSA at TLS 1.2,
+ // even if specified in verify preferences.
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: prefix + "NoVerify-RSA_PKCS1_MD5_SHA1",
+ config: Config{
+ MaxVersion: ver.version,
+ Credential: &rsaCertificate,
+ Bugs: ProtocolBugs{
+ IgnorePeerSignatureAlgorithmPreferences: true,
+ AlwaysSignAsLegacyVersion: true,
+ SendSignatureAlgorithm: signatureRSAPKCS1WithMD5AndSHA1,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureRSAPKCS1WithMD5AndSHA1)),
+ // Include a valid algorithm as well, to avoid an empty list
+ // if filtered out.
+ "-verify-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)),
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_SIGNATURE_TYPE:",
+ })
+ }
+ }
+
+ // Test that, when there are no signature algorithms in common in TLS
+ // 1.2, the server will still consider the legacy RSA key exchange.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoCommonSignatureAlgorithms-TLS12-Fallback",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ VerifySignatureAlgorithms: []signatureAlgorithm{
+ signatureECDSAWithP256AndSHA256,
+ },
+ },
+ expectations: connectionExpectations{
+ cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
+ },
+ })
+}
+
+func addBadECDSASignatureTests() {
+ for badR := BadValue(1); badR < NumBadValues; badR++ {
+ for badS := BadValue(1); badS < NumBadValues; badS++ {
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("BadECDSA-%d-%d", badR, badS),
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ Credential: &ecdsaP256Certificate,
+ Bugs: ProtocolBugs{
+ BadECDSAR: badR,
+ BadECDSAS: badS,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_SIGNATURE:",
+ })
+ testCases = append(testCases, testCase{
+ name: fmt.Sprintf("BadECDSA-%d-%d-TLS13", badR, badS),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &ecdsaP256Certificate,
+ Bugs: ProtocolBugs{
+ BadECDSAR: badR,
+ BadECDSAS: badS,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":BAD_SIGNATURE:",
+ })
+ }
+ }
+}
diff --git a/ssl/test/runner/state_machine_tests.go b/ssl/test/runner/state_machine_tests.go
new file mode 100644
index 0000000..b510019
--- /dev/null
+++ b/ssl/test/runner/state_machine_tests.go
@@ -0,0 +1,1491 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "strconv"
+)
+
+type stateMachineTestConfig struct {
+ protocol protocol
+ async bool
+ splitHandshake bool
+ packHandshake bool
+ implicitHandshake bool
+}
+
+// Adds tests that try to cover the range of the handshake state machine, under
+// various conditions. Some of these are redundant with other tests, but they
+// only cover the synchronous case.
+func addAllStateMachineCoverageTests() {
+ for _, async := range []bool{false, true} {
+ for _, protocol := range []protocol{tls, dtls, quic} {
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ })
+ // QUIC doesn't work with the implicit handshake API. Additionally,
+ // splitting or packing handshake records is meaningless in QUIC.
+ if protocol != quic {
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ implicitHandshake: true,
+ })
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ splitHandshake: true,
+ })
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ packHandshake: true,
+ })
+ }
+ }
+ }
+}
+
+func addStateMachineCoverageTests(config stateMachineTestConfig) {
+ var tests []testCase
+
+ // Basic handshake, with resumption. Client and server,
+ // session ID and session ticket.
+ // The following tests have a max version of 1.2, so they are not suitable
+ // for use with QUIC.
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ name: "Basic-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ // Ensure session tickets are used, not session IDs.
+ noSessionCache: true,
+ flags: []string{"-expect-no-hrr"},
+ })
+ tests = append(tests, testCase{
+ name: "Basic-Client-RenewTicket",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ RenewTicketOnResume: true,
+ },
+ },
+ flags: []string{"-expect-ticket-renewal"},
+ resumeSession: true,
+ resumeRenewedSession: true,
+ })
+ tests = append(tests, testCase{
+ name: "Basic-Client-NoTicket",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ resumeSession: true,
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ RequireSessionTickets: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-expect-no-session-id",
+ "-expect-no-hrr",
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server-NoTickets",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ resumeSession: true,
+ flags: []string{"-expect-session-id"},
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server-EarlyCallback",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ flags: []string{"-use-early-callback"},
+ resumeSession: true,
+ })
+ }
+
+ // TLS 1.3 basic handshake shapes.
+ tests = append(tests, testCase{
+ name: "TLS13-1RTT-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ resumeRenewedSession: true,
+ // 0-RTT being disabled overrides all other 0-RTT reasons.
+ flags: []string{"-expect-early-data-reason", "disabled"},
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-1RTT-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ resumeRenewedSession: true,
+ flags: []string{
+ // TLS 1.3 uses tickets, so the session should not be
+ // cached statefully.
+ "-expect-no-session-id",
+ // 0-RTT being disabled overrides all other 0-RTT reasons.
+ "-expect-early-data-reason", "disabled",
+ },
+ })
+
+ tests = append(tests, testCase{
+ name: "TLS13-HelloRetryRequest-Client",
+ 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,
+ },
+ },
+ // Cover HelloRetryRequest during an ECDHE-PSK resumption.
+ resumeSession: true,
+ flags: []string{"-expect-hrr"},
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-HelloRetryRequest-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ },
+ // Cover HelloRetryRequest during an ECDHE-PSK resumption.
+ resumeSession: true,
+ flags: []string{"-expect-hrr"},
+ })
+
+ // TLS 1.3 early data tests. DTLS 1.3 doesn't support early data yet.
+ // These tests are disabled for QUIC as well because they test features
+ // that do not apply to QUIC's use of TLS 1.3.
+ //
+ // TODO(crbug.com/381113363): Enable these tests for DTLS once we
+ // support early data in DTLS 1.3.
+ if config.protocol != dtls && config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyData-TooMuchData-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ MaxEarlyDataSize: 2,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ MaxEarlyDataSize: 2,
+ Bugs: ProtocolBugs{
+ ExpectEarlyData: [][]byte{[]byte(shimInitialWrite[:2])},
+ },
+ },
+ resumeShimPrefix: shimInitialWrite[2:],
+ resumeSession: true,
+ earlyData: true,
+ })
+
+ // Unfinished writes can only be tested when operations are async. EarlyData
+ // can't be tested as part of an ImplicitHandshake in this case since
+ // otherwise the early data will be sent as normal data.
+ if config.async && !config.implicitHandshake {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyData-UnfinishedWrite-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Write the server response before expecting early data.
+ ExpectEarlyData: [][]byte{},
+ ExpectLateEarlyData: [][]byte{[]byte(shimInitialWrite)},
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{"-on-resume-read-with-unfinished-write"},
+ })
+
+ // Rejected unfinished writes are discarded (from the
+ // perspective of the calling application) on 0-RTT
+ // reject.
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyData-RejectUnfinishedWrite-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{"-on-resume-read-with-unfinished-write"},
+ })
+ }
+
+ // Early data has no size limit in QUIC.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-MaxEarlyData-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{bytes.Repeat([]byte{1}, 14336+1)},
+ ExpectEarlyDataAccepted: true,
+ },
+ },
+ messageCount: 2,
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":TOO_MUCH_READ_EARLY_DATA:",
+ })
+ }
+
+ // Test that early data is disabled for DTLS 1.3.
+ if config.protocol == dtls {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ protocol: dtls,
+ name: "DTLS13-EarlyData",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ })
+ }
+
+ // TLS client auth.
+ // The following tests have a max version of 1.2, so they are not suitable
+ // for use with QUIC.
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-NoCertificate-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequestClientCert,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ClientAuth-NoCertificate-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ // Setting SSL_VERIFY_PEER allows anonymous clients.
+ flags: []string{"-verify-peer"},
+ })
+ }
+ if config.protocol != dtls {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-NoCertificate-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequestClientCert,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ClientAuth-NoCertificate-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ // Setting SSL_VERIFY_PEER allows anonymous clients.
+ flags: []string{"-verify-peer"},
+ })
+ }
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-RSA-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &rsaCertificate,
+ })
+ }
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-RSA-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &rsaCertificate,
+ })
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-ECDSA-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ })
+ }
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-ECDSA-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ })
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-NoCertificate-OldCallback",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequestClientCert,
+ },
+ flags: []string{"-use-old-client-cert-callback"},
+ })
+ }
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-NoCertificate-OldCallback-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequestClientCert,
+ },
+ flags: []string{"-use-old-client-cert-callback"},
+ })
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-OldCallback",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-use-old-client-cert-callback",
+ },
+ })
+ }
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-OldCallback-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-use-old-client-cert-callback",
+ },
+ })
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ClientAuth-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ })
+ }
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ClientAuth-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{"-require-any-client-certificate"},
+ })
+
+ // Test each key exchange on the server side for async keys.
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &rsaCertificate,
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server-ECDHE-RSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &rsaCertificate,
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server-ECDHE-ECDSA",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &ecdsaP256Certificate,
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Basic-Server-Ed25519",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ },
+ shimCertificate: &ed25519Certificate,
+ flags: []string{
+ "-verify-prefs", strconv.Itoa(int(signatureEd25519)),
+ },
+ })
+
+ // No session ticket support; server doesn't send NewSessionTicket.
+ tests = append(tests, testCase{
+ name: "SessionTicketsDisabled-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "SessionTicketsDisabled-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ })
+
+ // Skip ServerKeyExchange in PSK key exchange if there's no
+ // identity hint.
+ tests = append(tests, testCase{
+ name: "EmptyPSKHint-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
+ PreSharedKey: []byte("secret"),
+ },
+ flags: []string{"-psk", "secret"},
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "EmptyPSKHint-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
+ PreSharedKey: []byte("secret"),
+ },
+ flags: []string{"-psk", "secret"},
+ })
+ }
+
+ // OCSP stapling tests.
+ for _, vers := range allVersions(config.protocol) {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "OCSPStapling-Client-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-expect-ocsp-response",
+ base64FlagValue(testOCSPResponse),
+ "-verify-peer",
+ },
+ resumeSession: true,
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "OCSPStapling-Server-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ expectations: connectionExpectations{
+ peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ resumeSession: true,
+ })
+
+ // The client OCSP callback is an alternate certificate
+ // verification callback.
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientOCSPCallback-Pass-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-use-ocsp-callback",
+ },
+ })
+ var expectedLocalError string
+ if !config.async {
+ // TODO(davidben): Asynchronous fatal alerts are never
+ // sent. https://crbug.com/boringssl/130.
+ expectedLocalError = "remote error: bad certificate status response"
+ }
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientOCSPCallback-Fail-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-use-ocsp-callback",
+ "-fail-ocsp-callback",
+ },
+ shouldFail: true,
+ expectedLocalError: expectedLocalError,
+ expectedError: ":OCSP_CB_ERROR:",
+ })
+ // The callback still runs if the server does not send an OCSP
+ // response.
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientOCSPCallback-FailNoStaple-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-use-ocsp-callback",
+ "-fail-ocsp-callback",
+ },
+ shouldFail: true,
+ expectedLocalError: expectedLocalError,
+ expectedError: ":OCSP_CB_ERROR:",
+ })
+
+ // The server OCSP callback is a legacy mechanism for
+ // configuring OCSP, used by unreliable server software.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ServerOCSPCallback-SetInCallback-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ expectations: connectionExpectations{
+ peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ },
+ flags: []string{
+ "-use-ocsp-callback",
+ "-set-ocsp-in-callback",
+ },
+ resumeSession: true,
+ })
+
+ // The callback may decline OCSP, in which case we act as if
+ // the client did not support it, even if a response was
+ // configured.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ServerOCSPCallback-Decline-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ expectations: connectionExpectations{
+ // There should be no OCSP response from the peer.
+ peerCertificate: &rsaCertificate,
+ },
+ flags: []string{
+ "-use-ocsp-callback",
+ "-decline-ocsp-callback",
+ },
+ resumeSession: true,
+ })
+
+ // The callback may also signal an internal error.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ServerOCSPCallback-Fail-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
+ flags: []string{
+ "-use-ocsp-callback",
+ "-fail-ocsp-callback",
+ },
+ shouldFail: true,
+ expectedError: ":OCSP_CB_ERROR:",
+ })
+ }
+
+ // Certificate verification tests.
+ for _, vers := range allVersions(config.protocol) {
+ for _, useCustomCallback := range []bool{false, true} {
+ for _, testType := range []testType{clientTest, serverTest} {
+ suffix := "-Client"
+ if testType == serverTest {
+ suffix = "-Server"
+ }
+ suffix += "-" + vers.name
+ if useCustomCallback {
+ suffix += "-CustomCallback"
+ }
+
+ // The custom callback and legacy callback have different default
+ // alerts.
+ verifyFailLocalError := "remote error: handshake failure"
+ if useCustomCallback {
+ verifyFailLocalError = "remote error: unknown certificate"
+ }
+
+ // We do not reliably send asynchronous fatal alerts. See
+ // https://crbug.com/boringssl/130.
+ if config.async {
+ verifyFailLocalError = ""
+ }
+
+ flags := []string{"-verify-peer"}
+ if testType == serverTest {
+ flags = append(flags, "-require-any-client-certificate")
+ }
+ if useCustomCallback {
+ flags = append(flags, "-use-custom-verify-callback")
+ }
+
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationSucceed" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: append([]string{"-expect-verify-result"}, flags...),
+ resumeSession: true,
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationFail" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: append([]string{"-verify-fail"}, flags...),
+ shouldFail: true,
+ expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+ expectedLocalError: verifyFailLocalError,
+ })
+ // Tests that although the verify callback fails on resumption, by default we don't call it.
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationDoesNotFailOnResume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: append([]string{"-on-resume-verify-fail"}, flags...),
+ resumeSession: true,
+ })
+ if testType == clientTest && useCustomCallback {
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationFailsOnResume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: append([]string{
+ "-on-resume-verify-fail",
+ "-reverify-on-resume",
+ }, flags...),
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+ expectedLocalError: verifyFailLocalError,
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationPassesOnResume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: append([]string{
+ "-reverify-on-resume",
+ }, flags...),
+ resumeSession: true,
+ })
+ // TODO(crbug.com/381113363): Support 0-RTT in DTLS 1.3.
+ if vers.version >= VersionTLS13 && config.protocol != dtls {
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "EarlyData-RejectTicket-Client-Reverify" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: vers.version,
+ SessionTicketsDisabled: true,
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: append([]string{
+ "-reverify-on-resume",
+ // Session tickets are disabled, so the runner will not send a ticket.
+ "-on-retry-expect-no-session",
+ }, flags...),
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "EarlyData-Reject0RTT-Client-Reverify" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: false,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: append([]string{
+ "-reverify-on-resume",
+ }, flags...),
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "EarlyData-RejectTicket-Client-ReverifyFails" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ resumeConfig: &Config{
+ MaxVersion: vers.version,
+ SessionTicketsDisabled: true,
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ shouldFail: true,
+ expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+ flags: append([]string{
+ "-reverify-on-resume",
+ // Session tickets are disabled, so the runner will not send a ticket.
+ "-on-retry-expect-no-session",
+ "-on-retry-verify-fail",
+ }, flags...),
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "EarlyData-Reject0RTT-Client-ReverifyFails" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: false,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ shouldFail: true,
+ expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+ expectedLocalError: verifyFailLocalError,
+ flags: append([]string{
+ "-reverify-on-resume",
+ "-on-retry-verify-fail",
+ }, flags...),
+ })
+ // This tests that we only call the verify callback once.
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "EarlyData-Accept0RTT-Client-Reverify" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: append([]string{
+ "-reverify-on-resume",
+ }, flags...),
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "EarlyData-Accept0RTT-Client-ReverifyFails" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+ // We do not set expectedLocalError here because the shim rejects
+ // the connection without an alert.
+ flags: append([]string{
+ "-reverify-on-resume",
+ "-on-resume-verify-fail",
+ }, flags...),
+ })
+ }
+ }
+ }
+ }
+
+ // By default, the client is in a soft fail mode where the peer
+ // certificate is verified but failures are non-fatal.
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "CertificateVerificationSoftFail-" + vers.name,
+ config: Config{
+ MaxVersion: vers.version,
+ Credential: &rsaCertificate,
+ },
+ flags: []string{
+ "-verify-fail",
+ "-expect-verify-result",
+ },
+ resumeSession: true,
+ })
+ }
+
+ tests = append(tests, testCase{
+ name: "ShimSendAlert",
+ flags: []string{"-send-alert"},
+ shimWritesFirst: true,
+ shouldFail: true,
+ expectedLocalError: "remote error: decompression failure",
+ })
+
+ if config.protocol == tls {
+ tests = append(tests, testCase{
+ name: "Renegotiate-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-freely",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+
+ tests = append(tests, testCase{
+ name: "Renegotiate-Client-Explicit",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ renegotiate: 1,
+ flags: []string{
+ "-renegotiate-explicit",
+ "-expect-total-renegotiations", "1",
+ },
+ })
+
+ halfHelloRequestError := ":UNEXPECTED_RECORD:"
+ if config.packHandshake {
+ // If the HelloRequest is sent in the same record as the server Finished,
+ // BoringSSL rejects it before the handshake completes.
+ halfHelloRequestError = ":EXCESS_HANDSHAKE_DATA:"
+ }
+ tests = append(tests, testCase{
+ name: "SendHalfHelloRequest",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ PackHelloRequestWithFinished: config.packHandshake,
+ },
+ },
+ sendHalfHelloRequest: true,
+ flags: []string{"-renegotiate-ignore"},
+ shouldFail: true,
+ expectedError: halfHelloRequestError,
+ })
+
+ // NPN on client and server; results in post-ChangeCipherSpec message.
+ tests = append(tests, testCase{
+ name: "NPN-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{"-select-next-proto", "foo"},
+ resumeSession: true,
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: npn,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "NPN-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"bar"},
+ },
+ flags: []string{
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ "-expect-next-proto", "bar",
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ nextProto: "bar",
+ nextProtoType: npn,
+ },
+ })
+
+ // The client may select no protocol after seeing the server list.
+ tests = append(tests, testCase{
+ name: "NPN-Client-ClientSelectEmpty",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{"-select-empty-next-proto"},
+ resumeSession: true,
+ expectations: connectionExpectations{
+ noNextProto: true,
+ nextProtoType: npn,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "NPN-Server-ClientSelectEmpty",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"no-match"},
+ NoFallbackNextProto: true,
+ },
+ flags: []string{
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ "-expect-no-next-proto",
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ noNextProto: true,
+ nextProtoType: npn,
+ },
+ })
+
+ // The server may negotiate NPN, despite offering no protocols. In this
+ // case, the server must still be prepared for the client to select a
+ // fallback protocol.
+ tests = append(tests, testCase{
+ name: "NPN-Client-ServerAdvertiseEmpty",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NegotiateNPNWithNoProtos: true,
+ },
+ flags: []string{"-select-next-proto", "foo"},
+ resumeSession: true,
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: npn,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "NPN-Server-ServerAdvertiseEmpty",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{
+ "-advertise-empty-npn",
+ "-expect-next-proto", "foo",
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ nextProto: "foo",
+ nextProtoType: npn,
+ },
+ })
+
+ // Client does False Start and negotiates NPN.
+ tests = append(tests, testCase{
+ name: "FalseStart",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-select-next-proto", "foo",
+ },
+ shimWritesFirst: true,
+ resumeSession: true,
+ })
+
+ // Client does False Start and negotiates ALPN.
+ tests = append(tests, testCase{
+ name: "FalseStart-ALPN",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-advertise-alpn", "\x03foo",
+ "-expect-alpn", "foo",
+ },
+ shimWritesFirst: true,
+ resumeSession: true,
+ })
+
+ // False Start without session tickets.
+ tests = append(tests, testCase{
+ name: "FalseStart-SessionTicketsDisabled",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ NextProtos: []string{"foo"},
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ },
+ },
+ flags: []string{
+ "-false-start",
+ "-select-next-proto", "foo",
+ },
+ shimWritesFirst: true,
+ })
+
+ // Server parses a V2ClientHello. Test different lengths for the
+ // challenge field.
+ for _, challengeLength := range []int{16, 31, 32, 33, 48} {
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: fmt.Sprintf("SendV2ClientHello-%d", challengeLength),
+ config: Config{
+ // Choose a cipher suite that does not involve
+ // elliptic curves, so no extensions are
+ // involved.
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ Bugs: ProtocolBugs{
+ SendV2ClientHello: true,
+ V2ClientHelloChallengeLength: challengeLength,
+ },
+ },
+ flags: []string{
+ "-expect-msg-callback",
+ `read v2clienthello
+write hs 2
+write hs 11
+write hs 14
+read hs 16
+read ccs
+read hs 20
+write ccs
+write hs 20
+read alert 1 0
+`,
+ },
+ })
+ }
+
+ // Channel ID and NPN at the same time, to ensure their relative
+ // ordering is correct.
+ tests = append(tests, testCase{
+ name: "ChannelID-NPN-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ RequestChannelID: true,
+ NextProtos: []string{"foo"},
+ },
+ flags: []string{
+ "-send-channel-id", channelIDKeyPath,
+ "-select-next-proto", "foo",
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ nextProto: "foo",
+ nextProtoType: npn,
+ },
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ChannelID-NPN-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ ChannelID: &channelIDKey,
+ NextProtos: []string{"bar"},
+ },
+ flags: []string{
+ "-expect-channel-id",
+ base64FlagValue(channelIDBytes),
+ "-advertise-npn", "\x03foo\x03bar\x03baz",
+ "-expect-next-proto", "bar",
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ nextProto: "bar",
+ nextProtoType: npn,
+ },
+ })
+
+ // Bidirectional shutdown with the runner initiating.
+ tests = append(tests, testCase{
+ name: "Shutdown-Runner",
+ config: Config{
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ flags: []string{"-check-close-notify"},
+ })
+ }
+ if config.protocol != dtls {
+ // Test Channel ID
+ for _, ver := range allVersions(config.protocol) {
+ if ver.version < VersionTLS10 {
+ continue
+ }
+ // Client sends a Channel ID.
+ tests = append(tests, testCase{
+ name: "ChannelID-Client-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ RequestChannelID: true,
+ },
+ flags: []string{"-send-channel-id", channelIDKeyPath},
+ resumeSession: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ },
+ })
+
+ // Server accepts a Channel ID.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ChannelID-Server-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ ChannelID: &channelIDKey,
+ },
+ flags: []string{
+ "-expect-channel-id",
+ base64FlagValue(channelIDBytes),
+ },
+ resumeSession: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ },
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "InvalidChannelIDSignature-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ ChannelID: &channelIDKey,
+ Bugs: ProtocolBugs{
+ InvalidChannelIDSignature: true,
+ },
+ },
+ flags: []string{"-enable-channel-id"},
+ shouldFail: true,
+ expectedError: ":CHANNEL_ID_SIGNATURE_INVALID:",
+ })
+
+ if ver.version < VersionTLS13 {
+ // Channel ID requires ECDHE ciphers.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ChannelID-NoECDHE-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
+ ChannelID: &channelIDKey,
+ },
+ expectations: connectionExpectations{
+ channelID: false,
+ },
+ flags: []string{"-enable-channel-id"},
+ })
+
+ // Sanity-check setting expectations.channelID false works.
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "ChannelID-ECDHE-" + ver.name,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ ChannelID: &channelIDKey,
+ },
+ expectations: connectionExpectations{
+ channelID: false,
+ },
+ flags: []string{"-enable-channel-id"},
+ shouldFail: true,
+ expectedLocalError: "channel ID unexpectedly negotiated",
+ })
+ }
+ }
+
+ if !config.implicitHandshake {
+ // Bidirectional shutdown with the shim initiating. The runner,
+ // in the meantime, sends garbage before the close_notify which
+ // the shim must ignore. This test is disabled under implicit
+ // handshake tests because the shim never reads or writes.
+
+ // Tests that require checking for a close notify alert don't work with
+ // QUIC because alerts are handled outside of the TLS stack in QUIC.
+ if config.protocol != quic {
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ sendEmptyRecords: 1,
+ sendWarningAlerts: 1,
+ flags: []string{"-check-close-notify"},
+ })
+
+ // The shim should reject unexpected application data
+ // when shutting down.
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim-ApplicationData",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ messageCount: 1,
+ sendEmptyRecords: 1,
+ sendWarningAlerts: 1,
+ flags: []string{"-check-close-notify"},
+ shouldFail: true,
+ expectedError: ":APPLICATION_DATA_ON_SHUTDOWN:",
+ })
+
+ // Test that SSL_shutdown still processes KeyUpdate.
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim-KeyUpdate",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ sendKeyUpdates: 1,
+ keyUpdateRequest: keyUpdateRequested,
+ flags: []string{"-check-close-notify"},
+ })
+
+ // Test that SSL_shutdown processes HelloRequest
+ // correctly.
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim-HelloRequest-Ignore",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendHelloRequestBeforeEveryAppDataRecord: true,
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ flags: []string{
+ "-renegotiate-ignore",
+ "-check-close-notify",
+ },
+ })
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim-HelloRequest-Reject",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ renegotiate: 1,
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ flags: []string{"-check-close-notify"},
+ })
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim-HelloRequest-CannotHandshake",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ renegotiate: 1,
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ flags: []string{
+ "-check-close-notify",
+ "-renegotiate-freely",
+ },
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "Shutdown-Shim-Renegotiate-Server-Forbidden",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ renegotiate: 1,
+ shouldFail: true,
+ expectedError: ":NO_RENEGOTIATION:",
+ flags: []string{
+ "-check-close-notify",
+ },
+ })
+ }
+ }
+ }
+ if config.protocol == dtls {
+ tests = append(tests, testCase{
+ name: "SkipHelloVerifyRequest",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SkipHelloVerifyRequest: true,
+ },
+ },
+ })
+ tests = append(tests, testCase{
+ name: "DTLS13-HelloVerifyRequest",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ForceHelloVerifyRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_MESSAGE:",
+ })
+ tests = append(tests, testCase{
+ name: "DTLS13-HelloVerifyRequestEmptyCookie",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ForceHelloVerifyRequest: true,
+ EmptyHelloVerifyRequestCookie: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_MESSAGE:",
+ })
+ }
+
+ for _, test := range tests {
+ test.protocol = config.protocol
+ test.name += "-" + config.protocol.String()
+ if config.async {
+ test.name += "-Async"
+ test.flags = append(test.flags, "-async")
+ } else {
+ test.name += "-Sync"
+ }
+ if config.splitHandshake {
+ test.name += "-SplitHandshakeRecords"
+ test.config.Bugs.MaxHandshakeRecordLength = 1
+ if config.protocol == dtls {
+ test.config.Bugs.MaxPacketLength = 256
+ test.flags = append(test.flags, "-mtu", "256")
+ }
+ }
+ if config.packHandshake {
+ test.name += "-PackHandshake"
+ if config.protocol == dtls {
+ test.config.Bugs.MaxHandshakeRecordLength = 2
+ test.config.Bugs.PackHandshakeFragments = 20
+ test.config.Bugs.PackHandshakeRecords = 1500
+ test.config.Bugs.PackAppDataWithHandshake = true
+ } else {
+ test.config.Bugs.PackHandshakeFlight = true
+ }
+ }
+ if config.implicitHandshake {
+ test.name += "-ImplicitHandshake"
+ test.flags = append(test.flags, "-implicit-handshake")
+ }
+ testCases = append(testCases, test)
+ }
+}
diff --git a/ssl/test/runner/tls13_tests.go b/ssl/test/runner/tls13_tests.go
new file mode 100644
index 0000000..2b08ec8
--- /dev/null
+++ b/ssl/test/runner/tls13_tests.go
@@ -0,0 +1,2066 @@
+// 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 "strconv"
+
+func addTLS13RecordTests() {
+ for _, protocol := range []protocol{tls, dtls} {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "TLS13-RecordPadding-" + protocol.String(),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ RecordPadding: 10,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "TLS13-EmptyRecords-" + protocol.String(),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ OmitRecordContents: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "TLS13-OnlyPadding-" + protocol.String(),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ OmitRecordContents: true,
+ RecordPadding: 10,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+
+ if protocol == tls {
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "TLS13-WrongOuterRecord-" + protocol.String(),
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ OuterRecordType: recordTypeHandshake,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_OUTER_RECORD_TYPE:",
+ })
+ }
+ }
+}
+
+func addTLS13HandshakeTests() {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "NegotiatePSKResumption-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ NegotiatePSKResumption: true,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":MISSING_KEY_SHARE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "MissingKeyShare-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ MissingKeyShare: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":MISSING_KEY_SHARE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "MissingKeyShare-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ MissingKeyShare: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":MISSING_KEY_SHARE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "DuplicateKeyShares-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ DuplicateKeyShares: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DUPLICATE_KEY_SHARE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ },
+ },
+ })
+
+ // Test that enabling TLS 1.3 does not interfere with TLS 1.2 session ID
+ // resumption.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ResumeTLS12SessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS13)},
+ resumeSession: true,
+ })
+
+ // Test that the client correctly handles a TLS 1.3 ServerHello which echoes
+ // a TLS 1.2 session ID.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS12SessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+
+ // Test that the server correctly echoes back session IDs of
+ // various lengths. The first test additionally asserts that
+ // BoringSSL always sends the ChangeCipherSpec messages for
+ // compatibility mode, rather than negotiating it based on the
+ // ClientHello.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EmptySessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendClientHelloSessionID: []byte{},
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-ShortSessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendClientHelloSessionID: make([]byte, 16),
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-FullSessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendClientHelloSessionID: make([]byte, 32),
+ },
+ },
+ })
+
+ // The server should reject ClientHellos whose session IDs are too long.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-TooLongSessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendClientHelloSessionID: make([]byte, 33),
+ },
+ },
+ shouldFail: true,
+ expectedError: ":CLIENTHELLO_PARSE_FAILED:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-TooLongSessionID-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendClientHelloSessionID: make([]byte, 33),
+ },
+ },
+ shouldFail: true,
+ expectedError: ":CLIENTHELLO_PARSE_FAILED:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ // Test that the client correctly accepts or rejects short session IDs from
+ // the server. Our tests use 32 bytes by default, so the boundary condition
+ // is already covered.
+ testCases = append(testCases, testCase{
+ name: "Client-ShortSessionID",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ NewSessionIDLength: 1,
+ },
+ },
+ resumeSession: true,
+ })
+ testCases = append(testCases, testCase{
+ name: "Client-TooLongSessionID",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ NewSessionIDLength: 33,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ // Test that the client sends a fake session ID in TLS 1.3. We cover both
+ // normal and resumption handshakes to capture interactions with the
+ // session resumption path.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13SessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectClientHelloSessionID: true,
+ },
+ },
+ resumeSession: true,
+ })
+
+ // Test that the client omits the fake session ID when the max version is TLS 1.2 and below.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS12NoSessionID-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectNoSessionID: true,
+ },
+ },
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-on-initial-expect-early-data-reason", "no_session_offered",
+ "-on-resume-expect-early-data-reason", "accept",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-Reject-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-retry-expect-early-data-reason", "peer_declined",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ messageCount: 2,
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-on-initial-expect-early-data-reason", "no_session_offered",
+ "-on-resume-expect-early-data-reason", "accept",
+ },
+ })
+
+ // The above tests the most recent ticket. Additionally test that 0-RTT
+ // works on the first ticket issued by the server.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-FirstTicket-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ UseFirstSessionTicket: true,
+ },
+ },
+ messageCount: 2,
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-on-resume-expect-early-data-reason", "accept",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-OmitEarlyDataExtension-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ OmitEarlyDataExtension: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-OmitEarlyDataExtension-HelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ OmitEarlyDataExtension: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-TooMuchData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 16384 + 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-Interleaved-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ InterleaveEarlyData: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-EarlyDataInTLS12-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ // Though the session is not resumed and we send HelloRetryRequest,
+ // early data being disabled takes priority as the reject reason.
+ flags: []string{"-expect-early-data-reason", "disabled"},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-Interleaved-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 4,
+ InterleaveEarlyData: true,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-TooMuchData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendFakeEarlyDataLength: 16384 + 1,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
+ })
+
+ // Test that skipping early data looking for cleartext correctly
+ // processes an alert record.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-FatalAlert-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyAlert: true,
+ SendFakeEarlyDataLength: 4,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedError: ":SSLV3_ALERT_HANDSHAKE_FAILURE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-SecondClientHelloEarlyData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataOnSecondClientHello: true,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: bad record MAC",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EmptyEncryptedExtensions-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ EmptyEncryptedExtensions: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decoding message",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EncryptedExtensionsWithKeyShare-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ EncryptedExtensionsWithKeyShare: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendHelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ expectations: connectionExpectations{
+ curveID: CurveX25519,
+ },
+ flags: []string{"-expect-hrr"},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendHelloRetryRequest-2-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveP384},
+ CurvePreferences: []CurveID{CurveX25519, CurveP384},
+ },
+ // Although the ClientHello did not predict our preferred curve,
+ // we always select it whether it is predicted or not.
+ expectations: connectionExpectations{
+ curveID: CurveX25519,
+ },
+ flags: []string{"-expect-hrr"},
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnknownCurve-HelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCurve: bogusCurve,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-CipherChange-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_AES_128_GCM_SHA256,
+ SendHelloRetryRequestCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CIPHER_RETURNED:",
+ })
+
+ // Test that the client does not offer a PSK in the second ClientHello if the
+ // HelloRetryRequest is incompatible with it.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "HelloRetryRequest-NonResumableCipher-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{
+ TLS_AES_128_GCM_SHA256,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ ExpectNoTLS13PSKAfterHRR: true,
+ },
+ CipherSuites: []uint16{
+ TLS_AES_256_GCM_SHA384,
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ })
+
+ testCases = append(testCases, testCase{
+ name: "DisabledCurve-HelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP256},
+ Bugs: ProtocolBugs{
+ IgnorePeerCurvePreferences: true,
+ },
+ },
+ flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnnecessaryHelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveX25519},
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCurve: CurveX25519,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SecondHelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SecondHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-Empty-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysSendHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":EMPTY_HELLO_RETRY_REQUEST:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-DuplicateCurve-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires a HelloRetryRequest against BoringSSL's default
+ // configuration. Assert this ExpectMissingKeyShare.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ ExpectMissingKeyShare: true,
+ DuplicateHelloRetryRequestExtensions: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DUPLICATE_EXTENSION:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-Cookie-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte("cookie"),
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-DuplicateCookie-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte("cookie"),
+ DuplicateHelloRetryRequestExtensions: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DUPLICATE_EXTENSION:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-EmptyCookie-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-Cookie-Curve-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte("cookie"),
+ ExpectMissingKeyShare: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequest-Unknown-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ CustomHelloRetryRequestExtension: "extension",
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SecondClientHelloMissingKeyShare-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ SecondClientHelloMissingKeyShare: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":MISSING_KEY_SHARE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SecondClientHelloWrongCurve-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ MisinterpretHelloRetryRequestCurve: CurveP521,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequestVersionMismatch-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendServerHelloVersion: 0x0305,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequestCurveMismatch-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ // Send P-384 (correct) in the HelloRetryRequest.
+ SendHelloRetryRequestCurve: CurveP384,
+ // But send P-256 in the ServerHello.
+ SendCurve: CurveP256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ // Test the server selecting a curve that requires a HelloRetryRequest
+ // without sending it.
+ testCases = append(testCases, testCase{
+ name: "SkipHelloRetryRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SkipHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SecondServerHelloNoVersion-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ OmitServerSupportedVersionExtension: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":SECOND_SERVERHELLO_VERSION_MISMATCH:",
+ })
+ testCases = append(testCases, testCase{
+ name: "SecondServerHelloWrongVersion-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendServerSupportedVersionExtension: 0x1234,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":SECOND_SERVERHELLO_VERSION_MISMATCH:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "RequestContextInHandshake-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ SendRequestContext: []byte("request context"),
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnknownExtensionInCertificateRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ SendCustomCertificateRequest: 0x1212,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ })
+
+ testCases = append(testCases, testCase{
+ name: "MissingSignatureAlgorithmsInCertificateRequest-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ OmitCertificateRequestAlgorithms: true,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrailingKeyShareData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ TrailingKeyShareData: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "AlwaysSelectPSKIdentity-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysSelectPSKIdentity: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "InvalidPSKIdentity-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SelectPSKIdentityOnResume: 1,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":PSK_IDENTITY_NOT_FOUND:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ExtraPSKIdentity-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExtraPSKIdentity: true,
+ SendExtraPSKBinder: true,
+ },
+ },
+ resumeSession: true,
+ })
+
+ // Test that unknown NewSessionTicket extensions are tolerated.
+ testCases = append(testCases, testCase{
+ name: "CustomTicketExtension-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ CustomTicketExtension: "1234",
+ },
+ },
+ })
+
+ // Test the client handles 0-RTT being rejected by a full handshake
+ // and correctly reports a certificate change.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-RejectTicket-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &rsaCertificate,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Credential: &ecdsaP256Certificate,
+ SessionTicketsDisabled: true,
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-retry-expect-early-data-reason", "session_not_resumed",
+ // Test the peer certificate is reported correctly in each of the
+ // three logical connections.
+ "-on-initial-expect-peer-cert-file", rsaCertificate.ChainPath,
+ "-on-resume-expect-peer-cert-file", rsaCertificate.ChainPath,
+ "-on-retry-expect-peer-cert-file", ecdsaP256Certificate.ChainPath,
+ // Session tickets are disabled, so the runner will not send a ticket.
+ "-on-retry-expect-no-session",
+ },
+ })
+
+ // Test the server rejects 0-RTT if it does not recognize the ticket.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-RejectTicket-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Corrupt the ticket.
+ FilterTicket: func(in []byte) ([]byte, error) {
+ in[len(in)-1] ^= 1
+ return in, nil
+ },
+ },
+ },
+ messageCount: 2,
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-resume-expect-early-data-reason", "session_not_resumed",
+ },
+ })
+
+ // Test the client handles 0-RTT being rejected via a HelloRetryRequest.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-HRR-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-retry-expect-early-data-reason", "hello_retry_request",
+ },
+ })
+
+ // Test the server rejects 0-RTT if it needs to send a HelloRetryRequest.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-HRR-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ },
+ messageCount: 2,
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-resume-expect-early-data-reason", "hello_retry_request",
+ },
+ })
+
+ // Test the client handles a 0-RTT reject from both ticket rejection and
+ // HelloRetryRequest.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-HRR-RejectTicket-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Credential: &rsaCertificate,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Credential: &ecdsaP256Certificate,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ // The client sees HelloRetryRequest before the resumption result,
+ // though neither value is inherently preferable.
+ "-on-retry-expect-early-data-reason", "hello_retry_request",
+ // Test the peer certificate is reported correctly in each of the
+ // three logical connections.
+ "-on-initial-expect-peer-cert-file", rsaCertificate.ChainPath,
+ "-on-resume-expect-peer-cert-file", rsaCertificate.ChainPath,
+ "-on-retry-expect-peer-cert-file", ecdsaP256Certificate.ChainPath,
+ // Session tickets are disabled, so the runner will not send a ticket.
+ "-on-retry-expect-no-session",
+ },
+ })
+
+ // Test the server rejects 0-RTT if it needs to send a HelloRetryRequest.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-HRR-RejectTicket-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ // Corrupt the ticket.
+ FilterTicket: func(in []byte) ([]byte, error) {
+ in[len(in)-1] ^= 1
+ return in, nil
+ },
+ },
+ },
+ messageCount: 2,
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ // The server sees the missed resumption before HelloRetryRequest,
+ // though neither value is inherently preferable.
+ "-on-resume-expect-early-data-reason", "session_not_resumed",
+ },
+ })
+
+ // The client must check the server does not send the early_data
+ // extension while rejecting the session.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataWithoutResume-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ SendEarlyDataExtension: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // The client must fail with a dedicated error code if the server
+ // responds with TLS 1.2 when offering 0-RTT.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataVersionDowngrade-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
+ })
+
+ // Same as above, but the server also sends a warning alert before the
+ // ServerHello. Although the shim predicts TLS 1.3 for 0-RTT, it should
+ // still interpret data before ServerHello in a TLS-1.2-compatible way.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataVersionDowngrade-Client-TLS13-WarningAlert",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendSNIWarningAlert: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
+ })
+
+ // Test that the client rejects an (unsolicited) early_data extension if
+ // the server sent an HRR.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ServerAcceptsEarlyDataOnHRR-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ SendEarlyDataExtension: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ // The client will first process an early data reject from the HRR.
+ expectEarlyDataRejected: true,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "SkipChangeCipherSpec-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SkipChangeCipherSpec: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipChangeCipherSpec-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SkipChangeCipherSpec: true,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TooManyChangeCipherSpec-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtraChangeCipherSpec: 33,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TooManyChangeCipherSpec-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendExtraChangeCipherSpec: 33,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SendPostHandshakeChangeCipherSpec-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendPostHandshakeChangeCipherSpec: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ fooString := "foo"
+ barString := "bar"
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject
+ // that changed it.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-ALPNMismatch-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &fooString,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &barString,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ // The client does not learn ALPN was the cause.
+ "-on-retry-expect-early-data-reason", "peer_declined",
+ // In the 0-RTT state, we surface the predicted ALPN. After
+ // processing the reject, we surface the real one.
+ "-on-initial-expect-alpn", "foo",
+ "-on-resume-expect-alpn", "foo",
+ "-on-retry-expect-alpn", "bar",
+ },
+ })
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject if
+ // ALPN was omitted from the first connection.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-ALPNOmitted1-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ // The client does not learn ALPN was the cause.
+ "-on-retry-expect-early-data-reason", "peer_declined",
+ // In the 0-RTT state, we surface the predicted ALPN. After
+ // processing the reject, we surface the real one.
+ "-on-initial-expect-alpn", "",
+ "-on-resume-expect-alpn", "",
+ "-on-retry-expect-alpn", "foo",
+ },
+ })
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject if
+ // ALPN was omitted from the second connection.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-ALPNOmitted2-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ // The client does not learn ALPN was the cause.
+ "-on-retry-expect-early-data-reason", "peer_declined",
+ // In the 0-RTT state, we surface the predicted ALPN. After
+ // processing the reject, we surface the real one.
+ "-on-initial-expect-alpn", "foo",
+ "-on-resume-expect-alpn", "foo",
+ "-on-retry-expect-alpn", "",
+ },
+ })
+
+ // Test that the client enforces ALPN match on 0-RTT accept.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-BadALPNMismatch-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &fooString,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysAcceptEarlyData: true,
+ ALPNProtocol: &barString,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-on-initial-expect-alpn", "foo",
+ "-on-resume-expect-alpn", "foo",
+ "-on-retry-expect-alpn", "bar",
+ },
+ shouldFail: true,
+ expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ // Test that the client does not offer early data if it is incompatible
+ // with ALPN preferences.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-ALPNPreferenceChanged-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ NextProtos: []string{"foo", "bar"},
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-ticket-supports-early-data",
+ "-expect-no-offer-early-data",
+ // Offer different ALPN values in the initial and resumption.
+ "-on-initial-advertise-alpn", "\x03foo",
+ "-on-initial-expect-alpn", "foo",
+ "-on-resume-advertise-alpn", "\x03bar",
+ "-on-resume-expect-alpn", "bar",
+ // The ALPN mismatch comes from the client, so it reports it as the
+ // reason.
+ "-on-resume-expect-early-data-reason", "alpn_mismatch",
+ },
+ })
+
+ // Test that the client does not offer 0-RTT to servers which never
+ // advertise it.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-NonZeroRTTSession-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-on-resume-expect-no-offer-early-data",
+ // The client declines to offer 0-RTT because of the session.
+ "-on-resume-expect-early-data-reason", "unsupported_for_session",
+ },
+ })
+
+ // Test that the server correctly rejects 0-RTT when the previous
+ // session did not allow early data on resumption.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-NonZeroRTTSession-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{1, 2, 3, 4}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ // This test configures early data manually instead of the earlyData
+ // option, to customize the -enable-early-data flag.
+ flags: []string{
+ "-on-resume-enable-early-data",
+ "-expect-reject-early-data",
+ // The server rejects 0-RTT because of the session.
+ "-on-resume-expect-early-data-reason", "unsupported_for_session",
+ },
+ })
+
+ // Test that we reject early data where ALPN is omitted from the first
+ // connection, but negotiated in the second.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-ALPNOmitted1-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-initial-select-alpn", "",
+ "-on-resume-select-alpn", "foo",
+ "-on-resume-expect-early-data-reason", "alpn_mismatch",
+ },
+ })
+
+ // Test that we reject early data where ALPN is omitted from the second
+ // connection, but negotiated in the first.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-ALPNOmitted2-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{},
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-initial-select-alpn", "foo",
+ "-on-resume-select-alpn", "",
+ "-on-resume-expect-early-data-reason", "alpn_mismatch",
+ },
+ })
+
+ // Test that we reject early data with mismatched ALPN.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-ALPNMismatch-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"bar"},
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-initial-select-alpn", "foo",
+ "-on-resume-select-alpn", "bar",
+ "-on-resume-expect-early-data-reason", "alpn_mismatch",
+ },
+ })
+
+ // Test that the client offering 0-RTT and Channel ID forbids the server
+ // from accepting both.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataChannelID-AcceptBoth-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ RequestChannelID: true,
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+ expectedLocalError: "remote error: illegal parameter",
+ flags: []string{
+ "-send-channel-id", channelIDKeyPath,
+ },
+ })
+
+ // Test that the client offering Channel ID and 0-RTT allows the server
+ // to decline 0-RTT.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataChannelID-AcceptChannelID-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ RequestChannelID: true,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ },
+ flags: []string{
+ "-send-channel-id", channelIDKeyPath,
+ // The client never learns the reason was Channel ID.
+ "-on-retry-expect-early-data-reason", "peer_declined",
+ },
+ })
+
+ // Test that the client offering Channel ID and 0-RTT allows the server
+ // to decline Channel ID.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataChannelID-AcceptEarlyData-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-send-channel-id", channelIDKeyPath,
+ },
+ })
+
+ // Test that the server supporting Channel ID and 0-RTT declines 0-RTT
+ // if it would negotiate Channel ID.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyDataChannelID-OfferBoth-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ ChannelID: &channelIDKey,
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ expectations: connectionExpectations{
+ channelID: true,
+ },
+ flags: []string{
+ "-expect-channel-id",
+ base64FlagValue(channelIDBytes),
+ "-on-resume-expect-early-data-reason", "channel_id",
+ },
+ })
+
+ // Test that the server supporting Channel ID and 0-RTT accepts 0-RTT
+ // if not offered Channel ID.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyDataChannelID-OfferEarlyData-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ expectations: connectionExpectations{
+ channelID: false,
+ },
+ flags: []string{
+ "-enable-channel-id",
+ "-on-resume-expect-early-data-reason", "accept",
+ },
+ })
+
+ // Test that the server errors on 0-RTT streams without EndOfEarlyData.
+ // The subsequent records should fail to decrypt.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-SkipEndOfEarlyData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SkipEndOfEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedLocalError: "remote error: bad record MAC",
+ expectedError: ":BAD_DECRYPT:",
+ })
+
+ // Test that EndOfEarlyData is rejected in QUIC. Since we leave application
+ // data to the QUIC implementation, we never accept any data at all in
+ // the 0-RTT epoch, so the error is that the encryption level is rejected
+ // outright.
+ //
+ // TODO(crbug.com/381113363): Test this for DTLS 1.3 as well.
+ testCases = append(testCases, testCase{
+ protocol: quic,
+ testType: serverTest,
+ name: "EarlyData-UnexpectedEndOfEarlyData-QUIC",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEndOfEarlyDataInQUICAndDTLS: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":WRONG_ENCRYPTION_LEVEL_RECEIVED:",
+ })
+
+ // Test that the server errors on 0-RTT streams with a stray handshake
+ // message in them.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-UnexpectedHandshake-Server-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendStrayEarlyHandshake: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // Test that the client reports TLS 1.3 as the version while sending
+ // early data.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-Client-VersionAPI-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-expect-version", strconv.Itoa(VersionTLS13),
+ // EMS and RI are always reported as supported when we report
+ // TLS 1.3.
+ "-expect-extended-master-secret",
+ "-expect-secure-renegotiation",
+ },
+ })
+
+ // Test that client and server both notice handshake errors after data
+ // has started flowing.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-Client-BadFinished-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ expectedLocalError: "remote error: error decrypting message",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyData-Server-BadFinished-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ BadFinished: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":DIGEST_CHECK_FAILED:",
+ expectedLocalError: "remote error: error decrypting message",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Server-NonEmptyEndOfEarlyData-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ NonEmptyEndOfEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ServerSkipCertificateVerify-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Credential: &rsaChainCertificate,
+ Bugs: ProtocolBugs{
+ SkipCertificateVerify: true,
+ },
+ },
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ shimCertificate: &rsaCertificate,
+ flags: []string{
+ "-require-any-client-certificate",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "ClientSkipCertificateVerify-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Credential: &rsaChainCertificate,
+ Bugs: ProtocolBugs{
+ SkipCertificateVerify: true,
+ },
+ },
+ expectations: connectionExpectations{
+ peerCertificate: &rsaCertificate,
+ },
+ shimCertificate: &rsaCertificate,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // PSK/resumption handshakes should not accept CertificateRequest or
+ // Certificate messages.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertificateInResumption-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysSendCertificate: true,
+ },
+ },
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "CertificateRequestInResumption-TLS13",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ ClientAuth: RequireAnyClientCert,
+ Bugs: ProtocolBugs{
+ AlwaysSendCertificateRequest: true,
+ },
+ },
+ shimCertificate: &rsaCertificate,
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ expectedLocalError: "remote error: unexpected message",
+ })
+
+ // If the client or server has 0-RTT enabled but disabled TLS 1.3, it should
+ // report a reason of protocol_version.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyDataEnabled-Client-MaxTLS12",
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ flags: []string{
+ "-enable-early-data",
+ "-max-version", strconv.Itoa(VersionTLS12),
+ "-expect-early-data-reason", "protocol_version",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyDataEnabled-Server-MaxTLS12",
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ flags: []string{
+ "-enable-early-data",
+ "-max-version", strconv.Itoa(VersionTLS12),
+ "-expect-early-data-reason", "protocol_version",
+ },
+ })
+
+ // The server additionally reports protocol_version if it enabled TLS 1.3,
+ // but the peer negotiated TLS 1.2. (The corresponding situation does not
+ // exist on the client because negotiating TLS 1.2 with a 0-RTT ClientHello
+ // is a fatal error.)
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "EarlyDataEnabled-Server-NegotiateTLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-reason", "protocol_version",
+ },
+ })
+
+ // On 0-RTT reject, the server may end up negotiating a cipher suite with a
+ // different PRF hash. Test that the client handles this correctly.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-Reject0RTT-DifferentPRF-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_256_GCM_SHA384},
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-initial-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
+ // The client initially reports the old cipher suite while sending
+ // early data. After processing the 0-RTT reject, it reports the
+ // true cipher suite.
+ "-on-resume-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
+ "-on-retry-expect-cipher", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)),
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-Reject0RTT-DifferentPRF-HRR-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_256_GCM_SHA384},
+ // P-384 requires a HelloRetryRequest against BoringSSL's default
+ // configuration. Assert this with ExpectMissingKeyShare.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ ExpectMissingKeyShare: true,
+ },
+ },
+ resumeSession: true,
+ expectResumeRejected: true,
+ earlyData: true,
+ expectEarlyDataRejected: true,
+ flags: []string{
+ "-on-initial-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
+ // The client initially reports the old cipher suite while sending
+ // early data. After processing the 0-RTT reject, it reports the
+ // true cipher suite.
+ "-on-resume-expect-cipher", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
+ "-on-retry-expect-cipher", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)),
+ },
+ })
+
+ // Test that the client enforces cipher suite match on 0-RTT accept.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-CipherMismatch-Client-TLS13",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ AlwaysAcceptEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":CIPHER_MISMATCH_ON_EARLY_DATA:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ // Test that the client can write early data when it has received a partial
+ // ServerHello..Finished flight. See https://crbug.com/1208784. Note the
+ // EncryptedExtensions test assumes EncryptedExtensions and Finished are in
+ // separate records, i.e. that PackHandshakeFlight is disabled.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-WriteAfterServerHello",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Write the server response before expecting early data.
+ ExpectEarlyData: [][]byte{},
+ ExpectLateEarlyData: [][]byte{[]byte(shimInitialWrite)},
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-async",
+ "-on-resume-early-write-after-message",
+ strconv.Itoa(int(typeServerHello)),
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "EarlyData-WriteAfterEncryptedExtensions",
+ config: Config{
+ MinVersion: VersionTLS13,
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ // Write the server response before expecting early data.
+ ExpectEarlyData: [][]byte{},
+ ExpectLateEarlyData: [][]byte{[]byte(shimInitialWrite)},
+ },
+ },
+ resumeSession: true,
+ earlyData: true,
+ flags: []string{
+ "-async",
+ "-on-resume-early-write-after-message",
+ strconv.Itoa(int(typeEncryptedExtensions)),
+ },
+ })
+}
+
+func addTLS13CipherPreferenceTests() {
+ // Test that client preference is honored if the shim has AES hardware
+ // and ChaCha20-Poly1305 is preferred otherwise.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-CipherPreference-Server-ChaCha20-AES",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{
+ TLS_CHACHA20_POLY1305_SHA256,
+ TLS_AES_128_GCM_SHA256,
+ },
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ flags: []string{
+ "-expect-cipher-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+ "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-CipherPreference-Server-AES-ChaCha20",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{
+ TLS_AES_128_GCM_SHA256,
+ TLS_CHACHA20_POLY1305_SHA256,
+ },
+ CurvePreferences: []CurveID{CurveX25519},
+ },
+ flags: []string{
+ "-expect-cipher-aes", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
+ "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+ },
+ })
+
+ // Test that the client orders ChaCha20-Poly1305 and AES-GCM based on
+ // whether it has AES hardware.
+ testCases = append(testCases, testCase{
+ name: "TLS13-CipherPreference-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Use the client cipher order. (This is the default but
+ // is listed to be explicit.)
+ PreferServerCipherSuites: false,
+ },
+ flags: []string{
+ "-expect-cipher-aes", strconv.Itoa(int(TLS_AES_128_GCM_SHA256)),
+ "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+ },
+ })
+}
diff --git a/ssl/test/runner/trust_anchor_tests.go b/ssl/test/runner/trust_anchor_tests.go
new file mode 100644
index 0000000..bdb7812
--- /dev/null
+++ b/ssl/test/runner/trust_anchor_tests.go
@@ -0,0 +1,277 @@
+// 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 "golang.org/x/crypto/cryptobyte"
+
+func trustAnchorListFlagValue(ids ...[]byte) string {
+ b := cryptobyte.NewBuilder(nil)
+ for _, id := range ids {
+ addUint8LengthPrefixedBytes(b, id)
+ }
+ return base64FlagValue(b.BytesOrPanic())
+}
+
+func addTrustAnchorTests() {
+ id1 := []byte{1}
+ id2 := []byte{2, 2}
+ id3 := []byte{3, 3, 3}
+
+ // Unsolicited trust_anchors extensions should be rejected.
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-Unsolicited-Certificate",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ AlwaysMatchTrustAnchorID: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: unsupported extension",
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-Unsolicited-EncryptedExtensions",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{id1, id2},
+ Bugs: ProtocolBugs{
+ AlwaysSendAvailableTrustAnchors: true,
+ },
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: unsupported extension",
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // Test that the client sends trust anchors when configured, and correctly
+ // reports the server's response.
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-ClientRequest-Match",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{id1, id2},
+ Credential: rsaChainCertificate.WithTrustAnchorID(id1),
+ Bugs: ProtocolBugs{
+ ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
+ },
+ },
+ flags: []string{
+ "-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
+ "-expect-peer-match-trust-anchor",
+ "-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
+ },
+ })
+ // The client should not like it if the server indicates the match with a non-empty
+ // extension.
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-ClientRequest-Match-Non-Empty-Extension",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{id1, id2},
+ Credential: rsaChainCertificate.WithTrustAnchorID(id1),
+ Bugs: ProtocolBugs{
+ SendNonEmptyTrustAnchorMatch: true,
+ ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
+ },
+ },
+ flags: []string{
+ "-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: error decoding message",
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ // The client should not like it if the server indicates the match on the incorrect
+ // certificate in the Certificate message.
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-ClientRequest-Match-On-Incorrect-Certificate",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{id1, id2},
+ Credential: rsaChainCertificate.WithTrustAnchorID(id1),
+ Bugs: ProtocolBugs{
+ SendTrustAnchorWrongCertificate: true,
+ ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
+ },
+ },
+ flags: []string{
+ "-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: unsupported extension",
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-ClientRequest-NoMatch",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{id1, id2},
+ Bugs: ProtocolBugs{
+ ExpectPeerRequestedTrustAnchors: [][]byte{id3},
+ },
+ },
+ flags: []string{
+ "-requested-trust-anchors", trustAnchorListFlagValue(id3),
+ "-expect-no-peer-match-trust-anchor",
+ "-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
+ },
+ })
+
+ // An empty trust anchor ID is a syntax error, so most be rejected in both
+ // ClientHello and EncryptedExtensions.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrustAnchors-EmptyID-ClientHello",
+ config: Config{
+ MinVersion: VersionTLS13,
+ RequestTrustAnchors: [][]byte{{}},
+ },
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-EmptyID-EncryptedExtensions",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{{}},
+ },
+ flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
+ shouldFail: true,
+ expectedError: ":DECODE_ERROR:",
+ })
+
+ // Test the server selection logic, as well as whether it correctly reports
+ // available trust anchors and the match status. (The general selection flow
+ // is covered in addCertificateSelectionTests.)
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrustAnchors-ServerSelect-Match",
+ config: Config{
+ MinVersion: VersionTLS13,
+ RequestTrustAnchors: [][]byte{id2},
+ Bugs: ProtocolBugs{
+ ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
+ ExpectPeerMatchTrustAnchor: ptrTo(true),
+ },
+ },
+ shimCredentials: []*Credential{
+ rsaCertificate.WithTrustAnchorID(id1),
+ rsaCertificate.WithTrustAnchorID(id2),
+ },
+ flags: []string{"-expect-selected-credential", "1"},
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrustAnchors-ServerSelect-None",
+ config: Config{
+ MinVersion: VersionTLS13,
+ RequestTrustAnchors: [][]byte{id1},
+ },
+ shimCredentials: []*Credential{
+ rsaCertificate.WithTrustAnchorID(id2),
+ rsaCertificate.WithTrustAnchorID(id3),
+ },
+ shouldFail: true,
+ expectedError: ":NO_MATCHING_ISSUER:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrustAnchors-ServerSelect-Fallback",
+ config: Config{
+ MinVersion: VersionTLS13,
+ RequestTrustAnchors: [][]byte{id1},
+ Bugs: ProtocolBugs{
+ ExpectPeerAvailableTrustAnchors: [][]byte{id2, id3},
+ ExpectPeerMatchTrustAnchor: ptrTo(false),
+ },
+ },
+ shimCredentials: []*Credential{
+ rsaCertificate.WithTrustAnchorID(id2),
+ rsaCertificate.WithTrustAnchorID(id3),
+ &rsaCertificate,
+ },
+ flags: []string{"-expect-selected-credential", "2"},
+ })
+
+ // The ClientHello list may be empty. The client must be able to send it and
+ // receive available trust anchors.
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-ClientRequestEmpty",
+ config: Config{
+ MinVersion: VersionTLS13,
+ AvailableTrustAnchors: [][]byte{id1, id2},
+ Bugs: ProtocolBugs{
+ ExpectPeerRequestedTrustAnchors: [][]byte{},
+ },
+ },
+ flags: []string{
+ "-requested-trust-anchors", trustAnchorListFlagValue(),
+ "-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
+ },
+ })
+ // The server must be able to process it, and send available trust anchors.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrustAnchors-ServerReceiveEmptyRequest",
+ config: Config{
+ MinVersion: VersionTLS13,
+ RequestTrustAnchors: [][]byte{},
+ Bugs: ProtocolBugs{
+ ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
+ ExpectPeerMatchTrustAnchor: ptrTo(false),
+ },
+ },
+ shimCredentials: []*Credential{
+ rsaCertificate.WithTrustAnchorID(id1),
+ rsaCertificate.WithTrustAnchorID(id2),
+ &rsaCertificate,
+ },
+ flags: []string{"-expect-selected-credential", "2"},
+ })
+
+ // This extension requires TLS 1.3. If a server receives this and negotiates
+ // TLS 1.2, it should ignore the extension and not accidentally send
+ // something in ServerHello (implicitly checked by runner).
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TrustAnchors-TLS12-Server",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ RequestTrustAnchors: [][]byte{id1},
+ },
+ shimCredentials: []*Credential{
+ rsaCertificate.WithTrustAnchorID(id1),
+ &rsaCertificate,
+ },
+ // The first credential is skipped because the extension is ignored.
+ flags: []string{"-expect-selected-credential", "1"},
+ })
+ // The client should reject the extension in TLS 1.2 ServerHello.
+ testCases = append(testCases, testCase{
+ name: "TrustAnchors-TLS12-Client",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ AvailableTrustAnchors: [][]byte{id1},
+ Bugs: ProtocolBugs{
+ AlwaysSendAvailableTrustAnchors: true,
+ },
+ },
+ flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ expectedLocalError: "remote error: unsupported extension",
+ })
+}
diff --git a/ssl/test/runner/version_tests.go b/ssl/test/runner/version_tests.go
new file mode 100644
index 0000000..95d0e78
--- /dev/null
+++ b/ssl/test/runner/version_tests.go
@@ -0,0 +1,611 @@
+// 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
+
+func addVersionNegotiationTests() {
+ for _, protocol := range []protocol{tls, dtls, quic} {
+ for _, shimVers := range allVersions(protocol) {
+ // Assemble flags to disable all newer versions on the shim.
+ var flags []string
+ for _, vers := range allVersions(protocol) {
+ if vers.version > shimVers.version {
+ flags = append(flags, vers.excludeFlag)
+ }
+ }
+
+ flags2 := []string{"-max-version", shimVers.shimFlag(protocol)}
+
+ // Test configuring the runner's maximum version.
+ for _, runnerVers := range allVersions(protocol) {
+ expectedVersion := shimVers.version
+ if runnerVers.version < shimVers.version {
+ expectedVersion = runnerVers.version
+ }
+
+ suffix := shimVers.name + "-" + runnerVers.name
+ suffix += "-" + protocol.String()
+
+ // Determine the expected initial record-layer versions.
+ clientVers := shimVers.version
+ if clientVers > VersionTLS10 {
+ clientVers = VersionTLS10
+ }
+ clientVers = recordVersionToWire(clientVers, protocol)
+ serverVers := expectedVersion
+ if expectedVersion >= VersionTLS13 {
+ serverVers = VersionTLS12
+ }
+ serverVers = recordVersionToWire(serverVers, protocol)
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "VersionNegotiation-Client-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ Bugs: ProtocolBugs{
+ ExpectInitialRecordVersion: clientVers,
+ },
+ },
+ flags: flags,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ // The version name check does not recognize the
+ // |excludeFlag| construction in |flags|.
+ skipVersionNameCheck: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "VersionNegotiation-Client2-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ Bugs: ProtocolBugs{
+ ExpectInitialRecordVersion: clientVers,
+ },
+ },
+ flags: flags2,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "VersionNegotiation-Server-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ Bugs: ProtocolBugs{
+ ExpectInitialRecordVersion: serverVers,
+ },
+ },
+ flags: flags,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ // The version name check does not recognize the
+ // |excludeFlag| construction in |flags|.
+ skipVersionNameCheck: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "VersionNegotiation-Server2-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ Bugs: ProtocolBugs{
+ ExpectInitialRecordVersion: serverVers,
+ },
+ },
+ flags: flags2,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ })
+ }
+ }
+ }
+
+ // Test the version extension at all versions.
+ for _, protocol := range []protocol{tls, dtls, quic} {
+ for _, vers := range allVersions(protocol) {
+ suffix := vers.name + "-" + protocol.String()
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "VersionNegotiationExtension-" + suffix,
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSupportedVersions: []uint16{0x1111, vers.wire(protocol), 0x2222},
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ expectations: connectionExpectations{
+ version: vers.version,
+ },
+ })
+ }
+ }
+
+ // If all versions are unknown, negotiation fails.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoSupportedVersions",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSupportedVersions: []uint16{0x1111},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "NoSupportedVersions-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSupportedVersions: []uint16{0x1111},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ClientHelloVersionTooHigh",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0x0304,
+ OmitSupportedVersions: true,
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ConflictingVersionNegotiation",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: VersionTLS12,
+ SendSupportedVersions: []uint16{VersionTLS11},
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ // The extension takes precedence over the ClientHello version.
+ expectations: connectionExpectations{
+ version: VersionTLS11,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "ConflictingVersionNegotiation-2",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: VersionTLS11,
+ SendSupportedVersions: []uint16{VersionTLS12},
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ // The extension takes precedence over the ClientHello version.
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+
+ // Test that TLS 1.2 isn't negotiated by the supported_versions extension in
+ // the ServerHello.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "SupportedVersionSelection-TLS12",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ SendServerSupportedVersionExtension: VersionTLS12,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // Test that the maximum version is selected regardless of the
+ // client-sent order.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "IgnoreClientVersionOrder",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSupportedVersions: []uint16{VersionTLS12, VersionTLS13},
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS13,
+ },
+ })
+
+ // Test for version tolerance.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "MinorVersionTolerance",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0x03ff,
+ OmitSupportedVersions: true,
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "MajorVersionTolerance",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0x0400,
+ OmitSupportedVersions: true,
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ // TLS 1.3 must be negotiated with the supported_versions
+ // extension, not ClientHello.version.
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "VersionTolerance-TLS13",
+ config: Config{
+ Bugs: ProtocolBugs{
+ // Although TLS 1.3 does not use
+ // ClientHello.version, it still tolerates high
+ // values there.
+ SendClientVersion: 0x0400,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS13,
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "MinorVersionTolerance-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0xfe00,
+ OmitSupportedVersions: true,
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "MajorVersionTolerance-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0xfdff,
+ OmitSupportedVersions: true,
+ IgnoreTLS13DowngradeRandom: true,
+ },
+ },
+ expectations: connectionExpectations{
+ version: VersionTLS12,
+ },
+ })
+
+ // Test that versions below 3.0 are rejected.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "VersionTooLow",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0x0200,
+ OmitSupportedVersions: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ testType: serverTest,
+ name: "VersionTooLow-DTLS",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendClientVersion: 0xffff,
+ OmitSupportedVersions: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "ServerBogusVersion",
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendServerHelloVersion: 0x1234,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+
+ // Test TLS 1.3's downgrade signal.
+ for _, protocol := range []protocol{tls, dtls} {
+ for _, vers := range allVersions(protocol) {
+ if vers.version >= VersionTLS13 {
+ continue
+ }
+ clientShimError := "tls: downgrade from TLS 1.3 detected"
+ if vers.version < VersionTLS12 {
+ clientShimError = "tls: downgrade from TLS 1.2 detected"
+ }
+ // for _, test := range downgradeTests {
+ // The client should enforce the downgrade sentinel.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ name: "Downgrade-" + vers.name + "-Client-" + protocol.String(),
+ config: Config{
+ Bugs: ProtocolBugs{
+ NegotiateVersion: vers.wire(protocol),
+ },
+ },
+ expectations: connectionExpectations{
+ version: vers.version,
+ },
+ shouldFail: true,
+ expectedError: ":TLS13_DOWNGRADE:",
+ expectedLocalError: "remote error: illegal parameter",
+ })
+
+ // The server should emit the downgrade signal.
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "Downgrade-" + vers.name + "-Server-" + protocol.String(),
+ config: Config{
+ Bugs: ProtocolBugs{
+ SendSupportedVersions: []uint16{vers.wire(protocol)},
+ },
+ },
+ expectations: connectionExpectations{
+ version: vers.version,
+ },
+ shouldFail: true,
+ expectedLocalError: clientShimError,
+ })
+ }
+ }
+
+ // SSL 3.0 support has been removed. Test that the shim does not
+ // support it.
+ testCases = append(testCases, testCase{
+ name: "NoSSL3-Client",
+ config: Config{
+ MinVersion: VersionSSL30,
+ MaxVersion: VersionSSL30,
+ },
+ shouldFail: true,
+ expectedLocalError: "tls: client did not offer any supported protocol versions",
+ })
+ testCases = append(testCases, testCase{
+ name: "NoSSL3-Client-Unsolicited",
+ config: Config{
+ MinVersion: VersionSSL30,
+ MaxVersion: VersionSSL30,
+ Bugs: ProtocolBugs{
+ // The above test asserts the client does not
+ // offer SSL 3.0 in the supported_versions
+ // list. Additionally assert that it rejects an
+ // unsolicited SSL 3.0 ServerHello.
+ NegotiateVersion: VersionSSL30,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ expectedLocalError: "remote error: protocol version not supported",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "NoSSL3-Server",
+ config: Config{
+ MinVersion: VersionSSL30,
+ MaxVersion: VersionSSL30,
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ expectedLocalError: "remote error: protocol version not supported",
+ })
+}
+
+func addMinimumVersionTests() {
+ for _, protocol := range []protocol{tls, dtls, quic} {
+ for _, shimVers := range allVersions(protocol) {
+ // Assemble flags to disable all older versions on the shim.
+ var flags []string
+ for _, vers := range allVersions(protocol) {
+ if vers.version < shimVers.version {
+ flags = append(flags, vers.excludeFlag)
+ }
+ }
+
+ flags2 := []string{"-min-version", shimVers.shimFlag(protocol)}
+
+ for _, runnerVers := range allVersions(protocol) {
+ suffix := shimVers.name + "-" + runnerVers.name
+ suffix += "-" + protocol.String()
+
+ var expectedVersion uint16
+ var shouldFail bool
+ var expectedError, expectedLocalError string
+ if runnerVers.version >= shimVers.version {
+ expectedVersion = runnerVers.version
+ } else {
+ shouldFail = true
+ expectedError = ":UNSUPPORTED_PROTOCOL:"
+ expectedLocalError = "remote error: protocol version not supported"
+ }
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "MinimumVersion-Client-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ Bugs: ProtocolBugs{
+ // Ensure the server does not decline to
+ // select a version (versions extension) or
+ // cipher (some ciphers depend on versions).
+ NegotiateVersion: runnerVers.wire(protocol),
+ IgnorePeerCipherPreferences: shouldFail,
+ },
+ },
+ flags: flags,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ expectedLocalError: expectedLocalError,
+ // The version name check does not recognize the
+ // |excludeFlag| construction in |flags|.
+ skipVersionNameCheck: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: clientTest,
+ name: "MinimumVersion-Client2-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ Bugs: ProtocolBugs{
+ // Ensure the server does not decline to
+ // select a version (versions extension) or
+ // cipher (some ciphers depend on versions).
+ NegotiateVersion: runnerVers.wire(protocol),
+ IgnorePeerCipherPreferences: shouldFail,
+ },
+ },
+ flags: flags2,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ expectedLocalError: expectedLocalError,
+ })
+
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "MinimumVersion-Server-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ },
+ flags: flags,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ expectedLocalError: expectedLocalError,
+ // The version name check does not recognize the
+ // |excludeFlag| construction in |flags|.
+ skipVersionNameCheck: true,
+ })
+ testCases = append(testCases, testCase{
+ protocol: protocol,
+ testType: serverTest,
+ name: "MinimumVersion-Server2-" + suffix,
+ config: Config{
+ MaxVersion: runnerVers.version,
+ },
+ flags: flags2,
+ expectations: connectionExpectations{
+ version: expectedVersion,
+ },
+ shouldFail: shouldFail,
+ expectedError: expectedError,
+ expectedLocalError: expectedLocalError,
+ })
+ }
+ }
+ }
+}
+
+func addRecordVersionTests() {
+ for _, ver := range tlsVersions {
+ // Test that the record version is enforced.
+ testCases = append(testCases, testCase{
+ name: "CheckRecordVersion-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendRecordVersion: 0x03ff,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_NUMBER:",
+ })
+
+ // Test that the ClientHello may use any record version, for
+ // compatibility reasons.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "LooseInitialRecordVersion-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendInitialRecordVersion: 0x03ff,
+ },
+ },
+ })
+
+ // Test that garbage ClientHello record versions are rejected.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "GarbageInitialRecordVersion-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ Bugs: ProtocolBugs{
+ SendInitialRecordVersion: 0xffff,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_NUMBER:",
+ })
+ }
+}