blob: f46e5ad05ef3d76f4be15e69de62ebf32fb88585 [file] [log] [blame]
Eric Newberry1caa6342020-08-23 19:29:08 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
3 * Copyright (c) 2013-2020 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 "ndn-cxx/security/validation-policy-signed-interest.hpp"
23#include "ndn-cxx/security/interest-signer.hpp"
24#include "ndn-cxx/security/signing-helpers.hpp"
25#include "ndn-cxx/security/validation-policy-accept-all.hpp"
26#include "ndn-cxx/security/validation-policy-simple-hierarchy.hpp"
27
28#include "tests/boost-test.hpp"
29#include "tests/make-interest-data.hpp"
30#include "tests/unit/security/validator-fixture.hpp"
31
32#include <boost/lexical_cast.hpp>
33#include <boost/mpl/vector.hpp>
34
35namespace ndn {
36namespace security {
37inline namespace v2 {
38namespace tests {
39
40using namespace ndn::tests;
41
42BOOST_AUTO_TEST_SUITE(Security)
43
44class SignedInterestDefaultOptions
45{
46public:
47 static ValidationPolicySignedInterest::Options
48 getOptions()
49 {
50 return {};
51 }
52};
53
54template<class T, class InnerPolicy>
55class SignedInterestPolicyWrapper : public ValidationPolicySignedInterest
56{
57public:
58 SignedInterestPolicyWrapper()
59 : ValidationPolicySignedInterest(make_unique<InnerPolicy>(), T::getOptions())
60 {
61 }
62};
63
64template<class T, class InnerPolicy = ValidationPolicySimpleHierarchy>
65class ValidationPolicySignedInterestFixture
66 : public HierarchicalValidatorFixture<SignedInterestPolicyWrapper<T, InnerPolicy>>
67{
68public:
69 ValidationPolicySignedInterestFixture()
70 : m_signer(this->m_keyChain)
71 {
72 }
73
74 Interest
75 makeSignedInterest(const Identity& identity,
76 uint32_t signingFlags = InterestSigner::WantNonce | InterestSigner::WantTime)
77 {
78 Interest i(Name(identity.getName()).append("CMD"));
79 i.setCanBePrefix(false);
80 m_signer.makeSignedInterest(i, signingByIdentity(identity), signingFlags);
81 return i;
82 }
83
84public:
85 InterestSigner m_signer;
86
87 static constexpr uint32_t WantAll = InterestSigner::WantNonce |
88 InterestSigner::WantTime |
89 InterestSigner::WantSeqNum;
90};
91
92BOOST_FIXTURE_TEST_SUITE(TestValidationPolicySignedInterest,
93 ValidationPolicySignedInterestFixture<SignedInterestDefaultOptions>)
94
95BOOST_AUTO_TEST_CASE(BasicV3)
96{
97 auto i1 = makeSignedInterest(identity, WantAll);
98 VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
99 VALIDATE_FAILURE(i1, "Should fail (replay attack)");
100
101 advanceClocks(5_ms);
102 auto i2 = makeSignedInterest(identity, WantAll);
103 VALIDATE_SUCCESS(i2, "Should succeed (timestamp and sequence number larger than previous)");
104
105 Interest i3(Name(identity.getName()).append("CMD"));
106 i3.setCanBePrefix(false);
107 m_signer.makeSignedInterest(i3, signingWithSha256());
108 VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
109}
110
111BOOST_AUTO_TEST_CASE(DataPassthrough)
112{
113 Data d1("/Security/ValidatorFixture/Sub1");
114 m_keyChain.sign(d1);
115 VALIDATE_SUCCESS(d1, "Should succeed (fallback on inner validation policy for data)");
116}
117
118BOOST_AUTO_TEST_CASE(InnerPolicyReject)
119{
120 auto i1 = makeSignedInterest(otherIdentity);
121 VALIDATE_FAILURE(i1, "Should fail (inner policy should reject)");
122}
123
124class LimitedRecordsOptions
125{
126public:
127 static ValidationPolicySignedInterest::Options
128 getOptions()
129 {
130 ValidationPolicySignedInterest::Options options;
131 options.timestampGracePeriod = 15_s;
132 options.maxRecordCount = 3;
133 return options;
134 }
135};
136
137BOOST_FIXTURE_TEST_CASE(LimitedRecords, ValidationPolicySignedInterestFixture<LimitedRecordsOptions>)
138{
139 Identity id1 = addSubCertificate("/Security/ValidatorFixture/Sub1", identity);
140 cache.insert(id1.getDefaultKey().getDefaultCertificate());
141 Identity id2 = addSubCertificate("/Security/ValidatorFixture/Sub2", identity);
142 cache.insert(id2.getDefaultKey().getDefaultCertificate());
143 Identity id3 = addSubCertificate("/Security/ValidatorFixture/Sub3", identity);
144 cache.insert(id3.getDefaultKey().getDefaultCertificate());
145 Identity id4 = addSubCertificate("/Security/ValidatorFixture/Sub4", identity);
146 cache.insert(id4.getDefaultKey().getDefaultCertificate());
147
148 auto i1 = makeSignedInterest(id2);
149 auto i2 = makeSignedInterest(id3);
150 auto i3 = makeSignedInterest(id4);
151 auto i00 = makeSignedInterest(id1); // signed at 0s
152 advanceClocks(1_s);
153 auto i01 = makeSignedInterest(id1); // signed at 1s
154 advanceClocks(1_s);
155 auto i02 = makeSignedInterest(id1); // signed at 2s
156
157 VALIDATE_SUCCESS(i00, "Should succeed");
158 rewindClockAfterValidation();
159
160 VALIDATE_SUCCESS(i02, "Should succeed");
161 rewindClockAfterValidation();
162
163 VALIDATE_SUCCESS(i1, "Should succeed");
164 rewindClockAfterValidation();
165
166 VALIDATE_SUCCESS(i2, "Should succeed");
167 rewindClockAfterValidation();
168
169 VALIDATE_SUCCESS(i3, "Should succeed, forgets identity id1");
170 rewindClockAfterValidation();
171
172 VALIDATE_SUCCESS(i01, "Should succeed despite timestamp is reordered, because record has been evicted");
173}
174
175class UnlimitedRecordsOptions
176{
177public:
178 static ValidationPolicySignedInterest::Options
179 getOptions()
180 {
181 ValidationPolicySignedInterest::Options options;
182 options.timestampGracePeriod = 15_s;
183 options.maxRecordCount = -1;
184 return options;
185 }
186};
187
188BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicySignedInterestFixture<UnlimitedRecordsOptions>)
189{
190 std::vector<Identity> identities;
191 for (int i = 0; i < 20; ++i) {
192 Identity id = addSubCertificate("/Security/ValidatorFixture/Sub" + to_string(i), identity);
193 cache.insert(id.getDefaultKey().getDefaultCertificate());
194 identities.push_back(id);
195 }
196
197 auto i1 = makeSignedInterest(identities.at(0)); // signed at 0s
198 advanceClocks(1_s);
199 for (int i = 0; i < 20; ++i) {
200 auto i2 = makeSignedInterest(identities.at(i)); // signed at +1s
201
202 VALIDATE_SUCCESS(i2, "Should succeed");
203 rewindClockAfterValidation();
204 }
205 VALIDATE_FAILURE(i1, "Should fail (timestamp reorder)");
206}
207
208class ZeroRecordsOptions
209{
210public:
211 static ValidationPolicySignedInterest::Options
212 getOptions()
213 {
214 ValidationPolicySignedInterest::Options options;
215 options.timestampGracePeriod = 15_s;
216 options.maxRecordCount = 0;
217 return options;
218 }
219};
220
221BOOST_FIXTURE_TEST_CASE(ZeroRecords, ValidationPolicySignedInterestFixture<ZeroRecordsOptions>)
222{
223 auto i1 = makeSignedInterest(identity); // signed at 0s
224 advanceClocks(1_s);
225 auto i2 = makeSignedInterest(identity); // signed at +1s
226 VALIDATE_SUCCESS(i2, "Should succeed");
227 rewindClockAfterValidation();
228
229 VALIDATE_SUCCESS(i1, "Should succeed despite timestamp reordering, as records aren't kept");
230}
231
232BOOST_AUTO_TEST_SUITE(TimestampValidation)
233
234BOOST_AUTO_TEST_CASE(MissingTimestamp)
235{
236 auto i1 = makeSignedInterest(identity, InterestSigner::WantSeqNum);
237 VALIDATE_FAILURE(i1, "Should fail (timestamp missing)");
238}
239
240class DisabledTimestampValidationOptions
241{
242public:
243 static ValidationPolicySignedInterest::Options
244 getOptions()
245 {
246 ValidationPolicySignedInterest::Options options;
247 options.shouldValidateTimestamps = false;
248 return options;
249 }
250};
251
252BOOST_FIXTURE_TEST_CASE(Disabled,
253 ValidationPolicySignedInterestFixture<DisabledTimestampValidationOptions>)
254{
255 auto i1 = makeSignedInterest(identity); // signed at 0ms
256 advanceClocks(100_ms);
257 VALIDATE_SUCCESS(i1, "Should succeed");
258
259 auto i2 = makeSignedInterest(identity); // signed at +100ms
260 // Set i2 to have same timestamp as i1
261 auto si2 = i2.getSignatureInfo();
262 si2->setTime(i2.getSignatureInfo()->getTime());
263 i2.setSignatureInfo(*si2);
264 VALIDATE_SUCCESS(i2, "Should succeed");
265}
266
267class GracePeriod15Sec
268{
269public:
270 static ValidationPolicySignedInterest::Options
271 getOptions()
272 {
273 ValidationPolicySignedInterest::Options options;
274 options.timestampGracePeriod = 15_s;
275 return options;
276 }
277};
278
279BOOST_FIXTURE_TEST_CASE(TimestampTooOld, ValidationPolicySignedInterestFixture<GracePeriod15Sec>)
280{
281 auto i1 = makeSignedInterest(identity); // signed at 0s
282 advanceClocks(16_s); // verifying at +16s
283 VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
284 rewindClockAfterValidation();
285
286 auto i2 = makeSignedInterest(identity); // signed at +16s
287 VALIDATE_SUCCESS(i2, "Should succeed");
288}
289
290BOOST_FIXTURE_TEST_CASE(TimestampTooNew, ValidationPolicySignedInterestFixture<GracePeriod15Sec>)
291{
292 auto i1 = makeSignedInterest(identity); // signed at 0s
293 advanceClocks(1_s);
294 auto i2 = makeSignedInterest(identity); // signed at +1s
295 advanceClocks(1_s);
296 auto i3 = makeSignedInterest(identity); // signed at +2s
297
298 systemClock->advance(-18_s); // verifying at -16s
299 VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
300 rewindClockAfterValidation();
301
302 // SignedInterestValidator should not remember i1's timestamp
303 VALIDATE_FAILURE(i2, "Should fail (timestamp outside the grace period)");
304 rewindClockAfterValidation();
305
306 // SignedInterestValidator should not remember i2's timestamp, and should treat i3 as initial
307 advanceClocks(18_s); // verifying at +2s
308 VALIDATE_SUCCESS(i3, "Should succeed");
309}
310
311BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
312{
313 auto i1 = makeSignedInterest(identity); // signed at 0s
314 VALIDATE_SUCCESS(i1, "Should succeed");
315
316 auto i2 = makeSignedInterest(identity); // signed at 0s
317 auto si2 = i2.getSignatureInfo();
318 si2->setTime(i1.getSignatureInfo()->getTime());
319 i2.setSignatureInfo(*si2);
320 VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
321
322 advanceClocks(2_s);
323 auto i3 = makeSignedInterest(identity); // signed at +2s
324 VALIDATE_SUCCESS(i3, "Should succeed");
325}
326
327BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
328{
329 auto i2 = makeSignedInterest(identity); // signed at 0ms
330 advanceClocks(200_ms);
331 auto i3 = makeSignedInterest(identity); // signed at +200ms
332 advanceClocks(900_ms);
333 auto i1 = makeSignedInterest(identity); // signed at +1100ms
334 advanceClocks(300_ms);
335 auto i4 = makeSignedInterest(identity); // signed at +1400ms
336
337 systemClock->advance(-300_ms); // verifying at +1100ms
338 VALIDATE_SUCCESS(i1, "Should succeed");
339 rewindClockAfterValidation();
340
341 systemClock->advance(-1100_ms); // verifying at 0ms
342 VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
343 rewindClockAfterValidation();
344
345 // SignedInterestValidator should not remember i2's timestamp
346 advanceClocks(200_ms); // verifying at +200ms
347 VALIDATE_FAILURE(i3, "Should fail (timestamp reordered)");
348 rewindClockAfterValidation();
349
350 advanceClocks(1200_ms); // verifying at 1400ms
351 VALIDATE_SUCCESS(i4, "Should succeed");
352}
353
354template<class T>
355class GracePeriod
356{
357public:
358 static ValidationPolicySignedInterest::Options
359 getOptions()
360 {
361 ValidationPolicySignedInterest::Options options;
362 options.timestampGracePeriod = time::seconds(T::value);
363 return options;
364 }
365};
366
367typedef boost::mpl::vector<
368 GracePeriod<boost::mpl::int_<0>>,
369 GracePeriod<boost::mpl::int_<-1>>
370> GraceNonPositiveValues;
371
372BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositiveValues,
373 ValidationPolicySignedInterestFixture<GracePeriod>)
374{
375 auto i1 = this->makeSignedInterest(this->identity); // signed at 0ms
376 auto i2 = this->makeSignedInterest(this->subIdentity); // signed at 0ms
377 // ensure timestamps are exactly 0ms
378 for (auto interest : {&i1, &i2}) {
379 auto si = interest->getSignatureInfo();
380 si->setTime(time::system_clock::now());
381 interest->setSignatureInfo(*si);
382 }
383
384 VALIDATE_SUCCESS(i1, "Should succeed when validating at 0ms");
385 this->rewindClockAfterValidation();
386
387 this->advanceClocks(1_ms);
388 VALIDATE_FAILURE(i2, "Should fail when validating at 1ms");
389}
390
391BOOST_AUTO_TEST_SUITE_END() // TimestampValidation
392
393BOOST_AUTO_TEST_SUITE(SeqNumValidation)
394
395// By default, sequence number validation is disabled
396BOOST_AUTO_TEST_CASE(Disabled)
397{
398 auto i1 = makeSignedInterest(identity, WantAll); // signed at 0ms
399 VALIDATE_SUCCESS(i1, "Should succeed");
400
401 auto i2 = makeSignedInterest(identity, WantAll); // signed at +100ms
402 // Set i2 to have same seq num as i1
403 auto si2 = i2.getSignatureInfo();
404 si2->setSeqNum(i2.getSignatureInfo()->getSeqNum());
405 i2.setSignatureInfo(*si2);
406 VALIDATE_SUCCESS(i2, "Should succeed");
407}
408
409class SeqNumValidationOptions
410{
411public:
412 static ValidationPolicySignedInterest::Options
413 getOptions()
414 {
415 ValidationPolicySignedInterest::Options options;
416 options.shouldValidateSeqNums = true;
417 return options;
418 }
419};
420
421BOOST_FIXTURE_TEST_CASE(MissingSeqNum,
422 ValidationPolicySignedInterestFixture<SeqNumValidationOptions>)
423{
424 auto i1 = makeSignedInterest(identity, InterestSigner::WantTime);
425 VALIDATE_FAILURE(i1, "Should fail (sequence number missing");
426}
427
428BOOST_FIXTURE_TEST_CASE(SeqNumReorder,
429 ValidationPolicySignedInterestFixture<SeqNumValidationOptions>)
430{
431 auto i1 = makeSignedInterest(identity, WantAll); // seq num is i
432 VALIDATE_SUCCESS(i1, "Should succeed");
433
434 auto i2 = makeSignedInterest(identity, WantAll); // seq num is i+1
435 auto si2 = i2.getSignatureInfo();
436 si2->setSeqNum(i1.getSignatureInfo()->getSeqNum());
437 i2.setSignatureInfo(*si2);
438 VALIDATE_FAILURE(i2, "Should fail (sequence number reordered)");
439
440 auto i3 = makeSignedInterest(identity, WantAll); // seq num is i+2
441 VALIDATE_SUCCESS(i3, "Should succeed");
442}
443
444BOOST_AUTO_TEST_SUITE_END() // SeqNumValidation
445
446BOOST_AUTO_TEST_SUITE(NonceValidation)
447
448BOOST_AUTO_TEST_CASE(MissingNonce)
449{
450 auto i1 = makeSignedInterest(identity, InterestSigner::WantTime); // Specifically exclude nonce
451 VALIDATE_FAILURE(i1, "Should fail (nonce missing)");
452}
453
454BOOST_AUTO_TEST_CASE(DuplicateNonce)
455{
456 auto i1 = makeSignedInterest(identity, WantAll);
457 VALIDATE_SUCCESS(i1, "Should succeed");
458
459 auto i2 = makeSignedInterest(identity, WantAll);
460 auto si2 = i2.getSignatureInfo();
461 si2->setNonce(i1.getSignatureInfo()->getNonce());
462 i2.setSignatureInfo(*si2);
463 VALIDATE_FAILURE(i2, "Should fail (duplicate nonce)");
464
465 auto i3 = makeSignedInterest(identity, WantAll);
466 // On the off chance that the generated nonce is identical to i1
467 while (i3.getSignatureInfo()->getNonce() == i1.getSignatureInfo()->getNonce()) {
468 i3 = makeSignedInterest(identity, WantAll);
469 }
470 VALIDATE_SUCCESS(i3, "Should succeed");
471}
472
473class DisabledNonceValidationOptions
474{
475public:
476 static ValidationPolicySignedInterest::Options
477 getOptions()
478 {
479 ValidationPolicySignedInterest::Options options;
480 options.shouldValidateNonces = false;
481 return options;
482 }
483};
484
485BOOST_FIXTURE_TEST_CASE(Disabled,
486 ValidationPolicySignedInterestFixture<DisabledNonceValidationOptions>)
487{
488 auto i1 = makeSignedInterest(identity, WantAll ^ InterestSigner::WantNonce);
489 VALIDATE_SUCCESS(i1, "Should succeed");
490
491 // Ensure still works when a nonce is present
492 auto i2 = makeSignedInterest(identity, WantAll);
493 VALIDATE_SUCCESS(i2, "Should succeed");
494
495 // Ensure a duplicate still succeeds
496 auto i3 = makeSignedInterest(identity, WantAll);
497 auto si3 = i3.getSignatureInfo();
498 si3->setNonce(i2.getSignatureInfo()->getNonce());
499 i3.setSignatureInfo(*si3);
500 m_keyChain.sign(i3, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
501 .setSignatureInfo(*si3));
502 VALIDATE_SUCCESS(i3, "Should succeed");
503}
504
505class NonceLimit2Options
506{
507public:
508 static ValidationPolicySignedInterest::Options
509 getOptions()
510 {
511 ValidationPolicySignedInterest::Options options;
512 options.shouldValidateTimestamps = false;
513 options.shouldValidateSeqNums = false;
514 options.maxNonceRecordCount = 2;
515 return options;
516 }
517};
518
519BOOST_FIXTURE_TEST_CASE(NonceRecordLimit,
520 ValidationPolicySignedInterestFixture<NonceLimit2Options>)
521{
522 auto i1 = makeSignedInterest(identity, WantAll);
523 VALIDATE_SUCCESS(i1, "Should succeed");
524
525 auto i2 = makeSignedInterest(identity, WantAll);
526 // On the off chance that the generated nonce is identical to i1
527 while (i2.getSignatureInfo()->getNonce() == i1.getSignatureInfo()->getNonce()) {
528 i2 = makeSignedInterest(identity, WantAll);
529 }
530 VALIDATE_SUCCESS(i2, "Should succeed");
531
532 auto i3 = makeSignedInterest(identity, WantAll);
533 auto si3 = i3.getSignatureInfo();
534 si3->setNonce(i1.getSignatureInfo()->getNonce());
535 i3.setSignatureInfo(*si3);
536 m_keyChain.sign(i3, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
537 .setSignatureInfo(*si3));
538 VALIDATE_FAILURE(i3, "Should fail (duplicate nonce)");
539
540 // Pop i1's nonce off the list
541 auto i4 = makeSignedInterest(identity, WantAll);
542 // On the off chance that the generated nonce is identical to i1 or i2
543 while (i4.getSignatureInfo()->getNonce() == i1.getSignatureInfo()->getNonce() ||
544 i4.getSignatureInfo()->getNonce() == i2.getSignatureInfo()->getNonce()) {
545 i4 = makeSignedInterest(identity, WantAll);
546 }
547 VALIDATE_SUCCESS(i4, "Should succeed");
548
549 // Now i3 should succeed because i1's nonce has been popped off the list
550 auto i5 = makeSignedInterest(identity, WantAll);
551 auto si5 = i5.getSignatureInfo();
552 si5->setNonce(i1.getSignatureInfo()->getNonce());
553 i5.setSignatureInfo(*si5);
554 m_keyChain.sign(i5, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
555 .setSignatureInfo(*si5));
556 VALIDATE_SUCCESS(i5, "Should succeed");
557}
558
559BOOST_AUTO_TEST_SUITE_END() // NonceValidation
560
561BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicySignedInterest
562BOOST_AUTO_TEST_SUITE_END() // Security
563
564} // namespace tests
565} // inline namespace v2
566} // namespace security
567} // namespace ndn