security: Add helpers for Command Interest

refs: #1238

Change-Id: I5a42f888b83bcc6dc51ea02045e438a4905ed145
diff --git a/src/helper/command-interest-generator.cpp b/src/helper/command-interest-generator.cpp
new file mode 100644
index 0000000..f08d524
--- /dev/null
+++ b/src/helper/command-interest-generator.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "command-interest-generator.hpp"
+#include "../util/time.hpp"
+#include "../util/random.hpp"
+
+#include <unistd.h>
+
+namespace ndn
+{
+const Name CommandInterestGenerator::DEFAULT_CERTIFICATE_NAME = Name();
+
+CommandInterestGenerator::CommandInterestGenerator()
+  : m_lastTimestamp(time::now() / 1000000)
+{}
+
+void
+CommandInterestGenerator::generate(Interest& interest, 
+				   const Name& certificateName /*= DEFAULT_CERTIFICATE_NAME*/)
+{
+  int64_t timestamp = time::now();
+  while(timestamp == m_lastTimestamp)
+    {
+      usleep(1000); //Guarantee unqiueness of timestamp
+      timestamp = time::now();
+    }
+  
+  interest.getName().append(name::Component::fromNumber(timestamp)).append(name::Component::fromNumber(random::generateWord64()));
+
+  if(certificateName == DEFAULT_CERTIFICATE_NAME)
+    m_keyChain.sign(interest);
+  else
+    m_keyChain.sign(interest, certificateName);
+
+  m_lastTimestamp = timestamp;
+}
+  
+void
+CommandInterestGenerator::generateWithIdentity(Interest& interest, const Name& identity)
+{
+  int64_t timestamp = time::now() / 1000000;
+  if(timestamp <= m_lastTimestamp)
+    timestamp = m_lastTimestamp + 1;
+  
+  interest.getName().append(name::Component::fromNumber(timestamp)).append(name::Component::fromNumber(random::generateWord64()));
+
+  m_keyChain.signByIdentity(interest, identity);
+
+  m_lastTimestamp = timestamp;
+}
+
+}//ndn
diff --git a/src/helper/command-interest-generator.hpp b/src/helper/command-interest-generator.hpp
new file mode 100644
index 0000000..70208f2
--- /dev/null
+++ b/src/helper/command-interest-generator.hpp
@@ -0,0 +1,40 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NDN_COMMAND_INTEREST_GENERATOR_HPP
+#define NDN_COMMAND_INTEREST_GENERATOR_HPP
+
+#include "../interest.hpp"
+#include "../security/key-chain.hpp"
+
+
+namespace ndn
+{
+
+class CommandInterestGenerator
+{
+public:
+  static const Name DEFAULT_CERTIFICATE_NAME;
+
+  CommandInterestGenerator();
+
+  virtual
+  ~CommandInterestGenerator() {}
+
+  void
+  generate(Interest& interest, const Name& certificateName = DEFAULT_CERTIFICATE_NAME);
+  
+  void
+  generateWithIdentity(Interest& interest, const Name& identity);
+  
+private:
+  int64_t m_lastTimestamp;
+  KeyChain m_keyChain;
+};
+
+}//ndn
+
+#endif
diff --git a/src/helper/command-interest-validator.cpp b/src/helper/command-interest-validator.cpp
new file mode 100644
index 0000000..396ee8f
--- /dev/null
+++ b/src/helper/command-interest-validator.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "command-interest-validator.hpp"
+
+namespace ndn
+{
+const ssize_t CommandInterestValidator::POS_SIG_VALUE  = -1;
+const ssize_t CommandInterestValidator::POS_SIG_INFO   = -2;
+const ssize_t CommandInterestValidator::POS_RANDOM_VAL = -3;
+const ssize_t CommandInterestValidator::POS_TIMESTAMP  = -4;
+const int64_t CommandInterestValidator::GRACE_INTERVAL = 3000;
+
+void
+CommandInterestValidator::checkPolicy (const Interest& interest, 
+					      int stepCount, 
+					      const OnInterestValidated &onValidated, 
+					      const OnInterestValidationFailed &onValidationFailed,
+					      std::vector<shared_ptr<ValidationRequest> > &nextSteps)
+{
+  try{
+    Name interestName  = interest.getName();
+
+    Signature signature(interestName.get(POS_SIG_INFO).blockFromValue(), 
+			interestName.get(POS_SIG_VALUE).blockFromValue());
+    
+    SignatureSha256WithRsa sig(signature);
+    const Name & keyLocatorName = sig.getKeyLocator().getName();
+    Name keyName = IdentityCertificate::certificateNameToPublicKeyName(keyLocatorName);
+
+    //Check if command is in the trusted scope
+    bool inScope = false;
+    std::list<SecRuleSpecific>::iterator scopeIt = m_trustScopeForInterest.begin();
+    for(; scopeIt != m_trustScopeForInterest.end(); scopeIt++)
+      {
+	if(scopeIt->satisfy(interestName, keyName))
+	  {
+	    inScope = true;
+	    break;
+	  }
+      }
+    if(inScope == false)
+      {
+	onValidationFailed(interest.shared_from_this());
+	return;
+      }
+
+    //Check if timestamp is valid
+    uint64_t timestamp = interestName.get(POS_TIMESTAMP).toNumber();
+    uint64_t current = static_cast<uint64_t>(time::now()/1000000);
+    std::map<Name, uint64_t>::const_iterator timestampIt = m_lastTimestamp.find(keyName);
+    if(timestampIt == m_lastTimestamp.end())
+      {
+	if(timestamp > (current + m_graceInterval) || (timestamp + m_graceInterval) < current)
+	  {
+	    onValidationFailed(interest.shared_from_this());
+	    return;
+	  }
+      }
+    else if(m_lastTimestamp[keyName] >= timestamp)
+      {
+	onValidationFailed(interest.shared_from_this());
+	return;
+      }
+    
+    //Check if signature can be verified
+    Name signedName = interestName.getPrefix(-1);
+    Buffer signedBlob = Buffer(signedName.wireEncode().value(), signedName.wireEncode().value_size()); //These two lines could be optimized
+    if(!Validator::verifySignature(signedBlob.buf(), signedBlob.size(), sig, m_trustAnchorsForInterest[keyName]))
+      {
+	onValidationFailed(interest.shared_from_this());
+	return;
+      }
+
+    m_lastTimestamp[keyName] = timestamp;
+    onValidated(interest.shared_from_this());
+    return;
+
+  }catch(...){
+    onValidationFailed(interest.shared_from_this());
+  }
+}
+
+}//ndn
diff --git a/src/helper/command-interest-validator.hpp b/src/helper/command-interest-validator.hpp
new file mode 100644
index 0000000..7021a78
--- /dev/null
+++ b/src/helper/command-interest-validator.hpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NDN_COMMAND_INTEREST_VALIDATOR_HPP
+#define NDN_COMMAND_INTEREST_VALIDATOR_HPP
+
+#include "../security/validator.hpp"
+#include "../security/identity-certificate.hpp"
+#include "../security/sec-rule-specific.hpp"
+
+
+namespace ndn
+{
+
+class CommandInterestValidator : public Validator
+{
+public:
+  static const ssize_t POS_SIG_VALUE;
+  static const ssize_t POS_SIG_INFO;
+  static const ssize_t POS_RANDOM_VAL;
+  static const ssize_t POS_TIMESTAMP;
+  static const int64_t GRACE_INTERVAL;
+
+  CommandInterestValidator(int64_t graceInterval = GRACE_INTERVAL) 
+  { m_graceInterval = (graceInterval < 0 ? GRACE_INTERVAL : graceInterval); }
+
+  virtual
+  ~CommandInterestValidator() {}
+
+  inline void
+  addInterestRule(const std::string& regex, const IdentityCertificate& certificate);
+
+  inline void
+  addInterestRule(const std::string& regex, const Name& keyName, const PublicKey& publicKey);
+
+protected:
+  virtual void
+  checkPolicy (const Data& data, 
+               int stepCount, 
+               const OnDataValidated &onValidated, 
+               const OnDataValidationFailed &onValidationFailed,
+               std::vector<shared_ptr<ValidationRequest> > &nextSteps)
+  { onValidationFailed(data.shared_from_this()); }
+  
+  virtual void
+  checkPolicy (const Interest& interest, 
+               int stepCount, 
+               const OnInterestValidated &onValidated, 
+               const OnInterestValidationFailed &onValidationFailed,
+               std::vector<shared_ptr<ValidationRequest> > &nextSteps);
+private:
+  int64_t m_graceInterval; //ms
+  std::map<Name, PublicKey> m_trustAnchorsForInterest;
+  std::list<SecRuleSpecific> m_trustScopeForInterest;
+  std::map<Name, uint64_t> m_lastTimestamp;
+};
+
+void
+CommandInterestValidator::addInterestRule(const std::string& regex, const IdentityCertificate& certificate)
+{
+  Name keyName = IdentityCertificate::certificateNameToPublicKeyName(certificate.getName());
+  addInterestRule(regex, keyName, certificate.getPublicKeyInfo());
+}
+
+void
+CommandInterestValidator::addInterestRule(const std::string& regex, const Name& keyName, const PublicKey& publicKey)
+{
+  m_trustAnchorsForInterest[keyName] = publicKey;
+  shared_ptr<Regex> interestRegex = make_shared<Regex>(regex);
+  shared_ptr<Regex> signerRegex = Regex::fromName(keyName, true);
+  m_trustScopeForInterest.push_back(SecRuleSpecific(interestRegex, signerRegex));
+}
+
+}//ndn
+
+#endif
diff --git a/src/security/key-chain.hpp b/src/security/key-chain.hpp
index e79b9c2..86589eb 100644
--- a/src/security/key-chain.hpp
+++ b/src/security/key-chain.hpp
@@ -201,7 +201,7 @@
   void 
   sign(Data& data, const Name& certificateName)
   {
-    ptr_lib::shared_ptr<IdentityCertificate> cert = Info::getCertificate(certificateName);
+    shared_ptr<IdentityCertificate> cert = Info::getCertificate(certificateName);
     if (!cert)
       throw InfoError("Requested certificate [" + certificateName.toUri() + "] doesn't exist");
 
@@ -216,14 +216,14 @@
   void
   sign(Interest &interest, const Name &certificateName)
   {
-    ptr_lib::shared_ptr<IdentityCertificate> cert = Info::getCertificate(certificateName);
+    shared_ptr<IdentityCertificate> cert = Info::getCertificate(certificateName);
     if(!static_cast<bool>(cert))
       throw InfoError("Requested certificate [" + certificateName.toUri() + "] doesn't exist");
 
     SignatureSha256WithRsa signature;
     signature.setKeyLocator(certificateName.getPrefix(-1)); // implicit conversion should take care
 
-    Name interestName = interest.getName().append(Name::Component::fromNumber(getNow())).append(signature.getInfo());
+    Name& interestName = interest.getName().append(signature.getInfo());
 
     signature.setValue(Tpm::signInTpm(interestName.wireEncode().value(), 
                                       interestName.wireEncode().value_size(), 
@@ -402,8 +402,7 @@
     SignatureSha256WithRsa signature;
     signature.setKeyLocator(certificate.getName().getPrefix(-1)); // implicit conversion should take care
 
-    Name &interestName = interest.getName();
-    interestName.append(Name::Component::fromNumber(getNow())).append(signature.getInfo());
+    Name& interestName = interest.getName().append(signature.getInfo());
 
     signature.setValue(Tpm::signInTpm(interestName.wireEncode().value(), 
                                       interestName.wireEncode().value_size(), 
diff --git a/src/security/validator.cpp b/src/security/validator.cpp
index 7979120..a44c333 100644
--- a/src/security/validator.cpp
+++ b/src/security/validator.cpp
@@ -138,14 +138,14 @@
 {
   const Name &interestName = interest.getName();
 
-  if(interestName.size() < 3)
+  if(interestName.size() < 2)
     return false;
 
   try{
-    const Block &nameBlock = interestName.wireEncode();
+    const Block& nameBlock = interestName.wireEncode();
 
-    Signature sig((++nameBlock.elements().rbegin())->blockFromValue(), 
-                  (nameBlock.elements().rbegin())->blockFromValue());
+    Signature sig(interestName[-2].blockFromValue(), 
+                  interestName[-1].blockFromValue());
 
     switch(sig.getType()){
     case Signature::Sha256WithRsa:
@@ -153,7 +153,7 @@
         SignatureSha256WithRsa sigSha256Rsa(sig);
 
         return verifySignature(nameBlock.value(), 
-                               nameBlock.value_size() - (nameBlock.elements().rbegin())->size(), 
+                               nameBlock.value_size() - interestName[-1].size(), 
                                sigSha256Rsa, key);
       }
     default:
diff --git a/src/util/random.cpp b/src/util/random.cpp
index 6bbe203..c63e916 100644
--- a/src/util/random.cpp
+++ b/src/util/random.cpp
@@ -21,5 +21,17 @@
   return rng.GenerateWord32();
 }
 
+uint64_t
+generateWord64()
+{
+  static CryptoPP::AutoSeededRandomPool rng;
+  
+  uint64_t random;
+
+  rng.GenerateBlock(reinterpret_cast<unsigned char*>(&random), 8);
+
+  return random;
+}
+
 } // namespace random
 } // namespace ndn
diff --git a/src/util/random.hpp b/src/util/random.hpp
index bb5a3a6..f7dbc22 100644
--- a/src/util/random.hpp
+++ b/src/util/random.hpp
@@ -14,6 +14,9 @@
 uint32_t
 generateWord32();
 
+uint64_t
+generateWord64();
+
 } // namespace random
 } // namespace ndn
 
diff --git a/tests/test-command-interest.cpp b/tests/test-command-interest.cpp
new file mode 100644
index 0000000..59619ca
--- /dev/null
+++ b/tests/test-command-interest.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include <boost/test/unit_test.hpp>
+
+#include "helper/command-interest-generator.hpp"
+#include "helper/command-interest-validator.hpp"
+#include "util/random.hpp"
+
+namespace ndn {
+
+BOOST_AUTO_TEST_SUITE(TestCommandInterest)
+
+class TestCore
+{
+public:
+  TestCore()
+    : m_validity(false)
+  {}
+  
+  void
+  validated(const shared_ptr<const Interest>& interest)
+  { m_validity = true; }
+
+  void
+  validationFailed(const shared_ptr<const Interest>& interest)
+  { m_validity = false; }
+
+  void
+  reset()
+  { m_validity = false; }
+
+  bool m_validity;
+}; 
+
+BOOST_AUTO_TEST_CASE (Validation)
+{
+  KeyChain keyChain;
+  Name identity("/TestCommandInterest/Validation");
+  Name certName;
+  BOOST_REQUIRE_NO_THROW(certName = keyChain.createIdentity(identity));
+
+  TestCore core;
+  CommandInterestGenerator generator;
+  CommandInterestValidator validator;
+
+  validator.addInterestRule("^<TestCommandInterest><Validation>", *keyChain.getCertificate(certName));
+
+  //Test a legitimate command
+  shared_ptr<Interest> commandInterest1 = make_shared<Interest>("/TestCommandInterest/Validation/Command1");
+  generator.generateWithIdentity(*commandInterest1, identity);
+  validator.validate(*commandInterest1,
+  		     bind(&TestCore::validated, &core, _1),
+  		     bind(&TestCore::validationFailed, &core, _1));
+  
+  BOOST_CHECK_EQUAL(core.m_validity, true);
+  
+  //Test an outdated command
+  core.reset();
+  shared_ptr<Interest> commandInterest2 = make_shared<Interest>("/TestCommandInterest/Validation/Command2");
+  int64_t timestamp = time::now() / 1000000;
+  timestamp -= 5000;
+  commandInterest2->getName().append(name::Component::fromNumber(timestamp)).append(name::Component::fromNumber(random::generateWord64()));
+  keyChain.signByIdentity(*commandInterest2, identity);
+  validator.validate(*commandInterest2,
+  		     bind(&TestCore::validated, &core, _1),
+  		     bind(&TestCore::validationFailed, &core, _1));
+  
+  BOOST_CHECK_EQUAL(core.m_validity, false);
+  
+  //Test an unauthorized command
+  Name identity2("/TestCommandInterest/Validation2");
+  Name certName2;
+  BOOST_REQUIRE_NO_THROW(certName2 = keyChain.createIdentity(identity2));
+  
+  shared_ptr<Interest> commandInterest3 = make_shared<Interest>("/TestCommandInterest/Validation/Command3");
+  generator.generateWithIdentity(*commandInterest3, identity2);
+  validator.validate(*commandInterest3,
+  		     bind(&TestCore::validated, &core, _1),
+  		     bind(&TestCore::validationFailed, &core, _1));
+  
+  BOOST_CHECK_EQUAL(core.m_validity, false);
+
+  //Test another unauthorized command
+  shared_ptr<Interest> commandInterest4 = make_shared<Interest>("/TestCommandInterest/Validation2/Command");
+  generator.generateWithIdentity(*commandInterest4, identity);
+  validator.validate(*commandInterest4,
+  		     bind(&TestCore::validated, &core, _1),
+  		     bind(&TestCore::validationFailed, &core, _1));
+  
+  BOOST_CHECK_EQUAL(core.m_validity, false);
+
+  BOOST_CHECK_NO_THROW(keyChain.deleteIdentity(identity));
+  BOOST_CHECK_NO_THROW(keyChain.deleteIdentity(identity2));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+}
diff --git a/tests/test-signed-interest.cpp b/tests/test-signed-interest.cpp
index a7df556..aef2530 100644
--- a/tests/test-signed-interest.cpp
+++ b/tests/test-signed-interest.cpp
@@ -19,10 +19,11 @@
 {
   KeyChainImpl<SecPublicInfoSqlite3, SecTpmFile> keyChain;
 
-  Name identityName("/test");
-  Name certificateName = keyChain.createIdentity(identityName);
+  Name identityName("/TestSignedInterest/SignVerify");
+  Name certificateName;
+  BOOST_REQUIRE_NO_THROW(certificateName = keyChain.createIdentity(identityName));
 
-  Interest interest("/test/interest");
+  Interest interest("/TestSignedInterest/SignVerify/Interest1");
   keyChain.signByIdentity(interest, identityName);
   
   Block interestBlock(interest.wireEncode().wire(), interest.wireEncode().size());
@@ -30,10 +31,12 @@
   Interest interest2;
   interest2.wireDecode(interestBlock);
   
-  ptr_lib::shared_ptr<PublicKey> publicKey = keyChain.getPublicKeyFromTpm(keyChain.getDefaultKeyNameForIdentity(identityName));
+  shared_ptr<PublicKey> publicKey = keyChain.getPublicKeyFromTpm(keyChain.getDefaultKeyNameForIdentity(identityName));
   bool result = Validator::verifySignature(interest2, *publicKey);
   
-  BOOST_REQUIRE_EQUAL(result, true);
+  BOOST_CHECK_EQUAL(result, true);
+
+  BOOST_CHECK_NO_THROW(keyChain.deleteIdentity(identityName));
 }
 
 BOOST_AUTO_TEST_SUITE_END()