security: support new signed Interest format in Validator

refs #4804

Change-Id: I0391709dc1486c8156c03cf8e9d94e6cfbe30303
diff --git a/ndn-cxx/security/command-interest-signer.cpp b/ndn-cxx/security/command-interest-signer.cpp
deleted file mode 100644
index 63d37c9..0000000
--- a/ndn-cxx/security/command-interest-signer.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2013-2018 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/command-interest-signer.hpp"
-#include "ndn-cxx/util/random.hpp"
-
-namespace ndn {
-namespace security {
-
-CommandInterestPreparer::CommandInterestPreparer()
-  : m_lastUsedTimestamp(0)
-{
-}
-
-Name
-CommandInterestPreparer::prepareCommandInterestName(Name name)
-{
-  time::milliseconds timestamp = time::toUnixTimestamp(time::system_clock::now());
-  if (timestamp <= m_lastUsedTimestamp) {
-    timestamp = m_lastUsedTimestamp + 1_ms;
-  }
-  m_lastUsedTimestamp = timestamp;
-
-  name
-    .append(name::Component::fromNumber(timestamp.count()))
-    .append(name::Component::fromNumber(random::generateWord64())) // nonce
-    ;
-
-  return name;
-}
-
-CommandInterestSigner::CommandInterestSigner(KeyChain& keyChain)
-  : m_keyChain(keyChain)
-{
-}
-
-Interest
-CommandInterestSigner::makeCommandInterest(const Name& name, const SigningInfo& params)
-{
-  Interest commandInterest(prepareCommandInterestName(name));
-  commandInterest.setCanBePrefix(false);
-  m_keyChain.sign(commandInterest, params);
-  return commandInterest;
-}
-
-} // namespace security
-} // namespace ndn
diff --git a/ndn-cxx/security/command-interest-signer.hpp b/ndn-cxx/security/command-interest-signer.hpp
index 5439809..c4da552 100644
--- a/ndn-cxx/security/command-interest-signer.hpp
+++ b/ndn-cxx/security/command-interest-signer.hpp
@@ -22,74 +22,19 @@
 #ifndef NDN_SECURITY_COMMAND_INTEREST_SIGNER_HPP
 #define NDN_SECURITY_COMMAND_INTEREST_SIGNER_HPP
 
-#include "ndn-cxx/security/key-chain.hpp"
+#include "ndn-cxx/security/interest-signer.hpp"
 
 namespace ndn {
 namespace security {
 
 /**
- * @brief Helper class to prepare command interest name
- *
- * The preparer adds timestamp and nonce name components to the supplied name.
- *
- * This class is primarily designed to be used as part of CommandInterestSigner, but can also
- * be using in an application that defines custom signing methods not support by the KeyChain
- * (such as HMAC-SHA1).
- *
- * @sa https://redmine.named-data.net/projects/ndn-cxx/wiki/CommandInterest
+ * @brief Helper class to create command Interests.
+ * @deprecated Command Interests have been deprecated in favor of signed Interests with timestamp,
+ *             nonce, and/or sequence number components. Use InterestSigner instead.
  */
-class CommandInterestPreparer : noncopyable
-{
-public:
-  CommandInterestPreparer();
-
-  /**
-   * @brief Prepare name of the CommandInterest
-   *
-   * This method appends the timestamp and nonce name components to the supplied name.
-   */
-  Name
-  prepareCommandInterestName(Name name);
-
-private:
-  time::milliseconds m_lastUsedTimestamp;
-};
-
-/**
- * @brief Helper class to create command interests
- *
- * The signer adds timestamp and nonce name components to the supplied name, creates an
- * Interest, and signs it with the KeyChain.
- *
- * @sa https://redmine.named-data.net/projects/ndn-cxx/wiki/CommandInterest
- */
-class CommandInterestSigner : private CommandInterestPreparer
-{
-public:
-  explicit
-  CommandInterestSigner(KeyChain& keyChain);
-
-  /**
-   * @brief Create CommandInterest
-   *
-   * This method appends the timestamp and nonce name components to the supplied name, create
-   * an Interest object and signs it with the keychain.
-   *
-   * Note that signature of the command interest covers only Name of the interest.  Therefore,
-   * other fields in the returned interest can be changed without breaking validity of the
-   * signature, because s
-   *
-   * @sa https://redmine.named-data.net/projects/ndn-cxx/wiki/CommandInterest
-   */
-  Interest
-  makeCommandInterest(const Name& name, const SigningInfo& params = SigningInfo());
-
-private:
-  KeyChain& m_keyChain;
-};
+using CommandInterestSigner = InterestSigner;
 
 } // namespace security
 } // namespace ndn
 
-
 #endif // NDN_SECURITY_COMMAND_INTEREST_SIGNER_HPP
