New Security Layer

As I said before, I've dropped the tlslite integration in favor of a custom security layer, as python now has decent SSL support. The main reason is that the only hassle-free way to use tlslite is by username-password authentication. The second reason is, tlslite is no longer maintained and emits warnings when used on python 2.6, and does not work on python 3.0.

A Little about SSL

You see, SSL was conceived to solve a different problem: untrusted servers. When you connect to an internet site, you want to be sure there's no man-in-the-middle, and that you were really are browsing the site you meant to browse. That way you don't give your credit card details away. That was the state of mind of SSL.

Because of this, it has a notion of central body of authority, i.e., certificate authority chains. You (or your browser) trust the CA and whomever it has signed. That, however, is not the common use case on local networks, where RPyC is normally used. Having a central authority is good when there's a small number of servers and a huge number of unknown clients. When the ratio of servers / clients is larger, and there's a known set of clients, the model used by SSH is much more pragmatic.

A Little about SSH

In the SSH model, the two parties (client and server) are mutually authenticated, based on their public-key identity. The client knows its server's public key (known_hosts), and the server knows its clients' public keys (authorized_keys). Without going into asymmetric cryptography, this allows both parties to "strongly authenticate" the other party, and even prevents man-in-the-middle attacks. All in all, the SSH model is widely used and has been proven safe.

SSL certificates also allow mutual authentication, but the overhead and hassle needed to set it up is simply too large and most people just give up on it. SSH is easy to maintain and scale up.

Paramiko

Paramiko, a pure python implementation of SSH, was on my todo-list for long, and last week I decided to finally have a look at it. SSH is oriented for shell and file transfer, so it has a notion of different subsystems, multiplexed over the same transport, and sadly there was no easy way to simply create a secure socket. After a day of toying around with it, I managed to open a socket-like channel, but the overhead seemed far too much. Moreover, each transport starts a background thread and it seemed to be causing deadlocks in RPyC (on the other hand, I hadn't looked into it, and it could as well have been that my 'secure socket' implementation was bad). At the end of the day, though, I felt like I'm using a shotgun to kill a fly. SSH per se, and Paramiko, were simply too much.

X509

This made me rethink the whole issue, and I turned back to X509 certificates (despite my dislike of them). I realized I could create a self-signed root certificate per server, and a certificate per client. I could then use the server's certificate to sign each authorized client, and then both parties can authenticate themselves. But that meant that if a client were to connect to more than a single server, it had to have a pool of certificates… it meant that for N clients and M servers, you have N*M + M certificates floating around and needing maintenance. I scratched it out the next day and settled to write my own security layer.

What We Ended Up With

So after reading some RFCs and wikipedia, I realized cryptography isn't that complicated. Wait a sec before bashing me! First of all, I'm using the same algorithms used by SSH. Second, I'm using the well known and established PyCrypto lib… I'm not generating random numbers myself. And third, I'm having it reviewed by several peers with long experience in the domain.

The basis of the new security layer is encrypted but unauthenticated SSL — it simply means no one can eavesdrop on your session, but you don't know who you're talking to. On top of that, I added two adjacent layers: username-password based authentication, and public-key based authentication.

The username-password layer is much like what we have at the moment: the server authenticates your, and you trust the server blindly. It also means you have clear-text passwords in your code, and it is susceptible to man-in-the-middle attacks. I personally don't like being paranoid about security, and since RPyC in normally used on local networks, where you trust your peers or coworkers, I wouldn't trouble myself with combating such attacks. But I did, anyway.

The public-key based authenticator works just like SSH: the client has a database of the server's public keys (known_hosts), and the server has a database of the authorized clients' public keys (authorized_keys), and each party has a secret private key, which allows:

  • The server to authenticate the client
  • The client to authenticate the server
  • Prevent man-in-the-middle attacks (DNS poisoning, etc.)

I added a tool to manage these public key databases, but as nothing is stable at the moment, I will not go into details on it.

Layering

One nice feature of the authenticators used by RPyC is composability: you can stack them one over the other to achieve multiple levels of security. This is true both for the current and the new security layers. For example, see one user wanted allow only specific IPs to connect to his servers, as an additional layer. Without judging the effectiveness of filtering IPs, adding and combining layers is very easy:

def ip_authenticator(allowed_ips):
    def authorize(sock):
        if sock.getpeername()[0] not in allowed_ips:
            raise AuthenticationError("unknown ip")
        return sock, None 
    return authorize
 
def ip_and_vdb(allowed_ips, vdbauth):
    ipauth = ip_authenticator(allowed_ips)
    def authorize(sock):
        sock2, _ = ipauth(sock)
        return vdbauth(sock2)
    return authorize
 
vdb = VdbAuthenticator.from_file(...)
allowed_ips = set([...])
s = ThreadedServer(..., authenticator = ip_and_vdb(allowed_ips, vdb), ...)
s.start()

Functional composition rocks :)


Comments

Add a new comment
page_revision: 1, last_edited: 1248515179|%e %b %Y, %H:%M %Z (%O ago)
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License