Skip to content

Add OpenSSH certificate user authentication - #1060

Merged
ejohnstown merged 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:feat/osshCert
Jul 28, 2026
Merged

Add OpenSSH certificate user authentication#1060
ejohnstown merged 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:feat/osshCert

Conversation

@yosuke-wolfssl

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

Copy link
Copy Markdown
Contributor

Add OpenSSH certificate user authentication

Adds OpenSSH certificate (*-cert-v01@openssh.com) user auth to wolfSSHd behind
--enable-ossh-certs, which is opt-in and deliberately not part of
--enable-all
— existing --enable-all deployments are unaffected by this PR.
Operators can authorize users by trusting a single CA via TrustedUserCAKeys
instead of distributing every user's public key into authorized_keys.

What's implemented (Unix)

Library (src/ossh.c, src/internal.c) — portable, no platform deps:

  • Parse the certificate wire format; verify the CA signature against the
    embedded CA key (RSA incl. rsa-sha2-256/512, ECDSA P-256/384/521, Ed25519).
  • Critical options parsed in strict ascending order; out-of-order, duplicate,
    or unknown names rejected. Only force-command and source-address are
    supported, and an empty or malformed value is rejected rather than treated
    as unrestricted.
  • Extensions are parsed and tolerant (unknown, duplicate, and out-of-order
    accepted) but not enforced — see Known limitations.
  • Reconstruct the certified base user key and verify the user's signature
    against it, using the on-the-wire signed length.
  • Policy fields (caKey, principals, validity, source-address,
    force-command) are handed to the user-auth callback.

Daemon (apps/wolfsshd/auth.c, wolfsshd.c)CheckPublicKeyUnix enforces
fail-closed, in order: CA trust → principal binding (a principal-less cert is
rejected, matching sshd) → validity window → source-address → force-command.

Security model

The library proves only that the certificate is self-consistent (signed by its
embedded CA) and that the client holds the certified private key. It does not
decide trust — the callback checks caKey against a trust store and enforces
policy, mirroring how public-key auth delegates the authorized_keys check. The
library fails closed when no user-auth callback is set.

ForceCommand and certificate force-command

A certificate force-command restricts what the session may do — it denies
file transfer unless the command is exactly internal-sftp. A configured
ForceCommand replaces what is run for shell and exec, and does not by
itself deny SFTP or SCP.

Config ForceCommand Cert force-command SFTP SCP Shell/exec runs
/bin/wrapper allowed allowed /bin/wrapper
/bin/backup denied denied /bin/backup
internal-sftp allowed denied internal-sftp
/bin/wrapper /bin/backup denied denied /bin/wrapper

Row 1 is unchanged from master. Row 4 deliberately diverges from OpenSSH, which
checks adm_forced_command first and never reaches the certificate's command, so
a configured ForceCommand lifts the CA's restriction entirely; wolfSSHd honors
both. wolfSSHd also denies where sshd substitutes the forced command and lets
the client fail against the wrong program — the observable outcome matches in
every row.

Configuration

# wolfsshd config
TrustedUserCAKeys /etc/wolfssh/trusted-user-ca-keys.pub
./configure --enable-ossh-certs --enable-sshd   # or --enable-all --enable-ossh-certs

Platform support

  • Unix: fully supported and enforced.
  • Windows: intentionally fails closed — a certificate is never accepted on
    the threaded path. Follow-up in two PRs (per-connection cert-state relocation,
    then enforcement plus a Windows CI cert-auth job).

