Source: security/v2/validator.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/validator.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 SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */
var ValidationError = require('./validation-error.js').ValidationError; /** @ignore */
var CertificateFetcherOffline = require('./certificate-fetcher-offline.js').CertificateFetcherOffline; /** @ignore */
var CertificateStorage = require('./certificate-storage.js').CertificateStorage; /** @ignore */
var DataValidationState = require('./data-validation-state.js').DataValidationState; /** @ignore */
var InterestValidationState = require('./interest-validation-state.js').InterestValidationState; /** @ignore */
var Data = require('../../data.js').Data; /** @ignore */
var LOG = require('../../log.js').Log.LOG;

/**
 * The Validator class provides an interface for validating data and interest
 * packets.
 *
 * Every time a validation process is initiated, it creates a ValidationState
 * that exists until the validation finishes with either success or failure.
 * This state serves several purposes:
 * to record the Interest or Data packet being validated,
 * to record the failure callback,
 * to record certificates in the certification chain for the Interest or Data
 * packet being validated,
 * to record the names of the requested certificates in order to detect loops in
 * the certificate chain,
 * and to keep track of the validation chain size (also known as the validation
 * "depth").
 *
 * During validation, the policy and/or key fetcher can augment the validation
 * state with policy- and fetcher-specific information using tags.
 *
 * A Validator has a trust anchor cache to save static and dynamic trust
 * anchors, a verified certificate cache for saving certificates that are
 * already verified, and an unverified certificate cache for saving pre-fetched
 * but not yet verified certificates.
 *
 * Create a Validator with the policy and fetcher.
 * @param {ValidationPolicy} policy The validation policy to be associated with
 * this validator.
 * @param {CertificateFetcher} certificateFetcher (optional) The certificate
 * fetcher implementation. If omitted, use a CertificateFetcherOffline (assuming
 * that the validation policy doesn't need to fetch certificates).
 * @constructor
 */
var Validator = function Validator(policy, certificateFetcher)
{
  // Call the base constructor.
  CertificateStorage.call(this);

  if (certificateFetcher == undefined)
    certificateFetcher = new CertificateFetcherOffline();

  this.policy_ = policy;
  this.certificateFetcher_ = certificateFetcher;
  this.maxDepth_ = 25;

  if (this.policy_ == null)
    throw new Error("The policy is null");
  if (this.certificateFetcher_ == null)
    throw new Error("The certificateFetcher is null");

  this.policy_.setValidator(this);
  this.certificateFetcher_.setCertificateStorage(this);
};

Validator.prototype = new CertificateStorage();
Validator.prototype.name = "Validator";

exports.Validator = Validator;

/**
 * Get the ValidationPolicy given to the constructor.
 * @return {ValidationPolicy} The ValidationPolicy.
 */
Validator.prototype.getPolicy = function() { return this.policy_; };

/**
 * Get the CertificateFetcher given to (or created in) the constructor.
 * @return {CertificateFetcher} The CertificateFetcher.
 */
Validator.prototype.getFetcher = function() { return this.certificateFetcher_; };

/**
 * Set the maximum depth of the certificate chain.
 * @param {number} maxDepth The maximum depth.
 */
Validator.prototype.setMaxDepth = function(maxDepth)
{
  this.maxDepth_ = maxDepth;
};

/**
 * Get the maximum depth of the certificate chain.
 * @return {number} The maximum depth.
 */
Validator.prototype.getMaxDepth = function() { return this.maxDepth_; };

/**
 * Asynchronously validate the Data or Interest packet.
 * @param {Data|Interest} dataOrInterest The Data or Interest packet to validate,
 * which is copied.
 * @param {function} successCallback On validation success, this calls
 * successCallback(dataOrInterest).
 * @param {function} failureCallback On validation failure, this calls
 * failureCallback(dataOrInterest, error) where error is a ValidationError.
 */
Validator.prototype.validate = function
  (dataOrInterest, successCallback, failureCallback)
{
  var state;
  if (dataOrInterest instanceof Data) {
    state = new DataValidationState
      (dataOrInterest, successCallback, failureCallback);
    if (LOG > 3) console.log("Start validating data " +
      dataOrInterest.getName().toUri());
  }
  else {
    state = new InterestValidationState
      (dataOrInterest, successCallback, failureCallback);
    if (LOG > 3) console.log("Start validating interest " +
      dataOrInterest.getName().toUri());
  }

  var thisValidator = this;
  this.policy_.checkPolicy
    (dataOrInterest, state, function(certificateRequest, state) {
      if (certificateRequest == null)
        state.bypassValidation_();
      else
        // We need to fetch the key and validate it.
        thisValidator.requestCertificate_(certificateRequest, state);
    });
};

/**
 * Recursively validate the certificates in the certification chain.
 * @param {CertificateV2} certificate The certificate to check.
 * @param {ValidationState} state The current validation state.
 */
Validator.prototype.validateCertificate_ = function(certificate, state)
{
  if (LOG > 3) console.log("Start validating certificate " +
    certificate.getName().toUri());

  if (!certificate.isValid()) {
    state.fail(new ValidationError
      (ValidationError.EXPIRED_CERTIFICATE,
       "Retrieved certificate is not yet valid or expired `" +
       certificate.getName().toUri() + "`"));
    return;
  }

  var thisValidator = this;
  this.policy_.checkCertificatePolicy
    (certificate, state, function(certificateRequest, state) {
      if (certificateRequest == null)
        state.fail(new ValidationError
          (ValidationError.POLICY_ERROR,
           "Validation policy is not allowed to designate `" +
           certificate.getName().toUri() + "` as a trust anchor"));
      else {
        // We need to fetch the key and validate it.
        state.addCertificate(certificate);
        thisValidator.requestCertificate_(certificateRequest, state);
      }
    });
};

/**
 * Request a certificate for further validation.
 * @param {CertificateRequest} certificateRequest The certificate request.
 * @param {ValidationState} state The current validation state.
 */
Validator.prototype.requestCertificate_ = function(certificateRequest, state)
{
  if (state.getDepth() >= this.maxDepth_) {
    state.fail(new ValidationError
      (ValidationError.EXCEEDED_DEPTH_LIMIT, "Exceeded validation depth limit"));
    return;
  }

  if (state.hasSeenCertificateName(certificateRequest.interest_.getName())) {
    state.fail(new ValidationError
      (ValidationError.LOOP_DETECTED,
       "Validation loop detected for certificate `" +
         certificateRequest.interest_.getName().toUri() + "`"));
    return;
  }

  if (LOG > 3) console.log("Retrieving " +
    certificateRequest.interest_.getName().toUri());

  var thisValidator = this;

  var certificate = this.findTrustedCertificate(certificateRequest.interest_);
  if (certificate != null) {
    if (LOG > 3) console.log("Found trusted certificate " +
      certificate.getName().toUri());

    state.verifyCertificateChainPromise_(certificate)
    .then(function(certificate) {
      if (certificate != null)
        return state.verifyOriginalPacketPromise_(certificate);
      else
        return SyncPromise.resolve();
    })
    .then(function() {
      for (var i = 0; i < state.certificateChain_.length; ++i)
        thisValidator.cacheVerifiedCertificate(state.certificateChain_[i]);
    });

    return;
  }

  this.certificateFetcher_.fetch
    (certificateRequest, state, function(certificate, state) {
      thisValidator.validateCertificate_(certificate, state);
    });
};