From nobody Thu Apr 2 17:17:46 2026 Received: from mail-pg1-f169.google.com (mail-pg1-f169.google.com [209.85.215.169]) (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 C2275344050 for ; Wed, 11 Feb 2026 03:31:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770780702; cv=none; b=t/+ySFsMCbs7Ptt+0BBZl5CI6FEnpPk7tzRkmpASwYQNTIRZBllbt9FkPNi0f7y4U4K+CUx6/9LKfqc2MELt2Us2jip5gJ/QO3xGsOalNPA08Fb+xxXMVGKZrDq86MCRiRefXM3464LBkhY4SIhsAW+/RI2kS9+ulxk7vyABcqU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770780702; c=relaxed/simple; bh=wwQdaFdguyOpRhGX726OhemxFT7tG742bz91Nmlzmjs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=M7B5FBswMFQMlYlACxoMsTIlGBZ0GDi65sVkdmqe+X016Eau1MqFGs6SNjcJshLWOr64/ehpRcbGJ6jkH6svzf2AC/O/9+lDZTtXYD27F7D6A0/Rt81wLlzCpn9TMNe7QdsxE+EGhv3JAPqKrcSwJlqGD5ul+yIZEO/jj+gzrKs= 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=AaGl4XBK; arc=none smtp.client-ip=209.85.215.169 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="AaGl4XBK" Received: by mail-pg1-f169.google.com with SMTP id 41be03b00d2f7-c0ec27cad8cso569746a12.1 for ; Tue, 10 Feb 2026 19:31:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1770780700; x=1771385500; 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; bh=HJ/8xHDMdhMHTTYwlL4evaNX3TwyhC8RfbkwEhmL2HQ=; b=AaGl4XBKbX7Qk4WYmWHHwL4g+UxHNI1P31054D2YiaGE7t5N7LFR5QyCC+Us04U9zX QsT7NkO80dIuQEPN3dBNZfsymoxf6Vic1PSqJH6fQcylhqC45JXXmlMFW+FVuheupK5b ksZlYL3HTWPKt4FOIn7pa+BFTfCxFVvBdFRC5MyGlykmZ/e5ObpcOuXOI3TSy+aqacn9 CCP9DxtWN1gUoHqKwKqVrr8NJIqniGzfzh6pSOabASLG6W5D90q33j8Iv/fmigp+DpL4 7gx/AagMK4RK3M4Kvv5NFxjUrcwJIemLy1JskhOetqvqwvp5HutHJoTMILYqfjT3WObW WtRw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770780700; x=1771385500; 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; bh=HJ/8xHDMdhMHTTYwlL4evaNX3TwyhC8RfbkwEhmL2HQ=; b=NKf9i/aFPVjuzibiAylvL8pFfZnCxy9xEoPbSRBfxD0gG1PqYErlf/f9H/OycUwUCe Ro5x8luakpQEv9rwbBcWSOzpB96uEXl9YAEluAjCBpnmobVspPSEUAN++AO557FSqgTV MVRXwYEUCI89YXmiqdtaqDwXdItXbGq9Hc/qQF8rNNoQ8U8I1qEpHxQvZiEY5jwLUXxj 8Itz4YPTAhEkL5ZRik5pklnnYvD7tq1G8KI3Kk4UtkzVc/B1TzQULSx3FN8FmdIGsAS8 0mYx16aoTm4AHcMs904C0cm3QljMSSO5nezlvQJUBrpbyf1uGmYg7UzDYMz1Kni/8/yc vWMQ== X-Forwarded-Encrypted: i=1; AJvYcCVH02caAWp+VviVj4YJ/dg0/3MNy6MIB/xyTnv9ytGiQJgEupNDmCxrOYRMzgwxdofaLaIhrNiraUu6twQ=@vger.kernel.org X-Gm-Message-State: AOJu0YxEjqvAvV1Lxrrd9Yh/RJPktCIOGoDN+5G6KIXUBkTZ5pVJHbKk iwtXtC3/EHSyT++r6f3N7q0xejGgbJsPhm++EbdRxBmhRGlwJKEAzd7+ X-Gm-Gg: AZuq6aKUQw/rjrihVIP/VMcXRaN8Wq6j5+0g5VecF1EWPLrjnaAqfpfzsmZ2qWsE0XK e67J/NG/aL7PYq+LIIrWqth4R+6lrt2Qq0+TMEtXvQxN5hPsIkv7CMPhrlmYr9ITVwOFiuWLx85 zF4KysmTFANo09fByQMxGFE2cD2hWhhrRRdPKOz1GGrRw/AX2+rTS65/mmf/aWQt9KH0zHVF4hT hisG0IPShWTWvjUZxMQhLWsnod01l2PCuiPDped7BZSyY0KpY+ZekX3Q0CbB3W00IAKz3jUzIy4 vmHVbFHTicZ2dNyktlwN3dWqB3iuggRcnfAgCDtkZH4VzGFd6uiqJF98bBBK7RGkDF1TRkY6FGZ izBoDrPrArVT+/wd5lQdHhHvQp/sclP1kX71F3oksLr5/u6/S6vfNi1PtlNhuvebDzS4k4LX6e3 /O8DEV6S7/Dcc/mgIoVUtXdbxLRVo156+XIPFtUkFA1jvxHdGRVOix X-Received: by 2002:a05:6a21:38d:b0:38e:9220:ebbe with SMTP id adf61e73a8af0-393ad01ed9emr14265861637.23.1770780700142; Tue, 10 Feb 2026 19:31:40 -0800 (PST) Received: from toolbx.alistair23.me ([2403:581e:fdf9:0:6209:4521:6813:45b7]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-c6e197d63c9sm464856a12.20.2026.02.10.19.31.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Feb 2026 19:31:39 -0800 (PST) From: alistair23@gmail.com X-Google-Original-From: alistair.francis@wdc.com To: bhelgaas@google.com, lukas@wunner.de, rust-for-linux@vger.kernel.org, akpm@linux-foundation.org, linux-pci@vger.kernel.org, Jonathan.Cameron@huawei.com, linux-cxl@vger.kernel.org, linux-kernel@vger.kernel.org Cc: alex.gaynor@gmail.com, benno.lossin@proton.me, boqun.feng@gmail.com, a.hindborg@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com, tmgross@umich.edu, alistair23@gmail.com, ojeda@kernel.org, wilfred.mallawa@wdc.com, aliceryhl@google.com, Alistair Francis Subject: [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate Date: Wed, 11 Feb 2026 13:29:23 +1000 Message-ID: <20260211032935.2705841-17-alistair.francis@wdc.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260211032935.2705841-1-alistair.francis@wdc.com> References: <20260211032935.2705841-1-alistair.francis@wdc.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alistair Francis Support the GET_CERTIFICATE SPDM command. Signed-off-by: Alistair Francis --- lib/rspdm/consts.rs | 2 + lib/rspdm/lib.rs | 11 ++++ lib/rspdm/state.rs | 127 ++++++++++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 64 ++++++++++++++++++++- 4 files changed, 200 insertions(+), 4 deletions(-) diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs index 4d39ca2cb584..eaf2132da290 100644 --- a/lib/rspdm/consts.rs +++ b/lib/rspdm/consts.rs @@ -174,6 +174,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core= ::fmt::Result { =20 pub(crate) const SPDM_GET_DIGESTS: u8 =3D 0x81; =20 +pub(crate) const SPDM_GET_CERTIFICATE: u8 =3D 0x82; + #[cfg(CONFIG_CRYPTO_RSA)] pub(crate) const SPDM_ASYM_RSA: u32 =3D SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096; diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs index a7af86d1dc0b..b065b3f70356 100644 --- a/lib/rspdm/lib.rs +++ b/lib/rspdm/lib.rs @@ -125,6 +125,17 @@ return e.to_errno() as c_int; } =20 + let mut provisioned_slots =3D state.provisioned_slots; + while (provisioned_slots as usize) > 0 { + let slot =3D provisioned_slots.trailing_zeros() as u8; + + if let Err(e) =3D state.get_certificate(slot) { + return e.to_errno() as c_int; + } + + provisioned_slots &=3D !(1 << slot); + } + 0 } =20 diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs index 2606b825c494..1e5656144611 100644 --- a/lib/rspdm/state.rs +++ b/lib/rspdm/state.rs @@ -26,8 +26,9 @@ SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPD= M_VER_12, }; use crate::validator::{ - GetCapabilitiesReq, GetCapabilitiesRsp, GetDigestsReq, GetDigestsRsp, = GetVersionReq, - GetVersionRsp, NegotiateAlgsReq, NegotiateAlgsRsp, RegAlg, SpdmErrorRs= p, SpdmHeader, + GetCapabilitiesReq, GetCapabilitiesRsp, GetCertificateReq, GetCertific= ateRsp, GetDigestsReq, + GetDigestsRsp, GetVersionReq, GetVersionRsp, NegotiateAlgsReq, Negotia= teAlgsRsp, RegAlg, + SpdmErrorRsp, SpdmHeader, }; =20 /// The current SPDM session state for a device. Based on the @@ -107,6 +108,14 @@ pub struct SpdmState { pub(crate) certs: [KVec; SPDM_SLOTS], } =20 +#[repr(C, packed)] +pub(crate) struct SpdmCertChain { + length: u16, + _reserved: [u8; 2], + root_hash: bindings::__IncompleteArrayField, + certificates: bindings::__IncompleteArrayField, +} + impl SpdmState { pub(crate) fn new( dev: *mut bindings::device, @@ -620,4 +629,118 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Er= ror> { =20 Ok(()) } + + fn get_cert_exchange( + &mut self, + request_buf: &mut [u8], + response_vec: &mut KVec, + rsp_sz: usize, + ) -> Result<&mut GetCertificateRsp, Error> { + // SAFETY: `request` is repr(C) and packed, so we can convert it t= o a slice + let response_buf =3D unsafe { from_raw_parts_mut(response_vec.as_m= ut_ptr(), rsp_sz) }; + + let rc =3D self.spdm_exchange(request_buf, response_buf)?; + + if rc < (core::mem::size_of::() 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 =3D Untrusted::new_mut(respon= se_vec).validate_mut()?; + + if rc + < (core::mem::size_of::() + response.portio= n_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 =3D GetCertificateReq::default(); + request.version =3D self.version; + request.param1 =3D slot; + + let req_sz =3D core::mem::size_of::(); + let rsp_sz =3D ((core::mem::size_of::() + 0xfff= f) as u32) + .min(self.transport_sz) as usize; + + request.offset =3D 0; + request.length =3D (rsp_sz - core::mem::size_of::()).to_le() as u16; + + // SAFETY: `request` is repr(C) and packed, so we can convert it t= o a slice + let request_buf =3D unsafe { from_raw_parts_mut(&mut request as *m= ut _ as *mut u8, req_sz) }; + + let mut response_vec: KVec =3D KVec::with_capacity(rsp_sz, GFP= _KERNEL)?; + + let response =3D self.get_cert_exchange(request_buf, &mut response= _vec, rsp_sz)?; + + let total_cert_len =3D + ((response.portion_length + response.remainder_length) & 0xFFF= F) as usize; + + let mut certs_buf: KVec =3D KVec::new(); + + certs_buf.extend_from_slice( + &response_vec[8..(8 + response.portion_length as usize)], + GFP_KERNEL, + )?; + + let mut offset: usize =3D response.portion_length as usize; + let mut remainder_length =3D response.remainder_length as usize; + + while remainder_length > 0 { + request.offset =3D offset.to_le() as u16; + request.length =3D (remainder_length + .min(rsp_sz - core::mem::size_of::())) + .to_le() as u16; + + let request_buf =3D + unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut= u8, req_sz) }; + + let response =3D self.get_cert_exchange(request_buf, &mut resp= onse_vec, rsp_sz)?; + + if response.portion_length =3D=3D 0 + || (response.param1 & 0xF) !=3D slot + || offset as u16 + response.portion_length + response.rema= inder_length + !=3D 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 +=3D response.portion_length as usize; + remainder_length =3D response.remainder_length as usize; + } + + let header_length =3D core::mem::size_of::() + self= .hash_len; + + let ptr =3D certs_buf.as_mut_ptr(); + // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can conver= t it from a slice + let ptr =3D ptr.cast::(); + // SAFETY: `ptr` came from a reference and the cast above is valid. + let certs: &mut SpdmCertChain =3D unsafe { &mut *ptr }; + + if total_cert_len < header_length + || total_cert_len !=3D usize::from_le(certs.length as usize) + || total_cert_len !=3D 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 2150a23997db..a8bc3378676f 100644 --- a/lib/rspdm/validator.rs +++ b/lib/rspdm/validator.rs @@ -17,8 +17,9 @@ }; =20 use crate::consts::{ - SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_DIGE= STS, SPDM_GET_VERSION, - SPDM_HASH_ALGOS, SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS, SPDM_REQ_CA= PS, + SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_CERT= IFICATE, + SPDM_GET_DIGESTS, SPDM_GET_VERSION, SPDM_HASH_ALGOS, SPDM_MEAS_SPEC_DM= TF, SPDM_NEGOTIATE_ALGS, + SPDM_REQ_CAPS, }; =20 #[repr(C, packed)] @@ -364,3 +365,62 @@ fn validate(unvalidated: &mut Unvalidated>) -= > Result 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, +} + +impl Validate<&mut Unvalidated>> for &mut GetCertificateRsp { + type Err =3D Error; + + fn validate(unvalidated: &mut Unvalidated>) -> Result { + let raw =3D unvalidated.raw_mut(); + if raw.len() < mem::size_of::() { + return Err(EINVAL); + } + + let ptr =3D raw.as_mut_ptr(); + // CAST: `GetCertificateRsp` only contains integers and has `repr(= C)`. + let ptr =3D ptr.cast::(); + // SAFETY: `ptr` came from a reference and the cast above is valid. + let rsp: &mut GetCertificateRsp =3D unsafe { &mut *ptr }; + + rsp.portion_length =3D rsp.portion_length.to_le(); + rsp.remainder_length =3D rsp.remainder_length.to_le(); + + Ok(rsp) + } +} --=20 2.52.0