-
Notifications
You must be signed in to change notification settings - Fork 29
Attestation libraries #386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8010ce5
351b329
1df0f7f
af100ee
bd5a9c1
187cd98
a7dac26
3f6f80d
8f9b3e4
69226e8
048f554
9be5b7c
8d0d0ff
79429fe
e06f887
e336f77
5206772
7289430
75747e2
0646518
4479469
80db1c5
f39a0db
c47cef1
11b5d45
8ed50b6
c4aac80
379eb44
f6ca4b2
3b8a1f9
4da4aec
747f70d
bbd015a
784ab7a
73f549d
2c75ea4
cf3362e
8328683
cfb6054
10cd8be
baa4481
530af43
6be5319
62d10e8
304c0d0
4d85797
546f9f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Licensed under the Apache-2.0 license | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package(default_visibility = ["//visibility:public"]) | ||
|
|
||
| # All production crates (embedded-safe, no test stubs). | ||
| filegroup( | ||
| name = "attest_embedded_all", | ||
| srcs = [ | ||
| "//services/attest/api:attest_api", | ||
| "//services/attest/producer:attest_producer", | ||
| ], | ||
| ) | ||
|
|
||
| # Host-side tests; run without embedded target config. | ||
| test_suite( | ||
| name = "attest_host_tests", | ||
| tests = [ | ||
| "//services/attest/api:attest_api_test", | ||
| "//services/attest/producer:attest_producer_integration_test", | ||
| "//services/attest/producer:attest_producer_unit_test", | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <!-- SPDX-License-Identifier: Apache-2.0 --> | ||
|
|
||
| # attest service | ||
|
|
||
| OCP-EAT attestation token producer for OpenPRoT. | ||
|
|
||
| ## Crates | ||
|
|
||
| | Crate | Path | Purpose | | ||
| |---|---|---| | ||
| | `openprot-attest-api` | `api/` | Platform-independent traits, types, and error definitions. Callers depend only on this crate. | | ||
| | `openprot-attest-producer` | `producer/` | Concrete `AttestProducer` implementations (`HwAttestProducer`, `SwAttestProducer`, `SoftwareAttestProducer`). | | ||
|
|
||
| ## Dependency structure | ||
|
|
||
| ``` | ||
| application / verifier service | ||
| └── openprot-attest-api (traits + types only) | ||
| └── openprot-attest-producer (production implementations) | ||
| ``` | ||
|
|
||
| Platform code selects an implementation at construction time: | ||
|
|
||
| - **`HwAttestProducer`** — backed by the Caliptra mailbox driver; all signing inside the hardware boundary. | ||
| - **`SwAttestProducer`** — backed by a caller-supplied P-384 key (`SwSigner`); no Caliptra required. | ||
| - **`SoftwareAttestProducer`** — software stub for tests (feature = `test-support`). | ||
|
|
||
| See each crate's README for API details and usage examples. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # Licensed under the Apache-2.0 license | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") | ||
|
|
||
| rust_library( | ||
| name = "attest_api", | ||
| srcs = glob(["src/**/*.rs"]), | ||
| crate_name = "openprot_attest_api", | ||
| edition = "2021", | ||
| visibility = ["//visibility:public"], | ||
| deps = [ | ||
| "@rust_crates//:heapless", | ||
| "@rust_crates//:p384", | ||
| "@rust_crates//:sha2", | ||
| "@rust_crates//:thiserror", | ||
| "@rust_crates//:zeroize", | ||
| ], | ||
| ) | ||
|
|
||
| rust_test( | ||
| name = "attest_api_test", | ||
| crate = ":attest_api", | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Licensed under the Apache-2.0 license | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| [package] | ||
| name = "openprot-attest-api" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| license = "Apache-2.0" | ||
| description = "Platform-independent API for the OpenPRoT attestation producer service" | ||
|
|
||
| [dependencies] | ||
| heapless = { version = "0.9", default-features = false } | ||
| thiserror = "2" | ||
| p384 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic"] } | ||
| sha2 = { version = "0.10", default-features = false } | ||
| zeroize = { version = "1.8", default-features = false, features = ["derive"] } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| <!-- SPDX-License-Identifier: Apache-2.0 --> | ||
|
|
||
| # openprot-attest-api | ||
|
|
||
| Platform-independent trait and type definitions for the OpenPRoT attestation | ||
| producer service. | ||
|
|
||
| Callers and other OpenPRoT services depend **only** on this crate. It has no | ||
| dependency on the producer implementation, the verifier module, or `spdm-lib`. | ||
|
|
||
| ## Purpose | ||
|
|
||
| This crate defines the stable interface boundary for attestation token | ||
| generation. By depending on `openprot-attest-api` rather than | ||
| `openprot-attest-producer`, services can be tested with any `AttestProducer` | ||
| implementation — including the `SoftwareAttestProducer` stub — without pulling | ||
| in hardware dependencies. | ||
|
|
||
| ## Source files | ||
|
|
||
| | File | Contents | | ||
| |---|---| | ||
| | `src/lib.rs` | Public re-exports. `#![no_std]` `#![forbid(unsafe_code)]`. | | ||
| | `src/traits.rs` | `AttestProducer` trait. | | ||
| | `src/signing_abstraction.rs` | `HwSigner` trait; `SwSigner` struct with P-384 key validation and ECDSA signing. | | ||
| | `src/types.rs` | `Measurement`, `DigestAlgorithm`, `MeasurementAuthority`, `AttestConfig`, `OemId`, `MeasurementProvider` trait, `SwSignerConfig` struct. | | ||
| | `src/consts.rs` | Fixed-capacity constants (`MAX_CERT_SIZE`, `MAX_CHAIN_LEN`, etc.). | | ||
| | `src/error.rs` | `AttestError` — shared error type for both service crates. | | ||
|
|
||
| ## Key traits | ||
|
|
||
| ### `AttestProducer` | ||
|
|
||
| The primary interface implemented by `HwAttestProducer`, `SwAttestProducer`, | ||
| and the `SoftwareAttestProducer` stub in the producer crate. | ||
|
|
||
| ```rust | ||
| pub trait AttestProducer { | ||
| fn generate_token( | ||
| &self, | ||
| nonce: &[u8], | ||
| out: &mut Vec<u8, MAX_TOKEN_SIZE>, | ||
| ) -> Result<(), AttestError>; | ||
|
|
||
| /// `buf` is cleared before being populated with the chain (leaf → root). | ||
| fn cert_chain( | ||
| &self, | ||
| buf: &mut Vec<Vec<u8, MAX_CERT_SIZE>, MAX_CHAIN_LEN>, | ||
| ) -> Result<(), AttestError>; | ||
| } | ||
| ``` | ||
|
|
||
| ### `HwSigner` | ||
|
|
||
| Abstracts signing and certificate operations backed by the Caliptra hardware | ||
| boundary. | ||
|
|
||
| ```rust | ||
| pub trait HwSigner { | ||
| fn sign(&self, payload: &[u8]) -> Result<[u8; 96], AttestError>; | ||
| fn cert_chain_der( | ||
| &self, | ||
| buf: &mut Vec<Vec<u8, MAX_CERT_SIZE>, MAX_CHAIN_LEN>, | ||
| ) -> Result<(), AttestError>; | ||
| fn measurements( | ||
| &self, | ||
| out: &mut Vec<Measurement, MAX_MEASUREMENTS>, | ||
| ) -> Result<(), AttestError>; | ||
| } | ||
| ``` | ||
|
|
||
| The private Alias Key never leaves Caliptra. Production code implements this | ||
| trait via the Caliptra mailbox driver (`caliptra-sw`). | ||
|
|
||
| ### `SwSigner` | ||
|
|
||
| Holds a caller-supplied P-384 private key and DER certificate chain for | ||
| software signing (no Caliptra hardware required). Constructed via | ||
| `SwSigner::new(SwSignerConfig { ... })`, which validates: | ||
|
|
||
| - Scalar is a valid P-384 private key (`1 ≤ d < n`), via `p384::ecdsa::SigningKey::from_bytes`. | ||
| - Cert chain contains at least one certificate. | ||
| - Every certificate begins with `0x30` (DER SEQUENCE tag). | ||
|
|
||
| Returns `Err(AttestError::InvalidKey)` on any violation. The key is stored as | ||
| a `SigningKey` which zeroizes on drop. `SwSignerConfig` also zeroizes the raw | ||
| scalar on drop. | ||
|
|
||
| `sign()` produces a real ECDSA P-384 signature (SHA-384 prehash, RFC 6979 | ||
| deterministic nonce) over the COSE `Sig_Structure`. | ||
|
|
||
| ### `MeasurementProvider` | ||
|
|
||
| Plug in platform-specific firmware measurement sources (UEFI, BMC, etc.) | ||
| beyond the Caliptra-internal measurements. | ||
|
|
||
| ```rust | ||
| pub trait MeasurementProvider { | ||
| fn measurements( | ||
| &self, | ||
| out: &mut Vec<Measurement, MAX_MEASUREMENTS>, | ||
| ) -> Result<(), AttestError>; | ||
| } | ||
| ``` | ||
|
|
||
| ## Key types | ||
|
|
||
| | Type | Description | | ||
| |---|---| | ||
| | `Measurement` | Single firmware measurement: component name, version, digest algorithm, digest bytes, measurement authority. | | ||
| | `DigestAlgorithm` | `Sha384` or `Sha512`. | | ||
| | `MeasurementAuthority` | `Caliptra` (hardware-measured) or `Platform` (software-registered). | | ||
| | `AttestConfig` | Producer configuration: `oemid`, `hw_model`. | | ||
| | `OemId` | OEM identifier (IANA Private Enterprise Number or UUID form). | | ||
| | `SwSignerConfig` | Input to `SwSigner::new`: 48-byte P-384 scalar and DER cert chain. Zeroizes scalar on drop. | | ||
|
|
||
| ## Error variants | ||
|
|
||
| | Variant | Meaning | | ||
| |---|---| | ||
| | `Mailbox` | Caliptra mailbox communication failure. | | ||
| | `Der` | DER parse error (malformed certificate structure). | | ||
| | `ChainValidation` | DICE chain structural or compliance failure. | | ||
| | `InvalidNonce` | Nonce length outside the 8–64 byte range. | | ||
| | `Cbor` | CBOR encoding error. | | ||
| | `BufferFull` | Fixed-size buffer capacity exceeded. | | ||
| | `Cose` | COSE signing error. | | ||
| | `Provider` | Measurement provider error. | | ||
| | `InvalidKey` | Invalid P-384 key material supplied to `SwSigner::new`. | | ||
|
|
||
| ## Cargo | ||
|
|
||
| ```toml | ||
| [dependencies] | ||
| openprot-attest-api = { path = "services/attest/api" } | ||
| ``` | ||
|
|
||
| The crate is `no_std` and depends on `heapless` for fixed-capacity | ||
| collections, `thiserror` for `AttestError`, `p384` + `sha2` for ECDSA | ||
| signing, and `zeroize` for key material cleanup. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Licensed under the Apache-2.0 license | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| // Caliptra chain: VendorCA → IDevID → LDevID → AliasFMC → AliasRT (leaf) | ||
| pub const MAX_CHAIN_LEN: usize = 5; | ||
| pub const MAX_CERT_SIZE: usize = 2048; | ||
|
|
||
| pub const MAX_MEASUREMENTS: usize = 16; | ||
|
chrysh marked this conversation as resolved.
|
||
| pub const MAX_PROVIDERS: usize = 8; | ||
| pub const MAX_COMPONENT_LEN: usize = 64; | ||
| pub const MAX_VERSION_LEN: usize = 32; | ||
| pub const MAX_DIGEST_LEN: usize = 64; // SHA-512 | ||
|
|
||
| pub const MAX_OEMID_LEN: usize = 16; | ||
| pub const MAX_HW_MODEL_LEN: usize = 64; | ||
|
|
||
| // Upper bound for a fully-populated COSE_Sign1 token | ||
| pub const MAX_TOKEN_SIZE: usize = 8192; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Licensed under the Apache-2.0 license | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum AttestError { | ||
| #[error("Mailbox error: {0}")] | ||
| Mailbox(&'static str), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| #[error("DER parse error: {0}")] | ||
| Der(&'static str), | ||
| #[error("DICE chain validation error: {0}")] | ||
| ChainValidation(&'static str), | ||
| #[error("Invalid nonce: {0}")] | ||
| InvalidNonce(&'static str), | ||
| #[error("CBOR encoding error")] | ||
| Cbor, | ||
| #[error("Fixed-size buffer capacity exceeded")] | ||
| BufferFull, | ||
| #[error("COSE signing error")] | ||
| Cose, | ||
| #[error("Measurement provider error: {0}")] | ||
| Provider(&'static str), | ||
| #[error("Invalid key material: {0}")] | ||
| InvalidKey(&'static str), | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // Licensed under the Apache-2.0 license | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! Platform-independent API for the OpenPRoT attestation producer service. | ||
| //! | ||
| //! # Usage | ||
| //! | ||
| //! Applications depend only on this crate. Platform code provides a concrete | ||
| //! [`AttestProducer`] implementation (hardware-backed via a platform signer, or the | ||
| //! `test-support`-gated software stub in the `openprot-attest-producer` crate). | ||
| //! | ||
| //! ```text | ||
| //! ┌───────────────────────────────────────────────┐ | ||
| //! │ application / verifier service │ | ||
| //! │ depends on: openprot-attest-api │ | ||
| //! │ calls: AttestProducer::generate_token│ | ||
| //! └──────────────────┬────────────────────────────┘ | ||
| //! │ trait object / generic bound | ||
| //! ┌──────────────────▼────────────────────────────┐ | ||
| //! │ openprot-attest-producer │ | ||
| //! │ HwAttestProducer (production, HW key) │ | ||
| //! │ SwAttestProducer (production, SW key) │ | ||
| //! │ SoftwareAttestProducer (test-support) │ | ||
| //! └───────────────────────────────────────────────┘ | ||
| //! ``` | ||
|
|
||
| #![no_std] | ||
| #![forbid(unsafe_code)] | ||
|
|
||
| pub mod consts; | ||
| mod error; | ||
| mod signing_abstraction; | ||
| mod traits; | ||
| mod types; | ||
|
|
||
| pub use error::AttestError; | ||
| pub use signing_abstraction::{HwSigner, SwSigner}; | ||
| pub use traits::AttestProducer; | ||
| pub use types::{ | ||
| AttestConfig, DigestAlgorithm, Measurement, MeasurementAuthority, MeasurementProvider, OemId, | ||
| SwSignerConfig, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
attest_api_testBazel target exists but theapicrate (traits.rs,types.rs,signing_abstraction.rs,error.rs,consts.rs) contains no#[cfg(test)]modules, so the security-criticalSwSigner::newvalidation (zero-scalar check, P-384 order check, DER SEQUENCE check) has zero test coverage despite a passing test target implying otherwise. A future regression in that validation would pass silently.