~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/conch/ssh/transport.py

  • Committer: Thomas Hervé
  • Date: 2009-07-08 13:52:04 UTC
  • Revision ID: thomas@canonical.com-20090708135204-df5eesrthifpylf8
Remove twisted copy

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: twisted.conch.test.test_conch -*-
2
 
#
3
 
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
 
# See LICENSE for details.
5
 
 
6
 
7
 
 
8
 
"""The lowest level SSH protocol.  This handles the key negotiation, the encryption and the compression.
9
 
 
10
 
This module is unstable.
11
 
 
12
 
Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
13
 
"""
14
 
 
15
 
from __future__ import nested_scopes
16
 
 
17
 
# base library imports
18
 
import struct
19
 
import md5
20
 
import sha
21
 
import zlib
22
 
import math # for math.log
23
 
import array
24
 
 
25
 
# external library imports
26
 
from Crypto import Util
27
 
from Crypto.Cipher import XOR
28
 
from Crypto.PublicKey import RSA
29
 
from Crypto.Util import randpool
30
 
 
31
 
# twisted imports
32
 
from twisted.conch import error
33
 
from twisted.internet import protocol, defer
34
 
from twisted.python import log
35
 
 
36
 
# sibling importsa
37
 
from common import NS, getNS, MP, getMP, _MPpow, ffs, entropy # ease of use
38
 
import keys
39
 
 
40
 
 
41
 
class SSHTransportBase(protocol.Protocol):
42
 
    protocolVersion = '2.0'
43
 
    version = 'Twisted'
44
 
    comment = ''
45
 
    ourVersionString = ('SSH-'+protocolVersion+'-'+version+' '+comment).strip()
46
 
 
47
 
    supportedCiphers = ['aes256-ctr', 'aes256-cbc', 'aes192-ctr', 'aes192-cbc', 
48
 
                        'aes128-ctr', 'aes128-cbc', 'cast128-ctr', 
49
 
                        'cast128-cbc', 'blowfish-ctr', 'blowfish', 'idea-ctr'
50
 
                        'idea-cbc', '3des-ctr', '3des-cbc'] # ,'none']
51
 
    supportedMACs = ['hmac-sha1', 'hmac-md5'] # , 'none']
52
 
    
53
 
    # both of the above support 'none', but for security are disabled by
54
 
    # default.  to enable them, subclass this class and add it, or do:
55
 
    #   SSHTransportBase.supportedCiphers.append('none')
56
 
 
57
 
    supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1', 
58
 
                             'diffie-hellman-group1-sha1']
59
 
    supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
60
 
    supportedCompressions = ['none', 'zlib']
61
 
    supportedLanguages = ()
62
 
 
63
 
    gotVersion = 0
64
 
    ignoreNextPacket = 0
65
 
    buf = ''
66
 
    outgoingPacketSequence = 0
67
 
    incomingPacketSequence = 0
68
 
    currentEncryptions = None
69
 
    outgoingCompression = None
70
 
    incomingCompression = None
71
 
    sessionID = None
72
 
    isAuthorized = 0
73
 
    service = None
74
 
 
75
 
    def connectionLost(self, reason):
76
 
        if self.service:
77
 
            self.service.serviceStopped()
78
 
        if hasattr(self, 'avatar'):
79
 
            self.logoutFunction()
80
 
        log.msg('connection lost')
81
 
 
82
 
    def connectionMade(self):
83
 
        self.transport.write('%s\r\n'%(self.ourVersionString))
84
 
        self.sendKexInit()
85
 
 
86
 
    def sendKexInit(self):
87
 
        self.ourKexInitPayload = chr(MSG_KEXINIT)+entropy.get_bytes(16)+ \
88
 
                       NS(','.join(self.supportedKeyExchanges))+ \
89
 
                       NS(','.join(self.supportedPublicKeys))+ \
90
 
                       NS(','.join(self.supportedCiphers))+ \
91
 
                       NS(','.join(self.supportedCiphers))+ \
92
 
                       NS(','.join(self.supportedMACs))+ \
93
 
                       NS(','.join(self.supportedMACs))+ \
94
 
                       NS(','.join(self.supportedCompressions))+ \
95
 
                       NS(','.join(self.supportedCompressions))+ \
96
 
                       NS(','.join(self.supportedLanguages))+ \
97
 
                       NS(','.join(self.supportedLanguages))+ \
98
 
                       '\000'+'\000\000\000\000'
99
 
        self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
100
 
 
101
 
    def sendPacket(self, messageType, payload):
102
 
        payload = chr(messageType)+payload
103
 
        if self.outgoingCompression:
104
 
            payload = self.outgoingCompression.compress(payload) + self.outgoingCompression.flush(2)
105
 
        if self.currentEncryptions:
106
 
            bs = self.currentEncryptions.enc_block_size
107
 
        else:
108
 
            bs = 8
109
 
        totalSize = 5+len(payload)
110
 
        lenPad = bs-(totalSize%bs)
111
 
        if lenPad < 4:
112
 
            lenPad = lenPad+bs
113
 
        packet = struct.pack('!LB', totalSize+lenPad-4, lenPad)+ \
