diff --git a/.gitignore b/.gitignore index 93388d03bf9..6c19bd95836 100644 --- a/.gitignore +++ b/.gitignore @@ -487,6 +487,9 @@ wrapper/Ada/obj/ wolfssl/debug-trace-error-codes.h wolfssl/debug-untrace-error-codes.h +# AI tool configuration +.ai/ +.claude/ AGENTS.md CLAUDE.md @@ -495,3 +498,13 @@ compile_commands.json # Python cache __pycache__/ + +# Beads issue tracker +.beads/ +.dolt/ +*.db +.beads-credential-key + +# Caliptra hw-model test binary +wolfcrypt/src/port/caliptra/sim/caliptra_test_bin +wolfcrypt/src/port/caliptra/sim/*.o diff --git a/configure.ac b/configure.ac index bc4bdb51d24..d311bde6ea6 100644 --- a/configure.ac +++ b/configure.ac @@ -3201,6 +3201,41 @@ case "$ENABLED_STSAFE" in esac +# Caliptra Cryptographic Mailbox +AC_ARG_ENABLE([caliptra], + [AS_HELP_STRING([--enable-caliptra],[enable Caliptra cryptographic mailbox CryptoCb port; implies --enable-cryptocb (default: disabled)])], + [ ENABLED_CALIPTRA=$enableval ], + [ ENABLED_CALIPTRA=no ] +) +if test "x$ENABLED_CALIPTRA" = "xyes" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CALIPTRA" + AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_FREE" + # Caliptra requires WOLF_CRYPTO_CB and WOLF_CRYPTO_CB_FREE. + # ENABLED_CRYPTOCB is forced to yes below, alongside the cryptocb + # AC_ARG_ENABLE processing, so this declaration ordering is robust. +fi + +# Caliptra software simulator: provides caliptra_mailbox_exec() so that +# libwolfssl and example binaries link without a real hardware backend. +# Enabled by default when --enable-caliptra is on; disable for production +# builds that supply their own caliptra_mailbox_exec() implementation. +AC_ARG_ENABLE([caliptra-sim], + [AS_HELP_STRING([--disable-caliptra-sim],[Disable bundled Caliptra software simulator; requires integrator-supplied caliptra_mailbox_exec() (default: enabled when --enable-caliptra)])], + [ ENABLED_CALIPTRA_SIM=$enableval ], + [ ENABLED_CALIPTRA_SIM=yes ] +) +if test "x$ENABLED_CALIPTRA" = "xno" +then + ENABLED_CALIPTRA_SIM=no +fi +if test "x$ENABLED_CALIPTRA_SIM" = "xyes" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CALIPTRA_SIM" + AC_MSG_WARN([Caliptra software simulator enabled (default). This is for offline development and test only -- production builds must pass --disable-caliptra-sim and supply a real caliptra_mailbox_exec() implementation.]) +fi + + # NXP SE050 # Example: "./configure --with-se050=/home/pi/simw_top" ENABLED_SE050="no" @@ -10620,7 +10655,7 @@ AC_ARG_ENABLE([cryptocb-sw-test], [ ENABLED_CRYPTOCB_SW_TEST=yes ] ) -if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" +if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "x$ENABLED_CALIPTRA" = "xyes" then ENABLED_CRYPTOCB=yes fi @@ -12370,6 +12405,8 @@ AM_CONDITIONAL([BUILD_IOTSAFE_HWRNG],[test "x$ENABLED_IOTSAFE_HWRNG" = "xyes"]) AM_CONDITIONAL([BUILD_SE050],[test "x$ENABLED_SE050" = "xyes"]) AM_CONDITIONAL([BUILD_STSAFE],[test "x$ENABLED_STSAFE" != "xno"]) AM_CONDITIONAL([BUILD_TROPIC01],[test "x$ENABLED_TROPIC01" = "xyes"]) +AM_CONDITIONAL([BUILD_CALIPTRA],[test "x$ENABLED_CALIPTRA" = "xyes"]) +AM_CONDITIONAL([BUILD_CALIPTRA_SIM],[test "x$ENABLED_CALIPTRA_SIM" = "xyes"]) AM_CONDITIONAL([BUILD_KDF],[test "x$ENABLED_KDF" = "xyes"]) AM_CONDITIONAL([BUILD_HMAC],[test "x$ENABLED_HMAC" = "xyes"]) AM_CONDITIONAL([BUILD_ERROR_STRINGS],[test "x$ENABLED_ERROR_STRINGS" = "xyes"]) @@ -12940,6 +12977,8 @@ echo " * IoT-Safe HWRNG: $ENABLED_IOTSAFE_HWRNG" echo " * NXP SE050: $ENABLED_SE050" echo " * STMicro STSAFE: $ENABLED_STSAFE" echo " * TROPIC01: $ENABLED_TROPIC01" +echo " * Caliptra: $ENABLED_CALIPTRA" +echo " * Caliptra Sim: $ENABLED_CALIPTRA_SIM" echo " * Maxim Integrated MAXQ10XX: $ENABLED_MAXQ10XX" echo " * PSA: $ENABLED_PSA" echo " * System CA certs: $ENABLED_SYS_CA_CERTS" diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index 18d7a339cd5..2e10d12d0b6 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -244,6 +244,16 @@ if BUILD_TROPIC01 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/tropicsquare/tropic01.c endif +if BUILD_CALIPTRA +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/caliptra/caliptra_port.c +endif +if BUILD_CALIPTRA_SIM +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/caliptra/sim/caliptra_sim.c +endif +EXTRA_DIST += wolfcrypt/src/port/caliptra/caliptra_port.c \ + wolfcrypt/src/port/caliptra/README.md \ + wolfcrypt/src/port/caliptra/sim/caliptra_sim.c + if BUILD_PSA src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/psa/psa.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/psa/psa_hash.c diff --git a/wolfcrypt/src/port/caliptra/README.md b/wolfcrypt/src/port/caliptra/README.md new file mode 100644 index 00000000000..1599436e572 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/README.md @@ -0,0 +1,340 @@ +# wolfSSL Caliptra Cryptographic Mailbox Port + +## Purpose + +`caliptra_port.c` is a wolfSSL CryptoCb port that offloads cryptographic +operations to the Caliptra hardware security module via its Cryptographic +Mailbox protocol. + +Supported operations: + +| Algorithm | wolfSSL algo_type | Notes | +|-----------------|--------------------|-------------------------------------------------| +| SHA-384 | WC_ALGO_TYPE_HASH | Streaming Init/Update/Final | +| SHA-512 | WC_ALGO_TYPE_HASH | Streaming Init/Update/Final | +| HMAC-SHA-384/512| WC_ALGO_TYPE_HMAC | Single-shot; key as CMK in hmac->devCtx | +| AES-GCM encrypt | WC_ALGO_TYPE_CIPHER| Single wolfSSL call → 3 mailbox calls | +| AES-GCM decrypt | WC_ALGO_TYPE_CIPHER| Single wolfSSL call → 3 mailbox calls | +| ECDSA sign | WC_ALGO_TYPE_PK | P-384; private key CMK in key->devCtx | +| ECDSA verify | WC_ALGO_TYPE_PK | P-384; requires pre-imported CMK in key->devCtx | +| RNG | WC_ALGO_TYPE_RNG | Loops in 4096-byte chunks | + +SHA-256 is not supported by Caliptra firmware; `caliptra_hash()` returns +`CRYPTOCB_UNAVAILABLE` for `WC_HASH_TYPE_SHA256` and wolfSSL falls back to +software. + +## Build Guards + +```c +#if defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) +``` + +Both macros must be defined in your wolfSSL build configuration. + +### WOLF_CRYPTO_CB_FREE requirement + +`WOLF_CRYPTO_CB_FREE` must also be defined for correct operation. It enables +the `caliptra_hash_free` callback that releases the `CaliptraShaCtx` heap +allocation when a streaming SHA object is freed before `Final` is called. +Without it, any SHA Init/Update sequence that is abandoned (e.g., due to an +error or early exit) will leak that allocation. + +- **`--enable-caliptra`**: `WOLF_CRYPTO_CB_FREE` is set automatically by the + build system; no user action is required. +- **Manual `user_settings.h` configuration** (i.e., `#define WOLFSSL_CALIPTRA` + without the autoconf build): you must also add `#define WOLF_CRYPTO_CB_FREE`, + otherwise abandoned streaming SHA objects will leak their `CaliptraShaCtx` + allocation. + +## Memory Requirements + +By default, mailbox request/response structs are heap-allocated per +operation via `XMALLOC`/`XFREE`. + +Approximate heap in flight per operation: + +| Operation | Buffers | Peak heap | +|------------------------|---------|------------| +| AES-GCM encrypt/decrypt| 6 | ~30 KB | +| SHA-384/512 Update | 2–3 | ~4.5 KB | +| ECDSA sign/verify | 2–4 | ~4 KB | +| HMAC-SHA-384/512 | 2–3 | ~5 KB | + +The largest individual struct is the mailbox request with a full data payload, +at approximately 4.4 KB. Integrators on constrained embedded targets should +size their heap accordingly, or provide a custom `XMALLOC` implementation +backed by a static pool. + +### `WOLFSSL_CALIPTRA_STATIC_BUFFERS` + +When this macro is defined at compile time, mailbox request/response +structs are allocated on the caller's stack frame rather than the heap. +`CALIPTRA_ALLOC` becomes a no-op alias (`ptr = &local_buf`), and +`CALIPTRA_FREE` is a no-op. This is useful in heap-free environments +where every operation must succeed without a working `XMALLOC`. + +Stack pressure becomes the constraint instead — peak frame size for +AES-GCM encrypt/decrypt is approximately 30 KB. Sensitive material is +still zeroed before the function returns via `CALIPTRA_FREE_SENSITIVE`. + +To test the static-buffers path: + +```sh +./configure --enable-caliptra CFLAGS="-DWOLFSSL_CALIPTRA_STATIC_BUFFERS" +make +make check +``` + +All Caliptra subtests pass under both `WOLFSSL_CALIPTRA_STATIC_BUFFERS` +on and off. + +## Integrator-Provided Transport Hook + +The integrator must supply the following function. It is responsible for +writing `cmd_id` to the Caliptra mailbox command register, streaming the +request bytes through the data FIFO, ringing the doorbell, and reading back +the response: + +```c +int caliptra_mailbox_exec(word32 cmd_id, + const void* req, word32 req_len, + void* resp, word32 resp_len); +``` + +Return 0 on success, or a negative wolfSSL error code on failure. + +## Registration + +```c +#include + +/* Register the Caliptra device with the wolfSSL CryptoCb framework */ +wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, wc_caliptra_cb, NULL); + +/* Assign the device ID to any wolfSSL object that should use Caliptra */ +wc_InitSha384_ex(&sha, NULL, WOLF_CALIPTRA_DEVID); +wc_ecc_init_ex(&key, NULL, WOLF_CALIPTRA_DEVID); +``` + +## Key Material + +Keys are never passed as raw bytes at operation time. Instead, the +application imports key material once via `wc_caliptra_import_key()` and +stores the returned 128-byte `CaliptraCmk` opaque handle in the relevant +wolfSSL object's `devCtx` field before any operation: + +```c +CaliptraCmk aes_cmk; +wc_caliptra_import_key(raw_aes_key, 32, /*key_usage=*/0, &aes_cmk); + +Aes aes; +wc_AesInit(&aes, NULL, WOLF_CALIPTRA_DEVID); +aes.devCtx = &aes_cmk; /* set before wc_AesGcmEncrypt */ +``` + +The same pattern applies to HMAC (`hmac->devCtx`) and ECDSA sign +(`key->devCtx`). + +## Known Limitations + +### ECDH is unavailable + +`WC_PK_TYPE_ECDH` returns `CRYPTOCB_UNAVAILABLE`. + +The Caliptra ECDH protocol (`CM_ECDH_GENERATE` + `CM_ECDH_FINISH`) returns +the derived shared secret as an opaque 128-byte `Cmk` handle, not as raw +bytes. The wolfSSL ECDH CryptoCb interface (`info->pk.ecdh.out`) requires +raw shared-secret bytes. These interfaces are fundamentally incompatible +and cannot be bridged without Caliptra firmware changes. + +### AES-GCM encrypt ignores caller-provided IV + +Caliptra generates the IV server-side during `CM_AES_GCM_ENCRYPT_INIT`. +The caller's `aesgcm_enc.iv` pointer is ignored. After a successful +encrypt call, the server-generated IV (12 bytes) is cached in the `Aes` +object and must be retrieved via `wc_caliptra_aesgcm_get_iv()`: + +```c +/* After wc_AesGcmEncrypt returns 0: */ +byte iv[12]; +wc_caliptra_aesgcm_get_iv(&aes, iv, sizeof(iv)); +``` + +### HMAC is single-shot only + +The HMAC handler processes the entire message in one mailbox call. +`info->hmac.in` must point to the complete message and `info->hmac.inSz` +must be at most `CMB_MAX_DATA_SIZE` (4096) bytes. Streaming HMAC is not +supported. + +HMAC-SHA-384 and HMAC-SHA-512 are supported. HMAC-SHA-256 returns +`CRYPTOCB_UNAVAILABLE` and wolfSSL falls back to software. + +### ECDSA verify uses seed-based key derivation + +Caliptra's ECDSA firmware treats the 48-byte CMK input as a **seed** for +deterministic key-pair derivation (`ecc384.key_pair(seed)`), not as the raw +private scalar. This applies to both sign (`CM_ECDSA_SIGN`) and verify +(`CM_ECDSA_VERIFY`): the firmware re-derives the same `(d', Q')` from the seed +on every call. + +Consequence: importing a raw software private key as an ECDSA CMK seed will +produce a signature from a *different* key pair than the one the software key +represents. Software verification against the original public key will fail. + +**Correct pattern for hardware sign + hardware verify:** + +```c +byte seed[48]; +wc_RNG_GenerateBlock(&rng, seed, sizeof(seed)); + +CaliptraCmk sign_cmk, verify_cmk; +wc_caliptra_import_key(seed, 48, CMB_KEY_USAGE_ECDSA, &sign_cmk); +wc_caliptra_import_key(seed, 48, CMB_KEY_USAGE_ECDSA, &verify_cmk); +/* Both CMKs hold the same seed; firmware derives the same Q' for each. */ + +ecc_key key; +wc_ecc_init_ex(&key, NULL, WOLF_CALIPTRA_DEVID); +wc_ecc_make_key_ex(&rng, 48, &key, ECC_SECP384R1); /* init curve metadata */ +key.devCtx = &sign_cmk; +wc_ecc_sign_hash(hash, hashSz, sig, &sigLen, &rng, &key); +wc_ecc_free(&key); + +ecc_key vkey; +wc_ecc_init_ex(&vkey, NULL, WOLF_CALIPTRA_DEVID); +wc_ecc_make_key_ex(&rng, 48, &vkey, ECC_SECP384R1); +vkey.devCtx = &verify_cmk; +wc_ecc_verify_hash(sig, sigLen, hash, hashSz, &verify_res, &vkey); +wc_ecc_free(&vkey); +``` + +If `key->devCtx` is NULL when `wc_ecc_verify_hash()` is called, the port +returns `CRYPTOCB_UNAVAILABLE` and wolfSSL falls back to software ECC +verification using the raw public key coordinates in the `ecc_key` struct. + +The caller owns the CMK lifetime; the port does not delete `verify_cmk` after +verify. + +## File Layout + +``` +wolfcrypt/src/port/caliptra/ + caliptra_port.c — full implementation (this port) + README.md — this file + +wolfssl/wolfcrypt/port/caliptra/ + caliptra_port.h — public API, struct definitions, command IDs +``` + +## Testing + +Production test coverage lives in `wolfcrypt/test/test.c` under +`#ifdef WOLFSSL_CALIPTRA`. Build with `--enable-caliptra` and run +`wolfcrypt/test/testwolfcrypt` as normal. + +The `wolfcrypt/src/port/caliptra/sim/` directory contains two test backends: + +- **`caliptra_sim.c`** — software mailbox stub for offline development (no + external dependencies). **Not for production use.** This file is built + into `libwolfssl` by default when `--enable-caliptra` is used; production + deployments **must** pass `--disable-caliptra-sim` and link an + integrator-supplied `caliptra_mailbox_exec()` that talks to real Caliptra + hardware. Both `configure` and the C compiler will emit explicit warnings + whenever the simulator is included in the build. +- **`caliptra_hwmodel.c`** + **`Makefile`** — runs `caliptra_test.c` against + real Caliptra firmware via the [chipsalliance/caliptra-sw][caliptra-sw] + hw-model C binding. + +### Running the hw-model test + +The hw-model test exercises actual Caliptra firmware (ROM + runtime) through +the hardware emulator. All nine test cases pass: RNG, SHA-256, SHA-384, +AES-GCM, ECDSA sign+verify, and HMAC-SHA-384. + +#### Prerequisites + +| Requirement | Version / Notes | +|-------------|-----------------| +| Rust toolchain | 1.85 (set by `rust-toolchain.toml` in caliptra-sw) | +| RISC-V target | `riscv32imc-unknown-none-elf` (installed by `rustup` automatically) | +| C compiler | GCC or Clang | +| System libs | `libpthread`, `libstdc++`, `libdl`, `librt`, `libm` | +| wolfSSL | Built with `--enable-caliptra` (see step 3 below) | + +#### Step 1 — Clone caliptra-sw + +```sh +git clone https://github.com/chipsalliance/caliptra-sw.git ~/caliptra +cd ~/caliptra +``` + +The Makefile defaults to `~/caliptra`; override with `CALIPTRA_ROOT=` +if you clone elsewhere. + +#### Step 2 — Build the C binding and firmware image bundle + +```sh +cd ~/caliptra + +# Build the hw-model C binding static library and generate caliptra_model.h +cargo build -p caliptra-hw-model-c-binding +# Produces: target/debug/libcaliptra_hw_model_c_binding.a +# hw-model/c-binding/out/caliptra_model.h + +# Build the firmware image bundle (compiles FMC + runtime RISC-V firmware) +# Also produces a ROM binary; the frozen ROM in the repo can be used instead +# (see Makefile variables below). +cargo run --manifest-path=builder/Cargo.toml --bin image -- \ + --rom-with-log hw-model/c-binding/out/caliptra_rom.bin \ + --fw hw-model/c-binding/out/image_bundle.bin +# Produces: hw-model/c-binding/out/image_bundle.bin +# hw-model/c-binding/out/caliptra_rom.bin (optional; see note below) +``` + +The Makefile's default `ROM_PATH` points to the pre-built frozen ROM already +checked into caliptra-sw (`rom/ci_frozen_rom/2.1/caliptra-rom-2.1.0-a72a76f.bin`). +To use the freshly built ROM instead, pass `ROM_PATH` on the make command line +(see table below). + +#### Step 3 — Build wolfSSL with Caliptra support + +```sh +cd +./autogen.sh # if building from git, not a release tarball +./configure --enable-caliptra +make -j$(nproc) +``` + +#### Step 4 — Build and run the hw-model test + +```sh +make -C wolfcrypt/src/port/caliptra/sim/ run +``` + +Expected output (boot log abbreviated): + +``` +caliptra_hwmodel: runtime ready (boot status 0x600) +PASS: RNG generates nonzero +PASS: SHA-256 empty KAT +PASS: SHA-256 abc KAT +PASS: SHA-384 empty KAT +PASS: SHA-256 multi-update matches software +PASS: AES-GCM encrypt/decrypt roundtrip +PASS: Caliptra ECDSA sign+verify +PASS: HMAC-SHA-384 matches software +PASS: AES-GCM tampered tag returns AES_GCM_AUTH_E + +9/9 tests passed +``` + +#### Makefile variables + +| Variable | Default | Override example | +|----------|---------|-----------------| +| `CALIPTRA_ROOT` | `~/caliptra` | `make CALIPTRA_ROOT=/opt/caliptra run` | +| `ROM_PATH` | `$(CALIPTRA_ROOT)/rom/ci_frozen_rom/2.1/caliptra-rom-2.1.0-a72a76f.bin` | `make ROM_PATH=/path/to/rom.bin run` | +| `FW_PATH` | `$(CALIPTRA_ROOT)/hw-model/c-binding/out/image_bundle.bin` | `make FW_PATH=/path/to/fw.bin run` | +| `WOLFSSL_ROOT` | repo root (auto-detected) | `make WOLFSSL_ROOT=/path/to/wolfssl run` | + +[caliptra-sw]: https://github.com/chipsalliance/caliptra-sw diff --git a/wolfcrypt/src/port/caliptra/caliptra_port.c b/wolfcrypt/src/port/caliptra/caliptra_port.c new file mode 100644 index 00000000000..ddd35667905 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/caliptra_port.c @@ -0,0 +1,1630 @@ +/* caliptra_port.c — wolfSSL CryptoCb port for Caliptra Cryptographic Mailbox + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* + * wolfSSL CryptoCb port for the Caliptra hardware security module. + * + * Build guards: WOLFSSL_CALIPTRA && WOLF_CRYPTO_CB + * + * The integrator must supply: + * int caliptra_mailbox_exec(word32 cmd_id, + * const void* req, word32 req_len, + * void* resp, word32 resp_len); + * + * Keys for AES-GCM, HMAC, and ECDSA sign are supplied as CaliptraCmk handles + * stored in the relevant object's devCtx field before the first operation. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif +#include + +#if defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* aes->reg is wolfSSL's per-object IV register, used consistently across + * AES streaming modes (CBC, CTR, etc.) to carry IV state between calls. + * The Caliptra port stores the 12-byte server-generated IV here so it + * survives wc_AesGcmEncrypt() and can be retrieved via + * wc_caliptra_aesgcm_get_iv(). Verify at compile time that the field is + * wide enough. If this fires, the Aes struct layout has changed; update + * wc_caliptra_aesgcm_get_iv() and the XMEMCPY into aes->reg below. */ +wc_static_assert2(sizeof(((Aes*)0)->reg) >= 12u, + "Aes.reg too small for 12-byte Caliptra IV; " + "update wc_caliptra_aesgcm_get_iv"); + +/* The Caliptra mailbox wire format uses little-endian byte order for all + * multi-byte integer fields. HTOLE32/LE32TOH convert between host byte order + * and the LE wire format; on a LE host they are no-ops (zero overhead). + * Byte-blob fields (CaliptraCmk, context arrays, IV, tags, plaintext, keys) + * are always accessed via XMEMCPY and are endian-neutral. + * The checksum is also endian-neutral (it sums individual bytes). */ +#ifdef BIG_ENDIAN_ORDER + #define HTOLE32(x) ByteReverseWord32(x) + #define LE32TOH(x) ByteReverseWord32(x) +#else + #define HTOLE32(x) (x) + #define LE32TOH(x) (x) +#endif + +/* Safety invariant: caliptra_hmac() must return WC_HW_E, not + * CRYPTOCB_UNAVAILABLE, so wolfSSL's wc_HmacUpdate/wc_HmacFinal never + * silently fall through to software HMAC when a Caliptra key is loaded. + * + * Both functions gate software fallback on a single test (hmac.c): + * if (ret != CRYPTOCB_UNAVAILABLE) return ret; + * WC_HW_E passes this test (WC_HW_E != CRYPTOCB_UNAVAILABLE), so both + * calls return WC_HW_E immediately and the software path is never reached. + * If the two values were ever equal, Update would return the error but + * Final could fall through and produce a MAC over an unkeyed, empty state + * — a silent authentication bypass. The assertion below makes this + * invariant a hard compile-time failure rather than a latent trap. */ +wc_static_assert2(WC_HW_E != CRYPTOCB_UNAVAILABLE, + "WC_HW_E and CRYPTOCB_UNAVAILABLE must be distinct; " + "caliptra_hmac() relies on this for HMAC fallback safety"); + +#ifndef WOLF_CRYPTO_CB_FREE +#error "WOLFSSL_CALIPTRA requires WOLF_CRYPTO_CB_FREE to avoid CaliptraShaCtx leaks; add it to user_settings.h or use --enable-caliptra" +#endif + +/* ========================================================================= + * Internal helper: Caliptra mailbox request checksum + * + * The firmware verifies: sum(cmd_id.le_bytes) + sum(req[4..req_len]) + chksum == 0 + * i.e. chksum = -(sum(cmd_id.le_bytes) + sum(req[4..req_len])) mod 2^32 + * + * Must be called with the final (possibly trimmed) req_len, after all + * payload fields have been populated, and stored into req->hdr.chksum + * immediately before caliptra_mailbox_exec(). + * ========================================================================= */ + +static word32 caliptra_req_chksum(word32 cmd_id, const void *req, word32 req_len) +{ + const byte *buf = (const byte*)req; + word32 sum = 0; + word32 i; + /* Defense-in-depth: a NULL req is a caller bug (chksum precondition is + * "all payload populated"), but return a deterministic 0 rather than + * dereferencing. Likewise clamp req_len to sizeof(cmd_id) so callers + * that pass a too-short length still get a well-defined cmd_id-only + * checksum instead of skipping the payload loop with an arbitrary i + * starting value. */ + if (req == NULL) return 0u; + if (req_len < sizeof(word32)) req_len = sizeof(word32); + sum += (byte)(cmd_id); + sum += (byte)(cmd_id >> 8); + sum += (byte)(cmd_id >> 16); + sum += (byte)(cmd_id >> 24); + for (i = sizeof(word32); i < req_len; i++) + sum += buf[i]; + return 0u - sum; +} + +/* Response checksum verification — deliberately omitted. + * + * Why not verified here: + * The Caliptra firmware guarantees integrity at the FIPS boundary. The + * mailbox transport is memory-mapped (not a network interface), so a + * bit-flip attack on the response buffer requires physical access to RAM, + * which is outside this port's threat model. + * + * Formula (for integrators who want to add it in caliptra_mailbox_exec()): + * sum(resp[0..resp_len]) == 0 (mod 2^32) + * i.e. resp->hdr.chksum + sum of all bytes after the chksum field == 0. + * This differs from the request checksum in that there is no cmd_id term; + * the command ID is not echoed in the response header. + * + * If response integrity checking is required, verify inside + * caliptra_mailbox_exec() after reading the response buffer, before + * returning to this port. */ + +/* ========================================================================= + * CALIPTRA_ALLOC / CALIPTRA_FREE / CALIPTRA_OOM — mailbox buffer helpers + * + * Temporary request/response structs are 144–4232 B each, and each + * operation issues 1–3 mailbox calls. By default they are heap-allocated + * (XMALLOC/XFREE) so large objects are not placed on the stack. + * + * Define WOLFSSL_CALIPTRA_STATIC_BUFFERS at build time to stack-allocate + * instead, eliminating per-call heap pressure at the cost of stack depth: + * SHA-384/512 Init+Update+Final: up to ~8 KB (two buffers live at once) + * AES-GCM Encrypt/Decrypt: up to ~4 KB per Init/Update/Final frame + * All other operations: up to ~4 KB + * Thread safety is unaffected — stack buffers are per-invocation. + * + * Usage pattern in each function: + * + * #ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + * TypeA req_s; TypeB resp_s; <-- stack buffers (stack path only) + * #endif + * TypeA* req = NULL; <-- pointer always declared + * TypeB* resp = NULL; + * ... + * CALIPTRA_ALLOC(TypeA, req_s, req); + * CALIPTRA_ALLOC(TypeB, resp_s, resp); + * if (CALIPTRA_OOM(req) || CALIPTRA_OOM(resp)) { ret = MEMORY_E; goto lbl; } + * ... + * lbl: + * CALIPTRA_FREE(req); + * CALIPTRA_FREE(resp); + * + * In the stack path, CALIPTRA_ALLOC(T, buf, ptr) assigns ptr = &buf. + * The T and buf arguments are silently discarded in the heap path. + * ========================================================================= */ +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + #define CALIPTRA_ALLOC(T, buf, ptr) (ptr) = &(buf) + #define CALIPTRA_FREE(ptr) ((void)(ptr)) + /* Stack-buffers path: the buffer is on the caller's stack frame and will + * be released when the function returns. Zero it now anyway so the stack + * slot does not retain sensitive material across the return / future + * stack-frame reuse. */ + #define CALIPTRA_FREE_SENSITIVE(ptr) \ + do { if ((ptr) != NULL) wc_ForceZero((ptr), sizeof(*(ptr))); } while(0) + #define CALIPTRA_OOM(ptr) (0) +#else + #define CALIPTRA_ALLOC(T, buf, ptr) \ + (ptr) = (T*)XMALLOC(sizeof(T), NULL, DYNAMIC_TYPE_TMP_BUFFER) + #define CALIPTRA_FREE(ptr) \ + do { if ((ptr) != NULL) { \ + XFREE((ptr), NULL, DYNAMIC_TYPE_TMP_BUFFER); (ptr) = NULL; \ + } } while(0) + /* Heap-buffers path: zero the allocation before returning it to the + * allocator so the freed memory does not retain sensitive material. + * sizeof(*(ptr)) is well-defined because ptr is still typed at the + * call site (CmFooReq* / CmFooResp*). */ + #define CALIPTRA_FREE_SENSITIVE(ptr) \ + do { if ((ptr) != NULL) { \ + wc_ForceZero((ptr), sizeof(*(ptr))); \ + XFREE((ptr), NULL, DYNAMIC_TYPE_TMP_BUFFER); (ptr) = NULL; \ + } } while(0) + #define CALIPTRA_OOM(ptr) ((ptr) == NULL) +#endif + +/* ========================================================================= + * Internal helper: SHA algorithm ID from wolfSSL hash type + * ========================================================================= */ + +static int caliptra_sha_alg_from_type(enum wc_HashType hash_type) +{ + int type = (int)hash_type; + /* SHA-256 is not supported by Caliptra firmware; return -1 so the caller + * returns CRYPTOCB_UNAVAILABLE and wolfSSL falls back to software. */ + if (type == (int)WC_HASH_TYPE_SHA384) return CMB_SHA_ALG_SHA384; + if (type == (int)WC_HASH_TYPE_SHA512) return CMB_SHA_ALG_SHA512; + return -1; +} + +/* ========================================================================= + * Internal helper: extract devCtx pointer from hash object + * + * All supported hash structs (wc_Sha256, wc_Sha512 = wc_Sha384) include + * a void* devCtx field when WOLF_CRYPTO_CB is defined. This helper hides + * the type-specific casting. + * ========================================================================= */ + +static void** caliptra_hash_devctx_ptr(wc_CryptoInfo* info) +{ + /* SHA-256 is intentionally absent: Caliptra firmware does not support it. + * caliptra_sha_alg_from_type() returns -1 for SHA-256, so caliptra_hash() + * returns CRYPTOCB_UNAVAILABLE before this function is ever reached for + * that type. */ + switch (info->hash.type) { +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + if (info->hash.sha384 != NULL) + return (void**)&info->hash.sha384->devCtx; + break; +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + if (info->hash.sha512 != NULL) + return (void**)&info->hash.sha512->devCtx; + break; +#endif + default: + break; + } + return NULL; +} + +/* ========================================================================= + * RNG handler + * ========================================================================= */ + +static int caliptra_rng(wc_CryptoInfo* info) +{ + byte* output = info->rng.out; + word32 remaining = info->rng.sz; + word32 chunk; + int ret; + +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmRandomGenerateReq req_s; + CmRandomGenerateResp resp_s; +#endif + CmRandomGenerateReq* req = NULL; + CmRandomGenerateResp* resp = NULL; + + if (output == NULL || remaining == 0) + return BAD_FUNC_ARG; + + CALIPTRA_ALLOC(CmRandomGenerateReq, req_s, req); + CALIPTRA_ALLOC(CmRandomGenerateResp, resp_s, resp); + if (CALIPTRA_OOM(req) || CALIPTRA_OOM(resp)) { + ret = MEMORY_E; + goto rng_done; + } + + ret = 0; + while (remaining > 0) { + chunk = (remaining < (word32)CMB_MAX_DATA_SIZE) + ? remaining + : (word32)CMB_MAX_DATA_SIZE; + + XMEMSET(req, 0, sizeof(*req)); + req->size = HTOLE32(chunk); + + req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_RANDOM_GENERATE, req, + (word32)sizeof(*req))); + XMEMSET(resp, 0, sizeof(*resp)); + ret = caliptra_mailbox_exec(CM_RANDOM_GENERATE, + req, (word32)sizeof(*req), + resp, (word32)sizeof(*resp)); + if (ret != 0) + goto rng_done; + + if (LE32TOH(resp->hdr.fips_status) != 0) { + ret = WC_HW_E; + goto rng_done; + } + + /* Firmware may return fewer bytes than requested; trust data_len. + * The upper bound check (data_len > chunk) also bounds data_len + * below sizeof(resp->data): chunk <= CMB_MAX_DATA_SIZE and + * resp->data is exactly CMB_MAX_DATA_SIZE bytes, so the XMEMCPY + * below cannot read past the end of resp->data. */ + { + word32 rx_len = LE32TOH(resp->hdr.data_len); + if (rx_len == 0 || rx_len > chunk) { + ret = WC_HW_E; + goto rng_done; + } + XMEMCPY(output, resp->data, rx_len); + output += rx_len; + remaining -= rx_len; + } + } + +rng_done: + /* Defense-in-depth: on any error, zero the partial output buffer. + * The chunked loop above may have written some random bytes before + * failing (e.g. a multi-chunk request where the second chunk fails); + * those bytes are conceptually "leaked randomness" but more + * importantly a caller that ignores the error return could consume + * a partially-filled buffer. Mirrors the wc_caliptra_hmac error- + * zeroing pattern. */ + if (ret != 0 && info->rng.out != NULL && info->rng.sz > 0) + wc_ForceZero(info->rng.out, info->rng.sz); + CALIPTRA_FREE(req); + CALIPTRA_FREE(resp); + return ret; +} + +/* ========================================================================= + * SHA streaming handler — helpers + * + * caliptra_sha_do_init: allocate CaliptraShaCtx, send CM_SHA_INIT with an + * optional first data chunk, store the opaque context cookie returned by + * the firmware. Owns init_req/init_resp entirely; frees them before return. + * On failure *devctx_ptr is cleared and the allocated sha_ctx is freed. + * ========================================================================= */ + +static int caliptra_sha_do_init(void** devctx_ptr, int alg_id, + const byte* data, word32 data_sz) +{ + CaliptraShaCtx* sha_ctx; +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmShaInitReq init_req_s; + CmShaInitResp init_resp_s; +#endif + CmShaInitReq* init_req = NULL; + CmShaInitResp* init_resp = NULL; + word32 actual_len; + int ret; + + if (data_sz > 0 && data == NULL) + return BAD_FUNC_ARG; + if (data_sz > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + + /* sha_ctx persists beyond this function (stored in hash->devCtx); + * it is always heap-allocated regardless of WOLFSSL_CALIPTRA_STATIC_BUFFERS. */ + sha_ctx = (CaliptraShaCtx*)XMALLOC(sizeof(CaliptraShaCtx), + NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (sha_ctx == NULL) + return MEMORY_E; + XMEMSET(sha_ctx, 0, sizeof(*sha_ctx)); + + CALIPTRA_ALLOC(CmShaInitReq, init_req_s, init_req); + CALIPTRA_ALLOC(CmShaInitResp, init_resp_s, init_resp); + if (CALIPTRA_OOM(init_req) || CALIPTRA_OOM(init_resp)) { + ret = MEMORY_E; + goto do_init_done; + } + + XMEMSET(init_req, 0, sizeof(*init_req)); + init_req->hash_algorithm = HTOLE32((word32)alg_id); + init_req->input_size = HTOLE32(data_sz); + if (data_sz > 0 && data != NULL) + XMEMCPY(init_req->input, data, data_sz); + + actual_len = (word32)(sizeof(*init_req) - CMB_MAX_DATA_SIZE + data_sz); + + init_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_SHA_INIT, + init_req, actual_len)); + XMEMSET(init_resp, 0, sizeof(*init_resp)); + ret = caliptra_mailbox_exec(CM_SHA_INIT, + init_req, actual_len, + init_resp, (word32)sizeof(*init_resp)); + if (ret != 0) goto do_init_done; + if (LE32TOH(init_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto do_init_done; } + + XMEMCPY(sha_ctx->context, init_resp->context, CMB_SHA_CONTEXT_SIZE); + +do_init_done: + CALIPTRA_FREE(init_req); + CALIPTRA_FREE(init_resp); + if (ret == 0) { + *devctx_ptr = sha_ctx; + } + else { + wc_ForceZero(sha_ctx, sizeof(*sha_ctx)); + XFREE(sha_ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + return ret; +} + +/* ========================================================================= + * SHA streaming handler + * + * State machine uses CaliptraShaCtx* stored in hash_obj->devCtx. + * in != NULL → update (has_input): CM_SHA_INIT or CM_SHA_UPDATE + * in == NULL && digest != NULL → final: CM_SHA_FINAL; free devCtx + * ========================================================================= */ + +static int caliptra_hash(wc_CryptoInfo* info) +{ + void** devctx_ptr; + CaliptraShaCtx* sha_ctx; + int alg_id; + int ret = 0; + int has_input; + +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmShaUpdateReq upd_req_s; + CmShaUpdateResp upd_resp_s; + CmShaFinalReq final_req_s; + CmShaFinalResp final_resp_s; +#endif + CmShaUpdateReq* upd_req = NULL; + CmShaUpdateResp* upd_resp = NULL; + CmShaFinalReq* final_req = NULL; + CmShaFinalResp* final_resp = NULL; + + word32 actual_len; + + alg_id = caliptra_sha_alg_from_type(info->hash.type); + if (alg_id < 0) + return CRYPTOCB_UNAVAILABLE; + + devctx_ptr = caliptra_hash_devctx_ptr(info); + if (devctx_ptr == NULL) + return BAD_FUNC_ARG; + + sha_ctx = (CaliptraShaCtx*)*devctx_ptr; + has_input = (info->hash.in != NULL); + /* has_input true → Update path: add data (inSz may be 0 for empty chunk). + * has_input false → Final path if digest != NULL; no-op if both are NULL. */ + + if (has_input && info->hash.inSz > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + + if (has_input) { + /* ---- Update path ---- */ + if (sha_ctx == NULL) { + /* First update: send CM_SHA_INIT carrying the first data chunk. */ + ret = caliptra_sha_do_init(devctx_ptr, alg_id, + info->hash.in, info->hash.inSz); + if (ret != 0) goto hash_done; + sha_ctx = (CaliptraShaCtx*)*devctx_ptr; + } + else { + /* Subsequent update: call CM_SHA_UPDATE. */ + CALIPTRA_ALLOC(CmShaUpdateReq, upd_req_s, upd_req); + CALIPTRA_ALLOC(CmShaUpdateResp, upd_resp_s, upd_resp); + if (CALIPTRA_OOM(upd_req) || CALIPTRA_OOM(upd_resp)) { + ret = MEMORY_E; + goto hash_done; + } + + XMEMSET(upd_req, 0, sizeof(*upd_req)); + XMEMCPY(upd_req->context, sha_ctx->context, CMB_SHA_CONTEXT_SIZE); + upd_req->input_size = HTOLE32(info->hash.inSz); + if (info->hash.inSz > 0) + XMEMCPY(upd_req->input, info->hash.in, info->hash.inSz); + + actual_len = (word32)(sizeof(*upd_req) - CMB_MAX_DATA_SIZE + + info->hash.inSz); + + upd_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_SHA_UPDATE, + upd_req, actual_len)); + XMEMSET(upd_resp, 0, sizeof(*upd_resp)); + ret = caliptra_mailbox_exec(CM_SHA_UPDATE, + upd_req, actual_len, + upd_resp, (word32)sizeof(*upd_resp)); + if (ret == 0 && LE32TOH(upd_resp->hdr.fips_status) != 0) + ret = WC_HW_E; + if (ret == 0) + XMEMCPY(sha_ctx->context, upd_resp->context, + CMB_SHA_CONTEXT_SIZE); + + if (ret != 0) goto hash_done; + } + } + else if (info->hash.digest != NULL) { + /* ---- Final path ---- */ + word32 digest_len; + + /* sha_ctx is NULL when Final is called without any prior Update + * (empty message). Send CM_SHA_INIT with empty data first. */ + if (sha_ctx == NULL) { + ret = caliptra_sha_do_init(devctx_ptr, alg_id, NULL, 0); + if (ret != 0) goto hash_done; + sha_ctx = (CaliptraShaCtx*)*devctx_ptr; + } + + CALIPTRA_ALLOC(CmShaFinalReq, final_req_s, final_req); + CALIPTRA_ALLOC(CmShaFinalResp, final_resp_s, final_resp); + if (CALIPTRA_OOM(final_req) || CALIPTRA_OOM(final_resp)) { + ret = MEMORY_E; + goto hash_done; + } + + XMEMSET(final_req, 0, sizeof(*final_req)); + XMEMCPY(final_req->context, sha_ctx->context, CMB_SHA_CONTEXT_SIZE); + final_req->input_size = 0; /* no last-chunk data */ + + /* Trim: no trailing data bytes */ + actual_len = (word32)(sizeof(*final_req) - CMB_MAX_DATA_SIZE); + + final_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_SHA_FINAL, + final_req, actual_len)); + XMEMSET(final_resp, 0, sizeof(*final_resp)); + ret = caliptra_mailbox_exec(CM_SHA_FINAL, + final_req, actual_len, + final_resp, (word32)sizeof(*final_resp)); + if (ret != 0) goto hash_done; + if (LE32TOH(final_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto hash_done; } + + /* Copy digest to caller's output buffer */ + { + word32 expected_digest_len = (info->hash.type == WC_HASH_TYPE_SHA384) + ? 48u : 64u; + digest_len = LE32TOH(final_resp->hdr.data_len); + if (digest_len != expected_digest_len) { + ret = WC_HW_E; + goto hash_done; + } + XMEMCPY(info->hash.digest, final_resp->hash, expected_digest_len); + } + + /* Clean up per-object state */ + wc_ForceZero(sha_ctx, sizeof(*sha_ctx)); + XFREE(sha_ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *devctx_ptr = NULL; + sha_ctx = NULL; + } + +hash_done: + CALIPTRA_FREE(upd_req); + CALIPTRA_FREE(upd_resp); + CALIPTRA_FREE(final_req); + CALIPTRA_FREE(final_resp); + + if (ret != 0) { + /* On any error, release sha_ctx and clear the devCtx pointer so + * subsequent calls do not use stale state. The WC_ALGO_TYPE_FREE + * handler covers the abort-before-final case when the application + * frees the hash object without completing the digest. */ + if (*devctx_ptr != NULL) { + wc_ForceZero(*devctx_ptr, sizeof(CaliptraShaCtx)); + XFREE(*devctx_ptr, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *devctx_ptr = NULL; + } + } + + return ret; +} + +/* ========================================================================= + * HMAC handler (single-shot) + * + * The CaliptraCmk for the HMAC key must be stored in hmac->devCtx by the + * application before calling wc_HmacUpdate/Final. + * ========================================================================= */ + +static int caliptra_hmac(wc_CryptoInfo* info) +{ + /* CryptoCb convention: digest == NULL → Update call; digest != NULL → Final call. */ + Hmac* hmac = info->hmac.hmac; + + if (hmac == NULL) + return BAD_FUNC_ARG; + if (hmac->devCtx == NULL) + return CRYPTOCB_UNAVAILABLE; + /* devCtx is set: a Caliptra key is loaded. Caliptra HMAC is single-shot + * only; streaming via wc_HmacUpdate/Final is not supported. + * + * This function MUST return WC_HW_E, not CRYPTOCB_UNAVAILABLE. + * wc_HmacUpdate() and wc_HmacFinal() (hmac.c) gate software fallback on: + * if (ret != CRYPTOCB_UNAVAILABLE) return ret; + * WC_HW_E satisfies that test, so both calls return WC_HW_E immediately + * without executing any software HMAC. Returning CRYPTOCB_UNAVAILABLE + * instead would let Final fall through to software HMAC over an unkeyed, + * empty state — a silent authentication bypass. See the static_assert + * above (WC_HW_E != CRYPTOCB_UNAVAILABLE) which makes this a compile-time + * guarantee rather than a documented assumption. + * + * wc_HmacFree() does not reach this function: the dispatcher handles + * WC_ALGO_TYPE_FREE independently (returns 0 for non-hash types). */ + WOLFSSL_MSG("caliptra_hmac: Caliptra HMAC requires a single-shot " + "request; streaming via CryptoCb is not supported. " + "Use wc_caliptra_hmac() instead."); + return WC_HW_E; +} + +/* ========================================================================= + * wc_caliptra_hmac — public single-shot HMAC API + * ========================================================================= */ + +int wc_caliptra_hmac(const CaliptraCmk* cmk, + int hash_type, + const byte* msg, + word32 msg_len, + byte* mac_out, + word32* mac_len) +{ +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmHmacReq req_s; + CmHmacResp resp_s; +#endif + CmHmacReq* req = NULL; + CmHmacResp* resp = NULL; + word32 alg; + word32 digest_sz; + word32 actual_len; + int ret = 0; + + if (cmk == NULL || mac_out == NULL || mac_len == NULL) + return BAD_FUNC_ARG; + if (msg == NULL && msg_len > 0) + return BAD_FUNC_ARG; + if (msg_len > (word32)CMB_MAX_DATA_SIZE) + return BUFFER_E; + + switch (hash_type) { +#ifdef WOLFSSL_SHA384 + case WC_SHA384: + alg = CMB_SHA_ALG_SHA384; + digest_sz = WC_SHA384_DIGEST_SIZE; + break; +#endif +#ifdef WOLFSSL_SHA512 + case WC_SHA512: + alg = CMB_SHA_ALG_SHA512; + digest_sz = WC_SHA512_DIGEST_SIZE; + break; +#endif + default: + return BAD_FUNC_ARG; + } + if (*mac_len < digest_sz) + return BUFFER_E; + + CALIPTRA_ALLOC(CmHmacReq, req_s, req); + CALIPTRA_ALLOC(CmHmacResp, resp_s, resp); + if (CALIPTRA_OOM(req) || CALIPTRA_OOM(resp)) { + ret = MEMORY_E; + goto hmac_done; + } + + XMEMSET(req, 0, sizeof(*req)); + XMEMCPY(&req->cmk, cmk, sizeof(CaliptraCmk)); + req->hash_algorithm = HTOLE32(alg); + req->data_size = HTOLE32(msg_len); + if (msg_len > 0) + XMEMCPY(req->data, msg, msg_len); + + actual_len = (word32)(sizeof(*req) - CMB_MAX_DATA_SIZE + msg_len); + req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_HMAC, req, actual_len)); + + XMEMSET(resp, 0, sizeof(*resp)); + ret = caliptra_mailbox_exec(CM_HMAC, req, actual_len, + resp, (word32)sizeof(*resp)); + if (ret != 0) goto hmac_done; + if (LE32TOH(resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto hmac_done; } + /* Validate firmware-reported MAC length matches the algorithm digest + * size before copying. Same pattern as RNG (line ~316) and SHA Final + * (line ~542): a mismatch indicates firmware misbehavior and is a + * WC_HW_E rather than a silent truncation/overrun. */ + if (LE32TOH(resp->hdr.data_len) != digest_sz) { + ret = WC_HW_E; + goto hmac_done; + } + + XMEMCPY(mac_out, resp->mac, digest_sz); + *mac_len = digest_sz; + +hmac_done: + /* req carries the input message; resp carries the MAC. Both can + * carry sensitive material for HMAC-based KDF or password use. */ + CALIPTRA_FREE_SENSITIVE(req); + CALIPTRA_FREE_SENSITIVE(resp); + if (ret != 0) + XMEMSET(mac_out, 0, digest_sz); + return ret; +} + +/* ========================================================================= + * AES-GCM encrypt (single wolfSSL call → 3 Caliptra mailbox calls) + * + * The CaliptraCmk for the AES key must be stored in aes->devCtx. + * Caliptra generates the IV; the server-generated IV is stored in + * aes->reg[0..2] for the caller to retrieve (see README.md limitation). + * ========================================================================= */ + +static int caliptra_aesgcm_encrypt(wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aesgcm_enc.aes; + const byte* in = info->cipher.aesgcm_enc.in; + byte* out = info->cipher.aesgcm_enc.out; + word32 sz = info->cipher.aesgcm_enc.sz; + const byte* iv = info->cipher.aesgcm_enc.iv; + word32 ivSz = info->cipher.aesgcm_enc.ivSz; + const byte* authIn = info->cipher.aesgcm_enc.authIn; + word32 authInSz = info->cipher.aesgcm_enc.authInSz; + byte* authTag = info->cipher.aesgcm_enc.authTag; + word32 authTagSz = info->cipher.aesgcm_enc.authTagSz; + +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmAesGcmEncryptInitReq init_req_s; + CmAesGcmEncryptInitResp init_resp_s; + CmAesGcmEncryptUpdateReq upd_req_s; + CmAesGcmEncryptUpdateResp upd_resp_s; + CmAesGcmEncryptFinalReq final_req_s; + CmAesGcmEncryptFinalResp final_resp_s; +#endif + CmAesGcmEncryptInitReq* init_req = NULL; + CmAesGcmEncryptInitResp* init_resp = NULL; + CmAesGcmEncryptUpdateReq* upd_req = NULL; + CmAesGcmEncryptUpdateResp* upd_resp = NULL; + CmAesGcmEncryptFinalReq* final_req = NULL; + CmAesGcmEncryptFinalResp* final_resp = NULL; + + const byte* ctx_for_final = NULL; /* points to context to pass to Final */ + word32 actual_len; + word32 out_offset = 0; + int ret = 0; + + if (aes == NULL) + return BAD_FUNC_ARG; + if (aes->devCtx == NULL) + return CRYPTOCB_UNAVAILABLE; + if (authInSz > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + if (sz > (word32)CMB_MAX_DATA_SIZE) + return BUFFER_E; /* Caliptra single-Update limit; chunked input not yet supported */ + if (sz > 0 && in == NULL) + return BAD_FUNC_ARG; + /* Symmetric with the in NULL check above: if there is plaintext to + * encrypt then there must be somewhere to write the ciphertext. + * wolfSSL's core path validates this, defense-in-depth. */ + if (sz > 0 && out == NULL) + return BAD_FUNC_ARG; + /* Defense-in-depth: wolfSSL's core wc_AesGcmEncrypt validates the tag + * arguments before dispatching to CryptoCb, but a caller bypassing the + * standard API or using a future CryptoCb client must still see an + * error rather than silently producing unauthenticated ciphertext. */ + if (authTag == NULL || authTagSz == 0) + return BAD_FUNC_ARG; + + /* Caliptra generates the IV server-side; the caller-supplied iv/ivSz are + * silently ignored. wolfSSL's wc_AesGcmEncrypt requires ivSz > 0 as a + * precondition, so callers must pass a placeholder (e.g., a 12-byte + * zero buffer). After a successful return, call wc_caliptra_aesgcm_get_iv() + * to retrieve the actual 12-byte firmware-generated IV before passing it + * to wc_AesGcmDecrypt(). */ + (void)iv; + (void)ivSz; + + /* --- Step 1: Encrypt Init (AAD, CMK; IV generated by Caliptra) --- */ + CALIPTRA_ALLOC(CmAesGcmEncryptInitReq, init_req_s, init_req); + CALIPTRA_ALLOC(CmAesGcmEncryptInitResp, init_resp_s, init_resp); + if (CALIPTRA_OOM(init_req) || CALIPTRA_OOM(init_resp)) { ret = MEMORY_E; goto enc_done; } + + XMEMSET(init_req, 0, sizeof(*init_req)); + init_req->flags = 0; + XMEMCPY(&init_req->cmk, aes->devCtx, sizeof(CaliptraCmk)); + init_req->aad_size = HTOLE32(authInSz); + if (authInSz > 0 && authIn != NULL) + XMEMCPY(init_req->aad, authIn, authInSz); + + actual_len = (word32)(sizeof(*init_req) - CMB_MAX_DATA_SIZE + authInSz); + + init_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_AES_GCM_ENCRYPT_INIT, + init_req, actual_len)); + XMEMSET(init_resp, 0, sizeof(*init_resp)); + ret = caliptra_mailbox_exec(CM_AES_GCM_ENCRYPT_INIT, + init_req, actual_len, + init_resp, (word32)sizeof(*init_resp)); + if (ret != 0) goto enc_done; + if (LE32TOH(init_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto enc_done; } + + /* Write the Caliptra-generated IV into aes->reg (wolfSSL's per-object + * IV register) so it is available via wc_caliptra_aesgcm_get_iv(). + * Byte order: init_resp->iv is word32[3] typed [u32; 3] in the Rust API, + * serialised via zerocopy #[repr(C)] on RISC-V LE with no byte-swapping. + * LE u32 words in memory == the underlying byte sequence, so copying 12 + * raw bytes is correct. The caller retrieves these bytes unchanged via + * wc_caliptra_aesgcm_get_iv() and passes them back verbatim on decrypt. + * Confirmed: caliptra/api/src/mailbox.rs CmAesGcmEncryptInitResp.iv */ + XMEMCPY(aes->reg, init_resp->iv, sizeof(init_resp->iv)); + + /* --- Step 2: Encrypt Update (plaintext → ciphertext chunks) --- */ + if (sz > 0) { + CALIPTRA_ALLOC(CmAesGcmEncryptUpdateReq, upd_req_s, upd_req); + CALIPTRA_ALLOC(CmAesGcmEncryptUpdateResp, upd_resp_s, upd_resp); + if (CALIPTRA_OOM(upd_req) || CALIPTRA_OOM(upd_resp)) { ret = MEMORY_E; goto enc_done; } + + XMEMSET(upd_req, 0, sizeof(*upd_req)); + XMEMCPY(upd_req->context, init_resp->context, + CMB_AES_GCM_ENCRYPTED_CTX_SIZE); + upd_req->plaintext_size = HTOLE32(sz); + if (in != NULL) + XMEMCPY(upd_req->plaintext, in, sz); + + actual_len = (word32)(sizeof(*upd_req) - CMB_MAX_DATA_SIZE + sz); + + upd_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_AES_GCM_ENCRYPT_UPDATE, + upd_req, actual_len)); + XMEMSET(upd_resp, 0, sizeof(*upd_resp)); + ret = caliptra_mailbox_exec(CM_AES_GCM_ENCRYPT_UPDATE, + upd_req, actual_len, + upd_resp, (word32)sizeof(*upd_resp)); + if (ret != 0) goto enc_done; + if (LE32TOH(upd_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto enc_done; } + { + word32 ct_sz = LE32TOH(upd_resp->ciphertext_size); + if (ct_sz > (word32)CMB_MAX_AES_GCM_OUTPUT_SIZE) { + ret = WC_HW_E; goto enc_done; + } + + /* Copy ciphertext to output */ + if (ct_sz > 0 && out != NULL) { + word32 copy_sz = ct_sz; + if (copy_sz > sz) copy_sz = sz; + XMEMCPY(out, upd_resp->ciphertext, copy_sz); + out_offset = copy_sz; + } + } + ctx_for_final = upd_resp->context; + } + else { + /* No plaintext: pass Init context directly to Final; skip Update + * entirely to avoid a ~4 KB heap allocation for an empty message. */ + ctx_for_final = init_resp->context; + } + + /* --- Step 3: Encrypt Final (last block, get tag) --- */ + CALIPTRA_ALLOC(CmAesGcmEncryptFinalReq, final_req_s, final_req); + CALIPTRA_ALLOC(CmAesGcmEncryptFinalResp, final_resp_s, final_resp); + if (CALIPTRA_OOM(final_req) || CALIPTRA_OOM(final_resp)) { ret = MEMORY_E; goto enc_done; } + + XMEMSET(final_req, 0, sizeof(*final_req)); + XMEMCPY(final_req->context, ctx_for_final, + CMB_AES_GCM_ENCRYPTED_CTX_SIZE); + final_req->plaintext_size = 0; /* no remaining plaintext */ + + actual_len = (word32)(sizeof(*final_req) - CMB_MAX_DATA_SIZE); + + final_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_AES_GCM_ENCRYPT_FINAL, + final_req, actual_len)); + XMEMSET(final_resp, 0, sizeof(*final_resp)); + ret = caliptra_mailbox_exec(CM_AES_GCM_ENCRYPT_FINAL, + final_req, actual_len, + final_resp, (word32)sizeof(*final_resp)); + if (ret != 0) goto enc_done; + if (LE32TOH(final_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto enc_done; } + + /* Copy any final ciphertext bytes */ + { + word32 fin_ct_sz = LE32TOH(final_resp->ciphertext_size); + if (fin_ct_sz > 0 && out != NULL) { + /* out_offset <= sz is guaranteed: copy_sz on line ~592 is capped to sz. + * A firmware bug reporting more Final bytes than (sz - out_offset) + * indicates a protocol violation; reject it. */ + if (fin_ct_sz > (word32)CMB_MAX_AES_GCM_OUTPUT_SIZE || + fin_ct_sz > sz - out_offset) { + ret = WC_HW_E; + goto enc_done; + } + XMEMCPY(out + out_offset, final_resp->ciphertext, fin_ct_sz); + } + } + + /* Copy authentication tag (tag[4] = u32[4] = 16 bytes) */ + if (authTag != NULL && authTagSz >= 16) { + XMEMCPY(authTag, final_resp->tag, 16); + } + else if (authTag != NULL && authTagSz > 0 && authTagSz < 16) { + XMEMCPY(authTag, final_resp->tag, authTagSz); + } + +enc_done: + /* On encrypt error, clear the IV that was stashed in aes->reg at line + * ~767 above. A misbehaving caller that ignores the encrypt error + * and calls wc_caliptra_aesgcm_get_iv() afterwards will then see an + * all-zero IV, which causes wc_AesGcmDecrypt to fail GMAC verification + * downstream rather than silently using a stale or partially-written + * value. */ + if (ret != 0 && aes != NULL) + XMEMSET(aes->reg, 0, 12); + + /* init_req: aad; upd_req: plaintext; upd_resp/final_resp: ciphertext + tag. + * Plaintext is the obvious sensitive material; ciphertext less so but + * may still be sensitive to side-channel reuse. Zero everything. */ + CALIPTRA_FREE_SENSITIVE(init_req); + CALIPTRA_FREE_SENSITIVE(init_resp); + CALIPTRA_FREE_SENSITIVE(upd_req); + CALIPTRA_FREE_SENSITIVE(upd_resp); + CALIPTRA_FREE_SENSITIVE(final_req); + CALIPTRA_FREE_SENSITIVE(final_resp); + return ret; +} + +/* ========================================================================= + * AES-GCM decrypt (single wolfSSL call → 3 Caliptra mailbox calls) + * + * The CaliptraCmk for the AES key must be stored in aes->devCtx. + * The caller provides the IV (12 bytes) in aesgcm_dec.iv. + * ========================================================================= */ + +static int caliptra_aesgcm_decrypt(wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aesgcm_dec.aes; + const byte* in = info->cipher.aesgcm_dec.in; + byte* out = info->cipher.aesgcm_dec.out; + word32 sz = info->cipher.aesgcm_dec.sz; + const byte* iv = info->cipher.aesgcm_dec.iv; + word32 ivSz = info->cipher.aesgcm_dec.ivSz; + const byte* authIn = info->cipher.aesgcm_dec.authIn; + word32 authInSz = info->cipher.aesgcm_dec.authInSz; + const byte* authTag = info->cipher.aesgcm_dec.authTag; + word32 authTagSz = info->cipher.aesgcm_dec.authTagSz; + +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmAesGcmDecryptInitReq init_req_s; + CmAesGcmDecryptInitResp init_resp_s; + CmAesGcmDecryptUpdateReq upd_req_s; + CmAesGcmDecryptUpdateResp upd_resp_s; + CmAesGcmDecryptFinalReq final_req_s; + CmAesGcmDecryptFinalResp final_resp_s; +#endif + CmAesGcmDecryptInitReq* init_req = NULL; + CmAesGcmDecryptInitResp* init_resp = NULL; + CmAesGcmDecryptUpdateReq* upd_req = NULL; + CmAesGcmDecryptUpdateResp* upd_resp = NULL; + CmAesGcmDecryptFinalReq* final_req = NULL; + CmAesGcmDecryptFinalResp* final_resp = NULL; + + const byte* ctx_for_final = NULL; + word32 actual_len; + int ret = 0; + + if (aes == NULL) + return BAD_FUNC_ARG; + if (aes->devCtx == NULL) + return CRYPTOCB_UNAVAILABLE; + if (authInSz > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + if (sz > (word32)CMB_MAX_DATA_SIZE) + return BUFFER_E; /* Caliptra single-Update limit; chunked input not yet supported */ + if (sz > 0 && in == NULL) + return BAD_FUNC_ARG; + if (authTag == NULL && authTagSz > 0) + return BAD_FUNC_ARG; + if (iv == NULL) + return BAD_FUNC_ARG; + /* Caliptra's CM_AES_GCM_DECRYPT_INIT command takes a fixed 12-byte IV + * (mailbox.rs CmAesGcmDecryptInitReq.iv == [u32; 3]). Reject any + * shorter buffer up front rather than risking an OOB read at the + * XMEMCPY below. wolfSSL's core wc_AesGcmDecrypt() validates ivSz>0 + * but does not require ivSz==12. */ + if (ivSz < 12) + return BAD_FUNC_ARG; + + /* --- Step 1: Decrypt Init (IV, AAD, CMK) --- */ + CALIPTRA_ALLOC(CmAesGcmDecryptInitReq, init_req_s, init_req); + CALIPTRA_ALLOC(CmAesGcmDecryptInitResp, init_resp_s, init_resp); + if (CALIPTRA_OOM(init_req) || CALIPTRA_OOM(init_resp)) { ret = MEMORY_E; goto dec_done; } + + XMEMSET(init_req, 0, sizeof(*init_req)); + init_req->flags = 0; + XMEMCPY(&init_req->cmk, aes->devCtx, sizeof(CaliptraCmk)); + + /* Copy 12-byte caller IV into iv[3] (three u32 words). + * Byte order: the firmware reads init_req->iv as LEArray4x3 via a direct + * bitwise copy with no byte-swapping (cryptographic_mailbox.rs: + * `let cmd_iv: LEArray4x3 = cmd.iv.into()`). On RISC-V LE, raw memcpy + * of 12 bytes into a word32[3] field produces the correct LE u32 values. + * A caller who obtained the IV from wc_caliptra_aesgcm_get_iv() and passes + * it back here verbatim will have the IV interpreted identically to how + * Caliptra generated it. + * Confirmed: caliptra/api/src/mailbox.rs CmAesGcmDecryptInitReq.iv, + * caliptra/runtime/src/cryptographic_mailbox.rs cmd.iv.into() */ + XMEMCPY(init_req->iv, iv, 12); + + init_req->aad_size = HTOLE32(authInSz); + if (authInSz > 0 && authIn != NULL) + XMEMCPY(init_req->aad, authIn, authInSz); + + actual_len = (word32)(sizeof(*init_req) - CMB_MAX_DATA_SIZE + authInSz); + + init_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_AES_GCM_DECRYPT_INIT, + init_req, actual_len)); + XMEMSET(init_resp, 0, sizeof(*init_resp)); + ret = caliptra_mailbox_exec(CM_AES_GCM_DECRYPT_INIT, + init_req, actual_len, + init_resp, (word32)sizeof(*init_resp)); + if (ret != 0) goto dec_done; + if (LE32TOH(init_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto dec_done; } + + /* --- Step 2: Decrypt Update (ciphertext → plaintext) --- */ + if (sz > 0) { + CALIPTRA_ALLOC(CmAesGcmDecryptUpdateReq, upd_req_s, upd_req); + CALIPTRA_ALLOC(CmAesGcmDecryptUpdateResp, upd_resp_s, upd_resp); + if (CALIPTRA_OOM(upd_req) || CALIPTRA_OOM(upd_resp)) { ret = MEMORY_E; goto dec_done; } + + XMEMSET(upd_req, 0, sizeof(*upd_req)); + XMEMCPY(upd_req->context, init_resp->context, + CMB_AES_GCM_ENCRYPTED_CTX_SIZE); + upd_req->ciphertext_size = HTOLE32(sz); + if (in != NULL) + XMEMCPY(upd_req->ciphertext, in, sz); + + actual_len = (word32)(sizeof(*upd_req) - CMB_MAX_DATA_SIZE + sz); + + upd_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_AES_GCM_DECRYPT_UPDATE, + upd_req, actual_len)); + XMEMSET(upd_resp, 0, sizeof(*upd_resp)); + ret = caliptra_mailbox_exec(CM_AES_GCM_DECRYPT_UPDATE, + upd_req, actual_len, + upd_resp, (word32)sizeof(*upd_resp)); + if (ret != 0) goto dec_done; + if (LE32TOH(upd_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto dec_done; } + + /* Copy plaintext to output */ + { + word32 pt_sz = LE32TOH(upd_resp->plaintext_size); + /* Validate firmware-reported size before using it: defensive + * upper bound matching upd_resp->plaintext[CMB_MAX_DATA_SIZE]. + * Mirrors the encrypt path's check on ciphertext_size at the + * sibling site above. */ + if (pt_sz > (word32)CMB_MAX_DATA_SIZE) { + ret = WC_HW_E; goto dec_done; + } + if (pt_sz > 0 && out != NULL) { + word32 copy_sz = pt_sz; + if (copy_sz > sz) copy_sz = sz; + XMEMCPY(out, upd_resp->plaintext, copy_sz); + } + } + ctx_for_final = upd_resp->context; + } + else { + /* No ciphertext: pass Init context directly to Final; skip Update + * entirely to avoid a ~4 KB allocation for an empty message. */ + ctx_for_final = init_resp->context; + } + + /* --- Step 3: Decrypt Final (tag verification) --- */ + CALIPTRA_ALLOC(CmAesGcmDecryptFinalReq, final_req_s, final_req); + CALIPTRA_ALLOC(CmAesGcmDecryptFinalResp, final_resp_s, final_resp); + if (CALIPTRA_OOM(final_req) || CALIPTRA_OOM(final_resp)) { ret = MEMORY_E; goto dec_done; } + + XMEMSET(final_req, 0, sizeof(*final_req)); + XMEMCPY(final_req->context, ctx_for_final, + CMB_AES_GCM_ENCRYPTED_CTX_SIZE); + final_req->tag_len = HTOLE32((authTagSz <= 16) ? authTagSz : 16); + if (authTag != NULL) + XMEMCPY(final_req->tag, authTag, (authTagSz <= 16) ? authTagSz : 16); + final_req->ciphertext_size = 0; /* no remaining ciphertext */ + + /* Fixed size (no trailing data array to trim for empty final chunk) */ + actual_len = (word32)sizeof(*final_req) - CMB_MAX_DATA_SIZE; + + final_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_AES_GCM_DECRYPT_FINAL, + final_req, actual_len)); + XMEMSET(final_resp, 0, sizeof(*final_resp)); + ret = caliptra_mailbox_exec(CM_AES_GCM_DECRYPT_FINAL, + final_req, actual_len, + final_resp, (word32)sizeof(*final_resp)); + if (ret != 0) goto dec_done; + if (LE32TOH(final_resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto dec_done; } + + /* tag_verified == 1 means tags match (per CmAesGcmDecryptFinalResp + * header documentation). Use fail-safe semantics: anything other + * than the explicit "1 = OK" value is treated as an authentication + * failure. This protects against an out-of-spec firmware or a + * mailbox transport that fails to populate the field correctly. */ + if (LE32TOH(final_resp->tag_verified) != 1) + ret = AES_GCM_AUTH_E; + +dec_done: + /* Plaintext is written to out[] during Update (line ~979) before the + * tag is verified, and may also be partially written before any error + * after that point. On ANY error (auth fail, FIPS status, mailbox + * transport, OOM after Update) the caller MUST NOT use out[] — + * it is either unauthenticated or partially written. wolfSSL's + * convention is that on error, the contents of out[] are undefined. + * Defense-in-depth: zero out[] on every error so a caller that + * ignores the error return cannot accidentally consume unauthenticated + * or partial plaintext. */ + if (ret != 0 && out != NULL && sz > 0) + wc_ForceZero(out, sz); + + /* upd_req: ciphertext; upd_resp: plaintext. Plaintext is sensitive; + * ciphertext less so but zero everything for defense-in-depth. */ + CALIPTRA_FREE_SENSITIVE(init_req); + CALIPTRA_FREE_SENSITIVE(init_resp); + CALIPTRA_FREE_SENSITIVE(upd_req); + CALIPTRA_FREE_SENSITIVE(upd_resp); + CALIPTRA_FREE_SENSITIVE(final_req); + CALIPTRA_FREE_SENSITIVE(final_resp); + return ret; +} + +/* ========================================================================= + * Cipher dispatcher + * ========================================================================= */ + +static int caliptra_cipher(wc_CryptoInfo* info) +{ +#ifdef HAVE_AESGCM + if (info->cipher.type == WC_CIPHER_AES_GCM) { + if (info->cipher.enc) + return caliptra_aesgcm_encrypt(info); + else + return caliptra_aesgcm_decrypt(info); + } +#endif + return CRYPTOCB_UNAVAILABLE; +} + +/* ========================================================================= + * ECDSA Sign handler + * + * key->devCtx must hold a CaliptraCmk* for the private key. + * Output is DER-encoded: raw r||s from Caliptra is converted via + * wc_ecc_rs_raw_to_sig(). + * ========================================================================= */ + +static int caliptra_ecdsa_sign(wc_CryptoInfo* info) +{ + ecc_key* key = info->pk.eccsign.key; + const byte* hash = info->pk.eccsign.in; + word32 hashSz = info->pk.eccsign.inlen; + byte* sigOut = info->pk.eccsign.out; + word32* sigLen = info->pk.eccsign.outlen; + +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmEcdsaSignReq req_s; + CmEcdsaSignResp resp_s; +#endif + CmEcdsaSignReq* req = NULL; + CmEcdsaSignResp* resp = NULL; + word32 actual_len; + int ret = 0; + + if (key == NULL || hash == NULL || sigOut == NULL || sigLen == NULL) + return BAD_FUNC_ARG; + if (key->devCtx == NULL) + return CRYPTOCB_UNAVAILABLE; + if (hashSz > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + /* Caliptra ECDSA only implements P-384; reject other curves up front + * rather than relying on the firmware to reject them after a round + * trip. key->dp is the curve descriptor (set by wc_ecc_set_curve or + * wc_ecc_import_*); dp->size == 48 is the canonical P-384 indicator + * used elsewhere in wolfcrypt/src/ecc.c. */ + if (key->dp == NULL || key->dp->size != 48) + return BAD_FUNC_ARG; + + CALIPTRA_ALLOC(CmEcdsaSignReq, req_s, req); + CALIPTRA_ALLOC(CmEcdsaSignResp, resp_s, resp); + if (CALIPTRA_OOM(req) || CALIPTRA_OOM(resp)) { ret = MEMORY_E; goto sign_done; } + + XMEMSET(req, 0, sizeof(*req)); + XMEMCPY(&req->cmk, key->devCtx, sizeof(CaliptraCmk)); + req->message_size = HTOLE32(hashSz); + if (hashSz > 0) + XMEMCPY(req->message, hash, hashSz); + + actual_len = (word32)(sizeof(*req) - CMB_MAX_DATA_SIZE + hashSz); + + req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_ECDSA_SIGN, req, actual_len)); + XMEMSET(resp, 0, sizeof(*resp)); + ret = caliptra_mailbox_exec(CM_ECDSA_SIGN, + req, actual_len, + resp, (word32)sizeof(*resp)); + if (ret != 0) goto sign_done; + if (LE32TOH(resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto sign_done; } + + /* Convert raw r||s (each 48 bytes, P-384) to DER-encoded signature. */ + ret = wc_ecc_rs_raw_to_sig(resp->signature_r, 48, + resp->signature_s, 48, + sigOut, sigLen); + +sign_done: + /* req: message digest (intermediate signing material); + * resp: signature components. Zero both. */ + CALIPTRA_FREE_SENSITIVE(req); + CALIPTRA_FREE_SENSITIVE(resp); + return ret; +} + +/* ========================================================================= + * ECDSA Verify handler + * + * Caliptra requires a CMK reference for the public key. There are two paths: + * + * (a) key->devCtx holds a CaliptraCmk*: the pre-imported CMK is used directly. + * The application must call wc_caliptra_import_key(Qx||Qy, 96, + * CMB_KEY_USAGE_ECDSA, &cmk) and store the resulting CaliptraCmk in + * key->devCtx before calling wc_EccVerify(). + * + * (b) key->devCtx is NULL: returns CRYPTOCB_UNAVAILABLE so wolfSSL falls back + * to software ECC verify using the raw public key coordinates in key. + * ========================================================================= */ + +static int caliptra_ecdsa_verify(wc_CryptoInfo* info) +{ + ecc_key* key = info->pk.eccverify.key; + const byte* sig = info->pk.eccverify.sig; + word32 sigLen = info->pk.eccverify.siglen; + const byte* hash = info->pk.eccverify.hash; + word32 hashSz = info->pk.eccverify.hashlen; + int* res = info->pk.eccverify.res; + + /* r and s raw buffers (P-384: 48 bytes each) */ + byte r[48], s[48]; + word32 rLen = 48, sLen = 48; + +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmEcdsaVerifyReq ver_req_s; + CmEcdsaVerifyResp ver_resp_s; +#endif + CmEcdsaVerifyReq* ver_req = NULL; + CmEcdsaVerifyResp* ver_resp = NULL; + + word32 actual_len; + int ret = 0; + + if (key == NULL || sig == NULL || hash == NULL || res == NULL) + return BAD_FUNC_ARG; + if (hashSz > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + + /* Return CRYPTOCB_UNAVAILABLE before any work if no CMK is loaded. + * Import the public key with wc_caliptra_import_key(Qx||Qy, 96, + * CMB_KEY_USAGE_ECDSA, &cmk) and store the CaliptraCmk in key->devCtx + * before calling wc_EccVerify(). If key->devCtx is NULL, wolfSSL + * performs software ECC verification instead. + * See README.md §"ECDSA verify" for the full workflow. */ + if (key->devCtx == NULL) { + WOLFSSL_MSG("caliptra_ecdsa_verify: no pre-imported CMK; " + "falling back to software verify"); + return CRYPTOCB_UNAVAILABLE; + } + + *res = 0; /* default: invalid */ + + /* Decode DER signature to raw r, s (pure ASN operation; no CryptoCb dispatch). */ + XMEMSET(r, 0, sizeof(r)); + XMEMSET(s, 0, sizeof(s)); + ret = wc_ecc_sig_to_rs(sig, sigLen, r, &rLen, s, &sLen); + if (ret != 0) + return ret; + if (rLen > 48 || sLen > 48) + return BAD_FUNC_ARG; + + /* Left-pad r and s to exactly 48 bytes if shorter */ + if (rLen < 48) { + byte tmp[48]; + XMEMSET(tmp, 0, 48); + XMEMCPY(tmp + (48 - rLen), r, rLen); + XMEMCPY(r, tmp, 48); + rLen = 48; + } + if (sLen < 48) { + byte tmp[48]; + XMEMSET(tmp, 0, 48); + XMEMCPY(tmp + (48 - sLen), s, sLen); + XMEMCPY(s, tmp, 48); + sLen = 48; + } + /* --- Verify --- */ + CALIPTRA_ALLOC(CmEcdsaVerifyReq, ver_req_s, ver_req); + CALIPTRA_ALLOC(CmEcdsaVerifyResp, ver_resp_s, ver_resp); + if (CALIPTRA_OOM(ver_req) || CALIPTRA_OOM(ver_resp)) { ret = MEMORY_E; goto ver_cleanup; } + + XMEMSET(ver_req, 0, sizeof(*ver_req)); + XMEMCPY(&ver_req->cmk, key->devCtx, sizeof(CaliptraCmk)); + XMEMCPY(ver_req->signature_r, r, 48); + XMEMCPY(ver_req->signature_s, s, 48); + ver_req->message_size = HTOLE32(hashSz); + if (hashSz > 0 && hash != NULL) + XMEMCPY(ver_req->message, hash, hashSz); + + actual_len = (word32)(sizeof(*ver_req) - CMB_MAX_DATA_SIZE + hashSz); + + ver_req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_ECDSA_VERIFY, + ver_req, actual_len)); + XMEMSET(ver_resp, 0, sizeof(*ver_resp)); + ret = caliptra_mailbox_exec(CM_ECDSA_VERIFY, + ver_req, actual_len, + ver_resp, (word32)sizeof(*ver_resp)); + + /* Caliptra firmware signals ECDSA verification failure in two ways: + * + * Real hardware: cm_ecdsa_verify() returns + * Err(CaliptraError::RUNTIME_MAILBOX_SIGNATURE_MISMATCH), which the + * runtime translates to MboxStatusE::CmdFailure with no response + * written. The transport layer (caliptra_mailbox_exec) detects + * CmdFailure and MUST return SIG_VERIFY_E (not a generic error) so + * the port can distinguish verification failure from transport errors. + * + * Simulator / transports that mimic response-header style: + * caliptra_mailbox_exec returns 0 and the result is encoded in + * ver_resp->hdr.fips_status (0 = valid, non-zero = invalid). + * + * Source: caliptra/runtime/src/cryptographic_mailbox.rs: cm_ecdsa_verify() + * (Ecc384Result::Success / Ecc384Result::SigVerifyFailed paths) + * caliptra/api/src/mailbox.rs: MailboxRespHeader::FIPS_STATUS_APPROVED = 0 */ + if (ret == SIG_VERIFY_E) { + /* Hardware transport: signature cryptographically invalid. + * Translate to CryptoCb convention: ret=0, *res=0. */ + *res = 0; + ret = 0; + goto ver_cleanup; + } + if (ret != 0) goto ver_cleanup; + + /* Simulator / response-header path: result in fips_status. */ + *res = (LE32TOH(ver_resp->hdr.fips_status) == 0) ? 1 : 0; + +ver_cleanup: + /* ver_req holds the CaliptraCmk and the hash to verify; both are + * sensitive (the CMK is an opaque handle that grants firmware access + * to the public key, the hash may carry pre-image leakage). Match + * the FREE_SENSITIVE discipline used by every other CMK-bearing + * request struct in this port (HMAC, AES-GCM, ECDSA sign, import). */ + CALIPTRA_FREE_SENSITIVE(ver_req); + CALIPTRA_FREE(ver_resp); + return ret; +} + +/* ========================================================================= + * PK dispatcher + * ========================================================================= */ + +static int caliptra_pk(wc_CryptoInfo* info) +{ + switch (info->pk.type) { +#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) + case WC_PK_TYPE_ECDSA_SIGN: + return caliptra_ecdsa_sign(info); +#endif +#if defined(HAVE_ECC) && defined(HAVE_ECC_VERIFY) + case WC_PK_TYPE_ECDSA_VERIFY: + return caliptra_ecdsa_verify(info); +#endif + /* ECDH is explicitly unsupported: Caliptra ECDH returns an opaque + * Cmk, not raw shared-secret bytes required by the wolfSSL interface. + * See README.md for details. */ + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +/* ========================================================================= + * Hash object free handler (WOLF_CRYPTO_CB_FREE) + * + * Called when the application frees a hash object (e.g. wc_Sha384Free()) + * that was using the Caliptra device. If a CaliptraShaCtx was allocated + * during a streaming hash that was aborted before Final, it must be freed + * here to avoid a heap leak. + * + * info->free.type is the wc_HashType; info->free.obj is the hash struct ptr. + * ========================================================================= */ + +/* WOLF_CRYPTO_CB_FREE: required to free CaliptraShaCtx on hash abort. + * Without this, wc_Sha384Free/wc_Sha512Free before Final leaks devCtx. */ +#ifdef WOLF_CRYPTO_CB_FREE +static int caliptra_hash_free(wc_CryptoInfo* info) +{ + void** devctx_ptr = NULL; + + /* SHA-256 is intentionally absent: see caliptra_hash_devctx_ptr(). */ + switch (info->free.type) { +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: { + wc_Sha384* s = (wc_Sha384*)info->free.obj; + if (s != NULL) devctx_ptr = (void**)&s->devCtx; + break; + } +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: { + wc_Sha512* s = (wc_Sha512*)info->free.obj; + if (s != NULL) devctx_ptr = (void**)&s->devCtx; + break; + } +#endif + default: + break; + } + + if (devctx_ptr != NULL && *devctx_ptr != NULL) { + wc_ForceZero(*devctx_ptr, sizeof(CaliptraShaCtx)); + XFREE(*devctx_ptr, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *devctx_ptr = NULL; + } + return 0; +} +#endif /* WOLF_CRYPTO_CB_FREE */ + +/* ========================================================================= + * Top-level dispatcher + * ========================================================================= */ + +int wc_caliptra_cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + if (info == NULL) + return BAD_FUNC_ARG; + (void)devId; + (void)ctx; + + switch (info->algo_type) { + case WC_ALGO_TYPE_RNG: return caliptra_rng(info); + case WC_ALGO_TYPE_HASH: return caliptra_hash(info); + /* WC_ALGO_TYPE_HMAC is handled (not CRYPTOCB_UNAVAILABLE) so that + * when a Caliptra CMK is loaded in hmac->devCtx a streaming call + * returns WC_HW_E rather than silently falling back to software + * HMAC with no key. Use wc_caliptra_hmac() for the actual MAC. */ + case WC_ALGO_TYPE_HMAC: return caliptra_hmac(info); + case WC_ALGO_TYPE_CIPHER: return caliptra_cipher(info); + case WC_ALGO_TYPE_PK: return caliptra_pk(info); +#ifdef WOLF_CRYPTO_CB_FREE + case WC_ALGO_TYPE_FREE: + /* Free any per-object state allocated for streaming operations. */ + if (info->free.algo == WC_ALGO_TYPE_HASH) + return caliptra_hash_free(info); + return 0; +#endif /* WOLF_CRYPTO_CB_FREE */ + default: return CRYPTOCB_UNAVAILABLE; + } +} + +/* ========================================================================= + * Init / Cleanup + * ========================================================================= */ + +/* wc_caliptra_init / wc_caliptra_cleanup are defined as weak symbols so that + * platform-specific BSP code can override them without modifying wolfSSL. + * A platform that must open a device file, map MMIO, or verify mailbox + * reachability before first use should provide its own strong-linked + * definitions. The default no-op is correct for simulator and bare-metal + * environments where the mailbox is available unconditionally. + * + * Note: device registration is NOT performed here. After wc_caliptra_init() + * succeeds, the application must register the callback: + * ret = wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, wc_caliptra_cb, NULL); + * Separating registration from init lets the integrator supply a custom + * ctx pointer if needed. */ +#ifdef WOLFSSL_CALIPTRA_SIM +/* Defined in wolfcrypt/src/port/caliptra/sim/caliptra_sim.c. Idempotent; + * not race-safe at first init, so call from a single thread before any + * worker thread invokes the mailbox. See caliptra_sim.c header. */ +extern int sim_mailbox_lock_init_once(void); +extern void sim_mailbox_lock_free_once(void); +#endif + +#if defined(__GNUC__) || defined(__clang__) +__attribute__((weak)) +#endif +int wc_caliptra_init(void) +{ +#ifdef WOLFSSL_CALIPTRA_SIM + return sim_mailbox_lock_init_once(); +#else + return 0; +#endif +} + +#if defined(__GNUC__) || defined(__clang__) +__attribute__((weak)) +#endif +int wc_caliptra_cleanup(void) +{ +#ifdef WOLFSSL_CALIPTRA_SIM + sim_mailbox_lock_free_once(); +#endif + return 0; +} + +word32 wc_caliptra_req_chksum(word32 cmd_id, const void* req, word32 req_len) +{ + return HTOLE32(caliptra_req_chksum(cmd_id, req, req_len)); +} + +/* ========================================================================= + * Key Import / Delete utilities + * ========================================================================= */ + +int wc_caliptra_aesgcm_get_iv(const Aes* aes, byte* iv_out, word32 iv_len) +{ + if (aes == NULL || iv_out == NULL || iv_len < 12) + return BAD_FUNC_ARG; + XMEMCPY(iv_out, aes->reg, 12); + return 0; +} + +int wc_caliptra_import_key(const byte* key_data, + word32 key_len, + word32 key_usage, + CaliptraCmk* out_cmk) +{ +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmImportReq req_s; + CmImportResp resp_s; +#endif + CmImportReq* req = NULL; + CmImportResp* resp = NULL; + word32 actual_len; + int ret = 0; + + if (key_data == NULL || out_cmk == NULL) + return BAD_FUNC_ARG; + if (key_len > (word32)CMB_MAX_DATA_SIZE) + return BAD_FUNC_ARG; + if (key_len == 0) + return BAD_FUNC_ARG; + /* Validate key_usage against the documented CMB_KEY_USAGE_* values. + * Catches typos at import time (when the caller still has source-level + * context for the bad value) rather than at use time (when the + * mailbox command rejects the CMK with WC_HW_E and a confusing + * error path). Accepts every firmware-defined value, including + * MLDSA/MLKEM which this port does not currently dispatch but which + * may be exposed via wc_caliptra_import_key for application-level + * use (e.g. attestation key material). */ + switch (key_usage) { + case CMB_KEY_USAGE_HMAC: + case CMB_KEY_USAGE_AES: + case CMB_KEY_USAGE_ECDSA: + case CMB_KEY_USAGE_MLDSA: + case CMB_KEY_USAGE_MLKEM: + break; + default: + return BAD_FUNC_ARG; + } + + CALIPTRA_ALLOC(CmImportReq, req_s, req); + CALIPTRA_ALLOC(CmImportResp, resp_s, resp); + if (CALIPTRA_OOM(req) || CALIPTRA_OOM(resp)) { + ret = MEMORY_E; + goto import_done; + } + + XMEMSET(req, 0, sizeof(*req)); + req->key_usage = HTOLE32(key_usage); + req->input_size = HTOLE32(key_len); + XMEMCPY(req->input, key_data, key_len); + + actual_len = (word32)(sizeof(*req) - CMB_MAX_DATA_SIZE + key_len); + + req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_IMPORT, req, actual_len)); + XMEMSET(resp, 0, sizeof(*resp)); + ret = caliptra_mailbox_exec(CM_IMPORT, + req, actual_len, + resp, (word32)sizeof(*resp)); + if (ret != 0) goto import_done; + if (LE32TOH(resp->hdr.fips_status) != 0) { ret = WC_HW_E; goto import_done; } + + XMEMCPY(out_cmk, &resp->cmk, sizeof(CaliptraCmk)); + +import_done: + /* req: raw key bytes (the most sensitive material in this port). + * resp: CMK handle (an attacker-useful primitive). Zero both. */ + CALIPTRA_FREE_SENSITIVE(req); + CALIPTRA_FREE_SENSITIVE(resp); + return ret; +} + +int wc_caliptra_delete_key(const CaliptraCmk* cmk) +{ +#ifdef WOLFSSL_CALIPTRA_STATIC_BUFFERS + CmDeleteReq req_s; + CmDeleteResp resp_s; +#endif + CmDeleteReq* req = NULL; + CmDeleteResp* resp = NULL; + int ret = 0; + + if (cmk == NULL) + return BAD_FUNC_ARG; + + CALIPTRA_ALLOC(CmDeleteReq, req_s, req); + CALIPTRA_ALLOC(CmDeleteResp, resp_s, resp); + if (CALIPTRA_OOM(req) || CALIPTRA_OOM(resp)) { + ret = MEMORY_E; + goto delete_done; + } + + XMEMSET(req, 0, sizeof(*req)); + XMEMCPY(&req->cmk, cmk, sizeof(CaliptraCmk)); + + req->hdr.chksum = HTOLE32(caliptra_req_chksum(CM_DELETE, req, (word32)sizeof(*req))); + XMEMSET(resp, 0, sizeof(*resp)); + ret = caliptra_mailbox_exec(CM_DELETE, + req, (word32)sizeof(*req), + resp, (word32)sizeof(*resp)); + if (ret != 0) goto delete_done; + if (LE32TOH(resp->hdr.fips_status) != 0) ret = WC_HW_E; + +delete_done: + CALIPTRA_FREE(req); + CALIPTRA_FREE(resp); + return ret; +} + +#endif /* defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) */ diff --git a/wolfcrypt/src/port/caliptra/sim/Makefile b/wolfcrypt/src/port/caliptra/sim/Makefile new file mode 100644 index 00000000000..4e25b7e9912 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/sim/Makefile @@ -0,0 +1,105 @@ +# Makefile for the Caliptra hw-model test harness +# +# Builds caliptra_test_bin using the hw-model C binding as the mailbox +# transport instead of the software simulator (caliptra_sim.c). +# +# Prerequisites: +# - wolfSSL must be built in the repo root with --enable-caliptra (or at +# minimum WOLFSSL_CALIPTRA + WOLF_CRYPTO_CB in CFLAGS). +# - ~/caliptra/target/debug/libcaliptra_hw_model_c_binding.a must exist +# (built by: cd ~/caliptra && cargo build -p caliptra-hw-model-c-binding) +# - ROM and firmware images at the paths below. +# +# Usage: +# make # build +# make run # build and run +# make clean + +# ------------------------------------------------------------------------- +# Repository root (three levels up from this Makefile's directory) +# ------------------------------------------------------------------------- +WOLFSSL_ROOT ?= $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/../../../../..) + +# ------------------------------------------------------------------------- +# Caliptra hw-model C binding +# ------------------------------------------------------------------------- +CALIPTRA_ROOT ?= $(HOME)/caliptra +HWMODEL_OUT = $(CALIPTRA_ROOT)/hw-model/c-binding/out +HWMODEL_LIB_DIR = $(CALIPTRA_ROOT)/target/debug +HWMODEL_LIB = $(HWMODEL_LIB_DIR)/libcaliptra_hw_model_c_binding.a + +# ROM and firmware images (debug/unprovisioned mode — zero fuses) +ROM_PATH ?= $(CALIPTRA_ROOT)/rom/ci_frozen_rom/2.1/caliptra-rom-2.1.0-a72a76f.bin +FW_PATH ?= $(HWMODEL_OUT)/image_bundle.bin + +# ------------------------------------------------------------------------- +# Compiler +# ------------------------------------------------------------------------- +CC ?= gcc +CFLAGS += -std=c99 -Wall -Wextra -g +CFLAGS += -DWOLFSSL_CALIPTRA -DWOLF_CRYPTO_CB -DWOLF_CRYPTO_CB_FREE +CFLAGS += -DCALIPTRA_HWMODEL +# Tell wolfSSL settings.h to include wolfssl/options.h (autoconf feature flags) +CFLAGS += -DWOLFSSL_USE_OPTIONS_H +# Match the ABI of libwolfssl.a: the library was compiled without -std=c99 so +# GCC defaults to gnu17, which satisfies (__STDC_VERSION__ >= 201101L) and +# therefore defines HAVE_ANONYMOUS_INLINE_AGGREGATES=1 in types.h. Without +# this define, wc_CryptoInfo uses a sequential (non-union) layout that is +# incompatible with the union layout the library was built with. +CFLAGS += -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 + +# Include paths: +# 1. wolfSSL headers +# 2. hw-model C binding header (caliptra_model.h) +# 3. This directory (caliptra_top_reg.h, caliptra_hwmodel.h) +CFLAGS += -I$(WOLFSSL_ROOT) +CFLAGS += -I$(HWMODEL_OUT) +CFLAGS += -I$(dir $(lastword $(MAKEFILE_LIST))) + +# Pass ROM and FW paths to caliptra_test.c via preprocessor defines +CFLAGS += -DCALIPTRA_ROM_PATH='"$(ROM_PATH)"' +CFLAGS += -DCALIPTRA_FW_PATH='"$(FW_PATH)"' + +# ------------------------------------------------------------------------- +# Source files +# ------------------------------------------------------------------------- +SIM_DIR = $(dir $(lastword $(MAKEFILE_LIST))) +PORT_SRC = $(WOLFSSL_ROOT)/wolfcrypt/src/port/caliptra/caliptra_port.c + +SRCS = $(SIM_DIR)caliptra_hwmodel.c \ + $(SIM_DIR)caliptra_test.c \ + $(PORT_SRC) + +OBJS = $(patsubst %.c,%.o,$(SRCS)) + +TARGET = caliptra_test_bin + +# ------------------------------------------------------------------------- +# Link +# ------------------------------------------------------------------------- +WOLFSSL_LIB = $(WOLFSSL_ROOT)/src/.libs/libwolfssl.a + +# libcaliptra_hw_model_c_binding.a is a Rust library; needs C++ runtime, +# pthreads, and system libraries. +LDFLAGS = $(WOLFSSL_LIB) +LDFLAGS += $(HWMODEL_LIB) +LDFLAGS += -lpthread -lstdc++ -ldl -lrt -lm + +# ------------------------------------------------------------------------- +# Rules +# ------------------------------------------------------------------------- +.PHONY: all run clean + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +run: $(TARGET) + ./$(TARGET) + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/wolfcrypt/src/port/caliptra/sim/caliptra_hwmodel.c b/wolfcrypt/src/port/caliptra/sim/caliptra_hwmodel.c new file mode 100644 index 00000000000..ae18f5def1e --- /dev/null +++ b/wolfcrypt/src/port/caliptra/sim/caliptra_hwmodel.c @@ -0,0 +1,486 @@ +/* caliptra_hwmodel.c — Caliptra hw-model mailbox transport + * + * Implements caliptra_mailbox_exec() — the integrator-supplied transport hook + * declared in caliptra_port.h — using the Caliptra hw-model C binding. + * + * Also provides caliptra_hwmodel_init() / caliptra_hwmodel_cleanup() to boot + * and tear down the emulated hardware before and after test runs. + * + * This file is test-only; it is NOT part of the wolfSSL library. + * + * Build-time dependencies (set via Makefile -I and -L flags): + * caliptra_model.h — ~/caliptra/hw-model/c-binding/out/ + * caliptra_top_reg.h — this directory (wolfcrypt/src/port/caliptra/sim/) + * libcaliptra_hw_model_c_binding.a — ~/caliptra/target/debug/ + * + * Link flags required: + * -lpthread -lstdc++ -ldl -lrt -lm + * + * Boot files (passed to caliptra_hwmodel_init): + * ROM: ~/caliptra/rom/ci_frozen_rom/2.1/caliptra-rom-2.1.0-a72a76f.bin + * FW: ~/caliptra/hw-model/c-binding/out/image_bundle.bin + */ + +#include +#include +#include +#include +#include +#include + +/* hw-model C binding — provides caliptra_model_init_default, step, apb_read/write */ +#include "caliptra_model.h" + +/* Synthesized register constants */ +#include "caliptra_top_reg.h" + +/* Our public interface */ +#include "caliptra_hwmodel.h" + +/* wolfSSL word32 type (unsigned int on all supported platforms) */ +#include +#include +#include +#include + +/* ========================================================================= + * Constants + * ========================================================================= */ + +/* Base address of Caliptra peripherals on the APB bus */ +#define EXTERNAL_PERIPH_BASE 0x30000000u + +/* Boot status value written by the Caliptra runtime when it is ready to + * accept cryptographic mailbox commands. */ +#define RT_READY_FOR_COMMANDS 0x600u + +/* Maximum iterations to wait for the mailbox lock before giving up. + * Each iteration steps the model one clock cycle (~1 ns at 1 GHz). + * 100 000 steps is generous for simulation. */ +#define MBOX_LOCK_MAX_STEPS 100000 + +/* Mailbox status field values (bits [3:0] of MBOX_CSR_MBOX_STATUS). + * Source: hw/latest/registers/src/mbox.rs enums::MboxStatusE */ +#define MBOX_STATUS_BUSY 0u +#define MBOX_STATUS_DATA_READY 1u +#define MBOX_STATUS_CMD_COMPLETE 2u +#define MBOX_STATUS_CMD_FAILURE 3u + +/* Mailbox FSM states (bits [8:6] of MBOX_CSR_MBOX_STATUS). + * Source: hw/latest/registers/src/mbox.rs enums::MboxFsmE */ +#define MBOX_FSM_IDLE 0u + +/* ========================================================================= + * Global model handle + * One model, one thread; no locking. + * ========================================================================= */ +static struct caliptra_model *g_model; + +/* ========================================================================= + * Low-level APB register helpers + * ========================================================================= */ + +static inline void mbox_write(uint32_t reg_offset, uint32_t val) +{ + uint32_t addr = EXTERNAL_PERIPH_BASE + + CALIPTRA_TOP_REG_MBOX_CSR_BASE_ADDR + + reg_offset; + caliptra_model_apb_write_u32(g_model, addr, val); +} + +static inline uint32_t mbox_read(uint32_t reg_offset) +{ + uint32_t val = 0; + uint32_t addr = EXTERNAL_PERIPH_BASE + + CALIPTRA_TOP_REG_MBOX_CSR_BASE_ADDR + + reg_offset; + caliptra_model_apb_read_u32(g_model, addr, &val); + return val; +} + +static inline void soc_write(uint32_t reg_offset, uint32_t val) +{ + uint32_t addr = EXTERNAL_PERIPH_BASE + + CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_BASE_ADDR + + reg_offset; + caliptra_model_apb_write_u32(g_model, addr, val); +} + +/* Write to a register addressed by its full offset from EXTERNAL_PERIPH_BASE + * (i.e. the CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_* constants). */ +static inline void soc_write_direct(uint32_t periph_offset, uint32_t val) +{ + caliptra_model_apb_write_u32(g_model, + EXTERNAL_PERIPH_BASE + periph_offset, + val); +} + +/* ========================================================================= + * Mailbox status helpers + * ========================================================================= */ + +static inline uint32_t mbox_status(void) +{ + return mbox_read(MBOX_CSR_MBOX_STATUS) & MBOX_CSR_MBOX_STATUS_STATUS_MASK; +} + +static inline uint32_t mbox_fsm_state(void) +{ + return (mbox_read(MBOX_CSR_MBOX_STATUS) + & MBOX_CSR_MBOX_STATUS_MBOX_FSM_PS_MASK) + >> MBOX_CSR_MBOX_STATUS_MBOX_FSM_PS_LOW; +} + +/* ========================================================================= + * Fuse initialisation + * Writes all zero fuses — valid in debug/unprovisioned mode. + * ========================================================================= */ + +static void write_fuse_zeros(uint32_t base_offset, size_t n_words) +{ + for (size_t i = 0; i < n_words; i++) + soc_write(base_offset + (uint32_t)(i * sizeof(uint32_t)), 0u); +} + +static void init_fuses_zero(void) +{ + write_fuse_zeros(GENERIC_AND_FUSE_REG_FUSE_UDS_SEED_0, 16); + write_fuse_zeros(GENERIC_AND_FUSE_REG_FUSE_FIELD_ENTROPY_0, 8); + write_fuse_zeros(GENERIC_AND_FUSE_REG_FUSE_VENDOR_PK_HASH_0, 12); + soc_write(GENERIC_AND_FUSE_REG_FUSE_ECC_REVOCATION, 0); + write_fuse_zeros(GENERIC_AND_FUSE_REG_CPTRA_OWNER_PK_HASH_0, 12); + write_fuse_zeros(GENERIC_AND_FUSE_REG_FUSE_RUNTIME_SVN_0, 4); + soc_write(GENERIC_AND_FUSE_REG_FUSE_ANTI_ROLLBACK_DISABLE, 0); + write_fuse_zeros(GENERIC_AND_FUSE_REG_FUSE_IDEVID_CERT_ATTR_0, 24); + write_fuse_zeros(GENERIC_AND_FUSE_REG_FUSE_IDEVID_MANUF_HSM_ID_0, 4); + soc_write(GENERIC_AND_FUSE_REG_FUSE_LMS_REVOCATION, 0); + soc_write(GENERIC_AND_FUSE_REG_FUSE_MLDSA_REVOCATION, 0); + soc_write(GENERIC_AND_FUSE_REG_FUSE_SOC_STEPPING_ID, 0); + soc_write(GENERIC_AND_FUSE_REG_FUSE_PQC_KEY_TYPE, 0); +} + +/* ========================================================================= + * Mailbox lock acquisition + * Returns 0 on success, -EBUSY on timeout. + * ========================================================================= */ + +static int mbox_acquire_lock(void) +{ + for (int i = 0; i < MBOX_LOCK_MAX_STEPS; i++) { + /* A read that returns 0 atomically acquires the lock. + * A read that returns 1 means someone else holds it. */ + uint32_t lock_val = mbox_read(MBOX_CSR_MBOX_LOCK); + if ((lock_val & MBOX_CSR_MBOX_LOCK_LOCK_MASK) == 0) + return 0; /* lock acquired */ + caliptra_model_step(g_model); + } + fprintf(stderr, "caliptra_hwmodel: mailbox lock timeout\n"); + return -EBUSY; +} + +/* ========================================================================= + * Mailbox FIFO write + * Writes req_len bytes from req into the mailbox DATAIN register word by word. + * The final partial word (if any) is zero-padded to the right. + * ========================================================================= */ + +static void mbox_write_fifo(const void *req, word32 req_len) +{ + const uint8_t *p = (const uint8_t *)req; + word32 remaining = req_len; + + while (remaining >= 4) { + uint32_t word; + memcpy(&word, p, 4); + mbox_write(MBOX_CSR_MBOX_DATAIN, word); + p += 4; + remaining -= 4; + } + if (remaining > 0) { + uint32_t word = 0; + memcpy(&word, p, remaining); + mbox_write(MBOX_CSR_MBOX_DATAIN, word); + } +} + +/* ========================================================================= + * Mailbox FIFO read + * Reads up to resp_len bytes from DATAOUT into resp. + * resp_dlen is the byte count reported by the hardware (MBOX_DLEN after + * execute; may be larger or smaller than resp_len). + * ========================================================================= */ + +static void mbox_read_fifo(void *resp, word32 resp_len, uint32_t hw_dlen) +{ + uint32_t to_copy = hw_dlen < resp_len ? hw_dlen : resp_len; + uint8_t *p = (uint8_t *)resp; + + uint32_t full_words = to_copy / 4; + for (uint32_t i = 0; i < full_words; i++) { + uint32_t word = mbox_read(MBOX_CSR_MBOX_DATAOUT); + memcpy(p, &word, 4); + p += 4; + } + uint32_t tail = to_copy % 4; + if (tail > 0) { + uint32_t word = mbox_read(MBOX_CSR_MBOX_DATAOUT); + memcpy(p, &word, tail); + } + + /* Drain any remaining words the hardware produced but we don't need */ + uint32_t drained = to_copy; + while (drained + 4 <= hw_dlen) { + mbox_read(MBOX_CSR_MBOX_DATAOUT); + drained += 4; + } +} + +/* ========================================================================= + * File loading helper + * ========================================================================= */ + +static int load_file(const char *path, struct caliptra_buffer *out) +{ + FILE *fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "caliptra_hwmodel: cannot open '%s': %s\n", + path, strerror(errno)); + return -errno; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fprintf(stderr, "caliptra_hwmodel: fseek failed on '%s'\n", path); + fclose(fp); + return -EIO; + } + long sz = ftell(fp); + if (sz <= 0) { + fprintf(stderr, "caliptra_hwmodel: '%s' is empty or ftell failed\n", path); + fclose(fp); + return -EIO; + } + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return -EIO; + } + + void *buf = malloc((size_t)sz); + if (!buf) { + fclose(fp); + return -ENOMEM; + } + + size_t got = fread(buf, 1, (size_t)sz, fp); + fclose(fp); + if (got != (size_t)sz) { + fprintf(stderr, "caliptra_hwmodel: short read on '%s': %zu of %ld bytes\n", + path, got, sz); + free(buf); + return -EIO; + } + + out->data = (const uint8_t *)buf; + out->len = (uintptr_t)sz; + return 0; +} + +/* ========================================================================= + * Boot sequence + * ========================================================================= */ + +int caliptra_hwmodel_init(const char *rom_path, const char *fw_path) +{ + if (g_model) { + fprintf(stderr, "caliptra_hwmodel: already initialised\n"); + return -EALREADY; + } + + /* Load ROM and firmware images */ + struct caliptra_buffer rom = {0}; + struct caliptra_buffer fw = {0}; + int rc; + + rc = load_file(rom_path, &rom); + if (rc != 0) + return rc; + + rc = load_file(fw_path, &fw); + if (rc != 0) { + free((void *)rom.data); + return rc; + } + + /* Initialise the hw-model. + * security_state = DBG_UNLOCKED_UNPROVISIONED (0) allows booting with + * zero fuses in development/simulation environments. */ + uint8_t empty_byte = 0; + struct caliptra_model_init_params params = { + .rom = rom, + .dccm = { .data = &empty_byte, .len = 0 }, + .iccm = { .data = &empty_byte, .len = 0 }, + .security_state = CALIPTRA_SEC_STATE_DBG_UNLOCKED_UNPROVISIONED, + .soc_user = 0, + }; + + int init_rc = caliptra_model_init_default(params, &g_model); + if (init_rc != CALIPTRA_MODEL_STATUS_OK) { + fprintf(stderr, "caliptra_hwmodel: model init failed (status %d)\n", + init_rc); + free((void *)rom.data); + free((void *)fw.data); + return -EIO; + } + + /* Step until the fuse controller is ready */ + while (!caliptra_model_ready_for_fuses(g_model)) + caliptra_model_step(g_model); + + /* Write all-zero fuses (valid in debug/unprovisioned mode) */ + init_fuses_zero(); + + /* Signal fuse programming complete */ + soc_write_direct(CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_FUSE_WR_DONE, 1u); + + /* Release the boot FSM */ + soc_write_direct(CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_BOOTFSM_GO, 1u); + caliptra_model_step(g_model); + + /* Step until the ROM is ready to receive the firmware image */ + while (!caliptra_model_ready_for_fw(g_model)) + caliptra_model_step(g_model); + + /* Upload firmware via mailbox (command FWLD = 0x46574C44) */ + rc = mbox_acquire_lock(); + if (rc != 0) { + fprintf(stderr, "caliptra_hwmodel: cannot acquire lock for FW upload\n"); + caliptra_model_destroy(g_model); + g_model = NULL; + free((void *)rom.data); + free((void *)fw.data); + return rc; + } + + mbox_write(MBOX_CSR_MBOX_CMD, 0x46574C44u); /* "FWLD" */ + mbox_write(MBOX_CSR_MBOX_DLEN, (uint32_t)fw.len); + mbox_write_fifo(fw.data, (word32)fw.len); + mbox_write(MBOX_CSR_MBOX_EXECUTE, 1u); + + while (mbox_status() == MBOX_STATUS_BUSY) + caliptra_model_step(g_model); + + uint32_t upload_status = mbox_status(); + mbox_write(MBOX_CSR_MBOX_EXECUTE, 0u); + caliptra_model_step(g_model); + + if (upload_status == MBOX_STATUS_CMD_FAILURE) { + fprintf(stderr, "caliptra_hwmodel: firmware upload returned CMD_FAILURE\n"); + caliptra_model_destroy(g_model); + g_model = NULL; + free((void *)rom.data); + free((void *)fw.data); + return -EIO; + } + + /* Step until the runtime signals it is ready for commands */ + caliptra_model_step_until_boot_status(g_model, RT_READY_FOR_COMMANDS); + + free((void *)rom.data); + free((void *)fw.data); + + fprintf(stderr, "caliptra_hwmodel: runtime ready (boot status 0x%03X)\n", + RT_READY_FOR_COMMANDS); + return 0; +} + +void caliptra_hwmodel_cleanup(void) +{ + if (g_model) { + caliptra_model_destroy(g_model); + g_model = NULL; + } +} + +/* ========================================================================= + * wolfSSL transport hook + * + * Called by caliptra_port.c for every cryptographic mailbox operation. + * The request struct already contains a valid checksum (set by + * wc_caliptra_req_chksum before this function is called). + * + * Returns 0 on success. Returns a non-zero value if the hardware rejected + * the command (CMD_FAILURE), the mailbox lock could not be acquired, or the + * model has not been initialised. + * ========================================================================= */ + +int caliptra_mailbox_exec(word32 cmd_id, + const void *req, word32 req_len, + void *resp, word32 resp_len) +{ + if (!g_model) { + fprintf(stderr, "caliptra_mailbox_exec: model not initialised\n"); + return -1; + } + + /* Acquire the mailbox lock */ + int rc = mbox_acquire_lock(); + if (rc != 0) + return rc; + + /* Write command ID, data length, and request payload */ + mbox_write(MBOX_CSR_MBOX_CMD, (uint32_t)cmd_id); + mbox_write(MBOX_CSR_MBOX_DLEN, req_len); + mbox_write_fifo(req, req_len); + + /* Ring the doorbell */ + mbox_write(MBOX_CSR_MBOX_EXECUTE, 1u); + + /* Step until the firmware has processed the command */ + do { + caliptra_model_step(g_model); + } while (mbox_status() == MBOX_STATUS_BUSY); + + uint32_t status = mbox_status(); + + if (status == MBOX_STATUS_CMD_FAILURE) { + mbox_write(MBOX_CSR_MBOX_EXECUTE, 0u); + caliptra_model_step(g_model); + fprintf(stderr, + "caliptra_mailbox_exec: CMD_FAILURE for cmd 0x%08X\n", + (unsigned)cmd_id); + /* Per the caliptra_mailbox_exec contract documented in + * caliptra_port.h: when the failing command is CM_ECDSA_VERIFY, + * the transport MUST return SIG_VERIFY_E so the port can map a + * cryptographically invalid signature to the wolfSSL CryptoCb + * convention of (ret=0, *res=0). Any other negative code surfaces + * as a system error rather than a clean verify-failed result. + * All other commands keep the generic -EIO transport error. */ + if (cmd_id == CM_ECDSA_VERIFY) + return SIG_VERIFY_E; + return -EIO; + } + + /* Read response data if the firmware produced any */ + if (status == MBOX_STATUS_DATA_READY && resp != NULL && resp_len > 0) { + uint32_t hw_dlen = mbox_read(MBOX_CSR_MBOX_DLEN); + mbox_read_fifo(resp, resp_len, hw_dlen); + } + + /* Clear execute to release the mailbox lock */ + mbox_write(MBOX_CSR_MBOX_EXECUTE, 0u); + + /* One extra step: the FSM needs a clock edge to return to IDLE after + * execute is de-asserted (noted in hw-model examples/api/caliptra_api.c). */ + caliptra_model_step(g_model); + + /* Verify the mailbox FSM returned to IDLE */ + uint32_t fsm = mbox_fsm_state(); + if (fsm != MBOX_FSM_IDLE) { + fprintf(stderr, + "caliptra_mailbox_exec: FSM not IDLE after cmd 0x%08X" + " (state %u)\n", + (unsigned)cmd_id, (unsigned)fsm); + return -EIO; + } + + return 0; +} diff --git a/wolfcrypt/src/port/caliptra/sim/caliptra_hwmodel.h b/wolfcrypt/src/port/caliptra/sim/caliptra_hwmodel.h new file mode 100644 index 00000000000..50ae92d9aa1 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/sim/caliptra_hwmodel.h @@ -0,0 +1,42 @@ +/* caliptra_hwmodel.h — hw-model backend for the wolfSSL Caliptra port + * + * Declares the lifecycle functions that must be called before and after + * using the Caliptra mailbox transport via caliptra_mailbox_exec(). + * + * Typical usage: + * + * caliptra_hwmodel_init(ROM_PATH, FW_PATH); // boot the model + * wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, wc_caliptra_cb, NULL); + * ... run wolfSSL operations ... + * caliptra_hwmodel_cleanup(); // destroy the model + * + * This file is test-only; it is not part of the wolfSSL library. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Boot the Caliptra hw-model emulator. + * + * Loads ROM from rom_path, firmware from fw_path, initialises the hw-model, + * writes zero fuses (debug/unprovisioned mode), releases boot FSM, uploads + * firmware, and steps the model until the runtime is ready to accept + * cryptographic mailbox commands (boot status 0x600). + * + * Returns 0 on success, negative errno on failure. + * Calling init when already initialised returns -EALREADY. + */ +int caliptra_hwmodel_init(const char *rom_path, const char *fw_path); + +/* Destroy the hw-model and release all resources. + * + * Safe to call if init was never called or already failed. + */ +void caliptra_hwmodel_cleanup(void); + +#ifdef __cplusplus +} +#endif diff --git a/wolfcrypt/src/port/caliptra/sim/caliptra_sim.c b/wolfcrypt/src/port/caliptra/sim/caliptra_sim.c new file mode 100644 index 00000000000..d03954556d4 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/sim/caliptra_sim.c @@ -0,0 +1,1459 @@ +/* caliptra_sim.c — software simulation of the Caliptra cryptographic mailbox + * + * Implements caliptra_mailbox_exec() for use in test harnesses and as the + * default backend for --enable-caliptra builds without integrator-supplied + * mailbox hardware (see --enable-caliptra-sim in configure.ac). + * + * All internal crypto uses wolfSSL software (WC_NO_DEVID) to avoid recursion + * through the CryptoCb framework. Random bytes use wc_RNG_GenerateBlock so + * the sim is portable across every platform wolfSSL itself supports rather + * than relying on /dev/urandom. + * + * THREAD SAFETY: concurrent calls to caliptra_mailbox_exec() are now + * serialized by an internal mutex (sim_mailbox_lock), initialised by + * wc_caliptra_init() and torn down by wc_caliptra_cleanup(). This matches + * the real Caliptra hardware behaviour, which serialises requests via the + * mailbox lock semaphore (see Caliptra Cryptographic Mailbox spec), and + * makes the sim safe to use under the default multi-threaded wolfSSL + * builds. The mutex itself is initialised idempotently from a non-atomic + * flag, so callers MUST invoke wc_caliptra_init() before issuing the + * first mailbox operation from any thread (caliptra_test does this); a + * race during first-init itself is not defended against. Integrators + * needing a different concurrency model should build with + * --disable-caliptra-sim and link against a real (or differently- + * implemented) mailbox backend. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The body of this file only contributes to the link when the simulator + * backend is selected. Guard everything past the unconditional include + * block so that build systems (e.g., CMake or user_settings.h-driven + * tree builds) that compile every .c file unconditionally still get an + * empty translation unit when WOLFSSL_CALIPTRA_SIM is not defined. */ +#ifdef WOLFSSL_CALIPTRA_SIM + +/* misc.h normally forward-declares ConstantCompare/ForceZero only under + * NO_INLINE; otherwise the implementations live in wolfcrypt/src/misc.c + * and callers include that translation unit directly. Match the pattern + * used by src/tls.c and friends so we get the real symbols at link time. */ +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#include + +/* Make every build of the software simulator visibly noisy: this file + * implements caliptra_mailbox_exec() in software for offline test/dev + * usage only. Production deployments MUST link against the real + * Caliptra hardware mailbox via --disable-caliptra-sim plus an + * integrator-supplied caliptra_mailbox_exec(). + * + * Uses #pragma message rather than #warning so that builds running with + * -Werror=cpp (the default for wolfSSL) still surface the advisory + * without aborting the build. */ +#if defined(__GNUC__) || defined(__clang__) +#pragma message ("caliptra_sim.c: software simulator only -- must --disable-caliptra-sim for production") +#endif + +/* ========================================================================= + * Single mailbox-serialization mutex (HIGH-46 mitigation). + * + * The sim holds process-wide mutable state (sim_keys[], sim_sha[], + * sim_aes[]); wrapping every caliptra_mailbox_exec() call in this mutex + * makes those statics safe under default multi-threaded wolfSSL builds. + * sim_mailbox_lock_init is a plain (non-atomic) flag: first-init must be + * driven from a single thread via wc_caliptra_init() before any worker + * thread calls caliptra_mailbox_exec(). See file header THREAD SAFETY. + * ========================================================================= */ +static wolfSSL_Mutex sim_mailbox_lock; +static int sim_mailbox_lock_init = 0; + +int sim_mailbox_lock_init_once(void); +void sim_mailbox_lock_free_once(void); + +int sim_mailbox_lock_init_once(void) +{ + if (sim_mailbox_lock_init) + return 0; + if (wc_InitMutex(&sim_mailbox_lock) != 0) + return BAD_MUTEX_E; + sim_mailbox_lock_init = 1; + return 0; +} + +void sim_mailbox_lock_free_once(void) +{ + if (!sim_mailbox_lock_init) + return; + (void)wc_FreeMutex(&sim_mailbox_lock); + sim_mailbox_lock_init = 0; +} + +/* Use INVALID_DEVID for all internal wolfSSL calls to avoid CryptoCb recursion. + * WC_NO_DEVID is not defined in this wolfSSL build; INVALID_DEVID (-2) has + * the same effect: bypass the CryptoCb framework. */ +#ifndef WC_NO_DEVID +#define WC_NO_DEVID INVALID_DEVID +#endif + +/* The Caliptra mailbox wire format uses little-endian byte order for all + * multi-byte integer fields. HTOLE32/LE32TOH convert between host byte order + * and the LE wire format; on a LE host they are no-ops (zero overhead). */ +#ifdef BIG_ENDIAN_ORDER + #define HTOLE32(x) ByteReverseWord32(x) + #define LE32TOH(x) ByteReverseWord32(x) +#else + #define HTOLE32(x) (x) + #define LE32TOH(x) (x) +#endif + +/* SHA-256 is not a real Caliptra firmware command (CMB_SHA_ALG_SHA384=1, + * CMB_SHA_ALG_SHA512=2 only). The simulator accepts SHA-256 so that the + * software-fallback path (CRYPTOCB_UNAVAILABLE returned by the port) can be + * exercised; this local constant is never sent to real hardware. */ +#define SIM_SHA_ALG_SHA256 0 + +/* ========================================================================= + * Internal helper: write LE uint32 to a byte buffer + * ========================================================================= */ + +static void put_le32(byte* buf, word32 v) +{ + buf[0] = (byte)(v & 0xFF); + buf[1] = (byte)((v >> 8) & 0xFF); + buf[2] = (byte)((v >> 16) & 0xFF); + buf[3] = (byte)((v >> 24) & 0xFF); +} + +static word32 get_le32(const byte* buf) +{ + return (word32)buf[0] + | ((word32)buf[1] << 8) + | ((word32)buf[2] << 16) + | ((word32)buf[3] << 24); +} + +/* ========================================================================= + * Key table (CMK simulation) + * ========================================================================= */ + +#define SIM_MAX_KEYS 16 +#define SIM_KEY_TYPE_NONE 0 +#define SIM_KEY_TYPE_SYM 1 /* AES/HMAC symmetric key */ +#define SIM_KEY_TYPE_ECC_PUB 2 /* P-384 public key: 48+48 bytes */ +#define SIM_KEY_TYPE_ECC_PRV 3 /* P-384 private key: 48 bytes */ + +typedef struct { + int in_use; + int key_type; + byte key_data[96]; /* max P-384 pub key = 48+48 */ + word32 key_len; +} SimKey; + +static SimKey sim_keys[SIM_MAX_KEYS]; + +/* CMK encodes key index: bytes[0..3] = LE uint32 index (1-based, 0=invalid) + * rest of 128 bytes = 0 */ + +static int sim_key_alloc(void) +{ + int i; + for (i = 0; i < SIM_MAX_KEYS; i++) { + if (!sim_keys[i].in_use) { + XMEMSET(&sim_keys[i], 0, sizeof(SimKey)); + sim_keys[i].in_use = 1; + return i; /* 0-based index */ + } + } + return -1; +} + +static SimKey* sim_key_lookup(const byte cmk[128]) +{ + word32 idx = get_le32(cmk); + if (idx == 0 || idx > (word32)SIM_MAX_KEYS) + return NULL; + if (!sim_keys[idx - 1].in_use) + return NULL; + return &sim_keys[idx - 1]; +} + +static void sim_cmk_from_index(byte cmk[128], int idx_0based) +{ + XMEMSET(cmk, 0, 128); + put_le32(cmk, (word32)(idx_0based + 1)); +} + +/* ========================================================================= + * SHA state table (streaming SHA) + * ========================================================================= */ + +#define SIM_MAX_SHA_SLOTS 8 + +typedef enum { SHA_NONE = 0, SHA_256, SHA_384, SHA_512 } SimShaType; + +typedef struct { + int in_use; + SimShaType type; + union { + wc_Sha256 sha256; + wc_Sha384 sha384; + wc_Sha512 sha512; + } u; +} SimShaSlot; + +static SimShaSlot sim_sha[SIM_MAX_SHA_SLOTS]; + +/* context[0..3] = LE uint32 slot index (1-based) */ + +static int sim_sha_alloc(void) +{ + int i; + for (i = 0; i < SIM_MAX_SHA_SLOTS; i++) { + if (!sim_sha[i].in_use) { + XMEMSET(&sim_sha[i], 0, sizeof(SimShaSlot)); + sim_sha[i].in_use = 1; + return i; + } + } + return -1; +} + +static SimShaSlot* sim_sha_lookup(const byte ctx[200]) +{ + word32 idx = get_le32(ctx); + if (idx == 0 || idx > (word32)SIM_MAX_SHA_SLOTS) + return NULL; + if (!sim_sha[idx - 1].in_use) + return NULL; + return &sim_sha[idx - 1]; +} + +static void sim_sha_ctx_from_index(byte ctx[200], int idx_0based) +{ + XMEMSET(ctx, 0, 200); + put_le32(ctx, (word32)(idx_0based + 1)); +} + +static void sim_sha_free(int idx_0based) +{ + SimShaSlot* slot = &sim_sha[idx_0based]; + if (!slot->in_use) return; + switch (slot->type) { + case SHA_256: wc_Sha256Free(&slot->u.sha256); break; + case SHA_384: wc_Sha384Free(&slot->u.sha384); break; + case SHA_512: wc_Sha512Free(&slot->u.sha512); break; + case SHA_NONE: break; + } + XMEMSET(slot, 0, sizeof(*slot)); +} + +/* ========================================================================= + * AES-GCM state table (streaming AES-GCM) + * ========================================================================= */ + +#define SIM_MAX_AES_SLOTS 4 +#define SIM_AES_MAX_AAD 4096 +#define SIM_AES_MAX_DATA (4096 * 4) + +/* The sim's AAD buffer must be at least as large as the wire-format AAD + * field (CMB_MAX_DATA_SIZE bytes); otherwise sim_handle_aes_*_init would + * have to truncate or reject every maximum-sized AAD the real protocol + * permits. Trips at compile time if either constant drifts. */ +wc_static_assert2(SIM_AES_MAX_AAD >= CMB_MAX_DATA_SIZE, + "sim AAD buffer smaller than wire-format AAD max"); + +typedef struct { + int in_use; + int enc; /* 1=encrypt, 0=decrypt */ + byte raw_key[32]; + byte iv[12]; + byte aad[SIM_AES_MAX_AAD]; + word32 aad_len; + byte data[SIM_AES_MAX_DATA]; /* ciphertext (dec) or plaintext (enc) */ + word32 data_len; + /* For decrypt: accumulate decrypted plaintext so FINAL can re-verify */ + byte plaintext[SIM_AES_MAX_DATA]; + word32 pt_len; +} SimAesSlot; + +static SimAesSlot sim_aes[SIM_MAX_AES_SLOTS]; + +/* AES context[0..3] = LE uint32 slot index (1-based) + * context[4..15] = IV (12 bytes) */ + +static int sim_aes_alloc(void) +{ + int i; + for (i = 0; i < SIM_MAX_AES_SLOTS; i++) { + if (!sim_aes[i].in_use) { + XMEMSET(&sim_aes[i], 0, sizeof(SimAesSlot)); + sim_aes[i].in_use = 1; + return i; + } + } + return -1; +} + +static SimAesSlot* sim_aes_lookup(const byte ctx[128]) +{ + word32 idx = get_le32(ctx); + if (idx == 0 || idx > (word32)SIM_MAX_AES_SLOTS) + return NULL; + if (!sim_aes[idx - 1].in_use) + return NULL; + return &sim_aes[idx - 1]; +} + +static void sim_aes_ctx_from_index(byte ctx[128], int idx_0based, const byte iv[12]) +{ + XMEMSET(ctx, 0, 128); + put_le32(ctx, (word32)(idx_0based + 1)); + if (iv != NULL) + XMEMCPY(ctx + 4, iv, 12); +} + +static void sim_aes_free(int idx_0based) +{ + /* Slot holds raw_key and recovered plaintext; ForceZero so an + * optimising compiler cannot drop the wipe as dead-store. */ + wc_ForceZero(&sim_aes[idx_0based], sizeof(SimAesSlot)); +} + +/* ========================================================================= + * Random bytes via wolfSSL's RNG (portable across all supported platforms). + * Returns 0 on success, non-zero on failure. + * ========================================================================= */ + +static int sim_get_random(byte* buf, word32 len) +{ + WC_RNG rng; + int ret; + + if (buf == NULL && len > 0) return -1; + if (len == 0) return 0; + + ret = wc_InitRng_ex(&rng, NULL, WC_NO_DEVID); + if (ret != 0) return -1; + + ret = wc_RNG_GenerateBlock(&rng, buf, len); + (void)wc_FreeRng(&rng); + return (ret == 0) ? 0 : -1; +} + +/* ========================================================================= + * CM_IMPORT handler + * ========================================================================= */ + +static int sim_handle_import(const CmImportReq* req, word32 req_len, + CmImportResp* resp) +{ + int idx; + word32 key_len; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + idx = sim_key_alloc(); + if (idx < 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + key_len = LE32TOH(req->input_size); + if (key_len > 96) key_len = 96; /* safety cap */ + + sim_keys[idx].key_len = key_len; + if (key_len > 0) + XMEMCPY(sim_keys[idx].key_data, req->input, key_len); + + { + word32 key_usage = LE32TOH(req->key_usage); + switch (key_usage) { + case 0: /* sim-internal "SYM" (Reserved on real hw) */ + case CMB_KEY_USAGE_HMAC: /* 1 */ + case CMB_KEY_USAGE_AES: /* 2 */ + sim_keys[idx].key_type = SIM_KEY_TYPE_SYM; + break; + case CMB_KEY_USAGE_ECDSA: /* 3: private = 48 bytes, public = 96 bytes */ + sim_keys[idx].key_type = (key_len <= 48) + ? SIM_KEY_TYPE_ECC_PRV + : SIM_KEY_TYPE_ECC_PUB; + break; + default: + sim_keys[idx].key_type = SIM_KEY_TYPE_SYM; + break; + } + } + + sim_cmk_from_index(resp->cmk.bytes, idx); + return 0; +} + +/* ========================================================================= + * CM_DELETE handler + * ========================================================================= */ + +static int sim_handle_delete(const CmDeleteReq* req, word32 req_len, + CmDeleteResp* resp) +{ + word32 idx; + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + idx = get_le32(req->cmk.bytes); + if (idx == 0 || idx > (word32)SIM_MAX_KEYS) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + idx--; /* convert to 0-based */ + /* SimKey holds raw key material; ForceZero so an optimising compiler + * cannot drop the wipe as dead-store. */ + wc_ForceZero(&sim_keys[idx], sizeof(SimKey)); + return 0; +} + +/* ========================================================================= + * CM_SHA_INIT handler + * ========================================================================= */ + +static int sim_handle_sha_init(const CmShaInitReq* req, word32 req_len, + CmShaInitResp* resp) +{ + int idx; + SimShaSlot* slot; + int ret = 0; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + idx = sim_sha_alloc(); + if (idx < 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + slot = &sim_sha[idx]; + + { + word32 hash_alg = LE32TOH(req->hash_algorithm); + word32 input_sz = LE32TOH(req->input_size); + switch (hash_alg) { + case SIM_SHA_ALG_SHA256: + slot->type = SHA_256; + ret = wc_InitSha256_ex(&slot->u.sha256, NULL, WC_NO_DEVID); + if (ret == 0 && input_sz > 0) + ret = wc_Sha256Update(&slot->u.sha256, req->input, input_sz); + break; + case CMB_SHA_ALG_SHA384: + slot->type = SHA_384; + ret = wc_InitSha384_ex(&slot->u.sha384, NULL, WC_NO_DEVID); + if (ret == 0 && input_sz > 0) + ret = wc_Sha384Update(&slot->u.sha384, req->input, input_sz); + break; + case CMB_SHA_ALG_SHA512: + slot->type = SHA_512; + ret = wc_InitSha512_ex(&slot->u.sha512, NULL, WC_NO_DEVID); + if (ret == 0 && input_sz > 0) + ret = wc_Sha512Update(&slot->u.sha512, req->input, input_sz); + break; + default: + sim_sha_free(idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + } + + if (ret != 0) { + sim_sha_free(idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + sim_sha_ctx_from_index(resp->context, idx); + return 0; +} + +/* ========================================================================= + * CM_SHA_UPDATE handler + * ========================================================================= */ + +static int sim_handle_sha_update(const CmShaUpdateReq* req, word32 req_len, + CmShaUpdateResp* resp) +{ + SimShaSlot* slot; + int ret = 0; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + slot = sim_sha_lookup(req->context); + if (slot == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + { + word32 input_sz = LE32TOH(req->input_size); + if (input_sz > 0) { + switch (slot->type) { + case SHA_256: + ret = wc_Sha256Update(&slot->u.sha256, req->input, input_sz); + break; + case SHA_384: + ret = wc_Sha384Update(&slot->u.sha384, req->input, input_sz); + break; + case SHA_512: + ret = wc_Sha512Update(&slot->u.sha512, req->input, input_sz); + break; + case SHA_NONE: + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + } + } + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Copy context through (slot index preserved) */ + XMEMCPY(resp->context, req->context, CMB_SHA_CONTEXT_SIZE); + return 0; +} + +/* ========================================================================= + * CM_SHA_FINAL handler + * ========================================================================= */ + +static int sim_handle_sha_final(const CmShaFinalReq* req, word32 req_len, + CmShaFinalResp* resp) +{ + SimShaSlot* slot; + int slot_idx; + int ret = 0; + word32 digest_len = 0; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + slot = sim_sha_lookup(req->context); + if (slot == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + /* Get 0-based index for freeing later */ + slot_idx = (int)(get_le32(req->context) - 1); + + /* Process final chunk if any */ + { + word32 input_sz = LE32TOH(req->input_size); + if (input_sz > 0) { + switch (slot->type) { + case SHA_256: + ret = wc_Sha256Update(&slot->u.sha256, req->input, input_sz); + break; + case SHA_384: + ret = wc_Sha384Update(&slot->u.sha384, req->input, input_sz); + break; + case SHA_512: + ret = wc_Sha512Update(&slot->u.sha512, req->input, input_sz); + break; + case SHA_NONE: + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + } + } + + switch (slot->type) { + case SHA_256: + ret = wc_Sha256Final(&slot->u.sha256, resp->hash); + digest_len = 32; + break; + case SHA_384: + ret = wc_Sha384Final(&slot->u.sha384, resp->hash); + digest_len = 48; + break; + case SHA_512: + ret = wc_Sha512Final(&slot->u.sha512, resp->hash); + digest_len = 64; + break; + case SHA_NONE: + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + sim_sha_free(slot_idx); + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + resp->hdr.data_len = HTOLE32(digest_len); + return 0; +} + +/* ========================================================================= + * CM_RANDOM_GENERATE handler + * ========================================================================= */ + +static int sim_handle_random_generate(const CmRandomGenerateReq* req, + word32 req_len, + CmRandomGenerateResp* resp) +{ + word32 size; + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + size = LE32TOH(req->size); + if (size > (word32)CMB_MAX_DATA_SIZE) + size = (word32)CMB_MAX_DATA_SIZE; + + if (sim_get_random(resp->data, size) != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + resp->hdr.data_len = HTOLE32(size); + return 0; +} + +/* ========================================================================= + * CM_AES_GCM_ENCRYPT_INIT handler + * ========================================================================= */ + +static int sim_handle_aes_enc_init(const CmAesGcmEncryptInitReq* req, + word32 req_len, + CmAesGcmEncryptInitResp* resp) +{ + SimKey* key; + int idx; + SimAesSlot* slot; + byte iv[12]; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + key = sim_key_lookup(req->cmk.bytes); + if (key == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + if (key->key_len != 32) { /* Real Caliptra hw is AES-256 only */ + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + idx = sim_aes_alloc(); + if (idx < 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + slot = &sim_aes[idx]; + slot->enc = 1; + + XMEMCPY(slot->raw_key, key->key_data, key->key_len); + + /* Generate random IV */ + if (sim_get_random(iv, 12) != 0) { + sim_aes_free(idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + XMEMCPY(slot->iv, iv, 12); + + /* Copy AAD; reject (don't silently truncate) oversized AAD so that + * aad_len always reflects what's actually in slot->aad. */ + slot->aad_len = LE32TOH(req->aad_size); + if (slot->aad_len > (word32)SIM_AES_MAX_AAD) { + sim_aes_free(idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + if (slot->aad_len > 0) + XMEMCPY(slot->aad, req->aad, slot->aad_len); + + /* Write context: slot index + IV */ + sim_aes_ctx_from_index(resp->context, idx, iv); + + /* Return IV as u32[3] (raw bytes, same as the 12-byte IV) */ + XMEMCPY(resp->iv, iv, 12); + + return 0; +} + +/* ========================================================================= + * CM_AES_GCM_ENCRYPT_UPDATE handler + * ========================================================================= */ + +static int sim_handle_aes_enc_update(const CmAesGcmEncryptUpdateReq* req, + word32 req_len, + CmAesGcmEncryptUpdateResp* resp) +{ + SimAesSlot* slot; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + slot = sim_aes_lookup(req->context); + if (slot == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + /* Buffer plaintext */ + { + word32 pt_sz = LE32TOH(req->plaintext_size); + if (pt_sz > 0) { + if (slot->data_len + pt_sz > (word32)SIM_AES_MAX_DATA) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + XMEMCPY(slot->data + slot->data_len, req->plaintext, pt_sz); + slot->data_len += pt_sz; + } + } + + /* Defer encryption to FINAL; return empty ciphertext */ + XMEMCPY(resp->context, req->context, CMB_AES_GCM_ENCRYPTED_CTX_SIZE); + resp->ciphertext_size = HTOLE32(0); + return 0; +} + +/* ========================================================================= + * CM_AES_GCM_ENCRYPT_FINAL handler + * ========================================================================= */ + +static int sim_handle_aes_enc_final(const CmAesGcmEncryptFinalReq* req, + word32 req_len, + CmAesGcmEncryptFinalResp* resp) +{ + SimAesSlot* slot; + int slot_idx; + Aes aes; + int ret; + byte tag[16]; + word32 total_len; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + slot = sim_aes_lookup(req->context); + if (slot == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + slot_idx = (int)(get_le32(req->context) - 1); + + /* Append final plaintext chunk */ + { + word32 pt_sz = LE32TOH(req->plaintext_size); + if (pt_sz > 0) { + if (slot->data_len + pt_sz > (word32)SIM_AES_MAX_DATA) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + XMEMCPY(slot->data + slot->data_len, req->plaintext, pt_sz); + slot->data_len += pt_sz; + } + } + + total_len = slot->data_len; + + /* resp->ciphertext is sized for a single mailbox response, not the + * full streaming-input cap. The port always batches a single ENCRYPT + * up-front (so total_len fits CMB_MAX_DATA_SIZE), but a misbehaving + * direct caller could buffer more than that across multiple UPDATEs. + * Reject before the wc_AesGcmEncrypt write overflows resp->ciphertext. */ + if (total_len > (word32)CMB_MAX_DATA_SIZE) { + sim_aes_free(slot_idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + /* Encrypt all buffered data at once */ + ret = wc_AesInit(&aes, NULL, WC_NO_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(&aes, slot->raw_key, 32); + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, + resp->ciphertext, + slot->data, total_len, + slot->iv, 12, + tag, 16, + slot->aad_len > 0 ? slot->aad : NULL, + slot->aad_len); + } + wc_AesFree(&aes); + + sim_aes_free(slot_idx); + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Write tag as u32[4] (raw bytes) */ + XMEMCPY(resp->tag, tag, 16); + resp->ciphertext_size = HTOLE32(total_len); + return 0; +} + +/* ========================================================================= + * CM_AES_GCM_DECRYPT_INIT handler + * ========================================================================= */ + +static int sim_handle_aes_dec_init(const CmAesGcmDecryptInitReq* req, + word32 req_len, + CmAesGcmDecryptInitResp* resp) +{ + SimKey* key; + int idx; + SimAesSlot* slot; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + key = sim_key_lookup(req->cmk.bytes); + if (key == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + if (key->key_len != 32) { /* Real Caliptra hw is AES-256 only */ + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + idx = sim_aes_alloc(); + if (idx < 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + slot = &sim_aes[idx]; + slot->enc = 0; + + XMEMCPY(slot->raw_key, key->key_data, key->key_len); + + /* IV supplied by caller as byte[12] stored in iv[3] (u32[3]) */ + XMEMCPY(slot->iv, req->iv, 12); + + /* Copy AAD; reject (don't silently truncate) oversized AAD so that + * aad_len always reflects what's actually in slot->aad. */ + slot->aad_len = LE32TOH(req->aad_size); + if (slot->aad_len > (word32)SIM_AES_MAX_AAD) { + sim_aes_free(idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + if (slot->aad_len > 0) + XMEMCPY(slot->aad, req->aad, slot->aad_len); + + sim_aes_ctx_from_index(resp->context, idx, NULL); + return 0; +} + +/* ========================================================================= + * CM_AES_GCM_DECRYPT_UPDATE handler + * ========================================================================= */ + +static int sim_handle_aes_dec_update(const CmAesGcmDecryptUpdateReq* req, + word32 req_len, + CmAesGcmDecryptUpdateResp* resp) +{ + SimAesSlot* slot; + word32 chunk_sz; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + slot = sim_aes_lookup(req->context); + if (slot == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + chunk_sz = LE32TOH(req->ciphertext_size); + + /* The wire format caps a single UPDATE chunk at CMB_MAX_DATA_SIZE; the + * response struct's plaintext field is sized accordingly. Reject + * oversized chunks before they reach the memcpy into resp->plaintext + * below, which would otherwise overflow a 4096-byte buffer. */ + if (chunk_sz > (word32)CMB_MAX_DATA_SIZE) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + if (chunk_sz > 0) { + Aes aes; + byte throwaway_tag[16]; + int ret; + + /* Buffer ciphertext for later tag verification at FINAL */ + if (slot->data_len + chunk_sz > (word32)SIM_AES_MAX_DATA) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + XMEMCPY(slot->data + slot->data_len, req->ciphertext, chunk_sz); + slot->data_len += chunk_sz; + + /* Recover plaintext via the GCM CTR-mode symmetry: feeding the + * ciphertext back through wc_AesGcmEncrypt with the same key, IV, + * and AAD produces the original plaintext as its output (because + * the GCM keystream depends only on key+IV+counter, not on + * direction). The tag this call computes is over the ciphertext- + * shaped input, not over the real ciphertext, so we discard it — + * the real tag check happens at DECRYPT_FINAL by re-encrypting + * the recovered plaintext (unchanged). + * + * This replaces an earlier approach that called wc_AesGcmDecrypt + * with an all-zero tag and suppressed AES_GCM_AUTH_E, which relied + * on the undocumented "output before auth" behaviour of + * wc_AesGcmDecrypt. The encrypt-based path has no such + * dependency. */ + ret = wc_AesInit(&aes, NULL, WC_NO_DEVID); + if (ret == 0) + ret = wc_AesGcmSetKey(&aes, slot->raw_key, 32); + if (ret == 0) { + /* "Encrypt" the entire accumulated ciphertext to recover + * plaintext. (The port currently sends all data in one UPDATE, + * so this also works for the streaming case.) */ + ret = wc_AesGcmEncrypt(&aes, + slot->plaintext, + slot->data, slot->data_len, + slot->iv, 12, + throwaway_tag, 16, + slot->aad_len > 0 ? slot->aad : NULL, + slot->aad_len); + if (ret == 0) + slot->pt_len = slot->data_len; + } + wc_AesFree(&aes); + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Return decrypted plaintext for the current chunk */ + resp->plaintext_size = HTOLE32(chunk_sz); + XMEMCPY(resp->plaintext, slot->plaintext, chunk_sz); + } + + XMEMCPY(resp->context, req->context, CMB_AES_GCM_ENCRYPTED_CTX_SIZE); + return 0; +} + +/* ========================================================================= + * CM_AES_GCM_DECRYPT_FINAL handler + * ========================================================================= */ + +static int sim_handle_aes_dec_final(const CmAesGcmDecryptFinalReq* req, + word32 req_len, + CmAesGcmDecryptFinalResp* resp) +{ + SimAesSlot* slot; + int slot_idx; + Aes aes; + int ret; + byte tag[16]; + byte computed_tag[16]; + /* dummy_ct holds the re-encrypted ciphertext used solely as the + * destination buffer for wc_AesGcmEncrypt during tag verification — + * its contents are never read. At SIM_AES_MAX_DATA (16 KB) it is too + * large to live on the stack on constrained hosts. Concurrent access + * is serialised by sim_mailbox_lock (see file header THREAD SAFETY), + * so a function-local static is safe and cheaper than per-call + * heap allocation. Not zero-initialised: any prior contents are + * always fully overwritten by wc_AesGcmEncrypt below before any + * subsequent read could occur (and even if not, the buffer is + * never read at all). */ + static byte dummy_ct[SIM_AES_MAX_DATA]; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + slot = sim_aes_lookup(req->context); + if (slot == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + slot_idx = (int)(get_le32(req->context) - 1); + + /* Note: the port sends ciphertext_size=0 in FINAL (no remaining data). + * If there IS a final ciphertext chunk, handle it. */ + { + word32 ct_sz = LE32TOH(req->ciphertext_size); + if (ct_sz > 0) { + if (slot->data_len + ct_sz > (word32)SIM_AES_MAX_DATA) { + sim_aes_free(slot_idx); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + XMEMCPY(slot->data + slot->data_len, req->ciphertext, ct_sz); + slot->data_len += ct_sz; + } + } + + /* Extract the tag the caller wants us to verify */ + XMEMCPY(tag, req->tag, 16); + + /* Verify the tag by re-encrypting the plaintext we already computed + * during DECRYPT_UPDATE. The resulting computed_tag must match tag. */ + ret = wc_AesInit(&aes, NULL, WC_NO_DEVID); + if (ret == 0) + ret = wc_AesGcmSetKey(&aes, slot->raw_key, 32); + if (ret == 0 && slot->pt_len > 0) { + ret = wc_AesGcmEncrypt(&aes, + dummy_ct, + slot->plaintext, slot->pt_len, + slot->iv, 12, + computed_tag, 16, + slot->aad_len > 0 ? slot->aad : NULL, + slot->aad_len); + } else if (ret == 0) { + /* No plaintext (empty message): encrypt empty data to get tag */ + ret = wc_AesGcmEncrypt(&aes, + NULL, + NULL, 0, + slot->iv, 12, + computed_tag, 16, + slot->aad_len > 0 ? slot->aad : NULL, + slot->aad_len); + } + wc_AesFree(&aes); + + sim_aes_free(slot_idx); + + if (ret != 0) { + /* Intentionally returns 0 (not ret) even though the re-encrypt + * failed. This matches real Caliptra firmware's behaviour for + * CM_AES_GCM_DECRYPT_FINAL: errors are conveyed in the response + * header's fips_status field, not in the mailbox transport-level + * return code. The caliptra_port.c decrypt handler reads + * fips_status after the mailbox call (caliptra_port.c around + * line 1013: WC_HW_E if fips_status != 0), so signalling via + * fips_status=0xFF here is the correct simulation of the HW + * error path. Differs from other sim handlers that return the + * error code directly because those handlers correspond to HW + * commands that DO use the transport return for errors. */ + resp->hdr.fips_status = HTOLE32(0xFF); + return 0; + } + + /* Compare computed tag with provided tag in constant time so that the + * sim does not leak the mismatch position via timing. Mirrors real + * firmware: fips_status=0 in both cases; tag_verified encodes the + * result (1=match, 0=mismatch). + * Source: cryptographic_mailbox.rs: resp.tag_verified = (computed==expected) as u32 */ + resp->tag_verified = HTOLE32((ConstantCompare(computed_tag, tag, 16) == 0) ? 1 : 0); + + return 0; +} + +/* ========================================================================= + * CM_ECDSA_SIGN handler + * ========================================================================= */ + +static int sim_handle_ecdsa_sign(const CmEcdsaSignReq* req, word32 req_len, + CmEcdsaSignResp* resp) +{ + SimKey* key; + ecc_key ecc; + WC_RNG rng; + byte sig_der[160]; + word32 sig_len = sizeof(sig_der); + byte r[48], s[48]; + word32 rLen = 48, sLen = 48; + int ret; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + key = sim_key_lookup(req->cmk.bytes); + if (key == NULL || key->key_type != SIM_KEY_TYPE_ECC_PRV) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + ret = wc_ecc_init_ex(&ecc, NULL, WC_NO_DEVID); + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Import private key for P-384 */ + ret = wc_ecc_import_private_key_ex(key->key_data, key->key_len, + NULL, 0, &ecc, ECC_SECP384R1); + if (ret != 0) { + wc_ecc_free(&ecc); + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + ret = wc_InitRng(&rng); + if (ret != 0) { + wc_ecc_free(&ecc); + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + { + word32 msg_sz = LE32TOH(req->message_size); + ret = wc_ecc_sign_hash(req->message, msg_sz, + sig_der, &sig_len, &rng, &ecc); + } + wc_FreeRng(&rng); + wc_ecc_free(&ecc); + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Decode DER to raw r, s */ + XMEMSET(r, 0, 48); + XMEMSET(s, 0, 48); + ret = wc_ecc_sig_to_rs(sig_der, sig_len, r, &rLen, s, &sLen); + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Zero-pad r and s to 48 bytes (big-endian, left-pad with zeros) */ + if (rLen < 48) { + byte tmp[48]; + XMEMSET(tmp, 0, 48); + XMEMCPY(tmp + (48 - rLen), r, rLen); + XMEMCPY(r, tmp, 48); + } + if (sLen < 48) { + byte tmp[48]; + XMEMSET(tmp, 0, 48); + XMEMCPY(tmp + (48 - sLen), s, sLen); + XMEMCPY(s, tmp, 48); + } + + XMEMCPY(resp->signature_r, r, 48); + XMEMCPY(resp->signature_s, s, 48); + return 0; +} + +/* ========================================================================= + * CM_ECDSA_VERIFY handler + * ========================================================================= */ + +static int sim_handle_ecdsa_verify(const CmEcdsaVerifyReq* req, word32 req_len, + CmEcdsaVerifyResp* resp) +{ + SimKey* key; + ecc_key ecc; + byte sig_der[160]; + word32 sig_len = sizeof(sig_der); + byte r[48], s[48]; + int verify_res = 0; + int ret; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + key = sim_key_lookup(req->cmk.bytes); + if (key == NULL || key->key_type != SIM_KEY_TYPE_ECC_PUB) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + /* Encode raw r, s to DER */ + XMEMCPY(r, req->signature_r, 48); + XMEMCPY(s, req->signature_s, 48); + + ret = wc_ecc_rs_raw_to_sig(r, 48, s, 48, sig_der, &sig_len); + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* Import public key: x||y from key_data */ + ret = wc_ecc_init_ex(&ecc, NULL, WC_NO_DEVID); + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + /* P-384 public key: first 48 bytes = x, next 48 bytes = y */ + if (key->key_len >= 96) { + ret = wc_ecc_import_unsigned(&ecc, + key->key_data, /* Qx */ + key->key_data + 48, /* Qy */ + NULL, /* private (none) */ + ECC_SECP384R1); + } + else { + wc_ecc_free(&ecc); + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + if (ret != 0) { + wc_ecc_free(&ecc); + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + { + word32 msg_sz = LE32TOH(req->message_size); + ret = wc_ecc_verify_hash(sig_der, sig_len, + req->message, msg_sz, + &verify_res, &ecc); + } + wc_ecc_free(&ecc); + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + if (verify_res != 1) { + /* Real Caliptra firmware returns CmdFailure for invalid signatures + * (Ecc384Result::SigVerifyFailed → RUNTIME_MAILBOX_SIGNATURE_MISMATCH). + * The transport layer maps CmdFailure to SIG_VERIFY_E. + * Simulate that behavior so the port exercises the same code path. */ + return SIG_VERIFY_E; + } + + /* Valid signature: fips_status = 0 (FIPS_STATUS_APPROVED). */ + resp->hdr.fips_status = 0; + return 0; +} + +/* ========================================================================= + * CM_HMAC handler + * ========================================================================= */ + +static int sim_handle_hmac(const CmHmacReq* req, word32 req_len, + CmHmacResp* resp) +{ + SimKey* key; + Hmac hmac; + int hmac_type; + word32 mac_len; + int ret; + + (void)req_len; + + XMEMSET(resp, 0, sizeof(*resp)); + + key = sim_key_lookup(req->cmk.bytes); + if (key == NULL) { + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + + { + word32 hash_alg = LE32TOH(req->hash_algorithm); + switch (hash_alg) { + case SIM_SHA_ALG_SHA256: hmac_type = WC_SHA256; mac_len = 32; break; + case CMB_SHA_ALG_SHA384: hmac_type = WC_SHA384; mac_len = 48; break; + case CMB_SHA_ALG_SHA512: hmac_type = WC_SHA512; mac_len = 64; break; + default: + resp->hdr.fips_status = HTOLE32(0xFF); + return -1; + } + } + + ret = wc_HmacInit(&hmac, NULL, WC_NO_DEVID); + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + { + word32 data_sz = LE32TOH(req->data_size); + ret = wc_HmacSetKey(&hmac, hmac_type, key->key_data, key->key_len); + if (ret == 0 && data_sz > 0) + ret = wc_HmacUpdate(&hmac, req->data, data_sz); + } + if (ret == 0) + ret = wc_HmacFinal(&hmac, resp->mac); + wc_HmacFree(&hmac); + + if (ret != 0) { + resp->hdr.fips_status = HTOLE32(0xFF); + return ret; + } + + resp->hdr.data_len = HTOLE32(mac_len); + return 0; +} + +/* ========================================================================= + * caliptra_mailbox_exec — main dispatch (mutex-wrapped) + * + * The public entry point holds sim_mailbox_lock for the duration of the + * command so the static slot tables (sim_keys/sim_sha/sim_aes) and the + * function-local statics in handlers are accessed by at most one thread + * at a time. The mutex is initialised by wc_caliptra_init() — callers + * must invoke that before the first concurrent mailbox operation. + * ========================================================================= */ + +static int sim_mailbox_exec_locked(word32 cmd_id, + const void* req, word32 req_len, + void* resp, word32 resp_len) +{ + /* MEDIUM-37: reject callers passing a too-small resp buffer for the + * command they invoked. The handlers below XMEMSET(resp, 0, + * sizeof(*resp)) and dereference resp as a specific concrete type; + * an undersized buffer would overflow into adjacent memory. Real + * Caliptra firmware reports a Mailbox::IncorrectResponseSize error + * for this case — returning -1 is the closest sim-side analogue. */ + word32 expected_resp_sz; + switch (cmd_id) { + case CM_IMPORT: expected_resp_sz = sizeof(CmImportResp); break; + case CM_DELETE: expected_resp_sz = sizeof(CmDeleteResp); break; + case CM_SHA_INIT: expected_resp_sz = sizeof(CmShaInitResp); break; + case CM_SHA_UPDATE: expected_resp_sz = sizeof(CmShaUpdateResp); break; + case CM_SHA_FINAL: expected_resp_sz = sizeof(CmShaFinalResp); break; + case CM_RANDOM_GENERATE: expected_resp_sz = sizeof(CmRandomGenerateResp); break; + case CM_AES_GCM_ENCRYPT_INIT: expected_resp_sz = sizeof(CmAesGcmEncryptInitResp); break; + case CM_AES_GCM_ENCRYPT_UPDATE: expected_resp_sz = sizeof(CmAesGcmEncryptUpdateResp); break; + case CM_AES_GCM_ENCRYPT_FINAL: expected_resp_sz = sizeof(CmAesGcmEncryptFinalResp); break; + case CM_AES_GCM_DECRYPT_INIT: expected_resp_sz = sizeof(CmAesGcmDecryptInitResp); break; + case CM_AES_GCM_DECRYPT_UPDATE: expected_resp_sz = sizeof(CmAesGcmDecryptUpdateResp); break; + case CM_AES_GCM_DECRYPT_FINAL: expected_resp_sz = sizeof(CmAesGcmDecryptFinalResp); break; + case CM_ECDSA_SIGN: expected_resp_sz = sizeof(CmEcdsaSignResp); break; + case CM_ECDSA_VERIFY: expected_resp_sz = sizeof(CmEcdsaVerifyResp); break; + case CM_HMAC: expected_resp_sz = sizeof(CmHmacResp); break; + default: expected_resp_sz = 0; break; /* unknown cmd; falls through */ + } + if (expected_resp_sz != 0 && resp_len < expected_resp_sz) + return -1; + + switch (cmd_id) { + case CM_IMPORT: + return sim_handle_import((const CmImportReq*)req, req_len, + (CmImportResp*)resp); + + case CM_DELETE: + return sim_handle_delete((const CmDeleteReq*)req, req_len, + (CmDeleteResp*)resp); + + case CM_SHA_INIT: + return sim_handle_sha_init((const CmShaInitReq*)req, req_len, + (CmShaInitResp*)resp); + + case CM_SHA_UPDATE: + return sim_handle_sha_update((const CmShaUpdateReq*)req, req_len, + (CmShaUpdateResp*)resp); + + case CM_SHA_FINAL: + return sim_handle_sha_final((const CmShaFinalReq*)req, req_len, + (CmShaFinalResp*)resp); + + case CM_RANDOM_GENERATE: + return sim_handle_random_generate((const CmRandomGenerateReq*)req, + req_len, + (CmRandomGenerateResp*)resp); + + case CM_AES_GCM_ENCRYPT_INIT: + return sim_handle_aes_enc_init((const CmAesGcmEncryptInitReq*)req, + req_len, + (CmAesGcmEncryptInitResp*)resp); + + case CM_AES_GCM_ENCRYPT_UPDATE: + return sim_handle_aes_enc_update((const CmAesGcmEncryptUpdateReq*)req, + req_len, + (CmAesGcmEncryptUpdateResp*)resp); + + case CM_AES_GCM_ENCRYPT_FINAL: + return sim_handle_aes_enc_final((const CmAesGcmEncryptFinalReq*)req, + req_len, + (CmAesGcmEncryptFinalResp*)resp); + + case CM_AES_GCM_DECRYPT_INIT: + return sim_handle_aes_dec_init((const CmAesGcmDecryptInitReq*)req, + req_len, + (CmAesGcmDecryptInitResp*)resp); + + case CM_AES_GCM_DECRYPT_UPDATE: + return sim_handle_aes_dec_update((const CmAesGcmDecryptUpdateReq*)req, + req_len, + (CmAesGcmDecryptUpdateResp*)resp); + + case CM_AES_GCM_DECRYPT_FINAL: + return sim_handle_aes_dec_final((const CmAesGcmDecryptFinalReq*)req, + req_len, + (CmAesGcmDecryptFinalResp*)resp); + + case CM_ECDSA_SIGN: + return sim_handle_ecdsa_sign((const CmEcdsaSignReq*)req, req_len, + (CmEcdsaSignResp*)resp); + + case CM_ECDSA_VERIFY: + return sim_handle_ecdsa_verify((const CmEcdsaVerifyReq*)req, req_len, + (CmEcdsaVerifyResp*)resp); + + case CM_HMAC: + return sim_handle_hmac((const CmHmacReq*)req, req_len, + (CmHmacResp*)resp); + + default: + WOLFSSL_MSG_EX("caliptra_sim: unknown cmd_id 0x%08X", cmd_id); + return -1; + } +} + +int caliptra_mailbox_exec(word32 cmd_id, + const void* req, word32 req_len, + void* resp, word32 resp_len) +{ + int ret; + + if (!sim_mailbox_lock_init) + return BAD_MUTEX_E; + if (wc_LockMutex(&sim_mailbox_lock) != 0) + return BAD_MUTEX_E; + + ret = sim_mailbox_exec_locked(cmd_id, req, req_len, resp, resp_len); + + (void)wc_UnLockMutex(&sim_mailbox_lock); + return ret; +} + +#endif /* WOLFSSL_CALIPTRA_SIM */ diff --git a/wolfcrypt/src/port/caliptra/sim/caliptra_test.c b/wolfcrypt/src/port/caliptra/sim/caliptra_test.c new file mode 100644 index 00000000000..e011c0f7bc1 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/sim/caliptra_test.c @@ -0,0 +1,635 @@ +/* caliptra_test.c — test harness for the wolfSSL Caliptra port + * + * Exercises the port via the standard wolfSSL API with WOLF_CALIPTRA_DEVID. + * + * Two transport backends are supported: + * + * Software simulator (caliptra_sim.c) — default, no external dependencies: + * gcc $CFLAGS -DBUILDING_WOLFSSL \ + * caliptra_sim.c caliptra_test.c \ + * wolfcrypt/src/port/caliptra/caliptra_port.o \ + * src/.libs/libwolfssl.a -lpthread -lm \ + * -o caliptra_test_bin + * + * hw-model emulator (caliptra_hwmodel.c) — requires the Caliptra hw-model + * C binding. Build using the Makefile in this directory: + * make -C wolfcrypt/src/port/caliptra/sim/ run + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* hw-model backend: include init/cleanup declarations when requested */ +#ifdef CALIPTRA_HWMODEL +#include "caliptra_hwmodel.h" +#endif + +/* Use INVALID_DEVID when we want software fallback (no CryptoCb device) */ +#ifndef WC_NO_DEVID +#define WC_NO_DEVID INVALID_DEVID +#endif + +/* ========================================================================= + * Test infrastructure + * ========================================================================= */ + +static int tests_run = 0; +static int tests_pass = 0; + +#define TEST(name, cond) do { \ + tests_run++; \ + if (cond) { \ + printf("PASS: %s\n", name); \ + tests_pass++; \ + } else { \ + printf("FAIL: %s (line %d)\n", name, __LINE__); \ + } \ +} while (0) + +/* Compare byte arrays; return 1 if equal */ +static int bytes_eq(const byte* a, const byte* b, int len) +{ + int i, diff = 0; + for (i = 0; i < len; i++) diff |= (int)(a[i] ^ b[i]); + return diff == 0; +} + +/* ========================================================================= + * Test 1: RNG generates non-zero bytes + * ========================================================================= */ + +static void test_rng(void) +{ + WC_RNG rng; + byte buf1[32]; + byte buf2[32]; + int ret; + int any_nonzero = 0; + int i; + + memset(buf1, 0, sizeof(buf1)); + memset(buf2, 0, sizeof(buf2)); + + ret = wc_InitRng_ex(&rng, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) + ret = wc_RNG_GenerateBlock(&rng, buf1, sizeof(buf1)); + if (ret == 0) + ret = wc_RNG_GenerateBlock(&rng, buf2, sizeof(buf2)); + wc_FreeRng(&rng); + + for (i = 0; i < 32; i++) { + if (buf1[i]) any_nonzero = 1; + } + + /* Two successive 32-byte generations from the same RNG must differ. + * Catches a stuck-at-constant DRBG (e.g. a transport that returns + * a cached buffer instead of running the firmware DRBG) which a + * single nonzero-bytes check would not detect. */ + TEST("RNG generates nonzero and non-repeating", + ret == 0 && any_nonzero && !bytes_eq(buf1, buf2, 32)); +} + +/* ========================================================================= + * Test 2: SHA-256 empty message known-answer + * ========================================================================= */ + +static void test_sha256_empty(void) +{ + static const byte sha256_empty[] = { + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 + }; + + wc_Sha256 sha; + byte digest[32]; + int ret; + + ret = wc_InitSha256_ex(&sha, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) + ret = wc_Sha256Final(&sha, digest); + wc_Sha256Free(&sha); + + TEST("SHA-256 empty KAT", ret == 0 && bytes_eq(digest, sha256_empty, 32)); +} + +/* ========================================================================= + * Test 3: SHA-256 "abc" known-answer + * ========================================================================= */ + +static void test_sha256_abc(void) +{ + static const byte sha256_abc[] = { + 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 + }; + + wc_Sha256 sha; + byte digest[32]; + int ret; + + ret = wc_InitSha256_ex(&sha, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) + ret = wc_Sha256Update(&sha, (const byte*)"abc", 3); + if (ret == 0) + ret = wc_Sha256Final(&sha, digest); + wc_Sha256Free(&sha); + + TEST("SHA-256 abc KAT", ret == 0 && bytes_eq(digest, sha256_abc, 32)); +} + +/* ========================================================================= + * Test 4: SHA-384 empty message known-answer + * ========================================================================= */ + +static void test_sha384_empty(void) +{ + static const byte sha384_empty[] = { + 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, + 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, + 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, + 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, + 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, + 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b + }; + + wc_Sha384 sha; + byte digest[48]; + int ret; + + ret = wc_InitSha384_ex(&sha, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) + ret = wc_Sha384Final(&sha, digest); + wc_Sha384Free(&sha); + + TEST("SHA-384 empty KAT", ret == 0 && bytes_eq(digest, sha384_empty, 48)); +} + +/* ========================================================================= + * Test 5: SHA-256 multi-update matches single-call software + * ========================================================================= */ + +static void test_sha256_multiupdate(void) +{ + wc_Sha256 sha1, sha2; + byte d1[32], d2[32]; + int ret1, ret2; + + /* Via Caliptra device with two updates */ + ret1 = wc_InitSha256_ex(&sha1, NULL, WOLF_CALIPTRA_DEVID); + if (ret1 == 0) + ret1 = wc_Sha256Update(&sha1, (const byte*)"Hello, ", 7); + if (ret1 == 0) + ret1 = wc_Sha256Update(&sha1, (const byte*)"Caliptra!", 9); + if (ret1 == 0) + ret1 = wc_Sha256Final(&sha1, d1); + wc_Sha256Free(&sha1); + + /* Software reference: single update */ + ret2 = wc_InitSha256_ex(&sha2, NULL, WC_NO_DEVID); + if (ret2 == 0) + ret2 = wc_Sha256Update(&sha2, (const byte*)"Hello, Caliptra!", 16); + if (ret2 == 0) + ret2 = wc_Sha256Final(&sha2, d2); + wc_Sha256Free(&sha2); + + TEST("SHA-256 multi-update matches software", + ret1 == 0 && ret2 == 0 && bytes_eq(d1, d2, 32)); +} + +/* ========================================================================= + * Test 6: AES-GCM encrypt then decrypt (roundtrip) + * ========================================================================= */ + +static void test_aesgcm_roundtrip(void) +{ + /* TEST-ONLY: NIST AES-256 test vector key from FIPS 197 / SP 800-38D. + * Not for production use; reused across this file's AES-GCM tests for + * traceability against published vectors. */ + static const byte aes_key[32] = { + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 + }; + static const byte plaintext[] = "Hello, Caliptra! This is a test."; + static const byte aad[] = "additional data"; + + CaliptraCmk enc_cmk; + byte ciphertext[48]; + byte decrypted[48]; + byte enc_tag[16]; + byte iv_out[12]; + Aes enc_aes, dec_aes; + byte dummy_iv[12]; /* placeholder: wolfSSL requires ivSz>0; Caliptra ignores it */ + int ret; + + memset(ciphertext, 0, sizeof(ciphertext)); + memset(decrypted, 0, sizeof(decrypted)); + memset(enc_tag, 0, sizeof(enc_tag)); + memset(iv_out, 0, sizeof(iv_out)); + memset(dummy_iv, 0, sizeof(dummy_iv)); + + /* Import the AES key */ + ret = wc_caliptra_import_key(aes_key, 32, CMB_KEY_USAGE_AES /* 2 */, &enc_cmk); + if (ret != 0) { + TEST("AES-GCM encrypt/decrypt roundtrip", 0); + return; + } + + /* Encrypt: Caliptra generates the IV */ + ret = wc_AesInit(&enc_aes, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) { + enc_aes.devCtx = &enc_cmk; + ret = wc_AesGcmEncrypt(&enc_aes, + ciphertext, plaintext, 32, + dummy_iv, 12, + enc_tag, 16, + aad, 15); + if (ret == 0) { + ret = wc_caliptra_aesgcm_get_iv(&enc_aes, iv_out, sizeof(iv_out)); + } + wc_AesFree(&enc_aes); + } + + if (ret != 0) { + wc_caliptra_delete_key(&enc_cmk); + TEST("AES-GCM encrypt/decrypt roundtrip", 0); + return; + } + + /* Decrypt using the Caliptra-generated IV */ + ret = wc_AesInit(&dec_aes, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) { + dec_aes.devCtx = &enc_cmk; /* same key handle */ + ret = wc_AesGcmDecrypt(&dec_aes, + decrypted, ciphertext, 32, + iv_out, 12, + enc_tag, 16, + aad, 15); + wc_AesFree(&dec_aes); + } + + wc_caliptra_delete_key(&enc_cmk); + + TEST("AES-GCM encrypt/decrypt roundtrip", + ret == 0 && bytes_eq(decrypted, plaintext, 32)); +} + +/* ========================================================================= + * Test 7: ECDSA sign and verify + * ========================================================================= */ + +static void test_ecdsa_sign_verify(void) +{ + WC_RNG rng; + ecc_key raw_key; /* software key for material generation */ + ecc_key sign_key, ver_key; /* Caliptra-routed sign and verify keys */ + CaliptraCmk sign_cmk, verify_cmk; + byte sig_der[160]; + word32 sig_len = sizeof(sig_der); + byte hash[48]; + byte priv_bytes[48]; + word32 priv_len = sizeof(priv_bytes); + byte pub_x[48], pub_y[48]; + word32 pub_xlen = sizeof(pub_x), pub_ylen = sizeof(pub_y); + byte pub_key[96]; /* Qx || Qy for Caliptra verify import */ + int verify_res = 0; + int saved_devId; + int ret; + + memset(hash, 0xAB, sizeof(hash)); + memset(sig_der, 0, sizeof(sig_der)); + memset(priv_bytes, 0, sizeof(priv_bytes)); + memset(pub_x, 0, sizeof(pub_x)); + memset(pub_y, 0, sizeof(pub_y)); + memset(pub_key, 0, sizeof(pub_key)); + memset(&sign_cmk, 0, sizeof(sign_cmk)); + memset(&verify_cmk, 0, sizeof(verify_cmk)); + + ret = wc_InitRng(&rng); + if (ret != 0) { + TEST("Caliptra ECDSA sign+verify", 0); + return; + } + + /* Generate a P-384 keypair in software to obtain priv/pub material. */ + wc_ecc_init_ex(&raw_key, NULL, INVALID_DEVID); + ret = wc_ecc_make_key_ex(&rng, 48, &raw_key, ECC_SECP384R1); + if (ret == 0) + ret = wc_ecc_export_private_only(&raw_key, priv_bytes, &priv_len); + if (ret == 0) + ret = wc_ecc_export_public_raw(&raw_key, + pub_x, &pub_xlen, + pub_y, &pub_ylen); + wc_ecc_free(&raw_key); + if (ret != 0) { + wc_ForceZero(priv_bytes, sizeof(priv_bytes)); + wc_FreeRng(&rng); + TEST("Caliptra ECDSA sign+verify", 0); + return; + } + + /* Import priv as ECDSA sign CMK (sim treats <=48 bytes as private scalar). */ + ret = wc_caliptra_import_key(priv_bytes, priv_len, + CMB_KEY_USAGE_ECDSA, &sign_cmk); + if (ret != 0) { + wc_ForceZero(priv_bytes, sizeof(priv_bytes)); + wc_FreeRng(&rng); + TEST("Caliptra ECDSA sign+verify", 0); + return; + } + + /* Import Qx||Qy (96 bytes) as ECDSA verify CMK. */ + memcpy(pub_key, pub_x, 48); + memcpy(pub_key + 48, pub_y, 48); + ret = wc_caliptra_import_key(pub_key, sizeof(pub_key), + CMB_KEY_USAGE_ECDSA, &verify_cmk); + if (ret != 0) { + wc_caliptra_delete_key(&sign_cmk); + wc_ForceZero(priv_bytes, sizeof(priv_bytes)); + wc_FreeRng(&rng); + TEST("Caliptra ECDSA sign+verify", 0); + return; + } + + /* Sign via Caliptra. Use wc_ecc_init_ex + wc_ecc_import_unsigned with + * devId temporarily INVALID_DEVID so the import does not route through + * CryptoCb (which has no callback for "import an unsigned key"). After + * import, restore devId and set devCtx so wc_ecc_sign_hash dispatches + * through caliptra_ecdsa_sign(). This is the same idiom as + * wolfcrypt/test/test.c caliptra_test() Test 6. */ + wc_ecc_init_ex(&sign_key, NULL, WOLF_CALIPTRA_DEVID); + saved_devId = sign_key.devId; + sign_key.devId = INVALID_DEVID; + ret = wc_ecc_import_unsigned(&sign_key, pub_x, pub_y, + priv_bytes, ECC_SECP384R1); + sign_key.devId = saved_devId; + if (ret == 0) { + sign_key.devCtx = &sign_cmk; + ret = wc_ecc_sign_hash(hash, sizeof(hash), + sig_der, &sig_len, &rng, &sign_key); + } + wc_ecc_free(&sign_key); + + if (ret != 0) { + wc_caliptra_delete_key(&sign_cmk); + wc_caliptra_delete_key(&verify_cmk); + wc_ForceZero(priv_bytes, sizeof(priv_bytes)); + wc_FreeRng(&rng); + TEST("Caliptra ECDSA sign+verify", 0); + return; + } + + /* Verify via Caliptra. Same import-with-temporary-devId pattern; verify + * does not need the private scalar. */ + wc_ecc_init_ex(&ver_key, NULL, WOLF_CALIPTRA_DEVID); + saved_devId = ver_key.devId; + ver_key.devId = INVALID_DEVID; + ret = wc_ecc_import_unsigned(&ver_key, pub_x, pub_y, NULL, ECC_SECP384R1); + ver_key.devId = saved_devId; + if (ret == 0) { + ver_key.devCtx = &verify_cmk; + ret = wc_ecc_verify_hash(sig_der, sig_len, hash, sizeof(hash), + &verify_res, &ver_key); + } + wc_ecc_free(&ver_key); + + wc_caliptra_delete_key(&sign_cmk); + wc_caliptra_delete_key(&verify_cmk); + wc_ForceZero(priv_bytes, sizeof(priv_bytes)); + wc_FreeRng(&rng); + + TEST("Caliptra ECDSA sign+verify", ret == 0 && verify_res == 1); +} + +/* ========================================================================= + * Test 8: HMAC-SHA-384 matches software reference + * + * The Caliptra HMAC mailbox command is single-shot: all message data must + * be present in one mailbox call. wolfSSL's CryptoCb HMAC callback is + * invoked once per wc_HmacUpdate() and once at wc_HmacFinal(), so the + * streaming Hmac API cannot be plumbed through to Caliptra. caliptra_port.c + * returns WC_HW_E (not CRYPTOCB_UNAVAILABLE) when hmac->devCtx is set, which + * prevents wolfSSL from falling through to software HMAC over an unauthorized + * key. See the compile-time assertion in caliptra_port.c that enforces + * WC_HW_E != CRYPTOCB_UNAVAILABLE for this reason. + * + * The supported path is wc_caliptra_hmac() — a single-shot wrapper around + * the CM_HMAC mailbox command. This test exercises wc_caliptra_hmac and + * cross-validates against a wolfSSL software HMAC computed with + * INVALID_DEVID (bypasses CryptoCb). Same idiom as wolfcrypt/test/test.c + * caliptra_test() Test 5. + * ========================================================================= */ + +static void test_hmac_sha384(void) +{ + /* Caliptra firmware requires HMAC keys to be exactly 48 or 64 bytes + * (SHA-384 or SHA-512 block size); 32-byte keys are rejected. */ + static const byte hmac_key[48] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b + }; + static const byte msg[] = "data"; + static const word32 msg_len = 4; + + CaliptraCmk hmac_cmk; + byte caliptra_mac[48]; + word32 caliptra_mac_len = sizeof(caliptra_mac); + byte sw_mac[48]; + Hmac sw_hmac; + int ret; + + memset(&hmac_cmk, 0, sizeof(hmac_cmk)); + memset(caliptra_mac, 0, sizeof(caliptra_mac)); + memset(sw_mac, 0, sizeof(sw_mac)); + + /* Import the HMAC key into the Caliptra sim */ + ret = wc_caliptra_import_key(hmac_key, 48, CMB_KEY_USAGE_HMAC, &hmac_cmk); + if (ret != 0) { + TEST("HMAC-SHA-384 matches software", 0); + return; + } + + /* Caliptra HMAC via the supported single-shot wrapper */ + ret = wc_caliptra_hmac(&hmac_cmk, WC_SHA384, msg, msg_len, + caliptra_mac, &caliptra_mac_len); + wc_caliptra_delete_key(&hmac_cmk); + if (ret != 0 || caliptra_mac_len != 48) { + TEST("HMAC-SHA-384 matches software", 0); + return; + } + + /* Software reference using INVALID_DEVID to bypass CryptoCb */ + ret = wc_HmacInit(&sw_hmac, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_HmacSetKey(&sw_hmac, WC_SHA384, hmac_key, 48); + if (ret == 0) + ret = wc_HmacUpdate(&sw_hmac, msg, msg_len); + if (ret == 0) + ret = wc_HmacFinal(&sw_hmac, sw_mac); + wc_HmacFree(&sw_hmac); + } + + TEST("HMAC-SHA-384 matches software", + ret == 0 && bytes_eq(caliptra_mac, sw_mac, 48)); +} + +/* ========================================================================= + * Test 9: AES-GCM authentication failure (tampered tag) + * ========================================================================= */ + +static void test_aesgcm_auth_failure(void) +{ + /* TEST-ONLY: same NIST AES-256 test vector key as test_aesgcm_roundtrip. + * Hoisting to file scope was rejected as more change than necessary; + * the duplication is intentional so each test function reads as + * self-contained when reviewed in isolation. */ + static const byte aes_key[32] = { + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 + }; + static const byte plaintext[] = "Hello, Caliptra! This is a test."; + + CaliptraCmk cmk; + byte ciphertext[48]; + byte discarded[48]; + byte auth_tag[16]; + byte bad_tag[16]; + byte iv[12]; + byte dummy_iv[12]; /* placeholder: wolfSSL requires ivSz>0; Caliptra ignores it */ + Aes aes; + int ret; + + memset(ciphertext, 0, sizeof(ciphertext)); + memset(discarded, 0, sizeof(discarded)); + memset(auth_tag, 0, sizeof(auth_tag)); + memset(iv, 0, sizeof(iv)); + memset(dummy_iv, 0, sizeof(dummy_iv)); + + ret = wc_caliptra_import_key(aes_key, 32, CMB_KEY_USAGE_AES, &cmk); + if (ret != 0) { + TEST("AES-GCM tampered tag returns AES_GCM_AUTH_E", 0); + return; + } + + /* Encrypt to obtain valid ciphertext, tag, and Caliptra-generated IV */ + ret = wc_AesInit(&aes, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) { + aes.devCtx = &cmk; + ret = wc_AesGcmEncrypt(&aes, + ciphertext, plaintext, 32, + dummy_iv, 12, + auth_tag, 16, + NULL, 0); + if (ret == 0) + ret = wc_caliptra_aesgcm_get_iv(&aes, iv, sizeof(iv)); + wc_AesFree(&aes); + } + + if (ret != 0) { + wc_caliptra_delete_key(&cmk); + TEST("AES-GCM tampered tag returns AES_GCM_AUTH_E", 0); + return; + } + + /* Tamper the authentication tag: flip the first byte */ + memcpy(bad_tag, auth_tag, 16); + bad_tag[0] ^= 0xFF; + + /* Decrypt with the tampered tag — must return AES_GCM_AUTH_E, not 0 */ + ret = wc_AesInit(&aes, NULL, WOLF_CALIPTRA_DEVID); + if (ret == 0) { + aes.devCtx = &cmk; + ret = wc_AesGcmDecrypt(&aes, + discarded, ciphertext, 32, + iv, 12, + bad_tag, 16, + NULL, 0); + wc_AesFree(&aes); + } + + wc_caliptra_delete_key(&cmk); + + TEST("AES-GCM tampered tag returns AES_GCM_AUTH_E", ret == AES_GCM_AUTH_E); +} + +/* ========================================================================= + * main + * ========================================================================= */ + +int main(void) +{ + int ret; + + wolfSSL_Init(); + +#ifdef CALIPTRA_HWMODEL + /* Boot the hw-model emulator before registering the CryptoCb device. + * ROM_PATH and FW_PATH are supplied via -D flags in the Makefile. */ +#ifndef CALIPTRA_ROM_PATH +#error "CALIPTRA_ROM_PATH must be defined when building with CALIPTRA_HWMODEL" +#endif +#ifndef CALIPTRA_FW_PATH +#error "CALIPTRA_FW_PATH must be defined when building with CALIPTRA_HWMODEL" +#endif + ret = caliptra_hwmodel_init(CALIPTRA_ROM_PATH, CALIPTRA_FW_PATH); + if (ret != 0) { + printf("FATAL: caliptra_hwmodel_init failed: %d\n", ret); + wolfSSL_Cleanup(); + return 1; + } +#endif /* CALIPTRA_HWMODEL */ + + ret = wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, wc_caliptra_cb, NULL); + if (ret != 0) { + printf("FATAL: CryptoCb_RegisterDevice failed: %d\n", ret); +#ifdef CALIPTRA_HWMODEL + caliptra_hwmodel_cleanup(); +#endif + wolfSSL_Cleanup(); + return 1; + } + + test_rng(); + test_sha256_empty(); + test_sha256_abc(); + test_sha384_empty(); + test_sha256_multiupdate(); + test_aesgcm_roundtrip(); + test_ecdsa_sign_verify(); + test_hmac_sha384(); + test_aesgcm_auth_failure(); + + printf("\n%d/%d tests passed\n", tests_pass, tests_run); + + wc_CryptoCb_UnRegisterDevice(WOLF_CALIPTRA_DEVID); + wolfSSL_Cleanup(); + +#ifdef CALIPTRA_HWMODEL + caliptra_hwmodel_cleanup(); +#endif + + return (tests_pass == tests_run) ? 0 : 1; +} diff --git a/wolfcrypt/src/port/caliptra/sim/caliptra_top_reg.h b/wolfcrypt/src/port/caliptra/sim/caliptra_top_reg.h new file mode 100644 index 00000000000..bd2e2d7cbd4 --- /dev/null +++ b/wolfcrypt/src/port/caliptra/sim/caliptra_top_reg.h @@ -0,0 +1,99 @@ +/* caliptra_top_reg.h — synthesized Caliptra register definitions + * + * The RTL-generated caliptra_top_reg.h lives in the Caliptra RTL tree at + * hw/latest/rtl/src/soc_ifc/rtl/caliptra_top_reg/ which is not distributed + * in this repository. This file is a minimal synthesis derived from the + * Rust register source files: + * hw/latest/registers/src/mbox.rs (generator commit 22ef832b4d3a) + * hw/latest/registers/src/soc_ifc.rs + * + * Only the constants actually used by caliptra_hwmodel.c are defined here. + * If the libcaliptra or hw-model example headers are compiled together with + * this file, place this file first on the include path. + */ + +#pragma once + +#include + +/* ========================================================================= + * Mailbox (MBOX) block + * + * Full APB address: + * EXTERNAL_PERIPH_BASE(0x30000000) + CALIPTRA_TOP_REG_MBOX_CSR_BASE_ADDR + * + register_offset + * ========================================================================= */ + +/* Offset of the MBOX block from EXTERNAL_PERIPH_BASE */ +#define CALIPTRA_TOP_REG_MBOX_CSR_BASE_ADDR 0x20000u + +/* Register offsets within the MBOX block. + * Source: mbox.rs — self.ptr.wrapping_add(byte_offset / size_of::()) */ +#define MBOX_CSR_MBOX_LOCK 0x00u /* RO: read to acquire lock */ +#define MBOX_CSR_MBOX_USER 0x04u /* RO: AXI USER that holds lock */ +#define MBOX_CSR_MBOX_CMD 0x08u /* RW: mailbox command ID */ +#define MBOX_CSR_MBOX_DLEN 0x0cu /* RW: data length in bytes */ +#define MBOX_CSR_MBOX_DATAIN 0x10u /* WO: write next data word to FIFO */ +#define MBOX_CSR_MBOX_DATAOUT 0x14u /* RO: read next data word from FIFO */ +#define MBOX_CSR_MBOX_EXECUTE 0x18u /* RW: bit 0 = ring doorbell; write 0 to release */ +#define MBOX_CSR_MBOX_STATUS 0x1cu /* RO: status and FSM state */ +#define MBOX_CSR_MBOX_UNLOCK 0x20u /* RW (uC only): force unlock */ + +/* MBOX_LOCK bit. + * Reading MBOX_LOCK returns 0 if the lock was free (and atomically acquires + * it), or 1 if the lock was already held by another requester. */ +#define MBOX_CSR_MBOX_LOCK_LOCK_MASK 0x01u + +/* MBOX_STATUS bits. + * Source: mbox.rs StatusReadVal::status() → (self.0 >> 0) & 0xf + * mbox.rs StatusReadVal::mbox_fsm_ps() → (self.0 >> 6) & 7 */ +#define MBOX_CSR_MBOX_STATUS_STATUS_MASK 0x0fu /* bits [3:0] */ +#define MBOX_CSR_MBOX_STATUS_MBOX_FSM_PS_MASK 0x1c0u /* bits [8:6] */ +#define MBOX_CSR_MBOX_STATUS_MBOX_FSM_PS_LOW 6u /* shift amount */ + +/* ========================================================================= + * SOC_IFC / Generic-and-Fuse block + * + * Full APB address of a named register: + * EXTERNAL_PERIPH_BASE + CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_BASE_ADDR + * + register_offset + * + * The CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_* constants embed the + * block base so caliptra_model_apb_write_u32(model, EXTERNAL_PERIPH_BASE + + * CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_FOO, val) works directly. + * ========================================================================= */ + +/* Offset of the SOC_IFC block from EXTERNAL_PERIPH_BASE */ +#define CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_BASE_ADDR 0x30000u + +/* Full offsets from EXTERNAL_PERIPH_BASE for key control registers. + * Value = CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_BASE_ADDR + per-register offset. + * Source: soc_ifc.rs — self.ptr.wrapping_add(byte_offset / size_of::()) */ +#define CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_FLOW_STATUS 0x3003cu +#define CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_FUSE_WR_DONE 0x300b0u +#define CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_CPTRA_BOOTFSM_GO 0x300b8u + +/* CPTRA_FLOW_STATUS bit masks. + * Source: soc_ifc.rs CptraFlowStatusReadVal */ +#define GENERIC_AND_FUSE_REG_CPTRA_FLOW_STATUS_IDEVID_CSR_READY_MASK 0x01000000u +#define GENERIC_AND_FUSE_REG_CPTRA_FLOW_STATUS_READY_FOR_MB_PROCESSING_MASK 0x10000000u +#define GENERIC_AND_FUSE_REG_CPTRA_FLOW_STATUS_READY_FOR_RUNTIME_MASK 0x20000000u +#define GENERIC_AND_FUSE_REG_CPTRA_FLOW_STATUS_READY_FOR_FUSES_MASK 0x40000000u + +/* Fuse register offsets (relative to CALIPTRA_TOP_REG_GENERIC_AND_FUSE_REG_BASE_ADDR). + * Used with caliptra_fuse_write() helpers that add EXTERNAL_PERIPH_BASE + block base. + * Source: soc_ifc.rs register block methods. + * Array sizes from libcaliptra/inc/caliptra_types.h */ +#define GENERIC_AND_FUSE_REG_CPTRA_OWNER_PK_HASH_0 0x140u /* [12] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_UDS_SEED_0 0x200u /* [16] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_FIELD_ENTROPY_0 0x240u /* [ 8] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_VENDOR_PK_HASH_0 0x260u /* [12] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_ECC_REVOCATION 0x290u /* [ 1] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_RUNTIME_SVN_0 0x2b8u /* [ 4] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_ANTI_ROLLBACK_DISABLE 0x2c8u /* [ 1] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_IDEVID_CERT_ATTR_0 0x2ccu /* [24] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_IDEVID_MANUF_HSM_ID_0 0x32cu /* [ 4] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_LMS_REVOCATION 0x340u /* [ 1] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_MLDSA_REVOCATION 0x344u /* [ 1] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_SOC_STEPPING_ID 0x348u /* [ 1] u32 */ +#define GENERIC_AND_FUSE_REG_FUSE_PQC_KEY_TYPE 0x38cu /* [ 1] u32 */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index f3fc88c8a16..f63fcab1c72 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -470,6 +470,9 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #if defined(WOLFSSL_MAX3266X) || defined(WOLFSSL_MAX3266X_OLD) #include #endif + #if defined(WOLFSSL_CALIPTRA) + #include + #endif #endif #ifdef _MSC_VER @@ -1074,6 +1077,9 @@ WOLFSSL_TEST_SUBROUTINE int ariagcm_test(MC_ALGID); #if defined(WOLF_CRYPTO_CB) && !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void); #endif +#if defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t caliptra_test(void); +#endif #ifdef WOLFSSL_CERT_PIV WOLFSSL_TEST_SUBROUTINE wc_test_ret_t certpiv_test(void); #endif @@ -3325,6 +3331,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("crypto callback test passed!\n"); #endif +#if defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) + if ( (ret = caliptra_test()) != 0) + TEST_FAIL("caliptra test failed!\n", ret); + else + TEST_PASS("caliptra test passed!\n"); +#endif + #ifdef WOLFSSL_CERT_PIV if ( (ret = certpiv_test()) != 0) TEST_FAIL("cert piv test failed!\n", ret); @@ -75385,6 +75398,1461 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aes_siv_test(void) } #endif /* REALLY_LONG_DRBG_CONTINUOUS_TEST */ +#if defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) +/* + * caliptra_test() — exercise the Caliptra CryptoCb port through the standard + * wolfSSL API. + * + * Requires caliptra_mailbox_exec() at link time (provided by the integrator + * or by audit/caliptra_sim.c for host-side testing). + * + * Tests: + * 0. wc_caliptra_req_chksum direct KAT against the upstream Caliptra + * test vector (api/src/checksum.rs::test_calc_checksum) + * 1. RNG via wc_InitRng_ex / wc_RNG_GenerateBlock / wc_FreeRng + * 2. SHA-384 empty-message KAT (Init → Final; exercises the empty-message + * path where caliptra_hash sends CM_SHA_INIT with zero data then Final) + * 3. SHA-512 empty-message KAT (same path) + * 3a. SHA-384 streaming KAT: Init → Update("abc") → Final + * (exercises caliptra_sha_do_init with data, then straight to Final) + * 3b. SHA-512 streaming KAT: Init → Update("a") → Update("bc") → Final + * (exercises caliptra_sha_do_init on first Update, then CM_SHA_UPDATE + * on the second Update, then Final — the only path through all three + * mailbox commands) + * 3c. SHA-384 zero-length-Update KAT: + * Init → Update("a") → Update(non-NULL, 0) → Update("bc") → Final + * (exercises the untested branch: has_input=true with inSz==0; + * the port sends CM_SHA_UPDATE with input_size=0 — no guard exists + * to skip the mailbox call — so this test reveals whether Caliptra + * firmware accepts a zero-byte update without corrupting context) + * 3d. SHA-384 abort-cleanup: Init → Update → Free WITHOUT Final + * (exercises caliptra_hash_free; asserts sha.devCtx == NULL after Free) + * 4. AES-256-GCM KAT: decrypt vs. NIST GCM Test Case 16 (McGrew-Viega). + * Caliptra generates IVs server-side on AES-GCM encrypt; no encrypt KAT + * is possible through the public API. The decrypt KAT fully exercises + * Caliptra's AES-GCM implementation against an external standard vector. + * 4b. AES-256-GCM round-trip (encrypt-then-decrypt, IV retrieved from hardware) + * 4c. AES-256-GCM authentication failure detection + * 5. HMAC-SHA-384 via wc_caliptra_hmac(), cross-validated against software + * 5b. HMAC-SHA-512 via wc_caliptra_hmac() (when WOLFSSL_SHA512 is defined), + * cross-validated against software + * 6. ECDSA P-384 sign + verify via Caliptra CryptoCb + */ +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t caliptra_test(void) +{ + wc_test_ret_t ret = 0; + + /* SHA-384 empty-message digest (FIPS 180-4 test vector). */ + static const byte sha384_empty_digest[48] = { + 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, + 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, + 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, + 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, + 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, + 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b + }; + + /* SHA-512 empty-message digest (FIPS 180-4 test vector). */ + static const byte sha512_empty_digest[64] = { + 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, + 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, + 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, + 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, + 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, + 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, + 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, + 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e + }; + + /* SHA-384("abc") digest (NIST FIPS 180-4). */ + static const byte sha384_abc_digest[WC_SHA384_DIGEST_SIZE] = { + 0xcb, 0x00, 0x75, 0x3f, 0x45, 0xa3, 0x5e, 0x8b, + 0xb5, 0xa0, 0x3d, 0x69, 0x9a, 0xc6, 0x50, 0x07, + 0x27, 0x2c, 0x32, 0xab, 0x0e, 0xde, 0xd1, 0x63, + 0x1a, 0x8b, 0x60, 0x5a, 0x43, 0xff, 0x5b, 0xed, + 0x80, 0x86, 0x07, 0x2b, 0xa1, 0xe7, 0xcc, 0x23, + 0x58, 0xba, 0xec, 0xa1, 0x34, 0xc8, 0x25, 0xa7 + }; + + /* SHA-512("abc") digest (NIST FIPS 180-4). */ + static const byte sha512_abc_digest[WC_SHA512_DIGEST_SIZE] = { + 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, + 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, + 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, + 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a, + 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, + 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, + 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e, + 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f + }; + + /* 32-byte AES-256 key for AES-GCM round-trip. */ + static const byte aes_key[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + + /* 16-byte test plaintext for AES-GCM round-trip. */ + static const byte plaintext[16] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a + }; + + /* AES-256-GCM KAT vectors: NIST GCM Test Case 16 (AES-256). + * Source: McGrew & Viega, "The GCM Mode of Operation", Table 5, TC-16. + * The same vectors are used by wolfSSL aesgcm_test() in this file. + * Note: Caliptra generates IVs server-side on AES-GCM encrypt; the + * decrypt path accepts a caller-provided IV, making a decrypt-only KAT + * the only way to validate against an external standard vector. */ + static const byte kat_aes_key[32] = { + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08, + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 + }; + static const byte kat_iv[12] = { + 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, + 0xde, 0xca, 0xf8, 0x88 + }; + static const byte kat_aad[20] = { + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xab, 0xad, 0xda, 0xd2 + }; + static const byte kat_pt[60] = { + 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, + 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, + 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, + 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, + 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, + 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, + 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, + 0xba, 0x63, 0x7b, 0x39 + }; + static const byte kat_ct[60] = { + 0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07, + 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d, + 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9, + 0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa, + 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d, + 0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38, + 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a, + 0xbc, 0xc9, 0xf6, 0x62 + }; + static const byte kat_tag[16] = { + 0x76, 0xfc, 0x6e, 0xce, 0x0f, 0x4e, 0x17, 0x68, + 0xcd, 0xdf, 0x88, 0x53, 0xbb, 0x2d, 0x55, 0x1b + }; + + /* 48-byte HMAC key (SHA-384 output size, valid for Caliptra HMAC). */ + static const byte hmac_key[48] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b + }; + + /* 13-byte HMAC message: "Hello, World!" */ + static const byte hmac_msg[13] = { + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, + 0x6f, 0x72, 0x6c, 0x64, 0x21 + }; + + /* ---- register the Caliptra device ---- */ + ret = (wc_test_ret_t)wc_caliptra_init(); + if (ret != 0) { + printf("caliptra_test: wc_caliptra_init failed %d\n", (int)ret); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = (wc_test_ret_t)wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, + wc_caliptra_cb, NULL); + if (ret != 0) { + printf("caliptra_test: wc_CryptoCb_RegisterDevice failed %d\n", + (int)ret); + wc_caliptra_cleanup(); + return WC_TEST_RET_ENC_EC(ret); + } + + /* --------------------------------------------------------------- + * Test 0: wc_caliptra_req_chksum direct KAT + * + * Validate the public checksum utility against a known-good vector + * from upstream Caliptra's own test suite: + * caliptra/api/src/checksum.rs::test_calc_checksum + * calc_checksum(0xe8dc3994, &[0x83, 0xe7, 0x25]) == 0xfffffbe0 + * + * wc_caliptra_req_chksum(cmd_id, req, req_len) takes the WHOLE + * request struct (including the chksum field at req[0..4]) and + * sums req[4..req_len] internally — equivalent to the upstream's + * (cmd, data) signature where 'data' is everything after the + * chksum field. Construct a 7-byte buffer whose first 4 bytes + * are arbitrary placeholders (skipped by the implementation) and + * whose bytes 4..7 are the upstream test vector. + * + * The wolfSSL wrapper applies HTOLE32 to the result; on LE hosts + * this is identity, on BE hosts it byte-swaps. Compare against + * the LE-encoded expected value so the assertion holds on both. + * --------------------------------------------------------------- */ + { + const word32 chksum_cmd = 0xe8dc3994U; + const word32 chksum_expected = 0xfffffbe0U; + byte chksum_buf[7]; + word32 chksum_got; + + XMEMSET(chksum_buf, 0xAA, 4); /* placeholder for chksum field */ + chksum_buf[4] = 0x83; + chksum_buf[5] = 0xe7; + chksum_buf[6] = 0x25; + + chksum_got = wc_caliptra_req_chksum(chksum_cmd, + chksum_buf, sizeof(chksum_buf)); + + /* wc_caliptra_req_chksum applies HTOLE32 so the returned value + * is the chksum word as it would be stored on the wire (LE). + * On LE hosts: chksum_got == chksum_expected directly. + * On BE hosts: chksum_got's bytes in memory are the LE + * sequence; reading it as a host-byte-order word32 yields + * the byte-swapped form. Compare via byte-by-byte LE + * extraction to be endian-agnostic. */ + { + byte got_le[4]; + const byte exp_le[4] = { + (byte)(chksum_expected), + (byte)(chksum_expected >> 8), + (byte)(chksum_expected >> 16), + (byte)(chksum_expected >> 24) + }; + XMEMCPY(got_le, &chksum_got, sizeof(got_le)); + if (XMEMCMP(got_le, exp_le, sizeof(exp_le)) != 0) { + printf("caliptra_test: chksum KAT mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + } + printf("caliptra_test: wc_caliptra_req_chksum KAT passed\n"); + } + + /* --------------------------------------------------------------- + * Test 1: RNG + * --------------------------------------------------------------- */ + { + WC_RNG rng; + byte rng_buf[32]; + byte zero_buf[32]; + + XMEMSET(rng_buf, 0, sizeof(rng_buf)); + XMEMSET(zero_buf, 0, sizeof(zero_buf)); + + ret = (wc_test_ret_t)wc_InitRng_ex(&rng, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: wc_InitRng_ex failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_RNG_GenerateBlock(&rng, rng_buf, + sizeof(rng_buf)); + wc_FreeRng(&rng); + if (ret != 0) { + printf("caliptra_test: wc_RNG_GenerateBlock failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(rng_buf, zero_buf, sizeof(rng_buf)) == 0) { + printf("caliptra_test: RNG output is all zeros\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: RNG passed\n"); + } + + /* --------------------------------------------------------------- + * Test 2: SHA-384 empty-message KAT + * --------------------------------------------------------------- */ + { + wc_Sha384 sha; + byte digest[WC_SHA384_DIGEST_SIZE]; + + XMEMSET(digest, 0, sizeof(digest)); + + ret = (wc_test_ret_t)wc_InitSha384_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: wc_InitSha384_ex failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha384Final(&sha, digest); + wc_Sha384Free(&sha); + if (ret != 0) { + printf("caliptra_test: wc_Sha384Final failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(digest, sha384_empty_digest, + WC_SHA384_DIGEST_SIZE) != 0) { + printf("caliptra_test: SHA-384 KAT mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-384 passed\n"); + } + + /* --------------------------------------------------------------- + * Test 3: SHA-512 empty-message KAT + * --------------------------------------------------------------- */ + { + wc_Sha512 sha; + byte digest[WC_SHA512_DIGEST_SIZE]; + + XMEMSET(digest, 0, sizeof(digest)); + + ret = (wc_test_ret_t)wc_InitSha512_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: wc_InitSha512_ex failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha512Final(&sha, digest); + wc_Sha512Free(&sha); + if (ret != 0) { + printf("caliptra_test: wc_Sha512Final failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(digest, sha512_empty_digest, + WC_SHA512_DIGEST_SIZE) != 0) { + printf("caliptra_test: SHA-512 KAT mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-512 passed\n"); + } + + /* --------------------------------------------------------------- + * Test 3a: SHA-384 streaming KAT — Init → Update("abc") → Final + * + * Exercises caliptra_sha_do_init with a non-empty first chunk + * (CM_SHA_INIT carries 3 bytes), then Final with no remaining data. + * Vector: NIST FIPS 180-4 SHA-384("abc"). + * --------------------------------------------------------------- */ + { + wc_Sha384 sha; + byte digest[WC_SHA384_DIGEST_SIZE]; + + XMEMSET(digest, 0, sizeof(digest)); + + ret = (wc_test_ret_t)wc_InitSha384_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: SHA-384 streaming init failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha384Update(&sha, (const byte*)"abc", 3); + if (ret != 0) { + wc_Sha384Free(&sha); + printf("caliptra_test: SHA-384 streaming update failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha384Final(&sha, digest); + wc_Sha384Free(&sha); + if (ret != 0) { + printf("caliptra_test: SHA-384 streaming final failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(digest, sha384_abc_digest, WC_SHA384_DIGEST_SIZE) != 0) { + printf("caliptra_test: SHA-384 streaming KAT mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-384 streaming (single-update) passed\n"); + } + + /* --------------------------------------------------------------- + * Test 3b: SHA-512 streaming KAT — Init → Update("a") → + * Update("bc") → Final + * + * The first Update routes through caliptra_sha_do_init (which sends + * CM_SHA_INIT carrying 1 byte). The second Update sends CM_SHA_UPDATE + * with the saved context. This is the only path that exercises all + * three SHA mailbox commands in sequence. + * Vector: NIST FIPS 180-4 SHA-512("abc"). + * --------------------------------------------------------------- */ + { + wc_Sha512 sha; + byte digest[WC_SHA512_DIGEST_SIZE]; + + XMEMSET(digest, 0, sizeof(digest)); + + ret = (wc_test_ret_t)wc_InitSha512_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: SHA-512 streaming init failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha512Update(&sha, (const byte*)"a", 1); + if (ret == 0) + ret = (wc_test_ret_t)wc_Sha512Update(&sha, (const byte*)"bc", 2); + if (ret != 0) { + wc_Sha512Free(&sha); + printf("caliptra_test: SHA-512 streaming update failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha512Final(&sha, digest); + wc_Sha512Free(&sha); + if (ret != 0) { + printf("caliptra_test: SHA-512 streaming final failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(digest, sha512_abc_digest, WC_SHA512_DIGEST_SIZE) != 0) { + printf("caliptra_test: SHA-512 streaming KAT mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-512 streaming (two-update) passed\n"); + } + + /* --------------------------------------------------------------- + * Test 3c: SHA-384 zero-length-Update KAT + * + * Why this test exists + * -------------------- + * caliptra_hash() uses `has_input = (info->hash.in != NULL)` as the + * sole discriminator between the Update and Final branches. A caller + * that passes in != NULL with inSz == 0 (a zero-length update, which + * the wc_Sha384Update() API contract must accept) will cause the port + * to enter the Update branch and issue CM_SHA_UPDATE with input_size=0 + * — there is no guard that skips the mailbox call for inSz==0. + * + * This is the only branch of the state machine not covered by Tests + * 3a/3b. Two failure modes it catches: + * + * 1. Firmware rejects zero-byte CM_SHA_UPDATE → port propagates + * WC_HW_E → wc_Sha384Update returns an error that this test + * surfaces. Fix: add an early-return guard for inSz==0. + * + * 2. Firmware accepts zero-byte CM_SHA_UPDATE but corrupts the + * saved SHA context → subsequent Update/Final computes a wrong + * digest → XMEMCMP mismatch. + * + * The expected digest is sha384_abc_digest because + * SHA-384("a" || "" || "bc") == SHA-384("abc") — a zero-byte update + * must not change the accumulated hash state. + * + * Vector: NIST FIPS 180-4 SHA-384("abc"). + * --------------------------------------------------------------- */ + { + wc_Sha384 sha; + byte digest[WC_SHA384_DIGEST_SIZE]; + /* A non-NULL pointer with length 0. The exact address does not + * matter; the port must not dereference it for inSz==0 bytes. + * Using a string literal ensures a valid, non-NULL address + * without allocating extra storage. */ + const byte* empty_ptr = (const byte*)""; + + XMEMSET(digest, 0, sizeof(digest)); + + ret = (wc_test_ret_t)wc_InitSha384_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: SHA-384 zero-update init failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Update 1: 1 real byte — triggers CM_SHA_INIT carrying data. */ + ret = (wc_test_ret_t)wc_Sha384Update(&sha, (const byte*)"a", 1); + /* Update 2: non-NULL pointer, zero bytes — exercises the + * has_input=true, inSz==0 branch → CM_SHA_UPDATE(input_size=0). */ + if (ret == 0) + ret = (wc_test_ret_t)wc_Sha384Update(&sha, empty_ptr, 0); + /* Update 3: 2 real bytes — CM_SHA_UPDATE carrying "bc". */ + if (ret == 0) + ret = (wc_test_ret_t)wc_Sha384Update(&sha, (const byte*)"bc", 2); + if (ret != 0) { + wc_Sha384Free(&sha); + printf("caliptra_test: SHA-384 zero-update update failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha384Final(&sha, digest); + wc_Sha384Free(&sha); + if (ret != 0) { + printf("caliptra_test: SHA-384 zero-update final failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(digest, sha384_abc_digest, WC_SHA384_DIGEST_SIZE) != 0) { + printf("caliptra_test: SHA-384 zero-update KAT mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-384 streaming (zero-length update) passed\n"); + } + + /* --------------------------------------------------------------- + * Test 3d: SHA-384 abort-cleanup path + * Exercises caliptra_hash_free (the WOLF_CRYPTO_CB_FREE handler) + * for the streaming-hash abort sequence: Init -> Update -> Free + * WITHOUT Final. caliptra_sha_do_init allocates a heap-resident + * CaliptraShaCtx and stores it in sha->devCtx; if Final is never + * called, only caliptra_hash_free can release it. Without this + * test, the abort path leaks devCtx silently. + * + * Assertion: after wc_Sha384Free, sha.devCtx must be NULL — that + * is the observable side effect of caliptra_hash_free running to + * completion (see caliptra_port.c caliptra_hash_free: *devctx_ptr + * = NULL on the last line). The "abc" input is enough to force + * an INIT mailbox round-trip so devCtx is non-NULL pre-Free. + * --------------------------------------------------------------- */ + { + wc_Sha384 sha; + + XMEMSET(&sha, 0, sizeof(sha)); + + ret = (wc_test_ret_t)wc_InitSha384_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: SHA-384 abort: wc_InitSha384_ex " + "failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha384Update(&sha, (const byte*)"abc", 3); + if (ret != 0) { + printf("caliptra_test: SHA-384 abort: wc_Sha384Update " + "failed %d\n", (int)ret); + wc_Sha384Free(&sha); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + if (sha.devCtx == NULL) { + printf("caliptra_test: SHA-384 abort: devCtx unexpectedly NULL " + "after Update (no abort path to exercise)\n"); + wc_Sha384Free(&sha); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + + /* Skip Final. Go straight to Free. */ + wc_Sha384Free(&sha); + + if (sha.devCtx != NULL) { + printf("caliptra_test: SHA-384 abort: devCtx not freed by " + "wc_Sha384Free (caliptra_hash_free did not run or " + "did not clear devCtx)\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-384 abort-cleanup passed\n"); + } + +#ifdef WOLFSSL_SHA512 + /* --------------------------------------------------------------- + * Test 3e: SHA-512 abort-cleanup path + * Same shape as Test 3d but for SHA-512. Exercises the + * WC_HASH_TYPE_SHA512 arm of caliptra_hash_free, which is a + * separate switch case from WC_HASH_TYPE_SHA384 (different cast + * target: wc_Sha512* vs wc_Sha384*). + * --------------------------------------------------------------- */ + { + wc_Sha512 sha; + + XMEMSET(&sha, 0, sizeof(sha)); + + ret = (wc_test_ret_t)wc_InitSha512_ex(&sha, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: SHA-512 abort: wc_InitSha512_ex " + "failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_Sha512Update(&sha, (const byte*)"abc", 3); + if (ret != 0) { + printf("caliptra_test: SHA-512 abort: wc_Sha512Update " + "failed %d\n", (int)ret); + wc_Sha512Free(&sha); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + if (sha.devCtx == NULL) { + printf("caliptra_test: SHA-512 abort: devCtx unexpectedly NULL " + "after Update (no abort path to exercise)\n"); + wc_Sha512Free(&sha); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + + /* Skip Final. Go straight to Free. */ + wc_Sha512Free(&sha); + + if (sha.devCtx != NULL) { + printf("caliptra_test: SHA-512 abort: devCtx not freed by " + "wc_Sha512Free (caliptra_hash_free did not run or " + "did not clear devCtx)\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: SHA-512 abort-cleanup passed\n"); + } +#endif /* WOLFSSL_SHA512 */ + + /* --------------------------------------------------------------- + * Test 4: AES-256-GCM KAT (decrypt) + * Decrypt a known ciphertext using a caller-supplied IV against + * NIST GCM Test Case 16 (McGrew-Viega, AES-256). Validates + * Caliptra's AES-GCM implementation against an external vector. + * + * An encrypt KAT is not possible: Caliptra generates IVs server- + * side and provides no API to supply a fixed encrypt nonce. + * --------------------------------------------------------------- */ + { + Aes aes; + CaliptraCmk kat_cmk; + byte recovered[sizeof(kat_pt)]; + + XMEMSET(&kat_cmk, 0, sizeof(kat_cmk)); + XMEMSET(recovered, 0, sizeof(recovered)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(kat_aes_key, + sizeof(kat_aes_key), + CMB_KEY_USAGE_AES, + &kat_cmk); + if (ret != 0) { + printf("caliptra_test: AES-GCM KAT: import key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_AesInit(&aes, HEAP_HINT, WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: AES-GCM KAT: wc_AesInit failed %d\n", + (int)ret); + wc_caliptra_delete_key(&kat_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_AesGcmSetKey(&aes, kat_aes_key, + sizeof(kat_aes_key)); + if (ret != 0) { + printf("caliptra_test: AES-GCM KAT: wc_AesGcmSetKey failed %d\n", + (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&kat_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + aes.devCtx = &kat_cmk; + + ret = (wc_test_ret_t)wc_AesGcmDecrypt(&aes, + recovered, kat_ct, + sizeof(kat_ct), + kat_iv, sizeof(kat_iv), + kat_tag, sizeof(kat_tag), + kat_aad, sizeof(kat_aad)); + wc_AesFree(&aes); + wc_caliptra_delete_key(&kat_cmk); + if (ret != 0) { + printf("caliptra_test: AES-GCM KAT: decrypt failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(recovered, kat_pt, sizeof(kat_pt)) != 0) { + printf("caliptra_test: AES-GCM KAT: plaintext mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: AES-256-GCM KAT (decrypt) passed\n"); + } + + /* --------------------------------------------------------------- + * Test 4b: AES-256-GCM round-trip + * Import a 32-byte AES key, encrypt 16 bytes, retrieve the + * Caliptra-generated IV, then decrypt and verify plaintext. + * --------------------------------------------------------------- */ + { + Aes aes; + CaliptraCmk aes_cmk; + byte ciphertext[16]; + byte recovered[16]; + byte auth_tag[16]; + byte iv[12]; + + XMEMSET(&aes_cmk, 0, sizeof(aes_cmk)); + XMEMSET(ciphertext, 0, sizeof(ciphertext)); + XMEMSET(recovered, 0, sizeof(recovered)); + XMEMSET(auth_tag, 0, sizeof(auth_tag)); + XMEMSET(iv, 0, sizeof(iv)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(aes_key, + sizeof(aes_key), + CMB_KEY_USAGE_AES, + &aes_cmk); + if (ret != 0) { + printf("caliptra_test: import AES key failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_AesInit(&aes, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: wc_AesInit failed %d\n", (int)ret); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_AesGcmSetKey(&aes, aes_key, sizeof(aes_key)); + if (ret != 0) { + printf("caliptra_test: wc_AesGcmSetKey failed %d\n", (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Point devCtx at the imported CMK handle. */ + aes.devCtx = &aes_cmk; + + /* Encrypt: pass the local iv[12] buffer as a placeholder. Caliptra's + * CM_AES_GCM_ENCRYPT_INIT command always generates the IV in firmware + * and never reads one from the request; per caliptra_port.c the + * port silently ignores the caller-supplied iv/ivSz. We still must + * pass a non-NULL buffer with ivSz > 0 here because wolfSSL's core + * wc_AesGcmEncrypt() validates these arguments before the cryptocb + * dispatch fires. Retrieve the Caliptra-generated IV afterward via + * wc_caliptra_aesgcm_get_iv(); it overwrites this same buffer. */ + ret = (wc_test_ret_t)wc_AesGcmEncrypt(&aes, + ciphertext, plaintext, + sizeof(plaintext), + iv, sizeof(iv), + auth_tag, sizeof(auth_tag), + NULL, 0); + if (ret != 0) { + printf("caliptra_test: wc_AesGcmEncrypt failed %d\n", (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Retrieve the Caliptra-generated IV from aes.reg. */ + ret = (wc_test_ret_t)wc_caliptra_aesgcm_get_iv(&aes, iv, sizeof(iv)); + if (ret != 0) { + printf("caliptra_test: wc_caliptra_aesgcm_get_iv failed %d\n", + (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Decrypt with retrieved IV. */ + ret = (wc_test_ret_t)wc_AesGcmDecrypt(&aes, + recovered, ciphertext, + sizeof(ciphertext), + iv, sizeof(iv), + auth_tag, sizeof(auth_tag), + NULL, 0); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + if (ret != 0) { + printf("caliptra_test: wc_AesGcmDecrypt failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(recovered, plaintext, sizeof(plaintext)) != 0) { + printf("caliptra_test: AES-GCM plaintext mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: AES-256-GCM round-trip passed\n"); + } + + /* --------------------------------------------------------------- + * Test 4b': AES-256-GCM round-trip with empty plaintext (sz=0) + * Exercises the caliptra_aesgcm_encrypt/decrypt path that skips the + * Update mailbox call entirely (sz == 0 branch in the port). + * NIST GCM permits zero-length plaintext with optional AAD; the + * round-trip should yield an empty plaintext and a tag that + * authenticates over only the AAD (here, NULL). + * --------------------------------------------------------------- */ + { + Aes aes; + CaliptraCmk aes_cmk; + byte ciphertext[1]; /* unused, but non-NULL placeholder */ + byte recovered[1]; + byte auth_tag[16]; + byte iv[12]; + + XMEMSET(&aes_cmk, 0, sizeof(aes_cmk)); + XMEMSET(ciphertext, 0, sizeof(ciphertext)); + XMEMSET(recovered, 0, sizeof(recovered)); + XMEMSET(auth_tag, 0, sizeof(auth_tag)); + XMEMSET(iv, 0, sizeof(iv)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(aes_key, + sizeof(aes_key), + CMB_KEY_USAGE_AES, + &aes_cmk); + if (ret != 0) { + printf("caliptra_test: empty-message import AES key failed " + "%d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_AesInit(&aes, HEAP_HINT, + WOLF_CALIPTRA_DEVID); + if (ret == 0) + ret = (wc_test_ret_t)wc_AesGcmSetKey(&aes, aes_key, + sizeof(aes_key)); + if (ret != 0) { + printf("caliptra_test: empty-message AES init/setkey failed " + "%d\n", (int)ret); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + aes.devCtx = &aes_cmk; + + /* Encrypt with sz=0; the port skips the Update mailbox call. */ + ret = (wc_test_ret_t)wc_AesGcmEncrypt(&aes, + ciphertext, ciphertext, 0, + iv, sizeof(iv), + auth_tag, sizeof(auth_tag), + NULL, 0); + if (ret != 0) { + printf("caliptra_test: empty-message wc_AesGcmEncrypt " + "failed %d\n", (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_caliptra_aesgcm_get_iv(&aes, iv, sizeof(iv)); + if (ret != 0) { + printf("caliptra_test: empty-message get_iv failed %d\n", + (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Decrypt sz=0; port also skips the Update mailbox call. */ + ret = (wc_test_ret_t)wc_AesGcmDecrypt(&aes, + recovered, ciphertext, 0, + iv, sizeof(iv), + auth_tag, sizeof(auth_tag), + NULL, 0); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + if (ret != 0) { + printf("caliptra_test: empty-message wc_AesGcmDecrypt " + "failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + printf("caliptra_test: AES-256-GCM empty-message round-trip " + "passed\n"); + } + + /* --------------------------------------------------------------- + * Test 4c: AES-256-GCM authentication failure + * Encrypt with a valid key, tamper one byte of the authentication + * tag, then decrypt and verify AES_GCM_AUTH_E is returned. + * --------------------------------------------------------------- */ + { + Aes aes; + CaliptraCmk aes_cmk; + byte ciphertext[sizeof(plaintext)]; + byte discarded[sizeof(plaintext)]; + byte auth_tag[16]; + byte bad_tag[16]; + byte iv[12]; + int dec_ret; + + XMEMSET(&aes_cmk, 0, sizeof(aes_cmk)); + XMEMSET(ciphertext, 0, sizeof(ciphertext)); + XMEMSET(discarded, 0, sizeof(discarded)); + XMEMSET(auth_tag, 0, sizeof(auth_tag)); + XMEMSET(iv, 0, sizeof(iv)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(aes_key, + sizeof(aes_key), + CMB_KEY_USAGE_AES, + &aes_cmk); + if (ret != 0) { + printf("caliptra_test: auth-failure test: import AES key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_AesInit(&aes, HEAP_HINT, WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: auth-failure test: wc_AesInit failed %d\n", + (int)ret); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + aes.devCtx = &aes_cmk; + + /* See Test 4b for why we pass iv as a placeholder: Caliptra's + * CM_AES_GCM_ENCRYPT_INIT always generates the IV in firmware, but + * wolfSSL's core argument validation requires a non-NULL iv buffer + * with ivSz > 0 before the cryptocb dispatch fires. The actual IV + * is retrieved via wc_caliptra_aesgcm_get_iv() below. */ + ret = (wc_test_ret_t)wc_AesGcmEncrypt(&aes, + ciphertext, plaintext, + sizeof(plaintext), + iv, sizeof(iv), + auth_tag, sizeof(auth_tag), + NULL, 0); + if (ret == 0) + ret = (wc_test_ret_t)wc_caliptra_aesgcm_get_iv(&aes, iv, sizeof(iv)); + + if (ret != 0) { + printf("caliptra_test: auth-failure test: encrypt/get_iv failed %d\n", + (int)ret); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Tamper the authentication tag: flip the first byte */ + XMEMCPY(bad_tag, auth_tag, sizeof(auth_tag)); + bad_tag[0] ^= 0xFF; + + /* Decrypt with tampered tag — must return AES_GCM_AUTH_E */ + dec_ret = wc_AesGcmDecrypt(&aes, + discarded, ciphertext, + sizeof(ciphertext), + iv, sizeof(iv), + bad_tag, sizeof(bad_tag), + NULL, 0); + wc_AesFree(&aes); + wc_caliptra_delete_key(&aes_cmk); + + if (dec_ret != AES_GCM_AUTH_E) { + printf("caliptra_test: AES-GCM auth failure: expected AES_GCM_AUTH_E " + "(%d) got %d\n", AES_GCM_AUTH_E, dec_ret); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: AES-GCM authentication failure detection passed\n"); + } + + /* --------------------------------------------------------------- + * Test 5: HMAC-SHA-384 via wc_caliptra_hmac() + * Caliptra HMAC is single-shot; use wc_caliptra_hmac() directly. + * Cross-validate the output against a wolfSSL software reference. + * --------------------------------------------------------------- */ + { + CaliptraCmk hmac_cmk; + byte caliptra_mac[WC_SHA384_DIGEST_SIZE]; + word32 caliptra_mac_len; + byte sw_mac[WC_SHA384_DIGEST_SIZE]; + Hmac sw_hmac; + + XMEMSET(&hmac_cmk, 0, sizeof(hmac_cmk)); + XMEMSET(caliptra_mac, 0, sizeof(caliptra_mac)); + XMEMSET(sw_mac, 0, sizeof(sw_mac)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(hmac_key, + sizeof(hmac_key), + CMB_KEY_USAGE_HMAC, + &hmac_cmk); + if (ret != 0) { + printf("caliptra_test: import HMAC key failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + caliptra_mac_len = sizeof(caliptra_mac); + ret = (wc_test_ret_t)wc_caliptra_hmac(&hmac_cmk, WC_SHA384, + hmac_msg, sizeof(hmac_msg), + caliptra_mac, &caliptra_mac_len); + wc_caliptra_delete_key(&hmac_cmk); + if (ret != 0) { + printf("caliptra_test: wc_caliptra_hmac failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + /* Software HMAC reference (INVALID_DEVID bypasses CryptoCb) */ + ret = (wc_test_ret_t)wc_HmacInit(&sw_hmac, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + printf("caliptra_test: wc_HmacInit failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + ret = (wc_test_ret_t)wc_HmacSetKey(&sw_hmac, WC_SHA384, + hmac_key, sizeof(hmac_key)); + if (ret == 0) + ret = (wc_test_ret_t)wc_HmacUpdate(&sw_hmac, hmac_msg, + sizeof(hmac_msg)); + if (ret == 0) + ret = (wc_test_ret_t)wc_HmacFinal(&sw_hmac, sw_mac); + wc_HmacFree(&sw_hmac); + if (ret != 0) { + printf("caliptra_test: software HMAC failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(caliptra_mac, sw_mac, WC_SHA384_DIGEST_SIZE) != 0) { + printf("caliptra_test: HMAC-SHA-384 Caliptra/software mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: HMAC-SHA-384 passed\n"); + } + +#ifdef WOLFSSL_SHA512 + /* --------------------------------------------------------------- + * Test 5b: HMAC-SHA-512 via wc_caliptra_hmac() + * Same shape as Test 5 but exercises the WC_SHA512 branch in + * wc_caliptra_hmac (CMB_SHA_ALG_SHA512 mailbox alg). Cross-validated + * against a software wc_HmacSetKey/Update/Final with INVALID_DEVID. + * --------------------------------------------------------------- */ + { + CaliptraCmk hmac_cmk; + byte caliptra_mac[WC_SHA512_DIGEST_SIZE]; + word32 caliptra_mac_len; + byte sw_mac[WC_SHA512_DIGEST_SIZE]; + Hmac sw_hmac; + + XMEMSET(&hmac_cmk, 0, sizeof(hmac_cmk)); + XMEMSET(caliptra_mac, 0, sizeof(caliptra_mac)); + XMEMSET(sw_mac, 0, sizeof(sw_mac)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(hmac_key, + sizeof(hmac_key), + CMB_KEY_USAGE_HMAC, + &hmac_cmk); + if (ret != 0) { + printf("caliptra_test: HMAC-SHA-512 import key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + caliptra_mac_len = sizeof(caliptra_mac); + ret = (wc_test_ret_t)wc_caliptra_hmac(&hmac_cmk, WC_SHA512, + hmac_msg, sizeof(hmac_msg), + caliptra_mac, &caliptra_mac_len); + wc_caliptra_delete_key(&hmac_cmk); + if (ret != 0) { + printf("caliptra_test: HMAC-SHA-512 wc_caliptra_hmac failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_HmacInit(&sw_hmac, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + printf("caliptra_test: HMAC-SHA-512 wc_HmacInit failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + ret = (wc_test_ret_t)wc_HmacSetKey(&sw_hmac, WC_SHA512, + hmac_key, sizeof(hmac_key)); + if (ret == 0) + ret = (wc_test_ret_t)wc_HmacUpdate(&sw_hmac, hmac_msg, + sizeof(hmac_msg)); + if (ret == 0) + ret = (wc_test_ret_t)wc_HmacFinal(&sw_hmac, sw_mac); + wc_HmacFree(&sw_hmac); + if (ret != 0) { + printf("caliptra_test: HMAC-SHA-512 software ref failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + if (XMEMCMP(caliptra_mac, sw_mac, WC_SHA512_DIGEST_SIZE) != 0) { + printf("caliptra_test: HMAC-SHA-512 Caliptra/software mismatch\n"); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: HMAC-SHA-512 passed\n"); + } +#endif /* WOLFSSL_SHA512 */ + + /* --------------------------------------------------------------- + * Test 6: ECDSA P-384 sign + verify via Caliptra CryptoCb + * Generate a P-384 key pair, import private key as CMK, sign a + * 48-byte hash, import the public key via direct mailbox call + * (CM_IMPORT with 96-byte Qx||Qy input), then verify. + * --------------------------------------------------------------- */ + { + WC_RNG ecdsa_rng; + ecc_key raw_key; /* software key for key generation */ + ecc_key sign_key; /* Caliptra signing key */ + ecc_key ver_key; /* Caliptra verify key */ + CaliptraCmk sign_cmk; + CaliptraCmk verify_cmk; + byte hash[48]; + byte sig[160]; + word32 sig_len = sizeof(sig); + byte priv_bytes[48]; + word32 priv_len = 48; + byte pub_x[48], pub_y[48]; + word32 pub_xlen = 48, pub_ylen = 48; + int verify_res = 0; + int rng_init = 0; + int raw_init = 0; + byte pub_key[96]; /* Qx || Qy for Caliptra ECDSA verify import */ + int saved_devId; + + XMEMSET(hash, 0xAB, sizeof(hash)); + XMEMSET(sig, 0, sizeof(sig)); + XMEMSET(pub_key, 0, sizeof(pub_key)); + XMEMSET(&sign_cmk, 0, sizeof(sign_cmk)); + XMEMSET(&verify_cmk, 0, sizeof(verify_cmk)); + + ret = (wc_test_ret_t)wc_InitRng_ex(&ecdsa_rng, HEAP_HINT, + INVALID_DEVID); + if (ret != 0) { + printf("caliptra_test: ECDSA wc_InitRng failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + rng_init = 1; + + /* Generate a P-384 key pair in software to obtain key material */ + wc_ecc_init_ex(&raw_key, HEAP_HINT, INVALID_DEVID); + raw_init = 1; + ret = (wc_test_ret_t)wc_ecc_make_key_ex(&ecdsa_rng, 48, &raw_key, + ECC_SECP384R1); + if (ret != 0) { + printf("caliptra_test: ECDSA wc_ecc_make_key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + + /* Export private scalar */ + ret = (wc_test_ret_t)wc_ecc_export_private_only(&raw_key, + priv_bytes, + &priv_len); + if (ret != 0) { + printf("caliptra_test: ECDSA export private failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + + /* Export public coordinates */ + ret = (wc_test_ret_t)wc_ecc_export_public_raw(&raw_key, + pub_x, &pub_xlen, + pub_y, &pub_ylen); + if (ret != 0) { + printf("caliptra_test: ECDSA export public failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + + /* Import private key into Caliptra as an ECDSA CMK */ + ret = (wc_test_ret_t)wc_caliptra_import_key(priv_bytes, priv_len, + CMB_KEY_USAGE_ECDSA, + &sign_cmk); + if (ret != 0) { + printf("caliptra_test: ECDSA import private key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + + /* Sign: use a Caliptra-backed ecc_key. + * Import the actual key material so caliptra_ecdsa_sign is invoked. */ + wc_ecc_init_ex(&sign_key, HEAP_HINT, WOLF_CALIPTRA_DEVID); + { + int saved_sign_devId = sign_key.devId; + sign_key.devId = INVALID_DEVID; + ret = (wc_test_ret_t)wc_ecc_import_unsigned(&sign_key, pub_x, pub_y, + priv_bytes, ECC_SECP384R1); + sign_key.devId = saved_sign_devId; + } + if (ret == 0) { + sign_key.devCtx = &sign_cmk; + ret = (wc_test_ret_t)wc_ecc_sign_hash(hash, sizeof(hash), + sig, &sig_len, + &ecdsa_rng, &sign_key); + } + wc_ecc_free(&sign_key); + if (ret != 0) { + printf("caliptra_test: ECDSA sign failed %d\n", (int)ret); + wc_caliptra_delete_key(&sign_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + wc_caliptra_delete_key(&sign_cmk); + + /* Import public key (Qx||Qy = 96 bytes) for Caliptra ECDSA verify. */ + XMEMCPY(pub_key, pub_x, 48); + XMEMCPY(pub_key + 48, pub_y, 48); + ret = (wc_test_ret_t)wc_caliptra_import_key(pub_key, 96, + CMB_KEY_USAGE_ECDSA, + &verify_cmk); + if (ret != 0) { + printf("caliptra_test: ECDSA import public key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + + /* Verify: load public coordinates so wc_ecc_verify_hash + * can route through CryptoCb; set devCtx to the public CMK. */ + wc_ecc_init_ex(&ver_key, HEAP_HINT, WOLF_CALIPTRA_DEVID); + saved_devId = ver_key.devId; + ver_key.devId = INVALID_DEVID; + ret = (wc_test_ret_t)wc_ecc_import_unsigned(&ver_key, pub_x, pub_y, + NULL, ECC_SECP384R1); + ver_key.devId = saved_devId; + if (ret == 0) { + ver_key.devCtx = &verify_cmk; + ver_key.devId = WOLF_CALIPTRA_DEVID; + ret = (wc_test_ret_t)wc_ecc_verify_hash(sig, sig_len, + hash, sizeof(hash), + &verify_res, &ver_key); + } + wc_ecc_free(&ver_key); + wc_caliptra_delete_key(&verify_cmk); + if (ret != 0) { + printf("caliptra_test: ECDSA verify call failed %d\n", (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto ecdsa_cleanup; + } + if (verify_res != 1) { + printf("caliptra_test: ECDSA verify result %d (expected 1)\n", + verify_res); + ret = WC_TEST_RET_ENC_NC; + goto ecdsa_cleanup; + } + printf("caliptra_test: ECDSA P-384 sign+verify passed\n"); + + /* Negative test: corrupt the r value in the signature; verify MUST + * return ret=0 with verify_res=0 (not a system error). + * + * Corrupt a byte in the r value, not in the DER framing: corrupting + * framing bytes can cause wc_ecc_sig_to_rs to return a parse error + * before Caliptra is reached, making the test pass for the wrong + * reason. Instead: decode to raw r/s, flip a bit in r, re-encode. */ + { + ecc_key bad_ver_key; + CaliptraCmk bad_verify_cmk; + byte bad_sig[160]; + word32 bad_sig_len; + byte bad_r[48], bad_s[48]; + word32 bad_rLen = 48, bad_sLen = 48; + int bad_res = 1; /* init to 1; must become 0 */ + int bad_ret; + int bad_saved_devId; + + XMEMSET(&bad_verify_cmk, 0, sizeof(bad_verify_cmk)); + XMEMSET(bad_r, 0, sizeof(bad_r)); + XMEMSET(bad_s, 0, sizeof(bad_s)); + + /* Decode the good DER signature to raw r and s. */ + bad_ret = wc_ecc_sig_to_rs(sig, sig_len, + bad_r, &bad_rLen, bad_s, &bad_sLen); + if (bad_ret != 0) { + printf("caliptra_test: ECDSA negative-test sig decode " + "failed %d\n", bad_ret); + ret = WC_TEST_RET_ENC_NC; + goto ecdsa_cleanup; + } + /* Left-pad r to 48 bytes if the leading zero was stripped. */ + if (bad_rLen < 48) { + byte tmp[48]; + XMEMSET(tmp, 0, 48); + XMEMCPY(tmp + (48 - bad_rLen), bad_r, bad_rLen); + XMEMCPY(bad_r, tmp, 48); + } + /* Corrupt the high byte of r and re-encode to valid DER. */ + bad_r[0] ^= 0xFF; + bad_sig_len = (word32)sizeof(bad_sig); + bad_ret = wc_ecc_rs_raw_to_sig(bad_r, 48, bad_s, 48, + bad_sig, &bad_sig_len); + if (bad_ret != 0) { + printf("caliptra_test: ECDSA negative-test sig re-encode " + "failed %d\n", bad_ret); + ret = WC_TEST_RET_ENC_NC; + goto ecdsa_cleanup; + } + + /* Re-import the same public key for the negative verify test. */ + bad_ret = wc_caliptra_import_key(pub_key, 96, + CMB_KEY_USAGE_ECDSA, + &bad_verify_cmk); + if (bad_ret != 0) { + printf("caliptra_test: ECDSA negative-test import failed %d\n", + bad_ret); + ret = WC_TEST_RET_ENC_NC; + goto ecdsa_cleanup; + } + + wc_ecc_init_ex(&bad_ver_key, HEAP_HINT, WOLF_CALIPTRA_DEVID); + bad_saved_devId = bad_ver_key.devId; + bad_ver_key.devId = INVALID_DEVID; + bad_ret = wc_ecc_import_unsigned(&bad_ver_key, pub_x, pub_y, + NULL, ECC_SECP384R1); + bad_ver_key.devId = bad_saved_devId; + if (bad_ret == 0) { + bad_ver_key.devCtx = &bad_verify_cmk; + bad_ver_key.devId = WOLF_CALIPTRA_DEVID; + bad_ret = wc_ecc_verify_hash(bad_sig, bad_sig_len, + hash, sizeof(hash), + &bad_res, &bad_ver_key); + } + wc_ecc_free(&bad_ver_key); + wc_caliptra_delete_key(&bad_verify_cmk); + + /* Correct result: function returns 0, bad_res == 0 */ + if (bad_ret != 0 || bad_res != 0) { + printf("caliptra_test: ECDSA negative verify: expected " + "ret=0 res=0, got ret=%d res=%d\n", + bad_ret, bad_res); + ret = WC_TEST_RET_ENC_NC; + goto ecdsa_cleanup; + } + printf("caliptra_test: ECDSA P-384 negative verify passed\n"); + } + +ecdsa_cleanup: + /* Zero the stack-resident P-384 private scalar. priv_bytes holds + * the exported private key for the duration of the test and would + * otherwise remain on the stack until overwritten by later + * activity, regardless of whether we got here via success or + * an error goto. */ + ForceZero(priv_bytes, sizeof(priv_bytes)); + if (raw_init) { wc_ecc_free(&raw_key); raw_init = 0; } + if (rng_init) { wc_FreeRng(&ecdsa_rng); rng_init = 0; } + if (ret != 0) + goto caliptra_done; + } + + /* --------------------------------------------------------------- + * Test 7: HMAC streaming via wc_HmacSetKey/Update with a CMK in + * hmac->devCtx must be rejected with WC_HW_E. + * + * Caliptra HMAC is single-shot only: the mailbox command requires + * all message data in one transfer. wolfSSL's streaming Hmac API + * (wc_HmacInit / SetKey / Update / Final) cannot be plumbed through. + * caliptra_port.c's caliptra_hmac() returns WC_HW_E (NOT + * CRYPTOCB_UNAVAILABLE) when hmac->devCtx is non-NULL, which prevents + * wolfSSL from falling through to software HMAC over an unauthorized + * key. This test pins that contract: a wc_HmacSetKey or wc_HmacUpdate + * call against a Caliptra-routed Hmac MUST return WC_HW_E. + * --------------------------------------------------------------- */ + { + CaliptraCmk hmac_cmk; + Hmac h; + int hmac_init_ok = 0; + int streaming_ret; + + XMEMSET(&hmac_cmk, 0, sizeof(hmac_cmk)); + + ret = (wc_test_ret_t)wc_caliptra_import_key(hmac_key, + sizeof(hmac_key), + CMB_KEY_USAGE_HMAC, + &hmac_cmk); + if (ret != 0) { + printf("caliptra_test: HMAC streaming import key failed %d\n", + (int)ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + + ret = (wc_test_ret_t)wc_HmacInit(&h, HEAP_HINT, WOLF_CALIPTRA_DEVID); + if (ret != 0) { + printf("caliptra_test: HMAC streaming wc_HmacInit failed %d\n", + (int)ret); + wc_caliptra_delete_key(&hmac_cmk); + ret = WC_TEST_RET_ENC_EC(ret); + goto caliptra_done; + } + hmac_init_ok = 1; + h.devCtx = &hmac_cmk; + + /* wc_HmacSetKey dispatches through CryptoCb's HMAC callback (with + * digest == NULL, in == NULL, inSz == 0). caliptra_hmac() sees + * h->devCtx != NULL and returns WC_HW_E unconditionally. If + * wolfSSL ever changes its dispatch order to call Update first, + * Update also routes through caliptra_hmac() with the same outcome, + * so accept WC_HW_E from either call. */ + streaming_ret = wc_HmacSetKey(&h, WC_SHA384, + hmac_key, sizeof(hmac_key)); + if (streaming_ret == 0) { + /* SetKey did not fail; Update must. */ + streaming_ret = wc_HmacUpdate(&h, hmac_msg, sizeof(hmac_msg)); + } + + if (hmac_init_ok) + wc_HmacFree(&h); + wc_caliptra_delete_key(&hmac_cmk); + + if (streaming_ret != WC_HW_E) { + printf("caliptra_test: HMAC streaming expected WC_HW_E (%d) but " + "got %d\n", WC_HW_E, streaming_ret); + ret = WC_TEST_RET_ENC_NC; + goto caliptra_done; + } + printf("caliptra_test: HMAC streaming rejection passed\n"); + } + +caliptra_done: + wc_CryptoCb_UnRegisterDevice(WOLF_CALIPTRA_DEVID); + wc_caliptra_cleanup(); + return ret; +} +#endif /* defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) */ + #undef ERROR_OUT static WC_MAYBE_UNUSED const int fiducial4 = WC_TEST_RET_LN; diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 9635e1a6cfd..fde39946b70 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -111,6 +111,7 @@ noinst_HEADERS+= \ wolfssl/wolfcrypt/port/st/stm32.h \ wolfssl/wolfcrypt/port/st/stsafe.h \ wolfssl/wolfcrypt/port/tropicsquare/tropic01.h \ + wolfssl/wolfcrypt/port/caliptra/caliptra_port.h \ wolfssl/wolfcrypt/port/Espressif/esp-sdk-lib.h \ wolfssl/wolfcrypt/port/Espressif/esp32-crypt.h \ wolfssl/wolfcrypt/port/Espressif/esp_crt_bundle.h \ @@ -236,6 +237,10 @@ if BUILD_TROPIC01 nobase_include_HEADERS+= wolfssl/wolfcrypt/port/tropicsquare/tropic01.h endif +if BUILD_CALIPTRA +nobase_include_HEADERS+= wolfssl/wolfcrypt/port/caliptra/caliptra_port.h +endif + if BUILD_MAXQ10XX nobase_include_HEADERS+= wolfssl/wolfcrypt/port/maxim/maxq10xx.h endif diff --git a/wolfssl/wolfcrypt/port/caliptra/caliptra_port.h b/wolfssl/wolfcrypt/port/caliptra/caliptra_port.h new file mode 100644 index 00000000000..7d3ced9646d --- /dev/null +++ b/wolfssl/wolfcrypt/port/caliptra/caliptra_port.h @@ -0,0 +1,743 @@ +/* caliptra_port.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* + * wolfSSL CryptoCb port for Caliptra hardware security module. + * + * Struct layouts match the Caliptra mailbox.rs Rust API exactly: + * - CMK_SIZE_BYTES = 128 (NOT 64 — the opaque Cmk wrapper is 128 bytes) + * - No 'cmd' field in request structs — command ID sent via mailbox cmd register + * - CmShaFinalReq includes input_size + input[] (last data chunk) + * - AES-GCM encrypt: IV is generated server-side, returned in Init response + * - AES-GCM decrypt: IV is provided by caller in Init request as iv[3] (u32 words) + * - ECDH context is 76 bytes (CMB_ECDH_ENCRYPTED_CTX_SIZE) + */ + +#ifndef WOLFSSL_PORT_CALIPTRA_H +#define WOLFSSL_PORT_CALIPTRA_H + +#include + +#if defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) + +#include +#include + +/* ========================================================================= + * Device ID + * ========================================================================= */ + +/* CryptoCb device ID for the Caliptra port. + * + * wolfSSL device IDs are int; the only reserved value is INVALID_DEVID (-2). + * In-tree ports use small positive integers (1, 2, 3...) for named hardware; + * this port uses 0x43414C50 ("CALP" in ASCII, 1128352848 as a signed int) to + * avoid guessing a small integer that might collide with another port. + * + * Integrators who need a different value can override before including this + * header: + * #define WOLF_CALIPTRA_DEVID + * #include */ +#ifndef WOLF_CALIPTRA_DEVID +#define WOLF_CALIPTRA_DEVID 0x43414C50 +#endif + +/* ========================================================================= + * CMK size + * ========================================================================= */ + +/* Caliptra opaque key handle size in bytes. + * Source: mailbox.rs: pub const CMK_SIZE_BYTES: usize = 128; + * NOTE: older C references incorrectly used 64 — the correct value is 128. */ +#define CMK_SIZE_BYTES 128 + +/* ========================================================================= + * Mailbox command IDs + * These are sent via the mailbox command register, NOT embedded in structs. + * ========================================================================= */ + +#define CM_IMPORT 0x434D494D /* "CMim" */ +#define CM_DELETE 0x434D444C /* "CMdl" */ +#define CM_SHA_INIT 0x434D5349 /* "CMSI" */ +#define CM_SHA_UPDATE 0x434D5355 /* "CMSU" */ +#define CM_SHA_FINAL 0x434D5346 /* "CMSF" */ +#define CM_RANDOM_GENERATE 0x434D5247 /* "CMRG" */ +#define CM_AES_GCM_ENCRYPT_INIT 0x434D4749 /* "CMGI" */ +#define CM_AES_GCM_ENCRYPT_UPDATE 0x434D4755 /* "CMGU" */ +#define CM_AES_GCM_ENCRYPT_FINAL 0x434D4746 /* "CMGF" */ +#define CM_AES_GCM_DECRYPT_INIT 0x434D4449 /* "CMDI" */ +#define CM_AES_GCM_DECRYPT_UPDATE 0x434D4455 /* "CMDU" */ +#define CM_AES_GCM_DECRYPT_FINAL 0x434D4446 /* "CMDF" */ +#define CM_ECDSA_PUBLIC_KEY 0x434D4550 /* "CMEP" */ +#define CM_ECDSA_SIGN 0x434D4553 /* "CMES" */ +#define CM_ECDSA_VERIFY 0x434D4556 /* "CMEV" */ +#define CM_ECDH_GENERATE 0x434D4547 /* "CMEG" */ +#define CM_ECDH_FINISH 0x434D4546 /* "CMEF" */ +#define CM_HMAC 0x434D484D /* "CMHM" */ +#define CM_HKDF_EXTRACT 0x434D4B54 /* "CMKT" */ +#define CM_HKDF_EXPAND 0x434D4B50 /* "CMKP" */ + +/* ========================================================================= + * Mailbox protocol constants + * ========================================================================= */ + +/* Maximum data payload per mailbox message (MAX_CMB_DATA_SIZE in Rust). */ +#define CMB_MAX_DATA_SIZE 4096 + +/* SHA opaque context blob size returned by Init/Update, consumed by Update/Final. */ +#define CMB_SHA_CONTEXT_SIZE 200 + +/* AES-GCM opaque encrypted context size (CMB_AES_GCM_ENCRYPTED_CONTEXT_SIZE). */ +#define CMB_AES_GCM_ENCRYPTED_CTX_SIZE 128 + +/* ECDH opaque encrypted ephemeral keypair context size. + * 76 bytes. Source: mailbox.rs CMB_ECDH_ENCRYPTED_CTX_SIZE. */ +#define CMB_ECDH_ENCRYPTED_CTX_SIZE 76 + +/* Maximum HMAC output size (SHA-512 produces 64 bytes). */ +#define CMB_HMAC_MAX_SIZE 64 + +/* ECDH public key exchange data: Qx || Qy for P-384 = 48 + 48 = 96 bytes. */ +#define CMB_ECDH_EXCHANGE_DATA_SIZE 96 + +/* Maximum AES-GCM output size = plaintext + 16-byte authentication tag. */ +#define CMB_MAX_AES_GCM_OUTPUT_SIZE 4112 + + +/* Hash algorithm identifiers used in hash_algorithm fields. + * Values match CmHashAlgorithm in Caliptra mailbox.rs: + * Sha384 = 1, Sha512 = 2. SHA-256 is not supported by the firmware. */ +#define CMB_SHA_ALG_SHA384 1 +#define CMB_SHA_ALG_SHA512 2 + +/* Key usage identifiers matching CmKeyUsage in Caliptra mailbox.rs: + * Hmac = 1, Aes = 2, Ecdsa = 3, Mldsa = 4, Mlkem = 5. + * NOTE: 0 (Reserved) is invalid and will be rejected by the firmware. + * Firmware key size constraints: + * Aes: 32 bytes; Hmac: 48 or 64 bytes; Ecdsa: 48 bytes; Mlkem: 64 bytes */ +#define CMB_KEY_USAGE_HMAC 1 +#define CMB_KEY_USAGE_AES 2 +#define CMB_KEY_USAGE_ECDSA 3 +#define CMB_KEY_USAGE_MLDSA 4 +#define CMB_KEY_USAGE_MLKEM 5 + +/* ========================================================================= + * Header types + * These map directly to MailboxReqHeader / MailboxRespHeader in mailbox.rs. + * ========================================================================= */ + +/* Request header: checksum only. Command ID is in the mailbox command register. */ +typedef struct { + word32 chksum; +} CmReqHeader; + +/* Fixed-size response header. */ +typedef struct { + word32 chksum; + word32 fips_status; +} CmRespHeader; + +/* Variable-size response header; data_len carries the useful byte count. */ +typedef struct { + word32 chksum; + word32 fips_status; + word32 data_len; +} CmRespHeaderVarSize; + +/* ========================================================================= + * Opaque key handle (Cmk) + * ========================================================================= */ + +/* 128-byte opaque Caliptra key reference (returned by CM_IMPORT, etc.). + * Treat as an opaque blob; do not interpret the internal layout. */ +typedef struct { + byte bytes[CMK_SIZE_BYTES]; +} CaliptraCmk; + +/* ========================================================================= + * SHA streaming structures + * ========================================================================= + * + * Three-phase protocol: Init -> zero or more Updates -> Final. + * The 'context' blob is an opaque server-side cookie passed back + * on every subsequent call. No 'cmd' field — see command register. + * + * Size note: every request struct below embeds at least one + * CMB_MAX_DATA_SIZE (4096) byte buffer (input / aad / plaintext / + * ciphertext), so sizeof(CmShaUpdateReq) and friends are slightly larger + * than 4 KB. By default the port heap-allocates these via XMALLOC, so + * stack usage is bounded. When WOLFSSL_CALIPTRA_STATIC_BUFFERS is + * defined the port instead declares these as stack-local automatics + * (search for "WOLFSSL_CALIPTRA_STATIC_BUFFERS" in caliptra_port.c); + * in that mode callers must ensure adequate stack — see the + * WOLFSSL_CALIPTRA_STATIC_BUFFERS section in README.md for the + * per-operation stack-frame estimates and recommended thread stack + * sizes. + */ + +/* CM_SHA_INIT request. */ +typedef struct { + CmReqHeader hdr; + word32 hash_algorithm; /* CMB_SHA_ALG_SHA384/512 */ + word32 input_size; + byte input[CMB_MAX_DATA_SIZE]; +} CmShaInitReq; + +/* CM_SHA_INIT response (also reused for CM_SHA_UPDATE response). */ +typedef struct { + CmRespHeader hdr; + byte context[CMB_SHA_CONTEXT_SIZE]; +} CmShaInitResp; + +/* Alias: CM_SHA_UPDATE response reuses the same layout as Init response. */ +typedef CmShaInitResp CmShaUpdateResp; + +/* CM_SHA_UPDATE request. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_SHA_CONTEXT_SIZE]; + word32 input_size; + byte input[CMB_MAX_DATA_SIZE]; +} CmShaUpdateReq; + +/* CM_SHA_FINAL request. + * Final also carries the last data chunk (input_size + input[]). + * The C reference that omits these fields is incorrect. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_SHA_CONTEXT_SIZE]; + word32 input_size; /* byte count of last input chunk */ + byte input[CMB_MAX_DATA_SIZE]; +} CmShaFinalReq; + +/* CM_SHA_FINAL response. + * hdr.data_len carries the digest length; field name is 'hash' not 'digest'. */ +typedef struct { + CmRespHeaderVarSize hdr; /* hdr.data_len = digest length in bytes */ + byte hash[64]; /* SHA-512 max; smaller digests use prefix */ +} CmShaFinalResp; + +/* ========================================================================= + * AES-GCM streaming structures + * ========================================================================= + * + * Encrypt: IV is generated server-side and returned in EncryptInitResp. + * Decrypt: caller provides IV as three little-endian u32 words in + * DecryptInitReq. See the per-struct comments below (and caliptra_port.c + * line ~924) for the byte-order details: raw memcpy of 12 LE IV bytes into + * a word32[3] field produces the correct LE u32 values on LE hosts, which + * is what the firmware reads. + * Both paths use an opaque encrypted context blob between calls. + */ + +/* CM_AES_GCM_ENCRYPT_INIT request. + * IV is NOT provided by caller — Caliptra generates it and returns it. */ +typedef struct { + CmReqHeader hdr; + word32 flags; /* opaque flags field (present in Rust API) */ + CaliptraCmk cmk; /* 128-byte key vault reference */ + word32 aad_size; + byte aad[CMB_MAX_DATA_SIZE]; +} CmAesGcmEncryptInitReq; + +/* CM_AES_GCM_ENCRYPT_INIT response. + * iv[3]: server-generated IV as three u32 words (12 bytes total). + * + * Endianness: Caliptra is RISC-V little-endian; iv is typed [u32; 3] in the + * Rust API and serialised via zerocopy #[repr(C)] with no byte-swapping. + * On a LE host, LE u32 words in memory are identical to the underlying byte + * sequence, so XMEMCPY(dst, iv, 12) copies the 12 IV bytes in the correct + * order regardless of whether dst is typed as byte[] or word32[]. + * Source: caliptra/api/src/mailbox.rs CmAesGcmEncryptInitResp ([u32; 3]), + * caliptra/runtime/src/cryptographic_mailbox.rs (resp.iv assignment) */ +typedef struct { + CmRespHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 iv[3]; /* Caliptra-generated IV; caller must save */ +} CmAesGcmEncryptInitResp; + +/* CM_AES_GCM_DECRYPT_INIT request. + * Caller provides the IV used during encryption (must match). + * + * Endianness: iv[3] is typed [u32; 3] in the Rust API (same as EncryptInitResp). + * The firmware converts it to LEArray4x3 via a direct bitwise copy with no + * byte-swapping (cryptographic_mailbox.rs: `let cmd_iv: LEArray4x3 = cmd.iv.into()`). + * Copying the 12 IV bytes received from wc_caliptra_aesgcm_get_iv back into + * this field with XMEMCPY(iv, caller_iv, 12) is byte-order-correct. + * Source: caliptra/api/src/mailbox.rs CmAesGcmDecryptInitReq ([u32; 3]), + * caliptra/runtime/src/cryptographic_mailbox.rs (cmd.iv.into()) */ +typedef struct { + CmReqHeader hdr; + word32 flags; + CaliptraCmk cmk; + word32 iv[3]; /* caller-supplied IV for decryption */ + word32 aad_size; + byte aad[CMB_MAX_DATA_SIZE]; +} CmAesGcmDecryptInitReq; + +/* CM_AES_GCM_DECRYPT_INIT response. + * firmware also returns iv[3] (12 bytes) after the context, making + * the layout identical to CmAesGcmEncryptInitResp (148 bytes total). */ +typedef struct { + CmRespHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 iv[3]; /* echo of caller-supplied IV */ +} CmAesGcmDecryptInitResp; + +/* CM_AES_GCM_ENCRYPT_UPDATE request. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 plaintext_size; + byte plaintext[CMB_MAX_DATA_SIZE]; +} CmAesGcmEncryptUpdateReq; + +/* CM_AES_GCM_ENCRYPT_UPDATE response. + * ciphertext buffer is CMB_MAX_AES_GCM_OUTPUT_SIZE (4112) to accommodate tag. */ +typedef struct { + CmRespHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 ciphertext_size; + byte ciphertext[CMB_MAX_AES_GCM_OUTPUT_SIZE]; +} CmAesGcmEncryptUpdateResp; + +/* CM_AES_GCM_DECRYPT_UPDATE request. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 ciphertext_size; + byte ciphertext[CMB_MAX_DATA_SIZE]; +} CmAesGcmDecryptUpdateReq; + +/* CM_AES_GCM_DECRYPT_UPDATE response. */ +typedef struct { + CmRespHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 plaintext_size; + byte plaintext[CMB_MAX_DATA_SIZE]; +} CmAesGcmDecryptUpdateResp; + +/* CM_AES_GCM_ENCRYPT_FINAL request. + * Carries the last plaintext chunk; the authentication tag is returned in the + * Final response, not sent by the caller. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 plaintext_size; /* byte count of last plaintext chunk */ + byte plaintext[CMB_MAX_DATA_SIZE]; +} CmAesGcmEncryptFinalReq; + +/* CM_AES_GCM_ENCRYPT_FINAL response. + * tag[4]: 16-byte authentication tag as four u32 words. + * Also returns the final ciphertext block. */ +typedef struct { + CmRespHeader hdr; + word32 tag[4]; /* 16-byte auth tag as u32[4] */ + word32 ciphertext_size; + byte ciphertext[CMB_MAX_AES_GCM_OUTPUT_SIZE]; +} CmAesGcmEncryptFinalResp; + +/* CM_AES_GCM_DECRYPT_FINAL request. + * Caller provides the tag plus the last ciphertext chunk. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_AES_GCM_ENCRYPTED_CTX_SIZE]; + word32 tag_len; + word32 tag[4]; /* 16-byte auth tag as u32[4] */ + word32 ciphertext_size; /* last ciphertext chunk */ + byte ciphertext[CMB_MAX_DATA_SIZE]; +} CmAesGcmDecryptFinalReq; + +/* CM_AES_GCM_DECRYPT_FINAL response. + * firmware returns tag_verified + plaintext_size after the header. + * tag_verified: 1 = authentication OK, 0 = authentication failure. + * Source: cryptographic_mailbox.rs: resp.tag_verified = (computed==expected) as u32 */ +typedef struct { + CmRespHeader hdr; + word32 tag_verified; /* 1 = auth OK, 0 = auth failure */ + word32 plaintext_size; /* informational: total plaintext bytes + * returned across Update calls; no + * plaintext data follows this field. + * All decrypted bytes are returned in + * the preceding Update response(s). */ +} CmAesGcmDecryptFinalResp; + +/* ========================================================================= + * ECDSA (P-384) structures + * ========================================================================= + * + * All key material is referenced via CaliptraCmk handles (128-byte opaque). + * Public key components are always 48 bytes (P-384 field size). + * Signature r and s are always 48 bytes each (implicit, no size fields). + * + * Note: CmEcdsaVerifyReq uses a Cmk for the public key. The port must + * import raw public key material via CM_IMPORT before verifying. + */ + +/* CM_ECDSA_SIGN request. */ +typedef struct { + CmReqHeader hdr; + CaliptraCmk cmk; /* private key vault reference */ + word32 message_size; + byte message[CMB_MAX_DATA_SIZE]; +} CmEcdsaSignReq; + +/* CM_ECDSA_SIGN response. + * signature_r and signature_s are each exactly 48 bytes (P-384). */ +typedef struct { + CmRespHeader hdr; + byte signature_r[48]; + byte signature_s[48]; +} CmEcdsaSignResp; + +/* CM_ECDSA_VERIFY request. */ +typedef struct { + CmReqHeader hdr; + CaliptraCmk cmk; /* public key vault reference */ + byte signature_r[48]; + byte signature_s[48]; + word32 message_size; + byte message[CMB_MAX_DATA_SIZE]; +} CmEcdsaVerifyReq; + +/* CM_ECDSA_VERIFY response. + * Verify result is encoded in hdr.fips_status; no separate verify_result field. */ +typedef struct { + CmRespHeader hdr; +} CmEcdsaVerifyResp; + +/* CM_ECDSA_PUBLIC_KEY request — retrieve public key for a private key Cmk. */ +typedef struct { + CmReqHeader hdr; + CaliptraCmk cmk; +} CmEcdsaPublicKeyReq; + +/* CM_ECDSA_PUBLIC_KEY response. + * public_key_x and public_key_y are each exactly 48 bytes (P-384). */ +typedef struct { + CmRespHeader hdr; + byte public_key_x[48]; + byte public_key_y[48]; +} CmEcdsaPublicKeyResp; + +/* ========================================================================= + * ECDH structures + * ========================================================================= + * + * Protocol: Generate -> Finish. + * Generate: Caliptra creates a fresh ephemeral P-384 keypair; returns the + * opaque context (76 bytes) and the public exchange data (Qx||Qy, 96 bytes). + * Finish: caller provides peer's exchange data; Caliptra returns derived key + * as a 128-byte Cmk. + */ + +/* CM_ECDH_GENERATE request. + * No fields — Caliptra generates a fresh ephemeral keypair internally. */ +typedef struct { + CmReqHeader hdr; +} CmEcdhGenerateReq; + +/* CM_ECDH_GENERATE response. + * context: 76-byte encrypted ephemeral private key. + * exchange_data: Qx || Qy = 96 bytes (two 48-byte P-384 coordinates). */ +typedef struct { + CmRespHeader hdr; + byte context[CMB_ECDH_ENCRYPTED_CTX_SIZE]; /* 76 bytes */ + byte exchange_data[CMB_ECDH_EXCHANGE_DATA_SIZE]; /* 96 bytes */ +} CmEcdhGenerateResp; + +/* CM_ECDH_FINISH request. */ +typedef struct { + CmReqHeader hdr; + byte context[CMB_ECDH_ENCRYPTED_CTX_SIZE]; + word32 key_usage; /* extra field present in Rust API */ + byte incoming_exchange_data[CMB_ECDH_EXCHANGE_DATA_SIZE]; /* peer Qx||Qy */ +} CmEcdhFinishReq; + +/* CM_ECDH_FINISH response — derived key returned as 128-byte Cmk. */ +typedef struct { + CmRespHeader hdr; + CaliptraCmk output; +} CmEcdhFinishResp; + +/* ========================================================================= + * HMAC structure + * ========================================================================= */ + +/* CM_HMAC request. */ +typedef struct { + CmReqHeader hdr; + CaliptraCmk cmk; + word32 hash_algorithm; /* CMB_SHA_ALG_SHA384/512 */ + word32 data_size; + byte data[CMB_MAX_DATA_SIZE]; +} CmHmacReq; + +/* CM_HMAC response. + * hdr.data_len = MAC byte count; field name is 'mac' not 'hmac'. */ +typedef struct { + CmRespHeaderVarSize hdr; + byte mac[CMB_HMAC_MAX_SIZE]; +} CmHmacResp; + +/* ========================================================================= + * HKDF structures + * ========================================================================= */ + +/* CM_HKDF_EXTRACT request. + * Both salt and IKM are Cmk references (128 bytes each). */ +typedef struct { + CmReqHeader hdr; + word32 hash_algorithm; + CaliptraCmk salt; /* salt key vault reference */ + CaliptraCmk ikm; /* input keying material Cmk reference */ +} CmHkdfExtractReq; + +/* CM_HKDF_EXTRACT response — pseudorandom key returned as Cmk. */ +typedef struct { + CmRespHeader hdr; + CaliptraCmk prk; +} CmHkdfExtractResp; + +/* CM_HKDF_EXPAND request. */ +typedef struct { + CmReqHeader hdr; + CaliptraCmk prk; /* pseudorandom key Cmk reference */ + word32 hash_algorithm; + word32 key_usage; + word32 key_size; /* desired output length in bytes */ + word32 info_size; + byte info[CMB_MAX_DATA_SIZE]; +} CmHkdfExpandReq; + +/* CM_HKDF_EXPAND response — output key material returned as Cmk. */ +typedef struct { + CmRespHeader hdr; + CaliptraCmk okm; +} CmHkdfExpandResp; + +/* ========================================================================= + * RNG / Key management structures + * ========================================================================= */ + +/* CM_RANDOM_GENERATE request. */ +typedef struct { + CmReqHeader hdr; + word32 size; /* number of random bytes to generate */ +} CmRandomGenerateReq; + +/* CM_RANDOM_GENERATE response. + * hdr.data_len = actual byte count returned. */ +typedef struct { + CmRespHeaderVarSize hdr; + byte data[CMB_MAX_DATA_SIZE]; +} CmRandomGenerateResp; + +/* CM_IMPORT request — import raw key material into Caliptra key vault. + * input[] is variable-length; send the trimmed wire length via actual_len. + * Maximum key sizes: AES-256: 32 B; ECDSA private: 48 B; HMAC: 48/64 B; + * ECDSA public (Qx||Qy): 96 B; MLKEM: 64 B. */ +typedef struct { + CmReqHeader hdr; + word32 key_usage; + word32 input_size; /* byte count of raw key material */ + byte input[CMB_MAX_DATA_SIZE]; /* raw key material; send trimmed length */ +} CmImportReq; + +/* CM_IMPORT response — opaque 128-byte key reference for future operations. */ +typedef struct { + CmRespHeader hdr; + CaliptraCmk cmk; +} CmImportResp; + +/* CM_DELETE request. */ +typedef struct { + CmReqHeader hdr; + CaliptraCmk cmk; +} CmDeleteReq; + +/* CM_DELETE response. */ +typedef struct { + CmRespHeader hdr; +} CmDeleteResp; + +/* ========================================================================= + * Per-object SHA streaming state + * ========================================================================= + * + * Stored in the hash object's devCtx (or a side table keyed on the hash + * object pointer). The 'context' blob is the opaque cookie returned by + * CM_SHA_INIT and CM_SHA_UPDATE and consumed by the next call. + * + * IMPORTANT: This context is freed by caliptra_hash_free() which is only + * compiled when WOLF_CRYPTO_CB_FREE is defined. Without WOLF_CRYPTO_CB_FREE, + * calling wc_Sha384Free() or wc_Sha512Free() before the corresponding Final + * will leak this allocation. Define WOLF_CRYPTO_CB_FREE in your build to + * enable context cleanup on hash object free. + */ +typedef struct { + byte context[CMB_SHA_CONTEXT_SIZE]; /* opaque context cookie from Caliptra */ +} CaliptraShaCtx; + +/* ========================================================================= + * Transport hook + * ========================================================================= + * + * The integrator must provide this function. It marshals the request + * struct to the Caliptra mailbox (writing cmd_id to the command register, + * req bytes to the data FIFO, ringing the doorbell, then reading the + * response), and returns 0 on success or a negative wolfSSL error code. + * + * Special convention for CM_ECDSA_VERIFY: + * Real Caliptra hardware does not write a response when ECDSA verification + * fails; instead it sets mailbox status to CmdFailure and writes + * CaliptraError::RUNTIME_MAILBOX_SIGNATURE_MISMATCH to cptra_fw_error_non_fatal. + * The transport MUST detect this condition and return SIG_VERIFY_E (not a + * generic negative error code) so the port can translate it to the wolfSSL + * CryptoCb convention of (ret=0, *res=0) for a cryptographically invalid + * signature. Returning any other non-zero code for this condition will cause + * the caller to receive a system-error rather than a verify-failed result. + * Transports that write a response with fips_status != 0 for invalid + * signatures (as caliptra_sim.c does) may return 0; fips_status is + * checked as a fallback. + */ +extern int caliptra_mailbox_exec(word32 cmd_id, + const void* req, word32 req_len, + void* resp, word32 resp_len); + +/* ========================================================================= + * Public API + * ========================================================================= */ + +/* Compute the Caliptra mailbox request checksum for a prepared request struct. + * + * The firmware verifies: sum(cmd_id bytes LE) + sum(req[4..req_len]) + chksum == 0 + * This function returns the chksum value that satisfies that equation. + * + * Integrators who call caliptra_mailbox_exec() directly (e.g., for operations + * not covered by the CryptoCb layer) must compute and store this checksum into + * req->hdr.chksum AFTER populating all request fields and BEFORE calling + * caliptra_mailbox_exec(). Pass the actual wire length (trimmed, not the full + * struct sizeof) as req_len. */ +WOLFSSL_API word32 wc_caliptra_req_chksum(word32 cmd_id, + const void* req, + word32 req_len); + +/* Perform any platform-level initialization required before using Caliptra. + * This is a hook for platform-specific transport initialization, such as + * opening a device file descriptor to the Caliptra driver, mapping MMIO + * regions, or verifying the mailbox is reachable before first use. If the + * platform requires no such setup, it returns 0 immediately. + * This function does NOT register the device with the CryptoCb framework. + * The application must call wc_CryptoCb_RegisterDevice() separately: + * wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, wc_caliptra_cb, NULL); + * Returns 0 on success, negative wolfSSL error code on failure. */ +WOLFSSL_API int wc_caliptra_init(void); + +/* Symmetric counterpart to wc_caliptra_init; releases any resources acquired + * during init (e.g., close fd, unmap MMIO regions). On platforms with no + * platform-level init, this is a no-op and returns 0. */ +WOLFSSL_API int wc_caliptra_cleanup(void); + +/* CryptoCb callback — register with: + * wc_CryptoCb_RegisterDevice(WOLF_CALIPTRA_DEVID, wc_caliptra_cb, NULL); + * then set aes->devId / hmac->devId / ecc->devId = WOLF_CALIPTRA_DEVID to + * route those operations through Caliptra. + * + * IMPORTANT — AES-GCM encrypt IV: + * Caliptra generates the nonce server-side. The caller-supplied iv/ivSz + * passed to wc_AesGcmEncrypt() are silently ignored (but wc_AesGcmEncrypt + * requires ivSz > 0, so pass a placeholder, e.g. a 12-byte zero buffer). + * After a successful return call wc_caliptra_aesgcm_get_iv() to retrieve + * the actual 12-byte firmware-generated IV before passing it to + * wc_AesGcmDecrypt(). The IV must be transmitted alongside the ciphertext. + * + * IMPORTANT — HMAC: + * Caliptra HMAC is single-shot only. wc_HmacUpdate/Final with a Caliptra + * key always returns WC_HW_E. Use wc_caliptra_hmac() directly instead. + * + * Input size limit: + * AES-GCM and HMAC inputs are limited to CMB_MAX_DATA_SIZE (4096) bytes + * per call. Larger inputs return BUFFER_E. Chunked streaming is not yet + * implemented; this limit comes from the Caliptra mailbox protocol. */ +WOLFSSL_API int wc_caliptra_cb(int devId, wc_CryptoInfo* info, void* ctx); + +/* Import raw key bytes into the Caliptra key vault. + * key_data: pointer to raw key material. + * key_len: byte count of key_data. Maximum CMB_MAX_DATA_SIZE bytes. + * Typical sizes: AES-256: 32 B; ECDSA private: 48 B; + * HMAC: 48 or 64 B; ECDSA public (Qx||Qy): 96 B. + * key_usage: CMB_KEY_USAGE_* constant matching the intended algorithm. + * out_cmk: on success, receives the 128-byte opaque key handle. */ +WOLFSSL_API int wc_caliptra_import_key(const byte* key_data, + word32 key_len, + word32 key_usage, + CaliptraCmk* out_cmk); + +/* Delete a previously imported key from the Caliptra key vault. */ +WOLFSSL_API int wc_caliptra_delete_key(const CaliptraCmk* cmk); + +/* Retrieve the IV that Caliptra generated during the most recent + * wc_AesGcmEncrypt() call on this Aes object. + * + * Call wc_AesGcmEncrypt() with a placeholder iv (ivSz > 0 required by + * wolfSSL, but the value is ignored by Caliptra). Then call this function + * to retrieve the actual 12-byte IV before passing it to wc_AesGcmDecrypt(). + * + * iv_out must point to a buffer of at least 12 bytes; iv_len must be >= 12. + * Returns 0 on success, BAD_FUNC_ARG on bad args. + * + * Storage: the IV is held in aes->reg, wolfSSL's per-object IV register. + * The CryptoCb AES-GCM encrypt callback has no IV output parameter, so + * aes->reg is the correct place to preserve the Caliptra-generated IV for + * retrieval after wc_AesGcmEncrypt() returns. A compile-time assertion in + * caliptra_port.c verifies the field is wide enough to hold 12 bytes. */ +WOLFSSL_API int wc_caliptra_aesgcm_get_iv(const Aes* aes, + byte* iv_out, + word32 iv_len); + +/* Perform a single-shot HMAC using a Caliptra key vault handle. + * + * Caliptra HMAC is inherently single-shot; it cannot be used via the + * wolfSSL streaming Hmac API (wc_HmacUpdate/Final). Use this function + * directly when a Caliptra CMK is the HMAC key. + * + * cmk: Key handle returned by wc_caliptra_import_key() with + * key_usage = CMB_KEY_USAGE_HMAC. + * hash_type: WC_SHA384 or WC_SHA512. + * msg: Input message; may be NULL when msg_len == 0. + * msg_len: Byte count of msg; must be <= CMB_MAX_DATA_SIZE (4096). + * mac_out: Caller-supplied output buffer; must be >= digest size. + * mac_len: On entry: mac_out buffer capacity. + * On success: updated to the actual MAC byte count. + * On failure: mac_out is zeroed; *mac_len is unchanged. + * + * Returns 0 on success, negative wolfSSL error code on failure. */ +WOLFSSL_API int wc_caliptra_hmac(const CaliptraCmk* cmk, + int hash_type, + const byte* msg, + word32 msg_len, + byte* mac_out, + word32* mac_len); + +#endif /* defined(WOLFSSL_CALIPTRA) && defined(WOLF_CRYPTO_CB) */ + +#endif /* WOLFSSL_PORT_CALIPTRA_H */