About Me

Thursday, March 24, 2011

Welcome back to Twisted Conch in 60 Seconds, a series about writing custom SSH software with Twisted. Last time, I showed you how to set up a basic SSH server which could accept connections and then reject all authentication attempts. This time we'll extend that server to support password authentication. Let's consider the factory created in the first example:

The object in this snippet responsible for authenticating the client is Portal(None). However, this portal is missing two things it really needs in order to be useful. First, it's missing a username/password checker. Twisted includes one that's easy to use and reads credentials out of a file, so we'll start with that one:

from twisted.cred.checkers import FilePasswordDB

factory.portal.registerChecker(FilePasswordDB("ssh-passwords"))

FilePasswordDB will now try to read information from ssh-passwords in order to authenticate SSH client connection attempts. The file should be populated with lines like this:

alice:goodpasswordbob:badpasswordjpcalderone:supersecretpassword

The second thing the Portal needs is a Realm. After the FilePasswordDB says a user supplied the correct username/password combination, Conch needs an object that will represent the user who just authenticated. This object will be used to determine what actions the SSH user is allowed to take, and what consequences they will have. A realm only needs to implement one method:

After authentication succeeds, requestAvatar is called. The avatarId parameter tells the realm the name of the user who just authenticated successfully. The mind isn't used by Conch. The interfaces indicate what kind of user is being requested; in this case, it will include twisted.conch.interfaces.IConchUser (and we just assume that it does for now). The method must return a three-tuple. The first element is the kind of user the realm decided to give back (this must be one of the requested interfaces - again, we're assuming IConchUser for now). This just lets the calling code know what kind of user it ended up with. The second element is the user object itself. Conch conveniently provides us with a basic user class that implements almost no behavior, so it's suitable to be used directly in this simple example. The final element of the tuple is a logout callable. This will be invoked for us when the user logs out. This example has no custom logout logic, so we return a no-op function. Portal construction will now look like this:

factory.portal = Portal(SimpleRealm())

These are all the pieces necessary to do username/password authentication of SSH users. Here's the full code listing for this version:

Wednesday, March 23, 2011

Greetings once more, readers. A year ago I introduced you to the simplicity of web programming with Twisted. Since then, I've been eager to try to duplicate the success of the series for another topic. Welcome to Twisted Conch in 60 Seconds.

Twisted Conch implements the SSH protocol for both clients and servers. It also implements client and server applications. It is also a library for writing custom SSH client and server software. This series will focus on using Twisted Conch as an SSH library, and the first articles will cover writing custom SSH servers, with clients covered later.

In this first installment, the goal is to set up an SSH server which will accept SSH client connections butwhich cannot actually authenticate any users (so no one will be able to log in).

Before getting into any code, the first thing we need for an SSH server is a server key. This can be generated using ssh-keygen from OpenSSH or with Conch's own key generation tool, ckeygen. Since ample examples of ssh-keygen can be found elsewhere, here's an example of generating a key with ckeygen:

SSHFactory is an IProtocolFactory which is used to create protocol instances to handle new connections that arrive at a listening port. SSHFactory in particular creates protocol instances that can carry on the server side of an SSH connection. We need an instance to hook up to a listening port:

factory = SSHFactory()

Nothing very exciting going on there. SSHFactory doesn't take any initializer arguments. It does have some useful attributes you can set, though. Two important attributes are publicKeys and privateKeys. These define what key the server uses to identify itself to clients. A server can have multiple keys, so these attributes are bound to dictionaries. In this example we'll just give the server one key though. For this, we need to have a couple Key objects:

Clients connecting to this server will get to see this public key and then use it to challenge the server to prove it also has the private key by signing some random data correctly.

One more attribute needs to be set on the factory. privateKeys and publicKeys let clients identify the server. Now the server needs some way to identify clients. This is done using a Portal from twisted.cred. However, I said this server wouldn't be able to authenticate users, so this example will only set a placeholder value here which will fail all authentication attempts:

from twisted.cred.portal import Portalfactory.portal = Portal(None)

Later we'll revisit this attribute so that users can actually log in to the server.

The example is almost done now. The only thing left to do is listen on a port with this factory and run the reactor. For this we'll need to import the reactor. We have several options for how to listen on a port, but for this example I'll use the classic reactor.listenTCP:

from twisted.internet import reactorreactor.listenTCP(2022, factory)

This listens on TCP port 2022. Whenever a connection arrives, factory will be used to create a new protocol instance to handle it. This factory is going to create SSHServerTransport instances, but don't worry about that for now.

Since the reactor is the mainloop that drives any Twisted-based application, nothing actually happens until we run it:

reactor.run()

With that, you now have an SSH server. It's functional enough to prove its identity to clients that connect to it and even accept authentication attempts from them, but it will always reject their attempts. So it's not the most useful SSH server (but add a little logging and maybe you have a simple SSH honey pot!), but if you tune in next time, I'll demonstrate how it can be extended to support some more useful authentication options. Here's a full code listing for this example: