Ejemplo n.º 1
0
 def _createNewPin(self):
     pin = HmacHelper.generatePin()
     self._hmacHandler = HmacHelper(pin.decode('hex'))
     return pin
Ejemplo n.º 2
0
 def _createNewPin(self):
     pin = HmacHelper.generatePin() 
     self._hmacHandler = HmacHelper(pin.decode('hex'))
     return pin
Ejemplo n.º 3
0
class IotNode(BaseNode):
    """
    This class must be subclassed in order to provide commands or allow user interaction.
    Any setup tasks needed by the user may be place in __init__ or setupComplete as needed.
    """
    def __init__(self):
        """
        Initialize the network and security settings for the node
        :param str configFilename: The path to the configuration file generated by ndn-config
        """
        super(IotNode, self).__init__()
        self.deviceSuffix = None

        self._commands = []

        self.deviceSerial = self.getSerial()

        self.prefix = Name(self._hubPrefix).append(self.deviceSerial)

        self._certificateTimeouts = 0

###
# Startup and shutdown
###

    def _createNewPin(self):
        pin = HmacHelper.generatePin()
        self._hmacHandler = HmacHelper(pin.decode('hex'))
        return pin

    def beforeLoopStart(self):
        print("Serial: {}\nConfiguration PIN: {}".format(
            self.deviceSerial, self._createNewPin()))
        self.face.registerPrefix(self._hubPrefix,
                                 self._onConfigurationReceived,
                                 self._onConfigurationRegistrationFailure)

#####
# Pre-configuration flow
####

    def _extractNameFromField(self, protobufField):
        return Name('/'.join(protobufField.components))

    def _onConfigurationReceived(self, prefix, interest, transport, prefixId):
        # the interest we get here is signed by HMAC, let's verify it
        self.tempPrefixId = prefixId  # didn't get it from register because of the event loop
        interestName = interest.getName()
        replyData = Data(interestName)
        if len(interestName) == len(prefix):
            # this is a discovery request. Check the exclude to see if we should
            # return our serial
            serial = self.getSerial()
            serialComponent = Name.Component(serial)
            if not interest.getExclude().matches(serialComponent):
                replyData.setContent(serial)
                self.sendData(replyData, transport,
                              False)  # no point in signing
        elif (self._hmacHandler.verifyInterest(interest)):
            # we have a match! decode the network parameters
            configComponent = interest.getName()[len(prefix) + 1]
            replyData.setContent('200')
            self._hmacHandler.signData(replyData, keyName=self.prefix)
            transport.send(replyData.wireEncode().buf())

            environmentConfig = DeviceConfigurationMessage()
            ProtobufTlv.decode(environmentConfig, configComponent.getValue())
            networkPrefix = self._extractNameFromField(
                environmentConfig.configuration.networkPrefix)
            controllerName = self._extractNameFromField(
                environmentConfig.configuration.controllerName)
            controllerName = Name(networkPrefix).append(controllerName)

            self._policyManager.setEnvironmentPrefix(networkPrefix)
            self._policyManager.setTrustRootIdentity(controllerName)

            self.deviceSuffix = self._extractNameFromField(
                environmentConfig.configuration.deviceSuffix)

            self._configureIdentity = Name(networkPrefix).append(
                self.deviceSuffix)
            self._sendCertificateRequest(self._configureIdentity)
        #else, ignore

    def _onConfigurationRegistrationFailure(self, prefix):
        #this is so bad... try a few times
        if self._registrationFailures < 5:
            self._registrationFailures += 1
            self.log.warn("Could not register {}, retry: {}/{}".format(
                prefix.toUri(), self._registrationFailures, 5))
            self.face.registerPrefix(self._hubPrefix,
                                     self._onConfigurationReceived,
                                     self._onConfigurationRegistrationFailure)
        else:
            self.log.critical("Could not register device prefix, ABORTING")
            self._isStopped = True

###
# Certificate signing requests
# On startup, if we don't have a certificate signed by the controller, we request one.
###

    def _sendCertificateRequest(self, keyIdentity):
        """
        We compose a command interest with our public key info so the controller
        can sign us a certificate that can be used with other nodes in the network.
        """

        #TODO: GENERATE A NEW PUBLIC/PRIVATE PAIR INSTEAD OF COPYING
        makeKey = False
        try:
            defaultKey = self._identityStorage.getDefaultKeyNameForIdentity(
                keyIdentity)
            newKeyName = defaultKey
        except SecurityException:
            defaultIdentity = self._keyChain.getDefaultIdentity()
            defaultKey = self._identityStorage.getDefaultKeyNameForIdentity(
                defaultIdentity)
            newKeyName = self._identityStorage.getNewKeyName(keyIdentity, True)
            makeKey = True

        self.log.debug("Found key: " + defaultKey.toUri() + " renaming as: " +
                       newKeyName.toUri())

        keyType = self._identityStorage.getKeyType(defaultKey)
        keyDer = self._identityStorage.getKey(defaultKey)

        if makeKey:
            try:
                privateDer = self._identityManager.getPrivateKey(defaultKey)
            except SecurityException:
                # XXX: is recovery impossible?
                pass
            else:
                try:
                    self._identityStorage.addKey(newKeyName, keyType, keyDer)
                    self._identityManager.addPrivateKey(newKeyName, privateDer)
                except SecurityException:
                    # TODO: key shouldn't exist...
                    pass

        message = CertificateRequestMessage()
        message.command.keyType = keyType
        message.command.keyBits = keyDer.toRawStr()

        for component in range(newKeyName.size()):
            message.command.keyName.components.append(
                newKeyName.get(component).toEscapedString())

        paramComponent = ProtobufTlv.encode(message)

        interestName = Name(self._policyManager.getTrustRootIdentity()).append(
            "certificateRequest").append(paramComponent)
        interest = Interest(interestName)
        interest.setInterestLifetimeMilliseconds(
            10000)  # takes a tick to verify and sign
        self._hmacHandler.signInterest(interest, keyName=self.prefix)

        self.log.info("Sending certificate request to controller")
        self.log.debug("Certificate request: " + interest.getName().toUri())
        self.face.expressInterest(interest, self._onCertificateReceived,
                                  self._onCertificateTimeout)

    def _onCertificateTimeout(self, interest):
        #give up?
        self.log.warn("Timed out trying to get certificate")
        if self._certificateTimeouts > 5:
            self.log.critical("Trust root cannot be reached, exiting")
            self._isStopped = True
        else:
            self._certificateTimeouts += 1
            self.loop.call_soon(self._sendCertificateRequest,
                                self._configureIdentity)
        pass

    def _processValidCertificate(self, data):
        # unpack the cert from the HMAC signed packet and verify
        try:
            newCert = IdentityCertificate()
            newCert.wireDecode(data.getContent())
            self.log.info("Received certificate from controller")
            self.log.debug(str(newCert))

            # NOTE: we download and install the root certificate without verifying it (!)
            # otherwise our policy manager will reject it.
            # we may need a static method on KeyChain to allow verifying before adding

            rootCertName = newCert.getSignature().getKeyLocator().getKeyName()
            # update trust rules so we trust the controller
            self._policyManager.setDeviceIdentity(self._configureIdentity)
            self._policyManager.updateTrustRules()

            def onRootCertificateDownload(interest, data):
                try:
                    self._identityStorage.addCertificate(data)
                except SecurityException:
                    # already exists
                    pass
                self._keyChain.verifyData(newCert,
                                          self._finalizeCertificateDownload,
                                          self._certificateValidationFailed)

            def onRootCertificateTimeout(interest):
                # TODO: limit number of tries, then revert trust root + network prefix
                # reset salt, create new Hmac key
                self.face.expressInterest(rootCertName,
                                          onRootCertificateDownload,
                                          onRootCertificateTimeout)

            self.face.expressInterest(rootCertName, onRootCertificateDownload,
                                      onRootCertificateTimeout)

        except Exception as e:
            self.log.exception("Could not import new certificate",
                               exc_info=True)

    def _finalizeCertificateDownload(self, newCert):
        try:
            self._identityManager.addCertificate(newCert)
        except SecurityException:
            pass  # can't tell existing certificat from another error
        self._identityManager.setDefaultCertificateForKey(newCert)

        # unregister localhop prefix, register new prefix, change identity
        self.prefix = self._configureIdentity
        self._policyManager.setDeviceIdentity(self.prefix)

        self.face.setCommandCertificateName(self.getDefaultCertificateName())

        self.face.removeRegisteredPrefix(self.tempPrefixId)
        self.face.registerPrefix(self.prefix, self._onCommandReceived,
                                 self.onRegisterFailed)

        self.loop.call_later(5, self._updateCapabilities)

    def _certificateValidationFailed(self, data):
        self.log.error("Certificate from controller is invalid!")
        # remove trust info
        self._policyManager.removeTrustRules()

    def _onCertificateReceived(self, interest, data):
        # if we were successful, the content of this data is an HMAC
        # signed packet containing an encoded cert
        if self._hmacHandler.verifyData(data):
            self._processValidCertificate(data)
        else:
            self._certificateValidationFailed(data)

