예제 #1
0
    def _initialTimeOut(self, interest):
        """
        Initial sync interest timeout, which means there are no other publishers
        yet.
        """
        if not self._enabled:
            # Ignore callbacks after the application calls shutdown().
            return

        logging.getLogger(__name__).info("initial sync timeout")
        logging.getLogger(__name__).info("no other people")
        self._sequenceNo += 1
        if self._sequenceNo != 0:
            # Since there were no other users, we expect sequence no 0.
            raise RuntimeError(
              "ChronoSync: sequenceNo_ is not the expected value of 0 for first use.")

        tempContent = sync_state_pb2.SyncStateMsg()
        content = getattr(tempContent, "ss").add()
        content.name = self._applicationDataPrefixUri
        content.type = SyncState_UPDATE
        content.seqno.seq = self._sequenceNo
        content.seqno.session = self._sessionNo
        self._update(getattr(tempContent, "ss"))

        self._onInitialized()

        name = Name(self._applicationBroadcastPrefix)
        name.append(self._digestTree.getRoot())
        retryInterest = Interest(name)
        retryInterest.setInterestLifetimeMilliseconds(self._syncLifetime)
        self._face.expressInterest(retryInterest, self._onData, self._syncTimeout)
        logging.getLogger(__name__).info("Syncinterest expressed:")
        logging.getLogger(__name__).info("%s", name.toUri())
예제 #2
0
    def _createDKeyData(self, startTimeStamp, endTimeStamp, keyName,
                        privateKeyBlob, certificateKey):
        """
        Create a D-KEY Data packet with an EncryptedContent for the given
        private key, encrypted with the certificate key.

        :param str startTimeStamp: The start time stamp string to put in the name.
        :param str endTimeStamp: The end time stamp string to put in the name.
        :param Name keyName The key name to put in the data packet name and the
          EncryptedContent key locator.
        :param Blob privateKeyBlob: A Blob of the encoded private key.
        :param Blob certificateKey: The certificate key encoding, used to
          encrypt the private key.
        :return: The Data packet.
        :rtype: Data
        """
        name = Name(self._namespace)
        name.append(Encryptor.NAME_COMPONENT_D_KEY)
        name.append(startTimeStamp).append(endTimeStamp)
        data = Data(name)
        data.getMetaInfo().setFreshnessPeriod(
          self._freshnessHours * GroupManager.MILLISECONDS_IN_HOUR)
        encryptParams = EncryptParams(EncryptAlgorithmType.RsaOaep)
        Encryptor.encryptData(
          data, privateKeyBlob, keyName, certificateKey, encryptParams)
        self._keyChain.sign(data)
        return data
예제 #3
0
    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)
예제 #4
0
    def _createDKeyData(self, startTimeStamp, endTimeStamp, keyName,
                        privateKeyBlob, certificateKey):
        """
        Create a D-KEY Data packet with an EncryptedContent for the given
        private key, encrypted with the certificate key.

        :param str startTimeStamp: The start time stamp string to put in the name.
        :param str endTimeStamp: The end time stamp string to put in the name.
        :param Name keyName The key name to put in the data packet name and the
          EncryptedContent key locator.
        :param Blob privateKeyBlob: A Blob of the encoded private key.
        :param Blob certificateKey: The certificate key encoding, used to
          encrypt the private key.
        :return: The Data packet.
        :rtype: Data
        """
        name = Name(self._namespace)
        name.append(Encryptor.NAME_COMPONENT_D_KEY)
        name.append(startTimeStamp).append(endTimeStamp)
        data = Data(name)
        data.getMetaInfo().setFreshnessPeriod(
            self._freshnessHours * GroupManager.MILLISECONDS_IN_HOUR)
        encryptParams = EncryptParams(EncryptAlgorithmType.RsaOaep)
        Encryptor.encryptData(data, privateKeyBlob, keyName, certificateKey,
                              encryptParams)
        self._keyChain.sign(data)
        return data
예제 #5
0
파일: producer.py 프로젝트: tuple71/PyNDN2
    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)
예제 #6
0
    def _onData(self, interest, data):
        """
        Process Sync Data.
        """
        if not self._enabled:
            # Ignore callbacks after the application calls shutdown().
            return

        logging.getLogger(__name__).info(
            "Sync ContentObject received in callback")
        logging.getLogger(__name__).info("name: %s", data.getName().toUri())
        # TODO: Check if this works in Python 3.
        tempContent = SyncStateMsg()
        #pylint: disable=E1103
        tempContent.ParseFromString(data.getContent().toBytes())
        #pylint: enable=E1103
        content = getattr(tempContent, "ss")
        if self._digestTree.getRoot() == "00":
            isRecovery = True
            #processing initial sync data
            self._initialOndata(content)
        else:
            self._update(content)
            if (interest.getName().size() ==
                    self._applicationBroadcastPrefix.size() + 2):
                # Assume this is a recovery interest.
                isRecovery = True
            else:
                isRecovery = False

        # Send the interests to fetch the application data.
        syncStates = []
        for i in range(len(content)):
            syncState = content[i]

            # Only report UPDATE sync states.
            if syncState.type == SyncState_UPDATE:
                if len(syncState.application_info) > 0:
                    applicationInfo = Blob(syncState.application_info, True)
                else:
                    applicationInfo = Blob()

                syncStates.append(
                    self.SyncState(syncState.name, syncState.seqno.session,
                                   syncState.seqno.seq, applicationInfo))

        try:
            self._onReceivedSyncState(syncStates, isRecovery)
        except:
            logging.exception("Error in onReceivedSyncState")

        name = Name(self._applicationBroadcastPrefix)
        name.append(self._digestTree.getRoot())
        syncInterest = Interest(name)
        syncInterest.setInterestLifetimeMilliseconds(self._syncLifetime)
        self._face.expressInterest(syncInterest, self._onData,
                                   self._syncTimeout)
        logging.getLogger(__name__).info("Syncinterest expressed:")
        logging.getLogger(__name__).info("%s", name.toUri())
예제 #7
0
    def _onData(self, interest, data):
        """
        Process Sync Data.
        """
        if not self._enabled:
            # Ignore callbacks after the application calls shutdown().
            return

        logging.getLogger(__name__).info(
          "Sync ContentObject received in callback")
        logging.getLogger(__name__).info(
            "name: %s", data.getName().toUri())
        # TODO: Check if this works in Python 3.
        tempContent = SyncStateMsg()
#pylint: disable=E1103
        tempContent.ParseFromString(data.getContent().toBytes())
#pylint: enable=E1103
        content = getattr(tempContent, "ss")
        if self._digestTree.getRoot() == "00":
            isRecovery = True
            #processing initial sync data
            self._initialOndata(content)
        else:
            self._update(content)
            if (interest.getName().size() ==
                self._applicationBroadcastPrefix.size() + 2):
                # Assume this is a recovery interest.
                isRecovery = True
            else:
                isRecovery = False

        # Send the interests to fetch the application data.
        syncStates = []
        for i in range(len(content)):
            syncState = content[i]

            # Only report UPDATE sync states.
            if syncState.type == SyncState_UPDATE:
                if len(syncState.application_info) > 0:
                    applicationInfo = Blob(syncState.application_info, True)
                else:
                    applicationInfo = Blob()

                syncStates.append(self.SyncState(
                  syncState.name, syncState.seqno.session,
                  syncState.seqno.seq, applicationInfo))

        try:
            self._onReceivedSyncState(syncStates, isRecovery)
        except:
            logging.exception("Error in onReceivedSyncState")

        name = Name(self._applicationBroadcastPrefix)
        name.append(self._digestTree.getRoot())
        syncInterest = Interest(name)
        syncInterest.setInterestLifetimeMilliseconds(self._syncLifetime)
        self._face.expressInterest(syncInterest, self._onData, self._syncTimeout)
        logging.getLogger(__name__).info("Syncinterest expressed:")
        logging.getLogger(__name__).info("%s", name.toUri())
예제 #8
0
 def _sendRecovery(self, syncDigest):
     """
     Send Recovery Interest.
     """
     logging.getLogger(__name__).info("unknown digest: ")
     name = Name(self._applicationBroadcastPrefix)
     name.append("recovery").append(syncDigest)
     interest = Interest(name)
     interest.setInterestLifetimeMilliseconds(self._syncLifetime)
     self._face.expressInterest(interest, self._onData, self._syncTimeout)
     logging.getLogger(__name__).info("Recovery Syncinterest expressed:")
     logging.getLogger(__name__).info("%s", name.toUri())
예제 #9
0
    def addMember(self, memberCertificate):
        """
        Authorize a member identified by memberCertificate to decrypt data under
        the policy.

        :param CertificateV2 memberCertificate: The certificate that identifies
          the member to authorize.
        :return: The published KDK Data packet.
        :rtype: Data
        """
        kdkName = Name(self._nacKey.getIdentityName())
        kdkName.append(EncryptorV2.NAME_COMPONENT_KDK).append(
            # key-id
            self._nacKey.getName().get(-1)).append(
                EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY).append(
                    memberCertificate.getKeyName())

        secretLength = 32
        secret = bytearray(secretLength)
        for i in range(secretLength):
            secret[i] = _systemRandom.randint(0, 0xff)
        # To be compatible with OpenSSL which uses a null-terminated string,
        # replace each 0 with 1. And to be compatible with the Java security
        # library which interprets the secret as a char array converted to UTF8,
        # limit each byte to the ASCII range 1 to 127.
        for i in range(secretLength):
            if secret[i] == 0:
                secret[i] = 1

            secret[i] &= 0x7f

        kdkSafeBag = self._keyChain.exportSafeBag(
            self._nacKey.getDefaultCertificate(),
            Blob(secret, False).toBytes())

        memberKey = PublicKey(memberCertificate.getPublicKey())

        encryptedContent = EncryptedContent()
        encryptedContent.setPayload(kdkSafeBag.wireEncode())
        encryptedContent.setPayloadKey(
            memberKey.encrypt(
                Blob(secret, False).toBytes(), EncryptAlgorithmType.RsaOaep))

        kdkData = Data(kdkName)
        kdkData.setContent(encryptedContent.wireEncodeV2())
        # FreshnessPeriod can serve as a soft access control for revoking access.
        kdkData.getMetaInfo().setFreshnessPeriod(
            AccessManagerV2.DEFAULT_KDK_FRESHNESS_PERIOD_MS)
        self._keyChain.sign(kdkData, SigningInfo(self._identity))

        self._storage.insert(kdkData)

        return kdkData
