Bug 1043041: Use mozilla::pkix::Time instead of PRTime, r=keeler
authorBrian Smith <brian@briansmith.org>
Sat, 02 Aug 2014 08:49:12 -0700
changeset 14642 d641b9be5414ca0cd4387bc516b8f0cfd848e336
parent 14641 34706feaf2be580dc3ccdc51872932b6fcc7aa56
child 14643 37e60712078a5ef499dca27cc9377eeb199fc51c
push id3202
push userfranziskuskiefer@gmail.com
push dateMon, 01 Oct 2018 08:30:12 +0000
reviewerskeeler
bugs1043041
Bug 1043041: Use mozilla::pkix::Time instead of PRTime, r=keeler
lib/mozpkix/include/pkix/Time.h
lib/mozpkix/include/pkix/pkix.h
lib/mozpkix/include/pkix/pkixtypes.h
lib/mozpkix/lib/pkixbuild.cpp
lib/mozpkix/lib/pkixcheck.cpp
lib/mozpkix/lib/pkixcheck.h
lib/mozpkix/lib/pkixder.cpp
lib/mozpkix/lib/pkixder.h
lib/mozpkix/lib/pkixocsp.cpp
lib/mozpkix/lib/pkixtime.cpp
lib/mozpkix/lib/pkixutil.h
lib/mozpkix/moz.build
lib/mozpkix/test/gtest/nssgtest.cpp
lib/mozpkix/test/gtest/nssgtest.h
lib/mozpkix/test/gtest/pkixbuild_tests.cpp
lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp
lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp
lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
lib/mozpkix/test/lib/pkixtestutil.cpp
lib/mozpkix/test/lib/pkixtestutil.h
new file mode 100644
--- /dev/null
+++ b/lib/mozpkix/include/pkix/Time.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* 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/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_pkix__Time_h
+#define mozilla_pkix__Time_h
+
+#include <ctime>
+#include <limits>
+#include <stdint.h>
+
+#include "pkix/Result.h"
+
+namespace mozilla { namespace pkix {
+
+// Time with a range from the first second of year 0 (AD) through at least the
+// last second of year 9999, which is the range of legal times in X.509 and
+// OCSP. This type has second-level precision. The time zone is always UTC.
+//
+// Pass by value, not by reference.
+class Time
+{
+public:
+  // Construct an uninitilized instance.
+  //
+  // This will fail to compile because there is no default constructor:
+  //    Time x;
+  //
+  // This will succeed, leaving the time uninitialized:
+  //    Time x(Time::uninitialized);
+  enum Uninitialized { uninitialized };
+  Time(Uninitialized) { }
+
+  bool operator==(const Time& other) const
+  {
+    return elapsedSecondsAD == other.elapsedSecondsAD;
+  }
+  bool operator>(const Time& other) const
+  {
+    return elapsedSecondsAD > other.elapsedSecondsAD;
+  }
+  bool operator>=(const Time& other) const
+  {
+    return elapsedSecondsAD >= other.elapsedSecondsAD;
+  }
+  bool operator<(const Time& other) const
+  {
+    return elapsedSecondsAD < other.elapsedSecondsAD;
+  }
+  bool operator<=(const Time& other) const
+  {
+    return elapsedSecondsAD <= other.elapsedSecondsAD;
+  }
+
+  Result AddSeconds(uint64_t seconds)
+  {
+    if (std::numeric_limits<uint64_t>::max() - elapsedSecondsAD
+          < seconds) {
+      return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow
+    }
+    elapsedSecondsAD += seconds;
+    return Success;
+  }
+
+  Result SubtractSeconds(uint64_t seconds)
+  {
+    if (seconds > elapsedSecondsAD) {
+      return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow
+    }
+    elapsedSecondsAD -= seconds;
+    return Success;
+  }
+
+  static const uint64_t ONE_DAY_IN_SECONDS
+    = UINT64_C(24) * UINT64_C(60) * UINT64_C(60);
+
+private:
+  // This constructor is hidden to prevent accidents like this:
+  //
+  //    Time foo(time_t t)
+  //    {
+  //      // WRONG! 1970-01-01-00:00:00 == time_t(0), but not Time(0)!
+  //      return Time(t);
+  //    }
+  explicit Time(uint64_t elapsedSecondsAD)
+    : elapsedSecondsAD(elapsedSecondsAD)
+  {
+  }
+  friend Time TimeFromElapsedSecondsAD(uint64_t);
+
+  uint64_t elapsedSecondsAD;
+};
+
+inline Time TimeFromElapsedSecondsAD(uint64_t elapsedSecondsAD)
+{
+  return Time(elapsedSecondsAD);
+}
+
+Time Now();
+
+} } // namespace mozilla::pkix
+
+#endif // mozilla_pkix__Time_h
--- a/lib/mozpkix/include/pkix/pkix.h
+++ b/lib/mozpkix/include/pkix/pkix.h
@@ -84,17 +84,17 @@ namespace mozilla { namespace pkix {
 //
 // Result::ERROR_UNTRUSTED_CERT means that the end-entity certificate was
 //                              actively distrusted.
 // Result::SEC_ERROR_UNTRUSTED_ISSUER means that path building failed because
 //                                    of active distrust.
 // TODO(bug 968451): Document more of these.
 
 Result BuildCertChain(TrustDomain& trustDomain, Input cert,
-                      PRTime time, EndEntityOrCA endEntityOrCA,
+                      Time time, EndEntityOrCA endEntityOrCA,
                       KeyUsage requiredKeyUsageIfPresent,
                       KeyPurposeId requiredEKUIfPresent,
                       const CertPolicyId& requiredPolicy,
                       /*optional*/ const Input* stapledOCSPResponse);
 
 static const size_t OCSP_REQUEST_MAX_LENGTH = 127;
 Result CreateEncodedOCSPRequest(TrustDomain& trustDomain,
                                 const struct CertID& certID,
@@ -109,18 +109,18 @@ Result CreateEncodedOCSPRequest(TrustDom
 // The optional parameter thisUpdate will be the thisUpdate value of
 // the encoded response if it is considered trustworthy. Only
 // good, unknown, or revoked responses that verify correctly are considered
 // trustworthy. If the response is not trustworthy, thisUpdate will be 0.
 // Similarly, the optional parameter validThrough will be the time through
 // which the encoded response is considered trustworthy (that is, if a response had a
 // thisUpdate time of validThrough, it would be considered trustworthy).
 Result VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
-                                 const CertID& certID, PRTime time,
+                                 const CertID& certID, Time time,
                                  uint16_t maxLifetimeInDays,
                                  Input encodedResponse,
                        /* out */ bool& expired,
-              /* optional out */ PRTime* thisUpdate = nullptr,
-              /* optional out */ PRTime* validThrough = nullptr);
+              /* optional out */ Time* thisUpdate = nullptr,
+              /* optional out */ Time* validThrough = nullptr);
 
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix__pkix_h
--- a/lib/mozpkix/include/pkix/pkixtypes.h
+++ b/lib/mozpkix/include/pkix/pkixtypes.h
@@ -21,17 +21,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef mozilla_pkix__pkixtypes_h
 #define mozilla_pkix__pkixtypes_h
 
 #include "pkix/Input.h"
-#include "prtime.h"
+#include "pkix/Time.h"
 #include "stdint.h"
 
 namespace mozilla { namespace pkix {
 
 MOZILLA_PKIX_ENUM_CLASS DigestAlgorithm
 {
   sha512 = 1,
   sha384 = 2,
@@ -254,17 +254,17 @@ public:
   //                TrustDomain::FindIssuer
   //                  [...]
   //                    IssuerChecker::Check
   //                      [...]
   //
   // checker.Check is responsible for limiting the recursion to a reasonable
   // limit.
   virtual Result FindIssuer(Input encodedIssuerName,
-                            IssuerChecker& checker, PRTime time) = 0;
+                            IssuerChecker& checker, Time time) = 0;
 
   // Called as soon as we think we have a valid chain but before revocation
   // checks are done. This function can be used to compute additional checks,
   // especilaly checks that require the entire certificate chain. This callback
   // can also be used to save a copy of the built certificate chain for later
   // use.
   //
   // This function may be called multiple times, regardless of whether it
@@ -282,17 +282,17 @@ public:
   // very wrong to assume that the certificate chain is valid.
   //
   // certChain.GetDER(0) is the trust anchor.
   virtual Result IsChainValid(const DERArray& certChain) = 0;
 
   // issuerCertToDup is only non-const so CERT_DupCertificate can be called on
   // it.
   virtual Result CheckRevocation(EndEntityOrCA endEntityOrCA,
-                                 const CertID& certID, PRTime time,
+                                 const CertID& certID, Time time,
                     /*optional*/ const Input* stapledOCSPresponse,
                     /*optional*/ const Input* aiaExtension) = 0;
 
   // Check that the key size, algorithm, and parameters of the given public key
   // are acceptable.
   //
   // VerifySignedData() should do the same checks that this function does, but
   // mainly for efficiency, some keys are not passed to VerifySignedData().
--- a/lib/mozpkix/lib/pkixbuild.cpp
+++ b/lib/mozpkix/lib/pkixbuild.cpp
@@ -19,42 +19,40 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "pkix/pkix.h"
 
-#include <limits>
-
 #include "pkixcheck.h"
 #include "pkixutil.h"
 
 namespace mozilla { namespace pkix {
 
 static Result BuildForward(TrustDomain& trustDomain,
                            const BackCert& subject,
-                           PRTime time,
+                           Time time,
                            KeyUsage requiredKeyUsageIfPresent,
                            KeyPurposeId requiredEKUIfPresent,
                            const CertPolicyId& requiredPolicy,
                            /*optional*/ const Input* stapledOCSPResponse,
                            unsigned int subCACount);
 
 TrustDomain::IssuerChecker::IssuerChecker() { }
 TrustDomain::IssuerChecker::~IssuerChecker() { }
 
 // The implementation of TrustDomain::IssuerTracker is in a subclass only to
 // hide the implementation from external users.
 class PathBuildingStep : public TrustDomain::IssuerChecker
 {
 public:
   PathBuildingStep(TrustDomain& trustDomain, const BackCert& subject,
-                   PRTime time, KeyPurposeId requiredEKUIfPresent,
+                   Time time, KeyPurposeId requiredEKUIfPresent,
                    const CertPolicyId& requiredPolicy,
                    /*optional*/ const Input* stapledOCSPResponse,
                    unsigned int subCACount)
     : trustDomain(trustDomain)
     , subject(subject)
     , time(time)
     , requiredEKUIfPresent(requiredEKUIfPresent)
     , requiredPolicy(requiredPolicy)
@@ -69,17 +67,17 @@ public:
                /*optional*/ const Input* additionalNameConstraints,
                /*out*/ bool& keepGoing);
 
   Result CheckResult() const;
 
 private:
   TrustDomain& trustDomain;
   const BackCert& subject;
-  const PRTime time;
+  const Time time;
   const KeyPurposeId requiredEKUIfPresent;
   const CertPolicyId& requiredPolicy;
   /*optional*/ Input const* const stapledOCSPResponse;
   const unsigned int subCACount;
 
   Result RecordResult(Result currentResult, /*out*/ bool& keepGoing);
   Result result;
   bool resultWasSet;
@@ -203,17 +201,17 @@ PathBuildingStep::Check(Input potentialI
 //
 // Be very careful about changing the order of checks. The order is significant
 // because it affects which error we return when a certificate or certificate
 // chain has multiple problems. See the error ranking documentation in
 // pkix/pkix.h.
 static Result
 BuildForward(TrustDomain& trustDomain,
              const BackCert& subject,
-             PRTime time,
+             Time time,
              KeyUsage requiredKeyUsageIfPresent,
              KeyPurposeId requiredEKUIfPresent,
              const CertPolicyId& requiredPolicy,
              /*optional*/ const Input* stapledOCSPResponse,
              unsigned int subCACount)
 {
   Result rv;
 
@@ -291,17 +289,17 @@ BuildForward(TrustDomain& trustDomain,
   }
 
   // We've built a valid chain from the subject cert up to a trusted root.
   return Success;
 }
 
 Result
 BuildCertChain(TrustDomain& trustDomain, Input certDER,
-               PRTime time, EndEntityOrCA endEntityOrCA,
+               Time time, EndEntityOrCA endEntityOrCA,
                KeyUsage requiredKeyUsageIfPresent,
                KeyPurposeId requiredEKUIfPresent,
                const CertPolicyId& requiredPolicy,
                /*optional*/ const Input* stapledOCSPResponse)
 {
   // XXX: Support the legacy use of the subject CN field for indicating the
   // domain name the certificate is valid for.
   BackCert cert(certDER, endEntityOrCA, nullptr);
--- a/lib/mozpkix/lib/pkixcheck.cpp
+++ b/lib/mozpkix/lib/pkixcheck.cpp
@@ -30,28 +30,28 @@
 #include "pkix/ScopedPtr.h"
 #include "pkixder.h"
 #include "pkix/pkixnss.h"
 #include "pkixutil.h"
 
 namespace mozilla { namespace pkix {
 
 Result
-CheckValidity(Input encodedValidity, PRTime time)
+CheckValidity(Input encodedValidity, Time time)
 {
   Reader validity(encodedValidity);
-  PRTime notBefore;
+  Time notBefore(Time::uninitialized);
   if (der::TimeChoice(validity, notBefore) != Success) {
     return Result::ERROR_EXPIRED_CERTIFICATE;
   }
   if (time < notBefore) {
     return Result::ERROR_EXPIRED_CERTIFICATE;
   }
 
-  PRTime notAfter;
+  Time notAfter(Time::uninitialized);
   if (der::TimeChoice(validity, notAfter) != Success) {
     return Result::ERROR_EXPIRED_CERTIFICATE;
   }
   if (time > notAfter) {
     return Result::ERROR_EXPIRED_CERTIFICATE;
   }
 
   return der::End(validity);
@@ -613,17 +613,17 @@ CheckExtendedKeyUsage(EndEntityOrCA endE
   }
 
   return Success;
 }
 
 Result
 CheckIssuerIndependentProperties(TrustDomain& trustDomain,
                                  const BackCert& cert,
-                                 PRTime time,
+                                 Time time,
                                  KeyUsage requiredKeyUsageIfPresent,
                                  KeyPurposeId requiredEKUIfPresent,
                                  const CertPolicyId& requiredPolicy,
                                  unsigned int subCACount,
                                  /*out*/ TrustLevel& trustLevel)
 {
   Result rv;
 
--- a/lib/mozpkix/lib/pkixcheck.h
+++ b/lib/mozpkix/lib/pkixcheck.h
@@ -29,17 +29,17 @@
 
 namespace mozilla { namespace pkix {
 
 class BackCert;
 
 Result CheckIssuerIndependentProperties(
           TrustDomain& trustDomain,
           const BackCert& cert,
-          PRTime time,
+          Time time,
           KeyUsage requiredKeyUsageIfPresent,
           KeyPurposeId requiredEKUIfPresent,
           const CertPolicyId& requiredPolicy,
           unsigned int subCACount,
           /*out*/ TrustLevel& trustLevel);
 
 Result CheckNameConstraints(Input encodedNameConstraints,
                             const BackCert& firstChild,
--- a/lib/mozpkix/lib/pkixder.cpp
+++ b/lib/mozpkix/lib/pkixder.cpp
@@ -18,18 +18,19 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "pkixder.h"
+
 #include "pkix/bind.h"
-#include "cert.h"
+#include "pkixutil.h"
 
 namespace mozilla { namespace pkix { namespace der {
 
 namespace internal {
 
 // Too complicated to be inline
 Result
 ExpectTagAndGetLength(Reader& input, uint8_t expectedTag, uint16_t& length)
@@ -330,133 +331,117 @@ BitStringWithNoUnusedBits(Reader& input,
     return Result::ERROR_BAD_DER;
   }
   Reader::Mark mark(valueWithUnusedBits.GetMark());
   valueWithUnusedBits.SkipToEnd();
   return valueWithUnusedBits.GetInput(mark, value);
 }
 
 static inline Result
-ReadDigit(Reader& input, /*out*/ int& value)
+ReadDigit(Reader& input, /*out*/ unsigned int& value)
 {
   uint8_t b;
   if (input.Read(b) != Success) {
     return Result::ERROR_INVALID_TIME;
   }
   if (b < '0' || b > '9') {
     return Result::ERROR_INVALID_TIME;
   }
-  value = b - '0';
+  value = static_cast<unsigned int>(b - static_cast<uint8_t>('0'));
   return Success;
 }
 
 static inline Result
-ReadTwoDigits(Reader& input, int minValue, int maxValue, /*out*/ int& value)
+ReadTwoDigits(Reader& input, unsigned int minValue, unsigned int maxValue,
+              /*out*/ unsigned int& value)
 {
-  int hi;
+  unsigned int hi;
   Result rv = ReadDigit(input, hi);
   if (rv != Success) {
     return rv;
   }
-  int lo;
+  unsigned int lo;
   rv = ReadDigit(input, lo);
   if (rv != Success) {
     return rv;
   }
   value = (hi * 10) + lo;
   if (value < minValue || value > maxValue) {
     return Result::ERROR_INVALID_TIME;
   }
   return Success;
 }
 
-inline int
-daysBeforeYear(int year)
-{
-  return (365 * (year - 1))
-       + ((year - 1) / 4)    // leap years are every 4 years,
-       - ((year - 1) / 100)  // except years divisible by 100,
-       + ((year - 1) / 400); // except years divisible by 400.
-}
-
 namespace internal {
 
 // We parse GeneralizedTime and UTCTime according to RFC 5280 and we do not
 // accept all time formats allowed in the ASN.1 spec. That is,
 // GeneralizedTime must always be in the format YYYYMMDDHHMMSSZ and UTCTime
 // must always be in the format YYMMDDHHMMSSZ. Timezone formats of the form
 // +HH:MM or -HH:MM or NOT accepted.
 Result
-TimeChoice(Reader& tagged, uint8_t expectedTag, /*out*/ PRTime& time)
+TimeChoice(Reader& tagged, uint8_t expectedTag, /*out*/ Time& time)
 {
-  int days;
+  unsigned int days;
 
   Reader input;
   Result rv = ExpectTagAndGetValue(tagged, expectedTag, input);
   if (rv != Success) {
     return rv;
   }
 
-  int yearHi;
-  int yearLo;
+  unsigned int yearHi;
+  unsigned int yearLo;
   if (expectedTag == GENERALIZED_TIME) {
     rv = ReadTwoDigits(input, 0, 99, yearHi);
     if (rv != Success) {
       return rv;
     }
     rv = ReadTwoDigits(input, 0, 99, yearLo);
     if (rv != Success) {
       return rv;
     }
   } else if (expectedTag == UTCTime) {
     rv = ReadTwoDigits(input, 0, 99, yearLo);
     if (rv != Success) {
       return rv;
     }
-    yearHi = yearLo >= 50 ? 19 : 20;
+    yearHi = yearLo >= 50u ? 19u : 20u;
   } else {
     PR_NOT_REACHED("invalid tag given to TimeChoice");
     return Result::ERROR_INVALID_TIME;
   }
-  int year = (yearHi * 100) + yearLo;
-  if (year < 1970) {
+  unsigned int year = (yearHi * 100u) + yearLo;
+  if (year < 1970u) {
     // We don't support dates before January 1, 1970 because that is the epoch.
     return Result::ERROR_INVALID_TIME;
   }
-  if (year > 1970) {
-    // This is NOT equivalent to daysBeforeYear(year - 1970) because the
-    // leap year calculations in daysBeforeYear only works on absolute years.
-    days = daysBeforeYear(year) - daysBeforeYear(1970);
-    // We subtract 1 because we're interested in knowing how many days there
-    // were *before* the given year, relative to 1970.
-  } else {
-    days = 0;
-  }
+  days = DaysBeforeYear(year);
 
-  int month;
-  rv = ReadTwoDigits(input, 1, 12, month);
+  unsigned int month;
+  rv = ReadTwoDigits(input, 1u, 12u, month);
   if (rv != Success) {
     return rv;
   }
-  int daysInMonth;
-  static const int jan = 31;
-  const int feb = ((year % 4 == 0) &&
-                   ((year % 100 != 0) || (year % 400 == 0)))
-                ? 29
-                : 28;
-  static const int mar = 31;
-  static const int apr = 30;
-  static const int may = 31;
-  static const int jun = 30;
-  static const int jul = 31;
-  static const int aug = 31;
-  static const int sep = 30;
-  static const int oct = 31;
-  static const int nov = 30;
-  static const int dec = 31;
+  unsigned int daysInMonth;
+  static const unsigned int jan = 31u;
+  const unsigned int feb = ((year % 4u == 0u) &&
+                           ((year % 100u != 0u) || (year % 400u == 0u)))
+                         ? 29u
+                         : 28u;
+  static const unsigned int mar = 31u;
+  static const unsigned int apr = 30u;
+  static const unsigned int may = 31u;
+  static const unsigned int jun = 30u;
+  static const unsigned int jul = 31u;
+  static const unsigned int aug = 31u;
+  static const unsigned int sep = 30u;
+  static const unsigned int oct = 31u;
+  static const unsigned int nov = 30u;
+  static const unsigned int dec = 31u;
   switch (month) {
     case 1:  daysInMonth = jan; break;
     case 2:  daysInMonth = feb; days += jan; break;
     case 3:  daysInMonth = mar; days += jan + feb; break;
     case 4:  daysInMonth = apr; days += jan + feb + mar; break;
     case 5:  daysInMonth = may; days += jan + feb + mar + apr; break;
     case 6:  daysInMonth = jun; days += jan + feb + mar + apr + may; break;
     case 7:  daysInMonth = jul; days += jan + feb + mar + apr + may + jun;
@@ -476,54 +461,54 @@ TimeChoice(Reader& tagged, uint8_t expec
     case 12: daysInMonth = dec; days += jan + feb + mar + apr + may + jun +
                                         jul + aug + sep + oct + nov;
              break;
     default:
       PR_NOT_REACHED("month already bounds-checked by ReadTwoDigits");
       return Result::FATAL_ERROR_INVALID_STATE;
   }
 
-  int dayOfMonth;
-  rv = ReadTwoDigits(input, 1, daysInMonth, dayOfMonth);
+  unsigned int dayOfMonth;
+  rv = ReadTwoDigits(input, 1u, daysInMonth, dayOfMonth);
   if (rv != Success) {
     return rv;
   }
   days += dayOfMonth - 1;
 
-  int hours;
-  rv = ReadTwoDigits(input, 0, 23, hours);
+  unsigned int hours;
+  rv = ReadTwoDigits(input, 0u, 23u, hours);
   if (rv != Success) {
     return rv;
   }
-  int minutes;
-  rv = ReadTwoDigits(input, 0, 59, minutes);
+  unsigned int minutes;
+  rv = ReadTwoDigits(input, 0u, 59u, minutes);
   if (rv != Success) {
     return rv;
   }
-  int seconds;
-  rv = ReadTwoDigits(input, 0, 59, seconds);
+  unsigned int seconds;
+  rv = ReadTwoDigits(input, 0u, 59u, seconds);
   if (rv != Success) {
     return rv;
   }
 
   uint8_t b;
   if (input.Read(b) != Success) {
     return Result::ERROR_INVALID_TIME;
   }
   if (b != 'Z') {
     return Result::ERROR_INVALID_TIME;
   }
   if (End(input) != Success) {
     return Result::ERROR_INVALID_TIME;
   }
 
-  int64_t totalSeconds = (static_cast<int64_t>(days) * 24 * 60 * 60) +
-                         (static_cast<int64_t>(hours)     * 60 * 60) +
-                         (static_cast<int64_t>(minutes)        * 60) +
-                         seconds;
+  uint64_t totalSeconds = (static_cast<uint64_t>(days) * 24u * 60u * 60u) +
+                          (static_cast<uint64_t>(hours)      * 60u * 60u) +
+                          (static_cast<uint64_t>(minutes)          * 60u) +
+                          seconds;
 
-  time = totalSeconds * PR_USEC_PER_SEC;
+  time = TimeFromElapsedSecondsAD(totalSeconds);
   return Success;
 }
 
 } // namespace internal
 
 } } } // namespace mozilla::pkix::der
--- a/lib/mozpkix/lib/pkixder.h
+++ b/lib/mozpkix/lib/pkixder.h
@@ -34,17 +34,16 @@
 // input does not match the criteria.
 //
 // Skip* functions unconditionally advance the input mark and return Success if
 // they are able to do so; otherwise they fail with the input mark in an
 // undefined state.
 
 #include "pkix/Input.h"
 #include "pkix/pkixtypes.h"
-#include "prtime.h"
 
 namespace mozilla { namespace pkix { namespace der {
 
 enum Class
 {
    UNIVERSAL = 0 << 6,
 // APPLICATION = 1 << 6, // unused
    CONTEXT_SPECIFIC = 2 << 6,
@@ -328,34 +327,34 @@ Enumerated(Reader& input, uint8_t& value
 namespace internal {
 
 // internal::TimeChoice implements the shared functionality of GeneralizedTime
 // and TimeChoice. tag must be either UTCTime or GENERALIZED_TIME.
 //
 // Only times from 1970-01-01-00:00:00 onward are accepted, in order to
 // eliminate the chance for complications in converting times to traditional
 // time formats that start at 1970.
-Result TimeChoice(Reader& input, uint8_t tag, /*out*/ PRTime& time);
+Result TimeChoice(Reader& input, uint8_t tag, /*out*/ Time& time);
 
 } // namespace internal
 
 // Only times from 1970-01-01-00:00:00 onward are accepted, in order to
 // eliminate the chance for complications in converting times to traditional
 // time formats that start at 1970.
 inline Result
-GeneralizedTime(Reader& input, /*out*/ PRTime& time)
+GeneralizedTime(Reader& input, /*out*/ Time& time)
 {
   return internal::TimeChoice(input, GENERALIZED_TIME, time);
 }
 
 // Only times from 1970-01-01-00:00:00 onward are accepted, in order to
 // eliminate the chance for complications in converting times to traditional
 // time formats that start at 1970.
 inline Result
-TimeChoice(Reader& input, /*out*/ PRTime& time)
+TimeChoice(Reader& input, /*out*/ Time& time)
 {
   uint8_t expectedTag = input.Peek(UTCTime) ? UTCTime : GENERALIZED_TIME;
   return internal::TimeChoice(input, expectedTag, time);
 }
 
 // This parser will only parse values between 0..127. If this range is
 // increased then callers will need to be changed.
 inline Result
--- a/lib/mozpkix/lib/pkixocsp.cpp
+++ b/lib/mozpkix/lib/pkixocsp.cpp
@@ -27,72 +27,68 @@
 #include "pkix/bind.h"
 #include "pkix/pkix.h"
 #include "pkixcheck.h"
 #include "pkixutil.h"
 #include "pkixder.h"
 
 namespace mozilla { namespace pkix {
 
-static const PRTime ONE_DAY
-  = INT64_C(24) * INT64_C(60) * INT64_C(60) * PR_USEC_PER_SEC;
-static const PRTime SLOP = ONE_DAY;
-
 // These values correspond to the tag values in the ASN.1 CertStatus
 MOZILLA_PKIX_ENUM_CLASS CertStatus : uint8_t {
   Good = der::CONTEXT_SPECIFIC | 0,
   Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
   Unknown = der::CONTEXT_SPECIFIC | 2
 };
 
 class Context
 {
 public:
-  Context(TrustDomain& trustDomain, const CertID& certID, PRTime time,
-          uint16_t maxLifetimeInDays, /*optional out*/ PRTime* thisUpdate,
-          /*optional out*/ PRTime* validThrough)
+  Context(TrustDomain& trustDomain, const CertID& certID, Time time,
+          uint16_t maxLifetimeInDays, /*optional out*/ Time* thisUpdate,
+          /*optional out*/ Time* validThrough)
     : trustDomain(trustDomain)
     , certID(certID)
     , time(time)
     , maxLifetimeInDays(maxLifetimeInDays)
     , certStatus(CertStatus::Unknown)
     , thisUpdate(thisUpdate)
     , validThrough(validThrough)
     , expired(false)
   {
     if (thisUpdate) {
-      *thisUpdate = 0;
+      *thisUpdate = TimeFromElapsedSecondsAD(0);
     }
     if (validThrough) {
-      *validThrough = 0;
+      *validThrough = TimeFromElapsedSecondsAD(0);
     }
   }
 
   TrustDomain& trustDomain;
   const CertID& certID;
-  const PRTime time;
+  const Time time;
   const uint16_t maxLifetimeInDays;
   CertStatus certStatus;
-  PRTime* thisUpdate;
-  PRTime* validThrough;
+  Time* thisUpdate;
+  Time* validThrough;
   bool expired;
 
 private:
   Context(const Context&); // delete
   void operator=(const Context&); // delete
 };
 
 // Verify that potentialSigner is a valid delegated OCSP response signing cert
 // according to RFC 6960 section 4.2.2.2.
 static Result
 CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
                             BackCert& potentialSigner,
                             Input issuerSubject,
                             Input issuerSubjectPublicKeyInfo,
-                            PRTime time)
+                            Time time)
 {
   Result rv;
 
   // We don't need to do a complete verification of the signer (i.e. we don't
   // have to call BuildCertChain to verify the entire chain) because we
   // already know that the issuer is valid, since revocation checking is done
   // from the root to the parent after we've built a complete chain that we
   // know is otherwise valid. Rather, we just need to do a one-step validation
@@ -285,21 +281,21 @@ MapBadDERToMalformedOCSPResponse(Result 
   if (rv == Result::ERROR_BAD_DER) {
     return Result::ERROR_OCSP_MALFORMED_RESPONSE;
   }
   return rv;
 }
 
 Result
 VerifyEncodedOCSPResponse(TrustDomain& trustDomain, const struct CertID& certID,
-                          PRTime time, uint16_t maxOCSPLifetimeInDays,
+                          Time time, uint16_t maxOCSPLifetimeInDays,
                           Input encodedResponse,
                           /*out*/ bool& expired,
-                          /*optional out*/ PRTime* thisUpdate,
-                          /*optional out*/ PRTime* validThrough)
+                          /*optional out*/ Time* thisUpdate,
+                          /*optional out*/ Time* validThrough)
 {
   // Always initialize this to something reasonable.
   expired = false;
 
   Context context(trustDomain, certID, time, maxOCSPLifetimeInDays,
                   thisUpdate, validThrough);
 
   Reader input(encodedResponse);
@@ -492,17 +488,17 @@ ResponseData(Reader& input, Context& con
   // before verifying its signature.
   rv = VerifySignature(context, responderIDType, responderID, certs,
                        signedResponseData);
   if (rv != Success) {
     return rv;
   }
 
   // TODO: Do we even need to parse this? Should we just skip it?
-  PRTime producedAt;
+  Time producedAt(Time::uninitialized);
   rv = der::GeneralizedTime(input, producedAt);
   if (rv != Success) {
     return rv;
   }
 
   // We don't accept an empty sequence of responses. In practice, a legit OCSP
   // responder will never return an empty response, and handling the case of an
   // empty response makes things unnecessarily complicated.
@@ -586,59 +582,74 @@ SingleResponse(Reader& input, Context& c
 
   // http://tools.ietf.org/html/rfc6960#section-3.2
   // 5. The time at which the status being indicated is known to be
   //    correct (thisUpdate) is sufficiently recent;
   // 6. When available, the time at or before which newer information will
   //    be available about the status of the certificate (nextUpdate) is
   //    greater than the current time.
 
-  const PRTime maxLifetime =
-    context.maxLifetimeInDays * ONE_DAY;
-
-  PRTime thisUpdate;
+  Time thisUpdate(Time::uninitialized);
   rv = der::GeneralizedTime(input, thisUpdate);
   if (rv != Success) {
     return rv;
   }
 
-  if (thisUpdate > context.time + SLOP) {
+  static const uint64_t SLOP_SECONDS = Time::ONE_DAY_IN_SECONDS;
+
+  Time timePlusSlop(context.time);
+  rv = timePlusSlop.AddSeconds(SLOP_SECONDS);
+  if (rv != Success) {
+    return rv;
+  }
+  if (thisUpdate > timePlusSlop) {
     return Result::ERROR_OCSP_FUTURE_RESPONSE;
   }
 
-  PRTime notAfter;
+  Time notAfter(Time::uninitialized);
   static const uint8_t NEXT_UPDATE_TAG =
     der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
   if (input.Peek(NEXT_UPDATE_TAG)) {
-    PRTime nextUpdate;
+    Time nextUpdate(Time::uninitialized);
     rv = der::Nested(input, NEXT_UPDATE_TAG,
                     bind(der::GeneralizedTime, _1, ref(nextUpdate)));
     if (rv != Success) {
       return rv;
     }
 
     if (nextUpdate < thisUpdate) {
       return Result::ERROR_OCSP_MALFORMED_RESPONSE;
     }
-    if (nextUpdate - thisUpdate <= maxLifetime) {
+    notAfter = thisUpdate;
+    if (notAfter.AddSeconds(context.maxLifetimeInDays *
+                            Time::ONE_DAY_IN_SECONDS) != Success) {
+      // This could only happen if we're dealing with times beyond the year
+      // 10,000AD.
+      return Result::ERROR_OCSP_FUTURE_RESPONSE;
+    }
+    if (nextUpdate <= notAfter) {
       notAfter = nextUpdate;
-    } else {
-      notAfter = thisUpdate + maxLifetime;
     }
   } else {
     // NSS requires all OCSP responses without a nextUpdate to be recent.
     // Match that stricter behavior.
-    notAfter = thisUpdate + ONE_DAY;
+    notAfter = thisUpdate;
+    if (notAfter.AddSeconds(Time::ONE_DAY_IN_SECONDS) != Success) {
+      // This could only happen if we're dealing with times beyond the year
+      // 10,000AD.
+      return Result::ERROR_OCSP_FUTURE_RESPONSE;
+    }
   }
 
-  if (context.time < SLOP) { // prevent underflow
-    return Result::FATAL_ERROR_INVALID_ARGS;
+  Time timeMinusSlop(context.time);
+  rv = timeMinusSlop.SubtractSeconds(SLOP_SECONDS);
+  if (rv != Success) {
+    return rv;
   }
-
-  if (context.time - SLOP > notAfter) {
+  if (timeMinusSlop > notAfter) {
     context.expired = true;
   }
 
   rv = der::OptionalExtensions(input,
                                der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
                                ExtensionNotUnderstood);
   if (rv != Success) {
     return rv;
new file mode 100644
--- /dev/null
+++ b/lib/mozpkix/lib/pkixtime.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* 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/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkix/Time.h"
+#include "pkixutil.h"
+#ifdef WIN32
+#include "windows.h"
+#else
+#include "sys/time.h"
+#endif
+
+namespace mozilla { namespace pkix {
+
+Time
+Now()
+{
+  uint64_t seconds;
+
+#ifdef WIN32
+  // "Contains a 64-bit value representing the number of 100-nanosecond
+  // intervals since January 1, 1601 (UTC)."
+  //   - http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
+  FILETIME ft;
+  GetSystemTimeAsFileTime(&ft);
+  uint64_t ft64 = (static_cast<uint64_t>(ft.dwHighDateTime) << 32) |
+                  ft.dwLowDateTime;
+  seconds = (DaysBeforeYear(1601) * Time::ONE_DAY_IN_SECONDS) +
+            ft64 / (1000u * 1000u * 1000u / 100u);
+#else
+  // "The gettimeofday() function shall obtain the current time, expressed as
+  // seconds and microseconds since the Epoch."
+  //   - http://pubs.opengroup.org/onlinepubs/009695399/functions/gettimeofday.html
+  timeval tv;
+  (void) gettimeofday(&tv, nullptr);
+  seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) + tv.tv_sec;
+#endif
+
+  return TimeFromElapsedSecondsAD(seconds);
+}
+
+} } // namespace mozilla::pkix
--- a/lib/mozpkix/lib/pkixutil.h
+++ b/lib/mozpkix/lib/pkixutil.h
@@ -177,11 +177,22 @@ public:
 private:
   Input items[MAX_LENGTH]; // avoids any heap allocations
   size_t numItems;
 
   NonOwningDERArray(const NonOwningDERArray&) /* = delete*/;
   void operator=(const NonOwningDERArray&) /* = delete*/;
 };
 
+inline unsigned int
+DaysBeforeYear(unsigned int year)
+{
+  PR_ASSERT(year >= 1);
+  PR_ASSERT(year <= 9999);
+  return ((year - 1u) * 365u)
+       + ((year - 1u) / 4u)    // leap years are every 4 years,
+       - ((year - 1u) / 100u)  // except years divisible by 100,
+       + ((year - 1u) / 400u); // except years divisible by 400.
+}
+
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix__pkixutil_h
--- a/lib/mozpkix/moz.build
+++ b/lib/mozpkix/moz.build
@@ -7,16 +7,17 @@
 SOURCES += [
     'lib/pkixbind.cpp',
     'lib/pkixbuild.cpp',
     'lib/pkixcert.cpp',
     'lib/pkixcheck.cpp',
     'lib/pkixder.cpp',
     'lib/pkixnss.cpp',
     'lib/pkixocsp.cpp',
+    'lib/pkixtime.cpp',
 ]
 
 LOCAL_INCLUDES += [
     'include',
 ]
 
 TEST_DIRS += [
     'test/gtest',
--- a/lib/mozpkix/test/gtest/nssgtest.cpp
+++ b/lib/mozpkix/test/gtest/nssgtest.cpp
@@ -80,26 +80,28 @@ Pred_SECFailure(const char* expectedExpr
 
 /*static*/ void
 NSSTest::SetUpTestCase()
 {
   if (NSS_NoDB_Init(nullptr) != SECSuccess) {
     PR_Abort();
   }
 
-  now = PR_Now();
-  oneDayBeforeNow = now - ONE_DAY;
-  oneDayAfterNow = now + ONE_DAY;
+  now = Now();
+  pr_now = PR_Now();
+  pr_oneDayBeforeNow = pr_now - ONE_DAY;
+  pr_oneDayAfterNow = pr_now + ONE_DAY;
 }
 
 NSSTest::NSSTest()
   : arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE))
 {
   if (!arena) {
     PR_Abort();
   }
 }
 
-/*static*/ PRTime NSSTest::now;
-/*static*/ PRTime NSSTest::oneDayBeforeNow;
-/*static*/ PRTime NSSTest::oneDayAfterNow;
+/*static*/ mozilla::pkix::Time NSSTest::now(Now());
+/*static*/ PRTime NSSTest::pr_now;
+/*static*/ PRTime NSSTest::pr_oneDayBeforeNow;
+/*static*/ PRTime NSSTest::pr_oneDayAfterNow;
 
 } } } // namespace mozilla::pkix::test
--- a/lib/mozpkix/test/gtest/nssgtest.h
+++ b/lib/mozpkix/test/gtest/nssgtest.h
@@ -98,16 +98,17 @@ class NSSTest : public ::testing::Test
 {
 public:
   static void SetUpTestCase();
 
 protected:
   NSSTest();
 
   ScopedPLArenaPool arena;
-  static PRTime now;
-  static PRTime oneDayBeforeNow;
-  static PRTime oneDayAfterNow;
+  static mozilla::pkix::Time now;
+  static PRTime pr_now;
+  static PRTime pr_oneDayBeforeNow;
+  static PRTime pr_oneDayAfterNow;
 };
 
 } } } // namespace mozilla::pkix::test
 
 #endif // mozilla_pkix__nssgtest_h
--- a/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
+++ b/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
@@ -121,24 +121,24 @@ private:
       trustLevel = TrustLevel::TrustAnchor;
     } else {
       trustLevel = TrustLevel::InheritsTrust;
     }
     return Success;
   }
 
   virtual Result FindIssuer(Input encodedIssuerName,
-                            IssuerChecker& checker, PRTime time)
+                            IssuerChecker& checker, Time time)
   {
     SECItem encodedIssuerNameSECItem =
       UnsafeMapInputToSECItem(encodedIssuerName);
     ScopedCERTCertList
       candidates(CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
-                                            &encodedIssuerNameSECItem, time,
-                                            true));
+                                            &encodedIssuerNameSECItem, 0,
+                                            false));
     if (candidates) {
       for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
            !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
         bool keepGoing;
         Input derCert;
         Result rv = derCert.Init(n->cert->derCert.data, n->cert->derCert.len);
         EXPECT_EQ(Success, rv);
         if (rv != Success) {
@@ -153,17 +153,17 @@ private:
           break;
         }
       }
     }
 
     return Success;
   }
 
-  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, PRTime,
+  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
                                  /*optional*/ const Input*,
                                  /*optional*/ const Input*)
   {
     return Success;
   }
 
   virtual Result IsChainValid(const DERArray&)
   {
--- a/lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp
+++ b/lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp
@@ -77,23 +77,23 @@ private:
                               Input candidateCert,
                               /*out*/ TrustLevel& trustLevel)
   {
     trustLevel = TrustLevel::TrustAnchor;
     return Success;
   }
 
   virtual Result FindIssuer(Input /*encodedIssuerName*/,
-                            IssuerChecker& /*checker*/, PRTime /*time*/)
+                            IssuerChecker& /*checker*/, Time /*time*/)
   {
     ADD_FAILURE();
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
-  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, PRTime,
+  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
                                  /*optional*/ const Input*,
                                  /*optional*/ const Input*)
   {
     return Success;
   }
 
   virtual Result IsChainValid(const DERArray&)
   {
--- a/lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
+++ b/lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
@@ -26,45 +26,45 @@
 #include "pkix/pkixtypes.h"
 #include "pkixtestutil.h"
 
 using namespace mozilla::pkix;
 using namespace mozilla::pkix::test;
 
 namespace mozilla { namespace pkix {
 
-Result CheckValidity(const Input encodedValidity, PRTime time);
+Result CheckValidity(const Input encodedValidity, Time time);
 
 } } // namespace mozilla::pkix
 
-static const PRTime PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56));
+static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56));
 
 #define OLDER_GENERALIZEDTIME \
   0x18, 15,                               /* tag, length */ \
   '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \
   '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
 
 #define OLDER_UTCTIME \
   0x17, 13,                               /* tag, length */ \
   '9', '9', '0', '1', '0', '1',           /* (19)99-01-01 */ \
   '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
 
-static const PRTime NOW(YMDHMS(2016, 12, 31, 12, 23, 56));
+static const Time NOW(YMDHMS(2016, 12, 31, 12, 23, 56));
 
 #define NEWER_GENERALIZEDTIME \
   0x18, 15,                               /* tag, length */ \
   '2', '0', '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
   '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
 
 #define NEWER_UTCTIME \
   0x17, 13,                               /* tag, length */ \
   '2', '1', '0', '1', '0', '1',           /* 2021-01-01 */ \
   '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
 
-static const PRTime FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
+static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
 
 class pkixcheck_CheckValidity : public ::testing::Test { };
 
 TEST_F(pkixcheck_CheckValidity, BothEmptyNull)
 {
   static const uint8_t DER[] = {
     0x17/*UTCTime*/, 0/*length*/,
     0x17/*UTCTime*/, 0/*length*/,
--- a/lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp
+++ b/lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp
@@ -310,17 +310,17 @@ TEST_F(pkixder_universal_types_tests, En
 // e.g. TWO_CHARS(53) => '5', '3'
 #define TWO_CHARS(t) static_cast<uint8_t>('0' + ((t) / 10u)), \
                      static_cast<uint8_t>('0' + ((t) % 10u))
 
 // Calls TimeChoice on the UTCTime variant of the given generalized time.
 template <uint16_t LENGTH>
 Result
 TimeChoiceForEquivalentUTCTime(const uint8_t (&generalizedTimeDER)[LENGTH],
-                               /*out*/ PRTime& value)
+                               /*out*/ Time& value)
 {
   static_assert(LENGTH >= 4,
                 "TimeChoiceForEquivalentUTCTime input too small");
   uint8_t utcTimeDER[LENGTH - 2];
   utcTimeDER[0] = 0x17; // tag UTCTime
   utcTimeDER[1] = LENGTH - 1/*tag*/ - 1/*value*/ - 2/*century*/;
   // Copy the value except for the first two digits of the year
   for (size_t i = 2; i < LENGTH - 2; ++i) {
@@ -329,69 +329,69 @@ TimeChoiceForEquivalentUTCTime(const uin
 
   Input input(utcTimeDER);
   Reader reader(input);
   return TimeChoice(reader, value);
 }
 
 template <uint16_t LENGTH>
 void
-ExpectGoodTime(PRTime expectedValue,
+ExpectGoodTime(Time expectedValue,
                const uint8_t (&generalizedTimeDER)[LENGTH])
 {
   // GeneralizedTime
   {
     Input input(generalizedTimeDER);
     Reader reader(input);
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Success, GeneralizedTime(reader, value));
     EXPECT_EQ(expectedValue, value);
   }
 
   // TimeChoice: GeneralizedTime
   {
     Input input(generalizedTimeDER);
     Reader reader(input);
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Success, TimeChoice(reader, value));
     EXPECT_EQ(expectedValue, value);
   }
 
   // TimeChoice: UTCTime
   {
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Success,
               TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value));
     EXPECT_EQ(expectedValue, value);
   }
 }
 
 template <uint16_t LENGTH>
 void
 ExpectBadTime(const uint8_t (&generalizedTimeDER)[LENGTH])
 {
   // GeneralizedTime
   {
     Input input(generalizedTimeDER);
     Reader reader(input);
-    PRTime value;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME, GeneralizedTime(reader, value));
   }
 
   // TimeChoice: GeneralizedTime
   {
     Input input(generalizedTimeDER);
     Reader reader(input);
-    PRTime value;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME, TimeChoice(reader, value));
   }
 
   // TimeChoice: UTCTime
   {
-    PRTime value;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME,
               TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value));
   }
 }
 
 // Control value: a valid time
 TEST_F(pkixder_universal_types_tests, ValidControl)
 {
@@ -416,17 +416,17 @@ TEST_F(pkixder_universal_types_tests, Ti
 
 TEST_F(pkixder_universal_types_tests, TimeInvalidZeroLength)
 {
   const uint8_t DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH[] = {
     0x18,                           // GeneralizedTime
     0x00                            // Length = 0
   };
 
-  PRTime value;
+  Time value(Time::uninitialized);
 
   // GeneralizedTime
   Input gtBuf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH);
   Reader gt(gtBuf);
   ASSERT_EQ(Result::ERROR_INVALID_TIME, GeneralizedTime(gt, value));
 
   // TimeChoice: GeneralizedTime
   Input tc_gt_buf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH);
@@ -507,43 +507,43 @@ TEST_F(pkixder_universal_types_tests, Ge
     const uint8_t DER[] = {
       0x18,                           // Generalized Time
       15,                             // Length = 15
       TWO_CHARS(i / 100), TWO_CHARS(i % 100), // YYYY
       '1', '2', '3', '1', // 12-31
       '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z
     };
 
-    PRTime expectedValue = YMDHMS(i, 12, 31, 23, 59, 59);
+    Time expectedValue = YMDHMS(i, 12, 31, 23, 59, 59);
 
     // We have to test GeneralizedTime separately from UTCTime instead of using
     // ExpectGooDtime because the range of UTCTime is less than the range of
     // GeneralizedTime.
 
     // GeneralizedTime
     {
       Input input(DER);
       Reader reader(input);
-      PRTime value = 0;
+      Time value(Time::uninitialized);
       ASSERT_EQ(Success, GeneralizedTime(reader, value));
       EXPECT_EQ(expectedValue, value);
     }
 
     // TimeChoice: GeneralizedTime
     {
       Input input(DER);
       Reader reader(input);
-      PRTime value = 0;
+      Time value(Time::uninitialized);
       ASSERT_EQ(Success, TimeChoice(reader, value));
       EXPECT_EQ(expectedValue, value);
     }
 
     // TimeChoice: UTCTime, which is limited to years less than 2049.
     if (i <= 2049) {
-      PRTime value = 0;
+      Time value(Time::uninitialized);
       ASSERT_EQ(Success, TimeChoiceForEquivalentUTCTime(DER, value));
       EXPECT_EQ(expectedValue, value);
     }
   }
 }
 
 // In order to ensure we we don't run into any trouble with conversions to and
 // from time_t we only accept times from 1970 onwards.
@@ -666,32 +666,32 @@ TEST_F(pkixder_universal_types_tests, Ti
     0x18,                           // Generalized Time
     15,                             // Length = 15
     '2', '4', '0', '0', '0', '2', '2', '9', // 2400-02-29
     '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
   };
 
   // We don't use ExpectGoodTime here because UTCTime can't represent 2400.
 
-  PRTime expectedValue = YMDHMS(2400, 2, 29, 16, 45, 40);
+  Time expectedValue = YMDHMS(2400, 2, 29, 16, 45, 40);
 
   // GeneralizedTime
   {
     Input input(DER);
     Reader reader(input);
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Success, GeneralizedTime(reader, value));
     EXPECT_EQ(expectedValue, value);
   }
 
   // TimeChoice: GeneralizedTime
   {
     Input input(DER);
     Reader reader(input);
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Success, TimeChoice(reader, value));
     EXPECT_EQ(expectedValue, value);
   }
 }
 
 TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2014)
 {
   static const uint8_t DER[] = {
@@ -713,25 +713,25 @@ TEST_F(pkixder_universal_types_tests, Ti
   };
 
   // We don't use ExpectBadTime here because UTCTime can't represent 2100.
 
   // GeneralizedTime
   {
     Input input(DER);
     Reader reader(input);
-    PRTime value;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME, GeneralizedTime(reader, value));
   }
 
   // TimeChoice: GeneralizedTime
   {
     Input input(DER);
     Reader reader(input);
-    PRTime value;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME, TimeChoice(reader, value));
   }
 }
 
 TEST_F(pkixder_universal_types_tests, TimeHoursValidRange)
 {
   for (uint8_t i = 0; i <= 23; ++i) {
     const uint8_t DER[] = {
@@ -851,25 +851,25 @@ TEST_F(pkixder_universal_types_tests, Ti
   // We can't use ExpectBadTime here, because ExpectBadTime requires
   // consistent results for GeneralizedTime and UTCTime, but the results
   // for this input are different.
 
   // GeneralizedTime
   {
     Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR);
     Reader reader(input);
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME, GeneralizedTime(reader, value));
   }
 
   // TimeChoice: GeneralizedTime
   {
     Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR);
     Reader reader(input);
-    PRTime value = 0;
+    Time value(Time::uninitialized);
     ASSERT_EQ(Result::ERROR_INVALID_TIME, TimeChoice(reader, value));
   }
 
   // This test is not applicable to TimeChoice: UTCTime
 }
 
 TEST_F(pkixder_universal_types_tests, TimeInvalidYearChar)
 {
--- a/lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
+++ b/lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
@@ -37,23 +37,23 @@ class CreateEncodedOCSPRequestTrustDomai
 private:
   virtual Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
                               Input, /*out*/ TrustLevel&)
   {
     ADD_FAILURE();
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
-  virtual Result FindIssuer(Input, IssuerChecker&, PRTime)
+  virtual Result FindIssuer(Input, IssuerChecker&, Time)
   {
     ADD_FAILURE();
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
-  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, PRTime,
+  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
                                  const Input*, const Input*)
   {
     ADD_FAILURE();
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   virtual Result IsChainValid(const DERArray&)
   {
--- a/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
+++ b/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -46,24 +46,24 @@ public:
   Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&,
                       Input, /*out*/ TrustLevel& trustLevel)
   {
     EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
     trustLevel = TrustLevel::InheritsTrust;
     return Success;
   }
 
-  Result FindIssuer(Input, IssuerChecker&, PRTime)
+  Result FindIssuer(Input, IssuerChecker&, Time)
   {
     ADD_FAILURE();
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   virtual Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&,
-                                 PRTime time, /*optional*/ const Input*,
+                                 Time time, /*optional*/ const Input*,
                                  /*optional*/ const Input*)
   {
     // TODO: I guess mozilla::pkix should support revocation of designated
     // OCSP responder eventually, but we don't now, so this function should
     // never get called.
     ADD_FAILURE();
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
@@ -199,17 +199,17 @@ class pkixocsp_VerifyEncodedResponse_Wit
 {
 protected:
   // The result is owned by the arena
   Input CreateEncodedOCSPErrorResponse(uint8_t status)
   {
     static const Input EMPTY;
     OCSPResponseContext context(arena.get(),
                                 CertID(EMPTY, EMPTY, EMPTY),
-                                oneDayBeforeNow);
+                                pr_oneDayBeforeNow);
     context.responseStatus = status;
     context.skipResponseBytes = true;
     SECItem* response = CreateEncodedOCSPResponse(context);
     EXPECT_TRUE(response);
     // The result will be an empty Input on failure, but it doesn't
     // matter because the test is going to fail anyway.
     Input result;
     EXPECT_EQ(Success, result.Init(response->data, response->len));
@@ -291,74 +291,74 @@ public:
     return result;
   }
 };
 
 TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey)
 {
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID, byKey,
-                         rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                         &oneDayAfterNow));
+                         rootPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow));
   bool expired;
   ASSERT_EQ(Success,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
                                       now, END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byName)
 {
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID, rootName,
-                         rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                         &oneDayAfterNow));
+                         rootPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow));
   bool expired;
   ASSERT_EQ(Success,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey_without_nextUpdate)
 {
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID, byKey,
-                         rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                         nullptr));
+                         rootPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, nullptr));
   bool expired;
   ASSERT_EQ(Success,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_successful, revoked)
 {
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::revoked, *endEntityCertID, byKey,
-                         rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                         &oneDayAfterNow));
+                         rootPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow));
   bool expired;
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_successful, unknown)
 {
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::unknown, *endEntityCertID, byKey,
-                         rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                         &oneDayAfterNow));
+                         rootPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -399,35 +399,37 @@ protected:
         ? CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                                     ExtensionCriticality::NotCritical)
         : nullptr,
       nullptr
     };
     ScopedSECKEYPrivateKey signerPrivateKey;
     SECItem* signerDER(CreateEncodedCertificate(
                           arena.get(), ++rootIssuedCount, rootName,
-                          oneDayBeforeNow, oneDayAfterNow, certSubjectName,
+                          pr_oneDayBeforeNow, pr_oneDayAfterNow,
+                          certSubjectName,
                           signerEKU != SEC_OID_UNKNOWN ? extensions : nullptr,
                           rootPrivateKey.get(), signerPrivateKey));
     EXPECT_TRUE(signerDER);
     if (signerDEROut) {
       EXPECT_EQ(Success,
                 signerDEROut->Init(signerDER->data, signerDER->len));
     }
 
     const SECItem* signerNameDER = nullptr;
     if (signerName) {
       signerNameDER = ASCIIToDERName(arena.get(), signerName);
       EXPECT_TRUE(signerNameDER);
     }
     SECItem const* const certs[] = { signerDER, nullptr };
     return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID,
                                                signerName, signerPrivateKey,
-                                               oneDayBeforeNow, oneDayBeforeNow,
-                                               &oneDayAfterNow, certs);
+                                               pr_oneDayBeforeNow,
+                                               pr_oneDayBeforeNow,
+                                               &pr_oneDayAfterNow, certs);
   }
 
   static SECItem* CreateEncodedCertificate(PLArenaPool* arena,
                                            uint32_t serialNumber,
                                            const char* issuer,
                                            PRTime notBefore,
                                            PRTime notAfter,
                                            const char* subject,
@@ -487,18 +489,18 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
        good_byKey_missing_signer)
 {
   ScopedSECKEYPublicKey missingSignerPublicKey;
   ScopedSECKEYPrivateKey missingSignerPrivateKey;
   ASSERT_SECSuccess(GenerateKeyPair(missingSignerPublicKey,
                                     missingSignerPrivateKey));
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID, byKey,
-                         missingSignerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, nullptr));
+                         missingSignerPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, nullptr));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -507,17 +509,17 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
 {
   ScopedSECKEYPublicKey missingSignerPublicKey;
   ScopedSECKEYPrivateKey missingSignerPrivateKey;
   ASSERT_SECSuccess(GenerateKeyPair(missingSignerPublicKey,
                                     missingSignerPrivateKey));
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
                          "CN=missing", missingSignerPrivateKey,
-                         oneDayBeforeNow, oneDayBeforeNow, nullptr));
+                         pr_oneDayBeforeNow, pr_oneDayBeforeNow, nullptr));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -529,28 +531,28 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
                                               rootName,
-                                              now - (10 * ONE_DAY),
-                                              now - (2 * ONE_DAY),
+                                              pr_now - (10 * ONE_DAY),
+                                              pr_now - (2 * ONE_DAY),
                                               signerName, extensions,
                                               rootPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   SECItem const* const certs[] = { signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
-                         signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         signerName, signerPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow, certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future)
@@ -561,28 +563,28 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
                                               rootName,
-                                              now + (2 * ONE_DAY),
-                                              now + (10 * ONE_DAY),
+                                              pr_now + (2 * ONE_DAY),
+                                              pr_now + (10 * ONE_DAY),
                                               signerName, extensions,
                                               rootPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   SECItem const* const certs[] = { signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
-                         signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         signerName, signerPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow, certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -656,27 +658,27 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1,
-                        subCAName, oneDayBeforeNow, oneDayAfterNow,
+                        subCAName, pr_oneDayBeforeNow, pr_oneDayAfterNow,
                         signerName, extensions, unknownPrivateKey.get(),
                         signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   // OCSP response signed by that delegated responder
   SECItem const* const certs[] = { signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
-                         signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         signerName, signerPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow, certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -693,44 +695,45 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   const SECItem* subCAExtensions[] = {
     CreateEncodedBasicConstraints(arena.get(), true, 0,
                                   ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey subCAPrivateKey;
   SECItem* subCADER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
                                              rootName,
-                                             oneDayBeforeNow, oneDayAfterNow,
+                                             pr_oneDayBeforeNow, pr_oneDayAfterNow,
                                              subCAName, subCAExtensions,
                                              rootPrivateKey.get(),
                                              subCAPrivateKey));
   ASSERT_TRUE(subCADER);
 
   // Delegated responder cert signed by that sub-CA
   static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1, subCAName,
-                                              oneDayBeforeNow, oneDayAfterNow,
+                                              pr_oneDayBeforeNow,
+                                              pr_oneDayAfterNow,
                                               signerName, extensions,
                                               subCAPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   // OCSP response signed by the delegated responder issued by the sub-CA
   // that is trying to impersonate the root.
   SECItem const* const certs[] = { subCADER, signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
-                         signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         signerName, signerPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow, certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -747,44 +750,46 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   const SECItem* subCAExtensions[] = {
     CreateEncodedBasicConstraints(arena.get(), true, 0,
                                   ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey subCAPrivateKey;
   SECItem* subCADER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
                                              rootName,
-                                             oneDayBeforeNow, oneDayAfterNow,
+                                             pr_oneDayBeforeNow,
+                                             pr_oneDayAfterNow,
                                              subCAName, subCAExtensions,
                                              rootPrivateKey.get(),
                                              subCAPrivateKey));
   ASSERT_TRUE(subCADER);
 
   // Delegated responder cert signed by that sub-CA
   static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1, subCAName,
-                                              oneDayBeforeNow, oneDayAfterNow,
+                                              pr_oneDayBeforeNow,
+                                              pr_oneDayAfterNow,
                                               signerName, extensions,
                                               subCAPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   // OCSP response signed by the delegated responder issued by the sub-CA
   // that is trying to impersonate the root.
   SECItem const* const certs[] = { signerDER, subCADER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
-                         signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         signerName, signerPrivateKey, pr_oneDayBeforeNow,
+                         pr_oneDayBeforeNow, &pr_oneDayAfterNow, certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
--- a/lib/mozpkix/test/lib/pkixtestutil.cpp
+++ b/lib/mozpkix/test/lib/pkixtestutil.cpp
@@ -28,16 +28,17 @@
 #include <limits>
 #include <new>
 
 #include "cryptohi.h"
 #include "hasht.h"
 #include "pk11pub.h"
 #include "pkix/pkixnss.h"
 #include "pkixder.h"
+#include "pkixutil.h"
 #include "prerror.h"
 #include "prinit.h"
 #include "prprf.h"
 #include "secder.h"
 #include "secerr.h"
 
 using namespace std;
 
@@ -487,31 +488,34 @@ PRTimeToTimeChoice(PLArenaPool* arena, P
 {
   PRExplodedTime exploded;
   PR_ExplodeTime(time, PR_GMTParameters, &exploded);
   return PRTimeToEncodedTime(arena, time,
     (exploded.tm_year >= 1950 && exploded.tm_year < 2050) ? UTCTime
                                                           : GeneralizedTime);
 }
 
-PRTime
+Time
 YMDHMS(int16_t year, int16_t month, int16_t day,
        int16_t hour, int16_t minutes, int16_t seconds)
 {
   PRExplodedTime tm;
   tm.tm_usec = 0;
   tm.tm_sec = seconds;
   tm.tm_min = minutes;
   tm.tm_hour = hour;
   tm.tm_mday = day;
   tm.tm_month = month - 1; // tm_month is zero-based
   tm.tm_year = year;
   tm.tm_params.tp_gmt_offset = 0;
   tm.tm_params.tp_dst_offset = 0;
-  return PR_ImplodeTime(&tm);
+  PRTime time = PR_ImplodeTime(&tm);
+  return TimeFromElapsedSecondsAD((time / PR_USEC_PER_SEC) +
+                                  (DaysBeforeYear(1970) *
+                                   Time::ONE_DAY_IN_SECONDS));
 }
 
 static SECItem*
 SignedData(PLArenaPool* arena, const SECItem* tbsData,
            SECKEYPrivateKey* privKey, SECOidTag hashAlg,
            bool corrupt, /*optional*/ SECItem const* const* certs)
 {
   PR_ASSERT(arena);
--- a/lib/mozpkix/test/lib/pkixtestutil.h
+++ b/lib/mozpkix/test/lib/pkixtestutil.h
@@ -71,18 +71,18 @@ typedef mozilla::pkix::ScopedPtr<SECKEYP
 typedef mozilla::pkix::ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey>
   ScopedSECKEYPrivateKey;
 
 FILE* OpenFile(const char* dir, const char* filename, const char* mode);
 
 extern const PRTime ONE_DAY;
 
 // e.g. YMDHMS(2016, 12, 31, 1, 23, 45) => 2016-12-31:01:23:45 (GMT)
-PRTime YMDHMS(int16_t year, int16_t month, int16_t day,
-              int16_t hour, int16_t minutes, int16_t seconds);
+mozilla::pkix::Time YMDHMS(int16_t year, int16_t month, int16_t day,
+                           int16_t hour, int16_t minutes, int16_t seconds);
 
 SECStatus GenerateKeyPair(/*out*/ ScopedSECKEYPublicKey& publicKey,
                           /*out*/ ScopedSECKEYPrivateKey& privateKey);
 
 // The result will be owned by the arena
 const SECItem* ASCIIToDERName(PLArenaPool* arena, const char* cn);
 
 // Replace one substring in item with another of the same length, but only if