###
# Device capabilities
# On startup, tell the controller what types of commands are available
##

    def _onCapabilitiesAck(self, interest, data):
        self.log.debug('Received {}'.format(data.getName().toUri()))
        if not self._setupComplete:
            self._setupComplete = True
            self.log.info('Setup complete')
            self.loop.call_soon(self.setupComplete)

    def _onCapabilitiesTimeout(self, interest):
        #try again in 30s
        self.log.info('Timeout waiting for capabilities update')
        self.loop.call_later(30, self._updateCapabilities)

    def _updateCapabilities(self):
        """
        Send the controller a list of our commands.
        """
        fullCommandName = Name(
            self._policyManager.getTrustRootIdentity()).append(
                'updateCapabilities')
        capabilitiesMessage = UpdateCapabilitiesCommandMessage()

        for command in self._commands:
            commandName = Name(self.prefix).append(Name(command.suffix))
            capability = capabilitiesMessage.capabilities.add()
            for i in range(commandName.size()):
                capability.commandPrefix.components.append(
                    str(commandName.get(i).getValue()))

            for kw in command.keywords:
                capability.keywords.append(kw)

            capability.needsSignature = command.isSigned

        encodedCapabilities = ProtobufTlv.encode(capabilitiesMessage)
        fullCommandName.append(encodedCapabilities)
        interest = Interest(fullCommandName)
        interest.setInterestLifetimeMilliseconds(5000)
        self.face.makeCommandInterest(interest)
        signature = self._policyManager._extractSignature(interest)

        self.log.info("Sending capabilities to controller")
        self.face.expressInterest(interest, self._onCapabilitiesAck,
                                  self._onCapabilitiesTimeout)

        # update twice a minute
        self.loop.call_later(30, self._updateCapabilities)

###
# Interest handling
# Verification of and responses to incoming (command) interests
##

    def verificationFailed(self, dataOrInterest):
        """
        Called when verification of a data packet or command interest fails.
        :param pyndn.Data or pyndn.Interest: The packet that could not be verified
        """
        self.log.info("Received invalid" + dataOrInterest.getName().toUri())

    def _makeVerifiedCommandDispatch(self, function, transport):
        def onVerified(interest):
            self.log.info("Verified: " + interest.getName().toUri())
            responseData = function(interest)
            self.sendData(responseData, transport)

        return onVerified

    def unknownCommandResponse(self, interest):
        """
        Called when the node receives an interest where the handler is unknown or unimplemented.
        :return: the Data packet to return in case of unhandled interests. Return None
            to ignore and let the interest timeout or be handled by another node.
        :rtype: pyndn.Data
        """
        responseData = Data(Name(interest.getName()).append("unknown"))
        responseData.setContent("Unknown command name")
        responseData.getMetaInfo().setFreshnessPeriod(1000)  # expire soon

        return responseData

    def _onCommandReceived(self, prefix, interest, transport, prefixId):

        # first off, we shouldn't be here if we have no configured environment
        # just let this interest time out
        if (self._policyManager.getTrustRootIdentity() is None
                or self._policyManager.getEnvironmentPrefix() is None):
            return

        # if this is a cert request, we can serve it from our store (if it exists)
        certData = self._identityStorage.getCertificate(interest.getName())
        if certData is not None:
            self.log.info("Serving certificate request")
            # if we sign the certificate, we lose the controller's signature!
            self.sendData(certData, transport, False)
            return

        # else we must look in our command list to see if this requires verification
        # we dispatch directly or after verification as necessary

        # now we look for the first command that matches in our config
        self.log.debug("Received {}".format(interest.getName().toUri()))

        for command in self._commands:
            fullCommandName = Name(self.prefix).append(Name(command.suffix))
            if fullCommandName.match(interest.getName()):
                dispatchFunc = command.function

                if not command.isSigned:
                    responseData = dispatchFunc(interest)
                    self.sendData(responseData, transport)
                else:
                    try:
                        self._keyChain.verifyInterest(
                            interest,
                            self._makeVerifiedCommandDispatch(
                                dispatchFunc, transport),
                            self.verificationFailed)
                        return
                    except Exception as e:
                        self.log.exception("Exception while verifying command",
                                           exc_info=True)
                        self.verificationFailed(interest)
                        return
        #if we get here, just let it timeout
        return