예제 #10
0
 def _sendRecovery(self, syncDigest):
     """
     Send Recovery Interest.
     """
     logging.getLogger(__name__).info("unknown digest: ")
     name = Name(self._applicationBroadcastPrefix)
     name.append("recovery").append(syncDigest)
     interest = Interest(name)
     interest.setInterestLifetimeMilliseconds(self._syncLifetime)
     self._face.expressInterest(interest, self._onData, self._syncTimeout)
     logging.getLogger(__name__).info("Recovery Syncinterest expressed:")
     logging.getLogger(__name__).info("%s", name.toUri())
예제 #11
0
    def constructKeyName(identityName, keyId):
        """
        Construct a key name based on the appropriate naming conventions.

        :param Name identityName: The name of the identity.
        :param Name.Component keyId: The key ID name component.
        :return: The constructed name as a new Name.
        :rtype: Name
        """
        keyName = Name(identityName)
        keyName.append(CertificateV2.KEY_COMPONENT).append(keyId)

        return keyName
예제 #12
0
    def constructKeyName(identityName, keyId):
        """
        Construct a key name based on the appropriate naming conventions.

        :param Name identityName: The name of the identity.
        :param Name.Component keyId: The key ID name component.
        :return: The constructed name as a new Name.
        :rtype: Name
        """
        keyName = Name(identityName)
        keyName.append(CertificateV2.KEY_COMPONENT).append(keyId)

        return keyName
예제 #13
0
    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)
예제 #14
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)
예제 #15
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)
예제 #16
0
    def _makeSelfSignedCertificate(keyName, privateKeyBag, publicKeyEncoding,
                                   password, digestAlgorithm, wireFormat):
        certificate = CertificateV2()

        # Set the name.
        now = Common.getNowMilliseconds()
        certificateName = Name(keyName)
        certificateName.append("self").appendVersion(int(now))
        certificate.setName(certificateName)

        # Set the MetaInfo.
        certificate.getMetaInfo().setType(ContentType.KEY)
        # Set a one-hour freshness period.
        certificate.getMetaInfo().setFreshnessPeriod(3600 * 1000.0)

        # Set the content.
        publicKey = PublicKey(publicKeyEncoding)
        certificate.setContent(publicKey.getKeyDer())

        # Create a temporary in-memory Tpm and import the private key.
        tpm = Tpm("", "", TpmBackEndMemory())
        tpm._importPrivateKey(keyName, privateKeyBag.toBytes(), password)

        # Set the signature info.
        if publicKey.getKeyType() == KeyType.RSA:
            certificate.setSignature(Sha256WithRsaSignature())
        elif publicKey.getKeyType() == KeyType.EC:
            certificate.setSignature(Sha256WithEcdsaSignature())
        else:
            raise ValueError("Unsupported key type")
        signatureInfo = certificate.getSignature()
        KeyLocator.getFromSignature(signatureInfo).setType(
            KeyLocatorType.KEYNAME)
        KeyLocator.getFromSignature(signatureInfo).setKeyName(keyName)

        # Set a 20-year validity period.
        ValidityPeriod.getFromSignature(signatureInfo).setPeriod(
            now, now + 20 * 365 * 24 * 3600 * 1000.0)

        # Encode once to get the signed portion.
        encoding = certificate.wireEncode(wireFormat)
        signatureBytes = tpm.sign(encoding.toSignedBytes(), keyName,
                                  digestAlgorithm)
        signatureInfo.setSignature(signatureBytes)

        # Encode again to include the signature.
        certificate.wireEncode(wireFormat)

        return certificate
예제 #17
0
    def _makeSelfSignedCertificate(
      keyName, privateKeyBag, publicKeyEncoding, password, digestAlgorithm,
      wireFormat):
        certificate = CertificateV2()

        # Set the name.
        now = Common.getNowMilliseconds()
        certificateName = Name(keyName)
        certificateName.append("self").appendVersion(int(now))
        certificate.setName(certificateName)

        # Set the MetaInfo.
        certificate.getMetaInfo().setType(ContentType.KEY)
        # Set a one-hour freshness period.
        certificate.getMetaInfo().setFreshnessPeriod(3600 * 1000.0)

        # Set the content.
        publicKey = PublicKey(publicKeyEncoding)
        certificate.setContent(publicKey.getKeyDer())

        # Create a temporary in-memory Tpm and import the private key.
        tpm = Tpm("", "", TpmBackEndMemory())
        tpm._importPrivateKey(keyName, privateKeyBag.toBytes(), password)

        # Set the signature info.
        if publicKey.getKeyType() == KeyType.RSA:
            certificate.setSignature(Sha256WithRsaSignature())
        elif publicKey.getKeyType() == KeyType.EC:
            certificate.setSignature(Sha256WithEcdsaSignature())
        else:
            raise ValueError("Unsupported key type")
        signatureInfo = certificate.getSignature()
        KeyLocator.getFromSignature(signatureInfo).setType(KeyLocatorType.KEYNAME)
        KeyLocator.getFromSignature(signatureInfo).setKeyName(keyName)

        # Set a 20-year validity period.
        ValidityPeriod.getFromSignature(signatureInfo).setPeriod(
          now, now + 20 * 365 * 24 * 3600 * 1000.0)

        # Encode once to get the signed portion.
        encoding = certificate.wireEncode(wireFormat)
        signatureBytes = tpm.sign(encoding.toSignedBytes(), keyName,
          digestAlgorithm)
        signatureInfo.setSignature(signatureBytes)

        # Encode again to include the signature.
        certificate.wireEncode(wireFormat)

        return certificate
예제 #18
0
    def _sendSyncData(self, name, content):
        """
        Send the sync Data. Check if the data will satisfy our own pending
        Interest. If it does, then remove it and then renew the sync interest.
        Otherwise, just send the Data.

        :param Name name: The basis to use for the Data name.
        :param Blob content: The content of the Data.
        """
        logging.getLogger(__name__).debug(
            "Checking if the Data will satisfy our own pending interest")

        nameWithIblt = Name()
        nameWithIblt.append(self._iblt.encode())

        # Append the hash of our IBLT so that the Data name should be different
        # for each node. Use abs() since hash() can return negative.
        dataName = Name(name).appendNumber(abs(hash(nameWithIblt)))

        # Check if our own Interest got satisfied.
        if self._outstandingInterestName.equals(name):
            logging.getLogger(__name__).debug(
                "Satisfies our own pending Interest")
            # Remove the outstanding interest.
            # Debug: Implement stopping an ongoing fetch.
            #if self._fetcher != None:
            #    logging.getLogger(__name__).debug(
            #      "Removing our pending interest from the Face (stopping fetcher)")
            #    self._fetcher.stop()
            #    self._outstandingInterestName = Name()
            self._outstandingInterestName = Name()

            logging.getLogger(__name__).debug("Sending sync Data")

            # Send Data after removing the pending sync interest on the Face.
            self._segmentPublisher.publish(name, dataName, content,
                                           self._syncReplyFreshnessPeriod,
                                           self._signingInfo)

            logging.getLogger(__name__).info(
                "sendSyncData: Renewing sync interest")
            self._sendSyncInterest()
        else:
            logging.getLogger(__name__).debug(
                "Sending Sync Data for not our own Interest")
            self._segmentPublisher.publish(name, dataName, content,
                                           self._syncReplyFreshnessPeriod,
                                           self._signingInfo)
예제 #19
0
파일: producer.py 프로젝트: tuple71/PyNDN2
    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
예제 #20
0
파일: producer.py 프로젝트: MAHIS/PyNDN2
    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
예제 #21
0
    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)
예제 #22
0
    def _sendSyncData(self, name, content):
        """
        Send the sync Data. Check if the data will satisfy our own pending
        Interest. If it does, then remove it and then renew the sync interest.
        Otherwise, just send the Data.

        :param Name name: The basis to use for the Data name.
        :param Blob content: The content of the Data.
        """
        logging.getLogger(__name__).debug(
          "Checking if the Data will satisfy our own pending interest")

        nameWithIblt = Name()
        nameWithIblt.append(self._iblt.encode())

        # Append the hash of our IBLT so that the Data name should be different
        # for each node. Use abs() since hash() can return negative.
        dataName = Name(name).appendNumber(abs(hash(nameWithIblt)))

        # Check if our own Interest got satisfied.
        if self._outstandingInterestName.equals(name):
            logging.getLogger(__name__).debug("Satisfies our own pending Interest")
            # Remove the outstanding interest.
            # Debug: Implement stopping an ongoing fetch.
            #if self._fetcher != None:
            #    logging.getLogger(__name__).debug(
            #      "Removing our pending interest from the Face (stopping fetcher)")
            #    self._fetcher.stop()
            #    self._outstandingInterestName = Name()
            self._outstandingInterestName = Name()

            logging.getLogger(__name__).debug("Sending sync Data")

            # Send Data after removing the pending sync interest on the Face.
            self._segmentPublisher.publish(
              name, dataName, content, self._syncReplyFreshnessPeriod,
              self._signingInfo)

            logging.getLogger(__name__).info("sendSyncData: Renewing sync interest")
            self._sendSyncInterest()
        else:
            logging.getLogger(__name__).debug(
              "Sending Sync Data for not our own Interest")
            self._segmentPublisher.publish(
              name, dataName, content, self._syncReplyFreshnessPeriod,
              self._signingInfo)
예제 #23
0
    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)
