Source code for pyndn.security.v2.validation_policy_command_interest

# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
#
# Copyright (C) 2018-2019 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/ndn-cxx/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 <http://www.gnu.org/licenses/>.
# A copy of the GNU Lesser General Public License is in the file COPYING.

"""
This module defines the ValidationPolicyCommandInterest class which 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.
"""

from pyndn.name import Name
from pyndn.data import Data
from pyndn.util.common import Common
from pyndn.security.command_interest_signer import CommandInterestSigner
from pyndn.security.v2.validation_error import ValidationError
from pyndn.security.v2.validation_policy import ValidationPolicy

[docs]class ValidationPolicyCommandInterest(ValidationPolicy): """ Create a ValidationPolicyCommandInterest. :param ValidationPolicy innerPolicy: a ValidationPolicy for signed Interest signature validation and Data validation. This must not be None. :param ValidationPolicyCommandInterest.Options options: (optional) The stop-and-wait command Interest validation options. If omitted, use a default Options(). :raises: ValueError if innerPolicy is None. """ def __init__(self, innerPolicy, options = None): super(ValidationPolicyCommandInterest, self).__init__() if options == None: self._options = ValidationPolicyCommandInterest.Options() else: # Copy the Options. self._options = ValidationPolicyCommandInterest.Options(options) self._container = [] # of ValidationPolicyCommandInterest.LastTimestampRecord self._nowOffsetMilliseconds = 0 if innerPolicy == None: raise ValueError("inner policy is missing") self.setInnerPolicy(innerPolicy) if self._options._gracePeriod < 0.0: self._options._gracePeriod = 0.0
[docs] class Options(object): """ Create a ValidationPolicyCommandInterest.Options with the values. :param gracePeriodOrOptions: (optional) The tolerance of the initial timestamp in milliseconds. (However, if this is another ValidationPolicyCommandInterest.Options, then copy values from it.) If omitted, use a grace period of 2 minutes. 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. The grace period 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. :type gracePeriodOrOptions: float or ValidationPolicyCommandInterest.Options :param int maxRecords: (optional) The maximum number of distinct public keys of which to record the last timestamp. If omitted, use 1000. 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 max records to -1 allows tracking unlimited public keys. Setting max records to 0 disables using last timestamp records and causes every command Interest to be processed as initial. :param float recordLifetime: (optional) The maximum lifetime of a last timestamp record in milliseconds. If omitted, use 1 hour. A last timestamp record expires and can be deleted if it has not been refreshed within the record lifetime. Setting the record lifetime to 0 or negative makes last timestamp records expire immediately and causes every command Interest to be processed as initial. """ def __init__(self, gracePeriodOrOptions = None, maxRecords = None, recordLifetime = None): if isinstance(gracePeriodOrOptions, ValidationPolicyCommandInterest.Options): # The copy constructor. options = gracePeriodOrOptions self._gracePeriod = options._gracePeriod self._maxRecords = options._maxRecords self._recordLifetime = options._recordLifetime else: gracePeriod = gracePeriodOrOptions if gracePeriod == None: gracePeriod = 2 * 60 * 1000.0 if maxRecords == None: maxRecords = 1000 if recordLifetime == None: recordLifetime = 3600 * 1000.0 self._gracePeriod = gracePeriod self._maxRecords = maxRecords self._recordLifetime = recordLifetime
[docs] def checkPolicy(self, dataOrInterest, state, continueValidation): """ :param dataOrInterest: :type dataOrInterest: Data or Interest :param ValidationState state: :param continueValidation: :type continueValidation: function object """ if isinstance(dataOrInterest, Data): data = dataOrInterest self.getInnerPolicy().checkPolicy(data, state, continueValidation) else: interest = dataOrInterest keyName = [None] timestamp = [0] if not ValidationPolicyCommandInterest._parseCommandInterest( interest, state, keyName, timestamp): return if not self._checkTimestamp(state, keyName[0], timestamp[0]): return self.getInnerPolicy().checkPolicy(interest, state, continueValidation)
def _setNowOffsetMilliseconds(self, nowOffsetMilliseconds): """ Set the offset when _insertNewRecord() and _cleanUp() get the current time, which should only be used for testing. :param float nowOffsetMilliseconds: The offset in milliseconds. """ self._nowOffsetMilliseconds = nowOffsetMilliseconds
[docs] class LastTimestampRecord(object): """ :param Name keyName: :param float timestamp: :param float lastRefreshed: """ def __init__(self, keyName, timestamp, lastRefreshed): # Copy the Name. self._keyName = Name(keyName) self._timestamp = timestamp self._lastRefreshed = lastRefreshed
def _cleanUp(self): # _nowOffsetMilliseconds is only used for testing. now = Common.getNowMilliseconds() + self._nowOffsetMilliseconds expiring = now - self._options._recordLifetime while ((len(self._container) > 0 and self._container[0]._lastRefreshed <= expiring) or (self._options._maxRecords >= 0 and len(self._container) > self._options._maxRecords)): self._container.pop(0) @staticmethod def _parseCommandInterest(interest, state, keyLocatorName, timestamp): """ Get the keyLocatorName and timestamp from the command interest. :param Interest interest: The Interest to parse. :param ValidationState state: On error, this calls state.fail and returns False. :param Array<Name> keyLocatorName: Set keyLocatorName[0] to the KeyLocator name. :param Array<float> 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. :rtype: bool """ keyLocatorName[0] = Name() timestamp[0] = 0 name = interest.getName() if name.size() < CommandInterestSigner.MINIMUM_SIZE: state.fail(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] = ValidationPolicy.getKeyLocatorName(interest, state) if state.isOutcomeFailed(): # Already failed. return False return True def _checkTimestamp(self, state, keyName, timestamp): """ :param ValidationState state: On error, this calls state.fail and returns False. :param Name keyName: The key name. :param float timestamp: The timestamp as milliseconds since Jan 1, 1970 UTC. :return: On success, return True. On error, call state.fail and return False. :rtype: bool """ self._cleanUp() # _nowOffsetMilliseconds is only used for testing. now = Common.getNowMilliseconds() + self._nowOffsetMilliseconds if (timestamp < now - self._options._gracePeriod or timestamp > now + self._options._gracePeriod): state.fail(ValidationError(ValidationError.POLICY_ERROR, "Timestamp is outside the grace period for key " + keyName.toUri())) return False index = self._findByKeyName(keyName) if index >= 0: if timestamp <= self._container[index]._timestamp: state.fail(ValidationError(ValidationError.POLICY_ERROR, "Timestamp is reordered for key " + keyName.toUri())) return False def successCallback(interest): self._insertNewRecord(interest, keyName, timestamp) state.addSuccessCallback(successCallback) return True def _insertNewRecord(self, interest, keyName, timestamp): """ :param Interest interest: :param Name keyName: :param float timestamp: """ # _nowOffsetMilliseconds is only used for testing. now = Common.getNowMilliseconds() + self._nowOffsetMilliseconds newRecord = ValidationPolicyCommandInterest.LastTimestampRecord( keyName, timestamp, now) index = self._findByKeyName(keyName) if index >= 0: # Remove the existing record so we can move it to the end. self._container.pop(index) self._container.append(newRecord) def _findByKeyName(self, keyName): """ Find the record in container_ which has the keyName. :param Name keyName: The key name to search for. :return: The index in container_ of the record, or -1 if not found. :rtype: int """ for i in range(len(self._container)): if self._container[i]._keyName.equals(keyName): return i return -1