Exemplo n.º 1
0
    def __init__(self, identityStorage, configFilename=None):
        """
        :param pyndn.IdentityStorage: A class that stores signing identities and certificates.
        :param str configFilename: A configuration file specifying validation rules and network
            name settings.
        """

        # use the default configuration where possible
        # TODO: use environment variable for this, fall back to default
        path = os.path.dirname(__file__)
        templateFilename = os.path.join(path, '.default.conf')
        self._configTemplate = BoostInfoParser()
        self._configTemplate.read(templateFilename)

        if configFilename is None:
            configFilename = templateFilename

        certificateCache = CertificateCache()
        super(IotPolicyManager, self).__init__(configFilename,
                                               certificateCache)
        self._identityStorage = identityStorage

        self.setEnvironmentPrefix(None)
        self.setTrustRootIdentity(None)
        self.setDeviceIdentity(None)
Exemplo n.º 2
0
    def processConfiguration(self, confFile):
        config = BoostInfoParser()
        config.read(confFile)

        # TODO: handle missing configuration, refactor dict representation
        confObj = dict()
        try:
            confObj["identity"] = config["application/identity"][0].value
            confObj["signer"] = config["application/signer"][0].value
        except KeyError as e:
            msg = "Missing key in configuration: " + str(e)
            print msg
            return None
        return confObj
Exemplo n.º 3
0
    def __init__(self, configFileName, certificateCache=None,
            searchDepth=5, graceInterval=3000, keyTimestampTtl=3600000,
            maxTrackedKeys=1000):
        super(ConfigPolicyManager, self).__init__()
        if certificateCache is None:
            self._certificateCache = CertificateCache()
        else:
            self._certificateCache = certificateCache
        self._maxDepth = searchDepth
        self._keyGraceInterval = graceInterval
        self._keyTimestampTtl = keyTimestampTtl
        self._maxTrackedKeys = maxTrackedKeys

        # stores the fixed-signer certificate name associated with validation rules
        # so we don't keep loading from files
        self._fixedCertificateCache = {}

        # stores the timestamps for each public key used in command interests to avoid
        # replay attacks
        # key is public key name, value is last timestamp
        self._keyTimestamps = {}

        self.config = BoostInfoParser()
        self.config.read(configFileName)

        self.requiresVerification = True

        self._refreshManager = TrustAnchorRefreshManager()
        self._loadTrustAnchorCertificates()
Exemplo n.º 4
0
    def reset(self):
        """
        Reset the certificate cache and other fields to the constructor state.
        """
        self._certificateCache.reset()

        # stores the fixed-signer certificate name associated with validation rules
        # so we don't keep loading from files
        self._fixedCertificateCache = {}

        # stores the timestamps for each public key used in command interests to avoid
        # replay attacks
        # key is public key name, value is last timestamp
        self._keyTimestamps = {}

        self.requiresVerification = True

        self.config = BoostInfoParser()
        self._refreshManager = TrustAnchorRefreshManager()
Exemplo n.º 5
0
    def reset(self):
        """
        Reset the certificate cache and other fields to the constructor state.
        """
        self._certificateCache.reset()

        # stores the fixed-signer certificate name associated with validation rules
        # so we don't keep loading from files
        self._fixedCertificateCache = {}

        # stores the timestamps for each public key used in command interests to avoid
        # replay attacks
        # key is public key name, value is last timestamp
        self._keyTimestamps = {}

        self.requiresVerification = True

        self.config = BoostInfoParser()
        self._refreshManager = TrustAnchorRefreshManager()
Exemplo n.º 6
0
    def loadApplications(self, directory=None, override=False):
        if not directory:
            directory = self._applicationDirectory
        if override:
            self._applications.clear()
        if os.path.exists(directory):
            for f in os.listdir(directory):
                fullFileName = os.path.join(directory, f)
                if os.path.isfile(fullFileName) and f.endswith('.conf'):
                    appName = f.rstrip('.conf')
                    if appName in self._applications and not override:
                        print(
                            "loadApplications: " + appName +
                            " already exists, do nothing for configuration file: "
                            + fullFileName)
                    else:
                        self._applications[appName] = {
                            "tree": BoostInfoParser(),
                            "dataPrefix": [],
                            "version": int(time.time())
                        }
                        self._applications[appName]["tree"].read(fullFileName)
                        data = Data(
                            Name(self.prefix).append(appName).append(
                                "_schema").appendVersion(
                                    self._applications[appName]["version"]))
                        data.setContent(
                            str(self._applications[appName]["tree"].getRoot()))
                        self.signData(data)
                        self._memoryContentCache.add(data)
                        try:
                            validatorTree = self._applications[appName][
                                "tree"]["validator"][0]
                            for rule in validatorTree["rule"]:
                                self._applications[appName][
                                    "dataPrefix"].append(rule["id"][0].value)
                        # TODO: don't swallow any general exceptions, we want to catch only KeyError (make sure) here
                        except Exception as e:
                            print(
                                "loadApplications parse configuration file " +
                                fullFileName + " : " + str(e))

        return
Exemplo n.º 7
0
    def __init__(self, identityStorage, configFilename=None):
        """
        :param pyndn.IdentityStorage: A class that stores signing identities and certificates.
        :param str configFilename: A configuration file specifying validation rules and network
            name settings.
        """

        # use the default configuration where possible
        # TODO: use environment variable for this, fall back to default
        path = os.path.dirname(__file__)
        templateFilename = os.path.join(path, '.default.conf')
        self._configTemplate = BoostInfoParser()
        self._configTemplate.read(templateFilename)

        if configFilename is None:
            configFilename = templateFilename

        super(IotPolicyManager, self).__init__(identityStorage, configFilename)
        self.setEnvironmentPrefix(None)
        self.setTrustRootIdentity(None)
        self.setDeviceIdentity(None)
Exemplo n.º 8
0
            self.stop()
        else:
            self.loop.call_soon(self.displayMenu)
            
        

if __name__ == '__main__':
    import os
    import sys

    nArgs = len(sys.argv) - 1
    if nArgs == 0:
        from pyndn.util.boost_info_parser import BoostInfoParser
        fileName = os.path.expanduser('~/.ndn/iot_controller.conf')
    
        config = BoostInfoParser()
        config.read(fileName)
        deviceName = config["device/controllerName"][0].value
        networkName = config["device/environmentPrefix"][0].value
    elif nArgs == 2:
        networkName = sys.argv[1]
        deviceName = sys.argv[2]
    else:
        print('Usage: {} [network-name controller-name]'.format(sys.argv[0]))
        sys.exit(1)

    deviceSuffix = Name(deviceName)
    networkPrefix = Name(networkName)
    n = IotController(deviceSuffix, networkPrefix)
    n.start()
Exemplo n.º 9
0
class IotPolicyManager(ConfigPolicyManager):
    def __init__(self, identityStorage, configFilename=None):
        """
        :param pyndn.IdentityStorage: A class that stores signing identities and certificates.
        :param str configFilename: A configuration file specifying validation rules and network
            name settings.
        """

        # use the default configuration where possible
        # TODO: use environment variable for this, fall back to default
        path = os.path.dirname(__file__)
        templateFilename = os.path.join(path, '.default.conf')
        self._configTemplate = BoostInfoParser()
        self._configTemplate.read(templateFilename)

        if configFilename is None:
            configFilename = templateFilename
        
        certificateCache = CertificateCache()
        super(IotPolicyManager, self).__init__(configFilename, certificateCache)
        self._identityStorage = identityStorage

        self.setEnvironmentPrefix(None)
        self.setTrustRootIdentity(None)
        self.setDeviceIdentity(None)

    def updateTrustRules(self):
        """
        Should be called after either the device identity, trust root or network
        prefix is changed.

        Not called automatically in case they are all changing (typical for
        bootstrapping).

        Resets the validation rules if we don't have a trust root or enivronment

        """
        validatorTree = self._configTemplate["validator"][0].clone()

        if (self._environmentPrefix.size() > 0 and 
            self._trustRootIdentity.size() > 0 and 
            self._deviceIdentity.size() > 0):
            # don't sneak in a bad identity
            if not self._environmentPrefix.match(self._deviceIdentity):
                raise SecurityException("Device identity does not belong to configured network!")
            
            environmentUri = self._environmentPrefix.toUri()
            deviceUri = self._deviceIdentity.toUri()
             
            for rule in validatorTree["rule"]:
                ruleId = rule["id"][0].value
                if ruleId == 'Certificate Trust':
                    #modify the 'Certificate Trust' rule
                    rule["checker/key-locator/name"][0].value = environmentUri
                elif ruleId == 'Command Interests':
                    rule["filter/name"][0].value = deviceUri
                    rule["checker/key-locator/name"][0].value = environmentUri
            
        #remove old validation rules from config
        # replace with new validator rules
        self.config._root.subtrees["validator"] = [validatorTree]
        

    def inferSigningIdentity(self, fromName):
        """
        Used to map Data or Interest names to identitites.
        :param pyndn.Name fromName: The name of a Data or Interest packet
        """
        # works if you have an IotIdentityStorage
        return self._identityStorage.inferIdentityForName(fromName)

    def setTrustRootIdentity(self, identityName):
        """
        : param pyndn.Name identityName: The new identity to trust as the controller.
        """
        self._trustRootIdentity = Name(identityName)

    def getTrustRootIdentity(self):
        """
        : return pyndn.Name: The trusted controller's network name.
        """
        return Name(self._trustRootIdentity)

    def setEnvironmentPrefix(self, name):
        """
        : param pyndn.Name name: The new root of the network namespace (network prefix)
        """
        self._environmentPrefix = Name(name)

    def getEnvironmentPrefix(self):
        """
        :return: The root of the network namespace
        :rtype: pyndn.Name
        """
        return Name(self._environmentPrefix)

    def getDeviceIdentity(self):
        return self._deviceIdentity

    def setDeviceIdentity(self, identity):
        self._deviceIdentity = Name(identity)

    def hasRootCertificate(self):
        """
        :return: Whether we've downloaded the controller's network certificate
        :rtype: boolean
        """
        try:
            rootCertName = self._identityStorage.getDefaultCertificateNameForIdentity(
                    self._trustRootIdentity)
        except SecurityException:
            return False

        try:
            rootCert = self._identityStorage.getCertificate(rootCertName)
            if rootCert is not None:
                return True
        finally:
            return False

    def hasRootSignedCertificate(self):
        """
        :return: Whether we've received a network certificate from our controller
        :rtype: boolean
        """
        try:
            myCertName = self._identityStorage.getDefaultCertificateNameForIdentity(
                       self._deviceIdentity)
            myCert = self._identityStorage.getCertificate(myCertName)
            if self._trustRootIdentity.match(
                   myCert.getSignature().getKeyLocator().getKeyName()):
               return True
        except SecurityException:
           pass
       
        return False

    def removeTrustRules(self):
        """
        Resets the network prefix, device identity and trust root identity to
         empty values
        """
        self.setDeviceIdentity(None)
        self.setTrustRootIdentity(None)
        self.setEnvironmentPrefix(None)
        self.updateTrustRules()
