import python-paramiko-3.5.0-1.el10

i10ce changed/i10ce/python-paramiko-3.5.0-1.el10
Arkady L. Shane 4 weeks ago
parent 719fd4544c
commit a117c52b03
Signed by: tigro
GPG Key ID: 1EC08A25C9DB2503

2
.gitignore vendored

@ -1 +1 @@
SOURCES/paramiko-2.12.0.tar.gz
SOURCES/paramiko-3.5.0.tar.gz

@ -1 +1 @@
c8f18401ebcfc7aee9132eaa71bf33c76bf9301e SOURCES/paramiko-2.12.0.tar.gz
07b3054f764cdc1aa207cafc98a1e36eaa00f9d6 SOURCES/paramiko-3.5.0.tar.gz

@ -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.

@ -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

@ -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,

@ -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

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

@ -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...

@ -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 <tigro@msvsphere-os.ru> - 2.12.0-2
- Rebuilt for MSVSphere 8.10
* Fri Dec 29 2023 Paul Howarth <paul@city-fan.org> - 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 <tigro@msvsphere-os.ru> - 3.5.0-1
- Rebuilt for MSVSphere 10
* Mon Sep 16 2024 Paul Howarth <paul@city-fan.org> - 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 <paul@city-fan.org> - 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 <paul@city-fan.org> - 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 <releng@fedoraproject.org> - 3.4.0-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild
* Sat Jun 08 2024 Python Maint <python-maint@redhat.com> - 3.4.0-4
- Rebuilt for Python 3.13
* Fri Jan 26 2024 Fedora Release Engineering <releng@fedoraproject.org> - 3.4.0-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Mon Jan 22 2024 Fedora Release Engineering <releng@fedoraproject.org> - 3.4.0-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Tue Dec 19 2023 Gwyn Ciesla <gwync@protonmail.com> - 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 <paul@city-fan.org> - 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 <gwync@protonmail.com> - 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 <releng@fedoraproject.org> - 3.2.0-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
* Thu Jun 15 2023 Python Maint <python-maint@redhat.com> - 3.2.0-2
- Rebuilt for Python 3.12
* Sat May 27 2023 Paul Howarth <paul@city-fan.org> - 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 <paul@city-fan.org> - 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 <paul@city-fan.org> - 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 <releng@fedoraproject.org> - 2.12.0-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild
* Sun Nov 6 2022 Paul Howarth <paul@city-fan.org> - 2.12.0-1
- Update to 2.12.0 (rhbz#2140281)

Loading…
Cancel
Save