Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
386 changes: 342 additions & 44 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "regl"
version = "0.1.0"
edition = "2024"
description = "Rust Evidence Generation Library collects attestation evidence from TEE platforms"
description = "RATS Evidence Generation Library - collects attestation evidence from TEE platforms"
license = "Apache-2.0"
repository = "https://github.com/veraison/rust-regl"
keywords = ["cca", "attestation", "arm", "evidence", "tee"]
Expand All @@ -12,16 +12,21 @@ categories = ["hardware-support", "cryptography"]
thiserror = "2.0.18"
tempfile = "3"
ccatoken = { git = "https://github.com/veraison/rust-ccatoken.git" }
cmw = { git = "https://github.com/veraison/rust-cmw" }
cmw = "0.1.1"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = { version = "0.22", features = ["alloc"] }
ciborium = "0.2"
coset = "0.4"
p384 = { version = "0.13", features = ["ecdsa", "jwk"] }
ecdsa = { version = "0.16", features = ["signing"] }
sha2 = "0.11"
rand_core = { version = "0.6", features = ["getrandom"] }
url = "2"

[dev-dependencies]
log = "0.4.29"
env_logger = "0.11.10"
log = "0.4.33"
env_logger = "0.11.11"
clap = { version = "4.6.1", features = ["derive"] }
httpmock = "0.8"
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
Copyright 2026 Contributors to the Veraison project.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
32 changes: 20 additions & 12 deletions README.md
Comment thread
thomas-fossati marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
# rust-regl

Rust Evidence Generation Library (REGL) collects attestation evidence from TEE platforms.
RATS Evidence Generation Library (REGL) - collects attestation evidence from TEE platforms.
Comment thread
thomas-fossati marked this conversation as resolved.

## Attesters