Exemplo n.º 10
0
    def load(self, filePathOrInputOrConfigSection, inputName=None):
        """
        There are three forms of load:
        load(filePath) - Load the configuration from the given config file.
        load(input, inputName) - Load the configuration from the given input
        string.
        load(configSection, inputName) - Load the configuration from the given
        configSection.
        Each of these forms of load replaces any existing configuration.

        :param str filePath: The The path of the config file.
        :param str input: The contents of the configuration rules, with lines
          separated by NL or CR/NL.
        :param BoostInfoTree configSection: The configuration section loaded
          from the config file. It should have one "validator" section.
        :param str inputName: Used for log messages, etc.
        """
        if type(filePathOrInputOrConfigSection) is str and inputName == None:
            filePath = filePathOrInputOrConfigSection

            parser = BoostInfoParser()
            parser.read(filePath)
            self.load(parser.getRoot(), filePath)
        elif (type(filePathOrInputOrConfigSection) is str
              and type(inputName) is str):
            input = filePathOrInputOrConfigSection

            parser = BoostInfoParser()
            parser.read(input, inputName)
            self.load(parser.getRoot(), inputName)
        else:
            configSection = filePathOrInputOrConfigSection

            if self._isConfigured:
                # Reset the previous configuration.
                self._shouldBypass = False
                self._dataRules = []
                self._interestRules = []

                self._validator.resetAnchors()
                self._validator.resetVerifiedCertificates()

            self._isConfigured = True

            validatorList = configSection["validator"]
            if len(validatorList) != 1:
                raise ValidatorConfigError(
                    "ValidationPolicyConfig: Expected one validator section")
            validatorSection = validatorList[0]

            # Get the rules.
            ruleList = validatorSection["rule"]
            for i in range(len(ruleList)):
                rule = ConfigRule.create(ruleList[i])
                if rule.getIsForInterest():
                    self._interestRules.append(rule)
                else:
                    self._dataRules.append(rule)

            # Get the trust anchors.
            trustAnchorList = validatorSection["trust-anchor"]
            for i in range(len(trustAnchorList)):
                self._processConfigTrustAnchor(trustAnchorList[i], inputName)
