Enable more runner tests for QUIC Change-Id: Id1922197c5218460210e6404ad60b60afc591984 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40284 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index c9845f3..4e2fa50 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -534,7 +534,7 @@ } } - if (!config->expect_quic_transport_params.empty()) { + if (!config->expect_quic_transport_params.empty() && expect_handshake_done) { const uint8_t *peer_params; size_t peer_params_len; SSL_get_peer_quic_transport_params(ssl, &peer_params, &peer_params_len);
diff --git a/ssl/test/mock_quic_transport.cc b/ssl/test/mock_quic_transport.cc index 23445cb..0929432 100644 --- a/ssl/test/mock_quic_transport.cc +++ b/ssl/test/mock_quic_transport.cc
@@ -23,23 +23,14 @@ const uint8_t kTagHandshake = 'H'; const uint8_t kTagApplication = 'A'; - -bool write_header(BIO *bio, uint8_t tag, size_t len) { - uint8_t header[5]; - header[0] = tag; - header[1] = (len >> 24) & 0xff; - header[2] = (len >> 16) & 0xff; - header[3] = (len >> 8) & 0xff; - header[4] = len & 0xff; - return BIO_write_all(bio, header, sizeof(header)); -} +const uint8_t kTagAlert = 'L'; } // namespace MockQuicTransport::MockQuicTransport(bssl::UniquePtr<BIO> bio, SSL *ssl) : bio_(std::move(bio)), - read_secrets_(ssl_encryption_application + 1), - write_secrets_(ssl_encryption_application + 1), + read_levels_(ssl_encryption_application + 1), + write_levels_(ssl_encryption_application + 1), ssl_(ssl) {} bool MockQuicTransport::SetReadSecret(enum ssl_encryption_level_t level, @@ -47,7 +38,8 @@ const uint8_t *secret, size_t secret_len) { // TODO(davidben): Assert the various encryption secret invariants. - read_secrets_[level].assign(secret, secret + secret_len); + read_levels_[level].cipher = SSL_CIPHER_get_value(cipher); + read_levels_[level].secret.assign(secret, secret + secret_len); return true; } @@ -56,7 +48,8 @@ const uint8_t *secret, size_t secret_len) { // TODO(davidben): Assert the various encryption secret invariants. - write_secrets_[level].assign(secret, secret + secret_len); + write_levels_[level].cipher = SSL_CIPHER_get_value(cipher); + write_levels_[level].secret.assign(secret, secret + secret_len); return true; } @@ -80,39 +73,53 @@ return true; } -bool ReadHeader(BIO *bio, uint8_t *out_tag, size_t *out_len) { - uint8_t header[5]; - if (!ReadAll(bio, header)) { +} // namespace + +bool MockQuicTransport::ReadHeader(uint8_t *out_tag, size_t *out_len) { + uint8_t header[7]; + if (!ReadAll(bio_.get(), header)) { return false; } - - *out_len = header[1] << 24 | header[2] << 16 | header[3] << 8 | header[4]; *out_tag = header[0]; + uint16_t cipher_suite = header[1] << 8 | header[2]; + size_t remaining_bytes = + header[3] << 24 | header[4] << 16 | header[5] << 8 | header[6]; + + enum ssl_encryption_level_t level = SSL_quic_read_level(ssl_); + if (*out_tag == kTagApplication) { + if (SSL_in_early_data(ssl_)) { + level = ssl_encryption_early_data; + } else { + level = ssl_encryption_application; + } + } + if (cipher_suite != read_levels_[level].cipher) { + return false; + } + const std::vector<uint8_t> &secret = read_levels_[level].secret; + std::vector<uint8_t> read_secret(secret.size()); + if (remaining_bytes < secret.size()) { + return false; + } + remaining_bytes -= secret.size(); + if (!ReadAll(bio_.get(), bssl::MakeSpan(read_secret)) || + read_secret != secret) { + return false; + } + *out_len = remaining_bytes; return true; } -} // namespace - bool MockQuicTransport::ReadHandshake() { - enum ssl_encryption_level_t level = SSL_quic_read_level(ssl_); uint8_t tag; size_t len; - if (!ReadHeader(bio_.get(), &tag, &len)) { + if (!ReadHeader(&tag, &len)) { return false; } if (tag != kTagHandshake) { return false; } - const std::vector<uint8_t> &secret = read_secrets_[level]; - std::vector<uint8_t> read_secret(secret.size()); - if (!ReadAll(bio_.get(), bssl::MakeSpan(read_secret))) { - return false; - } - if (read_secret != secret) { - return false; - } - std::vector<uint8_t> buf(len); if (!ReadAll(bio_.get(), bssl::MakeSpan(buf))) { return false; @@ -139,34 +146,36 @@ uint8_t tag = 0; size_t len; while (true) { - if (!ReadHeader(bio_.get(), &tag, &len)) { + if (!ReadHeader(&tag, &len)) { // Assume that a failure to read the header means there's no more to read, // not an error reading. return 0; } - if (tag != kTagHandshake && tag != kTagApplication) { - return -1; - } - const std::vector<uint8_t> &secret = - read_secrets_[ssl_encryption_application]; - std::vector<uint8_t> read_secret(secret.size()); - if (!ReadAll(bio_.get(), bssl::MakeSpan(read_secret))) { - return -1; - } - if (read_secret != secret) { - return -1; - } if (tag == kTagApplication) { break; } + if (tag != kTagHandshake) { + return -1; + } std::vector<uint8_t> buf(len); if (!ReadAll(bio_.get(), bssl::MakeSpan(buf))) { return -1; } if (SSL_provide_quic_data(ssl_, SSL_quic_read_level(ssl_), buf.data(), - buf.size()) != 1 || - SSL_process_quic_post_handshake(ssl_) != 1) { + buf.size()) != 1) { + return -1; + } + if (SSL_in_init(ssl_)) { + int ret = SSL_do_handshake(ssl_); + if (ret < 0) { + int ssl_err = SSL_get_error(ssl_, ret); + if (ssl_err == SSL_ERROR_WANT_READ) { + continue; + } + return -1; + } + } else if (SSL_process_quic_post_handshake(ssl_) != 1) { return -1; } } @@ -188,26 +197,42 @@ return len; } +bool MockQuicTransport::WriteRecord(enum ssl_encryption_level_t level, + uint8_t tag, const uint8_t *data, + size_t len) { + uint16_t cipher_suite = write_levels_[level].cipher; + const std::vector<uint8_t> &secret = write_levels_[level].secret; + size_t tlv_len = secret.size() + len; + uint8_t header[7]; + header[0] = tag; + header[1] = (cipher_suite >> 8) & 0xff; + header[2] = cipher_suite & 0xff; + header[3] = (tlv_len >> 24) & 0xff; + header[4] = (tlv_len >> 16) & 0xff; + header[5] = (tlv_len >> 8) & 0xff; + header[6] = tlv_len & 0xff; + return BIO_write_all(bio_.get(), header, sizeof(header)) && + BIO_write_all(bio_.get(), secret.data(), secret.size()) && + BIO_write_all(bio_.get(), data, len); +} + bool MockQuicTransport::WriteHandshakeData(enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - const std::vector<uint8_t> &secret = write_secrets_[level]; - if (!write_header(bio_.get(), kTagHandshake, len) || - BIO_write_all(bio_.get(), secret.data(), secret.size()) != 1 || - BIO_write_all(bio_.get(), data, len) != 1) { - return false; - } - return true; + return WriteRecord(level, kTagHandshake, data, len); } bool MockQuicTransport::WriteApplicationData(const uint8_t *in, size_t len) { - const std::vector<uint8_t> &secret = - write_secrets_[ssl_encryption_application]; - if (!write_header(bio_.get(), kTagApplication, len) || - BIO_write_all(bio_.get(), secret.data(), secret.size()) != 1 || - BIO_write_all(bio_.get(), in, len) != 1) { - return false; + enum ssl_encryption_level_t level = ssl_encryption_application; + if (SSL_in_early_data(ssl_) && !SSL_is_server(ssl_)) { + level = ssl_encryption_early_data; } - return true; + return WriteRecord(level, kTagApplication, in, len); } bool MockQuicTransport::Flush() { return BIO_flush(bio_.get()); } + +bool MockQuicTransport::SendAlert(enum ssl_encryption_level_t level, + uint8_t alert) { + uint8_t alert_msg[] = {2, alert}; + return WriteRecord(level, kTagAlert, alert_msg, sizeof(alert_msg)); +}
diff --git a/ssl/test/mock_quic_transport.h b/ssl/test/mock_quic_transport.h index 6dfed5b..a56652d 100644 --- a/ssl/test/mock_quic_transport.h +++ b/ssl/test/mock_quic_transport.h
@@ -39,15 +39,35 @@ int ReadApplicationData(uint8_t *out, size_t max_out); bool WriteApplicationData(const uint8_t *in, size_t len); bool Flush(); + bool SendAlert(enum ssl_encryption_level_t level, uint8_t alert); private: + // Reads a record header from |bio_| and returns whether the record was read + // successfully. As part of reading the header, this function checks that the + // cipher suite and secret in the header are correct. On success, the tag + // indicating the TLS record type is put in |*out_tag|, the length of the TLS + // record is put in |*out_len|, and the next thing to be read from |bio_| is + // |*out_len| bytes of the TLS record. + bool ReadHeader(uint8_t *out_tag, size_t *out_len); + + // Writes a MockQuicTransport record to |bio_| at encryption level |level| + // with record type |tag| and a TLS record payload of length |len| from + // |data|. + bool WriteRecord(enum ssl_encryption_level_t level, uint8_t tag, + const uint8_t *data, size_t len); + bssl::UniquePtr<BIO> bio_; std::vector<uint8_t> pending_app_data_; size_t app_data_offset_; - std::vector<std::vector<uint8_t>> read_secrets_; - std::vector<std::vector<uint8_t>> write_secrets_; + struct EncryptionLevel { + uint16_t cipher; + std::vector<uint8_t> secret; + }; + + std::vector<EncryptionLevel> read_levels_; + std::vector<EncryptionLevel> write_levels_; SSL *ssl_; // Unowned. };
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index 0142ff6..a449f2f 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go
@@ -761,6 +761,7 @@ } if c.config.Bugs.MockQUICTransport != nil { c.config.Bugs.MockQUICTransport.readSecret = secret + c.config.Bugs.MockQUICTransport.readCipherSuite = suite.id } c.in.useTrafficSecret(version, suite, secret, side) c.seenHandshakePackEnd = false @@ -774,10 +775,26 @@ } if c.config.Bugs.MockQUICTransport != nil { c.config.Bugs.MockQUICTransport.writeSecret = secret + c.config.Bugs.MockQUICTransport.writeCipherSuite = suite.id } c.out.useTrafficSecret(version, suite, secret, side) } +func (c *Conn) setSkipEarlyData() { + if c.config.Bugs.MockQUICTransport != nil { + c.config.Bugs.MockQUICTransport.skipEarlyData = true + } else { + c.skipEarlyData = true + } +} + +func (c *Conn) shouldSkipEarlyData() bool { + if c.config.Bugs.MockQUICTransport != nil { + return c.config.Bugs.MockQUICTransport.skipEarlyData + } + return c.skipEarlyData +} + func (c *Conn) doReadRecord(want recordType) (recordType, *block, error) { RestartReadRecord: if c.isDTLS { @@ -904,6 +921,9 @@ } func (c *Conn) readTLS13ChangeCipherSpec() error { + if c.config.Bugs.MockQUICTransport != nil { + return nil + } if !c.expectTLS13ChangeCipherSpec { panic("c.expectTLS13ChangeCipherSpec not set") } @@ -964,7 +984,7 @@ break } - if c.expectTLS13ChangeCipherSpec && c.config.Bugs.MockQUICTransport == nil { + if c.expectTLS13ChangeCipherSpec { if err := c.readTLS13ChangeCipherSpec(); err != nil { return err } @@ -1986,6 +2006,9 @@ ticketNonce: nonce, maxEarlyDataSize: c.config.MaxEarlyDataSize, } + if c.config.Bugs.MockQUICTransport != nil && m.maxEarlyDataSize > 0 { + m.maxEarlyDataSize = 0xffffffff + } if c.config.Bugs.SendTicketLifetime != 0 { m.ticketLifetime = uint32(c.config.Bugs.SendTicketLifetime / time.Second)
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 28523f0..3d13b9b 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -621,7 +621,7 @@ } if hs.clientHello.hasEarlyData { - c.skipEarlyData = true + c.setSkipEarlyData() } // Read new ClientHello. @@ -737,7 +737,7 @@ c.input = nil } } else { - c.skipEarlyData = true + c.setSkipEarlyData() } } @@ -988,7 +988,7 @@ } c.flushHandshake() - if encryptedExtensions.extensions.hasEarlyData && !c.skipEarlyData { + if encryptedExtensions.extensions.hasEarlyData && !c.shouldSkipEarlyData() { for _, expectedMsg := range config.Bugs.ExpectLateEarlyData { if err := c.readRecord(recordTypeApplicationData); err != nil { return err @@ -1022,7 +1022,7 @@ } // Read end_of_early_data. - if encryptedExtensions.extensions.hasEarlyData { + if encryptedExtensions.extensions.hasEarlyData && config.Bugs.MockQUICTransport == nil { msg, err := c.readHandshake() if err != nil { return err
diff --git a/ssl/test/runner/mock_quic_transport.go b/ssl/test/runner/mock_quic_transport.go index 4d971c4..27a8bc3 100644 --- a/ssl/test/runner/mock_quic_transport.go +++ b/ssl/test/runner/mock_quic_transport.go
@@ -24,10 +24,28 @@ const tagHandshake = byte('H') const tagApplication = byte('A') +const tagAlert = byte('L') +// mockQUICTransport provides a record layer for sending/receiving messages +// when testing TLS over QUIC. It is only intended for testing, as it runs over +// an in-order reliable transport, looks nothing like the QUIC wire image, and +// provides no confidentiality guarantees. (In fact, it leaks keys in the +// clear.) +// +// Messages from TLS that are sent over a mockQUICTransport are wrapped in a +// TLV-like format. The first byte of a mockQUICTransport message is a tag +// indicating the TLS record type. This is followed by the 2 byte cipher suite +// ID of the cipher suite that would have been used to encrypt the record. Next +// is a 4-byte big-endian length indicating the length of the remaining payload. +// The payload starts with the key that would be used to encrypt the record, and +// the remainder of the payload is the plaintext of the TLS record. Note that +// the 4-byte length covers the length of the key and plaintext, but not the +// cipher suite ID or tag. type mockQUICTransport struct { net.Conn - readSecret, writeSecret []byte + readSecret, writeSecret []byte + readCipherSuite, writeCipherSuite uint16 + skipEarlyData bool } func newMockQUICTransport(conn net.Conn) *mockQUICTransport { @@ -35,24 +53,39 @@ } func (m *mockQUICTransport) read() (byte, []byte, error) { - header := make([]byte, 5) - if _, err := io.ReadFull(m.Conn, header); err != nil { - return 0, nil, err + for { + header := make([]byte, 7) + if _, err := io.ReadFull(m.Conn, header); err != nil { + return 0, nil, err + } + cipherSuite := binary.BigEndian.Uint16(header[1:3]) + length := binary.BigEndian.Uint32(header[3:]) + value := make([]byte, length) + if _, err := io.ReadFull(m.Conn, value); err != nil { + return 0, nil, fmt.Errorf("Error reading record") + } + if cipherSuite != m.readCipherSuite { + if m.skipEarlyData { + continue + } + return 0, nil, fmt.Errorf("Received cipher suite %d does not match expected %d", cipherSuite, m.readCipherSuite) + } + if len(m.readSecret) > len(value) { + return 0, nil, fmt.Errorf("Input length too short") + } + secret := value[:len(m.readSecret)] + out := value[len(m.readSecret):] + if !bytes.Equal(secret, m.readSecret) { + if m.skipEarlyData { + continue + } + return 0, nil, fmt.Errorf("secrets don't match: got %x but expected %x", secret, m.readSecret) + } + if m.skipEarlyData && header[0] == tagHandshake { + m.skipEarlyData = false + } + return header[0], out, nil } - var length uint32 - binary.Read(bytes.NewBuffer(header[1:]), binary.BigEndian, &length) - secret := make([]byte, len(m.readSecret)) - if _, err := io.ReadFull(m.Conn, secret); err != nil { - return 0, nil, err - } - if !bytes.Equal(secret, m.readSecret) { - return 0, nil, fmt.Errorf("secrets don't match") - } - out := make([]byte, int(length)) - if _, err := io.ReadFull(m.Conn, out); err != nil { - return 0, nil, err - } - return header[0], out, nil } func (m *mockQUICTransport) readRecord(want recordType) (recordType, *block, error) { @@ -65,6 +98,8 @@ returnType = recordTypeHandshake } else if typ == tagApplication { returnType = recordTypeApplicationData + } else if typ == tagAlert { + returnType = recordTypeAlert } else { return 0, nil, fmt.Errorf("unknown type %d\n", typ) } @@ -78,11 +113,13 @@ } else if typ != recordTypeHandshake { return 0, fmt.Errorf("unsupported record type %d\n", typ) } - payload := make([]byte, 1+4+len(m.writeSecret)+len(data)) + length := len(m.writeSecret) + len(data) + payload := make([]byte, 1+2+4+length) payload[0] = tag - binary.BigEndian.PutUint32(payload[1:5], uint32(len(data))) - copy(payload[5:], m.writeSecret) - copy(payload[5+len(m.writeSecret):], data) + binary.BigEndian.PutUint16(payload[1:3], m.writeCipherSuite) + binary.BigEndian.PutUint32(payload[3:7], uint32(length)) + copy(payload[7:], m.writeSecret) + copy(payload[7+len(m.writeSecret):], data) if _, err := m.Conn.Write(payload); err != nil { return 0, err }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 6a00a64..eda922d 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -3496,7 +3496,9 @@ expectedError = ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" } - // TODO(nharper): Consider enabling this test for QUIC. + // 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, @@ -4435,18 +4437,24 @@ // various conditions. Some of these are redundant with other tests, but they // only cover the synchronous case. func addAllStateMachineCoverageTests() { - // TODO(nharper): Add QUIC support for these tests? for _, async := range []bool{false, true} { - for _, protocol := range []protocol{tls, dtls} { + for _, protocol := range []protocol{tls, dtls, quic} { + if protocol == quic && async == true { + // QUIC doesn't work with async mode. + continue + } addStateMachineCoverageTests(stateMachineTestConfig{ protocol: protocol, async: async, }) - addStateMachineCoverageTests(stateMachineTestConfig{ - protocol: protocol, - async: async, - implicitHandshake: true, - }) + // QUIC doesn't work with implicit handshakes. + if protocol != quic { + addStateMachineCoverageTests(stateMachineTestConfig{ + protocol: protocol, + async: async, + implicitHandshake: true, + }) + } addStateMachineCoverageTests(stateMachineTestConfig{ protocol: protocol, async: async, @@ -4466,73 +4474,77 @@ // Basic handshake, with resumption. Client and server, // session ID and session ticket. - 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, + // 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, }, - }, - 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, + // 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, + }, }, - }, - 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, - }) + 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. - if config.protocol == tls { + // TLS 1.3 basic handshake shapes. DTLS 1.3 isn't supported yet. + if config.protocol != dtls { tests = append(tests, testCase{ name: "TLS13-1RTT-Client", config: Config{ @@ -4594,31 +4606,34 @@ flags: []string{"-expect-hrr"}, }) - 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{{'h', 'e'}}, + // Tests that specify a MaxEarlyDataSize don't work with QUIC. + if config.protocol != quic { + tests = append(tests, testCase{ + testType: clientTest, + name: "TLS13-EarlyData-TooMuchData-Client", + config: Config{ + MaxVersion: VersionTLS13, + MinVersion: VersionTLS13, + MaxEarlyDataSize: 2, }, - }, - resumeShimPrefix: "llo", - resumeSession: true, - flags: []string{ - "-enable-early-data", - "-expect-ticket-supports-early-data", - "-on-resume-expect-accept-early-data", - "-on-resume-shim-writes-first", - }, - }) + resumeConfig: &Config{ + MaxVersion: VersionTLS13, + MinVersion: VersionTLS13, + MaxEarlyDataSize: 2, + Bugs: ProtocolBugs{ + ExpectEarlyData: [][]byte{{'h', 'e'}}, + }, + }, + resumeShimPrefix: "llo", + resumeSession: true, + flags: []string{ + "-enable-early-data", + "-expect-ticket-supports-early-data", + "-on-resume-expect-accept-early-data", + "-on-resume-shim-writes-first", + }, + }) + } // Unfinished writes can only be tested when operations are async. EarlyData // can't be tested as part of an ImplicitHandshake in this case since @@ -4680,47 +4695,54 @@ }) } - 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, + // Early data has no size limit in QUIC. + if config.protocol != 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, - flags: []string{ - "-enable-early-data", - "-on-resume-expect-accept-early-data", - }, - shouldFail: true, - expectedError: ":TOO_MUCH_READ_EARLY_DATA:", - }) + messageCount: 2, + resumeSession: true, + flags: []string{ + "-enable-early-data", + "-on-resume-expect-accept-early-data", + }, + shouldFail: true, + expectedError: ":TOO_MUCH_READ_EARLY_DATA:", + }) + } } // TLS client auth. - 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 == tls { + // 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", @@ -4739,18 +4761,20 @@ flags: []string{"-verify-peer"}, }) } - tests = append(tests, testCase{ - testType: clientTest, - name: "ClientAuth-RSA-Client", - config: Config{ - MaxVersion: VersionTLS12, - ClientAuth: RequireAnyClientCert, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, - }) + if config.protocol != quic { + tests = append(tests, testCase{ + testType: clientTest, + name: "ClientAuth-RSA-Client", + config: Config{ + MaxVersion: VersionTLS12, + ClientAuth: RequireAnyClientCert, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, rsaCertificateFile), + "-key-file", path.Join(*resourceDir, rsaKeyFile), + }, + }) + } tests = append(tests, testCase{ testType: clientTest, name: "ClientAuth-RSA-Client-TLS13", @@ -4763,18 +4787,20 @@ "-key-file", path.Join(*resourceDir, rsaKeyFile), }, }) - tests = append(tests, testCase{ - testType: clientTest, - name: "ClientAuth-ECDSA-Client", - config: Config{ - MaxVersion: VersionTLS12, - ClientAuth: RequireAnyClientCert, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, - }) + if config.protocol != quic { + tests = append(tests, testCase{ + testType: clientTest, + name: "ClientAuth-ECDSA-Client", + config: Config{ + MaxVersion: VersionTLS12, + ClientAuth: RequireAnyClientCert, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), + "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), + }, + }) + } tests = append(tests, testCase{ testType: clientTest, name: "ClientAuth-ECDSA-Client-TLS13", @@ -4787,15 +4813,17 @@ "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), }, }) - tests = append(tests, testCase{ - testType: clientTest, - name: "ClientAuth-NoCertificate-OldCallback", - config: Config{ - MaxVersion: VersionTLS12, - ClientAuth: RequestClientCert, - }, - flags: []string{"-use-old-client-cert-callback"}, - }) + 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", @@ -4805,19 +4833,21 @@ }, flags: []string{"-use-old-client-cert-callback"}, }) - tests = append(tests, testCase{ - testType: clientTest, - name: "ClientAuth-OldCallback", - config: Config{ - MaxVersion: VersionTLS12, - ClientAuth: RequireAnyClientCert, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - "-use-old-client-cert-callback", - }, - }) + if config.protocol != quic { + tests = append(tests, testCase{ + testType: clientTest, + name: "ClientAuth-OldCallback", + config: Config{ + MaxVersion: VersionTLS12, + ClientAuth: RequireAnyClientCert, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, rsaCertificateFile), + "-key-file", path.Join(*resourceDir, rsaKeyFile), + "-use-old-client-cert-callback", + }, + }) + } tests = append(tests, testCase{ testType: clientTest, name: "ClientAuth-OldCallback-TLS13", @@ -4831,15 +4861,17 @@ "-use-old-client-cert-callback", }, }) - tests = append(tests, testCase{ - testType: serverTest, - name: "ClientAuth-Server", - config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, - }, - flags: []string{"-require-any-client-certificate"}, - }) + if config.protocol != quic { + tests = append(tests, testCase{ + testType: serverTest, + name: "ClientAuth-Server", + config: Config{ + MaxVersion: VersionTLS12, + Certificates: []Certificate{rsaCertificate}, + }, + flags: []string{"-require-any-client-certificate"}, + }) + } tests = append(tests, testCase{ testType: serverTest, name: "ClientAuth-Server-TLS13", @@ -4851,94 +4883,96 @@ }) // Test each key exchange on the server side for async keys. - tests = append(tests, testCase{ - testType: serverTest, - name: "Basic-Server-RSA", - config: Config{ - MaxVersion: VersionTLS12, - CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, - }) - 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}, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, - }) - 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}, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, - }) - tests = append(tests, testCase{ - testType: serverTest, - name: "Basic-Server-Ed25519", - config: Config{ - MaxVersion: VersionTLS12, - CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ed25519CertificateFile), - "-key-file", path.Join(*resourceDir, ed25519KeyFile), - "-verify-prefs", strconv.Itoa(int(signatureEd25519)), - }, - }) + 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}, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, rsaCertificateFile), + "-key-file", path.Join(*resourceDir, rsaKeyFile), + }, + }) + 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}, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, rsaCertificateFile), + "-key-file", path.Join(*resourceDir, rsaKeyFile), + }, + }) + 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}, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), + "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), + }, + }) + tests = append(tests, testCase{ + testType: serverTest, + name: "Basic-Server-Ed25519", + config: Config{ + MaxVersion: VersionTLS12, + CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + }, + flags: []string{ + "-cert-file", path.Join(*resourceDir, ed25519CertificateFile), + "-key-file", path.Join(*resourceDir, ed25519KeyFile), + "-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, - }, - }) + // 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"}, - }) + // 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 tlsVersions { @@ -5530,8 +5564,58 @@ }, }) + // 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", path.Join(*resourceDir, channelIDKeyFile), + "-select-next-proto", "foo", + }, + resumeSession: true, + expectChannelID: true, + expectedNextProto: "foo", + expectedNextProtoType: 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", + base64.StdEncoding.EncodeToString(channelIDBytes), + "-advertise-npn", "\x03foo\x03bar\x03baz", + "-expect-next-proto", "bar", + }, + resumeSession: true, + expectChannelID: true, + expectedNextProto: "bar", + expectedNextProtoType: 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 tlsVersions { + for _, ver := range allVersions(config.protocol) { if ver.version < VersionTLS10 { continue } @@ -5609,180 +5693,137 @@ } } - // 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", path.Join(*resourceDir, channelIDKeyFile), - "-select-next-proto", "foo", - }, - resumeSession: true, - expectChannelID: true, - expectedNextProto: "foo", - expectedNextProtoType: 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", - base64.StdEncoding.EncodeToString(channelIDBytes), - "-advertise-npn", "\x03foo\x03bar\x03baz", - "-expect-next-proto", "bar", - }, - resumeSession: true, - expectChannelID: true, - expectedNextProto: "bar", - expectedNextProtoType: 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.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 = 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, + // 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, - messageCount: 1, - sendEmptyRecords: 1, - sendWarningAlerts: 1, - flags: []string{"-check-close-notify"}, - shouldFail: true, - expectedError: ":APPLICATION_DATA_ON_SHUTDOWN:", - }) + shimShutsDown: true, + sendEmptyRecords: 1, + sendWarningAlerts: 1, + flags: []string{"-check-close-notify"}, + }) - // 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, + // 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, - sendKeyUpdates: 1, - keyUpdateRequest: keyUpdateRequested, - flags: []string{"-check-close-notify"}, - }) + shimShutsDown: true, + messageCount: 1, + sendEmptyRecords: 1, + sendWarningAlerts: 1, + flags: []string{"-check-close-notify"}, + shouldFail: true, + expectedError: ":APPLICATION_DATA_ON_SHUTDOWN:", + }) - // 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, + // 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, - 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", - }, - }) + shimShutsDown: true, + sendKeyUpdates: 1, + keyUpdateRequest: keyUpdateRequested, + flags: []string{"-check-close-notify"}, + }) - tests = append(tests, testCase{ - testType: serverTest, - name: "Shutdown-Shim-Renegotiate-Server-Forbidden", - config: Config{ - MaxVersion: VersionTLS12, - Bugs: ProtocolBugs{ - ExpectCloseNotify: true, + // 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, - renegotiate: 1, - shouldFail: true, - expectedError: ":NO_RENEGOTIATION:", - flags: []string{ - "-check-close-notify", - }, - }) + 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", + }, + }) + } } - } else { + } + if config.protocol == dtls { // TODO(davidben): DTLS 1.3 will want a similar thing for // HelloRetryRequest. tests = append(tests, testCase{
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 3f524ff..ad8ecdb 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc
@@ -1162,8 +1162,7 @@ static int SendQuicAlert(SSL *ssl, enum ssl_encryption_level_t level, uint8_t alert) { - // TODO(nharper): Support processing alerts. - return 0; + return GetTestState(ssl)->quic_transport->SendAlert(level, alert); } static const SSL_QUIC_METHOD g_quic_method = {