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