security: avoid ValidityPeriod under/overflow

refs #5176

Change-Id: I15f2225ea727b205798db29d14ef49a7565eb82e
diff --git a/ndn-cxx/security/validity-period.cpp b/ndn-cxx/security/validity-period.cpp
index 1c911a8..1d49147 100644
--- a/ndn-cxx/security/validity-period.cpp
+++ b/ndn-cxx/security/validity-period.cpp
@@ -45,9 +45,8 @@
 
 ValidityPeriod::ValidityPeriod(const time::system_clock::time_point& notBefore,
                                const time::system_clock::time_point& notAfter)
-  : m_notBefore(time_point_cast<TimePoint::duration>(notBefore + TimePoint::duration(1) -
-                                                     time::system_clock::time_point::duration(1)))
-  , m_notAfter(time_point_cast<TimePoint::duration>(notAfter))
+  : m_notBefore(toTimePointCeil(notBefore))
+  , m_notAfter(toTimePointFloor(notAfter))
 {
 }
 
@@ -114,24 +113,56 @@
   }
 
   try {
-    m_notBefore = time_point_cast<TimePoint::duration>(
-                    time::fromIsoString(readString(m_wire.elements()[NOT_BEFORE_OFFSET])));
-    m_notAfter = time_point_cast<TimePoint::duration>(
-                   time::fromIsoString(readString(m_wire.elements()[NOT_AFTER_OFFSET])));
+    m_notBefore = decodeTimePoint(m_wire.elements()[NOT_BEFORE_OFFSET]);
+    m_notAfter = decodeTimePoint(m_wire.elements()[NOT_AFTER_OFFSET]);
   }
   catch (const std::bad_cast&) {
     NDN_THROW(Error("Invalid date format in NOT-BEFORE or NOT-AFTER field"));
   }
 }
 
+ValidityPeriod::TimePoint
+ValidityPeriod::toTimePointFloor(const time::system_clock::time_point& t)
+{
+  return TimePoint(boost::chrono::floor<TimePoint::duration>(t.time_since_epoch()));
+}
+
+ValidityPeriod::TimePoint
+ValidityPeriod::toTimePointCeil(const time::system_clock::time_point& t)
+{
+  return TimePoint(boost::chrono::ceil<TimePoint::duration>(t.time_since_epoch()));
+}
+
+ValidityPeriod::TimePoint
+ValidityPeriod::decodeTimePoint(const Block& element)
+{
+  // Bug #5176, prevent time::system_clock::time_point under/overflow
+  static const auto minTime = toTimePointCeil(time::system_clock::time_point::min());
+  static const auto maxTime = toTimePointFloor(time::system_clock::time_point::max());
+  static const auto minValue = time::toIsoString(minTime);
+  static const auto maxValue = time::toIsoString(maxTime);
+  BOOST_ASSERT(minValue.size() == ISO_DATETIME_SIZE);
+  BOOST_ASSERT(maxValue.size() == ISO_DATETIME_SIZE);
+
+  auto value = readString(element);
+  BOOST_ASSERT(value.size() == ISO_DATETIME_SIZE);
+
+  if (value < minValue) {
+    return minTime;
+  }
+  if (value > maxValue) {
+    return maxTime;
+  }
+  return time_point_cast<TimePoint::duration>(time::fromIsoString(value));
+}
+
 ValidityPeriod&
 ValidityPeriod::setPeriod(const time::system_clock::time_point& notBefore,
                           const time::system_clock::time_point& notAfter)
 {
   m_wire.reset();
-  m_notBefore = time_point_cast<TimePoint::duration>(notBefore + TimePoint::duration(1) -
-                                                     time::system_clock::time_point::duration(1));
-  m_notAfter = time_point_cast<TimePoint::duration>(notAfter);
+  m_notBefore = toTimePointCeil(notBefore);
+  m_notAfter = toTimePointFloor(notAfter);
   return *this;
 }
 
diff --git a/ndn-cxx/security/validity-period.hpp b/ndn-cxx/security/validity-period.hpp
index 4a9f69e..f4da2f4 100644
--- a/ndn-cxx/security/validity-period.hpp
+++ b/ndn-cxx/security/validity-period.hpp
@@ -53,67 +53,88 @@
   makeRelative(time::seconds validFrom, time::seconds validUntil,
                const time::system_clock::time_point& now = time::system_clock::now());
 
