Probe: add probe token to New and Challenge

Change-Id: Iae8b009bb2e78f03910e53fb49e750ebc8a6e6ae
diff --git a/src/ca-detail/ca-sqlite.cpp b/src/ca-detail/ca-sqlite.cpp
index bb88cc7..cf53022 100644
--- a/src/ca-detail/ca-sqlite.cpp
+++ b/src/ca-detail/ca-sqlite.cpp
@@ -48,7 +48,8 @@
     challenge_secrets TEXT,
     challenge_tp TEXT,
     remaining_tries INTEGER,
-    remaining_time INTEGER
+    remaining_time INTEGER,
+    probe_token BLOB
   );
 CREATE UNIQUE INDEX IF NOT EXISTS
   CertRequestIdIndex ON CertRequests(request_id);
@@ -129,9 +130,14 @@
     std::string challengeTp = statement.getString(9);
     int remainingTries = statement.getInt(10);
     int remainingTime = statement.getInt(11);
-    return CertificateRequest(caName, requestId, status, challengeStatus, challengeType,
+    CertificateRequest request(caName, requestId, status, challengeStatus, challengeType,
                               challengeTp, remainingTime, remainingTries,
                               convertString2Json(challengeSecrets), cert);
+    if (statement.getSize(12) != 0) {
+      shared_ptr<Data> probeToken = make_shared<Data>(statement.getBlock(12));
+      request.setProbeToken(probeToken);
+    }
+    return request;
   }
   else {
     BOOST_THROW_EXCEPTION(Error("Request " + requestId + " cannot be fetched from database"));
@@ -141,6 +147,7 @@
 void
 CaSqlite::addRequest(const CertificateRequest& request)
 {
+  // check whether request is there already
   Sqlite3Statement statement1(m_database,
                               R"_SQLTEXT_(SELECT * FROM CertRequests where cert_key_name = ?)_SQLTEXT_");
   statement1.bind(1, request.m_cert.getKeyName().wireEncode(), SQLITE_TRANSIENT);
@@ -149,6 +156,7 @@
     return;
   }
 
+  // check whether certificate is already issued
   Sqlite3Statement statement2(m_database,
                               R"_SQLTEXT_(SELECT * FROM IssuedCerts where cert_key_name = ?)_SQLTEXT_");
   statement2.bind(1, request.m_cert.getKeyName().wireEncode(), SQLITE_TRANSIENT);
@@ -157,25 +165,54 @@
     return;
   }
 
-  Sqlite3Statement statement(m_database,
-                             R"_SQLTEXT_(INSERT INTO CertRequests (request_id, ca_name, status,
+  if (request.m_probeToken != nullptr) {
+    Sqlite3Statement statement(
+        m_database,
+        R"_SQLTEXT_(INSERT INTO CertRequests (request_id, ca_name, status,
+                             challenge_status, cert_key_name, cert_request, challenge_type, challenge_secrets,
+                             challenge_tp, remaining_tries, remaining_time, probe_token)
+                             values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?))_SQLTEXT_");
+    statement.bind(1, request.m_requestId, SQLITE_TRANSIENT);
+    statement.bind(2, request.m_caName.wireEncode(), SQLITE_TRANSIENT);
+    statement.bind(3, request.m_status);
+    statement.bind(4, request.m_challengeStatus, SQLITE_TRANSIENT);
+    statement.bind(5, request.m_cert.getKeyName().wireEncode(),
+                   SQLITE_TRANSIENT);
+    statement.bind(6, request.m_cert.wireEncode(), SQLITE_TRANSIENT);
+    statement.bind(7, request.m_challengeType, SQLITE_TRANSIENT);
+    statement.bind(8, convertJson2String(request.m_challengeSecrets),
+                   SQLITE_TRANSIENT);
+    statement.bind(9, request.m_challengeTp, SQLITE_TRANSIENT);
+    statement.bind(10, request.m_remainingTries);
+    statement.bind(11, request.m_remainingTime);
+    statement.bind(12, request.m_probeToken->wireEncode(), SQLITE_TRANSIENT);
+    if (statement.step() != SQLITE_DONE) {
+      BOOST_THROW_EXCEPTION(Error("Request " + request.m_requestId + " cannot be added to database"));
+    }
+  }
+  else {
+    Sqlite3Statement statement(
+        m_database,
+        R"_SQLTEXT_(INSERT INTO CertRequests (request_id, ca_name, status,
                              challenge_status, cert_key_name, cert_request, challenge_type, challenge_secrets,
                              challenge_tp, remaining_tries, remaining_time)
                              values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?))_SQLTEXT_");
