All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.security.policy.ConfigPolicyManager Maven / Gradle / Ivy

/**
 * Copyright (C) 2015-2018 Regents of the University of California.
 * @author: Jeff Thompson 
 * From PyNDN config_policy_manager.py by Adeola Bannis.
 * Originally from Yingdi Yu .
 *
 * 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 .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.security.policy;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.named_data.jndn.Data;
import net.named_data.jndn.Interest;
import net.named_data.jndn.KeyLocator;
import net.named_data.jndn.KeyLocatorType;
import net.named_data.jndn.Name;
import net.named_data.jndn.Signature;
import net.named_data.jndn.encoding.EncodingException;
import net.named_data.jndn.encoding.WireFormat;
import net.named_data.jndn.encoding.der.DerDecodingException;
import net.named_data.jndn.security.OnVerified;
import net.named_data.jndn.security.OnVerifiedInterest;
import net.named_data.jndn.security.OnDataValidationFailed;
import net.named_data.jndn.security.OnInterestValidationFailed;
import net.named_data.jndn.security.SecurityException;
import net.named_data.jndn.security.ValidationRequest;
import net.named_data.jndn.security.certificate.Certificate;
import net.named_data.jndn.security.certificate.IdentityCertificate;
import net.named_data.jndn.security.v2.CertificateCacheV2;
import net.named_data.jndn.security.v2.CertificateV2;
import net.named_data.jndn.util.Blob;
import net.named_data.jndn.util.BoostInfoParser;
import net.named_data.jndn.util.BoostInfoTree;
import net.named_data.jndn.util.Common;
import net.named_data.jndn.util.SignedBlob;
import net.named_data.jndn.util.regex.NdnRegexMatcherBase;
import net.named_data.jndn.util.regex.NdnRegexTopMatcher;

/**
 * A ConfigPolicyManager manages trust according to a configuration file in the
 * Validator Configuration File Format
 * (http://redmine.named-data.net/projects/ndn-cxx/wiki/CommandValidatorConf)
 *
 * Once a rule is matched, the ConfigPolicyManager looks in the
 * CertificateCache for the certificate matching the name in the KeyLocator
 * and uses its public key to verify the data packet or signed interest. If the
 * certificate can't be found, it is downloaded, verified and installed. A chain
 * of certificates will be followed to a maximum depth.
 * If the new certificate is accepted, it is used to complete the verification.
 *
 * The KeyLocators of data packets and signed interests MUST contain a name for
 * verification to succeed.
 */
public class ConfigPolicyManager extends PolicyManager {
  /**
   * Create a new ConfigPolicyManager which will act on the rules specified in
   * the configuration and download unknown certificates when necessary.
   * This creates a security v1 PolicyManager to verify certificates in format
   * v1. To verify certificate format v2, use the ConfigPolicyManager with a
   * CertificateCacheV2.
   * @param configFileName (optional) If not null or empty, the path to the
   * configuration file containing verification rules. Otherwise, you should
   * separately call load().
   * @param certificateCache (optional) A CertificateCache to hold known
   * certificates. If omitted, then create an internal CertificateCache.
   * @param searchDepth (optional) The maximum number of links to follow when
   * verifying a certificate chain.
   * @param graceInterval (optional) The window of time difference (in milliseconds)
   * allowed between the timestamp of the first interest signed with a new
   * public key and the validation time. If omitted, use a default value.
   * @param keyTimestampTtl (optional) How long a public key's last-used
   * timestamp is kept in the store (milliseconds). If omitted, use a default
   * value.
   * @param maxTrackedKeys (optional) The maximum number of public key use
   * timestamps to track. If omitted, use a default.
   */
  public ConfigPolicyManager
    (String configFileName, CertificateCache certificateCache, int searchDepth,
     double graceInterval, double keyTimestampTtl, int maxTrackedKeys)
       throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = true;
    certificateCache_ = certificateCache;
    maxDepth_ = searchDepth;
    keyGraceInterval_ = graceInterval;
    keyTimestampTtl_ = keyTimestampTtl;
    maxTrackedKeys_ = maxTrackedKeys;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCache certificateCache, int searchDepth,
     double graceInterval, double keyTimestampTtl)
    throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = true;
    certificateCache_ = certificateCache;
    maxDepth_ = searchDepth;
    keyGraceInterval_ = graceInterval;
    keyTimestampTtl_ = keyTimestampTtl;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCache certificateCache, int searchDepth,
     double graceInterval)
    throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = true;
    certificateCache_ = certificateCache;
    maxDepth_ = searchDepth;
    keyGraceInterval_ = graceInterval;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCache certificateCache, int searchDepth)
      throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = true;
    certificateCache_ = certificateCache;
    maxDepth_ = searchDepth;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCache certificateCache)
      throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = true;
    certificateCache_ = certificateCache;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  /**
   * This creates a security v1 PolicyManager to verify certificates in format
   * v1. To verify certificate format v2, use the ConfigPolicyManager with a
   * CertificateCacheV2.
   */
  public ConfigPolicyManager(String configFileName)
    throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = true;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  /**
   * Create a new ConfigPolicyManager which will act on the rules specified in
   * the configuration and download unknown certificates when necessary. Use
   * default parameter values. You must call load().
   * This creates a security v1 PolicyManager to verify certificates in format
   * v1. To verify certificate format v2, use the ConfigPolicyManager with a
   * CertificateCacheV2.
   */
  public ConfigPolicyManager()
  {
    isSecurityV1_ = true;

    reset();
  }

  /**
   * Create a new ConfigPolicyManager which will act on the rules specified in
   * the configuration and download unknown certificates when necessary. This
   * uses certificate format v2.
   * @param configFileName (optional) If not null or empty, the path to the
   * configuration file containing verification rules. Otherwise, you should
   * separately call load().
   * @param certificateCache A CertificateCacheV2 to hold known certificates.
   * @param searchDepth (optional) The maximum number of links to follow when
   * verifying a certificate chain.
   * @param graceInterval (optional) The window of time difference (in milliseconds)
   * allowed between the timestamp of the first interest signed with a new
   * public key and the validation time. If omitted, use a default value.
   * @param keyTimestampTtl (optional) How long a public key's last-used
   * timestamp is kept in the store (milliseconds). If omitted, use a default
   * value.
   * @param maxTrackedKeys (optional) The maximum number of public key use
   * timestamps to track. If omitted, use a default.
   */
  public ConfigPolicyManager
    (String configFileName, CertificateCacheV2 certificateCache, int searchDepth,
     double graceInterval, double keyTimestampTtl, int maxTrackedKeys)
       throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = false;
    certificateCacheV2_ = certificateCache;
    maxDepth_ = searchDepth;
    keyGraceInterval_ = graceInterval;
    keyTimestampTtl_ = keyTimestampTtl;
    maxTrackedKeys_ = maxTrackedKeys;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCacheV2 certificateCache, int searchDepth,
     double graceInterval, double keyTimestampTtl)
    throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = false;
    certificateCacheV2_ = certificateCache;
    maxDepth_ = searchDepth;
    keyGraceInterval_ = graceInterval;
    keyTimestampTtl_ = keyTimestampTtl;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCacheV2 certificateCache, int searchDepth,
     double graceInterval)
    throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = false;
    certificateCacheV2_ = certificateCache;
    maxDepth_ = searchDepth;
    keyGraceInterval_ = graceInterval;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCacheV2 certificateCache, int searchDepth)
      throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = false;
    certificateCacheV2_ = certificateCache;
    maxDepth_ = searchDepth;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  public ConfigPolicyManager
    (String configFileName, CertificateCacheV2 certificateCache)
      throws IOException, SecurityException, CertificateV2.Error
  {
    isSecurityV1_ = false;
    certificateCacheV2_ = certificateCache;

    reset();

    if (configFileName != null && !configFileName.equals(""))
      load(configFileName);
  }

  /**
   * Reset the certificate cache and other fields to the constructor state.
   */
  public final void
  reset()
  {
    if (isSecurityV1_)
      certificateCache_.reset();
    else
      certificateCacheV2_.clear();
    fixedCertificateCache_.clear();
    keyTimestamps_.clear();
    requiresVerification_ = true;
    config_ = new BoostInfoParser();
    refreshManager_ = new TrustAnchorRefreshManager(isSecurityV1_);
  }

  /**
   * Call reset() and load the configuration rules from the file.
   * @param configFileName The path to the configuration file containing the
   * verification rules.
   */
  public final void
  load(String configFileName)
    throws IOException, SecurityException, CertificateV2.Error
  {
    reset();
    config_.read(configFileName);
    loadTrustAnchorCertificates();
  }

  /**
   * Call reset() and load the configuration rules from the input.
   * @param input The contents of the configuration rules, with lines separated
   * by "\n" or "\r\n".
   * @param inputName Used for log messages, etc.
   */
  public void
  load(String input, String inputName)
    throws IOException, SecurityException, CertificateV2.Error
  {
    reset();
    config_.read(input, inputName);
    loadTrustAnchorCertificates();
  }

  /**
   * Check if the received data packet can escape from verification and be
   * trusted as valid. If the configuration file contains the trust anchor
   * 'any', nothing is verified.
   * @param data The received data packet.
   * @return true if the data does not need to be verified to be trusted as
   * valid, otherwise false.
   */
  public final boolean
  skipVerifyAndTrust(Data data)
  {
    return !requiresVerification_;
  }

  /**
   * Check if the received signed interest can escape from verification and be
   * trusted as valid. If the configuration file contains the trust anchor
   * 'any', nothing is verified.
   * @param interest The received interest.
   * @return true if the interest does not need to be verified to be trusted as
   * valid, otherwise false.
   */
  public final boolean
  skipVerifyAndTrust(Interest interest)
  {
    return !requiresVerification_;
  }

  /**
   * Check if this PolicyManager has a verification rule for the received data.
   * If the configuration file contains the trust anchor 'any', nothing is
   * verified.
   * @param data The received data packet.
   * @return true if the data must be verified, otherwise false.
   */
  public final boolean
  requireVerify(Data data)
  {
    return requiresVerification_;
  }

  /**
   * Check if this PolicyManager has a verification rule for the received signed
   * interest.
   * If the configuration file contains the trust anchor 'any', nothing is
   * verified.
   * @param interest The received interest.
   * @return true if the interest must be verified, otherwise false.
   */
  public final boolean
  requireVerify(Interest interest)
  {
    return requiresVerification_;
  }

  /**
   * Check whether the received data packet complies with the verification policy,
   * and get the indication of the next verification step.
   * @param data The Data object with the signature to check.
   * @param stepCount The number of verification steps that have been done, used
   * to track the verification progress.
   * @param onVerified If the signature is verified, this calls
   * onVerified.onVerified(data).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onValidationFailed If the signature check fails, this calls
   * onValidationFailed.onDataValidationFailed(data, reason).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @return the indication of next verification step, null if there is no
   * further step.
   */
  public final ValidationRequest
  checkVerificationPolicy
    (Data data, int stepCount, OnVerified onVerified,
     OnDataValidationFailed onValidationFailed) throws SecurityException
  {
    String[] failureReason = new String[] { "unknown" };
    Interest certificateInterest;
    try {
      certificateInterest = getCertificateInterest
        (stepCount, "data", data.getName(), data.getSignature(), failureReason);
    } catch (CertificateV2.Error ex) {
      throw new SecurityException(ex.getMessage());
    } catch (NdnRegexMatcherBase.Error ex) {
      throw new SecurityException
        ("ConfigPolicyManager: Error in getCertificateInterest:" + ex);
    }
    if (certificateInterest == null) {
      try {
        onValidationFailed.onDataValidationFailed(data, failureReason[0]);
      } catch (Throwable ex) {
        logger_.log(Level.SEVERE, "Error in onDataValidationFailed", ex);
      }
      return null;
    }

    if (certificateInterest.getName().size() > 0)
      return new ValidationRequest
        (certificateInterest,
         new OnCertificateDownloadComplete
           (data, stepCount, onVerified, onValidationFailed),
         onValidationFailed, 2, stepCount + 1);
    else {
      // Certificate is known. Verify the signature.
      // wireEncode returns the cached encoding if available.
      if (verify(data.getSignature(), data.wireEncode(), failureReason)) {
        try {
          onVerified.onVerified(data);
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, "Error in onVerified", ex);
        }
      }
      else {
        try {
          onValidationFailed.onDataValidationFailed(data, failureReason[0]);
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, "Error in onDataValidationFailed", ex);
        }
      }

      return null;
    }
  }

  /**
   * Check whether the received signed interest complies with the verification
   * policy, and get the indication of the next verification step.
   * @param interest The interest with the signature to check.
   * @param stepCount The number of verification steps that have been done, used
   * to track the verification progress.
   * @param onVerified If the signature is verified, this calls onVerified(interest).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onValidationFailed If the signature check fails, this calls
   * onValidationFailed.onInterestValidationFailed(interest, reason).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @return the indication of next verification step, null if there is no
   * further step.
   */
  public final ValidationRequest
  checkVerificationPolicy
    (Interest interest, int stepCount, OnVerifiedInterest onVerified,
     OnInterestValidationFailed onValidationFailed, WireFormat wireFormat)
    throws SecurityException
  {
    String[] failureReason = new String[] { "unknown" };
    Signature signature = extractSignature(interest, wireFormat, failureReason);
    if (signature == null) {
      // Can't get the signature from the interest name.
      try {
        onValidationFailed.onInterestValidationFailed
          (interest, failureReason[0]);
      } catch (Throwable ex) {
        logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", ex);
      }
      return null;
    }

    // For command interests, we need to ignore the last 4 components when
    //   matching the name.
    Interest certificateInterest;
    try {
      certificateInterest = getCertificateInterest
        (stepCount, "interest", interest.getName().getPrefix(-4), signature,
         failureReason);
    } catch (CertificateV2.Error ex) {
      throw new SecurityException(ex.getMessage());
    } catch (NdnRegexMatcherBase.Error ex) {
      throw new SecurityException
        ("ConfigPolicyManager: Error in getCertificateInterest:" + ex);
    }
    if (certificateInterest == null) {
      try {
        onValidationFailed.onInterestValidationFailed
          (interest, failureReason[0]);
      } catch (Throwable ex) {
        logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", ex);
      }
      return null;
    }

    if (certificateInterest.getName().size() > 0)
      return new ValidationRequest
        (certificateInterest,
         new OnCertificateDownloadCompleteForInterest
            (interest, stepCount, onVerified, onValidationFailed, wireFormat),
         new OnVerifyInterestFailedWrapper(onValidationFailed, interest),
         2, stepCount + 1);
    else {
      // For interests, we must check that the timestamp is fresh enough.
      // This is done after (possibly) downloading the certificate to avoid filling
      // the cache with bad keys.
      Name signatureName = KeyLocator.getFromSignature(signature).getKeyName();
      Name keyName;
      if (isSecurityV1_)
        keyName = IdentityCertificate.certificateNameToPublicKeyName(signatureName);
      else
        // For security V2, the KeyLocator name is already the key name.
        keyName = signatureName;
      double timestamp = interest.getName().get(-4).toNumber();

      if (!interestTimestampIsFresh(keyName, timestamp, failureReason)) {
        try {
          onValidationFailed.onInterestValidationFailed
            (interest, failureReason[0]);
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", ex);
        }
        return null;
      }

      // Certificate is known. Verify the signature.
      // wireEncode returns the cached encoding if available.
      if (verify(signature, interest.wireEncode(), failureReason)) {
        try {
          onVerified.onVerifiedInterest(interest);
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, "Error in onVerifiedInterest", ex);
        }
        updateTimestampForKey(keyName, timestamp);
      }
      else {
        try {
          onValidationFailed.onInterestValidationFailed
            (interest, failureReason[0]);
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", ex);
        }
      }

      return null;
    }
  }

  /**
   * Override to always indicate that the signing certificate name and data name
   * satisfy the signing policy.
   * @param dataName The name of data to be signed.
   * @param certificateName The name of signing certificate.
   * @return true to indicate that the signing certificate can be used to sign
   * the data.
   */
  public final boolean
  checkSigningPolicy(Name dataName, Name certificateName)
  {
    return true;
  }

  /**
   * Infer the signing identity name according to the policy. If the signing
   * identity cannot be inferred, return an empty name.
   * @param dataName The name of data to be signed.
   * @return The signing identity or an empty name if cannot infer.
   */
  public final Name
  inferSigningIdentity(Name dataName)
  {
    throw new UnsupportedOperationException
      ("ConfigPolicyManager.inferSigningIdentity is not implemented");
  }

  /**
   * TrustAnchorRefreshManager manages the trust-anchor certificates, including
   *   refresh.
   */
  private static class TrustAnchorRefreshManager {
    public TrustAnchorRefreshManager(boolean isSecurityV1)
    {
      isSecurityV1_ = isSecurityV1;
    }

    public static IdentityCertificate
    loadIdentityCertificateFromFile(String filename) throws SecurityException
    {
      StringBuilder encodedData = new StringBuilder();

      try {
        BufferedReader certFile = new BufferedReader(new FileReader(filename));
        // Use "try/finally instead of "try-with-resources" or "using"
        // which are not supported before Java 7.
        try {
          String line;
          while ((line = certFile.readLine()) != null)
            encodedData.append(line);
        } finally {
          certFile.close();
        }
      } catch (FileNotFoundException ex) {
        throw new SecurityException("Can't find IdentityCertificate file "
          + filename + ": " + ex.getMessage());
      } catch (IOException ex) {
        throw new SecurityException("Error reading IdentityCertificate file "
          + filename + ": " + ex.getMessage());
      }

      byte[] decodedData = Common.base64Decode(encodedData.toString());
      IdentityCertificate cert = new IdentityCertificate();
      try {
        cert.wireDecode(new Blob(decodedData, false));
      } catch (EncodingException ex) {
        throw new SecurityException("Can't decode the IdentityCertificate from file "
          + filename + ": " + ex.getMessage());
      }
      return cert;
    }

    public static CertificateV2
    loadCertificateV2FromFile(String filename) throws SecurityException
    {
      StringBuilder encodedData = new StringBuilder();

      try {
        BufferedReader certFile = new BufferedReader(new FileReader(filename));
        // Use "try/finally instead of "try-with-resources" or "using"
        // which are not supported before Java 7.
        try {
          String line;
          while ((line = certFile.readLine()) != null)
            encodedData.append(line);
        } finally {
          certFile.close();
        }
      } catch (FileNotFoundException ex) {
        throw new SecurityException("Can't find the certificate file "
          + filename + ": " + ex.getMessage());
      } catch (IOException ex) {
        throw new SecurityException("Error reading the certificate file "
          + filename + ": " + ex.getMessage());
      }

      byte[] decodedData = Common.base64Decode(encodedData.toString());
      CertificateV2 cert = new CertificateV2();
      try {
        cert.wireDecode(new Blob(decodedData, false));
      } catch (EncodingException ex) {
        throw new SecurityException("Can't decode the certificate from file "
          + filename + ": " + ex.getMessage());
      }
      return cert;
    }

    public IdentityCertificate
    getCertificate(Name certificateName) throws SecurityException
    {
      if (!isSecurityV1_)
        throw new SecurityException
          ("getCertificate: For security v2, use getCertificateV2()");

      // Assume the timestamp is already removed.
      return certificateCache_.getCertificate(certificateName);
    }

    public CertificateV2
    getCertificateV2(Name certificateName) throws SecurityException
    {
      if (isSecurityV1_)
        throw new SecurityException
          ("getCertificateV2: For security v1, use getCertificate()");

      // Assume the timestamp is already removed.
      return certificateCacheV2_.find(certificateName);
    }

    public void
    addDirectory(String directoryName, double refreshPeriod)
      throws SecurityException, CertificateV2.Error
    {
      File[] allFiles = new File(directoryName).listFiles();
      if (allFiles == null)
        throw new SecurityException
          ("Cannot find files in directory " + directoryName);

      // Use ArrayList without generics so it works with older Java compilers.
      ArrayList certificateNames = new ArrayList();
      for (int i = 0; i < allFiles.length; ++i) {
        File file = allFiles[i];

        if (isSecurityV1_) {
          IdentityCertificate cert;
          try {
            cert = loadIdentityCertificateFromFile(file.getAbsolutePath());
          }
          catch (Exception ex) {
            // Allow files that are not certificates.
            continue;
          }

          // Cut off the timestamp so it matches KeyLocator Name format.
          String certUri = cert.getName().getPrefix(-1).toUri();
          certificateCache_.insertCertificate(cert);
          certificateNames.add(certUri);
        }
        else {
          CertificateV2 cert;
          try {
            cert = loadCertificateV2FromFile(file.getAbsolutePath());
          }
          catch (Exception ex) {
            // Allow files that are not certificates.
            continue;
          }

          // Get the key name since this is in the KeyLocator.
          String certUri =
            CertificateV2.extractKeyNameFromCertName(cert.getName()).toUri();
          certificateCacheV2_.insert(cert);
          certificateNames.add(certUri);
        }
      }

      refreshDirectories_.put
        (directoryName, new DirectoryInfo
          (certificateNames, Common.getNowMilliseconds() + refreshPeriod,
           refreshPeriod));
    }

    public void
    refreshAnchors() throws SecurityException, CertificateV2.Error
    {
      double refreshTime = Common.getNowMilliseconds();

      // We will modify refreshDirectories_ in the loop, so copy its keys.
      Object[] directories = refreshDirectories_.keySet().toArray();
      for (int iDirectory = 0; iDirectory < directories.length; ++iDirectory) {
        String directory = (String)directories[iDirectory];
        DirectoryInfo info = (DirectoryInfo)refreshDirectories_.get(directory);

        double nextRefreshTime = info.nextRefresh_;
        if (nextRefreshTime <= refreshTime) {
          ArrayList certificateList = (ArrayList)info.certificateNames_.clone();

          // Delete the certificates associated with this directory if possible
          //   then re-import.
          // IdentityStorage subclasses may not support deletion.
          for (int i = 0; i < certificateList.size(); ++i) {
            if (isSecurityV1_)
              certificateCache_.deleteCertificate
                (new Name((String)certificateList.get(i)));
            else {
              // The name in the CertificateCacheV2 contains the but the name in
              // the certificateList does not, so find the certificate based on
              // the prefix first.
              CertificateV2 foundCertificate = certificateCacheV2_.find
                (new Name((String)certificateList.get(i)));
              if (foundCertificate != null)
                certificateCacheV2_.deleteCertificate(foundCertificate.getName());
            }
          }

          addDirectory(directory, info.refreshPeriod_);
        }
      }
    }

    private static class DirectoryInfo {
      public DirectoryInfo
        (ArrayList certificateNames, double nextRefresh, double refreshPeriod)
      {
        certificateNames_ = certificateNames;
        nextRefresh_ = nextRefresh;
        refreshPeriod_ = refreshPeriod;
      }

      ArrayList certificateNames_; // of String.
      double nextRefresh_;
      double refreshPeriod_;
    };

    private final boolean isSecurityV1_;
    private final CertificateCache certificateCache_ = new CertificateCache();
    private final CertificateCacheV2 certificateCacheV2_ = new CertificateCacheV2();
    // refreshDirectories_ maps the directory name to a DirectoryInfo of
    // the certificate names so they can be deleted when necessary, and the
    // next refresh time.
    // Use HashMap without generics so it works with older Java compilers.
    private final HashMap refreshDirectories_ = new HashMap();
  };

  /**
   * The configuration file allows 'trust anchor' certificates to be preloaded.
   * The certificates may also be loaded from a directory, and if the 'refresh'
   * option is set to an interval, the certificates are reloaded at the
   * specified interval
   */
  private void
  loadTrustAnchorCertificates() throws SecurityException, CertificateV2.Error
  {
    ArrayList anchors = config_.getRoot().get("validator/trust-anchor");

    for (int i = 0; i < anchors.size(); ++i) {
      BoostInfoTree anchor = anchors.get(i);

      String typeName = anchor.getFirstValue("type");
      boolean isPath = false;
      String certID = null;
      if (typeName.equals("file")) {
        certID = anchor.getFirstValue("file-name");
        isPath = true;
      }
      else if (typeName.equals("base64")) {
        certID = anchor.getFirstValue("base64-string");
        isPath = false;
      }
      else if (typeName.equals("dir")) {
        String dirName = anchor.getFirstValue("dir");

        double refreshPeriod = 0;
        ArrayList refreshTrees = anchor.get("refresh");
        if (refreshTrees.size() >= 1) {
          String refreshPeriodStr = refreshTrees.get(0).getValue();

          Pattern regex1 = Pattern.compile("(\\d+)([hms])");
          Matcher refreshMatch = regex1.matcher(refreshPeriodStr);
          if (!refreshMatch.find())
            refreshPeriod = 0;
          else {
            refreshPeriod = Integer.parseInt(refreshMatch.group(1));
            if (!refreshMatch.group(2).equals("s")) {
              refreshPeriod *= 60;
              if (!refreshMatch.group(2).equals("m"))
                refreshPeriod *= 60;
            }
          }
        }

        // Convert refreshPeriod from seconds to milliseconds.
        refreshManager_.addDirectory(dirName, refreshPeriod * 1000);
        continue;
      }
      else if (typeName.equals("any")) {
        // This disables all security!
        requiresVerification_ = false;
        break;
      }

      if (isSecurityV1_)
        lookupCertificate(certID, isPath);
      else
        lookupCertificateV2(certID, isPath);
    }
  }

  /**
   * Once a rule is found to match data or a signed interest, the name in the
   * KeyLocator must satisfy the condition in the 'checker' section of the rule,
   * else the data or interest is rejected.
   * @param signatureName The certificate name from the KeyLocator.
   * @param objectName The name of the data packet or interest. In the case of
   * signed interests, this excludes the timestamp, nonce and signature
   * components.
   * @param rule The rule from the configuration file that matches the data or
   * interest.
   * @param failureReason If matching fails, set failureReason[0] to the
   * failure reason.
   * @return True if matches.
   */
  private boolean
  checkSignatureMatch
    (Name signatureName, Name objectName, BoostInfoTree rule,
     String[] failureReason)
    throws SecurityException, NdnRegexMatcherBase.Error, CertificateV2.Error
  {
    BoostInfoTree checker = rule.get("checker").get(0);
    String checkerType = checker.getFirstValue("type");
    if (checkerType.equals("fixed-signer")) {
      BoostInfoTree signerInfo = checker.get("signer").get(0);
      String signerType = signerInfo.getFirstValue("type");

      Name certificateName;
      if (signerType.equals("file")) {
        if (isSecurityV1_) {
          Certificate cert = lookupCertificate
            (signerInfo.getFirstValue("file-name"), true);
          if (cert == null) {
            failureReason[0] = "Can't find fixed-signer certificate file: " +
              signerInfo.getFirstValue("file-name");
            return false;
          }
          certificateName = cert.getName();
        }
        else {
          CertificateV2 cert = lookupCertificateV2
            (signerInfo.getFirstValue("file-name"), true);
          if (cert == null) {
            failureReason[0] = "Can't find fixed-signer certificate file: " +
              signerInfo.getFirstValue("file-name");
            return false;
          }
          certificateName = cert.getName();
        }
      }
      else if (signerType.equals("base64")) {
        if (isSecurityV1_) {
          Certificate cert = lookupCertificate
            (signerInfo.getFirstValue("base64-string"), false);
          if (cert == null) {
            failureReason[0] = "Can't find fixed-signer certificate base64: " +
              signerInfo.getFirstValue("base64-string");
            return false;
          }
          certificateName = cert.getName();
        }
        else {
          CertificateV2 cert = lookupCertificateV2
            (signerInfo.getFirstValue("base64-string"), false);
          if (cert == null) {
            failureReason[0] = "Can't find fixed-signer certificate base64: " +
              signerInfo.getFirstValue("base64-string");
            return false;
          }
          certificateName = cert.getName();
        }
      }
      else {
        failureReason[0] = "Unrecognized fixed-signer signerType: " + signerType;
        return false;
      }

      if (certificateName.equals(signatureName))
        return true;
      else {
        failureReason[0] = "fixed-signer cert name \"" + certificateName.toUri() +
          "\" does not equal signatureName \"" + signatureName.toUri() + "\"";
        return false;
      }
    }
    else if (checkerType.equals("hierarchical")) {
      // This just means the data/interest name has the signing identity as a prefix.
      // That means everything before "ksk-?" in the key name.
      String identityRegex = "^([^]*)(<>*)";
      NdnRegexTopMatcher identityMatch = new NdnRegexTopMatcher
        (identityRegex);
      if (identityMatch.match(signatureName)) {
        Name identityPrefix = identityMatch.expand("\\1")
        .append(identityMatch.expand("\\2"));
        if (matchesRelation(objectName, identityPrefix, "is-prefix-of"))
          return true;
        else {
          failureReason[0] = "The hierarchical objectName \"" + objectName.toUri() +
            "\" is not a prefix of \"" + identityPrefix + "\"";
          return false;
        }
      }

      if (!isSecurityV1_) {
        // Check for a security v2 key name.
        String identityRegex2 = "^(<>*)<>$";
        NdnRegexTopMatcher identityMatch2 = new NdnRegexTopMatcher
          (identityRegex2);
        if (identityMatch2.match(signatureName)) {
          Name identityPrefix = identityMatch2.expand("\\1");
          if (matchesRelation(objectName, identityPrefix, "is-prefix-of"))
            return true;
          else {
            failureReason[0] = "The hierarchical objectName \"" + objectName.toUri() +
              "\" is not a prefix of \"" + identityPrefix + "\"";
            return false;
          }
        }
      }

      failureReason[0] = "The hierarchical identityRegex \"" + identityRegex +
        "\" does not match signatureName \"" + signatureName.toUri() + "\"";
      return false;
    }
    else if (checkerType.equals("customized")) {
      BoostInfoTree keyLocatorInfo = checker.get("key-locator").get(0);
      // Not checking type - only name is supported.

      // Is this a simple relation?
      String simpleRelationType = keyLocatorInfo.getFirstValue("relation");
      if (simpleRelationType != null) {
        Name matchName = new Name(keyLocatorInfo.getFirstValue("name"));
        if (matchesRelation(signatureName, matchName, simpleRelationType))
          return true;
        else {
          failureReason[0] = "The custom signatureName \"" + signatureName.toUri() +
            "\" does not match matchName \"" + matchName.toUri() +
            "\" using relation " + simpleRelationType;
          return false;
        }
      }

      // Is this a simple regex?
      String simpleKeyRegex = keyLocatorInfo.getFirstValue("regex");
      if (simpleKeyRegex != null) {
        if (new NdnRegexTopMatcher(simpleKeyRegex).match(signatureName))
          return true;
        else {
          failureReason[0] = "The custom signatureName \"" + signatureName.toUri() +
            "\" does not regex match simpleKeyRegex \"" + simpleKeyRegex + "\"";
          return false;
        }
      }

      // Is this a hyper-relation?
      ArrayList hyperRelationList = keyLocatorInfo.get("hyper-relation");
      if (hyperRelationList.size() >= 1) {
        BoostInfoTree hyperRelation = hyperRelationList.get(0);

        String keyRegex = hyperRelation.getFirstValue("k-regex");
        String keyExpansion = hyperRelation.getFirstValue("k-expand");
        String nameRegex = hyperRelation.getFirstValue("p-regex");
        String nameExpansion = hyperRelation.getFirstValue("p-expand");
        String relationType = hyperRelation.getFirstValue("h-relation");
        if (keyRegex != null && keyExpansion != null && nameRegex != null &&
            nameExpansion != null && relationType != null) {
          NdnRegexTopMatcher keyMatch = new NdnRegexTopMatcher(keyRegex);
          if (!keyMatch.match(signatureName)) {
            failureReason[0] = "The custom hyper-relation signatureName \"" +
              signatureName.toUri() + "\" does not match the keyRegex \"" +
              keyRegex + "\"";
            return false;
          }
          Name keyMatchPrefix = keyMatch.expand(keyExpansion);

          NdnRegexTopMatcher nameMatch = new NdnRegexTopMatcher(nameRegex);
          if (!nameMatch.match(objectName)) {
            failureReason[0] = "The custom hyper-relation objectName \"" +
              objectName.toUri() + "\" does not match the nameRegex \"" +
              nameRegex + "\"";
            return false;
          }
          Name nameMatchExpansion = nameMatch.expand(nameExpansion);

          if (matchesRelation(nameMatchExpansion, keyMatchPrefix, relationType))
            return true;
          else {
            failureReason[0] = "The custom hyper-relation nameMatch \"" +
              nameMatchExpansion.toUri() + "\" does not match the keyMatchPrefix \"" +
              keyMatchPrefix + "\" using relation " + relationType;
            return false;
          }
        }
      }
    }

    failureReason[0] = "Unrecognized checkerType: " + checkerType;
    return false;
  }

  /**
   * Similar to Python expand, return expansion where every \1, \2, etc. is
   * replaced by match.group(1), match.group(2), etc.  Note: Even though this is
   * a general utility function, we define it locally because it is only tested
   * to work in the cases used by this class.
   * @param match The match object from String.match.
   * @param expansion The string with \1, \2, etc. to replace from match.
   * @return The expanded string.
   */
  private static String
  expand(Matcher match, String expansion)
  {
    String result = "";
    int beginIndex = 0;
    Pattern regex = Pattern.compile("\\\\(\\d)");
    Matcher expansionMatcher = regex.matcher(expansion);
    while (expansionMatcher.find()) {
      result += expansion.substring(beginIndex, expansionMatcher.start());
      result += match.group(Integer.parseInt(expansionMatcher.group(1)));
      beginIndex = expansionMatcher.end();
    }

    // Add the substring after the last match.
    result += expansion.substring(beginIndex, expansion.length());
    return result;
  }

  /**
   * This looks up certificates specified as base64-encoded data or file names.
   * These are cached by filename or encoding to avoid repeated reading of files
   * or decoding.
   * @param certID
   * @param isPath
   * @return The IdentityCertificate or null if not found.
   */
  private IdentityCertificate
  lookupCertificate(String certID, boolean isPath) throws SecurityException
  {
    if (!isSecurityV1_)
      throw new SecurityException
        ("lookupCertificate: For security v2, use lookupCertificateV2()");

    IdentityCertificate cert;

    if (!fixedCertificateCache_.containsKey(certID)) {
      if (isPath)
        // Load the certificate data (base64 encoded IdentityCertificate)
        cert = TrustAnchorRefreshManager.loadIdentityCertificateFromFile(certID);
      else {
        byte[] certData = Common.base64Decode(certID);
        cert = new IdentityCertificate();
        try {
          cert.wireDecode(new Blob(certData, false));
        } catch (EncodingException ex) {
          throw new SecurityException("Cannot base64 decode the cert data: " +
            ex.getMessage());
        }
      }

      String certUri = cert.getName().getPrefix(-1).toUri();
      fixedCertificateCache_.put(certID, certUri);
      certificateCache_.insertCertificate(cert);
    }
    else
      cert = certificateCache_.getCertificate
        (new Name((String)fixedCertificateCache_.get(certID)));

    return cert;
  }

  /**
   * This looks up certificates specified as base64-encoded data or file names.
   * These are cached by filename or encoding to avoid repeated reading of files
   * or decoding.
   * @param certID
   * @param isPath
   * @return The CertificateV2 or null if not found.
   */
  private CertificateV2
  lookupCertificateV2(String certID, boolean isPath)
    throws SecurityException, CertificateV2.Error
  {
    if (isSecurityV1_)
      throw new SecurityException
        ("lookupCertificateV2: For security v1, use lookupCertificate()");

    CertificateV2 cert;

    if (!fixedCertificateCache_.containsKey(certID)) {
      if (isPath)
        // Load the certificate data (base64 encoded IdentityCertificate)
        cert = TrustAnchorRefreshManager.loadCertificateV2FromFile(certID);
      else {
        byte[] certData = Common.base64Decode(certID);
        cert = new CertificateV2();
        try {
          cert.wireDecode(new Blob(certData, false));
        } catch (EncodingException ex) {
          throw new SecurityException("Cannot base64 decode the cert data: " +
            ex.getMessage());
        }
      }

      String certUri = cert.getName().getPrefix(-1).toUri();
      fixedCertificateCache_.put(certID, certUri);
      certificateCacheV2_.insert(cert);
    }
    else
      cert = certificateCacheV2_.find
        (new Name((String)fixedCertificateCache_.get(certID)));

    return cert;
  }

  /**
   * Search the configuration file for the first rule that matches the data or
   * signed interest name. In the case of interests, the name to match should
   * exclude the timestamp, nonce, and signature components.
   * @param objName The name to be matched.
   * @param matchType The rule type to match, "data" or "interest".
   * @return The BoostInfoTree for the matching rule, or null if not found.
   */
  private BoostInfoTree
  findMatchingRule(Name objName, String matchType)
    throws NdnRegexMatcherBase.Error
  {
    ArrayList rules = config_.getRoot().get("validator/rule");
    for (int iRule = 0; iRule < rules.size(); ++iRule) {
      BoostInfoTree r = rules.get(iRule);

      if (r.getFirstValue("for").equals(matchType)) {
        boolean passed = true;
        ArrayList filters = r.get("filter");
        if (filters.isEmpty())
          // no filters means we pass!
          return r;
        else {
          for (int iFilter = 0; iFilter < filters.size(); ++iFilter) {
            BoostInfoTree f = filters.get(iFilter);

            // Don't check the type - it can only be name for now.
            // We need to see if this is a regex or a relation.
            String regexPattern = f.getFirstValue("regex");
            if (regexPattern == null) {
              String matchRelation = f.getFirstValue("relation");
              String matchUri = f.getFirstValue("name");
              Name matchName = new Name(matchUri);
              passed = matchesRelation(objName, matchName, matchRelation);
            }
            else
              passed = new NdnRegexTopMatcher(regexPattern).match(objName);

            if (!passed)
              break;
          }

          if (passed)
            return r;
        }
      }
    }

    return null;
  }

  /**
   * Determines if a name satisfies the relation to another name, based on
   * matchRelation.
   * @param name
   * @param matchName
   * @param matchRelation  Can be one of:
      "is-prefix-of" - passes if the name is equal to or has the other
         name as a prefix
      "is-strict-prefix-of" - passes if the name has the other name as a
         prefix, and is not equal
      "equal" - passes if the two names are equal
   * @return True if matches.
   */
  private static boolean
  matchesRelation(Name name, Name matchName, String matchRelation)
  {
    boolean passed = false;
    if (matchRelation.equals("is-strict-prefix-of")) {
      if (matchName.size() == name.size())
        passed = false;
      else if (matchName.match(name))
        passed = true;
    }
    else if (matchRelation.equals("is-prefix-of")) {
      if (matchName.match(name))
        passed = true;
    }
    else if (matchRelation.equals("equal")) {
      if (matchName.equals(name))
        passed = true;
    }

    return passed;
  }

  /**
   * Extract the signature information from the interest name.
   * @param interest The interest whose signature is needed.
   * @param wireFormat The wire format used to decode signature information
   * from the interest name.
   * @param failureReason If can't decode, set failureReason[0] to the failure
   * reason.
   * @return A shared_ptr for the Signature object. This is null if can't decode.
   */
  private static Signature
  extractSignature
    (Interest interest, WireFormat wireFormat, String[] failureReason)
  {
    if (interest.getName().size() < 2) {
      failureReason[0] = "The signed interest has less than 2 components: " +
        interest.getName().toUri();
      return null;
    }

    try {
      return wireFormat.decodeSignatureInfoAndValue
              (interest.getName().get(-2).getValue().buf(),
               interest.getName().get(-1).getValue().buf(), false);
    } catch (EncodingException ex) {
      failureReason[0] = "Error decoding the signed interest signature: " + ex;
      return null;
    }
  }

  /**
   * Determine whether the timestamp from the interest is newer than the last
   * use of this key, or within the grace interval on first use.
   * @param keyName The name of the public key used to sign the interest.
   * @param timestamp The timestamp extracted from the interest name.
   * @param failureReason If timestamp is not fresh, set failureReason[0] to the
   * failure reason.
   * @return True if timestamp is fresh as described above.
   */
  private boolean
  interestTimestampIsFresh(Name keyName, double timestamp, String[] failureReason)
  {
    String keyNameUri = keyName.toUri();
    if (!keyTimestamps_.containsKey(keyNameUri)) {
      double now = Common.getNowMilliseconds();
      double notBefore = now - keyGraceInterval_;
      double notAfter = now + keyGraceInterval_;

      if (!(timestamp > notBefore && timestamp < notAfter)) {
        failureReason[0] =
          "The command interest timestamp is not within the first use grace period of " +
          keyGraceInterval_ + " milliseconds.";
        return false;
      }
      else
        return true;
    }
    else {
      double lastTimestamp = (double)(Double)keyTimestamps_.get(keyNameUri);
      if (timestamp <= lastTimestamp) {
        failureReason[0] =
          "The command interest timestamp is not newer than the previous timestamp";
        return false;
      }
      return true;
    }
  }

  /**
   * Trim the table size down if necessary, and insert/update the latest
   * interest signing timestamp for the key. Any key which has not been used
   * within the TTL period is purged. If the table is still too large, the
   * oldest key is purged.
   * @param keyName The name of the public key used to sign the interest.
   * @param timestamp The timestamp extracted from the interest name.
   */
  private void
  updateTimestampForKey(Name keyName, double timestamp)
  {
    keyTimestamps_.put(keyName.toUri(), timestamp);

    if (keyTimestamps_.size() >= maxTrackedKeys_) {
      double now = Common.getNowMilliseconds();
      double oldestTimestamp = now;
      String oldestKey = "";

      // Get the keys to erase without disturbing the map.
      // Use ArrayList without generics so it works with older Java compilers.
      ArrayList keysToErase = new ArrayList();

      Object[] keys = keyTimestamps_.keySet().toArray();
      for (int i = 0; i < keys.length; ++i) {
        String keyUri = (String)keys[i];
        double ts = (double)(Double)keyTimestamps_.get(keyUri);
        if (now - ts > keyTimestampTtl_)
          keysToErase.add(keyUri);
        else if (ts < oldestTimestamp) {
          oldestTimestamp = ts;
          oldestKey = keyUri;
        }
      }

      // Now erase.
      for (int i = 0; i < keysToErase.size(); ++i)
        keyTimestamps_.remove(keysToErase.get(i));

      if (keyTimestamps_.size() > maxTrackedKeys_ && oldestKey.length() != 0)
        // have not removed enough
        keyTimestamps_.remove(oldestKey);
    }
  }

  /**
   * Check the type of signatureInfo to get the KeyLocator. Look in the
   * IdentityStorage for the public key with the name in the KeyLocator and use
   * it to verify the signedBlob. If the public key can't be found, return
   * false. (This is a generalized method which can verify both a Data packet
   * and an interest.)
   * @param signatureInfo An object of a subclass of Signature, e.g.
   * Sha256WithRsaSignature.
   * @param signedBlob the SignedBlob with the signed portion to verify.
   * @param failureReason If verification fails, set failureReason[0] to the
   * failure reason.
   * @return True if the signature verifies, False if not.
   */
  private boolean
  verify(Signature signatureInfo, SignedBlob signedBlob, String[] failureReason)
    throws SecurityException
  {
    // We have already checked once that there is a key locator.
    KeyLocator keyLocator = KeyLocator.getFromSignature(signatureInfo);

    if (keyLocator.getType() == KeyLocatorType.KEYNAME) {
      // Assume the key name is a certificate name.
      Name signatureName = keyLocator.getKeyName();
      Blob publicKeyDer;
      if (isSecurityV1_) {
        IdentityCertificate certificate =
          refreshManager_.getCertificate(signatureName);
        if (certificate == null)
          certificate = certificateCache_.getCertificate(signatureName);
        if (certificate == null) {
          failureReason[0] = "Cannot find a certificate with name " +
            signatureName.toUri();
          return false;
        }

        publicKeyDer = certificate.getPublicKeyInfo().getKeyDer();
        if (publicKeyDer.isNull()) {
          // We don't expect this to happen.
          failureReason[0] = "There is no public key in the certificate with name " +
            certificate.getName().toUri();
          return false;
        }
      }
      else {
        CertificateV2 certificate =
          refreshManager_.getCertificateV2(signatureName);
        if (certificate == null)
          certificate = certificateCacheV2_.find(signatureName);
        if (certificate == null) {
          failureReason[0] = "Cannot find a certificate with name " +
            signatureName.toUri();
          return false;
        }

        try {
          publicKeyDer = certificate.getPublicKey();
        } catch (CertificateV2.Error ex) {
          // We don't expect this to happen.
          failureReason[0] = "There is no public key in the certificate with name " +
            certificate.getName().toUri();
          return false;
        }
      }

      if (verifySignature(signatureInfo, signedBlob, publicKeyDer))
        return true;
      else {
        failureReason[0] = "The signature did not verify with the given public key";
        return false;
      }
    }
    else {
      failureReason[0] = "The KeyLocator does not have a key name";
      return false;
    }
  }

  /**
   * This is a helper for checkVerificationPolicy to verify the rule and return
   * a certificate interest to fetch the next certificate in the hierarchy if
   * needed.
   * @param stepCount The number of verification steps that have been done, used
   * to track the verification progress.
   * @param matchType Either "data" or "interest".
   * @param objectName The name of the data or interest packet.
   * @param signature The Signature object for the data or interest packet.
   * @param failureReason If can't determine the interest, set failureReason[0]
   * to the failure reason.
   * @return null if can't determine the interest, otherwise the interest for the
   * ValidationRequest to fetch the next certificate. However, if the interest
   * has an empty name, the validation succeeded and no need to fetch a
   * certificate.
   */
  private Interest
  getCertificateInterest
    (int stepCount, String matchType, Name objectName, Signature signature,
     String[] failureReason)
    throws SecurityException, NdnRegexMatcherBase.Error, CertificateV2.Error
  {
    if (stepCount > maxDepth_) {
      failureReason[0] = "The verification stepCount " + stepCount +
        " exceeded the maxDepth " + maxDepth_;
      return null;
    }

    // first see if we can find a rule to match this packet
    BoostInfoTree matchedRule = findMatchingRule(objectName, matchType);

    // No matching rule -> fail.
    if (matchedRule == null) {
      failureReason[0] = "No matching rule found for " + objectName.toUri();
      return null;
    }

    // TODO: Do a quick check if this is sig-type sha256.

    if (!KeyLocator.canGetFromSignature(signature)) {
      // We only support signature types with key locators.
      failureReason[0] = "The signature type does not support a KeyLocator";
      return null;
    }

    KeyLocator keyLocator = KeyLocator.getFromSignature(signature);

    Name signatureName = keyLocator.getKeyName();
    // No key name in KeyLocator -> fail.
    if (signatureName.size() == 0) {
      failureReason[0] = "The signature KeyLocator doesn't have a key name";
      return null;
    }

    boolean signatureMatches = checkSignatureMatch
      (signatureName, objectName, matchedRule, failureReason);
    if (!signatureMatches)
      return null;

    // Before we look up keys, refresh any certificate directories.
    refreshManager_.refreshAnchors();

    // If we don't actually have the certificate yet, return a certificateInterest
    //   for it.
    if (isSecurityV1_) {
      IdentityCertificate foundCert = refreshManager_.getCertificate(signatureName);
      if (foundCert == null)
        foundCert = certificateCache_.getCertificate(signatureName);
      if (foundCert == null)
        return new Interest(signatureName);
    }
    else {
      CertificateV2 foundCert = refreshManager_.getCertificateV2(signatureName);
      if (foundCert == null)
        foundCert = certificateCacheV2_.find(signatureName);
      if (foundCert == null)
        return new Interest(signatureName);
    }

    return new Interest();
  }

  /**
   * The onVerified method of this inner class is called by KeyChain.verifyData
   * because checkVerificationPolicy returned a ValidationRequest to fetch a
   * certificate and verify a certificate, through a separate call to
   * KeyChain.verifyData. When it verifies the data, it calls onVerified which
   * is this method.
   * @param data The fetched data packet containing the certificate which has
   * already been verified.
   * @param originalData The original data from checkVerificationPolicy.
   * @param stepCount The value from checkVerificationPolicy.
   * @param onVerified The value from checkVerificationPolicy.
   * @param onValidationFailed The value from checkVerificationPolicy.
   */
  private class OnCertificateDownloadComplete implements OnVerified {
    public OnCertificateDownloadComplete
      (Data originalData, int stepCount, OnVerified onVerified,
       OnDataValidationFailed onValidationFailed)
    {
      originalData_ = originalData;
      stepCount_ = stepCount;
      onVerified_ = onVerified;
      onValidationFailed_ = onValidationFailed;
    }

    public final void
    onVerified(Data data)
    {
      if (isSecurityV1_) {
        IdentityCertificate certificate;
        try {
          certificate = new IdentityCertificate(data);
        } catch (DerDecodingException ex) {
          try {
            onValidationFailed_.onDataValidationFailed
              (originalData_, "Cannot decode certificate " + data.getName().toUri());
          } catch (Throwable exception) {
            logger_.log(Level.SEVERE, "Error in onDataValidationFailed", exception);
          }
          return;
        }
        certificateCache_.insertCertificate(certificate);
      }
      else {
        CertificateV2 certificate;
        try {
          certificate = new CertificateV2(data);
        } catch (CertificateV2.Error ex) {
          try {
            onValidationFailed_.onDataValidationFailed
              (originalData_, "Cannot decode certificate " + data.getName().toUri());
          } catch (Throwable exception) {
            logger_.log(Level.SEVERE, "Error in onDataValidationFailed", exception);
          }
          return;
        }
        try {
          certificateCacheV2_.insert(certificate);
        } catch (CertificateV2.Error ex) {
          try {
            onValidationFailed_.onDataValidationFailed
              (originalData_, "Cannot insert certificate " + data.getName().toUri());
          } catch (Throwable exception) {
            logger_.log(Level.SEVERE, "Error in onDataValidationFailed", exception);
          }
          return;
        }
      }

      try {
        // Now that we stored the needed certificate, increment stepCount and try again
        //   to verify the originalData.
        checkVerificationPolicy
          (originalData_, stepCount_ + 1, onVerified_, onValidationFailed_);
      } catch (Exception ex) {
        try {
          onValidationFailed_.onDataValidationFailed
            (originalData_, "Error in checkVerificationPolicy: " + ex);
        } catch (Throwable exception) {
          logger_.log(Level.SEVERE, "Error in onDataValidationFailed", exception);
        }
      }
    }

    private final Data originalData_;
    private final int stepCount_;
    private final OnVerified onVerified_;
    private final OnDataValidationFailed onValidationFailed_;
  }

  /**
   * The onVerified method of this inner class is called by KeyChain.verifyData
   * because checkVerificationPolicy returned a ValidationRequest to fetch a
   * certificate and verify a certificate, through a separate call to
   * KeyChain.verifyData. When it verifies the data, it calls onVerified which
   * is this method.
   * @param data The fetched data packet containing the certificate which has
   * already been verified.
   * @param originalInterest The original interest from checkVerificationPolicy.
   * @param stepCount The value from checkVerificationPolicy.
   * @param onVerified The value from checkVerificationPolicy.
   * @param onValidationFailed The value from checkVerificationPolicy.
   */
  private class OnCertificateDownloadCompleteForInterest implements OnVerified {
    public OnCertificateDownloadCompleteForInterest
      (Interest originalInterest, int stepCount,
       OnVerifiedInterest onVerified,
       OnInterestValidationFailed onValidationFailed, WireFormat wireFormat)
    {
      originalInterest_ = originalInterest;
      stepCount_ = stepCount;
      onVerified_ = onVerified;
      onValidationFailed_ = onValidationFailed;
      wireFormat_ = wireFormat;
    }

    public final void
    onVerified(Data data)
    {
      if (isSecurityV1_) {
        IdentityCertificate certificate;
        try {
          certificate = new IdentityCertificate(data);
        } catch (DerDecodingException ex) {
          try {
            onValidationFailed_.onInterestValidationFailed
              (originalInterest_, "Cannot decode certificate " + data.getName().toUri());
          } catch (Throwable exception) {
            logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", exception);
          }
          return;
        }
        certificateCache_.insertCertificate(certificate);
      }
      else {
        CertificateV2 certificate;
        try {
          certificate = new CertificateV2(data);
        } catch (CertificateV2.Error ex) {
          try {
            onValidationFailed_.onInterestValidationFailed
              (originalInterest_, "Cannot decode certificate " + data.getName().toUri());
          } catch (Throwable exception) {
            logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", exception);
          }
          return;
        }
        try {
          certificateCacheV2_.insert(certificate);
        } catch (CertificateV2.Error ex) {
          try {
            onValidationFailed_.onInterestValidationFailed
              (originalInterest_, "Cannot insert certificate " + data.getName().toUri());
          } catch (Throwable exception) {
            logger_.log(Level.SEVERE, "Error in onDataValidationFailed", exception);
          }
          return;
        }
      }

      try {
        // Now that we stored the needed certificate, increment stepCount and try again
        //   to verify the originalInterest.
        checkVerificationPolicy
          (originalInterest_, stepCount_ + 1, onVerified_, onValidationFailed_,
           wireFormat_);
      } catch (Exception ex) {
        try {
          onValidationFailed_.onInterestValidationFailed
            (originalInterest_, "Error in checkVerificationPolicy: " + ex);
        } catch (Throwable exception) {
          logger_.log(Level.SEVERE, "Error in onInterestValidationFailed", exception);
        }
      }
    }

    private final Interest originalInterest_;
    private final int stepCount_;
    private final OnVerifiedInterest onVerified_;
    private final OnInterestValidationFailed onValidationFailed_;
    private final WireFormat wireFormat_;
  }

  /**
   * Ignore data and call onInterestValidationFailed(interest, reason). This is
   * so that an OnInterestValidationFailed can be passed as an
   * OnDataValidationFailed.
   */
  private class OnVerifyInterestFailedWrapper implements OnDataValidationFailed {
    public OnVerifyInterestFailedWrapper
      (OnInterestValidationFailed onValidationFailed, Interest interest)
    {
      onValidationFailed_ = onValidationFailed;
      interest_ = interest;
    }

    public final void
    onDataValidationFailed(Data data, String reason)
    {
      onValidationFailed_.onInterestValidationFailed(interest_, reason);
    }

    private final OnInterestValidationFailed onValidationFailed_;
    private final Interest interest_;
  }

  /**
   * A class implements Friend if it has a method
   * setConfigPolicyManagerFriendAccess which setFriendAccess calls to set
   * the FriendAccess object.
   */
  public interface Friend {
    void setConfigPolicyManagerFriendAccess(FriendAccess friendAccess);
  }

  /**
   * Call friend.setConfigPolicyManagerFriendAccess to pass an instance of
   * a FriendAccess class to allow a friend class to call private methods.
   * @param friend The friend class for calling setConfigPolicyManagerFriendAccess.
   * This uses friend.getClass() to make sure that it is a friend class.
   * Therefore, only a friend class gets an implementation of FriendAccess.
   */
  public static void setFriendAccess(Friend friend)
  {
    if (friend.getClass().getName().endsWith
          ("net.named_data.jndn.tests.integration_tests.TestPolicyManager") ||
        friend.getClass().getName().endsWith
          ("net.named_data.jndn.tests.integration_tests.TestVerificationRules"))
    {
      friend.setConfigPolicyManagerFriendAccess(new FriendAccessImpl());
    }
  }

  /**
   * A friend class can call the methods of FriendAccess to access private
   * methods.  This abstract class is public, but setFriendAccess passes an
   * instance of a private class which implements the methods.
   */
  public abstract static class FriendAccess {
    public abstract BoostInfoTree
    findMatchingRule
      (ConfigPolicyManager policyManager, Name objName, String matchType)
        throws NdnRegexMatcherBase.Error;

    public abstract boolean
    checkSignatureMatch
      (ConfigPolicyManager policyManager, Name signatureName, Name objectName,
       BoostInfoTree rule, String[] failureReason)
      throws SecurityException, NdnRegexMatcherBase.Error, CertificateV2.Error;
  }

  /**
   * setFriendAccess passes an instance of this private class which implements
   * the FriendAccess methods.
   */
  private static class FriendAccessImpl extends FriendAccess {
    public BoostInfoTree
    findMatchingRule
      (ConfigPolicyManager policyManager, Name objName, String matchType)
      throws NdnRegexMatcherBase.Error
    {
      return policyManager.findMatchingRule(objName, matchType);
    }

    public boolean
    checkSignatureMatch
      (ConfigPolicyManager policyManager, Name signatureName, Name objectName,
       BoostInfoTree rule, String[] failureReason)
      throws SecurityException, NdnRegexMatcherBase.Error, CertificateV2.Error
    {
      return policyManager.checkSignatureMatch
        (signatureName, objectName, rule, failureReason);
    }
  }

  private final boolean isSecurityV1_;
  private CertificateCache certificateCache_ = new CertificateCache();
  private CertificateCacheV2 certificateCacheV2_ = new CertificateCacheV2();
  private int maxDepth_ = 5;
  private double keyGraceInterval_ = 3000;
  private double keyTimestampTtl_ = 3600000;
  private int maxTrackedKeys_ = 1000;
  // fixedCertificateCache_ stores the fixed-signer certificate name associated with
  //    validation rules so we don't keep loading from files.
  // Use HashMap without generics so it works with older Java compilers.
  private final HashMap fixedCertificateCache_ = new HashMap();
  // keyTimestamps_ stores the timestamps for each public key used in command
  //   interests to avoid replay attacks.
  // key is the public key name, value is the last timestamp.
  private final HashMap keyTimestamps_ = new HashMap();
  private BoostInfoParser config_ = new BoostInfoParser();
  private boolean requiresVerification_ = true;
  private TrustAnchorRefreshManager refreshManager_;
  private static final Logger logger_ = Logger.getLogger
    (ConfigPolicyManager.class.getName());
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy