diff --git a/cmd/selfserv/selfserv.c b/cmd/selfserv/selfserv.c index 6c00d3a15d..4b1adb028f 100644 --- a/cmd/selfserv/selfserv.c +++ b/cmd/selfserv/selfserv.c @@ -1954,7 +1954,7 @@ server_main( if (enabledVersions.max < SSL_LIBRARY_VERSION_TLS_1_3) { errExit("You tried enabling 0RTT without enabling TLS 1.3!"); } - rv = SSL_SetupAntiReplay(10 * PR_USEC_PER_SEC, 7, 14); + rv = SSL_InitAntiReplay(PR_Now(), 10L * PR_USEC_PER_SEC, 7, 14); if (rv != SECSuccess) { errExit("error configuring anti-replay "); } diff --git a/fuzz/tls_client_target.cc b/fuzz/tls_client_target.cc index a5b2a2c5ff..461962c5d3 100644 --- a/fuzz/tls_client_target.cc +++ b/fuzz/tls_client_target.cc @@ -106,6 +106,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { // Probably not too important for clients. SSL_SetURL(ssl_fd, "server"); + FixTime(ssl_fd); SetSocketOptions(ssl_fd, config); EnableAllCipherSuites(ssl_fd); SetupCallbacks(ssl_fd, config.get()); diff --git a/fuzz/tls_common.cc b/fuzz/tls_common.cc index 1e66684dcd..b00ab26bf6 100644 --- a/fuzz/tls_common.cc +++ b/fuzz/tls_common.cc @@ -5,9 +5,18 @@ #include #include "ssl.h" +#include "sslexp.h" #include "tls_common.h" +static PRTime FixedTime(void*) { return 1234; } + +// Fix the time input, to avoid any time-based variation. +void FixTime(PRFileDesc* fd) { + SECStatus rv = SSL_SetTimeFunc(fd, FixedTime, nullptr); + assert(rv == SECSuccess); +} + PRStatus EnableAllProtocolVersions() { SSLVersionRange supported; diff --git a/fuzz/tls_common.h b/fuzz/tls_common.h index 8843347fa1..e53acceade 100644 --- a/fuzz/tls_common.h +++ b/fuzz/tls_common.h @@ -7,6 +7,7 @@ #include "prinit.h" +void FixTime(PRFileDesc* fd); PRStatus EnableAllProtocolVersions(); void EnableAllCipherSuites(PRFileDesc* fd); void DoHandshake(PRFileDesc* fd, bool isServer); diff --git a/fuzz/tls_server_target.cc b/fuzz/tls_server_target.cc index 0c09020776..41a55541c4 100644 --- a/fuzz/tls_server_target.cc +++ b/fuzz/tls_server_target.cc @@ -118,6 +118,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { PRFileDesc* ssl_fd = ImportFD(model.get(), fd.get()); assert(ssl_fd == fd.get()); + FixTime(ssl_fd); SetSocketOptions(ssl_fd, config); DoHandshake(ssl_fd, true); diff --git a/gtests/ssl_gtest/libssl_internals.c b/gtests/ssl_gtest/libssl_internals.c index 2a9803daab..998e4b9346 100644 --- a/gtests/ssl_gtest/libssl_internals.c +++ b/gtests/ssl_gtest/libssl_internals.c @@ -109,9 +109,10 @@ void SSLInt_PrintCipherSpecs(const char *label, PRFileDesc *fd) { } } -/* Force a timer expiry by backdating when all active timers were started. We - * could set the remaining time to 0 but then backoff would not work properly if - * we decide to test it. */ +/* DTLS timers are separate from the time that the rest of the stack uses. + * Force a timer expiry by backdating when all active timers were started. + * We could set the remaining time to 0 but then backoff would not work properly + * if we decide to test it. */ SECStatus SSLInt_ShiftDtlsTimers(PRFileDesc *fd, PRIntervalTime shift) { size_t i; sslSocket *ss = ssl_FindSocket(fd); @@ -297,10 +298,6 @@ SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group) { return groupDef->keaType; } -void SSLInt_SetTicketLifetime(uint32_t lifetime) { - ssl_ticket_lifetime = lifetime; -} - SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size) { sslSocket *ss; @@ -324,10 +321,6 @@ SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size) { return SECSuccess; } -void SSLInt_RolloverAntiReplay(void) { - tls13_AntiReplayRollover(ssl_TimeUsec()); -} - SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) { sslSocket *ss = ssl_FindSocket(fd); if (!ss) { diff --git a/gtests/ssl_gtest/libssl_internals.h b/gtests/ssl_gtest/libssl_internals.h index 8c78de28f5..b831dcafe4 100644 --- a/gtests/ssl_gtest/libssl_internals.h +++ b/gtests/ssl_gtest/libssl_internals.h @@ -40,8 +40,6 @@ SECStatus SSLInt_AdvanceReadSeqNum(PRFileDesc *fd, PRUint64 to); SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra); SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group); SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending); -void SSLInt_SetTicketLifetime(uint32_t lifetime); SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size); -void SSLInt_RolloverAntiReplay(void); #endif // ndef libssl_internals_h_ diff --git a/gtests/ssl_gtest/ssl_0rtt_unittest.cc b/gtests/ssl_gtest/ssl_0rtt_unittest.cc index 4a47592cae..863e07d1c2 100644 --- a/gtests/ssl_gtest/ssl_0rtt_unittest.cc +++ b/gtests/ssl_gtest/ssl_0rtt_unittest.cc @@ -46,7 +46,7 @@ TEST_P(TlsConnectTls13, ZeroRttServerRejectByOption) { } TEST_P(TlsConnectTls13, ZeroRttApparentReplayAfterRestart) { - // The test fixtures call SSL_SetupAntiReplay() in SetUp(). This results in + // The test fixtures call SSL_InitAntiReplay() in SetUp(). This results in // 0-RTT being rejected until at least one window passes. SetupFor0Rtt() // forces a rollover of the anti-replay filters, which clears this state. // Here, we do the setup manually here without that forced rollover. @@ -106,7 +106,7 @@ class TlsZeroRttReplayTest : public TlsConnectTls13 { SendReceive(); if (rollover) { - SSLInt_RolloverAntiReplay(); + RolloverAntiReplay(); } // Now replay that packet against the server. @@ -184,20 +184,21 @@ TEST_P(TlsConnectTls13, ZeroRttServerOnly) { CheckKeys(); } -// A small sleep after sending the ClientHello means that the ticket age that -// arrives at the server is too low. With a small tolerance for variation in -// ticket age (which is determined by the |window| parameter that is passed to -// SSL_SetupAntiReplay()), the server then rejects early data. +// Advancing time after sending the ClientHello means that the ticket age that +// arrives at the server is too low. The server then rejects early data if this +// delay exceeds half the anti-replay window. TEST_P(TlsConnectTls13, ZeroRttRejectOldTicket) { + static const PRTime kWindow = 10 * PR_USEC_PER_SEC; + EXPECT_EQ(SECSuccess, SSL_InitAntiReplay(now(), kWindow, 1, 3)); SetupForZeroRtt(); + + Reset(); + StartConnect(); client_->Set0RttEnabled(true); server_->Set0RttEnabled(true); - EXPECT_EQ(SECSuccess, SSL_SetupAntiReplay(1, 1, 3)); - SSLInt_RolloverAntiReplay(); // Make sure to flush replay state. - SSLInt_RolloverAntiReplay(); ExpectResumption(RESUME_TICKET); - ZeroRttSendReceive(true, false, []() { - PR_Sleep(PR_MillisecondsToInterval(10)); + ZeroRttSendReceive(true, false, [this]() { + AdvanceTime(1 + kWindow / 2); return true; }); Handshake(); @@ -212,13 +213,15 @@ TEST_P(TlsConnectTls13, ZeroRttRejectOldTicket) { // small tolerance for variation in ticket age and the ticket will appear to // arrive prematurely, causing the server to reject early data. TEST_P(TlsConnectTls13, ZeroRttRejectPrematureTicket) { + static const PRTime kWindow = 10 * PR_USEC_PER_SEC; + EXPECT_EQ(SECSuccess, SSL_InitAntiReplay(now(), kWindow, 1, 3)); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3); server_->Set0RttEnabled(true); StartConnect(); client_->Handshake(); // ClientHello server_->Handshake(); // ServerHello - PR_Sleep(PR_MillisecondsToInterval(10)); + AdvanceTime(1 + kWindow / 2); Handshake(); // Remainder of handshake CheckConnected(); SendReceive(); @@ -227,9 +230,6 @@ TEST_P(TlsConnectTls13, ZeroRttRejectPrematureTicket) { Reset(); client_->Set0RttEnabled(true); server_->Set0RttEnabled(true); - EXPECT_EQ(SECSuccess, SSL_SetupAntiReplay(1, 1, 3)); - SSLInt_RolloverAntiReplay(); // Make sure to flush replay state. - SSLInt_RolloverAntiReplay(); ExpectResumption(RESUME_TICKET); ExpectEarlyDataAccepted(false); StartConnect(); @@ -870,6 +870,59 @@ TEST_F(TlsConnectDatagram13, ZeroRttShortReadDtls) { CheckConnected(); } +// There are few ways in which TLS uses the clock and most of those operate on +// timescales that would be ridiculous to wait for in a test. This is the one +// test we have that uses the real clock. It tests that time passes by checking +// that a small sleep results in rejection of early data. 0-RTT has a +// configurable timer, which makes it ideal for this. +TEST_F(TlsConnectStreamTls13, TimePassesByDefault) { + // Set a tiny anti-replay window. This has to be at least 2 milliseconds to + // have any chance of being relevant as that is the smallest window that we + // can detect. Anything smaller rounds to zero. + static const unsigned int kTinyWindowMs = 5; + EXPECT_EQ(SECSuccess, SSL_InitAntiReplay( + PR_Now(), kTinyWindowMs * PR_USEC_PER_MSEC, 1, 5)); + + // Calling EnsureTlsSetup() replaces the time function on client and server, + // which we don't want, so initialize each directly. + client_->EnsureTlsSetup(); + server_->EnsureTlsSetup(); + client_->StartConnect(); // Also avoid StartConnect(). + server_->StartConnect(); + + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3); + server_->Set0RttEnabled(true); + Handshake(); + CheckConnected(); + SendReceive(); // Absorb a session ticket. + CheckKeys(); + + // Clear the first window. + PR_Sleep(PR_MillisecondsToInterval(kTinyWindowMs)); + + Reset(); + client_->EnsureTlsSetup(); + server_->EnsureTlsSetup(); + client_->StartConnect(); + server_->StartConnect(); + + // Early data is rejected by the server only if time passes for it as well. + client_->Set0RttEnabled(true); + server_->Set0RttEnabled(true); + ExpectResumption(RESUME_TICKET); + ZeroRttSendReceive(true, false, []() { + // Sleep long enough that we minimize the risk of our RTT estimation being + // duped by stutters in test execution. This is very long to allow for + // flaky and low-end hardware, especially what our CI runs on. + PR_Sleep(PR_MillisecondsToInterval(1000)); + return true; + }); + Handshake(); + ExpectEarlyDataAccepted(false); + CheckConnected(); +} + #ifndef NSS_DISABLE_TLS_1_3 INSTANTIATE_TEST_CASE_P(Tls13ZeroRttReplayTest, TlsZeroRttReplayTest, TlsConnectTestBase::kTlsVariantsAll); diff --git a/gtests/ssl_gtest/ssl_fuzz_unittest.cc b/gtests/ssl_gtest/ssl_fuzz_unittest.cc index f033b78439..bb78bc3d16 100644 --- a/gtests/ssl_gtest/ssl_fuzz_unittest.cc +++ b/gtests/ssl_gtest/ssl_fuzz_unittest.cc @@ -22,7 +22,7 @@ namespace nss_test { const uint8_t kShortEmptyFinished[8] = {0}; const uint8_t kLongEmptyFinished[128] = {0}; -class TlsFuzzTest : public ::testing::Test {}; +class TlsFuzzTest : public TlsConnectGeneric {}; // Record the application data stream. class TlsApplicationDataRecorder : public TlsRecordFilter { @@ -46,16 +46,9 @@ class TlsApplicationDataRecorder : public TlsRecordFilter { DataBuffer buffer_; }; -// Ensure that ssl_Time() returns a constant value. -FUZZ_F(TlsFuzzTest, SSL_Time_Constant) { - PRUint32 now = ssl_TimeSec(); - PR_Sleep(PR_SecondsToInterval(2)); - EXPECT_EQ(ssl_TimeSec(), now); -} - // Check that due to the deterministic PRNG we derive // the same master secret in two consecutive TLS sessions. -FUZZ_P(TlsConnectGeneric, DeterministicExporter) { +FUZZ_P(TlsFuzzTest, DeterministicExporter) { const char kLabel[] = "label"; std::vector out1(32), out2(32); @@ -95,7 +88,7 @@ FUZZ_P(TlsConnectGeneric, DeterministicExporter) { // Check that due to the deterministic RNG two consecutive // TLS sessions will have the exact same transcript. -FUZZ_P(TlsConnectGeneric, DeterministicTranscript) { +FUZZ_P(TlsFuzzTest, DeterministicTranscript) { // Make sure we have RSA blinding params. Connect(); @@ -130,9 +123,7 @@ FUZZ_P(TlsConnectGeneric, DeterministicTranscript) { // with all supported TLS versions, STREAM and DGRAM. // Check that records are NOT encrypted. // Check that records don't have a MAC. -FUZZ_P(TlsConnectGeneric, ConnectSendReceive_NullCipher) { - EnsureTlsSetup(); - +FUZZ_P(TlsFuzzTest, ConnectSendReceive_NullCipher) { // Set up app data filters. auto client_recorder = MakeTlsFilter(client_); auto server_recorder = MakeTlsFilter(server_); @@ -157,7 +148,7 @@ FUZZ_P(TlsConnectGeneric, ConnectSendReceive_NullCipher) { } // Check that an invalid Finished message doesn't abort the connection. -FUZZ_P(TlsConnectGeneric, BogusClientFinished) { +FUZZ_P(TlsFuzzTest, BogusClientFinished) { EnsureTlsSetup(); MakeTlsFilter( @@ -168,7 +159,7 @@ FUZZ_P(TlsConnectGeneric, BogusClientFinished) { } // Check that an invalid Finished message doesn't abort the connection. -FUZZ_P(TlsConnectGeneric, BogusServerFinished) { +FUZZ_P(TlsFuzzTest, BogusServerFinished) { EnsureTlsSetup(); MakeTlsFilter( @@ -179,7 +170,7 @@ FUZZ_P(TlsConnectGeneric, BogusServerFinished) { } // Check that an invalid server auth signature doesn't abort the connection. -FUZZ_P(TlsConnectGeneric, BogusServerAuthSignature) { +FUZZ_P(TlsFuzzTest, BogusServerAuthSignature) { EnsureTlsSetup(); uint8_t msg_type = version_ == SSL_LIBRARY_VERSION_TLS_1_3 ? kTlsHandshakeCertificateVerify @@ -190,7 +181,7 @@ FUZZ_P(TlsConnectGeneric, BogusServerAuthSignature) { } // Check that an invalid client auth signature doesn't abort the connection. -FUZZ_P(TlsConnectGeneric, BogusClientAuthSignature) { +FUZZ_P(TlsFuzzTest, BogusClientAuthSignature) { EnsureTlsSetup(); client_->SetupClientAuth(); server_->RequestClientAuth(true); @@ -199,7 +190,7 @@ FUZZ_P(TlsConnectGeneric, BogusClientAuthSignature) { } // Check that session ticket resumption works. -FUZZ_P(TlsConnectGeneric, SessionTicketResumption) { +FUZZ_P(TlsFuzzTest, SessionTicketResumption) { ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); Connect(); SendReceive(); @@ -212,7 +203,7 @@ FUZZ_P(TlsConnectGeneric, SessionTicketResumption) { } // Check that session tickets are not encrypted. -FUZZ_P(TlsConnectGeneric, UnencryptedSessionTickets) { +FUZZ_P(TlsFuzzTest, UnencryptedSessionTickets) { ConfigureSessionCache(RESUME_TICKET, RESUME_TICKET); auto filter = MakeTlsFilter( @@ -220,23 +211,45 @@ FUZZ_P(TlsConnectGeneric, UnencryptedSessionTickets) { Connect(); std::cerr << "ticket" << filter->buffer() << std::endl; - size_t offset = 4; /* lifetime */ + size_t offset = 4; // Skip lifetime. + if (version_ == SSL_LIBRARY_VERSION_TLS_1_3) { - offset += 4; /* ticket_age_add */ + offset += 4; // Skip ticket_age_add. uint32_t nonce_len = 0; EXPECT_TRUE(filter->buffer().Read(offset, 1, &nonce_len)); offset += 1 + nonce_len; } - offset += 2 + /* ticket length */ - 2; /* TLS_EX_SESS_TICKET_VERSION */ + + offset += 2; // Skip the ticket length. + + // This bit parses the contents of the ticket, which would ordinarily be + // encrypted. Start by checking that we have the right version. This needs + // to be updated every time that TLS_EX_SESS_TICKET_VERSION is changed. But + // we don't use the #define. That way, any time that code is updated, this + // test will fail unless it is manually checked. + uint32_t ticket_version; + EXPECT_TRUE(filter->buffer().Read(offset, 2, &ticket_version)); + EXPECT_EQ(0x010aU, ticket_version); + offset += 2; + // Check the protocol version number. uint32_t tls_version = 0; EXPECT_TRUE(filter->buffer().Read(offset, sizeof(version_), &tls_version)); EXPECT_EQ(version_, static_cast(tls_version)); + offset += sizeof(version_); // Check the cipher suite. uint32_t suite = 0; - EXPECT_TRUE(filter->buffer().Read(offset + sizeof(version_), 2, &suite)); + EXPECT_TRUE(filter->buffer().Read(offset, 2, &suite)); client_->CheckCipherSuite(static_cast(suite)); } + +INSTANTIATE_TEST_CASE_P( + FuzzStream, TlsFuzzTest, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream, + TlsConnectTestBase::kTlsVAll)); +INSTANTIATE_TEST_CASE_P( + FuzzDatagram, TlsFuzzTest, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram, + TlsConnectTestBase::kTlsV11Plus)); } diff --git a/gtests/ssl_gtest/ssl_resumption_unittest.cc b/gtests/ssl_gtest/ssl_resumption_unittest.cc index 264bde67f6..22a90f3b29 100644 --- a/gtests/ssl_gtest/ssl_resumption_unittest.cc +++ b/gtests/ssl_gtest/ssl_resumption_unittest.cc @@ -325,14 +325,17 @@ TEST_P(TlsConnectGeneric, ConnectResumeClientBothTicketServerTicketForget) { SendReceive(); } +// Tickets last two days maximum; this is a time longer than that. +static const PRTime kLongerThanTicketLifetime = + 3LL * 24 * 60 * 60 * PR_USEC_PER_SEC; + TEST_P(TlsConnectGenericResumption, ConnectWithExpiredTicketAtClient) { - SSLInt_SetTicketLifetime(1); // one second // This causes a ticket resumption. ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); Connect(); SendReceive(); - WAIT_(false, 1000); + AdvanceTime(kLongerThanTicketLifetime); Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); @@ -354,7 +357,6 @@ TEST_P(TlsConnectGenericResumption, ConnectWithExpiredTicketAtClient) { } TEST_P(TlsConnectGeneric, ConnectWithExpiredTicketAtServer) { - SSLInt_SetTicketLifetime(1); // one second // This causes a ticket resumption. ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); Connect(); @@ -373,7 +375,7 @@ TEST_P(TlsConnectGeneric, ConnectWithExpiredTicketAtServer) { EXPECT_TRUE(capture->captured()); EXPECT_LT(0U, capture->extension().len()); - WAIT_(false, 1000); // Let the ticket expire on the server. + AdvanceTime(kLongerThanTicketLifetime); Handshake(); CheckConnected(); @@ -1109,7 +1111,7 @@ TEST_P(TlsConnectGenericResumption, ReConnectAgainTicket) { ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); } -void CheckGetInfoResult(uint32_t alpnSize, uint32_t earlyDataSize, +void CheckGetInfoResult(PRTime now, uint32_t alpnSize, uint32_t earlyDataSize, ScopedCERTCertificate& cert, ScopedSSLResumptionTokenInfo& token) { ASSERT_TRUE(cert); @@ -1125,7 +1127,7 @@ void CheckGetInfoResult(uint32_t alpnSize, uint32_t earlyDataSize, ASSERT_EQ(earlyDataSize, token->maxEarlyDataSize); - ASSERT_LT(ssl_TimeUsec(), token->expirationTime); + ASSERT_LT(now, token->expirationTime); } // The client should generate a new, randomized session_id @@ -1175,7 +1177,7 @@ TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfo) { ScopedCERTCertificate cert( PK11_FindCertFromNickname(server_->name().c_str(), nullptr)); - CheckGetInfoResult(0, 0, cert, token); + CheckGetInfoResult(now(), 0, 0, cert, token); Handshake(); CheckConnected(); @@ -1183,6 +1185,56 @@ TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfo) { SendReceive(); } +TEST_P(TlsConnectGenericResumptionToken, RefuseExpiredTicketClient) { + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + Connect(); + SendReceive(); + + // Move the clock to the expiration time of the ticket. + SSLResumptionTokenInfo tokenInfo = {0}; + ScopedSSLResumptionTokenInfo token(&tokenInfo); + client_->GetTokenInfo(token); + AdvanceTime(token->expirationTime - now()); + + Reset(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + ExpectResumption(RESUME_TICKET); + + StartConnect(); + ASSERT_EQ(SECFailure, + SSL_SetResumptionToken(client_->ssl_fd(), + client_->GetResumptionToken().data(), + client_->GetResumptionToken().size())); + EXPECT_EQ(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR, PORT_GetError()); +} + +TEST_P(TlsConnectGenericResumptionToken, RefuseExpiredTicketServer) { + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + Connect(); + SendReceive(); + + Reset(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + ExpectResumption(RESUME_NONE); + + // Start the handshake and send the ClientHello. + StartConnect(); + ASSERT_EQ(SECSuccess, + SSL_SetResumptionToken(client_->ssl_fd(), + client_->GetResumptionToken().data(), + client_->GetResumptionToken().size())); + client_->Handshake(); + + // Move the clock to the expiration time of the ticket. + SSLResumptionTokenInfo tokenInfo = {0}; + ScopedSSLResumptionTokenInfo token(&tokenInfo); + client_->GetTokenInfo(token); + AdvanceTime(token->expirationTime - now()); + + Handshake(); + CheckConnected(); +} + TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfoAlpn) { EnableAlpn(); ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); @@ -1205,7 +1257,7 @@ TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfoAlpn) { ScopedCERTCertificate cert( PK11_FindCertFromNickname(server_->name().c_str(), nullptr)); - CheckGetInfoResult(1, 0, cert, token); + CheckGetInfoResult(now(), 1, 0, cert, token); Handshake(); CheckConnected(); @@ -1216,7 +1268,7 @@ TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfoAlpn) { TEST_P(TlsConnectTls13ResumptionToken, ConnectResumeGetInfoZeroRtt) { EnableAlpn(); - SSLInt_RolloverAntiReplay(); + RolloverAntiReplay(); ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); server_->Set0RttEnabled(true); Connect(); @@ -1240,7 +1292,7 @@ TEST_P(TlsConnectTls13ResumptionToken, ConnectResumeGetInfoZeroRtt) { ScopedCERTCertificate cert( PK11_FindCertFromNickname(server_->name().c_str(), nullptr)); - CheckGetInfoResult(1, 1024, cert, token); + CheckGetInfoResult(now(), 1, 1024, cert, token); ZeroRttSendReceive(true, true); Handshake(); diff --git a/gtests/ssl_gtest/tls_connect.cc b/gtests/ssl_gtest/tls_connect.cc index 596da6252e..fa3376937f 100644 --- a/gtests/ssl_gtest/tls_connect.cc +++ b/gtests/ssl_gtest/tls_connect.cc @@ -106,6 +106,10 @@ std::string VersionString(uint16_t version) { } } +// The default anti-replay window for tests. Tests that rely on a different +// value call SSL_InitAntiReplay directly. +static PRTime kAntiReplayWindow = 100 * PR_USEC_PER_SEC; + TlsConnectTestBase::TlsConnectTestBase(SSLProtocolVariant variant, uint16_t version) : variant_(variant), @@ -203,11 +207,15 @@ void TlsConnectTestBase::RestoreAlgorithmPolicy() { } } +PRTime TlsConnectTestBase::TimeFunc(void* arg) { + return *reinterpret_cast(arg); +} + void TlsConnectTestBase::SetUp() { SSL_ConfigServerSessionIDCache(1024, 0, 0, g_working_dir_path.c_str()); SSLInt_ClearSelfEncryptKey(); - SSLInt_SetTicketLifetime(30); - SSL_SetupAntiReplay(1 * PR_USEC_PER_SEC, 1, 3); + now_ = PR_Now(); + SSL_InitAntiReplay(now_, kAntiReplayWindow, 1, 3); ClearStats(); SaveAlgorithmPolicy(); Init(); @@ -282,10 +290,13 @@ void TlsConnectTestBase::EnsureTlsSetup() { : nullptr)); EXPECT_TRUE(client_->EnsureTlsSetup(client_model_ ? client_model_->ssl_fd() : nullptr)); + EXPECT_EQ(SECSuccess, SSL_SetTimeFunc(client_->ssl_fd(), + TlsConnectTestBase::TimeFunc, &now_)); + EXPECT_EQ(SECSuccess, SSL_SetTimeFunc(server_->ssl_fd(), + TlsConnectTestBase::TimeFunc, &now_)); } void TlsConnectTestBase::Handshake() { - EnsureTlsSetup(); client_->SetServerKeyBits(server_->server_key_bits()); client_->Handshake(); server_->Handshake(); @@ -302,16 +313,16 @@ void TlsConnectTestBase::EnableExtendedMasterSecret() { } void TlsConnectTestBase::Connect() { - server_->StartConnect(server_model_ ? server_model_->ssl_fd() : nullptr); - client_->StartConnect(client_model_ ? client_model_->ssl_fd() : nullptr); + StartConnect(); client_->MaybeSetResumptionToken(); Handshake(); CheckConnected(); } void TlsConnectTestBase::StartConnect() { - server_->StartConnect(server_model_ ? server_model_->ssl_fd() : nullptr); - client_->StartConnect(client_model_ ? client_model_->ssl_fd() : nullptr); + EnsureTlsSetup(); + server_->StartConnect(); + client_->StartConnect(); } void TlsConnectTestBase::ConnectWithCipherSuite(uint16_t cipher_suite) { @@ -679,8 +690,9 @@ void TlsConnectTestBase::SendReceive(size_t total) { // Do a first connection so we can do 0-RTT on the second one. void TlsConnectTestBase::SetupForZeroRtt() { + // Force rollover of the anti-replay window. // If we don't do this, then all 0-RTT attempts will be rejected. - SSLInt_RolloverAntiReplay(); + RolloverAntiReplay(); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3); @@ -792,12 +804,20 @@ void TlsConnectTestBase::ShiftDtlsTimers() { time_shift = time; } - if (time_shift == PR_INTERVAL_NO_TIMEOUT) { - return; + if (time_shift != PR_INTERVAL_NO_TIMEOUT) { + AdvanceTime(PR_IntervalToMicroseconds(time_shift)); + EXPECT_EQ(SECSuccess, + SSLInt_ShiftDtlsTimers(client_->ssl_fd(), time_shift)); + EXPECT_EQ(SECSuccess, + SSLInt_ShiftDtlsTimers(server_->ssl_fd(), time_shift)); } +} + +void TlsConnectTestBase::AdvanceTime(PRTime time_shift) { now_ += time_shift; } - EXPECT_EQ(SECSuccess, SSLInt_ShiftDtlsTimers(client_->ssl_fd(), time_shift)); - EXPECT_EQ(SECSuccess, SSLInt_ShiftDtlsTimers(server_->ssl_fd(), time_shift)); +// Advance time by a full anti-replay window. +void TlsConnectTestBase::RolloverAntiReplay() { + AdvanceTime(kAntiReplayWindow); } TlsConnectGeneric::TlsConnectGeneric() diff --git a/gtests/ssl_gtest/tls_connect.h b/gtests/ssl_gtest/tls_connect.h index 1a61376014..6d74df9728 100644 --- a/gtests/ssl_gtest/tls_connect.h +++ b/gtests/ssl_gtest/tls_connect.h @@ -48,6 +48,8 @@ class TlsConnectTestBase : public ::testing::Test { virtual void SetUp(); virtual void TearDown(); + PRTime now() const { return now_; } + // Initialize client and server. void Init(); // Clear the statistics. @@ -131,6 +133,8 @@ class TlsConnectTestBase : public ::testing::Test { // Move the DTLS timers for both endpoints to pop the next timer. void ShiftDtlsTimers(); + void AdvanceTime(PRTime time_shift); + void RolloverAntiReplay(); void SaveAlgorithmPolicy(); void RestoreAlgorithmPolicy(); @@ -164,10 +168,12 @@ class TlsConnectTestBase : public ::testing::Test { void CheckResumption(SessionResumptionMode expected); void CheckExtendedMasterSecret(); void CheckEarlyDataAccepted(); + static PRTime TimeFunc(void* arg); bool expect_extended_master_secret_; bool expect_early_data_accepted_; bool skip_version_checks_; + PRTime now_; // Track groups and make sure that there are no duplicates. class DuplicateGroupChecker { diff --git a/lib/ssl/authcert.c b/lib/ssl/authcert.c index d05b30a72c..737b4e797d 100644 --- a/lib/ssl/authcert.c +++ b/lib/ssl/authcert.c @@ -20,12 +20,12 @@ #include "sslimpl.h" /* - * This callback used by SSL to pull client sertificate upon + * This callback used by SSL to pull client certificate upon * server request */ SECStatus NSS_GetClientAuthData(void *arg, - PRFileDesc *socket, + PRFileDesc *fd, struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey) @@ -33,10 +33,14 @@ NSS_GetClientAuthData(void *arg, CERTCertificate *cert = NULL; SECKEYPrivateKey *privkey = NULL; char *chosenNickName = (char *)arg; /* CONST */ - void *proto_win = NULL; SECStatus rv = SECFailure; - proto_win = SSL_RevealPinArg(socket); + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + void *proto_win = SSL_RevealPinArg(fd); + PRTime now = ssl_Time(ss); if (chosenNickName) { cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(), @@ -64,7 +68,7 @@ NSS_GetClientAuthData(void *arg, if (!cert) continue; /* Only check unexpired certs */ - if (CERT_CheckCertValidTimes(cert, ssl_TimeUsec(), PR_TRUE) != + if (CERT_CheckCertValidTimes(cert, now, PR_TRUE) != secCertTimeValid) { CERT_DestroyCertificate(cert); continue; diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index 142c8bccf1..0386789a17 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -4841,7 +4841,8 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) * XXX If we've been called from ssl_BeginClientHandshake, then * this lookup is duplicative and wasteful. */ - sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, ss->url); + sid = ssl_LookupSID(ssl_Time(ss), &ss->sec.ci.peer, + ss->sec.ci.port, ss->peerID, ss->url); } else { sid = NULL; } @@ -8578,8 +8579,8 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length) ss->sec.ci.peer.pr_s6_addr32[2], ss->sec.ci.peer.pr_s6_addr32[3])); if (ssl_sid_lookup) { - sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sidBytes.data, - sidBytes.len, ss->dbHandle); + sid = (*ssl_sid_lookup)(ssl_Time(ss), &ss->sec.ci.peer, + sidBytes.data, sidBytes.len, ss->dbHandle); } else { errCode = SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED; goto loser; @@ -10276,7 +10277,7 @@ ssl3_HandleNewSessionTicket(sslSocket *ss, PRUint8 *b, PRUint32 length) * until it has verified the server's Finished message." See the comment in * ssl3_FinishHandshake for more details. */ - ss->ssl3.hs.newSessionTicket.received_timestamp = ssl_TimeUsec(); + ss->ssl3.hs.newSessionTicket.received_timestamp = ssl_Time(ss); if (length < 4) { (void)SSL3_SendAlert(ss, alert_fatal, decode_error); PORT_SetError(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET); @@ -11584,8 +11585,8 @@ ssl3_FillInCachedSID(sslSocket *ss, sslSessionID *sid, PK11SymKey *secret) sid->keaGroup = ssl_grp_none; } sid->sigScheme = ss->sec.signatureScheme; - sid->lastAccessTime = sid->creationTime = ssl_TimeUsec(); - sid->expirationTime = sid->creationTime + ssl3_sid_timeout * PR_USEC_PER_SEC; + sid->lastAccessTime = sid->creationTime = ssl_Time(ss); + sid->expirationTime = sid->creationTime + (ssl_ticket_lifetime * PR_USEC_PER_SEC); sid->localCert = CERT_DupCertificate(ss->sec.localCert); if (ss->sec.isServer) { sid->namedCurve = ss->sec.serverCert->namedCurve; diff --git a/lib/ssl/ssl3exthandle.c b/lib/ssl/ssl3exthandle.c index e25a8f887f..f4b50f8a70 100644 --- a/lib/ssl/ssl3exthandle.c +++ b/lib/ssl/ssl3exthandle.c @@ -246,7 +246,7 @@ ssl3_ClientSendSessionTicketXtn(const sslSocket *ss, TLSExtensionData *xtnData, session_ticket = &sid->u.ssl3.locked.sessionTicket; if (session_ticket->ticket.data && (xtnData->ticketTimestampVerified || - ssl_TicketTimeValid(session_ticket))) { + ssl_TicketTimeValid(ss, session_ticket))) { xtnData->ticketTimestampVerified = PR_FALSE; @@ -608,7 +608,6 @@ ssl3_ClientHandleStatusRequestXtn(const sslSocket *ss, TLSExtensionData *xtnData return SECSuccess; } -PRUint32 ssl_ticket_lifetime = 2 * 24 * 60 * 60; /* 2 days in seconds */ #define TLS_EX_SESS_TICKET_VERSION (0x010a) /* @@ -742,7 +741,7 @@ ssl3_EncodeSessionTicket(sslSocket *ss, const NewSessionTicket *ticket, } /* timestamp */ - now = ssl_TimeUsec(); + now = ssl_Time(ss); PORT_Assert(sizeof(now) == 8); rv = sslBuffer_AppendNumber(&plaintext, now, 8); if (rv != SECSuccess) @@ -797,7 +796,7 @@ ssl3_EncodeSessionTicket(sslSocket *ss, const NewSessionTicket *ticket, * This is compared to the expected time, which should differ only as a * result of clock errors or errors in the RTT estimate. */ - ticketAgeBaseline = (ssl_TimeUsec() - ss->ssl3.hs.serverHelloTime) / PR_USEC_PER_MSEC; + ticketAgeBaseline = (ssl_Time(ss) - ss->ssl3.hs.serverHelloTime) / PR_USEC_PER_MSEC; ticketAgeBaseline -= ticket->ticket_age_add; rv = sslBuffer_AppendNumber(&plaintext, ticketAgeBaseline, 4); if (rv != SECSuccess) @@ -1242,8 +1241,8 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket, } /* Use the ticket if it is valid and unexpired. */ - if (parsedTicket.timestamp + ssl_ticket_lifetime * PR_USEC_PER_SEC > - ssl_TimeUsec()) { + PRTime end = parsedTicket.timestamp + (ssl_ticket_lifetime * PR_USEC_PER_SEC); + if (end > ssl_Time(ss)) { rv = ssl_CreateSIDFromTicket(ss, ticket, &parsedTicket, &sid); if (rv != SECSuccess) { diff --git a/lib/ssl/sslcon.c b/lib/ssl/sslcon.c index 603da3e158..ed733bffb8 100644 --- a/lib/ssl/sslcon.c +++ b/lib/ssl/sslcon.c @@ -155,8 +155,8 @@ ssl_BeginClientHandshake(sslSocket *ss) SSL_TRC(3, ("%d: SSL[%d]: using external token", SSL_GETPID(), ss->fd)); } else if (!ss->opt.noCache) { /* Try to find server in our session-id cache */ - sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, - ss->url); + sid = ssl_LookupSID(ssl_Time(ss), &ss->sec.ci.peer, + ss->sec.ci.port, ss->peerID, ss->url); } if (sid) { @@ -170,25 +170,15 @@ ssl_BeginClientHandshake(sslSocket *ss) } } if (!sid) { - sid = PORT_ZNew(sslSessionID); + sid = ssl3_NewSessionID(ss, PR_FALSE); if (!sid) { goto loser; } - sid->references = 1; - sid->cached = never_cached; - sid->addr = ss->sec.ci.peer; - sid->port = ss->sec.ci.port; - if (ss->peerID != NULL) { - sid->peerID = PORT_Strdup(ss->peerID); - } - if (ss->url != NULL) { - sid->urlSvrName = PORT_Strdup(ss->url); - } + /* This session is a dummy, which we don't want to resume. */ + sid->u.ssl3.keys.resumable = PR_FALSE; } ss->sec.ci.sid = sid; - PORT_Assert(sid != NULL); - ss->gs.state = GS_INIT; ss->handshake = ssl_GatherRecord1stHandshake; diff --git a/lib/ssl/sslexp.h b/lib/ssl/sslexp.h index d8f050ea57..a1ef527365 100644 --- a/lib/ssl/sslexp.h +++ b/lib/ssl/sslexp.h @@ -159,7 +159,7 @@ typedef SECStatus(PR_CALLBACK *SSLExtensionHandler)( handler, handlerArg)) /* - * Setup the anti-replay buffer for supporting 0-RTT in TLS 1.3 on servers. + * Initialize the anti-replay buffer for supporting 0-RTT in TLS 1.3 on servers. * * To use 0-RTT on a server, you must call this function. Failing to call this * function will result in all 0-RTT being rejected. Connections will complete, @@ -181,11 +181,11 @@ typedef SECStatus(PR_CALLBACK *SSLExtensionHandler)( * The first tuning parameter to consider is |window|, which determines the * window over which ClientHello messages will be tracked. This also causes * early data to be rejected if a ClientHello contains a ticket age parameter - * that is outside of this window (see Section 4.2.10.4 of - * draft-ietf-tls-tls13-20 for details). Set |window| to account for any - * potential sources of clock error. |window| is the entire width of the - * window, which is symmetrical. Therefore to allow 5 seconds of clock error in - * both directions, set the value to 10 seconds (i.e., 10 * PR_USEC_PER_SEC). + * that is outside of this window (see Section 8.3 of RFC 8446 for details). + * Set |window| to account for any potential sources of clock error. |window| + * is the entire width of the window, which is symmetrical. Therefore to allow + * 5 seconds of clock error in both directions, set the value to 10 seconds + * (i.e., 10 * PR_USEC_PER_SEC). * * After calling this function, early data will be rejected until |window| * elapses. This prevents replay across crashes and restarts. Only call this @@ -219,10 +219,11 @@ typedef SECStatus(PR_CALLBACK *SSLExtensionHandler)( * Early data can be replayed at least once with every server instance that will * accept tickets that are encrypted with the same key. */ -#define SSL_SetupAntiReplay(window, k, bits) \ - SSL_EXPERIMENTAL_API("SSL_SetupAntiReplay", \ - (PRTime _window, unsigned int _k, unsigned int _bits), \ - (window, k, bits)) +#define SSL_InitAntiReplay(now, window, k, bits) \ + SSL_EXPERIMENTAL_API("SSL_InitAntiReplay", \ + (PRTime _now, PRTime _window, \ + unsigned int _k, unsigned int _bits), \ + (now, window, k, bits)) /* * This function allows a server application to generate a session ticket that @@ -723,8 +724,20 @@ typedef struct SSLAeadContextStr SSLAeadContext; hsHash, hsHashLen, label, labelLen, \ mech, keySize, keyp)) +/* SSL_SetTimeFunc overrides the default time function (PR_Now()) and provides + * an alternative source of time for the socket. This is used in testing, and in + * applications that need better control over how the clock is accessed. Set the + * function to NULL to use PR_Now().*/ +typedef PRTime(PR_CALLBACK *SSLTimeFunc)(void *arg); + +#define SSL_SetTimeFunc(fd, f, arg) \ + SSL_EXPERIMENTAL_API("SSL_SetTimeFunc", \ + (PRFileDesc * _fd, SSLTimeFunc _f, void *_arg), \ + (fd, f, arg)) + /* Deprecated experimental APIs */ #define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API +#define SSL_SetupAntiReplay(a, b, c) SSL_DEPRECATED_EXPERIMENTAL_API SEC_END_PROTOS diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index a9b7662908..8c472580bc 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -183,10 +183,11 @@ typedef SECStatus (*sslHandshakeFunc)(sslSocket *ss); void ssl_CacheSessionID(sslSocket *ss); void ssl_UncacheSessionID(sslSocket *ss); -void ssl_ServerCacheSessionID(sslSessionID *sid); +void ssl_ServerCacheSessionID(sslSessionID *sid, PRTime creationTime); void ssl_ServerUncacheSessionID(sslSessionID *sid); -typedef sslSessionID *(*sslSessionIDLookupFunc)(const PRIPv6Addr *addr, +typedef sslSessionID *(*sslSessionIDLookupFunc)(PRTime ssl_now, + const PRIPv6Addr *addr, unsigned char *sid, unsigned int sidLen, CERTCertDBHandle *dbHandle); @@ -946,6 +947,10 @@ struct sslSocketStr { /* Enabled version range */ SSLVersionRange vrange; + /* A function that returns the current time. */ + SSLTimeFunc now; + void *nowArg; + /* State flags */ unsigned long clientAuthRequested; unsigned long delayDisabled; /* Nagle delay disabled */ @@ -1104,8 +1109,7 @@ extern char ssl_trace; extern FILE *ssl_trace_iob; extern FILE *ssl_keylog_iob; extern PZLock *ssl_keylog_lock; -extern PRUint32 ssl3_sid_timeout; -extern PRUint32 ssl_ticket_lifetime; +static const PRUint32 ssl_ticket_lifetime = 2 * 24 * 60 * 60; // 2 days. extern const char *const ssl3_cipherName[]; @@ -1195,8 +1199,9 @@ extern SECStatus ssl3_InitPendingCipherSpecs(sslSocket *ss, PK11SymKey *secret, PRBool derive); extern void ssl_DestroyKeyMaterial(ssl3KeyMaterial *keyMaterial); extern sslSessionID *ssl3_NewSessionID(sslSocket *ss, PRBool is_server); -extern sslSessionID *ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, - const char *peerID, const char *urlSvrName); +extern sslSessionID *ssl_LookupSID(PRTime now, const PRIPv6Addr *addr, + PRUint16 port, const char *peerID, + const char *urlSvrName); extern void ssl_FreeSID(sslSessionID *sid); extern void ssl_DestroySID(sslSessionID *sid, PRBool freeIt); extern sslSessionID *ssl_ReferenceSID(sslSessionID *sid); @@ -1606,8 +1611,8 @@ PRBool ssl3_config_match(const ssl3CipherSuiteCfg *suite, PRUint8 policy, /* calls for accessing wrapping keys across processes. */ extern SECStatus -ssl_GetWrappingKey(unsigned int symWrapMechIndex, unsigned int wrapKeyIndex, - SSLWrappedSymWrappingKey *wswk); +ssl_GetWrappingKey(unsigned int symWrapMechIndex, + unsigned int wrapKeyIndex, SSLWrappedSymWrappingKey *wswk); /* The caller passes in the new value it wants * to set. This code tests the wrapped sym key entry in the file on disk. @@ -1719,13 +1724,8 @@ extern void ssl3_CheckCipherSuiteOrderConsistency(); extern int ssl_MapLowLevelError(int hiLevelError); -extern PRUint32 ssl_TimeSec(void); -#ifdef UNSAFE_FUZZER_MODE -#define ssl_TimeUsec() ((PRTime)12345678) -#else -#define ssl_TimeUsec() (PR_Now()) -#endif -extern PRBool ssl_TicketTimeValid(const NewSessionTicket *ticket); +PRTime ssl_Time(const sslSocket *ss); +PRBool ssl_TicketTimeValid(const sslSocket *ss, const NewSessionTicket *ticket); extern void SSL_AtomicIncrementLong(long *x); @@ -1763,7 +1763,7 @@ PK11SymKey *ssl_unwrapSymKey(PK11SymKey *wrapKey, CK_MECHANISM_TYPE target, CK_ATTRIBUTE_TYPE operation, int keySize, CK_FLAGS keyFlags, void *pinArg); -/* Remove when stable. */ +/* Experimental APIs. Remove when stable. */ SECStatus SSLExp_SetResumptionTokenCallback(PRFileDesc *fd, SSLResumptionTokenCallback cb, @@ -1815,6 +1815,8 @@ SSLExp_HkdfExpandLabelWithMech(PRUint16 version, PRUint16 cipherSuite, PK11SymKe CK_MECHANISM_TYPE mech, unsigned int keySize, PK11SymKey **keyp); +SECStatus SSLExp_SetTimeFunc(PRFileDesc *fd, SSLTimeFunc f, void *arg); + SEC_END_PROTOS #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS) diff --git a/lib/ssl/sslnonce.c b/lib/ssl/sslnonce.c index f8fb5d50f1..b7b5b7fe51 100644 --- a/lib/ssl/sslnonce.c +++ b/lib/ssl/sslnonce.c @@ -20,8 +20,6 @@ #include #endif -PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */ - static sslSessionID *cache = NULL; static PZLock *cacheLock = NULL; @@ -259,30 +257,28 @@ ssl_ReferenceSID(sslSessionID *sid) */ sslSessionID * -ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, +ssl_LookupSID(PRTime now, const PRIPv6Addr *addr, PRUint16 port, const char *peerID, const char *urlSvrName) { sslSessionID **sidp; sslSessionID *sid; - PRUint32 now; if (!urlSvrName) return NULL; - now = ssl_TimeSec(); LOCK_CACHE; sidp = &cache; while ((sid = *sidp) != 0) { PORT_Assert(sid->cached == in_client_cache); PORT_Assert(sid->references >= 1); - SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid)); + SSL_TRC(8, ("SSL: lookup: sid=0x%x", sid)); if (sid->expirationTime < now) { /* ** This session-id timed out. ** Don't even care who it belongs to, blow it out of our cache. */ - SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d", + SSL_TRC(7, ("SSL: lookup, throwing sid out, age=%d refs=%d", now - sid->creationTime, sid->references)); *sidp = sid->next; /* delink it from the list. */ @@ -316,7 +312,7 @@ ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, ** Although this is static, it is called via ss->sec.cache(). */ static void -CacheSID(sslSessionID *sid) +CacheSID(sslSessionID *sid, PRTime creationTime) { PORT_Assert(sid); PORT_Assert(sid->cached == never_cached); @@ -353,11 +349,16 @@ CacheSID(sslSessionID *sid) if (!sid->u.ssl3.lock) { return; } - PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0); - if (!sid->creationTime) - sid->lastAccessTime = sid->creationTime = ssl_TimeUsec(); - if (!sid->expirationTime) - sid->expirationTime = sid->creationTime + ssl3_sid_timeout * PR_USEC_PER_SEC; + PORT_Assert(sid->creationTime != 0); + if (!sid->creationTime) { + sid->lastAccessTime = sid->creationTime = creationTime; + } + PORT_Assert(sid->expirationTime != 0); + if (!sid->expirationTime) { + sid->expirationTime = sid->creationTime + (PR_MIN(ssl_ticket_lifetime, + sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint) * + PR_USEC_PER_SEC); + } /* * Put sid into the cache. Bump reference count to indicate that @@ -726,13 +727,13 @@ ssl_IsResumptionTokenUsable(sslSocket *ss, sslSessionID *sid) if (ticket->ticket_lifetime_hint != 0) { endTime = ticket->received_timestamp + (PRTime)(ticket->ticket_lifetime_hint * PR_USEC_PER_SEC); - if (endTime < ssl_TimeUsec()) { + if (endTime <= ssl_Time(ss)) { return PR_FALSE; } } // Check that the session entry didn't expire. - if (sid->expirationTime < ssl_TimeUsec()) { + if (sid->expirationTime < ssl_Time(ss)) { return PR_FALSE; } @@ -1087,10 +1088,12 @@ ssl_CacheExternalToken(sslSocket *ss) } if (!sid->creationTime) { - sid->lastAccessTime = sid->creationTime = ssl_TimeUsec(); + sid->lastAccessTime = sid->creationTime = ssl_Time(ss); } if (!sid->expirationTime) { - sid->expirationTime = sid->creationTime + ssl3_sid_timeout; + sid->expirationTime = sid->creationTime + (PR_MIN(ssl_ticket_lifetime, + sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint) * + PR_USEC_PER_SEC); } sslBuffer encodedToken = SSL_BUFFER_EMPTY; @@ -1129,11 +1132,11 @@ ssl_CacheSessionID(sslSocket *ss) PORT_Assert(!ss->resumptionTokenCallback); if (sec->isServer) { - ssl_ServerCacheSessionID(sec->ci.sid); + ssl_ServerCacheSessionID(sec->ci.sid, ssl_Time(ss)); return; } - CacheSID(sec->ci.sid); + CacheSID(sec->ci.sid, ssl_Time(ss)); } void @@ -1165,32 +1168,8 @@ SSL_ClearSessionCache(void) UNLOCK_CACHE; } -/* returns an unsigned int containing the number of seconds in PR_Now() */ -PRUint32 -ssl_TimeSec(void) -{ -#ifdef UNSAFE_FUZZER_MODE - return 1234; -#endif - - PRUint32 myTime; -#if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS) - myTime = time(NULL); /* accurate until the year 2038. */ -#else - /* portable, but possibly slower */ - PRTime now; - PRInt64 ll; - - now = PR_Now(); - LL_I2L(ll, 1000000L); - LL_DIV(now, now, ll); - LL_L2UI(myTime, now); -#endif - return myTime; -} - PRBool -ssl_TicketTimeValid(const NewSessionTicket *ticket) +ssl_TicketTimeValid(const sslSocket *ss, const NewSessionTicket *ticket) { PRTime endTime; @@ -1200,7 +1179,7 @@ ssl_TicketTimeValid(const NewSessionTicket *ticket) endTime = ticket->received_timestamp + (PRTime)(ticket->ticket_lifetime_hint * PR_USEC_PER_SEC); - return endTime > ssl_TimeUsec(); + return endTime > ssl_Time(ss); } void diff --git a/lib/ssl/sslsnce.c b/lib/ssl/sslsnce.c index d7abb3dc32..75402a780a 100644 --- a/lib/ssl/sslsnce.c +++ b/lib/ssl/sslsnce.c @@ -276,14 +276,24 @@ typedef struct inheritanceStr inheritance; /************************************************************************/ +/* This is used to set locking times for the cache. It is not used to set the + * PRTime attributes of sessions, which are driven by ss->now(). */ +static PRUint32 +ssl_CacheNow() +{ + return PR_Now() / PR_USEC_PER_SEC; +} + static PRUint32 LockSidCacheLock(sidCacheLock *lock, PRUint32 now) { SECStatus rv = sslMutex_Lock(&lock->mutex); if (rv != SECSuccess) return 0; - if (!now) - now = ssl_TimeSec(); + if (!now) { + now = ssl_CacheNow(); + } + lock->timeStamp = now; lock->pid = myPid; return now; @@ -299,7 +309,7 @@ UnlockSidCacheLock(sidCacheLock *lock) return rv; } -/* returns the value of ssl_TimeSec on success, zero on failure. */ +/* Returns non-zero |now| or ssl_CacheNow() on success, zero on failure. */ static PRUint32 LockSet(cacheDesc *cache, PRUint32 set, PRUint32 now) { @@ -630,9 +640,12 @@ FindSID(cacheDesc *cache, PRUint32 setNum, PRUint32 now, /* This is the primary function for finding entries in the server's sid cache. * Although it is static, this function is called via the global function * pointer ssl_sid_lookup. + * + * sslNow is the time that the calling socket understands, which might be + * different than what the cache uses to maintain its locks. */ static sslSessionID * -ServerSessionIDLookup(const PRIPv6Addr *addr, +ServerSessionIDLookup(PRTime sslNow, const PRIPv6Addr *addr, unsigned char *sessionID, unsigned int sessionIDLength, CERTCertDBHandle *dbHandle) @@ -712,7 +725,7 @@ ServerSessionIDLookup(const PRIPv6Addr *addr, } } if (psce) { - psce->lastAccessTime = now; + psce->lastAccessTime = sslNow; sce = *psce; /* grab a copy while holding the lock */ } } @@ -730,7 +743,7 @@ ServerSessionIDLookup(const PRIPv6Addr *addr, ** Place a sid into the cache, if it isn't already there. */ void -ssl_ServerCacheSessionID(sslSessionID *sid) +ssl_ServerCacheSessionID(sslSessionID *sid, PRTime creationTime) { PORT_Assert(sid); @@ -748,7 +761,7 @@ ssl_ServerCacheSessionID(sslSessionID *sid) PORT_Assert(sid->creationTime != 0); if (!sid->creationTime) - sid->lastAccessTime = sid->creationTime = ssl_TimeUsec(); + sid->lastAccessTime = sid->creationTime = creationTime; /* override caller's expiration time, which uses client timeout * duration, not server timeout duration. */ @@ -1089,7 +1102,7 @@ InitCache(cacheDesc *cache, int maxCacheEntries, int maxCertCacheEntries, cache->srvNameCacheData = (srvNameCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->srvNameCacheData); /* initialize the locks */ - init_time = ssl_TimeSec(); + init_time = ssl_CacheNow(); pLock = cache->sidCacheLocks; for (locks_to_initialize = cache->numSIDCacheLocks + 3; locks_initialized < locks_to_initialize; @@ -1518,7 +1531,7 @@ LockPoller(void *arg) if (sharedCache->stopPolling) break; - now = ssl_TimeSec(); + now = ssl_CacheNow(); then = now - expiration; for (pLock = cache->sidCacheLocks, locks_polled = 0; locks_to_poll > locks_polled && !sharedCache->stopPolling; diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c index 96b963bc26..802ef6714e 100644 --- a/lib/ssl/sslsock.c +++ b/lib/ssl/sslsock.c @@ -277,6 +277,8 @@ ssl_DupSocket(sslSocket *os) goto loser; } ss->vrange = os->vrange; + ss->now = os->now; + ss->nowArg = os->nowArg; ss->peerID = !os->peerID ? NULL : PORT_Strdup(os->peerID); ss->url = !os->url ? NULL : PORT_Strdup(os->url); @@ -2215,6 +2217,8 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd) ss->opt = sm->opt; ss->vrange = sm->vrange; + ss->now = sm->now; + ss->nowArg = sm->nowArg; PORT_Memcpy(ss->cipherSuites, sm->cipherSuites, sizeof sm->cipherSuites); PORT_Memcpy(ss->ssl3.dtlsSRTPCiphers, sm->ssl3.dtlsSRTPCiphers, sizeof(PRUint16) * sm->ssl3.dtlsSRTPCipherCount); @@ -3902,6 +3906,15 @@ ssl_FreeEphemeralKeyPairs(sslSocket *ss) } } +PRTime +ssl_Time(const sslSocket *ss) +{ + if (!ss->now) { + return PR_Now(); + } + return ss->now(ss->nowArg); +} + /* ** Create a newsocket structure for a file descriptor. */ @@ -3917,7 +3930,7 @@ ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant) makeLocks = PR_TRUE; /* Make a new socket and get it ready */ - ss = (sslSocket *)PORT_ZAlloc(sizeof(sslSocket)); + ss = PORT_ZNew(sslSocket); if (!ss) { return NULL; } @@ -4051,6 +4064,7 @@ struct { EXP(GetExtensionSupport), EXP(GetResumptionTokenInfo), EXP(HelloRetryRequestCallback), + EXP(InitAntiReplay), EXP(InstallExtensionHooks), EXP(HkdfExtract), EXP(HkdfExpandLabel), @@ -4066,7 +4080,7 @@ struct { EXP(SetMaxEarlyDataSize), EXP(SetResumptionTokenCallback), EXP(SetResumptionToken), - EXP(SetupAntiReplay), + EXP(SetTimeFunc), #endif { "", NULL } }; @@ -4102,6 +4116,21 @@ ssl_ClearPRCList(PRCList *list, void (*f)(void *)) } } +SECStatus +SSLExp_SetTimeFunc(PRFileDesc *fd, SSLTimeFunc f, void *arg) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetTimeFunc", + SSL_GETPID(), fd)); + return SECFailure; + } + ss->now = f; + ss->nowArg = arg; + return SECSuccess; +} + /* Experimental APIs for session cache handling. */ SECStatus @@ -4185,7 +4214,7 @@ SSLExp_SetResumptionToken(PRFileDesc *fd, const PRUint8 *token, /* Use the sid->cached as marker that this is from an external cache and * we don't have to look up anything in the NSS internal cache. */ sid->cached = in_external_cache; - sid->lastAccessTime = ssl_TimeSec(); + sid->lastAccessTime = ssl_Time(ss); ss->sec.ci.sid = sid; diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c index 19397d5c3a..16f0b35bbb 100644 --- a/lib/ssl/tls13con.c +++ b/lib/ssl/tls13con.c @@ -479,7 +479,7 @@ tls13_SetupClientHello(sslSocket *ss, sslClientHelloType chType) session_ticket = &sid->u.ssl3.locked.sessionTicket; PORT_Assert(session_ticket && session_ticket->ticket.data); - if (ssl_TicketTimeValid(session_ticket)) { + if (ssl_TicketTimeValid(ss, session_ticket)) { ss->statelessResume = PR_TRUE; } @@ -2724,7 +2724,7 @@ tls13_SendServerHelloSequence(sslSocket *ss) } } - ss->ssl3.hs.serverHelloTime = ssl_TimeUsec(); + ss->ssl3.hs.serverHelloTime = ssl_Time(ss); return SECSuccess; } @@ -4981,7 +4981,7 @@ tls13_HandleNewSessionTicket(sslSocket *ss, PRUint8 *b, PRUint32 length) return SECFailure; } - ticket.received_timestamp = ssl_TimeUsec(); + ticket.received_timestamp = ssl_Time(ss); rv = ssl3_ConsumeHandshakeNumber(ss, &ticket.ticket_lifetime_hint, 4, &b, &length); if (rv != SECSuccess) { diff --git a/lib/ssl/tls13con.h b/lib/ssl/tls13con.h index 668d9aad2b..d634f4b847 100644 --- a/lib/ssl/tls13con.h +++ b/lib/ssl/tls13con.h @@ -117,8 +117,8 @@ PRBool tls13_ShouldRequestClientAuth(sslSocket *ss); PRBool tls13_IsReplay(const sslSocket *ss, const sslSessionID *sid); void tls13_AntiReplayRollover(PRTime now); -SECStatus SSLExp_SetupAntiReplay(PRTime window, unsigned int k, - unsigned int bits); +SECStatus SSLExp_InitAntiReplay(PRTime now, PRTime window, unsigned int k, + unsigned int bits); SECStatus SSLExp_HelloRetryRequestCallback(PRFileDesc *fd, SSLHelloRetryRequestCallback cb, diff --git a/lib/ssl/tls13exthandle.c b/lib/ssl/tls13exthandle.c index 9def16d413..9d7c50efbd 100644 --- a/lib/ssl/tls13exthandle.c +++ b/lib/ssl/tls13exthandle.c @@ -448,7 +448,7 @@ tls13_ClientSendPreSharedKeyXtn(const sslSocket *ss, TLSExtensionData *xtnData, goto loser; /* Obfuscated age. */ - age = ssl_TimeUsec() - session_ticket->received_timestamp; + age = ssl_Time(ss) - session_ticket->received_timestamp; age /= PR_USEC_PER_MSEC; age += session_ticket->ticket_age_add; rv = sslBuffer_AppendNumber(buf, age, 4); diff --git a/lib/ssl/tls13replay.c b/lib/ssl/tls13replay.c index b090f9bca7..2770be761e 100644 --- a/lib/ssl/tls13replay.c +++ b/lib/ssl/tls13replay.c @@ -109,7 +109,7 @@ tls13_AntiReplayKeyGen() * memory barrier between the setup and use of this function. */ SECStatus -SSLExp_SetupAntiReplay(PRTime window, unsigned int k, unsigned int bits) +SSLExp_InitAntiReplay(PRTime now, PRTime window, unsigned int k, unsigned int bits) { SECStatus rv; @@ -153,7 +153,7 @@ SSLExp_SetupAntiReplay(PRTime window, unsigned int k, unsigned int bits) sslBloom_Fill(&ssl_anti_replay.filters[1]); ssl_anti_replay.current = 0; - ssl_anti_replay.nextUpdate = ssl_TimeUsec() + window; + ssl_anti_replay.nextUpdate = now + window; ssl_anti_replay.window = window; return SECSuccess; @@ -162,29 +162,15 @@ SSLExp_SetupAntiReplay(PRTime window, unsigned int k, unsigned int bits) return SECFailure; } -/* This is exposed to tests. Though it could, this doesn't take the lock on the - * basis that those tests use thread confinement. */ -void -tls13_AntiReplayRollover(PRTime now) -{ - ssl_anti_replay.current ^= 1; - ssl_anti_replay.nextUpdate = now + ssl_anti_replay.window; - sslBloom_Zero(ssl_anti_replay.filters + ssl_anti_replay.current); -} - static void -tls13_AntiReplayUpdate() +tls13_AntiReplayUpdate(PRTime now) { - PRTime now; - PR_ASSERT_CURRENT_THREAD_IN_MONITOR(ssl_anti_replay.lock); - - now = ssl_TimeUsec(); - if (now < ssl_anti_replay.nextUpdate) { - return; + if (now >= ssl_anti_replay.nextUpdate) { + ssl_anti_replay.current ^= 1; + ssl_anti_replay.nextUpdate = now + ssl_anti_replay.window; + sslBloom_Zero(ssl_anti_replay.filters + ssl_anti_replay.current); } - - tls13_AntiReplayRollover(now); } PRBool @@ -197,7 +183,7 @@ tls13_InWindow(const sslSocket *ss, const sslSessionID *sid) * calculate. The result should be close to zero. timeDelta is signed to * make the comparisons below easier. */ timeDelta = ss->xtnData.ticketAge - - ((ssl_TimeUsec() - sid->creationTime) / PR_USEC_PER_MSEC); + ((ssl_Time(ss) - sid->creationTime) / PR_USEC_PER_MSEC); /* Only allow the time delta to be at most half of our window. This is * symmetrical, though it doesn't need to be; this assumes that clock errors @@ -221,7 +207,7 @@ tls13_InWindow(const sslSocket *ss, const sslSessionID *sid) * prevent the same 0-RTT attempt from being accepted during window 1 and * later window 3. */ - return PR_ABS(timeDelta) < (ssl_anti_replay.window / 2); + return PR_ABS(timeDelta) < (ssl_anti_replay.window / (PR_USEC_PER_MSEC * 2)); } /* Checks for a duplicate in the two filters we have. Performs maintenance on @@ -262,13 +248,12 @@ tls13_IsReplay(const sslSocket *ss, const sslSessionID *sid) } PZ_EnterMonitor(ssl_anti_replay.lock); - tls13_AntiReplayUpdate(); + tls13_AntiReplayUpdate(ssl_Time(ss)); index = ssl_anti_replay.current; replay = sslBloom_Add(&ssl_anti_replay.filters[index], buf); if (!replay) { - replay = sslBloom_Check(&ssl_anti_replay.filters[index ^ 1], - buf); + replay = sslBloom_Check(&ssl_anti_replay.filters[index ^ 1], buf); } PZ_ExitMonitor(ssl_anti_replay.lock);