Linux Confidential Computing Development
 help / color / mirror / Atom feed
From: Jason Gunthorpe <jgg@nvidia.com>
To: "Reshetova, Elena" <elena.reshetova@intel.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@kernel.org>,
	"Jiang, Dave" <dave.jiang@intel.com>,
	"linux-coco@lists.linux.dev" <linux-coco@lists.linux.dev>,
	Jiri Pirko <jiri@resnulli.us>,
	"Tian, Kevin" <kevin.tian@intel.com>,
	Alexey Kardashevskiy <aik@amd.com>,
	Xu Yilun <yilun.xu@linux.intel.com>,
	Catalin Marinas <catalin.marinas@arm.com>,
	Suzuki K Poulose <suzuki.poulose@arm.com>,
	Steven Price <steven.price@arm.com>,
	Kiryl Shutsemau <kas@kernel.org>,
	"Edgecombe, Rick P" <rick.p.edgecombe@intel.com>,
	Dave Hansen <dave.hansen@linux.intel.com>,
	Jonathan Cameron <jic23@kernel.org>,
	Bjorn Helgaas <bhelgaas@google.com>,
	Ashish Kalra <ashish.kalra@amd.com>,
	Tom Lendacky <thomas.lendacky@amd.com>,
	John Allen <john.allen@amd.com>, Will Deacon <will@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	"patches@lists.linux.dev" <patches@lists.linux.dev>
Subject: Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance
Date: Mon, 2 Nov 2026 13:26:40 -0400	[thread overview]
Message-ID: <20261102172640.GB274202@nvidia.com> (raw)
In-Reply-To: <DS4PPFA84FA90FCEFB18E5620217FDCA728E76A2@DS4PPFA84FA90FC.namprd11.prod.outlook.com>

On Mon, Nov 02, 2026 at 10:11:44AM +0000, Reshetova, Elena wrote:
> > > AI tells me there are a couple of reasonable ways to implement slot
> > > selection and avoid downgrade attacks:
> > >
> > > 1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
> > >    1) Do via RustSPDM:
> > >         GET_DIGESTS / DIGESTS
> > >         GET_CERTIFICATE / CERTIFICATE
> > >    2) Userspace selects slots
> > >    3) TSM module does startup and also issues 'GET_DIGESTS' as part of
> > >       the signed transcript
> > >    4) Confirm that the digests from #1 match the digests from #2 in
> > >       the TSM transcript.
> > >
> > 
> > I am currently doing something similar to the above.
> > 
> > https://git.gitlab.arm.com/linux-arm/linux-cca/-
> > /commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6
> 
> If I understand this correctly, this is not quite the option 1 that
> Jason proposed above. You dont use GET_DIGESTS and I dont see
> a binding between cert chain discovered prior to TSM SPDM
> session and the one during the TSM SPDM session. 
> Or am I misunderstanding it? 

Right..

I have poked at this alot more and it seems a bit different than I
originally was told, especially if we narrow it to RMM only.

For SPDM 1.2 AI found me this statement:

 SPDM 1.2 para 304 (and 1.4 para 369 when multi-key is off) says the
 device "shall contain a single public-private key pair per supported
 algorithm for its leaf certificate, regardless of how many certificate
 chains are stored".

Since there is only ever one private key active, the only thing we are
trying to do here is to prevent a "fake" certificate chain
that isn't actually installed in the device. The 1.2 spec provides
CHALLENGE which solves this issue, it signs all the certificates with
the single device key.

So "all" we need to do is get the certificates with RustSPDM and then
use CHALLENGE to get it signed by the single key in the device.

This is the best RMM supporting SPDM 1.2 can do.

This is almost simple, except that RMM and Rust SPDM may negotiate
different algorithms and may see different certificates and private
keys because of this. The main challenge will be synchronizing so that
RustSPDM uses the same algorithms as RMM. The spec didn't really
expect this split, it was supposed to be one session as far as I can
tell. So it's hard.

RMM will need an ECN to have an optional negotiable feature to support
1.3 multikey/multislot. In this flow RMM must do the
GET_DIGESTS/CERTIFICATES as part of its signed transcript when
supporting multi-key.

So the logical design is to have RMM run its FSM past CERTIFICATES and
then stop. Userspace gets to see all the stuff it collected and select
the slot. Probably via RMI_PDEV_SET_PUBKEY we tell RMM the correct
slot and mulit-key to use with KEY_EXCHANGE, instead of telling it
during PDEV_CREATE.

From there it is a small step for RMM to support 1.2 devices by also
having it issue the GET_DIGESTS/CERTIFICATES/CHALLENGE flow in the
same way.

