Skip to content

Windows 11 / modern Windows 10 OpenSSH agent supports RSA SHA-2 signature flags #795

@Netzvamp

Description

@Netzvamp

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_init

After 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:

  1. Always include SHA-2 algorithms and let the agent return SSH_AGENT_FAILURE if it doesn't support the flags, then fall back to ssh-rsa. The sig_algorithms tuple already supports this fallback pattern — ssh-rsa just needs to remain at the end.
  2. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions