diff --git a/.gitignore b/.gitignore index 8febc1b..987cc23 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/paramiko-2.12.0.tar.gz +SOURCES/paramiko-3.5.0.tar.gz diff --git a/.python-paramiko.metadata b/.python-paramiko.metadata index 1de3a16..a83764b 100644 --- a/.python-paramiko.metadata +++ b/.python-paramiko.metadata @@ -1 +1 @@ -c8f18401ebcfc7aee9132eaa71bf33c76bf9301e SOURCES/paramiko-2.12.0.tar.gz +07b3054f764cdc1aa207cafc98a1e36eaa00f9d6 SOURCES/paramiko-3.5.0.tar.gz diff --git a/SOURCES/0003-remove-pytest-relaxed-dep.patch b/SOURCES/0003-remove-pytest-relaxed-dep.patch index d8a6931..e236dcd 100644 --- a/SOURCES/0003-remove-pytest-relaxed-dep.patch +++ b/SOURCES/0003-remove-pytest-relaxed-dep.patch @@ -1,34 +1,24 @@ --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,7 +2,6 @@ - invoke==1.6.0 - invocations==2.6.0 - pytest==4.4.2 --pytest-relaxed==1.1.5 + invoke>=2.0 + invocations>=3.2 + # Testing! +-pytest-relaxed>=2 # pytest-xdist for test dir watching and the inv guard task - pytest-xdist==1.28.0 - mock==2.0.0 ---- a/pytest.ini -+++ b/pytest.ini -@@ -1,7 +1,4 @@ - [pytest] --# We use pytest-relaxed just for its utils at the moment, so disable it at the --# plugin level until we adapt test organization to really use it. --addopts = -p no:relaxed - # Loop on failure - looponfailroots = tests paramiko - # Ignore some warnings we cannot easily handle. + pytest-xdist>=3 + # Linting! --- a/tests/test_client.py +++ b/tests/test_client.py -@@ -34,7 +34,6 @@ import weakref +@@ -33,7 +33,6 @@ import weakref from tempfile import mkstemp import pytest -from pytest_relaxed import raises - from mock import patch, Mock + from unittest.mock import patch, Mock import paramiko -@@ -787,11 +786,11 @@ class PasswordPassphraseTests(ClientTest +@@ -799,11 +798,11 @@ class PasswordPassphraseTests(ClientTest # TODO: more granular exception pending #387; should be signaling "no auth # methods available" because no key and no password @@ -43,14 +33,14 @@ @requires_sha1_signing def test_passphrase_kwarg_used_for_key_passphrase(self): -@@ -811,15 +810,15 @@ class PasswordPassphraseTests(ClientTest +@@ -823,15 +822,15 @@ class PasswordPassphraseTests(ClientTest password="television", ) - @raises(AuthenticationException) # TODO: more granular @requires_sha1_signing def test_password_kwarg_not_used_for_passphrase_when_passphrase_kwarg_given( # noqa - self + self, ): # Sanity: if we're given both fields, the password field is NOT used as # a passphrase. diff --git a/SOURCES/0004-remove-icecream-dep.patch b/SOURCES/0004-remove-icecream-dep.patch new file mode 100644 index 0000000..b1385f3 --- /dev/null +++ b/SOURCES/0004-remove-icecream-dep.patch @@ -0,0 +1,26 @@ +--- a/dev-requirements.txt ++++ b/dev-requirements.txt +@@ -17,7 +17,5 @@ coverage>=6.2,<7 + alabaster==0.7.13 + releases>=2.1 + watchdog<2 +-# Debuggery +-icecream>=2.1 + # Self (sans GSS which is a pain to bother with most of the time) + -e ".[invoke]" +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -22,13 +22,6 @@ from ._loop import LoopSocket + from ._stub_sftp import StubServer, StubSFTPServer + from ._util import _support + +-from icecream import ic, install as install_ic +- +- +-# Better print() for debugging - use ic()! +-install_ic() +-ic.configureOutput(includeContext=True) +- + + # Perform logging by default; pytest will capture and thus hide it normally, + # presenting it on error/failure. (But also allow turning it off when doing diff --git a/SOURCES/0004-remove-mock-dep.patch b/SOURCES/0004-remove-mock-dep.patch deleted file mode 100644 index 294fe97..0000000 --- a/SOURCES/0004-remove-mock-dep.patch +++ /dev/null @@ -1,117 +0,0 @@ -Prefer and use built-in unittest.mock in Python 3.3+ instead -of unnecessarily requiring the external mock package. This helps -distributions that are phasing out Python 2 to remove redundant -packages. - ---- a/dev-requirements.txt -+++ b/dev-requirements.txt -@@ -4,7 +4,7 @@ invocations==2.6.0 - pytest==4.4.2 - # pytest-xdist for test dir watching and the inv guard task - pytest-xdist==1.28.0 --mock==2.0.0 -+mock==2.0.0;python_version<"3.3" - # Linting! - flake8==3.8.3 - # Formatting! ---- a/tests/test_channelfile.py -+++ b/tests/test_channelfile.py -@@ -1,4 +1,7 @@ --from mock import patch, MagicMock -+try: -+ from unittest.mock import patch, MagicMock -+except ImportError: -+ from mock import patch, MagicMock - - from paramiko import Channel, ChannelFile, ChannelStderrFile, ChannelStdinFile - ---- a/tests/test_client.py -+++ b/tests/test_client.py -@@ -34,7 +34,10 @@ import weakref - from tempfile import mkstemp - - import pytest --from mock import patch, Mock -+try: -+ from unittest.mock import patch, Mock -+except ImportError: -+ from mock import patch, Mock - - import paramiko - from paramiko import SSHClient ---- a/tests/test_config.py -+++ b/tests/test_config.py -@@ -11,7 +11,11 @@ try: - except ImportError: - Result = None - --from mock import patch -+try: -+ from unittest.mock import patch -+except ImportError: -+ from mock import patch -+ - from pytest import raises, mark, fixture - - from paramiko import ( ---- a/tests/test_kex.py -+++ b/tests/test_kex.py -@@ -24,7 +24,11 @@ from binascii import hexlify, unhexlify - import os - import unittest - --from mock import Mock, patch -+try: -+ from unittest.mock import Mock, patch -+except ImportError: -+ from mock import Mock, patch -+ - import pytest - - from cryptography.hazmat.backends import default_backend ---- a/tests/test_pkey.py -+++ b/tests/test_pkey.py -@@ -41,7 +41,12 @@ from paramiko.common import o600 - - from cryptography.exceptions import UnsupportedAlgorithm - from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateNumbers --from mock import patch, Mock -+ -+try: -+ from unittest.mock import patch, Mock -+except ImportError: -+ from mock import patch, Mock -+ - import pytest - - from .util import _support, is_low_entropy, requires_sha1_signing ---- a/tests/test_proxy.py -+++ b/tests/test_proxy.py -@@ -1,7 +1,11 @@ - import signal - import socket - --from mock import patch -+try: -+ from unittest.mock import patch -+except ImportError: -+ from mock import patch -+ - from pytest import raises - - from paramiko import ProxyCommand, ProxyCommandFailure ---- a/tests/test_transport.py -+++ b/tests/test_transport.py -@@ -30,7 +30,11 @@ import time - import threading - import random - import unittest --from mock import Mock -+ -+try: -+ from unittest.mock import Mock -+except ImportError: -+ from mock import Mock - - from paramiko import ( - AuthHandler, diff --git a/SOURCES/0005-classes__str__.patch b/SOURCES/0005-classes__str__.patch deleted file mode 100644 index af943e8..0000000 --- a/SOURCES/0005-classes__str__.patch +++ /dev/null @@ -1,62 +0,0 @@ ---- a/paramiko/agent.py -+++ b/paramiko/agent.py -@@ -415,9 +415,6 @@ class AgentKey(PKey): - def asbytes(self): - return self.blob - -- def __str__(self): -- return self.asbytes() -- - def get_name(self): - return self.name - ---- a/paramiko/message.py -+++ b/paramiko/message.py -@@ -53,10 +53,7 @@ class Message(object): - else: - self.packet = BytesIO() - -- def __str__(self): -- """ -- Return the byte stream content of this message, as a string/bytes obj. -- """ -+ def __bytes__(self): - return self.asbytes() - - def __repr__(self): ---- a/paramiko/pkey.py -+++ b/paramiko/pkey.py -@@ -118,7 +119,7 @@ class PKey(object): - """ - return bytes() - -- def __str__(self): -+ def __bytes__(self): - return self.asbytes() - - # noinspection PyUnresolvedReferences ---- a/tests/test_agent.py -+++ b/tests/test_agent.py -@@ -48,3 +48,10 @@ class AgentTests(unittest.TestCase): - kwargs=dict(algorithm="rsa-sha2-512"), - expectation=SSH_AGENT_RSA_SHA2_512, - ) -+ -+ def test_agent_key_str_kinda_fixed(self): -+ # Tests for a missed spot in Python 3 upgrades: AgentKey.__str__ was -+ # returning bytes, as if under Python 2. When bug present, this -+ # explodes with "__str__ returned non-string". -+ key = AgentKey(ChaosAgent(), b("secret!!!")) -+ assert str(key) == repr(key) ---- a/tests/test_message.py -+++ b/tests/test_message.py -@@ -105,3 +105,9 @@ class MessageTest(unittest.TestCase): - self.assertEqual(msg.get_adaptive_int(), 5) - self.assertEqual(msg.get_so_far(), self.__d[:4]) - self.assertEqual(msg.get_remainder(), self.__d[4:]) -+ -+ def test_bytes_str_and_repr(self): -+ msg = Message(self.__d) -+ assert str(msg) == f"paramiko.Message({self.__d!r})" -+ assert repr(msg) == str(msg) -+ assert bytes(msg) == msg.asbytes() == self.__d diff --git a/SOURCES/0005-remove-invoke-dep.patch b/SOURCES/0005-remove-invoke-dep.patch new file mode 100644 index 0000000..4893f27 --- /dev/null +++ b/SOURCES/0005-remove-invoke-dep.patch @@ -0,0 +1,11 @@ +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -4,7 +4,7 @@ import shutil + import threading + from pathlib import Path + +-from invoke.vendor.lexicon import Lexicon ++from lexicon import Lexicon + + import pytest + from paramiko import ( diff --git a/SOURCES/0006-terrapin.patch b/SOURCES/0006-terrapin.patch deleted file mode 100644 index 42eb3c0..0000000 --- a/SOURCES/0006-terrapin.patch +++ /dev/null @@ -1,717 +0,0 @@ ---- a/paramiko/__init__.py -+++ b/paramiko/__init__.py -@@ -19,12 +19,15 @@ - # flake8: noqa - import sys - from paramiko._version import __version__, __version_info__ --from paramiko.transport import SecurityOptions, Transport -+from paramiko.transport import ( -+ SecurityOptions, -+ Transport, -+) - from paramiko.client import ( -- SSHClient, -- MissingHostKeyPolicy, - AutoAddPolicy, -+ MissingHostKeyPolicy, - RejectPolicy, -+ SSHClient, - WarningPolicy, - ) - from paramiko.auth_handler import AuthHandler -@@ -43,6 +46,7 @@ from paramiko.ssh_exception import ( - ConfigParseError, - CouldNotCanonicalize, - IncompatiblePeer, -+ MessageOrderError, - PasswordRequiredException, - ProxyCommandFailure, - SSHException, ---- a/paramiko/packet.py -+++ b/paramiko/packet.py -@@ -86,6 +86,7 @@ class Packetizer(object): - self.__need_rekey = False - self.__init_count = 0 - self.__remainder = bytes() -+ self._initial_kex_done = False - - # used for noticing when to re-key: - self.__sent_bytes = 0 -@@ -130,6 +131,12 @@ class Packetizer(object): - def closed(self): - return self.__closed - -+ def reset_seqno_out(self): -+ self.__sequence_number_out = 0 -+ -+ def reset_seqno_in(self): -+ self.__sequence_number_in = 0 -+ - def set_log(self, log): - """ - Set the Python log object to use for logging. -@@ -425,9 +432,12 @@ class Packetizer(object): - out += compute_hmac( - self.__mac_key_out, payload, self.__mac_engine_out - )[: self.__mac_size_out] -- self.__sequence_number_out = ( -- self.__sequence_number_out + 1 -- ) & xffffffff -+ next_seq = (self.__sequence_number_out + 1) & xffffffff -+ if next_seq == 0 and not self._initial_kex_done: -+ raise SSHException( -+ "Sequence number rolled over during initial kex!" -+ ) -+ self.__sequence_number_out = next_seq - self.write_all(out) - - self.__sent_bytes += len(out) -@@ -531,7 +541,12 @@ class Packetizer(object): - - msg = Message(payload[1:]) - msg.seqno = self.__sequence_number_in -- self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff -+ next_seq = (self.__sequence_number_in + 1) & xffffffff -+ if next_seq == 0 and not self._initial_kex_done: -+ raise SSHException( -+ "Sequence number rolled over during initial kex!" -+ ) -+ self.__sequence_number_in = next_seq - - # check for rekey - raw_packet_size = packet_size + self.__mac_size_in + 4 ---- a/paramiko/ssh_exception.py -+++ b/paramiko/ssh_exception.py -@@ -235,3 +235,13 @@ class ConfigParseError(SSHException): - """ - - pass -+ -+ -+class MessageOrderError(SSHException): -+ """ -+ Out-of-order protocol messages were received, violating "strict kex" mode. -+ -+ .. versionadded:: 3.4 -+ """ -+ -+ pass ---- a/paramiko/transport.py -+++ b/paramiko/transport.py -@@ -106,11 +106,12 @@ from paramiko.ecdsakey import ECDSAKey - from paramiko.server import ServerInterface - from paramiko.sftp_client import SFTPClient - from paramiko.ssh_exception import ( -- SSHException, - BadAuthenticationType, - ChannelException, - IncompatiblePeer, -+ MessageOrderError, - ProxyCommandFailure, -+ SSHException, - ) - from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value - -@@ -329,6 +330,8 @@ class Transport(threading.Thread, Closin - gss_deleg_creds=True, - disabled_algorithms=None, - server_sig_algs=True, -+ strict_kex=True, -+ packetizer_class=None, - ): - """ - Create a new SSH session over an existing socket, or socket-like -@@ -395,6 +398,13 @@ class Transport(threading.Thread, Closin - Whether to send an extra message to compatible clients, in server - mode, with a list of supported pubkey algorithms. Default: - ``True``. -+ :param bool strict_kex: -+ Whether to advertise (and implement, if client also advertises -+ support for) a "strict kex" mode for safer handshaking. Default: -+ ``True``. -+ :param packetizer_class: -+ Which class to use for instantiating the internal packet handler. -+ Default: ``None`` (i.e.: use `Packetizer` as normal). - - .. versionchanged:: 1.15 - Added the ``default_window_size`` and ``default_max_packet_size`` -@@ -405,10 +415,16 @@ class Transport(threading.Thread, Closin - Added the ``disabled_algorithms`` kwarg. - .. versionchanged:: 2.9 - Added the ``server_sig_algs`` kwarg. -+ .. versionchanged:: 3.4 -+ Added the ``strict_kex`` kwarg. -+ .. versionchanged:: 3.4 -+ Added the ``packetizer_class`` kwarg. - """ - self.active = False - self.hostname = None - self.server_extensions = {} -+ self.advertise_strict_kex = strict_kex -+ self.agreed_on_strict_kex = False - - if isinstance(sock, string_types): - # convert "host:port" into (host, port) -@@ -450,7 +466,7 @@ class Transport(threading.Thread, Closin - self.sock.settimeout(self._active_check_timeout) - - # negotiated crypto parameters -- self.packetizer = Packetizer(sock) -+ self.packetizer = (packetizer_class or Packetizer)(sock) - self.local_version = "SSH-" + self._PROTO_ID + "-" + self._CLIENT_ID - self.remote_version = "" - self.local_cipher = self.remote_cipher = "" -@@ -524,6 +540,20 @@ class Transport(threading.Thread, Closin - self.server_accept_cv = threading.Condition(self.lock) - self.subsystem_table = {} - -+ # Handler table, now set at init time for easier per-instance -+ # manipulation and subclass twiddling. -+ self._handler_table = { -+ MSG_EXT_INFO: self._parse_ext_info, -+ MSG_NEWKEYS: self._parse_newkeys, -+ MSG_GLOBAL_REQUEST: self._parse_global_request, -+ MSG_REQUEST_SUCCESS: self._parse_request_success, -+ MSG_REQUEST_FAILURE: self._parse_request_failure, -+ MSG_CHANNEL_OPEN_SUCCESS: self._parse_channel_open_success, -+ MSG_CHANNEL_OPEN_FAILURE: self._parse_channel_open_failure, -+ MSG_CHANNEL_OPEN: self._parse_channel_open, -+ MSG_KEXINIT: self._negotiate_keys, -+ } -+ - def _filter_algorithm(self, type_): - default = getattr(self, "_preferred_{}".format(type_)) - return tuple( -@@ -2067,6 +2097,20 @@ class Transport(threading.Thread, Closin - # be empty.) - return reply - -+ def _enforce_strict_kex(self, ptype): -+ """ -+ Conditionally raise `MessageOrderError` during strict initial kex. -+ -+ This method should only be called inside code that handles non-KEXINIT -+ messages; it does not interrogate ``ptype`` besides using it to log -+ more accurately. -+ """ -+ if self.agreed_on_strict_kex and not self.initial_kex_done: -+ name = MSG_NAMES.get(ptype, f"msg {ptype}") -+ raise MessageOrderError( -+ f"In strict-kex mode, but was sent {name!r}!" -+ ) -+ - def run(self): - # (use the exposed "run" method, because if we specify a thread target - # of a private method, threading.Thread will keep a reference to it -@@ -2111,16 +2155,21 @@ class Transport(threading.Thread, Closin - except NeedRekeyException: - continue - if ptype == MSG_IGNORE: -+ self._enforce_strict_kex(ptype) - continue - elif ptype == MSG_DISCONNECT: - self._parse_disconnect(m) - break - elif ptype == MSG_DEBUG: -+ self._enforce_strict_kex(ptype) - self._parse_debug(m) - continue - if len(self._expected_packet) > 0: - if ptype not in self._expected_packet: -- raise SSHException( -+ exc_class = SSHException -+ if self.agreed_on_strict_kex: -+ exc_class = MessageOrderError -+ raise exc_class( - "Expecting packet from {!r}, got {:d}".format( - self._expected_packet, ptype - ) -@@ -2135,7 +2184,7 @@ class Transport(threading.Thread, Closin - if error_msg: - self._send_message(error_msg) - else: -- self._handler_table[ptype](self, m) -+ self._handler_table[ptype](m) - elif ptype in self._channel_handler_table: - chanid = m.get_int() - chan = self._channels.get(chanid) -@@ -2342,12 +2391,18 @@ class Transport(threading.Thread, Closin - ) - else: - available_server_keys = self.preferred_keys -- # Signal support for MSG_EXT_INFO. -+ # Signal support for MSG_EXT_INFO so server will send it to us. - # NOTE: doing this here handily means we don't even consider this - # value when agreeing on real kex algo to use (which is a common - # pitfall when adding this apparently). - kex_algos.append("ext-info-c") - -+ # Similar to ext-info, but used in both server modes, so done outside -+ # of above if/else. -+ if self.advertise_strict_kex: -+ which = "s" if self.server_mode else "c" -+ kex_algos.append(f"kex-strict-{which}-v00@openssh.com") -+ - m = Message() - m.add_byte(cMSG_KEXINIT) - m.add_bytes(os.urandom(16)) -@@ -2388,7 +2443,8 @@ class Transport(threading.Thread, Closin - - def _get_latest_kex_init(self): - return self._really_parse_kex_init( -- Message(self._latest_kex_init), ignore_first_byte=True -+ Message(self._latest_kex_init), -+ ignore_first_byte=True, - ) - - def _parse_kex_init(self, m): -@@ -2427,10 +2483,39 @@ class Transport(threading.Thread, Closin - self._log(DEBUG, "kex follows: {}".format(kex_follows)) - self._log(DEBUG, "=== Key exchange agreements ===") - -- # Strip out ext-info "kex algo" -+ # Record, and strip out, ext-info and/or strict-kex non-algorithms - self._remote_ext_info = None -- if kex_algo_list[-1].startswith("ext-info-"): -- self._remote_ext_info = kex_algo_list.pop() -+ self._remote_strict_kex = None -+ to_pop = [] -+ for i, algo in enumerate(kex_algo_list): -+ if algo.startswith("ext-info-"): -+ self._remote_ext_info = algo -+ to_pop.insert(0, i) -+ elif algo.startswith("kex-strict-"): -+ # NOTE: this is what we are expecting from the /remote/ end. -+ which = "c" if self.server_mode else "s" -+ expected = f"kex-strict-{which}-v00@openssh.com" -+ # Set strict mode if agreed. -+ self.agreed_on_strict_kex = ( -+ algo == expected and self.advertise_strict_kex -+ ) -+ self._log( -+ DEBUG, f"Strict kex mode: {self.agreed_on_strict_kex}" -+ ) -+ to_pop.insert(0, i) -+ for i in to_pop: -+ kex_algo_list.pop(i) -+ -+ # CVE mitigation: expect zeroed-out seqno anytime we are performing kex -+ # init phase, if strict mode was negotiated. -+ if ( -+ self.agreed_on_strict_kex -+ and not self.initial_kex_done -+ and m.seqno != 0 -+ ): -+ raise MessageOrderError( -+ "In strict-kex mode, but KEXINIT was not the first packet!" -+ ) - - # as a server, we pick the first item in the client's list that we - # support. -@@ -2631,6 +2716,13 @@ class Transport(threading.Thread, Closin - ): - self._log(DEBUG, "Switching on inbound compression ...") - self.packetizer.set_inbound_compressor(compress_in()) -+ # Reset inbound sequence number if strict mode. -+ if self.agreed_on_strict_kex: -+ self._log( -+ DEBUG, -+ "Resetting inbound seqno after NEWKEYS due to strict mode", -+ ) -+ self.packetizer.reset_seqno_in() - - def _activate_outbound(self): - """switch on newly negotiated encryption parameters for -@@ -2638,6 +2730,13 @@ class Transport(threading.Thread, Closin - m = Message() - m.add_byte(cMSG_NEWKEYS) - self._send_message(m) -+ # Reset outbound sequence number if strict mode. -+ if self.agreed_on_strict_kex: -+ self._log( -+ DEBUG, -+ "Resetting outbound seqno after NEWKEYS due to strict mode", -+ ) -+ self.packetizer.reset_seqno_out() - block_size = self._cipher_info[self.local_cipher]["block-size"] - if self.server_mode: - IV_out = self._compute_key("B", block_size) -@@ -2728,7 +2827,9 @@ class Transport(threading.Thread, Closin - self.auth_handler = AuthHandler(self) - if not self.initial_kex_done: - # this was the first key exchange -- self.initial_kex_done = True -+ # (also signal to packetizer as it sometimes wants to know this -+ # status as well, eg when seqnos rollover) -+ self.initial_kex_done = self.packetizer._initial_kex_done = True - # send an event? - if self.completion_event is not None: - self.completion_event.set() -@@ -2982,18 +3083,6 @@ class Transport(threading.Thread, Closin - finally: - self.lock.release() - -- _handler_table = { -- MSG_EXT_INFO: _parse_ext_info, -- MSG_NEWKEYS: _parse_newkeys, -- MSG_GLOBAL_REQUEST: _parse_global_request, -- MSG_REQUEST_SUCCESS: _parse_request_success, -- MSG_REQUEST_FAILURE: _parse_request_failure, -- MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success, -- MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure, -- MSG_CHANNEL_OPEN: _parse_channel_open, -- MSG_KEXINIT: _negotiate_keys, -- } -- - _channel_handler_table = { - MSG_CHANNEL_SUCCESS: Channel._request_success, - MSG_CHANNEL_FAILURE: Channel._request_failed, ---- a/tests/test_transport.py -+++ b/tests/test_transport.py -@@ -23,12 +23,14 @@ Some unit tests for the ssh2 protocol in - from __future__ import with_statement - - from binascii import hexlify -+import itertools - from contextlib import contextmanager - import select - import socket - import time - import threading - import random -+import sys - import unittest - - try: -@@ -37,14 +39,15 @@ except ImportError: - from mock import Mock - - from paramiko import ( -+ AuthenticationException, - AuthHandler, - ChannelException, - DSSKey, -+ IncompatiblePeer, -+ MessageOrderError, - Packetizer, - RSAKey, - SSHException, -- AuthenticationException, -- IncompatiblePeer, - SecurityOptions, - ServerInterface, - Transport, -@@ -57,7 +60,11 @@ from paramiko.common import ( - MAX_WINDOW_SIZE, - MIN_PACKET_SIZE, - MIN_WINDOW_SIZE, -+ MSG_CHANNEL_OPEN, -+ MSG_DEBUG, -+ MSG_IGNORE, - MSG_KEXINIT, -+ MSG_UNIMPLEMENTED, - MSG_USERAUTH_SUCCESS, - cMSG_CHANNEL_WINDOW_ADJUST, - cMSG_UNIMPLEMENTED, -@@ -67,6 +74,7 @@ from paramiko.message import Message - - from .util import needs_builtin, _support, requires_sha1_signing, slow - from .loop import LoopSocket -+from pytest import mark, raises - - - LONG_BANNER = """\ -@@ -154,6 +162,10 @@ class NullServer(ServerInterface): - self._tcpip_dest = destination - return OPEN_SUCCEEDED - -+# Faux 'packet type' we do not implement and are unlikely ever to (but which is -+# technically "within spec" re RFC 4251 -+MSG_FUGGEDABOUTIT = 253 -+ - - class TransportTest(unittest.TestCase): - def setUp(self): -@@ -1119,6 +1131,16 @@ class TransportTest(unittest.TestCase): - # Real fix's behavior - self._expect_unimplemented() - -+ def test_can_override_packetizer_used(self): -+ class MyPacketizer(Packetizer): -+ pass -+ -+ # control case -+ assert Transport(sock=LoopSocket()).packetizer.__class__ is Packetizer -+ # overridden case -+ tweaked = Transport(sock=LoopSocket(), packetizer_class=MyPacketizer) -+ assert tweaked.packetizer.__class__ is MyPacketizer -+ - - class AlgorithmDisablingTests(unittest.TestCase): - def test_preferred_lists_default_to_private_attribute_contents(self): -@@ -1202,10 +1224,17 @@ def server( - connect=None, - pubkeys=None, - catch_error=False, -+ transport_factory=None, -+ server_transport_factory=None, -+ defer=False, -+ skip_verify=False, - ): - """ - SSH server contextmanager for testing. - -+ Yields a tuple of ``(tc, ts)`` (client- and server-side `Transport` -+ objects), or ``(tc, ts, err)`` when ``catch_error==True``. -+ - :param hostkey: - Host key to use for the server; if None, loads - ``test_rsa.key``. -@@ -1222,6 +1251,17 @@ def server( - :param catch_error: - Whether to capture connection errors & yield from contextmanager. - Necessary for connection_time exception testing. -+ :param transport_factory: -+ Like the same-named param in SSHClient: which Transport class to use. -+ :param server_transport_factory: -+ Like ``transport_factory``, but only impacts the server transport. -+ :param bool defer: -+ Whether to defer authentication during connecting. -+ -+ This is really just shorthand for ``connect={}`` which would do roughly -+ the same thing. Also: this implies skip_verify=True automatically! -+ :param bool skip_verify: -+ Whether NOT to do the default "make sure auth passed" check. - """ - if init is None: - init = {} -@@ -1230,12 +1270,21 @@ def server( - if client_init is None: - client_init = {} - if connect is None: -- connect = dict(username="slowdive", password="pygmalion") -+ # No auth at all please -+ if defer: -+ connect = dict() -+ # Default username based auth -+ else: -+ connect = dict(username="slowdive", password="pygmalion") - socks = LoopSocket() - sockc = LoopSocket() - sockc.link(socks) -- tc = Transport(sockc, **dict(init, **client_init)) -- ts = Transport(socks, **dict(init, **server_init)) -+ if transport_factory is None: -+ transport_factory = Transport -+ if server_transport_factory is None: -+ server_transport_factory = transport_factory -+ tc = transport_factory(sockc, **dict(init, **client_init)) -+ ts = server_transport_factory(socks, **dict(init, **server_init)) - - if hostkey is None: - hostkey = RSAKey.from_private_key_file(_support("test_rsa.key")) -@@ -1354,10 +1403,14 @@ class TestSHA2SignatureKeyExchange(unitt - - - class TestExtInfo(unittest.TestCase): -- def test_ext_info_handshake(self): -+ def test_ext_info_handshake_exposed_in_client_kexinit(self): - with server() as (tc, _): -+ # NOTE: this is latest KEXINIT /sent by us/ (Transport retains it) - kex = tc._get_latest_kex_init() -- assert kex["kex_algo_list"][-1] == "ext-info-c" -+ # flag in KexAlgorithms list -+ assert "ext-info-c" in kex["kex_algo_list"] -+ # data stored on Transport after hearing back from a compatible -+ # server (such as ourselves in server mode) - assert tc.server_extensions == { - "server-sig-algs": b"ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-dss" # noqa - } -@@ -1463,3 +1516,187 @@ class TestSHA2SignaturePubkeys(unittest. - ) as (tc, ts): - assert tc.is_authenticated() - assert tc._agreed_pubkey_algorithm == "rsa-sha2-256" -+ -+ -+class BadSeqPacketizer(Packetizer): -+ def read_message(self): -+ cmd, msg = super().read_message() -+ # Only mess w/ seqno if kexinit. -+ if cmd is MSG_KEXINIT: -+ # NOTE: this is /only/ the copy of the seqno which gets -+ # transmitted up from Packetizer; it's not modifying -+ # Packetizer's own internal seqno. For these tests, -+ # modifying the latter isn't required, and is also harder -+ # to do w/o triggering MAC mismatches. -+ msg.seqno = 17 # arbitrary nonzero int -+ return cmd, msg -+ -+ -+class TestStrictKex: -+ def test_kex_algos_includes_kex_strict_c(self): -+ with server() as (tc, _): -+ kex = tc._get_latest_kex_init() -+ assert "kex-strict-c-v00@openssh.com" in kex["kex_algo_list"] -+ -+ @mark.parametrize( -+ "server_active,client_active", -+ itertools.product([True, False], repeat=2), -+ ) -+ def test_mode_agreement(self, server_active, client_active): -+ with server( -+ server_init=dict(strict_kex=server_active), -+ client_init=dict(strict_kex=client_active), -+ ) as (tc, ts): -+ if server_active and client_active: -+ assert tc.agreed_on_strict_kex is True -+ assert ts.agreed_on_strict_kex is True -+ else: -+ assert tc.agreed_on_strict_kex is False -+ assert ts.agreed_on_strict_kex is False -+ -+ def test_mode_advertised_by_default(self): -+ # NOTE: no explicit strict_kex overrides... -+ with server() as (tc, ts): -+ assert all( -+ ( -+ tc.advertise_strict_kex, -+ tc.agreed_on_strict_kex, -+ ts.advertise_strict_kex, -+ ts.agreed_on_strict_kex, -+ ) -+ ) -+ -+ @mark.parametrize( -+ "ptype", -+ ( -+ # "normal" but definitely out-of-order message -+ MSG_CHANNEL_OPEN, -+ # Normally ignored, but not in this case -+ MSG_IGNORE, -+ # Normally triggers debug parsing, but not in this case -+ MSG_DEBUG, -+ # Normally ignored, but...you get the idea -+ MSG_UNIMPLEMENTED, -+ # Not real, so would normally trigger us /sending/ -+ # MSG_UNIMPLEMENTED, but... -+ MSG_FUGGEDABOUTIT, -+ ), -+ ) -+ def test_MessageOrderError_non_kex_messages_in_initial_kex(self, ptype): -+ class AttackTransport(Transport): -+ # Easiest apparent spot on server side which is: -+ # - late enough for both ends to have handshook on strict mode -+ # - early enough to be in the window of opportunity for Terrapin -+ # attack; essentially during actual kex, when the engine is -+ # waiting for things like MSG_KEXECDH_REPLY (for eg curve25519). -+ def _negotiate_keys(self, m): -+ self.clear_to_send_lock.acquire() -+ try: -+ self.clear_to_send.clear() -+ finally: -+ self.clear_to_send_lock.release() -+ if self.local_kex_init is None: -+ # remote side wants to renegotiate -+ self._send_kex_init() -+ self._parse_kex_init(m) -+ # Here, we would normally kick over to kex_engine, but instead -+ # we want the server to send the OOO message. -+ m = Message() -+ m.add_byte(byte_chr(ptype)) -+ # rest of packet unnecessary... -+ self._send_message(m) -+ -+ with raises(MessageOrderError): -+ with server(server_transport_factory=AttackTransport) as (tc, _): -+ pass # above should run and except during connect() -+ -+ def test_SSHException_raised_on_out_of_order_messages_when_not_strict( -+ self, -+ ): -+ # This is kind of dumb (either situation is still fatal!) but whatever, -+ # may as well be strict with our new strict flag... -+ with raises(SSHException) as info: # would be true either way, but -+ with server( -+ client_init=dict(strict_kex=False), -+ ) as (tc, _): -+ tc._expect_packet(MSG_KEXINIT) -+ tc.open_session() -+ assert info.type is SSHException # NOT MessageOrderError! -+ -+ def test_error_not_raised_when_kexinit_not_seq_0_but_unstrict(self): -+ with server( -+ client_init=dict( -+ # Disable strict kex -+ strict_kex=False, -+ # Give our clientside a packetizer that sets all kexinit -+ # Message objects to have .seqno==17, which would trigger the -+ # new logic if we'd forgotten to wrap it in strict-kex check -+ packetizer_class=BadSeqPacketizer, -+ ), -+ ): -+ pass # kexinit happens at connect... -+ -+ def test_MessageOrderError_raised_when_kexinit_not_seq_0_and_strict(self): -+ with raises(MessageOrderError): -+ with server( -+ # Give our clientside a packetizer that sets all kexinit -+ # Message objects to have .seqno==17, which should trigger the -+ # new logic (given we are NOT disabling strict-mode) -+ client_init=dict(packetizer_class=BadSeqPacketizer), -+ ): -+ pass # kexinit happens at connect... -+ -+ def test_sequence_numbers_reset_on_newkeys_when_strict(self): -+ with server(defer=True) as (tc, ts): -+ # When in strict mode, these should all be zero or close to it -+ # (post-kexinit, pre-auth). -+ # Server->client will be 1 (EXT_INFO got sent after NEWKEYS) -+ assert tc.packetizer._Packetizer__sequence_number_in == 1 -+ assert ts.packetizer._Packetizer__sequence_number_out == 1 -+ # Client->server will be 0 -+ assert tc.packetizer._Packetizer__sequence_number_out == 0 -+ assert ts.packetizer._Packetizer__sequence_number_in == 0 -+ -+ def test_sequence_numbers_not_reset_on_newkeys_when_not_strict(self): -+ with server(defer=True, client_init=dict(strict_kex=False)) as ( -+ tc, -+ ts, -+ ): -+ # When not in strict mode, these will all be ~3-4 or so -+ # (post-kexinit, pre-auth). Not encoding exact values as it will -+ # change anytime we mess with the test harness... -+ assert tc.packetizer._Packetizer__sequence_number_in != 0 -+ assert tc.packetizer._Packetizer__sequence_number_out != 0 -+ assert ts.packetizer._Packetizer__sequence_number_in != 0 -+ assert ts.packetizer._Packetizer__sequence_number_out != 0 -+ -+ def test_sequence_number_rollover_detected(self): -+ class RolloverTransport(Transport): -+ def __init__(self, *args, **kwargs): -+ super().__init__(*args, **kwargs) -+ # Induce an about-to-rollover seqno, such that it rolls over -+ # during initial kex. -+ setattr( -+ self.packetizer, -+ "_Packetizer__sequence_number_in", -+ sys.maxsize, -+ ) -+ setattr( -+ self.packetizer, -+ "_Packetizer__sequence_number_out", -+ sys.maxsize, -+ ) -+ -+ with raises( -+ SSHException, -+ match=r"Sequence number rolled over during initial kex!", -+ ): -+ with server( -+ client_init=dict( -+ # Disable strict kex - this should happen always -+ strict_kex=False, -+ ), -+ # Transport which tickles its packetizer seqno's -+ transport_factory=RolloverTransport, -+ ): -+ pass # kexinit happens at connect... diff --git a/SPECS/python-paramiko.spec b/SPECS/python-paramiko.spec index 9d4ac7d..74226b3 100644 --- a/SPECS/python-paramiko.spec +++ b/SPECS/python-paramiko.spec @@ -1,8 +1,8 @@ %global srcname paramiko Name: python-%{srcname} -Version: 2.12.0 -Release: 2%{?dist} +Version: 3.5.0 +Release: 1%{?dist} Summary: SSH2 protocol library for python # No version specified @@ -11,24 +11,15 @@ URL: https://github.com/paramiko/paramiko Source0: %{url}/archive/%{version}/%{srcname}-%{version}.tar.gz # Remove pytest-relaxed, which depends on pytest4 +# Can be removed when https://github.com/paramiko/paramiko/pull/1665/ is released Patch3: 0003-remove-pytest-relaxed-dep.patch -# Avoid use of deprecated python-mock by using unittest.mock instead -Patch4: 0004-remove-mock-dep.patch +# icecream not packaged in Fedora, nor needed for regular builds +Patch4: 0004-remove-icecream-dep.patch -# A handful of lower-level classes (notably 'paramiko.message.Message' and -# 'paramiko.pkey.PKey') previously returned 'bytes' objects from their -# implementation of '__str__', even under Python 3, and there was never any -# '__bytes__' method; these issues have been fixed by renaming '__str__' to -# '__bytes__' and relying on Python's default "stringification returns the -# output of '__repr__'" behavior re: any real attempts to 'str()' such objects -# (backported from version 3.2) -Patch5: 0005-classes__str__.patch - -# Address CVE 2023-48795 (a.k.a. the "Terrapin Attack", a vulnerability found -# in the SSH protocol re: treatment of packet sequence numbers) -# (backported from version 3.4) -Patch6: 0006-terrapin.patch +# Avoid use of lexicon via invoke since we're avoiding invoke as a dependency; +# instead, use lexicon directly +Patch5: 0005-remove-invoke-dep.patch BuildArch: noarch @@ -47,14 +38,14 @@ encrypted tunnel (this is how sftp works, for example). %package -n python%{python3_pkgversion}-%{srcname} Summary: SSH2 protocol library for python -BuildRequires: python%{python3_pkgversion}-devel -BuildRequires: %{py3_dist bcrypt} >= 3.1.3 -BuildRequires: %{py3_dist cryptography} >= 2.5 +BuildRequires: python%{python3_pkgversion}-devel >= 3.6 +BuildRequires: %{py3_dist bcrypt} >= 3.2 +BuildRequires: %{py3_dist cryptography} >= 3.3 +BuildRequires: %{py3_dist lexicon} >= 2.0.1 BuildRequires: %{py3_dist pyasn1} >= 0.1.7 -BuildRequires: %{py3_dist pynacl} >= 1.0.1 +BuildRequires: %{py3_dist pynacl} >= 1.5 BuildRequires: %{py3_dist pytest} BuildRequires: %{py3_dist setuptools} -BuildRequires: %{py3_dist six} Recommends: %{py3_dist pyasn1} >= 0.1.7 %description -n python%{python3_pkgversion}-%{srcname} @@ -86,13 +77,14 @@ sed -i -e '/^#!/,1d' demos/* sphinx-build -b html sites/docs/ html/ rm html/.buildinfo +rm -r html/.doctrees %check PYTHONPATH=%{buildroot}%{python3_sitelib} pytest-%{python3_version} %files -n python%{python3_pkgversion}-%{srcname} %license LICENSE -%doc NEWS README.rst +%doc README.rst %{python3_sitelib}/%{srcname}-*.egg-info/ %{python3_sitelib}/%{srcname}/ @@ -100,43 +92,280 @@ PYTHONPATH=%{buildroot}%{python3_sitelib} pytest-%{python3_version} %doc html/ demos/ %changelog -* Fri Jul 12 2024 Arkady L. Shane - 2.12.0-2 -- Rebuilt for MSVSphere 8.10 - -* Fri Dec 29 2023 Paul Howarth - 2.12.0-2 -- Address CVE 2023-48795 (a.k.a. the "Terrapin Attack", a vulnerability found - in the SSH protocol re: treatment of packet sequence numbers) as follows: - - The vulnerability only impacts encrypt-then-MAC digest algorithms in tandem - with CBC ciphers, and ChaCha20-poly1305; of these, Paramiko currently only - implements ``hmac-sha2-(256|512)-etm`` in tandem with 'AES-CBC' - - As the fix for the vulnerability requires both ends of the connection to - cooperate, the below changes will only take effect when the remote end is - OpenSSH ≥ 9.6 (or equivalent, such as Paramiko in server mode, as of this - patch version) and configured to use the new "strict kex" mode - - Paramiko will always attempt to use "strict kex" mode if offered by the - server, unless you override this by specifying 'strict_kex=False' in - 'Transport.__init__' - - Paramiko will now raise an 'SSHException' subclass ('MessageOrderError') - when protocol messages are received in unexpected order; this includes - situations like receiving 'MSG_DEBUG' or 'MSG_IGNORE' during initial key - exchange, which are no longer allowed during strict mode - - Key (re)negotiation, i.e. 'MSG_NEWKEYS', whenever it is encountered, now - resets packet sequence numbers (this should be invisible to users during - normal operation, only causing exceptions if the exploit is encountered, - which will usually result in, again, 'MessageOrderError') - - Sequence number rollover will now raise 'SSHException' if it occurs during - initial key exchange (regardless of strict mode status) -- Tweak 'ext-info-(c|s)' detection during KEXINIT protocol phase; the original - implementation made assumptions based on an OpenSSH implementation detail -- 'Transport' grew a new 'packetizer_class' kwarg for overriding the - packet-handler class used internally; this is mostly for testing, but advanced - users may find this useful when doing deep hacks -- A handful of lower-level classes (notably 'paramiko.message.Message' and - 'paramiko.pkey.PKey') previously returned 'bytes' objects from their - implementation of '__str__', even under Python 3, and there was never any - '__bytes__' method; these issues have been fixed by renaming '__str__' to - '__bytes__' and relying on Python's default "stringification returns the - output of '__repr__'" behavior re: any real attempts to 'str()' such objects +* Fri Jan 10 2025 Arkady L. Shane - 3.5.0-1 +- Rebuilt for MSVSphere 10 + +* Mon Sep 16 2024 Paul Howarth - 3.5.0-1 +- Update to 3.5.0 (rhbz#2312503) + - Add support for AES-GCM encryption ciphers (128 and 256 bit variants) + (GH#982, GH#2157, GH#2444, rhbz#2311855); this functionality has been + tested in client mode against OpenSSH 9.0, 9.2 and 9.6, as well as against + a number of proprietary appliance SSH servers + - Check for 'None' transport members inside '~paramiko.channel.Channel' when + closing the channel; this likely doesn't come up much in the real world, + but was causing warnings in the test suite + +* Mon Aug 12 2024 Paul Howarth - 3.4.1-1 +- Update to 3.4.1 + - Massage our import of the TripleDES cipher to support Cryptography ≥ 43; + this should prevent 'CryptographyDeprecationWarning' from appearing upon + import (GH#2419, GH#2421) + - Modify a test-harness skiptest check to work with newer versions of + Cryptography (GH#2420) + - Fix a 64-bit-ism in the test suite so the tests don't encounter a false + negative on 32-bit systems (GH#2353) + +* Mon Jul 22 2024 Paul Howarth - 3.4.0-6 +- Fix detection of SHA1 signing support + https://github.com/paramiko/paramiko/pull/2420 + https://github.com/pyca/cryptography/issues/11332 + https://github.com/PyO3/pyo3/issues/3059 +- Remove cache Sphinx build folder ".doctrees" + +* Fri Jul 19 2024 Fedora Release Engineering - 3.4.0-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild + +* Sat Jun 08 2024 Python Maint - 3.4.0-4 +- Rebuilt for Python 3.13 + +* Fri Jan 26 2024 Fedora Release Engineering - 3.4.0-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Mon Jan 22 2024 Fedora Release Engineering - 3.4.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Tue Dec 19 2023 Gwyn Ciesla - 3.4.0-1 +- 3.4.0 + - 'Transport' grew a new 'packetizer_class' kwarg for overriding the + packet-handler class used internally (mostly for testing, but advanced + users may find this useful when doing deep hacks) + - Address CVE 2023-48795 (https://terrapin-attack.com/) a.k.a. the "Terrapin + Attack", a vulnerability found in the SSH protocol re: treatment of packet + sequence numbers) as follows: + - The vulnerability only impacts encrypt-then-MAC digest algorithms in + tandem with CBC ciphers, and ChaCha20-poly1305; of these, Paramiko + currently only implements 'hmac-sha2-(256|512)-etm' in tandem with + 'AES-CBC'; if you are unable to upgrade to Paramiko versions containing + the below fixes right away, you may instead use the 'disabled_algorithms' + connection option to disable the ETM MACs and/or the CBC ciphers (this + option is present in Paramiko ≥ 2.6) + - As the fix for the vulnerability requires both ends of the connection to + cooperate, the below changes will only take effect when the remote end is + OpenSSH ≥ 9.6 (or equivalent, such as Paramiko in server mode, as of this + patch version) and configured to use the new "strict kex" mode (Paramiko + will always attempt to use "strict kex" mode if offered by the server, + unless you override this by specifying 'strict_kex=False' in + 'Transport.__init__') + - Paramiko will now raise an 'SSHException' subclass ('MessageOrderError') + when protocol messages are received in unexpected order; this includes + situations like receiving 'MSG_DEBUG' or 'MSG_IGNORE' during initial key + exchange, which are no longer allowed during strict mode + - Key (re)negotiation -- i.e. 'MSG_NEWKEYS', whenever it is encountered -- + now resets packet sequence numbers (this should be invisible to users + during normal operation, only causing exceptions if the exploit is + encountered, which will usually result in, again, 'MessageOrderError') + - Sequence number rollover will now raise 'SSHException' if it occurs + during initial key exchange (regardless of strict mode status) + - Tweak 'ext-info-(c|s)' detection during KEXINIT protocol phase; the + original implementation made assumptions based on an OpenSSH implementation + detail + +* Sun Jul 30 2023 Paul Howarth - 3.3.1-1 +- Update to 3.3.1 (rhbz#2227478) + - Cleaned up some very old root level files, mostly just to exercise some of + our doc build and release machinery + +* Fri Jul 28 2023 Gwyn Ciesla - 3.3.0-1 +- 3.3.0 + - Add support and tests for 'Match final ..' (frequently used in ProxyJump + configurations to exclude the jump host) to our SSH config parser (GH#1907, + GH#1992) + - Add an explicit 'max_concurrent_prefetch_requests' argument to + 'paramiko.client.SSHClient.get' and 'paramiko.client.SSHClient.getfo', + allowing users to limit the number of concurrent requests used during + prefetch (GH#1587, GH#2058) + +* Fri Jul 21 2023 Fedora Release Engineering - 3.2.0-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Thu Jun 15 2023 Python Maint - 3.2.0-2 +- Rebuilt for Python 3.12 + +* Sat May 27 2023 Paul Howarth - 3.2.0-1 +- Update to 3.2.0 (rhbz#2210398) + - Fixed a very sneaky bug found at the apparently rarely-traveled + intersection of RSA-SHA2 keys, certificates, SSH agents, and + stricter-than-OpenSSH server targets, which manifested as yet another + "well, if we turn off SHA2 at one end or another, everything works again" + problem, for example with version 12 of the Teleport server endpoint + - The 'server-sig-algs' and 'RSA-SHA2' features added around Paramiko 2.9 or + so, had the annoying side effect of not working with servers that don't + support *either* of those feature sets, requiring use of + 'disabled_algorithms' to forcibly disable the SHA2 algorithms on Paramiko's + end (GH#1961, GH#2012 and countless others) + - The *experimental* '~paramiko.transport.ServiceRequestingTransport' (noted + in its own entry in this changelog) includes a fix for this issue, + specifically by falling back to the same algorithm as the in-use pubkey if + it's in the algorithm list (leaving the "first algorithm in said list" as + an absolute final fallback) + - Implement '_fields()' on '~paramiko.agent.AgentKey' so that it may be + compared (via '==') with other '~paramiko.pkey.PKey' instances + - Since its inception, Paramiko has (for reasons lost to time) implemented + authentication as a side effect of handling affirmative replies to + 'MSG_SERVICE_REQUEST' protocol messages; what this means is Paramiko makes + one such request before every 'MSG_USERAUTH_REQUEST', i.e. every auth + attempt (GH#23) + - OpenSSH doesn't care if clients send multiple service requests, but other + server implementations are often stricter in what they accept after an + initial service request (due to the RFCs not being clear), which can + result in odd behavior when a user doesn't authenticate successfully on + the very first try (for example, when the right key for a target host is + the third in one's ssh-agent) + - This version of Paramiko now contains an opt-in + '~paramiko.transport.Transport' subclass, + '~paramiko.transport.ServiceRequestingTransport', which more-correctly + implements service request handling in the Transport, and uses an + auth-handler subclass internally that has been similarly adapted; users + wanting to try this new experimental code path may hand this class to + 'SSHClient.connect` as its 'transport_factory' kwarg + - This feature is *EXPERIMENTAL* and its code may be subject to change + - Minor backwards incompatible changes exist in the new code paths, most + notably the removal of the (inconsistently applied and rarely used) + 'event' arguments to the 'auth_xxx' methods + - GSSAPI support has only been partially implemented, and is untested + - Some minor backwards-*compatible* changes were made to the *existing* + Transport and AuthHandler classes to facilitate the new code; for + example, 'Transport._handler_table' and + 'AuthHandler._client_handler_table' are now properties instead of raw + attributes + - Users of '~paramiko.client.SSHClient' can now configure the authentication + logic Paramiko uses when connecting to servers; this functionality is + intended for advanced users and higher-level libraries such as 'Fabric' + (https://fabfile.org/); see '~paramiko.auth_strategy' for details (GH#387) + - Fabric's co-temporal release includes a proof-of-concept use of this + feature, implementing an auth flow much closer to that of the OpenSSH + client (versus Paramiko's legacy behavior); it is *strongly recommended* + that if this interests you, investigate replacing any direct use of + 'SSHClient' with Fabric's 'Connection' + - This feature is **EXPERIMENTAL**; please see its docs for details + - Enhanced '~paramiko.agent.AgentKey' with new attributes, such as: + - Added a 'comment' attribute (and constructor argument); + 'Agent.get_keys()' now uses this kwarg to store any comment field sent + over by the agent; the original version of the agent feature inexplicably + did not store the comment anywhere + - Agent-derived keys now attempt to instantiate a copy of the appropriate + key class for access to other algorithm-specific members (e.g. key size); + this is available as the '.inner_key' attribute + - This functionality is now in use in Fabric's new '--list-agent-keys' + feature, as well as in Paramiko's debug logging + - '~paramiko.pkey.PKey' now offers convenience "meta-constructors", static + methods that simplify the process of instantiating the correct subclass for + a given key input + - For example, 'PKey.from_path' can load a file path without knowing + *a priori* what type of key it is (thanks to some handy methods within + our cryptography dependency); going forwards, we expect this to be the + primary method of loading keys by user code that runs on "human time" + (i.e. where some minor efficiencies are worth the convenience) + - In addition, 'PKey.from_type_string' now exists, and is being used in + some internals to load ssh-agent keys + - As part of these changes, '~paramiko.pkey.PKey' and friends grew a + '~paramiko.pkey.PKey.identifiers' classmethod; this is inspired by the + '~paramiko.ecdsakey.ECDSAKey.supported_key_format_identifiers' classmethod + (which now refers to the new method); this also includes adding a '.name' + attribute to most key classes (which will eventually replace + '.get_name()') + - '~paramiko.pkey.PKey' grew a new '.algorithm_name' property that displays + the key algorithm; this is typically derived from the value of + '~paramiko.pkey.PKey.get_name'; for example, ED25519 keys have a 'get_name' + of 'ssh-ed25519' (the SSH protocol key type field value), and now have a + 'algorithm_name' of 'ED25519' + - '~paramiko.pkey.PKey' grew a new '.fingerprint' property that emits a + fingerprint string matching the SHA256+Base64 values printed by various + OpenSSH tooling (e.g. 'ssh-add -l', 'ssh -v'); this is intended to help + troubleshoot Paramiko-vs-OpenSSH behavior and will eventually replace the + venerable 'get_fingerprint' method + - '~paramiko.agent.AgentKey' had a dangling Python 3 incompatible '__str__' + method returning bytes; this method has been removed, allowing the + superclass' ('~paramiko.pkey.PKey') method to run instead + +* Sun Mar 12 2023 Paul Howarth - 3.1.0-1 +- Update to 3.1.0 (rhbz#2177436) + - Add an explicit 'channel_timeout' keyword argument to + 'paramiko.client.SSHClient.connect', allowing users to configure the + previously-hardcoded default value of 3600 seconds (GH#2009, GH#2013, and + others) + - Accept single tabs as field separators (in addition to single spaces) in + 'paramiko.hostkeys.HostKeyEntry.from_line' for parity with OpenSSH's + KnownHosts parser (GH#2173) + - Apply 'codespell' to the codebase, which found a lot of very old minor + spelling mistakes in docstrings; also, modernize many instances of '*largs' + vs. '*args' and '**kwarg' vs. '**kwargs' (GH#2178) + +* Sun Jan 22 2023 Paul Howarth - 3.0.0-1 +- Update to 3.0.0 (rhbz#2162914) + - Remove some unnecessary '__repr__' calls when handling bytes-vs-str + conversions; this was apparently doing a lot of unintentional data + processing, which adds up in some use cases, such as SFTP transfers, + which may now be significantly faster (GH#2110) + - Streamline some redundant (and costly) byte conversion calls in the + packetizer and the core SFTP module; this should lead to some SFTP + speedups at the very least (GH#2165) + - 'paramiko.util.retry_on_signal' (and any internal uses of same, and also + any internal retries of 'EINTR' on e.g. socket operations) has been + removed; as of Python 3.5, per PEP 475 (https://peps.python.org/pep-0475/), + this functionality (and retrying 'EINTR' generally) is now part of the + standard library + Note: This change is backwards incompatible if you were explicitly + importing/using this particular function; the observable behavior otherwise + should not be changing + - '~paramiko.config.SSHConfig' used to straight-up delete the 'proxycommand' + key from config lookup results when the source config said + 'ProxyCommand none'; this has been altered to preserve the key and give it + the Python value 'None', thus making the Python representation more in line + with the source config file + Note: This change is backwards incompatible if you were relying on the old + (1.x, 2.x) behavior for some reason (e.g. assuming all 'proxycommand' + values were valid subcommand strings) + - The behavior of private key classes' (i.e. anything inheriting from + '~paramiko.pkey.PKey') private key writing methods used to perform a + manual, extra 'chmod' call after writing; this hasn't been strictly + necessary since the mid 2.x release line (when key writing started giving + the 'mode' argument to 'os.open'), and has now been removed entirely; this + should only be observable if you were mocking Paramiko's system calls + during your own testing, or similar + - 'PKey.__cmp__' has been removed - ordering-oriented comparison of key files + is unlikely to have ever made sense (the old implementation attempted to + order by the hashes of the key material) and so we have not bothered + setting up '__lt__' and friends at this time; the class continues to have + its original '__eq__' untouched + Note: This change is backwards incompatible if you were actually trying to + sort public key objects (directly or indirectly); please file bug reports + detailing your use case if you have some intractable need for this + behavior, and we'll consider adding back the necessary Python 3 magic + methods so that it works as before + - A handful of lower-level classes (notably 'paramiko.message.Message' and + 'paramiko.pkey.PKey') previously returned 'bytes' objects from their + implementation of '__str__', even under Python 3; and there was never any + '__bytes__' method; these issues have been fixed by renaming '__str__' to + '__bytes__' and relying on Python's default "stringification returns the + output of '__repr__'" behavior re: any real attempts to 'str()' such objects + - 'paramiko.common.asbytes' has been moved to 'paramiko.util.asbytes' + Note: This change is backwards incompatible if you were directly using this + function (which is unlikely) + - Remove the now irrelevant 'paramiko.py3compat' module + Note: This change is backwards incompatible - such references should be + search-and-replaced with their modern Python 3.6+ equivalents; in some + cases, still-useful methods or values have been moved to 'paramiko.util' + (most) or 'paramiko.common' ('byte_*') + - Drop support for Python versions less than 3.6, including Python 2; so long + and thanks for all the fish! Our packaging metadata has been updated to + include 'python_requires', so this should not cause breakage unless you're + on an old installation method that can't read this metadata + Note: As part of this change, our dependencies have been updated; e.g. we + now require Cryptography>=3.3, up from 2.5 + +* Fri Jan 20 2023 Fedora Release Engineering - 2.12.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild * Sun Nov 6 2022 Paul Howarth - 2.12.0-1 - Update to 2.12.0 (rhbz#2140281)