Beispiel #1
0
class DHTResolver(object):
    def __init__(self, config, bootstrapNeighbors):
        self.config = config
        self.log = Logger(system=self)
        if os.path.isfile(config['dht.state.cache']):
            self.kserver = Server.loadState(config['dht.state.cache'])
        else:
            self.kserver = Server()
            self.kserver.bootstrap(bootstrapNeighbors)
        self.kserver.saveStateRegularly(config['dht.state.cache'], 60)

    def getProtocol(self):
        return self.kserver.protocol

    def getPublicKey(self, keyId):
        """
        Get the public key from the network, and return only if the key is
        the one that matches the keyId based on hash.
        """
        def verify(key):
            if key is not None and PublicKey(key).getKeyId() == keyId:
                return key
            return None

        self.log.debug("Getting key text for key id %s" % keyId)
        return self.kserver.get(keyId).addCallback(verify)

    def resolve(self, keyId):
        def parse(locations):
            self.log.debug("Locations for %s: %s" % (keyId, locations))
            results = []
            if locations is None or locations == "":
                return results
            for location in locations.split(','):
                host, port = location.split(':')
                results.append((host, int(port)))
            return results
        d = self.kserver.get("%s-location" % keyId)
        return d.addCallback(parse)

    def announceLocation(self, myKeyId, myPublicKey):
        def announce(ips):
            ips = self.localAddresses() + ips
            ipports = map(lambda ip: "%s:%i" % (ip, self.config['s2s.port']), ips)
            return self.kserver.set("%s-location" % myKeyId, ",".join(ipports))
        d = self.kserver.set(myKeyId, str(myPublicKey))
        d.addCallback(lambda _: self.kserver.inetVisibleIP())
        return d.addCallback(announce)

    def localAddresses(self):
        result = []
        for iface in netifaces.interfaces():
            addys = netifaces.ifaddresses(iface).get(netifaces.AF_INET)
            result += [ addy['addr'] for addy in (addys or []) if addy['addr'] != '127.0.0.1' ]
        return result
Beispiel #2
0
    def __init__(self, keyPath, authorizedKeysDir):
        """
        Handle all key interactions.

        @param keyPath: Path to the key to use as this server's id. If
        it doesn't exist, it will be created.

        @param authorizedKeysDir: Path to a directory to read/write
        authorized keys to.  They are stored in memory in the keystore
        upon this object's construction.
        """
        self.log = Logger(system=self)

        # first, check our key
        if not os.path.isfile(keyPath):
            self.log.debug("%s doesn't exist; creating new keypair" % keyPath)
            self.kpair = KeyPair.generate()
            self.kpair.store(keyPath)
        else:
            self.log.debug("using %s as this node's keypair" % keyPath)
            self.kpair = KeyPair.load(keyPath)

        # load other keys
        self.authorizedKeysDir = authorizedKeysDir
        self.refreshAuthorizedKeys()
Beispiel #3
0
 def __init__(self, resolver, contextFactory, keyStore, storage):
     self.connections = {}
     self.resolver = resolver
     self.contextFactory = contextFactory
     self.keyStore = keyStore
     self.storage = storage
     self.log = Logger(system=self)
