blob: c41a47f0ad7b2c2173bd705b970150db5490eab5 [file] [log] [blame]
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
3 * Copyright (c) 2013-2017 Regents of the University of California.
4 *
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
26#include <boost/algorithm/string.hpp>
27#include <boost/filesystem.hpp>
28#include <boost/lexical_cast.hpp>
29#include <boost/property_tree/info_parser.hpp>
30
31namespace ndn {
32namespace security {
33namespace v2 {
34namespace validator_config {
35
36ValidationPolicyConfig::ValidationPolicyConfig()
37 : m_shouldBypass(false)
38 , m_isConfigured(false)
39{
40}
41
42void
43ValidationPolicyConfig::load(const std::string& filename)
44{
45 std::ifstream inputFile;
46 inputFile.open(filename.c_str());
47 if (!inputFile.good() || !inputFile.is_open()) {
48 std::string msg = "Failed to read configuration file: ";
49 msg += filename;
50 BOOST_THROW_EXCEPTION(Error(msg));
51 }
52 load(inputFile, filename);
53 inputFile.close();
54}
55
56void
57ValidationPolicyConfig::load(const std::string& input, const std::string& filename)
58{
59 std::istringstream inputStream(input);
60 load(inputStream, filename);
61}
62
63void
64ValidationPolicyConfig::load(std::istream& input, const std::string& filename)
65{
66 ConfigSection tree;
67 try {
68 boost::property_tree::read_info(input, tree);
69 }
70 catch (const boost::property_tree::info_parser_error& error) {
71 std::stringstream msg;
72 msg << "Failed to parse configuration file";
73 msg << " " << filename;
74 msg << " " << error.message() << " line " << error.line();
75 BOOST_THROW_EXCEPTION(Error(msg.str()));
76 }
77
78 load(tree, filename);
79}
80
81void
82ValidationPolicyConfig::load(const ConfigSection& configSection,
83 const std::string& filename)
84{
85 if (m_isConfigured) {
Alexander Afanasyev6aff0242017-08-29 17:14:44 -040086 m_shouldBypass = false;
87 m_dataRules.clear();
88 m_interestRules.clear();
89
90 m_validator->resetAnchors();
91 m_validator->resetVerifiedCertificates();
Alexander Afanasyeve5a19b82017-01-30 22:30:46 -080092 }
93 m_isConfigured = true;
94
95 BOOST_ASSERT(!filename.empty());
96
97 if (configSection.begin() == configSection.end()) {
98 std::string msg = "Error processing configuration file";
99 msg += ": ";
100 msg += filename;
101 msg += " no data";
102 BOOST_THROW_EXCEPTION(Error(msg));
103 }
104
105 for (const auto& subSection : configSection) {
106 const std::string& sectionName = subSection.first;
107 const ConfigSection& section = subSection.second;
108
109 if (boost::iequals(sectionName, "rule")) {
110 auto rule = Rule::create(section, filename);
111 if (rule->getPktType() == tlv::Data) {
112 m_dataRules.push_back(std::move(rule));
113 }
114 else if (rule->getPktType() == tlv::Interest) {
115 m_interestRules.push_back(std::move(rule));
116 }
117 }
118 else if (boost::iequals(sectionName, "trust-anchor")) {
119 processConfigTrustAnchor(section, filename);
120 }
121 else {
122 std::string msg = "Error processing configuration file";
123 msg += " ";
124 msg += filename;
125 msg += " unrecognized section: " + sectionName;
126 BOOST_THROW_EXCEPTION(Error(msg));
127 }
128 }
129}
130
131void
132ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection, const std::string& filename)
133{
134 using namespace boost::filesystem;
135
136 ConfigSection::const_iterator propertyIt = configSection.begin();
137
138 // Get trust-anchor.type
139 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type")) {
140 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.type>"));
141 }
142
143 std::string type = propertyIt->second.data();
144 propertyIt++;
145
146 if (boost::iequals(type, "file")) {
147 // Get trust-anchor.file
148 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "file-name")) {
149 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.file-name>"));
150 }
151
152 std::string file = propertyIt->second.data();
153 propertyIt++;
154
155 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
156 if (propertyIt != configSection.end()) {
157 BOOST_THROW_EXCEPTION(Error("Expect the end of trust-anchor!"));
158 }
159
160 m_validator->loadAnchor(filename, absolute(file, path(filename).parent_path()).string(),
161 refresh, false);
162 return;
163 }
164 else if (boost::iequals(type, "base64")) {
165 // Get trust-anchor.base64-string
166 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
167 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.base64-string>"));
168
169 std::stringstream ss(propertyIt->second.data());
170 propertyIt++;
171
172 // Check other stuff
173 if (propertyIt != configSection.end())
174 BOOST_THROW_EXCEPTION(Error("Expecting the end of trust-anchor"));
175
176 auto idCert = io::load<Certificate>(ss);
177 if (idCert != nullptr) {
178 m_validator->loadAnchor("", std::move(*idCert));
179 }
180 else {
181 BOOST_THROW_EXCEPTION(Error("Cannot decode certificate from base64-string"));
182 }
183
184 return;
185 }
186 else if (boost::iequals(type, "dir")) {
187 if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
188 BOOST_THROW_EXCEPTION(Error("Expect <trust-anchor.dir>"));
189
190 std::string dirString(propertyIt->second.data());
191 propertyIt++;
192
193 time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
194 if (propertyIt != configSection.end()) {
195 BOOST_THROW_EXCEPTION(Error("Expecting the end of trust-anchor"));
196 }
197
198 path dirPath = absolute(dirString, path(filename).parent_path());
199 m_validator->loadAnchor(dirString, dirPath.string(), refresh, true);
200 return;
201 }
202 else if (boost::iequals(type, "any")) {
203 m_shouldBypass = true;
204 }
205 else {
206 BOOST_THROW_EXCEPTION(Error("Unsupported trust-anchor.type: " + type));
207 }
208}
209
210time::nanoseconds
211ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
212 const ConfigSection::const_iterator& end)
213{
214 time::nanoseconds refresh = time::nanoseconds::max();
215 if (it == end) {
216 return refresh;
217 }
218
219 if (!boost::iequals(it->first, "refresh")) {
220 BOOST_THROW_EXCEPTION(Error("Expecting <trust-anchor.refresh>"));
221 }
222
223 std::string inputString = it->second.data();
224 ++it;
225
226 char unit = inputString[inputString.size() - 1];
227 std::string refreshString = inputString.substr(0, inputString.size() - 1);
228
229 uint32_t refreshPeriod = 0;
230
231 try {
232 refreshPeriod = boost::lexical_cast<uint32_t>(refreshString);
233 }
234 catch (const boost::bad_lexical_cast&) {
235 BOOST_THROW_EXCEPTION(Error("Bad number: " + refreshString));
236 }
237
238 if (refreshPeriod == 0) {
239 return getDefaultRefreshPeriod();
240 }
241
242 switch (unit) {
243 case 'h':
244 return time::duration_cast<time::nanoseconds>(time::hours(refreshPeriod));
245 case 'm':
246 return time::duration_cast<time::nanoseconds>(time::minutes(refreshPeriod));
247 case 's':
248 return time::duration_cast<time::nanoseconds>(time::seconds(refreshPeriod));
249 default:
250 BOOST_THROW_EXCEPTION(Error(std::string("Wrong time unit: ") + unit));
251 }
252}
253
254time::nanoseconds
255ValidationPolicyConfig::getDefaultRefreshPeriod()
256{
257 return time::duration_cast<time::nanoseconds>(time::seconds(3600));
258}
259
260void
261ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
262 const ValidationContinuation& continueValidation)
263{
264 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
265
266 if (m_shouldBypass) {
267 return continueValidation(nullptr, state);
268 }
269
270 Name klName = getKeyLocatorName(data, *state);
271 if (!state->getOutcome()) { // already failed
272 return;
273 }
274
275 for (const auto& rule : m_dataRules) {
276 if (rule->match(tlv::Data, data.getName())) {
277 if (rule->check(tlv::Data, data.getName(), klName, state)) {
278 return continueValidation(make_shared<CertificateRequest>(Interest(klName)), state);
279 }
280 // rule->check calls state->fail(...) if the check fails
281 return;
282 }
283 }
284
285 return state->fail({ValidationError::POLICY_ERROR, "No rule matched for data `" + data.getName().toUri() + "`"});
286}
287
288void
289ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
290 const ValidationContinuation& continueValidation)
291{
292 BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
293
294 if (m_shouldBypass) {
295 return continueValidation(nullptr, state);
296 }
297
298 Name klName = getKeyLocatorName(interest, *state);
299 if (!state->getOutcome()) { // already failed
300 return;
301 }
302
303 for (const auto& rule : m_interestRules) {
304 if (rule->match(tlv::Interest, interest.getName())) {
305 if (rule->check(tlv::Interest, interest.getName(), klName, state)) {
306 return continueValidation(make_shared<CertificateRequest>(Interest(klName)), state);
307 }
308 // rule->check calls state->fail(...) if the check fails
309 return;
310 }
311 }
312
313 return state->fail({ValidationError::POLICY_ERROR, "No rule matched for interest `" + interest.getName().toUri() + "`"});
314}
315
316} // namespace validator_config
317} // namespace v2
318} // namespace security
319} // namespace ndn