[Twisted-Python] Possible bug on twisted.conch.ssh.keys?

Adriano Marques py.adriano at gmail.com
Mon Jul 28 09:29:25 EDT 2008


Hello folks,

I'm facing a problem here while trying to implement a ssh server and
client using twisted. There are some deprecated functions that are
pointing me to use new ones, which in turn doesn't seem to work fine.
I'm not sure if it is the case, mainly because the deprecated
functions are calling the new ones, but while debuging the code I
found that something there is wrong, and that can potentially be
hapenning because of the changes recently made to that module. As I'm
somehow new to twisted I'm not sure about my statements arround the
problem. Here follows the codes and exceptions I'm getting:

<client_code>
#!/usr/bin/env python

# This code was based on an example from twisted website

from twisted.conch.ssh import transport, userauth, connection
from twisted.conch.ssh import common, keys, channel
from twisted.internet import defer, protocol, reactor
from twisted.python import log
import struct, sys, getpass, os

USER = 'ssh'
HOST = 'localhost'
PORT = 2222
PUBKEY = "ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAAAgQD3n4sYqbdnlVyvy2SRUmQ5HJg2NDHyC0m4FIykDygXW2PTcSHU/Jany95D54qAEkUra0KHk94KvyGhNVYXqPmzPLu4onlr0Y/gQzEsIy15jDGjMWVCX/8+PQJxMYObSKs8U1o/h5fDNeKQs4bZbrm+35eT/Kvyerqa6ab5OsIEnQ=="
PRIVKEY = """-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQD3n4sYqbdnlVyvy2SRUmQ5HJg2NDHyC0m4FIykDygXW2PTcSHU
/Jany95D54qAEkUra0KHk94KvyGhNVYXqPmzPLu4onlr0Y/gQzEsIy15jDGjMWVC
X/8+PQJxMYObSKs8U1o/h5fDNeKQs4bZbrm+35eT/Kvyerqa6ab5OsIEnQIDAQAB
AoGAK2MWASVDkG+4RMkTfu77xpH/DYhJtApMSWe4WMqbELSfoh2xetsjHpV3BVjd
iKEq43ewuYasIh/pKZDp281sqqXdg9VI9ZW7kB00FhO2wA4emCIVj6CHMl1K9NrU
9Spy40garaYnDdud79SnKtxlKQEALOwCpTEcrjGOLBTbirUCQQD/FkP1zhpOSaOy
B32CiC13W5fFfREzBxzQ3Med1i1OYtFfBEdJqv/Z7APadNN8ySI3r+UAfGatTzJI
zbiSZ2fHAkEA+IJwWpw6d8R3t7+8UgTrqdsZhLRdidO0cHcIFcnU/QYZg+iw3F+q
Tuz/mngpfb5214r8zwcwlNzsC4+fyOOYewJBAMPstRw6Rog1FW8rQ6Kbt9hCWItO
aYR5BRADU6sOk1PuoIPLhHm3xrX6CmejbcEdt5NwHYNHCZI6DxRONmL024cCQBky
eJvnXVJJfG4IJdsXHqIBUiwPcbCI47HHj+1NoqfpF2s1i8E8ffM0upH5/xL93eTq
9ck0DGv7nn9pl6Tx1sMCQCWZbWDnmEdsSoNUQZ8fyQEDoZ/2gUP+R2/WpLikwmSA
41n7cUONVthIYOZw0qQPP//PyPtMVxcYT76D5QACEK8=
-----END RSA PRIVATE KEY-----"""

class ClientTransport(transport.SSHClientTransport):
    def connectionMade(self):
        transport.SSHClientTransport.connectionMade(self)

    def verifyHostKey(self, hostKey, fingerprint):
        return defer.succeed(1)

    def connectionSecure(self):
        self.requestService(ClientUserAuth(USER, ClientConnection()))

class ClientUserAuth(userauth.SSHUserAuthClient):
    def getPublicKey(self):
        # Issue 1
        return PUBKEY.encode("ascii")

    def getPrivateKey(self):
        # Issue 2
        return defer.succeed(keys.getPrivateKeyObject(data=PRIVKEY))

    def getPassword(self):
        return defer.succeed(getpass.getpass("%s@%s's password: " %
(USER, HOST)))

    def getGenericAnswers(self, name, instruction, questions):
        import pdb; pdb.set_trace()
        log.debug("Get Generic answers: %s, %s, %s" % (name,
                                                       instruction,
                                                       questions))

        answers = []
        for prompt, echo in questions:
            if echo:
                answer = raw_input(prompt)
            else:
                answer = getpass.getpass(prompt)
            answers.append(answer)
        return defer.succeed(answers)

    def tryAuth(self, kind):
        kind = "publickey"
        log.debug("tryAuth: %s" % kind)
        log.debug("Public Key: %s" % self.getPublicKey())
        return userauth.SSHUserAuthClient.tryAuth(self, kind)

    def signData(self, publicKey, signData):
        log.debug("Ran signData with %s and %s" % (publicKey, signData))
        import pdb; pdb.set_trace()
        signed = userauth.SSHUserAuthClient.signData(self, publicKey, signData)

        return signed

    def auth_publickey(self):
        log.debug("Trying Auth Method: Public Key")
        return userauth.SSHUserAuthClient.auth_publickey(self)

class ClientConnection(connection.SSHConnection):
    def serviceStarted(self):
        log.debug("Service started")
        self.openChannel(TrueChannel(2**16, 2**15, self))
        self.openChannel(FalseChannel(2**16, 2**15, self))
        self.openChannel(CatChannel(2**16, 2**15, self))

class TrueChannel(channel.SSHChannel):
    name = 'session'

    def openFailed(self, reason):
        print 'true failed', reason

    def channelOpen(self, ignoredData):
        self.conn.sendRequest(self, 'exec', common.NS('true'))

    def request_exit_status(self, data):
        status = struct.unpack('>L', data)[0]
        print 'true status was: %s' % status
        self.loseConnection()

class FalseChannel(channel.SSHChannel):
    name = 'session'

    def openFailed(self, reason):
        print 'false failed', reason

    def channelOpen(self, ignoredData):
        self.conn.sendRequest(self, 'exec', common.NS('false'))

    def request_exit_status(self, data):
        status = struct.unpack('>L', data)[0]
        print 'false status was: %s' % status
        self.loseConnection()

class CatChannel(channel.SSHChannel):
    name = 'session'

    def openFailed(self, reason):
        print 'echo failed', reason

    def channelOpen(self, ignoredData):
        self.data = ''
        d = self.conn.sendRequest(self, 'exec', common.NS('cat'), wantReply = 1)
        d.addCallback(self._cbRequest)

    def _cbRequest(self, ignored):
        self.write('hello conch\n')
        self.conn.sendEOF(self)

    def dataReceived(self, data):
        self.data += data

    def closed(self):
        print 'got data from cat: %s' % repr(self.data)
        self.loseConnection()
        reactor.stop()

protocol.ClientCreator(reactor, ClientTransport).connectTCP(HOST, 2222)
reactor.run()
</client_code>

The issues:

1 - I had to encode the public key to ascii prior to using it. I don't
know why, but the struct was raising an exception telling that one of
the chars where out of the ascii range. But it doesn't look to have
any character outside the ascii range, AFAIK. Anyway, it worked when I
did the conversion to ascii.

2 - When I use: keys.Key.fromString(data=PRIVKEY) instead of the
deprecated keys.getPrivateKeyObject(data=PRIVKEY), I get this error
while trying to authenticate:

<error>
# This was caught directly from pdb prompt:
> connectSSH.py(77)signData()
-> signed = userauth.SSHUserAuthClient.signData(self, publicKey, signData)
(Pdb) n
> site-packages/twisted/conch/ssh/userauth.py:374: DeprecationWarning: signData is deprecated since Twisted Conch 0.9.  Use Key(obj).sign(data).
  return keys.signData(privateKey, signData)
