From 14186666a5441c6b88a038d7eadb663ac900e0d0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 28 Sep 2026 16:36:52 +0200 Subject: [PATCH 1/4] ci: test against wolfSSL v5.9.4-stable in addition to master --- .github/workflows/psa-compliance.yml | 8 +++++++- .github/workflows/test-psa-api.yml | 16 ++++++++++++++-- .github/workflows/wolfcrypt-benchmark.yml | 8 +++++++- .github/workflows/wolfssl-psa-client-server.yml | 8 +++++++- .github/workflows/zephyr-4.x.yml | 9 +++++---- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/.github/workflows/psa-compliance.yml b/.github/workflows/psa-compliance.yml index bd4e7a4..74d3865 100644 --- a/.github/workflows/psa-compliance.yml +++ b/.github/workflows/psa-compliance.yml @@ -7,6 +7,12 @@ on: jobs: psa-compliance: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # master tracks upstream; the -stable tag is the release wolfPSA is + # validated against. Bump it together with the wolfPSA version. + wolfssl-ref: [ master, v5.9.4-stable ] steps: - name: Check out wolfPSA @@ -20,7 +26,7 @@ jobs: sudo apt-get install -y build-essential cmake git - name: Clone sibling wolfSSL - run: git clone --depth 1 https://github.com/wolfSSL/wolfssl ../wolfssl + run: git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} https://github.com/wolfSSL/wolfssl ../wolfssl - name: Build wolfPSA run: make diff --git a/.github/workflows/test-psa-api.yml b/.github/workflows/test-psa-api.yml index 4baa7eb..f0aa934 100644 --- a/.github/workflows/test-psa-api.yml +++ b/.github/workflows/test-psa-api.yml @@ -15,6 +15,12 @@ jobs: # library itself; the sibling wolfSSL only provides the link. test-ecc-curve-families: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # master tracks upstream; the -stable tag is the release wolfPSA is + # validated against. Bump it together with the wolfPSA version. + wolfssl-ref: [ master, v5.9.4-stable ] steps: - name: Check out wolfPSA @@ -26,7 +32,7 @@ jobs: sudo apt-get install -y build-essential autoconf automake libtool libtool-bin pkg-config - name: Clone sibling wolfSSL - run: git clone --depth 1 https://github.com/wolfSSL/wolfssl ../wolfssl + run: git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} https://github.com/wolfSSL/wolfssl ../wolfssl - name: Build variant user_settings (Koblitz + Brainpool) run: | @@ -61,6 +67,12 @@ jobs: test-psa-api: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # master tracks upstream; the -stable tag is the release wolfPSA is + # validated against. Bump it together with the wolfPSA version. + wolfssl-ref: [ master, v5.9.4-stable ] steps: - name: Check out wolfPSA @@ -72,7 +84,7 @@ jobs: sudo apt-get install -y build-essential autoconf automake libtool libtool-bin pkg-config - name: Clone sibling wolfSSL - run: git clone --depth 1 https://github.com/wolfSSL/wolfssl ../wolfssl + run: git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} https://github.com/wolfSSL/wolfssl ../wolfssl - name: Build wolfPSA run: make diff --git a/.github/workflows/wolfcrypt-benchmark.yml b/.github/workflows/wolfcrypt-benchmark.yml index 3196ec5..87397d1 100644 --- a/.github/workflows/wolfcrypt-benchmark.yml +++ b/.github/workflows/wolfcrypt-benchmark.yml @@ -7,6 +7,12 @@ on: jobs: wolfcrypt-benchmark: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # master tracks upstream; the -stable tag is the release wolfPSA is + # validated against. Bump it together with the wolfPSA version. + wolfssl-ref: [ master, v5.9.4-stable ] steps: - name: Check out wolfPSA @@ -18,7 +24,7 @@ jobs: sudo apt-get install -y build-essential - name: Clone sibling wolfSSL - run: git clone --depth 1 https://github.com/wolfSSL/wolfssl ../wolfssl + run: git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} https://github.com/wolfSSL/wolfssl ../wolfssl - name: Build wolfPSA run: make diff --git a/.github/workflows/wolfssl-psa-client-server.yml b/.github/workflows/wolfssl-psa-client-server.yml index c5f6b5a..0cf3677 100644 --- a/.github/workflows/wolfssl-psa-client-server.yml +++ b/.github/workflows/wolfssl-psa-client-server.yml @@ -7,6 +7,12 @@ on: jobs: wolfssl-psa-client-server: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # master tracks upstream; the -stable tag is the release wolfPSA is + # validated against. Bump it together with the wolfPSA version. + wolfssl-ref: [ master, v5.9.4-stable ] steps: - name: Check out wolfPSA @@ -18,7 +24,7 @@ jobs: sudo apt-get install -y build-essential autoconf automake libtool libtool-bin pkg-config - name: Clone sibling wolfSSL - run: git clone --depth 1 https://github.com/wolfSSL/wolfssl ../wolfssl + run: git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} https://github.com/wolfSSL/wolfssl ../wolfssl - name: Build wolfPSA run: make diff --git a/.github/workflows/zephyr-4.x.yml b/.github/workflows/zephyr-4.x.yml index 7e0c87d..304490a 100644 --- a/.github/workflows/zephyr-4.x.yml +++ b/.github/workflows/zephyr-4.x.yml @@ -20,7 +20,7 @@ concurrency: jobs: build: - name: ${{ matrix.zephyr-ref }} + name: ${{ matrix.zephyr-ref }} / wolfSSL ${{ matrix.wolfssl-ref }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} runs-on: ubuntu-22.04 timeout-minutes: 60 @@ -29,6 +29,8 @@ jobs: matrix: # 4.3 = the wolfPSA floor (upstream), 4.4 = current target. zephyr-ref: [ v4.3.0, v4.4.0 ] + # wolfSSL master plus the -stable tag wolfPSA is validated against. + wolfssl-ref: [ master, v5.9.4-stable ] steps: - name: Checkout wolfPSA CI driver uses: actions/checkout@v5 @@ -59,9 +61,8 @@ jobs: - name: Build and run wolfPSA samples/tests working-directory: .github/scripts/zephyr-4.x run: | - # wolfSSL repo/ref default to upstream wolfSSL/wolfssl master in the - # script. bash ./zephyr-test.sh \ -r "${{ steps.src.outputs.repo }}" \ -b "${{ steps.src.outputs.ref }}" \ - -z "${{ matrix.zephyr-ref }}" + -z "${{ matrix.zephyr-ref }}" \ + --wolfssl-ref "${{ matrix.wolfssl-ref }}" From 8c0035f3e2d055263d5d029cf01c530db275a9a9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 28 Sep 2026 16:36:52 +0200 Subject: [PATCH 2/4] Prepare v5.9.4 release: changelog, README tested wolfSSL version --- CHANGELOG.md | 10 +++++++--- README.md | 17 +++++++++++++++-- user_settings.h | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c491a19..a6625a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Changelog -## Unreleased: PSA Certified Crypto API 1.4 + PQC extension 1.4 +## v5.9.4 + +PSA Certified Crypto API 1.4 + PQC extension 1.4. Upgrade of the public API surface and implementation to PSA Certified -Crypto API 1.4 Final and the PQC extension 1.4, built against current -wolfSSL master. +Crypto API 1.4 Final and the PQC extension 1.4. This release is validated +against wolfSSL `v5.9.4-stable`: unit tests, TLS client/server, wolfCrypt +benchmark, the Arm PSA Architecture Test Suite and the Zephyr 4.3/4.4 +samples run in CI against both that tag and wolfSSL master. ### Breaking changes diff --git a/README.md b/README.md index ce24a07..fcfeb8f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,19 @@ wolfPSA is a PSA Crypto **engine** that implements the PSA Crypto API on ## Build +### wolfSSL version + +wolfPSA follows wolfSSL version numbering: wolfPSA v5.9.4 is tested against +wolfSSL `v5.9.4-stable`. To build against the matching release: + +``` +git clone --branch v5.9.4-stable https://github.com/wolfSSL/wolfssl +``` + +CI also tests against wolfSSL master. + +### Building + The build system assumes that wolfSSL is available in a sibling directory under the same path as this repository, e.g: @@ -55,9 +68,9 @@ make wolfPSA WOLFSSL_PATH=~/src/wolfssl ## Testing (PSA Architecture Test Suite) -See `psa-arch-tests/README.md` for instructions on how to run the tests. +See `test-psa-api/README.md` for instructions on how to run the tests. -### Current PSA crypto test results: +### Current PSA crypto test results (wolfSSL v5.9.4-stable): ``` ************ Crypto Suite Report ************ diff --git a/user_settings.h b/user_settings.h index 2bb8dfe..5bef3d8 100644 --- a/user_settings.h +++ b/user_settings.h @@ -116,7 +116,7 @@ #define WOLFSSL_LMS_VERIFY_ONLY #define WOLFSSL_HAVE_XMSS #define WOLFSSL_XMSS_VERIFY_ONLY -/* Ascon is marked experimental in wolfSSL master and refuses to build +/* Ascon is marked experimental in wolfSSL (5.9.4+) and refuses to build * without this opt-in. */ #define WOLFSSL_EXPERIMENTAL_SETTINGS #define HAVE_ASCON From 868ac3475ab89ada1a4ba8ff9c115f06ba46a47f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 29 Sep 2026 09:41:00 +0200 Subject: [PATCH 3/4] ci: run build config matrix against wolfSSL v5.9.4-stable too; update zephyr-test.sh ref comment --- .github/scripts/zephyr-4.x/zephyr-test.sh | 7 ++++--- .github/workflows/build-config-matrix.yml | 13 ++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/scripts/zephyr-4.x/zephyr-test.sh b/.github/scripts/zephyr-4.x/zephyr-test.sh index cdcb6ad..79705d9 100644 --- a/.github/scripts/zephyr-4.x/zephyr-test.sh +++ b/.github/scripts/zephyr-4.x/zephyr-test.sh @@ -22,13 +22,14 @@ # Examples: # ./zephyr-test.sh -z v4.3.0 # ./zephyr-test.sh -r https://github.com/me/wolfPSA -b my-fix -z v4.4.0 +# ./zephyr-test.sh -z v4.4.0 --wolfssl-ref v5.9.4-stable set -euo pipefail # Defaults point at the upstream wolfSSL org. The workflow sets -r/-b to the -# wolfPSA code under test and leaves the wolfSSL repo/ref at these upstream -# defaults. The --wolfssl-repo/--wolfssl-ref flags remain for pointing at a -# fork locally. +# wolfPSA code under test and --wolfssl-ref from its matrix (master plus the +# -stable tag wolfPSA is validated against), leaving the wolfSSL repo at the +# upstream default. --wolfssl-repo remains for pointing at a fork locally. WOLFPSA_REPO="https://github.com/wolfSSL/wolfPSA" WOLFPSA_BRANCH="master" ZEPHYR_VERSION="v4.4.0" diff --git a/.github/workflows/build-config-matrix.yml b/.github/workflows/build-config-matrix.yml index ddeefc9..b1d015b 100644 --- a/.github/workflows/build-config-matrix.yml +++ b/.github/workflows/build-config-matrix.yml @@ -6,11 +6,14 @@ on: jobs: build-config-matrix: + name: ${{ matrix.config.name }} / wolfSSL ${{ matrix.wolfssl-ref }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - include: + # wolfSSL master plus the -stable tag wolfPSA is validated against. + wolfssl-ref: [ master, v5.9.4-stable ] + config: - name: baseline modifiers: "" - name: md5 @@ -100,9 +103,9 @@ jobs: sudo apt-get install -y build-essential - name: Clone sibling wolfSSL - run: git clone --depth 1 https://github.com/wolfSSL/wolfssl ../wolfssl + run: git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} https://github.com/wolfSSL/wolfssl ../wolfssl - - name: Build ${{ matrix.name }} + - name: Build ${{ matrix.config.name }} env: - BUILD_TARGET: ${{ matrix.target || 'libwolfpsa.a' }} - run: ./build-test/build-variant.sh "${{ matrix.name }}" ${{ matrix.modifiers }} + BUILD_TARGET: ${{ matrix.config.target || 'libwolfpsa.a' }} + run: ./build-test/build-variant.sh "${{ matrix.config.name }}" ${{ matrix.config.modifiers }} From 690088ef9c3b2e34cf4f9004e78d911c8771c7f2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 29 Sep 2026 09:53:13 +0200 Subject: [PATCH 4/4] changelog: record all v5.9.4 behaviour changes, security fixes and bug fixes --- CHANGELOG.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6625a6..f2fd29a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,50 @@ samples run in CI against both that tag and wolfSSL master. by neither the effective uid nor root, is refused, and every ancestor is checked as well (a shared parent such as `/tmp` is accepted when sticky). Stores that relied on a permissive directory will stop opening. +- wolfSSL >= 5.9.2 is required: the ML-DSA backend moved to `wc_mldsa.c`, + which wolfSSL v5.9.1 does not ship (this release is tested against + v5.9.4-stable). Custom `user_settings.h` files enable ML-DSA with + `WOLFSSL_HAVE_MLDSA`, not `HAVE_DILITHIUM`. +- Overlapping input and output buffers are refused with + `PSA_ERROR_NOT_SUPPORTED` in `psa_cipher_encrypt()`/`psa_cipher_decrypt()` + (every mode with an IV) and in `psa_cipher_update()` for the block modes + (ECB, CBC, CBC-PKCS7). In-place `psa_cipher_update()` still works for the + stream modes (CTR, CFB, OFB, CCM*, ChaCha20). +- Multipart AEAD streams: `psa_aead_update()` returns output as it goes and + `psa_aead_finish()`/`psa_aead_verify()` emit only the tag, per the PSA + contract, instead of buffering the whole payload until finish. Callers must + size the update output buffers accordingly. Additional data supplied after + payload has started is rejected with `PSA_ERROR_BAD_STATE`. +- Auto-assigned key ids come from the vendor range + (`PSA_KEY_ID_VENDOR_MIN`..`PSA_KEY_ID_VENDOR_MAX`) instead of starting at 1, + so they can no longer collide with caller-chosen persistent ids. +- Importing a persistent key under an id already in use fails with + `PSA_ERROR_ALREADY_EXISTS` instead of overwriting the stored key. +- Key lifetimes that name a storage location other than local storage are + rejected instead of being stored locally in plaintext. +- Stricter key policies: key-derivation inputs require + `PSA_KEY_USAGE_DERIVE`; key agreement checks the full policy algorithm, not + only its base; permission checks require every requested usage bit; + `PSA_ALG_ANY_HASH` signature policies are honoured (within one family for + HashML-DSA); `psa_copy_key()` accepts narrowing a wildcard policy to a + concrete algorithm and intersects MAC/AEAD length wildcards. +- `psa_import_key()` checks declared bits against the data length for + byte-string key types (HMAC, RAW_DATA, DERIVE, PASSWORD, PASSWORD_HASH, + PEPPER), and refuses imports whose inferred bits overflow. +- Changed status codes: an empty signature, tag or reference digest reports + `PSA_ERROR_INVALID_SIGNATURE`, as does a wrong-length raw ECDSA signature; + a failed RSA decrypt unpad reports `PSA_ERROR_INVALID_PADDING`; an + unsupported GCM nonce length reports `PSA_ERROR_NOT_SUPPORTED`; store + allocation failures report `PSA_ERROR_INSUFFICIENT_MEMORY`; a store record + that exists but cannot be opened reports `PSA_ERROR_STORAGE_FAILURE` + instead of `PSA_ERROR_INVALID_HANDLE`. +- `NULL` buffers with zero length or zero capacity are accepted across the + API (outputs, nonces, tags, exports, IV generation, encapsulation), and a + zero-byte generation request succeeds. +- Shortened-tag ChaCha20-Poly1305, XChaCha20-Poly1305 and Ascon AEAD + algorithms are rejected at setup. Ed448 PureEdDSA rejects a non-empty + context. TLS 1.2 PSK-to-MS rejects PSKs above + `PSA_TLS12_PSK_TO_MS_PSK_MAX_SIZE` (128). ### Added @@ -98,17 +142,58 @@ samples run in CI against both that tag and wolfSSL master. AES-KW, RIPEMD-160, MD5, Ascon and ChaCha20-Poly1305 stay local; `wolfpsa/psa_engine.h` documents why, plus the X25519 and `WOLF_CRYPTO_CB_FIND` caveats. -- Fixed: the HMAC path of `psa_mac_*` never called `wc_HmacInit()`, so it - ran with devId 0 and a callback registered on device 0 captured wolfPSA's - HMACs while every other algorithm stayed local. -- Fixed: the one-shot AEAD paths called `wc_AesFree()` on uninitialized heap - when `wc_AesInit()` failed, reading `Aes.devId` and freeing `Aes.streamData` - from unwritten memory. Allocation and init are now one step. - Optional thread-safe key store: with `WOLFPSA_THREAD_SAFE` a single mutex built on wolfCrypt's portable `wc_*Mutex` API (created in `psa_crypto_init()`) guards the volatile-key list and id counter for concurrent PSA callers; a no-op in single-threaded builds. +### Security + +- Side-channel hardening is on in the default build: `WC_NO_HARDEN` was + replaced with `TFM_TIMING_RESISTANT`, `ECC_TIMING_RESISTANT` and + `WC_RSA_BLINDING`, so RSA and ECC private-key operations use the + constant-time paths and RSA blinding. +- `psa_import_key()` rejects a `data_length` that would wrap the internal + buffer size (a heap overflow for key types without an exact-length check). +- ECC keys are pinned to their curve: a public key on import and verify, the + peer point in ECDH, and the exported public key. secp256k1 and Brainpool + are only accepted when `HAVE_ECC_KOBLITZ` / `HAVE_ECC_BRAINPOOL` are set. +- Sensitive intermediate data is zeroized: cipher partial blocks and padded + plaintext on every exit, the CCM init/update/finish stack state, the + computed tag in multipart verify, KDF output on a mid-stream error, and + export buffers on a short key-data read. +- A failed `psa_copy_key()` clears the target id, and the interruptible + max-ops setting is held in an atomic. + +### Fixed + +- RSA PKCS#1 v1.5 hashed verify compared the recovered DigestInfo with the + raw hash, so every valid signature failed; `hash_length` is now bound to + the algorithm's digest length on sign and verify. +- PBKDF2-AES-CMAC-PRF-128 normalized 16-byte passwords instead of using them + directly (RFC 4615), deriving keys that disagreed with other implementations. +- The CCM counter increment lost its carry for nonces shorter than 13 bytes, + silently corrupting multipart CCM output past the first block. +- The TLS 1.2 PRF KDFs passed the wrong MAC algorithm id to `wc_PRF_TLS()`. +- OFB and CFB decryption keyed AES for the decrypt direction; both modes + run the block cipher forward, so they now use the encrypt key schedule. +- Ed25519ph/Ed448ph sign/verify enforce a 64-byte prehash, and SHAKE256-512 + was added to the hash engine so `psa_sign_message()` and + `psa_verify_message()` work for Ed448ph. +- Standalone EdDSA and Montgomery key generation and public-key export work in + builds without `HAVE_ECC`; `psa_export_public_key()` no longer falls through + for disabled PQC backends. +- A second `psa_aead_set_lengths()` call reports `PSA_ERROR_BAD_STATE`; + multipart AEAD length checks and the streaming GCM guards were corrected. +- `psa_hash_compare()` rejects a `NULL` reference hash; generic ECDH reports + NOT_SUPPORTED up front when no RNG is built, instead of failing at the end. +- The HMAC path of `psa_mac_*` never called `wc_HmacInit()`, so it + ran with devId 0 and a callback registered on device 0 captured wolfPSA's + HMACs while every other algorithm stayed local. +- The one-shot AEAD paths called `wc_AesFree()` on uninitialized heap + when `wc_AesInit()` failed, reading `Aes.devId` and freeing `Aes.streamData` + from unwritten memory. Allocation and init are now one step. + ### Build configuration - AES backend policy (`src/psa_config.h`, included by every wolfPSA source): @@ -131,6 +216,11 @@ samples run in CI against both that tag and wolfSSL master. - The multipart AEAD context holds its GCM and CCM `Aes` in a union, since an operation is one or the other. Under `WC_AES_BITSLICED` that takes `sizeof(wolfpsa_aead_ctx_t)` from 247,032 bytes to 123,736. +- `make unit-run` builds and runs every unit and regression test, and + `make cov` produces an HTML gcov coverage report for `src/`. +- CI runs the regression suite, a build-configuration matrix with each + feature switched off, and every functional workflow against both wolfSSL + master and `v5.9.4-stable`. ### Zephyr module @@ -149,6 +239,9 @@ samples run in CI against both that tag and wolfSSL master. `psa_its_*`/`psa_ps_*`). - wolfPSA follows the user's wolfCrypt configuration and exposes exactly the enabled, wolfPSA-implemented algorithms as the PSA API. +- The module selects the wolfSSL module's constant-time AES backend and ECC + zero-hash allowance by default, and the store returns `MEMORY_E` when an + allocation fails. ## v5.9.1