Skip to content

[RFC violation] Publickey authentication is impossible unless the server sends EXT_INFO server-sig-algs #1286

Description

@LCBH

[RFC violation] Publickey authentication is impossible unless the server sends EXT_INFO server-sig-algs

Component: client publickey path — src/internal.c (PrepareUserAuthRequestPublicKey, MatchIdLists)
Affected versions: v1.5.0-stable; master 6e49506b (2026-09-18) — both. Re-verified present at 6ed8e121 (2026-09-25), master HEAD at the time of writing
Type: RFC 4252 / RFC 8308 interoperability
Severity: Low — but it needs no attacker at all
Discovery method: differential fuzzing with tlspuffin, then reduced to the standalone PoC below


Summary

A wolfSSH client cannot authenticate by public key against a server that does not send
SSH_MSG_EXT_INFO with server-sig-algs. RFC 8308 makes sending EXT_INFO a MAY, so a fully conformant
server may omit it — and against such a server wolfSSH publickey authentication fails outright.

This is an interoperability defect against honest peers, not a security issue. We report it separately from
our security findings for that reason.

RFC citation

RFC 8308 §2.2:

Implementations MAY send SSH_MSG_EXT_INFO […]

RFC 4252 §7 defines publickey authentication with no dependence on RFC 8308. A client that requires an
optional extension in order to perform a mandatory-to-implement authentication method has made the optional
one mandatory.

Reproduction

Attached reproducer — one file, runs both modes, prints a verdict

repro_issue_3_publickey_requires_ext_info.py (attached) is self-contained: a server speaking SSH-2.0
directly (curve25519-sha256 + aes256-gcm@openssh.com, no SSH library, so the result does not depend on our
tooling), which generates its own host key and drives your own examples/client/client with an ordinary
RSA key from your keys/ directory. Requires python3 with cryptography.

$ python3 repro_issue_3_publickey_requires_ext_info.py \
      --client ./examples/client/client --wolfssh-root .

  mode   EXT_INFO sent          client signature algorithm  client result
  none   no                     (none offered)              FAILED   <-- the bug
  post   yes, after NEWKEYS     rsa-sha2-256                authenticated

  VERDICT: REPRODUCED -- with no EXT_INFO the client offers no signature
           algorithm at all; the `post` control shows the only variable was
           whether an optional RFC 8308 message was sent.

In --mode none the server simply does not send EXT_INFO — which is all a conformant server need do.
The client then sends no publickey USERAUTH_REQUEST at all and fails with WS_MATCH_KEY_ALGO_E; the server
records no signature algorithm, because none was ever offered.

The post control is the point: the identical exchange with server-sig-algs after NEWKEYS
authenticates normally, so the only variable is whether an optional message was sent. It exits 0 when
reproduced and 1 when not. The output above is a real run against v1.5.0-stable.

Root cause

PrepareUserAuthRequestPublicKey (internal.c:15430) builds the client's candidate list and then
intersects it with the peer's:

matchId = MatchIdLists(WOLFSSH_ENDPOINT_CLIENT, algoId, algoIdSz,
                       ssh->peerSigId, ssh->peerSigIdSz);   /* internal.c:15464 */
if (matchId == ID_UNKNOWN) {
    ret = WS_MATCH_KEY_ALGO_E;
}

MatchIdLists returns ID_UNKNOWN whenever either list is NULL or empty — its guard is
left != NULL && leftSz > 0 && right != NULL && rightSz > 0. And ssh->peerSigId is populated in exactly
one place: DoExtInfoServerSigAlgs (internal.c:6814).

So with no EXT_INFO received, peerSigId is empty, the intersection is empty, and the client sets
WS_MATCH_KEY_ALGO_E. There is no fallback to the client's own preference.

The client does advertise ext-info-c (internal.c:11085), so a server that implements RFC 8308 will
normally send EXT_INFO — which is presumably why this has not been hit. But the RFC does not require it.

Suggested fix

Fall back to the client's own strongest supported algorithm for the key type when peerSigId is empty or
yields no match:

matchId = MatchIdLists(WOLFSSH_ENDPOINT_CLIENT, algoId, algoIdSz,
                       ssh->peerSigId, ssh->peerSigIdSz);
if (matchId == ID_UNKNOWN) {
    if (ssh->peerSigIdSz == 0) {
        matchId = algoId[0];       /* no server-sig-algs: use our own preference */
    } else {
        ret = WS_MATCH_KEY_ALGO_E; /* server listed algorithms, none of them ours */
    }
}

The distinction matters: "the server told us what it accepts and we support none of them" is a genuine
failure; "the server told us nothing" is not.

Impact

Low, and no attacker is involved. The failure occurs against any honest server that omits an optional
message. Its practical significance is interoperability: a wolfSSH client cannot use publickey
authentication against such a peer, and the error surfaced (WS_MATCH_KEY_ALGO_E, "cannot match key algo
with peer") suggests an algorithm mismatch rather than a missing extension, which is misleading when
diagnosing.

Acknowledgements

Found with the tlspuffin fuzzer — https://github.com/tlspuffin/tlspuffin — designed and developed by the
tlspuffin team: Nataël Baffou, Olivier Demengeon, Tom Gouville, Lucca Hirschi, Steve Kremer (Inria, LORIA, France).

repro_issue_3_publickey_requires_ext_info.py

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions