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[] = {