예제 #24
0
    def _createEKeyData(self, startTimeStamp, endTimeStamp, publicKeyBlob):
        """
        Create an E-KEY Data packet for the given public key.

        :param str startTimeStamp: The start time stamp string to put in the name.
        :param str endTimeStamp: The end time stamp string to put in the name.
        :param Blob publicKeyBlob: A Blob of the public key DER.
        :return: The Data packet.
        :rtype: Data
        """
        name = Name(self._namespace)
        name.append(Encryptor.NAME_COMPONENT_E_KEY).append(startTimeStamp).append(endTimeStamp)

        data = Data(name)
        data.getMetaInfo().setFreshnessPeriod(self._freshnessHours * GroupManager.MILLISECONDS_IN_HOUR)
        data.setContent(publicKeyBlob)
        self._keyChain.sign(data)
        return data
예제 #25
0
    def toName(componentArray):
        """
        Return a Name made from the component array in a Protobuf message object,
        assuming that it was defined with "repeated bytes". For example:
        message Name {
        repeated bytes component = 8;
        }

        :param Array componentArray: The array from the Protobuf message object
          representing the "repeated bytes" component array.
        :return: A new Name.
        :rtype: Name
        """
        name = Name()
        for component in componentArray:
            name.append(Blob(component, True))

        return name
예제 #26
0
    def toName(componentArray):
        """
        Return a Name made from the component array in a Protobuf message object,
        assuming that it was defined with "repeated bytes". For example:
        message Name {
        repeated bytes component = 8;
        }

        :param Array componentArray: The array from the Protobuf message object
          representing the "repeated bytes" component array.
        :return: A new Name.
        :rtype: Name
        """
        name = Name()
        for component in componentArray:
            name.append(Blob(component, True))

        return name
예제 #27
0
    def _sendSyncInterest(self):
        """
        Send the sync interest for full synchronization. This forms the interest
        name: /<sync-prefix>/<own-IBLT>. This cancels any pending sync interest
        we sent earlier on the face.
        """
        # Debug: Implement stopping an ongoing fetch.
        ## If we send two sync interest one after the other
        ## since there is no new data in the network yet,
        ## when data is available it may satisfy both of them
        #if self._fetcher != None:
        #    self._fetcher.stop()

        # Sync Interest format for full sync: /<sync-prefix>/<ourLatestIBF>
        syncInterestName = Name(self._syncPrefix)

        # Append our latest IBLT.
        syncInterestName.append(self._iblt.encode())

        self._outstandingInterestName = syncInterestName

        # random1 is from 0.0 to 1.0.
        random1 = self._systemRandom.random()
        # Get a jitter of +/- syncInterestLifetime_ * 0.2 .
        jitter = (random1 - 0.5) * (self._syncInterestLifetime * 0.2)

        self._face.callLater(self._syncInterestLifetime / 2 + jitter,
                             self._sendSyncInterest)

        syncInterest = Interest(syncInterestName)
        syncInterest.setInterestLifetimeMilliseconds(
            self._syncInterestLifetime)
        syncInterest.refreshNonce()

        SegmentFetcher.fetch(
            self._face, syncInterest, None,
            lambda content: self._onSyncData(content, syncInterest),
            FullPSync2017._onError)

        logging.getLogger(__name__).debug("sendFullSyncInterest, nonce: " +
                                          syncInterest.getNonce().toHex() +
                                          ", hash: " +
                                          str(abs(hash(syncInterestName))))
예제 #28
0
    def _createEKeyData(self, startTimeStamp, endTimeStamp, publicKeyBlob):
        """
        Create an E-KEY Data packet for the given public key.

        :param str startTimeStamp: The start time stamp string to put in the name.
        :param str endTimeStamp: The end time stamp string to put in the name.
        :param Blob publicKeyBlob: A Blob of the public key DER.
        :return: The Data packet.
        :rtype: Data
        """
        name = Name(self._namespace)
        name.append(Encryptor.NAME_COMPONENT_E_KEY).append(
            startTimeStamp).append(endTimeStamp)

        data = Data(name)
        data.getMetaInfo().setFreshnessPeriod(
            self._freshnessHours * GroupManager.MILLISECONDS_IN_HOUR)
        data.setContent(publicKeyBlob)
        self._keyChain.sign(data)
        return data
예제 #29
0
    def _sendSyncInterest(self):
        """
        Send the sync interest for full synchronization. This forms the interest
        name: /<sync-prefix>/<own-IBLT>. This cancels any pending sync interest
        we sent earlier on the face.
        """
        # Debug: Implement stopping an ongoing fetch.
        ## If we send two sync interest one after the other
        ## since there is no new data in the network yet,
        ## when data is available it may satisfy both of them
        #if self._fetcher != None:
        #    self._fetcher.stop()

        # Sync Interest format for full sync: /<sync-prefix>/<ourLatestIBF>
        syncInterestName = Name(self._syncPrefix)

        # Append our latest IBLT.
        syncInterestName.append(self._iblt.encode())

        self._outstandingInterestName = syncInterestName

        # random1 is from 0.0 to 1.0.
        random1 = self._systemRandom.random()
        # Get a jitter of +/- syncInterestLifetime_ * 0.2 .
        jitter = (random1 - 0.5) * (self._syncInterestLifetime * 0.2)

        self._face.callLater(
          self._syncInterestLifetime / 2 + jitter, self._sendSyncInterest)

        syncInterest = Interest(syncInterestName)
        syncInterest.setInterestLifetimeMilliseconds(self._syncInterestLifetime)
        syncInterest.refreshNonce()

        SegmentFetcher.fetch(
          self._face, syncInterest, None,
          lambda content: self._onSyncData(content, syncInterest),
          FullPSync2017._onError)

        logging.getLogger(__name__).debug("sendFullSyncInterest, nonce: " +
          syncInterest.getNonce().toHex() + ", hash: " +
          str(abs(hash(syncInterestName))))
예제 #30
0
    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)
예제 #31
0
    def _initialTimeOut(self, interest):
        """
        Initial sync interest timeout, which means there are no other publishers
        yet.
        """
        if not self._enabled:
            # Ignore callbacks after the application calls shutdown().
            return

        logging.getLogger(__name__).info("initial sync timeout")
        logging.getLogger(__name__).info("no other people")
        self._sequenceNo += 1
        if self._sequenceNo != 0:
            # Since there were no other users, we expect sequence no 0.
            raise RuntimeError(
                "ChronoSync: sequenceNo_ is not the expected value of 0 for first use."
            )

        tempContent = SyncStateMsg()
        content = getattr(tempContent, "ss").add()
        content.name = self._applicationDataPrefixUri
        content.type = SyncState_UPDATE
        content.seqno.seq = self._sequenceNo
        content.seqno.session = self._sessionNo
        self._update(getattr(tempContent, "ss"))

        try:
            self._onInitialized()
        except:
            logging.exception("Error in onInitialized")

        name = Name(self._applicationBroadcastPrefix)
        name.append(self._digestTree.getRoot())
        retryInterest = Interest(name)
        retryInterest.setInterestLifetimeMilliseconds(self._syncLifetime)
        self._face.expressInterest(retryInterest, self._onData,
                                   self._syncTimeout)
        logging.getLogger(__name__).info("Syncinterest expressed:")
        logging.getLogger(__name__).info("%s", name.toUri())
예제 #32
0
    def expand(self, expandStr=""):
        """
        :param str expandStr:
        :rtype: Name
        """
        result = Name()

        backrefManager = (self._secondaryBackrefManager
                          if self._isSecondaryUsed else
                          self._primaryBackrefManager)

        backrefNo = backrefManager.size()

        if expandStr != "":
            usingExpand = expandStr
        else:
            usingExpand = self._expand

        offset = [0]
        while offset[0] < len(usingExpand):
            item = NdnRegexTopMatcher._getItemFromExpand(usingExpand, offset)
            if item[0] == '<':
                result.append(item[1:len(item) - 1])

            if item[0] == '\\':
                index = int(item[1:len(item)])

                if 0 == index:
                    for component in self._matchResult:
                        result.append(component)
                elif index <= backrefNo:
                    for component in backrefManager.getBackref(
                            index - 1).getMatchResult():
                        result.append(component)
                else:
                    raise NdnRegexMatcherBase.Error(
                        "Exceeded the range of back reference")

        return result
예제 #33
0
    def encryptData(data, payload, keyName, key, params):
        """
        Prepare an encrypted data packet by encrypting the payload using the key
        according to the params. In addition, this prepares the encoded
        EncryptedContent with the encryption result using keyName and params.
        The encoding is set as the content of the data packet. If params defines
        an asymmetric encryption algorithm and the payload is larger than the
        maximum plaintext size, this encrypts the payload with a symmetric key
        that is asymmetrically encrypted and provided as a nonce in the content
        of the data packet. The packet's <dataName>/ is updated to be
        <dataName>/FOR/<keyName>

        :param Data data: The data packet which is updated.
        :param Blob payload: The payload to encrypt.
        :param Name keyName: The key name for the EncryptedContent.
        :param Blob key: The encryption key value.
        :param EncryptParams params: The parameters for encryption.
        """
        data.getName().append(Encryptor.NAME_COMPONENT_FOR).append(keyName)

        algorithmType = params.getAlgorithmType()

        if (algorithmType == EncryptAlgorithmType.AesCbc
                or algorithmType == EncryptAlgorithmType.AesEcb):
            content = Encryptor._encryptSymmetric(payload, key, keyName,
                                                  params)
            data.setContent(content.wireEncode(TlvWireFormat.get()))
        elif (algorithmType == EncryptAlgorithmType.RsaPkcs
              or algorithmType == EncryptAlgorithmType.RsaOaep):
            # Cryptography doesn't have a direct way to get the maximum plain text
            # size, so try to encrypt the payload first and catch the error if
            # it is too big.
            try:
                content = Encryptor._encryptAsymmetric(payload, key, keyName,
                                                       params)
                data.setContent(content.wireEncode(TlvWireFormat.get()))
                return
            except ValueError as ex:
                message = ex.args[0]
                if not ("Data too long for key size" in message):
                    raise ex
                # Else the payload is larger than the maximum plaintext size. Continue.

            # 128-bit nonce.
            nonceKeyBuffer = bytearray(16)
            for i in range(16):
                nonceKeyBuffer[i] = _systemRandom.randint(0, 0xff)
            nonceKey = Blob(nonceKeyBuffer, False)

            nonceKeyName = Name(keyName)
            nonceKeyName.append("nonce")

            symmetricParams = EncryptParams(EncryptAlgorithmType.AesCbc,
                                            AesAlgorithm.BLOCK_SIZE)

            nonceContent = Encryptor._encryptSymmetric(payload, nonceKey,
                                                       nonceKeyName,
                                                       symmetricParams)

            payloadContent = Encryptor._encryptAsymmetric(
                nonceKey, key, keyName, params)

            nonceContentEncoding = nonceContent.wireEncode()
            payloadContentEncoding = payloadContent.wireEncode()
            content = bytearray(nonceContentEncoding.size() +
                                payloadContentEncoding.size())
            content[0:payloadContentEncoding.size(
            )] = payloadContentEncoding.buf()
            content[payloadContentEncoding.size():] = nonceContentEncoding.buf(
            )

            data.setContent(Blob(content, False))
        else:
            raise RuntimeError("Unsupported encryption method")
