Skip to content

Load every PEM block when adding root CAs, not just the first - #1149

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

Load every PEM block when adding root CAs, not just the first#1149
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/multiBlocks

Conversation

@yosuke-wolfssl

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

Copy link
Copy Markdown
Contributor

Problem

The certificate readers added in #1140 call wc_CertPemToDer() once, which
decodes the first PEM block and stops. For a host certificate that is correct.
For wolfSSH_CTX_AddRootCert_file() / _buffer() it is a trap: a CA bundle
installs exactly one CA and still returns WS_SUCCESS, so trust the caller
believes is installed is not. Reachable today — examples/echoserver hands a
whole -a caCert file to the buffer form, as does wolfsshd's UserCAKeysFile.

Separately, AddRootCert_file() rejected CA files written in OpenSSL's
trusted-certificate form, which the buffer entry point could already read.

Fix (src/internal.c, src/ssh.c)

LoadRootCaPemBuffer() walks every block in a PEM CA buffer:

  • wc_PemGetHeaderFooter() supplies the CERTIFICATE and TRUSTED CERTIFICATE headers, so text between blocks is stepped over and whichever
    header leads types the block.
  • wc_PemToDer() decodes each block; info.consumed gives its length, the
    same mechanism wolfSSL's own chain loader uses.
  • A block that will not decode, or that the cert manager refuses, is logged
    and skipped
    , matching ProcessChainBuffer(). The call fails only when the
    buffer leaves no CA installed.
  • SniffCertForm() now names the trusted form, so AddRootCert_file()
    takes it. UseCert_* and ReadCert_* still decline it: the trust settings
    it carries mean nothing in a certificate presented to a peer.

Behavior change

CA buffer Before After
CA in the second block WS_SUCCESS, CA never installed installed, WS_SUCCESS
[ca, undecodable] WS_SUCCESS, block unread WS_SUCCESS, block logged
[undecodable, ca] WS_PARSE_E, nothing installed installed, WS_SUCCESS
every block fails WS_PARSE_E WS_PARSE_E
no PEM block at all WS_PARSE_E WS_BAD_FILE_E
trusted-form CA file WS_BAD_FILETYPE_E installed, WS_SUCCESS

No signatures change; UseCert_*, ReadCert_* and the DER paths are untouched.

Tests

test_wolfSSH_CTX_AddRootCert_bundle(),
test_wolfSSH_CTX_AddRootCert_file_trusted() and
test_wolfSSH_ReadCert_buffer_trusted() in tests/api.c cover multi-block
bundles, a bad block leading and trailing, interstitial text, an embedded NUL,
the trusted form through both entry points, and the all-fail / no-block error
codes. Accept cases assert the CA verifies a leaf, not just the return code.

Verification

  • api.test passes; clean under -Werror across 6 GCC configurations,
    including --enable-smallstack.
  • ASan + UBSan clean, including a -DWOLFSSH_SMALL_STACK run so the heap path
    is the one exercised.
  • Negative controls: with the walk disabled the bundle test fails; with the
    trusted detection removed the file test fails.
  • A real openssl x509 -trustout -addtrust CA (certificate plus 24 bytes of
    trust settings) loads and verifies a leaf.

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

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR fixes CA bundle loading so wolfSSH_CTX_AddRootCert_*() processes all PEM certificate blocks in a buffer/file instead of only the first, and adds tests covering multi-block and malformed-bundle behavior.

Changes:

  • Add LoadRootCaPemBuffer() to iterate through PEM blocks and load each CA into the cert manager.
  • Update wolfSSH_ProcessBuffer() to use the bundle-walking loader for PEM CA buffers.
  • Add an API test to validate multi-CA bundles, malformed blocks, and interstitial text behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
wolfssh/ssh.h Clarifies API behavior for multi-PEM inputs (leaf-only vs CA bundle loading).
tests/api.c Adds bundle-focused tests and a helper to build multi-block PEM buffers in memory.
src/internal.c Implements PEM bundle walking for CA loading and wires it into PEM CA processing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/internal.c
Comment thread src/internal.c Outdated
Comment thread tests/api.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 #1149

