diff --git a/ssl/test/CMakeLists.txt b/ssl/test/CMakeLists.txt index 5cb7e800a..27d959634 100644 --- a/ssl/test/CMakeLists.txt +++ b/ssl/test/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable( async_bio.cc bssl_shim.cc + packeted_bio.cc test_config.cc ) diff --git a/ssl/test/async_bio.cc b/ssl/test/async_bio.cc index f30f41252..48615790f 100644 --- a/ssl/test/async_bio.cc +++ b/ssl/test/async_bio.cc @@ -22,6 +22,7 @@ namespace { extern const BIO_METHOD async_bio_method; struct async_bio { + bool datagram; size_t read_quota; size_t write_quota; }; @@ -39,6 +40,12 @@ static int async_write(BIO *bio, const char *in, int inl) { return 0; } + if (a->datagram) { + // Perform writes synchronously; the DTLS implementation drops any packets + // that failed to send. + return BIO_write(bio->next_bio, in, inl); + } + BIO_clear_retry_flags(bio); if (a->write_quota == 0) { @@ -47,7 +54,7 @@ static int async_write(BIO *bio, const char *in, int inl) { return -1; } - if ((size_t)inl > a->write_quota) { + if (!a->datagram && (size_t)inl > a->write_quota) { inl = a->write_quota; } int ret = BIO_write(bio->next_bio, in, inl); @@ -73,14 +80,14 @@ static int async_read(BIO *bio, char *out, int outl) { return -1; } - if ((size_t)outl > a->read_quota) { + if (!a->datagram && (size_t)outl > a->read_quota) { outl = a->read_quota; } int ret = BIO_read(bio->next_bio, out, outl); if (ret <= 0) { BIO_copy_next_retry(bio); } else { - a->read_quota -= ret; + a->read_quota -= (a->datagram ? 1 : ret); } return ret; } @@ -144,18 +151,27 @@ BIO *async_bio_create() { return BIO_new(&async_bio_method); } -void async_bio_allow_read(BIO *bio, size_t bytes) { - async_bio *a = get_data(bio); - if (a == NULL) { - return; +BIO *async_bio_create_datagram() { + BIO *ret = BIO_new(&async_bio_method); + if (!ret) { + return NULL; } - a->read_quota += bytes; + get_data(ret)->datagram = true; + return ret; } -void async_bio_allow_write(BIO *bio, size_t bytes) { +void async_bio_allow_read(BIO *bio, size_t count) { async_bio *a = get_data(bio); if (a == NULL) { return; } - a->write_quota += bytes; + a->read_quota += count; +} + +void async_bio_allow_write(BIO *bio, size_t count) { + async_bio *a = get_data(bio); + if (a == NULL) { + return; + } + a->write_quota += count; } diff --git a/ssl/test/async_bio.h b/ssl/test/async_bio.h index 7bec5feb5..2904036fc 100644 --- a/ssl/test/async_bio.h +++ b/ssl/test/async_bio.h @@ -18,18 +18,23 @@ #include -// async_bio_create creates a filter BIO for testing asynchronous -// state machines. Reads and writes will fail and return EAGAIN unless -// explicitly allowed. Each async BIO has a read quota and a write -// quota. Initially both are zero. As each is incremented, bytes are -// allowed to flow through the BIO. +// async_bio_create creates a filter BIO for testing asynchronous state +// machines which consume a stream socket. Reads and writes will fail +// and return EAGAIN unless explicitly allowed. Each async BIO has a +// read quota and a write quota. Initially both are zero. As each is +// incremented, bytes are allowed to flow through the BIO. BIO *async_bio_create(); -// async_bio_allow_read increments |bio|'s read quota by |bytes|. -void async_bio_allow_read(BIO *bio, size_t bytes); +// async_bio_create_datagram creates a filter BIO for testing for +// asynchronous state machines which consume datagram sockets. The read +// and write quota count in packets rather than bytes. +BIO *async_bio_create_datagram(); -// async_bio_allow_write increments |bio|'s write quota by |bytes|. -void async_bio_allow_write(BIO *bio, size_t bytes); +// async_bio_allow_read increments |bio|'s read quota by |count|. +void async_bio_allow_read(BIO *bio, size_t count); + +// async_bio_allow_write increments |bio|'s write quota by |count|. +void async_bio_allow_write(BIO *bio, size_t count); #endif // HEADER_ASYNC_BIO diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index 19c5f830f..4f26bfbdd 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -29,6 +29,7 @@ #include #include "async_bio.h" +#include "packeted_bio.h" #include "test_config.h" static int usage(const char *program) { @@ -124,16 +125,61 @@ static int next_proto_select_callback(SSL* ssl, return SSL_TLSEXT_ERR_OK; } +static int cookie_generate_callback(SSL *ssl, uint8_t *cookie, unsigned *cookie_len) { + *cookie_len = 32; + memset(cookie, 42, *cookie_len); + return 1; +} + +static int cookie_verify_callback(SSL *ssl, uint8_t *cookie, unsigned cookie_len) { + if (cookie_len != 32) { + fprintf(stderr, "Cookie length mismatch.\n"); + return 0; + } + for (size_t i = 0; i < cookie_len; i++) { + if (cookie[i] != 42) { + fprintf(stderr, "Cookie mismatch.\n"); + return 0; + } + } + return 1; +} + static SSL_CTX *setup_ctx(const TestConfig *config) { SSL_CTX *ssl_ctx = NULL; DH *dh = NULL; - ssl_ctx = SSL_CTX_new( - config->is_server ? SSLv23_server_method() : SSLv23_client_method()); + const SSL_METHOD *method; + if (config->is_dtls) { + // TODO(davidben): Get DTLS 1.2 working and test the version negotiation + // codepath. This doesn't currently work because + // - Session resumption is broken: https://crbug.com/403378 + // - DTLS hasn't been updated for EVP_AEAD. + if (config->is_server) { + method = DTLSv1_server_method(); + } else { + method = DTLSv1_client_method(); + } + } else { + if (config->is_server) { + method = SSLv23_server_method(); + } else { + method = SSLv23_client_method(); + } + } + ssl_ctx = SSL_CTX_new(method); if (ssl_ctx == NULL) { goto err; } + if (config->is_dtls) { + // DTLS needs read-ahead to function on a datagram BIO. + // + // TODO(davidben): this should not be necessary. DTLS code should only + // expect a datagram BIO. + SSL_CTX_set_read_ahead(ssl_ctx, 1); + } + if (!SSL_CTX_set_ecdh_auto(ssl_ctx, 1)) { goto err; } @@ -156,6 +202,9 @@ static SSL_CTX *setup_ctx(const TestConfig *config) { SSL_CTX_set_next_proto_select_cb( ssl_ctx, next_proto_select_callback, NULL); + SSL_CTX_set_cookie_generate_cb(ssl_ctx, cookie_generate_callback); + SSL_CTX_set_cookie_verify_cb(ssl_ctx, cookie_verify_callback); + DH_free(dh); return ssl_ctx; @@ -248,14 +297,23 @@ static int do_exchange(SSL_SESSION **out_session, if (config->no_ssl3) { SSL_set_options(ssl, SSL_OP_NO_SSLv3); } + if (config->cookie_exchange) { + SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE); + } BIO *bio = BIO_new_fd(fd, 1 /* take ownership */); if (bio == NULL) { BIO_print_errors_fp(stdout); return 1; } + if (config->is_dtls) { + BIO *packeted = packeted_bio_create(); + BIO_push(packeted, bio); + bio = packeted; + } if (config->async) { - BIO *async = async_bio_create(); + BIO *async = + config->is_dtls ? async_bio_create_datagram() : async_bio_create(); BIO_push(async, bio); bio = async; } @@ -329,6 +387,10 @@ static int do_exchange(SSL_SESSION **out_session, } if (config->write_different_record_sizes) { + if (config->is_dtls) { + fprintf(stderr, "write_different_record_sizes not supported for DTLS\n"); + return 6; + } // This mode writes a number of different record sizes in an attempt to // trip up the CBC record splitting code. uint8_t buf[32769]; diff --git a/ssl/test/packeted_bio.cc b/ssl/test/packeted_bio.cc new file mode 100644 index 000000000..629d6c5b0 --- /dev/null +++ b/ssl/test/packeted_bio.cc @@ -0,0 +1,130 @@ +/* Copyright (c) 2014, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "packeted_bio.h" + +#include +#include +#include + +namespace { + +extern const BIO_METHOD packeted_bio_method; + +static int packeted_write(BIO *bio, const char *in, int inl) { + if (bio->next_bio == NULL) { + return 0; + } + + BIO_clear_retry_flags(bio); + + // Write the length prefix. + uint8_t len_bytes[4]; + len_bytes[0] = (inl >> 24) & 0xff; + len_bytes[1] = (inl >> 16) & 0xff; + len_bytes[2] = (inl >> 8) & 0xff; + len_bytes[3] = inl & 0xff; + int ret = BIO_write(bio->next_bio, len_bytes, sizeof(len_bytes)); + if (ret <= 0) { + BIO_copy_next_retry(bio); + return ret; + } + + // Write the buffer. BIOs for which this operation fails are not supported. + ret = BIO_write(bio->next_bio, in, inl); + assert(ret == inl); + return ret; +} + +static int packeted_read(BIO *bio, char *out, int outl) { + if (bio->next_bio == NULL) { + return 0; + } + + BIO_clear_retry_flags(bio); + + // Read the length prefix. + uint8_t len_bytes[4]; + int ret = BIO_read(bio->next_bio, &len_bytes, sizeof(len_bytes)); + if (ret <= 0) { + BIO_copy_next_retry(bio); + return ret; + } + // BIOs for which a partial length comes back are not supported. + assert(ret == 4); + + uint32_t len = (len_bytes[0] << 24) | (len_bytes[1] << 16) | + (len_bytes[2] << 8) | len_bytes[3]; + char *buf = (char *)OPENSSL_malloc(len); + assert(buf != NULL); + ret = BIO_read(bio->next_bio, buf, len); + assert(ret == (int)len); + + if (outl > (int)len) { + outl = len; + } + memcpy(out, buf, outl); + OPENSSL_free(buf); + return outl; +} + +static long packeted_ctrl(BIO *bio, int cmd, long num, void *ptr) { + if (bio->next_bio == NULL) { + return 0; + } + BIO_clear_retry_flags(bio); + int ret = BIO_ctrl(bio->next_bio, cmd, num, ptr); + BIO_copy_next_retry(bio); + return ret; +} + +static int packeted_new(BIO *bio) { + bio->init = 1; + return 1; +} + +static int packeted_free(BIO *bio) { + if (bio == NULL) { + return 0; + } + + bio->init = 0; + return 1; +} + +static long packeted_callback_ctrl(BIO *bio, int cmd, bio_info_cb fp) { + if (bio->next_bio == NULL) { + return 0; + } + return BIO_callback_ctrl(bio->next_bio, cmd, fp); +} + +const BIO_METHOD packeted_bio_method = { + BIO_TYPE_FILTER, + "packeted bio", + packeted_write, + packeted_read, + NULL /* puts */, + NULL /* gets */, + packeted_ctrl, + packeted_new, + packeted_free, + packeted_callback_ctrl, +}; + +} // namespace + +BIO *packeted_bio_create() { + return BIO_new(&packeted_bio_method); +} diff --git a/ssl/test/packeted_bio.h b/ssl/test/packeted_bio.h new file mode 100644 index 000000000..384bd64b9 --- /dev/null +++ b/ssl/test/packeted_bio.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2014, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#ifndef HEADER_PACKETED_BIO +#define HEADER_PACKETED_BIO + +#include + + +// packeted_bio_create creates a filter BIO for testing protocols which expect +// datagram BIOs. It implements a reliable datagram socket and reads and writes +// packets by prefixing each packet with a big-endian 32-bit length. It must be +// layered over a reliable blocking stream BIO. +// +// Note: packeted_bio_create exists because a SOCK_DGRAM socketpair on OS X is +// does not block the caller, unlike on Linux. Writes simply fail with +// ENOBUFS. POSIX also does not guarantee that such sockets are reliable. +BIO *packeted_bio_create(); + + +#endif // HEADER_PACKETED_BIO diff --git a/ssl/test/runner/packet_adapter.go b/ssl/test/runner/packet_adapter.go new file mode 100644 index 000000000..b2f2765e5 --- /dev/null +++ b/ssl/test/runner/packet_adapter.go @@ -0,0 +1,50 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "encoding/binary" + "errors" + "net" +) + +type packetAdaptor struct { + net.Conn +} + +func newPacketAdaptor(conn net.Conn) net.Conn { + return &packetAdaptor{conn} +} + +func (p *packetAdaptor) Read(b []byte) (int, error) { + var length uint32 + if err := binary.Read(p.Conn, binary.BigEndian, &length); err != nil { + return 0, err + } + out := make([]byte, length) + n, err := p.Conn.Read(out) + if err != nil { + return 0, err + } + if n != int(length) { + return 0, errors.New("internal error: length mismatch!") + } + return copy(b, out), nil +} + +func (p *packetAdaptor) Write(b []byte) (int, error) { + length := uint32(len(b)) + if err := binary.Write(p.Conn, binary.BigEndian, length); err != nil { + return 0, err + } + n, err := p.Conn.Write(b) + if err != nil { + return 0, err + } + if n != len(b) { + return 0, errors.New("internal error: length mismatch!") + } + return len(b), nil +} diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 4fd5a1d84..1d44f990a 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -63,8 +63,16 @@ const ( serverTest ) +type protocol int + +const ( + tls protocol = iota + dtls +) + type testCase struct { testType testType + protocol protocol name string config Config shouldFail bool @@ -340,18 +348,6 @@ var testCases = []testCase{ shouldFail: true, expectedError: ":CCS_RECEIVED_EARLY:", }, - { - name: "FalseStart-SessionTicketsDisabled", - config: Config{ - CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - NextProtos: []string{"foo"}, - SessionTicketsDisabled: true, - }, - flags: []string{ - "-false-start", - "-select-next-proto", "foo", - }, - }, { testType: serverTest, name: "FallbackSCSV", @@ -454,16 +450,30 @@ var testCases = []testCase{ } func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error { - if _, err := conn.Write([]byte(test.sendPrefix)); err != nil { - return err + if test.protocol == dtls { + conn = newPacketAdaptor(conn) + } + + if test.sendPrefix != "" { + if _, err := conn.Write([]byte(test.sendPrefix)); err != nil { + return err + } } var tlsConn *Conn if test.testType == clientTest { - tlsConn = Server(conn, config) + if test.protocol == dtls { + tlsConn = DTLSServer(conn, config) + } else { + tlsConn = Server(conn, config) + } } else { config.InsecureSkipVerify = true - tlsConn = Client(conn, config) + if test.protocol == dtls { + tlsConn = DTLSClient(conn, config) + } else { + tlsConn = Client(conn, config) + } } if err := tlsConn.Handshake(); err != nil { @@ -475,6 +485,9 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) e } if messageLen < 0 { + if test.protocol == dtls { + return fmt.Errorf("messageLen < 0 not supported for DTLS tests") + } // Read until EOF. _, err := io.Copy(ioutil.Discard, tlsConn) return err @@ -490,9 +503,21 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) e tlsConn.Write(testMessage) buf := make([]byte, len(testMessage)) - _, err := io.ReadFull(tlsConn, buf) - if err != nil { - return err + if test.protocol == dtls { + bufTmp := make([]byte, len(buf)+1) + n, err := tlsConn.Read(bufTmp) + if err != nil { + return err + } + if n != len(buf) { + return fmt.Errorf("bad reply; length mismatch (%d vs %d)", n, len(buf)) + } + copy(buf, bufTmp) + } else { + _, err := io.ReadFull(tlsConn, buf) + if err != nil { + return err + } } for i, v := range buf { @@ -568,6 +593,10 @@ func runTest(test *testCase, buildDir string) error { } } + if test.protocol == dtls { + flags = append(flags, "-dtls") + } + if test.resumeSession { flags = append(flags, "-resume") } @@ -746,6 +775,36 @@ func addCipherSuiteTests() { resumeSession: resumeSession, }) } + + // TODO(davidben): Fix DTLS 1.2 support and test that. + if ver.version == VersionTLS10 && strings.Index(suite.name, "RC4") == -1 { + testCases = append(testCases, testCase{ + testType: clientTest, + protocol: dtls, + name: "D" + ver.name + "-" + suite.name + "-client", + config: Config{ + MinVersion: ver.version, + MaxVersion: ver.version, + CipherSuites: []uint16{suite.id}, + Certificates: []Certificate{cert}, + }, + resumeSession: resumeSession, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: dtls, + name: "D" + ver.name + "-" + suite.name + "-server", + config: Config{ + MinVersion: ver.version, + MaxVersion: ver.version, + CipherSuites: []uint16{suite.id}, + Certificates: []Certificate{cert}, + }, + certFile: certFile, + keyFile: keyFile, + resumeSession: resumeSession, + }) + } } } } @@ -918,15 +977,18 @@ func addClientAuthTests() { // 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 addStateMachineCoverageTests(async bool, splitHandshake bool) { +func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) { var suffix string var flags []string var maxHandshakeRecordLength int + if protocol == dtls { + suffix = "-DTLS" + } if async { - suffix = "-Async" + suffix += "-Async" flags = append(flags, "-async") } else { - suffix = "-Sync" + suffix += "-Sync" } if splitHandshake { suffix += "-SplitHandshakeRecords" @@ -935,7 +997,8 @@ func addStateMachineCoverageTests(async bool, splitHandshake bool) { // Basic handshake, with resumption. Client and server. testCases = append(testCases, testCase{ - name: "Basic-Client" + suffix, + protocol: protocol, + name: "Basic-Client" + suffix, config: Config{ Bugs: ProtocolBugs{ MaxHandshakeRecordLength: maxHandshakeRecordLength, @@ -945,7 +1008,8 @@ func addStateMachineCoverageTests(async bool, splitHandshake bool) { resumeSession: true, }) testCases = append(testCases, testCase{ - name: "Basic-Client-RenewTicket" + suffix, + protocol: protocol, + name: "Basic-Client-RenewTicket" + suffix, config: Config{ Bugs: ProtocolBugs{ MaxHandshakeRecordLength: maxHandshakeRecordLength, @@ -956,6 +1020,7 @@ func addStateMachineCoverageTests(async bool, splitHandshake bool) { resumeSession: true, }) testCases = append(testCases, testCase{ + protocol: protocol, testType: serverTest, name: "Basic-Server" + suffix, config: Config{ @@ -967,9 +1032,36 @@ func addStateMachineCoverageTests(async bool, splitHandshake bool) { resumeSession: true, }) + // TLS client auth. + testCases = append(testCases, testCase{ + protocol: protocol, + testType: clientTest, + name: "ClientAuth-Client" + suffix, + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, + ClientAuth: RequireAnyClientCert, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, + "-cert-file", rsaCertificateFile, + "-key-file", rsaKeyFile), + }) + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "ClientAuth-Server" + suffix, + config: Config{ + Certificates: []Certificate{rsaCertificate}, + }, + flags: append(flags, "-require-any-client-certificate"), + }) + // No session ticket support; server doesn't send NewSessionTicket. testCases = append(testCases, testCase{ - name: "SessionTicketsDisabled-Client" + suffix, + protocol: protocol, + name: "SessionTicketsDisabled-Client" + suffix, config: Config{ SessionTicketsDisabled: true, Bugs: ProtocolBugs{ @@ -979,6 +1071,7 @@ func addStateMachineCoverageTests(async bool, splitHandshake bool) { flags: flags, }) testCases = append(testCases, testCase{ + protocol: protocol, testType: serverTest, name: "SessionTicketsDisabled-Server" + suffix, config: Config{ @@ -990,88 +1083,108 @@ func addStateMachineCoverageTests(async bool, splitHandshake bool) { flags: flags, }) - // NPN on client and server; results in post-handshake message. - testCases = append(testCases, testCase{ - name: "NPN-Client" + suffix, - config: Config{ - CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - NextProtos: []string{"foo"}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, + if protocol == tls { + // NPN on client and server; results in post-handshake message. + testCases = append(testCases, testCase{ + protocol: protocol, + name: "NPN-Client" + suffix, + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, }, - }, - flags: append(flags, "-select-next-proto", "foo"), - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "NPN-Server" + suffix, - config: Config{ - NextProtos: []string{"bar"}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, + flags: append(flags, "-select-next-proto", "foo"), + }) + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "NPN-Server" + suffix, + config: Config{ + NextProtos: []string{"bar"}, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, }, - }, - flags: append(flags, - "-advertise-npn", "\x03foo\x03bar\x03baz", - "-expect-next-proto", "bar"), - }) + flags: append(flags, + "-advertise-npn", "\x03foo\x03bar\x03baz", + "-expect-next-proto", "bar"), + }) - // Client does False Start and negotiates NPN. - testCases = append(testCases, testCase{ - name: "FalseStart" + suffix, - config: Config{ - CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - NextProtos: []string{"foo"}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, + // Client does False Start and negotiates NPN. + testCases = append(testCases, testCase{ + protocol: protocol, + name: "FalseStart" + suffix, + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, }, - }, - flags: append(flags, - "-false-start", - "-select-next-proto", "foo"), - resumeSession: true, - }) + flags: append(flags, + "-false-start", + "-select-next-proto", "foo"), + resumeSession: true, + }) - // TLS client auth. - testCases = append(testCases, testCase{ - testType: clientTest, - name: "ClientAuth-Client" + suffix, - config: Config{ - CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - ClientAuth: RequireAnyClientCert, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, + // False Start without session tickets. + testCases = append(testCases, testCase{ + name: "FalseStart-SessionTicketsDisabled", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + SessionTicketsDisabled: true, }, - }, - flags: append(flags, - "-cert-file", rsaCertificateFile, - "-key-file", rsaKeyFile), - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "ClientAuth-Server" + suffix, - config: Config{ - Certificates: []Certificate{rsaCertificate}, - }, - flags: append(flags, "-require-any-client-certificate"), - }) + flags: []string{ + "-false-start", + "-select-next-proto", "foo", + }, + }) - // Client sends a V2ClientHello. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "SendV2ClientHello" + suffix, - config: Config{ - // Choose a cipher suite that does not involve - // elliptic curves, so no extensions are - // involved. - CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - SendV2ClientHello: true, + // Client sends a V2ClientHello. + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "SendV2ClientHello" + suffix, + config: Config{ + // Choose a cipher suite that does not involve + // elliptic curves, so no extensions are + // involved. + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + SendV2ClientHello: true, + }, }, - }, - flags: flags, - }) + flags: flags, + }) + } else { + testCases = append(testCases, testCase{ + protocol: protocol, + name: "SkipHelloVerifyRequest" + suffix, + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + SkipHelloVerifyRequest: true, + }, + }, + flags: flags, + }) + + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: "CookieExchange" + suffix, + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, "-cookie-exchange"), + }) + } } func addVersionNegotiationTests() { @@ -1169,7 +1282,9 @@ func main() { addVersionNegotiationTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { - addStateMachineCoverageTests(async, splitHandshake) + for _, protocol := range []protocol{tls, dtls} { + addStateMachineCoverageTests(async, splitHandshake, protocol) + } } } diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index b25f7e6c6..971622703 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -34,6 +34,7 @@ struct StringFlag { const BoolFlag kBoolFlags[] = { { "-server", &TestConfig::is_server }, + { "-dtls", &TestConfig::is_dtls }, { "-resume", &TestConfig::resume }, { "-fallback-scsv", &TestConfig::fallback_scsv }, { "-require-any-client-certificate", @@ -48,6 +49,7 @@ const BoolFlag kBoolFlags[] = { { "-no-tls11", &TestConfig::no_tls11 }, { "-no-tls1", &TestConfig::no_tls1 }, { "-no-ssl3", &TestConfig::no_ssl3 }, + { "-cookie-exchange", &TestConfig::cookie_exchange }, }; const size_t kNumBoolFlags = sizeof(kBoolFlags) / sizeof(kBoolFlags[0]); @@ -71,6 +73,7 @@ const size_t kNumStringFlags = sizeof(kStringFlags) / sizeof(kStringFlags[0]); TestConfig::TestConfig() : is_server(false), + is_dtls(false), resume(false), fallback_scsv(false), require_any_client_certificate(false), @@ -82,7 +85,8 @@ TestConfig::TestConfig() no_tls12(false), no_tls11(false), no_tls1(false), - no_ssl3(false) { + no_ssl3(false), + cookie_exchange(false) { } bool ParseConfig(int argc, char **argv, TestConfig *out_config) { diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 558d0334a..34d720ef9 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -22,6 +22,7 @@ struct TestConfig { TestConfig(); bool is_server; + bool is_dtls; bool resume; bool fallback_scsv; std::string key_file; @@ -41,6 +42,7 @@ struct TestConfig { bool no_tls11; bool no_tls1; bool no_ssl3; + bool cookie_exchange; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config);