Beispiel #4
0
class FriendsList(object):
    def __init__(self, storage, keyStore, resolver):
        self.storage = storage
        self.keyStore = keyStore
        self.resolver = resolver
        self.log = Logger(system=self)

    def addFriendById(self, name, keyId):
        """
        Lookup a public key with the given keyId and save if found.
        """
        d = self.resolver.getPublicKey(keyId)
        return d.addCallback(self._addFriendById, name)

    def _addFriendById(self, keyvalue, name):
        if keyvalue is None:
            raise KeyNotResolvedError("Could not find key for %s" % name)
        return self.addFriend(keyvalue, name)

    def addFriend(self, publicKey, name):
        """
        Add a friend with the given public key.
        """
        self.log.debug("Adding key belonging to %s: %s" % (name, publicKey))
        pk = PublicKey(publicKey)
        path = str(Path(pk.getKeyId()))
        self.storage.grantAccess(pk.getKeyId(), path)
        self.keyStore.setAuthorizedKey(pk, name)
        f = Friend(pk.getKeyId(), name, publicKey)
        return defer.succeed(f)

    def removeFriend(self, name):
        return self.keyStore.removeAuthorizedKey(name)

    def getFriends(self):
        friends = []
        for key in self.keyStore.getAuthorizedKeysList():
            friend = Friend(key.getKeyId(), key.name, str(key))
            friends.append(friend)
        return friends

    def __iter__(self):
        for friend in self.getFriends():
            yield friend

    def __len__(self):
        return len(self.getFriends())
Beispiel #5
0
 def __init__(self, keyStore, storage, resolver):
     self.keyStore = keyStore
     self.storage = storage
     self.contextFactory = PFSContextFactory(self.keyStore)
     self.pool = ConnectionPool(resolver, self.contextFactory, self.keyStore, self.storage)
     self.protocolFactory = TintProtocolFactory(self.pool)
     self.friends = FriendsList(self.storage, self.keyStore, resolver)
     self.log = Logger(system=self)
Beispiel #6
0
class AnyDBMStorage(ObservableStorage):
    implements(IStorage)

    def __init__(self, filename):
        super(ObservableStorage, self).__init__()
        self.filename = filename
        self.log = Logger(system=self)
        self.db = anydbm.open(self.filename, 'c')

    def get(self, key, default=None):
        key = Path.normalize(key)
        self.log.debug("Getting %s" % key)
        value = self.db[key] if key in self.db else default
        return defer.succeed(value)

    def set(self, key, value):
        key = Path.normalize(key)
        self.log.debug("Setting %s = %s" % (key, value))
        preexisting = key in self.db
        self.db[key] = str(value)
        return self.publishChange(key, value, preexisting)

    def push(self, key, value):
        path = Path(key)
        mkey = path.join('_maxid').path

        def dopush(current):
            id = int(current) + 1
            vkey = path.join(id).path
            d = defer.gatherResults([self.set(mkey, id), self.set(vkey, value)])
            return d.addCallback(lambda _: self.publishChange(vkey, value, False))
        return self.get(mkey, '-1').addCallback(dopush)

    def ls(self, key, offset=0, length=100):
        length = min(length, 1000)
        kids = set([])
        path = Path(key)
        for k in imap(Path, self.db.keys()):
            if k in path:
                kids.add(path.childFrom(k))
        kids = [ str(kid) for kid in kids if kid != '_maxid' ]
        kids.sort()
        results = []
        for k in kids[offset:][:length]:
            results.append({ 'key': k, 'value': self.db[path.join(k).path] })
        return defer.succeed(results)
Beispiel #7
0
 def __init__(self, config, bootstrapNeighbors):
     self.config = config
     self.log = Logger(system=self)
     if os.path.isfile(config['dht.state.cache']):
         self.kserver = Server.loadState(config['dht.state.cache'])
     else:
         self.kserver = Server()
         self.kserver.bootstrap(bootstrapNeighbors)
     self.kserver.saveStateRegularly(config['dht.state.cache'], 60)
Beispiel #8
0
 def __init__(self, connectionPool, timeout=10):
     """
     We have access to the connection pool so we can store connections
     there after they've been successfully made.
     """
     MsgPackProtocol.__init__(self, timeout)
     self.connectionPool = connectionPool
     self.log = Logger(system=self)
     self.storage = self.connectionPool.storage
     self.peersKeyId = None
