Prashanth Swaminathan | b210590 | 2015-08-20 14:28:54 -0700 | [diff] [blame] | 1 | /* -*- 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 | |
| 34 | namespace ndn { |
| 35 | namespace gep { |
| 36 | namespace tests { |
| 37 | |
| 38 | static 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 | |
| 45 | class ProducerFixture : public UnitTestTimeFixture |
| 46 | { |
| 47 | public: |
| 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 | |
| 109 | public: |
| 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 | |
| 126 | BOOST_FIXTURE_TEST_SUITE(TestProducer, ProducerFixture) |
| 127 | |
| 128 | BOOST_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 | |
| 269 | BOOST_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 | |
| 352 | BOOST_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 | |
| 396 | BOOST_AUTO_TEST_SUITE_END() |
| 397 | |
| 398 | } // namespace tests |
| 399 | } // namespace gep |
| 400 | } // namespace ndn |