Source: security/v2/trust-anchor-container.js

/**
 * Copyright (C) 2018 Regents of the University of California.
 * @author: Jeff Thompson <[email protected]>
 * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/src/security/v2/trust-anchor-container.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 Name = require('../../name.js').Name; /** @ignore */
var StaticTrustAnchorGroup = require('./static-trust-anchor-group.js').StaticTrustAnchorGroup; /** @ignore */
var DynamicTrustAnchorGroup = require('./dynamic-trust-anchor-group.js').DynamicTrustAnchorGroup; /** @ignore */
var CertificateV2 = require('./certificate-v2.js').CertificateV2; /** @ignore */
var CertificateContainerInterface = require('./certificate-container-interface.js').CertificateContainerInterface;

/**
 * A TrustAnchorContainer represents a container for trust anchors.
 *
 * There are two kinds of anchors:
 * static anchors that are permanent for the lifetime of the container, and
 * dynamic anchors that are periodically updated.
 *
 * Trust anchors are organized in groups. Each group has a unique group id.
 * The same anchor certificate (same name without considering the implicit
 * digest) can be inserted into multiple groups, but no more than once into each.
 *
 * Dynamic groups are created using the appropriate TrustAnchorContainer.insert
 * method. Once created, the dynamic anchor group cannot be updated.
 *
 * The returned pointer to Certificate from `find` methods is only guaranteed to
 * be valid until the next invocation of `find` and may be invalidated
 * afterwards.
 *
 * Create an empty TrustAnchorContainer.
 * @constructor
 */
var TrustAnchorContainer = function TrustAnchorContainer()
{
  // The key is the group ID string. The value is the TrustAnchorGroup.
  this.groups_ = {};
  this.anchors_ = new TrustAnchorContainer.AnchorContainer_();
};

exports.TrustAnchorContainer = TrustAnchorContainer;

/**
 * Create a TrustAnchorContainer.Error.
 * Call with: throw new TrustAnchorContainer.Error(new Error("message")).
 * @constructor
 * @param {Error} error The exception created with new Error.
 */
TrustAnchorContainer.Error = function TrustAnchorContainerError(error)
{
  if (error) {
    error.__proto__ = TrustAnchorContainer.Error.prototype;
    return error;
  }
};

TrustAnchorContainer.Error.prototype = new Error();
TrustAnchorContainer.Error.prototype.name = "TrustAnchorContainerError";

/**
 * There are two forms of insert:
 * insert(groupId, certificate) - Insert a static trust anchor. If the
 * certificate (having the same name without considering implicit digest)
 * already exists in the group with groupId, then do nothing.
 * insert(groupId, path, refreshPeriod, isDirectory) - Insert dynamic trust
 * anchors from the path.
 * @param {String} groupId The certificate group id.
 * @param {CertificateV2} certificate The certificate to insert, which is copied.
 * @param {String} path The path to load the trust anchors.
 * @param {number} refreshPeriod  The refresh time in milliseconds for the
 * anchors under path. This must be positive. The relevant trust anchors will
 * only be updated when find is called.
 * @param {boolean} isDirectory (optional) If true, then path is a directory. If
 * false or omitted, it is a single file.
 * @throws TrustAnchorContainer.Error If inserting a static trust anchor and
 * groupId is for a dynamic anchor group , or if inserting a dynamic trust
 * anchor and a group with groupId already exists.
 * @throws Error If refreshPeriod is not positive.
 */
TrustAnchorContainer.prototype.insert = function
  (groupId, certificateOrPath, refreshPeriod, isDirectory)
{
  if (certificateOrPath instanceof CertificateV2) {
    var certificate = certificateOrPath;

    var group = this.groups_[groupId];
    if (group === undefined) {
      group = new StaticTrustAnchorGroup(this.anchors_, groupId);
      this.groups_[groupId] = group;
    }

    if (!(group instanceof StaticTrustAnchorGroup))
      throw new TrustAnchorContainer.Error(new Error
        ("Cannot add a static anchor to the non-static anchor group " + groupId));

    group.add(certificate);
  }
  else {
    var path = certificateOrPath;

    if (isDirectory == null)
      isDirectory = false;

    if (this.groups_[groupId] !== undefined)
      throw new TrustAnchorContainer.Error(new Error
        ("Cannot create the dynamic group, because group " + groupId +
        " already exists"));

    this.groups_[groupId] = new DynamicTrustAnchorGroup
      (this.anchors_, groupId, path, refreshPeriod, isDirectory);
  }
};

/**
 * Remove all static and dynamic anchors.
 */
TrustAnchorContainer.prototype.clear = function()
{
  this.groups_ = {};
  this.anchors_.clear();
};