#####
# Setup methods
####

    def addCommand(self, suffix, dispatchFunc, keywords=[], isSigned=True):
        """
        Install a command. When an interest is expressed for 
        /<node prefix>/<suffix>, dispatchFunc will be called with the interest
         name to get the reply data. 

        :param Name suffix: The command name. This will be appended to the node
            prefix.
        
        :param list keywords: A list of strings that can be used to look up this
            command in the controller's directory.
        
        :param function dispatchFunc: A function that is called when the 
            command is received. It must take an Interest argument and return a 
            Data object or None.

        :param boolean isSigned: Whether the command must be signed. If this is
            True and an unsigned command is received, it will be immediately
            rejected, and dispatchFunc will not be called.
        """
        if (suffix.size() == 0):
            raise RuntimError("Command suffix is empty")
        suffixUri = suffix.toUri()

        for command in self._commands:
            if (suffixUri == command.suffix):
                raise RuntimeError("Command is already registered")

        newCommand = Command(suffix=suffixUri,
                             function=dispatchFunc,
                             keywords=tuple(keywords),
                             isSigned=isSigned)

        self._commands.append(newCommand)

    def removeCommand(self, suffix):
        """
        Unregister a command. Does nothing if the command does not exist.

        :param Name suffix: The command name. 
        """
        suffixUri = suffix.ToUri()
        toRemove = None
        for command in self._commands:
            if (suffixUri == command.suffix):
                toRemove = command
                break
        if toRemove is not None:
            self._commands.remove(toRemove)

    def setupComplete(self):
        """
        Entry point for user-defined behavior. After this is called, the 
        certificates are in place and capabilities have been sent to the 
        controller. The node can now search for other devices, set up
        control logic, etc
        """
        pass
Ejemplo n.º 4
0
class IotNode(BaseNode):
    """
    This class must be subclassed in order to provide commands or allow user interaction.
    Any setup tasks needed by the user may be place in __init__ or setupComplete as needed.
    """
    def __init__(self):
        """
        Initialize the network and security settings for the node
        :param str configFilename: The path to the configuration file generated by ndn-config
        """
        super(IotNode, self).__init__()
        self.deviceSuffix = None

        self._commands = []        
        
        self.deviceSerial = self.getSerial()

        self.prefix = Name(self._hubPrefix).append(self.deviceSerial)

        self._certificateTimeouts = 0

###
# Startup and shutdown
###
    def _createNewPin(self):
        pin = HmacHelper.generatePin() 
        self._hmacHandler = HmacHelper(pin.decode('hex'))
        return pin
        

    def beforeLoopStart(self):
        print("Serial: {}\nConfiguration PIN: {}".format(self.deviceSerial, self._createNewPin()))
        self.face.registerPrefix(self._hubPrefix,
            self._onConfigurationReceived, self._onConfigurationRegistrationFailure)

