Ejemplo n.º 1
0
    def generateCertificateForKey(self, keyName):
        # let any raised SecurityExceptions bubble up
        publicKeyBits = self._identityStorage.getKey(keyName)
        publicKeyType = self._identityStorage.getKeyType(keyName)

        publicKey = PublicKey(publicKeyType, publicKeyBits)

        timestamp = Common.getNowMilliseconds()

        # TODO: specify where the 'KEY' component is inserted
        # to delegate responsibility for cert delivery
        certificateName = keyName.getPrefix(-1).append('KEY').append(
            keyName.get(-1))
        certificateName.append("ID-CERT").append(
            Name.Component(struct.pack(">Q", timestamp)))

        certificate = IdentityCertificate(certificateName)

        certificate.setNotBefore(timestamp)
        certificate.setNotAfter(
            (timestamp + 30 * 86400 * 1000))  # about a month

        certificate.setPublicKeyInfo(publicKey)

        # ndnsec likes to put the key name in a subject description
        sd = CertificateSubjectDescription("2.5.4.41", keyName.toUri())
        certificate.addSubjectDescription(sd)

        certificate.encode()

        return certificate
Ejemplo n.º 2
0
    def signInterestWithSha256(self, interest, wireFormat=None):
        """
        Append a SignatureInfo for DigestSha256 to the Interest name, digest the
        name components and append a final name component with the signature
        bits (which is the digest).

        :param Interest interest: The Interest object to be signed. This appends
          name components of SignatureInfo and the signature bits.
        :param wireFormat: (optional) A WireFormat object used to encode the
           input. If omitted, use WireFormat.getDefaultWireFormat().
        :type wireFormat: A subclass of WireFormat
        """
        if wireFormat == None:
            # Don't use a default argument since getDefaultWireFormat can change.
            wireFormat = WireFormat.getDefaultWireFormat()

        signature = DigestSha256Signature()
        # Append the encoded SignatureInfo.
        interest.getName().append(wireFormat.encodeSignatureInfo(signature))

        # Append an empty signature so that the "signedPortion" is correct.
        interest.getName().append(Name.Component())
        # Encode once to get the signed portion.
        encoding = interest.wireEncode(wireFormat)

        # Digest and set the signature.
        sha256 = hashes.Hash(hashes.SHA256(), backend=default_backend())
        sha256.update(encoding.toSignedBytes())
        signatureBits = sha256.finalize()
        signature.setSignature(Blob(bytearray(signatureBits), False))

        # Remove the empty signature and append the real one.
        interest.setName(interest.getName().getPrefix(-1).append(
            wireFormat.encodeSignatureValue(signature)))
Ejemplo n.º 3
0
    def setKeyName(keyHandle, identityName, params):
        """
        Set the key name in keyHandle according to identityName and params.

        :param TpmKeyHandle keyHandle:
        :param Name identityName:
        :param KeyParams params:
        """
        if params.getKeyIdType() == KeyIdType.USER_SPECIFIED:
            keyId = params.getKeyId()
        elif params.getKeyIdType() == KeyIdType.SHA256:
            sha256 = hashes.Hash(hashes.SHA256(), backend=default_backend())
            sha256.update(keyHandle.derivePublicKey().toBytes())
            digest = sha256.finalize()
            keyId = Name.Component(digest)
        elif params.getKeyIdType() == KeyIdType.RANDOM:
            if params.getKeyId().getValue().size() == 0:
                raise TpmBackEnd.Error(
                  "setKeyName: The keyId is empty for type RANDOM")
            keyId = params.getKeyId()
        else:
            raise TpmBackEnd.Error(
              "setKeyName: unrecognized params.getKeyIdType()")

        keyHandle.setKeyName(PibKey.constructKeyName(identityName, keyId))
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    def test_key_management(self):
        for tpm in self.backEndList:
            identityName = Name("/Test/KeyName")
            keyId = Name.Component("1")
            keyName = PibKey.constructKeyName(identityName, keyId)

            # The key should not exist.
            self.assertEquals(False, tpm.hasKey(keyName))
            self.assertTrue(tpm.getKeyHandle(keyName) == None)

            # Create a key, which should exist.
            self.assertTrue(
              tpm.createKey(identityName, RsaKeyParams(keyId)) != None)
            self.assertTrue(tpm.hasKey(keyName))
            self.assertTrue(tpm.getKeyHandle(keyName) != None)

            # Create a key with the same name, which should throw an error.
            try:
                tpm.createKey(identityName, RsaKeyParams(keyId))
                self.fail("Did not throw the expected exception")
            except Tpm.Error:
                pass
            else:
                self.fail("Did not throw the expected exception")

            # Delete the key, then it should not exist.
            tpm.deleteKey(keyName)
            self.assertEquals(False, tpm.hasKey(keyName))
            self.assertTrue(tpm.getKeyHandle(keyName) == None)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
 def __init__(self, value=None):
     if value == None:
         self._type = Exclude.ANY
         self._component = None
     else:
         self._type = Exclude.COMPONENT
         self._component = (value if type(value) is Name.Component else
                            Name.Component(value))
Ejemplo n.º 8
0
 def __init__(self, value=None):
     if value == None:
         self._type = Exclude.ANY
         self._component = None
     else:
         self._type = Exclude.COMPONENT
         self._component = (value if isinstance(value, Name.Component)
                            else Name.Component(value))
Ejemplo n.º 9
0
    def clear(self):
        """
        Clear fields and reset to default values.
        """
        self._type = ContentType.BLOB
        self._otherTypeCode = -1
        self._freshnessPeriod = None
        self._finalBlockId = Name.Component()

        self._changeCount += 1
Ejemplo n.º 10
0
 def setFinalBlockID(self, finalBlockID):
     """
     Set the final block ID.
     
     :param finalBlockID: The final block ID.  If it is another 
       Name.Component, use its value. Otherwise pass value to the 
       Name.Component constructor.
     :type finalBlockID: Name.Component or value for the Name.Component 
       constructor
     """
     self._finalBlockID = (finalBlockID if type(finalBlockID) is Name.Component 
                           else Name.Component(finalBlockID))
     self._changeCount += 1
Ejemplo n.º 11
0
    def setFinalBlockId(self, finalBlockId):
        """
        Set the final block ID.

        :param finalBlockId: The final block ID.  If it is another
          Name.Component, use its value. Otherwise pass value to the
          Name.Component constructor.  If finalBlockId is None, set to a
          Name.Component of size 0 so that the finalBlockId is not specified
          and not encoded.
        :type finalBlockId: Name.Component or value for the Name.Component
          constructor
        """
        self._finalBlockId = (finalBlockId if type(finalBlockId) is Name.Component
                              else Name.Component(finalBlockId))
        self._changeCount += 1
Ejemplo n.º 12
0
 def __init__(self, value = None):
     if value == None:
         self._type = ContentType.BLOB
         self._freshnessPeriod = None
         self._finalBlockID = Name.Component()
     elif type(value) is MetaInfo:
         # Copy its values.
         self._type = value._type
         self._freshnessPeriod = value._freshnessPeriod
         self._finalBlockID = self._finalBlockID
     else:
         raise RuntimeError(
           "Unrecognized type for MetaInfo constructor: " +
           repr(type(value)))
                 
     self._changeCount = 0
Ejemplo n.º 13
0
    def __init__(self, value = None):
        if value == None:
            self._type = ContentType.BLOB
            self._otherTypeCode = -1
            self._freshnessPeriod = None
            self._finalBlockId = Name.Component()
        elif isinstance(value, MetaInfo):
            # Copy its values.
            self._type = value._type
            self._otherTypeCode = value._otherTypeCode
            self._freshnessPeriod = value._freshnessPeriod
            self._finalBlockId = value._finalBlockId
        else:
            raise RuntimeError(
              "Unrecognized type for MetaInfo constructor: " +
              str(type(value)))

        self._changeCount = 0
Ejemplo n.º 14
0
    def __init__(self, face, cleanupIntervalMilliseconds=None):
        if cleanupIntervalMilliseconds == None:
            cleanupIntervalMilliseconds = 1000.0

        self._face = face
        self._cleanupIntervalMilliseconds = cleanupIntervalMilliseconds
        self._nextCleanupTime = (Common.getNowMilliseconds() +
                                 cleanupIntervalMilliseconds)

        # The map key is the prefix.toUri(). The value is an OnInterest function.
        self._onDataNotFoundForPrefix = {}
        # elements are int
        self._registeredPrefixIdList = []
        # elements are MemoryContentCache._Content
        self._noStaleTimeCache = []
        # elements are MemoryContentCache.StaleTimeContent
        self._staleTimeCache = []
        self._emptyComponent = Name.Component()
        self._pendingInterestTable = []  # of PendingInterest
