blob: dc89da4f666b8d85c281d0338ee43fde0b09ae3e [file]
// Licensed under the Apache-2.0 license
//! SPDM Peer Certificate Store - AST1060-EVB Software Implementation
//!
//! **Software-based reference implementation** for storing peer certificates
//! received during SPDM protocol operations. Used by SPDM requester (client)
//! to store and validate certificates from the SPDM responder (server).
//!
//! ## Architecture
//!
//! - **Runtime storage:** Certificates received during protocol execution
//! - **Fixed-size buffers:** Static allocation, no heap required
//! - **Two slots:** Support for up to 2 peer certificate chains
//! - **No_std compatible:** Embedded-friendly design
//!
//! ## Hardware-Backed Implementations
//!
//! This software implementation serves as a reference. Future hardware-backed
//! versions could:
//! - Store peer certificates in secure flash
//! - Use hardware-accelerated certificate validation
//! - Implement certificate revocation checking
//! - Cache validated certificates across reboots
//!
//! ## Usage
//!
//! ```rust,no_run
//! use ast1060_peer_cert_store::Ast1060PeerCertStore;
//! use spdm_lib::cert_store::PeerCertStore;
//!
//! let mut peer_store = Ast1060PeerCertStore::new();
//!
//! // During SPDM GET_DIGESTS response
//! peer_store.set_supported_slots(0b11)?; // Slots 0 and 1
//! peer_store.set_provisioned_slots(0b01)?; // Only slot 0 provisioned
//!
//! // During SPDM GET_CERTIFICATE response (may be fragmented)
//! peer_store.assemble(0, &cert_portion1)?;
//! peer_store.assemble(0, &cert_portion2)?;
//! // ... until complete
//!
//! // Retrieve for verification
//! let cert_chain = peer_store.get_cert_chain(0, hash_algo)?;
//! ```
#![no_std]
use spdm_lib::cert_store::{CertStoreError, CertStoreResult, PeerCertStore, ReassemblyStatus};
use spdm_lib::commands::challenge::MeasurementSummaryHashType;
use spdm_lib::protocol::algorithms::SHA384_HASH_SIZE;
use spdm_lib::protocol::certs::{CertificateInfo, KeyUsageMask};
use spdm_lib::protocol::{BaseHashAlgoType, SpdmCertChainHeader};
/// Maximum certificate chain size (including header)
/// Typical X.509 chains are 1-4KB; using 4KB for safety
const MAX_CERT_CHAIN_SIZE: usize = 4096;
/// Maximum number of peer certificate slots
const MAX_PEER_SLOTS: usize = 2;
/// Per-slot peer certificate storage
#[derive(Clone)]
struct PeerSlot {
/// Complete certificate chain (SpdmCertChainHeader + root_hash + DER certs)
cert_chain: [u8; MAX_CERT_CHAIN_SIZE],
/// Actual length of data in cert_chain buffer
cert_chain_len: usize,
/// Digest from GET_DIGESTS response
digest: [u8; SHA384_HASH_SIZE],
/// Whether digest has been set
digest_valid: bool,
/// Optional KeyPairID
keypair_id: Option<u8>,
/// Optional CertificateInfo
cert_info: Option<CertificateInfo>,
/// Optional KeyUsageMask
key_usage_mask: Option<KeyUsageMask>,
/// MeasurementSummaryHashType requested in CHALLENGE
requested_msh_type: Option<MeasurementSummaryHashType>,
}
impl PeerSlot {
const fn new() -> Self {
Self {
cert_chain: [0u8; MAX_CERT_CHAIN_SIZE],
cert_chain_len: 0,
digest: [0u8; SHA384_HASH_SIZE],
digest_valid: false,
keypair_id: None,
cert_info: None,
key_usage_mask: None,
requested_msh_type: None,
}
}
fn reset(&mut self) {
self.cert_chain_len = 0;
self.digest_valid = false;
self.keypair_id = None;
self.cert_info = None;
self.key_usage_mask = None;
self.requested_msh_type = None;
}
/// Get the root hash from the certificate chain
fn get_root_hash(&self, hash_algo: BaseHashAlgoType) -> Option<&[u8]> {
if self.cert_chain_len == 0 {
return None;
}
// Certificate chain format:
// [SpdmCertChainHeader (4 bytes) | RootHash (hash_size bytes) | DER Certs]
let hash_size = hash_algo.hash_byte_size();
let header_size = core::mem::size_of::<SpdmCertChainHeader>();
let total_header = header_size + hash_size;
if self.cert_chain_len < total_header {
return None;
}
Some(&self.cert_chain[header_size..total_header])
}
/// Get the DER certificate chain (without header and root hash)
fn get_cert_chain(&self, hash_algo: BaseHashAlgoType) -> Option<&[u8]> {
if self.cert_chain_len == 0 {
return None;
}
let hash_size = hash_algo.hash_byte_size();
let header_size = core::mem::size_of::<SpdmCertChainHeader>();
let total_header = header_size + hash_size;
if self.cert_chain_len <= total_header {
return None;
}
Some(&self.cert_chain[total_header..self.cert_chain_len])
}
/// Get the raw certificate chain (with header)
fn get_raw_chain(&self) -> Option<&[u8]> {
if self.cert_chain_len == 0 {
return None;
}
Some(&self.cert_chain[..self.cert_chain_len])
}
}
/// SPDM peer certificate store implementation for AST1060-EVB.
///
/// Stores certificates received from SPDM responders during protocol operations.
/// Uses static allocation with fixed-size buffers.
pub struct Ast1060PeerCertStore {
slots: [PeerSlot; MAX_PEER_SLOTS],
supported_slot_mask: u8,
provisioned_slot_mask: u8,
}
impl Ast1060PeerCertStore {
/// Create a new peer certificate store instance.
pub const fn new() -> Self {
Self {
slots: [PeerSlot::new(), PeerSlot::new()],
supported_slot_mask: 0,
provisioned_slot_mask: 0,
}
}
fn validate_slot_id(&self, slot_id: u8) -> CertStoreResult<usize> {
if (slot_id as usize) >= MAX_PEER_SLOTS {
return Err(CertStoreError::InvalidSlotId(slot_id));
}
Ok(slot_id as usize)
}
}
impl PeerCertStore for Ast1060PeerCertStore {
fn slot_count(&self) -> u8 {
MAX_PEER_SLOTS as u8
}
fn set_supported_slots(&mut self, supported_slot_mask: u8) -> CertStoreResult<()> {
// Only support bits for slots 0 and 1
if supported_slot_mask > 0b11 {
return Err(CertStoreError::PlatformError);
}
self.supported_slot_mask = supported_slot_mask;
Ok(())
}
fn get_supported_slots(&self) -> CertStoreResult<u8> {
Ok(self.supported_slot_mask)
}
fn set_provisioned_slots(&mut self, provisioned_slot_mask: u8) -> CertStoreResult<()> {
// Only support bits for slots 0 and 1
if provisioned_slot_mask > 0b11 {
return Err(CertStoreError::PlatformError);
}
self.provisioned_slot_mask = provisioned_slot_mask;
Ok(())
}
fn get_provisioned_slots(&self) -> CertStoreResult<u8> {
Ok(self.provisioned_slot_mask)
}
fn get_cert_chain(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.get_cert_chain(hash_algo)
.ok_or(CertStoreError::CertReadError)
}
fn set_cert_chain(&mut self, slot_id: u8, cert_chain: &[u8]) -> CertStoreResult<()> {
let idx = self.validate_slot_id(slot_id)?;
if cert_chain.len() > MAX_CERT_CHAIN_SIZE {
return Err(CertStoreError::BufferTooSmall);
}
let slot = &mut self.slots[idx];
slot.cert_chain[..cert_chain.len()].copy_from_slice(cert_chain);
slot.cert_chain_len = cert_chain.len();
Ok(())
}
fn get_digest(&self, slot_id: u8) -> CertStoreResult<&[u8]> {
let idx = self.validate_slot_id(slot_id)?;
if !self.slots[idx].digest_valid {
return Err(CertStoreError::CertReadError);
}
Ok(&self.slots[idx].digest)
}
fn set_digest(&mut self, slot_id: u8, digest: &[u8]) -> CertStoreResult<()> {
let idx = self.validate_slot_id(slot_id)?;
if digest.len() > SHA384_HASH_SIZE {
return Err(CertStoreError::BufferTooSmall);
}
let slot = &mut self.slots[idx];
slot.digest[..digest.len()].copy_from_slice(digest);
slot.digest_valid = true;
Ok(())
}
fn get_keypair(&self, slot_id: u8) -> CertStoreResult<u8> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.keypair_id
.ok_or(CertStoreError::Undefined)
}
fn set_keypair(&mut self, slot_id: u8, keypair: u8) -> CertStoreResult<()> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx].keypair_id = Some(keypair);
Ok(())
}
fn get_cert_info(&self, slot_id: u8) -> CertStoreResult<CertificateInfo> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.cert_info
.ok_or(CertStoreError::Undefined)
}
fn set_cert_info(&mut self, slot_id: u8, cert_info: CertificateInfo) -> CertStoreResult<()> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx].cert_info = Some(cert_info);
Ok(())
}
fn get_key_usage_mask(&self, slot_id: u8) -> CertStoreResult<KeyUsageMask> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.key_usage_mask
.ok_or(CertStoreError::Undefined)
}
fn set_key_usage_mask(
&mut self,
slot_id: u8,
key_usage_mask: KeyUsageMask,
) -> CertStoreResult<()> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx].key_usage_mask = Some(key_usage_mask);
Ok(())
}
fn assemble(
&mut self,
slot_id: u8,
portion: &[u8],
) -> Result<ReassemblyStatus, CertStoreError> {
let idx = self.validate_slot_id(slot_id)?;
let slot = &mut self.slots[idx];
// Check if adding this portion would exceed buffer
if slot.cert_chain_len + portion.len() > MAX_CERT_CHAIN_SIZE {
return Err(CertStoreError::BufferTooSmall);
}
// Append portion to existing data
let start = slot.cert_chain_len;
let end = start + portion.len();
slot.cert_chain[start..end].copy_from_slice(portion);
slot.cert_chain_len = end;
// Return InProgress - caller determines when complete
Ok(ReassemblyStatus::InProgress)
}
fn reset(&mut self, slot_id: u8) {
if let Ok(idx) = self.validate_slot_id(slot_id) {
self.slots[idx].reset();
}
}
fn get_root_hash(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.get_root_hash(hash_algo)
.ok_or(CertStoreError::CertReadError)
}
fn get_raw_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.get_raw_chain()
.ok_or(CertStoreError::CertReadError)
}
fn get_requested_msh_type(&self, slot_id: u8) -> CertStoreResult<MeasurementSummaryHashType> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx]
.requested_msh_type
.clone()
.ok_or(CertStoreError::Undefined)
}
fn set_requested_msh_type(
&mut self,
slot_id: u8,
msh_type: MeasurementSummaryHashType,
) -> CertStoreResult<()> {
let idx = self.validate_slot_id(slot_id)?;
self.slots[idx].requested_msh_type = Some(msh_type);
Ok(())
}
}
impl Default for Ast1060PeerCertStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_count() {
let store = Ast1060PeerCertStore::new();
assert_eq!(store.slot_count(), 2);
}
#[test]
fn test_supported_slots() {
let mut store = Ast1060PeerCertStore::new();
assert_eq!(store.get_supported_slots().unwrap(), 0);
store.set_supported_slots(0b11).unwrap();
assert_eq!(store.get_supported_slots().unwrap(), 0b11);
// Should reject invalid masks
assert!(store.set_supported_slots(0b100).is_err());
}
#[test]
fn test_provisioned_slots() {
let mut store = Ast1060PeerCertStore::new();
assert_eq!(store.get_provisioned_slots().unwrap(), 0);
store.set_provisioned_slots(0b01).unwrap();
assert_eq!(store.get_provisioned_slots().unwrap(), 0b01);
}
#[test]
fn test_cert_chain_storage() {
let mut store = Ast1060PeerCertStore::new();
let test_chain = [0xAA; 100];
store.set_cert_chain(0, &test_chain).unwrap();
// Raw chain should include what we stored
let raw = store.get_raw_chain(0).unwrap();
assert_eq!(raw.len(), 100);
assert_eq!(&raw[..], &test_chain);
}
#[test]
fn test_cert_chain_too_large() {
let mut store = Ast1060PeerCertStore::new();
let huge_chain = [0xBB; MAX_CERT_CHAIN_SIZE + 1];
assert!(matches!(
store.set_cert_chain(0, &huge_chain),
Err(CertStoreError::BufferTooSmall)
));
}
#[test]
fn test_assemble() {
let mut store = Ast1060PeerCertStore::new();
let portion1 = [0xAA; 50];
let portion2 = [0xBB; 50];
// Assemble two portions
let status1 = store.assemble(0, &portion1).unwrap();
assert!(matches!(status1, ReassemblyStatus::InProgress));
let status2 = store.assemble(0, &portion2).unwrap();
assert!(matches!(status2, ReassemblyStatus::InProgress));
// Verify concatenated data
let raw = store.get_raw_chain(0).unwrap();
assert_eq!(raw.len(), 100);
assert_eq!(&raw[..50], &portion1);
assert_eq!(&raw[50..], &portion2);
}
#[test]
fn test_digest_storage() {
let mut store = Ast1060PeerCertStore::new();
let test_digest = [0xCC; SHA384_HASH_SIZE];
// Should fail before digest is set
assert!(store.get_digest(0).is_err());
store.set_digest(0, &test_digest).unwrap();
let digest = store.get_digest(0).unwrap();
assert_eq!(digest, &test_digest);
}
#[test]
fn test_keypair() {
let mut store = Ast1060PeerCertStore::new();
assert!(store.get_keypair(0).is_err());
store.set_keypair(0, 42).unwrap();
assert_eq!(store.get_keypair(0).unwrap(), 42);
}
#[test]
fn test_cert_info() {
let mut store = Ast1060PeerCertStore::new();
assert!(store.get_cert_info(0).is_err());
let info = CertificateInfo(0x12);
store.set_cert_info(0, info).unwrap();
assert_eq!(store.get_cert_info(0).unwrap(), info);
}
#[test]
fn test_key_usage_mask() {
let mut store = Ast1060PeerCertStore::new();
assert!(store.get_key_usage_mask(0).is_err());
let mask = KeyUsageMask::default();
store.set_key_usage_mask(0, mask).unwrap();
assert_eq!(store.get_key_usage_mask(0).unwrap(), mask);
}
#[test]
fn test_reset() {
let mut store = Ast1060PeerCertStore::new();
// Populate slot 0
store.set_cert_chain(0, &[0xAA; 100]).unwrap();
store.set_digest(0, &[0xBB; SHA384_HASH_SIZE]).unwrap();
store.set_keypair(0, 42).unwrap();
// Verify data is there
assert!(store.get_raw_chain(0).is_ok());
assert!(store.get_digest(0).is_ok());
assert!(store.get_keypair(0).is_ok());
// Reset
store.reset(0);
// Verify data is cleared
assert!(store.get_raw_chain(0).is_err());
assert!(store.get_digest(0).is_err());
assert!(store.get_keypair(0).is_err());
}
#[test]
fn test_invalid_slot_id() {
let mut store = Ast1060PeerCertStore::new();
assert!(matches!(
store.set_cert_chain(5, &[0xAA; 10]),
Err(CertStoreError::InvalidSlotId(5))
));
}
#[test]
fn test_requested_msh_type() {
let mut store = Ast1060PeerCertStore::new();
assert!(store.get_requested_msh_type(0).is_err());
let msh_type = MeasurementSummaryHashType::All;
store.set_requested_msh_type(0, msh_type).unwrap();
assert_eq!(store.get_requested_msh_type(0).unwrap(), msh_type);
}
}