[PATCH v6 18/25] lib: rspdm: Support SPDM get_digests
From: alistair23
Date: Mon Nov 09 2026 - 23:30:49 EST
From: Alistair Francis <alistair.francis@xxxxxxx>
Support the GET_DIGESTS SPDM command.
Signed-off-by: Alistair Francis <alistair.francis@xxxxxxx>
Reviewed-by: Jonathan Cameron <jonathan.cameron@xxxxxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 5 +-
lib/rspdm/lib.rs | 4 ++
lib/rspdm/state.rs | 70 +++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 101 ++++++++++++++++++++++++++++++++++++++++-
4 files changed, 177 insertions(+), 3 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index bcc149de77425..b80c37f1ee70c 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -18,10 +18,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;
-#[expect(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;
@@ -82,6 +83,8 @@
#[cfg(CONFIG_CRYPTO_SHA512)]
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 2d5eddd9fdc8c..ee996ecfb4ce1 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -276,6 +276,10 @@ fn from(code: SpdmErrorCode) -> Self {
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 169d0d3fee9bd..a44f98a941cd0 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -33,9 +33,11 @@
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, //
};
#[cfg(CONFIG_CRYPTO_SHA256)]
@@ -60,6 +62,8 @@
use crate::validator::{
GetCapabilitiesReq,
GetCapabilitiesRsp,
+ GetDigestsReq,
+ GetDigestsRsp,
GetVersionReq,
GetVersionRsp,
NegotiateAlgsReq,
@@ -129,6 +133,12 @@ pub(crate) struct SpdmState<T: Transport> {
/// Hash algorithm for measurement blocks. Selected by responder during
/// NEGOTIATE_ALGORITHMS exchange.
pub(crate) meas_hash_alg: u32,
+ /// Bitmask of responder's supported certificate slots. Received during
+ /// GET_DIGESTS exchange (from SPDM 1.3).
+ pub(crate) supported_slots: u8,
+ /// Bitmask of responder's provisioned certificate slots. Received during
+ /// GET_DIGESTS exchange.
+ pub(crate) provisioned_slots: u8,
/// Human-readable name of `base_asym_alg`'s signature encoding. Passed
/// to the crypto subsystem when calling `verify_signature()`.
@@ -139,6 +149,13 @@ pub(crate) struct SpdmState<T: Transport> {
/// Hash transformation for `base_hash_alg` computation.
crypto: Option<Shash>,
+
+ /// 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.
+ pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
+ /// Hash of each slot's certificate chain, as reported by the DIGESTS
+ /// response. Empty `KVec` if a slot is not provisioned.
+ pub(crate) digests: [KVec<u8>; SPDM_SLOTS],
}
impl<T: Transport> SpdmState<T> {
@@ -164,12 +181,24 @@ 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: c"",
sig_len: 0,
crypto: None,
+ certs: [const { KVec::new() }; SPDM_SLOTS],
+ digests: [const { KVec::new() }; SPDM_SLOTS],
}
}
+ /// Hash length of `base_hash_alg` (in bytes). H in SPDM specification.
+ pub(crate) fn hash_len(&self) -> usize {
+ self.crypto
+ .as_ref()
+ .map(|hash| hash.hash_len())
+ .unwrap_or(0)
+ }
+
/// Start a SPDM exchange
///
/// The data in `request_buf` is sent to the device and the response
@@ -230,6 +259,10 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
// Reset all negotiated session state as we are re-authenticating
self.data_transfer_sz = self.transport_sz;
+ for digest in self.digests.as_mut() {
+ digest.clear();
+ }
+
let mut request_buf = request.to_bytes()?;
let response_vec: KVec<u8> = KVec::from_elem(0u8, SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
@@ -432,4 +465,39 @@ 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.header.version = self.version;
+
+ let rsp_sz = core::mem::size_of::<SpdmHeader>() + SPDM_SLOTS * self.hash_len();
+
+ let mut request_buf = request.to_bytes()?;
+
+ let response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let response: GetDigestsRsp = self
+ .spdm_exchange(request_buf.as_mut_slice(), response_vec)?
+ .validate(&*self)?;
+
+ let mut deprovisioned_slots = self.provisioned_slots & !response.header.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);
+ }
+
+ self.provisioned_slots = response.header.param2;
+
+ let supported_slots = if self.version >= SPDM_VER_13 {
+ response.header.param1
+ } else {
+ 0xFF
+ };
+
+ self.supported_slots = supported_slots;
+ self.digests = response.digests;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 335f2f43dc07c..54207527e9f4d 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -12,6 +12,7 @@
SPDM_CAP_SUPPORTED_ALGORITHMS,
SPDM_CTEXPONENT,
SPDM_GET_CAPABILITIES,
+ SPDM_GET_DIGESTS,
SPDM_GET_VERSION,
SPDM_HASH_ALGOS,
SPDM_MEAS_SPEC_DMTF,
@@ -19,9 +20,11 @@
SPDM_MIN_VER,
SPDM_NEGOTIATE_ALGS,
SPDM_REQ_CAPS,
+ SPDM_SLOTS,
SPDM_VER_10,
SPDM_VER_11,
- SPDM_VER_12, //
+ SPDM_VER_12,
+ SPDM_VER_13, //
};
use crate::{
SpdmErrorCode,
@@ -725,3 +728,99 @@ fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<T>) -> Result<Self, Se
})
}
}
+
+pub(crate) struct GetDigestsReq {
+ pub(crate) header: SpdmHeader,
+}
+
+impl GetDigestsReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ self.header.to_bytes()
+ }
+}
+
+impl Default for GetDigestsReq {
+ fn default() -> Self {
+ GetDigestsReq {
+ header: SpdmHeader::new(SPDM_GET_DIGESTS),
+ }
+ }
+}
+
+pub(crate) struct GetDigestsRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) digests: [KVec<u8>; SPDM_SLOTS],
+
+ // KeyPairIDs, added in 1.3
+
+ // CertificateInfo, added in 1.3
+
+ // KeyUsageMask, added in 1.3
+ /// The actual length of the SPDM message. `raw_bytes` might be longer
+ /// as it includes transport specific padding.
+ length: usize,
+
+ /// Entire response, used for the transcript hash.
+ raw_bytes: KVec<u8>,
+}
+
+impl GetDigestsRsp {
+ #[expect(dead_code)]
+ pub(crate) fn as_bytes(&self) -> Result<&[u8], Error> {
+ self.raw_bytes.get(..self.length).ok_or(EPROTO)
+ }
+}
+
+impl<'c, T: Transport> Validate<'c, Untrusted<KVec<u8>>, &'c SpdmState<T>> for GetDigestsRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<T>) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EPROTO)?).validate(context)?;
+
+ if header.code != SPDM_GET_DIGESTS - 0x80 {
+ return Err(EPROTO);
+ }
+
+ if header.version != context.version {
+ dev_err!(&context.dev, "Invalid version response\n");
+ return Err(EPROTO);
+ }
+
+ if header.param2 == 0 {
+ dev_err!(&context.dev, "No certificates provisioned\n");
+ return Err(EPROTO);
+ }
+
+ let mut digests: [KVec<u8>; SPDM_SLOTS] = [const { KVec::new() }; SPDM_SLOTS];
+ let mut offset = mem::size_of::<SpdmHeader>();
+ let mut slot_mask = header.param2;
+
+ while (slot_mask.trailing_zeros() as usize) < SPDM_SLOTS {
+ let slot = slot_mask.trailing_zeros() as usize;
+
+ digests[slot].extend_from_slice(
+ unvalidated
+ .get(offset..(offset + context.hash_len()))
+ .ok_or(EPROTO)?,
+ GFP_KERNEL,
+ )?;
+ offset += context.hash_len();
+
+ slot_mask &= !(1 << slot);
+ }
+
+ if context.version >= SPDM_VER_13 && (header.param2 & !header.param1 != 0) {
+ dev_err!(&context.dev, "Malformed digests response\n");
+ return Err(EPROTO);
+ }
+
+ Ok(GetDigestsRsp {
+ header,
+ digests,
+ length: offset,
+ raw_bytes: unvalidated,
+ })
+ }
+}
--
2.56.0