/**
 * There are two forms of find:
 * find(keyName) - Search for a certificate across all groups (longest prefix
 * match).
 * find(interest) - Find a certificate for the given interest. Note: Interests
 * with implicit digest are not supported.
 * @param {Name} keyName The key name prefix for searching for the certificate.
 * @param {Interest} interest The input interest packet.
 * @return {CertificateV2} The found certificate, or null if not found.
 */
TrustAnchorContainer.prototype.find = function(keyNameOrInterest)
{
  if (keyNameOrInterest instanceof Name) {
    var keyName = keyNameOrInterest;

    this.refresh_();

    var i = this.anchors_.findFirstByName_(keyName);
    if (i < 0)
      return null;
    var certificate = this.anchors_.anchorsByName_[i].certificate;
    if (!keyName.isPrefixOf(certificate.getName()))
      return null;
    return certificate;
  }
  else {
    var interest = keyNameOrInterest;

    this.refresh_();

    var i = this.anchors_.findFirstByName_(interest.getName());
    if (i < 0)
      return null;

    for (; i < this.anchors_.anchorsByName_.length; ++i) {
      var certificate = this.anchors_.anchorsByName_[i].certificate;
      if (!interest.getName().isPrefixOf(certificate.getName()))
        break;
      if (interest.matchesData(certificate))
        return certificate;
    }

    return null;
  }
};

/**
 * Get the trust anchor group for the groupId.
 * @param {String} groupId The group ID.
 * @return {TrustAnchorGroup} The trust anchor group.
 * @throws TrustAnchorContainer.Error if the groupId does not exist.
 */
TrustAnchorContainer.prototype.getGroup = function(groupId)
{
  var group = this.groups_[groupId];
  if (group === undefined)
    throw new TrustAnchorContainer.Error(new Error
      ("Trust anchor group " + groupId + " does not exist"));

  return group;
};

/**
 * Get the number of trust anchors across all groups.
 * @return {number} The number of trust anchors.
 */
TrustAnchorContainer.prototype.size = function()
{
  return this.anchors_.size();
};

TrustAnchorContainer.AnchorContainer_ = function TrustAnchorContainerAnchorContainer()
{
  // Array of objects with fields "name" of type Name and "certificate" of type
  // CertificateV2. We can't use an {} object since the Name key itself is an
  // object, and also it needs to be sorted by Name.
  this.anchorsByName_ = [];
};

TrustAnchorContainer.AnchorContainer_.prototype = new CertificateContainerInterface();
TrustAnchorContainer.AnchorContainer_.prototype.name = "TrustAnchorContainerAnchorContainer";

/**
 * Add the certificate to the container.
 * @param {CertificateV2} certificate The certificate to add, which is copied.
 */
TrustAnchorContainer.AnchorContainer_.prototype.add = function(certificate)
{
  var certificateCopy = new CertificateV2(certificate);

  var name = certificateCopy.getName();
  var i = this.findFirstByName_(name);
  if (i < 0)
    // Not found, so set to insert at the end of the list.
    i = this.anchorsByName_.length;
  else {
    if (this.anchorsByName_[i].name.equals(name)) {
      // Just replace the existing entry value.
      this.anchorsByName_[i].certificate = certificateCopy;
      return;
    }
  }

  this.anchorsByName_.splice(i, 0, {name: name, certificate: certificateCopy});
};

/**
 * Remove the certificate with the given name. If the name does not exist, do
 * nothing.
 * @param {Name} certificateName The name of the certificate.
 */
TrustAnchorContainer.AnchorContainer_.prototype.remove = function(certificateName)
{
  for (var i = 0; i < this.anchorsByName_.length; ++i) {
    if (this.anchorsByName_[i].name.equals(certificateName)) {
      this.anchorsByName_.splice(i, 1);
      return;
    }
  }
};

/**
 * Clear all certificates.
 */
TrustAnchorContainer.AnchorContainer_.prototype.clear = function()
{
  this.anchorsByName_ = [];
};

TrustAnchorContainer.AnchorContainer_.prototype.size = function()
{
  return this.anchorsByName_.length;
};

/**
 * A private helper method to get the first entry in anchorsByName_ whose
 * name is greater than or equal to the given name.
 * @param {Name} name The name to search for.
 * @return {number} The index of the found anchorsByName_ entry, or -1 if
 * not found.
 */
TrustAnchorContainer.AnchorContainer_.prototype.findFirstByName_ = function(name)
{
  for (var i = 0; i < this.anchorsByName_.length; ++i) {
    if (this.anchorsByName_[i].name.compare(name) >= 0)
      return i;
  }

  return -1;
};

TrustAnchorContainer.prototype.refresh_ = function()
{
  for (var groupId in this.groups_)
    this.groups_[groupId].refresh();
};