Beispiel #1
0
    def getLink(self):
        """
        Get the link object. If necessary, decode it from the link wire encoding.

        :return: The link object, or None if not specified.
        :rtype: Link
        :raises ValueError: For error decoding the link wire encoding (if
          necessary).
        :deprecated: Use getForwardingHint.
        """
        if self._link.get() != None:
            return self._link.get()
        elif not self._linkWireEncoding.isNull():
            # Decode the link object from linkWireEncoding_.
            link = Link()
            link.wireDecode(self._linkWireEncoding,
                            self._linkWireEncodingFormat)
            self._link.set(link)

            # Clear _linkWireEncoding since it is now managed by the link object.
            self._linkWireEncoding = Blob()
            self._linkWireEncodingFormat = None

            return link
        else:
            return None
Beispiel #2
0
    def getLink(self):
        """
        Get the link object. If necessary, decode it from the link wire encoding.

        :return: The link object, or None if not specified.
        :rtype: Link
        :raises ValueError: For error decoding the link wire encoding (if
          necessary).
        :deprecated: Use getForwardingHint.
        """
        if self._link.get() != None:
            return self._link.get()
        elif not self._linkWireEncoding.isNull():
            # Decode the link object from linkWireEncoding_.
            link = Link()
            link.wireDecode(self._linkWireEncoding, self._linkWireEncodingFormat)
            self._link.set(link)

            # Clear _linkWireEncoding since it is now managed by the link object.
            self._linkWireEncoding = Blob()
            self._linkWireEncodingFormat = None

            return link
        else:
            return None
Beispiel #3
0
    def __init__(self, prefix, dataType, face, keyChain, database,
                 repeatAttempts = None, keyRetrievalLink = None):
        self._face = face
        self._keyChain = keyChain
        self._database = database
        self._maxRepeatAttempts = (3 if repeatAttempts == None else repeatAttempts)
        self._keyRetrievalLink = (Producer.NO_LINK if keyRetrievalLink == None
                                  else Link(keyRetrievalLink))

        # The dictionary key is the key Name The value is a Producer._KeyInfo.
        self._eKeyInfo = {}
        # The dictionary key is the float time stamp. The value is a Producer._KeyRequest.
        self._keyRequests = {}

        fixedPrefix = Name(prefix)
        fixedDataType = Name(dataType)

        # Fill _ekeyInfo with all permutations of dataType, including the 'E-KEY'
        # component of the name. This will be used in createContentKey to send
        # interests without reconstructing names every time.
        fixedPrefix.append(Encryptor.NAME_COMPONENT_READ)
        while fixedDataType.size() > 0:
            nodeName = Name(fixedPrefix)
            nodeName.append(fixedDataType)
            nodeName.append(Encryptor.NAME_COMPONENT_E_KEY)

            self._eKeyInfo[nodeName] = Producer._KeyInfo()
            fixedDataType = fixedDataType.getPrefix(-1)

        fixedPrefix.append(dataType)
        self._namespace = Name(prefix)
        self._namespace.append(Encryptor.NAME_COMPONENT_SAMPLE)
        self._namespace.append(dataType)
Beispiel #4
0
    def __init__(self,
                 face,
                 keyChain,
                 groupName,
                 consumerName,
                 database,
                 cKeyLink=None,
                 dKeyLink=None):
        self._database = database
        self._keyChain = keyChain
        self._face = face
        self._groupName = Name(groupName)
        self._consumerName = Name(consumerName)
        self._cKeyLink = (Consumer.NO_LINK
                          if cKeyLink == None else Link(cKeyLink))
        self._dKeyLink = (Consumer.NO_LINK
                          if dKeyLink == None else Link(dKeyLink))

        # The dictionary key is the C-KEY name. The value is the encoded key Blob.
        self._cKeyMap = {}
        # The dictionary key is the D-KEY name. The value is the encoded key Blob.
        self._dKeyMap = {}
Beispiel #5
0
    def __init__(self, value=None):
        if isinstance(value, Interest):
            # Copy the values.
            self._name = ChangeCounter(Name(value.getName()))
            self._minSuffixComponents = value._minSuffixComponents
            self._maxSuffixComponents = value._maxSuffixComponents
            self._didSetCanBePrefix = value._didSetCanBePrefix
            self._keyLocator = ChangeCounter(KeyLocator(value.getKeyLocator()))
            self._exclude = ChangeCounter(Exclude(value.getExclude()))
            self._childSelector = value._childSelector
            self._mustBeFresh = value._mustBeFresh

            self._nonce = value.getNonce()
            self._interestLifetimeMilliseconds = value._interestLifetimeMilliseconds
            self._forwardingHint = ChangeCounter(
                DelegationSet(value.getForwardingHint()))
            self._applicationParameters = value._applicationParameters
            self._linkWireEncoding = value._linkWireEncoding
            self._linkWireEncodingFormat = value._linkWireEncodingFormat
            self._link = ChangeCounter(None)
            if value._link.get() != None:
                self._link.set(Link(value._link.get()))
            self._selectedDelegationIndex = value._selectedDelegationIndex
            self._defaultWireEncoding = value.getDefaultWireEncoding()
            self._defaultWireEncodingFormat = value._defaultWireEncodingFormat
        else:
            self._name = ChangeCounter(Name(value))
            self._minSuffixComponents = None
            self._maxSuffixComponents = None if Interest._defaultCanBePrefix else 1
            # _didSetCanBePrefix is True if the app already called setDefaultCanBePrefix().
            self._didSetCanBePrefix = Interest._didSetDefaultCanBePrefix
            self._keyLocator = ChangeCounter(KeyLocator())
            self._exclude = ChangeCounter(Exclude())
            self._childSelector = None
            self._mustBeFresh = False

            self._nonce = Blob()
            self._interestLifetimeMilliseconds = None
            self._forwardingHint = ChangeCounter(DelegationSet())
            self._applicationParameters = Blob()
            self._linkWireEncoding = Blob()
            self._linkWireEncodingFormat = None
            self._link = ChangeCounter(None)
            self._selectedDelegationIndex = None
            self._defaultWireEncoding = SignedBlob()
            self._defaultWireEncodingFormat = None

        self._getNonceChangeCount = 0
        self._getDefaultWireEncodingChangeCount = 0
        self._changeCount = 0
        self._lpPacket = None
Beispiel #6
0
    def __init__(self, value=None):
        if type(value) is Interest:
            # Copy the values.
            self._name = ChangeCounter(Name(value.getName()))
            self._minSuffixComponents = value._minSuffixComponents
            self._maxSuffixComponents = value._maxSuffixComponents
            self._keyLocator = ChangeCounter(KeyLocator(value.getKeyLocator()))
            self._exclude = ChangeCounter(Exclude(value.getExclude()))
            self._childSelector = value._childSelector
            self._mustBeFresh = value._mustBeFresh

            self._nonce = value.getNonce()
            self._interestLifetimeMilliseconds = value._interestLifetimeMilliseconds
            self._forwardingHint = ChangeCounter(
                DelegationSet(value.getForwardingHint()))
            self._linkWireEncoding = value._linkWireEncoding
            self._linkWireEncodingFormat = value._linkWireEncodingFormat
            self._link = ChangeCounter(None)
            if value._link.get() != None:
                self._link.set(Link(value._link.get()))
            self._selectedDelegationIndex = value._selectedDelegationIndex
            self._defaultWireEncoding = value.getDefaultWireEncoding()
            self._defaultWireEncodingFormat = value._defaultWireEncodingFormat
            self._content = value._content
        else:
            self._name = ChangeCounter(
                Name(value) if type(value) is Name else Name())
            self._minSuffixComponents = None
            self._maxSuffixComponents = None
            self._keyLocator = ChangeCounter(KeyLocator())
            self._exclude = ChangeCounter(Exclude())
            self._childSelector = None
            self._mustBeFresh = True

            self._nonce = Blob()
            self._interestLifetimeMilliseconds = None
            self._forwardingHint = ChangeCounter(DelegationSet())
            self._linkWireEncoding = Blob()
            self._linkWireEncodingFormat = None
            self._link = ChangeCounter(None)
            self._selectedDelegationIndex = None
            self._defaultWireEncoding = SignedBlob()
            self._defaultWireEncodingFormat = None
            self._content = Blob()

        self._getNonceChangeCount = 0
        self._getDefaultWireEncodingChangeCount = 0
        self._changeCount = 0
        self._lpPacket = None