> connectSSH.py(79)signData()
-> return signed
(Pdb) p signed
<Deferred at 0x12a1760  current result:
<twisted.python.failure.Failure <type 'exceptions.RuntimeError'>>>
(Pdb) p signed.result
<twisted.python.failure.Failure <type 'exceptions.RuntimeError'>>
(Pdb) p signed.result.getErrorMessage()
'unknown type of object: <RSA Private Key (1023 bits)\nattr
e:\n\t01:00:01\nattr
d:\n\t2b:63:16:01:25:43:90:6f:b8:44:c9:13:7e:ee:fb:\n\tc6:91:ff:0d:88:49:b4:0a:4c:49:67:b8:58:ca:9b:\n\t10:b4:9f:a2:1d:b1:7a:db:23:1e:95:77:05:58:dd:\n\t88:a1:2a:e3:77:b0:b9:86:ac:22:1f:e9:29:90:e9:\n\tdb:cd:6c:aa:a5:dd:83:d5:48:f5:95:bb:90:1d:34:\n\t16:13:b6:c0:0e:1e:98:22:15:8f:a0:87:32:5d:4a:\n\tf4:da:d4:f5:2a:72:e3:48:1a:ad:a6:27:0d:db:9d:\n\tef:d4:a7:2a:dc:65:29:01:00:2c:ec:02:a5:31:1c:\n\tae:31:8e:2c:14:db:8a:b5\nattr
n:\n\t00:f7:9f:8b:18:a9:b7:67:95:5c:af:cb:64:91:52:\n\t64:39:1c:98:36:34:31:f2:0b:49:b8:14:8c:a4:0f:\n\t28:17:5b:63:d3:71:21:d4:fc:96:a7:cb:de:43:e7:\n\t8a:80:12:45:2b:6b:42:87:93:de:0a:bf:21:a1:35:\n\t56:17:a8:f9:b3:3c:bb:b8:a2:79:6b:d1:8f:e0:43:\n\t31:2c:23:2d:79:8c:31:a3:31:65:42:5f:ff:3e:3d:\n\t02:71:31:83:9b:48:ab:3c:53:5a:3f:87:97:c3:35:\n\te2:90:b3:86:d9:6e:b9:be:df:97:93:fc:ab:f2:7a:\n\tba:9a:e9:a6:f9:3a:c2:04:9d\nattr
q:\n\t00:ff:16:43:f5:ce:1a:4e:49:a3:b2:07:7d:82:88:\n\t2d:77:5b:97:c5:7d:11:33:07:1c:d0:dc:c7:9d:d6:\n\t2d:4e:62:d1:5f:04:47:49:aa:ff:d9:ec:03:da:74:\n\td3:7c:c9:22:37:af:e5:00:7c:66:ad:4f:32:48:cd:\n\tb8:92:67:67:c7\nattr
p:\n\t00:f8:82:70:5a:9c:3a:77:c4:77:b7:bf:bc:52:04:\n\teb:a9:db:19:84:b4:5d:89:d3:b4:70:77:08:15:c9:\n\td4:fd:06:19:83:e8:b0:dc:5f:aa:4e:ec:ff:9a:78:\n\t29:7d:be:76:d7:8a:fc:cf:07:30:94:dc:ec:0b:8f:\n\t9f:c8:e3:98:7b\nattr
u:\n\t25:99:6d:60:e7:98:47:6c:4a:83:54:41:9f:1f:c9:\n\t01:03:a1:9f:f6:81:43:fe:47:6f:d6:a4:b8:a4:c2:\n\t64:80:e3:59:fb:71:43:8d:56:d8:48:60:e6:70:d2:\n\ta4:0f:3f:ff:cf:c8:fb:4c:57:17:18:4f:be:83:e5:\n\t00:02:10:af>'
</error>

This is the function that raised that exception:
http://twistedmatrix.com/trac/browser/tags/releases/twisted-8.1.0/twisted/conch/ssh/keys.py#L387
It looks like the problem is that the class name doesn't start with
Crypto.PublicKey. Then, when I use the deprecated method, this is what
I have:

<not_an_error>
> connectSSH.py(77)signData()
-> signed = userauth.SSHUserAuthClient.signData(self, publicKey, signData)
(Pdb) n
tests/connectSSH.py:48: DeprecationWarning: getPrivateKeyObject is
deprecated since Twisted Conch 0.9.  Use Key.fromString().
  return defer.succeed(keys.getPrivateKeyObject(data=PRIVKEY))
site-packages/twisted/conch/ssh/userauth.py:374: DeprecationWarning:
signData is deprecated since Twisted Conch 0.9.  Use
Key(obj).sign(data).
  return keys.signData(privateKey, signData)