Ejemplo n.º 15
0
    def __init__(self, keyType, keyIdTypeOrKeyId):
        self._keyType = keyType

        if isinstance(keyIdTypeOrKeyId, Name.Component):
            keyId = keyIdTypeOrKeyId

            if keyId.getValue().size() == 0:
                raise ValueError("KeyParams: keyId is empty")

            self._keyIdType = KeyIdType.USER_SPECIFIED
            self._keyId = keyId
        else:
            keyIdType = keyIdTypeOrKeyId

            if keyIdType == KeyIdType.USER_SPECIFIED:
                raise ValueError("KeyParams: KeyIdType is USER_SPECIFIED")

            self._keyIdType = keyIdType
            self._keyId = Name.Component()
Ejemplo n.º 16
0
    def signInterestByCertificate(self,
                                  interest,
                                  certificateName,
                                  wireFormat=None):
        """
        Append a SignatureInfo to the Interest name, sign the name components
        and append a final name component with the signature bits.

        :param Interest interest: The Interest object to be signed. This appends
          name components of SignatureInfo and the signature bits.
        :param Name certificateName: The certificate name of the key to use for
          signing.
        :param wireFormat: (optional) A WireFormat object used to encode the
           input. If omitted, use WireFormat.getDefaultWireFormat().
        :type wireFormat: A subclass of WireFormat
        """
        if wireFormat == None:
            # Don't use a default argument since getDefaultWireFormat can change.
            wireFormat = WireFormat.getDefaultWireFormat()

        digestAlgorithm = [0]
        signature = self._makeSignatureByCertificate(certificateName,
                                                     digestAlgorithm)

        # Append the encoded SignatureInfo.
        interest.getName().append(wireFormat.encodeSignatureInfo(signature))

        # Append an empty signature so that the "signedPortion" is correct.
        interest.getName().append(Name.Component())
        # Encode once to get the signed portion, and sign.
        encoding = interest.wireEncode(wireFormat)
        signature.setSignature(
            self._privateKeyStorage.sign(
                encoding.toSignedBuffer(),
                IdentityCertificate.certificateNameToPublicKeyName(
                    certificateName), digestAlgorithm[0]))

        # Remove the empty signature and append the real one.
        interest.setName(interest.getName().getPrefix(-1).append(
            wireFormat.encodeSignatureValue(signature)))
Ejemplo n.º 17
0
    def _signInterest(self, interest, certificateName, wireFormat=None):
        """
        Append a SignatureInfo to the Interest name, sign the name components 
        and append a final name component with the signature bits.
        
        :param Interest interest: The Interest object to be signed. This appends 
          name components of SignatureInfo and the signature bits.
        :param Name certificateName: The certificate name of the key to use for 
          signing.
        :param wireFormat: (optional) A WireFormat object used to encode the 
           input. If omitted, use WireFormat.getDefaultWireFormat().
        :type wireFormat: A subclass of WireFormat
        """
        if wireFormat == None:
            # Don't use a default argument since getDefaultWireFormat can change.
            wireFormat = WireFormat.getDefaultWireFormat()

        # TODO: Handle signature algorithms other than Sha256WithRsa.
        signature = Sha256WithRsaSignature()
        signature.getKeyLocator().setType(KeyLocatorType.KEYNAME)
        signature.getKeyLocator().setKeyName(certificateName.getPrefix(-1))

        # Append the encoded SignatureInfo.
        interest.getName().append(wireFormat.encodeSignatureInfo(signature))

        # Append an empty signature so that the "signedPortion" is correct.
        interest.getName().append(Name.Component())
        # Encode once to get the signed portion.
        encoding = interest.wireEncode(wireFormat)
        signedSignature = self.sign(encoding.toSignedBuffer(), certificateName)

        # Remove the empty signature and append the real one.
        encoder = TlvEncoder(256)
        encoder.writeBlobTlv(Tlv.SignatureValue,
                             signedSignature.getSignature().buf())
        interest.setName(interest.getName().getPrefix(-1).append(
            wireFormat.encodeSignatureValue(signedSignature)))