114
 
                payload+entropy.get_bytes(lenPad)
115
 
        assert len(packet)%bs == 0, '%s extra bytes in packet'%(len(packet)%bs)
116
 
        if self.currentEncryptions:
117
 
            encPacket = self.currentEncryptions.encrypt(packet) + self.currentEncryptions.makeMAC(self.outgoingPacketSequence, packet)
118
 
        else:
119
 
            encPacket = packet
120
 
        self.transport.write(encPacket)
121
 
        self.outgoingPacketSequence+=1
122
 
 
123
 
    def getPacket(self):
124
 
        bs = self.currentEncryptions and self.currentEncryptions.dec_block_size or 8
125
 
        ms = self.currentEncryptions and self.currentEncryptions.verify_digest_size or 0
126
 
        if len(self.buf) < bs: return # not enough data
127
 
        if not hasattr(self, 'first'):
128
 
            if self.currentEncryptions:
129
 
                first = self.currentEncryptions.decrypt(self.buf[: bs])
130
 
            else:
131
 
                first = self.buf[: bs]
132
 
        else:
133
 
            first = self.first
134
 
            del self.first
135
 
        packetLen, randomLen = struct.unpack('!LB', first[: 5])
136
 
        if packetLen > 1048576: # 1024 ** 2
137
 
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, 'bad packet length %s'%packetLen)
138
 
            return
139
 
        if len(self.buf) < packetLen+4+ms:
140
 
            self.first = first
141
 
            return # not enough packet
142
 
        if(packetLen+4)%bs != 0:
143
 
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, 'bad packet mod (%s%%%s == %s'%(packetLen+4, bs, (packetLen+4)%bs))
144
 
            return
145
 
        encData, self.buf = self.buf[: 4+packetLen], self.buf[4+packetLen:]
146
 
        if self.currentEncryptions:
147
 
            packet = first+self.currentEncryptions.decrypt(encData[bs:])
148
 
        else:
149
 
            packet = encData
150
 
        if len(packet) != 4+packetLen:
151
 
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, 'bad packet length')
152
 
            return
153
 
        if ms: 
154
 
            macData, self.buf = self.buf[:ms],  self.buf[ms:]
155
 
            if not self.currentEncryptions.verify(self.incomingPacketSequence, packet, macData):
156
 
                self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC')
157
 
                return
158
 
        payload = packet[5: 4+packetLen-randomLen]
159
 
        if self.incomingCompression:
160
 
            try:
161
 
                payload = self.incomingCompression.decompress(payload)
162
 
            except zlib.error:
163
 
                self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR, 'compression error')
164
 
                return
165
 
        self.incomingPacketSequence+=1
166
 
        return payload
167
 
 
168
 
    def dataReceived(self, data):
169
 
        self.buf = self.buf+data
170
 
        if not self.gotVersion:
171
 
            parts = self.buf.split('\n')
172
 
            for p in parts:
173
 
                if p[: 4] == 'SSH-':
174
 
                    self.gotVersion = 1
175
 
                    self.otherVersionString = p.strip()
176
 
                    if p.split('-')[1]not in('1.99', '2.0'): # bad version
177
 
                        self.sendDisconnect(DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, 'bad version %s'%p.split('-')[1])
178
 
                        return
179
 
                    i = parts.index(p)
180
 
                    self.buf = '\n'.join(parts[i+1:])
181
 
        packet = self.getPacket()
182
 
        while packet:
183
 
            messageNum = ord(packet[0])
184
 
            if messageNum < 50:
185
 
                messageType = messages[messageNum][4:]
186
 
                f = getattr(self, 'ssh_%s'%messageType, None)
187
 
                if f:
188
 
                    f(packet[1:])
189
 
                else:
190
 
                    log.msg("couldn't handle %s"%messageType)
191
 
                    log.msg(repr(packet[1:]))
192
 
                    self.sendUnimplemented()
193
 
            elif self.service:
194
 
                log.callWithLogger(self.service, self.service.packetReceived,
195
 
                                                 ord(packet[0]), packet[1:])
196
 
            else:
197
 
                log.msg("couldn't handle %s"%messageNum)
198
 
                log.msg(repr(packet[1:]))
199
 
                self.sendUnimplemented()
200
 
            packet = self.getPacket()
201
 
 
202
 
    def ssh_DISCONNECT(self, packet):
203
 
        reasonCode = struct.unpack('>L', packet[: 4])[0]
204
 
        description, foo = getNS(packet[4:])
205
 
        self.receiveError(reasonCode, description)
206
 
        self.transport.loseConnection()
207
 
 
208
 
    def ssh_IGNORE(self, packet): pass
209
 
 
210
 
    def ssh_UNIMPLEMENTED(self, packet):
211
 
        seqnum = struct.unpack('>L', packet)
212
 
        self.receiveUnimplemented(seqnum)
213
 
 
214
 
    def ssh_DEBUG(self, packet):
215
 
        alwaysDisplay = ord(packet[0])
216
 
        message, lang, foo = getNS(packet, 2)
217
 
        self.receiveDebug(alwaysDisplay, message, lang)
218
 
 
219
 
    def setService(self, service):
220
 
        log.msg('starting service %s'%service.name)
221
 
        if self.service:
222
 
            self.service.serviceStopped()
223
 
        self.service = service
224
 
        service.transport = self
225
 
        self.service.serviceStarted()
226
 
 
227
 
    def sendDebug(self, message, alwaysDisplay = 0, language = ''):
228
 
        self.sendPacket(MSG_DEBUG, chr(alwaysDisplay)+NS(message)+NS(language))
229
 
 
230
 
    def sendIgnore(self, message):
231
 
        self.sendPacket(MSG_IGNORE, NS(message))
232
 
 
233
 
    def sendUnimplemented(self):
234
 
        seqnum = self.incomingPacketSequence
235
 
        self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
236
 
 
237
 
    def sendDisconnect(self, reason, desc):
238
 
        self.sendPacket(MSG_DISCONNECT, struct.pack('>L', reason)+NS(desc)+NS(''))
239
 
        log.msg('Disconnecting with error, code %s\nreason: %s'%(reason, desc))
240
 
        self.transport.loseConnection()
241
 
 
242
 
    # client methods
243
 
    def receiveError(self, reasonCode, description):
244
 
        log.msg('Got remote error, code %s\nreason: %s'%(reasonCode, description))
245
 
 
246
 
    def receiveUnimplemented(self, seqnum):
247
 
        log.msg('other side unimplemented packet #%s'%seqnum)
248
 
 
249
 
    def receiveDebug(self, alwaysDisplay, message, lang):
250
 
        if alwaysDisplay:
251
 
            log.msg('Remote Debug Message:', message)
252
 
 
253
 
    def isEncrypted(self, direction = "out"):
254
 
        """direction must be in ["out", "in", "both"]
255
 
        """
256
 
        if self.currentEncryptions == None:
257
 
            return 0
258
 
        elif direction == "out":
259
 
            return bool(self.currentEncryptions.enc_block_size)
260
 
        elif direction == "in":
261
 
            return bool(self.currentEncryptions.dec_block_size)
262
 
        elif direction == "both":
263
 
            return self.isEncrypted("in") and self.isEncrypted("out")
264
 
        else:
265
 
            raise TypeError, 'direction must be "out", "in", or "both"'
266
 
 
267
 
    def isVerified(self, direction = "out"):
268
 
        """direction must be in ["out", "in", "both"]
269
 
        """
270
 
        if self.currentEncryptions == None:
271
 
            return 0
272
 
        elif direction == "out":
273
 
            return self.currentEncryptions.outMAC != None
274
 
        elif direction == "in":
275
 
            return self.currentEncryptions.outCMAC != None
276
 
        elif direction == "both":
277
 
            return self.isVerified("in")and self.isVerified("out")
278
 
        else:
279
 
            raise TypeError, 'direction must be "out", "in", or "both"'
280
 
 
281
 
    def loseConnection(self):
282
 
        self.sendDisconnect(DISCONNECT_CONNECTION_LOST, "user closed connection")
283
 
 
284
 
class SSHServerTransport(SSHTransportBase):
285
 
    isClient = 0
286
 
    def ssh_KEXINIT(self, packet):
287
 
        self.clientKexInitPayload = chr(MSG_KEXINIT)+packet
288
 
        #cookie = packet[: 16] # taking this is useless
289
 
        k = getNS(packet[16:], 10)
290
 
        strings, rest = k[:-1], k[-1]
291
 
        kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS, langSC =  \
292
 
           [s.split(',')for s in strings]
293
 
        if ord(rest[0]): # first_kex_packet_follows
294
 
            if kexAlgs[0] != self.supportedKeyExchanges[0]or \
295
 
               keyAlgs[0] != self.supportedPublicKeys[0]or \
296
 
               not ffs(encSC, self.supportedCiphers)or \
297
 
               not ffs(encCS, self.supportedCiphers)or \
298
 
               not ffs(macSC, self.supportedMACs)or \
299
 
               not ffs(macCS, self.supportedMACs)or \
300
 
               not ffs(compCS, self.supportedCompressions)or \
301
 
               not ffs(compSC, self.supportedCompressions):
302
 
                self.ignoreNextPacket = 1 # guess was wrong
303
 
        self.kexAlg = ffs(kexAlgs, self.supportedKeyExchanges)
304
 
        self.keyAlg = ffs(keyAlgs, self.supportedPublicKeys)
305
 
        self.nextEncryptions = SSHCiphers(
306
 
        ffs(encSC, self.supportedCiphers), 
307
 
            ffs(encCS, self.supportedCiphers), 
308
 
            ffs(macSC, self.supportedMACs), 
309
 
            ffs(macCS, self.supportedMACs), 
310
 
         )
311
 
        self.outgoingCompressionType = ffs(compSC, self.supportedCompressions)
312
 
        self.incomingCompressionType = ffs(compCS, self.supportedCompressions)
313
 
        if None in(self.kexAlg, self.keyAlg, self.outgoingCompressionType, self.incomingCompressionType):
314
 
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
315
 
            return
316
 
        if None in self.nextEncryptions.__dict__.values():
317
 
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
318
 
            return
