Skip to content

add support for wrapped certs#318

Open
bigbrett wants to merge 4 commits intowolfSSL:mainfrom
bigbrett:wrapped-cached-certs
Open

add support for wrapped certs#318
bigbrett wants to merge 4 commits intowolfSSL:mainfrom
bigbrett:wrapped-cached-certs

Conversation

@bigbrett
Copy link
Contributor

Allows clients to use trusted root certificates that are AES-GCM wrapped and cached in server RAM rather than committed to NVM. The client stores the wrapped blob cheaply and unwraps it on demand for verification.

Changes

  • Add client-side certificate wrap/unwrap/cache API (wh_Client_CertWrap, wh_Client_CertUnwrapAndExport, wh_Client_CertUnwrapAndCache) as convenience wrappers around the existing keywrap functions, guarded by WOLFHSM_CFG_KEYWRAP
  • Modify server cert layer (wh_Server_CertReadTrusted, wh_Server_CertVerify, wh_Server_CertVerifyAcert) to support routing to keystore cache when the key type is WH_KEYTYPE_WRAPPED
  • Update all server request handlers (READTRUSTED, VERIFY, READTRUSTED_DMA, VERIFY_DMA, VERIFY_ACERT, VERIFY_ACERT_DMA) to translate the WH_KEYID_CLIENT_WRAPPED_FLAG and branch between cache and NVM paths
  • Add tests for cache-based cert verify, ReadTrusted, and DMA variants

Copilot AI review requested due to automatic review settings March 20, 2026 21:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds “wrapped cached certificates” support so clients can store trusted roots as AES-GCM wrapped blobs and have the server unwrap/cache them in RAM for cert verification, rather than requiring NVM-resident roots.

Changes:

  • Added client-side cert wrap/unwrap/export/cache convenience APIs (guarded by WOLFHSM_CFG_KEYWRAP).
  • Updated server cert manager + request handlers to route trusted-root reads/verifies to keystore cache when IDs indicate wrapped/cached certs.
  • Added documentation and new tests for cached-root verify/readtrusted (including DMA variants).

Reviewed changes

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

Show a summary per file
File Description
wolfhsm/wh_server_cert.h Updates cert APIs to accept whKeyId and documents cache vs NVM behavior.
src/wh_server_cert.c Implements cache-aware trusted-root reads and request-handler routing for wrapped IDs.
wolfhsm/wh_client.h Adds public client API declarations for cert wrap/unwrap/export/cache helpers.
src/wh_client_cert.c Implements the new client cert wrap/unwrap helper functions via keywrap APIs.
test/wh_test_cert.c Adds tests for cached-root verify/readtrusted, including DMA coverage.
docs/draft/wrapped-certs.md Adds draft documentation for workflow, ID encoding, and handler routing.

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

Copy link

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

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #318

Scan targets checked: wolfhsm-core-bugs, wolfhsm-src
Findings: 1

Medium (1)

DMA READTRUSTED cache path reads cert data before non-exportable check

File: src/wh_server_cert.c:652-670
Function: wh_Server_HandleCertRequest
Category: Logic errors

In the WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA handler, the new cache path calls wh_Server_KeystoreReadKey which writes the full certificate plaintext into cert_data (a DMA-mapped client-writable buffer obtained via wh_Server_DmaProcessClientAddress) before checking WH_NVM_FLAGS_NONEXPORTABLE on the metadata. In contrast, the NVM path in the same handler correctly calls wh_Nvm_GetMetadata first and only reads the certificate data (via wh_Server_CertReadTrusted) if the non-exportable flag is not set. This means for cached wrapped certs marked non-exportable, the plaintext is written to the DMA buffer before resp.rc is set to WH_ERROR_ACCESS. The non-DMA READTRUSTED handler has the same read-before-check ordering in its cache path, but cert_data is a server-local buffer there and resp.cert_len is not set on error, so the data is not included in the response.

if (req.id & WH_KEYID_CLIENT_WRAPPED_FLAG) {
    /* Cache path: translate and read cert + metadata */
    whKeyId certId = wh_KeyId_TranslateFromClient(
        WH_KEYTYPE_WRAPPED, server->comm->client_id,
        req.id);
    cert_len = req.cert_len;
    resp.rc  = wh_Server_KeystoreReadKey(
        server, certId, &meta, cert_data, &cert_len);
    if (resp.rc == WH_ERROR_OK) {
        if ((meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) !=
            0) {
            resp.rc = WH_ERROR_ACCESS;
        }
    }
}

Recommendation: Read the cached cert into a temporary server-side buffer first, check WH_NVM_FLAGS_NONEXPORTABLE on the returned metadata, and only copy to the DMA cert_data buffer if the cert is exportable. This matches the check-before-read pattern used in the NVM path. Alternatively, if wh_Server_KeystoreReadKey (or a variant) supports returning metadata without data (e.g., by passing NULL for the data buffer), use that to check exportability before the full read.


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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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


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

Copy link

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

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #318

Scan targets checked: wolfhsm-core-bugs, wolfhsm-src
Findings: 2

Medium (2)

Cache path in wh_Server_CertReadTrusted returns wrong error code on buffer-too-small

File: src/wh_server_cert.c:241-249
Function: wh_Server_CertReadTrusted
Category: Incorrect error handling

