[PATCH v2 17/21] lib: rspdm: Support SPDM get_digests

From: alistair23

Date: Tue Jun 23 2026 - 01:01:54 EST


From: Alistair Francis <alistair@xxxxxxxxxxxxx>

Support the GET_DIGESTS SPDM command.

Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 7 ++--
lib/rspdm/lib.rs | 4 +++
lib/rspdm/state.rs | 80 +++++++++++++++++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 53 ++++++++++++++++++++++++++++
4 files changed, 141 insertions(+), 3 deletions(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index e222821bad5d..c4d9521866af 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -19,10 +19,11 @@
pub(crate) const SPDM_VER_10: u8 = 0x10;
pub(crate) const SPDM_VER_11: u8 = 0x11;
pub(crate) const SPDM_VER_12: u8 = 0x12;
-#[allow(dead_code)]
pub(crate) const SPDM_VER_13: u8 = 0x13;
pub(crate) const SPDM_VER_14: u8 = 0x14;

+pub(crate) const SPDM_SLOTS: usize = 8;
+
pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_14;

@@ -106,7 +107,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
mem::size_of::<GetVersionRsp>() + (u8::MAX as usize) * mem::size_of::<u16>();

pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
-pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42; // SPDM 1.2.0 margin no 226

// SPDM cryptographic timeout of this implementation:
// Assume calculations may take up to 1 sec on a busy machine, which equals
@@ -143,6 +144,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_HASH_SHA_384: u32 = bit_u32(1);
pub(crate) const SPDM_HASH_SHA_512: u32 = bit_u32(2);

+pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
+
// If the crypto support isn't enabled don't offer the algorithms
// to the responder
#[cfg(CONFIG_CRYPTO_RSA)]
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index d418d15e4c70..bda5f91ca13c 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -110,6 +110,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
return e.to_errno() as c_int;
}

+ if let Err(e) = state.get_digests() {
+ return e.to_errno() as c_int;
+ }
+
-(EPROTONOSUPPORT as i32)
}

diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index e4eb009a977c..055f37289c8b 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -44,13 +44,17 @@
SPDM_OPAQUE_DATA_FMT_GENERAL,
SPDM_REQ,
SPDM_RSP_MIN_CAPS,
+ SPDM_SLOTS,
SPDM_VER_10,
SPDM_VER_11,
- SPDM_VER_12, //
+ SPDM_VER_12,
+ SPDM_VER_13, //
};
use crate::validator::{
GetCapabilitiesReq,
GetCapabilitiesRsp,
+ GetDigestsReq,
+ GetDigestsRsp,
GetVersionReq,
GetVersionRsp,
NegotiateAlgsReq,
@@ -86,6 +90,10 @@
/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
/// @meas_hash_alg: Hash algorithm for measurement blocks.
/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @supported_slots: Bitmask of responder's supported certificate slots.
+/// Received during GET_DIGESTS exchange (from SPDM 1.3).
+/// @provisioned_slots: Bitmask of responder's provisioned certificate slots.
+/// Received during GET_DIGESTS exchange.
/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
/// Passed to crypto subsystem when calling verify_signature().
/// @sig_len: Signature length of @base_asym_alg (in bytes).
@@ -97,6 +105,8 @@
/// @desc: Synchronous hash context for @base_hash_alg computation.
/// @hash_len: Hash length of @base_hash_alg (in bytes).
/// H in SPDM specification.
+/// @certs: Certificate chain in each of the 8 slots. Empty KVec if a slot is
+/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
#[expect(dead_code)]
pub(crate) struct SpdmState<'a> {
pub(crate) dev: *mut bindings::device,
@@ -111,6 +121,8 @@ pub(crate) struct SpdmState<'a> {
pub(crate) base_asym_alg: u32,
pub(crate) base_hash_alg: u32,
pub(crate) meas_hash_alg: u32,
+ pub(crate) supported_slots: u8,
+ pub(crate) provisioned_slots: u8,

/* Signature algorithm */
base_asym_enc: &'a CStr,
@@ -121,6 +133,9 @@ pub(crate) struct SpdmState<'a> {
pub(crate) shash: *mut bindings::crypto_shash,
pub(crate) desc: Option<&'a mut bindings::shash_desc>,
pub(crate) hash_len: usize,
+
+ // Certificates
+ pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
}

impl Drop for SpdmState<'_> {
@@ -161,12 +176,15 @@ pub(crate) fn new(
base_asym_alg: 0,
base_hash_alg: 0,
meas_hash_alg: 0,
+ supported_slots: 0,
+ provisioned_slots: 0,
base_asym_enc: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
sig_len: 0,
base_hash_alg_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
shash: core::ptr::null_mut(),
desc: None,
hash_len: 0,
+ certs: [const { KVec::new() }; SPDM_SLOTS],
}
}

@@ -608,4 +626,64 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {

Ok(())
}
+
+ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
+ let mut request = GetDigestsReq::default();
+ request.version = self.version;
+
+ let req_sz = core::mem::size_of::<GetDigestsReq>();
+ let rsp_sz = core::mem::size_of::<GetDigestsRsp>() + SPDM_SLOTS * self.hash_len;
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let len = self.spdm_exchange(request_buf, response_vec.as_mut_slice())? as usize;
+
+ // The transport must report a length within the buffer we provided.
+ if len > response_vec.len() {
+ pr_err!("Overflowed digests response\n");
+ return Err(EIO);
+ }
+ response_vec.truncate(len);
+
+ let response: &GetDigestsRsp = Untrusted::new(response_vec.as_slice()).validate()?;
+
+ if len
+ < core::mem::size_of::<GetDigestsRsp>()
+ + response.param2.count_ones() as usize * self.hash_len
+ {
+ pr_err!("Overflowed digests response\n");
+ return Err(EIO);
+ }
+
+ let mut deprovisioned_slots = self.provisioned_slots & !response.param2;
+ while (deprovisioned_slots.trailing_zeros() as usize) < SPDM_SLOTS {
+ let slot = deprovisioned_slots.trailing_zeros() as usize;
+ self.certs[slot].clear();
+ deprovisioned_slots &= !(1 << slot);
+ }
+
+ if self.version >= SPDM_VER_13 && (response.param2 & !response.param1 != 0) {
+ pr_err!("Malformed digests response\n");
+ return Err(EPROTO);
+ }
+
+ self.provisioned_slots = response.param2;
+ if self.provisioned_slots == 0 {
+ pr_err!("No certificates provisioned\n");
+ return Err(EPROTO);
+ }
+
+ let supported_slots = if self.version >= SPDM_VER_13 {
+ response.param1
+ } else {
+ 0xFF
+ };
+
+ self.supported_slots = supported_slots;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index c53828376fca..b3d5cab6a9ce 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -30,6 +30,7 @@
SPDM_ASYM_ALGOS,
SPDM_CTEXPONENT,
SPDM_GET_CAPABILITIES,
+ SPDM_GET_DIGESTS,
SPDM_GET_VERSION,
SPDM_HASH_ALGOS,
SPDM_MEAS_SPEC_DMTF,
@@ -373,3 +374,55 @@ fn validate(unvalidated: &[u8]) -> Result<Self, Self::Err> {
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct GetDigestsReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+}
+
+impl Default for GetDigestsReq {
+ fn default() -> Self {
+ GetDigestsReq {
+ version: 0,
+ code: SPDM_GET_DIGESTS,
+ param1: 0,
+ param2: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetDigestsRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) digests: __IncompleteArrayField<u8>,
+ // KeyPairIDs, added in 1.3
+
+ // CertificateInfo, added in 1.3
+
+ // KeyUsageMask, added in 1.3
+}
+
+impl Validate<Untrusted<&[u8]>> for &GetDigestsRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &[u8]) -> Result<Self, Self::Err> {
+ if unvalidated.len() < mem::size_of::<GetDigestsRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = unvalidated.as_ptr();
+ // CAST: `GetDigestsRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetDigestsRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &GetDigestsRsp = unsafe { &*ptr };
+
+ Ok(rsp)
+ }
+}
--
2.54.0