Exemplo n.º 11
0
class ConfigPolicyManager(PolicyManager):
    """
    Create a new ConfigPolicyManager which acts on the rules specified
    in the configuration file and downloads unknown certificates when necessary.

    :param string configFileName: The path to the configuration file containing
      verification rules.
    :param CertificateCache certificateCache: (optional) A CertificateCache to
        hold known certificates.
    :param int searchDepth: (optional) The maximum number of links to follow
      when verifying a certificate chain.
    :param int graceInterval: (optional) The window of time difference (in
        milliseconds) allowed between the timestamp of the first interest signed
        with a new public key and the validation time. If omitted, use a default
        value.
    :param int keyTimestampTtl: (optional) How long a public key's last-used
        timestamp is kept in the store (milliseconds). If omitted, use a default
        value.
    :param int maxTrackedKeys: (optional) The maximum number of public key use
        timestamps to track.
    """
    def __init__(self, configFileName, certificateCache=None,
            searchDepth=5, graceInterval=3000, keyTimestampTtl=3600000,
            maxTrackedKeys=1000):
        super(ConfigPolicyManager, self).__init__()
        if certificateCache is None:
            self._certificateCache = CertificateCache()
        else:
            self._certificateCache = certificateCache
        self._maxDepth = searchDepth
        self._keyGraceInterval = graceInterval
        self._keyTimestampTtl = keyTimestampTtl
        self._maxTrackedKeys = maxTrackedKeys

        # stores the fixed-signer certificate name associated with validation rules
        # so we don't keep loading from files
        self._fixedCertificateCache = {}

        # stores the timestamps for each public key used in command interests to avoid
        # replay attacks
        # key is public key name, value is last timestamp
        self._keyTimestamps = {}

        self.config = BoostInfoParser()
        self.config.read(configFileName)

        self.requiresVerification = True

        self._refreshManager = TrustAnchorRefreshManager()
        self._loadTrustAnchorCertificates()

    def requireVerify(self, dataOrInterest):
        """
        If the configuration file contains the trust anchor 'any',
        nothing is verified.
        """
        return self.requiresVerification

    def checkSigningPolicy(self, dataName, certificateName):
        """
        Override to always indicate that the signing certificate name and data
        name satisfy the signing policy.

        :param Name dataName: The name of data to be signed.
        :param Name certificateName: The name of signing certificate.
        :return: True to indicate that the signing certificate can be used to
          sign the data.
        :rtype: boolean
        """
        return True

    def skipVerifyAndTrust(self, dataOrInterest):
        """
        If the configuration file contains the trust anchor 'any',
        nothing is verified.
        """
        return not self.requiresVerification

    def _loadTrustAnchorCertificates(self):
        """
        The configuration file allows 'trust anchor' certificates to be preloaded.
        The certificates may also be loaded from a directory, and if the 'refresh'
        option is set to an interval, the certificates are reloaded at the
        specified interval
        """

        try:
            anchors = self.config["validator/trust-anchor"]
        except KeyError:
            return

        for anchor in anchors:
            typeName = anchor["type"][0].getValue()
            if typeName == 'file':
                certID = anchor["file-name"][0].getValue()
                isPath = True
            elif typeName == 'base64':
                certID = anchor["base64-string"][0].getValue()
                isPath = False
            elif typeName == "dir":
                dirName = anchor["dir"][0].getValue()
                try:
                    refreshPeriodStr = anchor["refresh"][0].getValue()
                except KeyError:
                    refreshPeriod = 0
                else:
                    refreshMatch = re.match('(\d+)([hms])', refreshPeriodStr)
                    if not refreshMatch:
                        refreshPeriod = 0
                    else:
                        refreshPeriod = int(refreshMatch.group(1))
                        if refreshMatch.group(2) != 's':
                            refreshPeriod *= 60
                            if refreshMatch.group(2) != 'm':
                                refreshPeriod *= 60

                # Convert refreshPeriod from seconds to milliseconds.
                self._refreshManager.addDirectory(dirName, refreshPeriod * 1000)
                continue
            elif typeName == "any":
                # this disables all security!
                self.requiresVerification = False
                break

            self._lookupCertificate(certID, isPath)

    def _checkSignatureMatch(self, signatureName, objectName, rule):
        """
        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.

        """
        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)
            elif signerType == 'base64':
                cert = self._lookupCertificate(signerInfo['base64-string'][0].getValue(), False)
            else:
                return False
            if cert is None:
                return False
            else:
                return cert.getName().equals(signatureName)
        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)))
                return self._matchesRelation(objectName, identityPrefix, 'is-prefix-of')
            else:
                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())
                return self._matchesRelation(signatureName, matchName, relationType)

            # is this a simple regex?
            try:
                keyRegex = keyLocatorInfo['regex'][0].getValue()
            except KeyError:
                pass
            else:
                return NdnRegexMatcher.match(keyRegex, signatureName) is not None

            # is this a hyper-relation?
            try:
                hyperRelation = keyLocatorInfo['hyper-relation'][0]
            except KeyError:
                pass
            else:
                try:
                    keyRegex = hyperRelation['k-regex'][0].getValue()
                    keyMatch = NdnRegexMatcher.match(keyRegex, signatureName)
                    keyExpansion = hyperRelation['k-expand'][0].getValue()
                    keyMatchPrefix = keyMatch.expand(keyExpansion)

                    nameRegex = hyperRelation['p-regex'][0].getValue()
                    nameMatch = NdnRegexMatcher.match(nameRegex, objectName)
                    nameExpansion = hyperRelation['p-expand'][0].getValue()
                    nameMatchStr = nameMatch.expand(nameExpansion)

                    relationType = hyperRelation['h-relation'][0].getValue()

                    return self._matchesRelation(Name(nameMatchStr), Name(keyMatchPrefix), relationType)
                except:
                    pass

        # unknown type
        return False

    def _lookupCertificate(self, certID, isPath):
        """
        This looks up certificates specified as base64-encoded data or file names.
        These are cached by filename or encoding to avoid repeated reading of files
        or decoding.
        """
        try:
            certUri = self._fixedCertificateCache[certID]
        except KeyError:
            if isPath:
                # load the certificate data (base64 encoded IdentityCertificate)
                cert = TrustAnchorRefreshManager.loadIdentityCertificateFromFile(
                        certID)
            else:
                certData = b64decode(certID)
                cert = IdentityCertificate()
                cert.wireDecode(certData)

            certUri = cert.getName()[:-1].toUri()
            self._fixedCertificateCache[certID] = certUri
            self._certificateCache.insertCertificate(cert)
        else:
            cert = self._certificateCache.getCertificate(Name(certUri))

        return cert

    def _findMatchingRule(self, objName, matchType):
        """
        Search the configuration file for the first rule that matches the data
        or signed interest name. In the case of interests, the name to match
        should exclude the timestamp, nonce, and signature components.
        :param Name objName: The name to be matched.
        :param string matchType: The rule type to match, "data" or "interest".
        """
        rules = self.config["validator/rule"]
        for r in rules:
            if r['for'][0].getValue() == matchType:
                passed = True
                try:
                    filters = r['filter']
                except KeyError:
                    # no filters means we pass!
                    return r
                else:
                    for f in filters:
                        # don't check the type - it can only be name for now
                        # we need to see if this is a regex or a relation
                        try:
                            regex = f['regex'][0].getValue()
                        except KeyError:
                            matchRelation = f['relation'][0].getValue()
                            matchUri = f['name'][0].getValue()
                            matchName = Name(matchUri)
                            passed = self._matchesRelation(objName, matchName, matchRelation)
                        else:
                            passed =  NdnRegexMatcher.match(regex, objName) is not None
                        if not passed:
                            break
                    if passed:
                        return r

        return None

    def _matchesRelation(self, name, matchName, matchRelation):
        """
        Determines if a name satisfies the relation to another name, which can
        be one of:
            'is-prefix-of' - passes if the name is equal to or has the other
               name as a prefix
            'is-strict-prefix-of' - passes if the name has the other name as a
               prefix, and is not equal
            'equal' - passes if the two names are equal
        """
        passed = False
        if matchRelation == 'is-strict-prefix-of':
            if matchName.size() == name.size():
                passed = False
            elif matchName.match(name):
                passed = True
        elif matchRelation == 'is-prefix-of':
            if matchName.match(name):
                passed = True
        elif matchRelation == 'equal':
            if matchName.equals(name):
                passed = True
        return passed

    @staticmethod
    def _extractSignature(dataOrInterest, wireFormat=None):
        """
        Extract the signature information from the interest name or from the
        data packet.
        :param dataOrInterest: The object whose signature is needed.
        :type dataOrInterest: Data or Interest
        :param WireFormat wireFormat: (optional) The wire format used to decode
          signature information from the interest name.
        """
        if isinstance(dataOrInterest, Data):
            return dataOrInterest.getSignature()
        elif isinstance(dataOrInterest, Interest):
            if wireFormat is None:
                # Don't use a default argument since getDefaultWireFormat can change.
                wireFormat = WireFormat.getDefaultWireFormat()
            try:
                signature = wireFormat.decodeSignatureInfoAndValue(
                   dataOrInterest.getName().get(-2).getValue().buf(),
                   dataOrInterest.getName().get(-1).getValue().buf())
            except (IndexError, ValueError):
                return None
            return signature
        return None

    def _interestTimestampIsFresh(self, keyName, timestamp):
        """
        Determine whether the timestamp from the interest is newer than the last use
        of this key, or within the grace interval on first use.

        :param Name keyName: The name of the public key used to sign the interest.
        :paramt int timestamp: The timestamp extracted from the interest name.
        """
        try:
            lastTimestamp = self._keyTimestamps[keyName.toUri()]
        except KeyError:
            now = Common.getNowMilliseconds()
            notBefore = now - self._keyGraceInterval
            notAfter = now + self._keyGraceInterval
            return timestamp > notBefore and timestamp < notAfter
        else:
            return timestamp > lastTimestamp

    def _updateTimestampForKey(self, keyName, timestamp):
        """
        Trim the table size down if necessary, and insert/update the latest
        interest signing timestamp for the key.

        Any key which has not been used within the TTL period is purged. If the
        table is still too large, the oldest key is purged.

        :param Name keyName: The name of the public key used to sign the interest.
        :paramt int timestamp: The timestamp extracted from the interest name.

        """
        self._keyTimestamps[keyName.toUri()] = timestamp

        if len(self._keyTimestamps) >= self._maxTrackedKeys:
            now = Common.getNowMilliseconds()
            oldestTimestamp = now
            oldestKey = None
            trackedKeys = self._keyTimestamps.keys()
            for keyUri in trackedKeys:
                ts = self._keyTimestamps[keyUri]
                if now - ts > self._keyTimestampTtl:
                    del self._keyTimestamps[keyUri]
                elif ts < oldestTimestamp:
                    oldestTimestamp = ts
                    oldestKey = keyUri

            if len(self._keyTimestamps) > self._maxTrackedKeys:
                # have not removed enough
                del self._keyTimestamps[oldestKey]




    def checkVerificationPolicy(self, dataOrInterest, stepCount, onVerified,
                                onVerifyFailed, wireFormat = None):
        """
        If there is a rule matching the data or interest, and the matching
        certificate is missing, download it. If there is no matching rule,
        verification fails. Otherwise, verify the signature using the public key
        in the IdentityStorage.

        :param dataOrInterest: The Data object or interest with the signature to
          check.
        :type dataOrInterest: Data or Interest
        :param int stepCount: The number of verification steps that have been
          done, used to track the verification progress.
        :param onVerified: If the signature is verified, this calls
          onVerified(dataOrInterest).
        :type onVerified: function object
        :param onVerifyFailed: If the signature check fails, this calls
          onVerifyFailed(dataOrInterest).
        :type onVerifyFailed: function object
        :return: None for no further step for looking up a certificate chain.
        :rtype: ValidationRequest
        """

        if stepCount > self._maxDepth:
            onVerifyFailed(dataOrInterest)
            return None

        signature = self._extractSignature(dataOrInterest, wireFormat)
        # no signature -> fail
        if signature is None:
            onVerifyFailed(dataOrInterest)
            return None

        signatureName = signature.getKeyLocator().getKeyName()
        # no key name in KeyLocator -> fail
        if signatureName.size() == 0:
            onVerifyFailed(dataOrInterest)
            return None

        objectName = dataOrInterest.getName()
        matchType = "data"

        #for command interests, we need to ignore the last 4 components when matching the name
        if isinstance(dataOrInterest, Interest):
            objectName = objectName.getPrefix(-4)
            matchType = "interest"

        # first see if we can find a rule to match this packet
        try:
            matchedRule = self._findMatchingRule(objectName, matchType)
        except:
            matchedRule = None

        # no matching rule -> fail
        if matchedRule is None:
            onVerifyFailed(dataOrInterest)
            return None

        signatureMatches = self._checkSignatureMatch(signatureName, objectName,
                matchedRule)
        if not signatureMatches:
            onVerifyFailed(dataOrInterest)
            return None

        # before we look up keys, refresh any certificate directories
        self._refreshManager.refreshAnchors()

        # now finally check that the data or interest was signed correctly
        # if we don't actually have the certificate yet, create a
        # ValidationRequest for it
        foundCert = self._refreshManager.getCertificate(signatureName)
        if foundCert is None:
            foundCert = self._certificateCache.getCertificate(signatureName)
        if foundCert is None:
            certificateInterest = Interest(signatureName)
            def onCertificateDownloadComplete(certificate):
                certificate = IdentityCertificate(certificate)
                self._certificateCache.insertCertificate(certificate)
                self.checkVerificationPolicy(dataOrInterest, stepCount+1, 
                        onVerified, onVerifyFailed)

            nextStep = ValidationRequest(certificateInterest, 
                    onCertificateDownloadComplete, onVerifyFailed, 
                    2, stepCount+1)
           
            return nextStep
        
        # for interests, we must check that the timestamp is fresh enough
        # I do this after (possibly) downloading the certificate to avoid 
        # filling the cache with bad keys
        if isinstance(dataOrInterest, Interest):
            keyName = foundCert.getPublicKeyName()
            timestamp = dataOrInterest.getName().get(-4).toNumber()

            if not self._interestTimestampIsFresh(keyName, timestamp):
                onVerifyFailed(dataOrInterest)
                return None

        # certificate is known, verify the signature
        if self._verify(signature, dataOrInterest.wireEncode()):
            onVerified(dataOrInterest)
            if isinstance(dataOrInterest, Interest):
                self._updateTimestampForKey(keyName, timestamp)
        else:
            onVerifyFailed(dataOrInterest)

    def _verify(self, signatureInfo, signedBlob):
        """
        Check the type of signatureInfo to get the KeyLocator. Look in the
        IdentityStorage for the public key with the name in the KeyLocator and
        use it to verify the signedBlob. If the public key can't be found,
        return false. (This is a generalized method which can verify both a Data
        packet and an interest.)

        :param Signature signatureInfo: An object of a subclass of Signature,
          e.g. Sha256WithRsaSignature.
        :param SignedBlob signedBlob: the SignedBlob with the signed portion to
          verify.
        :return: True if the signature verifies, False if not.
        :rtype: boolean
        """
        signature = signatureInfo
        if not isinstance(signature, Sha256WithRsaSignature):
            raise SecurityException(
           "ConfigPolicyManager: Signature is not Sha256WithRsaSignature.")

        if (signature.getKeyLocator().getType() == KeyLocatorType.KEYNAME):
            # Assume the key name is a certificate name.
            signatureName = signature.getKeyLocator().getKeyName()
            certificate = self._refreshManager.getCertificate(signatureName)
            if certificate is None:
                certificate = self._certificateCache.getCertificate(signatureName)
            if certificate is None:
                return False

            publicKeyDer = certificate.getPublicKeyInfo().getKeyDer()
            if publicKeyDer.isNull():
                # Can't find the public key with the name.
                return False

            return self._verifySha256WithRsaSignature(
              signature, signedBlob, publicKeyDer)
        else:
            # Can't find a key to verify.
            return False
Exemplo n.º 12
0
    def load(self, filePathOrInputOrConfigSection, inputName = None):
        """
        There are three forms of load:
        load(filePath) - Load the configuration from the given config file.
        load(input, inputName) - Load the configuration from the given input
        string.
        load(configSection, inputName) - Load the configuration from the given
        configSection.
        Each of these forms of load replaces any existing configuration.

        :param str filePath: The The path of the config file.
        :param str input: The contents of the configuration rules, with lines
          separated by NL or CR/NL.
        :param BoostInfoTree configSection: The configuration section loaded
          from the config file. It should have one "validator" section.
        :param str inputName: Used for log messages, etc.
        """
        if type(filePathOrInputOrConfigSection) is str and inputName == None:
            filePath = filePathOrInputOrConfigSection

            parser = BoostInfoParser()
            parser.read(filePath)
            self.load(parser.getRoot(), filePath)
        elif (type(filePathOrInputOrConfigSection) is str and
              type(inputName) is str):
            input = filePathOrInputOrConfigSection

            parser = BoostInfoParser()
            parser.read(input, inputName)
            self.load(parser.getRoot(), inputName)
        else:
            configSection = filePathOrInputOrConfigSection

            if self._isConfigured:
                # Reset the previous configuration.
                self._shouldBypass = False
                self._dataRules = []
                self._interestRules = []

                self._validator.resetAnchors()
                self._validator.resetVerifiedCertificates()

            self._isConfigured = True

            validatorList = configSection["validator"]
            if len(validatorList) != 1:
                raise ValidatorConfigError(
                  "ValidationPolicyConfig: Expected one validator section")
            validatorSection = validatorList[0]

            # Get the rules.
            ruleList = validatorSection["rule"]
            for i in range(len(ruleList)):
                rule = ConfigRule.create(ruleList[i])
                if rule.getIsForInterest():
                    self._interestRules.append(rule)
                else:
                    self._dataRules.append(rule)

            # Get the trust anchors.
            trustAnchorList = validatorSection["trust-anchor"]
            for i in range(len(trustAnchorList)):
                self._processConfigTrustAnchor(trustAnchorList[i], inputName)
