get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.2/patches/2234754/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2234754,
    "url": "http://patchwork.ozlabs.org/api/1.2/patches/2234754/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/linux-pci/patch/20260508031710.514574-19-alistair.francis@wdc.com/",
    "project": {
        "id": 28,
        "url": "http://patchwork.ozlabs.org/api/1.2/projects/28/?format=api",
        "name": "Linux PCI development",
        "link_name": "linux-pci",
        "list_id": "linux-pci.vger.kernel.org",
        "list_email": "linux-pci@vger.kernel.org",
        "web_url": null,
        "scm_url": null,
        "webscm_url": null,
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20260508031710.514574-19-alistair.francis@wdc.com>",
    "list_archive_url": null,
    "date": "2026-05-08T03:17:10",
    "name": "[18/18] lib: rspdm: Support SPDM challenge",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "13e0fc34c95491a1a724690150e8eebed18ed829",
    "submitter": {
        "id": 64571,
        "url": "http://patchwork.ozlabs.org/api/1.2/people/64571/?format=api",
        "name": "Alistair Francis",
        "email": "alistair23@gmail.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/linux-pci/patch/20260508031710.514574-19-alistair.francis@wdc.com/mbox/",
    "series": [
        {
            "id": 503312,
            "url": "http://patchwork.ozlabs.org/api/1.2/series/503312/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/linux-pci/list/?series=503312",
            "date": "2026-05-08T03:16:52",
            "name": "lib: Rust implementation of SPDM",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/503312/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2234754/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2234754/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "\n <linux-pci+bounces-54173-incoming=patchwork.ozlabs.org@vger.kernel.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "linux-pci@vger.kernel.org"
        ],
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=plWTWO3o;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c15:e001:75::12fc:5321; helo=sin.lore.kernel.org;\n envelope-from=linux-pci+bounces-54173-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)",
            "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=\"plWTWO3o\"",
            "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.214.169",
            "smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com",
            "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=gmail.com"
        ],
        "Received": [
            "from sin.lore.kernel.org (sin.lore.kernel.org\n [IPv6:2600:3c15:e001:75::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4gBZKX1y93z1yCg\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 08 May 2026 13:26:40 +1000 (AEST)",
            "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sin.lore.kernel.org (Postfix) with ESMTP id A301A304ABDC\n\tfor <incoming@patchwork.ozlabs.org>; Fri,  8 May 2026 03:20:25 +0000 (UTC)",
            "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 26FDB2FFFB5;\n\tFri,  8 May 2026 03:19:41 +0000 (UTC)",
            "from mail-pl1-f169.google.com (mail-pl1-f169.google.com\n [209.85.214.169])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 39D4B2F83A0\n\tfor <linux-pci@vger.kernel.org>; Fri,  8 May 2026 03:19:39 +0000 (UTC)",
            "by mail-pl1-f169.google.com with SMTP id\n d9443c01a7336-2b458ca2296so9970715ad.0\n        for <linux-pci@vger.kernel.org>; Thu, 07 May 2026 20:19:39 -0700 (PDT)",
            "from toolbx.alistair23.me ([2403:581e:fdf9:0:6209:4521:6813:45b7])\n        by smtp.gmail.com with ESMTPSA id\n d9443c01a7336-2baf1eafa62sm3220685ad.74.2026.05.07.20.19.31\n        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n        Thu, 07 May 2026 20:19:38 -0700 (PDT)"
        ],
        "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1778210381; cv=none;\n b=Lk46zMmHMrxEypOSnjGaigVyS2LGGrjUcWmr4+Bx8kbHeDDGlO8XOKEkNXpPwdcKcNfGVuLTRjIa7FEuX6s3tzAihTYeu/TUZxdStOTsh4H3WShv80dnkqG9rqEdG66O0k/30UTJr1GeV8cFRVin7MujbXTD5QWHrhvxhDP9JF8=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1778210381; c=relaxed/simple;\n\tbh=48LvjpVIfzqjq0xGyaF+qPR4OSFXmF4Q/GbzHzxPS+s=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=pBUEDT6WxaJslUM7zN9OUekhMcAVWIIkBlicb3OYyWynpe5d4JYRWzCvduNDgC7Qdp1QQcXUgUXtPVRVyPgsrfpEvDHE7T8bF/9TQOk2U1V89FrMQ6OvTQpCh84ou0fCLX21/jqesLciNRHSwpOq12eXNhNwjV8FkljtcKP0Nfc=",
        "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com;\n spf=pass smtp.mailfrom=gmail.com;\n dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=plWTWO3o; arc=none smtp.client-ip=209.85.214.169",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=gmail.com; s=20251104; t=1778210379; x=1778815179;\n darn=vger.kernel.org;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n         :message-id:reply-to;\n        bh=pprsevTDOKLZVgl88a8cDB8UwOK0KvhkdrcSb/4STD4=;\n        b=plWTWO3ouZ2OFZF9f3MfW+mFw+xX8jGk6Ci/BYA53GTUZexHVkel8cEloM7Fcw2sG5\n         5DDq1OQyyiNJ3wOltYRMa2R15q8FK1l20SUKr0G8zY4ai6s9+umvLKPY8xkxgppxPM9x\n         TmsfCWGq5GcdYlZ54+VRWxr6/Plg2MNqWYNj4aTGTfX7WNuHreM2j2ypFd0MFfrjK9ZV\n         fSzQR2VYbP5cOPP6h+eNYJp4vyhUQPhkDoc3PsfNUaLHjJkhhQbgP3OHUTqMijBaIfDC\n         R5d2QYRuQQ29M/IzB7cp55yPDK8pBsxtxksmg9eQQ5goMKqm6yXt7l06owVI+zj20kw8\n         mOXA==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=1e100.net; s=20251104; t=1778210379; x=1778815179;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n         :to:cc:subject:date:message-id:reply-to;\n        bh=pprsevTDOKLZVgl88a8cDB8UwOK0KvhkdrcSb/4STD4=;\n        b=rqtoQAylEYK0hzakv+fdnTvfiVtlY9E30uDoxapuRcdXjgwBwa4PlF2RthZGIk4xq3\n         agmG0LSsX/PwGoUOaEnuCUuxQq5NlQldQeCnZ/gzwdWlLvGlJEDfQEzXcHDD4D24K/2U\n         zeg1gaWgvOT25Eu/WJ8p/oICSOpFUyp6iK7zSD1CCFKWuEL+ets9JVD/Abn33Z+31YfU\n         1e7v8jgfxILonQV9eyJq8bdbxRXQXDgVHFGI5a2e+NrpCSRDvVijt/z3BrEq7TE8ri3u\n         vOiGtN5B3sFizwZDntc1dHrU2gHRtueFlZGXSK4/+oFJGaAO/IBMXDawLcg0RC1DdSjl\n         jWOQ==",
        "X-Forwarded-Encrypted": "i=1;\n AFNElJ/dvvh3SCRh4+jzzWsuFda47NJH9oy2yxeq7O0D3DBs9E5rmLvbpq22a5jFLsLkvgiVzi+jOlgZ8K4=@vger.kernel.org",
        "X-Gm-Message-State": "AOJu0YxBnPKniEDo/YA4wM0PjfdrfeFdHv/3u7h1qDq7np3jKrCtB+dl\n\tqwjB3ejGGdJRJYSSWIiPpmwX3pjPxsyM0x8gc/+9KpdtrkQd6O6fPkWw",
        "X-Gm-Gg": "Acq92OGiKsk4HFq8vVORzfQzrKbj7j3EsiRIHYWPRDfRV+55E6EU7J2I2XpzWBfbu3g\n\tP5kPXkoxxpdUBLBdMaiC+2lH/w+xHKD82Kdi3X/q9/AESKHxXr5KZQYMvXFXW9htyOZfu2Gz6gg\n\tL4+9n/iUzX85Co68XApzSz5ASTrK8wggkmV0ejsnFZ66q5H2DEZjE4+KsEewaPPGvYwHPeLb5Ft\n\tlD0whMjtxq7rwFxvAGS9qJZ7GGKTJNjALHc7ZJyg4K+qVYkRL/IoiAg6KOxGOXh2JpXuEVLp3PQ\n\tJGvFXVVr0GqxNJguaJzuVOsiKHuefb57j1TZ/0253ksx2QAFs2GXAVUgZiPXAwXVSdFFlTMxD2q\n\tDcNtGN5yh5lHLq2jR5iPL3ufaK/y8XzngsX6v/qGEUXPC+Sn36wzoog5geITZL2fGXs1mhcVaCk\n\tyXNBtC72bckBmFOVRDKj+nTbOQZP3lPKqnBksWA9o2TmafTaiMnAs=",
        "X-Received": "by 2002:a17:903:1b28:b0:2ba:881f:6192 with SMTP id\n d9443c01a7336-2baf0dd629cmr8678865ad.22.1778210378477;\n        Thu, 07 May 2026 20:19:38 -0700 (PDT)",
        "From": "alistair23@gmail.com",
        "X-Google-Original-From": "alistair.francis@wdc.com",
        "To": "alistair@alistair23.me,\n\tlinux-kernel@vger.kernel.org,\n\tlukas@wunner.de,\n\tJonathan.Cameron@huawei.com,\n\tbhelgaas@google.com,\n\trust-for-linux@vger.kernel.org,\n\takpm@linux-foundation.org,\n\tlinux-cxl@vger.kernel.org,\n\tdjbw@kernel.org,\n\tlinux-pci@vger.kernel.org",
        "Cc": "alex.gaynor@gmail.com,\n\twilfred.mallawa@wdc.com,\n\tgary@garyguo.net,\n\tbjorn3_gh@protonmail.com,\n\tbenno.lossin@proton.me,\n\taliceryhl@google.com,\n\tboqun.feng@gmail.com,\n\ta.hindborg@kernel.org,\n\ttmgross@umich.edu,\n\tojeda@kernel.org,\n\talistair23@gmail.com",
        "Subject": "[PATCH 18/18] lib: rspdm: Support SPDM challenge",
        "Date": "Fri,  8 May 2026 13:17:10 +1000",
        "Message-ID": "<20260508031710.514574-19-alistair.francis@wdc.com>",
        "X-Mailer": "git-send-email 2.52.0",
        "In-Reply-To": "<20260508031710.514574-1-alistair.francis@wdc.com>",
        "References": "<20260508031710.514574-1-alistair.francis@wdc.com>",
        "Precedence": "bulk",
        "X-Mailing-List": "linux-pci@vger.kernel.org",
        "List-Id": "<linux-pci.vger.kernel.org>",
        "List-Subscribe": "<mailto:linux-pci+subscribe@vger.kernel.org>",
        "List-Unsubscribe": "<mailto:linux-pci+unsubscribe@vger.kernel.org>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit"
    },
    "content": "From: Alistair Francis <alistair@alistair23.me>\n\nSupport the CHALLENGE SPDM command.\n\nSigned-off-by: Alistair Francis <alistair@alistair23.me>\n---\n lib/rspdm/consts.rs             |   6 +\n lib/rspdm/lib.rs                |   8 +-\n lib/rspdm/state.rs              | 216 +++++++++++++++++++++++++++++++-\n lib/rspdm/validator.rs          |  62 +++++++++\n rust/bindings/bindings_helper.h |   1 +\n 5 files changed, 291 insertions(+), 2 deletions(-)",
    "diff": "diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs\nindex 302bc0285478..9d41928da0c6 100644\n--- a/lib/rspdm/consts.rs\n+++ b/lib/rspdm/consts.rs\n@@ -116,6 +116,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n \n pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;\n \n+pub(crate) const SPDM_CHALLENGE: u8 = 0x83;\n+\n // If the crypto support isn't enabled don't offer the algorithms\n // to the responder\n #[cfg(CONFIG_CRYPTO_RSA)]\n@@ -147,3 +149,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n // pub(crate) const SPDM_MAX_REQ_ALG_STRUCT: usize = 4;\n \n pub(crate) const SPDM_OPAQUE_DATA_FMT_GENERAL: u8 = bit_u8(1);\n+\n+pub(crate) const SPDM_PREFIX_SZ: usize = 64;\n+pub(crate) const SPDM_COMBINED_PREFIX_SZ: usize = 100;\n+pub(crate) const SPDM_MAX_OPAQUE_DATA: usize = 1024;\ndiff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs\nindex d6421b2fab7d..7fcf5a2d3071 100644\n--- a/lib/rspdm/lib.rs\n+++ b/lib/rspdm/lib.rs\n@@ -135,17 +135,23 @@\n         provisioned_slots &= !(1 << slot);\n     }\n \n+    let mut verify = true;\n     let mut provisioned_slots = state.provisioned_slots;\n     while (provisioned_slots as usize) > 0 {\n         let slot = provisioned_slots.trailing_zeros() as u8;\n \n         if let Err(e) = state.validate_cert_chain(slot) {\n-            return e.to_errno() as c_int;\n+            pr_err!(\"Certificate in slot {slot} failed to verify: {e:?}\");\n+            verify = false;\n         }\n \n         provisioned_slots &= !(1 << slot);\n     }\n \n+    if let Err(e) = state.challenge(state.provisioned_slots.trailing_zeros() as u8, verify) {\n+        return e.to_errno() as c_int;\n+    }\n+\n     0\n }\n \ndiff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs\nindex 4c8ee553bb69..3cf7236af7b2 100644\n--- a/lib/rspdm/state.rs\n+++ b/lib/rspdm/state.rs\n@@ -8,6 +8,7 @@\n //! <https://www.dmtf.org/dsp/DSP0274>\n \n use core::ffi::c_void;\n+use core::mem::offset_of;\n use core::slice::from_raw_parts_mut;\n use kernel::prelude::*;\n use kernel::{\n@@ -19,6 +20,7 @@\n         Error, //\n     },\n     str::CStr,\n+    str::CString,\n     validate::Untrusted,\n };\n \n@@ -31,6 +33,7 @@\n     SPDM_ASYM_RSASSA_2048,\n     SPDM_ASYM_RSASSA_3072,\n     SPDM_ASYM_RSASSA_4096,\n+    SPDM_COMBINED_PREFIX_SZ,\n     SPDM_ERROR,\n     SPDM_GET_VERSION_LEN,\n     SPDM_HASH_ALGOS,\n@@ -38,12 +41,14 @@\n     SPDM_HASH_SHA_384,\n     SPDM_HASH_SHA_512,\n     SPDM_KEY_EX_CAP,\n+    SPDM_MAX_OPAQUE_DATA,\n     SPDM_MAX_VER,\n     SPDM_MEAS_CAP_MASK,\n     SPDM_MEAS_SPEC_DMTF,\n     SPDM_MIN_DATA_TRANSFER_SIZE,\n     SPDM_MIN_VER,\n     SPDM_OPAQUE_DATA_FMT_GENERAL,\n+    SPDM_PREFIX_SZ,\n     SPDM_REQ,\n     SPDM_RSP_MIN_CAPS,\n     SPDM_SLOTS,\n@@ -53,6 +58,8 @@\n     SPDM_VER_13, //\n };\n use crate::validator::{\n+    ChallengeReq,\n+    ChallengeRsp,\n     GetCapabilitiesReq,\n     GetCapabilitiesRsp,\n     GetCertificateReq,\n@@ -67,6 +74,8 @@\n     SpdmHeader, //\n };\n \n+const SPDM_CONTEXT: &str = \"responder-challenge_auth signing\";\n+\n /// The current SPDM session state for a device. Based on the\n /// C `struct spdm_state`.\n ///\n@@ -108,6 +117,14 @@\n ///  not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.\n /// @leaf_key: Public key portion of leaf certificate against which to check\n ///  responder's signatures.\n+/// @transcript: Concatenation of all SPDM messages exchanged during an\n+///  authentication or measurement sequence.  Used to verify the signature,\n+///  as it is computed over the hashed transcript.\n+/// @next_nonce: Requester nonce to be used for the next authentication\n+///  sequence.  Populated from user space through sysfs.\n+///  If user space does not provide a nonce, the kernel uses a random one.\n+///\n+/// `authenticated`: Whether device was authenticated successfully.\n pub struct SpdmState {\n     pub(crate) dev: *mut bindings::device,\n     pub(crate) transport: bindings::spdm_transport,\n@@ -134,9 +151,15 @@ pub struct SpdmState {\n     pub(crate) desc: Option<&'static mut bindings::shash_desc>,\n     pub(crate) hash_len: usize,\n \n+    pub(crate) authenticated: bool,\n+\n     // Certificates\n     pub(crate) certs: [KVec<u8>; SPDM_SLOTS],\n     pub(crate) leaf_key: Option<*mut bindings::public_key>,\n+\n+    transcript: VVec<u8>,\n+\n+    pub(crate) next_nonce: KVec<u8>,\n }\n \n #[repr(C, packed)]\n@@ -174,8 +197,11 @@ pub(crate) fn new(\n             shash: core::ptr::null_mut(),\n             desc: None,\n             hash_len: 0,\n+            authenticated: false,\n             certs: [const { KVec::new() }; SPDM_SLOTS],\n             leaf_key: None,\n+            transcript: VVec::new(),\n+            next_nonce: KVec::new(),\n         }\n     }\n \n@@ -291,7 +317,7 @@ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {\n     /// The data in `request_buf` is sent to the device and the response is\n     /// stored in `response_buf`.\n     pub(crate) fn spdm_exchange(\n-        &self,\n+        &mut self,\n         request_buf: &mut [u8],\n         response_buf: &mut [u8],\n     ) -> Result<i32, Error> {\n@@ -299,6 +325,8 @@ pub(crate) fn spdm_exchange(\n         let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;\n         let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;\n \n+        self.transcript.extend_from_slice(request_buf, GFP_KERNEL)?;\n+\n         let transport_function = self.transport.ok_or(EINVAL)?;\n         // SAFETY: `transport_function` is provided by the new(), we are\n         // calling the function.\n@@ -367,6 +395,12 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {\n         unsafe { response_vec.inc_len(rc as usize) };\n \n         let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;\n+        let rsp_sz = core::mem::size_of::<SpdmHeader>()\n+            + 2\n+            + response.version_number_entry_count as usize * 2;\n+\n+        self.transcript\n+            .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;\n \n         let mut foundver = false;\n         let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;\n@@ -438,6 +472,9 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {\n         let response: &mut GetCapabilitiesRsp =\n             Untrusted::new_mut(&mut response_vec).validate_mut()?;\n \n+        self.transcript\n+            .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;\n+\n         self.rsp_caps = u32::from_le(response.flags);\n         if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {\n             pr_err!(\n@@ -576,6 +613,9 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {\n         let response: &mut NegotiateAlgsRsp =\n             Untrusted::new_mut(&mut response_vec).validate_mut()?;\n \n+        self.transcript\n+            .extend_from_slice(&response_vec, GFP_KERNEL)?;\n+\n         self.base_asym_alg = response.base_asym_sel;\n         self.base_hash_alg = response.base_hash_sel;\n         self.meas_hash_alg = response.measurement_hash_algo;\n@@ -637,6 +677,10 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {\n         unsafe { response_vec.inc_len(len as usize) };\n \n         let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;\n+        let rsp_sz = core::mem::size_of::<SpdmHeader>() + response.param2 as usize * self.hash_len;\n+\n+        self.transcript\n+            .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;\n \n         if len\n             < (core::mem::size_of::<GetDigestsReq>()\n@@ -697,6 +741,10 @@ fn get_cert_exchange(\n         unsafe { response_vec.inc_len(rc as usize) };\n \n         let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;\n+        let rsp_sz = core::mem::size_of::<SpdmHeader>() + 4 + response.portion_length as usize;\n+\n+        self.transcript\n+            .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;\n \n         if rc\n             < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32\n@@ -853,4 +901,170 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {\n \n         Ok(())\n     }\n+\n+    pub(crate) fn challenge_rsp_len(&mut self, nonce_len: usize, opaque_len: usize) -> usize {\n+        // No measurement summary hash requested (MSHLength == 0)\n+        let mut length =\n+            core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len + opaque_len + 2;\n+\n+        if self.version >= 0x13 {\n+            length += 8;\n+        }\n+\n+        length + self.sig_len\n+    }\n+\n+    fn verify_signature(&mut self, signature: &mut [u8]) -> Result<(), Error> {\n+        let mut sig = bindings::public_key_signature::default();\n+        let mut mhash: KVec<u8> = KVec::new();\n+\n+        sig.s = signature as *mut _ as *mut u8;\n+        sig.s_size = self.sig_len as u32;\n+        sig.encoding = self.base_asym_enc.as_ptr() as *const u8;\n+        sig.hash_algo = self.base_hash_alg_name.as_ptr() as *const u8;\n+\n+        let mut m: KVec<u8> = KVec::new();\n+        m.extend_with(SPDM_COMBINED_PREFIX_SZ + self.hash_len, 0, GFP_KERNEL)?;\n+\n+        if let Some(desc) = &mut self.desc {\n+            desc.tfm = self.shash;\n+\n+            unsafe {\n+                to_result(bindings::crypto_shash_digest(\n+                    *desc,\n+                    self.transcript.as_ptr(),\n+                    (self.transcript.len() - self.sig_len) as u32,\n+                    m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr(),\n+                ))?;\n+            };\n+        } else {\n+            to_result(-(bindings::EPROTO as i32))?;\n+        }\n+\n+        if self.version <= 0x11 {\n+            sig.m = m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr();\n+        } else {\n+            let major = self.version >> 4;\n+            let minor = self.version & 0xF;\n+\n+            let output = CString::try_from_fmt(fmt!(\"dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*\"))?;\n+            let mut buf = output.into_vec();\n+            let zero_pad_len = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - SPDM_CONTEXT.len() - 1;\n+\n+            buf.extend_with(zero_pad_len, 0, GFP_KERNEL)?;\n+            buf.extend_from_slice(SPDM_CONTEXT.as_bytes(), GFP_KERNEL)?;\n+\n+            m[..SPDM_COMBINED_PREFIX_SZ].copy_from_slice(&buf);\n+\n+            mhash.extend_with(self.hash_len, 0, GFP_KERNEL)?;\n+\n+            if let Some(desc) = &mut self.desc {\n+                desc.tfm = self.shash;\n+\n+                unsafe {\n+                    to_result(bindings::crypto_shash_digest(\n+                        *desc,\n+                        m.as_ptr(),\n+                        m.len() as u32,\n+                        mhash.as_mut_ptr(),\n+                    ))?;\n+                };\n+            } else {\n+                to_result(-(bindings::EPROTO as i32))?;\n+            }\n+\n+            sig.m = mhash.as_mut_ptr();\n+        }\n+\n+        sig.m_size = self.hash_len as u32;\n+\n+        if let Some(leaf_key) = self.leaf_key {\n+            unsafe { to_result(bindings::public_key_verify_signature(leaf_key, &sig)) }\n+        } else {\n+            to_result(-(bindings::EPROTO as i32))\n+        }\n+    }\n+\n+    pub(crate) fn challenge(&mut self, slot: u8, verify: bool) -> Result<(), Error> {\n+        let mut request = ChallengeReq::default();\n+        request.version = self.version;\n+        request.param1 = slot;\n+\n+        let nonce_len = request.nonce.len();\n+\n+        if self.next_nonce.len() > 0 {\n+            request.nonce.copy_from_slice(&self.next_nonce);\n+            self.next_nonce.clear();\n+        } else {\n+            unsafe {\n+                bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)\n+            };\n+        }\n+\n+        let req_sz = if self.version <= 0x12 {\n+            offset_of!(ChallengeReq, context)\n+        } else {\n+            core::mem::size_of::<ChallengeReq>()\n+        };\n+\n+        let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);\n+\n+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice\n+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };\n+\n+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;\n+        // SAFETY: `response_vec` is rsp_sz length, initialised, aligned\n+        // and won't be mutated\n+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };\n+\n+        let rc = self.spdm_exchange(request_buf, response_buf)?;\n+\n+        if rc < (core::mem::size_of::<ChallengeRsp>() as i32) {\n+            pr_err!(\"Truncated challenge response\\n\");\n+            to_result(-(bindings::EIO as i32))?;\n+        }\n+\n+        // SAFETY: `rc` is the length of data read, which will be smaller\n+        // then the capacity of the vector\n+        unsafe { response_vec.inc_len(rc as usize) };\n+\n+        let _response: &mut ChallengeRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;\n+\n+        // MSHLength is 0 as no measurement summary hash requested\n+        let opaque_len_offset = core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len;\n+        let opaque_len = u16::from_le_bytes(\n+            response_vec[opaque_len_offset..(opaque_len_offset + 2)]\n+                .try_into()\n+                .unwrap_or([0, 0]),\n+        );\n+\n+        let rsp_sz = self.challenge_rsp_len(nonce_len, opaque_len as usize);\n+\n+        if rc < rsp_sz as i32 {\n+            pr_err!(\"Truncated challenge response\\n\");\n+            to_result(-(bindings::EIO as i32))?;\n+        }\n+\n+        self.transcript\n+            .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;\n+\n+        if verify {\n+            /* Verify signature at end of transcript against leaf key */\n+            let sig_start = response_vec.len() - self.sig_len;\n+            let signature = &mut response_vec[sig_start..rsp_sz];\n+\n+            match self.verify_signature(signature) {\n+                Ok(()) => {\n+                    pr_info!(\"Authenticated with certificate slot {slot}\");\n+                    self.authenticated = true;\n+                }\n+                Err(e) => {\n+                    pr_err!(\"Cannot verify challenge_auth signature: {e:?}\");\n+                    self.authenticated = false;\n+                }\n+            };\n+        }\n+\n+        Ok(())\n+    }\n }\ndiff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs\nindex 8b44a056b335..1975eca81be3 100644\n--- a/lib/rspdm/validator.rs\n+++ b/lib/rspdm/validator.rs\n@@ -28,6 +28,7 @@\n \n use crate::consts::{\n     SPDM_ASYM_ALGOS,\n+    SPDM_CHALLENGE,\n     SPDM_CTEXPONENT,\n     SPDM_GET_CAPABILITIES,\n     SPDM_GET_CERTIFICATE,\n@@ -460,3 +461,64 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>\n         Ok(rsp)\n     }\n }\n+\n+#[repr(C, packed)]\n+pub(crate) struct ChallengeReq {\n+    pub(crate) version: u8,\n+    pub(crate) code: u8,\n+    pub(crate) param1: u8,\n+    pub(crate) param2: u8,\n+\n+    pub(crate) nonce: [u8; 32],\n+    pub(crate) context: [u8; 8],\n+}\n+\n+impl Default for ChallengeReq {\n+    fn default() -> Self {\n+        ChallengeReq {\n+            version: 0,\n+            code: SPDM_CHALLENGE,\n+            param1: 0,\n+            param2: 0,\n+            nonce: [0; 32],\n+            context: [0; 8],\n+        }\n+    }\n+}\n+\n+#[repr(C, packed)]\n+pub(crate) struct ChallengeRsp {\n+    pub(crate) version: u8,\n+    pub(crate) code: u8,\n+    pub(crate) param1: u8,\n+    pub(crate) param2: u8,\n+\n+    pub(crate) cert_chain_hash: __IncompleteArrayField<u8>,\n+    pub(crate) nonce: [u8; 32],\n+    pub(crate) message_summary_hash: __IncompleteArrayField<u8>,\n+\n+    pub(crate) opaque_data_len: u16,\n+    pub(crate) opaque_data: __IncompleteArrayField<u8>,\n+\n+    pub(crate) context: [u8; 8],\n+    pub(crate) signature: __IncompleteArrayField<u8>,\n+}\n+\n+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut ChallengeRsp {\n+    type Err = Error;\n+\n+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {\n+        let raw = unvalidated.raw_mut();\n+        if raw.len() < mem::size_of::<ChallengeRsp>() {\n+            return Err(EINVAL);\n+        }\n+\n+        let ptr = raw.as_mut_ptr();\n+        // CAST: `ChallengeRsp` only contains integers and has `repr(C)`.\n+        let ptr = ptr.cast::<ChallengeRsp>();\n+        // SAFETY: `ptr` came from a reference and the cast above is valid.\n+        let rsp: &mut ChallengeRsp = unsafe { &mut *ptr };\n+\n+        Ok(rsp)\n+    }\n+}\ndiff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h\nindex 4e1519b2382d..ed2377be1b44 100644\n--- a/rust/bindings/bindings_helper.h\n+++ b/rust/bindings/bindings_helper.h\n@@ -31,6 +31,7 @@\n #include <linux/acpi.h>\n #include <linux/gpu_buddy.h>\n #include <crypto/hash.h>\n+#include <crypto/public_key.h>\n #include <drm/drm_device.h>\n #include <drm/drm_drv.h>\n #include <drm/drm_file.h>\n",
    "prefixes": [
        "18/18"
    ]
}