Skip to content

internal: sign Ed25519 user-auth requests through the agent - #1196

Open
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_11660
Open

internal: sign Ed25519 user-auth requests through the agent#1196
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_11660

Conversation

@yosuke-wolfssl

@yosuke-wolfssl yosuke-wolfssl commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Problem

In WOLFSSH_AGENT builds with ssh->agentEnabled, BuildUserAuthRequestEd25519() left its agent branch as a /* XXX: Pending */ placeholder that wrote nothing and returned WS_SUCCESS. A client authenticating with an Ed25519 key held by an ssh-agent emitted a SSH_MSG_USERAUTH_REQUEST whose trailing string signature field was simply absent — a malformed signed publickey request — rather than failing. RSA and ECDSA already implemented this path correctly. Closes 11660.

Fix (src/internal.c)

BuildUserAuthRequestEd25519() now calls wolfSSH_AGENT_SignRequest() and writes the returned blob length-prefixed into the reserved payload. That is BuildUserAuthRequestRsa()'s shape — signing straight into output rather than into a local buffer as BuildUserAuthRequestEcc() does — with the capacity corrected.

The capacity handed to the agent is the room the prepare phase actually set aside, not the bare signature size:

bytes
Reserved by PrepareUserAuthRequestEd25519() LENGTH_SZ*3 + 64 + 11 = 87
Outer length prefix 4
Capacity declared to the agent 83
ssh-ed25519 blob the agent returns 4+11+4+64 = 83

The two terms track each other for any signature and algorithm-name size, not just 64 and 11: the capacity is the reservation minus the one length prefix the builder writes itself. wolfSSH_AGENT_SignRequest() treats *sigSz as the caller's buffer capacity and returns WS_BUFFER_E above it, so an over-long blob is refused with nothing written.

PrepareUserAuthRequestEd25519() needed no functional change — wc_ed25519_sig_size() is constant, so its reservation was already right for the agent case. Only the placeholder comment changed.

The signature buffer the local signing path fills now gets allocated in that path instead of at the top of the function, so the agent path no longer takes a WOLFSSH_SMALL_STACK allocation it never reads.

Tests (tests/regress.c)

Three tests, built on the file's existing MemIo transport capture, Append* builders, and the library's own GetStringRef()/GetBoolean() parsers:

  • TestAgentEd25519UserAuthEmitsSignature() drives SendUserAuthRequest() over a mock agent and walks the emitted packet down to its signature field.
  • TestAgentEd25519UserAuthPropagatesAgentError() gives the agent no response and asserts WS_AGENT_NO_KEY_E reaches the caller with nothing written to the transport.
  • TestAgentEd25519UserAuthRejectsOversizeSignature() has the agent answer one byte past the capacity and asserts WS_BUFFER_E with nothing written — pinning the bound from above.

All three are mutation-verified: under-declaring the capacity as keySig->sigSz fails the first, swallowing the sign-request error fails the second, and deleting the size check in wolfSSH_AGENT_SignRequest() fails the third.

Verification

  • tests/regress.test, unit, api, kex, auth, testsuite all pass. scripts/{sftp,scp}.test flake under parallel make check and pass serially.
  • gcc-13 -Werror clean across 6 build configurations, including WOLFSSH_SMALL_STACK and the Zephyr defines.
  • ASan + UBSan clean on regress and unit.

Not in this PR

RSA agent user auth is broken independently, and by two separate defects. The first failure is in PrepareUserAuthRequestRsa(), which hands wc_RsaPublicKeyDecode() — an ASN.1 DER parser — the SSH wire blob string "ssh-rsa", mpint e, mpint n, yielding a raw ASN_PARSE_E (-140) out through the wolfSSH API. Only behind that does the second defect bite: BuildUserAuthRequestRsa() and BuildUserAuthRequestRsaCert() pass &keySig->sigSz as the agent capacity, under-declaring it by the algorithm-name prefix (271 vs 256 for a 2048-bit key). A fix needs GetOpenSshPublicKey(), as the WOLFSSH_TPM branch directly below already uses, and the capacity shape this PR uses for Ed25519. Tracked separately.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Aug 25, 2026
Copilot AI lite review requested due to automatic review settings August 25, 2026 01:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes agent-backed Ed25519 publickey authentication by ensuring SSH_MSG_USERAUTH_REQUEST includes the required trailing signature when the private key lives in an ssh-agent, and adds regression tests to validate both the success and error-propagation paths.

Changes:

  • Implement Ed25519 agent signing in BuildUserAuthRequestEd25519() by calling wolfSSH_AGENT_SignRequest() and length-prefixing the returned signature blob.
  • Update the Ed25519 prepare path comment to reflect agent behavior (no private key loaded locally).
  • Add two regress tests that (1) assert a signature is emitted and (2) assert agent errors propagate without writing a partial request.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tests/regress.c Adds a mock-agent harness and regression tests validating Ed25519 agent-signed USERAUTH_REQUEST packets and error propagation.
src/internal.c Implements the Ed25519 agent signing branch so signed publickey auth requests are well-formed when using an agent.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/internal.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1196

Scan targets checked: wolfssh-bugs, wolfssh-src

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

- BuildUserAuthRequestEd25519() signs with wolfSSH_AGENT_SignRequest()
  when the agent is enabled, writing the returned signature blob
  length-prefixed into the reserved payload and advancing idx past it.
  The capacity handed to the agent is the room the prepare phase set
  aside: two lengths plus the signature and public key type sizes.
- The buffer the local signing path fills is allocated in that path
  rather than at the top of the function; sig starts NULL and the
  small-stack free at the tail already null-checks it.
- PrepareUserAuthRequestEd25519() notes that the agent holds the
  private key and loads none locally.
- tests/regress.c gains TestAgentEd25519UserAuthEmitsSignature(),
  TestAgentEd25519UserAuthPropagatesAgentError() and
  TestAgentEd25519UserAuthRejectsOversizeSignature(), which drive
  SendUserAuthRequest() over a mock agent and parse the emitted
  USERAUTH_REQUEST down to its signature field.
- InitAgentEd25519Ctx() takes the signature size the mock agent
  answers with, so a caller can hand back a blob past the capacity.
- ParsePayloadLen() and BuildExtInfoSigAlgs() move to the shared test
  helper section so the new tests and the existing callers share them.

Issue: F-11660
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.

4 participants