From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49C033AD538 for ; Tue, 1 Sep 2026 01:06:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.44 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788224808; cv=none; b=Gr4IdCEp1vKOPHCrenZtHDdiS0ZugUvIiodVDZ2TnobJVdHtpliuSpB5hE89c/q7qS3PN5TSbvvtjAh/lkd2iHlTN75woD2tCx/npiXifYR7aymn3WKSm0vzajt8LbIE0knQSUE24Yr+srbaoTjDfj4Gua/npFxp65uXCy6D6oQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788224808; c=relaxed/simple; bh=J2T7JILoVByS1MCnyW03kxxqDPc+zD1Y62mEstcjhGs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Y1p6bVmn900q4Re/K80+OCjvz/ES40GXplIF+8mez0CUoq3uIVWc4BspalGrqHZ83apzjtiww9OWm6bXnlUi45IYUvyRC4bxjioXIvieFwbdtqyHji6ULuezwB0cUhGgVrr08ROw9OQcknlViQ8EjL487Ag0k1/HdZnb6faf278= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AKD2k6AN; arc=none smtp.client-ip=209.85.216.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AKD2k6AN" Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-398b3c37877so2969702a91.0 for ; Mon, 31 Aug 2026 18:06:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788224805; x=1788829605; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6+g1amS8TRdiwzXTKVJFNhTGQRsJ1L567PEixpjliHU=; b=AKD2k6ANVeCaqQk72UsRW/MXsWjkC9sv3DG5oxLDONtGMacee/UJHIQ7666bdKDVB/ yot9DhVmSutCQlxYl/cq3hmQ+gZ93xfjsC7dnejV2Vu1cE1s2uRFkuyni6Obum5BGfRt M4yNCzwvkUCAqnggj2VGgd+4+HcbTCRPRw9uYvFIXQwoZFOf0pKvoCU480RAV8n599FK 5CCy/TrNO5g6OKK6haz6RfKgjGzsoW0BCmx/FAB0wbGZL5iSxaRwDxXHFwaXuvnWNBU4 cOLs2rwmOenWUyMxlZNkdC2CKXHurNZw05s2ENmGPvZJZahqsSOTzSpcxftRZSph/PG3 /0PQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788224805; x=1788829605; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=6+g1amS8TRdiwzXTKVJFNhTGQRsJ1L567PEixpjliHU=; b=QSvgQNZt+rtk8dpUQojpdetASoVJOYJFj6Z7KLxYhXtIpX9UR0U0yI1je5M7nHu9GO BFnlrS3MnA5e/v+9t2fn1wtERkncKytxr/b+TOtCqv04Kai6ZTlRSGVBkWjc8XHNoHze EMT3Pr2UruQzs5cHsA1NAPWaz6vV9Hxdg8XPoJNv/HO8ZhYQW5HAw14eecrEh32ZfhlK f3/1L8udaJXXziGrcL49KI94BhYxxRNTxPj3Wq80IPAzBNbrDDYvJ1wJVL3pIYicHuYx nuwdMkYl+kF12yKn1c92v5dYqM0+/6RTugiGfGDeZTm7eBxvHOQfwLcc83kE74dPritV wZqw== X-Forwarded-Encrypted: i=1; AKwUvByYBicXZSrUV8I5b/zc0S0FWHc3Hu0oeZn+FvuJwenuwVz5CwtqSQdWLG7CCSbCo+03stL/q6BU9NISJHY5fg==@vger.kernel.org X-Gm-Message-State: AFuF++nrBwTL+3NvxzZnnCk+f4En9fdnMn3dprV+5tkCEyCCU43I8cS1 Reb4Fl8EdaTXQe7f54kG3VvOCuBnwXECtCViPQgfluYZydZcDsX+7gCP X-Gm-Gg: AYBFou0v9LoXEO18m2+dac2+3hhY4KRmDr8Ywt7xZINey+memgmSSBzxMoN6B77ULKs xM0hSgzOOG/7o/+bFQ6QrF8SYvjNzg1615PNyEMSqArHy0VXRnoTGxXdWZqzctwh0nJKq13Z2Op dWFWPA1D5Ae892QVf1a53ZEe1YY3GHoz3uz+IkWeFTMUT5hbQc6wmXxhaCqaxd44uicWE9jjJYd xEyeT/ymPV8NSAgMh7+witgqbdqnLgXMlYr9GvOUIL1bjLgTv/EVY4D0OC0wbrO7BhIFX0wHK2X EngCEZVHPf4Ee4BFO9Zw02bLk9MFbtGov60dqLNt2zR70f5bt/uVYhOS9ipG2ja81Tx7mrgTnEi zHrLjfU09WP3Lmh/yUoxmVZXI8bKgujlt4dqfOJCHOxgC7Hif4HGJBMxza818BDkm8qmmQ/iwA/ nBFgJT2P+gSSItf5FpfDQaTEl45ADd1d1VOiEsXXn2wwsRtW3JDqEatlmAizAAaq9cA5Uj80Fkc Cmdvg== X-Received: by 2002:a17:90b:5544:b0:398:bd37:6a49 with SMTP id 98e67ed59e1d1-398bd376c65mr24254913a91.12.1788224805187; Mon, 31 Aug 2026 18:06:45 -0700 (PDT) Received: from toolbx.alistair23.me ([2403:581e:fdf9:0:13b2:851f:d9cb:44c5]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3990d49e9e0sm2372024a91.11.2026.08.31.18.06.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 18:06:44 -0700 (PDT) From: alistair23@gmail.com X-Google-Original-From: alistair.francis@wdc.com To: linux-pci@vger.kernel.org, Jonathan.Cameron@huawei.com, djbw@kernel.org, rust-for-linux@vger.kernel.org, lukas@wunner.de, alistair@alistair23.me, jic23@kernel.org, linux-cxl@vger.kernel.org, bhelgaas@google.com, akpm@linux-foundation.org, linux-kernel@vger.kernel.org Cc: gary@garyguo.net, ojeda@kernel.org, benno.lossin@proton.me, a.hindborg@kernel.org, wilfred.mallawa@wdc.com, tmgross@umich.edu, alistair23@gmail.com, boqun.feng@gmail.com, bjorn3_gh@protonmail.com, alex.gaynor@gmail.com, aliceryhl@google.com Subject: [PATCH v3 19/21] lib: rspdm: Support SPDM certificate validation Date: Tue, 1 Sep 2026 11:03:45 +1000 Message-ID: <20260901010347.2614656-20-alistair.francis@wdc.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260901010347.2614656-1-alistair.francis@wdc.com> References: <20260901010347.2614656-1-alistair.francis@wdc.com> Precedence: bulk X-Mailing-List: rust-for-linux@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Alistair Francis 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 --- lib/rspdm/lib.rs | 12 +++ lib/rspdm/state.rs | 146 +++++++++++++++++++++++++++++++- rust/bindings/bindings_helper.h | 2 + 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs index 488203be821d..fa5513e8bd4e 100644 --- a/lib/rspdm/lib.rs +++ b/lib/rspdm/lib.rs @@ -129,6 +129,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 1e8a4402e634..fc7df9dd7b97 100644 --- a/lib/rspdm/state.rs +++ b/lib/rspdm/state.rs @@ -109,7 +109,8 @@ /// 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)] +/// @leaf_key: Public key portion of leaf certificate against which to check +/// responder's signatures. pub(crate) struct SpdmState<'a> { pub(crate) dev: *mut bindings::device, pub(crate) transport: bindings::spdm_transport, @@ -138,10 +139,20 @@ pub(crate) struct SpdmState<'a> { // Certificates pub(crate) certs: [KVec; SPDM_SLOTS], + pub(crate) leaf_key: Option<*mut bindings::public_key>, } impl Drop for SpdmState<'_> { fn drop(&mut self) { + if let Some(leaf_key) = self.leaf_key.take() { + // SAFETY: `leaf_key` was extracted from a x509 certificate + // in `validate_cert_chain()` so it is valid to pass to + // `public_key_free()`. + unsafe { + bindings::public_key_free(leaf_key); + } + } + if let Some(desc) = self.desc.take() { // SAFETY: `self.shash` is a valid handle let desc_len = core::mem::size_of::() @@ -200,6 +211,7 @@ pub(crate) fn new( desc: None, hash_len: 0, certs: [const { KVec::new() }; SPDM_SLOTS], + leaf_key: None, } } @@ -846,4 +858,136 @@ 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 header_len = 4 + self.hash_len; + + let mut offset = header_len; + let mut prev_cert: Option<*mut bindings::x509_certificate> = None; + + if offset >= cert_chain_len { + return Err(EPROTO); + } + + while offset < cert_chain_len { + // SAFETY: `cert_chain_buf[offset..]` is a non-empty slice of + // bytes valid for at least `cert_chain_len` bytes. + let cert_len = unsafe { + bindings::x509_get_certificate_length( + &cert_chain_buf[offset..] as *const _ as *const u8, + cert_chain_len - offset, + ) + }; + + if cert_len < 0 { + pr_err!("Invalid certificate length\n"); + + if let Some(prev) = prev_cert { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + unsafe { bindings::x509_free_certificate(prev) }; + } + + to_result(cert_len as i32)?; + } + + // SAFETY: `cert_chain_buf[offset..]` is a non-empty slice of + // bytes valid for at least `cert_len` bytes. + let cert_ptr = unsafe { + match from_err_ptr(bindings::x509_cert_parse( + &cert_chain_buf[offset..] as *const _ as *const c_void, + cert_len as usize, + )) { + Err(e) => { + if let Some(prev) = prev_cert { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + bindings::x509_free_certificate(prev); + } + return Err(e); + } + Ok(c) => c, + } + }; + // SAFETY: Cast the `struct x509_certificate` to a Rust binding + let cert = unsafe { *cert_ptr }; + + if cert.unsupported_sig || cert.blacklisted { + pr_err!("Certificate was rejected\n"); + + if let Some(prev) = prev_cert { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + unsafe { bindings::x509_free_certificate(prev) }; + } + // SAFETY: `cert_ptr` was just returned by + // `x509_cert_parse()`. + unsafe { bindings::x509_free_certificate(cert_ptr) }; + + return Err(EKEYREJECTED); + } + + if let Some(prev) = prev_cert { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + let rc = unsafe { bindings::public_key_verify_signature((*prev).pub_, cert.sig) }; + + if rc < 0 { + pr_err!("Signature validation error\n"); + + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + unsafe { bindings::x509_free_certificate(prev) }; + + // SAFETY: `cert_ptr` was just returned by + // `x509_cert_parse()`. + unsafe { bindings::x509_free_certificate(cert_ptr) }; + + to_result(rc)?; + } + } + + if let Some(prev) = prev_cert { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + unsafe { bindings::x509_free_certificate(prev) }; + } + + prev_cert = Some(cert_ptr); + offset += cert_len as usize; + } + + if let Some(prev) = prev_cert { + if let Some(validate) = self.validate { + // SAFETY: Call the `validate` function provided. + let rc = unsafe { validate(self.dev, slot, prev) }; + if let Err(e) = to_result(rc) { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + unsafe { bindings::x509_free_certificate(prev) }; + return Err(e); + } + } + + // The leaf key is the same for all slots, so just store the first one. + if self.leaf_key.is_none() { + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + self.leaf_key = unsafe { Some((*prev).pub_) }; + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. We are setting + // the `pub` key to null so it isn't freed below + unsafe { (*prev).pub_ = core::ptr::null_mut() }; + } + + // SAFETY: `prev_cert` is the previously parsed + // certificate from a prior loop iteration. + unsafe { bindings::x509_free_certificate(prev) }; + } + + Ok(()) + } } diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index d2781c27794b..5cc71552b524 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include #include -- 2.55.0