ndn-js
Version:
A JavaScript client library for Named Data Networking
207 lines (183 loc) • 8.28 kB
JavaScript
/**
* Copyright (C) 2019 Regents of the University of California.
* @author: Jeff Thompson <jefft0@remap.ucla.edu>
* @author: From the NAC library https://github.com/named-data/name-based-access-control/blob/new/src/encryptor.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.
*
* 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.
*/
/** @ignore */
var Crypto = require('../crypto.js'); /** @ignore */
var Name = require('../name.js').Name; /** @ignore */
var Data = require('../data.js').Data; /** @ignore */
var RsaKeyParams = require('../security/key-params.js').RsaKeyParams; /** @ignore */
var KeyType = require('../security/security-types').KeyType; /** @ignore */
var SigningInfo = require('../security/signing-info.js').SigningInfo; /** @ignore */
var PublicKey = require('../security/certificate/public-key.js').PublicKey; /** @ignore */
var EncryptedContent = require('./encrypted-content.js').EncryptedContent; /** @ignore */
var EncryptAlgorithmType = require('./algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */
var InMemoryStorageRetaining = require('../in-memory-storage/in-memory-storage-retaining.js').InMemoryStorageRetaining; /** @ignore */
var NdnCommon = require('../util/ndn-common.js').NdnCommon; /** @ignore */
var EncryptorV2 = require('./encryptor-v2.js').EncryptorV2; /** @ignore */
var LOG = require('../log.js').Log.LOG;
/**
* AccessManagerV2 controls the decryption policy by publishing granular
* per-namespace access policies in the form of key encryption
* (KEK, plaintext public) and key decryption (KDK, encrypted private key)
* key pairs. This works with EncryptorV2 and DecryptorV2 using security v2.
* For the meaning of "KDK", etc. see:
* https://github.com/named-data/name-based-access-control/blob/new/docs/spec.rst
*
* Create an AccessManagerV2 to serve the NAC public key for other data
* producers to fetch, and to serve encrypted versions of the private keys
* (as safe bags) for authorized consumers to fetch.
*
* KEK and KDK naming:
*
* [identity]/NAC/[dataset]/KEK /[key-id] (== KEK, public key)
*
* [identity]/NAC/[dataset]/KDK/[key-id] /ENCRYPTED-BY/[user]/KEY/[key-id] (== KDK, encrypted private key)
*
* \_____________ ______________/
* \/
* registered with NFD
*
* @param {PibIdentity} identity The data owner's namespace identity. (This will
* be used to sign the KEK and KDK.)
* @param {Name} dataset The name of dataset that this manager is controlling.
* @param {KeyChain} keyChain The KeyChain used to sign Data packets.
* @param {Face} face The Face for calling registerPrefix that will be used to
* publish the KEK and KDK Data packets.
* @constructor
*/
var AccessManagerV2 = function AccessManagerV2(identity, dataset, keyChain, face)
{
this.identity_ = identity;
this.keyChain_ = keyChain;
this.face_ = face;
// storage_ is for the KEK and KDKs.
this.storage_ = new InMemoryStorageRetaining()
// The NAC identity is: <identity>/NAC/<dataset>
// Generate the NAC key.
// TODO: Use a Promise.
var nacIdentity = this.keyChain_.createIdentityV2
(new Name(identity.getName())
.append(EncryptorV2.NAME_COMPONENT_NAC).append(dataset),
new RsaKeyParams());
this.nacKey_ = nacIdentity.getDefaultKey();
if (this.nacKey_.getKeyType() != KeyType.RSA) {
if (LOG > 3) console.log
("Cannot re-use existing KEK/KDK pair, as it is not an RSA key, regenerating");
this.nacKey_ = this.keyChain_.createKey(nacIdentity, new RsaKeyParams());
}
var nacKeyId = this.nacKey_.getName().get(-1);
var kekPrefix = new Name(this.nacKey_.getIdentityName())
.append(EncryptorV2.NAME_COMPONENT_KEK);
var kekData = new Data(this.nacKey_.getDefaultCertificate());
kekData.setName(new Name(kekPrefix).append(nacKeyId));
kekData.getMetaInfo().setFreshnessPeriod
(AccessManagerV2.DEFAULT_KEK_FRESHNESS_PERIOD_MS);
// TODO: Use a Promise.
this.keyChain_.sign(kekData, new SigningInfo(this.identity_));
// A KEK looks like a certificate, but doesn't have a ValidityPeriod.
this.storage_.insert(kekData);
var thisEncryptor = this;
var serveFromStorage = function(prefix, interest, face, interestFilterId, filter) {
var data = thisEncryptor.storage_.find(interest);
if (data != null) {
if (LOG > 3) console.log
("Serving " + data.getName().toUri() + " from InMemoryStorage");
try {
face.putData(data);
} catch (ex) {
console.log("AccessManagerV2: Error in Face.putData: " +
NdnCommon.getErrorWithStackTrace(ex));
}
}
else {
if (LOG > 3) console.log
("Didn't find CK data for " + interest.getName().toUri());
// TODO: Send NACK?
}
};
var onRegisterFailed = function(prefix) {
if (LOG > 0) console.log("Failed to register prefix " + prefix.toUri());
};
this.kekRegisteredPrefixId_ = this.face_.registerPrefix
(kekPrefix, serveFromStorage, onRegisterFailed);
var kdkPrefix = new Name(this.nacKey_.getIdentityName())
.append(EncryptorV2.NAME_COMPONENT_KDK).append(nacKeyId);
this.kdkRegisteredPrefixId_ = this.face_.registerPrefix
(kdkPrefix, serveFromStorage, onRegisterFailed);
};
exports.AccessManagerV2 = AccessManagerV2;
AccessManagerV2.prototype.shutdown = function()
{
this.face_.unsetInterestFilter(this.kekRegisteredPrefixId_);
this.face_.unsetInterestFilter(this.kdkRegisteredPrefixId_);
};
/**
* Authorize a member identified by memberCertificate to decrypt data under
* the policy.
* @param {CertificateV2} memberCertificate The certificate that identifies the
* member to authorize.
* @return {Data} The published KDK Data packet.
*/
AccessManagerV2.prototype.addMember = function(memberCertificate)
{
var kdkName = new Name(this.nacKey_.getIdentityName());
kdkName
.append(EncryptorV2.NAME_COMPONENT_KDK)
.append(this.nacKey_.getName().get(-1)) // key-id
.append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY)
.append(memberCertificate.getKeyName());
var secretLength = 32;
var secret = Crypto.randomBytes(secretLength);
// To be compatible with OpenSSL which uses a null-terminated string,
// replace each 0 with 1. And to be compatible with the Java security
// library which interprets the secret as a char array converted to UTF8,
// limit each byte to the ASCII range 1 to 127.
for (var i = 0; i < secretLength; ++i) {
if (secret[i] == 0)
secret[i] = 1;
secret[i] &= 0x7f;
}
var kdkSafeBag = this.keyChain_.exportSafeBag
(this.nacKey_.getDefaultCertificate(), secret);
var memberKey = new PublicKey(memberCertificate.getPublicKey());
var encryptedContent = new EncryptedContent();
encryptedContent.setPayload(kdkSafeBag.wireEncode());
// Debug: Use a Promise.
encryptedContent.setPayloadKey(memberKey.encrypt
(secret, EncryptAlgorithmType.RsaOaep));
var kdkData = new Data(kdkName);
kdkData.setContent(encryptedContent.wireEncodeV2());
// FreshnessPeriod can serve as a soft access control for revoking access.
kdkData.getMetaInfo().setFreshnessPeriod
(AccessManagerV2.DEFAULT_KDK_FRESHNESS_PERIOD_MS);
// Debug: Use a Promise.
this.keyChain_.sign(kdkData, new SigningInfo(this.identity_));
this.storage_.insert(kdkData);
return kdkData;
};
/**
* Get the number of packets stored in in-memory storage.
* @return {number} The number of packets.
*/
AccessManagerV2.prototype.size = function()
{
return this.storage_.size();
};
AccessManagerV2.DEFAULT_KEK_FRESHNESS_PERIOD_MS = 3600 * 1000.0;
AccessManagerV2.DEFAULT_KDK_FRESHNESS_PERIOD_MS = 3600 * 1000.0;