319
 
        log.msg('kex alg, key alg: %s %s'%(self.kexAlg, self.keyAlg))
320
 
        log.msg('server->client: %s %s %s'%(self.nextEncryptions.outCipType, 
321
 
                                            self.nextEncryptions.outMacType, 
322
 
                                            self.outgoingCompressionType))
323
 
        log.msg('client->server: %s %s %s'%(self.nextEncryptions.inCipType, 
324
 
                                            self.nextEncryptions.inMacType, 
325
 
                                            self.incomingCompressionType))
326
 
 
327
 
 
328
 
    def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
329
 
        if self.ignoreNextPacket:
330
 
            self.ignoreNextPacket = 0
331
 
            return
332
 
        if self.kexAlg == 'diffie-hellman-group1-sha1': # this is really KEXDH_INIT
333
 
            clientDHPubKey, foo = getMP(packet)
334
 
            y = Util.number.getRandomNumber(16, entropy.get_bytes)
335
 
            f = pow(DH_GENERATOR, y, DH_PRIME)
336
 
            sharedSecret = _MPpow(clientDHPubKey, y, DH_PRIME)
337
 
            h = sha.new()
338
 
            h.update(NS(self.otherVersionString))
339
 
            h.update(NS(self.ourVersionString))
340
 
            h.update(NS(self.clientKexInitPayload))
341
 
            h.update(NS(self.ourKexInitPayload))
342
 
            h.update(NS(self.factory.publicKeys[self.keyAlg]))
343
 
            h.update(MP(clientDHPubKey))
344
 
            h.update(MP(f))
345
 
            h.update(sharedSecret)
346
 
            exchangeHash = h.digest()
347
 
            self.sendPacket(MSG_KEXDH_REPLY, NS(self.factory.publicKeys[self.keyAlg])+ \
348
 
                           MP(f)+NS(keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
349
 
            self._keySetup(sharedSecret, exchangeHash)
350
 
        elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
351
 
            self.kexAlg = 'diffie-hellman-group-exchange-sha1-old'
352
 
            self.ideal = struct.unpack('>L', packet)[0]
353
 
            self.g, self.p = self.factory.getDHPrime(self.ideal)
354
 
            self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p)+MP(self.g))
355
 
        else:
356
 
            raise error.ConchError('bad kexalg: %s'%self.kexAlg)
357
 
 
358
 
    def ssh_KEX_DH_GEX_REQUEST(self, packet):
359
 
        if self.ignoreNextPacket:
360
 
            self.ignoreNextPacket = 0
361
 
            return
362
 
        self.min, self.ideal, self.max = struct.unpack('>3L', packet)
363
 
        self.g, self.p = self.factory.getDHPrime(self.ideal)
364
 
        self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p)+MP(self.g))
365
 
 
366
 
    def ssh_KEX_DH_GEX_INIT(self, packet):
367
 
        clientDHPubKey, foo = getMP(packet)
368
 
 
369
 
        # if y < 1024, openssh will reject us: "bad server public DH value".
370
 
        # y<1024 means f will be short, and of the form 2^y, so an observer
371
 
        # could trivially derive our secret y from f. Openssh detects this
372
 
        # and complains, so avoid creating such values by requiring y to be
373
 
        # larger than ln2(self.p)
374
 
 
375
 
        # TODO: we should also look at the value they send to us and reject
376
 
        # insecure values of f (if g==2 and f has a single '1' bit while the
377
 
        # rest are '0's, then they must have used a small y also).
378
 
 
379
 
        # TODO: This could be computed when self.p is set up
380
 
        #  or do as openssh does and scan f for a single '1' bit instead
381
 
 
382
 
        minimum = long(math.floor(math.log(self.p) / math.log(2)) + 1)
383
 
        tries = 0
384
 
        pSize = Util.number.size(self.p)
385
 
        y = Util.number.getRandomNumber(pSize, entropy.get_bytes)
386
 
        while tries < 10 and y < minimum:
387
 
            tries += 1
388
 
            y = Util.number.getRandomNumber(pSize, entropy.get_bytes)
389
 
        assert(y >= minimum) # TODO: test_conch just hangs if this is hit
390
 
        # the chance of it being hit are really really low
391
 
 
392
 
        f = pow(self.g, y, self.p)
393
 
        sharedSecret = _MPpow(clientDHPubKey, y, self.p)
394
 
        h = sha.new()
395
 
        h.update(NS(self.otherVersionString))
396
 
        h.update(NS(self.ourVersionString))
397
 
        h.update(NS(self.clientKexInitPayload))
398
 
        h.update(NS(self.ourKexInitPayload))
399
 
        h.update(NS(self.factory.publicKeys[self.keyAlg]))
400
 
        if self.kexAlg == 'diffie-hellman-group-exchange-sha1':
401
 
            h.update(struct.pack('>3L', self.min, self.ideal, self.max))
402
 
        else:
403
 
            h.update(struct.pack('>L', self.ideal))
404
 
        h.update(MP(self.p))
405
 
        h.update(MP(self.g))
406
 
        h.update(MP(clientDHPubKey))
407
 
        h.update(MP(f))
408
 
        h.update(sharedSecret)