Further, if you have a transcript that includes GET_DIGESTS then you
don't *have* to select the slot early. The early slot selection mixes
the certificate hash into the KEY_EXCHANGE, but if we did GET_DIGESTS
then we already have that mixed in. Once you have a signed GET_DIGESTS
then you can just request measurements on whatever slot you like
without any relay attack.

Unfortunately RMM doesn't provide an API for the guest to select the
slot during GET_MEASUREMENTs and we don't have any way to pass the
certificates from other slots to the guest. So from a guest RMM
perspective the slot is fixed, attestation subsystem should report the
single fixed slot (maybe not zero) and the other slots are blocked.

Anyhow, below is some AI assisted flow how you can incorporate rust
spdm and properly bind the CHALLENGE. I'm not really sure this is
worth implementing, I'd like to hear from ARM spec folks what the plan
is for multi-slot and multi-key. Proper spec support, including guest
discovery is far better.

So.. I think we should start with slot undiscoverable and effectively
wired to zero. TSMs that want to support discoverable slot really
should to incorporate the above into their spec'd SPDM flow and it
will work without requiring RustSPDM.

RustSPDM Should be used to demonstrate the flow where we start a
session, get to CERTIFICATES, stop, then let userspace select the
slot, then continue to KEY_EXCHANGE. This guarentees that the
certificates are hashed into the KEY_EXCHANGE for multi-key, so the
uAPI must support this kind of flow.

Elena is it OK?

Jason

Host-side slot selection. The host is always responsible to choose the slot.
RustSPDM controls slot selection, but the RMM establishes the operational SPDM
session.

With a single device key, the slot chooses a certificate chain, not a key.
Slot selection is therefore about getting the right certificate chain hashed
into the RMM's transcript (H(chain[S]) in TH1) and attested as
identity_digest. The goal is to make sure that chain is the one userspace
selected, from a complete and untampered list of the device's chains. No
attacker on the path can steer the selection by hiding slots, or substitute a
different chain into the RMM's transcript.

The selected slot S defines the RMM's transcript: KEY_EXCHANGE(S) puts
H(chain[S]) into TH1, and the RMM records chain[S] as identity_digest. With a
single key, though, S does not decide which key the guest trusts. Every slot
certifies the same K, and K is what the RMM attests (pubkey_digest) and what
signs the session and measurements. The guest is not limited to chain[S]: any
other slot's chain whose leaf is K is equally usable by the guest to establish
trust in K, if the guest can learn those certificates. Those chains need no
RMM attestation, because they authenticate themselves against K. The guest
only has to check that the leaf is K and that the chain passes its policy.
The host decides which chain the RMM attests, not which identities the guest
may accept. Today RHI only delivers chain[S] (RHI_DA_DEV_CERTIFICATE), so
other chains need a new RHI object or an out-of-band path. Since they do not
need attestation, an unauthenticated delivery is fine.

The attacker is anything on the host<->device path (DOE mailbox, switches,
interposers) that cannot sign with the device's leaf key. The host itself is
not defended against, that is the guest's problem.

Approach: require a single leaf key. Without MULTI_KEY, SPDM mandates one
public-private key pair per supported algorithm for all slots (SPDM 1.2 para
304, SPDM 1.4 para 369). Slots only differ in which CA chain vouches for that
key. The binding rule is then:

  The key the RMM verifies at KEY_EXCHANGE must be the same key K that signed
  RustSPDM's CHALLENGE_AUTH.

RustSPDM and the RMM run separate SPDM connections, sequentially.

Scope: this design is only for connections where MULTI_KEY_CONN_RSP is false:
 - SPDM 1.2 and older devices, which have no multi-key support.
 - SPDM 1.3+ devices with MULTI_KEY_CAP=00b.
 - SPDM 1.3+ devices with MULTI_KEY_CAP=10b when the RMM leaves
   ResponderMultiKeyConn clear.

We expect an RMM ABI change before the RMM supports multi-key. The kernel
must explicitly opt in to it, for example via a PDEV creation flag, and the
default must remain ResponderMultiKeyConn clear. A kernel that does not opt in
then always gets a single-key connection, and this design stays valid. A
kernel that opts in takes on the DIGESTS-in-TH1 flow below and does not use
this design. Until that ABI exists, check that ResponderMultiKeyConn is clear
in the RMM's ALGORITHMS anyway (#3), and fail if it is not.

