asyncssh 2.21.0__tar.gz → 2.21.1__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.21.0 → asyncssh-2.21.1}/.github/workflows/run_tests.yml +12 -1
- {asyncssh-2.21.0/asyncssh.egg-info → asyncssh-2.21.1}/PKG-INFO +2 -2
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/connection.py +7 -5
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/forward.py +2 -1
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/misc.py +3 -3
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/public_key.py +147 -97
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/scp.py +8 -3
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/sftp.py +7 -2
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/version.py +1 -1
- {asyncssh-2.21.0 → asyncssh-2.21.1/asyncssh.egg-info}/PKG-INFO +2 -2
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh.egg-info/requires.txt +1 -1
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/changes.rst +21 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/pyproject.toml +1 -1
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_agent.py +3 -4
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_connection_auth.py +5 -5
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_public_key.py +5 -16
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_sftp.py +3 -2
- {asyncssh-2.21.0 → asyncssh-2.21.1}/.coveragerc +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/.gitignore +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/.readthedocs.yaml +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/CONTRIBUTING.rst +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/COPYRIGHT +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/LICENSE +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/MANIFEST.in +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/README.rst +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/__init__.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/agent.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/asn1.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/auth.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/channel.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/compression.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/config.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/constants.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/cipher.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/pq.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/crypto/x509.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/dsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/editor.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/encryption.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/gss.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/kex.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/kex_dh.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/keysign.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/listener.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/logging.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/mac.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/packet.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/pattern.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/pbe.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/process.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/py.typed +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/rsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/saslprep.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/session.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/sk.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/socks.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/stream.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh/x11.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/api.rst +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/conf.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/contributing.rst +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/index.rst +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/requirements.txt +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/docs/rtd-req.txt +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/callback_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/callback_client2.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/callback_client3.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/callback_math_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/chat_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/check_exit_status.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/direct_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/direct_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/editor.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/gather_results.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/listening_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/math_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/math_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/redirect_input.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/redirect_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/reverse_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/reverse_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/scp_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/set_environment.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/set_terminal.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/sftp_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/show_environment.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/show_terminal.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/simple_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/simple_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/mypy.ini +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/pylintrc +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/setup.cfg +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/__init__.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/gss_stub.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/keysign_stub.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/server.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/sk_stub.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/sspi_stub.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_asn1.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_auth.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_channel.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_compression.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_config.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_connection.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_editor.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_encryption.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_forward.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_kex.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_logging.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_mac.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_packet.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_process.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_saslprep.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_sk.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_stream.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_subprocess.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_tuntap.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_x11.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/test_x509.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tests/util.py +0 -0
- {asyncssh-2.21.0 → asyncssh-2.21.1}/tox.ini +0 -0
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
name: Run tests
|
|
2
2
|
on: [push, pull_request]
|
|
3
3
|
|
|
4
|
+
permissions:
|
|
5
|
+
contents: read
|
|
6
|
+
|
|
4
7
|
jobs:
|
|
5
8
|
run-tests:
|
|
6
9
|
name: Run tests
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
actions: write
|
|
7
13
|
strategy:
|
|
8
14
|
fail-fast: false
|
|
9
15
|
matrix:
|
|
10
|
-
os: [ubuntu-latest, macos-latest, windows-
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-2022]
|
|
11
17
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
12
18
|
include:
|
|
13
19
|
- os: macos-latest
|
|
@@ -124,6 +130,8 @@ jobs:
|
|
|
124
130
|
runs-on: ubuntu-latest
|
|
125
131
|
needs: run-tests
|
|
126
132
|
if: ${{ always() }}
|
|
133
|
+
permissions:
|
|
134
|
+
actions: write
|
|
127
135
|
steps:
|
|
128
136
|
- name: Merge coverage
|
|
129
137
|
uses: actions/upload-artifact/merge@v4
|
|
@@ -137,6 +145,9 @@ jobs:
|
|
|
137
145
|
runs-on: ubuntu-latest
|
|
138
146
|
needs: merge-coverage
|
|
139
147
|
if: ${{ always() }}
|
|
148
|
+
permissions:
|
|
149
|
+
contents: read
|
|
150
|
+
actions: read
|
|
140
151
|
steps:
|
|
141
152
|
- uses: actions/checkout@v4
|
|
142
153
|
- uses: actions/setup-python@v5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.21.
|
|
3
|
+
Version: 2.21.1
|
|
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,7 +32,7 @@ Requires-Dist: typing_extensions>=4.0.0
|
|
|
32
32
|
Provides-Extra: bcrypt
|
|
33
33
|
Requires-Dist: bcrypt>=3.1.3; extra == "bcrypt"
|
|
34
34
|
Provides-Extra: fido2
|
|
35
|
-
Requires-Dist: fido2
|
|
35
|
+
Requires-Dist: fido2<2,>=0.9.2; extra == "fido2"
|
|
36
36
|
Provides-Extra: gssapi
|
|
37
37
|
Requires-Dist: gssapi>=1.2.0; extra == "gssapi"
|
|
38
38
|
Provides-Extra: libnacl
|
|
@@ -4169,7 +4169,7 @@ class SSHClientConnection(SSHConnection):
|
|
|
4169
4169
|
async def create_session(self, session_factory: SSHClientSessionFactory,
|
|
4170
4170
|
command: DefTuple[Optional[str]] = (), *,
|
|
4171
4171
|
subsystem: DefTuple[Optional[str]]= (),
|
|
4172
|
-
env: DefTuple[Env] = (),
|
|
4172
|
+
env: DefTuple[Optional[Env]] = (),
|
|
4173
4173
|
send_env: DefTuple[Optional[EnvSeq]] = (),
|
|
4174
4174
|
request_pty: DefTuple[Union[bool, str]] = (),
|
|
4175
4175
|
term_type: DefTuple[Optional[str]] = (),
|
|
@@ -5687,7 +5687,7 @@ class SSHClientConnection(SSHConnection):
|
|
|
5687
5687
|
return cast(SSHForwarder, peer)
|
|
5688
5688
|
|
|
5689
5689
|
@async_context_manager
|
|
5690
|
-
async def start_sftp_client(self, env: DefTuple[Env] = (),
|
|
5690
|
+
async def start_sftp_client(self, env: DefTuple[Optional[Env]] = (),
|
|
5691
5691
|
send_env: DefTuple[Optional[EnvSeq]] = (),
|
|
5692
5692
|
path_encoding: Optional[str] = 'utf-8',
|
|
5693
5693
|
path_errors = 'strict',
|
|
@@ -8042,7 +8042,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8042
8042
|
pkcs11_pin: Optional[str]
|
|
8043
8043
|
command: Optional[str]
|
|
8044
8044
|
subsystem: Optional[str]
|
|
8045
|
-
env: Env
|
|
8045
|
+
env: Optional[Env]
|
|
8046
8046
|
send_env: Optional[EnvSeq]
|
|
8047
8047
|
request_pty: _RequestPTY
|
|
8048
8048
|
term_type: Optional[str]
|
|
@@ -8115,7 +8115,8 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8115
8115
|
pkcs11_provider: DefTuple[Optional[str]] = (),
|
|
8116
8116
|
pkcs11_pin: Optional[str] = None,
|
|
8117
8117
|
command: DefTuple[Optional[str]] = (),
|
|
8118
|
-
subsystem: Optional[str] = None,
|
|
8118
|
+
subsystem: Optional[str] = None,
|
|
8119
|
+
env: DefTuple[Optional[Env]] = (),
|
|
8119
8120
|
send_env: DefTuple[Optional[EnvSeq]] = (),
|
|
8120
8121
|
request_pty: DefTuple[_RequestPTY] = (),
|
|
8121
8122
|
term_type: Optional[str] = None,
|
|
@@ -8354,7 +8355,8 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8354
8355
|
|
|
8355
8356
|
self.subsystem = subsystem
|
|
8356
8357
|
|
|
8357
|
-
self.env = cast(Env, env if env != () else
|
|
8358
|
+
self.env = cast(Optional[Env], env if env != () else
|
|
8359
|
+
config.get('SetEnv'))
|
|
8358
8360
|
|
|
8359
8361
|
self.send_env = cast(Optional[EnvSeq], send_env if send_env != () else
|
|
8360
8362
|
config.get('SendEnv'))
|
|
@@ -125,7 +125,7 @@ SockAddr = Union[Tuple[str, int], Tuple[str, int, int, int]]
|
|
|
125
125
|
EnvMap = Mapping[BytesOrStr, BytesOrStr]
|
|
126
126
|
EnvItems = Sequence[Tuple[BytesOrStr, BytesOrStr]]
|
|
127
127
|
EnvSeq = Sequence[BytesOrStr]
|
|
128
|
-
Env =
|
|
128
|
+
Env = Union[EnvMap, EnvItems, EnvSeq]
|
|
129
129
|
|
|
130
130
|
# Define a version of randrange which is based on SystemRandom(), so that
|
|
131
131
|
# we get back numbers suitable for cryptographic use.
|
|
@@ -141,8 +141,8 @@ _time_units = {'': 1, 's': 1, 'm': 60, 'h': 60*60,
|
|
|
141
141
|
def encode_env(env: Env) -> Iterator[Tuple[bytes, bytes]]:
|
|
142
142
|
"""Convert environemnt dict or list to bytes-based dictionary"""
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
if hasattr(env, 'items'):
|
|
145
|
+
env = cast(Env, env.items())
|
|
146
146
|
|
|
147
147
|
try:
|
|
148
148
|
for item in env:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2013-
|
|
1
|
+
# Copyright (c) 2013-2025 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
|
|
@@ -97,6 +97,8 @@ CertListArg = Union[_CertArg, Sequence[_CertArg]]
|
|
|
97
97
|
_KeyPairArg = Union['SSHKeyPair', _KeyArg, Tuple[_KeyArg, _CertArg]]
|
|
98
98
|
KeyPairListArg = Union[_KeyPairArg, Sequence[_KeyPairArg]]
|
|
99
99
|
|
|
100
|
+
_PassphraseCallable = Callable[[str], BytesOrStr]
|
|
101
|
+
_PassphraseArg = Optional[Union[_PassphraseCallable, BytesOrStr]]
|
|
100
102
|
|
|
101
103
|
# Default file names in .ssh directory to read private keys from
|
|
102
104
|
_DEFAULT_KEY_FILES = (
|
|
@@ -192,6 +194,51 @@ def _wrap_base64(data: bytes, wrap: int = 64) -> bytes:
|
|
|
192
194
|
for i in range(0, len(data), wrap)) + b'\n'
|
|
193
195
|
|
|
194
196
|
|
|
197
|
+
def _resolve_passphrase(
|
|
198
|
+
passphrase: _PassphraseArg, filename: str,
|
|
199
|
+
loop: Optional[asyncio.AbstractEventLoop]) -> Optional[BytesOrStr]:
|
|
200
|
+
"""Resolve a passphrase used to encrypt/decrypt SSH private keys"""
|
|
201
|
+
|
|
202
|
+
resolved_passphrase: Optional[BytesOrStr]
|
|
203
|
+
|
|
204
|
+
if callable(passphrase):
|
|
205
|
+
resolved_passphrase = passphrase(filename)
|
|
206
|
+
else:
|
|
207
|
+
resolved_passphrase = passphrase
|
|
208
|
+
|
|
209
|
+
if loop and inspect.isawaitable(resolved_passphrase):
|
|
210
|
+
resolved_passphrase = asyncio.run_coroutine_threadsafe(
|
|
211
|
+
resolved_passphrase, loop).result()
|
|
212
|
+
|
|
213
|
+
return resolved_passphrase
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class _EncryptedKey:
|
|
217
|
+
"""Encrypted SSH private key, decrypted just prior to use"""
|
|
218
|
+
|
|
219
|
+
def __init__(self, key_data: bytes, filename: str,
|
|
220
|
+
passphrase: _PassphraseArg,
|
|
221
|
+
loop: Optional[asyncio.AbstractEventLoop],
|
|
222
|
+
unsafe_skip_rsa_key_validation: bool):
|
|
223
|
+
self._key_data = key_data
|
|
224
|
+
self._filename = filename
|
|
225
|
+
self._passphrase = passphrase
|
|
226
|
+
self._loop = loop
|
|
227
|
+
self._unsafe_skip_rsa_key_validation = unsafe_skip_rsa_key_validation
|
|
228
|
+
|
|
229
|
+
def decrypt(self) -> 'SSHKey':
|
|
230
|
+
"""Decrypt this encrypted key data and return an SSH private key"""
|
|
231
|
+
|
|
232
|
+
resolved_passphrase = _resolve_passphrase(self._passphrase,
|
|
233
|
+
self._filename, self._loop)
|
|
234
|
+
|
|
235
|
+
key = import_private_key(self._key_data, resolved_passphrase,
|
|
236
|
+
self._unsafe_skip_rsa_key_validation)
|
|
237
|
+
key.set_filename(self._filename)
|
|
238
|
+
|
|
239
|
+
return key
|
|
240
|
+
|
|
241
|
+
|
|
195
242
|
class KeyGenerationError(ValueError):
|
|
196
243
|
"""Key generation error
|
|
197
244
|
|
|
@@ -2238,8 +2285,9 @@ class SSHLocalKeyPair(SSHKeyPair):
|
|
|
2238
2285
|
|
|
2239
2286
|
_key_type = 'local'
|
|
2240
2287
|
|
|
2241
|
-
def __init__(self, key: SSHKey, pubkey: Optional[SSHKey]
|
|
2242
|
-
cert: Optional[SSHCertificate]
|
|
2288
|
+
def __init__(self, key: SSHKey, pubkey: Optional[SSHKey],
|
|
2289
|
+
cert: Optional[SSHCertificate],
|
|
2290
|
+
enc_key: Optional[_EncryptedKey]):
|
|
2243
2291
|
if pubkey and pubkey.public_data != key.public_data:
|
|
2244
2292
|
raise ValueError('Public key mismatch')
|
|
2245
2293
|
|
|
@@ -2254,10 +2302,11 @@ class SSHLocalKeyPair(SSHKeyPair):
|
|
|
2254
2302
|
|
|
2255
2303
|
super().__init__(key.algorithm, key.algorithm, key.sig_algorithms,
|
|
2256
2304
|
key.sig_algorithms, key.public_data, comment,
|
|
2257
|
-
cert, key.get_filename(), key.use_executor
|
|
2258
|
-
key.use_webauthn)
|
|
2305
|
+
cert, key.get_filename(), key.use_executor or
|
|
2306
|
+
bool(enc_key), key.use_webauthn)
|
|
2259
2307
|
|
|
2260
2308
|
self._key = key
|
|
2309
|
+
self._enc_key = enc_key
|
|
2261
2310
|
|
|
2262
2311
|
def get_agent_private_key(self) -> bytes:
|
|
2263
2312
|
"""Return binary encoding of keypair for upload to SSH agent"""
|
|
@@ -2273,6 +2322,12 @@ class SSHLocalKeyPair(SSHKeyPair):
|
|
|
2273
2322
|
def sign(self, data: bytes) -> bytes:
|
|
2274
2323
|
"""Sign a block of data with this private key"""
|
|
2275
2324
|
|
|
2325
|
+
if self._enc_key:
|
|
2326
|
+
self._key = self._enc_key.decrypt()
|
|
2327
|
+
self._enc_key = None
|
|
2328
|
+
|
|
2329
|
+
self.use_executor = self._key.use_executor
|
|
2330
|
+
|
|
2276
2331
|
return self._key.sign(data, self.sig_algorithm)
|
|
2277
2332
|
|
|
2278
2333
|
|
|
@@ -2368,7 +2423,7 @@ def _match_block(data: bytes, start: int, header: bytes,
|
|
|
2368
2423
|
"""Match a block of data wrapped in a header/footer"""
|
|
2369
2424
|
|
|
2370
2425
|
match = re.compile(b'^' + header[:5] + b'END' + header[10:] +
|
|
2371
|
-
rb'[ \t\r\f\v]*$', re.M).search(data, start)
|
|
2426
|
+
rb'[ \t\n\r\f\v]*$', re.M).search(data, start)
|
|
2372
2427
|
|
|
2373
2428
|
if not match:
|
|
2374
2429
|
raise KeyImportError(f'Missing {fmt} footer')
|
|
@@ -3203,21 +3258,6 @@ def import_private_key(
|
|
|
3203
3258
|
raise KeyImportError('Invalid private key')
|
|
3204
3259
|
|
|
3205
3260
|
|
|
3206
|
-
def import_private_key_and_certs(
|
|
3207
|
-
data: bytes, passphrase: Optional[BytesOrStr] = None,
|
|
3208
|
-
unsafe_skip_rsa_key_validation: Optional[bool] = None) -> \
|
|
3209
|
-
Tuple[SSHKey, Optional[SSHX509CertificateChain]]:
|
|
3210
|
-
"""Import a private key and optional certificate chain"""
|
|
3211
|
-
|
|
3212
|
-
key, end = _decode_private(data, passphrase,
|
|
3213
|
-
unsafe_skip_rsa_key_validation)
|
|
3214
|
-
|
|
3215
|
-
if key:
|
|
3216
|
-
return key, import_certificate_chain(data[end:])
|
|
3217
|
-
else:
|
|
3218
|
-
raise KeyImportError('Invalid private key')
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
3261
|
def import_public_key(data: BytesOrStr) -> SSHKey:
|
|
3222
3262
|
"""Import a public key
|
|
3223
3263
|
|
|
@@ -3339,20 +3379,6 @@ def read_private_key(
|
|
|
3339
3379
|
return key
|
|
3340
3380
|
|
|
3341
3381
|
|
|
3342
|
-
def read_private_key_and_certs(
|
|
3343
|
-
filename: FilePath, passphrase: Optional[BytesOrStr] = None,
|
|
3344
|
-
unsafe_skip_rsa_key_validation: Optional[bool] = None) -> \
|
|
3345
|
-
Tuple[SSHKey, Optional[SSHX509CertificateChain]]:
|
|
3346
|
-
"""Read a private key and optional certificate chain from a file"""
|
|
3347
|
-
|
|
3348
|
-
key, cert = import_private_key_and_certs(read_file(filename), passphrase,
|
|
3349
|
-
unsafe_skip_rsa_key_validation)
|
|
3350
|
-
|
|
3351
|
-
key.set_filename(filename)
|
|
3352
|
-
|
|
3353
|
-
return key, cert
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
3382
|
def read_public_key(filename: FilePath) -> SSHKey:
|
|
3357
3383
|
"""Read a public key from a file
|
|
3358
3384
|
|
|
@@ -3512,31 +3538,37 @@ def load_keypairs(
|
|
|
3512
3538
|
"""
|
|
3513
3539
|
|
|
3514
3540
|
keys_to_load: Sequence[_KeyPairArg]
|
|
3541
|
+
key_data: Optional[bytes]
|
|
3542
|
+
key: Union['SSHKey', 'SSHKeyPair']
|
|
3515
3543
|
result: List[SSHKeyPair] = []
|
|
3516
3544
|
|
|
3517
3545
|
certlist = load_certificates(certlist)
|
|
3518
3546
|
certdict = {cert.key.public_data: cert for cert in certlist}
|
|
3519
3547
|
|
|
3520
3548
|
if isinstance(keylist, (PurePath, str)):
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
resolved_passphrase = passphrase(str(keylist))
|
|
3524
|
-
else:
|
|
3525
|
-
resolved_passphrase = passphrase
|
|
3549
|
+
data = read_file(keylist)
|
|
3550
|
+
key_data_list: List[bytes] = []
|
|
3526
3551
|
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3552
|
+
while data:
|
|
3553
|
+
fmt, _, end = _match_next(data, b'PRIVATE KEY')
|
|
3554
|
+
if fmt:
|
|
3555
|
+
key_data_list.append(data[:end])
|
|
3530
3556
|
|
|
3531
|
-
|
|
3532
|
-
unsafe_skip_rsa_key_validation)
|
|
3557
|
+
data = data[end:]
|
|
3533
3558
|
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3559
|
+
if len(key_data_list) > 1:
|
|
3560
|
+
resolved_passphrase = _resolve_passphrase(passphrase,
|
|
3561
|
+
str(keylist), loop)
|
|
3562
|
+
|
|
3563
|
+
keys_to_load = []
|
|
3564
|
+
|
|
3565
|
+
for key_data in key_data_list:
|
|
3566
|
+
key = import_private_key(key_data, resolved_passphrase,
|
|
3567
|
+
unsafe_skip_rsa_key_validation)
|
|
3568
|
+
key.set_filename(keylist)
|
|
3569
|
+
|
|
3570
|
+
keys_to_load.append(key)
|
|
3571
|
+
else:
|
|
3540
3572
|
keys_to_load = [keylist]
|
|
3541
3573
|
elif isinstance(keylist, (tuple, bytes, SSHKey, SSHKeyPair)):
|
|
3542
3574
|
keys_to_load = [cast(_KeyPairArg, keylist)]
|
|
@@ -3545,61 +3577,37 @@ def load_keypairs(
|
|
|
3545
3577
|
|
|
3546
3578
|
for key_to_load in keys_to_load:
|
|
3547
3579
|
allow_certs = False
|
|
3548
|
-
|
|
3549
|
-
|
|
3580
|
+
key_data = None
|
|
3581
|
+
key_prefix = ''
|
|
3550
3582
|
pubkey_or_certs = None
|
|
3551
|
-
pubkey_to_load: Optional[_KeyArg] = None
|
|
3552
3583
|
certs_to_load: Optional[_CertArg] = None
|
|
3553
|
-
|
|
3584
|
+
pubkey_to_load: Optional[_KeyArg] = None
|
|
3585
|
+
saved_exc = None
|
|
3586
|
+
enc_key: Optional[_EncryptedKey] = None
|
|
3554
3587
|
|
|
3555
3588
|
if isinstance(key_to_load, (PurePath, str, bytes)):
|
|
3556
3589
|
allow_certs = True
|
|
3557
3590
|
elif isinstance(key_to_load, tuple):
|
|
3558
3591
|
key_to_load, pubkey_or_certs = key_to_load
|
|
3559
3592
|
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3593
|
+
if isinstance(key_to_load, (PurePath, str)):
|
|
3594
|
+
key_prefix = str(key_to_load)
|
|
3595
|
+
key_data = read_file(key_to_load)
|
|
3596
|
+
elif isinstance(key_to_load, bytes):
|
|
3597
|
+
key_data = key_to_load
|
|
3563
3598
|
|
|
3564
|
-
|
|
3565
|
-
resolved_passphrase = passphrase(key_prefix)
|
|
3566
|
-
else:
|
|
3567
|
-
resolved_passphrase = passphrase
|
|
3599
|
+
certs: Optional[Sequence[SSHCertificate]]
|
|
3568
3600
|
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
resolved_passphrase, loop).result()
|
|
3601
|
+
if allow_certs:
|
|
3602
|
+
assert key_data is not None
|
|
3572
3603
|
|
|
3573
|
-
|
|
3574
|
-
key, certs_to_load = read_private_key_and_certs(
|
|
3575
|
-
key_to_load, resolved_passphrase,
|
|
3576
|
-
unsafe_skip_rsa_key_validation)
|
|
3604
|
+
_, _, end = _match_next(key_data, b'PRIVATE KEY')
|
|
3577
3605
|
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
else:
|
|
3581
|
-
key = read_private_key(key_to_load, resolved_passphrase,
|
|
3582
|
-
unsafe_skip_rsa_key_validation)
|
|
3583
|
-
|
|
3584
|
-
pubkey_to_load = key_prefix + '.pub'
|
|
3585
|
-
elif isinstance(key_to_load, bytes):
|
|
3586
|
-
if allow_certs:
|
|
3587
|
-
key, certs_to_load = import_private_key_and_certs(
|
|
3588
|
-
key_to_load, passphrase,
|
|
3589
|
-
unsafe_skip_rsa_key_validation)
|
|
3590
|
-
else:
|
|
3591
|
-
key = import_private_key(key_to_load, passphrase,
|
|
3592
|
-
unsafe_skip_rsa_key_validation)
|
|
3593
|
-
else:
|
|
3594
|
-
key = key_to_load
|
|
3595
|
-
except KeyImportError as exc:
|
|
3596
|
-
if skip_public or \
|
|
3597
|
-
(ignore_encrypted and str(exc).startswith('Passphrase')):
|
|
3598
|
-
continue
|
|
3599
|
-
|
|
3600
|
-
raise
|
|
3606
|
+
certs_to_load = import_certificate_chain(key_data[end:])
|
|
3607
|
+
key_data = key_data[:end]
|
|
3601
3608
|
|
|
3602
|
-
|
|
3609
|
+
if not certs_to_load:
|
|
3610
|
+
certs_to_load = key_prefix + '-cert.pub'
|
|
3603
3611
|
|
|
3604
3612
|
if pubkey_or_certs:
|
|
3605
3613
|
try:
|
|
@@ -3613,7 +3621,7 @@ def load_keypairs(
|
|
|
3613
3621
|
elif certs_to_load:
|
|
3614
3622
|
try:
|
|
3615
3623
|
certs = load_certificates(certs_to_load)
|
|
3616
|
-
except (OSError, KeyImportError):
|
|
3624
|
+
except (OSError, KeyImportError) as exc:
|
|
3617
3625
|
certs = None
|
|
3618
3626
|
else:
|
|
3619
3627
|
certs = None
|
|
@@ -3628,16 +3636,58 @@ def load_keypairs(
|
|
|
3628
3636
|
pubkey = import_public_key(pubkey_to_load)
|
|
3629
3637
|
else:
|
|
3630
3638
|
pubkey = pubkey_to_load
|
|
3639
|
+
|
|
3640
|
+
saved_exc = None
|
|
3631
3641
|
except (OSError, KeyImportError):
|
|
3632
3642
|
pubkey = None
|
|
3633
|
-
|
|
3643
|
+
elif key_prefix:
|
|
3644
|
+
try:
|
|
3645
|
+
pubkey = read_public_key(key_prefix + '.pub')
|
|
3634
3646
|
saved_exc = None
|
|
3647
|
+
except (OSError, KeyImportError):
|
|
3648
|
+
try:
|
|
3649
|
+
pubkey = read_public_key(key_prefix)
|
|
3650
|
+
saved_exc = None
|
|
3651
|
+
except (OSError, KeyImportError):
|
|
3652
|
+
pubkey = None
|
|
3635
3653
|
else:
|
|
3636
3654
|
pubkey = None
|
|
3637
3655
|
|
|
3638
3656
|
if saved_exc:
|
|
3639
3657
|
raise saved_exc # pylint: disable=raising-bad-type
|
|
3640
3658
|
|
|
3659
|
+
if key_data is not None:
|
|
3660
|
+
try:
|
|
3661
|
+
unencrypted_key = import_private_key(
|
|
3662
|
+
key_data, None, unsafe_skip_rsa_key_validation)
|
|
3663
|
+
unencrypted_key.set_filename(key_prefix)
|
|
3664
|
+
except KeyImportError:
|
|
3665
|
+
unencrypted_key = None
|
|
3666
|
+
|
|
3667
|
+
if unencrypted_key:
|
|
3668
|
+
key = unencrypted_key
|
|
3669
|
+
elif callable(passphrase) and key_prefix and (certs or pubkey):
|
|
3670
|
+
enc_key = _EncryptedKey(key_data, key_prefix, passphrase, loop,
|
|
3671
|
+
unsafe_skip_rsa_key_validation)
|
|
3672
|
+
|
|
3673
|
+
key = certs[0].key if certs else pubkey
|
|
3674
|
+
else:
|
|
3675
|
+
try:
|
|
3676
|
+
resolved_passphrase = _resolve_passphrase(passphrase,
|
|
3677
|
+
key_prefix, loop)
|
|
3678
|
+
|
|
3679
|
+
key = import_private_key(key_data, passphrase,
|
|
3680
|
+
unsafe_skip_rsa_key_validation)
|
|
3681
|
+
key.set_filename(key_prefix)
|
|
3682
|
+
except KeyImportError as exc:
|
|
3683
|
+
if skip_public or (ignore_encrypted and
|
|
3684
|
+
str(exc).startswith('Passphrase')):
|
|
3685
|
+
continue
|
|
3686
|
+
|
|
3687
|
+
raise
|
|
3688
|
+
else:
|
|
3689
|
+
key = cast(Union[SSHKey, SSHKeyPair], key_to_load)
|
|
3690
|
+
|
|
3641
3691
|
if not certs:
|
|
3642
3692
|
if isinstance(key, SSHKeyPair):
|
|
3643
3693
|
pubdata = key.key_public_data
|
|
@@ -3660,9 +3710,9 @@ def load_keypairs(
|
|
|
3660
3710
|
result.append(key)
|
|
3661
3711
|
else:
|
|
3662
3712
|
if cert:
|
|
3663
|
-
result.append(SSHLocalKeyPair(key, pubkey, cert))
|
|
3713
|
+
result.append(SSHLocalKeyPair(key, pubkey, cert, enc_key))
|
|
3664
3714
|
|
|
3665
|
-
result.append(SSHLocalKeyPair(key, pubkey))
|
|
3715
|
+
result.append(SSHLocalKeyPair(key, pubkey, None, enc_key))
|
|
3666
3716
|
|
|
3667
3717
|
return result
|
|
3668
3718
|
|
|
@@ -579,8 +579,9 @@ class _SCPSource(_SCPHandler):
|
|
|
579
579
|
for name in await SFTPGlob(self._fs).match(srcpath):
|
|
580
580
|
await self._send_files(cast(bytes, name.filename),
|
|
581
581
|
b'', name.attrs)
|
|
582
|
-
except asyncio.CancelledError:
|
|
582
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
583
583
|
cancelled = True
|
|
584
|
+
raise
|
|
584
585
|
except (OSError, SFTPError) as exc:
|
|
585
586
|
self.handle_error(exc)
|
|
586
587
|
finally:
|
|
@@ -745,8 +746,9 @@ class _SCPSink(_SCPHandler):
|
|
|
745
746
|
dstpath))
|
|
746
747
|
else:
|
|
747
748
|
await self._recv_files(b'', dstpath)
|
|
748
|
-
except asyncio.CancelledError:
|
|
749
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
749
750
|
cancelled = True
|
|
751
|
+
raise
|
|
750
752
|
except (OSError, SFTPError, ValueError) as exc:
|
|
751
753
|
self.handle_error(exc)
|
|
752
754
|
finally:
|
|
@@ -911,8 +913,9 @@ class _SCPCopier:
|
|
|
911
913
|
|
|
912
914
|
try:
|
|
913
915
|
await self._copy_files()
|
|
914
|
-
except asyncio.CancelledError:
|
|
916
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
915
917
|
cancelled = True
|
|
918
|
+
raise
|
|
916
919
|
except (OSError, SFTPError) as exc:
|
|
917
920
|
self._handle_error(exc)
|
|
918
921
|
finally:
|
|
@@ -1095,6 +1098,8 @@ async def _scp_handler(sftp_server: MaybeAwait[SFTPServer],
|
|
|
1095
1098
|
if inspect.isawaitable(sftp_server):
|
|
1096
1099
|
sftp_server = await sftp_server
|
|
1097
1100
|
|
|
1101
|
+
sftp_server: SFTPServer
|
|
1102
|
+
|
|
1098
1103
|
fs = SFTPServerFS(sftp_server)
|
|
1099
1104
|
|
|
1100
1105
|
handler: Union[_SCPSource, _SCPSink]
|
|
@@ -4656,7 +4656,8 @@ class SFTPClient:
|
|
|
4656
4656
|
parts = path.split(b'/')
|
|
4657
4657
|
last = len(parts) - 1
|
|
4658
4658
|
|
|
4659
|
-
exc: Type[
|
|
4659
|
+
exc: Union[Type[SFTPNotADirectory], Type[SFTPFailure],
|
|
4660
|
+
Type[SFTPFileAlreadyExists]]
|
|
4660
4661
|
|
|
4661
4662
|
for i, part in enumerate(parts):
|
|
4662
4663
|
curpath = posixpath.join(curpath, part)
|
|
@@ -6775,7 +6776,9 @@ class SFTPServerHandler(SFTPHandler):
|
|
|
6775
6776
|
data = self._server.read(src, read_from_offset, size)
|
|
6776
6777
|
|
|
6777
6778
|
if inspect.isawaitable(data):
|
|
6778
|
-
data = await
|
|
6779
|
+
data = await data
|
|
6780
|
+
|
|
6781
|
+
data: bytes
|
|
6779
6782
|
|
|
6780
6783
|
result = self._server.write(dst, write_to_offset, data)
|
|
6781
6784
|
|
|
@@ -8234,6 +8237,8 @@ async def _sftp_handler(sftp_server: MaybeAwait[SFTPServer],
|
|
|
8234
8237
|
if inspect.isawaitable(sftp_server):
|
|
8235
8238
|
sftp_server = await sftp_server
|
|
8236
8239
|
|
|
8240
|
+
sftp_server: SFTPServer
|
|
8241
|
+
|
|
8237
8242
|
handler = SFTPServerHandler(sftp_server, reader, writer, sftp_version)
|
|
8238
8243
|
|
|
8239
8244
|
await handler.run()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.21.
|
|
3
|
+
Version: 2.21.1
|
|
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,7 +32,7 @@ Requires-Dist: typing_extensions>=4.0.0
|
|
|
32
32
|
Provides-Extra: bcrypt
|
|
33
33
|
Requires-Dist: bcrypt>=3.1.3; extra == "bcrypt"
|
|
34
34
|
Provides-Extra: fido2
|
|
35
|
-
Requires-Dist: fido2
|
|
35
|
+
Requires-Dist: fido2<2,>=0.9.2; extra == "fido2"
|
|
36
36
|
Provides-Extra: gssapi
|
|
37
37
|
Requires-Dist: gssapi>=1.2.0; extra == "gssapi"
|
|
38
38
|
Provides-Extra: libnacl
|
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
Change Log
|
|
4
4
|
==========
|
|
5
5
|
|
|
6
|
+
Release 2.21.1 (28 Sep 2025)
|
|
7
|
+
----------------------------
|
|
8
|
+
|
|
9
|
+
* Added the capability to defer invoking passphrase callback until
|
|
10
|
+
an encrypted private key is actually used in a signing operation,
|
|
11
|
+
rather than triggering the callback when keys are loaded. This
|
|
12
|
+
will only work when a public key is provided with an encrypted
|
|
13
|
+
private key either explicitly or as part of the key format (such
|
|
14
|
+
as in OpenSSH's private key format).
|
|
15
|
+
|
|
16
|
+
* Improved handling of KeyboardInterrupt and task cancellation in
|
|
17
|
+
SCP. Thanks go to Viktor Kertesz for reporting this issue and
|
|
18
|
+
helping to understand the behavior in various versions of Python.
|
|
19
|
+
|
|
20
|
+
* Fixed the env option to support mappings other than dict. Thanks
|
|
21
|
+
go to Boris Pavlovic for reporting this issue.
|
|
22
|
+
|
|
23
|
+
* Fixed a potential race condition in SSHForwarder cleanup. Thanks
|
|
24
|
+
go to GitHub user misa-hase for reporting this issue and helping
|
|
25
|
+
to test the fix.
|
|
26
|
+
|
|
6
27
|
Release 2.21.0 (2 May 2025)
|
|
7
28
|
---------------------------
|
|
8
29
|
|
|
@@ -35,7 +35,7 @@ dynamic = ['version']
|
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
bcrypt = ['bcrypt >= 3.1.3']
|
|
38
|
-
fido2 = ['fido2 >= 0.9.2']
|
|
38
|
+
fido2 = ['fido2 >= 0.9.2, < 2']
|
|
39
39
|
gssapi = ['gssapi >= 1.2.0']
|
|
40
40
|
libnacl = ['libnacl >= 1.4.2']
|
|
41
41
|
pkcs11 = ['python-pkcs11 >= 0.7.0']
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2016-
|
|
1
|
+
# Copyright (c) 2016-2025 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
|
|
@@ -321,9 +321,8 @@ class _TestAgent(AsyncTestCase):
|
|
|
321
321
|
async with agent:
|
|
322
322
|
self.assertIsNone(await agent.add_keys([keypair]))
|
|
323
323
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
await agent.add_keys([key.convert_to_public()])
|
|
324
|
+
with self.assertRaises(asyncssh.KeyExportError):
|
|
325
|
+
await agent.add_keys([key.convert_to_public()])
|
|
327
326
|
|
|
328
327
|
await mock_agent.stop()
|
|
329
328
|
|