summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/selfserv/selfserv.c2
-rw-r--r--fuzz/tls_client_target.cc1
-rw-r--r--fuzz/tls_common.cc9
-rw-r--r--fuzz/tls_common.h1
-rw-r--r--fuzz/tls_server_target.cc1
-rw-r--r--gtests/ssl_gtest/libssl_internals.c15
-rw-r--r--gtests/ssl_gtest/libssl_internals.h2
-rw-r--r--gtests/ssl_gtest/ssl_0rtt_unittest.cc83
-rw-r--r--gtests/ssl_gtest/ssl_fuzz_unittest.cc61
-rw-r--r--gtests/ssl_gtest/ssl_resumption_unittest.cc72
-rw-r--r--gtests/ssl_gtest/tls_connect.cc44
-rw-r--r--gtests/ssl_gtest/tls_connect.h6
-rw-r--r--lib/ssl/authcert.c14
-rw-r--r--lib/ssl/ssl3con.c13
-rw-r--r--lib/ssl/ssl3exthandle.c11
-rw-r--r--lib/ssl/sslcon.c20
-rw-r--r--lib/ssl/sslexp.h33
-rw-r--r--lib/ssl/sslimpl.h34
-rw-r--r--lib/ssl/sslnonce.c69
-rw-r--r--lib/ssl/sslsnce.c31
-rw-r--r--lib/ssl/sslsock.c35
-rw-r--r--lib/ssl/tls13con.c6
-rw-r--r--lib/ssl/tls13con.h4
-rw-r--r--lib/ssl/tls13exthandle.c2
-rw-r--r--lib/ssl/tls13replay.c37
25 files changed, 384 insertions, 222 deletions
diff --git a/cmd/selfserv/selfserv.c b/cmd/selfserv/selfserv.c
index 6c00d3a15..4b1adb028 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 a5b2a2c5f..461962c5d 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 1e66684dc..b00ab26bf 100644
--- a/fuzz/tls_common.cc
+++ b/fuzz/tls_common.cc
@@ -5,9 +5,18 @@
#include <assert.h>
#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 8843347fa..e53accead 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 0c0902077..41a55541c 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 2a9803daa..998e4b934 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 8c78de28f..b831dcafe 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 4a47592ca..863e07d1c 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 f033b7843..bb78bc3d1 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<unsigned char> 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<TlsApplicationDataRecorder>(client_);
auto server_recorder = MakeTlsFilter<TlsApplicationDataRecorder>(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<TlsInspectorReplaceHandshakeMessage>(
@@ -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<TlsInspectorReplaceHandshakeMessage>(
@@ -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<TlsHandshakeRecorder>(
@@ -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<decltype(version_)>(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<uint16_t>(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 264bde67f..22a90f3b2 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 596da6252..fa3376937 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<PRTime*>(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 1a6137601..6d74df972 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 d05b30a72..737b4e797 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 142c8bccf..0386789a1 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 e25a8f887..f4b50f8a7 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 603da3e15..ed733bffb 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 d8f050ea5..a1ef52736 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 a9b766290..8c472580b 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 f8fb5d50f..b7b5b7fe5 100644
--- a/lib/ssl/sslnonce.c
+++ b/lib/ssl/sslnonce.c
@@ -20,8 +20,6 @@
#include <time.h>
#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 d7abb3dc3..75402a780 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 96b963bc2..802ef6714 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 19397d5c3..16f0b35bb 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 668d9aad2..d634f4b84 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 9def16d41..9d7c50efb 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 b090f9bca..2770be761 100644
--- a/lib/ssl/tls13replay.c
+++ b/lib/ssl/tls13replay.c
@@ -109,7 +109,7 @@ loser:
* 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 @@ loser:
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);