diff --git a/ndn-cxx/security/interest-signer.cpp b/ndn-cxx/security/interest-signer.cpp
new file mode 100644
index 0000000..479800e
--- /dev/null
+++ b/ndn-cxx/security/interest-signer.cpp
@@ -0,0 +1,78 @@
+/* -*- 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/interest-signer.hpp"
+#include "ndn-cxx/util/random.hpp"
+
+namespace ndn {
+namespace security {
+
+InterestSigner::InterestSigner(KeyChain& keyChain)
+  : m_keyChain(keyChain)
+{
+}
+
+void
+InterestSigner::makeSignedInterest(Interest& interest, SigningInfo params)
+{
+  SignatureInfo info = params.getSignatureInfo();
+  info.setTime(getFreshTimestamp());
+
+  std::vector<uint8_t> nonce(8);
+  random::generateSecureBytes(nonce.data(), nonce.size());
+  info.setNonce(nonce);
+
+  params.setSignatureInfo(info);
+  params.setSignedInterestFormat(SignedInterestFormat::V03);
+  m_keyChain.sign(interest, params);
+}
+
+Interest
+InterestSigner::makeCommandInterest(Name name, const SigningInfo& params)
+{
+  Interest interest;
+  time::milliseconds timestamp = time::toUnixTimestamp(getFreshTimestamp());
+  name
+    .append(name::Component::fromNumber(timestamp.count()))
+    .append(name::Component::fromNumber(random::generateWord64())) // nonce
+    ;
+  interest.setName(name);
+  interest.setCanBePrefix(false);
+  m_keyChain.sign(interest, params);
+  return interest;
+}
+
+time::system_clock::TimePoint
+InterestSigner::getFreshTimestamp()
+{
+  auto timestamp = time::system_clock::now();
+  if (time::duration_cast<time::milliseconds>(timestamp - m_lastUsedTimestamp) > 0_ms) {
+    m_lastUsedTimestamp = timestamp;
+  }
+  else {
+    m_lastUsedTimestamp = m_lastUsedTimestamp + 1_ms;
+    timestamp = m_lastUsedTimestamp;
+  }
+  return timestamp;
+}
+
+} // namespace security
+} // namespace ndn
diff --git a/ndn-cxx/security/interest-signer.hpp b/ndn-cxx/security/interest-signer.hpp
new file mode 100644
index 0000000..f04dc83
--- /dev/null
+++ b/ndn-cxx/security/interest-signer.hpp
@@ -0,0 +1,74 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_SECURITY_INTEREST_SIGNER_HPP
+#define NDN_SECURITY_INTEREST_SIGNER_HPP
+
+#include "ndn-cxx/security/key-chain.hpp"
+
+namespace ndn {
+namespace security {
+
+/**
+ * @brief Helper class to create signed Interests
+ *
+ * The signer generates timestamp and nonce elements for an Interest and signs it with the KeyChain.
+ */
+class InterestSigner
+{
+public:
+  explicit
+  InterestSigner(KeyChain& keyChain);
+
+  /**
+   * @brief Signs a signed Interest (following Packet Specification v0.3 or newer)
+   *
+   * This generates a nonce and timestamp for the signed Interest.
+   */
+  void
+  makeSignedInterest(Interest& interest, SigningInfo params = SigningInfo());
+
+  /**
+   * @brief Creates and signs a command Interest
+   * @deprecated Use the new signed Interest format instead of command Interests. These can be
+   *             created with makeSignedInterest.
+   *
+   * This generates a nonce and timestamp for the command Interest.
+   */
+  Interest
+  makeCommandInterest(Name name, const SigningInfo& params = SigningInfo());
+
+private:
+  /**
+   * @brief Get current timestamp, but ensure it is unique by increasing by 1 ms if already used
+   */
+  time::system_clock::TimePoint
+  getFreshTimestamp();
+
+private:
+  KeyChain& m_keyChain;
+  time::system_clock::TimePoint m_lastUsedTimestamp;
+};
+
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_SECURITY_INTEREST_SIGNER_HPP
diff --git a/ndn-cxx/security/validation-policy-command-interest.cpp b/ndn-cxx/security/validation-policy-command-interest.cpp
index 607356d..8da3d68 100644
--- a/ndn-cxx/security/validation-policy-command-interest.cpp
+++ b/ndn-cxx/security/validation-policy-command-interest.cpp
@@ -50,9 +50,9 @@
 ValidationPolicyCommandInterest::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
                                              const ValidationContinuation& continueValidation)
 {
-  bool isOk = false;
+  bool isOk;
   Name keyName;
-  uint64_t timestamp = 0;
+  time::system_clock::TimePoint timestamp;
   std::tie(isOk, keyName, timestamp) = parseCommandInterest(interest, state);
   if (!isOk) {
     return;
@@ -76,41 +76,62 @@
   }
 }
 
