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
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)))
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))
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
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)
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)
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))
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))
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
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
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
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
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
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
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()
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)))
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)))
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)))
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)
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
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")
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
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")
# 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)
def setMatchResult(self, value): """ :param str value: """ self._matchResult.append(Name.Component(value))
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)