[PATCH 15/18] lib: rspdm: Support SPDM get_certificate
From: alistair23
Date: Thu May 07 2026 - 23:21:11 EST
From: Alistair Francis <alistair@xxxxxxxxxxxxx>
Support the GET_CERTIFICATE SPDM command.
Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 2 +
lib/rspdm/lib.rs | 11 ++++
lib/rspdm/state.rs | 125 +++++++++++++++++++++++++++++++++++++++++
lib/rspdm/validator.rs | 57 +++++++++++++++++++
4 files changed, 195 insertions(+)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 092205dab74d..302bc0285478 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -114,6 +114,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
+pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
+
// 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 e42cfdd35524..d5aee761003a 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -124,6 +124,17 @@
return e.to_errno() as c_int;
}
+ let mut provisioned_slots = state.provisioned_slots;
+ while (provisioned_slots as usize) > 0 {
+ let slot = provisioned_slots.trailing_zeros() as u8;
+
+ if let Err(e) = state.get_certificate(slot) {
+ return e.to_errno() as c_int;
+ }
+
+ provisioned_slots &= !(1 << slot);
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index bcb1cc955c4c..69b6f67a6ef5 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -55,6 +55,8 @@
use crate::validator::{
GetCapabilitiesReq,
GetCapabilitiesRsp,
+ GetCertificateReq,
+ GetCertificateRsp,
GetDigestsReq,
GetDigestsRsp,
GetVersionReq,
@@ -135,6 +137,14 @@ pub struct SpdmState {
pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
}
+#[repr(C, packed)]
+pub(crate) struct SpdmCertChain {
+ length: u16,
+ _reserved: [u8; 2],
+ root_hash: bindings::__IncompleteArrayField<u8>,
+ certificates: bindings::__IncompleteArrayField<u8>,
+}
+
impl SpdmState {
pub(crate) fn new(
dev: *mut bindings::device,
@@ -661,4 +671,119 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ fn get_cert_exchange(
+ &mut self,
+ request_buf: &mut [u8],
+ response_vec: &mut KVec<u8>,
+ rsp_sz: usize,
+ ) -> Result<&mut GetCertificateRsp, Error> {
+ // SAFETY: `response_vec` is rsp_sz length, initialised, aligned
+ // and won't be mutated
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {
+ pr_err!("Truncated certificate response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
+
+ if rc
+ < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32
+ {
+ pr_err!("Truncated certificate response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ Ok(response)
+ }
+
+ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
+ let mut request = GetCertificateReq::default();
+ request.version = self.version;
+ request.param1 = slot;
+
+ let req_sz = core::mem::size_of::<GetCertificateReq>();
+ let rsp_sz = (core::mem::size_of::<GetCertificateRsp>() as u32 + u16::MAX as u32)
+ .min(self.transport_sz) as usize;
+
+ request.offset = 0;
+ request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()) as u16;
+
+ // 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::with_capacity(rsp_sz, GFP_KERNEL)?;
+
+ let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+ let total_cert_len =
+ ((response.portion_length + response.remainder_length) & 0xFFFF) as usize;
+
+ let mut certs_buf: KVec<u8> = KVec::new();
+
+ certs_buf.extend_from_slice(
+ &response_vec[8..(8 + response.portion_length as usize)],
+ GFP_KERNEL,
+ )?;
+
+ let mut offset: usize = response.portion_length as usize;
+ let mut remainder_length = response.remainder_length as usize;
+
+ while remainder_length > 0 {
+ request.offset = offset.to_le() as u16;
+ request.length =
+ ((remainder_length.min(rsp_sz - core::mem::size_of::<GetCertificateRsp>())) as u16)
+ .to_le();
+
+ let request_buf =
+ unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+ if response.portion_length == 0
+ || (response.param1 & 0xF) != slot
+ || offset as u16 + response.portion_length + response.remainder_length
+ != total_cert_len as u16
+ {
+ pr_err!("Malformed certificate response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ certs_buf.extend_from_slice(
+ &response_vec[8..(8 + response.portion_length as usize)],
+ GFP_KERNEL,
+ )?;
+ offset += response.portion_length as usize;
+ remainder_length = response.remainder_length as usize;
+ }
+
+ let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
+
+ let ptr = certs_buf.as_mut_ptr();
+ // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice
+ let ptr = ptr.cast::<SpdmCertChain>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let certs: &mut SpdmCertChain = unsafe { &mut *ptr };
+
+ if total_cert_len < header_length
+ || total_cert_len as u16 != certs.length
+ || total_cert_len != certs_buf.len()
+ {
+ pr_err!("Malformed certificate chain in slot {slot}\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ self.certs[slot as usize].clear();
+ self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 1e5ee8a7582b..8b44a056b335 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_CERTIFICATE,
SPDM_GET_DIGESTS,
SPDM_GET_VERSION,
SPDM_HASH_ALGOS,
@@ -403,3 +404,59 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct GetCertificateReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) offset: u16,
+ pub(crate) length: u16,
+}
+
+impl Default for GetCertificateReq {
+ fn default() -> Self {
+ GetCertificateReq {
+ version: 0,
+ code: SPDM_GET_CERTIFICATE,
+ param1: 0,
+ param2: 0,
+ offset: 0,
+ length: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetCertificateRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) portion_length: u16,
+ pub(crate) remainder_length: u16,
+
+ pub(crate) cert_chain: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCertificateRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<GetCertificateRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetCertificateRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetCertificateRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetCertificateRsp = unsafe { &mut *ptr };
+
+ Ok(rsp)
+ }
+}
--
2.52.0