-std::tuple<bool, Name, uint64_t>
+std::tuple<bool, Name, time::system_clock::TimePoint>
 ValidationPolicyCommandInterest::parseCommandInterest(const Interest& interest,
                                                       const shared_ptr<ValidationState>& state) const
 {
-  const Name& name = interest.getName();
-  if (name.size() < command_interest::MIN_SIZE) {
-    state->fail({ValidationError::POLICY_ERROR, "Command interest name `" +
-                 interest.getName().toUri() + "` is too short"});
-    return std::make_tuple(false, Name(), 0);
-  }
+  auto fmt = state->getTag<SignedInterestFormatTag>();
+  BOOST_ASSERT(fmt);
 
-  const name::Component& timestampComp = name.at(command_interest::POS_TIMESTAMP);
-  if (!timestampComp.isNumber()) {
-    state->fail({ValidationError::POLICY_ERROR, "Command interest `" +
-                 interest.getName().toUri() + "` doesn't include timestamp component"});
-    return std::make_tuple(false, Name(), 0);
+  time::system_clock::TimePoint timestamp;
+
+  if (*fmt == SignedInterestFormat::V03) {
+    BOOST_ASSERT(interest.getSignatureInfo());
+    auto optionalTimestamp = interest.getSignatureInfo()->getTime();
+
+    // Note that timestamp is a hard requirement of this policy
+    // TODO: Refactor to support other/combinations of the restrictions based on Nonce, Time, and/or SeqNum
+    if (!optionalTimestamp) {
+      state->fail({ValidationError::POLICY_ERROR, "Signed Interest `" +
+                   interest.getName().toUri() + "` doesn't include required SignatureTime element"});
+      return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
+    }
+    timestamp = *optionalTimestamp;
+  }
+  else {
+    const Name& name = interest.getName();
+    if (name.size() < command_interest::MIN_SIZE) {
+      state->fail({ValidationError::POLICY_ERROR, "Command interest name `" +
+                   interest.getName().toUri() + "` is too short"});
+      return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
+    }
+
+    const name::Component& timestampComp = name.at(command_interest::POS_TIMESTAMP);
+    if (!timestampComp.isNumber()) {
+      state->fail({ValidationError::POLICY_ERROR, "Command interest `" +
+                   interest.getName().toUri() + "` doesn't include timestamp component"});
+      return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
+    }
+    timestamp = time::fromUnixTimestamp(time::milliseconds(timestampComp.toNumber()));
   }
 
   Name klName = getKeyLocatorName(interest, *state);
   if (!state->getOutcome()) { // already failed
-    return std::make_tuple(false, Name(), 0);
+    return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
   }
 
-  return std::make_tuple(true, klName, timestampComp.toNumber());
+  return std::make_tuple(true, klName, timestamp);
 }
 
 bool
 ValidationPolicyCommandInterest::checkTimestamp(const shared_ptr<ValidationState>& state,
-                                                const Name& keyName, uint64_t timestamp)
+                                                const Name& keyName, time::system_clock::TimePoint timestamp)
 {
   this->cleanup();
 
   auto now = time::system_clock::now();
-  auto timestampPoint = time::fromUnixTimestamp(time::milliseconds(timestamp));
-  if (timestampPoint < now - m_options.gracePeriod || timestampPoint > now + m_options.gracePeriod) {
+
+  if (timestamp < now - m_options.gracePeriod || timestamp > now + m_options.gracePeriod) {
     state->fail({ValidationError::POLICY_ERROR,
                  "Timestamp is outside the grace period for key " + keyName.toUri()});
     return false;
@@ -132,7 +153,7 @@
 }
 
 void
-ValidationPolicyCommandInterest::insertNewRecord(const Name& keyName, uint64_t timestamp)
+ValidationPolicyCommandInterest::insertNewRecord(const Name& keyName, time::system_clock::TimePoint timestamp)
 {
   // try to insert new record
   auto now = time::steady_clock::now();
diff --git a/ndn-cxx/security/validation-policy-command-interest.hpp b/ndn-cxx/security/validation-policy-command-interest.hpp
index c7ea9c2..e57e14f 100644
--- a/ndn-cxx/security/validation-policy-command-interest.hpp
+++ b/ndn-cxx/security/validation-policy-command-interest.hpp
@@ -115,15 +115,15 @@
   void
   cleanup();
 
-  std::tuple<bool, Name, uint64_t>
+  std::tuple<bool, Name, time::system_clock::TimePoint>
   parseCommandInterest(const Interest& interest, const shared_ptr<ValidationState>& state) const;
 
   bool
   checkTimestamp(const shared_ptr<ValidationState>& state,
-                 const Name& keyName, uint64_t timestamp);
+                 const Name& keyName, time::system_clock::TimePoint timestamp);
 
   void
-  insertNewRecord(const Name& keyName, uint64_t timestamp);
+  insertNewRecord(const Name& keyName, time::system_clock::TimePoint timestamp);
 
 private:
   Options m_options;
@@ -131,7 +131,7 @@
   struct LastTimestampRecord
   {
     Name keyName;
-    uint64_t timestamp;
+    time::system_clock::TimePoint timestamp;
     time::steady_clock::TimePoint lastRefreshed;
   };
 
diff --git a/ndn-cxx/security/validation-policy-config.cpp b/ndn-cxx/security/validation-policy-config.cpp
index 6aeca86..ba7d3b8 100644
--- a/ndn-cxx/security/validation-policy-config.cpp
+++ b/ndn-cxx/security/validation-policy-config.cpp
@@ -246,7 +246,7 @@
   }
 
   for (const auto& rule : m_dataRules) {
-    if (rule->match(tlv::Data, data.getName())) {
+    if (rule->match(tlv::Data, data.getName(), state)) {
       if (rule->check(tlv::Data, data.getName(), klName, state)) {
         return continueValidation(make_shared<CertificateRequest>(klName), state);
       }
@@ -275,7 +275,7 @@
   }
 
   for (const auto& rule : m_interestRules) {
-    if (rule->match(tlv::Interest, interest.getName())) {
+    if (rule->match(tlv::Interest, interest.getName(), state)) {
       if (rule->check(tlv::Interest, interest.getName(), klName, state)) {
         return continueValidation(make_shared<CertificateRequest>(klName), state);
       }
diff --git a/ndn-cxx/security/validation-policy.cpp b/ndn-cxx/security/validation-policy.cpp
index 5e90dfc..969d403 100644
--- a/ndn-cxx/security/validation-policy.cpp
+++ b/ndn-cxx/security/validation-policy.cpp
@@ -90,10 +90,18 @@
 Name
 getKeyLocatorName(const Interest& interest, ValidationState& state)
 {
+  auto fmt = state.getTag<SignedInterestFormatTag>();
+  BOOST_ASSERT(fmt);
+
+  if (*fmt == SignedInterestFormat::V03) {
+    BOOST_ASSERT(interest.getSignatureInfo());
+    return getKeyLocatorName(*interest.getSignatureInfo(), state);
+  }
+
+  // Try Signed Interest format from Packet Specification v0.2
   const Name& name = interest.getName();
   if (name.size() < signed_interest::MIN_SIZE) {
-    state.fail({ValidationError::INVALID_KEY_LOCATOR,
-                "Invalid signed Interest: name too short"});
+    state.fail({ValidationError::INVALID_KEY_LOCATOR, "Invalid signed Interest: name too short"});
     return Name();
   }
 
diff --git a/ndn-cxx/security/validation-policy.hpp b/ndn-cxx/security/validation-policy.hpp
index 9c284d5..95b1b42 100644
--- a/ndn-cxx/security/validation-policy.hpp
+++ b/ndn-cxx/security/validation-policy.hpp
@@ -147,9 +147,9 @@
   unique_ptr<ValidationPolicy> m_innerPolicy;
 };
 
-/** \brief extract KeyLocator.Name from Data
+/** \brief extract KeyLocator.Name from a Data packet
  *
- *  Data must contain a KeyLocator of Name type.
+ *  The Data packet must contain a KeyLocator of Name type.
  *  Otherwise, state.fail is invoked with INVALID_KEY_LOCATOR error.
  */
 Name
@@ -157,8 +157,13 @@
 
 /** \brief extract KeyLocator.Name from signed Interest
  *
- *  Interest must have SignatureInfo and contain a KeyLocator of Name type.
- *  Otherwise, state.fail is invoked with INVALID_KEY_LOCATOR error.
+ *  Signed Interests according to Packet Specification v0.3+, as identified inside the state, must
+ *  have an InterestSignatureInfo element. Legacy signed Interests must contain a
+ *  (Data)SignatureInfo name component. In both cases, the included KeyLocator must be of the Name
+ *  type. otherwise, state.fail will be invoked with an INVALID_KEY_LOCATOR error.
+ *
+ *  Interests specified to this method must be tagged with a SignedInterestFormatTag to indicate
+ *  whether they are signed according to Packet Specification v0.3+ or a previous specification.
  */
 Name
 getKeyLocatorName(const Interest& interest, ValidationState& state);
diff --git a/ndn-cxx/security/validation-state.hpp b/ndn-cxx/security/validation-state.hpp
index dffdc08..063bc8b 100644
--- a/ndn-cxx/security/validation-state.hpp
+++ b/ndn-cxx/security/validation-state.hpp
@@ -23,8 +23,9 @@
 #define NDN_SECURITY_VALIDATION_STATE_HPP
 
 #include "ndn-cxx/detail/tag-host.hpp"
-#include "ndn-cxx/security/validation-callback.hpp"
 #include "ndn-cxx/security/certificate.hpp"
+#include "ndn-cxx/security/signing-info.hpp"
+#include "ndn-cxx/security/validation-callback.hpp"
 #include "ndn-cxx/util/signal.hpp"
 
 #include <list>
@@ -247,6 +248,8 @@
   InterestValidationFailureCallback m_failureCb;
 };
 
+using SignedInterestFormatTag = SimpleTag<SignedInterestFormat, 1002>;
+
 } // inline namespace v2
 } // namespace security
 } // namespace ndn
diff --git a/ndn-cxx/security/validator-config/checker.cpp b/ndn-cxx/security/validator-config/checker.cpp
index d86bf4d..ee73e79 100644
--- a/ndn-cxx/security/validator-config/checker.cpp
+++ b/ndn-cxx/security/validator-config/checker.cpp
@@ -38,10 +38,23 @@
   BOOST_ASSERT(pktType == tlv::Interest || pktType == tlv::Data);
 
   if (pktType == tlv::Interest) {
-    if (pktName.size() < signed_interest::MIN_SIZE)
-      return false;
+    auto fmt = state->getTag<SignedInterestFormatTag>();
+    BOOST_ASSERT(fmt);
 
-    return checkNames(pktName.getPrefix(-signed_interest::MIN_SIZE), klName, state);
+    if (*fmt == SignedInterestFormat::V03) {
+      // This check is redundant if parameter digest checking is enabled. However, the parameter
+      // digest checking can be disabled in API.
+      if (pktName.size() == 0 || pktName[-1].type() != tlv::ParametersSha256DigestComponent) {
+        return false;
+      }
+      return checkNames(pktName.getPrefix(-1), klName, state);
+    }
+    else {
+      if (pktName.size() < signed_interest::MIN_SIZE)
+        return false;
+
+      return checkNames(pktName.getPrefix(-signed_interest::MIN_SIZE), klName, state);
+    }
   }
   else {
     return checkNames(pktName, klName, state);
diff --git a/ndn-cxx/security/validator-config/filter.cpp b/ndn-cxx/security/validator-config/filter.cpp
index e0cc0ee..7421813 100644
--- a/ndn-cxx/security/validator-config/filter.cpp
+++ b/ndn-cxx/security/validator-config/filter.cpp
@@ -24,6 +24,7 @@
 #include "ndn-cxx/data.hpp"
 #include "ndn-cxx/interest.hpp"
 #include "ndn-cxx/security/security-common.hpp"
+#include "ndn-cxx/security/validation-state.hpp"
 #include "ndn-cxx/util/regex.hpp"
 
 #include <boost/algorithm/string/predicate.hpp>
@@ -34,15 +35,29 @@
 namespace validator_config {
 
 bool
-Filter::match(uint32_t pktType, const Name& pktName)
+Filter::match(uint32_t pktType, const Name& pktName, const shared_ptr<ValidationState>& state)
 {
   BOOST_ASSERT(pktType == tlv::Interest || pktType == tlv::Data);
 
   if (pktType == tlv::Interest) {
-    if (pktName.size() < signed_interest::MIN_SIZE)
-      return false;
+    auto fmt = state->getTag<SignedInterestFormatTag>();
+    BOOST_ASSERT(fmt);
 
-    return matchName(pktName.getPrefix(-signed_interest::MIN_SIZE));
+    if (*fmt == SignedInterestFormat::V03) {
+      // This check is redundant if parameter digest checking is enabled. However, the parameter
+      // digest checking can be disabled in API.
+      if (pktName.size() == 0 || pktName[-1].type() != tlv::ParametersSha256DigestComponent) {
+        return false;
+      }
+
+      return matchName(pktName.getPrefix(-1));
+    }
+    else {
+      if (pktName.size() < signed_interest::MIN_SIZE)
+        return false;
+
+      return matchName(pktName.getPrefix(-signed_interest::MIN_SIZE));
+    }
   }
   else {
     return matchName(pktName);
diff --git a/ndn-cxx/security/validator-config/filter.hpp b/ndn-cxx/security/validator-config/filter.hpp
index a199362..d852564 100644
--- a/ndn-cxx/security/validator-config/filter.hpp
+++ b/ndn-cxx/security/validator-config/filter.hpp
@@ -31,6 +31,9 @@
 namespace ndn {
 namespace security {
 inline namespace v2 {
+
+class ValidationState;
+
 namespace validator_config {
 
 /**
@@ -47,7 +50,7 @@
   ~Filter() = default;
 
   bool
-  match(uint32_t pktType, const Name& pktName);
+  match(uint32_t pktType, const Name& pktName, const shared_ptr<ValidationState>& state);
 
 public:
   /**
diff --git a/ndn-cxx/security/validator-config/rule.cpp b/ndn-cxx/security/validator-config/rule.cpp
index 91807d9..b320ad1 100644
--- a/ndn-cxx/security/validator-config/rule.cpp
+++ b/ndn-cxx/security/validator-config/rule.cpp
@@ -50,7 +50,7 @@
 }
 
 bool
-Rule::match(uint32_t pktType, const Name& pktName) const
+Rule::match(uint32_t pktType, const Name& pktName, const shared_ptr<ValidationState>& state) const
 {
   NDN_LOG_TRACE("Trying to match " << pktName);
   if (pktType != m_pktType) {
@@ -64,7 +64,7 @@
 
   bool retval = false;
   for (const auto& filter : m_filters) {
-    retval |= filter->match(pktType, pktName);
+    retval |= filter->match(pktType, pktName, state);
     if (retval) {
       break;
     }
diff --git a/ndn-cxx/security/validator-config/rule.hpp b/ndn-cxx/security/validator-config/rule.hpp
index 9d43c59..73410b2 100644
--- a/ndn-cxx/security/validator-config/rule.hpp
+++ b/ndn-cxx/security/validator-config/rule.hpp
@@ -63,13 +63,14 @@
    *
    * @param pktType tlv::Interest or tlv::Data
    * @param pktName packet name, for signed Interests the last two components are not removed
+   * @param state The associated validation state
    * @retval true  If at least one filter matches @p pktName
    * @retval false If none of the filters match @p pktName
    *
    * @throw Error the supplied pktType doesn't match one for which the rule is designed
    */
   bool
-  match(uint32_t pktType, const Name& pktName) const;
+  match(uint32_t pktType, const Name& pktName, const shared_ptr<ValidationState>& state) const;
 
   /**
    * @brief check if packet satisfies rule's condition
diff --git a/ndn-cxx/security/validator.cpp b/ndn-cxx/security/validator.cpp
index 2372352..479bbb9 100644
--- a/ndn-cxx/security/validator.cpp
+++ b/ndn-cxx/security/validator.cpp
@@ -97,7 +97,11 @@
                     const InterestValidationFailureCallback& failureCb)
 {
   auto state = make_shared<InterestValidationState>(interest, successCb, failureCb);
-  NDN_LOG_DEBUG_DEPTH("Start validating interest " << interest.getName());
+
+  auto fmt = interest.getSignatureInfo() ? SignedInterestFormat::V03 : SignedInterestFormat::V02;
+  state->setTag(make_shared<SignedInterestFormatTag>(fmt));
+
+  NDN_LOG_DEBUG_DEPTH("Start validating interest (" << fmt << ") " << interest.getName());
 
   m_policy->checkPolicy(interest, state,
       [this] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
diff --git a/tests/unit/security/command-interest-signer.t.cpp b/tests/unit/security/interest-signer.t.cpp
similarity index 65%
rename from tests/unit/security/command-interest-signer.t.cpp
rename to tests/unit/security/interest-signer.t.cpp
index fe85ba8..9362c76 100644
--- a/tests/unit/security/command-interest-signer.t.cpp
+++ b/tests/unit/security/interest-signer.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -19,7 +19,7 @@
  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
  */
 
-#include "ndn-cxx/security/command-interest-signer.hpp"
+#include "ndn-cxx/security/interest-signer.hpp"
 #include "ndn-cxx/security/signing-helpers.hpp"
 
 #include "tests/boost-test.hpp"
@@ -32,15 +32,15 @@
 using namespace ndn::tests;
 
 BOOST_AUTO_TEST_SUITE(Security)
-BOOST_FIXTURE_TEST_SUITE(TestCommandInterestSigner, IdentityManagementTimeFixture)
+BOOST_FIXTURE_TEST_SUITE(TestInterestSigner, IdentityManagementTimeFixture)
 
-BOOST_AUTO_TEST_CASE(Basic)
+BOOST_AUTO_TEST_CASE(V02)
 {
   addIdentity("/test");
 
-  CommandInterestSigner signer(m_keyChain);
+  InterestSigner signer(m_keyChain);
   Interest i1 = signer.makeCommandInterest("/hello/world");
-  BOOST_CHECK_EQUAL(i1.getName().size(), 6);
+  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);
 
@@ -48,7 +48,7 @@
   BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_TIMESTAMP).toNumber(), timestamp.count());
 
   Interest i2 = signer.makeCommandInterest("/hello/world/!", signingByIdentity("/test"));
-  BOOST_CHECK_EQUAL(i2.getName().size(), 7);
+  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));
@@ -61,7 +61,39 @@
   BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP), i1.getName().at(command_interest::POS_TIMESTAMP));
 }
 
-BOOST_AUTO_TEST_SUITE_END() // TestCommandInterestSigner
+BOOST_AUTO_TEST_CASE(V03)
+{
+  addIdentity("/test");
+
+  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());
+
+  BOOST_TEST(*i1.getSignatureInfo()->getTime() == time::system_clock::now());
+
+  Interest i2("/hello/world/!");
+  i2.setCanBePrefix(false);
+  signer.makeSignedInterest(i2, signingByIdentity("/test"));
+  BOOST_CHECK_EQUAL(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());
+
+  advanceClocks(100_s);
+
+  signer.makeSignedInterest(i2);
+  BOOST_CHECK_EQUAL(i2.isSigned(), true);
+
+  BOOST_TEST(*i2.getSignatureInfo()->getTime() == time::system_clock::now());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInterestSigner
 BOOST_AUTO_TEST_SUITE_END() // Security
 
 } // namespace tests
diff --git a/tests/unit/security/validation-policy-command-interest.t.cpp b/tests/unit/security/validation-policy-command-interest.t.cpp
index da6ced7..af9bc2a 100644
--- a/tests/unit/security/validation-policy-command-interest.t.cpp
+++ b/tests/unit/security/validation-policy-command-interest.t.cpp
@@ -71,10 +71,18 @@
   }
 
   Interest
-  makeCommandInterest(const Identity& identity)
+  makeCommandInterest(const Identity& identity, bool wantV3 = false)
   {
-    return m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"),
-                                        signingByIdentity(identity));
+    if (wantV3) {
+      Interest i(Name(identity.getName()).append("CMD"));
+      i.setCanBePrefix(false);
+      m_signer.makeSignedInterest(i, signingByIdentity(identity));
+      return i;
+    }
+    else {
+      return m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"),
+                                          signingByIdentity(identity));
+    }
   }
 
 public:
@@ -99,6 +107,22 @@
   VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
 }
 
+BOOST_AUTO_TEST_CASE(BasicV3)
+{
+  auto i1 = makeCommandInterest(identity, true);
+  VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
+  VALIDATE_FAILURE(i1, "Should fail (replay attack)");
+
+  advanceClocks(5_ms);
+  auto i2 = makeCommandInterest(identity, true);
+  VALIDATE_SUCCESS(i2, "Should succeed (timestamp 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(DataPassthru)
 {
   Data d1("/Security/ValidatorFixture/Sub1");
diff --git a/tests/unit/security/validator-config/checker.t.cpp b/tests/unit/security/validator-config/checker.t.cpp
index a5fb205..55e48a1 100644
--- a/tests/unit/security/validator-config/checker.t.cpp
+++ b/tests/unit/security/validator-config/checker.t.cpp
@@ -51,12 +51,6 @@
   }
 
   static Name
-  makeSignedInterestName(const Name& name)
-  {
-    return Name(name).append("SignatureInfo").append("SignatureValue");
-  }
-
-  static Name
   makeKeyLocatorName(const Name& name)
   {
     return Name(name).append("KEY").append("v=1");
@@ -329,28 +323,65 @@
                                  Hierarchical,
                                  CustomizedNameRelation, CustomizedRegex, CustomizedHyperRelation>;
 
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(Checks, T, Tests, T)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(DataChecks, T, Tests, T)
 {
   using namespace ndn::security::v2::tests;
+  using PktType = DataPkt;
 
   BOOST_REQUIRE_EQUAL(this->outcomes.size(), this->names.size());
   for (size_t i = 0; i < this->names.size(); ++i) {
     BOOST_REQUIRE_EQUAL(this->outcomes[i].size(), this->names.size());
     for (size_t j = 0; j < this->names.size(); ++j) {
-      const Name& pktName = this->names[i];
-      Name klName = this->makeKeyLocatorName(this->names[j]);
+      auto pktName = PktType::makeName(this->names[i], this->m_keyChain);
+      auto klName = this->makeKeyLocatorName(this->names[j]);
       bool expectedOutcome = this->outcomes[i][j];
 
-      auto dataState = make_shared<DummyValidationState>();
-      BOOST_CHECK_EQUAL(this->checker.check(tlv::Data, pktName, klName, dataState), expectedOutcome);
-      BOOST_CHECK_EQUAL(boost::logic::indeterminate(dataState->getOutcome()), expectedOutcome);
-      BOOST_CHECK_EQUAL(bool(dataState->getOutcome()), false);
+      auto state = PktType::makeState();
+      BOOST_CHECK_EQUAL(this->checker.check(PktType::getType(), pktName, klName, state), expectedOutcome);
+      BOOST_CHECK_EQUAL(boost::logic::indeterminate(state->getOutcome()), expectedOutcome);
+      BOOST_CHECK_EQUAL(bool(state->getOutcome()), false);
+    }
+  }
+}
 
-      auto interestState = make_shared<DummyValidationState>();
-      BOOST_CHECK_EQUAL(this->checker.check(tlv::Interest, this->makeSignedInterestName(pktName),
-                                            klName, interestState), expectedOutcome);
-      BOOST_CHECK_EQUAL(boost::logic::indeterminate(interestState->getOutcome()), expectedOutcome);
-      BOOST_CHECK_EQUAL(bool(interestState->getOutcome()), false);
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(InterestV02Checks, T, Tests, T)
+{
+  using namespace ndn::security::v2::tests;
+  using PktType = InterestV02Pkt;
+
+  BOOST_REQUIRE_EQUAL(this->outcomes.size(), this->names.size());
+  for (size_t i = 0; i < this->names.size(); ++i) {
+    BOOST_REQUIRE_EQUAL(this->outcomes[i].size(), this->names.size());
+    for (size_t j = 0; j < this->names.size(); ++j) {
+      auto pktName = PktType::makeName(this->names[i], this->m_keyChain);
+      auto klName = this->makeKeyLocatorName(this->names[j]);
+      bool expectedOutcome = this->outcomes[i][j];
+
+      auto state = PktType::makeState();
+      BOOST_CHECK_EQUAL(this->checker.check(PktType::getType(), pktName, klName, state), expectedOutcome);
+      BOOST_CHECK_EQUAL(boost::logic::indeterminate(state->getOutcome()), expectedOutcome);
+      BOOST_CHECK_EQUAL(bool(state->getOutcome()), false);
+    }
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(InterestV03Checks, T, Tests, T)
+{
+  using namespace ndn::security::v2::tests;
+  using PktType = InterestV03Pkt;
+
+  BOOST_REQUIRE_EQUAL(this->outcomes.size(), this->names.size());
+  for (size_t i = 0; i < this->names.size(); ++i) {
+    BOOST_REQUIRE_EQUAL(this->outcomes[i].size(), this->names.size());
+    for (size_t j = 0; j < this->names.size(); ++j) {
+      auto pktName = PktType::makeName(this->names[i], this->m_keyChain);
+      auto klName = this->makeKeyLocatorName(this->names[j]);
+      bool expectedOutcome = this->outcomes[i][j];
+
+      auto state = PktType::makeState();
+      BOOST_CHECK_EQUAL(this->checker.check(PktType::getType(), pktName, klName, state), expectedOutcome);
+      BOOST_CHECK_EQUAL(boost::logic::indeterminate(state->getOutcome()), expectedOutcome);
+      BOOST_CHECK_EQUAL(bool(state->getOutcome()), false);
     }
   }
 }
diff --git a/tests/unit/security/validator-config/filter.t.cpp b/tests/unit/security/validator-config/filter.t.cpp
index 2b3bf6d..9f6029e 100644
--- a/tests/unit/security/validator-config/filter.t.cpp
+++ b/tests/unit/security/validator-config/filter.t.cpp
@@ -20,11 +20,11 @@
  */
 
 #include "ndn-cxx/security/validator-config/filter.hpp"
-#include "ndn-cxx/security/command-interest-signer.hpp"
 
 #include "tests/boost-test.hpp"
 #include "tests/identity-management-fixture.hpp"
 #include "tests/unit/security/validator-config/common.hpp"
+#include "tests/unit/security/validator-fixture.hpp"
 
 namespace ndn {
 namespace security {
@@ -33,33 +33,38 @@
 namespace tests {
 
 using namespace ndn::tests;
+using namespace ndn::security::v2::tests;
 
 BOOST_AUTO_TEST_SUITE(Security)
 BOOST_AUTO_TEST_SUITE(ValidatorConfig)
 
-class FilterFixture : public IdentityManagementFixture
-{
-public:
-  Interest
-  makeSignedInterest(const Name& name)
-  {
-    Interest interest(name);
-    m_keyChain.sign(interest);
-    return interest;
-  }
-};
+BOOST_FIXTURE_TEST_SUITE(TestFilter, IdentityManagementFixture)
 
-BOOST_FIXTURE_TEST_SUITE(TestFilter, FilterFixture)
-
-#define CHECK_FOR_MATCHES(filter, same, longer, shorter, different)     \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/foo/bar").getName()), same); \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/foo/bar").getName()), same);   \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/foo/bar/bar").getName()), longer); \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/foo/bar/bar").getName()), longer);       \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/foo").getName()), shorter); \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/foo").getName()), shorter);              \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/other/prefix").getName()), different); \
-  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/other/prefix").getName()), different);
+#define CHECK_FOR_MATCHES(filter, same, longer, shorter, different) \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV02Pkt::makeName("/foo/bar", m_keyChain), \
+                                 InterestV02Pkt::makeState()), same);    \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV03Pkt::makeName("/foo/bar", m_keyChain), \
+                                 InterestV03Pkt::makeState()), same);    \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, DataPkt::makeName("/foo/bar", m_keyChain), \
+                                 DataPkt::makeState()), same);           \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV02Pkt::makeName("/foo/bar/bar", m_keyChain), \
+                                 InterestV02Pkt::makeState()), longer);  \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV03Pkt::makeName("/foo/bar/bar", m_keyChain), \
+                                 InterestV03Pkt::makeState()), longer);  \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, DataPkt::makeName("/foo/bar/bar", m_keyChain), \
+                                 DataPkt::makeState()), longer);         \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV02Pkt::makeName("/foo", m_keyChain), \
+                                 InterestV02Pkt::makeState()), shorter); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV03Pkt::makeName("/foo", m_keyChain), \
+                                 InterestV03Pkt::makeState()), shorter); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, DataPkt::makeName("/foo", m_keyChain), \
+                                 DataPkt::makeState()), shorter);        \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV02Pkt::makeName("/other/prefix", m_keyChain), \
+                                 InterestV02Pkt::makeState()), different); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, InterestV03Pkt::makeName("/other/prefix", m_keyChain), \
+                                 InterestV03Pkt::makeState()), different); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, DataPkt::makeName("/other/prefix", m_keyChain), \
+                                 DataPkt::makeState()), different);
 
 BOOST_AUTO_TEST_CASE(RelationName)
 {
@@ -85,7 +90,7 @@
   CHECK_FOR_MATCHES(f3, false, true, false, false);
 }
 
