[PATCH v5 22/23] lib: rspdm: Support SPDM certificate validation
From: alistair23
Date: Tue Oct 27 2026 - 01:34:24 EST
From: Alistair Francis <alistair.francis@xxxxxxx>
Support validating the SPDM certificate chain. This only performs basic
sanity checks on the chain before we continue on. This does not ensure
that the root CA is trusted, we leave that for userspace to check and
enforce. Instead we just make sure that the chain is correct, uses
supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
Signed-off-by: Alistair Francis <alistair.francis@xxxxxxx>
---
lib/rspdm/lib.rs | 12 ++++++++++
lib/rspdm/state.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 66 insertions(+), 2 deletions(-)
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 6b0be0a0303fa..5277fc8c56fb8 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -289,6 +289,18 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
provisioned_slots &= !(1 << slot);
}
+ 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.validate_cert_chain(slot) {
+ pr_err!("Certificate in slot {slot} failed to verify: {e:?}\n");
+ 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 7799d4ac70c3f..aa36d4ce7b41a 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -14,10 +14,15 @@
Bound,
Device, //
},
- error::Error,
+ error::{
+ to_result,
+ Error, //
+ },
hash::Shash,
+ public_key::PublicKey,
str::CStr,
validate::Untrusted,
+ x509::Certificate,
};
use crate::consts::{
@@ -97,7 +102,6 @@ fn exchange(
///
/// `T` is data and an exchange function owned by the caller,
/// so the borrow checker enforces that the transport data outlives `SpdmState`.
-#[expect(dead_code)]
pub(crate) struct SpdmState<'a, T: Transport> {
/// Responder device. Used for error reporting and passed to `transport`.
pub(crate) dev: &'a Device<Bound>,
@@ -148,6 +152,9 @@ pub(crate) struct SpdmState<'a, T: Transport> {
/// 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],
+ /// Public key portion of leaf certificate against which to check
+ /// responder's signatures.
+ pub(crate) leaf_key: Option<PublicKey>,
}
impl<'a, T: Transport> SpdmState<'a, T> {
@@ -173,6 +180,7 @@ pub(crate) fn new(
sig_len: 0,
crypto: None,
certs: [const { KVec::new() }; SPDM_SLOTS],
+ leaf_key: None,
}
}
@@ -240,6 +248,9 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
request.header.version = SPDM_MIN_VER;
self.version = SPDM_MIN_VER;
+ // Clear the leaf cert as we are re-authenticating
+ self.leaf_key = None;
+
let mut request_buf = request.to_bytes()?;
let response_vec: KVec<u8> = KVec::from_elem(0u8, SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
@@ -551,4 +562,45 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
Ok(())
}
+
+ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
+ let cert_chain_buf = &self.certs[slot as usize];
+ let cert_chain_len = cert_chain_buf.len();
+ // We skip over the RootHash
+ let mut offset = SPDM_CERT_CHAIN_HDR_SZ + self.hash_len();
+
+ if offset >= cert_chain_len {
+ return Err(EPROTO);
+ }
+
+ let mut prev_cert: Option<Certificate> = None;
+
+ while offset < cert_chain_len {
+ let (cert, cert_len) = Certificate::parse(&cert_chain_buf[offset..])?;
+ cert.check_accepted()?;
+
+ if let Some(prev) = &prev_cert {
+ prev.verify_signed(&cert)?;
+ }
+
+ let _ = prev_cert.replace(cert);
+ offset += cert_len;
+ }
+
+ if let Some(mut cert) = prev_cert.take() {
+ if let Some(validate) = self.validate {
+ // SAFETY: Call the `validate` function provided.
+ let rc = unsafe { validate(self.dev.as_raw(), slot, cert.raw()) };
+ to_result(rc)?;
+ }
+
+ // The leaf key is the same for all slots (MULT_KEY_CAP == 0),
+ // so just store the first one.
+ if self.leaf_key.is_none() {
+ self.leaf_key = cert.take_public_key();
+ }
+ }
+
+ Ok(())
+ }
}
--
2.56.0