Skip to content

Add a PSA Crypto hashing backend (--enable-psa-crypto) - #550

Open
odudex wants to merge 8 commits into
ElementsProject:masterfrom
odudex:PSA-crypto
Open

odudex wants to merge 8 commits into
ElementsProject:masterfrom
odudex:PSA-crypto

Conversation

@odudex

@odudex odudex commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Addresses #544
Built on top of #549

ESP-IDF 6 ships Mbed TLS 4 / TF-PSA-Crypto, which makes the legacy mbedtls_sha256_*/mbedtls_sha512_* API private. As a result, --enable-mbed-tls no longer builds against it, and firmware such as Kern and Jade can't get hardware-accelerated hashing without compatibility hacks. This PR adds a SHA-256/SHA-512 backend that uses the public PSA Crypto API. It sits alongside the existing mbedtls backend.

Changes

  • build (fix): CMake used to select the mbedtls backend whenever the mbedtls headers were installed, and then failed to build. It's now opt-in via WALLYCORE_ENABLE_MBED_TLS, matching --enable-mbed-tls.
  • internal: wally_sha256_midstate() computes the midstate with the portable compression function when the hash context is opaque.
  • ccan: adds the CCAN_CRYPTO_SHA{256,512}_USE_PSA backend.
    • Streaming hashes use psa_hash_setup/update/finish, and the one-shot helpers use psa_hash_compute.
    • wally_init() calls psa_crypto_init().
    • Hash contexts are never copied, so tx_io context caching is disabled, as it already is for the OpenSSL and mbedtls backends.
    • Any PSA failure calls abort(). The ccan hash API can't report errors, and the ESP-IDF 6 driver allocates memory for every hash. A silently wrong hash could produce wrong seeds or keys.
  • build: adds --enable-psa-crypto / WALLYCORE_ENABLE_PSA_CRYPTO.
    • It links libtfpsacrypto or libmbedcrypto, whichever provides psa_crypto_init().
    • Amalgamation builds define HAVE_PSA_CRYPTO_H.
    • It can't be combined with the mbedtls backend.
  • ci: adds a test_psa_crypto job (full test suite plus valgrind) and PSA variants in the amalgamation matrix.
    • Adds contrib/esp-idf-psa-check, a build-only check that runs in the espressif/idf:v6.1 image.
    • libmbedtls-dev is added to the image dependency scripts. Until the pinned image is rebuilt, the PSA jobs install it themselves.

jgriffiths and others added 8 commits September 4, 2026 12:03
The CMake config template spelled the SHA-256 define as
HAVE_MBEDTLS_SHA256_H_ (trailing underscore), so src/ccan_config.h never
selected the mbedtls backend under CMake and silently built the software
SHA-256 instead. The --enable-mbed-tls help string was a copy of the
--enable-minimal one.
The HMAC implementation used sizeof() of a hash context member to obtain
the block size and used that member as scratch space for the key block.
That ties HMAC to the context layout of whichever hashing backend is
selected, and is impossible for opaque backends such as PSA Crypto.

Use the block size constant of the hash (64 for SHA-256, 128 for
SHA-512) and a local key block instead. The byte operations are
unchanged for all backends.
CMake selected the mbedtls backend whenever its headers were installed,
then failed to build it (it needs the ESP-IDF sdkconfig.h and never links
mbedtls). Add a WALLYCORE_ENABLE_MBED_TLS option, off by default, to
match --enable-mbed-tls.
…kends

wally_sha256_midstate() exports the running SHA-256 state from the hash
context, which is impossible for backends whose context is opaque, such
as PSA Crypto. Since only whole 64-byte blocks contribute to a midstate,
compute it directly with the portable compression function instead.

Expose the ccan software transform as sha256_sw_transform() when the
PSA backend is selected. The builtin, OpenSSL and mbedtls backends are
unchanged. The PSA paths added here are not compiled until the build
option that selects the backend is added later in this series.

