ndn-js
Version:
A JavaScript client library for Named Data Networking
450 lines (381 loc) • 17 kB
JavaScript
/**
* Copyright (C) 2015-2019 Regents of the University of California.
* @author: Jeff Thompson <jefft0@remap.ucla.edu>
* @author: From ndn-group-encrypt unit tests
* https://github.com/named-data/ndn-group-encrypt/blob/master/tests/unit-tests/producer.t.cpp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version, with the additional exemption that
* compiling, linking, and/or using OpenSSL is allowed.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* A copy of the GNU Lesser General Public License is in the file COPYING.
*/
var assert = require("assert");
var fs = require("fs");
var Name = require('../../..').Name;
var Data = require('../../..').Data;
var Link = require('../../..').Link;
var Blob = require('../../..').Blob;
var MemoryIdentityStorage = require('../../..').MemoryIdentityStorage;
var MemoryPrivateKeyStorage = require('../../..').MemoryPrivateKeyStorage;
var KeyChain = require('../../..').KeyChain;
var IdentityManager = require('../../..').IdentityManager;
var NoVerifyPolicyManager = require('../../..').NoVerifyPolicyManager;
var Sqlite3ProducerDb = require('../../..').Sqlite3ProducerDb;
var Producer = require('../../..').Producer;
var Encryptor = require('../../..').Encryptor;
var RsaKeyParams = require('../../..').RsaKeyParams;
var RsaAlgorithm = require('../../..').RsaAlgorithm;
var AesAlgorithm = require('../../..').AesAlgorithm;
var EncryptParams = require('../../..').EncryptParams;
var EncryptAlgorithmType = require('../../..').EncryptAlgorithmType;
var EncryptedContent = require('../../..').EncryptedContent;
var Common = require('../unit-tests/unit-tests-common.js').UnitTestsCommon;
var DATA_CONTENT = new Buffer([
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
]);
var databaseFilePath;
var keyChain;
var certificateName;
var decryptionKeys = {}; // key: name URI string, value: Blob
var encryptionKeys = {}; // key: name URI string, value: Data
createEncryptionKey = function(eKeyName, timeMarker)
{
var params = new RsaKeyParams();
eKeyName = new Name(eKeyName);
eKeyName.append(timeMarker);
var dKeyBlob = RsaAlgorithm.generateKey(params).getKeyBits();
var eKeyBlob = RsaAlgorithm.deriveEncryptKey(dKeyBlob).getKeyBits();
decryptionKeys[eKeyName.toUri()] = dKeyBlob;
var keyData = new Data(eKeyName);
keyData.setContent(eKeyBlob);
keyChain.sign(keyData, certificateName);
encryptionKeys[eKeyName.toUri()] = keyData;
}
describe ("TestProducer", function() {
beforeEach(function(done) {
databaseFilePath = "policy_config/test.db";
try {
fs.unlinkSync(databaseFilePath);
}
catch (e) {}
// Set up the key chain.
var identityStorage = new MemoryIdentityStorage();
var privateKeyStorage = new MemoryPrivateKeyStorage();
keyChain = new KeyChain
(new IdentityManager(identityStorage, privateKeyStorage),
new NoVerifyPolicyManager());
var identityName = new Name("TestProducer");
certificateName = keyChain.createIdentityAndCertificate(identityName);
keyChain.getIdentityManager().setDefaultIdentity(identityName);
done();
});
afterEach(function(done) {
try {
fs.unlinkSync(databaseFilePath);
}
catch (e) {}
done();
});
it("ContentKeyRequest", function(done) {
var prefix = new Name("/prefix");
var suffix = new Name("/a/b/c");
var expectedInterest = new Name(prefix);
expectedInterest.append(Encryptor.NAME_COMPONENT_READ);
expectedInterest.append(suffix);
expectedInterest.append(Encryptor.NAME_COMPONENT_E_KEY);
var cKeyName = new Name(prefix);
cKeyName.append(Encryptor.NAME_COMPONENT_SAMPLE);
cKeyName.append(suffix);
cKeyName.append(Encryptor.NAME_COMPONENT_C_KEY);
var timeMarker = new Name("20150101T100000/20150101T120000");
var testTime1 = Common.fromIsoString("20150101T100001");
var testTime2 = Common.fromIsoString("20150101T110001");
var testTimeRounded1 = new Name.Component("20150101T100000");
var testTimeRounded2 = new Name.Component("20150101T110000");
var testTimeComponent2 = new Name.Component("20150101T110001");
// Create content keys required for this test case:
for (var i = 0; i < suffix.size(); ++i) {
createEncryptionKey(expectedInterest, timeMarker);
expectedInterest = expectedInterest.getPrefix(-2).append
(Encryptor.NAME_COMPONENT_E_KEY);
}
var expressInterestCallCount = 0;
// Prepare a TestFace to instantly answer calls to expressInterest.
var TestFace = function TestFace() {};
TestFace.prototype.expressInterest = function
(interest, onData, onTimeout, onNetworkNack)
{
try {
++expressInterestCallCount;
var interestName = new Name(interest.getName());
interestName.append(timeMarker);
assert.ok(encryptionKeys[interestName.toUri()] != undefined);
onData(interest, encryptionKeys[interestName.toUri()]);
return 0;
} catch (ex) { done(ex); }
};
var face = new TestFace();
// Verify that the content key is correctly encrypted for each domain, and
// the produce method encrypts the provided data with the same content key.
var testDb = new Sqlite3ProducerDb(databaseFilePath);
var producer = new Producer(prefix, suffix, face, keyChain, testDb);
var contentKey = null; // Blob
// We don't know which callback will be called first, so count.
var nCallbacksNeeded = 4;
var nCallbacksCalled = 0;
function checkEncryptionKeys
(result, testTime, roundedTime, expectedExpressInterestCallCount) {
try {
assert.equal(expressInterestCallCount, expectedExpressInterestCallCount);
} catch (ex) { done(ex); return; }
testDb.hasContentKeyPromise(testTime)
.then(function(exists) {
assert.equal(true, exists);
return testDb.getContentKeyPromise(testTime);
})
.then(function(localContentKey) {
contentKey = localContentKey;
var params = new EncryptParams(EncryptAlgorithmType.RsaOaep);
for (var i = 0; i < result.length; ++i) {
var key = result[i]; // Data
var keyName = key.getName();
assert.ok(cKeyName.equals(keyName.getSubName(0, 6)));
assert.ok(keyName.get(6).equals(roundedTime));
assert.ok(keyName.get(7).equals(Encryptor.NAME_COMPONENT_FOR));
assert.equal(decryptionKeys[keyName.getSubName(8).toUri()] != undefined, true);
var decryptionKey = decryptionKeys[keyName.getSubName(8).toUri()];
assert.equal(decryptionKey.size() != 0, true);
var encryptedKeyEncoding = key.getContent();
var content = new EncryptedContent();
content.wireDecode(encryptedKeyEncoding);
var encryptedKey = content.getPayload();
var retrievedKey = RsaAlgorithm.decrypt
(decryptionKey, encryptedKey, params);
assert.ok(contentKey.equals(retrievedKey));
}
assert.equal(result.length, 3);
if (++nCallbacksCalled == nCallbacksNeeded)
done();
})
.catch(function(ex) {
done(ex);
});
}
var contentKeyName1;
// An initial test to confirm that keys are created for this time slot.
producer.createContentKey(testTime1, function(keys) {
try {
checkEncryptionKeys(keys, testTime1, testTimeRounded1, 3);
// Verify that we do not repeat the search for e-keys. The total
// expressInterestCallCount should be the same.
producer.createContentKey(testTime2, function(keys) {
checkEncryptionKeys(keys, testTime2, testTimeRounded2, 3);
try {
// Confirm that produce encrypts with the correct key and has the
// right name.
var testData = new Data();
producer.produce
(testData, testTime2, new Blob(DATA_CONTENT, false), function() {
try {
var producedName = testData.getName();
assert.ok(cKeyName.getPrefix(-1).equals(producedName.getSubName(0, 5)));
assert.ok(testTimeComponent2.equals(producedName.get(5)));
assert.ok(Encryptor.NAME_COMPONENT_FOR.equals(producedName.get(6)));
assert.ok(cKeyName.equals(producedName.getSubName(7, 6)));
assert.ok(testTimeRounded2.equals(producedName.get(13)));
var dataBlob = testData.getContent();
var dataContent = new EncryptedContent();
dataContent.wireDecode(dataBlob);
var encryptedData = dataContent.getPayload();
var initialVector = dataContent.getInitialVector();
var params = new EncryptParams(EncryptAlgorithmType.AesCbc, 16);
params.setInitialVector(initialVector);
var decryptTest = AesAlgorithm.decrypt(contentKey, encryptedData, params);
assert.ok(decryptTest.equals(new Blob(DATA_CONTENT, false)));
if (++nCallbacksCalled == nCallbacksNeeded)
done();
} catch (ex) { done(ex); }
}, function(errorCode, message) { done(new Error(message)); });
} catch (ex) { done(ex); }
}, function(contentKeyName2) {
try {
// Confirm content key names are correct.
assert.ok(cKeyName.equals(contentKeyName1.getPrefix(-1)));
assert.ok(testTimeRounded1.equals(contentKeyName1.get(6)));
assert.ok(cKeyName.equals(contentKeyName2.getPrefix(-1)));
assert.ok(testTimeRounded2.equals(contentKeyName2.get(6)));
if (++nCallbacksCalled == nCallbacksNeeded)
done();
} catch (ex) { done(ex); }
}, function(errorCode, message) { done(new Error(message)); });
} catch (ex) { done(ex); }
}, function(contentKeyName) {
contentKeyName1 = contentKeyName;
}, function(errorCode, message) { done(new Error(message)); });
});
it("ContentKeySearch", function(done) {
var timeMarkerFirstHop = new Name("20150101T070000/20150101T080000");
var timeMarkerSecondHop = new Name("20150101T080000/20150101T090000");
var timeMarkerThirdHop = new Name("20150101T100000/20150101T110000");
var prefix = new Name("/prefix");
var suffix = new Name("/suffix");
var expectedInterest = new Name(prefix);
expectedInterest.append(Encryptor.NAME_COMPONENT_READ);
expectedInterest.append(suffix);
expectedInterest.append(Encryptor.NAME_COMPONENT_E_KEY);
var cKeyName = new Name(prefix);
cKeyName.append(Encryptor.NAME_COMPONENT_SAMPLE);
cKeyName.append(suffix);
cKeyName.append(Encryptor.NAME_COMPONENT_C_KEY);
var testTime = Common.fromIsoString("20150101T100001");
// Create content keys required for this test case:
createEncryptionKey(expectedInterest, timeMarkerFirstHop);
createEncryptionKey(expectedInterest, timeMarkerSecondHop);
createEncryptionKey(expectedInterest, timeMarkerThirdHop);
var requestCount = 0;
// Prepare a TestFace to instantly answer calls to expressInterest.
var TestFace = function TestFace() {};
TestFace.prototype.expressInterest = function
(interest, onData, onTimeout, onNetworkNack)
{
try {
assert.ok(expectedInterest.equals(interest.getName()));
var gotInterestName = false;
var interestName = null;
for (var i = 0; i < 3; ++i) {
interestName = new Name(interest.getName());
if (i == 0)
interestName.append(timeMarkerFirstHop);
else if (i == 1)
interestName.append(timeMarkerSecondHop);
else if (i == 2)
interestName.append(timeMarkerThirdHop);
// matchesName will check the Exclude.
if (interest.matchesName(interestName)) {
gotInterestName = true;
++requestCount;
break;
}
}
if (gotInterestName)
onData(interest, encryptionKeys[interestName.toUri()]);
return 0;
} catch (ex) { done(ex); }
};
var face = new TestFace();
// Verify that if a key is found, but not within the right time slot, the
// search is refined until a valid time slot is found.
var testDb = new Sqlite3ProducerDb(databaseFilePath);
var producer = new Producer(prefix, suffix, face, keyChain, testDb);
producer.createContentKey
(testTime, function(result) {
try {
assert.equal(requestCount, 3);
assert.equal(result.length, 1);
var keyData = result[0];
var keyName = keyData.getName();
assert.ok(cKeyName.equals(keyName.getSubName(0, 4)));
assert.ok(timeMarkerThirdHop.get(0).equals(keyName.get(4)));
assert.ok(Encryptor.NAME_COMPONENT_FOR.equals(keyName.get(5)));
assert.ok(expectedInterest.append(timeMarkerThirdHop).equals
(keyName.getSubName(6)));
done();
} catch (ex) { done(ex); }
},
function(contentKeyName) {},
function(errorCode, message) { done(new Error(message)); });
});
it("ContentKeyTimeout", function(done) {
var prefix = new Name("/prefix");
var suffix = new Name("/suffix");
var expectedInterest = new Name(prefix);
expectedInterest.append(Encryptor.NAME_COMPONENT_READ);
expectedInterest.append(suffix);
expectedInterest.append(Encryptor.NAME_COMPONENT_E_KEY);
var testTime = Common.fromIsoString("20150101T100001");
var timeoutCount = 0;
// Prepare a TestFace to instantly answer calls to expressInterest.
var TestFace = function TestFace() {};
TestFace.prototype.expressInterest = function
(interest, onData, onTimeout, onNetworkNack)
{
try {
assert.ok(expectedInterest.equals(interest.getName()));
++timeoutCount;
onTimeout(interest);
return 0;
} catch (ex) { done(ex); }
};
var face = new TestFace();
// Verify that if no response is received, the producer appropriately times
// out. The result vector should not contain elements that have timed out.
var testDb = new Sqlite3ProducerDb(databaseFilePath);
var producer = new Producer(prefix, suffix, face, keyChain, testDb);
producer.createContentKey
(testTime, function(result) {
try {
assert.equal(timeoutCount, 4);
assert.equal(result.length, 0);
done();
} catch (ex) { done(ex); }
},
function(contentKeyName) {},
function(errorCode, message) { done(new Error(message)); });
});
it("ProducerWithLink", function(done) {
var prefix = new Name("/prefix");
var suffix = new Name("/suffix");
var expectedInterest = new Name(prefix);
expectedInterest.append(Encryptor.NAME_COMPONENT_READ);
expectedInterest.append(suffix);
expectedInterest.append(Encryptor.NAME_COMPONENT_E_KEY);
var testTime = Common.fromIsoString("20150101T100001");
var timeoutCount = 0;
// Prepare a TestFace to instantly answer calls to expressInterest.
var TestFace = function TestFace() {};
TestFace.prototype.expressInterest = function
(interest, onData, onTimeout, onNetworkNack)
{
try {
assert.ok(expectedInterest.equals(interest.getName()));
assert.equal(interest.getLink().getDelegations().size(), 3);
++timeoutCount;
onTimeout(interest);
return 0;
} catch (ex) { done(ex); }
};
var face = new TestFace();
// Verify that if no response is received, the producer appropriately times
// out. The result vector should not contain elements that have timed out.
var link = new Link();
link.addDelegation(10, new Name("/test1"));
link.addDelegation(20, new Name("/test2"));
link.addDelegation(100, new Name("/test3"));
keyChain.sign(link, certificateName);
var testDb = new Sqlite3ProducerDb(databaseFilePath);
var producer = new Producer(prefix, suffix, face, keyChain, testDb, 3, link);
producer.createContentKey
(testTime, function(result) {
try {
assert.equal(timeoutCount, 4);
assert.equal(result.length, 0);
done();
} catch (ex) { done(ex); }
},
function(contentKeyName) {},
function(errorCode, message) { done(new Error(message)); });
});
});