Skip to content

Conversation

@dniasoff
Copy link

@dniasoff dniasoff commented Oct 30, 2025

Summary

  • Fixed SSH certificate authentication when using certificates loaded from an SSH agent
  • Added global storage to preserve full certificate blobs since the ssh-key crate doesn't maintain them
  • Modified authentication flow in russh/src/client/encrypted.rs to properly encode certificates during auth probes and signature creation
  • Enhanced agent client in russh/src/keys/agent/client.rs to detect and store certificate blobs when listing keys

Technical Details

The issue was that SSH certificates from agents weren't being properly encoded during authentication. The ssh-key crate parses certificates but doesn't preserve the full blob needed for authentication. This PR:

  1. Certificate blob storage: Adds a global HashMap (CERT_BLOBS) to store complete certificate blobs keyed by fingerprint
  2. Detection and storage: Modified AgentClient::request_identities() to detect certificates and store their blobs
  3. Authentication flow: Updated write_auth_request() to retrieve and use stored certificate blobs for proper encoding
  4. Signature requests: Modified sign_request_with_hash_alg() to send full certificate blobs to the agent

Test plan

  • Test SSH authentication with certificates loaded from ssh-agent
  • Test regular SSH key authentication to ensure no regression
  • Verify certificate fingerprint matching works correctly
  • Test with various certificate types (RSA, ECDSA, Ed25519)

🤖 Generated with Claude Code

This fixes SSH certificate authentication when certificates are provided
by an SSH agent (e.g., OpenSSH agent, Pageant, or Vault-issued certificates).

## Problem

The ssh-key crate's `parse_public_key()` function parses OpenSSH certificates
as opaque keys containing only the certificate nonce (~32 bytes) rather than
the full certificate blob (~600-700 bytes). When `.to_bytes()` is called on
these parsed certificates, only the public key portion is returned, losing all
certificate metadata including:
- Serial number
- Key ID
- Valid principals
- Validity period (not-before/not-after)
- Critical options and extensions
- CA signature

This caused three specific failures during certificate authentication:

1. **Double certificate suffix bug**: The code called `algo.to_certificate_type()`
   on an algorithm string that was already a certificate type
   (`[email protected]`), resulting in an invalid
   algorithm name (`[email protected]`)
   that servers rejected.

2. **Incomplete certificate in auth probe**: Only 32 bytes of certificate data
   were sent to the server during the publickey probe phase instead of the
   full certificate blob.

3. **Incomplete certificate in signature request**: Only the nonce was sent to
   the SSH agent when requesting a signature, preventing the agent from
   matching the certificate to the correct private key.

## Solution

The fix preserves the original certificate blobs throughout the authentication
flow:

1. **Certificate detection and storage** (`keys/agent/client.rs`): When
   receiving keys from the agent, detect certificates by checking if the
   algorithm name contains `[email protected]`. Store the original
   certificate blob in a global HashMap keyed by the certificate's SHA256
   fingerprint.

2. **Probe phase** (`client/encrypted.rs`): During publickey authentication
   probe, retrieve the stored certificate blob and send it to the server.
   Use the algorithm string as-is without calling `to_certificate_type()`
   since it's already the correct certificate type.

3. **Signature phase** (`client/encrypted.rs`): When constructing the data
   to be signed, use the stored certificate blob instead of the truncated
   key data.

4. **Agent signature request** (`keys/agent/client.rs`): When requesting a
   signature from the agent, send the full certificate blob instead of just
   the key data so the agent can match it to the correct private key.

## Testing

Tested with Vault-issued ECDSA certificates on Windows using the built-in
OpenSSH agent. Authentication now succeeds where it previously failed with
"userauth_failure" errors.

Verified server logs show successful authentication:
```
Accepted publickey for <user> from <ip> ssh2: ECDSA-CERT <fingerprint>
ID <key-id> (serial <serial>) CA ECDSA <ca-fingerprint>
```

Regular SSH key authentication (non-certificate) remains unaffected.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant