blob: 763bef126460c6e255a3df90e2fd70dbe749fe6d [file] [log] [blame]
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
Davide Pesavento0f830802018-01-16 23:58:58 -05003 * Copyright (c) 2013-2018 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
22#include "validation-policy-config.hpp"
23#include "validator.hpp"
24#include "../../util/io.hpp"
25
Davide Pesavento5df42a82018-03-08 20:06:51 -050026#include <boost/algorithm/string/predicate.hpp>
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080027#include <boost/filesystem.hpp>
28#include <boost/lexical_cast.hpp>
29#include <boost/property_tree/info_parser.hpp>
30
Davide Pesavento5df42a82018-03-08 20:06:51 -050031#include <fstream>
32
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080033namespace ndn {
34namespace security {
35namespace v2 {
36namespace validator_config {
37
38ValidationPolicyConfig::ValidationPolicyConfig()
39 : m_shouldBypass(false)
40 , m_isConfigured(false)
41{
42}
43
44void
45ValidationPolicyConfig::load(const std::string& filename)
46{
Davide Pesavento5df42a82018-03-08 20:06:51 -050047 std::ifstream inputFile(filename);
48 if (!inputFile) {
49 BOOST_THROW_EXCEPTION(Error("Failed to read configuration file: " + filename));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080050 }
51 load(inputFile, filename);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080052}
53
54void
55ValidationPolicyConfig::load(const std::string& input, const std::string& filename)
56{
57 std::istringstream inputStream(input);
58 load(inputStream, filename);
59}
60
61void
62ValidationPolicyConfig::load(std::istream& input, const std::string& filename)
63{
64 ConfigSection tree;
65 try {
66 boost::property_tree::read_info(input, tree);
67 }
Davide Pesavento5df42a82018-03-08 20:06:51 -050068 catch (const boost::property_tree::info_parser_error& e) {
69 BOOST_THROW_EXCEPTION(Error("Failed to parse configuration file " + filename +
70 " line " + to_string(e.line()) + ": " + e.message()));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080071 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080072 load(tree, filename);
73}
74
75void
Davide Pesavento5df42a82018-03-08 20:06:51 -050076ValidationPolicyConfig::load(const ConfigSection& configSection, const std::string& filename)
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080077{
Alexander Afanasyev7b112462018-10-17 11:51:52 -040078 if (m_validator == nullptr) {
79 BOOST_THROW_EXCEPTION(Error("Validator instance not assigned on the policy"));
80 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080081 if (m_isConfigured) {
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040082 m_shouldBypass = false;
83 m_dataRules.clear();
84 m_interestRules.clear();
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040085 m_validator->resetAnchors();
86 m_validator->resetVerifiedCertificates();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080087 }
88 m_isConfigured = true;
89
90 BOOST_ASSERT(!filename.empty());
91
92 if (configSection.begin() == configSection.end()) {
Davide Pesavento5df42a82018-03-08 20:06:51 -050093 BOOST_THROW_EXCEPTION(Error("Error processing configuration file " + filename + ": no data"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080094 }
95
96 for (const auto& subSection : configSection) {
97 const std::string& sectionName = subSection.first;
98 const ConfigSection& section = subSection.second;
99
100 if (boost::iequals(sectionName, "rule")) {
101 auto rule = Rule::create(section, filename);
102 if (rule->getPktType() == tlv::Data) {
103 m_dataRules.push_back(std::move(rule));
104 }
105 else if (rule->getPktType() == tlv::Interest) {
106 m_interestRules.push_back(std::move(rule));
107 }
108 }
109 else if (boost::iequals(sectionName, "trust-anchor")) {
110 processConfigTrustAnchor(section, filename);
111 }
112 else {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500113 BOOST_THROW_EXCEPTION(Error("Error processing configuration file " + filename +
114 ": unrecognized section " + sectionName));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800115 }
116 }
117}
118
119void
Davide Pesavento5df42a82018-03-08 20:06:51 -0500120ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection,
121 const std::string& filename)
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800122{
123 using namespace boost::filesystem;
124
Davide Pesavento5df42a82018-03-08 20:06:51 -0500125 auto propertyIt = configSection.begin();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800126
127 // Get trust-anchor.type
128 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type")) {
129 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.type>"));
130 }
131
132 std::string type = propertyIt->second.data();
133 propertyIt++;
134
135 if (boost::iequals(type, "file")) {
136 // Get trust-anchor.file
137 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "file-name")) {
138 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.file-name>"));
139 }
140
141 std::string file = propertyIt->second.data();
142 propertyIt++;
143
144 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
Davide Pesavento5df42a82018-03-08 20:06:51 -0500145 if (propertyIt != configSection.end())
146 BOOST_THROW_EXCEPTION(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800147
148 m_validator->loadAnchor(filename, absolute(file, path(filename).parent_path()).string(),
149 refresh, false);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800150 }
151 else if (boost::iequals(type, "base64")) {
152 // Get trust-anchor.base64-string
153 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
154 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.base64-string>"));
155
156 std::stringstream ss(propertyIt->second.data());
157 propertyIt++;
158
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800159 if (propertyIt != configSection.end())
Davide Pesavento5df42a82018-03-08 20:06:51 -0500160 BOOST_THROW_EXCEPTION(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800161
162 auto idCert = io::load<Certificate>(ss);
163 if (idCert != nullptr) {
164 m_validator->loadAnchor("", std::move(*idCert));
165 }
166 else {
167 BOOST_THROW_EXCEPTION(Error("Cannot decode certificate from base64-string"));
168 }
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800169 }
170 else if (boost::iequals(type, "dir")) {
171 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
Davide Pesavento5df42a82018-03-08 20:06:51 -0500172 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.dir>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800173
174 std::string dirString(propertyIt->second.data());
175 propertyIt++;
176
177 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
Davide Pesavento5df42a82018-03-08 20:06:51 -0500178 if (propertyIt != configSection.end())
179 BOOST_THROW_EXCEPTION(Error("Expecting end of <trust-anchor>"));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800180
181 path dirPath = absolute(dirString, path(filename).parent_path());
182 m_validator->loadAnchor(dirString, dirPath.string(), refresh, true);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800183 }
184 else if (boost::iequals(type, "any")) {
185 m_shouldBypass = true;
186 }
187 else {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500188 BOOST_THROW_EXCEPTION(Error("Unrecognized <trust-anchor.type>: " + type));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800189 }
190}
191
192time::nanoseconds
193ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
194 const ConfigSection::const_iterator& end)
195{
Davide Pesavento5df42a82018-03-08 20:06:51 -0500196 auto refresh = time::nanoseconds::max();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800197 if (it == end) {
198 return refresh;
199 }
200
201 if (!boost::iequals(it->first, "refresh")) {
202 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.refresh>"));
203 }
204
205 std::string inputString = it->second.data();
206 ++it;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800207 char unit = inputString[inputString.size() - 1];
208 std::string refreshString = inputString.substr(0, inputString.size() - 1);
209
Davide Pesavento5df42a82018-03-08 20:06:51 -0500210 int32_t refreshPeriod = -1;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800211 try {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500212 refreshPeriod = boost::lexical_cast<int32_t>(refreshString);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800213 }
214 catch (const boost::bad_lexical_cast&) {
Davide Pesavento5df42a82018-03-08 20:06:51 -0500215 // pass
216 }
217 if (refreshPeriod < 0) {
218 BOOST_THROW_EXCEPTION(Error("Bad refresh value: " + refreshString));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800219 }
220
221 if (refreshPeriod == 0) {
222 return getDefaultRefreshPeriod();
223 }
224
225 switch (unit) {
226 case 'h':
Davide Pesavento0f830802018-01-16 23:58:58 -0500227 return time::hours(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800228 case 'm':
Davide Pesavento0f830802018-01-16 23:58:58 -0500229 return time::minutes(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800230 case 's':
Davide Pesavento0f830802018-01-16 23:58:58 -0500231 return time::seconds(refreshPeriod);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800232 default:
Davide Pesaventodb4da5e2018-06-15 11:37:52 -0400233 BOOST_THROW_EXCEPTION(Error("Bad refresh time unit: "s + unit));
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800234 }
235}
236
237time::nanoseconds
238ValidationPolicyConfig::getDefaultRefreshPeriod()
239{
Davide Pesavento0f830802018-01-16 23:58:58 -0500240 return 1_h;
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800241}
242
243void
244ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
245 const ValidationContinuation& continueValidation)
246{
247 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
248
249 if (m_shouldBypass) {
250 return continueValidation(nullptr, state);
251 }
252
253 Name klName = getKeyLocatorName(data, *state);
254 if (!state->getOutcome()) { // already failed
255 return;
256 }
257
258 for (const auto& rule : m_dataRules) {
259 if (rule->match(tlv::Data, data.getName())) {
260 if (rule->check(tlv::Data, data.getName(), klName, state)) {
Junxiao Shib55e5d32018-07-18 13:32:00 -0600261 return continueValidation(make_shared<CertificateRequest>(klName), state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800262 }
263 // rule->check calls state->fail(...) if the check fails
264 return;
265 }
266 }
267
Davide Pesavento5df42a82018-03-08 20:06:51 -0500268 return state->fail({ValidationError::POLICY_ERROR,
269 "No rule matched for data `" + data.getName().toUri() + "`"});
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800270}
271
272void
273ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
274 const ValidationContinuation& continueValidation)
275{
276 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
277
278 if (m_shouldBypass) {
279 return continueValidation(nullptr, state);
280 }
281
282 Name klName = getKeyLocatorName(interest, *state);
283 if (!state->getOutcome()) { // already failed
284 return;
285 }
286
287 for (const auto& rule : m_interestRules) {
288 if (rule->match(tlv::Interest, interest.getName())) {
289 if (rule->check(tlv::Interest, interest.getName(), klName, state)) {
Junxiao Shib55e5d32018-07-18 13:32:00 -0600290 return continueValidation(make_shared<CertificateRequest>(klName), state);
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800291 }
292 // rule->check calls state->fail(...) if the check fails
293 return;
294 }
295 }
296
Davide Pesavento5df42a82018-03-08 20:06:51 -0500297 return state->fail({ValidationError::POLICY_ERROR,
298 "No rule matched for interest `" + interest.getName().toUri() + "`"});
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -0800299}
300
301} // namespace validator_config
302} // namespace v2
303} // namespace security
304} // namespace ndn