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 but
which 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:
exarkun@boson:/tmp$ ckeygen -t rsa -f id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
The key fingerprint is:
d5:1d:ba:47:a4:75:1a:fe:d4:ba:46:2e:44:67:0d:8b
exarkun@boson:/tmp$ ls -l id_rsa id_rsa.pub
-rw------- 1 1000 1000 886 2011-03-23 22:59 id_rsa
-rwxr-xr-x 1 1000 1000 226 2011-03-23 22:59 id_rsa.pub
exarkun@boson:/tmp$
from twisted.conch.ssh.factory import SSHFactory
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:
from twisted.conch.ssh.keys import Key
with open('id_rsa') as privateBlobFile:
privateBlob = privateBlobFile.read()
privateKey = Key.fromString(data=privateBlob)
with open('id_rsa.pub') as publicBlobFile:
publicBlob = publicBlobFile.read()
publicKey = Key.fromString(data=publicBlob)
factory.privateKeys = {'ssh-rsa': privateKey}
factory.publicKeys = {'ssh-rsa': publicKey}
from twisted.cred.portal import Portal
factory.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 reactor
reactor.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:
from twisted.cred.portal import Portal
from twisted.conch.ssh.factory import SSHFactory
from twisted.internet import reactor
from twisted.conch.ssh.keys import Key
with open('id_rsa') as privateBlobFile:
privateBlob = privateBlobFile.read()
privateKey = Key.fromString(data=privateBlob)
with open('id_rsa.pub') as publicBlobFile:
publicBlob = publicBlobFile.read()
publicKey = Key.fromString(data=publicBlob)
factory = SSHFactory()
factory.privateKeys = {'ssh-rsa': privateKey}
factory.publicKeys = {'ssh-rsa': publicKey}
factory.portal = Portal(None)
reactor.listenTCP(2022, factory)
reactor.run()
No comments:
Post a Comment