combine requester state
Change-Id: If91d08318ec1a40a8c031fdd493fe1f8662ef35d
diff --git a/src/requester-request.cpp b/src/requester-request.cpp
new file mode 100644
index 0000000..0fe0219
--- /dev/null
+++ b/src/requester-request.cpp
@@ -0,0 +1,336 @@
+/* -*- 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-request.hpp"
+#include "challenge/challenge-module.hpp"
+#include "detail/crypto-helpers.hpp"
+#include "detail/challenge-encoder.hpp"
+#include "detail/error-encoder.hpp"
+#include "detail/info-encoder.hpp"
+#include "detail/new-renew-revoke-encoder.hpp"
+#include "detail/probe-encoder.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 <ndn-cxx/metadata-object.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace ndncert {
+namespace requester {
+
+NDN_LOG_INIT(ndncert.client);
+
+shared_ptr<Interest>
+Request::genCaProfileDiscoveryInterest(const Name& caName)
+{
+ Name contentName = caName;
+ if (readString(caName.at(-1)) != "CA")
+ contentName.append("CA");
+ contentName.append("INFO");
+ return std::make_shared<Interest>(MetadataObject::makeDiscoveryInterest(contentName));
+}
+
+shared_ptr<Interest>
+Request::genCaProfileInterestFromDiscoveryResponse(const Data& reply)
+{
+ // set naming convention to be typed
+ auto convention = name::getConventionEncoding();
+ name::setConventionEncoding(name::Convention::TYPED);
+
+ auto metaData = MetadataObject(reply);
+ auto interestName= metaData.getVersionedName();
+ interestName.appendSegment(0);
+ auto interest = std::make_shared<Interest>(interestName);
+ interest->setCanBePrefix(false);
+
+ // set back the convention
+ name::setConventionEncoding(convention);
+
+ return interest;
+}
+
+optional<CaProfile>
+Request::onCaProfileResponse(const Data& reply)
+{
+ auto caItem = infotlv::decodeDataContent(reply.getContent());
+ if (!security::verifySignature(reply, *caItem.cert)) {
+ NDN_LOG_ERROR("Cannot verify replied Data packet signature.");
+ NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature."));
+ }
+ return caItem;
+}
+
+optional<CaProfile>
+Request::onCaProfileResponseAfterRedirection(const Data& reply, const Name& caCertFullName)
+{
+ auto caItem = infotlv::decodeDataContent(reply.getContent());
+ auto certBlock = caItem.cert->wireEncode();
+ caItem.cert = std::make_shared<security::Certificate>(certBlock);
+ if (caItem.cert->getFullName() != caCertFullName) {
+ NDN_LOG_ERROR("Ca profile does not match the certificate information offered by the original CA.");
+ NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature."));
+ }
+ return onCaProfileResponse(reply);
+}
+
+shared_ptr<Interest>
+Request::genProbeInterest(const CaProfile& ca, std::multimap<std::string, std::string>&& probeInfo)
+{
+ Name interestName = ca.caPrefix;
+ interestName.append("CA").append("PROBE");
+ auto interest =std::make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ interest->setApplicationParameters(probetlv::encodeApplicationParameters(std::move(probeInfo)));
+ return interest;
+}
+
+void
+Request::onProbeResponse(const Data& reply, const CaProfile& ca,
+ std::vector<std::pair<Name, int>>& identityNames, std::vector<Name>& otherCas)
+{
+ if (!security::verifySignature(reply, *ca.cert)) {
+ NDN_LOG_ERROR("Cannot verify replied Data packet signature.");
+ NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature."));
+ return;
+ }
+ processIfError(reply);
+ probetlv::decodeDataContent(reply.getContent(), identityNames, otherCas);
+}
+
+Request::Request(security::KeyChain& keyChain, const CaProfile& profile, RequestType requestType)
+ : m_keyChain(keyChain)
+ , caProfile(profile)
+ , type(requestType)
+{}
+
+shared_ptr<Interest>
+Request::genNewInterest(const Name& newIdentityName,
+ const time::system_clock::TimePoint& notBefore,
+ const time::system_clock::TimePoint& notAfter)
+{
+ if (!caProfile.caPrefix.isPrefixOf(newIdentityName)) {
+ return nullptr;
+ }
+ if (newIdentityName.empty()) {
+ NDN_LOG_TRACE("Randomly create a new name because newIdentityName is empty and the param is empty.");
+ identityName = caProfile.caPrefix;
+ identityName.append(std::to_string(random::generateSecureWord64()));
+ }
+ else {
+ identityName = newIdentityName;
+ }
+
+ // generate a newly key pair or use an existing key
+ const auto& pib = m_keyChain.getPib();
+ security::pib::Identity identity;
+ try {
+ identity = pib.getIdentity(identityName);
+ }
+ catch (const security::Pib::Error& e) {
+ identity = m_keyChain.createIdentity(identityName);
+ m_isNewlyCreatedIdentity = true;
+ m_isNewlyCreatedKey = true;
+ }
+ try {
+ m_keyPair = identity.getDefaultKey();
+ }
+ catch (const security::Pib::Error& e) {
+ m_keyPair = m_keyChain.createKey(identity);
+ m_isNewlyCreatedKey = true;
+ }
+ auto& keyName = m_keyPair.getName();
+
+ // generate certificate request
+ security::Certificate certRequest;
+ certRequest.setName(Name(keyName).append("cert-request").appendVersion());
+ certRequest.setContentType(ndn::tlv::ContentType_Key);
+ certRequest.setContent(m_keyPair.getPublicKey().data(), m_keyPair.getPublicKey().size());
+ SignatureInfo signatureInfo;
+ signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
+ m_keyChain.sign(certRequest, signingByKey(keyName).setSignatureInfo(signatureInfo));
+
+ // generate Interest packet
+ Name interestName = caProfile.caPrefix;
+ interestName.append("CA").append("NEW");
+ auto interest =std::make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ interest->setApplicationParameters(
+ requesttlv::encodeApplicationParameters(RequestType::NEW, ecdh.getSelfPubKey(), certRequest));
+
+ // sign the Interest packet
+ m_keyChain.sign(*interest, signingByKey(keyName));
+ return interest;
+}
+
+shared_ptr<Interest>
+Request::genRevokeInterest(const security::Certificate& certificate)
+{
+ if (!caProfile.caPrefix.isPrefixOf(certificate.getName())) {
+ return nullptr;
+ }
+ // generate Interest packet
+ Name interestName = caProfile.caPrefix;
+ interestName.append("CA").append("REVOKE");
+ auto interest =std::make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+ interest->setApplicationParameters(
+ requesttlv::encodeApplicationParameters(RequestType::REVOKE, ecdh.getSelfPubKey(), certificate));
+ return interest;
+}
+
+std::list<std::string>
+Request::onNewRenewRevokeResponse(const Data& reply)
+{
+ if (!security::verifySignature(reply, *caProfile.cert)) {
+ NDN_LOG_ERROR("Cannot verify replied Data packet signature.");
+ NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature."));
+ }
+ processIfError(reply);
+
+ const auto& contentTLV = reply.getContent();
+ std::vector<uint8_t> ecdhKey;
+ std::array<uint8_t, 32> salt;
+ auto challenges = requesttlv::decodeDataContent(contentTLV, ecdhKey, salt, requestId, status);
+
+ // ECDH and HKDF
+ auto sharedSecret = ecdh.deriveSecret(ecdhKey);
+ hkdf(sharedSecret.data(), sharedSecret.size(),
+ salt.data(), salt.size(), aesKey.data(), aesKey.size());
+
+ // update state
+ return challenges;
+}
+
+std::multimap<std::string, std::string>
+Request::selectOrContinueChallenge(const std::string& challengeSelected)
+{
+ auto challenge = ChallengeModule::createChallengeModule(challengeSelected);
+ if (challenge == nullptr) {
+ NDN_THROW(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT."));
+ }
+ challengeType = challengeSelected;
+ return challenge->getRequestedParameterList(status, challengeStatus);
+}
+
+shared_ptr<Interest>
+Request::genChallengeInterest(std::multimap<std::string, std::string>&& parameters)
+{
+ if (challengeType == "") {
+ NDN_THROW(std::runtime_error("The challenge has not been selected."));
+ }
+ auto challenge = ChallengeModule::createChallengeModule(challengeType);
+ if (challenge == nullptr) {
+ NDN_THROW(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT."));
+ }
+ auto challengeParams = challenge->genChallengeRequestTLV(status, challengeStatus, std::move(parameters));
+
+ Name interestName = caProfile.caPrefix;
+ interestName.append("CA").append("CHALLENGE").append(requestId.data(), requestId.size());
+ auto interest =std::make_shared<Interest>(interestName);
+ interest->setMustBeFresh(true);
+ interest->setCanBePrefix(false);
+
+ // encrypt the Interest parameters
+ auto paramBlock = encodeBlockWithAesGcm128(ndn::tlv::ApplicationParameters, aesKey.data(),
+ challengeParams.value(), challengeParams.value_size(),
+ requestId.data(), requestId.size(),
+ encryptionIv);
+ interest->setApplicationParameters(paramBlock);
+ m_keyChain.sign(*interest, signingByKey(m_keyPair.getName()));
+ return interest;
+}
+
+void
+Request::onChallengeResponse(const Data& reply)
+{
+ if (!security::verifySignature(reply, *caProfile.cert)) {
+ NDN_LOG_ERROR("Cannot verify replied Data packet signature.");
+ NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature."));
+ }
+ processIfError(reply);
+ challengetlv::decodeDataContent(reply.getContent(), *this);
+}
+
+shared_ptr<Interest>
+Request::genCertFetchInterest() const
+{
+ Name interestName = issuedCertName;
+ auto interest =std::make_shared<Interest>(interestName);
+ interest->setMustBeFresh(false);
+ interest->setCanBePrefix(false);
+ return interest;
+}
+
+shared_ptr<security::Certificate>
+Request::onCertFetchResponse(const Data& reply)
+{
+ try {
+ return std::make_shared<security::Certificate>(reply);
+ }
+ catch (const std::exception& e) {
+ NDN_LOG_ERROR("Cannot parse replied certificate ");
+ NDN_THROW(std::runtime_error("Cannot parse replied certificate "));
+ return nullptr;
+ }
+}
+
+void
+Request::endSession()
+{
+ if (status == Status::SUCCESS) {
+ 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(identityName);
+ m_keyChain.deleteIdentity(identity);
+ }
+ else if (m_isNewlyCreatedKey) {
+ auto identity = m_keyChain.getPib().getIdentity(identityName);
+ m_keyChain.deleteKey(identity, m_keyPair);
+ }
+}
+
+void
+Request::processIfError(const Data& data)
+{
+ auto errorInfo = errortlv::decodefromDataContent(data.getContent());
+ if (std::get<0>(errorInfo) == ErrorCode::NO_ERROR) {
+ return;
+ }
+ NDN_LOG_ERROR("Error info replied from the CA with Error code: " << std::get<0>(errorInfo) <<
+ " and Error Info: " << std::get<1>(errorInfo));
+ NDN_THROW(std::runtime_error("Error info replied from the CA with Error code: " +
+ boost::lexical_cast<std::string>(std::get<0>(errorInfo)) +
+ " and Error Info: " + std::get<1>(errorInfo)));
+}
+
+} // namespace requester
+} // namespace ndncert
+} // namespace ndn