#####
# Pre-configuration flow
####

    def _extractNameFromField(self, protobufField):
        return Name('/'.join(protobufField.components))

    def _onConfigurationReceived(self, prefix, interest, transport, prefixId):
        # the interest we get here is signed by HMAC, let's verify it
        self.tempPrefixId = prefixId # didn't get it from register because of the event loop
        interestName = interest.getName()
        replyData = Data(interestName)
        if len(interestName) == len(prefix):
            # this is a discovery request. Check the exclude to see if we should
            # return our serial
            serial = self.getSerial()
            serialComponent = Name.Component(serial)
            if not interest.getExclude().matches(serialComponent):
                replyData.setContent(serial)
                self.sendData(replyData, transport, False) # no point in signing
        elif (self._hmacHandler.verifyInterest(interest)):
            # we have a match! decode the network parameters
            configComponent = interest.getName()[len(prefix)+1]
            replyData.setContent('200')
            self._hmacHandler.signData(replyData, keyName=self.prefix)
            transport.send(replyData.wireEncode().buf())

            environmentConfig = DeviceConfigurationMessage()
            ProtobufTlv.decode(environmentConfig, configComponent.getValue()) 
            networkPrefix = self._extractNameFromField(environmentConfig.configuration.networkPrefix)
            controllerName = self._extractNameFromField(environmentConfig.configuration.controllerName)
            controllerName = Name(networkPrefix).append(controllerName)

            self._policyManager.setEnvironmentPrefix(networkPrefix)
            self._policyManager.setTrustRootIdentity(controllerName)

            self.deviceSuffix = self._extractNameFromField(environmentConfig.configuration.deviceSuffix)

            self._configureIdentity = Name(networkPrefix).append(self.deviceSuffix) 
            self._sendCertificateRequest(self._configureIdentity)
        #else, ignore
            
    def _onConfigurationRegistrationFailure(self, prefix):
        #this is so bad... try a few times
        if self._registrationFailures < 5:
            self._registrationFailures += 1
            self.log.warn("Could not register {}, retry: {}/{}".format(prefix.toUri(), self._registrationFailures, 5)) 
            self.face.registerPrefix(self._hubPrefix, self._onConfigurationReceived, 
                self._onConfigurationRegistrationFailure)
        else:
            self.log.critical("Could not register device prefix, ABORTING")
            self._isStopped = True

###
# Certificate signing requests
# On startup, if we don't have a certificate signed by the controller, we request one.
###
       
    def _sendCertificateRequest(self, keyIdentity):
        """
        We compose a command interest with our public key info so the controller
        can sign us a certificate that can be used with other nodes in the network.
        """

        #TODO: GENERATE A NEW PUBLIC/PRIVATE PAIR INSTEAD OF COPYING
        makeKey = False
        try:
            defaultKey = self._identityStorage.getDefaultKeyNameForIdentity(keyIdentity)
            newKeyName = defaultKey
        except SecurityException:
            defaultIdentity = self._keyChain.getDefaultIdentity()
            defaultKey = self._identityStorage.getDefaultKeyNameForIdentity(defaultIdentity)
            newKeyName = self._identityStorage.getNewKeyName(keyIdentity, True)
            makeKey = True
             
        self.log.debug("Found key: " + defaultKey.toUri()+ " renaming as: " + newKeyName.toUri())

        keyType = self._identityStorage.getKeyType(defaultKey)
        keyDer = self._identityStorage.getKey(defaultKey)

        if makeKey:
            try:
                privateDer = self._identityManager.getPrivateKey(defaultKey)
            except SecurityException:
                # XXX: is recovery impossible?
                pass
            else:
                try:
                    self._identityStorage.addKey(newKeyName, keyType, keyDer)
                    self._identityManager.addPrivateKey(newKeyName, privateDer)
                except SecurityException:
                    # TODO: key shouldn't exist...
                    pass

        message = CertificateRequestMessage()
        message.command.keyType = keyType
        message.command.keyBits = keyDer.toRawStr()

        for component in range(newKeyName.size()):
            message.command.keyName.components.append(newKeyName.get(component).toEscapedString())

        paramComponent = ProtobufTlv.encode(message)

        interestName = Name(self._policyManager.getTrustRootIdentity()).append("certificateRequest").append(paramComponent)
        interest = Interest(interestName)
        interest.setInterestLifetimeMilliseconds(10000) # takes a tick to verify and sign
        self._hmacHandler.signInterest(interest, keyName=self.prefix)

        self.log.info("Sending certificate request to controller")
        self.log.debug("Certificate request: "+interest.getName().toUri())
        self.face.expressInterest(interest, self._onCertificateReceived, self._onCertificateTimeout)
   

    def _onCertificateTimeout(self, interest):
        #give up?
        self.log.warn("Timed out trying to get certificate")
        if self._certificateTimeouts > 5:
            self.log.critical("Trust root cannot be reached, exiting")
            self._isStopped = True
        else:
            self._certificateTimeouts += 1
            self.loop.call_soon(self._sendCertificateRequest, self._configureIdentity)
        pass


    def _processValidCertificate(self, data):
        # unpack the cert from the HMAC signed packet and verify
        try:
            newCert = IdentityCertificate()
            newCert.wireDecode(data.getContent())
            self.log.info("Received certificate from controller")
            self.log.debug(str(newCert))

            # NOTE: we download and install the root certificate without verifying it (!)
            # otherwise our policy manager will reject it.
            # we may need a static method on KeyChain to allow verifying before adding
    
            rootCertName = newCert.getSignature().getKeyLocator().getKeyName()
            # update trust rules so we trust the controller
            self._policyManager.setDeviceIdentity(self._configureIdentity) 
            self._policyManager.updateTrustRules()

            def onRootCertificateDownload(interest, data):
                try:
                    self._identityStorage.addCertificate(data)
                except SecurityException:
                    # already exists
                    pass
                self._keyChain.verifyData(newCert, self._finalizeCertificateDownload, self._certificateValidationFailed)

            def onRootCertificateTimeout(interest):
                # TODO: limit number of tries, then revert trust root + network prefix
                # reset salt, create new Hmac key
                self.face.expressInterest(rootCertName, onRootCertificateDownload, onRootCertificateTimeout)

            self.face.expressInterest(rootCertName, onRootCertificateDownload, onRootCertificateTimeout)

        except Exception as e:
            self.log.exception("Could not import new certificate", exc_info=True)
   
    def _finalizeCertificateDownload(self, newCert):
        try:
            self._identityManager.addCertificate(newCert)
        except SecurityException:
            pass # can't tell existing certificat from another error
        self._identityManager.setDefaultCertificateForKey(newCert)

        # unregister localhop prefix, register new prefix, change identity
        self.prefix = self._configureIdentity
        self._policyManager.setDeviceIdentity(self.prefix)

        self.face.setCommandCertificateName(self.getDefaultCertificateName())

        self.face.removeRegisteredPrefix(self.tempPrefixId)
        self.face.registerPrefix(self.prefix, self._onCommandReceived, self.onRegisterFailed)

        self.loop.call_later(5, self._updateCapabilities)

    def _certificateValidationFailed(self, data):
        self.log.error("Certificate from controller is invalid!")
        # remove trust info
        self._policyManager.removeTrustRules()

    def _onCertificateReceived(self, interest, data):
        # if we were successful, the content of this data is an HMAC
        # signed packet containing an encoded cert
        if self._hmacHandler.verifyData(data):
            self._processValidCertificate(data)
        else:
            self._certificateValidationFailed(data)



