A Cargo subcommand for building OP-TEE Trusted Applications (TAs) and Client Applications (CAs) in Rust.
cargo-optee simplifies the development workflow for OP-TEE applications by
replacing complex Makefiles with a unified, type-safe command-line interface. It
handles cross-compilation, custom target specifications, environment setup, and
signing automatically.
┌──────────────────┐
│ TA Developer │
│ (CLI input) │
└────────┬─────────┘
│
▼
┌──────────────────────────────────────────────┐
│ cargo-optee (this tool) │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ 1. Parse CLI & Validate Parameters │ │
│ │ - Architecture (aarch64/arm) │ │
│ │ - Build mode (std/no-std) │ │
│ │ - Build type (TA/CA/PLUGIN) │ │
│ └──────────────────┬─────────────────────┘ │
│ │ │
│ ┌──────────────────▼─────────────────────┐ │
│ │ 2. Setup Build Environment │ │
│ │ - Set environment variables │ │
│ │ - Configure cross-compiler │ │
│ └──────────────────┬─────────────────────┘ │
│ │ │
│ ┌──────────────────▼─────────────────────┐ │
│ │ 3. Execute Build Pipeline │ │
│ │ - Run clippy (linting) │ │
│ │ - Build binary: cargo + gcc │ │
│ │ - Strip symbols: objcopy │ │
│ │ - Sign TA: Python script (TA only) │ │
│ └──────────────────┬─────────────────────┘ │
│ │ │
└─────────────────────┼────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Low-Level Tools (dependencies) │
│ │
│ - cargo: Rust compilation │
│ - gcc: Linking with OP-TEE libraries │
│ - objcopy: Symbol stripping │
│ - Python script: TA signing (TA only) │
│ │
└──────────────────────────────────────────────┘
Assume developers have Rust, Cargo, and the gcc toolchain installed and added to
PATH (the guide is in future plan). Then install cargo-optee using Cargo:
cargo install cargo-opteeThis section provides a quick start guide for building the Hello World example
using cargo-optee. Before proceeding, ensure you have set up the Docker
development environment. For detailed instructions on setting up the Docker
environment, refer to the QEMU emulation
guide.
First, pull and run the Docker image. We provide two types of images:
- no-std environment: For building TAs without the standard library (recommended for quick start)
- std environment: For building TAs with Rust standard library support
Pull and start the no-std development environment:
# Pull the pre-built development environment image
docker pull teaclave/teaclave-trustzone-emulator-nostd-expand-memory:latest
# Start the development container
docker run -it --rm \
--name teaclave_dev_env \
-v $(pwd):/root/teaclave_sdk_src \
-w /root/teaclave_sdk_src \
teaclave/teaclave-trustzone-emulator-nostd-expand-memory:latest📖 Note: If you need Rust standard library (std) support, refer to the Docker development environment guide with std support and use the
teaclave/teaclave-trustzone-emulator-std-expand-memory:latestimage.
1. Navigate to the Hello World example directory
Inside the Docker container, execute:
cd examples/hello_world-rs/2. Build the Trusted Application (TA)
Build the TA using cargo-optee. In the Docker environment, the OP-TEE TA
development kit is typically located at
/opt/teaclave/optee/optee_os/out/arm-plat-vexpress/export-ta_arm64:
# Build aarch64 no-std TA (default configuration)
cargo-optee build ta \
--manifest-path ta/Cargo.toml \
--ta-dev-kit-dir /opt/teaclave/optee/optee_os/out/arm-plat-vexpress/export-ta_arm64 \
--arch aarch64 \
--no-std3. Build the Client Application (CA)
Build the client application:
# Build aarch64 CA
cargo-optee build ca \
--manifest-path host/Cargo.toml \
--optee-client-export /opt/teaclave/optee/optee_client/export_arm64 \
--arch aarch64💡 Tip: If you configure metadata in your
Cargo.tomlfile (see Configuration System), you can simplify the command by only specifying--manifest-path:cargo-optee build ca --manifest-path host/Cargo.toml
After a successful build, you can find the generated files at the following locations:
- TA binary:
ta/target/aarch64-unknown-linux-gnu/release/133af0ca-bdab-11eb-9130-43bf7873bf67.ta - CA binary:
host/target/aarch64-unknown-linux-gnu/release/hello_world-rs
After building, you can:
- Use the
sync_to_emulatorcommand to sync build artifacts to the emulator environment - Start the QEMU emulator for testing
- Refer to the QEMU emulation guide for complete development and testing workflows
💡 Tip: If you configure metadata in
Cargo.toml(see Configuration System), you can simplify build commands by only specifying--manifest-path.
cargo-optee uses a flexible configuration system with the following priority
(highest to lowest):
- Command Line Arguments - Direct CLI flags override everything (see Build through CLI)
- Cargo.toml Metadata - Project-specific configuration in
[package.metadata.optee.*]sections (see Build through metadata) - Defaults - Built-in sensible defaults
This allows projects to define their standard configuration in Cargo.toml
while still permitting CLI overrides for specific builds.
Cargo-optee expects the following project structure by default.
project/
├── uuid.txt # TA UUID
├── ta/ # Trusted Application
│ ├── Cargo.toml
│ ├── src/
│ │ └── main.rs
│ └── build.rs # Build script
├── host/ # Client Application (host)
│ ├── Cargo.toml
│ ├── src/
│ │ └── main.rs
└── proto/ # Shared definitions such as TA command IDs and TA UUID
├── Cargo.toml
└── src/
└── lib.rs
See examples in the SDK for reference, such as hello_world-rs. The cargo new
command (planned, not yet available) will generate a project template with this
structure. For now, copy an existing example as a starting point.
For development and emulation, developers would like to build the one project and deploy to a target filesystem (e.g. QEMU shared folder) quickly. Frequent builds and quick rebuilds are common.
Using CLI arguments:
# 1. Create new project (future)
cargo-optee new my_app
cd my_app
# 2. Build TA and CA
cargo-optee build ta \
--ta-dev-kit-dir $TA_DEV_KIT_DIR \
--manifest-path ./ta/Cargo.toml \
--arch aarch64 \
--std
cargo-optee build ca \
--optee-client-export $OPTEE_CLIENT_EXPORT \
--manifest-path ./host/Cargo.toml \
--arch aarch64
# 3. Install to specific folder (future), e.g. QEMU shared folder for emulation
cargo-optee install --target /tmp/qemu-shared-folderUsing metadata configuration:
# 1. Configure once in Cargo.toml files, then simple builds
cd ta && cargo-optee build ta
cd ../host && cargo-optee build ca
# 2. Override specific parameters when needed
cd ta && cargo-optee build ta --debug # Override to debug build
cd host && cargo-optee build ca --arch arm # Override architectureFor production and CI environments, artifacts should be cleaned up after successful builds. It can help to avoid storage issues on CI runners.
Automated Build Pipeline:
#!/bin/bash
# CI build script
set -e
# Build TA (release mode)
cargo-optee build ta \
--ta-dev-kit-dir $TA_DEV_KIT_DIR \
--manifest-path ./ta/Cargo.toml \
--arch aarch64 \
--std \
--signing-key ./keys/production.pem
# Build CA (release mode)
cargo-optee build ca \
--optee-client-export $OPTEE_CLIENT_EXPORT \
--manifest-path ./host/Cargo.toml \
--arch aarch64
# Install to staging area (future)
cargo-optee install --target ./dist
# Clean build artifacts to save space (future)
cargo-optee clean --allcargo-optee build ta \
--ta-dev-kit-dir <PATH> \
[--manifest-path <PATH>] \
[--arch aarch64|arm] \
[--std] \
[--no-std] \
[--signing-key <PATH>] \
[--uuid-path <PATH>] \
[--debug]Required:
--ta-dev-kit-dir <PATH>: Path to OP-TEE TA development kit (available after building OP-TEE OS), user must provide this for building TAs.
Optional:
--manifest-path <PATH>: Path to Cargo.toml manifest file--arch <ARCH>: Target architecture (default:aarch64)aarch64: ARM 64-bit architecturearm: ARM 32-bit architecture
--std: Build with std support (usescargo -Z build-stdand custom target)--no-std: Build without std support (mutually exclusive with --std)--signing-key <PATH>: Path to signing key (default:<ta-dev-kit-dir>/keys/default_ta.pem)--uuid-path <PATH>: Path to UUID file (default:../uuid.txt)--debug: Build in debug mode (default: release mode)
Example:
# Build aarch64 TA with std support
cargo-optee build ta \
--ta-dev-kit-dir /opt/optee/export-ta_arm64 \
--manifest-path ./examples/ta/hello_world-rs/Cargo.toml \
--arch aarch64 \
--std
# Build arm TA without std (no-std)
cargo-optee build ta \
--ta-dev-kit-dir /opt/optee/export-ta_arm32 \
--manifest-path ./ta/Cargo.toml \
--arch arm
--no-std
# Build TA with Cargo.toml metadata configuration
# Note: ta-dev-kit-dir must be configured in Cargo.toml for this work
cargo-optee build ta \
--manifest-path ./ta/Cargo.tomlOutput:
- TA binary:
target/<target-triple>/release/<uuid>.ta - Intermediate files in
target/directory
cargo-optee build ca \
--optee-client-export <PATH> \
[--manifest-path <PATH>] \
[--arch aarch64|arm] \
[--debug]Required:
--optee-client-export <PATH>: Path to OP-TEE client library directory (available after building OP-TEE client), user must provide this for building CAs.
Optional:
--manifest-path <PATH>: Path to Cargo.toml manifest file--arch <ARCH>: Target architecture (default:aarch64)--debug: Build in debug mode (default: release mode)
Example:
# Build aarch64 client application
cargo-optee build ca \
--optee-client-export /opt/optee/export-client \
--manifest-path ./examples/ca/hello_world-rs/Cargo.toml \
--arch aarch64Output:
- CA binary:
target/<target-triple>/release/<binary-name>
We have one example for plugin: examples/ca/supp_plugin-rs-plugin.
cargo-optee build plugin \
--optee-client-export <PATH> \
--uuid-path <PATH> \
[--manifest-path <PATH>] \
[--arch aarch64|arm] \
[--debug]Required:
--optee-client-export <PATH>: Path to OP-TEE client library directory (available after building OP-TEE client), user must provide this for building plugins.--uuid-path <PATH>: Path to UUID file for naming the plugin
Optional:
--manifest-path <PATH>: Path to Cargo.toml manifest file--arch <ARCH>: Target architecture (default:aarch64)--debug: Build in debug mode (default: release mode)
Example:
# Build aarch64 plugin
cargo-optee build plugin \
--optee-client-export /opt/optee/export-client \
--manifest-path ./examples/ca/supp_plugin-rs-plugin/Cargo.toml \
--uuid-path ./examples/ca/supp_plugin-rs-plugin/plugin_uuid.txt \
--arch aarch64Output:
- Plugin binary:
target/<target-triple>/release/<uuid>.plugin.so
Configure TA builds in your Cargo.toml:
[package.metadata.optee.ta]
arch = "aarch64" # Target architecture: "aarch64" | "arm" (optional, default: "aarch64")
debug = false # Debug build: true | false (optional, default: false)
std = false # Use std library: true | false (optional, default: false)
uuid-path = "../uuid.txt" # Path to UUID file (optional, default: "../uuid.txt")
# Architecture-specific configuration (omitted architectures default to null/unsupported)
ta-dev-kit-dir = { aarch64 = "/opt/optee/export-ta_arm64", arm = "/opt/optee/export-ta_arm32" }
signing-key = "/path/to/key.pem" # Path to signing key (optional, defaults to ta-dev-kit/keys/default_ta.pem)Allowed entries:
arch: Target architecture ("aarch64"or"arm")debug: Build in debug mode (trueorfalse)std: Enable std library support (trueorfalse)uuid-path: Relative or absolute path to UUID fileta-dev-kit-dir: Architecture-specific paths to TA development kit (required)signing-key: Path to signing key file
Configure CA builds in your Cargo.toml:
[package.metadata.optee.ca]
arch = "aarch64" # Target architecture: "aarch64" | "arm" (optional, default: "aarch64")
debug = false # Debug build: true | false (optional, default: false)
# Architecture-specific configuration
# if your CA only supports aarch64, you can omit arm
optee-client-export = { aarch64 = "/opt/optee/export-client_arm64" } Allowed entries:
arch: Target architecture ("aarch64"or"arm")debug: Build in debug mode (trueorfalse)optee-client-export: Architecture-specific paths to OP-TEE client export (required)
Configure plugin builds in your Cargo.toml:
[package.metadata.optee.plugin]
arch = "aarch64" # Target architecture: "aarch64" | "arm" (optional, default: "aarch64")
debug = false # Debug build: true | false (optional, default: false)
uuid-path = "../plugin_uuid.txt" # Path to UUID file (required for plugins)
# Architecture-specific configuration
optee-client-export = { aarch64 = "/opt/optee/export-client_arm64", arm = "/opt/optee/export-client_arm32" }Allowed entries:
arch: Target architecture ("aarch64"or"arm")debug: Build in debug mode (trueorfalse)uuid-path: Relative or absolute path to UUID file (required for plugins)optee-client-export: Architecture-specific paths to OP-TEE client export (required)
| Feature | Status | Notes |
|---|---|---|
build ta |
✅ Implemented | Supports aarch64/arm, std/no-std |
build ca |
✅ Implemented | Supports aarch64/arm |
build plugin |
✅ Implemented | Supports aarch64/arm, builds shared library plugins |
clean |
✅ Implemented | Remove build artifacts |
new |
⏳ Planned | Project scaffolding |
install |
⏳ Planned | Deploy to target filesystem |
User Input:
cargo-optee build ta \
--ta-dev-kit-dir /opt/optee/export-ta_arm64 \
--manifest-path ./ta/Cargo.toml \
--arch aarch64cargo-optee translates to:
# 1. Clippy
cd ./ta
TA_DEV_KIT_DIR=/opt/optee/export-ta_arm64 \
RUSTFLAGS="-C panic=abort" \
cargo clippy --target aarch64-unknown-linux-gnu --release
# 2. Build
TA_DEV_KIT_DIR=/opt/optee/export-ta_arm64 \
RUSTFLAGS="-C panic=abort" \
cargo build --target aarch64-unknown-linux-gnu --release \
--manifest-path ./ta/Cargo.toml \
--config target.aarch64-unknown-linux-gnu.linker="aarch64-linux-gnu-gcc"
# 3. Strip
aarch64-linux-gnu-objcopy --strip-unneeded \
target/aarch64-unknown-linux-gnu/release/ta \
target/aarch64-unknown-linux-gnu/release/stripped_ta
# 4. Sign
python3 /opt/optee/export-ta_arm64/scripts/sign_encrypt.py \
--uuid <uuid-from-file> \
--key /opt/optee/export-ta_arm64/keys/default_ta.pem \
--in target/aarch64-unknown-linux-gnu/release/stripped_ta \
--out target/aarch64-unknown-linux-gnu/release/<uuid>.taUser Input:
cargo-optee build ta \
--ta-dev-kit-dir /opt/optee/export-ta_arm32 \
--manifest-path ./ta/Cargo.toml \
--arch arm \
--stdcargo-optee translates to:
# 1. Clippy
cd ./ta
TA_DEV_KIT_DIR=/opt/optee/export-ta_arm32 \
RUSTFLAGS="-C panic=abort" \
RUST_TARGET_PATH=/tmp/cargo-optee-XXXXX \
__CARGO_TESTS_ONLY_SRC_ROOT=/path/to/rust/library \
cargo -Z build-std=std,panic_abort clippy --target arm-unknown-optee --features std --release \
--manifest-path ./ta/Cargo.toml
# 2. Build
TA_DEV_KIT_DIR=/opt/optee/export-ta_arm32 \
RUSTFLAGS="-C panic=abort" \
RUST_TARGET_PATH=/tmp/cargo-optee-XXXXX \
__CARGO_TESTS_ONLY_SRC_ROOT=/path/to/rust/library \
cargo -Z build-std=std,panic_abort build --target arm-unknown-optee --features std --release \
--manifest-path ./ta/Cargo.toml \
--config target.arm-unknown-optee.linker="arm-linux-gnueabihf-gcc"
# 3. Strip
arm-linux-gnueabihf-objcopy --strip-unneeded \
target/arm-unknown-optee/release/ta \
target/arm-unknown-optee/release/stripped_ta
# 4. Sign
python3 /opt/optee/export-ta_arm32/scripts/sign_encrypt.py \
--uuid <uuid-from-file> \
--key /opt/optee/export-ta_arm32/keys/default_ta.pem \
--in target/arm-unknown-optee/release/stripped_ta \
--out target/arm-unknown-optee/release/<uuid>.taNote: /tmp/cargo-optee-XXXXX is a temporary directory containing the
embedded arm-unknown-optee.json target specification.
User Input:
cargo-optee build ca \
--optee-client-export /opt/optee/export-client \
--manifest-path ./host/Cargo.tomlcargo-optee translates to:
# 1. Clippy
cd ./host
OPTEE_CLIENT_EXPORT=/opt/optee/export-client \
cargo clippy --target aarch64-unknown-linux-gnu \
--manifest-path ./host/Cargo.toml
# 2. Build
OPTEE_CLIENT_EXPORT=/opt/optee/export-client \
cargo build --target aarch64-unknown-linux-gnu --release \
--manifest-path ./host/Cargo.toml \
--config target.aarch64-unknown-linux-gnu.linker="aarch64-linux-gnu-gcc"
# 3. Strip
aarch64-linux-gnu-objcopy --strip-unneeded \
target/aarch64-unknown-linux-gnu/release/<binary> \
target/aarch64-unknown-linux-gnu/release/<binary>This section explains how cargo orchestrates low-level tools to build the TA ELF binary. We use an aarch64 no-std TA as an example.
Dependency Structure:
ta
├── depends on: optee_utee (Rust API for OP-TEE TAs)
│ └── depends on: optee_utee_sys (FFI bindings to OP-TEE C API)
│ └── build.rs: outputs cargo:rustc-link-* directives
│ Links with C libraries from TA_DEV_KIT_DIR/lib/:
│ - libutee.a (OP-TEE user-space TA API)
│ - libutils.a (utility functions)
│ - libmbedtls.a (crypto library)
└── build.rs: uses optee_utee_build crate to:
- Configure TA properties (UUID, stack size, etc.)
- Generate TA header file (user_ta_header.rs)
- Output link directives
Build Flow:
Step 1: cargo-optee invokes cargo
(As shown in the previous section)
TA_DEV_KIT_DIR=/opt/optee/export-ta_arm64 \
RUSTFLAGS="-C panic=abort" \
cargo build --target aarch64-unknown-linux-gnu --release \
--config target.aarch64-unknown-linux-gnu.linker="aarch64-linux-gnu-gcc"Step 2: cargo prepares environment and invokes build scripts
Cargo automatically sets these environment variables:
TARGET=aarch64-unknown-linux-gnuPROFILE=releaseOUT_DIR=target/aarch64-unknown-linux-gnu/release/build/ta-<hash>/outRUSTC_LINKER=aarch64-linux-gnu-gcc(from--configflag)
Cargo inherits from cargo-optee:
TA_DEV_KIT_DIR=/opt/optee/export-ta_arm64RUSTFLAGS="-C panic=abort"
Cargo then executes build scripts in dependency order to set up the build directives:
-
optee_utee_sys/build.rs- Requires:
TA_DEV_KIT_DIR - Outputs
cargo:rustc-link-*directives to link C libraries:cargo:rustc-link-search={TA_DEV_KIT_DIR}/lib cargo:rustc-link-lib=static=utee cargo:rustc-link-lib=static=utils cargo:rustc-link-lib=static=mbedtls
- Requires:
-
ta/build.rs→ callsoptee_utee_buildcrate- Requires:
TA_DEV_KIT_DIR,TARGET,OUT_DIR,RUSTC_LINKER - Optional:
CARGO_PKG_VERSION,CARGO_PKG_DESCRIPTION(for automatic TA config) - Actions:
- Generates TA manifest (
user_ta_header.rs) with TA properties - Outputs linker directives based on target architecture and linker type
- Generates TA manifest (
- Requires:
Step 3: rustc compiles Rust source code
Rustc receives:
- Target triple:
--target aarch64-unknown-linux-gnu - Compiler flags:
-C panic=abort(fromRUSTFLAGS) - Profile:
release - All link directives from build scripts
Produces: .rlib files and object files (.o)
Step 4: gcc linker links final binary
The linker (specified by RUSTC_LINKER=aarch64-linux-gnu-gcc) links:
- Rust object files (
.o) - OP-TEE C static libraries:
libutee.a,libutils.a,libmbedtls.a - Using linker script:
ta.lds
Output: ELF binary at target/aarch64-unknown-linux-gnu/release/ta