Ejemplo n.º 18
0
    def signInterestWithSha256(self, interest, wireFormat = None):
        """
        Append a SignatureInfo for DigestSha256 to the Interest name, digest the
        name components and append a final name component with the signature
        bits (which is the digest).

        :param Interest interest: The Interest object to be signed. This appends
          name components of SignatureInfo and the signature bits.
        :param wireFormat: (optional) A WireFormat object used to encode the
           input. If omitted, use WireFormat.getDefaultWireFormat().
        :type wireFormat: A subclass of WireFormat
        """
        if wireFormat == None:
            # Don't use a default argument since getDefaultWireFormat can change.
            wireFormat = WireFormat.getDefaultWireFormat()

        signature = DigestSha256Signature()
        # Append the encoded SignatureInfo.
        interest.getName().append(wireFormat.encodeSignatureInfo(signature))

        # Append an empty signature so that the "signedPortion" is correct.
        interest.getName().append(Name.Component())
        # Encode once to get the signed portion.
        encoding = interest.wireEncode(wireFormat)

        # Get the bytes to sign.
        signedPortion = encoding.toSignedBuffer()
        if sys.version_info[0] == 2:
            # In Python 2.x, we need a str.  Use Blob to convert signedPortion.
            signedPortion = Blob(signedPortion, False).toRawStr()

        # Digest and set the signature.
        signature.setSignature(Blob(SHA256.new(signedPortion).digest()))

        # Remove the empty signature and append the real one.
        interest.setName(interest.getName().getPrefix(-1).append(
          wireFormat.encodeSignatureValue(signature)))
Ejemplo n.º 19
0
    def createKey(self, identityName, params):
        """
        Create a key for the identityName according to params.

        :param Name identityName: The name if the identity.
        :param KeyParams params: The KeyParams for creating the key.
        :return: The handle of the created key.
        :rtype: TpmKeyHandle
        :raises TpmBackEnd.Error: If the key cannot be created.
        """
        # Do key name checking.
        if params.getKeyIdType() == KeyIdType.USER_SPECIFIED:
            # The keyId is pre-set.
            keyName = PibKey.constructKeyName(identityName, params.getKeyId())
            if self.hasKey(keyName):
                raise Tpm.Error("Key `" + keyName.toUri() + "` already exists")
        elif params.getKeyIdType() == KeyIdType.SHA256:
            # The key name will be assigned in setKeyName after the key is generated.
            pass
        elif params.getKeyIdType() == KeyIdType.RANDOM:
            random = bytearray(8)
            while True:
                for i in range(len(random)):
                    random[i] = _systemRandom.randint(0, 0xff)

                keyId = Name.Component(Blob(random, False))
                keyName = PibKey.constructKeyName(identityName, keyId)

                if not self.hasKey(keyName):
                    # We got a unique one.
                    break

            params.setKeyId(keyId)
        else:
            raise Tpm.Error("Unsupported key id type")

        return self._doCreateKey(identityName, params)
