Skip to content
Open
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
13 changes: 7 additions & 6 deletions .github/docker/wolftpm-ci/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive \
WOLFTPM_CI_IMAGE=v1.1 \
WOLFTPM_CI_IMAGE=v1.2 \
CARGO_HOME=/opt/cargo \
RUSTUP_HOME=/opt/rustup

Expand All @@ -15,16 +15,17 @@ RUN sed -i "s|http://archive.ubuntu.com/ubuntu/|${UBUNTU_ARCHIVE_MIRROR}|g" \
&& apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o APT::Update::Error-Mode=any update \
&& apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 install --no-install-recommends -y \
autoconf automake bison build-essential ca-certificates ccache \
clang clang-14 clang-15 clang-17 clang-tools cmake curl \
clang clang-14 clang-15 clang-17 clang-tools cmake curl dbus \
device-tree-compiler dfu-util file flex g++-11 g++-12 g++-13 \
g++-multilib gcc-11 gcc-12 gcc-13 gcc-arm-none-eabi gcc-multilib \
git gperf iproute2 jq libcapstone-dev libclang-18-dev \
libclang-rt-18-dev libdw-dev libmagic1 libnewlib-arm-none-eabi \
libsdl2-dev libssl-dev libtool libtss2-tcti-mssim0 \
mono-mcs mono-tools-devel ninja-build nunit nunit-console openssl \
libsdl2-dev libssl-dev libtool libtpm2-pkcs11-1 libtpm2-pkcs11-tools \
libtss2-tcti-mssim0 libtss2-tcti-tabrmd0 \
mono-mcs mono-tools-devel ninja-build nunit nunit-console opensc openssl \
patch pkg-config procps psmisc python3 python3-dev python3-pip \
python3-venv rsync sudo tpm2-tools unzip util-linux valgrind wget \
xxd xz-utils zip \
python3-venv rsync sudo tpm2-abrmd tpm2-tools unzip util-linux valgrind \
wget xxd xz-utils zip \
&& rm -rf /var/lib/apt/lists/*

# Keep the Python tools away from Ubuntu's externally managed interpreter.
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/fwtpm-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,74 @@ jobs:
# 77 is the autotools "skip" code; treat it as a pass.
[ "${rc:-0}" -eq 0 ] || [ "${rc:-0}" -eq 77 ]

# ----------------------------------------------------------------
# tpm2-pkcs11 against fwtpm_server, directly and through tpm2-abrmd.
# wolfSSL allows a zero ECDSA hash so an all-zero digest is signed, as on
# the reference TPM.
# ----------------------------------------------------------------
fwtpm-tpm2-pkcs11:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/wolfssl/wolftpm-ci:v1.2
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- name: classic
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
- name: v185
wolftpm_config: --enable-fwtpm --enable-swtpm --enable-v185
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental
steps:
- name: Checkout wolfTPM
uses: actions/checkout@v4
with:
submodules: true

- name: Setup wolfSSL
uses: ./.github/actions/setup-wolfssl
with:
configure-flags: ${{ matrix.wolfssl_config }}
cflags: -DWC_RSA_NO_PADDING -DWC_ALLOW_ECC_ZERO_HASH

- name: Build wolfTPM
run: |
./autogen.sh
./configure ${{ matrix.wolftpm_config }}
make -j"$(nproc)"

- name: Run tpm2-pkcs11 tests
run: |
./src/fwtpm/fwtpm_server --clear > /tmp/fwtpm_pkcs11.log 2>&1 &
server_pid=$!
trap 'kill $server_pid $abrmd_pid 2>/dev/null || true' EXIT
sleep 1
kill -0 "$server_pid"
tpm2_startup -c -T mssim:host=localhost,port=2321

echo "=== direct (mssim) ==="
scripts/tpm2_pkcs11_test.sh

echo "=== through tpm2-abrmd ==="
export DBUS_SESSION_BUS_ADDRESS=$(dbus-daemon --session --fork \
--print-address)
tpm2-abrmd --session --allow-root \
--tcti=mssim:host=localhost,port=2321 > /tmp/abrmd.log 2>&1 &
abrmd_pid=$!
sleep 2
kill -0 "$abrmd_pid"
TPM2_PKCS11_TCTI=tabrmd:bus_type=session scripts/tpm2_pkcs11_test.sh

- name: Print logs on failure
if: failure()
run: |
cat /tmp/fwtpm_pkcs11.log /tmp/abrmd.log 2>/dev/null || true

# ----------------------------------------------------------------
# tpm2-tools compatibility test against IBM SW TPM
# Validates that tpm2_tools_test.sh works on a reference TPM.
Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/publish-ci-image.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Publish wolfTPM CI image

# Publish v1.1 before merging workflows that consume it. Consumers use the
# Publish v1.2 before merging workflows that consume it. Consumers use the
# canonical ghcr.io/wolfssl/wolftpm-ci package, including on forks. A fork of
# this publisher writes to its own namespace for image testing only.
# The canonical package must be public so fork PRs can pull it anonymously.
Expand Down Expand Up @@ -61,20 +61,21 @@ jobs:
load: true
push: false
# ghcr image paths must be lowercase (the wolfSSL org has a capital S).
tags: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.1
cache-from: type=registry,ref=ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.1
tags: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.2
cache-from: type=registry,ref=ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.2
cache-to: type=inline

- name: Check baked tools
env:
CI_IMAGE: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.1
CI_IMAGE: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.2
run: |
docker run --rm "$CI_IMAGE" bash -euo pipefail -c '
for tool in autoconf automake libtoolize cmake ninja pkg-config \
gcc-11 gcc-12 gcc-13 g++-11 g++-12 g++-13 \
clang-14 clang-15 clang-17 arm-none-eabi-gcc \
rustc cargo mcs nunit-console tpm2_activatecredential \
xxd semgrep pyspdxtools codespell m33mu; do
xxd semgrep pyspdxtools codespell m33mu tpm2_ptool tpm2-abrmd \
pkcs11-tool dbus-daemon; do
command -v "$tool"
done
cargo clippy --version
Expand All @@ -86,13 +87,13 @@ jobs:

- name: Push verified image
env:
CI_IMAGE: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.1
CI_IMAGE: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.2
run: docker push "$CI_IMAGE"

- name: Verify anonymous access for fork PRs
if: github.repository_owner == 'wolfssl'
env:
CI_IMAGE: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.1
CI_IMAGE: ghcr.io/${{ steps.owner.outputs.lc }}/wolftpm-ci:v1.2
run: |
registry_config=$(mktemp -d)
trap 'rm -rf "$registry_config"' EXIT
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ tests/fwtpm_hal_unit.log
tests/fwtpm_check.sh.log
tests/packet_auth.test
tests/packet_auth.log
tests/signature_padding.test
tests/signature_padding.log
tests/spdm_config.test
tests/spdm_config.log
tests/spdm_ctrl.test
Expand Down
10 changes: 10 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,16 @@ if test "x$ENABLED_FWTPM" = "xyes"; then
OPTION_FLAGS="$OPTION_FLAGS -DWOLFTPM_FWTPM_BUILD"
fi

# The fwtpm_server simulator serves tools without a resource manager, which
# keep every ContextLoad copy loaded; give it more transient object slots
# unless the build sets its own.
FWTPM_SERVER_CFLAGS=""
case "$CFLAGS $CPPFLAGS" in
*FWTPM_MAX_OBJECTS*) ;;
*) FWTPM_SERVER_CFLAGS="-DFWTPM_MAX_OBJECTS=7" ;;
esac
AC_SUBST([FWTPM_SERVER_CFLAGS])



# The following AM_CONDITIONAL statements set flags for use in the Makefiles.
Expand Down
2 changes: 1 addition & 1 deletion docs/fwtpm/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ All macros are compile-time overridable (for example `-DFWTPM_MAX_OBJECTS=8`).
|-------|---------|-------------|
| `FWTPM_MAX_COMMAND_SIZE` | 4096 | Maximum command and response buffer size (bytes) |
| `FWTPM_MAX_RANDOM_BYTES` | 48 | Maximum bytes per `GetRandom` call |
| `FWTPM_MAX_OBJECTS` | 3 | Maximum concurrently loaded transient objects |
| `FWTPM_MAX_OBJECTS` | 3 (autoconf `fwtpm_server`: 7) | Maximum concurrently loaded transient objects |
| `FWTPM_MAX_PERSISTENT` | 8 | Maximum persistent objects (via `EvictControl`) |
| `FWTPM_MAX_PRIVKEY_DER` | 1280 (256 with `NO_RSA`) | Maximum DER-encoded private key size (bytes) |
| `FWTPM_MAX_HASH_SEQ` | 4 | Maximum concurrent hash and HMAC sequences |
Expand Down
2 changes: 1 addition & 1 deletion docs/ja/fwtpm/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ make
|-------|---------|-------------|
| `FWTPM_MAX_COMMAND_SIZE` | 4096 | コマンドおよびレスポンスバッファの最大サイズ (バイト) |
| `FWTPM_MAX_RANDOM_BYTES` | 48 | `GetRandom` 1 回あたりの最大バイト数 |
| `FWTPM_MAX_OBJECTS` | 3 | 同時にロードできるトランジェントオブジェクトの最大数 |
| `FWTPM_MAX_OBJECTS` | 3 (autoconf `fwtpm_server`: 7) | 同時にロードできるトランジェントオブジェクトの最大数 |
| `FWTPM_MAX_PERSISTENT` | 8 | 永続オブジェクトの最大数 (`EvictControl` 経由) |
| `FWTPM_MAX_PRIVKEY_DER` | 1280 (`NO_RSA` の場合は 256) | DER エンコードされた秘密鍵の最大サイズ (バイト) |
| `FWTPM_MAX_HASH_SEQ` | 4 | 同時に実行できるハッシュおよび HMAC シーケンスの最大数 |
Expand Down
154 changes: 154 additions & 0 deletions scripts/tpm2_pkcs11_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/bin/bash
# tpm2_pkcs11_test.sh
# Run tpm2-pkcs11 (tpm2_ptool + OpenSC pkcs11-tool) against a running TPM,
# either directly or through tpm2-abrmd.
#
# Usage:
# scripts/tpm2_pkcs11_test.sh
#
# Environment:
# TPM2_PKCS11_TCTI TCTI for tpm2-pkcs11 and tpm2_ptool
# (default: mssim:host=localhost,port=2321;
# use tabrmd:bus_type=session behind tpm2-abrmd)
# PKCS11_MODULE path to libtpm2_pkcs11.so (default: searched)
#
# Requirements: tpm2-pkcs11 (libtpm2_pkcs11.so, tpm2_ptool), OpenSC
# pkcs11-tool, openssl
#
# Exit: 0 if all tests pass, 77 if a requirement is missing (SKIP), 1 on failure

TPM2_PKCS11_TCTI="${TPM2_PKCS11_TCTI:-mssim:host=localhost,port=2321}"
export TPM2_PKCS11_TCTI
export TPM2TOOLS_TCTI="$TPM2_PKCS11_TCTI"

if [ -z "$PKCS11_MODULE" ]; then
PKCS11_MODULE=$(find /usr/lib /usr/local/lib -name 'libtpm2_pkcs11.so*' \
2>/dev/null | head -1)
fi
for tool in tpm2_ptool tpm2_flushcontext pkcs11-tool openssl; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "SKIP: $tool not installed"
exit 77
fi
done
if [ -z "$PKCS11_MODULE" ] || [ ! -f "$PKCS11_MODULE" ]; then
echo "SKIP: libtpm2_pkcs11.so not found"
exit 77
fi

PIN=1234
SOPIN=5678
LABEL=wolftpm
TMP=$(mktemp -d)
export TPM2_PKCS11_STORE="$TMP/store"
mkdir -p "$TPM2_PKCS11_STORE"
trap 'rm -rf "$TMP"' EXIT

PASS=0
FAIL=0

run_test() {
local name="$1"
shift
if "$@" > "$TMP/out.log" 2>&1; then
echo " [PASS] $name"
PASS=$((PASS+1))
else
echo " [FAIL] $name"
sed 's/^/ /' "$TMP/out.log"
FAIL=$((FAIL+1))
fi
}

# Without a resource manager, tpm2-tools and tpm2-pkcs11 leave objects and
# sessions loaded when they exit. Flush them so later steps have free slots.
flush_tpm() {
case "$TPM2_PKCS11_TCTI" in
tabrmd*) ;;
*)
tpm2_flushcontext -t > /dev/null 2>&1
tpm2_flushcontext -l > /dev/null 2>&1
;;
esac
}

ptool() {
tpm2_ptool "$@"
local rc=$?
flush_tpm
return $rc
}

p11() {
pkcs11-tool --module "$PKCS11_MODULE" --token-label "$LABEL" \
--login --pin "$PIN" "$@"
local rc=$?
flush_tpm
return $rc
}

echo "=== tpm2-pkcs11 tests (TCTI: $TPM2_PKCS11_TCTI) ==="

run_test "ptool init" ptool init
run_test "ptool addtoken" ptool addtoken --pid=1 --label="$LABEL" \
--userpin="$PIN" --sopin="$SOPIN"
run_test "ptool addkey ecc256" ptool addkey --label="$LABEL" \
--userpin="$PIN" --algorithm=ecc256 --key-label=ecc --id=01
run_test "ptool addkey rsa2048" ptool addkey --label="$LABEL" \
--userpin="$PIN" --algorithm=rsa2048 --key-label=rsa --id=02

run_test "list objects" p11 --list-objects

# Keys are selected by CKA_ID (the ASCII bytes of the tpm2_ptool --id);
# a label match can return another key
# Export public keys for software verification
run_test "read ECC public key" p11 --read-object --type pubkey --id 3031 \
--output-file "$TMP/ecc.der"
run_test "read RSA public key" p11 --read-object --type pubkey --id 3032 \
--output-file "$TMP/rsa.der"

# ECDSA (CKM_ECDSA_SHA256) sign, verified by OpenSSL
printf 'wolfTPM tpm2-pkcs11 test' > "$TMP/msg.bin"
run_test "ECDSA-SHA256 sign" p11 --sign --mechanism ECDSA-SHA256 --id 3031 \
--signature-format openssl --input-file "$TMP/msg.bin" \
--output-file "$TMP/ecc.sig"
run_test "ECDSA-SHA256 verify (OpenSSL)" openssl dgst -sha256 \
-verify "$TMP/ecc.der" -keyform DER -signature "$TMP/ecc.sig" \
"$TMP/msg.bin"

# CKM_ECDSA of an all-zero digest, as for an empty message padded to the
# hash length
head -c 32 /dev/zero > "$TMP/zero.bin"
run_test "ECDSA sign of an all-zero digest" p11 --sign --mechanism ECDSA \
--id 3031 --input-file "$TMP/zero.bin" --output-file "$TMP/zero.sig"

# RSA-PKCS#1 v1.5 sign, verified by OpenSSL
run_test "RSA-SHA256 sign" p11 --sign --mechanism SHA256-RSA-PKCS --id 3032 \
--input-file "$TMP/msg.bin" --output-file "$TMP/rsa.sig"
run_test "RSA-SHA256 verify (OpenSSL)" openssl dgst -sha256 \
-verify "$TMP/rsa.der" -keyform DER -signature "$TMP/rsa.sig" \
"$TMP/msg.bin"

# RSA-OAEP (SHA-256) encrypted by OpenSSL, decrypted by the TPM
printf 'oaep plaintext' > "$TMP/pt.bin"
run_test "RSA-OAEP encrypt (OpenSSL)" openssl pkeyutl -encrypt -pubin \
-keyform DER -inkey "$TMP/rsa.der" -in "$TMP/pt.bin" -out "$TMP/ct.bin" \
-pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 \
-pkeyopt rsa_mgf1_md:sha256
run_test "RSA-OAEP decrypt" p11 --decrypt --mechanism RSA-PKCS-OAEP \
--hash-algorithm SHA256 --mgf MGF1-SHA256 --id 3032 \
--input-file "$TMP/ct.bin" --output-file "$TMP/dec.bin"
run_test "RSA-OAEP plaintext matches" cmp "$TMP/pt.bin" "$TMP/dec.bin"

# RSA-OAEP with SHA-1 and MGF1-SHA-1
run_test "RSA-OAEP-SHA1 encrypt (OpenSSL)" openssl pkeyutl -encrypt -pubin \
-keyform DER -inkey "$TMP/rsa.der" -in "$TMP/pt.bin" -out "$TMP/ct1.bin" \
-pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha1 \
-pkeyopt rsa_mgf1_md:sha1
run_test "RSA-OAEP-SHA1 decrypt" p11 --decrypt --mechanism RSA-PKCS-OAEP \
--hash-algorithm SHA-1 --mgf MGF1-SHA1 --id 3032 \
--input-file "$TMP/ct1.bin" --output-file "$TMP/dec1.bin"
run_test "RSA-OAEP-SHA1 plaintext matches" cmp "$TMP/pt.bin" "$TMP/dec1.bin"

echo "=== tpm2-pkcs11 results: $PASS passed, $FAIL failed ==="
[ "$FAIL" -eq 0 ]
16 changes: 16 additions & 0 deletions src/fwtpm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,19 @@ the v1.85 spec defines them generically:
only. Classical digest signing (RSASSA, RSAPSS, ECDSA) over these new
commands is not supported; use the existing `TPM2_Sign` /
`TPM2_VerifySignature` commands for those schemes.

Clock persistence requires a clock HAL returning milliseconds since the current
power-on. The standalone server registers a monotonic host clock. Server loops
checkpoint Clock while idle. Embedded integrations must call `FWTPM_Clock_Update()` at least every `FWTPM_CLOCK_POLL_MS` milliseconds;
custom transport waits, reads and writes must return within that interval.
`TPM_PT_CLOCK_UPDATE` reports the maximum NV checkpoint interval. Clock's `safe` flag stays clear after
an unclean shutdown until the prior boot's potentially reported range has passed
and the new value is committed. Orderly NV counters checkpoint on modulo-2
rollover and at shutdown, and round forward on an unclean startup.
An orderly shutdown preserves Clock's safety flag separately; shutdown and
library cleanup cannot make an unsafe Clock safe before recovery.

Existing NV journals remain readable. Legacy shutdown markers lack a Clock
safety flag and restore Clock conservatively as unsafe. Older firmware rejects
journals containing the extended orderly-shutdown marker; downgrades need a
compatible journal conversion.
Loading
Loading