blob: 2222fc21c2ae40f0ac9dfee20050773b8671058d [file] [log] [blame]
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
Davide Pesavento51974f62024-12-21 20:42:45 -05003 * Copyright (c) 2013-2024 Regents of the University of California.
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -08004 *
5 * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
6 *
7 * ndn-cxx library is free software: you can redistribute it and/or modify it under the
8 * terms of the GNU Lesser General Public License as published by the Free Software
9 * Foundation, either version 3 of the License, or (at your option) any later version.
10 *
11 * ndn-cxx library 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 Lesser General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License and GNU Lesser
16 * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
17 * <http://www.gnu.org/licenses/>.
18 *
19 * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
20 */
21
Alexander Afanasyev09236c22020-06-03 13:42:38 -040022#include "ndn-cxx/security/validation-policy-config.hpp"
23#include "ndn-cxx/security/validator.hpp"
Davide Pesavento7e780642018-11-24 15:51:34 -050024#include "ndn-cxx/util/io.hpp"
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080025
Davide Pesavento5df42a82018-03-08 20:06:51 -050026#include <boost/algorithm/string/predicate.hpp>
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080027#include <boost/lexical_cast.hpp>
28#include <boost/property_tree/info_parser.hpp>
29
Davide Pesavento51974f62024-12-21 20:42:45 -050030#include <filesystem>
Davide Pesavento5df42a82018-03-08 20:06:51 -050031#include <fstream>
32
Davide Pesavento47ce2ee2023-05-09 01:33:33 -040033namespace ndn::security::validator_config {
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080034
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080035void
36ValidationPolicyConfig::load(const std::string& filename)
37{
Davide Pesavento5df42a82018-03-08 20:06:51 -050038 std::ifstream inputFile(filename);
39 if (!inputFile) {
Davide Pesavento923ba442019-02-12 22:00:38 -050040 NDN_THROW(Error("Failed to read configuration file: " + filename));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080041 }
42 load(inputFile, filename);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080043}
44
45void
46ValidationPolicyConfig::load(const std::string& input, const std::string& filename)
47{
48 std::istringstream inputStream(input);
49 load(inputStream, filename);
50}
51
52void
53ValidationPolicyConfig::load(std::istream& input, const std::string& filename)
54{
55 ConfigSection tree;
56 try {
57 boost::property_tree::read_info(input, tree);
58 }
Davide Pesavento5df42a82018-03-08 20:06:51 -050059 catch (const boost::property_tree::info_parser_error& e) {
Davide Pesavento923ba442019-02-12 22:00:38 -050060 NDN_THROW(Error("Failed to parse configuration file " + filename +
61 " line " + to_string(e.line()) + ": " + e.message()));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080062 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080063 load(tree, filename);
64}
65
66void
Davide Pesavento5df42a82018-03-08 20:06:51 -050067ValidationPolicyConfig::load(const ConfigSection& configSection, const std::string& filename)
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080068{
Davide Pesavento0a6456c2019-11-14 00:33:11 -050069 BOOST_ASSERT(!filename.empty());
70
Alexander Afanasyev7b112462018-10-17 11:51:52 -040071 if (m_validator == nullptr) {
Davide Pesavento923ba442019-02-12 22:00:38 -050072 NDN_THROW(Error("Validator instance not assigned on the policy"));
Alexander Afanasyev7b112462018-10-17 11:51:52 -040073 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080074 if (m_isConfigured) {
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040075 m_shouldBypass = false;
76 m_dataRules.clear();
77 m_interestRules.clear();
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040078 m_validator->resetAnchors();
79 m_validator->resetVerifiedCertificates();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080080 }
81 m_isConfigured = true;
82
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080083 for (const auto& subSection : configSection) {
84 const std::string& sectionName = subSection.first;
85 const ConfigSection& section = subSection.second;
86
87 if (boost::iequals(sectionName, "rule")) {
88 auto rule = Rule::create(section, filename);
89 if (rule->getPktType() == tlv::Data) {
90 m_dataRules.push_back(std::move(rule));
91 }
92 else if (rule->getPktType() == tlv::Interest) {
93 m_interestRules.push_back(std::move(rule));
94 }
95 }
96 else if (boost::iequals(sectionName, "trust-anchor")) {
97 processConfigTrustAnchor(section, filename);
98 }
99 else {
Davide Pesavento923ba442019-02-12 22:00:38 -0500100 NDN_THROW(Error("Error processing configuration file " + filename +
101 ": unrecognized section " + sectionName));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800102 }
103 }
104}
105
106void
Davide Pesavento5df42a82018-03-08 20:06:51 -0500107ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection,
108 const std::string& filename)
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800109{
Davide Pesavento5df42a82018-03-08 20:06:51 -0500110 auto propertyIt = configSection.begin();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800111
112 // Get trust-anchor.type
113 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type")) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500114 NDN_THROW(Error("Expecting <trust-anchor.type>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800115 }
116
Davide Pesavento51974f62024-12-21 20:42:45 -0500117 auto baseDir = std::filesystem::absolute(filename).parent_path().lexically_normal();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800118 std::string type = propertyIt->second.data();
119 propertyIt++;
120
121 if (boost::iequals(type, "file")) {
122 // Get trust-anchor.file
123 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "file-name")) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500124 NDN_THROW(Error("Expecting <trust-anchor.file-name>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800125 }
126
127 std::string file = propertyIt->second.data();
128 propertyIt++;
129
130 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
Davide Pesavento5df42a82018-03-08 20:06:51 -0500131 if (propertyIt != configSection.end())
Davide Pesavento923ba442019-02-12 22:00:38 -0500132 NDN_THROW(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800133
Davide Pesavento51974f62024-12-21 20:42:45 -0500134 m_validator->loadAnchor(file, baseDir / file, refresh, false);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800135 }
136 else if (boost::iequals(type, "base64")) {
137 // Get trust-anchor.base64-string
138 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
Davide Pesavento923ba442019-02-12 22:00:38 -0500139 NDN_THROW(Error("Expecting <trust-anchor.base64-string>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800140
141 std::stringstream ss(propertyIt->second.data());
142 propertyIt++;
143
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800144 if (propertyIt != configSection.end())
Davide Pesavento923ba442019-02-12 22:00:38 -0500145 NDN_THROW(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800146
147 auto idCert = io::load<Certificate>(ss);
148 if (idCert != nullptr) {
149 m_validator->loadAnchor("", std::move(*idCert));
150 }
151 else {
Davide Pesavento923ba442019-02-12 22:00:38 -0500152 NDN_THROW(Error("Cannot decode certificate from base64-string"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800153 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800154 }
155 else if (boost::iequals(type, "dir")) {
156 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
Davide Pesavento923ba442019-02-12 22:00:38 -0500157 NDN_THROW(Error("Expecting <trust-anchor.dir>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800158
159 std::string dirString(propertyIt->second.data());
160 propertyIt++;
161
162 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
Davide Pesavento5df42a82018-03-08 20:06:51 -0500163 if (propertyIt != configSection.end())
Davide Pesavento923ba442019-02-12 22:00:38 -0500164 NDN_THROW(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800165
Davide Pesavento51974f62024-12-21 20:42:45 -0500166 m_validator->loadAnchor(dirString, baseDir / dirString, refresh, true);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800167 }
168 else if (boost::iequals(type, "any")) {
169 m_shouldBypass = true;
170 }
171 else {
Davide Pesavento923ba442019-02-12 22:00:38 -0500172 NDN_THROW(Error("Unrecognized <trust-anchor.type>: " + type));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800173 }
174}
175
176time::nanoseconds
177ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
178 const ConfigSection::const_iterator& end)
179{
Davide Pesavento5df42a82018-03-08 20:06:51 -0500180 auto refresh = time::nanoseconds::max();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800181 if (it == end) {
182 return refresh;
183 }
184
185 if (!boost::iequals(it->first, "refresh")) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500186 NDN_THROW(Error("Expecting <trust-anchor.refresh>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800187 }
188
189 std::string inputString = it->second.data();
190 ++it;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800191 char unit = inputString[inputString.size() - 1];
192 std::string refreshString = inputString.substr(0, inputString.size() - 1);
193
Davide Pesavento5df42a82018-03-08 20:06:51 -0500194 int32_t refreshPeriod = -1;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800195 try {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500196 refreshPeriod = boost::lexical_cast<int32_t>(refreshString);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800197 }
198 catch (const boost::bad_lexical_cast&) {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500199 // pass
200 }
201 if (refreshPeriod < 0) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500202 NDN_THROW(Error("Bad refresh value: " + refreshString));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800203 }
204
205 if (refreshPeriod == 0) {
206 return getDefaultRefreshPeriod();
207 }
208
209 switch (unit) {
210 case 'h':
Davide Pesavento0f830802018-01-16 23:58:58 -0500211 return time::hours(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800212 case 'm':
Davide Pesavento0f830802018-01-16 23:58:58 -0500213 return time::minutes(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800214 case 's':
Davide Pesavento0f830802018-01-16 23:58:58 -0500215 return time::seconds(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800216 default:
Davide Pesavento923ba442019-02-12 22:00:38 -0500217 NDN_THROW(Error("Bad refresh time unit: "s + unit));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800218 }
219}
220
221time::nanoseconds
222ValidationPolicyConfig::getDefaultRefreshPeriod()
223{
Davide Pesavento0f830802018-01-16 23:58:58 -0500224 return 1_h;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800225}
226
227void
228ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
229 const ValidationContinuation& continueValidation)
230{
231 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
232
233 if (m_shouldBypass) {
234 return continueValidation(nullptr, state);
235 }
236
Davide Pesavento2acce252022-09-08 22:03:03 -0400237 Name klName = getKeyLocatorName(data.getSignatureInfo(), *state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800238 if (!state->getOutcome()) { // already failed
239 return;
240 }
241
Davide Pesavento2acce252022-09-08 22:03:03 -0400242 auto sigType = tlv::SignatureTypeValue(data.getSignatureType());
243
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800244 for (const auto& rule : m_dataRules) {
Eric Newberry17d7c472020-06-18 21:29:22 -0700245 if (rule->match(tlv::Data, data.getName(), state)) {
Davide Pesavento2acce252022-09-08 22:03:03 -0400246 if (rule->check(tlv::Data, sigType, data.getName(), klName, state)) {
Junxiao Shib55e5d32018-07-18 13:32:00 -0600247 return continueValidation(make_shared<CertificateRequest>(klName), state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800248 }
249 // rule->check calls state->fail(...) if the check fails
250 return;
251 }
252 }
253
Davide Pesavento5df42a82018-03-08 20:06:51 -0500254 return state->fail({ValidationError::POLICY_ERROR,
255 "No rule matched for data `" + data.getName().toUri() + "`"});
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800256}
257
258void
259ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
260 const ValidationContinuation& continueValidation)
261{
262 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
263
264 if (m_shouldBypass) {
265 return continueValidation(nullptr, state);
266 }
267
Davide Pesavento2acce252022-09-08 22:03:03 -0400268 auto sigInfo = getSignatureInfo(interest, *state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800269 if (!state->getOutcome()) { // already failed
270 return;
271 }
272
Davide Pesavento2acce252022-09-08 22:03:03 -0400273 Name klName = getKeyLocatorName(sigInfo, *state);
274 if (!state->getOutcome()) { // already failed
275 return;
276 }
277
278 auto sigType = tlv::SignatureTypeValue(sigInfo.getSignatureType());
279
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800280 for (const auto& rule : m_interestRules) {
Eric Newberry17d7c472020-06-18 21:29:22 -0700281 if (rule->match(tlv::Interest, interest.getName(), state)) {
Alexander Afanasyev17d4b932021-03-17 17:58:40 -0400282 if (rule->check(tlv::Interest, sigType, interest.getName(), klName, state)) {
Junxiao Shib55e5d32018-07-18 13:32:00 -0600283 return continueValidation(make_shared<CertificateRequest>(klName), state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800284 }
285 // rule->check calls state->fail(...) if the check fails
286 return;
287 }
288 }
289
Davide Pesavento5df42a82018-03-08 20:06:51 -0500290 return state->fail({ValidationError::POLICY_ERROR,
291 "No rule matched for interest `" + interest.getName().toUri() + "`"});
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800292}
293
Davide Pesavento47ce2ee2023-05-09 01:33:33 -0400294} // namespace ndn::security::validator_config