Bug 1543874 - Use an external clock for SSL functions, r=ekr,kevinjacobs
authorMartin Thomson <mt@lowentropy.net>
Mon, 20 May 2019 09:45:56 +0100
changeset 15126 0556b3040451e8a7ca0a499005993a95921754d4
parent 15125 65efa74ef84a3b2fcab7fc960ee7c05e28bab2b1
child 15127 ec6843bc0ce7a4d30150ef65e137c3a1654198a0
push id3365
push usermartin.thomson@gmail.com
push dateMon, 20 May 2019 11:05:18 +0000
reviewersekr, kevinjacobs
bugs1543874
Bug 1543874 - Use an external clock for SSL functions, r=ekr,kevinjacobs Summary: This adds a new (experimental) API that allows users of libssl to provide their own clock function. This is primarily of use in testing, but it also enables our QUIC implementation, which also runs off an external clock. SSL Sockets (and session IDs, when they are in memory) now have a "now()" function and void* arg attached to them. By default, this is a function that calls PR_Now(). These values are copied from the socket to any session ID that is created from the socket, and to any session ID that is restored from the session cache. The ssl_Time() and ssl_TimeUsec() functions have been removed. As part of this, the experimental SSL_SetupAntiReplay() function had to be modified to take an external clock (PR_Now() suffices generally). That function relies on knowing the time, and it doesn't have a socket to work from. To avoid problems arising from the change in the signature, SSL_SetupAntiReplay is now removed. There are now three uses of time in the library: * The primary source of time runs of these newly added functions. This governs session expiry, 0-RTT checks, and related functions. * The session cache uses a separate time to manage its locking. This is of type PRUint32 in seconds (rather than PRTime in microseconds). In investigating this, I found several places where this time in seconds was leaking across to the main functions via the lastAccessTime property. That was fixed. The cache functions that use time now all call ssl_CacheNow() to get time. * DTLS timers run using PRIntervalTime. This is a little annoying and these could be made to use the main time source, but that would result in conversions between PRTime and PRIntervalTime at the DTLS API. PRIntervalTime has a different epoch to PRTime, so this would be a little awkward. Only the first of these can be controlled using the new API. Bugs found: * Expiration time of resumption tokens was based on the sid->expirationTime, which didn't account for the lifetime provided by the server. These are now capped by the minimum of ssl_ticket_lifetime and the value the server indicates. I removed ssl3_sid_timeout, the old limit, because inconsistent lifetimes between client and server messed with tests. The client would have a lower cap than the server, which prevented testing of the enforcement of server limits without jumping through hoops. * There was a missing time conversion in tls13_InWindow which made the window checks too lenient. * lastAccessTime was being set to seconds-since-epoch instead of microseconds-since-epoch in a few places. Reviewers: ekr, KevinJacobs Reviewed By: KevinJacobs Subscribers: cjpatton Bug #: 1543874 Differential Revision: https://phabricator.services.mozilla.com/D27238
cmd/selfserv/selfserv.c
fuzz/tls_client_target.cc
fuzz/tls_common.cc
fuzz/tls_common.h
fuzz/tls_server_target.cc
gtests/ssl_gtest/libssl_internals.c
gtests/ssl_gtest/libssl_internals.h
gtests/ssl_gtest/ssl_0rtt_unittest.cc
gtests/ssl_gtest/ssl_fuzz_unittest.cc
gtests/ssl_gtest/ssl_resumption_unittest.cc
gtests/ssl_gtest/tls_connect.cc
gtests/ssl_gtest/tls_connect.h
lib/ssl/authcert.c
lib/ssl/ssl3con.c
lib/ssl/ssl3exthandle.c
lib/ssl/sslcon.c
lib/ssl/sslexp.h
lib/ssl/sslimpl.h
lib/ssl/sslnonce.c
lib/ssl/sslsnce.c
lib/ssl/sslsock.c
lib/ssl/tls13con.c
lib/ssl/tls13con.h
lib/ssl/tls13exthandle.c
lib/ssl/tls13replay.c
--- a/cmd/selfserv/selfserv.c
+++ b/cmd/selfserv/selfserv.c
@@ -1949,17 +1949,17 @@ server_main(
             errExit("SSL_OptionSet SSL_NO_CACHE");
         }
     }
 
     if (zeroRTT) {
         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 ");
         }
         rv = SSL_OptionSet(model_sock, SSL_ENABLE_0RTT_DATA, PR_TRUE);
         if (rv != SECSuccess) {
             errExit("error enabling 0RTT ");
         }
     }