Add a test for wally_sha256_midstate() with vectors computed
independently in Python.
Add CCAN_CRYPTO_SHA256_USE_PSA / CCAN_CRYPTO_SHA512_USE_PSA, implementing
the ccan hash API over psa_hash_setup/update/finish, with the one-shot
sha256()/sha512() helpers using psa_hash_compute() so that drivers can
use their one-shot (e.g. DMA) path.

PSA operations are opaque and may be heap-backed, so hash contexts are
never copied by value: tx_io.c disables its context caching for this
backend as it does for OpenSSL/mbedtls.

The ccan hash API cannot report errors, so any PSA failure calls
abort(). Returning a wrong hash instead would silently give wrong seeds,
keys or signature hashes, and PSA can fail at runtime: the ESP-IDF 6
driver allocates every multi-part operation from internal DMA memory.

wally_init() calls psa_crypto_init() when this backend is compiled in.
It is idempotent; ESP-IDF calls it at boot but other platforms may not.
Add an autotools option (and a matching WALLYCORE_ENABLE_PSA_CRYPTO
CMake option) that selects the PSA Crypto hashing backend. It checks
for psa/crypto.h and links whichever of libtfpsacrypto (TF-PSA-Crypto
1.0 / Mbed TLS 4) or libmbedcrypto (Mbed TLS 2.28/3.x) provides
psa_crypto_init(). Amalgamation users define HAVE_PSA_CRYPTO_H.

The option is mutually exclusive with --enable-mbed-tls (and
WALLYCORE_ENABLE_MBED_TLS), which targets the legacy Mbed TLS API that
Mbed TLS 4 / ESP-IDF 6 no longer expose.

Document the PSA requirements of wally_init() in its API docs.
Add libmbedtls-dev to the CI image dependencies (Debian bookworm ships
Mbed TLS 2.28 and trixie 3.6; both provide the psa/crypto.h hashing API
and libmbedcrypto) and a test_psa_crypto job that configures with
--enable-psa-crypto, runs the test suite and valgrind. Extend the
amalgamation matrix with HAVE_PSA_CRYPTO_H builds. Until the pinned CI
image is rebuilt with the new dependency, these jobs install it
themselves if it is missing.

Add contrib/esp-idf-psa-check, a minimal ESP-IDF project that compiles
the amalgamation with the PSA backend and the definitions used by
Jade's libwally component, and a build-only CI job running it in the
espressif/idf:v6.1 image for esp32s3.
@jgriffiths

Copy link
Copy Markdown
Contributor

Any PSA failure calls abort()

This is a non-starter unfortunately, we will need to be able to return errors to the callers. Which probably means wrapping or extending the ccan hash code.

@odudex

odudex commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

This is a non-starter unfortunately, we will need to be able to return errors to the callers. Which probably means wrapping or extending the ccan hash code.

Agreed on returning errors. Before I rework it, which direction would you prefer?

A) Extend ccan: sha256_done/sha512_done and one-shot sha256()/sha512() return bool, in all backends. Builtin always returns true, and mbedtls could finally report its return codes. PSA errors are sticky on the operation, so init/update can stay void: a failure always shows up when the digest is produced. Small ccan diff, but it moves the API further from upstream ccan.

B) Wrap: leave the ccan API untouched and add a wally-internal hash layer (init/update/done returning int). It uses ccan for the builtin backend, and the PSA code moves out of ccan into wally. ccan stays close to upstream, but the wally diff is bigger and hashing gets another layer.

Either way, wally passes the errors up as WALLY_ERROR, HMAC/PBKDF2 and the sighash code become fallible, and a fault-injection test checks every hashing path. I'd lean towards A, since src/ccan has already diverged a lot (the mbedtls backend, the x86 SHA assembly), but I'm happy either way.

@jgriffiths

Copy link
Copy Markdown
Contributor

@odudex Agreed on A as the best solution. Originally the ccan changes were going to be pushed to upstream but that never happened due to time constraints. I'm OK with us diverging further since it seems there are no more upstream changes coming.

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.

2 participants