예제 #34
0
파일: producer.py 프로젝트: tuple71/PyNDN2
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()
예제 #35
0
파일: producer.py 프로젝트: tuple71/PyNDN2
    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
예제 #36
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()
예제 #37
0
    def getGroupKey(self, timeSlot, needRegenerate = True):
        """
        Create a group key for the interval into which timeSlot falls. This
        creates a group key if it doesn't exist, and encrypts the key using the
        public key of each eligible member.

        :param float timeSlot: The time slot to cover as milliseconds since
          Jan 1, 1970 UTC.
        :param bool needRegenerate: (optional) needRegenerate should be True if
          this is the first time this method is called, or a member was removed.
          needRegenerate can be False if this is not the first time this method
          is called, or a member was added. If omitted, use True.
        :return: A List of Data packets where the first is the E-KEY data packet
          with the group's public key and the rest are the D-KEY data packets
          with the group's private key encrypted with the public key of each
          eligible member.
        :raises GroupManagerDb.Error: For a database error.
        :raises SecurityException: For an error using the security KeyChain.
        """
        unsortedMemberKeys = {}
        result = []

        # Get the time interval.
        finalInterval = self._calculateInterval(timeSlot, unsortedMemberKeys)
        if finalInterval.isValid() == False:
          return result

        startTimeStamp = Schedule.toIsoString(finalInterval.getStartTime())
        endTimeStamp = Schedule.toIsoString(finalInterval.getEndTime())

        # Generate the private and public keys.
        eKeyName = Name(self._namespace)
        eKeyName.append(Encryptor.NAME_COMPONENT_E_KEY).append(
          startTimeStamp).append(endTimeStamp)

        if not needRegenerate and self._database.hasEKey(eKeyName):
            (publicKeyBlob, privateKeyBlob) = self._getEKey(eKeyName)
        else:
            (privateKeyBlob, publicKeyBlob) = self._generateKeyPair()
            if self._database.hasEKey(eKeyName):
                self._deleteEKey(eKeyName)
            self._addEKey(eKeyName, publicKeyBlob, privateKeyBlob)

        # Add the first element to the result.
        # The E-KEY (public key) data packet name convention is:
        # /<data_type>/E-KEY/[start-ts]/[end-ts]
        data = self._createEKeyData(startTimeStamp, endTimeStamp, publicKeyBlob)
        result.append(data)

        # Encrypt the private key with the public key from each member's certificate.
        # Sort the key names.
        for keyName in sorted(unsortedMemberKeys.keys()):
          certificateKey = unsortedMemberKeys[keyName]

          # Generate the name of the packet.
          # The D-KEY (private key) data packet name convention is:
          # /<data_type>/D-KEY/[start-ts]/[end-ts]/[member-name]
          data = self._createDKeyData(
            startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey)
          result.append(data)

        return result
예제 #38
0
    def getGroupKey(self, timeSlot, needRegenerate=True):
        """
        Create a group key for the interval into which timeSlot falls. This
        creates a group key if it doesn't exist, and encrypts the key using the
        public key of each eligible member.

        :param float timeSlot: The time slot to cover as milliseconds since
          Jan 1, 1970 UTC.
        :param bool needRegenerate: (optional) needRegenerate should be True if
          this is the first time this method is called, or a member was removed.
          needRegenerate can be False if this is not the first time this method
          is called, or a member was added. If omitted, use True.
        :return: A List of Data packets where the first is the E-KEY data packet
          with the group's public key and the rest are the D-KEY data packets
          with the group's private key encrypted with the public key of each
          eligible member.
        :raises GroupManagerDb.Error: For a database error.
        :raises SecurityException: For an error using the security KeyChain.
        """
        unsortedMemberKeys = {}
        result = []

        # Get the time interval.
        finalInterval = self._calculateInterval(timeSlot, unsortedMemberKeys)
        if finalInterval.isValid() == False:
            return result

        startTimeStamp = Schedule.toIsoString(finalInterval.getStartTime())
        endTimeStamp = Schedule.toIsoString(finalInterval.getEndTime())

        # Generate the private and public keys.
        eKeyName = Name(self._namespace)
        eKeyName.append(Encryptor.NAME_COMPONENT_E_KEY).append(
            startTimeStamp).append(endTimeStamp)

        if not needRegenerate and self._database.hasEKey(eKeyName):
            (publicKeyBlob, privateKeyBlob) = self._getEKey(eKeyName)
        else:
            (privateKeyBlob, publicKeyBlob) = self._generateKeyPair()
            if self._database.hasEKey(eKeyName):
                self._deleteEKey(eKeyName)
            self._addEKey(eKeyName, publicKeyBlob, privateKeyBlob)

        # Add the first element to the result.
        # The E-KEY (public key) data packet name convention is:
        # /<data_type>/E-KEY/[start-ts]/[end-ts]
        data = self._createEKeyData(startTimeStamp, endTimeStamp,
                                    publicKeyBlob)
        result.append(data)

        # Encrypt the private key with the public key from each member's certificate.
        # Sort the key names.
        for keyName in sorted(unsortedMemberKeys.keys()):
            certificateKey = unsortedMemberKeys[keyName]

            # Generate the name of the packet.
            # The D-KEY (private key) data packet name convention is:
            # /<data_type>/D-KEY/[start-ts]/[end-ts]/[member-name]
            data = self._createDKeyData(startTimeStamp, endTimeStamp, keyName,
                                        privateKeyBlob, certificateKey)
            result.append(data)

        return result
예제 #39
0
    def prepareUnsignedIdentityCertificate(self,
                                           keyName,
                                           publicKey,
                                           signingIdentity,
                                           notBefore,
                                           notAfter,
                                           subjectDescription=None,
                                           certPrefix=None):
        """
        Prepare an unsigned identity certificate.

        :param Name keyName: The key name, e.g., `/{identity_name}/ksk-123456`.
        :param PublicKey publicKey: (optional) The public key to sign. If
          ommited, use the keyName to get the public key from the identity
          storage.
        :param Name signingIdentity: The signing identity.
        :param float notBefore: See IdentityCertificate.
        :param float notAfter: See IdentityCertificate.
        :param Array<CertificateSubjectDescription> subjectDescription: A list
          of CertificateSubjectDescription. See IdentityCertificate. If None or
          empty, this adds a an ATTRIBUTE_NAME based on the keyName.
        :param Name certPrefix: (optional) The prefix before the `KEY`
          component. If None, this infers the certificate name according to the
          relation between the signingIdentity and the subject identity. If the
          signingIdentity is a prefix of the subject identity, `KEY` will be
          inserted after the signingIdentity, otherwise `KEY` is inserted after
          subject identity (i.e., before `ksk-...`).
        :return: The unsigned IdentityCertificate, or None if the inputs are
          invalid.
        :rtype: IdentityCertificate
        """
        if not isinstance(publicKey, PublicKey):
            # The publicKey was omitted. Shift arguments.
            certPrefix = subjectDescription
            subjectDescription = notAfter
            notAfter = notBefore
            notBefore = signingIdentity
            signingIdentity = publicKey

            publicKey = PublicKey(self._identityStorage.getKey(keyName))

        if keyName.size() < 1:
            return None

        tempKeyIdPrefix = keyName.get(-1).toEscapedString()
        if len(tempKeyIdPrefix) < 4:
            return None
        keyIdPrefix = tempKeyIdPrefix[0:4]
        if keyIdPrefix != "ksk-" and keyIdPrefix != "dsk-":
            return None

        certificate = IdentityCertificate()
        certName = Name()

        if certPrefix == None:
            # No certificate prefix hint, so infer the prefix.
            if signingIdentity.match(keyName):
                certName.append(signingIdentity) \
                    .append("KEY") \
                    .append(keyName.getSubName(signingIdentity.size())) \
                    .append("ID-CERT") \
                    .appendVersion(int(Common.getNowMilliseconds()))
            else:
                certName.append(keyName.getPrefix(-1)) \
                    .append("KEY") \
                    .append(keyName.get(-1)) \
                    .append("ID-CERT") \
                    .appendVersion(int(Common.getNowMilliseconds()))
        else:
            # A cert prefix hint is supplied, so determine the cert name.
            if certPrefix.match(keyName) and not certPrefix.equals(keyName):
                certName.append(certPrefix) \
                    .append("KEY") \
                    .append(keyName.getSubName(certPrefix.size())) \
                    .append("ID-CERT") \
                    .appendVersion(int(Common.getNowMilliseconds()))
            else:
                return None

        certificate.setName(certName)
        certificate.setNotBefore(notBefore)
        certificate.setNotAfter(notAfter)
        certificate.setPublicKeyInfo(publicKey)

        if subjectDescription == None or len(subjectDescription) == 0:
            certificate.addSubjectDescription(
                CertificateSubjectDescription("2.5.4.41",
                                              keyName.getPrefix(-1).toUri()))
        else:
            for i in range(len(subjectDescription)):
                certificate.addSubjectDescription(subjectDescription[i])

        try:
            certificate.encode()
        except Exception as ex:
            raise SecurityException("DerEncodingException: " + str(ex))

        return certificate
