Skip to content

Zephyr: add wolfPSA as a Zephyr PSA Crypto provider - #21

Open
Frauschi wants to merge 1 commit into
wolfSSL:masterfrom
Frauschi:zephyr-psa-provider
Open

Zephyr: add wolfPSA as a Zephyr PSA Crypto provider#21
Frauschi wants to merge 1 commit into
wolfSSL:masterfrom
Frauschi:zephyr-psa-provider

Conversation

@Frauschi

Copy link
Copy Markdown
Contributor

Summary

Adds a Zephyr module (modules/crypto/wolfPSA) that plugs wolfPSA into Zephyr's PSA_CRYPTO_PROVIDER_CUSTOM choice (Zephyr >= 4.3), supplying the PSA Crypto API in place of mbedTLS while reusing the wolfCrypt core compiled by the sibling wolfSSL Zephyr module. This gives Zephyr consumers (BLE host/mesh, TLS/Wi-Fi credentials, secure_storage, samples/psa/*) a wolfCrypt-backed PSA provider — and, paired with wolfCrypt FIPS, a certifiable crypto boundary that mbedTLS's PSA implementation cannot offer.

What's included

Provider wiring

  • Provider selection depends on WOLFSSL (never selects it) to avoid the socket Kconfig's !WOLFSSL dependency loop; consumers set CONFIG_WOLFSSL=y.
  • The PSA surface (PSA_WANT_*) is generated at CMake-configure time from the compiled wolfCrypt config intersected with what wolfPSA implements (zephyr/gen/gen-crypto-config.py + psa_want_probe.c), so it tracks the build.
  • wolfPSA owns no wolfCrypt config of its own — it follows the wolfSSL module's config and coexists with the wolfSSL TLS stack in one image (no forced WOLFCRYPT_ONLY).

Persistent keys

  • Backed by Zephyr secure_storage through a wolfCrypt AES-256-GCM custom ITS transform (zephyr/src/psa_its_transform_wolfcrypt.c) — encryption at rest with no Mbed TLS symbol in the image. src/psa_store_zephyr.c maps wolfPSA_Store_* onto the PSA ITS API; ZMS store backend on STM32H7 (128 KB flash sectors).

Thread safety & entropy

  • Optional thread-safe key store built on wolfCrypt's portable wc_*Mutex API.
  • DRBG seeding is owned by the wolfSSL module; wolfPSA requires HAVE_HASHDRBG.

Coverage

  • Favors Zephyr's own provider-agnostic PSA apps run against wolfPSA (psa_consumer reuses Zephyr's tests/crypto/mbedtls_psa ztest), plus bespoke suites: key-store lock (psa_concurrency), entropy path (psa_entropy), ITS transform confidentiality/tamper/uid-binding (psa_transform), persistent keys (psa_persistent_key), key purge (psa_purge), ITS round-trip (psa_its), secure storage (psa_secure_storage), store-unavailable fail-closed stub (psa_store_unavailable), and TLS + PSA coexistence in one image (psa_tls_coexist). Ships a psa_smoke sample.

Testing

  • Validated on native_sim/native/64 and hardware-validated on nucleo_h743zi (STM32H743ZI), including persistent keys stored to on-chip flash via the ZMS ITS backend.
  • Adds CI testing for the module for both Zephyr V4.3 and 4.4

Notes / requirements

@Frauschi Frauschi self-assigned this Jul 23, 2026
@Frauschi

Copy link
Copy Markdown
Contributor Author

The Zephyr CI tests will only succeed once wolfSSL/wolfssl#10983 is merged.

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

Scan targets checked: wolfpsa-bugs, wolfpsa-src

No new issues found in the changed files. ✅

Comment thread src/psa_key_storage.c Outdated
Comment on lines +2124 to +2132
if (status == PSA_SUCCESS) {
/* The key id is the storage locator, not part of the serialized attr
* blob, so it must be restored in the returned attributes. Preserve the
* deserialized lifetime, which psa_set_key_id() would otherwise reset to
* the default persistent lifetime. */
psa_key_lifetime_t lifetime = psa_get_key_lifetime(attributes);
psa_set_key_id(attributes, key_id);
psa_set_key_lifetime(attributes, lifetime);
}

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.

The key-id restore block this PR adds is unreachable for volatile keys: the function returns at :2098-2101 whenever wolfpsa_volatile_get_attributes() succeeds, so only persistent keys reach psa_set_key_id() at :2130.
The new behavior is therefore asymmetric — persistent keys get a populated id, volatile keys still get 0.
Proposed fix: Stamp the id inside wolfpsa_volatile_get_attributes() too, or hoist the added :2124-2132 block so both paths pass through it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changed so both paths now pass trough.