Beispiel #9
0
class ConnectionPool(object):
    def __init__(self, resolver, contextFactory, keyStore, storage):
        self.connections = {}
        self.resolver = resolver
        self.contextFactory = contextFactory
        self.keyStore = keyStore
        self.storage = storage
        self.log = Logger(system=self)

    def send(self, keyId, cmd, *args):
        if keyId in self.connections:
            return self.sendOnConnection(self.connections[keyId], cmd)
        d = self.resolver.resolve(keyId)
        d.addCallback(self.createConnection, keyId)
        return d.addCallback(self.sendOnConnection, cmd, args)

    def sendOnConnection(self, connection, cmd, args):
        if connection is None:
            return False
        return connection.sendCommand(cmd, args)

    def createConnection(self, addr, keyId):
        if addr is None:
            return False

        host, port = addr
        cc = ClientCreator(reactor, TintProtocol, self)
        d = cc.connectSSL(host, port, self.contextFactory)
        return d.addCallback(self.saveConnection, keyId)

    def forgetConnection(self, keyId):
        self.log.info("removing connection %s from pool" % keyId)
        del self.connections[keyId]

    def saveConnection(self, connection, keyId):
        self.connections[keyId] = connection
        self.log.info("saving connection %s in pool" % keyId)
        return connection
Beispiel #10
0
class DefaultPermissions(object):
    def __init__(self, storage):
        self.storage = storage
        self.log = Logger(system=self)

    def accessAvailable(self, requestor, key):
        def gather(results):
            access = set()
            for result in results:
                if result is not None:
                    access.update(result.split(','))
            return list(access)

        self.log.info("Testing access for %s to %s" % (requestor, key))
        ds = []
        for path in Path(key).ancestors():
            path = str(Path('a').join(requestor).join(path))
            ds.append(self.storage.get(path, None))
        return defer.gatherResults(ds).addCallback(gather)

    def canAccess(self, requestor, key, optype='*'):
        """
        @param key The path to the storage.  Should always start with a '/'.
        """
        def test(access):
            return '*' in access or optype in access
        d = self.accessAvailable(requestor, key)
        return d.addCallback(test)

    def grantAccess(self, requestor, key, optype="*"):
        def test(access):
            if not access:
                path = Path('a').join(requestor).join(Path(key))
                return self.storage.set(str(path), optype)
            return defer.succeed(optype)

        d = self.canAccess(requestor, key, optype)
        return d.addCallback(test)
Beispiel #11
0
class TintProtocol(MsgPackProtocol):
    def __init__(self, connectionPool, timeout=10):
        """
        We have access to the connection pool so we can store connections
        there after they've been successfully made.
        """
        MsgPackProtocol.__init__(self, timeout)
        self.connectionPool = connectionPool
        self.log = Logger(system=self)
        self.storage = self.connectionPool.storage
        self.peersKeyId = None

    def getPeersKeyId(self):
        if self.peersKeyId is None:
            cert = self.transport.getPeerCertificate()
            if cert is not None:
                issuerCommonName = cert.get_issuer().commonName
                key = self.connectionPool.keyStore.getIssuerPublicKey(issuerCommonName)
                self.peersKeyId = key.getKeyId()
        return self.peersKeyId

    def dataReceived(self, data):
        self.connectionPool.saveConnection(self, self.getPeersKeyId())
        self.log.debug("received data from %s: %s" % (self.getPeersKeyId(), data))
        MsgPackProtocol.dataReceived(self, data)

    def connectionLost(self, reason):
        keyId = self.getPeersKeyId()
        self.log.warning("Connection to %s lost: %s" % (keyId, str(reason)))
        self.connectionPool.forgetConnection(keyId)
        MsgPackProtocol.connectionLost(self, reason)

    def connectionFailed(self, reason):
        self.log.warning("Connection failed: %s" % str(reason))

    def cmd_get(self, key):
        return self.storage.get(self.getPeersKeyId(), key)

    def cmd_set(self, key, value):
        return self.storage.set(self.getPeersKeyId(), key, value)

    def cmd_push(self, key, value):
        return self.storage.push(self.getPeersKeyId(), key, value)

    def cmd_ls(self, key, offset, length):
        return self.storage.ls(self.getPeersKeyId(), key, offset, length)
Beispiel #12
0
class ConnectionPool(object):
    def __init__(self, resolver, contextFactory, keyStore, storage):
        self.connections = {}
        self.resolver = resolver
        self.contextFactory = contextFactory
        self.keyStore = keyStore
        self.storage = storage
        self.log = Logger(system=self)

    def send(self, keyId, cmd, *args):
        if keyId in self.connections:
            return self.sendOnConnection(self.connections[keyId], cmd, args)
        d = self.resolver.resolve(keyId)
        d.addCallback(self.createConnection, keyId)
        return d.addCallback(self.sendOnConnection, cmd, args)

    def sendOnConnection(self, connection, cmd, args):
        if connection is None:
            return False
        return connection.sendCommand(cmd, args)

    def createConnection(self, addrs, keyId):
        if len(addrs) == 0:
            raise HostUnreachableError("Cannot connect to %s" % keyId)

        host, port = addrs.pop()
        self.log.debug("Attempting to create connection to %s:%i" % (host, port))
        cc = ClientCreator(reactor, TintProtocol, self)
        d = cc.connectSSL(host, port, self.contextFactory, timeout=5)
        d.addCallback(self.saveConnection, keyId)
        if len(addrs) > 0:
            d.addErrback(lambda _: self.createConnection(addrs, keyId))
        return d

    def forgetConnection(self, keyId):
        self.log.info("removing connection %s from pool" % keyId)
        if keyId in self.connections:
            del self.connections[keyId]

    def saveConnection(self, connection, keyId):
        self.connections[keyId] = connection
        self.log.info("saving connection %s in pool" % keyId)
        return connection
Beispiel #13
0
 def __init__(self, filename):
     super(ObservableStorage, self).__init__()
     self.filename = filename
     self.log = Logger(system=self)
     self.db = anydbm.open(self.filename, 'c')
Beispiel #14
0
class KeyStore(service.Service):
    def __init__(self, keyPath, authorizedKeysDir):
        """
        Handle all key interactions.

        @param keyPath: Path to the key to use as this server's id. If
        it doesn't exist, it will be created.

        @param authorizedKeysDir: Path to a directory to read/write
        authorized keys to.  They are stored in memory in the keystore
        upon this object's construction.
        """
        self.log = Logger(system=self)

        # first, check our key
        if not os.path.isfile(keyPath):
            self.log.debug("%s doesn't exist; creating new keypair" % keyPath)
            self.kpair = KeyPair.generate()
            self.kpair.store(keyPath)
        else:
            self.log.debug("using %s as this node's keypair" % keyPath)
            self.kpair = KeyPair.load(keyPath)

        # load other keys
        self.authorizedKeysDir = authorizedKeysDir
        self.refreshAuthorizedKeys()

    def startService(self):
        self.log.debug("Using keypair id: %s" % self.getKeyId())
        service.Service.startService(self)

    def getKeyId(self):
        return self.kpair.getKeyId()

    def getPublicKey(self):
        return self.kpair.getPublicKey()

    def generateSignedTmpKeyPair(self, expiresIn):
        self.log.debug("Creating a new tmp keypair that expires in %i seconds" % expiresIn)
        return self.kpair.generateSignedTmpKeyPair(expiresIn)

    def refreshAuthorizedKeys(self):
        self.authorizedKeys = {}
        pemFilter = os.path.join(self.authorizedKeysDir, "*.pem")
        for fname in glob.glob(pemFilter):
            self.log.debug("Loading %s into authorized keys" % fname)
            keyId = PublicKey.load(fname).getIssuer()
            if keyId in self.authorizedKeys:
                msg = "There are two keys with the same issuer - %s and %s."
                msg += "  Only one can be used - please delete the duplicate."
                raise DuplicateIssuer(msg % (fname, self.authorizedKeys[keyId]))
            self.authorizedKeys[keyId] = fname

    def getIssuerPublicKey(self, issuerCommonName):
        # this shouldn't happen - if a connection is successfully made,
        # then that means the key *must* be in the local authorized keys.
        # However, this is a double check, cause safety first and all that.
        if issuerCommonName not in self.authorizedKeys:
            msg = "Issuer %s could not be found in local authorized keys"
            raise InvalidIssuer(msg % issuerCommonName)
        return PublicKey.load(self.authorizedKeys[issuerCommonName])

    def getAuthorizedKeysList(self):
        return [PublicKey.load(fname) for fname in self]

    def removeAuthorizedKey(self, fname):
        path = os.path.join(self.authorizedKeysDir, fname + ".pem")
        if not os.path.isfile(path):
            return False
        try:
            os.remove(path)
            self.refreshAuthorizedKeys()
            return True
        except:
            return False

    def setAuthorizedKey(self, publicKey, fname):
        path = os.path.join(self.authorizedKeysDir, fname + ".pem")

        if not fname.isalnum():
            msg = "Filename %s is not alpha numeric"
            raise InvalidAuthorizedKeyFilename(msg % fname)

        if path in self:
            msg = "There is already a key with the same filename - %s."
            msg += "  If you want to update - please delete first."
            raise DuplicateKeyFilename(msg % fname)

        if publicKey.getIssuer() in self.authorizedKeys:
            msg = "There are two keys with the same issuer - %s and %s."
            msg += "  Only one can be used - please delete the duplicate."
            raise DuplicateIssuer(msg % (fname, self.authorizedKeys[publicKey.getIssuer()]))

        publicKey.store(path)
        self.refreshAuthorizedKeys()

    def __len__(self):
        return len(self.authorizedKeys)

    def __iter__(self):
        return self.authorizedKeys.itervalues()