예제 #40
0
class EncryptorV2(object):
    """
    Create an EncryptorV2 with the given parameters. This uses the face to
    register to receive Interests for the prefix {ckPrefix}/CK.

    :param Name accessPrefix: The NAC prefix to fetch the Key Encryption Key
      (KEK) (e.g., /access/prefix/NAC/data/subset). This copies the Name.
    :param Name ckPrefix: The prefix under which Content Keys (CK) will be
      generated. (Each will have a unique version appended.) This copies the Name.
    :param SigningInfo ckDataSigningInfo: The SigningInfo parameters to sign the
      Content Key (CK) Data packet. This copies the SigningInfo.
    :param onError: On failure to create the CK data (failed to fetch the KEK,
      failed to encrypt with the KEK, etc.), this calls
      onError(errorCode, message) where errorCode is from EncryptError.ErrorCode
      and message is a str. The encrypt method will continue trying to retrieve
      the KEK until success (with each attempt separated by
      RETRY_DELAY_KEK_RETRIEVAL_MS) and onError may be called multiple times.
      NOTE: The library will log any exceptions thrown by this callback, but for
      better error handling the callback should catch and properly handle any
      exceptions.
    :type onError: function object
    :param Validator validator: The validation policy to ensure correctness of
      the KEK.
    :param KeyChain keyChain: The KeyChain used to sign Data packets.
    :param Face face: The Face that will be used to fetch the KEK and publish CK
      data.
    """
    def __init__(self, accessPrefix, ckPrefix, ckDataSigningInfo,
      onError, validator, keyChain, face):
        # Copy the Name.
        self._accessPrefix = Name(accessPrefix)
        self._ckPrefix = Name(ckPrefix)
        self._ckBits = bytearray(EncryptorV2.AES_KEY_SIZE)
        self._ckDataSigningInfo = SigningInfo(ckDataSigningInfo)
        self._isKekRetrievalInProgress = False
        self._onError = onError
        self._keyChain = keyChain
        self._face = face

        self._kekData = None
        # Storage for encrypted CKs.
        self._storage = InMemoryStorageRetaining()
        self._kekPendingInterestId = 0

        self.regenerateCk()

        def onInterest(prefix, interest, face, interestFilterId, filter):
            data = self._storage.find(interest)
            if data != None:
                logging.getLogger(__name__).info("Serving " +
                  data.getName().toUri() + " from InMemoryStorage")
                try:
                    face.putData(data)
                except:
                    logging.exception("Error in Face.putData")
            else:
                logging.getLogger(__name__).info(
                  "Didn't find CK data for " + interest.getName().toUri())
                # TODO: Send NACK?

        def onRegisterFailed(prefix):
            logging.getLogger(__name__).error(
              "Failed to register prefix " + prefix.toUri())

        self._ckRegisteredPrefixId = self._face.registerPrefix(
          Name(ckPrefix).append(EncryptorV2.NAME_COMPONENT_CK),
          onInterest, onRegisterFailed)

    def shutdown(self):
        self._face.unsetInterestFilter(self._ckRegisteredPrefixId)
        if self._kekPendingInterestId > 0:
            self._face.removePendingInterest(self._kekPendingInterestId)

    def encrypt(self, plainData):
        """
        Encrypt the plainData using the existing Content Key (CK) and return a
        new EncryptedContent.

        :param plainData: The data to encrypt.
        :type plainData: Blob or an array which implements the buffer protocol
        :return: The new EncryptedContent.
        :rtype: EncryptedContent
        """
        # Generate the initial vector.
        initialVector = bytearray(EncryptorV2.AES_IV_SIZE)
        for i in range(len(initialVector)):
            initialVector[i] = _systemRandom.randint(0, 0xff)

        params = EncryptParams(EncryptAlgorithmType.AesCbc)
        params.setInitialVector(Blob(initialVector, False))
        encryptedData = AesAlgorithm.encrypt(
          Blob(self._ckBits, False), Blob(plainData, False), params)

        content = EncryptedContent()
        content.setInitialVector(params.getInitialVector())
        content.setPayload(encryptedData)
        content.setKeyLocatorName(self._ckName)

        return content

    def regenerateCk(self):
        """
        Create a new Content Key (CK) and publish the corresponding CK Data
        packet. This uses the onError given to the constructor to report errors.
        """
        # TODO: Ensure that the CK Data packet for the old CK is published when
        # the CK is updated before the KEK is fetched.

        self._ckName = Name(self._ckPrefix)
        self._ckName.append(EncryptorV2.NAME_COMPONENT_CK)
        # The version is the ID of the CK.
        self._ckName.appendVersion(int(Common.getNowMilliseconds()))

        logging.getLogger(__name__).info("Generating new CK: " +
          self._ckName.toUri())
        for i in range(len(self._ckBits)):
            self._ckBits[i] = _systemRandom.randint(0, 0xff)

        # One implication: If the CK is updated before the KEK is fetched, then
        # the KDK for the old CK will not be published.
        if self._kekData == None:
            self._retryFetchingKek()
        else:
            self._makeAndPublishCkData(self._onError)

    def size(self):
        """
        Get the number of packets stored in in-memory storage.
        
        :return: The number of packets.
        :rtype: int
        """
        return self._storage.size()

    def _retryFetchingKek(self):
        if self._isKekRetrievalInProgress:
            return

        logging.getLogger(__name__).info("Retrying fetching of the KEK")
        self._isKekRetrievalInProgress = True

        def onReady():
            logging.getLogger(__name__).info("The KEK was retrieved and published")
            self._isKekRetrievalInProgress = False

        def onError(errorCode, message):
            logging.getLogger(__name__).info("Failed to retrieve KEK: " + message)
            self._isKekRetrievalInProgress = False
            self._onError(errorCode, message)

        self._fetchKekAndPublishCkData(onReady, onError, EncryptorV2.N_RETRIES)

    def _fetchKekAndPublishCkData(self, onReady, onError, nTriesLeft):
        """
        Create an Interest for <access-prefix>/KEK to retrieve the
        <access-prefix>/KEK/<key-id> KEK Data packet, and set _kekData.

        :param onReady: When the KEK is retrieved and published, this calls
          onReady().
        :type onError: function object
        :param onError: On failure, this calls onError(errorCode, message)
          where errorCode is from EncryptError.ErrorCode, and message is an
          error string.
        :type onError: function object
        :param int nTriesLeft: The number of retries for expressInterest timeouts.
        """
        logging.getLogger(__name__).info("Fetching KEK: " +
          Name(self._accessPrefix).append(EncryptorV2.NAME_COMPONENT_KEK).toUri())

        if self._kekPendingInterestId > 0:
            onError(EncryptError.ErrorCode.General,
              "fetchKekAndPublishCkData: There is already a _kekPendingInterestId")
            return

        def onData(interest, kekData):
            self._kekPendingInterestId = 0
            # TODO: Verify if the key is legitimate.
            self._kekData = kekData
            if self._makeAndPublishCkData(onError):
                onReady()
            # Otherwise, failure has already been reported.

        def onTimeout(interest):
            self._kekPendingInterestId = 0
            if nTriesLeft > 1:
                self._fetchKekAndPublishCkData(onReady, onError, nTriesLeft - 1)
            else:
                onError(EncryptError.ErrorCode.KekRetrievalTimeout,
                  "Retrieval of KEK [" + interest.getName().toUri() + "] timed out")
                logging.getLogger(__name__).info(
                  "Scheduling retry after all timeouts")
                self._face.callLater(
                  EncryptorV2.RETRY_DELAY_KEK_RETRIEVAL_MS, self._retryFetchingKek)

        def onNetworkNack(interest, networkNack):
            self._kekPendingInterestId = 0
            if nTriesLeft > 1:
                def callback():
                    self._fetchKekAndPublishCkData(onReady, onError, nTriesLeft - 1)
                self._face.callLater(EncryptorV2.RETRY_DELAY_AFTER_NACK_MS, callback)
            else:
                onError(EncryptError.ErrorCode.KekRetrievalFailure,
                  "Retrieval of KEK [" + interest.getName().toUri() +
                  "] failed. Got NACK (" + str(networkNack.getReason()) + ")")
                logging.getLogger(__name__).info("Scheduling retry from NACK")
                self._face.callLater(
                  EncryptorV2.RETRY_DELAY_KEK_RETRIEVAL_MS, self._retryFetchingKek)

        try:
            self._kekPendingInterestId = self._face.expressInterest(
              Interest(Name(self._accessPrefix).append(EncryptorV2.NAME_COMPONENT_KEK))
                .setMustBeFresh(True)
                .setCanBePrefix(True),
              onData, onTimeout, onNetworkNack)
        except Exception as ex:
            onError(EncryptError.ErrorCode.General,
              "expressInterest error: " + repr(ex))

    def _makeAndPublishCkData(self, onError):
        """
        Make a CK Data packet for _ckName encrypted by the KEK in _kekData and
        insert it in the _storage.

        :param onError: On failure, this calls onError(errorCode, message) where
          errorCode is from EncryptError.ErrorCode, and message is an error
          string.
        :type onError: function object
        :return: True on success, else False.
        :rtype: bool
        """
        try:
            kek = PublicKey(self._kekData.getContent())

            content = EncryptedContent()
            content.setPayload(kek.encrypt
              (Blob(self._ckBits, False), EncryptAlgorithmType.RsaOaep))

            ckData = Data(
              Name(self._ckName).append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY)
               .append(self._kekData.getName()))
            ckData.setContent(content.wireEncodeV2())
            # FreshnessPeriod can serve as a soft access control for revoking access.
            ckData.getMetaInfo().setFreshnessPeriod(
              EncryptorV2.DEFAULT_CK_FRESHNESS_PERIOD_MS)
            self._keyChain.sign(ckData, self._ckDataSigningInfo)
            self._storage.insert(ckData)

            logging.getLogger(__name__).info("Publishing CK data: " +
              ckData.getName().toUri())
            return True
        except Exception as ex:
            onError(EncryptError.ErrorCode.EncryptionFailure,
              "Failed to encrypt generated CK with KEK " +
              self._kekData.getName().toUri())
            return False

    NAME_COMPONENT_ENCRYPTED_BY = Name.Component("ENCRYPTED-BY")
    NAME_COMPONENT_NAC = Name.Component("NAC")
    NAME_COMPONENT_KEK = Name.Component("KEK")
    NAME_COMPONENT_KDK = Name.Component("KDK")
    NAME_COMPONENT_CK  = Name.Component("CK")

    RETRY_DELAY_AFTER_NACK_MS    = 1000.0
    RETRY_DELAY_KEK_RETRIEVAL_MS = 60 * 1000.0

    AES_KEY_SIZE = 32
    AES_IV_SIZE  = 16
    N_RETRIES    = 3

    DEFAULT_CK_FRESHNESS_PERIOD_MS = 3600 * 1000.0
