~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/ssh/transport.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_transport -*-
 
2
#
 
3
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
"""
 
7
The lowest level SSH protocol.  This handles the key negotiation, the
 
8
encryption and the compression.  The transport layer is described in
 
9
RFC 4253.
 
10
 
 
11
Maintainer: Paul Swartz
 
12
"""
 
13
 
 
14
# base library imports
 
15
import struct
 
16
import zlib
 
17
import array
 
18
 
 
19
# external library imports
 
20
from Crypto import Util
 
21
from Crypto.Cipher import XOR
 
22
 
 
23
# twisted imports
 
24
from twisted.internet import protocol, defer
 
25
from twisted.conch import error
 
26
from twisted.python import log, randbytes
 
27
from twisted.python.hashlib import md5, sha1
 
28
 
 
29
# sibling imports
 
30
from twisted.conch.ssh import keys
 
31
from twisted.conch.ssh.common import NS, getNS, MP, getMP, _MPpow, ffs
 
32
 
 
33
 
 
34
 
 
35
class SSHTransportBase(protocol.Protocol):
 
36
    """
 
37
    Protocol supporting basic SSH functionality: sending/receiving packets
 
38
    and message dispatch.  To connect to or run a server, you must use
 
39
    SSHClientTransport or SSHServerTransport.
 
40
 
 
41
    @ivar protocolVersion: A string representing the version of the SSH
 
42
        protocol we support.  Currently defaults to '2.0'.
 
43
 
 
44
    @ivar version: A string representing the version of the server or client.
 
45
        Currently defaults to 'Twisted'.
 
46
 
 
47
    @ivar comment: An optional string giving more information about the
 
48
        server or client.
 
49
 
 
50
    @ivar supportedCiphers: A list of strings representing the encryption
 
51
        algorithms supported, in order from most-preferred to least.
 
52
 
 
53
    @ivar supportedMACs: A list of strings representing the message
 
54
        authentication codes (hashes) supported, in order from most-preferred
 
55
        to least.  Both this and supportedCiphers can include 'none' to use
 
56
        no encryption or authentication, but that must be done manually,
 
57
 
 
58
    @ivar supportedKeyExchanges: A list of strings representing the
 
59
        key exchanges supported, in order from most-preferred to least.
 
60
 
 
61
    @ivar supportedPublicKeys:  A list of strings representing the
 
62
        public key types supported, in order from most-preferred to least.
 
63
 
 
64
    @ivar supportedCompressions: A list of strings representing compression
 
65
        types supported, from most-preferred to least.
 
66
 
 
67
    @ivar supportedLanguages: A list of strings representing languages
 
68
        supported, from most-preferred to least.
 
69
 
 
70
    @ivar isClient: A boolean indicating whether this is a client or server.
 
71
 
 
72
    @ivar gotVersion: A boolean indicating whether we have receieved the
 
73
        version string from the other side.
 
74
 
 
75
    @ivar buf: Data we've received but hasn't been parsed into a packet.
 
76
 
 
77
    @ivar outgoingPacketSequence: the sequence number of the next packet we
 
78
        will send.
 
79
 
 
80
    @ivar incomingPacketSequence: the sequence number of the next packet we
 
81
        are expecting from the other side.
 
82
 
 
83
    @ivar outgoingCompression: an object supporting the .compress(str) and
 
84
        .flush() methods, or None if there is no outgoing compression.  Used to
 
85
        compress outgoing data.
 
86
 
 
87
    @ivar outgoingCompressionType: A string representing the outgoing
 
88
        compression type.
 
89
 
 
90
    @ivar incomingCompression: an object supporting the .decompress(str)
 
91
        method, or None if there is no incoming compression.  Used to
 
92
        decompress incoming data.
 
93
 
 
94
    @ivar incomingCompressionType: A string representing the incoming
 
95
        compression type.
 
96
 
 
97
    @ivar ourVersionString: the version string that we sent to the other side.
 
98
        Used in the key exchange.
 
99
 
 
100
    @ivar otherVersionString: the version string sent by the other side.  Used
 
101
        in the key exchange.
 
102
 
 
103
    @ivar ourKexInitPayload: the MSG_KEXINIT payload we sent.  Used in the key
 
104
        exchange.
 
105
 
 
106
    @ivar otherKexInitPayload: the MSG_KEXINIT payload we received.  Used in
 
107
        the key exchange
 
108
 
 
109
    @ivar sessionID: a string that is unique to this SSH session.  Created as
 
110
        part of the key exchange, sessionID is used to generate the various
 
111
        encryption and authentication keys.
 
112
 
 
113
    @ivar service: an SSHService instance, or None.  If it's set to an object,
 
114
        it's the currently running service.
 
115
 
 
116
    @ivar kexAlg: the agreed-upon key exchange algorithm.
 
117
 
 
118
    @ivar keyAlg: the agreed-upon public key type for the key exchange.
 
119
 
 
120
    @ivar currentEncryptions: an SSHCiphers instance.  It represents the
 
121
        current encryption and authentication options for the transport.
 
122
 
 
123
    @ivar nextEncryptions: an SSHCiphers instance.  Held here until the
 
124
        MSG_NEWKEYS messages are exchanged, when nextEncryptions is
 
125
        transitioned to currentEncryptions.
 
126
 
 
127
    @ivar first: the first bytes of the next packet.  In order to avoid
 
128
        decrypting data twice, the first bytes are decrypted and stored until
 
129
        the whole packet is available.
 
130
 
 
131
    """
 
132
 
 
133
 
 
134
    protocolVersion = '2.0'
 
135
    version = 'Twisted'
 
136
    comment = ''
 
137
    ourVersionString = ('SSH-' + protocolVersion + '-' + version + ' '
 
138
            + comment).strip()
 
139
    supportedCiphers = ['aes256-ctr', 'aes256-cbc', 'aes192-ctr', 'aes192-cbc',
 
140
                        'aes128-ctr', 'aes128-cbc', 'cast128-ctr',
 
141
                        'cast128-cbc', 'blowfish-ctr', 'blowfish-cbc',
 
142
                        '3des-ctr', '3des-cbc'] # ,'none']
 
143
    supportedMACs = ['hmac-sha1', 'hmac-md5'] # , 'none']
 
144
    # both of the above support 'none', but for security are disabled by
 
145
    # default.  to enable them, subclass this class and add it, or do:
 
146
    #   SSHTransportBase.supportedCiphers.append('none')
 
147
    supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1',
 
148
                             'diffie-hellman-group1-sha1']
 
149
    supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
 
150
    supportedCompressions = ['none', 'zlib']
 
151
    supportedLanguages = ()
 
152
    isClient = False
 
153
    gotVersion = False
 
154
    buf = ''
 
155
    outgoingPacketSequence = 0
 
156
    incomingPacketSequence = 0
 
157
    outgoingCompression = None
 
158
    incomingCompression = None
 
159
    sessionID = None
 
160
    service = None
 
161
 
 
162
 
 
163
    def connectionLost(self, reason):
 
164
        if self.service:
 
165
            self.service.serviceStopped()
 
166
        if hasattr(self, 'avatar'):
 
167
            self.logoutFunction()
 
168
        log.msg('connection lost')
 
169
 
 
170
 
 
171
    def connectionMade(self):
 
172
        """
 
173
        Called when the connection is made to the other side.  We sent our
 
174
        version and the MSG_KEXINIT packet.
 
175
        """
 
176
        self.transport.write('%s\r\n' % (self.ourVersionString,))
 
177
        self.currentEncryptions = SSHCiphers('none', 'none', 'none', 'none')
 
178
        self.currentEncryptions.setKeys('', '', '', '', '', '')
 
179
        self.sendKexInit()
 
