do some file move and rename
Change-Id: I034ea026c52c588966d2c66fcb3d3deea2e40102
diff --git a/src/identity-challenge/challenge-credential.cpp b/src/identity-challenge/challenge-credential.cpp
new file mode 100644
index 0000000..f6e26c2
--- /dev/null
+++ b/src/identity-challenge/challenge-credential.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2017-2020, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "challenge-credential.hpp"
+#include <iostream>
+#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <ndn-cxx/security/transform/public-key.hpp>
+#include <ndn-cxx/util/io.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+_LOG_INIT(ndncert.challenge.credential);
+NDNCERT_REGISTER_CHALLENGE(ChallengeCredential, "Credential");
+
+const std::string ChallengeCredential::PARAMETER_KEY_CREDENTIAL_CERT = "issued-cert";
+const std::string ChallengeCredential::PARAMETER_KEY_PROOF_OF_PRIVATE_KEY = "proof-of-private-key";
+
+ChallengeCredential::ChallengeCredential(const std::string& configPath)
+ : ChallengeModule("Credential", 1, time::seconds(1))
+{
+ if (configPath.empty()) {
+ m_configFile = std::string(SYSCONFDIR) + "/ndncert/challenge-credential.conf";
+ }
+ else {
+ m_configFile = configPath;
+ }
+}
+
+void
+ChallengeCredential::parseConfigFile()
+{
+ JsonSection config;
+ try {
+ boost::property_tree::read_json(m_configFile, config);
+ }
+ catch (const boost::property_tree::info_parser_error& error) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to parse configuration file " + m_configFile +
+ " " + error.message() + " line " + std::to_string(error.line())));
+ }
+
+ if (config.begin() == config.end()) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Error processing configuration file: " + m_configFile + " no data"));
+ }
+
+ m_trustAnchors.clear();
+ auto anchorList = config.get_child("anchor-list");
+ auto it = anchorList.begin();
+ for (; it != anchorList.end(); it++) {
+ std::istringstream ss(it->second.get("certificate", ""));
+ auto cert = io::load<security::v2::Certificate>(ss);
+ if (cert == nullptr) {
+ _LOG_ERROR("Cannot load the certificate from config file");
+ continue;
+ }
+ m_trustAnchors.push_back(*cert);
+ }
+}
+
+// For CA
+std::tuple<ErrorCode, std::string>
+ChallengeCredential::handleChallengeRequest(const Block& params, CaState& request)
+{
+ params.parse();
+ if (m_trustAnchors.empty()) {
+ parseConfigFile();
+ }
+ security::v2::Certificate credential;
+ const uint8_t* signature;
+ size_t signatureLen;
+ const auto& elements = params.elements();
+ for (size_t i = 0; i < elements.size(); i++) {
+ if (elements[i].type() == tlv_parameter_key) {
+ if (readString(elements[i]) == PARAMETER_KEY_CREDENTIAL_CERT) {
+ try {
+ credential.wireDecode(elements[i + 1].blockFromValue());
+ }
+ catch (const std::exception& e) {
+ _LOG_ERROR("Cannot load challenge parameter: credential " << e.what());
+ return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Cannot challenge credential: credential." + std::string(e.what()));
+ }
+ }
+ else if (readString(elements[i]) == PARAMETER_KEY_PROOF_OF_PRIVATE_KEY) {
+ signature = elements[i + 1].value();
+ signatureLen = elements[i + 1].value_size();
+ }
+ }
+ }
+
+ // verify the credential and the self-signed cert
+ Name signingKeyName = credential.getSignature().getKeyLocator().getName();
+ security::transform::PublicKey key;
+ const auto& pubKeyBuffer = credential.getPublicKey();
+ key.loadPkcs8(pubKeyBuffer.data(), pubKeyBuffer.size());
+ for (auto anchor : m_trustAnchors) {
+ if (anchor.getKeyName() == signingKeyName) {
+ if (security::verifySignature(credential, anchor) &&
+ security::verifySignature((uint8_t*)request.m_requestId.c_str(), request.m_requestId.size(), signature, signatureLen, key)) {
+ return returnWithSuccess(request);
+ }
+ }
+ }
+
+ _LOG_TRACE("Cannot verify the proof of private key against credential");
+ return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Cannot verify the proof of private key against credential.");
+}
+
+// For Client
+std::vector<std::tuple<std::string, std::string>>
+ChallengeCredential::getRequestedParameterList(Status status, const std::string& challengeStatus)
+{
+ std::vector<std::tuple<std::string, std::string>> result;
+ if (status == Status::BEFORE_CHALLENGE) {
+ result.push_back(std::make_tuple(PARAMETER_KEY_CREDENTIAL_CERT, "Please provide the certificate issued by a trusted CA."));
+ result.push_back(std::make_tuple(PARAMETER_KEY_PROOF_OF_PRIVATE_KEY, "Please sign a Data packet with request ID as the content."));
+ return result;
+ }
+ BOOST_THROW_EXCEPTION(std::runtime_error("Unexpected status or challenge status."));
+}
+
+Block
+ChallengeCredential::genChallengeRequestTLV(Status status, const std::string& challengeStatus,
+ std::vector<std::tuple<std::string, std::string>>&& params)
+{
+ Block request = makeEmptyBlock(tlv_encrypted_payload);
+ if (status == Status::BEFORE_CHALLENGE) {
+ if (params.size() != 2) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Wrong parameter provided."));
+ }
+ request.push_back(makeStringBlock(tlv_selected_challenge, CHALLENGE_TYPE));
+ for (const auto& item : params) {
+ if (std::get<0>(item) == PARAMETER_KEY_CREDENTIAL_CERT) {
+ request.push_back(makeStringBlock(tlv_parameter_key, PARAMETER_KEY_CREDENTIAL_CERT));
+ Block valueBlock = makeEmptyBlock(tlv_parameter_value);
+ auto& certTlvStr = std::get<1>(item);
+ valueBlock.push_back(Block((uint8_t*)certTlvStr.c_str(), certTlvStr.size()));
+ request.push_back(valueBlock);
+ }
+ else if (std::get<0>(item) == PARAMETER_KEY_PROOF_OF_PRIVATE_KEY) {
+ request.push_back(makeStringBlock(tlv_parameter_key, PARAMETER_KEY_PROOF_OF_PRIVATE_KEY));
+ auto& sigTlvStr = std::get<1>(item);
+ Block valueBlock = makeBinaryBlock(tlv_parameter_value, (uint8_t*)sigTlvStr.c_str(), sigTlvStr.size());
+ request.push_back(valueBlock);
+ }
+ else {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Wrong parameter provided."));
+ }
+ }
+ }
+ else {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Unexpected status or challenge status."));
+ }
+ request.encode();
+ return request;
+}
+
+void
+ChallengeCredential::fulfillParameters(std::vector<std::tuple<std::string, std::string>>& params,
+ KeyChain& keyChain, const Name& issuedCertName, const std::string& requestId)
+{
+ auto& pib = keyChain.getPib();
+ auto id = pib.getIdentity(security::v2::extractIdentityFromCertName(issuedCertName));
+ auto issuedCert = id.getKey(security::v2::extractKeyNameFromCertName(issuedCertName)).getCertificate(issuedCertName);
+ auto issuedCertTlv = issuedCert.wireEncode();
+ auto signatureTlv = keyChain.sign((uint8_t*)requestId.c_str(), requestId.length(), security::signingByCertificate(issuedCertName));
+ for (auto& item : params) {
+ if (std::get<0>(item) == PARAMETER_KEY_CREDENTIAL_CERT) {
+ std::get<1>(item) = std::string((char*)issuedCertTlv.wire(), issuedCertTlv.size());
+ }
+ else if (std::get<0>(item) == PARAMETER_KEY_PROOF_OF_PRIVATE_KEY) {
+ std::get<1>(item) = std::string((char*)signatureTlv.value(), signatureTlv.value_size());
+ }
+ }
+ return;
+}
+
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/identity-challenge/challenge-credential.hpp b/src/identity-challenge/challenge-credential.hpp
new file mode 100644
index 0000000..0ecfdb2
--- /dev/null
+++ b/src/identity-challenge/challenge-credential.hpp
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#ifndef NDNCERT_CHALLENGE_CREDENTIAL_HPP
+#define NDNCERT_CHALLENGE_CREDENTIAL_HPP
+
+#include "challenge-module.hpp"
+
+namespace ndn {
+namespace ndncert {
+
+/**
+ * @brief Provide Credential based challenge
+ *
+ * Credential here means the certificate issued by a trust anchor. Once the requester
+ * could proof his/her possession of an existing certificate from other certificate issuer,
+ * the requester could finish the challenge.
+ *
+ * The requester needs to provide the proof of the possession of a certificate issued by
+ * a trust anchor. The challenge require the requester to pass the BASE64 certificate and
+ * a BASE64 Data packet signed by the credential pub key and whose content is the request id.
+ *
+ * The main process of this challenge module is:
+ * 1. Requester provides a certificate signed by that trusted certificate as credential.
+ * 2. The challenge module will verify the signature of the credential.
+ * 3. The content of the signed Data is the request id
+ *
+ * Failure info when application fails:
+ * FAILURE_INVALID_CREDENTIAL: When the cert issued from trust anchor or self-signed cert
+ * cannot be validated.
+ * FAILURE_INVALID_FORMAT: When the credential format is wrong.
+ */
+class ChallengeCredential : public ChallengeModule
+{
+public:
+ ChallengeCredential(const std::string& configPath = "");
+
+ // For CA
+ std::tuple<ErrorCode, std::string>
+ handleChallengeRequest(const Block& params, CaState& request) override;
+
+ // For Client
+ std::vector<std::tuple<std::string, std::string>>
+ getRequestedParameterList(Status status, const std::string& challengeStatus) override;
+
+ Block
+ genChallengeRequestTLV(Status status, const std::string& challengeStatus,
+ std::vector<std::tuple<std::string, std::string>>&& params) override;
+
+ static void
+ fulfillParameters(std::vector<std::tuple<std::string, std::string>>& params,
+ KeyChain& keyChain, const Name& issuedCertName, const std::string& requestId);
+
+ // challenge parameters
+ static const std::string PARAMETER_KEY_CREDENTIAL_CERT;
+ static const std::string PARAMETER_KEY_PROOF_OF_PRIVATE_KEY;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ void
+ parseConfigFile();
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ std::list<security::v2::Certificate> m_trustAnchors;
+ std::string m_configFile;
+};
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CHALLENGE_CREDENTIAL_HPP
diff --git a/src/identity-challenge/challenge-email.cpp b/src/identity-challenge/challenge-email.cpp
new file mode 100644
index 0000000..2775d9e
--- /dev/null
+++ b/src/identity-challenge/challenge-email.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "challenge-email.hpp"
+#include <regex>
+
+namespace ndn {
+namespace ndncert {
+
+_LOG_INIT(ndncert.challenge.email);
+NDNCERT_REGISTER_CHALLENGE(ChallengeEmail, "email");
+
+const std::string ChallengeEmail::NEED_CODE = "need-code";
+const std::string ChallengeEmail::WRONG_CODE = "wrong-code";
+const std::string ChallengeEmail::INVALID_EMAIL = "invalid-email";
+const std::string ChallengeEmail::PARAMETER_KEY_EMAIL = "email";
+const std::string ChallengeEmail::PARAMETER_KEY_CODE = "code";
+
+ChallengeEmail::ChallengeEmail(const std::string& scriptPath,
+ const size_t& maxAttemptTimes,
+ const time::seconds secretLifetime)
+ : ChallengeModule("email", maxAttemptTimes, secretLifetime)
+ , m_sendEmailScript(scriptPath)
+{
+}
+
+// For CA
+std::tuple<ErrorCode, std::string>
+ChallengeEmail::handleChallengeRequest(const Block& params, CaState& request)
+{
+ params.parse();
+ auto currentTime = time::system_clock::now();
+ if (request.m_status == Status::BEFORE_CHALLENGE) {
+ // for the first time, init the challenge
+ std::string emailAddress = readString(params.get(tlv_parameter_value));
+ if (!isValidEmailAddress(emailAddress)) {
+ return returnWithNewChallengeStatus(request, INVALID_EMAIL, JsonSection(), m_maxAttemptTimes - 1, m_secretLifetime);
+ }
+ auto lastComponentRequested = readString(request.m_cert.getIdentity().get(-1));
+ if (lastComponentRequested != emailAddress) {
+ _LOG_TRACE("Email and requested name do not match. Email " << emailAddress << "requested last component " << lastComponentRequested);
+ }
+ std::string emailCode = generateSecretCode();
+ JsonSection secretJson;
+ secretJson.add(PARAMETER_KEY_CODE, emailCode);
+ // send out the email
+ sendEmail(emailAddress, emailCode, request);
+ _LOG_TRACE("Secret for request " << request.m_requestId << " : " << emailCode);
+ return returnWithNewChallengeStatus(request, NEED_CODE, std::move(secretJson), m_maxAttemptTimes, m_secretLifetime);
+ }
+ if (request.m_challengeState) {
+ if (request.m_challengeState->m_challengeStatus == NEED_CODE ||
+ request.m_challengeState->m_challengeStatus == WRONG_CODE) {
+ _LOG_TRACE("Challenge Interest arrives. Challenge Status: " << request.m_challengeState->m_challengeStatus);
+ // the incoming interest should bring the pin code
+ std::string givenCode = readString(params.get(tlv_parameter_value));
+ auto secret = request.m_challengeState->m_secrets;
+ // check if run out of time
+ if (currentTime - request.m_challengeState->m_timestamp >= m_secretLifetime) {
+ return returnWithError(request, ErrorCode::OUT_OF_TIME, "Secret expired.");
+ }
+ // check if provided secret is correct
+ if (givenCode == secret.get<std::string>(PARAMETER_KEY_CODE)) {
+ // the code is correct
+ _LOG_TRACE("Correct secret code. Challenge succeeded.");
+ return returnWithSuccess(request);
+ }
+ // otherwise, check remaining attempt times
+ if (request.m_challengeState->m_remainingTries > 1) {
+ auto remainTime = m_secretLifetime - (currentTime - request.m_challengeState->m_timestamp);
+ _LOG_TRACE("Wrong secret code provided. Remaining Tries - 1.");
+ return returnWithNewChallengeStatus(request, WRONG_CODE, std::move(secret),
+ request.m_challengeState->m_remainingTries - 1,
+ time::duration_cast<time::seconds>(remainTime));
+ }
+ else {
+ // run out times
+ _LOG_TRACE("Wrong secret code provided. Ran out tires. Challenge failed.");
+ return returnWithError(request, ErrorCode::OUT_OF_TRIES, "Ran out tires.");
+ }
+ }
+ }
+ return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Unexpected status or challenge status");
+}
+
+// For Client
+std::vector<std::tuple<std::string, std::string>>
+ChallengeEmail::getRequestedParameterList(Status status, const std::string& challengeStatus)
+{
+ std::vector<std::tuple<std::string, std::string>> result;
+ if (status == Status::BEFORE_CHALLENGE && challengeStatus == "") {
+ result.push_back(std::make_tuple(PARAMETER_KEY_EMAIL, "Please input your email address"));
+ }
+ else if (status == Status::CHALLENGE && challengeStatus == NEED_CODE) {
+ result.push_back(std::make_tuple(PARAMETER_KEY_CODE, "Please input your verification code"));
+ }
+ else if (status == Status::CHALLENGE && challengeStatus == WRONG_CODE) {
+ result.push_back(std::make_tuple(PARAMETER_KEY_CODE, "Incorrect code, please try again"));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Unexpected status or challenge status."));
+ }
+ return result;
+}
+
+Block
+ChallengeEmail::genChallengeRequestTLV(Status status, const std::string& challengeStatus, std::vector<std::tuple<std::string, std::string>>&& params)
+{
+ Block request = makeEmptyBlock(tlv_encrypted_payload);
+ if (status == Status::BEFORE_CHALLENGE) {
+ if (params.size() != 1 || std::get<0>(params[0]) != PARAMETER_KEY_EMAIL) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Wrong parameter provided."));
+ }
+ request.push_back(makeStringBlock(tlv_selected_challenge, CHALLENGE_TYPE));
+ request.push_back(makeStringBlock(tlv_parameter_key, PARAMETER_KEY_EMAIL));
+ request.push_back(makeStringBlock(tlv_parameter_value, std::get<1>(params[0])));
+ }
+ else if (status == Status::CHALLENGE && (challengeStatus == NEED_CODE || challengeStatus == WRONG_CODE)) {
+ if (params.size() != 1 || std::get<0>(params[0]) != PARAMETER_KEY_CODE) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Wrong parameter provided."));
+ }
+ request.push_back(makeStringBlock(tlv_selected_challenge, CHALLENGE_TYPE));
+ request.push_back(makeStringBlock(tlv_parameter_key, PARAMETER_KEY_CODE));
+ request.push_back(makeStringBlock(tlv_parameter_value, std::get<1>(params[0])));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Unexpected status or challenge status."));
+ }
+ request.encode();
+ return request;
+}
+
+bool
+ChallengeEmail::isValidEmailAddress(const std::string& emailAddress)
+{
+ const std::string pattern = R"_REGEX_((^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9\-\.]+$))_REGEX_";
+ static const std::regex emailPattern(pattern);
+ return std::regex_match(emailAddress, emailPattern);
+}
+
+void
+ChallengeEmail::sendEmail(const std::string& emailAddress, const std::string& secret,
+ const CaState& request) const
+{
+ std::string command = m_sendEmailScript;
+ command += " \"" + emailAddress + "\" \"" + secret + "\" \"" + request.m_caPrefix.toUri() + "\" \"" + request.m_cert.getName().toUri() + "\"";
+ int result = system(command.c_str());
+ if (result == -1) {
+ _LOG_TRACE("EmailSending Script " + m_sendEmailScript + " fails.");
+ }
+ _LOG_TRACE("EmailSending Script " + m_sendEmailScript +
+ " was executed successfully with return value" + std::to_string(result) + ".");
+ return;
+}
+
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/identity-challenge/challenge-email.hpp b/src/identity-challenge/challenge-email.hpp
new file mode 100644
index 0000000..5cab3cb
--- /dev/null
+++ b/src/identity-challenge/challenge-email.hpp
@@ -0,0 +1,93 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#ifndef NDNCERT_CHALLENGE_EMAIL_HPP
+#define NDNCERT_CHALLENGE_EMAIL_HPP
+
+#include "challenge-module.hpp"
+
+namespace ndn {
+namespace ndncert {
+
+/**
+ * @brief Provide Email based challenge
+ *
+ * For challenge design
+ * @sa https://github.com/named-data/ndncert/wiki/NDN-Certificate-Management-Protocol
+ * For deployment instructions:
+ * @sa https://github.com/named-data/ndncert/wiki/Deploy-Email-Challenge
+ *
+ * The main process of this challenge module is:
+ * 1. Requester provides its email address.
+ * 2. The challenge module will send a verification code to this email address.
+ * 3. Requester provides the verification code to challenge module.
+ *
+ * There are several challenge status in EMAIL challenge:
+ * NEED_CODE: When email address is provided and the verification code has been sent out.
+ * WRONG_CODE: Wrong code but still within secret lifetime and within max try times.
+ *
+ * Failure info when application fails:
+ * FAILURE_MAXRETRY: When run out retry times.
+ * FAILURE_INVALID_EMAIL: When the email is invalid.
+ * FAILURE_TIMEOUT: When the secret lifetime expires.
+ */
+class ChallengeEmail : public ChallengeModule
+{
+public:
+ ChallengeEmail(const std::string& scriptPath = "ndncert-send-email-challenge",
+ const size_t& maxAttemptTimes = 3,
+ const time::seconds secretLifetime = time::seconds(300));
+
+ // For CA
+ std::tuple<ErrorCode, std::string>
+ handleChallengeRequest(const Block& params, CaState& request) override;
+
+ // For Client
+ std::vector<std::tuple<std::string, std::string>>
+ getRequestedParameterList(Status status, const std::string& challengeStatus) override;
+
+ Block
+ genChallengeRequestTLV(Status status, const std::string& challengeStatus,
+ std::vector<std::tuple<std::string, std::string>>&& params) override;
+
+ // challenge status
+ static const std::string NEED_CODE;
+ static const std::string WRONG_CODE;
+ static const std::string INVALID_EMAIL;
+ // challenge parameters
+ static const std::string PARAMETER_KEY_EMAIL;
+ static const std::string PARAMETER_KEY_CODE;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ static bool
+ isValidEmailAddress(const std::string& emailAddress);
+
+ void
+ sendEmail(const std::string& emailAddress, const std::string& secret,
+ const CaState& request) const;
+
+private:
+ std::string m_sendEmailScript;
+};
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CHALLENGE_EMAIL_HPP
diff --git a/src/identity-challenge/challenge-module.cpp b/src/identity-challenge/challenge-module.cpp
new file mode 100644
index 0000000..812a85d
--- /dev/null
+++ b/src/identity-challenge/challenge-module.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "identity-challenge/challenge-module.hpp"
+#include <ndn-cxx/util/random.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+ChallengeModule::ChallengeModule(const std::string& challengeType,
+ size_t maxAttemptTimes,
+ time::seconds secretLifetime)
+ : CHALLENGE_TYPE(challengeType)
+ , m_maxAttemptTimes(maxAttemptTimes)
+ , m_secretLifetime(secretLifetime)
+{
+}
+
+bool
+ChallengeModule::isChallengeSupported(const std::string& challengeType)
+{
+ ChallengeFactory& factory = getFactory();
+ auto i = factory.find(challengeType);
+ return i == factory.end() ? false : true;
+}
+
+unique_ptr<ChallengeModule>
+ChallengeModule::createChallengeModule(const std::string& challengeType)
+{
+ ChallengeFactory& factory = getFactory();
+ auto i = factory.find(challengeType);
+ return i == factory.end() ? nullptr : i->second();
+}
+
+ChallengeModule::ChallengeFactory&
+ChallengeModule::getFactory()
+{
+ static ChallengeModule::ChallengeFactory factory;
+ return factory;
+}
+
+std::string
+ChallengeModule::generateSecretCode()
+{
+ uint32_t securityCode = 0;
+ do {
+ securityCode = random::generateSecureWord32();
+ }
+ while (securityCode >= 4294000000);
+ securityCode /= 4294;
+ std::string result = std::to_string(securityCode);
+ while (result.length() < 6) {
+ result = "0" + result;
+ }
+ return result;
+}
+
+std::tuple<ErrorCode, std::string>
+ChallengeModule::returnWithError(CaState& request, ErrorCode errorCode, std::string&& errorInfo)
+{
+ request.m_status = Status::FAILURE;
+ request.m_challengeType = "";
+ request.m_challengeState = boost::none;
+ return std::make_tuple(errorCode, std::move(errorInfo));
+}
+
+std::tuple<ErrorCode, std::string>
+ChallengeModule::returnWithNewChallengeStatus(CaState& request, const std::string& challengeStatus,
+ JsonSection&& challengeSecret, size_t remainingTries, time::seconds remainingTime)
+{
+ request.m_status = Status::CHALLENGE;
+ request.m_challengeType = CHALLENGE_TYPE;
+ request.m_challengeState = ChallengeState(challengeStatus, time::system_clock::now(), remainingTries, remainingTime, std::move(challengeSecret));
+ return std::make_tuple(ErrorCode::NO_ERROR, "");
+}
+
+std::tuple<ErrorCode, std::string>
+ChallengeModule::returnWithSuccess(CaState& request)
+{
+ request.m_status = Status::PENDING;
+ request.m_challengeType = CHALLENGE_TYPE;
+ request.m_challengeState = boost::none;
+ return std::make_tuple(ErrorCode::NO_ERROR, "");
+}
+
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/identity-challenge/challenge-module.hpp b/src/identity-challenge/challenge-module.hpp
new file mode 100644
index 0000000..4349762
--- /dev/null
+++ b/src/identity-challenge/challenge-module.hpp
@@ -0,0 +1,104 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#ifndef NDNCERT_CHALLENGE_MODULE_HPP
+#define NDNCERT_CHALLENGE_MODULE_HPP
+
+#include "ca-state.hpp"
+
+namespace ndn {
+namespace ndncert {
+
+class ChallengeModule : noncopyable {
+public:
+ explicit
+ ChallengeModule(const std::string& challengeType, size_t maxAttemptTimes, time::seconds secretLifetime);
+
+ virtual ~ChallengeModule() = default;
+
+ template <class ChallengeType>
+ static void
+ registerChallengeModule(const std::string& typeName)
+ {
+ ChallengeFactory& factory = getFactory();
+ BOOST_ASSERT(factory.count(typeName) == 0);
+ factory[typeName] = [] { return make_unique<ChallengeType>(); };
+ }
+
+ static bool
+ isChallengeSupported(const std::string& challengeType);
+
+ static unique_ptr<ChallengeModule>
+ createChallengeModule(const std::string& challengeType);
+
+ // For CA
+ virtual std::tuple<ErrorCode, std::string>
+ handleChallengeRequest(const Block& params, CaState& request) = 0;
+
+ // For Client
+ virtual std::vector<std::tuple<std::string, std::string>>
+ getRequestedParameterList(Status status, const std::string& challengeStatus) = 0;
+
+ virtual Block
+ genChallengeRequestTLV(Status status, const std::string& challengeStatus,
+ std::vector<std::tuple<std::string, std::string>>&& params) = 0;
+
+ // helpers
+ static std::string
+ generateSecretCode();
+
+protected:
+ // used by challenge modules
+ std::tuple<ErrorCode, std::string>
+ returnWithError(CaState& request, ErrorCode errorCode, std::string&& errorInfo);
+
+ std::tuple<ErrorCode, std::string>
+ returnWithNewChallengeStatus(CaState& request, const std::string& challengeStatus,
+ JsonSection&& challengeSecret, size_t remainingTries, time::seconds remainingTime);
+
+ std::tuple<ErrorCode, std::string>
+ returnWithSuccess(CaState& request);
+
+public:
+ const std::string CHALLENGE_TYPE;
+ const size_t m_maxAttemptTimes;
+ const time::seconds m_secretLifetime;
+
+private:
+ typedef function<unique_ptr<ChallengeModule>()> ChallengeCreateFunc;
+ typedef std::map<std::string, ChallengeCreateFunc> ChallengeFactory;
+
+ static ChallengeFactory&
+ getFactory();
+};
+
+#define NDNCERT_REGISTER_CHALLENGE(C, T) \
+ static class NdnCert##C##ChallengeRegistrationClass { \
+ public: \
+ NdnCert##C##ChallengeRegistrationClass() \
+ { \
+ ::ndn::ndncert::ChallengeModule::registerChallengeModule<C>(T); \
+ } \
+ } g_NdnCert##C##ChallengeRegistrationVariable
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CHALLENGE_MODULE_HPP
diff --git a/src/identity-challenge/challenge-pin.cpp b/src/identity-challenge/challenge-pin.cpp
new file mode 100644
index 0000000..a5d5580
--- /dev/null
+++ b/src/identity-challenge/challenge-pin.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "challenge-pin.hpp"
+#include <ndn-cxx/util/random.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+_LOG_INIT(ndncert.challenge.pin);
+NDNCERT_REGISTER_CHALLENGE(ChallengePin, "pin");
+
+const std::string ChallengePin::NEED_CODE = "need-code";
+const std::string ChallengePin::WRONG_CODE = "wrong-code";
+const std::string ChallengePin::PARAMETER_KEY_CODE = "code";
+
+ChallengePin::ChallengePin(const size_t& maxAttemptTimes, const time::seconds& secretLifetime)
+ : ChallengeModule("pin", maxAttemptTimes, secretLifetime)
+{
+}
+
+// For CA
+std::tuple<ErrorCode, std::string>
+ChallengePin::handleChallengeRequest(const Block& params, CaState& request)
+{
+ params.parse();
+ auto currentTime = time::system_clock::now();
+ if (request.m_status == Status::BEFORE_CHALLENGE) {
+ _LOG_TRACE("Challenge Interest arrives. Init the challenge");
+ // for the first time, init the challenge
+ std::string secretCode = generateSecretCode();
+ JsonSection secretJson;
+ secretJson.add(PARAMETER_KEY_CODE, secretCode);
+ _LOG_TRACE("Secret for request " << request.m_requestId << " : " << secretCode);
+ return returnWithNewChallengeStatus(request, NEED_CODE, std::move(secretJson), m_maxAttemptTimes, m_secretLifetime);
+ }
+ if (request.m_challengeState) {
+ if (request.m_challengeState->m_challengeStatus == NEED_CODE ||
+ request.m_challengeState->m_challengeStatus == WRONG_CODE) {
+ _LOG_TRACE("Challenge Interest arrives. Challenge Status: " << request.m_challengeState->m_challengeStatus);
+ // the incoming interest should bring the pin code
+ std::string givenCode = readString(params.get(tlv_parameter_value));
+ auto secret = request.m_challengeState->m_secrets;
+ if (currentTime - request.m_challengeState->m_timestamp >= m_secretLifetime) {
+ return returnWithError(request, ErrorCode::OUT_OF_TIME, "Secret expired.");
+ }
+ if (givenCode == secret.get<std::string>(PARAMETER_KEY_CODE)) {
+ _LOG_TRACE("Correct PIN code. Challenge succeeded.");
+ return returnWithSuccess(request);
+ }
+ // check rest attempt times
+ if (request.m_challengeState->m_remainingTries > 1) {
+ auto remainTime = m_secretLifetime - (currentTime - request.m_challengeState->m_timestamp);
+ _LOG_TRACE("Wrong PIN code provided. Remaining Tries - 1.");
+ return returnWithNewChallengeStatus(request, WRONG_CODE, std::move(secret),
+ request.m_challengeState->m_remainingTries - 1,
+ time::duration_cast<time::seconds>(remainTime));
+ }
+ else {
+ // run out times
+ _LOG_TRACE("Wrong PIN code provided. Ran out tires. Challenge failed.");
+ return returnWithError(request, ErrorCode::OUT_OF_TRIES, "Ran out tires.");
+ }
+ }
+ }
+ return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Unexpected status or challenge status");
+}
+
+// For Client
+std::vector<std::tuple<std::string, std::string>>
+ChallengePin::getRequestedParameterList(Status status, const std::string& challengeStatus)
+{
+ std::vector<std::tuple<std::string, std::string>> result;
+ if (status == Status::BEFORE_CHALLENGE) {
+ // do nothing
+ }
+ else if (status == Status::CHALLENGE && challengeStatus == NEED_CODE) {
+ result.push_back(std::make_tuple(PARAMETER_KEY_CODE, "Please input your PIN code"));
+ }
+ else if (status == Status::CHALLENGE && challengeStatus == WRONG_CODE) {
+ result.push_back(std::make_tuple(PARAMETER_KEY_CODE, "Incorrect PIN code, please try again"));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Unexpected status or challenge status."));
+ }
+ return result;
+}
+
+Block
+ChallengePin::genChallengeRequestTLV(Status status, const std::string& challengeStatus, std::vector<std::tuple<std::string, std::string>>&& params)
+{
+ Block request = makeEmptyBlock(tlv_encrypted_payload);
+ if (status == Status::BEFORE_CHALLENGE) {
+ request.push_back(makeStringBlock(tlv_selected_challenge, CHALLENGE_TYPE));
+ }
+ else if (status == Status::CHALLENGE && (challengeStatus == NEED_CODE || challengeStatus == WRONG_CODE)) {
+ if (params.size() != 1 || std::get<0>(params[0]) != PARAMETER_KEY_CODE) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Wrong parameter provided."));
+ }
+ request.push_back(makeStringBlock(tlv_selected_challenge, CHALLENGE_TYPE));
+ request.push_back(makeStringBlock(tlv_parameter_key, PARAMETER_KEY_CODE));
+ request.push_back(makeStringBlock(tlv_parameter_value, std::get<1>(params[0])));
+ }
+ else {
+ BOOST_THROW_EXCEPTION(std::runtime_error("Unexpected status or challenge status."));
+ }
+ request.encode();
+ return request;
+}
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/identity-challenge/challenge-pin.hpp b/src/identity-challenge/challenge-pin.hpp
new file mode 100644
index 0000000..53f7a10
--- /dev/null
+++ b/src/identity-challenge/challenge-pin.hpp
@@ -0,0 +1,75 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert 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 General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#ifndef NDNCERT_CHALLENGE_PIN_HPP
+#define NDNCERT_CHALLENGE_PIN_HPP
+
+#include "challenge-module.hpp"
+
+namespace ndn {
+namespace ndncert {
+
+/**
+ * @brief Provide PIN code based challenge
+ *
+ * @sa https://github.com/named-data/ndncert/wiki/NDN-Certificate-Management-Protocol
+ *
+ * The main process of this challenge module is:
+ * 1. End entity provides empty string. The first POLL is only for selection.
+ * 2. The challenge module will generate a PIN code in ChallengeDefinedField.
+ * 3. End entity provides the verification code from some way to challenge module.
+ *
+ * There are four specific status defined in this challenge:
+ * NEED_CODE: When selection is made.
+ * WRONG_CODE: Get wrong verification code but still with secret lifetime and max retry times.
+ *
+ * Failure info when application fails:
+ * FAILURE_TIMEOUT: When secret is out-dated.
+ * FAILURE_MAXRETRY: When requester tries too many times.
+ */
+class ChallengePin : public ChallengeModule
+{
+public:
+ ChallengePin(const size_t& maxAttemptTimes = 3,
+ const time::seconds& secretLifetime = time::seconds(3600));
+
+ // For CA
+ std::tuple<ErrorCode, std::string>
+ handleChallengeRequest(const Block& params, CaState& request) override;
+
+ // For Client
+ std::vector<std::tuple<std::string, std::string>>
+ getRequestedParameterList(Status status, const std::string& challengeStatus) override;
+
+ Block
+ genChallengeRequestTLV(Status status, const std::string& challengeStatus,
+ std::vector<std::tuple<std::string, std::string>>&& params) override;
+
+ // challenge status
+ static const std::string NEED_CODE;
+ static const std::string WRONG_CODE;
+ // parameters
+ static const std::string PARAMETER_KEY_CODE;
+};
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CHALLENGE_PIN_HPP