예제 #41
0
파일: consumer.py 프로젝트: MAHIS/PyNDN2
    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)

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

                try:
                    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._keyChain.verifyData(
                        dKeyData, onVerified,
                        lambda d: Consumer._callOnError(onError, EncryptError.ErrorCode.Validation,
                                          "verifyData failed"))
                except Exception as ex:
                    try:
                        onError(EncryptError.ErrorCode.General,
                                "verifyData error: " + repr(ex))
                    except:
                        logging.exception("Error in onError")

            def onTimeout(dKeyInterest):
                # We should re-try at least once.
                try:
                    self._face.expressInterest(
                      interest, onData,
                      lambda contentInterest:
                        Consumer._callOnError(onError,
                          EncryptError.ErrorCode.Timeout, interest.getName().toUri()))
                except Exception as ex:
                    try:
                        onError(EncryptError.ErrorCode.General,
                                "expressInterest error: " + repr(ex))
                    except:
                        logging.exception("Error in onError")

            # Express the Interest.
            try:
                self._face.expressInterest(interest, onData, onTimeout)
            except Exception as ex:
                try:
                    onError(EncryptError.ErrorCode.General,
                            "expressInterest error: " + repr(ex))
                except:
                    logging.exception("Error in onError")
예제 #42
0
    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
예제 #43
0
    def _fetchKdk(self, contentKey, kdkPrefix, ckData, onError, nTriesLeft):
        """
        :param DecryptorV2.ContentKey contentKey:
        :param Name kdkPrefix:
        :param Data ckData:
        :param onError: On error, this calls onError(errorCode, message)
        :type onError: function object
        :param int nTriesLeft:
        """
        # <kdk-prefix>/KDK/<kdk-id>    /ENCRYPTED-BY  /<credential-identity>/KEY/<key-id>
        # \                          /                \                                /
        #  -----------  -------------                  ---------------  ---------------
        #             \/                                              \/
        #     from the CK data                                from configuration

        kdkName = Name(kdkPrefix)
        kdkName.append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY).append(
            self._credentialsKey.getName())

        logging.getLogger(__name__).info("Fetching KDK " + kdkName.toUri())

        def onData(kdkInterest, kdkData):
            try:
                contentKey.pendingInterest = 0
                # TODO: Verify that the key is legitimate.

                isOk = self._decryptAndImportKdk(kdkData, onError)
                if not isOk:
                    return
                # This way of getting the kdkKeyName is a bit hacky.
                kdkKeyName = kdkPrefix.getPrefix(-2).append("KEY").append(
                    kdkPrefix.get(-1))
                self._decryptCkAndProcessPendingDecrypts(
                    contentKey, ckData, kdkKeyName, onError)
            except Exception as ex:
                onError(EncryptError.ErrorCode.General,
                        "Error in fetchCk onData: " + repr(ex))

        def onTimeout(interest):
            contentKey.pendingInterest = 0
            if nTriesLeft > 1:
                self._fetchKdk(contentKey, kdkPrefix, ckData, onError,
                               nTriesLeft - 1)
            else:
                onError(
                    EncryptError.ErrorCode.KdkRetrievalTimeout,
                    "Retrieval of KDK [" + interest.getName().toUri() +
                    "] timed out")

        def onNetworkNack(interest, networkNack):
            contentKey.pendingInterest = 0
            onError(
                EncryptError.ErrorCode.KdkRetrievalFailure,
                "Retrieval of KDK [" + interest.getName().toUri() +
                "] failed. Got NACK (" + str(networkNack.getReason()) + ")")

        try:
            contentKey.pendingInterest = self._face.expressInterest(
                Interest(kdkName).setMustBeFresh(True).setCanBePrefix(False),
                onData, onTimeout, onNetworkNack)
        except Exception as ex:
            onError(EncryptError.ErrorCode.General,
                    "expressInterest error: " + repr(ex))
예제 #44
0
파일: consumer.py 프로젝트: pedosb/PyNDN2
    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)

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

                try:

                    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._keyChain.verifyData(
                        dKeyData, onVerified, lambda d: Consumer._callOnError(
                            onError, EncryptError.ErrorCode.Validation,
                            "verifyData failed"))
                except Exception as ex:
                    try:
                        onError(EncryptError.ErrorCode.General,
                                "verifyData error: " + repr(ex))
                    except:
                        logging.exception("Error in onError")

            def onTimeout(dKeyInterest):
                # We should re-try at least once.
                try:
                    self._face.expressInterest(
                        interest, onData,
                        lambda contentInterest: Consumer._callOnError(
                            onError, EncryptError.ErrorCode.Timeout,
                            interest.getName().toUri()))
                except Exception as ex:
                    try:
                        onError(EncryptError.ErrorCode.General,
                                "expressInterest error: " + repr(ex))
                    except:
                        logging.exception("Error in onError")

            # Express the Interest.
            try:
                self._face.expressInterest(interest, onData, onTimeout)
            except Exception as ex:
                try:
                    onError(EncryptError.ErrorCode.General,
                            "expressInterest error: " + repr(ex))
                except:
                    logging.exception("Error in onError")
예제 #45
0
    def _processSyncInterest(self, index, syncDigest, face):
        """
        Common interest processing, using digest log to find the difference
        after syncDigest.

        :return: True if sent a data packet to satisfy the interest, otherwise
          False.
        :rtype: bool
        """
        nameList = []  # of str
        sequenceNoList = []  # of int
        sessionNoList = []  # of int
        for j in range(index + 1, len(self._digestLog)):
            temp = self._digestLog[j].getData(
            )  # array of sync_state_pb2.SyncState.
            for i in range(len(temp)):
                syncState = temp[i]
                if syncState.type != SyncState_UPDATE:
                    continue

                if self._digestTree.find(syncState.name,
                                         syncState.seqno.session) != -1:
                    n = -1
                    for k in range(len(nameList)):
                        if nameList[k] == syncState.name:
                            n = k
                            break

                    if n == -1:
                        nameList.append(syncState.name)
                        sequenceNoList.append(syncState.seqno.seq)
                        sessionNoList.append(syncState.seqno.session)
                    else:
                        sequenceNoList[n] = syncState.seqno.seq
                        sessionNoList[n] = syncState.seqno.session

        tempContent = SyncStateMsg()
        for i in range(len(nameList)):
            content = getattr(tempContent, "ss").add()
            content.name = nameList[i]
            content.type = SyncState_UPDATE
            content.seqno.seq = sequenceNoList[i]
            content.seqno.session = sessionNoList[i]

        sent = False
        if len(getattr(tempContent, "ss")) != 0:
            name = Name(self._applicationBroadcastPrefix)
            name.append(syncDigest)
            # TODO: Check if this works in Python 3.
            #pylint: disable=E1103
            array = tempContent.SerializeToString()
            #pylint: enable=E1103
            data = Data(name)
            data.setContent(Blob(array))
            self._keyChain.sign(data, self._certificateName)

            try:
                face.putData(data)
            except Exception as ex:
                logging.getLogger(__name__).error("Error in face.putData: %s",
                                                  str(ex))
                return

            sent = True
            logging.getLogger(__name__).info("Sync Data send")
            logging.getLogger(__name__).info("%s", name.toUri())

        return sent
예제 #46
0
    def _processSyncInterest(self, index, syncDigest, face):
        """
        Common interest processing, using digest log to find the difference
        after syncDigest.

        :return: True if sent a data packet to satisfy the interest, otherwise
          False.
        :rtype: bool
        """
        nameList = []       # of str
        sequenceNoList = [] # of int
        sessionNoList = []  # of int
        for j in range(index + 1, len(self._digestLog)):
            temp = self._digestLog[j].getData() # array of sync_state_pb2.SyncState.
            for i in range(len(temp)):
                syncState = temp[i]
                if syncState.type != SyncState_UPDATE:
                    continue

                if self._digestTree.find(
                      syncState.name, syncState.seqno.session) != -1:
                    n = -1
                    for k in range(len(nameList)):
                        if nameList[k] == syncState.name:
                            n = k
                            break

                    if n == -1:
                        nameList.append(syncState.name)
                        sequenceNoList.append(syncState.seqno.seq)
                        sessionNoList.append(syncState.seqno.session)
                    else:
                        sequenceNoList[n] = syncState.seqno.seq
                        sessionNoList[n] = syncState.seqno.session

        tempContent = SyncStateMsg()
        for i in range(len(nameList)):
            content = getattr(tempContent, "ss").add()
            content.name = nameList[i]
            content.type = SyncState_UPDATE
            content.seqno.seq = sequenceNoList[i]
            content.seqno.session = sessionNoList[i]

        sent = False
        if len(getattr(tempContent, "ss")) != 0:
            name = Name(self._applicationBroadcastPrefix)
            name.append(syncDigest)
            # TODO: Check if this works in Python 3.
#pylint: disable=E1103
            array = tempContent.SerializeToString()
#pylint: enable=E1103
            data = Data(name)
            data.setContent(Blob(array))
            self._keyChain.sign(data, self._certificateName)

            try:
                face.putData(data)
            except Exception as ex:
                logging.getLogger(__name__).error(
                  "Error in face.putData: %s", str(ex))
                return

            sent = True
            logging.getLogger(__name__).info("Sync Data send")
            logging.getLogger(__name__).info("%s", name.toUri())

        return sent
