security: add validator for signed Interests
refs #4804
Change-Id: Ie6b64e9758591b54639c67e565abb3fdf40fb652
diff --git a/tests/unit/security/interest-signer.t.cpp b/tests/unit/security/interest-signer.t.cpp
index 9362c76..6d054ff 100644
--- a/tests/unit/security/interest-signer.t.cpp
+++ b/tests/unit/security/interest-signer.t.cpp
@@ -41,24 +41,28 @@
InterestSigner signer(m_keyChain);
Interest i1 = signer.makeCommandInterest("/hello/world");
BOOST_REQUIRE_EQUAL(i1.getName().size(), 6);
- BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_SIG_VALUE).blockFromValue().type(), tlv::SignatureValue);
- BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_SIG_INFO).blockFromValue().type(), tlv::SignatureInfo);
+ BOOST_TEST(i1.getName().at(command_interest::POS_SIG_VALUE).blockFromValue().type() == tlv::SignatureValue);
+ BOOST_TEST(i1.getName().at(command_interest::POS_SIG_INFO).blockFromValue().type() == tlv::SignatureInfo);
time::milliseconds timestamp = toUnixTimestamp(time::system_clock::now());
- BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_TIMESTAMP).toNumber(), timestamp.count());
+ BOOST_TEST(i1.getName().at(command_interest::POS_TIMESTAMP).toNumber() == timestamp.count());
Interest i2 = signer.makeCommandInterest("/hello/world/!", signingByIdentity("/test"));
BOOST_REQUIRE_EQUAL(i2.getName().size(), 7);
- BOOST_CHECK_EQUAL(i2.getName().at(command_interest::POS_SIG_VALUE).blockFromValue().type(), tlv::SignatureValue);
- BOOST_CHECK_EQUAL(i2.getName().at(command_interest::POS_SIG_INFO).blockFromValue().type(), tlv::SignatureInfo);
- BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP), i1.getName().at(command_interest::POS_TIMESTAMP));
+ BOOST_TEST(i2.getName().at(command_interest::POS_SIG_VALUE).blockFromValue().type() == tlv::SignatureValue);
+ BOOST_TEST(i2.getName().at(command_interest::POS_SIG_INFO).blockFromValue().type() == tlv::SignatureInfo);
+ // These doesn't play well with BOOST_TEST for some reason
+ BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP),
+ i1.getName().at(command_interest::POS_TIMESTAMP));
BOOST_CHECK_NE(i2.getName().at(command_interest::POS_RANDOM_VAL),
i1.getName().at(command_interest::POS_RANDOM_VAL)); // this sometimes can fail
advanceClocks(100_s);
i2 = signer.makeCommandInterest("/hello/world/!");
- BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP), i1.getName().at(command_interest::POS_TIMESTAMP));
+ // This doesn't play well with BOOST_TEST for some reason
+ BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP),
+ i1.getName().at(command_interest::POS_TIMESTAMP));
}
BOOST_AUTO_TEST_CASE(V03)
@@ -68,29 +72,51 @@
InterestSigner signer(m_keyChain);
Interest i1("/hello/world");
i1.setCanBePrefix(false);
- signer.makeSignedInterest(i1);
- BOOST_CHECK_EQUAL(i1.isSigned(), true);
- BOOST_REQUIRE_EQUAL(i1.getName().size(), 3);
- BOOST_REQUIRE(i1.getSignatureInfo());
+ signer.makeSignedInterest(i1, SigningInfo(),
+ InterestSigner::SigningFlags::WantNonce |
+ InterestSigner::SigningFlags::WantTime);
+ BOOST_TEST(i1.isSigned() == true);
+ BOOST_TEST_REQUIRE(i1.getName().size() == 3);
+ BOOST_TEST_REQUIRE(i1.getSignatureInfo().has_value());
+ BOOST_TEST(i1.getSignatureInfo()->getNonce().has_value() == true);
BOOST_TEST(*i1.getSignatureInfo()->getTime() == time::system_clock::now());
+ BOOST_TEST(i1.getSignatureInfo()->getSeqNum().has_value() == false);
Interest i2("/hello/world/!");
i2.setCanBePrefix(false);
- signer.makeSignedInterest(i2, signingByIdentity("/test"));
- BOOST_CHECK_EQUAL(i2.isSigned(), true);
+ signer.makeSignedInterest(i2, signingByIdentity("/test"),
+ InterestSigner::SigningFlags::WantNonce |
+ InterestSigner::SigningFlags::WantTime |
+ InterestSigner::SigningFlags::WantSeqNum);
+ BOOST_TEST(i2.isSigned() == true);
BOOST_REQUIRE_EQUAL(i2.getName().size(), 4);
BOOST_REQUIRE(i2.getSignatureInfo());
- BOOST_TEST(*i2.getSignatureInfo()->getTime() > *i1.getSignatureInfo()->getTime());
BOOST_TEST(*i2.getSignatureInfo()->getNonce() != *i1.getSignatureInfo()->getNonce());
+ BOOST_TEST(*i2.getSignatureInfo()->getTime() > *i1.getSignatureInfo()->getTime());
+ BOOST_TEST_REQUIRE(i2.getSignatureInfo()->getSeqNum().has_value() == true);
advanceClocks(100_s);
- signer.makeSignedInterest(i2);
- BOOST_CHECK_EQUAL(i2.isSigned(), true);
+ Interest i3("/hello/world/2");
+ i3.setCanBePrefix(false);
+ signer.makeSignedInterest(i3, SigningInfo(), InterestSigner::SigningFlags::WantSeqNum);
+ BOOST_TEST(i3.isSigned() == true);
+ BOOST_REQUIRE_EQUAL(i3.getName().size(), 4);
+ BOOST_REQUIRE(i3.getSignatureInfo());
- BOOST_TEST(*i2.getSignatureInfo()->getTime() == time::system_clock::now());
+ BOOST_TEST(i3.getSignatureInfo()->getNonce().has_value() == false);
+ BOOST_TEST(i3.getSignatureInfo()->getTime().has_value() == false);
+ BOOST_TEST_REQUIRE(i3.getSignatureInfo()->getSeqNum().has_value() == true);
+ BOOST_TEST(*i3.getSignatureInfo()->getSeqNum() > *i2.getSignatureInfo()->getSeqNum());
+
+ signer.makeSignedInterest(i3);
+ BOOST_TEST(i3.isSigned() == true);
+
+ BOOST_TEST(*i3.getSignatureInfo()->getTime() == time::system_clock::now());
+
+ BOOST_CHECK_THROW(signer.makeSignedInterest(i3, SigningInfo(), 0), std::invalid_argument);
}
BOOST_AUTO_TEST_SUITE_END() // TestInterestSigner
diff --git a/tests/unit/security/validation-policy-command-interest.t.cpp b/tests/unit/security/validation-policy-command-interest.t.cpp
index af9bc2a..9f32056 100644
--- a/tests/unit/security/validation-policy-command-interest.t.cpp
+++ b/tests/unit/security/validation-policy-command-interest.t.cpp
@@ -41,7 +41,7 @@
BOOST_AUTO_TEST_SUITE(Security)
-class DefaultOptions
+class CommandInterestDefaultOptions
{
public:
static ValidationPolicyCommandInterest::Options
@@ -89,7 +89,8 @@
CommandInterestSigner m_signer;
};
-BOOST_FIXTURE_TEST_SUITE(TestValidationPolicyCommandInterest, ValidationPolicyCommandInterestFixture<DefaultOptions>)
+BOOST_FIXTURE_TEST_SUITE(TestValidationPolicyCommandInterest,
+ ValidationPolicyCommandInterestFixture<CommandInterestDefaultOptions>)
BOOST_AUTO_TEST_SUITE(Accepts)
@@ -130,7 +131,7 @@
VALIDATE_SUCCESS(d1, "Should succeed (fallback on inner validation policy for data)");
}
-using ValidationPolicyAcceptAllCommands = ValidationPolicyCommandInterestFixture<DefaultOptions,
+using ValidationPolicyAcceptAllCommands = ValidationPolicyCommandInterestFixture<CommandInterestDefaultOptions,
ValidationPolicyAcceptAll>;
BOOST_FIXTURE_TEST_CASE(SignedWithSha256, ValidationPolicyAcceptAllCommands) // Bug 4635
diff --git a/tests/unit/security/validation-policy-signed-interest.t.cpp b/tests/unit/security/validation-policy-signed-interest.t.cpp
new file mode 100644
index 0000000..f46e5ad
--- /dev/null
+++ b/tests/unit/security/validation-policy-signed-interest.t.cpp
@@ -0,0 +1,567 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "ndn-cxx/security/validation-policy-signed-interest.hpp"
+#include "ndn-cxx/security/interest-signer.hpp"
+#include "ndn-cxx/security/signing-helpers.hpp"
+#include "ndn-cxx/security/validation-policy-accept-all.hpp"
+#include "ndn-cxx/security/validation-policy-simple-hierarchy.hpp"
+
+#include "tests/boost-test.hpp"
+#include "tests/make-interest-data.hpp"
+#include "tests/unit/security/validator-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace security {
+inline namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+
+class SignedInterestDefaultOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ return {};
+ }
+};
+
+template<class T, class InnerPolicy>
+class SignedInterestPolicyWrapper : public ValidationPolicySignedInterest
+{
+public:
+ SignedInterestPolicyWrapper()
+ : ValidationPolicySignedInterest(make_unique<InnerPolicy>(), T::getOptions())
+ {
+ }
+};
+
+template<class T, class InnerPolicy = ValidationPolicySimpleHierarchy>
+class ValidationPolicySignedInterestFixture
+ : public HierarchicalValidatorFixture<SignedInterestPolicyWrapper<T, InnerPolicy>>
+{
+public:
+ ValidationPolicySignedInterestFixture()
+ : m_signer(this->m_keyChain)
+ {
+ }
+
+ Interest
+ makeSignedInterest(const Identity& identity,
+ uint32_t signingFlags = InterestSigner::WantNonce | InterestSigner::WantTime)
+ {
+ Interest i(Name(identity.getName()).append("CMD"));
+ i.setCanBePrefix(false);
+ m_signer.makeSignedInterest(i, signingByIdentity(identity), signingFlags);
+ return i;
+ }
+
+public:
+ InterestSigner m_signer;
+
+ static constexpr uint32_t WantAll = InterestSigner::WantNonce |
+ InterestSigner::WantTime |
+ InterestSigner::WantSeqNum;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestValidationPolicySignedInterest,
+ ValidationPolicySignedInterestFixture<SignedInterestDefaultOptions>)
+
+BOOST_AUTO_TEST_CASE(BasicV3)
+{
+ auto i1 = makeSignedInterest(identity, WantAll);
+ VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
+ VALIDATE_FAILURE(i1, "Should fail (replay attack)");
+
+ advanceClocks(5_ms);
+ auto i2 = makeSignedInterest(identity, WantAll);
+ VALIDATE_SUCCESS(i2, "Should succeed (timestamp and sequence number larger than previous)");
+
+ Interest i3(Name(identity.getName()).append("CMD"));
+ i3.setCanBePrefix(false);
+ m_signer.makeSignedInterest(i3, signingWithSha256());
+ VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
+}
+
+BOOST_AUTO_TEST_CASE(DataPassthrough)
+{
+ Data d1("/Security/ValidatorFixture/Sub1");
+ m_keyChain.sign(d1);
+ VALIDATE_SUCCESS(d1, "Should succeed (fallback on inner validation policy for data)");
+}
+
+BOOST_AUTO_TEST_CASE(InnerPolicyReject)
+{
+ auto i1 = makeSignedInterest(otherIdentity);
+ VALIDATE_FAILURE(i1, "Should fail (inner policy should reject)");
+}
+
+class LimitedRecordsOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.timestampGracePeriod = 15_s;
+ options.maxRecordCount = 3;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(LimitedRecords, ValidationPolicySignedInterestFixture<LimitedRecordsOptions>)
+{
+ Identity id1 = addSubCertificate("/Security/ValidatorFixture/Sub1", identity);
+ cache.insert(id1.getDefaultKey().getDefaultCertificate());
+ Identity id2 = addSubCertificate("/Security/ValidatorFixture/Sub2", identity);
+ cache.insert(id2.getDefaultKey().getDefaultCertificate());
+ Identity id3 = addSubCertificate("/Security/ValidatorFixture/Sub3", identity);
+ cache.insert(id3.getDefaultKey().getDefaultCertificate());
+ Identity id4 = addSubCertificate("/Security/ValidatorFixture/Sub4", identity);
+ cache.insert(id4.getDefaultKey().getDefaultCertificate());
+
+ auto i1 = makeSignedInterest(id2);
+ auto i2 = makeSignedInterest(id3);
+ auto i3 = makeSignedInterest(id4);
+ auto i00 = makeSignedInterest(id1); // signed at 0s
+ advanceClocks(1_s);
+ auto i01 = makeSignedInterest(id1); // signed at 1s
+ advanceClocks(1_s);
+ auto i02 = makeSignedInterest(id1); // signed at 2s
+
+ VALIDATE_SUCCESS(i00, "Should succeed");
+ rewindClockAfterValidation();
+
+ VALIDATE_SUCCESS(i02, "Should succeed");
+ rewindClockAfterValidation();
+
+ VALIDATE_SUCCESS(i1, "Should succeed");
+ rewindClockAfterValidation();
+
+ VALIDATE_SUCCESS(i2, "Should succeed");
+ rewindClockAfterValidation();
+
+ VALIDATE_SUCCESS(i3, "Should succeed, forgets identity id1");
+ rewindClockAfterValidation();
+
+ VALIDATE_SUCCESS(i01, "Should succeed despite timestamp is reordered, because record has been evicted");
+}
+
+class UnlimitedRecordsOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.timestampGracePeriod = 15_s;
+ options.maxRecordCount = -1;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicySignedInterestFixture<UnlimitedRecordsOptions>)
+{
+ std::vector<Identity> identities;
+ for (int i = 0; i < 20; ++i) {
+ Identity id = addSubCertificate("/Security/ValidatorFixture/Sub" + to_string(i), identity);
+ cache.insert(id.getDefaultKey().getDefaultCertificate());
+ identities.push_back(id);
+ }
+
+ auto i1 = makeSignedInterest(identities.at(0)); // signed at 0s
+ advanceClocks(1_s);
+ for (int i = 0; i < 20; ++i) {
+ auto i2 = makeSignedInterest(identities.at(i)); // signed at +1s
+
+ VALIDATE_SUCCESS(i2, "Should succeed");
+ rewindClockAfterValidation();
+ }
+ VALIDATE_FAILURE(i1, "Should fail (timestamp reorder)");
+}
+
+class ZeroRecordsOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.timestampGracePeriod = 15_s;
+ options.maxRecordCount = 0;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(ZeroRecords, ValidationPolicySignedInterestFixture<ZeroRecordsOptions>)
+{
+ auto i1 = makeSignedInterest(identity); // signed at 0s
+ advanceClocks(1_s);
+ auto i2 = makeSignedInterest(identity); // signed at +1s
+ VALIDATE_SUCCESS(i2, "Should succeed");
+ rewindClockAfterValidation();
+
+ VALIDATE_SUCCESS(i1, "Should succeed despite timestamp reordering, as records aren't kept");
+}
+
+BOOST_AUTO_TEST_SUITE(TimestampValidation)
+
+BOOST_AUTO_TEST_CASE(MissingTimestamp)
+{
+ auto i1 = makeSignedInterest(identity, InterestSigner::WantSeqNum);
+ VALIDATE_FAILURE(i1, "Should fail (timestamp missing)");
+}
+
+class DisabledTimestampValidationOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.shouldValidateTimestamps = false;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(Disabled,
+ ValidationPolicySignedInterestFixture<DisabledTimestampValidationOptions>)
+{
+ auto i1 = makeSignedInterest(identity); // signed at 0ms
+ advanceClocks(100_ms);
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ auto i2 = makeSignedInterest(identity); // signed at +100ms
+ // Set i2 to have same timestamp as i1
+ auto si2 = i2.getSignatureInfo();
+ si2->setTime(i2.getSignatureInfo()->getTime());
+ i2.setSignatureInfo(*si2);
+ VALIDATE_SUCCESS(i2, "Should succeed");
+}
+
+class GracePeriod15Sec
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.timestampGracePeriod = 15_s;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(TimestampTooOld, ValidationPolicySignedInterestFixture<GracePeriod15Sec>)
+{
+ auto i1 = makeSignedInterest(identity); // signed at 0s
+ advanceClocks(16_s); // verifying at +16s
+ VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
+ rewindClockAfterValidation();
+
+ auto i2 = makeSignedInterest(identity); // signed at +16s
+ VALIDATE_SUCCESS(i2, "Should succeed");
+}
+
+BOOST_FIXTURE_TEST_CASE(TimestampTooNew, ValidationPolicySignedInterestFixture<GracePeriod15Sec>)
+{
+ auto i1 = makeSignedInterest(identity); // signed at 0s
+ advanceClocks(1_s);
+ auto i2 = makeSignedInterest(identity); // signed at +1s
+ advanceClocks(1_s);
+ auto i3 = makeSignedInterest(identity); // signed at +2s
+
+ systemClock->advance(-18_s); // verifying at -16s
+ VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
+ rewindClockAfterValidation();
+
+ // SignedInterestValidator should not remember i1's timestamp
+ VALIDATE_FAILURE(i2, "Should fail (timestamp outside the grace period)");
+ rewindClockAfterValidation();
+
+ // SignedInterestValidator should not remember i2's timestamp, and should treat i3 as initial
+ advanceClocks(18_s); // verifying at +2s
+ VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
+{
+ auto i1 = makeSignedInterest(identity); // signed at 0s
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ auto i2 = makeSignedInterest(identity); // signed at 0s
+ auto si2 = i2.getSignatureInfo();
+ si2->setTime(i1.getSignatureInfo()->getTime());
+ i2.setSignatureInfo(*si2);
+ VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
+
+ advanceClocks(2_s);
+ auto i3 = makeSignedInterest(identity); // signed at +2s
+ VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
+{
+ auto i2 = makeSignedInterest(identity); // signed at 0ms
+ advanceClocks(200_ms);
+ auto i3 = makeSignedInterest(identity); // signed at +200ms
+ advanceClocks(900_ms);
+ auto i1 = makeSignedInterest(identity); // signed at +1100ms
+ advanceClocks(300_ms);
+ auto i4 = makeSignedInterest(identity); // signed at +1400ms
+
+ systemClock->advance(-300_ms); // verifying at +1100ms
+ VALIDATE_SUCCESS(i1, "Should succeed");
+ rewindClockAfterValidation();
+
+ systemClock->advance(-1100_ms); // verifying at 0ms
+ VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
+ rewindClockAfterValidation();
+
+ // SignedInterestValidator should not remember i2's timestamp
+ advanceClocks(200_ms); // verifying at +200ms
+ VALIDATE_FAILURE(i3, "Should fail (timestamp reordered)");
+ rewindClockAfterValidation();
+
+ advanceClocks(1200_ms); // verifying at 1400ms
+ VALIDATE_SUCCESS(i4, "Should succeed");
+}
+
+template<class T>
+class GracePeriod
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.timestampGracePeriod = time::seconds(T::value);
+ return options;
+ }
+};
+
+typedef boost::mpl::vector<
+ GracePeriod<boost::mpl::int_<0>>,
+ GracePeriod<boost::mpl::int_<-1>>
+> GraceNonPositiveValues;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositiveValues,
+ ValidationPolicySignedInterestFixture<GracePeriod>)
+{
+ auto i1 = this->makeSignedInterest(this->identity); // signed at 0ms
+ auto i2 = this->makeSignedInterest(this->subIdentity); // signed at 0ms
+ // ensure timestamps are exactly 0ms
+ for (auto interest : {&i1, &i2}) {
+ auto si = interest->getSignatureInfo();
+ si->setTime(time::system_clock::now());
+ interest->setSignatureInfo(*si);
+ }
+
+ VALIDATE_SUCCESS(i1, "Should succeed when validating at 0ms");
+ this->rewindClockAfterValidation();
+
+ this->advanceClocks(1_ms);
+ VALIDATE_FAILURE(i2, "Should fail when validating at 1ms");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TimestampValidation
+
+BOOST_AUTO_TEST_SUITE(SeqNumValidation)
+
+// By default, sequence number validation is disabled
+BOOST_AUTO_TEST_CASE(Disabled)
+{
+ auto i1 = makeSignedInterest(identity, WantAll); // signed at 0ms
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ auto i2 = makeSignedInterest(identity, WantAll); // signed at +100ms
+ // Set i2 to have same seq num as i1
+ auto si2 = i2.getSignatureInfo();
+ si2->setSeqNum(i2.getSignatureInfo()->getSeqNum());
+ i2.setSignatureInfo(*si2);
+ VALIDATE_SUCCESS(i2, "Should succeed");
+}
+
+class SeqNumValidationOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.shouldValidateSeqNums = true;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(MissingSeqNum,
+ ValidationPolicySignedInterestFixture<SeqNumValidationOptions>)
+{
+ auto i1 = makeSignedInterest(identity, InterestSigner::WantTime);
+ VALIDATE_FAILURE(i1, "Should fail (sequence number missing");
+}
+
+BOOST_FIXTURE_TEST_CASE(SeqNumReorder,
+ ValidationPolicySignedInterestFixture<SeqNumValidationOptions>)
+{
+ auto i1 = makeSignedInterest(identity, WantAll); // seq num is i
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ auto i2 = makeSignedInterest(identity, WantAll); // seq num is i+1
+ auto si2 = i2.getSignatureInfo();
+ si2->setSeqNum(i1.getSignatureInfo()->getSeqNum());
+ i2.setSignatureInfo(*si2);
+ VALIDATE_FAILURE(i2, "Should fail (sequence number reordered)");
+
+ auto i3 = makeSignedInterest(identity, WantAll); // seq num is i+2
+ VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SeqNumValidation
+
+BOOST_AUTO_TEST_SUITE(NonceValidation)
+
+BOOST_AUTO_TEST_CASE(MissingNonce)
+{
+ auto i1 = makeSignedInterest(identity, InterestSigner::WantTime); // Specifically exclude nonce
+ VALIDATE_FAILURE(i1, "Should fail (nonce missing)");
+}
+
+BOOST_AUTO_TEST_CASE(DuplicateNonce)
+{
+ auto i1 = makeSignedInterest(identity, WantAll);
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ auto i2 = makeSignedInterest(identity, WantAll);
+ auto si2 = i2.getSignatureInfo();
+ si2->setNonce(i1.getSignatureInfo()->getNonce());
+ i2.setSignatureInfo(*si2);
+ VALIDATE_FAILURE(i2, "Should fail (duplicate nonce)");
+
+ auto i3 = makeSignedInterest(identity, WantAll);
+ // On the off chance that the generated nonce is identical to i1
+ while (i3.getSignatureInfo()->getNonce() == i1.getSignatureInfo()->getNonce()) {
+ i3 = makeSignedInterest(identity, WantAll);
+ }
+ VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+class DisabledNonceValidationOptions
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.shouldValidateNonces = false;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(Disabled,
+ ValidationPolicySignedInterestFixture<DisabledNonceValidationOptions>)
+{
+ auto i1 = makeSignedInterest(identity, WantAll ^ InterestSigner::WantNonce);
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ // Ensure still works when a nonce is present
+ auto i2 = makeSignedInterest(identity, WantAll);
+ VALIDATE_SUCCESS(i2, "Should succeed");
+
+ // Ensure a duplicate still succeeds
+ auto i3 = makeSignedInterest(identity, WantAll);
+ auto si3 = i3.getSignatureInfo();
+ si3->setNonce(i2.getSignatureInfo()->getNonce());
+ i3.setSignatureInfo(*si3);
+ m_keyChain.sign(i3, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
+ .setSignatureInfo(*si3));
+ VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+class NonceLimit2Options
+{
+public:
+ static ValidationPolicySignedInterest::Options
+ getOptions()
+ {
+ ValidationPolicySignedInterest::Options options;
+ options.shouldValidateTimestamps = false;
+ options.shouldValidateSeqNums = false;
+ options.maxNonceRecordCount = 2;
+ return options;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(NonceRecordLimit,
+ ValidationPolicySignedInterestFixture<NonceLimit2Options>)
+{
+ auto i1 = makeSignedInterest(identity, WantAll);
+ VALIDATE_SUCCESS(i1, "Should succeed");
+
+ auto i2 = makeSignedInterest(identity, WantAll);
+ // On the off chance that the generated nonce is identical to i1
+ while (i2.getSignatureInfo()->getNonce() == i1.getSignatureInfo()->getNonce()) {
+ i2 = makeSignedInterest(identity, WantAll);
+ }
+ VALIDATE_SUCCESS(i2, "Should succeed");
+
+ auto i3 = makeSignedInterest(identity, WantAll);
+ auto si3 = i3.getSignatureInfo();
+ si3->setNonce(i1.getSignatureInfo()->getNonce());
+ i3.setSignatureInfo(*si3);
+ m_keyChain.sign(i3, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
+ .setSignatureInfo(*si3));
+ VALIDATE_FAILURE(i3, "Should fail (duplicate nonce)");
+
+ // Pop i1's nonce off the list
+ auto i4 = makeSignedInterest(identity, WantAll);
+ // On the off chance that the generated nonce is identical to i1 or i2
+ while (i4.getSignatureInfo()->getNonce() == i1.getSignatureInfo()->getNonce() ||
+ i4.getSignatureInfo()->getNonce() == i2.getSignatureInfo()->getNonce()) {
+ i4 = makeSignedInterest(identity, WantAll);
+ }
+ VALIDATE_SUCCESS(i4, "Should succeed");
+
+ // Now i3 should succeed because i1's nonce has been popped off the list
+ auto i5 = makeSignedInterest(identity, WantAll);
+ auto si5 = i5.getSignatureInfo();
+ si5->setNonce(i1.getSignatureInfo()->getNonce());
+ i5.setSignatureInfo(*si5);
+ m_keyChain.sign(i5, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
+ .setSignatureInfo(*si5));
+ VALIDATE_SUCCESS(i5, "Should succeed");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // NonceValidation
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicySignedInterest
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // inline namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/validator-config.t.cpp b/tests/unit/security/validator-config.t.cpp
index b4654ac..dac6913 100644
--- a/tests/unit/security/validator-config.t.cpp
+++ b/tests/unit/security/validator-config.t.cpp
@@ -132,7 +132,7 @@
{
validator.load(configFile);
- CommandInterestSigner signer(m_keyChain);
+ InterestSigner signer(m_keyChain);
auto i = signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
size_t nValidated = 0, nFailed = 0;
@@ -150,6 +150,53 @@
BOOST_CHECK_EQUAL(nFailed, 1);
}
+BOOST_FIXTURE_TEST_CASE(ValidateSignedInterest, ValidatorConfigFixture)
+{
+ validator.load(configFile);
+
+ InterestSigner signer(m_keyChain);
+ Interest i1("/hello/world");
+ i1.setCanBePrefix(false);
+ signer.makeSignedInterest(i1);
+ size_t nValidated = 0, nFailed = 0;
+
+ validator.validate(i1, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+ BOOST_CHECK_EQUAL(nValidated, 1);
+ BOOST_CHECK_EQUAL(nFailed, 0);
+
+ validator.validate(i1, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+ BOOST_CHECK_EQUAL(nValidated, 1);
+ BOOST_CHECK_EQUAL(nFailed, 1);
+
+ Interest i2("/hello/world");
+ i2.setCanBePrefix(false);
+ signer.makeSignedInterest(i2, signingWithSha256());
+ validator.validate(i2, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+ BOOST_CHECK_EQUAL(nValidated, 2);
+ BOOST_CHECK_EQUAL(nFailed, 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(ValidateCommandInterest, ValidatorConfigFixture)
+{
+ validator.load(configFile);
+
+ InterestSigner signer(m_keyChain);
+ auto i1 = signer.makeCommandInterest("/hello/world");
+ size_t nValidated = 0, nFailed = 0;
+
+ validator.validate(i1, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+ BOOST_CHECK_EQUAL(nValidated, 1);
+ BOOST_CHECK_EQUAL(nFailed, 0);
+
+ validator.validate(i1, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+ BOOST_CHECK_EQUAL(nValidated, 1);
+ BOOST_CHECK_EQUAL(nFailed, 1);
+
+ auto i2 = signer.makeCommandInterest("/hello/world");
+ validator.validate(i2, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+ BOOST_CHECK_EQUAL(nValidated, 2);
+ BOOST_CHECK_EQUAL(nFailed, 1);
+}
BOOST_AUTO_TEST_SUITE_END() // TestValidatorConfig
BOOST_AUTO_TEST_SUITE_END() // Security