###
# Device capabilities
# On startup, tell the controller what types of commands are available
##

    def _onCapabilitiesAck(self, interest, data):
        self.log.debug('Received {}'.format(data.getName().toUri()))
        if not self._setupComplete:
            self._setupComplete = True
            self.log.info('Setup complete')
            self.loop.call_soon(self.setupComplete)

    def _onCapabilitiesTimeout(self, interest):
        #try again in 30s
        self.log.info('Timeout waiting for capabilities update')
        self.loop.call_later(30, self._updateCapabilities)

    def _updateCapabilities(self):
        """
        Send the controller a list of our commands.
        """ 
        fullCommandName = Name(self._policyManager.getTrustRootIdentity()
                ).append('updateCapabilities')
        capabilitiesMessage = UpdateCapabilitiesCommandMessage()

        for command in self._commands:
            commandName = Name(self.prefix).append(Name(command.suffix))
            capability = capabilitiesMessage.capabilities.add()
            for i in range(commandName.size()):
                capability.commandPrefix.components.append(
                        str(commandName.get(i).getValue()))

            for kw in command.keywords:
                capability.keywords.append(kw)

            capability.needsSignature = command.isSigned

        encodedCapabilities = ProtobufTlv.encode(capabilitiesMessage)
        fullCommandName.append(encodedCapabilities)
        interest = Interest(fullCommandName)
        interest.setInterestLifetimeMilliseconds(5000)
        self.face.makeCommandInterest(interest)
        signature = self._policyManager._extractSignature(interest)

        self.log.info("Sending capabilities to controller")
        self.face.expressInterest(interest, self._onCapabilitiesAck, self._onCapabilitiesTimeout)

        # update twice a minute
        self.loop.call_later(30, self._updateCapabilities)
     