SPDM 1.3 added multi-key, and with it the right way to do this. When
MULTI_KEY_CONN_RSP is true and the requester issues GET_DIGESTS immediately
after VCA, that DIGESTS is included in TH1 (SPDM 1.4 section 12.4, para 1239):
  TH1 = VCA || DIGESTS || H(chain[S]) || KEY_EXCHANGE || KEY_EXCHANGE_RSP
The KEY_EXCHANGE_RSP signature by slot S's key then covers the whole slot
inventory, with freshness from the RMM's KEY_EXCHANGE random. No RustSPDM
CHALLENGE is needed, and if the RMM cached and attested DIGESTS, the guest
could verify the inventory too, without the replay problem in Limitation 4.
However RMM 2.1-alp2 does not issue GET_DIGESTS (it was removed by
FENIMORE-868). Using this needs an RMM spec change: issue GET_DIGESTS
immediately after VCA when MultiKeyConn is negotiated, and cache and attest
DIGESTS alongside the VCA.

Part of the multi-key enhancement for RMM must include moving the slot selectg
from PDEV_CRETATE to later, after the RMM has issued
GET_DIGESTS/GET_CERTIFICATES. Possibly to RMI_PDEV_SET_PUBKEY. This will avoid
any need for RustSPDM on multi-key devices.

1. Run this sequence through RustSPDM:

    GET_VERSION / VERSION                          (VCA: Version)
    GET_CAPABILITIES / CAPABILITIES                (VCA: Capabilities)
    NEGOTIATE_ALGORITHMS / ALGORITHMS              (VCA: Algorithms)
    GET_DIGESTS / DIGESTS
    GET_CERTIFICATE / CERTIFICATE                  (every slot in the slot mask, all chunks)
    CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH

   M2 = VCA || DIGESTS || all CERTIFICATE || CHALLENGE/CHALLENGE_AUTH, the
   (A1, B1, C1) ordering in SPDM 1.2 Table 38.

   NEGOTIATE_ALGORITHMS should offer the same BaseAsymAlgo/BaseHashAlgo set
   and version ceiling as the RMM's request, so both connections converge on
   the same algorithm and therefore the same key. A mismatch is not a security
   problem, it just makes #4 fail. Only offer algorithms that are acceptable,
   because each one is a separate key that an attacker could steer towards.

   For 1.3+ responders:
    - MULTI_KEY_CAP=01b (always multikey): Cannot be supported without RMM
      support, fail.
    - MULTI_KEY_CAP=10b (requester chooses multikey): leave
      ResponderMultiKeyConn clear.

   Verify:
    - The CHALLENGE_AUTH signature verifies with slot 0's leaf key. Call this
      key K.
    - CHALLENGE_AUTH Param2 slot mask matches DIGESTS, and CertChainHash ==
      H(chain[0]).
    - H(chain[n]) == DIGESTS[n] for every slot in the slot mask.
    - Every chain[n] validates, and its leaf public key == K. This is the
      single key check that the whole design rests on. If it fails, the device
      is either multi-key or violating para 304, so reject it.

   Retain the VCA, DIGESTS, all certificates, K and the CHALLENGE transcript
   immutably as part of the SPDM connection.

2. Provide the slot certificate information to userspace so it can select a
   slot S.

3. Initialize the RMM with the selected slot. From here on RustSPDM must not
   send anything to the device. The RMM's GET_VERSION ends RustSPDM's
   connection.

    RMI_PDEV_CREATE(id_index = S)
    RMM  GET_VERSION / VERSION                     (RMM VCA)
    RMM  GET_CAPABILITIES / CAPABILITIES           (RMM VCA)
    RMM  NEGOTIATE_ALGORITHMS / ALGORITHMS         (RMM VCA)
    RMM  GET_CERTIFICATE / CERTIFICATE             (slot S, all chunks)

   The RMM asks the host to cache RMI_DEV_PROTOCOL_DATA (the VCA) and
   RMI_DEV_IDENTITY (chain[S]). Check:
    - For 1.3+ responders, ResponderMultiKeyConn is clear in the RMM's
      ALGORITHMS.
    - The DER certificates in the RMM's chain are byte-identical to chain[S]
      from #1. RootHash may differ if the RMM negotiated a different
      BaseHashAlgo, so compare the certificates portion. This check only
      confirms that userspace's view matches. It is not the security check,
      TH1 in #4 is.

   Retain the RMM's VCA immutably on the SPDM connection.

