root/trunk/twisted/conch/ssh/transport.py

Revision 33132, 56.7 KB (checked in by z3p, 6 months ago)

Merge conch-legacy-dh-messages-5352: Do not register overlapping old DH key exchange messages

Author: antoine
Reviewer: z3p
Fixes: #5352

twisted.conch.ssh.transport.messages is created by iterating over globals().
Since globals is a dictionary, the ordering is undefined, so we can end up with
old message IDs in messages. This patch checks for those old IDs and doesn't
insert them into the dictionary. While not ideal, until exarkun writes some
code for iterating over named constants, we can live with it.

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