asyncssh 2.15.0__tar.gz → 2.16.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {asyncssh-2.15.0 → asyncssh-2.16.0}/CONTRIBUTING.rst +9 -10
- {asyncssh-2.15.0/asyncssh.egg-info → asyncssh-2.16.0}/PKG-INFO +1 -1
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/config.py +7 -5
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/connection.py +189 -21
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/cipher.py +48 -30
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/x509.py +2 -1
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/logging.py +22 -12
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/misc.py +13 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/saslprep.py +4 -2
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/sftp.py +11 -13
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/version.py +1 -1
- {asyncssh-2.15.0 → asyncssh-2.16.0/asyncssh.egg-info}/PKG-INFO +1 -1
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/changes.rst +37 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_config.py +7 -1
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_connection.py +131 -1
- {asyncssh-2.15.0 → asyncssh-2.16.0}/.coveragerc +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/.github/workflows/run_tests.yml +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/.gitignore +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/.readthedocs.yaml +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/COPYRIGHT +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/LICENSE +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/MANIFEST.in +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/README.rst +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/__init__.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/agent.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/asn1.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/auth.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/channel.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/compression.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/constants.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/sntrup.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/dsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/editor.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/encryption.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/forward.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/gss.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/kex.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/kex_dh.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/keysign.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/listener.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/mac.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/packet.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/pattern.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/pbe.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/process.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/public_key.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/py.typed +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/rsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/scp.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/session.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/sk.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/socks.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/stream.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh/x11.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh.egg-info/requires.txt +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/api.rst +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/conf.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/contributing.rst +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/index.rst +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/requirements.txt +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/docs/rtd-req.txt +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/callback_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/callback_client2.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/callback_client3.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/callback_math_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/chat_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/check_exit_status.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/direct_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/direct_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/editor.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/gather_results.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/listening_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/math_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/math_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/redirect_input.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/redirect_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/reverse_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/reverse_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/scp_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/set_environment.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/set_terminal.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/sftp_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/show_environment.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/show_terminal.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/simple_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/simple_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/mypy.ini +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/pylintrc +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/setup.cfg +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/setup.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/__init__.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/gss_stub.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/keysign_stub.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/server.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/sk_stub.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/sspi_stub.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_agent.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_asn1.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_auth.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_channel.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_compression.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_connection_auth.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_editor.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_encryption.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_forward.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_kex.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_logging.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_mac.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_packet.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_process.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_public_key.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_saslprep.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_sftp.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_sk.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_stream.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_subprocess.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_tuntap.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_x11.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/test_x509.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tests/util.py +0 -0
- {asyncssh-2.15.0 → asyncssh-2.16.0}/tox.ini +0 -0
|
@@ -68,19 +68,18 @@ contributors list.
|
|
|
68
68
|
Branches
|
|
69
69
|
--------
|
|
70
70
|
|
|
71
|
-
There are two long-lived branches in AsyncSSH
|
|
71
|
+
There are two long-lived branches in AsyncSSH:
|
|
72
72
|
|
|
73
73
|
* The master branch is intended to contain the latest stable version
|
|
74
74
|
of the code. All official versions of AsyncSSH are released from
|
|
75
75
|
this branch, and each release has a corresponding tag added
|
|
76
|
-
matching its release number.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
APIs in the develop branch may be subject to change until they
|
|
83
|
-
are migrated back to master, and there's no guarantee of backward
|
|
76
|
+
matching its release number.
|
|
77
|
+
|
|
78
|
+
* The develop branch is intended to contain new features and bug fixes
|
|
79
|
+
ready to be tested before being added to an official release. APIs
|
|
80
|
+
in the develop branch may be subject to change until they are
|
|
81
|
+
migrated back to master, and there's no guarantee of backward
|
|
84
82
|
compatibility in this branch. However, pulling from this branch
|
|
85
83
|
will provide early access to new functionality and a chance to
|
|
86
|
-
influence this functionality before it is released.
|
|
84
|
+
influence this functionality before it is released. Also, all
|
|
85
|
+
pull requests should be submitted against this branch.
|
|
@@ -321,11 +321,15 @@ class SSHConfig:
|
|
|
321
321
|
|
|
322
322
|
args = []
|
|
323
323
|
loption = ''
|
|
324
|
+
allow_equal = True
|
|
324
325
|
|
|
325
326
|
for i, arg in enumerate(split_args, 1):
|
|
326
327
|
if arg.startswith('='):
|
|
327
328
|
if len(arg) > 1:
|
|
328
329
|
args.append(arg[1:])
|
|
330
|
+
elif not allow_equal:
|
|
331
|
+
args.extend(split_args[i-1:])
|
|
332
|
+
break
|
|
329
333
|
elif arg.endswith('='):
|
|
330
334
|
args.append(arg[:-1])
|
|
331
335
|
elif '=' in arg:
|
|
@@ -337,9 +341,7 @@ class SSHConfig:
|
|
|
337
341
|
|
|
338
342
|
if i == 1:
|
|
339
343
|
loption = args.pop(0).lower()
|
|
340
|
-
|
|
341
|
-
args.extend(split_args[i:])
|
|
342
|
-
break
|
|
344
|
+
allow_equal = loption in self._conditionals
|
|
343
345
|
|
|
344
346
|
if loption in self._no_split:
|
|
345
347
|
args = [line.lstrip()[len(loption):].strip()]
|
|
@@ -423,7 +425,7 @@ class SSHClientConfig(SSHConfig):
|
|
|
423
425
|
"""Settings from an OpenSSH client config file"""
|
|
424
426
|
|
|
425
427
|
_conditionals = {'host', 'match'}
|
|
426
|
-
_no_split = {'remotecommand'}
|
|
428
|
+
_no_split = {'proxycommand', 'remotecommand'}
|
|
427
429
|
_percent_expand = {'CertificateFile', 'IdentityAgent',
|
|
428
430
|
'IdentityFile', 'ProxyCommand', 'RemoteCommand'}
|
|
429
431
|
|
|
@@ -557,7 +559,7 @@ class SSHClientConfig(SSHConfig):
|
|
|
557
559
|
('PKCS11Provider', SSHConfig._set_string),
|
|
558
560
|
('PreferredAuthentications', SSHConfig._set_string),
|
|
559
561
|
('Port', SSHConfig._set_int),
|
|
560
|
-
('ProxyCommand', SSHConfig.
|
|
562
|
+
('ProxyCommand', SSHConfig._set_string),
|
|
561
563
|
('ProxyJump', SSHConfig._set_string),
|
|
562
564
|
('PubkeyAuthentication', SSHConfig._set_bool),
|
|
563
565
|
('RekeyLimit', SSHConfig._set_rekey_limits),
|
|
@@ -115,7 +115,7 @@ from .misc import ProtocolNotSupported, ServiceNotAvailable
|
|
|
115
115
|
from .misc import TermModesArg, TermSizeArg
|
|
116
116
|
from .misc import async_context_manager, construct_disc_error
|
|
117
117
|
from .misc import get_symbol_names, ip_address, map_handler_name
|
|
118
|
-
from .misc import parse_byte_count, parse_time_interval
|
|
118
|
+
from .misc import parse_byte_count, parse_time_interval, split_args
|
|
119
119
|
|
|
120
120
|
from .packet import Boolean, Byte, NameList, String, UInt32, PacketDecodeError
|
|
121
121
|
from .packet import SSHPacket, SSHPacketHandler, SSHPacketLogger
|
|
@@ -185,6 +185,10 @@ _ProtocolFactory = Union[_ClientFactory, _ServerFactory]
|
|
|
185
185
|
_Conn = TypeVar('_Conn', bound='SSHConnection')
|
|
186
186
|
_Options = TypeVar('_Options', bound='SSHConnectionOptions')
|
|
187
187
|
|
|
188
|
+
_ServerHostKeysHandler = Optional[Callable[[List[SSHKey], List[SSHKey],
|
|
189
|
+
List[SSHKey], List[SSHKey]],
|
|
190
|
+
MaybeAwait[None]]]
|
|
191
|
+
|
|
188
192
|
class _TunnelProtocol(Protocol):
|
|
189
193
|
"""Base protocol for connections to tunnel SSH over"""
|
|
190
194
|
|
|
@@ -227,7 +231,7 @@ _GlobalRequest = Tuple[Optional[_PacketHandler], SSHPacket, bool]
|
|
|
227
231
|
_GlobalRequestResult = Tuple[int, SSHPacket]
|
|
228
232
|
_KeyOrCertOptions = Mapping[str, object]
|
|
229
233
|
_ListenerArg = Union[bool, SSHListener]
|
|
230
|
-
_ProxyCommand = Optional[Sequence[str]]
|
|
234
|
+
_ProxyCommand = Optional[Union[str, Sequence[str]]]
|
|
231
235
|
_RequestPTY = Union[bool, str]
|
|
232
236
|
|
|
233
237
|
_TCPServerHandlerFactory = Callable[[str, int], SSHSocketSessionFactory]
|
|
@@ -1068,7 +1072,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1068
1072
|
self._set_keepalive_timer()
|
|
1069
1073
|
self.create_task(self._make_keepalive_request())
|
|
1070
1074
|
|
|
1071
|
-
def _force_close(self, exc: Optional[
|
|
1075
|
+
def _force_close(self, exc: Optional[Exception]) -> None:
|
|
1072
1076
|
"""Force this connection to close immediately"""
|
|
1073
1077
|
|
|
1074
1078
|
if not self._transport:
|
|
@@ -1309,7 +1313,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1309
1313
|
error_logger = self.logger
|
|
1310
1314
|
|
|
1311
1315
|
error_logger.debug1('Uncaught exception', exc_info=exc_info)
|
|
1312
|
-
self._force_close(exc_info[1])
|
|
1316
|
+
self._force_close(cast(Exception, exc_info[1]))
|
|
1313
1317
|
|
|
1314
1318
|
def session_started(self) -> None:
|
|
1315
1319
|
"""Handle session start when opening tunneled SSH connection"""
|
|
@@ -1995,6 +1999,11 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1995
1999
|
not self._waiter.cancelled():
|
|
1996
2000
|
self._waiter.set_result(None)
|
|
1997
2001
|
self._wait = None
|
|
2002
|
+
return
|
|
2003
|
+
|
|
2004
|
+
# This method is only in SSHServerConnection
|
|
2005
|
+
# pylint: disable=no-member
|
|
2006
|
+
cast(SSHServerConnection, self).send_server_host_keys()
|
|
1998
2007
|
|
|
1999
2008
|
def send_channel_open_confirmation(self, send_chan: int, recv_chan: int,
|
|
2000
2009
|
recv_window: int, recv_pktsize: int,
|
|
@@ -2012,6 +2021,13 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2012
2021
|
self.send_packet(MSG_CHANNEL_OPEN_FAILURE, UInt32(send_chan),
|
|
2013
2022
|
UInt32(code), String(reason), String(lang))
|
|
2014
2023
|
|
|
2024
|
+
def _send_global_request(self, request: bytes, *args: bytes,
|
|
2025
|
+
want_reply: bool = False) -> None:
|
|
2026
|
+
"""Send a global request"""
|
|
2027
|
+
|
|
2028
|
+
self.send_packet(MSG_GLOBAL_REQUEST, String(request),
|
|
2029
|
+
Boolean(want_reply), *args)
|
|
2030
|
+
|
|
2015
2031
|
async def _make_global_request(self, request: bytes,
|
|
2016
2032
|
*args: bytes) -> Tuple[int, SSHPacket]:
|
|
2017
2033
|
"""Send a global request and wait for the response"""
|
|
@@ -2024,8 +2040,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2024
2040
|
|
|
2025
2041
|
self._global_request_waiters.append(waiter)
|
|
2026
2042
|
|
|
2027
|
-
self.
|
|
2028
|
-
Boolean(True), *args)
|
|
2043
|
+
self._send_global_request(request, *args, want_reply=True)
|
|
2029
2044
|
|
|
2030
2045
|
return await waiter
|
|
2031
2046
|
|
|
@@ -3266,6 +3281,8 @@ class SSHClientConnection(SSHConnection):
|
|
|
3266
3281
|
self._server_host_key_algs: Optional[Sequence[bytes]] = None
|
|
3267
3282
|
self._server_host_key: Optional[SSHKey] = None
|
|
3268
3283
|
|
|
3284
|
+
self._server_host_keys_handler = options.server_host_keys_handler
|
|
3285
|
+
|
|
3269
3286
|
self._username = options.username
|
|
3270
3287
|
self._password = options.password
|
|
3271
3288
|
|
|
@@ -3580,6 +3597,8 @@ class SSHClientConnection(SSHConnection):
|
|
|
3580
3597
|
self._get_agent_keys = False
|
|
3581
3598
|
|
|
3582
3599
|
if self._get_pkcs11_keys:
|
|
3600
|
+
assert self._pkcs11_provider is not None
|
|
3601
|
+
|
|
3583
3602
|
pkcs11_keys = await self._loop.run_in_executor(
|
|
3584
3603
|
None, load_pkcs11_keys, self._pkcs11_provider, self._pkcs11_pin)
|
|
3585
3604
|
|
|
@@ -3924,6 +3943,80 @@ class SSHClientConnection(SSHConnection):
|
|
|
3924
3943
|
raise ChannelOpenError(OPEN_CONNECT_FAILED,
|
|
3925
3944
|
'Auth agent forwarding disabled')
|
|
3926
3945
|
|
|
3946
|
+
def _process_hostkeys_00_at_openssh_dot_com_global_request(
|
|
3947
|
+
self, packet: SSHPacket) -> None:
|
|
3948
|
+
"""Process a list of accepted server host keys"""
|
|
3949
|
+
|
|
3950
|
+
self.create_task(self._finish_hostkeys(packet))
|
|
3951
|
+
|
|
3952
|
+
async def _finish_hostkeys(self, packet: SSHPacket) -> None:
|
|
3953
|
+
"""Finish processing hostkeys global request"""
|
|
3954
|
+
|
|
3955
|
+
if not self._server_host_keys_handler:
|
|
3956
|
+
self.logger.debug1('Ignoring server host key message: no handler')
|
|
3957
|
+
self._report_global_response(False)
|
|
3958
|
+
return
|
|
3959
|
+
|
|
3960
|
+
if self._trusted_host_keys is None:
|
|
3961
|
+
self.logger.info('Server host key not verified: handler disabled')
|
|
3962
|
+
self._report_global_response(False)
|
|
3963
|
+
return
|
|
3964
|
+
|
|
3965
|
+
added = []
|
|
3966
|
+
removed = list(self._trusted_host_keys)
|
|
3967
|
+
retained = []
|
|
3968
|
+
revoked = []
|
|
3969
|
+
prove = []
|
|
3970
|
+
|
|
3971
|
+
while packet:
|
|
3972
|
+
try:
|
|
3973
|
+
key_data = packet.get_string()
|
|
3974
|
+
key = decode_ssh_public_key(key_data)
|
|
3975
|
+
|
|
3976
|
+
if key in self._revoked_host_keys:
|
|
3977
|
+
revoked.append(key)
|
|
3978
|
+
elif key in self._trusted_host_keys:
|
|
3979
|
+
retained.append(key)
|
|
3980
|
+
removed.remove(key)
|
|
3981
|
+
else:
|
|
3982
|
+
prove.append((key, String(key_data)))
|
|
3983
|
+
except KeyImportError:
|
|
3984
|
+
pass
|
|
3985
|
+
|
|
3986
|
+
if prove:
|
|
3987
|
+
pkttype, packet = await self._make_global_request(
|
|
3988
|
+
b'hostkeys-prove-00@openssh.com',
|
|
3989
|
+
b''.join(key_str for _, key_str in prove))
|
|
3990
|
+
|
|
3991
|
+
if pkttype == MSG_REQUEST_SUCCESS:
|
|
3992
|
+
prefix = String('hostkeys-prove-00@openssh.com') + \
|
|
3993
|
+
String(self._session_id)
|
|
3994
|
+
|
|
3995
|
+
for key, key_str in prove:
|
|
3996
|
+
sig = packet.get_string()
|
|
3997
|
+
|
|
3998
|
+
if key.verify(prefix + key_str, sig):
|
|
3999
|
+
added.append(key)
|
|
4000
|
+
else:
|
|
4001
|
+
self.logger.debug1('Server host key validation failed')
|
|
4002
|
+
else:
|
|
4003
|
+
self.logger.debug1('Server host key prove request failed')
|
|
4004
|
+
|
|
4005
|
+
packet.check_end()
|
|
4006
|
+
|
|
4007
|
+
self.logger.info(f'Server host key report: {len(added)} added, '
|
|
4008
|
+
f'{len(removed)} removed, {len(retained)} retained, '
|
|
4009
|
+
f'{len(revoked)} revoked')
|
|
4010
|
+
|
|
4011
|
+
result = self._server_host_keys_handler(added, removed,
|
|
4012
|
+
retained, revoked)
|
|
4013
|
+
|
|
4014
|
+
if inspect.isawaitable(result):
|
|
4015
|
+
assert result is not None
|
|
4016
|
+
await result
|
|
4017
|
+
|
|
4018
|
+
self._report_global_response(True)
|
|
4019
|
+
|
|
3927
4020
|
async def attach_x11_listener(self, chan: SSHClientChannel[AnyStr],
|
|
3928
4021
|
display: Optional[str],
|
|
3929
4022
|
auth_path: Optional[str],
|
|
@@ -4139,7 +4232,7 @@ class SSHClientConnection(SSHConnection):
|
|
|
4139
4232
|
if env:
|
|
4140
4233
|
try:
|
|
4141
4234
|
if isinstance(env, list):
|
|
4142
|
-
new_env.update((item.split('=',
|
|
4235
|
+
new_env.update((item.split('=', 1) for item in env))
|
|
4143
4236
|
else:
|
|
4144
4237
|
new_env.update(cast(Mapping[str, str], env))
|
|
4145
4238
|
except ValueError:
|
|
@@ -5594,6 +5687,7 @@ class SSHServerConnection(SSHConnection):
|
|
|
5594
5687
|
self._options = options
|
|
5595
5688
|
|
|
5596
5689
|
self._server_host_keys = options.server_host_keys
|
|
5690
|
+
self._all_server_host_keys = options.all_server_host_keys
|
|
5597
5691
|
self._server_host_key_algs = list(options.server_host_keys.keys())
|
|
5598
5692
|
self._known_client_hosts = options.known_client_hosts
|
|
5599
5693
|
self._trust_client_host = options.trust_client_host
|
|
@@ -5719,6 +5813,17 @@ class SSHServerConnection(SSHConnection):
|
|
|
5719
5813
|
|
|
5720
5814
|
return self._server_host_key
|
|
5721
5815
|
|
|
5816
|
+
def send_server_host_keys(self) -> None:
|
|
5817
|
+
"""Send list of available server host keys"""
|
|
5818
|
+
|
|
5819
|
+
if self._all_server_host_keys:
|
|
5820
|
+
self.logger.info('Sending server host keys')
|
|
5821
|
+
|
|
5822
|
+
keys = [String(key) for key in self._all_server_host_keys.keys()]
|
|
5823
|
+
self._send_global_request(b'hostkeys-00@openssh.com', *keys)
|
|
5824
|
+
else:
|
|
5825
|
+
self.logger.info('Sending server host keys disabled')
|
|
5826
|
+
|
|
5722
5827
|
def gss_kex_auth_supported(self) -> bool:
|
|
5723
5828
|
"""Return whether GSS key exchange authentication is supported"""
|
|
5724
5829
|
|
|
@@ -6425,6 +6530,26 @@ class SSHServerConnection(SSHConnection):
|
|
|
6425
6530
|
|
|
6426
6531
|
return chan, session
|
|
6427
6532
|
|
|
6533
|
+
def _process_hostkeys_prove_00_at_openssh_dot_com_global_request(
|
|
6534
|
+
self, packet: SSHPacket) -> None:
|
|
6535
|
+
"""Prove the server has private keys for all requested host keys"""
|
|
6536
|
+
|
|
6537
|
+
prefix = String('hostkeys-prove-00@openssh.com') + \
|
|
6538
|
+
String(self._session_id)
|
|
6539
|
+
|
|
6540
|
+
signatures = []
|
|
6541
|
+
|
|
6542
|
+
while packet:
|
|
6543
|
+
try:
|
|
6544
|
+
key_data = packet.get_string()
|
|
6545
|
+
key = self._all_server_host_keys[key_data]
|
|
6546
|
+
signatures.append(String(key.sign(prefix + String(key_data))))
|
|
6547
|
+
except (KeyError, KeyImportError):
|
|
6548
|
+
self._report_global_response(False)
|
|
6549
|
+
return
|
|
6550
|
+
|
|
6551
|
+
self._report_global_response(b''.join(signatures))
|
|
6552
|
+
|
|
6428
6553
|
async def attach_x11_listener(self, chan: SSHServerChannel[AnyStr],
|
|
6429
6554
|
auth_proto: bytes, auth_data: bytes,
|
|
6430
6555
|
screen: int) -> Optional[str]:
|
|
@@ -7019,11 +7144,13 @@ class SSHConnectionOptions(Options):
|
|
|
7019
7144
|
self.tunnel = tunnel if tunnel != () else config.get('ProxyJump')
|
|
7020
7145
|
self.passphrase = passphrase
|
|
7021
7146
|
|
|
7147
|
+
if proxy_command == ():
|
|
7148
|
+
proxy_command = cast(Optional[str], config.get('ProxyCommand'))
|
|
7149
|
+
|
|
7022
7150
|
if isinstance(proxy_command, str):
|
|
7023
|
-
proxy_command =
|
|
7151
|
+
proxy_command = split_args(proxy_command)
|
|
7024
7152
|
|
|
7025
|
-
self.proxy_command = proxy_command
|
|
7026
|
-
cast(Sequence[str], config.get('ProxyCommand'))
|
|
7153
|
+
self.proxy_command = proxy_command
|
|
7027
7154
|
|
|
7028
7155
|
self.family = cast(int, family if family != () else
|
|
7029
7156
|
config.get('AddressFamily', socket.AF_UNSPEC))
|
|
@@ -7178,6 +7305,17 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7178
7305
|
caution, as it can result in a host key mismatch
|
|
7179
7306
|
if the client trusts only a subset of the host
|
|
7180
7307
|
keys the server might return.
|
|
7308
|
+
:param server_host_keys_handler: (optional)
|
|
7309
|
+
A `callable` or coroutine handler function which if set will be
|
|
7310
|
+
called when a global request from the server is received which
|
|
7311
|
+
provides an updated list of server host keys. The handler takes
|
|
7312
|
+
four arguments (added, removed, retained, and revoked), each of
|
|
7313
|
+
which is a list of SSHKey public keys, reflecting differences
|
|
7314
|
+
between what the server reported and what is currently matching
|
|
7315
|
+
in known_hosts.
|
|
7316
|
+
|
|
7317
|
+
.. note:: This handler will only be called when known
|
|
7318
|
+
host checking is enabled and the check succeeded.
|
|
7181
7319
|
:param x509_trusted_certs: (optional)
|
|
7182
7320
|
A list of certificates which should be trusted for X.509 server
|
|
7183
7321
|
certificate authentication. If no trusted certificates are
|
|
@@ -7513,6 +7651,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7513
7651
|
:type known_hosts: *see* :ref:`SpecifyingKnownHosts`
|
|
7514
7652
|
:type host_key_alias: `str`
|
|
7515
7653
|
:type server_host_key_algs: `str` or `list` of `str`
|
|
7654
|
+
:type server_host_keys_handler: `callable` or coroutine
|
|
7516
7655
|
:type x509_trusted_certs: *see* :ref:`SpecifyingCertificates`
|
|
7517
7656
|
:type x509_trusted_cert_paths: `list` of `str`
|
|
7518
7657
|
:type x509_purposes: *see* :ref:`SpecifyingX509Purposes`
|
|
@@ -7542,7 +7681,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7542
7681
|
:type agent_identities:
|
|
7543
7682
|
*see* :ref:`SpecifyingPublicKeys` and :ref:`SpecifyingCertificates`
|
|
7544
7683
|
:type agent_forwarding: `bool`
|
|
7545
|
-
:type pkcs11_provider: `str`
|
|
7684
|
+
:type pkcs11_provider: `str` or `None`
|
|
7546
7685
|
:type pkcs11_pin: `str`
|
|
7547
7686
|
:type client_version: `str`
|
|
7548
7687
|
:type kex_algs: `str` or `list` of `str`
|
|
@@ -7583,6 +7722,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7583
7722
|
known_hosts: KnownHostsArg
|
|
7584
7723
|
host_key_alias: Optional[str]
|
|
7585
7724
|
server_host_key_algs: Union[str, Sequence[str]]
|
|
7725
|
+
server_host_keys_handler: _ServerHostKeysHandler
|
|
7586
7726
|
username: str
|
|
7587
7727
|
password: Optional[str]
|
|
7588
7728
|
client_host_keysign: Optional[str]
|
|
@@ -7602,7 +7742,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7602
7742
|
agent_path: Optional[str]
|
|
7603
7743
|
agent_identities: Optional[Sequence[bytes]]
|
|
7604
7744
|
agent_forward_path: Optional[str]
|
|
7605
|
-
pkcs11_provider: Optional[
|
|
7745
|
+
pkcs11_provider: Optional[str]
|
|
7606
7746
|
pkcs11_pin: Optional[str]
|
|
7607
7747
|
command: Optional[str]
|
|
7608
7748
|
subsystem: Optional[str]
|
|
@@ -7650,6 +7790,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7650
7790
|
known_hosts: KnownHostsArg = (),
|
|
7651
7791
|
host_key_alias: DefTuple[Optional[str]] = (),
|
|
7652
7792
|
server_host_key_algs: _AlgsArg = (),
|
|
7793
|
+
server_host_keys_handler: _ServerHostKeysHandler = None,
|
|
7653
7794
|
username: DefTuple[str] = (), password: Optional[str] = None,
|
|
7654
7795
|
client_host_keysign: DefTuple[KeySignPath] = (),
|
|
7655
7796
|
client_host_keys: Optional[_ClientKeysArg] = None,
|
|
@@ -7668,7 +7809,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7668
7809
|
agent_path: DefTuple[Optional[str]] = (),
|
|
7669
7810
|
agent_identities: DefTuple[Optional[IdentityListArg]] = (),
|
|
7670
7811
|
agent_forwarding: DefTuple[bool] = (),
|
|
7671
|
-
pkcs11_provider: DefTuple[Optional[
|
|
7812
|
+
pkcs11_provider: DefTuple[Optional[str]] = (),
|
|
7672
7813
|
pkcs11_pin: Optional[str] = None,
|
|
7673
7814
|
command: DefTuple[Optional[str]] = (),
|
|
7674
7815
|
subsystem: Optional[str] = None, env: DefTuple[_Env] = (),
|
|
@@ -7758,6 +7899,8 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7758
7899
|
_select_host_key_algs(server_host_key_algs,
|
|
7759
7900
|
cast(DefTuple[str], config.get('HostKeyAlgorithms', ())), [])
|
|
7760
7901
|
|
|
7902
|
+
self.server_host_keys_handler = server_host_keys_handler
|
|
7903
|
+
|
|
7761
7904
|
self.username = saslprep(cast(str, username if username != () else
|
|
7762
7905
|
config.get('User', local_username)))
|
|
7763
7906
|
|
|
@@ -7823,9 +7966,9 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7823
7966
|
|
|
7824
7967
|
if pkcs11_provider == ():
|
|
7825
7968
|
pkcs11_provider = \
|
|
7826
|
-
cast(Optional[
|
|
7969
|
+
cast(Optional[str], config.get('PKCS11Provider'))
|
|
7827
7970
|
|
|
7828
|
-
pkcs11_provider: Optional[
|
|
7971
|
+
pkcs11_provider: Optional[str]
|
|
7829
7972
|
|
|
7830
7973
|
if ignore_encrypted == ():
|
|
7831
7974
|
ignore_encrypted = client_keys == ()
|
|
@@ -7933,6 +8076,20 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
7933
8076
|
:param server_host_certs: (optional)
|
|
7934
8077
|
A list of optional certificates which can be paired with the
|
|
7935
8078
|
provided server host keys.
|
|
8079
|
+
:param send_server_host_keys: (optional)
|
|
8080
|
+
Whether or not to send a list of the allowed server host keys
|
|
8081
|
+
for clients to use to update their known hosts like for the
|
|
8082
|
+
server.
|
|
8083
|
+
|
|
8084
|
+
.. note:: Enabling this option will allow multiple server
|
|
8085
|
+
host keys of the same type to be configured. Only
|
|
8086
|
+
the first key of each type will be actively used
|
|
8087
|
+
during key exchange, but the others will be
|
|
8088
|
+
reported as reserved keys that clients should
|
|
8089
|
+
begin to trust, to allow for future key rotation.
|
|
8090
|
+
If this option is disabled, specifying multiple
|
|
8091
|
+
server host keys of the same type is treated as
|
|
8092
|
+
a configuration error.
|
|
7936
8093
|
:param passphrase: (optional)
|
|
7937
8094
|
The passphrase to use to decrypt server host keys if they are
|
|
7938
8095
|
encrypted, or a `callable` or coroutine which takes a filename
|
|
@@ -8174,6 +8331,7 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
8174
8331
|
:type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6`
|
|
8175
8332
|
:type server_host_keys: *see* :ref:`SpecifyingPrivateKeys`
|
|
8176
8333
|
:type server_host_certs: *see* :ref:`SpecifyingCertificates`
|
|
8334
|
+
:type send_server_host_keys: `bool`
|
|
8177
8335
|
:type passphrase: `str` or `bytes`
|
|
8178
8336
|
:type known_client_hosts: *see* :ref:`SpecifyingKnownHosts`
|
|
8179
8337
|
:type trust_client_host: `bool`
|
|
@@ -8227,6 +8385,8 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
8227
8385
|
server_factory: _ServerFactory
|
|
8228
8386
|
server_version: bytes
|
|
8229
8387
|
server_host_keys: 'OrderedDict[bytes, SSHKeyPair]'
|
|
8388
|
+
all_server_host_keys: 'OrderedDict[bytes, SSHKeyPair]'
|
|
8389
|
+
send_server_host_keys: bool
|
|
8230
8390
|
known_client_hosts: KnownHostsArg
|
|
8231
8391
|
trust_client_host: bool
|
|
8232
8392
|
authorized_client_keys: DefTuple[Optional[SSHAuthorizedKeys]]
|
|
@@ -8283,6 +8443,7 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
8283
8443
|
keepalive_count_max: DefTuple[int] = (),
|
|
8284
8444
|
server_host_keys: KeyPairListArg = (),
|
|
8285
8445
|
server_host_certs: CertListArg = (),
|
|
8446
|
+
send_server_host_keys: bool = False,
|
|
8286
8447
|
passphrase: Optional[BytesOrStr] = None,
|
|
8287
8448
|
known_client_hosts: KnownHostsArg = None,
|
|
8288
8449
|
trust_client_host: bool = False,
|
|
@@ -8354,14 +8515,21 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
8354
8515
|
server_host_certs, loop=loop)
|
|
8355
8516
|
|
|
8356
8517
|
self.server_host_keys = OrderedDict()
|
|
8518
|
+
self.all_server_host_keys = OrderedDict()
|
|
8357
8519
|
|
|
8358
8520
|
for keypair in server_keys:
|
|
8359
8521
|
for alg in keypair.host_key_algorithms:
|
|
8360
|
-
if alg in self.server_host_keys:
|
|
8361
|
-
raise ValueError('Multiple keys of type
|
|
8362
|
-
alg.decode(
|
|
8522
|
+
if alg in self.server_host_keys and not send_server_host_keys:
|
|
8523
|
+
raise ValueError('Multiple keys of type '
|
|
8524
|
+
f'{alg.decode("ascii")} found: '
|
|
8525
|
+
'Enable send_server_host_keys to '
|
|
8526
|
+
'allow reserved keys to be configured')
|
|
8527
|
+
|
|
8528
|
+
if alg not in self.server_host_keys:
|
|
8529
|
+
self.server_host_keys[alg] = keypair
|
|
8363
8530
|
|
|
8364
|
-
|
|
8531
|
+
if send_server_host_keys:
|
|
8532
|
+
self.all_server_host_keys[keypair.public_data] = keypair
|
|
8365
8533
|
|
|
8366
8534
|
self.known_client_hosts = known_client_hosts
|
|
8367
8535
|
self.trust_client_host = trust_client_host
|
|
@@ -9058,7 +9226,7 @@ async def create_server(server_factory: _ServerFactory,
|
|
|
9058
9226
|
async def get_server_host_key(
|
|
9059
9227
|
host = '', port: DefTuple[int] = (), *,
|
|
9060
9228
|
tunnel: DefTuple[_TunnelConnector] = (),
|
|
9061
|
-
proxy_command: DefTuple[
|
|
9229
|
+
proxy_command: DefTuple[_ProxyCommand] = (), family: DefTuple[int] = (),
|
|
9062
9230
|
flags: int = 0, local_addr: DefTuple[HostPort] = (),
|
|
9063
9231
|
sock: Optional[socket.socket] = None,
|
|
9064
9232
|
client_version: DefTuple[BytesOrStr] = (),
|
|
@@ -9202,7 +9370,7 @@ async def get_server_host_key(
|
|
|
9202
9370
|
async def get_server_auth_methods(
|
|
9203
9371
|
host = '', port: DefTuple[int] = (), username: DefTuple[str] = (), *,
|
|
9204
9372
|
tunnel: DefTuple[_TunnelConnector] = (),
|
|
9205
|
-
proxy_command: DefTuple[
|
|
9373
|
+
proxy_command: DefTuple[_ProxyCommand] = (), family: DefTuple[int] = (),
|
|
9206
9374
|
flags: int = 0, local_addr: DefTuple[HostPort] = (),
|
|
9207
9375
|
sock: Optional[socket.socket] = None,
|
|
9208
9376
|
client_version: DefTuple[BytesOrStr] = (),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2014-
|
|
1
|
+
# Copyright (c) 2014-2024 by Ron Frederick <ronf@timeheart.net> and others.
|
|
2
2
|
#
|
|
3
3
|
# This program and the accompanying materials are made available under
|
|
4
4
|
# the terms of the Eclipse Public License v2.0 which accompanies this
|
|
@@ -20,22 +20,23 @@
|
|
|
20
20
|
|
|
21
21
|
"""A shim around PyCA for accessing symmetric ciphers needed by AsyncSSH"""
|
|
22
22
|
|
|
23
|
+
from types import ModuleType
|
|
23
24
|
from typing import Any, MutableMapping, Optional, Tuple
|
|
24
25
|
import warnings
|
|
25
26
|
|
|
26
27
|
from cryptography.exceptions import InvalidTag
|
|
27
28
|
from cryptography.hazmat.primitives.ciphers import Cipher, CipherContext
|
|
28
29
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
29
|
-
from cryptography.hazmat.primitives.ciphers.algorithms import AES, ARC4
|
|
30
|
-
from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
|
|
31
30
|
from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
import cryptography.hazmat.primitives.ciphers.algorithms as _algs
|
|
33
|
+
|
|
34
|
+
_decrepit_algs: Optional[ModuleType]
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
try:
|
|
37
|
+
import cryptography.hazmat.decrepit.ciphers.algorithms as _decrepit_algs
|
|
38
|
+
except ImportError:
|
|
39
|
+
_decrepit_algs = None
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
_CipherAlgs = Tuple[Any, Any, int]
|
|
@@ -140,27 +141,44 @@ def get_cipher_params(cipher_name: str) -> _CipherParams:
|
|
|
140
141
|
|
|
141
142
|
|
|
142
143
|
_cipher_alg_list = (
|
|
143
|
-
('aes128-cbc', AES, CBC, 0, 16, 16, 16),
|
|
144
|
-
('aes192-cbc', AES, CBC, 0, 24, 16, 16),
|
|
145
|
-
('aes256-cbc', AES, CBC, 0, 32, 16, 16),
|
|
146
|
-
('aes128-ctr', AES, CTR, 0, 16, 16, 16),
|
|
147
|
-
('aes192-ctr', AES, CTR, 0, 24, 16, 16),
|
|
148
|
-
('aes256-ctr', AES, CTR, 0, 32, 16, 16),
|
|
149
|
-
('aes128-gcm', None,
|
|
150
|
-
('aes256-gcm', None,
|
|
151
|
-
('arcfour', ARC4, None, 0, 16, 1, 1),
|
|
152
|
-
('arcfour40', ARC4, None, 0, 5, 1, 1),
|
|
153
|
-
('arcfour128', ARC4, None, 1536, 16, 1, 1),
|
|
154
|
-
('arcfour256', ARC4, None, 1536, 32, 1, 1),
|
|
155
|
-
('blowfish-cbc', Blowfish, CBC, 0, 16, 8, 8),
|
|
156
|
-
('cast128-cbc', CAST5, CBC, 0, 16, 8, 8),
|
|
157
|
-
('des-cbc', TripleDES, CBC, 0, 8, 8, 8),
|
|
158
|
-
('des2-cbc', TripleDES, CBC, 0, 16, 8, 8),
|
|
159
|
-
('des3-cbc', TripleDES, CBC, 0, 24, 8, 8),
|
|
160
|
-
('seed-cbc', SEED, CBC, 0, 16, 16, 16)
|
|
144
|
+
('aes128-cbc', 'AES', CBC, 0, 16, 16, 16),
|
|
145
|
+
('aes192-cbc', 'AES', CBC, 0, 24, 16, 16),
|
|
146
|
+
('aes256-cbc', 'AES', CBC, 0, 32, 16, 16),
|
|
147
|
+
('aes128-ctr', 'AES', CTR, 0, 16, 16, 16),
|
|
148
|
+
('aes192-ctr', 'AES', CTR, 0, 24, 16, 16),
|
|
149
|
+
('aes256-ctr', 'AES', CTR, 0, 32, 16, 16),
|
|
150
|
+
('aes128-gcm', None, None, 0, 16, 12, 16),
|
|
151
|
+
('aes256-gcm', None, None, 0, 32, 12, 16),
|
|
152
|
+
('arcfour', 'ARC4', None, 0, 16, 1, 1),
|
|
153
|
+
('arcfour40', 'ARC4', None, 0, 5, 1, 1),
|
|
154
|
+
('arcfour128', 'ARC4', None, 1536, 16, 1, 1),
|
|
155
|
+
('arcfour256', 'ARC4', None, 1536, 32, 1, 1),
|
|
156
|
+
('blowfish-cbc', 'Blowfish', CBC, 0, 16, 8, 8),
|
|
157
|
+
('cast128-cbc', 'CAST5', CBC, 0, 16, 8, 8),
|
|
158
|
+
('des-cbc', 'TripleDES', CBC, 0, 8, 8, 8),
|
|
159
|
+
('des2-cbc', 'TripleDES', CBC, 0, 16, 8, 8),
|
|
160
|
+
('des3-cbc', 'TripleDES', CBC, 0, 24, 8, 8),
|
|
161
|
+
('seed-cbc', 'SEED', CBC, 0, 16, 16, 16)
|
|
161
162
|
)
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
with warnings.catch_warnings():
|
|
165
|
+
warnings.simplefilter('ignore')
|
|
166
|
+
|
|
167
|
+
for _cipher_name, _alg, _mode, _initial_bytes, \
|
|
168
|
+
_key_size, _iv_size, _block_size in _cipher_alg_list:
|
|
169
|
+
if _alg:
|
|
170
|
+
try:
|
|
171
|
+
_cipher = getattr(_algs, _alg)
|
|
172
|
+
except AttributeError as exc: # pragma: no cover
|
|
173
|
+
if _decrepit_algs:
|
|
174
|
+
try:
|
|
175
|
+
_cipher = getattr(_decrepit_algs, _alg)
|
|
176
|
+
except AttributeError:
|
|
177
|
+
raise exc from None
|
|
178
|
+
else:
|
|
179
|
+
raise
|
|
180
|
+
else:
|
|
181
|
+
_cipher = None
|
|
182
|
+
|
|
183
|
+
_cipher_algs[_cipher_name] = (_cipher, _mode, _initial_bytes)
|
|
184
|
+
register_cipher(_cipher_name, _key_size, _iv_size, _block_size)
|
|
@@ -404,7 +404,8 @@ def generate_x509_certificate(signing_key: PyCAKey, key: PyCAKey,
|
|
|
404
404
|
except KeyError:
|
|
405
405
|
raise ValueError('Unknown hash algorithm') from None
|
|
406
406
|
|
|
407
|
-
cert = builder.sign(cast(PyCAPrivateKey, signing_key),
|
|
407
|
+
cert = builder.sign(cast(PyCAPrivateKey, signing_key),
|
|
408
|
+
hash_alg) # type: ignore
|
|
408
409
|
data = cert.public_bytes(Encoding.DER)
|
|
409
410
|
|
|
410
411
|
return X509Certificate(cert, data)
|