예제 #47
0
    def prepareUnsignedIdentityCertificate(self, keyName, publicKey,
          signingIdentity, notBefore, notAfter, subjectDescription = None,
          certPrefix = None):
        """
        Prepare an unsigned identity certificate.

        :param Name keyName: The key name, e.g., `/{identity_name}/ksk-123456`.
        :param PublicKey publicKey: (optional) The public key to sign. If
          ommited, use the keyName to get the public key from the identity
          storage.
        :param Name signingIdentity: The signing identity.
        :param float notBefore: See IdentityCertificate.
        :param float notAfter: See IdentityCertificate.
        :param Array<CertificateSubjectDescription> subjectDescription: A list
          of CertificateSubjectDescription. See IdentityCertificate. If None or
          empty, this adds a an ATTRIBUTE_NAME based on the keyName.
        :param Name certPrefix: (optional) The prefix before the `KEY`
          component. If None, this infers the certificate name according to the
          relation between the signingIdentity and the subject identity. If the
          signingIdentity is a prefix of the subject identity, `KEY` will be
          inserted after the signingIdentity, otherwise `KEY` is inserted after
          subject identity (i.e., before `ksk-...`).
        :return: The unsigned IdentityCertificate, or None if the inputs are
          invalid.
        :rtype: IdentityCertificate
        """
        if not isinstance(publicKey, PublicKey):
            # The publicKey was omitted. Shift arguments.
            certPrefix = subjectDescription
            subjectDescription = notAfter
            notAfter = notBefore
            notBefore = signingIdentity
            signingIdentity = publicKey

            publicKey = PublicKey(self._identityStorage.getKey(keyName))

        if keyName.size() < 1:
            return None

        tempKeyIdPrefix = keyName.get(-1).toEscapedString()
        if len(tempKeyIdPrefix) < 4:
            return None
        keyIdPrefix = tempKeyIdPrefix[0:4]
        if keyIdPrefix != "ksk-" and keyIdPrefix != "dsk-":
            return None

        certificate = IdentityCertificate()
        certName = Name()

        if certPrefix == None:
            # No certificate prefix hint, so infer the prefix.
            if signingIdentity.match(keyName):
                certName.append(signingIdentity) \
                    .append("KEY") \
                    .append(keyName.getSubName(signingIdentity.size())) \
                    .append("ID-CERT") \
                    .appendVersion(int(Common.getNowMilliseconds()))
            else:
                certName.append(keyName.getPrefix(-1)) \
                    .append("KEY") \
                    .append(keyName.get(-1)) \
                    .append("ID-CERT") \
                    .appendVersion(int(Common.getNowMilliseconds()))
        else:
            # A cert prefix hint is supplied, so determine the cert name.
            if certPrefix.match(keyName) and not certPrefix.equals(keyName):
                certName.append(certPrefix) \
                    .append("KEY") \
                    .append(keyName.getSubName(certPrefix.size())) \
                    .append("ID-CERT") \
                    .appendVersion(int(Common.getNowMilliseconds()))
            else:
                return None

        certificate.setName(certName)
        certificate.setNotBefore(notBefore)
        certificate.setNotAfter(notAfter)
        certificate.setPublicKeyInfo(publicKey)

        if subjectDescription == None or len(subjectDescription) == 0:
            certificate.addSubjectDescription(CertificateSubjectDescription(
              "2.5.4.41", keyName.getPrefix(-1).toUri()))
        else:
            for i in range(len(subjectDescription)):
                certificate.addSubjectDescription(subjectDescription[i])

        try:
            certificate.encode()
        except Exception as ex:
            raise SecurityException("DerEncodingException: " + str(ex))

        return certificate
예제 #48
0
    def encryptData(data, payload, keyName, key, params):
        """
        Prepare an encrypted data packet by encrypting the payload using the key
        according to the params. In addition, this prepares the encoded
        EncryptedContent with the encryption result using keyName and params.
        The encoding is set as the content of the data packet. If params defines
        an asymmetric encryption algorithm and the payload is larger than the
        maximum plaintext size, this encrypts the payload with a symmetric key
        that is asymmetrically encrypted and provided as a nonce in the content
        of the data packet. The packet's <dataName>/ is updated to be
        <dataName>/FOR/<keyName>

        :param Data data: The data packet which is updated.
        :param Blob payload: The payload to encrypt.
        :param Name keyName: The key name for the EncryptedContent.
        :param Blob key: The encryption key value.
        :param EncryptParams params: The parameters for encryption.
        """
        data.getName().append(Encryptor.NAME_COMPONENT_FOR).append(keyName)

        algorithmType = params.getAlgorithmType()

        if (algorithmType == EncryptAlgorithmType.AesCbc or
            algorithmType == EncryptAlgorithmType.AesEcb):
            content = Encryptor._encryptSymmetric(payload, key, keyName, params)
            data.setContent(content.wireEncode(TlvWireFormat.get()))
        elif (algorithmType == EncryptAlgorithmType.RsaPkcs or
              algorithmType == EncryptAlgorithmType.RsaOaep):
            # Cryptography doesn't have a direct way to get the maximum plain text
            # size, so try to encrypt the payload first and catch the error if
            # it is too big.
            try:
                content = Encryptor._encryptAsymmetric(payload, key, keyName, params)
                data.setContent(content.wireEncode(TlvWireFormat.get()))
                return
            except ValueError as ex:
                message = ex.args[0]
                if not ("Data too long for key size" in message):
                    raise ex
                # Else the payload is larger than the maximum plaintext size. Continue.

            # 128-bit nonce.
            nonceKeyBuffer = bytearray(16)
            for i in range(16):
                nonceKeyBuffer[i] = _systemRandom.randint(0, 0xff)
            nonceKey = Blob(nonceKeyBuffer, False)

            nonceKeyName = Name(keyName)
            nonceKeyName.append("nonce")

            symmetricParams =  EncryptParams(
              EncryptAlgorithmType.AesCbc, AesAlgorithm.BLOCK_SIZE)

            nonceContent = Encryptor._encryptSymmetric(
              payload, nonceKey, nonceKeyName, symmetricParams)

            payloadContent = Encryptor._encryptAsymmetric(
              nonceKey, key, keyName, params)

            nonceContentEncoding = nonceContent.wireEncode()
            payloadContentEncoding = payloadContent.wireEncode()
            content = bytearray(
              nonceContentEncoding.size() + payloadContentEncoding.size())
            content[0:payloadContentEncoding.size()] = payloadContentEncoding.buf()
            content[payloadContentEncoding.size():] = nonceContentEncoding.buf()

            data.setContent(Blob(content, False))
        else:
            raise RuntimeError("Unsupported encryption method")