Beispiel #15
0
 def __init__(self, storage):
     self.storage = storage
     self.log = Logger(system=self)
Beispiel #16
0
 def __init__(self, storage, keyStore, resolver):
     self.storage = storage
     self.keyStore = keyStore
     self.resolver = resolver
     self.log = Logger(system=self)
Beispiel #17
0
class Peer(object):
    def __init__(self, keyStore, storage, resolver):
        self.keyStore = keyStore
        self.storage = storage
        self.contextFactory = PFSContextFactory(self.keyStore)
        self.pool = ConnectionPool(resolver, self.contextFactory, self.keyStore, self.storage)
        self.protocolFactory = TintProtocolFactory(self.pool)
        self.friends = FriendsList(self.storage, self.keyStore, resolver)
        self.log = Logger(system=self)

    def getKeyId(self):
        """
        Get the keyId used by this peer (this peer's identifier).

        This is stored in the key store.
        """
        return self.keyStore.getKeyId()

    def getPublicKey(self):
        """
        Get the keyId used by this peer (this peer's identifier).

        This is stored in the key store.
        """
        return self.keyStore.getPublicKey()

    def set(self, hostKeyId, storagePath, storageValue):
        """
        Set a value on a host.

        @param hostKeyId: The key id for the destination host to set the
        given key.  This could be the local host, in which case the hostKey
        will be the same as this C{Peer}'s keyStore keyId.

        @param storagePath: The path to the key to set.  For instance, this
        could be something like /chat/<somekey>/inbox.

        @param storageValue: The value to set.
        """
        if hostKeyId == self.getKeyId():
            return self.storage.set(hostKeyId, storagePath, storageValue)
        return self.pool.send(hostKeyId, 'set', storagePath, storageValue)

    def get(self, hostKeyId, storagePath):
        """
        Get a value from a host.

        @param hostKeyId: The key id for the destination host to get the
        given key.  This could be the local host, in which case the hostKey
        will be the same as this C{Peer}'s keyStore keyId.

        @param storagePath: The path to the key to get.  For instance, this
        could be something like /chat/<somekey>/inbox.
        """
        if hostKeyId == self.getKeyId():
            self.log.debug("getting storagePath %s on self" % storagePath)
            return self.storage.get(hostKeyId, storagePath)
        self.log.debug("getting storagePath %s on %s" % (storagePath, hostKeyId))
        return self.pool.send(hostKeyId, 'get', storagePath)

    def push(self, hostKeyId, storagePath, storageValue):
        """
        Given key, create a new key at <key>/<id> with the given value, where <id>
        is an auto-incrementing integer value starting at 0.
        """
        if hostKeyId == self.getKeyId():
            return self.storage.push(hostKeyId, storagePath, storageValue)
        return self.pool.send(hostKeyId, 'push', storagePath, storageValue)

    def ls(self, hostKeyId, storagePath, offset, length):
        """
        Given key, get all children keys (with the given offset and length).  Length cannot
        be more than 1000.
        """
        if hostKeyId == self.getKeyId():
            return self.storage.ls(hostKeyId, storagePath, offset, length)
        return self.pool.send(hostKeyId, 'ls', storagePath, offset, length)