Beispiel #7
0
    def __init__(self, value=None):
        if isinstance(value, Interest):
            # Copy the values.
            self._name = ChangeCounter(Name(value.getName()))
            self._canBePrefix = value._canBePrefix
            self._hopLimit = value._hopLimit
            self._keyLocator = ChangeCounter(KeyLocator(value.getKeyLocator()))
            self._mustBeFresh = value._mustBeFresh

            self._nonce = value.getNonce()
            self._interestLifetimeMilliseconds = value._interestLifetimeMilliseconds
            self._forwardingHint = ChangeCounter(
                DelegationSet(value.getForwardingHint()))
            self._applicationParameters = value._applicationParameters
            self._linkWireEncoding = value._linkWireEncoding
            self._linkWireEncodingFormat = value._linkWireEncodingFormat
            self._link = ChangeCounter(None)
            if value._link.get() != None:
                self._link.set(Link(value._link.get()))
            self._selectedDelegationIndex = value._selectedDelegationIndex
            self._defaultWireEncoding = value.getDefaultWireEncoding()
            self._defaultWireEncodingFormat = value._defaultWireEncodingFormat
        else:
            self._name = ChangeCounter(Name(value))
            self._canBePrefix = Interest._defaultCanBePrefix
            self._hopLimit = Interest._defaultHopLimit
            self._keyLocator = ChangeCounter(KeyLocator())
            self._mustBeFresh = False

            self._nonce = Blob()
            self._interestLifetimeMilliseconds = None
            self._forwardingHint = ChangeCounter(DelegationSet())
            self._applicationParameters = Blob()
            self._linkWireEncoding = Blob()
            self._linkWireEncodingFormat = None
            self._link = ChangeCounter(None)
            self._selectedDelegationIndex = None
            self._defaultWireEncoding = SignedBlob()
            self._defaultWireEncodingFormat = None

        self._getNonceChangeCount = 0
        self._getDefaultWireEncodingChangeCount = 0
        self._changeCount = 0
        self._lpPacket = None
Beispiel #8
0
    def consume(self, contentName, onConsumeComplete, onError, link=None):
        """
        Express an Interest to fetch the content packet with contentName, and
        decrypt it, fetching keys as needed.

        :param Name contentName: The name of the content packet.
        :param onConsumeComplete: When the content packet is fetched and
          decrypted, this calls onConsumeComplete(contentData, result) where
          contentData is the fetched Data packet and result is the decrypted
          plain text Blob.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onPlainText: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onError: function object
        :param Link link: (optional) The Link object to use in Interests for
          data retrieval. This makes a copy of the Link object. If the Link
          object's getDelegations().size() is zero, don't use it. If omitted,
          don't use a Link object.
        """
        if link == None:
            link = Consumer.NO_LINK

        interest = Interest(contentName)

        def onVerified(validData):
            # Decrypt the content.
            def onPlainText(plainText):
                try:
                    onConsumeComplete(validData, plainText)
                except:
                    logging.exception("Error in onConsumeComplete")

            self._decryptContent(validData, onPlainText, onError)

        # Copy the Link object since the passed link may become invalid.
        self._sendInterest(interest, 1, Link(link), onVerified, onError)
Beispiel #9
0
class Producer(object):
    """
    Create a Producer to use the given ProducerDb, Face and other values.

    A producer can produce data with a naming convention:
      <prefix>/SAMPLE/<dataType>/[timestamp]

    The produced data packet is encrypted with a content key,
    which is stored in the ProducerDb database.

    A producer also needs to produce data containing a content key
    encrypted with E-KEYs. A producer can retrieve E-KEYs through the face,
    and will re-try for at most repeatAttemps times when E-KEY retrieval fails.

    :param Name prefix: The producer name prefix. This makes a copy of the Name.
    :param Name dataType: The dataType portion of the producer name. This makes
      a copy of the Name.
    :param Face face: The face used to retrieve keys.
    :param KeyChain keyChain: The keyChain used to sign data packets.
    :param ProducerDb database: The ProducerDb database for storing keys.
    :param int repeatAttempts: (optional) The maximum retry for retrieving
      keys. If omitted, use a default value of 3.
    :param Link keyRetrievalLink: (optional) The Link object to use in Interests
      for key retrieval. This makes a copy of the Link object. If the Link
      object's getDelegations().size() is zero, don't use it. If omitted, don't
      use a Link object.
    """
    def __init__(self,
                 prefix,
                 dataType,
                 face,
                 keyChain,
                 database,
                 repeatAttempts=None,
                 keyRetrievalLink=None):
        self._face = face
        self._keyChain = keyChain
        self._database = database
        self._maxRepeatAttempts = (3 if repeatAttempts == None else
                                   repeatAttempts)
        self._keyRetrievalLink = (Producer.NO_LINK if keyRetrievalLink == None
                                  else Link(keyRetrievalLink))

        # The dictionary key is the key Name The value is a Producer._KeyInfo.
        self._eKeyInfo = {}
        # The dictionary key is the float time stamp. The value is a Producer._KeyRequest.
        self._keyRequests = {}

        fixedPrefix = Name(prefix)
        fixedDataType = Name(dataType)

        # Fill _ekeyInfo with all permutations of dataType, including the 'E-KEY'
        # component of the name. This will be used in createContentKey to send
        # interests without reconstructing names every time.
        fixedPrefix.append(Encryptor.NAME_COMPONENT_READ)
        while fixedDataType.size() > 0:
            nodeName = Name(fixedPrefix)
            nodeName.append(fixedDataType)
            nodeName.append(Encryptor.NAME_COMPONENT_E_KEY)

            self._eKeyInfo[nodeName] = Producer._KeyInfo()
            fixedDataType = fixedDataType.getPrefix(-1)

        fixedPrefix.append(dataType)
        self._namespace = Name(prefix)
        self._namespace.append(Encryptor.NAME_COMPONENT_SAMPLE)
        self._namespace.append(dataType)

    @staticmethod
    def defaultOnError(errorCode, message):
        """
        The default onError callback which does nothing.
        """
        # Do nothing.
        pass

    def createContentKey(self,
                         timeSlot,
                         onEncryptedKeys,
                         onError=defaultOnError):
        """
        Create the content key corresponding to the timeSlot. This first checks
        if the content key exists. For an existing content key, this returns the
        content key name directly. If the key does not exist, this creates one
        and encrypts it using the corresponding E-KEYs. The encrypted content
        keys are passed to the onEncryptedKeys callback.

        :param float timeSlot: The time slot as milliseconds since Jan 1,
          1970 UTC.
        :param onEncryptedKeys: If this creates a content key, then this calls
          onEncryptedKeys(keys) where keys is a list of encrypted content key
          Data packets. If onEncryptedKeys is None, this does not use it.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onEncryptedKeys: function object
        :param onError: (optional) This calls  errorCode, message) for an
          error, where errorCode is from EncryptError.ErrorCode and message is a
          str. If omitted, use a default callback which does nothing.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onError: function object
        :return: The content key name.
        :rtype: Name
        """
        hourSlot = Producer._getRoundedTimeSlot(timeSlot)

        # Create the content key name.
        contentKeyName = Name(self._namespace)
        contentKeyName.append(Encryptor.NAME_COMPONENT_C_KEY)
        contentKeyName.append(Schedule.toIsoString(hourSlot))

        # Check if we have created the content key before.
        if self._database.hasContentKey(timeSlot):
            # We have created the content key. Return its name directly.
            return contentKeyName

        # We haven't created the content key. Create one and add it into the
        # database.
        aesParams = AesKeyParams(128)
        contentKeyBits = AesAlgorithm.generateKey(aesParams).getKeyBits()
        self._database.addContentKey(timeSlot, contentKeyBits)

        # Now we need to retrieve the E-KEYs for content key encryption.
        timeCount = round(timeSlot)
        self._keyRequests[timeCount] = Producer._KeyRequest(len(
            self._eKeyInfo))
        keyRequest = self._keyRequests[timeCount]

        # Send interests for all nodes in the tree.
        for keyName in self._eKeyInfo:
            # For each current E-KEY.
            keyInfo = self._eKeyInfo[keyName]
            if (timeSlot < keyInfo.beginTimeSlot
                    or timeSlot >= keyInfo.endTimeSlot):
                # The current E-KEY cannot cover the content key, so retrieve one.
                keyRequest.repeatAttempts[keyName] = 0
                self._sendKeyInterest(Interest(keyName), timeSlot,
                                      onEncryptedKeys, onError)
            else:
                # The current E-KEY can cover the content key.
                # Encrypt the content key directly.
                eKeyName = Name(keyName)
                eKeyName.append(Schedule.toIsoString(keyInfo.beginTimeSlot))
                eKeyName.append(Schedule.toIsoString(keyInfo.endTimeSlot))
                self._encryptContentKey(keyInfo.keyBits, eKeyName, timeSlot,
                                        onEncryptedKeys, onError)

        return contentKeyName

    def produce(self, data, timeSlot, content, onError=defaultOnError):
        """
        Encrypt the given content with the content key that covers timeSlot, and
        update the data packet with the encrypted content and an appropriate
        data name.

        :param Data data: An empty Data object which is updated.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param Blob content: The content to encrypt.
        :param onError: (optional) This calls onError(errorCode, message) for an
          error, where errorCode is from EncryptError.ErrorCode and message is a
          str. If omitted, use a default callback which does nothing.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onError: function object
        """
        # Get a content key.
        contentKeyName = self.createContentKey(timeSlot, None, onError)
        contentKey = self._database.getContentKey(timeSlot)

        # Produce data.
        dataName = Name(self._namespace)
        dataName.append(Schedule.toIsoString(timeSlot))

        data.setName(dataName)
        params = EncryptParams(EncryptAlgorithmType.AesCbc, 16)
        Encryptor.encryptData(data, content, contentKeyName, contentKey,
                              params)
        self._keyChain.sign(data)

    class _KeyInfo(object):
        def __init__(self):
            self.beginTimeSlot = 0.0
            self.endTimeSlot = 0.0
            self.keyBits = None  # Blob

    class _KeyRequest(object):
        def __init__(self, interests):
            self.interestCount = interests  # int
            # The dictionary key is the Name. The value is an int count.
            self.repeatAttempts = {}
            self.encryptedKeys = []  # of Data

    @staticmethod
    def _getRoundedTimeSlot(timeSlot):
        """
        Round timeSlot to the nearest whole hour, so that we can store content
        keys uniformly (by start of the hour).

        :param float timeSlot: The time slot as milliseconds since Jan 1,
          1970 UTC.
        :return: The start of the hour as milliseconds since Jan 1, 1970 UTC.
        :rtype: float
        """
        return round(math.floor(round(timeSlot) / 3600000.0) * 3600000.0)

    def _sendKeyInterest(self, interest, timeSlot, onEncryptedKeys, onError):
        """
        Send an interest with the given name through the face with callbacks to
          _handleCoveringKey, _handleTimeout and _handleNetworkNack.

        :param Interest interest: The interest to send.
        :param float timeSlot: The time slot, passed to _handleCoveringKey,
          _handleTimeout and _handleNetworkNack.
        :param onEncryptedKeys: The OnEncryptedKeys callback, passed to
          _handleCoveringKey, _handleTimeout and _handleNetworkNack.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        """
        def onKey(interest, data):
            self._handleCoveringKey(interest, data, timeSlot, onEncryptedKeys,
                                    onError)

        def onTimeout(interest):
            self._handleTimeout(interest, timeSlot, onEncryptedKeys, onError)

        def onNetworkNack(interest, networkNack):
            self._handleNetworkNack(interest, networkNack, timeSlot,
                                    onEncryptedKeys, onError)

        if self._keyRetrievalLink.getDelegations().size() == 0:
            # We can use the supplied interest without copying.
            request = interest
        else:
            # Copy the supplied interest and add the Link.
            request = Interest(interest)
            # This will use a cached encoding if available.
            request.setLinkWireEncoding(self._keyRetrievalLink.wireEncode())

        self._face.expressInterest(request, onKey, onTimeout, onNetworkNack)

    def _handleTimeout(self, interest, timeSlot, onEncryptedKeys, onError):
        """
        This is called from an expressInterest timeout to update the state of
        keyRequest.

        :param Interest interest: The timed-out interest.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        """
        timeCount = round(timeSlot)
        keyRequest = self._keyRequests[timeCount]

        interestName = interest.getName()
        if keyRequest.repeatAttempts[interestName] < self._maxRepeatAttempts:
            # Increase the retrial count.
            keyRequest.repeatAttempts[interestName] += 1
            self._sendKeyInterest(interest, timeSlot, onEncryptedKeys, onError)
        else:
            # Treat an eventual timeout as a network Nack.
            self._handleNetworkNack(interest, NetworkNack(), timeSlot,
                                    onEncryptedKeys, onError)

    def _handleNetworkNack(self, interest, networkNack, timeSlot,
                           onEncryptedKeys, onError):
        """
        This is called from an expressInterest OnNetworkNack to handle a network
        Nack for the E-KEY requested through the Interest. Decrease the
        outstanding E-KEY interest count for the C-KEY corresponding to the
        timeSlot.

        :param Interest interest: The interest given to expressInterest.
        :param NetworkNack networkNack: The returned NetworkNack (unused).
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        """
        # We have run out of options....
        timeCount = round(timeSlot)
        self._updateKeyRequest(self._keyRequests[timeCount], timeCount,
                               onEncryptedKeys)

    def _updateKeyRequest(self, keyRequest, timeCount, onEncryptedKeys):
        """
        Decrease the count of outstanding E-KEY interests for the C-KEY for
        timeCount. If the count decreases to 0, invoke onEncryptedKeys.

        :param Producer._KeyRequest keyRequest: The KeyRequest with the
          interestCount to update.
        :param float timeCount: The time count for indexing keyRequests_.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        """
        keyRequest.interestCount -= 1
        if keyRequest.interestCount == 0 and onEncryptedKeys != None:
            try:
                onEncryptedKeys(keyRequest.encryptedKeys)
            except:
                logging.exception("Error in onEncryptedKeys")
            if timeCount in self._keyRequests:
                del self._keyRequests[timeCount]

    def _handleCoveringKey(self, interest, data, timeSlot, onEncryptedKeys,
                           onError):
        """
        This is called from an expressInterest OnData to check that the
        encryption key contained in data fits the timeSlot. This sends a refined
        interest if required.

        :param Interest interest: The interest given to expressInterest.
        :param Data data: The fetched Data packet.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        """
        timeCount = round(timeSlot)
        keyRequest = self._keyRequests[timeCount]

        interestName = interest.getName()
        keyName = data.getName()

        begin = Schedule.fromIsoString(
            str(keyName.get(Producer.START_TIME_STAMP_INDEX).getValue()))
        end = Schedule.fromIsoString(
            str(keyName.get(Producer.END_TIME_STAMP_INDEX).getValue()))

        if timeSlot >= end:
            # If the received E-KEY covers some earlier period, try to retrieve
            # an E-KEY covering a later one.
            keyRequest.repeatAttempts[interestName] = 0
            self._sendKeyInterest(Interest(interestName), timeSlot,
                                  onEncryptedKeys, onError)
        else:
            # If the received E-KEY covers the content key, encrypt the content.
            encryptionKey = data.getContent()
            # If everything is correct, save the E-KEY as the current key.
            if self._encryptContentKey(encryptionKey, keyName, timeSlot,
                                       onEncryptedKeys, onError):
                keyInfo = self._eKeyInfo[interestName]
                keyInfo.beginTimeSlot = begin
                keyInfo.endTimeSlot = end
                keyInfo.keyBits = encryptionKey

    def _encryptContentKey(self, encryptionKey, eKeyName, timeSlot,
                           onEncryptedKeys, onError):
        """
        Get the content key from the database_ and encrypt it for the timeSlot
          using encryptionKey.

        :param Blob encryptionKey: The encryption key value.
        :param Name eKeyName: The key name for the EncryptedContent.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
           this calls onEncryptedKeys(keys) where keys is a list of encrypted
           content key Data packets. If onEncryptedKeys is None, this does not
           use it.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        :return: True if encryption succeeds, otherwise False.
        :rtype: bool
        """
        timeCount = round(timeSlot)
        keyRequest = self._keyRequests[timeCount]

        keyName = Name(self._namespace)
        keyName.append(Encryptor.NAME_COMPONENT_C_KEY)
        keyName.append(
            Schedule.toIsoString(Producer._getRoundedTimeSlot(timeSlot)))

        contentKey = self._database.getContentKey(timeSlot)

        cKeyData = Data()
        cKeyData.setName(keyName)
        params = EncryptParams(EncryptAlgorithmType.RsaOaep)
        try:
            Encryptor.encryptData(cKeyData, contentKey, eKeyName,
                                  encryptionKey, params)
        except Exception as ex:
            try:
                onError(EncryptError.ErrorCode.EncryptionFailure,
                        "encryptData error: " + repr(ex))
            except:
                logging.exception("Error in onError")
            return False

        self._keyChain.sign(cKeyData)
        keyRequest.encryptedKeys.append(cKeyData)
        self._updateKeyRequest(keyRequest, timeCount, onEncryptedKeys)
        return True

    START_TIME_STAMP_INDEX = -2
    END_TIME_STAMP_INDEX = -1
    NO_LINK = Link()
Beispiel #10
0
class Consumer(object):
    """
    Create a Consumer to use the given ConsumerDb, Face and other values.

    :param Face face: The face used for data packet and key fetching.
    :param KeyChain keyChain: The keyChain used to verify data packets.
    :param Name groupName: The reading group name that the consumer belongs to.
      This makes a copy of the Name.
    :param Name consumerName: The identity of the consumer. This makes a copy of
      the Name.
    :param ConsumerDb database: The ConsumerDb database for storing decryption
      keys.
    :param Link cKeyLink: (optional) The Link object to use in Interests for
      C-KEY retrieval. This makes a copy of the Link object. If the Link
      object's getDelegations().size() is zero, don't use it. If omitted, don't
      use a Link object.
    :param Link dKeyLink: (optional) The Link object to use in Interests for
      D-KEY retrieval. This makes a copy of the Link object. If the Link
      object's getDelegations().size() is zero, don't use it. If omitted, don't
      use a Link object.
    """
    def __init__(self,
                 face,
                 keyChain,
                 groupName,
                 consumerName,
                 database,
                 cKeyLink=None,
                 dKeyLink=None):
        self._database = database
        self._keyChain = keyChain
        self._face = face
        self._groupName = Name(groupName)
        self._consumerName = Name(consumerName)
        self._cKeyLink = (Consumer.NO_LINK
                          if cKeyLink == None else Link(cKeyLink))
        self._dKeyLink = (Consumer.NO_LINK
                          if dKeyLink == None else Link(dKeyLink))

        # The dictionary key is the C-KEY name. The value is the encoded key Blob.
        self._cKeyMap = {}
        # The dictionary key is the D-KEY name. The value is the encoded key Blob.
        self._dKeyMap = {}

    def consume(self, contentName, onConsumeComplete, onError, link=None):
        """
        Express an Interest to fetch the content packet with contentName, and
        decrypt it, fetching keys as needed.

        :param Name contentName: The name of the content packet.
        :param onConsumeComplete: When the content packet is fetched and
          decrypted, this calls onConsumeComplete(contentData, result) where
          contentData is the fetched Data packet and result is the decrypted
          plain text Blob.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onPlainText: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onError: function object
        :param Link link: (optional) The Link object to use in Interests for
          data retrieval. This makes a copy of the Link object. If the Link
          object's getDelegations().size() is zero, don't use it. If omitted,
          don't use a Link object.
        """
        if link == None:
            link = Consumer.NO_LINK

        interest = Interest(contentName)

        def onVerified(validData):
            # Decrypt the content.
            def onPlainText(plainText):
                try:
                    onConsumeComplete(validData, plainText)
                except:
                    logging.exception("Error in onConsumeComplete")

            self._decryptContent(validData, onPlainText, onError)

        # Copy the Link object since the passed link may become invalid.
        self._sendInterest(interest, 1, Link(link), onVerified, onError)

    def setGroup(self, groupName):
        """
        Set the group name.

        :param Name groupName: The reading group name that the consumer belongs
          to. This makes a copy of the Name.
        """
        self._groupName = Name(groupName)

    def addDecryptionKey(self, keyName, keyBlob):
        """
        Add a new decryption key with keyName and keyBlob to the database.

        :param Name keyName: The key name.
        :param Blob keyBlob: The encoded key.
        :raises ConsumerDb.Error: If a key with the same keyName already exists
          in the database, or other database error.
        :raises RuntimeError: if the consumer name is not a prefix of the key name.
        """
        if not self._consumerName.match(keyName):
            raise RuntimeError(
                "addDecryptionKey: The consumer name must be a prefix of the key name"
            )

        self._database.addKey(keyName, keyBlob)

    @staticmethod
    def _decrypt(encryptedContent, keyBits, onPlainText, onError):
        """
        Decrypt encryptedContent using keyBits.

        :param encryptedContent: The EncryptedContent to decrypt, or a Blob
          which is first decoded as an EncryptedContent.
        :type encryptedContent: Blob or EncryptedContent
        :param {Blob} keyBits The key value.
        :param onPlainText: When encryptedBlob is decrypted, this calls
          onPlainText(decryptedBlob) with the decrypted Blob.
        :type onPlainText: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
        :type onError: function object
        """
        if isinstance(encryptedContent, Blob):
            # Decode as EncryptedContent.
            encryptedBlob = encryptedContent
            encryptedContent = EncryptedContent()
            encryptedContent.wireDecode(encryptedBlob)

        payload = encryptedContent.getPayload()

        if encryptedContent.getAlgorithmType() == EncryptAlgorithmType.AesCbc:
            # Prepare the parameters.
            decryptParams = EncryptParams(EncryptAlgorithmType.AesCbc)
            decryptParams.setInitialVector(encryptedContent.getInitialVector())

            # Decrypt the content.
            try:
                content = AesAlgorithm.decrypt(keyBits, payload, decryptParams)
            except Exception as ex:
                try:
                    onError(EncryptError.ErrorCode.InvalidEncryptedFormat,
                            repr(ex))
                except:
                    logging.exception("Error in onError")
                return
            onPlainText(content)
        elif encryptedContent.getAlgorithmType(
        ) == EncryptAlgorithmType.RsaOaep:
            # Prepare the parameters.
            decryptParams = EncryptParams(EncryptAlgorithmType.RsaOaep)

            # Decrypt the content.
            try:
                content = RsaAlgorithm.decrypt(keyBits, payload, decryptParams)
            except Exception as ex:
                Consumer._callOnError(
                    onError, EncryptError.ErrorCode.InvalidEncryptedFormat,
                    repr(ex))
                return
            onPlainText(content)
        else:
            Consumer._callOnError(
                onError, EncryptError.ErrorCode.UnsupportedEncryptionScheme,
                repr(encryptedContent.getAlgorithmType()))

    def _decryptContent(self, data, onPlainText, onError):
        """
        Decrypt the data packet.

        :param Data data: The data packet. This does not verify the packet.
        :param onPlainText: When the data packet is decrypted, this calls
          onPlainText(decryptedBlob) with the decrypted blob.
        :type onPlainText: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
        :type onError: function object
        """
        # Get the encrypted content.
        dataEncryptedContent = EncryptedContent()
        try:
            dataEncryptedContent.wireDecode(data.getContent())
        except Exception as ex:
            Consumer._callOnError(
                onError, EncryptError.ErrorCode.InvalidEncryptedFormat,
                repr(ex))
            return
        cKeyName = dataEncryptedContent.getKeyLocator().getKeyName()

        # Check if the content key is already in the store.
        if cKeyName in self._cKeyMap:
            cKey = self._cKeyMap[cKeyName]
            self._decrypt(dataEncryptedContent, cKey, onPlainText, onError)
        else:
            # Retrieve the C-KEY Data from the network.
            interestName = Name(cKeyName)
            interestName.append(Encryptor.NAME_COMPONENT_FOR).append(
                self._groupName)
            interest = Interest(interestName)

            def onVerified(validCKeyData):
                def localOnPlainText(cKeyBits):
                    # cKeyName is already a copy inside the local
                    #   dataEncryptedContent.
                    self._cKeyMap[cKeyName] = cKeyBits
                    Consumer._decrypt(dataEncryptedContent, cKeyBits,
                                      onPlainText, onError)

                self._decryptCKey(validCKeyData, localOnPlainText, onError)

            self._sendInterest(interest, 1, self._cKeyLink, onVerified,
                               onError)

    def _decryptCKey(self, cKeyData, onPlainText, onError):
        """
        Decrypt cKeyData.

        :param Data cKeyData: The C-KEY data packet.
        :param onPlainText: When the data packet is decrypted, this calls
          onPlainText(decryptedBlob) with the decrypted blob.
        :type onPlainText: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
        :type onError: function object
        """
        # Get the encrypted content.
        cKeyContent = cKeyData.getContent()
        cKeyEncryptedContent = EncryptedContent()
        try:
            cKeyEncryptedContent.wireDecode(cKeyContent)
        except Exception as ex:
            try:
                onError(EncryptError.ErrorCode.InvalidEncryptedFormat,
                        repr(ex))
            except:
                logging.exception("Error in onError")
            return
        eKeyName = cKeyEncryptedContent.getKeyLocator().getKeyName()
        dKeyName = eKeyName.getPrefix(-3)
        dKeyName.append(Encryptor.NAME_COMPONENT_D_KEY).append(
            eKeyName.getSubName(-2))

        # Check if the decryption key is already in the store.
        if dKeyName in self._dKeyMap:
            dKey = self._dKeyMap[dKeyName]
            Consumer._decrypt(cKeyEncryptedContent, dKey, onPlainText, onError)
        else:
            # Get the D-Key Data.
            interestName = Name(dKeyName)
            interestName.append(Encryptor.NAME_COMPONENT_FOR).append(
                self._consumerName)
            interest = Interest(interestName)

            def onVerified(validDKeyData):
                def localOnPlainText(dKeyBits):
                    # dKeyName is already a local copy.
                    self._dKeyMap[dKeyName] = dKeyBits
                    Consumer._decrypt(cKeyEncryptedContent, dKeyBits,
                                      onPlainText, onError)

                self._decryptDKey(validDKeyData, localOnPlainText, onError)

            self._sendInterest(interest, 1, self._dKeyLink, onVerified,
                               onError)

    def _decryptDKey(self, dKeyData, onPlainText, onError):
        """
        Decrypt dKeyData.

        :param Data dKeyData: The D-KEY data packet.
        :param onPlainText: When the data packet is decrypted, this calls
          onPlainText(decryptedBlob) with the decrypted blob.
        :type onPlainText: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
        :type onError: function object
        """
        # Get encrypted content.
        dataContent = dKeyData.getContent()

        # Process the nonce.
        # dataContent is a sequence of the two EncryptedContent.
        encryptedNonce = EncryptedContent()
        try:
            encryptedNonce.wireDecode(dataContent)
        except Exception as ex:
            try:
                onError(EncryptError.ErrorCode.InvalidEncryptedFormat,
                        repr(ex))
            except:
                logging.exception("Error in onError")
            return
        consumerKeyName = encryptedNonce.getKeyLocator().getKeyName()

        # Get consumer decryption key.
        try:
            consumerKeyBlob = self._getDecryptionKey(consumerKeyName)
        except Exception as ex:
            Consumer._callOnError(onError, EncryptError.ErrorCode.NoDecryptKey,
                                  "Database error: " + repr(ex))
            return
        if consumerKeyBlob.size() == 0:
            try:
                onError(
                    EncryptError.ErrorCode.NoDecryptKey,
                    "The desired consumer decryption key in not in the database"
                )
            except:
                logging.exception("Error in onError")
            return

        # Process the D-KEY.
        # Use the size of encryptedNonce to find the start of encryptedPayload.
        encryptedPayloadBlob = Blob(
            dataContent.buf()[encryptedNonce.wireEncode().size():], False)
        if encryptedPayloadBlob.size() == 0:
            try:
                onError(
                    EncryptError.ErrorCode.InvalidEncryptedFormat,
                    "The data packet does not satisfy the D-KEY packet format")
            except:
                logging.exception("Error in onError")
            return

        # Decrypt the D-KEY.
        Consumer._decrypt(
            encryptedNonce,
            consumerKeyBlob, lambda nonceKeyBits: Consumer._decrypt(
                encryptedPayloadBlob, nonceKeyBits, onPlainText, onError),
            onError)

    def _sendInterest(self, interest, nRetrials, link, onVerified, onError):
        """
        Express the interest, call verifyData for the fetched Data packet and
        call onVerified if verify succeeds. If verify fails, call
        onError(EncryptError.ErrorCode.Validation, "verifyData failed"). If the
        interest times out, re-express nRetrials times. If the interest times
        out nRetrials times, or for a network Nack, call
        onError(EncryptError.ErrorCode.DataRetrievalFailure, interest.getName().toUri()).

        :param Interest interest: The Interest to express.
        :param int nRetrials: The number of retrials left after a timeout.
        :param Link link: The Link object to use in the Interest. This does not
          make a copy of the Link object. If the Link object's
          getDelegations().size() is zero, don't use it.
        :param onVerified: When the fetched Data packet validation succeeds,
          this calls onVerified(data).
        :type onVerified: function object
        :param onError: This calls onError(errorCode, message) for an error,
          where errorCode is from EncryptError.ErrorCode and message is a str.
        :type onError: function object
        """

        # Prepare the callback functions.
        def onData(contentInterest, contentData):
            # The Interest has no selectors, so assume the library correctly
            # matched with the Data name before calling onData.

            try:
                self._keyChain.verifyData(
                    contentData, onVerified,
                    lambda d, reason: Consumer._callOnError(
                        onError, EncryptError.ErrorCode.Validation,
                        "verifyData failed. Reason: " + reason))
            except Exception as ex:
                try:
                    onError(EncryptError.ErrorCode.General,
                            "verifyData error: " + repr(ex))
                except:
                    logging.exception("Error in onError")

        def onNetworkNack(interest, networkNack):
            # We have run out of options. Report a retrieval failure.
            try:
                onError(EncryptError.ErrorCode.DataRetrievalFailure,
                        interest.getName().toUri())
            except:
                logging.exception("Error in onError")

        def onTimeout(interest):
            if nRetrials > 0:
                self._sendInterest(interest, nRetrials - 1, link, onVerified,
                                   onError)
            else:
                onNetworkNack(interest, NetworkNack())

        if link.getDelegations().size() == 0:
            # We can use the supplied interest without copying.
            request = interest
        else:
            # Copy the supplied interest and add the Link.
            request = Interest(interest)
            # This will use a cached encoding if available.
            request.setLinkWireEncoding(link.wireEncode())

        try:
            self._face.expressInterest(request, onData, onTimeout,
                                       onNetworkNack)
        except Exception as ex:
            try:
                onError(EncryptError.ErrorCode.General,
                        "expressInterest error: " + repr(ex))
            except:
                logging.exception("Error in onError")

    def _getDecryptionKey(self, decryptionKeyName):
        """
        Get the encoded blob of the decryption key with decryptionKeyName from
        the database.

        :param Name decryptionKeyName: The key name.
        :return: A Blob with the encoded key, or an isNull Blob if cannot find
          the key with keyName.
        :rtype: Blob
        :raises ConsumerDb.Error: For a database error.
        """
        return self._database.getKey(decryptionKeyName)

    @staticmethod
    def _callOnError(onError, errorCode, message):
        """
        Call onError(errorCode, message) within a try block to log exceptions
        that it throws. We name this separate helper function to use inside
        lambda expressions.
        """
        try:
            onError(errorCode, message)
        except:
            logging.exception("Error in onError")

    NO_LINK = Link()
Beispiel #11
0
class Producer(object):
    """
    Create a Producer to use the given ProducerDb, Face and other values.

    A producer can produce data with a naming convention:
      <prefix>/SAMPLE/<dataType>/[timestamp]

    The produced data packet is encrypted with a content key,
    which is stored in the ProducerDb database.

    A producer also needs to produce data containing a content key
    encrypted with E-KEYs. A producer can retrieve E-KEYs through the face,
    and will re-try for at most repeatAttemps times when E-KEY retrieval fails.

    :param Name prefix: The producer name prefix. This makes a copy of the Name.
    :param Name dataType: The dataType portion of the producer name. This makes
      a copy of the Name.
    :param Face face: The face used to retrieve keys.
    :param KeyChain keyChain: The keyChain used to sign data packets.
    :param ProducerDb database: The ProducerDb database for storing keys.
    :param int repeatAttempts: (optional) The maximum retry for retrieving
      keys. If omitted, use a default value of 3.
    :param Link keyRetrievalLink: (optional) The Link object to use in Interests
      for key retrieval. This makes a copy of the Link object. If the Link
      object's getDelegations().size() is zero, don't use it. If omitted, don't
      use a Link object.
    """
    def __init__(self,
                 prefix,
                 dataType,
                 face,
                 keyChain,
                 database,
                 repeatAttempts=None,
                 keyRetrievalLink=None):
        self._face = face
        self._keyChain = keyChain
        self._database = database
        self._maxRepeatAttempts = (3 if repeatAttempts == None else
                                   repeatAttempts)
        self._keyRetrievalLink = (Producer.NO_LINK if keyRetrievalLink == None
                                  else Link(keyRetrievalLink))

        # The dictionary key is the key Name The value is a Producer._KeyInfo.
        self._eKeyInfo = {}
        # The dictionary key is the float time stamp. The value is a Producer._KeyRequest.
        self._keyRequests = {}

        fixedPrefix = Name(prefix)
        fixedDataType = Name(dataType)

        # Fill _ekeyInfo with all permutations of dataType, including the 'E-KEY'
        # component of the name. This will be used in createContentKey to send
        # interests without reconstructing names every time.
        fixedPrefix.append(Encryptor.NAME_COMPONENT_READ)
        while fixedDataType.size() > 0:
            nodeName = Name(fixedPrefix)
            nodeName.append(fixedDataType)
            nodeName.append(Encryptor.NAME_COMPONENT_E_KEY)

            self._eKeyInfo[nodeName] = Producer._KeyInfo()
            fixedDataType = fixedDataType.getPrefix(-1)

        fixedPrefix.append(dataType)
        self._namespace = Name(prefix)
        self._namespace.append(Encryptor.NAME_COMPONENT_SAMPLE)
        self._namespace.append(dataType)

    @staticmethod
    def defaultOnError(errorCode, message):
        """
        The default onError callback which does nothing.
        """
        # Do nothing.
        pass

    def createContentKey(self,
                         timeSlot,
                         onEncryptedKeys,
                         onError=defaultOnError):
        """
        Create the content key corresponding to the timeSlot. This first checks
        if the content key exists. For an existing content key, this returns the
        content key name directly. If the key does not exist, this creates one
        and encrypts it using the corresponding E-KEYs. The encrypted content
        keys are passed to the onEncryptedKeys callback.

        :param float timeSlot: The time slot as milliseconds since Jan 1,
          1970 UTC.
        :param onEncryptedKeys: If this creates a content key, then this calls
          onEncryptedKeys(keys) where keys is a list of encrypted content key
          Data packets. If onEncryptedKeys is None, this does not use it.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onEncryptedKeys: function object
        :param onError: (optional) This calls  errorCode, message) for an
          error, where errorCode is from EncryptError.ErrorCode and message is a
          str. If omitted, use a default callback which does nothing.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onError: function object
        :return: The content key name.
        :rtype: Name
        """
        hourSlot = Producer._getRoundedTimeSlot(timeSlot)

        # Create the content key name.
        contentKeyName = Name(self._namespace)
        contentKeyName.append(Encryptor.NAME_COMPONENT_C_KEY)
        contentKeyName.append(Schedule.toIsoString(hourSlot))

        # Check if we have created the content key before.
        if self._database.hasContentKey(timeSlot):
            # We have created the content key. Return its name directly.
            return contentKeyName

        # We haven't created the content key. Create one and add it into the
        # database.
        aesParams = AesKeyParams(128)
        contentKeyBits = AesAlgorithm.generateKey(aesParams).getKeyBits()
        self._database.addContentKey(timeSlot, contentKeyBits)

        # Now we need to retrieve the E-KEYs for content key encryption.
        timeCount = round(timeSlot)
        self._keyRequests[timeCount] = Producer._KeyRequest(len(
            self._eKeyInfo))
        keyRequest = self._keyRequests[timeCount]

        # Check if the current E-KEYs can cover the content key.
        timeRange = Exclude()
        Producer.excludeAfter(timeRange,
                              Name.Component(Schedule.toIsoString(timeSlot)))
        # Send interests for all nodes in the tree.
        for keyName in self._eKeyInfo:
            # For each current E-KEY.
            keyInfo = self._eKeyInfo[keyName]
            if (timeSlot < keyInfo.beginTimeSlot
                    or timeSlot >= keyInfo.endTimeSlot):
                # The current E-KEY cannot cover the content key, so retrieve one.
                keyRequest.repeatAttempts[keyName] = 0
                self._sendKeyInterest(
                    Interest(keyName).setExclude(timeRange).setChildSelector(
                        1), timeSlot, onEncryptedKeys, onError)
            else:
                # The current E-KEY can cover the content key.
                # Encrypt the content key directly.
                eKeyName = Name(keyName)
                eKeyName.append(Schedule.toIsoString(keyInfo.beginTimeSlot))
                eKeyName.append(Schedule.toIsoString(keyInfo.endTimeSlot))
                self._encryptContentKey(keyInfo.keyBits, eKeyName, timeSlot,
                                        onEncryptedKeys, onError)

        return contentKeyName

    def produce(self, data, timeSlot, content, onError=defaultOnError):
        """
        Encrypt the given content with the content key that covers timeSlot, and
        update the data packet with the encrypted content and an appropriate
        data name.

        :param Data data: An empty Data object which is updated.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param Blob content: The content to encrypt.
        :param onError: (optional) This calls onError(errorCode, message) for an
          error, where errorCode is from EncryptError.ErrorCode and message is a
          str. If omitted, use a default callback which does nothing.
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onError: function object
        """
        # Get a content key.
        contentKeyName = self.createContentKey(timeSlot, None, onError)
        contentKey = self._database.getContentKey(timeSlot)

        # Produce data.
        dataName = Name(self._namespace)
        dataName.append(Schedule.toIsoString(timeSlot))

        data.setName(dataName)
        params = EncryptParams(EncryptAlgorithmType.AesCbc, 16)
        Encryptor.encryptData(data, content, contentKeyName, contentKey,
                              params)
        self._keyChain.sign(data)

    class _KeyInfo(object):
        def __init__(self):
            self.beginTimeSlot = 0.0
            self.endTimeSlot = 0.0
            self.keyBits = None  # Blob

    class _KeyRequest(object):
        def __init__(self, interests):
            self.interestCount = interests  # int
            # The dictionary key is the Name. The value is an int count.
            self.repeatAttempts = {}
            self.encryptedKeys = []  # of Data

    @staticmethod
    def _getRoundedTimeSlot(timeSlot):
        """
        Round timeSlot to the nearest whole hour, so that we can store content
        keys uniformly (by start of the hour).

        :param float timeSlot: The time slot as milliseconds since Jan 1,
          1970 UTC.
        :return: The start of the hour as milliseconds since Jan 1, 1970 UTC.
        :rtype: float
        """
        return round(math.floor(round(timeSlot) / 3600000.0) * 3600000.0)

    def _sendKeyInterest(self, interest, timeSlot, onEncryptedKeys, onError):
        """
        Send an interest with the given name through the face with callbacks to
          _handleCoveringKey, _handleTimeout and _handleNetworkNack.

        :param Interest interest: The interest to send.
        :param float timeSlot: The time slot, passed to _handleCoveringKey,
          _handleTimeout and _handleNetworkNack.
        :param onEncryptedKeys: The OnEncryptedKeys callback, passed to
          _handleCoveringKey, _handleTimeout and _handleNetworkNack.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        """
        def onKey(interest, data):
            self._handleCoveringKey(interest, data, timeSlot, onEncryptedKeys,
                                    onError)

        def onTimeout(interest):
            self._handleTimeout(interest, timeSlot, onEncryptedKeys, onError)

        def onNetworkNack(interest, networkNack):
            self._handleNetworkNack(interest, networkNack, timeSlot,
                                    onEncryptedKeys, onError)

        if self._keyRetrievalLink.getDelegations().size() == 0:
            # We can use the supplied interest without copying.
            request = interest
        else:
            # Copy the supplied interest and add the Link.
            request = Interest(interest)
            # This will use a cached encoding if available.
            request.setLinkWireEncoding(self._keyRetrievalLink.wireEncode())

        self._face.expressInterest(request, onKey, onTimeout, onNetworkNack)

    def _handleTimeout(self, interest, timeSlot, onEncryptedKeys, onError):
        """
        This is called from an expressInterest timeout to update the state of
        keyRequest.

        :param Interest interest: The timed-out interest.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        """
        timeCount = round(timeSlot)
        keyRequest = self._keyRequests[timeCount]

        interestName = interest.getName()
        if keyRequest.repeatAttempts[interestName] < self._maxRepeatAttempts:
            # Increase the retrial count.
            keyRequest.repeatAttempts[interestName] += 1
            self._sendKeyInterest(interest, timeSlot, onEncryptedKeys, onError)
        else:
            # Treat an eventual timeout as a network Nack.
            self._handleNetworkNack(interest, NetworkNack(), timeSlot,
                                    onEncryptedKeys, onError)

    def _handleNetworkNack(self, interest, networkNack, timeSlot,
                           onEncryptedKeys, onError):
        """
        This is called from an expressInterest OnNetworkNack to handle a network
        Nack for the E-KEY requested through the Interest. Decrease the
        outstanding E-KEY interest count for the C-KEY corresponding to the
        timeSlot.

        :param Interest interest: The interest given to expressInterest.
        :param NetworkNack networkNack: The returned NetworkNack (unused).
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        """
        # We have run out of options....
        timeCount = round(timeSlot)
        self._updateKeyRequest(self._keyRequests[timeCount], timeCount,
                               onEncryptedKeys)

    def _updateKeyRequest(self, keyRequest, timeCount, onEncryptedKeys):
        """
        Decrease the count of outstanding E-KEY interests for the C-KEY for
        timeCount. If the count decreases to 0, invoke onEncryptedKeys.

        :param Producer._KeyRequest keyRequest: The KeyRequest with the
          interestCount to update.
        :param float timeCount: The time count for indexing keyRequests_.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        """
        keyRequest.interestCount -= 1
        if keyRequest.interestCount == 0 and onEncryptedKeys != None:
            try:
                onEncryptedKeys(keyRequest.encryptedKeys)
            except:
                logging.exception("Error in onEncryptedKeys")
            if timeCount in self._keyRequests:
                del self._keyRequests[timeCount]

    def _handleCoveringKey(self, interest, data, timeSlot, onEncryptedKeys,
                           onError):
        """
        This is called from an expressInterest OnData to check that the
        encryption key contained in data fits the timeSlot. This sends a refined
        interest if required.

        :param Interest interest: The interest given to expressInterest.
        :param Data data: The fetched Data packet.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
          this calls onEncryptedKeys(keys) where keys is a list of encrypted
          content key Data packets. If onEncryptedKeys is None, this does not
          use it.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        """
        timeCount = round(timeSlot)
        keyRequest = self._keyRequests[timeCount]

        interestName = interest.getName()
        keyName = data.getName()

        begin = Schedule.fromIsoString(
            str(keyName.get(Producer.START_TIME_STAMP_INDEX).getValue()))
        end = Schedule.fromIsoString(
            str(keyName.get(Producer.END_TIME_STAMP_INDEX).getValue()))

        if timeSlot >= end:
            # If the received E-KEY covers some earlier period, try to retrieve
            # an E-KEY covering a later one.
            timeRange = Exclude(interest.getExclude())
            Producer.excludeBefore(
                timeRange, keyName.get(Producer.START_TIME_STAMP_INDEX))
            keyRequest.repeatAttempts[interestName] = 0
            self._sendKeyInterest(
                Interest(interestName).setExclude(timeRange).setChildSelector(
                    1), timeSlot, onEncryptedKeys, onError)
        else:
            # If the received E-KEY covers the content key, encrypt the content.
            encryptionKey = data.getContent()
            # If everything is correct, save the E-KEY as the current key.
            if self._encryptContentKey(encryptionKey, keyName, timeSlot,
                                       onEncryptedKeys, onError):
                keyInfo = self._eKeyInfo[interestName]
                keyInfo.beginTimeSlot = begin
                keyInfo.endTimeSlot = end
                keyInfo.keyBits = encryptionKey

    def _encryptContentKey(self, encryptionKey, eKeyName, timeSlot,
                           onEncryptedKeys, onError):
        """
        Get the content key from the database_ and encrypt it for the timeSlot
          using encryptionKey.

        :param Blob encryptionKey: The encryption key value.
        :param Name eKeyName: The key name for the EncryptedContent.
        :param float timeSlot: The time slot as milliseconds since Jan 1, 1970 UTC.
        :param onEncryptedKeys: When there are no more interests to process,
           this calls onEncryptedKeys(keys) where keys is a list of encrypted
           content key Data packets. If onEncryptedKeys is None, this does not
           use it.
        :type onEncryptedKeys: function object
        :param onError: This calls onError(errorCode, message) for an error.
        :type onError: function object
        :return: True if encryption succeeds, otherwise False.
        :rtype: bool
        """
        timeCount = round(timeSlot)
        keyRequest = self._keyRequests[timeCount]

        keyName = Name(self._namespace)
        keyName.append(Encryptor.NAME_COMPONENT_C_KEY)
        keyName.append(
            Schedule.toIsoString(Producer._getRoundedTimeSlot(timeSlot)))

        contentKey = self._database.getContentKey(timeSlot)

        cKeyData = Data()
        cKeyData.setName(keyName)
        params = EncryptParams(EncryptAlgorithmType.RsaOaep)
        try:
            Encryptor.encryptData(cKeyData, contentKey, eKeyName,
                                  encryptionKey, params)
        except Exception as ex:
            try:
                onError(EncryptError.ErrorCode.EncryptionFailure,
                        "encryptData error: " + repr(ex))
            except:
                logging.exception("Error in onError")
            return False

        self._keyChain.sign(cKeyData)
        keyRequest.encryptedKeys.append(cKeyData)
        self._updateKeyRequest(keyRequest, timeCount, onEncryptedKeys)
        return True

    # TODO: Move this to be the main representation inside the Exclude object.
    class ExcludeEntry(object):
        """
        Create a new ExcludeEntry.

        :param Name.Component component:
        :param bool anyFollowsComponent:
        """
        def __init__(self, component, anyFollowsComponent):
            self._component = component
            self._anyFollowsComponent = anyFollowsComponent

    @staticmethod
    def getExcludeEntries(exclude):
        """
        Create a list of ExcludeEntry from the Exclude object.

        :param Exclude exclude: The Exclude object to read.
        :return: A new array of ExcludeEntry.
        :rtype: Array<ExcludeEntry>
        """
        entries = []

        for i in range(exclude.size()):
            if exclude.get(i).getType() == Exclude.ANY:
                if len(entries) == 0:
                    # Add a "beginning ANY".
                    entries.append(
                        Producer.ExcludeEntry(Name.Component(), True))
                else:
                    # Set anyFollowsComponent of the final component.
                    entries[len(entries) - 1]._anyFollowsComponent = True
            else:
                entries.append(
                    Producer.ExcludeEntry(
                        exclude.get(i).getComponent(), False))

        return entries

    @staticmethod
    def setExcludeEntries(exclude, entries):
        """
        Set the Exclude object from the array of ExcludeEntry.

        :param Exclude exclude: The Exclude object to update.
        :param Array<ExcludeEntry> entries: The array of ExcludeEntry.
        """
        exclude.clear()

        for i in range(len(entries)):
            entry = entries[i]

            if (i == 0 and entry._component.getValue().size() == 0
                    and entry._anyFollowsComponent):
                # This is a "beginning ANY".
                exclude.appendAny()
            else:
                exclude.appendComponent(entry._component)
                if entry._anyFollowsComponent:
                    exclude.appendAny()

    @staticmethod
    def findEntryBeforeOrAt(entries, component):
        """
        Get the latest entry in the array whose component is less than or equal
        to component.

        :param Array<ExcludeEntry> entries: The array of ExcludeEntry.
        :param Name.Component component: The component to compare.
        :return: The index of the found entry, or -1 if not found.
        :rtype: int
        """
        i = len(entries) - 1
        while i >= 0:
            if entries[i]._component.compare(component) <= 0:
                break
            i -= 1

        return i

    @staticmethod
    def excludeAfter(exclude, fromComponent):
        """
        Exclude all components in the range beginning at "fromComponent".

        :param Exclude exclude: The Exclude object to update.
        :param Name.Component fromComponent: The first component in the exclude
          range.
        """
        entries = Producer.getExcludeEntries(exclude)

        iFoundFrom = Producer.findEntryBeforeOrAt(entries, fromComponent)
        if iFoundFrom < 0:
            # There is no entry before "fromComponent" so insert at the beginning.
            entries.insert(0, Producer.ExcludeEntry(fromComponent, True))
            iNewFrom = 0
        else:
            foundFrom = entries[iFoundFrom]

            if not foundFrom._anyFollowsComponent:
                if foundFrom._component.equals(fromComponent):
                    # There is already an entry with "fromComponent", so just
                    #   set the "ANY" flag.
                    foundFrom._anyFollowsComponent = True
                    iNewFrom = iFoundFrom
                else:
                    # Insert following the entry before "fromComponent".
                    entries.insert(iFoundFrom + 1,
                                   Producer.ExcludeEntry(fromComponent, True))
                    iNewFrom = iFoundFrom + 1
            else:
                # The entry before "fromComponent" already has an "ANY" flag,
                #   so do nothing.
                iNewFrom = iFoundFrom

        # Remove intermediate entries since they are inside the range.
        iRemoveBegin = iNewFrom + 1
        entries[iRemoveBegin:] = []

        Producer.setExcludeEntries(exclude, entries)

    @staticmethod
    def excludeBefore(exclude, to):
        """
        Exclude all components in the range ending at "to".

        :param Exclude exclude: The Exclude object to update.
        :param Name.Component to: The last component in the exclude range.
        """
        Producer.excludeRange(exclude, Name.Component(), to)

    @staticmethod
    def excludeRange(exclude, fromComponent, to):
        """
        Exclude all components in the range beginning at "fromComponent" and
        ending at "to".

        :param Exclude exclude: The Exclude object to update.
        :param Name.Component fromComponent: The first component in the exclude
          range.
        :param Name.Component to: The last component in the exclude range.
        """
        if fromComponent.compare(to) >= 0:
            if fromComponent.compare(to) == 0:
                raise RuntimeError(
                    "excludeRange: from == to. To exclude a single component, sue excludeOne."
                )
            else:
                raise RuntimeError(
                    "excludeRange: from must be less than to. Invalid range: ["
                    + fromComponent.toEscapedString() + ", " +
                    to.toEscapedString() + "]")

        entries = Producer.getExcludeEntries(exclude)

        iFoundFrom = Producer.findEntryBeforeOrAt(entries, fromComponent)
        if iFoundFrom < 0:
            # There is no entry before "fromComponent" so insert at the beginning.
            entries.insert(0, Producer.ExcludeEntry(fromComponent, True))
            iNewFrom = 0
        else:
            foundFrom = entries[iFoundFrom]

            if not foundFrom._anyFollowsComponent:
                if foundFrom._component.equals(fromComponent):
                    # There is already an entry with "fromComponent", so just
                    #   set the "ANY" flag.
                    foundFrom._anyFollowsComponent = True
                    iNewFrom = iFoundFrom
                else:
                    # Insert following the entry before "fromComponent".
                    entries.insert(iFoundFrom + 1,
                                   Producer.ExcludeEntry(fromComponent, True))
                    iNewFrom = iFoundFrom + 1
            else:
                # The entry before "fromComponent" already has an "ANY" flag,
                #   so do nothing.
                iNewFrom = iFoundFrom

        # We have at least one "fromComponent" before "to", so we know this will
        #   find an entry.
        iFoundTo = Producer.findEntryBeforeOrAt(entries, to)
        foundTo = entries[iFoundTo]
        if iFoundTo == iNewFrom:
            # Insert the "to" immediately after the "fromComponent".
            entries.insert(iNewFrom + 1, Producer.ExcludeEntry(to, False))
        else:
            if not foundTo._anyFollowsComponent:
                if foundTo._component.equals(to):
                    # The "to" entry already exists. Remove up to it.
                    iRemoveEnd = iFoundTo
                else:
                    # Insert following the previous entry, which will be removed.
                    entries.insert(iFoundTo + 1,
                                   Producer.ExcludeEntry(to, False))
                    iRemoveEnd = iFoundTo + 1
            else:
                # "to" follows a component which is already followed by "ANY",
                #   meaning the new range now encompasses it, so remove the component.
                iRemoveEnd = iFoundTo + 1

            # Remove intermediate entries since they are inside the range.
            iRemoveBegin = iNewFrom + 1
            entries[iRemoveBegin:iRemoveEnd] = []

        Producer.setExcludeEntries(exclude, entries)

    START_TIME_STAMP_INDEX = -2
    END_TIME_STAMP_INDEX = -1
    NO_LINK = Link()