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