409
 
        exchangeHash = h.digest()
410
 
        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)))
412
 
        self._keySetup(sharedSecret, exchangeHash)
413
 
 
414
 
    def ssh_NEWKEYS(self, packet):
415
 
        if packet != '':
416
 
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "NEWKEYS takes no data")
417
 
        self.currentEncryptions = self.nextEncryptions
418
 
        if self.outgoingCompressionType == 'zlib':
419
 
            self.outgoingCompression = zlib.compressobj(6)
420
 
            #self.outgoingCompression.compress = lambda x: self.outgoingCompression.compress(x) + self.outgoingCompression.flush(zlib.Z_SYNC_FLUSH)
421
 
        if self.incomingCompressionType == 'zlib':
422
 
            self.incomingCompression = zlib.decompressobj()
423
 
 
424
 
    def ssh_SERVICE_REQUEST(self, packet):
425
 
        service, rest = getNS(packet)
426
 
        cls = self.factory.getService(self, service)
427
 
        if not cls:
428
 
            self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE, "don't have service %s"%service)
429
 
            return
430
 
        else:
431
 
            self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
432
 
            self.setService(cls())
433
 
 
434
 
    def _keySetup(self, sharedSecret, exchangeHash):
435
 
        if not self.sessionID:
436
 
            self.sessionID = exchangeHash
437
 
        initIVCS = self._getKey('A', sharedSecret, exchangeHash)
438
 
        initIVSC = self._getKey('B', sharedSecret, exchangeHash)
439
 
        encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
440
 
        encKeySC = self._getKey('D', sharedSecret, exchangeHash)
441
 
        integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
442
 
        integKeySC = self._getKey('F', sharedSecret, exchangeHash)
443
 
        self.nextEncryptions.setKeys(initIVSC, encKeySC, initIVCS, encKeyCS, integKeySC, integKeyCS)
444
 
        self.sendPacket(MSG_NEWKEYS, '')
445
 
 
446
 
    def _getKey(self, c, sharedSecret, exchangeHash):
447
 
        k1 = sha.new(sharedSecret+exchangeHash+c+self.sessionID).digest()
448
 
        k2 = sha.new(sharedSecret+exchangeHash+k1).digest()
449
 
        return k1+k2
450
 
 
451
 
class SSHClientTransport(SSHTransportBase):
452
 
    isClient = 1
453
 
 
454
 
    def connectionMade(self):
455
 
        SSHTransportBase.connectionMade(self)
456
 
        self._gotNewKeys = 0
457
 
 
458
 
    def ssh_KEXINIT(self, packet):
459
 
        self.serverKexInitPayload = chr(MSG_KEXINIT)+packet
460
 
        #cookie = packet[: 16] # taking this is unimportant
461
 
        k = getNS(packet[16:], 10)
462
 
        strings, rest = k[:-1], k[-1]
463
 
        kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS, langSC =  \
464
 
           [s.split(',')for s in strings]
465
 
        self.kexAlg = ffs(self.supportedKeyExchanges, kexAlgs)
466
 
        self.keyAlg = ffs(self.supportedPublicKeys, keyAlgs)
467
 
        self.nextEncryptions = SSHCiphers(
468
 
        ffs(self.supportedCiphers, encCS), 
469
 
            ffs(self.supportedCiphers, encSC), 
470
 
            ffs(self.supportedMACs, macCS), 
471
 
            ffs(self.supportedMACs, macSC), 
472
 
         )
473
 
        self.outgoingCompressionType = ffs(self.supportedCompressions, compCS)
474
 
        self.incomingCompressionType = ffs(self.supportedCompressions, compSC)
475
 
        if None in(self.kexAlg, self.keyAlg, self.outgoingCompressionType, self.incomingCompressionType):
476
 
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
477
 
            return
478
 
        if None in self.nextEncryptions.__dict__.values():
479
 
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
480
 
            return
481
 
        log.msg('kex alg, key alg: %s %s'%(self.kexAlg, self.keyAlg))
482
 
        log.msg('client->server: %s %s %s'%(self.nextEncryptions.outCipType, 
483
 
                                            self.nextEncryptions.outMacType, 
484
 
                                            self.outgoingCompressionType))
485
 
        log.msg('server->client: %s %s %s'%(self.nextEncryptions.inCipType, 
486
 
                                            self.nextEncryptions.inMacType, 
487
 
                                            self.incomingCompressionType))
488
 
 
489
 
        if self.kexAlg == 'diffie-hellman-group1-sha1':
490
 
            self.x = Util.number.getRandomNumber(512, entropy.get_bytes)
491
 
            self.DHpubKey = pow(DH_GENERATOR, self.x, DH_PRIME)
492
 
            self.sendPacket(MSG_KEXDH_INIT, MP(self.DHpubKey))
493
 
        else:
494
 
            self.sendPacket(MSG_KEX_DH_GEX_REQUEST_OLD, '\x00\x00\x08\x00')
495
 
 
496
 
    def ssh_KEX_DH_GEX_GROUP(self, packet):
497
 
        if self.kexAlg == 'diffie-hellman-group1-sha1':
498
 
            pubKey, packet = getNS(packet)
