Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
| 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. Runs 18 tests (6 scenarios x SPDM 1.2/1.3/1.4): session establishment, signed/unsigned measurements, challenge authentication, heartbeat, key update. Matrix across ubuntu-22.04 (x64), ubuntu-24.04 (x64), ubuntu-24.04-arm (aarch64). |
| SPDM Emulator PQC Test | `spdm-emu-pqc-test.yml` | Post-quantum interop against spdm-emu (OpenSSL backend, wolfSSL master) on the x64 + aarch64 matrix: ML-DSA-44/65/87 signatures, ML-KEM-512/768/1024 key exchange, and a fully post-quantum leg (ML-KEM-768 + ML-DSA-65/87, also exercising chunking) for session/measurements/challenge. Also builds the ML-KEM-only config. |
190 changes: 176 additions & 14 deletions .github/workflows/spdm-emu-pqc-test.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: SPDM Emulator PQC (ML-DSA) Test
name: SPDM Emulator PQC Test

# Post-quantum interop, mirroring the classical SPDM Emulator Integration Test
# matrix (ubuntu 22.04/24.04 x64 + 24.04 aarch64, each with static and dynamic
# memory). The wc_MlDsaKey context API wolfSPDM verifies with lands
# post-v5.9.1-stable, so this job pins wolfSSL master. libspdm's ML-DSA is only
# in its OpenSSL backend (the mbedtls backend stubs it out), so spdm-emu is
# built with CRYPTO=openssl.
# Post-quantum interop (ML-DSA signing + ML-KEM key exchange, and a full-PQ
# combination), mirroring the classical SPDM Emulator Integration Test matrix
# (ubuntu 22.04/24.04 x64 + 24.04 aarch64, each with static and dynamic memory).
# The wc_MlDsaKey context API wolfSPDM verifies with lands post-v5.9.1-stable,
# so this job pins wolfSSL master. libspdm's ML-DSA/ML-KEM are only in its
# OpenSSL backend (the mbedtls backend stubs them out), so spdm-emu is built
# with CRYPTO=openssl.

on:
push:
Expand Down Expand Up @@ -62,30 +63,46 @@ jobs:
run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT

# --- wolfSSL master with ML-DSA (rebuilt to track upstream drift) ---
- name: Build wolfSSL master (--enable-mldsa)
- name: Build wolfSSL master (--enable-mldsa --enable-mlkem)
run: |
cd ~
git clone --depth 1 --branch master https://github.com/wolfSSL/wolfssl.git
cd wolfssl
./autogen.sh
./configure --enable-ecc --enable-sha384 --enable-aesgcm \
--enable-hkdf --enable-sp --enable-mldsa \
--enable-hkdf --enable-sp --enable-mldsa --enable-mlkem \
--prefix=$HOME/wolfssl-install
make -j"$(nproc)"
make install
grep LIBWOLFSSL_VERSION_STRING $HOME/wolfssl-install/include/wolfssl/version.h

# --- wolfSPDM with ML-DSA asserted on, static or dynamic memory ---
- name: Build and install wolfSPDM (--enable-mldsa)
# --- ML-KEM without ML-DSA: a real config (ML-KEM/Kyber is commonly
# enabled for TLS hybrid KEX while ML-DSA is not). Exercises the
# ML-KEM-only WOLFSPDM_CTX_STATIC_SIZE budget and unit tests, which
# the combined build below does not. Cleaned up before the full build. ---
- name: Build + test wolfSPDM ML-KEM-only (--disable-mldsa --enable-mlkem)
run: |
./autogen.sh
./configure --with-wolfssl=$HOME/wolfssl-install \
--prefix=$HOME/wolfspdm-install --enable-mldsa \
--disable-mldsa --enable-mlkem \
${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }}
make -j"$(nproc)"
make check
make distclean
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib

# --- wolfSPDM with ML-DSA + ML-KEM asserted on, static or dynamic memory ---
- name: Build and install wolfSPDM (--enable-mldsa --enable-mlkem)
run: |
./autogen.sh
./configure --with-wolfssl=$HOME/wolfssl-install \
--prefix=$HOME/wolfspdm-install --enable-mldsa --enable-mlkem \
${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }}
make -j"$(nproc)"
make install

- name: Run unit tests (includes ML-DSA verify)
- name: Run unit tests (includes ML-DSA verify + ML-KEM decap)
run: make check
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib
Expand All @@ -101,7 +118,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/spdm-emu/build
key: spdm-emu-pqc-openssl-v3-${{ matrix.os }}-${{ matrix.arch }}-${{ steps.cache-period.outputs.biweekly }}
key: spdm-emu-pqc-openssl-v4-${{ matrix.os }}-${{ matrix.arch }}-${{ steps.cache-period.outputs.biweekly }}