-  statement.bind(1, request.m_requestId, SQLITE_TRANSIENT);
-  statement.bind(2, request.m_caName.wireEncode(), SQLITE_TRANSIENT);
-  statement.bind(3, request.m_status);
-  statement.bind(4, request.m_challengeStatus, SQLITE_TRANSIENT);
-  statement.bind(5, request.m_cert.getKeyName().wireEncode(), SQLITE_TRANSIENT);
-  statement.bind(6, request.m_cert.wireEncode(), SQLITE_TRANSIENT);
-  statement.bind(7, request.m_challengeType, SQLITE_TRANSIENT);
-  statement.bind(8, convertJson2String(request.m_challengeSecrets), SQLITE_TRANSIENT);
-  statement.bind(9, request.m_challengeTp, SQLITE_TRANSIENT);
-  statement.bind(10, request.m_remainingTries);
-  statement.bind(11, request.m_remainingTime);
-
-  if (statement.step() != SQLITE_DONE) {
-    BOOST_THROW_EXCEPTION(Error("Request " + request.m_requestId + " cannot be added to database"));
+    statement.bind(1, request.m_requestId, SQLITE_TRANSIENT);
+    statement.bind(2, request.m_caName.wireEncode(), SQLITE_TRANSIENT);
+    statement.bind(3, request.m_status);
+    statement.bind(4, request.m_challengeStatus, SQLITE_TRANSIENT);
+    statement.bind(5, request.m_cert.getKeyName().wireEncode(),
+                   SQLITE_TRANSIENT);
+    statement.bind(6, request.m_cert.wireEncode(), SQLITE_TRANSIENT);
+    statement.bind(7, request.m_challengeType, SQLITE_TRANSIENT);
+    statement.bind(8, convertJson2String(request.m_challengeSecrets),
+                   SQLITE_TRANSIENT);
+    statement.bind(9, request.m_challengeTp, SQLITE_TRANSIENT);
+    statement.bind(10, request.m_remainingTries);
+    statement.bind(11, request.m_remainingTime);
+    if (statement.step() != SQLITE_DONE) {
+      BOOST_THROW_EXCEPTION(Error("Request " + request.m_requestId + " cannot be added to database"));
+    }
   }
 }
 
diff --git a/src/ca-module.cpp b/src/ca-module.cpp
index b91a03c..1794528 100644
--- a/src/ca-module.cpp
+++ b/src/ca-module.cpp
@@ -189,7 +189,29 @@
     return;
   }
 
-  // verify the self-signed certificate and the request
+  // parse probe token if any
+  std::string probeTokenStr = parameterJson.get("probe-token", "");
+  shared_ptr<Data> probeToken = nullptr;
+  if (probeTokenStr != "") {
+    try {
+      std::stringstream ss(probeTokenStr);
+      probeToken = io::load<security::v2::Certificate>(ss);
+    }
+    catch (const std::exception& e) {
+      _LOG_ERROR("Unrecognized probe token " << e.what());
+      return;
+    }
+  }
+  if (probeToken != nullptr) {
+    Name prefix = m_config.m_caName;
+    prefix.append("CA").append("_PROBE");
+    if (!prefix.isPrefixOf(probeToken->getName())) {
+      // the carried probe token is not a Probe Data packet
+      return;
+    }
+  }
+
+  // verify the self-signed certificate, the request, and the token
   if (!m_config.m_caName.isPrefixOf(clientCert->getName()) // under ca prefix
       || !security::v2::Certificate::isValidName(clientCert->getName()) // is valid cert name
       || clientCert->getName().size() != m_config.m_caName.size() + IS_SUBNAME_MIN_OFFSET) {
@@ -204,15 +226,27 @@
     _LOG_TRACE("Interest with bad signature.");
     return;
   }
+  if (probeToken != nullptr) {
+    const auto& pib = m_keyChain.getPib();
+    const auto& key = pib.getIdentity(m_config.m_caName).getDefaultKey();
+    const auto& caCert = key.getDefaultCertificate();
+    if (!security::verifySignature(*probeToken, caCert)) {
+      _LOG_TRACE("Token with bad signature.");
+      return;
+    }
+  }
 
   // create new request instance
   std::string requestId = std::to_string(random::generateWord64());
   CertificateRequest certRequest(m_config.m_caName, requestId, STATUS_BEFORE_CHALLENGE, *clientCert);
+  if (probeToken != nullptr) {
+    certRequest.setProbeToken(probeToken);
+  }
   try {
     m_storage->addRequest(certRequest);
   }
   catch (const std::exception& e) {
-    _LOG_TRACE("Cannot add new request instance into the storage" << e.what());
+    _LOG_TRACE("Cannot add new request instance into the storage " << e.what());
     return;
   }
 
@@ -390,7 +424,7 @@
 CaModule::getCertificateRequest(const Interest& request)
 {
   std::string requestId = readString(request.getName().at(m_config.m_caName.size() + 2));
-  _LOG_TRACE("Requet Id to query the database " << requestId);
+  _LOG_TRACE("Request Id to query the database " << requestId);
   CertificateRequest certRequest;
   try {
     certRequest = m_storage->getRequest(requestId);
diff --git a/src/ca-module.hpp b/src/ca-module.hpp
index 9f40c24..3926d97 100644
--- a/src/ca-module.hpp
+++ b/src/ca-module.hpp
@@ -64,6 +64,12 @@
   bool
   setStatusUpdateCallback(const StatusUpdateCallback& onUpdateCallback);
 
+  static JsonSection
+  jsonFromBlock(const Block& block);
+
+  static Block
+  dataContentFromJson(const JsonSection& jsonSection);
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   void
   onProbe(const Interest& request);
@@ -86,15 +92,9 @@
   security::v2::Certificate
   issueCertificate(const CertificateRequest& certRequest);
 
-  static Block
-  dataContentFromJson(const JsonSection& jsonSection);
-
   void
   registerPrefix();
 
-  static JsonSection
-  jsonFromBlock(const Block& block);
-
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   const JsonSection
   genProbeResponseJson(const Name& identifier,
diff --git a/src/certificate-request.cpp b/src/certificate-request.cpp
index 8db1f6f..22b67b0 100644
--- a/src/certificate-request.cpp
+++ b/src/certificate-request.cpp
@@ -52,6 +52,12 @@
 {
 }
 
+void
+CertificateRequest::setProbeToken(const shared_ptr<Data>& probeToken)
+{
+  m_probeToken = probeToken;
+}
+
 std::ostream&
 operator<<(std::ostream& os, const CertificateRequest& request)
 {
diff --git a/src/certificate-request.hpp b/src/certificate-request.hpp
index 8fa5a0d..96c70ca 100644
--- a/src/certificate-request.hpp
+++ b/src/certificate-request.hpp
@@ -45,11 +45,16 @@
                      const std::string& challengeStatus, const std::string& challengeType,
                      const std::string& challengeTp, int remainingTime, int remainingTries,
                      const JsonSection& challengeSecrets, const security::v2::Certificate& cert);
+
+  void
+  setProbeToken(const std::shared_ptr<Data>& probeToken);
+
 public:
   Name m_caName;
   std::string m_requestId = "";
   int m_status = -1;
   security::v2::Certificate m_cert;
+  std::shared_ptr<Data> m_probeToken = nullptr;
 
   std::string m_challengeStatus = "";
   std::string m_challengeType = "";
diff --git a/src/challenge-module/challenge-email.cpp b/src/challenge-module/challenge-email.cpp
index 8c49726..793eed7 100644
--- a/src/challenge-module/challenge-email.cpp
+++ b/src/challenge-module/challenge-email.cpp
@@ -19,6 +19,7 @@
  */
 
 #include "challenge-email.hpp"
+#include "../ca-module.hpp"
 #include "../logging.hpp"
 #include <regex>
 
@@ -57,6 +58,16 @@
       request.m_challengeStatus = FAILURE_INVALID_EMAIL;
       return;
     }
+    // check whether this email is the same as the one used in PROBE
+    if (request.m_probeToken != nullptr) {
+      const auto& content = request.m_probeToken->getContent();
+      const auto& json = CaModule::jsonFromBlock(content);
+      const auto& expectedEmail = json.get("email", "");
+      Name expectedPrefix(json.get(JSON_CA_NAME, ""));
+      if (expectedEmail != emailAddress || !expectedPrefix.isPrefixOf(request.m_cert.getName())) {
+        return;
+      }
+    }
     request.m_status = STATUS_CHALLENGE;
     request.m_challengeStatus = NEED_CODE;
     request.m_challengeType = CHALLENGE_TYPE;
diff --git a/src/client-module.cpp b/src/client-module.cpp
index 29898bc..d107114 100644
--- a/src/client-module.cpp
+++ b/src/client-module.cpp
@@ -116,7 +116,7 @@
 shared_ptr<Interest>
 ClientModule::generateNewInterest(const time::system_clock::TimePoint& notBefore,
                                   const time::system_clock::TimePoint& notAfter,
-                                  const Name& identityName)
+                                  const Name& identityName, const shared_ptr<Data>& probeToken)
 {
   // Name requestedName = identityName;
   if (!identityName.empty()) { // if identityName is not empty, find the corresponding CA
@@ -170,7 +170,7 @@
   auto interest = make_shared<Interest>(interestName);
   interest->setMustBeFresh(true);
   interest->setCanBePrefix(false);
-  interest->setApplicationParameters(paramFromJson(genNewRequestJson(m_ecdh.getBase64PubKey(), certRequest)));
+  interest->setApplicationParameters(paramFromJson(genNewRequestJson(m_ecdh.getBase64PubKey(), certRequest, probeToken)));
 
   // sign the Interest packet
   m_keyChain.sign(*interest, signingByKey(m_key.getName()));
@@ -341,7 +341,8 @@
 }
 
 const JsonSection
-ClientModule::genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest)
+ClientModule::genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest,
+                                const shared_ptr<Data>& probeToken)
 {
   JsonSection root;
   std::stringstream ss;
@@ -356,6 +357,23 @@
   }
   root.put(JSON_CLIENT_ECDH, ecdhPub);
   root.put(JSON_CLIENT_CERT_REQ, ss.str());
+  if (probeToken != nullptr) {
+    // clear the stringstream
+    ss.str("");
+    ss.clear();
+    // transform the probe data into a base64 string
+    try {
+      security::transform::bufferSource(probeToken->wireEncode().wire(), probeToken->wireEncode().size())
+      >> security::transform::base64Encode(true)
+      >> security::transform::streamSink(ss);
+    }
+    catch (const security::transform::Error& e) {
+      _LOG_ERROR("Cannot convert self-signed cert into BASE64 string " << e.what());
+      return root;
+    }
+    // add the token into the JSON
+    root.put("probe-token", ss.str());
+  }
   return root;
 }
 
diff --git a/src/client-module.hpp b/src/client-module.hpp
index 30cd333..388ad85 100644
--- a/src/client-module.hpp
+++ b/src/client-module.hpp
@@ -89,7 +89,7 @@
   shared_ptr<Interest>
   generateNewInterest(const time::system_clock::TimePoint& notBefore,
                       const time::system_clock::TimePoint& notAfter,
-                      const Name& identityName = Name());
+                      const Name& identityName = Name(), const shared_ptr<Data>& probeToken = nullptr);
 
   std::list<std::string>
   onNewResponse(const Data& reply);
@@ -124,7 +124,8 @@
   genProbeRequestJson(const ClientCaItem& ca, const std::string& probeInfo);
 
   const JsonSection
-  genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest);
+  genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest,
+                    const shared_ptr<Data>& probeToken = nullptr);
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   ClientConfig m_config;