Exemplo n.º 13
0
    def updateTrustSchema(self,
                          appName,
                          certName,
                          dataPrefix,
                          publishNew=False):
        if appName in self._applications:
            if dataPrefix.toUri() in self._applications[appName]["dataPrefix"]:
                print("some key is configured for namespace " +
                      dataPrefix.toUri() + " for application " + appName +
                      ". Ignoring this request.")
                return False
            else:
                # TODO: Handle malformed conf where validator tree does not exist
                validatorNode = self._applications[appName]["tree"][
                    "validator"][0]
        else:
            # This application does not previously exist, we create its trust schema
            # (and for now, add in static rules for sync data)

            self._applications[appName] = {
                "tree": BoostInfoParser(),
                "dataPrefix": [],
                "version": 0
            }
            validatorNode = self._applications[appName]["tree"].getRoot(
            ).createSubtree("validator")

            trustAnchorNode = validatorNode.createSubtree("trust-anchor")
            #trustAnchorNode.createSubtree("type", "file")
            #trustAnchorNode.createSubtree("file-name", os.path.expanduser("~/.ndn/iot/root.cert"))
            trustAnchorNode.createSubtree("type", "base64")
            trustAnchorNode.createSubtree(
                "base64-string",
                Blob(b64encode(self._rootCertificate.wireEncode().toBytes()),
                     False).toRawStr())

            #create cert verification rule
            # TODO: the idea for this would be, if the cert has /home-prefix/<one-component>/KEY/ksk-*/ID-CERT, then it should be signed by fixed controller(s)
            # if the cert has /home-prefix/<multiple-components>/KEY/ksk-*/ID-CERT, then it should be checked hierarchically (this is for subdomain support)
            certRuleNode = validatorNode.createSubtree("rule")
            certRuleNode.createSubtree("id", "Certs")
            certRuleNode.createSubtree("for", "data")

            filterNode = certRuleNode.createSubtree("filter")
            filterNode.createSubtree("type", "regex")
            filterNode.createSubtree("regex", "^[^<KEY>]*<KEY><>*<ID-CERT>")

            checkerNode = certRuleNode.createSubtree("checker")
            # TODO: wait how did my first hierarchical verifier work?
            #checkerNode.createSubtree("type", "hierarchical")

            checkerNode.createSubtree("type", "customized")
            checkerNode.createSubtree("sig-type", "rsa-sha256")

            keyLocatorNode = checkerNode.createSubtree("key-locator")
            keyLocatorNode.createSubtree("type", "name")
            # We don't put cert version in there
            keyLocatorNode.createSubtree(
                "name",
                Name(self.getDefaultCertificateName()).getPrefix(-1).toUri())
            keyLocatorNode.createSubtree("relation", "equal")

            # Discovery rule: anything that multicasts under my home prefix should be signed, and the signer should have been authorized by root
            # TODO: This rule as of right now is over-general
            discoveryRuleNode = validatorNode.createSubtree("rule")
            discoveryRuleNode.createSubtree("id", "sync-data")
            discoveryRuleNode.createSubtree("for", "data")

            filterNode = discoveryRuleNode.createSubtree("filter")
            filterNode.createSubtree("type", "regex")
            filterNode.createSubtree("regex", "^[^<MULTICAST>]*<MULTICAST><>*")

            checkerNode = discoveryRuleNode.createSubtree("checker")
            # TODO: wait how did my first hierarchical verifier work?
            #checkerNode.createSubtree("type", "hierarchical")

            checkerNode.createSubtree("type", "customized")
            checkerNode.createSubtree("sig-type", "rsa-sha256")

            keyLocatorNode = checkerNode.createSubtree("key-locator")
            keyLocatorNode.createSubtree("type", "name")
            keyLocatorNode.createSubtree("regex",
                                         "^[^<KEY>]*<KEY><>*<ID-CERT>")

        ruleNode = validatorNode.createSubtree("rule")
        ruleNode.createSubtree("id", dataPrefix.toUri())
        ruleNode.createSubtree("for", "data")

        filterNode = ruleNode.createSubtree("filter")
        filterNode.createSubtree("type", "name")
        filterNode.createSubtree("name", dataPrefix.toUri())
        filterNode.createSubtree("relation", "is-prefix-of")

        checkerNode = ruleNode.createSubtree("checker")
        checkerNode.createSubtree("type", "customized")
        checkerNode.createSubtree("sig-type", "rsa-sha256")

        keyLocatorNode = checkerNode.createSubtree("key-locator")
        keyLocatorNode.createSubtree("type", "name")
        # We don't put cert version in there
        keyLocatorNode.createSubtree("name", certName.getPrefix(-1).toUri())
        keyLocatorNode.createSubtree("relation", "equal")

        if not os.path.exists(self._applicationDirectory):
            os.makedirs(self._applicationDirectory)
        self._applications[appName]["tree"].write(
            os.path.join(self._applicationDirectory, appName + ".conf"))
        self._applications[appName]["dataPrefix"].append(dataPrefix.toUri())
        self._applications[appName]["version"] = int(time.time())
        if publishNew:
            # TODO: ideally, this is the trust schema of the application, and does not necessarily carry controller prefix.
            # We make it carry controller prefix here so that prefix registration / route setup is easier (implementation workaround)
            data = Data(
                Name(self.prefix).append(appName).append(
                    "_schema").appendVersion(
                        self._applications[appName]["version"]))
            data.setContent(str(self._applications[appName]["tree"].getRoot()))
            self.signData(data)
            self._memoryContentCache.add(data)
        return True
Exemplo n.º 14
0
        self.log.debug(msg)


if __name__ == '__main__':
    import sys
    import os
    # todo - should I enforce the suffix 'gateway'?
    nArgs = len(sys.argv) - 1
    if nArgs < 2:
        from pyndn.util.boost_info_parser import BoostInfoParser
        fileName = '/usr/local/etc/ndn/iot_controller.conf'
        if nArgs == 1:
            fileName = sys.argv[1]
    
        try:
            config = BoostInfoParser()
            config.read(fileName)
        except IOError:
            print('Could not read {}, exiting...'.format(fileName))
            sys.exit(1)
        else:
            deviceName = config["device/controllerName"][0].value
            networkName = config["device/environmentPrefix"][0].value
    elif nArgs == 2:
        networkName = sys.argv[1]
        deviceName = sys.argv[2]
    else:
        print('Usage: {} [network-name controller-name]'.format(sys.argv[0]))
        sys.exit(1)

    try:
Exemplo n.º 15
0
class IotPolicyManager(ConfigPolicyManager):
    def __init__(self, identityStorage, configFilename=None):
        """
        :param pyndn.IdentityStorage: A class that stores signing identities and certificates.
        :param str configFilename: A configuration file specifying validation rules and network
            name settings.
        """

        # use the default configuration where possible
        # TODO: use environment variable for this, fall back to default
        path = os.path.dirname(__file__)
        templateFilename = os.path.join(path, '.default.conf')
        self._configTemplate = BoostInfoParser()
        self._configTemplate.read(templateFilename)

        if configFilename is None:
            configFilename = templateFilename

        certificateCache = CertificateCache()
        super(IotPolicyManager, self).__init__(configFilename,
                                               certificateCache)
        self._identityStorage = identityStorage

        self.setEnvironmentPrefix(None)
        self.setTrustRootIdentity(None)
        self.setDeviceIdentity(None)

    def updateTrustRules(self):
        """
        Should be called after either the device identity, trust root or network
        prefix is changed.

        Not called automatically in case they are all changing (typical for
        bootstrapping).

        Resets the validation rules if we don't have a trust root or environment

        """
        validatorTree = self._configTemplate["validator"][0].clone()

        if (self._environmentPrefix.size() > 0
                and self._trustRootIdentity.size() > 0
                and self._deviceIdentity.size() > 0):
            # don't sneak in a bad identity
            if not self._environmentPrefix.match(self._deviceIdentity):
                raise SecurityException(
                    "Device identity does not belong to configured network!")

            environmentUri = self._environmentPrefix.toUri()
            deviceUri = self._deviceIdentity.toUri()

            for rule in validatorTree["rule"]:
                ruleId = rule["id"][0].value
                if ruleId == 'Certificate Trust':
                    #modify the 'Certificate Trust' rule
                    rule["checker/key-locator/name"][0].value = environmentUri
                elif ruleId == 'Command Interests':
                    rule["filter/name"][0].value = deviceUri
                    rule["checker/key-locator/name"][0].value = environmentUri

        #debug for adding trust anchor
        # try:
        #     validatorTree["trust-anchor"][0]["type"][0].value = "base64"
        #     rootCertificate = self._identityStorage.getCertificate(self._identityStorage.getDefaultCertificateNameForIdentity(self._trustRootIdentity))
        #     validatorTree["trust-anchor"][0]["base64-string"][0].value = Blob(b64encode(rootCertificate.wireEncode().toBytes()), False).toRawStr()
        # except KeyError as e:
        #     rootCertificate = self._identityStorage.getCertificate(self._identityStorage.getDefaultCertificateNameForIdentity(self._trustRootIdentity))
        #     treeNode = self.config._root.subtrees["validator"][0].createSubtree("trust-anchor")
        #     # todo: change this!
        #     treeNode.createSubtree("type", "file")
        #     print Blob(b64encode(rootCertificate.wireEncode().toBytes()), False).toRawStr()
        #     treeNode.createSubtree("file-name", "/home/zhehao/.ndn/.iot.root.cert")
        # self._loadTrustAnchorCertificates()

        #remove old validation rules from config
        # replace with new validator rules
        self.config._root.subtrees["validator"] = [validatorTree]

    def inferSigningIdentity(self, fromName):
        """
        Used to map Data or Interest names to identitites.
        :param pyndn.Name fromName: The name of a Data or Interest packet
        """
        # works if you have an IotIdentityStorage
        return self._identityStorage.inferIdentityForName(fromName)

    def setTrustRootIdentity(self, identityName):
        """
        : param pyndn.Name identityName: The new identity to trust as the controller.
        """
        self._trustRootIdentity = Name(identityName)

    def getTrustRootIdentity(self):
        """
        : return pyndn.Name: The trusted controller's network name.
        """
        return Name(self._trustRootIdentity)

    def setEnvironmentPrefix(self, name):
        """
        : param pyndn.Name name: The new root of the network namespace (network prefix)
        """
        self._environmentPrefix = Name(name)

    def getEnvironmentPrefix(self):
        """
        :return: The root of the network namespace
        :rtype: pyndn.Name
        """
        return Name(self._environmentPrefix)

    def getDeviceIdentity(self):
        return self._deviceIdentity

    def setDeviceIdentity(self, identity):
        self._deviceIdentity = Name(identity)

    def hasRootCertificate(self):
        """
        :return: Whether we've downloaded the controller's network certificate
        :rtype: boolean
        """
        try:
            rootCertName = self._identityStorage.getDefaultCertificateNameForIdentity(
                self._trustRootIdentity)
        except SecurityException:
            return False

        try:
            rootCert = self._identityStorage.getCertificate(rootCertName)
            if rootCert is not None:
                return True
        finally:
            return False

    def hasRootSignedCertificate(self):
        """
        :return: Whether we've received a network certificate from our controller
        :rtype: boolean
        """
        try:
            myCertName = self._identityStorage.getDefaultCertificateNameForIdentity(
                self._deviceIdentity)
            myCert = self._identityStorage.getCertificate(myCertName)
            if self._trustRootIdentity.match(
                    myCert.getSignature().getKeyLocator().getKeyName()):
                return True
        except SecurityException:
            pass

        return False

    def removeTrustRules(self):
        """
        Resets the network prefix, device identity and trust root identity to
         empty values
        """
        self.setDeviceIdentity(None)
        self.setTrustRootIdentity(None)
        self.setEnvironmentPrefix(None)
        self.updateTrustRules()
