[PATCH v2 18/21] lib: rspdm: Support SPDM get_certificate
From: alistair23
Date: Tue Jun 23 2026 - 01:03:33 EST
From: Alistair Francis <alistair@xxxxxxxxxxxxx>
Support the GET_CERTIFICATE SPDM command.
The kernel will send a GET_CERTIFICATE request to the the responder and
then iterate over all of the certificates returned.
Certificate validation happens in the next commit.
Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 2 +
lib/rspdm/lib.rs | 15 +++++
lib/rspdm/state.rs | 145 +++++++++++++++++++++++++++++++++++++++++
lib/rspdm/validator.rs | 56 ++++++++++++++++
4 files changed, 218 insertions(+)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index c4d9521866af..2fbc4ab41869 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -146,6 +146,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 bda5f91ca13c..488203be821d 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -114,6 +114,21 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
return e.to_errno() as c_int;
}
+ if state.provisioned_slots == 0 {
+ return -(bindings::EIO 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);
+ }
+
-(EPROTONOSUPPORT as i32)
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 055f37289c8b..9138df30e138 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -53,6 +53,8 @@
use crate::validator::{
GetCapabilitiesReq,
GetCapabilitiesRsp,
+ GetCertificateReq,
+ GetCertificateRsp,
GetDigestsReq,
GetDigestsRsp,
GetVersionReq,
@@ -157,6 +159,17 @@ fn drop(&mut self) {
}
}
+#[repr(C, packed)]
+pub(crate) struct SpdmCertChain {
+ // `length` is a u16 (with 2 bytes reserved) for SPDM versions 1.3
+ // and lower and u32 for 1.4. We don't currently support `LargeOffset`
+ // and `LargeLength`, so let's pretend this is always a u16
+ length: u16,
+ _reserved: [u8; 2],
+ root_hash: bindings::__IncompleteArrayField<u8>,
+ certificates: bindings::__IncompleteArrayField<u8>,
+}
+
impl SpdmState<'_> {
pub(crate) fn new(
dev: *mut bindings::device,
@@ -686,4 +699,136 @@ 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>,
+ ) -> Result<&GetCertificateRsp, Error> {
+ 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 < core::mem::size_of::<GetCertificateRsp>() {
+ pr_err!("Truncated certificate response\n");
+ return Err(EIO);
+ }
+ if len > response_vec.len() {
+ pr_err!("Overflowed get certificate response\n");
+ return Err(EIO);
+ }
+ response_vec.truncate(len);
+
+ let response: &GetCertificateRsp = Untrusted::new(response_vec.as_slice()).validate()?;
+
+ if len
+ < core::mem::size_of::<GetCertificateRsp>()
+ + u16::from_le(response.portion_length) as usize
+ {
+ pr_err!("Truncated certificate response\n");
+ return Err(EIO);
+ }
+
+ 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).to_le();
+
+ // 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 response = self.get_cert_exchange(request_buf, &mut response_vec)?;
+
+ if response.param1 != slot {
+ pr_err!("Invalid slot response\n");
+ return Err(EPROTO);
+ }
+
+ let total_cert_len = u16::from_le(response.portion_length) as usize
+ + u16::from_le(response.remainder_length) as usize;
+
+ let mut certs_buf: KVec<u8> = KVec::new();
+
+ certs_buf.extend_from_slice(
+ &response_vec[8..(8 + u16::from_le(response.portion_length) as usize)],
+ GFP_KERNEL,
+ )?;
+
+ let mut offset: u16 = u16::from_le(response.portion_length);
+ let mut remainder_length = u16::from_le(response.remainder_length) as usize;
+
+ while remainder_length > 0 {
+ request.offset = offset.to_le();
+ 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) };
+
+ response_vec.resize(
+ request.length as usize + core::mem::size_of::<GetCertificateRsp>(),
+ 0,
+ GFP_KERNEL,
+ )?;
+
+ let response = self.get_cert_exchange(request_buf, &mut response_vec)?;
+
+ if u16::from_le(response.portion_length) == 0
+ || (response.param1 & 0xF) != slot
+ || offset as usize
+ + u16::from_le(response.portion_length) as usize
+ + u16::from_le(response.remainder_length) as usize
+ != total_cert_len
+ {
+ pr_err!("Malformed certificate response\n");
+ return Err(EPROTO);
+ }
+
+ certs_buf.extend_from_slice(
+ &response_vec[8..(8 + u16::from_le(response.portion_length) as usize)],
+ GFP_KERNEL,
+ )?;
+ offset += u16::from_le(response.portion_length);
+ remainder_length = u16::from_le(response.remainder_length) as usize;
+ }
+
+ let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
+
+ if total_cert_len < header_length as usize || total_cert_len != certs_buf.len() {
+ pr_err!("Malformed certificate chain in slot {slot}\n");
+ return Err(EPROTO);
+ }
+
+ let cert_chain_length = {
+ let ptr = certs_buf.as_ptr();
+ // SAFETY: `SpdmCertChain` is repr(C) and packed. We just
+ // checked the length above 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: &SpdmCertChain = unsafe { &*ptr };
+ u16::from_le(certs.length) as usize
+ };
+
+ if total_cert_len != cert_chain_length {
+ pr_err!("Malformed certificate chain in slot {slot}\n");
+ return Err(EPROTO);
+ }
+
+ 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 b3d5cab6a9ce..4e78683d7b22 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,
@@ -426,3 +427,58 @@ fn validate(unvalidated: &[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<Untrusted<&[u8]>> for &GetCertificateRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &[u8]) -> Result<Self, Self::Err> {
+ if unvalidated.len() < mem::size_of::<GetCertificateRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = unvalidated.as_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: &GetCertificateRsp = unsafe { &*ptr };
+
+ Ok(rsp)
+ }
+}
--
2.54.0