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
195 changes: 195 additions & 0 deletions .github/workflows/spdm-emu-pqc-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
name: SPDM Emulator PQC (ML-DSA) 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.

on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
repository_dispatch:
types: [nightly-trigger]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
pqc-emu-test:
name: ${{ matrix.os }} (${{ matrix.arch }}) / dynamic-mem=${{ matrix.dynamic-mem }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
arch: x64
dynamic-mem: 'no'
- os: ubuntu-22.04
arch: x64
dynamic-mem: 'yes'
- os: ubuntu-24.04
arch: x64
dynamic-mem: 'no'
- os: ubuntu-24.04
arch: x64
dynamic-mem: 'yes'
- os: ubuntu-24.04-arm
arch: aarch64
dynamic-mem: 'no'
- os: ubuntu-24.04-arm
arch: aarch64
dynamic-mem: 'yes'
runs-on: ${{ matrix.os }}
timeout-minutes: 40
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake libtool cmake

- name: Compute cache period
id: cache-period
run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT

# --- wolfSSL master with ML-DSA (rebuilt to track upstream drift) ---
- name: Build wolfSSL master (--enable-mldsa)
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 \
--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)
run: |
./autogen.sh
./configure --with-wolfssl=$HOME/wolfssl-install \
--prefix=$HOME/wolfspdm-install --enable-mldsa \
${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }}
make -j"$(nproc)"
make install

- name: Run unit tests (includes ML-DSA verify)
run: make check
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib

# --- spdm-emu with OpenSSL backend (ML-DSA), cached per OS/arch ---
# Cache the whole build tree, not just build/bin: the OpenSSL-backed
# responder depends on the bundled OpenSSL libraries/providers built
# elsewhere under build/, so a build/bin-only cache restores a binary
# that fails mid-handshake. The full tree makes a restored build behave
# identically to a fresh one.
- name: Cache spdm-emu (openssl)
id: cache-spdm-emu
uses: actions/cache@v4
with:
path: ~/spdm-emu/build
key: spdm-emu-pqc-openssl-v3-${{ matrix.os }}-${{ matrix.arch }}-${{ steps.cache-period.outputs.biweekly }}

- name: Build spdm-emu (CRYPTO=openssl)
if: steps.cache-spdm-emu.outputs.cache-hit != 'true'
run: |
cd ~
git clone --depth 1 --recurse-submodules https://github.com/DMTF/spdm-emu.git
cd spdm-emu
mkdir build && cd build
cmake -DARCH=${{ matrix.arch }} -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=openssl ..
make copy_sample_key
make -j"$(nproc)" spdm_responder_emu

# --- ML-DSA interop: responder offers only ML-DSA, requester verifies.
# spdm-emu DataTransferSize is 0x1200 (4608 B): ML-DSA-44 (sig 2420)
# and ML-DSA-65 (3309) responses fit one message, so they complete
# without chunking. ML-DSA-87 (sig 4627) exceeds it and the responder
# chunks the response, which needs the SPDM 1.2 chunking engine
# (follow-on work) -- so 87 is covered by unit tests / cert parsing,
# not this over-the-wire job.
- name: ML-DSA 44/65 session + measurements + challenge
run: |
set -e
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

# Wait until the responder is accepting connections on port 2323.
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 ML_DSA_xx (responder), $2 cert dir, $3 demo label, then demo args.
# The OpenSSL-backed ML-DSA responder is slower to become ready than
# the mbedtls/ECDSA one, and it stops after a failed connection, so
# retry the whole case (restarting the responder) a few times.
run_case() {
local alg="$1" certdir="$2" label="$3"; shift 3
export SPDM_EMU_CERT_DIR="$certdir"
echo "::group::$alg $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 "$alg" \
--dhe SECP_384_R1 --aead AES_256_GCM \
>/tmp/pqc_emu_${alg}_${label}.log 2>&1 ) &
emu=$!
if wait_for_port; then
sleep 1
if "$DEMO" "$@" --ver 1.4 --debug; then rc=0; else rc=$?; fi
else
rc=1
fi
kill $emu 2>/dev/null || true
wait $emu 2>/dev/null || true
[ $rc -eq 0 ] && break
echo "--- responder log (attempt $attempt) ---"
cat /tmp/pqc_emu_${alg}_${label}.log || true
echo "attempt $attempt for $alg $label failed (rc=$rc), retrying"
sleep 1
done
echo "::endgroup::"
if [ $rc -ne 0 ]; then
echo "::error::$alg $label failed after retries (rc=$rc)"
exit $rc
fi
echo "$alg $label: OK"
}

for spec in "ML_DSA_44 mldsa44" "ML_DSA_65 mldsa65"; do
set -- $spec
alg="$1"; dir="$2"
run_case "$alg" "$dir" session --emu
run_case "$alg" "$dir" meas --meas
run_case "$alg" "$dir" challenge --challenge
done

- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: spdm-emu-pqc-logs-${{ matrix.os }}-${{ matrix.arch }}-dynmem-${{ matrix.dynamic-mem }}
path: |
config.log
test/*.log
/tmp/pqc_emu_*.log
retention-days: 5
24 changes: 24 additions & 0 deletions .github/workflows/wiki-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Sync docs to wiki

on:
push:
branches: [master, main]
paths: ['docs/**']
workflow_dispatch:

permissions:
contents: write

concurrency:
group: wiki-sync
cancel-in-progress: true

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Publish docs/ to wiki
uses: Andrew-Chen-Wang/github-wiki-action@50650fccf3a10f741995523cf9708c53cec8912a
with:
path: docs/
32 changes: 25 additions & 7 deletions .github/workflows/wolfssl-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,20 @@ jobs:
fi
echo "Latest stable wolfSSL: $LATEST"
echo "latest-stable=$LATEST" >> "$GITHUB_OUTPUT"
MATRIX=$(jq -nc --arg latest "$LATEST" '{
# ML-DSA (the wc_MlDsaKey context API wolfSPDM verifies with) lands
# post-v5.9.1-stable. Enable it for master and any latest-stable
# strictly newer than v5.9.1-stable; v5.8.0 floor stays classical.
PQC_LATEST=false
NEWEST=$(printf '%s\n%s\n' "v5.9.1-stable" "$LATEST" | sort -V | tail -n1)
if [ "$NEWEST" = "$LATEST" ] && [ "$LATEST" != "v5.9.1-stable" ]; then
PQC_LATEST=true
fi
echo "PQC for latest-stable: $PQC_LATEST"
MATRIX=$(jq -nc --arg latest "$LATEST" --argjson pqclatest "$PQC_LATEST" '{
include: [
{"wolfssl-version":"v5.8.0-stable","wolfssl-ref":"v5.8.0-stable","cache-key":"wolfssl-spdm-v5.8.0-v1"},
{"wolfssl-version":$latest,"wolfssl-ref":$latest,"cache-key":("wolfssl-spdm-" + $latest + "-v1")},
{"wolfssl-version":"master","wolfssl-ref":"master","cache-key":""}
{"wolfssl-version":"v5.8.0-stable","wolfssl-ref":"v5.8.0-stable","cache-key":"wolfssl-spdm-v5.8.0-v2","pqc":false},
{"wolfssl-version":$latest,"wolfssl-ref":$latest,"cache-key":("wolfssl-spdm-" + $latest + "-pqc" + ($pqclatest|tostring) + "-v2"),"pqc":$pqclatest},
{"wolfssl-version":"master","wolfssl-ref":"master","cache-key":"","pqc":true}
]
}')
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -84,9 +93,11 @@ jobs:
git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} \
https://github.com/wolfSSL/wolfssl.git
cd wolfssl
PQC_FLAGS=""
if [ "${{ matrix.pqc }}" = "true" ]; then PQC_FLAGS="--enable-mldsa"; fi
./autogen.sh
./configure --enable-wolftpm --enable-ecc --enable-sha384 \
--enable-aesgcm --enable-hkdf --enable-sp \
--enable-aesgcm --enable-hkdf --enable-sp $PQC_FLAGS \
--prefix=$HOME/wolfssl-install
make -j"$(nproc)"
make install
Expand All @@ -98,8 +109,13 @@ jobs:

- name: Build wolfSPDM
run: |
# On PQC-capable wolfSSL, pass --enable-mldsa so configure ERRORS if
# the wc_MlDsaKey API is somehow missing, instead of silently
# dropping ML-DSA coverage.
MLDSA_FLAG=""
if [ "${{ matrix.pqc }}" = "true" ]; then MLDSA_FLAG="--enable-mldsa"; fi
./autogen.sh
./configure --with-wolfssl=$HOME/wolfssl-install
./configure --with-wolfssl=$HOME/wolfssl-install $MLDSA_FLAG
make -j"$(nproc)"

- name: Run unit tests
Expand All @@ -109,9 +125,11 @@ jobs:

- name: Build with --enable-dynamic-mem
run: |
MLDSA_FLAG=""
if [ "${{ matrix.pqc }}" = "true" ]; then MLDSA_FLAG="--enable-mldsa"; fi
make distclean || true
./autogen.sh
./configure --with-wolfssl=$HOME/wolfssl-install --enable-dynamic-mem
./configure --with-wolfssl=$HOME/wolfssl-install --enable-dynamic-mem $MLDSA_FLAG
make -j"$(nproc)"

- name: Run unit tests (dynamic-mem)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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
- **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 @@ -42,6 +43,8 @@ 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.

## Build

```bash
Expand Down
9 changes: 6 additions & 3 deletions config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H

/* Define to 1 if you have the `wolfssl' library (-lwolfssl). */
/* Define to 1 if you have the 'wolfssl' library (-lwolfssl). */
#undef HAVE_LIBWOLFSSL

/* Define to 1 if you have the <stdint.h> header file. */
Expand Down Expand Up @@ -57,7 +57,7 @@
/* Define to the version of this package. */
#undef PACKAGE_VERSION

/* Define to 1 if all of the C90 standard headers exist (not just the ones
/* Define to 1 if all of the C89 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
#undef STDC_HEADERS
Expand All @@ -71,6 +71,9 @@
/* Enable dynamic memory allocation */
#undef WOLFSPDM_DYNAMIC_MEMORY

/* Disable ML-DSA support */
#undef WOLFSPDM_NO_MLDSA

/* 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 All @@ -86,7 +89,7 @@
#define below would cause a syntax error. */
#undef _UINT8_T

/* Define to `unsigned int' if <sys/types.h> does not define. */
/* Define as 'unsigned int' if <stddef.h> doesn't define. */
#undef size_t

/* Define to the type of an unsigned integer type of width exactly 16 bits if
Expand Down
Loading
Loading