-  /** @brief Set validity period [UNIX epoch + 1 nanosecond, UNIX epoch] that is always invalid
+  /**
+   * @brief Create a validity period that is invalid for any timepoint.
    */
   ValidityPeriod();
 
-  /** @brief Create validity period from @p block
+  /**
+   * @brief Decode validity period from @p block .
    */
   explicit
   ValidityPeriod(const Block& block);
 
-  /** @brief Create validity period [@p notBefore, @p notAfter]
-   *  @param notBefore exclusive beginning of the validity period range
-   *  @param notAfter exclusive end of the validity period range
-   *
-   *  @note The supplied time points will be rounded up to the whole seconds:
-   *        - @p notBefore is rounded up the next whole second
-   *        - @p notAfter is truncated to the previous whole second
+  /**
+   * @brief Create validity period [@p notBefore, @p notAfter].
+   * @param notBefore exclusive beginning of the validity period range,
+   *                  to be rounded up to the next whole second.
+   * @param notAfter exclusive end of the validity period range,
+   *                  to be rounded down to the previous whole second.
    */
   ValidityPeriod(const time::system_clock::time_point& notBefore,
                  const time::system_clock::time_point& notAfter);
 
-  /** @brief Check if @p now falls within the validity period
-   *  @param now Time point to check if it falls within the period
-   *  @return periodBegin <= @p now and @p now <= periodEnd
+  /**
+   * @brief Check if @p now falls within the validity period.
+   * @param now Time point to check if it falls within the period
+   * @return notBefore <= @p now and @p now <= notAfter.
    */
   bool
   isValid(const time::system_clock::time_point& now = time::system_clock::now()) const;
 
-  /** @brief Set validity period [@p notBefore, @p notAfter]
-   *  @param notBefore exclusive beginning of the validity period range
-   *  @param notAfter exclusive end of the validity period range
-   *
-   *  @note The supplied time points will be rounded up to the whole seconds:
-   *        - @p notBefore is rounded up the next whole second
-   *        - @p notAfter is truncated to the previous whole second
+  /**
+   * @brief Set validity period [@p notBefore, @p notAfter].
+   * @param notBefore exclusive beginning of the validity period range,
+   *                  to be rounded up to the next whole second.
+   * @param notAfter exclusive end of the validity period range,
+   *                  to be rounded down to the previous whole second.
    */
   ValidityPeriod&
   setPeriod(const time::system_clock::time_point& notBefore,
             const time::system_clock::time_point& notAfter);
 
-  /** @brief Get the stored validity period
+  /**
+   * @brief Get the stored validity period.
    */
   std::pair<time::system_clock::time_point, time::system_clock::time_point>
   getPeriod() const;
 
-  /** @brief Fast encoding or block size estimation
+  /**
+   * @brief Fast encoding or block size estimation.
    */
   template<encoding::Tag TAG>
   size_t
   wireEncode(EncodingImpl<TAG>& encoder) const;
 
-  /** @brief Encode ValidityPeriod into TLV block
+  /**
+   * @brief Encode ValidityPeriod into TLV block.
    */
   const Block&
   wireEncode() const;
 
-  /** @brief Decode ValidityPeriod from TLV block
-   *  @throw Error when an invalid TLV block supplied
+  /**
+   * @brief Decode ValidityPeriod from TLV block.
+   * @throw Error when an invalid TLV block supplied.
+   *
+   * @note If either timestamp in @p wire is earlier than 1677-09-21 or later than 2262-04-11,
+   *       it will be adjusted to these dates so that they are representable as
+   *       @c time::system_clock::time_point type returned by getPeriod() method.
    */
   void
   wireDecode(const Block& wire);
 
+private:
+  using TimePoint = boost::chrono::time_point<time::system_clock, time::seconds>;
+
+  static TimePoint
+  toTimePointFloor(const time::system_clock::time_point& t);
+
+  static TimePoint
+  toTimePointCeil(const time::system_clock::time_point& t);
+
+  static TimePoint
+  decodeTimePoint(const Block& element);
+
 private: // EqualityComparable concept
   // NOTE: the following "hidden friend" operators are available via
   //       argument-dependent lookup only and must be defined inline.
