Source: security/v2/certificate-v2.js

/**
 * Copyright (C) 2017-2018 Regents of the University of California.
 * @author: Jeff Thompson <jefft0@remap.ucla.edu>
 * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/src/security/v2/certificate.hpp
 *
 * 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 Name = require('../../name.js').Name; /** @ignore */
var Data = require('../../data.js').Data; /** @ignore */
var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */
var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */
var Sha256WithRsaSignature = require('../../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */
var Sha256WithEcdsaSignature = require('../../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */
var ContentType = require('../../meta-info.js').ContentType; /** @ignore */
var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */
var Schedule = require('../../encrypt/schedule.js').Schedule; /** @ignore */
var ValidityPeriod = require('../validity-period.js').ValidityPeriod; /** @ignore */
var InvalidArgumentException = require('../security-exception.js').InvalidArgumentException;

/**
 * CertificateV2 represents a certificate following the certificate format
 * naming convention.
 *
 * Overview of the NDN certificate format:
 *
 *     CertificateV2 ::= DATA-TLV TLV-LENGTH
 *                         Name      (= /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version])
 *                         MetaInfo  (.ContentType = KEY)
 *                         Content   (= X509PublicKeyContent)
 *                         SignatureInfo (= CertificateV2SignatureInfo)
 *                         SignatureValue
 *
 *     X509PublicKeyContent ::= CONTENT-TLV TLV-LENGTH
 *                                BYTE+ (= public key bits in PKCS#8 format)
 *
 *     CertificateV2SignatureInfo ::= SIGNATURE-INFO-TYPE TLV-LENGTH
 *                                      SignatureType
 *                                      KeyLocator
 *                                      ValidityPeriod
 *                                      ... optional critical or non-critical extension blocks ...
 *
 * An example of NDN certificate name:
 *
 *     /edu/ucla/cs/yingdi/KEY/%03%CD...%F1/%9F%D3...%B7/%FD%d2...%8E
 *     \_________________/    \___________/ \___________/\___________/
 *    Certificate Namespace      Key Id       Issuer Id     Version
 *         (Identity)
 *     \__________________________________/
 *                   Key Name
 *
 * Notes:
 *
 * - `Key Id` is an opaque name component to identify the instance of the public
 *   key for the certificate namespace. The value of `Key ID` is controlled by
 *   the namespace owner. The library includes helpers for generating key IDs
 *   using an 8-byte random number, SHA-256 digest of the public key, timestamp,
 *   and the specified numerical identifiers.
 *
 * - `Issuer Id` is sn opaque name component to identify the issuer of the
 *   certificate. The value is controlled by the issuer. The library includes
 *   helpers to set issuer the ID to an 8-byte random number, SHA-256 digest of
 *   the issuer's public key, and the specified numerical identifiers.
 *
 * - `Key Name` is a logical name of the key used for management purposes. the
 *    Key Name includes the certificate namespace, keyword `KEY`, and `KeyId`
 *    components.
 *
 * @see https://github.com/named-data/ndn-cxx/blob/master/docs/specs/certificate-format.rst
 *
 * Create a CertificateV2 from the content in the Data packet (if not omitted).
 * @param {Data} data (optional) The data packet with the content to copy.
 * If omitted, create a CertificateV2 with content type KEY and default or
 * unspecified values.
 * @constructor
 */
var CertificateV2 = function CertificateV2(data)
{
  // Call the base constructor.
  if (data != undefined) {
    Data.call(this, data);
    this.checkFormat_();
  }
  else {
    Data.call(this);
    this.getMetaInfo().setType(ContentType.KEY);
  }
};

CertificateV2.prototype = new Data();
CertificateV2.prototype.name = "CertificateV2";

exports.CertificateV2 = CertificateV2;

/**
 * Create a new CertificateV2.Error to report an error for not complying with
 * the certificate format.
 * Call with: throw new CertificateV2.Error(new Error("message")).
 * @constructor
 * @param {Error} error The exception created with new Error.
 */
CertificateV2.Error = function CertificateV2Error(error)
{
  if (error) {
    error.__proto__ = CertificateV2.Error.prototype;
    return error;
  }
};

CertificateV2.Error.prototype = new Error();
CertificateV2.Error.prototype.name = "CertificateV2Error";

CertificateV2.prototype.checkFormat_ = function()
{
  if (!CertificateV2.isValidName(this.getName()))
    throw new CertificateV2.Error(new Error
      ("The Data Name does not follow the certificate naming convention"));

  if (this.getMetaInfo().getType() != ContentType.KEY)
    throw new CertificateV2.Error(new Error("The Data ContentType is not KEY"));

  if (this.getMetaInfo().getFreshnessPeriod() < 0.0)
    throw new CertificateV2.Error(new Error
      ("The Data FreshnessPeriod is not set"));

  if (this.getContent().size() == 0)
    throw new CertificateV2.Error(new Error("The Data Content is empty"));
};

/**
 * Get key name from the certificate name.
 * @return {Name} The key name as a new Name.
 */
CertificateV2.prototype.getKeyName = function()
{
  return this.getName().getPrefix(CertificateV2.KEY_ID_OFFSET + 1);
};

/**
 * Get the identity name from the certificate name.
 * @return {Name} The identity name as a new Name.
 */
CertificateV2.prototype.getIdentity = function()
{
  return this.getName().getPrefix(CertificateV2.KEY_COMPONENT_OFFSET);
};

/**
 * Get the key ID component from the certificate name.
 * @return {Name.Component} The key ID name component.
 */
CertificateV2.prototype.getKeyId = function()
{
  return this.getName().get(CertificateV2.KEY_ID_OFFSET);
};

/**
 * Get the issuer ID component from the certificate name.
 * @return {Name.Component} The issuer ID component.
 */
CertificateV2.prototype.getIssuerId = function()
{
  return this.getName().get(CertificateV2.ISSUER_ID_OFFSET);
};

/**
 * Get the public key DER encoding.
 * @return {Blob} The DER encoding Blob.
 * @throws CertificateV2.Error If the public key is not set.
 */
CertificateV2.prototype.getPublicKey = function()
{
  if (this.getContent().size() == 0)
    throw new CertificateV2.Error(new Error
      ("The public key is not set (the Data content is empty)"));

  return this.getContent();
};

/**
 * Get the certificate validity period from the SignatureInfo.
 * @return {ValidityPeriod} The ValidityPeriod object.
 * @throws InvalidArgumentException If the SignatureInfo doesn't have a
 * ValidityPeriod.
 */
CertificateV2.prototype.getValidityPeriod = function()
{
  if (!ValidityPeriod.canGetFromSignature(this.getSignature()))
    throw new InvalidArgumentException(new Error
      ("The SignatureInfo does not have a ValidityPeriod"));

  return ValidityPeriod.getFromSignature(this.getSignature());
};

/**
 * Check if the time falls within the validity period.
 * @param {number} time (optional) The time to check as milliseconds since
 * Jan 1, 1970 UTC. If omitted, use the current time.
 * @return {boolean} True if the beginning of the validity period is less than
 * or equal to time and time is less than or equal to the end of the validity
 * period.
 * @throws InvalidArgumentException If the SignatureInfo doesn't have a
 * ValidityPeriod.
 */
CertificateV2.prototype.isValid = function(time)
{
  return this.getValidityPeriod().isValid(time);
};

// TODO: getExtension

/**
 * Override to call the base class wireDecode then check the certificate format.
 * @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().
 */
CertificateV2.prototype.wireDecode = function(input, wireFormat)
{
  wireFormat = (wireFormat || WireFormat.getDefaultWireFormat());

  Data.prototype.wireDecode.call(this, input, wireFormat);
  this.checkFormat_();
};

/**
 * Get a string representation of this certificate.
 * @return {string} The string representation.
 */
CertificateV2.prototype.toString = function()
{
  var result = "";
  result += "Certificate name:\n";
  result += "  " + this.getName().toUri() + "\n";
  result += "Validity:\n";
  result += "  NotBefore: " + Schedule.toIsoString
    (this.getValidityPeriod().getNotBefore()) + "\n";
  result += "  NotAfter: " + Schedule.toIsoString
    (this.getValidityPeriod().getNotAfter()) + "\n";

  // TODO: Print the extension.

  result += "Public key bits:\n";
  try {
    var keyBase64 = this.getPublicKey().buf().toString('base64');
    for (var i = 0; i < keyBase64.length; i += 64)
      result += (keyBase64.substr(i, 64) + "\n");
  } catch (ex) {
    // No public key.
  }

  result += "Signature Information:\n";
  result += "  Signature Type: ";
  if (this.getSignature() instanceof Sha256WithEcdsaSignature)
    result += "SignatureSha256WithEcdsa\n";
  else if (this.getSignature() instanceof Sha256WithRsaSignature)
    result += "SignatureSha256WithRsa\n";
  else
    result += "<unknown>\n";

  if (KeyLocator.canGetFromSignature(this.getSignature())) {
    result += "  Key Locator: ";
    var keyLocator = KeyLocator.getFromSignature(this.getSignature());
    if (keyLocator.getType() == KeyLocatorType.KEYNAME) {
      if (keyLocator.getKeyName().equals(this.getKeyName()))
        result += "Self-Signed ";

      result += "Name=" + keyLocator.getKeyName().toUri() + "\n";
    }
    else
      result += "<no KeyLocator key name>\n";
  }

  return result;
};

/**
 * Check if certificateName follows the naming convention for a certificate.
 * @param {Name} certificateName The name of the certificate.
 * @return {boolean} True if certificateName follows the naming convention.
 */
CertificateV2.isValidName = function(certificateName)
{
  // /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version]
  return (certificateName.size() >= CertificateV2.MIN_CERT_NAME_LENGTH &&
          certificateName.get(CertificateV2.KEY_COMPONENT_OFFSET).equals
            (CertificateV2.KEY_COMPONENT));
};

/**
 * Extract the identity namespace from certificateName.
 * @param {Name} certificateName The name of the certificate.
 * @return {Name} The identity namespace as a new Name.
 */
CertificateV2.extractIdentityFromCertName = function(certificateName)
{
  if (!CertificateV2.isValidName(certificateName))
    throw new InvalidArgumentException(new Error
      ("Certificate name `" + certificateName.toUri() +
        "` does not follow the naming conventions"));

  return certificateName.getPrefix(CertificateV2.KEY_COMPONENT_OFFSET);
};

/**
 * Extract key name from certificateName.
 * @param {Name} certificateName The name of the certificate.
 * @return {Name} The key name as a new Name.
 */
CertificateV2.extractKeyNameFromCertName = function(certificateName)
{
  if (!CertificateV2.isValidName(certificateName)) {
    throw new InvalidArgumentException(new Error
      ("Certificate name `" + certificateName.toUri() +
        "` does not follow the naming conventions"));
  }

  // Trim everything after the key ID.
  return certificateName.getPrefix(CertificateV2.KEY_ID_OFFSET + 1);
};

CertificateV2.VERSION_OFFSET = -1;
CertificateV2.ISSUER_ID_OFFSET = -2;
CertificateV2.KEY_ID_OFFSET = -3;
CertificateV2.KEY_COMPONENT_OFFSET = -4;
CertificateV2.MIN_CERT_NAME_LENGTH = 4;
CertificateV2.MIN_KEY_NAME_LENGTH = 2;
CertificateV2.KEY_COMPONENT = new Name.Component("KEY");