def _initialTimeOut(self, interest): """ Initial sync interest timeout, which means there are no other publishers yet. """ if not self._enabled: # Ignore callbacks after the application calls shutdown(). return logging.getLogger(__name__).info("initial sync timeout") logging.getLogger(__name__).info("no other people") self._sequenceNo += 1 if self._sequenceNo != 0: # Since there were no other users, we expect sequence no 0. raise RuntimeError( "ChronoSync: sequenceNo_ is not the expected value of 0 for first use.") tempContent = sync_state_pb2.SyncStateMsg() content = getattr(tempContent, "ss").add() content.name = self._applicationDataPrefixUri content.type = SyncState_UPDATE content.seqno.seq = self._sequenceNo content.seqno.session = self._sessionNo self._update(getattr(tempContent, "ss")) self._onInitialized() name = Name(self._applicationBroadcastPrefix) name.append(self._digestTree.getRoot()) retryInterest = Interest(name) retryInterest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest(retryInterest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _onData(self, interest, data): """ Process Sync Data. """ if not self._enabled: # Ignore callbacks after the application calls shutdown(). return logging.getLogger(__name__).info( "Sync ContentObject received in callback") logging.getLogger(__name__).info( "name: %s", data.getName().toUri()) # TODO: Check if this works in Python 3. tempContent = SyncStateMsg() #pylint: disable=E1103 tempContent.ParseFromString(data.getContent().toBytes()) #pylint: enable=E1103 content = getattr(tempContent, "ss") if self._digestTree.getRoot() == "00": isRecovery = True #processing initial sync data self._initialOndata(content) else: self._update(content) if (interest.getName().size() == self._applicationBroadcastPrefix.size() + 2): # Assume this is a recovery interest. isRecovery = True else: isRecovery = False # Send the interests to fetch the application data. syncStates = [] for i in range(len(content)): syncState = content[i] # Only report UPDATE sync states. if syncState.type == SyncState_UPDATE: if len(syncState.application_info) > 0: applicationInfo = Blob(syncState.application_info, True) else: applicationInfo = Blob() syncStates.append(self.SyncState( syncState.name, syncState.seqno.session, syncState.seqno.seq, applicationInfo)) try: self._onReceivedSyncState(syncStates, isRecovery) except: logging.exception("Error in onReceivedSyncState") name = Name(self._applicationBroadcastPrefix) name.append(self._digestTree.getRoot()) syncInterest = Interest(name) syncInterest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest(syncInterest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _onData(self, interest, data): """ Process Sync Data. """ if not self._enabled: # Ignore callbacks after the application calls shutdown(). return logging.getLogger(__name__).info( "Sync ContentObject received in callback") logging.getLogger(__name__).info("name: %s", data.getName().toUri()) # TODO: Check if this works in Python 3. tempContent = SyncStateMsg() #pylint: disable=E1103 tempContent.ParseFromString(data.getContent().toBytes()) #pylint: enable=E1103 content = getattr(tempContent, "ss") if self._digestTree.getRoot() == "00": isRecovery = True #processing initial sync data self._initialOndata(content) else: self._update(content) if (interest.getName().size() == self._applicationBroadcastPrefix.size() + 2): # Assume this is a recovery interest. isRecovery = True else: isRecovery = False # Send the interests to fetch the application data. syncStates = [] for i in range(len(content)): syncState = content[i] # Only report UPDATE sync states. if syncState.type == SyncState_UPDATE: if len(syncState.application_info) > 0: applicationInfo = Blob(syncState.application_info, True) else: applicationInfo = Blob() syncStates.append( self.SyncState(syncState.name, syncState.seqno.session, syncState.seqno.seq, applicationInfo)) try: self._onReceivedSyncState(syncStates, isRecovery) except: logging.exception("Error in onReceivedSyncState") name = Name(self._applicationBroadcastPrefix) name.append(self._digestTree.getRoot()) syncInterest = Interest(name) syncInterest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest(syncInterest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _sendRecovery(self, syncDigest): """ Send Recovery Interest. """ logging.getLogger(__name__).info("unknown digest: ") name = Name(self._applicationBroadcastPrefix) name.append("recovery").append(syncDigest) interest = Interest(name) interest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest(interest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Recovery Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _syncTimeout(self, interest): """ Sync interest time out. If the interest is the static one send again. """ if not self._enabled: # Ignore callbacks after the application calls shutdown(). return logging.getLogger(__name__).info("Sync Interest time out.") logging.getLogger(__name__).info("Sync Interest name: %s", interest.getName().toUri()) component = interest.getName().get(4).toEscapedString() if component == self._digestTree.getRoot(): name = Name(interest.getName()) retryInterest = Interest(interest.getName()) retryInterest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest(retryInterest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _syncTimeout(self, interest): """ Sync interest time out. If the interest is the static one send again. """ if not self._enabled: # Ignore callbacks after the application calls shutdown(). return logging.getLogger(__name__).info("Sync Interest time out.") logging.getLogger(__name__).info( "Sync Interest name: %s", interest.getName().toUri()) component = interest.getName().get(4).toEscapedString() if component == self._digestTree.getRoot(): name = Name(interest.getName()) retryInterest = Interest(interest.getName()) retryInterest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest( retryInterest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _initialTimeOut(self, interest): """ Initial sync interest timeout, which means there are no other publishers yet. """ if not self._enabled: # Ignore callbacks after the application calls shutdown(). return logging.getLogger(__name__).info("initial sync timeout") logging.getLogger(__name__).info("no other people") self._sequenceNo += 1 if self._sequenceNo != 0: # Since there were no other users, we expect sequence no 0. raise RuntimeError( "ChronoSync: sequenceNo_ is not the expected value of 0 for first use." ) tempContent = SyncStateMsg() content = getattr(tempContent, "ss").add() content.name = self._applicationDataPrefixUri content.type = SyncState_UPDATE content.seqno.seq = self._sequenceNo content.seqno.session = self._sessionNo self._update(getattr(tempContent, "ss")) try: self._onInitialized() except: logging.exception("Error in onInitialized") name = Name(self._applicationBroadcastPrefix) name.append(self._digestTree.getRoot()) retryInterest = Interest(name) retryInterest.setInterestLifetimeMilliseconds(self._syncLifetime) self._face.expressInterest(retryInterest, self._onData, self._syncTimeout) logging.getLogger(__name__).info("Syncinterest expressed:") logging.getLogger(__name__).info("%s", name.toUri())
def _checkSignatureMatch(self, signatureName, objectName, rule, failureReason): """ Once a rule is found to match data or a signed interest, the name in the KeyLocator must satisfy the condition in the 'checker' section of the rule, else the data or interest is rejected. :param Name signatureName: The certificate name from the KeyLocator . :param Name objectName: The name of the data packet or interest. In the case of signed interests, this excludes the timestamp, nonce and signature components. :param BoostInfoTree rule: The rule from the configuration file that matches the data or interest. :param Array<str> failureReason: If verification fails, set failureReason[0] to the failure reason string. :return: True if matches. :rtype: bool """ checker = rule['checker'][0] checkerType = checker['type'][0].getValue() if checkerType == 'fixed-signer': signerInfo = checker['signer'][0] signerType = signerInfo['type'][0].getValue() if signerType == 'file': cert = self._lookupCertificate( signerInfo['file-name'][0].getValue(), True) if cert is None: failureReason[0] = ( "Can't find fixed-signer certificate file: " + signerInfo['file-name'][0].getValue()) return False elif signerType == 'base64': cert = self._lookupCertificate( signerInfo['base64-string'][0].getValue(), False) if cert is None: failureReason[0] = ( "Can't find fixed-signer certificate base64: " + signerInfo['base64-string'][0].getValue()) return False else: failureReason[0] = ("Unrecognized fixed-signer signerType: " + signerType) return False if cert.getName().equals(signatureName): return True else: failureReason[0] = ("fixed-signer cert name \"" + cert.getName().toUri() + "\" does not equal signatureName \"" + signatureName.toUri() + "\"") return False elif checkerType == 'hierarchical': # this just means the data/interest name has the signing identity as a prefix # that means everything before 'ksk-?' in the key name identityRegex = '^([^<KEY>]*)<KEY>(<>*)<ksk-.+><ID-CERT>' identityMatch = NdnRegexMatcher.match(identityRegex, signatureName) if identityMatch is not None: identityPrefix = Name(identityMatch.group(1)).append( Name(identityMatch.group(2))) if self._matchesRelation(objectName, identityPrefix, 'is-prefix-of'): return True else: failureReason[0] = ("The hierarchical objectName \"" + objectName.toUri() + "\" is not a prefix of \"" + identityPrefix.toUri() + "\"") return False else: failureReason[0] = ("The hierarchical identityRegex \"" + identityRegex + "\" does not match signatureName \"" + signatureName.toUri() + "\"") return False elif checkerType == 'customized': keyLocatorInfo = checker['key-locator'][0] # not checking type - only name is supported # is this a simple relation? try: relationType = keyLocatorInfo['relation'][0].getValue() except KeyError: pass else: matchName = Name(keyLocatorInfo['name'][0].getValue()) if self._matchesRelation(signatureName, matchName, relationType): return True else: failureReason[0] = ("The custom signatureName \"" + signatureName.toUri() + "\" does not match matchName \"" + matchName.toUri() + "\" using relation " + relationType) return False # is this a simple regex? try: keyRegex = keyLocatorInfo['regex'][0].getValue() except KeyError: pass else: if NdnRegexMatcher.match(keyRegex, signatureName) is not None: return True else: failureReason[0] = ( "The custom signatureName \"" + signatureName.toUri() + "\" does not regex match simpleKeyRegex \"" + keyRegex + "\"") return False # is this a hyper-relation? try: hyperRelation = keyLocatorInfo['hyper-relation'][0] except KeyError: pass else: keyRegex = hyperRelation.getFirstValue('k-regex') keyExpansion = hyperRelation.getFirstValue('k-expand') nameRegex = hyperRelation.getFirstValue('p-regex') nameExpansion = hyperRelation.getFirstValue('p-expand') relationType = hyperRelation.getFirstValue('h-relation') if (keyRegex != None and keyExpansion != None and nameRegex != None and nameExpansion != None and relationType != None): keyMatch = NdnRegexMatcher.match(keyRegex, signatureName) if keyMatch == None: failureReason[0] = ( "The custom hyper-relation signatureName \"" + signatureName.toUri() + "\" does not match the keyRegex \"" + keyRegex + "\"") return False keyMatchPrefix = keyMatch.expand(keyExpansion) nameMatch = NdnRegexMatcher.match(nameRegex, objectName) if nameMatch == None: failureReason[0] = ( "The custom hyper-relation objectName \"" + objectName.toUri() + "\" does not match the nameRegex \"" + nameRegex + "\"") return False nameMatchStr = nameMatch.expand(nameExpansion) if self._matchesRelation(Name(nameMatchStr), Name(keyMatchPrefix), relationType): return True else: failureReason[0] = ( "The custom hyper-relation nameMatch \"" + nameMatchStr + "\" does not match the keyMatchPrefix \"" + keyMatchPrefix + "\" using relation " + relationType) return False failureReason[0] = "Unrecognized checkerType: " + checkerType return False
def _fetchKdk(self, contentKey, kdkPrefix, ckData, onError, nTriesLeft): """ :param DecryptorV2.ContentKey contentKey: :param Name kdkPrefix: :param Data ckData: :param onError: On error, this calls onError(errorCode, message) :type onError: function object :param int nTriesLeft: """ # <kdk-prefix>/KDK/<kdk-id> /ENCRYPTED-BY /<credential-identity>/KEY/<key-id> # \ / \ / # ----------- ------------- --------------- --------------- # \/ \/ # from the CK data from configuration kdkName = Name(kdkPrefix) kdkName.append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY).append( self._credentialsKey.getName()) logging.getLogger(__name__).info("Fetching KDK " + kdkName.toUri()) def onData(kdkInterest, kdkData): try: contentKey.pendingInterest = 0 # TODO: Verify that the key is legitimate. isOk = self._decryptAndImportKdk(kdkData, onError) if not isOk: return # This way of getting the kdkKeyName is a bit hacky. kdkKeyName = kdkPrefix.getPrefix(-2).append("KEY").append( kdkPrefix.get(-1)) self._decryptCkAndProcessPendingDecrypts( contentKey, ckData, kdkKeyName, onError) except Exception as ex: onError(EncryptError.ErrorCode.General, "Error in fetchCk onData: " + repr(ex)) def onTimeout(interest): contentKey.pendingInterest = 0 if nTriesLeft > 1: self._fetchKdk(contentKey, kdkPrefix, ckData, onError, nTriesLeft - 1) else: onError( EncryptError.ErrorCode.KdkRetrievalTimeout, "Retrieval of KDK [" + interest.getName().toUri() + "] timed out") def onNetworkNack(interest, networkNack): contentKey.pendingInterest = 0 onError( EncryptError.ErrorCode.KdkRetrievalFailure, "Retrieval of KDK [" + interest.getName().toUri() + "] failed. Got NACK (" + str(networkNack.getReason()) + ")") try: contentKey.pendingInterest = self._face.expressInterest( Interest(kdkName).setMustBeFresh(True).setCanBePrefix(False), onData, onTimeout, onNetworkNack) except Exception as ex: onError(EncryptError.ErrorCode.General, "expressInterest error: " + repr(ex))
class PibKeyContainer(object): """ Create a PibKeyContainer for an identity with identityName. This constructor should only be called by PibIdentityImpl. :param Name identityName: The name of the identity, which is copied. :param PibImpl pibImpl: The PIB backend implementation. """ def __init__(self, identityName, pibImpl): # Cache of loaded PibKeyImpl objects. Name => PibKeyImpl. self._keys = {} # Copy the Name. self._identityName = Name(identityName) self._pibImpl = pibImpl if pibImpl == None: raise ValueError("The pibImpl is None") self._keyNames = self._pibImpl.getKeysOfIdentity(identityName) def size(self): """ Get the number of keys in the container. :return: The number of keys. :rtype: int """ return len(self._keyNames) def add(self, key, keyName): """ Add a key with name keyName into the container. If a key with the same name already exists, this replaces it. :param key: The buffer of encoded key bytes. :type key: an array which implements the buffer protocol :param Name keyName: The name of the key, which is copied. :return: The PibKey object. :rtype: PibKey :raises ValueError: If the name of the key does not match the identity name. """ if not self._identityName.equals(PibKey.extractIdentityFromKeyName(keyName)): raise ValueError("The key name `" + keyName.toUri() + "` does not match the identity name `" + self._identityName.toUri() + "`") # Copy the Name. self._keyNames.add(Name(keyName)) self._keys[Name(keyName)] = PibKeyImpl(keyName, key, self._pibImpl) return self.get(keyName) def remove(self, keyName): """ Remove the key with name keyName from the container, and its related certificates. If the key does not exist, do nothing. :param Name keyName: The name of the key. :raises ValueError: If keyName does not match the identity name. """ if not self._identityName.equals(PibKey.extractIdentityFromKeyName(keyName)): raise ValueError("Key name `" + keyName.toUri() + "` does not match identity `" + self._identityName.toUri() + "`") try: self._keyNames.remove(keyName) except KeyError: # Do nothing if it doesn't exist. pass try: del self._keys[keyName] except KeyError: # Do nothing if it doesn't exist. pass self._pibImpl.removeKey(keyName) def get(self, keyName): """ Get the key with name keyName from the container. :param Name keyName: The name of the key. :return: The PibKey object. :rtype: PibKey :raises ValueError: If keyName does not match the identity name. :raises Pib.Error: If the key does not exist. """ if not self._identityName.equals(PibKey.extractIdentityFromKeyName(keyName)): raise ValueError("Key name `" + keyName.toUri() + "` does not match identity `" + self._identityName.toUri() + "`") try: pibKeyImpl = self._keys[keyName] except KeyError: pibKeyImpl = None if pibKeyImpl == None: pibKeyImpl = PibKeyImpl(keyName, self._pibImpl) # Copy the Name. self._keys[Name(keyName)] = pibKeyImpl return PibKey(pibKeyImpl) def getKeyNames(self): """ Get the names of all the keys in the container. :return: A new list of Name. :rtype: Array<Name> """ result = [] for name in self._keys: # Copy the Name. result.append(Name(name)) return result def isConsistent(self): """ Check if the container is consistent with the backend storage. :return: True if the container is consistent, False otherwise. :rtype: bool :note: This method is heavy-weight and should be used in a debugging mode only. """ return self._keyNames == self._pibImpl.getKeysOfIdentity(self._identityName)
class PibIdentityImpl(object): """ Create a PibIdentityImpl with identityName. :param Name identityName: The name of the identity, which is copied. :param PibImpl pibImpl: The Pib backend implementation. :param bool needInit: If true and the identity does not exist in the pibImpl back end, then create it (and If no default identity has been set, identityName becomes the default). If false, then throw Pib.Error if the identity does not exist in the pibImpl back end. :raises Pib.Error: If the identity does not exist in the pibImpl back end and needInit is false. """ def __init__(self, identityName, pibImpl, needInit): self._defaultKey = None # Copy the Name. self._identityName = Name(identityName) self._keys = PibKeyContainer(identityName, pibImpl) self._pibImpl = pibImpl if pibImpl == None: raise ValueError("The pibImpl is None") if needInit: self._pibImpl.addIdentity(self._identityName) else: if not self._pibImpl.hasIdentity(self._identityName): raise Pib.Error("Identity " + self._identityName.toUri() + " does not exist") def getName(self): """ Get the name of the identity. :return: The name of the identity. You must not change the Name object. If you need to change it then make a copy. :rtype: Name """ return self._identityName def addKey(self, key, keyName): """ Add the key. If a key with the same name already exists, overwrite the key. If no default key for the identity has been set, then set the added key as default for the identity. :param key: The public key bits. This copies the buffer. :type key: an array which implements the buffer protocol :param Name keyName: The name of the key. This copies the name. :return: The PibKey object. :rtype: PibKey """ # BOOST_ASSERT(keys_.isConsistent()) return self._keys.add(key, keyName) def removeKey(self, keyName): """ Remove the key with keyName and its related certificates. If the key does not exist, do nothing. :param Name keyName: The name of the key. """ # BOOST_ASSERT(keys_.isConsistent()) if (self._defaultKey != None and self._defaultKey.getName().equals(keyName)): self._defaultKey = None self._keys.remove(keyName) def getKey(self, keyName): """ Get the key with name keyName. :param Name keyName: The name of the key. :return: The PibKey object. :rtype: PibKey :raises ValueError: If keyName does not match the identity name. :raises Pib.Error: If the key does not exist. """ # BOOST_ASSERT(keys_.isConsistent()) return self._keys.get(keyName) def setDefaultKey(self, keyOrKeyName, arg2=None): """ setDefaultKey has two forms: setDefaultKey(keyName) - Set the key with name keyName as the default key of the identity. setDefaultKey(key, keyName) - Add a key with name keyName and set it as the default key of the identity. :param key: The buffer of encoded key bytes. (This is only used when calling setDefaultKey(key, keyName). ) :type key: an array which implements the buffer protocol :param Name keyName: The name of the key. This copies the name. :return: The PibKey object of the default key. :rtype: PibKey :raises ValueError: If the name of the key does not match the identity name. :raises Pib.Error: If calling setDefaultKey(keyName) and the key does not exist, or if calling setDefaultKey(key, keyName) and a key with the same name already exists. """ # BOOST_ASSERT(keys_.isConsistent()) if isinstance(keyOrKeyName, Name): keyName = keyOrKeyName self._defaultKey = self._keys.get(keyName) self._pibImpl.setDefaultKeyOfIdentity(self._identityName, keyName) return self._defaultKey else: key = keyOrKeyName keyName = arg2 self.addKey(key, keyName) return self.setDefaultKey(keyName) def getDefaultKey(self): """ Get the default key of this Identity. :return: The default PibKey. :rtype: PibKey :raises Pib.Error: If the default key has not been set. """ # BOOST_ASSERT(keys_.isConsistent()) if self._defaultKey == None: self._defaultKey = self._keys.get( self._pibImpl.getDefaultKeyOfIdentity(self._identityName)) # BOOST_ASSERT(pibImpl_->getDefaultKeyOfIdentity(identityName_) == defaultKey_.getName()); return self._defaultKey
def _processSyncInterest(self, index, syncDigest, face): """ Common interest processing, using digest log to find the difference after syncDigest. :return: True if sent a data packet to satisfy the interest, otherwise False. :rtype: bool """ nameList = [] # of str sequenceNoList = [] # of int sessionNoList = [] # of int for j in range(index + 1, len(self._digestLog)): temp = self._digestLog[j].getData() # array of sync_state_pb2.SyncState. for i in range(len(temp)): syncState = temp[i] if syncState.type != SyncState_UPDATE: continue if self._digestTree.find( syncState.name, syncState.seqno.session) != -1: n = -1 for k in range(len(nameList)): if nameList[k] == syncState.name: n = k break if n == -1: nameList.append(syncState.name) sequenceNoList.append(syncState.seqno.seq) sessionNoList.append(syncState.seqno.session) else: sequenceNoList[n] = syncState.seqno.seq sessionNoList[n] = syncState.seqno.session tempContent = SyncStateMsg() for i in range(len(nameList)): content = getattr(tempContent, "ss").add() content.name = nameList[i] content.type = SyncState_UPDATE content.seqno.seq = sequenceNoList[i] content.seqno.session = sessionNoList[i] sent = False if len(getattr(tempContent, "ss")) != 0: name = Name(self._applicationBroadcastPrefix) name.append(syncDigest) # TODO: Check if this works in Python 3. #pylint: disable=E1103 array = tempContent.SerializeToString() #pylint: enable=E1103 data = Data(name) data.setContent(Blob(array)) self._keyChain.sign(data, self._certificateName) try: face.putData(data) except Exception as ex: logging.getLogger(__name__).error( "Error in face.putData: %s", str(ex)) return sent = True logging.getLogger(__name__).info("Sync Data send") logging.getLogger(__name__).info("%s", name.toUri()) return sent
def _processSyncInterest(self, index, syncDigest, face): """ Common interest processing, using digest log to find the difference after syncDigest. :return: True if sent a data packet to satisfy the interest, otherwise False. :rtype: bool """ nameList = [] # of str sequenceNoList = [] # of int sessionNoList = [] # of int for j in range(index + 1, len(self._digestLog)): temp = self._digestLog[j].getData( ) # array of sync_state_pb2.SyncState. for i in range(len(temp)): syncState = temp[i] if syncState.type != SyncState_UPDATE: continue if self._digestTree.find(syncState.name, syncState.seqno.session) != -1: n = -1 for k in range(len(nameList)): if nameList[k] == syncState.name: n = k break if n == -1: nameList.append(syncState.name) sequenceNoList.append(syncState.seqno.seq) sessionNoList.append(syncState.seqno.session) else: sequenceNoList[n] = syncState.seqno.seq sessionNoList[n] = syncState.seqno.session tempContent = SyncStateMsg() for i in range(len(nameList)): content = getattr(tempContent, "ss").add() content.name = nameList[i] content.type = SyncState_UPDATE content.seqno.seq = sequenceNoList[i] content.seqno.session = sessionNoList[i] sent = False if len(getattr(tempContent, "ss")) != 0: name = Name(self._applicationBroadcastPrefix) name.append(syncDigest) # TODO: Check if this works in Python 3. #pylint: disable=E1103 array = tempContent.SerializeToString() #pylint: enable=E1103 data = Data(name) data.setContent(Blob(array)) self._keyChain.sign(data, self._certificateName) try: face.putData(data) except Exception as ex: logging.getLogger(__name__).error("Error in face.putData: %s", str(ex)) return sent = True logging.getLogger(__name__).info("Sync Data send") logging.getLogger(__name__).info("%s", name.toUri()) return sent
def _checkSignatureMatch(self, signatureName, objectName, rule, failureReason): """ Once a rule is found to match data or a signed interest, the name in the KeyLocator must satisfy the condition in the 'checker' section of the rule, else the data or interest is rejected. :param Name signatureName: The certificate name from the KeyLocator . :param Name objectName: The name of the data packet or interest. In the case of signed interests, this excludes the timestamp, nonce and signature components. :param BoostInfoTree rule: The rule from the configuration file that matches the data or interest. :param Array<str> failureReason: If verification fails, set failureReason[0] to the failure reason string. :return: True if matches. :rtype: bool """ checker = rule['checker'][0] checkerType = checker['type'][0].getValue() if checkerType == 'fixed-signer': signerInfo = checker['signer'][0] signerType = signerInfo['type'][0].getValue() if signerType == 'file': if self._isSecurityV1: cert = self._lookupCertificate( signerInfo['file-name'][0].getValue(), True) if cert is None: failureReason[0] = ( "Can't find fixed-signer certificate file: " + signerInfo['file-name'][0].getValue()) return False else: cert = self._lookupCertificateV2( signerInfo['file-name'][0].getValue(), True) if cert is None: failureReason[0] = ( "Can't find fixed-signer certificate file: " + signerInfo['file-name'][0].getValue()) return False elif signerType == 'base64': if self._isSecurityV1: cert = self._lookupCertificate( signerInfo['base64-string'][0].getValue(), False) if cert is None: failureReason[0] = ( "Can't find fixed-signer certificate base64: " + signerInfo['base64-string'][0].getValue()) return False else: cert = self._lookupCertificateV2( signerInfo['base64-string'][0].getValue(), False) if cert is None: failureReason[0] = ( "Can't find fixed-signer certificate base64: " + signerInfo['base64-string'][0].getValue()) return False else: failureReason[0] = ("Unrecognized fixed-signer signerType: " + signerType) return False if cert.getName().equals(signatureName): return True else: failureReason[0] = ("fixed-signer cert name \"" + cert.getName().toUri() + "\" does not equal signatureName \"" + signatureName.toUri() + "\"") return False elif checkerType == 'hierarchical': # this just means the data/interest name has the signing identity as a prefix # that means everything before 'ksk-?' in the key name identityRegex = '^([^<KEY>]*)<KEY>(<>*)<ksk-.+><ID-CERT>' identityMatch = NdnRegexTopMatcher(identityRegex) if identityMatch.match(signatureName): identityPrefix = identityMatch.expand("\\1").append( identityMatch.expand("\\2")) if self._matchesRelation(objectName, identityPrefix, 'is-prefix-of'): return True else: failureReason[0] = ("The hierarchical objectName \"" + objectName.toUri() + "\" is not a prefix of \"" + identityPrefix.toUri() + "\"") return False if not self._isSecurityV1: # Check for a security v2 key name. identityRegex2 = "^(<>*)<KEY><>$" identityMatch2 = NdnRegexTopMatcher(identityRegex2) if identityMatch2.match(signatureName): identityPrefix = identityMatch2.expand("\\1") if self._matchesRelation(objectName, identityPrefix, 'is-prefix-of'): return True else: failureReason[0] = ("The hierarchical objectName \"" + objectName.toUri() + "\" is not a prefix of \"" + identityPrefix.toUri() + "\"") return False failureReason[0] = ("The hierarchical identityRegex \"" + identityRegex + "\" does not match signatureName \"" + signatureName.toUri() + "\"") return False elif checkerType == 'customized': keyLocatorInfo = checker['key-locator'][0] # not checking type - only name is supported # is this a simple relation? relationType = keyLocatorInfo.getFirstValue("relation") if relationType != None: matchName = Name(keyLocatorInfo['name'][0].getValue()) if self._matchesRelation(signatureName, matchName, relationType): return True else: failureReason[0] = ("The custom signatureName \"" + signatureName.toUri() + "\" does not match matchName \"" + matchName.toUri() + "\" using relation " + relationType) return False # Is this a simple regex? simpleKeyRegex = keyLocatorInfo.getFirstValue("regex") if simpleKeyRegex != None: if NdnRegexTopMatcher(simpleKeyRegex).match(signatureName): return True else: failureReason[0] = ("The custom signatureName \"" + signatureName.toUri() + "\" does not regex match simpleKeyRegex \"" + simpleKeyRegex + "\"") return False # is this a hyper-relation? hyperRelationList = keyLocatorInfo["hyper-relation"] if len(hyperRelationList) >= 1: hyperRelation = hyperRelationList[0] keyRegex = hyperRelation.getFirstValue('k-regex') keyExpansion = hyperRelation.getFirstValue('k-expand') nameRegex = hyperRelation.getFirstValue('p-regex') nameExpansion = hyperRelation.getFirstValue('p-expand') relationType = hyperRelation.getFirstValue('h-relation') if (keyRegex != None and keyExpansion != None and nameRegex != None and nameExpansion != None and relationType != None): keyMatch = NdnRegexTopMatcher(keyRegex) if not keyMatch.match(signatureName): failureReason[0] = ( "The custom hyper-relation signatureName \"" + signatureName.toUri() + "\" does not match the keyRegex \"" + keyRegex + "\"") return False keyMatchPrefix = keyMatch.expand(keyExpansion) nameMatch = NdnRegexTopMatcher(nameRegex) if not nameMatch.match(objectName): failureReason[0] = ( "The custom hyper-relation objectName \"" + objectName.toUri() + "\" does not match the nameRegex \"" + nameRegex + "\"") return False nameMatchExpansion = nameMatch.expand(nameExpansion) if self._matchesRelation( nameMatchExpansion, keyMatchPrefix, relationType): return True else: failureReason[0] = ( "The custom hyper-relation nameMatch \"" + nameMatchExpansion.toUri() + "\" does not match the keyMatchPrefix \"" + keyMatchPrefix.toUri() + "\" using relation " + relationType) return False failureReason[0] = "Unrecognized checkerType: " + checkerType return False
class PibKeyContainer(object): """ Create a PibKeyContainer for an identity with identityName. This constructor should only be called by PibIdentityImpl. :param Name identityName: The name of the identity, which is copied. :param PibImpl pibImpl: The PIB backend implementation. """ def __init__(self, identityName, pibImpl): # Cache of loaded PibKeyImpl objects. Name => PibKeyImpl. self._keys = {} # Copy the Name. self._identityName = Name(identityName) self._pibImpl = pibImpl if pibImpl == None: raise ValueError("The pibImpl is None") self._keyNames = self._pibImpl.getKeysOfIdentity(identityName) def size(self): """ Get the number of keys in the container. :return: The number of keys. :rtype: int """ return len(self._keyNames) def add(self, key, keyName): """ Add a key with name keyName into the container. If a key with the same name already exists, this replaces it. :param key: The buffer of encoded key bytes. :type key: an array which implements the buffer protocol :param Name keyName: The name of the key, which is copied. :return: The PibKey object. :rtype: PibKey :raises ValueError: If the name of the key does not match the identity name. """ if not self._identityName.equals( PibKey.extractIdentityFromKeyName(keyName)): raise ValueError("The key name `" + keyName.toUri() + "` does not match the identity name `" + self._identityName.toUri() + "`") # Copy the Name. self._keyNames.add(Name(keyName)) self._keys[Name(keyName)] = PibKeyImpl(keyName, key, self._pibImpl) return self.get(keyName) def remove(self, keyName): """ Remove the key with name keyName from the container, and its related certificates. If the key does not exist, do nothing. :param Name keyName: The name of the key. :raises ValueError: If keyName does not match the identity name. """ if not self._identityName.equals( PibKey.extractIdentityFromKeyName(keyName)): raise ValueError("Key name `" + keyName.toUri() + "` does not match identity `" + self._identityName.toUri() + "`") try: self._keyNames.remove(keyName) except KeyError: # Do nothing if it doesn't exist. pass try: del self._keys[keyName] except KeyError: # Do nothing if it doesn't exist. pass self._pibImpl.removeKey(keyName) def get(self, keyName): """ Get the key with name keyName from the container. :param Name keyName: The name of the key. :return: The PibKey object. :rtype: PibKey :raises ValueError: If keyName does not match the identity name. :raises Pib.Error: If the key does not exist. """ if not self._identityName.equals( PibKey.extractIdentityFromKeyName(keyName)): raise ValueError("Key name `" + keyName.toUri() + "` does not match identity `" + self._identityName.toUri() + "`") try: pibKeyImpl = self._keys[keyName] except KeyError: pibKeyImpl = None if pibKeyImpl == None: pibKeyImpl = PibKeyImpl(keyName, self._pibImpl) # Copy the Name. self._keys[Name(keyName)] = pibKeyImpl return PibKey(pibKeyImpl) def getKeyNames(self): """ Get the names of all the keys in the container. :return: A new list of Name. :rtype: Array<Name> """ result = [] for name in self._keys: # Copy the Name. result.append(Name(name)) return result def isConsistent(self): """ Check if the container is consistent with the backend storage. :return: True if the container is consistent, False otherwise. :rtype: bool :note: This method is heavy-weight and should be used in a debugging mode only. """ return self._keyNames == self._pibImpl.getKeysOfIdentity( self._identityName)
class EncryptorV2(object): """ Create an EncryptorV2 with the given parameters. This uses the face to register to receive Interests for the prefix {ckPrefix}/CK. :param Name accessPrefix: The NAC prefix to fetch the Key Encryption Key (KEK) (e.g., /access/prefix/NAC/data/subset). This copies the Name. :param Name ckPrefix: The prefix under which Content Keys (CK) will be generated. (Each will have a unique version appended.) This copies the Name. :param SigningInfo ckDataSigningInfo: The SigningInfo parameters to sign the Content Key (CK) Data packet. This copies the SigningInfo. :param onError: On failure to create the CK data (failed to fetch the KEK, failed to encrypt with the KEK, etc.), this calls onError(errorCode, message) where errorCode is from EncryptError.ErrorCode and message is a str. The encrypt method will continue trying to retrieve the KEK until success (with each attempt separated by RETRY_DELAY_KEK_RETRIEVAL_MS) and onError may be called multiple times. NOTE: The library will log any exceptions thrown by this callback, but for better error handling the callback should catch and properly handle any exceptions. :type onError: function object :param Validator validator: The validation policy to ensure correctness of the KEK. :param KeyChain keyChain: The KeyChain used to sign Data packets. :param Face face: The Face that will be used to fetch the KEK and publish CK data. """ def __init__(self, accessPrefix, ckPrefix, ckDataSigningInfo, onError, validator, keyChain, face): # Copy the Name. self._accessPrefix = Name(accessPrefix) self._ckPrefix = Name(ckPrefix) self._ckBits = bytearray(EncryptorV2.AES_KEY_SIZE) self._ckDataSigningInfo = SigningInfo(ckDataSigningInfo) self._isKekRetrievalInProgress = False self._onError = onError self._keyChain = keyChain self._face = face self._kekData = None # Storage for encrypted CKs. self._storage = InMemoryStorageRetaining() self._kekPendingInterestId = 0 self.regenerateCk() def onInterest(prefix, interest, face, interestFilterId, filter): data = self._storage.find(interest) if data != None: logging.getLogger(__name__).info("Serving " + data.getName().toUri() + " from InMemoryStorage") try: face.putData(data) except: logging.exception("Error in Face.putData") else: logging.getLogger(__name__).info( "Didn't find CK data for " + interest.getName().toUri()) # TODO: Send NACK? def onRegisterFailed(prefix): logging.getLogger(__name__).error( "Failed to register prefix " + prefix.toUri()) self._ckRegisteredPrefixId = self._face.registerPrefix( Name(ckPrefix).append(EncryptorV2.NAME_COMPONENT_CK), onInterest, onRegisterFailed) def shutdown(self): self._face.unsetInterestFilter(self._ckRegisteredPrefixId) if self._kekPendingInterestId > 0: self._face.removePendingInterest(self._kekPendingInterestId) def encrypt(self, plainData): """ Encrypt the plainData using the existing Content Key (CK) and return a new EncryptedContent. :param plainData: The data to encrypt. :type plainData: Blob or an array which implements the buffer protocol :return: The new EncryptedContent. :rtype: EncryptedContent """ # Generate the initial vector. initialVector = bytearray(EncryptorV2.AES_IV_SIZE) for i in range(len(initialVector)): initialVector[i] = _systemRandom.randint(0, 0xff) params = EncryptParams(EncryptAlgorithmType.AesCbc) params.setInitialVector(Blob(initialVector, False)) encryptedData = AesAlgorithm.encrypt( Blob(self._ckBits, False), Blob(plainData, False), params) content = EncryptedContent() content.setInitialVector(params.getInitialVector()) content.setPayload(encryptedData) content.setKeyLocatorName(self._ckName) return content def regenerateCk(self): """ Create a new Content Key (CK) and publish the corresponding CK Data packet. This uses the onError given to the constructor to report errors. """ # TODO: Ensure that the CK Data packet for the old CK is published when # the CK is updated before the KEK is fetched. self._ckName = Name(self._ckPrefix) self._ckName.append(EncryptorV2.NAME_COMPONENT_CK) # The version is the ID of the CK. self._ckName.appendVersion(int(Common.getNowMilliseconds())) logging.getLogger(__name__).info("Generating new CK: " + self._ckName.toUri()) for i in range(len(self._ckBits)): self._ckBits[i] = _systemRandom.randint(0, 0xff) # One implication: If the CK is updated before the KEK is fetched, then # the KDK for the old CK will not be published. if self._kekData == None: self._retryFetchingKek() else: self._makeAndPublishCkData(self._onError) def size(self): """ Get the number of packets stored in in-memory storage. :return: The number of packets. :rtype: int """ return self._storage.size() def _retryFetchingKek(self): if self._isKekRetrievalInProgress: return logging.getLogger(__name__).info("Retrying fetching of the KEK") self._isKekRetrievalInProgress = True def onReady(): logging.getLogger(__name__).info("The KEK was retrieved and published") self._isKekRetrievalInProgress = False def onError(errorCode, message): logging.getLogger(__name__).info("Failed to retrieve KEK: " + message) self._isKekRetrievalInProgress = False self._onError(errorCode, message) self._fetchKekAndPublishCkData(onReady, onError, EncryptorV2.N_RETRIES) def _fetchKekAndPublishCkData(self, onReady, onError, nTriesLeft): """ Create an Interest for <access-prefix>/KEK to retrieve the <access-prefix>/KEK/<key-id> KEK Data packet, and set _kekData. :param onReady: When the KEK is retrieved and published, this calls onReady(). :type onError: function object :param onError: On failure, this calls onError(errorCode, message) where errorCode is from EncryptError.ErrorCode, and message is an error string. :type onError: function object :param int nTriesLeft: The number of retries for expressInterest timeouts. """ logging.getLogger(__name__).info("Fetching KEK: " + Name(self._accessPrefix).append(EncryptorV2.NAME_COMPONENT_KEK).toUri()) if self._kekPendingInterestId > 0: onError(EncryptError.ErrorCode.General, "fetchKekAndPublishCkData: There is already a _kekPendingInterestId") return def onData(interest, kekData): self._kekPendingInterestId = 0 # TODO: Verify if the key is legitimate. self._kekData = kekData if self._makeAndPublishCkData(onError): onReady() # Otherwise, failure has already been reported. def onTimeout(interest): self._kekPendingInterestId = 0 if nTriesLeft > 1: self._fetchKekAndPublishCkData(onReady, onError, nTriesLeft - 1) else: onError(EncryptError.ErrorCode.KekRetrievalTimeout, "Retrieval of KEK [" + interest.getName().toUri() + "] timed out") logging.getLogger(__name__).info( "Scheduling retry after all timeouts") self._face.callLater( EncryptorV2.RETRY_DELAY_KEK_RETRIEVAL_MS, self._retryFetchingKek) def onNetworkNack(interest, networkNack): self._kekPendingInterestId = 0 if nTriesLeft > 1: def callback(): self._fetchKekAndPublishCkData(onReady, onError, nTriesLeft - 1) self._face.callLater(EncryptorV2.RETRY_DELAY_AFTER_NACK_MS, callback) else: onError(EncryptError.ErrorCode.KekRetrievalFailure, "Retrieval of KEK [" + interest.getName().toUri() + "] failed. Got NACK (" + str(networkNack.getReason()) + ")") logging.getLogger(__name__).info("Scheduling retry from NACK") self._face.callLater( EncryptorV2.RETRY_DELAY_KEK_RETRIEVAL_MS, self._retryFetchingKek) try: self._kekPendingInterestId = self._face.expressInterest( Interest(Name(self._accessPrefix).append(EncryptorV2.NAME_COMPONENT_KEK)) .setMustBeFresh(True) .setCanBePrefix(True), onData, onTimeout, onNetworkNack) except Exception as ex: onError(EncryptError.ErrorCode.General, "expressInterest error: " + repr(ex)) def _makeAndPublishCkData(self, onError): """ Make a CK Data packet for _ckName encrypted by the KEK in _kekData and insert it in the _storage. :param onError: On failure, this calls onError(errorCode, message) where errorCode is from EncryptError.ErrorCode, and message is an error string. :type onError: function object :return: True on success, else False. :rtype: bool """ try: kek = PublicKey(self._kekData.getContent()) content = EncryptedContent() content.setPayload(kek.encrypt (Blob(self._ckBits, False), EncryptAlgorithmType.RsaOaep)) ckData = Data( Name(self._ckName).append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY) .append(self._kekData.getName())) ckData.setContent(content.wireEncodeV2()) # FreshnessPeriod can serve as a soft access control for revoking access. ckData.getMetaInfo().setFreshnessPeriod( EncryptorV2.DEFAULT_CK_FRESHNESS_PERIOD_MS) self._keyChain.sign(ckData, self._ckDataSigningInfo) self._storage.insert(ckData) logging.getLogger(__name__).info("Publishing CK data: " + ckData.getName().toUri()) return True except Exception as ex: onError(EncryptError.ErrorCode.EncryptionFailure, "Failed to encrypt generated CK with KEK " + self._kekData.getName().toUri()) return False NAME_COMPONENT_ENCRYPTED_BY = Name.Component("ENCRYPTED-BY") NAME_COMPONENT_NAC = Name.Component("NAC") NAME_COMPONENT_KEK = Name.Component("KEK") NAME_COMPONENT_KDK = Name.Component("KDK") NAME_COMPONENT_CK = Name.Component("CK") RETRY_DELAY_AFTER_NACK_MS = 1000.0 RETRY_DELAY_KEK_RETRIEVAL_MS = 60 * 1000.0 AES_KEY_SIZE = 32 AES_IV_SIZE = 16 N_RETRIES = 3 DEFAULT_CK_FRESHNESS_PERIOD_MS = 3600 * 1000.0
def _fetchKdk(self, contentKey, kdkPrefix, ckData, onError, nTriesLeft): """ :param DecryptorV2.ContentKey contentKey: :param Name kdkPrefix: :param Data ckData: :param onError: On error, this calls onError(errorCode, message) :type onError: function object :param int nTriesLeft: """ # <kdk-prefix>/KDK/<kdk-id> /ENCRYPTED-BY /<credential-identity>/KEY/<key-id> # \ / \ / # ----------- ------------- --------------- --------------- # \/ \/ # from the CK data from configuration kdkName = Name(kdkPrefix) kdkName.append(EncryptorV2.NAME_COMPONENT_ENCRYPTED_BY).append( self._credentialsKey.getName()) logging.getLogger(__name__).info("Fetching KDK " + kdkName.toUri()) def onData(kdkInterest, kdkData): try: contentKey.pendingInterest = 0 # TODO: Verify that the key is legitimate. isOk = self._decryptAndImportKdk(kdkData, onError) if not isOk: return # This way of getting the kdkKeyName is a bit hacky. kdkKeyName = kdkPrefix.getPrefix(-2).append("KEY").append( kdkPrefix.get(-1)) self._decryptCkAndProcessPendingDecrypts( contentKey, ckData, kdkKeyName, onError) except Exception as ex: onError(EncryptError.ErrorCode.General, "Error in fetchCk onData: " + repr(ex)) def onTimeout(interest): contentKey.pendingInterest = 0 if nTriesLeft > 1: self._fetchKdk( contentKey, kdkPrefix, ckData, onError, nTriesLeft - 1) else: onError(EncryptError.ErrorCode.KdkRetrievalTimeout, "Retrieval of KDK [" + interest.getName().toUri() + "] timed out") def onNetworkNack(interest, networkNack): contentKey.pendingInterest = 0 onError(EncryptError.ErrorCode.KdkRetrievalFailure, "Retrieval of KDK [" + interest.getName().toUri() + "] failed. Got NACK (" + str(networkNack.getReason()) + ")") try: contentKey.pendingInterest = self._face.expressInterest( Interest(kdkName).setMustBeFresh(True).setCanBePrefix(False), onData, onTimeout, onNetworkNack) except Exception as ex: onError(EncryptError.ErrorCode.General, "expressInterest error: " + repr(ex))
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 PibIdentityImpl(object): """ Create a PibIdentityImpl with identityName. :param Name identityName: The name of the identity, which is copied. :param PibImpl pibImpl: The Pib backend implementation. :param bool needInit: If true and the identity does not exist in the pibImpl back end, then create it (and If no default identity has been set, identityName becomes the default). If false, then throw Pib.Error if the identity does not exist in the pibImpl back end. :raises Pib.Error: If the identity does not exist in the pibImpl back end and needInit is false. """ def __init__(self, identityName, pibImpl, needInit): self._defaultKey = None # Copy the Name. self._identityName = Name(identityName) self._keys = PibKeyContainer(identityName, pibImpl) self._pibImpl = pibImpl if pibImpl == None: raise ValueError("The pibImpl is None") if needInit: self._pibImpl.addIdentity(self._identityName) else: if not self._pibImpl.hasIdentity(self._identityName): raise Pib.Error("Identity " + self._identityName.toUri() + " does not exist") def getName(self): """ Get the name of the identity. :return: The name of the identity. You must not change the Name object. If you need to change it then make a copy. :rtype: Name """ return self._identityName def addKey(self, key, keyName): """ Add the key. If a key with the same name already exists, overwrite the key. If no default key for the identity has been set, then set the added key as default for the identity. :param key: The public key bits. This copies the buffer. :type key: an array which implements the buffer protocol :param Name keyName: The name of the key. This copies the name. :return: The PibKey object. :rtype: PibKey """ # BOOST_ASSERT(keys_.isConsistent()) return self._keys.add(key, keyName) def removeKey(self, keyName): """ Remove the key with keyName and its related certificates. If the key does not exist, do nothing. :param Name keyName: The name of the key. """ # BOOST_ASSERT(keys_.isConsistent()) if (self._defaultKey != None and self._defaultKey.getName().equals(keyName)): self._defaultKey = None self._keys.remove(keyName) def getKey(self, keyName): """ Get the key with name keyName. :param Name keyName: The name of the key. :return: The PibKey object. :rtype: PibKey :raises ValueError: If keyName does not match the identity name. :raises Pib.Error: If the key does not exist. """ # BOOST_ASSERT(keys_.isConsistent()) return self._keys.get(keyName) def setDefaultKey(self, keyOrKeyName, arg2 = None): """ setDefaultKey has two forms: setDefaultKey(keyName) - Set the key with name keyName as the default key of the identity. setDefaultKey(key, keyName) - Add a key with name keyName and set it as the default key of the identity. :param key: The buffer of encoded key bytes. (This is only used when calling setDefaultKey(key, keyName). ) :type key: an array which implements the buffer protocol :param Name keyName: The name of the key. This copies the name. :return: The PibKey object of the default key. :rtype: PibKey :raises ValueError: If the name of the key does not match the identity name. :raises Pib.Error: If calling setDefaultKey(keyName) and the key does not exist, or if calling setDefaultKey(key, keyName) and a key with the same name already exists. """ # BOOST_ASSERT(keys_.isConsistent()) if isinstance(keyOrKeyName, Name): keyName = keyOrKeyName self._defaultKey = self._keys.get(keyName) self._pibImpl.setDefaultKeyOfIdentity(self._identityName, keyName) return self._defaultKey else: key = keyOrKeyName keyName = arg2 self.addKey(key, keyName) return self.setDefaultKey(keyName) def getDefaultKey(self): """ Get the default key of this Identity. :return: The default PibKey. :rtype: PibKey :raises Pib.Error: If the default key has not been set. """ # BOOST_ASSERT(keys_.isConsistent()) if self._defaultKey == None: self._defaultKey = self._keys.get( self._pibImpl.getDefaultKeyOfIdentity(self._identityName)) # BOOST_ASSERT(pibImpl_->getDefaultKeyOfIdentity(identityName_) == defaultKey_.getName()); return self._defaultKey