-BOOST_FIXTURE_TEST_SUITE(Create, FilterFixture)
+BOOST_FIXTURE_TEST_SUITE(Create, IdentityManagementFixture)
 
 BOOST_AUTO_TEST_CASE(Errors)
 {
diff --git a/tests/unit/security/validator-config/rule.t.cpp b/tests/unit/security/validator-config/rule.t.cpp
index dbc81bf..75eb6dc 100644
--- a/tests/unit/security/validator-config/rule.t.cpp
+++ b/tests/unit/security/validator-config/rule.t.cpp
@@ -39,87 +39,80 @@
 BOOST_AUTO_TEST_SUITE(Security)
 BOOST_AUTO_TEST_SUITE(ValidatorConfig)
 
-template<uint32_t PktType>
+template<class Packet>
 class RuleFixture : public IdentityManagementFixture
 {
 public:
   RuleFixture()
-    : rule(ruleId, PktType)
-    , pktName("/foo/bar")
+    : rule(ruleId, Packet::getType())
+    , pktName(Packet::makeName("/foo/bar", m_keyChain))
+    , state(Packet::makeState())
   {
-    if (PktType == tlv::Interest) {
-      pktName = Name("/foo/bar/SigInfo/SigValue");
-    }
   }
 
 public:
   const std::string ruleId = "rule-id";
   Rule rule;
   Name pktName;
+  shared_ptr<ValidationState> state;
 };
 
-using PktTypes = boost::mpl::vector_c<uint32_t, tlv::Data, tlv::Interest>;
+using PktTypes = boost::mpl::vector<DataPkt, InterestV02Pkt, InterestV03Pkt>;
 
 BOOST_AUTO_TEST_SUITE(TestRule)
 
-BOOST_FIXTURE_TEST_CASE(Errors, RuleFixture<tlv::Data>)
+BOOST_FIXTURE_TEST_CASE(Errors, RuleFixture<DataPkt>)
 {
-  BOOST_CHECK_THROW(rule.match(tlv::Interest, this->pktName), Error);
-
-  auto state = make_shared<DummyValidationState>();
+  BOOST_CHECK_THROW(rule.match(tlv::Interest, this->pktName, state), Error);
   BOOST_CHECK_THROW(rule.check(tlv::Interest, this->pktName, "/foo/bar", state), Error);
 }
 
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(Constructor, PktType, PktTypes, RuleFixture<PktType::value>)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Constructor, PktType, PktTypes, RuleFixture<PktType>)
 {
   BOOST_CHECK_EQUAL(this->rule.getId(), this->ruleId);
-  BOOST_CHECK_EQUAL(this->rule.getPktType(), PktType::value);
+  BOOST_CHECK_EQUAL(this->rule.getPktType(), PktType::getType());
 }
 
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(EmptyRule, PktType, PktTypes, RuleFixture<PktType::value>)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(EmptyRule, PktType, PktTypes, RuleFixture<PktType>)
 {
-  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, this->pktName), true);
-
-  auto state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), false);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::getType(), this->pktName, this->state), true);
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::getType(), this->pktName, "/foo/bar", this->state), false);
 }
 
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(Filters, PktType, PktTypes, RuleFixture<PktType::value>)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Filters, PktType, PktTypes, RuleFixture<PktType>)
 {
   this->rule.addFilter(make_unique<RegexNameFilter>(Regex("^<foo><bar>$")));
 
-  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, this->pktName), true);
-  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, "/not" + this->pktName.toUri()), false);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::getType(), this->pktName, this->state), true);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::getType(), "/not" + this->pktName.toUri(), this->state), false);
 
   this->rule.addFilter(make_unique<RegexNameFilter>(Regex("^<not><foo><bar>$")));
 
