diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..c2e0234 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,12 @@ +# CI Workflows + +| Workflow | File | Description | +|----------|------|-------------| +| Build and Test | `build-test.yml` | Matrix build across OS (ubuntu-latest, 22.04) and configure options (debug, nuvoton, dynamic-mem). Runs unit tests for each combination. | +| Multiple Compilers | `multi-compiler.yml` | Builds and tests with GCC 11-13 and Clang 14-17 using `-Wall -Wextra -Werror`. | +| Compiler Warnings | `compiler-warnings.yml` | GCC strict warnings (`-Wpedantic -Wconversion -Wshadow -Werror`) and Clang `-Werror` build. | +| Static Analysis | `static-analysis.yml` | Runs cppcheck (style, performance, portability) and Clang Static Analyzer (scan-build). | +| Memory Check | `memory-check.yml` | Valgrind leak check with `--leak-check=full` for both static and dynamic memory modes. | +| CodeQL Security | `codeql.yml` | GitHub CodeQL security-and-quality analysis. Runs on PRs and weekly (Monday 6 AM UTC). | +| Codespell | `codespell.yml` | Spell-checks source files. | +| SPDM Emulator Test | `spdm-emu-test.yml` | End-to-end integration test against the DMTF libspdm emulator via wolfTPM. Runs 6 tests: session establishment, signed/unsigned measurements, challenge authentication, heartbeat, and key update. Dependencies (wolfSSL, spdm-emu, wolfTPM) are cached and refreshed every ~15 days. | diff --git a/.github/workflows/multi-compiler.yml b/.github/workflows/multi-compiler.yml new file mode 100644 index 0000000..d0afc6f --- /dev/null +++ b/.github/workflows/multi-compiler.yml @@ -0,0 +1,67 @@ +name: Multiple Compilers + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + compiler_test: + name: ${{ matrix.cc }} + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - cc: gcc-11 + - cc: gcc-12 + - cc: gcc-13 + - cc: clang-14 + - cc: clang-15 + - cc: clang-17 + + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and tools + run: | + sudo apt-get update + sudo apt-get install -y ${{ matrix.cc }} autoconf automake libtool + + - name: Cache wolfSSL + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: ~/wolfssl-install + key: wolfssl-ubuntu-latest-v1 + + - name: Build wolfSSL + if: steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf \ + --enable-ecccustcurves --enable-keygen \ + --prefix=$HOME/wolfssl-install + make -j$(nproc) + make install + + - name: Build wolfSPDM with ${{ matrix.cc }} + run: | + ./autogen.sh + CC=${{ matrix.cc }} ./configure --with-wolfssl=$HOME/wolfssl-install \ + --enable-nuvoton + make -j$(nproc) CFLAGS="-Wall -Wextra -Werror" + + - name: Run unit tests + run: make check + env: + LD_LIBRARY_PATH: ${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml new file mode 100644 index 0000000..fce5dd9 --- /dev/null +++ b/.github/workflows/spdm-emu-test.yml @@ -0,0 +1,112 @@ +name: SPDM Emulator Integration Test + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + spdm-emu-test: + name: SPDM emulator integration test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool cmake libmbedtls-dev + + # Cache period rotates every ~15 days so dependencies stay fresh + - name: Compute cache period + id: cache-period + run: | + echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT + + # --- Build wolfSSL (cached) --- + - name: Cache wolfSSL + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: ~/wolfssl-install + key: wolfssl-${{ steps.cache-period.outputs.biweekly }} + + - name: Build wolfSSL + if: steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-wolftpm --enable-all \ + --prefix=$HOME/wolfssl-install + make -j$(nproc) + make install + + # --- Build wolfSPDM (always, this is what we're testing) --- + - name: Build and install wolfSPDM + run: | + ./autogen.sh + ./configure --with-wolfssl=$HOME/wolfssl-install \ + --prefix=$HOME/wolfspdm-install + make -j$(nproc) + make install + + # --- Build spdm-emu (cached) --- + - name: Cache spdm-emu + id: cache-spdm-emu + uses: actions/cache@v4 + with: + path: ~/spdm-emu + key: spdm-emu-${{ steps.cache-period.outputs.biweekly }} + + - name: Build spdm-emu + if: steps.cache-spdm-emu.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 --recursive https://github.com/DMTF/spdm-emu.git + cd spdm-emu + mkdir build && cd build + cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .. + make copy_sample_key && make -j$(nproc) + + # --- Build wolfTPM (cached, dynamically links wolfspdm at runtime) --- + - name: Cache wolfTPM + id: cache-wolftpm + uses: actions/cache@v4 + with: + path: ~/wolfTPM + key: wolftpm-${{ steps.cache-period.outputs.biweekly }} + + - name: Build wolfTPM + if: steps.cache-wolftpm.outputs.cache-hit != 'true' + run: | + cd ~ + # TODO: Switch to wolfSSL/wolfTPM once PR #453 is merged + git clone --depth 1 -b add-wolfspdm-backend \ + https://github.com/aidangarske/wolfTPM.git wolfTPM + cd wolfTPM + ./autogen.sh + ./configure --enable-spdm --enable-swtpm \ + --with-wolfspdm=$HOME/wolfspdm-install \ + --with-wolfcrypt=$HOME/wolfssl-install + make -j$(nproc) + + # --- Run integration tests --- + - name: Run SPDM emulator tests + run: | + cd ~/wolfTPM + export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib + export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin + ./examples/spdm/spdm_test.sh --emu + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: spdm-emu-test-logs + path: | + config.log + ~/wolfTPM/config.log diff --git a/README.md b/README.md index d508ab9..af7f956 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,14 @@ Lightweight SPDM 1.2+ requester-only stack implementation using wolfSSL/wolfCryp - SPDM 1.2 requester implementation - Algorithm Set B (FIPS 140-3 Level 3): ECDSA/ECDHE P-384, SHA-384, AES-256-GCM, HKDF-SHA384 -- **Zero-malloc by default** — fully static memory, ideal for constrained/embedded environments +- **Zero-malloc by default** — fully static memory (~32 KB context), ideal for constrained/embedded environments - Optional `--enable-dynamic-mem` for heap-allocated contexts (useful for small-stack platforms) +- Session establishment with full key exchange and encrypted messaging +- Device attestation via signed/unsigned measurements (GET_MEASUREMENTS) +- Sessionless attestation via CHALLENGE/CHALLENGE_AUTH with signature verification +- Certificate chain validation against trusted root CAs +- Session keep-alive via HEARTBEAT/HEARTBEAT_ACK +- Session key rotation via KEY_UPDATE/KEY_UPDATE_ACK (DSP0277) - Hardware SPDM via wolfTPM + Nuvoton TPM - Full transcript tracking for TH1/TH2 computation - Compatible with DMTF spdm-emu for interoperability testing @@ -53,7 +59,7 @@ make ### Memory Modes **Static (default):** Zero heap allocation. The caller provides a buffer -(`WOLFSPDM_CTX_STATIC_SIZE` bytes, ~22 KB) and wolfSPDM operates entirely +(`WOLFSPDM_CTX_STATIC_SIZE` bytes, ~32 KB) and wolfSPDM operates entirely within it. This is ideal for embedded and constrained environments where malloc is unavailable or undesirable. @@ -68,7 +74,7 @@ wolfSPDM_Free(ctx); ``` **Dynamic (`--enable-dynamic-mem`):** Context is heap-allocated via -`wolfSPDM_New()`. Useful on platforms with small stacks where a ~22 KB +`wolfSPDM_New()`. Useful on platforms with small stacks where a ~32 KB local variable is impractical. ```c @@ -98,17 +104,15 @@ cd wolfTPM ./configure --enable-spdm --enable-swtpm --with-wolfspdm=path/to/wolfspdm make -# Terminal 1: Start responder with Algorithm Set B -cd spdm-emu -./bin/spdm_responder_emu --ver 1.2 \ - --hash SHA_384 --asym ECDSA_P384 \ - --dhe SECP_384_R1 --aead AES_256_GCM - -# Terminal 2: Run wolfTPM example +# Run emulator tests (starts/stops emulator automatically) cd wolfTPM -./examples/spdm/spdm_demo --emu +./examples/spdm/spdm_test.sh --emu ``` +The test script automatically finds `spdm_responder_emu` in `../spdm-emu/build/bin/`, +starts it for each test, and runs session establishment, signed measurements, +unsigned measurements, challenge authentication, heartbeat, and key update. + ## Testing with Nuvoton NPCT75x ```bash @@ -122,8 +126,8 @@ cd wolfTPM ./configure --enable-spdm --enable-nuvoton --with-wolfspdm=path/to/wolfspdm make -# Run test suite -./examples/spdm/spdm_test.sh +# Run Nuvoton test suite +./examples/spdm/spdm_test.sh --nuvoton ``` ## API Reference @@ -143,6 +147,15 @@ make | `wolfSPDM_EncryptMessage()` | Encrypt outgoing message | | `wolfSPDM_DecryptMessage()` | Decrypt incoming message | | `wolfSPDM_SecuredExchange()` | Combined send/receive | +| `wolfSPDM_SetTrustedCAs()` | Load trusted root CA certificates for chain validation | +| `wolfSPDM_GetMeasurements()` | Retrieve device measurements with optional signature verification | +| `wolfSPDM_GetMeasurementCount()` | Get number of measurement blocks retrieved | +| `wolfSPDM_GetMeasurementBlock()` | Access individual measurement block data | +| `wolfSPDM_Challenge()` | Sessionless device attestation via CHALLENGE/CHALLENGE_AUTH | +| `wolfSPDM_Heartbeat()` | Session keep-alive (HEARTBEAT/HEARTBEAT_ACK) | +| `wolfSPDM_KeyUpdate()` | Rotate session encryption keys (KEY_UPDATE/KEY_UPDATE_ACK) | +| `wolfSPDM_SendData()` | Send application data over established session | +| `wolfSPDM_ReceiveData()` | Receive application data over established session | ## License diff --git a/src/spdm_context.c b/src/spdm_context.c index 68c0f42..f9b498d 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -25,9 +25,7 @@ #include #include -/* ========================================================================== - * Context Management - * ========================================================================== */ +/* --- Context Management --- */ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) { @@ -102,6 +100,19 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) wc_ecc_free(&ctx->ephemeralKey); } + /* Free responder public key (used for measurement/challenge verification) */ + if (ctx->hasResponderPubKey) { + wc_ecc_free(&ctx->responderPubKey); + } + +#ifndef NO_WOLFSPDM_CHALLENGE + /* Free M1/M2 challenge hash if still initialized */ + if (ctx->m1m2HashInit) { + wc_Sha384Free(&ctx->m1m2Hash); + ctx->m1m2HashInit = 0; + } +#endif + /* Zero entire struct (covers all sensitive key material) */ wc_ForceZero(ctx, sizeof(WOLFSPDM_CTX)); @@ -131,9 +142,7 @@ int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size) return wolfSPDM_Init(ctx); } -/* ========================================================================== - * Configuration - * ========================================================================== */ +/* --- Configuration --- */ int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx) { @@ -203,6 +212,24 @@ int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, } #endif /* WOLFSPDM_NUVOTON */ +int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, + word32 derCertsSz) +{ + if (ctx == NULL || derCerts == NULL || derCertsSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (derCertsSz > WOLFSPDM_MAX_CERT_CHAIN) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(ctx->trustedCAs, derCerts, derCertsSz); + ctx->trustedCAsSz = derCertsSz; + ctx->hasTrustedCAs = 1; + + return WOLFSPDM_SUCCESS; +} + void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) { if (ctx != NULL) { @@ -240,9 +267,7 @@ WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) return ctx->mode; } -/* ========================================================================== - * Session Status - * ========================================================================== */ +/* --- Session Status --- */ int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) { @@ -286,9 +311,7 @@ word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx) } #endif -/* ========================================================================== - * Session Establishment - Connect (Full Handshake) - * ========================================================================== */ +/* --- Session Establishment - Connect (Full Handshake) --- */ /* Standard SPDM 1.2 connection flow (for libspdm emulator, etc.) */ static int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) @@ -299,61 +322,30 @@ static int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) ctx->state = WOLFSPDM_STATE_INIT; wolfSPDM_TranscriptReset(ctx); - /* Step 1: GET_VERSION / VERSION */ - wolfSPDM_DebugPrint(ctx, "Step 1: GET_VERSION\n"); - rc = wolfSPDM_GetVersion(ctx); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } - - /* Step 2: GET_CAPABILITIES / CAPABILITIES */ - wolfSPDM_DebugPrint(ctx, "Step 2: GET_CAPABILITIES\n"); - rc = wolfSPDM_GetCapabilities(ctx); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } - - /* Step 3: NEGOTIATE_ALGORITHMS / ALGORITHMS */ - wolfSPDM_DebugPrint(ctx, "Step 3: NEGOTIATE_ALGORITHMS\n"); - rc = wolfSPDM_NegotiateAlgorithms(ctx); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } - - /* Step 4: GET_DIGESTS / DIGESTS */ - wolfSPDM_DebugPrint(ctx, "Step 4: GET_DIGESTS\n"); - rc = wolfSPDM_GetDigests(ctx); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } + SPDM_CONNECT_STEP(ctx, "Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + SPDM_CONNECT_STEP(ctx, "Step 2: GET_CAPABILITIES\n", + wolfSPDM_GetCapabilities(ctx)); + SPDM_CONNECT_STEP(ctx, "Step 3: NEGOTIATE_ALGORITHMS\n", + wolfSPDM_NegotiateAlgorithms(ctx)); + SPDM_CONNECT_STEP(ctx, "Step 4: GET_DIGESTS\n", + wolfSPDM_GetDigests(ctx)); + SPDM_CONNECT_STEP(ctx, "Step 5: GET_CERTIFICATE\n", + wolfSPDM_GetCertificate(ctx, 0)); - /* Step 5: GET_CERTIFICATE / CERTIFICATE */ - wolfSPDM_DebugPrint(ctx, "Step 5: GET_CERTIFICATE\n"); - rc = wolfSPDM_GetCertificate(ctx, 0); /* Slot 0 */ - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; + /* Validate certificate chain if trusted CAs are loaded */ + if (ctx->hasTrustedCAs) { + SPDM_CONNECT_STEP(ctx, "", wolfSPDM_ValidateCertChain(ctx)); } - - /* Step 6: KEY_EXCHANGE / KEY_EXCHANGE_RSP */ - wolfSPDM_DebugPrint(ctx, "Step 6: KEY_EXCHANGE\n"); - rc = wolfSPDM_KeyExchange(ctx); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; + else if (!ctx->hasResponderPubKey) { + wolfSPDM_DebugPrint(ctx, + "Warning: No trusted CAs loaded — chain not validated\n"); } - /* Step 7: FINISH / FINISH_RSP */ - wolfSPDM_DebugPrint(ctx, "Step 7: FINISH\n"); - rc = wolfSPDM_Finish(ctx); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } + SPDM_CONNECT_STEP(ctx, "Step 6: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); + SPDM_CONNECT_STEP(ctx, "Step 7: FINISH\n", + wolfSPDM_Finish(ctx)); ctx->state = WOLFSPDM_STATE_CONNECTED; wolfSPDM_DebugPrint(ctx, "SPDM Session Established! SessionID=0x%08x\n", @@ -421,9 +413,7 @@ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) return (rc == WOLFSPDM_SUCCESS) ? WOLFSPDM_SUCCESS : rc; } -/* ========================================================================== - * I/O Helper - * ========================================================================== */ +/* --- I/O Helper --- */ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, @@ -443,9 +433,7 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -/* ========================================================================== - * Debug Utilities - * ========================================================================== */ +/* --- Debug Utilities --- */ void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) { @@ -482,9 +470,57 @@ void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, fflush(stdout); } -/* ========================================================================== - * Error String - * ========================================================================== */ +/* --- Measurement Accessors --- */ + +#ifndef NO_WOLFSPDM_MEAS + +int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL || !ctx->hasMeasurements) { + return 0; + } + return (int)ctx->measBlockCount; +} + +int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, + byte* measIndex, byte* measType, byte* value, word32* valueSz) +{ + const WOLFSPDM_MEAS_BLOCK* blk; + + if (ctx == NULL || !ctx->hasMeasurements) { + return WOLFSPDM_E_INVALID_ARG; + } + if (blockIdx < 0 || blockIdx >= (int)ctx->measBlockCount) { + return WOLFSPDM_E_INVALID_ARG; + } + if (valueSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + blk = &ctx->measBlocks[blockIdx]; + + if (measIndex != NULL) { + *measIndex = blk->index; + } + if (measType != NULL) { + *measType = blk->dmtfType; + } + + if (value != NULL) { + word32 copySize = blk->valueSize; + if (copySize > *valueSz) { + copySize = *valueSz; + } + XMEMCPY(value, blk->value, copySize); + } + *valueSz = blk->valueSize; + + return WOLFSPDM_SUCCESS; +} + +#endif /* !NO_WOLFSPDM_MEAS */ + +/* --- Error String --- */ const char* wolfSPDM_GetErrorString(int error) { @@ -510,6 +546,12 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_ALGO_MISMATCH: return "Algorithm mismatch"; case WOLFSPDM_E_SESSION_INVALID: return "Invalid session"; case WOLFSPDM_E_KEY_EXCHANGE: return "Key exchange failed"; + case WOLFSPDM_E_MEASUREMENT: return "Measurement retrieval failed"; + case WOLFSPDM_E_MEAS_NOT_VERIFIED: return "Measurements not signature-verified"; + case WOLFSPDM_E_MEAS_SIG_FAIL: return "Measurement signature verification failed"; + case WOLFSPDM_E_CERT_PARSE: return "Failed to parse responder certificate"; + case WOLFSPDM_E_CHALLENGE: return "Challenge authentication failed"; + case WOLFSPDM_E_KEY_UPDATE: return "Key update failed"; default: return "Unknown error"; } } diff --git a/src/spdm_crypto.c b/src/spdm_crypto.c index 0dd935b..4018628 100644 --- a/src/spdm_crypto.c +++ b/src/spdm_crypto.c @@ -22,9 +22,7 @@ #include "spdm_internal.h" #include -/* ========================================================================== - * Random Number Generation - * ========================================================================== */ +/* --- Random Number Generation --- */ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) { @@ -46,9 +44,7 @@ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) return WOLFSPDM_SUCCESS; } -/* ========================================================================== - * ECDHE Key Generation (P-384) - * ========================================================================== */ +/* --- ECDHE Key Generation (P-384) --- */ int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx) { @@ -116,9 +112,7 @@ int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -/* ========================================================================== - * ECDH Shared Secret Computation - * ========================================================================== */ +/* --- ECDH Shared Secret Computation --- */ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY) @@ -182,10 +176,7 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } -/* ========================================================================== - * ECDSA Signing (P-384) - * Used for requester's signature in FINISH during mutual authentication - * ========================================================================== */ +/* --- ECDSA Signing (P-384) --- */ int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz) diff --git a/src/spdm_internal.h b/src/spdm_internal.h index b3d880a..0fcccfc 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -45,14 +45,13 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif -/* ========================================================================== - * State Machine Constants - * ========================================================================== */ +/* --- State Machine Constants --- */ #define WOLFSPDM_STATE_INIT 0 /* Initial state */ #define WOLFSPDM_STATE_VERSION 1 /* GET_VERSION complete */ @@ -64,10 +63,23 @@ extern "C" { #define WOLFSPDM_STATE_FINISH 7 /* FINISH complete */ #define WOLFSPDM_STATE_CONNECTED 8 /* Session established */ #define WOLFSPDM_STATE_ERROR 9 /* Error state */ +#ifndef NO_WOLFSPDM_MEAS +#define WOLFSPDM_STATE_MEASURED 10 /* Measurements retrieved */ +#endif + +/* --- Measurement Block Structure --- */ + +#ifndef NO_WOLFSPDM_MEAS +typedef struct WOLFSPDM_MEAS_BLOCK { + byte index; /* SPDM measurement index (1-based) */ + byte measurementSpec; /* Measurement specification (1=DMTF) */ + byte dmtfType; /* DMTFSpecMeasurementValueType */ + word16 valueSize; /* Actual value size in bytes */ + byte value[WOLFSPDM_MAX_MEAS_VALUE_SIZE]; /* Measurement value (digest/raw) */ +} WOLFSPDM_MEAS_BLOCK; +#endif /* !NO_WOLFSPDM_MEAS */ -/* ========================================================================== - * Internal Context Structure - * ========================================================================== */ +/* --- Internal Context Structure --- */ struct WOLFSPDM_CTX { /* State machine */ @@ -117,6 +129,7 @@ struct WOLFSPDM_CTX { /* Transcript hash for TH1/TH2 computation */ byte transcript[WOLFSPDM_MAX_TRANSCRIPT]; word32 transcriptLen; + word32 vcaLen; /* VCA transcript size (after ALGORITHMS, used by measurement sig) */ /* Certificate chain buffer for Ct computation */ byte certChain[WOLFSPDM_MAX_CERT_CHAIN]; @@ -164,11 +177,150 @@ struct WOLFSPDM_CTX { /* Message buffers */ byte sendBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_TAG_SIZE]; byte recvBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_TAG_SIZE]; + +#ifndef NO_WOLFSPDM_MEAS + /* Measurement data */ + WOLFSPDM_MEAS_BLOCK measBlocks[WOLFSPDM_MAX_MEAS_BLOCKS]; + word32 measBlockCount; + byte measNonce[32]; /* Nonce for signed measurements */ + byte measSummaryHash[WOLFSPDM_HASH_SIZE]; /* Summary hash from response */ + byte measSignature[WOLFSPDM_ECC_SIG_SIZE]; /* Captured signature (96 bytes P-384) */ + word32 measSignatureSize; /* 0 if unsigned, 96 if signed */ + int hasMeasurements; + +#ifndef NO_WOLFSPDM_MEAS_VERIFY + /* Saved GET_MEASUREMENTS request for L1/L2 transcript */ + byte measReqMsg[48]; /* Saved request (max 37 bytes) */ + word32 measReqMsgSz; +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ +#endif /* !NO_WOLFSPDM_MEAS */ + + /* Responder identity for signature verification (measurements + challenge) */ + ecc_key responderPubKey; /* Extracted from cert chain leaf */ + int hasResponderPubKey; /* 1 if key extracted successfully */ + + /* Certificate chain validation */ + byte trustedCAs[WOLFSPDM_MAX_CERT_CHAIN]; /* DER-encoded root CAs */ + word32 trustedCAsSz; + int hasTrustedCAs; /* 1 if CAs loaded */ + +#ifndef NO_WOLFSPDM_CHALLENGE + /* Challenge authentication */ + byte challengeNonce[32]; /* Saved nonce from CHALLENGE request */ + byte challengeMeasHashType; /* MeasurementSummaryHashType from req */ + + /* Running M1/M2 hash for CHALLENGE_AUTH signature verification. + * Per DSP0274, M1/M2 = A || B || C where: + * A = VCA (GET_VERSION..ALGORITHMS) + * B = GET_DIGESTS + DIGESTS + GET_CERTIFICATE + CERTIFICATE (all chunks) + * C = CHALLENGE + CHALLENGE_AUTH (before sig) + * This hash accumulates A+B during NegAlgo/GetDigests/GetCertificate, + * then C is added in VerifyChallengeAuthSig. */ + wc_Sha384 m1m2Hash; + int m1m2HashInit; /* 1 if m1m2Hash is initialized */ +#endif + + /* Key update state — app secrets for re-derivation */ + byte reqAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ + byte rspAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ }; -/* ========================================================================== - * Internal Function Declarations - Transcript - * ========================================================================== */ +/* --- Byte-Order Helpers --- */ + +static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)(val >> 8); +} +static WC_INLINE word16 SPDM_Get16LE(const byte* buf) { + return (word16)(buf[0] | (buf[1] << 8)); +} +static WC_INLINE void SPDM_Set16BE(byte* buf, word16 val) { + buf[0] = (byte)(val >> 8); buf[1] = (byte)(val & 0xFF); +} +static WC_INLINE word16 SPDM_Get16BE(const byte* buf) { + return (word16)((buf[0] << 8) | buf[1]); +} +static WC_INLINE void SPDM_Set32LE(byte* buf, word32 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)((val >> 8) & 0xFF); + buf[2] = (byte)((val >> 16) & 0xFF); buf[3] = (byte)((val >> 24) & 0xFF); +} +static WC_INLINE word32 SPDM_Get32LE(const byte* buf) { + return (word32)buf[0] | ((word32)buf[1] << 8) | + ((word32)buf[2] << 16) | ((word32)buf[3] << 24); +} +static WC_INLINE void SPDM_Set32BE(byte* buf, word32 val) { + buf[0] = (byte)(val >> 24); buf[1] = (byte)((val >> 16) & 0xFF); + buf[2] = (byte)((val >> 8) & 0xFF); buf[3] = (byte)(val & 0xFF); +} +static WC_INLINE word32 SPDM_Get32BE(const byte* buf) { + return ((word32)buf[0] << 24) | ((word32)buf[1] << 16) | + ((word32)buf[2] << 8) | (word32)buf[3]; +} +static WC_INLINE void SPDM_Set64LE(byte* buf, word64 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)((val >> 8) & 0xFF); + buf[2] = (byte)((val >> 16) & 0xFF); buf[3] = (byte)((val >> 24) & 0xFF); + buf[4] = (byte)((val >> 32) & 0xFF); buf[5] = (byte)((val >> 40) & 0xFF); + buf[6] = (byte)((val >> 48) & 0xFF); buf[7] = (byte)((val >> 56) & 0xFF); +} +static WC_INLINE word64 SPDM_Get64LE(const byte* buf) { + return (word64)buf[0] | ((word64)buf[1] << 8) | + ((word64)buf[2] << 16) | ((word64)buf[3] << 24) | + ((word64)buf[4] << 32) | ((word64)buf[5] << 40) | + ((word64)buf[6] << 48) | ((word64)buf[7] << 56); +} + +/* Build IV: BaseIV XOR zero-extended sequence number (DSP0277) */ +static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, + word64 seqNum, int nuvotonMode) +{ + XMEMCPY(iv, baseIv, WOLFSPDM_AEAD_IV_SIZE); +#ifdef WOLFSPDM_NUVOTON + if (nuvotonMode) { + byte seq[8]; int i; + SPDM_Set64LE(seq, seqNum); + for (i = 0; i < 8; i++) iv[i] ^= seq[i]; + } + else +#endif + { + (void)nuvotonMode; + iv[0] ^= (byte)(seqNum & 0xFF); + iv[1] ^= (byte)((seqNum >> 8) & 0xFF); + } +} + +/* --- Connect Step Macro --- */ + +#define SPDM_CONNECT_STEP(ctx, msg, func) do { \ + wolfSPDM_DebugPrint(ctx, msg); \ + rc = func; \ + if (rc != WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_ERROR; return rc; } \ +} while (0) + +/* --- Argument Validation Macros --- */ + +#define SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz) \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL || *(bufSz) < (minSz)) \ + return WOLFSPDM_E_BUFFER_SMALL + +#define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ + return WOLFSPDM_E_INVALID_ARG + +/* --- Response Code Check Macro --- */ + +#define SPDM_CHECK_RESPONSE(ctx, buf, bufSz, expected, fallbackErr) \ + do { \ + if ((buf)[1] != (expected)) { \ + int _ec; \ + if (wolfSPDM_CheckError((buf), (bufSz), &_ec)) { \ + wolfSPDM_DebugPrint((ctx), "SPDM error: 0x%02x\n", _ec); \ + return WOLFSPDM_E_PEER_ERROR; \ + } \ + return (fallbackErr); \ + } \ + } while (0) + +/* --- Internal Function Declarations - Transcript --- */ /* Reset transcript buffer */ void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); @@ -185,9 +337,13 @@ int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); /* Compute Ct = Hash(certificate_chain) */ int wolfSPDM_ComputeCertChainHash(WOLFSPDM_CTX* ctx); -/* ========================================================================== - * Internal Function Declarations - Crypto - * ========================================================================== */ +/* SHA-384 hash helper: Hash(d1 || d2 || d3), pass NULL/0 for unused buffers */ +int wolfSPDM_Sha384Hash(byte* out, + const byte* d1, word32 d1Sz, + const byte* d2, word32 d2Sz, + const byte* d3, word32 d3Sz); + +/* --- Internal Function Declarations - Crypto --- */ /* Generate ephemeral P-384 key for ECDHE */ int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); @@ -208,9 +364,7 @@ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz); -/* ========================================================================== - * Internal Function Declarations - Key Derivation - * ========================================================================== */ +/* --- Internal Function Declarations - Key Derivation --- */ /* Derive all keys from shared secret and TH1 */ int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); @@ -227,9 +381,7 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, byte* verifyData); -/* ========================================================================== - * Internal Function Declarations - Message Building - * ========================================================================== */ +/* --- Internal Function Declarations - Message Building --- */ /* Build GET_VERSION request */ int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); @@ -256,9 +408,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); /* Build END_SESSION request */ int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -/* ========================================================================== - * Internal Function Declarations - Message Parsing - * ========================================================================== */ +/* --- Internal Function Declarations - Message Parsing --- */ /* Parse VERSION response */ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); @@ -285,9 +435,7 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); /* Check for ERROR response */ int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); -/* ========================================================================== - * Internal Function Declarations - Secured Messaging - * ========================================================================== */ +/* --- Internal Function Declarations - Secured Messaging --- */ /* Encrypt plaintext using session keys */ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, @@ -299,9 +447,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, const byte* enc, word32 encSz, byte* plain, word32* plainSz); -/* ========================================================================== - * Internal Utility Functions - * ========================================================================== */ +/* --- Internal Utility Functions --- */ /* Send message via I/O callback and receive response */ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, @@ -315,6 +461,71 @@ void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...); void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, const byte* data, word32 len); +/* --- Internal Function Declarations - Measurements --- */ + +#ifndef NO_WOLFSPDM_MEAS +/* Build GET_MEASUREMENTS request */ +int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte requestSig); + +/* Parse MEASUREMENTS response */ +int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +#ifndef NO_WOLFSPDM_MEAS_VERIFY +/* Verify measurement signature (L1/L2 transcript) */ +int wolfSPDM_VerifyMeasurementSig(WOLFSPDM_CTX* ctx, + const byte* rspBuf, word32 rspBufSz, + const byte* reqMsg, word32 reqMsgSz); +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ +#endif /* !NO_WOLFSPDM_MEAS */ + +/* --- Internal Function Declarations - Certificate Chain Validation --- */ + +/* Extract responder's public key from certificate chain leaf cert */ +int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx); + +/* Validate certificate chain using trusted CAs and extract public key */ +int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); + +/* --- Internal Function Declarations - Challenge --- */ + +#ifndef NO_WOLFSPDM_CHALLENGE +/* Build CHALLENGE request */ +int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + int slotId, byte measHashType); + +/* Parse CHALLENGE_AUTH response */ +int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, word32* sigOffset); + +/* Verify CHALLENGE_AUTH signature */ +int wolfSPDM_VerifyChallengeAuthSig(WOLFSPDM_CTX* ctx, + const byte* rspBuf, word32 rspBufSz, + const byte* reqMsg, word32 reqMsgSz, word32 sigOffset); +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* --- Internal Function Declarations - Heartbeat --- */ + +/* Build HEARTBEAT request */ +int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Parse HEARTBEAT_ACK response */ +int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz); + +/* --- Internal Function Declarations - Key Update --- */ + +/* Build KEY_UPDATE request */ +int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte* tag); + +/* Parse KEY_UPDATE_ACK response */ +int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, byte operation, byte tag); + +/* Derive updated keys from saved app secrets */ +int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll); + #ifdef __cplusplus } #endif diff --git a/src/spdm_kdf.c b/src/spdm_kdf.c index d9d0256..c6e5a6b 100644 --- a/src/spdm_kdf.c +++ b/src/spdm_kdf.c @@ -242,6 +242,10 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) return rc; } + /* Save app secrets for KEY_UPDATE re-derivation */ + XMEMCPY(ctx->reqAppSecret, reqAppSecret, WOLFSPDM_HASH_SIZE); + XMEMCPY(ctx->rspAppSecret, rspAppSecret, WOLFSPDM_HASH_SIZE); + /* Derive new encryption keys from app data secrets */ rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, reqAppSecret, WOLFSPDM_HASH_SIZE, SPDM_LABEL_KEY, NULL, 0, @@ -279,3 +283,74 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) return WOLFSPDM_SUCCESS; } + +/* --- Key Update Re-derivation (DSP0277) --- */ + +int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll) +{ + byte newReqAppSecret[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Per DSP0277: KEY_UPDATE uses "traffic upd" label with NO context. + * info = outLen(2 LE) || "spdm1.2 " || "traffic upd" */ + + /* Always update requester key */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_UPDATE, NULL, 0, + newReqAppSecret, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, newReqAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_KEY, NULL, 0, + ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, newReqAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_IV, NULL, 0, + ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Save new requester secret for future updates */ + XMEMCPY(ctx->reqAppSecret, newReqAppSecret, WOLFSPDM_HASH_SIZE); + + /* Optionally update responder key */ + if (updateAll) { + byte newRspAppSecret[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_UPDATE, NULL, 0, + newRspAppSecret, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, newRspAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_KEY, NULL, 0, + ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, newRspAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_IV, NULL, 0, + ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Save new responder secret for future updates */ + XMEMCPY(ctx->rspAppSecret, newRspAppSecret, WOLFSPDM_HASH_SIZE); + } + + return WOLFSPDM_SUCCESS; +} diff --git a/src/spdm_msg.c b/src/spdm_msg.c index 9e31585..1a73497 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -21,12 +21,13 @@ #include "spdm_internal.h" #include +#include int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) { - if (buf == NULL || bufSz == NULL || *bufSz < 4) { + /* Note: ctx is not used for GET_VERSION, check buf/bufSz directly */ + if (buf == NULL || bufSz == NULL || *bufSz < 4) return WOLFSPDM_E_BUFFER_SMALL; - } /* Per SPDM spec, GET_VERSION always uses version 0x10 */ buf[0] = SPDM_VERSION_10; @@ -40,9 +41,7 @@ int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { - if (ctx == NULL || buf == NULL || bufSz == NULL || *bufSz < 20) { - return WOLFSPDM_E_BUFFER_SMALL; - } + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 20); XMEMSET(buf, 0, 20); buf[0] = ctx->spdmVersion; /* Use negotiated version */ @@ -52,10 +51,7 @@ int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) /* CTExponent and reserved at offsets 4-7 */ /* Requester flags (4 bytes LE) */ - buf[8] = (byte)(ctx->reqCaps & 0xFF); - buf[9] = (byte)((ctx->reqCaps >> 8) & 0xFF); - buf[10] = (byte)((ctx->reqCaps >> 16) & 0xFF); - buf[11] = (byte)((ctx->reqCaps >> 24) & 0xFF); + SPDM_Set32LE(&buf[8], ctx->reqCaps); /* DataTransferSize (4 LE) */ buf[12] = 0x00; buf[13] = 0x10; buf[14] = 0x00; buf[15] = 0x00; @@ -68,9 +64,7 @@ int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { - if (ctx == NULL || buf == NULL || bufSz == NULL || *bufSz < 48) { - return WOLFSPDM_E_BUFFER_SMALL; - } + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 48); XMEMSET(buf, 0, 48); buf[0] = ctx->spdmVersion; /* Use negotiated version */ @@ -100,36 +94,34 @@ int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufS return WOLFSPDM_SUCCESS; } -int wolfSPDM_BuildGetDigests(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, + byte* buf, word32* bufSz) { - if (ctx == NULL || buf == NULL || bufSz == NULL || *bufSz < 4) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - buf[0] = ctx->spdmVersion; /* Use negotiated version */ - buf[1] = SPDM_GET_DIGESTS; + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 4); + buf[0] = ctx->spdmVersion; + buf[1] = msgCode; buf[2] = 0x00; buf[3] = 0x00; *bufSz = 4; - return WOLFSPDM_SUCCESS; } +int wolfSPDM_BuildGetDigests(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + return wolfSPDM_BuildSimpleMsg(ctx, SPDM_GET_DIGESTS, buf, bufSz); +} + int wolfSPDM_BuildGetCertificate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, int slotId, word16 offset, word16 length) { - if (ctx == NULL || buf == NULL || bufSz == NULL || *bufSz < 8) { - return WOLFSPDM_E_BUFFER_SMALL; - } + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 8); buf[0] = ctx->spdmVersion; /* Use negotiated version */ buf[1] = SPDM_GET_CERTIFICATE; buf[2] = (byte)(slotId & 0x0F); buf[3] = 0x00; - buf[4] = (byte)(offset & 0xFF); - buf[5] = (byte)((offset >> 8) & 0xFF); - buf[6] = (byte)(length & 0xFF); - buf[7] = (byte)((length >> 8) & 0xFF); + SPDM_Set16LE(&buf[4], offset); + SPDM_Set16LE(&buf[6], length); *bufSz = 8; return WOLFSPDM_SUCCESS; @@ -144,9 +136,7 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) word32 pubKeyYSz = sizeof(pubKeyY); int rc; - if (ctx == NULL || buf == NULL || bufSz == NULL || *bufSz < 180) { - return WOLFSPDM_E_BUFFER_SMALL; - } + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 180); rc = wolfSPDM_GenerateEphemeralKey(ctx); if (rc != WOLFSPDM_SUCCESS) { @@ -225,6 +215,88 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) return WOLFSPDM_SUCCESS; } +/* --- Shared Signing Helpers --- */ + +/* Build SPDM 1.2+ signed hash per DSP0274: + * M = combined_spdm_prefix || zero_pad || context_str || inputDigest + * outputDigest = Hash(M) + * + * combined_spdm_prefix = "dmtf-spdm-v1.X.*" x4 = 64 bytes + * zero_pad = (36 - contextStrLen) bytes of 0x00 + * context_str = signing context string (variable length, max 36) */ +static int wolfSPDM_BuildSignedHash(byte spdmVersion, + const char* contextStr, word32 contextStrLen, + const byte* inputDigest, byte* outputDigest) +{ + byte signMsg[200]; /* 64 + 36 + 48 = 148 bytes max */ + word32 signMsgLen = 0; + word32 zeroPadLen; + byte majorVer, minorVer; + int i, rc; + + majorVer = (byte)('0' + ((spdmVersion >> 4) & 0xF)); + minorVer = (byte)('0' + (spdmVersion & 0xF)); + + /* combined_spdm_prefix: "dmtf-spdm-v1.X.*" x4 = 64 bytes */ + for (i = 0; i < 4; i++) { + XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", 16); + signMsg[signMsgLen + 11] = majorVer; + signMsg[signMsgLen + 13] = minorVer; + signMsg[signMsgLen + 15] = '*'; + signMsgLen += 16; + } + + /* Zero padding: 36 - contextStrLen bytes */ + zeroPadLen = 36 - contextStrLen; + XMEMSET(&signMsg[signMsgLen], 0x00, zeroPadLen); + signMsgLen += zeroPadLen; + + /* Signing context string */ + XMEMCPY(&signMsg[signMsgLen], contextStr, contextStrLen); + signMsgLen += contextStrLen; + + /* Input digest */ + XMEMCPY(&signMsg[signMsgLen], inputDigest, WOLFSPDM_HASH_SIZE); + signMsgLen += WOLFSPDM_HASH_SIZE; + + /* Hash M */ + rc = wolfSPDM_Sha384Hash(outputDigest, signMsg, signMsgLen, + NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) return rc; + + return WOLFSPDM_SUCCESS; +} + +/* Verify an SPDM ECDSA signature (raw r||s format) against a digest + * using the responder's public key stored in ctx. */ +static int wolfSPDM_VerifyEccSig(WOLFSPDM_CTX* ctx, + const byte* sigRaw, word32 sigRawSz, + const byte* digest, word32 digestSz) +{ + byte derSig[256]; + word32 derSigSz = sizeof(derSig); + const byte* sigR = sigRaw; + const byte* sigS = sigRaw + (sigRawSz / 2); + int verified = 0; + int rc; + + rc = wc_ecc_rs_raw_to_sig(sigR, sigRawSz / 2, + sigS, sigRawSz / 2, derSig, &derSigSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECC rs_raw_to_sig failed: %d\n", rc); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_ecc_verify_hash(derSig, derSigSz, digest, digestSz, + &verified, &ctx->responderPubKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECC verify_hash failed: %d\n", rc); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + return verified == 1 ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { byte th2Hash[WOLFSPDM_HASH_SIZE]; @@ -260,16 +332,10 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) if (mutualAuth) { buf[2] = 0x01; /* Param1: Signature field is included */ buf[3] = 0xFF; /* Param2: 0xFF = requester public key provisioned in trusted environment (GIVE_PUB_KEY) */ - wolfSPDM_DebugPrint(ctx, "FINISH: mutual auth with signature\n"); - wolfSPDM_DebugPrint(ctx, " Header: version=0x%02x code=0x%02x param1=0x%02x param2=0x%02x\n", - buf[0], buf[1], buf[2], buf[3]); } else { buf[2] = 0x00; /* Param1: No signature */ buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ - wolfSPDM_DebugPrint(ctx, "FINISH: no mutual auth\n"); - wolfSPDM_DebugPrint(ctx, " Header: version=0x%02x code=0x%02x param1=0x%02x param2=0x%02x\n", - buf[0], buf[1], buf[2], buf[3]); } /* Per DSP0274 / libspdm: When mutual auth is requested, the transcript @@ -283,27 +349,11 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) #ifdef WOLFSPDM_NUVOTON if (mutualAuth && ctx->reqPubKeyTPMTLen > 0) { byte cmHash[WOLFSPDM_HASH_SIZE]; - wc_Sha384 shaCm; - - rc = wc_InitSha384(&shaCm); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_Sha384Update(&shaCm, ctx->reqPubKeyTPMT, ctx->reqPubKeyTPMTLen); - if (rc != 0) { - wc_Sha384Free(&shaCm); - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_Sha384Final(&shaCm, cmHash); - wc_Sha384Free(&shaCm); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - + rc = wolfSPDM_Sha384Hash(cmHash, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen, NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) return rc; rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } + if (rc != WOLFSPDM_SUCCESS) return rc; } #endif @@ -321,72 +371,14 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); - /* For mutual auth, use SPDM 1.2+ signing context format per DSP0274: - * M = combined_spdm_prefix || zero_pad || signing_context || TH2 - * - * Where (per libspdm reference implementation): - * - combined_spdm_prefix = "dmtf-spdm-v1.3.*" repeated 4 times = 64 bytes - * (16 bytes each, NOT including null terminator) - * - zero_pad = 12 bytes of 0x00 (36 - strlen("requester-finish signing")) - * - signing_context = "requester-finish signing" (24 bytes) - * - TH2 = transcript hash (48 bytes for SHA-384) - * - * Total: 64 + 12 + 24 + 48 = 148 bytes */ + /* For mutual auth, use SPDM 1.2+ signing context format per DSP0274 */ if (mutualAuth) { - /* Build signing context per DSP0274 / libspdm */ - static const char context_str[] = "requester-finish signing"; /* 24 bytes */ - /* 16 bytes per prefix (sizeof - 1, no null terminator) */ - #define SPDM_SIGNING_PREFIX_SIZE 16 - #define SPDM_SIGNING_CONTEXT_STR_SIZE 24 - #define SPDM_SIGNING_ZERO_PAD_SIZE (36 - SPDM_SIGNING_CONTEXT_STR_SIZE) /* 12 */ - byte signMsg[200]; /* 64 + 12 + 24 + 48 = 148 bytes */ byte signMsgHash[WOLFSPDM_HASH_SIZE]; - word32 signMsgLen = 0; - wc_Sha384 sha; - int i; - byte majorVer, minorVer; - - /* Determine version digits from negotiated SPDM version byte (0x13 = 1.3) */ - majorVer = (byte)('0' + ((ctx->spdmVersion >> 4) & 0xF)); /* '1' */ - minorVer = (byte)('0' + (ctx->spdmVersion & 0xF)); /* '3' */ - - /* combined_spdm_prefix: "dmtf-spdm-v1.3.*" repeated 4 times = 64 bytes - * Each copy is 16 bytes (no null terminator), matching libspdm's - * SPDM_VERSION_1_2_SIGNING_PREFIX_CONTEXT_SIZE = sizeof(...) - 1 = 16 */ - for (i = 0; i < 4; i++) { - XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", SPDM_SIGNING_PREFIX_SIZE); - signMsg[signMsgLen + 11] = majorVer; /* Patch major version */ - signMsg[signMsgLen + 13] = minorVer; /* Patch minor version */ - signMsg[signMsgLen + 15] = '*'; /* Wildcard for update version */ - signMsgLen += SPDM_SIGNING_PREFIX_SIZE; - } - /* Zero padding: 36 - context_str_size = 12 bytes of 0x00 */ - XMEMSET(&signMsg[signMsgLen], 0x00, SPDM_SIGNING_ZERO_PAD_SIZE); - signMsgLen += SPDM_SIGNING_ZERO_PAD_SIZE; - - /* Signing context string: "requester-finish signing" (24 bytes) */ - XMEMCPY(&signMsg[signMsgLen], context_str, SPDM_SIGNING_CONTEXT_STR_SIZE); - signMsgLen += SPDM_SIGNING_CONTEXT_STR_SIZE; - - /* Append TH2 hash */ - XMEMCPY(&signMsg[signMsgLen], th2Hash, WOLFSPDM_HASH_SIZE); - signMsgLen += WOLFSPDM_HASH_SIZE; - - /* Hash M to get the value to sign */ - rc = wc_InitSha384(&sha); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_Sha384Update(&sha, signMsg, signMsgLen); - if (rc != 0) { - wc_Sha384Free(&sha); - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_Sha384Final(&sha, signMsgHash); - wc_Sha384Free(&sha); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "requester-finish signing", 24, th2Hash, signMsgHash); + if (rc != WOLFSPDM_SUCCESS) { + return rc; } /* Sign Hash(M) */ @@ -434,24 +426,12 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) } *bufSz = offset; - wolfSPDM_DebugPrint(ctx, "FINISH message size: %u bytes\n", *bufSz); - return WOLFSPDM_SUCCESS; } int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { - if (ctx == NULL || buf == NULL || bufSz == NULL || *bufSz < 4) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - buf[0] = ctx->spdmVersion; /* Use negotiated version */ - buf[1] = SPDM_END_SESSION; - buf[2] = 0x00; - buf[3] = 0x00; - *bufSz = 4; - - return WOLFSPDM_SUCCESS; + return wolfSPDM_BuildSimpleMsg(ctx, SPDM_END_SESSION, buf, bufSz); } int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode) @@ -476,23 +456,13 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) word32 i; byte highestVersion = SPDM_VERSION_12; /* Start at 1.2, find highest supported (capped at 1.3) */ - if (ctx == NULL || buf == NULL || bufSz < 6) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (buf[1] != SPDM_VERSION) { - int errCode; - if (wolfSPDM_CheckError(buf, bufSz, &errCode)) { - wolfSPDM_DebugPrint(ctx, "VERSION error: 0x%02x\n", errCode); - return WOLFSPDM_E_PEER_ERROR; - } - return WOLFSPDM_E_VERSION_MISMATCH; - } + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 6); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_VERSION, WOLFSPDM_E_VERSION_MISMATCH); /* Parse VERSION response: * Offset 4-5: VersionNumberEntryCount (LE) * Offset 6+: VersionNumberEntry array (2 bytes each, LE) */ - entryCount = (word16)(buf[4] | (buf[5] << 8)); + entryCount = SPDM_Get16LE(&buf[4]); /* Find highest supported version from entries (capped at 1.3 for now) * @@ -503,7 +473,6 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) */ for (i = 0; i < entryCount && (6 + i * 2 + 1) < bufSz; i++) { byte ver = buf[6 + i * 2 + 1]; /* Major.Minor in high byte */ - wolfSPDM_DebugPrint(ctx, "VERSION entry %u: 0x%02x\n", i, ver); /* Cap at 1.3 (0x13) - SPDM 1.4 FINISH handling needs work */ if (ver > highestVersion && ver <= SPDM_VERSION_13) { highestVersion = ver; @@ -519,20 +488,10 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - if (ctx == NULL || buf == NULL || bufSz < 12) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (buf[1] != SPDM_CAPABILITIES) { - int errCode; - if (wolfSPDM_CheckError(buf, bufSz, &errCode)) { - return WOLFSPDM_E_PEER_ERROR; - } - return WOLFSPDM_E_CAPS_MISMATCH; - } + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 12); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CAPABILITIES, WOLFSPDM_E_CAPS_MISMATCH); - ctx->rspCaps = (word32)buf[8] | ((word32)buf[9] << 8) | - ((word32)buf[10] << 16) | ((word32)buf[11] << 24); + ctx->rspCaps = SPDM_Get32LE(&buf[8]); ctx->state = WOLFSPDM_STATE_CAPS; wolfSPDM_DebugPrint(ctx, "Responder caps: 0x%08x\n", ctx->rspCaps); @@ -541,17 +500,8 @@ int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - if (ctx == NULL || buf == NULL || bufSz < 4) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (buf[1] != SPDM_ALGORITHMS) { - int errCode; - if (wolfSPDM_CheckError(buf, bufSz, &errCode)) { - return WOLFSPDM_E_PEER_ERROR; - } - return WOLFSPDM_E_ALGO_MISMATCH; - } + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_ALGORITHMS, WOLFSPDM_E_ALGO_MISMATCH); ctx->state = WOLFSPDM_STATE_ALGO; return WOLFSPDM_SUCCESS; @@ -559,17 +509,8 @@ int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - if (ctx == NULL || buf == NULL || bufSz < 4) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (buf[1] != SPDM_DIGESTS) { - int errCode; - if (wolfSPDM_CheckError(buf, bufSz, &errCode)) { - return WOLFSPDM_E_PEER_ERROR; - } - return WOLFSPDM_E_CERT_FAIL; - } + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_DIGESTS, WOLFSPDM_E_CERT_FAIL); ctx->state = WOLFSPDM_STATE_DIGESTS; return WOLFSPDM_SUCCESS; @@ -583,16 +524,10 @@ int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, return WOLFSPDM_E_INVALID_ARG; } - if (buf[1] != SPDM_CERTIFICATE) { - int errCode; - if (wolfSPDM_CheckError(buf, bufSz, &errCode)) { - return WOLFSPDM_E_PEER_ERROR; - } - return WOLFSPDM_E_CERT_FAIL; - } + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CERTIFICATE, WOLFSPDM_E_CERT_FAIL); - *portionLen = (word16)(buf[4] | (buf[5] << 8)); - *remainderLen = (word16)(buf[6] | (buf[7] << 8)); + *portionLen = SPDM_Get16LE(&buf[4]); + *remainderLen = SPDM_Get16LE(&buf[6]); /* Add certificate chain data (starting at offset 8) */ if (*portionLen > 0 && bufSz >= (word32)(8 + *portionLen)) { @@ -618,55 +553,28 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS byte expectedHmac[WOLFSPDM_HASH_SIZE]; int rc; - if (ctx == NULL || buf == NULL || bufSz < 140) { - wolfSPDM_DebugPrint(ctx, "ParseKeyExchangeRsp: INVALID - ctx=%p buf=%p bufSz=%u (need 140)\n", - (void*)ctx, (void*)buf, bufSz); - if (buf != NULL && bufSz >= 4) { - wolfSPDM_DebugPrint(ctx, "Response bytes: %02x %02x %02x %02x (code=%02x, err=%02x)\n", - buf[0], buf[1], buf[2], buf[3], buf[1], buf[2]); - } - return WOLFSPDM_E_INVALID_ARG; - } + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); - if (buf[1] != SPDM_KEY_EXCHANGE_RSP) { - int errCode; - if (wolfSPDM_CheckError(buf, bufSz, &errCode)) { - return WOLFSPDM_E_PEER_ERROR; - } - return WOLFSPDM_E_KEY_EXCHANGE; - } - - ctx->rspSessionId = (word16)(buf[4] | (buf[5] << 8)); + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16); /* Parse MutAuthRequested (offset 6) and ReqSlotIDParam (offset 7) per DSP0274 */ ctx->mutAuthRequested = buf[6]; ctx->reqSlotId = buf[7]; - wolfSPDM_DebugPrint(ctx, "RspSessionID: 0x%04x, SessionID: 0x%08x\n", - ctx->rspSessionId, ctx->sessionId); - wolfSPDM_DebugPrint(ctx, "MutAuthRequested: 0x%02x, ReqSlotIDParam: 0x%02x\n", - ctx->mutAuthRequested, ctx->reqSlotId); - /* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */ XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE); /* OpaqueLen at offset 136 */ - opaqueLen = (word16)(buf[136] | (buf[137] << 8)); + opaqueLen = SPDM_Get16LE(&buf[136]); sigOffset = 138 + opaqueLen; keRspPartialLen = sigOffset; - wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP parse: bufSz=%u, opaqueLen=%u, sigOffset=%u\n", - bufSz, opaqueLen, sigOffset); - wolfSPDM_DebugPrint(ctx, " Need: sigOffset(%u) + sig(%u) + hash(%u) = %u bytes\n", - sigOffset, WOLFSPDM_ECC_SIG_SIZE, WOLFSPDM_HASH_SIZE, - sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE); (void)opaqueLen; if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { - wolfSPDM_DebugPrint(ctx, " BUFFER_SMALL: have %u, need %u\n", - bufSz, sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE); return WOLFSPDM_E_BUFFER_SMALL; } @@ -726,9 +634,7 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - if (ctx == NULL || buf == NULL || bufSz < 4) { - return WOLFSPDM_E_INVALID_ARG; - } + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); if (buf[1] == SPDM_FINISH_RSP) { int addRc; @@ -749,3 +655,648 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) return WOLFSPDM_E_BAD_STATE; } + +/* --- Measurement Message Building and Parsing --- */ + +#ifndef NO_WOLFSPDM_MEAS + +int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte requestSig) +{ + word32 offset = 0; + + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Size: 4 header + (requestSig ? 32 nonce + 1 slotId : 0) */ + if (requestSig && *bufSz < 37) { + return WOLFSPDM_E_BUFFER_SMALL; + } + if (!requestSig && *bufSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_GET_MEASUREMENTS; + /* Param1: bits [7:1] = MeasurementSummaryHashType, bit 0 = signature requested */ + buf[offset++] = requestSig ? SPDM_MEAS_REQUEST_SIG_BIT : 0x00; + /* Param2: MeasurementOperation */ + buf[offset++] = operation; + + if (requestSig) { + /* Nonce (32 bytes) */ + int rc = wolfSPDM_GetRandom(ctx, &buf[offset], 32); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + XMEMCPY(ctx->measNonce, &buf[offset], 32); + offset += 32; + + /* SlotIDParam (1 byte) — slot 0 */ + buf[offset++] = 0x00; + } + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word32 offset; + byte numBlocks; + word32 recordLen; + word32 recordEnd; + word32 blockIdx; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 8); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_MEASUREMENTS, WOLFSPDM_E_MEASUREMENT); + + numBlocks = buf[4]; + /* MeasurementRecordLength: 3 bytes LE at offset 5..7 */ + recordLen = (word32)buf[5] | ((word32)buf[6] << 8) | ((word32)buf[7] << 16); + + wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: numBlocks=%u, recordLen=%u\n", + numBlocks, recordLen); + + /* Validate record fits in buffer */ + if (8 + recordLen > bufSz) { + wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: recordLen %u exceeds bufSz %u\n", + recordLen, bufSz); + return WOLFSPDM_E_MEASUREMENT; + } + + recordEnd = 8 + recordLen; + offset = 8; /* Start of measurement record */ + ctx->measBlockCount = 0; + + /* Parse each measurement block */ + for (blockIdx = 0; blockIdx < numBlocks; blockIdx++) { + word16 measSize; + + /* Check block header fits */ + if (offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE > recordEnd) { + wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: block %u header truncated\n", + blockIdx); + return WOLFSPDM_E_MEASUREMENT; + } + + /* Read block header: Index(1) + MeasSpec(1) + MeasSize(2 LE) */ + measSize = SPDM_Get16LE(&buf[offset + 2]); + + /* Check block data fits */ + if (offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + measSize > recordEnd) { + wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: block %u data truncated\n", + blockIdx); + return WOLFSPDM_E_MEASUREMENT; + } + + /* Store if we have room */ + if (ctx->measBlockCount < WOLFSPDM_MAX_MEAS_BLOCKS) { + WOLFSPDM_MEAS_BLOCK* blk = &ctx->measBlocks[ctx->measBlockCount]; + blk->index = buf[offset]; + blk->measurementSpec = buf[offset + 1]; + + /* Parse DMTF measurement value if MeasSpec==1 and size >= 3 */ + if (blk->measurementSpec == 0x01 && measSize >= 3) { + word16 valueSize; + word16 copySize; + + blk->dmtfType = buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE]; + valueSize = (word16)( + buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + 1] | + (buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + 2] << 8)); + + /* Validate valueSize against measSize */ + if (valueSize > measSize - 3) { + wolfSPDM_DebugPrint(ctx, + "MEASUREMENTS: block %u valueSize %u > measSize-3 %u\n", + blockIdx, valueSize, measSize - 3); + return WOLFSPDM_E_MEASUREMENT; + } + + /* Truncate if value exceeds our buffer */ + copySize = valueSize; + if (copySize > WOLFSPDM_MAX_MEAS_VALUE_SIZE) { + copySize = WOLFSPDM_MAX_MEAS_VALUE_SIZE; + } + blk->valueSize = copySize; + XMEMCPY(blk->value, + &buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + 3], copySize); + } + else { + /* Non-DMTF or too small: store raw */ + word16 copySize = measSize; + blk->dmtfType = 0; + if (copySize > WOLFSPDM_MAX_MEAS_VALUE_SIZE) { + copySize = WOLFSPDM_MAX_MEAS_VALUE_SIZE; + } + blk->valueSize = copySize; + if (copySize > 0) { + XMEMCPY(blk->value, + &buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE], copySize); + } + } + + ctx->measBlockCount++; + } + else { + wolfSPDM_DebugPrint(ctx, + "MEASUREMENTS: block %u exceeds MAX_MEAS_BLOCKS (%u), skipping\n", + blockIdx, WOLFSPDM_MAX_MEAS_BLOCKS); + } + + offset += WOLFSPDM_MEAS_BLOCK_HDR_SIZE + measSize; + } + + /* After measurement record: Nonce(32) + OpaqueDataLength(2) + OpaqueData + Signature */ + /* Nonce is present only if signature was requested */ + ctx->measSignatureSize = 0; + + if (offset + 32 + 2 <= bufSz) { + /* Nonce (32 bytes) — skip, we already have our own in ctx->measNonce */ + offset += 32; + + /* OpaqueDataLength (2 LE) */ + word16 opaqueLen = SPDM_Get16LE(&buf[offset]); + offset += 2; + + /* Skip opaque data */ + if (offset + opaqueLen > bufSz) { + wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: opaque data truncated\n"); + return WOLFSPDM_E_MEASUREMENT; + } + offset += opaqueLen; + + /* Signature (if present) */ + if (offset + WOLFSPDM_ECC_SIG_SIZE <= bufSz) { + XMEMCPY(ctx->measSignature, &buf[offset], WOLFSPDM_ECC_SIG_SIZE); + ctx->measSignatureSize = WOLFSPDM_ECC_SIG_SIZE; + } + } + + ctx->hasMeasurements = 1; + wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: parsed %u blocks\n", + ctx->measBlockCount); + + return WOLFSPDM_SUCCESS; +} + +#ifndef NO_WOLFSPDM_MEAS_VERIFY + +int wolfSPDM_VerifyMeasurementSig(WOLFSPDM_CTX* ctx, + const byte* rspBuf, word32 rspBufSz, + const byte* reqMsg, word32 reqMsgSz) +{ + byte digest[WOLFSPDM_HASH_SIZE]; + word32 sigOffset; + int rc; + + if (ctx == NULL || rspBuf == NULL || reqMsg == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->hasResponderPubKey) { + return WOLFSPDM_E_MEAS_NOT_VERIFIED; + } + + /* Signature is the last WOLFSPDM_ECC_SIG_SIZE bytes of the response */ + if (rspBufSz < WOLFSPDM_ECC_SIG_SIZE) { + return WOLFSPDM_E_MEASUREMENT; + } + sigOffset = rspBufSz - WOLFSPDM_ECC_SIG_SIZE; + + /* Build SPDM 1.2 signing context: + * M = combined_spdm_prefix || zero_pad || signing_context || Hash(L1||L2) + * + * Per DSP0274: + * - combined_spdm_prefix = "dmtf-spdm-v1.X.*" x4 = 64 bytes + * - zero_pad = 36 - strlen("responder-measurements signing") = 6 bytes + * - signing_context = "responder-measurements signing" (30 bytes) + * - Hash(L1||L2) = SHA-384(reqMsg || rspBuf[0..sigOffset-1]) = 48 bytes + * + * Total M = 64 + 6 + 30 + 48 = 148 bytes + * Then sign Hash(M) */ + + /* Compute L1||L2 hash per DSP0274 Section 10.11.1: + * L1/L2 = VCA || GET_MEASUREMENTS_request || MEASUREMENTS_response(before sig) */ + rc = wolfSPDM_Sha384Hash(digest, + ctx->transcript, ctx->vcaLen, + reqMsg, reqMsgSz, + rspBuf, sigOffset); + if (rc != WOLFSPDM_SUCCESS) return rc; + + /* Build M = prefix || zero_pad || context_str || L1L2_hash, then hash it */ + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-measurements signing", 30, digest, digest); + if (rc != WOLFSPDM_SUCCESS) return rc; + + /* Verify ECDSA signature (raw r||s format) */ + rc = wolfSPDM_VerifyEccSig(ctx, rspBuf + sigOffset, WOLFSPDM_ECC_SIG_SIZE, + digest, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "Measurement signature VERIFIED\n"); + return WOLFSPDM_SUCCESS; + } + + wolfSPDM_DebugPrint(ctx, "Measurement signature INVALID\n"); + return WOLFSPDM_E_MEAS_SIG_FAIL; +} + +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ +#endif /* !NO_WOLFSPDM_MEAS */ + +/* --- Responder Public Key Extraction --- + * Extract responder's ECC P-384 public key from the leaf certificate in the + * SPDM certificate chain. Used by both measurement signature verification + * and CHALLENGE authentication, so it lives outside measurement guards. */ + +/* Helper: find leaf cert in SPDM cert chain buffer. + * SPDM cert chain header: Length(2 LE) + Reserved(2) + RootHash(48) = 52 bytes + * After header: concatenated DER certificates, leaf is the last one. */ +static int wolfSPDM_FindLeafCert(const byte* certChain, word32 certChainLen, + const byte** leafCert, word32* leafCertSz) +{ + const byte* certDer; + word32 certDerSz; + word32 pos; + const byte* lastCert; + word32 lastCertSz; + + if (certChainLen <= 52) { + return WOLFSPDM_E_CERT_PARSE; + } + + certDer = certChain + 52; + certDerSz = certChainLen - 52; + lastCert = certDer; + lastCertSz = certDerSz; + pos = 0; + + while (pos < certDerSz) { + word32 certLen; + word32 hdrLen; + + if (certDer[pos] != 0x30) { + break; + } + + if (pos + 1 >= certDerSz) break; + + if (certDer[pos + 1] < 0x80) { + certLen = certDer[pos + 1]; + hdrLen = 2; + } + else if (certDer[pos + 1] == 0x81) { + if (pos + 2 >= certDerSz) break; + certLen = certDer[pos + 2]; + hdrLen = 3; + } + else if (certDer[pos + 1] == 0x82) { + if (pos + 3 >= certDerSz) break; + certLen = ((word32)certDer[pos + 2] << 8) | certDer[pos + 3]; + hdrLen = 4; + } + else if (certDer[pos + 1] == 0x83) { + if (pos + 4 >= certDerSz) break; + certLen = ((word32)certDer[pos + 2] << 16) | + ((word32)certDer[pos + 3] << 8) | certDer[pos + 4]; + hdrLen = 5; + } + else { + break; + } + + if (pos + hdrLen + certLen > certDerSz) break; + + lastCert = certDer + pos; + lastCertSz = hdrLen + certLen; + pos += hdrLen + certLen; + } + + *leafCert = lastCert; + *leafCertSz = lastCertSz; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx) +{ + DecodedCert cert; + const byte* leafCert; + word32 leafCertSz; + word32 idx; + int rc; + + if (ctx == NULL || ctx->certChainLen == 0) { + return WOLFSPDM_E_CERT_PARSE; + } + + /* Find the leaf (last) certificate in the SPDM cert chain */ + rc = wolfSPDM_FindLeafCert(ctx->certChain, ctx->certChainLen, + &leafCert, &leafCertSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "Certificate chain too short for header\n"); + return rc; + } + + /* Parse the leaf certificate */ + wc_InitDecodedCert(&cert, leafCert, leafCertSz, NULL); + rc = wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Certificate parse failed: %d\n", rc); + wc_FreeDecodedCert(&cert); + return WOLFSPDM_E_CERT_PARSE; + } + + /* Extract public key from cert and import into ecc_key */ + rc = wc_ecc_init(&ctx->responderPubKey); + if (rc != 0) { + wc_FreeDecodedCert(&cert); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + idx = 0; + rc = wc_EccPublicKeyDecode(cert.publicKey, &idx, &ctx->responderPubKey, + cert.pubKeySize); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECC public key decode failed: %d\n", rc); + wc_ecc_free(&ctx->responderPubKey); + wc_FreeDecodedCert(&cert); + return WOLFSPDM_E_CERT_PARSE; + } + + wc_FreeDecodedCert(&cert); + ctx->hasResponderPubKey = 1; + wolfSPDM_DebugPrint(ctx, "Extracted responder ECC P-384 public key\n"); + + return WOLFSPDM_SUCCESS; +} + +/* --- Certificate Chain Validation --- */ + +int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) +{ + byte caHash[WOLFSPDM_HASH_SIZE]; + const byte* chainRootHash; + int rc; + + if (ctx == NULL || ctx->certChainLen == 0) { + return WOLFSPDM_E_CERT_PARSE; + } + + if (!ctx->hasTrustedCAs) { + return WOLFSPDM_E_CERT_PARSE; + } + + /* SPDM cert chain header: Length(2 LE) + Reserved(2) + RootHash(48) */ + if (ctx->certChainLen <= 52) { + return WOLFSPDM_E_CERT_PARSE; + } + + /* Validate the root hash against our trusted CA */ + rc = wolfSPDM_Sha384Hash(caHash, ctx->trustedCAs, ctx->trustedCAsSz, + NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) return rc; + + chainRootHash = ctx->certChain + 4; /* Skip Length(2) + Reserved(2) */ + if (XMEMCMP(caHash, chainRootHash, WOLFSPDM_HASH_SIZE) != 0) { + wolfSPDM_DebugPrint(ctx, + "Root cert hash mismatch — chain not from trusted CA\n"); + return WOLFSPDM_E_CERT_PARSE; + } + + wolfSPDM_DebugPrint(ctx, "Root certificate hash VERIFIED against trusted CA\n"); + + /* Extract public key from the leaf cert (reuses FindLeafCert internally) */ + rc = wolfSPDM_ExtractResponderPubKey(ctx); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "Certificate chain validated\n"); + return WOLFSPDM_SUCCESS; +} + +/* --- Challenge Authentication (DSP0274 Section 10.8) --- */ + +#ifndef NO_WOLFSPDM_CHALLENGE + +int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + int slotId, byte measHashType) +{ + word32 offset = 0; + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 36); + + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_CHALLENGE; + buf[offset++] = (byte)(slotId & 0x0F); + buf[offset++] = measHashType; + + /* Save measHashType for ParseChallengeAuth */ + ctx->challengeMeasHashType = measHashType; + + /* Nonce (32 bytes random) */ + rc = wolfSPDM_GetRandom(ctx, &buf[offset], 32); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + XMEMCPY(ctx->challengeNonce, &buf[offset], 32); + offset += 32; + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, word32* sigOffset) +{ + word32 offset; + word16 opaqueLen; + + if (ctx == NULL || buf == NULL || sigOffset == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Minimum size: 4 hdr + 48 certChainHash + 48 nonce + 48 measSummary + * + 2 opaqueLen + 96 sig = 246 bytes (with meas hash) */ + if (bufSz < 4) { + return WOLFSPDM_E_CHALLENGE; + } + + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CHALLENGE_AUTH, WOLFSPDM_E_CHALLENGE); + + offset = 4; + + /* CertChainHash (H bytes, 48 for SHA-384) */ + if (offset + WOLFSPDM_HASH_SIZE > bufSz) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: too short for CertChainHash\n"); + return WOLFSPDM_E_CHALLENGE; + } + /* Verify cert chain hash matches what we computed */ + if (XMEMCMP(&buf[offset], ctx->certChainHash, WOLFSPDM_HASH_SIZE) != 0) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: CertChainHash mismatch\n"); + return WOLFSPDM_E_CHALLENGE; + } + offset += WOLFSPDM_HASH_SIZE; + + /* Nonce (32 bytes per DSP0274) */ + if (offset + 32 > bufSz) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: too short for Nonce\n"); + return WOLFSPDM_E_CHALLENGE; + } + offset += 32; + + /* MeasurementSummaryHash (H bytes if requested, 0 bytes if type=NONE) */ + if (ctx->challengeMeasHashType != SPDM_MEAS_SUMMARY_HASH_NONE) { + if (offset + WOLFSPDM_HASH_SIZE > bufSz) { + wolfSPDM_DebugPrint(ctx, + "CHALLENGE_AUTH: too short for MeasurementSummaryHash\n"); + return WOLFSPDM_E_CHALLENGE; + } + offset += WOLFSPDM_HASH_SIZE; + } + + /* OpaqueDataLength (2 LE) */ + if (offset + 2 > bufSz) { + return WOLFSPDM_E_CHALLENGE; + } + opaqueLen = SPDM_Get16LE(&buf[offset]); + offset += 2; + + /* Skip opaque data */ + if (offset + opaqueLen > bufSz) { + return WOLFSPDM_E_CHALLENGE; + } + offset += opaqueLen; + + /* Signature starts here */ + if (offset + WOLFSPDM_ECC_SIG_SIZE > bufSz) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: no room for signature\n"); + return WOLFSPDM_E_CHALLENGE; + } + + *sigOffset = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_VerifyChallengeAuthSig(WOLFSPDM_CTX* ctx, + const byte* rspBuf, word32 rspBufSz, + const byte* reqMsg, word32 reqMsgSz, word32 sigOffset) +{ + byte digest[WOLFSPDM_HASH_SIZE]; + int rc; + + (void)rspBufSz; + + if (ctx == NULL || rspBuf == NULL || reqMsg == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->hasResponderPubKey) { + return WOLFSPDM_E_CHALLENGE; + } + + /* Build M1/M2 hash per DSP0274 Section 10.8.3: + * A+B are already accumulated in ctx->m1m2Hash. Now add C and finalize. */ + if (!ctx->m1m2HashInit) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE: M1/M2 hash not initialized\n"); + return WOLFSPDM_E_CHALLENGE; + } + + /* Add C: CHALLENGE request + CHALLENGE_AUTH response (before sig) */ + rc = wc_Sha384Update(&ctx->m1m2Hash, reqMsg, reqMsgSz); + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + rc = wc_Sha384Update(&ctx->m1m2Hash, rspBuf, sigOffset); + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + + /* Finalize M1/M2 hash */ + rc = wc_Sha384Final(&ctx->m1m2Hash, digest); + ctx->m1m2HashInit = 0; /* Hash consumed */ + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + + /* Build M = prefix || zero_pad || context_str || hash, then hash it */ + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-challenge_auth signing", 32, digest, digest); + if (rc != WOLFSPDM_SUCCESS) return rc; + + /* Verify ECDSA signature (raw r||s format) */ + rc = wolfSPDM_VerifyEccSig(ctx, rspBuf + sigOffset, WOLFSPDM_ECC_SIG_SIZE, + digest, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH signature VERIFIED\n"); + return WOLFSPDM_SUCCESS; + } + + wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH signature INVALID\n"); + return WOLFSPDM_E_CHALLENGE; +} + +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* --- Heartbeat (DSP0274 Section 10.10) --- */ + +int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + return wolfSPDM_BuildSimpleMsg(ctx, SPDM_HEARTBEAT, buf, bufSz); +} + +int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_HEARTBEAT_ACK, WOLFSPDM_E_BAD_STATE); + + wolfSPDM_DebugPrint(ctx, "HEARTBEAT_ACK received\n"); + return WOLFSPDM_SUCCESS; +} + +/* --- Key Update (DSP0274 Section 10.9) --- */ + +int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte* tag) +{ + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 4); + if (tag == NULL) + return WOLFSPDM_E_INVALID_ARG; + + /* Generate random tag for request/response matching */ + rc = wolfSPDM_GetRandom(ctx, tag, 1); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_KEY_UPDATE; + buf[2] = operation; + buf[3] = *tag; + *bufSz = 4; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, byte operation, byte tag) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_UPDATE_ACK, WOLFSPDM_E_KEY_UPDATE); + + /* Verify echoed operation and tag */ + if (buf[2] != operation) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK: operation mismatch: 0x%02x != 0x%02x\n", + buf[2], operation); + return WOLFSPDM_E_KEY_UPDATE; + } + + if (buf[3] != tag) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK: tag mismatch: 0x%02x != 0x%02x\n", + buf[3], tag); + return WOLFSPDM_E_KEY_UPDATE; + } + + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK received\n"); + return WOLFSPDM_SUCCESS; +} diff --git a/src/spdm_nuvoton.c b/src/spdm_nuvoton.c index c61029c..6738151 100644 --- a/src/spdm_nuvoton.c +++ b/src/spdm_nuvoton.c @@ -36,77 +36,15 @@ #include #include -/* ========================================================================== - * Internal Byte-Order Helpers - * ========================================================================== */ - -/* Store a 16-bit value in big-endian format */ -static void SPDM_Set16BE(byte* buf, word16 val) -{ - buf[0] = (byte)(val >> 8); - buf[1] = (byte)(val & 0xFF); -} - -/* Read a 16-bit value from big-endian format */ -static word16 SPDM_Get16BE(const byte* buf) -{ - return (word16)((buf[0] << 8) | buf[1]); -} - -/* Store a 16-bit value in little-endian format */ -static void SPDM_Set16LE(byte* buf, word16 val) -{ - buf[0] = (byte)(val & 0xFF); - buf[1] = (byte)(val >> 8); -} - -/* Read a 16-bit value from little-endian format */ -static word16 SPDM_Get16LE(const byte* buf) -{ - return (word16)(buf[0] | (buf[1] << 8)); -} - -/* Store a 32-bit value in big-endian format */ -static void SPDM_Set32BE(byte* buf, word32 val) -{ - buf[0] = (byte)(val >> 24); - buf[1] = (byte)(val >> 16); - buf[2] = (byte)(val >> 8); - buf[3] = (byte)(val & 0xFF); -} - -/* Read a 32-bit value from big-endian format */ -static word32 SPDM_Get32BE(const byte* buf) -{ - return ((word32)buf[0] << 24) | ((word32)buf[1] << 16) | - ((word32)buf[2] << 8) | (word32)buf[3]; -} - -/* Store a 64-bit value in little-endian format */ -static void SPDM_Set64LE(byte* buf, word64 val) -{ - buf[0] = (byte)(val & 0xFF); - buf[1] = (byte)((val >> 8) & 0xFF); - buf[2] = (byte)((val >> 16) & 0xFF); - buf[3] = (byte)((val >> 24) & 0xFF); - buf[4] = (byte)((val >> 32) & 0xFF); - buf[5] = (byte)((val >> 40) & 0xFF); - buf[6] = (byte)((val >> 48) & 0xFF); - buf[7] = (byte)((val >> 56) & 0xFF); -} - -/* Read a 64-bit value from little-endian format */ -static word64 SPDM_Get64LE(const byte* buf) -{ - return (word64)buf[0] | ((word64)buf[1] << 8) | - ((word64)buf[2] << 16) | ((word64)buf[3] << 24) | - ((word64)buf[4] << 32) | ((word64)buf[5] << 40) | - ((word64)buf[6] << 48) | ((word64)buf[7] << 56); -} +/* Check for SPDM ERROR in response payload */ +#define SPDM_CHECK_ERROR_RSP(ctx, buf, sz, label) \ + if ((sz) >= 4 && (buf)[1] == SPDM_ERROR) { \ + wolfSPDM_DebugPrint(ctx, label ": SPDM ERROR 0x%02x 0x%02x\n", \ + (buf)[2], (buf)[3]); \ + return WOLFSPDM_E_PEER_ERROR; \ + } -/* ========================================================================== - * TCG SPDM Binding Message Framing - * ========================================================================== */ +/* --- TCG SPDM Binding Message Framing --- */ int wolfSPDM_BuildTcgClearMessage( WOLFSPDM_CTX* ctx, @@ -341,9 +279,7 @@ int wolfSPDM_ParseTcgSecuredMessage( return (int)payloadSz; } -/* ========================================================================== - * SPDM Vendor Defined Message Helpers - * ========================================================================== */ +/* --- SPDM Vendor Defined Message Helpers --- */ /* SPDM message codes */ #define SPDM_VERSION_1_3 0x13 @@ -465,9 +401,7 @@ int wolfSPDM_ParseVendorDefined( return (int)dataLen; } -/* ========================================================================== - * Nuvoton-Specific SPDM Functions - * ========================================================================== */ +/* --- Nuvoton-Specific SPDM Functions --- */ /* Helper: Send TCG clear message and receive response */ static int wolfSPDM_Nuvoton_SendClear( @@ -558,12 +492,7 @@ int wolfSPDM_Nuvoton_GetPubKey( return rc; } - /* Check for SPDM ERROR response */ - if (spdmPayloadSz >= 4 && spdmPayload[1] == SPDM_ERROR) { - wolfSPDM_DebugPrint(ctx, "GET_PUBK: SPDM ERROR 0x%02x 0x%02x\n", - spdmPayload[2], spdmPayload[3]); - return WOLFSPDM_E_PEER_ERROR; - } + SPDM_CHECK_ERROR_RSP(ctx, spdmPayload, spdmPayloadSz, "GET_PUBK"); /* Parse vendor-defined response */ rspPayloadSz = sizeof(rspPayload); @@ -608,10 +537,6 @@ int wolfSPDM_Nuvoton_GivePubKey( int rc; byte spdmMsg[256]; int spdmMsgSz; - byte encBuf[WOLFSPDM_MAX_MSG_SIZE]; - word32 encSz; - byte rxBuf[512]; - word32 rxSz; byte decBuf[256]; word32 decSz; @@ -633,63 +558,18 @@ int wolfSPDM_Nuvoton_GivePubKey( } /* GIVE_PUB is sent as a SECURED (encrypted) message per Nuvoton spec Rev 1.11. - * Section 4.2.4 shows GIVE_PUB_KEY uses tag 0x8201 (secured), not 0x8101 (clear). - * - * Note: GIVE_PUB is an application-phase vendor command, NOT part of the - * SPDM handshake transcript. TH2 only includes handshake messages. */ - - encSz = sizeof(encBuf); - rc = wolfSPDM_EncryptMessage(ctx, spdmMsg, (word32)spdmMsgSz, - encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Encrypt failed %d\n", rc); - return rc; - } - - /* Send encrypted message */ - if (ctx->ioCb == NULL) { - return WOLFSPDM_E_IO_FAIL; - } - - rxSz = sizeof(rxBuf); - rc = ctx->ioCb(ctx, encBuf, encSz, rxBuf, &rxSz, ctx->ioUserCtx); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "GIVE_PUB: I/O failed %d\n", rc); - return WOLFSPDM_E_IO_FAIL; - } - - /* Check if response is unencrypted SPDM message (likely an error). - * SPDM messages start with version byte (0x10-0x1F). - * Encrypted records start with session ID (first byte is low byte of reqSessionId). */ - if (rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { - /* Unencrypted SPDM message - check if it's an error */ - if (rxBuf[1] == SPDM_ERROR) { - wolfSPDM_DebugPrint(ctx, "GIVE_PUB: TPM returned unencrypted SPDM ERROR 0x%02x 0x%02x\n", - (rxSz >= 3) ? rxBuf[2] : 0, (rxSz >= 4) ? rxBuf[3] : 0); - return WOLFSPDM_E_PEER_ERROR; - } - wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Unexpected unencrypted response code 0x%02x\n", - rxBuf[1]); - return WOLFSPDM_E_PEER_ERROR; - } - - /* Decrypt response (encrypted record format) */ + * Section 4.2.4 shows GIVE_PUB_KEY uses tag 0x8201 (secured), not 0x8101 (clear). */ decSz = sizeof(decBuf); - rc = wolfSPDM_DecryptMessage(ctx, rxBuf, rxSz, decBuf, &decSz); + rc = wolfSPDM_SecuredExchange(ctx, spdmMsg, (word32)spdmMsgSz, + decBuf, &decSz); if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Decrypt failed %d\n", rc); + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: SecuredExchange failed %d\n", rc); return rc; } - /* Check for SPDM ERROR response in decrypted payload */ - if (decSz >= 4 && decBuf[1] == SPDM_ERROR) { - wolfSPDM_DebugPrint(ctx, "GIVE_PUB: SPDM ERROR 0x%02x 0x%02x\n", - decBuf[2], decBuf[3]); - return WOLFSPDM_E_PEER_ERROR; - } + SPDM_CHECK_ERROR_RSP(ctx, decBuf, decSz, "GIVE_PUB"); wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Success\n"); - return WOLFSPDM_SUCCESS; } @@ -740,12 +620,7 @@ int wolfSPDM_Nuvoton_GetStatus( return rc; } - /* Check for SPDM ERROR response */ - if (spdmPayloadSz >= 4 && spdmPayload[1] == SPDM_ERROR) { - wolfSPDM_DebugPrint(ctx, "GET_STS_: SPDM ERROR 0x%02x 0x%02x\n", - spdmPayload[2], spdmPayload[3]); - return WOLFSPDM_E_PEER_ERROR; - } + SPDM_CHECK_ERROR_RSP(ctx, spdmPayload, spdmPayloadSz, "GET_STS_"); /* Parse vendor-defined response */ rspPayloadSz = sizeof(rspPayload); @@ -798,10 +673,6 @@ int wolfSPDM_Nuvoton_SetOnlyMode( int rc; byte spdmMsg[256]; int spdmMsgSz; - byte encBuf[WOLFSPDM_MAX_MSG_SIZE]; - word32 encSz; - byte rxBuf[512]; - word32 rxSz; byte decBuf[256]; word32 decSz; byte param[1]; @@ -826,47 +697,21 @@ int wolfSPDM_Nuvoton_SetOnlyMode( return spdmMsgSz; } - /* Encrypt the message */ - encSz = sizeof(encBuf); - rc = wolfSPDM_EncryptMessage(ctx, spdmMsg, (word32)spdmMsgSz, - encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Send encrypted message */ - if (ctx->ioCb == NULL) { - return WOLFSPDM_E_IO_FAIL; - } - - rxSz = sizeof(rxBuf); - rc = ctx->ioCb(ctx, encBuf, encSz, rxBuf, &rxSz, ctx->ioUserCtx); - if (rc != 0) { - return WOLFSPDM_E_IO_FAIL; - } - - /* Decrypt response */ + /* Send encrypted via SecuredExchange */ decSz = sizeof(decBuf); - rc = wolfSPDM_DecryptMessage(ctx, rxBuf, rxSz, decBuf, &decSz); + rc = wolfSPDM_SecuredExchange(ctx, spdmMsg, (word32)spdmMsgSz, + decBuf, &decSz); if (rc != WOLFSPDM_SUCCESS) { return rc; } - /* Check for SPDM ERROR response */ - if (decSz >= 4 && decBuf[1] == SPDM_ERROR) { - wolfSPDM_DebugPrint(ctx, "SPDMONLY: SPDM ERROR 0x%02x 0x%02x\n", - decBuf[2], decBuf[3]); - return WOLFSPDM_E_PEER_ERROR; - } + SPDM_CHECK_ERROR_RSP(ctx, decBuf, decSz, "SPDMONLY"); wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success\n"); - return WOLFSPDM_SUCCESS; } -/* ========================================================================== - * Nuvoton SPDM Connection Flow - * ========================================================================== */ +/* --- Nuvoton SPDM Connection Flow --- */ /* Nuvoton-specific connection flow: * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH @@ -901,16 +746,9 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) ctx->state = WOLFSPDM_STATE_INIT; wolfSPDM_TranscriptReset(ctx); - /* Step 1: GET_VERSION / VERSION - * Note: For Nuvoton, GET_VERSION uses TCG binding header - * but the message parsing is the same as standard SPDM */ - wolfSPDM_DebugPrint(ctx, "Nuvoton Step 1: GET_VERSION\n"); - rc = wolfSPDM_GetVersion(ctx); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "GET_VERSION failed: %d\n", rc); - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } + /* Step 1: GET_VERSION / VERSION */ + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); /* Step 2: GET_PUBK (Nuvoton vendor command) * Gets the TPM's SPDM-Identity public key (TPMT_PUBLIC format) */ @@ -928,38 +766,16 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) * For Nuvoton, the cert_chain_buffer_hash is SHA-384(TPMT_PUBLIC) * instead of the standard certificate chain hash */ if (ctx->hasRspPubKey && ctx->rspPubKeyLen > 0) { - wc_Sha384 sha; - wolfSPDM_DebugPrint(ctx, "Nuvoton: Computing Ct = SHA-384(TPMT_PUBLIC[%u])\n", ctx->rspPubKeyLen); - - rc = wc_InitSha384(&sha); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Nuvoton: SHA-384 init failed\n"); - ctx->state = WOLFSPDM_STATE_ERROR; - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_Sha384Update(&sha, ctx->rspPubKey, ctx->rspPubKeyLen); - if (rc != 0) { - wc_Sha384Free(&sha); - wolfSPDM_DebugPrint(ctx, "Nuvoton: SHA-384 update failed\n"); - ctx->state = WOLFSPDM_STATE_ERROR; - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_Sha384Final(&sha, ctx->certChainHash); - wc_Sha384Free(&sha); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Nuvoton: SHA-384 final failed\n"); + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, + ctx->rspPubKey, ctx->rspPubKeyLen, NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_ERROR; - return WOLFSPDM_E_CRYPTO_FAIL; + return rc; } - - /* Add Ct to transcript */ rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, WOLFSPDM_HASH_SIZE); if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "Nuvoton: Failed to add Ct to transcript\n"); ctx->state = WOLFSPDM_STATE_ERROR; return rc; } @@ -968,14 +784,8 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) wolfSPDM_DebugPrint(ctx, "Nuvoton: Warning - no responder public key for Ct\n"); } - /* Step 3: KEY_EXCHANGE */ - wolfSPDM_DebugPrint(ctx, "Nuvoton Step 3: KEY_EXCHANGE\n"); - rc = wolfSPDM_KeyExchange(ctx); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE failed: %d\n", rc); - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 3: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); /* Step 4: GIVE_PUB (Nuvoton vendor command) - sent as SECURED message * Gives the host's SPDM-Identity public key to the TPM. @@ -996,15 +806,9 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB (skipped, no host key)\n"); } - /* Step 5: FINISH (first encrypted message) - * Completes the handshake with RequesterVerifyData */ - wolfSPDM_DebugPrint(ctx, "Nuvoton Step 5: FINISH\n"); - rc = wolfSPDM_Finish(ctx); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH failed: %d\n", rc); - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } + /* Step 5: FINISH */ + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 5: FINISH\n", + wolfSPDM_Finish(ctx)); ctx->state = WOLFSPDM_STATE_CONNECTED; wolfSPDM_DebugPrint(ctx, "Nuvoton: SPDM Session Established! " diff --git a/src/spdm_secured.c b/src/spdm_secured.c index f7f2bd3..e36034c 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -27,7 +27,7 @@ * * MCTP transport: * Header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes - * IV XOR: Rightmost 2 bytes (bytes 10-11) with 2-byte sequence number + * IV XOR: Leftmost 2 bytes (bytes 0-1) with 2-byte LE sequence number (DSP0277) * * Nuvoton TCG binding (Rev 1.11): * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes @@ -52,13 +52,9 @@ static int wolfSPDM_AesGcmSelfTest(WOLFSPDM_CTX* ctx) int rc; /* Build AAD matching what we'd use for SeqNum=0 */ - testAad[0] = (byte)(ctx->sessionId & 0xFF); - testAad[1] = (byte)((ctx->sessionId >> 8) & 0xFF); - testAad[2] = (byte)((ctx->sessionId >> 16) & 0xFF); - testAad[3] = (byte)((ctx->sessionId >> 24) & 0xFF); + SPDM_Set32LE(&testAad[0], ctx->sessionId); XMEMSET(&testAad[4], 0, 8); /* SeqNum = 0 */ - testAad[12] = (byte)((testPlainSz + 16) & 0xFF); - testAad[13] = (byte)(((testPlainSz + 16) >> 8) & 0xFF); + SPDM_Set16LE(&testAad[12], (word16)(testPlainSz + 16)); /* Encrypt */ rc = wc_AesGcmSetKey(&aesEnc, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); @@ -112,7 +108,6 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, word16 recordLen; word32 hdrSz; word32 aadSz; - word32 offset; int rc; if (ctx == NULL || plain == NULL || enc == NULL || encSz == NULL) { @@ -149,8 +144,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, } /* Build plaintext: AppDataLength(2 LE) || SPDM message || RandomData */ - plainBuf[0] = (byte)(appDataLen & 0xFF); - plainBuf[1] = (byte)((appDataLen >> 8) & 0xFF); + SPDM_Set16LE(plainBuf, appDataLen); XMEMCPY(&plainBuf[2], plain, plainSz); /* Fill RandomData with actual random bytes per Nuvoton spec */ if (padLen > 0) { @@ -165,24 +159,9 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, } /* Build header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes */ - offset = 0; - /* SessionID (4 bytes LE): ReqSessionId || RspSessionId */ - enc[offset++] = (byte)(ctx->sessionId & 0xFF); - enc[offset++] = (byte)((ctx->sessionId >> 8) & 0xFF); - enc[offset++] = (byte)((ctx->sessionId >> 16) & 0xFF); - enc[offset++] = (byte)((ctx->sessionId >> 24) & 0xFF); - /* SequenceNumber (8 bytes LE) - per Nuvoton spec */ - enc[offset++] = (byte)(ctx->reqSeqNum & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 8) & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 16) & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 24) & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 32) & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 40) & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 48) & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 56) & 0xFF); - /* Length (2 bytes LE) = encrypted payload + MAC */ - enc[offset++] = (byte)(recordLen & 0xFF); - enc[offset++] = (byte)((recordLen >> 8) & 0xFF); + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set64LE(&enc[4], ctx->reqSeqNum); + SPDM_Set16LE(&enc[12], recordLen); aadSz = 14; XMEMCPY(aad, enc, aadSz); @@ -207,51 +186,22 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, } /* Build plaintext: AppDataLen(2 LE) || MCTP header(0x05) || SPDM msg */ - plainBuf[0] = (byte)(appDataLen & 0xFF); - plainBuf[1] = (byte)((appDataLen >> 8) & 0xFF); + SPDM_Set16LE(plainBuf, appDataLen); plainBuf[2] = MCTP_MESSAGE_TYPE_SPDM; XMEMCPY(&plainBuf[3], plain, plainSz); /* Build header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) */ - offset = 0; - enc[offset++] = (byte)(ctx->sessionId & 0xFF); - enc[offset++] = (byte)((ctx->sessionId >> 8) & 0xFF); - enc[offset++] = (byte)((ctx->sessionId >> 16) & 0xFF); - enc[offset++] = (byte)((ctx->sessionId >> 24) & 0xFF); - enc[offset++] = (byte)(ctx->reqSeqNum & 0xFF); - enc[offset++] = (byte)((ctx->reqSeqNum >> 8) & 0xFF); - enc[offset++] = (byte)(recordLen & 0xFF); - enc[offset++] = (byte)((recordLen >> 8) & 0xFF); + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], (word16)ctx->reqSeqNum); + SPDM_Set16LE(&enc[6], recordLen); aadSz = 8; XMEMCPY(aad, enc, aadSz); } - /* Build IV: BaseIV XOR sequence number */ - XMEMCPY(iv, ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE); -#ifdef WOLFSPDM_NUVOTON - if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { - /* DSP0277 1.2 IV construction: - * Zero-extend 8-byte LE sequence number to iv_length (12 bytes), - * then XOR with base IV. Seq occupies leftmost bytes (0-7), - * zero-padding at bytes 8-11. - */ - iv[0] ^= (byte)(ctx->reqSeqNum & 0xFF); - iv[1] ^= (byte)((ctx->reqSeqNum >> 8) & 0xFF); - iv[2] ^= (byte)((ctx->reqSeqNum >> 16) & 0xFF); - iv[3] ^= (byte)((ctx->reqSeqNum >> 24) & 0xFF); - iv[4] ^= (byte)((ctx->reqSeqNum >> 32) & 0xFF); - iv[5] ^= (byte)((ctx->reqSeqNum >> 40) & 0xFF); - iv[6] ^= (byte)((ctx->reqSeqNum >> 48) & 0xFF); - iv[7] ^= (byte)((ctx->reqSeqNum >> 56) & 0xFF); - } - else -#endif - { - /* MCTP format: 2-byte sequence number XOR at bytes 10-11 (rightmost) */ - iv[10] ^= (byte)(ctx->reqSeqNum & 0xFF); - iv[11] ^= (byte)((ctx->reqSeqNum >> 8) & 0xFF); - } + /* Build IV: BaseIV XOR sequence number (DSP0277) */ + wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum, + ctx->mode == WOLFSPDM_MODE_NUVOTON); rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); if (rc != 0) { @@ -315,13 +265,9 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, } /* Parse header: SessionID(4) + SeqNum(8) + Length(2) */ - rspSessionId = (word32)enc[0] | ((word32)enc[1] << 8) | - ((word32)enc[2] << 16) | ((word32)enc[3] << 24); - rspSeqNum64 = (word64)enc[4] | ((word64)enc[5] << 8) | - ((word64)enc[6] << 16) | ((word64)enc[7] << 24) | - ((word64)enc[8] << 32) | ((word64)enc[9] << 40) | - ((word64)enc[10] << 48) | ((word64)enc[11] << 56); - rspLen = (word16)(enc[12] | (enc[13] << 8)); + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum64 = SPDM_Get64LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[12]); rspSeqNum = (word16)(rspSeqNum64 & 0xFFFF); /* For debug output */ if (rspSessionId != ctx->sessionId) { @@ -341,19 +287,8 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, XMEMCPY(aad, enc, aadSz); - /* DSP0277 1.2 IV construction: - * Zero-extend 8-byte LE sequence number to iv_length (12 bytes), - * then XOR with base IV. Seq occupies leftmost bytes (0-7). - * SeqNum bytes are at enc[4]-enc[11] in the record header. */ - XMEMCPY(iv, ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); - iv[0] ^= enc[4]; /* SeqNum byte 0 */ - iv[1] ^= enc[5]; /* SeqNum byte 1 */ - iv[2] ^= enc[6]; /* SeqNum byte 2 */ - iv[3] ^= enc[7]; /* SeqNum byte 3 */ - iv[4] ^= enc[8]; /* SeqNum byte 4 */ - iv[5] ^= enc[9]; /* SeqNum byte 5 */ - iv[6] ^= enc[10]; /* SeqNum byte 6 */ - iv[7] ^= enc[11]; /* SeqNum byte 7 */ + /* Build IV: BaseIV XOR sequence number (DSP0277 1.2) */ + wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64, 1); rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); if (rc != 0) { @@ -368,7 +303,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, } /* Parse decrypted: AppDataLen (2 LE) || SPDM message || RandomData */ - appDataLen = (word16)(decrypted[0] | (decrypted[1] << 8)); + appDataLen = SPDM_Get16LE(decrypted); if (cipherLen < (word32)(2 + appDataLen)) { return WOLFSPDM_E_BUFFER_SMALL; @@ -393,11 +328,10 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_BUFFER_SMALL; } - /* Parse header */ - rspSessionId = (word32)enc[0] | ((word32)enc[1] << 8) | - ((word32)enc[2] << 16) | ((word32)enc[3] << 24); - rspSeqNum = (word16)(enc[4] | (enc[5] << 8)); - rspLen = (word16)(enc[6] | (enc[7] << 8)); + /* Parse header: SessionID(4) + SeqNum(2) + Length(2) */ + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum = SPDM_Get16LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[6]); if (rspSessionId != ctx->sessionId) { wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", @@ -415,10 +349,8 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, XMEMCPY(aad, enc, aadSz); - /* Build IV: BaseIV XOR sequence number at bytes 10-11 (rightmost 2 bytes) */ - XMEMCPY(iv, ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); - iv[10] ^= (byte)(rspSeqNum & 0xFF); - iv[11] ^= (byte)((rspSeqNum >> 8) & 0xFF); + /* Build IV: BaseIV XOR sequence number (DSP0277) */ + wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum, 0); rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); if (rc != 0) { @@ -433,7 +365,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, } /* Parse decrypted: AppDataLen (2) || MCTP (1) || SPDM msg */ - appDataLen = (word16)(decrypted[0] | (decrypted[1] << 8)); + appDataLen = SPDM_Get16LE(decrypted); if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen)) { return WOLFSPDM_E_BUFFER_SMALL; @@ -456,6 +388,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } +#ifndef WOLFSPDM_LEAN int wolfSPDM_EncryptMessage(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz) @@ -489,6 +422,7 @@ int wolfSPDM_DecryptMessage(WOLFSPDM_CTX* ctx, return wolfSPDM_DecryptInternal(ctx, enc, encSz, plain, plainSz); } +#endif /* !WOLFSPDM_LEAN */ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, @@ -514,10 +448,87 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, return rc; } - rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); + return wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); +} + +/* --- Application Data Transfer --- */ + +#ifndef WOLFSPDM_LEAN +int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz) +{ + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; + word32 encSz = sizeof(encBuf); + int rc; + + if (ctx == NULL || data == NULL || dataSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED +#ifndef NO_WOLFSPDM_MEAS + && ctx->state != WOLFSPDM_STATE_MEASURED +#endif + ) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + /* Max payload: leave room for AEAD overhead */ + if (dataSz > WOLFSPDM_MAX_MSG_SIZE - 64) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Encrypt the application data */ + rc = wolfSPDM_EncryptInternal(ctx, data, dataSz, encBuf, &encSz); if (rc != WOLFSPDM_SUCCESS) { return rc; } + /* Send via I/O callback (no response expected for send-only) */ + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + { + byte rxBuf[16]; + word32 rxSz = sizeof(rxBuf); + rc = ctx->ioCb(ctx, encBuf, encSz, rxBuf, &rxSz, ctx->ioUserCtx); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + } + return WOLFSPDM_SUCCESS; } + +int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, word32* dataSz) +{ + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL || data == NULL || dataSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED +#ifndef NO_WOLFSPDM_MEAS + && ctx->state != WOLFSPDM_STATE_MEASURED +#endif + ) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + /* Receive via I/O callback (NULL tx to indicate receive-only) */ + rc = ctx->ioCb(ctx, NULL, 0, rxBuf, &rxSz, ctx->ioUserCtx); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + + /* Decrypt the received data */ + return wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, data, dataSz); +} +#endif /* !WOLFSPDM_LEAN */ diff --git a/src/spdm_session.c b/src/spdm_session.c index f5aa12e..ad4143a 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -94,6 +94,29 @@ int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + /* Save VCA transcript length (GET_VERSION through ALGORITHMS). + * Used by measurement signature verification per DSP0274. */ + ctx->vcaLen = ctx->transcriptLen; + +#ifndef NO_WOLFSPDM_CHALLENGE + /* Initialize M1/M2 running hash for potential CHALLENGE auth. + * Start with VCA (A portion of the M1/M2 transcript per DSP0274). */ + { + int hashRc = wc_InitSha384(&ctx->m1m2Hash); + if (hashRc == 0) { + hashRc = wc_Sha384Update(&ctx->m1m2Hash, ctx->transcript, + ctx->vcaLen); + if (hashRc == 0) { + ctx->m1m2HashInit = 1; + } + else { + wc_Sha384Free(&ctx->m1m2Hash); + } + } + /* Non-fatal: challenge just won't work if this fails */ + } +#endif + return wolfSPDM_ParseAlgorithms(ctx, rxBuf, rxSz); } @@ -110,12 +133,21 @@ int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) return rc; } - /* Note: GET_DIGESTS/DIGESTS are NOT added to transcript for TH1 per libspdm */ + /* Note: GET_DIGESTS/DIGESTS are NOT added to main transcript for TH1, + * but ARE needed for CHALLENGE M1/M2 (the "B" portion per DSP0274). */ rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); if (rc != WOLFSPDM_SUCCESS) { return rc; } +#ifndef NO_WOLFSPDM_CHALLENGE + /* Feed GET_DIGESTS request + DIGESTS response to M1/M2 challenge hash */ + if (ctx->m1m2HashInit) { + wc_Sha384Update(&ctx->m1m2Hash, txBuf, txSz); + wc_Sha384Update(&ctx->m1m2Hash, rxBuf, rxSz); + } +#endif + return wolfSPDM_ParseDigests(ctx, rxBuf, rxSz); } @@ -143,6 +175,14 @@ int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId) return rc; } +#ifndef NO_WOLFSPDM_CHALLENGE + /* Feed each GET_CERTIFICATE/CERTIFICATE chunk to M1/M2 challenge hash */ + if (ctx->m1m2HashInit) { + wc_Sha384Update(&ctx->m1m2Hash, txBuf, txSz); + wc_Sha384Update(&ctx->m1m2Hash, rxBuf, rxSz); + } +#endif + rc = wolfSPDM_ParseCertificate(ctx, rxBuf, rxSz, &portionLen, &remainderLen); if (rc != WOLFSPDM_SUCCESS) { return rc; @@ -159,7 +199,23 @@ int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId) return rc; } - return wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Auto-extract responder public key from leaf cert. + * Needed by both measurement signature verification and challenge auth. + * Non-fatal: caller can still proceed, but signature ops will fail. */ + if (!ctx->hasResponderPubKey) { + int keyRc = wolfSPDM_ExtractResponderPubKey(ctx); + if (keyRc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, + "Warning: Could not extract responder public key (%d)\n", keyRc); + } + } + + return WOLFSPDM_SUCCESS; } int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) @@ -225,20 +281,11 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) if (rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { /* Unencrypted SPDM message - check for ERROR */ if (rxBuf[1] == 0x7F) { /* SPDM_ERROR */ - wolfSPDM_DebugPrint(ctx, "FINISH: TPM returned unencrypted SPDM ERROR!\n"); - wolfSPDM_DebugPrint(ctx, " Error code: 0x%02x (%s)\n", rxBuf[2], - rxBuf[2] == SPDM_ERROR_INVALID_REQUEST ? "InvalidRequest" : - rxBuf[2] == SPDM_ERROR_BUSY ? "Busy" : - rxBuf[2] == SPDM_ERROR_UNEXPECTED_REQUEST ? "UnexpectedRequest" : - rxBuf[2] == SPDM_ERROR_UNSPECIFIED ? "Unspecified" : - rxBuf[2] == SPDM_ERROR_DECRYPT_ERROR ? "DecryptError" : - rxBuf[2] == SPDM_ERROR_UNSUPPORTED_REQUEST ? "UnsupportedRequest" : - rxBuf[2] == SPDM_ERROR_MAJOR_VERSION_MISMATCH ? "VersionMismatch" : "Unknown"); - wolfSPDM_DebugPrint(ctx, " Error data: 0x%02x\n", (rxSz >= 4) ? rxBuf[3] : 0); - + wolfSPDM_DebugPrint(ctx, "FINISH: peer returned SPDM ERROR 0x%02x\n", + rxBuf[2]); return WOLFSPDM_E_PEER_ERROR; } - wolfSPDM_DebugPrint(ctx, "FINISH: Unexpected unencrypted response code 0x%02x\n", + wolfSPDM_DebugPrint(ctx, "FINISH: unexpected response code 0x%02x\n", rxBuf[1]); return WOLFSPDM_E_PEER_ERROR; } @@ -246,13 +293,11 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); if (rc != WOLFSPDM_SUCCESS) { wolfSPDM_DebugPrint(ctx, "FINISH decrypt failed: %d\n", rc); - return rc; } rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); if (rc != WOLFSPDM_SUCCESS) { - return rc; } @@ -260,9 +305,295 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) rc = wolfSPDM_DeriveAppDataKeys(ctx); if (rc != WOLFSPDM_SUCCESS) { wolfSPDM_DebugPrint(ctx, "App data key derivation failed: %d\n", rc); + return rc; + } + return WOLFSPDM_SUCCESS; +} + +/* --- Measurements (Device Attestation) --- */ + +#ifndef NO_WOLFSPDM_MEAS + +int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, + int requestSignature) +{ + byte txBuf[48]; /* GET_MEASUREMENTS: max 37 bytes (with sig request) */ + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Must be at least past algorithm negotiation */ + if (ctx->state < WOLFSPDM_STATE_ALGO) { + return WOLFSPDM_E_BAD_STATE; + } + + /* Build GET_MEASUREMENTS request */ + rc = wolfSPDM_BuildGetMeasurements(ctx, txBuf, &txSz, + measOperation, (byte)requestSignature); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + +#ifndef NO_WOLFSPDM_MEAS_VERIFY + /* Save request message for L1 transcript (signature verification) */ + if (txSz <= sizeof(ctx->measReqMsg)) { + XMEMCPY(ctx->measReqMsg, txBuf, txSz); + ctx->measReqMsgSz = txSz; + } +#endif + + /* Send/receive: use secured exchange if session established, else cleartext */ + if (ctx->state == WOLFSPDM_STATE_CONNECTED) { + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + else { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GET_MEASUREMENTS exchange failed: %d\n", rc); + return rc; + } + + /* Parse the response */ + rc = wolfSPDM_ParseMeasurements(ctx, rxBuf, rxSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + +#ifndef NO_WOLFSPDM_MEAS_VERIFY + /* Verify signature if requested and signature was captured */ + if (requestSignature && ctx->measSignatureSize > 0) { + if (!ctx->hasResponderPubKey) { + wolfSPDM_DebugPrint(ctx, + "No responder public key — cannot verify signature\n"); + return WOLFSPDM_E_MEAS_NOT_VERIFIED; + } + + rc = wolfSPDM_VerifyMeasurementSig(ctx, rxBuf, rxSz, + ctx->measReqMsg, ctx->measReqMsgSz); + if (rc != WOLFSPDM_SUCCESS) { + return WOLFSPDM_E_MEAS_SIG_FAIL; + } + + ctx->state = WOLFSPDM_STATE_MEASURED; + return WOLFSPDM_SUCCESS; /* Verified! */ + } +#else + (void)requestSignature; +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ + + /* No signature requested or verification not compiled in */ + ctx->state = WOLFSPDM_STATE_MEASURED; + return WOLFSPDM_E_MEAS_NOT_VERIFIED; +} + +#endif /* !NO_WOLFSPDM_MEAS */ + +/* --- Challenge Authentication (Sessionless Attestation) --- */ + +#ifndef NO_WOLFSPDM_CHALLENGE + +int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType) +{ + byte txBuf[48]; /* CHALLENGE: 36 bytes */ + byte rxBuf[512]; /* CHALLENGE_AUTH: variable, up to ~300+ bytes */ + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + word32 sigOffset = 0; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Need cert chain for signature verification */ + if (ctx->state < WOLFSPDM_STATE_CERT) { + return WOLFSPDM_E_BAD_STATE; + } + + if (!ctx->hasResponderPubKey) { + wolfSPDM_DebugPrint(ctx, + "CHALLENGE: No responder public key for verification\n"); + return WOLFSPDM_E_CHALLENGE; + } + + /* Build CHALLENGE request */ + rc = wolfSPDM_BuildChallenge(ctx, txBuf, &txSz, slotId, measHashType); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "Sending CHALLENGE (slot=%d, measHash=0x%02x)\n", + slotId, measHashType); + + /* Cleartext exchange (no session needed) */ + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "CHALLENGE: SendReceive failed: %d\n", rc); + return rc; + } + + /* Parse CHALLENGE_AUTH response */ + rc = wolfSPDM_ParseChallengeAuth(ctx, rxBuf, rxSz, &sigOffset); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Verify signature */ + rc = wolfSPDM_VerifyChallengeAuthSig(ctx, rxBuf, rxSz, + txBuf, txSz, sigOffset); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "CHALLENGE authentication PASSED\n"); + return WOLFSPDM_SUCCESS; +} + +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* --- Heartbeat (Session Keep-Alive) --- */ + +int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx) +{ + byte txBuf[8]; + byte rxBuf[32]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED +#ifndef NO_WOLFSPDM_MEAS + && ctx->state != WOLFSPDM_STATE_MEASURED +#endif + ) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + rc = wolfSPDM_BuildHeartbeat(ctx, txBuf, &txSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Must be sent over encrypted channel */ + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "HEARTBEAT: SecuredExchange failed: %d\n", rc); + return rc; + } + + return wolfSPDM_ParseHeartbeatAck(ctx, rxBuf, rxSz); +} + +/* --- Key Update (Session Key Rotation) --- */ + +int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) +{ + byte txBuf[8]; + byte rxBuf[32]; + word32 txSz, rxSz; + byte tag, tag2; + byte operation; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->state != WOLFSPDM_STATE_CONNECTED +#ifndef NO_WOLFSPDM_MEAS + && ctx->state != WOLFSPDM_STATE_MEASURED +#endif + ) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + operation = updateAll ? SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS + : SPDM_KEY_UPDATE_OP_UPDATE_KEY; + + /* Step 1: Send KEY_UPDATE encrypted with CURRENT req key */ + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildKeyUpdate(ctx, txBuf, &txSz, operation, &tag); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "Sending KEY_UPDATE\n"); + + { + byte encBuf[64]; + byte rawRxBuf[64]; + word32 encSz = sizeof(encBuf); + word32 rawRxSz = sizeof(rawRxBuf); + + /* Encrypt with current req key */ + rc = wolfSPDM_EncryptInternal(ctx, txBuf, txSz, encBuf, &encSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Send and receive raw (don't decrypt yet) */ + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rawRxBuf, &rawRxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: SendReceive failed: %d\n", rc); + return rc; + } + + /* Step 2: Derive new keys BEFORE decrypting ACK. + * The responder derives new keys upon receiving KEY_UPDATE and + * encrypts the ACK with the NEW response key. */ + rc = wolfSPDM_DeriveUpdatedKeys(ctx, updateAll); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: DeriveUpdatedKeys failed: %d\n", rc); + return rc; + } + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + + /* Decrypt ACK with new rsp key */ + rxSz = sizeof(rxBuf); + rc = wolfSPDM_DecryptInternal(ctx, rawRxBuf, rawRxSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: ACK decrypt failed: %d\n", rc); + return rc; + } + } + + rc = wolfSPDM_ParseKeyUpdateAck(ctx, rxBuf, rxSz, operation, tag); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Step 3: Verify new key works (send VERIFY_NEW_KEY with new keys) */ + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildKeyUpdate(ctx, txBuf, &txSz, + SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, &tag2); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: VerifyNewKey exchange failed: %d\n", rc); + return rc; + } + + rc = wolfSPDM_ParseKeyUpdateAck(ctx, rxBuf, rxSz, + SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, tag2); + if (rc != WOLFSPDM_SUCCESS) { return rc; } + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE completed, new keys active\n"); return WOLFSPDM_SUCCESS; } diff --git a/src/spdm_transcript.c b/src/spdm_transcript.c index 3f0804b..0bf422c 100644 --- a/src/spdm_transcript.c +++ b/src/spdm_transcript.c @@ -22,17 +22,11 @@ #include "spdm_internal.h" #include -/* ========================================================================== - * Transcript Management - * - * SPDM uses transcript hashing for key derivation: - * VCA = GET_VERSION || VERSION || GET_CAPS || CAPS || NEG_ALGO || ALGO - * Ct = Hash(certificate_chain) - * TH1 = Hash(VCA || Ct || KEY_EXCHANGE || KEY_EXCHANGE_RSP_partial || Signature) - * TH2 = Hash(VCA || Ct || message_k || FINISH_header) - * - * where message_k = KEY_EXCHANGE || KEY_EXCHANGE_RSP_partial || Signature || ResponderVerifyData - * ========================================================================== */ +/* --- Transcript Management --- + * VCA = GET_VERSION || VERSION || GET_CAPS || CAPS || NEG_ALGO || ALGO + * Ct = Hash(certificate_chain) + * TH1 = Hash(VCA || Ct || KEY_EXCHANGE || KEY_EXCHANGE_RSP_partial || Signature) + * TH2 = Hash(VCA || Ct || message_k || FINISH_header) */ void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx) { @@ -86,70 +80,53 @@ int wolfSPDM_CertChainAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len) return WOLFSPDM_SUCCESS; } -int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) +int wolfSPDM_Sha384Hash(byte* out, + const byte* d1, word32 d1Sz, + const byte* d2, word32 d2Sz, + const byte* d3, word32 d3Sz) { wc_Sha384 sha; int rc; - if (ctx == NULL || hash == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - rc = wc_InitSha384(&sha); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + if (d1 != NULL && d1Sz > 0) { + rc = wc_Sha384Update(&sha, d1, d1Sz); + if (rc != 0) { wc_Sha384Free(&sha); return WOLFSPDM_E_CRYPTO_FAIL; } } - - rc = wc_Sha384Update(&sha, ctx->transcript, ctx->transcriptLen); - if (rc != 0) { - wc_Sha384Free(&sha); - return WOLFSPDM_E_CRYPTO_FAIL; + if (d2 != NULL && d2Sz > 0) { + rc = wc_Sha384Update(&sha, d2, d2Sz); + if (rc != 0) { wc_Sha384Free(&sha); return WOLFSPDM_E_CRYPTO_FAIL; } } - - rc = wc_Sha384Final(&sha, hash); + if (d3 != NULL && d3Sz > 0) { + rc = wc_Sha384Update(&sha, d3, d3Sz); + if (rc != 0) { wc_Sha384Free(&sha); return WOLFSPDM_E_CRYPTO_FAIL; } + } + rc = wc_Sha384Final(&sha, out); wc_Sha384Free(&sha); + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; +int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) +{ + if (ctx == NULL || hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; } - - return WOLFSPDM_SUCCESS; + return wolfSPDM_Sha384Hash(hash, ctx->transcript, ctx->transcriptLen, + NULL, 0, NULL, 0); } int wolfSPDM_ComputeCertChainHash(WOLFSPDM_CTX* ctx) { - wc_Sha384 sha; - int rc; - if (ctx == NULL) { return WOLFSPDM_E_INVALID_ARG; } - if (ctx->certChainLen == 0) { - /* No certificate chain - Ct is zeros */ XMEMSET(ctx->certChainHash, 0, sizeof(ctx->certChainHash)); return WOLFSPDM_SUCCESS; } - rc = wc_InitSha384(&sha); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_Sha384Update(&sha, ctx->certChain, ctx->certChainLen); - if (rc != 0) { - wc_Sha384Free(&sha); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_Sha384Final(&sha, ctx->certChainHash); - wc_Sha384Free(&sha); - - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - wolfSPDM_DebugPrint(ctx, "Ct = Hash(cert_chain[%u])\n", ctx->certChainLen); - - return WOLFSPDM_SUCCESS; + return wolfSPDM_Sha384Hash(ctx->certChainHash, + ctx->certChain, ctx->certChainLen, NULL, 0, NULL, 0); } diff --git a/test/unit_test.c b/test/unit_test.c index a7434e5..f13dd4a 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -31,6 +31,17 @@ static int g_testsFailed = 0; return 0; \ } while(0) +#define ASSERT_SUCCESS(expr) do { int _r = (expr); if (_r != 0) { \ + printf(" FAIL %s:%d: %s returned %d\n", __FILE__, __LINE__, #expr, _r); \ + g_testsFailed++; return -1; } } while(0) + +#define ASSERT_FAIL(expr) do { int _r = (expr); if (_r == 0) { \ + printf(" FAIL %s:%d: %s should have failed\n", __FILE__, __LINE__, #expr); \ + g_testsFailed++; return -1; } } while(0) + +#define ASSERT_EQ(a, b, msg) TEST_ASSERT((a) == (b), msg) +#define ASSERT_NE(a, b, msg) TEST_ASSERT((a) != (b), msg) + /* Dummy I/O callback for testing */ static int dummy_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) @@ -53,14 +64,11 @@ static int test_context_new_free(void) ctx = wolfSPDM_New(); TEST_ASSERT(ctx != NULL, "wolfSPDM_New returned NULL"); - - TEST_ASSERT(ctx->state == WOLFSPDM_STATE_INIT, "Initial state wrong"); - TEST_ASSERT(ctx->initialized == 1, "Should be initialized by New()"); + ASSERT_EQ(ctx->state, WOLFSPDM_STATE_INIT, "Initial state wrong"); + ASSERT_EQ(ctx->initialized, 1, "Should be initialized by New()"); wolfSPDM_Free(ctx); - - /* Free NULL should not crash */ - wolfSPDM_Free(NULL); + wolfSPDM_Free(NULL); /* Should not crash */ TEST_PASS(); } @@ -70,15 +78,13 @@ static int test_context_init(void) { WOLFSPDM_CTX ctxBuf; WOLFSPDM_CTX* ctx = &ctxBuf; - int rc; printf("test_context_init...\n"); - rc = wolfSPDM_Init(ctx); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "wolfSPDM_Init failed"); - TEST_ASSERT(ctx->initialized == 1, "Not marked initialized"); - TEST_ASSERT(ctx->rngInitialized == 1, "RNG not initialized"); - TEST_ASSERT(ctx->reqCaps == WOLFSPDM_DEFAULT_REQ_CAPS, "Default caps wrong"); + ASSERT_SUCCESS(wolfSPDM_Init(ctx)); + ASSERT_EQ(ctx->initialized, 1, "Not marked initialized"); + ASSERT_EQ(ctx->rngInitialized, 1, "RNG not initialized"); + ASSERT_EQ(ctx->reqCaps, WOLFSPDM_DEFAULT_REQ_CAPS, "Default caps wrong"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -88,23 +94,15 @@ static int test_context_static_alloc(void) { byte buffer[sizeof(WOLFSPDM_CTX) + 64]; WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)buffer; - int rc; printf("test_context_static_alloc...\n"); - TEST_ASSERT(wolfSPDM_GetCtxSize() == (int)sizeof(WOLFSPDM_CTX), - "GetCtxSize mismatch"); - - /* Too small buffer should fail */ - rc = wolfSPDM_InitStatic(ctx, 10); - TEST_ASSERT(rc == WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); - - rc = wolfSPDM_InitStatic(ctx, sizeof(buffer)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "InitStatic failed"); - TEST_ASSERT(ctx->initialized == 1, "Static ctx not initialized"); - - wolfSPDM_Free(ctx); /* Now safe — no XFREE on static ctx */ + ASSERT_EQ(wolfSPDM_GetCtxSize(), (int)sizeof(WOLFSPDM_CTX), "GetCtxSize mismatch"); + ASSERT_EQ(wolfSPDM_InitStatic(ctx, 10), WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + ASSERT_SUCCESS(wolfSPDM_InitStatic(ctx, sizeof(buffer))); + ASSERT_EQ(ctx->initialized, 1, "Static ctx not initialized"); + wolfSPDM_Free(ctx); TEST_PASS(); } @@ -112,22 +110,16 @@ static int test_context_set_io(void) { WOLFSPDM_CTX ctxBuf; WOLFSPDM_CTX* ctx = &ctxBuf; - int rc; int dummy = 42; printf("test_context_set_io...\n"); wolfSPDM_Init(ctx); - /* Dummy callback for testing */ - rc = wolfSPDM_SetIO(ctx, dummy_io_cb, &dummy); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "SetIO failed"); - TEST_ASSERT(ctx->ioCb == dummy_io_cb, "IO callback not set"); - TEST_ASSERT(ctx->ioUserCtx == &dummy, "User context not set"); - - /* NULL callback should fail */ - rc = wolfSPDM_SetIO(ctx, NULL, NULL); - TEST_ASSERT(rc == WOLFSPDM_E_INVALID_ARG, "NULL callback should fail"); + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, dummy_io_cb, &dummy)); + ASSERT_EQ(ctx->ioCb, dummy_io_cb, "IO callback not set"); + ASSERT_EQ(ctx->ioUserCtx, &dummy, "User context not set"); + ASSERT_EQ(wolfSPDM_SetIO(ctx, NULL, NULL), WOLFSPDM_E_INVALID_ARG, "NULL callback should fail"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -143,26 +135,22 @@ static int test_transcript_add_reset(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte data1[] = {0x01, 0x02, 0x03, 0x04}; byte data2[] = {0x05, 0x06, 0x07, 0x08}; - int rc; printf("test_transcript_add_reset...\n"); wolfSPDM_Init(ctx); + ASSERT_EQ(ctx->transcriptLen, 0, "Transcript should start empty"); - TEST_ASSERT(ctx->transcriptLen == 0, "Transcript should start empty"); - - rc = wolfSPDM_TranscriptAdd(ctx, data1, sizeof(data1)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TranscriptAdd failed"); - TEST_ASSERT(ctx->transcriptLen == 4, "Length should be 4"); - TEST_ASSERT(memcmp(ctx->transcript, data1, 4) == 0, "Data mismatch"); + ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, data1, sizeof(data1))); + ASSERT_EQ(ctx->transcriptLen, 4, "Length should be 4"); + ASSERT_EQ(memcmp(ctx->transcript, data1, 4), 0, "Data mismatch"); - rc = wolfSPDM_TranscriptAdd(ctx, data2, sizeof(data2)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "Second add failed"); - TEST_ASSERT(ctx->transcriptLen == 8, "Length should be 8"); - TEST_ASSERT(memcmp(ctx->transcript + 4, data2, 4) == 0, "Data2 mismatch"); + ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, data2, sizeof(data2))); + ASSERT_EQ(ctx->transcriptLen, 8, "Length should be 8"); + ASSERT_EQ(memcmp(ctx->transcript + 4, data2, 4), 0, "Data2 mismatch"); wolfSPDM_TranscriptReset(ctx); - TEST_ASSERT(ctx->transcriptLen == 0, "Reset should clear length"); + ASSERT_EQ(ctx->transcriptLen, 0, "Reset should clear length"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -174,23 +162,15 @@ static int test_transcript_hash(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte data[] = "test data for hashing"; byte hash[WOLFSPDM_HASH_SIZE]; - int rc; + byte zeros[WOLFSPDM_HASH_SIZE]; printf("test_transcript_hash...\n"); wolfSPDM_Init(ctx); - wolfSPDM_TranscriptAdd(ctx, data, sizeof(data) - 1); - - rc = wolfSPDM_TranscriptHash(ctx, hash); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TranscriptHash failed"); - - /* Verify hash is non-zero */ - int nonZero = 0; - for (int i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - if (hash[i] != 0) nonZero = 1; - } - TEST_ASSERT(nonZero, "Hash should be non-zero"); + ASSERT_SUCCESS(wolfSPDM_TranscriptHash(ctx, hash)); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(hash, zeros, WOLFSPDM_HASH_SIZE), 0, "Hash should be non-zero"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -201,25 +181,16 @@ static int test_certchain_hash(void) WOLFSPDM_CTX ctxBuf; WOLFSPDM_CTX* ctx = &ctxBuf; byte certData[] = {0x30, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; - int rc; + byte zeros[WOLFSPDM_HASH_SIZE]; printf("test_certchain_hash...\n"); wolfSPDM_Init(ctx); - - rc = wolfSPDM_CertChainAdd(ctx, certData, sizeof(certData)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "CertChainAdd failed"); - TEST_ASSERT(ctx->certChainLen == sizeof(certData), "CertChain len wrong"); - - rc = wolfSPDM_ComputeCertChainHash(ctx); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "ComputeCertChainHash failed"); - - /* Verify Ct is non-zero */ - int nonZero = 0; - for (int i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - if (ctx->certChainHash[i] != 0) nonZero = 1; - } - TEST_ASSERT(nonZero, "Ct should be non-zero"); + ASSERT_SUCCESS(wolfSPDM_CertChainAdd(ctx, certData, sizeof(certData))); + ASSERT_EQ(ctx->certChainLen, sizeof(certData), "CertChain len wrong"); + ASSERT_SUCCESS(wolfSPDM_ComputeCertChainHash(ctx)); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(ctx->certChainHash, zeros, WOLFSPDM_HASH_SIZE), 0, "Ct should be non-zero"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -234,21 +205,13 @@ static int test_random_generation(void) WOLFSPDM_CTX ctxBuf; WOLFSPDM_CTX* ctx = &ctxBuf; byte buf1[32], buf2[32]; - int rc; printf("test_random_generation...\n"); wolfSPDM_Init(ctx); - - rc = wolfSPDM_GetRandom(ctx, buf1, sizeof(buf1)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "GetRandom failed"); - - rc = wolfSPDM_GetRandom(ctx, buf2, sizeof(buf2)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "Second GetRandom failed"); - - /* Two random outputs should differ */ - TEST_ASSERT(memcmp(buf1, buf2, sizeof(buf1)) != 0, - "Random outputs should differ"); + ASSERT_SUCCESS(wolfSPDM_GetRandom(ctx, buf1, sizeof(buf1))); + ASSERT_SUCCESS(wolfSPDM_GetRandom(ctx, buf2, sizeof(buf2))); + ASSERT_NE(memcmp(buf1, buf2, sizeof(buf1)), 0, "Random outputs should differ"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -260,29 +223,20 @@ static int test_ephemeral_key_generation(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte pubKeyX[WOLFSPDM_ECC_KEY_SIZE]; byte pubKeyY[WOLFSPDM_ECC_KEY_SIZE]; + byte zeros[WOLFSPDM_ECC_KEY_SIZE]; word32 xSz = sizeof(pubKeyX); word32 ySz = sizeof(pubKeyY); - int rc; printf("test_ephemeral_key_generation...\n"); wolfSPDM_Init(ctx); - - rc = wolfSPDM_GenerateEphemeralKey(ctx); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "GenerateEphemeralKey failed"); - TEST_ASSERT(ctx->ephemeralKeyInitialized == 1, "Key not marked initialized"); - - rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, pubKeyY, &ySz); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "ExportEphemeralPubKey failed"); - TEST_ASSERT(xSz == WOLFSPDM_ECC_KEY_SIZE, "X coordinate wrong size"); - TEST_ASSERT(ySz == WOLFSPDM_ECC_KEY_SIZE, "Y coordinate wrong size"); - - /* Verify non-zero */ - int nonZero = 0; - for (word32 i = 0; i < xSz; i++) { - if (pubKeyX[i] != 0) nonZero = 1; - } - TEST_ASSERT(nonZero, "Public key X should be non-zero"); + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + ASSERT_EQ(ctx->ephemeralKeyInitialized, 1, "Key not marked initialized"); + ASSERT_SUCCESS(wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, pubKeyY, &ySz)); + ASSERT_EQ(xSz, WOLFSPDM_ECC_KEY_SIZE, "X coordinate wrong size"); + ASSERT_EQ(ySz, WOLFSPDM_ECC_KEY_SIZE, "Y coordinate wrong size"); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(pubKeyX, zeros, WOLFSPDM_ECC_KEY_SIZE), 0, "Public key X should be non-zero"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -297,24 +251,17 @@ static int test_hkdf_expand_label(void) byte secret[48]; byte output[32]; byte context[48]; - int rc; + byte zeros[32]; printf("test_hkdf_expand_label...\n"); memset(secret, 0x5A, sizeof(secret)); memset(context, 0x00, sizeof(context)); - rc = wolfSPDM_HkdfExpandLabel(0x13, secret, sizeof(secret), - SPDM_LABEL_KEY, context, sizeof(context), - output, sizeof(output)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "HkdfExpandLabel failed"); - - /* Verify non-zero output */ - int nonZero = 0; - for (int i = 0; i < 32; i++) { - if (output[i] != 0) nonZero = 1; - } - TEST_ASSERT(nonZero, "HKDF output should be non-zero"); + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(0x13, secret, sizeof(secret), + SPDM_LABEL_KEY, context, sizeof(context), output, sizeof(output))); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(output, zeros, sizeof(output)), 0, "HKDF output should be non-zero"); TEST_PASS(); } @@ -324,22 +271,16 @@ static int test_compute_verify_data(void) byte finishedKey[WOLFSPDM_HASH_SIZE]; byte thHash[WOLFSPDM_HASH_SIZE]; byte verifyData[WOLFSPDM_HASH_SIZE]; - int rc; + byte zeros[WOLFSPDM_HASH_SIZE]; printf("test_compute_verify_data...\n"); memset(finishedKey, 0xAB, sizeof(finishedKey)); memset(thHash, 0xCD, sizeof(thHash)); - rc = wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "ComputeVerifyData failed"); - - /* Verify it's an HMAC (non-zero) */ - int nonZero = 0; - for (int i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - if (verifyData[i] != 0) nonZero = 1; - } - TEST_ASSERT(nonZero, "VerifyData should be non-zero"); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(verifyData, zeros, WOLFSPDM_HASH_SIZE), 0, "VerifyData should be non-zero"); TEST_PASS(); } @@ -352,22 +293,16 @@ static int test_build_get_version(void) { byte buf[16]; word32 bufSz = sizeof(buf); - int rc; printf("test_build_get_version...\n"); - rc = wolfSPDM_BuildGetVersion(buf, &bufSz); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "BuildGetVersion failed"); - TEST_ASSERT(bufSz == 4, "GET_VERSION should be 4 bytes"); - TEST_ASSERT(buf[0] == SPDM_VERSION_10, "Version should be 0x10"); - TEST_ASSERT(buf[1] == SPDM_GET_VERSION, "Code should be 0x84"); - TEST_ASSERT(buf[2] == 0x00, "Param1 should be 0"); - TEST_ASSERT(buf[3] == 0x00, "Param2 should be 0"); + ASSERT_SUCCESS(wolfSPDM_BuildGetVersion(buf, &bufSz)); + ASSERT_EQ(bufSz, 4, "GET_VERSION should be 4 bytes"); + ASSERT_EQ(buf[0], SPDM_VERSION_10, "Version should be 0x10"); + ASSERT_EQ(buf[1], SPDM_GET_VERSION, "Code should be 0x84"); - /* Buffer too small */ bufSz = 2; - rc = wolfSPDM_BuildGetVersion(buf, &bufSz); - TEST_ASSERT(rc == WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + ASSERT_EQ(wolfSPDM_BuildGetVersion(buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); TEST_PASS(); } @@ -378,18 +313,15 @@ static int test_build_get_capabilities(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte buf[32]; word32 bufSz = sizeof(buf); - int rc; printf("test_build_get_capabilities...\n"); wolfSPDM_Init(ctx); ctx->spdmVersion = SPDM_VERSION_12; - - rc = wolfSPDM_BuildGetCapabilities(ctx, buf, &bufSz); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "BuildGetCapabilities failed"); - TEST_ASSERT(bufSz == 20, "GET_CAPABILITIES should be 20 bytes"); - TEST_ASSERT(buf[0] == SPDM_VERSION_12, "Version should be 0x12"); - TEST_ASSERT(buf[1] == SPDM_GET_CAPABILITIES, "Code should be 0xE1"); + ASSERT_SUCCESS(wolfSPDM_BuildGetCapabilities(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 20, "GET_CAPABILITIES should be 20 bytes"); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "Version should be 0x12"); + ASSERT_EQ(buf[1], SPDM_GET_CAPABILITIES, "Code should be 0xE1"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -401,18 +333,14 @@ static int test_build_negotiate_algorithms(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte buf[64]; word32 bufSz = sizeof(buf); - int rc; printf("test_build_negotiate_algorithms...\n"); wolfSPDM_Init(ctx); ctx->spdmVersion = SPDM_VERSION_12; - - rc = wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "BuildNegotiateAlgorithms failed"); - TEST_ASSERT(bufSz == 48, "NEGOTIATE_ALGORITHMS should be 48 bytes"); - TEST_ASSERT(buf[0] == SPDM_VERSION_12, "Version should be 0x12"); - TEST_ASSERT(buf[1] == SPDM_NEGOTIATE_ALGORITHMS, "Code should be 0xE3"); + ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 48, "NEGOTIATE_ALGORITHMS should be 48 bytes"); + ASSERT_EQ(buf[1], SPDM_NEGOTIATE_ALGORITHMS, "Code should be 0xE3"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -424,17 +352,14 @@ static int test_build_get_digests(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte buf[16]; word32 bufSz = sizeof(buf); - int rc; printf("test_build_get_digests...\n"); wolfSPDM_Init(ctx); ctx->spdmVersion = SPDM_VERSION_12; - - rc = wolfSPDM_BuildGetDigests(ctx, buf, &bufSz); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "BuildGetDigests failed"); - TEST_ASSERT(bufSz == 4, "GET_DIGESTS should be 4 bytes"); - TEST_ASSERT(buf[1] == SPDM_GET_DIGESTS, "Code should be 0x81"); + ASSERT_SUCCESS(wolfSPDM_BuildGetDigests(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 4, "GET_DIGESTS should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_GET_DIGESTS, "Code should be 0x81"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -446,18 +371,15 @@ static int test_build_get_certificate(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte buf[16]; word32 bufSz = sizeof(buf); - int rc; printf("test_build_get_certificate...\n"); wolfSPDM_Init(ctx); ctx->spdmVersion = SPDM_VERSION_12; - - rc = wolfSPDM_BuildGetCertificate(ctx, buf, &bufSz, 0, 0, 1024); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "BuildGetCertificate failed"); - TEST_ASSERT(bufSz == 8, "GET_CERTIFICATE should be 8 bytes"); - TEST_ASSERT(buf[1] == SPDM_GET_CERTIFICATE, "Code should be 0x82"); - TEST_ASSERT(buf[2] == 0x00, "SlotID should be 0"); + ASSERT_SUCCESS(wolfSPDM_BuildGetCertificate(ctx, buf, &bufSz, 0, 0, 1024)); + ASSERT_EQ(bufSz, 8, "GET_CERTIFICATE should be 8 bytes"); + ASSERT_EQ(buf[1], SPDM_GET_CERTIFICATE, "Code should be 0x82"); + ASSERT_EQ(buf[2], 0x00, "SlotID should be 0"); TEST_ASSERT(buf[6] == 0x00 && buf[7] == 0x04, "Length should be 1024"); wolfSPDM_Free(ctx); @@ -470,17 +392,14 @@ static int test_build_end_session(void) WOLFSPDM_CTX* ctx = &ctxBuf; byte buf[16]; word32 bufSz = sizeof(buf); - int rc; printf("test_build_end_session...\n"); wolfSPDM_Init(ctx); ctx->spdmVersion = SPDM_VERSION_12; - - rc = wolfSPDM_BuildEndSession(ctx, buf, &bufSz); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "BuildEndSession failed"); - TEST_ASSERT(bufSz == 4, "END_SESSION should be 4 bytes"); - TEST_ASSERT(buf[1] == SPDM_END_SESSION, "Code should be 0xEA"); + ASSERT_SUCCESS(wolfSPDM_BuildEndSession(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 4, "END_SESSION should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_END_SESSION, "Code should be 0xEA"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -522,6 +441,581 @@ static int test_error_strings(void) TEST_PASS(); } +/* ========================================================================== */ +/* Measurement Tests */ +/* ========================================================================== */ + +#ifndef NO_WOLFSPDM_MEAS + +static int test_build_get_measurements(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte buf[64]; + byte zeros[32]; + word32 bufSz; + + printf("test_build_get_measurements...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + + /* Build without signature */ + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, SPDM_MEAS_OPERATION_ALL, 0)); + ASSERT_EQ(bufSz, 4, "Without sig should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_GET_MEASUREMENTS, "Code should be 0xE0"); + ASSERT_EQ(buf[2], 0x00, "Param1 should be 0 (no sig)"); + + /* Build with signature */ + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, SPDM_MEAS_OPERATION_ALL, 1)); + ASSERT_EQ(bufSz, 37, "With sig should be 37 bytes"); + ASSERT_EQ(buf[2], SPDM_MEAS_REQUEST_SIG_BIT, "Sig bit should be set"); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(&buf[4], zeros, 32), 0, "Nonce should be non-zero"); + ASSERT_EQ(memcmp(ctx->measNonce, &buf[4], 32), 0, "Nonce should match context"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_measurement_accessors(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte measIdx, measType; + byte value[64]; + word32 valueSz; + + printf("test_measurement_accessors...\n"); + + wolfSPDM_Init(ctx); + ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 0, "Count should be 0 before measurements"); + + /* Manually populate 2 test blocks */ + ctx->hasMeasurements = 1; + ctx->measBlockCount = 2; + ctx->measBlocks[0].index = 1; + ctx->measBlocks[0].dmtfType = SPDM_MEAS_VALUE_TYPE_IMMUTABLE_ROM; + ctx->measBlocks[0].valueSize = 4; + ctx->measBlocks[0].value[0] = 0xAA; ctx->measBlocks[0].value[1] = 0xBB; + ctx->measBlocks[0].value[2] = 0xCC; ctx->measBlocks[0].value[3] = 0xDD; + ctx->measBlocks[1].index = 2; + ctx->measBlocks[1].dmtfType = SPDM_MEAS_VALUE_TYPE_MUTABLE_FW; + ctx->measBlocks[1].valueSize = 2; + ctx->measBlocks[1].value[0] = 0x11; ctx->measBlocks[1].value[1] = 0x22; + + ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 2, "Count should be 2"); + + /* Get block 0 */ + valueSz = sizeof(value); + ASSERT_SUCCESS(wolfSPDM_GetMeasurementBlock(ctx, 0, &measIdx, &measType, value, &valueSz)); + ASSERT_EQ(measIdx, 1, "Block 0 index should be 1"); + ASSERT_EQ(measType, SPDM_MEAS_VALUE_TYPE_IMMUTABLE_ROM, "Block 0 type wrong"); + ASSERT_EQ(valueSz, 4, "Block 0 size wrong"); + ASSERT_EQ(value[0], 0xAA, "Block 0 value wrong"); + + /* Get block 1 */ + valueSz = sizeof(value); + ASSERT_SUCCESS(wolfSPDM_GetMeasurementBlock(ctx, 1, &measIdx, &measType, value, &valueSz)); + ASSERT_EQ(measIdx, 2, "Block 1 index should be 2"); + + /* Out of range */ + valueSz = sizeof(value); + ASSERT_FAIL(wolfSPDM_GetMeasurementBlock(ctx, 2, &measIdx, &measType, value, &valueSz)); + ASSERT_FAIL(wolfSPDM_GetMeasurementBlock(ctx, -1, &measIdx, &measType, value, &valueSz)); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_parse_measurements(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + /* Fake MEASUREMENTS response: 2 blocks, recordLen=20 */ + byte rsp[] = { + 0x12, 0x60, 0x00, 0x00, /* header */ + 0x02, /* numBlocks */ + 0x14, 0x00, 0x00, /* recordLen = 20 LE */ + /* Block 1: Index=1, Spec=1, Size=7, DMTF Type=0x00, ValSize=4 */ + 0x01, 0x01, 0x07, 0x00, 0x00, 0x04, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, + /* Block 2: Index=2, Spec=1, Size=5, DMTF Type=0x01, ValSize=2 */ + 0x02, 0x01, 0x05, 0x00, 0x01, 0x02, 0x00, 0x11, 0x22 + }; + + printf("test_parse_measurements...\n"); + + wolfSPDM_Init(ctx); + ASSERT_SUCCESS(wolfSPDM_ParseMeasurements(ctx, rsp, sizeof(rsp))); + ASSERT_EQ(ctx->measBlockCount, 2, "Should have 2 blocks"); + ASSERT_EQ(ctx->hasMeasurements, 1, "hasMeasurements should be set"); + ASSERT_EQ(ctx->measBlocks[0].index, 1, "Block 0 index wrong"); + ASSERT_EQ(ctx->measBlocks[0].dmtfType, 0x00, "Block 0 type wrong"); + ASSERT_EQ(ctx->measBlocks[0].valueSize, 4, "Block 0 valueSize wrong"); + ASSERT_EQ(ctx->measBlocks[0].value[0], 0xAA, "Block 0 value[0] wrong"); + ASSERT_EQ(ctx->measBlocks[1].index, 2, "Block 1 index wrong"); + ASSERT_EQ(ctx->measBlocks[1].valueSize, 2, "Block 1 valueSize wrong"); + + /* Test truncated buffer */ + ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, rsp, 5)); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +#ifndef NO_WOLFSPDM_MEAS_VERIFY + +static int test_measurement_sig_verification(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + ecc_key sigKey; + WC_RNG rng; + /* Construct a minimal GET_MEASUREMENTS request (L1) */ + byte reqMsg[] = { + 0x12, 0xE0, 0x01, 0xFF, /* version, GET_MEASUREMENTS, sig bit, all */ + /* 32 bytes nonce */ + 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, 0x20, + 0x00 /* SlotID */ + }; + /* Construct a MEASUREMENTS response (L2) WITHOUT signature + * We'll append signature after signing */ + byte rspBase[] = { + 0x12, 0x60, 0x00, 0x00, /* header */ + 0x01, /* numBlocks=1 */ + 0x0B, 0x00, 0x00, /* recordLen=11 */ + /* Block 1 */ + 0x01, 0x01, 0x07, 0x00, /* Index=1, Spec=1, Size=7 */ + 0x00, 0x04, 0x00, /* Type=0, ValueSize=4 */ + 0xAA, 0xBB, 0xCC, 0xDD, /* Value */ + /* Nonce (32 bytes) */ + 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, 0x20, + /* OpaqueDataLength = 0 */ + 0x00, 0x00 + }; + byte rspBuf[256]; /* rspBase + 96 byte signature */ + word32 rspBufSz; + wc_Sha384 sha, sha2; + byte digest[WOLFSPDM_HASH_SIZE]; + byte derSig[256]; + word32 derSigSz = sizeof(derSig); + byte rawR[WOLFSPDM_ECC_KEY_SIZE]; + byte rawS[WOLFSPDM_ECC_KEY_SIZE]; + word32 rSz = sizeof(rawR); + word32 sSz = sizeof(rawS); + int rc; + + printf("test_measurement_sig_verification...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + + /* Generate ECC P-384 keypair for testing */ + rc = wc_InitRng(&rng); + TEST_ASSERT(rc == 0, "wc_InitRng failed"); + rc = wc_ecc_init(&sigKey); + TEST_ASSERT(rc == 0, "wc_ecc_init failed"); + rc = wc_ecc_make_key(&rng, 48, &sigKey); + TEST_ASSERT(rc == 0, "wc_ecc_make_key failed"); + + /* Copy public key into context for verification */ + rc = wc_ecc_init(&ctx->responderPubKey); + TEST_ASSERT(rc == 0, "wc_ecc_init responderPubKey failed"); + + /* Export/import just the public key */ + { + byte pubDer[256]; + word32 pubDerSz = sizeof(pubDer); + word32 idx = 0; + rc = wc_EccPublicKeyToDer(&sigKey, pubDer, pubDerSz, 1); + TEST_ASSERT(rc > 0, "EccPublicKeyToDer failed"); + pubDerSz = (word32)rc; + rc = wc_EccPublicKeyDecode(pubDer, &idx, &ctx->responderPubKey, + pubDerSz); + TEST_ASSERT(rc == 0, "EccPublicKeyDecode failed"); + } + ctx->hasResponderPubKey = 1; + + /* Build the response buffer (rspBase + signature) */ + XMEMCPY(rspBuf, rspBase, sizeof(rspBase)); + rspBufSz = sizeof(rspBase); + + /* Compute Hash(L1||L2) where L2 = rspBase (before signature) */ + /* Then build M = prefix||pad||context||hash, then Hash(M) */ + { + static const char context_str[] = "responder-measurements signing"; + #define TEST_PREFIX_SIZE 16 + #define TEST_CONTEXT_STR_SIZE 30 /* strlen, no null terminator */ + #define TEST_ZERO_PAD_SIZE (36 - TEST_CONTEXT_STR_SIZE) + byte signMsg[200]; + word32 signMsgLen = 0; + int i; + + /* L1||L2 hash */ + rc = wc_InitSha384(&sha); + TEST_ASSERT(rc == 0, "InitSha384 failed"); + wc_Sha384Update(&sha, reqMsg, sizeof(reqMsg)); + wc_Sha384Update(&sha, rspBuf, rspBufSz); + wc_Sha384Final(&sha, digest); + wc_Sha384Free(&sha); + + /* Build M */ + for (i = 0; i < 4; i++) { + XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", TEST_PREFIX_SIZE); + signMsgLen += TEST_PREFIX_SIZE; + } + XMEMSET(&signMsg[signMsgLen], 0x00, TEST_ZERO_PAD_SIZE); + signMsgLen += TEST_ZERO_PAD_SIZE; + XMEMCPY(&signMsg[signMsgLen], context_str, TEST_CONTEXT_STR_SIZE); + signMsgLen += TEST_CONTEXT_STR_SIZE; + XMEMCPY(&signMsg[signMsgLen], digest, WOLFSPDM_HASH_SIZE); + signMsgLen += WOLFSPDM_HASH_SIZE; + + /* Hash(M) */ + rc = wc_InitSha384(&sha2); + TEST_ASSERT(rc == 0, "InitSha384 for M failed"); + wc_Sha384Update(&sha2, signMsg, signMsgLen); + wc_Sha384Final(&sha2, digest); + wc_Sha384Free(&sha2); + } + + /* Sign Hash(M) with our test key (DER format) */ + rc = wc_ecc_sign_hash(digest, WOLFSPDM_HASH_SIZE, derSig, &derSigSz, + &rng, &sigKey); + TEST_ASSERT(rc == 0, "ecc_sign_hash failed"); + + /* Convert DER signature to raw r||s for SPDM */ + rc = wc_ecc_sig_to_rs(derSig, derSigSz, rawR, &rSz, rawS, &sSz); + TEST_ASSERT(rc == 0, "ecc_sig_to_rs failed"); + + /* Pad r and s to 48 bytes each (P-384) */ + { + byte sigRaw[WOLFSPDM_ECC_SIG_SIZE]; + XMEMSET(sigRaw, 0, sizeof(sigRaw)); + /* Right-align r and s in their 48-byte fields */ + XMEMCPY(sigRaw + (48 - rSz), rawR, rSz); + XMEMCPY(sigRaw + 48 + (48 - sSz), rawS, sSz); + XMEMCPY(rspBuf + rspBufSz, sigRaw, WOLFSPDM_ECC_SIG_SIZE); + } + rspBufSz += WOLFSPDM_ECC_SIG_SIZE; + + /* Test 1: Valid signature should verify */ + rc = wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, rspBufSz, + reqMsg, sizeof(reqMsg)); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, + "Valid signature should verify"); + + /* Test 2: Corrupt one signature byte -> should fail */ + rspBuf[rspBufSz - 10] ^= 0xFF; + rc = wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, rspBufSz, + reqMsg, sizeof(reqMsg)); + TEST_ASSERT(rc == WOLFSPDM_E_MEAS_SIG_FAIL, + "Corrupted sig should fail"); + rspBuf[rspBufSz - 10] ^= 0xFF; /* Restore */ + + /* Test 3: Corrupt one measurement byte -> should fail */ + rspBuf[15] ^= 0xFF; /* Corrupt a measurement value byte */ + rc = wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, rspBufSz, + reqMsg, sizeof(reqMsg)); + TEST_ASSERT(rc == WOLFSPDM_E_MEAS_SIG_FAIL, + "Corrupted measurement should fail"); + + wc_ecc_free(&sigKey); + wc_FreeRng(&rng); + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ +#endif /* !NO_WOLFSPDM_MEAS */ + +/* ========================================================================== */ +/* Certificate Chain Validation Tests */ +/* ========================================================================== */ + +static int test_set_trusted_cas(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte fakeCa[] = {0x30, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; + + printf("test_set_trusted_cas...\n"); + + wolfSPDM_Init(ctx); + ASSERT_FAIL(wolfSPDM_SetTrustedCAs(NULL, fakeCa, sizeof(fakeCa))); + ASSERT_FAIL(wolfSPDM_SetTrustedCAs(ctx, NULL, sizeof(fakeCa))); + ASSERT_FAIL(wolfSPDM_SetTrustedCAs(ctx, fakeCa, 0)); + ASSERT_SUCCESS(wolfSPDM_SetTrustedCAs(ctx, fakeCa, sizeof(fakeCa))); + ASSERT_EQ(ctx->hasTrustedCAs, 1, "hasTrustedCAs not set"); + ASSERT_EQ(ctx->trustedCAsSz, sizeof(fakeCa), "Size mismatch"); + ASSERT_EQ(memcmp(ctx->trustedCAs, fakeCa, sizeof(fakeCa)), 0, "Data mismatch"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_validate_cert_chain_no_cas(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + + printf("test_validate_cert_chain_no_cas...\n"); + + wolfSPDM_Init(ctx); + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_PARSE, "Should fail without trusted CAs"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Challenge Tests */ +/* ========================================================================== */ + +#ifndef NO_WOLFSPDM_CHALLENGE + +static int test_build_challenge(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte buf[64]; + byte zeros[32]; + word32 bufSz; + + printf("test_build_challenge...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 0, SPDM_MEAS_SUMMARY_HASH_NONE)); + ASSERT_EQ(bufSz, 36, "CHALLENGE should be 36 bytes"); + ASSERT_EQ(buf[1], SPDM_CHALLENGE, "Code should be 0x83"); + ASSERT_EQ(buf[3], SPDM_MEAS_SUMMARY_HASH_NONE, "MeasHashType wrong"); + XMEMSET(zeros, 0, sizeof(zeros)); + ASSERT_NE(memcmp(&buf[4], zeros, 32), 0, "Nonce should be non-zero"); + ASSERT_EQ(memcmp(ctx->challengeNonce, &buf[4], 32), 0, "Nonce should match context"); + + /* Test with different slot and meas hash type */ + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 3, SPDM_MEAS_SUMMARY_HASH_ALL)); + ASSERT_EQ(buf[2], 0x03, "SlotID should be 3"); + + /* Buffer too small */ + bufSz = 10; + ASSERT_EQ(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 0, SPDM_MEAS_SUMMARY_HASH_NONE), + WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_parse_challenge_auth(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + /* Fake CHALLENGE_AUTH: hdr(4) + CertHash(48) + Nonce(32) + OpaqueLen(2) + Sig(96) = 182 */ + byte rsp[182]; + word32 sigOffset = 0; + + printf("test_parse_challenge_auth...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + ctx->challengeMeasHashType = SPDM_MEAS_SUMMARY_HASH_NONE; + + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_12; + rsp[1] = SPDM_CHALLENGE_AUTH; + XMEMSET(&rsp[4], 0xAA, WOLFSPDM_HASH_SIZE); + XMEMCPY(ctx->certChainHash, &rsp[4], WOLFSPDM_HASH_SIZE); + XMEMSET(&rsp[52], 0xBB, 32); + XMEMSET(&rsp[86], 0xCC, WOLFSPDM_ECC_SIG_SIZE); + + ASSERT_SUCCESS(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset)); + ASSERT_EQ(sigOffset, 86, "Signature offset should be 86"); + + /* Wrong response code */ + rsp[1] = 0xFF; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), + WOLFSPDM_E_CHALLENGE, "Wrong code should fail"); + rsp[1] = SPDM_CHALLENGE_AUTH; + + /* CertChainHash mismatch */ + ctx->certChainHash[0] = 0x00; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), + WOLFSPDM_E_CHALLENGE, "Hash mismatch should fail"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* ========================================================================== */ +/* Heartbeat Tests */ +/* ========================================================================== */ + +static int test_build_heartbeat(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte buf[16]; + word32 bufSz; + + printf("test_build_heartbeat...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildHeartbeat(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 4, "HEARTBEAT should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_HEARTBEAT, "Code should be 0xE8"); + + bufSz = 2; + ASSERT_EQ(wolfSPDM_BuildHeartbeat(ctx, buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_parse_heartbeat_ack(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte ack[] = {0x12, SPDM_HEARTBEAT_ACK, 0x00, 0x00}; + byte err[] = {0x12, SPDM_ERROR, 0x01, 0x00}; + + printf("test_parse_heartbeat_ack...\n"); + + wolfSPDM_Init(ctx); + ASSERT_SUCCESS(wolfSPDM_ParseHeartbeatAck(ctx, ack, sizeof(ack))); + ASSERT_EQ(wolfSPDM_ParseHeartbeatAck(ctx, err, sizeof(err)), WOLFSPDM_E_PEER_ERROR, "Error should return PEER_ERROR"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_heartbeat_state_check(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + + printf("test_heartbeat_state_check...\n"); + + wolfSPDM_Init(ctx); + ASSERT_EQ(wolfSPDM_Heartbeat(ctx), WOLFSPDM_E_NOT_CONNECTED, "Heartbeat should fail when not connected"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Key Update Tests */ +/* ========================================================================== */ + +static int test_build_key_update(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte buf[16]; + word32 bufSz; + byte tag = 0; + + printf("test_build_key_update...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildKeyUpdate(ctx, buf, &bufSz, SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, &tag)); + ASSERT_EQ(bufSz, 4, "KEY_UPDATE should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_KEY_UPDATE, "Code should be 0xE9"); + ASSERT_EQ(buf[2], SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, "Operation should be UpdateAllKeys"); + ASSERT_EQ(buf[3], tag, "Tag should match returned value"); + + bufSz = 2; + ASSERT_EQ(wolfSPDM_BuildKeyUpdate(ctx, buf, &bufSz, SPDM_KEY_UPDATE_OP_UPDATE_KEY, &tag), + WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_parse_key_update_ack(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte ack[] = {0x12, SPDM_KEY_UPDATE_ACK, 0x02, 0x42}; + + printf("test_parse_key_update_ack...\n"); + + wolfSPDM_Init(ctx); + ASSERT_SUCCESS(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0x42)); + ASSERT_EQ(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0xFF), + WOLFSPDM_E_KEY_UPDATE, "Mismatched tag should fail"); + ASSERT_EQ(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), SPDM_KEY_UPDATE_OP_UPDATE_KEY, 0x42), + WOLFSPDM_E_KEY_UPDATE, "Mismatched op should fail"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_derive_updated_keys(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + byte origReqKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte origRspKey[WOLFSPDM_AEAD_KEY_SIZE]; + + printf("test_derive_updated_keys...\n"); + + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + XMEMSET(ctx->reqAppSecret, 0x5A, WOLFSPDM_HASH_SIZE); + XMEMSET(ctx->rspAppSecret, 0xA5, WOLFSPDM_HASH_SIZE); + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x22, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(origReqKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(origRspKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + + /* Update all keys */ + ASSERT_SUCCESS(wolfSPDM_DeriveUpdatedKeys(ctx, 1)); + ASSERT_NE(memcmp(ctx->reqDataKey, origReqKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Req key should change"); + ASSERT_NE(memcmp(ctx->rspDataKey, origRspKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Rsp key should change"); + + /* Update requester only */ + XMEMCPY(origReqKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(origRspKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + ASSERT_SUCCESS(wolfSPDM_DeriveUpdatedKeys(ctx, 0)); + ASSERT_NE(memcmp(ctx->reqDataKey, origReqKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Req key should change"); + ASSERT_EQ(memcmp(ctx->rspDataKey, origRspKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Rsp key should NOT change"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_key_update_state_check(void) +{ + WOLFSPDM_CTX ctxBuf; + WOLFSPDM_CTX* ctx = &ctxBuf; + + printf("test_key_update_state_check...\n"); + + wolfSPDM_Init(ctx); + ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 1), WOLFSPDM_E_NOT_CONNECTED, "KeyUpdate should fail when not connected"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + /* ========================================================================== */ /* Session State Tests */ /* ========================================================================== */ @@ -534,20 +1028,16 @@ static int test_session_state(void) printf("test_session_state...\n"); wolfSPDM_Init(ctx); - - TEST_ASSERT(wolfSPDM_IsConnected(ctx) == 0, "Should not be connected"); - TEST_ASSERT(wolfSPDM_GetSessionId(ctx) == 0, "SessionId should be 0"); - TEST_ASSERT(wolfSPDM_GetVersion_Negotiated(ctx) == 0, "Version should be 0"); + ASSERT_EQ(wolfSPDM_IsConnected(ctx), 0, "Should not be connected"); + ASSERT_EQ(wolfSPDM_GetSessionId(ctx), 0, "SessionId should be 0"); /* Simulate connected state */ ctx->state = WOLFSPDM_STATE_CONNECTED; ctx->sessionId = 0xAABBCCDD; ctx->spdmVersion = SPDM_VERSION_12; - - TEST_ASSERT(wolfSPDM_IsConnected(ctx) == 1, "Should be connected"); - TEST_ASSERT(wolfSPDM_GetSessionId(ctx) == 0xAABBCCDD, "SessionId wrong"); - TEST_ASSERT(wolfSPDM_GetVersion_Negotiated(ctx) == SPDM_VERSION_12, - "Version wrong"); + ASSERT_EQ(wolfSPDM_IsConnected(ctx), 1, "Should be connected"); + ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0xAABBCCDD, "SessionId wrong"); + ASSERT_EQ(wolfSPDM_GetVersion_Negotiated(ctx), SPDM_VERSION_12, "Version wrong"); wolfSPDM_Free(ctx); TEST_PASS(); @@ -596,6 +1086,37 @@ int main(void) test_check_error(); test_error_strings(); + /* Measurement tests */ +#ifndef NO_WOLFSPDM_MEAS + test_build_get_measurements(); + test_measurement_accessors(); + test_parse_measurements(); +#ifndef NO_WOLFSPDM_MEAS_VERIFY + test_measurement_sig_verification(); +#endif +#endif + + /* Certificate chain validation tests */ + test_set_trusted_cas(); + test_validate_cert_chain_no_cas(); + + /* Challenge tests */ +#ifndef NO_WOLFSPDM_CHALLENGE + test_build_challenge(); + test_parse_challenge_auth(); +#endif + + /* Heartbeat tests */ + test_build_heartbeat(); + test_parse_heartbeat_ack(); + test_heartbeat_state_check(); + + /* Key update tests */ + test_build_key_update(); + test_parse_key_update_ack(); + test_derive_updated_keys(); + test_key_update_state_check(); + /* Session state tests */ test_session_state(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 85f3dc8..1577afb 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -31,13 +31,22 @@ #include #include +/* Feature detection macros — external projects (e.g. wolfTPM) can check these + * to conditionally compile against optional wolfSPDM APIs. */ +#ifndef NO_WOLFSPDM_MEAS +#define WOLFSPDM_HAS_MEASUREMENTS +#endif +#ifndef NO_WOLFSPDM_CHALLENGE +#define WOLFSPDM_HAS_CHALLENGE +#endif +#define WOLFSPDM_HAS_HEARTBEAT +#define WOLFSPDM_HAS_KEY_UPDATE + #ifdef __cplusplus extern "C" { #endif -/* ========================================================================== - * Protocol Mode Selection - * ========================================================================== +/* --- Protocol Mode Selection --- * * wolfSPDM supports two protocol modes: * @@ -50,18 +59,14 @@ extern "C" { * WOLFSPDM_MODE_NUVOTON (requires --enable-nuvoton): * Nuvoton TPM-specific protocol with TCG binding headers. * Flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH - * Use with: Nuvoton NPCT75x TPMs (FW 7.2+) - * - * ========================================================================== */ + * Use with: Nuvoton NPCT75x TPMs (FW 7.2+) */ typedef enum { WOLFSPDM_MODE_STANDARD = 0, /* Standard SPDM 1.2 (default) */ WOLFSPDM_MODE_NUVOTON = 1, /* Nuvoton TCG binding + vendor commands */ } WOLFSPDM_MODE; -/* ========================================================================== - * wolfSPDM Overview - * ========================================================================== +/* --- wolfSPDM Overview --- * * wolfSPDM is a lightweight SPDM (Security Protocol and Data Model) * implementation using wolfCrypt for all cryptographic operations. @@ -93,18 +98,16 @@ typedef enum { * wolfSPDM_Free(ctx); // Frees the allocation * * Note: WOLFSPDM_CTX is approximately 22KB. On embedded systems with - * small stacks, declare it as a static global rather than a local variable. - * - * ========================================================================== */ + * small stacks, declare it as a static global rather than a local variable. */ /* Compile-time size for static allocation of WOLFSPDM_CTX. * Use this when you need a buffer large enough to hold WOLFSPDM_CTX * without access to the struct definition (e.g., in wolfTPM). - * Actual struct size: ~21.2 KB (base) / ~21.3 KB (with Nuvoton). - * Rounded up to 22 KB for platform alignment and minor future growth. + * Actual struct size: ~31.3 KB (with measurements) / ~29.9 KB (NO_WOLFSPDM_MEAS). + * Rounded up to 32 KB for platform alignment. * wolfSPDM_InitStatic() verifies at runtime that the provided buffer * is large enough; returns WOLFSPDM_E_BUFFER_SMALL if not. */ -#define WOLFSPDM_CTX_STATIC_SIZE 22528 +#define WOLFSPDM_CTX_STATIC_SIZE 32768 /* 32KB - fits CTX with cert validation + challenge + key update fields */ /* Forward declaration */ struct WOLFSPDM_CTX; @@ -115,9 +118,7 @@ typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; #include #endif -/* ========================================================================== - * I/O Callback - * ========================================================================== +/* --- I/O Callback --- * * The I/O callback is called by wolfSPDM to send and receive raw SPDM * messages. The transport layer (SPI, I2C, TCP, etc.) is handled externally. @@ -136,8 +137,7 @@ typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; * Notes: * - For MCTP transport, the callback should handle MCTP encapsulation * - For secured messages (after KEY_EXCHANGE), the callback receives - * already-encrypted data including the session header - * ========================================================================== */ + * already-encrypted data including the session header */ typedef int (*WOLFSPDM_IO_CB)( WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, @@ -145,9 +145,7 @@ typedef int (*WOLFSPDM_IO_CB)( void* userCtx ); -/* ========================================================================== - * Context Management - * ========================================================================== */ +/* --- Context Management --- */ /** * Initialize a wolfSPDM context for use. @@ -200,9 +198,7 @@ int wolfSPDM_GetCtxSize(void); */ int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); -/* ========================================================================== - * Configuration - * ========================================================================== */ +/* --- Configuration --- */ /** * Set the I/O callback for sending/receiving SPDM messages. @@ -261,9 +257,7 @@ int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, const byte* privKey, word32 privKeySz, const byte* pubKey, word32 pubKeySz); -/* ========================================================================== - * Session Establishment - * ========================================================================== */ +/* --- Session Establishment --- */ /** * Establish an SPDM session (full handshake). @@ -294,9 +288,7 @@ int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx); */ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); -/* ========================================================================== - * Individual Handshake Steps (for fine-grained control) - * ========================================================================== */ +/* --- Individual Handshake Steps (for fine-grained control) --- */ /** * Send GET_VERSION and receive VERSION response. @@ -361,10 +353,9 @@ int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); */ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); -/* ========================================================================== - * Secured Messaging - * ========================================================================== */ +/* --- Secured Messaging --- */ +#ifndef WOLFSPDM_LEAN /** * Encrypt a message for sending over the established session. * @@ -392,6 +383,7 @@ int wolfSPDM_EncryptMessage(WOLFSPDM_CTX* ctx, int wolfSPDM_DecryptMessage(WOLFSPDM_CTX* ctx, const byte* enc, word32 encSz, byte* plain, word32* plainSz); +#endif /* !WOLFSPDM_LEAN */ /** * Perform a secured message exchange (encrypt, send, receive, decrypt). @@ -408,9 +400,91 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); -/* ========================================================================== - * Session Information - * ========================================================================== */ +#ifndef NO_WOLFSPDM_MEAS +/* --- Measurements (Device Attestation) --- + * + * When requestSignature=1 (and NO_WOLFSPDM_MEAS_VERIFY is NOT defined): + * Retrieves measurements with a cryptographic signature from the responder, + * then verifies the signature using the responder's certificate (retrieved + * during wolfSPDM_Connect). Returns WOLFSPDM_SUCCESS if verification passes. + * Returns WOLFSPDM_E_MEAS_SIG_FAIL if the signature is invalid. + * + * When requestSignature=0: + * Retrieves measurements WITHOUT a signature. + * Returns WOLFSPDM_E_MEAS_NOT_VERIFIED. Measurements are informational + * only and should not be used for security-critical decisions. + * + * If compiled with NO_WOLFSPDM_MEAS_VERIFY, signature verification is + * disabled and returns WOLFSPDM_E_MEAS_NOT_VERIFIED regardless of + * requestSignature (signature bytes are still captured in the context). + * + * Contexts are NOT thread-safe; do not call from multiple threads. */ + +/** + * Retrieve measurements from the SPDM responder. + * + * @param ctx The wolfSPDM context. + * @param measOperation SPDM_MEAS_OPERATION_ALL (0xFF) or specific index. + * @param requestSignature 1 to request signed measurements, 0 for unsigned. + * @return WOLFSPDM_SUCCESS (verified), WOLFSPDM_E_MEAS_NOT_VERIFIED (unsigned), + * WOLFSPDM_E_MEAS_SIG_FAIL (sig invalid), or negative error code. + */ +int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, + int requestSignature); + +/** + * Get the number of measurement blocks retrieved. + * + * @param ctx The wolfSPDM context. + * @return Number of measurement blocks, or 0 if none. + */ +int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx); + +/** + * Get a specific measurement block by index. + * + * @param ctx The wolfSPDM context. + * @param blockIdx Index into retrieved blocks (0-based). + * @param measIndex [out] SPDM measurement index (1-based). + * @param measType [out] DMTFSpecMeasurementValueType. + * @param value [out] Buffer for measurement value. + * @param valueSz [in] Size of value buffer, [out] Actual value size. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, + byte* measIndex, byte* measType, byte* value, word32* valueSz); +#endif /* !NO_WOLFSPDM_MEAS */ + +#ifndef WOLFSPDM_LEAN +/* --- Application Data Transfer --- + * + * Send/receive application data over an established SPDM session. + * Max payload per call: WOLFSPDM_MAX_MSG_SIZE minus AEAD overhead (~4000 bytes). + * These are message-oriented (no partial reads/writes). + * Contexts are NOT thread-safe; do not call from multiple threads. */ + +/** + * Send application data over an established SPDM session. + * + * @param ctx The wolfSPDM context (must be connected). + * @param data Data to send. + * @param dataSz Size of data. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz); + +/** + * Receive application data over an established SPDM session. + * + * @param ctx The wolfSPDM context (must be connected). + * @param data Buffer for received data. + * @param dataSz [in] Size of buffer, [out] Actual data size. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, word32* dataSz); +#endif /* !WOLFSPDM_LEAN */ + +/* --- Session Information --- */ /** * Get the current session ID. @@ -446,9 +520,68 @@ word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); #endif -/* ========================================================================== - * Debug/Utility - * ========================================================================== */ +/* --- Certificate Chain Validation --- */ + +/** + * Load trusted root CA certificates for certificate chain validation. + * When set, wolfSPDM_Connect() will validate the responder's certificate + * chain against these CAs. Without this, only the public key is extracted. + * + * @param ctx The wolfSPDM context. + * @param derCerts DER-encoded CA certificate(s) (concatenated if multiple). + * @param derCertsSz Size of DER certificate data. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, + word32 derCertsSz); + +#ifndef NO_WOLFSPDM_CHALLENGE +/* --- Challenge Authentication (Sessionless Attestation) --- */ + +/** + * Perform CHALLENGE/CHALLENGE_AUTH exchange for sessionless attestation. + * Requires state >= WOLFSPDM_STATE_CERT (cert chain must be retrieved). + * Typical flow: GET_VERSION -> GET_CAPS -> NEGOTIATE_ALGO -> GET_DIGESTS + * -> GET_CERTIFICATE -> CHALLENGE + * + * @param ctx The wolfSPDM context. + * @param slotId Certificate slot (0-7, typically 0). + * @param measHashType Measurement summary hash type: + * SPDM_MEAS_SUMMARY_HASH_NONE (0x00), + * SPDM_MEAS_SUMMARY_HASH_TCB (0x01), or + * SPDM_MEAS_SUMMARY_HASH_ALL (0xFF). + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType); +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* --- Session Keep-Alive --- */ + +/** + * Send HEARTBEAT and receive HEARTBEAT_ACK. + * Must be in an established session (CONNECTED or MEASURED state). + * Sent over the encrypted channel. + * + * @param ctx The wolfSPDM context. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx); + +/* --- Key Update (Session Key Rotation) --- */ + +/** + * Perform KEY_UPDATE to rotate session encryption keys. + * Must be in an established session (CONNECTED or MEASURED state). + * Follows up with VERIFY_NEW_KEY to confirm the new keys work. + * + * @param ctx The wolfSPDM context. + * @param updateAll 0 = rotate requester key only, + * 1 = rotate both requester and responder keys. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll); + +/* --- Debug/Utility --- */ /** * Enable or disable debug output. diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index 603855b..9ddf173 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -49,6 +49,12 @@ enum WOLFSPDM_ERROR { WOLFSPDM_E_ALGO_MISMATCH = -18, /* Algorithm negotiation failed */ WOLFSPDM_E_SESSION_INVALID = -19, /* Session ID invalid or mismatch */ WOLFSPDM_E_KEY_EXCHANGE = -20, /* Key exchange failed */ + WOLFSPDM_E_MEASUREMENT = -21, /* Measurement retrieval/parsing failed */ + WOLFSPDM_E_MEAS_NOT_VERIFIED = -22, /* Measurements retrieved but not signature-verified */ + WOLFSPDM_E_MEAS_SIG_FAIL = -23, /* Measurement signature verification failed */ + WOLFSPDM_E_CERT_PARSE = -24, /* Failed to parse responder certificate */ + WOLFSPDM_E_CHALLENGE = -25, /* Challenge authentication failed */ + WOLFSPDM_E_KEY_UPDATE = -26, /* Key update failed */ }; /* Get human-readable error string */ diff --git a/wolfspdm/spdm_nuvoton.h b/wolfspdm/spdm_nuvoton.h index b908098..a1da94c 100644 --- a/wolfspdm/spdm_nuvoton.h +++ b/wolfspdm/spdm_nuvoton.h @@ -51,9 +51,7 @@ extern "C" { #endif -/* ========================================================================== - * TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) - * ========================================================================== */ +/* --- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) --- */ /* Message Tags */ #define WOLFSPDM_TCG_TAG_CLEAR 0x8101 /* Clear (unencrypted) message */ @@ -67,9 +65,7 @@ extern "C" { #define WOLFSPDM_FIPS_NON_FIPS 0x00 #define WOLFSPDM_FIPS_APPROVED 0x01 -/* ========================================================================== - * Nuvoton Vendor-Defined Command Codes - * ========================================================================== */ +/* --- Nuvoton Vendor-Defined Command Codes --- */ /* 8-byte ASCII vendor codes for SPDM VENDOR_DEFINED messages */ #define WOLFSPDM_VDCODE_LEN 8 @@ -84,9 +80,7 @@ extern "C" { #define WOLFSPDM_SPDMONLY_LOCK 0x01 #define WOLFSPDM_SPDMONLY_UNLOCK 0x00 -/* ========================================================================== - * TCG Binding Header Structures - * ========================================================================== */ +/* --- TCG Binding Header Structures --- */ /* Clear message header (tag 0x8101) * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + @@ -112,9 +106,7 @@ typedef struct WOLFSPDM_TCG_SECURED_HDR { word32 reserved; /* Must be 0 */ } WOLFSPDM_TCG_SECURED_HDR; -/* ========================================================================== - * Nuvoton SPDM Status - * ========================================================================== */ +/* --- Nuvoton SPDM Status --- */ typedef struct WOLFSPDM_NUVOTON_STATUS { int spdmEnabled; /* SPDM is enabled on the TPM */ @@ -125,9 +117,7 @@ typedef struct WOLFSPDM_NUVOTON_STATUS { byte specVersionMinor; /* SPDM spec version minor (1=1.1, 3=1.3) */ } WOLFSPDM_NUVOTON_STATUS; -/* ========================================================================== - * TCG Binding Message Framing Functions - * ========================================================================== */ +/* --- TCG Binding Message Framing Functions --- */ /** * Build a TCG SPDM clear message (tag 0x8101). @@ -202,9 +192,7 @@ int wolfSPDM_ParseTcgSecuredMessage( byte* mac, word32* macSz, WOLFSPDM_TCG_SECURED_HDR* hdr); -/* ========================================================================== - * Vendor-Defined Message Helpers - * ========================================================================== */ +/* --- Vendor-Defined Message Helpers --- */ /** * Build an SPDM VENDOR_DEFINED_REQUEST message. @@ -236,9 +224,7 @@ int wolfSPDM_ParseVendorDefined( char* vdCode, byte* payload, word32* payloadSz); -/* ========================================================================== - * Nuvoton-Specific SPDM Functions - * ========================================================================== */ +/* --- Nuvoton-Specific SPDM Functions --- */ /** * Get the TPM's SPDM-Identity public key (GET_PUBK vendor command). @@ -314,9 +300,7 @@ int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, */ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx); -/* ========================================================================== - * Nuvoton Context Fields - * ========================================================================== */ +/* --- Nuvoton Context Fields --- */ /* These fields are added to WOLFSPDM_CTX when WOLFSPDM_NUVOTON is defined */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 88a579c..043056b 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -37,9 +37,7 @@ extern "C" { #include #endif -/* ========================================================================== - * SPDM Protocol Constants (DMTF DSP0274 / DSP0277) - * ========================================================================== */ +/* --- SPDM Protocol Constants (DMTF DSP0274 / DSP0277) --- */ /* SPDM Version Numbers */ #define SPDM_VERSION_10 0x10 /* SPDM 1.0 (for GET_VERSION) */ @@ -109,10 +107,8 @@ extern "C" { #define SPDM_ERROR_RESPONSE_NOT_READY 0x42 #define SPDM_ERROR_REQUEST_RESYNCH 0x43 -/* ========================================================================== - * Algorithm Set B (FIPS 140-3 Level 3 compliant) - * This implementation ONLY supports Algorithm Set B for simplicity. - * ========================================================================== */ +/* --- Algorithm Set B (FIPS 140-3 Level 3 compliant) --- + * This implementation ONLY supports Algorithm Set B for simplicity. */ /* Hash Algorithms */ #define SPDM_HASH_ALGO_SHA_384 0x00000002 /* TPM_ALG_SHA384 */ @@ -139,9 +135,7 @@ extern "C" { #define WOLFSPDM_AEAD_TAG_SIZE 16 /* AES-GCM tag size */ #define WOLFSPDM_HMAC_SIZE 48 /* HMAC-SHA384 output size */ -/* ========================================================================== - * Capability Flags (per DSP0274) - * ========================================================================== */ +/* --- Capability Flags (per DSP0274) --- */ /* Requester Capabilities (GET_CAPABILITIES flags) */ #define SPDM_CAP_CERT_CAP 0x00000002 /* Certificate support */ @@ -164,20 +158,17 @@ extern "C" { /* Default requester capabilities for Algorithm Set B session */ #define WOLFSPDM_DEFAULT_REQ_CAPS (SPDM_CAP_CERT_CAP | SPDM_CAP_CHAL_CAP | \ SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ - SPDM_CAP_KEY_EX_CAP) + SPDM_CAP_KEY_EX_CAP | SPDM_CAP_HBEAT_CAP | \ + SPDM_CAP_KEY_UPD_CAP) -/* ========================================================================== - * Buffer/Message Size Limits - * ========================================================================== */ +/* --- Buffer/Message Size Limits --- */ #define WOLFSPDM_MAX_MSG_SIZE 4096 /* Maximum SPDM message size */ #define WOLFSPDM_MAX_CERT_CHAIN 4096 /* Maximum certificate chain size */ #define WOLFSPDM_MAX_TRANSCRIPT 4096 /* Maximum transcript buffer */ #define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ -/* ========================================================================== - * MCTP Transport Constants (for TCP/socket transport) - * ========================================================================== */ +/* --- MCTP Transport Constants (for TCP/socket transport) --- */ #define MCTP_MESSAGE_TYPE_SPDM 0x05 /* SPDM over MCTP */ #define MCTP_MESSAGE_TYPE_SECURED 0x06 /* Secured SPDM over MCTP */ @@ -193,9 +184,42 @@ extern "C" { #define SOCKET_SPDM_COMMAND_NORMAL 0x00000001 #endif -/* ========================================================================== - * Key Derivation Labels (SPDM 1.2 per DSP0277) - * ========================================================================== */ +#ifndef NO_WOLFSPDM_MEAS +/* --- Measurement Constants (DSP0274 Section 10.11) --- */ + +/* MeasurementSummaryHashType (Param1 of GET_MEASUREMENTS) */ +#define SPDM_MEAS_SUMMARY_HASH_NONE 0x00 +#define SPDM_MEAS_SUMMARY_HASH_TCB 0x01 +#define SPDM_MEAS_SUMMARY_HASH_ALL 0xFF + +/* MeasurementOperation (Param2 of GET_MEASUREMENTS) */ +#define SPDM_MEAS_OPERATION_TOTAL_NUMBER 0x00 +#define SPDM_MEAS_OPERATION_ALL 0xFF + +/* Request signature bit in Param1 */ +#define SPDM_MEAS_REQUEST_SIG_BIT 0x01 + +/* DMTFSpecMeasurementValueType (DSP0274 Table 22) */ +#define SPDM_MEAS_VALUE_TYPE_IMMUTABLE_ROM 0x00 +#define SPDM_MEAS_VALUE_TYPE_MUTABLE_FW 0x01 +#define SPDM_MEAS_VALUE_TYPE_HW_CONFIG 0x02 +#define SPDM_MEAS_VALUE_TYPE_FW_CONFIG 0x03 +#define SPDM_MEAS_VALUE_TYPE_MEAS_MANIFEST 0x04 +#define SPDM_MEAS_VALUE_TYPE_VERSION 0x05 +#define SPDM_MEAS_VALUE_TYPE_RAW_BIT 0x80 /* Bit 7: raw vs digest */ + +/* Configurable limits (override with -D at compile time) */ +#ifndef WOLFSPDM_MAX_MEAS_BLOCKS +#define WOLFSPDM_MAX_MEAS_BLOCKS 16 +#endif +#ifndef WOLFSPDM_MAX_MEAS_VALUE_SIZE +#define WOLFSPDM_MAX_MEAS_VALUE_SIZE 64 /* Fits SHA-512; SHA-384 uses 48 */ +#endif + +#define WOLFSPDM_MEAS_BLOCK_HDR_SIZE 4 /* Index(1) + MeasSpec(1) + Size(2 LE) */ +#endif /* !NO_WOLFSPDM_MEAS */ + +/* --- Key Derivation Labels (SPDM 1.2 per DSP0277) --- */ #define SPDM_BIN_CONCAT_PREFIX_12 "spdm1.2 " #define SPDM_BIN_CONCAT_PREFIX_13 "spdm1.3 " @@ -209,6 +233,12 @@ extern "C" { #define SPDM_LABEL_FINISHED "finished" #define SPDM_LABEL_KEY "key" #define SPDM_LABEL_IV "iv" +#define SPDM_LABEL_UPDATE "traffic upd" + +/* KEY_UPDATE Operations (DSP0274 Section 10.9) */ +#define SPDM_KEY_UPDATE_OP_UPDATE_KEY 1 +#define SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS 2 +#define SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY 3 #ifdef __cplusplus }