Ejemplo n.º 20
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
Ejemplo n.º 21
0
class Encryptor(object):
    NAME_COMPONENT_FOR = Name.Component("FOR")
    NAME_COMPONENT_READ = Name.Component("READ")
    NAME_COMPONENT_SAMPLE = Name.Component("SAMPLE")
    NAME_COMPONENT_ACCESS = Name.Component("ACCESS")
    NAME_COMPONENT_E_KEY = Name.Component("E-KEY")
    NAME_COMPONENT_D_KEY = Name.Component("D-KEY")
    NAME_COMPONENT_C_KEY = Name.Component("C-KEY")

    @staticmethod
    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")

    @staticmethod
    def _encryptSymmetric(payload, key, keyName, params):
        """
        Encrypt the payload using the symmetric key according to params, and
        return an EncryptedContent.

        :param Blob payload: The data to encrypt.
        :param Blob key: The key value.
        :param Name keyName: The key name for the EncryptedContent key locator.
        :param EncryptParams params: The parameters for encryption.
        :return: A new EncryptedContent.
        :rtype: EncryptedContent
        """
        algorithmType = params.getAlgorithmType()
        initialVector = params.getInitialVector()
        keyLocator = KeyLocator()
        keyLocator.setType(KeyLocatorType.KEYNAME)
        keyLocator.setKeyName(keyName)

        if (algorithmType == EncryptAlgorithmType.AesCbc
                or algorithmType == EncryptAlgorithmType.AesEcb):
            if (algorithmType == EncryptAlgorithmType.AesCbc):
                if initialVector.size() != AesAlgorithm.BLOCK_SIZE:
                    raise RuntimeError("incorrect initial vector size")

            encryptedPayload = AesAlgorithm.encrypt(key, payload, params)

            result = EncryptedContent()
            result.setAlgorithmType(algorithmType)
            result.setKeyLocator(keyLocator)
            result.setPayload(encryptedPayload)
            result.setInitialVector(initialVector)
            return result
        else:
            raise RuntimeError("Unsupported encryption method")

    @staticmethod
    def _encryptAsymmetric(payload, key, keyName, params):
        """
        Encrypt the payload using the asymmetric key according to params, and
        return an EncryptedContent.

        :param Blob payload: The data to encrypt. The size should be within
          range of the key.
        :param Blob key: The key value.
        :param Name keyName: The key name for the EncryptedContent key locator.
        :param EncryptParams params: The parameters for encryption.
        :return: A new EncryptedContent.
        :rtype: EncryptedContent
        """
        algorithmType = params.getAlgorithmType()
        keyLocator = KeyLocator()
        keyLocator.setType(KeyLocatorType.KEYNAME)
        keyLocator.setKeyName(keyName)

        if (algorithmType == EncryptAlgorithmType.RsaPkcs
                or algorithmType == EncryptAlgorithmType.RsaOaep):
            encryptedPayload = RsaAlgorithm.encrypt(key, payload, params)

            result = EncryptedContent()
            result.setAlgorithmType(algorithmType)
            result.setKeyLocator(keyLocator)
            result.setPayload(encryptedPayload)
            return result
        else:
            raise RuntimeError("Unsupported encryption method")
