blob: 5ca6aaa0692f3bf0e2684bd5a1c55dd8a31efcc4 [file] [log] [blame]
/* -*- 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/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>
Requester::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>
Requester::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>
Requester::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>
Requester::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>
Requester::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
Requester::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);
}
shared_ptr<Interest>
Requester::genNewInterest(RequestState& state, const Name& identityName,
const time::system_clock::TimePoint& notBefore,
const time::system_clock::TimePoint& notAfter)
{
if (!state.caProfile.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.identityName = state.caProfile.caPrefix;
state.identityName.append(std::to_string(random::generateSecureWord64()));
}
else {
state.identityName = identityName;
}
// generate a newly key pair or use an existing key
const auto& pib = state.keyChain.getPib();
security::pib::Identity identity;
try {
identity = pib.getIdentity(state.identityName);
}
catch (const security::Pib::Error& e) {
identity = state.keyChain.createIdentity(state.identityName);
state.isNewlyCreatedIdentity = true;
state.isNewlyCreatedKey = true;
}
try {
state.keyPair = identity.getDefaultKey();
}
catch (const security::Pib::Error& e) {
state.keyPair = state.keyChain.createKey(identity);
state.isNewlyCreatedKey = true;
}
auto& keyName = state.keyPair.getName();
// generate certificate request
security::Certificate certRequest;
certRequest.setName(Name(keyName).append("cert-request").appendVersion());
certRequest.setContentType(ndn::tlv::ContentType_Key);
certRequest.setContent(state.keyPair.getPublicKey().data(), state.keyPair.getPublicKey().size());
SignatureInfo signatureInfo;
signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
state.keyChain.sign(certRequest, signingByKey(keyName).setSignatureInfo(signatureInfo));
// generate Interest packet
Name interestName = state.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, state.ecdh.getSelfPubKey(), certRequest));
// sign the Interest packet
state.keyChain.sign(*interest, signingByKey(keyName));
return interest;
}
shared_ptr<Interest>
Requester::genRevokeInterest(RequestState& state, const security::Certificate& certificate)
{
if (!state.caProfile.caPrefix.isPrefixOf(certificate.getName())) {
return nullptr;
}
// generate Interest packet
Name interestName = state.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, state.ecdh.getSelfPubKey(), certificate));
return interest;
}
std::list<std::string>
Requester::onNewRenewRevokeResponse(RequestState& state, const Data& reply)
{
if (!security::verifySignature(reply, *state.caProfile.cert)) {
NDN_LOG_ERROR("Cannot verify replied Data packet signature.");
NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature."));
}
processIfError(reply);
auto contentTLV = reply.getContent();
std::vector<uint8_t> ecdhKey;
std::array<uint8_t, 32> salt;
auto challenges = requesttlv::decodeDataContent(contentTLV, ecdhKey, salt, state.requestId, state.status);
// ECDH and HKDF
auto sharedSecret = state.ecdh.deriveSecret(ecdhKey);
hkdf(sharedSecret.data(), sharedSecret.size(),
salt.data(), salt.size(), state.aesKey.data(), state.aesKey.size());
// update state
return challenges;
}
std::multimap<std::string, std::string>
Requester::selectOrContinueChallenge(RequestState& state, 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."));
}
state.challengeType = challengeSelected;
return challenge->getRequestedParameterList(state.status, state.challengeStatus);
}
shared_ptr<Interest>
Requester::genChallengeInterest(RequestState& state,
std::multimap<std::string, std::string>&& parameters)
{
if (state.challengeType == "") {
NDN_THROW(std::runtime_error("The challenge has not been selected."));
}
auto challenge = ChallengeModule::createChallengeModule(state.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(state.status, state.challengeStatus, std::move(parameters));
Name interestName = state.caProfile.caPrefix;
interestName.append("CA").append("CHALLENGE").append(state.requestId.data(), state.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, state.aesKey.data(),
challengeParams.value(), challengeParams.value_size(),
state.requestId.data(), state.requestId.size(),
state.encryptionIv);
interest->setApplicationParameters(paramBlock);
state.keyChain.sign(*interest, signingByKey(state.keyPair.getName()));
return interest;
}
void
Requester::onChallengeResponse(RequestState& state, const Data& reply)
{
if (!security::verifySignature(reply, *state.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(), state);
}
shared_ptr<Interest>
Requester::genCertFetchInterest(const RequestState& state)
{
Name interestName = state.issuedCertName;
auto interest =std::make_shared<Interest>(interestName);
interest->setMustBeFresh(false);
interest->setCanBePrefix(false);
return interest;
}
shared_ptr<security::Certificate>
Requester::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
Requester::endSession(RequestState& state)
{
if (state.status == Status::SUCCESS) {
return;
}
if (state.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.keyChain.getPib().getIdentity(state.identityName);
state.keyChain.deleteIdentity(identity);
}
else if (state.isNewlyCreatedKey) {
auto identity = state.keyChain.getPib().getIdentity(state.identityName);
state.keyChain.deleteKey(identity, state.keyPair);
}
}
void
Requester::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