180
 
 
181
 
 
182
    def sendKexInit(self):
 
183
        self.ourKexInitPayload = (chr(MSG_KEXINIT) +
 
184
               randbytes.secureRandom(16) +
 
185
               NS(','.join(self.supportedKeyExchanges)) +
 
186
               NS(','.join(self.supportedPublicKeys)) +
 
187
               NS(','.join(self.supportedCiphers)) +
 
188
               NS(','.join(self.supportedCiphers)) +
 
189
               NS(','.join(self.supportedMACs)) +
 
190
               NS(','.join(self.supportedMACs)) +
 
191
               NS(','.join(self.supportedCompressions)) +
 
192
               NS(','.join(self.supportedCompressions)) +
 
193
               NS(','.join(self.supportedLanguages)) +
 
194
               NS(','.join(self.supportedLanguages)) +
 
195
               '\000' + '\000\000\000\000')
 
196
        self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
 
197
 
 
198
 
 
199
    def sendPacket(self, messageType, payload):
 
200
        """
 
201
        Sends a packet.  If it's been set up, compress the data, encrypt it,
 
202
        and authenticate it before sending.
 
203
 
 
204
        @param messageType: The type of the packet; generally one of the
 
205
                            MSG_* values.
 
206
        @type messageType: C{int}
 
207
        @param payload: The payload for the message.
 
208
        @type payload: C{str}
 
209
        """
 
210
        payload = chr(messageType) + payload
 
211
        if self.outgoingCompression:
 
212
            payload = (self.outgoingCompression.compress(payload)
 
213
                       + self.outgoingCompression.flush(2))
 
214
        bs = self.currentEncryptions.encBlockSize
 
215
        # 4 for the packet length and 1 for the padding length
 
216
        totalSize = 5 + len(payload)
 
217
        lenPad = bs - (totalSize % bs)
 
218
        if lenPad < 4:
 
219
            lenPad = lenPad + bs
 
220
        packet = (struct.pack('!LB',
 
221
                              totalSize + lenPad - 4, lenPad) +
 
222
                  payload + randbytes.secureRandom(lenPad))
 
223
        encPacket = (
 
224
            self.currentEncryptions.encrypt(packet) +
 
225
            self.currentEncryptions.makeMAC(
 
226
                self.outgoingPacketSequence, packet))
 
227
        self.transport.write(encPacket)
 
228
        self.outgoingPacketSequence += 1
 
229
 
 
230
 
 
231
    def getPacket(self):
 
232
        """
 
233
        Try to return a decrypted, authenticated, and decompressed packet
 
234
        out of the buffer.  If there is not enough data, return None.
 
235
 
 
236
        @rtype: C{str}/C{None}
 
237
        """
 
238
        bs = self.currentEncryptions.decBlockSize
 
239
        ms = self.currentEncryptions.verifyDigestSize
 
240
        if len(self.buf) < bs: return # not enough data
 
241
        if not hasattr(self, 'first'):
 
242
            first = self.currentEncryptions.decrypt(self.buf[:bs])
 
243
        else:
 
244
            first = self.first
 
245
            del self.first
 
246
        packetLen, paddingLen = struct.unpack('!LB', first[:5])
 
247
        if packetLen > 1048576: # 1024 ** 2
 
248
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
 
249
                                'bad packet length %s' % packetLen)
 
250
            return
 
251
        if len(self.buf) < packetLen + 4 + ms:
 
252
            self.first = first
 
253
            return # not enough packet
 
254
        if(packetLen + 4) % bs != 0:
 
255
            self.sendDisconnect(
 
256
                DISCONNECT_PROTOCOL_ERROR,
 
257
                'bad packet mod (%i%%%i == %i)' % (packetLen + 4, bs,
 
258
                                                   (packetLen + 4) % bs))
 
259
            return
 
260
        encData, self.buf = self.buf[:4 + packetLen], self.buf[4 + packetLen:]
 
261
        packet = first + self.currentEncryptions.decrypt(encData[bs:])
 
262
        if len(packet) != 4 + packetLen:
 
263
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
 
264
                                'bad decryption')
 
265
            return
 
266
        if ms:
 
267
            macData, self.buf = self.buf[:ms], self.buf[ms:]
 
268
            if not self.currentEncryptions.verify(self.incomingPacketSequence,
 
269
                                                  packet, macData):
 
270
                self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC')
 
271
                return
 
272
        payload = packet[5:-paddingLen]
 
273
        if self.incomingCompression:
 
274
            try:
 
275
                payload = self.incomingCompression.decompress(payload)
 
276
            except: # bare except, because who knows what kind of errors
 
277
                    # decompression can raise
 
278
                log.err()
 
279
                self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR,
 
280
                                    'compression error')
 
281
                return
 
282
        self.incomingPacketSequence += 1
 
283
        return payload
 
284
 
 
285
 
 
286
    def dataReceived(self, data):
 
287
        """
 
288
        First, check for the version string (SSH-2.0-*).  After that has been
 
289
        received, this method adds data to the buffer, and pulls out any
 
290
        packets.
 
291
 
 
292
        @type data: C{str}
 
293
        """
 
294
        self.buf = self.buf + data
 
295
        if not self.gotVersion:
 
296
            if self.buf.find('\n', self.buf.find('SSH-')) == -1:
 
297
                return
 
298
            lines = self.buf.split('\n')
 
299
            for p in lines:
 
300
                if p.startswith('SSH-'):
 
301
                    self.gotVersion = True
 
302
                    self.otherVersionString = p.strip()
 
303
                    if p.split('-')[1] not in ('1.99', '2.0'): # bad version
 
304
                        self.sendDisconnect(
 
305
                            DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
 
306
                            'bad version ' + p.split('-')[1])
 
307
                        return
 
308
                    i = lines.index(p)
 
309
                    self.buf = '\n'.join(lines[i + 1:])
 
310
        packet = self.getPacket()
 
311
        while packet:
 
312
            messageNum = ord(packet[0])
 
313
            self.dispatchMessage(messageNum, packet[1:])
 
314
            packet = self.getPacket()
 
315
 
 
316
 
 
317
    def dispatchMessage(self, messageNum, payload):
 
318
        """
 
319
        Send a received message to the appropriate method.
 
320
 
 
321
        @type messageNum: C{int}
 
322
        @type payload: c{str}
 
323
        """
 
324
        if messageNum < 50 and messageNum in messages:
 
325
            messageType = messages[messageNum][4:]
 
326
            f = getattr(self, 'ssh_%s' % messageType, None)
 
327
            if f is not None:
 
328
                f(payload)
 
329
            else:
 
330
                log.msg("couldn't handle %s" % messageType)
 
331
                log.msg(repr(payload))
 
332
                self.sendUnimplemented()
 
333
        elif self.service:
 
334
            log.callWithLogger(self.service, self.service.packetReceived,
 
335
                               messageNum, payload)
 
336
        else:
 
337
            log.msg("couldn't handle %s" % messageNum)
 
338
            log.msg(repr(payload))
 
339
            self.sendUnimplemented()
 
340
 
 
341
 
 
342
    def ssh_KEXINIT(self, packet):
 
343
        """
 
344
        Called when we receive a MSG_KEXINIT message.  Payload::
 
345
            bytes[16] cookie
 
346
            string keyExchangeAlgorithms
 
347
            string keyAlgorithms
 
348
            string incomingEncryptions
 
349
            string outgoingEncryptions
 
350
            string incomingAuthentications
 
351
            string outgoingAuthentications
 
352
            string incomingCompressions
 
353
            string outgoingCompressions
 
354
            string incomingLanguages
 
355
            string outgoingLanguages
 
356
            bool firstPacketFollows
 
357
            unit32 0 (reserved)
 
358
 
 
359
        Starts setting up the key exchange, keys, encryptions, and
 
360
        authentications.  Extended by ssh_KEXINIT in SSHServerTransport and
 
361
        SSHClientTransport.
 
362
        """
 
363
        self.otherKexInitPayload = chr(MSG_KEXINIT) + packet
 
364
        #cookie = packet[: 16] # taking this is useless
 
365
        k = getNS(packet[16:], 10)
 
366
        strings, rest = k[:-1], k[-1]
 
367
        (kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
 
368
         langSC) = [s.split(',') for s in strings]
 
369
        # these are the server directions
 
370
        outs = [encSC, macSC, compSC]
 
371
        ins = [encCS, macSC, compCS]
 
372
        if self.isClient:
 
373
            outs, ins = ins, outs # switch directions
 
374
        server = (self.supportedKeyExchanges, self.supportedPublicKeys,
 
375
                self.supportedCiphers, self.supportedCiphers,
 
376
                self.supportedMACs, self.supportedMACs,
 
377
                self.supportedCompressions, self.supportedCompressions)
 
378
        client = (kexAlgs, keyAlgs, outs[0], ins[0], outs[1], ins[1],
 
379
                outs[2], ins[2])
 
380
        if self.isClient:
 
381
            server, client = client, server
 
382
        self.kexAlg = ffs(client[0], server[0])
 
383
        self.keyAlg = ffs(client[1], server[1])
 
384
        self.nextEncryptions = SSHCiphers(
 
385
            ffs(client[2], server[2]),
 
386
            ffs(client[3], server[3]),
 
387
            ffs(client[4], server[4]),
 
388
            ffs(client[5], server[5]))
 
389
        self.outgoingCompressionType = ffs(client[6], server[6])
 
390
        self.incomingCompressionType = ffs(client[7], server[7])
 
391
        if None in (self.kexAlg, self.keyAlg, self.outgoingCompressionType,
 
392
                    self.incomingCompressionType):
 
393
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
 
394
                                "couldn't match all kex parts")
 
395
            return
 
396
        if None in self.nextEncryptions.__dict__.values():
 
397
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
 
398
                                "couldn't match all kex parts")
 
399
            return
 
400
        log.msg('kex alg, key alg: %s %s' % (self.kexAlg, self.keyAlg))
 
401
        log.msg('outgoing: %s %s %s' % (self.nextEncryptions.outCipType,
 
402
                                        self.nextEncryptions.outMACType,
 
403
                                        self.outgoingCompressionType))
 
404
        log.msg('incoming: %s %s %s' % (self.nextEncryptions.inCipType,
 
405
                                        self.nextEncryptions.inMACType,
 
406
                                        self.incomingCompressionType))
 
407
        return kexAlgs, keyAlgs, rest # for SSHServerTransport to use
 
408
 
 
409
 
 
410
    def ssh_DISCONNECT(self, packet):
 
411
        """
 
412
        Called when we receive a MSG_DISCONNECT message.  Payload::
 
413
            long code
 
414
            string description
 
415
 
 
416
        This means that the other side has disconnected.  Pass the message up
 
417
        and disconnect ourselves.
 
418
        """
 
419
        reasonCode = struct.unpack('>L', packet[: 4])[0]
 
420
        description, foo = getNS(packet[4:])
 
421
        self.receiveError(reasonCode, description)
 
422
        self.transport.loseConnection()
 
423
 
 
424
 
 
425
    def ssh_IGNORE(self, packet):
 
426
        """
 
427
        Called when we receieve a MSG_IGNORE message.  No payload.
 
428
        This means nothing; we simply return.
 
429
        """
 
430
 
 
431
 
 
432
    def ssh_UNIMPLEMENTED(self, packet):
 
433
        """
 
434
        Called when we receieve a MSG_UNIMPLEMENTED message.  Payload::
 
435
            long packet
 
436
 
 
437
        This means that the other side did not implement one of our packets.
 
438
        """
 
439
        seqnum, = struct.unpack('>L', packet)
 
440
        self.receiveUnimplemented(seqnum)
 
441
 
 
442
 
 
443
    def ssh_DEBUG(self, packet):
 
444
        """
 
445
        Called when we receieve a MSG_DEBUG message.  Payload::
 
446
            bool alwaysDisplay
 
447
            string message
 
448
            string language
 
449
 
 
450
        This means the other side has passed along some debugging info.
 
451
        """
 
452
        alwaysDisplay = bool(packet[0])
 
453
        message, lang, foo = getNS(packet[1:], 2)
 
454
        self.receiveDebug(alwaysDisplay, message, lang)
 
455
 
 
456
 
 
457
    def setService(self, service):
 
458
        """
 
459
        Set our service to service and start it running.  If we were
 
460
        running a service previously, stop it first.
 
461
 
 
462
        @type service: C{SSHService}
 
463
        """
 
464
        log.msg('starting service %s' % service.name)
 
465
        if self.service:
 
466
            self.service.serviceStopped()
 
467
        self.service = service
 
468
        service.transport = self
 
469
        self.service.serviceStarted()
 
470
 
 
471
 
 
472
    def sendDebug(self, message, alwaysDisplay=False, language=''):
 
473
        """
 
474
        Send a debug message to the other side.
 
475
 
 
476
        @param message: the message to send.
 
477
        @type message: C{str}
 
478
        @param alwaysDisplay: if True, tell the other side to always
 
479
                              display this message.
 
480
        @type alwaysDisplay: C{bool}
 
481
        @param language: optionally, the language the message is in.
 
482
        @type language: C{str}
 
483
        """
 
484
        self.sendPacket(MSG_DEBUG, chr(alwaysDisplay) + NS(message) +
 
485
                        NS(language))
 
486
 
 
487
 
 
488
    def sendIgnore(self, message):
 
489
        """
 
490
        Send a message that will be ignored by the other side.  This is
 
491
        useful to fool attacks based on guessing packet sizes in the
 
492
        encrypted stream.
 
493
 
 
494
        @param message: data to send with the message
 
495
        @type message: C{str}
 
496
        """
 
497
        self.sendPacket(MSG_IGNORE, NS(message))
 
498
 
 
499
 
 
500
    def sendUnimplemented(self):
 
501
        """
 
502
        Send a message to the other side that the last packet was not
 
503
        understood.
 
504
        """
 
505
        seqnum = self.incomingPacketSequence
 
506
        self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
 
507
 
 
508
 
 
509
    def sendDisconnect(self, reason, desc):
 
510
        """
 
511
        Send a disconnect message to the other side and then disconnect.
 
512
 
 
513
        @param reason: the reason for the disconnect.  Should be one of the
 
514
                       DISCONNECT_* values.
 
515
        @type reason: C{int}
 
516
        @param desc: a descrption of the reason for the disconnection.
 
517
        @type desc: C{str}
 
518
        """
 
519
        self.sendPacket(
 
520
            MSG_DISCONNECT, struct.pack('>L', reason) + NS(desc) + NS(''))
 
521
        log.msg('Disconnecting with error, code %s\nreason: %s' % (reason,
 
522
                                                                   desc))
 
523
        self.transport.loseConnection()
 
524
 
 
525
 
 
526
    def _getKey(self, c, sharedSecret, exchangeHash):
 
527
        """
 
528
        Get one of the keys for authentication/encryption.
 
529
 
 
530
        @type c: C{str}
 
531
        @type sharedSecret: C{str}
 
532
        @type exchangeHash: C{str}
 
533
        """
 
534
        k1 = sha1(sharedSecret + exchangeHash + c + self.sessionID)
 
535
        k1 = k1.digest()
 
536
        k2 = sha1(sharedSecret + exchangeHash + k1).digest()
 
537
        return k1 + k2
 
538
 
 
539
 
 
540
    def _keySetup(self, sharedSecret, exchangeHash):
 
541
        """
 
542
        Set up the keys for the connection and sends MSG_NEWKEYS when
 
543
        finished,
 
544
 
 
545
        @param sharedSecret: a secret string agreed upon using a Diffie-
 
546
                             Hellman exchange, so it is only shared between
 
547
                             the server and the client.
 
548
        @type sharedSecret: C{str}
 
549
        @param exchangeHash: A hash of various data known by both sides.
 
550
        @type exchangeHash: C{str}
 
551
        """
 
552
        if not self.sessionID:
 
553
            self.sessionID = exchangeHash
 
554
        initIVCS = self._getKey('A', sharedSecret, exchangeHash)
 
555
        initIVSC = self._getKey('B', sharedSecret, exchangeHash)
 
556
        encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
 
557
        encKeySC = self._getKey('D', sharedSecret, exchangeHash)
 
558
        integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
 
559
        integKeySC = self._getKey('F', sharedSecret, exchangeHash)
 
560
        outs = [initIVSC, encKeySC, integKeySC]
 
561
        ins = [initIVCS, encKeyCS, integKeyCS]
 
562
        if self.isClient: # reverse for the client
 
563
            log.msg('REVERSE')
 
564
            outs, ins = ins, outs
 
565
        self.nextEncryptions.setKeys(outs[0], outs[1], ins[0], ins[1],
 
566
                                     outs[2], ins[2])
 
567
        self.sendPacket(MSG_NEWKEYS, '')
 
568
 
 
569
 
 
570
    def isEncrypted(self, direction="out"):
 
571
        """
 
572
        Return True if the connection is encrypted in the given direction.
 
573
        Direction must be one of ["out", "in", "both"].
 
574
        """
 
575
        if direction == "out":
 
576
            return self.currentEncryptions.outCipType != 'none'
 
577
        elif direction == "in":
 
578
            return self.currentEncryptions.inCipType != 'none'
 
579
        elif direction == "both":
 
580
            return self.isEncrypted("in") and self.isEncrypted("out")
 
581
        else:
 
582
            raise TypeError('direction must be "out", "in", or "both"')
 
583
 
 
584
 
 
585
    def isVerified(self, direction="out"):
 
586
        """
 
587
        Return True if the connecction is verified/authenticated in the
 
588
        given direction.  Direction must be one of ["out", "in", "both"].
 
589
        """
 
590
        if direction == "out":
 
591
            return self.currentEncryptions.outMACType != 'none'
 
592
        elif direction == "in":
 
593
            return self.currentEncryptions.inMACType != 'none'
 
594
        elif direction == "both":
 
595
            return self.isVerified("in")and self.isVerified("out")
 
596
        else:
 
597
            raise TypeError('direction must be "out", "in", or "both"')
 
598
 
 
599
 
 
600
    def loseConnection(self):
 
601
        """
 
602
        Lose the connection to the other side, sending a
 
603
        DISCONNECT_CONNECTION_LOST message.
 
604
        """
 
605
        self.sendDisconnect(DISCONNECT_CONNECTION_LOST,
 
606
                            "user closed connection")
 
607
 
 
608
 
 
609
    # client methods
 
610
    def receiveError(self, reasonCode, description):
 
611
        """
 
612
        Called when we receive a disconnect error message from the other
 
613
        side.
 
614
 
 
615
        @param reasonCode: the reason for the disconnect, one of the
 
616
                           DISCONNECT_ values.
 
617
        @type reasonCode: C{int}
 
618
        @param description: a human-readable description of the
 
619
                            disconnection.
 
620
        @type description: C{str}
 
621
        """
 
622
        log.msg('Got remote error, code %s\nreason: %s' % (reasonCode,
 
623
                                                           description))
 
624
 
 
625
 
 
626
    def receiveUnimplemented(self, seqnum):
 
627
        """
 
628
        Called when we receive an unimplemented packet message from the other
 
629
        side.
 
630
 
 
631
        @param seqnum: the sequence number that was not understood.
 
632
        @type seqnum: C{int}
 
633
        """
 
634
        log.msg('other side unimplemented packet #%s' % seqnum)
 
635
 
 
636
 
 
637
    def receiveDebug(self, alwaysDisplay, message, lang):
 
638
        """
 
639
        Called when we receive a debug message from the other side.
 
640
 
 
641
        @param alwaysDisplay: if True, this message should always be
 
642
                              displayed.
 
643
        @type alwaysDisplay: C{bool}
 
644
        @param message: the debug message
 
645
        @type message: C{str}
 
646
        @param lang: optionally the language the message is in.
 
647
        @type lang: C{str}
 
648
        """
 
649
        if alwaysDisplay:
 
650
            log.msg('Remote Debug Message: %s' % message)
 
651
 
 
652
 
 
653
 
 
654
class SSHServerTransport(SSHTransportBase):
 
655
    """
 
656
    SSHServerTransport implements the server side of the SSH protocol.
 
657
 
 
658
    @ivar isClient: since we are never the client, this is always False.
 
659
 
 
660
    @ivar ignoreNextPacket: if True, ignore the next key exchange packet.  This
 
661
        is set when the client sends a guessed key exchange packet but with
 
662
        an incorrect guess.
 
663
 
 
664
    @ivar dhGexRequest: the KEX_DH_GEX_REQUEST(_OLD) that the client sent.
 
665
        The key generation needs this to be stored.
 
666
 
 
667
    @ivar g: the Diffie-Hellman group generator.
 
668
 
 
669
    @ivar p: the Diffie-Hellman group prime.
 
670
    """
 
671
    isClient = False
 
672
    ignoreNextPacket = 0
 
673
 
 
674
 
 
675
    def ssh_KEXINIT(self, packet):
 
676
        """
 
677
        Called when we receive a MSG_KEXINIT message.  For a description
 
678
        of the packet, see SSHTransportBase.ssh_KEXINIT().  Additionally,
 
679
        this method checks if a guessed key exchange packet was sent.  If
 
680
        it was sent, and it guessed incorrectly, the next key exchange
 
681
        packet MUST be ignored.
 
682
        """
 
683
        retval = SSHTransportBase.ssh_KEXINIT(self, packet)
 
684
        if not retval: # disconnected
 
685
            return
 
686
        else:
 
687
            kexAlgs, keyAlgs, rest = retval
 
688
        if ord(rest[0]): # first_kex_packet_follows
 
689
            if (kexAlgs[0] != self.supportedKeyExchanges[0] or
 
690
                keyAlgs[0] != self.supportedPublicKeys[0]):
 
691
                self.ignoreNextPacket = True # guess was wrong
 
692
 
 
693
 
 
694
    def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
 
695
        """
 
696
        This represents two different key exchange methods that share the
 
697
        same integer value.
 
698
 
 
699
        KEXDH_INIT (for diffie-hellman-group1-sha1 exchanges) payload::
 
700
 
 
701
                integer e (the client's Diffie-Hellman public key)
 
702
 
 
703
            We send the KEXDH_REPLY with our host key and signature.
 
704
 
 
705
        KEX_DH_GEX_REQUEST_OLD (for diffie-hellman-group-exchange-sha1)
 
706
        payload::
 
707
 
 
708
                integer ideal (ideal size for the Diffie-Hellman prime)
 
709
 
 
710
            We send the KEX_DH_GEX_GROUP message with the group that is
 
711
            closest in size to ideal.
 
712
 
 
713
        If we were told to ignore the next key exchange packet by
 
714
        ssh_KEXINIT, drop it on the floor and return.
 
715
        """
 
716
        if self.ignoreNextPacket:
 
717
            self.ignoreNextPacket = 0
 
718
            return
 
719
        if self.kexAlg == 'diffie-hellman-group1-sha1':
 
720
            # this is really KEXDH_INIT
 
721
            clientDHpublicKey, foo = getMP(packet)
 
722
            y = Util.number.getRandomNumber(512, randbytes.secureRandom)
 
723
            serverDHpublicKey = _MPpow(DH_GENERATOR, y, DH_PRIME)
 
724
            sharedSecret = _MPpow(clientDHpublicKey, y, DH_PRIME)
 
725
            h = sha1()
 
726
            h.update(NS(self.otherVersionString))
 
727
            h.update(NS(self.ourVersionString))
 
728
            h.update(NS(self.otherKexInitPayload))
 
729
            h.update(NS(self.ourKexInitPayload))
 
730
            h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
 
731
            h.update(MP(clientDHpublicKey))
 
732
            h.update(serverDHpublicKey)
 
733
            h.update(sharedSecret)
 
734
            exchangeHash = h.digest()
 
735
            self.sendPacket(
 
736
                MSG_KEXDH_REPLY,
 
737
                NS(self.factory.publicKeys[self.keyAlg].blob()) +
 
738
                serverDHpublicKey +
 
739
                NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
 
740
            self._keySetup(sharedSecret, exchangeHash)
 
741
        elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
 
742
            self.dhGexRequest = packet
 
743
            ideal = struct.unpack('>L', packet)[0]
 
744
            self.g, self.p = self.factory.getDHPrime(ideal)
 
745
            self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
 
746
        else:
 
747
            raise error.ConchError('bad kexalg: %s' % self.kexAlg)
 
748
 
 
749
 
 
750
    def ssh_KEX_DH_GEX_REQUEST(self, packet):
 
751
        """
 
752
        Called when we receive a MSG_KEX_DH_GEX_REQUEST message.  Payload::
 
753
            integer minimum
 
754
            integer ideal
 
755
            integer maximum
 
756
 
 
757
        The client is asking for a Diffie-Hellman group between minimum and
 
758
        maximum size, and close to ideal if possible.  We reply with a
 
759
        MSG_KEX_DH_GEX_GROUP message.
 
760
 
 
761
        If we were told to ignore the next key exchange packekt by
 
762
        ssh_KEXINIT, drop it on the floor and return.
 
763
        """
 
764
        if self.ignoreNextPacket:
 
765
            self.ignoreNextPacket = 0
 
766
            return
 
767
        self.dhGexRequest = packet
 
768
        min, ideal, max = struct.unpack('>3L', packet)
 
769
        self.g, self.p = self.factory.getDHPrime(ideal)
 
770
        self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
 
771
 
 
772
 
 
773
    def ssh_KEX_DH_GEX_INIT(self, packet):
 
774
        """
 
775
        Called when we get a MSG_KEX_DH_GEX_INIT message.  Payload::
 
776
            integer e (client DH public key)
 
777
 
 
778
        We send the MSG_KEX_DH_GEX_REPLY message with our host key and
 
779
        signature.
 
780
        """
 
781
        clientDHpublicKey, foo = getMP(packet)
 
782
        # TODO: we should also look at the value they send to us and reject
 
783
        # insecure values of f (if g==2 and f has a single '1' bit while the
 
784
        # rest are '0's, then they must have used a small y also).
 
785
 
 
786
        # TODO: This could be computed when self.p is set up
 
787
        #  or do as openssh does and scan f for a single '1' bit instead
 
788
 
 
789
        pSize = Util.number.size(self.p)
 
790
        y = Util.number.getRandomNumber(pSize, randbytes.secureRandom)
 
791
 
 
792
        serverDHpublicKey = _MPpow(self.g, y, self.p)
 
793
        sharedSecret = _MPpow(clientDHpublicKey, y, self.p)
 
794
        h = sha1()
 
795
        h.update(NS(self.otherVersionString))
 
796
        h.update(NS(self.ourVersionString))
 
797
        h.update(NS(self.otherKexInitPayload))
 
798
        h.update(NS(self.ourKexInitPayload))
 
799
        h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
 
800
        h.update(self.dhGexRequest)
 
801
        h.update(MP(self.p))
 
802
        h.update(MP(self.g))
 
803
        h.update(MP(clientDHpublicKey))
 
804
        h.update(serverDHpublicKey)
 
805
        h.update(sharedSecret)
 
806
        exchangeHash = h.digest()
 
807
        self.sendPacket(
 
808
            MSG_KEX_DH_GEX_REPLY,
 
809
            NS(self.factory.publicKeys[self.keyAlg].blob()) +
 
810
            serverDHpublicKey +
 
811
            NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
 
812
        self._keySetup(sharedSecret, exchangeHash)
 
813
 
 
814
 
 
815
    def ssh_NEWKEYS(self, packet):
 
816
        """
 
817
        Called when we get a MSG_NEWKEYS message.  No payload.
 
818
        When we get this, the keys have been set on both sides, and we
 
819
        start using them to encrypt and authenticate the connection.
 
820
        """
 
821
        log.msg('NEW KEYS')
 
822
        if packet != '':
 
823
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
 
824
                                "NEWKEYS takes no data")
 
825
            return
 
826
        self.currentEncryptions = self.nextEncryptions
 
827
        if self.outgoingCompressionType == 'zlib':
 
828
            self.outgoingCompression = zlib.compressobj(6)
 
829
        if self.incomingCompressionType == 'zlib':
 
830
            self.incomingCompression = zlib.decompressobj()
 
831
 
 
832
 
 
833
    def ssh_SERVICE_REQUEST(self, packet):
 
834
        """
 
835
        Called when we get a MSG_SERVICE_REQUEST message.  Payload::
 
836
            string serviceName
 
837
 
 
838
        The client has requested a service.  If we can start the service,
 
839
        start it; otherwise, disconnect with
 
840
        DISCONNECT_SERVICE_NOT_AVAILABLE.
 
841
        """
 
842
        service, rest = getNS(packet)
 
843
        cls = self.factory.getService(self, service)
 
844
        if not cls:
 
845
            self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE,
 
846
                                "don't have service %s" % service)
 
847
            return
 
848
        else:
 
849
            self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
 
850
            self.setService(cls())
 
851
 
 
852
 
 
853
 
 
854
class SSHClientTransport(SSHTransportBase):
 
855
    """
 
856
    SSHClientTransport implements the client side of the SSH protocol.
 
857
 
 
858
    @ivar isClient: since we are always the client, this is always True.
 
859
 
 
860
    @ivar _gotNewKeys: if we receive a MSG_NEWKEYS message before we are
 
861
        ready to transition to the new keys, this is set to True so we
 
862
        can transition when the keys are ready locally.
 
863
 
 
864
    @ivar x: our Diffie-Hellman private key.
 
865
 
 
866
    @ivar e: our Diffie-Hellman public key.
 
867
 
 
868
    @ivar g: the Diffie-Hellman group generator.
 
869
 
 
870
    @ivar p: the Diffie-Hellman group prime
 
871
 
 
872
    @ivar instance: the SSHService object we are requesting.
 
873
    """
 
874
    isClient = True
 
875
 
 
876
 
 
877
    def connectionMade(self):
 
878
        """
 
879
        Called when the connection is started with the server.  Just sets
 
880
        up a private instance variable.
 
881
        """
 
882
        SSHTransportBase.connectionMade(self)
 
883
        self._gotNewKeys = 0
 
884
 
 
885
 
 
886
    def ssh_KEXINIT(self, packet):
 
887
        """
 
888
        Called when we receive a MSG_KEXINIT message.  For a description
 
889
        of the packet, see SSHTransportBase.ssh_KEXINIT().  Additionally,
 
890
        this method sends the first key exchange packet.  If the agreed-upon
 
891
        exchange is diffie-hellman-group1-sha1, generate a public key
 
892
        and send it in a MSG_KEXDH_INIT message.  If the exchange is
 
893
        diffie-hellman-group-exchange-sha1, ask for a 2048 bit group with a
 
894
        MSG_KEX_DH_GEX_REQUEST_OLD message.
 
895
        """
 
896
        if SSHTransportBase.ssh_KEXINIT(self, packet) is None:
 
897
            return # we disconnected
 
898
        if self.kexAlg == 'diffie-hellman-group1-sha1':
 
899
            self.x = Util.number.getRandomNumber(512, randbytes.secureRandom)
 
900
            self.e = _MPpow(DH_GENERATOR, self.x, DH_PRIME)
 
901
            self.sendPacket(MSG_KEXDH_INIT, self.e)
 
902
        elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
 
903
            self.sendPacket(MSG_KEX_DH_GEX_REQUEST_OLD, '\x00\x00\x08\x00')
 
904
        else:
 
905
            raise error.ConchError("somehow, the kexAlg has been set "
 
906
                                   "to something we don't support")
 
907
 
 
908
 
 
909
    def ssh_KEX_DH_GEX_GROUP(self, packet):
 
910
        """
 
911
        This handles two different message which share an integer value.
 
912
        If the key exchange is diffie-hellman-group1-sha1, this is
 
913
        MSG_KEXDH_REPLY.  Payload::
 
914
            string serverHostKey
 
915
            integer f (server Diffie-Hellman public key)
 
916
            string signature
 
917
 
 
918
        We verify the host key by calling verifyHostKey, then continue in
 
919
        _continueKEXDH_REPLY.
 
920
 
 
921
        If the key exchange is diffie-hellman-group-exchange-sha1, this is
 
922
        MSG_KEX_DH_GEX_GROUP.  Payload::
 
923
            string g (group generator)
 
924
            string p (group prime)
 
925
 
 
926
        We generate a Diffie-Hellman public key and send it in a
 
927
        MSG_KEX_DH_GEX_INIT message.
 
928
        """
 
929
        if self.kexAlg == 'diffie-hellman-group1-sha1':
 
930
            # actually MSG_KEXDH_REPLY
 
931
            pubKey, packet = getNS(packet)
 
932
            f, packet = getMP(packet)
 
933
            signature, packet = getNS(packet)
 
934
            fingerprint = ':'.join([ch.encode('hex') for ch in
 
935
                                    md5(pubKey).digest()])
 
936
            d = self.verifyHostKey(pubKey, fingerprint)
 
937
            d.addCallback(self._continueKEXDH_REPLY, pubKey, f, signature)
 
