diff --git a/coreconf/config.gypi b/coreconf/config.gypi index 97241c5839..55a730e36b 100644 --- a/coreconf/config.gypi +++ b/coreconf/config.gypi @@ -363,10 +363,12 @@ [ 'fuzz_tls==1', { 'cflags': [ '-Wno-unused-function', + '-Wno-unused-variable', ], 'xcode_settings': { 'OTHER_CFLAGS': [ '-Wno-unused-function', + '-Wno-unused-variable', ], }, }], diff --git a/gtests/ssl_gtest/ssl_fuzz_unittest.cc b/gtests/ssl_gtest/ssl_fuzz_unittest.cc index aa6a9cfca0..81d1691878 100644 --- a/gtests/ssl_gtest/ssl_fuzz_unittest.cc +++ b/gtests/ssl_gtest/ssl_fuzz_unittest.cc @@ -226,4 +226,101 @@ FUZZ_P(TlsConnectGeneric, BogusClientAuthSignature) { std::make_shared(kTlsHandshakeCertificateVerify)); Connect(); } + +// Check that session ticket resumption works. +FUZZ_P(TlsConnectGeneric, SessionTicketResumption) { + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + Connect(); + SendReceive(); + + Reset(); + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + ExpectResumption(RESUME_TICKET); + Connect(); + SendReceive(); +} + +class TlsSessionTicketMacDamager : public TlsExtensionFilter { + public: + TlsSessionTicketMacDamager() {} + virtual PacketFilter::Action FilterExtension(uint16_t extension_type, + const DataBuffer& input, + DataBuffer* output) { + if (extension_type != ssl_session_ticket_xtn && + extension_type != ssl_tls13_pre_shared_key_xtn) { + return KEEP; + } + + *output = input; + + // Handle everything before TLS 1.3. + if (extension_type == ssl_session_ticket_xtn) { + // Modify the last byte of the MAC. + output->data()[output->len() - 1] ^= 0xff; + } + + // Handle TLS 1.3. + if (extension_type == ssl_tls13_pre_shared_key_xtn) { + TlsParser parser(input); + + uint32_t ids_len; + EXPECT_TRUE(parser.Read(&ids_len, 2) && ids_len > 0); + + uint32_t ticket_len; + EXPECT_TRUE(parser.Read(&ticket_len, 2) && ticket_len > 0); + + // Modify the last byte of the MAC. + output->data()[2 + 2 + ticket_len - 1] ^= 0xff; + } + + return CHANGE; + } +}; + +// Check that session ticket resumption works with a bad MAC. +FUZZ_P(TlsConnectGeneric, SessionTicketResumptionBadMac) { + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + Connect(); + SendReceive(); + + Reset(); + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + ExpectResumption(RESUME_TICKET); + + client_->SetPacketFilter(std::make_shared()); + Connect(); + SendReceive(); +} + +// Check that session tickets are not encrypted. +FUZZ_P(TlsConnectGeneric, UnencryptedSessionTickets) { + ConfigureSessionCache(RESUME_TICKET, RESUME_TICKET); + + auto i1 = std::make_shared( + kTlsHandshakeNewSessionTicket); + server_->SetPacketFilter(i1); + Connect(); + + size_t offset = 4; /* lifetime */ + if (version_ == SSL_LIBRARY_VERSION_TLS_1_3) { + offset += 1 + 1 + /* ke_modes */ + 1 + 1; /* auth_modes */ + } + + offset += 2 + /* ticket length */ + 16 + /* SESS_TICKET_KEY_NAME_LEN */ + 16 + /* AES-128 IV */ + 2 + /* ciphertext length */ + 2; /* TLS_EX_SESS_TICKET_VERSION */ + + // Check the protocol version number. + uint32_t tls_version; + EXPECT_TRUE(i1->buffer().Read(offset, sizeof(version_), &tls_version)); + EXPECT_EQ(version_, static_cast(tls_version)); + + // Check the cipher suite. + uint32_t suite; + EXPECT_TRUE(i1->buffer().Read(offset + sizeof(version_), 2, &suite)); + client_->CheckCipherSuite(static_cast(suite)); +} } diff --git a/gtests/ssl_gtest/tls_agent.cc b/gtests/ssl_gtest/tls_agent.cc index 9c52822403..97aae79f43 100644 --- a/gtests/ssl_gtest/tls_agent.cc +++ b/gtests/ssl_gtest/tls_agent.cc @@ -241,6 +241,10 @@ bool TlsAgent::GetPeerChainLength(size_t* count) { return true; } +void TlsAgent::CheckCipherSuite(uint16_t cipher_suite) { + EXPECT_EQ(csinfo_.cipherSuite, cipher_suite); +} + void TlsAgent::RequestClientAuth(bool requireAuth) { EXPECT_TRUE(EnsureTlsSetup()); ASSERT_EQ(SERVER, role_); diff --git a/gtests/ssl_gtest/tls_agent.h b/gtests/ssl_gtest/tls_agent.h index 0cbfe6b566..60fad1aa98 100644 --- a/gtests/ssl_gtest/tls_agent.h +++ b/gtests/ssl_gtest/tls_agent.h @@ -163,6 +163,7 @@ class TlsAgent : public PollTarget { void ConfigNamedGroups(const std::vector& groups); void DisableECDHEServerKeyReuse(); bool GetPeerChainLength(size_t* count); + void CheckCipherSuite(uint16_t cipher_suite); const std::string& name() const { return name_; } diff --git a/gtests/ssl_gtest/tls_parser.h b/gtests/ssl_gtest/tls_parser.h index 8b6e5b9c05..d0de21bc52 100644 --- a/gtests/ssl_gtest/tls_parser.h +++ b/gtests/ssl_gtest/tls_parser.h @@ -26,6 +26,7 @@ const uint8_t kTlsApplicationDataType = 23; const uint8_t kTlsHandshakeClientHello = 1; const uint8_t kTlsHandshakeServerHello = 2; +const uint8_t kTlsHandshakeNewSessionTicket = 4; const uint8_t kTlsHandshakeHelloRetryRequest = 6; const uint8_t kTlsHandshakeEncryptedExtensions = 8; const uint8_t kTlsHandshakeCertificate = 11; diff --git a/lib/ssl/ssl3exthandle.c b/lib/ssl/ssl3exthandle.c index 5704809c3f..1a194fac85 100644 --- a/lib/ssl/ssl3exthandle.c +++ b/lib/ssl/ssl3exthandle.c @@ -1004,9 +1004,13 @@ ssl3_EncodeSessionTicket(sslSocket *ss, + sizeof(ticket->ticket_lifetime_hint) /* ticket lifetime hint */ + sizeof(ticket->flags) /* ticket flags */ + 1 + alpnSelection.len; /* npn value + length field. */ +#ifdef UNSAFE_FUZZER_MODE + padding_length = 0; +#else padding_length = AES_BLOCK_SIZE - (ciphertext_length % AES_BLOCK_SIZE); +#endif ciphertext_length += padding_length; if (SECITEM_AllocItem(NULL, &plaintext_item, ciphertext_length) == NULL) @@ -1159,6 +1163,10 @@ ssl3_EncodeSessionTicket(sslSocket *ss, /* Generate encrypted portion of ticket. */ PORT_Assert(aes_key); +#ifdef UNSAFE_FUZZER_MODE + ciphertext.len = plaintext_item.len; + PORT_Memcpy(ciphertext.data, plaintext_item.data, plaintext_item.len); +#else aes_ctx = PK11_CreateContextBySymKey(cipherMech, CKA_ENCRYPT, aes_key, &ivItem); if (!aes_ctx) goto loser; @@ -1170,10 +1178,10 @@ ssl3_EncodeSessionTicket(sslSocket *ss, PK11_DestroyContext(aes_ctx, PR_TRUE); if (rv != SECSuccess) goto loser; +#endif /* Convert ciphertext length to network order. */ - length_buf[0] = (ciphertext.len >> 8) & 0xff; - length_buf[1] = (ciphertext.len) & 0xff; + (void)ssl_EncodeUintX(ciphertext.len, 2, length_buf); /* Compute MAC. */ PORT_Assert(mac_key); @@ -1337,9 +1345,11 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data) */ if (PORT_Memcmp(enc_session_ticket.key_name, key_name, SESS_TICKET_KEY_NAME_LEN) != 0) { +#ifndef UNSAFE_FUZZER_MODE SSL_DBG(("%d: SSL[%d]: Session ticket key_name sent mismatch.", SSL_GETPID(), ss->fd)); goto no_ticket; +#endif } /* Verify the MAC on the ticket. MAC verification may also @@ -1376,9 +1386,11 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data) if (NSS_SecureMemcmp(computed_mac, enc_session_ticket.mac, computed_mac_length) != 0) { +#ifndef UNSAFE_FUZZER_MODE SSL_DBG(("%d: SSL[%d]: Session ticket MAC mismatch.", SSL_GETPID(), ss->fd)); goto no_ticket; +#endif } /* We ignore key_name for now. @@ -1392,6 +1404,12 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data) enc_session_ticket.encrypted_state.len); PORT_Assert(aes_key); +#ifdef UNSAFE_FUZZER_MODE + decrypted_state->len = enc_session_ticket.encrypted_state.len; + PORT_Memcpy(decrypted_state->data, + enc_session_ticket.encrypted_state.data, + enc_session_ticket.encrypted_state.len); +#else ivItem.data = enc_session_ticket.iv; ivItem.len = AES_BLOCK_SIZE; aes_ctx = PK11_CreateContextBySymKey(cipherMech, CKA_DECRYPT, @@ -1422,6 +1440,7 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data) if (padding_length != (PRUint32)*padding) goto no_ticket; } +#endif /* Deserialize session state. */ buffer = decrypted_state->data; @@ -1589,9 +1608,12 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data) goto no_ticket; } +#ifndef UNSAFE_FUZZER_MODE /* Done parsing. Check that all bytes have been consumed. */ - if (buffer_len != padding_length) + if (buffer_len != padding_length) { goto no_ticket; + } +#endif /* Use the ticket if it has not expired, otherwise free the allocated * memory since the ticket is of no use.