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