Scan targets checked: wolfssh-bugs, wolfssh-src

Findings: 5
5 finding(s) posted as inline comments (see file-level comments below)

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

Comment thread src/internal.c Outdated
Comment thread tests/api.c Outdated
Comment thread src/internal.c Outdated
Comment thread src/internal.c Outdated
Comment thread tests/api.c
Comment thread src/internal.c Outdated
Comment thread tests/api.c Outdated
Comment thread src/internal.c Outdated
Comment thread src/internal.c Outdated
Comment thread tests/api.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 #1149

Scan targets checked: wolfssh-bugs, wolfssh-src

Findings: 4
4 finding(s) posted as inline comments (see file-level comments below)

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

Comment thread src/internal.c
Comment thread src/internal.c Outdated
Comment thread tests/api.c
Comment thread tests/api.c Outdated
Comment thread src/internal.c
Comment thread src/internal.c Outdated
Comment thread tests/api.c
Comment thread tests/api.c Outdated

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

Scan targets checked: wolfssh-bugs, wolfssh-src

Findings: 9
9 finding(s) posted as inline comments (see file-level comments below)

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

Comment thread src/ssh.c
Comment thread src/internal.c
Comment thread tests/api.c Outdated
Comment thread tests/api.c Outdated
Comment thread tests/api.c Outdated
Comment thread src/internal.c Outdated
Comment thread src/ssh.c
Comment thread tests/api.c
Comment thread tests/api.c Outdated
Comment thread src/ssh.c
Comment thread src/internal.c
Comment thread tests/api.c Outdated
Comment thread tests/api.c Outdated
Comment thread tests/api.c Outdated
Comment thread src/internal.c Outdated
Comment thread src/ssh.c
Comment thread tests/api.c
Comment thread tests/api.c Outdated

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

Scan targets checked: wolfssh-bugs, wolfssh-src

Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)

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

Comment thread tests/api.c
Comment thread src/internal.c
@yosuke-wolfssl
yosuke-wolfssl force-pushed the fix/multiBlocks branch 2 times, most recently from 2f905c6 to 5c11e36 Compare August 7, 2026 07:36
@yosuke-wolfssl

Copy link
Copy Markdown
Contributor Author

Hi @ejohnstown ,
This is also one of the follow up.

- wolfSSH_ProcessBuffer() hands a PEM BUFTYPE_CA buffer to a new
  LoadRootCaPemBuffer(), which walks every block, decodes each with
  wc_PemToDer(), and gives the DER to
  wolfSSH_CERTMAN_LoadRootCA_buffer().
- A block that will not decode, or that the cert manager refuses, is
  logged and skipped. The call returns WS_PARSE_E when no block
  loaded, and WS_BAD_FILE_E when the buffer holds no block at all.
- wc_PemGetHeaderFooter() supplies the CERTIFICATE and TRUSTED
  CERTIFICATE headers, and a new FindInBuffer() seeks the next one
  without stopping at an embedded NUL.
- LoadRootCaPemBuffer() takes its EncryptedInfo from the heap under
  WOLFSSH_SMALL_STACK.
- internal.h defines WOLFSSH_HAVE_TRUSTED_CERT_PEM for wolfSSL 5.8.0
  and later, along with the WOLFSSL_V5_8_0 constant it tests.
- SniffCertForm() reports the trusted certificate form through a new
  out parameter. UseCertFile() takes that form for BUFTYPE_CA and
  answers WS_BAD_FILETYPE_E otherwise, as does
  wolfSSH_ReadCert_buffer().
- ssh.h records what each buffer entry point reads and when it fails.
- tests/api.c adds test_wolfSSH_CTX_AddRootCert_bundle(),
  test_wolfSSH_CTX_AddRootCert_file_trusted(), and
  test_wolfSSH_ReadCert_buffer_trusted().
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