Beispiel #18
0
class Peer(object):
    def __init__(self, keyStore, storage, resolver):
        self.keyStore = keyStore
        self.storage = storage
        self.resolver = resolver
        self.contextFactory = PFSContextFactory(self.keyStore)
        self.pool = ConnectionPool(self.resolver, self.contextFactory, self.keyStore, self.storage)
        self.protocolFactory = TintProtocolFactory(self.pool)
        self.log = Logger(system=self)

    def getKeyId(self):
        """
        Get the keyId used by this peer (this peer's identifier).

        This is stored in the key store.
        """
        return self.keyStore.getKeyId()

    def getPublicKey(self):
        """
        Get the keyId used by this peer (this peer's identifier).

        This is stored in the key store.
        """
        return self.keyStore.getPublicKey()

    def set(self, hostKeyId, storagePath, storageValue):
        """
        Set a value on a host.

        @param hostKeyId: The key id for the destination host to set the
        given key.  This could be the local host, in which case the hostKey
        will be the same as this C{Peer}'s keyStore keyId.

        @param storagePath: The path to the key to set.  For instance, this
        could be something like /chat/<somekey>/inbox.

        @param storageValue: The value to set.
        """
        if hostKeyId == self.getKeyId():
            return self.storage.set(hostKeyId, storagePath, storageValue)
        return self.pool.send(hostKeyId, 'set', storagePath, storageValue)

    def get(self, hostKeyId, storagePath):
        """
        Get a value from a host.

        @param hostKeyId: The key id for the destination host to get the
        given key.  This could be the local host, in which case the hostKey
        will be the same as this C{Peer}'s keyStore keyId.

        @param storagePath: The path to the key to get.  For instance, this
        could be something like /chat/<somekey>/inbox.
        """
        if hostKeyId == self.getKeyId():
            self.log.debug("getting storagePath %s on self" % storagePath)
            return self.storage.get(hostKeyId, storagePath)
        self.log.debug("getting storagePath %s on %s" % (storagePath, hostKeyId))
        return self.pool.send(hostKeyId, 'get', storagePath)

    def incr(self, hostKeyId, storagePath, amount=1, default=0):
        """
        Increment a value on a host.

        @param hostKeyId: The key id for the destination host to increment the
        given key.  This could be the local host, in which case the hostKey
        will be the same as this C{Peer}'s keyStore keyId.

        @param storagePath: The path to the key to increment.  For instance, this
        could be something like /chat/<somekey>/inbox.

        @param amount: The amount to increment.  This could be negative, which
        would make this actually a decrement.  By default this is 1.

        @param default: The amount to use as the default if no value exists
        at the given storagePath.  By default this is 0.
        """
        if hostKeyId == self.getKeyId():
            return self.storage.incr(hostKeyId, storagePath, amount, default)
        return self.pool.send(hostKeyId, 'incr', storagePath, amount, default)