blob: 29d0a86c2de7c1aa762cf99e66eea8a825230722 [file] [log] [blame]
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
Davide Pesavento47ce2ee2023-05-09 01:33:33 -04003 * Copyright (c) 2013-2023 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>
Davide Pesaventod8e0cad2021-05-26 21:43:47 -040027#include <boost/filesystem/operations.hpp>
28#include <boost/filesystem/path.hpp>
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080029#include <boost/lexical_cast.hpp>
30#include <boost/property_tree/info_parser.hpp>
31
Davide Pesavento5df42a82018-03-08 20:06:51 -050032#include <fstream>
33
Davide Pesavento47ce2ee2023-05-09 01:33:33 -040034namespace ndn::security::validator_config {
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080035
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080036void
37ValidationPolicyConfig::load(const std::string& filename)
38{
Davide Pesavento5df42a82018-03-08 20:06:51 -050039 std::ifstream inputFile(filename);
40 if (!inputFile) {
Davide Pesavento923ba442019-02-12 22:00:38 -050041 NDN_THROW(Error("Failed to read configuration file: " + filename));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080042 }
43 load(inputFile, filename);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080044}
45
46void
47ValidationPolicyConfig::load(const std::string& input, const std::string& filename)
48{
49 std::istringstream inputStream(input);
50 load(inputStream, filename);
51}
52
53void
54ValidationPolicyConfig::load(std::istream& input, const std::string& filename)
55{
56 ConfigSection tree;
57 try {
58 boost::property_tree::read_info(input, tree);
59 }
Davide Pesavento5df42a82018-03-08 20:06:51 -050060 catch (const boost::property_tree::info_parser_error& e) {
Davide Pesavento923ba442019-02-12 22:00:38 -050061 NDN_THROW(Error("Failed to parse configuration file " + filename +
62 " line " + to_string(e.line()) + ": " + e.message()));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080063 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080064 load(tree, filename);
65}
66
67void
Davide Pesavento5df42a82018-03-08 20:06:51 -050068ValidationPolicyConfig::load(const ConfigSection& configSection, const std::string& filename)
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080069{
Davide Pesavento0a6456c2019-11-14 00:33:11 -050070 BOOST_ASSERT(!filename.empty());
71
Alexander Afanasyev7b112462018-10-17 11:51:52 -040072 if (m_validator == nullptr) {
Davide Pesavento923ba442019-02-12 22:00:38 -050073 NDN_THROW(Error("Validator instance not assigned on the policy"));
Alexander Afanasyev7b112462018-10-17 11:51:52 -040074 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080075 if (m_isConfigured) {
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040076 m_shouldBypass = false;
77 m_dataRules.clear();
78 m_interestRules.clear();
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040079 m_validator->resetAnchors();
80 m_validator->resetVerifiedCertificates();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080081 }
82 m_isConfigured = true;
83
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080084 for (const auto& subSection : configSection) {
85 const std::string& sectionName = subSection.first;
86 const ConfigSection& section = subSection.second;
87
88 if (boost::iequals(sectionName, "rule")) {
89 auto rule = Rule::create(section, filename);
90 if (rule->getPktType() == tlv::Data) {
91 m_dataRules.push_back(std::move(rule));
92 }
93 else if (rule->getPktType() == tlv::Interest) {
94 m_interestRules.push_back(std::move(rule));
95 }
96 }
97 else if (boost::iequals(sectionName, "trust-anchor")) {
98 processConfigTrustAnchor(section, filename);
99 }
100 else {
Davide Pesavento923ba442019-02-12 22:00:38 -0500101 NDN_THROW(Error("Error processing configuration file " + filename +
102 ": unrecognized section " + sectionName));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800103 }
104 }
105}
106
107void
Davide Pesavento5df42a82018-03-08 20:06:51 -0500108ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection,
109 const std::string& filename)
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800110{
111 using namespace boost::filesystem;
112
Davide Pesavento5df42a82018-03-08 20:06:51 -0500113 auto propertyIt = configSection.begin();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800114
115 // Get trust-anchor.type
116 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type")) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500117 NDN_THROW(Error("Expecting <trust-anchor.type>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800118 }
119
120 std::string type = propertyIt->second.data();
121 propertyIt++;
122
123 if (boost::iequals(type, "file")) {
124 // Get trust-anchor.file
125 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "file-name")) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500126 NDN_THROW(Error("Expecting <trust-anchor.file-name>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800127 }
128
129 std::string file = propertyIt->second.data();
130 propertyIt++;
131
132 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
Davide Pesavento5df42a82018-03-08 20:06:51 -0500133 if (propertyIt != configSection.end())
Davide Pesavento923ba442019-02-12 22:00:38 -0500134 NDN_THROW(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800135
Varun Patil574c3f92020-11-25 09:07:33 +0530136 m_validator->loadAnchor(file, absolute(file, path(filename).parent_path()).string(),
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800137 refresh, false);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800138 }
139 else if (boost::iequals(type, "base64")) {
140 // Get trust-anchor.base64-string
141 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
Davide Pesavento923ba442019-02-12 22:00:38 -0500142 NDN_THROW(Error("Expecting <trust-anchor.base64-string>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800143
144 std::stringstream ss(propertyIt->second.data());
145 propertyIt++;
146
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800147 if (propertyIt != configSection.end())
Davide Pesavento923ba442019-02-12 22:00:38 -0500148 NDN_THROW(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800149
150 auto idCert = io::load<Certificate>(ss);
151 if (idCert != nullptr) {
152 m_validator->loadAnchor("", std::move(*idCert));
153 }
154 else {
Davide Pesavento923ba442019-02-12 22:00:38 -0500155 NDN_THROW(Error("Cannot decode certificate from base64-string"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800156 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800157 }
158 else if (boost::iequals(type, "dir")) {
159 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
Davide Pesavento923ba442019-02-12 22:00:38 -0500160 NDN_THROW(Error("Expecting <trust-anchor.dir>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800161
162 std::string dirString(propertyIt->second.data());
163 propertyIt++;
164
165 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
Davide Pesavento5df42a82018-03-08 20:06:51 -0500166 if (propertyIt != configSection.end())
Davide Pesavento923ba442019-02-12 22:00:38 -0500167 NDN_THROW(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800168
169 path dirPath = absolute(dirString, path(filename).parent_path());
170 m_validator->loadAnchor(dirString, dirPath.string(), refresh, true);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800171 }
172 else if (boost::iequals(type, "any")) {
173 m_shouldBypass = true;
174 }
175 else {
Davide Pesavento923ba442019-02-12 22:00:38 -0500176 NDN_THROW(Error("Unrecognized <trust-anchor.type>: " + type));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800177 }
178}
179
180time::nanoseconds
181ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
182 const ConfigSection::const_iterator& end)
183{
Davide Pesavento5df42a82018-03-08 20:06:51 -0500184 auto refresh = time::nanoseconds::max();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800185 if (it == end) {
186 return refresh;
187 }
188
189 if (!boost::iequals(it->first, "refresh")) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500190 NDN_THROW(Error("Expecting <trust-anchor.refresh>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800191 }
192
193 std::string inputString = it->second.data();
194 ++it;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800195 char unit = inputString[inputString.size() - 1];
196 std::string refreshString = inputString.substr(0, inputString.size() - 1);
197
Davide Pesavento5df42a82018-03-08 20:06:51 -0500198 int32_t refreshPeriod = -1;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800199 try {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500200 refreshPeriod = boost::lexical_cast<int32_t>(refreshString);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800201 }
202 catch (const boost::bad_lexical_cast&) {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500203 // pass
204 }
205 if (refreshPeriod < 0) {
Davide Pesavento923ba442019-02-12 22:00:38 -0500206 NDN_THROW(Error("Bad refresh value: " + refreshString));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800207 }
208
209 if (refreshPeriod == 0) {
210 return getDefaultRefreshPeriod();
211 }
212
213 switch (unit) {
214 case 'h':
Davide Pesavento0f830802018-01-16 23:58:58 -0500215 return time::hours(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800216 case 'm':
Davide Pesavento0f830802018-01-16 23:58:58 -0500217 return time::minutes(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800218 case 's':
Davide Pesavento0f830802018-01-16 23:58:58 -0500219 return time::seconds(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800220 default:
Davide Pesavento923ba442019-02-12 22:00:38 -0500221 NDN_THROW(Error("Bad refresh time unit: "s + unit));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800222 }
223}
224
225time::nanoseconds
226ValidationPolicyConfig::getDefaultRefreshPeriod()
227{
Davide Pesavento0f830802018-01-16 23:58:58 -0500228 return 1_h;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800229}
230
231void
232ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
233 const ValidationContinuation& continueValidation)
234{
235 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
236
237 if (m_shouldBypass) {
238 return continueValidation(nullptr, state);
239 }
240
Davide Pesavento2acce252022-09-08 22:03:03 -0400241 Name klName = getKeyLocatorName(data.getSignatureInfo(), *state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800242 if (!state->getOutcome()) { // already failed
243 return;
244 }
245
Davide Pesavento2acce252022-09-08 22:03:03 -0400246 auto sigType = tlv::SignatureTypeValue(data.getSignatureType());
247
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800248 for (const auto& rule : m_dataRules) {
Eric Newberry17d7c472020-06-18 21:29:22 -0700249 if (rule->match(tlv::Data, data.getName(), state)) {
Davide Pesavento2acce252022-09-08 22:03:03 -0400250 if (rule->check(tlv::Data, sigType, data.getName(), klName, state)) {
Junxiao Shib55e5d32018-07-18 13:32:00 -0600251 return continueValidation(make_shared<CertificateRequest>(klName), state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800252 }
253 // rule->check calls state->fail(...) if the check fails
254 return;
255 }
256 }
257
Davide Pesavento5df42a82018-03-08 20:06:51 -0500258 return state->fail({ValidationError::POLICY_ERROR,
259 "No rule matched for data `" + data.getName().toUri() + "`"});
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800260}
261
262void
263ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
264 const ValidationContinuation& continueValidation)
265{
266 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
267
268 if (m_shouldBypass) {
269 return continueValidation(nullptr, state);
270 }
271
Davide Pesavento2acce252022-09-08 22:03:03 -0400272 auto sigInfo = getSignatureInfo(interest, *state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800273 if (!state->getOutcome()) { // already failed
274 return;
275 }
276
Davide Pesavento2acce252022-09-08 22:03:03 -0400277 Name klName = getKeyLocatorName(sigInfo, *state);
278 if (!state->getOutcome()) { // already failed
279 return;
280 }
281
282 auto sigType = tlv::SignatureTypeValue(sigInfo.getSignatureType());
283
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800284 for (const auto& rule : m_interestRules) {
Eric Newberry17d7c472020-06-18 21:29:22 -0700285 if (rule->match(tlv::Interest, interest.getName(), state)) {
Alexander Afanasyev17d4b932021-03-17 17:58:40 -0400286 if (rule->check(tlv::Interest, sigType, interest.getName(), klName, state)) {
Junxiao Shib55e5d32018-07-18 13:32:00 -0600287 return continueValidation(make_shared<CertificateRequest>(klName), state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800288 }
289 // rule->check calls state->fail(...) if the check fails
290 return;
291 }
292 }
293
Davide Pesavento5df42a82018-03-08 20:06:51 -0500294 return state->fail({ValidationError::POLICY_ERROR,
295 "No rule matched for interest `" + interest.getName().toUri() + "`"});
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800296}
297
Davide Pesavento47ce2ee2023-05-09 01:33:33 -0400298} // namespace ndn::security::validator_config