> connectSSH.py(79)signData()
-> return signed
(Pdb) p signed
<Deferred at 0x12a1760  current result:
'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x80\xa9w-\x0c\x9e\xc3\xe8\xd0\xff\xb8\xf0\xbbi*\xecJ\x8e5\xac\x0e(@\x18\x81\x11\xf4\xff\xda\xde\xd6\x9b\xbe\xef\\\xc2\xd0F8Q\x15\xd0A\xa1!$\xe2\xe5\xa0\xf8\x12O\xa0*4\xe1)\xc2:\xbf\x16\x0b\x8a\xd2J\xa8D\x01\xdd\x10\xe3\xeb\x8b*9,\xf3#b\xff\xbf\xc9\xe9\xdc\x81\xa4B\x88$\r\x8b\xef|y\x80\r\x08\x8a*\x08x:\x0c{\xcf\x97\xb7"\xe19\x1f\xa2_wV4\xfa\x19"Vf\xcf\x80\xa7i\x98\xfa\xefM'>
</not_an_error>

After moving to the deprecated method, everything seems to work ok
with the client, but then I started to have a similar problem at the
server side, while verifying client's signature. Here is the relevant
part of the server code, which is the method requestAvatarId of the
Credential Checker I implemented:

<server_code>
# This code is based in an example that I caught from twisted website
class PublicKeyChecker():
    implements(checkers.ICredentialsChecker)
    credentialInterfaces = (credentials.ISSHPrivateKey, )

    def requestAvatarId(self, credentials):
        # I removed here the code that verified if the user is
registered in our database, to easy for you guys to understand the
relevant part of the code. Just consider that the variable user
contains the found user object.

        user_key = user.key.public_key
        if not credentials.blob == user_key:
            raise failure.Failure(error.ConchError("Wrong key."))
        if not credentials.signature:
            return failure.Failure(error.ValidPublicKey())

        pub_key = Key.fromString(data=credentials.blob)
        # Issue 3
        if verifySignature(pub_key,
                           credentials.signature,
                           credentials.sigData):
            return credentials.username
        else:
            return failure.Failure(error.ConchError("Incorrect Signature."))
    else:
        return failure.Failure(error.ConchError("Authentication Failed."))
</server_code>

3 - Then, while I get to the verifySignature method, I get the following error:

<error>
RuntimeError: unknown type of object: <RSA Public Key (1023 bits)
attr e:
	01:00:01
attr n:
	00:f7:9f:8b:18:a9:b7:67:95:5c:af:cb:64:91:52:
	64:39:1c:98:36:34:31:f2:0b:49:b8:14:8c:a4:0f:
	28:17:5b:63:d3:71:21:d4:fc:96:a7:cb:de:43:e7:
	8a:80:12:45:2b:6b:42:87:93:de:0a:bf:21:a1:35:
	56:17:a8:f9:b3:3c:bb:b8:a2:79:6b:d1:8f:e0:43:
	31:2c:23:2d:79:8c:31:a3:31:65:42:5f:ff:3e:3d:...
</error>

Which is the same exception raised by the key.type() function that
have put us at the same trouble in the client. Then, at the debuger, I
found that the correct key (which is a class with name starting with
Crypto.PublicKey) is right inside this "unknown" object, which is a
RSA Public Key in object.keyObject.keyObject! I ran the WingIDE
debugger and simply saw that, as you can see in this screenshot:
http://www.imageno.com/z99azx3v7qnzpic.html

Ok, I also get the deprecated warning while using the verifySignature
method instead of Key(obj).verify(sig, data), then I decided to test
it also just to don't make you guys wast your time with me for
nothing. I called pub_key.verify(credentials.signature,
credentials.sigData) instead of that verifySignature call, and it
returned False instead leading to a failure in authentication.

Both apps (client and server) are running inside the same machine,
consulting the same database, the same keys and user, so there it is
not reasonable to be failing if my code is correct. Maybe I missed
something. Can you guys help me?

Sorry about the very long email, but I tried to be as much verbose as
I could in order to help you guys help me.


Kind Regards,

-- 
Adriano Monteiro Marques

http://adriano-marques.blogspot.com
http://umit.sourceforge.net
py.adriano at gmail.com

"Don't stay in bed, unless you can make money in bed." - George Burns




More information about the Twisted-Python mailing list