@@ -132,8 +153,6 @@
   }
 
 private:
-  using TimePoint = boost::chrono::time_point<time::system_clock, time::seconds>;
-
   TimePoint m_notBefore;
   TimePoint m_notAfter;
 
diff --git a/tests/unit/security/validity-period.t.cpp b/tests/unit/security/validity-period.t.cpp
index 1fbd6b1..6281289 100644
--- a/tests/unit/security/validity-period.t.cpp
+++ b/tests/unit/security/validity-period.t.cpp
@@ -99,10 +99,25 @@
                     "(19700101T000000, 19791230T000000)");
 
   validity1.setPeriod(time::getUnixEpoch() + 1_ns,
-                      time::getUnixEpoch() + (10 * 365_days) + 1_ns);
+                      time::getUnixEpoch() + 3650_days + 1_ns);
   BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(validity1),
                     "(19700101T000001, 19791230T000000)");
 
+  validity1.setPeriod(time::getUnixEpoch() + 999999999_ns,
+                      time::getUnixEpoch() + 3650_days + 999999999_ns);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(validity1),
+                    "(19700101T000001, 19791230T000000)");
+
+  validity1.setPeriod(time::getUnixEpoch() - 2_days + 1_ns,
+                      time::getUnixEpoch() - 1_day + 1_ns);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(validity1),
+                    "(19691230T000001, 19691231T000000)");
+
+  validity1.setPeriod(time::getUnixEpoch() - 2_days + 999999999_ns,
+                      time::getUnixEpoch() - 1_day + 999999999_ns);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(validity1),
+                    "(19691230T000001, 19691231T000000)");
+
   BOOST_CHECK_EQUAL(ValidityPeriod(now, now).isValid(), true);
   BOOST_CHECK_EQUAL(ValidityPeriod(now + 1_s, now).isValid(), false);
 }
@@ -129,8 +144,28 @@
   BOOST_CHECK(v1.getPeriod() == v2.getPeriod());
 }
 
+const uint8_t VP2[] = {
+  0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x30, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, // 00010101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter
+      0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, // 99991231T235959
+      0x54, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39
+};
+
+BOOST_AUTO_TEST_CASE(DecodingLarge)
+{
+  ValidityPeriod v(Block{VP2});
+  BOOST_CHECK(v.isValid(time::fromIsoString("16770921T001245")));
+  BOOST_CHECK(v.isValid(time::fromIsoString("19010120T120000")));
+  BOOST_CHECK(v.isValid(time::fromIsoString("20230725T120000")));
+  BOOST_CHECK(v.isValid(time::fromIsoString("22001030T120000")));
+  BOOST_CHECK(v.isValid(time::fromIsoString("22620411T234716")));
+}
+
 const uint8_t VP_E1[] = {
-  0xfd, 0x00, 0xff, 0x26, // ValidityPeriod (error)
+  0xfd, 0x00, 0xff, 0x26, // ValidityPeriod (wrong TLV-TYPE)
     0xfd, 0x00, 0xfe, 0x0f, // NotBefore
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
@@ -141,7 +176,7 @@
 
 const uint8_t VP_E2[] = {
   0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
-    0xfd, 0x00, 0xff, 0x0f, // NotBefore (error)
+    0xfd, 0x00, 0xff, 0x0f, // NotBefore (wrong TLV-TYPE)
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
     0xfd, 0x00, 0xff, 0x0f, // NotAfter
@@ -154,7 +189,7 @@
     0xfd, 0x00, 0xfe, 0x0f, // NotBefore
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
-    0xfd, 0x00, 0xfe, 0x0f, // NotAfter (error)
+    0xfd, 0x00, 0xfe, 0x0f, // NotAfter (wrong TLV-TYPE)
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
 };
@@ -167,7 +202,7 @@
     0xfd, 0x00, 0xff, 0x0f, // NotAfter
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
-    0xfd, 0x00, 0xff, 0x0f, // NotAfter (error)
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter (duplicate)
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
 };
@@ -177,6 +212,7 @@
     0xfd, 0x00, 0xfe, 0x0f, // NotBefore
       0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
       0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+      // missing NotAfter
 };
 
 const uint8_t VP_E6[] = {