Skip to content

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_server works, 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:

  1. If WOLFTPM_PREFIX or WOLFSSL_PREFIX are set, it uses $PREFIX/include and $PREFIX/lib of an installed copy.
  2. Otherwise it uses the in-tree build: headers from the repo root, libraries from src/.libs. wolfSSL is located with pkg-config, then a local ./wolfssl or a sibling ../wolfssl checkout.

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.

See Also