This project contains standalone WebAssembly verification components used by TrustMee project.
Hardware (TEE) verifiers — each takes a quote/report and exports trustee:verifier/verifier-interface:
tdx-verifier/: TDX-related items (dcap-qvl-wasi,eventlog,tdx-verifier-component,tdx-verifier-test)snp-verifier/: SNP-related items (snp-verifier-component,snp-verifier-test). Verifies the cert chain in-guest, so building it needs the containerized OpenSSL-for-wasm toolchainsnp-verifier-host-crypto/: same SNP verification split differently (snp-verifier-host-crypto-component,snp-verifier-host-crypto-test). The component importstrustee:verifier/snp-host-crypto-interfaceand delegates cert-chain crypto to the host, so it builds with plaincargo build— no container, no OpenSSL cross-compile. The host must supply that importsgx-verifier/: SGX-related items (sgx-verifier-component,sgx-verifier-test); sharesdcap-qvl-wasiwith TDX and binds REPORT_DATA / CONFIGID. A real DCAP v3 quote from a non-debug enclave is committed assgx-verifier/test_data/sgx_dcap_v3_quote_not_debug.bin
Layered components — these import a lower-layer verifier interface and export the same one, so they compose with wac:
bootloader-verifier/: bootloader layer component (bootloader-verifier-component) that imports TDX verifier interface and verifies RTMR0/RTMR1 policykata-policy-verifier/: verifies the Kata Containers agent policy embedded in a confidential VM's init-data (kata-policy-core,kata-policy-verifier-component,kata-policy-verifier-test,stub-hardware-verifier-component). Forwards hardware evidence to a composed TDX/SNP layer, bindsdigest(init-data)to MRCONFIGID/HOSTDATA, then strictly parsespolicy.regoand emits the extracted facts as claims.stub-hardware-verifier-componentstands in for the hardware layer so the whole matrix runs without a live TEE; see kata-policy-verifier/README.md
Non-TEE verifiers:
recfa-verifier/: ReCFA control-flow attestation items (recfa-verifier-component,recfa-verifier-test). Wraps the verifier from ReCFA (ACSAC'21) and replays a folded control-flow event stream against a CFI policy. Not a TEE verifier — it reportshardware_rooted: falseand its evidence is unauthenticated, so read recfa-verifier/README.md before trusting a verdict
Test fixtures and tooling:
infinite-loop-verifier/: deliberately malicious component (infinite-loop-verifier-component) whoseevaluateisloop {}. Not a verifier — it exists so hosts can prove they contain a non-cooperative guest (wasmtime fuel or epoch interruption); a host that hangs on it will hang on any untrusted componenttools/component-signer/: adds TrustMee signature expiry metadata and signs verifier components withwasmsign2
To write a new component, see WRITING_A_VERIFIER_COMPONENT.md.
From the repo root (no cargo component required; uses the native wasm32-wasip2 target):
cargo build --manifest-path Cargo.toml -p tdx-verifier-component --release --target wasm32-wasip2
The resulting component is created at target/wasm32-wasip2/release/tdx_verifier_component.wasm.
For AMD SEV-SNP, use the builder script (it builds a containerized toolchain and OpenSSL for wasm32-wasip2):
bash snp-verifier/snp-verifier-component/scripts/build-snp-wasm-component.sh
The resulting component is created at target/wasm32-wasip2/release/snp_verifier_component.wasm.
The host-crypto SNP variant needs none of that, because the crypto lives on the host:
bash snp-verifier-host-crypto/snp-verifier-host-crypto-component/scripts/build-snp-host-crypto-wasm-component.sh
The resulting component is created at target/wasm32-wasip2/release/snp_verifier_host_crypto_component.wasm.
For Intel SGX (plain build, like TDX):
cargo build -p sgx-verifier-component --release --target wasm32-wasip2
The resulting component is created at target/wasm32-wasip2/release/sgx_verifier_component.wasm.
For ReCFA, the build compiles the vendored upstream C++ verifier, so it needs wasi-sdk (>= 24) located via WASI_SDK_PATH, $HOME/wasi-sdk, or /opt/wasi-sdk. Install it once if you have not (25.0 is known good, ~360 MB extracted):
curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz \
| tar -xz -C "$HOME" \
&& mv "$HOME/wasi-sdk-25.0-x86_64-linux" "$HOME/wasi-sdk"Then:
export WASI_SDK_PATH=$HOME/wasi-sdk
cargo build -p recfa-verifier-component --release --target wasm32-wasip2Setting WASI_SDK_PATH does not install anything — if it points at a directory with no bin/clang++, build.rs panics with WASI_SDK_PATH=… has no bin/clang++. This is the only component in the repo that needs a C++ cross-toolchain.
The resulting component is created at target/wasm32-wasip2/release/recfa_verifier_component.wasm.
There is also a builder script that validates the component and runs the fixture suite:
bash recfa-verifier/recfa-verifier-component/scripts/build-recfa-wasm-component.sh --test
Use the component signer tool to add TrustMee expiry metadata and sign the component:
cargo run -p trustmee-component-signer -- sign --component target/wasm32-wasip2/release/snp_verifier_component.wasm --signed-component target/wasm32-wasip2/release/snp_verifier_component.signed.wasm --signature-expires-at 2035-01-01T00:00:00Z --generate-key --private-key-out component.private.pem --public-key-out component.public.pem --trust-store-out component.trust-store.json --valid-until 2035-01-01T00:00:00Z
To generate a key pair without signing:
cargo run -p trustmee-component-signer -- generate-key --private-key-out component.private.pem --public-key-out component.public.pem
For the layered bootloader verifier component (imports TDX verifier interface and exports the same interface):
cargo build --manifest-path Cargo.toml -p bootloader-verifier-component --release --target wasm32-wasip2
Then compose bootloader + tdx so only bootloader exports are exposed:
bash bootloader-verifier/bootloader-verifier-component/scripts/build-bootloader-composed-component.sh
This script uses wac.
Install once if needed:
cargo install wac-cli
The composed component is written to:
target/wasm32-wasip2/release/bootloader_tdx_verifier_component.wasm.
The Kata policy verifier composes the same way, with a selectable lower layer:
bash kata-policy-verifier/kata-policy-verifier-component/scripts/build-kata-policy-composed-component.sh # stub lower layer (tests)
LOWER=tdx bash kata-policy-verifier/kata-policy-verifier-component/scripts/build-kata-policy-composed-component.sh # real TDX lower layer
LOWER=snp bash kata-policy-verifier/kata-policy-verifier-component/scripts/build-kata-policy-composed-component.sh # real SNP lower layerThe stub build is written to target/wasm32-wasip2/release/kata_policy_stub_verifier_component.wasm, the TDX one to kata_policy_tdx_verifier_component.wasm.
For Bootloader + TDX:
Run bootloader verifier test host:
cargo run -p bootloader-verifier-test -- --component target/wasm32-wasip2/release/bootloader_tdx_verifier_component.wasm --evidence-json bootloader-verifier/test_data/bootloader_evidence.json
For Intel TDX:
cargo run -p tdx-verifier-test -- --component target/wasm32-wasip2/release/tdx_verifier_component.wasm --quote tdx-verifier/test_data/tdx_quote.bin
Optional inputs:
--ccel /path/to/ccel.bin--pccs-url https://api.trustedservices.intel.com(defaults to Intel PCS)--expected-report-data-hex <hex>--expected-init-data-hash-hex <hex>--cache-dir <host-dir>(collateral cache base; a fresh subdirectory is pre-opened to the component ascache/)
On verification failure, the component returns JSON like {"status":"failed","error":"..."} and the host exits non-zero.
For AMD SEV-SNP:
cargo run -p snp-verifier-test -- --component target/wasm32-wasip2/release/snp_verifier_component.wasm --evidence-json snp-verifier/test_data/snp_evidence.json
Optional inputs:
--vlek /path/to/vlek.der(use VLEK instead of VCEK)--expected-report-data-hex <hex>--expected-init-data-hash-hex <hex>--cache-dir <host-dir>(VCEK cache base; a fresh subdirectory is pre-opened to the component ascache/)
If no VCEK/VLEK is provided, the component will fetch VCEK from AMD KDS using WASI-HTTP.
If the host pre-opens a cache/ directory for the component, fetched VCEKs are cached there.
Set SNP_VCEK_DISABLE_CACHE=1 to disable VCEK caching.
For AMD SEV-SNP with host-side crypto (same evidence, same claims; the harness provides the snp-host-crypto-interface import):
cargo run -p snp-verifier-host-crypto-test --release -- --component target/wasm32-wasip2/release/snp_verifier_host_crypto_component.wasm --evidence-json snp-verifier/test_data/snp_evidence.json
It accepts --report (raw attestation report) instead of --evidence-json, plus the same --vcek / --vlek / --expected-report-data-hex / --expected-init-data-hash-hex / --cache-dir options as above.
For Intel SGX:
cargo run -p sgx-verifier-test -- --component target/wasm32-wasip2/release/sgx_verifier_component.wasm --quote sgx-verifier/test_data/sgx_dcap_v3_quote_not_debug.bin
The committed quote is a real DCAP v3 (ECDSA-P256) quote from an enclave launched without the DEBUG attribute, so its measurements are meaningful. No collateral is committed with it, so the component fetches collateral from Intel PCS over WASI-HTTP — this command needs outbound network. Optional inputs:
--collateral /path/to/collateral.cbor(CBOR-encoded dcap-qvlQuoteCollateralV3; supplying it skips the PCS fetch)--expected-report-data-hex <hex>(REPORT_DATA binding)--expected-init-data-hash-hex <hex>(CONFIGID binding)--cache-dir <host-dir>
Expected output for that quote (abridged; TCB fields track whatever Intel currently publishes for this platform):
{"body":{"attributes.flags":"0500000000000000","mr_enclave":"1a818f25391de5c4939ff933175153ade954b3e31ce0b454829086c7b7f68956","mr_signer":"669b80648c2d9c97f32263fa1961f95f83818682d6359758221f0e7acb9584c0",…},"header":{"version":"0300","att_key_type":"0200",…},"tcb_status":"OutOfDateConfigurationNeeded","tee_type":"sgx",…}attributes.flags is 0x05 (INIT | MODE64BIT) — the DEBUG bit (0x02) is clear, which
is what makes this fixture usable as a production-enclave sample.
Its REPORT_DATA and CONFIGID are both all-zero (nothing was bound into them), so
--expected-report-data-hex 00 and --expected-init-data-hash-hex 00 pass — the
component zero-pads the expected value to 64 bytes — while any other value fails closed
with {"status":"failed","error":"REPORT_DATA is different from that in SGX Quote"}.
End-to-end test (SGX). Run from the repo root:
cargo test -p sgx-verifier-test # hermetic fixture checks: no network, no wasm
cargo build -p sgx-verifier-component --release --target wasm32-wasip2 \
&& cargo test -p sgx-verifier-test -- --ignoredThe default run asserts the committed fixture is still a v3 ECDSA quote over a
non-debug enclave with the recorded MRENCLAVE/MRSIGNER, and that the file is a whole
quote (its signature_data_len accounts for the tail) — it catches a fixture that was
swapped or truncated without needing a TEE or a network.
The --ignored tests drive the built component through wasmtime and need network to
Intel PCS for collateral (~20 s): one verifies the fixture end-to-end and checks the
returned claims, the other checks that the REPORT_DATA / CONFIGID bindings are enforced
and fail closed on a mismatch. As with ReCFA, the && chaining matters — a stale
.wasm left in target/ from an earlier build would otherwise be tested instead of
your current sources.
For the Kata policy verifier, the full scenario matrix runs against the stub hardware layer, so it needs no live TEE:
cargo test -p kata-policy-core # structural-parser unit tests
cargo run -p kata-policy-verifier-test --release -- scenarios \
--component target/wasm32-wasip2/release/kata_policy_stub_verifier_component.wasm \
--test-data kata-policy-verifier/test_dataExpected output:
PASS (a) valid policy accepted (tdx register)
PASS (a) valid policy accepted (snp register)
PASS (b) new image digest accepted with updated images claim
PASS (c) modified rule -> approved_rule_template=false
PASS (d) exec allowance surfaces in exec_commands claim
PASS (e) rule appended after policy_data rejected
PASS (e) rule smuggled on the policy_data line rejected
PASS (e) second policy_data assignment rejected
PASS (f) init-data digest != measured MRCONFIGID rejected
PASS (g) invalid hardware quote rejected
all scenarios passed
A single run, printing the claims:
cargo run -p kata-policy-verifier-test --release -- run --component target/wasm32-wasip2/release/kata_policy_stub_verifier_component.wasm --evidence-json kata-policy-verifier/test_data/stub-evidence-valid.json --init-data kata-policy-verifier/test_data/initdata.toml --approved-templates kata-policy-verifier/test_data/approved-templates.json
Against a composed TDX build, swap the component for kata_policy_tdx_verifier_component.wasm and pass real TDX evidence. See kata-policy-verifier/README.md for the endorsement formats and how to regenerate the fixtures.
For ReCFA:
Unlike the TEE verifiers, ReCFA takes no quote. The evidence is the folded control-flow event stream from the attester, and the reference values (disassembly, CFG, policy F, policy M, config) are passed as endorsements — nothing is hardcoded in the component.
Run from the repo root. This builds the component and the host harness, then replays the shipped fixture — a small hand-built program with a known-correct CFG — covering a direct call, the compressed direct-call encoding, an indirect call, an indirect jump, policy-M call-site reconstruction, and one case per violation class:
The && chaining matters: target/ may already hold a component from an earlier build, so
if you let a failed cargo build fall through, the suite happily passes against the stale
.wasm and the green table tells you nothing about your current sources.
export WASI_SDK_PATH=$HOME/wasi-sdk # must already contain bin/clang++, see Build above
cargo build -p recfa-verifier-component --release --target wasm32-wasip2 \
&& cargo build -p recfa-verifier-test \
&& ./target/debug/recfa-verifier-test \
--component target/wasm32-wasip2/release/recfa_verifier_component.wasm \
--disassembly recfa-verifier/test_data/prog.asm \
--cfg recfa-verifier/test_data/prog.dot \
--policy-f recfa-verifier/test_data/binfo.prog \
--batch recfa-verifier/test_data \
--compiler gcc--batch runs every *.trace in the directory and infers the expected outcome from the
pass_/fail_ filename prefix, picking up a same-named .map as policy M when present.
The vendored verifier keeps upstream's progress writes to stdout (|M|= …, Nevents sucess),
so expect that noise interleaved with the table; the verdict is carried entirely in the
returned JSON. The last lines should be:
CASE VERDICT EVENTS RESULT
fail_icall indirect-call-violation 2 OK
fail_ijmp indirect-jump-violation 2 OK
fail_shadow shadow-stack-violation 3 OK
pass_compressed secure 5 OK
pass_icall secure 9 OK
pass_ijmp secure 3 OK
pass_policy_m secure 11 OK
pass_simple secure 5 OK
PASS: all 8 cases behaved as expected
To evaluate a single trace and print the claims JSON, replace --batch with --trace:
./target/debug/recfa-verifier-test --component target/wasm32-wasip2/release/recfa_verifier_component.wasm --disassembly recfa-verifier/test_data/prog.asm --cfg recfa-verifier/test_data/prog.dot --policy-f recfa-verifier/test_data/binfo.prog --trace recfa-verifier/test_data/pass_simple.trace --compiler gcc
Optional inputs:
--policy-m /path/to/*.filtered.map(skipped direct call sites; omitting it means no call-site filtering)--compiler gcc|llvm(selects the indirect-jump register the disassembly regex matches; the wrong value silently drops every indirect jump from the policy)--num-executions N(must match how many runs ofmainthe trace contains)--bind-report-data/--bind-wrong-report-data(checks thatexpected-report-datais bound tosha256(evidence)and fails closed when it does not match)--expect secure|failed(assert the outcome, for use as a test)--cache-dir <host-dir>(a fresh subdirectory is pre-opened to the component ascache/; the component does not otherwise need a filesystem)
On a CFI violation the component fails closed and names the offending edge, e.g.
{"status":"failed","error":"indirect-call-violation at 0x401020: target 0x401100 is not permitted"},
and the host exits non-zero.
For a full attester-to-verifier run on a program written for this repo — compiled, statically instrumented with Dyninst, executed, and verified from the events that run recorded, with policy F generated by the patched typearmor pass — see recfa-verifier/custom-e2e/:
bash recfa-verifier/custom-e2e/build.sh --testIt builds the two attester toolchains as containers, regenerates every artifact, and
runs two end-to-end tests against the component: the untampered run verifies, and the
make-tamper.py hijack is rejected with the offending edge named.
recfa-verifier/README.md also documents a parity harness that compares the vendored
verifier against unmodified upstream check, and a run against real SPEC CPU2006
attester output. Both need inputs that are not committed (upstream ReCFA checked out,
and the 10.8 MB bzip2 trace plus a Dyninst-generated .dot) — see
recfa-verifier/ATTESTER_SETUP.md.
infinite-loop-verifier-component is not a verifier: its evaluate is loop {}. It
ships no test harness on purpose — build it and point your own host at it, which should
interrupt the guest (wasmtime fuel or epoch interruption) instead of hanging. A host that
hangs here will hang on any untrusted component.
cargo build -p infinite-loop-verifier-component --release --target wasm32-wasip2