wolfTPM/docs/rust-wrapper.md

262 lines
11 KiB
Markdown

# 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)