예제 #49
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()
예제 #50
0
class EncryptorV2(object):
    """
    Create an EncryptorV2 with the given parameters. This uses the face to
    register to receive Interests for the prefix {ckPrefix}/CK.

    :param Name accessPrefix: The NAC prefix to fetch the Key Encryption Key
      (KEK) (e.g., /access/prefix/NAC/data/subset). This copies the Name.
    :param Name ckPrefix: The prefix under which Content Keys (CK) will be
      generated. (Each will have a unique version appended.) This copies the Name.
    :param SigningInfo ckDataSigningInfo: The SigningInfo parameters to sign the
      Content Key (CK) Data packet. This copies the SigningInfo.
    :param onError: On failure to create the CK data (failed to fetch the KEK,
      failed to encrypt with the KEK, etc.), this calls
      onError(errorCode, message) where errorCode is from EncryptError.ErrorCode
      and message is a str. The encrypt method will continue trying to retrieve
      the KEK until success (with each attempt separated by
      RETRY_DELAY_KEK_RETRIEVAL_MS) and onError may be called multiple times.
      NOTE: The library will log any exceptions thrown by this callback, but for
      better error handling the callback should catch and properly handle any
      exceptions.
    :type onError: function object
    :param Validator validator: The validation policy to ensure correctness of
      the KEK.
    :param KeyChain keyChain: The KeyChain used to sign Data packets.
    :param Face face: The Face that will be used to fetch the KEK and publish CK
      data.
    """
    def __init__(self, accessPrefix, ckPrefix, ckDataSigningInfo, onError,
                 validator, keyChain, face):
        # Copy the Name.
        self._accessPrefix = Name(accessPrefix)
        self._ckPrefix = Name(ckPrefix)
        self._ckBits = bytearray(EncryptorV2.AES_KEY_SIZE)
        self._ckDataSigningInfo = SigningInfo(ckDataSigningInfo)
        self._isKekRetrievalInProgress = False
        self._onError = onError
        self._keyChain = keyChain
        self._face = face

        self._kekData = None
        # Storage for encrypted CKs.
        self._storage = InMemoryStorageRetaining()
        self._kekPendingInterestId = 0

        self.regenerateCk()

        def onInterest(prefix, interest, face, interestFilterId, filter):
            data = self._storage.find(interest)
            if data != None:
                logging.getLogger(__name__).info("Serving " +
                                                 data.getName().toUri() +
                                                 " from InMemoryStorage")
                try:
                    face.putData(data)
                except:
                    logging.exception("Error in Face.putData")
            else:
                logging.getLogger(__name__).info("Didn't find CK data for " +
                                                 interest.getName().toUri())
                # TODO: Send NACK?

        def onRegisterFailed(prefix):
            logging.getLogger(__name__).error("Failed to register prefix " +
                                              prefix.toUri())

        self._ckRegisteredPrefixId = self._face.registerPrefix(
            Name(ckPrefix).append(EncryptorV2.NAME_COMPONENT_CK), onInterest,
            onRegisterFailed)

    def shutdown(self):
        self._face.unsetInterestFilter(self._ckRegisteredPrefixId)
        if self._kekPendingInterestId > 0:
            self._face.removePendingInterest(self._kekPendingInterestId)

    def encrypt(self, plainData):
        """
        Encrypt the plainData using the existing Content Key (CK) and return a
        new EncryptedContent.

        :param plainData: The data to encrypt.
        :type plainData: Blob or an array which implements the buffer protocol
        :return: The new EncryptedContent.
        :rtype: EncryptedContent
        """
        # Generate the initial vector.
        initialVector = bytearray(EncryptorV2.AES_IV_SIZE)
        for i in range(len(initialVector)):
            initialVector[i] = _systemRandom.randint(0, 0xff)

        params = EncryptParams(EncryptAlgorithmType.AesCbc)
        params.setInitialVector(Blob(initialVector, False))
        encryptedData = AesAlgorithm.encrypt(Blob(self._ckBits, False),
                                             Blob(plainData, False), params)

        content = EncryptedContent()
        content.setInitialVector(params.getInitialVector())
        content.setPayload(encryptedData)
        content.setKeyLocatorName(self._ckName)

        return content

    def regenerateCk(self):
        """
        Create a new Content Key (CK) and publish the corresponding CK Data
        packet. This uses the onError given to the constructor to report errors.
        """
        # TODO: Ensure that the CK Data packet for the old CK is published when
        # the CK is updated before the KEK is fetched.

        self._ckName = Name(self._ckPrefix)
        self._ckName.append(EncryptorV2.NAME_COMPONENT_CK)
        # The version is the ID of the CK.
        self._ckName.appendVersion(int(Common.getNowMilliseconds()))

        logging.getLogger(__name__).info("Generating new CK: " +
                                         self._ckName.toUri())
        for i in range(len(self._ckBits)):
            self._ckBits[i] = _systemRandom.randint(0, 0xff)

        # One implication: If the CK is updated before the KEK is fetched, then
        # the KDK for the old CK will not be published.
        if self._kekData == None:
            self._retryFetchingKek()
        else:
            self._makeAndPublishCkData(self._onError)

    def size(self):
        """
        Get the number of packets stored in in-memory storage.
        
        :return: The number of packets.
        :rtype: int
        """
        return self._storage.size()

    def _retryFetchingKek(self):
        if self._isKekRetrievalInProgress:
            return

        logging.getLogger(__name__).info("Retrying fetching of the KEK")
        self._isKekRetrievalInProgress = True

        def onReady():
            logging.getLogger(__name__).info(
                "The KEK was retrieved and published")
            self._isKekRetrievalInProgress = False

        def onError(errorCode, message):
            logging.getLogger(__name__).info("Failed to retrieve KEK: " +
                                             message)
            self._isKekRetrievalInProgress = False
            self._onError(errorCode, message)

        self._fetchKekAndPublishCkData(onReady, onError, EncryptorV2.N_RETRIES)

    def _fetchKekAndPublishCkData(self, onReady, onError, nTriesLeft):
        """
        Create an Interest for <access-prefix>/KEK to retrieve the
        <access-prefix>/KEK/<key-id> KEK Data packet, and set _kekData.

        :param onReady: When the KEK is retrieved and published, this calls
          onReady().
        :type onError: function object
        :param onError: On failure, this calls onError(errorCode, message)
          where errorCode is from EncryptError.ErrorCode, and message is an
          error string.
        :type onError: function object
        :param int nTriesLeft: The number of retries for expressInterest timeouts.
        """
        logging.getLogger(__name__).info("Fetching KEK: " + Name(
            self._accessPrefix).append(EncryptorV2.NAME_COMPONENT_KEK).toUri())

        if self._kekPendingInterestId > 0:
            onError(
                EncryptError.ErrorCode.General,
                "fetchKekAndPublishCkData: There is already a _kekPendingInterestId"
            )
            return

        def onData(interest, kekData):
            self._kekPendingInterestId = 0
            # TODO: Verify if the key is legitimate.
            self._kekData = kekData
            if self._makeAndPublishCkData(onError):
                onReady()
            # Otherwise, failure has already been reported.

        def onTimeout(interest):
            self._kekPendingInterestId = 0
            if nTriesLeft > 1:
                self._fetchKekAndPublishCkData(onReady, onError,
                                               nTriesLeft - 1)
            else:
                onError(
                    EncryptError.ErrorCode.KekRetrievalTimeout,
                    "Retrieval of KEK [" + interest.getName().toUri() +
                    "] timed out")
                logging.getLogger(__name__).info(
                    "Scheduling retry after all timeouts")
                self._face.callLater(EncryptorV2.RETRY_DELAY_KEK_RETRIEVAL_MS,
                                     self._retryFetchingKek)

        def onNetworkNack(interest, networkNack):
            self._kekPendingInterestId = 0
            if nTriesLeft > 1:

                def callback():
                    self._fetchKekAndPublishCkData(onReady, onError,
                                                   nTriesLeft - 1)

                self._face.callLater(EncryptorV2.RETRY_DELAY_AFTER_NACK_MS,
                                     callback)
            else:
                onError(
                    EncryptError.ErrorCode.KekRetrievalFailure,
                    "Retrieval of KEK [" + interest.getName().toUri() +
                    "] failed. Got NACK (" + str(networkNack.getReason()) +
                    ")")
                logging.getLogger(__name__).info("Scheduling retry from NACK")
                self._face.callLater(EncryptorV2.RETRY_DELAY_KEK_RETRIEVAL_MS,
                                     self._retryFetchingKek)

        try:
            self._kekPendingInterestId = self._face.expressInterest(
                Interest(
                    Name(self._accessPrefix).append(
                        EncryptorV2.NAME_COMPONENT_KEK)).setMustBeFresh(
                            True).setCanBePrefix(True), onData, onTimeout,
                onNetworkNack)
        except Exception as ex:
            onError(EncryptError.ErrorCode.General,
                    "expressInterest error: " + repr(ex))

    def _makeAndPublishCkData(self, onError):
        """
        Make a CK Data packet for _ckName encrypted by the KEK in _kekData and
        insert it in the _storage.

        :param onError: On failure, this calls onError(errorCode, message) where
          errorCode is from EncryptError.ErrorCode, and message is an error
          string.
        :type onError: function object
        :return: True on success, else False.
        :rtype: bool
        """
        try:
            kek = PublicKey(self._kekData.getContent())

            content = EncryptedContent()
            content.setPayload(
                kek.encrypt(Blob(self._ckBits, False),
                            EncryptAlgorithmType.RsaOaep))

            ckData = Data(
                Name(self._ckName).append(
                    EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY).append(
                        self._kekData.getName()))
            ckData.setContent(content.wireEncodeV2())
            # FreshnessPeriod can serve as a soft access control for revoking access.
            ckData.getMetaInfo().setFreshnessPeriod(
                EncryptorV2.DEFAULT_CK_FRESHNESS_PERIOD_MS)
            self._keyChain.sign(ckData, self._ckDataSigningInfo)
            self._storage.insert(ckData)

            logging.getLogger(__name__).info("Publishing CK data: " +
                                             ckData.getName().toUri())
            return True
        except Exception as ex:
            onError(
                EncryptError.ErrorCode.EncryptionFailure,
                "Failed to encrypt generated CK with KEK " +
                self._kekData.getName().toUri())
            return False

    NAME_COMPONENT_ENCRYPTED_BY = Name.Component("ENCRYPTED-BY")
    NAME_COMPONENT_NAC = Name.Component("NAC")
    NAME_COMPONENT_KEK = Name.Component("KEK")
    NAME_COMPONENT_KDK = Name.Component("KDK")
    NAME_COMPONENT_CK = Name.Component("CK")

    RETRY_DELAY_AFTER_NACK_MS = 1000.0
    RETRY_DELAY_KEK_RETRIEVAL_MS = 60 * 1000.0

    AES_KEY_SIZE = 32
    AES_IV_SIZE = 16
    N_RETRIES = 3

    DEFAULT_CK_FRESHNESS_PERIOD_MS = 3600 * 1000.0
예제 #51
0
    def _fetchKdk(self, contentKey, kdkPrefix, ckData, onError, nTriesLeft):
        """
        :param DecryptorV2.ContentKey contentKey:
        :param Name kdkPrefix:
        :param Data ckData:
        :param onError: On error, this calls onError(errorCode, message)
        :type onError: function object
        :param int nTriesLeft:
        """
        # <kdk-prefix>/KDK/<kdk-id>    /ENCRYPTED-BY  /<credential-identity>/KEY/<key-id>
        # \                          /                \                                /
        #  -----------  -------------                  ---------------  ---------------
        #             \/                                              \/
        #     from the CK data                                from configuration

        kdkName = Name(kdkPrefix)
        kdkName.append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY).append(
          self._credentialsKey.getName())

        logging.getLogger(__name__).info("Fetching KDK " + kdkName.toUri())

        def onData(kdkInterest, kdkData):
            try:
                contentKey.pendingInterest = 0
                # TODO: Verify that the key is legitimate.

                isOk = self._decryptAndImportKdk(kdkData, onError)
                if not isOk:
                    return
                # This way of getting the kdkKeyName is a bit hacky.
                kdkKeyName = kdkPrefix.getPrefix(-2).append("KEY").append(
                  kdkPrefix.get(-1))
                self._decryptCkAndProcessPendingDecrypts(
                  contentKey, ckData, kdkKeyName, onError)
            except Exception as ex:
                onError(EncryptError.ErrorCode.General,
                  "Error in fetchCk onData: " + repr(ex))

        def onTimeout(interest):
            contentKey.pendingInterest = 0
            if nTriesLeft > 1:
                self._fetchKdk(
                  contentKey, kdkPrefix, ckData, onError, nTriesLeft - 1)
            else:
                onError(EncryptError.ErrorCode.KdkRetrievalTimeout,
                  "Retrieval of KDK [" + interest.getName().toUri() +
                  "] timed out")

        def onNetworkNack(interest, networkNack):
            contentKey.pendingInterest = 0
            onError(EncryptError.ErrorCode.KdkRetrievalFailure,
              "Retrieval of KDK [" + interest.getName().toUri() +
              "] failed. Got NACK (" + str(networkNack.getReason()) + ")")

        try:
            contentKey.pendingInterest = self._face.expressInterest(
              Interest(kdkName).setMustBeFresh(True).setCanBePrefix(False),
              onData, onTimeout, onNetworkNack)
        except Exception as ex:
            onError(EncryptError.ErrorCode.General,
              "expressInterest error: " + repr(ex))