499
 
            f, packet = getMP(packet)
500
 
            signature, packet = getNS(packet)
501
 
            fingerprint = ':'.join(map(lambda c: '%02x'%ord(c), md5.new(pubKey).digest()))
502
 
            d = self.verifyHostKey(pubKey, fingerprint)
503
 
            d.addCallback(self._continueGEX_GROUP, pubKey, f, signature)
504
 
            d.addErrback(lambda unused,self=self:self.sendDisconnect(DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
505
 
        else:
506
 
            self.p, rest = getMP(packet)
507
 
            self.g, rest = getMP(rest)
508
 
            self.x = getMP('\x00\x00\x00\x40'+entropy.get_bytes(64))[0]
509
 
            self.DHpubKey = pow(self.g, self.x, self.p)
510
 
            self.sendPacket(MSG_KEX_DH_GEX_INIT, MP(self.DHpubKey))
511
 
 
512
 
    def _continueGEX_GROUP(self, ignored, pubKey, f, signature):
513
 
        serverKey = keys.getPublicKeyObject(pubKey)
514
 
        sharedSecret = _MPpow(f, self.x, DH_PRIME)
515
 
        h = sha.new()
516
 
        h.update(NS(self.ourVersionString))
517
 
        h.update(NS(self.otherVersionString))
518
 
        h.update(NS(self.ourKexInitPayload))
519
 
        h.update(NS(self.serverKexInitPayload))
520
 
        h.update(NS(pubKey))
521
 
        h.update(MP(self.DHpubKey))
522
 
        h.update(MP(f))
523
 
        h.update(sharedSecret)
524
 
        exchangeHash = h.digest()
525
 
        if not keys.verifySignature(serverKey, signature, exchangeHash):
526
 
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, 'bad signature')
527
 
            return
528
 
        self._keySetup(sharedSecret, exchangeHash)
529
 
 
530
 
    def ssh_KEX_DH_GEX_REPLY(self, packet):
531
 
        pubKey, packet = getNS(packet)
532
 
        f, packet = getMP(packet)
533
 
        signature, packet = getNS(packet)
534
 
        fingerprint = ':'.join(map(lambda c: '%02x'%ord(c), md5.new(pubKey).digest()))
535
 
        d = self.verifyHostKey(pubKey, fingerprint)
536
 
        d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
537
 
        d.addErrback(lambda unused, self=self: self.sendDisconnect(DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
538
 
 
539
 
    def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
540
 
        serverKey = keys.getPublicKeyObject(pubKey)
541
 
        sharedSecret = _MPpow(f, self.x, self.p)
542
 
        h = sha.new()
543
 
        h.update(NS(self.ourVersionString))
544
 
        h.update(NS(self.otherVersionString))
545
 
        h.update(NS(self.ourKexInitPayload))
546
 
        h.update(NS(self.serverKexInitPayload))
547
 
        h.update(NS(pubKey))
548
 
        h.update('\x00\x00\x08\x00')
549
 
        h.update(MP(self.p))
550
 
        h.update(MP(self.g))
551
 
        h.update(MP(self.DHpubKey))
552
 
        h.update(MP(f))
553
 
        h.update(sharedSecret)
554
 
        exchangeHash = h.digest()
555
 
        if not keys.verifySignature(serverKey, signature, exchangeHash):
556
 
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, 'bad signature')
557
 
            return
558
 
        self._keySetup(sharedSecret, exchangeHash)
559
 
 
560
 
    def _keySetup(self, sharedSecret, exchangeHash):
561
 
        if not self.sessionID:
562
 
            self.sessionID = exchangeHash
563
 
        initIVCS = self._getKey('A', sharedSecret, exchangeHash)
564
 
        initIVSC = self._getKey('B', sharedSecret, exchangeHash)
565
 
        encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
566
 
        encKeySC = self._getKey('D', sharedSecret, exchangeHash)
567
 
        integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
568
 
        integKeySC = self._getKey('F', sharedSecret, exchangeHash)
569
 
        self.nextEncryptions.setKeys(initIVCS, encKeyCS, initIVSC, encKeySC, integKeyCS, integKeySC)
570
 
        self.sendPacket(MSG_NEWKEYS, '')
571
 
        if self._gotNewKeys:
572
 
            self.ssh_NEWKEYS('')
573
 
 
574
 
    def _getKey(self, c, sharedSecret, exchangeHash):
575
 
        k1 = sha.new(sharedSecret+exchangeHash+c+self.sessionID).digest()
576
 
        k2 = sha.new(sharedSecret+exchangeHash+k1).digest()
577
 
        return k1+k2
578
 
 
579
 
    def ssh_NEWKEYS(self, packet):
580
 
        if packet != '':
581
 
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "NEWKEYS takes no data")
582
 
        if not self.nextEncryptions.enc_block_size:
583
 
            self._gotNewKeys = 1
584
 
            return
585
 
        self.currentEncryptions = self.nextEncryptions
586
 
        if self.outgoingCompressionType == 'zlib':
587
 
            self.outgoingCompression = zlib.compressobj(6)
588
 
            #self.outgoingCompression.compress = lambda x: self.outgoingCompression.compress(x) + self.outgoingCompression.flush(zlib.Z_SYNC_FLUSH)
589
 
        if self.incomingCompressionType == 'zlib':
590
 
            self.incomingCompression = zlib.decompressobj()
591
 
        self.connectionSecure()
592
 
 
593
 
    def ssh_SERVICE_ACCEPT(self, packet):
594
 
        name = getNS(packet)[0]
595
 
        if name != self.instance.name:
596
 
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "received accept for service we did not request")
597
 
        self.setService(self.instance)
