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