###
# Interest handling
# Verification of and responses to incoming (command) interests
##
    def verificationFailed(self, dataOrInterest):
        """
        Called when verification of a data packet or command interest fails.
        :param pyndn.Data or pyndn.Interest: The packet that could not be verified
        """
        self.log.info("Received invalid" + dataOrInterest.getName().toUri())

    def _makeVerifiedCommandDispatch(self, function, transport):
        def onVerified(interest):
            self.log.info("Verified: " + interest.getName().toUri())
            responseData = function(interest)
            self.sendData(responseData, transport)
        return onVerified

    def unknownCommandResponse(self, interest):
        """
        Called when the node receives an interest where the handler is unknown or unimplemented.
        :return: the Data packet to return in case of unhandled interests. Return None
            to ignore and let the interest timeout or be handled by another node.
        :rtype: pyndn.Data
        """
        responseData = Data(Name(interest.getName()).append("unknown"))
        responseData.setContent("Unknown command name")
        responseData.getMetaInfo().setFreshnessPeriod(1000) # expire soon

        return responseData

    def _onCommandReceived(self, prefix, interest, transport, prefixId):

        # first off, we shouldn't be here if we have no configured environment
        # just let this interest time out
        if (self._policyManager.getTrustRootIdentity() is None or
                self._policyManager.getEnvironmentPrefix() is None):
            return

        # if this is a cert request, we can serve it from our store (if it exists)
        certData = self._identityStorage.getCertificate(interest.getName())
        if certData is not None:
            self.log.info("Serving certificate request")
            # if we sign the certificate, we lose the controller's signature!
            self.sendData(certData, transport, False)
            return

        # else we must look in our command list to see if this requires verification
        # we dispatch directly or after verification as necessary

        # now we look for the first command that matches in our config
        self.log.debug("Received {}".format(interest.getName().toUri()))
        
        for command in self._commands:
            fullCommandName = Name(self.prefix).append(Name(command.suffix))
            if fullCommandName.match(interest.getName()):
                dispatchFunc = command.function
                
                if not command.isSigned:
                    responseData = dispatchFunc(interest)
                    self.sendData(responseData, transport)
                else:
                    try:
                        self._keyChain.verifyInterest(interest, 
                                self._makeVerifiedCommandDispatch(dispatchFunc, transport),
                                self.verificationFailed)
                        return
                    except Exception as e:
                        self.log.exception("Exception while verifying command", exc_info=True)
                        self.verificationFailed(interest)
                        return
        #if we get here, just let it timeout
        return

#####
# Setup methods
####
    def addCommand(self, suffix, dispatchFunc, keywords=[], isSigned=True):
        """
        Install a command. When an interest is expressed for 
        /<node prefix>/<suffix>, dispatchFunc will be called with the interest
         name to get the reply data. 

        :param Name suffix: The command name. This will be appended to the node
            prefix.
        
        :param list keywords: A list of strings that can be used to look up this
            command in the controller's directory.
        
        :param function dispatchFunc: A function that is called when the 
            command is received. It must take an Interest argument and return a 
            Data object or None.

        :param boolean isSigned: Whether the command must be signed. If this is
            True and an unsigned command is received, it will be immediately
            rejected, and dispatchFunc will not be called.
        """
        if (suffix.size() == 0):
            raise RuntimError("Command suffix is empty")
        suffixUri = suffix.toUri()

        for command in self._commands:
            if (suffixUri == command.suffix):
                raise RuntimeError("Command is already registered")

        newCommand = Command(suffix=suffixUri, function=dispatchFunc, 
                keywords=tuple(keywords), isSigned=isSigned)

        self._commands.append(newCommand)

    def removeCommand(self, suffix):
        """
        Unregister a command. Does nothing if the command does not exist.

        :param Name suffix: The command name. 
        """
        suffixUri = suffix.ToUri()
        toRemove = None
        for command in self._commands:
            if (suffixUri == command.suffix):
                toRemove = command
                break
        if toRemove is not None:
            self._commands.remove(toRemove)


    def setupComplete(self):
        """
        Entry point for user-defined behavior. After this is called, the 
        certificates are in place and capabilities have been sent to the 
        controller. The node can now search for other devices, set up
        control logic, etc
        """
        pass