Exemplo n.º 16
0
class ConfigPolicyManager(PolicyManager):
    """
    Create a new ConfigPolicyManager which will act on the rules specified
    in the configuration and download unknown certificates when necessary.

    :param str configFileName: (optional) If not null or empty, the path to the
      configuration file containing verification rules. Otherwise, you should
      separately call load().
    :param CertificateCache certificateCache: (optional) A CertificateCache to
        hold known certificates.
    :param int searchDepth: (optional) The maximum number of links to follow
      when verifying a certificate chain.
    :param int graceInterval: (optional) The window of time difference (in
        milliseconds) allowed between the timestamp of the first interest signed
        with a new public key and the validation time. If omitted, use a default
        value.
    :param int keyTimestampTtl: (optional) How long a public key's last-used
        timestamp is kept in the store (milliseconds). If omitted, use a default
        value.
    :param int maxTrackedKeys: (optional) The maximum number of public key use
        timestamps to track.
    """
    def __init__(self,
                 configFileName=None,
                 certificateCache=None,
                 searchDepth=5,
                 graceInterval=3000,
                 keyTimestampTtl=3600000,
                 maxTrackedKeys=1000):
        super(ConfigPolicyManager, self).__init__()
        if certificateCache is None:
            self._certificateCache = CertificateCache()
        else:
            self._certificateCache = certificateCache
        self._maxDepth = searchDepth
        self._keyGraceInterval = graceInterval
        self._keyTimestampTtl = keyTimestampTtl
        self._maxTrackedKeys = maxTrackedKeys

        self.reset()

        if configFileName != None and configFileName != "":
            self.load(configFileName)

    def reset(self):
        """
        Reset the certificate cache and other fields to the constructor state.
        """
        self._certificateCache.reset()

        # stores the fixed-signer certificate name associated with validation rules
        # so we don't keep loading from files
        self._fixedCertificateCache = {}

        # stores the timestamps for each public key used in command interests to avoid
        # replay attacks
        # key is public key name, value is last timestamp
        self._keyTimestamps = {}

        self.requiresVerification = True

        self.config = BoostInfoParser()
        self._refreshManager = TrustAnchorRefreshManager()

    def load(self, configFileNameOrInput, inputName=None):
        """
        Call reset() and load the configuration rules from the file name or the
        input string. There are two forms:
        load(configFileName) reads configFileName from the file system.
        load(input, inputName) reads from the input, in which case inputName is
        used only for log messages, etc.

        :param str configFileName: The path to the file containing configuration
          rules.
        :param str input: The contents of the configuration rules, with lines
          separated by NL or CR/NL.
        :param str inputName: Use with input for log messages, etc.
        """
        self.reset()
        self.config.read(configFileNameOrInput, inputName)
        self._loadTrustAnchorCertificates()

    def requireVerify(self, dataOrInterest):
        """
        If the configuration file contains the trust anchor 'any',
        nothing is verified.
        """
        return self.requiresVerification

    def checkSigningPolicy(self, dataName, certificateName):
        """
        Override to always indicate that the signing certificate name and data
        name satisfy the signing policy.

        :param Name dataName: The name of data to be signed.
        :param Name certificateName: The name of signing certificate.
        :return: True to indicate that the signing certificate can be used to
          sign the data.
        :rtype: boolean
        """
        return True

    def skipVerifyAndTrust(self, dataOrInterest):
        """
        If the configuration file contains the trust anchor 'any',
        nothing is verified.
        """
        return not self.requiresVerification

    def _loadTrustAnchorCertificates(self):
        """
        The configuration file allows 'trust anchor' certificates to be preloaded.
        The certificates may also be loaded from a directory, and if the 'refresh'
        option is set to an interval, the certificates are reloaded at the
        specified interval
        """

        try:
            anchors = self.config["validator/trust-anchor"]
        except KeyError:
            return

        for anchor in anchors:
            typeName = anchor["type"][0].getValue()
            if typeName == 'file':
                certID = anchor["file-name"][0].getValue()
                isPath = True
            elif typeName == 'base64':
                certID = anchor["base64-string"][0].getValue()
                isPath = False
            elif typeName == "dir":
                dirName = anchor["dir"][0].getValue()
                try:
                    refreshPeriodStr = anchor["refresh"][0].getValue()
                except KeyError:
                    refreshPeriod = 0
                else:
                    refreshMatch = re.match('(\d+)([hms])', refreshPeriodStr)
                    if not refreshMatch:
                        refreshPeriod = 0
                    else:
                        refreshPeriod = int(refreshMatch.group(1))
                        if refreshMatch.group(2) != 's':
                            refreshPeriod *= 60
                            if refreshMatch.group(2) != 'm':
                                refreshPeriod *= 60

                # Convert refreshPeriod from seconds to milliseconds.
                self._refreshManager.addDirectory(dirName,
                                                  refreshPeriod * 1000)
                continue
            elif typeName == "any":
                # this disables all security!
                self.requiresVerification = False
                break

            self._lookupCertificate(certID, isPath)

    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 _lookupCertificate(self, certID, isPath):
        """
        This looks up certificates specified as base64-encoded data or file names.
        These are cached by filename or encoding to avoid repeated reading of files
        or decoding.

        :return: The certificate object, or None if not found.
        :rtype: IdentityCertificate
        """
        try:
            certUri = self._fixedCertificateCache[certID]
        except KeyError:
            if isPath:
                # load the certificate data (base64 encoded IdentityCertificate)
                cert = TrustAnchorRefreshManager.loadIdentityCertificateFromFile(
                    certID)
            else:
                certData = b64decode(certID)
                cert = IdentityCertificate()
                cert.wireDecode(Blob(certData, False))

            certUri = cert.getName()[:-1].toUri()
            self._fixedCertificateCache[certID] = certUri
            self._certificateCache.insertCertificate(cert)
        else:
            cert = self._certificateCache.getCertificate(Name(certUri))

        return cert

    def _findMatchingRule(self, objName, matchType):
        """
        Search the configuration file for the first rule that matches the data
        or signed interest name. In the case of interests, the name to match
        should exclude the timestamp, nonce, and signature components.
        :param Name objName: The name to be matched.
        :param string matchType: The rule type to match, "data" or "interest".
        """
        rules = self.config["validator/rule"]
        for r in rules:
            if r['for'][0].getValue() == matchType:
                passed = True
                try:
                    filters = r['filter']
                except KeyError:
                    # no filters means we pass!
                    return r
                else:
                    for f in filters:
                        # don't check the type - it can only be name for now
                        # we need to see if this is a regex or a relation
                        try:
                            regex = f['regex'][0].getValue()
                        except KeyError:
                            matchRelation = f['relation'][0].getValue()
                            matchUri = f['name'][0].getValue()
                            matchName = Name(matchUri)
                            passed = self._matchesRelation(
                                objName, matchName, matchRelation)
                        else:
                            passed = NdnRegexMatcher.match(regex,
                                                           objName) is not None
                        if not passed:
                            break
                    if passed:
                        return r

        return None

    @staticmethod
    def _matchesRelation(name, matchName, matchRelation):
        """
        Determines if a name satisfies the relation to another name, which can
        be one of:
            'is-prefix-of' - passes if the name is equal to or has the other
               name as a prefix
            'is-strict-prefix-of' - passes if the name has the other name as a
               prefix, and is not equal
            'equal' - passes if the two names are equal
        """
        passed = False
        if matchRelation == 'is-strict-prefix-of':
            if matchName.size() == name.size():
                passed = False
            elif matchName.match(name):
                passed = True
        elif matchRelation == 'is-prefix-of':
            if matchName.match(name):
                passed = True
        elif matchRelation == 'equal':
            if matchName.equals(name):
                passed = True
        return passed

    @staticmethod
    def _extractSignature(dataOrInterest, wireFormat=None):
        """
        Extract the signature information from the interest name or from the
        data packet.
        :param dataOrInterest: The object whose signature is needed.
        :type dataOrInterest: Data or Interest
        :param WireFormat wireFormat: (optional) The wire format used to decode
          signature information from the interest name.
        """
        if isinstance(dataOrInterest, Data):
            return dataOrInterest.getSignature()
        elif isinstance(dataOrInterest, Interest):
            if wireFormat is None:
                # Don't use a default argument since getDefaultWireFormat can change.
                wireFormat = WireFormat.getDefaultWireFormat()
            try:
                signature = wireFormat.decodeSignatureInfoAndValue(
                    dataOrInterest.getName().get(-2).getValue().buf(),
                    dataOrInterest.getName().get(-1).getValue().buf(), False)
            except (IndexError, ValueError):
                return None
            return signature
        return None

    def _interestTimestampIsFresh(self, keyName, timestamp, failureReason):
        """
        Determine whether the timestamp from the interest is newer than the last use
        of this key, or within the grace interval on first use.

        :param Name keyName: The name of the public key used to sign the interest.
        :paramt int timestamp: The timestamp extracted from the interest name.
        :param Array<str> failureReason: If verification fails, set
          failureReason[0] to the failure reason string.
        """
        try:
            lastTimestamp = self._keyTimestamps[keyName.toUri()]
        except KeyError:
            now = Common.getNowMilliseconds()
            notBefore = now - self._keyGraceInterval
            notAfter = now + self._keyGraceInterval
            if not (timestamp > notBefore and timestamp < notAfter):
                return False
                failureReason[0] = (
                    "The command interest timestamp is not within the first use grace period of "
                    + str(self._keyGraceInterval) + " milliseconds.")
            else:
                return True
        else:
            if timestamp <= lastTimestamp:
                failureReason[0] = (
                    "The command interest timestamp is not newer than the previous timestamp"
                )
                return False
            else:
                return True

    def _updateTimestampForKey(self, keyName, timestamp):
        """
        Trim the table size down if necessary, and insert/update the latest
        interest signing timestamp for the key.

        Any key which has not been used within the TTL period is purged. If the
        table is still too large, the oldest key is purged.

        :param Name keyName: The name of the public key used to sign the interest.
        :paramt int timestamp: The timestamp extracted from the interest name.

        """
        self._keyTimestamps[keyName.toUri()] = timestamp

        if len(self._keyTimestamps) >= self._maxTrackedKeys:
            now = Common.getNowMilliseconds()
            oldestTimestamp = now
            oldestKey = None
            trackedKeys = self._keyTimestamps.keys()
            for keyUri in trackedKeys:
                ts = self._keyTimestamps[keyUri]
                if now - ts > self._keyTimestampTtl:
                    del self._keyTimestamps[keyUri]
                elif ts < oldestTimestamp:
                    oldestTimestamp = ts
                    oldestKey = keyUri

            if len(self._keyTimestamps) > self._maxTrackedKeys:
                # have not removed enough
                del self._keyTimestamps[oldestKey]

    def checkVerificationPolicy(self,
                                dataOrInterest,
                                stepCount,
                                onVerified,
                                onValidationFailed,
                                wireFormat=None):
        """
        If there is a rule matching the data or interest, and the matching
        certificate is missing, download it. If there is no matching rule,
        verification fails. Otherwise, verify the signature using the public key
        in the IdentityStorage.

        :param dataOrInterest: The Data object or interest with the signature to
          check.
        :type dataOrInterest: Data or Interest
        :param int stepCount: The number of verification steps that have been
          done, used to track the verification progress.
        :param onVerified: If the signature is verified, this calls
          onVerified(dataOrInterest).
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onVerified: function object
        :param onValidationFailed: If the signature check fails, this calls
          onValidationFailed(dataOrInterest, reason).
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onValidationFailed: function object
        :return: None for no further step for looking up a certificate chain.
        :rtype: ValidationRequest
        """
        if stepCount > self._maxDepth:
            try:
                onValidationFailed(
                    dataOrInterest, "The verification stepCount " + stepCount +
                    " exceeded the maxDepth " + self._maxDepth)
            except:
                logging.exception("Error in onValidationFailed")
            return None

        signature = self._extractSignature(dataOrInterest, wireFormat)
        # no signature -> fail
        if signature is None:
            try:
                onValidationFailed(
                    dataOrInterest, "Cannot extract the signature from " +
                    dataOrInterest.getName().toUri())
            except:
                logging.exception("Error in onValidationFailed")
            return None

        if not KeyLocator.canGetFromSignature(signature):
            # We only support signature types with key locators.
            try:
                onValidationFailed(
                    dataOrInterest,
                    "The signature type does not support a KeyLocator")
            except:
                logging.exception("Error in onValidationFailed")
            return None

        keyLocator = None
        try:
            keyLocator = KeyLocator.getFromSignature(signature)
        except Exception as ex:
            # No key locator -> fail.
            try:
                onValidationFailed(
                    dataOrInterest,
                    "Error in KeyLocator.getFromSignature: " + str(ex))
            except:
                logging.exception("Error in onValidationFailed")
            return None

        signatureName = keyLocator.getKeyName()
        # no key name in KeyLocator -> fail
        if signatureName.size() == 0:
            try:
                onValidationFailed(
                    dataOrInterest,
                    "The signature KeyLocator doesn't have a key name")
            except:
                logging.exception("Error in onValidationFailed")
            return None

        objectName = dataOrInterest.getName()
        matchType = "data"

        #for command interests, we need to ignore the last 4 components when matching the name
        if isinstance(dataOrInterest, Interest):
            objectName = objectName.getPrefix(-4)
            matchType = "interest"

        # first see if we can find a rule to match this packet
        try:
            matchedRule = self._findMatchingRule(objectName, matchType)
        except:
            matchedRule = None

        # no matching rule -> fail
        if matchedRule is None:
            try:
                onValidationFailed(
                    dataOrInterest,
                    "No matching rule found for " + objectName.toUri())
            except:
                logging.exception("Error in onValidationFailed")
            return None

        failureReason = ["unknown"]
        signatureMatches = self._checkSignatureMatch(signatureName, objectName,
                                                     matchedRule,
                                                     failureReason)
        if not signatureMatches:
            try:
                onValidationFailed(dataOrInterest, failureReason[0])
            except:
                logging.exception("Error in onValidationFailed")
            return None

        # before we look up keys, refresh any certificate directories
        self._refreshManager.refreshAnchors()

        # now finally check that the data or interest was signed correctly
        # if we don't actually have the certificate yet, create a
        # ValidationRequest for it
        foundCert = self._refreshManager.getCertificate(signatureName)
        if foundCert is None:
            foundCert = self._certificateCache.getCertificate(signatureName)
        if foundCert is None:
            certificateInterest = Interest(signatureName)

            def onCertificateDownloadComplete(data):
                try:
                    certificate = IdentityCertificate(data)
                except:
                    try:
                        onValidationFailed(
                            dataOrInterest, "Cannot decode certificate " +
                            data.getName().toUri())
                    except:
                        logging.exception("Error in onValidationFailed")
                    return None
                self._certificateCache.insertCertificate(certificate)
                self.checkVerificationPolicy(dataOrInterest, stepCount + 1,
                                             onVerified, onValidationFailed)

            nextStep = ValidationRequest(certificateInterest,
                                         onCertificateDownloadComplete,
                                         onValidationFailed, 2, stepCount + 1)

            return nextStep

        # for interests, we must check that the timestamp is fresh enough
        # I do this after (possibly) downloading the certificate to avoid
        # filling the cache with bad keys
        if isinstance(dataOrInterest, Interest):
            keyName = foundCert.getPublicKeyName()
            timestamp = dataOrInterest.getName().get(-4).toNumber()

            if not self._interestTimestampIsFresh(keyName, timestamp,
                                                  failureReason):
                try:
                    onValidationFailed(dataOrInterest, failureReason[0])
                except:
                    logging.exception("Error in onValidationFailed")
                return None

        # certificate is known, verify the signature
        if self._verify(signature, dataOrInterest.wireEncode(), failureReason):
            try:
                onVerified(dataOrInterest)
            except:
                logging.exception("Error in onVerified")
            if isinstance(dataOrInterest, Interest):
                self._updateTimestampForKey(keyName, timestamp)
        else:
            try:
                onValidationFailed(dataOrInterest, failureReason[0])
            except:
                logging.exception("Error in onValidationFailed")

    def _verify(self, signatureInfo, signedBlob, failureReason):
        """
        Check the type of signatureInfo to get the KeyLocator. Look in the
        IdentityStorage for the public key with the name in the KeyLocator and
        use it to verify the signedBlob. If the public key can't be found,
        return false. (This is a generalized method which can verify both a Data
        packet and an interest.)

        :param Signature signatureInfo: An object of a subclass of Signature,
          e.g. Sha256WithRsaSignature.
        :param SignedBlob signedBlob: the SignedBlob with the signed portion to
          verify.
        :param Array<str> failureReason: If verification fails, set
          failureReason[0] to the failure reason string.
        :return: True if the signature verifies, False if not.
        :rtype: boolean
        """
        # We have already checked once that there is a key locator.
        keyLocator = KeyLocator.getFromSignature(signatureInfo)

        if (keyLocator.getType() == KeyLocatorType.KEYNAME):
            # Assume the key name is a certificate name.
            signatureName = keyLocator.getKeyName()
            certificate = self._refreshManager.getCertificate(signatureName)
            if certificate is None:
                certificate = self._certificateCache.getCertificate(
                    signatureName)
            if certificate is None:
                failureReason[0] = ("Cannot find a certificate with name " +
                                    signatureName.toUri())
                return False

            publicKeyDer = certificate.getPublicKeyInfo().getKeyDer()
            if publicKeyDer.isNull():
                # We don't expect this to happen.
                failureReason[0] = (
                    "There is no public key in the certificate with name " +
                    certificate.getName().toUri())
                return False

            if self.verifySignature(signatureInfo, signedBlob, publicKeyDer):
                return True
            else:
                failureReason[0] = (
                    "The signature did not verify with the given public key")
                return False
        else:
            failureReason[0] = "The KeyLocator does not have a key name"
            return False
Exemplo n.º 17
0
class ConfigPolicyManager(PolicyManager):
    """
    Create a new ConfigPolicyManager which will act on the rules specified
    in the configuration and download unknown certificates when necessary.
    If certificateCache is a CertificateCache (or omitted) this creates a
    security v1 PolicyManager to verify certificates in format v1. To verify
    certificates in format v2, use a CertificateCacheV2 for the certificateCache.

    :param str configFileName: (optional) If not None or empty, the path to the
      configuration file containing verification rules. Otherwise, you should
      separately call load().
    :param certificateCache: (optional) A CertificateCache to hold known
      certificates. If certificateCache is a CertificateCache (or omitted
      or None) this creates a security v1 PolicyManager to verify certificates
      in format v1. If this is a CertificateCacheV2, verify certificates in
      format v2. If omitted or None, create an internal v1 CertificateCache.
    :type certificateCache: CertificateCache or CertificateCacheV2
    :param int searchDepth: (optional) The maximum number of links to follow
      when verifying a certificate chain.
    :param int graceInterval: (optional) The window of time difference (in
        milliseconds) allowed between the timestamp of the first interest signed
        with a new public key and the validation time. If omitted, use a default
        value.
    :param int keyTimestampTtl: (optional) How long a public key's last-used
        timestamp is kept in the store (milliseconds). If omitted, use a default
        value.
    :param int maxTrackedKeys: (optional) The maximum number of public key use
        timestamps to track.
    """
    def __init__(self, configFileName = None, certificateCache = None,
            searchDepth=5, graceInterval=3000, keyTimestampTtl=3600000,
            maxTrackedKeys=1000):
        super(ConfigPolicyManager, self).__init__()

        if certificateCache is None:
            certificateCache = CertificateCache()

        # _certificateCacheV2 will be replaced below, but set it here to make pylint happy.
        self._certificateCacheV2 = CertificateCacheV2()
        if isinstance(certificateCache, CertificateCache):
            self._isSecurityV1 = True
            self._certificateCache = certificateCache
            self._certificateCacheV2 = None
        else:
            self._isSecurityV1 = False
            self._certificateCache = None
            self._certificateCacheV2 = certificateCache

        self._maxDepth = searchDepth
        self._keyGraceInterval = graceInterval
        self._keyTimestampTtl = keyTimestampTtl
        self._maxTrackedKeys = maxTrackedKeys

        self.reset()

        if configFileName != None and configFileName != "":
            self.load(configFileName)

    def reset(self):
        """
        Reset the certificate cache and other fields to the constructor state.
        """
        if self._isSecurityV1:
            self._certificateCache.reset()
        else:
            self._certificateCacheV2.clear()

        # stores the fixed-signer certificate name associated with validation rules
        # so we don't keep loading from files
        self._fixedCertificateCache = {}

        # stores the timestamps for each public key used in command interests to avoid
        # replay attacks
        # key is public key name, value is last timestamp
        self._keyTimestamps = {}

        self.requiresVerification = True

        self.config = BoostInfoParser()
        self._refreshManager = TrustAnchorRefreshManager(self._isSecurityV1)

    def load(self, configFileNameOrInput, inputName = None):
        """
        Call reset() and load the configuration rules from the file name or the
        input string. There are two forms:
        load(configFileName) reads configFileName from the file system.
        load(input, inputName) reads from the input, in which case inputName is
        used only for log messages, etc.

        :param str configFileName: The path to the file containing configuration
          rules.
        :param str input: The contents of the configuration rules, with lines
          separated by NL or CR/NL.
        :param str inputName: Use with input for log messages, etc.
        """
        self.reset()
        self.config.read(configFileNameOrInput, inputName)
        self._loadTrustAnchorCertificates()

    def requireVerify(self, dataOrInterest):
        """
        If the configuration file contains the trust anchor 'any',
        nothing is verified.
        """
        return self.requiresVerification

    def checkSigningPolicy(self, dataName, certificateName):
        """
        Override to always indicate that the signing certificate name and data
        name satisfy the signing policy.

        :param Name dataName: The name of data to be signed.
        :param Name certificateName: The name of signing certificate.
        :return: True to indicate that the signing certificate can be used to
          sign the data.
        :rtype: boolean
        """
        return True

    def skipVerifyAndTrust(self, dataOrInterest):
        """
        If the configuration file contains the trust anchor 'any',
        nothing is verified.
        """
        return not self.requiresVerification

    def _loadTrustAnchorCertificates(self):
        """
        The configuration file allows 'trust anchor' certificates to be preloaded.
        The certificates may also be loaded from a directory, and if the 'refresh'
        option is set to an interval, the certificates are reloaded at the
        specified interval
        """

        try:
            anchors = self.config["validator/trust-anchor"]
        except KeyError:
            return

        for anchor in anchors:
            typeName = anchor["type"][0].getValue()
            if typeName == 'file':
                certID = anchor["file-name"][0].getValue()
                isPath = True
            elif typeName == 'base64':
                certID = anchor["base64-string"][0].getValue()
                isPath = False
            elif typeName == "dir":
                dirName = anchor["dir"][0].getValue()
                try:
                    refreshPeriodStr = anchor["refresh"][0].getValue()
                except KeyError:
                    refreshPeriod = 0
                else:
                    refreshMatch = re.match('(\\d+)([hms])', refreshPeriodStr)
                    if not refreshMatch:
                        refreshPeriod = 0
                    else:
                        refreshPeriod = int(refreshMatch.group(1))
                        if refreshMatch.group(2) != 's':
                            refreshPeriod *= 60
                            if refreshMatch.group(2) != 'm':
                                refreshPeriod *= 60

                # Convert refreshPeriod from seconds to milliseconds.
                self._refreshManager.addDirectory(dirName, refreshPeriod * 1000)
                continue
            elif typeName == "any":
                # this disables all security!
                self.requiresVerification = False
                break

            if self._isSecurityV1:
                self._lookupCertificate(certID, isPath)
            else:
                self._lookupCertificateV2(certID, isPath)

    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

    def _lookupCertificate(self, certID, isPath):
        """
        This looks up certificates specified as base64-encoded data or file names.
        These are cached by filename or encoding to avoid repeated reading of files
        or decoding.

        :return: The certificate object, or None if not found.
        :rtype: IdentityCertificate
        """
        if not self._isSecurityV1:
            raise SecurityException(
              "lookupCertificate: For security v2, use lookupCertificateV2()")

        try:
            certUri = self._fixedCertificateCache[certID]
        except KeyError:
            if isPath:
                # load the certificate data (base64 encoded IdentityCertificate)
                cert = TrustAnchorRefreshManager.loadIdentityCertificateFromFile(
                        certID)
            else:
                certData = b64decode(certID)
                cert = IdentityCertificate()
                cert.wireDecode(Blob(certData, False))

            certUri = cert.getName()[:-1].toUri()
            self._fixedCertificateCache[certID] = certUri
            self._certificateCache.insertCertificate(cert)
        else:
            cert = self._certificateCache.getCertificate(Name(certUri))

        return cert

    def _lookupCertificateV2(self, certID, isPath):
        """
        This looks up certificates specified as base64-encoded data or file
        names. These are cached by filename or encoding to avoid repeated
        reading of files or decoding.

        :return: The CertificateV2, or None if not found.
        :rtype: CertificateV2
        """
        if self._isSecurityV1:
            raise SecurityException(
              "lookupCertificateV2: For security v1, use lookupCertificate()")

        try:
            certUri = self._fixedCertificateCache[certID]
        except KeyError:
            if isPath:
                # load the certificate data (base64 encoded IdentityCertificate)
                cert = TrustAnchorRefreshManager.loadCertificateV2FromFile(
                        certID)
            else:
                certData = b64decode(certID)
                cert = CertificateV2()
                cert.wireDecode(Blob(certData, False))

            certUri = cert.getName()[:-1].toUri()
            self._fixedCertificateCache[certID] = certUri
            self._certificateCacheV2.insert(cert)
        else:
            cert = self._certificateCacheV2.find(Name(certUri))

        return cert

    def _findMatchingRule(self, objName, matchType):
        """
        Search the configuration file for the first rule that matches the data
        or signed interest name. In the case of interests, the name to match
        should exclude the timestamp, nonce, and signature components.
        :param Name objName: The name to be matched.
        :param string matchType: The rule type to match, "data" or "interest".
        """
        rules = self.config["validator/rule"]
        for r in rules:
            if r['for'][0].getValue() == matchType:
                passed = True
                try:
                    filters = r['filter']
                except KeyError:
                    # no filters means we pass!
                    return r
                else:
                    for f in filters:
                        # don't check the type - it can only be name for now
                        # we need to see if this is a regex or a relation
                        regexPattern = f.getFirstValue("regex")
                        if regexPattern == None:
                            matchRelation =f.getFirstValue("relation")
                            matchUri = f.getFirstValue("name")
                            matchName = Name(matchUri)
                            passed = self._matchesRelation(objName, matchName, matchRelation)
                        else:
                            passed =  NdnRegexTopMatcher(regexPattern).match(objName)

                        if not passed:
                            break
                    if passed:
                        return r

        return None

    @staticmethod
    def _matchesRelation(name, matchName, matchRelation):
        """
        Determines if a name satisfies the relation to another name, which can
        be one of:
            'is-prefix-of' - passes if the name is equal to or has the other
               name as a prefix
            'is-strict-prefix-of' - passes if the name has the other name as a
               prefix, and is not equal
            'equal' - passes if the two names are equal
        """
        passed = False
        if matchRelation == 'is-strict-prefix-of':
            if matchName.size() == name.size():
                passed = False
            elif matchName.match(name):
                passed = True
        elif matchRelation == 'is-prefix-of':
            if matchName.match(name):
                passed = True
        elif matchRelation == 'equal':
            if matchName.equals(name):
                passed = True
        return passed

    @staticmethod
    def _extractSignature(dataOrInterest, wireFormat=None):
        """
        Extract the signature information from the interest name or from the
        data packet.
        :param dataOrInterest: The object whose signature is needed.
        :type dataOrInterest: Data or Interest
        :param WireFormat wireFormat: (optional) The wire format used to decode
          signature information from the interest name.
        """
        if isinstance(dataOrInterest, Data):
            return dataOrInterest.getSignature()
        elif isinstance(dataOrInterest, Interest):
            if wireFormat is None:
                # Don't use a default argument since getDefaultWireFormat can change.
                wireFormat = WireFormat.getDefaultWireFormat()
            try:
                signature = wireFormat.decodeSignatureInfoAndValue(
                   dataOrInterest.getName().get(-2).getValue().buf(),
                   dataOrInterest.getName().get(-1).getValue().buf(), False)
            except (IndexError, ValueError):
                return None
            return signature
        return None

    def _interestTimestampIsFresh(self, keyName, timestamp, failureReason):
        """
        Determine whether the timestamp from the interest is newer than the last use
        of this key, or within the grace interval on first use.

        :param Name keyName: The name of the public key used to sign the interest.
        :paramt int timestamp: The timestamp extracted from the interest name.
        :param Array<str> failureReason: If verification fails, set
          failureReason[0] to the failure reason string.
        """
        try:
            lastTimestamp = self._keyTimestamps[keyName.toUri()]
        except KeyError:
            now = Common.getNowMilliseconds()
            notBefore = now - self._keyGraceInterval
            notAfter = now + self._keyGraceInterval
            if not (timestamp > notBefore and timestamp < notAfter):
                return False
                failureReason[0] = (
                  "The command interest timestamp is not within the first use grace period of " +
                  str(self._keyGraceInterval) + " milliseconds.")
            else:
                return True
        else:
            if timestamp <= lastTimestamp:
                failureReason[0] = (
                  "The command interest timestamp is not newer than the previous timestamp")
                return False
            else:
                return True

    def _updateTimestampForKey(self, keyName, timestamp):
        """
        Trim the table size down if necessary, and insert/update the latest
        interest signing timestamp for the key.

        Any key which has not been used within the TTL period is purged. If the
        table is still too large, the oldest key is purged.

        :param Name keyName: The name of the public key used to sign the interest.
        :paramt int timestamp: The timestamp extracted from the interest name.

        """
        self._keyTimestamps[keyName.toUri()] = timestamp

        if len(self._keyTimestamps) >= self._maxTrackedKeys:
            now = Common.getNowMilliseconds()
            oldestTimestamp = now
            oldestKey = None
            trackedKeys = self._keyTimestamps.keys()
            for keyUri in trackedKeys:
                ts = self._keyTimestamps[keyUri]
                if now - ts > self._keyTimestampTtl:
                    del self._keyTimestamps[keyUri]
                elif ts < oldestTimestamp:
                    oldestTimestamp = ts
                    oldestKey = keyUri

            if len(self._keyTimestamps) > self._maxTrackedKeys:
                # have not removed enough
                del self._keyTimestamps[oldestKey]

    def checkVerificationPolicy(self, dataOrInterest, stepCount, onVerified,
                                onValidationFailed, wireFormat = None):
        """
        If there is a rule matching the data or interest, and the matching
        certificate is missing, download it. If there is no matching rule,
        verification fails. Otherwise, verify the signature using the public key
        in the IdentityStorage.

        :param dataOrInterest: The Data object or interest with the signature to
          check.
        :type dataOrInterest: Data or Interest
        :param int stepCount: The number of verification steps that have been
          done, used to track the verification progress.
        :param onVerified: If the signature is verified, this calls
          onVerified(dataOrInterest).
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onVerified: function object
        :param onValidationFailed: If the signature check fails, this calls
          onValidationFailed(dataOrInterest, reason).
          NOTE: The library will log any exceptions raised by this callback, but
          for better error handling the callback should catch and properly
          handle any exceptions.
        :type onValidationFailed: function object
        :return: None for no further step for looking up a certificate chain.
        :rtype: ValidationRequest
        """
        objectName = dataOrInterest.getName()
        matchType = "data"

        # For command interests, we need to ignore the last 4 components when
        #   matching the name.
        if isinstance(dataOrInterest, Interest):
            objectName = objectName.getPrefix(-4)
            matchType = "interest"

        signature = self._extractSignature(dataOrInterest, wireFormat)
        # no signature -> fail
        if signature is None:
            try:
                onValidationFailed(
                  dataOrInterest, "Cannot extract the signature from " +
                  dataOrInterest.getName().toUri())
            except:
                logging.exception("Error in onValidationFailed")
            return None

        failureReason = ["unknown"]
        certificateInterest = self._getCertificateInterest(
          stepCount, matchType, objectName, signature, failureReason)
        if certificateInterest is None:
            try:
                onValidationFailed(dataOrInterest, failureReason[0])
            except:
                logging.exception("Error in onValidationFailed")
            return None

        if certificateInterest.getName().size() > 0:
            def onCertificateDownloadComplete(data):
                if self._isSecurityV1:
                    try:
                        certificate = IdentityCertificate(data)
                    except:
                        try:
                            onValidationFailed(
                              dataOrInterest, "Cannot decode certificate " +
                              data.getName().toUri())
                        except:
                            logging.exception("Error in onValidationFailed")
                        return None
                    self._certificateCache.insertCertificate(certificate)
                else:
                    try:
                        certificate = CertificateV2(data)
                    except:
                        try:
                            onValidationFailed(
                              dataOrInterest, "Cannot decode certificate " +
                              data.getName().toUri())
                        except:
                            logging.exception("Error in onValidationFailed")
                        return None
                    self._certificateCacheV2.insert(certificate)

                self.checkVerificationPolicy(dataOrInterest, stepCount+1,
                        onVerified, onValidationFailed)

            return ValidationRequest(certificateInterest,
                    onCertificateDownloadComplete, onValidationFailed,
                    2, stepCount+1)

        # For interests, we must check that the timestamp is fresh enough.
        # This is done after (possibly) downloading the certificate to avoid
        # filling the cache with bad keys.
        if isinstance(dataOrInterest, Interest):
            signatureName = KeyLocator.getFromSignature(signature).getKeyName()
            if self._isSecurityV1:
                keyName = IdentityCertificate.certificateNameToPublicKeyName(
                  signatureName)
            else:
                keyName = signatureName
            timestamp = dataOrInterest.getName().get(-4).toNumber()

            if not self._interestTimestampIsFresh(
                  keyName, timestamp, failureReason):
                try:
                    onValidationFailed(dataOrInterest, failureReason[0])
                except:
                    logging.exception("Error in onValidationFailed")
                return None

        # Certificate is known. Verify the signature.
        # wireEncode returns the cached encoding if available.
        if self._verify(signature, dataOrInterest.wireEncode(), failureReason):
            try:
                onVerified(dataOrInterest)
            except:
                logging.exception("Error in onVerified")
            if isinstance(dataOrInterest, Interest):
                self._updateTimestampForKey(keyName, timestamp)
        else:
            try:
                onValidationFailed(dataOrInterest, failureReason[0])
            except:
                logging.exception("Error in onValidationFailed")

    def _getCertificateInterest(self, stepCount, matchType, objectName,
           signature, failureReason):
        """
        This is a helper for checkVerificationPolicy to verify the rule and
        return a certificate interest to fetch the next certificate in the
        hierarchy if needed.

        :param int stepCount: The number of verification steps that have been
          done, used to track the verification progress.
        :param str matchType: Either "data" or "interest".
        :param Name objectName: The name of the data or interest packet.
        :param Signature signature: The Signature object for the data or
          interest packet.
        :param Array<str> failureReason: If can't determine the interest, set
          failureReason[0] to the failure reason.
        :return: None if can't determine the interest, otherwise the interest
          for the ValidationRequest to fetch the next certificate. However, if
          the interest has an empty name, the validation succeeded and no need
          to fetch a certificate.
        :rtype: Interest
        """
        if stepCount > self._maxDepth:
            failureReason[0] = ("The verification stepCount " + stepCount +
                  " exceeded the maxDepth " + self._maxDepth)
            return None

        # First see if we can find a rule to match this packet.
        try:
            matchedRule = self._findMatchingRule(objectName, matchType)
        except:
            matchedRule = None

        # No matching rule -> fail.
        if matchedRule is None:
            failureReason[0] = "No matching rule found for " + objectName.toUri()
            return None

        if not KeyLocator.canGetFromSignature(signature):
            # We only support signature types with key locators.
            failureReason[0] = "The signature type does not support a KeyLocator"
            return None

        keyLocator = KeyLocator.getFromSignature(signature)

        signatureName = keyLocator.getKeyName()
        # No key name in KeyLocator -> fail.
        if signatureName.size() == 0:
            failureReason[0] = "The signature KeyLocator doesn't have a key name"
            return None

        signatureMatches = self._checkSignatureMatch(
          signatureName, objectName, matchedRule, failureReason)
        if not signatureMatches:
            return None

        # Before we look up keys, refresh any certificate directories.
        self._refreshManager.refreshAnchors()

        # If we don't actually have the certificate yet, return a
        #   certificateInterest for it.
        if self._isSecurityV1:
            foundCert = self._refreshManager.getCertificate(signatureName)
            if foundCert is None:
                foundCert = self._certificateCache.getCertificate(signatureName)
            if foundCert is None:
                return Interest(signatureName)
        else:
            foundCert = self._refreshManager.getCertificateV2(signatureName)
            if foundCert is None:
                foundCert = self._certificateCacheV2.find(signatureName)
            if foundCert is None:
                return Interest(signatureName)

        return Interest()

    def _verify(self, signatureInfo, signedBlob, failureReason):
        """
        Check the type of signatureInfo to get the KeyLocator. Look in the
        IdentityStorage for the public key with the name in the KeyLocator and
        use it to verify the signedBlob. If the public key can't be found,
        return false. (This is a generalized method which can verify both a Data
        packet and an interest.)

        :param Signature signatureInfo: An object of a subclass of Signature,
          e.g. Sha256WithRsaSignature.
        :param SignedBlob signedBlob: the SignedBlob with the signed portion to
          verify.
        :param Array<str> failureReason: If verification fails, set
          failureReason[0] to the failure reason string.
        :return: True if the signature verifies, False if not.
        :rtype: boolean
        """
        # We have already checked once that there is a key locator.
        keyLocator = KeyLocator.getFromSignature(signatureInfo)

        if (keyLocator.getType() == KeyLocatorType.KEYNAME):
            # Assume the key name is a certificate name.
            signatureName = keyLocator.getKeyName()

            if self._isSecurityV1:
                certificate = self._refreshManager.getCertificate(signatureName)
                if certificate is None:
                    certificate = self._certificateCache.getCertificate(
                      signatureName)
                if certificate is None:
                    failureReason[0] = ("Cannot find a certificate with name " +
                      signatureName.toUri())
                    return False

                publicKeyDer = certificate.getPublicKeyInfo().getKeyDer()
                if publicKeyDer.isNull():
                    # We don't expect this to happen.
                    failureReason[0] = (
                      "There is no public key in the certificate with name " +
                      certificate.getName().toUri())
                    return False
            else:
                certificate = self._refreshManager.getCertificateV2(signatureName)
                if certificate is None:
                    certificate = self._certificateCacheV2.find(
                      signatureName)
                if certificate is None:
                    failureReason[0] = ("Cannot find a certificate with name " +
                      signatureName.toUri())
                    return False

                try:
                    publicKeyDer = certificate.getPublicKey()
                except:
                    # We don't expect this to happen.
                    failureReason[0] = (
                      "There is no public key in the certificate with name " +
                      certificate.getName().toUri())
                    return False

            if self.verifySignature(signatureInfo, signedBlob, publicKeyDer):
                return True
            else:
                failureReason[0] = (
                  "The signature did not verify with the given public key")
                return False
        else:
            failureReason[0] = "The KeyLocator does not have a key name"
            return False