diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 7a6f5b075..0bd61279b 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -24,9 +24,17 @@ An accepted upload queues analysis; results appear after Coverity processes it. | **Fast (per-PR)** | every PR; push to main | host unit suites, ISO C99, house style, bare-scope scan, Arm PSA-FF conformance, cross-compile, compiler matrix, sanitizers, valgrind, integrations, core/port split guard | | **M33MU smoke** | every PR | per port, on both crypto engines: STM32H563 `positive gtzcneg crossdomain bothpsa confboot devcrypto`; MIMXRT700 `positive ahbscneg crossdomain bothpsa confboot devcrypto` | | **M33MU full** | PR labels `ci:all`, `ci:stm32h563`, `ci:imxrt700`; push to main; `cron: 0 8 * * *`; `workflow_dispatch` (port input) | every scenario of that port on both engines (see below) | -| **AArch64 smoke** | every PR | a representative subset: `virt-gicv3-a72` `smoke boot positive crossdomain ffa-direct confboot devcrypto` on both crypto engines, and `versal-virt` the same on the native engine | +| **AArch64 smoke** | every PR | a representative subset: `virt-gicv3-a72` `smoke boot positive crossdomain ffa-direct confboot devcrypto` on both crypto engines, `versal-virt` the same on the native engine, plus dedicated QEMU fwTPM and PKCS#11 interface and isolation jobs | | **AArch64 full** | PR labels `ci:all`, `ci:aarch64`, `ci:qemu-virt`, `ci:qemu-versal`; push to main; nightly; `workflow_dispatch` (cell input) | every AArch64 scenario and the Arm FF-A ACS groups on three QEMU cells under both crypto engines (see below) | +All dedicated API and interface-isolation rows require a real terminal pass +marker; a successful skip cannot satisfy the job. Each job identifies its +engine and operation profile. M33MU runs 24 positives across +`[default, full] x [native, hsm] x [psa, pkcs11, tpm] x [zephyr, freertos]`, plus +eight interface-isolation jobs. QEMU `virt` runs 20 positives/isolation jobs +across both profiles and engines. Versal cross-builds both engines, but its QEMU +model cannot provide the XMPU/RISAF isolation those runtime scenarios require. + The M33MU workflow (`m33mu.yml`) is label-selected the way wolfProvider's `pr-osp-select.yml` is: a `select` job checks the routing table (`scenario_matrix.py --selftest`), reads the PR's `ci:*` labels, the @@ -63,6 +71,10 @@ test-target` also reads. The table below is a representative slice: | `stm32h563_confboot_` | `confboot` | full Arm FF-M IPC suite | | `stm32h563_devstorage_` | `devstorage` | PSA ITS/PS conformance | | `stm32h563_devcrypto_` | `devcrypto` | PSA Crypto conformance | +| `stm32h563_pkcs11___` | `pkcs11` | each RTOS application runs the nine-case default or 40-case full PKCS#11 suite through `SERVICE_PKCS11` and the isolated provider | +| `stm32h563_tpm___` | `tpm` | each RTOS application sends real TPM 2.0 commands through `SERVICE_TPM` to the isolated fwTPM guest | +| `stm32h563_pkcs11neg__` | `pkcs11neg` | PKCS#11 partition-band isolation and provider recovery | +| `stm32h563_tpmneg__` | `tpmneg` | TPM relay and fwTPM partition-band isolation | | `stm32h563_vaultrecover_` | `vaultrecover` | #95 foreign pool reformatted | | `stm32h563_vaultrecoversec_` | `vaultrecoversec` | #95 SECURED never wipes | | `mimxrt700_positive_` | `positive` | MIMXRT700 chain under the RT700 model, both guests finish | @@ -114,6 +126,11 @@ full matrix. The cells and the smoke subset live in | Check name | What it proves | |------------|----------------| +| `qemuvirt_psa_` | the Normal-world PSA client uses the selected engine through FF-A | +| `qemuvirt_tpm__` | the Normal-world application sends real TPM 2.0 GetRandom, CreatePrimary, Sign, and full-profile PCR Extend/Read commands through FF-A and `SERVICE_TPM` to the isolated fwTPM | +| `qemuvirt_tpmneg__` | the fwTPM round trip plus TPM state-band, provider-RAM, and execute-never isolation negatives | +| `qemuvirt_pkcs11__` | the Normal-world application runs nine default or 40 full PKCS#11 cases through FF-A and the isolated provider | +| `qemuvirt_pkcs11neg__` | the PKCS#11 suite plus state-band, provider-RAM, execute-never, and engine-appropriate restart/recovery negatives | | `el3__` | the EL3 image links under the symbol guard, then the tier's QEMU AArch64 scenarios run through `tests/target/run_suite.sh qemu-a` | | `ffa_acs__` | the Arm FF-A ACS groups (discovery, direct and indirect messaging, memory, notifications, interrupts) at their asserted floors (full tier only) | diff --git a/.github/workflows/aarch64-cross-compile.yml b/.github/workflows/aarch64-cross-compile.yml index 7f757cf16..87cb4adb6 100644 --- a/.github/workflows/aarch64-cross-compile.yml +++ b/.github/workflows/aarch64-cross-compile.yml @@ -50,6 +50,62 @@ jobs: for e in json.load(sys.stdin): print(e["job"], "(" + e["engine"] + ")")' + interfaces-qemu-a: + name: qemuvirt_${{ matrix.scenario }}_${{ matrix.engine }}_${{ matrix.profile }} + if: >- + github.event_name != 'workflow_dispatch' || inputs.cell == '' || + inputs.cell == 'all' || inputs.cell == 'qemu-virt' || + inputs.cell == 'virt-gicv3-a72' + runs-on: ubuntu-latest + timeout-minutes: 45 + container: + image: ghcr.io/wolfssl/wolfboot-ci-aarch64:v1.15 + strategy: + fail-fast: false + matrix: + scenario: [positive, tpm, tpmneg, pkcs11, pkcs11neg] + engine: [native, hsm] + profile: [default, full] + env: + WT_ENGINE: ${{ matrix.engine }} + WT_CRYPTO_PROFILE: ${{ matrix.profile }} + WT_REQUIRE_SCENARIO_PASS: 1 + MACHINE: virt + GIC: 3 + CPU: cortex-a72 + SMP: 1 + steps: + - uses: actions/checkout@v4 + + - name: Trust the checked-out workspace + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Initialize wolfTrust dependencies + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --single-branch lib/wolfSSL lib/wolfHSM lib/wolfCOSE lib/wolfPSA lib/wolfTPM lib/wolfPKCS11 + + - name: Toolchain and emulator versions + run: | + aarch64-none-elf-gcc --version | head -1 + qemu-system-aarch64 --version | head -1 + + - name: Self-test the EL3 symbol guard and the expect library + run: | + tools/check-el3-symbols.sh --selftest + tests/target/lib/expect.sh --selftest + + - name: Run the direct SPM interface scenario + run: tests/target/run_suite.sh qemu-a ${{ matrix.scenario }} + + - name: Show the boot logs + if: always() + run: | + for f in logs/target-*.log; do + echo "== $f" + cat "$f" || true + done + qemu-a: name: ${{ matrix.job }} needs: select @@ -78,7 +134,7 @@ jobs: - name: Initialize wolfTrust dependencies run: | git config --global url."https://github.com/".insteadOf "git@github.com:" - git submodule update --init --single-branch lib/wolfSSL lib/wolfHSM lib/wolfCOSE lib/wolfPSA + git submodule update --init --single-branch lib/wolfSSL lib/wolfHSM lib/wolfCOSE lib/wolfPSA lib/wolfTPM lib/wolfPKCS11 - name: Toolchain and emulator versions run: | @@ -95,6 +151,17 @@ jobs: if: matrix.kind == 'suite' run: make ARCH=aarch64 TARGET=${{ matrix.target }} WT_GIC_VERSION="$GIC" WT_CPU="$CPU" BUILD_DIR=build-aarch64-guard el3-image + - name: Cross-build the Versal TPM and PKCS11 interfaces + if: matrix.kind == 'suite' && matrix.target == 'versal' + run: | + make ARCH=aarch64 TARGET=versal WT_GIC_VERSION="$GIC" \ + WT_CPU="$CPU" WT_PORT_BOOT_CPUS=1 WT_ENGINE=${{ matrix.engine }} CONFIG_TPM=y \ + WT_ATTEST_COSE=0 BUILD_DIR=build-versal-tpm secure-image + make ARCH=aarch64 TARGET=versal WT_GIC_VERSION="$GIC" \ + WT_CPU="$CPU" WT_PORT_BOOT_CPUS=1 CONFIG_PKCS11=y \ + WT_ENGINE=${{ matrix.engine }} WT_ATTEST_COSE=0 \ + BUILD_DIR=build-versal-pkcs11 secure-image + - name: Fetch the pinned Arm FF-A ACS once for the six groups if: matrix.kind == 'acs' run: | diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index a6ac52aa6..18a497135 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -25,7 +25,9 @@ jobs: git config --global url."https://github.com/".insteadOf git@github.com: git submodule update --init --depth 1 --jobs 4 \ lib/wolfCOSE lib/wolfHSM lib/wolfPSA lib/wolfSSL \ - lib/wolfhal + lib/wolfTPM lib/wolfPKCS11 lib/wolfhal + test -f lib/wolfTPM/src/fwtpm/fwtpm.c + test -f lib/wolfPKCS11/src/wolfpkcs11.c - name: Build STM32H563 secure firmware run: make -C tests/firmware/stm32h563 all TOOLPREFIX=arm-none-eabi- - name: Validate the LTO gate, rebuild, and size reduction @@ -96,3 +98,13 @@ jobs: WT_CONFORMANCE=1 secure-image TOOLPREFIX=arm-none-eabi- \ BUILD_DIR=build_rt700_conf_$engine done + - name: Build both interfaces with both crypto engines on both ports + run: | + for engine in native hsm; do + for target in stm32h563 mimxrt700; do + make TARGET="$target" CONFIG_TPM=y WT_ENGINE="$engine" \ + WT_ATTEST_COSE=0 BUILD_DIR="build-$target-tpm-$engine" secure-image + make TARGET="$target" CONFIG_PKCS11=y WT_ENGINE="$engine" \ + WT_ATTEST_COSE=0 BUILD_DIR="build-$target-pkcs11-$engine" secure-image + done + done diff --git a/.github/workflows/m33mu.yml b/.github/workflows/m33mu.yml index 91bdd2382..8bc9e447f 100644 --- a/.github/workflows/m33mu.yml +++ b/.github/workflows/m33mu.yml @@ -87,15 +87,20 @@ jobs: run: | ./test-app/emu-test-apps/test.sh - wolfboot-wolftrust-m33mu: - name: stm32h563_${{ matrix.guest }}_lifecycle_${{ matrix.engine }} + interfaces-m33mu: + name: stm32h563_${{ matrix.api }}_${{ matrix.os }}_${{ matrix.engine }}_${{ matrix.profile }} + if: >- + github.event_name != 'workflow_dispatch' || + inputs.port == 'all' || inputs.port == 'stm32h563' runs-on: ubuntu-latest strategy: fail-fast: false matrix: - guest: [zephyr, freertos] + api: [psa, pkcs11, tpm] + os: [zephyr, freertos] engine: [native, hsm] - timeout-minutes: 45 + profile: [default, full] + timeout-minutes: 60 container: image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 env: @@ -103,6 +108,9 @@ jobs: # upstreamed; upstream master does not yet ship this wolfTrust config. WOLFBOOT_REF: d85fa9dbdf6c36f47b7e96eba5c9df750ad3c963 WT_ENGINE: ${{ matrix.engine }} + WT_CRYPTO_PROFILE: ${{ matrix.profile }} + WT_INTERFACE_OS: ${{ matrix.os }} + WT_REQUIRE_SCENARIO_PASS: 1 CROSS_COMPILE: /usr/local/bin/arm-none-eabi- ZEPHYR_TOOLCHAIN_VARIANT: cross-compile # The wolfBoot configuration reserves the wolfTrust partition at the @@ -120,7 +128,7 @@ jobs: WT_ZEPHYR_DTC_OVERLAY_FILE: boards/wolfboot-stm32h563.overlay WT_MAX_GUESTS: 2 ZEPHYR_BOARD: nucleo_h563zi/stm32h563xx/ns - WT_TEST_GUEST: ${{ matrix.guest }} + WT_TEST_GUEST: ${{ matrix.os }} # Upstream master with danielinux/m33mu PR #21 (deterministic USART IRQ timing). M33MU_REF: f3c03675260264cdec815adebe4b020bb6fe57b8 steps: @@ -132,6 +140,7 @@ jobs: git config --global url."https://github.com/".insteadOf git@github.com: - name: Build pinned upstream M33MU + if: matrix.api == 'psa' && matrix.profile == 'full' run: | git clone --no-checkout https://github.com/danielinux/m33mu.git m33mu git -C m33mu fetch --depth 1 origin "$M33MU_REF" @@ -141,13 +150,16 @@ jobs: echo "M33MU=$GITHUB_WORKSPACE/m33mu/build/m33mu" >> "$GITHUB_ENV" - name: Initialize wolfTrust dependencies + if: matrix.api == 'psa' && matrix.profile == 'full' run: git submodule update --init --single-branch - name: Prepare Zephyr and FreeRTOS workspaces + if: matrix.api == 'psa' && matrix.profile == 'full' working-directory: tests/firmware/zephyr-stm32h5 run: make clone - name: Fetch secure-app wolfBoot + if: matrix.api == 'psa' && matrix.profile == 'full' run: | git clone --no-checkout https://github.com/aidangarske/wolfBoot.git wolfBoot git -C wolfBoot fetch --depth 1 origin "$WOLFBOOT_REF" @@ -155,6 +167,7 @@ jobs: git -C wolfBoot submodule update --init --single-branch - name: Build wolfBoot secure first stage + if: matrix.api == 'psa' && matrix.profile == 'full' working-directory: wolfBoot run: | cp config/examples/stm32h5-tz-wolftrust.config .config @@ -167,10 +180,12 @@ jobs: make -j1 wolfboot.bin wolfboot_signing_private_key.der - name: Build relocated wolfTrust + if: matrix.api == 'psa' && matrix.profile == 'full' run: | make build/wolftrust.bin build/secure_cmse_implib.o - name: Build Zephyr and FreeRTOS PSA guests + if: matrix.api == 'psa' && matrix.profile == 'full' run: | make -C tests/firmware/zephyr-stm32h5 \ WT_REUSE_SECURE_BUILD=1 \ @@ -180,12 +195,14 @@ jobs: build-guest0-psa build-freertos-guest1 - name: Pin guest measurements into the secure image + if: matrix.api == 'psa' && matrix.profile == 'full' run: | python3 tools/measure/patch_guest_digests.py build/wolftrust.bin \ "0:1:tests/firmware/zephyr-stm32h5/build/guest0_psa/zephyr/zephyr.bin" \ "1:1:tests/firmware/zephyr-stm32h5/build/freertos_guest1/freertos_guest1.bin" - name: Sign wolfTrust secure runtime + if: matrix.api == 'psa' && matrix.profile == 'full' working-directory: wolfBoot run: | IMAGE_HEADER_SIZE=1024 \ @@ -197,12 +214,14 @@ jobs: test -s "$GITHUB_WORKSPACE/build/wolftrust_v1_signed.bin" - name: Read the wolfBoot authenticated measurement + if: matrix.api == 'psa' && matrix.profile == 'full' run: | measurement=$(python3 tests/scripts/read_wolfboot_measurement.py \ build/wolftrust_v1_signed.bin) echo "WT_EXPECTED_MEASUREMENT_HEX=$measurement" >> "$GITHUB_ENV" - name: Boot the authenticated wolfBoot → wolfTrust → guests chain + if: matrix.api == 'psa' && matrix.profile == 'full' run: | echo "Guest vector tables (SP, reset PC):" od -An -tx4 -N8 tests/firmware/zephyr-stm32h5/build/guest0_psa/zephyr/zephyr.bin @@ -277,8 +296,43 @@ jobs: need_raw "freertos_guest1: psa_crypto_init st=0" need_raw "freertos_guest1: psa rng ok" need_raw "freertos_guest1: psa hash ok" + need_raw "freertos_guest1: psa ECC keygen/sign/verify ok" fi + - name: Run the direct SPM interface scenario + if: matrix.api != 'psa' || matrix.profile != 'full' + run: tests/target/run_suite.sh m33mu ${{ matrix.api == 'psa' && 'positive' || matrix.api }} + + interface-isolation-m33mu: + name: stm32h563_${{ matrix.scenario }}_${{ matrix.engine }}_${{ matrix.profile }} + if: >- + github.event_name != 'workflow_dispatch' || + inputs.port == 'all' || inputs.port == 'stm32h563' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + scenario: [tpmneg, pkcs11neg] + engine: [native, hsm] + profile: [default, full] + timeout-minutes: 45 + container: + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 + env: + WT_ENGINE: ${{ matrix.engine }} + WT_CRYPTO_PROFILE: ${{ matrix.profile }} + WT_REQUIRE_SCENARIO_PASS: 1 + steps: + - uses: actions/checkout@v4 + + - name: Trust the checked-out workspace + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git config --global url."https://github.com/".insteadOf git@github.com: + + - name: Run the direct interface scenario + run: tests/target/run_suite.sh m33mu ${{ matrix.scenario }} + # Label-selected, the wolfProvider way: one select job reads the ci:* labels # (ci:stm32h563 or ci:imxrt700 for that port's full matrix when only that port # changed, ci:all or ci:m33mu for everything after a core change, no label @@ -292,7 +346,8 @@ jobs: steps: - uses: actions/checkout@v4 - - id: p + - name: Generate and verify the M33MU scenario matrix + id: p env: PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ' ') }} PORT_INPUT: ${{ inputs.port }} diff --git a/.gitmodules b/.gitmodules index 1ac32c548..71f4549c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,9 @@ [submodule "lib/wolfCOSE"] path = lib/wolfCOSE url = https://github.com/wolfSSL/wolfCOSE.git +[submodule "lib/wolfPKCS11"] + path = lib/wolfPKCS11 + url = git@github.com:wolfSSL/wolfPKCS11.git +[submodule "lib/wolfTPM"] + path = lib/wolfTPM + url = git@github.com:wolfSSL/wolfTPM.git diff --git a/Makefile b/Makefile index 8d048c23b..5680312f6 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ WOLFHSM_DIR := $(ROOT)/lib/wolfHSM WOLFSSL_DIR := $(ROOT)/lib/wolfSSL WOLFHAL_DIR := $(ROOT)/lib/wolfhal WOLFCOSE_DIR := $(ROOT)/lib/wolfCOSE +WOLFTPM_DIR := $(ROOT)/lib/wolfTPM +WOLFPKCS11_DIR := $(ROOT)/lib/wolfPKCS11 ifeq ($(wildcard mk/target-$(TARGET).mk),) $(error unsupported secure build target TARGET=$(TARGET)) @@ -26,7 +28,8 @@ include mk/common.mk test-domain-host test-domain-compilers test-domain-sanitize \ test-domain-valgrind test-manifest-host test-manifest-compilers \ test-manifest-sanitize test-manifest-valgrind test-vnet-host \ - test-vnet test-vnet-target test-vnet-hardware \ + test-vnet test-vnet-target test-vnet-hardware test-tpm-target \ + test-pkcs11-target test-tpm-interfaces-target \ test-lifecycle-host test-lifecycle-compilers test-lifecycle-sanitize \ test-lifecycle-valgrind \ test-ipc-host test-ipc-compilers test-ipc-sanitize \ @@ -48,10 +51,12 @@ C99_CC_VERSION := $(shell $(CC) --version 2>/dev/null) ifneq ($(findstring clang,$(C99_CC_VERSION)),) C99_CFLAGS += -Wno-newline-eof \ --system-header-prefix=wolfssl/ \ - --system-header-prefix=wolfhsm/ + --system-header-prefix=wolfhsm/ \ + --system-header-prefix=wolftpm/ else C99_CFLAGS += -isystem $(abspath $(WOLFSSL_DIR)) \ - -isystem $(abspath $(WOLFHSM_DIR)) + -isystem $(abspath $(WOLFHSM_DIR)) \ + -isystem $(abspath $(WOLFTPM_DIR)) endif c99-check: @@ -83,6 +88,27 @@ else fi endif +# Focused STM32H563 M33MU entry points for the two application interfaces and +# their complete PR gate. These run the same scenario runner and assertions as +# the generated CI matrix. +test-tpm-target: + @$(MAKE) --no-print-directory test-target TARGET=stm32h563 \ + WT_INTERFACE_OS=$(WT_INTERFACE_OS) \ + WT_SCENARIOS=tpm + +test-pkcs11-target: + @$(MAKE) --no-print-directory test-target TARGET=stm32h563 \ + WT_INTERFACE_OS=$(WT_INTERFACE_OS) \ + WT_SCENARIOS=pkcs11 + +test-tpm-interfaces-target: + @set -e; for engine in native hsm; do \ + for os in zephyr freertos; do \ + WT_ENGINE=$$engine WT_INTERFACE_OS=$$os \ + tests/target/run_suite.sh m33mu tpm tpmneg pkcs11 pkcs11neg; \ + done; \ + done + # AArch64 twin of test-target on QEMU (virt GICv2/GICv3, xlnx-versal-virt); # auto-detect qemu-system-aarch64 + aarch64-none-elf (or WT_TARGET_SCENARIOS=1), # skip explicitly otherwise. MACHINE/GIC/CPU/SMP pass through to the runner. diff --git a/README.md b/README.md index f2aae0653..ac91ac143 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,25 @@ Both ports keep the public manifest, service, IPC, and PSA API contracts; AArch64 manifests add an optional FF-A section. Additional Cortex-M and Cortex-A ports are an intended extension point. +Non-secure applications can use PSA Crypto, PKCS#11, or TPM 2.0 as independent +interfaces through the SPM. `CONFIG_PKCS11=y` routes standard PKCS#11 `C_*` +calls to wolfPKCS11 inside `SERVICE_PKCS11`. `CONFIG_TPM=y` routes wolfTPM command +traffic to fwTPM inside `SERVICE_TPM`. Neither needs an additional guest or +RTOS. Both use FF-M IPC through the port's +normal client boundary: the same five CMSE gateway veneers as PSA clients on +Armv8-M, or FF-A through the EL3 SPMD on AArch64. Neither uses the PSA Crypto +API or depends on the other interface. See the [PKCS#11 Interface](docs/PKCS11.md) +and [TPM 2.0 Interface](docs/TPM.md). All three interfaces use the same +`WT_ENGINE=native` (default) or `WT_ENGINE=hsm` selector. +[Operation profiles](docs/Crypto-Profiles.md) select a small default, the full +operation set, or individual API operations. + +Runtime qualification for these optional interfaces is currently scoped to +STM32H563 under M33MU and `qemuvirt` under QEMU. MIMXRT700 and Versal have +compile and absolute-layout gates for both configurations, but this release +does not claim TPM or PKCS#11 application/service runtime coverage on those +two targets. + ## Architecture ```mermaid @@ -51,12 +70,16 @@ flowchart TB AT[Initial Attestation] FW[Firmware Update] VN[Optional virtual networking] + P11[Optional PKCS#11 relay] + TPM[Optional TPM 2.0 service] end SPM --> CR SPM --> ST SPM --> AT SPM --> FW SPM --> VN + SPM --> P11 + SPM --> TPM end subgraph LIBS[wolfSSL ecosystem components] @@ -65,6 +88,8 @@ flowchart TB COSE[wolfCOSE] HAL[wolfHAL] IP[wolfIP
optional bare-metal reference networking] + P11P[wolfPKCS11
Secure partition] + FTPM[wolfTPM fwTPM
Secure partition] end BOOT -->|authenticated measurement, lifecycle, and version handoff| SPM @@ -76,6 +101,8 @@ flowchart TB GA -->|optional networking| IP GB -->|optional networking| IP IP -->|VNet service over FF-M| GW + GA -->|PKCS#11 C_* proxy over FF-M| GW + GB -->|wolfTPM commands over FF-M| GW GW -->|validated requests| SPM SPM -->|Secure execution operations| ARCH SPM -->|platform callbacks| TARGET @@ -83,6 +110,12 @@ flowchart TB ST --> WC AT --> COSE AT --> WC + P11 --> P11P + P11P -->|native| WC + P11P -->|hsm| CR + TPM --> FTPM + FTPM -->|native| WC + FTPM -->|hsm| CR TARGET -->|STM32H563 register and RNG access| HAL ``` @@ -121,7 +154,7 @@ the services that depend on it fail closed. For the initial Secure build and host tests, install GNU Make, Python 3, Git, a native C compiler, and an `arm-none-eabi-` toolchain. Configure GitHub SSH -access before cloning because three configured submodule URLs use SSH. Guest +access before cloning because five configured submodule URLs use SSH. Guest setup, conformance tests, emulator runs, and fresh hardware builds require network access. See [Getting Started](docs/Getting-Started.md) for additional emulator and hardware prerequisites. @@ -198,6 +231,8 @@ documentation source: - [Threat Model](docs/Threat-Model.md) - [API Reference](docs/API-Reference.md) - [Services](docs/Services.md) +- [TPM 2.0 Interface](docs/TPM.md) +- [PKCS#11 Interface](docs/PKCS11.md) - [TF-M Compatibility](docs/TF-M-Compatibility.md) - [FF-A Compatibility](docs/FF-A-Compatibility.md) - [Macros](docs/Macros.md) diff --git a/config/crypto-profiles.json b/config/crypto-profiles.json new file mode 100644 index 000000000..03bb50858 --- /dev/null +++ b/config/crypto-profiles.json @@ -0,0 +1,27 @@ +{ + "default": { + "psa": [ + "psa_crypto_init", "psa_generate_random", "psa_hash_compute", + "psa_generate_key", "psa_import_key", "psa_export_public_key", + "psa_get_key_attributes", "psa_destroy_key", "psa_sign_hash", + "psa_verify_hash" + ], + "pkcs11": [ + "C_Initialize", "C_Finalize", "C_GetInfo", "C_GetSlotList", + "C_GetSlotInfo", "C_GetTokenInfo", "C_GetMechanismList", + "C_GetMechanismInfo", "C_InitToken", "C_InitPIN", "C_SetPIN", + "C_OpenSession", "C_CloseSession", "C_CloseAllSessions", + "C_GetSessionInfo", "C_Login", "C_Logout", "C_CreateObject", + "C_DestroyObject", "C_GetAttributeValue", "C_FindObjectsInit", + "C_FindObjects", "C_FindObjectsFinal", "C_GenerateKeyPair", + "C_GenerateRandom", "C_DigestInit", "C_Digest", "C_SignInit", + "C_Sign", "C_VerifyInit", "C_Verify" + ], + "mechanisms": ["CKM_SHA256", "CKM_EC_KEY_PAIR_GEN", "CKM_ECDSA"], + "tpm": [ + "Startup", "Shutdown", "SelfTest", "GetTestResult", "GetCapability", + "GetRandom", "Hash", "CreatePrimary", "ReadPublic", "Sign", + "VerifySignature", "FlushContext" + ] + } +} diff --git a/docs/Architecture.md b/docs/Architecture.md index 39880f1c8..5c3202544 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -9,8 +9,9 @@ enforce that policy. Two architecture ports run the same policy and service code. The Armv8-M adapter with the STM32H563 port is the reference implementation validated on -hardware; it runs two Non-secure guests on one Cortex-M33 and exposes Secure -services only through FF-M IPC. The AArch64 adapter replaces Trusted +hardware; it runs one or two Non-secure guests on one Cortex-M33 and exposes +Secure services through FF-M IPC. TPM and PKCS#11 run directly in Secure +partitions and need only the application guest. The AArch64 adapter replaces Trusted Firmware-A on Cortex-A and is validated under QEMU; see [AArch64 architecture](#aarch64-architecture). The boot flow, isolation, and scheduling sections below describe the Armv8-M reference port. diff --git a/docs/Crypto-Engines.md b/docs/Crypto-Engines.md index 4ad8ba6c9..d322f46cf 100644 --- a/docs/Crypto-Engines.md +++ b/docs/Crypto-Engines.md @@ -38,6 +38,33 @@ operations and volatile keys remain local to the Non-secure guest. The native wire is a separate explicit interface for vault-backed key operations. Secure services such as attestation use that vault backend directly. +## API selection + +`WT_ENGINE` selects the backend in the same way for all three application APIs. +`native` is the default; `hsm` opts into the wolfHSM backend. Legacy +`WT_ENGINE_HSM=0/1` remains equivalent and conflicting selectors fail the build. + +| Application API | Native engine | HSM engine | +| --- | --- | --- | +| PSA Crypto | Guest wolfPSA and wolfCrypt, with Secure RNG and explicit vault-key operations | Supported operations use the mediated wolfHSM backend | +| PKCS#11 | SERVICE_PKCS11 runs wolfCrypt directly | The same partition selects the wolfHSM callback through FF-M IPC | +| TPM 2.0 | SERVICE_TPM runs wolfCrypt directly | The same partition selects the wolfHSM callback through FF-M IPC | + +TPM and PKCS#11 run inside ordinary Secure partitions on both architectures. +They need no additional Non-secure guest, RTOS, or worker. Native uses wolfCrypt +directly; HSM uses the existing HSM service from the same partition. Working +state, callbacks, and keys remain confined to the corresponding domain. +Read-only code is shared where its configuration and ownership checks permit. +The fwTPM HSM configuration maps keys initialized without a device ID to its +private wolfHSM callback. Native uses direct software execution. + +Select the API with `CONFIG_PKCS11=y` or `CONFIG_TPM=y`, independently of the +engine. Both default to `n`; PSA builds include neither implementation. +CI exercises `[native, hsm] x [psa, pkcs11, tpm] x [zephyr, freertos]` on +STM32H563 under both operation profiles, plus both interface isolation scenarios. +Cortex-A exercises PSA, TPM, and PKCS#11 with both engines and profiles, including +the TPM and PKCS#11 isolation cases. Required matrix cells cannot pass by skipping. + ## One service seam `SERVICE_HSM` keeps its existing name and SID in both builds. The service diff --git a/docs/Crypto-Profiles.md b/docs/Crypto-Profiles.md new file mode 100644 index 000000000..92ab4d2a0 --- /dev/null +++ b/docs/Crypto-Profiles.md @@ -0,0 +1,123 @@ +# Crypto operation profiles + +`WT_ENGINE=native|hsm` selects the crypto engine independently of the application +API. `WT_CRYPTO_PROFILE=default|full` selects the exported operations. Native is +the default engine. Neither TPM nor PKCS#11 requires wolfHSM. + +The default profile covers RNG, SHA-256 and P-256 key generation, signing and +verification. It also includes the key, session and token management needed to +use those operations. PKCS#11 retains PIN setup, login, attempt limits and +recovery. `config/crypto-profiles.json` is the explicit default operation list. +The full profile retains the broader operations supported by the pinned +libraries and this platform's algorithm and memory configuration. + +## Select operations + +Use the same settings for the application and Secure image: + +```sh +make secure-image CONFIG_PKCS11=y WT_ENGINE=native WT_CRYPTO_PROFILE=default +make secure-image CONFIG_TPM=y WT_ENGINE=hsm WT_CRYPTO_PROFILE=full + +WT_CRYPTO_ENABLE='pkcs11:C_GetObjectSize tpm:PCR_Read' \ +WT_CRYPTO_DISABLE='psa:psa_sign_hash' \ + make secure-image CONFIG_PKCS11=y +``` + +Enable and disable lists contain whitespace-separated `api:operation` names. +Overrides apply after the selected profile; disable wins if both lists name the +same operation. They select entry points, not automatic dependencies between +application calls. An application that removes initialization or a required key +lifecycle call must use an appropriate lifecycle of its own. + +A JSON file can replace individual lists. Unspecified lists retain the selected +profile's defaults: + +```json +{ + "psa": ["psa_crypto_init", "psa_generate_random", "psa_hash_compute"], + "mechanisms": ["CKM_SHA256"] +} +``` + +Pass its absolute path with `WT_CRYPTO_CONFIG`. The accepted fields are `psa`, +`pkcs11`, `tpm`, and `mechanisms`, each a JSON array of names. An empty mechanism +list exports no crypto mechanisms. Unknown fields and operation names fail the +build. The TPM service requires at least one selected command; disable the +interface with `CONFIG_TPM=n` when no TPM commands are needed. + +Inspect known dependency entry points or generate a selection directly: + +```sh +python3 tools/crypto_profile.py --list +python3 tools/crypto_profile.py --output build/crypto-profile \ + --profile default --enable pkcs11:C_GetObjectSize --disable psa:psa_sign_hash +``` + +The command catalog includes dependency commands whose algorithm or feature +family can be disabled by platform settings. Selection does not override those +settings or provide missing NV storage. A default-derived TPM selection checks +that every requested command survived those guards. The full profile retains +the dependency guards; `TPM_CAP_COMMANDS` reports the commands actually built. + +## What is removed + +PSA public entry points forward to private implementations only when selected. +A disabled public entry point returns `PSA_ERROR_NOT_SUPPORTED`. Internal calls +retain private names so, for example, disabling multipart hashing does not break +a selected one-shot hash operation. Unreferenced private functions are removed +by section garbage collection and LTO. + +PKCS#11 keeps the standard function-list ABI. Disabled entries are typed stubs +returning `CKR_FUNCTION_NOT_SUPPORTED` without a transport call. The provider +also rejects a forged disabled wire operation before decoding its arguments or +calling the backend. Removed mechanisms return `CKR_MECHANISM_INVALID`. +Mechanism enumeration and action flags reflect the allowed operations. + +TPM generation filters the pinned fwTPM dispatch table in the build directory. +It preserves the original handlers and authorization metadata. Disabled commands +return `TPM_RC_COMMAND_CODE`, disappear from `TPM_CAP_COMMANDS`, and no longer +retain their handlers through the table. Dependency source and pins are not +modified. + +Removing an operation does not remove primitives needed internally. PKCS#11 +PIN protection still needs PBKDF2/HMAC. Public AES and HMAC mechanism dispatch +is omitted when unselected, while the internal PIN primitives and shared +structure layouts stay intact. fwTPM can need hashing and symmetric +crypto for object protection even when related public mechanisms are absent. +TPM and PKCS#11 images link no wolfPSA Crypto implementation. Their `psa_call` +functions are the FF-M IPC boundary. + +## Reference builds and verification + +FreeRTOS guest builds use `WT_LTO=1` by default; `WT_LTO=0` disables it. +RTOS, startup, libc and cross-world transport objects keep their compilation +boundaries. The default FreeRTOS TPM application uses the TPM for crypto and +links no application-side wolfCrypt/HSM engine. Set `WT_TPM_CLIENT_WOLFCRYPT=1` +when a custom application needs local cryptography for HMAC/parameter-encrypted +sessions or host-side key preparation. Full reference builds retain that support. +The TPM service still selects native/HSM normally. +TPM/PKCS#11 HSM providers omit unused software ECC fallback by default. +`WT_PROVIDER_ECC_FALLBACK=1` restores it; native builds always retain software +ECC, and the HSM server keeps its own implementation. PSA retains the software +ECC path required for deterministic ECDSA; that dependency is not removed. + +The `profile` PKCS#11 reference suite exercises the default operations, capability +filtering, disabled entry points, forged wire requests and PIN recovery. The +`all` suite retains the 40-case broader regression. CI tests both profiles across +both engines and both Cortex-M OS clients, plus the Cortex-A interfaces and +partition isolation. The PSA default tests run RNG, SHA-256 and P-256 from +FreeRTOS and check disabled API behavior on both OS clients. + +Run `make test` for the host regressions. Use `WT_CRYPTO_PROFILE=default` or +`full` with the existing target scenario runners. The runners default to `full` +to preserve existing conformance and broad test workloads; product builds default +to `default`. Applications must match the selected operation list. + +[Footprint measurements](Interface-Footprint.md) include a reproducible script +and distinguish the common crypto workload from full protocol coverage. + +TPM selections containing `ZGen_2Phase` retain software ECC point arithmetic +because its full-point operation is outside the HSM ECDH callback. Adding that +command restores the dependency automatically; it is absent from the small +default profile. diff --git a/docs/Home.md b/docs/Home.md index fa6de3973..2abb0cf9e 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -24,6 +24,13 @@ Both ports keep the public manifest, service, IPC, and PSA API contracts; AArch64 manifests add an optional FF-A section. Additional Cortex-M and Cortex-A ports are an intended extension point. +Non-secure applications can use PSA Crypto, PKCS#11, or TPM 2.0 as independent +interfaces through the SPM. `CONFIG_PKCS11=y` routes standard PKCS#11 `C_*` +calls to an isolated wolfPKCS11 provider. `CONFIG_TPM=y` routes wolfTPM command +traffic to an isolated firmware TPM. Both use FF-M IPC through the port's +normal client boundary: CMSE veneers on Armv8-M or FF-A through the EL3 SPMD +on AArch64. Neither uses the PSA Crypto API or depends on the other interface. + ## Architecture ```mermaid @@ -126,6 +133,8 @@ the services that depend on it fail closed. | [Threat Model](Threat-Model.md) | Protected assets, attacker capabilities, controls, and residual risks | | [API Reference](API-Reference.md) | PSA client, service, storage, update, lifecycle, attestation, and gateway APIs | | [Services](Services.md) | Behavior and access policy for each Secure service | +| [TPM 2.0 Interface](TPM.md) | Direct wolfTPM client, mediated service, firmware TPM, and isolation | +| [PKCS#11 Interface](PKCS11.md) | Zephyr, FreeRTOS, and Cortex-A clients with isolated native or HSM providers | | [TF-M Compatibility](TF-M-Compatibility.md) | Supported interfaces, intentional differences, and migration guidance | | [FF-A Compatibility](FF-A-Compatibility.md) | The AArch64 FF-A interface register, intentional differences, and Arm FF-A ACS results | | [Macros](Macros.md) | Supported build and manifest configuration | diff --git a/docs/Interface-Footprint.md b/docs/Interface-Footprint.md new file mode 100644 index 000000000..3560ff4f2 --- /dev/null +++ b/docs/Interface-Footprint.md @@ -0,0 +1,122 @@ +# Application-interface footprint + +Measured on 2026-10-26 for STM32H563. All six default-profile builds exercise +RNG, SHA-256, P-256 key generation, signing, and verification. Every row has one +FreeRTOS application and one wolfTrust image. TPM and PKCS#11 run directly in +Secure partitions; no provider image, extra RTOS, or worker is built or loaded. + +## Matched default builds + +Flash is linked `text + data`, in bytes. It excludes debug information, image +signatures, and unused address gaps. It is not source size, archive size, or a +reserved partition's capacity. + +| Engine | API | Secure | Application | Total | Above same-engine PSA | +| --- | --- | ---: | ---: | ---: | ---: | +| native | PSA | 94,208 | 33,488 | 127,696 | 0 | +| native | TPM | 116,332 | 15,476 | 131,808 | 4,112 | +| native | PKCS#11 | 125,276 | 11,052 | 136,328 | 8,632 | +| hsm | PSA | 118,076 | 43,156 | 161,232 | 0 | +| hsm | TPM | 160,960 | 15,476 | 176,436 | 15,204 | +| hsm | PKCS#11 | 164,384 | 11,052 | 175,436 | 14,204 | + +The API-disabled Secure images exactly match the pinned base under these +settings: 94,208 bytes native and 118,076 bytes HSM. Disabling both optional +interfaces adds no Secure flash cost. Vault, RNG, attestation, storage, and +update services remain enabled in every row. + +TPM and PKCS#11 link no wolfPSA Crypto implementation. Their `psa_call` helpers +use the existing FF-M IPC transport. The default TPM application also links no +local wolfCrypt/HSM engine, so its size is identical for both engine choices. +Native PSA runs wolfPSA and wolfCrypt in the application; native TPM and +PKCS#11 run their protocol implementations in Secure partitions. + +The extra RTOS and duplicate native math were removable overhead. The current +remaining differences are smaller, but PSA parity has not been reached. These +measurements do not establish a minimum size for either protocol. The retained +code implements TPM commands, authorization, and object formats, or PKCS#11 +token/session/object state, PIN protection, and wire handling. + +## Reductions and retained operations + +The previous default builds used a separate Cortex-M provider guest: + +| Engine | API | Previous total | Current total | Reduction | +| --- | --- | ---: | ---: | ---: | +| native | TPM | 156,756 | 131,808 | 24,948 | +| native | PKCS#11 | 162,445 | 136,328 | 26,117 | +| hsm | TPM | 180,104 | 176,436 | 3,668 | +| hsm | PKCS#11 | 186,676 | 175,436 | 11,240 | + +The new layout removes the provider guest, its scheduler, and asynchronous +forwarding/polling. TPM executes each command in one copied call. PKCS#11 +keeps bounded chunking for 4096-byte messages, with one execution call and no +worker polling. Native domains share stateless crypto code already in flash. +Both engines share low-level integer and assembly code, while HSM callbacks +and the software fallback paths that use them remain private. Writable-state +ownership checks reject unsafe sharing. + +Operations remain individually configurable. The default removes unselected +public AES/HMAC mechanism dispatch while keeping the internal PIN primitives +and common structure layouts. The full profile restores the broader reference +suite. Production PIN PBKDF2 remains at 600,000 rounds. See +[Crypto-Profiles.md](Crypto-Profiles.md) for selection and dependencies. + +`WT_PROVIDER_ECC_FALLBACK=1` restores optional software ECC fallback in HSM +builds. Native always retains software ECC. TPM `ZGen_2Phase` selections +restore the software point arithmetic they require. PSA retains software ECC +needed by deterministic-signature operations. + +Static RAM is `data + bss`, including configured heaps. It is separate from +flash and from dynamic peak usage or unused MPU window capacity. The Cortex-M +interface region reserves 48 KiB for state and 16 KiB for its stack. PKCS#11 +uses a bounded private 28-KiB heap inside that state region; the volatile +fwTPM configuration has no heap. + +STM32H563 interface builds default to a 256-KiB Secure flash slot so either +engine fits. The API-disabled standalone build keeps its 128-KiB slot. The +reserved slot size is a layout limit; the table counts only linked code/data. + +## Reproduce + +- Base: `7ae69b3cb6fae0a61e3d184264415d0ea0c01f36`. +- Compiler: Arm GNU Toolchain 15.2.Rel1, GCC 15.2.1. +- Container: `ghcr.io/wolfssl/wolfboot-ci-m33mu@sha256:f6b66c9240ecd94f99400565196f956d1b067059b659a66dbbe83581cc7bf4c7`. +- Cortex-M33, `-Os`, `WT_LTO=1`, one guest slot, default attestation, and + production PIN KDF settings. All rows use the same pinned dependencies. + +The PSA fixture uses guest 0's flash/RAM addresses and HSM client ID 1. Its +foreign-vector negative uses the other RAM window. The single-application +native and HSM pairs also pass real RNG, hash, P-256, and caller-boundary tests. + +Initialize dependencies and the FreeRTOS workspace inside that container, then: + +```sh +WT_CRYPTO_PROFILE=default bash tools/measure_interface_footprint.sh +``` + +The output contains `sizes.csv`, compiler/source configuration, logs, generated +operation selections, symbol/section reports, and all measured ELFs. The first +argument chooses an output directory. `WT_CRYPTO_PROFILE=full` measures the +broader profile, whose reference workloads differ from the default. + +Earlier 11,980-byte native PSA application figures covered RNG/hash without +P-256. They are not a comparable baseline for these builds. + +## Validation + +Implementation: [7c503777](https://github.com/aidangarske/wolfTrust-fork/commit/7c5037779d2129523f051f1a5ce32d1dd8ae03b8). +All 71 host suites and their C99 run pass. Six focused ASan/UBSan suites pass, +and four RT700 API/engine cross-builds pass their layout checks. + +Local host tests exercise the synchronous production transports through the +FF-M runtime, including exact bounds, identity checks, zeroization, connection +recovery, and prevention of implicit replay. M33MU and QEMU run the actual +fwTPM and wolfPKCS#11 implementations under both engines and profiles, with +partition isolation and restart tests. The Cortex-M clients cover Zephyr, +FreeRTOS, and the bare-metal TPM isolation client. + +Emulator-only PIN tests explicitly use 25 PBKDF2 rounds to bound execution +time. Size measurements retain 600,000 rounds. Emulator and cross-build results +are recorded separately from physical hardware qualification. Current branch +CI results are available in [PR 76 checks](https://github.com/wolfSSL/wolfTrust/pull/76/checks). diff --git a/docs/Macros.md b/docs/Macros.md index 43a28c9dc..b3a2e9203 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -21,7 +21,8 @@ says otherwise; the AArch64 variables are under | Define | Description | Requirement | | --- | --- | --- | -| `WT_MAX_GUESTS` | Selects one or two compiled STM32H563 guest contexts; default `2`. | The current port supports only `1` or `2`. Larger values require extending the partition tables and matching manifest, linker, emulator, flash, and measurement configuration. | +| `WT_MAX_GUESTS` | Selects one or two compiled STM32H563 guest contexts; default `1` for TPM/PKCS#11 and `2` otherwise. | The current port supports only `1` or `2`. Larger values require extending the partition tables and matching manifest, linker, emulator, flash, and measurement configuration. | +| `WT_SECURE_FLASH_SIZE` | STM32H563 defaults to 128 KiB, or 256 KiB with TPM/PKCS#11 so either engine fits. RT700 defaults to 256 KiB. | This reserves image capacity; linked `text + data` reports actual code/data usage. | | `WT_TIMESLICE_MS` | Guest scheduler interval in milliseconds; default `2`. | Must be nonzero and supported by the target timer. | | `WT_CO_STACK_SIZE` | Default fixed coroutine stack size in bytes, including each per-guest wolfHSM server tasklet in the hsm engine; default `10240` (measured: the deep M33MU workloads pass at 8K with PSPLIM overflow detection armed, so 10K carries at least 2K margin). With two guests, the hsm-only server slots total 20,480 stack bytes plus 512 guard bytes. Manifest-sized Secure Partition stacks use their declared sizes instead. | Size from measured stack high-water marks and keep at least the scheduler minimum. | | `WT_SHARED_UART` | Reference guest UART selection; default `3`. | Guest and Secure builds must use a consistent value. | @@ -34,6 +35,9 @@ says otherwise; the AArch64 variables are under | --- | --- | --- | | `WT_GUEST_FLASH_WRP` | When `1`, verify full STM32 guest-window WRP coverage before launch; default `0`. | Set to `1` for the hardened STM32H563 image and provision WRP after flashing. M33MU does not model WRP. | | `WT_ENGINE_HSM` | Legacy engine selector; unset by default. `0` maps to `WT_ENGINE=native` and `1` maps to `WT_ENGINE=hsm` when the public selector is not supplied. The build also derives this internal value from `WT_ENGINE`. | Prefer `WT_ENGINE` for new builds and do not supply conflicting selectors. The guest and Secure image must select the same engine. | +| `CONFIG_TPM` | Add `SERVICE_TPM` and its firmware TPM handler in the same Secure partition; default `n`. | Supported by STM32H563, MIMXRT700, QEMU `virt`, and Versal builds. Select either `WT_ENGINE=native` (default) or `WT_ENGINE=hsm`, as with PSA. It is mutually exclusive with `CONFIG_PKCS11`, `CONFIG_VNET`, and conformance manifests where those share the auxiliary service band. | +| `CONFIG_PKCS11` | Add `SERVICE_PKCS11` and its wolfPKCS11 handler in the same Secure partition; default `n`. | Supported by STM32H563, MIMXRT700, QEMU `virt`, and Versal builds. Select either `WT_ENGINE=native` (default) or `WT_ENGINE=hsm`, as with PSA. It is mutually exclusive with `CONFIG_TPM`, `CONFIG_VNET`, and conformance manifests where those share the auxiliary service band. | +| `WT_PKCS11_PIN_MAX_FAILURES` | Consecutive failed checks of one PKCS#11 PIN before the provider returns `CKR_PIN_LOCKED` for it; default `3`. | Set it on the Secure-image build for either architecture. Both refuse anything but a positive integer and pass it to the compiler. See [PKCS#11](PKCS11.md). | | `WT_ATTEST_COSE` | Must remain `1` in the current STM32H563 reference build; default `1`. The `0` configuration does not compile because the reset path still references attestation-gated handoff variables. | Requires the wolfCOSE submodule and the configured attestation key backend. | | `WT_WOLFCRYPT_SP_ASM` | Enable wolfCrypt SP Cortex-M assembly; default `1`. | Requires compatible Armv8-M assembly sources and toolchain. | | `WT_WOLFCRYPT_ARMASM` | Enable additional wolfCrypt Thumb-2 assembly; default `1`. | Requires a compatible GNU Arm toolchain. | @@ -66,14 +70,15 @@ in `port/common/aarch64/l3_layout.h`. | `WT_SECURE_FLASH_BASE` | Secure image link base; standalone default `0x0C000000`. | Must match the bootloader slot and linker layout. | | `WT_SECURE_FLASH_SIZE` | Available Secure image bytes; standalone default `0x00020000`. | Must cover the linked image without overlapping another flash region. | | `WT_SECURE_IMAGE_HEADER_SIZE` | Bytes reserved before linked code; default `0` (`0x400` for `TARGET=mimxrt700`, wolfBoot's RT700 header). | Set to match the header layout of an image signed for wolfBoot. | -| `WT_GUEST0_FLASH_BASE` | Guest 0 flash base; standalone default `0x08020000`. | Must match the guest link address and manifest executable window. | -| `WT_GUEST1_FLASH_BASE` | Guest 1 flash base; standalone default `0x08040000`. | Must match the guest link address and manifest executable window. | -| `WT_GUEST0_FLASH_SIZE` | Guest 0 flash window; default `0x00020000`. | Must contain the signed record's image size and use valid target alignment. | -| `WT_GUEST1_FLASH_SIZE` | Guest 1 flash window; default `0x00020000`. | Must contain the signed record's image size and use valid target alignment. | - -The target runners used for authenticated boot override standalone addresses -for the wolfBoot partition layout. Keep bootloader, Secure image, manifest, -guest linker files, measurement records, and flash commands consistent. +| `WT_GUEST0_FLASH_BASE` | Guest 0 flash base; STM32H563 default `0x080A0000`, RT700 default `0x28080000`. | Must match the guest link address and manifest executable window. | +| `WT_GUEST1_FLASH_BASE` | Guest 1 flash base; STM32H563 default `0x080E0000`, RT700 default `0x28100000`. STM32H563 PKCS#11 uses `0x080C0000`. | Must match the guest link address and manifest executable window. | +| `WT_GUEST0_FLASH_SIZE` | Guest 0 flash window; STM32H563 default `0x00040000`, RT700 default `0x00080000`. STM32H563 PKCS#11 uses `0x00020000`. | Must contain the signed record's image size and use valid target alignment. | +| `WT_GUEST1_FLASH_SIZE` | Guest 1 flash window; STM32H563 default `0x00020000`, RT700 default `0x00040000`. STM32H563 PKCS#11 uses `0x00040000`. | Must contain the signed record's image size and use valid target alignment. | + +The Secure-image build checks these guest windows against the selected +manifest before generating it. Target runners may still override the defaults +for their authenticated wolfBoot layout; keep the bootloader, guest linker +files, measurement records, and flash commands consistent with those values. ## Virtual network options diff --git a/docs/PKCS11.md b/docs/PKCS11.md new file mode 100644 index 000000000..63ae0e3df --- /dev/null +++ b/docs/PKCS11.md @@ -0,0 +1,213 @@ +# PKCS#11 Interface + +wolfTrust lets an application call the standard PKCS#11 `C_*` API through the +Secure Partition Manager. The reference Cortex-M clients run under Zephyr and +FreeRTOS. The Cortex-A test client runs in the Normal world under QEMU. The +application uses a PKCS#11 proxy, while wolfPKCS11 runs directly inside the +`SERVICE_PKCS11` Secure Partition. No second image, RTOS, or worker is needed. PKCS#11 is a direct application interface to wolfTrust. It +does not use the PSA Crypto API and it does not depend on TPM support. + +The HSM backend follows this path: + +```text +Application + PKCS#11 C_* API + | + v + wolfTrust PKCS#11 proxy + | + | FF-M IPC + | Cortex-M: CMSE gateway + | Cortex-A: FF-A through the EL3 SPMD + v + SERVICE_PKCS11 Secure Partition: wolfPKCS11 + | + | SERVICE_HSM + v + isolated wolfHSM service +``` + +The transport uses `psa_connect`, `psa_call`, and `psa_close` because those are +the standard FF-M IPC operations. That does not make it a PSA Crypto path. The +payload and public API are PKCS#11. + +The runtime-qualified targets in this release are STM32H563 under M33MU and +`qemuvirt` under QEMU. MIMXRT700 and Versal have `CONFIG_PKCS11=y` compile and +absolute-layout gates, but no PKCS#11 application/service runtime claim yet. +The Versal QEMU model also lacks the XMPU/RISAF needed for a Normal-world +level-3 isolation claim. + +## Build and run + +PKCS#11 uses the same `WT_ENGINE=native` (default) or `WT_ENGINE=hsm` +selector as PSA. Native needs no wolfHSM crypto. The application may run bare +metal or under an RTOS; the Secure service uses wolfTrust's existing runtime. +`WT_CRYPTO_PROFILE=default` selects the RNG/SHA-256/P-256 default operation +set; `full` retains the broader set. Individual operations and mechanisms are +configurable through [operation profiles](Crypto-Profiles.md). + +See [TPM and PKCS#11 footprint](Interface-Footprint.md) for measured Secure +and application sizes against the base revision. + +Enable the service and its manifest in the Secure image with: + +```sh +make TARGET=stm32h563 CONFIG_PKCS11=y WT_ENGINE=native secure-image \ + TOOLPREFIX=arm-none-eabi- +``` + +Run the complete PKCS#11 application suite with either supported guest OS: + +```sh +WT_ENGINE=hsm WT_INTERFACE_OS=zephyr \ + tests/target/run_suite.sh m33mu pkcs11 +WT_ENGINE=hsm WT_INTERFACE_OS=freertos \ + tests/target/run_suite.sh m33mu pkcs11 +``` + +`make test-pkcs11-target WT_INTERFACE_OS=zephyr` and +`make test-pkcs11-target WT_INTERFACE_OS=freertos` are equivalent focused +entry points. + +Build the Cortex-A service and run its positive and isolation scenarios with: + +```sh +make ARCH=aarch64 TARGET=qemuvirt CONFIG_PKCS11=y WT_ENGINE=hsm \ + secure-image TOOLPREFIX=aarch64-none-elf- +WT_ENGINE=hsm tests/target/run_suite.sh qemu-a pkcs11 pkcs11neg +``` + +The older `pkcs11core`, `pkcs11objects`, `pkcs11crypto`, and +`pkcs11negative` scenario names remain available for focused debugging. The CI +gate uses `pkcs11`, which runs all four groups in one authenticated boot. + +## Application and service boundary + +The application image contains the PKCS#11 proxy and FF-M client transport. It +does not contain the wolfPKCS11 implementation, wolfTPM, or a TPM transport. Link-time +checks enforce that separation for the Zephyr, FreeRTOS, and Cortex-A +Normal-world builds. + +The native configuration runs wolfCrypt directly in the PKCS#11 domain. The +HSM configuration registers the wolfHSM callback and reaches `SERVICE_HSM` +through FF-M IPC. Both use the same application proxy and service protocol. + +The proxy supports the PKCS#11 single-threaded initialization contract. It +rejects partial mutex callback sets with `CKR_ARGUMENTS_BAD` and rejects caller +locking callbacks or `CKF_OS_LOCKING_OK` with `CKR_CANT_LOCK`; applications +must serialize `C_*` calls. + +`C_OpenSession` accepts `pApplication` and `Notify` and ignores them, as +wolfPKCS11 does. Neither pointer is sent to the provider. A NULL `phSession` +is still forwarded, so the provider returns wolfPKCS11's own error codes in +wolfPKCS11's own order. + +On both architectures, wolfPKCS11 runs inside `SERVICE_PKCS11`. Cortex-M +uses private MPU state and stack regions; Cortex-A maps private writable pages +in the partition's S-EL0 translation table. Executable and read-only code can +be shared. Native builds reuse stateless wolfCrypt code already in the Secure +image, while RNG, initialization, PBKDF2 policy, and callback state stay private. + +Both engine variants are built with `WOLFPKCS11_NO_STORE`. The native variant +keeps the token's device ID at `INVALID_DEVID` and links no wolfHSM client. +The HSM variant sets `WOLFPKCS11_DEFAULT_DEVID` to the wolfHSM device and +keeps `WC_NO_DEFAULT_DEVID` enabled so explicitly local operations stay local. +PIN PBKDF2 always runs in software; its 600,000 rounds do not become 600,000 +individual HSM IPC transactions. Neither variant requires a TPM backend. + +The M33MU and QEMU emulator gates lower the PBKDF2 count with the test-only +`WT_PKCS11_TEST_PBKDF2_ITERATIONS` setting because emulated hashing is too +slow; production builds keep the 600,000-round default. + +The embedded application suite covers: + +- library, slot, token, and session behavior; +- object creation, lookup, attributes, and destruction; +- random, digest, HMAC, AES, P-256 ECDSA, and ECDH operations; +- invalid arguments, handles, state transitions, and signatures; +- user PIN lockout after repeated wrong PINs, and SO recovery. + +The full profile requires all 40 cases through the real proxy, service, and +selected engine. The default profile runs nine cases for its selected operations. This is API-level runtime evidence, not a cross-compile-only check. + +## PIN attempt limit + +The PKCS#11 partition has no trusted clock, so it is built with +`WOLFPKCS11_NO_TIME`, which removes wolfPKCS11's time-based failed-login +lockout. The handler enforces its own attempt limit instead, and that limit +does not use a clock: + +- After `WT_PKCS11_PIN_MAX_FAILURES` consecutive failed checks of the user + PIN (default 3), every later `C_Login` or `C_SetPIN` that would check the + user PIN returns `CKR_PIN_LOCKED` without checking it. +- The SO PIN has its own count with the same limit. It covers SO `C_Login`, + `C_SetPIN` in an SO session, and the SO PIN check in `C_InitToken`. +- Only a wrong PIN (`CKR_PIN_INCORRECT`) counts. A correct PIN before the + limit resets that PIN's count, and a backend error after a correct PIN + neither consumes nor clears an attempt. +- While a PIN is locked, a call that wolfPKCS11 rejects before it would test + the PIN, such as a NULL or wrong-length PIN, an invalid user type, a + read-only session, or a user who is already logged in, keeps its normal + error code. +- The counts belong to the token slot. Closing sessions, opening new ones, or + reconnecting to `SERVICE_PKCS11` does not reset them. +- A locked user PIN stays locked until the SO logs in and sets a new user PIN + with `C_InitPIN`, or until `C_InitToken` re-initializes the token. +- A locked SO PIN stays locked until `C_InitToken` succeeds, and the handler + refuses `C_InitToken` while the token is still initialized. The token is + RAM-only (`WOLFPKCS11_NO_STORE`), so it is erased, along with all of its + objects, when the last `C_Finalize` releases the library or the partition + restarts. Both counts are cleared with it. A `C_Finalize` that leaves the + library loaded, because of an extra `C_Initialize`, keeps the counts. +- `C_GetTokenInfo` reports the counts with the standard + `CKF_USER_PIN_COUNT_LOW`, `CKF_USER_PIN_FINAL_TRY`, and + `CKF_USER_PIN_LOCKED` flags and their SO equivalents. + +The counts live in private PKCS#11 RAM, the same as the token they protect. + +## SERVICE_PKCS11 + +`SERVICE_PKCS11` checks the SPM-stamped application identity and copies bounded +requests and responses into its private state. Messages up to 4096 bytes use +chunks that fit the 1024-byte FF-M transfer budget. The client stages the +request, calls `APP_EXECUTE` once, and reads the completed response in chunks. +No worker fetch or polling loop is involved. Duplicate execution is refused; +transport errors never implicitly replay an operation. Staging is wiped after +completion, abort, or handler failure. + +The application cannot register or use the Secure partition's HSM namespace. +An unrelated client or Secure Partition cannot access its requests or objects. + +The wire format carries PKCS#11 operation identifiers, handles, attributes, +mechanism parameters, and bounded data buffers. Handles remain provider-owned; +the proxy never receives a provider pointer. Caller buffers, templates, output +lists, and mechanism parameters keep their NULL or non-NULL state across the +boundary, even at length zero, so wolfPKCS11 applies its own argument checks. + +Mechanism parameters that contain further pointers cannot cross the wire. The +provider therefore leaves `CKM_RSA_PKCS_OAEP`, `CKM_HKDF_DERIVE`, +`CKM_HKDF_DATA`, `CKM_AES_CBC_ENCRYPT_DATA`, the TLS 1.2 key-derivation +mechanisms, and `CKM_PKCS5_PBKD2` out of `C_GetMechanismList`, and +`C_GetMechanismInfo` returns `CKR_MECHANISM_INVALID` for them. + +## Isolation and CI + +The PKCS#11 service runs as an isolation-level-3 Secure Partition on Cortex-M +and QEMU `virt`. `pkcs11neg` proves that: + +- another Secure Partition cannot read or write PKCS#11 state; +- PKCS#11 cannot read SPM-private RAM or execute its writable state; +- deliberate faults are contained and the partition restarts; +- the actual PKCS#11 suite completes after recovery. + +The host suite exercises the production copied-transfer service through FF-M, +including 4096-byte messages, malformed chunks, wrong callers, short outputs, +wiping, handler failures, reconnection, and prevention of duplicate execution. + +Pull-request CI has Zephyr and FreeRTOS `pkcs11` rows in the same explicit API +matrix as PSA and TPM. A separate `pkcs11neg` row keeps the L3 isolation proof +visible. The cross-compile workflow also builds `CONFIG_PKCS11=y` Secure images +for STM32H563 and MIMXRT700. The AArch64 QEMU matrix also runs `pkcs11` and +`pkcs11neg` on the `virt` cell. Both profiles run on both engines: nine default +profile cases and 40 full-profile cases, including disabled-operation and +capability checks for the default. diff --git a/docs/Services.md b/docs/Services.md index a2fc88e4e..696cdb908 100644 --- a/docs/Services.md +++ b/docs/Services.md @@ -15,6 +15,8 @@ manifest with a seventh service. | `SERVICE_FWU` | `0x1005` | Yes | PSA Firmware Update staging for wolfBoot | | `SERVICE_HSM` | `0x1006` | Yes | Copied request relay for the selected native or wolfHSM crypto engine, plus the attestation partition's signing door | | `SERVICE_VNET` | `0x1007` | Yes, optional | Secure virtual Ethernet switch implemented by wolfTrust for optional Non-secure wolfIP guests | +| `SERVICE_TPM` | `0x1008` | Yes, optional | Mediated mailbox to an isolated Non-secure firmware TPM for wolfTPM applications (`CONFIG_TPM=y`) | +| `SERVICE_PKCS11` | `0x1009` | Yes, optional | Copied-transfer relay between a PKCS#11 application proxy and an isolated wolfPKCS11 provider (`CONFIG_PKCS11=y`) | All service versions are `1`. ITS and Protected Storage declare a dependency on the Secure-only vault. The default manifest is @@ -198,5 +200,19 @@ Port selection comes from the stamped caller identity. Secure pool tokens and generation counters do not cross into a guest. The mediated PSA transport MTU is 1000 bytes so a frame and receive metadata fit the copied-transfer budget. +## Optional TPM and PKCS#11 interfaces + +`CONFIG_TPM=y` runs wolfTPM's firmware TPM inside `SERVICE_TPM`. +`CONFIG_PKCS11=y` runs wolfPKCS11 inside `SERVICE_PKCS11`. Both use ordinary +wolfTrust Secure partitions on Cortex-M and Cortex-A, without an additional +Non-secure guest, RTOS, or worker. A bare-metal or RTOS application uses the +same FF-M boundary. TPM returns each command's response in one call. PKCS#11 +stages larger serialized messages in bounded chunks and executes each once. + +All three interfaces select native wolfCrypt or wolfHSM with `WT_ENGINE`. +TPM and PKCS#11 are mutually exclusive and link no PSA Crypto front end. +Both default to `n`, so disabling them removes their protocol handlers. +See [TPM](TPM.md), [PKCS#11](PKCS11.md), and the [six-build footprint](Interface-Footprint.md). + See [API Reference](API-Reference.md) for client signatures and [Macros](Macros.md) for service build controls. diff --git a/docs/TPM.md b/docs/TPM.md new file mode 100644 index 000000000..bb0ba406e --- /dev/null +++ b/docs/TPM.md @@ -0,0 +1,95 @@ +# TPM 2.0 Interface + +A wolfTPM application sends TPM 2.0 commands to `SERVICE_TPM`. The service +runs wolfTPM's firmware TPM directly inside a normal wolfTrust Secure +Partition. It needs no second application image, RTOS, or worker. A bare-metal, +Zephyr, or FreeRTOS client uses the same transport. + +```text +Application: wolfTPM TPM2_* API + | + | TPM 2.0 frames over FF-M IPC + v +SERVICE_TPM Secure Partition: fwTPM + | + +-- WT_ENGINE=native: wolfCrypt + +-- WT_ENGINE=hsm: SERVICE_HSM / wolfHSM +``` + +The transport uses FF-M `psa_connect`, `psa_call`, and `psa_close`. These are +IPC operations, not PSA Crypto calls. TPM and PKCS#11 builds link no wolfPSA +Crypto implementation. Cortex-M reaches the SPM through its CMSE gateway; +Cortex-A uses FF-A through the EL3 SPMD. + +## Build and run + +All three application interfaces use `WT_ENGINE=native` (default) or +`WT_ENGINE=hsm`. TPM's native configuration does not need wolfHSM crypto. +`WT_CRYPTO_PROFILE=default` exports RNG, SHA-256, P-256 key generation, +signing, verification, and their required lifecycle commands. `full` retains +the broader supported command set, including PCR operations. Individual +commands can be added or removed with [operation profiles](Crypto-Profiles.md). + +```sh +make TARGET=stm32h563 CONFIG_TPM=y WT_ENGINE=native secure-image + +WT_ENGINE=native WT_CRYPTO_PROFILE=default WT_INTERFACE_OS=zephyr \ + tests/target/run_suite.sh m33mu tpm +WT_ENGINE=hsm WT_CRYPTO_PROFILE=full WT_INTERFACE_OS=freertos \ + tests/target/run_suite.sh m33mu tpm tpmneg + +make ARCH=aarch64 TARGET=qemuvirt CONFIG_TPM=y WT_ENGINE=native \ + secure-image TOOLPREFIX=aarch64-none-elf- +WT_ENGINE=native tests/target/run_suite.sh qemu-a tpm tpmneg +``` + +The Cortex-M runner authenticates wolfBoot, wolfTrust, and one application. +`make test-tpm-target WT_INTERFACE_OS=zephyr` and the `freertos` variant are +focused entry points. The isolation scenario also exercises a bare-metal +wolfTPM application. See [footprint measurements](Interface-Footprint.md) for +matched Secure-image and application sizes across all six configurations. + +## Service boundary + +The application links the wolfTPM client and `SERVICE_TPM` transport. It does +not link fwTPM. The Secure partition owns the `FWTPM_CTX`, keys, command +buffers, and stack. Native operations run wolfCrypt in this domain; the HSM +configuration registers the wolfHSM callback and calls the existing HSM +service from the Secure partition. + +`WT_TPM_OP_TRANSACT` copies one command in, executes it, and copies one response +out in the same call. Each frame is at most `WT_TPM_FRAME_MAX` (512 bytes), so +the combined transfer fits the 1024-byte FF-M budget. fwTPM advertises matching +limits through `TPM2_GetCapability`. The client checks TPM response framing. +Transport errors never cause an automatic replay of a potentially mutating +command. A lost connection is reopened for the next explicit command. + +The SPM supplies caller identity. Only the configured application client can +use the service; unrelated Non-secure clients and Secure Partitions are +refused. Command and response staging is wiped after every successful or failed +execution. There are no submit/poll/fetch/respond worker operations. + +The volatile `FWTPM_NO_NV` configuration has no heap. Keys and PCR values do +not survive reboot. On Cortex-M the linker and MPU assign the handler a +private state region and stack; on Cortex-A its S-EL0 translation table maps +its private writable pages. Native builds share read-only crypto code while +keeping working state private. HSM builds retain private callback state. + +## Validation and supported targets + +STM32H563 under M33MU and QEMU `virt` run actual commands and isolation tests. +The default suite checks capabilities, a maximum-sized hash command, RNG, +P-256 key creation, signing, verification, disabled commands, and cleanup. +The full suite also checks PCR extend/read and broader object operations. + +`tpmneg` proves that another Secure Partition cannot read or write TPM state, +that the TPM partition cannot read SPM-private RAM or execute its writable +state, that each fault is contained, and that commands succeed after recovery. +The host suite drives the production client/service protocol through a real +FF-M runtime and checks bounds, caller identity, wiping, and no replay. + +CI covers both engines and profiles, Zephyr and FreeRTOS applications on +Cortex-M, and Normal-world applications on Cortex-A. Required matrix cells +must report real passes; successful skips are rejected. MIMXRT700 and Versal +have compile and layout gates, without an API runtime qualification claim. +Versal QEMU lacks the XMPU/RISAF needed for Normal-world level-3 isolation. diff --git a/docs/Testing.md b/docs/Testing.md index 15905d690..64d448e66 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -31,8 +31,10 @@ Current suites cover domain and manifest validation, lifecycle, guest verification, rollback decisions, IPC and FF-M behavior, SPM policy, gateway vectors, Secure Partition layout and recovery, crypto-engine relay and key isolation, vault and storage services, attestation and COSE integration, -firmware update, runtime remeasurement, linked Secure layout, VNET, public PSA -headers, boot-handoff record consumption, and negative paths. The AArch64 +TPM and PKCS11 relay/client framing, PKCS11 proxy wire encoding and provider +parser negative inputs, firmware update, runtime remeasurement, linked Secure +layout, VNET, public PSA headers, +boot-handoff record consumption, and negative paths. The AArch64 suites cover the exception-syndrome decoder, the stage-1 table builder and domain switches, the FF-A function-id and version rules, the boot-information blob, the partition runtime state machine, memory transaction descriptors and @@ -111,24 +113,46 @@ WT_ENGINE=native make test-target WT_ENGINE=hsm make test-target ``` -It runs the port's smoke tier (STM32H563: `positive`, `gtzcneg`, -`crossdomain`, `bothpsa`, `confboot`, `devcrypto`; `WT_TIER=full` runs every -scenario). Detection accepts `m33mu` on -`PATH` or a path in `M33MU`. If the emulator is unavailable, -the target reports a skip rather than a pass. +It runs the port's smoke tier. STM32H563 includes `positive`, `gtzcneg`, +`crossdomain`, `bothpsa`, `confboot`, `devcrypto`, `tpm`, `tpmneg`, and the +combined `pkcs11` suite plus `pkcs11neg`. The four focused PKCS11 suites +(`pkcs11core`, `pkcs11objects`, `pkcs11crypto`, `pkcs11negative`) are direct +debugging entry points only; `WT_TIER=full` covers them through the combined +`pkcs11` scenario. +Detection accepts `m33mu` on `PATH` or a path in `M33MU`. If the emulator is +unavailable, the target reports a skip rather than a pass. Additional focused runs use: ```sh WT_ENGINE=native tests/target/run_m33mu_scenario.sh positive WT_ENGINE=hsm tests/target/run_m33mu_scenario.sh positive +make test-tpm-target +make test-pkcs11-target +make test-tpm-interfaces-target ``` +`make test-tpm-target` boots one Zephyr or FreeRTOS wolfTPM application and +runs fwTPM inside `SERVICE_TPM`. The PKCS#11 target likewise boots one client +and runs wolfPKCS11 inside `SERVICE_PKCS11`. The default PKCS#11 profile has +nine cases; the full profile has 40. Neither service needs another RTOS guest. + +The explicit Cortex-M matrix has 24 positive cells: +`[native, hsm] x [psa, pkcs11, tpm] x [zephyr, freertos] x [default, full]`. +Eight separate TPM/PKCS#11 cells test partition isolation across engines and +profiles. The Cortex-A matrix has 20 PSA/TPM/PKCS#11 operation and isolation +cells across both engines and profiles. + The runner's usage output is the authoritative scenario list. It includes authenticated-boot failure, rollback, runtime remeasurement, Secure Partition recovery, key and vault isolation, storage recovery, attestation negatives, firmware update, manifest rejection, GTZC behavior, and VNET paths. +The runner sets M33MU's emulated `DBGMCU_IDCODE` to supported STM32H563 +revision X for ordinary scenarios, so they pass through the same production +silicon guard as hardware. The `revneg` scenario replaces it with engineering +sample revision Z and requires the guard to halt before either guest starts. + Five scenarios cover processor-state isolation, and the emulator proves less than their names suggest: @@ -354,7 +378,9 @@ the run. These are emulator results. They prove the SAU attribution and the monitor's containment on a faithful core model; the silicon `ahbscneg` run on the EVK is -recorded separately. +recorded separately. The MIMXRT700 matrix does not yet run the TPM or PKCS#11 +application/service scenarios; those configurations are compile- and +layout-gated for this target. ## QEMU AArch64 scenarios The AArch64 twin of the M33MU runner boots the EL3 monitor, the Secure EL1 @@ -367,10 +393,15 @@ make test-target-a # MACHINE=virt GIC=3 CPU=cortex-a72 make test-target-a MACHINE=versal-virt tests/target/run_suite.sh qemu-a positive ffa-memneg WT_ENGINE=hsm tests/target/run_qemu_a_scenario.sh hsmattackneg +WT_ENGINE=native tests/target/run_suite.sh qemu-a tpm tpmneg +WT_ENGINE=hsm tests/target/run_suite.sh qemu-a pkcs11 pkcs11neg ``` -`make test-target-a` runs a quick subset (`WT_QEMU_A_SCENARIOS`); CI runs -every scenario below on the three cells under both engines. +`make test-target-a` runs a quick subset (`WT_QEMU_A_SCENARIOS`). CI runs the +common scenarios below on all three cells under both engines. The `tpm`, +`tpmneg`, `pkcs11`, and `pkcs11neg` runtime scenarios are deliberately limited +to `virt`: Versal cross-builds both interface configurations, but its QEMU +model has no XMPU/RISAF for the required Normal-world isolation proof. The runner auto-detects `qemu-system-aarch64` and the `aarch64-none-elf` toolchain and skips explicitly otherwise; CI runs it inside @@ -388,6 +419,8 @@ assert through `tests/target/lib/expect.sh`. | `parkneg`, `rdistneg`, `tickneg` | The monitor stops the boot with a panic, before it enters Secure EL1, when a declared secondary never parks, the GICv3 redistributor reads asleep (SKIP on GICv2), or the secure tick never reaches EL3 | | `positive-secure` | The neutral core boots at Secure EL1 and every Secure Partition initializes at Secure EL0 under its own translation table | | `positive` | The Normal-world guest discovers the partitions, reads framework and service versions, is refused an unknown service, and completes a data-carrying `psa_call` through the FF-M gateway: a wolfHSM echo under `hsm`, two random draws over the native wire under `native` | +| `tpm`, `tpmneg` | A Normal-world wolfTPM client sends real TPM 2.0 commands through FF-A and `SERVICE_TPM` to the fwTPM in its S-EL0 partition. GetRandom, CreatePrimary, Sign, signature verification, PCR extend, and PCR read must complete. The negative also proves state-band read/write isolation, SPM-private RAM isolation, and execute-never state before the same operation sequence passes. These scenarios run under both engines and profiles. | +| `pkcs11`, `pkcs11neg` | A Normal-world application runs nine default-profile or 40 full-profile PKCS#11 cases through FF-A and `SERVICE_PKCS11`. The S-EL0 handler uses native wolfCrypt or the existing HSM partition. The negative proves state-band read/write isolation, SPM-private RAM isolation, and execute-never state before the suite completes. Both engines and profiles run. | | `guest1` | SKIP: the AArch64 ports run a single Normal-world endpoint (`0x0000`), so the second-guest identity path has no AArch64 counterpart; M33MU covers it | | `crossdomain`, `keystoreneg`, `periphspneg` | A partition reading outside its domain, a non-keystore partition reading the keystore band, or a partition reading the SPM's secure UART takes a data abort at S-EL0, spends its restart budget, and escalates to fail-closed recovery | | `bandneg1` to `bandneg6` | Each of the vault, attestation, and crypto partitions reads and then writes another one's private band at boot; both accesses take a data abort at S-EL0 on that band, and every partition still initializes | @@ -550,8 +583,15 @@ The `M33MU` workflow (wolfBoot plus both guest lifecycles, then a `select` job that picks each port's tier and one matrix job that runs exactly those scenario groups) is tiered. Every pull request runs each port's smoke tier on both crypto engines (STM32H563: `positive`, -`gtzcneg`, `crossdomain`, `bothpsa`, `confboot`, `devcrypto`; MIMXRT700: +`gtzcneg`, `crossdomain`, `bothpsa`, `confboot`, `devcrypto`, `tpm`, +`tpmneg`, `pkcs11`, and `pkcs11neg`; MIMXRT700: `positive`, `ahbscneg`, `crossdomain`, `bothpsa`, `confboot`, `devcrypto`). +The separate STM32H563 interface matrix runs the full cross product +`[native, hsm] x [psa, pkcs11, tpm] x [zephyr, freertos] x [default, full]`. +All 24 application rows run on every PR. TPM and PKCS#11 also have eight +isolation-negative rows across engines and profiles. QEMU runs 20 interface +and isolation rows across both engines and profiles. Dedicated API rows set `WT_REQUIRE_SCENARIO_PASS=1`: a skipped +scenario or a missing terminal pass marker fails the job. The full matrix runs on a push to `main`, on the nightly schedule, on manual dispatch (with a `port` input), and on a pull request that carries the diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 51a7f5e2d..b2db5bb01 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -8,6 +8,8 @@ - [[Threat Model]] - [[API Reference]] - [[Services]] +- [[TPM]] +- [[PKCS11]] - [[TF-M Compatibility]] - [[FF-A Compatibility]] - [[Macros]] diff --git a/include/wolftrust/crypto_profile.h b/include/wolftrust/crypto_profile.h new file mode 100644 index 000000000..7373d1861 --- /dev/null +++ b/include/wolftrust/crypto_profile.h @@ -0,0 +1,48 @@ +/* crypto_profile.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_CRYPTO_PROFILE_H +#define WOLFTRUST_CRYPTO_PROFILE_H + +#include +#include +#include "wolfpkcs11/pkcs11.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" + +#ifdef WT_PKCS11_PROFILE_HEADER +#include WT_PKCS11_PROFILE_HEADER +#else +#define WT_P11_EXPORT(name) proxy_ ## name +static inline int wt_profile_pkcs11_operation(uint32_t operation) +{ + (void)operation; + return 1; +} +static inline int wt_profile_pkcs11_mechanism(CK_MECHANISM_TYPE mechanism) +{ + (void)mechanism; + return 1; +} +static inline CK_FLAGS wt_profile_pkcs11_flags(CK_FLAGS flags) +{ + return flags; +} +#endif +#endif /* WOLFTRUST_CRYPTO_PROFILE_H */ diff --git a/include/wolftrust/pkcs11/pkcs11_abi.h b/include/wolftrust/pkcs11/pkcs11_abi.h new file mode 100644 index 000000000..edb03a525 --- /dev/null +++ b/include/wolftrust/pkcs11/pkcs11_abi.h @@ -0,0 +1,57 @@ +/* pkcs11_abi.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_PKCS11_PKCS11_ABI_H +#define WOLFTRUST_PKCS11_PKCS11_ABI_H + +#include + +/* SERVICE_PKCS11 runs serialized calls inside its Secure partition. + * Messages larger than the FF-M transfer budget use bounded chunks. */ +#define WT_PKCS11_SERVICE_SID 4105U +#define WT_PKCS11_SERVICE_VERSION 1U + +#define WT_PKCS11_MESSAGE_MAX 4096U +#define WT_PKCS11_LIST_MAX 64U +#define WT_PKCS11_CHUNK_MAX 992U + +#define WT_PKCS11_OP_APP_BEGIN 1 +#define WT_PKCS11_OP_APP_WRITE 2 +#define WT_PKCS11_OP_APP_READ 5 +#define WT_PKCS11_OP_APP_ABORT 6 +#define WT_PKCS11_OP_APP_EXECUTE 13 + + +typedef struct wt_pkcs11_length { + uint32_t length; +} wt_pkcs11_length_t; + +typedef struct wt_pkcs11_range { + uint32_t offset; + uint32_t length; +} wt_pkcs11_range_t; + +typedef struct wt_pkcs11_chunk { + uint32_t offset; + uint32_t length; + uint8_t data[WT_PKCS11_CHUNK_MAX]; +} wt_pkcs11_chunk_t; + +#endif /* WOLFTRUST_PKCS11_PKCS11_ABI_H */ diff --git a/include/wolftrust/pkcs11/pkcs11_wire.h b/include/wolftrust/pkcs11/pkcs11_wire.h new file mode 100644 index 000000000..5de6bce92 --- /dev/null +++ b/include/wolftrust/pkcs11/pkcs11_wire.h @@ -0,0 +1,132 @@ +/* pkcs11_wire.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_PKCS11_PKCS11_WIRE_H +#define WOLFTRUST_PKCS11_PKCS11_WIRE_H + +#include +#include + +#define WT_PKCS11_WIRE_MAGIC 0x57503131U +#define WT_PKCS11_WIRE_VERSION 1U +#define WT_PKCS11_WIRE_UNAVAILABLE UINT32_MAX + +#define WT_P11_MECH_RAW 0U +#define WT_P11_MECH_CTR 1U +#define WT_P11_MECH_GCM 2U +#define WT_P11_MECH_CCM 3U +#define WT_P11_MECH_ECDH 4U + +enum wt_pkcs11_function { + WT_P11_INITIALIZE = 1, + WT_P11_FINALIZE, + WT_P11_GET_INFO, + WT_P11_GET_SLOT_LIST, + WT_P11_GET_SLOT_INFO, + WT_P11_GET_TOKEN_INFO, + WT_P11_GET_MECHANISM_LIST, + WT_P11_GET_MECHANISM_INFO, + WT_P11_INIT_TOKEN, + WT_P11_INIT_PIN, + WT_P11_SET_PIN, + WT_P11_OPEN_SESSION, + WT_P11_CLOSE_SESSION, + WT_P11_CLOSE_ALL_SESSIONS, + WT_P11_GET_SESSION_INFO, + WT_P11_GET_OPERATION_STATE, + WT_P11_SET_OPERATION_STATE, + WT_P11_LOGIN, + WT_P11_LOGOUT, + WT_P11_CREATE_OBJECT, + WT_P11_COPY_OBJECT, + WT_P11_DESTROY_OBJECT, + WT_P11_GET_OBJECT_SIZE, + WT_P11_GET_ATTRIBUTE_VALUE, + WT_P11_SET_ATTRIBUTE_VALUE, + WT_P11_FIND_OBJECTS_INIT, + WT_P11_FIND_OBJECTS, + WT_P11_FIND_OBJECTS_FINAL, + WT_P11_ENCRYPT_INIT, + WT_P11_ENCRYPT, + WT_P11_ENCRYPT_UPDATE, + WT_P11_ENCRYPT_FINAL, + WT_P11_DECRYPT_INIT, + WT_P11_DECRYPT, + WT_P11_DECRYPT_UPDATE, + WT_P11_DECRYPT_FINAL, + WT_P11_DIGEST_INIT, + WT_P11_DIGEST, + WT_P11_DIGEST_UPDATE, + WT_P11_DIGEST_KEY, + WT_P11_DIGEST_FINAL, + WT_P11_SIGN_INIT, + WT_P11_SIGN, + WT_P11_SIGN_UPDATE, + WT_P11_SIGN_FINAL, + WT_P11_SIGN_RECOVER_INIT, + WT_P11_SIGN_RECOVER, + WT_P11_VERIFY_INIT, + WT_P11_VERIFY, + WT_P11_VERIFY_UPDATE, + WT_P11_VERIFY_FINAL, + WT_P11_VERIFY_RECOVER_INIT, + WT_P11_VERIFY_RECOVER, + WT_P11_DIGEST_ENCRYPT_UPDATE, + WT_P11_DECRYPT_DIGEST_UPDATE, + WT_P11_SIGN_ENCRYPT_UPDATE, + WT_P11_DECRYPT_VERIFY_UPDATE, + WT_P11_GENERATE_KEY, + WT_P11_GENERATE_KEY_PAIR, + WT_P11_WRAP_KEY, + WT_P11_UNWRAP_KEY, + WT_P11_DERIVE_KEY, + WT_P11_SEED_RANDOM, + WT_P11_GENERATE_RANDOM, + WT_P11_GET_FUNCTION_STATUS, + WT_P11_CANCEL_FUNCTION, + WT_P11_WAIT_FOR_SLOT_EVENT +}; + +typedef struct wt_pkcs11_writer { + uint8_t* buffer; + size_t capacity; + size_t used; + int error; +} wt_pkcs11_writer_t; + +typedef struct wt_pkcs11_reader { + uint8_t* buffer; + size_t length; + size_t used; + int error; +} wt_pkcs11_reader_t; + +void wt_pkcs11_writer_init(wt_pkcs11_writer_t* writer, uint8_t* buffer, + size_t capacity); +void wt_pkcs11_put_u32(wt_pkcs11_writer_t* writer, uint32_t value); +void wt_pkcs11_put_bytes(wt_pkcs11_writer_t* writer, const void* data, + size_t length); + +void wt_pkcs11_reader_init(wt_pkcs11_reader_t* reader, uint8_t* buffer, + size_t length); +uint32_t wt_pkcs11_get_u32(wt_pkcs11_reader_t* reader); +uint8_t* wt_pkcs11_get_bytes(wt_pkcs11_reader_t* reader, size_t* length); + +#endif /* WOLFTRUST_PKCS11_PKCS11_WIRE_H */ diff --git a/include/wolftrust/pkcs11_provider.h b/include/wolftrust/pkcs11_provider.h new file mode 100644 index 000000000..fc31c7cd1 --- /dev/null +++ b/include/wolftrust/pkcs11_provider.h @@ -0,0 +1,35 @@ +/* pkcs11_provider.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_PKCS11_PROVIDER_H +#define WOLFTRUST_PKCS11_PROVIDER_H + +#include +#include + +#ifndef WT_PKCS11_PIN_MAX_FAILURES +#define WT_PKCS11_PIN_MAX_FAILURES 3U +#endif + +int wt_pkcs11_provider_dispatch(uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, + size_t* response_len, void* context); + +#endif /* WOLFTRUST_PKCS11_PROVIDER_H */ diff --git a/include/wolftrust/pkcs11_psa_transport.h b/include/wolftrust/pkcs11_psa_transport.h new file mode 100644 index 000000000..cf8c0c43a --- /dev/null +++ b/include/wolftrust/pkcs11_psa_transport.h @@ -0,0 +1,35 @@ +/* pkcs11_psa_transport.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_PKCS11_PSA_TRANSPORT_H +#define WOLFTRUST_PKCS11_PSA_TRANSPORT_H + +#include +#include + +/* Execute one serialized PKCS#11 call in the Secure partition. Requests + * and responses use bounded copied transfers; no client pointers cross. */ +int wt_pkcs11_psa_call(const uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, + size_t* response_len); + +void wt_pkcs11_psa_close(void); + +#endif /* WOLFTRUST_PKCS11_PSA_TRANSPORT_H */ diff --git a/include/wolftrust/platform.h b/include/wolftrust/platform.h index 773356d53..ba0402ec8 100644 --- a/include/wolftrust/platform.h +++ b/include/wolftrust/platform.h @@ -92,6 +92,8 @@ size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) || \ (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) /* Addresses the negative probes touch; test builds only. */ #define WT_PROBE_OUT_OF_DOMAIN 0u @@ -101,6 +103,10 @@ size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, #define WT_PROBE_ATTEST_DATA_BAND 3u #define WT_PROBE_HSM_DATA_BAND 4u #define WT_PROBE_SPM_PERIPHERAL 5u +#define WT_PROBE_TPM_DATA_BAND 6u +#define WT_PROBE_TPM_FTPM_RAM 7u +#define WT_PROBE_PKCS11_DATA_BAND 8u +#define WT_PROBE_PKCS11_PROVIDER_RAM 9u uintptr_t wt_platform_probe_address(unsigned int target); #endif diff --git a/include/wolftrust/provider_crypto.h b/include/wolftrust/provider_crypto.h new file mode 100644 index 000000000..cec159d34 --- /dev/null +++ b/include/wolftrust/provider_crypto.h @@ -0,0 +1,40 @@ +/* provider_crypto.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_PROVIDER_CRYPTO_H +#define WOLFTRUST_PROVIDER_CRYPTO_H + +#include "wolftrust/types.h" + +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +#define WT_HSM_NAMESPACE_COUNT (WT_MAX_GUESTS + 1U) +#else +#define WT_HSM_NAMESPACE_COUNT WT_MAX_GUESTS +#endif + +#define WT_PROVIDER_HSM_NAMESPACE WT_MAX_GUESTS +#define WT_PROVIDER_HSM_CLIENT_ID (WT_PROVIDER_HSM_NAMESPACE + 1U) + +int wt_provider_crypto_init(void); +int wt_provider_crypto_prepare(void); +void wt_provider_crypto_hsm_restarted(void); + +#endif /* WOLFTRUST_PROVIDER_CRYPTO_H */ diff --git a/include/wolftrust/services/hsm.h b/include/wolftrust/services/hsm.h index 9c35c3ba8..e139d6778 100644 --- a/include/wolftrust/services/hsm.h +++ b/include/wolftrust/services/hsm.h @@ -77,11 +77,20 @@ int wt_hsm_vault_was_reformatted(void); * made to follow, so there is no custom-transport path (WT-FFM-0011). */ int wt_hsm_guest_init_relay(wt_guest_id_t guest_id); +/* Bind one positive Secure-partition identity to a distinct wolfHSM key + * namespace. The FF-M core has already enforced the caller's manifest + * dependency before SERVICE_HSM sees the SPM-stamped partition identity. */ +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +int wt_hsm_relay_set_secure_client(int32_t partition_id, + wt_guest_id_t namespace_id); +#endif + /* SERVICE_HSM's platform submit hook (matches wt_hsm_relay_submit_fn): map - * the SPM-stamped caller to its guest server, pump one relayed wolfHSM - * packet through wh_Server_HandleRequestMessage in monitor RAM, and return - * the response packet. May block on the shared NVM mutex, so it must run - * from a scheduled coroutine, never the bootstrap context. */ + * the SPM-stamped caller to its configured server namespace, pump one relayed + * wolfHSM packet through wh_Server_HandleRequestMessage in monitor RAM, and + * return the response packet. May block on the shared NVM mutex, so it must + * run from a scheduled coroutine, never the bootstrap context. */ int wt_hsm_relay_submit(void* submit_ctx, int32_t client_id, const uint8_t* req, size_t req_len, uint8_t* resp, size_t resp_cap, size_t* resp_len); diff --git a/include/wolftrust/services/hsm_relay.h b/include/wolftrust/services/hsm_relay.h index 4ee3f81d7..98048c931 100644 --- a/include/wolftrust/services/hsm_relay.h +++ b/include/wolftrust/services/hsm_relay.h @@ -31,7 +31,8 @@ * written back as outvec[0]. The relay never parses packet contents — the * wolfHSM comm layer owns the protocol; the SPM owns caller identity and the * copied-transfer bounds (WT-FFM-0041/0047). A Secure Partition caller's - * PSA_IPC_CALL is refused with PSA_ERROR_NOT_PERMITTED. */ + * PSA_IPC_CALL is refused with PSA_ERROR_NOT_PERMITTED unless the platform + * explicitly binds that one caller as the local interface provider. */ /* One wolfHSM wire packet: whCommHeader (8) + WOLFHSM_CFG_COMM_DATA_LEN (368) * = 376 bytes on this platform; bound with headroom, under the FF-M copied @@ -50,6 +51,14 @@ typedef int (*wt_hsm_relay_submit_fn)(void* submit_ctx, int32_t client_id, * refuses every packet with PSA_ERROR_NOT_SUPPORTED. */ void wt_hsm_relay_set_submit(wt_hsm_relay_submit_fn fn, void* submit_ctx); +/* Allow one positive Secure-partition identity onto the ordinary HSM wire. + * Zero clears the binding. The default refuses every Secure caller, and + * Non-secure callers remain governed by their SPM-stamped negative identity. */ +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +void wt_hsm_relay_set_secure_client_policy(int32_t partition_id); +#endif + /* Attestation signing door (isolation level 3): the attestation partition * holds no key material and reaches the Initial Attestation Key only through * these psa_call types on SERVICE_HSM, served for the one partition @@ -60,6 +69,10 @@ void wt_hsm_relay_set_submit(wt_hsm_relay_submit_fn fn, void* submit_ctx); * platform start hook; NULL (the default) refuses both. */ #define WT_HSM_OP_ATTEST_SIGN 1 #define WT_HSM_OP_ATTEST_PUBLIC_KEY 2 +#if defined(WT_PKCS11_HSM_RESTART_PROBE) && \ + (WT_PKCS11_HSM_RESTART_PROBE == 1) +#define WT_HSM_OP_TEST_RESTART 0x7f01 +#endif /* Signer result for "no Initial Attestation Key yet"; equals the engines' * WH_ERROR_NOTREADY (asserted where they are linked) so the relay stays free diff --git a/include/wolftrust/services/pkcs11_relay.h b/include/wolftrust/services/pkcs11_relay.h new file mode 100644 index 000000000..f5e3fbaa8 --- /dev/null +++ b/include/wolftrust/services/pkcs11_relay.h @@ -0,0 +1,46 @@ +/* pkcs11_relay.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_SERVICES_PKCS11_RELAY_H +#define WOLFTRUST_SERVICES_PKCS11_RELAY_H + +#include "wolftrust/ffm.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/spm_gate.h" + +typedef int (*wt_pkcs11_local_provider_fn)(uint8_t* request, + size_t request_len, + uint8_t* response, + size_t response_cap, + size_t* response_len, + void* context); + +void wt_pkcs11_relay_set_client(int32_t app_client_id); +void wt_pkcs11_relay_set_local_provider( + wt_pkcs11_local_provider_fn provider, void* context); +void wt_pkcs11_relay_set_transport(wt_spm_transport_fn fn); +int wt_pkcs11_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, + int32_t partition_id); + +#if defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1) +int wt_pkcs11_relay_restart_probe(int restarted); +#endif + +#endif /* WOLFTRUST_SERVICES_PKCS11_RELAY_H */ diff --git a/include/wolftrust/services/tpm_local_provider.h b/include/wolftrust/services/tpm_local_provider.h new file mode 100644 index 000000000..c1928b320 --- /dev/null +++ b/include/wolftrust/services/tpm_local_provider.h @@ -0,0 +1,34 @@ +/* tpm_local_provider.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_SERVICES_TPM_LOCAL_PROVIDER_H +#define WOLFTRUST_SERVICES_TPM_LOCAL_PROVIDER_H + +#include "wolftrust/services/tpm_relay.h" + +int wt_tpm_local_provider_init(void** context); +int wt_tpm_local_provider_process(const uint8_t* command, + size_t command_len, + uint8_t* response, + size_t response_cap, + size_t* response_len, + void* context); + +#endif /* WOLFTRUST_SERVICES_TPM_LOCAL_PROVIDER_H */ diff --git a/include/wolftrust/services/tpm_relay.h b/include/wolftrust/services/tpm_relay.h new file mode 100644 index 000000000..9e46f2282 --- /dev/null +++ b/include/wolftrust/services/tpm_relay.h @@ -0,0 +1,58 @@ +/* tpm_relay.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_SERVICES_TPM_RELAY_H +#define WOLFTRUST_SERVICES_TPM_RELAY_H + +#include "wolftrust/ffm.h" +#include "wolftrust/spm_gate.h" +#include "wolftrust/tpm/tpm_abi.h" + +typedef int (*wt_tpm_local_provider_fn)(const uint8_t* command, + size_t command_len, + uint8_t* response, + size_t response_cap, + size_t* response_len, + void* context); + +/* Bind the SPM-stamped Non-secure caller (guest N is -(N+1)). */ +void wt_tpm_relay_set_client(int32_t app_client_id); + +/* The fwTPM command handler executes inside SERVICE_TPM's private domain. */ +void wt_tpm_relay_set_local_provider(wt_tpm_local_provider_fn provider, + void* context); + +/* Transport seam, mirroring the other services: direct gate calls on the + * host, the SVC transport when scheduled on target. NULL restores default. */ +void wt_tpm_relay_set_transport(wt_spm_transport_fn fn); + +/* SERVICE_TPM's dispatch loop: wait, get, serve one op, reply. + * Architecture-neutral so the same code is host-tested through a real + * psa_connect/psa_call round trip and scheduled on target. */ +int wt_tpm_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, + int32_t partition_id); + +#if defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1) +/* Test-only witness that cold boot and restart restore this partition's + * initialized data and clear its BSS. */ +int wt_tpm_relay_restart_probe(int restarted); +#endif + +#endif /* WOLFTRUST_SERVICES_TPM_RELAY_H */ diff --git a/include/wolftrust/spm_sched.h b/include/wolftrust/spm_sched.h index e6d54e397..1df0b30ba 100644 --- a/include/wolftrust/spm_sched.h +++ b/include/wolftrust/spm_sched.h @@ -57,7 +57,9 @@ int wt_spm_sched_validate(void); int wt_spm_sched_current_is_partition(void); #if (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ - (defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0)) + (defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) /* Test builds only: run a probing partition through its faults at boot. */ void wt_spm_sched_prime(int32_t partition_id); #endif @@ -99,4 +101,12 @@ int wt_spm_fwu_start(wt_ffm_runtime_t* runtime, int32_t partition_id); int wt_spm_vnet_start(wt_ffm_runtime_t* runtime, int32_t partition_id); +/* SERVICE_TPM: the mediated door to the isolated fwTPM partition + * (CONFIG_TPM builds). */ +int wt_spm_tpm_start(wt_ffm_runtime_t* runtime, int32_t partition_id); + +/* SERVICE_PKCS11: copied-transfer relay between the application and provider + * guests (CONFIG_PKCS11 builds). */ +int wt_spm_pkcs11_start(wt_ffm_runtime_t* runtime, int32_t partition_id); + #endif /* WOLFTRUST_SPM_SCHED_H */ diff --git a/include/wolftrust/spm_transport.h b/include/wolftrust/spm_transport.h index cc9a8b363..d6ed10947 100644 --- a/include/wolftrust/spm_transport.h +++ b/include/wolftrust/spm_transport.h @@ -34,7 +34,6 @@ struct wt_trap_frame; #define WT_SP_FAULT_PROBE_RESTARTED 0x40000000 #define WT_SP_FAULT_PROBE_SECOND 0x20000000 -#if defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0) /* Test builds only. A partition's own translation unit plants state in its * data band before a deliberate fault; after the restart the band must hold * its link-time image again. Returns 1 when it does. */ @@ -50,6 +49,8 @@ struct wt_trap_frame; } \ return (name##_data == 0x5A5A5A5Au) && (name##_bss == 0u); \ } + +#if defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0) int wt_hsm_relay_restart_probe(int restarted); int wt_attestation_restart_probe(int restarted); int wt_vault_restart_probe(int restarted); diff --git a/include/wolftrust/tpm/tpm_abi.h b/include/wolftrust/tpm/tpm_abi.h new file mode 100644 index 000000000..06513cc3a --- /dev/null +++ b/include/wolftrust/tpm/tpm_abi.h @@ -0,0 +1,35 @@ +/* tpm_abi.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_TPM_TPM_ABI_H +#define WOLFTRUST_TPM_TPM_ABI_H + +/* SERVICE_TPM copies a command into its private domain, runs fwTPM, and + * returns the response in the same call. No external responder is needed. */ +#define WT_TPM_SERVICE_SID 4104U +#define WT_TPM_SERVICE_VERSION 1U + +/* Command plus response fits the 1024-byte FF-M copied-transfer budget. */ +#define WT_TPM_FRAME_MAX 512U + +/* invec[0]=command, outvec[0]=response. Never replay a failed call. */ +#define WT_TPM_OP_TRANSACT 5 + +#endif /* WOLFTRUST_TPM_TPM_ABI_H */ diff --git a/include/wolftrust/tpm_psa_transport.h b/include/wolftrust/tpm_psa_transport.h new file mode 100644 index 000000000..df91ab90e --- /dev/null +++ b/include/wolftrust/tpm_psa_transport.h @@ -0,0 +1,42 @@ +/* tpm_psa_transport.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_TPM_PSA_TRANSPORT_H +#define WOLFTRUST_TPM_PSA_TRANSPORT_H + +#include "wolftrust/tpm/tpm_abi.h" + +#include +#include + +/* Non-secure client helpers for SERVICE_TPM. Each call is one psa_call on a + * cached connection; a lost connection is reopened on the next call. The + * application moves whole TPM 2.0 frames to the Secure service, which runs + * fwTPM directly. Return 0, or the negative service/PSA status. */ + +/* Execute one command synchronously; transport errors never replay it. */ +int wt_tpm_psa_transact(const uint8_t* command, size_t command_len, + uint8_t* response, size_t response_cap, + size_t* response_len); + +/* Drop the cached connection (after a transport error or for teardown). */ +void wt_tpm_psa_close(void); + +#endif /* WOLFTRUST_TPM_PSA_TRANSPORT_H */ diff --git a/lib/wolfPKCS11 b/lib/wolfPKCS11 new file mode 160000 index 000000000..1fde317f4 --- /dev/null +++ b/lib/wolfPKCS11 @@ -0,0 +1 @@ +Subproject commit 1fde317f4f22ec06b9f0dadcd3a4b49e0a653c09 diff --git a/lib/wolfTPM b/lib/wolfTPM new file mode 160000 index 000000000..e9da7bab3 --- /dev/null +++ b/lib/wolfTPM @@ -0,0 +1 @@ +Subproject commit e9da7bab386f40bd8959c47590d4a9b328eb9121 diff --git a/mk/arch-aarch64.mk b/mk/arch-aarch64.mk index 1a45eb843..dc8b55ab9 100644 --- a/mk/arch-aarch64.mk +++ b/mk/arch-aarch64.mk @@ -26,6 +26,10 @@ ifneq ($(WT_ISOLATION_LEVEL),3) $(error only isolation level 3 is implemented (WT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL))) endif ARCH_CFLAGS += -DWT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL) +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +WT_INTERFACE_LOCAL_PROVIDER := 1 +ARCH_CFLAGS += -DWT_INTERFACE_LOCAL_PROVIDER=$(WT_INTERFACE_LOCAL_PROVIDER) +endif # Level 3 layer shared by every AArch64 port: the board's memory_map.h names # WT_L3_BAND_BASE and port/common/aarch64/l3_layout.h places every Secure band @@ -37,7 +41,8 @@ PORT_HEADERS += $(wildcard $(PORT_COMMON_DIR)/*.h) WT_L3_OVERRIDABLE := WT_SPM_BOOT_INFO_PA WT_SPM_TABLE_POOL_PA WT_SPM_IMAGE_PA \ WT_SPM_IMAGE_SIZE WT_SPM_RAM_PA WT_SPM_RAM_SIZE WT_SPM_CONFDATA_PA \ WT_SPM_CONFDATA_SIZE WT_SPM_KEYSTORE_PA WT_SPM_KEYSTORE_SIZE \ - WT_SPM_RXTX_PA WT_SPM_RXTX_SIZE WT_SPM_SHARE_PA WT_SPM_SHARE_SIZE + WT_SPM_RXTX_PA WT_SPM_RXTX_SIZE WT_SPM_SHARE_PA WT_SPM_SHARE_SIZE \ + WT_SPM_INTERFACE_PA WT_SPM_INTERFACE_SIZE WT_L3_TOOL := python3 $(ROOT)/tools/aarch64_l3_layout.py \ --cc $(TOOLPREFIX)gcc -I$(PORT_COMMON_DIR) \ -DWT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL) \ @@ -62,13 +67,15 @@ WT_L3_CFLAGS := $(foreach v,WT_SPM_BOOT_INFO_PA WT_SPM_TABLE_POOL_PA \ WT_SPM_IMAGE_PA WT_SPM_IMAGE_SIZE WT_SPM_RAM_PA WT_SPM_RAM_SIZE \ WT_SPM_KEYSTORE_PA WT_SPM_KEYSTORE_SIZE WT_SPM_RXTX_PA WT_SPM_RXTX_SIZE \ WT_SPM_SHARE_PA WT_SPM_SHARE_SIZE WT_SPM_CONFDATA_PA \ - WT_SPM_CONFDATA_SIZE,-D$(v)=$($(v))u) \ + WT_SPM_CONFDATA_SIZE WT_SPM_INTERFACE_PA \ + WT_SPM_INTERFACE_SIZE,-D$(v)=$($(v))u) \ -DWT_SPM_TABLE_POOL_PAGES=$(WT_SPM_TABLE_POOL_PAGES)u -I$(PORT_COMMON_DIR) WT_L3_LDFLAGS := $(foreach v,WT_SPM_IMAGE_PA WT_SPM_IMAGE_SIZE \ WT_SPM_RAM_PA WT_SPM_RAM_SIZE WT_SPM_KEYSTORE_PA WT_SPM_KEYSTORE_SIZE \ WT_SPM_VAULT_PA WT_SPM_VAULT_SIZE WT_SPM_ATTEST_PA WT_SPM_ATTEST_SIZE \ WT_SPM_HSMDATA_PA WT_SPM_HSMDATA_SIZE WT_SPM_CONFDATA_PA \ - WT_SPM_CONFDATA_SIZE,-Wl,--defsym=$(v)=$($(v))) + WT_SPM_CONFDATA_SIZE WT_SPM_INTERFACE_PA \ + WT_SPM_INTERFACE_SIZE,-Wl,--defsym=$(v)=$($(v))) TARGET_CFLAGS += $(WT_L3_CFLAGS) TARGET_LDFLAGS += $(WT_L3_LDFLAGS) TARGET_EXTRA_SRCS += $(PORT_COMMON_DIR)/platform_l3.c @@ -181,10 +188,18 @@ SECURE_CMSE_IMPLIB := ARCH_LINK_OUTPUTS := # The core is not entered yet; keep it linked so the closure is proven. ARCH_LDFLAGS := -Wl,--undefined=wt_boot_run -# The manifest must grant exactly the keystore bands the shared layout places. +# The manifest must grant exactly the bands the shared layout places, and the +# linked map must keep every object's writable state in its declared owner +# band. In particular, direct TPM and PKCS#11 provider state may only land in +# the optional interface band granted to that provider partition. define arch_image_checks $(WT_L3_TOOL) --check-manifest $(MANIFEST_INPUT) $(PORT_DIR)/memory_map.h \ || { rm -f $(SECURE_ELF); exit 1; } + @python3 $(ROOT)/tools/check_secure_layout.py --ownership-only \ + --owner-profile aarch64 --nm $(TOOLPREFIX)nm $(SECURE_ELF) \ + --map $(SECURE_MAP) --owners $(ROOT)/tools/secure_owners.txt \ + $(if $(filter 1,$(WT_CONFORMANCE)),,--production) \ + --objects-nm $(TOOLPREFIX)nm --objects $(ALL_SECURE_OBJS) endef $(BUILD_DIR)/wolftrust.elf: $(ROOT)/tools/aarch64_l3_layout.py \ $(PORT_COMMON_DIR)/l3_layout.h $(MANIFEST_INPUT) diff --git a/mk/arch-armv8m.mk b/mk/arch-armv8m.mk index 3bfe0c291..d2e46b994 100644 --- a/mk/arch-armv8m.mk +++ b/mk/arch-armv8m.mk @@ -3,6 +3,10 @@ TOOLPREFIX ?= arm-none-eabi- CPU_FLAGS := -mcpu=$(WT_CPU) -mthumb -mgeneral-regs-only ARCH_CFLAGS := -mcmse -DWT_TARGET_BUILD=1 +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +WT_INTERFACE_LOCAL_PROVIDER := 1 +ARCH_CFLAGS += -DWT_INTERFACE_LOCAL_PROVIDER=1 -DWT_PKCS11_HEAP_SIZE=0x7000U +endif # Architectural-context isolation negatives, test builds only. The FP-probe # build must carry exactly its one deliberate FP instruction and no other. @@ -74,11 +78,21 @@ $(BUILD_DIR)/wolftrust.elf $(ARCH_LINK_OUTPUTS): \ # CONFIG_VNET included (virtual networking rides SERVICE_VNET psa_call). NSC_ALLOWED := __acle_se_WolfTrust_FFM_(FrameworkVersion|ServiceVersion|Connect|Call|Close)$$ NSC_COUNT := 5 +WT_INTERFACE_BODY := $(if $(filter y,$(CONFIG_TPM)),FWTPM_ProcessCommand,\ + $(if $(filter y,$(CONFIG_PKCS11)),wt_pkcs11_provider_dispatch)) # Post-link image checks run by the common link rule. define arch_image_checks @$(TOOLPREFIX)nm $(SECURE_ELF) > $(BUILD_DIR)/nsc-syms.txt || \ { echo "FAIL: nm on the secure image failed" >&2; exit 1; } + @if grep -Eq ' (vTaskStartScheduler|xTaskCreate|pvPortMalloc|vPortFree)$$' \ + $(BUILD_DIR)/nsc-syms.txt; then \ + echo "FAIL: interface handler links an RTOS runtime" >&2; exit 1; \ + fi + @if test -n "$(strip $(WT_INTERFACE_BODY))" && \ + ! grep -Eq ' [TW] $(strip $(WT_INTERFACE_BODY))$$' $(BUILD_DIR)/nsc-syms.txt; then \ + echo "FAIL: selected interface handler is absent from wolfTrust" >&2; exit 1; \ + fi @if grep ' __acle_se_' $(BUILD_DIR)/nsc-syms.txt | \ grep -vE '$(NSC_ALLOWED)'; then \ echo "FAIL: non-secure-callable veneer outside the FF-M gateway (WT-FFM-0054)" >&2; \ @@ -111,3 +125,13 @@ define arch_image_checks python3 $(ROOT)/tools/check_stack_seal.py --estack "$$estack" \ --sstack "$$sstack" $(BUILD_DIR)/sec-sections.txt endef + +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +ifeq ($(TARGET),stm32h563) +WT_SECURE_LAYOUT_ARGS += --tpm-band 0x30065000:0x30071000 \ + --pkcs11-band 0x30065000:0x30071000 +else ifeq ($(TARGET),mimxrt700) +WT_SECURE_LAYOUT_ARGS += --tpm-band 0x301C5000:0x301D1000 \ + --pkcs11-band 0x301C5000:0x301D1000 +endif +endif diff --git a/mk/common.mk b/mk/common.mk index 6fb356ee6..759f28813 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -3,6 +3,7 @@ # ARCH_* inputs; repository paths come from the Makefile. CC := $(TOOLPREFIX)gcc OBJCOPY := $(TOOLPREFIX)objcopy +NM := $(TOOLPREFIX)nm SIZE := $(TOOLPREFIX)size # A linked image that fails any post-link security check must not remain a @@ -91,6 +92,9 @@ WT_PANIC_NEG_PROBE ?= 0 WT_BUSFAULT_NEG_PROBE ?= 0 WT_SVC_NEG_PROBE ?= 0 WT_VNET_NEG_PROBE ?= 0 +WT_TPM_NEG_PROBE ?= 0 +WT_PKCS11_NEG_PROBE ?= 0 +WT_PKCS11_HSM_RESTART_PROBE ?= 0 WT_MANIFEST_NEG_PROBE ?= 0 WT_REMEASURE_PROBE ?= 0 WT_BOOTUPDATE_PROBE ?= 0 @@ -109,7 +113,19 @@ WT_VNET_RX_IRQ ?= 130 WT_VNET_TIMEOUT_TICKS ?= 500 WT_VNET_UNKNOWN_UCAST_FLOOD ?= 0 -HSM_INCLUDES := -I$(WOLFHSM_DIR) -I$(WOLFSSL_DIR) -I$(BUILD_DIR) +# Firmware-TPM (fwTPM) subsystem. Off by default; when on, links the +# SERVICE_TPM mediated relay into the secure image so a Non-secure wolfTPM +# client reaches the isolated fwTPM partition. Host unit tests under +# tests/host/tpm_relay/ build regardless of this switch. +CONFIG_TPM ?= n + +# PKCS#11 subsystem. The application-side C_* proxy and provider guest both +# use SERVICE_PKCS11; the provider reaches SERVICE_HSM with the hsm engine. +CONFIG_PKCS11 ?= n +WT_INTERFACE_LOCAL_PROVIDER ?= 0 + +HSM_INCLUDES := -I$(WOLFHSM_DIR) -I$(WOLFSSL_DIR) -I$(WOLFTPM_DIR) \ + -I$(WOLFPKCS11_DIR) -I$(BUILD_DIR) HSM_INCLUDES_SECURE := $(HSM_INCLUDES) -I$(WOLFHAL_DIR) -I$(abspath $(WOLFHSM_RUNNER_DIR)) HSM_DEFS_SECURE := -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG \ -UNO_CODING \ @@ -136,6 +152,12 @@ SECURE_CFLAGS_COSE := -I$(WOLFCOSE_DIR)/include \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE endif +WOLFPKCS11_COMMON_DEFS := -DWOLFPKCS11_USER_SETTINGS \ + -DWOLFPKCS11_NO_STORE -DWOLFPKCS11_NO_TIME -DWOLFPKCS11_NO_ENV +ifeq ($(WT_ENGINE),hsm) +WOLFPKCS11_COMMON_DEFS += -DWOLFPKCS11_DEFAULT_DEVID=0x5748534D +endif + SECURE_CFLAGS := $(CPU_FLAGS) -std=c99 -ffreestanding -fno-builtin -nostdlib -Os -g \ -ffunction-sections -fdata-sections -Wall -Wextra \ -I$(ROOT)/include -I$(PORT_DIR) \ @@ -161,6 +183,44 @@ else WT_VNET_DATA_LENGTH := 0 endif +ifeq ($(CONFIG_PKCS11),y) +ifeq ($(filter stm32h563 mimxrt700 qemuvirt versal,$(TARGET)),) +$(error TARGET=$(TARGET) has no SERVICE_PKCS11 manifest; build with CONFIG_PKCS11=n) +endif +SECURE_CFLAGS += -DCONFIG_PKCS11=1 +ifeq ($(WT_INTERFACE_LOCAL_PROVIDER),1) +SECURE_CFLAGS += $(WOLFPKCS11_COMMON_DEFS) +ifneq ($(WT_PKCS11_PIN_MAX_FAILURES),) +ifneq ($(shell printf '%s' '$(WT_PKCS11_PIN_MAX_FAILURES)' | grep -Ex '[1-9][0-9]*'),$(WT_PKCS11_PIN_MAX_FAILURES)) +$(error WT_PKCS11_PIN_MAX_FAILURES must be > 0) +endif +SECURE_CFLAGS += -DWT_PKCS11_PIN_MAX_FAILURES=$(WT_PKCS11_PIN_MAX_FAILURES)U +endif +# Production keeps wolfPKCS11's 600,000-round PIN PBKDF2; only the QEMU +# emulator gate lowers it, explicitly, because emulated SHA-256 is too slow. +ifneq ($(WT_PKCS11_TEST_PBKDF2_ITERATIONS),) +$(info NOTE: emulator-only PIN PBKDF2 iterations=$(WT_PKCS11_TEST_PBKDF2_ITERATIONS)) +SECURE_CFLAGS += -DPBKDF2_ITERATIONS=$(WT_PKCS11_TEST_PBKDF2_ITERATIONS) +endif +endif +# The PKCS#11 relay owns the same mutually exclusive 20 KiB auxiliary band +# used by SERVICE_VNET. The linker gives it distinct section bounds. +WT_VNET_DATA_LENGTH := 0x5000 +endif + +ifeq ($(ARCH),armv8m) +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +WT_VNET_DATA_LENGTH := 0x10000 +endif +endif + +ifeq ($(CONFIG_TPM),y) +ifeq ($(filter stm32h563 mimxrt700 qemuvirt versal,$(TARGET)),) +$(error TARGET=$(TARGET) has no SERVICE_TPM manifest; build with CONFIG_TPM=n) +endif +SECURE_CFLAGS += -DCONFIG_TPM=1 +endif + ifeq ($(WT_LAUNCH_DEBUG),1) SECURE_CFLAGS += -DWT_LAUNCH_DEBUG=1 endif @@ -214,6 +274,24 @@ endif ifeq ($(WT_VNET_NEG_PROBE),1) SECURE_CFLAGS += -DWT_VNET_NEG_PROBE=1 endif +ifeq ($(WT_TPM_NEG_PROBE),1) +ifneq ($(CONFIG_TPM),y) +$(error WT_TPM_NEG_PROBE=1 requires CONFIG_TPM=y) +endif +SECURE_CFLAGS += -DWT_TPM_NEG_PROBE=1 +endif +ifeq ($(WT_PKCS11_NEG_PROBE),1) +ifneq ($(CONFIG_PKCS11),y) +$(error WT_PKCS11_NEG_PROBE=1 requires CONFIG_PKCS11=y) +endif +SECURE_CFLAGS += -DWT_PKCS11_NEG_PROBE=1 +endif +ifeq ($(WT_PKCS11_HSM_RESTART_PROBE),1) +ifneq ($(CONFIG_PKCS11)/$(WT_ENGINE_HSM)/$(WT_INTERFACE_LOCAL_PROVIDER),y/1/1) +$(error WT_PKCS11_HSM_RESTART_PROBE=1 requires the local HSM PKCS11 provider) +endif +SECURE_CFLAGS += -DWT_PKCS11_HSM_RESTART_PROBE=1 +endif ifneq ($(filter 1 2 3,$(WT_MANIFEST_NEG_PROBE)),) SECURE_CFLAGS += -DWT_MANIFEST_NEG_PROBE=$(WT_MANIFEST_NEG_PROBE) endif @@ -345,6 +423,36 @@ WOLFCRYPT_SECURE_SRCS := \ $(WOLFSSL_DIR)/wolfcrypt/src/wc_port.c WOLFCRYPT_SECURE_SRCS += $(ARCH_WOLFCRYPT_ASM_SRCS) +WOLFTPM_SECURE_SRCS := +WOLFPKCS11_SECURE_SRCS := +TPM_WOLFCRYPT_SECURE_SRCS := +P11_WOLFCRYPT_SECURE_SRCS := +P11_WOLFHSM_SECURE_SRCS := +TPM_WOLFHSM_SECURE_SRCS := +WOLFTPM_SECURE_CFLAGS := -DWOLFTPM_USER_SETTINGS -DWOLFTPM_FWTPM \ + -DFWTPM_NO_NV -include wolftrust/tpm/tpm_abi.h \ + -DFWTPM_MAX_COMMAND_SIZE=WT_TPM_FRAME_MAX \ + -DFWTPM_MAX_PERSISTENT=2 -DFWTPM_MAX_SESSIONS=2 \ + -DFWTPM_NO_POLICY -DFWTPM_NO_CREDENTIAL -DFWTPM_NO_DA \ + -DFWTPM_NO_PARAM_ENC -DFWTPM_NO_KEY_MIGRATION \ + -DFWTPM_NO_CONTEXT -DFWTPM_NO_SYM_ENCRYPT -DFWTPM_NO_CLOCK +TPM_CRYPTO_DEVICE_CFLAGS := $(if $(filter hsm,$(WT_ENGINE)),-DWOLF_CRYPTO_CB_FIND) +WOLFTPM_SECURE_CFLAGS += $(TPM_CRYPTO_DEVICE_CFLAGS) +WOLFPKCS11_SECURE_CFLAGS := $(WOLFPKCS11_COMMON_DEFS) -DWOLFSSL_PUBLIC_MP \ + -include time.h +WT_PROVIDER_ECC_FALLBACK ?= 0 +ifeq ($(WT_ENGINE)/$(WT_PROVIDER_ECC_FALLBACK),hsm/0) +TPM_CRYPTO_DEVICE_CFLAGS += -DWT_TPM_HSM_CALLBACK_ECC +WOLFTPM_SECURE_CFLAGS += -DWT_TPM_HSM_CALLBACK_ECC +WOLFPKCS11_SECURE_CFLAGS += -DWOLF_CRYPTO_CB_ONLY_ECC +else ifeq ($(WT_ENGINE),hsm) +ifneq ($(WT_PROVIDER_ECC_FALLBACK),1) +$(error WT_PROVIDER_ECC_FALLBACK must be 0 or 1) +endif +endif + +$(BUILD_DIR)/wt_sec_tpm_local_provider.o: SECURE_CFLAGS += $(WOLFTPM_SECURE_CFLAGS) + WT_SECURE_EXTRA_SRCS := \ $(ARCH_SRCS) \ $(ROOT)/src/arch/common/spm_gate_core.c \ @@ -409,10 +517,102 @@ WT_SECURE_EXTRA_SRCS += \ $(ROOT)/src/services/vnet/vnet_relay_service.c endif +ifeq ($(WT_INTERFACE_LOCAL_PROVIDER),1) +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +include $(ROOT)/mk/crypto-profile.mk +SECURE_CFLAGS += $(WT_CRYPTO_PROFILE_FLAGS) +HSM_LIB_CFLAGS += $(WT_CRYPTO_PROFILE_FLAGS) +HSM_WOLFHSM_CFLAGS += $(WT_CRYPTO_PROFILE_FLAGS) +endif +endif + +ifeq ($(CONFIG_TPM),y) +WT_SECURE_EXTRA_SRCS += \ + $(ROOT)/src/services/tpm/tpm_relay_service.c +ifeq ($(WT_INTERFACE_LOCAL_PROVIDER),1) +WOLFTPM_SECURE_SRCS += \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm.c \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm_command.c \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm_crypto.c \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm_nv.c \ + $(WOLFTPM_DIR)/src/tpm2_util.c \ + $(WOLFTPM_DIR)/src/tpm2_packet.c \ + $(WOLFTPM_DIR)/src/tpm2_param_enc.c \ + $(WOLFTPM_DIR)/src/tpm2_crypto.c \ + $(ROOT)/src/client/provider_crypto.c +ifeq ($(WT_ENGINE),hsm) +WOLFTPM_SECURE_SRCS += $(ROOT)/src/client/hsm_psa_transport.c +TPM_WOLFHSM_SECURE_SRCS := \ + $(WOLFHSM_DIR)/src/wh_client.c \ + $(WOLFHSM_DIR)/src/wh_client_crypto.c \ + $(WOLFHSM_DIR)/src/wh_client_cryptocb.c +endif +WT_SECURE_EXTRA_SRCS += $(ROOT)/src/services/tpm/tpm_local_provider.c +endif +endif + +ifeq ($(CONFIG_PKCS11),y) +WT_SECURE_EXTRA_SRCS += \ + $(ROOT)/src/services/pkcs11/pkcs11_relay_service.c +ifeq ($(WT_INTERFACE_LOCAL_PROVIDER),1) +WOLFPKCS11_SECURE_SRCS += \ + $(WOLFPKCS11_DIR)/src/wolfpkcs11.c \ + $(WOLFPKCS11_DIR)/src/internal.c \ + $(WOLFPKCS11_DIR)/src/slot.c \ + $(WOLFPKCS11_DIR)/src/crypto.c \ + $(ROOT)/src/client/provider_crypto.c +ifeq ($(WT_ENGINE),hsm) +WOLFPKCS11_SECURE_SRCS += $(ROOT)/src/client/hsm_psa_transport.c +P11_WOLFHSM_SECURE_SRCS += \ + $(WOLFHSM_DIR)/src/wh_client.c \ + $(WOLFHSM_DIR)/src/wh_client_crypto.c \ + $(WOLFHSM_DIR)/src/wh_client_cryptocb.c +endif +WOLFCRYPT_SECURE_SRCS += \ + $(WOLFSSL_DIR)/wolfcrypt/src/sha512.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/kdf.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/pwdbased.c +P11_WOLFCRYPT_SECURE_SRCS := $(WOLFCRYPT_SECURE_SRCS) +WT_SECURE_EXTRA_SRCS += \ + $(ROOT)/src/pkcs11/pkcs11_wire.c \ + $(ROOT)/src/pkcs11/pkcs11_provider.c \ + $(ROOT)/src/pkcs11/pkcs11_secure_heap.c +endif +endif + +ifeq ($(CONFIG_TPM)/$(WT_INTERFACE_LOCAL_PROVIDER),y/1) +TPM_WOLFCRYPT_SECURE_SRCS := $(WOLFCRYPT_SECURE_SRCS) +endif + +# Shared code has no writable state; working contexts stay in the caller domain. +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +HSM_LIB_CFLAGS += -DWOLFSSL_PUBLIC_MP +WT_INTERFACE_SHARED_CRYPTO := $(ARCH_WOLFCRYPT_ASM_SRCS) \ + $(WOLFSSL_DIR)/wolfcrypt/src/sp_int.c +ifeq ($(WT_ENGINE),native) +WT_INTERFACE_SHARED_CRYPTO := $(filter-out \ + %/wc_port.c %/cryptocb.c %/random.c %/pwdbased.c,$(WOLFCRYPT_SECURE_SRCS)) +endif +P11_WOLFCRYPT_SECURE_SRCS := $(filter-out $(WT_INTERFACE_SHARED_CRYPTO),$(P11_WOLFCRYPT_SECURE_SRCS)) +TPM_WOLFCRYPT_SECURE_SRCS := $(filter-out $(WT_INTERFACE_SHARED_CRYPTO),$(TPM_WOLFCRYPT_SECURE_SRCS)) +endif + HSM_SECURE_BASE_OBJS := $(patsubst %.c,$(BUILD_DIR)/sec_%.o,$(notdir $(SECURE_SRCS))) HSM_WOLFHSM_SEC_OBJS := $(patsubst %.c,$(BUILD_DIR)/wh_sec_%.o,$(notdir $(WOLFHSM_SECURE_SRCS))) HSM_WOLFCRYPT_SEC_OBJS := $(patsubst %.c,$(BUILD_DIR)/wc_sec_%.o,$(notdir $(WOLFCRYPT_SECURE_SRCS))) HSM_WT_EXTRA_OBJS := $(patsubst %.c,$(BUILD_DIR)/wt_sec_%.o,$(notdir $(WT_SECURE_EXTRA_SRCS))) +WOLFTPM_SECURE_OBJS := $(patsubst %.c,$(BUILD_DIR)/tpm_sec_%.o,$(notdir $(WOLFTPM_SECURE_SRCS))) +WOLFPKCS11_SECURE_OBJS := $(patsubst %.c,$(BUILD_DIR)/p11_sec_%.o,$(notdir $(WOLFPKCS11_SECURE_SRCS))) +TPM_WOLFCRYPT_RAW_OBJS := $(patsubst %.c,$(BUILD_DIR)/tpm_wc_raw_%.o,$(notdir $(TPM_WOLFCRYPT_SECURE_SRCS))) +TPM_WOLFCRYPT_SEC_OBJS := $(patsubst %.c,$(BUILD_DIR)/tpm_wc_%.o,$(notdir $(TPM_WOLFCRYPT_SECURE_SRCS))) +P11_WOLFCRYPT_RAW_OBJS := $(patsubst %.c,$(BUILD_DIR)/p11_wc_raw_%.o,$(notdir $(P11_WOLFCRYPT_SECURE_SRCS))) +P11_WOLFCRYPT_SEC_OBJS := $(patsubst %.c,$(BUILD_DIR)/p11_wc_%.o,$(notdir $(P11_WOLFCRYPT_SECURE_SRCS))) +P11_WOLFHSM_RAW_OBJS := $(patsubst %.c,$(BUILD_DIR)/p11_wh_raw_%.o,$(notdir $(P11_WOLFHSM_SECURE_SRCS))) +P11_WOLFHSM_SEC_OBJS := $(patsubst %.c,$(BUILD_DIR)/p11_wh_%.o,$(notdir $(P11_WOLFHSM_SECURE_SRCS))) +TPM_WOLFHSM_RAW_OBJS := $(patsubst %.c,$(BUILD_DIR)/tpm_wh_raw_%.o,$(notdir $(TPM_WOLFHSM_SECURE_SRCS))) +TPM_WOLFHSM_SEC_OBJS := $(patsubst %.c,$(BUILD_DIR)/tpm_wh_%.o,$(notdir $(TPM_WOLFHSM_SECURE_SRCS))) +TPM_NAMESPACE_MAP := $(BUILD_DIR)/tpm_wc_symbols.map +P11_NAMESPACE_MAP := $(BUILD_DIR)/p11_wc_symbols.map MANIFEST_OBJ := $(BUILD_DIR)/wt_sec_wolftrust_manifest_generated.o # Arch sources that live in subdirectories of src/arch// (C or assembly); # each gets its own rule below because the pattern rules key on one directory. @@ -426,9 +626,28 @@ ALL_SECURE_OBJS := $(strip \ $(HSM_WOLFHSM_SEC_OBJS) \ $(HSM_WOLFCRYPT_SEC_OBJS) \ $(HSM_WT_EXTRA_OBJS) \ + $(WOLFTPM_SECURE_OBJS) \ + $(WOLFPKCS11_SECURE_OBJS) \ + $(TPM_WOLFCRYPT_SEC_OBJS) \ + $(TPM_WOLFHSM_SEC_OBJS) \ + $(P11_WOLFCRYPT_SEC_OBJS) \ + $(P11_WOLFHSM_SEC_OBJS) \ $(ARCH_SECURE_OBJS) \ $(MANIFEST_OBJ)) +ifneq ($(WT_CRYPTO_PROFILE_HEADER),) +# Namespaced objects also have raw compile prerequisites; a dependency on +# only the final object does not order those compilations after generation. +WT_PROFILE_RAW_OBJS := $(TPM_WOLFCRYPT_RAW_OBJS) $(P11_WOLFCRYPT_RAW_OBJS) \ + $(TPM_WOLFHSM_RAW_OBJS) $(P11_WOLFHSM_RAW_OBJS) \ + $(patsubst $(BUILD_DIR)/tpm_sec_%.o,$(BUILD_DIR)/tpm_sec_raw_%.o,$(WOLFTPM_SECURE_OBJS)) \ + $(patsubst $(BUILD_DIR)/p11_sec_%.o,$(BUILD_DIR)/p11_sec_raw_%.o,$(WOLFPKCS11_SECURE_OBJS)) +$(ALL_SECURE_OBJS) $(WT_PROFILE_RAW_OBJS): $(WT_CRYPTO_PROFILE_HEADER) +endif + +# Rebuild namespace maps when makefile edits change private source membership. +$(ALL_SECURE_OBJS) $(WT_PROFILE_RAW_OBJS): $(MAKEFILE_LIST) + # LTO cannot safely rewrite objects whose symbols are consumed by inline # assembly or synthesized by the CMSE linker. Objects that own a # filename-selected isolation band retain their object identity. @@ -1427,13 +1646,21 @@ $(MANIFEST_DIR): # The stamp records the selected variant; a mismatch regenerates even when # mtimes tie within one second, so a stale variant can never be linked. MANIFEST_ARCH_OPTS ?= --address-bits 32 -MANIFEST_MODE := MANIFEST_INPUT=$(MANIFEST_INPUT) CONFIG_VNET=$(CONFIG_VNET) WT_CONFORMANCE=$(WT_CONFORMANCE) GEN_OPTS=--supported-features 0x1 --supported-framework-version 0x100 $(MANIFEST_ARCH_OPTS) +MANIFEST_GUEST_LAYOUT_ARGS ?= +MANIFEST_LAYOUT_CHECK := $(ROOT)/tools/manifest/check_guest_layout.py +MANIFEST_MODE := MANIFEST_INPUT=$(MANIFEST_INPUT) CONFIG_VNET=$(CONFIG_VNET) CONFIG_TPM=$(CONFIG_TPM) CONFIG_PKCS11=$(CONFIG_PKCS11) WT_CONFORMANCE=$(WT_CONFORMANCE) GUEST_LAYOUT=$(MANIFEST_GUEST_LAYOUT_ARGS) GEN_OPTS=--supported-features 0x1 --supported-framework-version 0x100 $(MANIFEST_ARCH_OPTS) -$(MANIFEST_STAMP): $(ROOT)/tools/manifest/generate.py $(MANIFEST_INPUT) \ +$(MANIFEST_STAMP): $(ROOT)/tools/manifest/generate.py \ + $(MANIFEST_LAYOUT_CHECK) $(MANIFEST_INPUT) \ FORCE | $(MANIFEST_DIR) + @if test -n '$(strip $(MANIFEST_GUEST_LAYOUT_ARGS))'; then \ + python3 $(MANIFEST_LAYOUT_CHECK) $(MANIFEST_INPUT) \ + $(MANIFEST_GUEST_LAYOUT_ARGS); \ + fi @if test -f "$@" && test "$$(cat "$@" 2>/dev/null)" = '$(MANIFEST_MODE)' \ && ! test $(MANIFEST_INPUT) -nt "$@" \ - && ! test $(ROOT)/tools/manifest/generate.py -nt "$@"; then \ + && ! test $(ROOT)/tools/manifest/generate.py -nt "$@" \ + && ! test $(MANIFEST_LAYOUT_CHECK) -nt "$@"; then \ :; \ else \ python3 $(ROOT)/tools/manifest/generate.py $(MANIFEST_INPUT) \ @@ -1468,9 +1695,13 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_GUEST1_FLASH_SIZE=$(WT_GUEST1_FLASH_SIZE)' \ 'WT_ENGINE_HSM=$(WT_ENGINE_HSM)' \ 'WT_LTO=$(WT_LTO)' \ + 'WT_PROVIDER_ECC_FALLBACK=$(WT_PROVIDER_ECC_FALLBACK)' \ 'WT_ATTEST_COSE=$(WT_ATTEST_COSE)' \ 'WT_CONFORMANCE=$(WT_CONFORMANCE)' \ 'CONFIG_VNET=$(CONFIG_VNET)' \ + 'CONFIG_TPM=$(CONFIG_TPM)' \ + 'TPM_CRYPTO_DEVICE_CFLAGS=$(TPM_CRYPTO_DEVICE_CFLAGS)' \ + 'CONFIG_PKCS11=$(CONFIG_PKCS11)' \ 'WT_FFM_NEGATIVE_PROBE=$(WT_FFM_NEGATIVE_PROBE)' \ 'WT_KEYSTORE_NEG_PROBE=$(WT_KEYSTORE_NEG_PROBE)' \ 'WT_PERIPH_SP_NEG_PROBE=$(WT_PERIPH_SP_NEG_PROBE)' \ @@ -1485,6 +1716,9 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_SP_FAULT_ALWAYS_PROBE=$(WT_SP_FAULT_ALWAYS_PROBE)' \ 'WT_PANIC_NEG_PROBE=$(WT_PANIC_NEG_PROBE)' \ 'WT_VNET_NEG_PROBE=$(WT_VNET_NEG_PROBE)' \ + 'WT_TPM_NEG_PROBE=$(WT_TPM_NEG_PROBE)' \ + 'WT_PKCS11_NEG_PROBE=$(WT_PKCS11_NEG_PROBE)' \ + 'WT_PKCS11_HSM_RESTART_PROBE=$(WT_PKCS11_HSM_RESTART_PROBE)' \ 'WT_MANIFEST_NEG_PROBE=$(WT_MANIFEST_NEG_PROBE)' \ 'WT_FP_NEG_PROBE=$(WT_FP_NEG_PROBE)' \ 'WT_SEAL_NEG_PROBE=$(WT_SEAL_NEG_PROBE)' \ @@ -1518,6 +1752,9 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_GUEST_CORE_CLOCK_HZ=$(WT_GUEST_CORE_CLOCK_HZ)' \ 'WT_GUEST_UART_CLOCK_HZ=$(WT_GUEST_UART_CLOCK_HZ)' \ 'WT_EXTRA_CFLAGS=$(WT_EXTRA_CFLAGS)' \ + 'WT_INTERFACE_SHARED_CRYPTO=$(WT_INTERFACE_SHARED_CRYPTO)' \ + 'WT_PKCS11_PIN_MAX_FAILURES=$(WT_PKCS11_PIN_MAX_FAILURES)' \ + 'WT_PKCS11_TEST_PBKDF2_ITERATIONS=$(WT_PKCS11_TEST_PBKDF2_ITERATIONS)' \ 'WT_EXTRA_LDFLAGS=$(WT_EXTRA_LDFLAGS)' > "$$tmp"; \ if test -f "$@" && cmp -s "$$tmp" "$@"; then \ rm -f "$$tmp"; \ @@ -1537,12 +1774,95 @@ $(BUILD_DIR)/wc_sec_%.o: $(WOLFSSL_DIR)/wolfcrypt/src/port/arm/%.c $(WOLFHSM_CFG $(BUILD_DIR)/wc_sec_%.o: $(WOLFSSL_DIR)/wolfcrypt/src/port/st/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(HSM_LIB_CFLAGS) -c -o $@ $< +ifeq ($(WT_INTERFACE_LOCAL_PROVIDER),1) +ifeq ($(CONFIG_TPM),y) +$(BUILD_DIR)/tpm_sec_raw_fwtpm_command.o: $(WT_CRYPTO_PROFILE_DIR)/fwtpm_command.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFTPM_SECURE_CFLAGS) -c -o $@ $< +endif + +$(BUILD_DIR)/tpm_sec_raw_%.o: $(WOLFTPM_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFTPM_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm_sec_raw_%.o: $(WOLFTPM_DIR)/src/fwtpm/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFTPM_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm_sec_raw_%.o: $(ROOT)/src/client/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) -UWC_NO_DEFAULT_DEVID $(WOLFTPM_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm_wh_raw_%.o: $(WOLFHSM_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_WOLFHSM_CFLAGS) -UWC_NO_DEFAULT_DEVID -c -o $@ $< + +$(BUILD_DIR)/tpm_wh_%.o: $(BUILD_DIR)/tpm_wh_raw_%.o $(TPM_NAMESPACE_MAP) + $(OBJCOPY) --redefine-syms=$(TPM_NAMESPACE_MAP) $< $@ + +$(BUILD_DIR)/tpm_wc_raw_%.o: $(WOLFSSL_DIR)/wolfcrypt/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) -UWC_NO_DEFAULT_DEVID $(TPM_CRYPTO_DEVICE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm_wc_raw_%.o: $(WOLFSSL_DIR)/wolfcrypt/src/port/arm/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) -UWC_NO_DEFAULT_DEVID $(TPM_CRYPTO_DEVICE_CFLAGS) -c -o $@ $< + +$(TPM_NAMESPACE_MAP): $(TPM_WOLFCRYPT_RAW_OBJS) | $(BUILD_DIR) + @$(NM) -g --defined-only $^ | awk 'NF >= 2 && $$(NF - 1) ~ /^[A-ZTWV]$$/ { print $$NF, "wt_tpm_" $$NF }' | LC_ALL=C sort -u > $@.tmp + @mv $@.tmp $@ + +$(BUILD_DIR)/tpm_wc_%.o: $(BUILD_DIR)/tpm_wc_raw_%.o $(TPM_NAMESPACE_MAP) + $(OBJCOPY) --redefine-syms=$(TPM_NAMESPACE_MAP) $< $@ + +$(BUILD_DIR)/tpm_sec_%.o: $(BUILD_DIR)/tpm_sec_raw_%.o $(TPM_NAMESPACE_MAP) + $(OBJCOPY) --redefine-syms=$(TPM_NAMESPACE_MAP) $< $@ + +ifeq ($(CONFIG_PKCS11),y) +$(BUILD_DIR)/p11_sec_raw_crypto.o: $(WT_CRYPTO_PROFILE_DIR)/pkcs11_crypto.c \ + $(WOLFPKCS11_DIR)/src/crypto.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< +endif + +$(BUILD_DIR)/p11_sec_raw_%.o: $(WOLFPKCS11_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/p11_sec_raw_%.o: $(ROOT)/src/client/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/p11_wc_raw_%.o: $(WOLFSSL_DIR)/wolfcrypt/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/p11_wc_raw_%.o: $(WOLFSSL_DIR)/wolfcrypt/src/port/arm/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/p11_wh_raw_%.o: $(WOLFHSM_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_WOLFHSM_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< + +$(P11_NAMESPACE_MAP): $(P11_WOLFCRYPT_RAW_OBJS) | $(BUILD_DIR) + @$(NM) -g --defined-only $^ | awk 'NF >= 2 && $$(NF - 1) ~ /^[A-ZTWV]$$/ { print $$NF, "wt_p11_" $$NF }' | LC_ALL=C sort -u > $@.tmp + @mv $@.tmp $@ + +$(BUILD_DIR)/p11_wc_%.o: $(BUILD_DIR)/p11_wc_raw_%.o $(P11_NAMESPACE_MAP) + $(OBJCOPY) --redefine-syms=$(P11_NAMESPACE_MAP) $< $@ + +$(BUILD_DIR)/p11_sec_%.o: $(BUILD_DIR)/p11_sec_raw_%.o $(P11_NAMESPACE_MAP) + $(OBJCOPY) --redefine-syms=$(P11_NAMESPACE_MAP) $< $@ + +$(BUILD_DIR)/p11_wh_%.o: $(BUILD_DIR)/p11_wh_raw_%.o $(P11_NAMESPACE_MAP) + $(OBJCOPY) --redefine-syms=$(P11_NAMESPACE_MAP) $< $@ +else +$(BUILD_DIR)/tpm_sec_%.o: $(WOLFTPM_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFTPM_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm_sec_%.o: $(WOLFTPM_DIR)/src/fwtpm/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFTPM_SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/p11_sec_%.o: $(WOLFPKCS11_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(HSM_LIB_CFLAGS) $(WOLFPKCS11_SECURE_CFLAGS) -c -o $@ $< +endif + $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/arch/$(ARCH)/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/arch/common/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< +$(BUILD_DIR)/wt_sec_spm_gate_core.o: $(MANIFEST_GEN_H) + $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/sched/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< @@ -1576,6 +1896,18 @@ $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/vnet/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAM $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/services/vnet/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< +$(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/services/tpm/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/services/pkcs11/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/client/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/pkcs11/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/services/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< diff --git a/mk/crypto-profile.mk b/mk/crypto-profile.mk new file mode 100644 index 000000000..700c76274 --- /dev/null +++ b/mk/crypto-profile.mk @@ -0,0 +1,38 @@ +# crypto-profile.mk +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +# Generate one operation selection shared by the provider and its clients. +WT_CRYPTO_PROFILE ?= default +WT_CRYPTO_PROFILE_DIR := $(abspath $(BUILD_DIR)/crypto-profile) +WT_CRYPTO_PROFILE_HEADER := $(WT_CRYPTO_PROFILE_DIR)/selected_profile.h +WT_CRYPTO_PROFILE_FLAGS := -include $(WT_CRYPTO_PROFILE_HEADER) +export WT_CRYPTO_PROFILE WT_CRYPTO_CONFIG WT_CRYPTO_ENABLE WT_CRYPTO_DISABLE + +.PHONY: crypto-profile-force +crypto-profile-force: + +$(WT_CRYPTO_PROFILE_HEADER): crypto-profile-force + @python3 $(ROOT)/tools/crypto_profile.py --root $(abspath $(ROOT)) \ + --output $(WT_CRYPTO_PROFILE_DIR) --profile $(WT_CRYPTO_PROFILE) + +$(WT_CRYPTO_PROFILE_DIR)/fwtpm_command.c: $(WT_CRYPTO_PROFILE_HEADER) + @test -f $@ + +$(WT_CRYPTO_PROFILE_DIR)/pkcs11_crypto.c: $(WT_CRYPTO_PROFILE_HEADER) + @test -f $@ diff --git a/mk/target-mimxrt700.mk b/mk/target-mimxrt700.mk index 4ffd1da1f..2e35cd02c 100644 --- a/mk/target-mimxrt700.mk +++ b/mk/target-mimxrt700.mk @@ -10,8 +10,21 @@ TARGET_CONF_DIR := $(PORT_DIR)/conformance ifeq ($(CONFIG_VNET),y) $(error TARGET=mimxrt700 has no SERVICE_VNET manifest; build with CONFIG_VNET=n) endif +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +WT_MAX_GUESTS ?= 1 +ifeq ($(WT_CONFORMANCE),1) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with WT_CONFORMANCE=1; the manifests are mutually exclusive) +endif +endif +ifeq ($(CONFIG_TPM)$(CONFIG_PKCS11),yy) +$(error CONFIG_TPM=y conflicts with CONFIG_PKCS11=y; select one application API) +endif ifeq ($(WT_CONFORMANCE),1) MANIFEST_INPUT := $(PORT_DIR)/manifest-conformance.json +else ifeq ($(CONFIG_TPM),y) +MANIFEST_INPUT := $(PORT_DIR)/manifest-tpm.json +else ifeq ($(CONFIG_PKCS11),y) +MANIFEST_INPUT := $(PORT_DIR)/manifest-pkcs11.json else MANIFEST_INPUT := $(PORT_DIR)/manifest.json endif @@ -37,6 +50,9 @@ WT_GUEST0_FLASH_BASE ?= 0x28080000 WT_GUEST1_FLASH_BASE ?= 0x28100000 WT_GUEST0_FLASH_SIZE ?= 0x00080000 WT_GUEST1_FLASH_SIZE ?= 0x00040000 +MANIFEST_GUEST_LAYOUT_ARGS := \ + --guest 1=$(WT_GUEST0_FLASH_BASE):$(WT_GUEST0_FLASH_SIZE) \ + --guest 2=$(WT_GUEST1_FLASH_BASE):$(WT_GUEST1_FLASH_SIZE) TARGET_CFLAGS := \ -DWT_SHARED_UART=$(WT_SHARED_UART) \ @@ -60,7 +76,9 @@ SECURE_LD := $(PORT_DIR)/secure.ld # conformance band from memory_map.h; this port uses no wolfHAL. WT_SECURE_LAYOUT_ARGS := --band vault=0x301D5000:0x301D7000 \ --band attest=0x301D7000:0x301D7800 --band hsm=0x301D7800:0x301E9000 \ - --confdata 0x301F3000:0x301F5C00 --no-wolfhal + --confdata 0x301F3000:0x301F5C00 --tpm-band 0x301F5000:0x301F5800 \ + --pkcs11-band 0x301D0000:0x301D5000 \ + --no-wolfhal TARGET_PLATFORM_SRC := $(PORT_DIR)/platform_mimxrt700.c TARGET_PARTITIONS_SRC := $(PORT_DIR)/partitions.c diff --git a/mk/target-qemuvirt.mk b/mk/target-qemuvirt.mk index 5a0eb4d52..9102714cf 100644 --- a/mk/target-qemuvirt.mk +++ b/mk/target-qemuvirt.mk @@ -9,12 +9,37 @@ PORT_HEADERS := $(wildcard $(PORT_DIR)/*.h) # The conformance PAL is shared by the AArch64 targets; WT_CONFORMANCE=1 swaps # in the manifest that also hosts Arm's test partitions. TARGET_CONF_DIR := $(ROOT)/port/common/aarch64/conformance +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +ifeq ($(WT_CONFORMANCE),1) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with WT_CONFORMANCE=1; the manifests are mutually exclusive) +endif +ifeq ($(CONFIG_VNET),y) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with CONFIG_VNET=y; the manifests are mutually exclusive) +endif +endif +ifeq ($(CONFIG_TPM)$(CONFIG_PKCS11),yy) +$(error CONFIG_TPM=y conflicts with CONFIG_PKCS11=y; select one application API) +endif ifeq ($(WT_CONFORMANCE),1) MANIFEST_INPUT := $(PORT_DIR)/manifest-conformance.json +else ifeq ($(CONFIG_TPM),y) +MANIFEST_INPUT := $(BUILD_DIR)/manifest-tpm.json +else ifeq ($(CONFIG_PKCS11),y) +MANIFEST_INPUT := $(BUILD_DIR)/manifest-pkcs11.json else MANIFEST_INPUT := $(PORT_DIR)/manifest.json endif +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +$(BUILD_DIR)/manifest-%.json: $(PORT_DIR)/manifest.json \ + $(ROOT)/tools/manifest/aarch64_interface.py + @mkdir -p $(@D) + python3 $(ROOT)/tools/manifest/aarch64_interface.py \ + $(PORT_DIR)/manifest.json $@ --interface $* \ + --stack-base 0x0E2A0000 --data-base 0x0E350000 \ + --data-size 0x00080000 +endif + WT_EL3_TEXT_BASE ?= 0x00000000 WT_EL3_RAM_BASE ?= 0x0E000000 WT_EL3_RAM_SIZE ?= 0x00040000 diff --git a/mk/target-stm32h563.mk b/mk/target-stm32h563.mk index 5b9533008..e8d4043ca 100644 --- a/mk/target-stm32h563.mk +++ b/mk/target-stm32h563.mk @@ -7,11 +7,29 @@ TARGET_CONF_DIR := $(PORT_DIR)/conformance # WT_CONFORMANCE=1 swaps in the manifest that also hosts Arm's test partitions; # CONFIG_VNET=y swaps in the variant that adds the SERVICE_VNET partition so -# the default image carries no virtual network service at all +# the default image carries no virtual network service at all. These manifest +# variants are mutually exclusive, so reject a combination that would silently +# discard a requested service rather than build it. +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +WT_MAX_GUESTS ?= 1 +ifeq ($(WT_CONFORMANCE),1) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with WT_CONFORMANCE=1; the manifests are mutually exclusive) +endif +ifeq ($(CONFIG_VNET),y) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with CONFIG_VNET=y; the manifests are mutually exclusive) +endif +endif +ifeq ($(CONFIG_TPM)$(CONFIG_PKCS11),yy) +$(error CONFIG_TPM=y conflicts with CONFIG_PKCS11=y; select one application API) +endif ifeq ($(WT_CONFORMANCE),1) MANIFEST_INPUT := $(PORT_DIR)/manifest-conformance.json else ifeq ($(CONFIG_VNET),y) MANIFEST_INPUT := $(PORT_DIR)/manifest-vnet.json +else ifeq ($(CONFIG_TPM),y) +MANIFEST_INPUT := $(PORT_DIR)/manifest-tpm.json +else ifeq ($(CONFIG_PKCS11),y) +MANIFEST_INPUT := $(PORT_DIR)/manifest-pkcs11.json else MANIFEST_INPUT := $(PORT_DIR)/manifest.json endif @@ -35,12 +53,22 @@ endif # Secure runtime placement. The default preserves the standalone image; # the wolfBoot handoff build relocates it to 0x0C020000. WT_SECURE_FLASH_BASE ?= 0x0C000000 -WT_SECURE_FLASH_SIZE ?= 0x00020000 +WT_SECURE_FLASH_SIZE ?= $(if $(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),0x00040000,0x00020000) WT_SECURE_IMAGE_HEADER_SIZE ?= 0 -WT_GUEST0_FLASH_BASE ?= 0x08020000 -WT_GUEST1_FLASH_BASE ?= 0x08040000 +ifeq ($(CONFIG_PKCS11),y) +WT_GUEST0_FLASH_BASE ?= 0x080A0000 +WT_GUEST1_FLASH_BASE ?= 0x080C0000 WT_GUEST0_FLASH_SIZE ?= 0x00020000 +WT_GUEST1_FLASH_SIZE ?= 0x00040000 +else +WT_GUEST0_FLASH_BASE ?= 0x080A0000 +WT_GUEST1_FLASH_BASE ?= 0x080E0000 +WT_GUEST0_FLASH_SIZE ?= 0x00040000 WT_GUEST1_FLASH_SIZE ?= 0x00020000 +endif +MANIFEST_GUEST_LAYOUT_ARGS := \ + --guest 1=$(WT_GUEST0_FLASH_BASE):$(WT_GUEST0_FLASH_SIZE) \ + --guest 2=$(WT_GUEST1_FLASH_BASE):$(WT_GUEST1_FLASH_SIZE) TARGET_CFLAGS := \ -DWT_SHARED_UART=$(WT_SHARED_UART) \ diff --git a/mk/target-versal.mk b/mk/target-versal.mk index c3e57f9dc..0bd4f35dc 100644 --- a/mk/target-versal.mk +++ b/mk/target-versal.mk @@ -14,12 +14,37 @@ PORT_HEADERS := $(wildcard $(PORT_DIR)/*.h) # The conformance PAL is shared by the AArch64 targets; WT_CONFORMANCE=1 swaps # in the manifest that also hosts Arm's test partitions. TARGET_CONF_DIR := $(ROOT)/port/common/aarch64/conformance +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +ifeq ($(WT_CONFORMANCE),1) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with WT_CONFORMANCE=1; the manifests are mutually exclusive) +endif +ifeq ($(CONFIG_VNET),y) +$(error CONFIG_TPM/CONFIG_PKCS11 conflicts with CONFIG_VNET=y; the manifests are mutually exclusive) +endif +endif +ifeq ($(CONFIG_TPM)$(CONFIG_PKCS11),yy) +$(error CONFIG_TPM=y conflicts with CONFIG_PKCS11=y; select one application API) +endif ifeq ($(WT_CONFORMANCE),1) MANIFEST_INPUT := $(PORT_DIR)/manifest-conformance.json +else ifeq ($(CONFIG_TPM),y) +MANIFEST_INPUT := $(BUILD_DIR)/manifest-tpm.json +else ifeq ($(CONFIG_PKCS11),y) +MANIFEST_INPUT := $(BUILD_DIR)/manifest-pkcs11.json else MANIFEST_INPUT := $(PORT_DIR)/manifest.json endif +ifneq ($(filter y,$(CONFIG_TPM) $(CONFIG_PKCS11)),) +$(BUILD_DIR)/manifest-%.json: $(PORT_DIR)/manifest.json \ + $(ROOT)/tools/manifest/aarch64_interface.py + @mkdir -p $(@D) + python3 $(ROOT)/tools/manifest/aarch64_interface.py \ + $(PORT_DIR)/manifest.json $@ --interface $* \ + --stack-base 0x7F2A0000 --data-base 0x7F350000 \ + --data-size 0x00080000 +endif + WT_EL3_TEXT_BASE ?= 0xFFFC0000 WT_EL3_RAM_BASE ?= 0xFFFE0000 WT_EL3_RAM_SIZE ?= 0x00020000 diff --git a/port/common/aarch64/l3_layout.h b/port/common/aarch64/l3_layout.h index 00a469207..540baa5d3 100644 --- a/port/common/aarch64/l3_layout.h +++ b/port/common/aarch64/l3_layout.h @@ -90,6 +90,14 @@ #ifndef WT_SPM_SHARE_SIZE #define WT_SPM_SHARE_SIZE 0x00001000u #endif +/* CONFIG_TPM and CONFIG_PKCS11 are mutually exclusive and share this private + * provider band. The selected service is its only manifest owner. */ +#ifndef WT_SPM_INTERFACE_PA +#define WT_SPM_INTERFACE_PA (WT_L3_BAND_BASE + 0x00350000u) +#endif +#ifndef WT_SPM_INTERFACE_SIZE +#define WT_SPM_INTERFACE_SIZE 0x00080000u +#endif /* Arm FF-A ACS conformance images: SP1..SP4 load into 1 MB bands from here. */ #define WT_L3_FFA_ACS_PA (WT_L3_BAND_BASE + 0x00400000u) @@ -112,7 +120,8 @@ !WT_L3_IN_RAM_S(WT_SPM_CONFDATA_PA, WT_SPM_CONFDATA_SIZE) || \ !WT_L3_IN_RAM_S(WT_SPM_KEYSTORE_PA, WT_SPM_KEYSTORE_SIZE) || \ !WT_L3_IN_RAM_S(WT_SPM_RXTX_PA, WT_SPM_RXTX_SIZE) || \ - !WT_L3_IN_RAM_S(WT_SPM_SHARE_PA, WT_SPM_SHARE_SIZE) + !WT_L3_IN_RAM_S(WT_SPM_SHARE_PA, WT_SPM_SHARE_SIZE) || \ + !WT_L3_IN_RAM_S(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE) #error "a level 3 band lies outside the port's Secure RAM" #endif #if defined(WT_SPM_TABLE_POOL_PAGES) && \ @@ -132,7 +141,8 @@ WT_SPM_IMAGE_SIZE | WT_SPM_RAM_PA | WT_SPM_RAM_SIZE | \ WT_SPM_CONFDATA_PA | WT_SPM_CONFDATA_SIZE | WT_SPM_KEYSTORE_PA | \ WT_SPM_KEYSTORE_SIZE | WT_SPM_RXTX_PA | WT_SPM_RXTX_SIZE | \ - WT_SPM_SHARE_PA | WT_SPM_SHARE_SIZE) & 0xFFFu) != 0u + WT_SPM_SHARE_PA | WT_SPM_SHARE_SIZE | WT_SPM_INTERFACE_PA | \ + WT_SPM_INTERFACE_SIZE) & 0xFFFu) != 0u #error "a level 3 band is not page aligned" #endif #if !WT_L3_APART(WT_SPM_BOOT_INFO_PA, 0x1000u, WT_SPM_TABLE_POOL_PA, WT_L3_POOL_SIZE) || \ @@ -166,4 +176,24 @@ #error "two level 3 bands overlap" #endif +#if !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_BOOT_INFO_PA, 0x1000u) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_TABLE_POOL_PA, WT_L3_POOL_SIZE) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_IMAGE_PA, WT_SPM_IMAGE_SIZE) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_RAM_PA, WT_SPM_RAM_SIZE) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_CONFDATA_PA, WT_SPM_CONFDATA_SIZE) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_KEYSTORE_PA, WT_SPM_KEYSTORE_SIZE) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_RXTX_PA, WT_SPM_RXTX_SIZE) || \ + !WT_L3_APART(WT_SPM_INTERFACE_PA, WT_SPM_INTERFACE_SIZE, \ + WT_SPM_SHARE_PA, WT_SPM_SHARE_SIZE) || \ + ((WT_SPM_INTERFACE_PA + WT_SPM_INTERFACE_SIZE) > WT_L3_FFA_ACS_PA) +#error "the application-interface band overlaps another Secure band" +#endif + #endif /* WOLFTRUST_PORT_AARCH64_L3_LAYOUT_H */ diff --git a/port/common/aarch64/platform_l3.c b/port/common/aarch64/platform_l3.c index b0603e6d5..2000baebf 100644 --- a/port/common/aarch64/platform_l3.c +++ b/port/common/aarch64/platform_l3.c @@ -104,6 +104,8 @@ size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, #if (defined(WT_FFM_NEGATIVE_PROBE) && (WT_FFM_NEGATIVE_PROBE == 1)) || \ (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) || \ (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) uintptr_t wt_platform_probe_address(unsigned int target) @@ -115,6 +117,12 @@ uintptr_t wt_platform_probe_address(unsigned int target) return (uintptr_t)WT_SPM_ATTEST_PA; case WT_PROBE_HSM_DATA_BAND: return (uintptr_t)WT_SPM_HSMDATA_PA; + case WT_PROBE_TPM_DATA_BAND: + case WT_PROBE_PKCS11_DATA_BAND: + return (uintptr_t)WT_SPM_INTERFACE_PA; + case WT_PROBE_TPM_FTPM_RAM: + case WT_PROBE_PKCS11_PROVIDER_RAM: + return (uintptr_t)WT_SPM_HSMDATA_PA; case WT_PROBE_SPM_PERIPHERAL: return (uintptr_t)WT_L3_SPM_PERIPHERAL_BASE; default: diff --git a/port/mimxrt700/manifest-pkcs11.json b/port/mimxrt700/manifest-pkcs11.json new file mode 100644 index 000000000..5535eeb5a --- /dev/null +++ b/port/mimxrt700/manifest-pkcs11.json @@ -0,0 +1,565 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 3, + "profile_capabilities": { + "capabilities": 127, + "max_domains": 10, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 0 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 939786240, + "stack_base": 806912000, + "stack_size": 4096, + "memory_resources": [ + { + "base": 939786240, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 806912000, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 127, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 671612928, + "stack_base": 537919488, + "stack_size": 262144, + "memory_resources": [ + { + "base": 671612928, + "size": 524288, + "attributes": 5, + "share_id": 0 + }, + { + "base": 537919488, + "size": 262144, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1074855936, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 672137216, + "stack_base": 538181632, + "stack_size": 262144, + "memory_resources": [ + { + "base": 672137216, + "size": 262144, + "attributes": 5, + "share_id": 0 + }, + { + "base": 538181632, + "size": 262144, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1074855936, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939851776, + "stack_base": 807370752, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939851776, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807370752, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807235584, + "size": 2048, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939855872, + "stack_base": 807362560, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939855872, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807362560, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807237632, + "size": 71680, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939872256, + "stack_base": 807333888, + "stack_size": 16384, + "memory_resources": [ + { + "base": 939872256, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807333888, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807227392, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939876352, + "stack_base": 807325696, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939876352, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807325696, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939880448, + "stack_base": 807317504, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939880448, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807317504, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939884544, + "stack_base": 807309312, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939884544, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807309312, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 9, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939888640, + "stack_base": 807211008, + "stack_size": 16384, + "memory_resources": [ + { + "base": 939888640, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807211008, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807161856, + "size": 49152, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_FWU", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_FWU", + "sid": 4101, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_PKCS11", + "domain_id": 9, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PKCS11", + "sid": 4105, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 7, + "max_services_per_partition": 1, + "max_dependencies_per_partition": 1, + "max_stateless_handles": 32 + } +} diff --git a/port/mimxrt700/manifest-tpm.json b/port/mimxrt700/manifest-tpm.json new file mode 100644 index 000000000..b73a6c005 --- /dev/null +++ b/port/mimxrt700/manifest-tpm.json @@ -0,0 +1,565 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 3, + "profile_capabilities": { + "capabilities": 127, + "max_domains": 10, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 0 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 939786240, + "stack_base": 806912000, + "stack_size": 4096, + "memory_resources": [ + { + "base": 939786240, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 806912000, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 127, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 671612928, + "stack_base": 537919488, + "stack_size": 262144, + "memory_resources": [ + { + "base": 671612928, + "size": 524288, + "attributes": 5, + "share_id": 0 + }, + { + "base": 537919488, + "size": 262144, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1074855936, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 672137216, + "stack_base": 538181632, + "stack_size": 262144, + "memory_resources": [ + { + "base": 672137216, + "size": 262144, + "attributes": 5, + "share_id": 0 + }, + { + "base": 538181632, + "size": 262144, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1074855936, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939851776, + "stack_base": 807370752, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939851776, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807370752, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807235584, + "size": 2048, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939855872, + "stack_base": 807362560, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939855872, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807362560, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807237632, + "size": 71680, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939872256, + "stack_base": 807333888, + "stack_size": 16384, + "memory_resources": [ + { + "base": 939872256, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807333888, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807227392, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939876352, + "stack_base": 807325696, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939876352, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807325696, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939880448, + "stack_base": 807317504, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939880448, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807317504, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939884544, + "stack_base": 807309312, + "stack_size": 8192, + "memory_resources": [ + { + "base": 939884544, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807309312, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 9, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 939888640, + "stack_base": 807211008, + "stack_size": 16384, + "memory_resources": [ + { + "base": 939888640, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 807211008, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 807161856, + "size": 49152, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_FWU", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_FWU", + "sid": 4101, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_TPM", + "domain_id": 9, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_TPM", + "sid": 4104, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 7, + "max_services_per_partition": 1, + "max_dependencies_per_partition": 1, + "max_stateless_handles": 32 + } +} diff --git a/port/mimxrt700/memory_map.h b/port/mimxrt700/memory_map.h index d812fccc7..36dd0ec4d 100644 --- a/port/mimxrt700/memory_map.h +++ b/port/mimxrt700/memory_map.h @@ -133,8 +133,23 @@ #define WT_SP_VNET_STACK_BASE (WT_RAM_S_BASE + 0x0006B000u) /* 0x301F3000 */ #define WT_SP_VNET_STACK_SIZE WT_SP_SECURE_STACK_SIZE -#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) /* 0x301D0000 */ +#if defined(CONFIG_TPM) || defined(CONFIG_PKCS11) +#define WT_VNET_DATA_BASE 0x301C5000u +#define WT_VNET_DATA_SIZE 0x00010000u +#else +#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) #define WT_VNET_DATA_SIZE 0x00005000u +#endif + +/* Interface state and stacks share the auxiliary window, with disjoint MPU grants. */ +#define WT_PKCS11_DATA_BASE (WT_KEYSTORE_BASE - 0x00010000u) +#define WT_PKCS11_DATA_SIZE 0x0000C000u +#define WT_SP_PKCS11_STACK_BASE (WT_PKCS11_DATA_BASE + WT_PKCS11_DATA_SIZE) +#define WT_SP_PKCS11_STACK_SIZE 0x00004000u +#define WT_TPM_DATA_BASE WT_PKCS11_DATA_BASE +#define WT_TPM_DATA_SIZE 0x0000C000u +#define WT_SP_TPM_STACK_BASE (WT_TPM_DATA_BASE + WT_TPM_DATA_SIZE) +#define WT_SP_TPM_STACK_SIZE 0x00004000u /* wolfBoot update partition on XSPI0 (Secure alias). */ #define WT_FWU_UPDATE_FLASH_BASE_S 0x38180000u diff --git a/port/mimxrt700/platform_mimxrt700.c b/port/mimxrt700/platform_mimxrt700.c index df93a1347..b911c993a 100644 --- a/port/mimxrt700/platform_mimxrt700.c +++ b/port/mimxrt700/platform_mimxrt700.c @@ -148,7 +148,7 @@ int wt_platform_priv_stack_ok(const void *stack, size_t size) /* Only a coroutine that stays privileged reaches here, so require the stack * wholly inside SPM-private RAM: the secure SRAM below the lowest * partition-writable band (WT-FFM-0011). */ -#if defined(CONFIG_VNET) +#if defined(CONFIG_VNET) || defined(CONFIG_PKCS11) || defined(CONFIG_TPM) uintptr_t priv_end = WT_VNET_DATA_BASE; #else uintptr_t priv_end = WT_KEYSTORE_BASE; @@ -375,7 +375,7 @@ size_t wt_platform_spm_private_regions(wt_memory_region_t* regions, if (max < 2u) { return 0u; } -#if defined(CONFIG_VNET) +#if defined(CONFIG_VNET) || defined(CONFIG_PKCS11) || defined(CONFIG_TPM) first_band = WT_VNET_DATA_BASE; #endif regions[0].base = WT_RAM_S_BASE; @@ -478,6 +478,8 @@ size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) || \ (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) uintptr_t wt_platform_probe_address(unsigned int target) { @@ -493,6 +495,18 @@ uintptr_t wt_platform_probe_address(unsigned int target) #if defined(CONFIG_VNET) case WT_PROBE_VNET_DATA_BAND: return (uintptr_t)WT_VNET_DATA_BASE; +#endif +#if defined(CONFIG_TPM) + case WT_PROBE_TPM_DATA_BAND: + return (uintptr_t)WT_TPM_DATA_BASE; + case WT_PROBE_TPM_FTPM_RAM: + return (uintptr_t)WT_GUEST1_RAM_BASE; +#endif +#if defined(CONFIG_PKCS11) + case WT_PROBE_PKCS11_DATA_BAND: + return (uintptr_t)WT_PKCS11_DATA_BASE; + case WT_PROBE_PKCS11_PROVIDER_RAM: + return (uintptr_t)WT_GUEST1_RAM_BASE; #endif default: return (uintptr_t)WT_RAM_S_BASE; diff --git a/port/mimxrt700/secure.ld b/port/mimxrt700/secure.ld index 8ed53b163..b6398a2fe 100644 --- a/port/mimxrt700/secure.ld +++ b/port/mimxrt700/secure.ld @@ -62,6 +62,11 @@ MEMORY { * the first 8 KiB as the SERVICE_VNET partition stack (WT_SP_VNET_STACK_*, * manifest-vnet.json domain) - the two builds are mutually exclusive. */ CONFDATA (rw): ORIGIN = 0x301F3000, LENGTH = 12K + /* SERVICE_TPM mailbox band (WT-FFM-0011 Level 3): the one in-flight TPM + * command and response slot the confined TPM relay owns, carved from the + * CONFDATA window tail only in CONFIG_TPM builds. Mirrors WT_TPM_DATA_ in + * memory_map.h. */ + TPMDATA (rw): ORIGIN = 0x301F5000, LENGTH = 2K /* Secure per-partition stacks (WT-FFM-0011). The top 40 KiB of the secure * RAM window, reserved so each Secure Partition runs on its own secure * stack the MPU can confine. Mirrors WT_SP_SECURE_RAM_* in memory_map.h. */ @@ -81,6 +86,14 @@ _wt_part_stacks_base = ORIGIN(FWUSTACK); _wt_part_stacks_limit = ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK); _wt_sp_stacks_base = ORIGIN(SPSTACKS); _wt_sp_stacks_limit = ORIGIN(SPSTACKS) + LENGTH(SPSTACKS); +_wt_tpm_stack_base = ORIGIN(VNETDATA) + + ((SIZEOF(.tpm_data) + SIZEOF(.tpm_bss)) != 0 ? 48K : 0); +_wt_tpm_stack_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.tpm_data) + SIZEOF(.tpm_bss)) != 0 ? 64K : 0); +_wt_pkcs11_stack_base = ORIGIN(VNETDATA) + + ((SIZEOF(.pkcs11_data) + SIZEOF(.pkcs11_bss)) != 0 ? 48K : 0); +_wt_pkcs11_stack_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.pkcs11_data) + SIZEOF(.pkcs11_bss)) != 0 ? 64K : 0); _sidata = LOADADDR(.data); SECTIONS { @@ -269,6 +282,53 @@ SECTIONS { _e_vnet = .; } > VNETDATA + .pkcs11_data : { + . = ALIGN(32); + _s_pkcs11 = .; + *wt_sec_pkcs11*.o(.data .data.*) + *p11_sec_*.o(.data .data.*) + *p11_wc_*.o(.data .data.*) + *p11_wh_*.o(.data .data.*) + . = ALIGN(4); + _e_pkcs11_data = .; + } > VNETDATA AT > FLASH + _si_pkcs11 = LOADADDR(.pkcs11_data); + + .pkcs11_bss (NOLOAD) : { + _s_pkcs11_bss = .; + *wt_sec_pkcs11*.o(.bss .bss.* COMMON) + *p11_sec_*.o(.bss .bss.* COMMON) + *p11_wc_*.o(.bss .bss.* COMMON) + *p11_wh_*.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_pkcs11 = .; + } > VNETDATA + + /* SERVICE_TPM relay data and bss pulled out of shared SPM .data/.bss so its + * MPU grant covers exactly its own state (isolation level 3). Empty outside + * CONFIG_TPM builds. */ + .tpm_data : { + . = ALIGN(32); + _s_tpm = .; + *wt_sec_tpm*.o(.data .data.*) + *tpm_sec_*.o(.data .data.*) + *tpm_wc_*.o(.data .data.*) + *tpm_wh_*.o(.data .data.*) + . = ALIGN(4); + _e_tpm_data = .; + } > VNETDATA AT > FLASH + _si_tpm = LOADADDR(.tpm_data); + + .tpm_bss (NOLOAD) : { + _s_tpm_bss = .; + *wt_sec_tpm*.o(.bss .bss.* COMMON) + *tpm_sec_*.o(.bss .bss.* COMMON) + *tpm_wc_*.o(.bss .bss.* COMMON) + *tpm_wh_*.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_tpm = .; + } > VNETDATA + /* Linker-synthesized SG veneers (--cmse-implib) open the RAM code band: the * first 1 KiB is the SAU NSC window (WT_NSC_* in memory_map.h), padded so * nothing but veneers is Non-secure callable. The dot assignment keeps the @@ -329,6 +389,8 @@ SECTIONS { "vault data band must sit directly above the vnet data band") ASSERT(_e_vnet <= (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), "vnet data band overflow") + ASSERT(_e_pkcs11 <= (ORIGIN(VNETDATA) + 48K), + "pkcs11 data band overflow") ASSERT(ORIGIN(ATTESTDATA) == (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), "attestation data band must sit directly above the vault data band") ASSERT(ORIGIN(HSMDATA) == (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), @@ -345,6 +407,12 @@ SECTIONS { _wt_band_hsm_limit = ORIGIN(HSMDATA) + LENGTH(HSMDATA); _wt_band_vnet_base = ORIGIN(VNETDATA); _wt_band_vnet_limit = ORIGIN(VNETDATA) + LENGTH(VNETDATA); + _wt_band_pkcs11_base = ORIGIN(VNETDATA); + _wt_band_pkcs11_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.pkcs11_data) + SIZEOF(.pkcs11_bss)) != 0 ? 48K : 0); + _wt_band_tpm_base = ORIGIN(VNETDATA); + _wt_band_tpm_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.tpm_data) + SIZEOF(.tpm_bss)) != 0 ? 48K : 0); ASSERT(_e_vault <= (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), "vault data band overflow") diff --git a/port/stm32h563/manifest-pkcs11.json b/port/stm32h563/manifest-pkcs11.json new file mode 100644 index 000000000..01582c231 --- /dev/null +++ b/port/stm32h563/manifest-pkcs11.json @@ -0,0 +1,565 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 3, + "profile_capabilities": { + "capabilities": 127, + "max_domains": 10, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 0 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 201326592, + "stack_base": 805470208, + "stack_size": 4096, + "memory_resources": [ + { + "base": 201326592, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805470208, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 127, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 134873088, + "stack_base": 536870912, + "stack_size": 65536, + "memory_resources": [ + { + "base": 134873088, + "size": 131072, + "attributes": 5, + "share_id": 0 + }, + { + "base": 536870912, + "size": 65536, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1073760256, + "size": 1024, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 135004160, + "stack_base": 536936448, + "stack_size": 65536, + "memory_resources": [ + { + "base": 135004160, + "size": 262144, + "attributes": 5, + "share_id": 0 + }, + { + "base": 536936448, + "size": 65536, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1073760256, + "size": 1024, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201392128, + "stack_base": 805928960, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201392128, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805928960, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805793792, + "size": 2048, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201396224, + "stack_base": 805920768, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201396224, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805920768, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805795840, + "size": 71680, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201412608, + "stack_base": 805892096, + "stack_size": 16384, + "memory_resources": [ + { + "base": 201412608, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805892096, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805785600, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201416704, + "stack_base": 805883904, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201416704, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805883904, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201420800, + "stack_base": 805875712, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201420800, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805875712, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201424896, + "stack_base": 805867520, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201424896, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805867520, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 9, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201428992, + "stack_base": 805769216, + "stack_size": 16384, + "memory_resources": [ + { + "base": 201428992, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805769216, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805720064, + "size": 49152, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_FWU", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_FWU", + "sid": 4101, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_PKCS11", + "domain_id": 9, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PKCS11", + "sid": 4105, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 7, + "max_services_per_partition": 1, + "max_dependencies_per_partition": 1, + "max_stateless_handles": 32 + } +} diff --git a/port/stm32h563/manifest-tpm.json b/port/stm32h563/manifest-tpm.json new file mode 100644 index 000000000..026df8b3c --- /dev/null +++ b/port/stm32h563/manifest-tpm.json @@ -0,0 +1,565 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 3, + "profile_capabilities": { + "capabilities": 127, + "max_domains": 10, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 0 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 201326592, + "stack_base": 805470208, + "stack_size": 4096, + "memory_resources": [ + { + "base": 201326592, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805470208, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 127, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 134873088, + "stack_base": 536870912, + "stack_size": 65536, + "memory_resources": [ + { + "base": 134873088, + "size": 262144, + "attributes": 5, + "share_id": 0 + }, + { + "base": 536870912, + "size": 65536, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1073760256, + "size": 1024, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 135135232, + "stack_base": 536936448, + "stack_size": 65536, + "memory_resources": [ + { + "base": 135135232, + "size": 131072, + "attributes": 5, + "share_id": 0 + }, + { + "base": 536936448, + "size": 65536, + "attributes": 19, + "share_id": 0 + }, + { + "base": 1073760256, + "size": 1024, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201392128, + "stack_base": 805928960, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201392128, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805928960, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805793792, + "size": 2048, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201396224, + "stack_base": 805920768, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201396224, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805920768, + "size": 8192, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805795840, + "size": 71680, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201412608, + "stack_base": 805892096, + "stack_size": 16384, + "memory_resources": [ + { + "base": 201412608, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805892096, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805785600, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201416704, + "stack_base": 805883904, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201416704, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805883904, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201420800, + "stack_base": 805875712, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201420800, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805875712, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201424896, + "stack_base": 805867520, + "stack_size": 8192, + "memory_resources": [ + { + "base": 201424896, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805867520, + "size": 8192, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 9, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 201428992, + "stack_base": 805769216, + "stack_size": 16384, + "memory_resources": [ + { + "base": 201428992, + "size": 4096, + "attributes": 5, + "share_id": 0 + }, + { + "base": 805769216, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 805720064, + "size": 49152, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_FWU", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_FWU", + "sid": 4101, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_TPM", + "domain_id": 9, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_TPM", + "sid": 4104, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 7, + "max_services_per_partition": 1, + "max_dependencies_per_partition": 1, + "max_stateless_handles": 32 + } +} diff --git a/port/stm32h563/memory_map.h b/port/stm32h563/memory_map.h index edbe2a8b8..e733b10fe 100644 --- a/port/stm32h563/memory_map.h +++ b/port/stm32h563/memory_map.h @@ -41,10 +41,10 @@ #define WT_FLASH_TO_S_ALIAS(address) \ ((address) - WT_FLASH_NS_BASE + WT_FLASH_S_ALIAS_BASE) #ifndef WT_GUEST0_FLASH_BASE -#define WT_GUEST0_FLASH_BASE 0x08020000u +#define WT_GUEST0_FLASH_BASE 0x080A0000u #endif #ifndef WT_GUEST1_FLASH_BASE -#define WT_GUEST1_FLASH_BASE 0x08040000u +#define WT_GUEST1_FLASH_BASE 0x080E0000u #endif #ifndef WT_GUEST_FLASH_SIZE #define WT_GUEST_FLASH_SIZE 0x00020000u @@ -52,7 +52,7 @@ /* Per-guest window sizes; guest0 can grow past guest1's without moving it * across the bank-1/bank-2 watermark boundary. */ #ifndef WT_GUEST0_FLASH_SIZE -#define WT_GUEST0_FLASH_SIZE WT_GUEST_FLASH_SIZE +#define WT_GUEST0_FLASH_SIZE 0x00040000u #endif #ifndef WT_GUEST1_FLASH_SIZE #define WT_GUEST1_FLASH_SIZE WT_GUEST_FLASH_SIZE @@ -184,8 +184,23 @@ * so the unprivileged coroutine reaches only its own state. MUST match the * VNETDATA region in src/services/wolfhsm/runner/secure.ld and the * manifest-vnet.json domain resource. */ -#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) /* 0x30070000 */ -#define WT_VNET_DATA_SIZE 0x00005000u /* 20 KiB */ +#if defined(CONFIG_TPM) || defined(CONFIG_PKCS11) +#define WT_VNET_DATA_BASE 0x30065000u +#define WT_VNET_DATA_SIZE 0x00010000u +#else +#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) +#define WT_VNET_DATA_SIZE 0x00005000u +#endif + +/* Interface state and stacks share the auxiliary window, with disjoint MPU grants. */ +#define WT_PKCS11_DATA_BASE (WT_KEYSTORE_BASE - 0x00010000u) +#define WT_PKCS11_DATA_SIZE 0x0000C000u +#define WT_SP_PKCS11_STACK_BASE (WT_PKCS11_DATA_BASE + WT_PKCS11_DATA_SIZE) +#define WT_SP_PKCS11_STACK_SIZE 0x00004000u +#define WT_TPM_DATA_BASE WT_PKCS11_DATA_BASE +#define WT_TPM_DATA_SIZE 0x0000C000u +#define WT_SP_TPM_STACK_BASE (WT_TPM_DATA_BASE + WT_TPM_DATA_SIZE) +#define WT_SP_TPM_STACK_SIZE 0x00004000u /* wolfBoot update partition (WOLFBOOT_PARTITION_UPDATE_ADDRESS): the secure * flash window SERVICE_FWU stages a candidate image into (WT-FWU-0002). Secure diff --git a/port/stm32h563/platform_stm32h563.c b/port/stm32h563/platform_stm32h563.c index 27643aade..c3dbda3d7 100644 --- a/port/stm32h563/platform_stm32h563.c +++ b/port/stm32h563/platform_stm32h563.c @@ -528,7 +528,7 @@ size_t wt_platform_spm_private_regions(wt_memory_region_t* regions, if (max < 1u) { return 0u; } -#if defined(CONFIG_VNET) +#if defined(CONFIG_VNET) || defined(CONFIG_PKCS11) || defined(CONFIG_TPM) first_band = WT_VNET_DATA_BASE; #endif regions[0].base = WT_RAM_S_BASE; @@ -623,6 +623,8 @@ size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) || \ (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) uintptr_t wt_platform_probe_address(unsigned int target) { @@ -638,6 +640,18 @@ uintptr_t wt_platform_probe_address(unsigned int target) #if defined(CONFIG_VNET) case WT_PROBE_VNET_DATA_BAND: return (uintptr_t)WT_VNET_DATA_BASE; +#endif +#if defined(CONFIG_TPM) + case WT_PROBE_TPM_DATA_BAND: + return (uintptr_t)WT_TPM_DATA_BASE; + case WT_PROBE_TPM_FTPM_RAM: + return (uintptr_t)WT_GUEST1_RAM_BASE; +#endif +#if defined(CONFIG_PKCS11) + case WT_PROBE_PKCS11_DATA_BAND: + return (uintptr_t)WT_PKCS11_DATA_BASE; + case WT_PROBE_PKCS11_PROVIDER_RAM: + return (uintptr_t)WT_GUEST1_RAM_BASE; #endif default: return (uintptr_t)WT_RAM_S_BASE; @@ -650,7 +664,7 @@ int wt_platform_priv_stack_ok(const void *stack, size_t size) /* Only a coroutine that stays privileged reaches here, so require the stack * wholly inside SPM-private RAM: the secure SRAM below the lowest * partition-writable band (WT-FFM-0011). */ -#if defined(CONFIG_VNET) +#if defined(CONFIG_VNET) || defined(CONFIG_PKCS11) || defined(CONFIG_TPM) uintptr_t priv_end = WT_VNET_DATA_BASE; #else uintptr_t priv_end = WT_KEYSTORE_BASE; diff --git a/src/arch/aarch64/spm/coroutine_aarch64.c b/src/arch/aarch64/spm/coroutine_aarch64.c index e5575e07f..857075059 100644 --- a/src/arch/aarch64/spm/coroutine_aarch64.c +++ b/src/arch/aarch64/spm/coroutine_aarch64.c @@ -38,6 +38,10 @@ #include "wolftrust/arch.h" #include "wolftrust/platform.h" #include "wolftrust/services/hsm.h" +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +#include "wolftrust/provider_crypto.h" +#endif #include "wolftrust/spm_transport.h" #include @@ -391,6 +395,14 @@ void wt_spm_init_partitions(void) if (wt_hsm_guest_init_relay((wt_guest_id_t)0) != 0) { wt_platform_panic(); } +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) + if (!wt_hsm_guest_ready((wt_guest_id_t)WT_PROVIDER_HSM_NAMESPACE) && + wt_hsm_guest_init_relay( + (wt_guest_id_t)WT_PROVIDER_HSM_NAMESPACE) != 0) { + wt_platform_panic(); + } +#endif #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) /* The core's boot-time attestation bootstrap found no seated guest; run * it now. A failure leaves attestation failing closed, as at boot. */ diff --git a/src/arch/aarch64/spm/platform_arch.c b/src/arch/aarch64/spm/platform_arch.c index 8db1ceac8..b3dbcc994 100644 --- a/src/arch/aarch64/spm/platform_arch.c +++ b/src/arch/aarch64/spm/platform_arch.c @@ -129,6 +129,9 @@ void wt_arch_zero_guest_memory(uintptr_t base, size_t size) extern uint8_t _si_vault[], _s_vault[], _e_vault_data[]; extern uint8_t _si_attest[], _s_attest[], _e_attest_data[]; extern uint8_t _si_hsm[], _s_hsm[], _e_hsm_data[]; +#if defined(CONFIG_TPM) || defined(CONFIG_PKCS11) +extern uint8_t _si_interface[], _s_interface[], _e_interface_data[]; +#endif /* A keystore band returns to its link-time image: zeroed, then its .data * reloaded; any other band has no load image and only zeroes. */ @@ -138,6 +141,9 @@ void wt_arch_sp_band_reset(uintptr_t base, size_t size) { _si_vault, _s_vault, _e_vault_data }, { _si_attest, _s_attest, _e_attest_data }, { _si_hsm, _s_hsm, _e_hsm_data }, +#if defined(CONFIG_TPM) || defined(CONFIG_PKCS11) + { _si_interface, _s_interface, _e_interface_data }, +#endif }; size_t i; diff --git a/src/arch/aarch64/spm/wolftrust.ld b/src/arch/aarch64/spm/wolftrust.ld index bce7bafc3..d2a567b23 100644 --- a/src/arch/aarch64/spm/wolftrust.ld +++ b/src/arch/aarch64/spm/wolftrust.ld @@ -16,6 +16,7 @@ MEMORY ATTESTDATA (rw) : ORIGIN = WT_SPM_ATTEST_PA, LENGTH = WT_SPM_ATTEST_SIZE HSMDATA (rw) : ORIGIN = WT_SPM_HSMDATA_PA, LENGTH = WT_SPM_HSMDATA_SIZE CONFDATA (rw) : ORIGIN = WT_SPM_CONFDATA_PA, LENGTH = WT_SPM_CONFDATA_SIZE + INTERFACEDATA (rw) : ORIGIN = WT_SPM_INTERFACE_PA, LENGTH = WT_SPM_INTERFACE_SIZE } SECTIONS @@ -31,6 +32,7 @@ SECTIONS /* wolfCrypt globals that are only ever read (the AES prefetch anchor, * the DRBG selection flag): read-only, so every partition sees them. */ *(.data.always_prefetch* .bss.always_prefetch*) + *(.data.never_prefetch* .bss.never_prefetch*) *(.data.sha256DrbgDisabled* .bss.sha256DrbgDisabled*) *(.rodata .rodata.*) . = ALIGN(4096); @@ -88,6 +90,41 @@ SECTIONS . = ALIGN(16); _e_attest = .; } > ATTESTDATA + .interface_data : { + _s_interface = .; + *tpm_sec_*.o(.data .data.*) + *tpm_wc_*.o(.data .data.*) + *tpm_wh_*.o(.data .data.*) + *p11_sec_*.o(.data .data.*) + *p11_wc_*.o(.data .data.*) + *p11_wh_*.o(.data .data.*) + *wt_sec_tpm_local_provider.o(.data .data.*) + *wt_sec_pkcs11_secure_*.o(.data .data.*) + *wt_sec_pkcs11_provider.o(.data .data.*) + *wt_sec_pkcs11_wire.o(.data .data.*) + *wt_sec_tpm_relay_service.o(.data .data.*) + *wt_sec_pkcs11_relay_service.o(.data .data.*) + . = ALIGN(16); + _e_interface_data = .; + } > INTERFACEDATA AT > IMAGE + _si_interface = LOADADDR(.interface_data); + .interface_bss (NOLOAD) : { + _s_interface_bss = .; + *tpm_sec_*.o(.bss .bss.* COMMON) + *tpm_wc_*.o(.bss .bss.* COMMON) + *tpm_wh_*.o(.bss .bss.* COMMON) + *p11_sec_*.o(.bss .bss.* COMMON) + *p11_wc_*.o(.bss .bss.* COMMON) + *p11_wh_*.o(.bss .bss.* COMMON) + *wt_sec_tpm_local_provider.o(.bss .bss.* COMMON) + *wt_sec_pkcs11_secure_*.o(.bss .bss.* COMMON) + *wt_sec_pkcs11_provider.o(.bss .bss.* COMMON) + *wt_sec_pkcs11_wire.o(.bss .bss.* COMMON) + *wt_sec_tpm_relay_service.o(.bss .bss.* COMMON) + *wt_sec_pkcs11_relay_service.o(.bss .bss.* COMMON) + . = ALIGN(16); + _e_interface = .; + } > INTERFACEDATA .hsm_data : { _s_hsm = .; *wt_hsm.o(.data .data.*) @@ -179,7 +216,7 @@ SECTIONS . = ALIGN(4096); __spm_stack_guard = .; . += 4096; - . += 0x4000; + . += WT_SPM_STACK_SIZE; __spm_stack_top = .; } > RAM /* Outside the bss-clear range and NOLOAD, so the conformance NVM shadow @@ -204,6 +241,7 @@ ASSERT(__image_end <= WT_SPM_IMAGE_PA + WT_SPM_IMAGE_SIZE, "SPM image band overf ASSERT(_e_keystore <= WT_SPM_KEYSTORE_PA + WT_SPM_KEYSTORE_SIZE, "keystore band overflow") ASSERT(WT_SPM_HSMDATA_PA + WT_SPM_HSMDATA_SIZE == WT_SPM_KEYSTORE_PA + WT_SPM_KEYSTORE_SIZE, "the keystore bands must tile the keystore window") ASSERT(_e_conf_data <= WT_SPM_CONFDATA_PA + WT_SPM_CONFDATA_SIZE, "conformance data band overflow") +ASSERT(_e_interface <= WT_SPM_INTERFACE_PA + WT_SPM_INTERFACE_SIZE, "interface data band overflow") ASSERT(_e_conf_bss <= WT_SPM_CONFDATA_PA + 0x1C000, "conformance data reaches the pseudo-MMIO pages") ASSERT((_s_conf_data & 0xFFF) == 0, "conformance partition segments must be page aligned") ASSERT((_e_conf_unowned == _s_conf_unowned) && (_e_conf_bss == _s_conf_bss), diff --git a/src/arch/armv8m/start_armv8m.c b/src/arch/armv8m/start_armv8m.c index 41a73980a..0916f64b1 100644 --- a/src/arch/armv8m/start_armv8m.c +++ b/src/arch/armv8m/start_armv8m.c @@ -63,11 +63,33 @@ extern uint32_t _e_vnet_data; extern uint32_t _s_vnet_bss; extern uint32_t _e_vnet; #endif +#if defined(CONFIG_TPM) +extern uint32_t _si_tpm; +extern uint32_t _s_tpm; +extern uint32_t _e_tpm_data; +extern uint32_t _s_tpm_bss; +extern uint32_t _e_tpm; +#endif +#if defined(CONFIG_PKCS11) +extern uint32_t _si_pkcs11; +extern uint32_t _s_pkcs11; +extern uint32_t _e_pkcs11_data; +extern uint32_t _s_pkcs11_bss; +extern uint32_t _e_pkcs11; +#endif extern uint32_t _wt_part_stacks_base; extern uint32_t _wt_part_stacks_limit; extern uint32_t _wt_sp_stacks_base; extern uint32_t _wt_sp_stacks_limit; +#if defined(CONFIG_TPM) +extern uint32_t _wt_tpm_stack_base; +extern uint32_t _wt_tpm_stack_limit; +#endif +#if defined(CONFIG_PKCS11) +extern uint32_t _wt_pkcs11_stack_base; +extern uint32_t _wt_pkcs11_stack_limit; +#endif /* Bytes of partition band and stack RAM zeroed at reset, read over SWD. */ volatile uint32_t g_wt_boot_zeroed_bytes; @@ -82,6 +104,14 @@ extern uint32_t _wt_band_hsm_limit; extern uint32_t _wt_band_vnet_base; extern uint32_t _wt_band_vnet_limit; #endif +#if defined(CONFIG_TPM) +extern uint32_t _wt_band_tpm_base; +extern uint32_t _wt_band_tpm_limit; +#endif +#if defined(CONFIG_PKCS11) +extern uint32_t _wt_band_pkcs11_base; +extern uint32_t _wt_band_pkcs11_limit; +#endif /* One partition data band: where its .data loads from, where its .data and * .bss live, and the full MPU-granted extent around them. */ @@ -106,6 +136,14 @@ static const wt_band_image_t g_wt_bands[] = { { &_si_vnet, &_s_vnet, &_e_vnet_data, &_s_vnet_bss, &_e_vnet, &_wt_band_vnet_base, &_wt_band_vnet_limit }, #endif +#if defined(CONFIG_TPM) + { &_si_tpm, &_s_tpm, &_e_tpm_data, &_s_tpm_bss, &_e_tpm, + &_wt_band_tpm_base, &_wt_band_tpm_limit }, +#endif +#if defined(CONFIG_PKCS11) + { &_si_pkcs11, &_s_pkcs11, &_e_pkcs11_data, &_s_pkcs11_bss, + &_e_pkcs11, &_wt_band_pkcs11_base, &_wt_band_pkcs11_limit }, +#endif }; static void wt_band_load(const wt_band_image_t* band) @@ -204,10 +242,29 @@ static void wt_reset_main(void) for (dst = &_wt_sp_stacks_base; dst < &_wt_sp_stacks_limit; ++dst) { *dst = 0u; } +#if defined(CONFIG_TPM) + for (dst = &_wt_tpm_stack_base; dst < &_wt_tpm_stack_limit; ++dst) { + *dst = 0u; + } +#endif +#if defined(CONFIG_PKCS11) + for (dst = &_wt_pkcs11_stack_base; + dst < &_wt_pkcs11_stack_limit; ++dst) { + *dst = 0u; + } +#endif zeroed += (uint32_t)((uintptr_t)&_wt_part_stacks_limit - (uintptr_t)&_wt_part_stacks_base); zeroed += (uint32_t)((uintptr_t)&_wt_sp_stacks_limit - (uintptr_t)&_wt_sp_stacks_base); +#if defined(CONFIG_TPM) + zeroed += (uint32_t)((uintptr_t)&_wt_tpm_stack_limit - + (uintptr_t)&_wt_tpm_stack_base); +#endif +#if defined(CONFIG_PKCS11) + zeroed += (uint32_t)((uintptr_t)&_wt_pkcs11_stack_limit - + (uintptr_t)&_wt_pkcs11_stack_base); +#endif g_wt_boot_zeroed_bytes = zeroed; #if defined(WT_SEAL_NEG_PROBE) && (WT_SEAL_NEG_PROBE == 3) diff --git a/src/arch/common/spm_gate_core.c b/src/arch/common/spm_gate_core.c index f5ecf0211..a229b8a48 100644 --- a/src/arch/common/spm_gate_core.c +++ b/src/arch/common/spm_gate_core.c @@ -43,6 +43,10 @@ #include "wolftrust/services/fwu_service.h" #include "wolftrust/services/hsm.h" #include "wolftrust/services/hsm_relay.h" +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +#include "wolftrust/provider_crypto.h" +#endif #include "wolftrust/services/crypto_native.h" #include "wolftrust/services/nvm_client.h" #include "wolftrust/sync/mutex.h" @@ -204,6 +208,12 @@ static void wt_spm_fault_release(void* ctx) wt_initial_attest_hsm_restarted(); } #endif +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) + if (c->slot->partition_id == g_spm_hsm_partition_id) { + wt_provider_crypto_hsm_restarted(); + } +#endif } static void wt_spm_fault_messages(void* ctx) @@ -276,7 +286,9 @@ static int wt_spm_fault_restart(void* ctx) arg = (void*)((intptr_t)slot->arg | WT_SP_FAULT_PROBE_RESTARTED); #endif #if (defined(WT_VNET_NEG_PROBE) && (WT_VNET_NEG_PROBE == 1)) || \ - (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) + (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) /* Two-stage probe progression persists in slot->arg: fault 1 arms * RESTARTED, fault 2 arms SECOND, the third run serves normally. */ if (((intptr_t)slot->arg & WT_SP_FAULT_PROBE_RESTARTED) != 0) { @@ -594,6 +606,16 @@ static int wt_spm_dispatch_held(wt_spm_sp_t* slot, wt_spm_call_t* call, pinned = wt_spm_keystore_op_pinned(call->op, slot->partition_id, PARTITION_VAULT_ID, PARTITION_HSM_ID); +#if defined(WT_INTERFACE_LOCAL_PROVIDER) + if (!pinned && call->op == WT_SPM_OP_KEYSTORE_ENTROPY) { +#ifdef PARTITION_TPM_ID + pinned = slot->partition_id == PARTITION_TPM_ID; +#endif +#ifdef PARTITION_PKCS11_ID + pinned = slot->partition_id == PARTITION_PKCS11_ID; +#endif + } +#endif if (!pinned) { g_wt_ks_reject_pid = (uint32_t)slot->partition_id; g_wt_ks_reject_count++; @@ -1178,7 +1200,9 @@ int wt_spm_sched_add(wt_ffm_runtime_t* runtime, int32_t partition_id, } #if (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ - (defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0)) + (defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0)) || \ + (defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1)) || \ + (defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1)) /* Run the probing partition at boot until its probes have faulted and its * next entry parks in the service loop, so no client request is lost to the * deliberate faults. Never built into production images. */ diff --git a/src/client/pkcs11_proxy.c b/src/client/pkcs11_proxy.c new file mode 100644 index 000000000..19885fcca --- /dev/null +++ b/src/client/pkcs11_proxy.c @@ -0,0 +1,1333 @@ +/* pkcs11_proxy.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/pkcs11_psa_transport.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" +#include "wolftrust/zeroize.h" + +#include "wolfpkcs11/pkcs11.h" +#include "wolftrust/crypto_profile.h" + +#include +#include + +#define WT_P11_TEMPLATE_MAX 32U + +static uint8_t g_request[WT_PKCS11_MESSAGE_MAX]; +static uint8_t g_response[WT_PKCS11_MESSAGE_MAX]; + +static CK_RV proxy_finish(wt_pkcs11_reader_t* reader, CK_RV rv) +{ + wt_forceZero(g_request, sizeof(g_request)); + wt_forceZero(g_response, sizeof(g_response)); + if (reader != NULL) { + wt_pkcs11_reader_init(reader, g_response, 0U); + } + return rv; +} + +static void proxy_begin(wt_pkcs11_writer_t* writer, uint32_t function) +{ + wt_forceZero(g_request, sizeof(g_request)); + wt_forceZero(g_response, sizeof(g_response)); + wt_pkcs11_writer_init(writer, g_request, sizeof(g_request)); + wt_pkcs11_put_u32(writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(writer, WT_PKCS11_WIRE_VERSION); + wt_pkcs11_put_u32(writer, function); +} + +static CK_RV proxy_invoke(wt_pkcs11_writer_t* writer, + wt_pkcs11_reader_t* reader) +{ + size_t response_len = 0U; + uint32_t magic; + uint32_t version; + CK_RV rv; + + wt_pkcs11_reader_init(reader, g_response, 0U); + if (writer->error != 0) { + return proxy_finish(reader, CKR_ARGUMENTS_BAD); + } + wt_forceZero(g_response, sizeof(g_response)); + if (wt_pkcs11_psa_call(g_request, writer->used, g_response, + sizeof(g_response), &response_len) != 0) { + return proxy_finish(reader, CKR_DEVICE_ERROR); + } + wt_forceZero(g_request, sizeof(g_request)); + wt_pkcs11_reader_init(reader, g_response, response_len); + magic = wt_pkcs11_get_u32(reader); + version = wt_pkcs11_get_u32(reader); + rv = (CK_RV)wt_pkcs11_get_u32(reader); + if (reader->error != 0 || magic != WT_PKCS11_WIRE_MAGIC || + version != WT_PKCS11_WIRE_VERSION) { + return proxy_finish(reader, CKR_DEVICE_ERROR); + } + return rv; +} + +static CK_RV proxy_invoke_complete(wt_pkcs11_writer_t* writer, + wt_pkcs11_reader_t* reader) +{ + CK_RV rv = proxy_invoke(writer, reader); + + return proxy_finish(reader, rv); +} + +static int proxy_u32_list_valid(const wt_pkcs11_reader_t* reader, + uint32_t count) +{ + size_t remaining; + + if (reader->error != 0 || reader->used > reader->length || + count > WT_PKCS11_LIST_MAX) { + return 0; + } + remaining = reader->length - reader->used; + return remaining == (size_t)count * sizeof(uint32_t); +} + +static int proxy_put_ulong(wt_pkcs11_writer_t* writer, CK_ULONG value) +{ +#if ULONG_MAX > UINT32_MAX + if (value > UINT32_MAX) { + writer->error = 1; + return -1; + } +#endif + wt_pkcs11_put_u32(writer, (uint32_t)value); + return 0; +} + +/* wolfPKCS11 rejects some NULL buffers even at length zero, so the provider + * must see the caller's NULL, not an empty wire buffer. */ +static void proxy_put_buffer(wt_pkcs11_writer_t* writer, const void* data, + CK_ULONG length) +{ + wt_pkcs11_put_u32(writer, data != NULL ? 1U : 0U); + wt_pkcs11_put_bytes(writer, data, (size_t)length); +} + +static int proxy_put_blob(wt_pkcs11_writer_t* writer, const void* data, + CK_ULONG length) +{ + if (proxy_put_ulong(writer, length) != 0) { + return -1; + } + proxy_put_buffer(writer, data, length); + return writer->error != 0 ? -1 : 0; +} + +static int proxy_put_template(wt_pkcs11_writer_t* writer, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + int include_values) +{ + CK_ULONG i; + + if (count > WT_P11_TEMPLATE_MAX || + (attributes == NULL && count != 0U)) { + writer->error = 1; + return -1; + } + wt_pkcs11_put_u32(writer, attributes != NULL ? 1U : 0U); + (void)proxy_put_ulong(writer, count); + for (i = 0U; i < count; i++) { + CK_ULONG capacity = attributes[i].pValue != NULL ? + attributes[i].ulValueLen : 0U; + + (void)proxy_put_ulong(writer, attributes[i].type); + (void)proxy_put_ulong(writer, + include_values != 0 ? attributes[i].ulValueLen : capacity); + wt_pkcs11_put_u32(writer, attributes[i].pValue != NULL ? 1U : 0U); + if (include_values != 0) { + wt_pkcs11_put_bytes(writer, attributes[i].pValue, + (size_t)attributes[i].ulValueLen); + } + else { + wt_pkcs11_put_bytes(writer, NULL, 0U); + } + } + return writer->error != 0 ? -1 : 0; +} + +static int proxy_get_template(wt_pkcs11_reader_t* reader, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + int values_returned) +{ + CK_ULONG i; + uint32_t returned; + uint32_t actual; + uint8_t* value; + size_t value_len; + + returned = wt_pkcs11_get_u32(reader); + if (reader->error != 0 || returned != (uint32_t)count) { + return -1; + } + for (i = 0U; i < count; i++) { + actual = wt_pkcs11_get_u32(reader); + value = wt_pkcs11_get_bytes(reader, &value_len); + if (reader->error != 0) { + return -1; + } + if (values_returned == 0) { + continue; + } + if (attributes[i].pValue != NULL && actual <= attributes[i].ulValueLen) { + if (value_len != actual) { + return -1; + } + (void)memcpy(attributes[i].pValue, value, value_len); + } + else if (value_len != 0U) { + return -1; + } + attributes[i].ulValueLen = actual == WT_PKCS11_WIRE_UNAVAILABLE ? + CK_UNAVAILABLE_INFORMATION : (CK_ULONG)actual; + } + return 0; +} + +static int proxy_put_mechanism(wt_pkcs11_writer_t* writer, + CK_MECHANISM_PTR mechanism) +{ + uint32_t kind = WT_P11_MECH_RAW; + + if (mechanism == NULL) { + wt_pkcs11_put_u32(writer, 0U); + return 0; + } + wt_pkcs11_put_u32(writer, 1U); + (void)proxy_put_ulong(writer, mechanism->mechanism); + switch (mechanism->mechanism) { + case CKM_AES_CTR: + kind = WT_P11_MECH_CTR; + break; + case CKM_AES_GCM: + kind = WT_P11_MECH_GCM; + break; + case CKM_AES_CCM: + kind = WT_P11_MECH_CCM; + break; + case CKM_ECDH1_DERIVE: + kind = WT_P11_MECH_ECDH; + break; + default: + break; + } + wt_pkcs11_put_u32(writer, kind); + if (kind == WT_P11_MECH_CTR) { + CK_AES_CTR_PARAMS* params = (CK_AES_CTR_PARAMS*)mechanism->pParameter; + if (params == NULL || mechanism->ulParameterLen != sizeof(*params)) { + writer->error = 1; + } + else { + (void)proxy_put_ulong(writer, params->ulCounterBits); + wt_pkcs11_put_bytes(writer, params->cb, sizeof(params->cb)); + } + } + else if (kind == WT_P11_MECH_GCM) { + CK_GCM_PARAMS* params = (CK_GCM_PARAMS*)mechanism->pParameter; + if (params == NULL || mechanism->ulParameterLen != sizeof(*params)) { + writer->error = 1; + } + else { + proxy_put_buffer(writer, params->pIv, params->ulIvLen); + (void)proxy_put_ulong(writer, params->ulIvBits); + proxy_put_buffer(writer, params->pAAD, params->ulAADLen); + (void)proxy_put_ulong(writer, params->ulTagBits); + } + } + else if (kind == WT_P11_MECH_CCM) { + CK_CCM_PARAMS* params = (CK_CCM_PARAMS*)mechanism->pParameter; + if (params == NULL || mechanism->ulParameterLen != sizeof(*params)) { + writer->error = 1; + } + else { + (void)proxy_put_ulong(writer, params->ulDataLen); + proxy_put_buffer(writer, params->pIv, params->ulIvLen); + proxy_put_buffer(writer, params->pAAD, params->ulAADLen); + (void)proxy_put_ulong(writer, params->ulMacLen); + } + } + else if (kind == WT_P11_MECH_ECDH) { + CK_ECDH1_DERIVE_PARAMS* params = + (CK_ECDH1_DERIVE_PARAMS*)mechanism->pParameter; + if (params == NULL || mechanism->ulParameterLen != sizeof(*params)) { + writer->error = 1; + } + else { + (void)proxy_put_ulong(writer, params->kdf); + proxy_put_buffer(writer, params->pSharedData, + params->ulSharedDataLen); + proxy_put_buffer(writer, params->pPublicData, + params->ulPublicDataLen); + } + } + else { + proxy_put_buffer(writer, mechanism->pParameter, + mechanism->ulParameterLen); + } + return writer->error != 0 ? -1 : 0; +} + +static CK_RV proxy_simple(uint32_t function) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, function); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_one(uint32_t function, CK_ULONG value) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, function); + (void)proxy_put_ulong(&writer, value); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_output(uint32_t function, CK_SESSION_HANDLE session, + CK_BYTE_PTR input, CK_ULONG input_len, + CK_BYTE_PTR output, CK_ULONG_PTR output_len) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + CK_ULONG capacity; + uint32_t actual; + uint8_t* data; + size_t data_len; + + if (output_len == NULL || (input == NULL && input_len != 0U)) { + return CKR_ARGUMENTS_BAD; + } + capacity = output != NULL ? *output_len : 0U; + proxy_begin(&writer, function); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_blob(&writer, input, input_len); + wt_pkcs11_put_u32(&writer, output != NULL ? 1U : 0U); + (void)proxy_put_ulong(&writer, capacity); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + actual = wt_pkcs11_get_u32(&reader); + data = wt_pkcs11_get_bytes(&reader, &data_len); + if (reader.error != 0) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + if (rv == CKR_OK && output != NULL) { + if (actual > capacity || data_len != actual) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + (void)memcpy(output, data, data_len); + } + else if (data_len != 0U) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + *output_len = (CK_ULONG)actual; + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_final_output(uint32_t function, CK_SESSION_HANDLE session, + CK_BYTE_PTR output, CK_ULONG_PTR output_len) +{ + return proxy_output(function, session, NULL, 0U, output, output_len); +} + +static CK_RV proxy_data(uint32_t function, CK_SESSION_HANDLE session, + CK_BYTE_PTR data, CK_ULONG length) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + if (data == NULL && length != 0U) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, function); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_blob(&writer, data, length); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_mechanism_init(uint32_t function, + CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE key, int has_key) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, function); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_mechanism(&writer, mechanism); + if (has_key != 0) { + (void)proxy_put_ulong(&writer, key); + } + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_Initialize(CK_VOID_PTR args) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_C_INITIALIZE_ARGS_PTR init = (CK_C_INITIALIZE_ARGS_PTR)args; + int callbacks = 0; + + if (init != NULL) { + callbacks = (init->CreateMutex != NULL) + + (init->DestroyMutex != NULL) + + (init->LockMutex != NULL) + + (init->UnlockMutex != NULL); + if (init->pReserved != NULL || + (callbacks != 0 && callbacks != 4)) { + return CKR_ARGUMENTS_BAD; + } + if (callbacks != 0 || (init->flags & CKF_OS_LOCKING_OK) != 0U) { + return CKR_CANT_LOCK; + } + } + + proxy_begin(&writer, WT_P11_INITIALIZE); + wt_pkcs11_put_u32(&writer, 0U); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_Finalize(CK_VOID_PTR reserved) +{ + if (reserved != NULL) { + return CKR_ARGUMENTS_BAD; + } + return proxy_simple(WT_P11_FINALIZE); +} + +static CK_RV proxy_fixed_info(uint32_t function, CK_ULONG selector, + void* info, size_t info_size) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint8_t* data; + size_t length; + CK_RV rv; + + if (info == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, function); + if (selector != CK_UNAVAILABLE_INFORMATION) { + (void)proxy_put_ulong(&writer, selector); + } + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + data = wt_pkcs11_get_bytes(&reader, &length); + if (reader.error != 0 || (rv == CKR_OK && length != info_size)) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + if (rv == CKR_OK) { + (void)memcpy(info, data, length); + } + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_C_GetInfo(CK_INFO_PTR info) +{ + return proxy_fixed_info(WT_P11_GET_INFO, CK_UNAVAILABLE_INFORMATION, + info, sizeof(*info)); +} + +static CK_RV proxy_C_GetSlotList(CK_BBOOL present, CK_SLOT_ID_PTR slots, + CK_ULONG_PTR count) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_ULONG capacity; + CK_ULONG i; + uint32_t returned; + uint32_t sent; + CK_RV rv; + + if (count == NULL) { + return CKR_ARGUMENTS_BAD; + } + capacity = slots != NULL ? *count : 0U; + proxy_begin(&writer, WT_P11_GET_SLOT_LIST); + wt_pkcs11_put_u32(&writer, present); + wt_pkcs11_put_u32(&writer, slots != NULL ? 1U : 0U); + (void)proxy_put_ulong(&writer, capacity); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + returned = wt_pkcs11_get_u32(&reader); + sent = wt_pkcs11_get_u32(&reader); + if (!proxy_u32_list_valid(&reader, sent) || sent > returned || + (CK_ULONG)sent > capacity) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + for (i = 0U; i < sent; i++) { + uint32_t value = wt_pkcs11_get_u32(&reader); + if (slots != NULL && i < capacity) { + slots[i] = (CK_SLOT_ID)value; + } + } + if (reader.error != 0) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + *count = returned; + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_C_GetSlotInfo(CK_SLOT_ID slot, CK_SLOT_INFO_PTR info) +{ + return proxy_fixed_info(WT_P11_GET_SLOT_INFO, slot, info, sizeof(*info)); +} + +static CK_RV proxy_C_GetTokenInfo(CK_SLOT_ID slot, CK_TOKEN_INFO_PTR info) +{ + return proxy_fixed_info(WT_P11_GET_TOKEN_INFO, slot, info, sizeof(*info)); +} + +static CK_RV proxy_C_GetMechanismList(CK_SLOT_ID slot, + CK_MECHANISM_TYPE_PTR mechanisms, + CK_ULONG_PTR count) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_ULONG capacity; + CK_ULONG i; + uint32_t returned; + uint32_t sent; + CK_RV rv; + + if (count == NULL) { + return CKR_ARGUMENTS_BAD; + } + capacity = mechanisms != NULL ? *count : 0U; + proxy_begin(&writer, WT_P11_GET_MECHANISM_LIST); + (void)proxy_put_ulong(&writer, slot); + wt_pkcs11_put_u32(&writer, mechanisms != NULL ? 1U : 0U); + (void)proxy_put_ulong(&writer, capacity); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + returned = wt_pkcs11_get_u32(&reader); + sent = wt_pkcs11_get_u32(&reader); + if (!proxy_u32_list_valid(&reader, sent) || sent > returned || + (CK_ULONG)sent > capacity) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + for (i = 0U; i < sent; i++) { + uint32_t value = wt_pkcs11_get_u32(&reader); + if (mechanisms != NULL && i < capacity) { + mechanisms[i] = (CK_MECHANISM_TYPE)value; + } + } + if (reader.error != 0) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + *count = returned; + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_C_GetMechanismInfo(CK_SLOT_ID slot, + CK_MECHANISM_TYPE type, + CK_MECHANISM_INFO_PTR info) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + if (info == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_GET_MECHANISM_INFO); + (void)proxy_put_ulong(&writer, slot); + (void)proxy_put_ulong(&writer, type); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + info->ulMinKeySize = wt_pkcs11_get_u32(&reader); + info->ulMaxKeySize = wt_pkcs11_get_u32(&reader); + info->flags = wt_pkcs11_get_u32(&reader); + return proxy_finish(&reader, + reader.error != 0 ? CKR_DEVICE_ERROR : rv); +} + +static CK_RV proxy_C_InitToken(CK_SLOT_ID slot, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len, CK_UTF8CHAR_PTR label) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + if (label == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_INIT_TOKEN); + (void)proxy_put_ulong(&writer, slot); + (void)proxy_put_blob(&writer, pin, pin_len); + wt_pkcs11_put_bytes(&writer, label, 32U); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_InitPIN(CK_SESSION_HANDLE session, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_INIT_PIN); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_blob(&writer, pin, pin_len); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_SetPIN(CK_SESSION_HANDLE session, CK_UTF8CHAR_PTR old_pin, + CK_ULONG old_len, CK_UTF8CHAR_PTR new_pin, + CK_ULONG new_len) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_SET_PIN); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_blob(&writer, old_pin, old_len); + (void)proxy_put_blob(&writer, new_pin, new_len); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_OpenSession(CK_SLOT_ID slot, CK_FLAGS flags, + CK_VOID_PTR application, CK_NOTIFY notify, + CK_SESSION_HANDLE_PTR session) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint32_t opened; + CK_RV rv; + + /* wolfPKCS11 ignores both, so neither pointer crosses into the provider; + * a NULL session is forwarded so wolfPKCS11 orders its own checks. */ + (void)application; + (void)notify; + proxy_begin(&writer, WT_P11_OPEN_SESSION); + (void)proxy_put_ulong(&writer, slot); + (void)proxy_put_ulong(&writer, flags); + wt_pkcs11_put_u32(&writer, session != NULL ? 1U : 0U); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + opened = wt_pkcs11_get_u32(&reader); + if (reader.error != 0) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + if (session != NULL) { + *session = (CK_SESSION_HANDLE)opened; + } + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_C_CloseSession(CK_SESSION_HANDLE session) +{ + return proxy_one(WT_P11_CLOSE_SESSION, session); +} + +static CK_RV proxy_C_CloseAllSessions(CK_SLOT_ID slot) +{ + return proxy_one(WT_P11_CLOSE_ALL_SESSIONS, slot); +} + +static CK_RV proxy_C_GetSessionInfo(CK_SESSION_HANDLE session, + CK_SESSION_INFO_PTR info) +{ + return proxy_fixed_info(WT_P11_GET_SESSION_INFO, session, info, + sizeof(*info)); +} + +static CK_RV proxy_C_Login(CK_SESSION_HANDLE session, CK_USER_TYPE type, + CK_UTF8CHAR_PTR pin, CK_ULONG pin_len) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_LOGIN); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_ulong(&writer, type); + (void)proxy_put_blob(&writer, pin, pin_len); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_Logout(CK_SESSION_HANDLE session) +{ + return proxy_one(WT_P11_LOGOUT, session); +} + +static CK_RV proxy_template_object(uint32_t function, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE source, + int has_source, + CK_ATTRIBUTE_PTR attributes, + CK_ULONG count, + CK_OBJECT_HANDLE_PTR object) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + if (object == NULL || count > WT_P11_TEMPLATE_MAX || + (attributes == NULL && count != 0U)) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, function); + (void)proxy_put_ulong(&writer, session); + if (has_source != 0) { + (void)proxy_put_ulong(&writer, source); + } + (void)proxy_put_template(&writer, attributes, count, 1); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + *object = (CK_OBJECT_HANDLE)wt_pkcs11_get_u32(&reader); + return proxy_finish(&reader, + reader.error != 0 ? CKR_DEVICE_ERROR : rv); +} + +static CK_RV proxy_C_CreateObject(CK_SESSION_HANDLE session, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + CK_OBJECT_HANDLE_PTR object) +{ + return proxy_template_object(WT_P11_CREATE_OBJECT, session, 0U, 0, + attributes, count, object); +} + +static CK_RV proxy_C_CopyObject(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE source, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + CK_OBJECT_HANDLE_PTR object) +{ + return proxy_template_object(WT_P11_COPY_OBJECT, session, source, 1, + attributes, count, object); +} + +static CK_RV proxy_C_DestroyObject(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_DESTROY_OBJECT); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_ulong(&writer, object); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_GetObjectSize(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, CK_ULONG_PTR size) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + uint32_t encoded_size; + + if (size == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_GET_OBJECT_SIZE); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_ulong(&writer, object); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + encoded_size = wt_pkcs11_get_u32(&reader); + *size = encoded_size == WT_PKCS11_WIRE_UNAVAILABLE ? + CK_UNAVAILABLE_INFORMATION : (CK_ULONG)encoded_size; + return proxy_finish(&reader, + reader.error != 0 ? CKR_DEVICE_ERROR : rv); +} + +static CK_RV proxy_C_GetAttributeValue(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + CK_ATTRIBUTE_PTR attributes, + CK_ULONG count) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + proxy_begin(&writer, WT_P11_GET_ATTRIBUTE_VALUE); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_ulong(&writer, object); + (void)proxy_put_template(&writer, attributes, count, 0); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + if (proxy_get_template(&reader, attributes, count, + rv == CKR_OK || rv == CKR_ATTRIBUTE_SENSITIVE || + rv == CKR_ATTRIBUTE_TYPE_INVALID || + rv == CKR_BUFFER_TOO_SMALL) != 0) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_C_SetAttributeValue(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + CK_ATTRIBUTE_PTR attributes, + CK_ULONG count) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_SET_ATTRIBUTE_VALUE); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_ulong(&writer, object); + (void)proxy_put_template(&writer, attributes, count, 1); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_FindObjectsInit(CK_SESSION_HANDLE session, + CK_ATTRIBUTE_PTR attributes, + CK_ULONG count) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_FIND_OBJECTS_INIT); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_template(&writer, attributes, count, 1); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_FindObjects(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE_PTR objects, + CK_ULONG maximum, CK_ULONG_PTR count) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint32_t returned; + CK_ULONG i; + CK_RV rv; + + if (count == NULL || (objects == NULL && maximum != 0U)) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_FIND_OBJECTS); + (void)proxy_put_ulong(&writer, session); + wt_pkcs11_put_u32(&writer, objects != NULL ? 1U : 0U); + (void)proxy_put_ulong(&writer, maximum); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + returned = wt_pkcs11_get_u32(&reader); + if (!proxy_u32_list_valid(&reader, returned) || + (CK_ULONG)returned > maximum) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + for (i = 0U; i < returned; i++) { + uint32_t object = wt_pkcs11_get_u32(&reader); + if (i < maximum) { + objects[i] = (CK_OBJECT_HANDLE)object; + } + } + if (reader.error != 0) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + *count = returned; + return proxy_finish(&reader, rv); +} + +static CK_RV proxy_C_FindObjectsFinal(CK_SESSION_HANDLE session) +{ + return proxy_one(WT_P11_FIND_OBJECTS_FINAL, session); +} + +static CK_RV proxy_C_EncryptInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE key) +{ + return proxy_mechanism_init(WT_P11_ENCRYPT_INIT, session, mechanism, + key, 1); +} + +static CK_RV proxy_C_Encrypt(CK_SESSION_HANDLE session, CK_BYTE_PTR input, + CK_ULONG input_len, CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_output(WT_P11_ENCRYPT, session, input, input_len, output, + output_len); +} + +static CK_RV proxy_C_EncryptUpdate(CK_SESSION_HANDLE session, + CK_BYTE_PTR input, CK_ULONG input_len, + CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_output(WT_P11_ENCRYPT_UPDATE, session, input, input_len, + output, output_len); +} + +static CK_RV proxy_C_EncryptFinal(CK_SESSION_HANDLE session, + CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_final_output(WT_P11_ENCRYPT_FINAL, session, output, + output_len); +} + +static CK_RV proxy_C_DecryptInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE key) +{ + return proxy_mechanism_init(WT_P11_DECRYPT_INIT, session, mechanism, + key, 1); +} + +static CK_RV proxy_C_Decrypt(CK_SESSION_HANDLE session, CK_BYTE_PTR input, + CK_ULONG input_len, CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_output(WT_P11_DECRYPT, session, input, input_len, output, + output_len); +} + +static CK_RV proxy_C_DecryptUpdate(CK_SESSION_HANDLE session, + CK_BYTE_PTR input, CK_ULONG input_len, + CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_output(WT_P11_DECRYPT_UPDATE, session, input, input_len, + output, output_len); +} + +static CK_RV proxy_C_DecryptFinal(CK_SESSION_HANDLE session, + CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_final_output(WT_P11_DECRYPT_FINAL, session, output, + output_len); +} + +static CK_RV proxy_C_DigestInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism) +{ + return proxy_mechanism_init(WT_P11_DIGEST_INIT, session, mechanism, 0U, 0); +} + +static CK_RV proxy_C_Digest(CK_SESSION_HANDLE session, CK_BYTE_PTR input, + CK_ULONG input_len, CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_output(WT_P11_DIGEST, session, input, input_len, output, + output_len); +} + +static CK_RV proxy_C_DigestUpdate(CK_SESSION_HANDLE session, + CK_BYTE_PTR input, CK_ULONG input_len) +{ + return proxy_data(WT_P11_DIGEST_UPDATE, session, input, input_len); +} + +static CK_RV proxy_C_DigestKey(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_DIGEST_KEY); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_ulong(&writer, key); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_DigestFinal(CK_SESSION_HANDLE session, + CK_BYTE_PTR output, CK_ULONG_PTR output_len) +{ + return proxy_final_output(WT_P11_DIGEST_FINAL, session, output, output_len); +} + +static CK_RV proxy_C_SignInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE key) +{ + return proxy_mechanism_init(WT_P11_SIGN_INIT, session, mechanism, key, 1); +} + +static CK_RV proxy_C_Sign(CK_SESSION_HANDLE session, CK_BYTE_PTR input, + CK_ULONG input_len, CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_output(WT_P11_SIGN, session, input, input_len, output, + output_len); +} + +static CK_RV proxy_C_SignUpdate(CK_SESSION_HANDLE session, CK_BYTE_PTR input, + CK_ULONG input_len) +{ + return proxy_data(WT_P11_SIGN_UPDATE, session, input, input_len); +} + +static CK_RV proxy_C_SignFinal(CK_SESSION_HANDLE session, CK_BYTE_PTR output, + CK_ULONG_PTR output_len) +{ + return proxy_final_output(WT_P11_SIGN_FINAL, session, output, output_len); +} + +static CK_RV proxy_C_VerifyInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE key) +{ + return proxy_mechanism_init(WT_P11_VERIFY_INIT, session, mechanism, key, 1); +} + +static CK_RV proxy_C_Verify(CK_SESSION_HANDLE session, CK_BYTE_PTR data, + CK_ULONG data_len, CK_BYTE_PTR signature, + CK_ULONG signature_len) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + proxy_begin(&writer, WT_P11_VERIFY); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_blob(&writer, data, data_len); + (void)proxy_put_blob(&writer, signature, signature_len); + return proxy_invoke_complete(&writer, &reader); +} + +static CK_RV proxy_C_VerifyUpdate(CK_SESSION_HANDLE session, + CK_BYTE_PTR input, CK_ULONG input_len) +{ + return proxy_data(WT_P11_VERIFY_UPDATE, session, input, input_len); +} + +static CK_RV proxy_C_VerifyFinal(CK_SESSION_HANDLE session, + CK_BYTE_PTR signature, + CK_ULONG signature_len) +{ + return proxy_data(WT_P11_VERIFY_FINAL, session, signature, signature_len); +} + +static CK_RV proxy_C_GenerateKey(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + CK_OBJECT_HANDLE_PTR key) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + if (key == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_GENERATE_KEY); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_mechanism(&writer, mechanism); + (void)proxy_put_template(&writer, attributes, count, 1); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + *key = (CK_OBJECT_HANDLE)wt_pkcs11_get_u32(&reader); + return proxy_finish(&reader, + reader.error != 0 ? CKR_DEVICE_ERROR : rv); +} + +static CK_RV proxy_C_GenerateKeyPair(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_ATTRIBUTE_PTR public_attributes, + CK_ULONG public_count, + CK_ATTRIBUTE_PTR private_attributes, + CK_ULONG private_count, + CK_OBJECT_HANDLE_PTR public_key, + CK_OBJECT_HANDLE_PTR private_key) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + if (public_key == NULL || private_key == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_GENERATE_KEY_PAIR); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_mechanism(&writer, mechanism); + (void)proxy_put_template(&writer, public_attributes, public_count, 1); + (void)proxy_put_template(&writer, private_attributes, private_count, 1); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + *public_key = (CK_OBJECT_HANDLE)wt_pkcs11_get_u32(&reader); + *private_key = (CK_OBJECT_HANDLE)wt_pkcs11_get_u32(&reader); + return proxy_finish(&reader, + reader.error != 0 ? CKR_DEVICE_ERROR : rv); +} + +static CK_RV proxy_C_DeriveKey(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE base_key, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + CK_OBJECT_HANDLE_PTR key) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + if (key == NULL) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_DERIVE_KEY); + (void)proxy_put_ulong(&writer, session); + (void)proxy_put_mechanism(&writer, mechanism); + (void)proxy_put_ulong(&writer, base_key); + (void)proxy_put_template(&writer, attributes, count, 1); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + *key = (CK_OBJECT_HANDLE)wt_pkcs11_get_u32(&reader); + return proxy_finish(&reader, + reader.error != 0 ? CKR_DEVICE_ERROR : rv); +} + +static CK_RV proxy_C_SeedRandom(CK_SESSION_HANDLE session, CK_BYTE_PTR seed, + CK_ULONG length) +{ + return proxy_data(WT_P11_SEED_RANDOM, session, seed, length); +} + +static CK_RV proxy_C_GenerateRandom(CK_SESSION_HANDLE session, + CK_BYTE_PTR output, CK_ULONG length) +{ + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint8_t* data; + size_t data_len; + CK_RV rv; + + if (output == NULL && length != 0U) { + return CKR_ARGUMENTS_BAD; + } + proxy_begin(&writer, WT_P11_GENERATE_RANDOM); + (void)proxy_put_ulong(&writer, session); + wt_pkcs11_put_u32(&writer, output != NULL ? 1U : 0U); + (void)proxy_put_ulong(&writer, length); + rv = proxy_invoke(&writer, &reader); + if (reader.error != 0) { + return proxy_finish(&reader, rv); + } + data = wt_pkcs11_get_bytes(&reader, &data_len); + if (reader.error != 0 || (rv == CKR_OK && data_len != length)) { + return proxy_finish(&reader, CKR_DEVICE_ERROR); + } + if (rv == CKR_OK && length != 0U) { + (void)memcpy(output, data, data_len); + } + return proxy_finish(&reader, rv); +} + +/* Operations not implemented by wolfPKCS11 on this target still traverse the + * secure relay. The provider returns CKR_FUNCTION_NOT_SUPPORTED. */ +static CK_RV proxy_unsupported(uint32_t function) +{ + return proxy_simple(function); +} + +static CK_RV proxy_C_GetOperationState(CK_SESSION_HANDLE session, + CK_BYTE_PTR state, CK_ULONG_PTR length) +{ + (void)session; (void)state; (void)length; + return proxy_unsupported(WT_P11_GET_OPERATION_STATE); +} +static CK_RV proxy_C_SetOperationState(CK_SESSION_HANDLE session, + CK_BYTE_PTR state, CK_ULONG length, CK_OBJECT_HANDLE encryption_key, + CK_OBJECT_HANDLE authentication_key) +{ + (void)session; (void)state; (void)length; (void)encryption_key; + (void)authentication_key; + return proxy_unsupported(WT_P11_SET_OPERATION_STATE); +} +static CK_RV proxy_C_SignRecoverInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE key) +{ + (void)session; (void)mechanism; (void)key; + return proxy_unsupported(WT_P11_SIGN_RECOVER_INIT); +} +static CK_RV proxy_C_SignRecover(CK_SESSION_HANDLE session, CK_BYTE_PTR data, + CK_ULONG data_len, CK_BYTE_PTR signature, CK_ULONG_PTR signature_len) +{ + (void)session; (void)data; (void)data_len; (void)signature; + (void)signature_len; + return proxy_unsupported(WT_P11_SIGN_RECOVER); +} +static CK_RV proxy_C_VerifyRecoverInit(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE key) +{ + (void)session; (void)mechanism; (void)key; + return proxy_unsupported(WT_P11_VERIFY_RECOVER_INIT); +} +static CK_RV proxy_C_VerifyRecover(CK_SESSION_HANDLE session, + CK_BYTE_PTR signature, CK_ULONG signature_len, CK_BYTE_PTR data, + CK_ULONG_PTR data_len) +{ + (void)session; (void)signature; (void)signature_len; (void)data; + (void)data_len; + return proxy_unsupported(WT_P11_VERIFY_RECOVER); +} + +#define WT_PROXY_COMBINED(name, id) \ +static CK_RV name(CK_SESSION_HANDLE session, CK_BYTE_PTR input, \ + CK_ULONG input_len, CK_BYTE_PTR output, \ + CK_ULONG_PTR output_len) \ +{ \ + (void)session; (void)input; (void)input_len; (void)output; \ + (void)output_len; \ + return proxy_unsupported(id); \ +} + +WT_PROXY_COMBINED(proxy_C_DigestEncryptUpdate, WT_P11_DIGEST_ENCRYPT_UPDATE) +WT_PROXY_COMBINED(proxy_C_DecryptDigestUpdate, WT_P11_DECRYPT_DIGEST_UPDATE) +WT_PROXY_COMBINED(proxy_C_SignEncryptUpdate, WT_P11_SIGN_ENCRYPT_UPDATE) +WT_PROXY_COMBINED(proxy_C_DecryptVerifyUpdate, WT_P11_DECRYPT_VERIFY_UPDATE) + +static CK_RV proxy_C_WrapKey(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE wrapping_key, + CK_OBJECT_HANDLE key, CK_BYTE_PTR wrapped, CK_ULONG_PTR wrapped_len) +{ + (void)session; (void)mechanism; (void)wrapping_key; (void)key; + (void)wrapped; (void)wrapped_len; + return proxy_unsupported(WT_P11_WRAP_KEY); +} +static CK_RV proxy_C_UnwrapKey(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE unwrapping_key, + CK_BYTE_PTR wrapped, CK_ULONG wrapped_len, CK_ATTRIBUTE_PTR attributes, + CK_ULONG count, CK_OBJECT_HANDLE_PTR key) +{ + (void)session; (void)mechanism; (void)unwrapping_key; (void)wrapped; + (void)wrapped_len; (void)attributes; (void)count; (void)key; + return proxy_unsupported(WT_P11_UNWRAP_KEY); +} +static CK_RV proxy_C_GetFunctionStatus(CK_SESSION_HANDLE session) +{ + (void)session; + return proxy_unsupported(WT_P11_GET_FUNCTION_STATUS); +} +static CK_RV proxy_C_CancelFunction(CK_SESSION_HANDLE session) +{ + (void)session; + return proxy_unsupported(WT_P11_CANCEL_FUNCTION); +} +static CK_RV proxy_C_WaitForSlotEvent(CK_FLAGS flags, CK_SLOT_ID_PTR slot, + CK_VOID_PTR reserved) +{ + (void)flags; (void)slot; (void)reserved; + return proxy_unsupported(WT_P11_WAIT_FOR_SLOT_EVENT); +} + +static CK_FUNCTION_LIST g_proxy_functions = { + { 2, 40 }, + WT_P11_EXPORT(C_Initialize), + WT_P11_EXPORT(C_Finalize), + WT_P11_EXPORT(C_GetInfo), + C_GetFunctionList, + WT_P11_EXPORT(C_GetSlotList), + WT_P11_EXPORT(C_GetSlotInfo), + WT_P11_EXPORT(C_GetTokenInfo), + WT_P11_EXPORT(C_GetMechanismList), + WT_P11_EXPORT(C_GetMechanismInfo), + WT_P11_EXPORT(C_InitToken), + WT_P11_EXPORT(C_InitPIN), + WT_P11_EXPORT(C_SetPIN), + WT_P11_EXPORT(C_OpenSession), + WT_P11_EXPORT(C_CloseSession), + WT_P11_EXPORT(C_CloseAllSessions), + WT_P11_EXPORT(C_GetSessionInfo), + WT_P11_EXPORT(C_GetOperationState), + WT_P11_EXPORT(C_SetOperationState), + WT_P11_EXPORT(C_Login), + WT_P11_EXPORT(C_Logout), + WT_P11_EXPORT(C_CreateObject), + WT_P11_EXPORT(C_CopyObject), + WT_P11_EXPORT(C_DestroyObject), + WT_P11_EXPORT(C_GetObjectSize), + WT_P11_EXPORT(C_GetAttributeValue), + WT_P11_EXPORT(C_SetAttributeValue), + WT_P11_EXPORT(C_FindObjectsInit), + WT_P11_EXPORT(C_FindObjects), + WT_P11_EXPORT(C_FindObjectsFinal), + WT_P11_EXPORT(C_EncryptInit), + WT_P11_EXPORT(C_Encrypt), + WT_P11_EXPORT(C_EncryptUpdate), + WT_P11_EXPORT(C_EncryptFinal), + WT_P11_EXPORT(C_DecryptInit), + WT_P11_EXPORT(C_Decrypt), + WT_P11_EXPORT(C_DecryptUpdate), + WT_P11_EXPORT(C_DecryptFinal), + WT_P11_EXPORT(C_DigestInit), + WT_P11_EXPORT(C_Digest), + WT_P11_EXPORT(C_DigestUpdate), + WT_P11_EXPORT(C_DigestKey), + WT_P11_EXPORT(C_DigestFinal), + WT_P11_EXPORT(C_SignInit), + WT_P11_EXPORT(C_Sign), + WT_P11_EXPORT(C_SignUpdate), + WT_P11_EXPORT(C_SignFinal), + WT_P11_EXPORT(C_SignRecoverInit), + WT_P11_EXPORT(C_SignRecover), + WT_P11_EXPORT(C_VerifyInit), + WT_P11_EXPORT(C_Verify), + WT_P11_EXPORT(C_VerifyUpdate), + WT_P11_EXPORT(C_VerifyFinal), + WT_P11_EXPORT(C_VerifyRecoverInit), + WT_P11_EXPORT(C_VerifyRecover), + proxy_C_DigestEncryptUpdate, + proxy_C_DecryptDigestUpdate, + proxy_C_SignEncryptUpdate, + proxy_C_DecryptVerifyUpdate, + WT_P11_EXPORT(C_GenerateKey), + WT_P11_EXPORT(C_GenerateKeyPair), + WT_P11_EXPORT(C_WrapKey), + WT_P11_EXPORT(C_UnwrapKey), + WT_P11_EXPORT(C_DeriveKey), + WT_P11_EXPORT(C_SeedRandom), + WT_P11_EXPORT(C_GenerateRandom), + WT_P11_EXPORT(C_GetFunctionStatus), + WT_P11_EXPORT(C_CancelFunction), + WT_P11_EXPORT(C_WaitForSlotEvent) +}; + +CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR functions) +{ + if (functions == NULL) { + return CKR_ARGUMENTS_BAD; + } + *functions = &g_proxy_functions; + return CKR_OK; +} diff --git a/src/client/pkcs11_psa_transport.c b/src/client/pkcs11_psa_transport.c new file mode 100644 index 000000000..e8aea7fa3 --- /dev/null +++ b/src/client/pkcs11_psa_transport.c @@ -0,0 +1,205 @@ +/* pkcs11_psa_transport.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/pkcs11_psa_transport.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/zeroize.h" + +#include "psa/client.h" + +#include + +static psa_handle_t g_pkcs11_handle = PSA_NULL_HANDLE; + +static int wt_pkcs11_conn_dead(psa_status_t status) +{ + return status == PSA_ERROR_PROGRAMMER_ERROR || + status == PSA_ERROR_CONNECTION_BUSY || + status == PSA_ERROR_COMMUNICATION_FAILURE || + status == PSA_ERROR_BAD_STATE; +} + +static int wt_pkcs11_connect(void) +{ + psa_handle_t handle; + + if (PSA_HANDLE_IS_VALID(g_pkcs11_handle)) { + return 0; + } + handle = psa_connect(WT_PKCS11_SERVICE_SID, + WT_PKCS11_SERVICE_VERSION); + if (!PSA_HANDLE_IS_VALID(handle)) { + return (int)handle; + } + g_pkcs11_handle = handle; + return 0; +} + +void wt_pkcs11_psa_close(void) +{ + if (PSA_HANDLE_IS_VALID(g_pkcs11_handle)) { + psa_close(g_pkcs11_handle); + } + g_pkcs11_handle = PSA_NULL_HANDLE; +} + +static int wt_pkcs11_call(int32_t op, const void* in, size_t in_len, + void* out, size_t out_len, size_t* actual) +{ + psa_invec in_vec; + psa_outvec out_vec; + psa_status_t status; + int rc; + + rc = wt_pkcs11_connect(); + if (rc != 0) { + return rc; + } + in_vec.base = in; + in_vec.len = in_len; + out_vec.base = out; + out_vec.len = out_len; + status = psa_call(g_pkcs11_handle, op, + (in != NULL) ? &in_vec : NULL, + (in != NULL) ? 1U : 0U, + (out != NULL) ? &out_vec : NULL, + (out != NULL) ? 1U : 0U); + if (status == PSA_SUCCESS && actual != NULL) { + *actual = out_vec.len; + } + else if (wt_pkcs11_conn_dead(status)) { + wt_pkcs11_psa_close(); + } + return (int)status; +} + +static int wt_pkcs11_begin(int32_t op, size_t length) +{ + wt_pkcs11_length_t message; + + if (length == 0U || length > WT_PKCS11_MESSAGE_MAX) { + return (int)PSA_ERROR_INVALID_ARGUMENT; + } + message.length = (uint32_t)length; + return wt_pkcs11_call(op, &message, sizeof(message), NULL, 0U, NULL); +} + +static int wt_pkcs11_push(int32_t op, const uint8_t* message, size_t length) +{ + wt_pkcs11_chunk_t chunk; + size_t offset = 0U; + size_t amount; + int rc; + + while (offset < length) { + amount = length - offset; + if (amount > WT_PKCS11_CHUNK_MAX) { + amount = WT_PKCS11_CHUNK_MAX; + } + chunk.offset = (uint32_t)offset; + chunk.length = (uint32_t)amount; + (void)memcpy(chunk.data, message + offset, amount); + rc = wt_pkcs11_call(op, &chunk, + sizeof(chunk.offset) + sizeof(chunk.length) + + amount, + NULL, 0U, NULL); + wt_forceZero(&chunk, sizeof(chunk)); + if (rc != 0) { + return rc; + } + offset += amount; + } + return 0; +} + +static int wt_pkcs11_pull(int32_t op, uint8_t* message, size_t length) +{ + wt_pkcs11_range_t range; + size_t offset = 0U; + size_t amount; + size_t actual; + int rc; + + while (offset < length) { + amount = length - offset; + if (amount > WT_PKCS11_CHUNK_MAX) { + amount = WT_PKCS11_CHUNK_MAX; + } + range.offset = (uint32_t)offset; + range.length = (uint32_t)amount; + actual = 0U; + rc = wt_pkcs11_call(op, &range, sizeof(range), message + offset, + amount, &actual); + if (rc != 0) { + return rc; + } + if (actual != amount) { + return (int)PSA_ERROR_COMMUNICATION_FAILURE; + } + offset += amount; + } + return 0; +} + +static void wt_pkcs11_abort(void) +{ + (void)wt_pkcs11_call(WT_PKCS11_OP_APP_ABORT, NULL, 0U, NULL, 0U, NULL); +} + +int wt_pkcs11_psa_call(const uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, + size_t* response_len) +{ + wt_pkcs11_length_t length; + size_t actual = 0U; + int rc; + + if (request == NULL || request_len == 0U || response == NULL || + response_cap == 0U || response_len == NULL) { + return (int)PSA_ERROR_INVALID_ARGUMENT; + } + *response_len = 0U; + rc = wt_pkcs11_begin(WT_PKCS11_OP_APP_BEGIN, request_len); + if (rc != 0) { + return rc; + } + rc = wt_pkcs11_push(WT_PKCS11_OP_APP_WRITE, request, request_len); + if (rc == 0) { + rc = wt_pkcs11_call(WT_PKCS11_OP_APP_EXECUTE, NULL, 0U, &length, + sizeof(length), &actual); + } + if (rc != 0 || actual != sizeof(length)) { + wt_pkcs11_abort(); + return (rc != 0) ? rc : (int)PSA_ERROR_COMMUNICATION_FAILURE; + } + if (length.length == 0U || length.length > WT_PKCS11_MESSAGE_MAX || + length.length > response_cap) { + wt_pkcs11_abort(); + return (int)PSA_ERROR_BUFFER_TOO_SMALL; + } + rc = wt_pkcs11_pull(WT_PKCS11_OP_APP_READ, response, length.length); + if (rc == 0) { + *response_len = length.length; + } + else { + wt_pkcs11_abort(); + } + return rc; +} diff --git a/src/client/provider_crypto.c b/src/client/provider_crypto.c new file mode 100644 index 000000000..0aada4f90 --- /dev/null +++ b/src/client/provider_crypto.c @@ -0,0 +1,120 @@ +/* provider_crypto.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/provider_crypto.h" + +#if defined(WT_ENGINE_HSM) +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_client_cryptocb.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" +#include "wolfssl/wolfcrypt/cryptocb.h" +#include "wolftrust/hsm_psa_transport.h" + +#define WT_SERVICE_HSM_SID 4102U +#define WT_SERVICE_HSM_VERSION 1U + +static wt_hsm_psa_transport_ctx_t g_transport; +static const wt_hsm_psa_transport_cfg_t g_transport_config = { + WT_SERVICE_HSM_SID, + WT_SERVICE_HSM_VERSION, +}; +static whClientContext g_client; +static whClientConfig g_client_config; +static whCommClientConfig g_comm_config; +static uint8_t g_client_initialized; +static uint8_t g_hsm_restarted; + +#if defined(CONFIG_TPM) && defined(WOLF_CRYPTO_CB_FIND) +/* fwTPM initializes keys without a device ID. */ +static int wt_provider_find_device(int devId, int algoType) +{ + (void)algoType; + return devId == INVALID_DEVID ? WH_DEV_ID : devId; +} +#endif + +int wt_provider_crypto_init(void) +{ + int rc; + +#if defined(CONFIG_TPM) && defined(WOLF_CRYPTO_CB_FIND) + wc_CryptoCb_SetDeviceFindCb(wt_provider_find_device); +#endif + g_comm_config.transport_cb = &wt_hsm_psa_transport_cb; + g_comm_config.transport_context = &g_transport; + g_comm_config.transport_config = &g_transport_config; + g_comm_config.client_id = WT_PROVIDER_HSM_CLIENT_ID; + g_client_config.comm = &g_comm_config; + + rc = wh_Client_Init(&g_client, &g_client_config); + if (rc == WH_ERROR_OK) { + rc = wh_Client_CommInitRequest(&g_client); + } + if (rc == WH_ERROR_OK) { + rc = wh_Client_CommInitResponse(&g_client, NULL, NULL); + } + if (rc != WH_ERROR_OK) { + (void)wh_Client_Cleanup(&g_client); + } + else { + g_client_initialized = 1U; + g_hsm_restarted = 0U; + } + return rc; +} + +void wt_provider_crypto_hsm_restarted(void) +{ + g_hsm_restarted = 1U; +} + +int wt_provider_crypto_prepare(void) +{ + if (g_hsm_restarted != 0U) { + if (g_client_initialized != 0U) { + (void)wh_Client_Cleanup(&g_client); + g_client_initialized = 0U; + } + } + if (g_client_initialized == 0U) { + return wt_provider_crypto_init(); + } + return WH_ERROR_OK; +} + +#else +#include "wolfssl/wolfcrypt/wc_port.h" + +int wt_provider_crypto_init(void) +{ + return wolfCrypt_Init(); +} + +int wt_provider_crypto_prepare(void) +{ + return 0; +} + +void wt_provider_crypto_hsm_restarted(void) +{ +} +#endif diff --git a/src/client/tpm_psa_transport.c b/src/client/tpm_psa_transport.c new file mode 100644 index 000000000..bd5f5def4 --- /dev/null +++ b/src/client/tpm_psa_transport.c @@ -0,0 +1,169 @@ +/* tpm_psa_transport.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/tpm_psa_transport.h" +#include "psa/client.h" + +static psa_handle_t g_tpm_handle = PSA_NULL_HANDLE; + +static int wt_tpm_psa_connect(void) +{ + psa_handle_t handle; + + if (PSA_HANDLE_IS_VALID(g_tpm_handle)) { + return 0; + } + handle = psa_connect(WT_TPM_SERVICE_SID, WT_TPM_SERVICE_VERSION); + if (!PSA_HANDLE_IS_VALID(handle)) { + return (int)handle; + } + g_tpm_handle = handle; + return 0; +} + +void wt_tpm_psa_close(void) +{ + if (PSA_HANDLE_IS_VALID(g_tpm_handle)) { + psa_close(g_tpm_handle); + } + g_tpm_handle = PSA_NULL_HANDLE; +} + +/* Statuses that mean the cached connection is dead: service recovery marks + * every connection to the failed partition as errored, so close the handle on + * the first such status and let the next call reconnect, keeping the public + * header's promise that a lost connection reopens on the next call. */ +static int wt_tpm_psa_conn_dead(psa_status_t st) +{ + return st == PSA_ERROR_PROGRAMMER_ERROR || + st == PSA_ERROR_CONNECTION_BUSY || + st == PSA_ERROR_COMMUNICATION_FAILURE || + st == PSA_ERROR_BAD_STATE; +} + +int wt_tpm_psa_transact(const uint8_t* command, size_t command_len, + uint8_t* response, size_t response_cap, + size_t* response_len) +{ + psa_invec input; + psa_outvec output; + psa_status_t status; + int rc; + + if (command == NULL || command_len == 0U || + command_len > WT_TPM_FRAME_MAX || response == NULL || + response_cap == 0U || response_len == NULL) { + return (int)PSA_ERROR_INVALID_ARGUMENT; + } + *response_len = 0U; + rc = wt_tpm_psa_connect(); + if (rc != 0) { + return rc; + } + input.base = command; + input.len = command_len; + output.base = response; + output.len = response_cap < WT_TPM_FRAME_MAX ? response_cap : WT_TPM_FRAME_MAX; + status = psa_call(g_tpm_handle, WT_TPM_OP_TRANSACT, &input, 1U, &output, 1U); + if (status == PSA_SUCCESS) { + if (output.len == 0U || output.len > response_cap || + output.len > WT_TPM_FRAME_MAX) { + return (int)PSA_ERROR_COMMUNICATION_FAILURE; + } + *response_len = output.len; + } + else if (wt_tpm_psa_conn_dead(status)) { + wt_tpm_psa_close(); + } + return (int)status; +} + +#if defined(WOLFTPM_SWTPM) +/* wolfTPM client transport: built with WOLFTPM_SWTPM so the core routes + * every command through TPM2_SWTPM_SendCommand, which this file provides in + * place of the socket client. One synchronous call returns the response. */ +#include +#include +#include +#include + +/* The core still runs its TIS chip-startup sequence; the mediated door has + * no TIS registers, so startup wait, locality, and device info are no-ops. */ +int TPM2_TIS_StartupWait(TPM2_CTX* ctx, int timeout) +{ + (void)ctx; + (void)timeout; + return TPM_RC_SUCCESS; +} + +int TPM2_TIS_RequestLocality(TPM2_CTX* ctx, int timeout) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + (void)timeout; + ctx->locality = WOLFTPM_LOCALITY_DEFAULT; + return TPM_RC_SUCCESS; +} + +int TPM2_TIS_GetInfo(TPM2_CTX* ctx) +{ + (void)ctx; + return TPM_RC_SUCCESS; +} + +void TPM2_SwtpmClose(TPM2_CTX* ctx) +{ + (void)ctx; + wt_tpm_psa_close(); +} + +int TPM2_SWTPM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + size_t rsp_len = 0U; + uint32_t declared; + int rc; + + (void)ctx; + if (packet == NULL || packet->buf == NULL || packet->pos <= 0 || + packet->size <= 0 || packet->pos > packet->size) { + return BAD_FUNC_ARG; + } + rc = wt_tpm_psa_transact(packet->buf, (size_t)packet->pos, packet->buf, + (size_t)packet->size, &rsp_len); + if (rc != 0 || rsp_len < (size_t)TPM2_HEADER_SIZE || + rsp_len > (size_t)packet->size) { + return TPM_RC_FAILURE; + } + /* The response header declares its own total length; reject a mediated + * frame whose body length disagrees rather than let the core parse stale + * buffer tail past the bytes actually received. */ + declared = ((uint32_t)packet->buf[2] << 24) | + ((uint32_t)packet->buf[3] << 16) | + ((uint32_t)packet->buf[4] << 8) | + (uint32_t)packet->buf[5]; + if (declared != (uint32_t)rsp_len) { + return TPM_RC_FAILURE; + } + /* Bind the parser to the bytes actually received. */ + packet->size = (int)rsp_len; + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SWTPM */ diff --git a/src/ffm_boot.c b/src/ffm_boot.c index 813a5201d..a11e32c82 100644 --- a/src/ffm_boot.c +++ b/src/ffm_boot.c @@ -25,6 +25,8 @@ #include "wolftrust/services/hsm_relay.h" #include "wolftrust/services/fwu_service.h" #include "wolftrust/services/vnet_relay.h" +#include "wolftrust/services/tpm_relay.h" +#include "wolftrust/services/pkcs11_relay.h" #include "wolftrust/services/storage_service.h" #include "wolftrust/services/vault_service.h" #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) @@ -192,6 +194,21 @@ int wt_ffm_boot_init(const wt_system_manifest_t* manifest) PARTITION_VNET_ID, wt_vnet_relay_dispatch, NULL); (void)vault_ret; +#endif +#ifdef PARTITION_TPM_ID + /* SERVICE_TPM: the single mediated door to the isolated fwTPM + * partition. Fail-closed until the platform installs the submit + * hook that reaches the fwTPM responder. */ + vault_ret = wt_ffm_register_partition(&g_ffm_runtime, + PARTITION_TPM_ID, + wt_tpm_relay_dispatch, NULL); + (void)vault_ret; +#endif +#ifdef PARTITION_PKCS11_ID + vault_ret = wt_ffm_register_partition(&g_ffm_runtime, + PARTITION_PKCS11_ID, + wt_pkcs11_relay_dispatch, NULL); + (void)vault_ret; #endif } return ret; @@ -250,6 +267,16 @@ int wt_ffm_boot_start_sched(void) ret = wt_spm_vnet_start(&g_ffm_runtime, PARTITION_VNET_ID); } #endif +#ifdef PARTITION_TPM_ID + if (ret == WT_FFM_SUCCESS) { + ret = wt_spm_tpm_start(&g_ffm_runtime, PARTITION_TPM_ID); + } +#endif +#ifdef PARTITION_PKCS11_ID + if (ret == WT_FFM_SUCCESS) { + ret = wt_spm_pkcs11_start(&g_ffm_runtime, PARTITION_PKCS11_ID); + } +#endif #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) /* SERVICE_ATTEST now runs as a scheduled Secure Partition; the inline * dispatch registered in wt_ffm_boot_init is replaced by this coroutine. */ @@ -297,6 +324,18 @@ int wt_ffm_boot_start_sched(void) wt_spm_sched_prime(PARTITION_VAULT_ID); #endif } +#endif +#if defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1) + if (ret == WT_FFM_SUCCESS) { + wt_spm_sched_prime(PARTITION_HSM_ID); + wt_spm_sched_prime(PARTITION_TPM_ID); + } +#endif +#if defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1) + if (ret == WT_FFM_SUCCESS) { + wt_spm_sched_prime(PARTITION_HSM_ID); + wt_spm_sched_prime(PARTITION_PKCS11_ID); + } #endif return ret; } diff --git a/src/pkcs11/pkcs11_provider.c b/src/pkcs11/pkcs11_provider.c new file mode 100644 index 000000000..9bbec1578 --- /dev/null +++ b/src/pkcs11/pkcs11_provider.c @@ -0,0 +1,1461 @@ +/* pkcs11_provider.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/pkcs11_provider.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" +#include "wolftrust/zeroize.h" + +#include "wolfpkcs11/pkcs11.h" +static CK_RV provider_backend_mechanism_info(CK_SLOT_ID slot, + CK_MECHANISM_TYPE mechanism, CK_MECHANISM_INFO_PTR info) +{ + return C_GetMechanismInfo(slot, mechanism, info); +} + +#define WT_PKCS11_PROVIDER_BUILD +#include "wolftrust/crypto_profile.h" + +#include + +#define WT_P11_TEMPLATE_MAX 32U +#define WT_P11_BLOB_OVERHEAD 4U +#define WT_P11_OUTPUT_OVERHEAD 8U +#define WT_P11_PIN_SLOTS 4U +#define WT_P11_PIN_FLAGS (CKF_USER_PIN_COUNT_LOW | CKF_USER_PIN_FINAL_TRY | \ + CKF_USER_PIN_LOCKED | CKF_SO_PIN_COUNT_LOW | CKF_SO_PIN_FINAL_TRY | \ + CKF_SO_PIN_LOCKED) + +#if WT_PKCS11_PIN_MAX_FAILURES < 1 +#error "WT_PKCS11_PIN_MAX_FAILURES must allow at least one PIN attempt" +#endif + +typedef union wt_provider_data { + CK_ULONG alignment; + uint8_t bytes[WT_PKCS11_MESSAGE_MAX]; +} wt_provider_data_t; + +static wt_provider_data_t g_provider_storage; +#define g_provider_data g_provider_storage.bytes + +typedef struct wt_provider_pin_state { + unsigned int user_failures; + unsigned int so_failures; +} wt_provider_pin_state_t; + +/* WOLFPKCS11_NO_TIME compiles out wolfPKCS11's clock-based lockout, so the + * provider keeps its own count. It lives in RAM like the NO_STORE token. */ +static wt_provider_pin_state_t g_provider_pin[WT_P11_PIN_SLOTS]; + +typedef struct wt_provider_mechanism { + CK_MECHANISM mechanism; + union { + CK_AES_CTR_PARAMS ctr; + CK_GCM_PARAMS gcm; + CK_CCM_PARAMS ccm; + CK_ECDH1_DERIVE_PARAMS ecdh; + } params; +} wt_provider_mechanism_t; + +static CK_ULONG provider_ulong(wt_pkcs11_reader_t* reader) +{ + return (CK_ULONG)wt_pkcs11_get_u32(reader); +} + +/* A presence flag, then the bytes: NULL and non-NULL empty buffers differ. */ +static uint8_t* provider_buffer(wt_pkcs11_reader_t* reader, size_t* length) +{ + uint32_t present = wt_pkcs11_get_u32(reader); + uint8_t* data = wt_pkcs11_get_bytes(reader, length); + + if (reader->error != 0 || present > 1U || + (present == 0U && *length != 0U)) { + reader->error = 1; + *length = 0U; + return NULL; + } + return present != 0U ? data : NULL; +} + +static uint8_t* provider_blob(wt_pkcs11_reader_t* reader, CK_ULONG* length) +{ + uint32_t declared = wt_pkcs11_get_u32(reader); + size_t actual = 0U; + uint8_t* data = provider_buffer(reader, &actual); + + if (reader->error != 0 || actual != declared) { + reader->error = 1; + *length = 0U; + return NULL; + } + *length = (CK_ULONG)actual; + return data; +} + +static int provider_scratch_reserve(size_t length, size_t* used, + uint8_t** data) +{ + size_t offset = *used; + size_t remainder = offset % sizeof(CK_ULONG); + size_t padding = remainder != 0U ? sizeof(CK_ULONG) - remainder : 0U; + + if (offset > sizeof(g_provider_data) || + padding > sizeof(g_provider_data) - offset) { + return -1; + } + offset += padding; + if (length > sizeof(g_provider_data) - offset) { + return -1; + } + *data = g_provider_data + offset; + *used = offset + length; + return 0; +} + +static int provider_template(wt_pkcs11_reader_t* reader, + CK_ATTRIBUTE* attributes, CK_ULONG* count, + int output_values, CK_ULONG* capacities, + uint8_t* value_requested, + size_t* scratch_used, CK_ATTRIBUTE_PTR* pointer) +{ + CK_ULONG i; + uint32_t present; + + if (scratch_used == NULL || pointer == NULL) { + return -1; + } + *pointer = NULL; + present = wt_pkcs11_get_u32(reader); + *count = provider_ulong(reader); + if (reader->error != 0 || *count > WT_P11_TEMPLATE_MAX || + present > 1U || (present == 0U && *count != 0U)) { + return -1; + } + if (present != 0U) { + *pointer = attributes; + } + for (i = 0U; i < *count; i++) { + uint32_t has_value; + size_t wire_len; + uint8_t* wire_value; + + attributes[i].type = provider_ulong(reader); + attributes[i].ulValueLen = provider_ulong(reader); + if (capacities != NULL) { + capacities[i] = attributes[i].ulValueLen; + } + has_value = wt_pkcs11_get_u32(reader); + wire_value = wt_pkcs11_get_bytes(reader, &wire_len); + if (reader->error != 0 || has_value > 1U) { + return -1; + } + if (output_values != 0) { + if (capacities == NULL || value_requested == NULL) { + return -1; + } + value_requested[i] = (uint8_t)has_value; + attributes[i].pValue = NULL; + if (has_value == 0U) { + capacities[i] = 0U; + } + if (wire_len != 0U) { + return -1; + } + } + else { + if (wire_len != attributes[i].ulValueLen || + (has_value == 0U && wire_len != 0U)) { + return -1; + } + if (has_value != 0U) { + uint8_t* aligned_value; + + if (provider_scratch_reserve(wire_len, scratch_used, + &aligned_value) != 0) { + return -1; + } + if (wire_len != 0U) { + (void)memcpy(aligned_value, wire_value, wire_len); + } + attributes[i].pValue = aligned_value; + } + else { + attributes[i].pValue = NULL; + } + } + } + return 0; +} + +static int provider_prepare_attribute_outputs( + CK_ATTRIBUTE* attributes, CK_ULONG count, const CK_ULONG* capacities, + const uint8_t* value_requested, CK_ULONG* output_lengths, + size_t payload_capacity, size_t* scratch_used, int* has_output) +{ + CK_ULONG i; + size_t payload_used = 0U; + int result = 0; + + *has_output = 0; + for (i = 0U; i < count; i++) { + CK_ULONG actual = attributes[i].ulValueLen; + uint8_t* output; + + attributes[i].pValue = NULL; + output_lengths[i] = 0U; + if (value_requested[i] == 0U || + actual == CK_UNAVAILABLE_INFORMATION) { + continue; + } + if (actual > capacities[i]) { + result = 1; + continue; + } + if (actual > UINT32_MAX || payload_used > payload_capacity || + (size_t)actual > payload_capacity - payload_used || + provider_scratch_reserve((size_t)actual, scratch_used, + &output) != 0) { + result = -1; + break; + } + attributes[i].pValue = output; + attributes[i].ulValueLen = actual; + output_lengths[i] = actual; + payload_used += (size_t)actual; + *has_output = 1; + } + if (result < 0) { + *scratch_used = 0U; + *has_output = 0; + for (i = 0U; i < count; i++) { + attributes[i].pValue = NULL; + output_lengths[i] = 0U; + } + } + return result; +} + +static uint32_t provider_mechanism_kind(CK_MECHANISM_TYPE mechanism) +{ + switch (mechanism) { + case CKM_AES_CTR: + return WT_P11_MECH_CTR; + case CKM_AES_GCM: + return WT_P11_MECH_GCM; + case CKM_AES_CCM: + return WT_P11_MECH_CCM; + case CKM_ECDH1_DERIVE: + return WT_P11_MECH_ECDH; + default: + return WT_P11_MECH_RAW; + } +} + +static int provider_raw_mechanism_supported(CK_MECHANISM_TYPE mechanism) +{ + switch (mechanism) { + case CKM_RSA_PKCS_PSS: + case CKM_SHA1_RSA_PKCS_PSS: + case CKM_SHA224_RSA_PKCS_PSS: + case CKM_SHA256_RSA_PKCS_PSS: + case CKM_SHA384_RSA_PKCS_PSS: + case CKM_SHA512_RSA_PKCS_PSS: + case CKM_DH_PKCS_DERIVE: + case CKM_MD5_HMAC: + case CKM_SHA1_HMAC: + case CKM_SHA224_HMAC: + case CKM_SHA256_HMAC: + case CKM_SHA384_HMAC: + case CKM_SHA512_HMAC: + case CKM_SHA3_224_HMAC: + case CKM_SHA3_256_HMAC: + case CKM_SHA3_384_HMAC: + case CKM_SHA3_512_HMAC: + case CKM_TLS_MAC: + case CKM_AES_CBC: + case CKM_AES_CBC_PAD: + case CKM_AES_CTS: + case CKM_AES_CMAC_GENERAL: + case CKM_AES_KEY_WRAP: + case CKM_AES_KEY_WRAP_PAD: + return 1; + default: + return 0; + } +} + +/* Mechanisms whose required parameters hold nested pointers, which the wire + * cannot carry; they are hidden so no caller is offered one it cannot use. */ +static int provider_mechanism_transportable(CK_MECHANISM_TYPE mechanism) +{ + if (!wt_profile_pkcs11_mechanism(mechanism)) { + return 0; + } + switch (mechanism) { + case CKM_RSA_PKCS_OAEP: + case CKM_HKDF_DERIVE: + case CKM_HKDF_DATA: + case CKM_AES_CBC_ENCRYPT_DATA: + case CKM_TLS12_MASTER_KEY_DERIVE: + case CKM_TLS12_KEY_AND_MAC_DERIVE: + case CKM_TLS12_MASTER_KEY_DERIVE_DH: + case CKM_PKCS5_PBKD2: + return 0; + default: + return 1; + } +} + +static CK_ULONG provider_filter_mechanisms(CK_MECHANISM_TYPE* mechanisms, + CK_ULONG count, CK_SLOT_ID slot) +{ + CK_MECHANISM_INFO info; + CK_ULONG kept = 0U; + CK_ULONG i; + + for (i = 0U; i < count; i++) { + if (provider_mechanism_transportable(mechanisms[i]) != 0 && + provider_backend_mechanism_info(slot, mechanisms[i], &info) == CKR_OK && + (wt_profile_pkcs11_flags(info.flags) & + (CKF_ENCRYPT | CKF_DECRYPT | CKF_DIGEST | CKF_SIGN | + CKF_VERIFY | CKF_SIGN_RECOVER | CKF_VERIFY_RECOVER | + CKF_GENERATE | CKF_GENERATE_KEY_PAIR | CKF_DERIVE | + CKF_WRAP | CKF_UNWRAP)) != 0U) { + mechanisms[kept] = mechanisms[i]; + kept++; + } + } + return kept; +} + +static int provider_mechanism(wt_pkcs11_reader_t* reader, + wt_provider_mechanism_t* decoded, + CK_MECHANISM_PTR* mechanism, + size_t* scratch_used) +{ + uint32_t present = wt_pkcs11_get_u32(reader); + uint32_t kind; + size_t length; + uint8_t* data; + + if (scratch_used == NULL) { + return -1; + } + (void)memset(decoded, 0, sizeof(*decoded)); + if (present == 0U) { + *mechanism = NULL; + return reader->error != 0 ? -1 : 0; + } + if (present != 1U) { + return -1; + } + decoded->mechanism.mechanism = provider_ulong(reader); + kind = wt_pkcs11_get_u32(reader); + if (kind != provider_mechanism_kind(decoded->mechanism.mechanism)) { + return -1; + } + if (kind == WT_P11_MECH_CTR) { + decoded->params.ctr.ulCounterBits = provider_ulong(reader); + data = wt_pkcs11_get_bytes(reader, &length); + if (length != sizeof(decoded->params.ctr.cb)) { + return -1; + } + (void)memcpy(decoded->params.ctr.cb, data, length); + decoded->mechanism.pParameter = &decoded->params.ctr; + decoded->mechanism.ulParameterLen = sizeof(decoded->params.ctr); + } + else if (kind == WT_P11_MECH_GCM) { + data = provider_buffer(reader, &length); + decoded->params.gcm.pIv = data; + decoded->params.gcm.ulIvLen = (CK_ULONG)length; + decoded->params.gcm.ulIvBits = provider_ulong(reader); + data = provider_buffer(reader, &length); + decoded->params.gcm.pAAD = data; + decoded->params.gcm.ulAADLen = (CK_ULONG)length; + decoded->params.gcm.ulTagBits = provider_ulong(reader); + decoded->mechanism.pParameter = &decoded->params.gcm; + decoded->mechanism.ulParameterLen = sizeof(decoded->params.gcm); + } + else if (kind == WT_P11_MECH_CCM) { + decoded->params.ccm.ulDataLen = provider_ulong(reader); + data = provider_buffer(reader, &length); + decoded->params.ccm.pIv = data; + decoded->params.ccm.ulIvLen = (CK_ULONG)length; + data = provider_buffer(reader, &length); + decoded->params.ccm.pAAD = data; + decoded->params.ccm.ulAADLen = (CK_ULONG)length; + decoded->params.ccm.ulMacLen = provider_ulong(reader); + decoded->mechanism.pParameter = &decoded->params.ccm; + decoded->mechanism.ulParameterLen = sizeof(decoded->params.ccm); + } + else if (kind == WT_P11_MECH_ECDH) { + decoded->params.ecdh.kdf = provider_ulong(reader); + data = provider_buffer(reader, &length); + decoded->params.ecdh.pSharedData = data; + decoded->params.ecdh.ulSharedDataLen = (CK_ULONG)length; + data = provider_buffer(reader, &length); + decoded->params.ecdh.pPublicData = data; + decoded->params.ecdh.ulPublicDataLen = (CK_ULONG)length; + decoded->mechanism.pParameter = &decoded->params.ecdh; + decoded->mechanism.ulParameterLen = sizeof(decoded->params.ecdh); + } + else if (kind == WT_P11_MECH_RAW) { + uint8_t* aligned_data; + + data = provider_buffer(reader, &length); + if (reader->error != 0) { + return -1; + } + if (length != 0U && + provider_raw_mechanism_supported( + decoded->mechanism.mechanism) == 0) { + return -1; + } + if (data != NULL) { + if (provider_scratch_reserve(length, scratch_used, + &aligned_data) != 0) { + return -1; + } + if (length != 0U) { + (void)memcpy(aligned_data, data, length); + } + decoded->mechanism.pParameter = aligned_data; + } + else { + decoded->mechanism.pParameter = NULL; + } + decoded->mechanism.ulParameterLen = (CK_ULONG)length; + } + else { + return -1; + } + *mechanism = &decoded->mechanism; + return reader->error != 0 ? -1 : 0; +} + +static void provider_response_begin(wt_pkcs11_writer_t* writer, + uint8_t* response, size_t capacity) +{ + wt_pkcs11_writer_init(writer, response, capacity); + wt_pkcs11_put_u32(writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(writer, WT_PKCS11_WIRE_VERSION); + wt_pkcs11_put_u32(writer, 0U); +} + +static void provider_response_rv(uint8_t* response, CK_RV rv) +{ + uint32_t value = (uint32_t)rv; + + response[8] = (uint8_t)value; + response[9] = (uint8_t)(value >> 8); + response[10] = (uint8_t)(value >> 16); + response[11] = (uint8_t)(value >> 24); +} + +static void provider_put_object(wt_pkcs11_writer_t* writer, + CK_OBJECT_HANDLE object) +{ + wt_pkcs11_put_u32(writer, (uint32_t)object); +} + +static void provider_put_length(wt_pkcs11_writer_t* writer, CK_ULONG length) +{ + wt_pkcs11_put_u32(writer, length == CK_UNAVAILABLE_INFORMATION ? + WT_PKCS11_WIRE_UNAVAILABLE : (uint32_t)length); +} + +static void provider_put_output(wt_pkcs11_writer_t* writer, CK_RV rv, + CK_BYTE_PTR output, CK_ULONG actual, + CK_ULONG capacity) +{ + wt_pkcs11_put_u32(writer, (uint32_t)actual); + if (output != NULL && rv == CKR_OK && actual <= capacity) { + wt_pkcs11_put_bytes(writer, output, actual); + } + else { + wt_pkcs11_put_bytes(writer, NULL, 0U); + } +} + +static CK_ULONG provider_output_capacity(const wt_pkcs11_writer_t* writer, + CK_ULONG requested, + size_t overhead) +{ + size_t available; + + if (writer->error != 0 || writer->used > writer->capacity || + overhead > writer->capacity - writer->used) { + return 0U; + } + available = writer->capacity - writer->used - overhead; + if (available > sizeof(g_provider_data)) { + available = sizeof(g_provider_data); + } + if (requested < available) { + available = (size_t)requested; + } + return (CK_ULONG)available; +} + +static int provider_request_valid(const wt_pkcs11_reader_t* reader) +{ + return reader->error == 0 && reader->used == reader->length; +} + +static unsigned int* provider_pin_failures(CK_SLOT_ID slot, int so) +{ + if (slot == 0U || slot > WT_P11_PIN_SLOTS) { + return NULL; + } + return so != 0 ? &g_provider_pin[slot - 1U].so_failures : + &g_provider_pin[slot - 1U].user_failures; +} + +/* Every wrong PIN surfaces as CKR_PIN_INCORRECT; any other failure, such as a + * backend error after a correct PIN, neither consumes nor clears an attempt. */ +static void provider_pin_record(unsigned int* failures, CK_RV rv) +{ + if (failures == NULL) { + return; + } + if (rv == CKR_OK) { + *failures = 0U; + } + else if (rv == CKR_PIN_INCORRECT && + *failures < WT_PKCS11_PIN_MAX_FAILURES) { + (*failures)++; + } +} + +static CK_RV provider_pin_counter(CK_SESSION_HANDLE session, int set_pin, + CK_USER_TYPE user, CK_SESSION_INFO* info, + unsigned int** failures) +{ + CK_RV rv; + int so; + + *failures = NULL; + (void)memset(info, 0, sizeof(*info)); + rv = C_GetSessionInfo(session, info); + if (rv == CKR_OK) { + so = (set_pin != 0) ? (info->state == CKS_RW_SO_FUNCTIONS) : + (user == CKU_SO); + *failures = provider_pin_failures(info->slotID, so); + if (*failures == NULL) { + rv = CKR_FUNCTION_FAILED; + } + } + return rv; +} + +static int provider_pin_len_valid(const CK_TOKEN_INFO* token, + CK_UTF8CHAR_PTR pin, CK_ULONG pin_len) +{ + return pin != NULL && pin_len >= token->ulMinPinLen && + pin_len <= token->ulMaxPinLen; +} + +/* The checks C_Login makes before it hashes the PIN: a locked PIN refuses + * only a call that would really test it, so other errors keep their code. */ +static int provider_login_tests_pin(const CK_SESSION_INFO* session, + const CK_TOKEN_INFO* token, + CK_USER_TYPE user, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len) +{ + int tests = provider_pin_len_valid(token, pin, pin_len) && + (session->state == CKS_RO_PUBLIC_SESSION || + session->state == CKS_RW_PUBLIC_SESSION); + + if (tests != 0 && user == CKU_SO) { + tests = (token->flags & CKF_TOKEN_INITIALIZED) != 0U && + (session->flags & CKF_RW_SESSION) != 0U; + } + else if (tests != 0 && user == CKU_USER) { + tests = (token->flags & CKF_USER_PIN_INITIALIZED) != 0U; + } + else { + tests = 0; + } + return tests; +} + +/* The checks C_SetPIN makes before it hashes the old PIN. */ +static int provider_set_pin_tests_pin(const CK_SESSION_INFO* session, + const CK_TOKEN_INFO* token, + CK_UTF8CHAR_PTR old_pin, + CK_ULONG old_len, + CK_UTF8CHAR_PTR new_pin, + CK_ULONG new_len) +{ + if (provider_pin_len_valid(token, old_pin, old_len) == 0 || + provider_pin_len_valid(token, new_pin, new_len) == 0) { + return 0; + } + if (session->state == CKS_RW_SO_FUNCTIONS) { + return 1; + } + return (session->state == CKS_RW_USER_FUNCTIONS || + session->state == CKS_RW_PUBLIC_SESSION) && + (token->flags & CKF_USER_PIN_INITIALIZED) != 0U; +} + +static CK_RV provider_login(CK_SESSION_HANDLE session, CK_USER_TYPE user, + CK_UTF8CHAR_PTR pin, CK_ULONG pin_len) +{ + CK_SESSION_INFO info; + CK_TOKEN_INFO token; + unsigned int* failures = NULL; + CK_RV rv = provider_pin_counter(session, 0, user, &info, &failures); + + if (rv == CKR_OK && *failures >= WT_PKCS11_PIN_MAX_FAILURES) { + (void)memset(&token, 0, sizeof(token)); + rv = C_GetTokenInfo(info.slotID, &token); + if (rv != CKR_OK || provider_login_tests_pin(&info, &token, user, + pin, pin_len) != 0) { + rv = CKR_PIN_LOCKED; + } + } + if (rv == CKR_OK) { + rv = C_Login(session, user, pin, pin_len); + provider_pin_record(failures, rv); + } + return rv; +} + +static CK_RV provider_set_pin(CK_SESSION_HANDLE session, + CK_UTF8CHAR_PTR old_pin, CK_ULONG old_len, + CK_UTF8CHAR_PTR new_pin, CK_ULONG new_len) +{ + CK_SESSION_INFO info; + CK_TOKEN_INFO token; + unsigned int* failures = NULL; + CK_RV rv = provider_pin_counter(session, 1, CKU_USER, &info, &failures); + + if (rv == CKR_OK && *failures >= WT_PKCS11_PIN_MAX_FAILURES) { + (void)memset(&token, 0, sizeof(token)); + rv = C_GetTokenInfo(info.slotID, &token); + if (rv != CKR_OK || provider_set_pin_tests_pin(&info, &token, + old_pin, old_len, new_pin, new_len) != 0) { + rv = CKR_PIN_LOCKED; + } + } + if (rv == CKR_OK) { + rv = C_SetPIN(session, old_pin, old_len, new_pin, new_len); + provider_pin_record(failures, rv); + } + return rv; +} + +static CK_RV provider_init_pin(CK_SESSION_HANDLE session, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len) +{ + CK_SESSION_INFO info; + unsigned int* failures = NULL; + CK_RV rv = C_InitPIN(session, pin, pin_len); + + if (rv == CKR_OK) { + (void)memset(&info, 0, sizeof(info)); + if (C_GetSessionInfo(session, &info) == CKR_OK) { + failures = provider_pin_failures(info.slotID, 0); + } + if (failures != NULL) { + *failures = 0U; + } + } + return rv; +} + +static CK_RV provider_init_token(CK_SLOT_ID slot, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len, CK_UTF8CHAR_PTR label) +{ + CK_TOKEN_INFO info; + unsigned int* failures = provider_pin_failures(slot, 1); + CK_RV rv = CKR_OK; + + if (failures == NULL || *failures >= WT_PKCS11_PIN_MAX_FAILURES) { + (void)memset(&info, 0, sizeof(info)); + rv = C_GetTokenInfo(slot, &info); + if (rv == CKR_OK && failures == NULL) { + rv = CKR_FUNCTION_FAILED; + } + /* A blank token has no SO PIN to guess, so it can always be set. */ + else if (rv == CKR_OK && (info.flags & CKF_TOKEN_INITIALIZED) != 0U && + label != NULL && + provider_pin_len_valid(&info, pin, pin_len) != 0) { + rv = CKR_PIN_LOCKED; + } + } + if (rv == CKR_OK) { + rv = C_InitToken(slot, pin, pin_len, label); + if (rv == CKR_OK) { + g_provider_pin[slot - 1U].so_failures = 0U; + g_provider_pin[slot - 1U].user_failures = 0U; + } + else { + provider_pin_record(failures, rv); + } + } + return rv; +} + +/* Clear the counts only once C_Finalize really tore the token down: an extra + * C_Initialize keeps the library, and the token, alive across one finalize. */ +static void provider_pin_finalized(void) +{ +#ifdef WOLFPKCS11_NO_STORE + CK_INFO info; + + if (C_GetInfo(&info) == CKR_CRYPTOKI_NOT_INITIALIZED) { + wt_forceZero(g_provider_pin, sizeof(g_provider_pin)); + } +#endif +} + +static CK_FLAGS provider_pin_flags(unsigned int failures, CK_FLAGS low, + CK_FLAGS final_try, CK_FLAGS locked) +{ + CK_FLAGS flags = 0U; + + if (failures > 0U) { + flags |= low; + } + if (failures >= WT_PKCS11_PIN_MAX_FAILURES) { + flags |= locked; + } + else if (failures + 1U == WT_PKCS11_PIN_MAX_FAILURES) { + flags |= final_try; + } + return flags; +} + +static void provider_token_pin_flags(CK_SLOT_ID slot, CK_TOKEN_INFO* info) +{ + const unsigned int* user = provider_pin_failures(slot, 0); + const unsigned int* so = provider_pin_failures(slot, 1); + + info->flags &= ~(CK_FLAGS)WT_P11_PIN_FLAGS; + if (user != NULL && so != NULL) { + info->flags |= provider_pin_flags(*user, CKF_USER_PIN_COUNT_LOW, + CKF_USER_PIN_FINAL_TRY, CKF_USER_PIN_LOCKED); + info->flags |= provider_pin_flags(*so, CKF_SO_PIN_COUNT_LOW, + CKF_SO_PIN_FINAL_TRY, CKF_SO_PIN_LOCKED); + } +} + +int wt_pkcs11_provider_dispatch(uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, + size_t* response_len, void* context) +{ + wt_pkcs11_reader_t reader; + wt_pkcs11_writer_t writer; + wt_provider_mechanism_t decoded_mechanism; + CK_MECHANISM_PTR mechanism = NULL; + CK_ATTRIBUTE attributes[WT_P11_TEMPLATE_MAX]; + CK_ATTRIBUTE private_attributes[WT_P11_TEMPLATE_MAX]; + CK_ATTRIBUTE_PTR template_ptr = NULL; + CK_ATTRIBUTE_PTR private_template_ptr = NULL; + CK_ULONG attribute_capacities[WT_P11_TEMPLATE_MAX]; + CK_ULONG attribute_output_lengths[WT_P11_TEMPLATE_MAX]; + uint8_t attribute_value_requested[WT_P11_TEMPLATE_MAX]; + CK_SLOT_ID slots[WT_PKCS11_LIST_MAX]; + CK_MECHANISM_TYPE mechanisms[WT_PKCS11_LIST_MAX]; + CK_OBJECT_HANDLE objects[WT_PKCS11_LIST_MAX]; + CK_ULONG count = 0U; + CK_ULONG private_count = 0U; + CK_ULONG capacity = 0U; + CK_ULONG length = 0U; + CK_ULONG second_length = 0U; + size_t second_wire_length = 0U; + CK_ULONG session; + CK_ULONG value; + CK_ULONG second_value; + CK_ULONG i; + CK_RV rv = CKR_ARGUMENTS_BAD; + uint8_t* data = NULL; + uint8_t* second_data = NULL; + uint32_t function; + uint32_t has_output; + CK_ULONG wire_capacity; + size_t provider_data_used = 0U; + size_t attribute_payload_capacity; + size_t attribute_wire_overhead; + int attribute_output_status; + int has_attribute_output; + + (void)context; + if (request == NULL || request_len == 0U || response == NULL || + response_cap < 12U || response_len == NULL) { + return -1; + } + wt_forceZero(g_provider_data, sizeof(g_provider_data)); + (void)memset(attributes, 0, sizeof(attributes)); + (void)memset(private_attributes, 0, sizeof(private_attributes)); + (void)memset(attribute_capacities, 0, sizeof(attribute_capacities)); + (void)memset(attribute_output_lengths, 0, + sizeof(attribute_output_lengths)); + (void)memset(attribute_value_requested, 0, + sizeof(attribute_value_requested)); + wt_pkcs11_reader_init(&reader, request, request_len); + if (wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_MAGIC || + wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_VERSION) { + return -1; + } + function = wt_pkcs11_get_u32(&reader); + provider_response_begin(&writer, response, response_cap); + if (!wt_profile_pkcs11_operation(function)) { + provider_response_rv(response, CKR_FUNCTION_NOT_SUPPORTED); + *response_len = writer.used; + return 0; + } + + switch (function) { + case WT_P11_INITIALIZE: + { + CK_C_INITIALIZE_ARGS init; + uint32_t bad_reserved = wt_pkcs11_get_u32(&reader); + + (void)memset(&init, 0, sizeof(init)); + if (bad_reserved != 0U) { + init.pReserved = &init; + } + rv = provider_request_valid(&reader) ? + C_Initialize(bad_reserved != 0U ? &init : NULL) : + CKR_ARGUMENTS_BAD; + break; + } + case WT_P11_FINALIZE: + if (provider_request_valid(&reader)) { + rv = C_Finalize(NULL); + provider_pin_finalized(); + } + break; + case WT_P11_GET_INFO: + { + CK_INFO info; + rv = provider_request_valid(&reader) ? C_GetInfo(&info) : + CKR_ARGUMENTS_BAD; + wt_pkcs11_put_bytes(&writer, rv == CKR_OK ? &info : NULL, + rv == CKR_OK ? sizeof(info) : 0U); + break; + } + case WT_P11_GET_SLOT_LIST: + { + CK_BBOOL present = (CK_BBOOL)wt_pkcs11_get_u32(&reader); + has_output = wt_pkcs11_get_u32(&reader); + capacity = provider_ulong(&reader); + if (!provider_request_valid(&reader) || has_output > 1U) { + rv = CKR_ARGUMENTS_BAD; + count = 0U; + } + else { + count = capacity < WT_PKCS11_LIST_MAX ? capacity : + WT_PKCS11_LIST_MAX; + rv = C_GetSlotList(present, has_output != 0U ? slots : NULL, + &count); + } + wt_pkcs11_put_u32(&writer, (uint32_t)count); + value = (has_output != 0U && rv == CKR_OK) ? count : 0U; + wt_pkcs11_put_u32(&writer, (uint32_t)value); + for (i = 0U; i < value; i++) { + wt_pkcs11_put_u32(&writer, (uint32_t)slots[i]); + } + break; + } + case WT_P11_GET_SLOT_INFO: + { + CK_SLOT_INFO info; + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_GetSlotInfo((CK_SLOT_ID)value, &info) : CKR_ARGUMENTS_BAD; + wt_pkcs11_put_bytes(&writer, rv == CKR_OK ? &info : NULL, + rv == CKR_OK ? sizeof(info) : 0U); + break; + } + case WT_P11_GET_TOKEN_INFO: + { + CK_TOKEN_INFO info; + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_GetTokenInfo((CK_SLOT_ID)value, &info) : CKR_ARGUMENTS_BAD; + if (rv == CKR_OK) { + provider_token_pin_flags((CK_SLOT_ID)value, &info); + } + wt_pkcs11_put_bytes(&writer, rv == CKR_OK ? &info : NULL, + rv == CKR_OK ? sizeof(info) : 0U); + break; + } + case WT_P11_GET_MECHANISM_LIST: + value = provider_ulong(&reader); + has_output = wt_pkcs11_get_u32(&reader); + capacity = provider_ulong(&reader); + if (!provider_request_valid(&reader) || has_output > 1U) { + rv = CKR_ARGUMENTS_BAD; + count = 0U; + } + else { + count = WT_PKCS11_LIST_MAX; + rv = C_GetMechanismList((CK_SLOT_ID)value, mechanisms, + &count); + if (rv == CKR_BUFFER_TOO_SMALL) { + rv = CKR_DEVICE_MEMORY; + } + if (rv == CKR_OK) { + count = provider_filter_mechanisms(mechanisms, count, + (CK_SLOT_ID)value); + if (has_output != 0U && capacity < count) { + rv = CKR_BUFFER_TOO_SMALL; + } + } + else { + count = 0U; + } + } + wt_pkcs11_put_u32(&writer, (uint32_t)count); + second_value = (has_output != 0U && rv == CKR_OK) ? count : 0U; + wt_pkcs11_put_u32(&writer, (uint32_t)second_value); + for (i = 0U; i < second_value; i++) { + wt_pkcs11_put_u32(&writer, (uint32_t)mechanisms[i]); + } + break; + case WT_P11_GET_MECHANISM_INFO: + { + CK_MECHANISM_INFO info; + (void)memset(&info, 0, sizeof(info)); + value = provider_ulong(&reader); + second_value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_GetMechanismInfo((CK_SLOT_ID)value, + (CK_MECHANISM_TYPE)second_value, &info) : + CKR_ARGUMENTS_BAD; + if (rv == CKR_OK) { + info.flags = wt_profile_pkcs11_flags(info.flags); + if ((info.flags & (CKF_ENCRYPT | CKF_DECRYPT | CKF_DIGEST | + CKF_SIGN | CKF_VERIFY | CKF_SIGN_RECOVER | + CKF_VERIFY_RECOVER | CKF_GENERATE | + CKF_GENERATE_KEY_PAIR | CKF_DERIVE | CKF_WRAP | + CKF_UNWRAP)) == 0U) { + rv = CKR_MECHANISM_INVALID; + } + } + if (rv == CKR_OK && provider_mechanism_transportable( + (CK_MECHANISM_TYPE)second_value) == 0) { + (void)memset(&info, 0, sizeof(info)); + rv = CKR_MECHANISM_INVALID; + } + wt_pkcs11_put_u32(&writer, (uint32_t)info.ulMinKeySize); + wt_pkcs11_put_u32(&writer, (uint32_t)info.ulMaxKeySize); + wt_pkcs11_put_u32(&writer, (uint32_t)info.flags); + break; + } + case WT_P11_INIT_TOKEN: + value = provider_ulong(&reader); + data = provider_blob(&reader, &length); + second_data = wt_pkcs11_get_bytes(&reader, &second_wire_length); + if (second_wire_length != 32U) { + reader.error = 1; + } + rv = provider_request_valid(&reader) ? + provider_init_token((CK_SLOT_ID)value, data, length, + second_data) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_INIT_PIN: + session = provider_ulong(&reader); + data = provider_blob(&reader, &length); + rv = provider_request_valid(&reader) ? + provider_init_pin(session, data, length) : CKR_ARGUMENTS_BAD; + break; + case WT_P11_SET_PIN: + session = provider_ulong(&reader); + data = provider_blob(&reader, &length); + second_data = provider_blob(&reader, &second_length); + rv = provider_request_valid(&reader) ? + provider_set_pin(session, data, length, second_data, + second_length) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_OPEN_SESSION: + { + CK_SESSION_HANDLE opened = CK_INVALID_HANDLE; + value = provider_ulong(&reader); + second_value = provider_ulong(&reader); + has_output = wt_pkcs11_get_u32(&reader); + rv = (provider_request_valid(&reader) && has_output <= 1U) ? + C_OpenSession((CK_SLOT_ID)value, (CK_FLAGS)second_value, + NULL, NULL, has_output != 0U ? &opened : NULL) : + CKR_ARGUMENTS_BAD; + wt_pkcs11_put_u32(&writer, (uint32_t)opened); + break; + } + case WT_P11_CLOSE_SESSION: + session = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? C_CloseSession(session) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_CLOSE_ALL_SESSIONS: + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_CloseAllSessions((CK_SLOT_ID)value) : CKR_ARGUMENTS_BAD; + break; + case WT_P11_GET_SESSION_INFO: + { + CK_SESSION_INFO info; + session = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_GetSessionInfo(session, &info) : CKR_ARGUMENTS_BAD; + wt_pkcs11_put_bytes(&writer, rv == CKR_OK ? &info : NULL, + rv == CKR_OK ? sizeof(info) : 0U); + break; + } + case WT_P11_LOGIN: + session = provider_ulong(&reader); + value = provider_ulong(&reader); + data = provider_blob(&reader, &length); + rv = provider_request_valid(&reader) ? + provider_login(session, (CK_USER_TYPE)value, data, length) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_LOGOUT: + session = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? C_Logout(session) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_CREATE_OBJECT: + { + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + session = provider_ulong(&reader); + if (provider_template(&reader, attributes, &count, 0, NULL, NULL, + &provider_data_used, &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_CreateObject(session, template_ptr, count, &object); + } + provider_put_object(&writer, object); + break; + } + case WT_P11_COPY_OBJECT: + { + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + session = provider_ulong(&reader); + value = provider_ulong(&reader); + if (provider_template(&reader, attributes, &count, 0, NULL, NULL, + &provider_data_used, &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_CopyObject(session, (CK_OBJECT_HANDLE)value, + template_ptr, count, &object); + } + provider_put_object(&writer, object); + break; + } + case WT_P11_DESTROY_OBJECT: + session = provider_ulong(&reader); + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_DestroyObject(session, (CK_OBJECT_HANDLE)value) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_GET_OBJECT_SIZE: + session = provider_ulong(&reader); + value = provider_ulong(&reader); + length = 0U; + rv = provider_request_valid(&reader) ? + C_GetObjectSize(session, (CK_OBJECT_HANDLE)value, &length) : + CKR_ARGUMENTS_BAD; + provider_put_length(&writer, length); + break; + case WT_P11_GET_ATTRIBUTE_VALUE: + session = provider_ulong(&reader); + value = provider_ulong(&reader); + if (provider_template(&reader, attributes, &count, 1, + attribute_capacities, + attribute_value_requested, + &provider_data_used, &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_GetAttributeValue(session, (CK_OBJECT_HANDLE)value, + template_ptr, count); + if (rv == CKR_OK || rv == CKR_ATTRIBUTE_SENSITIVE || + rv == CKR_ATTRIBUTE_TYPE_INVALID || + rv == CKR_BUFFER_TOO_SMALL) { + attribute_wire_overhead = 4U + + ((size_t)count * WT_P11_OUTPUT_OVERHEAD); + attribute_payload_capacity = 0U; + if (writer.used <= writer.capacity && + attribute_wire_overhead <= + writer.capacity - writer.used) { + attribute_payload_capacity = writer.capacity - + writer.used - attribute_wire_overhead; + } + attribute_output_status = + provider_prepare_attribute_outputs(attributes, count, + attribute_capacities, + attribute_value_requested, + attribute_output_lengths, + attribute_payload_capacity, + &provider_data_used, &has_attribute_output); + if (attribute_output_status >= 0 && + has_attribute_output != 0) { + rv = C_GetAttributeValue(session, + (CK_OBJECT_HANDLE)value, template_ptr, count); + } + if (attribute_output_status < 0) { + rv = CKR_DEVICE_MEMORY; + } + else if (attribute_output_status > 0 && rv == CKR_OK) { + rv = CKR_BUFFER_TOO_SMALL; + } + } + } + else { + count = 0U; + } + wt_pkcs11_put_u32(&writer, (uint32_t)count); + for (i = 0U; i < count; i++) { + provider_put_length(&writer, attributes[i].ulValueLen); + if (attributes[i].pValue != NULL && + attributes[i].ulValueLen != CK_UNAVAILABLE_INFORMATION && + attributes[i].ulValueLen <= attribute_capacities[i] && + attributes[i].ulValueLen <= + attribute_output_lengths[i]) { + wt_pkcs11_put_bytes(&writer, attributes[i].pValue, + attributes[i].ulValueLen); + } + else { + wt_pkcs11_put_bytes(&writer, NULL, 0U); + } + } + break; + case WT_P11_SET_ATTRIBUTE_VALUE: + session = provider_ulong(&reader); + value = provider_ulong(&reader); + if (provider_template(&reader, attributes, &count, 0, NULL, NULL, + &provider_data_used, &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_SetAttributeValue(session, (CK_OBJECT_HANDLE)value, + template_ptr, count); + } + break; + case WT_P11_FIND_OBJECTS_INIT: + session = provider_ulong(&reader); + if (provider_template(&reader, attributes, &count, 0, NULL, NULL, + &provider_data_used, &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_FindObjectsInit(session, template_ptr, count); + } + break; + case WT_P11_FIND_OBJECTS: + session = provider_ulong(&reader); + has_output = wt_pkcs11_get_u32(&reader); + capacity = provider_ulong(&reader); + capacity = capacity < WT_PKCS11_LIST_MAX ? capacity : + WT_PKCS11_LIST_MAX; + rv = (provider_request_valid(&reader) && has_output <= 1U) ? + C_FindObjects(session, has_output != 0U ? objects : NULL, + capacity, &count) : + CKR_ARGUMENTS_BAD; + if (rv != CKR_OK) { + count = 0U; + } + wt_pkcs11_put_u32(&writer, (uint32_t)count); + for (i = 0U; i < count; i++) { + provider_put_object(&writer, objects[i]); + } + break; + case WT_P11_FIND_OBJECTS_FINAL: + session = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_FindObjectsFinal(session) : CKR_ARGUMENTS_BAD; + break; + case WT_P11_ENCRYPT_INIT: + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0) { + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_EncryptInit(session, mechanism, + (CK_OBJECT_HANDLE)value) : CKR_ARGUMENTS_BAD; + } + break; + case WT_P11_DECRYPT_INIT: + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0) { + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_DecryptInit(session, mechanism, + (CK_OBJECT_HANDLE)value) : CKR_ARGUMENTS_BAD; + } + break; + case WT_P11_DIGEST_INIT: + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0) { + rv = provider_request_valid(&reader) ? + C_DigestInit(session, mechanism) : CKR_ARGUMENTS_BAD; + } + break; + case WT_P11_SIGN_INIT: + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0) { + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_SignInit(session, mechanism, (CK_OBJECT_HANDLE)value) : + CKR_ARGUMENTS_BAD; + } + break; + case WT_P11_VERIFY_INIT: + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0) { + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_VerifyInit(session, mechanism, + (CK_OBJECT_HANDLE)value) : CKR_ARGUMENTS_BAD; + } + break; + case WT_P11_ENCRYPT: + case WT_P11_ENCRYPT_UPDATE: + case WT_P11_DECRYPT: + case WT_P11_DECRYPT_UPDATE: + case WT_P11_DIGEST: + case WT_P11_SIGN: + session = provider_ulong(&reader); + data = provider_blob(&reader, &length); + has_output = wt_pkcs11_get_u32(&reader); + capacity = provider_ulong(&reader); + wire_capacity = provider_output_capacity(&writer, capacity, + WT_P11_OUTPUT_OVERHEAD); + if (has_output > 1U || !provider_request_valid(&reader)) { + rv = CKR_ARGUMENTS_BAD; + second_length = 0U; + } + else { + second_length = has_output != 0U ? wire_capacity : 0U; + if (function == WT_P11_ENCRYPT) { + rv = C_Encrypt(session, data, length, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else if (function == WT_P11_ENCRYPT_UPDATE) { + rv = C_EncryptUpdate(session, data, length, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else if (function == WT_P11_DECRYPT) { + rv = C_Decrypt(session, data, length, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else if (function == WT_P11_DECRYPT_UPDATE) { + rv = C_DecryptUpdate(session, data, length, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else if (function == WT_P11_DIGEST) { + rv = C_Digest(session, data, length, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else { + rv = C_Sign(session, data, length, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + } + provider_put_output(&writer, rv, + has_output != 0U ? g_provider_data : NULL, + second_length, wire_capacity); + break; + case WT_P11_ENCRYPT_FINAL: + case WT_P11_DECRYPT_FINAL: + case WT_P11_DIGEST_FINAL: + case WT_P11_SIGN_FINAL: + session = provider_ulong(&reader); + data = provider_blob(&reader, &length); + has_output = wt_pkcs11_get_u32(&reader); + capacity = provider_ulong(&reader); + wire_capacity = provider_output_capacity(&writer, capacity, + WT_P11_OUTPUT_OVERHEAD); + (void)data; + if (length != 0U || has_output > 1U || + !provider_request_valid(&reader)) { + rv = CKR_ARGUMENTS_BAD; + second_length = 0U; + } + else { + second_length = has_output != 0U ? wire_capacity : 0U; + if (function == WT_P11_ENCRYPT_FINAL) { + rv = C_EncryptFinal(session, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else if (function == WT_P11_DECRYPT_FINAL) { + rv = C_DecryptFinal(session, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else if (function == WT_P11_DIGEST_FINAL) { + rv = C_DigestFinal(session, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + else { + rv = C_SignFinal(session, + has_output != 0U ? g_provider_data : NULL, + &second_length); + } + } + provider_put_output(&writer, rv, + has_output != 0U ? g_provider_data : NULL, + second_length, wire_capacity); + break; + case WT_P11_DIGEST_UPDATE: + case WT_P11_SIGN_UPDATE: + case WT_P11_VERIFY_UPDATE: + case WT_P11_VERIFY_FINAL: + case WT_P11_SEED_RANDOM: + session = provider_ulong(&reader); + data = provider_blob(&reader, &length); + if (!provider_request_valid(&reader)) { + rv = CKR_ARGUMENTS_BAD; + } + else if (function == WT_P11_DIGEST_UPDATE) { + rv = C_DigestUpdate(session, data, length); + } + else if (function == WT_P11_SIGN_UPDATE) { + rv = C_SignUpdate(session, data, length); + } + else if (function == WT_P11_VERIFY_UPDATE) { + rv = C_VerifyUpdate(session, data, length); + } + else if (function == WT_P11_VERIFY_FINAL) { + rv = C_VerifyFinal(session, data, length); + } + else { + rv = C_SeedRandom(session, data, length); + } + break; + case WT_P11_DIGEST_KEY: + session = provider_ulong(&reader); + value = provider_ulong(&reader); + rv = provider_request_valid(&reader) ? + C_DigestKey(session, (CK_OBJECT_HANDLE)value) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_VERIFY: + session = provider_ulong(&reader); + data = provider_blob(&reader, &length); + second_data = provider_blob(&reader, &second_length); + rv = provider_request_valid(&reader) ? + C_Verify(session, data, length, second_data, second_length) : + CKR_ARGUMENTS_BAD; + break; + case WT_P11_GENERATE_KEY: + { + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0 && + provider_template(&reader, attributes, &count, 0, + NULL, NULL, &provider_data_used, + &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_GenerateKey(session, mechanism, template_ptr, count, + &key); + } + provider_put_object(&writer, key); + break; + } + case WT_P11_GENERATE_KEY_PAIR: + { + CK_OBJECT_HANDLE public_key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0 && + provider_template(&reader, attributes, &count, 0, + NULL, NULL, &provider_data_used, + &template_ptr) == 0 && + provider_template(&reader, private_attributes, + &private_count, 0, NULL, NULL, + &provider_data_used, + &private_template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_GenerateKeyPair(session, mechanism, template_ptr, + count, private_template_ptr, private_count, &public_key, + &private_key); + } + provider_put_object(&writer, public_key); + provider_put_object(&writer, private_key); + break; + } + case WT_P11_DERIVE_KEY: + { + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + session = provider_ulong(&reader); + if (provider_mechanism(&reader, &decoded_mechanism, + &mechanism, &provider_data_used) == 0) { + value = provider_ulong(&reader); + if (provider_template(&reader, attributes, &count, 0, + NULL, NULL, &provider_data_used, + &template_ptr) == 0 && + provider_request_valid(&reader)) { + rv = C_DeriveKey(session, mechanism, + (CK_OBJECT_HANDLE)value, template_ptr, + count, &key); + } + } + provider_put_object(&writer, key); + break; + } + case WT_P11_GENERATE_RANDOM: + session = provider_ulong(&reader); + has_output = wt_pkcs11_get_u32(&reader); + length = provider_ulong(&reader); + wire_capacity = provider_output_capacity(&writer, length, + WT_P11_BLOB_OVERHEAD); + if (length > wire_capacity || has_output > 1U || + (has_output == 0U && length != 0U) || + !provider_request_valid(&reader)) { + rv = CKR_ARGUMENTS_BAD; + } + else { + rv = C_GenerateRandom(session, + has_output != 0U ? g_provider_data : NULL, length); + } + wt_pkcs11_put_bytes(&writer, rv == CKR_OK ? g_provider_data : NULL, + rv == CKR_OK ? length : 0U); + break; + default: + rv = CKR_FUNCTION_NOT_SUPPORTED; + break; + } + + if (reader.error != 0) { + rv = CKR_ARGUMENTS_BAD; + } + if (writer.error != 0) { + wt_forceZero(g_provider_data, sizeof(g_provider_data)); + return -1; + } + provider_response_rv(response, rv); + *response_len = writer.used; + wt_forceZero(g_provider_data, sizeof(g_provider_data)); + return 0; +} diff --git a/src/pkcs11/pkcs11_secure_heap.c b/src/pkcs11/pkcs11_secure_heap.c new file mode 100644 index 000000000..14128e45c --- /dev/null +++ b/src/pkcs11/pkcs11_secure_heap.c @@ -0,0 +1,186 @@ +/* pkcs11_secure_heap.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/zeroize.h" + +#include +#include +#include + +#ifndef WT_PKCS11_HEAP_SIZE +#define WT_PKCS11_HEAP_SIZE 0x60000U +#endif +#define WT_PKCS11_HEAP_ALIGN 16U + +typedef struct wt_pkcs11_heap_block { + size_t size; + size_t used; +} wt_pkcs11_heap_block_t; + +#define WT_PKCS11_HEAP_HEADER_SIZE \ + ((sizeof(wt_pkcs11_heap_block_t) + WT_PKCS11_HEAP_ALIGN - 1U) & \ + ~(size_t)(WT_PKCS11_HEAP_ALIGN - 1U)) + +static uint8_t g_heap[WT_PKCS11_HEAP_SIZE] __attribute__((aligned(16))); +static int g_heap_ready; + +static int heap_block_valid(const wt_pkcs11_heap_block_t* block) +{ + uintptr_t address = (uintptr_t)block; + uintptr_t begin = (uintptr_t)g_heap; + uintptr_t end = begin + sizeof(g_heap); + size_t remaining; + + if (address < begin || address > end - WT_PKCS11_HEAP_HEADER_SIZE || + ((address - begin) & (WT_PKCS11_HEAP_ALIGN - 1U)) != 0U) { + return 0; + } + remaining = (size_t)(end - address) - WT_PKCS11_HEAP_HEADER_SIZE; + if (block->size == 0U || + (block->size & (WT_PKCS11_HEAP_ALIGN - 1U)) != 0U || + block->size > remaining || block->used > 1U) { + return 0; + } + return 1; +} + +static wt_pkcs11_heap_block_t* heap_next(wt_pkcs11_heap_block_t* block) +{ + return (wt_pkcs11_heap_block_t*)(void*)((uint8_t*)block + + WT_PKCS11_HEAP_HEADER_SIZE + block->size); +} + +static wt_pkcs11_heap_block_t* heap_find_pointer(const void* pointer) +{ + wt_pkcs11_heap_block_t* block = + (wt_pkcs11_heap_block_t*)(void*)g_heap; + + while (heap_block_valid(block)) { + if ((const uint8_t*)pointer == + (const uint8_t*)block + WT_PKCS11_HEAP_HEADER_SIZE) { + return block; + } + block = heap_next(block); + } + return NULL; +} + +static void heap_init(void) +{ + wt_pkcs11_heap_block_t* first = + (wt_pkcs11_heap_block_t*)(void*)g_heap; + + first->size = sizeof(g_heap) - WT_PKCS11_HEAP_HEADER_SIZE; + first->used = 0U; + g_heap_ready = 1; +} + +void* XMALLOC(size_t size, void* heap, int type) +{ + wt_pkcs11_heap_block_t* block; + wt_pkcs11_heap_block_t* next; + wt_pkcs11_heap_block_t* split; + void* result = NULL; + + (void)heap; + (void)type; + if (g_heap_ready == 0) { + heap_init(); + } + if (size == 0U) { + size = 1U; + } + if (size > sizeof(g_heap) - WT_PKCS11_HEAP_HEADER_SIZE - + (WT_PKCS11_HEAP_ALIGN - 1U)) { + return NULL; + } + size = (size + WT_PKCS11_HEAP_ALIGN - 1U) & + ~(size_t)(WT_PKCS11_HEAP_ALIGN - 1U); + block = (wt_pkcs11_heap_block_t*)(void*)g_heap; + while (result == NULL && heap_block_valid(block)) { + if (block->used == 0U) { + next = heap_next(block); + while (heap_block_valid(next) && next->used == 0U) { + block->size += WT_PKCS11_HEAP_HEADER_SIZE + next->size; + next = heap_next(block); + } + if (block->size >= size) { + if (block->size >= size + WT_PKCS11_HEAP_HEADER_SIZE + + WT_PKCS11_HEAP_ALIGN) { + split = (wt_pkcs11_heap_block_t*)(void*)((uint8_t*)block + + WT_PKCS11_HEAP_HEADER_SIZE + size); + split->size = block->size - size - WT_PKCS11_HEAP_HEADER_SIZE; + split->used = 0U; + block->size = size; + } + block->used = 1U; + result = (uint8_t*)block + WT_PKCS11_HEAP_HEADER_SIZE; + } + } + if (result == NULL) { + block = heap_next(block); + } + } + return result; +} + +void XFREE(void* pointer, void* heap, int type) +{ + wt_pkcs11_heap_block_t* block; + + (void)heap; + (void)type; + if (pointer != NULL) { + block = heap_find_pointer(pointer); + if (block != NULL && block->used != 0U) { + wt_forceZero(pointer, block->size); + block->used = 0U; + } + } +} + +void* XREALLOC(void* pointer, size_t size, void* heap, int type) +{ + wt_pkcs11_heap_block_t* block; + void* replacement; + size_t copy_size; + + if (pointer == NULL) { + return XMALLOC(size, heap, type); + } + if (size == 0U) { + XFREE(pointer, heap, type); + return NULL; + } + block = heap_find_pointer(pointer); + if (block == NULL || block->used == 0U) { + return NULL; + } + if (block->size >= size) { + return pointer; + } + replacement = XMALLOC(size, heap, type); + if (replacement != NULL) { + copy_size = block->size < size ? block->size : size; + (void)memcpy(replacement, pointer, copy_size); + XFREE(pointer, heap, type); + } + return replacement; +} diff --git a/src/pkcs11/pkcs11_wire.c b/src/pkcs11/pkcs11_wire.c new file mode 100644 index 000000000..98c0709b1 --- /dev/null +++ b/src/pkcs11/pkcs11_wire.c @@ -0,0 +1,110 @@ +/* pkcs11_wire.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/pkcs11/pkcs11_wire.h" + +#include + +void wt_pkcs11_writer_init(wt_pkcs11_writer_t* writer, uint8_t* buffer, + size_t capacity) +{ + writer->buffer = buffer; + writer->capacity = capacity; + writer->used = 0U; + writer->error = (buffer == NULL || capacity == 0U); +} + +void wt_pkcs11_put_u32(wt_pkcs11_writer_t* writer, uint32_t value) +{ + uint8_t encoded[4]; + + encoded[0] = (uint8_t)value; + encoded[1] = (uint8_t)(value >> 8); + encoded[2] = (uint8_t)(value >> 16); + encoded[3] = (uint8_t)(value >> 24); + if (writer->error != 0 || writer->used > writer->capacity || + sizeof(encoded) > writer->capacity - writer->used) { + writer->error = 1; + return; + } + (void)memcpy(writer->buffer + writer->used, encoded, sizeof(encoded)); + writer->used += sizeof(encoded); +} + +void wt_pkcs11_put_bytes(wt_pkcs11_writer_t* writer, const void* data, + size_t length) +{ + if (length > UINT32_MAX || (data == NULL && length != 0U)) { + writer->error = 1; + return; + } + wt_pkcs11_put_u32(writer, (uint32_t)length); + if (writer->error != 0 || writer->used > writer->capacity || + length > writer->capacity - writer->used) { + writer->error = 1; + return; + } + if (length != 0U) { + (void)memcpy(writer->buffer + writer->used, data, length); + writer->used += length; + } +} + +void wt_pkcs11_reader_init(wt_pkcs11_reader_t* reader, uint8_t* buffer, + size_t length) +{ + reader->buffer = buffer; + reader->length = length; + reader->used = 0U; + reader->error = (buffer == NULL || length == 0U); +} + +uint32_t wt_pkcs11_get_u32(wt_pkcs11_reader_t* reader) +{ + const uint8_t* value; + + if (reader->error != 0 || reader->used > reader->length || + 4U > reader->length - reader->used) { + reader->error = 1; + return 0U; + } + value = reader->buffer + reader->used; + reader->used += 4U; + return (uint32_t)value[0] | ((uint32_t)value[1] << 8) | + ((uint32_t)value[2] << 16) | ((uint32_t)value[3] << 24); +} + +uint8_t* wt_pkcs11_get_bytes(wt_pkcs11_reader_t* reader, size_t* length) +{ + uint32_t encoded_length; + uint8_t* data; + + encoded_length = wt_pkcs11_get_u32(reader); + if (reader->error != 0 || reader->used > reader->length || + encoded_length > reader->length - reader->used) { + reader->error = 1; + *length = 0U; + return NULL; + } + data = reader->buffer + reader->used; + reader->used += encoded_length; + *length = encoded_length; + return data; +} diff --git a/src/services/hsm_relay_service.c b/src/services/hsm_relay_service.c index 89b684754..119d21216 100644 --- a/src/services/hsm_relay_service.c +++ b/src/services/hsm_relay_service.c @@ -20,6 +20,10 @@ #include "wolftrust/services/hsm_relay.h" #include "wolftrust/zeroize.h" +#if defined(WT_PKCS11_HSM_RESTART_PROBE) && \ + (WT_PKCS11_HSM_RESTART_PROBE == 1) +#include "wolftrust/arch.h" +#endif #include @@ -36,6 +40,10 @@ static int wt_hsm_relay_default_submit(void* submit_ctx, int32_t client_id, static wt_hsm_relay_submit_fn g_relay_submit = NULL; static void* g_relay_submit_ctx = NULL; static wt_spm_transport_fn g_relay_transport = wt_spm_transport_direct; +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +static int32_t g_relay_secure_client = 0; +#endif static int32_t g_relay_attest_client = 0; static wt_hsm_attest_sign_fn g_relay_attest_sign = NULL; static wt_hsm_attest_public_key_fn g_relay_attest_public_key = NULL; @@ -58,6 +66,14 @@ void wt_hsm_relay_set_submit(wt_hsm_relay_submit_fn fn, void* submit_ctx) g_relay_submit_ctx = (fn != NULL) ? submit_ctx : NULL; } +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +void wt_hsm_relay_set_secure_client_policy(int32_t partition_id) +{ + g_relay_secure_client = (partition_id > 0) ? partition_id : 0; +} +#endif + void wt_hsm_relay_set_transport(wt_spm_transport_fn fn) { g_relay_transport = (fn != NULL) ? fn : wt_spm_transport_direct; @@ -289,11 +305,30 @@ int wt_hsm_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, if (msg.type == PSA_IPC_CONNECT || msg.type == PSA_IPC_DISCONNECT) { reply_status = PSA_SUCCESS; - } else if (msg.type == PSA_IPC_CALL) { - /* The ordinary crypto wire serves Non-secure clients only. */ - reply_status = (msg.client_id < 0) ? - wt_hsm_relay_call(runtime, partition_id, &msg) : - PSA_ERROR_NOT_PERMITTED; + } +#if defined(WT_PKCS11_HSM_RESTART_PROBE) && \ + (WT_PKCS11_HSM_RESTART_PROBE == 1) + else if (msg.type == WT_HSM_OP_TEST_RESTART && msg.client_id < 0) { + wt_arch_sp_fault_probe(0U); + reply_status = PSA_ERROR_GENERIC_ERROR; + } +#endif + else if (msg.type == PSA_IPC_CALL) { +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) + /* Authorize before copying the request. The backend repeats the + * identity-to-namespace check before touching wolfHSM state. */ + if (msg.client_id < 0 || + (g_relay_secure_client > 0 && + msg.client_id == g_relay_secure_client)) { +#else + if (msg.client_id < 0) { +#endif + reply_status = wt_hsm_relay_call(runtime, partition_id, &msg); + } + else { + reply_status = PSA_ERROR_NOT_PERMITTED; + } } else if (msg.type == WT_HSM_OP_ATTEST_SIGN || msg.type == WT_HSM_OP_ATTEST_PUBLIC_KEY) { reply_status = wt_hsm_relay_attest_call(runtime, partition_id, &msg); diff --git a/src/services/pkcs11/pkcs11_relay_service.c b/src/services/pkcs11/pkcs11_relay_service.c new file mode 100644 index 000000000..f9393663d --- /dev/null +++ b/src/services/pkcs11/pkcs11_relay_service.c @@ -0,0 +1,372 @@ +/* pkcs11_relay_service.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/services/pkcs11_relay.h" +#include "wolftrust/zeroize.h" + +#include + +#define WT_PKCS11_SLOT_IDLE 0U +#define WT_PKCS11_SLOT_RECEIVING 1U +#define WT_PKCS11_SLOT_PROCESSING 3U +#define WT_PKCS11_SLOT_DONE 5U + +static int32_t g_relay_app_client; +static wt_spm_transport_fn g_relay_transport = wt_spm_transport_direct; +static wt_pkcs11_local_provider_fn g_local_provider; +static void* g_local_provider_context; + +#if defined(WT_PKCS11_TEST_HOOKS) +void wt_pkcs11_test_relay_chunk_zeroized(const void* memory, size_t size, + psa_status_t status); +#endif + +static struct { + uint8_t request[WT_PKCS11_MESSAGE_MAX]; + uint8_t response[WT_PKCS11_MESSAGE_MAX]; + uint32_t request_len; + uint32_t request_used; + uint32_t response_len; + uint8_t state; +} g_relay_slot; + +#if defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1) +#include "wolftrust/spm_transport.h" +WT_RESTART_PROBE_DEFINE(wt_pkcs11_relay_restart_probe) +#endif + +void wt_pkcs11_relay_set_client(int32_t app_client_id) +{ + g_relay_app_client = app_client_id; + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); +} + +void wt_pkcs11_relay_set_transport(wt_spm_transport_fn fn) +{ + g_relay_transport = (fn != NULL) ? fn : wt_spm_transport_direct; +} + +void wt_pkcs11_relay_set_local_provider( + wt_pkcs11_local_provider_fn provider, void* context) +{ + g_local_provider = provider; + g_local_provider_context = context; +} + +static int wt_pkcs11_read(wt_ffm_runtime_t* runtime, int32_t partition_id, + psa_handle_t msg_handle, void* buffer, + size_t capacity, size_t* out_len) +{ + wt_spm_call_t call; + size_t len = 0U; + + for (;;) { + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_READ; + call.partition_id = partition_id; + call.msg_handle = msg_handle; + call.vec_idx = 0U; + call.buffer = (uint8_t*)buffer + len; + call.num_bytes = capacity - len; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + if (call.ret_size == 0U) { + break; + } + len += call.ret_size; + if (len >= capacity) { + break; + } + } + *out_len = len; + return WT_FFM_SUCCESS; +} + +static int wt_pkcs11_write(wt_ffm_runtime_t* runtime, int32_t partition_id, + psa_handle_t msg_handle, const void* data, + size_t len) +{ + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_WRITE; + call.partition_id = partition_id; + call.msg_handle = msg_handle; + call.vec_idx = 0U; + call.buffer = (void*)(uintptr_t)data; + call.num_bytes = len; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS || + call.ret_int != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + return WT_FFM_SUCCESS; +} + +static psa_status_t wt_pkcs11_read_exact(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg, void* data, + size_t len) +{ + size_t got = 0U; + + if (msg->in_size[0] != len || + wt_pkcs11_read(runtime, partition_id, msg->handle, data, len, + &got) != WT_FFM_SUCCESS || got != len) { + return PSA_ERROR_INVALID_ARGUMENT; + } + return PSA_SUCCESS; +} + +static psa_status_t wt_pkcs11_write_exact(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg, + const void* data, size_t len) +{ + if (msg->out_size[0] < len) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + if (wt_pkcs11_write(runtime, partition_id, msg->handle, data, len) != + WT_FFM_SUCCESS) { + return PSA_ERROR_GENERIC_ERROR; + } + return PSA_SUCCESS; +} + +static psa_status_t wt_pkcs11_begin(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + wt_pkcs11_length_t length; + psa_status_t status; + + status = wt_pkcs11_read_exact(runtime, partition_id, msg, &length, + sizeof(length)); + if (status != PSA_SUCCESS || length.length == 0U || + length.length > WT_PKCS11_MESSAGE_MAX) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (g_relay_slot.state == WT_PKCS11_SLOT_PROCESSING) { + return PSA_ERROR_BAD_STATE; + } + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); + g_relay_slot.request_len = length.length; + g_relay_slot.state = WT_PKCS11_SLOT_RECEIVING; + return PSA_SUCCESS; +} + +static psa_status_t wt_pkcs11_write_chunk(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + wt_pkcs11_chunk_t chunk; + size_t frame_len; + psa_status_t status; + + if (msg->in_size[0] < sizeof(chunk.offset) + sizeof(chunk.length) || + msg->in_size[0] > sizeof(chunk)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + wt_forceZero(&chunk, sizeof(chunk)); + status = wt_pkcs11_read_exact(runtime, partition_id, msg, &chunk, + msg->in_size[0]); + if (status == PSA_SUCCESS) { + frame_len = sizeof(chunk.offset) + sizeof(chunk.length) + chunk.length; + if (chunk.length == 0U || chunk.length > WT_PKCS11_CHUNK_MAX || + frame_len != msg->in_size[0]) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + } + if (status == PSA_SUCCESS && + g_relay_slot.state != WT_PKCS11_SLOT_RECEIVING) { + status = PSA_ERROR_BAD_STATE; + } + if (status == PSA_SUCCESS) { + if (g_relay_slot.request_used > g_relay_slot.request_len || + chunk.offset != g_relay_slot.request_used || + chunk.length > g_relay_slot.request_len - + g_relay_slot.request_used) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + } + if (status == PSA_SUCCESS) { + (void)memcpy(g_relay_slot.request + g_relay_slot.request_used, + chunk.data, chunk.length); + g_relay_slot.request_used += chunk.length; + } + + wt_forceZero(&chunk, sizeof(chunk)); +#if defined(WT_PKCS11_TEST_HOOKS) + wt_pkcs11_test_relay_chunk_zeroized(&chunk, sizeof(chunk), status); +#endif + return status; +} + +static psa_status_t wt_pkcs11_execute(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + wt_pkcs11_length_t length; + size_t response_len = 0U; + psa_status_t status; + int rc; + + if (msg->in_size[0] != 0U || + msg->out_size[0] != sizeof(length)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (g_relay_slot.state != WT_PKCS11_SLOT_RECEIVING || + g_relay_slot.request_used != g_relay_slot.request_len) { + return PSA_ERROR_BAD_STATE; + } + g_relay_slot.state = WT_PKCS11_SLOT_PROCESSING; + rc = g_local_provider(g_relay_slot.request, g_relay_slot.request_len, + g_relay_slot.response, sizeof(g_relay_slot.response), &response_len, + g_local_provider_context); + if (rc != 0 || response_len == 0U || + response_len > sizeof(g_relay_slot.response)) { + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); + return PSA_ERROR_GENERIC_ERROR; + } + wt_forceZero(g_relay_slot.request, sizeof(g_relay_slot.request)); + g_relay_slot.request_len = 0U; + g_relay_slot.request_used = 0U; + g_relay_slot.response_len = (uint32_t)response_len; + g_relay_slot.state = WT_PKCS11_SLOT_DONE; + length.length = (uint32_t)response_len; + status = wt_pkcs11_write_exact(runtime, partition_id, msg, &length, + sizeof(length)); + if (status != PSA_SUCCESS) { + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); + } + return status; +} + +static psa_status_t wt_pkcs11_read_chunk(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + wt_pkcs11_range_t range; + const uint8_t* src; + uint32_t total; + psa_status_t status; + + status = wt_pkcs11_read_exact(runtime, partition_id, msg, &range, + sizeof(range)); + if (status != PSA_SUCCESS || range.length == 0U || + range.length > WT_PKCS11_CHUNK_MAX) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (g_relay_slot.state != WT_PKCS11_SLOT_DONE) { + return PSA_ERROR_BAD_STATE; + } + src = g_relay_slot.response; + total = g_relay_slot.response_len; + if (range.offset > total || range.length > total - range.offset) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wt_pkcs11_write_exact(runtime, partition_id, msg, + src + range.offset, range.length); + if (status == PSA_SUCCESS && + range.offset + range.length == total) { + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); + } + return status; +} + +static psa_status_t wt_pkcs11_app_call(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + switch (msg->type) { + case WT_PKCS11_OP_APP_EXECUTE: + return wt_pkcs11_execute(runtime, partition_id, msg); + case WT_PKCS11_OP_APP_BEGIN: + return wt_pkcs11_begin(runtime, partition_id, msg); + case WT_PKCS11_OP_APP_WRITE: + return wt_pkcs11_write_chunk(runtime, partition_id, msg); + case WT_PKCS11_OP_APP_READ: + return wt_pkcs11_read_chunk(runtime, partition_id, msg); + case WT_PKCS11_OP_APP_ABORT: + if (g_relay_slot.state == WT_PKCS11_SLOT_PROCESSING) { + return PSA_ERROR_BAD_STATE; + } + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); + return PSA_SUCCESS; + default: + return PSA_ERROR_NOT_SUPPORTED; + } +} + +static psa_status_t wt_pkcs11_relay_call(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + if (g_relay_app_client >= 0 || g_local_provider == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + if (msg->client_id != g_relay_app_client) { + return PSA_ERROR_NOT_PERMITTED; + } + return wt_pkcs11_app_call(runtime, partition_id, msg); +} + +int wt_pkcs11_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, + int32_t partition_id) +{ + psa_signal_t asserted = 0U; + psa_msg_t msg; + psa_status_t reply_status; + wt_spm_call_t call; + + (void)context; + if (wt_spm_wait_service_signal(g_relay_transport, runtime, partition_id, + &asserted, NULL) != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + if (asserted == 0U) { + return WT_FFM_SUCCESS; + } + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_GET; + call.partition_id = partition_id; + call.signal = asserted; + call.msg = &msg; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS || + call.ret_status != PSA_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + if (msg.type == PSA_IPC_CONNECT || msg.type == PSA_IPC_DISCONNECT) { + reply_status = PSA_SUCCESS; + } + else { + reply_status = wt_pkcs11_relay_call(runtime, partition_id, &msg); + } + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_REPLY; + call.partition_id = partition_id; + call.msg_handle = msg.handle; + call.status = reply_status; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS || + call.ret_int != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + return WT_FFM_SUCCESS; +} diff --git a/src/services/tpm/tpm_local_provider.c b/src/services/tpm/tpm_local_provider.c new file mode 100644 index 000000000..690f5ad61 --- /dev/null +++ b/src/services/tpm/tpm_local_provider.c @@ -0,0 +1,77 @@ +/* tpm_local_provider.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/services/tpm_local_provider.h" +#include "wolftrust/provider_crypto.h" + +#include +#include + +#include +#include + +static FWTPM_CTX g_tpm; + +int wt_tpm_local_provider_init(void** context) +{ + int rc; + + if (context == NULL) { + return -1; + } + *context = NULL; + (void)memset(&g_tpm, 0, sizeof(g_tpm)); + rc = FWTPM_Init(&g_tpm); + if (rc == 0) { + *context = &g_tpm; + } + return rc; +} + +int wt_tpm_local_provider_process(const uint8_t* command, + size_t command_len, + uint8_t* response, + size_t response_cap, + size_t* response_len, + void* context) +{ + FWTPM_CTX* tpm = (FWTPM_CTX*)context; + int length; + int rc; + + if (tpm == NULL || command == NULL || command_len == 0U || + command_len > (size_t)INT_MAX || response == NULL || + response_cap == 0U || response_cap > (size_t)INT_MAX || + response_len == NULL) { + return -1; + } + *response_len = 0U; + if (wt_provider_crypto_prepare() != 0) { + return -1; + } + length = (int)response_cap; + rc = FWTPM_ProcessCommand(tpm, command, (int)command_len, response, + &length, 0); + if (rc != 0 || length <= 0 || (size_t)length > response_cap) { + return -1; + } + *response_len = (size_t)length; + return 0; +} diff --git a/src/services/tpm/tpm_relay_service.c b/src/services/tpm/tpm_relay_service.c new file mode 100644 index 000000000..7d3cfdb33 --- /dev/null +++ b/src/services/tpm/tpm_relay_service.c @@ -0,0 +1,242 @@ +/* tpm_relay_service.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/services/tpm_relay.h" +#include "wolftrust/zeroize.h" + +#include + +static int32_t g_relay_app_client = 0; +static wt_spm_transport_fn g_relay_transport = wt_spm_transport_direct; +static wt_tpm_local_provider_fn g_local_provider; +static void* g_local_provider_context; + +/* The one in-flight slot, kept off the partition stack in its own band. */ +static struct { + uint8_t cmd[WT_TPM_FRAME_MAX]; + uint8_t resp[WT_TPM_FRAME_MAX]; + uint16_t cmd_len; +} g_relay_slot; + +#if defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1) +#include "wolftrust/spm_transport.h" +WT_RESTART_PROBE_DEFINE(wt_tpm_relay_restart_probe) +#endif + +void wt_tpm_relay_set_client(int32_t app_client_id) +{ + g_relay_app_client = app_client_id; + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); +} + +void wt_tpm_relay_set_transport(wt_spm_transport_fn fn) +{ + g_relay_transport = (fn != NULL) ? fn : wt_spm_transport_direct; +} + +void wt_tpm_relay_set_local_provider(wt_tpm_local_provider_fn provider, + void* context) +{ + g_local_provider = provider; + g_local_provider_context = context; +} + +/* Drain invec[0] into a bounded private buffer (copied transfers). */ +static int wt_tpm_relay_read_req(wt_ffm_runtime_t* runtime, + int32_t partition_id, + psa_handle_t msg_handle, uint8_t* buffer, + size_t capacity, size_t* out_len) +{ + wt_spm_call_t call; + size_t len = 0U; + + for (;;) { + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_READ; + call.partition_id = partition_id; + call.msg_handle = msg_handle; + call.vec_idx = 0U; + call.buffer = buffer + len; + call.num_bytes = capacity - len; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + if (call.ret_size == 0U) { + break; + } + len += call.ret_size; + if (len >= capacity) { + break; + } + } + *out_len = len; + return WT_FFM_SUCCESS; +} + +static int wt_tpm_relay_write_resp(wt_ffm_runtime_t* runtime, + int32_t partition_id, + psa_handle_t msg_handle, const void* data, + size_t len) +{ + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_WRITE; + call.partition_id = partition_id; + call.msg_handle = msg_handle; + call.vec_idx = 0U; + call.buffer = (void*)(uintptr_t)data; + call.num_bytes = len; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS || + call.ret_int != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + return WT_FFM_SUCCESS; +} + +/* Copy one inbound frame into dst; the frame must be 1..WT_TPM_FRAME_MAX. */ +static psa_status_t wt_tpm_relay_take_frame(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg, + uint8_t* dst, uint16_t* dst_len) +{ + size_t len = 0U; + + if (msg->in_size[0] == 0U || msg->in_size[0] > WT_TPM_FRAME_MAX) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (wt_tpm_relay_read_req(runtime, partition_id, msg->handle, dst, + WT_TPM_FRAME_MAX, &len) != WT_FFM_SUCCESS || + len != msg->in_size[0]) { + return PSA_ERROR_INVALID_ARGUMENT; + } + *dst_len = (uint16_t)len; + return PSA_SUCCESS; +} + +/* Deliver one outbound frame into outvec[0], which must hold it whole. */ +static psa_status_t wt_tpm_relay_give_frame(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg, + const uint8_t* src, + uint16_t src_len) +{ + if (msg->out_size[0] < (size_t)src_len) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (wt_tpm_relay_write_resp(runtime, partition_id, msg->handle, src, + src_len) != WT_FFM_SUCCESS) { + return PSA_ERROR_GENERIC_ERROR; + } + return PSA_SUCCESS; +} + +static psa_status_t wt_tpm_transact(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + psa_status_t status; + size_t response_len = 0U; + int rc; + + if (g_local_provider == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + if (msg->out_size[0] == 0U || msg->out_size[0] > WT_TPM_FRAME_MAX) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wt_tpm_relay_take_frame(runtime, partition_id, msg, + g_relay_slot.cmd, &g_relay_slot.cmd_len); + if (status == PSA_SUCCESS) { + rc = g_local_provider(g_relay_slot.cmd, g_relay_slot.cmd_len, + g_relay_slot.resp, sizeof(g_relay_slot.resp), &response_len, + g_local_provider_context); + if (rc != 0 || response_len == 0U || response_len > WT_TPM_FRAME_MAX) { + status = PSA_ERROR_GENERIC_ERROR; + } + else { + status = wt_tpm_relay_give_frame(runtime, partition_id, msg, + g_relay_slot.resp, (uint16_t)response_len); + } + } + wt_forceZero(&g_relay_slot, sizeof(g_relay_slot)); + return status; +} + +static psa_status_t wt_tpm_relay_call(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + if (g_relay_app_client >= 0 || g_local_provider == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + if (msg->client_id != g_relay_app_client) { + return PSA_ERROR_NOT_PERMITTED; + } + if (msg->type == WT_TPM_OP_TRANSACT) { + return wt_tpm_transact(runtime, partition_id, msg); + } + return PSA_ERROR_NOT_SUPPORTED; +} + +int wt_tpm_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, + int32_t partition_id) +{ + psa_signal_t asserted = 0U; + psa_msg_t msg; + psa_status_t reply_status; + wt_spm_call_t call; + + (void)context; + if (wt_spm_wait_service_signal(g_relay_transport, runtime, partition_id, + &asserted, NULL) != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + if (asserted == 0U) { + return WT_FFM_SUCCESS; + } + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_GET; + call.partition_id = partition_id; + call.signal = asserted; + call.msg = &msg; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS || + call.ret_status != PSA_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + + if (msg.type == PSA_IPC_CONNECT || msg.type == PSA_IPC_DISCONNECT) { + reply_status = PSA_SUCCESS; + } else { + reply_status = wt_tpm_relay_call(runtime, partition_id, &msg); + } + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_REPLY; + call.partition_id = partition_id; + call.msg_handle = msg.handle; + call.status = reply_status; + if (g_relay_transport(runtime, &call) != WT_FFM_SUCCESS || + call.ret_int != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } + return WT_FFM_SUCCESS; +} diff --git a/src/services/wolfhsm/runner/secure.ld b/src/services/wolfhsm/runner/secure.ld index 197105b86..47c781c5d 100644 --- a/src/services/wolfhsm/runner/secure.ld +++ b/src/services/wolfhsm/runner/secure.ld @@ -68,6 +68,12 @@ MEMORY { * RAM window, reserved so each Secure Partition runs on its own secure * stack the MPU can confine. Mirrors WT_SP_SECURE_RAM_* in memory_map.h. */ SPSTACKS (rw): ORIGIN = 0x30096000, LENGTH = 40K + /* SERVICE_TPM mailbox band (CONFIG_TPM builds): the command/response mailbox + * the confined fwTPM-relay partition touches in-thread, carved from the + * otherwise-empty upper CONFDATA window (CONFIG_TPM, CONFIG_VNET, and + * WT_CONFORMANCE are mutually exclusive). Empty outside CONFIG_TPM builds. + * Mirrors WT_TPM_DATA_* in port/stm32h563/memory_map.h. */ + TPMDATA (rw): ORIGIN = 0x30095000, LENGTH = 2K } _estack = ORIGIN(RAM) + LENGTH(RAM); @@ -77,6 +83,14 @@ _wt_part_stacks_base = ORIGIN(FWUSTACK); _wt_part_stacks_limit = ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK); _wt_sp_stacks_base = ORIGIN(SPSTACKS); _wt_sp_stacks_limit = ORIGIN(SPSTACKS) + LENGTH(SPSTACKS); +_wt_tpm_stack_base = ORIGIN(VNETDATA) + + ((SIZEOF(.tpm_data) + SIZEOF(.tpm_bss)) != 0 ? 48K : 0); +_wt_tpm_stack_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.tpm_data) + SIZEOF(.tpm_bss)) != 0 ? 64K : 0); +_wt_pkcs11_stack_base = ORIGIN(VNETDATA) + + ((SIZEOF(.pkcs11_data) + SIZEOF(.pkcs11_bss)) != 0 ? 48K : 0); +_wt_pkcs11_stack_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.pkcs11_data) + SIZEOF(.pkcs11_bss)) != 0 ? 64K : 0); _sidata = LOADADDR(.data); SECTIONS { @@ -121,6 +135,7 @@ SECTIONS { * tools/check_secure_layout.py fails the link if a setter of them is * referenced (wc_Sha256Drbg_Enable/Disable). */ *(.data.always_prefetch* .bss.always_prefetch*) + *(.data.never_prefetch* .bss.never_prefetch*) *(.data.sha256DrbgDisabled* .bss.sha256DrbgDisabled*) *(.rodata*) *(.ARM.extab* .gnu.linkonce.armextab.*) @@ -284,6 +299,56 @@ SECTIONS { _e_vnet = .; } > VNETDATA + /* SERVICE_PKCS11 copied request and response slot. CONFIG_PKCS11 and + * CONFIG_VNET are mutually exclusive, so the selected relay owns this + * entire auxiliary band. */ + .pkcs11_data : { + . = ALIGN(32); + _s_pkcs11 = .; + *wt_sec_pkcs11*.o(.data .data.*) + *p11_sec_*.o(.data .data.*) + *p11_wc_*.o(.data .data.*) + *p11_wh_*.o(.data .data.*) + . = ALIGN(4); + _e_pkcs11_data = .; + } > VNETDATA AT > FLASH + _si_pkcs11 = LOADADDR(.pkcs11_data); + + .pkcs11_bss (NOLOAD) : { + _s_pkcs11_bss = .; + *wt_sec_pkcs11*.o(.bss .bss.* COMMON) + *p11_sec_*.o(.bss .bss.* COMMON) + *p11_wc_*.o(.bss .bss.* COMMON) + *p11_wh_*.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_pkcs11 = .; + } > VNETDATA + + /* SERVICE_TPM mailbox band: first-claim the fwTPM-relay partition's mailbox + * and wiring out of shared SPM .data/.bss so its MPU grant covers exactly its + * own state (isolation level 3). Empty outside CONFIG_TPM builds. */ + .tpm_data : { + . = ALIGN(32); + _s_tpm = .; + *wt_sec_tpm*.o(.data .data.*) + *tpm_sec_*.o(.data .data.*) + *tpm_wc_*.o(.data .data.*) + *tpm_wh_*.o(.data .data.*) + . = ALIGN(4); + _e_tpm_data = .; + } > VNETDATA AT > FLASH + _si_tpm = LOADADDR(.tpm_data); + + .tpm_bss (NOLOAD) : { + _s_tpm_bss = .; + *wt_sec_tpm*.o(.bss .bss.* COMMON) + *tpm_sec_*.o(.bss .bss.* COMMON) + *tpm_wc_*.o(.bss .bss.* COMMON) + *tpm_wh_*.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_tpm = .; + } > VNETDATA + .data : { _sdata = .; *(.data*) @@ -307,6 +372,8 @@ SECTIONS { "vault data band must sit directly above the vnet data band") ASSERT(_e_vnet <= (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), "vnet data band overflow") + ASSERT(_e_pkcs11 <= (ORIGIN(VNETDATA) + 48K), + "pkcs11 data band overflow") ASSERT(ORIGIN(ATTESTDATA) == (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), "attestation data band must sit directly above the vault data band") ASSERT(ORIGIN(HSMDATA) == (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), @@ -323,6 +390,12 @@ SECTIONS { _wt_band_hsm_limit = ORIGIN(HSMDATA) + LENGTH(HSMDATA); _wt_band_vnet_base = ORIGIN(VNETDATA); _wt_band_vnet_limit = ORIGIN(VNETDATA) + LENGTH(VNETDATA); + _wt_band_pkcs11_base = ORIGIN(VNETDATA); + _wt_band_pkcs11_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.pkcs11_data) + SIZEOF(.pkcs11_bss)) != 0 ? 48K : 0); + _wt_band_tpm_base = ORIGIN(VNETDATA); + _wt_band_tpm_limit = ORIGIN(VNETDATA) + + ((SIZEOF(.tpm_data) + SIZEOF(.tpm_bss)) != 0 ? 48K : 0); ASSERT(_e_vault <= (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), "vault data band overflow") diff --git a/src/services/wolfhsm/runner/user_settings.h b/src/services/wolfhsm/runner/user_settings.h index b84f2bf08..5bf652154 100644 --- a/src/services/wolfhsm/runner/user_settings.h +++ b/src/services/wolfhsm/runner/user_settings.h @@ -55,13 +55,21 @@ * NO_STDLIB_H and NO_STRING_H keep the freestanding Cortex-M build * independent of libc headers; the linked local stubs provide these calls. * ---------------------------------------------------------------------- */ +#if defined(CONFIG_PKCS11) && defined(WT_INTERFACE_LOCAL_PROVIDER) +#define XMALLOC_USER +#else #define NO_WOLFSSL_MEMORY #define WOLFSSL_NO_MALLOC +#endif #define NO_STDLIB_H #define NO_STRING_H #define NO_CTYPE_H #define WOLFSSL_NO_ASSERT_H +#if defined(WOLFTPM_FWTPM) && defined(WT_INTERFACE_LOCAL_PROVIDER) +#define XSLEEP_MS(ms) do { (void)(ms); } while (0) +#endif + #include "libc_stubs.h" /* ------------------------------------------------------------------------- @@ -133,6 +141,14 @@ * ---------------------------------------------------------------------- */ #define HAVE_AESGCM #define GCM_SMALL +#if defined(WT_INTERFACE_LOCAL_PROVIDER) || \ + (defined(WT_ENGINE_HSM) && (defined(CONFIG_TPM) || defined(CONFIG_PKCS11))) +#define WOLFSSL_AES_CFB +#define WOLFSSL_AES_COUNTER +#define HAVE_AESCTR +#define HAVE_AESCCM +#define WOLFSSL_ECDSA_DETERMINISTIC_K +#endif /* ------------------------------------------------------------------------- * Hash: SHA-256. @@ -192,7 +208,13 @@ int wolftrust_rng_generate_block(unsigned char *output, unsigned int sz); #define NO_DSA #define NO_DES3 #define NO_MD5 +#if defined(CONFIG_PKCS11) && defined(WT_INTERFACE_LOCAL_PROVIDER) +#define HAVE_PBKDF2 +#define WOLFPKCS11_PBKDF2 +#define WOLFPKCS11_PIN_KDF_SOFTWARE +#else #define NO_PWDBASED +#endif #define NO_PKCS12 #define NO_CODING /* base-64 encode/decode not needed */ #define NO_ASN_TIME /* no RTC on secure side */ diff --git a/src/services/wolfhsm/runner/wh_settings_local.h b/src/services/wolfhsm/runner/wh_settings_local.h index 6bf919c98..ed31f13e9 100644 --- a/src/services/wolfhsm/runner/wh_settings_local.h +++ b/src/services/wolfhsm/runner/wh_settings_local.h @@ -70,6 +70,10 @@ * (each guest links its own client subset on the non-secure side). *---------------------------------------------------------------------------*/ #define WOLFHSM_CFG_ENABLE_SERVER +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +#define WOLFHSM_CFG_ENABLE_CLIENT +#endif /*--------------------------------------------------------------------------- * Crypto diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index 65a84505e..97cd99abe 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -77,6 +77,7 @@ #include "wolftrust/sync/mutex.h" #include "wolftrust/nvm_store.h" #include "wolftrust/services/nvm_client.h" +#include "wolftrust/provider_crypto.h" #include "wolftrust/services/hsm.h" #include "wolftrust/services/hsm_relay.h" #include "wolftrust/services/vault_service.h" @@ -123,7 +124,13 @@ typedef struct wt_hsm_guest { bool ready; } wt_hsm_guest_t; -static wt_hsm_guest_t g_guests[WT_MAX_GUESTS]; +static wt_hsm_guest_t g_guests[WT_HSM_NAMESPACE_COUNT]; +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +static int32_t g_secure_client_id; +static wt_guest_id_t g_secure_namespace_id; +static bool g_secure_client_bound; +#endif /* The store every server binds to: the direct vault store during the * privileged bootstrap (IAK provisioning, rollback floors), then the crypto @@ -276,7 +283,7 @@ static int wt_hsm_guest_init(wt_guest_id_t guest_id) /* ------------------------------------------------------------------ * 1. Bounds + duplicate check. * ---------------------------------------------------------------- */ - if (guest_id >= WT_MAX_GUESTS) { + if (guest_id >= WT_HSM_NAMESPACE_COUNT) { return WH_ERROR_BADARGS; } g = &g_guests[guest_id]; @@ -343,19 +350,21 @@ static int wt_hsm_guest_init(wt_guest_id_t guest_id) /* ------------------------------------------------------------------ * 7. Create tasklet. * ---------------------------------------------------------------- */ - tasklet = wt_hsm_guest_tasklet(guest_id); - if (tasklet == NULL) { - tasklet = wt_tasklet_create_blocked(wt_hsm_priv_stack(guest_id), - WT_CO_STACK_SIZE, - wt_hsm_tasklet_main, - (void *)(uintptr_t)guest_id); - } - if (tasklet == NULL) { - wh_Server_Cleanup(&g->server); - wc_FreeRng(g->crypto.rng); - return WH_ERROR_ABORTED; + if (guest_id < WT_MAX_GUESTS) { + tasklet = wt_hsm_guest_tasklet(guest_id); + if (tasklet == NULL) { + tasklet = wt_tasklet_create_blocked(wt_hsm_priv_stack(guest_id), + WT_CO_STACK_SIZE, + wt_hsm_tasklet_main, + (void *)(uintptr_t)guest_id); + } + if (tasklet == NULL) { + wh_Server_Cleanup(&g->server); + wc_FreeRng(g->crypto.rng); + return WH_ERROR_ABORTED; + } + wt_hsm_priv_register(guest_id, tasklet); } - wt_hsm_priv_register(guest_id, tasklet); /* ------------------------------------------------------------------ * 8. Mark guest ready. @@ -386,7 +395,7 @@ typedef struct wt_hsm_relay_buf { uint8_t resp_ready; } wt_hsm_relay_buf_t; -static wt_hsm_relay_buf_t g_relay_bufs[WT_MAX_GUESTS]; +static wt_hsm_relay_buf_t g_relay_bufs[WT_HSM_NAMESPACE_COUNT]; static int wt_hsm_relay_srv_init(void* context, const void* config, whCommSetConnectedCb connectcb, @@ -528,12 +537,29 @@ static void wt_hsm_bind_server_cfg(wt_guest_id_t guest_id) int wt_hsm_guest_init_relay(wt_guest_id_t guest_id) { - if (guest_id >= WT_MAX_GUESTS) { + if (guest_id >= WT_HSM_NAMESPACE_COUNT) { return WH_ERROR_BADARGS; } return wt_hsm_guest_init(guest_id); } +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) +int wt_hsm_relay_set_secure_client(int32_t partition_id, + wt_guest_id_t namespace_id) +{ + if (partition_id <= 0 || namespace_id < WT_MAX_GUESTS || + namespace_id >= WT_HSM_NAMESPACE_COUNT) { + return WH_ERROR_BADARGS; + } + wt_hsm_relay_set_secure_client_policy(partition_id); + g_secure_client_id = partition_id; + g_secure_namespace_id = namespace_id; + g_secure_client_bound = true; + return WH_ERROR_OK; +} +#endif + #if defined(WT_HSM_PIN_NEG_PROBE) && (WT_HSM_PIN_NEG_PROBE == 1) /* Forge every pointer the server pump follows to an SPM-private address the * unprivileged relay cannot read, so wt_hsm_server_pin must restore them before @@ -566,13 +592,27 @@ int wt_hsm_relay_submit(void* submit_ctx, int32_t client_id, int rc = WH_ERROR_OK; (void)submit_ctx; - if (req == NULL || resp == NULL || resp_len == NULL || client_id >= 0) { + if (req == NULL || resp == NULL || resp_len == NULL) { return WH_ERROR_BADARGS; } - /* The SPM stamps NS callers as -(guest + 1); the mapping mirrors - * wt_ffm_boot_caller_guest. */ - gid = (wt_guest_id_t)(-client_id - 1); - if (gid >= WT_MAX_GUESTS) { + if (client_id < 0) { + /* The SPM stamps NS callers as -(guest + 1); the mapping mirrors + * wt_ffm_boot_caller_guest. */ + gid = (wt_guest_id_t)(-(client_id + 1)); + if (gid >= WT_MAX_GUESTS) { + return WH_ERROR_BADARGS; + } + } +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) + else if (g_secure_client_bound && client_id == g_secure_client_id) { + gid = g_secure_namespace_id; + } +#endif + else { + return WH_ERROR_ACCESS; + } + if (gid >= WT_HSM_NAMESPACE_COUNT) { return WH_ERROR_BADARGS; } g = &g_guests[gid]; @@ -641,7 +681,7 @@ int wt_hsm_relay_submit(void* submit_ctx, int32_t client_id, * ====================================================================== */ bool wt_hsm_guest_ready(wt_guest_id_t guest_id) { - if (guest_id >= WT_MAX_GUESTS) { + if (guest_id >= WT_HSM_NAMESPACE_COUNT) { return false; } return g_guests[guest_id].ready; @@ -726,7 +766,7 @@ int wt_hsm_relay_reinit_servers(void) wt_tasklet_t *tasklet; wt_guest_id_t gid; - for (gid = 0; gid < WT_MAX_GUESTS; gid++) { + for (gid = 0; gid < WT_HSM_NAMESPACE_COUNT; gid++) { g = &g_guests[gid]; if (!g->ready) { continue; @@ -781,7 +821,7 @@ int wt_hsm_bind_nvm(whNvmContext* nvm) return WH_ERROR_BADARGS; } g_hsm_nvm = nvm; - for (gid = 0; gid < WT_MAX_GUESTS; gid++) { + for (gid = 0; gid < WT_HSM_NAMESPACE_COUNT; gid++) { g_guests[gid].server_cfg.nvm = nvm; } g_hsm_attest_server_cfg.nvm = nvm; diff --git a/src/spm_partitions.c b/src/spm_partitions.c index 0b2111fa7..b725ec773 100644 --- a/src/spm_partitions.c +++ b/src/spm_partitions.c @@ -23,6 +23,9 @@ * transport. Deliberate test faults go through wt_arch_sp_fault_probe(). */ #include "wolftrust/spm_transport.h" +#if defined(WT_INTERFACE_LOCAL_PROVIDER) +#include "wolftrust/provider_crypto.h" +#endif #include "wolftrust/ffm.h" #include "wolftrust/ffm_domain.h" @@ -57,6 +60,16 @@ #include "wolftrust/services/vnet_relay.h" #include "wolftrust/services/vnet_service.h" #endif +#if defined(CONFIG_TPM) +#include "wolftrust/services/tpm_relay.h" +#if defined(WT_INTERFACE_LOCAL_PROVIDER) +#include "wolftrust/services/tpm_local_provider.h" +#endif +#endif +#if defined(CONFIG_PKCS11) +#include "wolftrust/pkcs11_provider.h" +#include "wolftrust/services/pkcs11_relay.h" +#endif #include "wolftrust/sp_recovery.h" #include "wolftrust/spm_gate.h" @@ -118,6 +131,108 @@ static void wt_spm_band_probe(void* arg, unsigned int target) ~(intptr_t)WT_SP_FAULT_PROBE_SECOND)) #endif +#if defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1) +#define WT_TPM_PROBE_ID(arg) \ + ((int32_t)((intptr_t)(arg) & ~(intptr_t)WT_SP_FAULT_PROBE_RESTARTED & \ + ~(intptr_t)WT_SP_FAULT_PROBE_SECOND)) + +/* Prove that an unrelated partition cannot read or write TPMDATA. */ +static void wt_spm_tpm_band_probe(void* arg) +{ + volatile uint32_t* band = (volatile uint32_t*) + wt_platform_probe_address(WT_PROBE_TPM_DATA_BAND); + volatile uint32_t seen; + + if (((intptr_t)arg & WT_SP_FAULT_PROBE_RESTARTED) == 0) { + seen = *band; + (void)seen; + wt_arch_sp_fault_probe(3u); + } + else if (((intptr_t)arg & WT_SP_FAULT_PROBE_SECOND) == 0) { + *band = 0xA5A5A5A5u; + wt_arch_sp_fault_probe(4u); + } +} + +/* Prove that the relay cannot bypass IPC or execute from its data band. */ +static void wt_spm_tpm_partition_probe(void* arg) +{ + volatile uint32_t seen; + void (*xn_probe)(void); + + if (((intptr_t)arg & WT_SP_FAULT_PROBE_RESTARTED) == 0) { + if (wt_tpm_relay_restart_probe(1) == 0) { + wt_arch_sp_fault_probe(2u); + } + (void)wt_tpm_relay_restart_probe(0); + seen = *(const volatile uint32_t*) + wt_platform_probe_address(WT_PROBE_OUT_OF_DOMAIN); + (void)seen; + wt_arch_sp_fault_probe(3u); + } + else if (((intptr_t)arg & WT_SP_FAULT_PROBE_SECOND) == 0) { + if (wt_tpm_relay_restart_probe(1) == 0) { + wt_arch_sp_fault_probe(2u); + } + xn_probe = (void (*)(void)) + (wt_platform_probe_address(WT_PROBE_TPM_DATA_BAND) | 1u); + xn_probe(); + wt_arch_sp_fault_probe(4u); + } +} +#endif + +#if defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1) +#define WT_PKCS11_PROBE_ID(arg) \ + ((int32_t)((intptr_t)(arg) & ~(intptr_t)WT_SP_FAULT_PROBE_RESTARTED & \ + ~(intptr_t)WT_SP_FAULT_PROBE_SECOND)) + +/* Prove that an unrelated partition cannot read or write PKCS11DATA. */ +static void wt_spm_pkcs11_band_probe(void* arg) +{ + volatile uint32_t* band = (volatile uint32_t*) + wt_platform_probe_address(WT_PROBE_PKCS11_DATA_BAND); + volatile uint32_t seen; + + if (((intptr_t)arg & WT_SP_FAULT_PROBE_RESTARTED) == 0) { + seen = *band; + (void)seen; + wt_arch_sp_fault_probe(3u); + } + else if (((intptr_t)arg & WT_SP_FAULT_PROBE_SECOND) == 0) { + *band = 0xA5A5A5A5u; + wt_arch_sp_fault_probe(4u); + } +} + +/* Prove that the relay cannot bypass IPC or execute from its data band. */ +static void wt_spm_pkcs11_partition_probe(void* arg) +{ + volatile uint32_t seen; + void (*xn_probe)(void); + + if (((intptr_t)arg & WT_SP_FAULT_PROBE_RESTARTED) == 0) { + if (wt_pkcs11_relay_restart_probe(1) == 0) { + wt_arch_sp_fault_probe(2u); + } + (void)wt_pkcs11_relay_restart_probe(0); + seen = *(const volatile uint32_t*) + wt_platform_probe_address(WT_PROBE_OUT_OF_DOMAIN); + (void)seen; + wt_arch_sp_fault_probe(3u); + } + else if (((intptr_t)arg & WT_SP_FAULT_PROBE_SECOND) == 0) { + if (wt_pkcs11_relay_restart_probe(1) == 0) { + wt_arch_sp_fault_probe(2u); + } + xn_probe = (void (*)(void)) + (wt_platform_probe_address(WT_PROBE_PKCS11_DATA_BAND) | 1u); + xn_probe(); + wt_arch_sp_fault_probe(4u); + } +} +#endif + #if defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0) /* Restart proof (WT-FFM-0051): the partition plants state in its own band * and faults; its restarted instance must find the band's link-time image. @@ -180,6 +295,14 @@ static void wt_spm_hsm_entry(void* arg) wt_spm_band_probe(arg, WT_PROBE_ATTEST_DATA_BAND); #endif #endif +#if defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1) + partition_id = WT_TPM_PROBE_ID(arg); + wt_spm_tpm_band_probe(arg); +#endif +#if defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1) + partition_id = WT_PKCS11_PROBE_ID(arg); + wt_spm_pkcs11_band_probe(arg); +#endif #if defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0) partition_id = WT_RESTART_PROBE_ID(arg); #if (WT_RESTART_NEG_PROBE == 1) @@ -206,6 +329,19 @@ static uint8_t g_spm_iak_public[WT_HSM_ATTEST_PUBLIC_KEY_LEN]; static size_t g_spm_iak_public_len; #endif +#if defined(WT_ENGINE_HSM) && (defined(CONFIG_PKCS11) || \ + defined(CONFIG_TPM)) && defined(WT_INTERFACE_LOCAL_PROVIDER) +static int wt_spm_hsm_init_provider_namespace(void) +{ + if (!wt_hsm_guest_ready((wt_guest_id_t)WT_PROVIDER_HSM_NAMESPACE) && + wt_hsm_guest_init_relay( + (wt_guest_id_t)WT_PROVIDER_HSM_NAMESPACE) != 0) { + return WT_FFM_ERROR_STATE; + } + return WT_FFM_SUCCESS; +} +#endif + /* Wire the crypto partition's band to the relay seams and to the vault's * door; boot and every restart run the same steps. */ static int wt_spm_hsm_bind(int32_t partition_id) @@ -222,6 +358,18 @@ static int wt_spm_hsm_bind(int32_t partition_id) if (wt_hsm_bind_nvm(wt_nvm_client_partition_nvm()) != 0) { return WT_FFM_ERROR_STATE; } +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) + if (wt_hsm_relay_set_secure_client( +#if defined(CONFIG_PKCS11) + PARTITION_PKCS11_ID, +#else + PARTITION_TPM_ID, +#endif + (wt_guest_id_t)WT_PROVIDER_HSM_NAMESPACE) != 0) { + return WT_FFM_ERROR_STATE; + } +#endif wt_hsm_relay_set_submit(wt_hsm_relay_submit, NULL); #else /* Native engine: SERVICE_HSM stays the single mediated door; its packets @@ -252,6 +400,12 @@ static int wt_spm_hsm_restore(int32_t partition_id) return WT_FFM_ERROR_STATE; } } +#if (defined(CONFIG_PKCS11) || defined(CONFIG_TPM)) && \ + defined(WT_INTERFACE_LOCAL_PROVIDER) + if (wt_spm_hsm_init_provider_namespace() != WT_FFM_SUCCESS) { + return WT_FFM_ERROR_STATE; + } +#endif #endif #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) if (wt_hsm_attest_restore((g_spm_iak_public_len != 0u) ? @@ -267,6 +421,13 @@ int wt_spm_hsm_start(wt_ffm_runtime_t* runtime, int32_t partition_id) { int ret; +#if defined(WT_ENGINE_HSM) && (defined(CONFIG_PKCS11) || \ + defined(CONFIG_TPM)) && defined(WT_INTERFACE_LOCAL_PROVIDER) + ret = wt_spm_hsm_init_provider_namespace(); + if (ret != WT_FFM_SUCCESS) { + return ret; + } +#endif #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) /* Taken before the store is rebound to the door view, which the boot * context cannot call through. */ @@ -761,3 +922,91 @@ int wt_spm_vnet_start(wt_ffm_runtime_t* runtime, int32_t partition_id) (void*)(intptr_t)partition_id); } #endif /* CONFIG_VNET */ + +#if defined(CONFIG_TPM) +#ifndef WT_TPM_APP_GUEST +#define WT_TPM_APP_GUEST 0 +#endif +/* The ordinary Secure partition runs fwTPM in its own protection domain. */ +static void wt_spm_tpm_entry(void* arg) +{ + int32_t partition_id = (int32_t)(intptr_t)arg; +#if defined(WT_INTERFACE_LOCAL_PROVIDER) + void* provider_context = NULL; +#endif + +#if defined(WT_TPM_NEG_PROBE) && (WT_TPM_NEG_PROBE == 1) + partition_id = WT_TPM_PROBE_ID(arg); + wt_spm_tpm_partition_probe(arg); +#endif + wt_tpm_relay_set_transport(wt_spm_svc_transport); +#if defined(WT_INTERFACE_LOCAL_PROVIDER) + if (wt_provider_crypto_init() != 0 || + wt_tpm_local_provider_init(&provider_context) != 0) { + psa_panic(); + } + wt_tpm_relay_set_local_provider(wt_tpm_local_provider_process, + provider_context); + wt_tpm_relay_set_client(-(WT_TPM_APP_GUEST + 1)); +#endif + for (;;) { + (void)wt_tpm_relay_dispatch(NULL, NULL, partition_id); + } +} + +int wt_spm_tpm_start(wt_ffm_runtime_t* runtime, int32_t partition_id) +{ + return wt_spm_sched_add(runtime, partition_id, wt_spm_tpm_entry, + (void*)(intptr_t)partition_id); +} +#endif /* CONFIG_TPM */ + +#if defined(CONFIG_PKCS11) +#ifndef WT_PKCS11_APP_GUEST +#define WT_PKCS11_APP_GUEST 0 +#endif + +#if defined(WT_INTERFACE_LOCAL_PROVIDER) +static int wt_spm_pkcs11_local_dispatch(uint8_t* request, + size_t request_len, + uint8_t* response, + size_t response_cap, + size_t* response_len, + void* context) +{ + if (wt_provider_crypto_prepare() != 0) { + *response_len = 0U; + return -1; + } + return wt_pkcs11_provider_dispatch(request, request_len, response, + response_cap, response_len, context); +} +#endif + +static void wt_spm_pkcs11_entry(void* arg) +{ + int32_t partition_id = (int32_t)(intptr_t)arg; + +#if defined(WT_PKCS11_NEG_PROBE) && (WT_PKCS11_NEG_PROBE == 1) + partition_id = WT_PKCS11_PROBE_ID(arg); + wt_spm_pkcs11_partition_probe(arg); +#endif + wt_pkcs11_relay_set_transport(wt_spm_svc_transport); +#if defined(WT_INTERFACE_LOCAL_PROVIDER) + if (wt_provider_crypto_init() != 0) { + psa_panic(); + } + wt_pkcs11_relay_set_local_provider(wt_spm_pkcs11_local_dispatch, NULL); + wt_pkcs11_relay_set_client(-(WT_PKCS11_APP_GUEST + 1)); +#endif + for (;;) { + (void)wt_pkcs11_relay_dispatch(NULL, NULL, partition_id); + } +} + +int wt_spm_pkcs11_start(wt_ffm_runtime_t* runtime, int32_t partition_id) +{ + return wt_spm_sched_add(runtime, partition_id, wt_spm_pkcs11_entry, + (void*)(intptr_t)partition_id); +} +#endif /* CONFIG_PKCS11 */ diff --git a/tests/firmware/aarch64-ns-smoke/Makefile b/tests/firmware/aarch64-ns-smoke/Makefile index 3610508f8..cb0bb2187 100644 --- a/tests/firmware/aarch64-ns-smoke/Makefile +++ b/tests/firmware/aarch64-ns-smoke/Makefile @@ -1,3 +1,5 @@ +.DEFAULT_GOAL := all + # Makefile # # Copyright (C) 2026 wolfSSL Inc. @@ -26,6 +28,14 @@ TOOLPREFIX ?= aarch64-none-elf- MACHINE ?= virt WT_NS_BASE ?= 0x40000000 WT_CONF_SUITE ?= ipc +WT_NS_GUEST_TPM ?= 0 +WT_NS_GUEST_PKCS11 ?= 0 +WT_NS_PKCS11_HSM_RESTART_PROBE ?= 0 +ifneq ($(filter 1,$(WT_NS_GUEST_TPM) $(WT_NS_GUEST_PKCS11)),) +WT_NS_IMG_SIZE ?= 0x100000 +WT_NS_RAM_SIZE ?= 0x100000 +WT_NS_STACK_SIZE ?= 0x40000 +endif # The crypto and attestation suites link wolfPSA, wolfCrypt, and a heap. ifneq ($(filter crypto attestation,$(WT_CONF_SUITE)),) WT_NS_IMG_SIZE ?= 0x100000 @@ -60,7 +70,8 @@ endif # Send a direct request to the Secure echo partition (ffa-guest-direct proof). WT_NS_GUEST_ECHO ?= 0 CFLAGS := -mcpu=generic -mgeneral-regs-only -mstrict-align -ffreestanding \ - -fno-builtin -fno-pic -fno-stack-protector -nostdlib -O2 -g \ + -fno-builtin -fno-pic -fno-stack-protector -ffunction-sections \ + -fdata-sections -nostdlib -O2 -g \ -Wall -Wextra -Werror -I$(ROOT)/include -DWT_NS_UART=$(UART)u \ -DWT_NS_GICR=$(GICR)u ifeq ($(WT_NS_GUEST_ECHO),1) @@ -111,7 +122,7 @@ WT_NS_GUEST_MEMNEG ?= 0 # it (the storage proof: the first SP-to-SP message driven from the Normal # world). WT_NS_GUEST_STORAGE ?= 0 -LDFLAGS := -nostartfiles -Wl,--build-id=none -T ns.ld \ +LDFLAGS := -nostartfiles -Wl,--build-id=none -Wl,--gc-sections -T ns.ld \ -Wl,--defsym=WT_NS_BASE=$(WT_NS_BASE) \ -Wl,--defsym=WT_NS_IMG_SIZE=$(WT_NS_IMG_SIZE) \ -Wl,--defsym=WT_NS_RAM_SIZE=$(WT_NS_RAM_SIZE) \ @@ -124,7 +135,10 @@ SRCS := ns.S ns.c # matching secure build (WT_NS_MANIFEST_INC). WT_NS_MANIFEST_INC ?= WOLFHSM_DIR ?= $(ROOT)/lib/wolfHSM -ifneq ($(filter 1,$(WT_NS_GUEST_PSA) $(WT_NS_GUEST_STORAGE) $(WT_NS_GUEST_MEMNEG) $(WT_RUN_CONFORMANCE)),) +WOLFTPM_DIR ?= $(ROOT)/lib/wolfTPM +WOLFPKCS11_DIR ?= $(ROOT)/lib/wolfPKCS11 +WOLFSSL_DIR ?= $(ROOT)/lib/wolfSSL +ifneq ($(filter 1,$(WT_NS_GUEST_PSA) $(WT_NS_GUEST_STORAGE) $(WT_NS_GUEST_MEMNEG) $(WT_NS_GUEST_TPM) $(WT_NS_GUEST_PKCS11) $(WT_RUN_CONFORMANCE)),) ifneq ($(WT_NS_MANIFEST_INC),) CFLAGS += -I$(WT_NS_MANIFEST_INC) endif @@ -132,6 +146,50 @@ SRCS += $(ROOT)/src/client/psa_ffm_client.c \ $(ROOT)/src/arch/aarch64/ffa/psa_ffa_transport_arch.c \ $(ROOT)/src/arch/aarch64/common/libc_min.c endif +ifneq ($(filter 1,$(WT_NS_GUEST_TPM) $(WT_NS_GUEST_PKCS11)),) +SRCS += crypto/ns_crypto_port.c +endif +ifneq ($(filter 1,$(WT_NS_GUEST_TPM) $(WT_NS_GUEST_PKCS11)),) +include $(ROOT)/mk/crypto-profile.mk +CFLAGS += $(WT_CRYPTO_PROFILE_FLAGS) +endif +ifeq ($(WT_NS_GUEST_TPM),1) +CFLAGS += -DWT_NS_GUEST_TPM=1 -DWOLFTPM_SWTPM \ + -DWOLFTPM_USER_SETTINGS -DWOLFSSL_USER_SETTINGS \ + -DWT_TPM_APP_NAME=\"tpm-aarch64\" \ + -Iinterface -I$(WOLFTPM_DIR) -I$(WOLFSSL_DIR) \ + -I$(ROOT)/tests/firmware/stm32h563-tpm/tpm-app \ + -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter \ + -Wno-type-limits +SRCS += $(ROOT)/src/client/tpm_psa_transport.c \ + $(ROOT)/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.c \ + $(addprefix $(WOLFTPM_DIR)/src/,tpm2.c tpm2_util.c tpm2_packet.c \ + tpm2_wrap.c tpm2_asn.c tpm2_crypto.c tpm2_param_enc.c \ + tpm2_cryptocb.c) \ + $(addprefix $(WOLFSSL_DIR)/wolfcrypt/src/,aes.c asn.c coding.c \ + cryptocb.c ecc.c error.c hash.c hmac.c kdf.c memory.c \ + pwdbased.c random.c sha.c sha256.c sha512.c sp_c64.c sp_int.c \ + wc_port.c wolfmath.c) +endif +ifeq ($(WT_NS_GUEST_PKCS11),1) +CFLAGS += -Wno-unused-function -Wno-unused-variable \ + -DWT_NS_GUEST_PKCS11=1 -D$(if $(filter default,$(WT_CRYPTO_PROFILE)),WT_PKCS11_SUITE_PROFILE,WT_PKCS11_SUITE_ALL) \ + -DWOLFPKCS11_USER_SETTINGS \ + -DWT_PKCS11_APP_NAME=\"pkcs11-aarch64\" \ + -I$(WOLFPKCS11_DIR) \ + -I$(ROOT)/tests/firmware/stm32h563-tpm/freertos-pkcs11 +SRCS += $(ROOT)/src/client/pkcs11_proxy.c \ + $(ROOT)/src/client/pkcs11_psa_transport.c \ + $(ROOT)/src/pkcs11/pkcs11_wire.c \ + $(ROOT)/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.c +endif +ifeq ($(WT_NS_PKCS11_HSM_RESTART_PROBE),1) +ifneq ($(WT_NS_GUEST_PKCS11),1) +$(error WT_NS_PKCS11_HSM_RESTART_PROBE=1 requires WT_NS_GUEST_PKCS11=1) +endif +CFLAGS += -DWT_NS_PKCS11_HSM_RESTART_PROBE=1 \ + -DWT_PKCS11_HSM_RESTART_PROBE=1 +endif # The crypto engine of the matching secure build picks the guest's client. WT_ENGINE ?= native ifneq ($(words $(WT_ENGINE))/$(filter native hsm,$(WT_ENGINE)),1/$(WT_ENGINE)) @@ -310,7 +368,7 @@ $(addprefix $(BUILD_DIR)/conf/,UsefulBuf.o qcbor_encode.o qcbor_decode.o ieee754 CONF_CFLAGS := $(filter-out -mgeneral-regs-only,$(CONF_CFLAGS)) endif -$(ELF): $(SRCS) $(CONF_OBJS) ns.ld | $(BUILD_DIR) +$(ELF): $(SRCS) $(CONF_OBJS) $(WT_CRYPTO_PROFILE_HEADER) ns.ld | $(BUILD_DIR) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRCS) $(CONF_OBJS) -lgcc $(BIN): $(ELF) diff --git a/tests/firmware/aarch64-ns-smoke/interface/user_settings.h b/tests/firmware/aarch64-ns-smoke/interface/user_settings.h new file mode 100644 index 000000000..0febf1cad --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/interface/user_settings.h @@ -0,0 +1,76 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_AARCH64_INTERFACE_USER_SETTINGS_H +#define WOLFTRUST_AARCH64_INTERFACE_USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define NO_FILESYSTEM +#define NO_WOLFSSL_DIR +#define WOLFSSL_USER_IO +#define NO_WRITEV +#define NO_ASN_TIME +#define NO_ERROR_STRINGS +#define SIZEOF_LONG_LONG 8 +#define USE_WOLF_STRCASECMP +#define WC_RESEED_INTERVAL 1000000 +#define XSLEEP_MS(ms) do { (void)(ms); } while (0) + +#define WOLFSSL_SP_MATH +#define WOLFSSL_SP_SMALL +#define WOLFSSL_HAVE_SP_ECC +#define HAVE_SP_ECC +#define SP_WORD_SIZE 64 +#define HAVE___UINT128_T 1 +#define WOLFSSL_SP_NO_DYN_STACK +#define ECC_TIMING_RESISTANT + +#define HAVE_ECC +#define ECC_USER_CURVES +#define NO_ECC192 +#define NO_ECC224 +#define NO_ECC384 +#define NO_ECC521 + +#define NO_SHA +#define WOLFSSL_AES_COUNTER +#define HAVE_AESCTR +#define WOLFSSL_AES_CFB +#define HAVE_AESGCM +#define HAVE_AESCCM +#define HAVE_HKDF +#define HAVE_PBKDF2 +#define WOLFSSL_HAVE_PRF +#define WOLFSSL_ECDSA_DETERMINISTIC_K + +#define HAVE_HASHDRBG +#define CUSTOM_RAND_GENERATE_BLOCK wolftrust_guest_rng_stub +int wolftrust_guest_rng_stub(unsigned char* output, unsigned int size); + +#define NO_RSA +#define NO_DH +#define NO_DSA +#define NO_DES3 +#define NO_MD5 +#define NO_PKCS12 +#define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 + +#endif /* WOLFTRUST_AARCH64_INTERFACE_USER_SETTINGS_H */ diff --git a/tests/firmware/aarch64-ns-smoke/ns.c b/tests/firmware/aarch64-ns-smoke/ns.c index 15a4ba106..e738d6d0c 100644 --- a/tests/firmware/aarch64-ns-smoke/ns.c +++ b/tests/firmware/aarch64-ns-smoke/ns.c @@ -47,6 +47,19 @@ #include +#if defined(WT_NS_GUEST_TPM) +#include "tpm_tests.h" +#endif +#if defined(WT_NS_GUEST_PKCS11) +#include "pkcs11_tests.h" +#if defined(WT_NS_PKCS11_HSM_RESTART_PROBE) +#include "psa/client.h" +#include "psa/error.h" +#include "psa_manifest/sid.h" +#include "wolftrust/services/hsm_relay.h" +#endif +#endif + #define UART_DR 0x00u #define UART_FR 0x18u #define UART_FR_TXFF (1u << 5) @@ -100,6 +113,72 @@ static void put_dec(uint32_t value) put_str(&buf[i]); } +#if defined(WT_NS_GUEST_TPM) +void wt_tpm_puts(const char* text) +{ + put_str(text); +} + +void wt_tpm_print_i32(int value) +{ + if (value < 0) { + put_char('-'); + put_dec((uint32_t)(-(value + 1)) + 1u); + } + else { + put_dec((uint32_t)value); + } +} + +void wt_tpm_wait(void) +{ + __asm__ volatile("yield"); +} + +int wt_ns_generate_seed(unsigned char* output, unsigned int size); + +int wolftrust_guest_rng_stub(unsigned char* output, unsigned int size) +{ + return wt_ns_generate_seed(output, size); +} +#endif + +#if defined(WT_NS_GUEST_PKCS11) +void wt_pkcs11_puts(const char* text) +{ + put_str(text); +} + +void wt_pkcs11_print_u32(unsigned int value) +{ + put_dec(value); +} + +void wt_pkcs11_wait(void) +{ + __asm__ volatile("yield"); +} + +#if defined(WT_NS_PKCS11_HSM_RESTART_PROBE) +static int guest_pkcs11_restart_hsm(void) +{ + psa_handle_t handle; + psa_status_t status; + + handle = psa_connect(SERVICE_HSM_SID, 1U); + if (!PSA_HANDLE_IS_VALID(handle)) { + return -1; + } + status = psa_call(handle, WT_HSM_OP_TEST_RESTART, NULL, 0U, NULL, 0U); + psa_close(handle); + put_str("[NS] pkcs11 HSM restart status=0x"); + put_hex((uint32_t)status); + put_str("\r\n"); + return status == PSA_ERROR_COMMUNICATION_FAILURE ? 0 : -1; +} +#endif +#endif + /* One FF-A SMC to the SPMD at the NS physical instance: in0/in1 in x0/x1, the * reply x0-x3 written back to out[0..3]. */ static void ffa_smc(uint64_t in0, uint64_t in1, uint64_t* out) @@ -2217,4 +2296,25 @@ void ns_main(void) #if defined(WT_NS_GUEST_CONF) guest_conformance(); #endif + +#if defined(WT_NS_GUEST_TPM) + put_str(wt_tpm_run() == 0 ? "[NS] tpm suite complete\r\n" : + "[NS] tpm suite FAILED\r\n"); +#endif + +#if defined(WT_NS_GUEST_PKCS11) +#if defined(WT_NS_PKCS11_HSM_RESTART_PROBE) + if (wt_pkcs11_run() == 0 && guest_pkcs11_restart_hsm() == 0 && + wt_pkcs11_run() == 0) { + put_str("[NS] pkcs11 recovered after HSM restart\r\n"); + put_str("[NS] pkcs11 suite complete\r\n"); + } + else { + put_str("[NS] pkcs11 suite FAILED\r\n"); + } +#else + put_str(wt_pkcs11_run() == 0 ? "[NS] pkcs11 suite complete\r\n" : + "[NS] pkcs11 suite FAILED\r\n"); +#endif +#endif } diff --git a/tests/firmware/stm32h563-tpm/Makefile b/tests/firmware/stm32h563-tpm/Makefile new file mode 100644 index 000000000..6c8fb401f --- /dev/null +++ b/tests/firmware/stm32h563-tpm/Makefile @@ -0,0 +1,271 @@ +.DEFAULT_GOAL := all + +# stm32h563-tpm end-to-end firmware-TPM test. +# +# A bare-metal wolfTPM client calls fwTPM inside the existing Secure partition. +# FreeRTOS and Zephyr clients exercise the same service and engine choices. +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + +TOOLPREFIX ?= arm-none-eabi- +CC := $(TOOLPREFIX)gcc +OBJCOPY := $(TOOLPREFIX)objcopy +SIZE := $(TOOLPREFIX)size +NM := $(TOOLPREFIX)nm +M33MU ?= m33mu + +ROOT := ../../.. +PORT_DIR := $(ROOT)/port/stm32h563 +WOLFSSL_DIR := $(ROOT)/lib/wolfSSL +WOLFTPM_DIR := $(ROOT)/lib/wolfTPM + +CONFIG_TPM ?= y +WT_ENGINE ?= $(if $(filter 1,$(WT_ENGINE_HSM)),hsm,native) +ifneq ($(WT_ENGINE_HSM),) +ifeq ($(filter 0 1,$(WT_ENGINE_HSM)),) +$(error unsupported WT_ENGINE_HSM=$(WT_ENGINE_HSM)) +endif +ifneq ($(WT_ENGINE),$(if $(filter 1,$(WT_ENGINE_HSM)),hsm,native)) +$(error conflicting WT_ENGINE and WT_ENGINE_HSM selectors) +endif +endif +ifneq ($(filter $(WT_ENGINE),native hsm),$(WT_ENGINE)) +$(error unsupported WT_ENGINE=$(WT_ENGINE)) +endif +EMU_TIMEOUT ?= 180 +WT_MAX_GUESTS ?= 1 +WT_CRYPTO_PROFILE ?= default +WT_PKCS11_SUITE ?= $(if $(filter default,$(WT_CRYPTO_PROFILE)),profile,all) + +# The application window matches manifest-tpm.json; runners may override it. +WT_GUEST0_FLASH_ORIGIN ?= 0x080A0000 +WT_GUEST_FLASH_SIZE ?= 0x00020000 +WT_GUEST0_RAM_BASE ?= 0x20000000 +WT_GUEST_RAM_SIZE ?= 0x00010000 +WT_GUEST0_EMU_OFFSET ?= 0xA0000 + +WT_GUEST_CORE_CLOCK_HZ ?= 240000000 +WT_GUEST_UART_CLOCK_HZ ?= 120000000 + +CPU_FLAGS := -mcpu=cortex-m33 -mthumb -mgeneral-regs-only + +GUEST_CFLAGS := $(CPU_FLAGS) -ffreestanding -fno-builtin -nostdlib -Os -g \ + -Wall -Wextra -Wno-unused-function -Wno-unused-variable \ + -Wno-unused-parameter -Wno-type-limits -mno-unaligned-access \ + -ffunction-sections -fdata-sections \ + -Inonsecure \ + -Itpm-app \ + -I$(ROOT)/include \ + -I$(PORT_DIR) \ + -I$(WOLFSSL_DIR) \ + -I$(WOLFTPM_DIR) \ + -DWOLFSSL_USER_SETTINGS -DWOLFTPM_USER_SETTINGS \ + -include nonsecure/user_settings.h \ + -DWOLFSSL_SP_ASM -DWOLFSSL_SP_ARM_CORTEX_M_ASM -DWOLFSSL_ARM_ARCH=8 \ + -DWOLFSSL_ARMASM -DWOLFSSL_ARMASM_NO_HW_CRYPTO -DWOLFSSL_ARMASM_INLINE \ + -DWOLFSSL_ARMASM_NO_NEON -DWOLFSSL_ARMASM_THUMB2 \ + -DNO_ERROR_STRINGS -DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT \ + -DWC_RESEED_INTERVAL=1000000 \ + -DWOLFTPM_SMALL_STACK \ + -DWT_GUEST_CORE_CLOCK_HZ=$(WT_GUEST_CORE_CLOCK_HZ) \ + -DWT_GUEST_UART_CLOCK_HZ=$(WT_GUEST_UART_CLOCK_HZ) \ + -DWT_GUEST0_FLASH_BASE=$(WT_GUEST0_FLASH_ORIGIN)u +WT_LTO ?= 1 +ifeq ($(WT_LTO),1) +GUEST_CFLAGS += -flto=auto +else ifneq ($(WT_LTO),0) +$(error unsupported WT_LTO=$(WT_LTO)) +endif +GUEST_KEEP_FLAGS := -Wl,-u,memcpy,-u,memset,-u,memmove,-u,memcmp +GUEST_CFLAGS += $(GUEST_EXTRA_CFLAGS) + +# guest0: the bare-metal wolfTPM client routed through SERVICE_TPM by building +# with WOLFTPM_SWTPM and supplying TPM2_SWTPM_SendCommand. +APP_CFLAGS := -DWT_WOLFHSM_CLIENT_ID=1 -DWT_TPM_ROLE_APP -DWOLFTPM_SWTPM -DGUEST_HEAP_POOL_SZ=32768u \ + -DWOLF_CRYPTO_CB -DWOLFTPM_CRYPTOCB \ + -DWT_TPM_APP_NAME=\"tpm-guest0\" + +TPM_BUILD_ROOT ?= build +BUILD_DIR ?= $(TPM_BUILD_ROOT)/tpm +PKCS11_BUILD_DIR ?= $(TPM_BUILD_ROOT)/pkcs11-$(WT_PKCS11_SUITE) +TPM_FREERTOS_BUILD_DIR ?= $(TPM_BUILD_ROOT)/tpm-freertos +TOP_BUILD_DIR := $(ROOT)/build +SECURE_BIN := $(TOP_BUILD_DIR)/wolftrust.bin +SECURE_CMSE_IMPLIB := $(TOP_BUILD_DIR)/secure_cmse_implib.o + +WOLFCRYPT_SRCS := \ + $(WOLFSSL_DIR)/wolfcrypt/src/aes.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/cryptocb.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/ecc.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/random.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/sha256.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/asn.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/coding.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/error.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/hash.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/hmac.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/memory.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/sha.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/sp_cortexm.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/sp_int.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/wolfmath.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/wc_port.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/sha512.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/kdf.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/pwdbased.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/port/arm/thumb2-aes-asm_c.c \ + $(WOLFSSL_DIR)/wolfcrypt/src/port/arm/thumb2-sha256-asm_c.c + +COMMON_SRCS := \ + nonsecure/guest.c \ + nonsecure/guest_libc.c \ + $(ROOT)/src/client/psa_ffm_client.c \ + $(ROOT)/src/client/crypto_native_client.c \ + $(ROOT)/src/client/tpm_psa_transport.c \ + $(WOLFCRYPT_SRCS) + +APP_SRCS := \ + tpm-app/tpm_tests.c \ + $(WOLFTPM_DIR)/src/tpm2.c \ + $(WOLFTPM_DIR)/src/tpm2_util.c \ + $(WOLFTPM_DIR)/src/tpm2_packet.c \ + $(WOLFTPM_DIR)/src/tpm2_wrap.c \ + $(WOLFTPM_DIR)/src/tpm2_asn.c \ + $(WOLFTPM_DIR)/src/tpm2_crypto.c \ + $(WOLFTPM_DIR)/src/tpm2_param_enc.c \ + $(WOLFTPM_DIR)/src/tpm2_cryptocb.c + +ifeq ($(WT_ENGINE),hsm) +GUEST_CFLAGS += -DWT_ENGINE_HSM=1 -DWOLF_CRYPTO_CB -DWOLFHSM_CFG \ + -I$(ROOT)/lib/wolfHSM -I$(BUILD_DIR) +COMMON_SRCS := $(filter-out %/crypto_native_client.c,$(COMMON_SRCS)) +COMMON_SRCS += \ + $(ROOT)/src/client/hsm_psa_transport.c \ + $(ROOT)/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/src/wolfhsm_client_glue.c +COMMON_SRCS += $(addprefix $(ROOT)/lib/wolfHSM/src/, \ + wh_client.c wh_client_crypto.c wh_client_cryptocb.c wh_comm.c \ + wh_crypto.c wh_keyid.c wh_message_comm.c wh_message_crypto.c \ + wh_message_keystore.c wh_message_nvm.c wh_message_customcb.c \ + wh_message_counter.c wh_utils.c) +GUEST_ENGINE_DEPS := $(BUILD_DIR)/wolfhsm_cfg.h +else +GUEST_CFLAGS += -DWT_ENGINE_NATIVE=1 +endif + +include $(ROOT)/mk/crypto-profile.mk +GUEST_CFLAGS += $(WT_CRYPTO_PROFILE_FLAGS) + +G0_OBJS := $(patsubst %.c,$(BUILD_DIR)/g0_%.o,$(notdir $(COMMON_SRCS) $(APP_SRCS))) + +vpath %.c $(sort $(dir $(COMMON_SRCS) $(APP_SRCS))) + +GUEST0_ELF := $(BUILD_DIR)/guest0.elf +GUEST0_BIN := $(BUILD_DIR)/guest0.bin + +.PHONY: all secure clean run tpm tpm-freertos-app \ + pkcs11 pkcs11-guests pkcs11-freertos-app \ + run-tpm run-pkcs11 + +tpm: + @$(MAKE) --no-print-directory all + +pkcs11: + @$(MAKE) --no-print-directory secure CONFIG_TPM=n CONFIG_PKCS11=y \ + WT_ENGINE=$(WT_ENGINE) + @$(MAKE) --no-print-directory pkcs11-guests + +pkcs11-guests: pkcs11-freertos-app + +pkcs11-freertos-app: + @$(MAKE) --no-print-directory -C ../zephyr-stm32h5 freertos-workspace + @BUILD_DIR="$(abspath $(PKCS11_BUILD_DIR))" \ + SECURE_CMSE_IMPLIB="$(abspath $(SECURE_CMSE_IMPLIB))" \ + WT_PKCS11_SUITE="$(WT_PKCS11_SUITE)" \ + WT_GUEST0_FLASH_BASE="$(WT_GUEST0_FLASH_ORIGIN)" \ + WT_GUEST0_FLASH_SIZE=0x00020000 \ + WT_GUEST0_RAM_BASE="$(WT_GUEST0_RAM_BASE)" \ + WT_GUEST_RAM_SIZE="$(WT_GUEST_RAM_SIZE)" \ + sh ./scripts/build_freertos_pkcs11.sh + +tpm-freertos-app: + @$(MAKE) --no-print-directory -C ../zephyr-stm32h5 freertos-workspace + @BUILD_DIR="$(abspath $(TPM_FREERTOS_BUILD_DIR))" \ + SECURE_CMSE_IMPLIB="$(abspath $(SECURE_CMSE_IMPLIB))" \ + WT_GUEST0_FLASH_BASE="$(WT_GUEST0_FLASH_ORIGIN)" \ + WT_GUEST0_FLASH_SIZE="$(WT_GUEST_FLASH_SIZE)" \ + WT_GUEST0_RAM_BASE="$(WT_GUEST0_RAM_BASE)" \ + WT_GUEST_RAM_SIZE="$(WT_GUEST_RAM_SIZE)" \ + sh ./scripts/build_freertos_tpm.sh + +all: secure $(GUEST0_BIN) + @$(SIZE) $(GUEST0_ELF) + +secure: + rm -rf $(TOP_BUILD_DIR) + CONFIG_TPM=$(CONFIG_TPM) WT_MAX_GUESTS=$(WT_MAX_GUESTS) \ + WT_GUEST0_FLASH_BASE=$(WT_GUEST0_FLASH_ORIGIN) \ + $(MAKE) -C $(ROOT) build/wolftrust.bin build/secure_cmse_implib.o + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/wolfhsm_cfg.h: | $(BUILD_DIR) + @printf '#include "%s"\n' '$(abspath $(ROOT)/tests/firmware/stm32h563/nonsecure/wh_settings_guest.h)' > $@ + +$(BUILD_DIR)/g0_%.o: %.c $(WT_CRYPTO_PROFILE_HEADER) nonsecure/user_settings.h $(GUEST_ENGINE_DEPS) | $(BUILD_DIR) + $(CC) $(GUEST_CFLAGS) $(APP_CFLAGS) -c -o $@ $< + +$(GUEST0_ELF): GUEST_KEEP_FLAGS += -Wl,-u,wt_tpm_psa_transact,-u,TPM2_SWTPM_SendCommand + +define GUEST_LINK +$(BUILD_DIR)/$(1).elf: $(2) nonsecure/guest.ld $(SECURE_CMSE_IMPLIB) | $(BUILD_DIR) + $(CC) $(GUEST_CFLAGS) $$(GUEST_KEEP_FLAGS) \ + -Wl,-Tnonsecure/guest.ld \ + -Wl,--defsym=GUEST_FLASH_ORIGIN=$(3) \ + -Wl,--defsym=GUEST_FLASH_LENGTH=$(WT_GUEST_FLASH_SIZE) \ + -Wl,--defsym=GUEST_RAM_ORIGIN=$(4) \ + -Wl,--defsym=GUEST_RAM_LENGTH=$(WT_GUEST_RAM_SIZE) \ + -Wl,--gc-sections \ + -o $$@ $(2) $(SECURE_CMSE_IMPLIB) -lgcc + $(SIZE) $$@ +endef + +$(eval $(call GUEST_LINK,guest0,$(G0_OBJS),$(WT_GUEST0_FLASH_ORIGIN),$(WT_GUEST0_RAM_BASE))) + +# Every Secure crossing is SPM-mediated: no retired direct veneers, the +# application rides SERVICE_TPM and contains no fwTPM implementation. +$(GUEST0_BIN): $(GUEST0_ELF) + @! $(NM) --defined-only $< | grep -Eq 'WolfTrust_HSM_(Submit|Poll|Cancel)|WolfTrust_Attest_' || \ + { echo "FAIL: retired direct veneers linked into $<" >&2; exit 1; } + @$(NM) --defined-only $< | grep -q 'wt_tpm_psa_transact' || \ + { echo "FAIL: $< is not wired to the SERVICE_TPM transport" >&2; exit 1; } + @$(NM) --defined-only $< | grep -q 'TPM2_SWTPM_SendCommand' || \ + { echo "FAIL: wolfTPM client not routed through SERVICE_TPM in $<" >&2; exit 1; } + $(OBJCOPY) -O binary $< $@ + +run run-tpm: + @cd $(ROOT) && WT_ENGINE=$(WT_ENGINE) WT_CRYPTO_PROFILE=$(WT_CRYPTO_PROFILE) \ + M33MU=$(M33MU) bash tests/target/run_suite.sh m33mu tpm + +run-pkcs11: + @cd $(ROOT) && WT_ENGINE=$(WT_ENGINE) WT_CRYPTO_PROFILE=$(WT_CRYPTO_PROFILE) \ + M33MU=$(M33MU) bash tests/target/run_suite.sh m33mu pkcs11 + +clean: + rm -rf $(TPM_BUILD_ROOT) diff --git a/tests/firmware/stm32h563-tpm/freertos-pkcs11/FreeRTOSConfig.h b/tests/firmware/stm32h563-tpm/freertos-pkcs11/FreeRTOSConfig.h new file mode 100644 index 000000000..eba40a9db --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-pkcs11/FreeRTOSConfig.h @@ -0,0 +1,86 @@ +/* FreeRTOSConfig.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_FREERTOS_PKCS11_CONFIG_H +#define WOLFTRUST_FREERTOS_PKCS11_CONFIG_H + +#include + +#define configENABLE_MPU 0 +#define configENABLE_FPU 0 +#define configENABLE_TRUSTZONE 0 + +#define configUSE_PREEMPTION 1 +#define configUSE_TIME_SLICING 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configMAX_PRIORITIES 4 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_16_BIT_TICKS 0 + +#define configCPU_CLOCK_HZ 240000000u +#define configTICK_RATE_HZ 10u +#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ + +#define configTOTAL_HEAP_SIZE ((size_t)(40u * 1024u)) +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configMINIMAL_STACK_SIZE ((uint16_t)256) +#define configMAX_TASK_NAME_LEN 12 + +#define configUSE_MUTEXES 1 +#define configUSE_TICKLESS_IDLE 0 +#define configUSE_APPLICATION_TASK_TAG 0 +#define configUSE_NEWLIB_REENTRANT 0 +#define configUSE_CO_ROUTINES 0 +#define configUSE_COUNTING_SEMAPHORES 0 +#define configUSE_RECURSIVE_MUTEXES 0 +#define configUSE_QUEUE_SETS 0 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configUSE_TRACE_FACILITY 0 +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#define configUSE_MALLOC_FAILED_HOOK 0 +#define configUSE_TIMERS 0 + +#define INCLUDE_vTaskPrioritySet 0 +#define INCLUDE_uxTaskPriorityGet 0 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 0 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 0 +#define INCLUDE_xTaskGetCurrentTaskHandle 0 + +#define configPRIO_BITS 4 +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2 +#define configKERNEL_INTERRUPT_PRIORITY \ + (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) +#define configMAX_SYSCALL_INTERRUPT_PRIORITY \ + (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +#define vPortSVCHandler SVC_Handler +#define xPortPendSVHandler PendSV_Handler +#define xPortSysTickHandler SysTick_Handler + +#define configASSERT(x) do { if (!(x)) { for (;;) {} } } while (0) + +#endif /* WOLFTRUST_FREERTOS_PKCS11_CONFIG_H */ diff --git a/tests/firmware/stm32h563-tpm/freertos-pkcs11/guest0.ld b/tests/firmware/stm32h563-tpm/freertos-pkcs11/guest0.ld new file mode 100644 index 000000000..23766ed7e --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-pkcs11/guest0.ld @@ -0,0 +1,39 @@ +ENTRY(Reset_Handler) + +MEMORY { + FLASH (rx) : ORIGIN = GUEST_FLASH_ORIGIN, LENGTH = GUEST_FLASH_LENGTH + RAM (rwx): ORIGIN = GUEST_RAM_ORIGIN, LENGTH = GUEST_RAM_LENGTH +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); +_sidata = LOADADDR(.data); + +SECTIONS { + .vectors GUEST_FLASH_ORIGIN : { + KEEP(*(.vectors)) + } > FLASH + + .reset : { + KEEP(*(.reset)) + } > FLASH + + .text : { + *(.text*) + *(.rodata*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + + .data : { + _sdata = .; + *(.data*) + _edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : { + _sbss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > RAM +} diff --git a/tests/firmware/stm32h563-tpm/freertos-pkcs11/main.c b/tests/firmware/stm32h563-tpm/freertos-pkcs11/main.c new file mode 100644 index 000000000..71dbcfe84 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-pkcs11/main.c @@ -0,0 +1,279 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +#include "FreeRTOS.h" +#include "task.h" + +#include "pkcs11_tests.h" + +typedef union wt_heap_header { + size_t size; + uint64_t align; +} wt_heap_header_t; + +extern uint32_t _sidata; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; +extern uint32_t _estack; + +#define USART3_BASE 0x40004800u +#define USART_CR1(b) (*(volatile uint32_t *)((b) + 0x00u)) +#define USART_CR2(b) (*(volatile uint32_t *)((b) + 0x04u)) +#define USART_CR3(b) (*(volatile uint32_t *)((b) + 0x08u)) +#define USART_BRR(b) (*(volatile uint32_t *)((b) + 0x0Cu)) +#define USART_ISR(b) (*(volatile uint32_t *)((b) + 0x1Cu)) +#define USART_TDR(b) (*(volatile uint32_t *)((b) + 0x28u)) +#define USART_CR1_UE (1u << 0) +#define USART_CR1_RE (1u << 2) +#define USART_CR1_TE (1u << 3) +#define USART_ISR_TXE (1u << 7) + +static void default_handler(void) +{ + for (;;) {} +} + +void Reset_Handler(void); +void NMI_Handler(void) __attribute__((weak, alias("default_handler"))); +void HardFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("default_handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void SVC_Handler(void); +void PendSV_Handler(void); +void SysTick_Handler(void); +void DebugMon_Handler(void) __attribute__((weak, alias("default_handler"))); + +__attribute__((used, section(".vectors"))) +const uint32_t g_vectors[16] = { + [0] = (uint32_t)&_estack, + [1] = (uint32_t)&Reset_Handler, + [2] = (uint32_t)&NMI_Handler, + [3] = (uint32_t)&HardFault_Handler, + [4] = (uint32_t)&MemManage_Handler, + [5] = (uint32_t)&BusFault_Handler, + [6] = (uint32_t)&UsageFault_Handler, + [11] = (uint32_t)&SVC_Handler, + [12] = (uint32_t)&DebugMon_Handler, + [14] = (uint32_t)&PendSV_Handler, + [15] = (uint32_t)&SysTick_Handler, +}; + +static void copy_data(void) +{ + uint32_t *src = &_sidata; + uint32_t *dst = &_sdata; + + while (dst < &_edata) { + *dst++ = *src++; + } +} + +static void zero_bss(void) +{ + uint32_t *dst = &_sbss; + + while (dst < &_ebss) { + *dst++ = 0u; + } +} + +static void uart_init(void) +{ + USART_CR1(USART3_BASE) = 0u; + USART_CR2(USART3_BASE) = 0u; + USART_CR3(USART3_BASE) = 0u; + USART_BRR(USART3_BASE) = 120000000u / 115200u; + USART_CR1(USART3_BASE) = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; +} + +static void uart_putc(char c) +{ + while ((USART_ISR(USART3_BASE) & USART_ISR_TXE) == 0u) {} + USART_TDR(USART3_BASE) = (uint32_t)(uint8_t)c; +} + +void wt_pkcs11_puts(const char *text) +{ + while (*text != '\0') { + uart_putc(*text++); + } +} + +void wt_pkcs11_print_u32(unsigned int value) +{ + char text[10]; + unsigned int index = 0u; + + if (value == 0u) { + uart_putc('0'); + return; + } + while (value != 0u) { + text[index++] = (char)('0' + (value % 10u)); + value /= 10u; + } + while (index != 0u) { + uart_putc(text[--index]); + } +} + +void wt_pkcs11_wait(void) +{ + volatile uint32_t index; + + for (index = 0u; index < 200000u; index++) {} +} + +void wt_pkcs11_sleep_ms(unsigned int ms) +{ + vTaskDelay(pdMS_TO_TICKS(ms)); +} + +void *XMALLOC(size_t size, void *heap, int type) +{ + wt_heap_header_t *header; + + (void)heap; + (void)type; + if (size > (SIZE_MAX - sizeof(*header))) { + return NULL; + } + header = (wt_heap_header_t *)pvPortMalloc(size + sizeof(*header)); + if (header == NULL) { + return NULL; + } + header->size = size; + return (void *)(header + 1); +} + +void XFREE(void *ptr, void *heap, int type) +{ + (void)heap; + (void)type; + if (ptr != NULL) { + vPortFree(((wt_heap_header_t *)ptr) - 1); + } +} + +void *XREALLOC(void *ptr, size_t size, void *heap, int type) +{ + wt_heap_header_t *old_header; + unsigned char *old_data = (unsigned char *)ptr; + unsigned char *new_data; + size_t copy_size; + size_t index; + + if (ptr == NULL) { + return XMALLOC(size, heap, type); + } + if (size == 0u) { + XFREE(ptr, heap, type); + return NULL; + } + old_header = ((wt_heap_header_t *)ptr) - 1; + new_data = (unsigned char *)XMALLOC(size, heap, type); + if (new_data == NULL) { + return NULL; + } + copy_size = old_header->size < size ? old_header->size : size; + for (index = 0u; index < copy_size; index++) { + new_data[index] = old_data[index]; + } + XFREE(ptr, heap, type); + return new_data; +} + +static void test_task(void *arg) +{ + int rc; + + (void)arg; + wt_pkcs11_puts("pkcs11-freertos: scheduler started\r\n"); + rc = wt_pkcs11_run(); + if (rc == 0) { + wt_pkcs11_puts("pkcs11-freertos: suite "); + wt_pkcs11_puts(wt_pkcs11_suite_name()); + wt_pkcs11_puts(" complete\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7f"); +#endif + } + else { + wt_pkcs11_puts("pkcs11-freertos: suite failed\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7e"); +#endif + } + vTaskSuspend(NULL); + for (;;) {} +} + +__attribute__((section(".reset"))) +void Reset_Handler(void) +{ + copy_data(); + zero_bss(); + uart_init(); + wt_pkcs11_puts("pkcs11-freertos: alive\r\n"); + + if (xTaskCreate(test_task, "pkcs11", 3072, NULL, + tskIDLE_PRIORITY + 1, NULL) != pdPASS) { + wt_pkcs11_puts("pkcs11-freertos: xTaskCreate failed\r\n"); + for (;;) {} + } + vTaskStartScheduler(); + wt_pkcs11_puts("pkcs11-freertos: scheduler exited\r\n"); + for (;;) {} +} + +void vAssertCalled(const char *file, int line) +{ + (void)file; + (void)line; + wt_pkcs11_puts("pkcs11-freertos: assertion failed\r\n"); + for (;;) {} +} + +void vApplicationMallocFailedHook(void) +{ + wt_pkcs11_puts("pkcs11-freertos: malloc failed\r\n"); + for (;;) {} +} + +void vApplicationStackOverflowHook(TaskHandle_t task, char *name) +{ + (void)task; + (void)name; + wt_pkcs11_puts("pkcs11-freertos: stack overflow\r\n"); + for (;;) {} +} + +int printf(const char *format, ...) +{ + (void)format; + return 0; +} diff --git a/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.c b/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.c new file mode 100644 index 000000000..2cc971fab --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.c @@ -0,0 +1,1380 @@ +/* pkcs11_tests.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +#include "wolfpkcs11/pkcs11.h" + +#include "wolftrust/pkcs11_psa_transport.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" + +#include "pkcs11_tests.h" + +#define WT_PKCS11_SLOT 1UL +#define WT_ARRAY_SIZE(a) ((CK_ULONG)(sizeof(a) / sizeof((a)[0]))) + +#ifndef WT_PKCS11_APP_NAME +#define WT_PKCS11_APP_NAME "pkcs11-freertos" +#endif + +typedef int (*wt_test_fn)(void); + +static CK_FUNCTION_LIST_PTR g_fn; +static CK_SESSION_HANDLE g_session = CK_INVALID_HANDLE; +static unsigned int g_passed; +static unsigned int g_failed; +static int g_keep_session; + +static CK_UTF8CHAR g_so_pin[] = "wolfTrust-so"; +static CK_UTF8CHAR g_user_pin[] = "wolfTrust-user"; +static CK_UTF8CHAR g_alt_pin[] = "wolfTrust-alt"; +static CK_UTF8CHAR g_token_label[] = + "wolfTrust HSM token "; + +static CK_BBOOL g_true = CK_TRUE; +static CK_BBOOL g_false = CK_FALSE; +static CK_OBJECT_CLASS g_data_class = CKO_DATA; +static CK_OBJECT_CLASS g_secret_class = CKO_SECRET_KEY; +static CK_OBJECT_CLASS g_public_class = CKO_PUBLIC_KEY; +static CK_OBJECT_CLASS g_private_class = CKO_PRIVATE_KEY; +static CK_KEY_TYPE g_aes_type = CKK_AES; +static CK_KEY_TYPE g_generic_type = CKK_GENERIC_SECRET; +static CK_KEY_TYPE g_ec_type = CKK_EC; + +static const CK_BYTE g_p256_params[] = { + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 +}; +static const CK_BYTE g_digest_input[] = "abc"; +static const CK_BYTE g_sha256_abc[32] = { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad +}; + +static void app_prefix(void) +{ + wt_pkcs11_puts(WT_PKCS11_APP_NAME ": "); +} + +static void report_case(const char *name, int passed) +{ + app_prefix(); + wt_pkcs11_puts(passed != 0 ? "PASS " : "FAIL "); + wt_pkcs11_puts(name); + wt_pkcs11_puts("\r\n"); + if (passed != 0) { + g_passed++; + } + else { + g_failed++; + } +} + +static void run_case(const char *name, wt_test_fn test) +{ + report_case(name, test() == 0); +} + +static int buffer_nonzero(const CK_BYTE *buffer, CK_ULONG length) +{ + CK_ULONG index; + CK_BYTE value = 0u; + + for (index = 0u; index < length; index++) { + value |= buffer[index]; + } + return value != 0u; +} + +static int malformed_initialize_rejected(void) +{ + uint8_t request[12]; + uint8_t response[12]; + size_t response_len = 0u; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_RV rv; + + wt_pkcs11_writer_init(&writer, request, sizeof(request)); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_VERSION); + wt_pkcs11_put_u32(&writer, WT_P11_INITIALIZE); + if (writer.error != 0 || wt_pkcs11_psa_call(request, writer.used, + response, sizeof(response), &response_len) != 0) { + return -1; + } + wt_pkcs11_reader_init(&reader, response, response_len); + if (wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_MAGIC || + wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_VERSION) { + return -1; + } + rv = (CK_RV)wt_pkcs11_get_u32(&reader); + return reader.error == 0 && reader.used == reader.length && + rv == CKR_ARGUMENTS_BAD ? 0 : -1; +} + +static int library_initialize(void) +{ + CK_RV rv = CKR_FUNCTION_FAILED; + unsigned int tries; + + if (C_GetFunctionList(&g_fn) != CKR_OK || g_fn == NULL) { + return -1; + } + if (malformed_initialize_rejected() != 0) { + return -1; + } + for (tries = 0u; tries < 50u; tries++) { + rv = g_fn->C_Initialize(NULL); + if (rv == CKR_OK) { + return 0; + } + if (rv == CKR_CRYPTOKI_ALREADY_INITIALIZED) { + return -1; + } + (void)g_fn->C_Finalize(NULL); + wt_pkcs11_wait(); + } + return -1; +} + +static int token_login(void) +{ + CK_RV rv; + + rv = g_fn->C_InitToken(WT_PKCS11_SLOT, g_so_pin, + (CK_ULONG)(sizeof(g_so_pin) - 1u), + g_token_label); + if (rv != CKR_OK) { + return -1; + } + rv = g_fn->C_OpenSession(WT_PKCS11_SLOT, + CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &g_session); + if (rv != CKR_OK) { + return -1; + } + rv = g_fn->C_Login(g_session, CKU_SO, g_so_pin, + (CK_ULONG)(sizeof(g_so_pin) - 1u)); + if (rv != CKR_OK) { + return -1; + } + rv = g_fn->C_InitPIN(g_session, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)); + if (rv != CKR_OK || g_fn->C_Logout(g_session) != CKR_OK) { + return -1; + } + rv = g_fn->C_Login(g_session, CKU_USER, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)); + return rv == CKR_OK ? 0 : -1; +} + +static int suite_setup(void) +{ + if (library_initialize() != 0) { + return -1; + } + if (token_login() != 0) { + (void)g_fn->C_Finalize(NULL); + return -1; + } + return 0; +} + +static int suite_ready(void) +{ + if (g_fn != NULL && g_session != CK_INVALID_HANDLE) { + return 0; + } + return suite_setup(); +} + +static void suite_cleanup(void) +{ + if (g_fn != NULL) { + if (g_session != CK_INVALID_HANDLE) { + (void)g_fn->C_Logout(g_session); + (void)g_fn->C_CloseSession(g_session); + } + (void)g_fn->C_Finalize(NULL); + } + g_session = CK_INVALID_HANDLE; + g_fn = NULL; +} + +static int test_library_info(void) +{ + CK_INFO info; + + return g_fn->C_GetInfo(&info) == CKR_OK ? 0 : -1; +} + +static int test_slot_list(void) +{ + CK_SLOT_ID slots[65]; + CK_ULONG count = ~(CK_ULONG)0u; + CK_ULONG index; + int found = 0; + + if (g_fn->C_GetSlotList(CK_TRUE, NULL, &count) != CKR_OK || + count == 0u || count > WT_ARRAY_SIZE(slots)) { + return -1; + } + count = WT_ARRAY_SIZE(slots); + if (g_fn->C_GetSlotList(CK_TRUE, slots, &count) != CKR_OK) { + return -1; + } + for (index = 0u; index < count; index++) { + if (slots[index] == WT_PKCS11_SLOT) { + found = 1; + } + } + return found != 0 ? 0 : -1; +} + +static int test_slot_token_info(void) +{ + CK_SLOT_INFO slot_info; + CK_TOKEN_INFO token_info; + + if (g_fn->C_GetSlotInfo(WT_PKCS11_SLOT, &slot_info) != CKR_OK) { + return -1; + } + return g_fn->C_GetTokenInfo(WT_PKCS11_SLOT, &token_info) == CKR_OK ? + 0 : -1; +} + +static int mechanism_present(const CK_MECHANISM_TYPE *list, CK_ULONG count, + CK_MECHANISM_TYPE mechanism) +{ + CK_ULONG index; + + for (index = 0u; index < count; index++) { + if (list[index] == mechanism) { + return 1; + } + } + return 0; +} + +static int test_mechanism_list(void) +{ + CK_MECHANISM_TYPE list[65]; + CK_MECHANISM_INFO info; + CK_ULONG count = ~(CK_ULONG)0u; + + if (g_fn->C_GetMechanismList(WT_PKCS11_SLOT, NULL, &count) != CKR_OK || + count == 0u || count > WT_ARRAY_SIZE(list)) { + return -1; + } + count = WT_ARRAY_SIZE(list); + if (g_fn->C_GetMechanismList(WT_PKCS11_SLOT, list, &count) != CKR_OK || + mechanism_present(list, count, CKM_EC_KEY_PAIR_GEN) == 0 || + mechanism_present(list, count, CKM_ECDSA) == 0 || +#if defined(WT_PKCS11_SUITE_PROFILE) + count != 3u || + mechanism_present(list, count, CKM_ECDH1_DERIVE) != 0 || + mechanism_present(list, count, CKM_AES_KEY_GEN) != 0 || +#else + mechanism_present(list, count, CKM_ECDH1_DERIVE) == 0 || + mechanism_present(list, count, CKM_AES_KEY_GEN) == 0 || +#endif + mechanism_present(list, count, CKM_SHA256) == 0 || + mechanism_present(list, count, CKM_PKCS5_PBKD2) != 0) { + return -1; + } + if (g_fn->C_GetMechanismInfo(WT_PKCS11_SLOT, CKM_PKCS5_PBKD2, &info) != + CKR_MECHANISM_INVALID) { + return -1; + } + return g_fn->C_GetMechanismInfo(WT_PKCS11_SLOT, CKM_ECDSA, &info) == + CKR_OK ? 0 : -1; +} + +static int test_session_info(void) +{ + CK_SESSION_INFO info; + + if (g_fn->C_GetSessionInfo(g_session, &info) != CKR_OK) { + return -1; + } + return (info.flags & (CKF_SERIAL_SESSION | CKF_RW_SESSION)) == + (CKF_SERIAL_SESSION | CKF_RW_SESSION) ? 0 : -1; +} + +static int test_read_only_session(void) +{ + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_SESSION_INFO info; + CK_RV rv; + + rv = g_fn->C_OpenSession(WT_PKCS11_SLOT, CKF_SERIAL_SESSION, + NULL, NULL, &session); + if (rv == CKR_OK) { + rv = g_fn->C_GetSessionInfo(session, &info); + } + if (session != CK_INVALID_HANDLE) { + (void)g_fn->C_CloseSession(session); + } + if (rv != CKR_OK || (info.flags & CKF_RW_SESSION) != 0u) { + return -1; + } + return 0; +} + +static CK_RV session_notify(CK_SESSION_HANDLE session, + CK_NOTIFICATION event, CK_VOID_PTR application) +{ + (void)session; + (void)event; + (void)application; + return CKR_OK; +} + +static int test_session_callbacks(void) +{ + static int application_data; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_RV rv; + + rv = g_fn->C_OpenSession(WT_PKCS11_SLOT, CKF_SERIAL_SESSION, + &application_data, session_notify, &session); + if (rv == CKR_OK) { + rv = g_fn->C_CloseSession(session); + } + if (rv != CKR_OK) { + return -1; + } + return g_fn->C_OpenSession(WT_PKCS11_SLOT, CKF_SERIAL_SESSION, NULL, + NULL, NULL) == CKR_ARGUMENTS_BAD ? 0 : -1; +} + +static int test_pin_change(void) +{ + CK_RV rv; + + rv = g_fn->C_SetPIN(g_session, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u), g_alt_pin, + (CK_ULONG)(sizeof(g_alt_pin) - 1u)); + if (rv != CKR_OK || g_fn->C_Logout(g_session) != CKR_OK) { + return -1; + } + rv = g_fn->C_Login(g_session, CKU_USER, g_alt_pin, + (CK_ULONG)(sizeof(g_alt_pin) - 1u)); + if (rv != CKR_OK) { + return -1; + } + rv = g_fn->C_SetPIN(g_session, g_alt_pin, + (CK_ULONG)(sizeof(g_alt_pin) - 1u), g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)); + return rv == CKR_OK ? 0 : -1; +} + +static void run_core_suite(void) +{ + report_case("library initialize", library_initialize() == 0); + if (g_fn == NULL) { + return; + } + run_case("library info", test_library_info); + run_case("slot enumeration", test_slot_list); + run_case("slot and token info", test_slot_token_info); + run_case("mechanism enumeration", test_mechanism_list); + report_case("token and user login", token_login() == 0); + if (g_session == CK_INVALID_HANDLE) { + suite_cleanup(); + return; + } + run_case("session info", test_session_info); + run_case("read-only session", test_read_only_session); + run_case("session callbacks accepted", test_session_callbacks); + run_case("PIN change", test_pin_change); + if (g_keep_session == 0) { + suite_cleanup(); + } +} + +static CK_RV create_data_object(const char *label, CK_BBOOL private_value, + CK_OBJECT_HANDLE *object) +{ + CK_BYTE value[] = "wolfTrust PKCS11 object"; + CK_ATTRIBUTE attributes[] = { + { CKA_CLASS, &g_data_class, sizeof(g_data_class) }, + { CKA_TOKEN, &g_false, sizeof(g_false) }, + { CKA_PRIVATE, &private_value, sizeof(private_value) }, + { CKA_MODIFIABLE, &g_true, sizeof(g_true) }, + { CKA_LABEL, (void *)label, (CK_ULONG)strlen(label) }, + { CKA_VALUE, value, sizeof(value) - 1u }, + }; + + return g_fn->C_CreateObject(g_session, attributes, + WT_ARRAY_SIZE(attributes), object); +} + +static int test_object_create_size(void) +{ + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_ULONG size = 0u; + CK_RV rv; + + rv = create_data_object("size-object", CK_FALSE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_GetObjectSize(g_session, object, &size); + } + if (object != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, object); + } + return rv == CKR_OK && size > 0u ? 0 : -1; +} + +static int test_object_attributes(void) +{ + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_OBJECT_CLASS object_class = 0u; + CK_BYTE value[32]; + CK_ATTRIBUTE query = { CKA_VALUE, NULL, ~(CK_ULONG)0u }; + CK_ATTRIBUTE attributes[] = { + { CKA_CLASS, &object_class, sizeof(object_class) }, + { CKA_VALUE, value, sizeof(value) }, + }; + CK_RV rv; + + rv = create_data_object("attribute-object", CK_FALSE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_GetAttributeValue(g_session, object, &query, 1u); + } + if (rv == CKR_OK && (query.ulValueLen == 0u || + query.ulValueLen > sizeof(value))) { + rv = CKR_FUNCTION_FAILED; + } + if (rv == CKR_OK) { + rv = g_fn->C_GetAttributeValue(g_session, object, attributes, + WT_ARRAY_SIZE(attributes)); + } + if (object != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, object); + } + if (rv != CKR_OK || object_class != CKO_DATA || + attributes[1].ulValueLen == 0u || + attributes[1].ulValueLen > sizeof(value)) { + return -1; + } + return 0; +} + +static int test_object_set_attribute(void) +{ + static const char new_label[] = "updated-object"; + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_BYTE label[32]; + CK_ATTRIBUTE set = { + CKA_LABEL, (void *)new_label, sizeof(new_label) - 1u + }; + CK_ATTRIBUTE get = { CKA_LABEL, label, sizeof(label) }; + CK_RV rv; + + rv = create_data_object("old-object", CK_FALSE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_SetAttributeValue(g_session, object, &set, 1u); + } + if (rv == CKR_OK) { + rv = g_fn->C_GetAttributeValue(g_session, object, &get, 1u); + } + if (object != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, object); + } + if (rv != CKR_OK || get.ulValueLen != sizeof(new_label) - 1u || + memcmp(label, new_label, sizeof(new_label) - 1u) != 0) { + return -1; + } + return 0; +} + +static int test_object_copy(void) +{ + static const char copy_label[] = "copy-object"; + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + CK_ATTRIBUTE label = { + CKA_LABEL, (void *)copy_label, sizeof(copy_label) - 1u + }; + CK_RV rv; + + rv = create_data_object("source-object", CK_FALSE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_CopyObject(g_session, object, &label, 1u, ©); + } + if (copy != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, copy); + } + if (object != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, object); + } + return rv == CKR_OK && copy != CK_INVALID_HANDLE ? 0 : -1; +} + +static int test_object_find(void) +{ + static const char label_text[] = "find-object"; + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found[65]; + CK_ULONG count = 0u; + CK_ATTRIBUTE label = { + CKA_LABEL, (void *)label_text, sizeof(label_text) - 1u + }; + CK_RV rv; + + rv = create_data_object(label_text, CK_FALSE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_FindObjectsInit(g_session, &label, 1u); + } + if (rv == CKR_OK) { + rv = g_fn->C_FindObjects(g_session, found, WT_ARRAY_SIZE(found), + &count); + } + if (rv == CKR_OK) { + rv = g_fn->C_FindObjectsFinal(g_session); + } + if (object != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, object); + } + return rv == CKR_OK && count == 1u && found[0] == object ? 0 : -1; +} + +static int test_private_visibility(void) +{ + static const char label_text[] = "private-object"; + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found = CK_INVALID_HANDLE; + CK_ULONG count = 0u; + CK_ATTRIBUTE label = { + CKA_LABEL, (void *)label_text, sizeof(label_text) - 1u + }; + CK_RV rv; + + rv = create_data_object(label_text, CK_TRUE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_Logout(g_session); + } + if (rv == CKR_OK) { + rv = g_fn->C_FindObjectsInit(g_session, &label, 1u); + } + if (rv == CKR_OK) { + rv = g_fn->C_FindObjects(g_session, &found, 1u, &count); + } + if (rv == CKR_OK) { + rv = g_fn->C_FindObjectsFinal(g_session); + } + if (g_fn->C_Login(g_session, CKU_USER, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)) != CKR_OK) { + rv = CKR_FUNCTION_FAILED; + } + if (object != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, object); + } + return rv == CKR_OK && count == 0u ? 0 : -1; +} + +static int test_object_destroy(void) +{ + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + CK_OBJECT_CLASS object_class = 0u; + CK_ATTRIBUTE attribute = { + CKA_CLASS, &object_class, sizeof(object_class) + }; + CK_RV rv; + + rv = create_data_object("destroy-object", CK_FALSE, &object); + if (rv == CKR_OK) { + rv = g_fn->C_DestroyObject(g_session, object); + } + if (rv == CKR_OK) { + rv = g_fn->C_GetAttributeValue(g_session, object, &attribute, 1u); + } + return rv == CKR_OBJECT_HANDLE_INVALID ? 0 : -1; +} + +static void run_object_suite(void) +{ + report_case("suite setup", suite_ready() == 0); + if (g_session == CK_INVALID_HANDLE) { + return; + } + run_case("object create and size", test_object_create_size); + run_case("object attributes", test_object_attributes); + run_case("object attribute update", test_object_set_attribute); + run_case("object copy", test_object_copy); + run_case("object find", test_object_find); + run_case("private object visibility", test_private_visibility); + run_case("object destroy", test_object_destroy); + if (g_keep_session == 0) { + suite_cleanup(); + } +} + +static CK_RV generate_aes_key(CK_OBJECT_HANDLE *key) +{ + CK_ULONG value_len = 16u; + CK_MECHANISM mechanism = { CKM_AES_KEY_GEN, NULL, 0u }; + CK_ATTRIBUTE attributes[] = { + { CKA_CLASS, &g_secret_class, sizeof(g_secret_class) }, + { CKA_KEY_TYPE, &g_aes_type, sizeof(g_aes_type) }, + { CKA_VALUE_LEN, &value_len, sizeof(value_len) }, + { CKA_TOKEN, &g_false, sizeof(g_false) }, + { CKA_ENCRYPT, &g_true, sizeof(g_true) }, + { CKA_DECRYPT, &g_true, sizeof(g_true) }, + }; + + return g_fn->C_GenerateKey(g_session, &mechanism, attributes, + WT_ARRAY_SIZE(attributes), key); +} + +static int aes_round_trip(CK_MECHANISM_TYPE type, void *parameter, + CK_ULONG parameter_len) +{ + static const CK_BYTE plain[16] = { + 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, + 0x30, 0x31, 0x32, 0x33, 0x40, 0x41, 0x42, 0x43 + }; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mechanism = { type, parameter, parameter_len }; + CK_BYTE encrypted[64]; + CK_BYTE decrypted[32]; + CK_ULONG encrypted_len = sizeof(encrypted); + CK_ULONG decrypted_len = sizeof(decrypted); + CK_RV rv; + unsigned int step = 1u; + + rv = generate_aes_key(&key); + if (rv == CKR_OK) { + step = 2u; + rv = g_fn->C_EncryptInit(g_session, &mechanism, key); + } + if (rv == CKR_OK) { + step = 3u; + rv = g_fn->C_Encrypt(g_session, (CK_BYTE_PTR)plain, sizeof(plain), + encrypted, &encrypted_len); + } + if (rv == CKR_OK) { + step = 4u; + rv = g_fn->C_DecryptInit(g_session, &mechanism, key); + } + if (rv == CKR_OK) { + step = 5u; + rv = g_fn->C_Decrypt(g_session, encrypted, encrypted_len, + decrypted, &decrypted_len); + } + if (key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, key); + } + if (rv != CKR_OK || decrypted_len != sizeof(plain) || + memcmp(decrypted, plain, sizeof(plain)) != 0) { + app_prefix(); + wt_pkcs11_puts("AES detail mechanism="); + wt_pkcs11_print_u32((unsigned int)type); + wt_pkcs11_puts(" step="); + wt_pkcs11_print_u32(step); + wt_pkcs11_puts(" rv="); + wt_pkcs11_print_u32((unsigned int)rv); + wt_pkcs11_puts(" encrypted="); + wt_pkcs11_print_u32((unsigned int)encrypted_len); + wt_pkcs11_puts(" decrypted="); + wt_pkcs11_print_u32((unsigned int)decrypted_len); + wt_pkcs11_puts("\r\n"); + return -1; + } + return 0; +} + +static int test_random(void) +{ + CK_BYTE random[32]; + + memset(random, 0, sizeof(random)); + if (g_fn->C_GenerateRandom(g_session, random, sizeof(random)) != CKR_OK) { + return -1; + } + return buffer_nonzero(random, sizeof(random)) != 0 ? 0 : -1; +} + +static int test_sha256_single(void) +{ + CK_MECHANISM mechanism = { CKM_SHA256, NULL, 0u }; + CK_BYTE digest[32]; + CK_ULONG digest_len = ~(CK_ULONG)0u; + CK_RV rv; + + rv = g_fn->C_DigestInit(g_session, &mechanism); + if (rv == CKR_OK) { + rv = g_fn->C_Digest(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, NULL, &digest_len); + } + if (rv != CKR_OK || digest_len != sizeof(digest)) { + return -1; + } + digest_len = sizeof(digest); + if (rv == CKR_OK) { + rv = g_fn->C_Digest(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, digest, &digest_len); + } + if (rv != CKR_OK || digest_len != sizeof(digest) || + memcmp(digest, g_sha256_abc, sizeof(digest)) != 0) { + return -1; + } + return 0; +} + +static int test_sha256(void) +{ + CK_MECHANISM mechanism = { CKM_SHA256, NULL, 0u }; + CK_BYTE digest[32]; + CK_ULONG digest_len; + CK_RV rv; + + if (test_sha256_single() != 0) { + return -1; + } + digest_len = sizeof(digest); + rv = g_fn->C_DigestInit(g_session, &mechanism); + if (rv == CKR_OK) { + rv = g_fn->C_DigestUpdate(g_session, (CK_BYTE_PTR)g_digest_input, 1u); + } + if (rv == CKR_OK) { + rv = g_fn->C_DigestUpdate(g_session, + (CK_BYTE_PTR)&g_digest_input[1], 2u); + } + if (rv == CKR_OK) { + rv = g_fn->C_DigestFinal(g_session, digest, &digest_len); + } + return rv == CKR_OK && digest_len == sizeof(digest) && + memcmp(digest, g_sha256_abc, sizeof(digest)) == 0 ? 0 : -1; +} + +static int test_hmac_sha256(void) +{ + static CK_BYTE key_value[] = "wolfTrust-hmac-key"; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_MECHANISM mechanism = { CKM_SHA256_HMAC, NULL, 0u }; + CK_ATTRIBUTE attributes[] = { + { CKA_CLASS, &g_secret_class, sizeof(g_secret_class) }, + { CKA_KEY_TYPE, &g_generic_type, sizeof(g_generic_type) }, + { CKA_VALUE, key_value, sizeof(key_value) - 1u }, + { CKA_TOKEN, &g_false, sizeof(g_false) }, + { CKA_SIGN, &g_true, sizeof(g_true) }, + { CKA_VERIFY, &g_true, sizeof(g_true) }, + }; + CK_BYTE signature[32]; + CK_ULONG signature_len = sizeof(signature); + CK_RV rv; + + rv = g_fn->C_CreateObject(g_session, attributes, + WT_ARRAY_SIZE(attributes), &key); + if (rv == CKR_OK) { + rv = g_fn->C_SignInit(g_session, &mechanism, key); + } + if (rv == CKR_OK) { + rv = g_fn->C_Sign(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, + signature, &signature_len); + } + if (rv == CKR_OK) { + rv = g_fn->C_VerifyInit(g_session, &mechanism, key); + } + if (rv == CKR_OK) { + rv = g_fn->C_Verify(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, + signature, signature_len); + } + if (key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, key); + } + return rv == CKR_OK ? 0 : -1; +} + +static int test_aes_cbc(void) +{ + CK_BYTE iv[16] = { 0u }; + + return aes_round_trip(CKM_AES_CBC, iv, sizeof(iv)); +} + +static int test_aes_ctr(void) +{ + CK_AES_CTR_PARAMS parameters; + + memset(¶meters, 0, sizeof(parameters)); + parameters.ulCounterBits = 128u; + return aes_round_trip(CKM_AES_CTR, ¶meters, sizeof(parameters)); +} + +static int test_aes_gcm(void) +{ + CK_BYTE iv[12] = { 1u, 2u, 3u, 4u }; + CK_BYTE aad[8] = { 5u, 6u, 7u, 8u }; + CK_GCM_PARAMS parameters; + + memset(¶meters, 0, sizeof(parameters)); + parameters.pIv = iv; + parameters.ulIvLen = sizeof(iv); + parameters.ulIvBits = sizeof(iv) * 8u; + parameters.pAAD = aad; + parameters.ulAADLen = sizeof(aad); + parameters.ulTagBits = 128u; + return aes_round_trip(CKM_AES_GCM, ¶meters, sizeof(parameters)); +} + +static int test_aes_ccm(void) +{ + CK_BYTE iv[12] = { 1u, 2u, 3u, 4u }; + CK_BYTE aad[8] = { 5u, 6u, 7u, 8u }; + CK_CCM_PARAMS parameters; + + memset(¶meters, 0, sizeof(parameters)); + parameters.ulDataLen = 16u; + parameters.pIv = iv; + parameters.ulIvLen = sizeof(iv); + parameters.pAAD = aad; + parameters.ulAADLen = sizeof(aad); + parameters.ulMacLen = 16u; + return aes_round_trip(CKM_AES_CCM, ¶meters, sizeof(parameters)); +} + +static CK_RV generate_ec_key_pair(CK_BBOOL sign, CK_BBOOL derive, + CK_OBJECT_HANDLE *public_key, + CK_OBJECT_HANDLE *private_key) +{ + CK_MECHANISM mechanism = { CKM_EC_KEY_PAIR_GEN, NULL, 0u }; + CK_ATTRIBUTE public_attributes[] = { + { CKA_CLASS, &g_public_class, sizeof(g_public_class) }, + { CKA_KEY_TYPE, &g_ec_type, sizeof(g_ec_type) }, + { CKA_EC_PARAMS, (void *)g_p256_params, sizeof(g_p256_params) }, + { CKA_TOKEN, &g_false, sizeof(g_false) }, + { CKA_VERIFY, &sign, sizeof(sign) }, + { CKA_DERIVE, &derive, sizeof(derive) }, + }; + CK_ATTRIBUTE private_attributes[] = { + { CKA_CLASS, &g_private_class, sizeof(g_private_class) }, + { CKA_KEY_TYPE, &g_ec_type, sizeof(g_ec_type) }, + { CKA_TOKEN, &g_false, sizeof(g_false) }, + { CKA_PRIVATE, &g_true, sizeof(g_true) }, + { CKA_SENSITIVE, &g_true, sizeof(g_true) }, + { CKA_SIGN, &sign, sizeof(sign) }, + { CKA_DERIVE, &derive, sizeof(derive) }, + }; + + return g_fn->C_GenerateKeyPair(g_session, &mechanism, + public_attributes, + WT_ARRAY_SIZE(public_attributes), + private_attributes, + WT_ARRAY_SIZE(private_attributes), + public_key, private_key); +} + +static int test_ecdsa(void) +{ + static const CK_BYTE digest[32] = { + 0x50, 0x4b, 0x43, 0x53, 0x23, 0x31, 0x31, 0x20, + 0x6f, 0x76, 0x65, 0x72, 0x20, 0x66, 0x77, 0x54, + 0x50, 0x4d, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, + 0x67, 0x68, 0x20, 0x53, 0x50, 0x4d, 0x20, 0x21 + }; + CK_OBJECT_HANDLE public_key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + CK_MECHANISM mechanism = { CKM_ECDSA, NULL, 0u }; + CK_BYTE signature[64]; + CK_ULONG signature_len = sizeof(signature); + CK_RV rv; + + rv = generate_ec_key_pair(CK_TRUE, CK_FALSE, + &public_key, &private_key); + if (rv == CKR_OK) { + rv = g_fn->C_SignInit(g_session, &mechanism, private_key); + } + if (rv == CKR_OK) { + rv = g_fn->C_Sign(g_session, (CK_BYTE_PTR)digest, sizeof(digest), + signature, &signature_len); + } + if (rv == CKR_OK) { + rv = g_fn->C_VerifyInit(g_session, &mechanism, public_key); + } + if (rv == CKR_OK) { + rv = g_fn->C_Verify(g_session, (CK_BYTE_PTR)digest, sizeof(digest), + signature, signature_len); + } + if (private_key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, private_key); + } + if (public_key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, public_key); + } + return rv == CKR_OK ? 0 : -1; +} + +static int test_ecdh(void) +{ + CK_OBJECT_HANDLE public_key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_BYTE point[80]; + CK_ATTRIBUTE point_attribute = { CKA_EC_POINT, point, sizeof(point) }; + CK_ULONG value_len = 32u; + CK_ECDH1_DERIVE_PARAMS parameters; + CK_MECHANISM mechanism; + CK_ATTRIBUTE secret_attributes[] = { + { CKA_CLASS, &g_secret_class, sizeof(g_secret_class) }, + { CKA_KEY_TYPE, &g_generic_type, sizeof(g_generic_type) }, + { CKA_TOKEN, &g_false, sizeof(g_false) }, + { CKA_PRIVATE, &g_false, sizeof(g_false) }, + { CKA_SENSITIVE, &g_true, sizeof(g_true) }, + { CKA_EXTRACTABLE, &g_false, sizeof(g_false) }, + { CKA_VALUE_LEN, &value_len, sizeof(value_len) }, + }; + CK_RV rv; + unsigned int step = 1u; + + rv = generate_ec_key_pair(CK_FALSE, CK_TRUE, + &public_key, &private_key); + if (rv == CKR_OK) { + step = 2u; + rv = g_fn->C_GetAttributeValue(g_session, public_key, + &point_attribute, 1u); + } + memset(¶meters, 0, sizeof(parameters)); + parameters.kdf = CKD_NULL; + parameters.pPublicData = point; + parameters.ulPublicDataLen = point_attribute.ulValueLen; + mechanism.mechanism = CKM_ECDH1_DERIVE; + mechanism.pParameter = ¶meters; + mechanism.ulParameterLen = sizeof(parameters); + if (rv == CKR_OK) { + step = 3u; + rv = g_fn->C_DeriveKey(g_session, &mechanism, private_key, + secret_attributes, + WT_ARRAY_SIZE(secret_attributes), &secret); + } + if (rv == CKR_OK && secret == CK_INVALID_HANDLE) { + step = 4u; + rv = CKR_FUNCTION_FAILED; + } + if (secret != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, secret); + } + if (private_key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, private_key); + } + if (public_key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, public_key); + } + if (rv != CKR_OK) { + app_prefix(); + wt_pkcs11_puts("ECDH detail step="); + wt_pkcs11_print_u32(step); + wt_pkcs11_puts(" rv="); + wt_pkcs11_print_u32((unsigned int)rv); + wt_pkcs11_puts(" point="); + wt_pkcs11_print_u32((unsigned int)point_attribute.ulValueLen); + wt_pkcs11_puts(" secret="); + wt_pkcs11_print_u32((unsigned int)secret); + wt_pkcs11_puts("\r\n"); + } + return rv == CKR_OK ? 0 : -1; +} + +static void run_crypto_suite(void) +{ + report_case("suite setup", suite_ready() == 0); + if (g_session == CK_INVALID_HANDLE) { + return; + } + run_case("random", test_random); + run_case("SHA256 one-shot and multipart", test_sha256); + run_case("HMAC SHA256", test_hmac_sha256); + run_case("AES CBC", test_aes_cbc); + run_case("AES CTR", test_aes_ctr); + run_case("AES GCM", test_aes_gcm); + run_case("AES CCM", test_aes_ccm); + run_case("ECDSA", test_ecdsa); + run_case("ECDH", test_ecdh); + if (g_keep_session == 0) { + suite_cleanup(); + } +} + +static CK_RV test_create_mutex(CK_VOID_PTR_PTR mutex) +{ + *mutex = NULL; + return CKR_OK; +} + +static CK_RV test_mutex_op(CK_VOID_PTR mutex) +{ + (void)mutex; + return CKR_OK; +} + +static int test_bad_initialize_argument(void) +{ + CK_C_INITIALIZE_ARGS arguments; + + memset(&arguments, 0, sizeof(arguments)); + arguments.pReserved = &arguments; + if (g_fn->C_Initialize(&arguments) != CKR_ARGUMENTS_BAD) { + return -1; + } + memset(&arguments, 0, sizeof(arguments)); + arguments.CreateMutex = test_create_mutex; + if (g_fn->C_Initialize(&arguments) != CKR_ARGUMENTS_BAD) { + return -1; + } + arguments.DestroyMutex = test_mutex_op; + arguments.LockMutex = test_mutex_op; + arguments.UnlockMutex = test_mutex_op; + if (g_fn->C_Initialize(&arguments) != CKR_CANT_LOCK) { + return -1; + } + memset(&arguments, 0, sizeof(arguments)); + arguments.flags = CKF_OS_LOCKING_OK; + return g_fn->C_Initialize(&arguments) == CKR_CANT_LOCK ? 0 : -1; +} + +static int test_invalid_slot(void) +{ + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + return g_fn->C_OpenSession(0xffffu, CKF_SERIAL_SESSION, NULL, NULL, + &session) == CKR_SLOT_ID_INVALID ? 0 : -1; +} + +static int test_invalid_session(void) +{ + CK_SESSION_INFO info; + + return g_fn->C_GetSessionInfo(CK_INVALID_HANDLE, &info) == + CKR_SESSION_HANDLE_INVALID ? 0 : -1; +} + +static int test_wrong_pin(void) +{ + static CK_UTF8CHAR wrong_pin[] = "wrong-pin"; + CK_RV rv; + + rv = g_fn->C_Logout(g_session); + if (rv == CKR_OK) { + rv = g_fn->C_Login(g_session, CKU_USER, wrong_pin, + (CK_ULONG)(sizeof(wrong_pin) - 1u)); + } + if (g_fn->C_Login(g_session, CKU_USER, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)) != CKR_OK) { + return -1; + } + return rv == CKR_PIN_INCORRECT ? 0 : -1; +} + +static int test_invalid_object(void) +{ + CK_OBJECT_CLASS object_class = 0u; + CK_ATTRIBUTE attribute = { + CKA_CLASS, &object_class, sizeof(object_class) + }; + + return g_fn->C_GetAttributeValue(g_session, CK_INVALID_HANDLE, + &attribute, 1u) == + CKR_OBJECT_HANDLE_INVALID ? 0 : -1; +} + +static int test_null_create_template(void) +{ + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + + return g_fn->C_CreateObject(g_session, NULL, 1u, &object) == + CKR_ARGUMENTS_BAD ? 0 : -1; +} + +static int test_unknown_mechanism(void) +{ + CK_MECHANISM mechanism = { 0x8000ffffUL, NULL, 0u }; + + return g_fn->C_DigestInit(g_session, &mechanism) == + CKR_MECHANISM_INVALID ? 0 : -1; +} + +static int test_short_digest_buffer(void) +{ + CK_MECHANISM mechanism = { CKM_SHA256, NULL, 0u }; + CK_BYTE digest[32]; + CK_ULONG digest_len = 1u; + CK_RV rv; + + rv = g_fn->C_DigestInit(g_session, &mechanism); + if (rv == CKR_OK) { + rv = g_fn->C_Digest(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, + digest, &digest_len); + } + if (rv != CKR_BUFFER_TOO_SMALL) { + return -1; + } + digest_len = sizeof(digest); + return g_fn->C_Digest(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, + digest, &digest_len) == CKR_OK ? 0 : -1; +} + +static int test_sign_without_init(void) +{ + CK_BYTE signature[64]; + CK_ULONG signature_len = sizeof(signature); + + return g_fn->C_Sign(g_session, (CK_BYTE_PTR)g_digest_input, + sizeof(g_digest_input) - 1u, + signature, &signature_len) == + CKR_OPERATION_NOT_INITIALIZED ? 0 : -1; +} + +static int test_bad_signature(void) +{ + CK_OBJECT_HANDLE public_key = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + CK_MECHANISM mechanism = { CKM_ECDSA, NULL, 0u }; + CK_BYTE digest[32] = { 0x5au }; + CK_BYTE signature[64]; + CK_ULONG signature_len = sizeof(signature); + CK_RV rv; + + rv = generate_ec_key_pair(CK_TRUE, CK_FALSE, + &public_key, &private_key); + if (rv == CKR_OK) { + rv = g_fn->C_SignInit(g_session, &mechanism, private_key); + } + if (rv == CKR_OK) { + rv = g_fn->C_Sign(g_session, digest, sizeof(digest), + signature, &signature_len); + } + if (rv == CKR_OK) { + signature[0] ^= 1u; + rv = g_fn->C_VerifyInit(g_session, &mechanism, public_key); + } + if (rv == CKR_OK) { + rv = g_fn->C_Verify(g_session, digest, sizeof(digest), + signature, signature_len); + } + if (private_key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, private_key); + } + if (public_key != CK_INVALID_HANDLE) { + (void)g_fn->C_DestroyObject(g_session, public_key); + } + if (rv != CKR_SIGNATURE_INVALID) { + wt_pkcs11_puts("bad signature rv="); + wt_pkcs11_print_u32((unsigned int)rv); + wt_pkcs11_puts("\r\n"); + } + return rv == CKR_SIGNATURE_INVALID ? 0 : -1; +} + +#define WT_PKCS11_LOCKOUT_BOUND 16u + +static int test_pin_lockout(void) +{ + static CK_UTF8CHAR wrong_pin[] = "wrong-pin"; + CK_TOKEN_INFO info; + unsigned int attempt; + CK_RV rv = CKR_OK; + int result = 0; + + if (g_fn->C_Logout(g_session) != CKR_OK) { + return -1; + } + /* The provider's limit is its own build option, so find it by trying. */ + for (attempt = 0u; attempt <= WT_PKCS11_LOCKOUT_BOUND; attempt++) { + rv = g_fn->C_Login(g_session, CKU_USER, wrong_pin, + (CK_ULONG)(sizeof(wrong_pin) - 1u)); + if (rv != CKR_PIN_INCORRECT) { + break; + } + } + if (attempt == 0u || rv != CKR_PIN_LOCKED) { + result = -1; + } + if (g_fn->C_Login(g_session, CKU_USER, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)) != CKR_PIN_LOCKED) { + result = -1; + } + if (g_fn->C_CloseSession(g_session) != CKR_OK) { + result = -1; + } + g_session = CK_INVALID_HANDLE; + if (g_fn->C_OpenSession(WT_PKCS11_SLOT, + CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &g_session) != CKR_OK) { + return -1; + } + if (g_fn->C_Login(g_session, CKU_USER, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)) != CKR_PIN_LOCKED) { + result = -1; + } + if (g_fn->C_GetTokenInfo(WT_PKCS11_SLOT, &info) != CKR_OK || + (info.flags & CKF_USER_PIN_LOCKED) == 0u) { + result = -1; + } + if (g_fn->C_Login(g_session, CKU_SO, g_so_pin, + (CK_ULONG)(sizeof(g_so_pin) - 1u)) != CKR_OK || + g_fn->C_InitPIN(g_session, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)) != CKR_OK || + g_fn->C_Logout(g_session) != CKR_OK || + g_fn->C_Login(g_session, CKU_USER, g_user_pin, + (CK_ULONG)(sizeof(g_user_pin) - 1u)) != CKR_OK) { + return -1; + } + return result; +} + +static void run_negative_suite(void) +{ + report_case("suite setup", suite_ready() == 0); + if (g_session == CK_INVALID_HANDLE) { + return; + } + run_case("reserved initialize argument rejected", + test_bad_initialize_argument); + run_case("invalid slot rejected", test_invalid_slot); + run_case("invalid session rejected", test_invalid_session); + run_case("wrong PIN rejected", test_wrong_pin); + run_case("invalid object rejected", test_invalid_object); + run_case("NULL create template rejected", test_null_create_template); + run_case("unknown mechanism rejected", test_unknown_mechanism); + run_case("short digest buffer reported", test_short_digest_buffer); + run_case("sign without init rejected", test_sign_without_init); + run_case("bad signature rejected", test_bad_signature); + run_case("PIN lockout and SO recovery", test_pin_lockout); + if (g_keep_session == 0) { + suite_cleanup(); + } +} + +static int test_profile_disabled(void) +{ + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + + return g_fn->C_CopyObject(g_session, 1u, NULL, 0u, &object) == + CKR_FUNCTION_NOT_SUPPORTED ? 0 : -1; +} + +static int test_profile_forged(void) +{ + uint8_t request[12]; + uint8_t response[12]; + size_t response_len = 0u; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + + wt_pkcs11_writer_init(&writer, request, sizeof(request)); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_VERSION); + wt_pkcs11_put_u32(&writer, WT_P11_COPY_OBJECT); + if (writer.error != 0 || wt_pkcs11_psa_call(request, writer.used, + response, sizeof(response), &response_len) != 0) { + return -1; + } + wt_pkcs11_reader_init(&reader, response, response_len); + if (wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_MAGIC || + wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_VERSION || + wt_pkcs11_get_u32(&reader) != CKR_FUNCTION_NOT_SUPPORTED) { + return -1; + } + return reader.error == 0 && reader.used == reader.length ? 0 : -1; +} + +static void run_profile_suite(void) +{ + report_case("suite setup", suite_setup() == 0); + if (g_session == CK_INVALID_HANDLE) { + return; + } + run_case("profile mechanisms", test_mechanism_list); + run_case("random", test_random); + run_case("SHA256 one-shot", test_sha256_single); + run_case("ECDSA", test_ecdsa); + run_case("disabled operation rejected", test_profile_disabled); + run_case("forged disabled operation rejected", test_profile_forged); + run_case("bad signature rejected", test_bad_signature); + run_case("PIN lockout and SO recovery", test_pin_lockout); + suite_cleanup(); +} + +const char *wt_pkcs11_suite_name(void) +{ +#if defined(WT_PKCS11_SUITE_PROFILE) + return "profile"; +#elif defined(WT_PKCS11_SUITE_CORE) + return "core"; +#elif defined(WT_PKCS11_SUITE_OBJECTS) + return "objects"; +#elif defined(WT_PKCS11_SUITE_CRYPTO) + return "crypto"; +#elif defined(WT_PKCS11_SUITE_NEGATIVE) + return "negative"; +#else + return "all"; +#endif +} + +int wt_pkcs11_run(void) +{ + g_passed = 0u; + g_failed = 0u; + +#if defined(WT_PKCS11_SUITE_PROFILE) + run_profile_suite(); +#endif + +#if defined(WT_PKCS11_SUITE_ALL) + g_keep_session = 1; +#endif +#if defined(WT_PKCS11_SUITE_CORE) || defined(WT_PKCS11_SUITE_ALL) + run_core_suite(); +#endif +#if defined(WT_PKCS11_SUITE_OBJECTS) || defined(WT_PKCS11_SUITE_ALL) + run_object_suite(); +#endif +#if defined(WT_PKCS11_SUITE_CRYPTO) || defined(WT_PKCS11_SUITE_ALL) + run_crypto_suite(); +#endif +#if defined(WT_PKCS11_SUITE_NEGATIVE) || defined(WT_PKCS11_SUITE_ALL) + run_negative_suite(); +#endif +#if defined(WT_PKCS11_SUITE_ALL) + g_keep_session = 0; + suite_cleanup(); +#endif + + app_prefix(); + wt_pkcs11_puts("TOTAL PASSED: "); + wt_pkcs11_print_u32(g_passed); + wt_pkcs11_puts("\r\n"); + app_prefix(); + wt_pkcs11_puts("TOTAL FAILED: "); + wt_pkcs11_print_u32(g_failed); + wt_pkcs11_puts("\r\n"); + return g_failed == 0u ? 0 : -1; +} diff --git a/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.h b/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.h new file mode 100644 index 000000000..7d59bd959 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-pkcs11/pkcs11_tests.h @@ -0,0 +1,31 @@ +/* pkcs11_tests.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_PKCS11_TESTS_H +#define WOLFTRUST_PKCS11_TESTS_H + +int wt_pkcs11_run(void); +const char *wt_pkcs11_suite_name(void); + +void wt_pkcs11_puts(const char *text); +void wt_pkcs11_print_u32(unsigned int value); +void wt_pkcs11_wait(void); + +#endif /* WOLFTRUST_PKCS11_TESTS_H */ diff --git a/tests/firmware/stm32h563-tpm/freertos-pkcs11/user_settings.h b/tests/firmware/stm32h563-tpm/freertos-pkcs11/user_settings.h new file mode 100644 index 000000000..22e6a9c86 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-pkcs11/user_settings.h @@ -0,0 +1,85 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_FREERTOS_PKCS11_USER_SETTINGS_H +#define WOLFTRUST_FREERTOS_PKCS11_USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define NO_FILESYSTEM +#define NO_WOLFSSL_DIR +#define WOLFSSL_USER_IO +#define NO_WRITEV +#define SIZEOF_LONG_LONG 8 +#define USE_WOLF_STRCASECMP +#define FREERTOS + +/* Route wolfCrypt and wolfPKCS11 allocations through the FreeRTOS heap. + * The application supplies realloc because heap_4 only exposes malloc/free. */ +#define XMALLOC_USER + +/* __ARM_EABI__ otherwise selects wolfTPM's POSIX sleep fallback before its + * FreeRTOS case. Keep all waits on the guest scheduler. */ +void wt_pkcs11_sleep_ms(unsigned int ms); +#define XSLEEP_MS(ms) wt_pkcs11_sleep_ms(ms) + +#define WOLFSSL_SP_MATH +#define WOLFSSL_SP_SMALL +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_SP_NO_DYN_STACK +#define ECC_TIMING_RESISTANT + +#define HAVE_ECC +#define ECC_USER_CURVES +#define NO_ECC192 +#define NO_ECC224 +#define NO_ECC384 +#define NO_ECC521 + +#define NO_SHA +#define WOLFSSL_AES_COUNTER +#define HAVE_AESCTR +#define WOLFSSL_AES_CFB +#define HAVE_AESGCM +#define HAVE_AESCCM +#define HAVE_HKDF +#define HAVE_PBKDF2 +#define WOLFPKCS11_PBKDF2 +#define WOLFSSL_HAVE_PRF +#define WOLFSSL_ECDSA_DETERMINISTIC_K + +#define HAVE_HASHDRBG +#define CUSTOM_RAND_GENERATE_BLOCK wolftrust_guest_rng_stub +#ifndef WOLFTRUST_GUEST_RNG_STUB_DECLARED +#define WOLFTRUST_GUEST_RNG_STUB_DECLARED +int wolftrust_guest_rng_stub(unsigned char *output, unsigned int sz); +#endif + +#define NO_RSA +#define NO_DH +#define NO_DSA +#define NO_DES3 +#define NO_MD5 +#define NO_PKCS12 +#define NO_ASN_TIME + +#define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 + +#endif /* WOLFTRUST_FREERTOS_PKCS11_USER_SETTINGS_H */ diff --git a/tests/firmware/stm32h563-tpm/freertos-tpm/FreeRTOSConfig.h b/tests/firmware/stm32h563-tpm/freertos-tpm/FreeRTOSConfig.h new file mode 100644 index 000000000..3f729e16d --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-tpm/FreeRTOSConfig.h @@ -0,0 +1,21 @@ +/* FreeRTOSConfig.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "../freertos-pkcs11/FreeRTOSConfig.h" diff --git a/tests/firmware/stm32h563-tpm/freertos-tpm/main.c b/tests/firmware/stm32h563-tpm/freertos-tpm/main.c new file mode 100644 index 000000000..d5e9fe1d9 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-tpm/main.c @@ -0,0 +1,270 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include + +#include "FreeRTOS.h" +#include "task.h" + +#include "wolfssl/wolfcrypt/random.h" +#include "wolftrust/crypto_native_client.h" + +#include "tpm_tests.h" + +typedef union wt_heap_header { + size_t size; + uint64_t align; +} wt_heap_header_t; + +extern uint32_t _sidata; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; +extern uint32_t _estack; + +#define USART3_BASE 0x40004800u +#define USART_CR1(b) (*(volatile uint32_t *)((b) + 0x00u)) +#define USART_CR2(b) (*(volatile uint32_t *)((b) + 0x04u)) +#define USART_CR3(b) (*(volatile uint32_t *)((b) + 0x08u)) +#define USART_BRR(b) (*(volatile uint32_t *)((b) + 0x0Cu)) +#define USART_ISR(b) (*(volatile uint32_t *)((b) + 0x1Cu)) +#define USART_TDR(b) (*(volatile uint32_t *)((b) + 0x28u)) +#define USART_CR1_UE (1u << 0) +#define USART_CR1_RE (1u << 2) +#define USART_CR1_TE (1u << 3) +#define USART_ISR_TXE (1u << 7) + +static void default_handler(void) +{ + for (;;) {} +} + +void Reset_Handler(void); +void NMI_Handler(void) __attribute__((weak, alias("default_handler"))); +void HardFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("default_handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void SVC_Handler(void); +void PendSV_Handler(void); +void SysTick_Handler(void); +void DebugMon_Handler(void) __attribute__((weak, alias("default_handler"))); + +__attribute__((used, section(".vectors"))) +const uint32_t g_vectors[16] = { + [0] = (uint32_t)&_estack, + [1] = (uint32_t)&Reset_Handler, + [2] = (uint32_t)&NMI_Handler, + [3] = (uint32_t)&HardFault_Handler, + [4] = (uint32_t)&MemManage_Handler, + [5] = (uint32_t)&BusFault_Handler, + [6] = (uint32_t)&UsageFault_Handler, + [11] = (uint32_t)&SVC_Handler, + [12] = (uint32_t)&DebugMon_Handler, + [14] = (uint32_t)&PendSV_Handler, + [15] = (uint32_t)&SysTick_Handler, +}; + +static void copy_data(void) +{ + uint32_t *src = &_sidata; + uint32_t *dst = &_sdata; + + while (dst < &_edata) { + *dst++ = *src++; + } +} + +static void zero_bss(void) +{ + uint32_t *dst = &_sbss; + + while (dst < &_ebss) { + *dst++ = 0u; + } +} + +static void uart_init(void) +{ + USART_CR1(USART3_BASE) = 0u; + USART_CR2(USART3_BASE) = 0u; + USART_CR3(USART3_BASE) = 0u; + USART_BRR(USART3_BASE) = 120000000u / 115200u; + USART_CR1(USART3_BASE) = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; +} + +static void uart_putc(char c) +{ + while ((USART_ISR(USART3_BASE) & USART_ISR_TXE) == 0u) {} + USART_TDR(USART3_BASE) = (uint32_t)(uint8_t)c; +} + +void wt_tpm_puts(const char *text) +{ + while (*text != '\0') { + uart_putc(*text++); + } +} + +void wt_tpm_print_i32(int value) +{ + char text[12]; + unsigned int magnitude; + unsigned int index = 0u; + + if (value < 0) { + uart_putc('-'); + magnitude = (unsigned int)(-(value + 1)) + 1u; + } + else { + magnitude = (unsigned int)value; + } + if (magnitude == 0u) { + uart_putc('0'); + return; + } + while (magnitude != 0u) { + text[index++] = (char)('0' + (magnitude % 10u)); + magnitude /= 10u; + } + while (index != 0u) { + uart_putc(text[--index]); + } +} + +void wt_tpm_wait(void) +{ + vTaskDelay(pdMS_TO_TICKS(1)); +} + +void wt_tpm_sleep_ms(unsigned int ms) +{ + vTaskDelay(pdMS_TO_TICKS(ms)); +} + +int wc_GenerateSeed(OS_Seed *os, byte *output, word32 size) +{ + (void)os; + return wolftrust_guest_rng_stub((unsigned char *)output, + (unsigned int)size); +} + +void *XMALLOC(size_t size, void *heap, int type) +{ + wt_heap_header_t *header; + + (void)heap; + (void)type; + if (size > (SIZE_MAX - sizeof(*header))) { + return NULL; + } + header = (wt_heap_header_t *)pvPortMalloc(size + sizeof(*header)); + if (header == NULL) { + return NULL; + } + header->size = size; + return (void *)(header + 1); +} + +void XFREE(void *ptr, void *heap, int type) +{ + (void)heap; + (void)type; + if (ptr != NULL) { + vPortFree(((wt_heap_header_t *)ptr) - 1); + } +} + +void *XREALLOC(void *ptr, size_t size, void *heap, int type) +{ + wt_heap_header_t *old_header; + unsigned char *old_data = (unsigned char *)ptr; + unsigned char *new_data; + size_t copy_size; + size_t index; + + if (ptr == NULL) { + return XMALLOC(size, heap, type); + } + if (size == 0u) { + XFREE(ptr, heap, type); + return NULL; + } + old_header = ((wt_heap_header_t *)ptr) - 1; + new_data = (unsigned char *)XMALLOC(size, heap, type); + if (new_data == NULL) { + return NULL; + } + copy_size = old_header->size < size ? old_header->size : size; + for (index = 0u; index < copy_size; index++) { + new_data[index] = old_data[index]; + } + XFREE(ptr, heap, type); + return new_data; +} + +static void test_task(void *arg) +{ + int rc; + + (void)arg; + wt_tpm_puts("tpm-freertos: scheduler started\r\n"); + rc = wt_tpm_run(); + if (rc == 0) { + wt_tpm_puts("tpm-freertos: suite complete\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7f"); +#endif + } + else { + wt_tpm_puts("tpm-freertos: suite failed\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7e"); +#endif + } + vTaskSuspend(NULL); + for (;;) {} +} + +__attribute__((section(".reset"))) +void Reset_Handler(void) +{ + copy_data(); + zero_bss(); + uart_init(); + wt_tpm_puts("tpm-freertos: alive\r\n"); + + if (xTaskCreate(test_task, "tpm", 3072, NULL, + tskIDLE_PRIORITY + 1, NULL) != pdPASS) { + wt_tpm_puts("tpm-freertos: xTaskCreate failed\r\n"); + for (;;) {} + } + vTaskStartScheduler(); + wt_tpm_puts("tpm-freertos: scheduler exited\r\n"); + for (;;) {} +} + +int printf(const char *format, ...) +{ + (void)format; + return 0; +} diff --git a/tests/firmware/stm32h563-tpm/freertos-tpm/user_settings.h b/tests/firmware/stm32h563-tpm/freertos-tpm/user_settings.h new file mode 100644 index 000000000..b1d564f3a --- /dev/null +++ b/tests/firmware/stm32h563-tpm/freertos-tpm/user_settings.h @@ -0,0 +1,77 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_FREERTOS_TPM_USER_SETTINGS_H +#define WOLFTRUST_FREERTOS_TPM_USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define NO_FILESYSTEM +#define NO_WOLFSSL_DIR +#define WOLFSSL_USER_IO +#define NO_WRITEV +#define SIZEOF_LONG_LONG 8 +#define USE_WOLF_STRCASECMP +#define FREERTOS +#define XMALLOC_USER + +void wt_tpm_sleep_ms(unsigned int ms); +#define XSLEEP_MS(ms) wt_tpm_sleep_ms(ms) + +#define WOLFSSL_SP_MATH +#define WOLFSSL_SP_SMALL +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_SP_NO_DYN_STACK +#define ECC_TIMING_RESISTANT + +#define HAVE_ECC +#define ECC_USER_CURVES +#define NO_ECC192 +#define NO_ECC224 +#define NO_ECC384 +#define NO_ECC521 + +#define NO_SHA +#define WOLFSSL_AES_COUNTER +#define WOLFSSL_AES_CFB +#define HAVE_AESGCM +#define HAVE_AESCCM +#define HAVE_HKDF +#define HAVE_PBKDF2 +#define WOLFSSL_HAVE_PRF +#define WOLFSSL_ECDSA_DETERMINISTIC_K + +#define HAVE_HASHDRBG +#define CUSTOM_RAND_GENERATE_BLOCK wolftrust_guest_rng_stub +#ifndef WOLFTRUST_GUEST_RNG_STUB_DECLARED +#define WOLFTRUST_GUEST_RNG_STUB_DECLARED +int wolftrust_guest_rng_stub(unsigned char *output, unsigned int sz); +#endif + +#define NO_RSA +#define NO_DH +#define NO_DSA +#define NO_DES3 +#define NO_MD5 +#define NO_PKCS12 +#define NO_ASN_TIME +#define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 + +#endif /* WOLFTRUST_FREERTOS_TPM_USER_SETTINGS_H */ diff --git a/tests/firmware/stm32h563-tpm/nonsecure/guest.c b/tests/firmware/stm32h563-tpm/nonsecure/guest.c new file mode 100644 index 000000000..c3190dd41 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/nonsecure/guest.c @@ -0,0 +1,216 @@ +/* guest.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Bare-metal wolfTPM client. fwTPM runs inside the Secure service. */ + +#include +#include +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/random.h" + +#include "memory_map.h" +#include "wolftrust/crypto_native_client.h" +#if defined(WT_ENGINE_HSM) +int wolfhsm_guest_init(void); +#endif +#include "wolftrust/tpm_psa_transport.h" + +#include "tpm_tests.h" +#define GUEST_NAME "tpm-guest0" + +#ifndef WT_GUEST_UART_CLOCK_HZ +#define WT_GUEST_UART_CLOCK_HZ 120000000u +#endif + +extern uint32_t _sidata; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; +extern uint32_t _estack; + +#define USART3_BASE 0x40004800u +#define USART_CR1(b) (*(volatile uint32_t *)((b) + 0x00u)) +#define USART_CR2(b) (*(volatile uint32_t *)((b) + 0x04u)) +#define USART_CR3(b) (*(volatile uint32_t *)((b) + 0x08u)) +#define USART_BRR(b) (*(volatile uint32_t *)((b) + 0x0Cu)) +#define USART_ISR(b) (*(volatile uint32_t *)((b) + 0x1Cu)) +#define USART_TDR(b) (*(volatile uint32_t *)((b) + 0x28u)) +#define USART_CR1_UE (1u << 0) +#define USART_CR1_RE (1u << 2) +#define USART_CR1_TE (1u << 3) +#define USART_ISR_TXE (1u << 7) + +static void default_handler(void) { for (;;) {} } + +void Reset_Handler(void); +void NMI_Handler(void) __attribute__((weak, alias("default_handler"))); +void HardFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("default_handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void SVC_Handler(void) __attribute__((weak, alias("default_handler"))); +void DebugMon_Handler(void) __attribute__((weak, alias("default_handler"))); +void PendSV_Handler(void) __attribute__((weak, alias("default_handler"))); +void SysTick_Handler(void) __attribute__((weak, alias("default_handler"))); + +__attribute__((used, section(".vectors"))) +const uint32_t g_vectors[16] = { + [0] = (uint32_t)&_estack, + [1] = (uint32_t)&Reset_Handler, + [2] = (uint32_t)&NMI_Handler, + [3] = (uint32_t)&HardFault_Handler, + [4] = (uint32_t)&MemManage_Handler, + [5] = (uint32_t)&BusFault_Handler, + [6] = (uint32_t)&UsageFault_Handler, + [11] = (uint32_t)&SVC_Handler, + [12] = (uint32_t)&DebugMon_Handler, + [14] = (uint32_t)&PendSV_Handler, + [15] = (uint32_t)&SysTick_Handler +}; + +/* Both guests share USART3 (WT_SHARED_UART=3); lines carry the guest tag. */ +static void wt_uart_init(void) +{ + USART_CR1(USART3_BASE) = 0u; + USART_CR2(USART3_BASE) = 0u; + USART_CR3(USART3_BASE) = 0u; + USART_BRR(USART3_BASE) = WT_GUEST_UART_CLOCK_HZ / 115200u; + USART_CR1(USART3_BASE) = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; +} + +static void wt_uart_putc(char c) +{ + while ((USART_ISR(USART3_BASE) & USART_ISR_TXE) == 0u) { } + USART_TDR(USART3_BASE) = (uint32_t)(uint8_t)c; +} + +static void wt_uart_puts(const char *s) +{ + while (*s) wt_uart_putc(*s++); +} + +static void wt_uart_put_i32(int32_t v) +{ + char buf[12]; + uint32_t u; + uint32_t i = 0; + + if (v < 0) { + wt_uart_putc('-'); + u = (uint32_t)(-(v + 1)) + 1u; + } + else { + u = (uint32_t)v; + } + if (u == 0u) { wt_uart_putc('0'); return; } + while (u != 0u) { buf[i++] = (char)('0' + (u % 10u)); u /= 10u; } + while (i != 0u) wt_uart_putc(buf[--i]); +} + +static void guest_line(const char *s) +{ + wt_uart_puts(GUEST_NAME ": "); + wt_uart_puts(s); + wt_uart_puts("\r\n"); +} + +static void guest_line_i32(const char *s, int32_t v) +{ + wt_uart_puts(GUEST_NAME ": "); + wt_uart_puts(s); + wt_uart_put_i32(v); + wt_uart_puts("\r\n"); +} + +static void wt_copy_data(void) +{ + uint32_t *src = &_sidata; + uint32_t *dst = &_sdata; + while (dst < &_edata) *dst++ = *src++; +} + +static void wt_zero_bss(void) +{ + uint32_t *dst = &_sbss; + while (dst < &_ebss) *dst++ = 0u; +} + +static void wt_spin(uint32_t n) +{ + volatile uint32_t i; + for (i = 0u; i < n; i++) { } +} + +/* ---- entropy: wolfCrypt seeds from the Secure vault RNG over the mediated + * native wire (the native client supplies wolftrust_guest_rng_stub), the + * same source every other guest uses. ---- */ + +int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) +{ + (void)os; + return wolftrust_guest_rng_stub((unsigned char*)output, (unsigned int)sz); +} + +/* ---- guest0: platform hooks for the shared wolfTPM application suite. + * Every wolfTPM2_* call becomes a TPM 2.0 command carried by SERVICE_TPM. */ + +void wt_tpm_puts(const char *text) +{ + wt_uart_puts(text); +} + +void wt_tpm_print_i32(int value) +{ + wt_uart_put_i32((int32_t)value); +} + +void wt_tpm_wait(void) +{ + wt_spin(200000u); +} + +static int run_app(void) +{ + int rc; + + guest_line("alive"); + rc = wt_tpm_run(); +#if defined(WT_TPM_EXIT_BKPT) && (WT_TPM_EXIT_BKPT == 1) + if (rc == 0) { + /* Emulator harness end-marker; never on hardware. */ + __asm volatile("bkpt 0x7f"); + } +#endif + return rc; +} + +void Reset_Handler(void) +{ + wt_copy_data(); + wt_zero_bss(); + wt_uart_init(); + (void)run_app(); + for (;;) { __asm volatile("wfi"); } +} diff --git a/tests/firmware/stm32h563-tpm/nonsecure/guest.ld b/tests/firmware/stm32h563-tpm/nonsecure/guest.ld new file mode 100644 index 000000000..e3073a7dd --- /dev/null +++ b/tests/firmware/stm32h563-tpm/nonsecure/guest.ld @@ -0,0 +1,76 @@ +/* Link script for stm32h563-tpm NS guests. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +ENTRY(Reset_Handler) + +MEMORY { + FLASH (rx) : ORIGIN = GUEST_FLASH_ORIGIN, LENGTH = GUEST_FLASH_LENGTH + RAM (rwx): ORIGIN = GUEST_RAM_ORIGIN, LENGTH = GUEST_RAM_LENGTH +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); +_sidata = LOADADDR(.data); + +SECTIONS { + /* Shared status area at the bottom of NS RAM, reserved by the monitor + * convention; nothing in this test uses it but the secure side reserves + * 0..0x100 across guests. */ + .shared ORIGIN(RAM) (NOLOAD) : { + *(.shared*) + } > RAM + + /* wolfHSM CMSE transport buffer is kept as a NOLOAD reservation so we + * never collide with it if the secure side decides to write a status + * byte; this test does not use HSM. */ + .hsm_transport (ORIGIN(RAM) + 0x100) (NOLOAD) : { + _hsm_transport_base = .; + . += 0x200; + _hsm_transport_end = .; + } > RAM + + .vectors GUEST_FLASH_ORIGIN : { + KEEP(*(.vectors)) + } > FLASH + + .reset : { + KEEP(*(.reset)) + } > FLASH + + .text : { + *(.text*) + *(.rodata*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + + .data : { + _sdata = .; + *(.data*) + _edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : { + _sbss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > RAM +} diff --git a/tests/firmware/stm32h563-tpm/nonsecure/guest_libc.c b/tests/firmware/stm32h563-tpm/nonsecure/guest_libc.c new file mode 100644 index 000000000..8f77b8d94 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/nonsecure/guest_libc.c @@ -0,0 +1,170 @@ +/* guest_libc.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* The libc the freestanding guest links: the Secure runner's string and + * ctype stubs, the two copy loops gcc emits calls to, and a first-fit + * allocator for wolfCrypt's memory.c. */ + +#include +#include +#include + +#include "wolftrust/zeroize.h" + +#include "../../../../src/services/wolfhsm/runner/libc_stubs.c" + +void *memcpy(void *dst, const void *src, size_t n) +{ + unsigned char *d = (unsigned char *)dst; + const unsigned char *s = (const unsigned char *)src; + + while (n-- > 0u) { + *d++ = *s++; + } + return dst; +} + +void *memset(void *dst, int c, size_t n) +{ + unsigned char *d = (unsigned char *)dst; + + while (n-- > 0u) { + *d++ = (unsigned char)c; + } + return dst; +} + +/* wolfPSA allocates a slot per key, so the conformance suites need a heap + * that really frees: one static arena of 8-byte-aligned blocks, first-fit, + * neighbours merged on every free. */ +#ifndef GUEST_HEAP_POOL_SZ +#define GUEST_HEAP_POOL_SZ (32u * 1024u) +#endif + +typedef struct guest_heap_block { + uint32_t size; + uint32_t used; +} guest_heap_block_t; + +static uint8_t s_heap_pool[GUEST_HEAP_POOL_SZ] __attribute__((aligned(8))); +static int s_heap_ready; + +static guest_heap_block_t *guest_heap_next(const guest_heap_block_t *block) +{ + const uint8_t *next = (const uint8_t *)block + sizeof(*block) + block->size; + + if (next >= s_heap_pool + GUEST_HEAP_POOL_SZ) { + return NULL; + } + return (guest_heap_block_t *)(uintptr_t)next; +} + +static void guest_heap_init(void) +{ + guest_heap_block_t *first = (guest_heap_block_t *)(uintptr_t)s_heap_pool; + + if (s_heap_ready == 0) { + first->size = GUEST_HEAP_POOL_SZ - (uint32_t)sizeof(*first); + first->used = 0u; + s_heap_ready = 1; + } +} + +void *malloc(size_t size) +{ + guest_heap_block_t *block; + guest_heap_block_t *rest; + uint32_t need; + + if (size == 0u || size > GUEST_HEAP_POOL_SZ) { + return NULL; + } + guest_heap_init(); + need = (uint32_t)((size + 7u) & ~(size_t)7u); + for (block = (guest_heap_block_t *)(uintptr_t)s_heap_pool; block != NULL; + block = guest_heap_next(block)) { + if (block->used != 0u || block->size < need) { + continue; + } + if (block->size >= need + (uint32_t)sizeof(*block) + 8u) { + rest = (guest_heap_block_t *)(uintptr_t) + ((uint8_t *)block + sizeof(*block) + need); + rest->size = block->size - need - (uint32_t)sizeof(*block); + rest->used = 0u; + block->size = need; + } + block->used = 1u; + return (void *)((uint8_t *)block + sizeof(*block)); + } + return NULL; +} + +void free(void *ptr) +{ + guest_heap_block_t *block; + guest_heap_block_t *next; + + if (ptr == NULL) { + return; + } + block = (guest_heap_block_t *)(uintptr_t)((uint8_t *)ptr - sizeof(*block)); + wt_forceZero(ptr, block->size); + block->used = 0u; + for (block = (guest_heap_block_t *)(uintptr_t)s_heap_pool; block != NULL; + block = guest_heap_next(block)) { + next = guest_heap_next(block); + while (block->used == 0u && next != NULL && next->used == 0u) { + block->size += (uint32_t)sizeof(*next) + next->size; + next = guest_heap_next(block); + } + } +} + +void *realloc(void *ptr, size_t size) +{ + guest_heap_block_t *block; + void *new_ptr; + + if (size == 0u) { + free(ptr); + return NULL; + } + if (ptr == NULL) { + return malloc(size); + } + block = (guest_heap_block_t *)(uintptr_t)((uint8_t *)ptr - sizeof(*block)); + if (block->size >= size) { + return ptr; + } + new_ptr = malloc(size); + if (new_ptr != NULL) { + memcpy(new_ptr, ptr, block->size); + free(ptr); + } + return new_ptr; +} + +/* wolfPKCS11's library init logs through printf; the freestanding guest has + * no stdio and prints its own markers over the UART, so logging is dropped. */ +int printf(const char *fmt, ...) +{ + (void)fmt; + return 0; +} diff --git a/tests/firmware/stm32h563-tpm/nonsecure/user_settings.h b/tests/firmware/stm32h563-tpm/nonsecure/user_settings.h new file mode 100644 index 000000000..1abb648d0 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/nonsecure/user_settings.h @@ -0,0 +1,77 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* wolfCrypt settings for the bare-metal TPM application. */ + +#ifndef WOLFTRUST_TPM_GUEST_USER_SETTINGS_H +#define WOLFTRUST_TPM_GUEST_USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define NO_FILESYSTEM +#define NO_WOLFSSL_DIR +#define WOLFSSL_USER_IO +#define NO_WRITEV +#define SIZEOF_LONG_LONG 8 +#define USE_WOLF_STRCASECMP + +#define WOLFSSL_SP_MATH +#define WOLFSSL_SP_SMALL +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_SP_NO_DYN_STACK +#define ECC_TIMING_RESISTANT + +#define HAVE_ECC +#define ECC_USER_CURVES +#define NO_ECC192 +#define NO_ECC224 +#define NO_ECC384 +#define NO_ECC521 + +#define NO_SHA +#define WOLFSSL_AES_COUNTER +#define WOLFSSL_AES_CFB +#define HAVE_AESGCM +#define HAVE_AESCCM +#define HAVE_HKDF +#define HAVE_PBKDF2 +#define WOLFSSL_HAVE_PRF +#define WOLFSSL_ECDSA_DETERMINISTIC_K + +/* Entropy comes from the Secure side over the SPM-mediated client. */ +#define HAVE_HASHDRBG +#define CUSTOM_RAND_GENERATE_BLOCK wolftrust_guest_rng_stub +#ifndef WOLFTRUST_GUEST_RNG_STUB_DECLARED +#define WOLFTRUST_GUEST_RNG_STUB_DECLARED +int wolftrust_guest_rng_stub(unsigned char *output, unsigned int sz); +#endif + +#define NO_RSA +#define NO_DH +#define NO_DSA +#define NO_DES3 +#define NO_MD5 +#define NO_PKCS12 +#define NO_ASN_TIME + +/* wolfHSM needs anonymous aggregates on; wolfCrypt's C99 auto-detect says no. */ +#define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 + +#endif /* WOLFTRUST_TPM_GUEST_USER_SETTINGS_H */ diff --git a/tests/firmware/stm32h563-tpm/scripts/build_freertos_pkcs11.sh b/tests/firmware/stm32h563-tpm/scripts/build_freertos_pkcs11.sh new file mode 100755 index 000000000..461fcbf69 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/scripts/build_freertos_pkcs11.sh @@ -0,0 +1,148 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +TPM_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +ROOT=$(CDPATH= cd -- "$TPM_DIR/../../.." && pwd) + +. "$ROOT/tests/target/lib/guest_build.sh" + +APP_DIR="$TPM_DIR/freertos-pkcs11" +FREERTOS_DIR="${FREERTOS_DIR:-$ROOT/tests/firmware/zephyr-stm32h5/.workspace/freertos}" +FREERTOS_KERNEL="$FREERTOS_DIR/FreeRTOS/Source" +FREERTOS_PORT="$FREERTOS_KERNEL/portable/GCC/ARM_CM33_NTZ/non_secure" +if [ "${WT_CRYPTO_PROFILE:-default}" = default ]; then + WT_PKCS11_SUITE="${WT_PKCS11_SUITE:-profile}" +else + WT_PKCS11_SUITE="${WT_PKCS11_SUITE:-all}" +fi +BUILD_DIR="${BUILD_DIR:-$TPM_DIR/build/pkcs11-${WT_PKCS11_SUITE}}" +SECURE_CMSE_IMPLIB="${SECURE_CMSE_IMPLIB:-$ROOT/build/secure_cmse_implib.o}" + +WT_GUEST0_FLASH_BASE="${WT_GUEST0_FLASH_BASE:-0x080A0000}" +WT_GUEST0_FLASH_SIZE="${WT_GUEST0_FLASH_SIZE:-0x00020000}" +WT_GUEST0_RAM_BASE="${WT_GUEST0_RAM_BASE:-0x20000000}" +WT_GUEST_RAM_SIZE="${WT_GUEST_RAM_SIZE:-0x00010000}" + +case "$WT_PKCS11_SUITE" in + profile) SUITE_DEF=WT_PKCS11_SUITE_PROFILE ;; + core) SUITE_DEF=WT_PKCS11_SUITE_CORE ;; + objects) SUITE_DEF=WT_PKCS11_SUITE_OBJECTS ;; + crypto) SUITE_DEF=WT_PKCS11_SUITE_CRYPTO ;; + negative) SUITE_DEF=WT_PKCS11_SUITE_NEGATIVE ;; + all) SUITE_DEF=WT_PKCS11_SUITE_ALL ;; + *) + echo "WT_PKCS11_SUITE must be profile, core, objects, crypto, negative, or all" >&2 + exit 2 + ;; +esac + +if [ ! -d "$FREERTOS_KERNEL" ]; then + echo "missing FreeRTOS workspace: $FREERTOS_KERNEL" >&2 + echo "run make -C tests/firmware/zephyr-stm32h5 clone first" >&2 + exit 1 +fi +if [ ! -f "$SECURE_CMSE_IMPLIB" ]; then + echo "missing CMSE import library: $SECURE_CMSE_IMPLIB" >&2 + exit 1 +fi + +mkdir -p "$BUILD_DIR" + +GUEST_CC="${GUEST_CC:-arm-none-eabi-gcc}" +GUEST_OBJCOPY="${GUEST_OBJCOPY:-arm-none-eabi-objcopy}" +GUEST_NM="${GUEST_NM:-arm-none-eabi-nm}" +GUEST_SIZE="${GUEST_SIZE:-arm-none-eabi-size}" + +WOLFPKCS11_DIR="$ROOT/lib/wolfPKCS11" + +FREERTOS_SRCS=" +$FREERTOS_KERNEL/tasks.c +$FREERTOS_KERNEL/list.c +$FREERTOS_KERNEL/queue.c +$FREERTOS_PORT/port.c +$FREERTOS_PORT/portasm.c +$FREERTOS_KERNEL/portable/MemMang/heap_4.c" + +CLIENT_SRCS=" +$ROOT/src/client/pkcs11_proxy.c +$ROOT/src/pkcs11/pkcs11_wire.c" + +# Keep the real PBKDF2 token path while bounding emulator runtime. This value +# is test-only; production builds retain wolfPKCS11's configured iteration count. +wt_guest_prepare_profile + +CFLAGS=" +-std=c99 +-mcpu=cortex-m33 -mthumb -mgeneral-regs-only -mno-unaligned-access +-ffreestanding -fno-builtin -nostdlib -Os -g $WT_GUEST_LTO_FLAGS +-Wall -Wextra -Wno-unused-function -Wno-unused-variable +-Wno-unused-parameter -Wno-type-limits +-ffunction-sections -fdata-sections +-I$APP_DIR +-I$FREERTOS_KERNEL/include +-I$FREERTOS_PORT +-I$ROOT/include +-I$ROOT/port/stm32h563 +-I$WOLFPKCS11_DIR +-DWOLFPKCS11_USER_SETTINGS -DWOLFPKCS11_NO_ENV +-D$SUITE_DEF=1 +-DWT_M33MU_EXPECT_BKPT=1 +-include time.h +-include $APP_DIR/user_settings.h" + +CFLAGS="$CFLAGS $WT_GUEST_PROFILE_FLAGS" + +LDFLAGS="$WT_GUEST_LTO_FLAGS $WT_GUEST_KEEP_FLAGS +-Wl,-u,vTaskStartScheduler,-u,C_GetFunctionList,-u,wt_pkcs11_psa_call + +-Wl,-T$APP_DIR/guest0.ld +-Wl,--defsym=GUEST_FLASH_ORIGIN=$WT_GUEST0_FLASH_BASE +-Wl,--defsym=GUEST_FLASH_LENGTH=$WT_GUEST0_FLASH_SIZE +-Wl,--defsym=GUEST_RAM_ORIGIN=$WT_GUEST0_RAM_BASE +-Wl,--defsym=GUEST_RAM_LENGTH=$WT_GUEST_RAM_SIZE +-Wl,--gc-sections" + +# shellcheck disable=SC2086 +wt_guest_compile_platform "$APP_DIR/main.c" $FREERTOS_SRCS \ + "$ROOT/tests/firmware/stm32h563/nonsecure/libc_stubs_guest.c" "$ROOT/src/client/psa_ffm_client.c" "$ROOT/src/client/pkcs11_psa_transport.c" + +# shellcheck disable=SC2086 +$GUEST_CC $CFLAGS $LDFLAGS \ + -o "$BUILD_DIR/guest0.elf" \ + "$APP_DIR/pkcs11_tests.c" \ + $WT_GUEST_PLATFORM_OBJECTS \ + $CLIENT_SRCS \ + "$SECURE_CMSE_IMPLIB" \ + -lgcc + +NM_OUT=$($GUEST_NM --defined-only "$BUILD_DIR/guest0.elf") || { + echo "FAIL: nm on the FreeRTOS PKCS11 guest failed" >&2 + exit 1 +} +printf '%s\n' "$NM_OUT" | grep -q 'vTaskStartScheduler' || { + echo "FAIL: FreeRTOS scheduler missing from PKCS11 guest" >&2 + exit 1 +} +printf '%s\n' "$NM_OUT" | grep -q 'C_GetFunctionList' || { + echo "FAIL: PKCS11 proxy entry point missing from application guest" >&2 + exit 1 +} +printf '%s\n' "$NM_OUT" | grep -q 'wt_pkcs11_psa_call' || { + echo "FAIL: SERVICE_PKCS11 transport missing from application guest" >&2 + exit 1 +} +if printf '%s\n' "$NM_OUT" | grep -Eq 'WP11_|wolfTPM|TPM2_'; then + echo "FAIL: PKCS11 provider or TPM implementation linked into app guest" >&2 + exit 1 +fi +if printf '%s\n' "$NM_OUT" | \ + grep -Eq 'WolfTrust_HSM_(Submit|Poll|Cancel)|WolfTrust_Attest_'; then + echo "FAIL: retired direct veneer linked into PKCS11 guest" >&2 + exit 1 +fi + +wt_guest_reject_psa_crypto "$NM_OUT" + +$GUEST_OBJCOPY -O binary "$BUILD_DIR/guest0.elf" "$BUILD_DIR/guest0.bin" +$GUEST_SIZE "$BUILD_DIR/guest0.elf" diff --git a/tests/firmware/stm32h563-tpm/scripts/build_freertos_tpm.sh b/tests/firmware/stm32h563-tpm/scripts/build_freertos_tpm.sh new file mode 100755 index 000000000..233c8a045 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/scripts/build_freertos_tpm.sh @@ -0,0 +1,204 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +TPM_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +ROOT=$(CDPATH= cd -- "$TPM_DIR/../../.." && pwd) + +. "$ROOT/tests/target/lib/guest_build.sh" + +. "$ROOT/tests/target/lib/engine.sh" + +APP_DIR="$TPM_DIR/freertos-tpm" +TEST_DIR="$TPM_DIR/tpm-app" +FREERTOS_DIR="${FREERTOS_DIR:-$ROOT/tests/firmware/zephyr-stm32h5/.workspace/freertos}" +FREERTOS_KERNEL="$FREERTOS_DIR/FreeRTOS/Source" +FREERTOS_PORT="$FREERTOS_KERNEL/portable/GCC/ARM_CM33_NTZ/non_secure" +BUILD_DIR="${BUILD_DIR:-$TPM_DIR/build/tpm-freertos}" +SECURE_CMSE_IMPLIB="${SECURE_CMSE_IMPLIB:-$ROOT/build/secure_cmse_implib.o}" + +WT_GUEST0_FLASH_BASE="${WT_GUEST0_FLASH_BASE:-0x080A0000}" +WT_GUEST0_FLASH_SIZE="${WT_GUEST0_FLASH_SIZE:-0x00040000}" +WT_GUEST0_RAM_BASE="${WT_GUEST0_RAM_BASE:-0x20000000}" +WT_GUEST_RAM_SIZE="${WT_GUEST_RAM_SIZE:-0x00010000}" + +if [ ! -d "$FREERTOS_KERNEL" ]; then + echo "missing FreeRTOS workspace: $FREERTOS_KERNEL" >&2 + echo "run make -C tests/firmware/zephyr-stm32h5 clone first" >&2 + exit 1 +fi +if [ ! -f "$SECURE_CMSE_IMPLIB" ]; then + echo "missing CMSE import library: $SECURE_CMSE_IMPLIB" >&2 + exit 1 +fi + +mkdir -p "$BUILD_DIR" + +GUEST_CC="${GUEST_CC:-arm-none-eabi-gcc}" +GUEST_OBJCOPY="${GUEST_OBJCOPY:-arm-none-eabi-objcopy}" +GUEST_NM="${GUEST_NM:-arm-none-eabi-nm}" +GUEST_SIZE="${GUEST_SIZE:-arm-none-eabi-size}" + +WOLFSSL_DIR="$ROOT/lib/wolfSSL" +WOLFTPM_DIR="$ROOT/lib/wolfTPM" + +FREERTOS_SRCS=" +$FREERTOS_KERNEL/tasks.c +$FREERTOS_KERNEL/list.c +$FREERTOS_KERNEL/queue.c +$FREERTOS_PORT/port.c +$FREERTOS_PORT/portasm.c +$FREERTOS_KERNEL/portable/MemMang/heap_4.c" + +WOLFCRYPT_SRCS=" +$WOLFSSL_DIR/wolfcrypt/src/aes.c +$WOLFSSL_DIR/wolfcrypt/src/cryptocb.c +$WOLFSSL_DIR/wolfcrypt/src/ecc.c +$WOLFSSL_DIR/wolfcrypt/src/random.c +$WOLFSSL_DIR/wolfcrypt/src/sha256.c +$WOLFSSL_DIR/wolfcrypt/src/asn.c +$WOLFSSL_DIR/wolfcrypt/src/coding.c +$WOLFSSL_DIR/wolfcrypt/src/error.c +$WOLFSSL_DIR/wolfcrypt/src/hash.c +$WOLFSSL_DIR/wolfcrypt/src/hmac.c +$WOLFSSL_DIR/wolfcrypt/src/memory.c +$WOLFSSL_DIR/wolfcrypt/src/sha.c +$WOLFSSL_DIR/wolfcrypt/src/sp_cortexm.c +$WOLFSSL_DIR/wolfcrypt/src/sp_int.c +$WOLFSSL_DIR/wolfcrypt/src/wolfmath.c +$WOLFSSL_DIR/wolfcrypt/src/wc_port.c +$WOLFSSL_DIR/wolfcrypt/src/sha512.c +$WOLFSSL_DIR/wolfcrypt/src/kdf.c +$WOLFSSL_DIR/wolfcrypt/src/pwdbased.c +$WOLFSSL_DIR/wolfcrypt/src/port/arm/thumb2-aes-asm_c.c +$WOLFSSL_DIR/wolfcrypt/src/port/arm/thumb2-sha256-asm_c.c" + +WOLFTPM_SRCS=" +$WOLFTPM_DIR/src/tpm2.c +$WOLFTPM_DIR/src/tpm2_util.c +$WOLFTPM_DIR/src/tpm2_packet.c +$WOLFTPM_DIR/src/tpm2_wrap.c +$WOLFTPM_DIR/src/tpm2_asn.c +$WOLFTPM_DIR/src/tpm2_crypto.c +$WOLFTPM_DIR/src/tpm2_param_enc.c +$WOLFTPM_DIR/src/tpm2_cryptocb.c" + +ENGINE_DEFS="-DWT_ENGINE_NATIVE=1" +ENGINE_SRCS="$ROOT/src/client/crypto_native_client.c" +if [ "$WT_ENGINE" = hsm ]; then + mkdir -p "$BUILD_DIR/wh_include" + printf '#include "%s"\n' "$ROOT/tests/firmware/stm32h563/nonsecure/wh_settings_guest.h" > "$BUILD_DIR/wh_include/wolfhsm_cfg.h" + ENGINE_DEFS="-DWT_ENGINE_HSM=1 -DWOLFHSM_CFG + -I$ROOT/lib/wolfHSM -I$BUILD_DIR/wh_include + -DWT_WOLFHSM_CLIENT_ID=1" + ENGINE_SRCS="$ROOT/src/client/hsm_psa_transport.c + $ROOT/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/src/wolfhsm_client_glue.c" + for source in wh_client wh_client_crypto wh_client_cryptocb wh_comm \ + wh_crypto wh_keyid wh_message_comm wh_message_crypto \ + wh_message_keystore wh_message_nvm wh_message_customcb \ + wh_message_counter wh_utils; do + ENGINE_SRCS="$ENGINE_SRCS $ROOT/lib/wolfHSM/src/$source.c" + done +fi + +CLIENT_SRCS=" +$ENGINE_SRCS" + +wt_guest_prepare_profile + +CFLAGS=" +-std=c99 +-mcpu=cortex-m33 -mthumb -mgeneral-regs-only -mno-unaligned-access +-ffreestanding -fno-builtin -nostdlib -Os -g $WT_GUEST_LTO_FLAGS +-Wall -Wextra -Wno-unused-function -Wno-unused-variable +-Wno-unused-parameter -Wno-type-limits +-ffunction-sections -fdata-sections +-I$APP_DIR +-I$TEST_DIR +-I$FREERTOS_KERNEL/include +-I$FREERTOS_PORT +-I$ROOT/include +-I$ROOT/port/stm32h563 +-I$WOLFSSL_DIR +-I$WOLFTPM_DIR +-DWOLFSSL_USER_SETTINGS -DWOLFTPM_USER_SETTINGS +-DWOLFTPM_SWTPM -DWOLFTPM_SMALL_STACK +-DWOLF_CRYPTO_CB +$ENGINE_DEFS -DWT_TPM_APP_NAME=\"tpm-freertos\" +-DWT_M33MU_EXPECT_BKPT=1 +-DWOLFSSL_SP_ASM -DWOLFSSL_SP_ARM_CORTEX_M_ASM -DWOLFSSL_ARM_ARCH=8 +-DWOLFSSL_ARMASM -DWOLFSSL_ARMASM_NO_HW_CRYPTO -DWOLFSSL_ARMASM_INLINE +-DWOLFSSL_ARMASM_NO_NEON -DWOLFSSL_ARMASM_THUMB2 +-DNO_ERROR_STRINGS -DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT +-DWC_RESEED_INTERVAL=1000000 +-include $APP_DIR/user_settings.h" + +CFLAGS="$CFLAGS $WT_GUEST_PROFILE_FLAGS" + +# Password-authorized TPM commands need no application-side crypto engine. +WT_TPM_CLIENT_WOLFCRYPT="${WT_TPM_CLIENT_WOLFCRYPT:-$(if [ "${WT_CRYPTO_PROFILE:-default}" = default ]; then echo 0; else echo 1; fi)}" +case "$WT_TPM_CLIENT_WOLFCRYPT" in + 0) + CFLAGS="$CFLAGS -DWOLFTPM2_NO_WOLFCRYPT -UWOLFTPM_SMALL_STACK" + WOLFCRYPT_SRCS= + CLIENT_SRCS= + ;; + 1) ;; + *) echo "WT_TPM_CLIENT_WOLFCRYPT must be 0 or 1" >&2; exit 2 ;; +esac + +LDFLAGS="$WT_GUEST_LTO_FLAGS $WT_GUEST_KEEP_FLAGS +-Wl,-u,vTaskStartScheduler,-u,wt_tpm_psa_transact,-u,TPM2_SWTPM_SendCommand + +-Wl,-T$TPM_DIR/freertos-pkcs11/guest0.ld +-Wl,--defsym=GUEST_FLASH_ORIGIN=$WT_GUEST0_FLASH_BASE +-Wl,--defsym=GUEST_FLASH_LENGTH=$WT_GUEST0_FLASH_SIZE +-Wl,--defsym=GUEST_RAM_ORIGIN=$WT_GUEST0_RAM_BASE +-Wl,--defsym=GUEST_RAM_LENGTH=$WT_GUEST_RAM_SIZE +-Wl,--gc-sections" + +# shellcheck disable=SC2086 +wt_guest_compile_platform "$APP_DIR/main.c" $FREERTOS_SRCS \ + "$ROOT/tests/firmware/stm32h563/nonsecure/libc_stubs_guest.c" "$ROOT/src/client/psa_ffm_client.c" "$ROOT/src/client/tpm_psa_transport.c" + +# shellcheck disable=SC2086 +$GUEST_CC $CFLAGS $LDFLAGS \ + -o "$BUILD_DIR/guest0.elf" \ + "$TEST_DIR/tpm_tests.c" \ + $WT_GUEST_PLATFORM_OBJECTS \ + $WOLFCRYPT_SRCS \ + $WOLFTPM_SRCS \ + $CLIENT_SRCS \ + "$SECURE_CMSE_IMPLIB" \ + -lgcc + +NM_OUT=$($GUEST_NM --defined-only "$BUILD_DIR/guest0.elf") || { + echo "FAIL: nm on the FreeRTOS TPM guest failed" >&2 + exit 1 +} +printf '%s\n' "$NM_OUT" | grep -q 'vTaskStartScheduler' || { + echo "FAIL: FreeRTOS scheduler missing from TPM guest" >&2 + exit 1 +} +printf '%s\n' "$NM_OUT" | grep -q 'wt_tpm_psa_transact' || { + echo "FAIL: SERVICE_TPM transport missing from application guest" >&2 + exit 1 +} +printf '%s\n' "$NM_OUT" | grep -q 'TPM2_SWTPM_SendCommand' || { + echo "FAIL: wolfTPM client is not routed through SERVICE_TPM" >&2 + exit 1 +} +if printf '%s\n' "$NM_OUT" | grep -q 'FWTPM_ProcessCommand'; then + echo "FAIL: fwTPM provider linked into TPM application guest" >&2 + exit 1 +fi +if printf '%s\n' "$NM_OUT" | \ + grep -Eq 'WolfTrust_HSM_(Submit|Poll|Cancel)|WolfTrust_Attest_'; then + echo "FAIL: retired direct veneer linked into TPM guest" >&2 + exit 1 +fi + +wt_guest_reject_psa_crypto "$NM_OUT" + +$GUEST_OBJCOPY -O binary "$BUILD_DIR/guest0.elf" "$BUILD_DIR/guest0.bin" +$GUEST_SIZE "$BUILD_DIR/guest0.elf" diff --git a/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.c b/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.c new file mode 100644 index 000000000..a0f4b6243 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.c @@ -0,0 +1,259 @@ +/* tpm_tests.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include + +#include +#include +#include + +#include "wolftrust/tpm_psa_transport.h" + +#include "tpm_tests.h" + +#ifndef WT_TPM_APP_NAME +#define WT_TPM_APP_NAME "tpm-app" +#endif + +static WOLFTPM2_DEV g_dev; +static WOLFTPM2_KEY g_key; +static TPMT_PUBLIC g_pub; + +#define WT_TPM_HASH_FIXED_BYTES (TPM2_HEADER_SIZE + 2U + 2U + 4U) +#define WT_TPM_HASH_PAYLOAD_BYTES \ + (WT_TPM_FRAME_MAX - WT_TPM_HASH_FIXED_BYTES) +typedef char wt_tpm_hash_frame_nonempty[ + (WT_TPM_FRAME_MAX > WT_TPM_HASH_FIXED_BYTES) ? 1 : -1]; +typedef char wt_tpm_hash_payload_fits[ + (WT_TPM_HASH_PAYLOAD_BYTES <= MAX_DIGEST_BUFFER) ? 1 : -1]; + +static void app_prefix(void) +{ + wt_tpm_puts(WT_TPM_APP_NAME ": "); +} + +static void app_line(const char *text) +{ + app_prefix(); + wt_tpm_puts(text); + wt_tpm_puts("\r\n"); +} + +static void app_line_i32(const char *text, int value) +{ + app_prefix(); + wt_tpm_puts(text); + wt_tpm_print_i32(value); + wt_tpm_puts("\r\n"); +} + +static int tpm_property_matches(UINT32 property, UINT32 expected) +{ + GetCapability_In in; + GetCapability_Out out; + TPML_TAGGED_TPM_PROPERTY* properties; + TPM_RC rc; + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + in.capability = TPM_CAP_TPM_PROPERTIES; + in.property = property; + in.propertyCount = 1U; + rc = TPM2_GetCapability(&in, &out); + properties = &out.capabilityData.data.tpmProperties; + + if (rc != TPM_RC_SUCCESS || + out.capabilityData.capability != TPM_CAP_TPM_PROPERTIES || + properties->count != 1U || + properties->tpmProperty[0].property != property || + properties->tpmProperty[0].value != expected) { + return -1; + } + return 0; +} + +#if defined(WT_PROFILE_FULL) && !WT_PROFILE_FULL +static int tpm_command_capabilities(void) +{ + GetCapability_In in; + GetCapability_Out out; + UINT32 index; + UINT32 command; + int found_sign = 0; + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + in.capability = TPM_CAP_COMMANDS; + in.property = 0; + in.propertyCount = MAX_CAP_CC; + if (TPM2_GetCapability(&in, &out) != TPM_RC_SUCCESS || + out.capabilityData.capability != TPM_CAP_COMMANDS || + out.capabilityData.data.command.count != WT_PROFILE_TPM_COMMAND_COUNT) { + return -1; + } + for (index = 0; index < out.capabilityData.data.command.count; index++) { + command = out.capabilityData.data.command.commandAttributes[index] & + TPMA_CC_commandIndex; + if (command == TPM_CC_Sign) { + found_sign = 1; + } + if (command == TPM_CC_PCR_Extend) { + return -1; + } + } + return found_sign ? 0 : -1; +} +#endif + +static int tpm_max_frame_hash(void) +{ + Hash_In in; + Hash_Out out; + TPM_RC rc; + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + in.data.size = (UINT16)WT_TPM_HASH_PAYLOAD_BYTES; + memset(in.data.buffer, 0xA5, in.data.size); + in.hashAlg = TPM_ALG_SHA256; + in.hierarchy = TPM_RH_NULL; + + rc = TPM2_Hash(&in, &out); + return rc == TPM_RC_SUCCESS && out.outHash.size == 32U ? 0 : -1; +} + +int wt_tpm_run(void) +{ + static const byte digest[32] = { + 0x77, 0x6f, 0x6c, 0x66, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x66, 0x77, 0x54, 0x50, 0x4d, 0x20, + 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x62, 0x79, 0x20, 0x46, 0x46, 0x2d, 0x4d + }; + byte rnd[16]; + byte sig[80]; + byte pcr[32]; + int sig_size = (int)sizeof(sig); + int pcr_size = (int)sizeof(pcr); + int tries; + int rc = -1; + + for (tries = 0; tries < 50 && rc != 0; tries++) { + rc = wolfTPM2_Init(&g_dev, NULL, NULL); + if (rc != 0) { + wt_tpm_psa_close(); + wt_tpm_wait(); + } + } + if (rc != 0) { + app_line_i32("tpm init FAILED rc=", rc); + return -1; + } + app_line("tpm init ok"); + + if (tpm_property_matches(TPM_PT_INPUT_BUFFER, WT_TPM_FRAME_MAX) != 0 || + tpm_property_matches(TPM_PT_MAX_COMMAND_SIZE, + WT_TPM_FRAME_MAX) != 0 || + tpm_property_matches(TPM_PT_MAX_RESPONSE_SIZE, + WT_TPM_FRAME_MAX) != 0) { + app_line("tpm frame caps FAILED"); + return -1; + } + app_line("tpm frame caps ok"); +#if defined(WT_PROFILE_FULL) && !WT_PROFILE_FULL + if (tpm_command_capabilities() != 0) { + app_line("tpm command capabilities FAILED"); + return -1; + } + app_line("tpm command capabilities match profile"); +#endif + + + if (tpm_max_frame_hash() != 0) { + app_line("tpm max frame FAILED"); + return -1; + } + app_line("tpm max frame ok"); + + rc = wolfTPM2_GetRandom(&g_dev, rnd, (word32)sizeof(rnd)); + if (rc != 0) { + app_line_i32("tpm random FAILED rc=", rc); + return -1; + } + app_line("tpm random ok"); + + rc = wolfTPM2_GetKeyTemplate_ECC(&g_pub, + TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA | TPMA_OBJECT_sign, + TPM_ECC_NIST_P256, TPM_ALG_ECDSA); + if (rc == 0) { + rc = wolfTPM2_CreatePrimaryKey(&g_dev, &g_key, TPM_RH_OWNER, &g_pub, + NULL, 0); + } + if (rc != 0) { + app_line_i32("tpm primary FAILED rc=", rc); + return -1; + } + app_line("tpm primary ok"); + + rc = wolfTPM2_SignHash(&g_dev, &g_key, digest, (int)sizeof(digest), sig, + &sig_size); + if (rc != 0) { + app_line_i32("tpm sign FAILED rc=", rc); + return -1; + } + app_line("tpm sign ok"); + + rc = wolfTPM2_VerifyHash(&g_dev, &g_key, sig, sig_size, digest, + (int)sizeof(digest)); + if (rc != 0) { + app_line_i32("tpm verify FAILED rc=", rc); + return -1; + } + app_line("tpm verify ok"); + +#if !defined(WT_PROFILE_TPM_PCR_Extend) || WT_PROFILE_TPM_PCR_Extend + rc = wolfTPM2_ExtendPCR(&g_dev, 7, TPM_ALG_SHA256, digest, + (int)sizeof(digest)); + if (rc == 0) { + rc = wolfTPM2_ReadPCR(&g_dev, 7, TPM_ALG_SHA256, pcr, &pcr_size); + } + if (rc != 0 || pcr_size != 32) { + app_line_i32("tpm pcr FAILED rc=", rc); + return -1; + } + app_line("tpm pcr ok"); +#else + rc = wolfTPM2_ExtendPCR(&g_dev, 7, TPM_ALG_SHA256, digest, + (int)sizeof(digest)); + if (rc != TPM_RC_COMMAND_CODE) { + app_line_i32("disabled tpm command accepted rc=", rc); + return -1; + } + app_line("tpm disabled command rejected"); +#endif + + (void)wolfTPM2_UnloadHandle(&g_dev, &g_key.handle); + (void)wolfTPM2_Cleanup(&g_dev); + app_line("tpm round trip ok"); + return 0; +} diff --git a/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.h b/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.h new file mode 100644 index 000000000..16ed39269 --- /dev/null +++ b/tests/firmware/stm32h563-tpm/tpm-app/tpm_tests.h @@ -0,0 +1,30 @@ +/* tpm_tests.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_TPM_TESTS_H +#define WOLFTRUST_TPM_TESTS_H + +int wt_tpm_run(void); + +void wt_tpm_puts(const char *text); +void wt_tpm_print_i32(int value); +void wt_tpm_wait(void); + +#endif /* WOLFTRUST_TPM_TESTS_H */ diff --git a/tests/firmware/stm32h563-vnet/Makefile b/tests/firmware/stm32h563-vnet/Makefile index 96a0f1149..898a4b5d0 100644 --- a/tests/firmware/stm32h563-vnet/Makefile +++ b/tests/firmware/stm32h563-vnet/Makefile @@ -37,19 +37,21 @@ EMU_TIMEOUT ?= 60 WT_TIMESLICE_MS ?= 2 WT_MAX_GUESTS ?= 2 -# Guest flash/RAM map matches the existing baremetal test target. -WT_GUEST0_FLASH_ORIGIN ?= 0x08020000 -WT_GUEST1_FLASH_ORIGIN ?= 0x08040000 -WT_GUEST_FLASH_SIZE ?= 0x00020000 +# Guest flash/RAM map matches the STM32H563 manifest. +WT_GUEST0_FLASH_ORIGIN ?= 0x080A0000 +WT_GUEST1_FLASH_ORIGIN ?= 0x080E0000 +WT_GUEST0_FLASH_SIZE ?= 0x00040000 +WT_GUEST1_FLASH_SIZE ?= 0x00020000 +WT_GUEST_FLASH_SIZE ?= $(WT_GUEST1_FLASH_SIZE) WT_GUEST0_RAM_BASE := 0x20000000 -WT_GUEST1_RAM_BASE := 0x20008000 -WT_GUEST_RAM_SIZE := 0x00008000 +WT_GUEST1_RAM_BASE := 0x20010000 +WT_GUEST_RAM_SIZE := 0x00010000 # Emulator load offsets — m33mu lays out NS images at fixed offsets in its # own image array and the secure side knows the partitions from # memory_map.h. Match the values used by run-stm32h563-uarts. -WT_GUEST0_EMU_OFFSET ?= 0x20000 -WT_GUEST1_EMU_OFFSET ?= 0x40000 +WT_GUEST0_EMU_OFFSET ?= 0xA0000 +WT_GUEST1_EMU_OFFSET ?= 0xE0000 WT_GUEST_CORE_CLOCK_HZ ?= 240000000 WT_GUEST_UART_CLOCK_HZ ?= 120000000 @@ -109,14 +111,18 @@ all: secure $(GUEST0_BIN) $(GUEST1_BIN) secure: rm -rf $(TOP_BUILD_DIR) CONFIG_VNET=$(CONFIG_VNET) WT_MAX_GUESTS=$(WT_MAX_GUESTS) \ + WT_GUEST0_FLASH_BASE=$(WT_GUEST0_FLASH_ORIGIN) \ + WT_GUEST1_FLASH_BASE=$(WT_GUEST1_FLASH_ORIGIN) \ + WT_GUEST0_FLASH_SIZE=$(WT_GUEST0_FLASH_SIZE) \ + WT_GUEST1_FLASH_SIZE=$(WT_GUEST1_FLASH_SIZE) \ $(MAKE) -C $(ROOT) build/wolftrust.bin build/secure_cmse_implib.o $(BUILD_DIR): mkdir -p $(BUILD_DIR) # Per-guest ELF rule. The two binaries are byte-different only at link -# time: guest0 lives at FLASH/RAM 0x08020000 / 0x20000000, guest1 at -# 0x08040000 / 0x20008000. _estack lands at top of RAM and guest.c +# time: guest0 lives at FLASH/RAM 0x080A0000 / 0x20000000, guest1 at +# 0x080E0000 / 0x20010000. _estack lands at top of RAM and guest.c # selects its role from that value. define GUEST_LINK $(BUILD_DIR)/$(1).elf: $(GUEST_SRCS) nonsecure/guest.ld | $(BUILD_DIR) diff --git a/tests/firmware/stm32h563-vnet/run_vnet_test.sh b/tests/firmware/stm32h563-vnet/run_vnet_test.sh index 52fbfc39b..5634deead 100755 --- a/tests/firmware/stm32h563-vnet/run_vnet_test.sh +++ b/tests/firmware/stm32h563-vnet/run_vnet_test.sh @@ -14,8 +14,8 @@ EMU_TIMEOUT="${EMU_TIMEOUT:-60}" SECURE_BIN="${SECURE_BIN:-$SCRIPT_DIR/../../../build/wolftrust.bin}" GUEST0_BIN="${GUEST0_BIN:-$SCRIPT_DIR/build/guest0.bin}" GUEST1_BIN="${GUEST1_BIN:-$SCRIPT_DIR/build/guest1.bin}" -WT_GUEST0_EMU_OFFSET="${WT_GUEST0_EMU_OFFSET:-0x20000}" -WT_GUEST1_EMU_OFFSET="${WT_GUEST1_EMU_OFFSET:-0x40000}" +WT_GUEST0_EMU_OFFSET="${WT_GUEST0_EMU_OFFSET:-0xA0000}" +WT_GUEST1_EMU_OFFSET="${WT_GUEST1_EMU_OFFSET:-0xE0000}" LOG_FILE=$(mktemp) RC_FILE=$(mktemp) diff --git a/tests/firmware/stm32h563/Makefile b/tests/firmware/stm32h563/Makefile index ebb92b63d..28e1001d9 100644 --- a/tests/firmware/stm32h563/Makefile +++ b/tests/firmware/stm32h563/Makefile @@ -39,12 +39,14 @@ WT_GUEST_CORE_CLOCK_HZ ?= 240000000 WT_GUEST_UART_CLOCK_HZ ?= 120000000 CFLAGS_COMMON += -DWT_GUEST_CORE_CLOCK_HZ=$(WT_GUEST_CORE_CLOCK_HZ) CFLAGS_COMMON += -DWT_GUEST_UART_CLOCK_HZ=$(WT_GUEST_UART_CLOCK_HZ) -WT_GUEST0_FLASH_ORIGIN ?= 0x08020000 -WT_GUEST1_FLASH_ORIGIN ?= 0x08040000 -WT_GUEST_FLASH_SIZE ?= 0x00020000 -WT_GUEST_RAM_SIZE ?= 0x00008000 -WT_GUEST0_EMU_OFFSET ?= 0x20000 -WT_GUEST1_EMU_OFFSET ?= 0x40000 +WT_GUEST0_FLASH_ORIGIN ?= 0x080A0000 +WT_GUEST1_FLASH_ORIGIN ?= 0x080E0000 +WT_GUEST0_FLASH_SIZE ?= 0x00040000 +WT_GUEST1_FLASH_SIZE ?= 0x00020000 +WT_GUEST_FLASH_SIZE ?= $(WT_GUEST1_FLASH_SIZE) +WT_GUEST_RAM_SIZE ?= 0x00010000 +WT_GUEST0_EMU_OFFSET ?= 0xA0000 +WT_GUEST1_EMU_OFFSET ?= 0xE0000 WT_GUEST_FLASH_LENGTH ?= $(WT_GUEST_FLASH_SIZE) WT_BENCH_MIN_RUNTIME_SEC ?= 0.0 CFLAGS_COMMON += -DWT_GUEST0_FLASH_BASE=$(WT_GUEST0_FLASH_ORIGIN)u @@ -455,6 +457,8 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_SHARED_UART=$(WT_SHARED_UART)' \ 'WT_GUEST0_FLASH_ORIGIN=$(WT_GUEST0_FLASH_ORIGIN)' \ 'WT_GUEST1_FLASH_ORIGIN=$(WT_GUEST1_FLASH_ORIGIN)' \ + 'WT_GUEST0_FLASH_SIZE=$(WT_GUEST0_FLASH_SIZE)' \ + 'WT_GUEST1_FLASH_SIZE=$(WT_GUEST1_FLASH_SIZE)' \ 'WT_GUEST_FLASH_SIZE=$(WT_GUEST_FLASH_SIZE)' \ 'WT_GUEST_FLASH_LENGTH=$(WT_GUEST_FLASH_LENGTH)' \ 'WT_GUEST_RAM_SIZE=$(WT_GUEST_RAM_SIZE)' \ @@ -482,6 +486,10 @@ TOPLEVEL_SECURE_MAKE_VARS := \ WT_WOLFCRYPT_ARMASM=$(WT_WOLFCRYPT_ARMASM) \ WT_WOLFCRYPT_STM32_HASH=$(WT_WOLFCRYPT_STM32_HASH) \ WT_SHARED_UART=$(WT_SHARED_UART) \ + WT_GUEST0_FLASH_BASE=$(WT_GUEST0_FLASH_ORIGIN) \ + WT_GUEST1_FLASH_BASE=$(WT_GUEST1_FLASH_ORIGIN) \ + WT_GUEST0_FLASH_SIZE=$(WT_GUEST0_FLASH_SIZE) \ + WT_GUEST1_FLASH_SIZE=$(WT_GUEST1_FLASH_SIZE) \ WT_GUEST_CORE_CLOCK_HZ=$(WT_GUEST_CORE_CLOCK_HZ) \ WT_GUEST_UART_CLOCK_HZ=$(WT_GUEST_UART_CLOCK_HZ) TOPLEVEL_SECURE_ELF_TARGET := build/wolftrust.elf @@ -514,7 +522,7 @@ $(GUEST1_ELF): $(ALL_GUEST1_OBJS) $(SECURE_CMSE_IMPLIB) nonsecure/guest.ld $(BUI -Wl,-Tnonsecure/guest.ld \ -Wl,--defsym=GUEST_FLASH_ORIGIN=$(WT_GUEST1_FLASH_ORIGIN) \ -Wl,--defsym=GUEST_FLASH_LENGTH=$(WT_GUEST_FLASH_LENGTH) \ - -Wl,--defsym=GUEST_RAM_ORIGIN=0x20008000 \ + -Wl,--defsym=GUEST_RAM_ORIGIN=0x20010000 \ -Wl,--defsym=GUEST_RAM_LENGTH=$(WT_GUEST_RAM_SIZE) \ -Wl,--gc-sections \ -o $@ $(ALL_GUEST1_OBJS) $(SECURE_CMSE_IMPLIB) -lgcc @@ -537,7 +545,7 @@ $(GUEST1_ELF): $(NONSECURE_SRCS) nonsecure/guest.ld $(BUILD_MODE_STAMP) | $(BUIL -Wl,-Tnonsecure/guest.ld \ -Wl,--defsym=GUEST_FLASH_ORIGIN=$(WT_GUEST1_FLASH_ORIGIN) \ -Wl,--defsym=GUEST_FLASH_LENGTH=$(WT_GUEST_FLASH_LENGTH) \ - -Wl,--defsym=GUEST_RAM_ORIGIN=0x20008000 \ + -Wl,--defsym=GUEST_RAM_ORIGIN=0x20010000 \ -Wl,--defsym=GUEST_RAM_LENGTH=$(WT_GUEST_RAM_SIZE) \ -o $@ $(NONSECURE_SRCS) -lgcc diff --git a/tests/firmware/stm32h563/run_dual_uart_test.sh b/tests/firmware/stm32h563/run_dual_uart_test.sh index 72b608e48..55ccbe463 100755 --- a/tests/firmware/stm32h563/run_dual_uart_test.sh +++ b/tests/firmware/stm32h563/run_dual_uart_test.sh @@ -13,14 +13,14 @@ WT_CO_STACK_SIZE="${WT_CO_STACK_SIZE:-24576}" WT_WOLFCRYPT_SP_ASM="${WT_WOLFCRYPT_SP_ASM:-1}" WT_WOLFCRYPT_ARMASM="${WT_WOLFCRYPT_ARMASM:-1}" WT_SHARED_UART="${WT_SHARED_UART:-3}" -WT_GUEST0_FLASH_ORIGIN="${WT_GUEST0_FLASH_ORIGIN:-0x08020000}" -WT_GUEST1_FLASH_ORIGIN="${WT_GUEST1_FLASH_ORIGIN:-0x08040000}" +WT_GUEST0_FLASH_ORIGIN="${WT_GUEST0_FLASH_ORIGIN:-0x080A0000}" +WT_GUEST1_FLASH_ORIGIN="${WT_GUEST1_FLASH_ORIGIN:-0x080E0000}" WT_GUEST_FLASH_SIZE="${WT_GUEST_FLASH_SIZE:-0x00020000}" WT_GUEST_FLASH_LENGTH="${WT_GUEST_FLASH_LENGTH:-$WT_GUEST_FLASH_SIZE}" -WT_GUEST_RAM_SIZE="${WT_GUEST_RAM_SIZE:-0x00008000}" +WT_GUEST_RAM_SIZE="${WT_GUEST_RAM_SIZE:-0x00010000}" WT_BENCH_MIN_RUNTIME_SEC="${WT_BENCH_MIN_RUNTIME_SEC:-0.0}" -WT_GUEST0_EMU_OFFSET="${WT_GUEST0_EMU_OFFSET:-0x20000}" -WT_GUEST1_EMU_OFFSET="${WT_GUEST1_EMU_OFFSET:-0x40000}" +WT_GUEST0_EMU_OFFSET="${WT_GUEST0_EMU_OFFSET:-0xA0000}" +WT_GUEST1_EMU_OFFSET="${WT_GUEST1_EMU_OFFSET:-0xE0000}" cleanup() { if [ -n "${EMU_PID:-}" ] && kill -0 "$EMU_PID" 2>/dev/null; then diff --git a/tests/firmware/zephyr-stm32h5/Makefile b/tests/firmware/zephyr-stm32h5/Makefile index c896dbde8..c269640a4 100644 --- a/tests/firmware/zephyr-stm32h5/Makefile +++ b/tests/firmware/zephyr-stm32h5/Makefile @@ -14,8 +14,8 @@ ZEPHYR_BOARD ?= nucleo_h563zi/stm32h563xx/ns EMU_TIMEOUT ?= 300 M33MU ?= m33mu -WT_GUEST0_EMU_OFFSET ?= 0x20000 -WT_GUEST1_EMU_OFFSET ?= 0x40000 +WT_GUEST0_EMU_OFFSET ?= 0xA0000 +WT_GUEST1_EMU_OFFSET ?= 0xE0000 WT_SHARED_UART ?= 3 # Dual-guest demo: Zephyr drives the TEE client as guest-a, a baremetal @@ -32,10 +32,13 @@ WT_FWU_PROBE ?= 0 # Baremetal guest-b memory map — matches WT_GUEST1_FLASH_BASE / WT_GUEST1_RAM_BASE # in port/stm32h563/memory_map.h. Kept here as overridable variables so # this Makefile is self-contained. -WT_GUEST1_FLASH_BASE ?= 0x08040000 +WT_GUEST0_FLASH_BASE ?= 0x080A0000 +WT_GUEST1_FLASH_BASE ?= 0x080E0000 WT_GUEST1_RAM_BASE ?= 0x20010000 -WT_GUEST_FLASH_SIZE ?= 0x00020000 -WT_GUEST_RAM_SIZE ?= 0x00008000 +WT_GUEST0_FLASH_SIZE ?= 0x00040000 +WT_GUEST1_FLASH_SIZE ?= 0x00020000 +WT_GUEST_FLASH_SIZE ?= $(WT_GUEST1_FLASH_SIZE) +WT_GUEST_RAM_SIZE ?= 0x00010000 WT_GUEST_CORE_CLOCK_HZ ?= 240000000 WT_GUEST_UART_CLOCK_HZ ?= 120000000 @@ -74,6 +77,7 @@ FREERTOS_GUEST1_DIR := $(CURDIR)/apps/freertos_guest1 FREERTOS_GUEST1_BIN := $(CURDIR)/build/freertos_guest1/freertos_guest1.bin .PHONY: all clone workspace patch secure build-guest0 build-guest0-psa \ + build-guest0-pkcs11 build-guest0-tpm \ build-guest1 freertos-workspace build-freertos-guest1 \ run run-uarts run-tui zephyr-freertos-uarts clean @@ -105,7 +109,12 @@ ifeq ($(WT_REUSE_SECURE_BUILD),1) test -s "$(SECURE_CMSE_IMPLIB)" else rm -rf "$(TOP_BUILD_DIR)" - WT_MAX_GUESTS="$(WT_MAX_GUESTS)" $(MAKE) -C "$(ROOT)" build/wolftrust.bin build/secure_cmse_implib.o + WT_MAX_GUESTS="$(WT_MAX_GUESTS)" \ + WT_GUEST0_FLASH_BASE="$(WT_GUEST0_FLASH_BASE)" \ + WT_GUEST1_FLASH_BASE="$(WT_GUEST1_FLASH_BASE)" \ + WT_GUEST0_FLASH_SIZE="$(WT_GUEST0_FLASH_SIZE)" \ + WT_GUEST1_FLASH_SIZE="$(WT_GUEST1_FLASH_SIZE)" \ + $(MAKE) -C "$(ROOT)" build/wolftrust.bin build/secure_cmse_implib.o endif build-guest0: patch secure @@ -131,6 +140,25 @@ build-guest0-psa: patch secure WT_FWU_PROBE="$(WT_FWU_PROBE)" \ "$(SCRIPT_DIR)/build_guest.sh" guest0_psa +build-guest0-pkcs11: patch secure + ZEPHYR_TAG="$(ZEPHYR_TAG)" ZEPHYR_WORKSPACE="$(ZEPHYR_WORKSPACE)" \ + WEST_PYTHON="$(WEST_PYTHON)" WEST_BIN="$(WEST_BIN)" \ + ZEPHYR_BOARD="$(ZEPHYR_BOARD)" \ + SECURE_BIN="$(SECURE_BIN)" SECURE_CMSE_IMPLIB="$(SECURE_CMSE_IMPLIB)" \ + WT_SHARED_UART="$(WT_SHARED_UART)" \ + WT_PKCS11_SUITE="$(WT_PKCS11_SUITE)" \ + WT_M33MU_EXPECT_BKPT="$(WT_M33MU_EXPECT_BKPT)" \ + "$(SCRIPT_DIR)/build_guest.sh" guest0_pkcs11 + +build-guest0-tpm: patch secure + ZEPHYR_TAG="$(ZEPHYR_TAG)" ZEPHYR_WORKSPACE="$(ZEPHYR_WORKSPACE)" \ + WEST_PYTHON="$(WEST_PYTHON)" WEST_BIN="$(WEST_BIN)" \ + ZEPHYR_BOARD="$(ZEPHYR_BOARD)" \ + SECURE_BIN="$(SECURE_BIN)" SECURE_CMSE_IMPLIB="$(SECURE_CMSE_IMPLIB)" \ + WT_SHARED_UART="$(WT_SHARED_UART)" \ + WT_M33MU_EXPECT_BKPT="$(WT_M33MU_EXPECT_BKPT)" \ + "$(SCRIPT_DIR)/build_guest.sh" guest0_tpm + build-guest1: $(GUEST1_BIN) $(GUEST1_BUILD_DIR): diff --git a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/FreeRTOSConfig.h b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/FreeRTOSConfig.h index e34a4a495..427504246 100644 --- a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/FreeRTOSConfig.h +++ b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/FreeRTOSConfig.h @@ -32,7 +32,7 @@ /* Heap (heap_4.c). The wolfPSA + wolfCrypt cryptocb path allocates * key scratch buffers on the FreeRTOS heap, so it needs a few KiB. */ -#define configTOTAL_HEAP_SIZE ( (size_t) ( 16 * 1024 ) ) +#define configTOTAL_HEAP_SIZE ( (size_t) ( 28 * 1024 ) ) #define configSUPPORT_DYNAMIC_ALLOCATION 1 #define configSUPPORT_STATIC_ALLOCATION 0 #define configMINIMAL_STACK_SIZE ( (uint16_t) 256 ) diff --git a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c index 489806ac0..f1036a2c5 100644 --- a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c +++ b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c @@ -41,6 +41,9 @@ /* SERVICE_HSM (port/stm32h563/manifest.json): the single mediated door to * the secure crypto engine — the same path guest0 uses. */ #define WT_SERVICE_HSM_SID 4102u +#ifndef WT_FREERTOS_OTHER_RAM_BASE +#define WT_FREERTOS_OTHER_RAM_BASE 0x20000000u +#endif #if defined(WT_ENGINE_HSM) /* wolfHSM client glue (module/wolfhsm-client/src/wolfhsm_client_glue.c). */ @@ -94,7 +97,7 @@ void SysTick_Handler(void); void DebugMon_Handler(void) __attribute__((weak, alias("default_handler"))); -__attribute__((section(".vectors"))) +__attribute__((used, section(".vectors"))) const uint32_t g_vectors[16] = { [0] = (uint32_t)&_estack, [1] = (uint32_t)&Reset_Handler, @@ -298,6 +301,55 @@ static void run_psa_smoke(void) } } +/* Exercise the same key generation and signing primitives as the TPM client. */ +static void run_psa_ecc(void) +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t key = PSA_KEY_ID_NULL; + uint8_t signature[PSA_ECDSA_SIGNATURE_SIZE(256)]; + size_t signature_len = 0U; + psa_status_t status; + + psa_set_key_usage_flags(&attributes, + PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); + psa_set_key_type(&attributes, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attributes, 256U); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + status = psa_generate_key(&attributes, &key); + if (status == PSA_SUCCESS) { + status = psa_sign_hash(key, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + k_hash_expected, sizeof(k_hash_expected), signature, + sizeof(signature), &signature_len); + } + if (status == PSA_SUCCESS) { + status = psa_verify_hash(key, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + k_hash_expected, sizeof(k_hash_expected), signature, + signature_len); + } + if (key != PSA_KEY_ID_NULL && psa_destroy_key(key) != PSA_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + if (status == PSA_SUCCESS) { + uart_puts("freertos_guest1: psa ECC keygen/sign/verify ok\r\n"); + } + else { + uart_puts("freertos_guest1: psa ECC FAILED st="); + uart_put_i32((int32_t)status); + uart_puts("\r\n"); + } +#if defined(WT_PROFILE_PSA_psa_cipher_encrypt) && !WT_PROFILE_PSA_psa_cipher_encrypt + if (psa_cipher_encrypt(0, PSA_ALG_CTR, NULL, 0, NULL, 0, + &signature_len) == PSA_ERROR_NOT_SUPPORTED) { + uart_puts("freertos_guest1: disabled psa operation rejected\r\n"); + } + else { + uart_puts("freertos_guest1: disabled psa operation FAILED\r\n"); + } +#endif + +} + /* FF-M isolation negatives from the FreeRTOS client: the SPM must reject a * forged handle, an oversized input vector, and a connect to an unknown SID — * the same rejections guest0 proves — and none of them faults this guest. */ @@ -350,10 +402,8 @@ static void run_ffm_negatives(void) "freertos_guest1: ffm oversized-vector rejected\r\n" : "freertos_guest1: ffm oversized-vector NOT rejected\r\n"); - /* Cross-guest vector: a base inside guest0's NS RAM window must be - * refused by the caller-banded memcheck — guests are isolated from each - * other through the SPM, not merely NS from Secure (WT-FFM-0011). */ - in_vec.base = (const void *)0x20000000u; /* guest0 NS RAM, not ours */ + /* A vector outside this caller's NS RAM band must be refused before copy. */ + in_vec.base = (const void *)(uintptr_t)WT_FREERTOS_OTHER_RAM_BASE; in_vec.len = 16u; out_vec.base = digest; out_vec.len = sizeof(digest); @@ -422,6 +472,7 @@ static void crypto_task(void *arg) run_ffm_sha256_kat(); run_ffm_rng(); run_psa_smoke(); + run_psa_ecc(); run_ffm_negatives(); } for (;;) { @@ -433,6 +484,12 @@ static void crypto_task(void *arg) } } +#if defined(WT_ENGINE_NATIVE) +#define WT_PSA_STACK_WORDS 4096U +#else +#define WT_PSA_STACK_WORDS 2048U +#endif + /* ---- entry ------------------------------------------------------------ */ __attribute__((section(".reset"))) @@ -443,11 +500,8 @@ void Reset_Handler(void) uart_init(); uart_puts("freertos_guest1: alive\r\n"); - /* 2048 stack words = 8 KiB. The first wolfCrypt DRBG init through - * the FF-M RNG hook plus wolfPSA hash setup burns several KiB in - * peak call depth; 4 KiB triggered a STKOF on the FreeRTOS port's - * PSPLIM guard under the old transport, so keep the headroom. */ - if (xTaskCreate(crypto_task, "crypto", 2048, NULL, + /* Native ECC needs headroom for the Cortex-M SP math call chain. */ + if (xTaskCreate(crypto_task, "crypto", WT_PSA_STACK_WORDS, NULL, tskIDLE_PRIORITY + 1, NULL) != pdPASS) { uart_puts("freertos_guest1: xTaskCreate failed\r\n"); for (;;) {} diff --git a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/user_settings.h b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/user_settings.h index e6fd6b6d1..46691ec5a 100644 --- a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/user_settings.h +++ b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/user_settings.h @@ -60,7 +60,7 @@ int wolftrust_guest_rng_stub(unsigned char *output, unsigned int sz); #define NO_DSA #define NO_DES3 #define NO_MD5 -#define NO_PWDBASED +#define HAVE_PBKDF2 #define NO_PKCS12 #define NO_ASN_TIME diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/CMakeLists.txt b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/CMakeLists.txt new file mode 100644 index 000000000..90db5fdf8 --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolftrust_guest0_pkcs11) + +if(NOT DEFINED WOLFTRUST_CMSE_IMPLIB) + message(FATAL_ERROR "WOLFTRUST_CMSE_IMPLIB must point at build/secure_cmse_implib.o") +endif() + +set(WT_REPO_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../..) +include(${WT_REPO_ROOT}/tools/cmake/CryptoProfile.cmake) +set(WT_PROFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/crypto-profile) +wt_crypto_profile(${WT_REPO_ROOT} ${WT_PROFILE_DIR}) +target_compile_options(app PRIVATE -include ${WT_PROFILE_DIR}/selected_profile.h) +set(WT_TEST_DIR ${WT_REPO_ROOT}/tests/firmware/stm32h563-tpm/freertos-pkcs11) + +target_sources(app PRIVATE + src/main.c + ${WT_TEST_DIR}/pkcs11_tests.c + ${WT_REPO_ROOT}/src/client/psa_ffm_client.c + ${WT_REPO_ROOT}/src/client/pkcs11_psa_transport.c + ${WT_REPO_ROOT}/src/client/pkcs11_proxy.c + ${WT_REPO_ROOT}/src/pkcs11/pkcs11_wire.c + ${WOLFTRUST_CMSE_IMPLIB} +) + +target_include_directories(app PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${WT_REPO_ROOT}/include + ${WT_REPO_ROOT}/lib/wolfPKCS11 + ${WT_TEST_DIR} +) + +if(NOT DEFINED WT_PKCS11_SUITE) + if(WT_CRYPTO_PROFILE STREQUAL "default") + set(WT_PKCS11_SUITE profile) + else() + set(WT_PKCS11_SUITE all) + endif() +endif() + +if(WT_PKCS11_SUITE STREQUAL "profile") + set(WT_PKCS11_SUITE_DEF WT_PKCS11_SUITE_PROFILE) +elseif(WT_PKCS11_SUITE STREQUAL "core") + set(WT_PKCS11_SUITE_DEF WT_PKCS11_SUITE_CORE) +elseif(WT_PKCS11_SUITE STREQUAL "objects") + set(WT_PKCS11_SUITE_DEF WT_PKCS11_SUITE_OBJECTS) +elseif(WT_PKCS11_SUITE STREQUAL "crypto") + set(WT_PKCS11_SUITE_DEF WT_PKCS11_SUITE_CRYPTO) +elseif(WT_PKCS11_SUITE STREQUAL "negative") + set(WT_PKCS11_SUITE_DEF WT_PKCS11_SUITE_NEGATIVE) +elseif(WT_PKCS11_SUITE STREQUAL "all") + set(WT_PKCS11_SUITE_DEF WT_PKCS11_SUITE_ALL) +else() + message(FATAL_ERROR "WT_PKCS11_SUITE must be profile, core, objects, crypto, negative, or all") +endif() + +target_compile_definitions(app PRIVATE + WOLFPKCS11_USER_SETTINGS + WOLFPKCS11_NO_ENV + ${WT_PKCS11_SUITE_DEF}=1 + WT_PKCS11_APP_NAME="pkcs11-zephyr" +) + +if(WT_M33MU_EXPECT_BKPT) + target_compile_definitions(app PRIVATE WT_M33MU_EXPECT_BKPT=1) +endif() diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/app.overlay b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/app.overlay new file mode 100644 index 000000000..9c1051bc0 --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/app.overlay @@ -0,0 +1,13 @@ +/ { + wolftrust_tee0: wolftrust-tee { + compatible = "wolfssl,wolftrust-tee"; + label = "WOLFTRUST_TEE_0"; + wolftrust-guest-slot = <0>; + status = "okay"; + }; +}; + +/* The PKCS#11 manifest grants guest 0 128 KiB; the provider starts at 0x080C0000. */ +&slot0_ns_partition { + reg = <0x000a0000 DT_SIZE_K(128)>; +}; diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/prj.conf b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/prj.conf new file mode 100644 index 000000000..322a48beb --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/prj.conf @@ -0,0 +1,11 @@ +CONFIG_BUILD_WITH_TFM=n +CONFIG_CONSOLE=y +CONFIG_LOG=n +CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_PRINTK=y +CONFIG_SERIAL=y +CONFIG_TRUSTED_EXECUTION_NONSECURE=y +CONFIG_UART_CONSOLE=y +CONFIG_WOLFTRUST_ZEPHYR=n +CONFIG_WOLFTRUST_WOLFHSM_CLIENT=n +CONFIG_WOLFTRUST_NATIVE_CLIENT=n diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/src/main.c b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/src/main.c new file mode 100644 index 000000000..fdd67274b --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/src/main.c @@ -0,0 +1,61 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include + +#include "pkcs11_tests.h" + +void wt_pkcs11_puts(const char *text) +{ + printk("%s", text); +} + +void wt_pkcs11_print_u32(unsigned int value) +{ + printk("%u", value); +} + +void wt_pkcs11_wait(void) +{ + k_msleep(1); +} + +int main(void) +{ + int rc; + + printk("pkcs11-zephyr: alive\r\n"); + rc = wt_pkcs11_run(); + if (rc == 0) { + printk("pkcs11-zephyr: suite %s complete\r\n", + wt_pkcs11_suite_name()); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7f"); +#endif + } + else { + printk("pkcs11-zephyr: suite failed\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7e"); +#endif + } + return rc; +} diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/user_settings.h b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/user_settings.h new file mode 100644 index 000000000..5ed660f94 --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_pkcs11/user_settings.h @@ -0,0 +1,26 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_ZEPHYR_PKCS11_USER_SETTINGS_H +#define WOLFTRUST_ZEPHYR_PKCS11_USER_SETTINGS_H + +#define WOLFPKCS11_NO_ENV + +#endif /* WOLFTRUST_ZEPHYR_PKCS11_USER_SETTINGS_H */ diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c index 6c7198c84..18e992018 100644 --- a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c @@ -893,6 +893,13 @@ static void exercise_psa_cipher(void) st = psa_cipher_encrypt(key, PSA_ALG_CTR, plaintext, sizeof(plaintext), ciphertext, sizeof(ciphertext), &ct_len); +#if defined(WT_PROFILE_PSA_psa_cipher_encrypt) && !WT_PROFILE_PSA_psa_cipher_encrypt + if (st == PSA_ERROR_NOT_SUPPORTED) { + LOG_INF("disabled PSA operation rejected"); + } else { + LOG_ERR("disabled PSA operation accepted st=%d", st); + } +#endif LOG_INF("psa_cipher_encrypt(AES-CTR) st=%d ct_len=%u", (int)st, (unsigned)ct_len); diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/CMakeLists.txt b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/CMakeLists.txt new file mode 100644 index 000000000..dfe0e9f71 --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolftrust_guest0_tpm) + +if(NOT DEFINED WOLFTRUST_CMSE_IMPLIB) + message(FATAL_ERROR "WOLFTRUST_CMSE_IMPLIB must point at build/secure_cmse_implib.o") +endif() + +set(WT_REPO_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../..) +include(${WT_REPO_ROOT}/tools/cmake/CryptoProfile.cmake) +set(WT_PROFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/crypto-profile) +wt_crypto_profile(${WT_REPO_ROOT} ${WT_PROFILE_DIR}) +target_compile_options(app PRIVATE -include ${WT_PROFILE_DIR}/selected_profile.h) +set(WT_WOLFTPM_DIR ${WT_REPO_ROOT}/lib/wolfTPM) +set(WT_TEST_DIR ${WT_REPO_ROOT}/tests/firmware/stm32h563-tpm/tpm-app) + +set(WT_WOLFTPM_SOURCES + ${WT_WOLFTPM_DIR}/src/tpm2.c + ${WT_WOLFTPM_DIR}/src/tpm2_util.c + ${WT_WOLFTPM_DIR}/src/tpm2_packet.c + ${WT_WOLFTPM_DIR}/src/tpm2_wrap.c + ${WT_WOLFTPM_DIR}/src/tpm2_asn.c + ${WT_WOLFTPM_DIR}/src/tpm2_crypto.c + ${WT_WOLFTPM_DIR}/src/tpm2_param_enc.c + ${WT_WOLFTPM_DIR}/src/tpm2_cryptocb.c +) + +target_sources(app PRIVATE + src/main.c + ${WT_TEST_DIR}/tpm_tests.c + ${WT_REPO_ROOT}/src/client/tpm_psa_transport.c + ${WT_WOLFTPM_SOURCES} + ${WOLFTRUST_CMSE_IMPLIB} +) + +target_include_directories(app PRIVATE + ${WT_REPO_ROOT}/include + ${WT_WOLFTPM_DIR} + ${WT_TEST_DIR} +) + +target_compile_definitions(app PRIVATE + WOLFTPM_USER_SETTINGS + WOLFTPM_ZEPHYR + WOLFTPM_SWTPM + WOLFTPM_SMALL_STACK + WT_TPM_APP_NAME="tpm-zephyr" +) + +if(WT_M33MU_EXPECT_BKPT) + target_compile_definitions(app PRIVATE WT_M33MU_EXPECT_BKPT=1) +endif() + +set_source_files_properties( + ${WT_WOLFTPM_SOURCES} + PROPERTIES COMPILE_OPTIONS + "-Wno-unused-function;-Wno-unused-variable;-Wno-unused-parameter;-Wno-type-limits" +) diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/app.overlay b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/app.overlay new file mode 100644 index 000000000..e89f6b22c --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/app.overlay @@ -0,0 +1,8 @@ +/ { + wolftrust_tee0: wolftrust-tee { + compatible = "wolfssl,wolftrust-tee"; + label = "WOLFTRUST_TEE_0"; + wolftrust-guest-slot = <0>; + status = "okay"; + }; +}; diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/prj.conf b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/prj.conf new file mode 100644 index 000000000..7f1d4c635 --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/prj.conf @@ -0,0 +1,12 @@ +CONFIG_BUILD_WITH_TFM=n +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=16384 +CONFIG_CONSOLE=y +CONFIG_LOG=n +CONFIG_MAIN_STACK_SIZE=24576 +CONFIG_PRINTK=y +CONFIG_SERIAL=y +CONFIG_TRUSTED_EXECUTION_NONSECURE=y +CONFIG_UART_CONSOLE=y +CONFIG_WOLFTRUST_ZEPHYR=n +CONFIG_WOLFTRUST_WOLFHSM_CLIENT=n +CONFIG_WOLFTRUST_NATIVE_CLIENT=y diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/src/main.c b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/src/main.c new file mode 100644 index 000000000..4650e4b52 --- /dev/null +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_tpm/src/main.c @@ -0,0 +1,60 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include + +#include "tpm_tests.h" + +void wt_tpm_puts(const char *text) +{ + printk("%s", text); +} + +void wt_tpm_print_i32(int value) +{ + printk("%d", value); +} + +void wt_tpm_wait(void) +{ + k_msleep(1); +} + +int main(void) +{ + int rc; + + printk("tpm-zephyr: alive\r\n"); + rc = wt_tpm_run(); + if (rc == 0) { + printk("tpm-zephyr: suite complete\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7f"); +#endif + } + else { + printk("tpm-zephyr: suite failed\r\n"); +#if defined(WT_M33MU_EXPECT_BKPT) && (WT_M33MU_EXPECT_BKPT == 1) + __asm volatile("bkpt 0x7e"); +#endif + } + return rc; +} diff --git a/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/src/wolfhsm_client_glue.c b/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/src/wolfhsm_client_glue.c index e25e113eb..1ead3067e 100644 --- a/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/src/wolfhsm_client_glue.c +++ b/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/src/wolfhsm_client_glue.c @@ -147,10 +147,22 @@ static int wolfhsm_guest_register_retry(void) return rc; } +#if defined(WT_TPM_ROLE_FTPM) && defined(WOLF_CRYPTO_CB_FIND) +/* fwTPM initializes keys without a device ID. */ +static int wolfhsm_guest_find_device(int devId, int algoType) +{ + (void)algoType; + return devId == INVALID_DEVID ? WH_DEV_ID : devId; +} +#endif + int wolfhsm_guest_init(void) { int rc; +#if defined(WT_TPM_ROLE_FTPM) && defined(WOLF_CRYPTO_CB_FIND) + wc_CryptoCb_SetDeviceFindCb(wolfhsm_guest_find_device); +#endif rc = wolfhsm_guest_connect(); if (rc == WH_ERROR_OK) { return WH_ERROR_OK; diff --git a/tests/firmware/zephyr-stm32h5/module/wolfpsa/CMakeLists.txt b/tests/firmware/zephyr-stm32h5/module/wolfpsa/CMakeLists.txt index 20329b72f..0fd1b8d0f 100644 --- a/tests/firmware/zephyr-stm32h5/module/wolfpsa/CMakeLists.txt +++ b/tests/firmware/zephyr-stm32h5/module/wolfpsa/CMakeLists.txt @@ -84,10 +84,11 @@ if(CONFIG_WOLFPSA) zephyr_library_named(wolftrust_wolfpsa) # Compile every wolfPSA src/*.c EXCEPT psa_store_posix.c (POSIX-only). - file(GLOB WOLFPSA_SRCS ${WT_WOLFPSA_DIR}/src/*.c) - list(FILTER WOLFPSA_SRCS EXCLUDE REGEX "psa_store_posix\\.c$") - # wolfTrust supplies the target-specific Initial Attestation backend. - list(FILTER WOLFPSA_SRCS EXCLUDE REGEX "psa_attestation\\.c$") + include(${WT_REPO_ROOT}/tools/cmake/CryptoProfile.cmake) + set(WT_PROFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/crypto-profile) + wt_crypto_profile(${WT_REPO_ROOT} ${WT_PROFILE_DIR}) + file(GLOB WOLFPSA_SRCS ${WT_PROFILE_DIR}/psa/*.c) + zephyr_compile_options(-include ${WT_PROFILE_DIR}/selected_profile.h) zephyr_library_sources(${WOLFPSA_SRCS}) zephyr_library_sources( diff --git a/tests/firmware/zephyr-stm32h5/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch b/tests/firmware/zephyr-stm32h5/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch index b4a2f035f..f75edd367 100644 --- a/tests/firmware/zephyr-stm32h5/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch +++ b/tests/firmware/zephyr-stm32h5/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch @@ -4,8 +4,8 @@ Date: Tue, 26 May 2026 00:00:01 +0000 Subject: [PATCH 2/3] boards: stm32: add nucleo_h563zi /ns variant for wolfTrust Adds a proper /ns board variant so Zephyr builds against the non-secure -half of wolfTrust's TrustZone split. The NS payload sits at 0x08020000 -with 128 KiB of flash and 32 KiB of SRAM1 starting at 0x20000000, which +half of wolfTrust's TrustZone split. The NS payload sits at 0x080A0000 +with 256 KiB of flash and 64 KiB of SRAM1 starting at 0x20000000, which matches wolfTrust's guest-a partition window (single-guest TEE mode). Also removes the default-y BUILD_WITH_TFM under TRUSTED_EXECUTION_NONSECURE @@ -85,8 +85,8 @@ index 000000000000..a1a1a1a1a1a1 + reg = <0x20000400 DT_SIZE_K(63)>; +}; + -+/* wolfTrust occupies secure flash 0x08000000..0x0801FFFF (128 KiB). -+ * NS payload sits at 0x08020000 with 128 KiB available. */ ++/* The authenticated wolfTrust layout places guest 0 at 0x080A0000 with ++ * 256 KiB available. */ +&flash0 { + /delete-node/ partitions; + @@ -95,9 +95,9 @@ index 000000000000..a1a1a1a1a1a1 + #address-cells = <1>; + #size-cells = <1>; + -+ slot0_ns_partition: partition@20000 { ++ slot0_ns_partition: partition@a0000 { + label = "image-0-nonsecure"; -+ reg = <0x00020000 DT_SIZE_K(128)>; ++ reg = <0x000a0000 DT_SIZE_K(256)>; + }; + }; +}; @@ -119,8 +119,8 @@ index 000000000000..b2b2b2b2b2b2 +arch: arm +toolchain: + - zephyr -+ram: 32 -+flash: 128 ++ram: 64 ++flash: 256 +vendor: st diff --git a/boards/st/nucleo_h563zi/nucleo_h563zi_stm32h563xx_ns_defconfig b/boards/st/nucleo_h563zi/nucleo_h563zi_stm32h563xx_ns_defconfig new file mode 100644 diff --git a/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh b/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh index f2b169fd1..f7177737c 100755 --- a/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh +++ b/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh @@ -15,17 +15,20 @@ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) SUBTREE_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) ROOT=$(CDPATH= cd -- "$SUBTREE_DIR/../../.." && pwd) +. "$ROOT/tests/target/lib/guest_build.sh" + FREERTOS_DIR="${FREERTOS_DIR:-${SUBTREE_DIR}/.workspace/freertos}" FREERTOS_KERNEL="${FREERTOS_DIR}/FreeRTOS/Source" FREERTOS_PORT="${FREERTOS_KERNEL}/portable/GCC/ARM_CM33_NTZ/non_secure" APP_DIR="${SUBTREE_DIR}/apps/freertos_guest1" -BUILD_DIR="${SUBTREE_DIR}/build/freertos_guest1" +BUILD_DIR="${BUILD_DIR:-${SUBTREE_DIR}/build/freertos_guest1}" SECURE_CMSE_IMPLIB="${SECURE_CMSE_IMPLIB:-${ROOT}/build/secure_cmse_implib.o}" -WT_GUEST1_FLASH_BASE="${WT_GUEST1_FLASH_BASE:-0x08040000}" +WT_GUEST1_FLASH_BASE="${WT_GUEST1_FLASH_BASE:-0x080E0000}" WT_GUEST1_RAM_BASE="${WT_GUEST1_RAM_BASE:-0x20010000}" +WT_WOLFHSM_CLIENT_ID="${WT_WOLFHSM_CLIENT_ID:-2}" WT_GUEST_FLASH_SIZE="${WT_GUEST_FLASH_SIZE:-0x00020000}" -WT_GUEST_RAM_SIZE="${WT_GUEST_RAM_SIZE:-0x00008000}" +WT_GUEST_RAM_SIZE="${WT_GUEST_RAM_SIZE:-0x00010000}" WOLFSSL_DIR="${ROOT}/lib/wolfSSL" WOLFPSA_DIR="${ROOT}/lib/wolfPSA" @@ -70,6 +73,7 @@ ${WOLFSSL_DIR}/wolfcrypt/src/coding.c \ ${WOLFSSL_DIR}/wolfcrypt/src/error.c \ ${WOLFSSL_DIR}/wolfcrypt/src/hash.c \ ${WOLFSSL_DIR}/wolfcrypt/src/hmac.c \ +${WOLFSSL_DIR}/wolfcrypt/src/pwdbased.c \ ${WOLFSSL_DIR}/wolfcrypt/src/memory.c \ ${WOLFSSL_DIR}/wolfcrypt/src/sha.c \ ${WOLFSSL_DIR}/wolfcrypt/src/sp_cortexm.c \ @@ -80,13 +84,21 @@ ${WOLFSSL_DIR}/wolfcrypt/src/port/arm/thumb2-aes-asm_c.c \ ${WOLFSSL_DIR}/wolfcrypt/src/port/arm/thumb2-sha256-asm_c.c" # wolfPSA subset for the PSA Crypto front-end (psa_crypto_init / -# psa_generate_random / psa_hash_compute); the rest of the API rides the +# psa_generate_random / psa_hash_compute / P-256 keygen/sign/verify); the +# remaining FF-M services use the # neutral FF-M client directly. WOLFPSA_SRCS="\ ${WOLFPSA_DIR}/src/psa_engine.c \ ${WOLFPSA_DIR}/src/psa_crypto.c \ ${WOLFPSA_DIR}/src/psa_random.c \ -${WOLFPSA_DIR}/src/psa_hash_engine.c" +${WOLFPSA_DIR}/src/psa_hash_engine.c \ +${WOLFPSA_DIR}/src/psa_key_storage.c \ +${WOLFPSA_DIR}/src/psa_asymmetric.c \ +${WOLFPSA_DIR}/src/psa_asymmetric_api.c \ +${WOLFPSA_DIR}/src/psa_ecc.c \ +${WOLFPSA_DIR}/src/psa_rsa.c \ +${WOLFPSA_DIR}/src/psa_api_stub.c \ +${WOLFPSA_DIR}/src/psa_store_zephyr.c" # wolfHSM client subset: the same single mediated crypto path guest0 uses — # wolfCrypt (WH_DEV_ID) -> cryptocb -> wh_Client -> the SERVICE_HSM relay @@ -116,15 +128,14 @@ WT_CLIENT_SRCS="\ ${ROOT}/src/client/psa_ffm_client.c \ ${ROOT}/src/client/crypto_native_client.c" -GUEST_GLUE_SRCS="\ -${BAREMETAL_NS_DIR}/libc_stubs_guest.c" +GUEST_GLUE_SRCS="" else WT_CLIENT_SRCS="\ ${ROOT}/src/client/psa_ffm_client.c \ ${ROOT}/src/client/hsm_psa_transport.c" GUEST_GLUE_SRCS="\ -${BAREMETAL_NS_DIR}/libc_stubs_guest.c \ + \ ${WOLFHSM_MODULE_DIR}/src/wolfhsm_client_glue.c" fi @@ -138,17 +149,19 @@ printf '#include "%s"\n' "${BAREMETAL_NS_DIR}/wh_settings_guest.h" \ printf '#include "%s"\n' "${BAREMETAL_NS_DIR}/wh_settings_guest.h" \ > "${WH_CFG_DIR}/wolfhsm_guest_cfg.h" -APP_SRCS="${APP_DIR}/main.c" if [ "${WT_ENGINE}" = "native" ]; then WT_ENGINE_DEFS="-DWT_ENGINE_NATIVE=1" else -WT_ENGINE_DEFS="-DWOLFHSM_CFG -DWOLF_CRYPTO_CB -DWT_ENGINE_HSM=1 -DWT_WOLFHSM_CLIENT_ID=2" +WT_ENGINE_DEFS="-DWOLFHSM_CFG -DWOLF_CRYPTO_CB -DWT_ENGINE_HSM=1 -DWT_WOLFHSM_CLIENT_ID=${WT_WOLFHSM_CLIENT_ID}" fi +wt_guest_prepare_profile +WOLFPSA_SRCS=$(printf '%s\n' "$WT_GUEST_PROFILE_DIR"/psa/*.c) + CFLAGS="\ -mcpu=cortex-m33 -mthumb -mgeneral-regs-only \ --ffreestanding -fno-builtin -nostdlib -Os -g \ +-ffreestanding -fno-builtin -nostdlib -Os -g $WT_GUEST_LTO_FLAGS \ -Wall -Wextra -Wno-unused-function -Wno-unused-variable \ -Wno-unused-parameter -Wno-type-limits \ -ffunction-sections -fdata-sections \ @@ -166,9 +179,10 @@ CFLAGS="\ -I${ROOT}/port/stm32h563 \ -DWOLFSSL_USER_SETTINGS \ -DWOLFSSL_PSA_ENGINE \ --DWOLFPSA_NO_TRACE \ +-DWOLFPSA_NO_TRACE -DWOLFPSA_CUSTOM_STORE \ ${WT_ENGINE_DEFS} \ -DWC_RESEED_INTERVAL=1000000 \ +-DWT_FREERTOS_OTHER_RAM_BASE=${WT_FREERTOS_OTHER_RAM_BASE:-0x20000000}u \ -include ${SUBTREE_DIR}/module/wolfpsa/wolfpsa_no_trace.h \ -DWOLFSSL_SP_ASM -DWOLFSSL_SP_ARM_CORTEX_M_ASM -DWOLFSSL_ARM_ARCH=8 \ -DWOLFSSL_ARMASM -DWOLFSSL_ARMASM_NO_HW_CRYPTO -DWOLFSSL_ARMASM_INLINE \ @@ -178,7 +192,17 @@ ${WT_ENGINE_DEFS} \ -DWC_RSA_DIRECT \ -include ${APP_DIR}/user_settings.h" -LDFLAGS="\ +if [ "$WT_ENGINE" = hsm ]; then + WT_GUEST_KEEP_FLAGS="$WT_GUEST_KEEP_FLAGS -Wl,-u,wt_hsm_psa_transport_cb" +else + WT_GUEST_KEEP_FLAGS="$WT_GUEST_KEEP_FLAGS -Wl,-u,wt_crypto_native_call" +fi + +CFLAGS="$CFLAGS $WT_GUEST_PROFILE_FLAGS" + +LDFLAGS="$WT_GUEST_LTO_FLAGS $WT_GUEST_KEEP_FLAGS +-Wl,-u,vTaskStartScheduler,-u,psa_crypto_init,-u,psa_generate_random,-u,psa_hash_compute +\ -Wl,-T${APP_DIR}/freertos_guest1.ld \ -Wl,--defsym=GUEST_FLASH_ORIGIN=${WT_GUEST1_FLASH_BASE} \ -Wl,--defsym=GUEST_FLASH_LENGTH=${WT_GUEST_FLASH_SIZE} \ @@ -186,11 +210,14 @@ LDFLAGS="\ -Wl,--defsym=GUEST_RAM_LENGTH=${WT_GUEST_RAM_SIZE} \ -Wl,--gc-sections" +# shellcheck disable=SC2086 +wt_guest_compile_platform "$APP_DIR/main.c" $FREERTOS_KERNEL_SRCS \ + "$BAREMETAL_NS_DIR/libc_stubs_guest.c" + # shellcheck disable=SC2086 ${GUEST_CC} ${CFLAGS} ${LDFLAGS} \ -o "${BUILD_DIR}/freertos_guest1.elf" \ - ${APP_SRCS} \ - ${FREERTOS_KERNEL_SRCS} \ + $WT_GUEST_PLATFORM_OBJECTS \ ${WOLFCRYPT_GUEST_SRCS} \ ${WOLFPSA_SRCS} \ ${WOLFHSM_CLIENT_SRCS} \ @@ -202,7 +229,7 @@ ${GUEST_CC} ${CFLAGS} ${LDFLAGS} \ # Mediated-path proof (WT-FFM-0054): the wolfHSM client must reach the secure # side ONLY through the SPM-mediated psa_call transport — assert the transport # is linked and the retired direct veneers are absent. -NM_OUT=$(arm-none-eabi-nm "${BUILD_DIR}/freertos_guest1.elf") || { +NM_OUT=$(arm-none-eabi-nm --defined-only "${BUILD_DIR}/freertos_guest1.elf") || { echo "FAIL: nm on the guest1 image failed" >&2 exit 1 } diff --git a/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh b/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh index 1bbde406e..6914bd620 100755 --- a/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh +++ b/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh @@ -2,7 +2,7 @@ set -eu if [ $# -ne 1 ]; then - echo "usage: $0 guest0|guest0_psa|guest1" >&2 + echo "usage: $0 guest0|guest0_psa|guest0_pkcs11|guest0_tpm|guest1" >&2 exit 2 fi @@ -32,6 +32,11 @@ WT_WRITE_ONCE_RESET_PROBE="${WT_WRITE_ONCE_RESET_PROBE:-0}" WT_HSM_ATTACK_PROBE="${WT_HSM_ATTACK_PROBE:-0}" WT_MPU_BYPASS_PROBE="${WT_MPU_BYPASS_PROBE:-0}" WT_PERIPH_NEG_PROBE="${WT_PERIPH_NEG_PROBE:-0}" +if [ "${WT_CRYPTO_PROFILE:-default}" = default ]; then + WT_PKCS11_SUITE="${WT_PKCS11_SUITE:-profile}" +else + WT_PKCS11_SUITE="${WT_PKCS11_SUITE:-all}" +fi . "$ROOT/tests/target/lib/engine.sh" if [ ! -d "$APP_DIR" ]; then @@ -70,7 +75,12 @@ set -- \ "-DWT_MPU_BYPASS_PROBE=$WT_MPU_BYPASS_PROBE" \ "-DWT_PERIPH_NEG_PROBE=$WT_PERIPH_NEG_PROBE" -if [ "$WT_ENGINE" = "native" ]; then +if [ "$APP_NAME" = "guest0_pkcs11" ]; then + set -- "$@" \ + "-DWT_PKCS11_SUITE=$WT_PKCS11_SUITE" \ + "-DCONFIG_WOLFTRUST_WOLFHSM_CLIENT=n" \ + "-DCONFIG_WOLFTRUST_NATIVE_CLIENT=n" +elif [ "$WT_ENGINE" = "native" ]; then set -- "$@" \ "-DCONFIG_WOLFTRUST_WOLFHSM_CLIENT=n" \ "-DCONFIG_WOLFTRUST_NATIVE_CLIENT=y" @@ -130,7 +140,33 @@ if printf '%s\n' "$NM_OUT" | \ echo "FAIL: retired direct veneers linked into $APP_NAME" >&2 exit 1 fi -if [ "$WT_ENGINE" = "native" ]; then +if [ "$APP_NAME" = "guest0_pkcs11" ]; then + if ! printf '%s\n' "$NM_OUT" | grep -q 'C_GetFunctionList'; then + echo "FAIL: $APP_NAME is missing the PKCS11 C_* proxy" >&2 + exit 1 + fi + if ! printf '%s\n' "$NM_OUT" | grep -q 'wt_pkcs11_psa_call'; then + echo "FAIL: $APP_NAME is not wired to SERVICE_PKCS11" >&2 + exit 1 + fi + if printf '%s\n' "$NM_OUT" | grep -Eq 'WP11_|TPM2_'; then + echo "FAIL: a PKCS11 or TPM provider implementation linked into $APP_NAME" >&2 + exit 1 + fi +elif [ "$APP_NAME" = "guest0_tpm" ]; then + if ! printf '%s\n' "$NM_OUT" | grep -q 'wt_tpm_psa_transact'; then + echo "FAIL: $APP_NAME is not wired to SERVICE_TPM" >&2 + exit 1 + fi + if ! printf '%s\n' "$NM_OUT" | grep -q 'TPM2_SWTPM_SendCommand'; then + echo "FAIL: wolfTPM client not routed through SERVICE_TPM in $APP_NAME" >&2 + exit 1 + fi + if printf '%s\n' "$NM_OUT" | grep -q 'FWTPM_ProcessCommand'; then + echo "FAIL: the fwTPM provider linked into $APP_NAME" >&2 + exit 1 + fi +elif [ "$WT_ENGINE" = "native" ]; then if ! printf '%s\n' "$NM_OUT" | grep -q 'wt_crypto_native_call'; then echo "FAIL: $APP_NAME is not wired to the native crypto wire client" >&2 exit 1 diff --git a/tests/firmware/zephyr-stm32h5/scripts/run_m33mu.sh b/tests/firmware/zephyr-stm32h5/scripts/run_m33mu.sh index 75a650952..8e52a7774 100755 --- a/tests/firmware/zephyr-stm32h5/scripts/run_m33mu.sh +++ b/tests/firmware/zephyr-stm32h5/scripts/run_m33mu.sh @@ -9,8 +9,8 @@ EMU_TIMEOUT="${EMU_TIMEOUT:-300}" SECURE_BIN="${SECURE_BIN:-$SUBTREE_DIR/../../../build/wolftrust.bin}" GUEST0_BIN="${GUEST0_BIN:-$SUBTREE_DIR/build/guest0/zephyr/zephyr.bin}" GUEST1_BIN="${GUEST1_BIN:-$SUBTREE_DIR/build/guest1/guest1.bin}" -WT_GUEST0_EMU_OFFSET="${WT_GUEST0_EMU_OFFSET:-0x20000}" -WT_GUEST1_EMU_OFFSET="${WT_GUEST1_EMU_OFFSET:-0x40000}" +WT_GUEST0_EMU_OFFSET="${WT_GUEST0_EMU_OFFSET:-0xA0000}" +WT_GUEST1_EMU_OFFSET="${WT_GUEST1_EMU_OFFSET:-0xE0000}" make -C "$SUBTREE_DIR" all >/dev/null diff --git a/tests/firmware/zephyr-stm32h5/scripts/setup_venv.sh b/tests/firmware/zephyr-stm32h5/scripts/setup_venv.sh index f25a2a7f6..82bed7c73 100755 --- a/tests/firmware/zephyr-stm32h5/scripts/setup_venv.sh +++ b/tests/firmware/zephyr-stm32h5/scripts/setup_venv.sh @@ -5,13 +5,14 @@ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) SUBTREE_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) VENV_DIR="$SUBTREE_DIR/.venv" PYTHON_BIN="${WEST_PYTHON:-$VENV_DIR/bin/python3}" +WEST_BIN_PATH="${WEST_BIN:-$VENV_DIR/bin/west}" if [ ! -x "$PYTHON_BIN" ]; then python3 -m venv "$VENV_DIR" fi -if ! "$PYTHON_BIN" -m west --version >/dev/null 2>&1; then - "$PYTHON_BIN" -m pip install west +if ! "$WEST_BIN_PATH" --version >/dev/null 2>&1; then + "$PYTHON_BIN" -m pip install --force-reinstall west fi if ! "$PYTHON_BIN" -c 'import elftools' >/dev/null 2>&1; then diff --git a/tests/host/Makefile b/tests/host/Makefile index 2e055747f..cde737bc4 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -34,7 +34,10 @@ endif UNIT_SUITES := domain manifest lifecycle guest_verify rollback sp_recovery \ fabric_windows periph irq_claim rt700_trng rt700_xspi \ ipc ffm spm \ - vnet wolfhsm_loopback wolfhsm_relay native_wire native_wire_client \ + vnet wolfhsm_loopback wolfhsm_relay tpm_relay pkcs11_relay \ + tpm_client pkcs11_client pkcs11_proxy pkcs11_provider \ + crypto_profile pkcs11_secure_heap native_wire \ + native_wire_client \ keystore_isolation \ attestation_service attestation attestation_token \ attestation_golden attestation_negatives attestation_replay \ diff --git a/tests/host/crypto_profile/Makefile b/tests/host/crypto_profile/Makefile new file mode 100644 index 000000000..5a5d94d01 --- /dev/null +++ b/tests/host/crypto_profile/Makefile @@ -0,0 +1,78 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I../pkcs11_provider \ + -I$(ROOT)/include \ + -I$(ROOT)/lib/wolfPKCS11 \ + -DWOLFPKCS11_USER_SETTINGS -DWOLFPKCS11_NO_STORE \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +PROFILE_DIR := $(abspath $(BUILD_DIR)/profile) +PROFILE_HEADER := $(PROFILE_DIR)/selected_profile.h +CFLAGS += -include $(PROFILE_HEADER) +OBJS := $(addprefix $(BUILD_DIR)/,pkcs11_proxy.o pkcs11_provider.o pkcs11_wire.o provider_stubs.o main.o) +TEST_BIN := $(BUILD_DIR)/test_crypto_profile + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(PROFILE_HEADER): $(ROOT)/tools/crypto_profile.py $(ROOT)/config/crypto-profiles.json | $(BUILD_DIR) + python3 $(ROOT)/tools/crypto_profile.py --output $(PROFILE_DIR) $(PROFILE_ARGS) + +$(BUILD_DIR)/pkcs11_proxy.o: $(ROOT)/src/client/pkcs11_proxy.c $(PROFILE_HEADER) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/pkcs11_provider.o: $(ROOT)/src/pkcs11/pkcs11_provider.c $(PROFILE_HEADER) + $(CC) $(CFLAGS) -include ../pkcs11_provider/provider_stubs.h -c -o $@ $< + +$(BUILD_DIR)/pkcs11_wire.o: $(ROOT)/src/pkcs11/pkcs11_wire.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/provider_stubs.o: ../pkcs11_provider/provider_stubs.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/main.o: main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(OBJS) + +$(OBJS): $(PROFILE_HEADER) + +run: $(TEST_BIN) + $(TEST_BIN) + python3 test_generator.py --build-dir $(BUILD_DIR) + $(MAKE) --no-print-directory all BUILD_DIR=$(BUILD_DIR)/custom \ + PROFILE_ARGS="--enable pkcs11:C_GetObjectSize --disable pkcs11:C_GetMechanismInfo" + $(BUILD_DIR)/custom/test_crypto_profile + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/crypto_profile/main.c b/tests/host/crypto_profile/main.c new file mode 100644 index 000000000..efee50671 --- /dev/null +++ b/tests/host/crypto_profile/main.c @@ -0,0 +1,157 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolfpkcs11/pkcs11.h" +#include "wolftrust/pkcs11_provider.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/pkcs11_psa_transport.h" + +#include +#include + +extern CK_FUNCTION_LIST g_provider_test_functions; +extern unsigned int g_provider_test_calls; +static unsigned int transport_calls; +static unsigned int failures; + +static void check(int condition, const char* name) +{ + printf("%s: WT-FFM-0059 %s\n", condition ? "PASS" : "FAIL", name); + if (!condition) { + failures++; + } +} + +int wt_pkcs11_psa_call(const uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, size_t* response_len) +{ + uint8_t copy[WT_PKCS11_MESSAGE_MAX]; + + if (request_len > sizeof(copy)) { + return -1; + } + transport_calls++; + memcpy(copy, request, request_len); + return wt_pkcs11_provider_dispatch(copy, request_len, response, + response_cap, response_len, NULL); +} + +static CK_RV mechanism_info(CK_SLOT_ID slot, CK_MECHANISM_TYPE mechanism, + CK_MECHANISM_INFO_PTR info) +{ + (void)slot; + info->ulMinKeySize = 1; + info->ulMaxKeySize = 256; + info->flags = mechanism == CKM_ECDSA ? CKF_SIGN | CKF_VERIFY | CKF_WRAP : + CKF_DIGEST | CKF_ENCRYPT; + return CKR_OK; +} + +static CK_RV object_size(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, + CK_ULONG_PTR size) +{ + (void)session; + (void)object; + g_provider_test_calls++; + *size = 17; + return CKR_OK; +} + +int main(void) +{ + CK_FUNCTION_LIST_PTR functions = NULL; + CK_OBJECT_HANDLE object; + CK_MECHANISM_INFO info; + CK_MECHANISM_TYPE mechanisms[16]; + CK_MECHANISM mechanism = { CKM_SHA256_HMAC, NULL, 0 }; + CK_ULONG count = 16; + CK_ULONG size = 0; + CK_BYTE random[8]; + uint8_t request[12]; + uint8_t response[12]; + size_t response_len = 0; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + unsigned int before; + + g_provider_test_functions.C_GetMechanismInfo = mechanism_info; + g_provider_test_functions.C_GetObjectSize = object_size; + check(C_GetFunctionList(&functions) == CKR_OK && functions != NULL, + "function list preserves the PKCS11 ABI"); + if (functions == NULL) { + return 1; + } + check(functions->C_Initialize(NULL) == CKR_OK, + "selected initialization reaches the provider"); + check(functions->C_GenerateRandom(1, random, sizeof(random)) == CKR_OK, + "selected RNG completes a proxy/provider round trip"); + before = transport_calls; + check(functions->C_CopyObject(1, 1, NULL, 0, &object) == + CKR_FUNCTION_NOT_SUPPORTED && transport_calls == before, + "disabled entry point makes no transport call"); + wt_pkcs11_writer_init(&writer, request, sizeof(request)); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_VERSION); + wt_pkcs11_put_u32(&writer, WT_P11_COPY_OBJECT); + before = g_provider_test_calls; + check(wt_pkcs11_psa_call(request, writer.used, response, + sizeof(response), &response_len) == 0, + "forged disabled request returns a response"); + wt_pkcs11_reader_init(&reader, response, response_len); + (void)wt_pkcs11_get_u32(&reader); + (void)wt_pkcs11_get_u32(&reader); + check(wt_pkcs11_get_u32(&reader) == CKR_FUNCTION_NOT_SUPPORTED && + reader.error == 0 && reader.used == reader.length && + g_provider_test_calls == before, + "provider refuses disabled operation before decoding/backend"); + check(functions->C_GetMechanismList(1, mechanisms, &count) == CKR_OK && + count == 2 && mechanisms[0] == CKM_SHA256 && + mechanisms[1] == CKM_ECDSA, + "capability list excludes removed mechanisms"); +#if WT_PROFILE_PKCS11_C_GetMechanismInfo + check(functions->C_GetMechanismInfo(1, CKM_ECDSA, &info) == CKR_OK && + info.flags == (CKF_SIGN | CKF_VERIFY), + "capability flags exclude disabled actions"); + check(functions->C_GetMechanismInfo(1, CKM_PKCS5_PBKD2, &info) == + CKR_MECHANISM_INVALID, + "disabled mechanism information is refused"); +#else + check(functions->C_GetMechanismInfo(1, CKM_ECDSA, &info) == + CKR_FUNCTION_NOT_SUPPORTED, + "removing public mechanism info preserves internal enumeration"); +#endif +#if WT_PROFILE_PKCS11_C_GetObjectSize + before = g_provider_test_calls; + check(functions->C_GetObjectSize(1, 1, &size) == CKR_OK && size == 17 && + g_provider_test_calls == before + 1, + "added operation reaches the backend"); +#else + before = transport_calls; + check(functions->C_GetObjectSize(1, 1, &size) == CKR_FUNCTION_NOT_SUPPORTED && + transport_calls == before, + "unselected operation keeps its typed stub"); +#endif + before = g_provider_test_calls; + check(functions->C_SignInit(1, &mechanism, 1) == CKR_MECHANISM_INVALID && + g_provider_test_calls == before, + "allowed operation cannot use a disabled mechanism"); + return failures != 0; +} diff --git a/tests/host/crypto_profile/test_generator.py b/tests/host/crypto_profile/test_generator.py new file mode 100644 index 000000000..0149135be --- /dev/null +++ b/tests/host/crypto_profile/test_generator.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# test_generator.py +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +"""Operation selection regressions against the pinned dependency sources.""" + +import argparse +import importlib.util +import json +import os +import shlex +import subprocess +from pathlib import Path +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[3] +spec = importlib.util.spec_from_file_location("crypto_profile", ROOT / "tools/crypto_profile.py") +profile = importlib.util.module_from_spec(spec) +spec.loader.exec_module(profile) + + +class GeneratorTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.catalog, cls.signatures, cls.sources, cls.commands = profile.discover(ROOT) + cls.defaults = json.loads((ROOT / "config/crypto-profiles.json").read_text())["default"] + + def test_default_and_overrides(self): + selected, mechanisms = profile.select(self.catalog, self.defaults, + "default", {}, ["tpm:PCR_Read", "pkcs11:C_CopyObject"], + ["psa:psa_sign_hash"]) + self.assertIn("PCR_Read", selected["tpm"]) + self.assertIn("C_CopyObject", selected["pkcs11"]) + self.assertNotIn("psa_sign_hash", selected["psa"]) + self.assertEqual(mechanisms, self.defaults["mechanisms"]) + + def test_full_means_implemented_proxy_operations(self): + selected, mechanisms = profile.select(self.catalog, self.defaults, + "full", {}, [], []) + self.assertIn("C_DeriveKey", selected["pkcs11"]) + self.assertNotIn("C_WrapKey", selected["pkcs11"]) + self.assertIsNone(mechanisms) + + def test_reject_unknown_operations(self): + for value in ("tpm:NotACommand", "psa:psa_connect", "pkcs11:C_WrapKey", "bad"): + with self.subTest(value=value), self.assertRaises(ValueError): + profile.select(self.catalog, self.defaults, "default", {}, [value], []) + + def test_reject_invalid_configuration(self): + for custom in ([], {"psa": "psa_sign_hash"}, {"tpm": [1]}, {"bogus": []}, + {"mechanisms": ["CKM_SHA256; invalid"]}): + with self.subTest(custom=custom), self.assertRaises(ValueError): + profile.select(self.catalog, self.defaults, "default", custom, [], []) + + def test_tpm_table_filters_handlers_and_keeps_auth_metadata(self): + selected = profile.selected_tpm(self.commands, {"Sign"}) + start, end, rows = profile.tpm_table(selected) + self.assertEqual([m.group(1) for m in rows], ["Sign"]) + self.assertIn("FwCmd_Sign, 1, 1, 0, FW_CMD_FLAG_ENC", selected[start:end]) + self.assertIn("== 1U", selected[end:]) + # Parser and handlers are untouched; only dispatcher roots disappear. + original_start, original_end, _ = profile.tpm_table(self.commands) + self.assertEqual(selected[:start], self.commands[:original_start]) + self.assertTrue(selected.endswith(self.commands[original_end + 3:])) + + def test_fail_closed_on_dependency_table_change(self): + with self.assertRaises(ValueError): + profile.tpm_table(self.commands.replace(profile.TPM_TABLE, "changed table")) + + def test_internal_psa_dependencies_keep_private_names(self): + with tempfile.TemporaryDirectory(dir=BUILD_DIR) as temporary: + output = Path(temporary) + profile.generate(ROOT, output, "default", {}, [], []) + api = (output / "psa/profile_api.c").read_text() + private = (output / "private_psa.h").read_text() + self.assertIn("return wt_profile_real_psa_generate_key", api) + self.assertIn("#define psa_hash_setup wt_profile_real_psa_hash_setup", private) + disabled = api[api.index("psa_status_t psa_cipher_encrypt("):] + disabled = disabled[:disabled.index("\n}")] + self.assertIn("PSA_ERROR_NOT_SUPPORTED", disabled) + self.assertNotIn("wt_profile_real_psa_cipher_encrypt", disabled) + + def test_repeated_generation_preserves_file_times(self): + with tempfile.TemporaryDirectory(dir=BUILD_DIR) as temporary: + output = Path(temporary) + profile.generate(ROOT, output, "default", {}, [], []) + before = {p: p.stat().st_mtime_ns for p in output.rglob("*") if p.is_file()} + profile.generate(ROOT, output, "default", {}, [], []) + self.assertEqual(before, {p: p.stat().st_mtime_ns for p in before}) + + def test_multipart_actions_and_mechanism_aliases(self): + header = profile.pkcs11_header(self.signatures, + {"C_DigestInit", "C_DigestUpdate", "C_DigestFinal"}, + ["CKM_EC_KEY_PAIR_GEN", "CKM_ECDSA_KEY_PAIR_GEN"]) + self.assertNotIn("flags &= ~(CK_FLAGS)CKF_DIGEST;", header) + self.assertIn("flags &= ~(CK_FLAGS)CKF_SIGN;", header) + self.assertNotIn("case CKM_EC", header) + + def test_raw_provider_objects_wait_for_generated_header(self): + cases = (("p11_wc_raw_random.o", "PKCS11", "native"), + ("p11_sec_raw_internal.o", "PKCS11", "native"), + ("p11_sec_raw_crypto.o", "PKCS11", "native"), + ("p11_wh_raw_wh_client.o", "PKCS11", "hsm"), + ("tpm_wc_raw_random.o", "TPM", "native"), + ("tpm_sec_raw_fwtpm_crypto.o", "TPM", "native"), + ("tpm_wh_raw_wh_client.o", "TPM", "hsm")) + for object_name, api, engine in cases: + with self.subTest(object=object_name), tempfile.TemporaryDirectory(dir=BUILD_DIR) as temporary: + output = Path(temporary) + compiler = output / "compiler.py" + compiler.write_text("#!/usr/bin/env python3\n" + "import pathlib, sys\n" + "args = sys.argv[1:]\n" + "if '-o' not in args: raise SystemExit(0)\n" + "seen = False\n" + "for i, arg in enumerate(args):\n" + " if arg == '-include' and pathlib.Path(args[i+1]).name == 'selected_profile.h':\n" + " assert pathlib.Path(args[i+1]).is_file(), args[i+1]\n" + " seen = True\n" + "assert seen, 'profile header not passed to compiler'\n" + "pathlib.Path(args[args.index('-o')+1]).touch()\n") + compiler.chmod(0o755) + build = output / "objects" + # This independent make has its own parallel job pool; Python + # does not pass through the parent make's jobserver descriptors. + environment = os.environ.copy() + environment.pop("MAKEFLAGS", None) + environment.pop("MFLAGS", None) + result = subprocess.run(["make", "--no-print-directory", "-j8", + "-C", str(ROOT), "ARCH=aarch64", "TARGET=qemuvirt", "TOOLPREFIX=", + "CONFIG_" + api + "=y", "WT_ENGINE=" + engine, + "BUILD_DIR=" + str(build), "CC=" + str(compiler), + str(build / object_name)], text=True, capture_output=True, + env=environment) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertTrue((build / "crypto-profile/selected_profile.h").is_file()) + self.assertTrue((build / object_name).is_file()) + + def test_empty_pkcs11_selection_compiles(self): + with tempfile.TemporaryDirectory(dir=BUILD_DIR) as temporary: + output = Path(temporary) + profile.generate(ROOT, output, "default", + {"pkcs11": [], "mechanisms": []}, [], []) + compiler = shlex.split(os.environ.get("CC", "cc")) + subprocess.run(compiler + ["-std=c99", "-Wall", "-Wextra", "-Werror", + "-fsyntax-only", "-x", "c", "-DWOLFPKCS11_USER_SETTINGS", + "-I" + str(ROOT / "tests/host/pkcs11_provider"), + "-I" + str(ROOT / "include"), "-I" + str(ROOT / "lib/wolfPKCS11"), + "-include", str(output / "selected_profile.h"), "-"], + input='#include "wolftrust/crypto_profile.h"\nint main(void) { return wt_profile_pkcs11_operation(0); }\n', + text=True, capture_output=True, check=True) + + def test_public_mechanisms_do_not_remove_internal_pin_crypto(self): + for mechanisms, aes, hmac in ( + (self.defaults["mechanisms"], False, False), + (["CKM_SHA256_HMAC"], False, True), + (["CKM_AES_GCM"], True, False), + (None, True, True), + ): + with self.subTest(mechanisms=mechanisms): + source = profile.pkcs11_mechanism_source(ROOT, mechanisms) + self.assertEqual("#define NO_AES" in source, not aes) + self.assertEqual("#define NO_HMAC" in source, not hmac) + for macro in ("#define NO_AES", "#define NO_HMAC"): + if macro in source: + self.assertLess(source.index(""), + source.index(macro)) + + def test_tpm_full_point_dependency_restores_software_ecc(self): + for enabled in ([], ["tpm:ZGen_2Phase"]): + with self.subTest(enabled=enabled), tempfile.TemporaryDirectory(dir=BUILD_DIR) as temporary: + output = Path(temporary) + profile.generate(ROOT, output, "default", {}, enabled, []) + compiler = shlex.split(os.environ.get("CC", "cc")) + result = subprocess.run(compiler + ["-E", "-dM", "-x", "c", + "-DWT_TPM_HSM_CALLBACK_ECC", "-include", + str(output / "selected_profile.h"), "-"], input="", + text=True, capture_output=True, check=True) + self.assertEqual("#define WOLF_CRYPTO_CB_ONLY_ECC 1" in result.stdout, + not enabled) + + def test_ci_matrix_keeps_every_profile_engine_api_cell(self): + matrix_spec = importlib.util.spec_from_file_location("scenario_matrix", + ROOT / "tests/target/lib/scenario_matrix.py") + matrix = importlib.util.module_from_spec(matrix_spec) + matrix_spec.loader.exec_module(matrix) + workflow = (ROOT / ".github/workflows/m33mu.yml").read_text() + aarch = (ROOT / ".github/workflows/aarch64-cross-compile.yml").read_text() + self.assertIsNone(matrix.interface_matrices_error(workflow, aarch)) + for original, replacement in ( + ("engine: [native, hsm]", "engine: [native]"), + ("profile: [default, full]", "profile: [full]"), + ("api: [psa, pkcs11, tpm]", "api: [psa, tpm]"), + ("WT_ENGINE: ${{ matrix.engine }}", "WT_ENGINE: native"), + ("WT_INTERFACE_OS: ${{ matrix.os }}", "WT_INTERFACE_OS: zephyr"), + ("WT_CRYPTO_PROFILE: ${{ matrix.profile }}", "WT_CRYPTO_PROFILE: full"), + ): + with self.subTest(replacement=replacement): + self.assertIsNotNone(matrix.interface_matrices_error( + workflow.replace(original, replacement, 1), aarch)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--build-dir", type=Path, required=True) + args = parser.parse_args() + BUILD_DIR = args.build_dir.resolve() + BUILD_DIR.mkdir(parents=True, exist_ok=True) + unittest.main(argv=["test_generator.py"], verbosity=1) diff --git a/tests/host/keystore_ipc/Makefile b/tests/host/keystore_ipc/Makefile index 189ff8056..9e45aaec2 100644 --- a/tests/host/keystore_ipc/Makefile +++ b/tests/host/keystore_ipc/Makefile @@ -35,6 +35,8 @@ CFLAGS := \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ -DWOLFHSM_CFG_NO_CRYPTO \ -DWOLFHSM_CFG_NO_SYS_TIME \ + -DCONFIG_PKCS11=1 \ + -DWT_INTERFACE_LOCAL_PROVIDER=1 \ -DWT_VAULT_WIPE_PROBE=1 \ -I. \ -I$(ROOT)/include \ diff --git a/tests/host/keystore_ipc/main.c b/tests/host/keystore_ipc/main.c index c59565b0a..b4ef3c2ce 100644 --- a/tests/host/keystore_ipc/main.c +++ b/tests/host/keystore_ipc/main.c @@ -1112,11 +1112,30 @@ static void test_door_confinement(void) check(status == PSA_ERROR_NOT_PERMITTED && g_submit_calls == 0U, "K2 another partition is refused on the ordinary wire"); (void)wt_ffm_close(&g_runtime, TEST_ROGUE_PARTITION, handle); + wt_hsm_relay_set_secure_client_policy(TEST_ROGUE_PARTITION); + handle = wt_ffm_connect(&g_runtime, TEST_ROGUE_PARTITION, TEST_HSM_SID, + 1U); + out_vec[0].len = sizeof(out); + status = wt_ffm_call(&g_runtime, TEST_ROGUE_PARTITION, handle, + PSA_IPC_CALL, in_vec, 1U, out_vec, 1U); + check(status == PSA_SUCCESS && g_submit_calls == 1U && + out_vec[0].len == 1U && out[0] == 0x5AU, + "K2 the explicitly bound Secure partition uses its ordinary wire"); + (void)wt_ffm_close(&g_runtime, TEST_ROGUE_PARTITION, handle); + wt_hsm_relay_set_secure_client_policy(0); + handle = wt_ffm_connect(&g_runtime, TEST_ROGUE_PARTITION, TEST_HSM_SID, + 1U); + out_vec[0].len = sizeof(out); + status = wt_ffm_call(&g_runtime, TEST_ROGUE_PARTITION, handle, + PSA_IPC_CALL, in_vec, 1U, out_vec, 1U); + check(status == PSA_ERROR_NOT_PERMITTED && g_submit_calls == 1U, + "K2 clearing the binding re-denies the Secure partition"); + (void)wt_ffm_close(&g_runtime, TEST_ROGUE_PARTITION, handle); handle = wt_ffm_connect(&g_runtime, TEST_NS_CLIENT, TEST_HSM_SID, 1U); out_vec[0].len = sizeof(out); status = wt_ffm_call(&g_runtime, TEST_NS_CLIENT, handle, PSA_IPC_CALL, in_vec, 1U, out_vec, 1U); - check(status == PSA_SUCCESS && g_submit_calls == 1U && + check(status == PSA_SUCCESS && g_submit_calls == 2U && out_vec[0].len == 1U && out[0] == 0x5AU, "K2 a Non-secure client is served on the ordinary wire"); (void)wt_ffm_close(&g_runtime, TEST_NS_CLIENT, handle); diff --git a/tests/host/manifest/Makefile b/tests/host/manifest/Makefile index 0be1ca404..fbd7cdc72 100644 --- a/tests/host/manifest/Makefile +++ b/tests/host/manifest/Makefile @@ -29,6 +29,7 @@ MANIFEST_TEST := $(BUILD_DIR)/test_manifest MANIFEST_SRCS := $(ROOT)/src/domain.c $(ROOT)/src/manifest.c main.c GENERATOR := $(ROOT)/tools/manifest/generate.py GENERATOR_TEST := test_generator.py +GUEST_LAYOUT_TEST := test_guest_layout.py GENERATOR_FIXTURE := fixtures/level3.json GENERATED_DIR := $(BUILD_DIR)/generated GENERATED_STAMP := $(GENERATED_DIR)/.stamp @@ -64,8 +65,13 @@ run: $(MANIFEST_TEST) $(GENERATED_TEST) $(MANIFEST_TEST) $(GENERATED_TEST) python3 $(GENERATOR_TEST) + python3 $(GUEST_LAYOUT_TEST) sh test_variant_stamp.sh $(BUILD_DIR)/variant sh test_probe_stamp.sh $(BUILD_DIR)/probe-stamp + sh test_tpm_target_guard.sh + sh test_pkcs11_pin_limit_guard.sh + sh test_interface_runner_guard.sh + sh test_guest_runner_layout.sh compiler-gcc: $(MAKE) run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc diff --git a/tests/host/manifest/test_guest_layout.py b/tests/host/manifest/test_guest_layout.py new file mode 100644 index 000000000..d774f6841 --- /dev/null +++ b/tests/host/manifest/test_guest_layout.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Tests for the build-time guest manifest layout check.""" + +import importlib.util +import sys +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +CHECKER_PATH = ROOT / "tools" / "manifest" / "check_guest_layout.py" +SPEC = importlib.util.spec_from_file_location("check_guest_layout", CHECKER_PATH) +CHECKER = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(CHECKER) + + +def guest(domain_id, base, size): + return { + "id": domain_id, + "domain_class": 2, + "entry_point": base, + "memory_resources": [ + {"base": base, "size": size, "attributes": 5}, + {"base": 0x20000000, "size": 0x1000, "attributes": 19}, + ], + } + + +class GuestLayoutTests(unittest.TestCase): + def setUp(self): + self.expected = {1: (0x080A0000, 0x40000), + 2: (0x080E0000, 0x20000)} + self.manifest = {"domains": [ + {"id": 0, "domain_class": 0}, + guest(1, *self.expected[1]), + guest(2, *self.expected[2]), + ]} + + def test_matching_layout_passes(self): + self.assertEqual(CHECKER.check_layout(self.manifest, self.expected), []) + + def test_wrong_base_fails(self): + self.manifest["domains"][1]["memory_resources"][0]["base"] += 0x1000 + self.assertTrue(CHECKER.check_layout(self.manifest, self.expected)) + + def test_wrong_size_fails(self): + self.manifest["domains"][1]["memory_resources"][0]["size"] //= 2 + self.assertTrue(CHECKER.check_layout(self.manifest, self.expected)) + + def test_entry_point_must_be_vector_base(self): + self.manifest["domains"][1]["entry_point"] += 4 + self.assertTrue(CHECKER.check_layout(self.manifest, self.expected)) + + def test_multiple_executable_regions_fail(self): + self.manifest["domains"][1]["memory_resources"].append( + {"base": 0x08100000, "size": 0x1000, "attributes": 5}) + self.assertTrue(CHECKER.check_layout(self.manifest, self.expected)) + + def test_unexpected_guest_fails(self): + self.manifest["domains"].append(guest(3, 0x08100000, 0x10000)) + self.assertTrue(CHECKER.check_layout(self.manifest, self.expected)) + + def test_missing_region_fields_fail_cleanly(self): + resource = self.manifest["domains"][1]["memory_resources"][0] + for field in ("base", "size", "attributes"): + with self.subTest(field=field): + saved = resource.pop(field) + self.assertTrue( + CHECKER.check_layout(self.manifest, self.expected)) + resource[field] = saved + + def test_non_integer_region_fields_fail_cleanly(self): + resource = self.manifest["domains"][1]["memory_resources"][0] + for field in ("base", "size", "attributes"): + with self.subTest(field=field): + saved = resource[field] + resource[field] = "invalid" + self.assertTrue( + CHECKER.check_layout(self.manifest, self.expected)) + resource[field] = saved + + def test_invalid_entry_point_fails_cleanly(self): + domain = self.manifest["domains"][1] + for entry_point in (None, "invalid"): + with self.subTest(entry_point=entry_point): + domain["entry_point"] = entry_point + self.assertTrue( + CHECKER.check_layout(self.manifest, self.expected)) + + def test_non_integer_domain_id_fails_cleanly(self): + self.manifest["domains"][1]["id"] = [] + self.assertTrue(CHECKER.check_layout(self.manifest, self.expected)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/host/manifest/test_guest_runner_layout.sh b/tests/host/manifest/test_guest_runner_layout.sh new file mode 100644 index 000000000..528c9236d --- /dev/null +++ b/tests/host/manifest/test_guest_runner_layout.sh @@ -0,0 +1,217 @@ +#!/bin/sh +# Keep every public STM32H563 firmware runner on the manifest's guest layout. +set -eu + +root=$(cd "$(dirname "$0")/../../.." && pwd) +checks=0 +failures=0 + +check_value() { + directory=$1 + variable=$2 + expected=$3 + description=$4 + actual=$(make -s -C "$directory" -pn 2>/dev/null | + awk -v key="$variable" '$1 == key && $2 == "=" { print $3; exit }') + checks=$((checks + 1)) + if [ "$actual" = "$expected" ]; then + echo " [check] PASS $description" + else + failures=$((failures + 1)) + echo " [check] FAIL $description: expected $expected, found $actual" + fi +} + +check_recipe() { + directory=$1 + target=$2 + needle=$3 + description=$4 + output_file=$(mktemp) + checks=$((checks + 1)) + if MAKEFLAGS= MFLAGS= BUILD_DIR=build WT_ENGINE=hsm WT_ENGINE_HSM=1 \ + make -n -C "$directory" "$target" \ + >"$output_file" 2>/dev/null && + grep -Fq "$needle" "$output_file"; then + echo " [check] PASS $description" + else + failures=$((failures + 1)) + echo " [check] FAIL $description" + fi + rm -f "$output_file" +} + +check_file() { + file=$1 + needle=$2 + description=$3 + checks=$((checks + 1)) + if grep -Fq "$needle" "$file"; then + echo " [check] PASS $description" + else + failures=$((failures + 1)) + echo " [check] FAIL $description" + fi +} + +check_rejected_size() { + build_dir=$(mktemp -d) + log_file="$build_dir/rejected-size.log" + checks=$((checks + 1)) + if MAKEFLAGS= MFLAGS= make -s -C "$root" TARGET=stm32h563 \ + WT_GUEST0_FLASH_SIZE=0x00020000 BUILD_DIR="$build_dir" \ + "$build_dir/manifest/.stamp" >"$log_file" 2>&1; then + rejected=0 + else + rejected=1 + fi + if [ "$rejected" -eq 1 ] && + grep -Fq 'expected 0x80a0000:0x20000' "$log_file"; then + echo " [check] PASS manifest rejects a runner slot-size mismatch" + else + failures=$((failures + 1)) + echo " [check] FAIL manifest accepts a runner slot-size mismatch" + fi + rm -f "$log_file" + rmdir "$build_dir/manifest" 2>/dev/null || true + rmdir "$build_dir" 2>/dev/null || true +} + +bare="$root/tests/firmware/stm32h563" +vnet="$root/tests/firmware/stm32h563-vnet" +zephyr="$root/tests/firmware/zephyr-stm32h5" +pkcs11_app="$root/tests/firmware/stm32h563-tpm/scripts/build_freertos_pkcs11.sh" + +for directory in "$bare" "$vnet"; do + check_value "$directory" WT_GUEST0_FLASH_ORIGIN 0x080A0000 \ + "$(basename "$directory") guest 0 links at the manifest base" + check_value "$directory" WT_GUEST1_FLASH_ORIGIN 0x080E0000 \ + "$(basename "$directory") guest 1 links at the manifest base" + check_value "$directory" WT_GUEST0_FLASH_SIZE 0x00040000 \ + "$(basename "$directory") guest 0 uses the manifest flash size" + check_value "$directory" WT_GUEST1_FLASH_SIZE 0x00020000 \ + "$(basename "$directory") guest 1 uses the manifest flash size" + check_value "$directory" WT_GUEST0_EMU_OFFSET 0xA0000 \ + "$(basename "$directory") guest 0 loads at its link offset" + check_value "$directory" WT_GUEST1_EMU_OFFSET 0xE0000 \ + "$(basename "$directory") guest 1 loads at its link offset" +done + +check_value "$zephyr" WT_GUEST0_FLASH_BASE 0x080A0000 \ + "Zephyr guest 0 links at the manifest base" +check_value "$zephyr" WT_GUEST1_FLASH_BASE 0x080E0000 \ + "Zephyr companion links at the manifest base" +check_value "$zephyr" WT_GUEST0_FLASH_SIZE 0x00040000 \ + "Zephyr guest 0 uses the manifest flash size" +check_value "$zephyr" WT_GUEST1_FLASH_SIZE 0x00020000 \ + "Zephyr companion uses the manifest flash size" +check_value "$zephyr" WT_GUEST0_EMU_OFFSET 0xA0000 \ + "Zephyr guest 0 loads at its link offset" +check_value "$zephyr" WT_GUEST1_EMU_OFFSET 0xE0000 \ + "Zephyr companion loads at its link offset" + +check_recipe "$bare" ../../../build/wolftrust.bin \ + "WT_GUEST0_FLASH_BASE=0x080A0000" \ + "baremetal runner forwards its layout to the Secure build" +check_recipe "$bare" ../../../build/wolftrust.bin \ + "WT_GUEST0_FLASH_SIZE=0x00040000" \ + "baremetal runner forwards guest 0's flash size" +check_recipe "$bare" ../../../build/wolftrust.bin \ + "WT_GUEST1_FLASH_SIZE=0x00020000" \ + "baremetal runner forwards guest 1's flash size" +check_recipe "$vnet" secure "WT_GUEST1_FLASH_BASE=0x080E0000" \ + "VNET runner forwards its layout to the Secure build" +check_recipe "$vnet" secure "WT_GUEST0_FLASH_SIZE=0x00040000" \ + "VNET runner forwards guest 0's flash size" +check_recipe "$vnet" secure "WT_GUEST1_FLASH_SIZE=0x00020000" \ + "VNET runner forwards guest 1's flash size" +check_recipe "$zephyr" secure 'WT_GUEST0_FLASH_BASE="0x080A0000"' \ + "Zephyr runner forwards its layout to the Secure build" +check_recipe "$zephyr" secure 'WT_GUEST0_FLASH_SIZE="0x00040000"' \ + "Zephyr runner forwards guest 0's flash size" +check_recipe "$zephyr" secure 'WT_GUEST1_FLASH_SIZE="0x00020000"' \ + "Zephyr runner forwards guest 1's flash size" +slot_work=$(mktemp -d) +for slot_api in psa tpm pkcs11; do + slot_tpm=n + slot_p11=n + slot_size=0x00020000 + if [ "$slot_api" = tpm ]; then slot_tpm=y; slot_size=0x00040000; fi + if [ "$slot_api" = pkcs11 ]; then slot_p11=y; slot_size=0x00040000; fi + checks=$((checks + 1)) + if env -u WT_SECURE_FLASH_SIZE MAKEFLAGS= MFLAGS= \ + make -n -C "$root" TARGET=stm32h563 TOOLPREFIX= \ + WT_ENGINE=hsm WT_ENGINE_HSM=1 CONFIG_TPM="$slot_tpm" \ + CONFIG_PKCS11="$slot_p11" BUILD_DIR="$slot_work/build" secure-image \ + > "$slot_work/output" 2>&1 && + grep -Fq -- "-DWT_SECURE_FLASH_SIZE=$slot_size" "$slot_work/output"; then + echo " [check] PASS $slot_api standalone Secure slot fits its engines" + else + failures=$((failures + 1)) + echo " [check] FAIL $slot_api standalone Secure slot is wrong" + fi +done +rm -rf "$slot_work" +check_file "$pkcs11_app" \ + 'WT_GUEST0_FLASH_SIZE="${WT_GUEST0_FLASH_SIZE:-0x00020000}"' \ + "standalone PKCS11 application defaults to its non-overlapping slot" +check_file "$zephyr/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch" \ + 'reg = <0x000a0000 DT_SIZE_K(256)>;' \ + "Zephyr board partition matches the manifest window" +check_file "$zephyr/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch" \ + '+flash: 256' \ + "Zephyr board metadata reports the manifest flash window" +check_file "$zephyr/patches/zephyr/0002-boards-arm-nucleo_h563zi-add-wolftrust-ns-reference.patch" \ + '+ram: 64' \ + "Zephyr board metadata reports the manifest RAM window" +check_rejected_size + +# Trace the actual build script without requiring a cross compiler on the host. +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT INT TERM +mkdir -p "$work/bin" "$work/freertos/FreeRTOS/Source" +touch "$work/implib.o" +cat > "$work/bin/cc" <> "$work/compiler.args" +EOF +cat > "$work/bin/arm-none-eabi-nm" <<'EOF' +#!/bin/sh +echo wt_hsm_psa_transport_cb +EOF +printf '#!/bin/sh\nexit 0\n' > "$work/bin/arm-none-eabi-size" +cp "$work/bin/arm-none-eabi-size" "$work/bin/objcopy" +chmod +x "$work/bin/"* +for slot in default single; do + rm -f "$work/compiler.args" + if [ "$slot" = default ]; then + client=2 + foreign=0x20000000 + set -- -u WT_WOLFHSM_CLIENT_ID -u WT_FREERTOS_OTHER_RAM_BASE + else + client=1 + foreign=0x20010000 + set -- WT_WOLFHSM_CLIENT_ID=1 WT_FREERTOS_OTHER_RAM_BASE=0x20010000 + fi + checks=$((checks + 1)) + if env "$@" PATH="$work/bin:$PATH" WT_ENGINE=hsm WT_ENGINE_HSM=1 \ + FREERTOS_DIR="$work/freertos" SECURE_CMSE_IMPLIB="$work/implib.o" \ + GUEST_CC="$work/bin/cc" GUEST_OBJCOPY="$work/bin/objcopy" \ + BUILD_DIR="$work/$slot" \ + sh "$zephyr/scripts/build_freertos_guest.sh" > "$work/build.log" 2>&1 && + grep -qx -- "-DWT_WOLFHSM_CLIENT_ID=$client" "$work/compiler.args" && + grep -qx -- "-DWT_FREERTOS_OTHER_RAM_BASE=${foreign}u" "$work/compiler.args"; then + echo " [check] PASS $slot PSA client identity and foreign RAM reach the compiler" + else + failures=$((failures + 1)) + echo " [check] FAIL $slot PSA client identity or foreign RAM was lost" + fi +done + +echo "Guest runner layout tests: $checks checks, $failures failures" +if [ "$failures" -eq 0 ]; then + echo "PASS: guest_runner_layout" + exit 0 +fi +echo "FAIL: guest_runner_layout" +exit 1 diff --git a/tests/host/manifest/test_interface_runner_guard.sh b/tests/host/manifest/test_interface_runner_guard.sh new file mode 100755 index 000000000..887180885 --- /dev/null +++ b/tests/host/manifest/test_interface_runner_guard.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +set -eu + +root=$(cd "$(dirname "$0")/../../.." && pwd) +target_dir="$root/tests/firmware/stm32h563-tpm" +checks=0 +failures=0 + +check_recipe() { + target=$1 + needle=$2 + description=$3 + output=$(make -n -C "$target_dir" MAKE=true "$target") + checks=$((checks + 1)) + if printf '%s\n' "$output" | grep -Fq "$needle"; then + echo " [check] PASS $description" + else + failures=$((failures + 1)) + echo " [check] FAIL $description" + fi +} + +check_recipe run-tpm "run_suite.sh m33mu tpm" \ + "TPM uses the authenticated single-application runner" +check_recipe run-pkcs11 "run_suite.sh m33mu pkcs11" \ + "PKCS11 uses the authenticated single-application runner" +check_recipe secure "WT_MAX_GUESTS=1" \ + "interface Secure images default to one guest context" +for retired in tpm-provider pkcs11-provider; do + checks=$((checks + 1)) + if make -n -C "$target_dir" "$retired" >/dev/null 2>&1; then + failures=$((failures + 1)) + echo " [check] FAIL retired $retired target still exists" + else + echo " [check] PASS no extra $retired image can be built" + fi +done +check_recipe pkcs11-freertos-app "freertos-workspace" \ + "PKCS11 application provisions FreeRTOS" +check_recipe tpm-freertos-app "freertos-workspace" \ + "TPM application provisions FreeRTOS" + +# Required API rows must distinguish a real pass from a successful skip. +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT INT TERM +mkdir -p "$work/tests/target" +cp "$root/tests/target/run_suite.sh" "$work/tests/target/run_suite.sh" +cat > "$work/tests/target/run_qemu_a_scenario.sh" <<'EOF' +#!/bin/sh +case "$WT_FAKE_RESULT" in + skip) echo 'SKIP: qemu-a/tpm: unavailable' ;; + missing) echo 'no terminal verdict' ;; + pass) echo 'PASS: qemu-a/tpm (fixture)' ;; + fail) exit 1 ;; +esac +EOF +chmod +x "$work/tests/target/run_qemu_a_scenario.sh" +check_verdict() { + checks=$((checks + 1)) + if WT_FAKE_RESULT="$1" WT_REQUIRE_SCENARIO_PASS="$2" \ + bash "$work/tests/target/run_suite.sh" qemu-a tpm \ + > "$work/out" 2>&1; then actual=0; else actual=1; fi + if [ "$actual" -eq "$3" ]; then + echo " [check] PASS verdict=$1 required=$2 (WT-FFM-0054)" + else + failures=$((failures + 1)) + echo " [check] FAIL verdict=$1 required=$2" + fi +} +check_verdict skip 0 0 +check_verdict skip 1 1 +check_verdict missing 1 1 +check_verdict pass 1 0 +check_verdict fail 1 1 + +echo "Interface runner guard tests: $checks checks, $failures failures" +if [ "$failures" -eq 0 ]; then + echo "PASS: interface_runner_guard" + exit 0 +fi +echo "FAIL: interface_runner_guard" +exit 1 diff --git a/tests/host/manifest/test_pkcs11_pin_limit_guard.sh b/tests/host/manifest/test_pkcs11_pin_limit_guard.sh new file mode 100644 index 000000000..765ac91df --- /dev/null +++ b/tests/host/manifest/test_pkcs11_pin_limit_guard.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +# Secure PKCS11 builds propagate PIN policy and select either crypto engine. +set -eu +root=$(cd "$(dirname "$0")/../../.." && pwd) +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT INT TERM +checks=0 +failures=0 +unset WT_PKCS11_PIN_MAX_FAILURES WT_PKCS11_TEST_PBKDF2_ITERATIONS 2>/dev/null || true + +report() { + checks=$((checks + 1)) + if [ "$1" -eq 0 ]; then + echo " [check] PASS $2" + else + failures=$((failures + 1)) + echo " [check] FAIL $2" + fi +} + +run_make() { + make -n -C "$root" ARCH="$arch" TARGET="$target" TOOLPREFIX= \ + CONFIG_PKCS11=y CONFIG_TPM=n WT_ENGINE="$engine" \ + BUILD_DIR="$work/$arch-$engine" "$@" > "$work/make.out" 2>&1 +} + +for arch in armv8m aarch64; do + case "$arch" in + armv8m) target=stm32h563 ;; + aarch64) target=qemuvirt ;; + esac + for engine in native hsm; do + rc=0 + run_make WT_PKCS11_PIN_MAX_FAILURES=5 || rc=1 + if [ "$rc" -eq 0 ] && grep 'pkcs11_provider\.c' "$work/make.out" | + grep -q -- '-DWT_PKCS11_PIN_MAX_FAILURES=5U'; then + report 0 "$arch/$engine requested PIN limit reaches the Secure compiler" + else + report 1 "$arch/$engine requested PIN limit reaches the Secure compiler" + fi + rc=0 + run_make || rc=1 + if [ "$rc" -eq 0 ] && grep -q 'pkcs11_provider\.c' "$work/make.out" && + ! grep -q -- '-DWT_PKCS11_PIN_MAX_FAILURES=' "$work/make.out" && + ! grep -q -- '-DPBKDF2_ITERATIONS=' "$work/make.out"; then + report 0 "$arch/$engine production PIN policy keeps its defaults" + else + report 1 "$arch/$engine production PIN policy keeps its defaults" + fi + if [ "$engine" = hsm ]; then + if grep 'pkcs11_provider\.c' "$work/make.out" | + grep -q -- '-DWOLFPKCS11_DEFAULT_DEVID=0x5748534D'; then + report 0 "$arch HSM PKCS11 selects the HSM callback (WT-FFM-0054)" + else + report 1 "$arch HSM PKCS11 selects the HSM callback (WT-FFM-0054)" + fi + elif grep 'pkcs11_provider\.c' "$work/make.out" | + grep -q -- 'WOLFPKCS11_DEFAULT_DEVID'; then + report 1 "$arch native PKCS11 uses wolfCrypt without an HSM callback" + else + report 0 "$arch native PKCS11 uses wolfCrypt without an HSM callback" + fi + for bad in 0 07 abc -1; do + rc=0 + run_make WT_PKCS11_PIN_MAX_FAILURES="$bad" || rc=1 + if [ "$rc" -ne 0 ] && grep -q 'must be > 0' "$work/make.out"; then + report 0 "$arch/$engine PIN limit '$bad' is refused" + else + report 1 "$arch/$engine PIN limit '$bad' is refused" + fi + done + done +done + +echo "PKCS11 PIN limit guard tests: $checks checks, $failures failures" +if [ "$failures" -eq 0 ]; then + echo "PASS: pkcs11_pin_limit_guard" + exit 0 +fi +echo "FAIL: pkcs11_pin_limit_guard" +exit 1 diff --git a/tests/host/manifest/test_tpm_target_guard.sh b/tests/host/manifest/test_tpm_target_guard.sh new file mode 100755 index 000000000..caf099d11 --- /dev/null +++ b/tests/host/manifest/test_tpm_target_guard.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# The optional application interfaces may only be selected for ports whose +# Secure manifest exposes the matching service. +set -eu + +root=$(cd "$(dirname "$0")/../../.." && pwd) +checks=0 +failures=0 + +check_make() { + config=$1 + target=$2 + arch=$3 + engine=$4 + expected=$5 + checks=$((checks + 1)) + if (cd "$root" && make -s ARCH="$arch" TARGET="$target" \ + TOOLPREFIX= WT_ENGINE="$engine" "$config=y" FORCE \ + >/dev/null 2>&1); then + actual=pass + else + actual=fail + fi + + if [ "$actual" = "$expected" ]; then + echo " [check] PASS $config=y $expected for $target" + else + failures=$((failures + 1)) + echo " [check] FAIL $config=y should $expected for $target" + fi +} + +check_conflict() { + config=$1 + target=$2 + arch=$3 + engine=$4 + checks=$((checks + 1)) + if (cd "$root" && make -s ARCH="$arch" TARGET="$target" \ + TOOLPREFIX= WT_ENGINE="$engine" "$config=y" CONFIG_VNET=y \ + FORCE >/dev/null 2>&1); then + failures=$((failures + 1)) + echo " [check] FAIL $config=y accepted CONFIG_VNET=y for $target" + else + echo " [check] PASS $config=y rejects CONFIG_VNET=y for $target" + fi +} + +for selected_engine in native hsm; do + for selected_config in CONFIG_TPM CONFIG_PKCS11; do + check_make "$selected_config" stm32h563 armv8m "$selected_engine" pass + check_make "$selected_config" mimxrt700 armv8m "$selected_engine" pass + check_make "$selected_config" qemuvirt aarch64 "$selected_engine" pass + check_make "$selected_config" versal aarch64 "$selected_engine" pass + check_make "$selected_config" unsupported armv8m "$selected_engine" fail + check_make "$selected_config" stm32h563 armv8m invalid fail + done +done + +for target_arch in "stm32h563 armv8m" "mimxrt700 armv8m" \ + "qemuvirt aarch64" "versal aarch64"; do + set -- $target_arch + check_conflict CONFIG_TPM "$1" "$2" native + check_conflict CONFIG_PKCS11 "$1" "$2" hsm +done + +echo "Interface target guard tests: $checks checks, $failures failures" +if [ "$failures" -eq 0 ]; then + echo "PASS: tpm_target_guard (WT-FFM-0054, both provider engines)" + exit 0 +fi +echo "FAIL: tpm_target_guard" +exit 1 diff --git a/tests/host/pkcs11_client/Makefile b/tests/host/pkcs11_client/Makefile new file mode 100644 index 000000000..b1bd50fb5 --- /dev/null +++ b/tests/host/pkcs11_client/Makefile @@ -0,0 +1,64 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I. \ + -I$(ROOT)/include \ + -std=c11 -O2 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +OBJS := \ + $(BUILD_DIR)/pkcs11_psa_transport.o \ + $(BUILD_DIR)/main.o +TEST_BIN := $(BUILD_DIR)/test_pkcs11_client +TRANSPORT_SRC := $(ROOT)/src/client/pkcs11_psa_transport.c + +.PHONY: all run check-zeroize compilers sanitize valgrind clean + +all: check-zeroize $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/pkcs11_psa_transport.o: \ + $(TRANSPORT_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/main.o: main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(OBJS) + +check-zeroize: + @grep -Fq 'wt_forceZero(&chunk, sizeof(chunk));' $(TRANSPORT_SRC) + @! grep -Fq 'memset(&chunk' $(TRANSPORT_SRC) + @echo "PASS: PKCS11 client chunk uses optimization-resistant zeroization" + +run: check-zeroize $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/pkcs11_client/main.c b/tests/host/pkcs11_client/main.c new file mode 100644 index 000000000..f33e9477e --- /dev/null +++ b/tests/host/pkcs11_client/main.c @@ -0,0 +1,376 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* PKCS11 PSA client suite: a scripted relay checks chunking, synchronous execution, + * response framing, abort recovery, and cached-connection recovery. */ + +#include "psa/client.h" +#include "psa/error.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/pkcs11_psa_transport.h" + +#include +#include +#include + +enum mock_state { + MOCK_IDLE, + MOCK_WRITING, + MOCK_PENDING, + MOCK_DONE +}; + +static int g_failures; +static int g_connects; +static int g_calls; +static int g_closes; +static int g_aborts; +static int32_t g_fail_op; +static psa_status_t g_fail_status; +static enum mock_state g_state; +static uint8_t g_request[WT_PKCS11_MESSAGE_MAX]; +static size_t g_request_expected; +static size_t g_request_received; +static uint8_t g_response[WT_PKCS11_MESSAGE_MAX]; +static size_t g_response_len; +static unsigned int g_executions; +static unsigned int g_polls; +static int g_auto_complete; +static size_t g_forced_poll_len; +static size_t g_forced_read_len; + +static void check(int ok, const char* what) +{ + if (ok != 0) { + (void)printf("PASS: %s\n", what); + } + else { + (void)printf("FAIL: %s\n", what); + g_failures++; + } +} + +psa_handle_t psa_connect(uint32_t sid, uint32_t version) +{ + check(sid == WT_PKCS11_SERVICE_SID && + version == WT_PKCS11_SERVICE_VERSION, + "client connects to SERVICE_PKCS11 with the supported version"); + g_connects++; + return (psa_handle_t)42; +} + +void psa_close(psa_handle_t handle) +{ + check(handle == (psa_handle_t)42, + "client closes the cached PKCS11 handle"); + g_closes++; +} + +static psa_status_t mock_begin(const psa_invec* in_vec, size_t in_len) +{ + const wt_pkcs11_length_t* length; + + if (in_vec == NULL || in_len != 1U || + in_vec[0].len != sizeof(*length) || + in_vec[0].base == NULL || g_state != MOCK_IDLE) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + length = (const wt_pkcs11_length_t*)in_vec[0].base; + if (length->length == 0U || length->length > sizeof(g_request)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + g_request_expected = length->length; + g_request_received = 0U; + g_state = MOCK_WRITING; + return PSA_SUCCESS; +} + +static psa_status_t mock_write(const psa_invec* in_vec, size_t in_len) +{ + const wt_pkcs11_chunk_t* chunk; + size_t header = sizeof(chunk->offset) + sizeof(chunk->length); + + if (in_vec == NULL || in_len != 1U || in_vec[0].base == NULL || + in_vec[0].len < header || g_state != MOCK_WRITING) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + chunk = (const wt_pkcs11_chunk_t*)in_vec[0].base; + if (chunk->length != in_vec[0].len - header || + chunk->offset != g_request_received || + chunk->length > WT_PKCS11_CHUNK_MAX || + chunk->length > g_request_expected - g_request_received) { + return PSA_ERROR_INVALID_ARGUMENT; + } + (void)memcpy(g_request + g_request_received, chunk->data, + chunk->length); + g_request_received += chunk->length; + return PSA_SUCCESS; +} + +static psa_status_t mock_poll(psa_outvec* out_vec, size_t out_len) +{ + wt_pkcs11_length_t length; + + if (out_vec == NULL || out_len != 1U || out_vec[0].base == NULL || + out_vec[0].len < sizeof(length)) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + if (g_state == MOCK_PENDING) { + if (g_auto_complete == 0) { + return PSA_ERROR_CONNECTION_BUSY; + } + g_state = MOCK_DONE; + } + if (g_state != MOCK_DONE) { + return PSA_ERROR_BAD_STATE; + } + length.length = (uint32_t)g_response_len; + (void)memcpy(out_vec[0].base, &length, sizeof(length)); + out_vec[0].len = g_forced_poll_len != 0U ? g_forced_poll_len : + sizeof(length); + return PSA_SUCCESS; +} + +static psa_status_t mock_read(const psa_invec* in_vec, size_t in_len, + psa_outvec* out_vec, size_t out_len) +{ + const wt_pkcs11_range_t* range; + + if (in_vec == NULL || in_len != 1U || out_vec == NULL || out_len != 1U || + in_vec[0].base == NULL || in_vec[0].len != sizeof(*range) || + out_vec[0].base == NULL || g_state != MOCK_DONE) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + range = (const wt_pkcs11_range_t*)in_vec[0].base; + if (range->offset > g_response_len || + range->length > g_response_len - range->offset || + out_vec[0].len < range->length) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + (void)memcpy(out_vec[0].base, g_response + range->offset, range->length); + out_vec[0].len = g_forced_read_len != 0U ? g_forced_read_len : + range->length; + if (range->offset + range->length == g_response_len) { + g_state = MOCK_IDLE; + } + return PSA_SUCCESS; +} + +psa_status_t psa_call(psa_handle_t handle, int32_t type, + const psa_invec* in_vec, size_t in_len, + psa_outvec* out_vec, size_t out_len) +{ + g_calls++; + if (handle != (psa_handle_t)42) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + if (type == g_fail_op) { + g_fail_op = 0; + return g_fail_status; + } + switch (type) { + case WT_PKCS11_OP_APP_BEGIN: + return mock_begin(in_vec, in_len); + case WT_PKCS11_OP_APP_WRITE: + return mock_write(in_vec, in_len); + case WT_PKCS11_OP_APP_EXECUTE: + g_executions++; + if (g_state != MOCK_WRITING || + g_request_received != g_request_expected) { + return PSA_ERROR_BAD_STATE; + } + g_state = MOCK_PENDING; + return mock_poll(out_vec, out_len); + case 4 /* retired polling opcode */: + g_polls++; + return PSA_ERROR_NOT_SUPPORTED; + case WT_PKCS11_OP_APP_READ: + return mock_read(in_vec, in_len, out_vec, out_len); + case WT_PKCS11_OP_APP_ABORT: + g_state = MOCK_IDLE; + g_aborts++; + return PSA_SUCCESS; + default: + return PSA_ERROR_PROGRAMMER_ERROR; + } +} + +static void mock_reset(void) +{ + wt_pkcs11_psa_close(); + g_connects = 0; + g_calls = 0; + g_closes = 0; + g_aborts = 0; + g_fail_op = 0; + g_fail_status = PSA_SUCCESS; + g_state = MOCK_IDLE; + g_request_expected = 0U; + g_request_received = 0U; + g_response_len = 0U; + g_executions = 0U; + g_polls = 0U; + g_auto_complete = 1; + g_forced_poll_len = 0U; + g_forced_read_len = 0U; + (void)memset(g_request, 0, sizeof(g_request)); + (void)memset(g_response, 0, sizeof(g_response)); +} + +static void fill_pattern(uint8_t* buffer, size_t length, uint8_t mask) +{ + size_t i; + + for (i = 0U; i < length; i++) { + buffer[i] = (uint8_t)i ^ mask; + } +} + +static void test_arguments(void) +{ + uint8_t byte = 0U; + size_t length = 0U; + + mock_reset(); + check(wt_pkcs11_psa_call(NULL, 1U, &byte, 1U, &length) == + PSA_ERROR_INVALID_ARGUMENT && + wt_pkcs11_psa_call(&byte, 0U, &byte, 1U, &length) == + PSA_ERROR_INVALID_ARGUMENT && + wt_pkcs11_psa_call(&byte, 1U, NULL, 1U, &length) == + PSA_ERROR_INVALID_ARGUMENT && + wt_pkcs11_psa_call(&byte, 1U, &byte, 0U, &length) == + PSA_ERROR_INVALID_ARGUMENT && + wt_pkcs11_psa_call(&byte, 1U, &byte, 1U, NULL) == + PSA_ERROR_INVALID_ARGUMENT && + g_connects == 0 && g_calls == 0, + "client rejects invalid call arguments before connecting"); +} + +static void test_chunking_and_execution(void) +{ + uint8_t request[WT_PKCS11_CHUNK_MAX + 17U]; + uint8_t output[WT_PKCS11_CHUNK_MAX + 23U]; + size_t output_len = 0U; + + mock_reset(); + fill_pattern(request, sizeof(request), 0x5AU); + fill_pattern(g_response, sizeof(output), 0xA5U); + g_response_len = sizeof(output); + check(wt_pkcs11_psa_call(request, sizeof(request), output, sizeof(output), + &output_len) == PSA_SUCCESS && + output_len == sizeof(output) && + memcmp(g_request, request, sizeof(request)) == 0 && + memcmp(output, g_response, sizeof(output)) == 0 && + g_connects == 1 && g_state == MOCK_IDLE && + g_executions == 1U && g_polls == 0U, + "chunked request completes with one synchronous execution and no polls"); + + mock_reset(); + g_auto_complete = 0; + check(wt_pkcs11_psa_call(request, 16U, output, sizeof(output), + &output_len) == PSA_ERROR_CONNECTION_BUSY && g_aborts == 1 && + g_state == MOCK_IDLE && g_executions == 1U && g_polls == 0U, + "failed execution aborts without replay"); +} + +static void test_response_failures(void) +{ + uint8_t request[32]; + uint8_t output[32]; + size_t output_len = 99U; + + fill_pattern(request, sizeof(request), 0x11U); + + mock_reset(); + g_response_len = sizeof(output); + check(wt_pkcs11_psa_call(request, sizeof(request), output, 8U, + &output_len) == PSA_ERROR_BUFFER_TOO_SMALL && + output_len == 0U && g_aborts == 1 && g_state == MOCK_IDLE, + "short response capacity aborts the completed transaction"); + + mock_reset(); + g_response_len = sizeof(output); + g_forced_poll_len = sizeof(wt_pkcs11_length_t) - 1U; + check(wt_pkcs11_psa_call(request, sizeof(request), output, sizeof(output), + &output_len) == PSA_ERROR_COMMUNICATION_FAILURE && + g_aborts == 1 && g_state == MOCK_IDLE, + "short response-length metadata aborts the transaction"); + + mock_reset(); + g_response_len = sizeof(output); + g_forced_read_len = sizeof(output) - 1U; + check(wt_pkcs11_psa_call(request, sizeof(request), output, sizeof(output), + &output_len) == PSA_ERROR_COMMUNICATION_FAILURE && + g_aborts == 1 && g_state == MOCK_IDLE, + "short response chunk aborts the transaction"); + + mock_reset(); + g_response_len = sizeof(output); + g_fail_op = WT_PKCS11_OP_APP_WRITE; + g_fail_status = PSA_ERROR_GENERIC_ERROR; + check(wt_pkcs11_psa_call(request, sizeof(request), output, sizeof(output), + &output_len) == PSA_ERROR_GENERIC_ERROR && + g_aborts == 1 && g_state == MOCK_IDLE, + "request-write failure aborts the partial transaction"); +} + +static void test_connection_recovery(void) +{ + static const psa_status_t dead[] = { + PSA_ERROR_PROGRAMMER_ERROR, + PSA_ERROR_CONNECTION_BUSY, + PSA_ERROR_COMMUNICATION_FAILURE, + PSA_ERROR_BAD_STATE + }; + uint8_t request[16] = { 0U }; + uint8_t output[16]; + size_t output_len; + size_t i; + + for (i = 0U; i < sizeof(dead) / sizeof(dead[0]); i++) { + mock_reset(); + g_fail_op = WT_PKCS11_OP_APP_BEGIN; + g_fail_status = dead[i]; + check(wt_pkcs11_psa_call(request, sizeof(request), output, + sizeof(output), &output_len) == dead[i] && + g_connects == 1 && g_closes == 1, + "dead connection status evicts the cached handle"); + g_response_len = sizeof(output); + check(wt_pkcs11_psa_call(request, sizeof(request), output, + sizeof(output), &output_len) == PSA_SUCCESS && + g_connects == 2, + "the request after eviction reconnects"); + } +} + +int main(void) +{ + test_arguments(); + test_chunking_and_execution(); + test_response_failures(); + test_connection_recovery(); + + wt_pkcs11_psa_close(); + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: PKCS11 PSA client chunking, execution, and recovery\n"); + return 0; +} diff --git a/tests/host/pkcs11_provider/Makefile b/tests/host/pkcs11_provider/Makefile new file mode 100644 index 000000000..fd6071555 --- /dev/null +++ b/tests/host/pkcs11_provider/Makefile @@ -0,0 +1,68 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I. \ + -I$(ROOT)/include \ + -I$(ROOT)/lib/wolfPKCS11 \ + -DWOLFPKCS11_USER_SETTINGS -DWOLFPKCS11_NO_STORE \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +OBJS := \ + $(BUILD_DIR)/pkcs11_provider.o \ + $(BUILD_DIR)/pkcs11_wire.o \ + $(BUILD_DIR)/provider_stubs.o \ + $(BUILD_DIR)/main.o +TEST_BIN := $(BUILD_DIR)/test_pkcs11_provider + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/pkcs11_provider.o: $(ROOT)/src/pkcs11/pkcs11_provider.c \ + provider_stubs.h | $(BUILD_DIR) + $(CC) $(CFLAGS) -include provider_stubs.h -c -o $@ $< + +$(BUILD_DIR)/pkcs11_wire.o: $(ROOT)/src/pkcs11/pkcs11_wire.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/provider_stubs.o: provider_stubs.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/main.o: main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/pkcs11_provider/main.c b/tests/host/pkcs11_provider/main.c new file mode 100644 index 000000000..db2027577 --- /dev/null +++ b/tests/host/pkcs11_provider/main.c @@ -0,0 +1,1066 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolftrust/pkcs11_provider.h" +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" + +#include "wolfpkcs11/pkcs11.h" + +#include +#include +#include + +#define TEST_BUFFER_SIZE 128U +#define TEST_CANARY 0xa5U +#define TEST_TEMPLATE_MAX 32U + +extern unsigned int g_provider_test_calls; +extern unsigned int g_provider_test_aligned; +extern unsigned int g_provider_test_get_attribute_calls; +extern unsigned int g_provider_test_ecdh_valid; +extern unsigned int g_provider_test_pin_checks; +extern unsigned int g_provider_test_token_initialized; +extern unsigned int g_provider_test_open_output; +extern unsigned int g_provider_test_open_callbacks; +extern unsigned int g_provider_test_null_seen; +extern CK_RV g_provider_test_after_pin_rv; + +#define TEST_PIN_FLAGS (CKF_USER_PIN_COUNT_LOW | CKF_USER_PIN_FINAL_TRY | \ + CKF_USER_PIN_LOCKED | CKF_SO_PIN_COUNT_LOW | CKF_SO_PIN_FINAL_TRY | \ + CKF_SO_PIN_LOCKED) + +static int g_failures; + +static void check(int ok, const char* what) +{ + if (ok != 0) { + (void)printf("PASS: %s\n", what); + } + else { + (void)printf("FAIL: %s\n", what); + g_failures++; + } +} + +static void request_begin(wt_pkcs11_writer_t* writer, uint8_t* request, + uint32_t function) +{ + wt_pkcs11_writer_init(writer, request, TEST_BUFFER_SIZE); + wt_pkcs11_put_u32(writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(writer, WT_PKCS11_WIRE_VERSION); + wt_pkcs11_put_u32(writer, function); +} + +static uint32_t response_rv(uint8_t* response, size_t response_len) +{ + wt_pkcs11_reader_t reader; + uint32_t rv; + + wt_pkcs11_reader_init(&reader, response, response_len); + if (wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_MAGIC || + wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_VERSION) { + return UINT32_MAX; + } + rv = wt_pkcs11_get_u32(&reader); + return reader.error == 0 ? rv : UINT32_MAX; +} + +static int dispatch_invalid(uint8_t* request, size_t request_len, + size_t response_cap, int expected_rc, + int expect_response) +{ + uint8_t guarded[TEST_BUFFER_SIZE + 2U]; + size_t response_len = 0U; + int rc; + + (void)memset(guarded, TEST_CANARY, sizeof(guarded)); + g_provider_test_calls = 0U; + rc = wt_pkcs11_provider_dispatch(request, request_len, &guarded[1], + response_cap, &response_len, NULL); + if (rc != expected_rc || g_provider_test_calls != 0U || + guarded[0] != TEST_CANARY || + guarded[response_cap + 1U] != TEST_CANARY) { + return 0; + } + if (expect_response != 0) { + return response_rv(&guarded[1], response_len) == CKR_ARGUMENTS_BAD; + } + return response_len == 0U; +} + +static void test_truncated_header(void) +{ + uint8_t request[4]; + wt_pkcs11_writer_t writer; + + wt_pkcs11_writer_init(&writer, request, sizeof(request)); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_MAGIC); + check(dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, -1, 0), + "truncated provider header fails before PKCS11 dispatch"); +} + +static size_t oversized_template_request(uint8_t* request) +{ + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_CREATE_OBJECT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, TEST_TEMPLATE_MAX + 1U); + return writer.used; +} + +static void test_oversized_template(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + size_t request_len = oversized_template_request(request); + + check(dispatch_invalid(request, request_len, TEST_BUFFER_SIZE, 0, 1), + "oversized provider template fails before C_CreateObject"); +} + +static void test_oversized_get_attribute_template(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_GET_ATTRIBUTE_VALUE); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 7U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, TEST_TEMPLATE_MAX + 1U); + g_provider_test_get_attribute_calls = 0U; + check(dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, 0, 1) && + g_provider_test_get_attribute_calls == 0U, + "oversized get-attribute template stays within provider bounds"); +} + +static void test_truncated_blob(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_INIT_PIN); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 4U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 16U); + check(dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, 0, 1), + "blob length beyond provider frame fails before C_InitPIN"); +} + +static void test_truncated_mechanism(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_ENCRYPT_INIT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, CKM_AES_CTR); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 128U); + wt_pkcs11_put_u32(&writer, 16U); + check(dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, 0, 1), + "short mechanism payload fails before C_EncryptInit"); +} + +static int raw_mechanism_is_rejected(CK_MECHANISM_TYPE mechanism) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t parameter[8] = { 0U }; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_ENCRYPT_INIT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, (uint32_t)mechanism); + wt_pkcs11_put_u32(&writer, 0U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, parameter, sizeof(parameter)); + wt_pkcs11_put_u32(&writer, 1U); + return dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, 0, 1); +} + +static void test_mechanism_kind_mismatch(void) +{ + check(raw_mechanism_is_rejected(CKM_AES_CTR) && + raw_mechanism_is_rejected(CKM_AES_GCM) && + raw_mechanism_is_rejected(CKM_AES_CCM) && + raw_mechanism_is_rejected(CKM_ECDH1_DERIVE), + "pointer-bearing mechanisms reject a mismatched raw wire tag"); + check(raw_mechanism_is_rejected(CKM_RSA_PKCS_OAEP) && + raw_mechanism_is_rejected(CKM_HKDF_DERIVE) && + raw_mechanism_is_rejected(CKM_HKDF_DATA) && + raw_mechanism_is_rejected(CKM_AES_CBC_ENCRYPT_DATA), + "unsupported nested-pointer mechanisms fail closed"); +} + +static int ecdh_shared_data_is_null(uint32_t shared_present) +{ + static const uint8_t point[] = { 0x04U, 0x11U, 0x22U }; + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + size_t response_len = 0U; + int rc; + + request_begin(&writer, request, WT_P11_DERIVE_KEY); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, CKM_ECDH1_DERIVE); + wt_pkcs11_put_u32(&writer, WT_P11_MECH_ECDH); + wt_pkcs11_put_u32(&writer, CKD_NULL); + wt_pkcs11_put_u32(&writer, shared_present); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, point, sizeof(point)); + wt_pkcs11_put_u32(&writer, 12U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 0U); + g_provider_test_calls = 0U; + g_provider_test_ecdh_valid = 0U; + g_provider_test_null_seen = 2U; + rc = wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, NULL); + if (rc != 0 || response_rv(response, response_len) != CKR_OK || + g_provider_test_calls != 1U || g_provider_test_ecdh_valid == 0U) { + return -1; + } + return g_provider_test_null_seen == 1U ? 1 : 0; +} + +static void test_ecdh_shared_data_presence(void) +{ + check(ecdh_shared_data_is_null(0U) == 1, + "NULL empty ECDH shared data reaches wolfPKCS11 as NULL"); + check(ecdh_shared_data_is_null(1U) == 0, + "non-NULL empty ECDH shared data reaches wolfPKCS11 as non-NULL"); +} + +static void test_short_response(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + size_t request_len = oversized_template_request(request); + + check(dispatch_invalid(request, request_len, 12U, -1, 0), + "short provider response preserves its bounds"); +} + +static void test_aligned_template_value(void) +{ + uint64_t request_words[TEST_BUFFER_SIZE / sizeof(uint64_t)]; + uint8_t response[TEST_BUFFER_SIZE]; + uint8_t* request = (uint8_t*)request_words; + wt_pkcs11_writer_t writer; + CK_OBJECT_CLASS object_class = CKO_DATA; + size_t response_len = 0U; + int rc; + + request_begin(&writer, request, WT_P11_CREATE_OBJECT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, CKA_CLASS); + wt_pkcs11_put_u32(&writer, sizeof(object_class)); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, &object_class, sizeof(object_class)); + g_provider_test_calls = 0U; + g_provider_test_aligned = 0U; + rc = wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, NULL); + check(rc == 0 && g_provider_test_calls == 1U && + g_provider_test_aligned != 0U && + response_rv(response, response_len) == CKR_OK, + "provider passes aligned scalar attribute values to PKCS11"); +} + +static size_t get_attribute_request(uint8_t* request) +{ + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_GET_ATTRIBUTE_VALUE); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 7U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, CKA_LABEL); + wt_pkcs11_put_u32(&writer, WT_PKCS11_MESSAGE_MAX + 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + return writer.used; +} + +static int get_attribute_response(const uint8_t* response, + size_t response_len, uint32_t expected_rv, + size_t expected_value_len) +{ + wt_pkcs11_reader_t reader; + uint8_t* value; + size_t value_len; + + wt_pkcs11_reader_init(&reader, (uint8_t*)response, response_len); + if (wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_MAGIC || + wt_pkcs11_get_u32(&reader) != WT_PKCS11_WIRE_VERSION || + wt_pkcs11_get_u32(&reader) != expected_rv || + wt_pkcs11_get_u32(&reader) != 1U || + wt_pkcs11_get_u32(&reader) != 3U) { + return 0; + } + value = wt_pkcs11_get_bytes(&reader, &value_len); + return reader.error == 0 && reader.used == reader.length && + value_len == expected_value_len && + (value_len == 0U || memcmp(value, "key", value_len) == 0); +} + +static void test_large_attribute_capacity(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + size_t request_len = get_attribute_request(request); + size_t response_len = 0U; + int rc; + + g_provider_test_get_attribute_calls = 0U; + rc = wt_pkcs11_provider_dispatch(request, request_len, response, + sizeof(response), &response_len, NULL); + check(rc == 0 && g_provider_test_get_attribute_calls == 2U && + get_attribute_response(response, response_len, CKR_OK, 3U), + "large attribute capacity stages only the returned value"); +} + +static void test_attribute_response_boundary(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + size_t request_len = get_attribute_request(request); + size_t response_len = 0U; + int rc; + + g_provider_test_get_attribute_calls = 0U; + rc = wt_pkcs11_provider_dispatch(request, request_len, response, 24U, + &response_len, NULL); + check(rc == 0 && g_provider_test_get_attribute_calls == 1U && + get_attribute_response(response, response_len, + CKR_DEVICE_MEMORY, 0U), + "attribute value beyond the response frame fails safely"); +} + +static void test_attribute_caller_buffer_too_small(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint8_t* value; + size_t value_len; + size_t response_len = 0U; + int valid; + int rc; + + request_begin(&writer, request, WT_P11_GET_ATTRIBUTE_VALUE); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 7U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 2U); + wt_pkcs11_put_u32(&writer, CKA_LABEL); + wt_pkcs11_put_u32(&writer, 2U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + wt_pkcs11_put_u32(&writer, CKA_LABEL); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + + g_provider_test_get_attribute_calls = 0U; + rc = wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, NULL); + wt_pkcs11_reader_init(&reader, response, response_len); + valid = wt_pkcs11_get_u32(&reader) == WT_PKCS11_WIRE_MAGIC && + wt_pkcs11_get_u32(&reader) == WT_PKCS11_WIRE_VERSION && + wt_pkcs11_get_u32(&reader) == CKR_BUFFER_TOO_SMALL && + wt_pkcs11_get_u32(&reader) == 2U && + wt_pkcs11_get_u32(&reader) == 3U; + value = wt_pkcs11_get_bytes(&reader, &value_len); + valid = valid && value_len == 0U; + valid = valid && wt_pkcs11_get_u32(&reader) == 3U; + value = wt_pkcs11_get_bytes(&reader, &value_len); + valid = valid && reader.error == 0 && reader.used == reader.length && + value_len == 3U && memcmp(value, "key", 3U) == 0; + check(rc == 0 && g_provider_test_get_attribute_calls == 2U && valid, + "mixed attribute capacities preserve buffer-too-small semantics"); +} + +static void test_attribute_type_invalid_with_valid_peer(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint8_t* value; + size_t value_len; + size_t response_len = 0U; + int valid; + int rc; + + request_begin(&writer, request, WT_P11_GET_ATTRIBUTE_VALUE); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 7U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 2U); + wt_pkcs11_put_u32(&writer, CKA_LABEL); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + wt_pkcs11_put_u32(&writer, CKA_APPLICATION); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + + g_provider_test_get_attribute_calls = 0U; + rc = wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, NULL); + wt_pkcs11_reader_init(&reader, response, response_len); + valid = wt_pkcs11_get_u32(&reader) == WT_PKCS11_WIRE_MAGIC && + wt_pkcs11_get_u32(&reader) == WT_PKCS11_WIRE_VERSION && + wt_pkcs11_get_u32(&reader) == CKR_ATTRIBUTE_TYPE_INVALID && + wt_pkcs11_get_u32(&reader) == 2U && + wt_pkcs11_get_u32(&reader) == 3U; + value = wt_pkcs11_get_bytes(&reader, &value_len); + valid = valid && value_len == 3U && memcmp(value, "key", 3U) == 0; + valid = valid && + wt_pkcs11_get_u32(&reader) == (uint32_t)CK_UNAVAILABLE_INFORMATION; + value = wt_pkcs11_get_bytes(&reader, &value_len); + valid = valid && reader.error == 0 && reader.used == reader.length && + value_len == 0U; + check(rc == 0 && g_provider_test_get_attribute_calls == 2U && valid, + "invalid attribute preserves valid peer output and status"); +} + +static void test_sensitive_attribute_with_valid_peer(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + uint8_t* value; + size_t value_len; + size_t response_len = 0U; + int valid; + int rc; + + request_begin(&writer, request, WT_P11_GET_ATTRIBUTE_VALUE); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 7U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 2U); + wt_pkcs11_put_u32(&writer, CKA_LABEL); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + wt_pkcs11_put_u32(&writer, CKA_PRIVATE_EXPONENT); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + + g_provider_test_get_attribute_calls = 0U; + rc = wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, NULL); + wt_pkcs11_reader_init(&reader, response, response_len); + valid = wt_pkcs11_get_u32(&reader) == WT_PKCS11_WIRE_MAGIC && + wt_pkcs11_get_u32(&reader) == WT_PKCS11_WIRE_VERSION && + wt_pkcs11_get_u32(&reader) == CKR_ATTRIBUTE_SENSITIVE && + wt_pkcs11_get_u32(&reader) == 2U && + wt_pkcs11_get_u32(&reader) == 3U; + value = wt_pkcs11_get_bytes(&reader, &value_len); + valid = valid && value_len == 3U && memcmp(value, "key", 3U) == 0; + valid = valid && + wt_pkcs11_get_u32(&reader) == (uint32_t)CK_UNAVAILABLE_INFORMATION; + value = wt_pkcs11_get_bytes(&reader, &value_len); + valid = valid && reader.error == 0 && reader.used == reader.length && + value_len == 0U; + check(rc == 0 && g_provider_test_get_attribute_calls == 2U && valid, + "sensitive attribute preserves valid peer output and status"); +} + +static uint32_t dispatch_rv(uint8_t* request, size_t request_len) +{ + uint8_t response[TEST_BUFFER_SIZE]; + size_t response_len = 0U; + + if (wt_pkcs11_provider_dispatch(request, request_len, response, + sizeof(response), &response_len, + NULL) != 0) { + return UINT32_MAX; + } + return response_rv(response, response_len); +} + +static void put_pin(wt_pkcs11_writer_t* writer, const char* pin) +{ + size_t length = strlen(pin); + + wt_pkcs11_put_u32(writer, (uint32_t)length); + wt_pkcs11_put_u32(writer, 1U); + wt_pkcs11_put_bytes(writer, pin, length); +} + +static uint32_t pin_login(uint32_t session, uint32_t user, const char* pin) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_LOGIN); + wt_pkcs11_put_u32(&writer, session); + wt_pkcs11_put_u32(&writer, user); + put_pin(&writer, pin); + return dispatch_rv(request, writer.used); +} + +static uint32_t pin_login_null(uint32_t session, uint32_t user) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_LOGIN); + wt_pkcs11_put_u32(&writer, session); + wt_pkcs11_put_u32(&writer, user); + wt_pkcs11_put_u32(&writer, 0U); + wt_pkcs11_put_u32(&writer, 0U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + return dispatch_rv(request, writer.used); +} + +static uint32_t pin_logout(uint32_t session) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_LOGOUT); + wt_pkcs11_put_u32(&writer, session); + return dispatch_rv(request, writer.used); +} + +static uint32_t pin_set(uint32_t session, const char* old_pin, + const char* new_pin) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_SET_PIN); + wt_pkcs11_put_u32(&writer, session); + put_pin(&writer, old_pin); + put_pin(&writer, new_pin); + return dispatch_rv(request, writer.used); +} + +static uint32_t pin_init(uint32_t session, const char* pin) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_INIT_PIN); + wt_pkcs11_put_u32(&writer, session); + put_pin(&writer, pin); + return dispatch_rv(request, writer.used); +} + +static uint32_t token_init(const char* so_pin) +{ + static const uint8_t label[32] = { 't', 'o', 'k', 'e', 'n' }; + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_INIT_TOKEN); + wt_pkcs11_put_u32(&writer, 1U); + put_pin(&writer, so_pin); + wt_pkcs11_put_bytes(&writer, label, sizeof(label)); + return dispatch_rv(request, writer.used); +} + +static CK_FLAGS pin_flags_for(unsigned int failures, CK_FLAGS low, + CK_FLAGS final_try, CK_FLAGS locked) +{ + CK_FLAGS flags = failures > 0U ? low : 0U; + + if (failures >= WT_PKCS11_PIN_MAX_FAILURES) { + flags |= locked; + } + else if (failures + 1U == WT_PKCS11_PIN_MAX_FAILURES) { + flags |= final_try; + } + return flags; +} + +static int token_pin_counts_are(unsigned int user, unsigned int so) +{ + CK_FLAGS expected = pin_flags_for(user, CKF_USER_PIN_COUNT_LOW, + CKF_USER_PIN_FINAL_TRY, + CKF_USER_PIN_LOCKED) | + pin_flags_for(so, CKF_SO_PIN_COUNT_LOW, + CKF_SO_PIN_FINAL_TRY, + CKF_SO_PIN_LOCKED); + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE + sizeof(CK_TOKEN_INFO)]; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + CK_TOKEN_INFO info; + uint8_t* data; + size_t length = 0U; + size_t response_len = 0U; + + request_begin(&writer, request, WT_P11_GET_TOKEN_INFO); + wt_pkcs11_put_u32(&writer, 1U); + if (wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, + NULL) != 0 || + response_rv(response, response_len) != CKR_OK) { + return 0; + } + wt_pkcs11_reader_init(&reader, response, response_len); + (void)wt_pkcs11_get_u32(&reader); + (void)wt_pkcs11_get_u32(&reader); + (void)wt_pkcs11_get_u32(&reader); + data = wt_pkcs11_get_bytes(&reader, &length); + if (reader.error != 0 || length != sizeof(info)) { + return 0; + } + (void)memcpy(&info, data, sizeof(info)); + return (info.flags & TEST_PIN_FLAGS) == expected; +} + +static uint32_t session_open(uint32_t flags, uint32_t has_output, + uint32_t* handle) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + size_t response_len = 0U; + uint32_t rv; + + request_begin(&writer, request, WT_P11_OPEN_SESSION); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, flags); + wt_pkcs11_put_u32(&writer, has_output); + g_provider_test_calls = 0U; + if (wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, + NULL) != 0) { + return UINT32_MAX; + } + wt_pkcs11_reader_init(&reader, response, response_len); + (void)wt_pkcs11_get_u32(&reader); + (void)wt_pkcs11_get_u32(&reader); + rv = wt_pkcs11_get_u32(&reader); + *handle = wt_pkcs11_get_u32(&reader); + return reader.error == 0 && reader.used == reader.length ? rv : + UINT32_MAX; +} + +static void test_open_session_arguments(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + uint32_t handle = 0U; + + check(session_open(CKF_SERIAL_SESSION | CKF_RW_SESSION, 1U, &handle) == + CKR_OK && handle == 2U && g_provider_test_calls == 1U && + g_provider_test_open_output != 0U && + g_provider_test_open_callbacks == 0U, + "open session returns the provider handle without callbacks"); + check(session_open(CKF_SERIAL_SESSION, 0U, &handle) == + CKR_ARGUMENTS_BAD && handle == CK_INVALID_HANDLE && + g_provider_test_calls == 1U && + g_provider_test_open_output == 0U, + "NULL session output reaches wolfPKCS11's own argument check"); + check(session_open(0U, 0U, &handle) == + CKR_SESSION_PARALLEL_NOT_SUPPORTED && + g_provider_test_calls == 1U, + "open session keeps wolfPKCS11's flag-before-output check order"); + + request_begin(&writer, request, WT_P11_OPEN_SESSION); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, CKF_SERIAL_SESSION); + wt_pkcs11_put_u32(&writer, 2U); + check(dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, 0, 1), + "malformed session output selector fails before C_OpenSession"); +} + +static void test_user_pin_lockout(void) +{ + unsigned int checks; + uint32_t handle = 0U; + uint32_t i; + int ok = 1; + + for (i = 0U; i < WT_PKCS11_PIN_MAX_FAILURES; i++) { + ok = ok && pin_login(1U, CKU_USER, "wrong") == CKR_PIN_INCORRECT; + } + checks = g_provider_test_pin_checks; + check(ok && pin_login(1U, CKU_USER, "user-pin") == CKR_PIN_LOCKED && + g_provider_test_pin_checks == checks, + "user PIN locks after the failure limit before reaching the token"); + check(token_pin_counts_are(WT_PKCS11_PIN_MAX_FAILURES, 0U), + "token info reports the user PIN lock"); + check(pin_login(1U, CKU_USER, "ab") == CKR_PIN_LEN_RANGE && + pin_login_null(1U, CKU_USER) == CKR_ARGUMENTS_BAD && + pin_login(1U, CKU_CONTEXT_SPECIFIC, "user-pin") == + CKR_OPERATION_NOT_INITIALIZED && + pin_login(1U, 0x99U, "user-pin") == CKR_USER_TYPE_INVALID && + g_provider_test_pin_checks == checks, + "a locked PIN keeps C_Login errors raised before any PIN test"); + check(pin_set(3U, "user-pin", "next-pin") == CKR_SESSION_READ_ONLY && + pin_set(1U, "ab", "next-pin") == CKR_PIN_INCORRECT && + pin_set(1U, "user-pin", "ab") == CKR_PIN_LEN_RANGE && + g_provider_test_pin_checks == checks, + "a locked PIN keeps C_SetPIN errors raised before any PIN test"); + check(session_open(CKF_SERIAL_SESSION | CKF_RW_SESSION, 1U, &handle) == + CKR_OK && + pin_login(handle, CKU_USER, "user-pin") == CKR_PIN_LOCKED && + g_provider_test_pin_checks == checks, + "a new session does not reset the user PIN lock"); + check(pin_set(handle, "user-pin", "next-pin") == CKR_PIN_LOCKED && + g_provider_test_pin_checks == checks, + "C_SetPIN cannot verify a locked user PIN"); + check(pin_login(handle, CKU_SO, "so-pin") == CKR_OK && + pin_login(handle, CKU_USER, "user-pin") == + CKR_USER_ANOTHER_ALREADY_LOGGED_IN && + g_provider_test_pin_checks == checks + 1U, + "a locked user PIN still reports another user logged in"); + check(pin_init(handle, "user-pin") == CKR_OK && + pin_logout(handle) == CKR_OK && token_pin_counts_are(0U, 0U) && + pin_login(handle, CKU_USER, "user-pin") == CKR_OK, + "SO re-initialising the user PIN clears the lock"); + ok = 1; + for (i = 0U; i < WT_PKCS11_PIN_MAX_FAILURES; i++) { + ok = ok && pin_set(handle, "wrong", "next-pin") == CKR_PIN_INCORRECT; + } + check(ok && pin_logout(handle) == CKR_OK && + pin_login(handle, CKU_USER, "user-pin") == CKR_PIN_LOCKED, + "failed C_SetPIN attempts count toward the user PIN lock"); + check(pin_login(handle, CKU_SO, "so-pin") == CKR_OK && + pin_init(handle, "user-pin") == CKR_OK && + pin_logout(handle) == CKR_OK, + "SO recovers the user PIN after a C_SetPIN lock"); +} + +static void test_success_resets_pin_count(void) +{ + uint32_t round; + uint32_t i; + int ok = 1; + + for (round = 0U; round < 2U; round++) { + for (i = 0U; i + 1U < WT_PKCS11_PIN_MAX_FAILURES; i++) { + ok = ok && pin_login(1U, CKU_USER, "wrong") == CKR_PIN_INCORRECT; + } + ok = ok && token_pin_counts_are(WT_PKCS11_PIN_MAX_FAILURES - 1U, 0U) && + pin_login(1U, CKU_USER, "user-pin") == CKR_OK && + token_pin_counts_are(0U, 0U) && pin_logout(1U) == CKR_OK; + } + check(ok, "a correct PIN before the limit resets the failure count"); +} + +static void test_backend_failures_keep_count(void) +{ + int ok; + + g_provider_test_after_pin_rv = CKR_HOST_MEMORY; + check(pin_login(1U, CKU_USER, "user-pin") == CKR_HOST_MEMORY && + token_pin_counts_are(0U, 0U), + "a backend failure after a correct PIN consumes no attempt"); + if (WT_PKCS11_PIN_MAX_FAILURES > 1U) { + g_provider_test_after_pin_rv = CKR_OK; + ok = pin_login(1U, CKU_USER, "wrong") == CKR_PIN_INCORRECT; + g_provider_test_after_pin_rv = CKR_FUNCTION_FAILED; + ok = ok && pin_login(1U, CKU_USER, "user-pin") == + CKR_FUNCTION_FAILED && + pin_set(1U, "user-pin", "next-pin") == CKR_FUNCTION_FAILED && + token_pin_counts_are(1U, 0U); + g_provider_test_after_pin_rv = CKR_OK; + ok = ok && pin_login(1U, CKU_USER, "user-pin") == CKR_OK && + pin_logout(1U) == CKR_OK && token_pin_counts_are(0U, 0U); + check(ok, "a backend failure neither consumes nor clears an attempt"); + } + g_provider_test_after_pin_rv = CKR_OK; +} + +static void test_so_pin_lockout(void) +{ + unsigned int checks; + uint32_t i; + int ok = 1; + + for (i = 0U; i < WT_PKCS11_PIN_MAX_FAILURES; i++) { + ok = ok && token_init("wrong") == CKR_PIN_INCORRECT; + } + checks = g_provider_test_pin_checks; + check(ok && token_init("so-pin") == CKR_PIN_LOCKED && + pin_login(1U, CKU_SO, "so-pin") == CKR_PIN_LOCKED && + g_provider_test_pin_checks == checks && + token_pin_counts_are(0U, WT_PKCS11_PIN_MAX_FAILURES), + "C_InitToken failures lock the SO PIN for every SO check"); + check(token_init("ab") == CKR_PIN_LEN_RANGE && + pin_login(3U, CKU_SO, "so-pin") == + CKR_SESSION_READ_ONLY_EXISTS && + pin_login_null(1U, CKU_SO) == CKR_ARGUMENTS_BAD && + g_provider_test_pin_checks == checks, + "a locked SO PIN keeps errors raised before any PIN test"); + g_provider_test_token_initialized = 0U; + check(token_init("so-pin") == CKR_OK && token_pin_counts_are(0U, 0U) && + pin_login(1U, CKU_SO, "so-pin") == CKR_OK && + pin_logout(1U) == CKR_OK, + "an erased token can be initialised again and clears both locks"); +} + +static int mechanism_list_is(uint32_t has_output, uint32_t capacity, + uint32_t rv, uint32_t total, uint32_t sent) +{ + uint8_t request[TEST_BUFFER_SIZE]; + uint8_t response[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + wt_pkcs11_reader_t reader; + size_t response_len = 0U; + uint32_t i; + int ok; + + request_begin(&writer, request, WT_P11_GET_MECHANISM_LIST); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, has_output); + wt_pkcs11_put_u32(&writer, capacity); + if (wt_pkcs11_provider_dispatch(request, writer.used, response, + sizeof(response), &response_len, + NULL) != 0) { + return 0; + } + wt_pkcs11_reader_init(&reader, response, response_len); + (void)wt_pkcs11_get_u32(&reader); + (void)wt_pkcs11_get_u32(&reader); + ok = wt_pkcs11_get_u32(&reader) == rv && + wt_pkcs11_get_u32(&reader) == total && + wt_pkcs11_get_u32(&reader) == sent; + for (i = 0U; i < sent; i++) { + uint32_t mechanism = wt_pkcs11_get_u32(&reader); + + ok = ok && (mechanism == CKM_SHA256 || mechanism == CKM_ECDSA); + } + return ok && reader.error == 0 && reader.used == reader.length; +} + +static uint32_t mechanism_info_rv(uint32_t mechanism) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_GET_MECHANISM_INFO); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, mechanism); + return dispatch_rv(request, writer.used); +} + +static void test_mechanism_filter(void) +{ + check(mechanism_list_is(0U, 0U, CKR_OK, 2U, 0U), + "mechanism count omits mechanisms the wire cannot carry"); + check(mechanism_list_is(1U, 1U, CKR_BUFFER_TOO_SMALL, 2U, 0U), + "short mechanism list reports the filtered count"); + check(mechanism_list_is(1U, 8U, CKR_OK, 2U, 2U), + "mechanism list returns only transportable mechanisms"); + check(mechanism_info_rv(CKM_SHA256) == CKR_OK && + mechanism_info_rv(CKM_PKCS5_PBKD2) == CKR_MECHANISM_INVALID && + mechanism_info_rv(CKM_TLS12_KEY_AND_MAC_DERIVE) == + CKR_MECHANISM_INVALID, + "mechanism info refuses mechanisms the wire cannot carry"); +} + +static uint32_t library_call(uint32_t function) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, function); + if (function == WT_P11_INITIALIZE) { + wt_pkcs11_put_u32(&writer, 0U); + } + return dispatch_rv(request, writer.used); +} + +static void test_finalize_pin_counts(void) +{ + uint32_t i; + int ok = 1; + + for (i = 0U; i < WT_PKCS11_PIN_MAX_FAILURES; i++) { + ok = ok && pin_login(1U, CKU_USER, "wrong") == CKR_PIN_INCORRECT; + } + check(ok && library_call(WT_P11_INITIALIZE) == CKR_OK && + library_call(WT_P11_FINALIZE) == CKR_OK && + pin_login(1U, CKU_USER, "user-pin") == CKR_PIN_LOCKED, + "a finalize that leaves the library loaded keeps the PIN lock"); + ok = library_call(WT_P11_FINALIZE) == CKR_OK && + library_call(WT_P11_INITIALIZE) == CKR_OK; +#ifdef WOLFPKCS11_NO_STORE + check(ok && token_pin_counts_are(0U, 0U) && + token_init("so-pin") == CKR_OK && + pin_login(1U, CKU_USER, "user-pin") == CKR_OK && + pin_logout(1U) == CKR_OK, + "finalizing the RAM-only token clears its PIN counts"); +#else + check(ok && token_pin_counts_are(WT_PKCS11_PIN_MAX_FAILURES, 0U), + "a stored token keeps its PIN counts across finalize"); + check(pin_login(1U, CKU_SO, "so-pin") == CKR_OK && + pin_init(1U, "user-pin") == CKR_OK && pin_logout(1U) == CKR_OK, + "SO recovers the stored token's user PIN"); +#endif +} + +static int null_case(uint8_t* request, size_t request_len, uint32_t rv, + unsigned int null_seen) +{ + g_provider_test_calls = 0U; + g_provider_test_null_seen = 2U; + return dispatch_rv(request, request_len) == rv && + g_provider_test_calls == 1U && + g_provider_test_null_seen == null_seen; +} + +static size_t seed_request(uint8_t* request, uint32_t present, + uint32_t length) +{ + static const uint8_t seed[1] = { 0x11U }; + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_SEED_RANDOM); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, length); + wt_pkcs11_put_u32(&writer, present); + wt_pkcs11_put_bytes(&writer, length != 0U ? seed : NULL, length); + return writer.used; +} + +static size_t output_request(uint8_t* request, uint32_t function, + uint32_t has_output) +{ + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, function); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, has_output); + wt_pkcs11_put_u32(&writer, 0U); + return writer.used; +} + +static size_t raw_parameter_request(uint8_t* request, uint32_t present) +{ + wt_pkcs11_writer_t writer; + + request_begin(&writer, request, WT_P11_ENCRYPT_INIT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, CKM_AES_CBC); + wt_pkcs11_put_u32(&writer, WT_P11_MECH_RAW); + wt_pkcs11_put_u32(&writer, present); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + wt_pkcs11_put_u32(&writer, 1U); + return writer.used; +} + +static void test_null_pointers_reach_provider(void) +{ + uint8_t request[TEST_BUFFER_SIZE]; + wt_pkcs11_writer_t writer; + size_t length; + + length = seed_request(request, 0U, 0U); + check(null_case(request, length, CKR_ARGUMENTS_BAD, 1U), + "a NULL empty buffer reaches wolfPKCS11 as NULL"); + length = seed_request(request, 1U, 0U); + check(null_case(request, length, CKR_OK, 0U), + "a non-NULL empty buffer reaches wolfPKCS11 as non-NULL"); + length = seed_request(request, 0U, 1U); + check(dispatch_invalid(request, length, TEST_BUFFER_SIZE, 0, 1), + "a NULL buffer with data fails before PKCS11 dispatch"); + + length = output_request(request, WT_P11_GENERATE_RANDOM, 0U); + check(null_case(request, length, CKR_ARGUMENTS_BAD, 1U), + "a NULL random output reaches wolfPKCS11 as NULL"); + length = output_request(request, WT_P11_GENERATE_RANDOM, 1U); + check(null_case(request, length, CKR_OK, 0U), + "an empty random output reaches wolfPKCS11 as non-NULL"); + length = output_request(request, WT_P11_FIND_OBJECTS, 0U); + check(null_case(request, length, CKR_ARGUMENTS_BAD, 1U), + "a NULL object list reaches wolfPKCS11 as NULL"); + length = output_request(request, WT_P11_FIND_OBJECTS, 1U); + check(null_case(request, length, CKR_OK, 0U), + "an empty object list reaches wolfPKCS11 as non-NULL"); + + request_begin(&writer, request, WT_P11_CREATE_OBJECT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 0U); + wt_pkcs11_put_u32(&writer, 0U); + check(null_case(request, writer.used, CKR_ARGUMENTS_BAD, 1U), + "a NULL template reaches wolfPKCS11 as NULL"); + request_begin(&writer, request, WT_P11_CREATE_OBJECT); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 0U); + wt_pkcs11_put_u32(&writer, 1U); + check(dispatch_invalid(request, writer.used, TEST_BUFFER_SIZE, 0, 1), + "a NULL template with attributes fails before PKCS11 dispatch"); + + length = raw_parameter_request(request, 0U); + check(null_case(request, length, CKR_FUNCTION_FAILED, 1U), + "a NULL mechanism parameter reaches wolfPKCS11 as NULL"); + length = raw_parameter_request(request, 1U); + check(null_case(request, length, CKR_FUNCTION_FAILED, 0U), + "an empty mechanism parameter reaches wolfPKCS11 as non-NULL"); +} + +int main(void) +{ + test_truncated_header(); + test_oversized_template(); + test_oversized_get_attribute_template(); + test_truncated_blob(); + test_truncated_mechanism(); + test_mechanism_kind_mismatch(); + test_ecdh_shared_data_presence(); + test_short_response(); + test_aligned_template_value(); + test_large_attribute_capacity(); + test_attribute_response_boundary(); + test_attribute_caller_buffer_too_small(); + test_attribute_type_invalid_with_valid_peer(); + test_sensitive_attribute_with_valid_peer(); + test_null_pointers_reach_provider(); + test_mechanism_filter(); + test_open_session_arguments(); + test_user_pin_lockout(); + test_success_resets_pin_count(); + test_backend_failures_keep_count(); + test_so_pin_lockout(); + test_finalize_pin_counts(); + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: PKCS11 provider frames, sessions, and PIN lockout\n"); + return 0; +} diff --git a/tests/host/pkcs11_provider/provider_stubs.c b/tests/host/pkcs11_provider/provider_stubs.c new file mode 100644 index 000000000..436d50a23 --- /dev/null +++ b/tests/host/pkcs11_provider/provider_stubs.c @@ -0,0 +1,509 @@ +/* provider_stubs.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "wolfpkcs11/pkcs11.h" + +#include +#include + +#define TEST_SLOT 1U +#define TEST_PIN_MAX 16U +#define TEST_PIN_MIN 4U +#define TEST_RO_SESSION 3U + +unsigned int g_provider_test_calls; +unsigned int g_provider_test_aligned; +unsigned int g_provider_test_get_attribute_calls; +unsigned int g_provider_test_ecdh_valid; +unsigned int g_provider_test_pin_checks; +unsigned int g_provider_test_token_initialized = 1U; +unsigned int g_provider_test_open_output; +unsigned int g_provider_test_open_callbacks; +unsigned int g_provider_test_null_seen; +unsigned int g_provider_test_library_count = 1U; +CK_RV g_provider_test_after_pin_rv = CKR_OK; + +/* Models wolfPKCS11 built with WOLFPKCS11_NO_TIME: no lockout of its own. */ +static char g_so_pin[TEST_PIN_MAX] = "so-pin"; +static char g_user_pin[TEST_PIN_MAX] = "user-pin"; +static CK_ULONG g_state = CKS_RW_PUBLIC_SESSION; + +static CK_RV test_initialize(CK_VOID_PTR args) +{ + (void)args; + g_provider_test_library_count++; + return CKR_OK; +} + +static CK_RV test_finalize(CK_VOID_PTR reserved) +{ + if (g_provider_test_library_count == 0U) { + return CKR_CRYPTOKI_NOT_INITIALIZED; + } + if (reserved != NULL) { + return CKR_ARGUMENTS_BAD; + } + g_provider_test_library_count--; + if (g_provider_test_library_count == 0U) { + g_provider_test_token_initialized = 0U; + } + return CKR_OK; +} + +static CK_RV test_get_info(CK_INFO_PTR info) +{ + if (g_provider_test_library_count == 0U) { + return CKR_CRYPTOKI_NOT_INITIALIZED; + } + if (info == NULL) { + return CKR_ARGUMENTS_BAD; + } + (void)memset(info, 0, sizeof(*info)); + return CKR_OK; +} + +static int test_session_valid(CK_SESSION_HANDLE session) +{ + return session == 1U || session == 2U || session == TEST_RO_SESSION; +} + +static int test_pin_len_valid(CK_UTF8CHAR_PTR pin, CK_ULONG pin_len) +{ + return pin != NULL && pin_len >= TEST_PIN_MIN && pin_len < TEST_PIN_MAX; +} + +static int test_logged_in(void) +{ + return g_state != CKS_RW_PUBLIC_SESSION; +} + +static int test_pin_matches(const char* expected, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len) +{ + g_provider_test_pin_checks++; + return pin != NULL && pin_len == (CK_ULONG)strlen(expected) && + memcmp(expected, pin, (size_t)pin_len) == 0; +} + +static CK_RV test_store_pin(char* target, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len) +{ + if (pin == NULL || pin_len == 0U || pin_len >= TEST_PIN_MAX) { + return CKR_PIN_LEN_RANGE; + } + (void)memcpy(target, pin, (size_t)pin_len); + target[pin_len] = '\0'; + return CKR_OK; +} + +static CK_RV test_get_mechanism_list(CK_SLOT_ID slot, + CK_MECHANISM_TYPE_PTR mechanisms, + CK_ULONG_PTR count) +{ + static const CK_MECHANISM_TYPE list[] = { + CKM_SHA256, CKM_PKCS5_PBKD2, CKM_HKDF_DERIVE, CKM_ECDSA, + CKM_TLS12_KEY_AND_MAC_DERIVE + }; + CK_ULONG total = (CK_ULONG)(sizeof(list) / sizeof(list[0])); + + if (slot != TEST_SLOT) { + return CKR_SLOT_ID_INVALID; + } + if (count == NULL) { + return CKR_ARGUMENTS_BAD; + } + if (mechanisms != NULL && *count < total) { + *count = total; + return CKR_BUFFER_TOO_SMALL; + } + if (mechanisms != NULL) { + (void)memcpy(mechanisms, list, sizeof(list)); + } + *count = total; + return CKR_OK; +} + +static CK_RV test_get_mechanism_info(CK_SLOT_ID slot, CK_MECHANISM_TYPE type, + CK_MECHANISM_INFO_PTR info) +{ + (void)type; + if (slot != TEST_SLOT) { + return CKR_SLOT_ID_INVALID; + } + if (info == NULL) { + return CKR_ARGUMENTS_BAD; + } + info->ulMinKeySize = 1U; + info->ulMaxKeySize = 2U; + info->flags = CKF_DIGEST; + return CKR_OK; +} + +static CK_RV test_get_token_info(CK_SLOT_ID slot, CK_TOKEN_INFO_PTR info) +{ + if (slot != TEST_SLOT) { + return CKR_SLOT_ID_INVALID; + } + if (info == NULL) { + return CKR_ARGUMENTS_BAD; + } + (void)memset(info, 0, sizeof(*info)); + info->flags = CKF_LOGIN_REQUIRED; + if (g_user_pin[0] != '\0') { + info->flags |= CKF_USER_PIN_INITIALIZED; + } + if (g_provider_test_token_initialized != 0U) { + info->flags |= CKF_TOKEN_INITIALIZED; + } + info->ulMinPinLen = TEST_PIN_MIN; + info->ulMaxPinLen = TEST_PIN_MAX - 1U; + return CKR_OK; +} + +static CK_RV test_init_token(CK_SLOT_ID slot, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len, CK_UTF8CHAR_PTR label) +{ + CK_RV rv; + + if (slot != TEST_SLOT) { + return CKR_SLOT_ID_INVALID; + } + if (pin == NULL || label == NULL) { + return CKR_ARGUMENTS_BAD; + } + if (!test_pin_len_valid(pin, pin_len)) { + return CKR_PIN_LEN_RANGE; + } + if (g_provider_test_token_initialized != 0U && + !test_pin_matches(g_so_pin, pin, pin_len)) { + return CKR_PIN_INCORRECT; + } + rv = test_store_pin(g_so_pin, pin, pin_len); + if (rv == CKR_OK) { + g_provider_test_token_initialized = 1U; + g_state = CKS_RW_PUBLIC_SESSION; + } + return rv; +} + +static CK_RV test_init_pin(CK_SESSION_HANDLE session, CK_UTF8CHAR_PTR pin, + CK_ULONG pin_len) +{ + g_provider_test_calls++; + if (!test_session_valid(session)) { + return CKR_SESSION_HANDLE_INVALID; + } + if (g_state != CKS_RW_SO_FUNCTIONS) { + return CKR_USER_NOT_LOGGED_IN; + } + return test_store_pin(g_user_pin, pin, pin_len); +} + +static CK_RV test_set_pin(CK_SESSION_HANDLE session, CK_UTF8CHAR_PTR old_pin, + CK_ULONG old_len, CK_UTF8CHAR_PTR new_pin, + CK_ULONG new_len) +{ + char* target = g_state == CKS_RW_SO_FUNCTIONS ? g_so_pin : g_user_pin; + + if (!test_session_valid(session)) { + return CKR_SESSION_HANDLE_INVALID; + } + if (old_pin == NULL || new_pin == NULL) { + return CKR_ARGUMENTS_BAD; + } + if (!test_pin_len_valid(old_pin, old_len)) { + return CKR_PIN_INCORRECT; + } + if (!test_pin_len_valid(new_pin, new_len)) { + return CKR_PIN_LEN_RANGE; + } + if (session == TEST_RO_SESSION) { + return CKR_SESSION_READ_ONLY; + } + if (target[0] == '\0') { + return CKR_USER_PIN_NOT_INITIALIZED; + } + if (!test_pin_matches(target, old_pin, old_len)) { + return CKR_PIN_INCORRECT; + } + if (g_provider_test_after_pin_rv != CKR_OK) { + return g_provider_test_after_pin_rv; + } + return test_store_pin(target, new_pin, new_len); +} + +static CK_RV test_open_session(CK_SLOT_ID slot, CK_FLAGS flags, + CK_VOID_PTR application, CK_NOTIFY notify, + CK_SESSION_HANDLE_PTR session) +{ + g_provider_test_calls++; + g_provider_test_open_output = session != NULL; + g_provider_test_open_callbacks = application != NULL || notify != NULL; + if (slot != TEST_SLOT) { + return CKR_SLOT_ID_INVALID; + } + if ((flags & CKF_SERIAL_SESSION) == 0U) { + return CKR_SESSION_PARALLEL_NOT_SUPPORTED; + } + if (session == NULL) { + return CKR_ARGUMENTS_BAD; + } + *session = 2U; + return CKR_OK; +} + +static CK_RV test_close_session(CK_SESSION_HANDLE session) +{ + return test_session_valid(session) ? CKR_OK : CKR_SESSION_HANDLE_INVALID; +} + +static CK_RV test_get_session_info(CK_SESSION_HANDLE session, + CK_SESSION_INFO_PTR info) +{ + if (!test_session_valid(session)) { + return CKR_SESSION_HANDLE_INVALID; + } + if (info == NULL) { + return CKR_ARGUMENTS_BAD; + } + (void)memset(info, 0, sizeof(*info)); + info->slotID = TEST_SLOT; + info->state = g_state; + info->flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + if (session == TEST_RO_SESSION) { + info->state = g_state == CKS_RW_USER_FUNCTIONS ? + CKS_RO_USER_FUNCTIONS : CKS_RO_PUBLIC_SESSION; + info->flags = CKF_SERIAL_SESSION; + } + return CKR_OK; +} + +static CK_RV test_login(CK_SESSION_HANDLE session, CK_USER_TYPE user, + CK_UTF8CHAR_PTR pin, CK_ULONG pin_len) +{ + if (!test_session_valid(session)) { + return CKR_SESSION_HANDLE_INVALID; + } + if (pin == NULL) { + return CKR_ARGUMENTS_BAD; + } + if (!test_pin_len_valid(pin, pin_len)) { + return CKR_PIN_LEN_RANGE; + } + if (user == CKU_CONTEXT_SPECIFIC) { + return CKR_OPERATION_NOT_INITIALIZED; + } + if (user != CKU_SO && user != CKU_USER) { + return CKR_USER_TYPE_INVALID; + } + if (test_logged_in()) { + return (g_state == CKS_RW_SO_FUNCTIONS) == (user == CKU_SO) ? + CKR_USER_ALREADY_LOGGED_IN : + CKR_USER_ANOTHER_ALREADY_LOGGED_IN; + } + if (user == CKU_SO && session == TEST_RO_SESSION) { + return CKR_SESSION_READ_ONLY_EXISTS; + } + if ((user == CKU_SO ? g_so_pin : g_user_pin)[0] == '\0') { + return CKR_USER_PIN_NOT_INITIALIZED; + } + if (!test_pin_matches(user == CKU_SO ? g_so_pin : g_user_pin, pin, + pin_len)) { + return CKR_PIN_INCORRECT; + } + if (g_provider_test_after_pin_rv != CKR_OK) { + return g_provider_test_after_pin_rv; + } + g_state = user == CKU_SO ? CKS_RW_SO_FUNCTIONS : CKS_RW_USER_FUNCTIONS; + return CKR_OK; +} + +static CK_RV test_logout(CK_SESSION_HANDLE session) +{ + if (!test_session_valid(session)) { + return CKR_SESSION_HANDLE_INVALID; + } + g_state = CKS_RW_PUBLIC_SESSION; + return CKR_OK; +} + +static CK_RV test_create_object(CK_SESSION_HANDLE session, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + CK_OBJECT_HANDLE_PTR object) +{ + (void)session; + g_provider_test_calls++; + g_provider_test_null_seen = attributes == NULL; + g_provider_test_aligned = count == 1U && attributes != NULL && + attributes[0].type == CKA_CLASS && attributes[0].pValue != NULL && + attributes[0].ulValueLen == sizeof(CK_OBJECT_CLASS) && + ((uintptr_t)attributes[0].pValue % sizeof(CK_ULONG)) == 0U; + if (g_provider_test_aligned != 0U && object != NULL) { + *object = 7U; + return CKR_OK; + } + return CKR_ARGUMENTS_BAD; +} + +static CK_RV test_encrypt_init(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE key) +{ + (void)session; + (void)key; + g_provider_test_calls++; + g_provider_test_null_seen = mechanism == NULL || + mechanism->pParameter == NULL; + return CKR_FUNCTION_FAILED; +} + +static CK_RV test_seed_random(CK_SESSION_HANDLE session, CK_BYTE_PTR seed, + CK_ULONG seed_len) +{ + (void)session; + (void)seed_len; + g_provider_test_calls++; + g_provider_test_null_seen = seed == NULL; + return seed == NULL ? CKR_ARGUMENTS_BAD : CKR_OK; +} + +static CK_RV test_generate_random(CK_SESSION_HANDLE session, + CK_BYTE_PTR output, CK_ULONG length) +{ + (void)session; + g_provider_test_calls++; + g_provider_test_null_seen = output == NULL; + if (output == NULL) { + return CKR_ARGUMENTS_BAD; + } + (void)memset(output, 0x5a, (size_t)length); + return CKR_OK; +} + +static CK_RV test_find_objects(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE_PTR objects, + CK_ULONG maximum, CK_ULONG_PTR count) +{ + (void)session; + (void)maximum; + g_provider_test_calls++; + g_provider_test_null_seen = objects == NULL; + if (objects == NULL || count == NULL) { + return CKR_ARGUMENTS_BAD; + } + *count = 0U; + return CKR_OK; +} + +static CK_RV test_derive_key(CK_SESSION_HANDLE session, + CK_MECHANISM_PTR mechanism, + CK_OBJECT_HANDLE base_key, + CK_ATTRIBUTE_PTR attributes, CK_ULONG count, + CK_OBJECT_HANDLE_PTR key) +{ + static const uint8_t point[] = { 0x04U, 0x11U, 0x22U }; + CK_ECDH1_DERIVE_PARAMS* params = NULL; + + g_provider_test_calls++; + if (mechanism != NULL && mechanism->mechanism == CKM_ECDH1_DERIVE && + mechanism->pParameter != NULL && + mechanism->ulParameterLen == sizeof(*params)) { + params = (CK_ECDH1_DERIVE_PARAMS*)mechanism->pParameter; + } + g_provider_test_null_seen = params != NULL && + params->pSharedData == NULL; + g_provider_test_ecdh_valid = session == 1U && base_key == 12U && + attributes != NULL && count == 0U && key != NULL && params != NULL && + params->kdf == CKD_NULL && params->ulSharedDataLen == 0U && + params->ulPublicDataLen == sizeof(point) && + params->pPublicData != NULL && + memcmp(params->pPublicData, point, sizeof(point)) == 0; + if (g_provider_test_ecdh_valid == 0U) { + return CKR_MECHANISM_PARAM_INVALID; + } + *key = 7U; + return CKR_OK; +} + +static CK_RV test_get_attribute_value(CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + CK_ATTRIBUTE_PTR attributes, + CK_ULONG count) +{ + static const uint8_t label[] = { 'k', 'e', 'y' }; + CK_ULONG i; + CK_RV rv = CKR_OK; + + (void)session; + (void)object; + g_provider_test_get_attribute_calls++; + for (i = 0U; i < count; i++) { + if (attributes[i].type == CKA_PRIVATE_EXPONENT) { + attributes[i].ulValueLen = CK_UNAVAILABLE_INFORMATION; + if (rv == CKR_OK) { + rv = CKR_ATTRIBUTE_SENSITIVE; + } + } + else if (attributes[i].type != CKA_LABEL) { + attributes[i].ulValueLen = CK_UNAVAILABLE_INFORMATION; + if (rv == CKR_OK) { + rv = CKR_ATTRIBUTE_TYPE_INVALID; + } + } + else if (attributes[i].pValue == NULL) { + attributes[i].ulValueLen = sizeof(label); + } + else if (attributes[i].ulValueLen < sizeof(label)) { + attributes[i].ulValueLen = sizeof(label); + if (rv == CKR_OK) { + rv = CKR_BUFFER_TOO_SMALL; + } + } + else { + (void)memcpy(attributes[i].pValue, label, sizeof(label)); + attributes[i].ulValueLen = sizeof(label); + } + } + return rv; +} + +CK_FUNCTION_LIST g_provider_test_functions = { + .C_Initialize = test_initialize, + .C_Finalize = test_finalize, + .C_GetInfo = test_get_info, + .C_GetTokenInfo = test_get_token_info, + .C_GetMechanismList = test_get_mechanism_list, + .C_GetMechanismInfo = test_get_mechanism_info, + .C_InitToken = test_init_token, + .C_InitPIN = test_init_pin, + .C_SetPIN = test_set_pin, + .C_OpenSession = test_open_session, + .C_CloseSession = test_close_session, + .C_GetSessionInfo = test_get_session_info, + .C_Login = test_login, + .C_Logout = test_logout, + .C_CreateObject = test_create_object, + .C_GetAttributeValue = test_get_attribute_value, + .C_FindObjects = test_find_objects, + .C_EncryptInit = test_encrypt_init, + .C_SeedRandom = test_seed_random, + .C_GenerateRandom = test_generate_random, + .C_DeriveKey = test_derive_key +}; diff --git a/tests/host/pkcs11_provider/provider_stubs.h b/tests/host/pkcs11_provider/provider_stubs.h new file mode 100644 index 000000000..19988e9f5 --- /dev/null +++ b/tests/host/pkcs11_provider/provider_stubs.h @@ -0,0 +1,82 @@ +/* provider_stubs.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_HOST_PKCS11_PROVIDER_STUBS_H +#define WOLFTRUST_HOST_PKCS11_PROVIDER_STUBS_H + +#include "wolfpkcs11/pkcs11.h" + +extern CK_FUNCTION_LIST g_provider_test_functions; +extern unsigned int g_provider_test_aligned; + +#define C_Initialize g_provider_test_functions.C_Initialize +#define C_Finalize g_provider_test_functions.C_Finalize +#define C_GetInfo g_provider_test_functions.C_GetInfo +#define C_GetSlotList g_provider_test_functions.C_GetSlotList +#define C_GetSlotInfo g_provider_test_functions.C_GetSlotInfo +#define C_GetTokenInfo g_provider_test_functions.C_GetTokenInfo +#define C_GetMechanismList g_provider_test_functions.C_GetMechanismList +#define C_GetMechanismInfo g_provider_test_functions.C_GetMechanismInfo +#define C_InitToken g_provider_test_functions.C_InitToken +#define C_InitPIN g_provider_test_functions.C_InitPIN +#define C_SetPIN g_provider_test_functions.C_SetPIN +#define C_OpenSession g_provider_test_functions.C_OpenSession +#define C_CloseSession g_provider_test_functions.C_CloseSession +#define C_CloseAllSessions g_provider_test_functions.C_CloseAllSessions +#define C_GetSessionInfo g_provider_test_functions.C_GetSessionInfo +#define C_Login g_provider_test_functions.C_Login +#define C_Logout g_provider_test_functions.C_Logout +#define C_CreateObject g_provider_test_functions.C_CreateObject +#define C_CopyObject g_provider_test_functions.C_CopyObject +#define C_DestroyObject g_provider_test_functions.C_DestroyObject +#define C_GetObjectSize g_provider_test_functions.C_GetObjectSize +#define C_GetAttributeValue g_provider_test_functions.C_GetAttributeValue +#define C_SetAttributeValue g_provider_test_functions.C_SetAttributeValue +#define C_FindObjectsInit g_provider_test_functions.C_FindObjectsInit +#define C_FindObjects g_provider_test_functions.C_FindObjects +#define C_FindObjectsFinal g_provider_test_functions.C_FindObjectsFinal +#define C_EncryptInit g_provider_test_functions.C_EncryptInit +#define C_Encrypt g_provider_test_functions.C_Encrypt +#define C_EncryptUpdate g_provider_test_functions.C_EncryptUpdate +#define C_EncryptFinal g_provider_test_functions.C_EncryptFinal +#define C_DecryptInit g_provider_test_functions.C_DecryptInit +#define C_Decrypt g_provider_test_functions.C_Decrypt +#define C_DecryptUpdate g_provider_test_functions.C_DecryptUpdate +#define C_DecryptFinal g_provider_test_functions.C_DecryptFinal +#define C_DigestInit g_provider_test_functions.C_DigestInit +#define C_Digest g_provider_test_functions.C_Digest +#define C_DigestUpdate g_provider_test_functions.C_DigestUpdate +#define C_DigestKey g_provider_test_functions.C_DigestKey +#define C_DigestFinal g_provider_test_functions.C_DigestFinal +#define C_SignInit g_provider_test_functions.C_SignInit +#define C_Sign g_provider_test_functions.C_Sign +#define C_SignUpdate g_provider_test_functions.C_SignUpdate +#define C_SignFinal g_provider_test_functions.C_SignFinal +#define C_VerifyInit g_provider_test_functions.C_VerifyInit +#define C_Verify g_provider_test_functions.C_Verify +#define C_VerifyUpdate g_provider_test_functions.C_VerifyUpdate +#define C_VerifyFinal g_provider_test_functions.C_VerifyFinal +#define C_GenerateKey g_provider_test_functions.C_GenerateKey +#define C_GenerateKeyPair g_provider_test_functions.C_GenerateKeyPair +#define C_DeriveKey g_provider_test_functions.C_DeriveKey +#define C_SeedRandom g_provider_test_functions.C_SeedRandom +#define C_GenerateRandom g_provider_test_functions.C_GenerateRandom + +#endif /* WOLFTRUST_HOST_PKCS11_PROVIDER_STUBS_H */ diff --git a/tests/host/pkcs11_provider/user_settings.h b/tests/host/pkcs11_provider/user_settings.h new file mode 100644 index 000000000..94dafe10b --- /dev/null +++ b/tests/host/pkcs11_provider/user_settings.h @@ -0,0 +1,26 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_HOST_PKCS11_PROVIDER_USER_SETTINGS_H +#define WOLFTRUST_HOST_PKCS11_PROVIDER_USER_SETTINGS_H + +/* The provider parser test needs only the public PKCS11 ABI types. */ + +#endif /* WOLFTRUST_HOST_PKCS11_PROVIDER_USER_SETTINGS_H */ diff --git a/tests/host/pkcs11_proxy/Makefile b/tests/host/pkcs11_proxy/Makefile new file mode 100644 index 000000000..309dd0d25 --- /dev/null +++ b/tests/host/pkcs11_proxy/Makefile @@ -0,0 +1,63 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I. \ + -I$(ROOT)/include \ + -I$(ROOT)/lib/wolfPKCS11 \ + -DWOLFPKCS11_USER_SETTINGS \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +OBJS := \ + $(BUILD_DIR)/pkcs11_proxy.o \ + $(BUILD_DIR)/pkcs11_wire.o \ + $(BUILD_DIR)/main.o +TEST_BIN := $(BUILD_DIR)/test_pkcs11_proxy + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/pkcs11_proxy.o: $(ROOT)/src/client/pkcs11_proxy.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/pkcs11_wire.o: $(ROOT)/src/pkcs11/pkcs11_wire.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/main.o: main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/pkcs11_proxy/main.c b/tests/host/pkcs11_proxy/main.c new file mode 100644 index 000000000..72e9c1475 --- /dev/null +++ b/tests/host/pkcs11_proxy/main.c @@ -0,0 +1,591 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* PKCS11 proxy wire suite: a scripted provider validates templates and every + * supported mechanism encoding, and returns hostile list counts. */ + +#include "wolfpkcs11/pkcs11.h" + +#include "wolftrust/pkcs11/pkcs11_abi.h" +#include "wolftrust/pkcs11/pkcs11_wire.h" +#include "wolftrust/pkcs11_psa_transport.h" + +#include +#include +#include + +enum reply_kind { + REPLY_OK, + REPLY_OBJECT, + REPLY_SLOT_VALID, + REPLY_SLOT_HUGE, + REPLY_SLOT_TRUNCATED, + REPLY_FIND_HUGE, + REPLY_SIZE_UNAVAILABLE, + REPLY_ATTRIBUTE_UNAVAILABLE, + REPLY_ATTRIBUTE_BUFFER_TOO_SMALL, + REPLY_ATTRIBUTE_TYPE_INVALID, + REPLY_ATTRIBUTE_SENSITIVE, + REPLY_ATTRIBUTE_OBJECT_INVALID, + REPLY_SESSION_PARALLEL, + REPLY_ARGUMENTS_BAD +}; + +static int g_failures; +static enum reply_kind g_reply; +static uint8_t g_request[WT_PKCS11_MESSAGE_MAX]; +static size_t g_request_len; +static const uint8_t g_terminal_attribute_value[sizeof(CK_OBJECT_CLASS)] = { + 0xa5U +}; + +static void check(int ok, const char* what) +{ + if (ok != 0) { + (void)printf("PASS: %s\n", what); + } + else { + (void)printf("FAIL: %s\n", what); + g_failures++; + } +} + +int wt_pkcs11_psa_call(const uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, + size_t* response_len) +{ + wt_pkcs11_writer_t writer; + CK_RV rv = CKR_OK; + + if (request == NULL || request_len > sizeof(g_request) || + response == NULL || response_len == NULL) { + return -1; + } + (void)memcpy(g_request, request, request_len); + g_request_len = request_len; + wt_pkcs11_writer_init(&writer, response, response_cap); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_MAGIC); + wt_pkcs11_put_u32(&writer, WT_PKCS11_WIRE_VERSION); + if (g_reply == REPLY_ATTRIBUTE_BUFFER_TOO_SMALL) { + rv = CKR_BUFFER_TOO_SMALL; + } + else if (g_reply == REPLY_ATTRIBUTE_TYPE_INVALID) { + rv = CKR_ATTRIBUTE_TYPE_INVALID; + } + else if (g_reply == REPLY_ATTRIBUTE_SENSITIVE) { + rv = CKR_ATTRIBUTE_SENSITIVE; + } + else if (g_reply == REPLY_ATTRIBUTE_OBJECT_INVALID) { + rv = CKR_OBJECT_HANDLE_INVALID; + } + else if (g_reply == REPLY_SESSION_PARALLEL) { + rv = CKR_SESSION_PARALLEL_NOT_SUPPORTED; + } + else if (g_reply == REPLY_ARGUMENTS_BAD) { + rv = CKR_ARGUMENTS_BAD; + } + wt_pkcs11_put_u32(&writer, rv); + if (g_reply == REPLY_OBJECT) { + wt_pkcs11_put_u32(&writer, 77U); + } + else if (g_reply == REPLY_SESSION_PARALLEL || + g_reply == REPLY_ARGUMENTS_BAD) { + wt_pkcs11_put_u32(&writer, 0U); + } + else if (g_reply == REPLY_SLOT_VALID) { + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 7U); + } + else if (g_reply == REPLY_SLOT_HUGE) { + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, UINT32_MAX); + } + else if (g_reply == REPLY_SLOT_TRUNCATED) { + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, 1U); + } + else if (g_reply == REPLY_FIND_HUGE) { + wt_pkcs11_put_u32(&writer, WT_PKCS11_LIST_MAX + 1U); + } + else if (g_reply == REPLY_SIZE_UNAVAILABLE) { + wt_pkcs11_put_u32(&writer, UINT32_MAX); + } + else if (g_reply == REPLY_ATTRIBUTE_UNAVAILABLE) { + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, UINT32_MAX); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + } + else if (g_reply == REPLY_ATTRIBUTE_BUFFER_TOO_SMALL) { + wt_pkcs11_put_u32(&writer, 2U); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_bytes(&writer, (const uint8_t*)"key", 3U); + } + else if (g_reply == REPLY_ATTRIBUTE_TYPE_INVALID || + g_reply == REPLY_ATTRIBUTE_SENSITIVE) { + wt_pkcs11_put_u32(&writer, 2U); + wt_pkcs11_put_u32(&writer, 3U); + wt_pkcs11_put_bytes(&writer, (const uint8_t*)"key", 3U); + wt_pkcs11_put_u32(&writer, UINT32_MAX); + wt_pkcs11_put_bytes(&writer, NULL, 0U); + } + else if (g_reply == REPLY_ATTRIBUTE_OBJECT_INVALID) { + wt_pkcs11_put_u32(&writer, 1U); + wt_pkcs11_put_u32(&writer, sizeof(CK_OBJECT_CLASS)); + wt_pkcs11_put_bytes(&writer, g_terminal_attribute_value, + sizeof(g_terminal_attribute_value)); + } + if (writer.error != 0) { + return -1; + } + *response_len = writer.used; + return 0; +} + +void wt_pkcs11_psa_close(void) +{ +} + +static int request_begin(wt_pkcs11_reader_t* reader, uint32_t function) +{ + wt_pkcs11_reader_init(reader, g_request, g_request_len); + return wt_pkcs11_get_u32(reader) == WT_PKCS11_WIRE_MAGIC && + wt_pkcs11_get_u32(reader) == WT_PKCS11_WIRE_VERSION && + wt_pkcs11_get_u32(reader) == function && reader->error == 0; +} + +static int get_blob(wt_pkcs11_reader_t* reader, const uint8_t* expected, + size_t expected_len) +{ + size_t length = 0U; + uint8_t* data = wt_pkcs11_get_bytes(reader, &length); + + return reader->error == 0 && length == expected_len && + (length == 0U || memcmp(data, expected, length) == 0); +} + +static int request_done(const wt_pkcs11_reader_t* reader) +{ + return reader->error == 0 && reader->used == reader->length; +} + +static void test_lists(CK_FUNCTION_LIST_PTR fn) +{ + CK_SLOT_ID slots[2] = { 0U, 0U }; + CK_MECHANISM_TYPE mechanisms[2] = { 0U, 0U }; + CK_OBJECT_HANDLE objects[2] = { 0U, 0U }; + CK_ULONG count; + + g_reply = REPLY_SLOT_VALID; + count = 2U; + check(fn->C_GetSlotList(CK_TRUE, slots, &count) == CKR_OK && + count == 1U && slots[0] == 7U, + "valid provider slot list decodes"); + + g_reply = REPLY_SLOT_HUGE; + count = 2U; + check(fn->C_GetSlotList(CK_TRUE, slots, &count) == CKR_DEVICE_ERROR, + "provider list above the protocol maximum fails closed"); + + g_reply = REPLY_SLOT_TRUNCATED; + count = 2U; + check(fn->C_GetSlotList(CK_TRUE, slots, &count) == CKR_DEVICE_ERROR, + "provider list count exceeding its frame fails closed"); + + g_reply = REPLY_SLOT_HUGE; + count = 2U; + check(fn->C_GetMechanismList(1U, mechanisms, &count) == CKR_DEVICE_ERROR, + "provider mechanism count above the protocol maximum fails closed"); + + g_reply = REPLY_FIND_HUGE; + count = 0U; + check(fn->C_FindObjects(9U, objects, 2U, &count) == CKR_DEVICE_ERROR, + "provider object count above the protocol maximum fails closed"); +} + +static CK_RV test_notify(CK_SESSION_HANDLE session, CK_NOTIFICATION event, + CK_VOID_PTR application) +{ + (void)session; + (void)event; + (void)application; + return CKR_OK; +} + +static void test_open_session(CK_FUNCTION_LIST_PTR fn) +{ + static int application_data; + CK_SESSION_HANDLE session = 0U; + wt_pkcs11_reader_t reader; + + g_reply = REPLY_OBJECT; + check(fn->C_OpenSession(3U, CKF_SERIAL_SESSION | CKF_RW_SESSION, + &application_data, test_notify, &session) == + CKR_OK && session == 77U && + request_begin(&reader, WT_P11_OPEN_SESSION) && + wt_pkcs11_get_u32(&reader) == 3U && + wt_pkcs11_get_u32(&reader) == + (CKF_SERIAL_SESSION | CKF_RW_SESSION) && + wt_pkcs11_get_u32(&reader) == 1U && request_done(&reader), + "open session accepts and does not forward callback arguments"); + + g_reply = REPLY_SESSION_PARALLEL; + check(fn->C_OpenSession(3U, 0U, NULL, NULL, NULL) == + CKR_SESSION_PARALLEL_NOT_SUPPORTED && + request_begin(&reader, WT_P11_OPEN_SESSION) && + wt_pkcs11_get_u32(&reader) == 3U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && request_done(&reader), + "NULL session output is left to wolfPKCS11's check order"); +} + +static void test_template(CK_FUNCTION_LIST_PTR fn) +{ + static const uint8_t label[] = { 'k', 'e', 'y' }; + CK_ATTRIBUTE attribute; + CK_OBJECT_HANDLE object = 0U; + wt_pkcs11_reader_t reader; + + attribute.type = CKA_LABEL; + attribute.pValue = (void*)label; + attribute.ulValueLen = sizeof(label); + g_reply = REPLY_OBJECT; + check(fn->C_CreateObject(9U, &attribute, 1U, &object) == CKR_OK && + object == 77U && + request_begin(&reader, WT_P11_CREATE_OBJECT) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == 1U && + wt_pkcs11_get_u32(&reader) == 1U && + wt_pkcs11_get_u32(&reader) == CKA_LABEL && + wt_pkcs11_get_u32(&reader) == sizeof(label) && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, label, sizeof(label)) && + request_done(&reader), + "object template serializes byte-exactly"); +} + +static void test_unavailable_length(CK_FUNCTION_LIST_PTR fn) +{ + CK_ULONG size = 0U; + CK_ATTRIBUTE attribute; + CK_OBJECT_CLASS object_class = CKO_DATA; + + g_reply = REPLY_SIZE_UNAVAILABLE; + check(fn->C_GetObjectSize(9U, 77U, &size) == CKR_OK && + size == CK_UNAVAILABLE_INFORMATION, + "object size preserves the unavailable sentinel"); + + attribute.type = CKA_PRIVATE_EXPONENT; + attribute.pValue = NULL; + attribute.ulValueLen = 0U; + g_reply = REPLY_ATTRIBUTE_UNAVAILABLE; + check(fn->C_GetAttributeValue(9U, 77U, &attribute, 1U) == CKR_OK && + attribute.ulValueLen == CK_UNAVAILABLE_INFORMATION, + "attribute length preserves the unavailable sentinel"); + + attribute.type = CKA_CLASS; + attribute.pValue = &object_class; + attribute.ulValueLen = sizeof(object_class); + g_reply = REPLY_ATTRIBUTE_OBJECT_INVALID; + check(fn->C_GetAttributeValue(9U, 77U, &attribute, 1U) == + CKR_OBJECT_HANDLE_INVALID && object_class == CKO_DATA && + attribute.ulValueLen == sizeof(object_class), + "terminal attribute errors discard output and preserve status"); +} + +static void test_partial_attribute_statuses(CK_FUNCTION_LIST_PTR fn) +{ + CK_ATTRIBUTE attributes[2]; + uint8_t short_value[2] = { 0xa5U, 0xa5U }; + uint8_t valid_value[3] = { 0U, 0U, 0U }; + uint8_t unavailable_value[3] = { 0xa5U, 0xa5U, 0xa5U }; + + attributes[0].type = CKA_APPLICATION; + attributes[0].pValue = short_value; + attributes[0].ulValueLen = sizeof(short_value); + attributes[1].type = CKA_LABEL; + attributes[1].pValue = valid_value; + attributes[1].ulValueLen = sizeof(valid_value); + g_reply = REPLY_ATTRIBUTE_BUFFER_TOO_SMALL; + check(fn->C_GetAttributeValue(9U, 77U, attributes, 2U) == + CKR_BUFFER_TOO_SMALL && + attributes[0].ulValueLen == 3U && + short_value[0] == 0xa5U && short_value[1] == 0xa5U && + attributes[1].ulValueLen == 3U && + memcmp(valid_value, "key", 3U) == 0, + "buffer-too-small status preserves valid peer output"); + + attributes[0].type = CKA_LABEL; + attributes[0].pValue = valid_value; + attributes[0].ulValueLen = sizeof(valid_value); + attributes[1].type = CKA_APPLICATION; + attributes[1].pValue = unavailable_value; + attributes[1].ulValueLen = sizeof(unavailable_value); + (void)memset(valid_value, 0, sizeof(valid_value)); + g_reply = REPLY_ATTRIBUTE_TYPE_INVALID; + check(fn->C_GetAttributeValue(9U, 77U, attributes, 2U) == + CKR_ATTRIBUTE_TYPE_INVALID && + attributes[0].ulValueLen == 3U && + memcmp(valid_value, "key", 3U) == 0 && + attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION && + unavailable_value[0] == 0xa5U, + "invalid-attribute status preserves valid peer output"); + + attributes[1].type = CKA_PRIVATE_EXPONENT; + attributes[1].ulValueLen = sizeof(unavailable_value); + (void)memset(valid_value, 0, sizeof(valid_value)); + g_reply = REPLY_ATTRIBUTE_SENSITIVE; + check(fn->C_GetAttributeValue(9U, 77U, attributes, 2U) == + CKR_ATTRIBUTE_SENSITIVE && + attributes[0].ulValueLen == 3U && + memcmp(valid_value, "key", 3U) == 0 && + attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION && + unavailable_value[0] == 0xa5U, + "sensitive-attribute status preserves valid peer output"); +} + +static int mechanism_prefix(wt_pkcs11_reader_t* reader, uint32_t function, + CK_ULONG mechanism, uint32_t kind) +{ + return request_begin(reader, function) && + wt_pkcs11_get_u32(reader) == 9U && + wt_pkcs11_get_u32(reader) == 1U && + wt_pkcs11_get_u32(reader) == mechanism && + wt_pkcs11_get_u32(reader) == kind && reader->error == 0; +} + +static void test_mechanisms(CK_FUNCTION_LIST_PTR fn) +{ + uint8_t counter[16]; + uint8_t iv[12]; + uint8_t aad[7]; + uint8_t shared[3]; + uint8_t public_data[5]; + CK_AES_CTR_PARAMS ctr; + CK_GCM_PARAMS gcm; + CK_CCM_PARAMS ccm; + CK_ECDH1_DERIVE_PARAMS ecdh; + CK_MECHANISM mechanism; + CK_OBJECT_HANDLE key = 0U; + wt_pkcs11_reader_t reader; + + (void)memset(counter, 0x11, sizeof(counter)); + (void)memset(iv, 0x22, sizeof(iv)); + (void)memset(aad, 0x33, sizeof(aad)); + (void)memset(shared, 0x44, sizeof(shared)); + (void)memset(public_data, 0x55, sizeof(public_data)); + mechanism.mechanism = CKM_SHA256; + mechanism.pParameter = NULL; + mechanism.ulParameterLen = 0U; + g_reply = REPLY_OK; + check(fn->C_DigestInit(9U, &mechanism) == CKR_OK && + mechanism_prefix(&reader, WT_P11_DIGEST_INIT, CKM_SHA256, 0U) && + wt_pkcs11_get_u32(&reader) == 0U && + get_blob(&reader, NULL, 0U) && request_done(&reader), + "raw mechanism serializes byte-exactly"); + + (void)memset(&ctr, 0, sizeof(ctr)); + ctr.ulCounterBits = 128U; + (void)memcpy(ctr.cb, counter, sizeof(counter)); + mechanism.mechanism = CKM_AES_CTR; + mechanism.pParameter = &ctr; + mechanism.ulParameterLen = sizeof(ctr); + check(fn->C_EncryptInit(9U, &mechanism, 12U) == CKR_OK && + mechanism_prefix(&reader, WT_P11_ENCRYPT_INIT, CKM_AES_CTR, + 1U) && + wt_pkcs11_get_u32(&reader) == 128U && + get_blob(&reader, counter, sizeof(counter)) && + wt_pkcs11_get_u32(&reader) == 12U && request_done(&reader), + "AES-CTR mechanism serializes byte-exactly"); + + (void)memset(&gcm, 0, sizeof(gcm)); + gcm.pIv = iv; + gcm.ulIvLen = sizeof(iv); + gcm.ulIvBits = 96U; + gcm.pAAD = aad; + gcm.ulAADLen = sizeof(aad); + gcm.ulTagBits = 128U; + mechanism.mechanism = CKM_AES_GCM; + mechanism.pParameter = &gcm; + mechanism.ulParameterLen = sizeof(gcm); + check(fn->C_EncryptInit(9U, &mechanism, 12U) == CKR_OK && + mechanism_prefix(&reader, WT_P11_ENCRYPT_INIT, CKM_AES_GCM, + 2U) && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, iv, sizeof(iv)) && + wt_pkcs11_get_u32(&reader) == 96U && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, aad, sizeof(aad)) && + wt_pkcs11_get_u32(&reader) == 128U && + wt_pkcs11_get_u32(&reader) == 12U && request_done(&reader), + "AES-GCM mechanism serializes byte-exactly"); + + (void)memset(&ccm, 0, sizeof(ccm)); + ccm.ulDataLen = 41U; + ccm.pIv = iv; + ccm.ulIvLen = sizeof(iv); + ccm.pAAD = aad; + ccm.ulAADLen = sizeof(aad); + ccm.ulMacLen = 16U; + mechanism.mechanism = CKM_AES_CCM; + mechanism.pParameter = &ccm; + mechanism.ulParameterLen = sizeof(ccm); + check(fn->C_EncryptInit(9U, &mechanism, 12U) == CKR_OK && + mechanism_prefix(&reader, WT_P11_ENCRYPT_INIT, CKM_AES_CCM, + 3U) && + wt_pkcs11_get_u32(&reader) == 41U && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, iv, sizeof(iv)) && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, aad, sizeof(aad)) && + wt_pkcs11_get_u32(&reader) == 16U && + wt_pkcs11_get_u32(&reader) == 12U && request_done(&reader), + "AES-CCM mechanism serializes byte-exactly"); + + (void)memset(&ecdh, 0, sizeof(ecdh)); + ecdh.kdf = CKD_NULL; + ecdh.pSharedData = shared; + ecdh.ulSharedDataLen = sizeof(shared); + ecdh.pPublicData = public_data; + ecdh.ulPublicDataLen = sizeof(public_data); + mechanism.mechanism = CKM_ECDH1_DERIVE; + mechanism.pParameter = &ecdh; + mechanism.ulParameterLen = sizeof(ecdh); + g_reply = REPLY_OBJECT; + check(fn->C_DeriveKey(9U, &mechanism, 12U, NULL, 0U, &key) == CKR_OK && + key == 77U && + mechanism_prefix(&reader, WT_P11_DERIVE_KEY, + CKM_ECDH1_DERIVE, 4U) && + wt_pkcs11_get_u32(&reader) == CKD_NULL && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, shared, sizeof(shared)) && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, public_data, sizeof(public_data)) && + wt_pkcs11_get_u32(&reader) == 12U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && request_done(&reader), + "ECDH mechanism and NULL template serialize byte-exactly"); + + ecdh.pSharedData = NULL; + ecdh.ulSharedDataLen = 0U; + check(fn->C_DeriveKey(9U, &mechanism, 12U, NULL, 0U, &key) == CKR_OK && + mechanism_prefix(&reader, WT_P11_DERIVE_KEY, + CKM_ECDH1_DERIVE, 4U) && + wt_pkcs11_get_u32(&reader) == CKD_NULL && + wt_pkcs11_get_u32(&reader) == 0U && + get_blob(&reader, NULL, 0U), + "NULL empty ECDH shared data is forwarded as NULL"); + ecdh.pSharedData = shared; + check(fn->C_DeriveKey(9U, &mechanism, 12U, NULL, 0U, &key) == CKR_OK && + mechanism_prefix(&reader, WT_P11_DERIVE_KEY, + CKM_ECDH1_DERIVE, 4U) && + wt_pkcs11_get_u32(&reader) == CKD_NULL && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, NULL, 0U), + "non-NULL empty ECDH shared data is forwarded as non-NULL"); +} + +static void test_null_pointers(CK_FUNCTION_LIST_PTR fn) +{ + static CK_BYTE empty[1]; + CK_OBJECT_HANDLE object = 0U; + CK_ULONG count = 1U; + CK_MECHANISM mechanism; + wt_pkcs11_reader_t reader; + + g_reply = REPLY_ARGUMENTS_BAD; + check(fn->C_GenerateRandom(9U, NULL, 0U) == CKR_ARGUMENTS_BAD && + request_begin(&reader, WT_P11_GENERATE_RANDOM) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && request_done(&reader), + "a NULL random output is forwarded as NULL"); + check(fn->C_FindObjects(9U, NULL, 0U, &count) == CKR_ARGUMENTS_BAD && + request_begin(&reader, WT_P11_FIND_OBJECTS) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && request_done(&reader), + "a NULL object list is forwarded as NULL"); + + g_reply = REPLY_OK; + check(fn->C_SeedRandom(9U, NULL, 0U) == CKR_OK && + request_begin(&reader, WT_P11_SEED_RANDOM) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && + get_blob(&reader, NULL, 0U) && request_done(&reader), + "a NULL empty buffer is forwarded as NULL"); + check(fn->C_SeedRandom(9U, empty, 0U) == CKR_OK && + request_begin(&reader, WT_P11_SEED_RANDOM) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, NULL, 0U) && request_done(&reader), + "a non-NULL empty buffer is forwarded as non-NULL"); + check(fn->C_Login(9U, CKU_USER, NULL, 0U) == CKR_OK && + request_begin(&reader, WT_P11_LOGIN) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == CKU_USER && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && + get_blob(&reader, NULL, 0U) && request_done(&reader), + "a NULL PIN is forwarded as NULL"); + + mechanism.mechanism = CKM_SHA256; + mechanism.pParameter = empty; + mechanism.ulParameterLen = 0U; + check(fn->C_DigestInit(9U, &mechanism) == CKR_OK && + mechanism_prefix(&reader, WT_P11_DIGEST_INIT, CKM_SHA256, 0U) && + wt_pkcs11_get_u32(&reader) == 1U && + get_blob(&reader, NULL, 0U) && request_done(&reader), + "an empty non-NULL mechanism parameter is forwarded as non-NULL"); + + g_reply = REPLY_OBJECT; + check(fn->C_CreateObject(9U, NULL, 0U, &object) == CKR_OK && + request_begin(&reader, WT_P11_CREATE_OBJECT) && + wt_pkcs11_get_u32(&reader) == 9U && + wt_pkcs11_get_u32(&reader) == 0U && + wt_pkcs11_get_u32(&reader) == 0U && request_done(&reader), + "a NULL template is forwarded as NULL"); +} + +int main(void) +{ + CK_FUNCTION_LIST_PTR functions = NULL; + + check(C_GetFunctionList(&functions) == CKR_OK && functions != NULL, + "proxy exports its PKCS11 function list"); + if (functions != NULL) { + test_lists(functions); + test_open_session(functions); + test_template(functions); + test_unavailable_length(functions); + test_partial_attribute_statuses(functions); + test_mechanisms(functions); + test_null_pointers(functions); + } + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: PKCS11 proxy wire bounds, templates, and mechanisms\n"); + return 0; +} diff --git a/tests/host/pkcs11_proxy/user_settings.h b/tests/host/pkcs11_proxy/user_settings.h new file mode 100644 index 000000000..42bc4611b --- /dev/null +++ b/tests/host/pkcs11_proxy/user_settings.h @@ -0,0 +1,26 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_HOST_PKCS11_PROXY_USER_SETTINGS_H +#define WOLFTRUST_HOST_PKCS11_PROXY_USER_SETTINGS_H + +/* The proxy test needs only the public PKCS11 ABI types. */ + +#endif /* WOLFTRUST_HOST_PKCS11_PROXY_USER_SETTINGS_H */ diff --git a/tests/host/pkcs11_relay/Makefile b/tests/host/pkcs11_relay/Makefile new file mode 100644 index 000000000..545182a51 --- /dev/null +++ b/tests/host/pkcs11_relay/Makefile @@ -0,0 +1,92 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I. \ + -I$(ROOT)/include \ + -DWT_PKCS11_TEST_HOOKS \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +_WT_SRCS := \ + $(ROOT)/src/ffm.c \ + $(ROOT)/src/ffm_domain.c \ + $(ROOT)/src/ipc.c \ + $(ROOT)/src/spm_gate.c + +_WTS_SRCS := \ + $(ROOT)/src/services/pkcs11/pkcs11_relay_service.c + +_WTC_SRCS := \ + $(ROOT)/src/client/pkcs11_psa_transport.c + +_TEST_SRCS := main.c + +_WT_OBJS := $(patsubst %.c,wt_%.o,$(notdir $(_WT_SRCS))) +_WTS_OBJS := $(patsubst %.c,wts_%.o,$(notdir $(_WTS_SRCS))) +_WTC_OBJS := $(patsubst %.c,wtc_%.o,$(notdir $(_WTC_SRCS))) +_TEST_OBJS := $(patsubst %.c,%.o,$(notdir $(_TEST_SRCS))) + +ALL_OBJS := $(addprefix $(BUILD_DIR)/,$(_WT_OBJS) $(_WTS_OBJS) $(_WTC_OBJS) $(_TEST_OBJS)) +TEST_BIN := $(BUILD_DIR)/test_pkcs11_relay +RELAY_SRC := $(ROOT)/src/services/pkcs11/pkcs11_relay_service.c + +.PHONY: all run check-zeroize compilers sanitize valgrind clean + +all: check-zeroize $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wts_%.o: $(ROOT)/src/services/pkcs11/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wtc_%.o: $(ROOT)/src/client/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(ALL_OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(ALL_OBJS) + +check-zeroize: + @test "$$(grep -Fc 'wt_forceZero(&chunk, sizeof(chunk));' $(RELAY_SRC))" -eq 2 + @! grep -Fq 'memset(&chunk' $(RELAY_SRC) + @echo "PASS: PKCS11 relay chunk uses optimization-resistant zeroization" + +run: check-zeroize $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/pkcs11_relay/main.c b/tests/host/pkcs11_relay/main.c new file mode 100644 index 000000000..ddd2de8e4 --- /dev/null +++ b/tests/host/pkcs11_relay/main.c @@ -0,0 +1,565 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* WT-FFM-0054: the actual client and Secure service exchange copied frames + * through the FF-M runtime. The handler needs no worker or operating system. */ + +#include +#include +#include + +#include "wolftrust/ffm.h" +#include "wolftrust/pkcs11_psa_transport.h" +#include "wolftrust/services/pkcs11_relay.h" +#include "psa/client.h" + +#define TEST_PKCS11_PARTITION 9 +#define TEST_APP_CLIENT (-1) +#define TEST_PROVIDER_CLIENT (-2) +#define TEST_OTHER_CLIENT (-3) +#define TEST_SP_CLIENT 3 + +static int g_failures; +static wt_ffm_runtime_t g_runtime; +static int32_t g_transport_fail_op; +static int32_t g_transport_repeat_fail_op; +static unsigned int g_transport_repeat_failures; +static unsigned int g_provider_dispatches; +static uint8_t g_received_request[WT_PKCS11_MESSAGE_MAX]; +static size_t g_received_length; +static const uint8_t* g_private_request; +static uint8_t* g_private_response; +static int g_relay_partial_read; +static unsigned int g_chunk_wipes; +static psa_status_t g_chunk_wipe_status; +static int g_chunk_wipe_dirty; + +typedef struct test_dispatch_context { + const uint8_t* response; + size_t response_len; + int fail; +} test_dispatch_context_t; + +psa_handle_t psa_connect(uint32_t sid, uint32_t version) +{ + if (sid != WT_PKCS11_SERVICE_SID || + version != WT_PKCS11_SERVICE_VERSION) { + return PSA_ERROR_CONNECTION_REFUSED; + } + return wt_ffm_connect(&g_runtime, TEST_APP_CLIENT, sid, version); +} + +void psa_close(psa_handle_t handle) +{ + (void)wt_ffm_close(&g_runtime, TEST_APP_CLIENT, handle); +} + +psa_status_t psa_call(psa_handle_t handle, int32_t type, + const psa_invec* in_vec, size_t in_len, + psa_outvec* out_vec, size_t out_len) +{ + if (type == g_transport_fail_op) { + g_transport_fail_op = 0; + return PSA_ERROR_GENERIC_ERROR; + } + if (type == g_transport_repeat_fail_op && + g_transport_repeat_failures > 0U) { + g_transport_repeat_failures--; + return PSA_ERROR_COMMUNICATION_FAILURE; + } + return wt_ffm_call(&g_runtime, TEST_APP_CLIENT, handle, type, + in_vec, in_len, out_vec, out_len); +} + +static void check(int ok, const char* what) +{ + if (ok != 0) { + printf("PASS: %s\n", what); + } + else { + printf("FAIL: %s\n", what); + g_failures++; + } +} + +void wt_pkcs11_test_relay_chunk_zeroized(const void* memory, size_t size, + psa_status_t status) +{ + const uint8_t* bytes = (const uint8_t*)memory; + size_t i; + + g_chunk_wipes++; + g_chunk_wipe_status = status; + for (i = 0U; i < size; i++) { + if (bytes[i] != 0U) { + g_chunk_wipe_dirty = 1; + break; + } + } +} + +static void chunk_wipe_reset(void) +{ + g_chunk_wipes = 0U; + g_chunk_wipe_status = PSA_SUCCESS; + g_chunk_wipe_dirty = 0; +} + +static int chunk_wipe_matches(psa_status_t status) +{ + return g_chunk_wipes == 1U && g_chunk_wipe_status == status && + g_chunk_wipe_dirty == 0; +} + +static int test_relay_transport(wt_ffm_runtime_t* runtime, + wt_spm_call_t* call) +{ + int rc = wt_spm_transport_direct(runtime, call); + + if (rc == WT_FFM_SUCCESS && g_relay_partial_read != 0 && + call->op == WT_SPM_OP_READ && call->ret_size > 0U) { + call->ret_size--; + g_relay_partial_read = 0; + } + return rc; +} + +static int test_check_read(void* context, psa_client_id_t caller, + const void* address, size_t size) +{ + (void)context; + (void)caller; + return size == 0U || address != NULL; +} + +static int test_check_write(void* context, psa_client_id_t caller, + void* address, size_t size) +{ + (void)context; + (void)caller; + return size == 0U || address != NULL; +} + +static void test_panic(void* context, int32_t partition_id) +{ + (void)context; + (void)partition_id; +} + +static int test_dispatch(void* context, wt_ffm_runtime_t* runtime, + int32_t partition_id) +{ + (void)context; + return wt_pkcs11_relay_dispatch(NULL, runtime, partition_id); +} + +static const wt_ffm_port_ops_t g_port_ops = { + test_check_read, + test_check_write, + test_dispatch, + test_panic +}; + +static const wt_service_descriptor_t g_services[] = { + { + "SERVICE_PKCS11", WT_PKCS11_SERVICE_SID, 1U, + WT_SERVICE_VERSION_RELAXED, 0x40U, 0U, 1U, 1U + } +}; + +static const wt_partition_manifest_t g_partitions[] = { + { + "PARTITION_PKCS11", TEST_PKCS11_PARTITION, WT_FFM_VERSION_1_0, + WT_PARTITION_MODEL_IPC, WT_PARTITION_PRIORITY_NORMAL, + g_services, sizeof(g_services) / sizeof(g_services[0]), + NULL, 0U, NULL, 0U + } +}; + +static const wt_system_manifest_t g_manifest = { + .format_version = WT_MANIFEST_FORMAT_VERSION, + .generator_version = "pkcs11-relay-test", + .features = WT_MANIFEST_FEATURE_IPC, + .partitions = g_partitions, + .partition_count = sizeof(g_partitions) / sizeof(g_partitions[0]) +}; + +static psa_status_t p11_op(int32_t client_id, int32_t op, const void* input, + size_t input_len, void* output, size_t output_cap, + size_t* output_len) +{ + psa_invec iv[1]; + psa_outvec ov[1]; + psa_handle_t handle; + psa_status_t status; + + handle = wt_ffm_connect(&g_runtime, client_id, WT_PKCS11_SERVICE_SID, + WT_PKCS11_SERVICE_VERSION); + if (!PSA_HANDLE_IS_VALID(handle)) { + return PSA_ERROR_CONNECTION_REFUSED; + } + iv[0].base = input; + iv[0].len = input_len; + ov[0].base = output; + ov[0].len = output_cap; + status = wt_ffm_call(&g_runtime, client_id, handle, op, + input != NULL ? iv : NULL, + input != NULL ? 1U : 0U, + output != NULL ? ov : NULL, + output != NULL ? 1U : 0U); + if (output_len != NULL && output != NULL) { + *output_len = ov[0].len; + } + (void)wt_ffm_close(&g_runtime, client_id, handle); + return status; +} + +static psa_status_t begin_message(int32_t client_id, int32_t op, + size_t length) +{ + wt_pkcs11_length_t frame; + + frame.length = (uint32_t)length; + return p11_op(client_id, op, &frame, sizeof(frame), NULL, 0U, NULL); +} + +static psa_status_t write_chunks(int32_t client_id, int32_t op, + const uint8_t* data, size_t length) +{ + wt_pkcs11_chunk_t chunk; + size_t offset = 0U; + size_t take; + psa_status_t status; + + while (offset < length) { + take = length - offset; + if (take > WT_PKCS11_CHUNK_MAX) { + take = WT_PKCS11_CHUNK_MAX; + } + chunk.offset = (uint32_t)offset; + chunk.length = (uint32_t)take; + (void)memcpy(chunk.data, data + offset, take); + status = p11_op(client_id, op, &chunk, + sizeof(chunk.offset) + sizeof(chunk.length) + take, + NULL, 0U, NULL); + if (status != PSA_SUCCESS) { + return status; + } + offset += take; + } + return PSA_SUCCESS; +} + +static psa_status_t report_length(int32_t client_id, int32_t op, + size_t* length) +{ + wt_pkcs11_length_t frame; + size_t output_len = 0U; + psa_status_t status; + + status = p11_op(client_id, op, NULL, 0U, &frame, sizeof(frame), + &output_len); + if (status == PSA_SUCCESS) { + if (output_len != sizeof(frame)) { + return PSA_ERROR_GENERIC_ERROR; + } + *length = frame.length; + } + return status; +} + +static psa_status_t read_chunks(int32_t client_id, int32_t op, uint8_t* data, + size_t length) +{ + wt_pkcs11_range_t range; + size_t offset = 0U; + size_t take; + size_t output_len; + psa_status_t status; + + while (offset < length) { + take = length - offset; + if (take > WT_PKCS11_CHUNK_MAX) { + take = WT_PKCS11_CHUNK_MAX; + } + range.offset = (uint32_t)offset; + range.length = (uint32_t)take; + status = p11_op(client_id, op, &range, sizeof(range), data + offset, + take, &output_len); + if (status != PSA_SUCCESS || output_len != take) { + return status != PSA_SUCCESS ? status : PSA_ERROR_GENERIC_ERROR; + } + offset += take; + } + return PSA_SUCCESS; +} + +static int provider_dispatch(uint8_t* request, size_t request_len, + uint8_t* response, size_t response_cap, + size_t* response_len, void* context) +{ + test_dispatch_context_t* test = (test_dispatch_context_t*)context; + + g_provider_dispatches++; + g_private_request = request; + g_private_response = response; + g_received_length = request_len; + (void)memcpy(g_received_request, request, request_len); + if (test->fail != 0 || request == NULL || request_len == 0U || + response == NULL || response_len == NULL || + test->response_len > response_cap) { + return -1; + } + (void)memcpy(response, test->response, test->response_len); + *response_len = test->response_len; + return 0; +} + +static int provider_oversize_dispatch(uint8_t* request, size_t request_len, + uint8_t* response, + size_t response_cap, + size_t* response_len, void* context) +{ + (void)context; + if (request == NULL || request_len == 0U || response == NULL || + response_cap == 0U || response_len == NULL) { + return -1; + } + response[0] = 0xA5U; + *response_len = response_cap + 1U; + return 0; +} + +static int buffer_is_zero(const uint8_t* buffer, size_t length) +{ + size_t i; + + for (i = 0U; i < length; i++) { + if (buffer[i] != 0U) { + return 0; + } + } + return 1; +} + +static void test_direct_execution(void) +{ + uint8_t request[WT_PKCS11_MESSAGE_MAX]; + uint8_t response[WT_PKCS11_MESSAGE_MAX]; + uint8_t collected[WT_PKCS11_MESSAGE_MAX]; + test_dispatch_context_t context; + size_t length = 0U; + size_t i; + unsigned int calls; + + check(wt_ffm_init(&g_runtime, &g_manifest, &g_port_ops, NULL) == + WT_FFM_SUCCESS, "WT-FFM-0054 direct PKCS11 runtime initializes"); + for (i = 0U; i < sizeof(request); i++) { + request[i] = (uint8_t)i; + response[i] = (uint8_t)(i ^ 0x5AU); + } + wt_pkcs11_psa_close(); + context.response = response; + context.response_len = sizeof(response); + context.fail = 0; + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_BEGIN, NULL, 0U, + NULL, 0U, NULL) == PSA_ERROR_NOT_SUPPORTED, + "unbound PKCS11 service fails closed"); + wt_pkcs11_relay_set_client(TEST_APP_CLIENT); + wt_pkcs11_relay_set_local_provider(provider_dispatch, &context); + calls = g_provider_dispatches; + check(begin_message(TEST_APP_CLIENT, WT_PKCS11_OP_APP_BEGIN, + sizeof(request)) == PSA_SUCCESS && + write_chunks(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, request, + sizeof(request)) == PSA_SUCCESS, + "direct PKCS11 stages the full bounded message"); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_EXECUTE, NULL, 0U, + collected, 1U, NULL) == PSA_ERROR_INVALID_ARGUMENT && + g_provider_dispatches == calls, + "bad reply metadata capacity cannot execute a PKCS11 operation"); + check(report_length(TEST_OTHER_CLIENT, WT_PKCS11_OP_APP_EXECUTE, + &length) == PSA_ERROR_NOT_PERMITTED && + g_provider_dispatches == calls, + "WT-FFM-0059 wrong PKCS11 caller cannot execute the staged operation"); + check(report_length(TEST_APP_CLIENT, WT_PKCS11_OP_APP_EXECUTE, + &length) == PSA_SUCCESS && length == sizeof(response) && + g_provider_dispatches == calls + 1U, + "one execute call returns the completed response length without polling"); + check(report_length(TEST_APP_CLIENT, WT_PKCS11_OP_APP_EXECUTE, + &length) == PSA_ERROR_BAD_STATE && + g_provider_dispatches == calls + 1U, + "duplicate execute never replays a completed PKCS11 operation"); + check(read_chunks(TEST_APP_CLIENT, WT_PKCS11_OP_APP_READ, collected, + sizeof(collected)) == PSA_SUCCESS && + memcmp(collected, response, sizeof(response)) == 0, + "direct PKCS11 returns the exact maximum-sized response"); + check(g_received_length == sizeof(request) && + memcmp(g_received_request, request, sizeof(request)) == 0 && + g_private_request != request && g_private_response != collected && + buffer_is_zero(g_private_request, WT_PKCS11_MESSAGE_MAX) && + buffer_is_zero(g_private_response, WT_PKCS11_MESSAGE_MAX), + "both directions copy exact bytes and scrub all private staging"); + check(report_length(TEST_PROVIDER_CLIENT, 7 /* retired worker FETCH */, + &length) == PSA_ERROR_NOT_PERMITTED, + "there is no external provider role in the direct service"); + wt_pkcs11_relay_set_local_provider(NULL, NULL); +} + +static void test_bounds_and_recovery(void) +{ + uint8_t request[31]; + uint8_t response[41]; + uint8_t collected[41]; + test_dispatch_context_t context; + wt_pkcs11_chunk_t chunk; + wt_pkcs11_range_t range; + size_t length = 0U; + unsigned int calls; + + (void)memset(request, 0xA5, sizeof(request)); + (void)memset(response, 0x5A, sizeof(response)); + context.response = response; + context.response_len = sizeof(response); + context.fail = 0; + wt_pkcs11_relay_set_client(TEST_APP_CLIENT); + wt_pkcs11_relay_set_local_provider(provider_dispatch, &context); + wt_pkcs11_relay_set_transport(test_relay_transport); + check(begin_message(TEST_APP_CLIENT, WT_PKCS11_OP_APP_BEGIN, 0U) == + PSA_ERROR_INVALID_ARGUMENT && + begin_message(TEST_APP_CLIENT, WT_PKCS11_OP_APP_BEGIN, + WT_PKCS11_MESSAGE_MAX + 1U) == PSA_ERROR_INVALID_ARGUMENT, + "empty and oversized PKCS11 messages are refused"); + check(begin_message(TEST_APP_CLIENT, WT_PKCS11_OP_APP_BEGIN, 16U) == + PSA_SUCCESS, + "begin partial request"); + check(report_length(TEST_APP_CLIENT, WT_PKCS11_OP_APP_EXECUTE, + &length) == PSA_ERROR_BAD_STATE, + "partial requests never execute"); + chunk.offset = 0U; + chunk.length = 7U; + (void)memset(chunk.data, 0xA5, sizeof(chunk.data)); + chunk_wipe_reset(); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, &chunk, 16U, + NULL, 0U, NULL) == PSA_ERROR_INVALID_ARGUMENT && + chunk_wipe_matches(PSA_ERROR_INVALID_ARGUMENT), + "malformed chunk is refused and scrubbed"); + chunk.length = 8U; + chunk.offset = UINT32_MAX; + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, &chunk, 16U, + NULL, 0U, NULL) == PSA_ERROR_INVALID_ARGUMENT, + "chunk offset overflow is refused"); + chunk.offset = 0U; + g_relay_partial_read = 1; + chunk_wipe_reset(); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, &chunk, 16U, + NULL, 0U, NULL) == PSA_ERROR_INVALID_ARGUMENT && + chunk_wipe_matches(PSA_ERROR_INVALID_ARGUMENT), + "short copied read is refused and scrubbed"); + chunk_wipe_reset(); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, &chunk, 16U, + NULL, 0U, NULL) == PSA_SUCCESS && + chunk_wipe_matches(PSA_SUCCESS), + "valid copied chunk is scrubbed after staging"); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, &chunk, 16U, + NULL, 0U, NULL) == PSA_ERROR_INVALID_ARGUMENT, + "duplicate chunk offsets are refused"); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_ABORT, NULL, 0U, + NULL, 0U, NULL) == PSA_SUCCESS, + "abort discards a partial request"); + chunk_wipe_reset(); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, &chunk, 16U, + NULL, 0U, NULL) == PSA_ERROR_BAD_STATE && + chunk_wipe_matches(PSA_ERROR_BAD_STATE), + "bad-state chunk is refused and scrubbed"); + calls = g_provider_dispatches; + check(wt_pkcs11_psa_call(request, sizeof(request), collected, + sizeof(collected), &length) == 0 && length == sizeof(response) && + memcmp(collected, response, sizeof(response)) == 0 && + g_provider_dispatches == calls + 1U, + "the production client completes one synchronous service exchange"); + context.fail = 1; + calls = g_provider_dispatches; + check(wt_pkcs11_psa_call(request, sizeof(request), collected, + sizeof(collected), &length) == PSA_ERROR_GENERIC_ERROR && + g_provider_dispatches == calls + 1U && + buffer_is_zero(g_private_request, WT_PKCS11_MESSAGE_MAX) && + buffer_is_zero(g_private_response, WT_PKCS11_MESSAGE_MAX), + "handler failure scrubs both frames without replay"); + context.fail = 0; + wt_pkcs11_relay_set_local_provider(provider_oversize_dispatch, NULL); + check(wt_pkcs11_psa_call(request, sizeof(request), collected, + sizeof(collected), &length) == PSA_ERROR_GENERIC_ERROR && + buffer_is_zero(g_private_request, WT_PKCS11_MESSAGE_MAX) && + buffer_is_zero(g_private_response, WT_PKCS11_MESSAGE_MAX), + "oversized handler response is refused and scrubbed"); + wt_pkcs11_relay_set_local_provider(provider_dispatch, &context); + check(wt_pkcs11_psa_call(request, sizeof(request), collected, + sizeof(collected), &length) == 0, + "a new request succeeds after handler failures"); + calls = g_provider_dispatches; + g_transport_repeat_fail_op = WT_PKCS11_OP_APP_EXECUTE; + g_transport_repeat_failures = 1U; + check(wt_pkcs11_psa_call(request, sizeof(request), collected, + sizeof(collected), &length) == PSA_ERROR_COMMUNICATION_FAILURE && + g_provider_dispatches == calls, + "lost execute transport never triggers an implicit replay"); + check(wt_pkcs11_psa_call(request, sizeof(request), collected, + sizeof(collected), &length) == 0 && + g_provider_dispatches == calls + 1U, + "next explicit request reconnects and succeeds"); + check(begin_message(TEST_APP_CLIENT, WT_PKCS11_OP_APP_BEGIN, + sizeof(request)) == PSA_SUCCESS && + write_chunks(TEST_APP_CLIENT, WT_PKCS11_OP_APP_WRITE, request, + sizeof(request)) == PSA_SUCCESS && + report_length(TEST_APP_CLIENT, WT_PKCS11_OP_APP_EXECUTE, + &length) == PSA_SUCCESS, + "prepare a response for bounded reads"); + range.offset = UINT32_MAX; + range.length = 1U; + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_READ, &range, + sizeof(range), collected, 1U, NULL) == PSA_ERROR_INVALID_ARGUMENT, + "response range overflow is refused"); + range.offset = 0U; + range.length = sizeof(response); + check(p11_op(TEST_APP_CLIENT, WT_PKCS11_OP_APP_READ, &range, + sizeof(range), collected, 1U, NULL) == PSA_ERROR_BUFFER_TOO_SMALL, + "short response capacity cannot consume the reply"); + check(read_chunks(TEST_APP_CLIENT, WT_PKCS11_OP_APP_READ, collected, + sizeof(collected)) == PSA_SUCCESS && + buffer_is_zero(g_private_response, WT_PKCS11_MESSAGE_MAX), + "valid read consumes and scrubs the reply after a rejected read"); + check(p11_op(TEST_SP_CLIENT, WT_PKCS11_OP_APP_ABORT, NULL, 0U, + NULL, 0U, NULL) != PSA_SUCCESS, + "WT-FFM-0059 unrelated Secure caller cannot access PKCS11"); + wt_pkcs11_psa_close(); + wt_pkcs11_relay_set_local_provider(NULL, NULL); + wt_pkcs11_relay_set_transport(NULL); +} + +int main(void) +{ + test_direct_execution(); + test_bounds_and_recovery(); + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: WT-FFM-0054 synchronous PKCS11 service\n"); + return 0; +} diff --git a/tests/host/pkcs11_secure_heap/Makefile b/tests/host/pkcs11_secure_heap/Makefile new file mode 100644 index 000000000..991dbb73d --- /dev/null +++ b/tests/host/pkcs11_secure_heap/Makefile @@ -0,0 +1,56 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +OBJS := \ + $(BUILD_DIR)/pkcs11_secure_heap.o \ + $(BUILD_DIR)/main.o +TEST_BIN := $(BUILD_DIR)/test_pkcs11_secure_heap + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/pkcs11_secure_heap.o: \ + $(ROOT)/src/pkcs11/pkcs11_secure_heap.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/main.o: main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/pkcs11_secure_heap/main.c b/tests/host/pkcs11_secure_heap/main.c new file mode 100644 index 000000000..65260f126 --- /dev/null +++ b/tests/host/pkcs11_secure_heap/main.c @@ -0,0 +1,105 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +void* XMALLOC(size_t size, void* heap, int type); +void XFREE(void* pointer, void* heap, int type); +void* XREALLOC(void* pointer, size_t size, void* heap, int type); + +static int check(int condition, const char* label) +{ + if (!condition) { + (void)fprintf(stderr, "FAIL: %s\n", label); + return 1; + } + (void)printf("PASS: %s\n", label); + return 0; +} + +int main(void) +{ + uint8_t* first; + uint8_t* replacement; + uint8_t* reused; + size_t fake_header[2]; + uint8_t outside = 0xA5U; + size_t i; + int intact = 1; + int scrubbed = 1; + int failed = 0; + + first = (uint8_t*)XMALLOC(64U, NULL, 0); + failed |= check(first != NULL, "secure heap allocates an aligned block"); + if (first == NULL) { + return 1; + } + failed |= check(((uintptr_t)first & 15U) == 0U, + "secure heap returns 16-byte alignment"); + + fake_header[0] = 16U; + fake_header[1] = 1U; + (void)memcpy(first, fake_header, sizeof(fake_header)); + (void)memset(first + 16U, 0x5AU, 16U); + XFREE(first + 16U, NULL, 0); + for (i = 16U; i < 32U; ++i) { + if (first[i] != 0x5AU) { + intact = 0; + } + } + failed |= check(intact, + "interior free cannot forge an allocation header"); + failed |= check(XREALLOC(first + 16U, 128U, NULL, 0) == NULL, + "interior realloc is refused"); + + XFREE(&outside, NULL, 0); + failed |= check(outside == 0xA5U, + "foreign free leaves memory unchanged"); + + replacement = (uint8_t*)XREALLOC(first, 128U, NULL, 0); + failed |= check(replacement != NULL, + "valid realloc grows an allocation"); + if (replacement == NULL) { + return 1; + } + failed |= check(memcmp(replacement, fake_header, sizeof(fake_header)) == 0, + "valid realloc preserves allocation contents"); + + (void)memset(replacement, 0xA5, 128U); + XFREE(replacement, NULL, 0); + for (i = 0U; i < 128U; ++i) { + if (replacement[i] != 0U) { + scrubbed = 0; + } + } + failed |= check(scrubbed, "free scrubs the complete allocation"); + + reused = (uint8_t*)XMALLOC(128U, NULL, 0); + failed |= check(reused == first, + "free blocks coalesce and reuse the first span"); + XFREE(reused, NULL, 0); + + if (failed == 0) { + (void)printf("PASS: secure PKCS11 heap pointer validation\n"); + } + return failed != 0; +} diff --git a/tests/host/secure_layout/Makefile b/tests/host/secure_layout/Makefile index 25644822f..5a6227aeb 100644 --- a/tests/host/secure_layout/Makefile +++ b/tests/host/secure_layout/Makefile @@ -23,6 +23,8 @@ all: run run: @python3 ../../../tools/check_secure_layout.py --self-test + @python3 test_tpm_band_init.py + @python3 test_aarch64_interface_layout.py @python3 ../../../tools/check_no_fp_insn.py --self-test @python3 ../../../tools/check_stack_seal.py --self-test @out="$$( $(MAKE) --no-print-directory -C ../../.. \ diff --git a/tests/host/secure_layout/test_aarch64_interface_layout.py b/tests/host/secure_layout/test_aarch64_interface_layout.py new file mode 100644 index 000000000..4693d9e8e --- /dev/null +++ b/tests/host/secure_layout/test_aarch64_interface_layout.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Reject invalid AArch64 interface bands and manifest grants.""" + +import copy +import importlib.util +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile + + +ROOT = Path(__file__).resolve().parents[3] +TOOL = ROOT / "tools/aarch64_l3_layout.py" +HEADER = ROOT / "port/qemuvirt/memory_map.h" +INCLUDE = ROOT / "port/common/aarch64" + + +def load_layout_tool(): + spec = importlib.util.spec_from_file_location("aarch64_l3_layout", TOOL) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def check_manifest(layout, manifest, values): + path = None + try: + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", + delete=False) as handle: + json.dump(manifest, handle) + path = handle.name + layout.check_manifest(path, values) + finally: + if path is not None: + os.unlink(path) + + +def require_manifest_rejected(layout, name, manifest, values): + try: + check_manifest(layout, manifest, values) + except SystemExit as error: + if "interface band" not in str(error): + raise SystemExit("FAIL: %s failed for the wrong reason: %s" % + (name, error)) + return + raise SystemExit("FAIL: interface %s grant was accepted" % name) + + +def require_rejected(name, address, size="0x1000u"): + command = [ + sys.executable, + str(TOOL), + "--cc", + os.environ.get("CC", "cc"), + "-I", + str(INCLUDE), + "-D", + "WT_ISOLATION_LEVEL=3", + "-D", + "WT_SPM_TABLE_POOL_PAGES=128u", + "-D", + "WT_SPM_INTERFACE_PA=%s" % address, + "-D", + "WT_SPM_INTERFACE_SIZE=%s" % size, + "--shell", + str(HEADER), + ] + result = subprocess.run(command, capture_output=True, text=True, + check=False) + if result.returncode == 0: + raise SystemExit("FAIL: interface overlap with %s was accepted" % name) + if "application-interface band overlaps" not in result.stderr: + raise SystemExit("FAIL: %s collision failed for the wrong reason: %s" % + (name, result.stderr.strip())) + + +require_rejected("boot information", "0x0E040000u") +require_rejected("translation table pool", "0x0E080000u") + +layout = load_layout_tool() +values = { + "WT_SPM_KEYSTORE_PA": 0x1000, + "WT_SPM_KEYSTORE_SIZE": 0x3000, + "WT_SPM_VAULT_PA": 0x1000, + "WT_SPM_VAULT_SIZE": 0x1000, + "WT_SPM_ATTEST_PA": 0x2000, + "WT_SPM_ATTEST_SIZE": 0x1000, + "WT_SPM_HSMDATA_PA": 0x3000, + "WT_SPM_HSMDATA_SIZE": 0x1000, + "WT_SPM_INTERFACE_PA": 0x8000, + "WT_SPM_INTERFACE_SIZE": 0x1000, +} +manifest = { + "partitions": [ + {"name": "PARTITION_VAULT", "domain_id": 1}, + {"name": "PARTITION_ATTEST", "domain_id": 2}, + {"name": "PARTITION_HSM", "domain_id": 3}, + {"name": "PARTITION_TPM", "domain_id": 4}, + ], + "domains": [ + {"id": 1, "memory_resources": [ + {"base": 0x1000, "size": 0x1000}]}, + {"id": 2, "memory_resources": [ + {"base": 0x2000, "size": 0x1000}]}, + {"id": 3, "memory_resources": [ + {"base": 0x3000, "size": 0x1000}]}, + {"id": 4, "memory_resources": [ + {"base": 0x8000, "size": 0x1000}]}, + ], +} +check_manifest(layout, manifest, values) +for overlap_name, overlap in ( + ("subset", {"base": 0x8100, "size": 0x200}), + ("superset", {"base": 0x7F00, "size": 0x1200}), + ("partial-overlap", {"base": 0x7F00, "size": 0x200})): + invalid = copy.deepcopy(manifest) + invalid["domains"][0]["memory_resources"].append(overlap) + require_manifest_rejected(layout, overlap_name, invalid, values) + +invalid = copy.deepcopy(manifest) +invalid["partitions"].append( + {"name": "PARTITION_PKCS11", "domain_id": 5}) +invalid["domains"].append( + {"id": 5, "memory_resources": [ + {"base": 0x8000, "size": 0x1000}]}) +require_manifest_rejected(layout, "multiple-owner", invalid, values) + +invalid = copy.deepcopy(manifest) +invalid["domains"][3]["memory_resources"].append( + {"base": 0x8000, "size": 0x1000}) +require_manifest_rejected(layout, "duplicate-exact", invalid, values) + +invalid = copy.deepcopy(manifest) +invalid["partitions"] = [partition for partition in invalid["partitions"] + if partition["name"] != "PARTITION_TPM"] +require_manifest_rejected(layout, "ownerless-overlap", invalid, values) + +print("PASS: AArch64 interface layout and single-owner grants are enforced") diff --git a/tests/host/secure_layout/test_tpm_band_init.py b/tests/host/secure_layout/test_tpm_band_init.py new file mode 100644 index 000000000..31fca0f97 --- /dev/null +++ b/tests/host/secure_layout/test_tpm_band_init.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Check Armv8-M initialization of the TPM and PKCS11 data bands.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +START = ROOT / "src/arch/armv8m/start_armv8m.c" +LINKERS = ( + ROOT / "src/services/wolfhsm/runner/secure.ld", + ROOT / "port/mimxrt700/secure.ld", +) + + +def require(text, needle, source): + if needle not in text: + raise SystemExit("FAIL: %s lacks %s" % (source, needle)) + + +start = START.read_text(encoding="utf-8") +for symbol in ( + "_si_tpm", "_s_tpm", "_e_tpm_data", "_s_tpm_bss", "_e_tpm", + "_wt_band_tpm_base", "_wt_band_tpm_limit", + "_wt_tpm_stack_base", "_wt_tpm_stack_limit", +): + require(start, "extern uint32_t %s;" % symbol, START) + +for symbol in ( + "_si_pkcs11", "_s_pkcs11", "_e_pkcs11_data", "_s_pkcs11_bss", + "_e_pkcs11", "_wt_band_pkcs11_base", "_wt_band_pkcs11_limit", + "_wt_pkcs11_stack_base", "_wt_pkcs11_stack_limit", +): + require(start, "extern uint32_t %s;" % symbol, START) + +require( + start, + "{ &_si_tpm, &_s_tpm, &_e_tpm_data, &_s_tpm_bss, &_e_tpm,\n" + " &_wt_band_tpm_base, &_wt_band_tpm_limit },", + START, +) + +require( + start, + "{ &_si_pkcs11, &_s_pkcs11, &_e_pkcs11_data, &_s_pkcs11_bss,\n" + " &_e_pkcs11, &_wt_band_pkcs11_base, &_wt_band_pkcs11_limit },", + START, +) + +for linker in LINKERS: + text = linker.read_text(encoding="utf-8") + for api, prefix in (("tpm", "tpm"), ("pkcs11", "p11")): + require(text, "_wt_band_%s_base = ORIGIN(VNETDATA);" % api, + linker) + require(text, "_wt_band_%s_limit = ORIGIN(VNETDATA) +\n" + " ((SIZEOF(.%s_data) + SIZEOF(.%s_bss)) != 0 ? 48K : 0);" + % (api, api, api), linker) + for edge, amount in (("base", "48K"), ("limit", "64K")): + require(text, "_wt_%s_stack_%s = ORIGIN(VNETDATA) +\n" + " ((SIZEOF(.%s_data) + SIZEOF(.%s_bss)) != 0 ? %s : 0);" + % (api, edge, api, api, amount), linker) + for pattern in ("wt_sec_%s*.o" % api, "%s_sec_*.o" % prefix, + "%s_wc_*.o" % prefix, "%s_wh_*.o" % prefix): + require(text, "*%s(.data .data.*)" % pattern, linker) + require(text, "*%s(.bss .bss.* COMMON)" % pattern, linker) + +require( + start, + "for (dst = &_wt_tpm_stack_base; dst < &_wt_tpm_stack_limit; ++dst)", + START, +) + +require( + start, + "for (dst = &_wt_pkcs11_stack_base;\n" + " dst < &_wt_pkcs11_stack_limit; ++dst)", + START, +) + +print("PASS: Armv8-M TPM and PKCS11 band initialization contract") diff --git a/tests/host/tpm_client/Makefile b/tests/host/tpm_client/Makefile new file mode 100644 index 000000000..c24e48d09 --- /dev/null +++ b/tests/host/tpm_client/Makefile @@ -0,0 +1,94 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +ROOT := ../../.. +WOLFTPM := $(ROOT)/lib/wolfTPM + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -DWOLFTPM_SWTPM \ + -DWOLFTPM_USER_SETTINGS \ + -DWOLFTPM2_NO_WOLFCRYPT \ + -DSINGLE_THREADED \ + -D_DEFAULT_SOURCE \ + -ffunction-sections -fdata-sections \ + -I. \ + -I$(ROOT)/include \ + -I$(WOLFTPM) \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +ifeq ($(shell uname -s),Darwin) +GC_LDFLAGS := -Wl,-dead_strip +else +GC_LDFLAGS := -Wl,--gc-sections +endif + +_SRCS := \ + $(ROOT)/src/client/tpm_psa_transport.c \ + $(WOLFTPM)/src/tpm2.c \ + $(WOLFTPM)/src/tpm2_util.c \ + main.c + +_OBJS := $(addprefix $(BUILD_DIR)/,tpm_psa_transport.o tpm2.o tpm2_util.o main.o) +TEST_BIN := $(BUILD_DIR)/test_tpm_client + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/tpm_psa_transport.o: $(ROOT)/src/client/tpm_psa_transport.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm2.o: $(WOLFTPM)/src/tpm2.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/tpm2_util.o: $(WOLFTPM)/src/tpm2_util.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(_OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(GC_LDFLAGS) $(EXTRA_LDFLAGS) -o $@ $(_OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/tpm_client/main.c b/tests/host/tpm_client/main.c new file mode 100644 index 000000000..0bb8999c0 --- /dev/null +++ b/tests/host/tpm_client/main.c @@ -0,0 +1,293 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* TPM PSA client suite: a scripted PSA transport checks connection recovery, + * synchronous execution, response framing, and the SWTPM initialization contract. */ + +#include "psa/client.h" +#include "psa/error.h" +#include "wolftrust/tpm_psa_transport.h" + +#include +#include +#include +#include + +#include +#include +#include + +static int g_failures; +static int g_connects; +static int g_calls; +static int g_closes; +static psa_status_t g_call_status; +static uint8_t g_response[WT_TPM_FRAME_MAX]; +static size_t g_response_len; +static size_t g_forced_out_len; + +static void check(int ok, const char* what) +{ + if (ok) { + (void)printf("PASS: %s\n", what); + } + else { + (void)printf("FAIL: %s\n", what); + g_failures++; + } +} + +psa_handle_t psa_connect(uint32_t sid, uint32_t version) +{ + check(sid == WT_TPM_SERVICE_SID && version == WT_TPM_SERVICE_VERSION, + "client connects to SERVICE_TPM with the supported version"); + g_connects++; + return (psa_handle_t)42; +} + +void psa_close(psa_handle_t handle) +{ + check(handle == (psa_handle_t)42, "client closes the cached TPM handle"); + g_closes++; +} + +psa_status_t psa_call(psa_handle_t handle, int32_t type, + const psa_invec* in_vec, size_t in_len, + psa_outvec* out_vec, size_t out_len) +{ + size_t copy_len; + + g_calls++; + if (handle != (psa_handle_t)42) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + if (g_call_status != PSA_SUCCESS) { + return g_call_status; + } + if (type != WT_TPM_OP_TRANSACT || in_vec == NULL || in_len != 1U || + out_vec == NULL || out_len != 1U || + in_vec[0].len > WT_TPM_FRAME_MAX || + out_vec[0].len > WT_TPM_FRAME_MAX) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + copy_len = g_response_len; + if (copy_len > out_vec[0].len) { + copy_len = out_vec[0].len; + } + (void)memcpy(out_vec[0].base, g_response, copy_len); + out_vec[0].len = (g_forced_out_len != 0U) ? g_forced_out_len : copy_len; + return PSA_SUCCESS; +} + +static void mock_reset(void) +{ + wt_tpm_psa_close(); + g_connects = 0; + g_calls = 0; + g_closes = 0; + g_call_status = PSA_SUCCESS; + g_response_len = 0U; + g_forced_out_len = 0U; + (void)memset(g_response, 0, sizeof(g_response)); +} + +static void set_success_response(size_t declared, size_t received) +{ + g_response[0] = 0x80U; + g_response[1] = 0x01U; + g_response[2] = (uint8_t)(declared >> 24); + g_response[3] = (uint8_t)(declared >> 16); + g_response[4] = (uint8_t)(declared >> 8); + g_response[5] = (uint8_t)declared; + g_response_len = received; +} + +static void packet_init(TPM2_Packet* packet, uint8_t* frame, size_t size) +{ + (void)memset(frame, 0, size); + packet->buf = frame; + packet->size = (int)size; + packet->pos = TPM2_HEADER_SIZE; +} + +static void test_arguments(void) +{ + uint8_t frame[WT_TPM_FRAME_MAX + 1U]; + size_t len = 0U; + + mock_reset(); + check(wt_tpm_psa_transact(NULL, 1U, frame, sizeof(frame), &len) == + PSA_ERROR_INVALID_ARGUMENT && + wt_tpm_psa_transact(frame, 0U, frame, sizeof(frame), &len) == + PSA_ERROR_INVALID_ARGUMENT && + wt_tpm_psa_transact(frame, sizeof(frame), frame, sizeof(frame), + &len) == PSA_ERROR_INVALID_ARGUMENT && + g_connects == 0 && g_calls == 0, + "transact rejects invalid commands before connecting"); + check(wt_tpm_psa_transact(frame, 1U, NULL, 1U, &len) == + PSA_ERROR_INVALID_ARGUMENT && + wt_tpm_psa_transact(frame, 1U, frame, 0U, &len) == + PSA_ERROR_INVALID_ARGUMENT && + wt_tpm_psa_transact(frame, 1U, frame, sizeof(frame), NULL) == + PSA_ERROR_INVALID_ARGUMENT && + g_connects == 0 && g_calls == 0, + "transact rejects invalid response buffers before connecting"); +} + +static void test_connection_recovery(void) +{ + static const psa_status_t dead[] = { + PSA_ERROR_PROGRAMMER_ERROR, + PSA_ERROR_CONNECTION_BUSY, + PSA_ERROR_COMMUNICATION_FAILURE, + PSA_ERROR_BAD_STATE + }; + uint8_t frame[TPM2_HEADER_SIZE] = { 0U }; + size_t i; + size_t length; + + for (i = 0U; i < sizeof(dead) / sizeof(dead[0]); i++) { + mock_reset(); + set_success_response(TPM2_HEADER_SIZE, TPM2_HEADER_SIZE); + check(wt_tpm_psa_transact(frame, sizeof(frame), frame, sizeof(frame), &length) == PSA_SUCCESS && + g_connects == 1 && g_calls == 1, + "first TPM request opens one cached connection"); + check(wt_tpm_psa_transact(frame, sizeof(frame), frame, sizeof(frame), &length) == PSA_SUCCESS && + g_connects == 1 && g_calls == 2, + "next TPM request reuses the cached connection"); + g_call_status = dead[i]; + check(wt_tpm_psa_transact(frame, sizeof(frame), frame, sizeof(frame), &length) == dead[i] && + g_closes == 1, + "a dead-connection status evicts the cached handle"); + g_call_status = PSA_SUCCESS; + check(wt_tpm_psa_transact(frame, sizeof(frame), frame, sizeof(frame), &length) == PSA_SUCCESS && + g_connects == 2, + "the request after eviction reconnects"); + } +} + +static void test_execution_and_framing(void) +{ + TPM2_CTX ctx; + TPM2_Packet packet; + uint8_t frame[32]; + int rc; + + mock_reset(); + packet_init(&packet, frame, sizeof(frame)); + set_success_response(TPM2_HEADER_SIZE, TPM2_HEADER_SIZE); + rc = TPM2_SWTPM_SendCommand(&ctx, &packet); + check(rc == TPM_RC_SUCCESS && g_calls == 1 && + packet.size == TPM2_HEADER_SIZE, + "one synchronous call returns the framed response"); + + mock_reset(); + packet_init(&packet, frame, sizeof(frame)); + g_call_status = PSA_ERROR_CONNECTION_BUSY; + rc = TPM2_SWTPM_SendCommand(&ctx, &packet); + check(rc == TPM_RC_FAILURE && g_calls == 1, + "a failed command is never implicitly replayed"); + + mock_reset(); + packet_init(&packet, frame, sizeof(frame)); + set_success_response(TPM2_HEADER_SIZE, TPM2_HEADER_SIZE - 1U); + check(TPM2_SWTPM_SendCommand(&ctx, &packet) == TPM_RC_FAILURE, + "a response shorter than the TPM header is refused"); + + mock_reset(); + packet_init(&packet, frame, sizeof(frame)); + set_success_response(TPM2_HEADER_SIZE + 1U, TPM2_HEADER_SIZE); + check(TPM2_SWTPM_SendCommand(&ctx, &packet) == TPM_RC_FAILURE, + "a response whose declared length disagrees is refused"); + + mock_reset(); + packet_init(&packet, frame, sizeof(frame)); + set_success_response(TPM2_HEADER_SIZE, TPM2_HEADER_SIZE); + g_forced_out_len = sizeof(frame) + 1U; + check(TPM2_SWTPM_SendCommand(&ctx, &packet) == TPM_RC_FAILURE, + "a response reported beyond the packet capacity is refused"); +} + +static void test_packet_bounds(void) +{ + TPM2_CTX ctx; + TPM2_Packet packet; + uint8_t frame[32]; + + mock_reset(); + packet_init(&packet, frame, sizeof(frame)); + packet.size = 0; + check(TPM2_SWTPM_SendCommand(&ctx, &packet) == BAD_FUNC_ARG && + g_connects == 0 && g_calls == 0, + "SWTPM transport rejects a zero packet capacity before I/O"); + + packet_init(&packet, frame, sizeof(frame)); + packet.size = -1; + check(TPM2_SWTPM_SendCommand(&ctx, &packet) == BAD_FUNC_ARG && + g_connects == 0 && g_calls == 0, + "SWTPM transport rejects a negative packet capacity before I/O"); + + packet_init(&packet, frame, sizeof(frame)); + packet.pos = packet.size + 1; + check(TPM2_SWTPM_SendCommand(&ctx, &packet) == BAD_FUNC_ARG && + g_connects == 0 && g_calls == 0, + "SWTPM transport rejects a command beyond packet capacity"); +} + +static void test_locality_contract(void) +{ + TPM2_CTX ctx; + int rc; + + mock_reset(); + check(TPM2_TIS_RequestLocality(NULL, 1) == BAD_FUNC_ARG, + "locality request rejects a NULL context"); + rc = TPM2_Init(&ctx, NULL, NULL); + check(rc == TPM_RC_SUCCESS && ctx.locality == WOLFTPM_LOCALITY_DEFAULT && + TPM2_GetActiveCtx() == &ctx, + "direct TPM initialization records the granted locality"); + check(TPM2_Cleanup(&ctx) == TPM_RC_SUCCESS && + TPM2_GetActiveCtx() == NULL, + "direct TPM cleanup clears the active context"); + + rc = TPM2_Init_ex(&ctx, NULL, NULL, 1); + check(rc == TPM_RC_SUCCESS && ctx.locality == WOLFTPM_LOCALITY_DEFAULT && + TPM2_GetActiveCtx() == &ctx, + "explicit SWTPM startup records the granted locality"); + check(TPM2_Cleanup(&ctx) == TPM_RC_SUCCESS && + TPM2_GetActiveCtx() == NULL, + "explicit SWTPM startup remains cleanup-safe"); +} + +int main(void) +{ + test_arguments(); + test_connection_recovery(); + test_execution_and_framing(); + test_packet_bounds(); + test_locality_contract(); + + wt_tpm_psa_close(); + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: TPM PSA client recovery, framing, and initialization\n"); + return 0; +} diff --git a/tests/host/tpm_client/user_settings.h b/tests/host/tpm_client/user_settings.h new file mode 100644 index 000000000..a7f36f6e4 --- /dev/null +++ b/tests/host/tpm_client/user_settings.h @@ -0,0 +1,26 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFTRUST_TEST_TPM_CLIENT_USER_SETTINGS_H +#define WOLFTRUST_TEST_TPM_CLIENT_USER_SETTINGS_H + +/* This host suite exercises the transport and TPM context lifecycle only. */ + +#endif /* WOLFTRUST_TEST_TPM_CLIENT_USER_SETTINGS_H */ diff --git a/tests/host/tpm_relay/Makefile b/tests/host/tpm_relay/Makefile new file mode 100644 index 000000000..86f544354 --- /dev/null +++ b/tests/host/tpm_relay/Makefile @@ -0,0 +1,90 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I. \ + -I$(ROOT)/include \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +# The relay is port-free: the FF-M runtime plus tpm_relay_service.c run here +# exactly as SERVICE_TPM's dispatch drives them on target. No wolfHSM or +# wolfCrypt dependency; the handler seam supplies deterministic responses. +_WT_SRCS := \ + $(ROOT)/src/ffm.c \ + $(ROOT)/src/ffm_domain.c \ + $(ROOT)/src/ipc.c \ + $(ROOT)/src/spm_gate.c + +_WTS_SRCS := \ + $(ROOT)/src/services/tpm/tpm_relay_service.c + +_TEST_SRCS := main.c + +_WT_OBJS := $(patsubst %.c,wt_%.o,$(notdir $(_WT_SRCS))) +_WTS_OBJS := $(patsubst %.c,wts_%.o,$(notdir $(_WTS_SRCS))) +_TEST_OBJS := $(patsubst %.c,%.o,$(notdir $(_TEST_SRCS))) + +ALL_OBJS := $(addprefix $(BUILD_DIR)/,$(_WT_OBJS) $(_WTS_OBJS) $(_TEST_OBJS)) +TEST_BIN := $(BUILD_DIR)/test_tpm_relay + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wts_%.o: $(ROOT)/src/services/tpm/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(ALL_OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(ALL_OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/tpm_relay/main.c b/tests/host/tpm_relay/main.c new file mode 100644 index 000000000..d4bf4006d --- /dev/null +++ b/tests/host/tpm_relay/main.c @@ -0,0 +1,270 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +#include "wolftrust/ffm.h" +#include "wolftrust/services/tpm_relay.h" +#include "psa/client.h" + +#define TEST_TPM_PARTITION 2 +#define TEST_APP_CLIENT (-1) +#define TEST_OTHER_CLIENT (-3) +#define TEST_SP_CLIENT (3) + +static int g_failures; + +static void check(int ok, const char* what) +{ + if (ok) { + printf("PASS: %s\n", what); + } + else { + printf("FAIL: %s\n", what); + g_failures++; + } +} + +/* ---- in-process FF-M runtime fixture ---- */ +static wt_ffm_runtime_t g_runtime; + +static int test_check_read(void* context, psa_client_id_t caller, + const void* address, size_t size) +{ + (void)context; + (void)caller; + return size == 0U || address != NULL; +} + +static int test_check_write(void* context, psa_client_id_t caller, + void* address, size_t size) +{ + (void)context; + (void)caller; + return size == 0U || address != NULL; +} + +static void test_panic(void* context, int32_t partition_id) +{ + (void)context; + (void)partition_id; +} + +static int test_dispatch(void* context, wt_ffm_runtime_t* runtime, + int32_t partition_id) +{ + (void)context; + return wt_tpm_relay_dispatch(NULL, runtime, partition_id); +} + +static const wt_ffm_port_ops_t g_port_ops = { + test_check_read, + test_check_write, + test_dispatch, + test_panic +}; + +static const wt_service_descriptor_t g_services[] = { + { + "SERVICE_TPM", WT_TPM_SERVICE_SID, 1U, WT_SERVICE_VERSION_RELAXED, + 0x40U, 0U, 1U, 1U + } +}; + +static const wt_partition_manifest_t g_partitions[] = { + { + "PARTITION_TPM", TEST_TPM_PARTITION, WT_FFM_VERSION_1_0, + WT_PARTITION_MODEL_IPC, WT_PARTITION_PRIORITY_NORMAL, + g_services, sizeof(g_services) / sizeof(g_services[0]), + NULL, 0U, NULL, 0U + } +}; + +static const wt_system_manifest_t g_manifest = { + .format_version = WT_MANIFEST_FORMAT_VERSION, + .generator_version = "tpm-relay-test", + .features = WT_MANIFEST_FEATURE_IPC, + .partitions = g_partitions, + .partition_count = sizeof(g_partitions) / sizeof(g_partitions[0]) +}; + +/* One op from one client: connect, call, close. */ +static psa_status_t tpm_op(int32_t client_id, int32_t op, const uint8_t* in, + size_t in_len, uint8_t* out, size_t out_cap, + size_t* out_len) +{ + psa_invec iv[1]; + psa_outvec ov[1]; + int32_t handle; + psa_status_t st; + + handle = (int32_t)wt_ffm_connect(&g_runtime, client_id, + WT_TPM_SERVICE_SID, 1U); + if (!PSA_HANDLE_IS_VALID(handle)) { + return PSA_ERROR_CONNECTION_REFUSED; + } + iv[0].base = in; + iv[0].len = in_len; + ov[0].base = out; + ov[0].len = out_cap; + st = wt_ffm_call(&g_runtime, client_id, handle, op, + (in != NULL) ? iv : NULL, (in != NULL) ? 1U : 0U, + (out != NULL) ? ov : NULL, (out != NULL) ? 1U : 0U); + if (out_len != NULL) { + *out_len = ov[0].len; + } + (void)wt_ffm_close(&g_runtime, client_id, handle); + return st; +} + +static unsigned int g_direct_calls; +static const uint8_t* g_direct_command; +static uint8_t* g_direct_response; +static int g_direct_fail; +static int g_direct_length_mode; + +static int direct_handler(const uint8_t* command, size_t command_len, + uint8_t* response, size_t response_cap, + size_t* response_len, void* context) +{ + size_t i; + + (void)context; + g_direct_calls++; + g_direct_command = command; + g_direct_response = response; + if (command_len > response_cap) { + return -1; + } + for (i = 0U; i < command_len; i++) { + response[i] = command[i] ^ 0x5AU; + } + *response_len = command_len; + if (g_direct_length_mode > 0) { + *response_len = response_cap + 1U; + } + else if (g_direct_length_mode < 0) { + *response_len = 0U; + } + return g_direct_fail; +} + +static void test_direct_exchange(void) +{ + uint8_t command[WT_TPM_FRAME_MAX + 1U]; + uint8_t response[WT_TPM_FRAME_MAX]; + size_t length = 0U; + size_t i; + unsigned int calls; + int exact = 1; + int scrubbed = 1; + + check(wt_ffm_init(&g_runtime, &g_manifest, &g_port_ops, NULL) == + WT_FFM_SUCCESS, "WT-FFM-0054 direct TPM runtime initializes"); + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 1U, + response, sizeof(response), &length) == PSA_ERROR_NOT_SUPPORTED, + "unbound TPM service fails closed"); + wt_tpm_relay_set_client(TEST_APP_CLIENT); + wt_tpm_relay_set_local_provider(direct_handler, NULL); + for (i = 0U; i < sizeof(command); i++) { + command[i] = (uint8_t)i; + } + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, + WT_TPM_FRAME_MAX, response, sizeof(response), &length) == + PSA_SUCCESS && length == WT_TPM_FRAME_MAX, + "one copied call carries maximum TPM command and response"); + for (i = 0U; i < sizeof(response); i++) { + if (response[i] != (uint8_t)(command[i] ^ 0x5AU)) { + exact = 0; + } + if (g_direct_command[i] != 0U || g_direct_response[i] != 0U) { + scrubbed = 0; + } + } + check(exact && scrubbed, "direct TPM delivers exact bytes and scrubs staging"); + calls = g_direct_calls; + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 0U, + response, sizeof(response), &length) == PSA_ERROR_INVALID_ARGUMENT && + tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, 0U, &length) == PSA_ERROR_INVALID_ARGUMENT && + g_direct_calls == calls, + "empty input or output is refused before executing a TPM command"); + check(tpm_op(TEST_OTHER_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_ERROR_NOT_PERMITTED && + g_direct_calls == calls, + "WT-FFM-0059 wrong TPM caller never reaches the handler"); + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, + sizeof(command), response, 16U, &length) == + PSA_ERROR_INVALID_ARGUMENT && g_direct_calls == calls, + "oversized direct command is rejected before dispatch"); + check(tpm_op(TEST_APP_CLIENT, 1 /* retired SUBMIT */, command, 16U, + response, sizeof(response), &length) == PSA_ERROR_NOT_SUPPORTED && + g_direct_calls == calls, + "retired worker operations cannot execute commands"); + check(tpm_op(TEST_SP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) != PSA_SUCCESS && + g_direct_calls == calls, + "WT-FFM-0059 unrelated Secure caller cannot access TPM"); + g_direct_fail = -1; + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_ERROR_GENERIC_ERROR && + g_direct_calls == calls + 1U && g_direct_command[0] == 0U && + g_direct_response[0] == 0U, + "failed direct command is scrubbed and never replayed"); + g_direct_fail = 0; + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_SUCCESS, + "a new direct command succeeds after a handler failure"); + g_direct_length_mode = 1; + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_ERROR_GENERIC_ERROR && + g_direct_response[0] == 0U, + "oversized TPM handler response is refused and scrubbed"); + g_direct_length_mode = -1; + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_ERROR_GENERIC_ERROR, + "empty TPM handler response is refused"); + g_direct_length_mode = 0; + calls = g_direct_calls; + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, 8U, &length) == PSA_ERROR_INVALID_ARGUMENT && + g_direct_calls == calls + 1U && g_direct_response[0] == 0U, + "short output cannot retain or replay an executed TPM command"); + wt_tpm_relay_set_client(1); + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_ERROR_NOT_SUPPORTED, + "invalid caller binding fails closed"); + wt_tpm_relay_set_client(TEST_APP_CLIENT); + check(tpm_op(TEST_APP_CLIENT, WT_TPM_OP_TRANSACT, command, 16U, + response, sizeof(response), &length) == PSA_SUCCESS, + "new command succeeds after rebinding the restarted service"); +} + +int main(void) +{ + test_direct_exchange(); + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: WT-FFM-0054 synchronous TPM service\n"); + return 0; +} diff --git a/tests/host/wolfhsm_relay/Makefile b/tests/host/wolfhsm_relay/Makefile index df393c946..2f5780243 100644 --- a/tests/host/wolfhsm_relay/Makefile +++ b/tests/host/wolfhsm_relay/Makefile @@ -156,6 +156,12 @@ $(TEST_BIN): $(ALL_OBJS) | $(BUILD_DIR) run: $(TEST_BIN) $(TEST_BIN) + $(MAKE) run-device-find BUILD_DIR=$(BUILD_DIR)/device-find \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS) -DWOLF_CRYPTO_CB_FIND -DWT_TPM_ROLE_FTPM -DWT_TEST_TPM_DEVICE_FIND" + +.PHONY: run-device-find +run-device-find: $(TEST_BIN) + $(TEST_BIN) compilers: $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc diff --git a/tests/host/wolfhsm_relay/main.c b/tests/host/wolfhsm_relay/main.c index 24a9ef962..079ead7c2 100644 --- a/tests/host/wolfhsm_relay/main.c +++ b/tests/host/wolfhsm_relay/main.c @@ -428,6 +428,10 @@ int main(void) uint8_t rng_large[1000]; int rc; int guest_init_rc; +#ifdef WT_TEST_TPM_DEVICE_FIND + ecc_key device_find_key; + WC_RNG device_find_rng; +#endif /* ECC state for the sign/verify round trip */ whKeyId key_id = WH_KEYID_ERASED; @@ -465,6 +469,22 @@ int main(void) check(guest_init_rc == 0, "FreeRTOS guest init installs retry after cold-start failure"); +#ifdef WT_TEST_TPM_DEVICE_FIND + /* An unavailable HSM must not silently turn fwTPM ECC into software. */ + memset(&device_find_rng, 0, sizeof(device_find_rng)); + rc = wc_ecc_init(&device_find_key); + check(rc == 0 && device_find_key.devId == INVALID_DEVID, + "WT-FFM-0054 fwTPM key starts without a device ID"); + if (rc == 0) { + rc = wc_ecc_make_key_ex(&device_find_rng, 32, &device_find_key, + ECC_SECP256R1); + check(rc == WC_HW_E, + "WT-FFM-0054 implicit fwTPM ECC reaches unavailable HSM"); + wc_ecc_free(&device_find_key); + } + return g_failures == 0 ? 0 : 1; +#endif + /* === FF-M runtime up first: the client transport connects through it. */ check(wt_ffm_init(&g_runtime, &g_manifest, &g_port_ops, NULL) == WT_FFM_SUCCESS, diff --git a/tests/target/lib/guest_build.sh b/tests/target/lib/guest_build.sh new file mode 100644 index 000000000..6aa4ee65a --- /dev/null +++ b/tests/target/lib/guest_build.sh @@ -0,0 +1,88 @@ +# guest_build.sh +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +# Keep the guest optimization policy consistent with the Secure image. +case "${WT_LTO:-1}" in + 0) WT_GUEST_LTO_FLAGS= ;; + 1) WT_GUEST_LTO_FLAGS=-flto=auto ;; + *) echo "unsupported WT_LTO=${WT_LTO}" >&2; exit 1 ;; +esac +# LTO can emit memory calls after its global dead-code pass. +WT_GUEST_KEEP_FLAGS=-Wl,-u,memcpy,-u,memset,-u,memmove,-u,memcmp + + +# Keep RTOS assembly and cross-world calls opaque to whole-program analysis. +wt_guest_compile_platform() +{ + WT_GUEST_PLATFORM_OBJECTS= + wt_platform_index=0 + for wt_platform_source do + wt_platform_object="$BUILD_DIR/platform_$wt_platform_index.o" + # CFLAGS is the same intentional word list used by the final link. + # shellcheck disable=SC2086 + "$GUEST_CC" $CFLAGS -fno-lto -c "$wt_platform_source" \ + -o "$wt_platform_object" + WT_GUEST_PLATFORM_OBJECTS="$WT_GUEST_PLATFORM_OBJECTS $wt_platform_object" + wt_platform_index=$((wt_platform_index + 1)) + done +} + + +wt_guest_prepare_profile() +{ + WT_GUEST_PROFILE_DIR="$BUILD_DIR/crypto-profile" + python3 "$ROOT/tools/crypto_profile.py" --root "$ROOT" \ + --output "$WT_GUEST_PROFILE_DIR" --profile "${WT_CRYPTO_PROFILE:-default}" + WT_GUEST_PROFILE_FLAGS="-include $WT_GUEST_PROFILE_DIR/selected_profile.h" +} + +# Interface providers use FF-M IPC but must not pull in PSA Crypto. +wt_guest_reject_psa_crypto() +{ + if printf '%s\n' "$1" | grep -Eq '[[:space:]](wt_profile_real_)?psa_(crypto_init|generate_|hash_|cipher_|aead_|mac_|sign_|verify_|key_derivation_)'; then + echo "FAIL: PSA Crypto linked into an independent TPM/PKCS11 image" >&2 + exit 1 + fi +} + +# A selected HSM engine need not retain a second software ECC implementation. +wt_guest_hsm_ecc_flags() +{ + WT_GUEST_HSM_ECC_FLAGS= + case "${WT_PROVIDER_ECC_FALLBACK:-0}" in + 0) + if [ "$WT_ENGINE" = hsm ]; then + WT_GUEST_HSM_ECC_FLAGS=-DWOLF_CRYPTO_CB_ONLY_ECC + fi + ;; + 1) ;; + *) echo "WT_PROVIDER_ECC_FALLBACK must be 0 or 1" >&2; exit 2 ;; + esac +} + +wt_guest_profile_enabled() +{ + python3 - "$WT_GUEST_PROFILE_DIR/selection.json" "$1" "$2" <<'PYTHON' +import json +import sys +with open(sys.argv[1], encoding="utf-8") as selection: + names = json.load(selection)[sys.argv[2]] +raise SystemExit(0 if sys.argv[3] in names else 1) +PYTHON +} diff --git a/tests/target/lib/scenario.sh b/tests/target/lib/scenario.sh index 35a4d56a7..c397ff0be 100644 --- a/tests/target/lib/scenario.sh +++ b/tests/target/lib/scenario.sh @@ -43,6 +43,11 @@ scenario_secure_flags() { spbudgetneg) echo "WT_SP_FAULT_ALWAYS_PROBE=1" ;; vnet) echo "CONFIG_VNET=y" ;; vnetneg) echo "CONFIG_VNET=y WT_VNET_NEG_PROBE=1" ;; + tpm) echo "CONFIG_TPM=y" ;; + tpmneg) echo "CONFIG_TPM=y WT_TPM_NEG_PROBE=1" ;; + pkcs11|pkcs11core|pkcs11objects|pkcs11crypto|pkcs11negative) + echo "CONFIG_PKCS11=y" ;; + pkcs11neg) echo "CONFIG_PKCS11=y WT_PKCS11_NEG_PROBE=1" ;; manifestneg) echo "WT_MANIFEST_NEG_PROBE=1" ;; manifestneg2) echo "WT_MANIFEST_NEG_PROBE=2" ;; fpneg) echo "WT_SP_FAULT_PROBE=1 WT_FP_NEG_PROBE=1" ;; diff --git a/tests/target/lib/scenario_matrix.py b/tests/target/lib/scenario_matrix.py index bf85ffaf1..4d3128e9f 100755 --- a/tests/target/lib/scenario_matrix.py +++ b/tests/target/lib/scenario_matrix.py @@ -33,14 +33,26 @@ import argparse import json import os +import re import sys +from pathlib import Path ENGINES = ("native", "hsm") # Scenarios whose Secure-image probe or client wire lives only in the wolfHSM # engine: the native engine does not link it, so the behaviour under test does # not exist there. +PKCS11_SCENARIOS = frozenset(( + "pkcs11", "pkcs11core", "pkcs11objects", "pkcs11crypto", + "pkcs11negative", "pkcs11neg", +)) HSM_ONLY = frozenset(("hsmattackneg", "hsmpinneg", "hsmfaultneg")) +TPM_SCENARIOS = frozenset(("tpm", "tpmneg")) +INTERFACE_SCENARIOS = TPM_SCENARIOS | PKCS11_SCENARIOS + + +# Every application interface supports either selected crypto engine. +NATIVE_ONLY = frozenset() # smoke: the per-PR set (one job per scenario and engine). groups: the full # tier, packed so each job builds the emulator and wolfBoot once. @@ -50,7 +62,7 @@ "arm": "m33mu", "image": "ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15", "smoke": ("positive", "gtzcneg", "crossdomain", "bothpsa", "confboot", - "devcrypto"), + "devcrypto", "tpm", "tpmneg", "pkcs11", "pkcs11neg"), "groups": ( ("positive", "Positive lifecycle"), ("bothpsa", "Both-OS PSA parity (Zephyr + FreeRTOS)"), @@ -83,6 +95,11 @@ ("bootupdate", "Full boot-and-update swap gate"), ("vnet", "wolfIP virtual network (mediated ping)"), ("vnetneg", "Confined VNET isolation negatives"), + ("tpm", "Zephyr and FreeRTOS TPM 2.0 through the isolated fwTPM"), + ("tpmneg", "Confined TPM relay and fwTPM isolation negatives"), + ("pkcs11", + "Zephyr and FreeRTOS PKCS11 API through an isolated provider"), + ("pkcs11neg", "Confined PKCS11 relay and provider isolation negatives"), ("manifestneg", "Corrupted-manifest activation refused"), ("manifestneg2", "Level 2 manifest refused at boot"), ("manifestneg3", "Overlapping partition tables refused at boot"), @@ -179,7 +196,8 @@ "guest1", "smcfuzz", "secramneg", "periphneg", "periphspneg", "resetneg", "ffa-memneg", "hsmattackneg", "hsmpinneg", "attestneg", "vaultrecover", "vaultrecoversec", "confboot", "storage", "devstorage", - "devattest", "devattestqcbor", "devcrypto", + "devattest", "devattestqcbor", "devcrypto", "tpm", "tpmneg", + "pkcs11", "pkcs11neg", ) QEMU_A_ACS = ( "ffaacs-discovery", "ffaacs-direct", "ffaacs-memory", "ffaacs-notify", @@ -190,8 +208,12 @@ # representative GICv3 virt cell (both engines) and the versal cell (native). # boot-smp2 and the ACS stay in the full tier. QEMU_A_SMOKE = ("smoke", "boot", "positive", "crossdomain", "ffa-direct", - "confboot", "devcrypto") + "confboot", "devcrypto", "tpm", "tpmneg", "pkcs11", + "pkcs11neg") QEMU_A_SMOKE_CELLS = {"virt-gicv3-a72": ENGINES, "versal-virt": ("native",)} +QEMU_A_TPM = frozenset(("tpm", "tpmneg")) +QEMU_A_PKCS11 = frozenset(("pkcs11", "pkcs11neg")) +QEMU_A_INTERFACES = QEMU_A_TPM | QEMU_A_PKCS11 def qemu_a_entry(cell, engine, kind, tier, scenarios): @@ -209,17 +231,24 @@ def qemu_a_cell_suite(cell): return tuple(s for s in QEMU_A_SUITE if s not in exempt) +def qemu_a_scenarios(cell, engine, scenarios): + """The direct interfaces run in their own visible QEMU PR matrix.""" + return tuple(s for s in scenarios if s not in QEMU_A_INTERFACES) + + def qemu_a_full(cell): out = [] for engine in ENGINES: out.append(qemu_a_entry(cell, engine, "suite", "full", - qemu_a_cell_suite(cell))) + qemu_a_scenarios(cell, engine, + qemu_a_cell_suite(cell)))) out.append(qemu_a_entry(cell, engine, "acs", "full", QEMU_A_ACS)) return out def qemu_a_smoke(cell): - return [qemu_a_entry(cell, engine, "suite", "smoke", QEMU_A_SMOKE) + return [qemu_a_entry(cell, engine, "suite", "smoke", + qemu_a_scenarios(cell, engine, QEMU_A_SMOKE)) for engine in QEMU_A_SMOKE_CELLS.get(cell["name"], ())] @@ -256,7 +285,12 @@ def group_name(port, scenario): def engines_for(scenarios, engine): - engines = ("hsm",) if all(s in HSM_ONLY for s in scenarios) else ENGINES + if all(s in HSM_ONLY for s in scenarios): + engines = ("hsm",) + elif all(s in NATIVE_ONLY for s in scenarios): + engines = ("native",) + else: + engines = ENGINES return [e for e in engines if engine is None or e == engine] @@ -300,7 +334,11 @@ def ci_plan(event, labels, port_input): continue else: full = True - out.extend(entries(port, "full" if full else "smoke", None)) + planned = entries(port, "full" if full else "smoke", None) + if port == "stm32h563": + planned = [e for e in planned if not any( + s in INTERFACE_SCENARIOS for s in e["key"].split())] + out.extend(planned) return out @@ -362,11 +400,74 @@ def qemu_a_l3_gaps(): return gaps +def interface_axes_error(workflow, job, axes, bindings): + """Fail closed if a profile/API/engine cell or its runtime binding is lost.""" + match = re.search(r"^ " + re.escape(job) + r":\n(.*?)(?=^ [\w-]+:|\Z)", + workflow, re.MULTILINE | re.DOTALL) + if match is None: + return "missing interface job " + job + body = match.group(1) + found = re.findall(r"^ (\w+): \[([^\]\n]*)\]$", body, re.MULTILINE) + actual = {name: tuple(v.strip() for v in values.split(",")) + for name, values in found} + if actual != axes or len(found) != len(axes): + return "wrong interface axes for " + job + name = re.search(r"^ name: (.*)$", body, re.MULTILINE) + if name is None or any("matrix." + axis not in name.group(1) for axis in axes): + return "interface name omits a matrix axis for " + job + for variable, expression in bindings.items(): + if not re.search(r"^ " + re.escape(variable) + ": " + + re.escape(expression) + r"$", body, re.MULTILINE): + return "wrong interface binding for " + variable + if "WT_REQUIRE_SCENARIO_PASS: 1" not in body: + return "interface matrix does not require real runtime passes" + return None + + +def interface_matrices_error(workflow, aarch_workflow): + shared = {"engine": ("native", "hsm"), "profile": ("default", "full")} + bindings = {"WT_ENGINE": "${{ matrix.engine }}", + "WT_CRYPTO_PROFILE": "${{ matrix.profile }}"} + for source, job, axes, extra in ( + (workflow, "interfaces-m33mu", + dict(shared, api=("psa", "pkcs11", "tpm"), os=("zephyr", "freertos")), + {"WT_INTERFACE_OS": "${{ matrix.os }}"}), + (workflow, "interface-isolation-m33mu", + dict(shared, scenario=("tpmneg", "pkcs11neg")), {}), + (aarch_workflow, "interfaces-qemu-a", + dict(shared, scenario=("positive", "tpm", "tpmneg", "pkcs11", "pkcs11neg")), {}), + ): + error = interface_axes_error(source, job, axes, dict(bindings, **extra)) + if error: + return error + if "${{ matrix.api == 'psa' && 'positive' || matrix.api }}" not in workflow: + return "wrong API scenario binding" + if "run_suite.sh m33mu ${{ matrix.scenario }}" not in workflow or \ + "run_suite.sh qemu-a ${{ matrix.scenario }}" not in aarch_workflow: + return "wrong interface scenario binding" + return None + + def selftest(): """The routing table above and the engine rule, run by the select job.""" gaps = qemu_a_l3_gaps() + workflow = (Path(__file__).parents[3] / ".github" / "workflows" / + "m33mu.yml").read_text(encoding="utf-8") + aarch_workflow = (Path(__file__).parents[3] / ".github" / "workflows" / + "aarch64-cross-compile.yml").read_text(encoding="utf-8") if gaps: return gaps[0] + pr_jobs = {e["job"] for e in ci_plan("pull_request", [], "")} + for scenario in sorted(INTERFACE_SCENARIOS): + if engines_for((scenario,), None) != list(ENGINES): + return "%s does not support both crypto engines" % scenario + for engine in ENGINES: + required_job = "stm32h563_%s_%s" % (scenario, engine) + if required_job in pr_jobs: + return "generated matrix duplicates explicit job %s" % required_job + error = interface_matrices_error(workflow, aarch_workflow) + if error: + return error for event, labels, port_input, expect in SELFTEST: got = " ".join(sorted(set(e["port"] + ":" + e["tier"] for e in ci_plan(event, labels.split(), port_input)))) @@ -380,12 +481,22 @@ def selftest(): if got != expect: return "qemu_a_plan(%s, %r, %r) = %s, expected %s" % ( event, labels, cell_input, got, expect) + pr_aarch = qemu_a_plan("pull_request", [], "") + for entry_ in pr_aarch: + scenarios = set(entry_["scenarios"].split()) + if scenarios & set(QEMU_A_INTERFACES): + return "bad AArch64 interface plan for %s" % entry_["job"] + if "inputs.cell == ''" not in aarch_workflow: + return "AArch64 interface matrix skips an input-less nightly dispatch" for port in sorted(PORTS): for tier in ("smoke", "full"): for e in entries(port, tier, None): if e["engine"] == "native" and all( s in HSM_ONLY for s in e["key"].split()): return "native job for the hsm-only %s" % e["key"] + if e["engine"] == "hsm" and all( + s in NATIVE_ONLY for s in e["key"].split()): + return "hsm job for the native-only %s" % e["key"] if e["engine"] not in ENGINES or e["image"] == "": return "bad entry %r" % e return None diff --git a/tests/target/run_h5_hardware.sh b/tests/target/run_h5_hardware.sh index 35c8edfc1..0806a3b4d 100755 --- a/tests/target/run_h5_hardware.sh +++ b/tests/target/run_h5_hardware.sh @@ -42,7 +42,7 @@ set -o pipefail mode="${1:-all}" scenario="${2:-positive}" case "$mode" in build|flash|all) ;; *) echo "usage: $0 build|flash|all [scenario]" >&2; exit 2 ;; esac -case "$scenario" in positive|restart|crossdomain|keystoreneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|vaultrecover|vaultrecoversec|authneg|writeonce|hsmattackneg|bootupdate|vnet|vnetneg|gtzcneg|fpneg|sealneg|sealpivotneg|periphneg|mspovfneg|busfaultneg|nsbusfaultneg|xnneg|svcneg) ;; *) echo "usage: $0 $mode positive|restart|crossdomain|keystoreneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|vaultrecover|vaultrecoversec|authneg|writeonce|hsmattackneg|bootupdate|vnet|vnetneg|gtzcneg|fpneg|sealneg|sealpivotneg|periphneg|mspovfneg|busfaultneg|nsbusfaultneg|xnneg|svcneg" >&2; exit 2 ;; esac +case "$scenario" in positive|restart|crossdomain|keystoreneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|vaultrecover|vaultrecoversec|authneg|writeonce|hsmattackneg|bootupdate|vnet|vnetneg|tpm|tpmneg|gtzcneg|fpneg|sealneg|sealpivotneg|periphneg|mspovfneg|busfaultneg|nsbusfaultneg|xnneg|svcneg) ;; *) echo "usage: $0 $mode positive|restart|crossdomain|keystoreneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|vaultrecover|vaultrecoversec|authneg|writeonce|hsmattackneg|bootupdate|vnet|vnetneg|tpm|tpmneg|gtzcneg|fpneg|sealneg|sealpivotneg|periphneg|mspovfneg|busfaultneg|nsbusfaultneg|xnneg|svcneg" >&2; exit 2 ;; esac repo="$(cd "$(dirname "$0")/../.." && pwd)" cd "$repo" @@ -61,7 +61,7 @@ SERIAL="${H5_SERIAL:-/dev/ttyACM0}" # confboot reboots the whole chain once per panic test (real SYSRESETREQ, each # re-running wolfBoot), so it needs a long ceiling; the capture stops early on # the suite report. -case "$scenario" in restart|nsbusfaultneg) cap_default=32 ;; confboot) cap_default=900 ;; devstorage|devcrypto|devattest|devattestqcbor|vaultrecover|vaultrecoversec) cap_default=600 ;; bootupdate) cap_default=45 ;; authneg) cap_default=30 ;; writeonce) cap_default=40 ;; *) cap_default=25 ;; esac +case "$scenario" in restart|nsbusfaultneg) cap_default=32 ;; confboot) cap_default=900 ;; devstorage|devcrypto|devattest|devattestqcbor|vaultrecover|vaultrecoversec) cap_default=600 ;; bootupdate) cap_default=45 ;; authneg) cap_default=30 ;; writeonce) cap_default=40 ;; tpm|tpmneg) cap_default=90 ;; *) cap_default=25 ;; esac CAP_S="${H5_CAPTURE_SECONDS:-$cap_default}" LOGFILE="${WT_SCENARIO_LOG:-ci-h5-hardware-$scenario.log}" case "$LOGFILE" in /*) ;; *) LOGFILE="$repo/$LOGFILE" ;; esac @@ -94,6 +94,9 @@ guest1="$repo/tests/firmware/zephyr-stm32h5/build/freertos_guest1/freertos_guest if [ "$scenario" = "vnet" ] || [ "$scenario" = "vnetneg" ]; then guest0="$repo/tests/firmware/stm32h563-vnet/build/guest0.bin" guest1="$repo/tests/firmware/stm32h563-vnet/build/guest1.bin" +elif [ "$scenario" = "tpm" ] || [ "$scenario" = "tpmneg" ]; then + guest0="$repo/tests/firmware/stm32h563-tpm/build/tpm/guest0.bin" + guest1="$repo/tests/firmware/stm32h563-tpm/build/tpm/guest1.bin" fi if [ "$mode" != "flash" ]; then @@ -170,6 +173,9 @@ if [ "$mode" != "flash" ]; then [ "$scenario" = "svcneg" ] && secure_flags="WT_SVC_NEG_PROBE=1" [ "$scenario" = "vnet" ] && secure_flags="CONFIG_VNET=y" [ "$scenario" = "vnetneg" ] && secure_flags="CONFIG_VNET=y WT_VNET_NEG_PROBE=1" + { [ "$scenario" = "tpm" ] || [ "$scenario" = "tpmneg" ]; } && \ + secure_flags="CONFIG_TPM=y" + [ "$scenario" = "tpmneg" ] && secure_flags="CONFIG_TPM=y WT_TPM_NEG_PROBE=1" # WT_CONF_DIAG_TRAP=0: the emulator-only hang-probe fault would become a # conformance-monitor reset on silicon and can eat the suite's report window. [ "$scenario" = "confboot" ] && { secure_flags="WT_CONFORMANCE=1 WT_CONF_DIAG_TRAP=0"; guest_flags="WT_RUN_CONFORMANCE=1"; } @@ -204,6 +210,15 @@ if [ "$mode" != "flash" ]; then WT_GUEST0_RAM_BASE=0x20000000 WT_GUEST1_RAM_BASE=0x20010000 \ WT_GUEST_RAM_SIZE=0x00010000 } >> "$LOGFILE" 2>&1 + elif [ "$scenario" = "tpm" ] || [ "$scenario" = "tpmneg" ]; then + { + rm -rf tests/firmware/stm32h563-tpm/build + make -C tests/firmware/stm32h563-tpm \ + build/tpm/guest0.bin build/tpm/guest1.bin BUILD_DIR=build/tpm \ + WT_GUEST0_FLASH_ORIGIN=0x080A0000 WT_GUEST1_FLASH_ORIGIN=0x080E0000 \ + WT_GUEST0_RAM_BASE=0x20000000 WT_GUEST1_RAM_BASE=0x20010000 \ + WT_GUEST_RAM_SIZE=0x00010000 + } >> "$LOGFILE" 2>&1 else { make -C tests/firmware/zephyr-stm32h5 clone @@ -835,6 +850,53 @@ if [ "$mode" != "build" ]; then expect "guest0 receives guest1's mediated echo reply" \ "ping reply from 10.0.0.2 seq=1" ;; + tpm) + # Mediated firmware TPM on silicon: the stock wolfTPM application in + # guest0 completes a TPM 2.0 session (Startup, GetRandom, CreatePrimary, + # Sign, Verify, PCR) through SERVICE_TPM psa_call to wolfTPM's fwTPM in + # isolated guest1. No emulator BKPT on hardware; the capture window plus + # the needles are the gate. + refute_re "no fault markers on silicon" \ + '^(\[MEMFAULT\]|\[HARDFLT\]|HardFault|SecureFault)' + expect "guest0 alive" "tpm-guest0: alive" + expect "fwTPM ready in guest1" "ftpm-guest1: ready" + expect "TPM startup crosses SERVICE_TPM" "tpm-guest0: tpm init ok" + expect "fwTPM capabilities match the relay frame limit" \ + "tpm-guest0: tpm frame caps ok" + expect "valid TPM command reaches the advertised frame limit" \ + "tpm-guest0: tpm max frame ok" + expect "ECDSA sign inside the fwTPM" "tpm-guest0: tpm sign ok" + expect "full mediated TPM round trip" "tpm-guest0: tpm round trip ok" + ;; + tpmneg) + # Four contained Secure-Thread faults prove the TPMDATA and peer-guest + # boundaries. The live transaction afterward proves restart recovery. + refute_re "isolation faults did not escalate to HardFault" \ + '^(\[HARDFLT\]|HardFault|SecureFault)' + zeroed=$(read_secure_u32 g_wt_boot_zeroed_bytes) + if [ -n "$zeroed" ] && [ $((0x$zeroed)) -ge $((0x2A800)) ]; then + check_pass "cold boot scrubbed the TPM stack and band (0x$zeroed bytes total)" + else + check_fail "TPM stack/band cold-boot scrub" \ + "zeroed-bytes latch 0x${zeroed:-none}, want at least 0x2A800" + fi + fault_cnt=$(read_secure_u32 g_tasklet_fault_count) + if [ -n "$fault_cnt" ] && [ $((0x$fault_cnt)) -ge 4 ]; then + check_pass "TPM isolation probes faulted and were contained (count=0x$fault_cnt)" + else + check_fail "TPM isolation faults" \ + "SP fault count 0x${fault_cnt:-none}, want >= 4" + fi + expect "application guest alive after the quarantines" \ + "tpm-guest0: alive" + expect "fwTPM ready after the quarantines" "ftpm-guest1: ready" + expect "fwTPM capabilities match the relay frame limit" \ + "tpm-guest0: tpm frame caps ok" + expect "valid TPM command reaches the advertised frame limit" \ + "tpm-guest0: tpm max frame ok" + expect "mediated TPM completes after every restart" \ + "tpm-guest0: tpm round trip ok" + ;; vnetneg) # Confined SERVICE_VNET isolation on silicon (WT-FFM-0011/0056): the # unprivileged vnet SP reads SPM RAM then executes from its XN data diff --git a/tests/target/run_m33mu_scenario.sh b/tests/target/run_m33mu_scenario.sh index 4a3878dfd..61cf14133 100755 --- a/tests/target/run_m33mu_scenario.sh +++ b/tests/target/run_m33mu_scenario.sh @@ -68,6 +68,18 @@ family="$scenario" case "$scenario" in bandneg[1-6]) family=bandneg; band_probe="${scenario#bandneg}" ;; restartneg[1-3]) family=restartneg ;; + pkcs11) + family=pkcs11 + pkcs11_suite=all + ;; + pkcs11core|pkcs11objects|pkcs11crypto|pkcs11negative) + family=pkcs11 + pkcs11_suite="${scenario#pkcs11}" + ;; + pkcs11neg) + family=pkcs11 + pkcs11_suite=core + ;; esac repo="$(cd "$(dirname "$0")/../.." && pwd)" @@ -86,6 +98,16 @@ git config --global --add safe.directory "$repo" # Crypto engine under test: hsm (wolfHSM server) or native (direct wolfCrypt). # Flows into the secure image build and both guest builds. . "$repo/tests/target/lib/engine.sh" +WT_INTERFACE_OS="${WT_INTERFACE_OS:-freertos}" +case "$WT_INTERFACE_OS" in + zephyr|freertos) ;; + *) echo "WT_INTERFACE_OS must be zephyr or freertos" >&2; exit 2 ;; +esac +export WT_INTERFACE_OS +export WT_CRYPTO_PROFILE="${WT_CRYPTO_PROFILE:-full}" +if [ "$family" = pkcs11 ] && [ "$WT_CRYPTO_PROFILE" = default ]; then + pkcs11_suite=profile +fi export CROSS_COMPILE=/usr/local/bin/arm-none-eabi- export ZEPHYR_TOOLCHAIN_VARIANT=cross-compile export WT_SECURE_FLASH_BASE=0x0C060000 @@ -99,11 +121,22 @@ export WT_GUEST0_FLASH_BASE=0x080A0000 # the bank-1/bank-2 watermark boundary (0x08100000). export WT_GUEST1_FLASH_BASE=0x080E0000 export WT_GUEST0_FLASH_SIZE=0x00040000 +export WT_GUEST1_FLASH_SIZE=0x00020000 export WT_GUEST_RAM_SIZE=0x00010000 export WT_GUEST1_RAM_BASE=0x20010000 export WT_ZEPHYR_DTC_OVERLAY_FILE=boards/wolfboot-stm32h563.overlay export WT_MAX_GUESTS=2 export ZEPHYR_BOARD=nucleo_h563zi/stm32h563xx/ns +if [ "$scenario" = tpm ] || [ "$scenario" = tpmneg ] || [ "$family" = pkcs11 ]; then + export WT_MAX_GUESTS=1 + export WT_PKCS11_TEST_PBKDF2_ITERATIONS=25 +fi +if [ "$family" = "pkcs11" ]; then + # Match the optional PKCS11 manifest's application window. + export WT_GUEST0_FLASH_SIZE=0x00020000 + export WT_GUEST1_FLASH_BASE=0x080C0000 + export WT_GUEST1_FLASH_SIZE=0x00040000 +fi WOLFBOOT_REF=d85fa9dbdf6c36f47b7e96eba5c9df750ad3c963 M33MU_REF=f3c03675260264cdec815adebe4b020bb6fe57b8 @@ -127,23 +160,43 @@ else cd "$repo" fi +# The production port reads DBGMCU_IDCODE before launching either guest. +# M33MU supplies that register from this environment variable: ordinary +# scenarios model supported revision X, while revneg overrides it below with +# engineering-sample revision Z and must halt in the production guard. +if ! grep -aq M33MU_STM32H5_IDCODE "$M33MU"; then + echo "FAIL: $M33MU lacks M33MU_STM32H5_IDCODE; rebuild M33MU at $M33MU_REF or later" >&2 + exit 1 +fi +export M33MU_STM32H5_IDCODE=0x10070484 + # --- wolfTrust dependencies + a clean secure build (drop any stale build/ # whose generated wolfhsm_cfg.h bakes in a host-absolute path). The guest # CMake cache must go too: scenarios configure guest0_psa with different # -D sets and CMake refuses to regenerate over a conflicting cache. --- -git submodule update --init --single-branch +git submodule update --init --single-branch --depth 1 rm -rf build tests/firmware/zephyr-stm32h5/build # --- Secure-app wolfBoot first stage. --- -rm -rf wolfBoot -git clone --no-checkout https://github.com/aidangarske/wolfBoot.git wolfBoot +if [ -d wolfBoot/.git ] && + [ "$(git -C wolfBoot rev-parse HEAD 2>/dev/null)" = "$WOLFBOOT_REF" ] && + [ -z "$(git -C wolfBoot status --porcelain --untracked-files=no \ + --ignore-submodules=none 2>/dev/null)" ]; then + echo "Reusing wolfBoot at $WOLFBOOT_REF" +else + rm -rf wolfBoot + git clone --no-checkout https://github.com/aidangarske/wolfBoot.git wolfBoot + git -C wolfBoot -c protocol.version=2 fetch --depth 1 origin "$WOLFBOOT_REF" + git -C wolfBoot checkout --detach "$WOLFBOOT_REF" +fi cd wolfBoot -git -c protocol.version=2 fetch --depth 1 origin "$WOLFBOOT_REF" -git checkout --detach "$WOLFBOOT_REF" -git submodule update --init --single-branch +git submodule update --init --single-branch --depth 1 cp "$repo/wolfBoot/config/examples/stm32h5-tz-wolftrust.config" .config # Build keytools serially first: a parallel keytools link races on the shared -# sp_* objects and intermittently fails "undefined reference". +# sp_* objects and intermittently fails "undefined reference". Clean first +# because its dependency files contain absolute paths and the same checkout is +# commonly mounted at different paths by local and CI containers. +make -C tools/keytools clean make keytools # Force -j1 for wolfboot.bin: wolfBoot FORCE-regenerates include/target.h in # place and its objects don't depend on it, so any -j (including a jobserver @@ -164,8 +217,8 @@ env $secure_flags make build/wolftrust.bin build/secure_cmse_implib.o # build/wolftrust.elf (poisoning post-mortem symbolization), and signing now # happens only after the guest digests are stamped into the measurement slot # (patch-then-sign, WT-FFM-0049), which needs the final guest binaries. -cp "$repo/build/wolftrust.bin" "$repo/build/wolftrust-unsigned.bin" -cp "$repo/build/wolftrust.elf" "$repo/build/wolftrust-signed.elf" +cp -f "$repo/build/wolftrust.bin" "$repo/build/wolftrust-unsigned.bin" +cp -f "$repo/build/wolftrust.elf" "$repo/build/wolftrust-signed.elf" # --- Guests. The restart scenario injects a Non-secure fault probe in the PSA # guest; the others build it unmodified. --- @@ -196,19 +249,64 @@ elif [ "$scenario" = "periphneg" ]; then guest_flags="WT_PERIPH_NEG_PROBE=1" fi -# Guest images per scenario: the vnet scenario swaps the Zephyr/FreeRTOS pair -# for the bare-metal wolfIP guests, relinked into the standard NS windows. +# Guest images per scenario: TPM pairs a Zephyr or FreeRTOS wolfTPM +# application with fwTPM. PKCS11 pairs the selected OS's C_* proxy with an +# independent wolfPKCS11 provider. g0_img="$repo/tests/firmware/zephyr-stm32h5/build/guest0_psa/zephyr/zephyr.bin" g1_img="$repo/tests/firmware/zephyr-stm32h5/build/freertos_guest1/freertos_guest1.bin" if [ "$scenario" = "vnet" ] || [ "$scenario" = "vnetneg" ]; then rm -rf tests/firmware/stm32h563-vnet/build make -C tests/firmware/stm32h563-vnet build/guest0.bin build/guest1.bin \ - WT_GUEST0_FLASH_ORIGIN=0x080A0000 WT_GUEST1_FLASH_ORIGIN=0x080E0000 \ - WT_GUEST0_RAM_BASE=0x20000000 WT_GUEST1_RAM_BASE=0x20010000 \ + WT_GUEST0_FLASH_ORIGIN=0x080A0000 \ + WT_GUEST0_RAM_BASE=0x20000000 \ WT_GUEST_RAM_SIZE=0x00010000 \ GUEST_EXTRA_CFLAGS="-DWT_VNET_EXIT_BKPT=1" g0_img="$repo/tests/firmware/stm32h563-vnet/build/guest0.bin" g1_img="$repo/tests/firmware/stm32h563-vnet/build/guest1.bin" +elif [ "$scenario" = "tpmneg" ]; then + rm -rf tests/firmware/stm32h563-tpm/build + make -C tests/firmware/stm32h563-tpm \ + build/tpm/guest0.bin \ + BUILD_DIR=build/tpm \ + WT_GUEST0_FLASH_ORIGIN=0x080A0000 \ + WT_GUEST0_RAM_BASE=0x20000000 \ + WT_GUEST_RAM_SIZE=0x00010000 \ + GUEST_EXTRA_CFLAGS="-DWT_TPM_EXIT_BKPT=1" + g0_img="$repo/tests/firmware/stm32h563-tpm/build/tpm/guest0.bin" + g1_img="" +elif [ "$scenario" = "tpm" ]; then + rm -rf tests/firmware/stm32h563-tpm/build + g1_img="" + if [ "$WT_INTERFACE_OS" = "zephyr" ]; then + # shellcheck disable=SC2086 + env $secure_flags WT_REUSE_SECURE_BUILD=1 WT_M33MU_EXPECT_BKPT=1 \ + make -C tests/firmware/zephyr-stm32h5 build-guest0-tpm + g0_img="$repo/tests/firmware/zephyr-stm32h5/build/guest0_tpm/zephyr/zephyr.bin" + else + make -C tests/firmware/stm32h563-tpm tpm-freertos-app \ + TPM_FREERTOS_BUILD_DIR=build/tpm-freertos \ + WT_GUEST0_FLASH_ORIGIN=0x080A0000 \ + WT_GUEST_FLASH_SIZE=0x00040000 \ + WT_GUEST0_RAM_BASE=0x20000000 WT_GUEST_RAM_SIZE=0x00010000 + g0_img="$repo/tests/firmware/stm32h563-tpm/build/tpm-freertos/guest0.bin" + fi +elif [ "$family" = "pkcs11" ]; then + rm -rf tests/firmware/stm32h563-tpm/build + g1_img="" + if [ "$WT_INTERFACE_OS" = "zephyr" ]; then + # shellcheck disable=SC2086 + env $secure_flags WT_REUSE_SECURE_BUILD=1 WT_M33MU_EXPECT_BKPT=1 \ + make -C tests/firmware/zephyr-stm32h5 \ + WT_PKCS11_SUITE="$pkcs11_suite" build-guest0-pkcs11 + g0_img="$repo/tests/firmware/zephyr-stm32h5/build/guest0_pkcs11/zephyr/zephyr.bin" + else + make -C tests/firmware/stm32h563-tpm pkcs11-freertos-app \ + PKCS11_BUILD_DIR="build/$scenario" \ + WT_PKCS11_SUITE="$pkcs11_suite" \ + WT_GUEST0_FLASH_ORIGIN=0x080A0000 \ + WT_GUEST0_RAM_BASE=0x20000000 WT_GUEST_RAM_SIZE=0x00010000 + g0_img="$repo/tests/firmware/stm32h563-tpm/build/$scenario/guest0.bin" + fi else make -C tests/firmware/zephyr-stm32h5 clone env $guest_flags $secure_flags WT_REUSE_SECURE_BUILD=1 WT_EXPECTED_LIFECYCLE=0x1000u \ @@ -218,16 +316,16 @@ fi echo "Guest vector tables (SP, reset PC):" od -An -tx4 -N8 "$g0_img" -od -An -tx4 -N8 "$g1_img" +if [ -n "$g1_img" ]; then od -An -tx4 -N8 "$g1_img"; fi # --- Pin the guest measurements into the secure image, then sign: the wolfBoot # signature covers the pins, extending the chain of trust to the guests. # The harness (not the guest) holds the expected wolfBoot measurement and # asserts the token's reported value below. --- -cp "$repo/build/wolftrust-unsigned.bin" "$repo/build/wolftrust.bin" -python3 tools/measure/patch_guest_digests.py "$repo/build/wolftrust.bin" \ - "0:${WT_GUEST0_VERSION:-1}:$g0_img" \ - "1:${WT_GUEST1_VERSION:-1}:$g1_img" +cp -f "$repo/build/wolftrust-unsigned.bin" "$repo/build/wolftrust.bin" +guest_pins=("0:${WT_GUEST0_VERSION:-1}:$g0_img") +if [ -n "$g1_img" ]; then guest_pins+=("1:${WT_GUEST1_VERSION:-1}:$g1_img"); fi +python3 tools/measure/patch_guest_digests.py "$repo/build/wolftrust.bin" "${guest_pins[@]}" IMAGE_HEADER_SIZE=1024 WOLFBOOT_PARTITION_SIZE=0x40000 WOLFBOOT_SECTOR_SIZE=0x2000 \ "$repo/wolfBoot/tools/keytools/sign" --ecc256 \ "$repo/build/wolftrust.bin" \ @@ -274,10 +372,22 @@ quit_flag="--quit-on-faults" expect_bkpt=0x7f timeout_s=60 if [ "$scenario" = "vnet" ] || [ "$scenario" = "vnetneg" ]; then - # wolfIP's initial ARP delay uses guest time, which can take longer than - # 60 wall-clock seconds to advance on a shared CI runner. + # wolfIP's initial ARP delay uses guest time, which can outlast 60 + # wall-clock seconds on a shared CI runner. timeout_s=180 fi +if [ "$scenario" = "tpm" ] || [ "$scenario" = "tpmneg" ] || \ + [ "$family" = "pkcs11" ]; then + # The TPM and PKCS11 scenarios run ECC and symmetric operations through + # isolated provider guests, which is slow under M33MU. + timeout_s=360 +fi +if [ "$scenario" = "pkcs11" ]; then + # The full suite includes real PBKDF2 and ECC work. FreeRTOS is slower than + # Zephyr under instruction emulation even though each focused suite fits the + # ordinary timeout. + timeout_s=1200 +fi if [ "$scenario" = "restart" ]; then quit_flag="" timeout_s=40 @@ -323,7 +433,9 @@ elif [ "$scenario" = "xnneg" ]; then quit_flag="" timeout_s=40 elif [ "$scenario" = "spfaultneg" ] || [ "$scenario" = "panicneg" ] || - [ "$scenario" = "vnetneg" ] || [ "$scenario" = "sealneg" ] || + [ "$scenario" = "vnetneg" ] || [ "$scenario" = "tpmneg" ] || + [ "$scenario" = "pkcs11neg" ] || + [ "$scenario" = "sealneg" ] || [ "$scenario" = "sealpivotneg" ] || [ "$scenario" = "svcneg" ] || [ "$scenario" = "busfaultneg" ] || [ "$family" = "bandneg" ] || [ "$family" = "restartneg" ]; then @@ -335,10 +447,6 @@ elif [ "$scenario" = "spfaultneg" ] || [ "$scenario" = "panicneg" ] || elif [ "$scenario" = "revneg" ]; then # Production image on revision Z silicon (ES0565 2.2.9): the emulator # reports the refused IDCODE through the real DBGMCU read. - if ! grep -aq M33MU_STM32H5_IDCODE "$M33MU"; then - echo "FAIL: $M33MU lacks M33MU_STM32H5_IDCODE; rebuild M33MU at $M33MU_REF or later" >&2 - exit 1 - fi export M33MU_STM32H5_IDCODE=0x10016484 elif [ "$scenario" = "authneg" ]; then # Guest0 is refused at launch so the BKPT scenario end never fires; the run @@ -373,11 +481,23 @@ elif [ "$scenario" = "bootupdate" ]; then fi log="$repo/ci-m33mu-$scenario.log" +g1_emu_offset=0xE0000 +if [ "$family" = "pkcs11" ]; then + g1_emu_offset=0xC0000 +fi +guest_images=("$g0_img:0xA0000") +if [ -n "$g1_img" ]; then guest_images+=("$g1_img:$g1_emu_offset"); fi +if [ "$scenario" = tpm ] || [ "$scenario" = tpmneg ] || [ "$family" = pkcs11 ]; then + if [ "$WT_MAX_GUESTS" != 1 ] || [ "${#guest_images[@]}" != 1 ] || \ + [ "${#guest_pins[@]}" != 1 ]; then + echo "FAIL: interface scenario requires exactly one authenticated application" >&2 + exit 1 + fi +fi set +e "$M33MU" "$repo/wolfBoot/wolfboot.bin" \ "$repo/build/wolftrust_v1_signed.bin:0x60000" \ - "$g0_img:0xA0000" \ - "$g1_img:0xE0000" \ + "${guest_images[@]}" \ ${update_img:+"$update_img"} \ --uart-stdout --expect-bkpt $expect_bkpt $quit_flag --timeout "$timeout_s" | tee "$log" emu_status=${PIPESTATUS[0]} @@ -529,6 +649,13 @@ case "$scenario" in "freertos_guest1: psa rng ok" expect "guest1 psa_hash_compute KAT (PSA API parity)" \ "freertos_guest1: psa hash ok" + expect "FreeRTOS: PSA P-256 key generation, signing and verification" \ + "freertos_guest1: psa ECC keygen/sign/verify ok" + if [ "$WT_CRYPTO_PROFILE" = default ]; then + expect_flat "Zephyr: disabled PSA API refused" "disabled PSA operation rejected" + expect "FreeRTOS: disabled PSA API refused" \ + "freertos_guest1: disabled psa operation rejected" + fi expect "[EXPECT BKPT] Success clean exit" "[EXPECT BKPT] Success" echo "PASS: target/$scenario" ;; @@ -554,7 +681,14 @@ case "$scenario" in "psa_hash_compute(SHA-256) KAT verified" expect "FreeRTOS: PSA psa_hash_compute(SHA-256) KAT" \ "freertos_guest1: psa hash ok" + expect "FreeRTOS: PSA P-256 key generation, signing and verification" \ + "freertos_guest1: psa ECC keygen/sign/verify ok" expect "[EXPECT BKPT] Success clean exit" "[EXPECT BKPT] Success" + if [ "$WT_CRYPTO_PROFILE" = default ]; then + expect_flat "Zephyr: disabled PSA API refused" "disabled PSA operation rejected" + expect "FreeRTOS: disabled PSA API refused" \ + "freertos_guest1: disabled psa operation rejected" + fi echo "PASS: target/bothpsa" ;; bothiso) @@ -949,6 +1083,94 @@ case "$scenario" in expect "[EXPECT BKPT] Success clean exit after the swap" "[EXPECT BKPT] Success" echo "PASS: target/bootupdate" ;; + tpm) + # Isolated firmware-TPM (wolfTPM fwTPM) in its own Non-secure partition + # (guest1), driven by a stock wolfTPM application (guest0) over the FF-M + # mediated door; the round trip ends before the BKPT. + refute_re "no fault markers in boot log" \ + '^(\[MEMFAULT\]|\[HARDFLT\]|HardFault|SecureFault)' + tpm_app="tpm-$WT_INTERFACE_OS" + expect "$WT_INTERFACE_OS TPM application guest alive" "$tpm_app: alive" + expect "fwTPM capabilities match the relay frame limit" \ + "$tpm_app: tpm frame caps ok" + expect "valid TPM command reaches the advertised frame limit" \ + "$tpm_app: tpm max frame ok" + if [ "$WT_CRYPTO_PROFILE" = default ]; then + expect_flat "TPM capabilities match the selected commands" \ + "$tpm_app: tpm command capabilities match profile" + expect_flat "disabled TPM command refused by the provider" \ + "$tpm_app: tpm disabled command rejected" + fi + expect_flat "wolfTPM round trip through the isolated fwTPM" \ + "$tpm_app: tpm round trip ok" + expect "[EXPECT BKPT] Success clean exit" "[EXPECT BKPT] Success" + echo "PASS: target/tpm" + ;; + tpmneg) + # The HSM partition is denied read and write access to TPMDATA. The TPM + # relay is denied direct access to the fwTPM guest RAM and execution from + # its own data band. Both partitions restart before the mediated TPM flow. + expect_n_re "TPMDATA read and write faulted on the HSM partition stack" 2 \ + '^\[MEMFAULT\] sp=0x3009[67]' + expect "TPM handler cannot read SPM private RAM" \ + "addr=0x30028000" + expect_re "TPM relay cannot execute from its XN TPMDATA band" \ + '^\[MEMFAULT\] pc=0x30065000 addr=0x30065000' + expect_n_re "both TPM relay faults used its own partition stack" 2 \ + '^\[MEMFAULT\] sp=0x3007[1-4]' + refute_re "isolation faults did not escalate" \ + '^(\[HARDFLT\]|HardFault|SecureFault)' + expect "application guest alive after the quarantines" \ + "tpm-guest0: alive" + expect "fwTPM capabilities match the relay frame limit" \ + "tpm-guest0: tpm frame caps ok" + expect "valid TPM command reaches the advertised frame limit" \ + "tpm-guest0: tpm max frame ok" + expect_flat "mediated TPM still completes after every restart" \ + "tpm-guest0: tpm round trip ok" + expect "[EXPECT BKPT] Success clean exit" "[EXPECT BKPT] Success" + echo "PASS: target/tpmneg" + ;; + pkcs11|pkcs11core|pkcs11objects|pkcs11crypto|pkcs11negative|pkcs11neg) + if [ "$scenario" = "pkcs11neg" ]; then + expect_n_re "PKCS11DATA read and write faulted on the HSM partition stack" 2 \ + '^\[MEMFAULT\] sp=0x3009[67]' + expect "PKCS11 handler cannot read SPM private RAM" \ + "addr=0x30028000" + expect_re "PKCS11 relay cannot execute from its XN data band" \ + '^\[MEMFAULT\] pc=0x30065000 addr=0x30065000' + expect_n_re "both PKCS11 relay faults used its own partition stack" 2 \ + '^\[MEMFAULT\] sp=0x3007[1-4]' + refute_re "isolation faults did not escalate" \ + '^(\[HARDFLT\]|HardFault|SecureFault)' + else + refute_re "no fault markers in boot log" \ + '^(\[MEMFAULT\]|\[HARDFLT\]|HardFault|SecureFault)' + fi + pkcs11_app="pkcs11-$WT_INTERFACE_OS" + expect "$WT_INTERFACE_OS PKCS11 application guest alive" \ + "$pkcs11_app: alive" + if [ "$WT_INTERFACE_OS" = "freertos" ]; then + expect "FreeRTOS scheduler ran the PKCS11 task" \ + "$pkcs11_app: scheduler started" + fi + expect "the selected PKCS11 suite completed" \ + "$pkcs11_app: suite $pkcs11_suite complete" + expect "the PKCS11 suite reported zero failures" \ + "$pkcs11_app: TOTAL FAILED: 0" + case "$pkcs11_suite" in + core) expected_passed=10 ;; + objects) expected_passed=8 ;; + crypto) expected_passed=10 ;; + negative) expected_passed=12 ;; + all) expected_passed=40 ;; + profile) expected_passed=9 ;; + esac + expect "the PKCS11 suite ran every planned case" \ + "$pkcs11_app: TOTAL PASSED: $expected_passed" + expect "[EXPECT BKPT] Success clean exit" "[EXPECT BKPT] Success" + echo "PASS: target/$scenario" + ;; vnet) # Mediated inter-guest networking (WT-FFM-0058): both bare-metal wolfIP # guests come up, guest0 sends an ICMP echo to guest1 through SERVICE_VNET diff --git a/tests/target/run_qemu_a_scenario.sh b/tests/target/run_qemu_a_scenario.sh index 6645039f6..2bafa8bb9 100755 --- a/tests/target/run_qemu_a_scenario.sh +++ b/tests/target/run_qemu_a_scenario.sh @@ -39,11 +39,12 @@ # # QEMU_TIMEOUT= (default 120) TOOLPREFIX (default aarch64-none-elf-) set -euo pipefail +export WT_CRYPTO_PROFILE="${WT_CRYPTO_PROFILE:-full}" scenario="${1:-}" case "$scenario" in - smoke|boot|boot-smp2|parkneg|rdistneg|tickneg|positive-secure|crossdomain|spfaultneg|tablesneg|proofneg|manifestneg|manifestneg2|manifestneg3|keystoreneg|bandneg1|bandneg2|bandneg3|bandneg4|bandneg5|bandneg6|periphspneg|restartneg1|restartneg2|restartneg3|spbudgetneg|panicneg|svcneg|fpneg|mspovfneg|xnneg|ffa-direct|ffa-sint|ns-smoke|ffa-discovery|ffa-guest-direct|psci|psci-el2|el2dirtyneg|ffa-preempt|positive|guest1|smcfuzz|secramneg|periphneg|resetneg|ffa-memneg|hsmattackneg|hsmpinneg|attestneg|vaultrecover|vaultrecoversec|confboot|storage|devstorage|devattest|devattestqcbor|devcrypto|ffaacs-discovery|ffaacs-direct|ffaacs-memory|ffaacs-notify|ffaacs-indirect|ffaacs-interrupts) ;; - *) echo "usage: $0 smoke|boot|boot-smp2|parkneg|rdistneg|tickneg|positive-secure|crossdomain|spfaultneg|tablesneg|proofneg|manifestneg|manifestneg2|manifestneg3|keystoreneg|bandneg1|bandneg2|bandneg3|bandneg4|bandneg5|bandneg6|periphspneg|restartneg1|restartneg2|restartneg3|spbudgetneg|panicneg|svcneg|fpneg|mspovfneg|xnneg|ffa-direct|ffa-sint|ns-smoke|ffa-discovery|ffa-guest-direct|psci|psci-el2|el2dirtyneg|ffa-preempt|positive|guest1|smcfuzz|secramneg|periphneg|resetneg|ffa-memneg|hsmattackneg|hsmpinneg|attestneg|vaultrecover|vaultrecoversec|confboot|storage|devstorage|devattest|devattestqcbor|devcrypto|ffaacs-discovery|ffaacs-direct|ffaacs-memory|ffaacs-notify|ffaacs-indirect|ffaacs-interrupts" >&2; exit 2 ;; + smoke|boot|boot-smp2|parkneg|rdistneg|tickneg|positive-secure|crossdomain|spfaultneg|tablesneg|proofneg|manifestneg|manifestneg2|manifestneg3|keystoreneg|bandneg1|bandneg2|bandneg3|bandneg4|bandneg5|bandneg6|periphspneg|restartneg1|restartneg2|restartneg3|spbudgetneg|panicneg|svcneg|fpneg|mspovfneg|xnneg|ffa-direct|ffa-sint|ns-smoke|ffa-discovery|ffa-guest-direct|psci|psci-el2|el2dirtyneg|ffa-preempt|positive|guest1|smcfuzz|secramneg|periphneg|resetneg|ffa-memneg|hsmattackneg|hsmpinneg|attestneg|vaultrecover|vaultrecoversec|confboot|storage|devstorage|devattest|devattestqcbor|devcrypto|tpm|tpmneg|pkcs11|pkcs11neg|ffaacs-discovery|ffaacs-direct|ffaacs-memory|ffaacs-notify|ffaacs-indirect|ffaacs-interrupts) ;; + *) echo "usage: $0 smoke|boot|boot-smp2|parkneg|rdistneg|tickneg|positive-secure|crossdomain|spfaultneg|tablesneg|proofneg|manifestneg|manifestneg2|manifestneg3|keystoreneg|bandneg1|bandneg2|bandneg3|bandneg4|bandneg5|bandneg6|periphspneg|restartneg1|restartneg2|restartneg3|spbudgetneg|panicneg|svcneg|fpneg|mspovfneg|xnneg|ffa-direct|ffa-sint|ns-smoke|ffa-discovery|ffa-guest-direct|psci|psci-el2|el2dirtyneg|ffa-preempt|positive|guest1|smcfuzz|secramneg|periphneg|resetneg|ffa-memneg|hsmattackneg|hsmpinneg|attestneg|vaultrecover|vaultrecoversec|confboot|storage|devstorage|devattest|devattestqcbor|devcrypto|tpm|tpmneg|pkcs11|pkcs11neg|ffaacs-discovery|ffaacs-direct|ffaacs-memory|ffaacs-notify|ffaacs-indirect|ffaacs-interrupts" >&2; exit 2 ;; esac # The Arm FF-A ACS runs one test group per scenario: the groups wolfTrust @@ -126,7 +127,7 @@ fi # write starts it: the smoke and boot run on core 0 alone and boot-smp2 skips. case "$scenario:$MACHINE" in smoke:virt) SMP="${SMP:-2}"; cpus="$SMP" ;; - boot:virt|positive-secure:virt|crossdomain:virt|spfaultneg:virt|tablesneg:virt|proofneg:virt|manifestneg:virt|manifestneg2:virt|manifestneg3:virt|keystoreneg:virt|bandneg[1-6]:virt|periphspneg:virt|restartneg1:virt|restartneg2:virt|restartneg3:virt|spbudgetneg:virt|panicneg:virt|svcneg:virt|fpneg:virt|mspovfneg:virt|xnneg:virt|ffa-direct:virt|ffa-sint:virt|ns-smoke:virt|ffa-discovery:virt|ffa-guest-direct:virt|psci-el2:virt|el2dirtyneg:virt|ffa-preempt:virt|positive:virt|smcfuzz:virt|secramneg:virt|periphneg:virt|ffa-memneg:virt|hsmattackneg:virt|hsmpinneg:virt|vaultrecoversec:virt) SMP="${SMP:-1}"; cpus="$SMP" ;; + boot:virt|positive-secure:virt|crossdomain:virt|spfaultneg:virt|tablesneg:virt|proofneg:virt|manifestneg:virt|manifestneg2:virt|manifestneg3:virt|keystoreneg:virt|bandneg[1-6]:virt|periphspneg:virt|restartneg1:virt|restartneg2:virt|restartneg3:virt|spbudgetneg:virt|panicneg:virt|svcneg:virt|fpneg:virt|mspovfneg:virt|xnneg:virt|ffa-direct:virt|ffa-sint:virt|ns-smoke:virt|ffa-discovery:virt|ffa-guest-direct:virt|psci-el2:virt|el2dirtyneg:virt|ffa-preempt:virt|positive:virt|smcfuzz:virt|secramneg:virt|periphneg:virt|ffa-memneg:virt|hsmattackneg:virt|hsmpinneg:virt|vaultrecoversec:virt|tpm:virt|tpmneg:virt|pkcs11:virt|pkcs11neg:virt) SMP="${SMP:-1}"; cpus="$SMP" ;; boot-smp2:virt) SMP=2; cpus=2 ;; # The Normal world probes PSCI with a real parked secondary beside it. psci:virt) SMP="${SMP:-2}"; cpus="$SMP" ;; @@ -204,6 +205,13 @@ else ffaacs-*) probe=(WT_EL3_NS_SMOKE=1 WT_FFA_ACS=1 WT_EL3_NS_EL2=1 WT_PSA_NS_WINDOW_SIZE=0x00200000 "WT_SPM_TABLE_POOL_PA=$acs_pool_pa" WT_SPM_TABLE_POOL_PAGES=512) ;; ffa-direct|ffa-sint) probe=(WT_EL3_TEST_DRIVER=1) ;; ns-smoke|ffa-discovery|psci|positive|smcfuzz|secramneg|periphneg|resetneg|ffa-memneg|storage|hsmattackneg) probe=(WT_EL3_NS_SMOKE=1) ;; + tpm) probe=(WT_EL3_NS_SMOKE=1 CONFIG_TPM=y WT_ATTEST_COSE=0 WT_PSA_NS_WINDOW_SIZE=0x00200000) ;; + tpmneg) probe=(WT_EL3_NS_SMOKE=1 CONFIG_TPM=y WT_TPM_NEG_PROBE=1 WT_ATTEST_COSE=0 WT_PSA_NS_WINDOW_SIZE=0x00200000) ;; + # The production PIN KDF remains at 600,000 rounds. Emulator coverage uses + # the same PBKDF2 path with a short work factor, as the M33MU PKCS11 test + # does, so the complete API suite remains practical in the CI matrix. + pkcs11) probe=(WT_EL3_NS_SMOKE=1 CONFIG_PKCS11=y WT_ATTEST_COSE=0 WT_PSA_NS_WINDOW_SIZE=0x00200000 WT_PKCS11_TEST_PBKDF2_ITERATIONS=25) ;; + pkcs11neg) probe=(WT_EL3_NS_SMOKE=1 CONFIG_PKCS11=y WT_PKCS11_NEG_PROBE=1 WT_ATTEST_COSE=0 WT_PSA_NS_WINDOW_SIZE=0x00200000 WT_PKCS11_TEST_PBKDF2_ITERATIONS=25) ;; hsmpinneg) probe=(WT_EL3_NS_SMOKE=1 WT_HSM_PIN_NEG_PROBE=1) ;; # The test handoff's unlocked lifecycle leaves the forced SECURED # lifecycle as the only thing that refuses the reformat. @@ -219,6 +227,9 @@ else # The Normal world starts at NS-EL2 and runs an AArch32 EL1 beneath it. psci-el2) probe=(WT_EL3_NS_SMOKE=1 WT_EL3_NS_EL2=1) ;; esac + if [ "$scenario" = pkcs11neg ] && [ "$WT_ENGINE" = hsm ]; then + probe+=(WT_PKCS11_HSM_RESTART_PROBE=1) + fi make ARCH=aarch64 TARGET="$target" TOOLPREFIX="$TOOLPREFIX" WT_GIC_VERSION="$GIC" \ WT_CPU="$CPU" WT_PORT_BOOT_CPUS="$cpus" BUILD_DIR="build-aarch64-$tag-$scenario" \ "${probe[@]}" @@ -238,6 +249,8 @@ else [ "$scenario" = ffa-memneg ] || [ "$scenario" = storage ] || \ [ "$scenario" = hsmattackneg ] || [ "$scenario" = vaultrecoversec ] || \ [ "$scenario" = hsmpinneg ] || \ + [ "$scenario" = tpm ] || [ "$scenario" = tpmneg ] || \ + [ "$scenario" = pkcs11 ] || [ "$scenario" = pkcs11neg ] || \ [ "$scenario" = vaultrecover ] || [ "$scenario" = attestneg ] || \ [ "$scenario" = confboot ] || [ "$scenario" = devstorage ] || \ [ "$scenario" = devattest ] || [ "$scenario" = devattestqcbor ] || [ "$scenario" = devcrypto ]; then @@ -269,6 +282,16 @@ else [ "$scenario" = ffa-memneg ] && ns_memneg=1 ns_storage=0 [ "$scenario" = storage ] && ns_storage=1 + ns_tpm=0 + [ "$scenario" = tpm ] && ns_tpm=1 + [ "$scenario" = tpmneg ] && ns_tpm=1 + ns_pkcs11=0 + [ "$scenario" = pkcs11 ] && ns_pkcs11=1 + [ "$scenario" = pkcs11neg ] && ns_pkcs11=1 + ns_pkcs11_restart=0 + if [ "$scenario" = pkcs11neg ] && [ "$WT_ENGINE" = hsm ]; then + ns_pkcs11_restart=1 + fi # confboot runs Arm's val NSPE from the payload against the conformance # image built above (its fetched upstream tree + generated test list). ns_conf=0 @@ -306,6 +329,8 @@ else WT_NS_GUEST_SECRAM="$ns_secram" WT_NS_SECURE_PROBE_PA="$ns_secure_probe" \ WT_NS_GUEST_RESET="$ns_reset" WT_NS_GUEST_MEMNEG="$ns_memneg" \ WT_NS_GUEST_STORAGE="$ns_storage" WT_RUN_CONFORMANCE="$ns_conf" \ + WT_NS_GUEST_TPM="$ns_tpm" WT_NS_GUEST_PKCS11="$ns_pkcs11" \ + WT_NS_PKCS11_HSM_RESTART_PROBE="$ns_pkcs11_restart" \ WT_ATTEST_CBOR="$ns_cbor" \ WT_CONF_SUITE="$ns_suite" WT_NS_ATTEST_NEG="$ns_attest_neg" \ WT_NS_CONF_UPSTREAM="$build/upstream/psa-arch-tests/api-tests" \ @@ -396,6 +421,8 @@ if [ "$scenario" = ns-smoke ] || [ "$scenario" = ffa-discovery ] || \ [ "$scenario" = ffa-memneg ] || [ "$scenario" = storage ] || \ [ "$scenario" = hsmattackneg ] || [ "$scenario" = vaultrecoversec ] || \ [ "$scenario" = hsmpinneg ] || [ "$scenario" = periphneg ] || \ + [ "$scenario" = tpm ] || [ "$scenario" = tpmneg ] || \ + [ "$scenario" = pkcs11 ] || [ "$scenario" = pkcs11neg ] || \ [ "$scenario" = vaultrecover ] || [ "$scenario" = attestneg ] || \ [ "$scenario" = confboot ] || [ "$scenario" = devstorage ] || \ [ "$scenario" = devattest ] || [ "$scenario" = devattestqcbor ] || [ "$scenario" = devcrypto ]; then @@ -1151,6 +1178,101 @@ case "$scenario" in expect "val returned to the payload" "[NS] conformance val_entry returned" expect "semihosting exit 0 reached QEMU" "[EXPECT EXIT] Success" ;; + tpm|tpmneg) + refute_re "no synchronous exception reached EL3" '^\[SYNC EL=1' + refute_re "no EL3 panic" '\[EL3\] panic' + refute_re "no SPMC panic" '\[SPM\] panic' + expect "the isolated TPM service initialized" "tpm-aarch64: tpm init ok" + expect "the application queried the TPM frame limits" "tpm-aarch64: tpm frame caps ok" + expect "the application completed TPM2 GetRandom" "tpm-aarch64: tpm random ok" + expect "the application completed TPM2 CreatePrimary" "tpm-aarch64: tpm primary ok" + expect "the application completed TPM2 Sign" "tpm-aarch64: tpm sign ok" + expect "the application verified the TPM signature" "tpm-aarch64: tpm verify ok" + if [ "$WT_CRYPTO_PROFILE" = default ]; then + expect "TPM capabilities match the selection" "tpm-aarch64: tpm command capabilities match profile" + expect "disabled TPM command refused" "tpm-aarch64: tpm disabled command rejected" + else + expect "the application extended and read a PCR" "tpm-aarch64: tpm pcr ok" + fi + expect "the direct TPM 2.0 application suite passed" "tpm-aarch64: tpm round trip ok" + expect "the Normal-world application completed" "[NS] tpm suite complete" + expect "TPM requests crossed the FF-A SPM endpoint" "[SPM] direct req from=0x0000 to=0x80fd" + if [ "$scenario" = tpmneg ]; then + interface_far=$(printf 'FAR=0x%016x' $((WT_SPM_INTERFACE_PA))) + spm_far=$(printf 'FAR=0x%016x' $((WT_SPM_RAM_PA))) + interface_xn_far=$(printf 'FAR=0x%016x' $((WT_SPM_INTERFACE_PA + 1))) + interface_data_faults=$(grep -c "^\[SYNC EL=0 EC=0x24 .*$interface_far" "$log" || true) + if [ "$interface_data_faults" -eq 2 ]; then + check_pass "an unrelated partition was denied TPM state on read and write" + else + check_fail "TPM state-band isolation" \ + "expected 2 S-EL0 data aborts at $interface_far, saw $interface_data_faults" + fi + expect "the TPM handler was denied SPM private RAM" \ + "[SYNC EL=0 EC=0x24 ISS=0x000000f $spm_far]" + expect "the TPM state band was execute-never" \ + "[SYNC EL=0 EC=0x22 ISS=0x0000000 $interface_xn_far]" + else + refute_re "no partition fault" '^\[SYNC EL=0' + fi + expect "semihosting exit 0 reached QEMU" "[EXPECT EXIT] Success" + ;; + pkcs11|pkcs11neg) + refute_re "no synchronous exception reached EL3" '^\[SYNC EL=1' + refute_re "no EL3 panic" '\[EL3\] panic' + refute_re "no SPMC panic" '\[SPM\] panic' + if [ "$WT_CRYPTO_PROFILE" = default ]; then + expect "the default PKCS11 operation suite passed" "pkcs11-aarch64: TOTAL PASSED: 9" + expect "the application exercised random generation" "pkcs11-aarch64: PASS random" + expect "the application exercised signing" "pkcs11-aarch64: PASS ECDSA" + expect "disabled proxy operation refused" "pkcs11-aarch64: PASS disabled operation rejected" + expect "forged disabled operation refused" "pkcs11-aarch64: PASS forged disabled operation rejected" + else + expect "the application exercised token and user login" "pkcs11-aarch64: PASS token and user login" + expect "the application exercised object storage" "pkcs11-aarch64: PASS object create and size" + expect "the application exercised random generation" "pkcs11-aarch64: PASS random" + expect "the application exercised symmetric crypto" "pkcs11-aarch64: PASS AES CTR" + expect "the application exercised signing" "pkcs11-aarch64: PASS ECDSA" + expect "the application exercised key agreement" "pkcs11-aarch64: PASS ECDH" + expect "all PKCS11 cases passed" "pkcs11-aarch64: TOTAL PASSED: 40" + fi + expect "the PKCS11 suite reported zero failures" "pkcs11-aarch64: TOTAL FAILED: 0" + refute_re "the application did not report a PKCS11 case failure" 'pkcs11-aarch64: FAIL' + expect "the Normal-world application completed" "[NS] pkcs11 suite complete" + expect "PKCS11 requests crossed the FF-A SPM endpoint" "[SPM] direct req from=0x0000 to=0x80fd" + if [ "$scenario" = pkcs11neg ]; then + interface_far=$(printf 'FAR=0x%016x' $((WT_SPM_INTERFACE_PA))) + spm_far=$(printf 'FAR=0x%016x' $((WT_SPM_RAM_PA))) + interface_xn_far=$(printf 'FAR=0x%016x' $((WT_SPM_INTERFACE_PA + 1))) + interface_data_faults=$(grep -c "^\[SYNC EL=0 EC=0x24 .*$interface_far" "$log" || true) + if [ "$interface_data_faults" -eq 2 ]; then + check_pass "an unrelated partition was denied PKCS11 state on read and write" + else + check_fail "PKCS11 state-band isolation" \ + "expected 2 S-EL0 data aborts at $interface_far, saw $interface_data_faults" + fi + expect "the PKCS11 handler was denied SPM private RAM" \ + "[SYNC EL=0 EC=0x24 ISS=0x000000f $spm_far]" + expect "the PKCS11 state band was execute-never" \ + "[SYNC EL=0 EC=0x22 ISS=0x0000000 $interface_xn_far]" + partition_faults=$(grep -c '^\[SYNC EL=0' "$log" || true) + expected_faults=4 + [ "$WT_ENGINE" = hsm ] && expected_faults=5 + if [ "$partition_faults" -eq "$expected_faults" ]; then + check_pass "all expected partition faults occurred" + else + check_fail "PKCS11 isolation fault count" \ + "expected $expected_faults deliberate S-EL0 faults, saw $partition_faults" + fi + if [ "$WT_ENGINE" = hsm ]; then + expect "the live PKCS11 client recovered after the HSM restart" \ + "[NS] pkcs11 recovered after HSM restart" + fi + else + refute_re "no partition fault" '^\[SYNC EL=0' + fi + expect "semihosting exit 0 reached QEMU" "[EXPECT EXIT] Success" + ;; storage) # SERVICE_ITS fronts the VAULT partition, so a guest ITS round trip is the # first request whose service calls a second partition: the SVC gate runs diff --git a/tests/target/run_suite.sh b/tests/target/run_suite.sh index 8ca9687cb..c85359d0e 100755 --- a/tests/target/run_suite.sh +++ b/tests/target/run_suite.sh @@ -7,6 +7,7 @@ # tests/target/run_suite.sh qemu-a ... -> run_qemu_a_scenario.sh # tests/target/run_suite.sh rt700-m33mu ... -> run_rt700_m33mu.sh # WT_SUITE_LOG_DIR where target-.log lands (default logs) +# WT_REQUIRE_SCENARIO_PASS=1 requires a real terminal PASS, including API CI. set -u usage() { echo "usage: $0 m33mu|qemu-a|rt700-m33mu ..." >&2; exit 2; } @@ -31,7 +32,12 @@ for s in "$@"; do echo "RUN: target/$s" if "tests/target/$runner" "$s" > "$logdir/target-$s.log" 2>&1; then grep -F ' [check] ' "$logdir/target-$s.log" || true - if grep -q '^SKIP:' "$logdir/target-$s.log"; then + if [ "${WT_REQUIRE_SCENARIO_PASS:-0}" = 1 ] && + ! grep -Eq "^PASS: (target|qemu-a)/$s( |$)" "$logdir/target-$s.log"; then + echo "FAIL: target/$s (required scenario did not report a real pass)" + tail -5 "$logdir/target-$s.log" + rc=1 + elif grep -q '^SKIP:' "$logdir/target-$s.log"; then echo "SKIP: target/$s ($(grep -m1 '^SKIP:' "$logdir/target-$s.log" | sed -E 's/^SKIP: [^:]*: //'))" else echo "PASS: target/$s" diff --git a/tools/aarch64_l3_layout.py b/tools/aarch64_l3_layout.py index 8ade00f96..dabf36604 100755 --- a/tools/aarch64_l3_layout.py +++ b/tools/aarch64_l3_layout.py @@ -10,6 +10,7 @@ import json import os import re +import shlex import subprocess import sys @@ -25,17 +26,19 @@ "WT_SPM_HSMDATA_PA", "WT_SPM_HSMDATA_SIZE", "WT_SPM_RXTX_PA", "WT_SPM_RXTX_SIZE", "WT_SPM_SHARE_PA", "WT_SPM_SHARE_SIZE", + "WT_SPM_INTERFACE_PA", "WT_SPM_INTERFACE_SIZE", "WT_L3_BAND_BASE", "WT_L3_HANDOFF_PA", "WT_L3_FFA_ACS_PA", "WT_L3_SPM_PERIPHERAL_BASE", ) # The C code reads these from -D flags; the rest only through memory_map.h. -CFLAGS_MACROS = MACROS[:20] +CFLAGS_MACROS = MACROS[:22] LDFLAGS_MACROS = ( "WT_SPM_IMAGE_PA", "WT_SPM_IMAGE_SIZE", "WT_SPM_RAM_PA", "WT_SPM_RAM_SIZE", "WT_SPM_KEYSTORE_PA", "WT_SPM_KEYSTORE_SIZE", "WT_SPM_VAULT_PA", "WT_SPM_VAULT_SIZE", "WT_SPM_ATTEST_PA", "WT_SPM_ATTEST_SIZE", "WT_SPM_HSMDATA_PA", "WT_SPM_HSMDATA_SIZE", "WT_SPM_CONFDATA_PA", "WT_SPM_CONFDATA_SIZE", + "WT_SPM_INTERFACE_PA", "WT_SPM_INTERFACE_SIZE", ) # Each band and the partition whose data wolftrust.ld links into it. KEYSTORE_BANDS = (("WT_SPM_VAULT_PA", "WT_SPM_VAULT_SIZE", "PARTITION_VAULT"), @@ -43,6 +46,7 @@ "PARTITION_ATTEST"), ("WT_SPM_HSMDATA_PA", "WT_SPM_HSMDATA_SIZE", "PARTITION_HSM")) +INTERFACE_PARTITIONS = ("PARTITION_TPM", "PARTITION_PKCS11") INTEGER = re.compile(r"\b(0[xX][0-9a-fA-F]+|[0-9]+)[uUlL]*\b") EXPRESSION = re.compile(r"^[0-9a-fA-FxX+\-*() ]+$") @@ -53,7 +57,10 @@ def evaluate(cc, header, defines, includes): os.path.dirname(os.path.abspath(header)), "l3_port.h") probe += "".join("%d=%s\n" % (index, name) for index, name in enumerate(MACROS)) - command = [cc, "-E", "-P", "-x", "c"] + command = shlex.split(cc) + if not command: + raise SystemExit("FAIL: --cc must name a compiler") + command += ["-E", "-P", "-x", "c"] command += ["-D%s" % define for define in defines] command += ["-I%s" % path for path in includes] result = subprocess.run(command + ["-"], input=probe, text=True, @@ -80,9 +87,8 @@ def evaluate(cc, header, defines, includes): def check_manifest(path, values): - """Each keystore band is granted exactly once, to the domain of the - partition linked into it, and nothing else is granted inside the - keystore window.""" + """Require exact ownership of the keystore and optional interface bands, + and reject any other manifest grants that overlap those bands.""" with open(path, encoding="utf-8") as handle: manifest = json.load(handle) window = (values["WT_SPM_KEYSTORE_PA"], @@ -106,6 +112,33 @@ def check_manifest(path, values): if grant not in bands: errors.append("grant 0x%X+0x%X to domain %s is not its keystore " "band" % grant) + interface_owners = [owners[name] for name in INTERFACE_PARTITIONS + if name in owners] + interface_grants = [] + interface_base = values["WT_SPM_INTERFACE_PA"] + interface_size = values["WT_SPM_INTERFACE_SIZE"] + interface_end = interface_base + interface_size + for domain in manifest.get("domains", ()): + for resource in domain.get("memory_resources", ()): + base, size = resource["base"], resource["size"] + if base < interface_end and base + size > interface_base: + interface_grants.append((base, size, domain["id"])) + if len(interface_owners) > 1: + errors.append("multiple partitions own the interface band: %s" % + interface_owners) + elif interface_owners: + expected = (interface_base, interface_size, interface_owners[0]) + if interface_grants.count(expected) != 1: + errors.append("interface band is granted %d times to domain %s" % + (interface_grants.count(expected), + interface_owners[0])) + for grant in interface_grants: + if grant != expected: + errors.append("grant 0x%X+0x%X to domain %s overlaps the " + "interface band" % grant) + elif interface_grants: + errors.append("default manifest has a grant overlapping the optional " + "interface band") if errors: raise SystemExit("FAIL: %s: %s" % (path, "; ".join(errors))) diff --git a/tools/check_secure_layout.py b/tools/check_secure_layout.py index df19f5146..c2363c1d9 100644 --- a/tools/check_secure_layout.py +++ b/tools/check_secure_layout.py @@ -21,6 +21,12 @@ HSM_LIMIT = 0x30089000 CONFDATA_ORIGIN = 0x30093000 CONFDATA_DATA_LIMIT = 0x30095C00 +# SERVICE_TPM mailbox band (CONFIG_TPM builds only). STM32H563 default; other +# ports pass their own with --tpm-band. +TPM_ORIGIN = 0x30095000 +TPM_LIMIT = 0x30095800 +PKCS11_ORIGIN = 0x30070000 +PKCS11_LIMIT = 0x30075000 REQUIRED_ENTRIES = ( "Reset_Handler", @@ -38,6 +44,7 @@ re.compile(pattern) for pattern in ( r"^always_prefetch(?:\.|$)", + r"^never_prefetch(?:\.|$)", r"^sha256DrbgDisabled(?:\.|$)", ) ) @@ -66,6 +73,10 @@ class Layout: bands: tuple = BANDS confdata_origin: int = CONFDATA_ORIGIN confdata_data_limit: int = CONFDATA_DATA_LIMIT + tpm_origin: int = TPM_ORIGIN + tpm_limit: int = TPM_LIMIT + pkcs11_origin: int = PKCS11_ORIGIN + pkcs11_limit: int = PKCS11_LIMIT wolfhal: bool = True @@ -79,10 +90,27 @@ class Layout: "attest": ("_s_attest", "_e_attest"), "hsm": ("_s_hsm", "_e_hsm"), "vnet": ("_s_vnet", "_e_vnet"), + "tpm": ("_s_tpm", "_e_tpm"), + "pkcs11": ("_s_pkcs11", "_e_pkcs11"), "conf": ("_sconfdata", "_econfbss"), "conf-server": ("_s_conf_server_data", "_e_conf_server_data"), "conf-driver": ("_s_conf_driver_data", "_e_conf_driver_data"), } +# AArch64 keeps the optional direct-application provider in one shared +# interface band. TPM and PKCS#11 are mutually exclusive build modes, so both +# owner classes intentionally resolve to the same linker bounds. The other +# partition bands retain their per-owner bounds while S-EL1 state stays in the +# private SPM RAM window. +AARCH64_OWNER_REGIONS = dict(OWNER_REGIONS, **{ + "spm": ("__data_start", "__spm_ram_end"), + "tpm": ("_s_interface", "_e_interface"), + "pkcs11": ("_s_interface", "_e_interface"), + "conf": ("_s_conf_data", "_e_conf_data"), +}) +OWNER_PROFILES = { + "armv8m": OWNER_REGIONS, + "aarch64": AARCH64_OWNER_REGIONS, +} SHARED_OWNER = "shared" SPM_OWNER = "spm" # The SPM also owns the secure stacks the linker script places by section. @@ -274,7 +302,8 @@ def parse_map(text): return regions, sections -def validate(symbols, production=False, layout=DEFAULT_LAYOUT): +def validate(symbols, production=False, layout=DEFAULT_LAYOUT, + require_tpm=False, require_pkcs11=False): errors = [] table = symbol_table(symbols) @@ -324,6 +353,52 @@ def address(name): if production and bounds["_sconfdata"] != bounds["_econfbss"]: errors.append("production image carries conformance data") + # SERVICE_TPM mailbox band (CONFIG_TPM builds only): when it carries state, + # hold it to the same fixed origin and containment the keystore bands get. + # Empty outside CONFIG_TPM; a linked relay object makes emptiness an error. + tpm_edges = { + name: table.get(name, ()) + for name in ("_s_tpm", "_e_tpm_data", "_s_tpm_bss", "_e_tpm") + } + if all(len(matches) == 1 for matches in tpm_edges.values()): + s_tpm = tpm_edges["_s_tpm"][0].address + e_tpm_data = tpm_edges["_e_tpm_data"][0].address + s_tpm_bss = tpm_edges["_s_tpm_bss"][0].address + e_tpm = tpm_edges["_e_tpm"][0].address + if require_tpm and e_tpm <= s_tpm: + errors.append("required tpm band is empty") + if e_tpm > s_tpm: + if s_tpm != layout.tpm_origin: + errors.append("tpm band origin is not 0x%08x" % + layout.tpm_origin) + if not (s_tpm <= e_tpm_data <= s_tpm_bss <= e_tpm <= + layout.tpm_limit): + errors.append("tpm state escapes its isolation band") + elif require_tpm: + errors.append("required tpm band markers are missing or duplicated") + + pkcs11_edges = { + name: table.get(name, ()) + for name in ("_s_pkcs11", "_e_pkcs11_data", "_s_pkcs11_bss", + "_e_pkcs11") + } + if all(len(matches) == 1 for matches in pkcs11_edges.values()): + s_pkcs11 = pkcs11_edges["_s_pkcs11"][0].address + e_pkcs11_data = pkcs11_edges["_e_pkcs11_data"][0].address + s_pkcs11_bss = pkcs11_edges["_s_pkcs11_bss"][0].address + e_pkcs11 = pkcs11_edges["_e_pkcs11"][0].address + if require_pkcs11 and e_pkcs11 <= s_pkcs11: + errors.append("required pkcs11 band is empty") + if e_pkcs11 > s_pkcs11: + if s_pkcs11 != layout.pkcs11_origin: + errors.append("pkcs11 band origin is not 0x%08x" % + layout.pkcs11_origin) + if not (s_pkcs11 <= e_pkcs11_data <= s_pkcs11_bss <= + e_pkcs11 <= layout.pkcs11_limit): + errors.append("pkcs11 state escapes its isolation band") + elif require_pkcs11: + errors.append("required pkcs11 band markers are missing or duplicated") + if (text_start is not None and text_end is not None and text_start >= text_end): errors.append("secure text range is empty or reversed") @@ -396,7 +471,7 @@ def address(name): def validate_ownership(symbols, regions, sections, owners, objects=(), - production=False): + production=False, owner_regions=OWNER_REGIONS): """Every writable input section must lie in its object's owner region.""" errors = [] table = symbol_table(symbols) @@ -454,7 +529,7 @@ def report(kind, name): continue if owner == SPM_OWNER and section.output in SPM_STACK_SECTIONS: continue - low, high = OWNER_REGIONS[owner] + low, high = owner_regions[owner] start = bound(low) stop = bound(high) if (start is None or stop is None or @@ -537,7 +612,8 @@ def check_lto_provenance(objects, owners_text, error=None, def check_elf(nm, elf, map_text=None, owners_text=None, objects=(), production=False, runner=None, output=None, error=None, - layout=DEFAULT_LAYOUT): + layout=DEFAULT_LAYOUT, ownership_only=False, + owner_regions=OWNER_REGIONS): if runner is None: runner = subprocess.run if output is None: @@ -556,19 +632,32 @@ def check_elf(nm, elf, map_text=None, owners_text=None, objects=(), return result.returncode symbols = parse_nm(result.stdout) - errors = validate(symbols, production, layout) + require_tpm = any( + Path(path).name.endswith("tpm_relay_service.o") for path in objects + ) + require_pkcs11 = any( + Path(path).name.endswith("pkcs11_relay_service.o") for path in objects + ) + errors = [] + if not ownership_only: + errors = validate(symbols, production, layout, require_tpm=require_tpm, + require_pkcs11=require_pkcs11) if map_text is not None: owners, owner_errors = parse_owners(owners_text or "") regions, sections = parse_map(map_text) errors.extend(owner_errors) - errors.extend(validate_ownership(symbols, regions, sections, owners, - objects, production)) + errors.extend(validate_ownership( + symbols, regions, sections, owners, objects, production, + owner_regions)) if errors: for message in errors: print("FAIL: %s" % message, file=error) return 1 - print("PASS: secure layout (WT-FFM-0010/0011)", file=output) + if ownership_only: + print("PASS: secure ownership (WT-FFM-0011)", file=output) + else: + print("PASS: secure layout (WT-FFM-0010/0011)", file=output) return 0 @@ -657,6 +746,10 @@ def self_test(): "30093060 ? _econfdata", "30093060 ? _sconfbss", "30093080 ? _econfbss", + "30095000 ? _s_tpm", + "30095004 ? _e_tpm_data", + "30095004 ? _s_tpm_bss", + "30095420 ? _e_tpm", "30028004 0000000c D g_whalTimeout", "30077840 00000100 B g_guests", "30028020 00000080 B g_co_stack_slots", @@ -709,6 +802,14 @@ def rendered(symbols): file=sys.stderr) return False + empty_tpm = good + for name in ("_e_tpm_data", "_s_tpm_bss", "_e_tpm"): + empty_tpm = changed(empty_tpm, name, 0x30095000) + errors = validate(empty_tpm, require_tpm=True) + if not any("required tpm band is empty" in message for message in errors): + print("self-test accepted a required empty TPM band", file=sys.stderr) + return False + default_handler = next( symbol for symbol in good if symbol.name == "default_handler" ) @@ -741,6 +842,10 @@ def rendered(symbols): "conformance data origin is not"), ("conformance limit", changed(good, "_econfbss", 0x30095C04), "conformance state escapes its isolation band"), + ("tpm origin", changed(good, "_s_tpm", 0x30095100), + "tpm band origin is not"), + ("tpm limit", changed(good, "_e_tpm", 0x30096000), + "tpm state escapes its isolation band"), ("empty text", changed(good, "_e_secure_text", 0x0C000800), "secure text range is empty or reversed"), ("missing entry", removed(good, "SVC_Handler"), @@ -785,6 +890,9 @@ def rendered(symbols): ("writable DRBG flag", list(good) + [ Symbol(0x30028200, 4, "b", "sha256DrbgDisabled") ], "shared library state is not read-only"), + ("writable CCM prefetch anchor", list(good) + [ + Symbol(0x30028200, 4, "D", "never_prefetch") + ], "shared library state is not read-only"), ) + heap_cases + ( ("missing timeout", removed(good, "g_whalTimeout"), "expected one g_whalTimeout symbol, found 0"), @@ -1021,6 +1129,47 @@ def moved(name, address=None, origin=None, size=None, output=None): (label, ", ".join(errors)), file=sys.stderr) return False + aarch_symbols = list(good) + parse_nm("\n".join(( + "40000000 ? __data_start", + "40008000 ? __spm_ram_end", + "40010000 ? _s_interface", + "40011000 ? _e_interface", + "40020000 ? _s_conf_data", + "40021000 ? _e_conf_data", + ))) + aarch_regions = [ + Region("RAM", 0x40000000, 0x8000, True), + Region("INTERFACEDATA", 0x40010000, 0x1000, True), + Region("CONFDATA", 0x40020000, 0x1000, True), + ] + aarch_sections = [ + Section(".data", ".data.spm", 0x40000000, 4, + "build/sec_monitor.o"), + Section(".interface_bss", ".bss.tpm", 0x40010000, 4, + "build/wt_sec_tpm_local_provider.o"), + ] + aarch_owners_text = (SELF_TEST_OWNERS + + "wt_sec_tpm_local_provider.o tpm\n") + aarch_owners, owner_errors = parse_owners(aarch_owners_text) + errors = validate_ownership( + aarch_symbols, aarch_regions, aarch_sections, aarch_owners, + owner_regions=AARCH64_OWNER_REGIONS) + if owner_errors or errors: + print("self-test valid AArch64 ownership rejected: %s" % + ", ".join(owner_errors + errors), file=sys.stderr) + return False + misplaced = [aarch_sections[0], Section( + ".bss", ".bss.tpm", 0x40000004, 4, + "build/wt_sec_tpm_local_provider.o")] + errors = validate_ownership( + aarch_symbols, aarch_regions, misplaced, aarch_owners, + owner_regions=AARCH64_OWNER_REGIONS) + if not any("tpm object places writable state outside its region" in error + for error in errors): + print("self-test accepted AArch64 TPM state in SPM RAM", + file=sys.stderr) + return False + regions, sections = parse_map(render_map(placed)) errors = validate_ownership( good, regions, sections, owners, @@ -1065,6 +1214,17 @@ def run(args, **_kwargs): print("self-test invalid CLI path did not fail", file=sys.stderr) return False + error = io.StringIO() + if (check_elf( + "fake-nm", Path("empty-tpm.elf"), + objects=[Path("build/wt_sec_tpm_relay_service.o")], + runner=completed(0, rendered(empty_tpm)), + output=io.StringIO(), error=error) != 1 or + "required tpm band is empty" not in error.getvalue()): + print("self-test required TPM object did not reject an empty band", + file=sys.stderr) + return False + error = io.StringIO() if (check_elf("fake-nm", Path("invalid.elf"), map_text=render_map(placed), owners_text="a.o nobody\n", @@ -1165,14 +1325,20 @@ def named_band(text): return name, low, high -def port_layout(bands, confdata, wolfhal): +def port_layout(bands, confdata, wolfhal, tpm_band=None, pkcs11_band=None): """Every port declares all three bands or none (the STM32H563 default).""" if bands and sorted(name for name, _, _ in bands) != sorted(BAND_NAMES): raise argparse.ArgumentTypeError( "--band must name each of %s exactly once" % "/".join(BAND_NAMES)) ordered = tuple(sorted(bands, key=lambda b: BAND_NAMES.index(b[0]))) if bands else BANDS + tpm_origin = tpm_band[0] if tpm_band else TPM_ORIGIN + tpm_limit = tpm_band[1] if tpm_band else TPM_LIMIT + pkcs11_origin = pkcs11_band[0] if pkcs11_band else PKCS11_ORIGIN + pkcs11_limit = pkcs11_band[1] if pkcs11_band else PKCS11_LIMIT return Layout(bands=ordered, confdata_origin=confdata[0], - confdata_data_limit=confdata[1], wolfhal=wolfhal) + confdata_data_limit=confdata[1], tpm_origin=tpm_origin, + tpm_limit=tpm_limit, pkcs11_origin=pkcs11_origin, + pkcs11_limit=pkcs11_limit, wolfhal=wolfhal) def main(): @@ -1185,10 +1351,21 @@ def main(): parser.add_argument("--map", type=Path) parser.add_argument("--owners", type=Path) parser.add_argument("--production", action="store_true") + parser.add_argument("--ownership-only", action="store_true", + help="check object ownership without Armv8-M symbols") + parser.add_argument("--owner-profile", choices=OWNER_PROFILES, + default="armv8m", + help="linker bounds used for writable-state owners") parser.add_argument("--band", type=named_band, action="append", default=[], metavar="NAME=ORIGIN:LIMIT") parser.add_argument("--confdata", type=band, metavar="ORIGIN:DATA_LIMIT", default=(CONFDATA_ORIGIN, CONFDATA_DATA_LIMIT)) + parser.add_argument("--tpm-band", type=band, metavar="ORIGIN:LIMIT", + default=None, dest="tpm_band", + help="SERVICE_TPM mailbox band bounds for this port") + parser.add_argument("--pkcs11-band", type=band, metavar="ORIGIN:LIMIT", + default=None, dest="pkcs11_band", + help="SERVICE_PKCS11 relay band bounds for this port") parser.add_argument("--no-wolfhal", action="store_true", help="the port links no wolfHAL, so no g_whalTimeout") args = parser.parse_args() @@ -1200,7 +1377,8 @@ def main(): if args.map is None or args.owners is None: parser.error("--map and --owners are required to check an ELF") try: - layout = port_layout(args.band, args.confdata, not args.no_wolfhal) + layout = port_layout(args.band, args.confdata, not args.no_wolfhal, + args.tpm_band, args.pkcs11_band) except argparse.ArgumentTypeError as exc: parser.error(str(exc)) @@ -1216,7 +1394,8 @@ def main(): map_text=args.map.read_text(encoding="utf-8", errors="replace"), owners_text=args.owners.read_text(encoding="utf-8"), objects=args.objects, production=args.production, - layout=layout) + layout=layout, ownership_only=args.ownership_only, + owner_regions=OWNER_PROFILES[args.owner_profile]) if __name__ == "__main__": diff --git a/tools/cmake/CryptoProfile.cmake b/tools/cmake/CryptoProfile.cmake new file mode 100644 index 000000000..b412b2c22 --- /dev/null +++ b/tools/cmake/CryptoProfile.cmake @@ -0,0 +1,44 @@ +# CryptoProfile.cmake +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +function(wt_crypto_profile root output) + if(NOT DEFINED WT_CRYPTO_PROFILE) + if(DEFINED ENV{WT_CRYPTO_PROFILE}) + set(WT_CRYPTO_PROFILE "$ENV{WT_CRYPTO_PROFILE}") + else() + set(WT_CRYPTO_PROFILE default) + endif() + endif() + set(WT_CRYPTO_PROFILE "${WT_CRYPTO_PROFILE}" PARENT_SCOPE) + get_filename_component(root "${root}" ABSOLUTE) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + execute_process( + COMMAND ${Python3_EXECUTABLE} ${root}/tools/crypto_profile.py + --root ${root} --output ${output} --profile ${WT_CRYPTO_PROFILE} + RESULT_VARIABLE result) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Could not generate the crypto operation profile") + endif() + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + ${root}/tools/crypto_profile.py ${root}/config/crypto-profiles.json) + if(DEFINED ENV{WT_CRYPTO_CONFIG}) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "$ENV{WT_CRYPTO_CONFIG}") + endif() +endfunction() diff --git a/tools/crypto_profile.py b/tools/crypto_profile.py new file mode 100644 index 000000000..719671cb9 --- /dev/null +++ b/tools/crypto_profile.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python3 +# crypto_profile.py +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +"""Generate build-only operation selections against the pinned dependencies.""" + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re + + +KINDS = ("psa", "pkcs11", "tpm") +TPM_TABLE = "static const FWTPM_CMD_ENTRY fwCmdTable[] = {" + + +def write_file(path, text): + if not path.exists() or path.read_text() != text: + path.write_text(text) + + +def masked_c(source): + """Keep offsets while hiding braces in comments and string literals.""" + token = r'/\*.*?\*/|//[^\n]*|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'' + return re.sub(token, lambda m: re.sub(r"[^\n]", " ", m.group()), + source, flags=re.S) + + +def definitions(source, pattern): + masked = masked_c(source) + for match in re.finditer(pattern, masked): + depth = 1 + end = match.end() + while end < len(masked) and depth: + depth += (masked[end] == "{") - (masked[end] == "}") + end += 1 + if depth: + raise ValueError("unbalanced function definition") + yield match, end + + +def tpm_table(source): + if source.count(TPM_TABLE) != 1: + raise ValueError("pinned fwTPM command-table format changed") + start = source.index(TPM_TABLE) + len(TPM_TABLE) + end = source.index("\n};", start) + body = source[start:end] + rows = list(re.finditer( + r"(?m)^[ \t]*\{\s*TPM_CC_(\w+)\s*,[^\n]*\},[ \t]*$", body)) + if len(rows) != len(re.findall(r"\{\s*TPM_CC_", masked_c(body))): + raise ValueError("unrecognized fwTPM command descriptor") + if not rows: + raise ValueError("empty fwTPM command catalog") + return start, end, rows + + +def discover(root): + proxy = (root / "src/client/pkcs11_proxy.c").read_text() + signatures = {} + supported = set() + for match, end in definitions(proxy, + r"static\s+CK_RV\s+proxy_(C_\w+)\s*\(([^;{}]*?)\)\s*\{"): + signatures[match.group(1)] = proxy[match.start(2):match.end(2)] + if "proxy_unsupported(" not in proxy[match.end():end]: + supported.add(match.group(1)) + if not signatures: + raise ValueError("PKCS11 proxy catalog is empty") + psa = root / "lib/wolfPSA" + headers = "\n".join(p.read_text() for p in + (psa / "wolfpsa/psa").rglob("*.h")) + public = set(re.findall(r"\bpsa_status_t\s+(psa_\w+)\s*\(", headers)) + sources = {p.name: p.read_text() for p in (psa / "src").glob("*.c") + if p.name not in ("psa_store_posix.c", "psa_attestation.c")} + implemented = set() + for source in sources.values(): + implemented.update(m.group(1) for m, _ in definitions(source, + r"\bpsa_status_t\s+(psa_\w+)\s*\([^;{}]*?\)\s*\{")) + commands = (root / "lib/wolfTPM/src/fwtpm/fwtpm_command.c").read_text() + _, _, rows = tpm_table(commands) + return ({"psa": public & implemented, "pkcs11": supported, + "tpm": {m.group(1) for m in rows}}, signatures, sources, commands) + + +def select(catalog, defaults, profile, custom, enable, disable): + if not isinstance(custom, dict): + raise ValueError("profile configuration must be a JSON object") + for kind, names in custom.items(): + if not isinstance(names, list) or any(not isinstance(n, str) for n in names): + raise ValueError("profile field %s must be a list of names" % kind) + selected = {kind: set(catalog[kind] if profile == "full" else + defaults[kind]) for kind in KINDS} + mechanisms = None if profile == "full" else defaults["mechanisms"] + for kind, names in custom.items(): + if kind == "mechanisms": + mechanisms = names + elif kind in KINDS: + selected[kind] = set(names) + else: + raise ValueError("unknown profile field: " + kind) + for values, enabled in ((enable, True), (disable, False)): + for value in values: + kind, separator, name = value.partition(":") + if not separator or kind not in KINDS or name not in catalog[kind]: + raise ValueError("unknown operation: " + value) + if enabled: + selected[kind].add(name) + else: + selected[kind].discard(name) + for kind in KINDS: + unknown = selected[kind] - catalog[kind] + if unknown: + raise ValueError("unknown %s operations: %s" % + (kind, ", ".join(sorted(unknown)))) + if not selected["tpm"]: + raise ValueError("a TPM profile requires at least one command") + if mechanisms is not None and any(not re.fullmatch(r"CKM_[A-Z0-9_]+", n) + for n in mechanisms): + raise ValueError("invalid PKCS11 mechanism name") + return selected, mechanisms + + +def psa_wrappers(root, output, sources, enabled, public): + signatures = {} + pattern = r"\bpsa_status_t\s+(psa_\w+)\s*\(([^;{}]*?)\)\s*\{" + for source in sources.values(): + for match, _ in definitions(source, pattern): + if match.group(1) in public: + signatures.setdefault(match.group(1), + source[match.start(2):match.end(2)]) + renames = ["/* Private implementation names preserve internal dependencies. */"] + renames.extend("#define %s wt_profile_real_%s" % (name, name) + for name in sorted(public)) + write_file(output / "private_psa.h", "\n".join(renames) + "\n") + psa_output = output / "psa" + psa_output.mkdir(exist_ok=True) + for name in sources: + original = (root / "lib/wolfPSA/src" / name).resolve() + write_file(psa_output / name, + '#include "../private_psa.h"\n#include %s\n' % + json.dumps(str(original))) + adapters = ['/* Generated public entry points for the selected profile. */', + '#include ', '#include '] + for name, parameters in sorted(signatures.items()): + args = arguments(masked_c(parameters)) + if name in enabled: + adapters.append("extern psa_status_t wt_profile_real_%s(%s);" % + (name, parameters)) + adapters.append("psa_status_t %s(%s)\n{" % (name, parameters)) + if name in enabled: + adapters.append(" return wt_profile_real_%s(%s);" % + (name, ", ".join(args))) + else: + adapters.extend(" (void)%s;" % arg for arg in args) + adapters.append(" return PSA_ERROR_NOT_SUPPORTED;") + adapters.append("}") + write_file(psa_output / "profile_api.c", "\n".join(adapters) + "\n") + + +def selected_tpm(source, enabled, full=False): + start, end, rows = tpm_table(source) + body = source[start:end] + for row in reversed(rows): + if row.group(1) not in enabled: + body = body[:row.start()] + body[row.end():] + result = source[:start] + body + source[end:] + if not full: + # A selected command hidden by dependency guards must fail the build. + check = ("\ntypedef char wt_selected_tpm_commands_available[\n" + " (sizeof(fwCmdTable) / sizeof(fwCmdTable[0]) == %dU) ? 1 : -1];\n" + % len(enabled)) + result = result[:start + len(body) + 3] + check + result[start + len(body) + 3:] + return result + + +def opcode(name): + name = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", name[2:]) + return "WT_P11_" + re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name).upper() + + +def arguments(parameters): + if parameters.strip() == "void": + return [] + result = [] + for parameter in parameters.split(","): + match = re.search(r"([A-Za-z_]\w*)\s*(?:\[[^]]*\])?\s*$", parameter) + if match is None: + raise ValueError("unrecognized PKCS11 parameter: " + parameter) + result.append(match.group(1)) + return result + + +def pkcs11_header(signatures, enabled, mechanisms): + lines = ["/* Generated operation selection. */", "#ifndef WT_PKCS11_PROFILE_H", + "#define WT_PKCS11_PROFILE_H", + "static inline int wt_profile_pkcs11_mechanism(CK_MECHANISM_TYPE mechanism)", "{"] + if mechanisms is None: + lines.extend([" (void)mechanism;", " return 1;"]) + elif mechanisms: + # Equality tests accept numeric aliases without duplicate case labels. + expression = " || ".join("mechanism == %s" % name + for name in sorted(mechanisms)) + lines.append(" return " + expression + ";") + else: + lines.extend([" (void)mechanism;", " return 0;"]) + lines.append("}") + for name, parameters in sorted(signatures.items()): + args = arguments(parameters) + if name in enabled: + lines.append("#define WT_P11_EXPORT_%s proxy_%s" % (name, name)) + if re.search(r"CK_MECHANISM_PTR\s+mechanism\b", parameters): + lines.extend(["#ifdef WT_PKCS11_PROVIDER_BUILD", + "static inline CK_RV wt_filtered_%s(%s)" % (name, parameters), + "{", " if (mechanism != NULL &&", + " !wt_profile_pkcs11_mechanism(mechanism->mechanism)) {", + " return CKR_MECHANISM_INVALID;", " }", + " return %s(%s);" % (name, ", ".join(args)), "}", + "#undef %s" % name, + "#define %s wt_filtered_%s" % (name, name), "#endif"]) + else: + lines.append("static inline CK_RV wt_disabled_%s(%s)\n{" % (name, parameters)) + lines.extend(" (void)%s;" % arg for arg in args) + lines.append(" return CKR_FUNCTION_NOT_SUPPORTED;\n}") + lines.append("#define WT_P11_EXPORT_%s wt_disabled_%s" % (name, name)) + lines.append("#ifdef WT_PKCS11_PROVIDER_BUILD\n#undef %s\n#define %s wt_disabled_%s\n#endif" % + (name, name, name)) + lines.extend(["#define WT_P11_EXPORT(name) WT_P11_EXPORT_ ## name", + "static inline int wt_profile_pkcs11_operation(uint32_t operation)", "{"]) + if enabled: + lines.append(" switch (operation) {") + lines.extend(" case %s:" % opcode(name) for name in sorted(enabled)) + lines.extend([" return 1;", " default: return 0;", " }"]) + else: + lines.extend([" (void)operation;", " return 0;"]) + lines.extend(["}", "static inline CK_FLAGS wt_profile_pkcs11_flags(CK_FLAGS flags)", "{"]) + actions = { + "CKF_ENCRYPT": (("C_EncryptInit", "C_Encrypt"), ("C_EncryptInit", "C_EncryptUpdate", "C_EncryptFinal")), + "CKF_DECRYPT": (("C_DecryptInit", "C_Decrypt"), ("C_DecryptInit", "C_DecryptUpdate", "C_DecryptFinal")), + "CKF_DIGEST": (("C_DigestInit", "C_Digest"), ("C_DigestInit", "C_DigestUpdate", "C_DigestFinal")), + "CKF_SIGN": (("C_SignInit", "C_Sign"), ("C_SignInit", "C_SignUpdate", "C_SignFinal")), + "CKF_VERIFY": (("C_VerifyInit", "C_Verify"), ("C_VerifyInit", "C_VerifyUpdate", "C_VerifyFinal")), + "CKF_SIGN_RECOVER": (("C_SignRecoverInit", "C_SignRecover"),), + "CKF_VERIFY_RECOVER": (("C_VerifyRecoverInit", "C_VerifyRecover"),), + "CKF_GENERATE": (("C_GenerateKey",),), + "CKF_GENERATE_KEY_PAIR": (("C_GenerateKeyPair",),), + "CKF_DERIVE": (("C_DeriveKey",),), + "CKF_WRAP": (("C_WrapKey",),), + "CKF_UNWRAP": (("C_UnwrapKey",),), + } + for flag, alternatives in actions.items(): + if not any(set(names) <= enabled for names in alternatives): + lines.append(" flags &= ~(CK_FLAGS)%s;" % flag) + lines.extend([" return flags;", "}", "#endif", ""]) + return "\n".join(lines) + + +def pkcs11_mechanism_source(root, mechanisms): + # Keep shared structure layouts and PIN primitives intact in other files. + lines = ["/* Generated public mechanisms; internal PIN crypto stays enabled. */", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include "] + if mechanisms is not None: + if not any(name.startswith("CKM_AES_") for name in mechanisms): + lines.append("#define NO_AES") + if not any("HMAC" in name or "HKDF" in name or + "PBKD" in name or "PBE" in name for name in mechanisms): + lines.append("#define NO_HMAC") + lines.extend(["#include %s" % json.dumps(str( + (root / "lib/wolfPKCS11/src/crypto.c").resolve())), ""]) + return "\n".join(lines) + + +def generate(root, output, profile, custom, enable, disable): + catalog, signatures, sources, commands = discover(root) + defaults = json.loads((root / "config/crypto-profiles.json").read_text())["default"] + selected, mechanisms = select(catalog, defaults, profile, custom, enable, disable) + output.mkdir(parents=True, exist_ok=True) + write_file(output / "pkcs11_profile.h", + pkcs11_header(signatures, selected["pkcs11"], mechanisms)) + write_file(output / "pkcs11_crypto.c", pkcs11_mechanism_source(root, mechanisms)) + write_file(output / "fwtpm_command.c", selected_tpm(commands, selected["tpm"], profile == "full")) + psa_wrappers(root, output, sources, selected["psa"], catalog["psa"]) + flags = ["/* Generated build configuration. */", + "#ifndef WT_SELECTED_CRYPTO_PROFILE_H", + "#define WT_SELECTED_CRYPTO_PROFILE_H", + "#define WT_PROFILE_FULL %d" % (profile == "full"), + "#define WT_PROFILE_TPM_COMMAND_COUNT %dU" % len(selected["tpm"]), + "#define WT_PKCS11_PROFILE_HEADER %s" % + json.dumps(str((output / "pkcs11_profile.h").resolve()))] + for kind in KINDS: + for name in sorted(catalog[kind]): + flags.append("#define WT_PROFILE_%s_%s %d" % + (kind.upper(), name, name in selected[kind])) + flags.extend([ + "/* TPM ZGen_2Phase needs a full point, not the ECDH callback's x output. */", + "#if defined(WT_TPM_HSM_CALLBACK_ECC) && !WT_PROFILE_TPM_ZGen_2Phase", + "#define WOLF_CRYPTO_CB_ONLY_ECC 1", + "#endif", + ]) + flags.extend(["#endif", ""]) + write_file(output / "selected_profile.h", "\n".join(flags)) + manifest = {kind: sorted(names) for kind, names in selected.items()} + manifest.update(profile=profile, mechanisms=mechanisms, + tpm_commands="requested; dependency feature guards still apply; default-derived builds check availability", + fwtpm_source_sha256=hashlib.sha256(commands.encode()).hexdigest()) + write_file(output / "selection.json", json.dumps(manifest, indent=2) + "\n") + return manifest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parent.parent) + parser.add_argument("--output", type=Path) + parser.add_argument("--profile", choices=("default", "full"), default="default") + parser.add_argument("--config", type=Path) + parser.add_argument("--enable", action="append", default=[]) + parser.add_argument("--disable", action="append", default=[]) + parser.add_argument("--list", action="store_true") + args = parser.parse_args() + try: + if args.list: + print(json.dumps({k: sorted(v) for k, v in discover(args.root)[0].items()}, indent=2)) + elif args.output is None: + parser.error("--output is required") + else: + config = args.config or (Path(os.environ["WT_CRYPTO_CONFIG"]) + if os.environ.get("WT_CRYPTO_CONFIG") else None) + custom = json.loads(config.read_text()) if config else {} + enable = args.enable + os.environ.get("WT_CRYPTO_ENABLE", "").split() + disable = args.disable + os.environ.get("WT_CRYPTO_DISABLE", "").split() + generate(args.root, args.output, args.profile, custom, enable, disable) + except (OSError, ValueError, KeyError) as error: + parser.error(str(error)) + + +if __name__ == "__main__": + main() diff --git a/tools/manifest/aarch64_interface.py b/tools/manifest/aarch64_interface.py new file mode 100644 index 000000000..2b232a887 --- /dev/null +++ b/tools/manifest/aarch64_interface.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Add one optional application-interface partition to an AArch64 manifest.""" + +import argparse +import copy +import json +import sys +from pathlib import Path + + +INTERFACES = { + "tpm": { + "partition": "PARTITION_TPM", + "service": "SERVICE_TPM", + "sid": 4104, + "uuid": "14a60fe3-05ca-583d-a88a-1e9d0a47d74d", + "dependencies": [4102], + }, + "pkcs11": { + "partition": "PARTITION_PKCS11", + "service": "SERVICE_PKCS11", + "sid": 4105, + "uuid": "ab4f39e1-d12d-5b91-a21c-57d66f7f04ae", + "dependencies": [4102], + }, +} + + +def parse_int(value): + return int(value, 0) + + +def add_interface(manifest, kind, stack_base, data_base, data_size): + interface = INTERFACES[kind] + secure_domains = [domain for domain in manifest["domains"] + if domain["domain_class"] == 1] + if not secure_domains: + raise ValueError("base manifest has no Secure Partition domain") + if any(partition["name"] == interface["partition"] + for partition in manifest["partitions"]): + raise ValueError("interface partition already exists") + + domain_id = max(domain["id"] for domain in manifest["domains"]) + 1 + template = copy.deepcopy(secure_domains[-1]) + image = copy.deepcopy(template["memory_resources"][0]) + template["id"] = domain_id + template["stack_base"] = stack_base + template["stack_size"] = 0x4000 + template["memory_resources"] = [ + image, + { + "base": stack_base, + "size": 0x4000, + "attributes": 19, + "share_id": 0, + }, + { + "base": data_base, + "size": data_size, + "attributes": 19, + "share_id": 0, + }, + ] + manifest["domains"].append(template) + + partition = { + "name": interface["partition"], + "domain_id": domain_id, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [{ + "name": interface["service"], + "sid": interface["sid"], + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": True, + "connection_based": True, + }], + "dependencies": interface["dependencies"], + "interrupts": [], + } + manifest["partitions"].append(partition) + manifest["profile_capabilities"]["max_domains"] = len( + manifest["domains"]) + manifest["limits"]["max_partitions"] = len(manifest["partitions"]) + + ffa_template = copy.deepcopy(manifest["ffa"]["partitions"][-1]) + ffa_template["domain_id"] = domain_id + ffa_template["uuids"] = [interface["uuid"]] + manifest["ffa"]["partitions"].append(ffa_template) + return manifest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("input", type=Path) + parser.add_argument("output", type=Path) + parser.add_argument("--interface", choices=sorted(INTERFACES), + required=True) + parser.add_argument("--stack-base", type=parse_int, required=True) + parser.add_argument("--data-base", type=parse_int, required=True) + parser.add_argument("--data-size", type=parse_int, required=True) + args = parser.parse_args() + + try: + manifest = json.loads(args.input.read_text(encoding="utf-8")) + manifest = add_interface(manifest, args.interface, args.stack_base, + args.data_base, args.data_size) + args.output.write_text(json.dumps(manifest, indent=2) + "\n", + encoding="utf-8") + except (KeyError, TypeError, ValueError, OSError, + json.JSONDecodeError) as error: + print("interface manifest generation failed: {}".format(error), + file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/manifest/check_guest_layout.py b/tools/manifest/check_guest_layout.py new file mode 100644 index 000000000..ba3f6aa0d --- /dev/null +++ b/tools/manifest/check_guest_layout.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Check that compiled guest image layouts match an Armv8-M manifest.""" + +import argparse +import json +import sys +from pathlib import Path + + +DOMAIN_CLASS_NONSECURE = 2 +MEMORY_ATTR_EXECUTE = 1 << 2 + + +def is_integer(value): + """Return whether value is an integer, excluding JSON booleans.""" + return isinstance(value, int) and not isinstance(value, bool) + + +def display(value): + """Format valid addresses as hex and malformed values safely.""" + return f"{value:#x}" if is_integer(value) else repr(value) + + +def parse_guest(value): + """Parse ID=BASE:SIZE into an integer tuple.""" + try: + domain_id_text, layout = value.split("=", 1) + base_text, size_text = layout.split(":", 1) + domain_id = int(domain_id_text, 0) + base = int(base_text, 0) + size = int(size_text, 0) + except (ValueError, TypeError) as error: + raise argparse.ArgumentTypeError( + "guest layout must be ID=BASE:SIZE") from error + + if domain_id < 0 or base < 0 or size <= 0: + raise argparse.ArgumentTypeError( + "guest ID and base must be non-negative and size must be positive") + return domain_id, base, size + + +def check_layout(manifest, expected): + """Return layout errors for the expected Non-secure guest domains.""" + errors = [] + if not isinstance(manifest, dict): + return ["manifest root must be an object"] + + domains = manifest.get("domains") + if not isinstance(domains, list): + return ["manifest domains must be an array"] + + guests = [domain for domain in domains if isinstance(domain, dict) and + domain.get("domain_class") == DOMAIN_CLASS_NONSECURE] + actual_ids = [] + for domain in guests: + domain_id = domain.get("id") + if is_integer(domain_id): + actual_ids.append(domain_id) + else: + errors.append( + f"Non-secure domain has invalid ID {display(domain_id)}") + + if len(actual_ids) != len(set(actual_ids)): + errors.append("manifest contains duplicate Non-secure domain IDs") + + expected_ids = set(expected) + actual_id_set = set(actual_ids) + if actual_id_set != expected_ids: + errors.append( + "Non-secure domain IDs do not match: expected " + f"{sorted(expected_ids)}, found {sorted(actual_id_set)}") + + for domain in guests: + domain_id = domain.get("id") + if not is_integer(domain_id) or domain_id not in expected: + continue + + base, size = expected[domain_id] + resources = domain.get("memory_resources") + if not isinstance(resources, list): + errors.append(f"domain {domain_id} memory resources must be an array") + continue + + executable = [] + for index, resource in enumerate(resources): + if not isinstance(resource, dict): + errors.append( + f"domain {domain_id} resource {index} must be an object") + continue + attributes = resource.get("attributes") + if not is_integer(attributes): + errors.append( + f"domain {domain_id} resource {index} has invalid " + f"attributes {display(attributes)}") + continue + if attributes & MEMORY_ATTR_EXECUTE: + executable.append(resource) + if len(executable) != 1: + errors.append( + f"domain {domain_id} must have exactly one executable region") + continue + + resource = executable[0] + actual_base = resource.get("base") + actual_size = resource.get("size") + if (not is_integer(actual_base) or not is_integer(actual_size) or + actual_base != base or actual_size != size): + errors.append( + f"domain {domain_id} executable region is " + f"{display(actual_base)}:{display(actual_size)}, expected " + f"{base:#x}:{size:#x}") + entry_point = domain.get("entry_point") + if not is_integer(entry_point) or entry_point != base: + errors.append( + f"domain {domain_id} entry point is " + f"{display(entry_point)}, expected vector base {base:#x}") + + return errors + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("manifest", type=Path) + parser.add_argument( + "--guest", action="append", default=[], type=parse_guest, + help="expected Non-secure guest layout as ID=BASE:SIZE") + args = parser.parse_args() + + expected = {} + for domain_id, base, size in args.guest: + if domain_id in expected: + parser.error(f"duplicate guest domain ID {domain_id}") + expected[domain_id] = (base, size) + if not expected: + parser.error("at least one --guest layout is required") + + try: + with args.manifest.open("r", encoding="utf-8") as manifest_file: + manifest = json.load(manifest_file) + except (OSError, json.JSONDecodeError) as error: + parser.error(str(error)) + + errors = check_layout(manifest, expected) + if errors: + for error in errors: + print(f"guest layout error: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/measure_interface_footprint.sh b/tools/measure_interface_footprint.sh new file mode 100755 index 000000000..55ae35b20 --- /dev/null +++ b/tools/measure_interface_footprint.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# measure_interface_footprint.sh +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTrust is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +# Run in the M33MU CI image after initializing dependencies and FreeRTOS. +set -euo pipefail +root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$root" +out="${1:-$root/build/interface-footprint}" +mkdir -p "$out" +out="$(cd "$out" && pwd)" +export FREERTOS_DIR="${FREERTOS_DIR:-$root/tests/firmware/zephyr-stm32h5/.workspace/freertos}" +export WT_CRYPTO_PROFILE="${WT_CRYPTO_PROFILE:-default}" +export WT_LTO="${WT_LTO:-1}" +export WT_SECURE_FLASH_BASE=0x0C060000 WT_SECURE_FLASH_SIZE=0x40000 +export WT_MAX_GUESTS=1 +printf 'api,engine,role,text,data,bss,flash\n' > "$out/sizes.csv" +{ + git rev-parse HEAD + git diff --stat HEAD --ignore-submodules=all + git status --porcelain --ignore-submodules=all + arm-none-eabi-gcc --version | head -1 + printf 'profile=%s lto=%s guests=%s\n' "$WT_CRYPTO_PROFILE" "$WT_LTO" "$WT_MAX_GUESTS" +} > "$out/configuration.txt" +record() +{ + local text data bss rest + read -r text data bss rest < <(arm-none-eabi-size "$4" | tail -1) + arm-none-eabi-nm -S --size-sort "$4" > "$4.symbols.txt" + arm-none-eabi-size -A -d "$4" > "$4.sections.txt" + printf '%s,%s,%s,%s,%s,%s,%s\n' "$1" "$2" "$3" "$text" "$data" "$bss" "$((text + data))" >> "$out/sizes.csv" +} +for WT_ENGINE in native hsm; do + export WT_ENGINE + for api in psa pkcs11 tpm; do + secure="$out/$api-$WT_ENGINE-secure" + config=(CONFIG_PKCS11=n CONFIG_TPM=n) + case "$api" in + pkcs11) config=(CONFIG_PKCS11=y CONFIG_TPM=n) ;; + tpm) config=(CONFIG_TPM=y CONFIG_PKCS11=n) ;; + esac + echo "BUILD $api $WT_ENGINE" + make -j"${JOBS:-4}" TARGET=stm32h563 BUILD_DIR="$secure" \ + "${config[@]}" secure-image > "$out/$api-$WT_ENGINE-secure.log" 2>&1 + record "$api" "$WT_ENGINE" secure "$secure/wolftrust.elf" + export SECURE_CMSE_IMPLIB="$secure/secure_cmse_implib.o" + export BUILD_DIR="$out/$api-$WT_ENGINE-app" + mkdir -p "$BUILD_DIR" + if [ "$api" = psa ]; then + WT_GUEST1_FLASH_BASE=0x080A0000 WT_GUEST1_RAM_BASE=0x20000000 \ + WT_WOLFHSM_CLIENT_ID=1 WT_FREERTOS_OTHER_RAM_BASE=0x20010000 \ + bash tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh > "$BUILD_DIR/build.log" 2>&1 + record "$api" "$WT_ENGINE" app "$BUILD_DIR/freertos_guest1.elf" + else + export WT_PKCS11_SUITE=all + [ "$WT_CRYPTO_PROFILE" != default ] || export WT_PKCS11_SUITE=profile + bash "tests/firmware/stm32h563-tpm/scripts/build_freertos_$api.sh" > "$BUILD_DIR/build.log" 2>&1 + record "$api" "$WT_ENGINE" app "$BUILD_DIR/guest0.elf" + + fi + done +done +cat "$out/sizes.csv" diff --git a/tools/secure_owners.txt b/tools/secure_owners.txt index cb82b99cc..7b650bb31 100644 --- a/tools/secure_owners.txt +++ b/tools/secure_owners.txt @@ -23,6 +23,7 @@ sec_manifest.o spm sec_monitor.o spm sec_partition.o spm sec_partitions.o spm +sec_platform_qemu.o spm sec_periph.o spm sec_fabric_windows.o spm sec_platform_stm32h563.o spm @@ -38,34 +39,66 @@ wc_sec_wc_port.o spm wt_sec_boot_handoff.o spm wt_sec_cmse.o spm wt_sec_coroutine.o spm +wt_sec_coroutine_aarch64.o spm wt_sec_coroutine_armv8m.o spm +wt_sec_console.o spm +wt_sec_conf_backend.o spm +wt_sec_domain.o spm +wt_sec_el3_board.o spm +wt_sec_esr.o spm +wt_sec_ffa_boot_info.o spm +wt_sec_ffa_mem.o spm +wt_sec_ffa_msg.o spm +wt_sec_ffa_notif.o spm +wt_sec_ffa_partinfo.o spm +wt_sec_ffa_runtime.o spm wt_sec_ffm_gateway.o spm wt_sec_ffm_nsc.o spm +wt_sec_gicv2.o spm +wt_sec_gicv3.o spm wt_sec_guest_context_armv8m.o spm wt_sec_hsm_flash.o spm wt_sec_silicon_guard.o spm wt_sec_silicon_rev.o spm wt_sec_xspi_nor.o spm wt_sec_irq_armv8m.o spm +wt_sec_mmu.o spm wt_sec_mpu_armv8m.o spm wt_sec_mutex.o spm wt_sec_nvm_boot.o spm +wt_sec_platform_arch.o spm +wt_sec_platform_l3.o spm +wt_sec_pl011.o spm +wt_sec_psa_service.o spm wt_sec_reg.o spm wt_sec_rng_entropy.o spm wt_sec_sau_armv8m.o spm wt_sec_sp_fault_armv8m.o spm +wt_sec_sp_entry.o spm +wt_sec_sp_trap.o spm +wt_sec_spm_entry.o spm wt_sec_spm_gate_core.o spm +wt_sec_spm_irq.o spm +wt_sec_spm_main.o spm +wt_sec_spm_mem.o spm +wt_sec_spm_svc_glue.o spm +wt_sec_spm_switch.o spm wt_sec_spm_svc.o spm wt_sec_start_armv8m.o spm +wt_sec_tables.o spm +wt_sec_timer.o spm +wt_sec_uart.o spm wt_sec_stm32h5_rng.o spm wt_sec_wolftrust_manifest_generated.o spm wt_sec_wt_hsm_priv.o spm wh_sec_wh_flash_unit.o vault +wh_sec_wh_flash_ramsim.o vault wh_sec_wh_lock.o vault wh_sec_wh_nvm.o vault wh_sec_wh_nvm_flash.o vault wt_sec_hsm_flash_ctx.o vault +wt_sec_hsm_nvm.o vault wt_sec_nvm_store.o vault wt_sec_vault_service.o vault wt_sec_wt_hsm_seal.o vault @@ -122,6 +155,23 @@ wt_sec_vnet_ring.o vnet wt_sec_vnet_service.o vnet wt_sec_vnet_switch.o vnet +wt_sec_tpm_relay_service.o tpm +wt_sec_tpm_local_provider.o tpm +tpm_sec_* tpm +tpm_wc_* tpm +tpm_wh_* tpm +wt_sec_pkcs11_relay_service.o pkcs11 +wt_sec_hsm_psa_transport.o pkcs11 +wt_sec_pkcs11_provider.o pkcs11 +wt_sec_pkcs11_secure_heap.o pkcs11 +wt_sec_pkcs11_wire.o pkcs11 +wh_sec_wh_client.o pkcs11 +wh_sec_wh_client_crypto.o pkcs11 +wh_sec_wh_client_cryptocb.o pkcs11 +p11_sec_* pkcs11 +p11_wc_* pkcs11 +p11_wh_* pkcs11 + wc_sec_aes.o shared wc_sec_asn.o shared wc_sec_coding.o shared @@ -129,11 +179,15 @@ wc_sec_ecc.o shared wc_sec_error.o shared wc_sec_hash.o shared wc_sec_hmac.o shared +wc_sec_kdf.o shared wc_sec_logging.o shared wc_sec_memory.o shared +wc_sec_pwdbased.o shared wc_sec_random.o shared wc_sec_sha256.o shared +wc_sec_sha512.o shared wc_sec_sp_cortexm.o shared +wc_sec_sp_c64.o shared wc_sec_sp_int.o shared wc_sec_thumb2-aes-asm_c.o shared wc_sec_thumb2-sha256-asm_c.o shared