--- a/fuzz/tls_client_target.cc
+++ b/fuzz/tls_client_target.cc
@@ -101,16 +101,17 @@ extern "C" int LLVMFuzzerTestOneInput(co
   static PRDescIdentity id = PR_GetUniqueIdentity("fuzz-client");
   ScopedPRFileDesc fd(DummyIOLayerMethods::CreateFD(id, socket.get()));
   PRFileDesc* ssl_fd = ImportFD(nullptr, fd.get());
   assert(ssl_fd == fd.get());
 
   // 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());
   DoHandshake(ssl_fd, false);
 
   // Release all SIDs.
   SSL_ClearSessionCache();
 
--- a/fuzz/tls_common.cc
+++ b/fuzz/tls_common.cc
@@ -1,18 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #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;
 
   SECStatus rv = SSL_VersionRangeGetSupported(ssl_variant_stream, &supported);
   assert(rv == SECSuccess);
 
   rv = SSL_VersionRangeSetDefault(ssl_variant_stream, &supported);
   assert(rv == SECSuccess);
--- a/fuzz/tls_common.h
+++ b/fuzz/tls_common.h
@@ -2,13 +2,14 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef tls_common_h__
 #define tls_common_h__
 
 #include "prinit.h"
 
+void FixTime(PRFileDesc* fd);
 PRStatus EnableAllProtocolVersions();
 void EnableAllCipherSuites(PRFileDesc* fd);
 void DoHandshake(PRFileDesc* fd, bool isServer);
 
 #endif  // tls_common_h__
--- a/fuzz/tls_server_target.cc
+++ b/fuzz/tls_server_target.cc
@@ -113,16 +113,17 @@ extern "C" int LLVMFuzzerTestOneInput(co
 
   // Create and import dummy socket.
   std::unique_ptr<DummyPrSocket> socket(new DummyPrSocket(data, len));
   static PRDescIdentity id = PR_GetUniqueIdentity("fuzz-server");
   ScopedPRFileDesc fd(DummyIOLayerMethods::CreateFD(id, socket.get()));
   PRFileDesc* ssl_fd = ImportFD(model.get(), fd.get());
   assert(ssl_fd == fd.get());
 
+  FixTime(ssl_fd);
   SetSocketOptions(ssl_fd, config);
   DoHandshake(ssl_fd, true);
 
   return 0;
 }
 
 extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size,
                                           size_t max_size, unsigned int seed) {
--- a/gtests/ssl_gtest/libssl_internals.c
+++ b/gtests/ssl_gtest/libssl_internals.c
@@ -104,19 +104,20 @@ void SSLInt_PrintCipherSpecs(const char 
   for (cur_p = PR_NEXT_LINK(&ss->ssl3.hs.cipherSpecs);
        cur_p != &ss->ssl3.hs.cipherSpecs; cur_p = PR_NEXT_LINK(cur_p)) {
     ssl3CipherSpec *spec = (ssl3CipherSpec *)cur_p;
     fprintf(stderr, "  %s spec epoch=%d (%s) refct=%d\n", SPEC_DIR(spec),
             spec->epoch, spec->phase, spec->refCt);
   }
 }
 
-/* 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);
   if (!ss) {
     return SECFailure;
   }
 
   for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
@@ -292,20 +293,16 @@ SECStatus SSLInt_AdvanceWriteSeqByAWindo
 
 SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group) {
   const sslNamedGroupDef *groupDef = ssl_LookupNamedGroup(group);
   if (!groupDef) return ssl_kea_null;
 
   return groupDef->keaType;
 }
 
-void SSLInt_SetTicketLifetime(uint32_t lifetime) {
-  ssl_ticket_lifetime = lifetime;
-}
-
 SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size) {
   sslSocket *ss;
 
   ss = ssl_FindSocket(fd);
   if (!ss) {
     return SECFailure;
   }
 
@@ -319,20 +316,16 @@ SECStatus SSLInt_SetSocketMaxEarlyDataSi
   ssl_GetSpecWriteLock(ss);
   ss->ssl3.crSpec->earlyDataRemaining = size;
   ss->ssl3.cwSpec->earlyDataRemaining = size;
   ssl_ReleaseSpecWriteLock(ss);
 
   return SECSuccess;
 }
 
-void SSLInt_RolloverAntiReplay(void) {
-  tls13_AntiReplayRollover(ssl_TimeUsec());
-}
-
 SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) {
   sslSocket *ss = ssl_FindSocket(fd);
   if (!ss) {
     return SECFailure;
   }
 
   ssl_GetSSL3HandshakeLock(ss);
   *pending = ss->ssl3.hs.msg_body.len > 0;
--- a/gtests/ssl_gtest/libssl_internals.h
+++ b/gtests/ssl_gtest/libssl_internals.h
@@ -35,13 +35,11 @@ PRBool SSLInt_DamageEarlyTrafficSecret(P
 SECStatus SSLInt_Set0RttAlpn(PRFileDesc *fd, PRUint8 *data, unsigned int len);
 PRBool SSLInt_HasCertWithAuthType(PRFileDesc *fd, SSLAuthType authType);
 PRBool SSLInt_SendAlert(PRFileDesc *fd, uint8_t level, uint8_t type);
 SECStatus SSLInt_AdvanceWriteSeqNum(PRFileDesc *fd, PRUint64 to);
 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_
--- a/gtests/ssl_gtest/ssl_0rtt_unittest.cc
+++ b/gtests/ssl_gtest/ssl_0rtt_unittest.cc
@@ -41,17 +41,17 @@ TEST_P(TlsConnectTls13, ZeroRttServerRej
   ExpectResumption(RESUME_TICKET);
   ZeroRttSendReceive(true, false);
   Handshake();
   CheckConnected();
   SendReceive();
 }
 
 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.
 
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
   server_->Set0RttEnabled(true);  // So we signal that we allow 0-RTT.
   Connect();
@@ -101,17 +101,17 @@ class TlsZeroRttReplayTest : public TlsC
     ZeroRttSendReceive(true, true);
     Handshake();
     EXPECT_LT(0U, first_packet->packet().len());
     ExpectEarlyDataAccepted(true);
     CheckConnected();
     SendReceive();
 
     if (rollover) {
-      SSLInt_RolloverAntiReplay();
+      RolloverAntiReplay();
     }
 
     // Now replay that packet against the server.
     Reset();
     server_->StartConnect();
     server_->Set0RttEnabled(true);
 
     // Capture the early_data extension, which should not appear.
@@ -179,62 +179,62 @@ TEST_P(TlsConnectTls13, ZeroRttServerOnl
 
   // Now make sure that things complete.
   Handshake();
   CheckConnected();
   SendReceive();
   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();
   ExpectEarlyDataAccepted(false);
   CheckConnected();
   SendReceive();
 }
 
 // In this test, we falsely inflate the estimate of the RTT by delaying the
 // ServerHello on the first handshake.  This results in the server estimating a
 // higher value of the ticket age than the client ultimately provides.  Add a
 // 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();
   CheckKeys();
 
   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();
   ZeroRttSendReceive(true, false);
   Handshake();
   CheckConnected();
   SendReceive();
 }
@@ -865,14 +865,67 @@ TEST_F(TlsConnectDatagram13, ZeroRttShor
   EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), read);
   EXPECT_EQ(0, memcmp(data, buffer.data(), sizeof(data)));
 
   Handshake();  // Complete the handshake.
   ExpectEarlyDataAccepted(true);
   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);
 #endif
 
 }  // namespace nss_test
--- a/gtests/ssl_gtest/ssl_fuzz_unittest.cc
+++ b/gtests/ssl_gtest/ssl_fuzz_unittest.cc
@@ -17,17 +17,17 @@ namespace nss_test {
 #else
 #define FUZZ_F(c, f) TEST_F(c, DISABLED_Fuzz_##f)
 #define FUZZ_P(c, f) TEST_P(c, DISABLED_Fuzz_##f)
 #endif
 
 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 {
  public:
   TlsApplicationDataRecorder(const std::shared_ptr<TlsAgent>& a)
       : TlsRecordFilter(a), buffer_() {}
 
   virtual PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
@@ -41,26 +41,19 @@ class TlsApplicationDataRecorder : publi
   }
 
   const DataBuffer& buffer() const { return buffer_; }
 
  private:
   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);
 
   // Make sure we have RSA blinding params.
   Connect();
 
   Reset();
   ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
@@ -90,17 +83,17 @@ FUZZ_P(TlsConnectGeneric, DeterministicE
   EXPECT_EQ(SECSuccess, rv);
 
   // The two exported keys should be the same.
   EXPECT_EQ(out1, out2);
 }
 
 // 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();
 
   // Connect a few times and compare the transcripts byte-by-byte.
   DataBuffer last;
   for (size_t i = 0; i < 5; i++) {
     Reset();
     ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
@@ -125,19 +118,17 @@ FUZZ_P(TlsConnectGeneric, DeterministicT
     last = buffer;
   }
 }
 
 // Check that we can establish and use a connection
 // 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_);
 
   Connect();
 
   // Construct the plaintext.
   DataBuffer buf;
@@ -152,91 +143,113 @@ FUZZ_P(TlsConnectGeneric, ConnectSendRec
   Receive(buf.len());
 
   // Check for plaintext on the wire.
   EXPECT_EQ(buf, client_recorder->buffer());
   EXPECT_EQ(buf, server_recorder->buffer());
 }
 
 // Check that an invalid Finished message doesn't abort the connection.
-FUZZ_P(TlsConnectGeneric, BogusClientFinished) {
+FUZZ_P(TlsFuzzTest, BogusClientFinished) {
   EnsureTlsSetup();
 
   MakeTlsFilter<TlsInspectorReplaceHandshakeMessage>(
       client_, kTlsHandshakeFinished,
       DataBuffer(kShortEmptyFinished, sizeof(kShortEmptyFinished)));
   Connect();
   SendReceive();
 }
 
 // Check that an invalid Finished message doesn't abort the connection.
-FUZZ_P(TlsConnectGeneric, BogusServerFinished) {
+FUZZ_P(TlsFuzzTest, BogusServerFinished) {
   EnsureTlsSetup();
 
   MakeTlsFilter<TlsInspectorReplaceHandshakeMessage>(
       server_, kTlsHandshakeFinished,
       DataBuffer(kLongEmptyFinished, sizeof(kLongEmptyFinished)));
   Connect();
   SendReceive();
 }
 
 // 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
                          : kTlsHandshakeServerKeyExchange;
   MakeTlsFilter<TlsLastByteDamager>(server_, msg_type);
   Connect();
   SendReceive();
 }
 
 // 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);
   MakeTlsFilter<TlsLastByteDamager>(client_, kTlsHandshakeCertificateVerify);
   Connect();
 }
 
 // Check that session ticket resumption works.
-FUZZ_P(TlsConnectGeneric, SessionTicketResumption) {
+FUZZ_P(TlsFuzzTest, SessionTicketResumption) {
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   Connect();
   SendReceive();
 
   Reset();
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   ExpectResumption(RESUME_TICKET);
   Connect();
   SendReceive();
 }
 
 // Check that session tickets are not encrypted.
-FUZZ_P(TlsConnectGeneric, UnencryptedSessionTickets) {
+FUZZ_P(TlsFuzzTest, UnencryptedSessionTickets) {
   ConfigureSessionCache(RESUME_TICKET, RESUME_TICKET);
 
   auto filter = MakeTlsFilter<TlsHandshakeRecorder>(
       server_, kTlsHandshakeNewSessionTicket);
   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));
 }
--- a/gtests/ssl_gtest/ssl_resumption_unittest.cc
+++ b/gtests/ssl_gtest/ssl_resumption_unittest.cc
@@ -320,24 +320,27 @@ TEST_P(TlsConnectGeneric, ConnectResumeC
   Reset();
   ClearServerCache();
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   ExpectResumption(RESUME_NONE);
   Connect();
   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);
   ExpectResumption(RESUME_NONE);
 
   // TLS 1.3 uses the pre-shared key extension instead.
   SSLExtensionType xtn = (version_ >= SSL_LIBRARY_VERSION_TLS_1_3)
                              ? ssl_tls13_pre_shared_key_xtn
@@ -349,17 +352,16 @@ TEST_P(TlsConnectGenericResumption, Conn
     EXPECT_FALSE(capture->captured());
   } else {
     EXPECT_TRUE(capture->captured());
     EXPECT_EQ(0U, capture->extension().len());
   }
 }
 
 TEST_P(TlsConnectGeneric, ConnectWithExpiredTicketAtServer) {
-  SSLInt_SetTicketLifetime(1);  // one second
   // This causes a ticket resumption.
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   Connect();
   SendReceive();
 
   Reset();
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   ExpectResumption(RESUME_NONE);
@@ -368,17 +370,17 @@ TEST_P(TlsConnectGeneric, ConnectWithExp
                              ? ssl_tls13_pre_shared_key_xtn
                              : ssl_session_ticket_xtn;
   auto capture = MakeTlsFilter<TlsExtensionCapture>(client_, xtn);
   StartConnect();
   client_->Handshake();
   EXPECT_TRUE(capture->captured());
   EXPECT_LT(0U, capture->extension().len());
 
-  WAIT_(false, 1000);  // Let the ticket expire on the server.
+  AdvanceTime(kLongerThanTicketLifetime);
 
   Handshake();
   CheckConnected();
 }
 
 TEST_P(TlsConnectGeneric, ConnectResumeCorruptTicket) {
   // This causes a ticket resumption.
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
@@ -1104,33 +1106,33 @@ TEST_P(TlsConnectGenericResumption, ReCo
   ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
   ExpectResumption(RESUME_TICKET, 2);
   Connect();
   // Only the client knows this.
   CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519,
                       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);
   ASSERT_TRUE(token->peerCert);
 
   // Check that the server cert is the correct one.
   ASSERT_EQ(cert->derCert.len, token->peerCert->derCert.len);
   EXPECT_EQ(0, memcmp(cert->derCert.data, token->peerCert->derCert.data,
                       cert->derCert.len));
 
   ASSERT_EQ(alpnSize, token->alpnSelectionLen);
   EXPECT_EQ(0, memcmp("a", token->alpnSelection, token->alpnSelectionLen));
 
   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
 // when resuming using an external token.
 TEST_P(TlsConnectGenericResumptionToken, CheckSessionId) {
   ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
   auto original_sid = MakeTlsFilter<CaptureSessionId>(client_);
   Connect();
@@ -1170,24 +1172,74 @@ TEST_P(TlsConnectGenericResumptionToken,
 
   // Get resumption token infos
   SSLResumptionTokenInfo tokenInfo = {0};
   ScopedSSLResumptionTokenInfo token(&tokenInfo);
   client_->GetTokenInfo(token);
   ScopedCERTCertificate cert(
       PK11_FindCertFromNickname(server_->name().c_str(), nullptr));
 
-  CheckGetInfoResult(0, 0, cert, token);
+  CheckGetInfoResult(now(), 0, 0, cert, token);
 
   Handshake();
   CheckConnected();
 
   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);
   Connect();
   CheckAlpn("a");
   SendReceive();
 
   Reset();
@@ -1200,28 +1252,28 @@ TEST_P(TlsConnectGenericResumptionToken,
 
   // Get resumption token infos
   SSLResumptionTokenInfo tokenInfo = {0};
   ScopedSSLResumptionTokenInfo token(&tokenInfo);
   client_->GetTokenInfo(token);
   ScopedCERTCertificate cert(
       PK11_FindCertFromNickname(server_->name().c_str(), nullptr));
 
-  CheckGetInfoResult(1, 0, cert, token);
+  CheckGetInfoResult(now(), 1, 0, cert, token);
 
   Handshake();
   CheckConnected();
   CheckAlpn("a");
 
   SendReceive();
 }
 
 TEST_P(TlsConnectTls13ResumptionToken, ConnectResumeGetInfoZeroRtt) {
   EnableAlpn();
-  SSLInt_RolloverAntiReplay();
+  RolloverAntiReplay();
   ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
   server_->Set0RttEnabled(true);
   Connect();
   CheckAlpn("a");
   SendReceive();
 
   Reset();
   EnableAlpn();
@@ -1235,17 +1287,17 @@ TEST_P(TlsConnectTls13ResumptionToken, C
 
   // Get resumption token infos
   SSLResumptionTokenInfo tokenInfo = {0};
   ScopedSSLResumptionTokenInfo token(&tokenInfo);
   client_->GetTokenInfo(token);
   ScopedCERTCertificate cert(
       PK11_FindCertFromNickname(server_->name().c_str(), nullptr));
 
-  CheckGetInfoResult(1, 1024, cert, token);
+  CheckGetInfoResult(now(), 1, 1024, cert, token);
 
   ZeroRttSendReceive(true, true);
   Handshake();
   ExpectEarlyDataAccepted(true);
   CheckConnected();
   CheckAlpn("a");
 
   SendReceive();
--- a/gtests/ssl_gtest/tls_connect.cc
+++ b/gtests/ssl_gtest/tls_connect.cc
@@ -101,16 +101,20 @@ std::string VersionString(uint16_t versi
       return "1.3";
     default:
       std::cerr << "Invalid version: " << version << std::endl;
       EXPECT_TRUE(false);
       return "";
   }
 }
 
+// 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),
       client_(new TlsAgent(TlsAgent::kClient, TlsAgent::CLIENT, variant_)),
       server_(new TlsAgent(TlsAgent::kServerRsa, TlsAgent::SERVER, variant_)),
       client_model_(nullptr),
       server_model_(nullptr),
       version_(version),
@@ -198,21 +202,25 @@ void TlsConnectTestBase::RestoreAlgorith
     auto algorithm = std::get<0>(*it);
     auto policy = std::get<1>(*it);
     SECStatus rv = NSS_SetAlgorithmPolicy(
         algorithm, policy, NSS_USE_POLICY_IN_SSL | NSS_USE_ALG_IN_SSL_KX);
     ASSERT_EQ(SECSuccess, rv);
   }
 }
 
+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();
 }
 
 void TlsConnectTestBase::TearDown() {
   client_ = nullptr;
   server_ = nullptr;
@@ -277,46 +285,49 @@ void TlsConnectTestBase::ExpectResumptio
   EXPECT_EQ(expected_resumptions_ == 0, expected == RESUME_NONE);
 }
 
 void TlsConnectTestBase::EnsureTlsSetup() {
   EXPECT_TRUE(server_->EnsureTlsSetup(server_model_ ? server_model_->ssl_fd()
                                                     : 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();
 
   ASSERT_TRUE_WAIT((client_->state() != TlsAgent::STATE_CONNECTING) &&
                        (server_->state() != TlsAgent::STATE_CONNECTING),
                    5000);
 }
 
 void TlsConnectTestBase::EnableExtendedMasterSecret() {
   client_->EnableExtendedMasterSecret();
   server_->EnableExtendedMasterSecret();
   ExpectExtendedMasterSecret(true);
 }
 
 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) {
   EnsureTlsSetup();
   client_->EnableSingleCipher(cipher_suite);
 
   Connect();
   SendReceive();
@@ -674,18 +685,19 @@ void TlsConnectTestBase::SendReceive(siz
   ASSERT_GT(total, server_->received_bytes());
   client_->SendData(total - server_->received_bytes());
   server_->SendData(total - client_->received_bytes());
   Receive(total);  // Receive() is cumulative
 }
 
 // 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);
   server_->Set0RttEnabled(true);  // So we signal that we allow 0-RTT.
   Connect();
   SendReceive();  // Need to read so that we absorb the session ticket.
   CheckKeys();
 
@@ -787,22 +799,30 @@ void TlsConnectTestBase::ShiftDtlsTimers
     time_shift = time;
   }
   rv = DTLS_GetHandshakeTimeout(server_->ssl_fd(), &time);
   if (rv == SECSuccess &&
       (time < time_shift || time_shift == PR_INTERVAL_NO_TIMEOUT)) {
     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));
   }
+}
 
-  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; }
+
+// Advance time by a full anti-replay window.
+void TlsConnectTestBase::RolloverAntiReplay() {
+  AdvanceTime(kAntiReplayWindow);
 }
 
 TlsConnectGeneric::TlsConnectGeneric()
     : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {}
 
 TlsConnectPre12::TlsConnectPre12()
     : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {}
 
--- a/gtests/ssl_gtest/tls_connect.h
+++ b/gtests/ssl_gtest/tls_connect.h
@@ -43,16 +43,18 @@ class TlsConnectTestBase : public ::test
   static ::testing::internal::ParamGenerator<uint16_t> kTlsVAll;
 
   TlsConnectTestBase(SSLProtocolVariant variant, uint16_t version);
   virtual ~TlsConnectTestBase();
 
   virtual void SetUp();
   virtual void TearDown();
 
+  PRTime now() const { return now_; }
+
   // Initialize client and server.
   void Init();
   // Clear the statistics.
   void ClearStats();
   // Clear the server session cache.
   void ClearServerCache();
   // Make sure TLS is configured for a connection.
   virtual void EnsureTlsSetup();
@@ -126,16 +128,18 @@ class TlsConnectTestBase : public ::test
   void Receive(size_t amount);
   void ExpectExtendedMasterSecret(bool expected);
   void ExpectEarlyDataAccepted(bool expected);
   void DisableECDHEServerKeyReuse();
   void SkipVersionChecks();
 
   // 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();
 
  protected:
   SSLProtocolVariant variant_;
   std::shared_ptr<TlsAgent> client_;
   std::shared_ptr<TlsAgent> server_;
@@ -159,20 +163,22 @@ class TlsConnectTestBase : public ::test
                                               SEC_OID_ANSIX9_DSA_SIGNATURE,
                                               SEC_OID_CURVE25519};
   std::vector<std::tuple<SECOidTag, uint32_t>> saved_policies_;
 
  private:
   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 {
    public:
     void AddAndCheckGroup(SSLNamedGroup group) {
       EXPECT_EQ(groups_.end(), groups_.find(group))
           << "Group " << group << " should not be duplicated";
       groups_.insert(group);
--- a/lib/ssl/authcert.c
+++ b/lib/ssl/authcert.c
@@ -15,33 +15,37 @@
 #include "secder.h"
 #include "keyhi.h"
 #include "nss.h"
 #include "ssl.h"
 #include "pk11func.h" /* for PK11_ function calls */
 #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)
 {
     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(),
                                         chosenNickName, certUsageSSLClient,
                                         PR_FALSE, proto_win);
         if (cert) {
             privkey = PK11_FindKeyByAnyCert(cert, proto_win);
             if (privkey) {
@@ -59,17 +63,17 @@ NSS_GetClientAuthData(void *arg,
         if (names != NULL) {
             for (i = 0; i < names->numnicknames; i++) {
                 cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(),
                                                 names->nicknames[i], certUsageSSLClient,
                                                 PR_FALSE, proto_win);
                 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;
                 }
                 rv = NSS_CmpCertChainWCANames(cert, caNames);
                 if (rv == SECSuccess) {
                     privkey =
                         PK11_FindKeyByAnyCert(cert, proto_win);
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -4836,17 +4836,18 @@ ssl3_SendClientHello(sslSocket *ss, sslC
          * as the original one. */
         sid = ssl_ReferenceSID(ss->sec.ci.sid);
     } else if (!ss->opt.noCache) {
         /* We ignore ss->sec.ci.sid here, and use ssl_Lookup because Lookup
          * handles expired entries and other details.
          * 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;
     }
 
     /* We can't resume based on a different token. If the sid exists,
      * make sure the token that holds the master secret still exists ...
      * If we previously did client-auth, make sure that the token that holds
      * the private key still exists, is logged in, hasn't been removed, etc.
@@ -8573,18 +8574,18 @@ ssl3_HandleClientHello(sslSocket *ss, PR
          ss->xtnData.emptySessionTicket)) {
         if (sidBytes.len > 0 && !ss->opt.noCache) {
             SSL_TRC(7, ("%d: SSL3[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x",
                         SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0],
                         ss->sec.ci.peer.pr_s6_addr32[1],
                         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;
             }
         }
     } else if (ss->statelessResume) {
         /* Fill in the client's session ID if doing a stateless resume.
          * (When doing stateless resumes, server echos client's SessionID.)
@@ -10271,17 +10272,17 @@ ssl3_HandleNewSessionTicket(sslSocket *s
         PORT_SetError(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET);
         return SECFailure;
     }
 
     /* RFC5077 Section 3.3: "The client MUST NOT treat the ticket as valid
      * 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);
         return SECFailure;
     }
 
     rv = ssl3_ConsumeHandshakeNumber(ss, &temp, 4, &b, &length);
     if (rv != SECSuccess) {
@@ -11579,18 +11580,18 @@ ssl3_FillInCachedSID(sslSocket *ss, sslS
     sid->keaType = ss->sec.keaType;
     sid->keaKeyBits = ss->sec.keaKeyBits;
     if (ss->sec.keaGroup) {
         sid->keaGroup = ss->sec.keaGroup->name;
     } else {
         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;
     }
 
     if (ss->xtnData.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT &&
         ss->xtnData.nextProto.data) {
         SECITEM_FreeItem(&sid->u.ssl3.alpnSelection, PR_FALSE);
--- a/lib/ssl/ssl3exthandle.c
+++ b/lib/ssl/ssl3exthandle.c
@@ -241,17 +241,17 @@ ssl3_ClientSendSessionTicketXtn(const ss
      * caller will call this function twice, and we need the inputs to be
      * consistent between the two calls. Note that currently the caller
      * will only be holding the lock when we are the client and when we're
      * attempting to resume an existing session.
      */
     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;
 
         rv = sslBuffer_Append(buf, session_ticket->ticket.data,
                               session_ticket->ticket.len);
         if (rv != SECSuccess) {
             return SECFailure;
         }
@@ -603,17 +603,16 @@ ssl3_ClientHandleStatusRequestXtn(const 
         return SECFailure;
     }
 
     /* Keep track of negotiated extensions. */
     xtnData->negotiated[xtnData->numNegotiated++] = ssl_cert_status_xtn;
     return SECSuccess;
 }
 
-PRUint32 ssl_ticket_lifetime = 2 * 24 * 60 * 60; /* 2 days in seconds */
 #define TLS_EX_SESS_TICKET_VERSION (0x010a)
 
 /*
  * Called from ssl3_SendNewSessionTicket, tls13_SendNewSessionTicket
  */
 SECStatus
 ssl3_EncodeSessionTicket(sslSocket *ss, const NewSessionTicket *ticket,
                          const PRUint8 *appToken, unsigned int appTokenLen,
@@ -737,17 +736,17 @@ ssl3_EncodeSessionTicket(sslSocket *ss, 
             goto loser;
     } else {
         rv = sslBuffer_AppendNumber(&plaintext, 0, 1);
         if (rv != SECSuccess)
             goto loser;
     }
 
     /* timestamp */
-    now = ssl_TimeUsec();
+    now = ssl_Time(ss);
     PORT_Assert(sizeof(now) == 8);
     rv = sslBuffer_AppendNumber(&plaintext, now, 8);
     if (rv != SECSuccess)
         goto loser;
 
     /* HostName (length and value) */
     rv = sslBuffer_AppendVariable(&plaintext, srvName->data, srvName->len, 2);
     if (rv != SECSuccess)
@@ -792,17 +791,17 @@ ssl3_EncodeSessionTicket(sslSocket *ss, 
      *
      * We calculate the client's estimate of this as:
      *    ticket_create + ticket_age_baseline + obfuscated_age
      *    = ticket_create + 1rtt + ticket_age_client
      *
      * 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)
         goto loser;
 
     /* Application token */
     rv = sslBuffer_AppendVariable(&plaintext, appToken, appTokenLen, 2);
     if (rv != SECSuccess)
@@ -1237,18 +1236,18 @@ ssl3_ProcessSessionTicketCommon(sslSocke
         SSL_DBG(("%d: SSL[%d]: Session ticket parsing failed.",
                  SSL_GETPID(), ss->fd));
         ssl3stats = SSL_GetStatistics();
         SSL_AtomicIncrementLong(&ssl3stats->hch_sid_ticket_parse_failures);
         goto loser; /* code already set */
     }
 
     /* 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) {
             goto loser; /* code already set */
         }
         if (appToken && parsedTicket.applicationToken.len) {
             rv = SECITEM_CopyItem(NULL, appToken,
                                   &parsedTicket.applicationToken);
--- a/lib/ssl/sslcon.c
+++ b/lib/ssl/sslcon.c
@@ -150,50 +150,40 @@ ssl_BeginClientHandshake(sslSocket *ss)
     SSL_TRC(3, ("%d: SSL[%d]: sending client-hello", SSL_GETPID(), ss->fd));
 
     /* If there's an sid set from an external cache, use it. */
     if (ss->sec.ci.sid && ss->sec.ci.sid->cached == in_external_cache) {
         sid = ss->sec.ci.sid;
         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) {
         if (sid->version >= ss->vrange.min && sid->version <= ss->vrange.max) {
             PORT_Assert(!ss->sec.localCert);
             ss->sec.localCert = CERT_DupCertificate(sid->localCert);
         } else {
             ssl_UncacheSessionID(ss);
             ssl_FreeSID(sid);
             sid = NULL;
         }
     }
     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;
 
     /* ssl3_SendClientHello will override this if it succeeds. */
     ss->version = SSL_LIBRARY_VERSION_3_0;
 
     ssl_GetSSL3HandshakeLock(ss);
     ssl_GetXmitBufLock(ss);
--- a/lib/ssl/sslexp.h
+++ b/lib/ssl/sslexp.h
@@ -154,17 +154,17 @@ typedef SECStatus(PR_CALLBACK *SSLExtens
     SSL_EXPERIMENTAL_API("SSL_InstallExtensionHooks",                       \
                          (PRFileDesc * _fd, PRUint16 _extension,            \
                           SSLExtensionWriter _writer, void *_writerArg,     \
                           SSLExtensionHandler _handler, void *_handlerArg), \
                          (fd, extension, writer, writerArg,                 \
                           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,
  * but early data will be rejected.
  *
  * NSS uses a Bloom filter to track the ClientHello messages that it receives
  * (specifically, it uses the PSK binder).  This function initializes a pair of
  * Bloom filters.  The two filters are alternated over time, with new
@@ -176,21 +176,21 @@ typedef SECStatus(PR_CALLBACK *SSLExtens
  * The false-positive probability of Bloom filters means that some valid
  * handshakes will be marked as potential replays.  Early data will be rejected
  * for a false positive.  To minimize this and to allow a trade-off of space
  * against accuracy, the size of the Bloom filter can be set by this function.
  *
  * 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
  * function once to avoid inadvertently disabling 0-RTT (use PR_CallOnce() to
  * avoid this problem).
  *
  * The primary tuning parameter is |bits| which determines the amount of memory
  * allocated to each Bloom filter.  NSS will allocate two Bloom filters, each
@@ -214,20 +214,21 @@ typedef SECStatus(PR_CALLBACK *SSLExtens
  * specification for the details of the generic problems with this technique.
  *
  * In addition to the generic anti-replay weaknesses, the state that the server
  * maintains is in local memory only.  Servers that operate in a cluster, even
  * those that use shared memory for tickets, will not share anti-replay state.
  * 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
  * will embed the provided token.
  *
  * This function will cause a NewSessionTicket message to be sent by a server.
  * This happens even if SSL_ENABLE_SESSION_TICKETS is disabled.  This allows a
  * server to suppress the usually automatic generation of a session ticket at
@@ -718,14 +719,26 @@ typedef struct SSLAeadContextStr SSLAead
                           const PRUint8 *_hsHash, unsigned int _hsHashLen, \
                           const char *_label, unsigned int _labelLen,      \
                           CK_MECHANISM_TYPE _mech, unsigned int _keySize,  \
                           PK11SymKey **_keyp),                             \
                          (version, cipherSuite, prk,                       \
                           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
 
 #endif /* __sslexp_h_ */
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -178,20 +178,21 @@ struct ssl3CertNodeStr {
     struct ssl3CertNodeStr *next;
     CERTCertificate *cert;
 };
 
 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);
 
 /* Socket ops */
 struct sslSocketOpsStr {
     int (*connect)(sslSocket *, const PRNetAddr *);
     PRFileDesc *(*accept)(sslSocket *, PRNetAddr *);
@@ -941,16 +942,20 @@ struct sslSocketStr {
     /* Pointer to operations vector for this socket */
     const sslSocketOps *ops;
 
     /* SSL socket options */
     sslOptions opt;
     /* 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 */
     unsigned long firstHsDone;       /* first handshake is complete. */
     unsigned long enoughFirstHsDone; /* enough of the first handshake is
                                       * done for callbacks to be able to
                                       * retrieve channel security
                                       * parameters from the SSL socket. */
@@ -1099,18 +1104,17 @@ struct sslSelfEncryptKeysStr {
 };
 typedef struct sslSelfEncryptKeysStr sslSelfEncryptKeys;
 
 extern char ssl_debug;
 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[];
 
 extern sslSessionIDLookupFunc ssl_sid_lookup;
 
 extern const sslNamedGroupDef ssl_named_groups[];
 
 /************************************************************************/
@@ -1190,18 +1194,19 @@ extern SECStatus ssl_SaveWriteData(sslSo
 extern SECStatus ssl_BeginClientHandshake(sslSocket *ss);
 extern SECStatus ssl_BeginServerHandshake(sslSocket *ss);
 extern SECStatus ssl_Do1stHandshake(sslSocket *ss);
 
 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);
 
 extern int ssl3_SendApplicationData(sslSocket *ss, const PRUint8 *in,
                                     int len, int flags);
 
 extern PRBool ssl_FdIsBlocking(PRFileDesc *fd);
@@ -1601,18 +1606,18 @@ extern unsigned int ssl3_config_match_in
 /* Return PR_TRUE if suite is usable.  This if the suite is permitted by policy,
  * enabled, has a certificate (as needed), has a viable key agreement method, is
  * usable with the negotiated TLS version, and is otherwise usable. */
 PRBool ssl3_config_match(const ssl3CipherSuiteCfg *suite, PRUint8 policy,
                          const SSLVersionRange *vrange, const sslSocket *ss);
 
 /* 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.
  * If it is uninitialized, this function writes the caller's value into
  * the disk entry, and returns false.
  * Otherwise, it overwrites the caller's wswk with the value obtained from
  * the disk, and returns PR_TRUE.
  * This is all done while holding the locks/semaphores necessary to make
@@ -1714,23 +1719,18 @@ SECStatus ssl_InsertRecordHeader(const s
 /********************** misc calls *********************/
 
 #ifdef DEBUG
 extern void ssl3_CheckCipherSuiteOrderConsistency();
 #endif
 
 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);
 
 SECStatus ssl3_ApplyNSSPolicy(void);
 
 extern SECStatus
 ssl3_TLSPRFWithMasterSecret(sslSocket *ss, ssl3CipherSpec *spec,
                             const char *label, unsigned int labelLen,
@@ -1758,17 +1758,17 @@ PRBool ssl_IsResumptionTokenUsable(sslSo
 /* unwrap helper function to handle the case where the wrapKey doesn't wind
  *  * up in the correct token for the master secret */
 PK11SymKey *ssl_unwrapSymKey(PK11SymKey *wrapKey,
                              CK_MECHANISM_TYPE wrapType, SECItem *param,
                              SECItem *wrappedKey,
                              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,
                                             void *ctx);
 SECStatus SSLExp_SetResumptionToken(PRFileDesc *fd, const PRUint8 *token,
                                     unsigned int len);
 
 SECStatus SSLExp_GetResumptionTokenInfo(const PRUint8 *tokenData, unsigned int tokenLen,
@@ -1810,16 +1810,18 @@ SECStatus SSLExp_HkdfExpandLabel(PRUint1
                                  PK11SymKey **key);
 SECStatus
 SSLExp_HkdfExpandLabelWithMech(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
                                const PRUint8 *hsHash, unsigned int hsHashLen,
                                const char *label, unsigned int labelLen,
                                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)
 #define SSL_GETPID getpid
 #elif defined(WIN32)
 extern int __cdecl _getpid(void);
 #define SSL_GETPID _getpid
 #else
--- a/lib/ssl/sslnonce.c
+++ b/lib/ssl/sslnonce.c
@@ -15,18 +15,16 @@
 #include "sslimpl.h"
 #include "sslproto.h"
 #include "nssilock.h"
 #include "sslencode.h"
 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
 #include <time.h>
 #endif
 
-PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */
-
 static sslSessionID *cache = NULL;
 static PZLock *cacheLock = NULL;
 
 /* sids can be in one of 5 states:
  *
  * never_cached,        created, but not yet put into cache.
  * in_client_cache,     in the client cache's linked list.
  * in_server_cache,     entry came from the server's cache file.
@@ -254,40 +252,38 @@ ssl_ReferenceSID(sslSessionID *sid)
 
 /*
 **  Lookup sid entry in cache by Address, port, and peerID string.
 **  If found, Increment reference count, and return pointer to caller.
 **  If it has timed out or ref count is zero, remove from list and free it.
 */
 
 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. */
             sid->cached = invalid_cache;                            /* mark not on list. */
             ssl_FreeLockedSID(sid);                                 /* drop ref count, free. */
         } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
                    (sid->port == port) &&                           /* server port matches */
                    /* proxy (peerID) matches */
@@ -311,17 +307,17 @@ ssl_LookupSID(const PRIPv6Addr *addr, PR
     return sid;
 }
 
 /*
 ** Add an sid to the cache or return a previously cached entry to the cache.
 ** 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);
 
     SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
                 "time=%x cached=%d",
                 sid, sid->cached, sid->addr.pr_s6_addr32[0],
                 sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2],
@@ -348,21 +344,26 @@ CacheSID(sslSessionID *sid)
     }
     PRINT_BUF(8, (0, "sessionID:",
                   sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength));
 
     sid->u.ssl3.lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, NULL);
     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
      * cache is holding a reference. Uncache will reduce the cache
      * reference.
      */
     LOCK_CACHE;
     sid->references++;
@@ -721,23 +722,23 @@ ssl_IsResumptionTokenUsable(sslSocket *s
     PORT_Assert(sid);
 
     // Check that the ticket didn't expire.
     PRTime endTime = 0;
     NewSessionTicket *ticket = &sid->u.ssl3.locked.sessionTicket;
     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;
     }
 
     // Check that the server name (SNI) matches the one set for this session.
     // Don't use the token if there's no server name.
     if (sid->urlSvrName == NULL || PORT_Strcmp(ss->url, sid->urlSvrName) != 0) {
         return PR_FALSE;
     }
@@ -1082,20 +1083,22 @@ ssl_CacheExternalToken(sslSocket *ss)
     }
 
     /* Don't export token if the session used client authentication. */
     if (sid->u.ssl3.clAuthValid) {
         return;
     }
 
     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;
 
     if (ssl_EncodeResumptionToken(sid, &encodedToken) != SECSuccess) {
         SSL_TRC(3, ("SSL [%d]: encoding resumption token failed", ss->fd));
         return;
     }
@@ -1124,21 +1127,21 @@ ssl_CacheSessionID(sslSocket *ss)
 
     if (!ss->sec.isServer && ss->resumptionTokenCallback) {
         ssl_CacheExternalToken(ss);
         return;
     }
 
     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
 ssl_UncacheSessionID(sslSocket *ss)
 {
     if (ss->opt.noCache) {
         return;
     }
@@ -1160,52 +1163,28 @@ void
 SSL_ClearSessionCache(void)
 {
     LOCK_CACHE;
     while (cache != NULL)
         UncacheSID(cache);
     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;
 
     if (ticket->ticket_lifetime_hint == 0) {
         return PR_TRUE;
     }
 
     endTime = ticket->received_timestamp +
               (PRTime)(ticket->ticket_lifetime_hint * PR_USEC_PER_SEC);
-    return endTime > ssl_TimeUsec();
+    return endTime > ssl_Time(ss);
 }
 
 void
 ssl3_SetSIDSessionTicket(sslSessionID *sid,
                          /*in/out*/ NewSessionTicket *newSessionTicket)
 {
     PORT_Assert(sid);
     PORT_Assert(newSessionTicket);
--- a/lib/ssl/sslsnce.c
+++ b/lib/ssl/sslsnce.c
@@ -271,40 +271,50 @@ typedef struct inheritanceStr inheritanc
 #if defined(XP_UNIX) || defined(XP_BEOS)
 
 #define DEFAULT_CACHE_DIRECTORY "/tmp"
 
 #endif /* XP_UNIX || XP_BEOS */
 
 /************************************************************************/
 
+/* 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;
 }
 
 static SECStatus
 UnlockSidCacheLock(sidCacheLock *lock)
 {
     SECStatus rv;
 
     lock->pid = 0;
     rv = sslMutex_Unlock(&lock->mutex);
     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)
 {
     PRUint32 lockNum = set % cache->numSIDCacheLocks;
     sidCacheLock *lock = cache->sidCacheLocks + lockNum;
 
     return LockSidCacheLock(lock, now);
 }
@@ -625,19 +635,22 @@ FindSID(cacheDesc *cache, PRUint32 setNu
     return NULL;
 }
 
 /************************************************************************/
 
 /* 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)
 {
     sslSessionID *sid = 0;
     sidCacheEntry *psce;
     certCacheEntry *pcce = 0;
     srvNameCacheEntry *psnce = 0;
@@ -707,17 +720,17 @@ ServerSessionIDLookup(const PRIPv6Addr *
                 ** Don't invalidate the SID cache entry, but don't find it.
                 */
                 PORT_Assert(!("Didn't get name Cache Lock!"));
                 psce = 0;
                 psnce = 0;
             }
         }
         if (psce) {
-            psce->lastAccessTime = now;
+            psce->lastAccessTime = sslNow;
             sce = *psce; /* grab a copy while holding the lock */
         }
     }
     UnlockSet(cache, set);
     if (psce) {
         /* sce conains a copy of the cache entry.
         ** Convert shared memory format to local format
         */
@@ -725,17 +738,17 @@ ServerSessionIDLookup(const PRIPv6Addr *
     }
     return sid;
 }
 
 /*
 ** 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);
 
     sidCacheEntry sce;
     PRUint32 now = 0;
     cacheDesc *cache = &globalCache;
 
     if (sid->u.ssl3.sessionIDLength == 0) {
@@ -743,17 +756,17 @@ ssl_ServerCacheSessionID(sslSessionID *s
     }
 
     if (sid->cached == never_cached || sid->cached == invalid_cache) {
         PRUint32 set;
         SECItem *name;
 
         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.
          */
         sid->expirationTime =
             sid->creationTime + cache->ssl3Timeout * PR_USEC_PER_SEC;
         SSL_TRC(8, ("%d: SSL: CacheMT: cached=%d addr=0x%08x%08x%08x%08x time=%x "
                     "cipherSuite=%d",
                     myPid, sid->cached,
@@ -1084,17 +1097,17 @@ InitCache(cacheDesc *cache, int maxCache
     cache->keyCacheData = (SSLWrappedSymWrappingKey *)(cache->cacheMem + (ptrdiff_t)cache->keyCacheData);
     cache->ticketKeyNameSuffix = (PRUint8 *)(cache->cacheMem + (ptrdiff_t)cache->ticketKeyNameSuffix);
     cache->ticketEncKey = (encKeyCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->ticketEncKey);
     cache->ticketMacKey = (encKeyCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->ticketMacKey);
     cache->ticketKeysValid = (PRUint32 *)(cache->cacheMem + (ptrdiff_t)cache->ticketKeysValid);
     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;
          ++locks_initialized, ++pLock) {
 
         SECStatus err = sslMutex_Init(&pLock->mutex, shared);
         if (err) {
             cache->numSIDCacheLocksInitialized = locks_initialized;
@@ -1513,17 +1526,17 @@ LockPoller(void *arg)
     PRUint32 expiration = cache->mutexTimeout;
 
     timeout = PR_SecondsToInterval(expiration);
     while (!sharedCache->stopPolling) {
         PR_Sleep(timeout);
         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;
              ++locks_polled, ++pLock) {
             pid_t pid;
 
             if (pLock->timeStamp < then &&
                 pLock->timeStamp != 0 &&
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -272,16 +272,18 @@ ssl_DupSocket(sslSocket *os)
 
     ss->opt = os->opt;
     ss->opt.useSocks = PR_FALSE;
     rv = SECITEM_CopyItem(NULL, &ss->opt.nextProtoNego, &os->opt.nextProtoNego);
     if (rv != SECSuccess) {
         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);
 
     ss->ops = os->ops;
     ss->rTimeout = os->rTimeout;
     ss->wTimeout = os->wTimeout;
     ss->cTimeout = os->cTimeout;
@@ -2210,16 +2212,18 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
     PORT_Assert(ss);
     if (ss == NULL) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return NULL;
     }
 
     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);
     ss->ssl3.dtlsSRTPCipherCount = sm->ssl3.dtlsSRTPCipherCount;
     PORT_Memcpy(ss->ssl3.signatureSchemes, sm->ssl3.signatureSchemes,
                 sizeof(ss->ssl3.signatureSchemes[0]) *
                     sm->ssl3.signatureSchemeCount);
     ss->ssl3.signatureSchemeCount = sm->ssl3.signatureSchemeCount;
@@ -3897,32 +3901,41 @@ void
 ssl_FreeEphemeralKeyPairs(sslSocket *ss)
 {
     while (!PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs)) {
         PRCList *cursor = PR_LIST_TAIL(&ss->ephemeralKeyPairs);
         ssl_FreeEphemeralKeyPair((sslEphemeralKeyPair *)cursor);
     }
 }
 
+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.
 */
 static sslSocket *
 ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
 {
     SECStatus rv;
     sslSocket *ss;
     int i;
     ssl_SetDefaultsFromEnvironment();
 
     if (ssl_force_locks)
         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;
     }
     ss->opt = ssl_defaults;
     if (protocolVariant == ssl_variant_datagram) {
         ss->opt.enableRenegotiation = SSL_RENEGOTIATE_NEVER;
     }
     ss->opt.useSocks = PR_FALSE;
@@ -4046,32 +4059,33 @@ struct {
     EXP(DestroyAead),
     EXP(DestroyResumptionTokenInfo),
     EXP(EnableESNI),
     EXP(EncodeESNIKeys),
     EXP(GetCurrentEpoch),
     EXP(GetExtensionSupport),
     EXP(GetResumptionTokenInfo),
     EXP(HelloRetryRequestCallback),
+    EXP(InitAntiReplay),
     EXP(InstallExtensionHooks),
     EXP(HkdfExtract),
     EXP(HkdfExpandLabel),
     EXP(HkdfExpandLabelWithMech),
     EXP(KeyUpdate),
     EXP(MakeAead),
     EXP(RecordLayerData),
     EXP(RecordLayerWriteCallback),
     EXP(SecretCallback),
     EXP(SendCertificateRequest),
     EXP(SendSessionTicket),
     EXP(SetESNIKeyPair),
     EXP(SetMaxEarlyDataSize),
     EXP(SetResumptionTokenCallback),
     EXP(SetResumptionToken),
-    EXP(SetupAntiReplay),
+    EXP(SetTimeFunc),
 #endif
     { "", NULL }
 };
 #undef EXP
 #undef PUB
 
 void *
 SSL_GetExperimentalAPI(const char *name)
@@ -4097,16 +4111,31 @@ ssl_ClearPRCList(PRCList *list, void (*f
         PR_REMOVE_LINK(cursor);
         if (f) {
             f(cursor);
         }
         PORT_Free(cursor);
     }
 }
 
+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
 SSLExp_SetResumptionTokenCallback(PRFileDesc *fd,
                                   SSLResumptionTokenCallback cb,
                                   void *ctx)
 {
     sslSocket *ss = ssl_FindSocket(fd);
@@ -4180,17 +4209,17 @@ SSLExp_SetResumptionToken(PRFileDesc *fd
     rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, SSL3_SESSIONID_BYTES);
     if (rv != SECSuccess) {
         goto loser; // Code set by PK11_GenerateRandom.
     }
     sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
     /* 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;
 
     ssl_ReleaseSSL3HandshakeLock(ss);
     ssl_Release1stHandshakeLock(ss);
     return SECSuccess;
 
 loser:
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -474,17 +474,17 @@ tls13_SetupClientHello(sslSocket *ss, ss
         sid->version < SSL_LIBRARY_VERSION_TLS_1_3) {
         return SECSuccess;
     }
 
     /* The caller must be holding sid->u.ssl3.lock for reading. */
     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;
     }
 
     if (ss->statelessResume) {
         PORT_Assert(ss->sec.ci.sid);
         rv = tls13_RecoverWrappedSharedSecret(ss, ss->sec.ci.sid);
         if (rv != SECSuccess) {
             FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
@@ -2719,17 +2719,17 @@ tls13_SendServerHelloSequence(sslSocket 
         }
         if (tls13_ShouldRequestClientAuth(ss)) {
             TLS13_SET_HS_STATE(ss, wait_client_cert);
         } else {
             TLS13_SET_HS_STATE(ss, wait_finished);
         }
     }
 
-    ss->ssl3.hs.serverHelloTime = ssl_TimeUsec();
+    ss->ssl3.hs.serverHelloTime = ssl_Time(ss);
     return SECSuccess;
 }
 
 SECStatus
 tls13_HandleServerHelloPart2(sslSocket *ss)
 {
     SECStatus rv;
     sslSessionID *sid = ss->sec.ci.sid;
@@ -4976,17 +4976,17 @@ tls13_HandleNewSessionTicket(sslSocket *
         return SECFailure;
     }
     if (!tls13_IsPostHandshake(ss) || ss->sec.isServer) {
         FATAL_ERROR(ss, SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET,
                     unexpected_message);
         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) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET,
                     decode_error);
         return SECFailure;
     }
     ticket.ticket.type = siBuffer;
--- a/lib/ssl/tls13con.h
+++ b/lib/ssl/tls13con.h
@@ -112,18 +112,18 @@ PRUint16 tls13_EncodeDraftVersion(SSL3Pr
 SECStatus tls13_ClientReadSupportedVersion(sslSocket *ss);
 SECStatus tls13_NegotiateVersion(sslSocket *ss,
                                  const TLSExtension *supported_versions);
 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,
                                            void *arg);
 SECStatus tls13_SendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request,
                               PRBool buffer);
 SECStatus SSLExp_KeyUpdate(PRFileDesc *fd, PRBool requestUpdate);
 PRBool tls13_MaybeTls13(sslSocket *ss);
--- a/lib/ssl/tls13exthandle.c
+++ b/lib/ssl/tls13exthandle.c
@@ -443,17 +443,17 @@ tls13_ClientSendPreSharedKeyXtn(const ss
     if (rv != SECSuccess)
         goto loser;
     rv = sslBuffer_AppendVariable(buf, session_ticket->ticket.data,
                                   session_ticket->ticket.len, 2);
     if (rv != SECSuccess)
         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);
     if (rv != SECSuccess)
         goto loser;
 
     /* Write out the binder list length. */
     binderLen = tls13_GetHashSize(ss);
--- a/lib/ssl/tls13replay.c
+++ b/lib/ssl/tls13replay.c
@@ -104,17 +104,17 @@ loser:
  * multiple threads if the server is multi-threaded.  A monitor is used to
  * ensure that only one thread can access the structures that change over time,
  * but no such guarantee is provided for configuration data.
  *
  * Functions that read from static configuration data depend on there being a
  * 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;
 
     if (k == 0 || bits == 0) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
     if ((k * (bits + 7) / 8) > SSL_MAX_BLOOM_FILTER_SIZE) {
@@ -148,61 +148,47 @@ SSLExp_SetupAntiReplay(PRTime window, un
     if (rv != SECSuccess) {
         goto loser; /* Code already set. */
     }
     /* When starting out, ensure that 0-RTT is not accepted until the window is
      * updated.  A ClientHello might have been accepted prior to a restart. */
     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;
 
 loser:
     (void)tls13_AntiReplayReset(NULL, NULL);
     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)
+static void
+tls13_AntiReplayUpdate(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()
-{
-    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
 tls13_InWindow(const sslSocket *ss, const sslSessionID *sid)
 {
     PRInt32 timeDelta;
 
     /* Calculate the difference between the client's view of the age of the
      * ticket (in |ss->xtnData.ticketAge|) and the server's view, which we now
      * 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
      * on server and client will tend to cancel each other out.
      *
      * There are two anti-replay filters that roll over each window.  In the
      * worst case, immediately after a rollover of the filters, we only have a
      * single window worth of recorded 0-RTT attempts.  Thus, the period in
@@ -216,17 +202,17 @@ tls13_InWindow(const sslSocket *ss, cons
      * occur immediately afterwards.  Window 1 is still checked for new 0-RTT
      * attempts for the remainder of window 2.  Therefore, attempts to replay
      * are detected because the value is recorded in window 1.  When rollover
      * occurs again, window 1 is erased and window 3 instated.  If we allowed an
      * attempt to be late by more than half a window, then this check would not
      * 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
  * the filters as a side-effect. This only detects a probable replay, it's
  * possible that this will return true when the 0-RTT attempt is not genuinely a
  * replay.  In that case, we reject 0-RTT unnecessarily, but that's OK because
  * no client expects 0-RTT to work every time. */
 PRBool
@@ -257,20 +243,19 @@ tls13_IsReplay(const sslSocket *ss, cons
                                   ss->xtnData.pskBinder.len,
                                   label, strlen(label),
                                   buf, size);
     if (rv != SECSuccess) {
         return PR_TRUE;
     }
 
     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);
     return replay;
 }