blob: 9ae71c03046c40768d2cf68911f273bbd6f6367b [file] [log] [blame]
Zhiyi Zhang23564c82017-03-01 10:22:22 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -07003 * Copyright (c) 2017-2019, Regents of the University of California.
Zhiyi Zhang23564c82017-03-01 10:22:22 -08004 *
5 * This file is part of ndncert, a certificate management system based on NDN.
6 *
7 * ndncert is free software: you can redistribute it and/or modify it under the terms
8 * of the GNU General Public License as published by the Free Software Foundation, either
9 * version 3 of the License, or (at your option) any later version.
10 *
11 * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License along with
16 * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * See AUTHORS.md for complete list of ndncert authors and contributors.
19 */
20
21#include "client-module.hpp"
22#include "logging.hpp"
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070023#include "challenge-module.hpp"
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070024#include "crypto-support/enc-tlv.hpp"
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080025#include <ndn-cxx/util/io.hpp>
Zhiyi Zhang23564c82017-03-01 10:22:22 -080026#include <ndn-cxx/security/signing-helpers.hpp>
27#include <ndn-cxx/security/verification-helpers.hpp>
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070028#include <ndn-cxx/util/random.hpp>
29#include <ndn-cxx/security/transform/base64-encode.hpp>
30#include <ndn-cxx/security/transform/buffer-source.hpp>
31#include <ndn-cxx/security/transform/stream-sink.hpp>
Zhiyi Zhang23564c82017-03-01 10:22:22 -080032
33namespace ndn {
34namespace ndncert {
35
36_LOG_INIT(ndncert.client);
37
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070038ClientModule::ClientModule(security::v2::KeyChain& keyChain)
39 : m_keyChain(keyChain)
Zhiyi Zhang23564c82017-03-01 10:22:22 -080040{
41}
42
Davide Pesavento08994782018-01-22 12:13:41 -050043ClientModule::~ClientModule() = default;
44
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070045shared_ptr<Interest>
46ClientModule::generateProbeInfoInterest(const Name& caName)
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080047{
48 Name interestName = caName;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070049 if (readString(caName.at(-1)) != "CA")
50 interestName.append("CA");
51 interestName.append("_PROBE").append("INFO");
52 auto interest = make_shared<Interest>(interestName);
53 interest->setMustBeFresh(true);
54 interest->setCanBePrefix(false);
55 return interest;
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080056}
57
Zhiyi Zhangcaab5462019-10-18 13:41:02 -070058bool
59ClientModule::verifyProbeInfoResponse(const Data& reply)
60{
61 // parse the ca item
62 auto contentJson = getJsonFromData(reply);
63 auto caItem = ClientConfig::extractCaItem(contentJson);
64
65 // verify the probe Data's sig
66 if (!security::verifySignature(reply, caItem.m_anchor)) {
67 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
68 return false;
69 }
70 return true;
71}
72
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080073void
Zhiyi Zhangcaab5462019-10-18 13:41:02 -070074ClientModule::addCaFromProbeInfoResponse(const Data& reply)
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080075{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070076 // parse the ca item
77 auto contentJson = getJsonFromData(reply);
78 auto caItem = ClientConfig::extractCaItem(contentJson);
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080079
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070080 // update the local config
81 bool findItem = false;
82 for (auto& item : m_config.m_caItems) {
83 if (item.m_caName == caItem.m_caName) {
84 findItem = true;
85 item = caItem;
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080086 }
87 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070088 if (!findItem) {
89 m_config.m_caItems.push_back(caItem);
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080090 }
Zhiyi Zhang23564c82017-03-01 10:22:22 -080091}
92
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070093shared_ptr<Interest>
94ClientModule::generateProbeInterest(const ClientCaItem& ca, const std::string& probeInfo)
Zhiyi Zhang23564c82017-03-01 10:22:22 -080095{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070096 Name interestName = ca.m_caName;
97 interestName.append("CA").append("_PROBE");
98 auto interest = make_shared<Interest>(interestName);
99 interest->setMustBeFresh(true);
100 interest->setCanBePrefix(false);
Yufeng Zhang424d0362019-06-12 16:48:27 -0700101 auto paramJson = genProbeRequestJson(ca, probeInfo);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700102 interest->setApplicationParameters(paramFromJson(paramJson));
103
104 // update local state
105 m_ca = ca;
106 return interest;
107}
108
109void
110ClientModule::onProbeResponse(const Data& reply)
111{
112 if (!security::verifySignature(reply, m_ca.m_anchor)) {
113 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
114 return;
115 }
116 auto contentJson = getJsonFromData(reply);
117
118 // read the available name and put it into the state
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800119 auto nameUri = contentJson.get(JSON_CA_NAME, "");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700120 if (nameUri != "") {
121 m_identityName = Name(nameUri);
122 }
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700123 else {
124 NDN_LOG_TRACE("The JSON_CA_NAME is empty.");
125 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700126}
127
128shared_ptr<Interest>
129ClientModule::generateNewInterest(const time::system_clock::TimePoint& notBefore,
130 const time::system_clock::TimePoint& notAfter,
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700131 const Name& identityName, const shared_ptr<Data>& probeToken)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700132{
133 // Name requestedName = identityName;
134 if (!identityName.empty()) { // if identityName is not empty, find the corresponding CA
135 bool findCa = false;
136 for (const auto& caItem : m_config.m_caItems) {
137 if (caItem.m_caName.isPrefixOf(identityName)) {
138 m_ca = caItem;
139 findCa = true;
140 }
141 }
142 if (!findCa) { // if cannot find, cannot proceed
143 return nullptr;
144 }
145 m_identityName = identityName;
146 }
147 else { // if identityName is empty, check m_identityName or generate a random name
148 if (!m_identityName.empty()) {
149 // do nothing
150 }
151 else {
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700152 NDN_LOG_TRACE("Randomly create a new name because m_identityName is empty and the param is empty.");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700153 auto id = std::to_string(random::generateSecureWord64());
154 m_identityName = m_ca.m_caName;
155 m_identityName.append(id);
156 }
157 }
158
159 // generate a newly key pair or use an existing key
Zhiyi Zhang10130782018-02-01 18:28:49 -0800160 const auto& pib = m_keyChain.getPib();
Zhiyi Zhang10130782018-02-01 18:28:49 -0800161 try {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700162 auto identity = pib.getIdentity(m_identityName);
163 m_key = m_keyChain.createKey(identity);
Zhiyi Zhang10130782018-02-01 18:28:49 -0800164 }
165 catch (const security::Pib::Error& e) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700166 auto identity = m_keyChain.createIdentity(m_identityName);
167 m_key = identity.getDefaultKey();
Zhiyi Zhang10130782018-02-01 18:28:49 -0800168 }
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800169
170 // generate certificate request
171 security::v2::Certificate certRequest;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700172 certRequest.setName(Name(m_key.getName()).append("cert-request").appendVersion());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800173 certRequest.setContentType(tlv::ContentType_Key);
174 certRequest.setFreshnessPeriod(time::hours(24));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700175 certRequest.setContent(m_key.getPublicKey().data(), m_key.getPublicKey().size());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800176 SignatureInfo signatureInfo;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700177 signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
178 m_keyChain.sign(certRequest, signingByKey(m_key.getName()).setSignatureInfo(signatureInfo));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800179
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700180 // generate Interest packet
181 Name interestName = m_ca.m_caName;
182 interestName.append("CA").append("_NEW");
183 auto interest = make_shared<Interest>(interestName);
184 interest->setMustBeFresh(true);
185 interest->setCanBePrefix(false);
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700186 interest->setApplicationParameters(paramFromJson(genNewRequestJson(m_ecdh.getBase64PubKey(), certRequest, probeToken)));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800187
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700188 // sign the Interest packet
189 m_keyChain.sign(*interest, signingByKey(m_key.getName()));
190 return interest;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800191}
192
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700193std::list<std::string>
194ClientModule::onNewResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800195{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700196 if (!security::verifySignature(reply, m_ca.m_anchor)) {
197 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
198 return std::list<std::string>();
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800199 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700200 auto contentJson = getJsonFromData(reply);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800201
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700202 // ECDH
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800203 const auto& peerKeyBase64Str = contentJson.get(JSON_CA_ECDH, "");
204 const auto& saltStr = contentJson.get(JSON_CA_SALT, "");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700205 uint64_t saltInt = std::stoull(saltStr);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700206 m_ecdh.deriveSecret(peerKeyBase64Str);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800207
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700208 // HKDF
Zhiyi Zhang36706832019-07-04 21:33:03 -0700209 hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
210 (uint8_t*)&saltInt, sizeof(saltInt), m_aesKey, sizeof(m_aesKey));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800211
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700212 // update state
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800213 m_status = contentJson.get(JSON_CA_STATUS, 0);
214 m_requestId = contentJson.get(JSON_CA_REQUEST_ID, "");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700215
216 auto challengesJson = contentJson.get_child(JSON_CA_CHALLENGES);
217 m_challengeList.clear();
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800218 for (const auto& challengeJson : challengesJson) {
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800219 m_challengeList.push_back(challengeJson.second.get(JSON_CA_CHALLENGE_ID, ""));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800220 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700221 return m_challengeList;
222}
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800223
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700224shared_ptr<Interest>
225ClientModule::generateChallengeInterest(const JsonSection& paramJson)
226{
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800227 m_challengeType = paramJson.get(JSON_CLIENT_SELECTED_CHALLENGE, "");
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800228
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700229 Name interestName = m_ca.m_caName;
230 interestName.append("CA").append("_CHALLENGE").append(m_requestId);
231 auto interest = make_shared<Interest>(interestName);
232 interest->setMustBeFresh(true);
233 interest->setCanBePrefix(false);
234
235 // encrypt the Interest parameters
236 std::stringstream ss;
237 boost::property_tree::write_json(ss, paramJson);
238 auto payload = ss.str();
Zhiyi Zhang36706832019-07-04 21:33:03 -0700239 auto paramBlock = genEncBlock(tlv::ApplicationParameters, m_aesKey, sizeof(m_aesKey),
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700240 (const uint8_t*)payload.c_str(), payload.size());
241 interest->setApplicationParameters(paramBlock);
242
243 m_keyChain.sign(*interest, signingByKey(m_key.getName()));
244 return interest;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800245}
246
247void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700248ClientModule::onChallengeResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800249{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700250 if (!security::verifySignature(reply, m_ca.m_anchor)) {
251 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800252 return;
253 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700254 auto result = parseEncBlock(m_aesKey, sizeof(m_aesKey), reply.getContent());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700255 std::string payload((const char*)result.data(), result.size());
256 std::istringstream ss(payload);
257 JsonSection contentJson;
258 boost::property_tree::json_parser::read_json(ss, contentJson);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800259
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700260 // update state
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800261 m_status = contentJson.get(JSON_CA_STATUS, 0);
262 m_challengeStatus = contentJson.get(JSON_CHALLENGE_STATUS, "");
263 m_remainingTries = contentJson.get(JSON_CHALLENGE_REMAINING_TRIES, 0);
264 m_freshBefore = time::system_clock::now() + time::seconds(contentJson.get(JSON_CHALLENGE_REMAINING_TIME, 0));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700265}
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700266
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700267shared_ptr<Interest>
268ClientModule::generateDownloadInterest()
269{
270 Name interestName = m_ca.m_caName;
271 interestName.append("CA").append("_DOWNLOAD").append(m_requestId);
272 auto interest = make_shared<Interest>(interestName);
273 interest->setMustBeFresh(true);
274 interest->setCanBePrefix(false);
275 return interest;
276}
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700277
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700278shared_ptr<Interest>
279ClientModule::generateCertFetchInterest()
280{
281 Name interestName = m_identityName;
282 interestName.append("KEY").append(m_certId);
283 auto interest = make_shared<Interest>(interestName);
284 interest->setMustBeFresh(true);
285 interest->setCanBePrefix(false);
286 return interest;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800287}
288
289void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700290ClientModule::onDownloadResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800291{
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800292 try {
293 security::v2::Certificate cert(reply.getContent().blockFromValue());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700294 m_keyChain.addCertificate(m_key, cert);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800295 _LOG_TRACE("Got DOWNLOAD response and installed the cert " << cert.getName());
296 }
297 catch (const std::exception& e) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700298 _LOG_ERROR("Cannot add replied certificate into the keychain " << e.what());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800299 return;
300 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700301 m_isCertInstalled = true;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800302}
303
304void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700305ClientModule::onCertFetchResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800306{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700307 onDownloadResponse(reply);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800308}
309
310JsonSection
311ClientModule::getJsonFromData(const Data& data)
312{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700313 std::istringstream ss(encoding::readString(data.getContent()));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800314 JsonSection json;
315 boost::property_tree::json_parser::read_json(ss, json);
316 return json;
317}
318
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700319std::vector<std::string>
320ClientModule::parseProbeComponents(const std::string& probe)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700321{
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700322 std::vector<std::string> components;
Yufeng Zhang424d0362019-06-12 16:48:27 -0700323 std::string delimiter = ":";
324 size_t last = 0;
325 size_t next = 0;
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700326 while ((next = probe.find(delimiter, last)) != std::string::npos) {
327 components.push_back(probe.substr(last, next - last));
328 last = next + 1;
329 }
330 components.push_back(probe.substr(last));
331 return components;
332}
Yufeng Zhang424d0362019-06-12 16:48:27 -0700333
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700334const JsonSection
335ClientModule::genProbeRequestJson(const ClientCaItem& ca, const std::string& probeInfo)
336{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700337 JsonSection root;
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700338 std::vector<std::string> fields = parseProbeComponents(ca.m_probe);
339 std::vector<std::string> arguments = parseProbeComponents(probeInfo);;
Yufeng Zhang424d0362019-06-12 16:48:27 -0700340
341 if (arguments.size() != fields.size()) {
342 BOOST_THROW_EXCEPTION(Error("Error in genProbeRequestJson: argument list does not match field list in the config file."));
343 }
Yufeng Zhang424d0362019-06-12 16:48:27 -0700344 for (size_t i = 0; i < fields.size(); ++i) {
345 root.put(fields.at(i), arguments.at(i));
346 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700347 return root;
348}
349
350const JsonSection
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700351ClientModule::genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest,
352 const shared_ptr<Data>& probeToken)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700353{
354 JsonSection root;
355 std::stringstream ss;
356 try {
357 security::transform::bufferSource(certRequest.wireEncode().wire(), certRequest.wireEncode().size())
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800358 >> security::transform::base64Encode(false)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700359 >> security::transform::streamSink(ss);
360 }
361 catch (const security::transform::Error& e) {
362 _LOG_ERROR("Cannot convert self-signed cert into BASE64 string " << e.what());
363 return root;
364 }
365 root.put(JSON_CLIENT_ECDH, ecdhPub);
366 root.put(JSON_CLIENT_CERT_REQ, ss.str());
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700367 if (probeToken != nullptr) {
368 // clear the stringstream
369 ss.str("");
370 ss.clear();
371 // transform the probe data into a base64 string
372 try {
373 security::transform::bufferSource(probeToken->wireEncode().wire(), probeToken->wireEncode().size())
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800374 >> security::transform::base64Encode(false)
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700375 >> security::transform::streamSink(ss);
376 }
377 catch (const security::transform::Error& e) {
378 _LOG_ERROR("Cannot convert self-signed cert into BASE64 string " << e.what());
379 return root;
380 }
381 // add the token into the JSON
382 root.put("probe-token", ss.str());
383 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700384 return root;
385}
386
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800387Block
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700388ClientModule::paramFromJson(const JsonSection& json)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800389{
390 std::stringstream ss;
391 boost::property_tree::write_json(ss, json);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700392 return makeStringBlock(ndn::tlv::ApplicationParameters, ss.str());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800393}
394
395} // namespace ndncert
396} // namespace ndn