- name: Build spdm-emu (CRYPTO=openssl)
if: steps.cache-spdm-emu.outputs.cache-hit != 'true'
Expand Down Expand Up @@ -161,6 +178,7 @@ jobs:
fi
kill $emu 2>/dev/null || true
wait $emu 2>/dev/null || true
pkill -f spdm_responder_emu 2>/dev/null || true
[ $rc -eq 0 ] && break
echo "--- responder log (attempt $attempt) ---"
cat /tmp/pqc_emu_${alg}_${label}.log || true
Expand Down Expand Up @@ -190,6 +208,150 @@ jobs:
fi
echo "All ML-DSA 44/65/87 interop cases passed."

# --- ML-KEM interop: responder offers only ML-KEM (--dhe NONE) with ECDSA
# signing, so the handshake performs ML-KEM key exchange in isolation.
# The requester forces KEM-only advertisement with --kex. ek (<=1568 B)
# and ciphertext c (<=1568 B) fit one message under the responder's
# DataTransferSize, so this tests the KEM path without chunking.
- name: ML-KEM 512/768/1024 session + measurements + challenge
run: |
export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib
export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin
export SPDM_EMU_CERT_DIR=ecp384
DEMO=./examples/spdm_demo
FAILURES=""

wait_for_port() {
local i
for i in $(seq 1 50); do
if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi
sleep 0.2
done
return 1
}

# $1 emu KEM name, $2 demo --kex name, $3 label, then demo args.
run_kem() {
local kem="$1" kex="$2" label="$3"; shift 3
echo "::group::$kem $label"
local attempt rc=1 emu
for attempt in 1 2 3; do
( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \
--hash SHA_384 --asym ECDSA_P384 --pqc_asym NONE \
--dhe NONE --kem "$kem" --aead AES_256_GCM \
>/tmp/pqc_emu_${kem}_${label}.log 2>&1 ) &
emu=$!
if wait_for_port; then
sleep 1
if "$DEMO" "$@" --ver 1.4 --kex "$kex" --debug; then rc=0; else rc=$?; fi
else
rc=1
fi
kill $emu 2>/dev/null || true
wait $emu 2>/dev/null || true
pkill -f spdm_responder_emu 2>/dev/null || true
[ $rc -eq 0 ] && break
echo "--- responder log (attempt $attempt) ---"
cat /tmp/pqc_emu_${kem}_${label}.log || true
echo "attempt $attempt for $kem $label failed (rc=$rc), retrying"
sleep 1
done
echo "::endgroup::"
if [ $rc -ne 0 ]; then
echo "::error::$kem $label failed after retries (rc=$rc)"
FAILURES="$FAILURES $kem/$label"
else
echo "$kem $label: OK"
fi
}

for spec in "ML_KEM_512 mlkem512" "ML_KEM_768 mlkem768" \
"ML_KEM_1024 mlkem1024"; do
set -- $spec
kem="$1"; kex="$2"
run_kem "$kem" "$kex" session --emu
run_kem "$kem" "$kex" meas --meas
run_kem "$kem" "$kex" challenge --challenge
done

if [ -n "$FAILURES" ]; then
echo "::error::ML-KEM interop failures:$FAILURES"
exit 1
fi
echo "All ML-KEM 512/768/1024 interop cases passed."

# --- Full post-quantum handshake: ML-KEM-768 key exchange + ML-DSA
# signing together, no classical asymmetric crypto. ML-DSA-65 fits one
# message; ML-DSA-87 (sig 4627 B) + ciphertext c (1088 B) exceeds the
# DataTransferSize, so this case also exercises CHUNK_GET reassembly -
# ML-KEM + ML-DSA + chunking in a single handshake.
- name: Full PQ (ML-KEM-768 + ML-DSA 65/87) session + measurements + challenge
run: |
export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib
export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin
DEMO=./examples/spdm_demo
FAILURES=""

wait_for_port() {
local i
for i in $(seq 1 50); do
if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi
sleep 0.2
done
return 1
}

