mirror of https://github.com/wolfSSL/wolfTPM.git
262 lines
11 KiB
Markdown
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)
|