598
 
 
599
 
    def requestService(self, instance):
600
 
        """
601
 
        Request that a service be run over this transport.
602
 
 
603
 
        @type instance: subclass of L{twisted.conch.ssh.service.SSHService}
604
 
        """
605
 
        self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
606
 
        self.instance = instance
607
 
 
608
 
    # client methods
609
 
    def verifyHostKey(self, hostKey, fingerprint):
610
 
        """Returns a Deferred that gets a callback if it is a valid key, or
611
 
        an errback if not.
612
 
 
613
 
        @type hostKey:      C{str}
614
 
        @type fingerprint:  C{str}
615
 
        @rtype:             L{Deferred}
616
 
        """
617
 
        # return  if it's good
618
 
        return defer.fail(NotImplementedError)
619
 
 
620
 
    def connectionSecure(self):
621
 
        """
622
 
        Called when the encryption has been set up.  Generally, 
623
 
        requestService() is called to run another service over the transport.
624
 
        """
625
 
        raise NotImplementedError
626
 
 
627
 
class _DummyCipher:
628
 
    block_size = 1
629
 
    
630
 
    def encrypt(self, x):
631
 
        return x
632
 
    
633
 
    decrypt = encrypt
634
 
 
635
 
class SSHCiphers:
636
 
    cipherMap = {
637
 
        '3des-cbc':('DES3', 24, 0), 
638
 
        'blowfish-cbc':('Blowfish', 16,0 ), 
639
 
        'aes256-cbc':('AES', 32, 0), 
640
 
        'aes192-cbc':('AES', 24, 0), 
641
 
        'aes128-cbc':('AES', 16, 0), 
642
 
        'arcfour':('ARC4', 16, 0), 
643
 
        'idea-cbc':('IDEA', 16, 0), 
644
 
        'cast128-cbc':('CAST', 16, 0), 
645
 
        'aes128-ctr':('AES', 16, 1),
646
 
        'aes192-ctr':('AES', 24, 1),
647
 
        'aes256-ctr':('AES', 32, 1),
648
 
        '3des-ctr':('DES3', 24, 1),
649
 
        'blowfish-ctr':('Blowfish', 16, 1),
650
 
        'idea-ctr':('IDEA', 16, 1),
651
 
        'cast128-ctr':('CAST', 16, 1),
652
 
        'none':(None, 0, 0),
653
 
    }
654
 
    macMap = {
655
 
        'hmac-sha1': 'sha', 
656
 
        'hmac-md5': 'md5',
657
 
        'none':None
658
 
     }
659
 
 
660
 
    def __init__(self, outCip, inCip, outMac, inMac):
661
 
        self.outCipType = outCip
662
 
        self.inCipType = inCip
663
 
        self.outMacType = outMac
664
 
        self.inMacType = inMac
665
 
        self.enc_block_size = 0
666
 
        self.dec_block_size = 0
667
 
 
668
 
    def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
669
 
        o = self._getCipher(self.outCipType, outIV, outKey)
670
 
        self.encrypt = o.encrypt
671
 
        self.enc_block_size = o.block_size
672
 
        o = self._getCipher(self.inCipType, inIV, inKey)
673
 
        self.decrypt = o.decrypt
674
 
        self.dec_block_size = o.block_size
675
 
        self.outMAC = self._getMAC(self.outMacType, outInteg)
676
 
        self.inMAC = self._getMAC(self.inMacType, inInteg)
677
 
        self.verify_digest_size = self.inMAC[3]
678
 
 
679
 
    def _getCipher(self, cip, iv, key):
680
 
        modName, keySize, counterMode = self.cipherMap[cip]
681
 
        if not modName: # no cipher
682
 
            return _DummyCipher()
683
 
        mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x')
684
 
        if counterMode:
685
 
            return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size], counter=_Counter(iv, mod.block_size))
686
 
        else:
687
 
            return mod.new(key[: keySize], mod.MODE_CBC, iv[: mod.block_size])
688
 
 
689
 
    def _getMAC(self, mac, key):
690
 
        modName = self.macMap[mac]
691
 
        if not modName:
692
 
            return None
693
 
        mod = __import__(modName, {}, {}, '')
694
 
        if not hasattr(mod, 'digest_size'):
695
 
            ds = len(mod.new().digest())
696
 
        else:
697
 
            ds = mod.digest_size
698
 
        key = key[: ds]+'\x00'*(64-ds)
699
 
        i = XOR.new('\x36').encrypt(key)
700
 
        o = XOR.new('\x5c').encrypt(key)
701
 
        return mod, i,o, ds
702
 
 
703
 
    def encrypt(self, blocks):
704
 
        return blocks
