Ticket #1902: conch-sshbugs.patch

File conch-sshbugs.patch, 9.6 KB (added by philmayers, 13 years ago)

infrastructure for SSH bug workaround test cases; attempt at a test case and the relevant client-side fix

  • twisted/conch/test/test_ssh.py

     
    313313        def getService(self, trans, name):
    314314            return factory.SSHFactory.getService(self, trans, name)
    315315
     316    class ConchTestBuggyServerFactory(ConchTestServerFactory):
     317        def buildProtocol(self, addr):
     318            proto = ConchTestBuggyServer()
     319            proto.supportPublicKeys = self.privateKeys.keys()
     320            proto.factory = self
     321            self.proto = proto
     322            return proto
     323       
    316324    class ConchTestBase:
    317325
    318326        done = 0
     
    343351            ConchTestBase.connectionLost(self, reason)
    344352            transport.SSHServerTransport.connectionLost(self, reason)
    345353
     354    class BuggyKeyFuncs:
     355        def __init__(self):
     356            # Dirty hack. An instance of this class pretends to be the
     357            # keys module, and provides various buggy behaviours to
     358            # emulate various crappy SSH implementations
     359
     360            # Copy the key funcs we *don't* have from "keys"
     361            from twisted.conch.ssh import keys
     362            for o in dir(keys):
     363                if not hasattr(self, o):
     364                    setattr(self, o, getattr(keys, o))
     365                   
     366        def signData(self, obj, data):
     367            mapping = {
     368                'ssh-rsa': self.signData_rsa,
     369                'ssh-dss': self.signData_dsa
     370            }
     371            objType = self.objectType(obj)
     372            #return common.NS(objType)+mapping[objType](obj, data)
     373            # SSH.com does not return the sigtype - just the sigdata
     374            return mapping[objType](obj, data)
     375           
     376    class ConchTestBuggyServer(ConchTestBase, transport.SSHServerTransport):
     377        supportedPublicKeys = ['ssh-dss',]
     378       
     379        def connectionMade(self):
     380            self.keys = BuggyKeyFuncs()
     381            # FIXME: should be a super().connectionMade() call?
     382            transport.SSHServerTransport.connectionMade(self)
     383
     384        def sendPacket(self, messageType, payload):
     385            if messageType==transport.MSG_SERVICE_ACCEPT:
     386                # EMULATE BUG - SSH.com does not put the service name in the accept
     387                payload = ''
     388
     389            # FIXME: should be a super().sendPacket() call?
     390            transport.SSHServerTransport.sendPacket(self, messageType, payload)
     391           
    346392    class ConchTestClient(ConchTestBase, transport.SSHClientTransport):
    347393
    348394        def connectionLost(self, reason):
     
    350396            transport.SSHClientTransport.connectionLost(self, reason)
    351397
    352398        def verifyHostKey(self, key, fp):
    353             unittest.assertEquals(key, keys.getPublicKeyString(data = publicRSA_openssh))
    354             unittest.assertEquals(fp,'3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af')
     399            unittest.assertIn(
     400                    key,
     401                    (
     402                        keys.getPublicKeyString(data = publicRSA_openssh),
     403                        keys.getPublicKeyString(data = publicDSA_openssh),
     404                    )
     405                )
     406            unittest.assertIn(
     407                    fp,
     408                    (
     409                        '3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af',
     410                        '16:af:52:0e:e1:a9:0b:78:e7:e8:51:52:91:13:ae:6a',
     411                    )
     412                )
    355413            return defer.succeed(1)
    356414
    357415        def connectionSecure(self):
     
    698756    if not Crypto:
    699757        skip = "can't run w/o PyCrypto"
    700758
     759    def testBuggyServerOurClient(self):
     760        """test a fake/buggy server against the Conch client
     761        """
     762        realm = ConchTestRealm()
     763        p = portal.Portal(realm)
     764        sshpc = ConchTestSSHChecker()
     765        sshpc.registerChecker(ConchTestPasswordChecker())
     766        sshpc.registerChecker(ConchTestPublicKeyChecker())
     767        p.registerChecker(sshpc)
     768        fac = ConchTestBuggyServerFactory()
     769        fac.portal = p
     770        fac.startFactory()
     771        self.server = fac.buildProtocol(None)
     772        self.clientTransport = LoopbackRelay(self.server)
     773        self.client = ConchTestClient()
     774        self.serverTransport = LoopbackRelay(self.client)
     775
     776        self.server.makeConnection(self.serverTransport)
     777        self.client.makeConnection(self.clientTransport)
     778
     779        while self.serverTransport.buffer or self.clientTransport.buffer:
     780            log.callWithContext({'system': 'serverTransport'},
     781                                self.serverTransport.clearBuffer)
     782            log.callWithContext({'system': 'clientTransport'},
     783                                self.clientTransport.clearBuffer)
     784        self.failIf(self.server.done and self.client.done)
     785       
    701786    def testOurServerOurClient(self):
    702787        """test the Conch server against the Conch client
    703788        """
  • twisted/conch/ssh/transport.py

     
    3535
    3636# sibling importsa
    3737from common import NS, getNS, MP, getMP, _MPpow, ffs, entropy # ease of use
    38 import keys
    3938
    4039
    4140class SSHTransportBase(protocol.Protocol):
     
    8079        log.msg('connection lost')
    8180
    8281    def connectionMade(self):
     82        # Get our key/signature functions - subclasses may override these
     83        # by setting the instance/class variable first e.g. test cases
     84        if not hasattr(self, 'keys'):
     85            import keys
     86            self.keys = keys
     87           
    8388        self.transport.write('%s\r\n'%(self.ourVersionString))
    8489        self.sendKexInit()
    8590
     
    345350            h.update(sharedSecret)
    346351            exchangeHash = h.digest()
    347352            self.sendPacket(MSG_KEXDH_REPLY, NS(self.factory.publicKeys[self.keyAlg])+ \
    348                            MP(f)+NS(keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
     353                           MP(f)+NS(self.keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
    349354            self._keySetup(sharedSecret, exchangeHash)
    350355        elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
    351356            self.kexAlg = 'diffie-hellman-group-exchange-sha1-old'
     
    408413        h.update(sharedSecret)
    409414        exchangeHash = h.digest()
    410415        self.sendPacket(MSG_KEX_DH_GEX_REPLY, NS(self.factory.publicKeys[self.keyAlg])+ \
    411                        MP(f)+NS(keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
     416                       MP(f)+NS(self.keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
    412417        self._keySetup(sharedSecret, exchangeHash)
    413418
    414419    def ssh_NEWKEYS(self, packet):
     
    510515            self.sendPacket(MSG_KEX_DH_GEX_INIT, MP(self.DHpubKey))
    511516
    512517    def _continueGEX_GROUP(self, ignored, pubKey, f, signature):
    513         serverKey = keys.getPublicKeyObject(pubKey)
     518        serverKey = self.keys.getPublicKeyObject(pubKey)
    514519        sharedSecret = _MPpow(f, self.x, DH_PRIME)
    515520        h = sha.new()
    516521        h.update(NS(self.ourVersionString))
     
    522527        h.update(MP(f))
    523528        h.update(sharedSecret)
    524529        exchangeHash = h.digest()
    525         if not keys.verifySignature(serverKey, signature, exchangeHash):
     530        if not self.keys.verifySignature(serverKey, signature, exchangeHash):
    526531            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, 'bad signature')
    527532            return
    528533        self._keySetup(sharedSecret, exchangeHash)
     
    537542        d.addErrback(lambda unused, self=self: self.sendDisconnect(DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
    538543
    539544    def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
    540         serverKey = keys.getPublicKeyObject(pubKey)
     545        serverKey = self.keys.getPublicKeyObject(pubKey)
    541546        sharedSecret = _MPpow(f, self.x, self.p)
    542547        h = sha.new()
    543548        h.update(NS(self.ourVersionString))
     
    552557        h.update(MP(f))
    553558        h.update(sharedSecret)
    554559        exchangeHash = h.digest()
    555         if not keys.verifySignature(serverKey, signature, exchangeHash):
     560        if not self.keys.verifySignature(serverKey, signature, exchangeHash):
    556561            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, 'bad signature')
    557562            return
    558563        self._keySetup(sharedSecret, exchangeHash)
     
    591596        self.connectionSecure()
    592597
    593598    def ssh_SERVICE_ACCEPT(self, packet):
     599        if len(packet) == 0: # SSH.com bug, empty SERVICE ACCEPT packet
     600            self.setService(self.instance)
     601            return
    594602        name = getNS(packet)[0]
    595603        if name != self.instance.name:
    596604            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "received accept for service we did not request")
  • twisted/conch/ssh/keys.py

     
    429429        'ssh-dss': verifySignature_dsa,
    430430     }
    431431    objType = objectType(obj)
     432    if len(sig) == 40: # SSH.com bug, no header
     433        return mapping[objType](obj, sig, data)
    432434    sigType, sigData = common.getNS(sig)
    433435    if objType != sigType: # object and signature are not of same type
    434436        return 0
     
    439441    return obj.verify(pkcs1Digest(data, lenSig(obj)), sigTuple)
    440442
    441443def verifySignature_dsa(obj, sig, data):
    442     sig = common.getNS(sig)[0]
     444    if len(sig) != 40: # SSH.com bug, no header
     445        sig = common.getNS(sig)[0]
    443446    assert(len(sig) == 40)
    444447    l = len(sig)/2
    445448    sigTuple = map(Util.number.bytes_to_long, [sig[: l], sig[l:]])