diff options
Diffstat (limited to 'src/ssl/test')
-rw-r--r-- | src/ssl/test/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/ssl/test/bssl_shim.cc | 70 | ||||
-rw-r--r-- | src/ssl/test/malloc.cc | 139 | ||||
-rw-r--r-- | src/ssl/test/runner/common.go | 34 | ||||
-rw-r--r-- | src/ssl/test/runner/conn.go | 16 | ||||
-rw-r--r-- | src/ssl/test/runner/dtls.go | 68 | ||||
-rw-r--r-- | src/ssl/test/runner/handshake_client.go | 27 | ||||
-rw-r--r-- | src/ssl/test/runner/handshake_server.go | 28 | ||||
-rw-r--r-- | src/ssl/test/runner/key_agreement.go | 17 | ||||
-rw-r--r-- | src/ssl/test/runner/runner.go | 700 | ||||
-rw-r--r-- | src/ssl/test/test_config.cc | 3 | ||||
-rw-r--r-- | src/ssl/test/test_config.h | 3 |
12 files changed, 571 insertions, 537 deletions
diff --git a/src/ssl/test/CMakeLists.txt b/src/ssl/test/CMakeLists.txt index a0d7a5e..3b07903 100644 --- a/src/ssl/test/CMakeLists.txt +++ b/src/ssl/test/CMakeLists.txt @@ -5,9 +5,10 @@ add_executable( async_bio.cc bssl_shim.cc - malloc.cc packeted_bio.cc test_config.cc + + $<TARGET_OBJECTS:test_support> ) target_link_libraries(bssl_shim ssl crypto) diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc index 1cf96f2..40cb149 100644 --- a/src/ssl/test/bssl_shim.cc +++ b/src/ssl/test/bssl_shim.cc @@ -406,14 +406,6 @@ static ScopedSSL_CTX SetupCtx(const TestConfig *config) { return nullptr; } - 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.get(), 1); - } - if (!SSL_CTX_set_cipher_list(ssl_ctx.get(), "ALL")) { return nullptr; } @@ -599,6 +591,9 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, if (config->allow_unsafe_legacy_renegotiation) { SSL_set_options(ssl.get(), SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); } + if (config->no_legacy_server_connect) { + SSL_clear_options(ssl.get(), SSL_OP_LEGACY_SERVER_CONNECT); + } if (!config->expected_channel_id.empty()) { SSL_enable_tls_channel_id(ssl.get()); } @@ -660,8 +655,9 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, !SSL_set_cipher_list(ssl.get(), config->cipher.c_str())) { return false; } - if (config->reject_peer_renegotiations) { - SSL_set_reject_peer_renegotiations(ssl.get(), 1); + if (!config->reject_peer_renegotiations) { + /* Renegotiations are disabled by default. */ + SSL_set_reject_peer_renegotiations(ssl.get(), 0); } int sock = Connect(config->port); @@ -703,6 +699,11 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, } } + if (SSL_get_current_cipher(ssl.get()) != nullptr) { + fprintf(stderr, "non-null cipher before handshake\n"); + return false; + } + int ret; if (config->implicit_handshake) { if (config->is_server) { @@ -722,6 +723,11 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, return false; } + if (SSL_get_current_cipher(ssl.get()) == nullptr) { + fprintf(stderr, "null cipher after handshake\n"); + return false; + } + if (is_resume && (!!SSL_session_reused(ssl.get()) == config->expect_session_miss)) { fprintf(stderr, "session was%s reused\n", @@ -834,30 +840,6 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, } } - if (config->renegotiate) { - if (config->async) { - fprintf(stderr, "-renegotiate is not supported with -async.\n"); - return false; - } - if (config->implicit_handshake) { - fprintf(stderr, "-renegotiate is not supported with -implicit-handshake.\n"); - return false; - } - - SSL_renegotiate(ssl.get()); - - ret = SSL_do_handshake(ssl.get()); - if (ret != 1) { - return false; - } - - SSL_set_state(ssl.get(), SSL_ST_ACCEPT); - ret = SSL_do_handshake(ssl.get()); - if (ret != 1) { - return false; - } - } - if (config->export_keying_material > 0) { std::vector<uint8_t> result( static_cast<size_t>(config->export_keying_material)); @@ -874,6 +856,26 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, } } + if (config->tls_unique) { + uint8_t tls_unique[16]; + size_t tls_unique_len; + if (!SSL_get_tls_unique(ssl.get(), tls_unique, &tls_unique_len, + sizeof(tls_unique))) { + fprintf(stderr, "failed to get tls-unique\n"); + return false; + } + + if (tls_unique_len != 12) { + fprintf(stderr, "expected 12 bytes of tls-unique but got %u", + static_cast<unsigned>(tls_unique_len)); + return false; + } + + if (WriteAll(ssl.get(), tls_unique, tls_unique_len) < 0) { + return false; + } + } + if (config->write_different_record_sizes) { if (config->is_dtls) { fprintf(stderr, "write_different_record_sizes not supported for DTLS\n"); diff --git a/src/ssl/test/malloc.cc b/src/ssl/test/malloc.cc deleted file mode 100644 index 2ec5582..0000000 --- a/src/ssl/test/malloc.cc +++ /dev/null @@ -1,139 +0,0 @@ -/* 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 <openssl/base.h> - -#if defined(__has_feature) -#if __has_feature(address_sanitizer) -#define OPENSSL_ASAN -#endif -#endif - -// This file isn't built on ARM or Aarch64 because we link statically in those -// builds and trying to override malloc in a static link doesn't work. It's also -// disabled on ASan builds as this interferes with ASan's malloc interceptor. -// -// TODO(davidben): See if this and ASan's interceptors can be made to coexist. -#if defined(__linux__) && !defined(OPENSSL_ARM) && \ - !defined(OPENSSL_AARCH64) && !defined(OPENSSL_ASAN) - -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> - -#include <new> - - -/* This file defines overrides for the standard allocation functions that allow - * a given allocation to be made to fail for testing. If the program is run - * with MALLOC_NUMBER_TO_FAIL set to a base-10 number then that allocation will - * return NULL. If MALLOC_ABORT_ON_FAIL is also defined then the allocation - * will abort() rather than return NULL. - * - * This code is not thread safe. */ - -static uint64_t current_malloc_count = 0; -static uint64_t malloc_number_to_fail = 0; -static char failure_enabled = 0, abort_on_fail = 0; -static int in_call = 0; - -extern "C" { -/* These are other names for the standard allocation functions. */ -extern void *__libc_malloc(size_t size); -extern void *__libc_calloc(size_t num_elems, size_t size); -extern void *__libc_realloc(void *ptr, size_t size); -} - -static void exit_handler(void) { - if (failure_enabled && current_malloc_count > malloc_number_to_fail) { - _exit(88); - } -} - -static void cpp_new_handler() { - // Return to try again. It won't fail a second time. - return; -} - -/* should_fail_allocation returns true if the current allocation should fail. */ -static int should_fail_allocation() { - static int init = 0; - char should_fail; - - if (in_call) { - return 0; - } - - in_call = 1; - - if (!init) { - const char *env = getenv("MALLOC_NUMBER_TO_FAIL"); - if (env != NULL && env[0] != 0) { - char *endptr; - malloc_number_to_fail = strtoull(env, &endptr, 10); - if (*endptr == 0) { - failure_enabled = 1; - atexit(exit_handler); - std::set_new_handler(cpp_new_handler); - } - } - abort_on_fail = (NULL != getenv("MALLOC_ABORT_ON_FAIL")); - init = 1; - } - - in_call = 0; - - if (!failure_enabled) { - return 0; - } - - should_fail = (current_malloc_count == malloc_number_to_fail); - current_malloc_count++; - - if (should_fail && abort_on_fail) { - abort(); - } - return should_fail; -} - -extern "C" { - -void *malloc(size_t size) { - if (should_fail_allocation()) { - return NULL; - } - - return __libc_malloc(size); -} - -void *calloc(size_t num_elems, size_t size) { - if (should_fail_allocation()) { - return NULL; - } - - return __libc_calloc(num_elems, size); -} - -void *realloc(void *ptr, size_t size) { - if (should_fail_allocation()) { - return NULL; - } - - return __libc_realloc(ptr, size); -} - -} // extern "C" - -#endif /* defined(linux) && !ARM && !AARCH64 && !ASAN */ diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go index 4ac7250..edebba1 100644 --- a/src/ssl/test/runner/common.go +++ b/src/ssl/test/runner/common.go @@ -188,6 +188,7 @@ type ConnectionState struct { VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates ChannelID *ecdsa.PublicKey // the channel ID for this connection SRTPProtectionProfile uint16 // the negotiated DTLS-SRTP protection profile + TLSUnique []byte } // ClientAuthType declares the policy the server will follow for @@ -478,7 +479,9 @@ type ProtocolBugs struct { // MaxHandshakeRecordLength, if non-zero, is the maximum size of a // handshake record. Handshake messages will be split into multiple // records at the specified size, except that the client_version will - // never be fragmented. + // never be fragmented. For DTLS, it is the maximum handshake fragment + // size, not record size; DTLS allows multiple handshake fragments in a + // single handshake record. See |PackHandshakeFragments|. MaxHandshakeRecordLength int // FragmentClientVersion will allow MaxHandshakeRecordLength to apply to @@ -681,13 +684,14 @@ type ProtocolBugs struct { // fragments in DTLS. SendEmptyFragments bool - // NeverResumeOnRenego, if true, causes renegotiations to always be full - // handshakes. - NeverResumeOnRenego bool + // SendSplitAlert, if true, causes an alert to be sent with the header + // and record body split across multiple packets. The peer should + // discard these packets rather than process it. + SendSplitAlert bool - // NoSignatureAlgorithmsOnRenego, if true, causes renegotiations to omit - // the signature_algorithms extension. - NoSignatureAlgorithmsOnRenego bool + // FailIfResumeOnRenego, if true, causes renegotiations to fail if the + // client offers a resumption or the server accepts one. + FailIfResumeOnRenego bool // IgnorePeerCipherPreferences, if true, causes the peer's cipher // preferences to be ignored. @@ -707,6 +711,22 @@ type ProtocolBugs struct { // BadFinished, if true, causes the Finished hash to be broken. BadFinished bool + + // DHGroupPrime, if not nil, is used to define the (finite field) + // Diffie-Hellman group. The generator used is always two. + DHGroupPrime *big.Int + + // PackHandshakeFragments, if true, causes handshake fragments to be + // packed into individual handshake records, up to the specified record + // size. + PackHandshakeFragments int + + // PackHandshakeRecords, if true, causes handshake records to be packed + // into individual packets, up to the specified packet size. + PackHandshakeRecords int + + // EnableAllCiphersInDTLS, if true, causes RC4 to be enabled in DTLS. + EnableAllCiphersInDTLS bool } func (c *Config) serverInit() { diff --git a/src/ssl/test/runner/conn.go b/src/ssl/test/runner/conn.go index fd198ca..adbc1c3 100644 --- a/src/ssl/test/runner/conn.go +++ b/src/ssl/test/runner/conn.go @@ -44,7 +44,11 @@ type Conn struct { // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate // serverName contains the server name indicated by the client, if any. - serverName string + serverName string + // firstFinished contains the first Finished hash sent during the + // handshake. This is the "tls-unique" channel binding value. + firstFinished [12]byte + clientRandom, serverRandom [32]byte masterSecret [48]byte @@ -1260,6 +1264,15 @@ func (c *Conn) Handshake() error { return nil } + if c.isDTLS && c.config.Bugs.SendSplitAlert { + c.conn.Write([]byte{ + byte(recordTypeAlert), // type + 0xfe, 0xff, // version + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // sequence + 0x0, 0x2, // length + }) + c.conn.Write([]byte{alertLevelError, byte(alertInternalError)}) + } if c.isClient { c.handshakeErr = c.clientHandshake() } else { @@ -1290,6 +1303,7 @@ func (c *Conn) ConnectionState() ConnectionState { state.ServerName = c.serverName state.ChannelID = c.channelID state.SRTPProtectionProfile = c.srtpProtectionProfile + state.TLSUnique = c.firstFinished[:] } return state diff --git a/src/ssl/test/runner/dtls.go b/src/ssl/test/runner/dtls.go index 85c4247..50f7786 100644 --- a/src/ssl/test/runner/dtls.go +++ b/src/ssl/test/runner/dtls.go @@ -196,6 +196,8 @@ func (c *Conn) dtlsFlushHandshake() error { return nil } + // This is a test-only DTLS implementation, so there is no need to + // retain |c.pendingFragments| for a future retransmit. var fragments [][]byte fragments, c.pendingFragments = c.pendingFragments, fragments @@ -208,38 +210,66 @@ func (c *Conn) dtlsFlushHandshake() error { fragments = tmp } - // Send them all. + maxRecordLen := c.config.Bugs.PackHandshakeFragments + maxPacketLen := c.config.Bugs.PackHandshakeRecords + + // Pack handshake fragments into records. + var records [][]byte for _, fragment := range fragments { if c.config.Bugs.SplitFragmentHeader { - if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment[:2]); err != nil { - return err - } - fragment = fragment[2:] - } else if c.config.Bugs.SplitFragmentBody && len(fragment) > 12 { - if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment[:13]); err != nil { - return err + records = append(records, fragment[:2]) + records = append(records, fragment[2:]) + } else if c.config.Bugs.SplitFragmentBody { + if len(fragment) > 12 { + records = append(records, fragment[:13]) + records = append(records, fragment[13:]) + } else { + records = append(records, fragment) } - fragment = fragment[13:] + } else if i := len(records) - 1; len(records) > 0 && len(records[i])+len(fragment) <= maxRecordLen { + records[i] = append(records[i], fragment...) + } else { + // The fragment will be appended to, so copy it. + records = append(records, append([]byte{}, fragment...)) + } + } + + // Format them into packets. + var packets [][]byte + for _, record := range records { + b, err := c.dtlsSealRecord(recordTypeHandshake, record) + if err != nil { + return err + } + + if i := len(packets) - 1; len(packets) > 0 && len(packets[i])+len(b.data) <= maxPacketLen { + packets[i] = append(packets[i], b.data...) + } else { + // The sealed record will be appended to and reused by + // |c.out|, so copy it. + packets = append(packets, append([]byte{}, b.data...)) } + c.out.freeBlock(b) + } - // TODO(davidben): A real DTLS implementation needs to - // retransmit handshake messages. For testing purposes, we don't - // actually care. - if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment); err != nil { + // Send all the packets. + for _, packet := range packets { + if _, err := c.conn.Write(packet); err != nil { return err } } return nil } -func (c *Conn) dtlsWriteRawRecord(typ recordType, data []byte) (n int, err error) { +// dtlsSealRecord seals a record into a block from |c.out|'s pool. +func (c *Conn) dtlsSealRecord(typ recordType, data []byte) (b *block, err error) { recordHeaderLen := dtlsRecordHeaderLen maxLen := c.config.Bugs.MaxHandshakeRecordLength if maxLen <= 0 { maxLen = 1024 } - b := c.out.newBlock() + b = c.out.newBlock() explicitIVLen := 0 explicitIVIsSeq := false @@ -286,6 +316,14 @@ func (c *Conn) dtlsWriteRawRecord(typ recordType, data []byte) (n int, err error } copy(b.data[recordHeaderLen+explicitIVLen:], data) c.out.encrypt(b, explicitIVLen) + return +} + +func (c *Conn) dtlsWriteRawRecord(typ recordType, data []byte) (n int, err error) { + b, err := c.dtlsSealRecord(typ, data) + if err != nil { + return + } _, err = c.conn.Write(b.data) if err != nil { diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go index 0dac05d..a950313 100644 --- a/src/ssl/test/runner/handshake_client.go +++ b/src/ssl/test/runner/handshake_client.go @@ -115,7 +115,7 @@ NextCipherSuite: continue } // Don't advertise non-DTLS cipher suites on DTLS. - if c.isDTLS && suite.flags&suiteNoDTLS != 0 { + if c.isDTLS && suite.flags&suiteNoDTLS != 0 && !c.config.Bugs.EnableAllCiphersInDTLS { continue } hello.cipherSuites = append(hello.cipherSuites, suiteId) @@ -133,16 +133,13 @@ NextCipherSuite: return errors.New("tls: short read from Rand: " + err.Error()) } - if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes && (c.cipherSuite == nil || !c.config.Bugs.NoSignatureAlgorithmsOnRenego) { + if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes { hello.signatureAndHashes = c.config.signatureAndHashesForClient() } var session *ClientSessionState var cacheKey string sessionCache := c.config.ClientSessionCache - if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != nil { - sessionCache = nil - } if sessionCache != nil { hello.ticketSupported = !c.config.SessionTicketsDisabled @@ -316,10 +313,10 @@ NextCipherSuite: if err := hs.readSessionTicket(); err != nil { return err } - if err := hs.readFinished(); err != nil { + if err := hs.readFinished(c.firstFinished[:]); err != nil { return err } - if err := hs.sendFinished(isResume); err != nil { + if err := hs.sendFinished(nil, isResume); err != nil { return err } } else { @@ -329,7 +326,7 @@ NextCipherSuite: if err := hs.establishKeys(); err != nil { return err } - if err := hs.sendFinished(isResume); err != nil { + if err := hs.sendFinished(c.firstFinished[:], isResume); err != nil { return err } // Most retransmits are triggered by a timeout, but the final @@ -344,7 +341,7 @@ NextCipherSuite: if err := hs.readSessionTicket(); err != nil { return err } - if err := hs.readFinished(); err != nil { + if err := hs.readFinished(nil); err != nil { return err } } @@ -727,6 +724,12 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { } if hs.serverResumedSession() { + // For test purposes, assert that the server never accepts the + // resumption offer on renegotiation. + if c.cipherSuite != nil && c.config.Bugs.FailIfResumeOnRenego { + return false, errors.New("tls: server resumed session on renegotiation") + } + // Restore masterSecret and peerCerts from previous state hs.masterSecret = hs.session.masterSecret c.peerCertificates = hs.session.serverCertificates @@ -737,7 +740,7 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { return false, nil } -func (hs *clientHandshakeState) readFinished() error { +func (hs *clientHandshakeState) readFinished(out []byte) error { c := hs.c c.readRecord(recordTypeChangeCipherSpec) @@ -764,6 +767,7 @@ func (hs *clientHandshakeState) readFinished() error { } } c.serverVerify = append(c.serverVerify[:0], serverFinished.verifyData...) + copy(out, serverFinished.verifyData) hs.writeServerHash(serverFinished.marshal()) return nil } @@ -807,7 +811,7 @@ func (hs *clientHandshakeState) readSessionTicket() error { return nil } -func (hs *clientHandshakeState) sendFinished(isResume bool) error { +func (hs *clientHandshakeState) sendFinished(out []byte, isResume bool) error { c := hs.c var postCCSBytes []byte @@ -859,6 +863,7 @@ func (hs *clientHandshakeState) sendFinished(isResume bool) error { } else { finished.verifyData = hs.finishedHash.clientSum(hs.masterSecret) } + copy(out, finished.verifyData) if c.config.Bugs.BadFinished { finished.verifyData[0]++ } diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go index 59ed9df..85cc0d2 100644 --- a/src/ssl/test/runner/handshake_server.go +++ b/src/ssl/test/runner/handshake_server.go @@ -69,7 +69,7 @@ func (c *Conn) serverHandshake() error { return err } } - if err := hs.sendFinished(); err != nil { + if err := hs.sendFinished(c.firstFinished[:]); err != nil { return err } // Most retransmits are triggered by a timeout, but the final @@ -81,7 +81,7 @@ func (c *Conn) serverHandshake() error { }); err != nil { return err } - if err := hs.readFinished(isResume); err != nil { + if err := hs.readFinished(nil, isResume); err != nil { return err } c.didResume = true @@ -94,7 +94,7 @@ func (c *Conn) serverHandshake() error { if err := hs.establishKeys(); err != nil { return err } - if err := hs.readFinished(isResume); err != nil { + if err := hs.readFinished(c.firstFinished[:], isResume); err != nil { return err } if c.config.Bugs.AlertBeforeFalseStartTest != 0 { @@ -108,7 +108,7 @@ func (c *Conn) serverHandshake() error { if err := hs.sendSessionTicket(); err != nil { return err } - if err := hs.sendFinished(); err != nil { + if err := hs.sendFinished(nil); err != nil { return err } } @@ -274,6 +274,10 @@ Curves: hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation } + if c.config.Bugs.NoRenegotiationInfo { + hs.hello.secureRenegotiation = nil + } + hs.hello.compressionMethod = compressionNone hs.hello.duplicateExtension = c.config.Bugs.DuplicateExtension if len(hs.clientHello.serverName) > 0 { @@ -333,6 +337,12 @@ Curves: _, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey) + // For test purposes, check that the peer never offers a session when + // renegotiating. + if c.cipherSuite != nil && len(hs.clientHello.sessionId) > 0 && c.config.Bugs.FailIfResumeOnRenego { + return false, errors.New("tls: offered resumption on renegotiation") + } + if hs.checkForResumption() { return true, nil } @@ -382,10 +392,6 @@ Curves: func (hs *serverHandshakeState) checkForResumption() bool { c := hs.c - if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != nil { - return false - } - if len(hs.clientHello.sessionTicket) > 0 { if c.config.SessionTicketsDisabled { return false @@ -748,7 +754,7 @@ func (hs *serverHandshakeState) establishKeys() error { return nil } -func (hs *serverHandshakeState) readFinished(isResume bool) error { +func (hs *serverHandshakeState) readFinished(out []byte, isResume bool) error { c := hs.c c.readRecord(recordTypeChangeCipherSpec) @@ -817,6 +823,7 @@ func (hs *serverHandshakeState) readFinished(isResume bool) error { return errors.New("tls: client's Finished message is incorrect") } c.clientVerify = append(c.clientVerify[:0], clientFinished.verifyData...) + copy(out, clientFinished.verifyData) hs.writeClientHash(clientFinished.marshal()) return nil @@ -853,11 +860,12 @@ func (hs *serverHandshakeState) sendSessionTicket() error { return nil } -func (hs *serverHandshakeState) sendFinished() error { +func (hs *serverHandshakeState) sendFinished(out []byte) error { c := hs.c finished := new(finishedMsg) finished.verifyData = hs.finishedHash.serverSum(hs.masterSecret) + copy(out, finished.verifyData) if c.config.Bugs.BadFinished { finished.verifyData[0]++ } diff --git a/src/ssl/test/runner/key_agreement.go b/src/ssl/test/runner/key_agreement.go index 5e44b54..2ee0087 100644 --- a/src/ssl/test/runner/key_agreement.go +++ b/src/ssl/test/runner/key_agreement.go @@ -561,11 +561,18 @@ type dheKeyAgreement struct { } func (ka *dheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { - // 2048-bit MODP Group with 256-bit Prime Order Subgroup (RFC - // 5114, Section 2.3) - ka.p, _ = new(big.Int).SetString("87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F25D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA3016C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0EF13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D967E144E5140564251CCACB83E6B486F6B3CA3F7971506026C0B857F689962856DED4010ABD0BE621C3A3960A54E710C375F26375D7014103A4B54330C198AF126116D2276E11715F693877FAD7EF09CADB094AE91E1A1597", 16) - ka.g, _ = new(big.Int).SetString("3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF205407F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC831D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6184B523D1DB246C32F63078490F00EF8D647D148D47954515E2327CFEF98C582664B4C0F6CC41659", 16) - q, _ := new(big.Int).SetString("8CF83642A709A097B447997640129DA299B1A47D1EB3750BA308B0FE64F5FBD3", 16) + var q *big.Int + if p := config.Bugs.DHGroupPrime; p != nil { + ka.p = p + ka.g = big.NewInt(2) + q = p + } else { + // 2048-bit MODP Group with 256-bit Prime Order Subgroup (RFC + // 5114, Section 2.3) + ka.p, _ = new(big.Int).SetString("87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F25D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA3016C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0EF13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D967E144E5140564251CCACB83E6B486F6B3CA3F7971506026C0B857F689962856DED4010ABD0BE621C3A3960A54E710C375F26375D7014103A4B54330C198AF126116D2276E11715F693877FAD7EF09CADB094AE91E1A1597", 16) + ka.g, _ = new(big.Int).SetString("3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF205407F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC831D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6184B523D1DB246C32F63078490F00EF8D647D148D47954515E2327CFEF98C582664B4C0F6CC41659", 16) + q, _ = new(big.Int).SetString("8CF83642A709A097B447997640129DA299B1A47D1EB3750BA308B0FE64F5FBD3", 16) + } var err error ka.xOurs, err = rand.Int(config.rand(), q) diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go index ec2fede..bd03cb1 100644 --- a/src/ssl/test/runner/runner.go +++ b/src/ssl/test/runner/runner.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "io/ioutil" + "math/big" "net" "os" "os/exec" @@ -160,6 +161,10 @@ type testCase struct { // resumeSession controls whether a second connection should be tested // which attempts to resume the first session. resumeSession bool + // expectResumeRejected, if true, specifies that the attempted + // resumption must be rejected by the client. This is only valid for a + // serverTest. + expectResumeRejected bool // resumeConfig, if not nil, points to a Config to be used on // resumption. Unless newSessionsOnResume is set, // SessionTicketKey, ServerSessionCache, and @@ -196,6 +201,9 @@ type testCase struct { // flags, if not empty, contains a list of command-line flags that will // be passed to the shim program. flags []string + // testTLSUnique, if true, causes the shim to send the tls-unique value + // which will be compared against the expected value. + testTLSUnique bool } var testCases = []testCase{ @@ -1085,6 +1093,49 @@ var testCases = []testCase{ }, expectedCipher: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, }, + { + 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"}, + }, + { + protocol: dtls, + name: "PackDTLSHandshake", + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: 2, + PackHandshakeFragments: 20, + PackHandshakeRecords: 200, + }, + }, + }, + { + testType: serverTest, + protocol: dtls, + name: "NoRC4-DTLS", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_RC4_128_SHA}, + Bugs: ProtocolBugs{ + EnableAllCiphersInDTLS: true, + }, + }, + shouldFail: true, + expectedError: ":NO_SHARED_CIPHER:", + }, } func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, isResume bool) error { @@ -1144,16 +1195,20 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i if isResume && test.expectedResumeVersion != 0 { expectedVersion = test.expectedResumeVersion } - if vers := tlsConn.ConnectionState().Version; expectedVersion != 0 && vers != expectedVersion { + connState := tlsConn.ConnectionState() + if vers := connState.Version; expectedVersion != 0 && vers != expectedVersion { return fmt.Errorf("got version %x, expected %x", vers, expectedVersion) } - if cipher := tlsConn.ConnectionState().CipherSuite; test.expectedCipher != 0 && cipher != test.expectedCipher { + if cipher := connState.CipherSuite; test.expectedCipher != 0 && cipher != test.expectedCipher { return fmt.Errorf("got cipher %x, expected %x", cipher, test.expectedCipher) } + if didResume := connState.DidResume; isResume && didResume == test.expectResumeRejected { + return fmt.Errorf("didResume is %t, but we expected the opposite", didResume) + } if test.expectChannelID { - channelID := tlsConn.ConnectionState().ChannelID + channelID := connState.ChannelID if channelID == nil { return fmt.Errorf("no channel ID negotiated") } @@ -1165,18 +1220,18 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i } if expected := test.expectedNextProto; expected != "" { - if actual := tlsConn.ConnectionState().NegotiatedProtocol; actual != expected { + if actual := connState.NegotiatedProtocol; actual != expected { return fmt.Errorf("next proto mismatch: got %s, wanted %s", actual, expected) } } if test.expectedNextProtoType != 0 { - if (test.expectedNextProtoType == alpn) != tlsConn.ConnectionState().NegotiatedProtocolFromALPN { + if (test.expectedNextProtoType == alpn) != connState.NegotiatedProtocolFromALPN { return fmt.Errorf("next proto type mismatch") } } - if p := tlsConn.ConnectionState().SRTPProtectionProfile; p != test.expectedSRTPProtectionProfile { + if p := connState.SRTPProtectionProfile; p != test.expectedSRTPProtectionProfile { return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, test.expectedSRTPProtectionProfile) } @@ -1194,6 +1249,17 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i } } + if test.testTLSUnique { + var peersValue [12]byte + if _, err := io.ReadFull(tlsConn, peersValue[:]); err != nil { + return err + } + expected := tlsConn.ConnectionState().TLSUnique + if !bytes.Equal(peersValue[:], expected) { + return fmt.Errorf("tls-unique mismatch: peer sent %x, but %x was expected", peersValue[:], expected) + } + } + if test.shimWritesFirst { var buf [5]byte _, err := io.ReadFull(tlsConn, buf[:]) @@ -1321,6 +1387,10 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { panic("Error expected without shouldFail in " + test.name) } + if test.expectResumeRejected && !test.resumeSession { + panic("expectResumeRejected without resumeSession in " + test.name) + } + listener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IP{127, 0, 0, 1}}) if err != nil { panic(err) @@ -1371,6 +1441,13 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { flags = append(flags, "-use-export-context") } } + if test.expectResumeRejected { + flags = append(flags, "-expect-session-miss") + } + + if test.testTLSUnique { + flags = append(flags, "-tls-unique") + } flags = append(flags, test.flags...) @@ -1569,6 +1646,14 @@ func isDTLSCipher(suiteName string) bool { return !hasComponent(suiteName, "RC4") } +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 addCipherSuiteTests() { for _, suite := range testCipherSuites { const psk = "12345" @@ -1667,6 +1752,21 @@ func addCipherSuiteTests() { } } } + + testCases = append(testCases, testCase{ + name: "WeakDH", + config: Config{ + CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + // This is a 1023-bit prime number, generated + // with: + // openssl gendh 1023 | openssl asn1parse -i + DHGroupPrime: bigFromHex("518E9B7930CE61C6E445C8360584E5FC78D9137C0FFDC880B495D5338ADF7689951A6821C17A76B3ACB8E0156AEA607B7EC406EBEDBB84D8376EB8FE8F8BA1433488BEE0C3EDDFD3A32DBB9481980A7AF6C96BFCF490A094CFFB2B8192C1BB5510B77B658436E27C2D4D023FE3718222AB0CA1273995B51F6D625A4944D0DD4B"), + }, + }, + shouldFail: true, + expectedError: "BAD_DH_P_LENGTH", + }) } func addBadECDSASignatureTests() { @@ -1866,245 +1966,235 @@ func addExtendedMasterSecretTests() { } } - // 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. - testCases = append(testCases, testCase{ - name: "ExtendedMasterSecret-Resume", - config: Config{ - Bugs: ProtocolBugs{ - RequireExtendedMasterSecret: true, - }, - }, - flags: []string{expectEMSFlag}, - resumeSession: true, - }) + 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{ + Bugs: ProtocolBugs{ + RequireExtendedMasterSecret: true, + }, + } + + noSupportConfig := Config{ + 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) + } + } + } } // 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, splitHandshake bool, protocol protocol) { - var suffix string - var flags []string - var maxHandshakeRecordLength int - if protocol == dtls { - suffix = "-DTLS" - } - if async { - suffix += "-Async" - flags = append(flags, "-async") - } else { - suffix += "-Sync" - } - if splitHandshake { - suffix += "-SplitHandshakeRecords" - maxHandshakeRecordLength = 1 - } + var tests []testCase // Basic handshake, with resumption. Client and server, // session ID and session ticket. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "Basic-Client" + suffix, - config: Config{ - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, - }, - flags: flags, + tests = append(tests, testCase{ + name: "Basic-Client", resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, - name: "Basic-Client-RenewTicket" + suffix, + tests = append(tests, testCase{ + name: "Basic-Client-RenewTicket", config: Config{ Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - RenewTicketOnResume: true, + RenewTicketOnResume: true, }, }, - flags: flags, resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, - name: "Basic-Client-NoTicket" + suffix, + tests = append(tests, testCase{ + name: "Basic-Client-NoTicket", config: Config{ SessionTicketsDisabled: true, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: flags, resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, - name: "Basic-Client-Implicit" + suffix, - config: Config{ - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, - }, - flags: append(flags, "-implicit-handshake"), + tests = append(tests, testCase{ + name: "Basic-Client-Implicit", + flags: []string{"-implicit-handshake"}, resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, - testType: serverTest, - name: "Basic-Server" + suffix, - config: Config{ - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, - }, - flags: flags, + tests = append(tests, testCase{ + testType: serverTest, + name: "Basic-Server", resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "Basic-Server-NoTickets" + suffix, + name: "Basic-Server-NoTickets", config: Config{ SessionTicketsDisabled: true, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: flags, resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, - testType: serverTest, - name: "Basic-Server-Implicit" + suffix, - config: Config{ - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, - }, - flags: append(flags, "-implicit-handshake"), + tests = append(tests, testCase{ + testType: serverTest, + name: "Basic-Server-Implicit", + flags: []string{"-implicit-handshake"}, resumeSession: true, }) - testCases = append(testCases, testCase{ - protocol: protocol, - testType: serverTest, - name: "Basic-Server-EarlyCallback" + suffix, - config: Config{ - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, - }, - flags: append(flags, "-use-early-callback"), + tests = append(tests, testCase{ + testType: serverTest, + name: "Basic-Server-EarlyCallback", + flags: []string{"-use-early-callback"}, resumeSession: true, }) // TLS client auth. - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: clientTest, - name: "ClientAuth-Client" + suffix, + name: "ClientAuth-Client", config: Config{ ClientAuth: RequireAnyClientCert, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, + flags: []string{ "-cert-file", rsaCertificateFile, - "-key-file", rsaKeyFile), + "-key-file", rsaKeyFile, + }, }) - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "ClientAuth-Server" + suffix, + name: "ClientAuth-Server", config: Config{ Certificates: []Certificate{rsaCertificate}, }, - flags: append(flags, "-require-any-client-certificate"), + flags: []string{"-require-any-client-certificate"}, }) // No session ticket support; server doesn't send NewSessionTicket. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "SessionTicketsDisabled-Client" + suffix, + tests = append(tests, testCase{ + name: "SessionTicketsDisabled-Client", config: Config{ SessionTicketsDisabled: true, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: flags, }) - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "SessionTicketsDisabled-Server" + suffix, + name: "SessionTicketsDisabled-Server", config: Config{ SessionTicketsDisabled: true, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: flags, }) // Skip ServerKeyExchange in PSK key exchange if there's no // identity hint. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "EmptyPSKHint-Client" + suffix, + tests = append(tests, testCase{ + name: "EmptyPSKHint-Client", config: Config{ CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA}, PreSharedKey: []byte("secret"), - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, "-psk", "secret"), + flags: []string{"-psk", "secret"}, }) - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "EmptyPSKHint-Server" + suffix, + name: "EmptyPSKHint-Server", config: Config{ CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA}, PreSharedKey: []byte("secret"), - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, "-psk", "secret"), + flags: []string{"-psk", "secret"}, }) if protocol == tls { + tests = append(tests, testCase{ + name: "Renegotiate-Client", + renegotiate: true, + }) // NPN on client and server; results in post-handshake message. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "NPN-Client" + suffix, + tests = append(tests, testCase{ + name: "NPN-Client", config: Config{ NextProtos: []string{"foo"}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, "-select-next-proto", "foo"), + flags: []string{"-select-next-proto", "foo"}, expectedNextProto: "foo", expectedNextProtoType: npn, }) - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "NPN-Server" + suffix, + name: "NPN-Server", config: Config{ NextProtos: []string{"bar"}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, + flags: []string{ "-advertise-npn", "\x03foo\x03bar\x03baz", - "-expect-next-proto", "bar"), + "-expect-next-proto", "bar", + }, expectedNextProto: "bar", expectedNextProtoType: npn, }) @@ -2112,146 +2202,148 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) // TODO(davidben): Add tests for when False Start doesn't trigger. // Client does False Start and negotiates NPN. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "FalseStart" + suffix, + tests = append(tests, testCase{ + name: "FalseStart", config: Config{ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, Bugs: ProtocolBugs{ - ExpectFalseStart: true, - MaxHandshakeRecordLength: maxHandshakeRecordLength, + ExpectFalseStart: true, }, }, - flags: append(flags, + flags: []string{ "-false-start", - "-select-next-proto", "foo"), + "-select-next-proto", "foo", + }, shimWritesFirst: true, resumeSession: true, }) // Client does False Start and negotiates ALPN. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "FalseStart-ALPN" + suffix, + tests = append(tests, testCase{ + name: "FalseStart-ALPN", config: Config{ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, Bugs: ProtocolBugs{ - ExpectFalseStart: true, - MaxHandshakeRecordLength: maxHandshakeRecordLength, + ExpectFalseStart: true, }, }, - flags: append(flags, + flags: []string{ "-false-start", - "-advertise-alpn", "\x03foo"), + "-advertise-alpn", "\x03foo", + }, shimWritesFirst: true, resumeSession: true, }) // Client does False Start but doesn't explicitly call // SSL_connect. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "FalseStart-Implicit" + suffix, + tests = append(tests, testCase{ + name: "FalseStart-Implicit", config: Config{ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, + flags: []string{ "-implicit-handshake", "-false-start", - "-advertise-alpn", "\x03foo"), + "-advertise-alpn", "\x03foo", + }, }) // False Start without session tickets. - testCases = append(testCases, testCase{ - name: "FalseStart-SessionTicketsDisabled" + suffix, + tests = append(tests, testCase{ + name: "FalseStart-SessionTicketsDisabled", config: Config{ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, SessionTicketsDisabled: true, Bugs: ProtocolBugs{ - ExpectFalseStart: true, - MaxHandshakeRecordLength: maxHandshakeRecordLength, + ExpectFalseStart: true, }, }, - flags: append(flags, + flags: []string{ "-false-start", "-select-next-proto", "foo", - ), + }, shimWritesFirst: true, }) // Server parses a V2ClientHello. - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "SendV2ClientHello" + suffix, + name: "SendV2ClientHello", 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, + SendV2ClientHello: true, }, }, - flags: flags, }) // Client sends a Channel ID. - testCases = append(testCases, testCase{ - protocol: protocol, - name: "ChannelID-Client" + suffix, + tests = append(tests, testCase{ + name: "ChannelID-Client", config: Config{ RequestChannelID: true, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, - "-send-channel-id", channelIDKeyFile, - ), + flags: []string{"-send-channel-id", channelIDKeyFile}, resumeSession: true, expectChannelID: true, }) // Server accepts a Channel ID. - testCases = append(testCases, testCase{ - protocol: protocol, + tests = append(tests, testCase{ testType: serverTest, - name: "ChannelID-Server" + suffix, + name: "ChannelID-Server", config: Config{ ChannelID: channelIDKey, - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, }, - flags: append(flags, + flags: []string{ "-expect-channel-id", base64.StdEncoding.EncodeToString(channelIDBytes), - ), + }, resumeSession: true, expectChannelID: true, }) } else { - testCases = append(testCases, testCase{ - protocol: protocol, - name: "SkipHelloVerifyRequest" + suffix, + tests = append(tests, testCase{ + name: "SkipHelloVerifyRequest", config: Config{ Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - SkipHelloVerifyRequest: true, + SkipHelloVerifyRequest: true, }, }, - flags: flags, }) } + + var suffix string + var flags []string + var maxHandshakeRecordLength int + if protocol == dtls { + suffix = "-DTLS" + } + if async { + suffix += "-Async" + flags = append(flags, "-async") + } else { + suffix += "-Sync" + } + if splitHandshake { + suffix += "-SplitHandshakeRecords" + maxHandshakeRecordLength = 1 + } + for _, test := range tests { + test.protocol = protocol + test.name += suffix + test.config.Bugs.MaxHandshakeRecordLength = maxHandshakeRecordLength + test.flags = append(test.flags, flags...) + testCases = append(testCases, test) + } } func addDDoSCallbackTests() { @@ -2637,8 +2729,8 @@ func addExtensionTests() { CorruptTicket: true, }, }, - resumeSession: true, - flags: []string{"-expect-session-miss"}, + resumeSession: true, + expectResumeRejected: true, }) // Resume with an oversized session id. testCases = append(testCases, testCase{ @@ -2799,7 +2891,6 @@ func addResumptionVersionTests() { testCases = append(testCases, testCase{ protocol: protocol, name: "Resume-Client-NoResume" + suffix, - flags: []string{"-expect-session-miss"}, resumeSession: true, config: Config{ MaxVersion: sessionVers.version, @@ -2811,24 +2902,21 @@ func addResumptionVersionTests() { CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, }, newSessionsOnResume: true, + expectResumeRejected: true, expectedResumeVersion: resumeVers.version, }) - var flags []string - if sessionVers.version != resumeVers.version { - flags = append(flags, "-expect-session-miss") - } testCases = append(testCases, testCase{ protocol: protocol, testType: serverTest, name: "Resume-Server" + suffix, - flags: flags, resumeSession: true, config: Config{ MaxVersion: sessionVers.version, CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, }, - expectedVersion: sessionVers.version, + expectedVersion: sessionVers.version, + expectResumeRejected: sessionVers.version != resumeVers.version, resumeConfig: &Config{ MaxVersion: resumeVers.version, CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, @@ -2857,57 +2945,50 @@ func addResumptionVersionTests() { } func addRenegotiationTests() { + // Servers cannot renegotiate. testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server", - flags: []string{"-renegotiate"}, - shimWritesFirst: true, + testType: serverTest, + name: "Renegotiate-Server-Forbidden", + renegotiate: true, + flags: []string{"-reject-peer-renegotiations"}, + shouldFail: true, + expectedError: ":NO_RENEGOTIATION:", + expectedLocalError: "remote error: no renegotiation", }) + // TODO(agl): test the renegotiation info SCSV. testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-Full", + name: "Renegotiate-Client", config: Config{ Bugs: ProtocolBugs{ - NeverResumeOnRenego: true, + FailIfResumeOnRenego: true, }, }, - flags: []string{"-renegotiate"}, - shimWritesFirst: true, + renegotiate: true, }) testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-EmptyExt", + name: "Renegotiate-Client-EmptyExt", + renegotiate: true, config: Config{ Bugs: ProtocolBugs{ EmptyRenegotiationInfo: true, }, }, - flags: []string{"-renegotiate"}, - shimWritesFirst: true, - shouldFail: true, - expectedError: ":RENEGOTIATION_MISMATCH:", + shouldFail: true, + expectedError: ":RENEGOTIATION_MISMATCH:", }) testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-BadExt", + name: "Renegotiate-Client-BadExt", + renegotiate: true, config: Config{ Bugs: ProtocolBugs{ BadRenegotiationInfo: true, }, }, - flags: []string{"-renegotiate"}, - shimWritesFirst: true, - shouldFail: true, - expectedError: ":RENEGOTIATION_MISMATCH:", - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-ClientInitiated", - renegotiate: true, + shouldFail: true, + expectedError: ":RENEGOTIATION_MISMATCH:", }) testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-ClientInitiated-NoExt", + name: "Renegotiate-Client-NoExt", renegotiate: true, config: Config{ Bugs: ProtocolBugs{ @@ -2916,75 +2997,16 @@ func addRenegotiationTests() { }, shouldFail: true, expectedError: ":UNSAFE_LEGACY_RENEGOTIATION_DISABLED:", + flags: []string{"-no-legacy-server-connect"}, }) testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-ClientInitiated-NoExt-Allowed", + name: "Renegotiate-Client-NoExt-Allowed", renegotiate: true, config: Config{ Bugs: ProtocolBugs{ NoRenegotiationInfo: true, }, }, - flags: []string{"-allow-unsafe-legacy-renegotiation"}, - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-ClientInitiated-Forbidden", - renegotiate: true, - flags: []string{"-reject-peer-renegotiations"}, - shouldFail: true, - expectedError: ":NO_RENEGOTIATION:", - expectedLocalError: "remote error: no renegotiation", - }) - // Regression test for CVE-2015-0291. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "Renegotiate-Server-NoSignatureAlgorithms", - config: Config{ - Bugs: ProtocolBugs{ - NeverResumeOnRenego: true, - NoSignatureAlgorithmsOnRenego: true, - }, - }, - flags: []string{"-renegotiate"}, - shimWritesFirst: true, - }) - // TODO(agl): test the renegotiation info SCSV. - testCases = append(testCases, testCase{ - name: "Renegotiate-Client", - renegotiate: true, - }) - testCases = append(testCases, testCase{ - name: "Renegotiate-Client-Full", - config: Config{ - Bugs: ProtocolBugs{ - NeverResumeOnRenego: true, - }, - }, - renegotiate: true, - }) - testCases = append(testCases, testCase{ - name: "Renegotiate-Client-EmptyExt", - renegotiate: true, - config: Config{ - Bugs: ProtocolBugs{ - EmptyRenegotiationInfo: true, - }, - }, - shouldFail: true, - expectedError: ":RENEGOTIATION_MISMATCH:", - }) - testCases = append(testCases, testCase{ - name: "Renegotiate-Client-BadExt", - renegotiate: true, - config: Config{ - Bugs: ProtocolBugs{ - BadRenegotiationInfo: true, - }, - }, - shouldFail: true, - expectedError: ":RENEGOTIATION_MISMATCH:", }) testCases = append(testCases, testCase{ name: "Renegotiate-Client-SwitchCiphers", @@ -3365,6 +3387,59 @@ func addExportKeyingMaterialTests() { }) } +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{ + Bugs: ProtocolBugs{ + NoExtendedMasterSecret: !hasEMS, + }, + }, + } + + if isResumption { + test.resumeSession = true + test.resumeConfig = &Config{ + Bugs: ProtocolBugs{ + NoExtendedMasterSecret: !hasEMS, + }, + } + } + + if isResumption && !hasEMS { + test.shouldFail = true + test.expectedError = "failed to get tls-unique" + } + + testCases = append(testCases, test) + } + } + } +} + func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) { defer wg.Done() @@ -3463,6 +3538,7 @@ func main() { addFastRadioPaddingTests() addDTLSRetransmitTests() addExportKeyingMaterialTests() + addTLSUniqueTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { for _, protocol := range []protocol{tls, dtls} { diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc index 25906f7..363b6f3 100644 --- a/src/ssl/test/test_config.cc +++ b/src/ssl/test/test_config.cc @@ -65,7 +65,6 @@ const Flag<bool> kBoolFlags[] = { { "-expect-session-miss", &TestConfig::expect_session_miss }, { "-expect-extended-master-secret", &TestConfig::expect_extended_master_secret }, - { "-renegotiate", &TestConfig::renegotiate }, { "-allow-unsafe-legacy-renegotiation", &TestConfig::allow_unsafe_legacy_renegotiation }, { "-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling }, @@ -81,6 +80,8 @@ const Flag<bool> kBoolFlags[] = { { "-handshake-never-done", &TestConfig::handshake_never_done }, { "-use-export-context", &TestConfig::use_export_context }, { "-reject-peer-renegotiations", &TestConfig::reject_peer_renegotiations }, + { "-no-legacy-server-connect", &TestConfig::no_legacy_server_connect }, + { "-tls-unique", &TestConfig::tls_unique }, }; const Flag<std::string> kStringFlags[] = { diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h index f107a0f..5d753c8 100644 --- a/src/ssl/test/test_config.h +++ b/src/ssl/test/test_config.h @@ -54,7 +54,6 @@ struct TestConfig { bool expect_extended_master_secret = false; std::string psk; std::string psk_identity; - bool renegotiate = false; bool allow_unsafe_legacy_renegotiation = false; std::string srtp_profiles; bool enable_ocsp_stapling = false; @@ -78,6 +77,8 @@ struct TestConfig { std::string export_context; bool use_export_context = false; bool reject_peer_renegotiations = false; + bool no_legacy_server_connect = false; + bool tls_unique = false; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config); |