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
75 changes: 75 additions & 0 deletions .github/workflows/fwtpm-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,81 @@ 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.1
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: Install tpm2-pkcs11, tpm2-abrmd and OpenSC
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
libtpm2-pkcs11-1 libtpm2-pkcs11-tools tpm2-abrmd \
libtss2-tcti-tabrmd0 opensc dbus

- 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
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.
58 changes: 58 additions & 0 deletions src/fwtpm/fwtpm.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
#include <wolftpm/fwtpm/fwtpm_nv.h>
#include <string.h>

/* Clock resumes from its NV checkpoint and becomes safe one checkpoint
* interval later, unless the NV state kept it safe. */
static void FwClockRestoreSafe(FWTPM_CTX* ctx)
Comment thread
embhorn marked this conversation as resolved.
{
ctx->clockUnsafeUntil = ctx->clockNvValue >
~(UINT64)0 - FWTPM_CLOCK_UPDATE_MS ? ~(UINT64)0 :
ctx->clockNvValue + FWTPM_CLOCK_UPDATE_MS;
}

int FWTPM_Init(FWTPM_CTX* ctx)
{
int rc;
Expand Down Expand Up @@ -134,6 +143,8 @@ int FWTPM_Init(FWTPM_CTX* ctx)
/* Initialize NV storage - loads existing state or creates fresh seeds */
#ifndef FWTPM_NO_NV
if (rc == 0) {
/* A compaction during the load cannot establish Clock safety. */
ctx->clockUnsafeUntil = ~(UINT64)0;
rc = FWTPM_NV_Init(ctx);
}
#else
Expand All @@ -152,6 +163,7 @@ int FWTPM_Init(FWTPM_CTX* ctx)
if (rc == 0) {
ctx->pcrAllocatedBanks = FWTPM_PCR_ALLOC_DEFAULT;
ctx->endorsementProofVersion = 1;
ctx->clockSafe = 1;
}
#ifndef FWTPM_NO_DA
if (rc == 0) {
Expand All @@ -163,6 +175,13 @@ int FWTPM_Init(FWTPM_CTX* ctx)
#endif
#endif

/* The NV load keeps clockSafe only from a committed orderly marker.
* Fresh state has reported no Clock yet, so it is safe. */
if (rc == TPM_RC_SUCCESS) {
ctx->clockNvValue = ctx->clockOffset;
FwClockRestoreSafe(ctx);
}

if (rc != 0) {
if (rngInit) {
wc_FreeRng(&ctx->rng);
Expand Down Expand Up @@ -232,6 +251,45 @@ UINT64 FWTPM_Clock_GetMs(FWTPM_CTX* ctx)
return now + ctx->clockOffset;
}

int FWTPM_Clock_PowerOn(FWTPM_CTX* ctx)
{
UINT64 halNow = 0;
if (ctx == NULL) {
return BAD_FUNC_ARG;
}
if (ctx->clockHal.get_ms != NULL) {
halNow = ctx->clockHal.get_ms(ctx->clockHal.ctx);
}
/* Powered-off time is not counted, whatever the HAL epoch. Unsigned
* wrap is intended, as in ClockSet. */
ctx->clockOffset = ctx->clockNvValue - halNow;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No-NV power cycle reports a rolled-back Clock as safe

In an FWTPM_NO_NV build, FWTPM_Clock_Update never advances clockNvValue, while Shutdown sets nvOrderly and its NV save is a no-op. After ReadClock reports a positive value with safe=YES, an orderly power cycle in the same context makes FWTPM_Clock_PowerOn reset Clock to the old clockNvValue (normally zero) and retain safe=YES. This PR introduces that power-cycle path, and the new Clock tests are excluded from no-NV builds.

Suggested fix: Handle no-NV power cycles so Clock cannot retain a true safety flag after returning below a value already reported, and add a no-NV regression test.
Basis: The FWTPM_Clock_PowerOn API documentation in wolftpm/fwtpm/fwtpm.h promises that Clock cannot roll back and describes when Clock is safe.

#ifndef FWTPM_NO_NV
ctx->clockSafe = ctx->nvOrderly && ctx->clockSafe;
#else
/* Without NV, Clock restarts from its boot value and can roll back. */
ctx->clockSafe = 0;
#endif
FwClockRestoreSafe(ctx);
return TPM_RC_SUCCESS;
}

int FWTPM_Clock_Update(FWTPM_CTX* ctx)
{
if (ctx == NULL) return BAD_FUNC_ARG;
#if !defined(FWTPM_NO_CLOCK) && !defined(FWTPM_NO_NV)
if (ctx->wasStarted && ctx->powerOn) {
UINT64 now = FWTPM_Clock_GetMs(ctx);
if (now >= ctx->clockNvValue &&
(now - ctx->clockNvValue >=
FWTPM_CLOCK_UPDATE_MS - FWTPM_CLOCK_POLL_MS ||
(!ctx->clockSafe && now >= ctx->clockUnsafeUntil))) {
return FWTPM_NV_SaveClock(ctx);
}
}
#endif
return TPM_RC_SUCCESS;
}

#ifndef FWTPM_NO_PP
int FWTPM_PP_SetHAL(FWTPM_CTX* ctx,
int (*get_pp)(void* halCtx), void* halCtx)
Expand Down
Loading
Loading