Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-asyncssh for openSUSE:Factory checked in at 2023-12-18 22:57:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-asyncssh (Old) and /work/SRC/openSUSE:Factory/.python-asyncssh.new.9037 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-asyncssh" Mon Dec 18 22:57:21 2023 rev:26 rq:1133889 version:2.14.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-asyncssh/python-asyncssh.changes 2023-11-13 22:20:41.635111844 +0100 +++ /work/SRC/openSUSE:Factory/.python-asyncssh.new.9037/python-asyncssh.changes 2023-12-18 22:57:30.619055507 +0100 @@ -1,0 +2,14 @@ +Mon Dec 18 15:55:18 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 2.14.2 (bsc#1218165, CVE-2023-48795): + * Implemented "strict kex" support and other countermeasures to + * protect against the Terrapin Attack described in + CVE-2023-48795 + * Fixed config parser to properly an optional equals delimiter + in all config arguments. + * Fixed TCP send error handling to avoid race condition when + receiving incoming disconnect message. + * Improved type signature in SSHConnection async context + manager. + +------------------------------------------------------------------- Old: ---- asyncssh-2.14.1.tar.gz New: ---- asyncssh-2.14.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-asyncssh.spec ++++++ --- /var/tmp/diff_new_pack.Ol77YX/_old 2023-12-18 22:57:31.707095353 +0100 +++ /var/tmp/diff_new_pack.Ol77YX/_new 2023-12-18 22:57:31.711095500 +0100 @@ -16,10 +16,9 @@ # -%define skip_python2 1 -%define skip_python36 1 +%{?sle15_python_module_pythons} Name: python-asyncssh -Version: 2.14.1 +Version: 2.14.2 Release: 0 Summary: Asynchronous SSHv2 client and server library License: EPL-2.0 OR GPL-2.0-or-later ++++++ asyncssh-2.14.1.tar.gz -> asyncssh-2.14.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/.github/workflows/run_tests.yml new/asyncssh-2.14.2/.github/workflows/run_tests.yml --- old/asyncssh-2.14.1/.github/workflows/run_tests.yml 2023-11-09 03:28:14.000000000 +0100 +++ new/asyncssh-2.14.2/.github/workflows/run_tests.yml 2023-11-09 04:54:11.000000000 +0100 @@ -67,7 +67,9 @@ - name: Install Linux dependencies if: ${{ runner.os == 'Linux' }} - run: sudo apt install -y --no-install-recommends libnettle8 libsodium-dev libssl-dev libkrb5-dev ssh cmake ninja-build + run: | + sudo apt update + sudo apt install -y --no-install-recommends libnettle8 libsodium-dev libssl-dev libkrb5-dev ssh cmake ninja-build - name: Install macOS dependencies if: ${{ runner.os == 'macOS' }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/PKG-INFO new/asyncssh-2.14.2/PKG-INFO --- old/asyncssh-2.14.1/PKG-INFO 2023-11-09 04:02:21.000000000 +0100 +++ new/asyncssh-2.14.2/PKG-INFO 2023-12-18 16:49:17.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: asyncssh -Version: 2.14.1 +Version: 2.14.2 Summary: AsyncSSH: Asynchronous SSHv2 client and server library Home-page: http://asyncssh.timeheart.net Author: Ron Frederick diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/asyncssh/config.py new/asyncssh-2.14.2/asyncssh/config.py --- old/asyncssh-2.14.1/asyncssh/config.py 2022-12-27 22:30:36.000000000 +0100 +++ new/asyncssh-2.14.2/asyncssh/config.py 2023-12-18 16:45:25.000000000 +0100 @@ -315,22 +315,26 @@ continue try: - args = shlex.split(line) + split_args = shlex.split(line) except ValueError as exc: self._error(str(exc)) - option = args.pop(0) + args = [] - if option.endswith('='): - option = option[:-1] - elif '=' in option: - option, arg = option.split('=', 1) - args[:0] =[arg] - elif args and args[0] == '=': - del args[0] - elif args and args[0].startswith('='): - args[0] = args[0][1:] + for arg in split_args: + if arg.startswith('='): + if len(arg) > 1: + args.append(arg[1:]) + elif arg.endswith('='): + args.append(arg[:-1]) + elif '=' in arg: + arg, val = arg.split('=', 1) + args.append(arg) + args.append(val) + else: + args.append(arg) + option = args.pop(0) loption = option.lower() if loption in self._no_split: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/asyncssh/connection.py new/asyncssh-2.14.2/asyncssh/connection.py --- old/asyncssh-2.14.1/asyncssh/connection.py 2023-11-09 03:28:14.000000000 +0100 +++ new/asyncssh-2.14.2/asyncssh/connection.py 2023-12-18 16:45:25.000000000 +0100 @@ -179,6 +179,7 @@ _ProtocolFactory = Union[_ClientFactory, _ServerFactory] _Conn = TypeVar('_Conn', 'SSHClientConnection', 'SSHServerConnection') +_ConnSelf = TypeVar('_ConnSelf', bound='SSHConnection') class _TunnelProtocol(Protocol): """Base protocol for connections to tunnel SSH over""" @@ -317,12 +318,6 @@ self._conn.connection_lost(exc) - def is_closing(self) -> bool: - """Return whether the transport is closing or not""" - - assert self._transport is not None - return self._transport.is_closing() - def write(self, data: bytes) -> None: """Write data to this tunnel""" @@ -866,6 +861,7 @@ self._kexinit_sent = False self._kex_complete = False self._ignore_first_kex = False + self._strict_kex = False self._gss: Optional[GSSBase] = None self._gss_kex = False @@ -944,7 +940,7 @@ self._disable_trivial_auth = False - async def __aenter__(self) -> 'SSHConnection': + async def __aenter__(self: _ConnSelf) -> _ConnSelf: """Allow SSHConnection to be used as an async context manager""" return self @@ -1403,19 +1399,22 @@ (alg_type, b','.join(local_algs).decode('ascii'), b','.join(remote_algs).decode('ascii'))) - def _get_ext_info_kex_alg(self) -> List[bytes]: - """Return the kex alg to add if any to request extension info""" + def _get_extra_kex_algs(self) -> List[bytes]: + """Return the extra kex algs to add""" - return [b'ext-info-c' if self.is_client() else b'ext-info-s'] + if self.is_client(): + return [b'ext-info-c', b'kex-strict-c-...@openssh.com'] + else: + return [b'ext-info-s', b'kex-strict-s-...@openssh.com'] def _send(self, data: bytes) -> None: """Send data to the SSH connection""" if self._transport: - if self._transport.is_closing(): - self._force_close(BrokenPipeError()) - else: + try: self._transport.write(data) + except BrokenPipeError: # pragma: no cover + pass def _send_version(self) -> None: """Start the SSH handshake""" @@ -1551,6 +1550,11 @@ else: skip_reason = 'kex not in progress' exc_reason = 'Key exchange not in progress' + elif self._strict_kex and not self._recv_encryption and \ + MSG_IGNORE <= pkttype <= MSG_DEBUG: + skip_reason = 'strict kex violation' + exc_reason = 'Strict key exchange violation: ' \ + 'unexpected packet type %d received' % pkttype elif MSG_USERAUTH_FIRST <= pkttype <= MSG_USERAUTH_LAST: if self._auth: handler = self._auth @@ -1586,8 +1590,13 @@ raise ProtocolError(str(exc)) from None if not processed: - self.logger.debug1('Unknown packet type %d received', pkttype) - self.send_packet(MSG_UNIMPLEMENTED, UInt32(seq)) + if self._strict_kex and not self._recv_encryption: + exc_reason = 'Strict key exchange violation: ' \ + 'unexpected packet type %d received' % pkttype + else: + self.logger.debug1('Unknown packet type %d received', + pkttype) + self.send_packet(MSG_UNIMPLEMENTED, UInt32(seq)) if exc_reason: raise ProtocolError(exc_reason) @@ -1596,9 +1605,16 @@ self._auth_final = True if self._transport: - self._recv_seq = (seq + 1) & 0xffffffff self._recv_handler = self._recv_pkthdr + if self._recv_seq == 0xffffffff and not self._recv_encryption: + raise ProtocolError('Sequence rollover before kex complete') + + if pkttype == MSG_NEWKEYS and self._strict_kex: + self._recv_seq = 0 + else: + self._recv_seq = (seq + 1) & 0xffffffff + return True def send_packet(self, pkttype: int, *args: bytes, @@ -1650,7 +1666,15 @@ mac = b'' self._send(packet + mac) - self._send_seq = (seq + 1) & 0xffffffff + + if self._send_seq == 0xffffffff and not self._send_encryption: + self._send_seq = 0 + raise ProtocolError('Sequence rollover before kex complete') + + if pkttype == MSG_NEWKEYS and self._strict_kex: + self._send_seq = 0 + else: + self._send_seq = (seq + 1) & 0xffffffff if self._kex_complete: self._rekey_bytes_sent += pktlen @@ -1694,7 +1718,7 @@ kex_algs = expand_kex_algs(self._kex_algs, gss_mechs, bool(self._server_host_key_algs)) + \ - self._get_ext_info_kex_alg() + self._get_extra_kex_algs() host_key_algs = self._server_host_key_algs or [b'null'] @@ -2196,13 +2220,27 @@ if self.is_server(): self._client_kexinit = packet.get_consumed_payload() - if b'ext-info-c' in peer_kex_algs and not self._session_id: - self._can_send_ext_info = True + if not self._session_id: + if b'ext-info-c' in peer_kex_algs: + self._can_send_ext_info = True + + if b'kex-strict-c-...@openssh.com' in peer_kex_algs: + self._strict_kex = True else: self._server_kexinit = packet.get_consumed_payload() - if b'ext-info-s' in peer_kex_algs and not self._session_id: - self._can_send_ext_info = True + if not self._session_id: + if b'ext-info-s' in peer_kex_algs: + self._can_send_ext_info = True + + if b'kex-strict-s-...@openssh.com' in peer_kex_algs: + self._strict_kex = True + + if self._strict_kex and not self._recv_encryption and \ + self._recv_seq != 0: + raise ProtocolError('Strict key exchange violation: ' + 'KEXINIT was not the first packet') + if self._kexinit_sent: self._kexinit_sent = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/asyncssh/version.py new/asyncssh-2.14.2/asyncssh/version.py --- old/asyncssh-2.14.1/asyncssh/version.py 2023-11-09 03:30:33.000000000 +0100 +++ new/asyncssh-2.14.2/asyncssh/version.py 2023-12-18 16:45:49.000000000 +0100 @@ -26,4 +26,4 @@ __url__ = 'http://asyncssh.timeheart.net' -__version__ = '2.14.1' +__version__ = '2.14.2' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/asyncssh.egg-info/PKG-INFO new/asyncssh-2.14.2/asyncssh.egg-info/PKG-INFO --- old/asyncssh-2.14.1/asyncssh.egg-info/PKG-INFO 2023-11-09 04:02:21.000000000 +0100 +++ new/asyncssh-2.14.2/asyncssh.egg-info/PKG-INFO 2023-12-18 16:49:17.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: asyncssh -Version: 2.14.1 +Version: 2.14.2 Summary: AsyncSSH: Asynchronous SSHv2 client and server library Home-page: http://asyncssh.timeheart.net Author: Ron Frederick diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/docs/changes.rst new/asyncssh-2.14.2/docs/changes.rst --- old/asyncssh-2.14.1/docs/changes.rst 2023-11-09 03:50:21.000000000 +0100 +++ new/asyncssh-2.14.2/docs/changes.rst 2023-12-18 16:45:57.000000000 +0100 @@ -3,13 +3,32 @@ Change Log ========== +Release 2.14.2 (18 Dec 2023) +---------------------------- + +* Implemented "strict kex" support and other countermeasures to + protect against the Terrapin Attack described in `CVE-2023-48795 + <https://github.com/advisories/GHSA-hfmc-7525-mj55>`. Thanks once + again go to Fabian Bäumer, Marcus Brinkmann, and Jörg Schwenk for + identifying and reporting this vulnerability and providing detailed + analysis and suggestions about proposed fixes. + +* Fixed config parser to properly an optional equals delimiter in all + config arguments. Thanks go to Fawaz Orabi for reporting this issue. + +* Fixed TCP send error handling to avoid race condition when receiving + incoming disconnect message. + +* Improved type signature in SSHConnection async context manager. Thanks + go to Pieter-Jan Briers for providing this. + Release 2.14.1 (8 Nov 2023) --------------------------- * Hardened AsyncSSH state machine against potential message injection attacks, described in more detail in `CVE-2023-46445 - <https://github.com/advisories/CVE-2023-46445>`_ and `CVE-2023-46446 - <https://github.com/advisories/CVE-2023-46446>`_. Thanks go to + <https://github.com/advisories/GHSA-cfc2-wr2v-gxm5>`_ and `CVE-2023-46446 + <https://github.com/advisories/GHSA-c35q-ffpf-5qpm>`_. Thanks go to Fabian Bäumer, Marcus Brinkmann, and Jörg Schwenk for identifying and reporting these vulnerabilities and providing detailed analysis and suggestions about the proposed fixes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/tests/test_connection.py new/asyncssh-2.14.2/tests/test_connection.py --- old/asyncssh-2.14.1/tests/test_connection.py 2023-11-09 03:28:14.000000000 +0100 +++ new/asyncssh-2.14.2/tests/test_connection.py 2023-12-18 16:45:25.000000000 +0100 @@ -30,9 +30,10 @@ from unittest.mock import patch import asyncssh -from asyncssh.constants import MSG_DEBUG +from asyncssh.constants import MSG_IGNORE, MSG_DEBUG from asyncssh.constants import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT -from asyncssh.constants import MSG_KEXINIT, MSG_NEWKEYS, MSG_KEX_FIRST +from asyncssh.constants import MSG_KEXINIT, MSG_NEWKEYS +from asyncssh.constants import MSG_KEX_FIRST, MSG_KEX_LAST from asyncssh.constants import MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS from asyncssh.constants import MSG_USERAUTH_FAILURE, MSG_USERAUTH_BANNER from asyncssh.constants import MSG_USERAUTH_FIRST @@ -43,6 +44,7 @@ from asyncssh.crypto.cipher import GCMCipher from asyncssh.encryption import get_encryption_algs from asyncssh.kex import get_kex_algs +from asyncssh.kex_dh import MSG_KEX_ECDH_REPLY from asyncssh.mac import _HMAC, _mac_handler, get_mac_algs from asyncssh.packet import Boolean, NameList, String, UInt32 from asyncssh.public_key import get_default_public_key_algs @@ -51,8 +53,8 @@ from .server import Server, ServerTestCase -from .util import asynctest, gss_available, nc_available, patch_gss -from .util import patch_getnameinfo, x509_available +from .util import asynctest, patch_extra_kex, patch_getnameinfo, patch_gss +from .util import gss_available, nc_available, x509_available class _CheckAlgsClientConnection(asyncssh.SSHClientConnection): @@ -931,22 +933,6 @@ await self.connect(kex_algs=['fail']) @asynctest - async def test_skip_ext_info(self): - """Test not requesting extension info from the server""" - - def skip_ext_info(self): - """Don't request extension information""" - - # pylint: disable=unused-argument - - return [] - - with patch('asyncssh.connection.SSHConnection._get_ext_info_kex_alg', - skip_ext_info): - async with self.connect(): - pass - - @asynctest async def test_unknown_ext_info(self): """Test receiving unknown extension information""" @@ -971,6 +957,54 @@ await self.connect() @asynctest + async def test_message_before_kexinit_strict_kex(self): + """Test receiving a message before KEXINIT with strict_kex enabled""" + + def send_packet(self, pkttype, *args, **kwargs): + if pkttype == MSG_KEXINIT: + self.send_packet(MSG_IGNORE, String(b'')) + + asyncssh.connection.SSHConnection.send_packet( + self, pkttype, *args, **kwargs) + + with patch('asyncssh.connection.SSHClientConnection.send_packet', + send_packet): + with self.assertRaises(asyncssh.ProtocolError): + await self.connect() + + @asynctest + async def test_message_during_kex_strict_kex(self): + """Test receiving an unexpected message with strict_kex enabled""" + + def send_packet(self, pkttype, *args, **kwargs): + if pkttype == MSG_KEX_ECDH_REPLY: + self.send_packet(MSG_IGNORE, String(b'')) + + asyncssh.connection.SSHConnection.send_packet( + self, pkttype, *args, **kwargs) + + with patch('asyncssh.connection.SSHServerConnection.send_packet', + send_packet): + with self.assertRaises(asyncssh.ProtocolError): + await self.connect() + + @asynctest + async def test_unknown_message_during_kex_strict_kex(self): + """Test receiving an unknown message with strict_kex enabled""" + + def send_packet(self, pkttype, *args, **kwargs): + if pkttype == MSG_KEX_ECDH_REPLY: + self.send_packet(MSG_KEX_LAST) + + asyncssh.connection.SSHConnection.send_packet( + self, pkttype, *args, **kwargs) + + with patch('asyncssh.connection.SSHServerConnection.send_packet', + send_packet): + with self.assertRaises(asyncssh.ProtocolError): + await self.connect() + + @asynctest async def test_encryption_algs(self): """Test connecting with different encryption algorithms""" @@ -1602,6 +1636,81 @@ await self.create_connection(_InternalErrorClient) +@patch_extra_kex +class _TestConnectionNoStrictKex(ServerTestCase): + """Unit tests for connection API with ext info and strict kex disabled""" + + @classmethod + async def start_server(cls): + """Start an SSH server to connect to""" + + return (await cls.create_server(_TunnelServer, gss_host=(), + compression_algs='*', + encryption_algs='*', + kex_algs='*', mac_algs='*')) + + @asynctest + async def test_skip_ext_info(self): + """Test not requesting extension info from the server""" + + async with self.connect(): + pass + + @asynctest + async def test_message_before_kexinit(self): + """Test receiving a message before KEXINIT""" + + def send_packet(self, pkttype, *args, **kwargs): + if pkttype == MSG_KEXINIT: + self.send_packet(MSG_IGNORE, String(b'')) + + asyncssh.connection.SSHConnection.send_packet( + self, pkttype, *args, **kwargs) + + with patch('asyncssh.connection.SSHClientConnection.send_packet', + send_packet): + async with self.connect(): + pass + + @asynctest + async def test_message_during_kex(self): + """Test receiving an unexpected message in key exchange""" + + def send_packet(self, pkttype, *args, **kwargs): + if pkttype == MSG_KEX_ECDH_REPLY: + self.send_packet(MSG_IGNORE, String(b'')) + + asyncssh.connection.SSHConnection.send_packet( + self, pkttype, *args, **kwargs) + + with patch('asyncssh.connection.SSHServerConnection.send_packet', + send_packet): + async with self.connect(): + pass + + @asynctest + async def test_sequence_wrap_during_kex(self): + """Test sequence wrap during initial key exchange""" + + def send_packet(self, pkttype, *args, **kwargs): + if pkttype == MSG_KEXINIT: + if self._options.command == 'send': + self._send_seq = 0xfffffffe + else: + self._recv_seq = 0xfffffffe + + asyncssh.connection.SSHConnection.send_packet( + self, pkttype, *args, **kwargs) + + with patch('asyncssh.connection.SSHClientConnection.send_packet', + send_packet): + with self.assertRaises(asyncssh.ProtocolError): + await self.connect(command='send') + + with self.assertRaises(asyncssh.ProtocolError): + await self.connect(command='recv') + + class _TestConnectionListenSock(ServerTestCase): """Unit test for specifying a listen socket""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/tests/test_connection_auth.py new/asyncssh-2.14.2/tests/test_connection_auth.py --- old/asyncssh-2.14.1/tests/test_connection_auth.py 2023-10-22 04:09:07.000000000 +0200 +++ new/asyncssh-2.14.2/tests/test_connection_auth.py 2023-12-18 16:45:25.000000000 +0100 @@ -739,7 +739,7 @@ return [] - with patch('asyncssh.connection.SSHConnection._get_ext_info_kex_alg', + with patch('asyncssh.connection.SSHConnection._get_extra_kex_algs', skip_ext_info): try: async with self.connect(username='user', @@ -1245,7 +1245,7 @@ return [] - with patch('asyncssh.connection.SSHConnection._get_ext_info_kex_alg', + with patch('asyncssh.connection.SSHConnection._get_extra_kex_algs', skip_ext_info): try: async with self.connect(username='ckey', client_keys='ckey', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-2.14.1/tests/util.py new/asyncssh-2.14.2/tests/util.py --- old/asyncssh-2.14.1/tests/util.py 2023-10-01 02:35:42.000000000 +0200 +++ new/asyncssh-2.14.2/tests/util.py 2023-12-18 16:45:25.000000000 +0100 @@ -106,6 +106,20 @@ return patch('socket.getnameinfo', getnameinfo)(cls) +def patch_extra_kex(cls): + """Decorator for skipping extra kex algs""" + + def skip_extra_kex_algs(self): + """Don't send extra key exchange algorithms""" + + # pylint: disable=unused-argument + + return [] + + return patch('asyncssh.connection.SSHConnection._get_extra_kex_algs', + skip_extra_kex_algs)(cls) + + def patch_gss(cls): """Decorator for patching GSSAPI classes"""