-
Notifications
You must be signed in to change notification settings - Fork 169
Description
The SSHAgentKeyPair.__init__ in asyncssh/agent.py unconditionally disables RSA SHA-2 signature algorithms (rsa-sha2-256, rsa-sha2-512) when running on sys.platform == 'win32':
# Neither Pageant nor the Win10 OpenSSH agent seems to support the
# ssh-agent protocol flags used to request RSA SHA2 signatures yet
if sig_algorithm == b'ssh-rsa' and sys.platform != 'win32':
sig_algorithms: Sequence[bytes] = \
(b'rsa-sha2-256', b'rsa-sha2-512', b'ssh-rsa')
else:
sig_algorithms = (sig_algorithm,)This means that on Windows, asyncssh will only attempt ssh-rsa (SHA-1) signatures when using RSA keys from the agent. Since modern OpenSSH servers (8.8+) disable ssh-rsa by default, this causes authentication failures on Windows that work fine on Linux/macOS.
The problem in practice
Connecting to an OpenSSH 9.2 server (Debian 12) as root with an RSA key loaded in the Windows OpenSSH agent:
INFO:asyncssh: Beginning auth for user root
DEBUG:asyncssh: Trying public key auth with ssh-rsa key
INFO:asyncssh: Auth failed for user root
INFO:asyncssh: Connection failure: Permission denied for user root on host ki1
The same connection works fine using the native Windows ssh.exe client:
debug3: sign_and_send_pubkey: signing using rsa-sha2-512 SHA256:t3O9...
Authenticated to ki1 ([172.17.5.6]:22) using "publickey".
Root cause
The Windows 11 OpenSSH agent (and Windows 10 builds shipping OpenSSH 8.9+) does support the SSH_AGENT_RSA_SHA2_256 and SSH_AGENT_RSA_SHA2_512 flags in the SSH_AGENTC_SIGN_REQUEST message. The guard in asyncssh is based on the behavior of older Windows 10 OpenSSH agent versions that did not support these flags.
Reproduction
import asyncio
import asyncssh
async def main():
# Agent has an RSA key loaded via Windows OpenSSH agent
conn = await asyncssh.connect(
"example.com",
username="root",
known_hosts=None,
agent_path=r"\\.\pipe\openssh-ssh-agent",
)
# Raises: PermissionDenied: Permission denied for user root on host example.com
asyncio.run(main())Verified workaround
Monkey-patching SSHAgentKeyPair.__init__ to re-enable the SHA-2 algorithm list on Windows resolves the issue immediately:
from asyncssh.agent import SSHAgentKeyPair
_orig_init = SSHAgentKeyPair.__init__
def _patched_init(self, agent, algorithm, public_data, comment):
_orig_init(self, agent, algorithm, public_data, comment)
if (self.sig_algorithm == b'ssh-rsa'
and self.sig_algorithms == (b'ssh-rsa',)):
self.sig_algorithms = (b'rsa-sha2-256', b'rsa-sha2-512', b'ssh-rsa')
if not self.has_cert:
self.host_key_algorithms = self.sig_algorithms
SSHAgentKeyPair.__init__ = _patched_initAfter patching:
DEBUG:asyncssh: Trying public key auth with rsa-sha2-256 key
DEBUG:asyncssh: Signing request with rsa-sha2-256 key
INFO:asyncssh: Auth for user root succeeded
Suggested fix
Instead of a blanket sys.platform != 'win32' check, asyncssh could:
- Always include SHA-2 algorithms and let the agent return
SSH_AGENT_FAILUREif it doesn't support the flags, then fall back tossh-rsa. Thesig_algorithmstuple already supports this fallback pattern —ssh-rsajust needs to remain at the end. - Or detect the Windows OpenSSH agent version and enable SHA-2 accordingly.
Option 1 is simpler and handles both old and new agents gracefully:
# Always offer SHA-2 variants first, with ssh-rsa as fallback.
# Agents that don't support the SSH_AGENT_RSA_SHA2_* flags will
# fail on SHA-2, and asyncssh will fall back to ssh-rsa.
if sig_algorithm == b'ssh-rsa':
sig_algorithms: Sequence[bytes] = \
(b'rsa-sha2-256', b'rsa-sha2-512', b'ssh-rsa')
else:
sig_algorithms = (sig_algorithm,)Environment
- OS: Windows 11 Pro (Build 26100)
- Python: 3.13.2
- asyncssh: 2.22.0
- Server: OpenSSH 9.2p1 (Debian 12)
- Key type: RSA 2048-bit, loaded via Windows OpenSSH agent (KeepassXC → ssh-agent)