| Attester | Struct | Backend | Description |
|---|---|---|---|
| `cca-tsm` | `CcaTsmAttester` | Linux TSM (`/sys/kernel/config/tsm`) | Talks directly to the kernel TSM interface on Arm CCA hardware. Requires root or write access to configfs-tsm. |
| `cca-ratsd` | `CcaRatsdAttester` | RATSD daemon | Posts a challenge to a [RATSD](https://github.com/veraison/ratsd) daemon, then parses the CMW envelope to extract the Arm CCA attestation token. "CCA-specific" means it knows how to find and decode CCA evidence inside the CMW — it looks for items whose content type contains `configfs-tsm` and whose provider is `arm_cca_guest`. |
| `cca-sim` | `CcaSimulatedAttester` | Embedded blob | Returns a pre-built CCA token embedded at compile time. Useful for testing and development without hardware or a running RATSD. |
| `ratsd` | `RatsdAttester` | RATSD daemon (generic) | Posts a challenge to a RATSD daemon and returns the raw JSON response as-is. No TEE-specific parsing — use this if you want the CMW envelope or other RATSD-level data directly. |
| `cca-tsm` | `CcaTsmAttester` | Linux TSM (`/sys/kernel/config/tsm`) | Talks directly to the kernel TSM interface on Arm CCA hardware. Requires root. |
| `cca-ratsd` | `CcaRatsdAttester` | RATSD daemon | Posts a challenge to a [RATSD](https://github.com/veraison/ratsd) daemon and extracts the CCA attestation token from the CMW envelope. |
| `cca-sim` | `CcaSimulatedAttester` | Pure Rust | Builds a CCA token from JSON claims and JWK keys with ES384 COSE_Sign1 signatures. No hardware needed. |
| `ratsd` | `RatsdAttester` | RATSD daemon | Posts a challenge to a RATSD daemon and returns the raw JSON response. No TEE-specific parsing. |

## Usage

```rust
use regl::attesters::{cca, ratsd, Attester};
use url::Url;

// Generic RATSD — explicit URL required
// Generic RATSD - returns the raw JSON response, no TEE-specific parsing
let url = Url::parse("http://localhost:8895").unwrap();
let attester = ratsd::RatsdAttester::with_url(url);
let response: Vec<u8> = attester.get_evidence(&challenge).unwrap();

// CCA-specific RATSD parses CMW envelope, returns CCA token bytes
// CCA-specific RATSD - parses CMW envelope, returns CCA token bytes
let url = Url::parse("http://localhost:8895").unwrap();
let attester = cca::CcaRatsdAttester::with_url(url);
let evidence = attester.get_evidence(&challenge).unwrap();

// TSM-backed attester (requires Linux CCA TSM hardware and root/sudo)
let attester = cca::CcaTsmAttester::default();
let evidence = attester.get_evidence(&challenge).unwrap();

// Simulated attester - builds a token from JSON claims and JWK keys (no hardware needed)
let claims_json = std::fs::read_to_string("test-data/cca-claims.json").unwrap();
let iak_jwk = std::fs::read_to_string("test-data/iak.jwk").unwrap();
let rak_jwk = std::fs::read_to_string("test-data/rak.jwk").unwrap();
let attester = cca::CcaSimulatedAttester::new(&claims_json, &iak_jwk, Some(&rak_jwk)).unwrap();
let evidence = attester.get_evidence(&challenge).unwrap();
```

> **Note:** The library itself does not read environment variables. The
> `RATSD_URL` env var is resolved only in the example binaries
> (`examples/attester.rs`) for convenience they fall back to
> (`examples/attester.rs`) for convenience - they fall back to
> `http://localhost:8895` if the variable is not set. Production code
> should pass an explicit `Url` via `with_url()`.

Expand Down Expand Up @@ -95,11 +102,12 @@ Set `RUST_LOG=info` to see progress logs from the attester.

## Utilities

`regl::attesters::cca::utils` provides CCA evidence decoding and pretty-printing:
`regl::attesters::cca::utils` provides CCA evidence encoding, decoding, and pretty-printing:

- `decode::decode_cca_token()` — decode raw CBOR evidence to typed Rust structs
- `print::pretty_print_token()` — decode and format the evidence as JSON
- `types` — serde-enabled CCA evidence structs with human-readable field names
- `regl::attesters::cca::utils::encode_cca_token()` - build a CCA token (CBOR tag 399) from typed claims and signing keys
- `regl::attesters::cca::utils::decode_cca_token()` - decode raw CBOR evidence to typed Rust structs (`CcaToken`, `PlatformClaims`, `RealmClaims`, `SwComponent`)
- `regl::attesters::cca::utils::pretty_print_token()` - decode and serialize a CCA token as indented JSON
- `CcaToken`, `PlatformClaims`, `RealmClaims`, `SwComponent` - serde-enabled CCA evidence structs with human-readable field names

## License

Expand Down
6 changes: 4 additions & 2 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ skip-tree = [
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
unknown-git = "deny"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
allow-git = [
"https://github.com/veraison/rust-ccatoken",
]

[sources.allow-org]
# 1 or more github.com organizations to allow git sources for
Expand Down
26 changes: 24 additions & 2 deletions examples/attester.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright 2026 Contributors to the Veraison project
// SPDX-License-Identifier: Apache-2.0

use clap::{Parser, ValueEnum};
use log::{error, info};
use regl::attesters::cca::utils::print::pretty_print_token;
use regl::attesters::cca::utils::pretty_print_token;
use regl::attesters::{Attester, cca, cca::CcaError};
use std::fs;
use url::Url;
Expand Down Expand Up @@ -28,10 +31,29 @@ enum AttesterType {
CcaRatsd,
}

fn create_sim_attester() -> Box<dyn Attester<AttesterError = CcaError>> {
let claims_path =
std::env::var("CCA_CLAIMS_FILE").unwrap_or_else(|_| "test-data/cca-claims.json".into());
let iak_path = std::env::var("CCA_IAK_FILE").unwrap_or_else(|_| "test-data/iak.jwk".into());
let rak_path = std::env::var("CCA_RAK_FILE").ok();

let claims =
fs::read_to_string(&claims_path).unwrap_or_else(|e| panic!("reading {claims_path}: {e}"));
let iak = fs::read_to_string(&iak_path).unwrap_or_else(|e| panic!("reading {iak_path}: {e}"));
let rak = rak_path
.as_ref()
.map(|p| fs::read_to_string(p).unwrap_or_else(|e| panic!("reading {p}: {e}")));

Box::new(
cca::CcaSimulatedAttester::new(&claims, &iak, rak.as_deref())
.expect("failed to create simulated attester"),
)
}

fn create_attester(kind: &AttesterType) -> Box<dyn Attester<AttesterError = CcaError>> {
match kind {
AttesterType::CcaTsm => Box::new(cca::CcaTsmAttester::default()),
AttesterType::CcaSim => Box::new(cca::CcaSimulatedAttester::default()),
AttesterType::CcaSim => create_sim_attester(),
AttesterType::CcaRatsd => {
let raw = std::env::var("RATSD_URL").unwrap_or_else(|_| "http://localhost:8895".into());
let url = Url::parse(&raw).expect("RATSD_URL must be a valid URL");
Expand Down
3 changes: 3 additions & 0 deletions examples/tsm.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2026 Contributors to the Veraison project
// SPDX-License-Identifier: Apache-2.0

use clap::Parser;
use log::{error, info};
use regl::tsm::{TsmReportBuilder, linuxtsm::LinuxTsmReportBuilder};
Expand Down
59 changes: 39 additions & 20 deletions src/attesters/cca/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2026 Contributors to the Veraison project
// SPDX-License-Identifier: Apache-2.0

use super::Attester;
use crate::tsm::linuxtsm::LinuxTsmReportBuilder;
use crate::tsm::{TsmError, TsmReport, TsmReportBuilder};
Expand All @@ -11,7 +14,7 @@ mod ratsd;
mod simulated;

pub use ratsd::CcaRatsdAttester;
use simulated::FakeTsmBuilder;
pub use simulated::CcaSimulatedAttester;

/// Arm CCA nonce size in bytes, as required by the CCA specification
/// (https://datatracker.ietf.org/doc/draft-ffm-rats-cca-token/).
Expand All @@ -20,9 +23,6 @@ const NONCE_SIZE: usize = 64;
#[derive(Debug, Default)]
pub struct CcaTsmAttester {}

#[derive(Debug, Default)]
pub struct CcaSimulatedAttester {}

impl Attester for CcaTsmAttester {
type AttesterError = CcaError;

Expand All @@ -39,22 +39,6 @@ impl Attester for CcaTsmAttester {
}
}

impl Attester for CcaSimulatedAttester {
type AttesterError = CcaError;

fn get_evidence(&self, challenge: &[u8]) -> Result<Vec<u8>> {
if challenge.len() != NONCE_SIZE {
return Err(CcaError::InvalidNonce(format!(
"expected {NONCE_SIZE} bytes, got {}",
challenge.len()
)));
}
let builder = FakeTsmBuilder::default();
let challenge = challenge.to_vec();
Ok(get_tsm_report(builder, challenge)?.outblob)
}
}

fn get_tsm_report<B>(generator: B, inblob: Vec<u8>) -> Result<TsmReport>
where
B: TsmReportBuilder,
Expand Down Expand Up @@ -133,4 +117,39 @@ mod tests {
let msg = format!("{err}");
assert!(msg.contains("something went wrong"));
}

// --- Error display ---

#[test]
fn invalid_nonce_displays_message() {
// Test that the Display impl formats the error with the expected and actual lengths.
// Using a sample bad length of 5 bytes for a visual check; the real value is
// generated at runtime based on the actual nonce passed by the caller.
let err = CcaError::InvalidNonce(format!(
"expected {} bytes, got {}",
NONCE_SIZE,
b"short".len()
));
let msg = format!("{err}");
assert!(msg.contains("invalid nonce"));
assert!(msg.contains("64"));
assert!(msg.contains("5"));
}

#[test]
fn tsm_error_displays_message() {
let err = CcaError::Tsm(TsmError::Unsupported);
let msg = format!("{err}");
assert!(msg.contains("TSM error"));
}

#[test]
fn ratsd_error_displays_message() {
let err = CcaError::Ratsd(crate::attesters::ratsd::RatsdError::Custom(
"bad response".into(),
));
let msg = format!("{err}");
assert!(msg.contains("RATSD error"));
assert!(msg.contains("bad response"));
}
}
7 changes: 5 additions & 2 deletions src/attesters/cca/ratsd.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2026 Contributors to the Veraison project
// SPDX-License-Identifier: Apache-2.0

//! CCA-specific RATSD attester.
//!
//! Uses the generic [`RatsdAttester`](crate::attesters::ratsd::RatsdAttester)
Expand Down Expand Up @@ -149,7 +152,7 @@ mod tests {
}

// -----------------------------------------------------------------------
// extract_cca_token success case
// extract_cca_token - success case
// -----------------------------------------------------------------------

#[test]
Expand Down Expand Up @@ -181,7 +184,7 @@ mod tests {
}

// -----------------------------------------------------------------------
// extract_cca_token error cases
// extract_cca_token - error cases
// -----------------------------------------------------------------------

#[test]
Expand Down
Loading
Loading