asyncssh 2.22.0__tar.gz → 2.23.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.22.0/asyncssh.egg-info → asyncssh-2.23.0}/PKG-INFO +11 -7
- {asyncssh-2.22.0 → asyncssh-2.23.0}/README.rst +8 -4
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/agent.py +1 -3
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/config.py +40 -4
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/connection.py +53 -26
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/pq.py +5 -3
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/encryption.py +26 -2
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/kex.py +1 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/misc.py +1 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/scp.py +2 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/server.py +4 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/sftp.py +22 -17
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/sk.py +1 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/version.py +1 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/x11.py +11 -9
- {asyncssh-2.22.0 → asyncssh-2.23.0/asyncssh.egg-info}/PKG-INFO +11 -7
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh.egg-info/requires.txt +2 -2
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/api.rst +1 -15
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/changes.rst +40 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/pyproject.toml +1 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_config.py +21 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_connection.py +30 -2
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_forward.py +24 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_kex.py +6 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_process.py +17 -22
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tox.ini +1 -1
- {asyncssh-2.22.0 → asyncssh-2.23.0}/.coveragerc +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/.github/workflows/run_tests.yml +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/.gitignore +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/.readthedocs.yaml +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/CONTRIBUTING.rst +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/COPYRIGHT +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/LICENSE +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/MANIFEST.in +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/__init__.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/asn1.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/auth.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/channel.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/compression.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/constants.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/cipher.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/crypto/x509.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/dsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/editor.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/forward.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/gss.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/kex_dh.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/keysign.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/listener.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/logging.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/mac.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/packet.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/pattern.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/pbe.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/process.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/public_key.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/py.typed +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/rsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/saslprep.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/session.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/socks.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/stream.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/conf.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/contributing.rst +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/index.rst +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/requirements.txt +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/docs/rtd-req.txt +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/callback_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/callback_client2.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/callback_client3.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/callback_math_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/chat_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/check_exit_status.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/direct_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/direct_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/editor.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/gather_results.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/listening_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/math_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/math_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/redirect_input.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/redirect_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/reverse_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/reverse_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/scp_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/set_environment.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/set_terminal.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/sftp_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/show_environment.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/show_terminal.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/simple_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/simple_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/mypy.ini +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/pylintrc +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/setup.cfg +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/__init__.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/gss_stub.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/keysign_stub.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/server.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/sk_stub.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/sspi_stub.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_agent.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_asn1.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_auth.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_channel.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_compression.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_connection_auth.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_editor.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_encryption.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_logging.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_mac.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_packet.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_public_key.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_saslprep.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_sftp.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_sk.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_stream.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_subprocess.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_tuntap.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_x11.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/test_x509.py +0 -0
- {asyncssh-2.22.0 → asyncssh-2.23.0}/tests/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.23.0
|
|
4
4
|
Summary: AsyncSSH: Asynchronous SSHv2 client and server library
|
|
5
5
|
Author-email: Ron Frederick <ronf@timeheart.net>
|
|
6
6
|
License: EPL-2.0 OR GPL-2.0-or-later
|
|
@@ -32,10 +32,10 @@ Provides-Extra: bcrypt
|
|
|
32
32
|
Requires-Dist: bcrypt>=3.1.3; extra == "bcrypt"
|
|
33
33
|
Provides-Extra: fido2
|
|
34
34
|
Requires-Dist: fido2>=2; extra == "fido2"
|
|
35
|
+
Provides-Extra: ifaddr
|
|
36
|
+
Requires-Dist: ifaddr>=0.2.0; extra == "ifaddr"
|
|
35
37
|
Provides-Extra: gssapi
|
|
36
38
|
Requires-Dist: gssapi>=1.2.0; extra == "gssapi"
|
|
37
|
-
Provides-Extra: libnacl
|
|
38
|
-
Requires-Dist: libnacl>=1.4.2; extra == "libnacl"
|
|
39
39
|
Provides-Extra: pkcs11
|
|
40
40
|
Requires-Dist: python-pkcs11>=0.7.0; extra == "pkcs11"
|
|
41
41
|
Provides-Extra: pyopenssl
|
|
@@ -196,6 +196,9 @@ functionality:
|
|
|
196
196
|
* Install fido2 from https://pypi.org/project/fido2 if you want support
|
|
197
197
|
for key exchange and authentication with U2F/FIDO2 security keys.
|
|
198
198
|
|
|
199
|
+
* Install ifaddr from https://pypi.org/project/ifaddr/ if you want
|
|
200
|
+
support for matching on local network IP addresses.
|
|
201
|
+
|
|
199
202
|
* Install python-pkcs11 from https://pypi.org/project/python-pkcs11 if
|
|
200
203
|
you want support for accessing PIV keys on PKCS#11 security tokens.
|
|
201
204
|
|
|
@@ -221,24 +224,25 @@ easy to install any or all of these dependencies:
|
|
|
221
224
|
|
|
222
225
|
| bcrypt
|
|
223
226
|
| fido2
|
|
227
|
+
| ifaddr
|
|
224
228
|
| gssapi
|
|
225
229
|
| pkcs11
|
|
226
230
|
| pyOpenSSL
|
|
227
231
|
| pywin32
|
|
228
232
|
|
|
229
|
-
For example, to install bcrypt, fido2, gssapi, pkcs11, and pyOpenSSL
|
|
233
|
+
For example, to install bcrypt, fido2, gssapi, ifaddr, pkcs11, and pyOpenSSL
|
|
230
234
|
on UNIX, you can run:
|
|
231
235
|
|
|
232
236
|
::
|
|
233
237
|
|
|
234
|
-
pip install 'asyncssh[bcrypt,fido2,gssapi,pkcs11,pyOpenSSL]'
|
|
238
|
+
pip install 'asyncssh[bcrypt,fido2,gssapi,ifaddr,pkcs11,pyOpenSSL]'
|
|
235
239
|
|
|
236
|
-
To install bcrypt, fido2, pkcs11, pyOpenSSL, and pywin32 on Windows,
|
|
240
|
+
To install bcrypt, fido2, ifaddr, pkcs11, pyOpenSSL, and pywin32 on Windows,
|
|
237
241
|
you can run:
|
|
238
242
|
|
|
239
243
|
::
|
|
240
244
|
|
|
241
|
-
pip install 'asyncssh[bcrypt,fido2,pkcs11,pyOpenSSL,pywin32]'
|
|
245
|
+
pip install 'asyncssh[bcrypt,fido2,ifaddr,pkcs11,pyOpenSSL,pywin32]'
|
|
242
246
|
|
|
243
247
|
Note that you will still need to manually install the libnettle library
|
|
244
248
|
for UMAC support. Unfortunately, since liboqs and libnettle are not
|
|
@@ -150,6 +150,9 @@ functionality:
|
|
|
150
150
|
* Install fido2 from https://pypi.org/project/fido2 if you want support
|
|
151
151
|
for key exchange and authentication with U2F/FIDO2 security keys.
|
|
152
152
|
|
|
153
|
+
* Install ifaddr from https://pypi.org/project/ifaddr/ if you want
|
|
154
|
+
support for matching on local network IP addresses.
|
|
155
|
+
|
|
153
156
|
* Install python-pkcs11 from https://pypi.org/project/python-pkcs11 if
|
|
154
157
|
you want support for accessing PIV keys on PKCS#11 security tokens.
|
|
155
158
|
|
|
@@ -175,24 +178,25 @@ easy to install any or all of these dependencies:
|
|
|
175
178
|
|
|
176
179
|
| bcrypt
|
|
177
180
|
| fido2
|
|
181
|
+
| ifaddr
|
|
178
182
|
| gssapi
|
|
179
183
|
| pkcs11
|
|
180
184
|
| pyOpenSSL
|
|
181
185
|
| pywin32
|
|
182
186
|
|
|
183
|
-
For example, to install bcrypt, fido2, gssapi, pkcs11, and pyOpenSSL
|
|
187
|
+
For example, to install bcrypt, fido2, gssapi, ifaddr, pkcs11, and pyOpenSSL
|
|
184
188
|
on UNIX, you can run:
|
|
185
189
|
|
|
186
190
|
::
|
|
187
191
|
|
|
188
|
-
pip install 'asyncssh[bcrypt,fido2,gssapi,pkcs11,pyOpenSSL]'
|
|
192
|
+
pip install 'asyncssh[bcrypt,fido2,gssapi,ifaddr,pkcs11,pyOpenSSL]'
|
|
189
193
|
|
|
190
|
-
To install bcrypt, fido2, pkcs11, pyOpenSSL, and pywin32 on Windows,
|
|
194
|
+
To install bcrypt, fido2, ifaddr, pkcs11, pyOpenSSL, and pywin32 on Windows,
|
|
191
195
|
you can run:
|
|
192
196
|
|
|
193
197
|
::
|
|
194
198
|
|
|
195
|
-
pip install 'asyncssh[bcrypt,fido2,pkcs11,pyOpenSSL,pywin32]'
|
|
199
|
+
pip install 'asyncssh[bcrypt,fido2,ifaddr,pkcs11,pyOpenSSL,pywin32]'
|
|
196
200
|
|
|
197
201
|
Note that you will still need to manually install the libnettle library
|
|
198
202
|
for UMAC support. Unfortunately, since liboqs and libnettle are not
|
|
@@ -121,9 +121,7 @@ class SSHAgentKeyPair(SSHKeyPair):
|
|
|
121
121
|
else:
|
|
122
122
|
sig_algorithm = algorithm
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
# ssh-agent protocol flags used to request RSA SHA2 signatures yet
|
|
126
|
-
if sig_algorithm == b'ssh-rsa' and sys.platform != 'win32':
|
|
124
|
+
if sig_algorithm == b'ssh-rsa':
|
|
127
125
|
sig_algorithms: Sequence[bytes] = \
|
|
128
126
|
(b'rsa-sha2-256', b'rsa-sha2-512', b'ssh-rsa')
|
|
129
127
|
else:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2020-
|
|
1
|
+
# Copyright (c) 2020-2026 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
|
|
@@ -29,14 +29,20 @@ import subprocess
|
|
|
29
29
|
from hashlib import sha1
|
|
30
30
|
from pathlib import Path, PurePath
|
|
31
31
|
from subprocess import DEVNULL
|
|
32
|
-
from typing import Callable, Dict, List, NoReturn, Optional
|
|
33
|
-
from typing import Set, Tuple, Union, cast
|
|
32
|
+
from typing import Callable, Dict, Iterator, List, NoReturn, Optional
|
|
33
|
+
from typing import Sequence, Set, Tuple, Union, cast
|
|
34
34
|
|
|
35
35
|
from .constants import DEFAULT_PORT
|
|
36
36
|
from .logging import logger
|
|
37
|
-
from .misc import DefTuple, FilePath, ip_address
|
|
37
|
+
from .misc import DefTuple, FilePath, IllegalUserName, ip_address
|
|
38
38
|
from .pattern import HostPatternList, WildcardPatternList
|
|
39
39
|
|
|
40
|
+
try:
|
|
41
|
+
import ifaddr
|
|
42
|
+
_ifaddr_available = True
|
|
43
|
+
except ImportError: # pragma: no cover
|
|
44
|
+
_ifaddr_available = False
|
|
45
|
+
|
|
40
46
|
|
|
41
47
|
ConfigPaths = Union[None, FilePath, Sequence[FilePath]]
|
|
42
48
|
|
|
@@ -52,6 +58,18 @@ def _exec(cmd: str) -> bool:
|
|
|
52
58
|
stdout=DEVNULL, stderr=DEVNULL).returncode == 0
|
|
53
59
|
|
|
54
60
|
|
|
61
|
+
def _get_local_ips() -> Iterator[str]:
|
|
62
|
+
"""Return local IP addresses of the system"""
|
|
63
|
+
|
|
64
|
+
for adapter in ifaddr.get_adapters():
|
|
65
|
+
for ip in adapter.ips:
|
|
66
|
+
if isinstance(ip.ip, tuple):
|
|
67
|
+
addr, _, scope_id = ip.ip
|
|
68
|
+
yield f'{addr}%{scope_id}' if scope_id else addr
|
|
69
|
+
else:
|
|
70
|
+
yield ip.ip
|
|
71
|
+
|
|
72
|
+
|
|
55
73
|
class ConfigParseError(ValueError):
|
|
56
74
|
"""Configuration parsing exception"""
|
|
57
75
|
|
|
@@ -183,6 +201,10 @@ class SSHConfig:
|
|
|
183
201
|
elif match == 'final':
|
|
184
202
|
result = cast(bool, self._final)
|
|
185
203
|
else:
|
|
204
|
+
if (match == 'localnetwork' and
|
|
205
|
+
not _ifaddr_available): # pragma: no cover
|
|
206
|
+
self._error('Local network match requires ifaddr module')
|
|
207
|
+
|
|
186
208
|
match_val = self._match_val(match)
|
|
187
209
|
|
|
188
210
|
if match != 'exec' and match_val is None:
|
|
@@ -201,6 +223,15 @@ class SSHConfig:
|
|
|
201
223
|
ip = ip_address(cast(str, match_val)) \
|
|
202
224
|
if match_val else None
|
|
203
225
|
result = host_pat.matches(None, match_val, ip)
|
|
226
|
+
elif match == 'localnetwork':
|
|
227
|
+
host_pat = HostPatternList(arg)
|
|
228
|
+
|
|
229
|
+
for addr in cast(Iterator[str], match_val):
|
|
230
|
+
if host_pat.matches(None, addr, ip_address(addr)):
|
|
231
|
+
result = True
|
|
232
|
+
break
|
|
233
|
+
else:
|
|
234
|
+
result = False
|
|
204
235
|
else:
|
|
205
236
|
wild_pat = WildcardPatternList(arg)
|
|
206
237
|
result = wild_pat.matches(match_val)
|
|
@@ -514,6 +545,8 @@ class SSHClientConfig(SSHConfig):
|
|
|
514
545
|
return self._options.get('Hostname', self._orig_host)
|
|
515
546
|
elif match == 'originalhost':
|
|
516
547
|
return self._orig_host
|
|
548
|
+
elif match == 'localnetwork':
|
|
549
|
+
return _get_local_ips()
|
|
517
550
|
elif match == 'localuser':
|
|
518
551
|
return self._local_user
|
|
519
552
|
elif match == 'user':
|
|
@@ -679,6 +712,9 @@ class SSHServerConfig(SSHConfig):
|
|
|
679
712
|
def _set_tokens(self) -> None:
|
|
680
713
|
"""Set the tokens available for percent expansion"""
|
|
681
714
|
|
|
715
|
+
if self._user == '..' or '/' in self._user or '\\' in self._user:
|
|
716
|
+
raise IllegalUserName('Unsafe username substitution')
|
|
717
|
+
|
|
682
718
|
self._tokens.update({'u': self._user})
|
|
683
719
|
|
|
684
720
|
_handlers = {option.lower(): (option, handler) for option, handler in (
|
|
@@ -82,6 +82,7 @@ from .constants import OPEN_UNKNOWN_CHANNEL_TYPE
|
|
|
82
82
|
|
|
83
83
|
from .encryption import Encryption, get_encryption_algs
|
|
84
84
|
from .encryption import get_default_encryption_algs
|
|
85
|
+
from .encryption import encryption_needs_mac
|
|
85
86
|
from .encryption import get_encryption_params, get_encryption
|
|
86
87
|
|
|
87
88
|
from .forward import SSHForwarder
|
|
@@ -196,6 +197,9 @@ class _TunnelProtocol(Protocol):
|
|
|
196
197
|
def close(self) -> None:
|
|
197
198
|
"""Close this tunnel"""
|
|
198
199
|
|
|
200
|
+
async def wait_closed(self):
|
|
201
|
+
"""Wait for this tunnel to close"""
|
|
202
|
+
|
|
199
203
|
class _TunnelConnectorProtocol(_TunnelProtocol, Protocol):
|
|
200
204
|
"""Protocol to open a connection to tunnel an SSH connection over"""
|
|
201
205
|
|
|
@@ -279,7 +283,7 @@ async def _canonicalize_host(loop: asyncio.AbstractEventLoop,
|
|
|
279
283
|
options: 'SSHConnectionOptions') -> Optional[str]:
|
|
280
284
|
"""Canonicalize a host name"""
|
|
281
285
|
|
|
282
|
-
host = options.
|
|
286
|
+
host = options.orig_host
|
|
283
287
|
|
|
284
288
|
if not options.canonicalize_hostname or not options.canonical_domains:
|
|
285
289
|
logger.info('Host canonicalization disabled')
|
|
@@ -387,6 +391,11 @@ async def _open_proxy(
|
|
|
387
391
|
|
|
388
392
|
self._conn.connection_lost(exc)
|
|
389
393
|
|
|
394
|
+
def process_exited(self):
|
|
395
|
+
"""Called when the child process has exited"""
|
|
396
|
+
|
|
397
|
+
self._close_event.set()
|
|
398
|
+
|
|
390
399
|
def write(self, data: bytes) -> None:
|
|
391
400
|
"""Write data to this tunnel"""
|
|
392
401
|
|
|
@@ -403,13 +412,20 @@ async def _open_proxy(
|
|
|
403
412
|
|
|
404
413
|
if self._transport: # pragma: no cover
|
|
405
414
|
self._transport.close()
|
|
415
|
+
self._transport = None
|
|
406
416
|
|
|
407
|
-
|
|
417
|
+
async def wait_closed(self):
|
|
418
|
+
"""Wait for this subprocess to exit"""
|
|
419
|
+
|
|
420
|
+
await self._close_event.wait()
|
|
408
421
|
|
|
422
|
+
_, tunnel = await loop.subprocess_exec(_ProxyCommandTunnel, *command,
|
|
423
|
+
start_new_session=True)
|
|
409
424
|
|
|
410
|
-
|
|
425
|
+
conn = cast(_Conn, cast(_ProxyCommandTunnel, tunnel).get_conn())
|
|
426
|
+
conn.set_tunnel(tunnel)
|
|
411
427
|
|
|
412
|
-
return
|
|
428
|
+
return conn
|
|
413
429
|
|
|
414
430
|
|
|
415
431
|
async def _open_tunnel(tunnels: object, options: _Options,
|
|
@@ -437,8 +453,8 @@ async def _open_tunnel(tunnels: object, options: _Options,
|
|
|
437
453
|
|
|
438
454
|
last_conn = conn
|
|
439
455
|
conn = await connect(host, port, username=username,
|
|
440
|
-
passphrase=options.passphrase,
|
|
441
|
-
config=config)
|
|
456
|
+
passphrase=options.passphrase,
|
|
457
|
+
tunnel=conn or (), config=config)
|
|
442
458
|
conn.set_tunnel(last_conn)
|
|
443
459
|
|
|
444
460
|
if options.canonicalize_hostname != 'always':
|
|
@@ -459,7 +475,7 @@ async def _connect(options: _Options, config: DefTuple[ConfigPaths],
|
|
|
459
475
|
|
|
460
476
|
canon_host = await _canonicalize_host(loop, options)
|
|
461
477
|
|
|
462
|
-
host = canon_host if canon_host else options.
|
|
478
|
+
host = canon_host if canon_host else options.orig_host
|
|
463
479
|
canonical = bool(canon_host)
|
|
464
480
|
final = options.config.has_match_final()
|
|
465
481
|
|
|
@@ -651,7 +667,8 @@ def _expand_algs(alg_type: str, algs: str,
|
|
|
651
667
|
|
|
652
668
|
def _select_algs(alg_type: str, algs: _AlgsArg, config_algs: _AlgsArg,
|
|
653
669
|
possible_algs: List[bytes], default_algs: List[bytes],
|
|
654
|
-
none_value: Optional[bytes] = None
|
|
670
|
+
none_value: Optional[bytes] = None,
|
|
671
|
+
allow_empty: bool = False) -> Sequence[bytes]:
|
|
655
672
|
"""Select a set of allowed algorithms"""
|
|
656
673
|
|
|
657
674
|
if algs == ():
|
|
@@ -682,6 +699,8 @@ def _select_algs(alg_type: str, algs: _AlgsArg, config_algs: _AlgsArg,
|
|
|
682
699
|
return result
|
|
683
700
|
elif none_value:
|
|
684
701
|
return [none_value]
|
|
702
|
+
elif allow_empty:
|
|
703
|
+
return []
|
|
685
704
|
else:
|
|
686
705
|
raise ValueError(f'No {alg_type} algorithms selected')
|
|
687
706
|
|
|
@@ -714,7 +733,7 @@ def _validate_algs(config: SSHConfig, kex_algs_arg: _AlgsArg,
|
|
|
714
733
|
get_default_encryption_algs())
|
|
715
734
|
mac_algs = _select_algs('MAC', mac_algs_arg,
|
|
716
735
|
cast(_AlgsArg, config.get('MACs', ())),
|
|
717
|
-
get_mac_algs(), get_default_mac_algs())
|
|
736
|
+
get_mac_algs(), get_default_mac_algs(), None, True)
|
|
718
737
|
cmp_algs = _select_algs('compression', cmp_algs_arg,
|
|
719
738
|
cast(_AlgsArg, config.get_compression_algs()),
|
|
720
739
|
get_compression_algs(),
|
|
@@ -1090,15 +1109,15 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1090
1109
|
|
|
1091
1110
|
self._owner = None
|
|
1092
1111
|
|
|
1112
|
+
if self._tunnel:
|
|
1113
|
+
self._tunnel.close()
|
|
1114
|
+
self._tunnel = None
|
|
1115
|
+
|
|
1093
1116
|
self._cancel_login_timer()
|
|
1094
1117
|
self._close_event.set()
|
|
1095
1118
|
|
|
1096
1119
|
self._inpbuf = b''
|
|
1097
1120
|
|
|
1098
|
-
if self._tunnel:
|
|
1099
|
-
self._tunnel.close()
|
|
1100
|
-
self._tunnel = None
|
|
1101
|
-
|
|
1102
1121
|
def _cancel_login_timer(self) -> None:
|
|
1103
1122
|
"""Cancel the login timer"""
|
|
1104
1123
|
|
|
@@ -1489,8 +1508,8 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1489
1508
|
|
|
1490
1509
|
raise KeyExchangeFailed(
|
|
1491
1510
|
f'No matching {alg_type} algorithm found, sent '
|
|
1492
|
-
f'{b",".join(local_algs).decode("ascii")} and received '
|
|
1493
|
-
f'{b",".join(remote_algs).decode("ascii")}')
|
|
1511
|
+
f'{b",".join(local_algs).decode("ascii") or "<None>"} and received '
|
|
1512
|
+
f'{b",".join(remote_algs).decode("ascii") or "<None>"}')
|
|
1494
1513
|
|
|
1495
1514
|
def _get_extra_kex_algs(self) -> List[bytes]:
|
|
1496
1515
|
"""Return the extra kex algs to add"""
|
|
@@ -1852,7 +1871,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1852
1871
|
self.logger.debug2(' Key exchange algs: %s', kex_algs)
|
|
1853
1872
|
self.logger.debug2(' Host key algs: %s', host_key_algs)
|
|
1854
1873
|
self.logger.debug2(' Encryption algs: %s', self._enc_algs)
|
|
1855
|
-
self.logger.debug2(' MAC algs: %s', self._mac_algs)
|
|
1874
|
+
self.logger.debug2(' MAC algs: %s', self._mac_algs or '<None>')
|
|
1856
1875
|
self.logger.debug2(' Compression algs: %s', self._cmp_algs)
|
|
1857
1876
|
|
|
1858
1877
|
cookie = os.urandom(16)
|
|
@@ -1905,12 +1924,6 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1905
1924
|
mac_keysize_sc, mac_hashsize_sc, etm_sc = \
|
|
1906
1925
|
get_encryption_params(self._enc_alg_sc, self._mac_alg_sc)
|
|
1907
1926
|
|
|
1908
|
-
if mac_keysize_cs == 0:
|
|
1909
|
-
self._mac_alg_cs = self._enc_alg_cs
|
|
1910
|
-
|
|
1911
|
-
if mac_keysize_sc == 0:
|
|
1912
|
-
self._mac_alg_sc = self._enc_alg_sc
|
|
1913
|
-
|
|
1914
1927
|
cmp_after_auth_cs = get_compression_params(self._cmp_alg_cs)
|
|
1915
1928
|
cmp_after_auth_sc = get_compression_params(self._cmp_alg_sc)
|
|
1916
1929
|
|
|
@@ -2406,11 +2419,11 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2406
2419
|
self.logger.debug2(' Host key algs: %s', peer_host_key_algs)
|
|
2407
2420
|
self.logger.debug2(' Client to server:')
|
|
2408
2421
|
self.logger.debug2(' Encryption algs: %s', enc_algs_cs)
|
|
2409
|
-
self.logger.debug2(' MAC algs: %s', mac_algs_cs)
|
|
2422
|
+
self.logger.debug2(' MAC algs: %s', mac_algs_cs or '<None>')
|
|
2410
2423
|
self.logger.debug2(' Compression algs: %s', cmp_algs_cs)
|
|
2411
2424
|
self.logger.debug2(' Server to client:')
|
|
2412
2425
|
self.logger.debug2(' Encryption algs: %s', enc_algs_sc)
|
|
2413
|
-
self.logger.debug2(' MAC algs: %s', mac_algs_sc)
|
|
2426
|
+
self.logger.debug2(' MAC algs: %s', mac_algs_sc or '<None>')
|
|
2414
2427
|
self.logger.debug2(' Compression algs: %s', cmp_algs_sc)
|
|
2415
2428
|
|
|
2416
2429
|
kex_alg = self._choose_alg('key exchange', kex_algs, peer_kex_algs)
|
|
@@ -2431,8 +2444,17 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2431
2444
|
self._enc_alg_sc = self._choose_alg('encryption', self._enc_algs,
|
|
2432
2445
|
enc_algs_sc)
|
|
2433
2446
|
|
|
2434
|
-
|
|
2435
|
-
|
|
2447
|
+
if encryption_needs_mac(self._enc_alg_cs):
|
|
2448
|
+
self._mac_alg_cs = self._choose_alg('MAC', self._mac_algs,
|
|
2449
|
+
mac_algs_cs)
|
|
2450
|
+
else:
|
|
2451
|
+
self._mac_alg_cs = self._enc_alg_cs
|
|
2452
|
+
|
|
2453
|
+
if encryption_needs_mac(self._enc_alg_sc):
|
|
2454
|
+
self._mac_alg_sc = self._choose_alg('MAC', self._mac_algs,
|
|
2455
|
+
mac_algs_sc)
|
|
2456
|
+
else:
|
|
2457
|
+
self._mac_alg_sc = self._enc_alg_sc
|
|
2436
2458
|
|
|
2437
2459
|
self._cmp_alg_cs = self._choose_alg('compression', self._cmp_algs,
|
|
2438
2460
|
cmp_algs_cs)
|
|
@@ -2851,6 +2873,9 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2851
2873
|
if self._agent:
|
|
2852
2874
|
await self._agent.wait_closed()
|
|
2853
2875
|
|
|
2876
|
+
if self._tunnel:
|
|
2877
|
+
await self._tunnel.wait_closed()
|
|
2878
|
+
|
|
2854
2879
|
await self._close_event.wait()
|
|
2855
2880
|
|
|
2856
2881
|
def disconnect(self, code: int, reason: str,
|
|
@@ -7277,6 +7302,7 @@ class SSHConnectionOptions(Options, Generic[_Options]):
|
|
|
7277
7302
|
waiter: Optional[asyncio.Future]
|
|
7278
7303
|
protocol_factory: _ProtocolFactory
|
|
7279
7304
|
version: bytes
|
|
7305
|
+
orig_host: str
|
|
7280
7306
|
host: str
|
|
7281
7307
|
port: int
|
|
7282
7308
|
tunnel: object
|
|
@@ -7369,6 +7395,7 @@ class SSHConnectionOptions(Options, Generic[_Options]):
|
|
|
7369
7395
|
self.protocol_factory = protocol_factory
|
|
7370
7396
|
self.version = _validate_version(version)
|
|
7371
7397
|
|
|
7398
|
+
self.orig_host = host
|
|
7372
7399
|
self.host = cast(str, config.get('Hostname', host))
|
|
7373
7400
|
self.port = cast(int, port if port != () else
|
|
7374
7401
|
config.get('Port', DEFAULT_PORT))
|
|
@@ -58,10 +58,12 @@ class PQDH:
|
|
|
58
58
|
self.pubkey_bytes, self.privkey_bytes, \
|
|
59
59
|
self.ciphertext_bytes, self.secret_bytes, \
|
|
60
60
|
oqs_name = _pq_algs[alg_name]
|
|
61
|
-
except KeyError:
|
|
62
|
-
raise ValueError(
|
|
61
|
+
except KeyError:
|
|
62
|
+
raise ValueError('Unknown PQ algorithm ' +
|
|
63
|
+
alg_name.decode()) from None
|
|
63
64
|
|
|
64
|
-
if not hasattr(_oqs, 'OQS_' + oqs_name +
|
|
65
|
+
if not hasattr(_oqs, 'OQS_' + oqs_name + # pragma: no cover
|
|
66
|
+
'_keypair'):
|
|
65
67
|
oqs_name += '_ipd'
|
|
66
68
|
|
|
67
69
|
self._keypair = getattr(_oqs, 'OQS_' + oqs_name + '_keypair')
|
|
@@ -46,6 +46,12 @@ class Encryption:
|
|
|
46
46
|
|
|
47
47
|
raise NotImplementedError
|
|
48
48
|
|
|
49
|
+
@classmethod
|
|
50
|
+
def needs_mac(cls) -> bool:
|
|
51
|
+
"""Return whether a MAC algorithm is needed for this encryption"""
|
|
52
|
+
|
|
53
|
+
return True
|
|
54
|
+
|
|
49
55
|
@classmethod
|
|
50
56
|
def get_mac_params(cls, mac_alg: bytes) -> Tuple[int, int, bool]:
|
|
51
57
|
"""Get parameters of the MAC algorithm used with this encryption"""
|
|
@@ -161,6 +167,12 @@ class GCMEncryption(Encryption):
|
|
|
161
167
|
|
|
162
168
|
return cls(GCMCipher(cipher_name, key, iv))
|
|
163
169
|
|
|
170
|
+
@classmethod
|
|
171
|
+
def needs_mac(cls) -> bool:
|
|
172
|
+
"""GCM encryption doesn't need an external MAC algorithm"""
|
|
173
|
+
|
|
174
|
+
return False
|
|
175
|
+
|
|
164
176
|
@classmethod
|
|
165
177
|
def get_mac_params(cls, mac_alg: bytes) -> Tuple[int, int, bool]:
|
|
166
178
|
"""Get parameters of the MAC algorithm used with this encryption"""
|
|
@@ -200,6 +212,12 @@ class ChachaEncryption(Encryption):
|
|
|
200
212
|
|
|
201
213
|
return cls(ChachaCipher(key))
|
|
202
214
|
|
|
215
|
+
@classmethod
|
|
216
|
+
def needs_mac(cls) -> bool:
|
|
217
|
+
"""Chacha20 encryption doesn't need an external MAC algorithm"""
|
|
218
|
+
|
|
219
|
+
return False
|
|
220
|
+
|
|
203
221
|
@classmethod
|
|
204
222
|
def get_mac_params(cls, mac_alg: bytes) -> Tuple[int, int, bool]:
|
|
205
223
|
"""Get parameters of the MAC algorithm used with this encryption"""
|
|
@@ -258,8 +276,14 @@ def get_default_encryption_algs() -> List[bytes]:
|
|
|
258
276
|
return _default_enc_algs
|
|
259
277
|
|
|
260
278
|
|
|
261
|
-
def
|
|
262
|
-
|
|
279
|
+
def encryption_needs_mac(enc_alg: bytes) -> bool:
|
|
280
|
+
"""Return whether an encryption algorithm needs a MAC algorithm"""
|
|
281
|
+
|
|
282
|
+
encryption, _ = _enc_params[enc_alg]
|
|
283
|
+
return encryption.needs_mac()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def get_encryption_params(enc_alg: bytes, mac_alg: bytes = b'') -> _EncParams:
|
|
263
287
|
"""Get parameters of an encryption and MAC algorithm"""
|
|
264
288
|
|
|
265
289
|
encryption, cipher_name = _enc_params[enc_alg]
|
|
@@ -346,7 +346,8 @@ class _SCPHandler:
|
|
|
346
346
|
|
|
347
347
|
if isinstance(exc, SFTPError):
|
|
348
348
|
reason = exc.reason.encode('utf-8')
|
|
349
|
-
elif isinstance(exc, OSError)
|
|
349
|
+
elif isinstance(exc, OSError) and \
|
|
350
|
+
exc.strerror: # pragma: no branch (win32)
|
|
350
351
|
reason = exc.strerror.encode('utf-8')
|
|
351
352
|
|
|
352
353
|
filename = cast(BytesOrStr, exc.filename)
|
|
@@ -854,6 +854,10 @@ class SSHServer:
|
|
|
854
854
|
* An :class:`SSHListener` object
|
|
855
855
|
* `True` to set up standard port forwarding
|
|
856
856
|
* `False` to reject the request
|
|
857
|
+
* A callable to use as an accept handler, taking
|
|
858
|
+
arguments of the original host and port of the
|
|
859
|
+
client and returning a boolean to indicate
|
|
860
|
+
whether or not to allow connection forwarding.
|
|
857
861
|
* A coroutine object which returns one of the above
|
|
858
862
|
|
|
859
863
|
"""
|
|
@@ -145,6 +145,7 @@ if sys.platform == 'win32': # pragma: no cover
|
|
|
145
145
|
else:
|
|
146
146
|
_LocalPath = bytes
|
|
147
147
|
|
|
148
|
+
_SFTPExtensions = Sequence[Tuple[bytes, bytes]]
|
|
148
149
|
_SFTPFileObj = IO[bytes]
|
|
149
150
|
_SFTPPath = Union[bytes, FilePath]
|
|
150
151
|
_SFTPPaths = Union[_SFTPPath, Sequence[_SFTPPath]]
|
|
@@ -1718,7 +1719,7 @@ class SFTPAttrs(Record):
|
|
|
1718
1719
|
untrans_name: Optional[bytes]
|
|
1719
1720
|
extended: Sequence[Tuple[bytes, bytes]] = ()
|
|
1720
1721
|
|
|
1721
|
-
def _format_ns(self, k: str):
|
|
1722
|
+
def _format_ns(self, k: str) -> str:
|
|
1722
1723
|
"""Convert epoch seconds & nanoseconds to a string date & time"""
|
|
1723
1724
|
|
|
1724
1725
|
result = time.ctime(getattr(self, k))
|
|
@@ -2241,7 +2242,7 @@ class SFTPRanges(Record):
|
|
|
2241
2242
|
class SFTPGlob:
|
|
2242
2243
|
"""SFTP glob matcher"""
|
|
2243
2244
|
|
|
2244
|
-
def __init__(self, fs: _SFTPGlobProtocol, multiple=False):
|
|
2245
|
+
def __init__(self, fs: _SFTPGlobProtocol, multiple: bool = False):
|
|
2245
2246
|
self._fs = fs
|
|
2246
2247
|
self._multiple = multiple
|
|
2247
2248
|
self._prev_matches: Set[bytes] = set()
|
|
@@ -2280,7 +2281,7 @@ class SFTPGlob:
|
|
|
2280
2281
|
|
|
2281
2282
|
return path, patlist
|
|
2282
2283
|
|
|
2283
|
-
def _report_match(self, path, attrs):
|
|
2284
|
+
def _report_match(self, path: bytes, attrs: SFTPAttrs) -> None:
|
|
2284
2285
|
"""Report a matching name"""
|
|
2285
2286
|
|
|
2286
2287
|
self._matched = True
|
|
@@ -2293,7 +2294,7 @@ class SFTPGlob:
|
|
|
2293
2294
|
|
|
2294
2295
|
self._new_matches.append(SFTPName(path, attrs=attrs))
|
|
2295
2296
|
|
|
2296
|
-
async def _stat(self, path) -> Optional[SFTPAttrs]:
|
|
2297
|
+
async def _stat(self, path: bytes) -> Optional[SFTPAttrs]:
|
|
2297
2298
|
"""Cache results of calls to stat"""
|
|
2298
2299
|
|
|
2299
2300
|
try:
|
|
@@ -2309,7 +2310,7 @@ class SFTPGlob:
|
|
|
2309
2310
|
self._stat_cache[path] = attrs
|
|
2310
2311
|
return attrs
|
|
2311
2312
|
|
|
2312
|
-
async def _scandir(self, path) -> AsyncIterator[SFTPName]:
|
|
2313
|
+
async def _scandir(self, path: bytes) -> AsyncIterator[SFTPName]:
|
|
2313
2314
|
"""Cache results of calls to scandir"""
|
|
2314
2315
|
|
|
2315
2316
|
try:
|
|
@@ -2392,7 +2393,7 @@ class SFTPGlob:
|
|
|
2392
2393
|
|
|
2393
2394
|
async def match(self, pattern: bytes,
|
|
2394
2395
|
error_handler: SFTPErrorHandler = None,
|
|
2395
|
-
sftp_version = MIN_SFTP_VERSION) -> Sequence[SFTPName]:
|
|
2396
|
+
sftp_version: int = MIN_SFTP_VERSION) -> Sequence[SFTPName]:
|
|
2396
2397
|
"""Match against a glob pattern"""
|
|
2397
2398
|
|
|
2398
2399
|
self._new_matches = []
|
|
@@ -2476,7 +2477,7 @@ class SFTPHandler(SSHPacketLogger):
|
|
|
2476
2477
|
self._reader = None
|
|
2477
2478
|
self._writer = None
|
|
2478
2479
|
|
|
2479
|
-
def _log_extensions(self, extensions:
|
|
2480
|
+
def _log_extensions(self, extensions: _SFTPExtensions) -> None:
|
|
2480
2481
|
"""Dump a formatted list of extensions to the debug log"""
|
|
2481
2482
|
|
|
2482
2483
|
for name, data in extensions:
|
|
@@ -3628,7 +3629,7 @@ class SFTPClientFile:
|
|
|
3628
3629
|
|
|
3629
3630
|
return self._offset
|
|
3630
3631
|
|
|
3631
|
-
async def stat(self, flags = FILEXFER_ATTR_DEFINED_V4) -> SFTPAttrs:
|
|
3632
|
+
async def stat(self, flags: int = FILEXFER_ATTR_DEFINED_V4) -> SFTPAttrs:
|
|
3632
3633
|
"""Return file attributes of the remote file
|
|
3633
3634
|
|
|
3634
3635
|
This method queries file attributes of the currently open file.
|
|
@@ -5737,6 +5738,14 @@ class SFTPClient:
|
|
|
5737
5738
|
else:
|
|
5738
5739
|
raise
|
|
5739
5740
|
|
|
5741
|
+
names[0].filename = self.decode(cast(bytes, names[0].filename),
|
|
5742
|
+
isinstance(path, (str, PurePath)))
|
|
5743
|
+
|
|
5744
|
+
if names[0].longname is not None:
|
|
5745
|
+
names[0].longname = \
|
|
5746
|
+
self.decode(cast(bytes, names[0].longname),
|
|
5747
|
+
isinstance(path, (str, PurePath)))
|
|
5748
|
+
|
|
5740
5749
|
return names[0]
|
|
5741
5750
|
else:
|
|
5742
5751
|
return self.decode(cast(bytes, names[0].filename),
|
|
@@ -7289,10 +7298,8 @@ class SFTPServer:
|
|
|
7289
7298
|
if pflags & FXF_EXCL:
|
|
7290
7299
|
flags |= os.O_EXCL
|
|
7291
7300
|
|
|
7292
|
-
|
|
7293
|
-
flags |= os.O_BINARY
|
|
7294
|
-
except AttributeError: # pragma: no cover
|
|
7295
|
-
pass
|
|
7301
|
+
if sys.platform == 'win32': # pragma: no cover
|
|
7302
|
+
flags |= os.O_BINARY # pylint: disable=no-member
|
|
7296
7303
|
|
|
7297
7304
|
perms = 0o666 if attrs.permissions is None else attrs.permissions
|
|
7298
7305
|
|
|
@@ -7392,10 +7399,8 @@ class SFTPServer:
|
|
|
7392
7399
|
desired_access & ACE4_WRITE_DATA:
|
|
7393
7400
|
mode += '+'
|
|
7394
7401
|
|
|
7395
|
-
|
|
7396
|
-
open_flags |= os.O_BINARY
|
|
7397
|
-
except AttributeError: # pragma: no cover
|
|
7398
|
-
pass
|
|
7402
|
+
if sys.platform == 'win32': # pragma: no cover
|
|
7403
|
+
open_flags |= os.O_BINARY # pylint: disable=no-member
|
|
7399
7404
|
|
|
7400
7405
|
perms = 0o666 if attrs.permissions is None else attrs.permissions
|
|
7401
7406
|
|
|
@@ -7604,7 +7609,7 @@ class SFTPServer:
|
|
|
7604
7609
|
# information.
|
|
7605
7610
|
|
|
7606
7611
|
# pylint: disable=no-member
|
|
7607
|
-
listdir_result = self.listdir(path)
|
|
7612
|
+
listdir_result = self.listdir(path)
|
|
7608
7613
|
|
|
7609
7614
|
if inspect.isawaitable(listdir_result):
|
|
7610
7615
|
listdir_result = await cast(
|
|
@@ -359,6 +359,6 @@ try: # pragma: no cover
|
|
|
359
359
|
sk_use_webauthn = WindowsClient.is_available() and \
|
|
360
360
|
hasattr(ctypes, 'windll') and \
|
|
361
361
|
not ctypes.windll.shell32.IsUserAnAdmin()
|
|
362
|
-
except ImportError:
|
|
362
|
+
except (AttributeError, ImportError):
|
|
363
363
|
WindowsClient = None # type: ignore
|
|
364
364
|
sk_use_webauthn = False
|