Testing

  • tests/api.c — parse, CA-signature verify, critical-option handling and
    extension tolerance, OsshCertBaseId mapping, OsshRsaCertSigId selection, and
    rejection of a CA blob whose curve name and key type disagree.
  • tests/unit.c — Ed25519 public-key parse, the user-signature wire-length
    path, and the wolfSSH_RsaVerify NULL-signature guard.
  • test_configuration.c — principal binding (incl. empty-list rejection),
    validity window, source-address CIDR matching and enforcement through
    CheckPublicKeyUnix, and force-command precedence.
  • sshd_ossh_cert_test.sh — end-to-end via the wolfSSH and OpenSSH clients:
    valid certs (Ed25519/RSA/ECDSA CAs and user keys), untrusted CA, wrong and empty
    principal, expired, unknown critical option, source-address match/deny,
    force-command, internal-sftp, SFTP/SCP gating, and a configured ForceCommand
    alongside a certificate.
  • Wired into CI explicitly, since --enable-all no longer implies the feature:
    sshd-test.yml builds --enable-all --enable-ossh-certs (so make check and
    the run_all_sshd_tests.sh e2e suite cover it), and os-check.yml adds an
    --enable-all --enable-ossh-certs matrix entry on Ubuntu and macOS. The plain
    --enable-all entries stay, so both states are built. sshd_ossh_cert_test.sh
    self-skips (exit 77) when the feature is off.

Known limitations

  • Certificate extensions are parsed but not enforced. permit-pty,
    permit-port-forwarding, permit-agent-forwarding, permit-X11-forwarding
    and permit-user-rc are recognized and validated, then ignored. In OpenSSH
    these are opt-in permissions where absence means deny, so wolfSSH is the
    permissive direction here. The live gap is permit-pty: a
    ssh-keygen -O clear -O force-command=... certificate still gets an
    interactive pty, because SHELL_Subsystem() decides on SHELL_IsPty(ssh)
    alone. The four forwarding/user-rc extensions have no corresponding wolfSSHd
    feature to gate today. Enforcement is a follow-up.
  • Windows: fail-closed only (see above).
  • Source-address negation (!entry) is not implemented; a negated entry fails
    the whole list closed and logs the offending entry.
  • Host certificates are not implemented, and this PR adds no public API
    for them — the loader arrives with the feature. With --enable-ossh-certs and
    X.509 disabled, a configured HostCertificate now fails daemon startup with
    WS_UNIMPLEMENTED_E — the previous guard was a never-defined macro, so the
    directive was read and silently discarded. Remove HostCertificate or enable
    X.509. This only affects builds that opt into --enable-ossh-certs;
    --enable-all keeps its current behavior, including the TrustedUserCAKeys
    X.509 load failure.
  • No C-level test drives the certificate reconstruction and user-signature path in
    DoUserAuthRequestPublicKey; it is covered end-to-end only. Follow-up.

Notes for reviewers

  • WS_UserAuthData_PublicKey: the cert fields are appended after every existing
    member, and isOsshCert sits after dataToSignSz rather than packing with
    isCert, so every member a user-auth callback can reach keeps the same offset
    whether or not WOLFSSH_OSSH_CERTS is enabled (verified with offsetof in both
    builds). A flag mismatch therefore truncates rather than misaligns. The struct
    is a stack local in DoUserAuthRequest(), so the padding is not a
    per-connection cost.
  • Client-side host-key advertisement uses cannedKeyAlgoNamesHostKey, which omits
    the *-cert-v01@openssh.com names (host-certificate verification is
    unimplemented) while keeping the plain, X.509, and ML-DSA names in sync.
  • src/ossh.c is in the Windows MSVC project because the OpenSSH key decoders it
    defines are referenced unconditionally.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Jun 25, 2026
Copilot AI review requested due to automatic review settings June 25, 2026 01:59
@yosuke-wolfssl
yosuke-wolfssl marked this pull request as draft June 25, 2026 01:59

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@yosuke-wolfssl
yosuke-wolfssl force-pushed the feat/osshCert branch 2 times, most recently from 2576e03 to 1c40cc1 Compare June 25, 2026 02:53

@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 #1060

Scan targets checked: wolfssh-bugs, wolfssh-src

No new issues found in the changed files. ✅

@yosuke-wolfssl
yosuke-wolfssl marked this pull request as ready for review June 25, 2026 04:11

@aidangarske aidangarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🐺 Skoll Code Review

Overall recommendation: REQUEST_CHANGES
Findings: 4 total — 4 posted, 0 skipped

