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

net.named_data.jndn.security.v2.ValidationPolicyCommandInterest Maven / Gradle / Ivy

/**
 * Copyright (C) 2018 Regents of the University of California.
 * @author: Jeff Thompson 
 * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/src/security/v2/validation-policy-command-interest.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 .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.security.v2;

import java.util.ArrayList;
import net.named_data.jndn.Data;
import net.named_data.jndn.Interest;
import net.named_data.jndn.Name;
import net.named_data.jndn.security.CommandInterestSigner;
import net.named_data.jndn.security.ValidatorConfigError;
import net.named_data.jndn.util.Common;

/**
 * ValidationPolicyCommandInterest extends ValidationPolicy as a policy for
 * stop-and-wait command Interests. See:
 * https://redmine.named-data.net/projects/ndn-cxx/wiki/CommandInterest
 *
 * This policy checks the timestamp field of a stop-and-wait command Interest.
 * Signed Interest validation and Data validation requests are delegated to an
 * inner policy.
 */
public class ValidationPolicyCommandInterest extends ValidationPolicy {
  public static class Options {
    /**
     * Create a ValidationPolicyCommandInterest.Options with the values.
     * @param gracePeriod See below for description.
     * @param maxRecords See below for description.
     * @param recordLifetime See below for description.
     */
    public Options(double gracePeriod, int maxRecords, double recordLifetime)
    {
      gracePeriod_ = gracePeriod;
      maxRecords_ = maxRecords;
      recordLifetime_ = recordLifetime;
    }

    /**
     * Create a ValidationPolicyCommandInterest.Options with the values and
     * where recordLifetime is 1 hour.
     * @param gracePeriod See below for description.
     * @param maxRecords See below for description.
     */
    public Options(double gracePeriod, int maxRecords)
    {
      gracePeriod_ = gracePeriod;
      maxRecords_ = maxRecords;
      recordLifetime_ = 3600 * 1000.0;
    }

    /**
     * Create a ValidationPolicyCommandInterest.Options with the gracePeriod and
     * where maxRecords is 1000 and recordLifetime is 1 hour.
     * @param gracePeriod See below for description.
     */
    public Options(double gracePeriod)
    {
      gracePeriod_ = gracePeriod;
      maxRecords_ = 1000;
      recordLifetime_ = 3600 * 1000.0;
    }

    /**
     * Create a ValidationPolicyCommandInterest.Options where gracePeriod is 2
     * minutes, maxRecords is 1000 and recordLifetime is 1 hour.
     */
    public Options()
    {
      gracePeriod_ = 2 * 60 * 1000.0;
      maxRecords_ = 1000;
      recordLifetime_ = 3600 * 1000.0;
    }

    /**
     * Create a ValidationPolicyCommandInterest.Options from the given options.
     * @param options The ValidationPolicyCommandInterest.Options with values to
     * copy.
     */
    public Options(Options options)
    {
      gracePeriod_ = options.gracePeriod_;
      maxRecords_ = options.maxRecords_;
      recordLifetime_ = options.recordLifetime_;
    }

    /**
     * gracePeriod is the tolerance of the initial timestamp in milliseconds.
     *
     * A stop-and-wait command Interest is considered "initial" if the validator
     * has not recorded the last timestamp from the same public key, or when
     * such knowledge has been erased. For an initial command Interest, its
     * timestamp is compared to the current system clock, and the command
     * Interest is rejected if the absolute difference is greater than the grace
     * interval.
     *
     * This should be positive. Setting this option to 0 or negative causes the
     * validator to require exactly the same timestamp as the system clock,
     * which most likely rejects all command Interests.
     */
    public double gracePeriod_;

    /**
     * maxRecords is the maximum number of distinct public keys of which to
     * record the last timestamp.
     *
     * The validator records the last timestamps for every public key. For a
     * subsequent command Interest using the same public key, its timestamp is
     * compared to the last timestamp from that public key, and the command
     * Interest is rejected if its timestamp is less than or equal to the
     * recorded timestamp.
     *
     * This option limits the number of distinct public keys being tracked. If
     * the limit is exceeded, then the oldest record is deleted.
     *
     * Setting this option to -1 allows tracking unlimited public keys. Setting
     * this option to 0 disables using last timestamp records and causes every
     * command Interest to be processed as initial.
     */
    public int maxRecords_;

    /**
     * recordLifetime is the maximum lifetime of a last timestamp record in
     * milliseconds.
     *
     * A last timestamp record expires and can be deleted if it has not been
     * refreshed within this duration. Setting this option to 0 or negative
     * makes last timestamp records expire immediately and causes every command
     * Interest to be processed as initial.
     */
    public double recordLifetime_;
  }

  /**
   * Create a ValidationPolicyCommandInterest.
   * @param innerPolicy a ValidationPolicy for signed Interest signature
   * validation and Data validation. This must not be null.
   * @param options The stop-and-wait command Interest validation options.
   * @throws AssertionError if innerPolicy is null.
   */
  public ValidationPolicyCommandInterest
    (ValidationPolicy innerPolicy, Options options)
  {
    // Copy the Options.
    options_ = new Options(options);

    if (innerPolicy == null)
      throw new AssertionError("inner policy is missing");

    setInnerPolicy(innerPolicy);

    if (options_.gracePeriod_ < 0.0)
      options_.gracePeriod_ = 0.0;
  }

  /**
   * Create a ValidationPolicyCommandInterest with default Options.
   * @param innerPolicy a ValidationPolicy for signed Interest signature
   * validation and Data validation. This must not be null.
   * @throws AssertionError if innerPolicy is null.
   */
  public ValidationPolicyCommandInterest(ValidationPolicy innerPolicy)
  {
    options_ = new Options();

    if (innerPolicy == null)
      throw new AssertionError("inner policy is missing");

    setInnerPolicy(innerPolicy);
  }

  public void
  checkPolicy
    (Data data, ValidationState state, ValidationContinuation continueValidation)
    throws CertificateV2.Error, ValidatorConfigError
  {
    getInnerPolicy().checkPolicy(data, state, continueValidation);
  }

  public void
  checkPolicy
    (Interest interest, ValidationState state,
     ValidationContinuation continueValidation)
    throws CertificateV2.Error, ValidatorConfigError
  {
    Name[] keyName = new Name[1];
    double[] timestamp = new double[1];
    if (!parseCommandInterest(interest, state, keyName, timestamp))
      return;

    if (!checkTimestamp(state, keyName[0], timestamp[0]))
      return;

    getInnerPolicy().checkPolicy(interest, state, continueValidation);
  }

  /**
   * Set the offset when insertNewRecord() and cleanUp() get the current time,
   * which should only be used for testing.
   * @param nowOffsetMilliseconds The offset in milliseconds.
   */
  public final void
  setNowOffsetMilliseconds_(double nowOffsetMilliseconds)
  {
    nowOffsetMilliseconds_ = nowOffsetMilliseconds;
  }

  private static class LastTimestampRecord
  {
    public LastTimestampRecord
      (Name keyName, double timestamp, double lastRefreshed)
    {
      // Copy the Name.
      keyName_ = new Name(keyName);
      timestamp_ = timestamp;
      lastRefreshed_ = lastRefreshed;
    }

    public Name keyName_;
    public double timestamp_;
    public double lastRefreshed_;
  };

  private void
  cleanUp()
  {
    // nowOffsetMilliseconds_ is only used for testing.
    double now = Common.getNowMilliseconds() + nowOffsetMilliseconds_;
    double expiring = now - options_.recordLifetime_;

    while ((container_.size() > 0 && container_.get(0).lastRefreshed_ <= expiring) ||
           (options_.maxRecords_ >= 0 && container_.size() > options_.maxRecords_))
      container_.remove(0);
  }

  /**
   * Get the keyLocatorName and timestamp from the command interest.
   * @param interest The Interest to parse.
   * @param state On error, this calls state.fail and returns false.
   * @param keyLocatorName Set keyLocatorName[0] to the KeyLocator name.
   * @param timestamp Set timestamp[0] to the timestamp as milliseconds since
   * Jan 1, 1970 UTC.
   * @return On success, return true. On error, call state.fail and return false.
   */
  private static boolean
  parseCommandInterest
    (Interest interest, ValidationState state, Name[] keyLocatorName,
     double[] timestamp)
  {
    keyLocatorName[0] = new Name();
    timestamp[0] = 0;

    Name name = interest.getName();
    if (name.size() < CommandInterestSigner.MINIMUM_SIZE) {
      state.fail(new ValidationError(ValidationError.POLICY_ERROR,
        "Command interest name `" + interest.getName().toUri() + "` is too short"));
      return false;
    }

    timestamp[0] = name.get(CommandInterestSigner.POS_TIMESTAMP).toNumber();

    keyLocatorName[0] = getKeyLocatorName(interest, state);
    if (state.isOutcomeFailed())
      // Already failed.
      return false;

    return true;
  }

  /**
   *
   * @param state On error, this calls state.fail and returns false.
   * @param keyName The key name.
   * @param timestamp The timestamp as milliseconds since Jan 1, 1970 UTC.
   * @return On success, return true. On error, call state.fail and return false.
   */
  private boolean
  checkTimestamp(ValidationState state, final Name keyName, final double timestamp)
  {
    cleanUp();

    // nowOffsetMilliseconds_ is only used for testing.
    double now = Common.getNowMilliseconds() + nowOffsetMilliseconds_;
    if (timestamp < now - options_.gracePeriod_ ||
        timestamp > now + options_.gracePeriod_) {
      state.fail(new ValidationError(ValidationError.POLICY_ERROR,
        "Timestamp is outside the grace period for key " + keyName.toUri()));
      return false;
    }

    int index = findByKeyName(keyName);
    if (index >= 0) {
      if (timestamp <= container_.get(index).timestamp_) {
        state.fail(new ValidationError(ValidationError.POLICY_ERROR,
          "Timestamp is reordered for key " + keyName.toUri()));
        return false;
      }
    }

    InterestValidationState interestState = (InterestValidationState)state;
    interestState.addSuccessCallback
      (new InterestValidationSuccessCallback() {
        public void successCallback(Interest interest) {
          insertNewRecord(interest, keyName, timestamp);
        }
      });

    return true;
  }

  private void
  insertNewRecord(Interest interest, Name keyName, double timestamp)
  {
    // nowOffsetMilliseconds_ is only used for testing.
    double now = Common.getNowMilliseconds() + nowOffsetMilliseconds_;
    LastTimestampRecord newRecord = new LastTimestampRecord
      (keyName, timestamp, now);

    int index = findByKeyName(keyName);
    if (index >= 0)
      // Remove the existing record so we can move it to the end.
      container_.remove(index);

    container_.add(newRecord);
  }

  /**
   * Find the record in container_ which has the keyName.
   * @param keyName The key name to search for.
   * @return The index in container_ of the record, or -1 if not found.
   */
  int
  findByKeyName(Name keyName)
  {
    for (int i = 0; i < container_.size(); ++i) {
      if (container_.get(i).keyName_.equals(keyName))
        return i;
    }

    return -1;
  }

  private final Options options_;
  private final ArrayList container_ =
    new ArrayList();
  private double nowOffsetMilliseconds_ = 0;
  // This is to force an import of net.named_data.jndn.util.
  private static Common dummyCommon_ = new Common();
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy