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