blob: 61732744650b9d2ac4b8913e4af13eac7a6f03b1 [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
115template<typename...A>
116void
117setNameComponent(Name& name, ssize_t index, const A& ...a)
118{
119 Name name2 = name.getPrefix(index);
120 name2.append(name::Component(a...));
121 name2.append(name.getSubName(index + 1));
122 name = name2;
123}
124
125BOOST_AUTO_TEST_SUITE(Security)
126BOOST_FIXTURE_TEST_SUITE(TestCommandInterestValidator, CommandInterestValidatorFixture)
127
128BOOST_AUTO_TEST_CASE(Normal)
129{
130 auto i1 = makeCommandInterest();
131 assertAccept(*i1);
132
133 advanceClocks(time::milliseconds(5));
134 auto i2 = makeCommandInterest();
135 assertAccept(*i2);
136
137 advanceClocks(time::seconds(2));
138 auto i3 = makeCommandInterest();
139 assertAccept(*i3);
140}
141
142BOOST_AUTO_TEST_CASE(DataPassthru)
143{
144 auto d1 = makeData("/data");
145 int nAccepts = 0;
146 validator->validate(*d1,
147 [&nAccepts] (const shared_ptr<const Data>&) { ++nAccepts; },
148 [] (const shared_ptr<const Data>&, const std::string& msg) {
149 BOOST_ERROR("validation request should succeed but fails with " << msg);
150 });
151 BOOST_CHECK_EQUAL(nAccepts, 1);
152}
153
154BOOST_AUTO_TEST_SUITE(Rejects)
155
156BOOST_AUTO_TEST_CASE(NameTooShort)
157{
158 auto i1 = makeInterest("/name/too/short");
159 assertReject(*i1, CommandInterestValidator::ErrorCode::NAME_TOO_SHORT);
160}
161
162BOOST_AUTO_TEST_CASE(BadTimestamp)
163{
164 auto i1 = makeCommandInterest();
165 Name n1 = i1->getName();
166 setNameComponent(n1, signed_interest::POS_TIMESTAMP, "not-timestamp");
167 i1->setName(n1);
168 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_TIMESTAMP);
169}
170
171BOOST_AUTO_TEST_CASE(BadSigInfo)
172{
173 auto i1 = makeCommandInterest();
174 Name n1 = i1->getName();
175 setNameComponent(n1, signed_interest::POS_SIG_INFO, "not-SignatureInfo");
176 i1->setName(n1);
177 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_SIG_INFO);
178}
179
180BOOST_AUTO_TEST_CASE(MissingKeyLocator)
181{
182 auto i1 = makeCommandInterest();
183 Name n1 = i1->getName();
184 SignatureInfo sigInfo;
185 setNameComponent(n1, signed_interest::POS_SIG_INFO,
186 sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
187 i1->setName(n1);
188 assertReject(*i1, CommandInterestValidator::ErrorCode::MISSING_KEY_LOCATOR);
189}
190
191BOOST_AUTO_TEST_CASE(BadKeyLocatorType)
192{
193 auto i1 = makeCommandInterest();
194 Name n1 = i1->getName();
195 KeyLocator kl;
196 kl.setKeyDigest(makeBinaryBlock(tlv::KeyDigest, "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD", 8));
197 SignatureInfo sigInfo;
198 sigInfo.setKeyLocator(kl);
199 setNameComponent(n1, signed_interest::POS_SIG_INFO,
200 sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
201 i1->setName(n1);
202 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_KEY_LOCATOR_TYPE);
203}
204
205BOOST_AUTO_TEST_CASE(BadCertName)
206{
207 auto i1 = makeCommandInterest();
208 Name n1 = i1->getName();
209 KeyLocator kl;
210 kl.setName("/bad/cert/name");
211 SignatureInfo sigInfo;
212 sigInfo.setKeyLocator(kl);
213 setNameComponent(n1, signed_interest::POS_SIG_INFO,
214 sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
215 i1->setName(n1);
216 assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_CERT_NAME);
217}
218
219BOOST_AUTO_TEST_CASE(InnerReject)
220{
221 inner->setResult(false);
222 auto i1 = makeCommandInterest();
223 assertReject(*i1, CommandInterestValidator::ErrorCode::NONE);
224}
225
226BOOST_AUTO_TEST_CASE(TimestampOutOfGracePositive)
227{
228 CommandInterestValidator::Options options;
229 options.gracePeriod = time::seconds(15);
230 initialize(options);
231
232 auto i1 = makeCommandInterest(); // signed at 0s
233 advanceClocks(time::seconds(16)); // verifying at +16s
234 assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
235
236 auto i2 = makeCommandInterest(); // signed at +16s
237 assertAccept(*i2); // verifying at +16s
238}
239
240BOOST_AUTO_TEST_CASE(TimestampOutOfGraceNegative)
241{
242 CommandInterestValidator::Options options;
243 options.gracePeriod = time::seconds(15);
244 initialize(options);
245
246 auto i1 = makeCommandInterest(); // signed at 0s
247 advanceClocks(time::seconds(1));
248 auto i2 = makeCommandInterest(); // signed at +1s
249 advanceClocks(time::seconds(1));
250 auto i3 = makeCommandInterest(); // signed at +2s
251
252 systemClock->advance(time::seconds(-18)); // verifying at -16s
253 assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
254
255 // CommandInterestValidator should not remember i1's timestamp
256 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
257
258 // CommandInterestValidator should not remember i2's timestamp, and should treat i3 as initial
259 advanceClocks(time::seconds(18)); // verifying at +2s
260 assertAccept(*i3);
261}
262
263BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
264{
265 auto i1 = makeCommandInterest(); // signed at 0s
266 assertAccept(*i1);
267
268 auto i2 = makeCommandInterest();
269 Name n1 = i1->getName();
270 Name n2 = i2->getName();
271 setNameComponent(n2, signed_interest::POS_TIMESTAMP,
272 n1[signed_interest::POS_TIMESTAMP]);
273 i2->setName(n2); // signed at 0s
274 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
275
276 advanceClocks(time::seconds(2));
277 auto i3 = makeCommandInterest(); // signed at +2s
278 assertAccept(*i3);
279}
280
281BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
282{
283 auto i2 = makeCommandInterest(); // signed at 0ms
284 advanceClocks(time::milliseconds(200));
285 auto i3 = makeCommandInterest(); // signed at +200ms
286 advanceClocks(time::milliseconds(900));
287 auto i1 = makeCommandInterest(); // signed at +1100ms
288 advanceClocks(time::milliseconds(300));
289 auto i4 = makeCommandInterest(); // signed at +1400ms
290
291 systemClock->advance(time::milliseconds(-300)); // verifying at +1100ms
292 assertAccept(*i1);
293
294 systemClock->advance(time::milliseconds(-1100)); // verifying at 0ms
295 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
296
297 // CommandInterestValidator should not remember i2's timestamp
298 advanceClocks(time::milliseconds(200)); // verifying at +200ms
299 assertReject(*i3, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
300
301 advanceClocks(time::milliseconds(1200)); // verifying at 1400ms
302 assertAccept(*i4);
303}
304
305BOOST_AUTO_TEST_SUITE_END() // Rejects
306
307BOOST_AUTO_TEST_SUITE(Options)
308
309typedef boost::mpl::vector<
310 boost::mpl::int_<0>,
311 boost::mpl::int_<-1>
312> GraceNonPositiveValues;
313
314BOOST_AUTO_TEST_CASE_TEMPLATE(GraceNonPositive, VALUE, GraceNonPositiveValues)
315{
316 CommandInterestValidator::Options options;
317 options.gracePeriod = time::seconds(VALUE::value);
318 initialize(options);
319
320 auto i1 = makeCommandInterest(1); // signed at 0ms
321 auto i2 = makeCommandInterest(2); // signed at 0ms
322 for (auto interest : {i1, i2}) {
323 Name name = interest->getName();
324 setNameComponent(name, signed_interest::POS_TIMESTAMP,
325 name::Component::fromNumber(time::toUnixTimestamp(time::system_clock::now()).count()));
326 interest->setName(name);
327 } // ensure timestamps are exactly 0ms
328
329 assertAccept(*i1); // verifying at 0ms
330
331 advanceClocks(time::milliseconds(1));
332 assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE); // verifying at 1ms
333}
334
335BOOST_AUTO_TEST_CASE(TimestampsLimited)
336{
337 CommandInterestValidator::Options options;
338 options.gracePeriod = time::seconds(15);
339 options.maxTimestamps = 3;
340 initialize(options);
341
342 auto i1 = makeCommandInterest(1);
343 auto i2 = makeCommandInterest(2);
344 auto i3 = makeCommandInterest(3);
345 auto i00 = makeCommandInterest(0); // signed at 0s
346 advanceClocks(time::seconds(1));
347 auto i01 = makeCommandInterest(0); // signed at 1s
348 advanceClocks(time::seconds(1));
349 auto i02 = makeCommandInterest(0); // signed at 2s
350
351 assertAccept(*i00);
352 assertAccept(*i02);
353 assertAccept(*i1);
354 assertAccept(*i2);
355 assertAccept(*i3); // forgets identity 0
356 assertAccept(*i01); // accepted despite timestamp is reordered, because record has been evicted
357}
358
359BOOST_AUTO_TEST_CASE(TimestampsUnlimited)
360{
361 CommandInterestValidator::Options options;
362 options.gracePeriod = time::seconds(15);
363 options.maxTimestamps = -1;
364 initialize(options);
365
366 auto i1 = makeCommandInterest(0); // signed at 0s
367 advanceClocks(time::seconds(1));
368 for (int identity = 0; identity < 20; ++identity) {
369 auto i2 = makeCommandInterest(identity); // signed at +1s
370 assertAccept(*i2);
371 }
372 assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
373}
374
375BOOST_AUTO_TEST_CASE(TimestampsDisabled)
376{
377 CommandInterestValidator::Options options;
378 options.gracePeriod = time::seconds(15);
379 options.maxTimestamps = 0;
380 initialize(options);
381
382 auto i1 = makeCommandInterest(); // signed at 0s
383 advanceClocks(time::seconds(1));
384 auto i2 = makeCommandInterest(); // signed at +1s
385 assertAccept(*i2);
386
387 assertAccept(*i1); // accepted despite timestamp is reordered, because record isn't kept
388}
389
390BOOST_AUTO_TEST_CASE(TtlLimited)
391{
392 CommandInterestValidator::Options options;
393 options.gracePeriod = time::seconds(120);
394 options.timestampTtl = time::seconds(300);
395 initialize(options);
396
397 auto i1 = makeCommandInterest(); // signed at 0s
398 advanceClocks(time::seconds(240));
399 auto i2 = makeCommandInterest(); // signed at +240s
400 advanceClocks(time::seconds(120));
401 auto i3 = makeCommandInterest(); // signed at +360s
402
403 systemClock->advance(time::seconds(-360)); // rewind system clock to 0s
404 assertAccept(*i1);
405 assertAccept(*i3);
406
407 advanceClocks(time::seconds(30), time::seconds(301)); // advance steady clock by 301s, and system clock to +301s
408 assertAccept(*i2); // accepted despite timestamp is reordered, because record has been expired
409}
410
411BOOST_AUTO_TEST_CASE(TtlZero)
412{
413 CommandInterestValidator::Options options;
414 options.gracePeriod = time::seconds(15);
415 options.timestampTtl = time::seconds::zero();
416 initialize(options);
417
418 auto i1 = makeCommandInterest(); // signed at 0s
419 advanceClocks(time::seconds(1));
420 auto i2 = makeCommandInterest(); // signed at +1s
421 assertAccept(*i2);
422
423 assertAccept(*i1); // accepted despite timestamp is reordered, because record has been expired
424}
425
426BOOST_AUTO_TEST_SUITE_END() // Options
427
428BOOST_AUTO_TEST_SUITE_END() // TestCommandInterestValidator
429BOOST_AUTO_TEST_SUITE_END() // Security
430
431} // namespace tests
432} // namespace security
433} // namespace ndn