[Twisted-Python] SSL + AMP

Brian Warner warner at lothar.com
Thu Mar 20 18:59:17 EDT 2008

> >  Does foolscap already support such fall-back behavior for coping with
> >  firewalls? Or does that break the security model?

Not yet, and yes.

> "server A can introduce a client B to C by passing B the FURL to some
> object hosted in C's tub"

A motivating use case would be a chat system, in which the central server was
used to introduce clients to each other, and all messages were sent directly
from one client to another. B and C might be two separate clients, and the
point of introduction is when B wants to connect to C.

The server can stash a reference to an object in C's tub, or a FURL to that
object that was generated by C. It can then send one of these to B (perhaps
as an argument to publish message, or a response to a "get all connected
clients" request message). The gift/introduction code in Foolscap lets you
send live references (i.e. RemoteReference instances) to third parties, and
in the current release this causes the FURL of the target to be delivered and
automatically connected to.

But of course this requires that the recipient of the gift be able to connect
to the target, and if that target is behind a firewall then the introduction
will fail.

We don't have any sort of relay or hole-punching code in place yet. As a
result, in Tahoe (http://allmydata.org), the few places that use Introduction
are careful to arrange for the target of the introduction (the "gift") to be
on the publically-visible server, rather than on the probably-behind-NAT

Possible solutions:

1: non-Membrane plaintext Forwarder

   It is pretty easy to build a server-side proxy (called a Forwarder) that
   has a doRemoteCall() method that just echoes the request on to another
   object. Each time B sends a message to that-which-they-think-is-C, really
   they're sending it to a Forwarder on A, and the Forwarder sends the
   request on to C, gets the response, and forwards the response back to B.

   This takes about 5 lines of code:

     class Forwarder(Referenceable):
       def __init__(self, target):
         self.target = target
       def doRemoteCall(self, methname, args, kwargs):
         return self.target.callRemote(methname, *args, **kwargs)

   The first disadvantage is that the Forwarder gets to see (and control) all
   messages, so you're vulnerable to it for confidentiality and integrity.
   The second disadvantage is that if there are other object references in
   the arguments or the responses, those need to be wrapped in Forwarders
   too. This is a job for the "Membrane" pattern.

2: yes-Membrane plaintext Forwarder

   Eventually Foolscap will have utility functions for building easy
   membranes, but not yet. (foolscap ticket #44). Having this would remove
   the prohibitions on putting object references in your arguments and return
   values, but doesn't remove the vulnerability to the forwarder.

3: per-message ciphertext proxy

   Like the Forwarder, but you change the client to encrypt all its messages
   first, and the server to decrypt them. This keeps the proxy from seeing
   the contents of the messages. Encrypt the responses too. Add more crypto
   goo to make sure the proxy can't corrupt either.

   Foolscap has some support for serializing arbitrary object graphs, which
   would help. The "Sealer/Unsealer" code (foolscap ticket #20) that is under
   development will handle both the serialization and the encryption. You'd
   need some extra layer to figure out what keys to use.

4: server-side per-Tub ciphertext proxy

   The Foolscap negotiation protocol is specifically designed to provide for
   multiple Tubs listening on the same port. We build a Relay process to
   which the NAT-bound Tubs can attach, registering themselves with a (tubid,
   listener_rref) tuple. This listener_rref object has a single accept()
   method, which returns a handler_rref object. When B wants to connect to C,
   the FURL it uses will contain C's tubid but will have connection hints
   that point to the relay's listening socket. When B connects to the relay
   and says it wants to talk to tubidC, the relay (during the negotiation
   process) tells the listener_rref that it wants a new connection, gets the
   handler_rref, then switches into a simple proxy mode where it copies data
   from the B-side connection into remote_write() messages sent to the
   handler_rref. On C, the contents of these messages are written into a
   loopback TCP socket that mimics the data being exchanged between B and the
   relay. Responses generated by C get proxied backwards to the relay over
   the same connection.

   This requires more code, but it's the "right way to do it" for relay. The
   intermediate relay doesn't get to see or corrupt the traffic that it is
   carrying, and the remote objects on B and C don't need to be aware that
   anything unusual is going on. C needs to register with the relay, and the
   FURL needs to be created with the right connection hints, but that can be
   done once at startup (instead of requiring that every single message be
   specially encrypted).

   This one is tracked with foolscap ticket #46, copied directly from this


More information about the Twisted-Python mailing list