Posted findings

  • [High] RSA certificate guards assume rsa-sha2-256 is always availablesrc/internal.c:15861-15876
  • [Medium] OpenSSH RSA certificate CA signatures bypass SHA-1 soft-disable policysrc/ossh.c:1013-1025
  • [Medium] OSSH certificate API test assumes RSA and ECDSA are enabledtests/api.c:1579-1598
  • [Medium] New sshd OSSH cert test cannot be selected or excludedapps/wolfsshd/test/run_all_sshd_tests.sh:5-14

Review generated by Skoll.

Comment thread src/internal.c Outdated
Comment thread src/ossh.c
Comment thread tests/api.c
Comment thread apps/wolfsshd/test/run_all_sshd_tests.sh
@yosuke-wolfssl
yosuke-wolfssl marked this pull request as draft July 14, 2026 09:03
@yosuke-wolfssl
yosuke-wolfssl marked this pull request as ready for review July 15, 2026 01:19

@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 #1060

Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 5
3 finding(s) posted as inline comments (see file-level comments below)

Medium (1)

Unknown key type in keys file aborts scan instead of skipping the line

File: apps/wolfsshd/auth.c:276-278
Function: CheckAuthKeysLine
Category: Error handling defect

CheckAuthKeysLine returns WS_FATAL_ERROR (-1001) whenever the key-type token on a line is not in its internal allowedTypes[] list. SearchKeysFile treats any negative return code as a hard scan error and breaks out of the scan loop immediately (lines 865-868), returning the error code to the caller. A TrustedUserCAKeys file — the new use-case introduced by this PR — may legitimately contain entries whose type strings wolfSSH does not yet support (e.g., sk-ssh-ed25519@openssh.com FIDO2 CA keys, or future algorithm identifiers). If any such line appears before the matching CA key entry, SearchKeysFile returns WS_FATAL_ERROR and the entire CA trust check fails, denying certificate authentication even when the correct CA is present later in the file.

Recommendation: In CheckAuthKeysLine, change the !typeOk branch from returning WS_FATAL_ERROR to returning WSSHD_AUTH_FAILURE so that lines with unsupported key types are silently skipped rather than treated as a parse error that aborts the containing scan loop in SearchKeysFile.

Referenced code: apps/wolfsshd/auth.c:276-278 (3 lines)


Low (1)

Integration test uses a hardcoded port with no availability guard

Category: Hardcoded paths, ports, or environment dependencies

PORT=22226 is hardcoded at the top of the script with no check that the port is free before starting the test server. Parallel CI runs of this test, or a pre-existing process on port 22226, will cause the sshd startup to fail or produce spurious test failures. This is a test-quality issue, not a production defect.

Recommendation: Use a port chosen dynamically (e.g., via a helper that probes an available ephemeral port) or guard startup with a retry/wait loop, consistent with the pattern used by other integration tests in the repository.


This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread apps/wolfsshd/test/test_configuration.c
Comment thread apps/wolfsshd/test/sshd_ossh_cert_test.sh Outdated
Comment thread apps/wolfsshd/test/sshd_ossh_cert_test.sh
@yosuke-wolfssl
yosuke-wolfssl force-pushed the feat/osshCert branch 3 times, most recently from d268d3a to 9e43f35 Compare July 16, 2026 02:29
@yosuke-wolfssl

Copy link
Copy Markdown
Contributor Author

Hello @aidangarske , @ejohnstown ,
Could you review this again ?

@aidangarske

Copy link
Copy Markdown
Member

This is something @ejohnstown should review when he has time

@aidangarske aidangarske removed their assignment Jul 16, 2026
Comment thread keys/fred-ossh-forcecmd-cert.pub Outdated
@yosuke-wolfssl

Copy link
Copy Markdown
Contributor Author

Hello @ejohnstown ,
All tests are passed. Can you review this again please ?

@ejohnstown
ejohnstown self-requested a review July 24, 2026 21:46
@ejohnstown
ejohnstown dismissed aidangarske’s stale review July 24, 2026 21:47

Unavailable for rereview.

@ejohnstown ejohnstown 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.

Review comments provided directly.

@ejohnstown
ejohnstown merged commit 029d412 into wolfSSL:master Jul 28, 2026
148 checks passed
@yosuke-wolfssl
yosuke-wolfssl deleted the feat/osshCert branch July 28, 2026 07:50
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.

6 participants