# 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](fwtpm/overview.md) and [SWTPM](system-interfaces.md). ## Step 1: Build the C Library Run these from the wolfTPM repository root: ```sh ./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 ```sh 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): ```sh ./src/fwtpm/fwtpm_server --clear --port 2321 --platform-port 2322 & ``` Run the tests against it: ```sh 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: ```sh 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: ```sh 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: ```sh 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 ```rust 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: ```rust 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 - [fwTPM](fwtpm/overview.md) - [SWTPM](system-interfaces.md) - [Build Options](build-options.md) - [C# Wrapper](csharp-wrapper.md)