/**
* 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 Data = require('../../data.js').Data;
var ContentType = require('../../meta-info.js').ContentType;
var WireFormat = require('../../encoding/wire-format.js').WireFormat;
var DerNode = require('../../encoding/der/der-node.js').DerNode;
var KeyType = require('../../security/security-types.js').KeyType;
var PublicKey = require('./public-key.js').PublicKey;
var CertificateSubjectDescription = require('./certificate-subject-description.js').CertificateSubjectDescription;
var CertificateExtension = require('./certificate-extension.js').CertificateExtension;
/**
* Create a Certificate from the content in the data packet (if not omitted).
* @param {Data} data (optional) The data packet with the content to decode.
* If omitted, create a Certificate with default values and the Data content
* is empty.
*/
var Certificate = function Certificate(data)
{
// Call the base constructor.
if (data != undefined)
Data.call(this, data);
else
Data.call(this);
this.subjectDescriptionList = []; // of CertificateSubjectDescription
this.extensionList = []; // of CertificateExtension
this.notBefore = Number.MAX_VALUE; // MillisecondsSince1970
this.notAfter = -Number.MAX_VALUE; // MillisecondsSince1970
this.key = new PublicKey();
if (data != undefined)
this.decode();
};
Certificate.prototype = new Data();
Certificate.prototype.name = "Certificate";
exports.Certificate = Certificate;
/**
* Encode the contents of the certificate in DER format and set the Content
* and MetaInfo fields.
*/
Certificate.prototype.encode = function()
{
var root = this.toDer();
this.setContent(root.encode());
this.getMetaInfo().setType(ContentType.KEY);
};
/**
* Add a subject description.
* @param {CertificateSubjectDescription} description The description to be added.
*/
Certificate.prototype.addSubjectDescription = function(description)
{
this.subjectDescriptionList.push(description);
};
/**
* Get the subject description list.
* @returns {Array<CertificateSubjectDescription>} The subject description list.
*/
Certificate.prototype.getSubjectDescriptionList = function()
{
return this.subjectDescriptionList;
};
/**
* Add a certificate extension.
* @param {CertificateSubjectDescription} extension The extension to be added.
*/
Certificate.prototype.addExtension = function(extension)
{
this.extensionList.push(extension);
};
/**
* Get the certificate extension list.
* @returns {Array<CertificateExtension>} The extension list.
*/
Certificate.prototype.getExtensionList = function()
{
return this.extensionList;
};
Certificate.prototype.setNotBefore = function(notBefore)
{
this.notBefore = notBefore;
};
Certificate.prototype.getNotBefore = function()
{
return this.notBefore;
};
Certificate.prototype.setNotAfter = function(notAfter)
{
this.notAfter = notAfter;
};
Certificate.prototype.getNotAfter = function()
{
return this.notAfter;
};
Certificate.prototype.setPublicKeyInfo = function(key)
{
this.key = key;
};
Certificate.prototype.getPublicKeyInfo = function()
{
return this.key;
};
/**
* Check if the certificate is valid.
* @returns {Boolean} True if the current time is earlier than notBefore.
*/
Certificate.prototype.isTooEarly = function()
{
var now = new Date().getTime();
return now < this.notBefore;
};
/**
* Check if the certificate is valid.
* @returns {Boolean} True if the current time is later than notAfter.
*/
Certificate.prototype.isTooLate = function()
{
var now = new Date().getTime();
return now > this.notAfter;
};
/**
* Encode the certificate fields in DER format.
* @returns {DerSequence} The DER encoded contents of the certificate.
*/
Certificate.prototype.toDer = function()
{
var root = new DerNode.DerSequence();
var validity = new DerNode.DerSequence();
var notBefore = new DerNode.DerGeneralizedTime(this.notBefore);
var notAfter = new DerNode.DerGeneralizedTime(this.notAfter);
validity.addChild(notBefore);
validity.addChild(notAfter);
root.addChild(validity);
var subjectList = new DerNode.DerSequence();
for (var i = 0; i < this.subjectDescriptionList.length; ++i)
subjectList.addChild(this.subjectDescriptionList[i].toDer());
root.addChild(subjectList);
root.addChild(this.key.toDer());
if (this.extensionList.length > 0) {
var extensionList = new DerNode.DerSequence();
for (var i = 0; i < this.extensionList.length; ++i)
extensionList.addChild(this.extensionList[i].toDer());
root.addChild(extensionList);
}
return root;
};
/**
* Populate the fields by the decoding DER data from the Content.
*/
Certificate.prototype.decode = function()
{
var root = DerNode.parse(this.getContent().buf());
// We need to ensure that there are:
// validity (notBefore, notAfter)
// subject list
// public key
// (optional) extension list
var rootChildren = root.getChildren();
// 1st: validity info
var validityChildren = DerNode.getSequence(rootChildren, 0).getChildren();
this.notBefore = validityChildren[0].toVal();
this.notAfter = validityChildren[1].toVal();
// 2nd: subjectList
var subjectChildren = DerNode.getSequence(rootChildren, 1).getChildren();
for (var i = 0; i < subjectChildren.length; ++i) {
var sd = DerNode.getSequence(subjectChildren, i);
var descriptionChildren = sd.getChildren();
var oidStr = descriptionChildren[0].toVal();
var value = descriptionChildren[1].toVal().buf().toString('binary');
this.addSubjectDescription(new CertificateSubjectDescription(oidStr, value));
}
// 3rd: public key
var publicKeyInfo = rootChildren[2].encode();
this.key = new PublicKey(publicKeyInfo);
if (rootChildren.length > 3) {
var extensionChildren = DerNode.getSequence(rootChildren, 3).getChildren();
for (var i = 0; i < extensionChildren.length; ++i) {
var extInfo = DerNode.getSequence(extensionChildren, i);
var children = extInfo.getChildren();
var oidStr = children[0].toVal();
var isCritical = children[1].toVal();
var value = children[2].toVal();
this.addExtension(new CertificateExtension(oidStr, isCritical, value));
}
}
};
/**
* Override to call the base class wireDecode then populate the certificate
* fields.
* @param {Blob|Buffer} input The buffer with the bytes to decode.
* @param {WireFormat} wireFormat (optional) A WireFormat object used to decode
* this object. If omitted, use WireFormat.getDefaultWireFormat().
*/
Certificate.prototype.wireDecode = function(input, wireFormat)
{
wireFormat = (wireFormat || WireFormat.getDefaultWireFormat());
Data.prototype.wireDecode.call(this, input, wireFormat);
this.decode();
};
Certificate.prototype.toString = function()
{
var s = "Certificate name:\n";
s += " " + this.getName().toUri() + "\n";
s += "Validity:\n";
var notBeforeStr = Certificate.toIsoString(Math.round(this.notBefore));
var notAfterStr = Certificate.toIsoString(Math.round(this.notAfter));
s += " NotBefore: " + notBeforeStr + "\n";
s += " NotAfter: " + notAfterStr + "\n";
for (var i = 0; i < this.subjectDescriptionList.length; ++i) {
var sd = this.subjectDescriptionList[i];
s += "Subject Description:\n";
s += " " + sd.getOidString() + ": " + sd.getValue() + "\n";
}
s += "Public key bits:\n";
var keyDer = this.key.getKeyDer();
var encodedKey = keyDer.buf().toString('base64');
for (var i = 0; i < encodedKey.length; i += 64)
s += encodedKey.substring(i, Math.min(i + 64, encodedKey.length)) + "\n";
if (this.extensionList.length > 0) {
s += "Extensions:\n";
for (var i = 0; i < this.extensionList.length; ++i) {
var ext = this.extensionList[i];
s += " OID: " + ext.getOid() + "\n";
s += " Is critical: " + (ext.getIsCritical() ? 'Y' : 'N') + "\n";
s += " Value: " + ext.getValue().toHex() + "\n" ;
}
}
return s;
};
/**
* Convert a UNIX timestamp to ISO time representation with the "T" in the middle.
* @param {type} msSince1970 Timestamp as milliseconds since Jan 1, 1970.
* @returns {string} The string representation.
*/
Certificate.toIsoString = function(msSince1970)
{
var utcTime = new Date(Math.round(msSince1970));
return utcTime.getUTCFullYear() +
Certificate.to2DigitString(utcTime.getUTCMonth() + 1) +
Certificate.to2DigitString(utcTime.getUTCDate()) +
"T" +
Certificate.to2DigitString(utcTime.getUTCHours()) +
Certificate.to2DigitString(utcTime.getUTCMinutes()) +
Certificate.to2DigitString(utcTime.getUTCSeconds());
};
/**
* A private method to zero pad an integer to 2 digits.
* @param {number} x The number to pad. Assume it is a non-negative integer.
* @returns {string} The padded string.
*/
Certificate.to2DigitString = function(x)
{
var result = x.toString();
return result.length === 1 ? "0" + result : result;
};