-  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, this->pktName), true);
-  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, "/not" + this->pktName.toUri()), true);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::getType(), this->pktName, this->state), true);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::getType(), "/not" + this->pktName.toUri(), this->state), true);
 
-  auto state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), false);
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::getType(), this->pktName, "/foo/bar", this->state), false);
 }
 
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(Checkers, PktType, PktTypes, RuleFixture<PktType::value>)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Checkers, PktType, PktTypes, RuleFixture<PktType>)
 {
   this->rule.addChecker(make_unique<HyperRelationChecker>("^(<>+)$", "\\1",
                                                         "^<not>?(<>+)$", "\\1",
                                                         NameRelation::EQUAL));
 
-  auto state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), true);
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::getType(), this->pktName, "/foo/bar", this->state), true);
 
-  state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/not/foo/bar", state), true);
+  this->state = PktType::makeState(); // reset state
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::getType(), this->pktName, "/not/foo/bar", this->state), true);
 
   this->rule.addChecker(make_unique<HyperRelationChecker>("^(<>+)$", "\\1",
                                                         "^(<>+)$", "\\1",
                                                         NameRelation::EQUAL));
-  state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), true);
+  this->state = PktType::makeState(); // reset state
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::getType(), this->pktName, "/foo/bar", this->state), true);
 