# $1 pqc_asym (ML_DSA_xx), $2 cert dir, $3 label, then demo args. KEM is
# ML-KEM-768 throughout; the requester forces it with --kex mlkem768.
run_pq() {
local pqc="$1" certdir="$2" label="$3"; shift 3
export SPDM_EMU_CERT_DIR="$certdir"
echo "::group::$pqc+ML_KEM_768 $label"
local attempt rc=1 emu
for attempt in 1 2 3; do
( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \
--hash SHA_384 --asym NONE --pqc_asym "$pqc" \
--dhe NONE --kem ML_KEM_768 --aead AES_256_GCM \
>/tmp/pqc_emu_${pqc}_kem_${label}.log 2>&1 ) &
emu=$!
if wait_for_port; then
sleep 1
if "$DEMO" "$@" --ver 1.4 --kex mlkem768 --debug; then rc=0; else rc=$?; fi
else
rc=1
fi
kill $emu 2>/dev/null || true
wait $emu 2>/dev/null || true
pkill -f spdm_responder_emu 2>/dev/null || true
[ $rc -eq 0 ] && break
echo "--- responder log (attempt $attempt) ---"
cat /tmp/pqc_emu_${pqc}_kem_${label}.log || true
echo "attempt $attempt for $pqc+KEM $label failed (rc=$rc), retrying"
sleep 1
done
echo "::endgroup::"
if [ $rc -ne 0 ]; then
echo "::error::$pqc+ML_KEM_768 $label failed after retries (rc=$rc)"
FAILURES="$FAILURES $pqc+kem/$label"
else
echo "$pqc+ML_KEM_768 $label: OK"
fi
}

for spec in "ML_DSA_65 mldsa65" "ML_DSA_87 mldsa87"; do
set -- $spec
pqc="$1"; dir="$2"
run_pq "$pqc" "$dir" session --emu
run_pq "$pqc" "$dir" meas --meas
run_pq "$pqc" "$dir" challenge --challenge
done

if [ -n "$FAILURES" ]; then
echo "::error::Full-PQ interop failures:$FAILURES"
exit 1
fi
echo "All full-PQ (ML-KEM + ML-DSA) interop cases passed."

- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ wolfSPDM is a lightweight C library implementing [SPDM 1.2 / 1.3 / 1.4](https://
- **Standard SPDM 1.2 / 1.3 / 1.4 requester** per DMTF DSP0274 and DSP0277
- **Algorithm Set B fixed:** ECDSA P-384, ECDHE P-384, SHA-384, AES-256-GCM, HKDF-SHA384
- **Post-quantum signatures (SPDM 1.4):** optional ML-DSA-44 / 65 / 87 (FIPS 204), dual-stacked with ECDSA P-384 — see the [Post-Quantum ML-DSA](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-DSA) wiki page
- **Post-quantum key exchange (SPDM 1.4):** optional ML-KEM-512 / 768 / 1024 (FIPS 203), advertised alongside ECDHE P-384 — see the [Post-Quantum ML-KEM](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-KEM) wiki page
- **Fully post-quantum SPDM handshake:** ML-KEM key exchange + ML-DSA authentication (no classical asymmetric crypto), proven end-to-end against spdm-emu
- **Zero-malloc by default:** static memory, ~32 KB context, ideal for constrained/embedded environments
- **Optional `--enable-dynamic-mem`** for heap-allocated contexts on small-stack platforms
- **Full session lifecycle:** key exchange, finish, encrypted messaging, heartbeat keep-alive, key update
Expand Down Expand Up @@ -43,7 +45,7 @@ sudo ldconfig

`--enable-sp` enables Single Precision math with optimized ECC P-384, required for SPDM Algorithm Set B on ARM64 and other constrained targets. `--enable-all` works as a superset.

For post-quantum ML-DSA signatures, add `--enable-mldsa` and use wolfSSL master (or a release that ships the `wc_MlDsaKey` context API). wolfSPDM then auto-enables ML-DSA; `./configure --disable-mldsa` forces it off.
For post-quantum cryptography, add `--enable-mldsa` (signatures, FIPS 204) and/or `--enable-mlkem` (key exchange, FIPS 203) to wolfSSL — use wolfSSL master (or a release that ships the `wc_MlDsaKey` context API and `wc_MlKemKey` API). wolfSPDM then auto-enables each when the linked wolfSSL provides it; `./configure --disable-mldsa` / `--disable-mlkem` force them off. Enabling both gives a fully post-quantum SPDM handshake (ML-KEM key exchange + ML-DSA authentication).

## Build

Expand All @@ -60,6 +62,8 @@ make check
|---|---|
| `--enable-debug` | Debug output with `-g -O0` (default: `-O2`) |
| `--enable-dynamic-mem` | Use heap allocation for `WOLFSPDM_CTX` (default: static) |
| `--disable-mldsa` / `--disable-mlkem` | Force off ML-DSA signatures / ML-KEM key exchange (default: auto-follow wolfSSL) |
| `--disable-chunking` | Compile out SPDM 1.2 message chunking (default: enabled) |
| `--with-wolfssl=PATH` | wolfSSL installation path |

### Memory Modes
Expand Down Expand Up @@ -148,6 +152,9 @@ Full documentation is available in the [GitHub Wiki](https://github.com/aidangar
- [Supported Operations](https://github.com/aidangarske/wolfSPDM/wiki/Supported-Operations): SPDM operation coverage and API mapping
- [API Reference](https://github.com/aidangarske/wolfSPDM/wiki/API-Reference): Public function groups and common error-code references
- [Configuration and Macros](https://github.com/aidangarske/wolfSPDM/wiki/Configuration-and-Macros): Configure flags and compile-time feature controls
- [Post-Quantum ML-DSA](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-DSA): Post-quantum signatures (FIPS 204)
- [Post-Quantum ML-KEM](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-KEM): Post-quantum key exchange (FIPS 203) and the fully post-quantum handshake
- [Message Chunking](https://github.com/aidangarske/wolfSPDM/wiki/Message-Chunking): SPDM 1.2 CHUNK_GET reassembly for large responses
- [Testing and CI](https://github.com/aidangarske/wolfSPDM/wiki/Testing-and-CI): Unit tests, emulator integration tests, and CI workflow coverage
- [Project Structure](https://github.com/aidangarske/wolfSPDM/wiki/Project-Structure): Source layout and module responsibilities
- [Attestation Notes](https://github.com/aidangarske/wolfSPDM/wiki/Attestation-Notes): Measurement and challenge attestation behavior
Expand Down
3 changes: 3 additions & 0 deletions config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
/* Disable ML-DSA support */
#undef WOLFSPDM_NO_MLDSA

/* Disable ML-KEM support */
#undef WOLFSPDM_NO_MLKEM

/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
<pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
#define below would cause a syntax error. */
Expand Down
58 changes: 58 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,63 @@ else
mldsa_status=disabled
fi

# ML-KEM (FIPS 203) post-quantum key exchange (DSP0274 1.4). Default: auto-follow
# the linked wolfSSL - on when it reports WOLFSSL_HAVE_MLKEM, off otherwise.
AC_ARG_ENABLE([mlkem],
[AS_HELP_STRING([--disable-mlkem], [Disable ML-KEM support even if wolfSSL has it])],
[enable_mlkem=$enableval],
[enable_mlkem=auto])

AC_MSG_CHECKING([whether wolfSSL provides ML-KEM])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/settings.h>
#ifndef WOLFSSL_HAVE_MLKEM
#error "no mlkem"
#endif
int main(void) { return 0; }
]])],
[have_wolfssl_mlkem=yes],
[have_wolfssl_mlkem=no])
AC_MSG_RESULT([$have_wolfssl_mlkem])

# wolfSPDM uses the wc_MlKemKey_* API (keygen, encapsulation-key encode, and
# decapsulation). Capability-test for it rather than gating on a version number.
have_mlkem_api=no
if test "x$enable_mlkem" != "xno" && test "x$have_wolfssl_mlkem" = "xyes"; then
AC_MSG_CHECKING([for the wc_MlKemKey API])
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/wc_mlkem.h>
]], [[
MlKemKey k; word32 len = 0;
(void)wc_MlKemKey_Init(&k, 0, 0, 0);
(void)wc_MlKemKey_MakeKey(&k, 0);
(void)wc_MlKemKey_EncodePublicKey(&k, 0, 0);
(void)wc_MlKemKey_PublicKeySize(&k, &len);
(void)wc_MlKemKey_CipherTextSize(&k, &len);
(void)wc_MlKemKey_SharedSecretSize(&k, &len);
(void)wc_MlKemKey_Decapsulate(&k, 0, 0, 0);
(void)wc_MlKemKey_Free(&k);
]])],
[have_mlkem_api=yes],
[have_mlkem_api=no])
AC_MSG_RESULT([$have_mlkem_api])
fi

if test "x$enable_mlkem" = "xno"; then
AC_DEFINE([WOLFSPDM_NO_MLKEM], [1], [Disable ML-KEM support])
mlkem_status=disabled
elif test "x$have_mlkem_api" = "xyes"; then
mlkem_status=enabled
elif test "x$enable_mlkem" = "xyes"; then
AC_MSG_ERROR([--enable-mlkem requires a wolfSSL with the wc_MlKemKey API, built with --enable-mlkem. Use --disable-mlkem or update wolfSSL.])
else
AC_DEFINE([WOLFSPDM_NO_MLKEM], [1], [Disable ML-KEM support])
mlkem_status=disabled
fi

# Output files
AC_CONFIG_FILES([Makefile wolfspdm.pc])
AC_OUTPUT
Expand All @@ -160,5 +217,6 @@ echo " Debug: $enable_debug"
echo " Dynamic mem: $enable_dynamic_mem"
echo " Chunking: $chunking_status"
echo " ML-DSA: $mldsa_status"
echo " ML-KEM: $mlkem_status"
echo " wolfSSL: ${WOLFSSL_DIR:-system}"
echo ""
Loading
Loading