Rust Wrapper¶
The wolftpm crate provides safe Rust bindings for wolfTPM. The raw FFI is generated with bindgen and kept in the sys module. The rest of the crate is a safe API: functions return Result, TPM handles are released when they go out of scope, and all unsafe stays inside the crate. The crate lives in wrapper/rust/wolftpm in the wolfTPM source tree.
Requirements¶
- Rust and Cargo, stable, rustc 1.81 or newer.
- A built wolfTPM C library (
libwolftpm) and its wolfSSL dependency (libwolfssl). The crate links a prebuilt library. It does not build the C code itself. - For the tests, a software TPM. wolfTPM's own
fwtpm_serverworks, or any other TPM emulator or software TPM. See fwTPM and SWTPM.
Step 1: Build the C Library¶
Run these from the wolfTPM repository root:
./autogen.sh
./configure --enable-swtpm --enable-fwtpm
make
This produces src/.libs/libwolftpm. With --enable-fwtpm it also builds the software TPM server at src/fwtpm/fwtpm_server.
Step 2: Build the Crate¶
cd wrapper/rust/wolftpm
cargo build
The build finds the libraries in this order:
- If
WOLFTPM_PREFIXorWOLFSSL_PREFIXare set, it uses$PREFIX/includeand$PREFIX/libof an installed copy. - Otherwise it uses the in-tree build: headers from the repo root, libraries from
src/.libs. wolfSSL is located withpkg-config, then a local./wolfsslor a sibling../wolfsslcheckout.
Shared libraries are preferred. Static is used when no shared library is present.
The library is no_std and uses alloc for owned keys, blobs, and output buffers. Bare-metal applications must provide a global allocator. The build script translates Rust cross-compilation targets such as riscv32imac-unknown-none-elf to clang's target spelling when generating the FFI bindings.
The wrapper/rust directory also has a Makefile for the common tasks. After building the C library, make -C wrapper/rust builds, lints, and documents the crate, and make -C wrapper/rust test runs the tests (a software TPM must be listening on localhost:2321).
Step 3: Run the Tests¶
The integration tests talk to a software TPM over a socket. They are behind the swtpm-tests feature, so a plain cargo test does not need a running server.
Start a server on port 2321 (from the repo root):
./src/fwtpm/fwtpm_server --clear --port 2321 --platform-port 2322 &
Run the tests against it:
cd wrapper/rust/wolftpm
TPM2_SWTPM_HOST=localhost TPM2_SWTPM_PORT=2321 \
cargo test --features swtpm-tests -- --test-threads=1
Use --test-threads=1 because the software TPM serves one client at a time.
Run a single test file, for example the signing tests:
cargo test --features swtpm-tests --test sign -- --test-threads=1
The default host and port are localhost:2321, so the environment variables can be omitted when the server is on that address.
What the Tests Check¶
Each file under tests/ exercises one area against the software TPM:
| File | Checks |
|---|---|
tests/smoke.rs |
Random bytes differ across draws, and a primary key loads. |
tests/keys.rs |
Create and load a child key, blob round-trip, short-auth blob round-trip, and an auth-protected parent. |
tests/sign.rs |
Sign a digest and verify it, and reject a tampered signature. |
tests/seal.rs |
Seal a secret and unseal it, and fail with the wrong auth. |
tests/seal_pcr.rs |
PCR-bound seal and unseal, unseal fails after a PCR changes, and an invalid PCR selection is rejected. |
tests/nv.rs |
Define an NV index, write and read it, then delete it. |
tests/pcr.rs |
Read a PCR, extend it, and confirm the value changed. |
tests/certify.rs |
An attestation key (ECC and RSA) certifies another key. |
tests/quote.rs |
Quote PCRs with an ECC and RSA AIK, and reject a bad PCR selection. |
tests/credential.rs |
MakeCredential then ActivateCredential round-trip. |
tests/ek.rs |
Create the endorsement key and export its public part. |
tests/persist.rs |
Persist a key, read it back, and evict it. |
tests/rsa.rs |
RSA-OAEP encrypt and decrypt, and an explicit OAEP-SHA1 round-trip. |
tests/hmac.rs |
Raw-key HMAC and a TPM-resident keyed-hash key HMAC. |
tests/caps.rs |
Self-test and capability query. |
tests/ecdh.rs |
ECDH generate then recover the same shared secret. |
tests/symmetric.rs |
AES-CFB encrypt and decrypt round-trip. |
tests/import.rs |
Import an external RSA and ECC private key. |
Example Test Output¶
running 2 tests
test certify_with_ecc_aik ... ok
test certify_with_rsa_aik ... ok
test result: ok. 2 passed; 0 failed; 0 ignored
running 4 tests
test create_and_load_child ... ok
test key_blob_roundtrip_then_load ... ok
test key_blob_roundtrip_preserves_short_auth ... ok
test auth_protected_parent_loads_child ... ok
test result: ok. 4 passed; 0 failed; 0 ignored
running 2 tests
test sign_then_verify ... ok
test verify_rejects_tampered_signature ... ok
test result: ok. 2 passed; 0 failed; 0 ignored
running 2 tests
test rsa_oaep_roundtrip ... ok
test rsa_oaep_sha1_roundtrip ... ok
test result: ok. 2 passed; 0 failed; 0 ignored
running 1 test
test make_and_activate_credential ... ok
test result: ok. 1 passed; 0 failed; 0 ignored
Step 4: Run the Examples¶
There are two examples. The first is minimal:
cargo run --example create_primary
It opens the software TPM, reads random bytes, and creates an RSA and an ECC storage root key.
The second runs the whole API in one pass:
cargo run --example full_flow
Expected output. The random bytes and handle values differ between runs:
device connected to software TPM
get_random a91b37b1838d002453f1632ba2c0adbc
create_primary ECC SRK handle 0x80000000
create_and_load signing key handle 0x80000001
sign/verify 64 byte signature, verified
seal/unseal recovered "my secret"
key blob 257 bytes, reloaded as handle 0x80000002
pcr read/extend PCR16 000000000000.. -> debb3e7acfff..
nv define/rw 32 bytes at index 0x01500100
certify 157 byte attestation
done all operations succeeded
If the wolfTPM C library was built with debug output, you will also see verbose TPM2_* traces from the library. A normal build prints only the lines above.
Using the Wrapper¶
use wolftpm::{Device, HashAlg, Hierarchy, KeyAlg, KeyBlob, Template};
fn main() -> Result<(), wolftpm::TpmError> {
// Connect to a software TPM. Use Device::open() for the platform default.
let dev = Device::open_swtpm()?;
// Random bytes from the TPM.
let mut nonce = [0u8; 32];
dev.get_random(&mut nonce)?;
// Storage root key under the owner hierarchy.
let srk = dev.create_primary(Hierarchy::Owner, KeyAlg::EccP256, None)?;
// Signing key under the SRK, then sign and verify a digest.
let signer = dev.create_and_load(&srk, &Template::signing(KeyAlg::EccP256)?, None)?;
let digest = [0x11u8; 32];
let sig = signer.sign_hash(&digest)?;
signer.verify_hash(&digest, &sig)?;
// Seal a secret to the TPM and read it back.
let sealed = dev.seal(&srk, b"my secret", None)?;
let _secret = dev.unseal(sealed, &srk, None)?;
// Persist a key as bytes, then load it again. Scope the reloaded key so it
// releases its TPM handle before more transient objects are created below
// (many TPMs allow only three transient objects at once).
let bytes = {
let blob = dev.create_key(&srk, &Template::signing(KeyAlg::EccP256)?, None)?;
blob.to_bytes()?
};
{
let restored = KeyBlob::from_bytes(&dev, &bytes)?;
let _loaded = restored.load(&srk, None)?;
}
// Read and extend a PCR.
let _value = dev.pcr_read(16, HashAlg::Sha256)?;
dev.pcr_extend(16, HashAlg::Sha256, &[0xAB; 32])?;
// Define, write, read, and delete an NV index.
let mut slot = dev.nv_create(0x0150_0100, 32, None)?;
dev.nv_write(&mut slot, b"metadata", 0)?;
let mut buf = [0u8; 32];
dev.nv_read(&mut slot, &mut buf, 0)?;
dev.nv_delete(0x0150_0100)?;
// Attest that a key lives in this TPM, signed by an attestation key.
let aik = dev.create_and_load(&srk, &Template::attestation(KeyAlg::EccP256)?, None)?;
let _attestation = dev.certify(&signer, &aik, &nonce)?;
Ok(())
}
Keys and the device release their TPM handles automatically when they drop.
What the Crate Covers¶
- Device open and cleanup, TPM random numbers, self-test, and capability query.
- Primary and child keys. Templates for storage, signing, attestation, EK, RSA decrypt, keyed-hash HMAC, symmetric AES, and ECDH keys.
- Key blob serialize and load for persistence, and external RSA and ECC key import.
- Persistent key handles: store, read back, and evict.
- Sign and verify.
- RSA-OAEP encrypt and decrypt, including an explicit label hash (SHA-1 for Microsoft enrollment interop).
- Symmetric AES-CFB encrypt and decrypt.
- ECDH key agreement.
- HMAC, both raw-key and with a TPM-resident keyed-hash key.
- Seal and unseal, plain and bound to a PCR policy.
- PCR read and extend.
- NV define, write, read, delete, and certificate read (EK certificate).
- Attestation: certify a key, and quote PCRs.
- Credential activation: MakeCredential and ActivateCredential.
Recovered secrets (unseal, RSA decrypt, ECDH, AES decrypt, credential activation) are returned in a Secret that zeroizes its buffer on drop.
Transport Security¶
For confidentiality on the TPM transport, start a parameter-encryption session with Device::start_encrypted_session before the secret-bearing operations:
let srk = dev.create_primary(Hierarchy::Owner, KeyAlg::EccP256, None)?;
let _session = dev.start_encrypted_session(&srk)?; // salted HMAC + AES-CFB
let sealed = dev.seal(&srk, b"secret", None)?; // command param encrypted
let plain = dev.unseal(sealed, &srk, None)?; // response param encrypted
While the session is alive its salted HMAC session occupies auth slot 1, so wolfTPM encrypts the sensitive command and response parameters of seal/unseal, RSA and AES encrypt/decrypt, HMAC, NV, ECDH, and key create/load. Only one session is allowed at a time. The attestation commands (certify, quote, activate_credential) need the same auth slot and are refused while a session is active, so drop the session before calling them.
Warning
Without a session, parameters cross the transport in the clear. Either use a session or run over a trusted local transport (the Linux kernel device or a local socket) rather than a remote TPM2_SWTPM_HOST or an observable physical bus.
License¶
GPLv3, or a commercial wolfSSL license, matching wolfTPM.