blob: 0e8565e6cf4c979a924caa1993a19179da122ce3 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2017-2024, 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/request-encoder.hpp"
#include "detail/probe-encoder.hpp"
#include <ndn-cxx/metadata-object.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/logger.hpp>
#include <ndn-cxx/util/random.hpp>
#include <boost/lexical_cast.hpp>
namespace ndncert::requester {
NDN_LOG_INIT(ndncert.client);
std::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>(ndn::MetadataObject::makeDiscoveryInterest(contentName));
}
std::shared_ptr<Interest>
Request::genCaProfileInterestFromDiscoveryResponse(const Data& reply)
{
auto metaData = ndn::MetadataObject(reply);
auto interestName = metaData.getVersionedName();
interestName.appendSegment(0);
return std::make_shared<Interest>(interestName);
}
std::optional<CaProfile>
Request::onCaProfileResponse(const Data& reply)
{
auto caItem = infotlv::decodeDataContent(reply.getContent());
if (!ndn::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;
}
std::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<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);
}
std::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->setApplicationParameters(probetlv::encodeApplicationParameters(probeInfo));
return interest;
}
void
Request::onProbeResponse(const Data& reply, const CaProfile& ca,
std::vector<std::pair<Name, int>>& identityNames, std::vector<Name>& otherCas)
{
if (!ndn::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."));
}
processIfError(reply);
probetlv::decodeDataContent(reply.getContent(), identityNames, otherCas);
}
Request::Request(ndn::KeyChain& keyChain, const CaProfile& profile, RequestType requestType)
: m_caProfile(profile)
, m_type(requestType)
, m_keyChain(keyChain)
{
}
std::shared_ptr<Interest>
Request::genNewInterest(const Name& keyName,
const time::system_clock::time_point& notBefore,
const time::system_clock::time_point& notAfter)
{
if (keyName.empty() || !m_caProfile.caPrefix.isPrefixOf(keyName)) {
return nullptr;
}
m_identityName = ndn::security::extractIdentityFromKeyName(keyName);
auto identity = m_keyChain.getPib().getIdentity(m_identityName);
m_keyPair = identity.getKey(keyName);
// generate certificate request
ndn::security::MakeCertificateOptions opts;
opts.issuerId = Name::Component("cert-request");
opts.validity.emplace(notBefore, notAfter);
auto certRequest = m_keyChain.makeCertificate(m_keyPair, signingByKey(keyName), opts);
// generate Interest packet
Name interestName = m_caProfile.caPrefix;
interestName.append("CA").append("NEW");
auto interest = std::make_shared<Interest>(interestName);
interest->setMustBeFresh(true);
interest->setApplicationParameters(
requesttlv::encodeApplicationParameters(RequestType::NEW, m_ecdh.getSelfPubKey(), certRequest));
// sign the Interest packet
m_keyChain.sign(*interest, signingByKey(keyName));
return interest;
}
std::shared_ptr<Interest>
Request::genRevokeInterest(const Certificate& certificate)
{
if (!m_caProfile.caPrefix.isPrefixOf(certificate.getName())) {
return nullptr;
}
// generate Interest packet
Name interestName = m_caProfile.caPrefix;
interestName.append("CA").append("REVOKE");
auto interest = std::make_shared<Interest>(interestName);
interest->setMustBeFresh(true);
interest->setApplicationParameters(
requesttlv::encodeApplicationParameters(RequestType::REVOKE, m_ecdh.getSelfPubKey(), certificate));
return interest;
}
std::list<std::string>
Request::onNewRenewRevokeResponse(const Data& reply)
{
if (!ndn::security::verifySignature(reply, *m_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, m_requestId);
// ECDH and HKDF
auto sharedSecret = m_ecdh.deriveSecret(ecdhKey);
hkdf(sharedSecret.data(), sharedSecret.size(),
salt.data(), salt.size(), m_aesKey.data(), m_aesKey.size(),
m_requestId.data(), m_requestId.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."));
}
m_challengeType = challengeSelected;
return challenge->getRequestedParameterList(m_status, m_challengeStatus);
}
std::shared_ptr<Interest>
Request::genChallengeInterest(std::multimap<std::string, std::string>&& parameters)
{
if (m_challengeType.empty()) {
NDN_THROW(std::runtime_error("The challenge has not been selected."));
}
auto challenge = ChallengeModule::createChallengeModule(m_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(m_status, m_challengeStatus, parameters);
Name interestName = m_caProfile.caPrefix;
interestName.append("CA").append("CHALLENGE").append(Name::Component(m_requestId));
auto interest = std::make_shared<Interest>(interestName);
interest->setMustBeFresh(true);
// encrypt the Interest parameters
auto paramBlock = encodeBlockWithAesGcm128(ndn::tlv::ApplicationParameters, m_aesKey.data(),
challengeParams.value(), challengeParams.value_size(),
m_requestId.data(), m_requestId.size(),
m_encryptionIv);
interest->setApplicationParameters(paramBlock);
m_keyChain.sign(*interest, signingByKey(m_keyPair.getName()));
return interest;
}
void
Request::onChallengeResponse(const Data& reply)
{
if (!ndn::security::verifySignature(reply, *m_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);
}
std::shared_ptr<Interest>
Request::genCertFetchInterest() const
{
auto interest = std::make_shared<Interest>(m_issuedCertName);
if (!m_forwardingHint.empty()) {
interest->setForwardingHint({m_forwardingHint});
}
return interest;
}
std::shared_ptr<Certificate>
Request::onCertFetchResponse(const Data& reply)
{
try {
return std::make_shared<Certificate>(reply);
}
catch (const std::exception&) {
NDN_LOG_ERROR("Cannot parse replied certificate");
NDN_THROW(std::runtime_error("Cannot parse replied certificate"));
}
}
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 ndncert::requester