Example #1
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
    def publish(self, interestName, dataName, content, freshnessPeriod, 
      signingInfo = SigningInfo()):
        """
        Put all the segments in the memory store.

        :param Name interestName: If the Interest name ends in a segment,
          immediately send the Data packet for the segment to the Face.
        :param Name dataName: The Data name, which has components after the
          Interest name.
        :param Blob content: The content of the data to be segmented.
        :param float freshnessPeriod The freshness period of the segments,
          in milliseconds.
        :param SigningInfo signingInfo (optional) The SigningInfo for signing
          segment Data packets. If omitted, use the default SigningInfo().
        """
        interestSegment = 0
        if interestName[-1].isSegment():
            interestSegment = interestName[-1].toSegment()

        rawBuffer = content.buf()
        iSegmentBegin = 0
        iEnd = len(content)

        maxPacketSize = int(Common.MAX_NDN_PACKET_SIZE / 2)

        totalSegments = int(len(content) / maxPacketSize)
        finalBlockId = Name.Component.fromSegment(totalSegments)

        segmentPrefix = Name(dataName)
        segmentPrefix.appendVersion(int(Common.getNowMilliseconds()))

        segmentNo = 0
        while(True):
            iSegmentEnd = iSegmentBegin + maxPacketSize
            if iSegmentEnd > iEnd:
                iSegmentEnd = iEnd

            segmentName = Name(segmentPrefix)
            segmentName.appendSegment(segmentNo)

            data = Data(segmentName)
            data.setContent(Blob(rawBuffer[iSegmentBegin : iSegmentEnd]))
            data.getMetaInfo().setFreshnessPeriod(freshnessPeriod)
            data.getMetaInfo().setFinalBlockId(finalBlockId)

            iSegmentBegin = iSegmentEnd

            self._keyChain.sign(data, signingInfo)

            # Only send the segment to the Face if it has a pending interest.
            # Otherwise, the segment is unsolicited.
            if interestSegment == segmentNo:
                self._face.putData(data)

            # Until InMemoryStorageFifo implements an eviction policy, use InMemoryStorageRetaining.
            # storage_.insert(*data, freshnessPeriod)
            self._storage.insert(data)

            # Make and return a callback since segmentName is different each time.
            def makeCallback(localSegmentName):
                def callback():
                    self._storage.remove(localSegmentName)
                return callback

            self._face.callLater(freshnessPeriod, makeCallback(segmentName))

            segmentNo += 1
            
            if not (iSegmentBegin < iEnd):
                break
Example #3
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
    def publish(self,
                interestName,
                dataName,
                content,
                freshnessPeriod,
                signingInfo=SigningInfo()):
        """
        Put all the segments in the memory store.

        :param Name interestName: If the Interest name ends in a segment,
          immediately send the Data packet for the segment to the Face.
        :param Name dataName: The Data name, which has components after the
          Interest name.
        :param Blob content: The content of the data to be segmented.
        :param float freshnessPeriod The freshness period of the segments,
          in milliseconds.
        :param SigningInfo signingInfo (optional) The SigningInfo for signing
          segment Data packets. If omitted, use the default SigningInfo().
        """
        interestSegment = 0
        if interestName[-1].isSegment():
            interestSegment = interestName[-1].toSegment()

        rawBuffer = content.buf()
        iSegmentBegin = 0
        iEnd = len(content)

        maxPacketSize = int(Common.MAX_NDN_PACKET_SIZE / 2)

        totalSegments = int(len(content) / maxPacketSize)
        finalBlockId = Name.Component.fromSegment(totalSegments)

        segmentPrefix = Name(dataName)
        segmentPrefix.appendVersion(int(Common.getNowMilliseconds()))

        segmentNo = 0
        while (True):
            iSegmentEnd = iSegmentBegin + maxPacketSize
            if iSegmentEnd > iEnd:
                iSegmentEnd = iEnd

            segmentName = Name(segmentPrefix)
            segmentName.appendSegment(segmentNo)

            data = Data(segmentName)
            data.setContent(Blob(rawBuffer[iSegmentBegin:iSegmentEnd]))
            data.getMetaInfo().setFreshnessPeriod(freshnessPeriod)
            data.getMetaInfo().setFinalBlockId(finalBlockId)

            iSegmentBegin = iSegmentEnd

            self._keyChain.sign(data, signingInfo)

            # Only send the segment to the Face if it has a pending interest.
            # Otherwise, the segment is unsolicited.
            if interestSegment == segmentNo:
                self._face.putData(data)

            # Until InMemoryStorageFifo implements an eviction policy, use InMemoryStorageRetaining.
            # storage_.insert(*data, freshnessPeriod)
            self._storage.insert(data)

            # Make and return a callback since segmentName is different each time.
            def makeCallback(localSegmentName):
                def callback():
                    self._storage.remove(localSegmentName)

                return callback

            self._face.callLater(freshnessPeriod, makeCallback(segmentName))

            segmentNo += 1

            if not (iSegmentBegin < iEnd):
                break