refactor client module to requester
Change-Id: I2b9835af7f03942bfdb6a886c95cfb4b907e2068
diff --git a/src/challenge-modules/challenge-email.cpp b/src/challenge-modules/challenge-email.cpp
index 327fb79..c12ba88 100644
--- a/src/challenge-modules/challenge-email.cpp
+++ b/src/challenge-modules/challenge-email.cpp
@@ -19,7 +19,6 @@
*/
#include "challenge-email.hpp"
-#include "../ca-module.hpp"
#include <regex>
namespace ndn {
diff --git a/src/challenge-modules/challenge-pin.cpp b/src/challenge-modules/challenge-pin.cpp
index 4853eed..fd5b5a5 100644
--- a/src/challenge-modules/challenge-pin.cpp
+++ b/src/challenge-modules/challenge-pin.cpp
@@ -19,7 +19,6 @@
*/
#include "challenge-pin.hpp"
-
#include <ndn-cxx/util/random.hpp>
namespace ndn {
diff --git a/src/client-module.cpp b/src/client-module.cpp
deleted file mode 100644
index ffda468..0000000
--- a/src/client-module.cpp
+++ /dev/null
@@ -1,406 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * 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 "client-module.hpp"
-
-#include <ndn-cxx/security/signing-helpers.hpp>
-#include <ndn-cxx/security/transform/base64-encode.hpp>
-#include <ndn-cxx/security/transform/buffer-source.hpp>
-#include <ndn-cxx/security/transform/stream-sink.hpp>
-#include <ndn-cxx/security/verification-helpers.hpp>
-#include <ndn-cxx/util/io.hpp>
-#include <ndn-cxx/util/random.hpp>
-
-#include "challenge-module.hpp"
-#include "crypto-support/enc-tlv.hpp"
-#include "protocol-detail/challenge.hpp"
-#include "protocol-detail/info.hpp"
-#include "protocol-detail/new-renew-revoke.hpp"
-#include "protocol-detail/probe.hpp"
-#include "protocol-detail/error.hpp"
-#include "ndncert-common.hpp"
-
-namespace ndn {
-namespace ndncert {
-
-_LOG_INIT(ndncert.client);
-
-ClientModule::ClientModule(security::v2::KeyChain& keyChain)
- : m_keyChain(keyChain)
-{
-}
-
-ClientModule::~ClientModule()
-{
- endSession();
-}
-
-shared_ptr<Interest>
-ClientModule::generateInfoInterest(const Name& caName)
-{
- Name interestName = caName;
- if (readString(caName.at(-1)) != "CA")
- interestName.append("CA");
- interestName.append("INFO");
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
- return interest;
-}
-
-bool
-ClientModule::verifyInfoResponse(const Data& reply)
-{
- // parse the ca item
- auto caItem = INFO::decodeDataContent(reply.getContent());
-
- // verify the probe Data's sig
- if (!security::verifySignature(reply, *caItem.m_cert)) {
- _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
- return false;
- }
- return true;
-}
-
-void
-ClientModule::addCaFromInfoResponse(const Data& reply)
-{
- const Block& contentBlock = reply.getContent();
-
- // parse the ca item
- auto caItem = INFO::decodeDataContent(contentBlock);
-
- // update the local config
- bool findItem = false;
- for (auto& item : m_config.m_caItems) {
- if (item.m_caPrefix == caItem.m_caPrefix) {
- findItem = true;
- item = caItem;
- }
- }
- if (!findItem) {
- m_config.m_caItems.push_back(caItem);
- }
-}
-
-shared_ptr<Interest>
-ClientModule::generateProbeInterest(const CaConfigItem& ca,
- std::vector<std::tuple<std::string, std::string>>&& probeInfo)
-{
- Name interestName = ca.m_caPrefix;
- interestName.append("CA").append("PROBE");
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
- interest->setApplicationParameters(PROBE::encodeApplicationParameters(std::move(probeInfo)));
-
- // update local state
- m_ca = ca;
- return interest;
-}
-
-void
-ClientModule::onProbeResponse(const Data& reply)
-{
- if (!security::verifySignature(reply, *m_ca.m_cert)) {
- _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
- return;
- }
-
- // error handling
- processIfError(reply);
-
- auto contentTLV = reply.getContent();
- contentTLV.parse();
-
- // read the available name and put it into the state
- if (contentTLV.get(tlv_probe_response).hasValue()) {
- Block probeResponseBlock = contentTLV.get(tlv_probe_response);
- probeResponseBlock.parse();
- m_identityName.wireDecode(probeResponseBlock.get(tlv::Name));
- }
- else {
- NDN_LOG_TRACE("The JSON_CA_NAME is empty.");
- }
-}
-
-shared_ptr<Interest>
-ClientModule::generateNewInterest(const time::system_clock::TimePoint& notBefore,
- const time::system_clock::TimePoint& notAfter,
- const Name& identityName)
-{
- // Name requestedName = identityName;
- if (!identityName.empty()) { // if identityName is not empty, find the corresponding CA
- bool findCa = false;
- for (const auto& caItem : m_config.m_caItems) {
- if (caItem.m_caPrefix.isPrefixOf(identityName)) {
- m_ca = caItem;
- findCa = true;
- }
- }
- if (!findCa) { // if cannot find, cannot proceed
- return nullptr;
- }
- m_identityName = identityName;
- }
- else { // if identityName is empty, check m_identityName or generate a random name
- if (!m_identityName.empty()) {
- // do nothing
- }
- else {
- NDN_LOG_TRACE("Randomly create a new name because m_identityName is empty and the param is empty.");
- auto id = std::to_string(random::generateSecureWord64());
- m_identityName = m_ca.m_caPrefix;
- m_identityName.append(id);
- }
- }
-
- // generate a newly key pair or use an existing key
- const auto& pib = m_keyChain.getPib();
- security::pib::Identity identity;
- try {
- identity = pib.getIdentity(m_identityName);
- }
- catch (const security::Pib::Error& e) {
- identity = m_keyChain.createIdentity(m_identityName);
- m_isNewlyCreatedIdentity = true;
- m_isNewlyCreatedKey = true;
- }
- try {
- m_key = identity.getDefaultKey();
- }
- catch (const security::Pib::Error& e) {
- m_key = m_keyChain.createKey(identity);
- m_isNewlyCreatedKey = true;
- }
-
- // generate certificate request
- security::v2::Certificate certRequest;
- certRequest.setName(Name(m_key.getName()).append("cert-request").appendVersion());
- certRequest.setContentType(tlv::ContentType_Key);
- certRequest.setContent(m_key.getPublicKey().data(), m_key.getPublicKey().size());
- SignatureInfo signatureInfo;
- signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
- m_keyChain.sign(certRequest, signingByKey(m_key.getName()).setSignatureInfo(signatureInfo));
-
- // generate Interest packet
- Name interestName = m_ca.m_caPrefix;
- interestName.append("CA").append("NEW");
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
- interest->setApplicationParameters(
- NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::NEW, m_ecdh.getBase64PubKey(), certRequest));
-
- // sign the Interest packet
- m_keyChain.sign(*interest, signingByKey(m_key.getName()));
- return interest;
-}
-
-std::list<std::string>
-ClientModule::onNewRenewRevokeResponse(const Data& reply)
-{
- if (!security::verifySignature(reply, *m_ca.m_cert)) {
- _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
- return std::list<std::string>();
- }
-
- // error handling
- processIfError(reply);
-
- auto contentTLV = reply.getContent();
- contentTLV.parse();
-
- // ECDH
- const auto& peerKeyBase64Str = readString(contentTLV.get(tlv_ecdh_pub));
- const auto& saltStr = readString(contentTLV.get(tlv_salt));
- uint64_t saltInt = std::stoull(saltStr);
- m_ecdh.deriveSecret(peerKeyBase64Str);
-
- // HKDF
- hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
- (uint8_t*)&saltInt, sizeof(saltInt), m_aesKey, sizeof(m_aesKey));
-
- // update state
- m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
- m_requestId = readString(contentTLV.get(tlv_request_id));
- m_challengeList.clear();
- for (auto const& element : contentTLV.elements()) {
- if (element.type() == tlv_challenge) {
- m_challengeList.push_back(readString(element));
- }
- }
- return m_challengeList;
-}
-
-shared_ptr<Interest>
-ClientModule::generateRevokeInterest(const security::v2::Certificate& certificate)
-{
- // Name requestedName = identityName;
- bool findCa = false;
- for (const auto& caItem : m_config.m_caItems) {
- if (caItem.m_caPrefix.isPrefixOf(certificate.getName())) {
- m_ca = caItem;
- findCa = true;
- }
- }
- if (!findCa) { // if cannot find, cannot proceed
- _LOG_TRACE("Cannot find corresponding CA for the certificate.");
- return nullptr;
- }
-
- // generate Interest packet
- Name interestName = m_ca.m_caPrefix;
- interestName.append("CA").append("REVOKE");
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
- interest->setApplicationParameters(
- NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::REVOKE, m_ecdh.getBase64PubKey(), certificate));
-
- // return the Interest packet
- return interest;
-}
-
-shared_ptr<Interest>
-ClientModule::generateChallengeInterest(const Block& challengeRequest)
-{
- challengeRequest.parse();
- m_challengeType = readString(challengeRequest.get(tlv_selected_challenge));
-
- Name interestName = m_ca.m_caPrefix;
- interestName.append("CA").append("CHALLENGE").append(m_requestId);
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
-
- // encrypt the Interest parameters
- auto paramBlock = encodeBlockWithAesGcm128(tlv::ApplicationParameters, m_aesKey,
- challengeRequest.value(), challengeRequest.value_size(),
- (const uint8_t*)"test", strlen("test"));
- interest->setApplicationParameters(paramBlock);
-
- m_keyChain.sign(*interest, signingByKey(m_key.getName()));
- return interest;
-}
-
-void
-ClientModule::onChallengeResponse(const Data& reply)
-{
- if (!security::verifySignature(reply, *m_ca.m_cert)) {
- _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
- return;
- }
-
- // error handling
- processIfError(reply);
-
- auto result = decodeBlockWithAesGcm128(reply.getContent(), m_aesKey, (const uint8_t*)"test", strlen("test"));
-
- Block contentTLV = makeBinaryBlock(tlv_encrypted_payload, result.data(), result.size());
- contentTLV.parse();
-
- // update state
- m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
- m_challengeStatus = readString(contentTLV.get(tlv_challenge_status));
- m_remainingTries = readNonNegativeInteger(contentTLV.get(tlv_remaining_tries));
- m_freshBefore = time::system_clock::now() +
- time::seconds(readNonNegativeInteger(contentTLV.get(tlv_remaining_time)));
-
- if (contentTLV.find(tlv_issued_cert_name) != contentTLV.elements_end()) {
- Block issuedCertNameBlock = contentTLV.get(tlv_issued_cert_name);
- issuedCertNameBlock.parse();
- m_issuedCertName.wireDecode(issuedCertNameBlock.get(tlv::Name));
- }
-}
-
-shared_ptr<Interest>
-ClientModule::generateDownloadInterest()
-{
- Name interestName = m_ca.m_caPrefix;
- interestName.append("CA").append("DOWNLOAD").append(m_requestId);
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
- return interest;
-}
-
-shared_ptr<Interest>
-ClientModule::generateCertFetchInterest()
-{
- Name interestName = m_issuedCertName;
- auto interest = make_shared<Interest>(interestName);
- interest->setMustBeFresh(true);
- interest->setCanBePrefix(false);
- return interest;
-}
-
-void
-ClientModule::onCertFetchResponse(const Data& reply)
-{
- try {
- security::v2::Certificate cert(reply.getContent().blockFromValue());
- m_keyChain.addCertificate(m_key, cert);
- _LOG_TRACE("Fetched and installed the cert " << cert.getName());
- }
- catch (const std::exception& e) {
- _LOG_ERROR("Cannot add replied certificate into the keychain " << e.what());
- return;
- }
-}
-
-void
-ClientModule::endSession()
-{
- if (getApplicationStatus() == Status::SUCCESS || getApplicationStatus() == Status::ENDED) {
- return;
- }
- if (m_isNewlyCreatedIdentity) {
- // put the identity into the if scope is because it may cause an error
- // outside since when endSession is called, identity may not have been created yet.
- auto identity = m_keyChain.getPib().getIdentity(m_identityName);
- m_keyChain.deleteIdentity(identity);
- }
- else if (m_isNewlyCreatedKey) {
- auto identity = m_keyChain.getPib().getIdentity(m_identityName);
- m_keyChain.deleteKey(identity, m_key);
- }
- m_status = Status::ENDED;
-}
-
-void
-ClientModule::processIfError(const Data& data)
-{
- auto contentTLV = data.getContent();
- if (ErrorTLV::isErrorContent(contentTLV)) {
- try {
- auto error = ErrorTLV::decodefromDataContent(contentTLV);
- _LOG_ERROR("Error data returned for " << data.getName() << ": " << std::endl <<
- "Code: " << errorCodeToString(std::get<0>(error)) << std::endl <<
- "Info: " << std::get<1>(error) << std::endl);
- } catch (const std::exception& e) {
- _LOG_ERROR("Cannot parse error data content for " << data.getName());
- return;
- }
- }
-}
-
-} // namespace ndncert
-} // namespace ndn
diff --git a/src/client-module.hpp b/src/client-module.hpp
deleted file mode 100644
index b9c0f35..0000000
--- a/src/client-module.hpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * 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.
- */
-
-#ifndef NDNCERT_CLIENT_MODULE_HPP
-#define NDNCERT_CLIENT_MODULE_HPP
-
-#include "configuration.hpp"
-#include "request-state.hpp"
-#include "crypto-support/crypto-helper.hpp"
-
-namespace ndn {
-namespace ndncert {
-
-// TODO
-// For each CA item in Client.Conf, create a validator instance and initialize it with CA's cert
-// The validator instance should be in CaConfigItem
-
-class ClientModule : noncopyable
-{
-public:
- /**
- * @brief Error that can be thrown from ClientModule
- */
- class Error : public std::runtime_error
- {
- public:
- using std::runtime_error::runtime_error;
- };
-
-public:
- ClientModule(security::v2::KeyChain& keyChain);
-
- ~ClientModule();
-
- ClientConfig&
- getClientConf()
- {
- return m_config;
- }
-
- Status
- getApplicationStatus() const
- {
- return m_status;
- }
-
- std::string
- getChallengeStatus() const
- {
- return m_challengeStatus;
- }
-
- shared_ptr<Interest>
- generateInfoInterest(const Name& caName);
-
- bool
- verifyInfoResponse(const Data& reply);
-
- /**
- * @brief Process the replied PROBE INFO Data packet
- * Warning: this function will add a new trust anchor into the application.
- * Please invoke this function only when reply can be fully trusted or the CA
- * can be verified in later challenge phase.
- */
- void
- addCaFromInfoResponse(const Data& reply);
-
- shared_ptr<Interest>
- generateProbeInterest(const CaConfigItem& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo);
-
- void
- onProbeResponse(const Data& reply);
-
- shared_ptr<Interest>
- generateNewInterest(const time::system_clock::TimePoint& notBefore,
- const time::system_clock::TimePoint& notAfter,
- const Name& identityName = Name());
-
- shared_ptr<Interest>
- generateRevokeInterest(const security::v2::Certificate& certificate);
-
- std::list<std::string>
- onNewRenewRevokeResponse(const Data& reply);
-
- shared_ptr<Interest>
- generateChallengeInterest(const Block& paramTLV);
-
- void
- onChallengeResponse(const Data& reply);
-
- shared_ptr<Interest>
- generateDownloadInterest();
-
- shared_ptr<Interest>
- generateCertFetchInterest();
-
- void
- onCertFetchResponse(const Data& reply);
-
- void
- endSession();
-
- static void
- processIfError(const Data& data);
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
- ClientConfig m_config;
- security::v2::KeyChain& m_keyChain;
-
- CaConfigItem m_ca;
- security::Key m_key;
- Name m_identityName;
-
- std::string m_requestId = "";
- Status m_status = Status::NOT_STARTED;
- std::string m_challengeStatus = "";
- std::string m_challengeType = "";
- Name m_issuedCertName;
- std::list<std::string> m_challengeList;
- bool m_isCertInstalled = false;
- bool m_isNewlyCreatedIdentity = false;
- bool m_isNewlyCreatedKey = false;
-
- int m_remainingTries = 0;
- time::system_clock::TimePoint m_freshBefore;
-
- ECDHState m_ecdh;
- uint8_t m_aesKey[16] = {0};
-};
-
-} // namespace ndncert
-} // namespace ndn
-
-#endif // NDNCERT_CLIENT_MODULE_HPP
diff --git a/src/configuration.cpp b/src/configuration.cpp
index 8d1d397..10fe352 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -27,7 +27,7 @@
namespace ndncert {
void
-CaConfigItem::parse(const JsonSection& configJson)
+CaProfile::parse(const JsonSection& configJson)
{
// CA prefix
m_caPrefix = Name(configJson.get(CONFIG_CA_PREFIX, ""));
@@ -79,7 +79,7 @@
}
JsonSection
-CaConfigItem::toJson() const
+CaProfile::toJson() const
{
JsonSection caItem;
caItem.put(CONFIG_CA_PREFIX, m_caPrefix.toUri());
@@ -150,7 +150,7 @@
}
void
-ClientConfig::load(const std::string& fileName)
+RequesterCaCache::load(const std::string& fileName)
{
JsonSection configJson;
try {
@@ -166,12 +166,12 @@
}
void
-ClientConfig::load(const JsonSection& configSection)
+RequesterCaCache::load(const JsonSection& configSection)
{
m_caItems.clear();
auto caList = configSection.get_child("ca-list");
for (auto item : caList) {
- CaConfigItem caItem;
+ CaProfile caItem;
caItem.parse(item.second);
if (caItem.m_cert == nullptr) {
BOOST_THROW_EXCEPTION(std::runtime_error("No CA certificate is loaded from JSON configuration."));
@@ -181,7 +181,7 @@
}
void
-ClientConfig::save(const std::string& fileName) const
+RequesterCaCache::save(const std::string& fileName) const
{
JsonSection configJson;
for (const auto& caItem : m_caItems) {
@@ -196,9 +196,21 @@
}
void
-ClientConfig::removeCaItem(const Name& caName)
+RequesterCaCache::removeCaProfile(const Name& caName)
{
- m_caItems.remove_if([&](const CaConfigItem& item) { return item.m_caPrefix == caName; });
+ m_caItems.remove_if([&](const CaProfile& item) { return item.m_caPrefix == caName; });
+}
+
+void
+RequesterCaCache::addCaProfile(const CaProfile& profile)
+{
+ for (auto& item : m_caItems) {
+ if (item.m_caPrefix == profile.m_caPrefix) {
+ item = profile;
+ return;
+ }
+ }
+ m_caItems.push_back(profile);
}
} // namespace ndncert
diff --git a/src/configuration.hpp b/src/configuration.hpp
index 892c056..6b9edeb 100644
--- a/src/configuration.hpp
+++ b/src/configuration.hpp
@@ -26,7 +26,7 @@
namespace ndn {
namespace ndncert {
-struct CaConfigItem {
+struct CaProfile {
/**
* CA Name prefix (without /CA suffix).
*/
@@ -131,7 +131,7 @@
void
load(const std::string& fileName);
- CaConfigItem m_caItem;
+ CaProfile m_caItem;
/**
* Used for CA redirection as specified in
* https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-0.3-PROBE-Extensions#probe-extension-for-redirection
@@ -153,7 +153,7 @@
* For Client configuration format, please refer to:
* https://github.com/named-data/ndncert/wiki/Client-Configuration-Sample
*/
-class ClientConfig {
+class RequesterCaCache {
public:
/**
* @throw std::runtime_error when config file cannot be correctly parsed.
@@ -171,9 +171,15 @@
save(const std::string& fileName) const;
void
- removeCaItem(const Name& caName);
+ removeCaProfile(const Name& caName);
- std::list<CaConfigItem> m_caItems;
+ /**
+ * Be cautious. This will add a new trust anchor for requesters.
+ */
+ void
+ addCaProfile(const CaProfile& profile);
+
+ std::list<CaProfile> m_caItems;
};
} // namespace ndncert
diff --git a/src/protocol-detail/challenge.cpp b/src/protocol-detail/challenge.cpp
index 61a3519..85875fd 100644
--- a/src/protocol-detail/challenge.cpp
+++ b/src/protocol-detail/challenge.cpp
@@ -19,8 +19,6 @@
*/
#include "challenge.hpp"
-#include "../ndncert-common.hpp"
-#include "../request-state.hpp"
namespace ndn {
namespace ndncert {
diff --git a/src/protocol-detail/error.cpp b/src/protocol-detail/error.cpp
index b74180b..dabfa6a 100644
--- a/src/protocol-detail/error.cpp
+++ b/src/protocol-detail/error.cpp
@@ -37,16 +37,11 @@
ErrorTLV::decodefromDataContent(const Block& block)
{
block.parse();
+ if (block.find(tlv_error_code) == block.elements_end()) {
+ return std::make_tuple(ErrorCode::NO_ERROR, "");
+ }
ErrorCode error = static_cast<ErrorCode>(readNonNegativeInteger(block.get(tlv_error_code)));
- auto description = readString(block.get(tlv_error_info));
- return std::make_tuple(error, description);
-}
-
-bool
-ErrorTLV::isErrorContent(const Block& block)
-{
- block.parse();
- return block.find(tlv_error_code) != block.elements_end();
+ return std::make_tuple(error, readString(block.get(tlv_error_info)));
}
} // namespace ndncert
diff --git a/src/protocol-detail/error.hpp b/src/protocol-detail/error.hpp
index d39fe1a..8a2cda0 100644
--- a/src/protocol-detail/error.hpp
+++ b/src/protocol-detail/error.hpp
@@ -39,9 +39,6 @@
*/
static std::tuple<ErrorCode, std::string>
decodefromDataContent(const Block& block);
-
- static bool
- isErrorContent(const Block& block);
};
} // namespace ndncert
diff --git a/src/protocol-detail/info.cpp b/src/protocol-detail/info.cpp
index 06eba0e..7c0e449 100644
--- a/src/protocol-detail/info.cpp
+++ b/src/protocol-detail/info.cpp
@@ -24,7 +24,7 @@
namespace ndncert {
Block
-INFO::encodeDataContent(const CaConfigItem& caConfig, const security::v2::Certificate& certificate)
+INFO::encodeDataContent(const CaProfile& caConfig, const security::v2::Certificate& certificate)
{
auto content = makeEmptyBlock(tlv::Content);
content.push_back(makeNestedBlock(tlv_ca_prefix, caConfig.m_caPrefix));
@@ -48,10 +48,10 @@
return content;
}
-CaConfigItem
+CaProfile
INFO::decodeDataContent(const Block& block)
{
- CaConfigItem result;
+ CaProfile result;
block.parse();
for (auto const& item : block.elements()) {
switch (item.type()) {
diff --git a/src/protocol-detail/info.hpp b/src/protocol-detail/info.hpp
index 03f0d61..571d120 100644
--- a/src/protocol-detail/info.hpp
+++ b/src/protocol-detail/info.hpp
@@ -32,12 +32,12 @@
* Encode CA configuration and its certificate into a TLV block as INFO Data packet content.
*/
static Block
- encodeDataContent(const CaConfigItem& caConfig, const security::v2::Certificate& certificate);
+ encodeDataContent(const CaProfile& caConfig, const security::v2::Certificate& certificate);
/**
* Decode CA configuration from the TLV block of INFO Data packet content.
*/
- static CaConfigItem
+ static CaProfile
decodeDataContent(const Block& block);
};
diff --git a/src/requester.cpp b/src/requester.cpp
new file mode 100644
index 0000000..7bf9594
--- /dev/null
+++ b/src/requester.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * 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 "requester.hpp"
+#include "challenge-module.hpp"
+#include "crypto-support/enc-tlv.hpp"
+#include "protocol-detail/challenge.hpp"
+#include "protocol-detail/error.hpp"
+#include "protocol-detail/info.hpp"
+#include "protocol-detail/new-renew-revoke.hpp"
+#include "protocol-detail/probe.hpp"
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <ndn-cxx/security/transform/base64-encode.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
+#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/util/io.hpp>
+#include <ndn-cxx/util/random.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+_LOG_INIT(ndncert.client);
+
+RequesterState::RequesterState(security::v2::KeyChain& keyChain, const CaProfile& caItem, RequestType requestType)
+ : m_caItem(caItem)
+ , m_keyChain(keyChain)
+ , m_type(requestType)
+{
+}
+
+shared_ptr<Interest>
+Requester::genCaProfileInterest(const Name& caName)
+{
+ Name interestName = caName;
+ if (readString(caName.at(-1)) != "CA")
+ interestName.append("CA");
+ interestName.append("INFO");
+ auto interest = make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ return interest;
+}
+
+boost::optional<CaProfile>
+Requester::onCaProfileResponse(const Data& reply)
+{
+ auto caItem = INFO::decodeDataContent(reply.getContent());
+ if (!security::verifySignature(reply, *caItem.m_cert)) {
+ _LOG_ERROR("Cannot verify replied Data packet signature.");
+ return boost::none;
+ }
+ return caItem;
+}
+
+shared_ptr<Interest>
+Requester::genProbeInterest(const CaProfile& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo)
+{
+ Name interestName = ca.m_caPrefix;
+ interestName.append("CA").append("PROBE");
+ auto interest = make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ interest->setApplicationParameters(PROBE::encodeApplicationParameters(std::move(probeInfo)));
+ return interest;
+}
+
+void
+Requester::onProbeResponse(const Data& reply, const CaProfile& ca,
+ std::vector<Name>& identityNames, std::vector<Name>& otherCas)
+{
+ if (!security::verifySignature(reply, *ca.m_cert)) {
+ _LOG_ERROR("Cannot verify replied Data packet signature.");
+ return;
+ }
+ processIfError(reply);
+ PROBE::decodeDataContent(reply.getContent(), identityNames, otherCas);
+}
+
+shared_ptr<Interest>
+Requester::genNewInterest(RequesterState& state, const Name& identityName,
+ const time::system_clock::TimePoint& notBefore,
+ const time::system_clock::TimePoint& notAfter)
+{
+ if (!state.m_caItem.m_caPrefix.isPrefixOf(identityName)) {
+ return nullptr;
+ }
+ if (identityName.empty()) {
+ NDN_LOG_TRACE("Randomly create a new name because identityName is empty and the param is empty.");
+ state.m_identityName = state.m_caItem.m_caPrefix;
+ state.m_identityName.append(std::to_string(random::generateSecureWord64()));
+ }
+ else {
+ state.m_identityName = identityName;
+ }
+
+ // generate a newly key pair or use an existing key
+ const auto& pib = state.m_keyChain.getPib();
+ security::pib::Identity identity;
+ try {
+ identity = pib.getIdentity(state.m_identityName);
+ }
+ catch (const security::Pib::Error& e) {
+ identity = state.m_keyChain.createIdentity(state.m_identityName);
+ state.m_isNewlyCreatedIdentity = true;
+ state.m_isNewlyCreatedKey = true;
+ }
+ try {
+ state.m_keyPair = identity.getDefaultKey();
+ }
+ catch (const security::Pib::Error& e) {
+ state.m_keyPair = state.m_keyChain.createKey(identity);
+ state.m_isNewlyCreatedKey = true;
+ }
+ auto& keyName = state.m_keyPair.getName();
+
+ // generate certificate request
+ security::v2::Certificate certRequest;
+ certRequest.setName(Name(keyName).append("cert-request").appendVersion());
+ certRequest.setContentType(tlv::ContentType_Key);
+ certRequest.setContent(state.m_keyPair.getPublicKey().data(), state.m_keyPair.getPublicKey().size());
+ SignatureInfo signatureInfo;
+ signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
+ state.m_keyChain.sign(certRequest, signingByKey(keyName).setSignatureInfo(signatureInfo));
+
+ // generate Interest packet
+ Name interestName = state.m_caItem.m_caPrefix;
+ interestName.append("CA").append("NEW");
+ auto interest = make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ interest->setApplicationParameters(
+ NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::NEW, state.m_ecdh.getBase64PubKey(), certRequest));
+
+ // sign the Interest packet
+ state.m_keyChain.sign(*interest, signingByKey(keyName));
+ return interest;
+}
+
+shared_ptr<Interest>
+Requester::genRevokeInterest(RequesterState& state, const security::v2::Certificate& certificate)
+{
+ if (!state.m_caItem.m_caPrefix.isPrefixOf(certificate.getName())) {
+ return nullptr;
+ }
+ // generate Interest packet
+ Name interestName = state.m_caItem.m_caPrefix;
+ interestName.append("CA").append("REVOKE");
+ auto interest = make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ interest->setApplicationParameters(
+ NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::REVOKE, state.m_ecdh.getBase64PubKey(), certificate));
+ return interest;
+}
+
+std::list<std::string>
+Requester::onNewRenewRevokeResponse(RequesterState& state, const Data& reply)
+{
+ if (!security::verifySignature(reply, *state.m_caItem.m_cert)) {
+ _LOG_ERROR("Cannot verify replied Data packet signature.");
+ return std::list<std::string>();
+ }
+ processIfError(reply);
+
+ auto contentTLV = reply.getContent();
+ contentTLV.parse();
+
+ // ECDH
+ const auto& peerKeyBase64Str = readString(contentTLV.get(tlv_ecdh_pub));
+ const auto& saltStr = readString(contentTLV.get(tlv_salt));
+ uint64_t saltInt = std::stoull(saltStr);
+ state.m_ecdh.deriveSecret(peerKeyBase64Str);
+
+ // HKDF
+ hkdf(state.m_ecdh.context->sharedSecret, state.m_ecdh.context->sharedSecretLen,
+ (uint8_t*)&saltInt, sizeof(saltInt), state.m_aesKey, sizeof(state.m_aesKey));
+
+ // update state
+ state.m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
+ state.m_requestId = readString(contentTLV.get(tlv_request_id));
+ std::list<std::string> challengeList;
+ for (auto const& element : contentTLV.elements()) {
+ if (element.type() == tlv_challenge) {
+ challengeList.push_back(readString(element));
+ }
+ }
+ return challengeList;
+}
+
+std::vector<std::tuple<std::string, std::string>>
+Requester::selectOrContinueChallenge(RequesterState& state, const std::string& challengeSelected)
+{
+ auto challenge = ChallengeModule::createChallengeModule(challengeSelected);
+ if (challenge == nullptr) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT."));
+ }
+ state.m_challengeType = challengeSelected;
+ return challenge->getRequestedParameterList(state.m_status, state.m_challengeStatus);
+}
+
+shared_ptr<Interest>
+Requester::genChallengeInterest(const RequesterState& state,
+ std::vector<std::tuple<std::string, std::string>>&& parameters)
+{
+ if (state.m_challengeType == "") {
+ BOOST_THROW_EXCEPTION(std::runtime_error("The challenge has not been selected."));
+ }
+ auto challenge = ChallengeModule::createChallengeModule(state.m_challengeType);
+ if (challenge == nullptr) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT."));
+ }
+ auto challengeParams = challenge->genChallengeRequestTLV(state.m_status, state.m_challengeStatus, std::move(parameters));
+
+ Name interestName = state.m_caItem.m_caPrefix;
+ interestName.append("CA").append("CHALLENGE").append(state.m_requestId);
+ auto interest = make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+
+ // encrypt the Interest parameters
+ auto paramBlock = encodeBlockWithAesGcm128(tlv::ApplicationParameters, state.m_aesKey,
+ challengeParams.value(), challengeParams.value_size(),
+ (const uint8_t*)"test", strlen("test"));
+ interest->setApplicationParameters(paramBlock);
+ state.m_keyChain.sign(*interest, signingByKey(state.m_keyPair.getName()));
+ return interest;
+}
+
+void
+Requester::onChallengeResponse(RequesterState& state, const Data& reply)
+{
+ if (!security::verifySignature(reply, *state.m_caItem.m_cert)) {
+ _LOG_ERROR("Cannot verify replied Data packet signature.");
+ return;
+ }
+ processIfError(reply);
+ auto result = decodeBlockWithAesGcm128(reply.getContent(), state.m_aesKey, (const uint8_t*)"test", strlen("test"));
+ Block contentTLV = makeBinaryBlock(tlv_encrypted_payload, result.data(), result.size());
+ contentTLV.parse();
+
+ // update state
+ state.m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
+ state.m_challengeStatus = readString(contentTLV.get(tlv_challenge_status));
+ state.m_remainingTries = readNonNegativeInteger(contentTLV.get(tlv_remaining_tries));
+ state.m_freshBefore = time::system_clock::now() +
+ time::seconds(readNonNegativeInteger(contentTLV.get(tlv_remaining_time)));
+
+ if (contentTLV.find(tlv_issued_cert_name) != contentTLV.elements_end()) {
+ Block issuedCertNameBlock = contentTLV.get(tlv_issued_cert_name);
+ issuedCertNameBlock.parse();
+ state.m_issuedCertName.wireDecode(issuedCertNameBlock.get(tlv::Name));
+ }
+}
+
+shared_ptr<Interest>
+Requester::genCertFetchInterest(const RequesterState& state)
+{
+ Name interestName = state.m_issuedCertName;
+ auto interest = make_shared<Interest>(interestName);
+ interest->setMustBeFresh(false);
+ interest->setCanBePrefix(false);
+ return interest;
+}
+
+shared_ptr<security::v2::Certificate>
+Requester::onCertFetchResponse(const Data& reply)
+{
+ try {
+ return std::make_shared<security::v2::Certificate>(reply.getContent().blockFromValue());
+ }
+ catch (const std::exception& e) {
+ _LOG_ERROR("Cannot parse replied certificate ");
+ return nullptr;
+ }
+}
+
+void
+Requester::endSession(RequesterState& state)
+{
+ if (state.m_status == Status::SUCCESS || state.m_status == Status::ENDED) {
+ return;
+ }
+ if (state.m_isNewlyCreatedIdentity) {
+ // put the identity into the if scope is because it may cause an error
+ // outside since when endSession is called, identity may not have been created yet.
+ auto identity = state.m_keyChain.getPib().getIdentity(state.m_identityName);
+ state.m_keyChain.deleteIdentity(identity);
+ }
+ else if (state.m_isNewlyCreatedKey) {
+ auto identity = state.m_keyChain.getPib().getIdentity(state.m_identityName);
+ state.m_keyChain.deleteKey(identity, state.m_keyPair);
+ }
+ state.m_status = Status::ENDED;
+}
+
+void
+Requester::processIfError(const Data& data)
+{
+ auto errorInfo = ErrorTLV::decodefromDataContent(data.getContent());
+ if (std::get<0>(errorInfo) == ErrorCode::NO_ERROR) {
+ return;
+ }
+ BOOST_THROW_EXCEPTION(std::runtime_error("Error info replied from the CA with Error code: " +
+ errorCodeToString(std::get<0>(errorInfo)) +
+ " and Error Info: " + std::get<1>(errorInfo)));
+}
+
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/requester.hpp b/src/requester.hpp
new file mode 100644
index 0000000..44dd7ee
--- /dev/null
+++ b/src/requester.hpp
@@ -0,0 +1,123 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * 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.
+ */
+
+#ifndef NDNCERT_CLIENT_MODULE_HPP
+#define NDNCERT_CLIENT_MODULE_HPP
+
+#include "configuration.hpp"
+#include "request-state.hpp"
+#include "crypto-support/crypto-helper.hpp"
+
+namespace ndn {
+namespace ndncert {
+
+// TODO
+// For each RequesterState, create a validator instance and initialize it with CA's cert
+// The validator instance should be in CaProfile
+
+struct RequesterState {
+ explicit
+ RequesterState(security::v2::KeyChain& keyChain, const CaProfile& caItem, RequestType requestType);
+
+ CaProfile m_caItem;
+ security::v2::KeyChain& m_keyChain;
+ RequestType m_type;
+
+ Name m_identityName;
+ security::Key m_keyPair;
+ std::string m_requestId;
+ Status m_status = Status::NOT_STARTED;
+ std::string m_challengeType;
+ std::string m_challengeStatus;
+ int m_remainingTries = 0;
+ time::system_clock::TimePoint m_freshBefore;
+ Name m_issuedCertName;
+
+ ECDHState m_ecdh;
+ uint8_t m_aesKey[16] = {0};
+
+ bool m_isCertInstalled = false;
+ bool m_isNewlyCreatedIdentity = false;
+ bool m_isNewlyCreatedKey = false;
+};
+
+class Requester : noncopyable
+{
+public:
+ // INFO related helpers
+ static shared_ptr<Interest>
+ genCaProfileInterest(const Name& caName);
+
+ /**
+ * Will first verify the signature of the packet using the key provided inside the profile.
+ * The application should be cautious whether to add CaProfile into the RequesterCaCache.
+ */
+ static boost::optional<CaProfile>
+ onCaProfileResponse(const Data& reply);
+
+ // PROBE related helpers
+ static shared_ptr<Interest>
+ genProbeInterest(const CaProfile& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo);
+
+ static void
+ onProbeResponse(const Data& reply, const CaProfile& ca,
+ std::vector<Name>& identityNames, std::vector<Name>& otherCas);
+
+ // NEW/REVOKE/RENEW related helpers
+ static shared_ptr<Interest>
+ genNewInterest(RequesterState& state, const Name& identityName,
+ const time::system_clock::TimePoint& notBefore,
+ const time::system_clock::TimePoint& notAfter);
+
+ static shared_ptr<Interest>
+ genRevokeInterest(RequesterState& state, const security::v2::Certificate& certificate);
+
+ static std::list<std::string>
+ onNewRenewRevokeResponse(RequesterState& state, const Data& reply);
+
+ // CHALLENGE helpers
+ static std::vector<std::tuple<std::string, std::string>>
+ selectOrContinueChallenge(RequesterState& state, const std::string& challengeSelected);
+
+ static shared_ptr<Interest>
+ genChallengeInterest(const RequesterState& state,
+ std::vector<std::tuple<std::string, std::string>>&& parameters);
+
+ static void
+ onChallengeResponse(RequesterState& state, const Data& reply);
+
+ static shared_ptr<Interest>
+ genCertFetchInterest(const RequesterState& state);
+
+ shared_ptr<security::v2::Certificate>
+ onCertFetchResponse(const Data& reply);
+
+ void
+ endSession(RequesterState& state);
+
+private:
+ static void
+ processIfError(const Data& data);
+};
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CLIENT_MODULE_HPP