Ejemplo n.º 22
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
Ejemplo n.º 23
0
class CertificateV2(Data):
    """
    Create a CertificateV2 from the content in the Data packet (if not omitted).

    :param Data data: (optional) The data packet with the content to copy.
      If omitted, create a CertificateV2 with content type KEY and default
      or unspecified values.
    """
    def __init__(self, data=None):
        super(CertificateV2, self).__init__(data)
        if isinstance(data, Data):
            self._checkFormat()
        else:
            self.getMetaInfo().setType(ContentType.KEY)

    class Error(Exception):
        """
        Create a CertificateV2.Error to report an error for not complying
        with the certificate format.

        :param str message: The error message.
        """
        def __init__(self, message):
            super(CertificateV2.Error, self).__init__(message)

    def _checkFormat(self):
        if not CertificateV2.isValidName(self.getName()):
            raise CertificateV2.Error(
                "The Data Name does not follow the certificate naming convention"
            )

        if self.getMetaInfo().getType() != ContentType.KEY:
            raise CertificateV2.Error("The Data ContentType is not KEY")

        if self.getMetaInfo().getFreshnessPeriod() < 0.0:
            raise CertificateV2.Error("The Data FreshnessPeriod is not set")

        if self.getContent().size() == 0:
            raise CertificateV2.Error("The Data Content is empty")

    def getKeyName(self):
        """
        Get key name from the certificate name.

        :return: The key name as a new Name.
        :rtype: Name
        """
        return self.getName().getPrefix(CertificateV2.KEY_ID_OFFSET + 1)

    def getIdentity(self):
        """
        Get the identity name from the certificate name.

        :return: The identity name as a new Name.
        :rtype: Name
        """
        return self.getName().getPrefix(CertificateV2.KEY_COMPONENT_OFFSET)

    def getKeyId(self):
        """
        Get the key ID component from the certificate name.

        :return: The key ID name component.
        :rtype: Name.Component
        """
        return self.getName().get(CertificateV2.KEY_ID_OFFSET)

    def getIssuerId(self):
        """
        Get the issuer ID component from the certificate name.

        :return: The issuer ID component.
        :rtype: Name.Component
        """
        return self.getName().get(CertificateV2.ISSUER_ID_OFFSET)

    def getPublicKey(self):
        """
        Get the public key DER encoding.

        :return: The DER encoding Blob.
        :rtype: Blob
        :raises CertificateV2.Error: If the public key is not set.
        """
        if self.getContent().size() == 0:
            raise CertificateV2.Error(
                "The public key is not set (the Data content is empty)")

        return self.getContent()

    def getValidityPeriod(self):
        """
        Get the certificate validity period from the SignatureInfo.

        :return: The ValidityPeriod object.
        :rtype: ValidityPeriod
        :raises ValueError: If the SignatureInfo doesn't have a
          ValidityPeriod.
        """
        if not ValidityPeriod.canGetFromSignature(self.getSignature()):
            raise ValueError(
                "The SignatureInfo does not have a ValidityPeriod")

        return ValidityPeriod.getFromSignature(self.getSignature())

    def isValid(self, time=None):
        """
        Check if the time falls within the validity period.

        :param float time: (optional) The time to check as milliseconds since
          Jan 1, 1970 UTC. If omitted, use the current time.
        :return: True if the beginning of the validity period is less than or
          equal to time and time is less than or equal to the end of the
          validity period.
        :rtype: bool
        :raises ValueError: If the SignatureInfo doesn't have a
          ValidityPeriod.
        """
        return self.getValidityPeriod().isValid(time)

    def wireDecode(self, buf, wireFormat=None):
        """
        Override to call the base class wireDecode then check the certificate
        format.

        :param input: The array with the bytes to decode. If input is not a
          Blob, then copy the bytes to save the defaultWireEncoding (otherwise
          take another pointer to the same Blob).
        :type input: A Blob or an array type with int elements
        :param wireFormat: (optional) A WireFormat object used to decode this
           Data object. If omitted, use WireFormat.getDefaultWireFormat().
        :type wireFormat: A subclass of WireFormat
        """
        Data.wireDecode(self, buf, wireFormat)
        self._checkFormat()

    def __str__(self):
        """
        Get a string representation of this certificate.

        :return: The string representation.
        :rtype: str
        """
        result = ""
        result += "Certificate name:\n"
        result += "  " + self.getName().toUri() + "\n"
        result += "Validity:\n"
        result += "  NotBefore: " + Schedule.toIsoString(
            self.getValidityPeriod().getNotBefore()) + "\n"
        result += "  NotAfter: " + Schedule.toIsoString(
            self.getValidityPeriod().getNotAfter()) + "\n"

        # TODO: Print the extension.

        result += "Public key bits:\n"
        try:
            result += Common.base64Encode(self.getPublicKey().toBytes(), True)
        except:
            # No public key.
            pass

        result += "Signature Information:\n"
        result += "  Signature Type: "
        if type(self.getSignature()) is Sha256WithEcdsaSignature:
            result += "SignatureSha256WithEcdsa\n"
        elif type(self.getSignature()) is Sha256WithRsaSignature:
            result += "SignatureSha256WithRsa\n"
        else:
            result += "<unknown>\n"

        if KeyLocator.canGetFromSignature(self.getSignature()):
            result += "  Key Locator: "
            keyLocator = KeyLocator.getFromSignature(self.getSignature())
            if keyLocator.getType() == KeyLocatorType.KEYNAME:
                if keyLocator.getKeyName().equals(self.getKeyName()):
                    result += "Self-Signed "

                result += "Name=" + keyLocator.getKeyName().toUri() + "\n"
            else:
                result += "<no KeyLocator key name>\n"

        return result

    @staticmethod
    def isValidName(certificateName):
        """
        Check if certificateName follows the naming convention for a certificate.

        :param Name certificateName: The name of the certificate.
        :return: True if certificateName follows the naming convention.
        :rtype: bool
        """
        # /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version]
        return (certificateName.size() >= CertificateV2.MIN_CERT_NAME_LENGTH
                and certificateName.get(
                    CertificateV2.KEY_COMPONENT_OFFSET).equals(
                        CertificateV2.KEY_COMPONENT))

    @staticmethod
    def extractIdentityFromCertName(certificateName):
        """
        Extract the identity namespace from certificateName.

        :param Name certificateName: The name of the certificate.
        :return: The identity namespace as a new Name.
        :rtype: Name
        """
        if not CertificateV2.isValidName(certificateName):
            raise ValueError("Certificate name `" + certificateName.toUri() +
                             "` does not follow the naming conventions")

        return certificateName.getPrefix(CertificateV2.KEY_COMPONENT_OFFSET)

    @staticmethod
    def extractKeyNameFromCertName(certificateName):
        """
        Extract key name from certificateName.

        :param Name certificateName: The name of the certificate.
        :return: The key name as a new Name.
        :rtype: Name
        """
        if not CertificateV2.isValidName(certificateName):
            raise ValueError("Certificate name `" + certificateName.toUri() +
                             "` does not follow the naming conventions")

        # Trim everything after the key ID.
        return certificateName.getPrefix(CertificateV2.KEY_ID_OFFSET + 1)

    VERSION_OFFSET = -1
    ISSUER_ID_OFFSET = -2
    KEY_ID_OFFSET = -3
    KEY_COMPONENT_OFFSET = -4
    MIN_CERT_NAME_LENGTH = 4
    MIN_KEY_NAME_LENGTH = 2
    KEY_COMPONENT = Name.Component("KEY")