-  state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/not/foo/bar", state), false);
+  this->state = PktType::makeState(); // reset state
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::getType(), this->pktName, "/not/foo/bar", this->state), false);
 }
 
 BOOST_AUTO_TEST_SUITE(Create)
@@ -153,11 +146,11 @@
   BOOST_CHECK_THROW(Rule::create(makeSection(config), "test-config"), Error);
 }
 
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(FilterAndChecker, PktType, PktTypes, RuleFixture<PktType::value>)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(FilterAndChecker, PktType, PktTypes, RuleFixture<PktType>)
 {
   std::string config = R"CONF(
       id rule-id
-      for )CONF" + (PktType::value == tlv::Data ? "data"s : "interest"s) + R"CONF(
+      for )CONF" + (PktType::getType() == tlv::Data ? "data"s : "interest"s) + R"CONF(
       filter
       {
         type name
@@ -183,14 +176,13 @@
     )CONF";
   auto rule = Rule::create(makeSection(config), "test-config");
 
-  BOOST_CHECK_EQUAL(rule->match(PktType::value, this->pktName), true);
-  BOOST_CHECK_EQUAL(rule->match(PktType::value, "/not" + this->pktName.toUri()), false);
+  BOOST_CHECK_EQUAL(rule->match(PktType::getType(), this->pktName, this->state), true);
+  BOOST_CHECK_EQUAL(rule->match(PktType::getType(), "/not" + this->pktName.toUri(), this->state), false);
 
-  auto state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(rule->check(PktType::value, this->pktName, "/foo/bar", state), true);
+  BOOST_CHECK_EQUAL(rule->check(PktType::getType(), this->pktName, "/foo/bar", this->state), true);
 
-  state = make_shared<DummyValidationState>();
-  BOOST_CHECK_EQUAL(rule->check(PktType::value, this->pktName, "/not/foo/bar", state), false);
+  this->state = PktType::makeState(); // reset state
+  BOOST_CHECK_EQUAL(rule->check(PktType::getType(), this->pktName, "/not/foo/bar", this->state), false);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // Create
diff --git a/tests/unit/security/validator-fixture.hpp b/tests/unit/security/validator-fixture.hpp
index 276ac40..e9e5d5b 100644
--- a/tests/unit/security/validator-fixture.hpp
+++ b/tests/unit/security/validator-fixture.hpp
@@ -179,6 +179,84 @@
   }
 };
 