4. When the RMM reaches PDEV_NEEDS_KEY, call RMI_PDEV_SET_PUBKEY with K from
   #1. Do not extract the key from the RMM's cached RMI_DEV_IDENTITY. The RMM
   does not parse X.509, it only checks that the key encoding fits the
   negotiated algorithm (RmiPublicKeyParamsIsValid).

    RMM  KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP   (clear text)
    RMM  FINISH / FINISH_RSP                       (in session, opaque to host)

   The RMM verifies the KEY_EXCHANGE_RSP signature with K over:
     TH1 = RMM VCA || H(RMM's chain[S]) || KEY_EXCHANGE || KEY_EXCHANGE_RSP
           (excluding Signature and ResponderVerifyData)
   The device computes TH1 with H(its own chain[S]), so a substituted chain,
   a modified VCA or a device without K all fail here.

   If RustSPDM and the RMM negotiated different algorithms, or the RMM
   negotiated MultiKeyConn and slot S uses a different key, SET_PUBKEY or
   KEY_EXCHANGE fails.

   KEY_EXCHANGE and KEY_EXCHANGE_RSP are clear text, so the host can also
   verify this signature with K. This is defence in depth against RMM bugs
   and makes the retained transcript self-verifying. It is not required by
   the security argument, which relies on the RMM's verification (DEN0137
   IZJNMR).

5. Report to userspace that the connection is open with slot S, and allow it
   to view the immutable data from the connection: all #1 certificates, the
   slot layout and the RMM's negotiated version and algorithms.

   The kernel retains the #1 CHALLENGE transcript and the RMM's
   KEY_EXCHANGE/KEY_EXCHANGE_RSP transcript. They are not needed by userspace,
   which trusts the kernel's verification. They exist for debugging and for
   a possible future RHI object that lets the guest verify the inventory
   itself (Limitation 4).

Security argument: K signs RustSPDM's slot discovery (VCA, DIGESTS, every
certificate) and, through TH1, the RMM's VCA and selected chain. An attacker
without K cannot:
 - hide or alter a slot in #1, because DIGESTS and the slot mask are signed by K;
 - substitute the chain the RMM fetches, because Ct in TH1 is signed by K;
 - alter either connection's negotiation, because both VCAs are signed by K;
 - put a different device behind the RMM, because it cannot sign with K.
The single key check in #1 ensures K is the key behind every slot, so the
slot 0 challenge vouches for the whole inventory, including S.

The CHALLENGE is mandatory because it is the only thing that proves the
certificate list is untampered. Each chain authenticates itself against K, so
a modified or added chain already fails chain validation or the leaf == K
check. Removal is different: without the signed DIGESTS and slot mask, an
attacker can silently drop slots. The CHALLENGE proves the list userspace sees
is complete and was produced by the device holding K, at the time of #1.
Policies that depend on what is present, for example "the owner slot must be
provisioned" or "reject if the vendor chain carries attribute X", or tooling
that confirms a SET_CERTIFICATE took effect, rely on that completeness.

The host hands
K to the RMM at PDEV_NEEDS_KEY, which binds the RMM's session to the device
and inventory that userspace selected from. Trust in K comes from the selected
chain passing the verifier's trust policy.

Limitations:
1) Connections with MULTI_KEY_CONN_RSP true are not supported. They should use
   DIGESTS in TH1 (see Scope), which needs RMM support.
2) Slots only differ in which CA vouches for K. They protect against a
   compromised CA, not a compromised device key. A leaked K lets an attacker
   impersonate the entire device and SPDM session so everything is broken.
3) Trust in K comes only from the verifier's policy on the chain. An attacker
   holding any CA the verifier accepts can impersonate the whole device, with
   its own key and a self-consistent inventory that hides the other slots.
   Signing the inventory cannot detect this. Only policy (pinning the expected
   root/slot per device) can.
4) The guest only sees id_index, chain[S] (identity_digest), K (pubkey_digest)
   and the RMM's VCA (protocol_data_digest). The RMM never captures DIGESTS,
   so the guest cannot see the inventory directly.
   With a single key this only matters to guest policies that depend on
   completeness (see Security argument). Which key is trusted does not
   depend on it. Every slot certifies the same K, so the host can hand the
   guest any other chain for K unauthenticated, and the guest just checks
   that its leaf is K. The host can still withhold chains, though.
   Partial fix for completeness: give the guest the #1 CHALLENGE transcript over RHI. This
   needs a new RHI object type, because RHI 1.0-bet3 only has VCA,
   CERTIFICATE, MEASUREMENTS and INTERFACE_REPORT. It is signed by K, and K
   is RMM-attested, so the guest can verify the inventory.
   However the host chooses the nonce, so the host could replay an older
   inventory.
