blob: 9a9d1e8c48e436975d961d7f2fe873195d2dec1d [file] [log] [blame]
Junxiao Shidc2d6d22016-08-04 14:30:23 +00001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3 * Copyright (c) 2013-2016 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 "security/command-interest-validator.hpp"
23#include "security/signing-helpers.hpp"
24#include <boost/lexical_cast.hpp>
25
26#include "boost-test.hpp"
27#include "../../dummy-validator.hpp"
28#include "../identity-management-time-fixture.hpp"
29#include "../make-interest-data.hpp"
30
31namespace ndn {
32namespace security {
33namespace tests {
34
35using namespace ndn::tests;
36
37class CommandInterestValidatorFixture : public IdentityManagementTimeFixture
38{
39protected:
40 CommandInterestValidatorFixture()
41 {
42 this->initialize(CommandInterestValidator::Options{});
43 }
44
45 void
46 initialize(const CommandInterestValidator::Options& options)
47 {
48 auto inner = make_unique<DummyValidator>();
49 this->inner = inner.get();
50 this->validator = make_unique<CommandInterestValidator>(std::move(inner), options);
51 }
52
53 Name
54 makeIdentity(int identity)
55 {
56 Name name("/localhost/CommandInterestValidatorIdentity");
57 name.appendSequenceNumber(identity);
58 BOOST_REQUIRE(m_keyChain.doesIdentityExist(name) || this->addIdentity(name));
59 return name;
60 }
61
62 shared_ptr<Interest>
63 makeCommandInterest(int identity = 0)
64 {
65 auto interest = makeInterest("/CommandInterestPrefix");
66 m_keyChain.sign(*interest, signingByIdentity(makeIdentity(identity)));
67 BOOST_TEST_MESSAGE("makeCommandInterest " << interest->getName());
68 return interest;
69 }
70
71 /** \brief check that validator accepts interest
72 * \param interest to be validated
73 */
74 void
75 assertAccept(const Interest& interest)
76 {
77 BOOST_TEST_MESSAGE("assertAccept " << interest.getName());
78 int nAccepts = 0;
79 validator->validate(interest,
80 [&nAccepts] (const shared_ptr<const Interest>&) { ++nAccepts; },
81 [] (const shared_ptr<const Interest>&, const std::string& msg) {
82 BOOST_ERROR("validation request should succeed but fails with: " << msg);
83 });
84 BOOST_CHECK_EQUAL(nAccepts, 1);
85 }
86
87 /** \brief check that validator rejects interest
88 * \param interest to be validated
89 * \param error if not NONE, further check the error code matches \p error
90 * if NONE, error code is not checked
91 */
92 void
93 assertReject(const Interest& interest, CommandInterestValidator::ErrorCode error)
94 {
95 BOOST_TEST_MESSAGE("assertReject " << interest.getName());
96 int nRejects = 0;
97 validator->validate(interest,
98 [] (const shared_ptr<const Interest>&) {
99 BOOST_ERROR("validation request should fail but succeeds");
100 },
101 [&nRejects, error] (const shared_ptr<const Interest>&, const std::string& msg) {
102 ++nRejects;
103 if (error != CommandInterestValidator::ErrorCode::NONE) {
104 BOOST_CHECK_EQUAL(msg, boost::lexical_cast<std::string>(error));
105 }
106 });
107 BOOST_CHECK_EQUAL(nRejects, 1);
108 }
109
110protected:
111 DummyValidator* inner;
112 unique_ptr<CommandInterestValidator> validator;
113};
114
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000115BOOST_AUTO_TEST_SUITE(Security)
116BOOST_FIXTURE_TEST_SUITE(TestCommandInterestValidator, CommandInterestValidatorFixture)
117
118BOOST_AUTO_TEST_CASE(Normal)
119{
120 auto i1 = makeCommandInterest();
121 assertAccept(*i1);
122
123 advanceClocks(time::milliseconds(5));
124 auto i2 = makeCommandInterest();
125 assertAccept(*i2);
126
127 advanceClocks(time::seconds(2));
128 auto i3 = makeCommandInterest();
129 assertAccept(*i3);
130}
131
132BOOST_AUTO_TEST_CASE(DataPassthru)
133{
134 auto d1 = makeData("/data");
135 int nAccepts = 0;
136 validator->validate(*d1,
137 [&nAccepts] (const shared_ptr<const Data>&) { ++nAccepts; },
138 [] (const shared_ptr<const Data>&, const std::string& msg) {
139 BOOST_ERROR("validation request should succeed but fails with " << msg);
140 });
141 BOOST_CHECK_EQUAL(nAccepts, 1);
142}
143
144BOOST_AUTO_TEST_SUITE(Rejects)
145
146BOOST_AUTO_TEST_CASE(NameTooShort)
147{
148 auto i1 = makeInterest("/name/too/short");
149 assertReject(*i1, CommandInterestValidator::ErrorCode::NAME_TOO_SHORT);
150}
151
152BOOST_AUTO_TEST_CASE(BadTimestamp)
153{
154 auto i1 = makeCommandInterest();
Junxiao Shi198c3812016-08-12 19:24:18 +0000155 setNameComponent(*i1, signed_interest::POS_TIMESTAMP, "not-timestamp");
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000156 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_TIMESTAMP);
157}
158
159BOOST_AUTO_TEST_CASE(BadSigInfo)
160{
161 auto i1 = makeCommandInterest();
Junxiao Shi198c3812016-08-12 19:24:18 +0000162 setNameComponent(*i1, signed_interest::POS_SIG_INFO, "not-SignatureInfo");
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000163 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_SIG_INFO);
164}
165
166BOOST_AUTO_TEST_CASE(MissingKeyLocator)
167{
168 auto i1 = makeCommandInterest();
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000169 SignatureInfo sigInfo;
Junxiao Shi198c3812016-08-12 19:24:18 +0000170 setNameComponent(*i1, signed_interest::POS_SIG_INFO,
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000171 sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000172 assertReject(*i1, CommandInterestValidator::ErrorCode::MISSING_KEY_LOCATOR);
173}
174
175BOOST_AUTO_TEST_CASE(BadKeyLocatorType)
176{
177 auto i1 = makeCommandInterest();
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000178 KeyLocator kl;
179 kl.setKeyDigest(makeBinaryBlock(tlv::KeyDigest, "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD", 8));
180 SignatureInfo sigInfo;
181 sigInfo.setKeyLocator(kl);
Junxiao Shi198c3812016-08-12 19:24:18 +0000182 setNameComponent(*i1, signed_interest::POS_SIG_INFO,
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000183 sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000184 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_KEY_LOCATOR_TYPE);
185}
186
187BOOST_AUTO_TEST_CASE(BadCertName)
188{
189 auto i1 = makeCommandInterest();
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000190 KeyLocator kl;
191 kl.setName("/bad/cert/name");
192 SignatureInfo sigInfo;
193 sigInfo.setKeyLocator(kl);
Junxiao Shi198c3812016-08-12 19:24:18 +0000194 setNameComponent(*i1, signed_interest::POS_SIG_INFO,
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000195 sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000196 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_CERT_NAME);
197}
198
199BOOST_AUTO_TEST_CASE(InnerReject)
200{
201 inner->setResult(false);
202 auto i1 = makeCommandInterest();
203 assertReject(*i1, CommandInterestValidator::ErrorCode::NONE);
204}
205
206BOOST_AUTO_TEST_CASE(TimestampOutOfGracePositive)
207{
208 CommandInterestValidator::Options options;
209 options.gracePeriod = time::seconds(15);
210 initialize(options);
211
212 auto i1 = makeCommandInterest(); // signed at 0s
213 advanceClocks(time::seconds(16)); // verifying at +16s
214 assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
215
216 auto i2 = makeCommandInterest(); // signed at +16s
217 assertAccept(*i2); // verifying at +16s
218}
219
220BOOST_AUTO_TEST_CASE(TimestampOutOfGraceNegative)
221{
222 CommandInterestValidator::Options options;
223 options.gracePeriod = time::seconds(15);
224 initialize(options);
225
226 auto i1 = makeCommandInterest(); // signed at 0s
227 advanceClocks(time::seconds(1));
228 auto i2 = makeCommandInterest(); // signed at +1s
229 advanceClocks(time::seconds(1));
230 auto i3 = makeCommandInterest(); // signed at +2s
231
232 systemClock->advance(time::seconds(-18)); // verifying at -16s
233 assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
234
235 // CommandInterestValidator should not remember i1's timestamp
236 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
237
238 // CommandInterestValidator should not remember i2's timestamp, and should treat i3 as initial
239 advanceClocks(time::seconds(18)); // verifying at +2s
240 assertAccept(*i3);
241}
242
243BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
244{
245 auto i1 = makeCommandInterest(); // signed at 0s
246 assertAccept(*i1);
247
Junxiao Shi198c3812016-08-12 19:24:18 +0000248 auto i2 = makeCommandInterest(); // signed at 0s
249 setNameComponent(*i2, signed_interest::POS_TIMESTAMP,
250 i1->getName()[signed_interest::POS_TIMESTAMP]);
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000251 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
252
253 advanceClocks(time::seconds(2));
254 auto i3 = makeCommandInterest(); // signed at +2s
255 assertAccept(*i3);
256}
257
258BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
259{
260 auto i2 = makeCommandInterest(); // signed at 0ms
261 advanceClocks(time::milliseconds(200));
262 auto i3 = makeCommandInterest(); // signed at +200ms
263 advanceClocks(time::milliseconds(900));
264 auto i1 = makeCommandInterest(); // signed at +1100ms
265 advanceClocks(time::milliseconds(300));
266 auto i4 = makeCommandInterest(); // signed at +1400ms
267
268 systemClock->advance(time::milliseconds(-300)); // verifying at +1100ms
269 assertAccept(*i1);
270
271 systemClock->advance(time::milliseconds(-1100)); // verifying at 0ms
272 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
273
274 // CommandInterestValidator should not remember i2's timestamp
275 advanceClocks(time::milliseconds(200)); // verifying at +200ms
276 assertReject(*i3, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
277
278 advanceClocks(time::milliseconds(1200)); // verifying at 1400ms
279 assertAccept(*i4);
280}
281
282BOOST_AUTO_TEST_SUITE_END() // Rejects
283
284BOOST_AUTO_TEST_SUITE(Options)
285
286typedef boost::mpl::vector<
287 boost::mpl::int_<0>,
288 boost::mpl::int_<-1>
289> GraceNonPositiveValues;
290
291BOOST_AUTO_TEST_CASE_TEMPLATE(GraceNonPositive, VALUE, GraceNonPositiveValues)
292{
293 CommandInterestValidator::Options options;
294 options.gracePeriod = time::seconds(VALUE::value);
295 initialize(options);
296
297 auto i1 = makeCommandInterest(1); // signed at 0ms
298 auto i2 = makeCommandInterest(2); // signed at 0ms
299 for (auto interest : {i1, i2}) {
Junxiao Shi198c3812016-08-12 19:24:18 +0000300 setNameComponent(*interest, signed_interest::POS_TIMESTAMP,
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000301 name::Component::fromNumber(time::toUnixTimestamp(time::system_clock::now()).count()));
Junxiao Shidc2d6d22016-08-04 14:30:23 +0000302 } // ensure timestamps are exactly 0ms
303
304 assertAccept(*i1); // verifying at 0ms
305
306 advanceClocks(time::milliseconds(1));
307 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE); // verifying at 1ms
308}
309
310BOOST_AUTO_TEST_CASE(TimestampsLimited)
311{
312 CommandInterestValidator::Options options;
313 options.gracePeriod = time::seconds(15);
314 options.maxTimestamps = 3;
315 initialize(options);
316
317 auto i1 = makeCommandInterest(1);
318 auto i2 = makeCommandInterest(2);
319 auto i3 = makeCommandInterest(3);
320 auto i00 = makeCommandInterest(0); // signed at 0s
321 advanceClocks(time::seconds(1));
322 auto i01 = makeCommandInterest(0); // signed at 1s
323 advanceClocks(time::seconds(1));
324 auto i02 = makeCommandInterest(0); // signed at 2s
325
326 assertAccept(*i00);
327 assertAccept(*i02);
328 assertAccept(*i1);
329 assertAccept(*i2);
330 assertAccept(*i3); // forgets identity 0
331 assertAccept(*i01); // accepted despite timestamp is reordered, because record has been evicted
332}
333
334BOOST_AUTO_TEST_CASE(TimestampsUnlimited)
335{
336 CommandInterestValidator::Options options;
337 options.gracePeriod = time::seconds(15);
338 options.maxTimestamps = -1;
339 initialize(options);
340
341 auto i1 = makeCommandInterest(0); // signed at 0s
342 advanceClocks(time::seconds(1));
343 for (int identity = 0; identity < 20; ++identity) {
344 auto i2 = makeCommandInterest(identity); // signed at +1s
345 assertAccept(*i2);
346 }
347 assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
348}
349
350BOOST_AUTO_TEST_CASE(TimestampsDisabled)
351{
352 CommandInterestValidator::Options options;
353 options.gracePeriod = time::seconds(15);
354 options.maxTimestamps = 0;
355 initialize(options);
356
357 auto i1 = makeCommandInterest(); // signed at 0s
358 advanceClocks(time::seconds(1));
359 auto i2 = makeCommandInterest(); // signed at +1s
360 assertAccept(*i2);
361
362 assertAccept(*i1); // accepted despite timestamp is reordered, because record isn't kept
363}
364
365BOOST_AUTO_TEST_CASE(TtlLimited)
366{
367 CommandInterestValidator::Options options;
368 options.gracePeriod = time::seconds(120);
369 options.timestampTtl = time::seconds(300);
370 initialize(options);
371
372 auto i1 = makeCommandInterest(); // signed at 0s
373 advanceClocks(time::seconds(240));
374 auto i2 = makeCommandInterest(); // signed at +240s
375 advanceClocks(time::seconds(120));
376 auto i3 = makeCommandInterest(); // signed at +360s
377
378 systemClock->advance(time::seconds(-360)); // rewind system clock to 0s
379 assertAccept(*i1);
380 assertAccept(*i3);
381
382 advanceClocks(time::seconds(30), time::seconds(301)); // advance steady clock by 301s, and system clock to +301s
383 assertAccept(*i2); // accepted despite timestamp is reordered, because record has been expired
384}
385
386BOOST_AUTO_TEST_CASE(TtlZero)
387{
388 CommandInterestValidator::Options options;
389 options.gracePeriod = time::seconds(15);
390 options.timestampTtl = time::seconds::zero();
391 initialize(options);
392
393 auto i1 = makeCommandInterest(); // signed at 0s
394 advanceClocks(time::seconds(1));
395 auto i2 = makeCommandInterest(); // signed at +1s
396 assertAccept(*i2);
397
398 assertAccept(*i1); // accepted despite timestamp is reordered, because record has been expired
399}
400
401BOOST_AUTO_TEST_SUITE_END() // Options
402
403BOOST_AUTO_TEST_SUITE_END() // TestCommandInterestValidator
404BOOST_AUTO_TEST_SUITE_END() // Security
405
406} // namespace tests
407} // namespace security
408} // namespace ndn