+
+struct DataPkt
+{
+  static uint32_t
+  getType()
+  {
+    return tlv::Data;
+  }
+
+  static Name
+  makeName(Name name, KeyChain& keyChain)
+  {
+    return name;
+  }
+
+  static shared_ptr<ValidationState>
+  makeState()
+  {
+    return make_shared<DummyValidationState>();
+  }
+};
+
+struct InterestV02Pkt
+{
+  static uint32_t
+  getType()
+  {
+    return tlv::Interest;
+  }
+
+  static Name
+  makeName(Name name, KeyChain& keyChain)
+  {
+    Interest interest(name);
+    interest.setCanBePrefix(false);
+    SigningInfo params;
+    params.setSignedInterestFormat(SignedInterestFormat::V02);
+    keyChain.sign(interest, params);
+    return interest.getName();
+  }
+
+  static shared_ptr<ValidationState>
+  makeState()
+  {
+    auto state = make_shared<DummyValidationState>();
+    state->setTag(make_shared<SignedInterestFormatTag>(SignedInterestFormat::V02));
+    return state;
+  }
+};
+
+struct InterestV03Pkt
+{
+  static uint32_t
+  getType()
+  {
+    return tlv::Interest;
+  }
+
+  static Name
+  makeName(Name name, KeyChain& keyChain)
+  {
+    Interest interest(name);
+    interest.setCanBePrefix(false);
+    SigningInfo params;
+    params.setSignedInterestFormat(SignedInterestFormat::V03);
+    keyChain.sign(interest, params);
+    return interest.getName();
+  }
+
+  static shared_ptr<ValidationState>
+  makeState()
+  {
+    auto state = make_shared<DummyValidationState>();
+    state->setTag(make_shared<SignedInterestFormatTag>(SignedInterestFormat::V03));
+    return state;
+  }
+};
+
 } // namespace tests
 } // inline namespace v2
 } // namespace security