Ejemplo n.º 24
0
# KeyLocator
#
from pyndn.name import Name
from pyndn.util.blob import Blob
from pyndn.key_locator import KeyLocator, KeyLocatorType
for p in [("type", KeyLocatorType.KEYNAME, KeyLocatorType.KEY_LOCATOR_DIGEST), ("keyName", Name("yes"), Name("another")),  ("keyData", Blob(), Blob())]:
    res = testPropertyRW( KeyLocator(), p[0], [p[1],p[2]])
    if not res[0]: print(res)

# MetaInfo
# TODO:  Support FinalBlockId of None.
#
from pyndn.name import Name
from pyndn.meta_info import MetaInfo, ContentType
for p in [("type", ContentType.BLOB, ContentType.LINK), ("freshnessPeriod", 47, None), ("finalBlockId", Name.Component("12"), Name.Component())]:
    res = testPropertyRW( MetaInfo(), p[0], [p[1],p[2]])
    if not res[0]: print(res)

# Sha256WithRsaSignature
# TODO: When passing None as the KeyLocator, it generates a blank KeyLocator object, so equivalence checks fail.  Don't check None.
# TODO: Should the signature value be a blob? (Or allow Signature?)
#
from pyndn.key_locator import KeyLocator
from pyndn.signature import Signature
from pyndn.sha256_with_rsa_signature import Sha256WithRsaSignature
for p in [("keyLocator", KeyLocator(), KeyLocator()), ("signature", Blob(), Blob())]:
    res = testPropertyRW( Sha256WithRsaSignature(), p[0], [p[1],p[2]])
    if not res[0]: print(res)

Ejemplo n.º 25
0
 def setMatchResult(self, value):
     """
     :param str value:
     """
     self._matchResult.append(Name.Component(value))
Ejemplo n.º 26
0
from pyndn.util.blob import Blob
from pyndn.key_locator import KeyLocator, KeyLocatorType
for p in [("type", KeyLocatorType.KEYNAME, KeyLocatorType.KEY_LOCATOR_DIGEST),
          ("keyName", Name("yes"), Name("another")),
          ("keyData", Blob(), Blob())]:
    res = testPropertyRW(KeyLocator(), p[0], [p[1], p[2]])
    if not res[0]: print(res)

# MetaInfo
# TODO:  Support FinalBlockId of None.
#
from pyndn.name import Name
from pyndn.meta_info import MetaInfo, ContentType
for p in [("type", ContentType.BLOB, ContentType.LINK),
          ("freshnessPeriod", 47, None),
          ("finalBlockId", Name.Component("12"), Name.Component())]:
    res = testPropertyRW(MetaInfo(), p[0], [p[1], p[2]])
    if not res[0]: print(res)

# Sha256WithRsaSignature
# TODO: When passing None as the KeyLocator, it generates a blank KeyLocator object, so equivalence checks fail.  Don't check None.
# TODO: Should the signature value be a blob? (Or allow Signature?)
#
from pyndn.key_locator import KeyLocator
from pyndn.signature import Signature
from pyndn.sha256_with_rsa_signature import Sha256WithRsaSignature
for p in [("keyLocator", KeyLocator(), KeyLocator()),
          ("signature", Blob(), Blob())]:
    res = testPropertyRW(Sha256WithRsaSignature(), p[0], [p[1], p[2]])
    if not res[0]: print(res)