mirror of https://github.com/wolfSSL/wolfTPM.git
Merge pull request #482 from aidangarske/spdm-runners
Add hardware SPDM CI runner + fix SPDM auto-connect regressionpull/484/head
commit
35629e96df
|
|
@ -0,0 +1,199 @@
|
|||
name: Hardware SPDM Test
|
||||
|
||||
# Runs examples/spdm/spdm_test.sh on the self-hosted Pi with a real TPM.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'src/spdm/**'
|
||||
- 'wolftpm/spdm/**'
|
||||
- 'examples/spdm/**'
|
||||
- 'src/tpm2_wrap.c'
|
||||
- 'src/tpm2_spdm.c'
|
||||
- 'src/tpm2.c'
|
||||
- 'hal/tpm_io_linux.c'
|
||||
- 'configure.ac'
|
||||
- '.github/workflows/hw-spdm-test.yml'
|
||||
- 'scripts/hw-runner-health-check.sh'
|
||||
- 'tests/unit_tests.c'
|
||||
pull_request_target:
|
||||
branches: [master]
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- 'src/spdm/**'
|
||||
- 'wolftpm/spdm/**'
|
||||
- 'examples/spdm/**'
|
||||
- 'src/tpm2_wrap.c'
|
||||
- 'src/tpm2_spdm.c'
|
||||
- 'src/tpm2.c'
|
||||
- 'hal/tpm_io_linux.c'
|
||||
- 'configure.ac'
|
||||
- '.github/workflows/hw-spdm-test.yml'
|
||||
- 'scripts/hw-runner-health-check.sh'
|
||||
- 'tests/unit_tests.c'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
# Serialize runs; hardware state is shared.
|
||||
concurrency:
|
||||
group: hw-spdm-runner
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
hw-spdm:
|
||||
if: >
|
||||
github.event_name != 'pull_request_target' ||
|
||||
contains(fromJSON('["OWNER","MEMBER"]'),
|
||||
github.event.pull_request.author_association)
|
||||
runs-on: [self-hosted, Linux, ARM64, wolftpm-spdm]
|
||||
timeout-minutes: 25
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- vendor: nuvoton
|
||||
expected_vendor: "NPCT75x"
|
||||
wolftpm_config: "--enable-spdm --enable-nuvoton --enable-debug"
|
||||
spi_cs: "0"
|
||||
modes: "nuvoton"
|
||||
- vendor: nations
|
||||
expected_vendor: "NS350"
|
||||
wolftpm_config: "--enable-spdm --enable-nations --enable-debug"
|
||||
spi_cs: "1"
|
||||
modes: "nations nations-psk"
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout wolfTPM
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Hardware health check
|
||||
id: health
|
||||
continue-on-error: true
|
||||
run: bash scripts/hw-runner-health-check.sh "${{ matrix.spi_cs }}"
|
||||
|
||||
- name: Warn if health check failed
|
||||
if: steps.health.outcome == 'failure'
|
||||
run: echo "::warning::Runner hardware check failed on ${{ runner.name }}. Skipping hardware tests."
|
||||
|
||||
- name: Clean workspace (preserve wolfssl cache dir)
|
||||
if: steps.health.outcome == 'success'
|
||||
run: git clean -xdf
|
||||
|
||||
- name: Resolve wolfSSL SHA and choose prefix
|
||||
id: wolfssl
|
||||
if: steps.health.outcome == 'success'
|
||||
run: |
|
||||
MAX_CACHE_AGE_SECS=86400
|
||||
SHA=$(curl -sf https://api.github.com/repos/wolfssl/wolfssl/commits/master | jq -r .sha)
|
||||
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
||||
HIT=false
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
PREFIX="$HOME/wolfssl-install"
|
||||
if [ -f "$PREFIX/.sha" ] && [ "$(cat "$PREFIX/.sha")" = "$SHA" ]; then
|
||||
AGE=$(( $(date +%s) - $(stat -c %Y "$PREFIX/.sha") ))
|
||||
[ "$AGE" -lt "$MAX_CACHE_AGE_SECS" ] && HIT=true
|
||||
fi
|
||||
else
|
||||
PREFIX="$RUNNER_TEMP/wolfssl-install"
|
||||
mkdir -p "$PREFIX"
|
||||
fi
|
||||
echo "prefix=$PREFIX" >> "$GITHUB_OUTPUT"
|
||||
echo "hit=$HIT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout wolfSSL
|
||||
if: steps.health.outcome == 'success' && steps.wolfssl.outputs.hit != 'true'
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
repository: wolfssl/wolfssl
|
||||
path: wolfssl
|
||||
ref: ${{ steps.wolfssl.outputs.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build & install wolfSSL (user prefix, no sudo)
|
||||
if: steps.health.outcome == 'success' && steps.wolfssl.outputs.hit != 'true'
|
||||
working-directory: ./wolfssl
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --prefix=${{ steps.wolfssl.outputs.prefix }} \
|
||||
--enable-wolftpm --enable-ecc --enable-sha384 \
|
||||
--enable-aesgcm --enable-hkdf --enable-sp
|
||||
make -j"$(nproc)"
|
||||
make install
|
||||
echo '${{ steps.wolfssl.outputs.sha }}' > '${{ steps.wolfssl.outputs.prefix }}/.sha'
|
||||
|
||||
- name: Build wolfTPM (${{ matrix.vendor }})
|
||||
if: steps.health.outcome == 'success'
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure ${{ matrix.wolftpm_config }} \
|
||||
CPPFLAGS='-DTPM2_SPI_DEV_CS="${{ matrix.spi_cs }}"' \
|
||||
CFLAGS="-I${{ steps.wolfssl.outputs.prefix }}/include" \
|
||||
LDFLAGS="-L${{ steps.wolfssl.outputs.prefix }}/lib"
|
||||
make -j"$(nproc)"
|
||||
|
||||
- name: Detect ${{ matrix.vendor }} TPM on SPI CS ${{ matrix.spi_cs }}
|
||||
id: detect
|
||||
if: steps.health.outcome == 'success'
|
||||
env:
|
||||
LD_LIBRARY_PATH: ${{ steps.wolfssl.outputs.prefix }}/lib
|
||||
run: |
|
||||
EXPECTED='${{ matrix.expected_vendor }}'
|
||||
# caps may hang or error if no chip is at this CS; time-bound it.
|
||||
OUT=$(timeout 15 ./examples/wrap/caps 2>&1 || true)
|
||||
echo "$OUT"
|
||||
if echo "$OUT" | grep -q "Vendor ${EXPECTED}"; then
|
||||
echo "present=true" >> "$GITHUB_OUTPUT"
|
||||
echo "[detect] ${EXPECTED} TPM found on CS ${{ matrix.spi_cs }}"
|
||||
else
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Warn if ${{ matrix.vendor }} TPM not present
|
||||
if: steps.health.outcome == 'success' && steps.detect.outputs.present == 'false'
|
||||
run: echo "::warning::${{ matrix.expected_vendor }} not detected on SPI CS ${{ matrix.spi_cs }}. Skipping ${{ matrix.vendor }} SPDM tests — wire the chip and re-run, or ignore if this vendor isn't installed on this runner."
|
||||
|
||||
- name: Run SPDM hardware tests (${{ matrix.vendor }})
|
||||
if: steps.health.outcome == 'success' && steps.detect.outputs.present == 'true'
|
||||
env:
|
||||
LD_LIBRARY_PATH: ${{ steps.wolfssl.outputs.prefix }}/lib
|
||||
run: |
|
||||
set -e
|
||||
for mode in ${{ matrix.modes }}; do
|
||||
echo "=== spdm_test.sh mode=$mode ==="
|
||||
./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl "$mode" \
|
||||
2>&1 | tee "spdm-${{ matrix.vendor }}-${mode}.log"
|
||||
done
|
||||
|
||||
- name: Post-job cleanup
|
||||
if: always() && steps.health.outcome == 'success' && steps.detect.outputs.present == 'true'
|
||||
env:
|
||||
LD_LIBRARY_PATH: ${{ steps.wolfssl.outputs.prefix }}/lib
|
||||
run: |
|
||||
pgrep -u "$(id -u)" -a | grep -vE '(Runner\.Listener|Runner\.Worker|actions-runner)' || true
|
||||
pkill -u "$(id -u)" -f 'spdm_ctrl|unit\.test|caps$' 2>/dev/null || true
|
||||
sleep 1
|
||||
gpioset gpiochip0 4=0 2>/dev/null && sleep 0.1 && gpioset gpiochip0 4=1 2>/dev/null || true
|
||||
sleep 2
|
||||
./examples/spdm/spdm_ctrl --connect --unlock 2>/dev/null || true
|
||||
./examples/management/flush 2>/dev/null || true
|
||||
echo "[cleanup] done"
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: hw-spdm-logs-${{ matrix.vendor }}
|
||||
path: |
|
||||
*.log
|
||||
config.log
|
||||
retention-days: 14
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env bash
|
||||
# Pre-flight check for the self-hosted hardware SPDM runner.
|
||||
# Fails fast (before the 10-minute build) if required device nodes aren't
|
||||
# present or aren't accessible to the current user.
|
||||
#
|
||||
# Usage: hw-runner-health-check.sh <expected_spi_cs_number>
|
||||
set -euo pipefail
|
||||
|
||||
CS="${1:?usage: $0 <spi_cs_number>}"
|
||||
SPIDEV="/dev/spidev0.${CS}"
|
||||
GPIOCHIP="/dev/gpiochip0"
|
||||
|
||||
echo "[health] runner user: $(id)"
|
||||
|
||||
if [ ! -c "$SPIDEV" ]; then
|
||||
echo "[health] FAIL: $SPIDEV missing. Is SPI enabled in config.txt? Is the TPM wired to CS${CS}?"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -r "$SPIDEV" ] || [ ! -w "$SPIDEV" ]; then
|
||||
echo "[health] FAIL: $SPIDEV not rw for $(whoami). Add runner user to 'spi' group."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -c "$GPIOCHIP" ]; then
|
||||
echo "[health] FAIL: $GPIOCHIP missing."
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v gpioset >/dev/null; then
|
||||
echo "[health] FAIL: gpioset not on PATH. Install the 'gpiod' package."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[health] OK: $SPIDEV accessible, $GPIOCHIP present, gpioset on PATH"
|
||||
|
|
@ -252,10 +252,15 @@ int wolfTPM2_SPDM_SecuredExchange(
|
|||
vdMsg, (word32)vdMsgSz, vdRsp, &vdRspSz);
|
||||
}
|
||||
|
||||
/* Parse VENDOR_DEFINED_RESPONSE to extract TPM response */
|
||||
/* Parse VENDOR_DEFINED_RESPONSE to extract TPM response.
|
||||
* ParseVendorDefined returns payload dataLen (>= 0) on success,
|
||||
* negative WOLFSPDM_E_* on failure. */
|
||||
if (rc == 0) {
|
||||
rc = wolfSPDM_ParseVendorDefined(vdRsp, vdRspSz,
|
||||
rspVdCode, rspPlain, rspSz);
|
||||
if (rc >= 0) {
|
||||
rc = 0; /* success - convert dataLen to success indicator */
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify response is for our TPM2_CMD request */
|
||||
|
|
|
|||
|
|
@ -292,7 +292,16 @@ int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* Vendor-specific connect handles SetTisIO, SetMode, and auto-generates
|
||||
* a host ephemeral key pair for mutual authentication (MutAuth=1).
|
||||
* Plain wolfTPM2_SpdmConnect() skips that setup and FINISH fails. */
|
||||
#if defined(WOLFSPDM_NUVOTON)
|
||||
rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0);
|
||||
#elif defined(WOLFSPDM_NATIONS)
|
||||
rc = wolfTPM2_SpdmConnectNations(dev, NULL, 0, NULL, 0);
|
||||
#else
|
||||
rc = wolfTPM2_SpdmConnect(dev);
|
||||
#endif
|
||||
if (rc != 0) {
|
||||
#ifdef DEBUG_WOLFTPM
|
||||
printf("SPDM auto-connect failed: %d\n", rc);
|
||||
|
|
|
|||
|
|
@ -91,6 +91,36 @@
|
|||
|
||||
#ifndef WOLFTPM2_NO_WRAPPER
|
||||
|
||||
#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \
|
||||
!defined(WOLFTPM2_NO_ASN)
|
||||
/* Query TPM_CAP_ALGS to see if a given algorithm is supported.
|
||||
* Returns 1 if supported, 0 otherwise. Used to skip test iterations on TPMs
|
||||
* that don't implement a given hash (e.g. Nuvoton NPCT75x lacks SHA512).
|
||||
* Guarded by the same ifdef as its only caller (test_wolfTPM2_EccSignVerifyDig)
|
||||
* so non-ECC builds don't trip -Werror=unused-function. */
|
||||
static int test_tpm_alg_supported(TPM_ALG_ID alg)
|
||||
{
|
||||
GetCapability_In in;
|
||||
GetCapability_Out out;
|
||||
word32 i;
|
||||
|
||||
XMEMSET(&in, 0, sizeof(in));
|
||||
XMEMSET(&out, 0, sizeof(out));
|
||||
in.capability = TPM_CAP_ALGS;
|
||||
in.property = alg;
|
||||
in.propertyCount = 1;
|
||||
if (TPM2_GetCapability(&in, &out) != TPM_RC_SUCCESS) {
|
||||
return 1; /* On error, assume supported and let the real call fail */
|
||||
}
|
||||
for (i = 0; i < out.capabilityData.data.algorithms.count; i++) {
|
||||
if (out.capabilityData.data.algorithms.algProperties[i].alg == alg) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* !WOLFTPM2_NO_WOLFCRYPT && HAVE_ECC && !WOLFTPM2_NO_ASN */
|
||||
|
||||
static void test_wolfTPM2_Init(void)
|
||||
{
|
||||
int rc;
|
||||
|
|
@ -1638,6 +1668,15 @@ static void test_wolfTPM2_EccSignVerifyDig(WOLFTPM2_DEV* dev,
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Skip if this TPM doesn't implement the requested hash alg. Some TPMs
|
||||
* (e.g. Nuvoton NPCT75x) only support a subset of hashes; the TPM rejects
|
||||
* Create with TPM_RC_SIZE param 1, not TPM_RC_HASH, so the existing
|
||||
* post-hoc skip-check can't catch it. Query capabilities up front. */
|
||||
if (!test_tpm_alg_supported(hashAlg)) {
|
||||
printf("Hash alg 0x%x not supported by TPM... Skipping\n", hashAlg);
|
||||
return;
|
||||
}
|
||||
|
||||
/* -- Use TPM key to sign and verify with wolfCrypt -- */
|
||||
/* Create ECC key for signing */
|
||||
rc = wolfTPM2_GetKeyTemplate_ECC_ex(&publicTemplate, hashAlg,
|
||||
|
|
|
|||
Loading…
Reference in New Issue