938
            d.addErrback(
 
939
                lambda unused: self.sendDisconnect(
 
940
                    DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
 
941
            return d
 
942
        else:
 
943
            self.p, rest = getMP(packet)
 
944
            self.g, rest = getMP(rest)
 
945
            self.x = Util.number.getRandomNumber(320, randbytes.secureRandom)
 
946
            self.e = _MPpow(self.g, self.x, self.p)
 
947
            self.sendPacket(MSG_KEX_DH_GEX_INIT, self.e)
 
948
 
 
949
 
 
950
    def _continueKEXDH_REPLY(self, ignored, pubKey, f, signature):
 
951
        """
 
952
        The host key has been verified, so we generate the keys.
 
953
 
 
954
        @param pubKey: the public key blob for the server's public key.
 
955
        @type pubKey: C{str}
 
956
        @param f: the server's Diffie-Hellman public key.
 
957
        @type f: C{long}
 
958
        @param signature: the server's signature, verifying that it has the
 
959
            correct private key.
 
960
        @type signature: C{str}
 
961
        """
 
962
        serverKey = keys.Key.fromString(pubKey)
 
963
        sharedSecret = _MPpow(f, self.x, DH_PRIME)
 
964
        h = sha1()
 
965
        h.update(NS(self.ourVersionString))
 
966
        h.update(NS(self.otherVersionString))
 
967
        h.update(NS(self.ourKexInitPayload))
 
968
        h.update(NS(self.otherKexInitPayload))
 
969
        h.update(NS(pubKey))
 
970
        h.update(self.e)
 
971
        h.update(MP(f))
 
972
        h.update(sharedSecret)
 
973
        exchangeHash = h.digest()
 
974
        if not serverKey.verify(signature, exchangeHash):
 
975
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
 
976
                                'bad signature')
 
977
            return
 
978
        self._keySetup(sharedSecret, exchangeHash)
 
979
 
 
980
 
 
981
    def ssh_KEX_DH_GEX_REPLY(self, packet):
 
982
        """
 
983
        Called when we receieve a MSG_KEX_DH_GEX_REPLY message.  Payload::
 
984
            string server host key
 
985
            integer f (server DH public key)
 
986
 
 
987
        We verify the host key by calling verifyHostKey, then continue in
 
988
        _continueGEX_REPLY.
 
989
        """
 
990
        pubKey, packet = getNS(packet)
 
991
        f, packet = getMP(packet)
 
992
        signature, packet = getNS(packet)
 
993
        fingerprint = ':'.join(map(lambda c: '%02x'%ord(c),
 
994
            md5(pubKey).digest()))
 
995
        d = self.verifyHostKey(pubKey, fingerprint)
 
996
        d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
 
997
        d.addErrback(
 
998
            lambda unused: self.sendDisconnect(
 
999
                DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
 
1000
        return d
 
1001
 
 
1002
 
 
1003
    def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
 
1004
        """
 
1005
        The host key has been verified, so we generate the keys.
 
1006
 
 
1007
        @param pubKey: the public key blob for the server's public key.
 
1008
        @type pubKey: C{str}
 
1009
        @param f: the server's Diffie-Hellman public key.
 
1010
        @type f: C{long}
 
1011
        @param signature: the server's signature, verifying that it has the
 
1012
            correct private key.
 
1013
        @type signature: C{str}
 
1014
        """
 
1015
        serverKey = keys.Key.fromString(pubKey)
 
1016
        sharedSecret = _MPpow(f, self.x, self.p)
 
1017
        h = sha1()
 
1018
        h.update(NS(self.ourVersionString))
 
1019
        h.update(NS(self.otherVersionString))
 
1020
        h.update(NS(self.ourKexInitPayload))
 
1021
        h.update(NS(self.otherKexInitPayload))
 
1022
        h.update(NS(pubKey))
 
1023
        h.update('\x00\x00\x08\x00')
 
1024
        h.update(MP(self.p))
 
1025
        h.update(MP(self.g))
 
1026
        h.update(self.e)
 
1027
        h.update(MP(f))
 
1028
        h.update(sharedSecret)
 
1029
        exchangeHash = h.digest()
 
1030
        if not serverKey.verify(signature, exchangeHash):
 
1031
            self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
 
1032
                                'bad signature')
 
1033
            return
 
1034
        self._keySetup(sharedSecret, exchangeHash)
 
1035
 
 
1036
 
 
1037
    def _keySetup(self, sharedSecret, exchangeHash):
 
1038
        """
 
1039
        See SSHTransportBase._keySetup().
 
1040
        """
 
1041
        SSHTransportBase._keySetup(self, sharedSecret, exchangeHash)
 
1042
        if self._gotNewKeys:
 
1043
            self.ssh_NEWKEYS('')
 
1044
 
 
1045
 
 
1046
    def ssh_NEWKEYS(self, packet):
 
1047
        """
 
1048
        Called when we receieve a MSG_NEWKEYS message.  No payload.
 
1049
        If we've finished setting up our own keys, start using them.
 
1050
        Otherwise, remeber that we've receieved this message.
 
1051
        """
 
1052
        if packet != '':
 
1053
            self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
 
1054
                                "NEWKEYS takes no data")
 
1055
            return
 
1056
        if not self.nextEncryptions.encBlockSize:
 
1057
            self._gotNewKeys = 1
 
1058
            return
 
1059
        log.msg('NEW KEYS')
 
1060
        self.currentEncryptions = self.nextEncryptions
 
1061
        if self.outgoingCompressionType == 'zlib':
 
1062
            self.outgoingCompression = zlib.compressobj(6)
 
1063
        if self.incomingCompressionType == 'zlib':
 
1064
            self.incomingCompression = zlib.decompressobj()
 
1065
        self.connectionSecure()
 
1066
 
 
1067
 
 
1068
    def ssh_SERVICE_ACCEPT(self, packet):
 
1069
        """
 
1070
        Called when we receieve a MSG_SERVICE_ACCEPT message.  Payload::
 
1071
            string service name
 
1072
 
 
1073
        Start the service we requested.
 
1074
        """
 
1075
        name = getNS(packet)[0]
 
1076
        if name != self.instance.name:
 
1077
            self.sendDisconnect(
 
1078
                DISCONNECT_PROTOCOL_ERROR,
 
1079
                "received accept for service we did not request")
 
1080
        self.setService(self.instance)
 
1081
 
 
1082
 
 
1083
    def requestService(self, instance):
 
1084
        """
 
1085
        Request that a service be run over this transport.
 
1086
 
 
1087
        @type instance: subclass of L{twisted.conch.ssh.service.SSHService}
 
1088
        """
 
1089
        self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
 
1090
        self.instance = instance
 
1091
 
 
1092
 
 
1093
    # client methods
 
1094
    def verifyHostKey(self, hostKey, fingerprint):
 
1095
        """
 
1096
        Returns a Deferred that gets a callback if it is a valid key, or
 
1097
        an errback if not.
 
1098
 
 
1099
        @type hostKey:      C{str}
 
1100
        @type fingerprint:  C{str}
 
1101
        @rtype:             L{twisted.internet.defer.Deferred}
 
1102
        """
 
1103
        # return if it's good
 
1104
        return defer.fail(NotImplementedError())
 
1105
 
 
1106
 
 
1107
    def connectionSecure(self):
 
1108
        """
 
1109
        Called when the encryption has been set up.  Generally,
 
1110
        requestService() is called to run another service over the transport.
 
1111
        """
 
1112
        raise NotImplementedError()
 
1113
 
 
1114
 
 
1115
 
 
1116
class _DummyCipher:
 
1117
    """
 
1118
    A cipher for the none encryption method.
 
1119
 
 
1120
    @ivar block_size: the block size of the encryption.  In the case of the
 
1121
    none cipher, this is 8 bytes.
 
1122
    """
 
1123
    block_size = 8
 
1124
 
 
1125
 
 
1126
    def encrypt(self, x):
 
1127
        return x
 
1128
 
 
1129
 
 
1130
    decrypt = encrypt
 
1131
 
 
1132
 
 
1133
class SSHCiphers:
 
1134
    """
 
1135
    SSHCiphers represents all the encryption operations that need to occur
 
1136
    to encrypt and authenticate the SSH connection.
 
1137
 
 
1138
    @cvar cipherMap: A dictionary mapping SSH encryption names to 3-tuples of
 
1139
                     (<Crypto.Cipher.* name>, <block size>, <counter mode>)
 
1140
    @cvar macMap: A dictionary mapping SSH MAC names to hash modules.
 
1141
 
 
1142
    @ivar outCipType: the string type of the outgoing cipher.
 
1143
    @ivar inCipType: the string type of the incoming cipher.
 
1144
    @ivar outMACType: the string type of the incoming MAC.
 
1145
    @ivar inMACType: the string type of the incoming MAC.
 
1146
    @ivar encBlockSize: the block size of the outgoing cipher.
 
1147
    @ivar decBlockSize: the block size of the incoming cipher.
 
1148
    @ivar verifyDigestSize: the size of the incoming MAC.
 
1149
    @ivar outMAC: a tuple of (<hash module>, <inner key>, <outer key>,
 
1150
        <digest size>) representing the outgoing MAC.
 
1151
    @ivar inMAc: see outMAC, but for the incoming MAC.
 
1152
    """
 
1153
 
 
1154
 
 
1155
    cipherMap = {
 
1156
        '3des-cbc':('DES3', 24, 0),
 
1157
        'blowfish-cbc':('Blowfish', 16,0 ),
 
1158
        'aes256-cbc':('AES', 32, 0),
 
1159
        'aes192-cbc':('AES', 24, 0),
 
1160
        'aes128-cbc':('AES', 16, 0),
 
1161
        'cast128-cbc':('CAST', 16, 0),
 
1162
        'aes128-ctr':('AES', 16, 1),
 
1163
        'aes192-ctr':('AES', 24, 1),
 
1164
        'aes256-ctr':('AES', 32, 1),
 
1165
        '3des-ctr':('DES3', 24, 1),
 
1166
        'blowfish-ctr':('Blowfish', 16, 1),
 
1167
        'cast128-ctr':('CAST', 16, 1),
 
1168
        'none':(None, 0, 0),
 
1169
    }
 
1170
    macMap = {
 
1171
        'hmac-sha1': sha1,
 
1172
        'hmac-md5': md5,
 
1173
        'none': None
 
1174
     }
 
1175
 
 
1176
 
 
1177
    def __init__(self, outCip, inCip, outMac, inMac):
 
1178
        self.outCipType = outCip
 
1179
        self.inCipType = inCip
 
1180
        self.outMACType = outMac
 
1181
        self.inMACType = inMac
 
1182
        self.encBlockSize = 0
 
1183
        self.decBlockSize = 0
 
1184
        self.verifyDigestSize = 0
 
1185
        self.outMAC = (None, '', '', 0)
 
1186
        self.inMAC = (None, '', '', 0)
 
1187
 
 
1188
 
 
1189
    def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
 
1190
        """
 
1191
        Set up the ciphers and hashes using the given keys,
 
1192
 
 
1193
        @param outIV: the outgoing initialization vector
 
1194
        @param outKey: the outgoing encryption key
 
1195
        @param inIV: the incoming initialization vector
 
1196
        @param inKey: the incoming encryption key
 
1197
        @param outInteg: the outgoing integrity key
 
1198
        @param inInteg: the incoming integrity key.
 
1199
        """
 
1200
        o = self._getCipher(self.outCipType, outIV, outKey)
 
1201
        self.encrypt = o.encrypt
 
1202
        self.encBlockSize = o.block_size
 
1203
        o = self._getCipher(self.inCipType, inIV, inKey)
 
1204
        self.decrypt = o.decrypt
 
1205
        self.decBlockSize = o.block_size
 
1206
        self.outMAC = self._getMAC(self.outMACType, outInteg)
 
1207
        self.inMAC = self._getMAC(self.inMACType, inInteg)
 
1208
        if self.inMAC:
 
1209
            self.verifyDigestSize = self.inMAC[3]
 
1210
 
 
1211
 
 
1212
    def _getCipher(self, cip, iv, key):
 
1213
        """
 
1214
        Creates an initialized cipher object.
 
1215
 
 
1216
        @param cip: the name of the cipher: maps into Crypto.Cipher.*
 
1217
        @param iv: the initialzation vector
 
1218
        @param key: the encryption key
 
1219
        """
 
1220
        modName, keySize, counterMode = self.cipherMap[cip]
 
1221
        if not modName: # no cipher
 
1222
            return _DummyCipher()
 
1223
        mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x')
 
1224
        if counterMode:
 
1225
            return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size],
 
1226
                           counter=_Counter(iv, mod.block_size))
 
1227
        else:
 
1228
            return mod.new(key[:keySize], mod.MODE_CBC, iv[:mod.block_size])
 
1229
 
 
1230
 
 
1231
    def _getMAC(self, mac, key):
 
1232
        """
 
1233
        Gets a 4-tuple representing the message authentication code.
 
1234
        (<hash module>, <inner hash value>, <outer hash value>,
 
1235
        <digest size>)
 
1236
 
 
1237
        @param mac: a key mapping into macMap
 
1238
        @type mac: C{str}
 
1239
        @param key: the MAC key.
 
1240
        @type key: C{str}
 
1241
        """
 
1242
        mod = self.macMap[mac]
 
1243
        if not mod:
 
1244
            return (None, '', '', 0)
 
1245
        ds = mod().digest_size
 
1246
        key = key[:ds] + '\x00' * (64 - ds)
 
1247
        i = XOR.new('\x36').encrypt(key)
 
1248
        o = XOR.new('\x5c').encrypt(key)
 
1249
        return mod, i, o, ds
 
1250
 
 
1251
 
 
1252
    def encrypt(self, blocks):
 
1253
        """
 
1254
        Encrypt blocks.  Overridden by the encrypt method of a
 
1255
        Crypto.Cipher.* object in setKeys().
 
1256
 
 
1257
        @type blocks: C{str}
 
1258
        """
 
1259
        raise NotImplementedError()
 
1260
 
 
1261
 
 
1262
    def decrypt(self, blocks):
 
1263
        """
 
1264
        Decrypt blocks.  See encrypt().
 
1265
 
 
1266
        @type blocks: C{str}
 
1267
        """
 
1268
        raise NotImplementedError()
 
1269
 
 
1270
 
 
1271
    def makeMAC(self, seqid, data):
 
1272
        """
 
1273
        Create a message authentication code (MAC) for the given packet using
 
1274
        the outgoing MAC values.
 
1275
 
 
1276
        @param seqid: the sequence ID of the outgoing packet
 
1277
        @type seqid: C{int}
 
1278
        @param data: the data to create a MAC for
 
1279
        @type data: C{str}
 
1280
        @rtype: C{str}
 
1281
        """
 
1282
        if not self.outMAC[0]:
 
1283
            return ''
 
1284
        data = struct.pack('>L', seqid) + data
 
1285
        mod, i, o, ds = self.outMAC
 
1286
        inner = mod(i + data)
 
1287
        outer = mod(o + inner.digest())
 
1288
        return outer.digest()
 
1289
 
 
1290
 
 
1291
    def verify(self, seqid, data, mac):
 
1292
        """
 
1293
        Verify an incoming MAC using the incoming MAC values.  Return True
 
1294
        if the MAC is valid.
 
1295
 
 
1296
        @param seqid: the sequence ID of the incoming packet
 
1297
        @type seqid: C{int}
 
1298
        @param data: the packet data to verify
 
1299
        @type data: C{str}
 
1300
        @param mac: the MAC sent with the packet
 
1301
        @type mac: C{str}
 
1302
        @rtype: C{bool}
 
1303
        """
 
1304
        if not self.inMAC[0]:
 
1305
            return mac == ''
 
1306
        data = struct.pack('>L', seqid) + data
 
1307
        mod, i, o, ds = self.inMAC
 
1308
        inner = mod(i + data)
 
1309
        outer = mod(o + inner.digest())
 
1310
        return mac == outer.digest()
 
1311
 
 
1312
 
 
1313
 
 
1314
class _Counter:
 
1315
    """
 
1316
    Stateful counter which returns results packed in a byte string
 
1317
    """
 
1318
 
 
1319
 
 
1320
    def __init__(self, initialVector, blockSize):
 
1321
        """
 
1322
        @type initialVector: C{str}
 
1323
        @param initialVector: A byte string representing the initial counter
 
1324
                              value.
 
1325
        @type blockSize: C{int}
 
1326
        @param blockSize: The length of the output buffer, as well as the
 
1327
        number of bytes at the beginning of C{initialVector} to consider.
 
1328
        """
 
1329
        initialVector = initialVector[:blockSize]
 
1330
        self.count = getMP('\xff\xff\xff\xff' + initialVector)[0]
 
1331
        self.blockSize = blockSize
 
1332
        self.count = Util.number.long_to_bytes(self.count - 1)
 
1333
        self.count = '\x00' * (self.blockSize - len(self.count)) + self.count
 
1334
        self.count = array.array('c', self.count)
 
1335
        self.len = len(self.count) - 1
 
1336
 
 
1337
 
 
1338
    def __call__(self):
 
1339
        """
 
1340
        Increment the counter and return the new value.
 
1341
        """
 
1342
        i = self.len
 
1343
        while i > -1:
 
1344
            self.count[i] = n = chr((ord(self.count[i]) + 1) % 256)
 
1345
            if n == '\x00':
 
1346
                i -= 1
 
1347
            else:
 
1348
                return self.count.tostring()
 
1349
 
 
1350
        self.count = array.array('c', '\x00' * self.blockSize)
 
1351
        return self.count.tostring()
 
1352
 
 
1353
 
 
1354
 
 
1355
# Diffie-Hellman primes from Oakley Group 2 [RFC 2409]
 
1356
DH_PRIME = long('17976931348623159077083915679378745319786029604875601170644'
 
1357
'442368419718021615851936894783379586492554150218056548598050364644054819923'
 
1358
'910005079287700335581663922955313623907650873575991482257486257500742530207'
 
1359
'744771258955095793777842444242661733472762929938766870920560605027081084290'
 
1360
'7692932019128194467627007L')
 
1361
DH_GENERATOR = 2L
 
1362
 
 
1363
 
 
1364
 
 
1365
MSG_DISCONNECT = 1
 
1366
MSG_IGNORE = 2
 
1367
MSG_UNIMPLEMENTED = 3
 
1368
MSG_DEBUG = 4
 
1369
MSG_SERVICE_REQUEST = 5
 
1370
MSG_SERVICE_ACCEPT = 6
 
1371
MSG_KEXINIT = 20
 
1372
MSG_NEWKEYS = 21
 
1373
MSG_KEXDH_INIT = 30
 
1374
MSG_KEXDH_REPLY = 31
 
1375
MSG_KEX_DH_GEX_REQUEST_OLD = 30
 
1376
MSG_KEX_DH_GEX_REQUEST = 34
 
1377
MSG_KEX_DH_GEX_GROUP = 31
 
1378
MSG_KEX_DH_GEX_INIT = 32
 
1379
MSG_KEX_DH_GEX_REPLY = 33
 
1380
 
 
1381
 
 
1382
 
 
1383
DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
 
1384
DISCONNECT_PROTOCOL_ERROR = 2
 
1385
DISCONNECT_KEY_EXCHANGE_FAILED = 3
 
1386
DISCONNECT_RESERVED = 4
 
1387
DISCONNECT_MAC_ERROR = 5
 
1388
DISCONNECT_COMPRESSION_ERROR = 6
 
1389
DISCONNECT_SERVICE_NOT_AVAILABLE = 7
 
1390
DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
 
1391
DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
 
1392
DISCONNECT_CONNECTION_LOST = 10
 
1393
DISCONNECT_BY_APPLICATION = 11
 
1394
DISCONNECT_TOO_MANY_CONNECTIONS = 12
 
1395
DISCONNECT_AUTH_CANCELLED_BY_USER = 13
 
1396
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
 
1397
DISCONNECT_ILLEGAL_USER_NAME = 15
 
1398
 
 
1399
 
 
1400
 
 
1401
messages = {}
 
1402
for name, value in globals().items():
 
1403
    if name.startswith('MSG_'):
 
1404
        messages[value] = name