Comment thread src/psa_lock.h Outdated
Comment on lines +40 to +48
* Scope: this lock covers only wolfPSA's in-memory key-store structures.
* Per-operation crypto uses stack-local wolfCrypt contexts and per-call local
* WC_RNGs, and wolfpsa_get_key_data() returns an owned COPY of the key material,
* so no live list node escapes the locked region. The persistent-store backends
* are deliberately NOT covered by this lock and do not need it: they are
* concurrency-safe on their own (the file backend commits with an atomic
* temp-file rename and reads through a held file handle; a platform store such
* as Zephyr secure_storage owns its own consistency).
*/

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.

wolfpsa_persist_key() does a probe-then-write (Store_Open(read=1) → Close → Store_OpenSz(write)) that the new mutex does not span, so two threads importing the same persistent id both pass the probe and both get PSA_SUCCESS instead of ALREADY_EXISTS.

This makes the claim in this comment inaccurate (due to existing behavior not touched in this PR).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed by properly adding the lock calls, so imports are properly serialized

* When CONFIG_HWINFO is configured but the driver returns no id, key derivation
* fails closed (PSA_ERROR_HARDWARE_FAILURE) rather than silently falling back to
* a device-independent key. */
static psa_status_t wolfpsa_its_derive_key(secure_storage_its_uid_t uid,

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.

device-id is not the most secure way to derive secrets, as it is often "well-known" to an attacker.

Add the caveat to README.md, matching what Zephyr documents for its own device-id key provider; point production at a secure element / protected fuse / TF-M key. Also note the absence of

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in README

return (rc == 0) ? PSA_SUCCESS : PSA_ERROR_GENERIC_ERROR;
}

/* Build the GCM additional-authenticated-data: create_flags || uid. */

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.

rollback protection: the AAD built here binds flags+UID but nothing version-like, so an old ciphertext for the same UID replays. (See note about UID not being secret, this exposes the attestation to a replay attack)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added in documentation

Comment thread .github/workflows/zephyr-4.x.yml Outdated
-r "${{ steps.src.outputs.repo }}" \
-b "${{ steps.src.outputs.ref }}" \
-z "${{ matrix.zephyr-ref }}" \
--wolfssl-repo "https://github.com/${{ github.repository_owner }}/wolfssl" \

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.

github.repository_owner is always "wolfssl". This might be a leftover from testing from a private branc. Point to master so this PR depends on wolfSSL/wolfssl#10983

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Add a Zephyr module that plugs wolfPSA into the in-tree PSA_CRYPTO_PROVIDER
choice (CONFIG_PSA_CRYPTO_PROVIDER_CUSTOM, Zephyr >= 4.3), supplying the PSA
Crypto API in place of Mbed TLS while reusing the wolfCrypt core compiled by the
sibling wolfSSL Zephyr module.

  - Provider selection depends on WOLFSSL (never selects it) to avoid the socket
    Kconfig's !WOLFSSL dependency loop; consumers set CONFIG_WOLFSSL=y.
  - The PSA surface is generated at CMake-configure time from the compiled
    wolfCrypt config intersected with what wolfPSA implements, so it tracks the
    build. wolfPSA owns no wolfCrypt config of its own and follows the wolfSSL
    module's, coexisting with the wolfSSL TLS stack in one image.
  - Persistent keys are backed by Zephyr secure_storage via a wolfCrypt
    AES-256-GCM custom ITS transform (encryption at rest, no Mbed TLS symbol in
    the image); ZMS store backend on STM32H7.
  - Optional thread-safe key store built on wolfCrypt's portable wc_*Mutex API.
  - DRBG seeding is owned by the wolfSSL module; wolfPSA requires HAVE_HASHDRBG.
  - Coverage favors Zephyr's own provider-agnostic PSA apps run against wolfPSA,
    plus bespoke tests for the key-store lock, entropy path, and ITS transform.

Version numbering tracks wolfSSL.
@Frauschi
Frauschi force-pushed the zephyr-psa-provider branch from 7de3a9d to e15146a Compare July 28, 2026 16:29
@Frauschi
Frauschi requested a review from danielinux July 28, 2026 16:35
@Frauschi Frauschi assigned danielinux and unassigned Frauschi Jul 28, 2026
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.

3 participants