Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/packet.py: 41%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
2#
3# This file is part of paramiko.
4#
5# Paramiko is free software; you can redistribute it and/or modify it under the
6# terms of the GNU Lesser General Public License as published by the Free
7# Software Foundation; either version 2.1 of the License, or (at your option)
8# any later version.
9#
10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13# details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19"""
20Packet handling
21"""
23import errno
24import os
25import socket
26import struct
27import threading
28import time
29from hmac import HMAC
31from paramiko import util
32from paramiko.common import (
33 linefeed_byte,
34 cr_byte_value,
35 MSG_NAMES,
36 DEBUG,
37 xffffffff,
38 zero_byte,
39 byte_ord,
40)
41from paramiko.util import u
42from paramiko.ssh_exception import SSHException, ProxyCommandFailure
43from paramiko.message import Message
46def compute_hmac(key, message, digest_class):
47 return HMAC(key, message, digest_class).digest()
50class NeedRekeyException(Exception):
51 """
52 Exception indicating a rekey is needed.
53 """
55 pass
58def first_arg(e):
59 arg = None
60 if type(e.args) is tuple and len(e.args) > 0:
61 arg = e.args[0]
62 return arg
65class Packetizer:
66 """
67 Implementation of the base SSH packet protocol.
68 """
70 # READ the secsh RFC's before raising these values. if anything,
71 # they should probably be lower.
72 REKEY_PACKETS = pow(2, 29)
73 REKEY_BYTES = pow(2, 29)
75 # Allow receiving this many packets after a re-key request before
76 # terminating
77 REKEY_PACKETS_OVERFLOW_MAX = pow(2, 29)
78 # Allow receiving this many bytes after a re-key request before terminating
79 REKEY_BYTES_OVERFLOW_MAX = pow(2, 29)
81 def __init__(self, socket):
82 self.__socket = socket
83 self.__logger = None
84 self.__closed = False
85 self.__dump_packets = False
86 self.__need_rekey = False
87 self.__init_count = 0
88 self.__remainder = bytes()
89 self._initial_kex_done = False
91 # used for noticing when to re-key:
92 self.__sent_bytes = 0
93 self.__sent_packets = 0
94 self.__received_bytes = 0
95 self.__received_packets = 0
96 self.__received_bytes_overflow = 0
97 self.__received_packets_overflow = 0
99 # current inbound/outbound ciphering:
100 self.__block_size_out = 8
101 self.__block_size_in = 8
102 self.__mac_size_out = 0
103 self.__mac_size_in = 0
104 self.__block_engine_out = None
105 self.__block_engine_in = None
106 self.__sdctr_out = False
107 self.__mac_engine_out = None
108 self.__mac_engine_in = None
109 self.__mac_key_out = bytes()
110 self.__mac_key_in = bytes()
111 self.__compress_engine_out = None
112 self.__compress_engine_in = None
113 self.__sequence_number_out = 0
114 self.__sequence_number_in = 0
115 self.__etm_out = False
116 self.__etm_in = False
118 # AEAD (eg aes128-gcm/aes256-gcm) cipher use
119 self.__aead_out = False
120 self.__aead_in = False
121 self.__iv_out = None
122 self.__iv_in = None
124 # lock around outbound writes (packet computation)
125 self.__write_lock = threading.RLock()
127 # keepalives:
128 self.__keepalive_interval = 0
129 self.__keepalive_last = time.time()
130 self.__keepalive_callback = None
132 self.__timer = None
133 self.__handshake_complete = False
134 self.__timer_expired = False
136 @property
137 def closed(self):
138 return self.__closed
140 def reset_seqno_out(self):
141 self.__sequence_number_out = 0
143 def reset_seqno_in(self):
144 self.__sequence_number_in = 0
146 def set_log(self, log):
147 """
148 Set the Python log object to use for logging.
149 """
150 self.__logger = log
152 def set_outbound_cipher(
153 self,
154 block_engine,
155 block_size,
156 mac_engine,
157 mac_size,
158 mac_key,
159 sdctr=False,
160 etm=False,
161 aead=False,
162 iv_out=None,
163 ):
164 """
165 Switch outbound data cipher.
166 :param etm: Set encrypt-then-mac from OpenSSH
167 """
168 self.__block_engine_out = block_engine
169 self.__sdctr_out = sdctr
170 self.__block_size_out = block_size
171 self.__mac_engine_out = mac_engine
172 self.__mac_size_out = mac_size
173 self.__mac_key_out = mac_key
174 self.__sent_bytes = 0
175 self.__sent_packets = 0
176 self.__etm_out = etm
177 self.__aead_out = aead
178 self.__iv_out = iv_out
179 # wait until the reset happens in both directions before clearing
180 # rekey flag
181 self.__init_count |= 1
182 if self.__init_count == 3:
183 self.__init_count = 0
184 self.__need_rekey = False
186 def set_inbound_cipher(
187 self,
188 block_engine,
189 block_size,
190 mac_engine,
191 mac_size,
192 mac_key,
193 etm=False,
194 aead=False,
195 iv_in=None,
196 ):
197 """
198 Switch inbound data cipher.
199 :param etm: Set encrypt-then-mac from OpenSSH
200 """
201 self.__block_engine_in = block_engine
202 self.__block_size_in = block_size
203 self.__mac_engine_in = mac_engine
204 self.__mac_size_in = mac_size
205 self.__mac_key_in = mac_key
206 self.__received_bytes = 0
207 self.__received_packets = 0
208 self.__received_bytes_overflow = 0
209 self.__received_packets_overflow = 0
210 self.__etm_in = etm
211 self.__aead_in = aead
212 self.__iv_in = iv_in
213 # wait until the reset happens in both directions before clearing
214 # rekey flag
215 self.__init_count |= 2
216 if self.__init_count == 3:
217 self.__init_count = 0
218 self.__need_rekey = False
220 def set_outbound_compressor(self, compressor):
221 self.__compress_engine_out = compressor
223 def set_inbound_compressor(self, compressor):
224 self.__compress_engine_in = compressor
226 def close(self):
227 self.__closed = True
228 self.__socket.close()
230 def set_hexdump(self, hexdump):
231 self.__dump_packets = hexdump
233 def get_hexdump(self):
234 return self.__dump_packets
236 def get_mac_size_in(self):
237 return self.__mac_size_in
239 def get_mac_size_out(self):
240 return self.__mac_size_out
242 def need_rekey(self):
243 """
244 Returns ``True`` if a new set of keys needs to be negotiated. This
245 will be triggered during a packet read or write, so it should be
246 checked after every read or write, or at least after every few.
247 """
248 return self.__need_rekey
250 def set_keepalive(self, interval, callback):
251 """
252 Turn on/off the callback keepalive. If ``interval`` seconds pass with
253 no data read from or written to the socket, the callback will be
254 executed and the timer will be reset.
255 """
256 self.__keepalive_interval = interval
257 self.__keepalive_callback = callback
258 self.__keepalive_last = time.time()
260 def read_timer(self):
261 self.__timer_expired = True
263 def start_handshake(self, timeout):
264 """
265 Tells `Packetizer` that the handshake process started.
266 Starts a book keeping timer that can signal a timeout in the
267 handshake process.
269 :param float timeout: amount of seconds to wait before timing out
270 """
271 if not self.__timer:
272 self.__timer = threading.Timer(float(timeout), self.read_timer)
273 self.__timer.start()
275 def handshake_timed_out(self):
276 """
277 Checks if the handshake has timed out.
279 If `start_handshake` wasn't called before the call to this function,
280 the return value will always be `False`. If the handshake completed
281 before a timeout was reached, the return value will be `False`
283 :return: handshake time out status, as a `bool`
284 """
285 if not self.__timer:
286 return False
287 if self.__handshake_complete:
288 return False
289 return self.__timer_expired
291 def complete_handshake(self):
292 """
293 Tells `Packetizer` that the handshake has completed.
294 """
295 if self.__timer:
296 self.__timer.cancel()
297 self.__timer_expired = False
298 self.__handshake_complete = True
300 def read_all(self, n, check_rekey=False):
301 """
302 Read as close to N bytes as possible, blocking as long as necessary.
304 :param int n: number of bytes to read
305 :return: the data read, as a `str`
307 :raises:
308 ``EOFError`` -- if the socket was closed before all the bytes could
309 be read
310 """
311 out = bytes()
312 # handle over-reading from reading the banner line
313 if len(self.__remainder) > 0:
314 out = self.__remainder[:n]
315 self.__remainder = self.__remainder[n:]
316 n -= len(out)
317 while n > 0:
318 got_timeout = False
319 if self.handshake_timed_out():
320 raise EOFError()
321 try:
322 x = self.__socket.recv(n)
323 if len(x) == 0:
324 raise EOFError()
325 out += x
326 n -= len(x)
327 except socket.timeout:
328 got_timeout = True
329 except socket.error as e:
330 # on Linux, sometimes instead of socket.timeout, we get
331 # EAGAIN. this is a bug in recent (> 2.6.9) kernels but
332 # we need to work around it.
333 arg = first_arg(e)
334 if arg == errno.EAGAIN:
335 got_timeout = True
336 elif self.__closed:
337 raise EOFError()
338 else:
339 raise
340 if got_timeout:
341 if self.__closed:
342 raise EOFError()
343 if check_rekey and (len(out) == 0) and self.__need_rekey:
344 raise NeedRekeyException()
345 self._check_keepalive()
346 return out
348 def write_all(self, out):
349 self.__keepalive_last = time.time()
350 iteration_with_zero_as_return_value = 0
351 while len(out) > 0:
352 retry_write = False
353 try:
354 n = self.__socket.send(out)
355 except socket.timeout:
356 retry_write = True
357 except socket.error as e:
358 arg = first_arg(e)
359 if arg == errno.EAGAIN:
360 retry_write = True
361 else:
362 n = -1
363 except ProxyCommandFailure:
364 raise # so it doesn't get swallowed by the below catchall
365 except Exception:
366 # could be: (32, 'Broken pipe')
367 n = -1
368 if retry_write:
369 n = 0
370 if self.__closed:
371 n = -1
372 else:
373 if n == 0 and iteration_with_zero_as_return_value > 10:
374 # We shouldn't retry the write, but we didn't
375 # manage to send anything over the socket. This might be an
376 # indication that we have lost contact with the remote
377 # side, but are yet to receive an EOFError or other socket
378 # errors. Let's give it some iteration to try and catch up.
379 n = -1
380 iteration_with_zero_as_return_value += 1
381 if n < 0:
382 raise EOFError()
383 if n == len(out):
384 break
385 out = out[n:]
386 return
388 def readline(self, timeout):
389 """
390 Read a line from the socket. We assume no data is pending after the
391 line, so it's okay to attempt large reads.
392 """
393 buf = self.__remainder
394 while linefeed_byte not in buf:
395 buf += self._read_timeout(timeout)
396 n = buf.index(linefeed_byte)
397 self.__remainder = buf[n + 1 :]
398 buf = buf[:n]
399 if (len(buf) > 0) and (buf[-1] == cr_byte_value):
400 buf = buf[:-1]
401 return u(buf)
403 def _inc_iv_counter(self, iv):
404 # Per https://www.rfc-editor.org/rfc/rfc5647.html#section-7.1 ,
405 # we increment the last 8 bytes of the 12-byte IV...
406 iv_counter_b = iv[4:]
407 iv_counter = int.from_bytes(iv_counter_b, "big")
408 inc_iv_counter = iv_counter + 1
409 inc_iv_counter_b = inc_iv_counter.to_bytes(8, "big")
410 # ...then re-concatenate it with the static first 4 bytes
411 new_iv = iv[0:4] + inc_iv_counter_b
412 return new_iv
414 def send_message(self, data):
415 """
416 Write a block of data using the current cipher, as an SSH block.
417 """
418 # encrypt this sucka
419 data = data.asbytes()
420 cmd = byte_ord(data[0])
421 if cmd in MSG_NAMES:
422 cmd_name = MSG_NAMES[cmd]
423 else:
424 cmd_name = "${:x}".format(cmd)
425 orig_len = len(data)
426 self.__write_lock.acquire()
427 try:
428 if self.__compress_engine_out is not None:
429 data = self.__compress_engine_out(data)
430 packet = self._build_packet(data)
431 if self.__dump_packets:
432 self._log(
433 DEBUG,
434 "Write packet <{}>, length {}".format(cmd_name, orig_len),
435 )
436 self._log(DEBUG, util.format_binary(packet, "OUT: "))
437 if self.__block_engine_out is not None:
438 if self.__etm_out:
439 # packet length is not encrypted in EtM
440 out = packet[0:4] + self.__block_engine_out.update(
441 packet[4:]
442 )
443 elif self.__aead_out:
444 # Packet-length field is used as the 'associated data'
445 # under AES-GCM, so like EtM, it's not encrypted. See
446 # https://www.rfc-editor.org/rfc/rfc5647#section-7.3
447 out = packet[0:4] + self.__block_engine_out.encrypt(
448 self.__iv_out, packet[4:], packet[0:4]
449 )
450 self.__iv_out = self._inc_iv_counter(self.__iv_out)
451 else:
452 out = self.__block_engine_out.update(packet)
453 else:
454 out = packet
455 # Append an MAC when needed (eg, not under AES-GCM)
456 if self.__block_engine_out is not None and not self.__aead_out:
457 packed = struct.pack(">I", self.__sequence_number_out)
458 payload = packed + (out if self.__etm_out else packet)
459 out += compute_hmac(
460 self.__mac_key_out, payload, self.__mac_engine_out
461 )[: self.__mac_size_out]
462 next_seq = (self.__sequence_number_out + 1) & xffffffff
463 if next_seq == 0 and not self._initial_kex_done:
464 raise SSHException(
465 "Sequence number rolled over during initial kex!"
466 )
467 self.__sequence_number_out = next_seq
468 self.write_all(out)
470 self.__sent_bytes += len(out)
471 self.__sent_packets += 1
472 sent_too_much = (
473 self.__sent_packets >= self.REKEY_PACKETS
474 or self.__sent_bytes >= self.REKEY_BYTES
475 )
476 if sent_too_much and not self.__need_rekey:
477 # only ask once for rekeying
478 msg = "Rekeying (hit {} packets, {} bytes sent)"
479 self._log(
480 DEBUG, msg.format(self.__sent_packets, self.__sent_bytes)
481 )
482 self.__received_bytes_overflow = 0
483 self.__received_packets_overflow = 0
484 self._trigger_rekey()
485 finally:
486 self.__write_lock.release()
488 def read_message(self):
489 """
490 Only one thread should ever be in this function (no other locking is
491 done).
493 :raises: `.SSHException` -- if the packet is mangled
494 :raises: `.NeedRekeyException` -- if the transport should rekey
495 """
496 header = self.read_all(self.__block_size_in, check_rekey=True)
497 if self.__etm_in:
498 packet_size = struct.unpack(">I", header[:4])[0]
499 remaining = packet_size - self.__block_size_in + 4
500 packet = header[4:] + self.read_all(remaining, check_rekey=False)
501 mac = self.read_all(self.__mac_size_in, check_rekey=False)
502 mac_payload = (
503 struct.pack(">II", self.__sequence_number_in, packet_size)
504 + packet
505 )
506 my_mac = compute_hmac(
507 self.__mac_key_in, mac_payload, self.__mac_engine_in
508 )[: self.__mac_size_in]
509 if not util.constant_time_bytes_eq(my_mac, mac):
510 raise SSHException("Mismatched MAC")
511 header = packet
513 if self.__aead_in:
514 # Grab unencrypted (considered 'additional data' under GCM) packet
515 # length.
516 packet_size = struct.unpack(">I", header[:4])[0]
517 aad = header[:4]
518 remaining = (
519 packet_size - self.__block_size_in + 4 + self.__mac_size_in
520 )
521 packet = header[4:] + self.read_all(remaining, check_rekey=False)
522 header = self.__block_engine_in.decrypt(self.__iv_in, packet, aad)
524 self.__iv_in = self._inc_iv_counter(self.__iv_in)
526 if self.__block_engine_in is not None and not self.__aead_in:
527 header = self.__block_engine_in.update(header)
528 if self.__dump_packets:
529 self._log(DEBUG, util.format_binary(header, "IN: "))
531 # When ETM or AEAD (GCM) are in use, we've already read the packet size
532 # & decrypted everything, so just set the packet back to the header we
533 # obtained.
534 if self.__etm_in or self.__aead_in:
535 packet = header
536 # Otherwise, use the older non-ETM logic
537 else:
538 packet_size = struct.unpack(">I", header[:4])[0]
540 # leftover contains decrypted bytes from the first block (after the
541 # length field)
542 leftover = header[4:]
543 if (packet_size - len(leftover)) % self.__block_size_in != 0:
544 raise SSHException("Invalid packet blocking")
545 buf = self.read_all(
546 packet_size + self.__mac_size_in - len(leftover)
547 )
548 packet = buf[: packet_size - len(leftover)]
549 post_packet = buf[packet_size - len(leftover) :]
551 if self.__block_engine_in is not None:
552 packet = self.__block_engine_in.update(packet)
553 packet = leftover + packet
555 if self.__dump_packets:
556 self._log(DEBUG, util.format_binary(packet, "IN: "))
558 if self.__mac_size_in > 0 and not self.__etm_in and not self.__aead_in:
559 mac = post_packet[: self.__mac_size_in]
560 mac_payload = (
561 struct.pack(">II", self.__sequence_number_in, packet_size)
562 + packet
563 )
564 my_mac = compute_hmac(
565 self.__mac_key_in, mac_payload, self.__mac_engine_in
566 )[: self.__mac_size_in]
567 if not util.constant_time_bytes_eq(my_mac, mac):
568 raise SSHException("Mismatched MAC")
569 padding = byte_ord(packet[0])
570 payload = packet[1 : packet_size - padding]
572 if self.__dump_packets:
573 self._log(
574 DEBUG,
575 "Got payload ({} bytes, {} padding)".format(
576 packet_size, padding
577 ),
578 )
580 if self.__compress_engine_in is not None:
581 payload = self.__compress_engine_in(payload)
583 msg = Message(payload[1:])
584 msg.seqno = self.__sequence_number_in
585 next_seq = (self.__sequence_number_in + 1) & xffffffff
586 if next_seq == 0 and not self._initial_kex_done:
587 raise SSHException(
588 "Sequence number rolled over during initial kex!"
589 )
590 self.__sequence_number_in = next_seq
592 # check for rekey
593 raw_packet_size = packet_size + self.__mac_size_in + 4
594 self.__received_bytes += raw_packet_size
595 self.__received_packets += 1
596 if self.__need_rekey:
597 # we've asked to rekey -- give them some packets to comply before
598 # dropping the connection
599 self.__received_bytes_overflow += raw_packet_size
600 self.__received_packets_overflow += 1
601 if (
602 self.__received_packets_overflow
603 >= self.REKEY_PACKETS_OVERFLOW_MAX
604 ) or (
605 self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX
606 ):
607 raise SSHException(
608 "Remote transport is ignoring rekey requests"
609 )
610 elif (self.__received_packets >= self.REKEY_PACKETS) or (
611 self.__received_bytes >= self.REKEY_BYTES
612 ):
613 # only ask once for rekeying
614 err = "Rekeying (hit {} packets, {} bytes received)"
615 self._log(
616 DEBUG,
617 err.format(self.__received_packets, self.__received_bytes),
618 )
619 self.__received_bytes_overflow = 0
620 self.__received_packets_overflow = 0
621 self._trigger_rekey()
623 cmd = byte_ord(payload[0])
624 if cmd in MSG_NAMES:
625 cmd_name = MSG_NAMES[cmd]
626 else:
627 cmd_name = "${:x}".format(cmd)
628 if self.__dump_packets:
629 self._log(
630 DEBUG,
631 "Read packet <{}>, length {}".format(cmd_name, len(payload)),
632 )
633 return cmd, msg
635 # ...protected...
637 def _log(self, level, msg):
638 if self.__logger is None:
639 return
640 if issubclass(type(msg), list):
641 for m in msg:
642 self.__logger.log(level, m)
643 else:
644 self.__logger.log(level, msg)
646 def _check_keepalive(self):
647 if (
648 not self.__keepalive_interval
649 or not self.__block_engine_out
650 or self.__need_rekey
651 ):
652 # wait till we're encrypting, and not in the middle of rekeying
653 return
654 now = time.time()
655 if now > self.__keepalive_last + self.__keepalive_interval:
656 self.__keepalive_callback()
657 self.__keepalive_last = now
659 def _read_timeout(self, timeout):
660 start = time.time()
661 while True:
662 try:
663 x = self.__socket.recv(128)
664 if len(x) == 0:
665 raise EOFError()
666 break
667 except socket.timeout:
668 pass
669 if self.__closed:
670 raise EOFError()
671 now = time.time()
672 if now - start >= timeout:
673 raise socket.timeout()
674 return x
676 def _build_packet(self, payload):
677 # pad up at least 4 bytes, to nearest block-size (usually 8)
678 bsize = self.__block_size_out
679 # do not include payload length in computations for padding in EtM mode
680 # (payload length won't be encrypted)
681 addlen = 4 if self.__etm_out or self.__aead_out else 8
682 padding = 3 + bsize - ((len(payload) + addlen) % bsize)
683 packet = struct.pack(">IB", len(payload) + padding + 1, padding)
684 packet += payload
685 if self.__sdctr_out or self.__block_engine_out is None:
686 # cute trick i caught openssh doing: if we're not encrypting or
687 # SDCTR mode (RFC4344),
688 # don't waste random bytes for the padding
689 packet += zero_byte * padding
690 else:
691 packet += os.urandom(padding)
692 return packet
694 def _trigger_rekey(self):
695 # outside code should check for this flag
696 self.__need_rekey = True