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 |