[Twisted-Python] SSL + AMP
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
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
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:
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
This one is tracked with foolscap ticket #46, copied directly from this
More information about the Twisted-Python