Import of SPDM Requester and Responder services This imports the requester, responder, and support trait implementations from the OCP demo.
diff --git a/services/spdm/hash/BUILD.bazel b/services/spdm/hash/BUILD.bazel new file mode 100644 index 0000000..ce65e90 --- /dev/null +++ b/services/spdm/hash/BUILD.bazel
@@ -0,0 +1,20 @@ +# Licensed under the Apache-2.0 license + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "spdm_hash_lib", + srcs = glob(["src/**/*.rs"]), + crate_name = "openprot_spdm_hash", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//services/crypto/client:crypto_client", + "@oot_crates_no_std//:spdm-lib", + ], +) + +rust_test( + name = "spdm_hash_test", + crate = ":spdm_hash_lib", +)
diff --git a/services/spdm/hash/Cargo.toml b/services/spdm/hash/Cargo.toml new file mode 100644 index 0000000..80b2e8c --- /dev/null +++ b/services/spdm/hash/Cargo.toml
@@ -0,0 +1,12 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "openprot-spdm-hash" +version = "0.1.0" +edition = "2021" +description = "SPDM hash implementation using OpenPRoT crypto service" +license = "Apache-2.0" + +[dependencies] +spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" } +crypto-client = { path = "../../crypto/client" }
diff --git a/services/spdm/hash/README.md b/services/spdm/hash/README.md new file mode 100644 index 0000000..17119c4 --- /dev/null +++ b/services/spdm/hash/README.md
@@ -0,0 +1,124 @@ +# SPDM Hash + +Hash functions for SPDM protocol operations, implemented via OpenPRoT crypto service. + +## Overview + +Implements the `SpdmHash` trait from spdm-lib supporting SHA-384 and SHA-512 algorithms. All cryptographic operations are delegated to the centralized crypto service via IPC. + +## Architecture + +``` +SpdmCryptoHash → CryptoClient → IPC → CryptoServer → RustCryptoBackend → SHA2 +``` + +## Supported Algorithms + +- **SHA-384** (48 bytes) — Default per SPDM spec +- **SHA-512** (64 bytes) + +## Usage Patterns + +### Stateless (One-Shot) + +For small messages that fit in a single IPC call: + +```rust +use openprot_spdm_hash::SpdmCryptoHash; +use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType}; + +let mut hasher = SpdmCryptoHash::new(handle::CRYPTO); +let mut output = [0u8; 48]; +hasher.hash(SpdmHashAlgoType::SHA384, b"data to hash", &mut output)?; +``` + +### Stateful (Streaming) + +For large messages or data that arrives in chunks: + +```rust +let mut hasher = SpdmCryptoHash::new(handle::CRYPTO); + +// Initialize +hasher.init(SpdmHashAlgoType::SHA384, None)?; + +// Accumulate data +hasher.update(chunk1)?; +hasher.update(chunk2)?; +hasher.update(chunk3)?; + +// Finalize +let mut output = [0u8; 48]; +hasher.finalize(&mut output)?; + +// Clean up for next use +hasher.reset(); +``` + +### With Initial Data + +The `init()` method supports providing initial data: + +```rust +// Initialize with VCA (Version/Capabilities/Algorithms) data +hasher.init(SpdmHashAlgoType::SHA384, Some(vca_buffer))?; + +// Then add additional messages +hasher.update(request_data)?; +hasher.update(response_data)?; + +// Finalize +hasher.finalize(&mut output)?; +``` + +## SPDM Use Cases + +This implementation is used by spdm-lib for: + +- **Transcript Hashing**: M1 and L1 transcript hashes for CHALLENGE and MEASUREMENTS +- **Signature Context**: Hash of signing context for CHALLENGE responses +- **Measurement Summaries**: Hashing measurement blocks for attestation +- **Certificate Verification**: Hashing certificate chains + +## Dependencies + +- `spdm-lib` — SPDM protocol library (https://github.com/9elements/spdm-lib.git, branch: buildup) +- `crypto-client` — OpenPRoT crypto service client + +## State Management + +The implementation maintains internal state to support streaming operations: + +- **Idle**: No active session +- **SHA-384 Session**: Active SHA-384 streaming hash +- **SHA-512 Session**: Active SHA-512 streaming hash + +State transitions: +- `init()` → Creates session (Idle → Sha384/Sha512) +- `update()` → Feeds data (stays in current session) +- `finalize()` → Completes hash (Sha384/Sha512 → Idle) +- `reset()` → Aborts session (Any → Idle) +- `hash()` → Operates independently of state + +## Performance + +- **One-shot operations**: Single IPC round-trip (~10-50μs) +- **Streaming operations**: One IPC call per begin/update/finish +- **Maximum one-shot size**: ~900 bytes (IPC buffer limit) +- **Streaming advantage**: Can handle arbitrarily large data + +## Security + +- **Algorithms**: FIPS 180-4 compliant SHA-384/512 via RustCrypto +- **Constant-time**: RustCrypto implementations aim for constant-time where feasible +- **Trust boundary**: Assumes crypto service is trusted +- **IPC protection**: Relies on kernel IPC channel security + +## Future Enhancements + +- Hardware acceleration via AST1060 HACE (transparent when backend is upgraded) +- Additional hash algorithms if required by future SPDM versions + +## License + +Apache-2.0
diff --git a/services/spdm/hash/src/lib.rs b/services/spdm/hash/src/lib.rs new file mode 100644 index 0000000..13dba1d --- /dev/null +++ b/services/spdm/hash/src/lib.rs
@@ -0,0 +1,237 @@ +// Licensed under the Apache-2.0 license + +//! SPDM Hash Implementation +//! +//! Provides cryptographic hashing for SPDM protocol operations by +//! delegating to the OpenPRoT crypto service via IPC. +//! +//! ## Architecture +//! +//! This crate implements the `SpdmHash` trait from spdm-lib by wrapping +//! the `CryptoClient` and calling its hash methods. It supports both +//! stateless one-shot hashing and stateful streaming operations. +//! +//! ## Supported Algorithms +//! +//! - **SHA-384** (48-byte output) — Default per SPDM spec +//! - **SHA-512** (64-byte output) +//! +//! ## Usage +//! +//! ### Stateless (One-Shot) +//! +//! ```rust,no_run +//! use openprot_spdm_hash::SpdmCryptoHash; +//! use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType}; +//! +//! let mut hasher = SpdmCryptoHash::new(crypto_handle); +//! let mut output = [0u8; 48]; +//! hasher.hash(SpdmHashAlgoType::SHA384, b"data", &mut output).unwrap(); +//! ``` +//! +//! ### Stateful (Streaming) +//! +//! ```rust,no_run +//! use openprot_spdm_hash::SpdmCryptoHash; +//! use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType}; +//! +//! let mut hasher = SpdmCryptoHash::new(crypto_handle); +//! +//! // Initialize +//! hasher.init(SpdmHashAlgoType::SHA384, None).unwrap(); +//! +//! // Accumulate data +//! hasher.update(b"chunk1").unwrap(); +//! hasher.update(b"chunk2").unwrap(); +//! +//! // Finalize +//! let mut output = [0u8; 48]; +//! hasher.finalize(&mut output).unwrap(); +//! +//! // Clean up +//! hasher.reset(); +//! ``` + +#![no_std] +#![warn(missing_docs)] + +use crypto_client::{CryptoClient, Sha384Session, Sha512Session}; +use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType, SpdmHashError, SpdmHashResult}; + +/// SPDM hash implementation using OpenPRoT crypto service. +/// +/// This struct wraps a `CryptoClient` handle and maintains internal state +/// to support both stateless one-shot hashing and stateful streaming operations. +pub struct SpdmCryptoHash { + crypto: CryptoClient, + state: HashState, +} + +/// Internal state tracking for streaming hash operations. +enum HashState { + /// No active hash session + Idle, + /// Active SHA-384 streaming session + Sha384(Sha384Session), + /// Active SHA-512 streaming session + Sha512(Sha512Session), +} + +impl SpdmCryptoHash { + /// Create a new SPDM hash implementation using the crypto service. + /// + /// # Arguments + /// + /// * `crypto_handle` — IPC channel handle for the crypto service + /// (typically from your app's generated handle module, e.g., `handle::CRYPTO`) + pub const fn new(crypto_handle: u32) -> Self { + Self { + crypto: CryptoClient::new(crypto_handle), + state: HashState::Idle, + } + } +} + +impl SpdmHash for SpdmCryptoHash { + fn hash( + &mut self, + hash_algo: SpdmHashAlgoType, + data: &[u8], + hash: &mut [u8], + ) -> SpdmHashResult<()> { + match hash_algo { + SpdmHashAlgoType::SHA384 => { + if hash.len() < 48 { + return Err(SpdmHashError::BufferTooSmall); + } + let result = self + .crypto + .sha384(data) + .map_err(|_| SpdmHashError::PlatformError)?; + hash[..48].copy_from_slice(&result); + Ok(()) + } + SpdmHashAlgoType::SHA512 => { + if hash.len() < 64 { + return Err(SpdmHashError::BufferTooSmall); + } + let result = self + .crypto + .sha512(data) + .map_err(|_| SpdmHashError::PlatformError)?; + hash[..64].copy_from_slice(&result); + Ok(()) + } + } + } + + fn init(&mut self, hash_algo: SpdmHashAlgoType, data: Option<&[u8]>) -> SpdmHashResult<()> { + // Ensure we're in Idle state before starting a new session + if !matches!(self.state, HashState::Idle) { + return Err(SpdmHashError::PlatformError); + } + + // Start the appropriate session + match hash_algo { + SpdmHashAlgoType::SHA384 => { + let session = self + .crypto + .sha384_begin() + .map_err(|_| SpdmHashError::PlatformError)?; + self.state = HashState::Sha384(session); + } + SpdmHashAlgoType::SHA512 => { + let session = self + .crypto + .sha512_begin() + .map_err(|_| SpdmHashError::PlatformError)?; + self.state = HashState::Sha512(session); + } + } + + // If initial data was provided, update with it + if let Some(initial_data) = data { + self.update(initial_data)?; + } + + Ok(()) + } + + fn update(&mut self, data: &[u8]) -> SpdmHashResult<()> { + match &mut self.state { + HashState::Idle => Err(SpdmHashError::PlatformError), + HashState::Sha384(session) => session + .update(data) + .map_err(|_| SpdmHashError::PlatformError), + HashState::Sha512(session) => session + .update(data) + .map_err(|_| SpdmHashError::PlatformError), + } + } + + fn finalize(&mut self, hash: &mut [u8]) -> SpdmHashResult<()> { + // Take the session out of state, replacing with Idle + let state = core::mem::replace(&mut self.state, HashState::Idle); + + match state { + HashState::Idle => Err(SpdmHashError::PlatformError), + HashState::Sha384(session) => { + if hash.len() < 48 { + return Err(SpdmHashError::BufferTooSmall); + } + let result = session + .finalize() + .map_err(|_| SpdmHashError::PlatformError)?; + hash[..48].copy_from_slice(&result); + Ok(()) + } + HashState::Sha512(session) => { + if hash.len() < 64 { + return Err(SpdmHashError::BufferTooSmall); + } + let result = session + .finalize() + .map_err(|_| SpdmHashError::PlatformError)?; + hash[..64].copy_from_slice(&result); + Ok(()) + } + } + } + + fn reset(&mut self) { + // Simply drop the current session and return to Idle + self.state = HashState::Idle; + } + + fn algo(&self) -> SpdmHashAlgoType { + match &self.state { + HashState::Idle => SpdmHashAlgoType::SHA384, // Default + HashState::Sha384(_) => SpdmHashAlgoType::SHA384, + HashState::Sha512(_) => SpdmHashAlgoType::SHA512, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_struct_creation() { + let _hasher = SpdmCryptoHash::new(42); + } + + #[test] + fn test_default_algo() { + let hasher = SpdmCryptoHash::new(42); + assert_eq!(hasher.algo(), SpdmHashAlgoType::SHA384); + } + + #[test] + fn test_reset_idempotent() { + let mut hasher = SpdmCryptoHash::new(42); + hasher.reset(); + hasher.reset(); + assert_eq!(hasher.algo(), SpdmHashAlgoType::SHA384); + } +}
diff --git a/services/spdm/requester/BUILD.bazel b/services/spdm/requester/BUILD.bazel new file mode 100644 index 0000000..0362cd5 --- /dev/null +++ b/services/spdm/requester/BUILD.bazel
@@ -0,0 +1,21 @@ +# Licensed under the Apache-2.0 license + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "spdm_requester_lib", + srcs = glob(["src/**/*.rs"]), + crate_name = "openprot_spdm_requester", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//services/spdm/transport-mctp:spdm_transport_mctp", + "@rust_crates//:heapless", + "@rust_crates//:spdm-lib", + ], +) + +rust_test( + name = "spdm_requester_test", + crate = ":spdm_requester_lib", +)
diff --git a/services/spdm/requester/Cargo.toml b/services/spdm/requester/Cargo.toml new file mode 100644 index 0000000..e87282e --- /dev/null +++ b/services/spdm/requester/Cargo.toml
@@ -0,0 +1,13 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "openprot-spdm-requester" +version = "0.1.0" +edition = "2021" +description = "SPDM requester implementation for OpenPRoT" +license = "Apache-2.0" + +[dependencies] +openprot-spdm-transport-mctp = { path = "../transport-mctp" } +spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" } +heapless = { workspace = true }
diff --git a/services/spdm/requester/README.md b/services/spdm/requester/README.md new file mode 100644 index 0000000..0cca5d2 --- /dev/null +++ b/services/spdm/requester/README.md
@@ -0,0 +1,29 @@ +# openprot-spdm-requester + +SPDM requester (client) implementation for OpenPRoT. + +## Overview + +This crate provides the SPDM requester role, which initiates attestation operations with SPDM responders. The requester sends requests to retrieve: +- Version information +- Device capabilities +- Cryptographic algorithms +- Certificates and measurements +- Challenge-response attestation + +## Status + +This crate is in early development. Basic infrastructure is in place, but SPDM protocol operations are not yet implemented. + +## Dependencies + +- `spdm-lib` — SPDM protocol library from 9elements +- `heapless` — `no_std` collections + +## Future Work + +- Implement SPDM 1.2+ request builders +- Add GET_VERSION, GET_CAPABILITIES support +- Implement CHALLENGE and GET_MEASUREMENTS +- Add certificate chain validation +- Integrate with MCTP transport layer
diff --git a/services/spdm/requester/src/lib.rs b/services/spdm/requester/src/lib.rs new file mode 100644 index 0000000..7b6bf73 --- /dev/null +++ b/services/spdm/requester/src/lib.rs
@@ -0,0 +1,67 @@ +// Licensed under the Apache-2.0 license + +//! SPDM Requester Service +//! +//! This service implements the SPDM requester (client) role, which initiates +//! attestation and measurement operations with SPDM responders. +//! +//! ## Overview +//! +//! The SPDM requester sends requests to responders to: +//! - Get version information +//! - Negotiate capabilities and algorithms +//! - Challenge the responder for attestation +//! - Retrieve measurements and certificates +//! +//! ## Architecture +//! +//! ```text +//! ┌─────────────────────────┐ +//! │ Application │ +//! │ (attestation manager) │ +//! └───────────┬─────────────┘ +//! │ +//! ▼ +//! ┌─────────────────────────┐ +//! │ SPDM Requester │◄── This crate +//! │ (request builder) │ +//! └───────────┬─────────────┘ +//! │ SPDM messages +//! ▼ +//! ┌─────────────────────────┐ +//! │ MCTP Transport │ +//! └─────────────────────────┘ +//! ``` + +#![no_std] +#![warn(missing_docs)] + +/// SPDM requester state and configuration. +#[derive(Debug)] +pub struct SpdmRequester { + /// Remote endpoint ID for MCTP transport. + pub remote_eid: u8, +} + +impl SpdmRequester { + /// Create a new SPDM requester targeting the given endpoint. + pub fn new(remote_eid: u8) -> Self { + Self { remote_eid } + } + + /// Get the remote endpoint ID. + pub fn remote_eid(&self) -> u8 { + self.remote_eid + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_requester_creation() { + let requester = SpdmRequester::new(42); + assert_eq!(requester.remote_eid(), 42); + } +}
diff --git a/services/spdm/responder/BUILD.bazel b/services/spdm/responder/BUILD.bazel new file mode 100644 index 0000000..35e35f8 --- /dev/null +++ b/services/spdm/responder/BUILD.bazel
@@ -0,0 +1,14 @@ +# Licensed under the Apache-2.0 license + +load("@rules_rust//rust:defs.bzl", "rust_library") + +rust_library( + name = "spdm_responder_lib", + srcs = glob(["src/**/*.rs"]), + crate_name = "spdm_responder", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "@oot_crates_no_std//:spdm-lib", + ], +)
diff --git a/services/spdm/responder/Cargo.toml b/services/spdm/responder/Cargo.toml new file mode 100644 index 0000000..8080708 --- /dev/null +++ b/services/spdm/responder/Cargo.toml
@@ -0,0 +1,11 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "spdm-responder" +version = "0.1.0" +edition = "2021" +description = "SPDM responder service for OpenPRoT" +license = "Apache-2.0" + +[dependencies] +spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" }
diff --git a/services/spdm/responder/README.md b/services/spdm/responder/README.md new file mode 100644 index 0000000..f21e695 --- /dev/null +++ b/services/spdm/responder/README.md
@@ -0,0 +1,5 @@ +# SPDM Responder Service + +SPDM responder service for OpenPRoT - wraps spdm-lib SpdmContext for simplified message processing. + +See source code documentation for detailed usage.
diff --git a/services/spdm/responder/src/lib.rs b/services/spdm/responder/src/lib.rs new file mode 100644 index 0000000..09f3806 --- /dev/null +++ b/services/spdm/responder/src/lib.rs
@@ -0,0 +1,315 @@ +// Licensed under the Apache-2.0 license + +//! SPDM Responder Service +//! +//! This service implements the SPDM responder (server) role, which responds +//! to attestation and measurement requests from SPDM requesters. +//! +//! ## Overview +//! +//! The SPDM responder receives requests and provides: +//! - Version and capability information +//! - Certificate chains for authentication +//! - Device measurements +//! - Challenge-response attestation +//! +//! ## Architecture +//! +//! ```text +//! ┌─────────────────────────┐ +//! │ MCTP Transport │ +//! │ (incoming messages) │ +//! └───────────┬─────────────┘ +//! │ SPDM requests +//! ▼ +//! ┌─────────────────────────┐ +//! │ SPDM Responder │◄── This crate +//! │ (SpdmContext wrapper) │ +//! └───────────┬─────────────┘ +//! │ +//! ▼ +//! ┌─────────────────────────────────────┐ +//! │ Platform Implementations │ +//! │ - CertStore (certificates) │ +//! │ - Hash (SHA-384) │ +//! │ - RNG (random numbers) │ +//! │ - Evidence (measurements) │ +//! │ - Transport (MCTP) │ +//! └─────────────────────────────────────┘ +//! ``` +//! +//! ## Usage +//! +//! ```rust,no_run +//! use spdm_responder::SpdmResponder; +//! +//! // Create platform implementations +//! let cert_store = Ast1060CertStore::new(crypto_handle); +//! let hash = SpdmCryptoHash::new(crypto_handle); +//! let rng = SpdmCryptoRng::new(crypto_handle); +//! let evidence = Ast1060Evidence::new(); +//! let transport = MctpSpdmTransport::new(mctp_client); +//! +//! // Create responder +//! let mut responder = SpdmResponder::new( +//! transport, +//! cert_store, +//! hash, +//! rng, +//! evidence, +//! )?; +//! +//! // Process messages in loop +//! loop { +//! responder.process_message()?; +//! } +//! ``` + +#![no_std] + +use spdm_lib::cert_store::SpdmCertStore; +use spdm_lib::codec::MessageBuf; +use spdm_lib::context::SpdmContext; +use spdm_lib::error::SpdmError; +use spdm_lib::platform::evidence::SpdmEvidence; +use spdm_lib::platform::hash::SpdmHash; +use spdm_lib::platform::rng::SpdmRng; +use spdm_lib::platform::transport::SpdmTransport; +use spdm_lib::protocol::algorithms::{ + AeadCipherSuite, AlgorithmPriorityTable, BaseAsymAlgo, BaseHashAlgo, DeviceAlgorithms, + DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, + MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, +}; +use spdm_lib::protocol::version::SpdmVersion; + +/// Supported SPDM versions (static to avoid lifetime issues) +static SUPPORTED_VERSIONS: [SpdmVersion; 2] = [SpdmVersion::V12, SpdmVersion::V11]; +use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; + +/// Maximum SPDM message size +const MAX_SPDM_MSG_SIZE: usize = 4096; + +/// SPDM responder result type +pub type ResponderResult<T> = Result<T, ResponderError>; + +/// SPDM responder errors +#[derive(Debug)] +pub enum ResponderError { + /// SPDM protocol error + SpdmError(SpdmError), + /// Message buffer error + BufferError, +} + +impl From<SpdmError> for ResponderError { + fn from(e: SpdmError) -> Self { + ResponderError::SpdmError(e) + } +} + +/// SPDM responder configuration +#[derive(Debug, Clone, Copy)] +pub struct ResponderConfig { + /// CT exponent for timing + pub ct_exponent: u8, + /// Data transfer size + pub data_transfer_size: u32, + /// Maximum SPDM message size + pub max_spdm_msg_size: u32, +} + +impl Default for ResponderConfig { + fn default() -> Self { + Self { + ct_exponent: 0, + data_transfer_size: 1024, + max_spdm_msg_size: MAX_SPDM_MSG_SIZE as u32, + } + } +} + +/// SPDM responder state and configuration. +/// +/// This wraps the spdm-lib `SpdmContext` and provides a simplified interface +/// for processing SPDM messages. +pub struct SpdmResponder<'a> { + context: SpdmContext<'a>, +} + +impl<'a> SpdmResponder<'a> { + /// Create a new SPDM responder with platform implementations. + /// + /// # Arguments + /// + /// * `transport` - Transport layer implementation (e.g., MCTP) + /// * `cert_store` - Certificate store with device certificates + /// * `hash` - Hash implementation for protocol operations + /// * `m1_hash` - Hash implementation for M1 transcript + /// * `l1_hash` - Hash implementation for L1 transcript + /// * `rng` - Random number generator + /// * `evidence` - Evidence provider for measurements + /// * `config` - Optional configuration (uses defaults if None) + /// + /// # Returns + /// + /// A new `SpdmResponder` instance ready to process messages. + pub fn new( + transport: &'a mut dyn SpdmTransport, + cert_store: &'a mut dyn SpdmCertStore, + hash: &'a mut dyn SpdmHash, + m1_hash: &'a mut dyn SpdmHash, + l1_hash: &'a mut dyn SpdmHash, + rng: &'a mut dyn SpdmRng, + evidence: &'a dyn SpdmEvidence, + config: Option<ResponderConfig>, + ) -> ResponderResult<Self> { + let config = config.unwrap_or_default(); + + // Create device capabilities + let capabilities = create_device_capabilities(config); + + // Create local algorithms + let algorithms = create_local_algorithms(); + + // Create SPDM context + let context = SpdmContext::new( + &SUPPORTED_VERSIONS, + transport, + capabilities, + algorithms, + cert_store, + None, // No peer cert store needed for responder + hash, + m1_hash, + l1_hash, + rng, + evidence, + )?; + + Ok(Self { context }) + } + + /// Process a single SPDM message. + /// + /// This method: + /// 1. Receives a request via the transport layer + /// 2. Processes it through the SPDM context + /// 3. Sends the response back via the transport layer + /// + /// # Arguments + /// + /// * `buffer` - Message buffer (must be at least MAX_SPDM_MSG_SIZE bytes) + /// + /// # Returns + /// + /// - `Ok(())` if message processed successfully + /// - `Err(ResponderError)` on error + /// + /// # Note + /// + /// This should be called in a loop to continuously process messages. + /// Transport errors indicate connection closed. + pub fn process_message(&mut self, buffer: &'a mut [u8]) -> ResponderResult<()> { + let mut message_buf = MessageBuf::new(buffer); + self.context.responder_process_message(&mut message_buf)?; + Ok(()) + } + + /// Get reference to the underlying SPDM context. + /// + /// This allows direct access to context state if needed. + pub fn context(&self) -> &SpdmContext<'a> { + &self.context + } + + /// Get mutable reference to the underlying SPDM context. + /// + /// This allows direct manipulation of context state if needed. + pub fn context_mut(&mut self) -> &mut SpdmContext<'a> { + &mut self.context + } +} + +/// Create SPDM device capabilities based on configuration. +fn create_device_capabilities(config: ResponderConfig) -> DeviceCapabilities { + let mut flags_value = 0u32; + + // Certificate capability + flags_value |= 1 << 1; // CERT_CAP + + // Challenge capability + flags_value |= 1 << 2; // CHAL_CAP + + // Measurements capability (with signature) + flags_value |= 2 << 3; // MEAS_CAP (0b10 = measurements with signature) + + // Measurements freshness capability + flags_value |= 1 << 5; // MEAS_FRESH_CAP + + // Chunk capability + flags_value |= 1 << 17; // CHUNK_CAP + + let flags = CapabilityFlags::new(flags_value); + + DeviceCapabilities { + ct_exponent: config.ct_exponent, + flags, + data_transfer_size: config.data_transfer_size, + max_spdm_msg_size: config.max_spdm_msg_size, + include_supported_algorithms: true, + } +} + +/// Create local device algorithms configuration. +/// +/// Configures supported cryptographic algorithms: +/// - Measurement: DMTF specification with SHA-384 +/// - Asymmetric: ECDSA with NIST P-384 +/// - Hash: SHA-384 +fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { + // Measurement specification (DMTF) + let mut measurement_spec = MeasurementSpecification::default(); + measurement_spec.set_dmtf_measurement_spec(1); + + // Measurement hash algorithm (SHA-384) + let mut measurement_hash_algo = MeasurementHashAlgo::default(); + measurement_hash_algo.set_tpm_alg_sha_384(1); + + // Base asymmetric algorithm (ECDSA P-384) + let mut base_asym_algo = BaseAsymAlgo::default(); + base_asym_algo.set_tpm_alg_ecdsa_ecc_nist_p384(1); + + // Base hash algorithm (SHA-384) + let mut base_hash_algo = BaseHashAlgo::default(); + base_hash_algo.set_tpm_alg_sha_384(1); + + let device_algorithms = DeviceAlgorithms { + measurement_spec, + other_param_support: OtherParamSupport::default(), + measurement_hash_algo, + base_asym_algo, + base_hash_algo, + mel_specification: MelSpecification::default(), + dhe_group: DheNamedGroup::default(), + aead_cipher_suite: AeadCipherSuite::default(), + req_base_asym_algo: ReqBaseAsymAlg::default(), + key_schedule: KeySchedule::default(), + }; + + let algorithm_priority_table = AlgorithmPriorityTable { + measurement_specification: None, + opaque_data_format: None, + base_asym_algo: None, + base_hash_algo: None, + mel_specification: None, + dhe_group: None, + aead_cipher_suite: None, + req_base_asym_algo: None, + key_schedule: None, + }; + + LocalDeviceAlgorithms { + device_algorithms, + algorithm_priority_table, + } +}
diff --git a/services/spdm/rng/BUILD.bazel b/services/spdm/rng/BUILD.bazel new file mode 100644 index 0000000..2f93ea1 --- /dev/null +++ b/services/spdm/rng/BUILD.bazel
@@ -0,0 +1,20 @@ +# Licensed under the Apache-2.0 license + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "spdm_rng_lib", + srcs = glob(["src/**/*.rs"]), + crate_name = "openprot_spdm_rng", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//services/crypto/client:crypto_client", + "@oot_crates_no_std//:spdm-lib", + ], +) + +rust_test( + name = "spdm_rng_test", + crate = ":spdm_rng_lib", +)
diff --git a/services/spdm/rng/Cargo.toml b/services/spdm/rng/Cargo.toml new file mode 100644 index 0000000..8731a23 --- /dev/null +++ b/services/spdm/rng/Cargo.toml
@@ -0,0 +1,12 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "openprot-spdm-rng" +version = "0.1.0" +edition = "2021" +description = "SPDM RNG implementation using OpenPRoT crypto service" +license = "Apache-2.0" + +[dependencies] +spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" } +crypto-client = { path = "../../crypto/client" }
diff --git a/services/spdm/rng/README.md b/services/spdm/rng/README.md new file mode 100644 index 0000000..73b4a42 --- /dev/null +++ b/services/spdm/rng/README.md
@@ -0,0 +1,47 @@ +# SPDM RNG + +Random number generator for SPDM protocol operations, implemented via OpenPRoT crypto service. + +## Overview + +This crate implements the `SpdmRng` trait from spdm-lib by delegating to the +centralized crypto service. All randomness is generated using ChaCha20 CSPRNG +seeded from system entropy. + +## Architecture + +``` +SpdmCryptoRng → CryptoClient → IPC → CryptoServer → RustCryptoBackend → ChaCha20Rng +``` + +## Security Model + +- **Entropy Source:** getrandom crate (platform-dependent: hardware RNG, /dev/urandom, etc.) +- **PRNG:** ChaCha20 stream cipher (NIST approved, used in TLS 1.3) +- **Seeding:** Fresh seed from system entropy on each crypto service operation + +## Usage + +```rust +use openprot_spdm_rng::SpdmCryptoRng; +use spdm_lib::platform::rng::SpdmRng; + +let mut rng = SpdmCryptoRng::new(handle::CRYPTO); +let mut challenge = [0u8; 32]; +rng.get_random_bytes(&mut challenge)?; +``` + +## Future Enhancements + +When AST1060 hardware RNG driver is available, only the crypto backend needs updating: +- Modify `RustCryptoBackend::OneShot<GetRandomBytes>` to use hardware RNG +- No changes needed to this crate or any SPDM code + +## Dependencies + +- `spdm-lib` — SPDM protocol library +- `crypto-client` — OpenPRoT crypto service client + +## License + +Apache-2.0
diff --git a/services/spdm/rng/src/lib.rs b/services/spdm/rng/src/lib.rs new file mode 100644 index 0000000..6134f69 --- /dev/null +++ b/services/spdm/rng/src/lib.rs
@@ -0,0 +1,79 @@ +// Licensed under the Apache-2.0 license + +//! SPDM RNG Implementation +//! +//! Provides random number generation for SPDM protocol operations by +//! delegating to the OpenPRoT crypto service via IPC. +//! +//! ## Architecture +//! +//! This crate implements the `SpdmRng` trait from spdm-lib by wrapping +//! the `CryptoClient` and calling its `get_random_bytes()` method, which +//! uses ChaCha20 CSPRNG seeded from system entropy. +//! +//! ## Usage +//! +//! ```rust,no_run +//! use openprot_spdm_rng::SpdmCryptoRng; +//! use spdm_lib::platform::rng::SpdmRng; +//! +//! let mut rng = SpdmCryptoRng::new(crypto_handle); +//! let mut buffer = [0u8; 32]; +//! rng.get_random_bytes(&mut buffer).unwrap(); +//! ``` + +#![no_std] +#![warn(missing_docs)] + +use crypto_client::CryptoClient; +use spdm_lib::platform::rng::{SpdmRng, SpdmRngResult}; + +/// SPDM RNG implementation using OpenPRoT crypto service. +/// +/// This struct wraps a `CryptoClient` handle and delegates all RNG +/// operations to the centralized crypto service via IPC. +pub struct SpdmCryptoRng { + crypto: CryptoClient, +} + +impl SpdmCryptoRng { + /// Create a new SPDM RNG using the crypto service. + /// + /// # Arguments + /// + /// * `crypto_handle` — IPC channel handle for the crypto service + /// (typically from your app's generated handle module, e.g., `handle::CRYPTO`) + pub const fn new(crypto_handle: u32) -> Self { + Self { + crypto: CryptoClient::new(crypto_handle), + } + } +} + +impl SpdmRng for SpdmCryptoRng { + fn get_random_bytes(&mut self, buf: &mut [u8]) -> SpdmRngResult<()> { + self.crypto + .get_random_bytes(buf) + .map_err(|_| spdm_lib::platform::rng::SpdmRngError::InvalidSize) + } + + fn generate_random_number(&mut self, random_number: &mut [u8]) -> SpdmRngResult<()> { + // Both methods are identical in spdm-lib: fill a buffer with random bytes + self.crypto + .get_random_bytes(random_number) + .map_err(|_| spdm_lib::platform::rng::SpdmRngError::InvalidSize) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Note: These tests require a running crypto service, so they're + // integration tests that would run in a QEMU environment + + #[test] + fn test_struct_creation() { + let _rng = SpdmCryptoRng::new(42); + } +}