blob: 73a0c291a7406bebe536881d360a002242240580 [file] [log] [blame]
tylerliuf51e3162020-12-20 19:22:59 -08001/*
2 * Copyright (c) 2017-2020, Regents of the University of California.
3 *
4 * This file is part of ndncert, a certificate management system based on NDN.
5 *
6 * ndncert is free software: you can redistribute it and/or modify it under the terms
7 * of the GNU General Public License as published by the Free Software Foundation, either
8 * version 3 of the License, or (at your option) any later version.
9 *
10 * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 *
14 * You should have received copies of the GNU General Public License along with
15 * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * See AUTHORS.md for complete list of ndncert authors and contributors.
18 */
19
20#include "challenge-possession.hpp"
21#include <ndn-cxx/security/verification-helpers.hpp>
22#include <ndn-cxx/security/signing-helpers.hpp>
23#include <ndn-cxx/security/transform/public-key.hpp>
24#include <ndn-cxx/util/io.hpp>
25#include <ndn-cxx/util/random.hpp>
26
27namespace ndn {
28namespace ndncert {
29
30NDN_LOG_INIT(ndncert.challenge.possession);
tylerliuf26d41b2021-10-12 16:50:16 -070031NDNCERT_REGISTER_CHALLENGE(ChallengePossession, "possession");
tylerliuf51e3162020-12-20 19:22:59 -080032
33const std::string ChallengePossession::PARAMETER_KEY_CREDENTIAL_CERT = "issued-cert";
34const std::string ChallengePossession::PARAMETER_KEY_NONCE = "nonce";
35const std::string ChallengePossession::PARAMETER_KEY_PROOF = "proof";
36const std::string ChallengePossession::NEED_PROOF = "need-proof";
37
38ChallengePossession::ChallengePossession(const std::string& configPath)
39 : ChallengeModule("Possession", 1, time::seconds(60))
40{
41 if (configPath.empty()) {
42 m_configFile = std::string(NDNCERT_SYSCONFDIR) + "/ndncert/challenge-credential.conf";
43 }
44 else {
45 m_configFile = configPath;
46 }
47}
48
49void
50ChallengePossession::parseConfigFile()
51{
52 JsonSection config;
53 try {
54 boost::property_tree::read_json(m_configFile, config);
55 }
56 catch (const boost::property_tree::info_parser_error& error) {
57 NDN_THROW(std::runtime_error("Failed to parse configuration file " + m_configFile +
58 " " + error.message() + " line " + std::to_string(error.line())));
59 }
60
61 if (config.begin() == config.end()) {
62 NDN_THROW(std::runtime_error("Error processing configuration file: " + m_configFile + " no data"));
63 }
64
65 m_trustAnchors.clear();
66 auto anchorList = config.get_child("anchor-list");
67 auto it = anchorList.begin();
68 for (; it != anchorList.end(); it++) {
69 std::istringstream ss(it->second.get("certificate", ""));
70 auto cert = io::load<security::Certificate>(ss);
71 if (cert == nullptr) {
72 NDN_LOG_ERROR("Cannot load the certificate from config file");
73 continue;
74 }
75 m_trustAnchors.push_back(*cert);
76 }
77}
78
79// For CA
80std::tuple<ErrorCode, std::string>
81ChallengePossession::handleChallengeRequest(const Block& params, ca::RequestState& request)
82{
83 params.parse();
84 if (m_trustAnchors.empty()) {
85 parseConfigFile();
86 }
87 security::Certificate credential;
88 const uint8_t* signature = nullptr;
89 size_t signatureLen = 0;
90 const auto& elements = params.elements();
91 for (size_t i = 0; i < elements.size() - 1; i++) {
92 if (elements[i].type() == tlv::ParameterKey && elements[i + 1].type() == tlv::ParameterValue) {
93 if (readString(elements[i]) == PARAMETER_KEY_CREDENTIAL_CERT) {
94 try {
95 credential.wireDecode(elements[i + 1].blockFromValue());
96 }
97 catch (const std::exception& e) {
98 NDN_LOG_ERROR("Cannot load challenge parameter: credential " << e.what());
99 return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Cannot challenge credential: credential." + std::string(e.what()));
100 }
101 }
102 else if (readString(elements[i]) == PARAMETER_KEY_PROOF) {
103 signature = elements[i + 1].value();
104 signatureLen = elements[i + 1].value_size();
105 }
106 }
107 }
108
109 // verify the credential and the self-signed cert
110 if (request.status == Status::BEFORE_CHALLENGE) {
111 NDN_LOG_TRACE("Challenge Interest arrives. Check certificate and init the challenge");
112 // check the certificate
113 bool checkOK = false;
114 if (credential.hasContent() && signatureLen == 0) {
115 Name signingKeyName = credential.getSignatureInfo().getKeyLocator().getName();
116 security::transform::PublicKey key;
117 const auto &pubKeyBuffer = credential.getPublicKey();
118 key.loadPkcs8(pubKeyBuffer.data(), pubKeyBuffer.size());
119 for (auto anchor : m_trustAnchors) {
120 if (anchor.getKeyName() == signingKeyName) {
121 if (security::verifySignature(credential, anchor)) {
122 checkOK = true;
123 }
124 }
125 }
126 } else {
127 return returnWithError(request, ErrorCode::BAD_INTEREST_FORMAT, "Cannot find certificate");
128 }
129 if (!checkOK) {
130 return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Certificate cannot be verified");
131 }
132
133 // for the first time, init the challenge
134 std::array<uint8_t, 16> secretCode{};
135 random::generateSecureBytes(secretCode.data(), 16);
136 JsonSection secretJson;
137 secretJson.add(PARAMETER_KEY_NONCE, toHex(secretCode.data(), 16));
138 auto credential_block = credential.wireEncode();
139 secretJson.add(PARAMETER_KEY_CREDENTIAL_CERT, toHex(credential_block.wire(), credential_block.size()));
140 NDN_LOG_TRACE("Secret for request " << toHex(request.requestId.data(), request.requestId.size())
141 << " : " << toHex(secretCode.data(), 16));
142 return returnWithNewChallengeStatus(request, NEED_PROOF, std::move(secretJson), m_maxAttemptTimes, m_secretLifetime);
143 } else if (request.challengeState && request.challengeState->challengeStatus == NEED_PROOF) {
144 NDN_LOG_TRACE("Challenge Interest (proof) arrives. Check the proof");
145 //check the format and load credential
146 if (credential.hasContent() || signatureLen == 0) {
147 return returnWithError(request, ErrorCode::BAD_INTEREST_FORMAT, "Cannot find certificate");
148 }
149 credential = security::Certificate(Block(fromHex(request.challengeState->secrets.get(PARAMETER_KEY_CREDENTIAL_CERT, ""))));
150 auto secretCode = *fromHex(request.challengeState->secrets.get(PARAMETER_KEY_NONCE, ""));
151
152 //check the proof
153 security::transform::PublicKey key;
154 const auto& pubKeyBuffer = credential.getPublicKey();
155 key.loadPkcs8(pubKeyBuffer.data(), pubKeyBuffer.size());
156 if (security::verifySignature(secretCode.data(), secretCode.size(), signature, signatureLen, key)) {
157 return returnWithSuccess(request);
158 }
159 return returnWithError(request, ErrorCode::INVALID_PARAMETER,
160 "Cannot verify the proof of private key against credential.");
161 }
162 NDN_LOG_TRACE("Proof of possession: bad state");
163 return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Fail to recognize the request.");
164}
165
166// For Client
167std::multimap<std::string, std::string>
168ChallengePossession::getRequestedParameterList(Status status, const std::string& challengeStatus)
169{
170 std::multimap<std::string, std::string> result;
171 if (status == Status::BEFORE_CHALLENGE) {
172 result.emplace(PARAMETER_KEY_CREDENTIAL_CERT, "Please provide the certificate issued by a trusted CA.");
173 return result;
174 } else if (status == Status::CHALLENGE && challengeStatus == NEED_PROOF) {
175 result.emplace(PARAMETER_KEY_PROOF, "Please sign a Data packet with request ID as the content.");
176 } else {
177 NDN_THROW(std::runtime_error("Unexpected status or challenge status."));
178 }
179
180 return result;
181}
182
183Block
184ChallengePossession::genChallengeRequestTLV(Status status, const std::string& challengeStatus,
185 const std::multimap<std::string, std::string>& params)
186{
187 Block request(tlv::EncryptedPayload);
188 if (status == Status::BEFORE_CHALLENGE) {
189 if (params.size() != 1) {
190 NDN_THROW(std::runtime_error("Wrong parameter provided."));
191 }
192 request.push_back(makeStringBlock(tlv::SelectedChallenge, CHALLENGE_TYPE));
193 for (const auto& item : params) {
194 if (std::get<0>(item) == PARAMETER_KEY_CREDENTIAL_CERT) {
195 request.push_back(makeStringBlock(tlv::ParameterKey, PARAMETER_KEY_CREDENTIAL_CERT));
196 Block valueBlock(tlv::ParameterValue);
197 auto& certTlvStr = std::get<1>(item);
198 valueBlock.push_back(Block((uint8_t*)certTlvStr.c_str(), certTlvStr.size()));
199 request.push_back(valueBlock);
200 }
201 else {
202 NDN_THROW(std::runtime_error("Wrong parameter provided."));
203 }
204 }
205 } else if (status == Status::CHALLENGE && challengeStatus == NEED_PROOF){
206 if (params.size() != 1) {
207 NDN_THROW(std::runtime_error("Wrong parameter provided."));
208 }
209 for (const auto &item : params) {
210 if (std::get<0>(item) == PARAMETER_KEY_PROOF) {
211 request.push_back(makeStringBlock(tlv::ParameterKey, PARAMETER_KEY_PROOF));
212 auto &sigTlvStr = std::get<1>(item);
213 Block valueBlock = makeBinaryBlock(tlv::ParameterValue, (uint8_t *) sigTlvStr.c_str(),
214 sigTlvStr.size());
215 request.push_back(valueBlock);
216 } else {
217 NDN_THROW(std::runtime_error("Wrong parameter provided."));
218 }
219 }
220 } else {
221 NDN_THROW(std::runtime_error("Unexpected status or challenge status."));
222 }
223 request.encode();
224 return request;
225}
226
227void
228ChallengePossession::fulfillParameters(std::multimap<std::string, std::string>& params,
229 KeyChain& keyChain, const Name& issuedCertName,
230 const std::array<uint8_t, 16>& nonce)
231{
232 auto& pib = keyChain.getPib();
233 auto id = pib.getIdentity(security::extractIdentityFromCertName(issuedCertName));
234 auto issuedCert = id.getKey(security::extractKeyNameFromCertName(issuedCertName)).getCertificate(issuedCertName);
235 auto issuedCertTlv = issuedCert.wireEncode();
236 auto signatureTlv = keyChain.sign(nonce.data(), nonce.size(), security::signingByCertificate(issuedCertName));
237 for (auto& item : params) {
238 if (std::get<0>(item) == PARAMETER_KEY_CREDENTIAL_CERT) {
239 std::get<1>(item) = std::string((char*)issuedCertTlv.wire(), issuedCertTlv.size());
240 }
241 else if (std::get<0>(item) == PARAMETER_KEY_PROOF) {
242 std::get<1>(item) = std::string((char*)signatureTlv.value(), signatureTlv.value_size());
243 }
244 }
245 return;
246}
247
248} // namespace ndncert
249} // namespace ndn