705
 
 
706
 
    def decrypt(self, blocks):
707
 
        return blocks
708
 
 
709
 
    def makeMAC(self, seqid, data):
710
 
        if not self.outMAC: return ''
711
 
        data = struct.pack('>L', seqid)+data
712
 
        mod, i, o, ds = self.outMAC
713
 
        inner = mod.new(i+data)
714
 
        outer = mod.new(o+inner.digest())
715
 
        return outer.digest()
716
 
 
717
 
    def verify(self, seqid, data, mac):
718
 
        if not self.inMAC:
719
 
            return mac == ''
720
 
        data = struct.pack('>L', seqid)+data
721
 
        mod, i,o, ds = self.inMAC
722
 
        inner = mod.new(i+data)
723
 
        outer = mod.new(o+inner.digest())
724
 
        return mac == outer.digest()
725
 
 
726
 
class _Counter:
727
 
    """
728
 
    Stateful counter which returns results packed in a byte string
729
 
    """
730
 
    def __init__(self, initialVector, blockSize):
731
 
        """
732
 
        @type initialVector: C{str}
733
 
        @param initialVector: A byte string representing the initial counter value.
734
 
 
735
 
        @type blockSize: C{int}
736
 
        @param blockSize: The length of the output buffer, as well as the
737
 
        number of bytes at the beginning of C{initialVector} to consider.
738
 
        """
739
 
        initialVector = initialVector[:blockSize]
740
 
        self.count = getMP('\xff\xff\xff\xff' + initialVector)[0]
741
 
        self.blockSize = blockSize
742
 
        self.count = Util.number.long_to_bytes(self.count - 1)
743
 
        self.count = '\x00' * (self.blockSize - len(self.count)) + self.count
744
 
        self.count = array.array('c', self.count)
745
 
        self.len = len(self.count) - 1
746
 
 
747
 
 
748
 
    def __call__(self):
749
 
        """
750
 
        Increment the counter and return the new value.
751
 
        """
752
 
        i = self.len
753
 
        while i > -1:
754
 
            self.count[i] = n = chr((ord(self.count[i]) + 1) % 256)
755
 
            if n == '\x00':
756
 
                i -= 1
757
 
            else:
758
 
                return self.count.tostring()
759
 
 
760
 
        self.count = array.array('c', '\x00' * self.blockSize)
761
 
        return self.count.tostring()
762
 
 
763
 
 
764
 
 
765
 
def buffer_dump(b, title = None):
766
 
    r = title or ''
767
 
    while b:
768
 
        c, b = b[: 16], b[16:]
769
 
        while c:
770
 
            a, c = c[: 2], c[2:]
771
 
            if len(a) == 2:
772
 
                r = r+'%02x%02x '%(ord(a[0]), ord(a[1]))
773
 
            else:
774
 
                r = r+'%02x'%ord(a[0])
775
 
        r = r+'\n'
776
 
    return r
777
 
 
778
 
DH_PRIME = 179769313486231590770839156793787453197860296048756011706444423684197180216158519368947833795864925541502180565485980503646440548199239100050792877003355816639229553136239076508735759914822574862575007425302077447712589550957937778424442426617334727629299387668709205606050270810842907692932019128194467627007L
779
 
DH_GENERATOR = 2L
780
 
 
781
 
MSG_DISCONNECT = 1
782
 
MSG_IGNORE = 2
783
 
MSG_UNIMPLEMENTED = 3
784
 
MSG_DEBUG = 4
785
 
MSG_SERVICE_REQUEST = 5
786
 
MSG_SERVICE_ACCEPT = 6
787
 
MSG_KEXINIT = 20
788
 
MSG_NEWKEYS = 21
789
 
MSG_KEXDH_INIT = 30
790
 
MSG_KEXDH_REPLY = 31
791
 
MSG_KEX_DH_GEX_REQUEST_OLD = 30
792
 
MSG_KEX_DH_GEX_REQUEST = 34
793
 
MSG_KEX_DH_GEX_GROUP = 31
794
 
MSG_KEX_DH_GEX_INIT = 32
795
 
MSG_KEX_DH_GEX_REPLY = 33
796
 
 
797
 
DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
798
 
DISCONNECT_PROTOCOL_ERROR = 2
799
 
DISCONNECT_KEY_EXCHANGE_FAILED = 3
800
 
DISCONNECT_RESERVED = 4
801
 
DISCONNECT_MAC_ERROR = 5
802
 
DISCONNECT_COMPRESSION_ERROR = 6
803
 
DISCONNECT_SERVICE_NOT_AVAILABLE = 7
804
 
DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
805
 
DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
806
 
DISCONNECT_CONNECTION_LOST = 10
807
 
DISCONNECT_BY_APPLICATION = 11
808
 
DISCONNECT_TOO_MANY_CONNECTIONS = 12
809
 
DISCONNECT_AUTH_CANCELLED_BY_USER = 13
810
 
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
811
 
DISCONNECT_ILLEGAL_USER_NAME = 15
812
 
 
813
 
messages = {}
814
 
for name, value in globals().items():
815
 
    if name.startswith('MSG_'):
816
 
        messages[value] = name
817