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:", + }) + } +}