| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /** |
| * Copyright (c) 2014-2015, Regents of the University of California |
| * |
| * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN). |
| * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors. |
| * |
| * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms |
| * of the GNU General Public License as published by the Free Software Foundation, |
| * either version 3 of the License, or (at your option) any later version. |
| * |
| * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
| * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
| * PURPOSE. See the GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * ndn-group-encrypt, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "producer.hpp" |
| #include "algo/encryptor.hpp" |
| #include "algo/rsa.hpp" |
| #include "algo/aes.hpp" |
| #include "encrypted-content.hpp" |
| #include "unit-test-time-fixture.hpp" |
| #include "random-number-generator.hpp" |
| |
| #include <ndn-cxx/util/dummy-client-face.hpp> |
| |
| #include "boost-test.hpp" |
| #include <boost/asio.hpp> |
| #include <boost/filesystem.hpp> |
| |
| namespace ndn { |
| namespace gep { |
| namespace tests { |
| |
| static const uint8_t DATA_CONTEN[] = { |
| 0xcb, 0xe5, 0x6a, 0x80, 0x41, 0x24, 0x58, 0x23, |
| 0x84, 0x14, 0x15, 0x61, 0x80, 0xb9, 0x5e, 0xbd, |
| 0xce, 0x32, 0xb4, 0xbe, 0xbc, 0x91, 0x31, 0xd6, |
| 0x19, 0x00, 0x80, 0x8b, 0xfa, 0x00, 0x05, 0x9c |
| }; |
| |
| class ProducerFixture : public UnitTestTimeFixture |
| { |
| public: |
| ProducerFixture() |
| : tmpPath(boost::filesystem::path(TMP_TESTS_PATH)) |
| , face1(util::makeDummyClientFace(io, {true, true})) |
| , face2(util::makeDummyClientFace(io, {true, true})) |
| , readInterestOffset1(0) |
| , readDataOffset1(0) |
| , readInterestOffset2(0) |
| , readDataOffset2(0) |
| { |
| boost::filesystem::create_directories(tmpPath); |
| } |
| |
| ~ProducerFixture() |
| { |
| boost::filesystem::remove_all(tmpPath); |
| } |
| |
| void |
| createEncryptionKey(Name eKeyName, const Name& timeMarker) |
| { |
| RandomNumberGenerator rng; |
| RsaKeyParams params; |
| eKeyName.append(timeMarker); |
| |
| Buffer dKeyBuf = algo::Rsa::generateKey(rng, params).getKeyBits(); |
| Buffer eKeyBuf = algo::Rsa::deriveEncryptKey(dKeyBuf).getKeyBits(); |
| decryptionKeys[eKeyName] = dKeyBuf; |
| |
| shared_ptr<Data> keyData = make_shared<Data>(eKeyName); |
| keyData->setContent(eKeyBuf.buf(), eKeyBuf.size()); |
| keyChain.sign(*keyData); |
| encryptionKeys[eKeyName] = keyData; |
| } |
| |
| bool |
| passPacket() |
| { |
| bool hasPassed = false; |
| |
| checkFace(face1->sentInterests, readInterestOffset1, *face2, hasPassed); |
| checkFace(face1->sentDatas, readDataOffset1, *face2, hasPassed); |
| checkFace(face2->sentInterests, readInterestOffset2, *face1, hasPassed); |
| checkFace(face2->sentDatas, readDataOffset2, *face1, hasPassed); |
| |
| return hasPassed; |
| } |
| |
| template<typename Packet> |
| void |
| checkFace(std::vector<Packet>& receivedPackets, |
| size_t& readPacketOffset, |
| util::DummyClientFace& receiver, |
| bool& hasPassed) |
| { |
| while (receivedPackets.size() > readPacketOffset) { |
| receiver.receive(receivedPackets[readPacketOffset]); |
| readPacketOffset++; |
| hasPassed = true; |
| } |
| } |
| |
| public: |
| boost::filesystem::path tmpPath; |
| |
| shared_ptr<util::DummyClientFace> face1; |
| shared_ptr<util::DummyClientFace> face2; |
| |
| size_t readInterestOffset1; |
| size_t readDataOffset1; |
| size_t readInterestOffset2; |
| size_t readDataOffset2; |
| |
| KeyChain keyChain; |
| |
| std::unordered_map<Name, Buffer> decryptionKeys; |
| std::unordered_map<Name, shared_ptr<Data>> encryptionKeys; |
| }; |
| |
| BOOST_FIXTURE_TEST_SUITE(TestProducer, ProducerFixture) |
| |
| BOOST_AUTO_TEST_CASE(ContentKeyRequest) |
| { |
| std::string dbDir = tmpPath.c_str(); |
| dbDir += "/test.db"; |
| |
| Name prefix("/prefix"); |
| Name suffix("/a/b/c"); |
| Name expectedInterest = prefix; |
| expectedInterest.append(NAME_COMPONENT_READ); |
| expectedInterest.append(suffix); |
| expectedInterest.append(NAME_COMPONENT_E_KEY); |
| |
| Name cKeyName = prefix; |
| cKeyName.append(NAME_COMPONENT_SAMPLE); |
| cKeyName.append(suffix); |
| cKeyName.append(NAME_COMPONENT_C_KEY); |
| |
| Name timeMarker("20150101T100000/20150101T120000"); |
| time::system_clock::TimePoint testTime1 = time::fromIsoString("20150101T100001"); |
| time::system_clock::TimePoint testTime2 = time::fromIsoString("20150101T110001"); |
| name::Component testTimeRounded1("20150101T100000"); |
| name::Component testTimeRounded2("20150101T110000"); |
| name::Component testTimeComponent2("20150101T110001"); |
| |
| // Create content keys required for this test case: |
| for (size_t i = 0; i < suffix.size(); i++) { |
| createEncryptionKey(expectedInterest, timeMarker); |
| expectedInterest = expectedInterest.getPrefix(-2).append(NAME_COMPONENT_E_KEY); |
| } |
| |
| face2->setInterestFilter(prefix, |
| [&] (const InterestFilter&, const Interest& i) { |
| Name interestName = i.getName(); |
| interestName.append(timeMarker); |
| BOOST_REQUIRE_EQUAL(encryptionKeys.find(interestName) != |
| encryptionKeys.end(), true); |
| face2->put(*(encryptionKeys[interestName])); |
| return; |
| }, |
| RegisterPrefixSuccessCallback(), |
| [] (const Name&, const std::string& e) { }); |
| |
| do { |
| advanceClocks(time::milliseconds(10), 20); |
| } while (passPacket()); |
| |
| /* |
| Verify that content key is correctly encrypted for each domain, and the |
| produce method encrypts provided data with the same content key. |
| */ |
| Producer producer(prefix, suffix, *face1, dbDir); |
| ProducerDB testDb(dbDir); |
| Buffer contentKey; |
| |
| auto checkEncryptionKeys = |
| [&](const std::vector<Data>& result, |
| const time::system_clock::TimePoint& testTime, |
| const name::Component& roundedTime) { |
| BOOST_CHECK_EQUAL(testDb.hasContentKey(testTime), true); |
| contentKey = testDb.getContentKey(testTime); |
| |
| algo::EncryptParams params(tlv::AlgorithmRsaOaep); |
| std::vector<Data>::const_iterator it; |
| for (it = result.begin(); it != result.end(); ++it) { |
| Name keyName = it->getName(); |
| BOOST_CHECK_EQUAL(keyName.getSubName(0,6), cKeyName); |
| BOOST_CHECK_EQUAL(keyName.get(6), roundedTime); |
| BOOST_CHECK_EQUAL(keyName.get(7), NAME_COMPONENT_FOR); |
| BOOST_CHECK_EQUAL(decryptionKeys.find(keyName.getSubName(8)) != |
| decryptionKeys.end(), true); |
| Name testName = it->getName().getSubName(-8); |
| Buffer decryptionKey; |
| |
| decryptionKey = decryptionKeys.at(keyName.getSubName(8)); |
| BOOST_CHECK_EQUAL(decryptionKey.size() != 0, true); |
| Block encryptedKeyBlock = it->getContent(); |
| encryptedKeyBlock.parse(); |
| |
| EncryptedContent content(*(encryptedKeyBlock.elements_begin())); |
| const Buffer& encryptedKey = content.getPayload(); |
| Buffer retrievedKey = algo::Rsa::decrypt(decryptionKey.buf(), |
| decryptionKey.size(), |
| encryptedKey.buf(), |
| encryptedKey.size(), |
| params); |
| |
| BOOST_CHECK_EQUAL_COLLECTIONS(contentKey.begin(), |
| contentKey.end(), |
| retrievedKey.begin(), |
| retrievedKey.end()); |
| } |
| BOOST_CHECK_EQUAL(result.size(), 3); |
| }; |
| |
| // Initial test to confirm that keys are created for this timeslot |
| Name contentKeyName1 = |
| producer.createContentKey(testTime1, |
| std::bind(checkEncryptionKeys, _1, testTime1, testTimeRounded1)); |
| |
| do { |
| advanceClocks(time::milliseconds(10), 20); |
| } while (passPacket()); |
| |
| // Verify that we do not repeat the search for e-keys, don't advance clock |
| Name contentKeyName2 = |
| producer.createContentKey(testTime2, |
| std::bind(checkEncryptionKeys, _1, testTime2, testTimeRounded2)); |
| |
| // Confirm content key names are correct |
| BOOST_CHECK_EQUAL(contentKeyName1.getPrefix(-1), cKeyName); |
| BOOST_CHECK_EQUAL(contentKeyName1.get(6), testTimeRounded1); |
| BOOST_CHECK_EQUAL(contentKeyName2.getPrefix(-1), cKeyName); |
| BOOST_CHECK_EQUAL(contentKeyName2.get(6), testTimeRounded2); |
| |
| // Confirm produce encrypts with correct key and has right name |
| Data testData; |
| producer.produce(testData, testTime2, DATA_CONTEN, sizeof(DATA_CONTEN)); |
| |
| Name producedName = testData.getName(); |
| BOOST_CHECK_EQUAL(producedName.getSubName(0,5), cKeyName.getPrefix(-1)); |
| BOOST_CHECK_EQUAL(producedName.get(5), testTimeComponent2); |
| BOOST_CHECK_EQUAL(producedName.get(6), NAME_COMPONENT_FOR); |
| BOOST_CHECK_EQUAL(producedName.getSubName(7,6), cKeyName); |
| BOOST_CHECK_EQUAL(producedName.get(13), testTimeRounded2); |
| |
| Block dataBlock = testData.getContent(); |
| dataBlock.parse(); |
| |
| EncryptedContent dataContent(*(dataBlock).elements_begin()); |
| const Buffer& encData = dataContent.getPayload(); |
| const Buffer& iv = dataContent.getInitialVector(); |
| |
| algo::EncryptParams params(tlv::AlgorithmAesCbc, 16); |
| params.setIV(iv.buf(), iv.size()); |
| Buffer decryptTest = algo::Aes::decrypt(contentKey.buf(), contentKey.size(), |
| encData.buf(), encData.size(), params); |
| BOOST_CHECK_EQUAL_COLLECTIONS(decryptTest.begin(), |
| decryptTest.end(), |
| DATA_CONTEN, |
| DATA_CONTEN + sizeof(DATA_CONTEN)); |
| } |
| |
| BOOST_AUTO_TEST_CASE(ContentKeySearch) |
| { |
| std::string dbDir = tmpPath.c_str(); |
| dbDir += "/test.db"; |
| |
| Name timeMarkerFirstHop("20150101T070000/20150101T080000"); |
| Name timeMarkerSecondHop("20150101T080000/20150101T090000"); |
| Name timeMarkerThirdHop("20150101T100000/20150101T110000"); |
| |
| Name prefix("/prefix"); |
| Name suffix("/suffix"); |
| Name expectedInterest = prefix; |
| expectedInterest.append(NAME_COMPONENT_READ); |
| expectedInterest.append(suffix); |
| expectedInterest.append(NAME_COMPONENT_E_KEY); |
| |
| Name cKeyName = prefix; |
| cKeyName.append(NAME_COMPONENT_SAMPLE); |
| cKeyName.append(suffix); |
| cKeyName.append(NAME_COMPONENT_C_KEY); |
| |
| time::system_clock::TimePoint testTime = time::fromIsoString("20150101T100001"); |
| |
| // Create content keys required for this test case: |
| createEncryptionKey(expectedInterest, timeMarkerFirstHop); |
| createEncryptionKey(expectedInterest, timeMarkerSecondHop); |
| createEncryptionKey(expectedInterest, timeMarkerThirdHop); |
| |
| size_t requestCount = 0; |
| face2->setInterestFilter(prefix, |
| [&] (const InterestFilter&, const Interest& i) { |
| BOOST_REQUIRE_EQUAL(i.getName(), expectedInterest); |
| Name interestName = i.getName(); |
| switch(requestCount) { |
| case 0: |
| interestName.append(timeMarkerFirstHop); |
| break; |
| |
| case 1: |
| interestName.append(timeMarkerSecondHop); |
| break; |
| |
| case 2: |
| interestName.append(timeMarkerThirdHop); |
| break; |
| |
| default: |
| break; |
| } |
| face2->put(*(encryptionKeys[interestName])); |
| requestCount++; |
| return; |
| }, |
| RegisterPrefixSuccessCallback(), |
| [] (const Name&, const std::string& e) { }); |
| |
| do { |
| advanceClocks(time::milliseconds(10), 20); |
| } while (passPacket()); |
| |
| /* |
| Verify that if a key is found, but not within the right timeslot, the search |
| is refined until a valid timeslot is found. |
| */ |
| Producer producer(prefix, suffix, *face1, dbDir); |
| producer.createContentKey(testTime, |
| [&](const std::vector<Data>& result){ |
| BOOST_CHECK_EQUAL(requestCount, 3); |
| BOOST_CHECK_EQUAL(result.size(), 1); |
| |
| Data keyData = result[0]; |
| Name keyName = keyData.getName(); |
| BOOST_CHECK_EQUAL(keyName.getSubName(0,4), cKeyName); |
| BOOST_CHECK_EQUAL(keyName.get(4), timeMarkerThirdHop[0]); |
| BOOST_CHECK_EQUAL(keyName.get(5), NAME_COMPONENT_FOR); |
| BOOST_CHECK_EQUAL(keyName.getSubName(6), |
| expectedInterest.append(timeMarkerThirdHop)); |
| }); |
| do { |
| advanceClocks(time::milliseconds(10), 20); |
| } while (passPacket()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(ContentKeyTimeout) |
| { |
| std::string dbDir = tmpPath.c_str(); |
| dbDir += "/test.db"; |
| |
| Name prefix("/prefix"); |
| Name suffix("/suffix"); |
| Name expectedInterest = prefix; |
| expectedInterest.append(NAME_COMPONENT_READ); |
| expectedInterest.append(suffix); |
| expectedInterest.append(NAME_COMPONENT_E_KEY); |
| |
| time::system_clock::TimePoint testTime = time::fromIsoString("20150101T100001"); |
| |
| size_t timeoutCount = 0; |
| face2->setInterestFilter(prefix, |
| [&] (const InterestFilter&, const Interest& i) { |
| BOOST_CHECK_EQUAL(i.getName(), expectedInterest); |
| timeoutCount++; |
| return; |
| }, |
| RegisterPrefixSuccessCallback(), |
| [] (const Name&, const std::string& e) { }); |
| |
| do { |
| advanceClocks(time::milliseconds(10), 20); |
| } while (passPacket()); |
| |
| /* |
| Verify that if no response is received, the producer appropriately times out. |
| The result vector should not contain elements that have timed out. |
| */ |
| Producer producer(prefix, suffix, *face1, dbDir); |
| producer.createContentKey(testTime, |
| [&](const std::vector<Data>& result){ |
| BOOST_CHECK_EQUAL(timeoutCount, 4); |
| BOOST_CHECK_EQUAL(result.size(), 0); |
| }); |
| |
| do { |
| advanceClocks(time::milliseconds(10), 500); |
| } while (passPacket()); |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() |
| |
| } // namespace tests |
| } // namespace gep |
| } // namespace ndn |