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

391 statements  

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. 

18 

19""" 

20Packet handling 

21""" 

22 

23import errno 

24import os 

25import socket 

26import struct 

27import threading 

28import time 

29from hmac import HMAC 

30 

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 

44 

45 

46def compute_hmac(key, message, digest_class): 

47 return HMAC(key, message, digest_class).digest() 

48 

49 

50class NeedRekeyException(Exception): 

51 """ 

52 Exception indicating a rekey is needed. 

53 """ 

54 

55 pass 

56 

57 

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 

63 

64 

65class Packetizer: 

66 """ 

67 Implementation of the base SSH packet protocol. 

68 """ 

69 

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) 

74 

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) 

80 

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 

90 

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 

98 

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 

117 

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 

123 

124 # lock around outbound writes (packet computation) 

125 self.__write_lock = threading.RLock() 

126 

127 # keepalives: 

128 self.__keepalive_interval = 0 

129 self.__keepalive_last = time.time() 

130 self.__keepalive_callback = None 

131 

132 self.__timer = None 

133 self.__handshake_complete = False 

134 self.__timer_expired = False 

135 

136 @property 

137 def closed(self): 

138 return self.__closed 

139 

140 def reset_seqno_out(self): 

141 self.__sequence_number_out = 0 

142 

143 def reset_seqno_in(self): 

144 self.__sequence_number_in = 0 

145 

146 def set_log(self, log): 

147 """ 

148 Set the Python log object to use for logging. 

149 """ 

150 self.__logger = log 

151 

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 

185 

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 

219 

220 def set_outbound_compressor(self, compressor): 

221 self.__compress_engine_out = compressor 

222 

223 def set_inbound_compressor(self, compressor): 

224 self.__compress_engine_in = compressor 

225 

226 def close(self): 

227 self.__closed = True 

228 self.__socket.close() 

229 

230 def set_hexdump(self, hexdump): 

231 self.__dump_packets = hexdump 

232 

233 def get_hexdump(self): 

234 return self.__dump_packets 

235 

236 def get_mac_size_in(self): 

237 return self.__mac_size_in 

238 

239 def get_mac_size_out(self): 

240 return self.__mac_size_out 

241 

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 

249 

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() 

259 

260 def read_timer(self): 

261 self.__timer_expired = True 

262 

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. 

268 

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() 

274 

275 def handshake_timed_out(self): 

276 """ 

277 Checks if the handshake has timed out. 

278 

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` 

282 

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 

290 

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 

299 

300 def read_all(self, n, check_rekey=False): 

301 """ 

302 Read as close to N bytes as possible, blocking as long as necessary. 

303 

304 :param int n: number of bytes to read 

305 :return: the data read, as a `str` 

306 

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 

347 

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 

387 

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) 

402 

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 

413 

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) 

469 

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() 

487 

488 def read_message(self): 

489 """ 

490 Only one thread should ever be in this function (no other locking is 

491 done). 

492 

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 

512 

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) 

523 

524 self.__iv_in = self._inc_iv_counter(self.__iv_in) 

525 

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: ")) 

530 

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] 

539 

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) :] 

550 

551 if self.__block_engine_in is not None: 

552 packet = self.__block_engine_in.update(packet) 

553 packet = leftover + packet 

554 

555 if self.__dump_packets: 

556 self._log(DEBUG, util.format_binary(packet, "IN: ")) 

557 

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] 

571 

572 if self.__dump_packets: 

573 self._log( 

574 DEBUG, 

575 "Got payload ({} bytes, {} padding)".format( 

576 packet_size, padding 

577 ), 

578 ) 

579 

580 if self.__compress_engine_in is not None: 

581 payload = self.__compress_engine_in(payload) 

582 

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 

591 

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() 

622 

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 

634 

635 # ...protected... 

636 

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) 

645 

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 

658 

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 

675 

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 

693 

694 def _trigger_rekey(self): 

695 # outside code should check for this flag 

696 self.__need_rekey = True