5) Requires slot 0 to be provisioned (SPDM says it is by default) and the
   responder to set CHAL_CAP.

  reply	other threads:[~2026-11-02 17:26 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance Aneesh Kumar K.V (Arm)
2026-10-30 13:29   ` Jiri Pirko
2026-10-28 15:26 ` [RFC PATCH v1 02/11] attestation: Filter evidence objects " Aneesh Kumar K.V (Arm)
2026-10-30 13:44   ` Jiri Pirko
2026-10-31  6:24     ` Aneesh Kumar K.V
2026-11-02 19:14       ` Jiri Pirko
2026-10-28 15:26 ` [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted Aneesh Kumar K.V (Arm)
2026-10-30 13:51   ` Jiri Pirko
2026-10-30 15:36     ` Jason Gunthorpe
2026-10-31  4:09     ` Aneesh Kumar K.V
2026-11-02 11:28       ` Jiri Pirko
2026-10-28 15:26 ` [RFC PATCH v1 04/11] attestation: Bind object selection to accepted evidence snapshots Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 05/11] attestation: Make the evidence-get challenge attribute optional Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family Aneesh Kumar K.V (Arm)
2026-10-30 14:01   ` Jiri Pirko
2026-10-30 15:43     ` Jason Gunthorpe
2026-11-02 12:45       ` Jiri Pirko
2026-11-03 15:45         ` Jason Gunthorpe
2026-10-31  4:05     ` Aneesh Kumar K.V
2026-11-02 11:50       ` Jiri Pirko
2026-11-03 15:35       ` Jason Gunthorpe
2026-10-31  4:22     ` Aneesh Kumar K.V
2026-11-02 11:30       ` Jiri Pirko
2026-11-03 15:39       ` Jason Gunthorpe
2026-10-28 15:26 ` [RFC PATCH v1 07/11] attestation: Add attestation helpers for PCI/TSM integration Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts Aneesh Kumar K.V (Arm)
2026-10-28 22:56   ` Dave Jiang
2026-10-29  5:30     ` Aneesh Kumar K.V
2026-10-29 12:41       ` Jason Gunthorpe
2026-10-30 14:04   ` Jiri Pirko
2026-10-30 14:08   ` Jiri Pirko
2026-10-30 15:39     ` Jason Gunthorpe
2026-11-02 12:11       ` Jiri Pirko
2026-11-03 15:41         ` Jason Gunthorpe
2026-10-28 15:26 ` [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance Aneesh Kumar K.V (Arm)
2026-10-29 16:56   ` Dave Jiang
2026-10-29 17:10     ` Jason Gunthorpe
2026-10-30  9:58       ` Reshetova, Elena
2026-10-30 13:15         ` Jason Gunthorpe
2026-10-31  3:47           ` Aneesh Kumar K.V
2026-11-02 10:11             ` Reshetova, Elena
2026-11-02 17:26               ` Jason Gunthorpe [this message]
2026-11-03 12:01                 ` Reshetova, Elena
2026-11-03 15:16                   ` Jason Gunthorpe
2026-11-03 16:31                 ` Aneesh Kumar K.V
2026-11-03 17:56                   ` Jason Gunthorpe
2026-11-02 10:05           ` Reshetova, Elena
2026-11-02 10:22           ` Reshetova, Elena
2026-11-02 17:37             ` Jason Gunthorpe
2026-10-28 15:26 ` [RFC PATCH v1 11/11] virt: coco: arm-cca-guest: Refresh selected evidence against trusted digests Aneesh Kumar K.V (Arm)
2026-10-30 12:34 ` [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Xu Yilun
2026-10-30 15:32   ` Jason Gunthorpe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261102172640.GB274202@nvidia.com \
    --to=jgg@nvidia.com \
    --cc=aik@amd.com \
    --cc=aneesh.kumar@kernel.org \
    --cc=ashish.kalra@amd.com \
    --cc=bhelgaas@google.com \
    --cc=catalin.marinas@arm.com \
    --cc=dave.hansen@linux.intel.com \
    --cc=dave.jiang@intel.com \
    --cc=elena.reshetova@intel.com \
    --cc=jic23@kernel.org \
    --cc=jiri@resnulli.us \
    --cc=john.allen@amd.com \
    --cc=kas@kernel.org \
    --cc=kevin.tian@intel.com \
    --cc=linux-coco@lists.linux.dev \
    --cc=mark.rutland@arm.com \
    --cc=patches@lists.linux.dev \
    --cc=rick.p.edgecombe@intel.com \
    --cc=steven.price@arm.com \
    --cc=suzuki.poulose@arm.com \
    --cc=thomas.lendacky@amd.com \
    --cc=will@kernel.org \
    --cc=yilun.xu@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox