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-10-05 20:04:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-asyncssh (Old)
and /work/SRC/openSUSE:Factory/.python-asyncssh.new.28202 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-asyncssh"
Thu Oct 5 20:04:58 2023 rev:24 rq:1115789 version:2.14.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-asyncssh/python-asyncssh.changes
2023-07-03 17:42:15.224530767 +0200
+++
/work/SRC/openSUSE:Factory/.python-asyncssh.new.28202/python-asyncssh.changes
2023-10-05 20:06:09.181215623 +0200
@@ -1,0 +2,25 @@
+Thu Oct 5 09:42:35 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 2.14.0:
+ * Added support for a new accept_handler argument when setting
+ up local port forwarding, allowing the client host and port to
+ be validated and/or logged for each new forwarded connection.
+ * Added an option to disable expensive RSA private key checks
+ when using OpenSSL 3.x. Functions that read private keys have
+ been modified to include a new unsafe_skip_rsa_key_validation
+ argument which can be used to avoid these additional checks,
+ if you are loading keys from a trusted source.
+ * Added host information into AsyncSSH exceptions when host key
+ validation fails, and a few other improvements related to
+ X.509 certificate validation errors.
+ * Fixed a regression which prevented keys loaded into an SSH
+ agent with a certificate from working correctly beginning in
+ AsyncSSH after version 2.5.0.
+ * Fixed an issue which was triggering an internal exception
+ when shutting down server sessions with the line editor enabled
+ which could cause some output to be lost on exit, especially when
+ running on Windows.
+ * Fixed a documentation error in SSHClientConnectionOptions and
+ SSHServerConnectionOptions.
+
+-------------------------------------------------------------------
Old:
----
asyncssh-2.13.2.tar.gz
New:
----
asyncssh-2.14.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-asyncssh.spec ++++++
--- /var/tmp/diff_new_pack.2czv4J/_old 2023-10-05 20:06:10.345257676 +0200
+++ /var/tmp/diff_new_pack.2czv4J/_new 2023-10-05 20:06:10.349257820 +0200
@@ -19,7 +19,7 @@
%define skip_python2 1
%define skip_python36 1
Name: python-asyncssh
-Version: 2.13.2
+Version: 2.14.0
Release: 0
Summary: Asynchronous SSHv2 client and server library
License: EPL-2.0 OR GPL-2.0-or-later
++++++ asyncssh-2.13.2.tar.gz -> asyncssh-2.14.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/.github/workflows/run_tests.yml
new/asyncssh-2.14.0/.github/workflows/run_tests.yml
--- old/asyncssh-2.13.2/.github/workflows/run_tests.yml 2022-12-27
22:30:36.000000000 +0100
+++ new/asyncssh-2.14.0/.github/workflows/run_tests.yml 2023-10-01
02:35:42.000000000 +0200
@@ -111,7 +111,7 @@
V = sys.version_info
p = platform.system().lower()
subprocess.run(
- ['tox', '-e', f'py{V.major}{V.minor}-{p}', '--', '-ra'],
+ ['tox', 'run', '-e', f'py{V.major}{V.minor}-{p}', '--', '-ra'],
check=True)
- name: Upload coverage data
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/PKG-INFO new/asyncssh-2.14.0/PKG-INFO
--- old/asyncssh-2.13.2/PKG-INFO 2023-06-22 05:08:52.759388400 +0200
+++ new/asyncssh-2.14.0/PKG-INFO 2023-10-01 03:06:55.014456500 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: asyncssh
-Version: 2.13.2
+Version: 2.14.0
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.13.2/asyncssh/__init__.py
new/asyncssh-2.14.0/asyncssh/__init__.py
--- old/asyncssh-2.13.2/asyncssh/__init__.py 2022-08-11 02:01:29.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/__init__.py 2023-10-01 02:35:42.000000000
+0200
@@ -44,6 +44,7 @@
from .connection import SSHAcceptor, SSHClientConnection, SSHServerConnection
from .connection import SSHClientConnectionOptions, SSHServerConnectionOptions
+from .connection import SSHAcceptHandler
from .connection import create_connection, create_server, connect, listen
from .connection import connect_reverse, listen_reverse, get_server_host_key
from .connection import get_server_auth_methods, run_client, run_server
@@ -86,6 +87,8 @@
from .public_key import load_keypairs, load_public_keys, load_certificates
from .public_key import load_resident_keys
+from .rsa import set_default_skip_rsa_key_validation
+
from .scp import scp
from .session import DataType, SSHClientSession, SSHServerSession
@@ -164,5 +167,5 @@
'read_certificate_list', 'read_known_hosts', 'read_private_key',
'read_private_key_list', 'read_public_key', 'read_public_key_list',
'run_client', 'run_server', 'scp', 'set_debug_level', 'set_log_level',
- 'set_sftp_log_level',
+ 'set_sftp_log_level', 'set_default_skip_rsa_key_validation',
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/agent.py
new/asyncssh-2.14.0/asyncssh/agent.py
--- old/asyncssh-2.13.2/asyncssh/agent.py 2023-02-18 23:52:45.000000000
+0100
+++ new/asyncssh-2.14.0/asyncssh/agent.py 2023-10-01 02:35:42.000000000
+0200
@@ -149,6 +149,18 @@
self._is_cert = is_cert
self._flags = 0
+ @property
+ def has_cert(self) -> bool:
+ """ Return if this key pair has an associated cert"""
+
+ return self._is_cert
+
+ @property
+ def has_x509_chain(self) -> bool:
+ """ Return if this key pair has an associated X.509 cert chain"""
+
+ return False
+
def set_certificate(self, cert: SSHCertificate) -> None:
"""Set certificate to use with this key"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/connection.py
new/asyncssh-2.14.0/asyncssh/connection.py
--- old/asyncssh-2.13.2/asyncssh/connection.py 2023-06-22 04:57:39.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/connection.py 2023-10-01 02:35:42.000000000
+0200
@@ -233,6 +233,7 @@
_VersionArg = DefTuple[BytesOrStr]
+SSHAcceptHandler = Callable[[str, int], MaybeAwait[bool]]
# SSH service names
_USERAUTH_SERVICE = b'ssh-userauth'
@@ -2886,10 +2887,10 @@
return SSHForwarder(cast(SSHForwarder, peer))
@async_context_manager
- async def forward_local_port(self, listen_host: str,
- listen_port: int,
- dest_host: str,
- dest_port: int) -> SSHListener:
+ async def forward_local_port(
+ self, listen_host: str, listen_port: int,
+ dest_host: str, dest_port: int,
+ accept_handler: Optional[SSHAcceptHandler] = None) -> SSHListener:
"""Set up local port forwarding
This method is a coroutine which attempts to set up port
@@ -2906,10 +2907,17 @@
The hostname or address to forward the connections to
:param dest_port:
The port number to forward the connections to
+ :param accept_handler:
+ A `callable` or coroutine which takes arguments of the
+ original host and port of the client and decides whether
+ or not to allow connection forwarding, returning `True` to
+ accept the connection and begin forwarding or `False` to
+ reject and close it.
:type listen_host: `str`
:type listen_port: `int`
:type dest_host: `str`
:type dest_port: `int`
+ :type accept_handler: `callable` or coroutine
:returns: :class:`SSHListener`
@@ -2923,6 +2931,21 @@
Tuple[SSHTCPChannel[bytes], SSHTCPSession[bytes]]:
"""Forward a local connection over SSH"""
+ if accept_handler:
+ result = accept_handler(orig_host, orig_port)
+
+ if inspect.isawaitable(result):
+ result = await cast(Awaitable[bool], result)
+
+ if not result:
+ self.logger.info('Request for TCP forwarding from '
+ '%s to %s denied by application',
+ (orig_host, orig_port),
+ (dest_host, dest_port))
+
+ raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED,
+ 'Connection forwarding denied')
+
return (await self.create_connection(session_factory,
dest_host, dest_port,
orig_host, orig_port))
@@ -3240,7 +3263,12 @@
self._host_key_alias or self._host,
self._peer_addr, self._port, key_data)
except ValueError as exc:
- raise HostKeyNotVerifiable(str(exc)) from None
+ host = self._host
+
+ if self._host_key_alias:
+ host += f' with alias {self._host_key_alias}'
+
+ raise HostKeyNotVerifiable(f'{exc} for host {host}') from None
self._server_host_key = host_key
return host_key
@@ -3288,7 +3316,9 @@
self.logger.info('Auth failed for user %s', self._username)
- self._force_close(PermissionDenied('Permission denied'))
+ self._force_close(PermissionDenied('Permission denied for user '
+ f'{self._username} on host '
+ f'{self._host}'))
def gss_kex_auth_requested(self) -> bool:
"""Return whether to allow GSS key exchange authentication or not"""
@@ -4688,9 +4718,9 @@
**kwargs) # type: ignore
@async_context_manager
- async def forward_local_port_to_path(self, listen_host: str,
- listen_port: int,
- dest_path: str) -> SSHListener:
+ async def forward_local_port_to_path(
+ self, listen_host: str, listen_port: int, dest_path: str,
+ accept_handler: Optional[SSHAcceptHandler] = None) -> SSHListener:
"""Set up local TCP port forwarding to a remote UNIX domain socket
This method is a coroutine which attempts to set up port
@@ -4705,9 +4735,16 @@
The port number on the local host to listen on
:param dest_path:
The path on the remote host to forward the connections to
+ :param accept_handler:
+ A `callable` or coroutine which takes arguments of the
+ original host and port of the client and decides whether
+ or not to allow connection forwarding, returning `True` to
+ accept the connection and begin forwarding or `False` to
+ reject and close it.
:type listen_host: `str`
:type listen_port: `int`
:type dest_path: `str`
+ :type accept_handler: `callable` or coroutine
:returns: :class:`SSHListener`
@@ -4717,10 +4754,24 @@
async def tunnel_connection(
session_factory: SSHUNIXSessionFactory[bytes],
- _orig_host: str, _orig_port: int) -> \
+ orig_host: str, orig_port: int) -> \
Tuple[SSHUNIXChannel[bytes], SSHUNIXSession[bytes]]:
"""Forward a local connection over SSH"""
+ if accept_handler:
+ result = accept_handler(orig_host, orig_port)
+
+ if inspect.isawaitable(result):
+ result = await cast(Awaitable[bool], result)
+
+ if not result:
+ self.logger.info('Request for TCP forwarding from '
+ '%s to %s denied by application',
+ (orig_host, orig_port), dest_path)
+
+ raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED,
+ 'Connection forwarding denied')
+
return (await self.create_unix_connection(session_factory,
dest_path))
@@ -5730,6 +5781,10 @@
if listener is True:
listener = await self.forward_local_port(
listen_host, listen_port, listen_host, listen_port)
+ elif callable(listener):
+ listener = await self.forward_local_port(
+ listen_host, listen_port,
+ listen_host, listen_port, listener)
except OSError:
self.logger.debug1('Failed to create TCP listener')
self._report_global_response(False)
@@ -6548,7 +6603,8 @@
if x509_trusted_cert_paths:
for path in x509_trusted_cert_paths:
if not Path(path).is_dir():
- raise ValueError('Path not a directory: ' + str(path))
+ raise ValueError('X.509 trusted certificate path not '
+ f'a directory: {path}')
self.x509_trusted_certs = x509_trusted_certs
self.x509_trusted_cert_paths = x509_trusted_cert_paths
@@ -6972,7 +7028,7 @@
build up a configuration. When an option is not explicitly
specified, its value will be pulled from this options object
(if present) before falling back to the default value.
- :type client_factory: `callable` returning :class:`SSHClientConnection`
+ :type client_factory: `callable` returning :class:`SSHClient`
:type proxy_command: `str` or `list` of `str`
:type known_hosts: *see* :ref:`SpecifyingKnownHosts`
:type host_key_alias: `str`
@@ -7621,7 +7677,7 @@
build up a configuration. When an option is not explicitly
specified, its value will be pulled from this options object
(if present) before falling back to the default value.
- :type server_factory: `callable` returning :class:`SSHServerConnection`
+ :type server_factory: `callable` returning :class:`SSHServer`
:type proxy_command: `str` or `list` of `str`
:type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6`
:type server_host_keys: *see* :ref:`SpecifyingPrivateKeys`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/crypto/rsa.py
new/asyncssh-2.14.0/asyncssh/crypto/rsa.py
--- old/asyncssh-2.13.2/asyncssh/crypto/rsa.py 2023-02-18 23:52:45.000000000
+0100
+++ new/asyncssh-2.14.0/asyncssh/crypto/rsa.py 2023-10-01 02:35:42.000000000
+0200
@@ -98,12 +98,14 @@
@classmethod
def construct(cls, n: int, e: int, d: int, p: int, q: int,
- dmp1: int, dmq1: int, iqmp: int) -> 'RSAPrivateKey':
+ dmp1: int, dmq1: int, iqmp: int,
+ skip_validation: bool) -> 'RSAPrivateKey':
"""Construct an RSA private key"""
pub = rsa.RSAPublicNumbers(e, n)
priv = rsa.RSAPrivateNumbers(p, q, d, dmp1, dmq1, iqmp, pub)
- priv_key = priv.private_key()
+ priv_key = priv.private_key(
+ unsafe_skip_rsa_key_validation=skip_validation)
return cls(priv_key, pub, priv)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/crypto/x509.py
new/asyncssh-2.14.0/asyncssh/crypto/x509.py
--- old/asyncssh-2.13.2/asyncssh/crypto/x509.py 2023-06-22 04:57:39.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/crypto/x509.py 2023-10-01 02:35:42.000000000
+0200
@@ -308,7 +308,7 @@
None)
x509_ctx.verify_certificate()
except crypto.X509StoreContextError as exc:
- raise ValueError(str(exc)) from None
+ raise ValueError(f'X.509 chain validation error: {exc}') from None
def generate_x509_certificate(signing_key: PyCAKey, key: PyCAKey,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/editor.py
new/asyncssh-2.14.0/asyncssh/editor.py
--- old/asyncssh-2.13.2/asyncssh/editor.py 2023-06-22 04:57:39.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/editor.py 2023-10-01 02:35:42.000000000
+0200
@@ -704,8 +704,10 @@
self._ring_bell()
self._bell_rung = False
- self._chan.write(''.join(self._outbuf))
- self._outbuf.clear()
+
+ if self._outbuf:
+ self._chan.write(''.join(self._outbuf))
+ self._outbuf.clear()
else:
self._session.data_received(data, datatype)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/public_key.py
new/asyncssh-2.14.0/asyncssh/public_key.py
--- old/asyncssh-2.13.2/asyncssh/public_key.py 2023-06-22 04:57:39.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/public_key.py 2023-10-01 02:35:42.000000000
+0200
@@ -262,13 +262,13 @@
@classmethod
def make_private(cls, key_params: object) -> 'SSHKey':
- """Construct an RSA private key"""
+ """Construct a private key"""
raise NotImplementedError
@classmethod
def make_public(cls, key_params: object) -> 'SSHKey':
- """Construct an RSA public key"""
+ """Construct a public key"""
raise NotImplementedError
@@ -2096,6 +2096,18 @@
return self._key_type
+ @property
+ def has_cert(self) -> bool:
+ """ Return if this key pair has an associated cert"""
+
+ return bool(self._cert)
+
+ @property
+ def has_x509_chain(self) -> bool:
+ """ Return if this key pair has an associated X.509 cert chain"""
+
+ return self._cert.is_x509_chain if self._cert else False
+
def get_algorithm(self) -> str:
"""Return the algorithm associated with this key pair"""
@@ -2194,9 +2206,9 @@
self.sig_algorithm = sig_algorithm
- if not self._cert:
+ if not self.has_cert:
self.algorithm = sig_algorithm
- elif self._cert.is_x509_chain:
+ elif self.has_x509_chain:
self.algorithm = sig_algorithm
cert = cast('SSHX509CertificateChain', self._cert)
@@ -2402,7 +2414,9 @@
return None, (), len(data)
-def _decode_pkcs1_private(pem_name: bytes, key_data: object) -> SSHKey:
+def _decode_pkcs1_private(
+ pem_name: bytes, key_data: object,
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> SSHKey:
"""Decode a PKCS#1 format private key"""
handler = _pem_map.get(pem_name)
@@ -2415,6 +2429,10 @@
raise KeyImportError('Invalid %s private key' %
pem_name.decode('ascii'))
+ if pem_name == b'RSA':
+ key_params = cast(Tuple, key_params) + \
+ (unsafe_skip_rsa_key_validation,)
+
return handler.make_private(key_params)
@@ -2434,7 +2452,9 @@
return handler.make_public(key_params)
-def _decode_pkcs8_private(key_data: object) -> SSHKey:
+def _decode_pkcs8_private(
+ key_data: object,
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> SSHKey:
"""Decode a PKCS#8 format private key"""
if (isinstance(key_data, tuple) and len(key_data) >= 3 and
@@ -2455,6 +2475,10 @@
handler.pem_name.decode('ascii')
if handler.pem_name else 'PKCS#8')
+ if alg == ObjectIdentifier('1.2.840.113549.1.1.1'):
+ key_params = cast(Tuple, key_params) + \
+ (unsafe_skip_rsa_key_validation,)
+
return handler.make_private(key_params)
else:
raise KeyImportError('Invalid PKCS#8 private key')
@@ -2486,8 +2510,9 @@
raise KeyImportError('Invalid PKCS#8 public key')
-def _decode_openssh_private(data: bytes,
- passphrase: Optional[BytesOrStr]) -> SSHKey:
+def _decode_openssh_private(
+ data: bytes, passphrase: Optional[BytesOrStr],
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> SSHKey:
"""Decode an OpenSSH format private key"""
try:
@@ -2578,6 +2603,10 @@
if len(pad) >= block_size or pad != bytes(range(1, len(pad) + 1)):
raise KeyImportError('Invalid OpenSSH private key')
+ if alg == b'ssh-rsa':
+ key_params = cast(Tuple, key_params) + \
+ (unsafe_skip_rsa_key_validation,)
+
key = handler.make_private(key_params)
key.set_comment(comment)
return key
@@ -2609,8 +2638,9 @@
raise KeyImportError('Invalid OpenSSH private key') from None
-def _decode_der_private(key_data: object,
- passphrase: Optional[BytesOrStr]) -> SSHKey:
+def _decode_der_private(
+ key_data: object, passphrase: Optional[BytesOrStr],
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> SSHKey:
"""Decode a DER format private key"""
# First, if there's a passphrase, try to decrypt PKCS#8
@@ -2623,7 +2653,7 @@
# Then, try to decode PKCS#8
try:
- return _decode_pkcs8_private(key_data)
+ return _decode_pkcs8_private(key_data, unsafe_skip_rsa_key_validation)
except KeyImportError:
# PKCS#8 failed - try PKCS#1 instead
pass
@@ -2631,7 +2661,8 @@
# If that fails, try each of the possible PKCS#1 encodings
for pem_name in _pem_map:
try:
- return _decode_pkcs1_private(pem_name, key_data)
+ return _decode_pkcs1_private(pem_name, key_data,
+ unsafe_skip_rsa_key_validation)
except KeyImportError:
# Try the next PKCS#1 encoding
pass
@@ -2667,13 +2698,15 @@
return SSHX509Certificate.construct_from_der(data, comment)
-def _decode_pem_private(pem_name: bytes, headers: Mapping[bytes, bytes],
- data: bytes, passphrase: Optional[BytesOrStr]) -> \
- SSHKey:
+def _decode_pem_private(
+ pem_name: bytes, headers: Mapping[bytes, bytes],
+ data: bytes, passphrase: Optional[BytesOrStr],
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> SSHKey:
"""Decode a PEM format private key"""
if pem_name == b'OPENSSH':
- return _decode_openssh_private(data, passphrase)
+ return _decode_openssh_private(data, passphrase,
+ unsafe_skip_rsa_key_validation)
if headers.get(b'Proc-Type') == b'4,ENCRYPTED':
if passphrase is None:
@@ -2715,9 +2748,10 @@
'private key') from None
if pem_name:
- return _decode_pkcs1_private(pem_name, key_data)
+ return _decode_pkcs1_private(pem_name, key_data,
+ unsafe_skip_rsa_key_validation)
else:
- return _decode_pkcs8_private(key_data)
+ return _decode_pkcs8_private(key_data, unsafe_skip_rsa_key_validation)
def _decode_pem_public(pem_name: bytes, data: bytes) -> SSHKey:
@@ -2750,8 +2784,10 @@
return SSHX509Certificate.construct_from_der(data)
-def _decode_private(data: bytes, passphrase: Optional[BytesOrStr]) -> \
- Tuple[Optional[SSHKey], Optional[int]]:
+def _decode_private(
+ data: bytes, passphrase: Optional[BytesOrStr],
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> \
+ Tuple[Optional[SSHKey], Optional[int]]:
"""Decode a private key"""
fmt, key_info, end = _match_next(data, b'PRIVATE KEY')
@@ -2759,10 +2795,12 @@
key: Optional[SSHKey]
if fmt == 'der':
- key = _decode_der_private(key_info[0], passphrase)
+ key = _decode_der_private(key_info[0], passphrase,
+ unsafe_skip_rsa_key_validation)
elif fmt == 'pem':
pem_name, headers, data = key_info
- key = _decode_pem_private(pem_name, headers, data, passphrase)
+ key = _decode_pem_private(pem_name, headers, data, passphrase,
+ unsafe_skip_rsa_key_validation)
else:
key = None
@@ -2799,7 +2837,7 @@
if fmt == 'pem' and key_info[0] == b'OPENSSH':
key = _decode_openssh_public(key_info[2])
else:
- key, _ = _decode_private(data, None)
+ key, _ = _decode_private(data, None, False)
if key:
key = key.convert_to_public()
@@ -2836,14 +2874,16 @@
return cert, end
-def _decode_private_list(data: bytes, passphrase: Optional[BytesOrStr]) -> \
- Sequence[SSHKey]:
+def _decode_private_list(
+ data: bytes, passphrase: Optional[BytesOrStr],
+ unsafe_skip_rsa_key_validation: Optional[bool]) -> Sequence[SSHKey]:
"""Decode a private key list"""
keys: List[SSHKey] = []
while data:
- key, end = _decode_private(data, passphrase)
+ key, end = _decode_private(data, passphrase,
+ unsafe_skip_rsa_key_validation)
if key:
keys.append(key)
@@ -3122,8 +3162,9 @@
key.set_comment(comment)
return key
-def import_private_key(data: BytesOrStr,
- passphrase: Optional[BytesOrStr] = None) -> SSHKey:
+def import_private_key(
+ data: BytesOrStr, passphrase: Optional[BytesOrStr] = None,
+ unsafe_skip_rsa_key_validation: Optional[bool] = None) -> SSHKey:
"""Import a private key
This function imports a private key encoded in PKCS#1 or PKCS#8 DER
@@ -3134,8 +3175,13 @@
The data to import.
:param passphrase: (optional)
The passphrase to use to decrypt the key.
+ :param unsafe_skip_rsa_key_validation: (optional)
+ Whether or not to skip key validation when loading RSA private
+ keys, defaulting to performing these checks unless changed by
+ calling :func:`set_default_skip_rsa_key_validation`.
:type data: `bytes` or ASCII `str`
:type passphrase: `str` or `bytes`
+ :type unsafe_skip_rsa_key_validation: bool
:returns: An :class:`SSHKey` private key
@@ -3147,7 +3193,7 @@
except UnicodeEncodeError:
raise KeyImportError('Invalid encoding for key') from None
- key, _ = _decode_private(data, passphrase)
+ key, _ = _decode_private(data, passphrase, unsafe_skip_rsa_key_validation)
if key:
return key
@@ -3155,12 +3201,14 @@
raise KeyImportError('Invalid private key')
-def import_private_key_and_certs(data: bytes,
- passphrase: Optional[BytesOrStr] = None) -> \
- Tuple[SSHKey, Optional[SSHX509CertificateChain]]:
+def import_private_key_and_certs(
+ data: bytes, passphrase: Optional[BytesOrStr] = None,
+ unsafe_skip_rsa_key_validation: Optional[bool] = None) -> \
+ Tuple[SSHKey, Optional[SSHX509CertificateChain]]:
"""Import a private key and optional certificate chain"""
- key, end = _decode_private(data, passphrase)
+ key, end = _decode_private(data, passphrase,
+ unsafe_skip_rsa_key_validation)
if key:
return key, import_certificate_chain(data[end:])
@@ -3256,8 +3304,9 @@
raise KeyImportError('Invalid certificate subject')
-def read_private_key(filename: FilePath,
- passphrase: Optional[BytesOrStr] = None) -> SSHKey:
+def read_private_key(
+ filename: FilePath, passphrase: Optional[BytesOrStr] = None,
+ unsafe_skip_rsa_key_validation: Optional[bool] = None) -> SSHKey:
"""Read a private key from a file
This function reads a private key from a file. See the function
@@ -3268,26 +3317,34 @@
The file to read the key from.
:param passphrase: (optional)
The passphrase to use to decrypt the key.
+ :param unsafe_skip_rsa_key_validation: (optional)
+ Whether or not to skip key validation when loading RSA private
+ keys, defaulting to performing these checks unless changed by
+ calling :func:`set_default_skip_rsa_key_validation`.
:type filename: :class:`PurePath <pathlib.PurePath>` or `str`
:type passphrase: `str` or `bytes`
+ :type unsafe_skip_rsa_key_validation: bool
:returns: An :class:`SSHKey` private key
"""
- key = import_private_key(read_file(filename), passphrase)
+ key = import_private_key(read_file(filename), passphrase,
+ unsafe_skip_rsa_key_validation)
key.set_filename(filename)
return key
-def read_private_key_and_certs(filename: FilePath,
- passphrase: Optional[BytesOrStr] = None) -> \
- Tuple[SSHKey, Optional[SSHX509CertificateChain]]:
+def read_private_key_and_certs(
+ filename: FilePath, passphrase: Optional[BytesOrStr] = None,
+ unsafe_skip_rsa_key_validation: Optional[bool] = None) -> \
+ Tuple[SSHKey, Optional[SSHX509CertificateChain]]:
"""Read a private key and optional certificate chain from a file"""
- key, cert = import_private_key_and_certs(read_file(filename), passphrase)
+ key, cert = import_private_key_and_certs(read_file(filename), passphrase,
+ unsafe_skip_rsa_key_validation)
key.set_filename(filename)
@@ -3334,9 +3391,10 @@
return import_certificate(read_file(filename))
-def read_private_key_list(filename: FilePath,
- passphrase: Optional[BytesOrStr] = None) -> \
- Sequence[SSHKey]:
+def read_private_key_list(
+ filename: FilePath, passphrase: Optional[BytesOrStr] = None,
+ unsafe_skip_rsa_key_validation: Optional[bool] = None) -> \
+ Sequence[SSHKey]:
"""Read a list of private keys from a file
This function reads a list of private keys from a file. See the
@@ -3348,14 +3406,20 @@
The file to read the keys from.
:param passphrase: (optional)
The passphrase to use to decrypt the keys.
+ :param unsafe_skip_rsa_key_validation: (optional)
+ Whether or not to skip key validation when loading RSA private
+ keys, defaulting to performing these checks unless changed by
+ calling :func:`set_default_skip_rsa_key_validation`.
:type filename: :class:`PurePath <pathlib.PurePath>` or `str`
:type passphrase: `str` or `bytes`
+ :type unsafe_skip_rsa_key_validation: bool
:returns: A list of :class:`SSHKey` private keys
"""
- keys = _decode_private_list(read_file(filename), passphrase)
+ keys = _decode_private_list(read_file(filename), passphrase,
+ unsafe_skip_rsa_key_validation)
for key in keys:
key.set_filename(filename)
@@ -3404,10 +3468,12 @@
return _decode_certificate_list(read_file(filename))
-def load_keypairs(keylist: KeyPairListArg,
- passphrase: Optional[BytesOrStr] = None,
- certlist: CertListArg = (), skip_public: bool = False,
- ignore_encrypted: bool = False) -> Sequence[SSHKeyPair]:
+def load_keypairs(
+ keylist: KeyPairListArg, passphrase: Optional[BytesOrStr] = None,
+ certlist: CertListArg = (), skip_public: bool = False,
+ ignore_encrypted: bool = False,
+ unsafe_skip_rsa_key_validation: Optional[bool] = None) -> \
+ Sequence[SSHKeyPair]:
"""Load SSH private keys and optional matching certificates
This function loads a list of SSH keys and optional matching
@@ -3427,10 +3493,15 @@
An internal parameter used to skip public keys and certificates
when IdentitiesOnly and IdentityFile are used to specify a
mixture of private and public keys.
+ :param unsafe_skip_rsa_key_validation: (optional)
+ Whether or not to skip key validation when loading RSA private
+ keys, defaulting to performing these checks unless changed by
+ calling :func:`set_default_skip_rsa_key_validation`.
:type keylist: *see* :ref:`SpecifyingPrivateKeys`
:type passphrase: `str` or `bytes`
:type certlist: *see* :ref:`SpecifyingCertificates`
:type skip_public: `bool`
+ :type unsafe_skip_rsa_key_validation: bool
:returns: A list of :class:`SSHKeyPair` objects
@@ -3444,7 +3515,8 @@
if isinstance(keylist, (PurePath, str)):
try:
- priv_keys = read_private_key_list(keylist, passphrase)
+ priv_keys = read_private_key_list(keylist, passphrase,
+ unsafe_skip_rsa_key_validation)
keys_to_load = [keylist] if len(priv_keys) <= 1 else priv_keys
except KeyImportError:
keys_to_load = [keylist]
@@ -3472,21 +3544,25 @@
key_prefix = str(key_to_load)
if allow_certs:
- key, certs_to_load = \
- read_private_key_and_certs(key_to_load, passphrase)
+ key, certs_to_load = read_private_key_and_certs(
+ key_to_load, passphrase,
+ unsafe_skip_rsa_key_validation)
if not certs_to_load:
certs_to_load = key_prefix + '-cert.pub'
else:
- key = read_private_key(key_to_load, passphrase)
+ key = read_private_key(key_to_load, passphrase,
+ unsafe_skip_rsa_key_validation)
pubkey_to_load = key_prefix + '.pub'
elif isinstance(key_to_load, bytes):
if allow_certs:
- key, certs_to_load = \
- import_private_key_and_certs(key_to_load, passphrase)
+ key, certs_to_load = import_private_key_and_certs(
+ key_to_load, passphrase,
+ unsafe_skip_rsa_key_validation)
else:
- key = import_private_key(key_to_load, passphrase)
+ key = import_private_key(key_to_load, passphrase,
+ unsafe_skip_rsa_key_validation)
else:
key = key_to_load
except KeyImportError as exc:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/rsa.py
new/asyncssh-2.14.0/asyncssh/rsa.py
--- old/asyncssh-2.13.2/asyncssh/rsa.py 2023-06-22 04:57:39.000000000 +0200
+++ new/asyncssh-2.14.0/asyncssh/rsa.py 2023-10-01 02:35:42.000000000 +0200
@@ -43,9 +43,49 @@
_PrivateKeyArgs = Tuple[int, int, int, int, int, int, int, int]
+_PrivateKeyConstructArgs = Tuple[int, int, int, int, int, int, int, int, bool]
_PublicKeyArgs = Tuple[int, int]
+_default_skip_rsa_key_validation = False
+
+
+def set_default_skip_rsa_key_validation(skip_validation: bool) -> None:
+ """Set whether to disable RSA key validation in OpenSSL
+
+ OpenSSL 3.x does additional validation when loading RSA keys
+ as an added security measure. However, the result is that
+ loading a key can take significantly longer than it did before.
+
+ If all your RSA keys are coming from a trusted source, you can
+ call this function with a value of `True` to default to skipping
+ these checks on RSA keys, reducing the cost back down to what it
+ was in earlier releases.
+
+ This can also be set on a case by case basis by using the new
+ `unsafe_skip_rsa_key_validation` argument on the functions used
+ to load keys. This will only affect loading keys of type RSA.
+
+ .. note:: The extra cost only applies to loading existing keys, and
+ not to generating new keys. Also, in cases where a key is
+ used repeatedly, it can be loaded once into an `SSHKey`
+ object and reused without having to pay the cost each time.
+ So, this call should not be needed in most applications.
+
+ If an application does need this, it is strongly
+ recommended that the `unsafe_skip_rsa_key_validation`
+ argument be used rather than using this function to
+ change the default behavior for all load operations.
+
+ """
+
+ # pylint: disable=global-statement
+
+ global _default_skip_rsa_key_validation
+
+ _default_skip_rsa_key_validation = skip_validation
+
+
class RSAKey(SSHKey):
"""Handler for RSA public key encryption"""
@@ -94,9 +134,14 @@
def make_private(cls, key_params: object) -> SSHKey:
"""Construct an RSA private key"""
- n, e, d, p, q, dmp1, dmq1, iqmp = cast(_PrivateKeyArgs, key_params)
+ n, e, d, p, q, dmp1, dmq1, iqmp, unsafe_skip_rsa_key_validation = \
+ cast(_PrivateKeyConstructArgs, key_params)
+
+ if unsafe_skip_rsa_key_validation is None:
+ unsafe_skip_rsa_key_validation = _default_skip_rsa_key_validation
- return cls(RSAPrivateKey.construct(n, e, d, p, q, dmp1, dmq1, iqmp))
+ return cls(RSAPrivateKey.construct(n, e, d, p, q, dmp1, dmq1, iqmp,
+ unsafe_skip_rsa_key_validation))
@classmethod
def make_public(cls, key_params: object) -> SSHKey:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/server.py
new/asyncssh-2.14.0/asyncssh/server.py
--- old/asyncssh-2.13.2/asyncssh/server.py 2023-06-22 04:57:39.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/server.py 2023-10-01 02:35:42.000000000
+0200
@@ -31,7 +31,7 @@
if TYPE_CHECKING:
# pylint: disable=cyclic-import
- from .connection import SSHServerConnection
+ from .connection import SSHServerConnection, SSHAcceptHandler
from .channel import SSHServerChannel, SSHTCPChannel, SSHUNIXChannel
from .session import SSHServerSession, SSHTCPSession, SSHUNIXSession
@@ -45,7 +45,7 @@
_NewUNIXSession = Union[bool, 'SSHUNIXSession', SSHSocketSessionFactory,
Tuple['SSHUNIXChannel', 'SSHUNIXSession'],
Tuple['SSHUNIXChannel', SSHSocketSessionFactory]]
-_NewListener = Union[bool, SSHListener]
+_NewListener = Union[bool, 'SSHAcceptHandler', SSHListener]
class SSHServer:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh/version.py
new/asyncssh-2.14.0/asyncssh/version.py
--- old/asyncssh-2.13.2/asyncssh/version.py 2023-06-22 04:57:55.000000000
+0200
+++ new/asyncssh-2.14.0/asyncssh/version.py 2023-10-01 03:04:52.000000000
+0200
@@ -26,4 +26,4 @@
__url__ = 'http://asyncssh.timeheart.net'
-__version__ = '2.13.2'
+__version__ = '2.14.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/asyncssh.egg-info/PKG-INFO
new/asyncssh-2.14.0/asyncssh.egg-info/PKG-INFO
--- old/asyncssh-2.13.2/asyncssh.egg-info/PKG-INFO 2023-06-22
05:08:52.000000000 +0200
+++ new/asyncssh-2.14.0/asyncssh.egg-info/PKG-INFO 2023-10-01
03:06:54.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: asyncssh
-Version: 2.13.2
+Version: 2.14.0
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.13.2/asyncssh.egg-info/requires.txt
new/asyncssh-2.14.0/asyncssh.egg-info/requires.txt
--- old/asyncssh-2.13.2/asyncssh.egg-info/requires.txt 2023-06-22
05:08:52.000000000 +0200
+++ new/asyncssh-2.14.0/asyncssh.egg-info/requires.txt 2023-10-01
03:06:54.000000000 +0200
@@ -1,4 +1,4 @@
-cryptography>=3.1
+cryptography>=39.0
typing_extensions>=3.6
[bcrypt]
@@ -17,7 +17,7 @@
python-pkcs11>=0.7.0
[pyOpenSSL]
-pyOpenSSL>=17.0.0
+pyOpenSSL>=23.0.0
[pywin32]
pywin32>=227
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/docs/api.rst
new/asyncssh-2.14.0/docs/api.rst
--- old/asyncssh-2.13.2/docs/api.rst 2023-06-22 04:57:39.000000000 +0200
+++ new/asyncssh-2.14.0/docs/api.rst 2023-10-01 02:35:42.000000000 +0200
@@ -1617,6 +1617,11 @@
.. index:: SSH agent support
+set_default_skip_rsa_key_validation
+-----------------------------------
+
+.. autofunction:: set_default_skip_rsa_key_validation
+
SSH Agent Support
=================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/docs/changes.rst
new/asyncssh-2.14.0/docs/changes.rst
--- old/asyncssh-2.13.2/docs/changes.rst 2023-06-22 04:59:01.000000000
+0200
+++ new/asyncssh-2.14.0/docs/changes.rst 2023-10-01 03:04:52.000000000
+0200
@@ -3,6 +3,44 @@
Change Log
==========
+Release 2.14.0 (30 Sep 2023)
+----------------------------
+
+* Added support for a new accept_handler argument when setting up
+ local port forwarding, allowing the client host and port to be
+ validated and/or logged for each new forwarded connection. An
+ accept handler can also be returned from the server_requested
+ function to provide this functionality when acting as a server.
+ Thanks go to GitHub user zgxkbtl for suggesting this feature.
+
+* Added an option to disable expensive RSA private key checks when
+ using OpenSSL 3.x. Functions that read private keys have been
+ modified to include a new unsafe_skip_rsa_key_validation argument
+ which can be used to avoid these additional checks, if you are
+ loading keys from a trusted source.
+
+* Added host information into AsyncSSH exceptions when host key
+ validation fails, and a few other improvements related to X.509
+ certificate validation errors. Thanks go to Peter Moore for
+ suggesting this and providing an example.
+
+* Fixed a regression which prevented keys loaded into an SSH agent
+ with a certificate from working correctly beginning in AsyncSSH
+ after version 2.5.0. Thanks go to GitHub user htol for reporting
+ this issue and suggesting the commit which caused the problem.
+
+* Fixed an issue which was triggering an internal exception when
+ shutting down server sessions with the line editor enabled which
+ could cause some output to be lost on exit, especially when running
+ on Windows. Thanks go to GitHub user jerrbe for reporting this issue.
+
+* Fixed an issue in a unit test seen in Python 3.12 beta. Thanks go
+ to Georg Sauthoff for providing this fix.
+
+* Fixed a documentation error in SSHClientConnectionOptions and
+ SSHServerConnectionOptions. Thanks go to GitHub user bowenerchen
+ for reporting this issue.
+
Release 2.13.2 (21 Jun 2023)
----------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/setup.py new/asyncssh-2.14.0/setup.py
--- old/asyncssh-2.13.2/setup.py 2022-04-16 22:13:09.000000000 +0200
+++ new/asyncssh-2.14.0/setup.py 2023-10-01 02:35:42.000000000 +0200
@@ -57,14 +57,14 @@
long_description = long_description,
platforms = 'Any',
python_requires = '>= 3.6',
- install_requires = ['cryptography >= 3.1', 'typing_extensions >= 3.6'],
+ install_requires = ['cryptography >= 39.0', 'typing_extensions >= 3.6'],
extras_require = {
'bcrypt': ['bcrypt >= 3.1.3'],
'fido2': ['fido2 >= 0.9.2'],
'gssapi': ['gssapi >= 1.2.0'],
'libnacl': ['libnacl >= 1.4.2'],
'pkcs11': ['python-pkcs11 >= 0.7.0'],
- 'pyOpenSSL': ['pyOpenSSL >= 17.0.0'],
+ 'pyOpenSSL': ['pyOpenSSL >= 23.0.0'],
'pywin32': ['pywin32 >= 227']
},
packages = ['asyncssh', 'asyncssh.crypto'],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/tests/test_connection.py
new/asyncssh-2.14.0/tests/test_connection.py
--- old/asyncssh-2.13.2/tests/test_connection.py 2023-06-22
04:57:39.000000000 +0200
+++ new/asyncssh-2.14.0/tests/test_connection.py 2023-10-01
02:35:42.000000000 +0200
@@ -2068,6 +2068,13 @@
await self.connect()
@asynctest
+ async def test_host_key_unknown(self):
+ """Test unknown host key alias"""
+
+ with self.assertRaises(asyncssh.HostKeyNotVerifiable):
+ await self.connect(host_key_alias='unknown')
+
+ @asynctest
async def test_host_key_match(self):
"""Test host key match"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/tests/test_forward.py
new/asyncssh-2.14.0/tests/test_forward.py
--- old/asyncssh-2.13.2/tests/test_forward.py 2022-12-27 22:30:36.000000000
+0100
+++ new/asyncssh-2.14.0/tests/test_forward.py 2023-10-01 02:35:42.000000000
+0200
@@ -183,6 +183,18 @@
return listen_host != 'fail'
+class _TCPAcceptHandlerServer(Server):
+ """Server for testing forwarding accept handler"""
+
+ async def server_requested(self, listen_host, listen_port):
+ """Handle a request to create a new socket listener"""
+
+ def accept_handler(_orig_host: str, _orig_port: int) -> bool:
+ return True
+
+ return accept_handler
+
+
class _UNIXConnectionServer(Server):
"""Server for testing direct and forwarded UNIX domain connections"""
@@ -594,6 +606,39 @@
await self._check_local_connection(listener.get_port(),
delay=0.1)
+ @asynctest
+ async def test_forward_local_port_accept_handler(self):
+ """Test forwarding of a local port with an accept handler"""
+
+ def accept_handler(_orig_host: str, _orig_port: int) -> bool:
+ return True
+
+ async with self.connect() as conn:
+ async with conn.forward_local_port('', 0, '', 7,
+ accept_handler) as listener:
+ await self._check_local_connection(listener.get_port(),
+ delay=0.1)
+
+ @asynctest
+ async def test_forward_local_port_accept_handler_denial(self):
+ """Test forwarding of a local port with an accept handler denial"""
+
+ async def accept_handler(_orig_host: str, _orig_port: int) -> bool:
+ return False
+
+ async with self.connect() as conn:
+ async with conn.forward_local_port('', 0, '', 7,
+ accept_handler) as listener:
+ listen_port = listener.get_port()
+
+ reader, writer = await asyncio.open_connection('127.0.0.1',
+ listen_port)
+
+ self.assertEqual((await reader.read()), b'')
+
+ writer.close()
+ await maybe_wait_closed(writer)
+
@unittest.skipIf(sys.platform == 'win32',
'skip UNIX domain socket tests on Windows')
@asynctest
@@ -855,6 +900,33 @@
await listener.wait_closed()
+class _TestTCPForwardingAcceptHandler(_CheckForwarding):
+ """Unit tests for TCP forwarding with accept handler"""
+
+ @classmethod
+ async def start_server(cls):
+ """Start an SSH server which supports TCP connection forwarding"""
+
+ return await cls.create_server(
+ _TCPAcceptHandlerServer, authorized_client_keys='authorized_keys')
+
+ @asynctest
+ async def test_forward_remote_port_accept_handler(self):
+ """Test forwarding of a remote port with accept handler"""
+
+ server = await asyncio.start_server(echo, None, 0,
+ family=socket.AF_INET)
+ server_port = server.sockets[0].getsockname()[1]
+
+ async with self.connect() as conn:
+ async with conn.forward_remote_port(
+ '', 0, '127.0.0.1', server_port) as listener:
+ await self._check_local_connection(listener.get_port())
+
+ server.close()
+ await server.wait_closed()
+
+
class _TestAsyncTCPForwarding(_TestTCPForwarding):
"""Unit tests for AsyncSSH TCP connection forwarding with async return"""
@@ -1000,6 +1072,39 @@
os.remove('local')
@asynctest
+ async def test_forward_local_port_to_path_accept_handler(self):
+ """Test forwarding of port to UNIX path with accept handler"""
+
+ def accept_handler(_orig_host: str, _orig_port: int) -> bool:
+ return True
+
+ async with self.connect() as conn:
+ async with conn.forward_local_port_to_path(
+ '', 0, '/echo', accept_handler) as listener:
+ await self._check_local_connection(listener.get_port(),
+ delay=0.1)
+
+ @asynctest
+ async def test_forward_local_port_to_path_accept_handler_denial(self):
+ """Test forwarding of port to UNIX path with accept handler denial"""
+
+ async def accept_handler(_orig_host: str, _orig_port: int) -> bool:
+ return False
+
+ async with self.connect() as conn:
+ async with conn.forward_local_port_to_path(
+ '', 0, '/echo', accept_handler) as listener:
+ listen_port = listener.get_port()
+
+ reader, writer = await asyncio.open_connection('127.0.0.1',
+ listen_port)
+
+ self.assertEqual((await reader.read()), b'')
+
+ writer.close()
+ await maybe_wait_closed(writer)
+
+ @asynctest
async def test_forward_local_port_to_path(self):
"""Test forwarding of a local port to a remote UNIX domain socket"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/tests/test_process.py
new/asyncssh-2.14.0/tests/test_process.py
--- old/asyncssh-2.13.2/tests/test_process.py 2022-12-30 17:45:50.000000000
+0100
+++ new/asyncssh-2.14.0/tests/test_process.py 2023-10-01 02:35:42.000000000
+0200
@@ -902,7 +902,7 @@
proc1.stdin.write(data)
proc1.stdin.write_eof()
- stdout_data, _ = await proc2.communicate()
+ stdout_data = await proc2.stdout.read()
self.assertEqual(stdout_data, data.encode('ascii'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/tests/util.py
new/asyncssh-2.14.0/tests/util.py
--- old/asyncssh-2.13.2/tests/util.py 2023-06-22 04:57:39.000000000 +0200
+++ new/asyncssh-2.14.0/tests/util.py 2023-10-01 02:35:42.000000000 +0200
@@ -32,8 +32,7 @@
from unittest.mock import patch
-from cryptography.hazmat.backends.openssl import backend
-
+from asyncssh import set_default_skip_rsa_key_validation
from asyncssh.gss import gss_available
from asyncssh.logging import logger
from asyncssh.misc import ConnectionLost, SignalReceived
@@ -77,15 +76,10 @@
# pylint: enable=no-member
-# pylint: disable=protected-access
-
-# Disable RSA key blinding to speed up unit tests
-backend._rsa_skip_check_key = True
-
-# pylint: enable=protected-access
-
_test_keys = {}
+set_default_skip_rsa_key_validation(True)
+
def asynctest(coro):
"""Decorator for async tests, for use with AsyncTestCase"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/asyncssh-2.13.2/tox.ini new/asyncssh-2.14.0/tox.ini
--- old/asyncssh-2.13.2/tox.ini 2022-12-27 22:30:36.000000000 +0100
+++ new/asyncssh-2.14.0/tox.ini 2023-10-01 02:35:42.000000000 +0200
@@ -26,6 +26,7 @@
windows: win32
usedevelop = True
setenv =
+ PIP_USE_PEP517 = 1
COVERAGE_FILE = .coverage.{envname}
commands =
{envpython} -m pytest --cov --cov-report=term-missing:skip-covered
{posargs}