The newly added cache path in wh_Server_CertReadTrusted delegates to wh_Server_KeystoreReadKey, which returns WH_ERROR_NOSPACE when the certificate is larger than the provided buffer (see wh_Server_KeystoreReadKey line 652: if (cacheMeta->len > *outSz) return WH_ERROR_NOSPACE). However, the existing NVM path returns WH_ERROR_BUFFER_SIZE for the same condition. Furthermore, this PR's own newly-added @return documentation for this function explicitly states: "If the stored certificate data is too large for the buffer, WH_ERROR_BUFFER_SIZE will be returned." Any caller that checks for WH_ERROR_BUFFER_SIZE to detect and handle buffer-too-small (e.g., to retry with a larger buffer) will not catch WH_ERROR_NOSPACE from the cache path, leading to inconsistent error handling depending on whether the cert is in NVM or cache.

/* Cache path: wrapped certs are read from keystore cache, not NVM */
    if (WH_KEYID_TYPE(id) == WH_KEYTYPE_WRAPPED) {
        uint32_t sz = *inout_cert_len;
        rc          = wh_Server_KeystoreReadKey(server, id, &meta, cert, &sz);
        if (rc == WH_ERROR_OK) {
            *inout_cert_len = sz;
        }
        return rc;
    }

Recommendation: After the wh_Server_KeystoreReadKey call, translate WH_ERROR_NOSPACE to WH_ERROR_BUFFER_SIZE and update *inout_cert_len with the actual certificate size (available from meta.len after the call, since wh_Server_KeystoreReadKey populates outMeta before the size check). For example:

if (rc == WH_ERROR_NOSPACE) {
    *inout_cert_len = meta.len;
    return WH_ERROR_BUFFER_SIZE;
}

Non-exportable certificate data written to DMA buffer before access check in cache path

File: src/wh_server_cert.c:652-671
Function: wh_Server_HandleCertRequest
Category: DMA security issues

In the WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA handler, the new cache path calls wh_Server_KeystoreReadKey to read both the certificate data and metadata into cert_data before checking WH_NVM_FLAGS_NONEXPORTABLE. If the certificate is non-exportable, resp.rc is set to WH_ERROR_ACCESS, but the plaintext certificate data has already been written into the DMA-mapped cert_data buffer. In shared-memory DMA configurations (e.g., POSIX transport), cert_data may point directly to client-accessible memory, meaning the client can read the non-exportable certificate data from its DMA buffer despite the access denial. This contrasts with the NVM path in the same handler, which correctly checks WH_NVM_FLAGS_NONEXPORTABLE via wh_Nvm_GetMetadata before reading any certificate data into the buffer.

if (req.id & WH_KEYID_CLIENT_WRAPPED_FLAG) {
    /* Cache path: translate and read cert + metadata */
    whKeyId certId = wh_KeyId_TranslateFromClient(
        WH_KEYTYPE_WRAPPED, server->comm->client_id,
        req.id);
    cert_len = req.cert_len;
    resp.rc  = wh_Server_KeystoreReadKey(
        server, certId, &meta, cert_data, &cert_len);
    if (resp.rc == WH_ERROR_OK) {
        if ((meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) !=
            0) {
            resp.rc = WH_ERROR_ACCESS;
        }
    }
}

Recommendation: Match the NVM path's check-before-read pattern. Either (1) add a metadata-only query to the keystore cache API and check WH_NVM_FLAGS_NONEXPORTABLE before reading the data, or (2) zero the cert_data buffer with XMEMSET(cert_data, 0, cert_len) immediately after setting resp.rc = WH_ERROR_ACCESS to prevent any residual data from being accessible via DMA. Option (2) is the simpler fix and provides defense-in-depth even if the DMA post-processing is conditional on success.


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

@bigbrett bigbrett marked this pull request as ready for review March 23, 2026 19:19
Copy link

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

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #318

Scan targets checked: wolfhsm-core-bugs, wolfhsm-src
Findings: 1

High (1)

Buffer overflow in non-DMA READTRUSTED handler cache path due to missing cert_len reset

File: src/wh_server_cert.c:498-517
Function: wh_Server_HandleCertRequest
Category: Incorrect error handling

In the WH_MESSAGE_CERT_ACTION_READTRUSTED case, the cache path calls wh_Server_KeystoreReadKey twice: first with out=NULL to get metadata, then with cert_data to get the actual data. The first call overwrites cert_len with cacheMeta->len (the actual stored cert size) via the *outSz = cacheMeta->len assignment in wh_Server_KeystoreReadKey. The second call then passes this overwritten cert_len as *outSz, making the size check (cacheMeta->len > *outSz) always evaluate to false. This means memcpy(out, cacheBuffer, cacheMeta->len) proceeds unconditionally, even if the cert is larger than the actual output buffer (cert_data), which is bounded by min(max_transport_cert_len, WOLFHSM_CFG_MAX_CERT_SIZE). The DMA READTRUSTED handler correctly avoids this by resetting cert_len = req.cert_len before the second call.

rc = wh_Server_KeystoreReadKey(server, certId, &meta, NULL,
                                               &cert_len);
if (rc == WH_ERROR_OK) {
    if (meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) {
        rc = WH_ERROR_ACCESS;
    }
    else {
        rc = wh_Server_KeystoreReadKey(
            server, certId, NULL, cert_data, &cert_len);
        if (rc == WH_ERROR_OK) {
            resp.cert_len = cert_len;
        }

Recommendation: Save the original buffer capacity before the first call and restore it before the second call, matching the pattern used in the DMA handler. For example: uint32_t buf_capacity = cert_len; before the first call, then cert_len = buf_capacity; before the second wh_Server_KeystoreReadKey call.


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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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


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

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