/**
* Copyright (C) 2014-2015 Regents of the University of California.
* @author: Jeff Thompson <[email protected]>
* From ndn-cxx security by Yingdi Yu <[email protected]>.
*
* 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.
*/
var Name = require('../name.js').Name;
var Interest = require('../interest.js').Interest;
var Data = require('../data.js').Data;
var KeyLocatorType = require('../key-locator.js').KeyLocatorType;
var Sha256WithRsaSignature = require('../sha256-with-rsa-signature.js').Sha256WithRsaSignature;
var WireFormat = require('../encoding/wire-format.js').WireFormat;
var Tlv = require('../encoding/tlv/tlv.js').Tlv;
var TlvEncoder = require('../encoding/tlv/tlv-encoder.js').TlvEncoder;
var SecurityException = require('./security-exception.js').SecurityException;
var RsaKeyParams = require('./key-params.js').RsaKeyParams;
var IdentityCertificate = require('./certificate/identity-certificate.js').IdentityCertificate;
/**
* A KeyChain provides a set of interfaces to the security library such as
* identity management, policy configuration and packet signing and verification.
* Note: This class is an experimental feature. See the API docs for more detail at
* http://named-data.net/doc/ndn-ccl-api/key-chain.html .
*
* Create a new KeyChain with the given IdentityManager and PolicyManager.
* @param {IdentityManager} identityManager An object of a subclass of
* IdentityManager.
* @param {PolicyManager} policyManager An object of a subclass of
* PolicyManager.
* @constructor
*/
var KeyChain = function KeyChain(identityManager, policyManager)
{
this.identityManager = identityManager;
this.policyManager = policyManager;
this.face = null;
this.maxSteps = 100;
};
exports.KeyChain = KeyChain;
/*****************************************
* Identity Management *
*****************************************/
/**
* Create an identity by creating a pair of Key-Signing-Key (KSK) for this
* identity and a self-signed certificate of the KSK.
* @param {Name} identityName The name of the identity.
* @param {KeyParams} params (optional) The key parameters if a key needs to be
* generated for the identity. If omitted, use KeyChain.DEFAULT_KEY_PARAMS.
* @returns {Name} The name of the certificate for the auto-generated KSK of the
* identity.
*/
KeyChain.prototype.createIdentityAndCertificate = function(identityName, params)
{
if (params == undefined)
params = KeyChain.DEFAULT_KEY_PARAMS;
return this.identityManager.createIdentityAndCertificate(identityName, params);
};
/**
* Create an identity by creating a pair of Key-Signing-Key (KSK) for this
* identity and a self-signed certificate of the KSK.
* @deprecated Use createIdentityAndCertificate which returns the
* certificate name instead of the key name. You can use
* IdentityCertificate.certificateNameToPublicKeyName to convert the
* certificate name to the key name.
* @param {Name} identityName The name of the identity.
* @param {KeyParams} params (optional) The key parameters if a key needs to be
* generated for the identity. If omitted, use KeyChain.DEFAULT_KEY_PARAMS.
* @returns {Name} The key name of the auto-generated KSK of the identity.
*/
KeyChain.prototype.createIdentity = function(identityName, params)
{
return IdentityCertificate.certificateNameToPublicKeyName
(this.createIdentityAndCertificate(identityName, params));
};
/**
* Delete the identity from the public and private key storage. If the
* identity to be deleted is the current default system default, this will not
* delete the identity and will return immediately.
* @param identityName {Name} The name of the identity.
*/
KeyChain.prototype.deleteIdentity = function(identityName)
{
this.identityManager.deleteIdentity(identityName);
};
/**
* Get the default identity.
* @returns {Name} The name of default identity.
* @throws SecurityException if the default identity is not set.
*/
KeyChain.prototype.getDefaultIdentity = function()
{
return this.identityManager.getDefaultIdentity();
};
/**
* Get the default certificate name of the default identity.
* @returns {Name} The requested certificate name.
* @throws SecurityException if the default identity is not set or the default
* key name for the identity is not set or the default certificate name for
* the key name is not set.
*/
KeyChain.prototype.getDefaultCertificateName = function()
{
return this.identityManager.getDefaultCertificateName();
};
/**
* Generate a pair of RSA keys for the specified identity.
* @param {Name} identityName The name of the identity.
* @param {boolean} isKsk (optional) true for generating a Key-Signing-Key (KSK),
* false for a Data-Signing-Key (DSK). If omitted, generate a Data-Signing-Key.
* @param {number} keySize (optional) The size of the key. If omitted, use a
* default secure key size.
* @returns {Name} The generated key name.
*/
KeyChain.prototype.generateRSAKeyPair = function(identityName, isKsk, keySize)
{
return this.identityManager.generateRSAKeyPair(identityName, isKsk, keySize);
};
/**
* Set a key as the default key of an identity.
* @param {Name} keyName The name of the key.
* @param {Name} identityName (optional) the name of the identity. If not
* specified, the identity name is inferred from the keyName.
*/
KeyChain.prototype.setDefaultKeyForIdentity = function(keyName, identityName)
{
if (identityName == null)
identityName = new Name();
return this.identityManager.setDefaultKeyForIdentity(keyName, identityName);
};
/**
* Generate a pair of RSA keys for the specified identity and set it as default
* key for the identity.
* @param {Name} identityName The name of the identity.
* @param {boolean} isKsk (optional) true for generating a Key-Signing-Key (KSK),
* false for a Data-Signing-Key (DSK). If omitted, generate a Data-Signing-Key.
* @param {number} keySize (optional) The size of the key. If omitted, use a
* default secure key size.
* @returns {Name} The generated key name.
*/
KeyChain.prototype.generateRSAKeyPairAsDefault = function
(identityName, isKsk, keySize)
{
return this.identityManager.generateRSAKeyPairAsDefault
(identityName, isKsk, keySize);
};
/**
* Create a public key signing request.
* @param {Name} keyName The name of the key.
* @returns {Blob} The signing request data.
*/
KeyChain.prototype.createSigningRequest = function(keyName)
{
return this.identityManager.getPublicKey(keyName).getKeyDer();
};
/**
* Install an identity certificate into the public key identity storage.
* @param {IdentityCertificate} certificate The certificate to to added.
*/
KeyChain.prototype.installIdentityCertificate = function(certificate)
{
this.identityManager.addCertificate(certificate);
};
/**
* Set the certificate as the default for its corresponding key.
* @param {IdentityCertificate} certificate The certificate.
*/
KeyChain.prototype.setDefaultCertificateForKey = function(certificate)
{
this.identityManager.setDefaultCertificateForKey(certificate);
};
/**
* Get a certificate with the specified name.
* @param {Name} certificateName The name of the requested certificate.
* @returns {IdentityCertificate} The requested certificate which is valid.
*/
KeyChain.prototype.getCertificate = function(certificateName)
{
return this.identityManager.getCertificate(certificateName);
};
/**
* Get a certificate even if the certificate is not valid anymore.
* @param {Name} certificateName The name of the requested certificate.
* @returns {IdentityCertificate} The requested certificate.
*/
KeyChain.prototype.getAnyCertificate = function(certificateName)
{
return this.identityManager.getAnyCertificate(certificateName);
};
/**
* Get an identity certificate with the specified name.
* @param {Name} certificateName The name of the requested certificate.
* @returns {IdentityCertificate} The requested certificate which is valid.
*/
KeyChain.prototype.getIdentityCertificate = function(certificateName)
{
return this.identityManager.getCertificate(certificateName);
};
/**
* Get an identity certificate even if the certificate is not valid anymore.
* @param {Name} certificateName The name of the requested certificate.
* @returns {IdentityCertificate} The requested certificate.
*/
KeyChain.prototype.getAnyIdentityCertificate = function(certificateName)
{
return this.identityManager.getAnyCertificate(certificateName);
};
/**
* Revoke a key.
* @param {Name} keyName The name of the key that will be revoked.
*/
KeyChain.prototype.revokeKey = function(keyName)
{
//TODO: Implement
};
/**
* Revoke a certificate.
* @param {Name} certificateName The name of the certificate that will be
* revoked.
*/
KeyChain.prototype.revokeCertificate = function(certificateName)
{
//TODO: Implement
};
/**
* Get the identity manager given to or created by the constructor.
* @returns {IdentityManager} The identity manager.
*/
KeyChain.prototype.getIdentityManager = function()
{
return this.identityManager;
};
/*****************************************
* Policy Management *
*****************************************/
/**
* Get the policy manager given to or created by the constructor.
* @returns {PolicyManager} The policy manager.
*/
KeyChain.prototype.getPolicyManager = function()
{
return this.policyManager;
};
/*****************************************
* Sign/Verify *
*****************************************/
/**
* Sign the target. If it is a Data or Interest object, set its signature. If it
* is an array, produce a Signature object.
* @param {Data|Interest|Buffer} target If this is a Data object, wire encode for
* signing, update its signature and key locator field and wireEncoding. If this
* is an Interest object, wire encode for signing, append a SignatureInfo to the
* Interest name, sign the name components and append a final name component
* with the signature bits. If it is an array, sign it and produce a Signature
* object.
* @param {Name} certificateName The certificate name of the key to use for
* signing.
* @param {WireFormat} wireFormat (optional) A WireFormat object used to encode
* the input. If omitted, use WireFormat getDefaultWireFormat().
* @param {function} onComplete (optional) If target is a Data object, this calls
* onComplete(data) with the supplied Data object which has been modified to set
* its signature. If target is an Interest object, this calls
* onComplete(interest) with the supplied Interest object which has been
* modified to set its signature. If target is a Buffer, this calls
* onComplete(signature) where signature is the produced Signature object. If
* omitted, the return value is described below. (Some crypto libraries only use
* a callback, so onComplete is required to use these.)
* @returns {Signature} If onComplete is omitted, return the generated Signature
* object (if target is a Buffer) or null (if target is Data or Interest).
* Otherwise, if onComplete is supplied then return null and use onComplete as
* described above.
*/
KeyChain.prototype.sign = function(target, certificateName, wireFormat, onComplete)
{
if (target instanceof Interest)
return this.identityManager.signInterestByCertificate
(target, certificateName, wireFormat, onComplete);
else if (target instanceof Data)
return this.identityManager.signByCertificate
(target, certificateName, wireFormat, onComplete);
else
return this.identityManager.signByCertificate
(target, certificateName, onComplete);
};
/**
* Sign the target. If it is a Data object, set its signature. If it is an
* array, produce a signature object.
* @param {Data|Buffer} target If this is a Data object, wire encode for
* signing, update its signature and key locator field and wireEncoding. If it
* is an array, sign it and return a Signature object.
* @param identityName (optional) The identity name for the key to use for
* signing. If omitted, infer the signing identity from the data packet name.
* @param wireFormat (optional) A WireFormat object used to encode the input. If
* omitted, use WireFormat getDefaultWireFormat().
* @param {function} onComplete (optional) If target is a Data object, this calls
* onComplete(data) with the supplied Data object which has been modified to set
* its signature. If target is a Buffer, this calls
* onComplete(signature) where signature is the produced Signature object. If
* omitted, the return value is described below. (Some crypto libraries only use
* a callback, so onComplete is required to use these.)
* @returns {Signature} If onComplete is omitted, return the generated Signature
* object (if target is a Buffer) or null (if target is Data).
* Otherwise, if onComplete is supplied then return null and use onComplete as
* described above.
*/
KeyChain.prototype.signByIdentity = function
(target, identityName, wireFormat, onComplete)
{
if (identityName == null)
identityName = new Name();
if (target instanceof Data) {
var data = target;
var signingCertificateName;
if (identityName.size() == 0) {
var inferredIdentity = this.policyManager.inferSigningIdentity
(data.getName());
if (inferredIdentity.size() == 0)
signingCertificateName = this.identityManager.getDefaultCertificateName();
else
signingCertificateName =
this.identityManager.getDefaultCertificateNameForIdentity
(inferredIdentity);
}
else
signingCertificateName =
this.identityManager.getDefaultCertificateNameForIdentity(identityName);
if (signingCertificateName.size() == 0)
throw new SecurityException(new Error
("No qualified certificate name found!"));
if (!this.policyManager.checkSigningPolicy
(data.getName(), signingCertificateName))
throw new SecurityException(new Error
("Signing Cert name does not comply with signing policy"));
return this.identityManager.signByCertificate
(data, signingCertificateName, wireFormat, onComplete);
}
else {
var array = target;
var signingCertificateName =
this.identityManager.getDefaultCertificateNameForIdentity(identityName);
if (signingCertificateName.size() == 0)
throw new SecurityException(new Error
("No qualified certificate name found!"));
return this.identityManager.signByCertificate
(array, signingCertificateName, onComplete);
}
};
/**
* Sign the target using DigestSha256.
* @param {Data|Interest} target If this is a Data object, wire encode for
* signing, digest it and set its SignatureInfo to a DigestSha256, updating its
* signature and wireEncoding. If this is an Interest object, wire encode for
* signing, append a SignatureInfo for DigestSha256 to the Interest name, digest
* the name components and append a final name component with the signature bits.
* @param {WireFormat} wireFormat (optional) A WireFormat object used to encode
* the input. If omitted, use WireFormat getDefaultWireFormat().
*/
KeyChain.prototype.signWithSha256 = function(target, wireFormat)
{
if (target instanceof Interest)
this.identityManager.signInterestWithSha256(target, wireFormat);
else
this.identityManager.signWithSha256(target, wireFormat);
};
/**
* Check the signature on the Data object and call either onVerify or
* onVerifyFailed. We use callback functions because verify may fetch
* information to check the signature.
* @param {Data} data The Data object with the signature to check.
* @param {function} onVerified If the signature is verified, this calls
* onVerified(data).
* @param {function} onVerifyFailed If the signature check fails, this calls
* onVerifyFailed(data).
* @param {number} stepCount
*/
KeyChain.prototype.verifyData = function
(data, onVerified, onVerifyFailed, stepCount)
{
if (this.policyManager.requireVerify(data)) {
var nextStep = this.policyManager.checkVerificationPolicy
(data, stepCount, onVerified, onVerifyFailed);
if (nextStep != null) {
var thisKeyChain = this;
this.face.expressInterest
(nextStep.interest,
function(callbackInterest, callbackData) {
thisKeyChain.onCertificateData(callbackInterest, callbackData, nextStep);
},
function(callbackInterest) {
thisKeyChain.onCertificateInterestTimeout
(callbackInterest, nextStep.retry, onVerifyFailed, data, nextStep);
});
}
}
else if (this.policyManager.skipVerifyAndTrust(data))
onVerified(data);
else
onVerifyFailed(data);
};
/**
* Check the signature on the signed interest and call either onVerify or
* onVerifyFailed. We use callback functions because verify may fetch
* information to check the signature.
* @param {Interest} interest The interest with the signature to check.
* @param {function} onVerified If the signature is verified, this calls
* onVerified(interest).
* @param {function} onVerifyFailed If the signature check fails, this calls
* onVerifyFailed(interest).
*/
KeyChain.prototype.verifyInterest = function
(interest, onVerified, onVerifyFailed, stepCount, wireFormat)
{
wireFormat = (wireFormat || WireFormat.getDefaultWireFormat());
if (this.policyManager.requireVerify(interest)) {
var nextStep = this.policyManager.checkVerificationPolicy
(interest, stepCount, onVerified, onVerifyFailed, wireFormat);
if (nextStep != null) {
var thisKeyChain = this;
this.face.expressInterest
(nextStep.interest,
function(callbackInterest, callbackData) {
thisKeyChain.onCertificateData(callbackInterest, callbackData, nextStep);
},
function(callbackInterest) {
thisKeyChain.onCertificateInterestTimeout
(callbackInterest, nextStep.retry, onVerifyFailed, data, nextStep);
});
}
}
else if (this.policyManager.skipVerifyAndTrust(interest))
onVerified(interest);
else
onVerifyFailed(interest);
};
/**
* Set the Face which will be used to fetch required certificates.
* @param {Face} face A pointer to the Face object.
*/
KeyChain.prototype.setFace = function(face)
{
this.face = face;
};
KeyChain.DEFAULT_KEY_PARAMS = new RsaKeyParams();
KeyChain.prototype.onCertificateData = function(interest, data, nextStep)
{
// Try to verify the certificate (data) according to the parameters in nextStep.
this.verifyData
(data, nextStep.onVerified, nextStep.onVerifyFailed, nextStep.stepCount);
};
KeyChain.prototype.onCertificateInterestTimeout = function
(interest, retry, onVerifyFailed, originalDataOrInterest, nextStep)
{
if (retry > 0) {
// Issue the same expressInterest as in verifyData except decrement retry.
var thisKeyChain = this;
this.face.expressInterest
(interest,
function(callbackInterest, callbackData) {
thisKeyChain.onCertificateData(callbackInterest, callbackData, nextStep);
},
function(callbackInterest) {
thisKeyChain.onCertificateInterestTimeout
(callbackInterest, retry - 1, onVerifyFailed, originalDataOrInterest, nextStep);
});
}
else
onVerifyFailed(originalDataOrInterest);
};