diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4e933c1..eaf578ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,102 @@ jobs: ln -s $RUST_STD_DIR rust make std-examples + # Test cargo-optee build no-std examples - aarch64 + test-cargo-optee-nostd-aarch64: + runs-on: ubuntu-latest + container: + image: teaclave/teaclave-trustzone-emulator-nostd-expand-memory:latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup environment + run: | + # Run entrypoint.sh to set up the bash profile + /entrypoint.sh true + # Install jq for JSON parsing + apt-get update && apt-get install -y jq + - name: Test cargo-optee no-std build (aarch64) + shell: bash -l {0} # Use login shell to load the profile + run: | + # Build using cargo-optee tool with default aarch64 settings + ./ci/build.sh + + # Test cargo-optee build no-std examples - arm32 + test-cargo-optee-nostd-arm32: + runs-on: ubuntu-latest + container: + image: teaclave/teaclave-trustzone-emulator-nostd-expand-memory:latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup environment + run: | + # Run entrypoint.sh to set up the bash profile + /entrypoint.sh true + # Install jq for JSON parsing + apt-get update && apt-get install -y jq + - name: Test cargo-optee no-std build (arm32) + shell: bash -l {0} # Use login shell to load the profile + run: | + # Switch to ARM32 configuration and build + # Note: Explicit environment reload needed for GitHub Actions + # (wrapper function in .bashrc not working for non-interactive shells: docker exec cmd=[...]) + # We can fix this by updating entrypoint.sh to make ~/.profile source ~/.bashrc + switch_config --ta no-std/arm32 && \ + switch_config --host arm32 && \ + source ${TEACLAVE_TOOLCHAIN_BASE}/environment && \ + ./ci/build.sh --ta arm --host arm + + # Test cargo-optee build std examples - aarch64 + test-cargo-optee-std-aarch64: + runs-on: ubuntu-latest + container: + image: teaclave/teaclave-trustzone-emulator-std-expand-memory:latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup environment + run: | + # Run entrypoint.sh to set up the bash profile + /entrypoint.sh true + # Install jq for JSON parsing + apt-get update && apt-get install -y jq + - name: Test cargo-optee std build (aarch64) + shell: bash -l {0} # Use login shell to load the profile + run: | + # Link rust directory for std support + ln -s $RUST_STD_DIR rust + # Build using cargo-optee tool with default aarch64 std settings + ./ci/build.sh --std + + # Test cargo-optee build std examples - arm32 + test-cargo-optee-std-arm32: + runs-on: ubuntu-latest + container: + image: teaclave/teaclave-trustzone-emulator-std-expand-memory:latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup environment + run: | + # Run entrypoint.sh to set up the bash profile + /entrypoint.sh true + # Install jq for JSON parsing + apt-get update && apt-get install -y jq + - name: Test cargo-optee std build (arm32) + shell: bash -l {0} # Use login shell to load the profile + run: | + # Link rust directory for std support + ln -s $RUST_STD_DIR rust + # Switch to ARM32 std configuration and build + # Note: Explicit environment reload needed for GitHub Actions + # (wrapper function in .bashrc not working for non-interactive shells: docker exec cmd=[...]) + # We can fix this by updating entrypoint.sh to make ~/.profile source ~/.bashrc + switch_config --ta std/arm32 && \ + switch_config --host arm32 && \ + source ${TEACLAVE_TOOLCHAIN_BASE}/environment && \ + ./ci/build.sh --ta arm --host arm --std + license: runs-on: ubuntu-latest steps: diff --git a/.licenserc.yaml b/.licenserc.yaml index c0456a78..d0c5dc2d 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -29,7 +29,7 @@ header: - '**/Cargo.lock' - 'KEYS' - 'DISCLAIMER' - - '*.json' + - '**/*.json' - 'examples/tls_server-rs/ta/test-ca/**' - '**/uuid.txt' - '**/plugin_uuid.txt' diff --git a/README.md b/README.md index ebdcf2a0..fe956a48 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ The Docker image automates the entire setup process for TrustZone emulation in QEMU, enabling you to focus on writing and testing your applications efficiently, without the hassle of manual configuration. +**UPDATES:** We have introduced a more convenient build tool, [cargo-optee](./cargo-optee/README.md), +which provides better abstraction and a more ergonomic developer experience. In the long term, +we plan to gradually move away from the Makefile + Cargo workflow and adopt a more idiomatic Rust-based +development approach. To ensure a smooth transition, the existing Makefile-based build components are +still retained and supported. + **Choose your development mode in Emulator:** - 🚀 [Quick Emulation And Development in Docker](docs/emulate-and-dev-in-docker.md) - 🚀 [Developing TAs with Rust Standard Library](docs/emulate-and-dev-in-docker-std.md) diff --git a/cargo-optee/Cargo.toml b/cargo-optee/Cargo.toml new file mode 100644 index 00000000..d9322789 --- /dev/null +++ b/cargo-optee/Cargo.toml @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "cargo-optee" +version = "0.1.0" +authors = ["Teaclave Contributors "] +license = "Apache-2.0" +repository = "https://github.com/apache/teaclave-trustzone-sdk.git" +description = "A cargo subcommand for building OP-TEE Trusted Applications" +edition = "2021" + +[[bin]] +name = "cargo-optee" +path = "src/main.rs" + +[dependencies] +clap = { version = "4.5", features = ["derive"] } +anyhow = "1.0" +cargo_metadata = "0.18" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +toml = "0.8" +indexmap = "=2.11.4" +env_logger = "0.11" +log = "0.4" +tempfile = "3.8" +dirs = "5.0" diff --git a/cargo-optee/README.md b/cargo-optee/README.md new file mode 100644 index 00000000..d4cc47f6 --- /dev/null +++ b/cargo-optee/README.md @@ -0,0 +1,690 @@ +# cargo-optee + +A Cargo subcommand for building OP-TEE Trusted Applications (TAs) and Client +Applications (CAs) in Rust. + +## Overview + +`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. + +## High-Level Design + +### Architecture + +``` + ┌──────────────────┐ + │ 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/xargo + gcc │ │ + │ │ - Strip symbols: objcopy │ │ + │ │ - Sign TA: Python script (TA only) │ │ + │ └──────────────────┬─────────────────────┘ │ + │ │ │ + └─────────────────────┼────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────┐ + │ Low-Level Tools (dependencies) │ + │ │ + │ - cargo/xargo: Rust compilation │ + │ - gcc: Linking with OP-TEE libraries │ + │ - objcopy: Symbol stripping │ + │ - Python script: TA signing (TA only) │ + │ │ + └──────────────────────────────────────────────┘ +``` + +## Quick Start + +### Installation + +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: + +```bash +cargo install cargo-optee +``` + +### Quick Build for Hello World + +This 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](../docs/emulate-and-dev-in-docker.md). + +#### Prerequisites + +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:** +```bash +# 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](../docs/emulate-and-dev-in-docker-std.md) and use the +> `teaclave/teaclave-trustzone-emulator-std-expand-memory:latest` image. + +#### Build Steps + +**1. Navigate to the Hello World example directory** + +Inside the Docker container, execute: +```bash +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`: + +```bash +# 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-std +``` + +**3. Build the Client Application (CA)** + +Build the client application: +```bash +# 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.toml` file (see +> [Configuration System](#configuration-system)), you can simplify the command +> by only specifying `--manifest-path`: +> ```bash +> cargo-optee build ca --manifest-path host/Cargo.toml +> ``` + +#### Build Output + +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` + +#### Next Steps + +After building, you can: +1. Use the `sync_to_emulator` command to sync build artifacts to the emulator + environment +2. Start the QEMU emulator for testing +3. Refer to the [QEMU emulation guide](../docs/emulate-and-dev-in-docker.md) for + complete development and testing workflows + +> 💡 **Tip**: If you configure metadata in `Cargo.toml` (see [Configuration +> System](#configuration-system)), you can simplify build commands by only +> specifying `--manifest-path`. + +## Configuration System + +`cargo-optee` uses a flexible configuration system with the following priority +(highest to lowest): + +1. **Command Line Arguments** - Direct CLI flags override everything (see [Build + through CLI](#build-through-cli)) +2. **Cargo.toml Metadata** - Project-specific configuration in + `[package.metadata.optee.*]` sections (see [Build through + metadata](#build-through-metadata)) +3. **Defaults** - Built-in sensible defaults + +This allows projects to define their standard configuration in `Cargo.toml` +while still permitting CLI overrides for specific builds. + +### Project Structure + +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. + +### Usage Workflows (including future design) + +#### Development/Emulation Environment + +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:** +```bash +# 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-folder +``` + +**Using metadata configuration:** +```bash +# 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 architecture +``` + +#### Production/CI Environment + +For 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:** +```bash +#!/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 --all +``` + +### Build through CLI + +#### Build Trusted Application (TA) + +```bash +cargo-optee build ta \ + --ta-dev-kit-dir \ + [--manifest-path ] \ + [--arch aarch64|arm] \ + [--std] \ + [--no-std] \ + [--signing-key ] \ + [--uuid-path ] \ + [--debug] +``` + +**Required:** +- `--ta-dev-kit-dir `: Path to OP-TEE TA development kit (available after + building OP-TEE OS), user must provide this for building TAs. + +**Optional:** +- `--manifest-path `: Path to Cargo.toml manifest file +- `--arch `: Target architecture (default: `aarch64`) + - `aarch64`: ARM 64-bit architecture + - `arm`: ARM 32-bit architecture +- `--std`: Build with std support (uses xargo and custom target) +- `--no-std`: Build without std support (uses cargo, mutually exclusive with + --std) +- `--signing-key `: Path to signing key (default: + `/keys/default_ta.pem`) +- `--uuid-path `: Path to UUID file (default: `../uuid.txt`) +- `--debug`: Build in debug mode (default: release mode) + +**Example:** +```bash +# Build aarch64 TA with std support +cargo-optee build ta \ + --ta-dev-kit-dir /opt/optee/export-ta_arm64 \ + --manifest-path ./examples/hello_world-rs/ta/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.toml +``` + +**Output:** +- TA binary: `target//release/.ta` +- Intermediate files in `target/` directory + +#### Build Client Application (CA) + +```bash +cargo-optee build ca \ + --optee-client-export \ + [--manifest-path ] \ + [--arch aarch64|arm] \ + [--debug] +``` + +**Required:** +- `--optee-client-export `: Path to OP-TEE client library directory + (available after building OP-TEE client), user must provide this for building + CAs. + +**Optional:** +- `--manifest-path `: Path to Cargo.toml manifest file +- `--arch `: Target architecture (default: `aarch64`) +- `--debug`: Build in debug mode (default: release mode) + +**Example:** +```bash +# Build aarch64 client application +cargo-optee build ca \ + --optee-client-export /opt/optee/export-client \ + --manifest-path ./examples/hello_world-rs/host/Cargo.toml \ + --arch aarch64 +``` + +**Output:** +- CA binary: `target//release/` + +#### Build Plugin + +We have one example for plugin: `supp_plugin-rs/plugin`. + +```bash +cargo-optee build plugin \ + --optee-client-export \ + --uuid-path \ + [--manifest-path ] \ + [--arch aarch64|arm] \ + [--debug] +``` + +**Required:** +- `--optee-client-export `: Path to OP-TEE client library directory + (available after building OP-TEE client), user must provide this for building + plugins. +- `--uuid-path `: Path to UUID file for naming the plugin + +**Optional:** +- `--manifest-path `: Path to Cargo.toml manifest file +- `--arch `: Target architecture (default: `aarch64`) +- `--debug`: Build in debug mode (default: release mode) + +**Example:** +```bash +# Build aarch64 plugin +cargo-optee build plugin \ + --optee-client-export /opt/optee/export-client \ + --manifest-path ./examples/supp_plugin-rs/plugin/Cargo.toml \ + --uuid-path ./examples/supp_plugin-rs/plugin_uuid.txt \ + --arch aarch64 +``` + +**Output:** +- Plugin binary: `target//release/.plugin.so` + +### Build through metadata + +#### Trusted Application (TA) Metadata + +Configure TA builds in your `Cargo.toml`: + +```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 (`true` or `false`) +- `std`: Enable std library support (`true` or `false`) +- `uuid-path`: Relative or absolute path to UUID file +- `ta-dev-kit-dir`: Architecture-specific paths to TA development kit (required) +- `signing-key`: Path to signing key file + +#### Client Application (CA) Metadata + +Configure CA builds in your `Cargo.toml`: + +```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 (`true` or `false`) +- `optee-client-export`: Architecture-specific paths to OP-TEE client export + (required) + +#### Plugin Metadata + +Configure plugin builds in your `Cargo.toml`: + +```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 (`true` or `false`) +- `uuid-path`: Relative or absolute path to UUID file (required for plugins) +- `optee-client-export`: Architecture-specific paths to OP-TEE client export + (required) + +## Implementation Status + +| 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 | + +----- +## Appendix + +### Complete Parameter Reference + +#### Command Convention: User Input to Cargo Commands + +##### Example 1: Build aarch64 no-std TA + +**User Input:** +```bash +cargo-optee build ta \ + --ta-dev-kit-dir /opt/optee/export-ta_arm64 \ + --manifest-path ./ta/Cargo.toml \ + --arch aarch64 +``` + +**cargo-optee translates to:** +```bash +# 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 \ + --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/.ta +``` + +#### Example 2: Build arm std TA + +**User Input:** +```bash +cargo-optee build ta \ + --ta-dev-kit-dir /opt/optee/export-ta_arm32 \ + --manifest-path ./ta/Cargo.toml \ + --arch arm \ + --std +``` + +**cargo-optee translates to:** +```bash +# 1. Clippy +cd ./ta +TA_DEV_KIT_DIR=/opt/optee/export-ta_arm32 \ +RUSTFLAGS="-C panic=abort" \ +RUST_TARGET_PATH=/tmp/cargo-optee-XXXXX \ +xargo 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 \ +xargo 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 \ + --key /opt/optee/export-ta_arm32/keys/default_ta.pem \ + --in target/arm-unknown-optee/release/stripped_ta \ + --out target/arm-unknown-optee/release/.ta +``` + +**Note:** `/tmp/cargo-optee-XXXXX` is a temporary directory containing the +embedded `arm-unknown-optee.json` target specification. + +##### Example 3: Build aarch64 CA (Client Application) + +**User Input:** +```bash +cargo-optee build ca \ + --optee-client-export /opt/optee/export-client \ + --manifest-path ./host/Cargo.toml +``` + +**cargo-optee translates to:** +```bash +# 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/ \ + target/aarch64-unknown-linux-gnu/release/ +``` + +#### Build Command Convention: Cargo to Low-Level Tools + +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) +```bash +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-gnu` +- `PROFILE=release` +- `OUT_DIR=target/aarch64-unknown-linux-gnu/release/build/ta-/out` +- `RUSTC_LINKER=aarch64-linux-gnu-gcc` (from `--config` flag) + +Cargo inherits from cargo-optee: +- `TA_DEV_KIT_DIR=/opt/optee/export-ta_arm64` +- `RUSTFLAGS="-C panic=abort"` + +Cargo then executes build scripts in dependency order to set up the build +directives: + +1. **`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 + ``` + +2. **`ta/build.rs`** → calls **`optee_utee_build`** crate + - Requires: `TA_DEV_KIT_DIR`, `TARGET`, `OUT_DIR`, `RUSTC_LINKER` + - Optional: `CARGO_PKG_VERSION`, `CARGO_PKG_DESCRIPTION` (for automatic TA + config) + - Actions: + 1. Generates TA manifest (`user_ta_header.rs`) with TA properties + 2. Outputs linker directives based on target architecture and linker type + +**Step 3: rustc compiles Rust source code** + +Rustc receives: +- Target triple: `--target aarch64-unknown-linux-gnu` +- Compiler flags: `-C panic=abort` (from `RUSTFLAGS`) +- 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` diff --git a/cargo-optee/aarch64-unknown-optee.json b/cargo-optee/aarch64-unknown-optee.json new file mode 100644 index 00000000..44900141 --- /dev/null +++ b/cargo-optee/aarch64-unknown-optee.json @@ -0,0 +1,21 @@ +{ + "arch": "aarch64", + "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", + "features": "+strict-align", + "dynamic-linking": false, + "executables": true, + "has-rpath": true, + "linker-flavor": "ld", + "linker-is-gnu": true, + "llvm-target": "aarch64-unknown-linux-gnu", + "max-atomic-width": 128, + "os": "optee", + "position-independent-executables": true, + "relro-level": "full", + "target-c-int-width": "32", + "target-endian": "little", + "target-pointer-width": "64", + "vendor": "unknown", + "panic-strategy": "abort" +} + \ No newline at end of file diff --git a/cargo-optee/arm-unknown-optee.json b/cargo-optee/arm-unknown-optee.json new file mode 100644 index 00000000..79cf1257 --- /dev/null +++ b/cargo-optee/arm-unknown-optee.json @@ -0,0 +1,21 @@ +{ + "arch": "arm", + "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", + "dynamic-linking": false, + "executables": true, + "features": "+strict-align,+v6,+vfp2", + "has-rpath": true, + "linker-flavor": "ld", + "linker-is-gnu": true, + "llvm-target": "arm-unknown-linux-gnueabihf", + "max-atomic-width": 64, + "os": "optee", + "position-independent-executables": true, + "relro-level": "full", + "target-c-int-width": "32", + "target-endian": "little", + "target-pointer-width": "32", + "vendor": "unknown", + "panic-strategy": "abort", + "singlethread": true +} diff --git a/cargo-optee/src/ca_builder.rs b/cargo-optee/src/ca_builder.rs new file mode 100644 index 00000000..9744e337 --- /dev/null +++ b/cargo-optee/src/ca_builder.rs @@ -0,0 +1,258 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use anyhow::{bail, Result}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use crate::common; +use crate::common::{ + get_package_name, get_target_and_cross_compile, get_target_directory_from_metadata, + print_cargo_command, print_output_and_bail, read_uuid_from_file, BuildMode, + ChangeDirectoryGuard, +}; +use crate::config::CaBuildConfig; + +// Main function to build the CA, optionally installing to a target directory +pub fn build_ca(config: CaBuildConfig, install_dir: Option<&Path>) -> Result<()> { + // Change to the CA directory + let _guard = ChangeDirectoryGuard::new(&config.path)?; + + let component_type = if config.plugin { "Plugin" } else { "CA" }; + // Get the absolute path for better clarity + let absolute_path = std::fs::canonicalize(&config.path).unwrap_or_else(|_| config.path.clone()); + println!( + "Building {} in directory: {}", + component_type, + absolute_path.display() + ); + + // Step 1: Run clippy for code quality checks + run_clippy(&config)?; + + // Step 2: Build the CA + build_binary(&config)?; + + // Step 3: Post-build processing (strip for binaries, copy for plugins) + let final_binary = post_build(&config)?; + + // Print the final binary path with descriptive prompt + let absolute_final_binary = final_binary + .canonicalize() + .unwrap_or_else(|_| final_binary.clone()); + if config.plugin { + println!("Plugin copied to: {}", absolute_final_binary.display()); + } else { + println!( + "CA binary stripped and saved to: {}", + absolute_final_binary.display() + ); + } + + // Step 4: Install if requested + if let Some(install_dir) = install_dir { + use std::fs; + + // Check if install directory exists + if !install_dir.exists() { + bail!("Install directory does not exist: {:?}", install_dir); + } + + // Get package name from the final binary path + let package_name = final_binary + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| anyhow::anyhow!("Could not get binary name"))?; + + // Copy binary to install directory + let dest_path = install_dir.join(package_name); + fs::copy(&final_binary, &dest_path)?; + + println!( + "{} installed to: {:?}", + component_type, + dest_path.canonicalize().unwrap_or(dest_path) + ); + } + + println!("{} build successfully!", component_type); + + Ok(()) +} + +fn run_clippy(config: &CaBuildConfig) -> Result<()> { + println!("Running cargo fmt and clippy..."); + + // Run cargo fmt + let fmt_output = Command::new("cargo").arg("fmt").output()?; + + if !fmt_output.status.success() { + print_output_and_bail("cargo fmt", &fmt_output)?; + } + + // Determine target based on arch (CA runs in Normal World Linux) + let (target, _cross_compile) = get_target_and_cross_compile(config.arch, BuildMode::Ca)?; + + let mut clippy_cmd = Command::new("cargo"); + clippy_cmd.arg("clippy"); + clippy_cmd.arg("--target").arg(&target); + + // Set OPTEE_CLIENT_EXPORT environment variable for build scripts + clippy_cmd.env("OPTEE_CLIENT_EXPORT", &config.optee_client_export); + + clippy_cmd.arg("--"); + clippy_cmd.arg("-D").arg("warnings"); + clippy_cmd.arg("-D").arg("clippy::unwrap_used"); + clippy_cmd.arg("-D").arg("clippy::expect_used"); + clippy_cmd.arg("-D").arg("clippy::panic"); + + let clippy_output = clippy_cmd.output()?; + + if !clippy_output.status.success() { + print_output_and_bail("clippy", &clippy_output)?; + } + + Ok(()) +} + +fn build_binary(config: &CaBuildConfig) -> Result<()> { + let component_type = if config.plugin { "Plugin" } else { "CA" }; + println!("Building {} binary...", component_type); + + // Determine target and cross-compile based on arch (CA runs in Normal World Linux) + let (target, cross_compile) = get_target_and_cross_compile(config.arch, BuildMode::Ca)?; + + let mut build_cmd = Command::new("cargo"); + build_cmd.arg("build"); + build_cmd.arg("--target").arg(&target); + + // Add --no-default-features if specified + if config.no_default_features { + build_cmd.arg("--no-default-features"); + } + + // Add additional features if specified + if let Some(ref features) = config.features { + build_cmd.arg("--features").arg(features); + } + + if !config.debug { + build_cmd.arg("--release"); + } + + // Configure linker + let linker = format!("{}gcc", cross_compile); + let linker_cfg = format!("target.{}.linker=\"{}\"", target, linker); + build_cmd.arg("--config").arg(&linker_cfg); + + // Set OPTEE_CLIENT_EXPORT environment variable + build_cmd.env("OPTEE_CLIENT_EXPORT", &config.optee_client_export); + + // Apply custom environment variables + for (key, value) in &config.env { + build_cmd.env(key, value); + } + + // Print the full cargo build command for debugging + print_cargo_command(&build_cmd, "Building CA binary"); + + let build_output = build_cmd.output()?; + + if !build_output.status.success() { + print_output_and_bail("build", &build_output)?; + } + + Ok(()) +} + +fn post_build(config: &CaBuildConfig) -> Result { + if config.plugin { + copy_plugin(config) + } else { + strip_binary(config) + } +} + +fn copy_plugin(config: &CaBuildConfig) -> Result { + println!("Processing plugin..."); + + // Determine target based on arch (CA runs in Normal World Linux) + let (target, _cross_compile) = get_target_and_cross_compile(config.arch, BuildMode::Ca)?; + + let profile = if config.debug { "debug" } else { "release" }; + + // Use cargo metadata to get the target directory (supports workspace and CARGO_TARGET_DIR) + let target_directory = get_target_directory_from_metadata()?; + let target_dir = target_directory.join(target).join(profile); + + // Get the library name from Cargo.toml (we're already in the project directory) + let lib_name = get_package_name()?; + + // Plugin is built as a shared library (lib.so) + let plugin_src = common::join_format_and_check::<&str>( + &target_dir, + &[], + &format!("lib{}.so", lib_name), + "Plugin library", + )?; + + // Read UUID from specified file + let uuid = read_uuid_from_file( + config + .uuid_path + .as_ref() + .ok_or_else(|| anyhow::anyhow!("UUID path is required for plugin builds"))?, + )?; + + // Copy to .plugin.so + let plugin_dest = target_dir.join(format!("{}.plugin.so", uuid)); + std::fs::copy(&plugin_src, &plugin_dest)?; + + Ok(plugin_dest) +} + +fn strip_binary(config: &CaBuildConfig) -> Result { + println!("Stripping binary..."); + + // Determine target and cross-compile based on arch (CA runs in Normal World Linux) + let (target, cross_compile) = get_target_and_cross_compile(config.arch, BuildMode::Ca)?; + + let profile = if config.debug { "debug" } else { "release" }; + + // Use cargo metadata to get the target directory (supports workspace and CARGO_TARGET_DIR) + let target_directory = get_target_directory_from_metadata()?; + let target_dir = target_directory.join(target).join(profile); + + // Get the binary name from Cargo.toml (we're already in the project directory) + let binary_name = get_package_name()?; + + let binary_path = common::join_and_check(&target_dir, &[binary_name], "Binary")?; + + let objcopy = format!("{}objcopy", cross_compile); + + let strip_output = Command::new(&objcopy) + .arg("--strip-unneeded") + .arg(&binary_path) + .arg(&binary_path) // Strip in place + .output()?; + + if !strip_output.status.success() { + print_output_and_bail(&objcopy, &strip_output)?; + } + + Ok(binary_path) +} diff --git a/cargo-optee/src/cli.rs b/cargo-optee/src/cli.rs new file mode 100644 index 00000000..4cedfd44 --- /dev/null +++ b/cargo-optee/src/cli.rs @@ -0,0 +1,209 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::{Args, Parser, Subcommand}; +use std::path::PathBuf; + +use crate::common::Arch; + +#[derive(Debug, Parser)] +#[command(version, about)] +pub struct Cli { + #[command(subcommand)] + pub cmd: Command, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Build OP-TEE components + #[clap(name = "build")] + #[command(subcommand)] + Build(BuildCommand), + /// Install OP-TEE components + #[clap(name = "install")] + #[command(subcommand)] + Install(InstallCommand), + /// Clean OP-TEE components + #[clap(name = "clean")] + Clean { + #[command(flatten)] + clean_cmd: CleanCommand, + }, +} + +#[derive(Debug, Subcommand)] +pub enum BuildCommand { + /// Build a Trusted Application (TA) + #[command(about = "Build a Trusted Application (TA)")] + TA { + #[command(flatten)] + build_cmd: TABuildArgs, + }, + /// Build a Client Application (Host) + #[command(about = "Build a Client Application (Host)")] + CA { + #[command(flatten)] + build_cmd: CABuildArgs, + }, + /// Build a Plugin (Shared Library) + #[command(about = "Build a Plugin (Shared Library)")] + Plugin { + #[command(flatten)] + build_cmd: PluginBuildArgs, + }, +} + +#[derive(Debug, Subcommand)] +pub enum InstallCommand { + /// Install a Trusted Application (TA) + #[command(about = "Install a Trusted Application (TA) to target directory")] + TA { + /// Target directory to install the TA binary (default: "shared") + #[arg(long = "target-dir", default_value = "shared")] + target_dir: PathBuf, + + #[command(flatten)] + build_cmd: TABuildArgs, + }, + /// Install a Client Application (Host) + #[command(about = "Install a Client Application (Host) to target directory")] + CA { + /// Target directory to install the CA binary (default: "shared") + #[arg(long = "target-dir", default_value = "shared")] + target_dir: PathBuf, + + #[command(flatten)] + build_cmd: CABuildArgs, + }, + /// Install a Plugin (Shared Library) + #[command(about = "Install a Plugin (Shared Library) to target directory")] + Plugin { + /// Target directory to install the plugin binary (default: "shared") + #[arg(long = "target-dir", default_value = "shared")] + target_dir: PathBuf, + + #[command(flatten)] + build_cmd: PluginBuildArgs, + }, +} + +/// Clean command arguments +#[derive(Debug, Args)] +pub struct CleanCommand { + /// Path to the Cargo.toml manifest file + #[arg(long = "manifest-path")] + pub manifest_path: Option, +} + +/// Common build command arguments shared across TA, CA, and Plugin builds +#[derive(Debug, Args)] +pub struct CommonBuildArgs { + /// Path to the Cargo.toml manifest file + #[arg(long = "manifest-path")] + pub manifest_path: Option, + + /// Target architecture (default: aarch64) + #[arg(long = "arch")] + pub arch: Option, + + /// Enable debug build (default: false) + #[arg(long = "debug")] + pub debug: bool, + + /// Environment overrides in the form of `"KEY=VALUE"` strings. This flag can be repeated. + /// + /// This is generally not needed to be used explicitly during regular development. + /// + /// This makes sense to be used to specify custom var e.g. `RUSTFLAGS`. + #[arg(long = "env", value_parser = parse_env_var, action = clap::ArgAction::Append)] + pub env: Vec<(String, String)>, + + /// Disable default features (will append --no-default-features to cargo build) + #[arg(long = "no-default-features")] + pub no_default_features: bool, + + /// Custom features to enable (will append --features to cargo build) + #[arg(long = "features")] + pub features: Option, +} + +/// TA-specific build arguments +#[derive(Debug, Args)] +pub struct TABuildArgs { + #[command(flatten)] + pub common: CommonBuildArgs, + + /// Enable std feature for the TA + /// If neither --std nor --no-std is specified, the value will be read from Cargo.toml metadata + #[arg(long = "std", action = clap::ArgAction::SetTrue, conflicts_with = "no_std")] + pub std: bool, + + /// Disable std feature for the TA (use no-std mode) + /// If neither --std nor --no-std is specified, the value will be read from Cargo.toml metadata + #[arg(long = "no-std", action = clap::ArgAction::SetTrue, conflicts_with = "std")] + pub no_std: bool, + + /// OP-TEE TA development kit export directory + #[arg(long = "ta-dev-kit-dir")] + pub ta_dev_kit_dir: Option, + + /// TA signing key path (default: TA_DEV_KIT_DIR/keys/default_ta.pem) + #[arg(long = "signing-key")] + pub signing_key: Option, + + /// UUID file path (default: "../uuid.txt") + #[arg(long = "uuid-path")] + pub uuid_path: Option, +} + +/// CA-specific build arguments +#[derive(Debug, Args)] +pub struct CABuildArgs { + #[command(flatten)] + pub common: CommonBuildArgs, + + /// OP-TEE client export directory + #[arg(long = "optee-client-export")] + pub optee_client_export: Option, +} + +/// Plugin-specific build arguments +#[derive(Debug, Args)] +pub struct PluginBuildArgs { + #[command(flatten)] + pub common: CommonBuildArgs, + + /// OP-TEE client export directory + #[arg(long = "optee-client-export")] + pub optee_client_export: Option, + + /// UUID file path (default: "../uuid.txt") + #[arg(long = "uuid-path")] + pub uuid_path: Option, +} + +/// Parse environment variable in KEY=VALUE format +pub fn parse_env_var(s: &str) -> Result<(String, String), String> { + s.split_once('=') + .map(|(key, value)| (key.to_string(), value.to_string())) + .ok_or_else(|| { + format!( + "Invalid environment variable format: '{}'. Expected 'KEY=VALUE'", + s + ) + }) +} diff --git a/cargo-optee/src/common.rs b/cargo-optee/src/common.rs new file mode 100644 index 00000000..3e711b88 --- /dev/null +++ b/cargo-optee/src/common.rs @@ -0,0 +1,311 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use anyhow::{bail, Result}; +use clap::ValueEnum; +use serde_json; +use std::env; +use std::fs; +use std::path::PathBuf; +use std::process::{Command, Output}; +use toml::Value; + +/// RAII guard to ensure we return to the original directory +pub struct ChangeDirectoryGuard { + original: PathBuf, +} + +impl ChangeDirectoryGuard { + pub fn new(new_dir: &PathBuf) -> Result { + let original = env::current_dir()?; + env::set_current_dir(new_dir)?; + Ok(Self { original }) + } +} + +impl Drop for ChangeDirectoryGuard { + fn drop(&mut self) { + let _ = env::set_current_dir(&self.original); + } +} + +/// Target architecture for building +#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)] +pub enum Arch { + /// ARM 64-bit architecture + Aarch64, + /// ARM 32-bit architecture + Arm, +} + +impl std::str::FromStr for Arch { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "aarch64" | "arm64" => Ok(Arch::Aarch64), + "arm" | "arm32" => Ok(Arch::Arm), + _ => Err(format!("Invalid architecture: {}", s)), + } + } +} + +/// Build mode for OP-TEE components +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BuildMode { + /// Client Application (CA) - runs in Normal World Linux environment + /// Can use standard library, uses standard Linux targets + Ca, + /// Trusted Application with std support + /// Uses OP-TEE custom targets (e.g., aarch64-unknown-optee) + TaStd, + /// Trusted Application without std support + /// Uses standard Linux targets but runs in TEE environment + TaNoStd, +} + +/// Target configurations for different architectures and build modes +/// Format: (Architecture, BuildMode, target, cross_compile_prefix) +const TARGET_CONFIGS: [(Arch, BuildMode, &str, &str); 6] = [ + // ARM 32-bit configurations + ( + Arch::Arm, + BuildMode::Ca, + "arm-unknown-linux-gnueabihf", + "arm-linux-gnueabihf-", + ), + ( + Arch::Arm, + BuildMode::TaNoStd, + "arm-unknown-linux-gnueabihf", + "arm-linux-gnueabihf-", + ), + ( + Arch::Arm, + BuildMode::TaStd, + "arm-unknown-optee", + "arm-linux-gnueabihf-", + ), + // AArch64 configurations + ( + Arch::Aarch64, + BuildMode::Ca, + "aarch64-unknown-linux-gnu", + "aarch64-linux-gnu-", + ), + ( + Arch::Aarch64, + BuildMode::TaNoStd, + "aarch64-unknown-linux-gnu", + "aarch64-linux-gnu-", + ), + ( + Arch::Aarch64, + BuildMode::TaStd, + "aarch64-unknown-optee", + "aarch64-linux-gnu-", + ), +]; + +/// Unified function to derive target and cross-compile prefix from architecture and build mode +pub fn get_target_and_cross_compile(arch: Arch, mode: BuildMode) -> Result<(String, String)> { + for &(config_arch, config_mode, target, cross_compile_prefix) in &TARGET_CONFIGS { + if config_arch == arch && config_mode == mode { + return Ok((target.to_string(), cross_compile_prefix.to_string())); + } + } + + bail!( + "No target configuration found for arch: {:?}, mode: {:?}", + arch, + mode + ) +} + +/// Helper function to print command output and return error +pub fn print_output_and_bail(cmd_name: &str, output: &Output) -> Result<()> { + eprintln!( + "{} stdout: {}", + cmd_name, + String::from_utf8_lossy(&output.stdout) + ); + eprintln!( + "{} stderr: {}", + cmd_name, + String::from_utf8_lossy(&output.stderr) + ); + bail!( + "{} failed with exit code: {:?}", + cmd_name, + output.status.code() + ) +} + +/// Print cargo command for debugging +pub fn print_cargo_command(cmd: &Command, description: &str) { + println!("{}...", description); + + // Extract program and args + let program = cmd.get_program(); + let args: Vec<_> = cmd.get_args().collect(); + + // Extract all environment variables + let envs: Vec = cmd + .get_envs() + .filter_map(|(k, v)| match (k.to_str(), v.and_then(|v| v.to_str())) { + (Some(key), Some(value)) => Some(format!("{}={}", key, value)), + _ => None, + }) + .collect(); + + // Print environment variables + if !envs.is_empty() { + println!(" Environment: {}", envs.join(" ")); + } + + // Print command + println!( + " Command: {} {}", + program.to_string_lossy(), + args.into_iter() + .map(|s| s.to_string_lossy()) + .collect::>() + .join(" ") + ); +} + +/// Get the target directory using cargo metadata +pub fn get_target_directory_from_metadata() -> Result { + // We're already in the project directory, so no need for --manifest-path + let output = Command::new("cargo") + .arg("metadata") + .arg("--format-version") + .arg("1") + .arg("--no-deps") + .output()?; + + if !output.status.success() { + bail!("Failed to get cargo metadata"); + } + + let metadata: serde_json::Value = serde_json::from_slice(&output.stdout)?; + let target_directory = metadata + .get("target_directory") + .and_then(|v| v.as_str()) + .ok_or_else(|| anyhow::anyhow!("Could not get target directory from cargo metadata"))?; + + Ok(PathBuf::from(target_directory)) +} + +/// Read UUID from a file (e.g., uuid.txt) +pub fn read_uuid_from_file(uuid_path: &std::path::Path) -> Result { + if !uuid_path.exists() { + bail!("UUID file not found: {}", uuid_path.display()); + } + + let uuid_content = fs::read_to_string(uuid_path)?; + let uuid = uuid_content.trim().to_string(); + + if uuid.is_empty() { + bail!("UUID file is empty: {}", uuid_path.display()); + } + + Ok(uuid) +} + +/// Join path segments and check if the resulting path exists +pub fn join_and_check>( + base: &std::path::Path, + segments: &[P], + error_context: &str, +) -> Result { + let mut path = base.to_path_buf(); + for segment in segments { + path = path.join(segment); + } + + if !path.exists() { + bail!("{} does not exist: {:?}", error_context, path); + } + + Ok(path) +} + +/// Join path segments with a formatted final segment and check if the resulting path exists +pub fn join_format_and_check>( + base: &std::path::Path, + segments: &[P], + formatted_segment: &str, + error_context: &str, +) -> Result { + let mut path = base.to_path_buf(); + for segment in segments { + path = path.join(segment); + } + + let final_path = path.join(formatted_segment); + + if !final_path.exists() { + bail!("{} does not exist: {:?}", error_context, final_path); + } + + Ok(final_path) +} + +/// Clean build artifacts for any OP-TEE component (TA, CA, Plugin) +pub fn clean_project(project_path: &std::path::Path) -> Result<()> { + println!("Cleaning build artifacts in: {:?}", project_path); + + let output = Command::new("cargo") + .arg("clean") + .current_dir(project_path) + .output()?; + + if !output.status.success() { + print_output_and_bail("cargo clean", &output)?; + } + + // Also clean the intermediate cargo-optee directory if it exists + let intermediate_dir = project_path.join("target").join("cargo-optee"); + if intermediate_dir.exists() { + fs::remove_dir_all(&intermediate_dir)?; + println!("Removed intermediate directory: {:?}", intermediate_dir); + } + + println!("Build artifacts cleaned successfully"); + Ok(()) +} + +/// Get the package name from Cargo.toml in the current directory +pub fn get_package_name() -> Result { + // We assume we're already in the project directory (via ChangeDirectoryGuard) + let manifest_path = PathBuf::from("Cargo.toml"); + if !manifest_path.exists() { + bail!("Cargo.toml not found in current directory"); + } + + let cargo_toml_content = fs::read_to_string(&manifest_path)?; + let cargo_toml: Value = toml::from_str(&cargo_toml_content)?; + + let package_name = cargo_toml + .get("package") + .and_then(|p| p.get("name")) + .and_then(|n| n.as_str()) + .ok_or_else(|| anyhow::anyhow!("Could not find package name in Cargo.toml"))?; + + Ok(package_name.to_string()) +} diff --git a/cargo-optee/src/config.rs b/cargo-optee/src/config.rs new file mode 100644 index 00000000..c776a9f7 --- /dev/null +++ b/cargo-optee/src/config.rs @@ -0,0 +1,617 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use anyhow::{bail, Result}; +use cargo_metadata::MetadataCommand; +use serde_json::Value; +use std::path::{Path, PathBuf}; + +use crate::common::Arch; + +/// Component type for OP-TEE builds +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ComponentType { + /// Trusted Application (TA) + Ta, + /// Client Application (CA) + Ca, + /// Plugin (Shared Library) + Plugin, +} + +impl ComponentType { + /// Convert to string for metadata lookup in Cargo.toml + pub fn as_str(&self) -> &'static str { + match self { + ComponentType::Ta => "ta", + ComponentType::Ca => "ca", + ComponentType::Plugin => "plugin", + } + } +} + +/// Path type for validation +enum PathType { + /// Expects a directory + Directory, + /// Expects a file + File, +} + +#[derive(Clone)] +pub struct TaBuildConfig { + pub arch: Arch, // Architecture + pub debug: bool, // Debug mode (default false = release) + pub path: PathBuf, // Path to TA directory + pub uuid_path: Option, // Path to UUID file + // Customized variables + pub env: Vec<(String, String)>, // Custom environment variables for cargo build + pub no_default_features: bool, // Disable default features + pub features: Option, // Additional features to enable + // ta specific variables + pub std: bool, // Enable std feature + pub ta_dev_kit_dir: PathBuf, // Path to TA dev kit + pub signing_key: PathBuf, // Path to signing key +} + +impl TaBuildConfig { + pub fn resolve( + project_path: &Path, + cmd_arch: Option, + cmd_debug: Option, + cmd_uuid_path: Option, + common_env: Vec<(String, String)>, + common_no_default_features: bool, + common_features: Option, + cmd_std: Option, + cmd_ta_dev_kit_dir: Option, + cmd_signing_key: Option, + ) -> Result { + // Get base configuration from metadata + let metadata_config = MetadataConfig::resolve(project_path, ComponentType::Ta, cmd_arch)?; + + // Determine final arch: CLI > metadata > default + let arch = cmd_arch + .or_else(|| metadata_config.as_ref().map(|c| c.arch)) + .unwrap_or(Arch::Aarch64); + + // Handle priority: CLI > metadata > default + let debug = cmd_debug + .or_else(|| metadata_config.as_ref().map(|c| c.debug)) + .unwrap_or(false); + + let std = cmd_std + .or_else(|| metadata_config.as_ref().map(|c| c.std)) + .unwrap_or(false); + + // Handle ta_dev_kit_dir: CLI > metadata > error (required) + let ta_dev_kit_dir_config = cmd_ta_dev_kit_dir + .or_else(|| { + metadata_config + .as_ref() + .and_then(|c| c.ta_dev_kit_dir.clone()) + }) + .ok_or_else(|| ta_dev_kit_dir_error())?; + + // Resolve ta_dev_kit_dir path (relative to absolute) + let ta_dev_kit_dir = resolve_path_relative_to_project( + &ta_dev_kit_dir_config, + project_path, + PathType::Directory, + "TA development kit directory", + )?; + + // Handle signing_key: CLI > metadata > default (ta_dev_kit_dir/keys/default_ta.pem) + let signing_key_config = cmd_signing_key + .or_else(|| metadata_config.as_ref().and_then(|c| c.signing_key.clone())) + .unwrap_or_else(|| ta_dev_kit_dir_config.join("keys").join("default_ta.pem")); + + // Resolve signing_key path (relative to absolute) + let signing_key = resolve_path_relative_to_project( + &signing_key_config, + project_path, + PathType::File, + "Signing key file", + )?; + + // Handle uuid_path: CLI > metadata > default (../uuid.txt) + let uuid_path = resolve_uuid_path( + cmd_uuid_path, + metadata_config.as_ref().and_then(|c| c.uuid_path.clone()), + project_path, + PathBuf::from("../uuid.txt"), + )?; + + // Merge environment variables: metadata env + CLI env (CLI overrides metadata) + let mut env = metadata_config + .as_ref() + .map(|c| c.env.clone()) + .unwrap_or_default(); + env.extend(common_env); + + Ok(TaBuildConfig { + arch, + debug, + std, + ta_dev_kit_dir, + signing_key, + path: project_path.to_path_buf(), + uuid_path: Some(uuid_path), + env, + no_default_features: common_no_default_features, + features: common_features, + }) + } + + /// Print the final TA configuration parameters being used + pub fn print_config(&self) { + println!("Building TA with:"); + println!(" Arch: {:?}", self.arch); + println!(" Debug: {}", self.debug); + println!(" Std: {}", self.std); + println!(" TA dev kit dir: {:?}", self.ta_dev_kit_dir); + println!(" Signing key: {:?}", self.signing_key); + if let Some(ref uuid_path) = self.uuid_path { + let absolute_uuid_path = uuid_path + .canonicalize() + .unwrap_or_else(|_| uuid_path.clone()); + println!(" UUID path: {:?}", absolute_uuid_path); + } + if !self.env.is_empty() { + println!(" Environment variables: {} set", self.env.len()); + } + } +} + +#[derive(Clone)] +pub struct CaBuildConfig { + pub arch: Arch, // Architecture + pub debug: bool, // Debug mode (default false = release) + pub path: PathBuf, // Path to CA directory + pub uuid_path: Option, // Path to UUID file (for plugins) + // Customized variables + pub env: Vec<(String, String)>, // Custom environment variables for cargo build + pub no_default_features: bool, // Disable default features + pub features: Option, // Additional features to enable + // ca specific variables + pub optee_client_export: PathBuf, // Path to OP-TEE client export + pub plugin: bool, // Build as plugin (shared library) +} + +impl CaBuildConfig { + pub fn resolve( + project_path: &Path, + cmd_arch: Option, + cmd_debug: Option, + cmd_uuid_path: Option, + common_env: Vec<(String, String)>, + common_no_default_features: bool, + common_features: Option, + cmd_optee_client_export: Option, + plugin: bool, + ) -> Result { + let component_type = if plugin { + ComponentType::Plugin + } else { + ComponentType::Ca + }; + + // Get base configuration from metadata + let metadata_config = MetadataConfig::resolve(project_path, component_type, cmd_arch)?; + + // Determine final arch: CLI > metadata > default + let arch = cmd_arch + .or_else(|| metadata_config.as_ref().map(|c| c.arch)) + .unwrap_or(Arch::Aarch64); + + // Handle priority: CLI > metadata > default + let debug = cmd_debug + .or_else(|| metadata_config.as_ref().map(|c| c.debug)) + .unwrap_or(false); + + // Handle optee_client_export: CLI > metadata > error (required) + let optee_client_export_config = cmd_optee_client_export + .or_else(|| { + metadata_config + .as_ref() + .and_then(|c| c.optee_client_export.clone()) + }) + .ok_or_else(|| optee_client_export_error())?; + + // Resolve optee_client_export path (relative to absolute) + let optee_client_export = resolve_path_relative_to_project( + &optee_client_export_config, + project_path, + PathType::Directory, + "OP-TEE client export directory", + )?; + + // Handle uuid_path: only for plugins, CLI > metadata > default + let uuid_path = if plugin { + Some(resolve_uuid_path( + cmd_uuid_path, + metadata_config.as_ref().and_then(|c| c.uuid_path.clone()), + project_path, + PathBuf::from("../uuid.txt"), + )?) + } else { + None + }; + + // Merge environment variables: metadata env + CLI env (CLI overrides metadata) + let mut env = metadata_config + .as_ref() + .map(|c| c.env.clone()) + .unwrap_or_default(); + env.extend(common_env); + + Ok(CaBuildConfig { + arch, + debug, + path: project_path.to_path_buf(), + uuid_path, + env, + no_default_features: common_no_default_features, + features: common_features, + optee_client_export, + plugin, + }) + } + + /// Print the final CA/Plugin configuration parameters being used + pub fn print_config(&self) { + let component_name = if self.plugin { "Plugin" } else { "CA" }; + println!("Building {} with:", component_name); + println!(" Arch: {:?}", self.arch); + println!(" Debug: {}", self.debug); + println!(" OP-TEE client export: {:?}", self.optee_client_export); + if self.plugin { + if let Some(ref uuid_path) = self.uuid_path { + let absolute_uuid_path = uuid_path + .canonicalize() + .unwrap_or_else(|_| uuid_path.clone()); + println!(" UUID path: {:?}", absolute_uuid_path); + } + } + if !self.env.is_empty() { + println!(" Environment variables: {} set", self.env.len()); + } + } +} + +/// Build configuration parsed from Cargo.toml metadata only +/// This struct is used internally for metadata parsing and does not handle priority resolution +#[derive(Debug, Clone)] +struct MetadataConfig { + pub arch: Arch, + pub debug: bool, + pub std: bool, + pub ta_dev_kit_dir: Option, + pub optee_client_export: Option, + pub signing_key: Option, + pub uuid_path: Option, + /// additional environment key-value pairs, that should be passed to underlying + /// build commands + pub env: Vec<(String, String)>, +} + +impl MetadataConfig { + /// Extract build configuration from metadata only + /// This function only parses metadata and does not handle priority resolution + /// Determines arch with priority: cmd_arch > metadata > default + /// Returns None if metadata is not found or parsing fails + pub fn resolve( + project_path: &Path, + component_type: ComponentType, + cmd_arch: Option, + ) -> Result> { + // Try to find application metadata (optional) + let app_metadata = match discover_app_metadata(project_path) { + Ok(meta) => meta, + Err(_) => return Ok(None), + }; + + // Determine architecture with priority: cmd_arch > metadata > default + let arch = cmd_arch + .or_else(|| { + app_metadata + .get("optee")? + .get(component_type.as_str())? + .get("arch") + .and_then(|v| v.as_str()) + .and_then(|s| s.parse().ok()) + }) + .unwrap_or(Arch::Aarch64); + + // Extract metadata config with the determined architecture + // Return None if metadata parsing fails (metadata not found or invalid) + extract_build_config_with_arch(&app_metadata, arch, component_type) + .map(Some) + .or_else(|_| Ok(None)) + } +} + +/// Discover application metadata from the current project +fn discover_app_metadata(project_path: &Path) -> Result { + let cargo_toml_path = project_path.join("Cargo.toml"); + if !cargo_toml_path.exists() { + bail!( + "Cargo.toml not found in project directory: {:?}", + project_path + ); + } + + // Get metadata for the current project + let metadata = MetadataCommand::new() + .manifest_path(&cargo_toml_path) + .no_deps() + .exec()?; + + // Find the current project package + // First try to get root package (for non-workspace projects) + let current_package = if let Some(root_pkg) = metadata.root_package() { + root_pkg + } else { + // For workspace projects, find the package that corresponds to this manifest + let cargo_toml_path_str = cargo_toml_path.to_string_lossy(); + metadata + .packages + .iter() + .find(|pkg| { + pkg.manifest_path + .to_string() + .contains(&*cargo_toml_path_str) + }) + .ok_or_else(|| { + anyhow::anyhow!( + "Could not find package for manifest: {}", + cargo_toml_path_str + ) + })? + }; + + Ok(current_package.metadata.clone()) +} + +/// Extract build configuration from application package metadata with specific architecture +fn extract_build_config_with_arch( + metadata: &Value, + arch: Arch, + component_type: ComponentType, +) -> Result { + let optee_metadata = metadata + .get("optee") + .ok_or_else(|| anyhow::anyhow!("No optee metadata found in application package"))?; + + let component_metadata = optee_metadata.get(component_type.as_str()).ok_or_else(|| { + anyhow::anyhow!( + "No {} metadata found in optee section", + component_type.as_str() + ) + })?; + + // Parse debug with fallback to false + let debug = component_metadata + .get("debug") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + // Parse std with fallback to false + let std = component_metadata + .get("std") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + // Architecture-specific path resolution + let arch_key = match arch { + Arch::Aarch64 => "aarch64", + Arch::Arm => "arm", + }; + + // Parse architecture-specific ta_dev_kit_dir (for TA only) + let ta_dev_kit_dir = if component_type == ComponentType::Ta { + component_metadata + .get("ta-dev-kit-dir") + .and_then(|v| { + // Try architecture-specific first + if let Some(arch_value) = v.get(arch_key) { + // Only accept string values, no null support + arch_value.as_str() + } else { + // Architecture key missing, try fallback to non-specific + if v.is_string() { + v.as_str() + } else { + None + } + } + }) + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + } else { + None + }; + + // Parse architecture-specific optee_client_export (for CA and Plugin) + let optee_client_export = + if component_type == ComponentType::Ca || component_type == ComponentType::Plugin { + component_metadata + .get("optee-client-export") + .and_then(|v| { + // Try architecture-specific first + if let Some(arch_value) = v.get(arch_key) { + // Only accept string values, no null support + arch_value.as_str() + } else { + // Architecture key missing, try fallback to non-specific + if v.is_string() { + v.as_str() + } else { + None + } + } + }) + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + } else { + None + }; + + // Parse signing key (for TA only) + let signing_key = if component_type == ComponentType::Ta { + component_metadata + .get("signing-key") + .and_then(|v| v.as_str()) + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + } else { + None + }; + + // Parse environment variables + let env: Vec<(String, String)> = component_metadata + .get("env") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str()) + .filter_map(|s| { + if let Some(eq_pos) = s.find('=') { + let (key, value) = s.split_at(eq_pos); + let value = &value[1..]; // Skip the '=' character + Some((key.to_string(), value.to_string())) + } else { + eprintln!("Warning: Invalid environment variable format in metadata: '{}'. Expected 'KEY=VALUE'", s); + None + } + }) + .collect() + }) + .unwrap_or_default(); + + // Parse uuid_path from metadata (for TA and Plugin) + // component_metadata already points to the correct section (optee.ta or optee.plugin) + let uuid_path = + if component_type == ComponentType::Ta || component_type == ComponentType::Plugin { + component_metadata + .get("uuid-path") + .and_then(|v| v.as_str()) + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + } else { + None // CA doesn't need uuid_path + }; + + Ok(MetadataConfig { + arch, + debug, + std, + ta_dev_kit_dir, + optee_client_export, + signing_key, + uuid_path, + env, + }) +} + +/// Resolve uuid_path with priority: CLI > metadata > default +/// Returns the resolved absolute path +fn resolve_uuid_path( + cmd_uuid_path: Option, + metadata_uuid_path: Option, + project_path: &Path, + default: PathBuf, +) -> Result { + let uuid_path_was_from_cli = cmd_uuid_path.is_some(); + let uuid_path_str = cmd_uuid_path.or(metadata_uuid_path).unwrap_or(default); + + if uuid_path_was_from_cli { + // CLI provided - resolve relative to current directory + Ok(std::env::current_dir()?.join(&uuid_path_str)) + } else { + // From metadata or default - resolve relative to project directory + Ok(project_path.join(&uuid_path_str)) + } +} + +/// Generate error message for missing ta-dev-kit-dir configuration +fn ta_dev_kit_dir_error() -> anyhow::Error { + anyhow::anyhow!( + "ta-dev-kit-dir is MANDATORY but not configured.\n\ + Please set it via:\n\ + 1. Command line: --ta-dev-kit-dir \n\ + 2. Cargo.toml metadata: [package.metadata.optee.ta] section\n\ + \n\ + Example Cargo.toml:\n\ + [package.metadata.optee.ta]\n\ + ta-dev-kit-dir = {{ aarch64 = \"/path/to/optee_os/out/arm-plat-vexpress/export-ta_arm64\" }}\n\ + \n\ + For help with available options, run: cargo-optee build ta --help" + ) +} + +/// Generate error message for missing optee-client-export configuration +fn optee_client_export_error() -> anyhow::Error { + anyhow::anyhow!( + "optee-client-export is MANDATORY but not configured.\n\ + Please set it via:\n\ + 1. Command line: --optee-client-export \n\ + 2. Cargo.toml metadata: [package.metadata.optee.ca] or [package.metadata.optee.plugin] section\n\ + \n\ + Example Cargo.toml:\n\ + [package.metadata.optee.ca]\n\ + optee-client-export = {{ aarch64 = \"/path/to/optee_client/export_arm64\" }}\n\ + \n\ + For help with available options, run: cargo-optee build ca --help" + ) +} + +/// Resolve a potentially relative path to an absolute path based on the project directory +/// and validate that it exists +fn resolve_path_relative_to_project( + path: &PathBuf, + project_path: &std::path::Path, + path_type: PathType, + error_context: &str, +) -> anyhow::Result { + let resolved_path = if path.is_absolute() { + path.clone() + } else { + project_path.join(path) + }; + + // Validate that the path exists + if !resolved_path.exists() { + bail!("{} does not exist: {:?}", error_context, resolved_path); + } + + // Additional validation: check if it's actually a directory or file as expected + match path_type { + PathType::Directory => { + if !resolved_path.is_dir() { + bail!("{} is not a directory: {:?}", error_context, resolved_path); + } + } + PathType::File => { + if !resolved_path.is_file() { + bail!("{} is not a file: {:?}", error_context, resolved_path); + } + } + } + + Ok(resolved_path) +} diff --git a/cargo-optee/src/main.rs b/cargo-optee/src/main.rs new file mode 100644 index 00000000..9992d99f --- /dev/null +++ b/cargo-optee/src/main.rs @@ -0,0 +1,280 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::Parser; +use std::env; +use std::path::PathBuf; +use std::process; + +mod ca_builder; +mod cli; +mod common; +mod config; +mod ta_builder; + +use cli::{BuildCommand, Cli, Command, CommonBuildArgs, InstallCommand}; + +fn main() { + // Setup cargo environment + if let Err(e) = setup_cargo_environment() { + eprintln!("Error: {}", e); + process::exit(1); + } + + // Drop extra `optee` argument provided by `cargo`. + let mut found_optee = false; + let filtered_args: Vec = env::args() + .filter(|x| { + if found_optee { + true + } else { + found_optee = x == "optee"; + x != "optee" + } + }) + .collect(); + + let cli = Cli::parse_from(filtered_args); + let result = execute_command(cli.cmd); + + if let Err(e) = result { + eprintln!("Error: {}", e); + process::exit(1); + } +} + +fn execute_command(cmd: Command) -> anyhow::Result<()> { + match cmd { + Command::Build(build_cmd) => match build_cmd { + BuildCommand::TA { build_cmd } => { + // Convert bool flags to Option: --std -> Some(true), --no-std -> Some(false), neither -> None + let std_mode = match (build_cmd.std, build_cmd.no_std) { + (true, false) => Some(true), + (false, true) => Some(false), + _ => None, + }; + + execute_ta_command( + build_cmd.common, + std_mode, + build_cmd.ta_dev_kit_dir, + build_cmd.signing_key, + build_cmd.uuid_path, + None, + ) + } + BuildCommand::CA { build_cmd } => execute_ca_command( + build_cmd.common, + build_cmd.optee_client_export, + None, + false, + None, + ), + BuildCommand::Plugin { build_cmd } => execute_ca_command( + build_cmd.common, + build_cmd.optee_client_export, + build_cmd.uuid_path, + true, + None, + ), + }, + Command::Install(install_cmd) => match install_cmd { + InstallCommand::TA { + target_dir, + build_cmd, + } => { + // Convert bool flags to Option: --std -> Some(true), --no-std -> Some(false), neither -> None + let std_mode = match (build_cmd.std, build_cmd.no_std) { + (true, false) => Some(true), + (false, true) => Some(false), + _ => None, + }; + + execute_ta_command( + build_cmd.common, + std_mode, + build_cmd.ta_dev_kit_dir, + build_cmd.signing_key, + build_cmd.uuid_path, + Some(&target_dir), + ) + } + InstallCommand::CA { + target_dir, + build_cmd, + } => execute_ca_command( + build_cmd.common, + build_cmd.optee_client_export, + None, + false, + Some(&target_dir), + ), + InstallCommand::Plugin { + target_dir, + build_cmd, + } => execute_ca_command( + build_cmd.common, + build_cmd.optee_client_export, + build_cmd.uuid_path, + true, + Some(&target_dir), + ), + }, + Command::Clean { clean_cmd } => { + let project_path = resolve_project_path(clean_cmd.manifest_path.as_ref())?; + + // Clean build artifacts using the common function + crate::common::clean_project(&project_path) + } + } +} + +/// Execute TA build or install (shared logic) +fn execute_ta_command( + common: CommonBuildArgs, + std: Option, + ta_dev_kit_dir: Option, + signing_key: Option, + uuid_path: Option, + install_target_dir: Option<&PathBuf>, +) -> anyhow::Result<()> { + // Resolve project path from manifest or current directory + let project_path = resolve_project_path(common.manifest_path.as_ref())?; + + // Resolve TA build configuration with priority: CLI > metadata > default + let ta_config = config::TaBuildConfig::resolve( + &project_path, + common.arch, + Some(common.debug), + uuid_path, + common.env, + common.no_default_features, + common.features, + std, // None means read from config, Some(true/false) means CLI override + ta_dev_kit_dir, + signing_key, + )?; + + // Print the final configuration being used + ta_config.print_config(); + + ta_builder::build_ta(ta_config, install_target_dir.map(|p| p.as_path())) +} + +/// Execute CA build or install (shared logic) +fn execute_ca_command( + common: CommonBuildArgs, + optee_client_export: Option, + uuid_path: Option, + plugin: bool, + install_target_dir: Option<&PathBuf>, +) -> anyhow::Result<()> { + // Resolve project path from manifest or current directory + let project_path = resolve_project_path(common.manifest_path.as_ref())?; + + // Resolve CA build configuration with priority: CLI > metadata > default + let ca_config = config::CaBuildConfig::resolve( + &project_path, + common.arch, + Some(common.debug), + uuid_path, + common.env, + common.no_default_features, + common.features, + optee_client_export, + plugin, + )?; + + // Print the final configuration being used + ca_config.print_config(); + + ca_builder::build_ca(ca_config, install_target_dir.map(|p| p.as_path())) +} + +/// Resolve project path from manifest path or current directory +fn resolve_project_path(manifest_path: Option<&PathBuf>) -> anyhow::Result { + if let Some(manifest) = manifest_path { + let parent = manifest + .parent() + .ok_or_else(|| anyhow::anyhow!("Invalid manifest path"))?; + + // Normalize: if parent is empty (e.g., manifest is just "Cargo.toml"), + // use current directory instead + if parent.as_os_str().is_empty() { + std::env::current_dir().map_err(Into::into) + } else { + // Canonicalize must succeed, otherwise treat as invalid manifest path + parent + .canonicalize() + .map_err(|_| anyhow::anyhow!("Invalid manifest path")) + } + } else { + std::env::current_dir().map_err(Into::into) + } +} + +/// Setup cargo environment by checking availability and adding to PATH if needed +fn setup_cargo_environment() -> anyhow::Result<()> { + // Check if cargo is already available in PATH + let cargo_available = std::process::Command::new("which") + .arg("cargo") + .output() + .is_ok_and(|output| output.status.success()); + + if cargo_available { + return Ok(()); + } + + // Check if ~/.cargo/bin/cargo exists + let cargo_bin_dir = if let Ok(home) = env::var("HOME") { + let cargo_path = std::path::Path::new(&home) + .join(".cargo") + .join("bin") + .join("cargo"); + if cargo_path.exists() { + cargo_path.parent().map(|p| p.to_path_buf()) + } else { + None + } + } else { + None + }; + + // Or check $CARGO_HOME/bin/cargo + let cargo_bin_dir = cargo_bin_dir.or_else(|| { + env::var("CARGO_HOME").ok().and_then(|cargo_home| { + let cargo_path = std::path::Path::new(&cargo_home).join("bin").join("cargo"); + if cargo_path.exists() { + cargo_path.parent().map(|p| p.to_path_buf()) + } else { + None + } + }) + }); + + // If found, add cargo bin directory to current process PATH + if let Some(cargo_bin_dir) = cargo_bin_dir { + let current_path = env::var("PATH").unwrap_or_default(); + let separator = if cfg!(windows) { ";" } else { ":" }; + let new_path = format!("{}{}{}", cargo_bin_dir.display(), separator, current_path); + env::set_var("PATH", &new_path); + return Ok(()); + } + + // If not found, prompt user to install Cargo + anyhow::bail!("cargo command not found. Please install Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"); +} diff --git a/cargo-optee/src/ta_builder.rs b/cargo-optee/src/ta_builder.rs new file mode 100644 index 00000000..805fd602 --- /dev/null +++ b/cargo-optee/src/ta_builder.rs @@ -0,0 +1,408 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use crate::common; +use anyhow::{bail, Result}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use tempfile::TempDir; + +use crate::common::{ + get_package_name, get_target_and_cross_compile, get_target_directory_from_metadata, + print_cargo_command, print_output_and_bail, read_uuid_from_file, BuildMode, + ChangeDirectoryGuard, +}; +use crate::config::TaBuildConfig; + +// Embed the target JSON files at compile time +const AARCH64_TARGET_JSON: &str = include_str!("../aarch64-unknown-optee.json"); +const ARM_TARGET_JSON: &str = include_str!("../arm-unknown-optee.json"); + +// Main function to build the TA, optionally installing to a target directory +pub fn build_ta(config: TaBuildConfig, install_dir: Option<&Path>) -> Result<()> { + // Verify we're in a valid Rust project directory + let manifest_path = config.path.join("Cargo.toml"); + if !manifest_path.exists() { + bail!( + "No Cargo.toml found in TA project directory: {:?}\n\ + Please run cargo-optee from a TA project directory or specify --manifest-path", + config.path + ); + } + + // Change to the TA directory (RAII guard ensures we return to original directory) + let _guard = ChangeDirectoryGuard::new(&config.path)?; + + // Check if required cross-compile toolchain is available + let build_mode = if config.std { + BuildMode::TaStd + } else { + BuildMode::TaNoStd + }; + let (_, cross_compile_prefix) = get_target_and_cross_compile(config.arch, build_mode)?; + check_toolchain_exists(&cross_compile_prefix)?; + + // Get the absolute path for better clarity + let absolute_path = std::fs::canonicalize(&config.path).unwrap_or_else(|_| config.path.clone()); + println!("Building TA in directory: {}", absolute_path.display()); + + // Step 1: Run clippy for code quality checks + run_clippy(&config)?; + + // Step 2: Build the TA + build_binary(&config)?; + + // Step 3: Strip the binary + let (stripped_path, target_dir) = strip_binary(&config)?; + + // Step 4: Sign the TA + sign_ta(&config, &stripped_path, &target_dir)?; + + // Step 5: Install if requested + if let Some(install_dir) = install_dir { + // Check if install directory exists + if !install_dir.exists() { + bail!("Install directory does not exist: {:?}", install_dir); + } + + let uuid_path = config + .uuid_path + .as_ref() + .ok_or_else(|| anyhow::anyhow!("UUID path is required but not configured"))?; + let uuid = read_uuid_from_file(uuid_path)?; + let ta_file = common::join_format_and_check::<&str>( + &target_dir, + &[], + &format!("{}.ta", uuid), + "Signed TA file", + )?; + + let dest_path = install_dir.join(format!("{}.ta", uuid)); + fs::copy(&ta_file, &dest_path)?; + + println!( + "TA installed to: {:?}", + dest_path.canonicalize().unwrap_or(dest_path) + ); + } + + println!("TA build successfully!"); + + Ok(()) +} + +fn run_clippy(config: &TaBuildConfig) -> Result<()> { + println!("Running cargo fmt and clippy..."); + + // Run cargo fmt (we're already in the project directory via ChangeDirectoryGuard) + let fmt_output = Command::new("cargo").arg("fmt").output()?; + + if !fmt_output.status.success() { + print_output_and_bail("cargo fmt", &fmt_output)?; + } + + // Setup clippy command with common environment + let (mut clippy_cmd, _temp_dir) = setup_build_command(config, "clippy")?; + + clippy_cmd.arg("--"); + clippy_cmd.arg("-D").arg("warnings"); + clippy_cmd.arg("-D").arg("clippy::unwrap_used"); + clippy_cmd.arg("-D").arg("clippy::expect_used"); + clippy_cmd.arg("-D").arg("clippy::panic"); + + let clippy_output = clippy_cmd.output()?; + + if !clippy_output.status.success() { + print_output_and_bail("clippy", &clippy_output)?; + } + + Ok(()) +} + +fn build_binary(config: &TaBuildConfig) -> Result<()> { + // Determine target and cross-compile based on arch and std mode + let build_mode = if config.std { + BuildMode::TaStd + } else { + BuildMode::TaNoStd + }; + let (target, cross_compile) = get_target_and_cross_compile(config.arch, build_mode)?; + + // Setup build command with common environment (we're already in the project directory) + let (mut build_cmd, _temp_dir) = setup_build_command(config, "build")?; + + if !config.debug { + build_cmd.arg("--release"); + } + + // Configure linker + let linker = format!("{}gcc", cross_compile); + let linker_cfg = format!("target.{}.linker=\"{}\"", target, linker); + build_cmd.arg("--config").arg(&linker_cfg); + + // Print the full cargo build command for debugging + print_cargo_command(&build_cmd, "Building TA binary"); + + let build_output = build_cmd.output()?; + + if !build_output.status.success() { + print_output_and_bail("build", &build_output)?; + } + + Ok(()) +} + +fn strip_binary(config: &TaBuildConfig) -> Result<(PathBuf, PathBuf)> { + println!("Stripping binary..."); + + // Determine target based on arch and std mode + let build_mode = if config.std { + BuildMode::TaStd + } else { + BuildMode::TaNoStd + }; + let (target, cross_compile) = get_target_and_cross_compile(config.arch, build_mode)?; + + let profile = if config.debug { "debug" } else { "release" }; + + // Use cargo metadata to get the target directory (supports workspace and CARGO_TARGET_DIR) + let target_directory = get_target_directory_from_metadata()?; + let profile_dir = target_directory.join(target).join(profile); + + // Get the actual package name from Cargo.toml (we're already in the project directory) + let package_name = get_package_name()?; + + let binary_path = common::join_and_check(&profile_dir, &[&package_name], "Binary")?; + + let stripped_path = profile_dir.join(format!("stripped_{}", package_name)); + + let objcopy = format!("{}objcopy", cross_compile); + + let strip_output = Command::new(&objcopy) + .arg("--strip-unneeded") + .arg(&binary_path) + .arg(&stripped_path) + .output()?; + + if !strip_output.status.success() { + print_output_and_bail(&objcopy, &strip_output)?; + } + + Ok((stripped_path, profile_dir)) +} + +fn sign_ta(config: &TaBuildConfig, stripped_path: &Path, target_dir: &Path) -> Result<()> { + println!("Signing TA with signing key {:?}...", config.signing_key); + + // Read UUID from specified file + let uuid_path = config + .uuid_path + .as_ref() + .ok_or_else(|| anyhow::anyhow!("UUID path is required but not configured"))?; + let uuid = read_uuid_from_file(uuid_path)?; + + // Validate signing key exists + if !config.signing_key.exists() { + bail!("Signing key not found at {:?}", config.signing_key); + } + + // Sign script path + let sign_script = common::join_and_check( + &config.ta_dev_kit_dir, + &["scripts", "sign_encrypt.py"], + "Sign script", + )?; + + // Output path - use the actual target_dir + let output_path = target_dir.join(format!("{}.ta", uuid)); + + let sign_output = Command::new("python3") + .arg(&sign_script) + .arg("--uuid") + .arg(&uuid) + .arg("--key") + .arg(&config.signing_key) + .arg("--in") + .arg(stripped_path) + .arg("--out") + .arg(&output_path) + .output()?; + + if !sign_output.status.success() { + print_output_and_bail("sign_encrypt.py", &sign_output)?; + } + + println!("SIGN => {}", uuid); + let absolute_output_path = output_path.canonicalize().unwrap_or(output_path); + println!("TA signed and saved to: {:?}", absolute_output_path); + + Ok(()) +} + +/// Check if the required cross-compile toolchain is available +fn check_toolchain_exists(cross_compile_prefix: &str) -> Result<()> { + let gcc_command = format!("{}gcc", cross_compile_prefix); + let objcopy_command = format!("{}objcopy", cross_compile_prefix); + + // Check if gcc exists + let gcc_check = Command::new("which").arg(&gcc_command).output(); + + // Check if objcopy exists + let objcopy_check = Command::new("which").arg(&objcopy_command).output(); + + let gcc_exists = gcc_check.map_or(false, |output| output.status.success()); + let objcopy_exists = objcopy_check.map_or(false, |output| output.status.success()); + + if !gcc_exists || !objcopy_exists { + let missing_tools: Vec<&str> = [ + if !gcc_exists { + Some(gcc_command.as_str()) + } else { + None + }, + if !objcopy_exists { + Some(objcopy_command.as_str()) + } else { + None + }, + ] + .iter() + .filter_map(|&x| x) + .collect(); + + eprintln!("Error: Required cross-compile toolchain not found!"); + eprintln!("Missing tools: {}", missing_tools.join(", ")); + eprintln!(); + eprintln!("Please install the required toolchain:"); + eprintln!(); + eprintln!("# For aarch64 host (ARM64 machine):"); + eprintln!("apt update && apt -y install gcc gcc-arm-linux-gnueabihf"); + eprintln!(); + eprintln!("# For x86_64 host (Intel/AMD machine):"); + eprintln!("apt update && apt -y install gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf"); + eprintln!(); + eprintln!("Or manually install the cross-compilation tools for your target architecture."); + + bail!("Cross-compile toolchain not available"); + } + + Ok(()) +} + +// Helper function to setup base command with common environment variables +fn setup_build_command( + config: &TaBuildConfig, + command: &str, +) -> Result<(Command, Option)> { + // Determine target and cross-compile based on arch and std mode + let build_mode = if config.std { + BuildMode::TaStd + } else { + BuildMode::TaNoStd + }; + let (target, _cross_compile) = get_target_and_cross_compile(config.arch, build_mode)?; + + // Determine builder (cargo or xargo) + let builder = if config.std { "xargo" } else { "cargo" }; + + // Setup custom targets if using std - keep TempDir alive + let temp_dir = if config.std { + Some(setup_custom_targets()?) + } else { + None + }; + + let mut cmd = Command::new(builder); + cmd.arg(command); + cmd.arg("--target").arg(&target); + + // Add --no-default-features if specified + if config.no_default_features { + cmd.arg("--no-default-features"); + } + + // Build features list + let mut features = Vec::new(); + if config.std { + features.push("std".to_string()); + } + if let Some(ref custom_features) = config.features { + // Split custom features by comma and add them + for feature in custom_features.split(',') { + let feature = feature.trim(); + if !feature.is_empty() { + features.push(feature.to_string()); + } + } + } + + // Add features if any are specified + if !features.is_empty() { + cmd.arg("--features").arg(features.join(",")); + } + + // Add no-std specific flags to avoid the linking error of _Unwind_Resume + if !config.std { + cmd.arg("-Z").arg("build-std=core,alloc"); + cmd.arg("-Z") + .arg("build-std-features=panic_immediate_abort"); + } + + // Set RUSTFLAGS - preserve existing ones and add panic=abort + let mut rustflags = env::var("RUSTFLAGS").unwrap_or_default(); + if !rustflags.is_empty() { + rustflags.push(' '); + } + rustflags.push_str("-C panic=abort"); + cmd.env("RUSTFLAGS", &rustflags); + + // Apply custom environment variables + for (key, value) in &config.env { + cmd.env(key, value); + } + + // Set TA_DEV_KIT_DIR environment variable (use absolute path) + let absolute_ta_dev_kit_dir = config + .ta_dev_kit_dir + .canonicalize() + .unwrap_or_else(|_| config.ta_dev_kit_dir.clone()); + cmd.env("TA_DEV_KIT_DIR", &absolute_ta_dev_kit_dir); + + // Set RUST_TARGET_PATH for custom targets when using std + if let Some(ref temp_dir_ref) = temp_dir { + cmd.env("RUST_TARGET_PATH", temp_dir_ref.path()); + } + + Ok((cmd, temp_dir)) +} + +// Helper function to setup custom target JSONs for std builds +// Returns TempDir to keep it alive during the build +fn setup_custom_targets() -> Result { + let temp_dir = TempDir::new()?; + + // Write the embedded target JSON files + let aarch64_path = temp_dir.path().join("aarch64-unknown-optee.json"); + let arm_path = temp_dir.path().join("arm-unknown-optee.json"); + + fs::write(aarch64_path, AARCH64_TARGET_JSON)?; + fs::write(arm_path, ARM_TARGET_JSON)?; + + Ok(temp_dir) +} diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 00000000..654b33fb --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,437 @@ +#!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -e + +# Show usage +show_usage() { + cat << EOF +Usage: TA_DEV_KIT_DIR= OPTEE_CLIENT_EXPORT= $0 [OPTIONS] + +Required environment variables: + TA_DEV_KIT_DIR Path to OP-TEE OS TA dev kit directory + OPTEE_CLIENT_EXPORT Path to OP-TEE client export directory + +Options: + --ta TA architecture: aarch64 or arm (default: aarch64) + --host Host architecture for CA and plugins: aarch64 or arm (default: aarch64) + --std Install with std support (default: no-std) + --ta-install-dir TA installation directory (default: ./tests/shared) + --ca-install-dir CA installation directory (default: ./tests/shared) + --plugin-install-dir Plugin installation directory (default: ./tests/shared) + --help Show this help message + +Examples: + # Install for aarch64 in no-std mode + TA_DEV_KIT_DIR=/path/to/export-ta_arm64 OPTEE_CLIENT_EXPORT=/path/to/export ./build.sh + + # Install for ARM32 in std mode with custom directories + TA_DEV_KIT_DIR=/path/to/export-ta_arm32 OPTEE_CLIENT_EXPORT=/path/to/export ./build.sh --ta arm --host arm --std --ta-install-dir /target/lib/optee_armtz --ca-install-dir /target/usr/bin + +Note: Binaries are installed to './tests/shared' directory by default. +EOF +} + +# Parse command line arguments +ARCH_TA="aarch64" # Default: aarch64 +ARCH_HOST="aarch64" # Default: aarch64 +STD="" # Default: empty (no-std) +TA_INSTALL_DIR="" # Default: will be set to ./tests/shared if not specified +CA_INSTALL_DIR="" # Default: will be set to ./tests/shared if not specified +PLUGIN_INSTALL_DIR="" # Default: will be set to ./tests/shared if not specified + +# Parse arguments (support both positional and flag-style) +while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + show_usage + exit 0 + ;; + --ta) + ARCH_TA="$2" + shift 2 + ;; + --host) + ARCH_HOST="$2" + shift 2 + ;; + --std) + STD="std" + shift + ;; + --ta-install-dir) + TA_INSTALL_DIR="$2" + shift 2 + ;; + --ca-install-dir) + CA_INSTALL_DIR="$2" + shift 2 + ;; + --plugin-install-dir) + PLUGIN_INSTALL_DIR="$2" + shift 2 + ;; + *) + # Positional arguments (backward compatibility) + if [[ -z "${ARCH_TA_SET:-}" ]]; then + ARCH_TA="$1" + ARCH_TA_SET=1 + elif [[ -z "${ARCH_HOST_SET:-}" ]]; then + ARCH_HOST="$1" + ARCH_HOST_SET=1 + elif [[ "$1" == "std" ]]; then + STD="std" + fi + shift + ;; + esac +done + +# Validate architecture +if [[ "$ARCH_TA" != "aarch64" && "$ARCH_TA" != "arm" ]]; then + echo "Error: ARCH_TA must be 'aarch64' or 'arm'" + exit 1 +fi + +if [[ "$ARCH_HOST" != "aarch64" && "$ARCH_HOST" != "arm" ]]; then + echo "Error: ARCH_HOST must be 'aarch64' or 'arm'" + exit 1 +fi + +# Check required environment variables +if [ -z "$TA_DEV_KIT_DIR" ]; then + echo "Error: TA_DEV_KIT_DIR environment variable is not set" + exit 1 +fi + +if [ -z "$OPTEE_CLIENT_EXPORT" ]; then + echo "Error: OPTEE_CLIENT_EXPORT environment variable is not set" + exit 1 +fi + +echo "===========================================" +echo "Installing with configuration:" +echo " ARCH_TA: $ARCH_TA" +echo " ARCH_HOST: $ARCH_HOST" +echo " STD: ${STD:-no-std}" +echo " TA_DEV_KIT_DIR: $TA_DEV_KIT_DIR" +echo " OPTEE_CLIENT_EXPORT: $OPTEE_CLIENT_EXPORT" +echo "===========================================" + +# Step 1: Build cargo-optee tool +echo "" +echo "Step 1: Building cargo-optee tool..." +cd cargo-optee +cargo build --release +CARGO_OPTEE="$(pwd)/target/release/cargo-optee" +cd .. + +if [ ! -f "$CARGO_OPTEE" ]; then + echo "Error: Failed to build cargo-optee" + exit 1 +fi + +echo "cargo-optee built successfully: $CARGO_OPTEE" + +# Prepare std flag for cargo-optee +STD_FLAG="" +if [ -n "$STD" ]; then + STD_FLAG="--std" +fi + +# Step 2: Install all examples to shared directory +echo "" +echo "Step 2: Installing all examples..." + +# Set up installation directories +# Each directory defaults to ./tests/shared if not specified +if [ -z "$TA_INSTALL_DIR" ]; then + TA_INSTALL_DIR="$(pwd)/tests/shared" +fi + +if [ -z "$CA_INSTALL_DIR" ]; then + CA_INSTALL_DIR="$(pwd)/tests/shared" +fi + +if [ -z "$PLUGIN_INSTALL_DIR" ]; then + PLUGIN_INSTALL_DIR="$(pwd)/tests/shared" +fi + +# Create all directories +mkdir -p "$TA_INSTALL_DIR" +mkdir -p "$CA_INSTALL_DIR" +mkdir -p "$PLUGIN_INSTALL_DIR" + +echo "Installing binaries to:" +echo " TAs: $TA_INSTALL_DIR" +echo " CAs: $CA_INSTALL_DIR" +echo " Plugins: $PLUGIN_INSTALL_DIR" + +EXAMPLES_DIR="$(pwd)/examples" +METADATA_JSON="$EXAMPLES_DIR/metadata.json" + +if [ ! -f "$METADATA_JSON" ]; then + echo "Error: $METADATA_JSON not found" + exit 1 +fi + +# Check if jq is available for JSON parsing +if ! command -v jq &> /dev/null; then + echo "Error: jq is required to parse metadata.json" + echo "Please install jq: apt-get install jq" + exit 1 +fi + +echo "Loading example metadata from $METADATA_JSON..." + +# Get all example names +ALL_EXAMPLES=($(jq -r '.examples | keys[]' "$METADATA_JSON")) + +if [ -n "$STD" ]; then + echo "Building in STD mode (std-only + common examples)" +else + echo "Building in NO-STD mode (no-std-only + common examples)" +fi + +CURRENT=0 +FAILED_EXAMPLES="" + +# Build examples +for EXAMPLE_NAME in "${ALL_EXAMPLES[@]}"; do + CATEGORY=$(jq -r ".examples[\"$EXAMPLE_NAME\"].category" "$METADATA_JSON") + + # Determine if we should build this example + SHOULD_BUILD=false + if [ -n "$STD" ]; then + # STD mode: build std-only and common + if [[ "$CATEGORY" == "std-only" || "$CATEGORY" == "common" ]]; then + SHOULD_BUILD=true + fi + else + # NO-STD mode: build no-std-only and common + if [[ "$CATEGORY" == "no-std-only" || "$CATEGORY" == "common" ]]; then + SHOULD_BUILD=true + fi + fi + + if [ "$SHOULD_BUILD" = false ]; then + continue + fi + + CURRENT=$((CURRENT + 1)) + EXAMPLE_DIR="$EXAMPLES_DIR/$EXAMPLE_NAME" + + if [ ! -d "$EXAMPLE_DIR" ]; then + echo "ERROR: Example directory not found: $EXAMPLE_DIR" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME" + continue + fi + + echo "" + echo "==========================================" + echo "[$CURRENT] Building: $EXAMPLE_NAME ($CATEGORY)" + echo "==========================================" + + # Get TA, CA, and Plugin directories from metadata + TAS_JSON=$(jq -c ".examples[\"$EXAMPLE_NAME\"].tas" "$METADATA_JSON") + CAS_JSON=$(jq -c ".examples[\"$EXAMPLE_NAME\"].cas" "$METADATA_JSON") + PLUGINS_JSON=$(jq -c ".examples[\"$EXAMPLE_NAME\"].plugins // []" "$METADATA_JSON") + + # Build all TAs for this example + TA_COUNT=$(echo "$TAS_JSON" | jq 'length') + CA_COUNT=$(echo "$CAS_JSON" | jq 'length') + PLUGIN_COUNT=$(echo "$PLUGINS_JSON" | jq 'length') + + echo "→ Found $TA_COUNT TA(s), $CA_COUNT CA(s), and $PLUGIN_COUNT Plugin(s)" + + if [ "$TA_COUNT" -gt 0 ]; then + for ((i=0; i<$TA_COUNT; i++)); do + TA_DIR=$(echo "$TAS_JSON" | jq -r ".[$i]") + TA_DIR_FULL_PATH="$EXAMPLES_DIR/$TA_DIR" + + if [ ! -d "$TA_DIR_FULL_PATH" ]; then + echo "ERROR: TA directory not found: $TA_DIR_FULL_PATH" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($TA_DIR)" + continue + fi + + if [ ! -f "$TA_DIR_FULL_PATH/Cargo.toml" ]; then + echo "ERROR: Cargo.toml not found in TA directory: $TA_DIR_FULL_PATH" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($TA_DIR)" + continue + fi + + echo "" + echo "→ Building TA [$((i+1))/$TA_COUNT]: $TA_DIR" + + # Determine STD_FLAG for TA + TA_STD_FLAG="" + if [ -n "$STD" ]; then + # In std mode: always pass --std flag to cargo-optee + TA_STD_FLAG="--std" + fi + + # Change to TA directory and run cargo-optee without --manifest-path + cd "$TA_DIR_FULL_PATH" + + # Run cargo-optee install and capture both stdout and stderr + if $CARGO_OPTEE install ta \ + --target-dir "$TA_INSTALL_DIR" \ + --ta-dev-kit-dir "$TA_DEV_KIT_DIR" \ + --arch "$ARCH_TA" \ + $TA_STD_FLAG; then + echo " ✓ TA installed successfully" + # Clean up build artifacts + $CARGO_OPTEE clean + else + echo " ✗ ERROR: Failed to install TA: $TA_DIR" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($TA_DIR)" + cd "$EXAMPLES_DIR" # Return to examples directory + continue + fi + + # Return to examples directory + cd "$EXAMPLES_DIR" + done + else + echo "WARNING: No TAs defined for $EXAMPLE_NAME" + fi + + # Build each CA + CA_INDEX=0 + while [[ "$CA_INDEX" -lt "$CA_COUNT" ]]; do + CA_DIR=$(echo "$CAS_JSON" | jq -r ".[$CA_INDEX]") + CA_DIR_FULL_PATH="$EXAMPLES_DIR/$CA_DIR" + + echo "" + echo "→ Building CA [$((CA_INDEX+1))/$CA_COUNT]: $CA_DIR" + + if [ ! -d "$CA_DIR_FULL_PATH" ]; then + echo "ERROR: CA directory not found: $CA_DIR_FULL_PATH" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($CA_DIR)" + CA_INDEX=$((CA_INDEX + 1)) + continue + fi + + if [ ! -f "$CA_DIR_FULL_PATH/Cargo.toml" ]; then + echo "ERROR: Cargo.toml not found in CA directory: $CA_DIR_FULL_PATH" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($CA_DIR)" + CA_INDEX=$((CA_INDEX + 1)) + continue + fi + + # Change to CA directory and run cargo-optee without --manifest-path + cd "$CA_DIR_FULL_PATH" + + if $CARGO_OPTEE install ca \ + --target-dir "$CA_INSTALL_DIR" \ + --optee-client-export "$OPTEE_CLIENT_EXPORT" \ + --arch "$ARCH_HOST"; then + echo " ✓ CA installed successfully" + # Clean up build artifacts + $CARGO_OPTEE clean + else + echo " ✗ ERROR: Failed to install CA: $CA_DIR" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($CA_DIR)" + cd "$EXAMPLES_DIR" # Return to examples directory + CA_INDEX=$((CA_INDEX + 1)) + continue + fi + + # Return to examples directory + cd "$EXAMPLES_DIR" + CA_INDEX=$((CA_INDEX + 1)) + done + + # Build each Plugin + PLUGIN_INDEX=0 + while [[ "$PLUGIN_INDEX" -lt "$PLUGIN_COUNT" ]]; do + PLUGIN_DIR=$(echo "$PLUGINS_JSON" | jq -r ".[$PLUGIN_INDEX]") + PLUGIN_DIR_FULL_PATH="$EXAMPLES_DIR/$PLUGIN_DIR" + + echo "" + echo "→ Building Plugin [$((PLUGIN_INDEX+1))/$PLUGIN_COUNT]: $PLUGIN_DIR" + + if [ ! -d "$PLUGIN_DIR_FULL_PATH" ]; then + echo "ERROR: Plugin directory not found: $PLUGIN_DIR_FULL_PATH" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($PLUGIN_DIR)" + PLUGIN_INDEX=$((PLUGIN_INDEX + 1)) + continue + fi + + if [ ! -f "$PLUGIN_DIR_FULL_PATH/Cargo.toml" ]; then + echo "ERROR: Cargo.toml not found in Plugin directory: $PLUGIN_DIR_FULL_PATH" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($PLUGIN_DIR)" + PLUGIN_INDEX=$((PLUGIN_INDEX + 1)) + continue + fi + + # Change to Plugin directory and run cargo-optee without --manifest-path + cd "$PLUGIN_DIR_FULL_PATH" + + if $CARGO_OPTEE install plugin \ + --target-dir "$PLUGIN_INSTALL_DIR" \ + --optee-client-export "$OPTEE_CLIENT_EXPORT" \ + --arch "$ARCH_HOST"; then + echo " ✓ Plugin installed successfully" + # Clean up build artifacts + $CARGO_OPTEE clean + else + echo " ✗ ERROR: Failed to install Plugin: $PLUGIN_DIR" + FAILED_EXAMPLES="$FAILED_EXAMPLES\n - $EXAMPLE_NAME ($PLUGIN_DIR)" + cd "$EXAMPLES_DIR" # Return to examples directory + PLUGIN_INDEX=$((PLUGIN_INDEX + 1)) + continue + fi + + # Return to examples directory + cd "$EXAMPLES_DIR" + PLUGIN_INDEX=$((PLUGIN_INDEX + 1)) + done + + echo "" + echo "✓ Example $EXAMPLE_NAME completed successfully" +done + +# Summary +echo "" +echo "===========================================" +echo " INSTALL SUMMARY" +echo "===========================================" +echo "" +echo "Mode: ${STD:-no-std}" +echo "Architecture: TA=$ARCH_TA, CA=$ARCH_HOST" +echo "Examples: $CURRENT installed" +echo "TA install dir: $TA_INSTALL_DIR" +echo "CA install dir: $CA_INSTALL_DIR" +echo "Plugin install dir: $PLUGIN_INSTALL_DIR" +echo "" + +if [ -n "$FAILED_EXAMPLES" ]; then + echo "❌ INSTALL FAILED" + echo "" + echo "Failed components:" + echo -e "$FAILED_EXAMPLES" + echo "" + exit 1 +else + echo "✅ ALL EXAMPLES INSTALLED SUCCESSFULLY!" + echo "" +fi + diff --git a/docs/emulate-and-dev-in-docker-std.md b/docs/emulate-and-dev-in-docker-std.md index a89231c9..08e6972f 100644 --- a/docs/emulate-and-dev-in-docker-std.md +++ b/docs/emulate-and-dev-in-docker-std.md @@ -4,20 +4,27 @@ permalink: /trustzone-sdk-docs/emulate-and-dev-in-docker-std.md # 🚀 Developing TAs with Rust Standard Library in Docker -This guide covers the **dev-env with std support** that enables **developing TA using Rust standard library (std)**, -compared to the regular no-std environment documented in [emulate-and-dev-in-docker.md](emulate-and-dev-in-docker.md). +This guide covers the **dev-env with std support** that enables **developing TA +using Rust standard library (std)**, compared to the regular no-std environment +documented in [emulate-and-dev-in-docker.md](emulate-and-dev-in-docker.md). -The **dev-env with std support** provides a complete setup for building TAs that can use Rust's standard library features like collections, networking, etc. +The **dev-env with std support** provides a complete setup for building TAs that +can use Rust's standard library features like collections, networking, etc. -> 📖 **Prerequisites**: Read the [original Docker development guide](emulate-and-dev-in-docker.md) -> first. This document focuses only on std-specific differences and capabilities. +> 📖 **Prerequisites**: Read the [original Docker development +> guide](emulate-and-dev-in-docker.md) first. This document focuses only on +> std-specific differences and capabilities. ## What the Dev-Env with Std Support Provides -The **dev-env with std support** enables **developing TA using Rust std** by providing: -- **Flexible configuration management** - Switch between std/no-std modes and architectures dynamically -- **Rust standard library tailored for OP-TEE** - Build TAs using collections, networking, serialization capabilities -- **Mixed development support** - Combine different host and TA architectures, including switching between no-std/std in the same project +The **dev-env with std support** enables **developing TA using Rust std** by +providing: +- **Flexible configuration management** - Switch between std/no-std modes and + architectures dynamically +- **Rust standard library tailored for OP-TEE** - Build TAs using collections, + networking, serialization capabilities +- **Mixed development support** - Combine different host and TA architectures, + including switching between no-std/std in the same project ## 1. Setting Up the Dev-Env with Std Support @@ -40,13 +47,21 @@ $ docker run -it --rm \ $ ln -s $RUST_STD_DIR rust ``` -> 📝 **Note**: This symlink is required for current SDK examples due to hardcoded std dependency paths in Cargo.toml. Your own projects may organize std files differently. +> 📝 **Note**: This symlink is required for current SDK examples due to +> hardcoded std dependency paths in Cargo.toml. Your own projects may organize +> std files differently. ## 2. Configuration Management System -The key difference is the **unified configuration system** that allows switching between std/no-std modes and different architectures on demand. +The key difference is the **unified configuration system** that allows switching +between std/no-std modes and different architectures on demand. -### Check Available Configurations +And [cargo-optee](../cargo-optee/README.md#configuration-system) is available as +an alternative to the original configuration management tool: switch_config. + +### If you use switch_config: + +#### Check Available Configurations ```bash # Show current active configuration $ switch_config --status @@ -63,7 +78,7 @@ $ switch_config --list **Default Configuration:** Host=`aarch64`, TA=`std/aarch64` -### Switching Between Configurations +#### Switching Between Configurations ```bash # Switch TA configurations $ switch_config --ta std/aarch64 # Enable std for 64-bit TA @@ -77,9 +92,16 @@ $ switch_config --host arm32 # Use 32-bit host $ switch_config --host arm32 && switch_config --ta std/aarch64 ``` +### If you use cargo-optee: + +You can see the [cargo-optee configuration +system](../cargo-optee/README.md#configuration-system) for details. + ## 3. Building and Target Differences -Follow the [original building instructions](emulate-and-dev-in-docker.md#2-build-the-hello-world-example), but note these important target differences: +Follow the [original building +instructions](emulate-and-dev-in-docker.md#2-build-the-hello-world-example), but +note these important target differences: | Configuration | TA Target | Build Tool | Host Target | |---------------|-----------|------------|-------------| @@ -117,6 +139,13 @@ $ make clean && make TA=ta/target/aarch64-unknown-linux-gnu/release/133af0ca-bdab-11eb-9130-43bf7873bf67.ta ``` +If you are using cargo-optee, the relevant workflow is already clearly +documented in the [cargo-optee appendix](../cargo-optee/README.md#appendix), so +it will not be repeated here. + ## 5. Emulation and Execution -The emulation process is identical to the no-std environment. Follow [sections 3-6 of the original guide](emulate-and-dev-in-docker.md#3-make-the-artifacts-accessible-to-the-emulator) for complete emulation setup instructions. +The emulation process is identical to the no-std environment. Follow [sections +3-6 of the original +guide](emulate-and-dev-in-docker.md#3-make-the-artifacts-accessible-to-the-emulator) +for complete emulation setup instructions. \ No newline at end of file diff --git a/docs/emulate-and-dev-in-docker.md b/docs/emulate-and-dev-in-docker.md index b9497d53..839b0a92 100644 --- a/docs/emulate-and-dev-in-docker.md +++ b/docs/emulate-and-dev-in-docker.md @@ -7,8 +7,8 @@ permalink: /trustzone-sdk-docs/emulate-and-dev-in-docker.md This guide walks you through building and running QEMU emulation using the Teaclave TrustZone SDK. -We provide a Docker image with prebuilt QEMU and OP-TEE images to streamline -the entire Trusted Application (TA) development workflow. The image allows +We provide a Docker image with prebuilt QEMU and OP-TEE images to streamline the +entire Trusted Application (TA) development workflow. The image allows developers to build TAs and emulate a guest virtual machine (VM) that includes both the Normal World and Secure World environments. @@ -33,19 +33,30 @@ $ docker run -it --rm \ ## 2. Build the Hello World Example +Update: cargo-optee is available as an alternative to the original Makefile +system. See the [cargo-optee documentation](../cargo-optee/README.md) for +details. + +### If you use Makefile: + **Still in Terminal A** (inside the Docker container): ```bash # Build the Hello World example (both CA and TA) make -C examples/hello_world-rs/ ``` Under the hood, the Makefile builds both the Trusted Application (TA) and the -Host Application separately. After a successful build, you'll find the -resulting binaries in the `hello_world-rs` directory: +Host Application separately. After a successful build, you'll find the resulting +binaries in the `hello_world-rs` directory: ```bash TA=ta/target/aarch64-unknown-linux-gnu/release/133af0ca-bdab-11eb-9130-43bf7873bf67.ta HOST_APP=host/target/aarch64-unknown-linux-gnu/release/hello_world-rs ``` +### If you use cargo-optee: + +You can see the [cargo-optee build +commands](../cargo-optee/README.md#build-commands) for details. + ## 3. Make the Artifacts Accessible to the Emulator After building the Hello World example, the next step is to make the compiled artifacts accessible to the emulator. @@ -54,11 +65,12 @@ There are **two approaches** to do this. You can choose either based on your preference: - 📦 **Manual sync**: Explicitly sync host and TA binaries to the emulator - ⚙️ **Makefile integration**: Use `make emulate` to build and sync in one step + (only when you use Makefile for building) #### Option 1: Manual Sync via `sync_to_emulator` We provide a helper command called `sync_to_emulator`, which simplifies the -process of syncing the build outputs to the emulation environment. -Run the following commands inside the container: +process of syncing the build outputs to the emulation environment. Run the +following commands inside the container: ```bash sync_to_emulator --ta $TA sync_to_emulator --host $HOST_APP @@ -66,20 +78,23 @@ sync_to_emulator --host $HOST_APP Run `sync_to_emulator -h` for more usage options. #### Option 2: Integrate sync with TA's Makefile -For convenience during daily development, the sync invocation can be integrated into -the Makefile. In the `hello_world-rs` example, an `emulate` target is provided. -This helps automatically build the artifacts and sync them to the emulator in one step: +For convenience during daily development, the sync invocation can be integrated +into the Makefile. In the `hello_world-rs` example, an `emulate` target is +provided. This helps automatically build the artifacts and sync them to the +emulator in one step: ```bash make -C examples/hello_world-rs/ emulate ``` ## 4. Multi-Terminal Execution -The emulation workflow requires three additional terminals to monitor -various aspects of the system: +The emulation workflow requires three additional terminals to monitor various +aspects of the system: -- **Terminal B**: 🖥️ **Normal World Listener** - Provides access to the guest VM shell -- **Terminal C**: 🔒 **Secure World Listener** - Monitors Trusted Application output logs +- **Terminal B**: 🖥️ **Normal World Listener** - Provides access to the guest + VM shell +- **Terminal C**: 🔒 **Secure World Listener** - Monitors Trusted Application + output logs - **Terminal D**: 🚀 **QEMU Control** - Controls the QEMU emulator Built-in commands are provided in the Docker image. These commands are located @@ -115,18 +130,17 @@ After the listeners are set up, we can start the QEMU emulator. $ docker exec -it teaclave_dev_env bash -l -c "LISTEN_MODE=ON start_qemuv8" ``` -> ⏳ **Wait for the QEMU environment to fully boot...** -You should see boot messages in Terminal D and the guest VM shell prompt -in Terminal B. +> ⏳ **Wait for the QEMU environment to fully boot...** You should see boot +messages in Terminal D and the guest VM shell prompt in Terminal B. After QEMU in Terminal D successfully launches, switch to Terminal B, which provides shell access to the guest VM's normal world. -**Terminal B** (Inside Guest VM): -From this shell, you'll find that the artifacts synced in **Step 3** are already -available in the current working directory. Additionally, the `ta/` and -`plugin/` subdirectories are automatically mounted to be used by TEE OS during -TA execution and plugin loading. +**Terminal B** (Inside Guest VM): From this shell, you'll find that the +artifacts synced in **Step 3** are already available in the current working +directory. Additionally, the `ta/` and `plugin/` subdirectories are +automatically mounted to be used by TEE OS during TA execution and plugin +loading. For more details on the mount configuration, refer to the `listen_on_guest_vm_shell` command in the development environment. @@ -150,31 +164,33 @@ Now we are ready to interact with the TA from normal world shell. # Execute the Hello World Client Application $ ./host/hello_world-rs ``` -The secure world logs, including TA debug messages, are displayed in **Terminal C**. +The secure world logs, including TA debug messages, are displayed in **Terminal +C**. ## 6. Iterative Development with Frequent Code Updates and Execution -During active development and debugging, you can leave Terminals B, C, and D open to -avoid restarting them each time. Simply return to Terminal A, and repeat Step 2 (build) -and Step 3 (sync) to rebuild and update the artifacts. Once synced, switch to -Terminal B to re-run the client application. This setup streamlines iterative -development and testing. +During active development and debugging, you can leave Terminals B, C, and D +open to avoid restarting them each time. Simply return to Terminal A, and repeat +Step 2 (build) and Step 3 (sync) to rebuild and update the artifacts. Once +synced, switch to Terminal B to re-run the client application. This setup +streamlines iterative development and testing. ## Summary -By following this guide, you can emulate and debug Trusted Applications using our -pre-configured Docker-based development environment. +By following this guide, you can emulate and debug Trusted Applications using +our pre-configured Docker-based development environment. -- **Terminal A** serves as the main interface for building and syncing artifacts. -- **Terminal B** gives access to the normal world inside the guest VM, where you +- **Terminal A** serves as the main interface for building and syncing + artifacts. +- **Terminal B** gives access to the normal world inside the guest VM, where you can run client applications like the Hello World example. -- **Terminal C** captures logs and debug output from the secure world, making it +- **Terminal C** captures logs and debug output from the secure world, making it easy to trace TA behavior. -- **Terminal D** controls the QEMU emulator and shows system-level logs during +- **Terminal D** controls the QEMU emulator and shows system-level logs during boot and runtime. -Together, these terminals provide a complete and efficient workflow for TrustZone -development and emulation. +Together, these terminals provide a complete and efficient workflow for +TrustZone development and emulation. ### Development Environment Details The setup scripts and built-in commands can be found in `/opt/teaclave/`. Please refer to the Dockerfile in the SDK source repository for more information about -how we set up the development environment. +how we set up the development environment. \ No newline at end of file diff --git a/docs/ta-development-modes.md b/docs/ta-development-modes.md index 23a3376d..db001f65 100644 --- a/docs/ta-development-modes.md +++ b/docs/ta-development-modes.md @@ -35,7 +35,6 @@ permalink: /trustzone-sdk-docs/ta-development-modes.md - **Common**: See [Overview of OP-TEE Rust Examples](https://teaclave.apache.org/trustzone-sdk-docs/overview-of-optee-rust-examples/). -- **`no-std`**: Excludes `test_serde`, `test_message_passing_interface`, - `test_tls_client`, `test_tls_server`, `test_secure_db_abstraction`. +- **`no-std`**: Excludes `test_tls_client`, `test_tls_server`, `test_secure_db_abstraction`. - **`std`**: Excludes `test_mnist_rs`, `test_build_with_optee_utee_sys`. \ No newline at end of file diff --git a/examples/hello_world-rs/host/Cargo.toml b/examples/hello_world-rs/host/Cargo.toml index b148a29e..cffbee2a 100644 --- a/examples/hello_world-rs/host/Cargo.toml +++ b/examples/hello_world-rs/host/Cargo.toml @@ -31,3 +31,8 @@ optee-teec = { path = "../../../optee-teec" } [profile.release] lto = true + +[package.metadata.optee.ca] +arch = "aarch64" +debug = false +optee-client-export = { aarch64 = "/opt/teaclave/optee/optee_client/export_arm64", arm = "/opt/teaclave/optee/optee_client/export_arm32" } \ No newline at end of file diff --git a/examples/message_passing_interface-rs/ta/Cargo.lock b/examples/message_passing_interface-rs/ta/Cargo.lock index d3553b9c..538e6260 100644 --- a/examples/message_passing_interface-rs/ta/Cargo.lock +++ b/examples/message_passing_interface-rs/ta/Cargo.lock @@ -106,20 +106,21 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "optee-utee" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bitflags", "hex", "libc_alloc", "optee-utee-macros", "optee-utee-sys", + "rustc_version", "strum_macros", "uuid 0.8.2", ] [[package]] name = "optee-utee-build" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "prettyplease", @@ -132,7 +133,7 @@ dependencies = [ [[package]] name = "optee-utee-macros" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "quote 0.6.13", @@ -142,7 +143,7 @@ dependencies = [ [[package]] name = "optee-utee-sys" -version = "0.6.0" +version = "0.7.0" dependencies = [ "libc", ] @@ -201,6 +202,15 @@ dependencies = [ "proc-macro2 1.0.95", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -213,6 +223,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.219" diff --git a/examples/metadata.json b/examples/metadata.json new file mode 100644 index 00000000..fc293428 --- /dev/null +++ b/examples/metadata.json @@ -0,0 +1,138 @@ +{ + "examples": { + "build_with_optee_utee_sys-rs": { + "category": "no-std-only", + "tas": ["build_with_optee_utee_sys-rs/ta"], + "cas": ["build_with_optee_utee_sys-rs/host"] + }, + "mnist-rs": { + "category": "no-std-only", + "tas": [ + "mnist-rs/ta/inference", + "mnist-rs/ta/train" + ], + "cas": ["mnist-rs/host"] + }, + "secure_db_abstraction-rs": { + "category": "std-only", + "tas": ["secure_db_abstraction-rs/ta"], + "cas": ["secure_db_abstraction-rs/host"] + }, + "tls_client-rs": { + "category": "std-only", + "tas": ["tls_client-rs/ta"], + "cas": ["tls_client-rs/host"] + }, + "tls_server-rs": { + "category": "std-only", + "tas": ["tls_server-rs/ta"], + "cas": ["tls_server-rs/host"] + }, + "acipher-rs": { + "category": "common", + "tas": ["acipher-rs/ta"], + "cas": ["acipher-rs/host"] + }, + "aes-rs": { + "category": "common", + "tas": ["aes-rs/ta"], + "cas": ["aes-rs/host"] + }, + "authentication-rs": { + "category": "common", + "tas": ["authentication-rs/ta"], + "cas": ["authentication-rs/host"] + }, + "big_int-rs": { + "category": "common", + "tas": ["big_int-rs/ta"], + "cas": ["big_int-rs/host"] + }, + "client_pool-rs": { + "category": "common", + "tas": ["client_pool-rs/ta"], + "cas": ["client_pool-rs/host"] + }, + "diffie_hellman-rs": { + "category": "common", + "tas": ["diffie_hellman-rs/ta"], + "cas": ["diffie_hellman-rs/host"] + }, + "digest-rs": { + "category": "common", + "tas": ["digest-rs/ta"], + "cas": ["digest-rs/host"] + }, + "error_handling-rs": { + "category": "common", + "tas": ["error_handling-rs/ta"], + "cas": ["error_handling-rs/host"] + }, + "hello_world-rs": { + "category": "common", + "tas": ["hello_world-rs/ta"], + "cas": ["hello_world-rs/host"] + }, + "hotp-rs": { + "category": "common", + "tas": ["hotp-rs/ta"], + "cas": ["hotp-rs/host"] + }, + "inter_ta-rs": { + "category": "common", + "tas": ["inter_ta-rs/ta"], + "cas": ["inter_ta-rs/host"] + }, + "message_passing_interface-rs": { + "category": "common", + "tas": ["message_passing_interface-rs/ta"], + "cas": ["message_passing_interface-rs/host"] + }, + "property-rs": { + "category": "common", + "tas": ["property-rs/ta"], + "cas": ["property-rs/host"] + }, + "random-rs": { + "category": "common", + "tas": ["random-rs/ta"], + "cas": ["random-rs/host"] + }, + "secure_storage-rs": { + "category": "common", + "tas": ["secure_storage-rs/ta"], + "cas": ["secure_storage-rs/host"] + }, + "serde-rs": { + "category": "common", + "tas": ["serde-rs/ta"], + "cas": ["serde-rs/host"] + }, + "signature_verification-rs": { + "category": "common", + "tas": ["signature_verification-rs/ta"], + "cas": ["signature_verification-rs/host"] + }, + "supp_plugin-rs": { + "category": "common", + "tas": ["supp_plugin-rs/ta"], + "cas": ["supp_plugin-rs/host"], + "plugins": ["supp_plugin-rs/plugin"] + }, + "tcp_client-rs": { + "category": "common", + "tas": ["tcp_client-rs/ta"], + "cas": ["tcp_client-rs/host"] + }, + "time-rs": { + "category": "common", + "tas": ["time-rs/ta"], + "cas": ["time-rs/host"] + }, + "udp_socket-rs": { + "category": "common", + "tas": ["udp_socket-rs/ta"], + "cas": ["udp_socket-rs/host"] + } + } +} diff --git a/examples/mnist-rs/ta/inference/Cargo.toml b/examples/mnist-rs/ta/inference/Cargo.toml index b10e3061..a472f267 100644 --- a/examples/mnist-rs/ta/inference/Cargo.toml +++ b/examples/mnist-rs/ta/inference/Cargo.toml @@ -38,3 +38,6 @@ spin = { workspace = true } [build-dependencies] proto = { workspace = true } optee-utee-build = { workspace = true } + +[package.metadata.optee.ta] +uuid-path = "uuid.txt" \ No newline at end of file diff --git a/examples/mnist-rs/ta/train/Cargo.toml b/examples/mnist-rs/ta/train/Cargo.toml index a0c24f98..287368e2 100644 --- a/examples/mnist-rs/ta/train/Cargo.toml +++ b/examples/mnist-rs/ta/train/Cargo.toml @@ -38,3 +38,6 @@ bytemuck = { workspace = true, features = ["min_const_generics"] } [build-dependencies] proto = { workspace = true } optee-utee-build = { workspace = true } + +[package.metadata.optee.ta] +uuid-path = "uuid.txt" \ No newline at end of file diff --git a/examples/secure_db_abstraction-rs/ta/Cargo.lock b/examples/secure_db_abstraction-rs/ta/Cargo.lock index fa38b0e8..330c90b3 100644 --- a/examples/secure_db_abstraction-rs/ta/Cargo.lock +++ b/examples/secure_db_abstraction-rs/ta/Cargo.lock @@ -105,20 +105,21 @@ dependencies = [ [[package]] name = "optee-utee" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bitflags", "hex", "libc_alloc", "optee-utee-macros", "optee-utee-sys", + "rustc_version", "strum_macros", "uuid 0.8.2", ] [[package]] name = "optee-utee-build" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "prettyplease", @@ -131,7 +132,7 @@ dependencies = [ [[package]] name = "optee-utee-macros" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "quote 0.6.13", @@ -141,7 +142,7 @@ dependencies = [ [[package]] name = "optee-utee-sys" -version = "0.6.0" +version = "0.7.0" dependencies = [ "libc", ] @@ -199,6 +200,15 @@ dependencies = [ "proc-macro2 1.0.93", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -217,6 +227,12 @@ dependencies = [ "serde", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.217" diff --git a/examples/secure_db_abstraction-rs/ta/Cargo.toml b/examples/secure_db_abstraction-rs/ta/Cargo.toml index 8b666fc9..95ead8c0 100644 --- a/examples/secure_db_abstraction-rs/ta/Cargo.toml +++ b/examples/secure_db_abstraction-rs/ta/Cargo.toml @@ -24,6 +24,10 @@ repository = "https://github.com/apache/teaclave-trustzone-sdk.git" description = "An example of Rust OP-TEE TrustZone SDK." edition = "2018" +[features] +default = [] +std = ["optee-utee/std", "optee-utee-sys/std"] + [dependencies] proto = { path = "../proto" } optee-utee-sys = { path = "../../../optee-utee/optee-utee-sys", features = ["std"] } diff --git a/examples/serde-rs/ta/Cargo.lock b/examples/serde-rs/ta/Cargo.lock index 36a6e57b..07166a86 100644 --- a/examples/serde-rs/ta/Cargo.lock +++ b/examples/serde-rs/ta/Cargo.lock @@ -101,20 +101,21 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "optee-utee" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bitflags", "hex", "libc_alloc", "optee-utee-macros", "optee-utee-sys", + "rustc_version", "strum_macros", "uuid 0.8.2", ] [[package]] name = "optee-utee-build" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "prettyplease", @@ -127,7 +128,7 @@ dependencies = [ [[package]] name = "optee-utee-macros" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "quote 0.6.13", @@ -137,7 +138,7 @@ dependencies = [ [[package]] name = "optee-utee-sys" -version = "0.6.0" +version = "0.7.0" dependencies = [ "libc", ] @@ -196,6 +197,15 @@ dependencies = [ "proc-macro2 1.0.103", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -208,6 +218,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" diff --git a/examples/supp_plugin-rs/plugin/Cargo.toml b/examples/supp_plugin-rs/plugin/Cargo.toml index dd7949e9..1cdb80f9 100644 --- a/examples/supp_plugin-rs/plugin/Cargo.toml +++ b/examples/supp_plugin-rs/plugin/Cargo.toml @@ -40,3 +40,6 @@ lto = true [lib] crate-type = ["cdylib"] name = "syslog_plugin" + +[package.metadata.optee.plugin] +uuid-path = "../plugin_uuid.txt" \ No newline at end of file diff --git a/examples/supp_plugin-rs/ta/Cargo.toml b/examples/supp_plugin-rs/ta/Cargo.toml index b607aced..d26e62d9 100644 --- a/examples/supp_plugin-rs/ta/Cargo.toml +++ b/examples/supp_plugin-rs/ta/Cargo.toml @@ -41,3 +41,6 @@ optee-utee-build = { path = "../../../optee-utee-build" } panic = "abort" lto = true opt-level = 1 + +[package.metadata.optee.ta] +uuid-path = "../ta_uuid.txt" \ No newline at end of file diff --git a/examples/tls_client-rs/ta/Cargo.lock b/examples/tls_client-rs/ta/Cargo.lock index d91f1c9c..22dcafb1 100644 --- a/examples/tls_client-rs/ta/Cargo.lock +++ b/examples/tls_client-rs/ta/Cargo.lock @@ -522,20 +522,21 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "optee-utee" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bitflags", "hex", "libc_alloc", "optee-utee-macros", "optee-utee-sys", + "rustc_version", "strum_macros", "uuid 0.8.2", ] [[package]] name = "optee-utee-build" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "prettyplease", @@ -548,7 +549,7 @@ dependencies = [ [[package]] name = "optee-utee-macros" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "quote 0.6.13", @@ -558,7 +559,7 @@ dependencies = [ [[package]] name = "optee-utee-sys" -version = "0.6.0" +version = "0.7.0" dependencies = [ "libc", ] diff --git a/examples/tls_client-rs/ta/Cargo.toml b/examples/tls_client-rs/ta/Cargo.toml index 3f85be63..c238c3d6 100644 --- a/examples/tls_client-rs/ta/Cargo.toml +++ b/examples/tls_client-rs/ta/Cargo.toml @@ -24,6 +24,10 @@ repository = "https://github.com/apache/teaclave-trustzone-sdk.git" description = "An example of Rust OP-TEE TrustZone SDK." edition = "2018" +[features] +default = [] +std = ["optee-utee/std", "optee-utee-sys/std"] + [dependencies] proto = { path = "../proto" } optee-utee-sys = { path = "../../../optee-utee/optee-utee-sys", features = ["std"] } diff --git a/examples/tls_server-rs/ta/Cargo.lock b/examples/tls_server-rs/ta/Cargo.lock index 116c086f..4216b66a 100644 --- a/examples/tls_server-rs/ta/Cargo.lock +++ b/examples/tls_server-rs/ta/Cargo.lock @@ -522,20 +522,21 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "optee-utee" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bitflags", "hex", "libc_alloc", "optee-utee-macros", "optee-utee-sys", + "rustc_version", "strum_macros", "uuid 0.8.2", ] [[package]] name = "optee-utee-build" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "prettyplease", @@ -548,7 +549,7 @@ dependencies = [ [[package]] name = "optee-utee-macros" -version = "0.6.0" +version = "0.7.0" dependencies = [ "litemap", "quote 0.6.13", @@ -558,7 +559,7 @@ dependencies = [ [[package]] name = "optee-utee-sys" -version = "0.6.0" +version = "0.7.0" dependencies = [ "libc", ] diff --git a/examples/tls_server-rs/ta/Cargo.toml b/examples/tls_server-rs/ta/Cargo.toml index 279fa348..8e65ea38 100644 --- a/examples/tls_server-rs/ta/Cargo.toml +++ b/examples/tls_server-rs/ta/Cargo.toml @@ -24,6 +24,10 @@ repository = "https://github.com/apache/teaclave-trustzone-sdk.git" description = "An example of Rust OP-TEE TrustZone SDK." edition = "2018" +[features] +default = [] +std = ["optee-utee/std", "optee-utee-sys/std"] + [dependencies] proto = { path = "../proto" } optee-utee-sys = { path = "../../../optee-utee/optee-utee-sys", features = ["std"] } diff --git a/optee-utee-build/src/linker.rs b/optee-utee-build/src/linker.rs index d2643596..9cf37cc1 100644 --- a/optee-utee-build/src/linker.rs +++ b/optee-utee-build/src/linker.rs @@ -126,10 +126,11 @@ impl Linker { out_dir: PathBuf, ta_dev_kit_dir: PathBuf, ) -> Result<(), Error> { - const ENV_TARGET_TA: &str = "TARGET_TA"; - println!("cargo:rerun-if-env-changed={}", ENV_TARGET_TA); + // cargo passes TARGET as env to the build scripts + const ENV_TARGET: &str = "TARGET"; + println!("cargo:rerun-if-env-changed={}", ENV_TARGET); let mut aarch64_flag = true; - match env::var(ENV_TARGET_TA) { + match env::var(ENV_TARGET) { Ok(ref v) if v == "arm-unknown-linux-gnueabihf" || v == "arm-unknown-optee" => { match self.linker_type { LinkerType::Cc => println!("cargo:rustc-link-arg=-Wl,--no-warn-mismatch"),