blob: 84ed8c37df38b620b891d73f7c76c62f37c1ccf0 [file] [log] [blame]
Prashanth Swaminathanb2105902015-08-20 14:28:54 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3 * Copyright (c) 2014-2015, Regents of the University of California
4 *
5 * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
6 * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
7 *
8 * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
9 * of the GNU General Public License as published by the Free Software Foundation,
10 * either version 3 of the License, or (at your option) any later version.
11 *
12 * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * ndn-group-encrypt, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "producer.hpp"
21#include "algo/encryptor.hpp"
22#include "algo/rsa.hpp"
23#include "algo/aes.hpp"
24#include "encrypted-content.hpp"
25#include "unit-test-time-fixture.hpp"
26#include "random-number-generator.hpp"
27
28#include <ndn-cxx/util/dummy-client-face.hpp>
29
30#include "boost-test.hpp"
31#include <boost/asio.hpp>
32#include <boost/filesystem.hpp>
33
34namespace ndn {
35namespace gep {
36namespace tests {
37
38static const uint8_t DATA_CONTEN[] = {
39 0xcb, 0xe5, 0x6a, 0x80, 0x41, 0x24, 0x58, 0x23,
40 0x84, 0x14, 0x15, 0x61, 0x80, 0xb9, 0x5e, 0xbd,
41 0xce, 0x32, 0xb4, 0xbe, 0xbc, 0x91, 0x31, 0xd6,
42 0x19, 0x00, 0x80, 0x8b, 0xfa, 0x00, 0x05, 0x9c
43};
44
45class ProducerFixture : public UnitTestTimeFixture
46{
47public:
48 ProducerFixture()
49 : tmpPath(boost::filesystem::path(TMP_TESTS_PATH))
50 , face1(util::makeDummyClientFace(io, {true, true}))
51 , face2(util::makeDummyClientFace(io, {true, true}))
52 , readInterestOffset1(0)
53 , readDataOffset1(0)
54 , readInterestOffset2(0)
55 , readDataOffset2(0)
56 {
57 boost::filesystem::create_directories(tmpPath);
58 }
59
60 ~ProducerFixture()
61 {
62 boost::filesystem::remove_all(tmpPath);
63 }
64
65 void
66 createEncryptionKey(Name eKeyName, const Name& timeMarker)
67 {
68 RandomNumberGenerator rng;
69 RsaKeyParams params;
70 eKeyName.append(timeMarker);
71
72 Buffer dKeyBuf = algo::Rsa::generateKey(rng, params).getKeyBits();
73 Buffer eKeyBuf = algo::Rsa::deriveEncryptKey(dKeyBuf).getKeyBits();
74 decryptionKeys[eKeyName] = dKeyBuf;
75
76 shared_ptr<Data> keyData = make_shared<Data>(eKeyName);
77 keyData->setContent(eKeyBuf.buf(), eKeyBuf.size());
78 keyChain.sign(*keyData);
79 encryptionKeys[eKeyName] = keyData;
80 }
81
82 bool
83 passPacket()
84 {
85 bool hasPassed = false;
86
87 checkFace(face1->sentInterests, readInterestOffset1, *face2, hasPassed);
88 checkFace(face1->sentDatas, readDataOffset1, *face2, hasPassed);
89 checkFace(face2->sentInterests, readInterestOffset2, *face1, hasPassed);
90 checkFace(face2->sentDatas, readDataOffset2, *face1, hasPassed);
91
92 return hasPassed;
93 }
94
95 template<typename Packet>
96 void
97 checkFace(std::vector<Packet>& receivedPackets,
98 size_t& readPacketOffset,
99 util::DummyClientFace& receiver,
100 bool& hasPassed)
101 {
102 while (receivedPackets.size() > readPacketOffset) {
103 receiver.receive(receivedPackets[readPacketOffset]);
104 readPacketOffset++;
105 hasPassed = true;
106 }
107 }
108
109public:
110 boost::filesystem::path tmpPath;
111
112 shared_ptr<util::DummyClientFace> face1;
113 shared_ptr<util::DummyClientFace> face2;
114
115 size_t readInterestOffset1;
116 size_t readDataOffset1;
117 size_t readInterestOffset2;
118 size_t readDataOffset2;
119
120 KeyChain keyChain;
121
122 std::unordered_map<Name, Buffer> decryptionKeys;
123 std::unordered_map<Name, shared_ptr<Data>> encryptionKeys;
124};
125
126BOOST_FIXTURE_TEST_SUITE(TestProducer, ProducerFixture)
127
128BOOST_AUTO_TEST_CASE(ContentKeyRequest)
129{
130 std::string dbDir = tmpPath.c_str();
131 dbDir += "/test.db";
132
133 Name prefix("/prefix");
134 Name suffix("/a/b/c");
135 Name expectedInterest = prefix;
136 expectedInterest.append(NAME_COMPONENT_READ);
137 expectedInterest.append(suffix);
138 expectedInterest.append(NAME_COMPONENT_E_KEY);
139
140 Name cKeyName = prefix;
141 cKeyName.append(NAME_COMPONENT_SAMPLE);
142 cKeyName.append(suffix);
143 cKeyName.append(NAME_COMPONENT_C_KEY);
144
145 Name timeMarker("20150101T100000/20150101T120000");
146 time::system_clock::TimePoint testTime1 = time::fromIsoString("20150101T100001");
147 time::system_clock::TimePoint testTime2 = time::fromIsoString("20150101T110001");
148 name::Component testTimeRounded1("20150101T100000");
149 name::Component testTimeRounded2("20150101T110000");
150
151 // Create content keys required for this test case:
152 for (size_t i = 0; i < suffix.size(); i++) {
153 createEncryptionKey(expectedInterest, timeMarker);
154 expectedInterest = expectedInterest.getPrefix(-2).append(NAME_COMPONENT_E_KEY);
155 }
156
157 face2->setInterestFilter(prefix,
158 [&] (const InterestFilter&, const Interest& i) {
159 Name interestName = i.getName();
160 interestName.append(timeMarker);
161 BOOST_REQUIRE_EQUAL(encryptionKeys.find(interestName) !=
162 encryptionKeys.end(), true);
163 face2->put(*(encryptionKeys[interestName]));
164 return;
165 },
166 RegisterPrefixSuccessCallback(),
167 [] (const Name&, const std::string& e) { });
168
169 do {
170 advanceClocks(time::milliseconds(10), 20);
171 } while (passPacket());
172
173 /*
174 Verify that content key is correctly encrypted for each domain, and the
175 produce method encrypts provided data with the same content key.
176 */
177 Producer producer(prefix, suffix, *face1, dbDir);
178 ProducerDB testDb(dbDir);
179 Buffer contentKey;
180
181 auto checkEncryptionKeys =
182 [&](const std::vector<Data>& result,
183 const time::system_clock::TimePoint& testTime,
184 const name::Component& roundedTime) {
185 BOOST_CHECK_EQUAL(testDb.hasContentKey(testTime), true);
186 contentKey = testDb.getContentKey(testTime);
187
188 algo::EncryptParams params(tlv::AlgorithmRsaOaep);
189 std::vector<Data>::const_iterator it;
190 for (it = result.begin(); it != result.end(); ++it) {
191 Name keyName = it->getName();
192 BOOST_CHECK_EQUAL(keyName.getSubName(0,6), cKeyName);
193 BOOST_CHECK_EQUAL(keyName.get(6), roundedTime);
194 BOOST_CHECK_EQUAL(keyName.get(7), NAME_COMPONENT_FOR);
195 BOOST_CHECK_EQUAL(decryptionKeys.find(keyName.getSubName(8)) !=
196 decryptionKeys.end(), true);
197 Name testName = it->getName().getSubName(-8);
198 Buffer decryptionKey;
199
200 decryptionKey = decryptionKeys.at(keyName.getSubName(8));
201 BOOST_CHECK_EQUAL(decryptionKey.size() != 0, true);
202 Block encryptedKeyBlock = it->getContent();
203 encryptedKeyBlock.parse();
204
205 EncryptedContent content(*(encryptedKeyBlock.elements_begin()));
206 const Buffer& encryptedKey = content.getPayload();
207 Buffer retrievedKey = algo::Rsa::decrypt(decryptionKey.buf(),
208 decryptionKey.size(),
209 encryptedKey.buf(),
210 encryptedKey.size(),
211 params);
212
213 BOOST_CHECK_EQUAL_COLLECTIONS(contentKey.begin(),
214 contentKey.end(),
215 retrievedKey.begin(),
216 retrievedKey.end());
217 }
218 BOOST_CHECK_EQUAL(result.size(), 3);
219 };
220
221 // Initial test to confirm that keys are created for this timeslot
222 Name contentKeyName1 =
223 producer.createContentKey(testTime1,
224 std::bind(checkEncryptionKeys, _1, testTime1, testTimeRounded1));
225
226 do {
227 advanceClocks(time::milliseconds(10), 20);
228 } while (passPacket());
229
230 // Verify that we do not repeat the search for e-keys, don't advance clock
231 Name contentKeyName2 =
232 producer.createContentKey(testTime2,
233 std::bind(checkEncryptionKeys, _1, testTime2, testTimeRounded2));
234
235 // Confirm content key names are correct
236 BOOST_CHECK_EQUAL(contentKeyName1.getPrefix(-1), cKeyName);
237 BOOST_CHECK_EQUAL(contentKeyName1.get(6), testTimeRounded1);
238 BOOST_CHECK_EQUAL(contentKeyName2.getPrefix(-1), cKeyName);
239 BOOST_CHECK_EQUAL(contentKeyName2.get(6), testTimeRounded2);
240
241 // Confirm produce encrypts with correct key and has right name
242 Data testData;
243 producer.produce(testData, testTime2, DATA_CONTEN, sizeof(DATA_CONTEN));
244
245 Name producedName = testData.getName();
246 BOOST_CHECK_EQUAL(producedName.getSubName(0,5), cKeyName.getPrefix(-1));
247 BOOST_CHECK_EQUAL(producedName.get(5), testTimeRounded2);
248 BOOST_CHECK_EQUAL(producedName.get(6), NAME_COMPONENT_FOR);
249 BOOST_CHECK_EQUAL(producedName.getSubName(7,6), cKeyName);
250 BOOST_CHECK_EQUAL(producedName.get(13), testTimeRounded2);
251
252 Block dataBlock = testData.getContent();
253 dataBlock.parse();
254
255 EncryptedContent dataContent(*(dataBlock).elements_begin());
256 const Buffer& encData = dataContent.getPayload();
257 const Buffer& iv = dataContent.getInitialVector();
258
259 algo::EncryptParams params(tlv::AlgorithmAesCbc, 16);
260 params.setIV(iv.buf(), iv.size());
261 Buffer decryptTest = algo::Aes::decrypt(contentKey.buf(), contentKey.size(),
262 encData.buf(), encData.size(), params);
263 BOOST_CHECK_EQUAL_COLLECTIONS(decryptTest.begin(),
264 decryptTest.end(),
265 DATA_CONTEN,
266 DATA_CONTEN + sizeof(DATA_CONTEN));
267}
268
269BOOST_AUTO_TEST_CASE(ContentKeySearch)
270{
271 std::string dbDir = tmpPath.c_str();
272 dbDir += "/test.db";
273
274 Name timeMarkerFirstHop("20150101T070000/20150101T080000");
275 Name timeMarkerSecondHop("20150101T080000/20150101T090000");
276 Name timeMarkerThirdHop("20150101T100000/20150101T110000");
277
278 Name prefix("/prefix");
279 Name suffix("/suffix");
280 Name expectedInterest = prefix;
281 expectedInterest.append(NAME_COMPONENT_READ);
282 expectedInterest.append(suffix);
283 expectedInterest.append(NAME_COMPONENT_E_KEY);
284
285 Name cKeyName = prefix;
286 cKeyName.append(NAME_COMPONENT_SAMPLE);
287 cKeyName.append(suffix);
288 cKeyName.append(NAME_COMPONENT_C_KEY);
289
290 time::system_clock::TimePoint testTime = time::fromIsoString("20150101T100001");
291
292 // Create content keys required for this test case:
293 createEncryptionKey(expectedInterest, timeMarkerFirstHop);
294 createEncryptionKey(expectedInterest, timeMarkerSecondHop);
295 createEncryptionKey(expectedInterest, timeMarkerThirdHop);
296
297 size_t requestCount = 0;
298 face2->setInterestFilter(prefix,
299 [&] (const InterestFilter&, const Interest& i) {
300 BOOST_REQUIRE_EQUAL(i.getName(), expectedInterest);
301 Name interestName = i.getName();
302 switch(requestCount) {
303 case 0:
304 interestName.append(timeMarkerFirstHop);
305 break;
306
307 case 1:
308 interestName.append(timeMarkerSecondHop);
309 break;
310
311 case 2:
312 interestName.append(timeMarkerThirdHop);
313 break;
314
315 default:
316 break;
317 }
318 face2->put(*(encryptionKeys[interestName]));
319 requestCount++;
320 return;
321 },
322 RegisterPrefixSuccessCallback(),
323 [] (const Name&, const std::string& e) { });
324
325 do {
326 advanceClocks(time::milliseconds(10), 20);
327 } while (passPacket());
328
329 /*
330 Verify that if a key is found, but not within the right timeslot, the search
331 is refined until a valid timeslot is found.
332 */
333 Producer producer(prefix, suffix, *face1, dbDir);
334 producer.createContentKey(testTime,
335 [&](const std::vector<Data>& result){
336 BOOST_CHECK_EQUAL(requestCount, 3);
337 BOOST_CHECK_EQUAL(result.size(), 1);
338
339 Data keyData = result[0];
340 Name keyName = keyData.getName();
341 BOOST_CHECK_EQUAL(keyName.getSubName(0,4), cKeyName);
342 BOOST_CHECK_EQUAL(keyName.get(4), timeMarkerThirdHop[0]);
343 BOOST_CHECK_EQUAL(keyName.get(5), NAME_COMPONENT_FOR);
344 BOOST_CHECK_EQUAL(keyName.getSubName(6),
345 expectedInterest.append(timeMarkerThirdHop));
346 });
347 do {
348 advanceClocks(time::milliseconds(10), 20);
349 } while (passPacket());
350}
351
352BOOST_AUTO_TEST_CASE(ContentKeyTimeout)
353{
354 std::string dbDir = tmpPath.c_str();
355 dbDir += "/test.db";
356
357 Name prefix("/prefix");
358 Name suffix("/suffix");
359 Name expectedInterest = prefix;
360 expectedInterest.append(NAME_COMPONENT_READ);
361 expectedInterest.append(suffix);
362 expectedInterest.append(NAME_COMPONENT_E_KEY);
363
364 time::system_clock::TimePoint testTime = time::fromIsoString("20150101T100001");
365
366 size_t timeoutCount = 0;
367 face2->setInterestFilter(prefix,
368 [&] (const InterestFilter&, const Interest& i) {
369 BOOST_CHECK_EQUAL(i.getName(), expectedInterest);
370 timeoutCount++;
371 return;
372 },
373 RegisterPrefixSuccessCallback(),
374 [] (const Name&, const std::string& e) { });
375
376 do {
377 advanceClocks(time::milliseconds(10), 20);
378 } while (passPacket());
379
380 /*
381 Verify that if no response is received, the producer appropriately times out.
382 The result vector should not contain elements that have timed out.
383 */
384 Producer producer(prefix, suffix, *face1, dbDir);
385 producer.createContentKey(testTime,
386 [&](const std::vector<Data>& result){
387 BOOST_CHECK_EQUAL(timeoutCount, 4);
388 BOOST_CHECK_EQUAL(result.size(), 0);
389 });
390
391 do {
392 advanceClocks(time::milliseconds(10), 500);
393 } while (passPacket());
394}
395
396BOOST_AUTO_TEST_SUITE_END()
397
398} // namespace tests
399} // namespace gep
400} // namespace ndn