diff mbox series

[v2,07/18] spdm: Introduce library to authenticate devices

Message ID bbbea6e1b7d27463243a0fcb871ad2953312fe3a.1719771133.git.lukas@wunner.de
State New, archived
Headers show
Series PCI device authentication | expand

Commit Message

Lukas Wunner June 30, 2024, 7:42 p.m. UTC
From: Jonathan Cameron <Jonathan.Cameron@huawei.com>

The Security Protocol and Data Model (SPDM) allows for device
authentication, measurement, key exchange and encrypted sessions.

SPDM was conceived by the Distributed Management Task Force (DMTF).
Its specification defines a request/response protocol spoken between
host and attached devices over a variety of transports:

  https://www.dmtf.org/dsp/DSP0274

This implementation supports SPDM 1.0 through 1.3 (the latest version).
It is designed to be transport-agnostic as the kernel already supports
four different SPDM-capable transports:

* PCIe Data Object Exchange, which is a mailbox in PCI config space
  (PCIe r6.2 sec 6.30, drivers/pci/doe.c)
* Management Component Transport Protocol
  (MCTP, Documentation/networking/mctp.rst)
* TCP/IP (in draft stage)
  https://www.dmtf.org/sites/default/files/standards/documents/DSP0287_1.0.0WIP99.pdf
* SCSI and ATA (in draft stage)
  "SECURITY PROTOCOL IN/OUT" and "TRUSTED SEND/RECEIVE" commands

Use cases for SPDM include, but are not limited to:

* PCIe Component Measurement and Authentication (PCIe r6.2 sec 6.31)
* Compute Express Link (CXL r3.0 sec 14.11.6)
* Open Compute Project (Attestation of System Components v1.0)
  https://www.opencompute.org/documents/attestation-v1-0-20201104-pdf
* Open Compute Project (Datacenter NVMe SSD Specification v2.0)
  https://www.opencompute.org/documents/datacenter-nvme-ssd-specification-v2-0r21-pdf

The initial focus of this implementation is enabling PCIe CMA device
authentication.  As such, only a subset of the SPDM specification is
contained herein, namely the request/response sequence GET_VERSION,
GET_CAPABILITIES, NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE
and CHALLENGE.

This sequence first negotiates the SPDM protocol version, capabilities
and algorithms with the device.  It then retrieves the up to eight
certificate chains which may be provisioned on the device.  Finally it
performs challenge-response authentication with the device using one of
those eight certificate chains and the algorithms negotiated before.
The challenge-response authentication comprises computing a hash over
all exchanged messages to detect modification by a man-in-the-middle
or media error.  The hash is then signed with the device's private key
and the resulting signature is verified by the kernel using the device's
public key from the certificate chain.  Nonces are included in the
message sequence to protect against replay attacks.

A simple API is provided for subsystems wishing to authenticate devices:
spdm_create(), spdm_authenticate() (can be called repeatedly for
reauthentication) and spdm_destroy().  Certificates presented by devices
are validated against an in-kernel keyring of trusted root certificates.
A pointer to the keyring is passed to spdm_create().

The set of supported cryptographic algorithms is limited to those
declared mandatory in PCIe r6.2 sec 6.31.3.  Adding more algorithms
is straightforward as long as the crypto subsystem supports them.

Future commits will extend this implementation with support for
measurement, key exchange and encrypted sessions.

So far, only the SPDM requester role is implemented.  Care was taken to
allow for effortless addition of the responder role at a later stage.
This could be needed for a PCIe host bridge operating in endpoint mode.
The responder role will be able to reuse struct definitions and helpers
such as spdm_create_combined_prefix().

Credits:  Jonathan wrote a proof-of-concept of this SPDM implementation.
Lukas reworked it for upstream.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Co-developed-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 MAINTAINERS                 |  11 +
 include/linux/spdm.h        |  33 ++
 lib/Kconfig                 |  15 +
 lib/Makefile                |   2 +
 lib/spdm/Makefile           |  10 +
 lib/spdm/core.c             | 425 ++++++++++++++++++++++
 lib/spdm/req-authenticate.c | 704 ++++++++++++++++++++++++++++++++++++
 lib/spdm/spdm.h             | 520 ++++++++++++++++++++++++++
 8 files changed, 1720 insertions(+)
 create mode 100644 include/linux/spdm.h
 create mode 100644 lib/spdm/Makefile
 create mode 100644 lib/spdm/core.c
 create mode 100644 lib/spdm/req-authenticate.c
 create mode 100644 lib/spdm/spdm.h

Comments

Jeff Johnson June 30, 2024, 9:29 p.m. UTC | #1
On 6/30/24 12:42, Lukas Wunner wrote:
> From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> The Security Protocol and Data Model (SPDM) allows for device
> authentication, measurement, key exchange and encrypted sessions.
...
> diff --git a/lib/spdm/core.c b/lib/spdm/core.c
> new file mode 100644
> index 000000000000..f06402f6d127
> --- /dev/null
> +++ b/lib/spdm/core.c
> @@ -0,0 +1,425 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Core routines for message exchange, message transcript,
> + * signature verification and session state lifecycle
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
...
> +EXPORT_SYMBOL_GPL(spdm_destroy);
> +
> +MODULE_LICENSE("GPL");

missing MODULE_DESCRIPTION()
this will generate a warning when built as a module with make W=1
Alexey Kardashevskiy July 8, 2024, 9:57 a.m. UTC | #2
On 1/7/24 05:42, Lukas Wunner wrote:
> From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> The Security Protocol and Data Model (SPDM) allows for device
> authentication, measurement, key exchange and encrypted sessions.
> 
> SPDM was conceived by the Distributed Management Task Force (DMTF).
> Its specification defines a request/response protocol spoken between
> host and attached devices over a variety of transports:
> 
>    https://www.dmtf.org/dsp/DSP0274
> 
> This implementation supports SPDM 1.0 through 1.3 (the latest version).
> It is designed to be transport-agnostic as the kernel already supports
> four different SPDM-capable transports:
> 
> * PCIe Data Object Exchange, which is a mailbox in PCI config space
>    (PCIe r6.2 sec 6.30, drivers/pci/doe.c)
> * Management Component Transport Protocol
>    (MCTP, Documentation/networking/mctp.rst)
> * TCP/IP (in draft stage)
>    https://www.dmtf.org/sites/default/files/standards/documents/DSP0287_1.0.0WIP99.pdf
> * SCSI and ATA (in draft stage)
>    "SECURITY PROTOCOL IN/OUT" and "TRUSTED SEND/RECEIVE" commands
> 
> Use cases for SPDM include, but are not limited to:
> 
> * PCIe Component Measurement and Authentication (PCIe r6.2 sec 6.31)
> * Compute Express Link (CXL r3.0 sec 14.11.6)
> * Open Compute Project (Attestation of System Components v1.0)
>    https://www.opencompute.org/documents/attestation-v1-0-20201104-pdf
> * Open Compute Project (Datacenter NVMe SSD Specification v2.0)
>    https://www.opencompute.org/documents/datacenter-nvme-ssd-specification-v2-0r21-pdf
> 
> The initial focus of this implementation is enabling PCIe CMA device
> authentication.  As such, only a subset of the SPDM specification is
> contained herein, namely the request/response sequence GET_VERSION,
> GET_CAPABILITIES, NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE
> and CHALLENGE.
> 
> This sequence first negotiates the SPDM protocol version, capabilities
> and algorithms with the device.  It then retrieves the up to eight
> certificate chains which may be provisioned on the device.  Finally it
> performs challenge-response authentication with the device using one of
> those eight certificate chains and the algorithms negotiated before.
> The challenge-response authentication comprises computing a hash over
> all exchanged messages to detect modification by a man-in-the-middle
> or media error.  The hash is then signed with the device's private key
> and the resulting signature is verified by the kernel using the device's
> public key from the certificate chain.  Nonces are included in the
> message sequence to protect against replay attacks.
> 
> A simple API is provided for subsystems wishing to authenticate devices:
> spdm_create(), spdm_authenticate() (can be called repeatedly for
> reauthentication) and spdm_destroy().  Certificates presented by devices
> are validated against an in-kernel keyring of trusted root certificates.
> A pointer to the keyring is passed to spdm_create().
> 
> The set of supported cryptographic algorithms is limited to those
> declared mandatory in PCIe r6.2 sec 6.31.3.  Adding more algorithms
> is straightforward as long as the crypto subsystem supports them.
> 
> Future commits will extend this implementation with support for
> measurement, key exchange and encrypted sessions.
> 
> So far, only the SPDM requester role is implemented.  Care was taken to
> allow for effortless addition of the responder role at a later stage.
> This could be needed for a PCIe host bridge operating in endpoint mode.
> The responder role will be able to reuse struct definitions and helpers
> such as spdm_create_combined_prefix().
> 
> Credits:  Jonathan wrote a proof-of-concept of this SPDM implementation.
> Lukas reworked it for upstream.
> 
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Co-developed-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
> ---
>   MAINTAINERS                 |  11 +
>   include/linux/spdm.h        |  33 ++
>   lib/Kconfig                 |  15 +
>   lib/Makefile                |   2 +
>   lib/spdm/Makefile           |  10 +
>   lib/spdm/core.c             | 425 ++++++++++++++++++++++
>   lib/spdm/req-authenticate.c | 704 ++++++++++++++++++++++++++++++++++++
>   lib/spdm/spdm.h             | 520 ++++++++++++++++++++++++++
>   8 files changed, 1720 insertions(+)
>   create mode 100644 include/linux/spdm.h
>   create mode 100644 lib/spdm/Makefile
>   create mode 100644 lib/spdm/core.c
>   create mode 100644 lib/spdm/req-authenticate.c
>   create mode 100644 lib/spdm/spdm.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d6c90161c7bf..dbe16eea8818 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20145,6 +20145,17 @@ M:	Security Officers <security@kernel.org>
>   S:	Supported
>   F:	Documentation/process/security-bugs.rst
>   
> +SECURITY PROTOCOL AND DATA MODEL (SPDM)
> +M:	Jonathan Cameron <jic23@kernel.org>
> +M:	Lukas Wunner <lukas@wunner.de>
> +L:	linux-coco@lists.linux.dev
> +L:	linux-cxl@vger.kernel.org
> +L:	linux-pci@vger.kernel.org
> +S:	Maintained
> +T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
> +F:	include/linux/spdm.h
> +F:	lib/spdm/
> +
>   SECURITY SUBSYSTEM
>   M:	Paul Moore <paul@paul-moore.com>
>   M:	James Morris <jmorris@namei.org>
> diff --git a/include/linux/spdm.h b/include/linux/spdm.h
> new file mode 100644
> index 000000000000..0da7340020c4
> --- /dev/null
> +++ b/include/linux/spdm.h
> @@ -0,0 +1,33 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#ifndef _SPDM_H_
> +#define _SPDM_H_
> +
> +#include <linux/types.h>
> +
> +struct key;
> +struct device;
> +struct spdm_state;
> +
> +typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
> +				 const void *request, size_t request_sz,
> +				 void *response, size_t response_sz);
> +
> +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
> +			       void *transport_priv, u32 transport_sz,
> +			       struct key *keyring);
> +
> +int spdm_authenticate(struct spdm_state *spdm_state);
> +
> +void spdm_destroy(struct spdm_state *spdm_state);
> +
> +#endif
> diff --git a/lib/Kconfig b/lib/Kconfig
> index d33a268bc256..9011fa32af45 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -782,3 +782,18 @@ config POLYNOMIAL
>   
>   config FIRMWARE_TABLE
>   	bool
> +
> +config SPDM
> +	tristate
> +	select CRYPTO
> +	select KEYS
> +	select ASYMMETRIC_KEY_TYPE
> +	select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> +	select X509_CERTIFICATE_PARSER
> +	help
> +	  The Security Protocol and Data Model (SPDM) allows for device
> +	  authentication, measurement, key exchange and encrypted sessions.
> +
> +	  Crypto algorithms negotiated with SPDM are limited to those enabled
> +	  in .config.  Drivers selecting SPDM therefore need to also select
> +	  any algorithms they deem mandatory.
> diff --git a/lib/Makefile b/lib/Makefile
> index 3b1769045651..b2ef14d1fa71 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -301,6 +301,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
>   obj-$(CONFIG_ASN1) += asn1_decoder.o
>   obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
>   
> +obj-$(CONFIG_SPDM) += spdm/
> +
>   obj-$(CONFIG_FONT_SUPPORT) += fonts/
>   
>   hostprogs	:= gen_crc32table
> diff --git a/lib/spdm/Makefile b/lib/spdm/Makefile
> new file mode 100644
> index 000000000000..f579cc898dbc
> --- /dev/null
> +++ b/lib/spdm/Makefile
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# DMTF Security Protocol and Data Model (SPDM)
> +# https://www.dmtf.org/dsp/DSP0274
> +#
> +# Copyright (C) 2024 Intel Corporation
> +
> +obj-$(CONFIG_SPDM) += spdm.o
> +
> +spdm-y := core.o req-authenticate.o
> diff --git a/lib/spdm/core.c b/lib/spdm/core.c
> new file mode 100644
> index 000000000000..f06402f6d127
> --- /dev/null
> +++ b/lib/spdm/core.c
> @@ -0,0 +1,425 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Core routines for message exchange, message transcript,
> + * signature verification and session state lifecycle
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#include "spdm.h"
> +
> +#include <linux/dev_printk.h>
> +#include <linux/module.h>
> +
> +#include <crypto/hash.h>
> +#include <crypto/public_key.h>
> +
> +static int spdm_err(struct device *dev, struct spdm_error_rsp *rsp)
> +{
> +	switch (rsp->error_code) {
> +	case SPDM_INVALID_REQUEST:
> +		dev_err(dev, "Invalid request\n");
> +		return -EINVAL;
> +	case SPDM_INVALID_SESSION:
> +		if (rsp->version == 0x11) {
> +			dev_err(dev, "Invalid session %#x\n", rsp->error_data);
> +			return -EINVAL;
> +		}
> +		break;
> +	case SPDM_BUSY:
> +		dev_err(dev, "Busy\n");
> +		return -EBUSY;
> +	case SPDM_UNEXPECTED_REQUEST:
> +		dev_err(dev, "Unexpected request\n");
> +		return -EINVAL;
> +	case SPDM_UNSPECIFIED:
> +		dev_err(dev, "Unspecified error\n");
> +		return -EINVAL;
> +	case SPDM_DECRYPT_ERROR:
> +		dev_err(dev, "Decrypt error\n");
> +		return -EIO;
> +	case SPDM_UNSUPPORTED_REQUEST:
> +		dev_err(dev, "Unsupported request %#x\n", rsp->error_data);
> +		return -EINVAL;
> +	case SPDM_REQUEST_IN_FLIGHT:
> +		dev_err(dev, "Request in flight\n");
> +		return -EINVAL;
> +	case SPDM_INVALID_RESPONSE_CODE:
> +		dev_err(dev, "Invalid response code\n");
> +		return -EINVAL;
> +	case SPDM_SESSION_LIMIT_EXCEEDED:
> +		dev_err(dev, "Session limit exceeded\n");
> +		return -EBUSY;
> +	case SPDM_SESSION_REQUIRED:
> +		dev_err(dev, "Session required\n");
> +		return -EINVAL;
> +	case SPDM_RESET_REQUIRED:
> +		dev_err(dev, "Reset required\n");
> +		return -ECONNRESET;
> +	case SPDM_RESPONSE_TOO_LARGE:
> +		dev_err(dev, "Response too large\n");
> +		return -EINVAL;
> +	case SPDM_REQUEST_TOO_LARGE:
> +		dev_err(dev, "Request too large\n");
> +		return -EINVAL;
> +	case SPDM_LARGE_RESPONSE:
> +		dev_err(dev, "Large response\n");
> +		return -EMSGSIZE;
> +	case SPDM_MESSAGE_LOST:
> +		dev_err(dev, "Message lost\n");
> +		return -EIO;
> +	case SPDM_INVALID_POLICY:
> +		dev_err(dev, "Invalid policy\n");
> +		return -EINVAL;
> +	case SPDM_VERSION_MISMATCH:
> +		dev_err(dev, "Version mismatch\n");
> +		return -EINVAL;
> +	case SPDM_RESPONSE_NOT_READY:
> +		dev_err(dev, "Response not ready\n");
> +		return -EINPROGRESS;
> +	case SPDM_REQUEST_RESYNCH:
> +		dev_err(dev, "Request resynchronization\n");
> +		return -ECONNRESET;
> +	case SPDM_OPERATION_FAILED:
> +		dev_err(dev, "Operation failed\n");
> +		return -EINVAL;
> +	case SPDM_NO_PENDING_REQUESTS:
> +		return -ENOENT;
> +	case SPDM_VENDOR_DEFINED_ERROR:
> +		dev_err(dev, "Vendor defined error\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_err(dev, "Undefined error %#x\n", rsp->error_code);
> +	return -EINVAL;
> +}
> +
> +/**
> + * spdm_exchange() - Perform SPDM message exchange with device
> + *
> + * @spdm_state: SPDM session state
> + * @req: Request message
> + * @req_sz: Size of @req
> + * @rsp: Response message
> + * @rsp_sz: Size of @rsp
> + *
> + * Send the request @req to the device via the @transport in @spdm_state and
> + * receive the response into @rsp, respecting the maximum buffer size @rsp_sz.
> + * The request version is automatically populated.
> + *
> + * Return response size on success or a negative errno.  Response size may be
> + * less than @rsp_sz and the caller is responsible for checking that.  It may
> + * also be more than expected (though never more than @rsp_sz), e.g. if the
> + * transport receives only dword-sized chunks.
> + */
> +ssize_t spdm_exchange(struct spdm_state *spdm_state,
> +		      void *req, size_t req_sz, void *rsp, size_t rsp_sz)
> +{
> +	struct spdm_header *request = req;
> +	struct spdm_header *response = rsp;
> +	ssize_t rc, length;
> +
> +	if (req_sz < sizeof(struct spdm_header) ||
> +	    rsp_sz < sizeof(struct spdm_header))
> +		return -EINVAL;
> +
> +	request->version = spdm_state->version;
> +
> +	rc = spdm_state->transport(spdm_state->transport_priv, spdm_state->dev,
> +				   req, req_sz, rsp, rsp_sz);
> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	if (length < sizeof(struct spdm_header))
> +		return length; /* Truncated response is handled by callers */
> +
> +	if (response->code == SPDM_ERROR)
> +		return spdm_err(spdm_state->dev, (struct spdm_error_rsp *)rsp);
> +
> +	if (response->code != (request->code & ~SPDM_REQ)) {
> +		dev_err(spdm_state->dev,
> +			"Response code %#x does not match request code %#x\n",
> +			response->code, request->code);
> +		return -EPROTO;
> +	}
> +
> +	return length;
> +}
> +
> +/**
> + * spdm_alloc_transcript() - Allocate transcript buffer
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Allocate a buffer to accommodate the concatenation of all SPDM messages
> + * exchanged during an authentication sequence.  Used to verify the signature,
> + * as it is computed over the hashed transcript.
> + *
> + * Transcript size is initially one page.  It grows by additional pages as
> + * needed.  Minimum size of an authentication sequence is 1k (only one slot
> + * occupied, only one ECC P256 certificate in chain, SHA 256 hash selected).
> + * Maximum can be several MBytes.  Between 4k and 64k is probably typical.
> + *
> + * Return 0 on success or a negative errno.
> + */
> +int spdm_alloc_transcript(struct spdm_state *spdm_state)
> +{
> +	spdm_state->transcript = kvmalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (!spdm_state->transcript)
> +		return -ENOMEM;
> +
> +	spdm_state->transcript_end = spdm_state->transcript;
> +	spdm_state->transcript_max = PAGE_SIZE;
> +
> +	return 0;
> +}
> +
> +/**
> + * spdm_free_transcript() - Free transcript buffer
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Free the transcript buffer after performing authentication.  Reset the
> + * pointer to the current end of transcript as well as the allocation size.
> + */
> +void spdm_free_transcript(struct spdm_state *spdm_state)
> +{
> +	kvfree(spdm_state->transcript);
> +	spdm_state->transcript_end = NULL;
> +	spdm_state->transcript_max = 0;
> +}
> +
> +/**
> + * spdm_append_transcript() - Append a message to transcript buffer
> + *
> + * @spdm_state: SPDM session state
> + * @msg: SPDM message
> + * @msg_sz: Size of @msg
> + *
> + * Append an SPDM message to the transcript after reception or transmission.
> + * Reallocate a larger transcript buffer if the message exceeds its current
> + * allocation size.
> + *
> + * If the message to be appended is known to fit into the allocation size,
> + * it may be directly received into or transmitted from the transcript buffer
> + * instead of calling this function:  Simply use the @transcript_end pointer in
> + * struct spdm_state as the position to store the message, then advance the
> + * pointer by the message size.
> + *
> + * Return 0 on success or a negative errno.
> + */
> +int spdm_append_transcript(struct spdm_state *spdm_state,
> +			   const void *msg, size_t msg_sz)
> +{
> +	size_t transcript_sz = spdm_state->transcript_end -
> +			       spdm_state->transcript;
> +
> +	if (transcript_sz + msg_sz > spdm_state->transcript_max) {
> +		size_t new_sz = round_up(transcript_sz + msg_sz, PAGE_SIZE);
> +		void *new = kvrealloc(spdm_state->transcript,
> +				      spdm_state->transcript_max,
> +				      new_sz, GFP_KERNEL);
> +		if (!new)
> +			return -ENOMEM;
> +
> +		spdm_state->transcript = new;
> +		spdm_state->transcript_end = new + transcript_sz;
> +		spdm_state->transcript_max = new_sz;
> +	}
> +
> +	memcpy(spdm_state->transcript_end, msg, msg_sz);
> +	spdm_state->transcript_end += msg_sz;
> +
> +	return 0;
> +}
> +
> +/**
> + * spdm_create_combined_prefix() - Create combined_spdm_prefix for a hash
> + *
> + * @version: SPDM version negotiated during GET_VERSION exchange
> + * @spdm_context: SPDM context of signature generation (or verification)
> + * @buf: Buffer to receive combined_spdm_prefix (100 bytes)
> + *
> + * From SPDM 1.2, a hash is prefixed with the SPDM version and context before
> + * a signature is generated (or verified) over the resulting concatenation
> + * (SPDM 1.2.0 section 15).  Create that prefix.
> + */
> +void spdm_create_combined_prefix(u8 version, const char *spdm_context,
> +				 void *buf)
> +{
> +	u8 major = FIELD_GET(0xf0, version);
> +	u8 minor = FIELD_GET(0x0f, version);
> +	size_t len = strlen(spdm_context);
> +	int rc, zero_pad;
> +
> +	rc = snprintf(buf, SPDM_PREFIX_SZ + 1,
> +		      "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*"
> +		      "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*",
> +		      major, minor, major, minor, major, minor, major, minor);
> +	WARN_ON(rc != SPDM_PREFIX_SZ);
> +
> +	zero_pad = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - 1 - len;
> +	WARN_ON(zero_pad < 0);
> +
> +	memset(buf + SPDM_PREFIX_SZ + 1, 0, zero_pad);
> +	memcpy(buf + SPDM_PREFIX_SZ + 1 + zero_pad, spdm_context, len);
> +}
> +
> +/**
> + * spdm_verify_signature() - Verify signature against leaf key
> + *
> + * @spdm_state: SPDM session state
> + * @spdm_context: SPDM context (used to create combined_spdm_prefix)
> + *
> + * Implementation of the abstract SPDMSignatureVerify() function described in
> + * SPDM 1.2.0 section 16:  Compute the hash over @spdm_state->transcript and
> + * verify that the signature at the end of the transcript was generated by
> + * @spdm_state->leaf_key.  Hashing the entire transcript allows detection
> + * of message modification by a man-in-the-middle or media error.
> + *
> + * Return 0 on success or a negative errno.
> + */
> +int spdm_verify_signature(struct spdm_state *spdm_state,
> +			  const char *spdm_context)
> +{
> +	struct public_key_signature sig = {
> +		.s = spdm_state->transcript_end - spdm_state->sig_len,
> +		.s_size = spdm_state->sig_len,
> +		.encoding = spdm_state->base_asym_enc,
> +		.hash_algo = spdm_state->base_hash_alg_name,
> +	};
> +	u8 *mhash __free(kfree) = NULL;
> +	u8 *m __free(kfree);
> +	int rc;
> +
> +	m = kmalloc(SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len, GFP_KERNEL);
> +	if (!m)
> +		return -ENOMEM;
> +
> +	/* Hash the transcript (sans trailing signature) */
> +	rc = crypto_shash_digest(spdm_state->desc, spdm_state->transcript,
> +				 (void *)sig.s - spdm_state->transcript,
> +				 m + SPDM_COMBINED_PREFIX_SZ);
> +	if (rc)
> +		return rc;
> +
> +	if (spdm_state->version <= 0x11) {
> +		/*
> +		 * SPDM 1.0 and 1.1 compute the signature only over the hash
> +		 * (SPDM 1.0.0 section 4.9.2.7).
> +		 */
> +		sig.digest = m + SPDM_COMBINED_PREFIX_SZ;
> +		sig.digest_size = spdm_state->hash_len;
> +	} else {
> +		/*
> +		 * From SPDM 1.2, the hash is prefixed with spdm_context before
> +		 * computing the signature over the resulting message M
> +		 * (SPDM 1.2.0 sec 15).
> +		 */
> +		spdm_create_combined_prefix(spdm_state->version, spdm_context,
> +					    m);
> +
> +		/*
> +		 * RSA and ECDSA algorithms require that M is hashed once more.
> +		 * EdDSA and SM2 algorithms omit that step.
> +		 * The switch statement prepares for their introduction.
> +		 */
> +		switch (spdm_state->base_asym_alg) {
> +		default:
> +			mhash = kmalloc(spdm_state->hash_len, GFP_KERNEL);
> +			if (!mhash)
> +				return -ENOMEM;
> +
> +			rc = crypto_shash_digest(spdm_state->desc, m,
> +				SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len,
> +				mhash);
> +			if (rc)
> +				return rc;
> +
> +			sig.digest = mhash;
> +			sig.digest_size = spdm_state->hash_len;
> +			break;
> +		}
> +	}
> +
> +	return public_key_verify_signature(spdm_state->leaf_key, &sig);
> +}
> +
> +/**
> + * spdm_reset() - Free cryptographic data structures
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Free cryptographic data structures when an SPDM session is destroyed or
> + * when the device is reauthenticated.
> + */
> +void spdm_reset(struct spdm_state *spdm_state)
> +{
> +	public_key_free(spdm_state->leaf_key);
> +	spdm_state->leaf_key = NULL;
> +
> +	kfree(spdm_state->desc);
> +	spdm_state->desc = NULL;
> +
> +	crypto_free_shash(spdm_state->shash);
> +	spdm_state->shash = NULL;
> +}
> +
> +/**
> + * spdm_create() - Allocate SPDM session
> + *
> + * @dev: Responder device
> + * @transport: Transport function to perform one message exchange
> + * @transport_priv: Transport private data
> + * @transport_sz: Maximum message size the transport is capable of (in bytes)
> + * @keyring: Trusted root certificates
> + *
> + * Return a pointer to the allocated SPDM session state or NULL on error.
> + */
> +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
> +			       void *transport_priv, u32 transport_sz,
> +			       struct key *keyring)
> +{
> +	struct spdm_state *spdm_state = kzalloc(sizeof(*spdm_state), GFP_KERNEL);
> +
> +	if (!spdm_state)
> +		return NULL;
> +
> +	spdm_state->dev = dev;
> +	spdm_state->transport = transport;
> +	spdm_state->transport_priv = transport_priv;
> +	spdm_state->transport_sz = transport_sz;
> +	spdm_state->root_keyring = keyring;
> +
> +	mutex_init(&spdm_state->lock);
> +
> +	return spdm_state;
> +}
> +EXPORT_SYMBOL_GPL(spdm_create);
> +
> +/**
> + * spdm_destroy() - Destroy SPDM session
> + *
> + * @spdm_state: SPDM session state
> + */
> +void spdm_destroy(struct spdm_state *spdm_state)
> +{
> +	u8 slot;
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS)
> +		kvfree(spdm_state->slot[slot]);
> +
> +	spdm_reset(spdm_state);
> +	mutex_destroy(&spdm_state->lock);
> +	kfree(spdm_state);
> +}
> +EXPORT_SYMBOL_GPL(spdm_destroy);
> +
> +MODULE_LICENSE("GPL");
> diff --git a/lib/spdm/req-authenticate.c b/lib/spdm/req-authenticate.c
> new file mode 100644
> index 000000000000..51fdb88f519b
> --- /dev/null
> +++ b/lib/spdm/req-authenticate.c
> @@ -0,0 +1,704 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Requester role: Authenticate a device
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#include "spdm.h"
> +
> +#include <linux/dev_printk.h>
> +#include <linux/key.h>
> +#include <linux/random.h>
> +
> +#include <asm/unaligned.h>
> +#include <crypto/hash.h>
> +#include <crypto/hash_info.h>
> +#include <keys/asymmetric-type.h>
> +#include <keys/x509-parser.h>
> +
> +/* SPDM 1.2.0 margin no 359 and 803 */
> +static const char *spdm_context = "responder-challenge_auth signing";
> +
> +/*
> + * All SPDM messages exchanged during an authentication sequence up to and
> + * including GET_DIGESTS fit into a single page, hence are stored in the
> + * transcript without bounds checking.  Only subsequent GET_CERTIFICATE
> + * and CHALLENGE exchanges may exceed one page.
> + */
> +static_assert(PAGE_SIZE >=
> +	sizeof(struct spdm_get_version_req) +
> +	struct_size_t(struct spdm_get_version_rsp,
> +		      version_number_entries, 255) +
> +	sizeof(struct spdm_get_capabilities_req) +
> +	sizeof(struct spdm_get_capabilities_rsp) +
> +	sizeof(struct spdm_negotiate_algs_req) +
> +	sizeof(struct spdm_negotiate_algs_rsp) +
> +	sizeof(struct spdm_req_alg_struct) * 2 * SPDM_MAX_REQ_ALG_STRUCT +
> +	sizeof(struct spdm_get_digests_req) +
> +	struct_size_t(struct spdm_get_digests_rsp,
> +		      digests, SPDM_SLOTS * SHA512_DIGEST_SIZE));
> +
> +static int spdm_get_version(struct spdm_state *spdm_state)
> +{
> +	struct spdm_get_version_req *req = spdm_state->transcript;
> +	struct spdm_get_version_rsp *rsp;
> +	bool foundver = false;
> +	int rc, length, i;
> +
> +	spdm_state->version = 0x10;
> +
> +	*req = (struct spdm_get_version_req) {
> +		.code = SPDM_GET_VERSION,
> +	};
> +
> +	rsp = spdm_state->transcript_end += sizeof(*req);
> +
> +	rc = spdm_exchange(spdm_state, req, sizeof(*req), rsp,
> +			   struct_size(rsp, version_number_entries, 255));
> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	if (length < sizeof(*rsp) ||
> +	    length < struct_size(rsp, version_number_entries,
> +				 rsp->version_number_entry_count)) {
> +		dev_err(spdm_state->dev, "Truncated version response\n");
> +		return -EIO;
> +	}
> +
> +	spdm_state->transcript_end +=
> +		     struct_size(rsp, version_number_entries,
> +				 rsp->version_number_entry_count);
> +
> +	for (i = 0; i < rsp->version_number_entry_count; i++) {
> +		u8 ver = le16_to_cpu(rsp->version_number_entries[i]) >> 8;
> +
> +		if (ver >= spdm_state->version && ver <= SPDM_MAX_VER) {
> +			spdm_state->version = ver;
> +			foundver = true;
> +		}
> +	}
> +	if (!foundver) {
> +		dev_err(spdm_state->dev, "No common supported version\n");
> +		return -EPROTO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int spdm_get_capabilities(struct spdm_state *spdm_state)
> +{
> +	struct spdm_get_capabilities_req *req = spdm_state->transcript_end;
> +	struct spdm_get_capabilities_rsp *rsp;
> +	size_t req_sz, rsp_sz;
> +	int rc, length;
> +
> +	*req = (struct spdm_get_capabilities_req) {
> +		.code = SPDM_GET_CAPABILITIES,
> +		.ctexponent = SPDM_CTEXPONENT,
> +		.flags = cpu_to_le32(SPDM_REQ_CAPS),
> +	};
> +
> +	if (spdm_state->version == 0x10) {
> +		req_sz = offsetofend(typeof(*req), param2);
> +		rsp_sz = offsetofend(typeof(*rsp), flags);
> +	} else if (spdm_state->version == 0x11) {
> +		req_sz = offsetofend(typeof(*req), flags);
> +		rsp_sz = offsetofend(typeof(*rsp), flags);
> +	} else {
> +		req_sz = sizeof(*req);
> +		rsp_sz = sizeof(*rsp);
> +		req->data_transfer_size = cpu_to_le32(spdm_state->transport_sz);
> +		req->max_spdm_msg_size = cpu_to_le32(spdm_state->transport_sz);
> +	}
> +
> +	rsp = spdm_state->transcript_end += req_sz;
> +
> +	rc = spdm_exchange(spdm_state, req, req_sz, rsp, rsp_sz);
> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	if (length < rsp_sz) {
> +		dev_err(spdm_state->dev, "Truncated capabilities response\n");
> +		return -EIO;
> +	}
> +
> +	spdm_state->transcript_end += rsp_sz;
> +
> +	spdm_state->rsp_caps = le32_to_cpu(rsp->flags);
> +	if ((spdm_state->rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS)
> +		return -EPROTONOSUPPORT;
> +
> +	if (spdm_state->version >= 0x12) {
> +		u32 data_transfer_size = le32_to_cpu(rsp->data_transfer_size);
> +		if (data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE) {
> +			dev_err(spdm_state->dev,
> +				"Malformed capabilities response\n");
> +			return -EPROTO;
> +		}
> +		spdm_state->transport_sz = min(spdm_state->transport_sz,
> +					       data_transfer_size);
> +	}
> +
> +	return 0;
> +}
> +
> +static int spdm_parse_algs(struct spdm_state *spdm_state)
> +{
> +	switch (spdm_state->base_asym_alg) {
> +	case SPDM_ASYM_RSASSA_2048:
> +		spdm_state->sig_len = 256;
> +		spdm_state->base_asym_enc = "pkcs1";
> +		break;
> +	case SPDM_ASYM_RSASSA_3072:
> +		spdm_state->sig_len = 384;
> +		spdm_state->base_asym_enc = "pkcs1";
> +		break;
> +	case SPDM_ASYM_RSASSA_4096:
> +		spdm_state->sig_len = 512;
> +		spdm_state->base_asym_enc = "pkcs1";
> +		break;
> +	case SPDM_ASYM_ECDSA_ECC_NIST_P256:
> +		spdm_state->sig_len = 64;
> +		spdm_state->base_asym_enc = "p1363";
> +		break;
> +	case SPDM_ASYM_ECDSA_ECC_NIST_P384:
> +		spdm_state->sig_len = 96;
> +		spdm_state->base_asym_enc = "p1363";
> +		break;
> +	case SPDM_ASYM_ECDSA_ECC_NIST_P521:
> +		spdm_state->sig_len = 132;
> +		spdm_state->base_asym_enc = "p1363";
> +		break;
> +	default:
> +		dev_err(spdm_state->dev, "Unknown asym algorithm\n");
> +		return -EINVAL;
> +	}
> +
> +	switch (spdm_state->base_hash_alg) {
> +	case SPDM_HASH_SHA_256:
> +		spdm_state->base_hash_alg_name = "sha256";
> +		break;
> +	case SPDM_HASH_SHA_384:
> +		spdm_state->base_hash_alg_name = "sha384";
> +		break;
> +	case SPDM_HASH_SHA_512:
> +		spdm_state->base_hash_alg_name = "sha512";
> +		break;
> +	default:
> +		dev_err(spdm_state->dev, "Unknown hash algorithm\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * shash and desc allocations are reused for subsequent measurement
> +	 * retrieval, hence are not freed until spdm_reset().
> +	 */
> +	spdm_state->shash = crypto_alloc_shash(spdm_state->base_hash_alg_name,
> +					       0, 0);
> +	if (!spdm_state->shash)
> +		return -ENOMEM;
> +
> +	spdm_state->desc = kzalloc(sizeof(*spdm_state->desc) +
> +				   crypto_shash_descsize(spdm_state->shash),
> +				   GFP_KERNEL);
> +	if (!spdm_state->desc)
> +		return -ENOMEM;
> +
> +	spdm_state->desc->tfm = spdm_state->shash;
> +
> +	/* Used frequently to compute offsets, so cache H */
> +	spdm_state->hash_len = crypto_shash_digestsize(spdm_state->shash);
> +
> +	return crypto_shash_init(spdm_state->desc);
> +}
> +
> +static int spdm_negotiate_algs(struct spdm_state *spdm_state)
> +{
> +	struct spdm_negotiate_algs_req *req = spdm_state->transcript_end;
> +	struct spdm_negotiate_algs_rsp *rsp;
> +	struct spdm_req_alg_struct *req_alg_struct;
> +	size_t req_sz = sizeof(*req);
> +	size_t rsp_sz = sizeof(*rsp);
> +	int rc, length;
> +
> +	/* Request length shall be <= 128 bytes (SPDM 1.1.0 margin no 185) */
> +	BUILD_BUG_ON(req_sz > 128);
> +
> +	*req = (struct spdm_negotiate_algs_req) {
> +		.code = SPDM_NEGOTIATE_ALGS,
> +		.length = cpu_to_le16(req_sz),
> +		.base_asym_algo = cpu_to_le32(SPDM_ASYM_ALGOS),
> +		.base_hash_algo = cpu_to_le32(SPDM_HASH_ALGOS),
> +	};
> +
> +	rsp = spdm_state->transcript_end += req_sz;

= and += in one statament just hurts to read but ok :)

> +
> +	rc = spdm_exchange(spdm_state, req, req_sz, rsp, rsp_sz);

rsp_sz is 36 bytes here. And spdm_exchange() cannot return more than 36 
because this is how pci_doe() works...

> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	if (length < sizeof(*rsp) ||
> +	    length < sizeof(*rsp) + rsp->param1 * sizeof(*req_alg_struct)) {
> +		dev_err(spdm_state->dev, "Truncated algorithms response\n");

... but here you expect more than 36 as realistically rsp->param1 > 0.
How was this tested and what do I miss here? Thanks,



> +		return -EIO;
> +	}
> +
> +	/*
> +	 * If request contained a ReqAlgStruct not supported by responder,
> +	 * the corresponding RespAlgStruct may be omitted in response.
> +	 * Calculate the actual (possibly shorter) response length:
> +	 */
> +	spdm_state->transcript_end +=
> +		     sizeof(*rsp) + rsp->param1 * sizeof(*req_alg_struct);
> +
> +	spdm_state->base_asym_alg = le32_to_cpu(rsp->base_asym_sel);
> +	spdm_state->base_hash_alg = le32_to_cpu(rsp->base_hash_sel);
> +
> +	if ((spdm_state->base_asym_alg & SPDM_ASYM_ALGOS) == 0 ||
> +	    (spdm_state->base_hash_alg & SPDM_HASH_ALGOS) == 0) {
> +		dev_err(spdm_state->dev, "No common supported algorithms\n");
> +		return -EPROTO;
> +	}
> +
> +	/* Responder shall select exactly 1 alg (SPDM 1.0.0 table 14) */
> +	if (hweight32(spdm_state->base_asym_alg) != 1 ||
> +	    hweight32(spdm_state->base_hash_alg) != 1 ||
> +	    rsp->ext_asym_sel_count != 0 ||
> +	    rsp->ext_hash_sel_count != 0 ||
> +	    rsp->param1 > req->param1) {
> +		dev_err(spdm_state->dev, "Malformed algorithms response\n");
> +		return -EPROTO;
> +	}
> +
> +	return spdm_parse_algs(spdm_state);
> +}
> +
> +static int spdm_get_digests(struct spdm_state *spdm_state)
> +{
> +	struct spdm_get_digests_req *req = spdm_state->transcript_end;
> +	struct spdm_get_digests_rsp *rsp;
> +	unsigned long deprovisioned_slots;
> +	int rc, length;
> +	size_t rsp_sz;
> +	u8 slot;
> +
> +	*req = (struct spdm_get_digests_req) {
> +		.code = SPDM_GET_DIGESTS,
> +	};
> +
> +	rsp = spdm_state->transcript_end += sizeof(*req);
> +
> +	/*
> +	 * Assume all 8 slots are populated.  We know the hash length (and thus
> +	 * the response size) because the responder only returns digests for
> +	 * the hash algorithm selected during the NEGOTIATE_ALGORITHMS exchange
> +	 * (SPDM 1.1.2 margin no 206).
> +	 */
> +	rsp_sz = sizeof(*rsp) + SPDM_SLOTS * spdm_state->hash_len;
> +
> +	rc = spdm_exchange(spdm_state, req, sizeof(*req), rsp, rsp_sz);
> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	if (length < sizeof(*rsp) ||
> +	    length < sizeof(*rsp) + hweight8(rsp->param2) *
> +				    spdm_state->hash_len) {
> +		dev_err(spdm_state->dev, "Truncated digests response\n");
> +		return -EIO;
> +	}
> +
> +	spdm_state->transcript_end += sizeof(*rsp) + hweight8(rsp->param2) *
> +						     spdm_state->hash_len;
> +
> +	deprovisioned_slots = spdm_state->provisioned_slots & ~rsp->param2;
> +	for_each_set_bit(slot, &deprovisioned_slots, SPDM_SLOTS) {
> +		kvfree(spdm_state->slot[slot]);
> +		spdm_state->slot_sz[slot] = 0;
> +		spdm_state->slot[slot] = NULL;
> +	}
> +
> +	/*
> +	 * Authentication-capable endpoints must carry at least 1 cert chain
> +	 * (SPDM 1.0.0 section 4.9.2.1).
> +	 */
> +	spdm_state->provisioned_slots = rsp->param2;
> +	if (!spdm_state->provisioned_slots) {
> +		dev_err(spdm_state->dev, "No certificates provisioned\n");
> +		return -EPROTO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int spdm_get_certificate(struct spdm_state *spdm_state, u8 slot)
> +{
> +	struct spdm_cert_chain *certs __free(kvfree) = NULL;
> +	struct spdm_get_certificate_rsp *rsp __free(kvfree);
> +	struct spdm_get_certificate_req req = {
> +		.code = SPDM_GET_CERTIFICATE,
> +		.param1 = slot,
> +	};
> +	size_t rsp_sz, total_length, header_length;
> +	u16 remainder_length = 0xffff;
> +	u16 portion_length;
> +	u16 offset = 0;
> +	int rc, length;
> +
> +	/*
> +	 * It is legal for the responder to send more bytes than requested.
> +	 * (Note the "should" in SPDM 1.0.0 table 19.)  If we allocate a
> +	 * too small buffer, we can't calculate the hash over the (truncated)
> +	 * response.  Only choice is thus to allocate the maximum possible 64k.
> +	 */
> +	rsp_sz = min_t(u32, sizeof(*rsp) + 0xffff, spdm_state->transport_sz);
> +	rsp = kvmalloc(rsp_sz, GFP_KERNEL);
> +	if (!rsp)
> +		return -ENOMEM;
> +
> +	do {
> +		/*
> +		 * If transport_sz is sufficiently large, first request will be
> +		 * for offset 0 and length 0xffff, which means entire cert
> +		 * chain (SPDM 1.0.0 table 18).
> +		 */
> +		req.offset = cpu_to_le16(offset);
> +		req.length = cpu_to_le16(min_t(size_t, remainder_length,
> +					       rsp_sz - sizeof(*rsp)));
> +
> +		rc = spdm_exchange(spdm_state, &req, sizeof(req), rsp, rsp_sz);
> +		if (rc < 0)
> +			return rc;
> +
> +		length = rc;
> +		if (length < sizeof(*rsp) ||
> +		    length < sizeof(*rsp) + le16_to_cpu(rsp->portion_length)) {
> +			dev_err(spdm_state->dev,
> +				"Truncated certificate response\n");
> +			return -EIO;
> +		}
> +
> +		portion_length = le16_to_cpu(rsp->portion_length);
> +		remainder_length = le16_to_cpu(rsp->remainder_length);
> +
> +		rc = spdm_append_transcript(spdm_state, &req, sizeof(req));
> +		if (rc)
> +			return rc;
> +
> +		rc = spdm_append_transcript(spdm_state, rsp,
> +					    sizeof(*rsp) + portion_length);
> +		if (rc)
> +			return rc;
> +
> +		/*
> +		 * On first response we learn total length of cert chain.
> +		 * Should portion_length + remainder_length exceed 0xffff,
> +		 * the min() ensures that the malformed check triggers below.
> +		 */
> +		if (!certs) {
> +			total_length = min(portion_length + remainder_length,
> +					   0xffff);
> +			certs = kvmalloc(total_length, GFP_KERNEL);
> +			if (!certs)
> +				return -ENOMEM;
> +		}
> +
> +		if (!portion_length ||
> +		    (rsp->param1 & 0xf) != slot ||
> +		    offset + portion_length + remainder_length != total_length)
> +		{
> +			dev_err(spdm_state->dev,
> +				"Malformed certificate response\n");
> +			return -EPROTO;
> +		}
> +
> +		memcpy((u8 *)certs + offset, rsp->cert_chain, portion_length);
> +		offset += portion_length;
> +	} while (remainder_length > 0);
> +
> +	header_length = sizeof(struct spdm_cert_chain) + spdm_state->hash_len;
> +
> +	if (total_length < header_length ||
> +	    total_length != le16_to_cpu(certs->length)) {
> +		dev_err(spdm_state->dev,
> +			"Malformed certificate chain in slot %u\n", slot);
> +		return -EPROTO;
> +	}
> +
> +	kvfree(spdm_state->slot[slot]);
> +	spdm_state->slot_sz[slot] = total_length;
> +	spdm_state->slot[slot] = no_free_ptr(certs);
> +
> +	return 0;
> +}
> +
> +static int spdm_validate_cert_chain(struct spdm_state *spdm_state, u8 slot)
> +{
> +	struct x509_certificate *cert __free(x509_free_certificate) = NULL;
> +	struct x509_certificate *prev __free(x509_free_certificate) = NULL;
> +	size_t header_length, total_length;
> +	bool is_leaf_cert;
> +	size_t offset = 0;
> +	struct key *key;
> +	int rc, length;
> +	u8 *certs;
> +
> +	header_length = sizeof(struct spdm_cert_chain) + spdm_state->hash_len;
> +	total_length = spdm_state->slot_sz[slot] - header_length;
> +	certs = (u8 *)spdm_state->slot[slot] + header_length;
> +
> +	do {
> +		rc = x509_get_certificate_length(certs + offset,
> +						 total_length - offset);
> +		if (rc < 0) {
> +			dev_err(spdm_state->dev, "Invalid certificate length "
> +				"at slot %u offset %zu\n", slot, offset);
> +			return rc;
> +		}
> +
> +		length = rc;
> +		is_leaf_cert = offset + length == total_length;
> +
> +		cert = x509_cert_parse(certs + offset, length);
> +		if (IS_ERR(cert)) {
> +			dev_err(spdm_state->dev, "Certificate parse error %pe "
> +				"at slot %u offset %zu\n", cert, slot, offset);
> +			return PTR_ERR(cert);
> +		}
> +		if (cert->unsupported_sig) {
> +			dev_err(spdm_state->dev, "Unsupported signature "
> +				"at slot %u offset %zu\n", slot, offset);
> +			return -EKEYREJECTED;
> +		}
> +		if (cert->blacklisted)
> +			return -EKEYREJECTED;
> +
> +		/*
> +		 * Basic Constraints CA value shall be false for leaf cert,
> +		 * true for intermediate and root certs (SPDM 1.3.0 table 42).
> +		 * Key Usage bit for digital signature shall be set, except
> +		 * for GenericCert in slot > 0 (SPDM 1.3.0 margin no 354).
> +		 * KeyCertSign bit must be 0 for non-CA (RFC 5280 sec 4.2.1.9).
> +		 */
> +		if ((is_leaf_cert ==
> +		     test_bit(KEY_EFLAG_CA, &cert->pub->key_eflags)) ||
> +		    (is_leaf_cert && slot == 0 &&
> +		     !test_bit(KEY_EFLAG_DIGITALSIG, &cert->pub->key_eflags)) ||
> +		    (is_leaf_cert &&
> +		     test_bit(KEY_EFLAG_KEYCERTSIGN, &cert->pub->key_eflags))) {
> +			dev_err(spdm_state->dev, "Malformed certificate "
> +				"at slot %u offset %zu\n", slot, offset);
> +			return -EKEYREJECTED;
> +		}
> +
> +		if (!prev) {
> +			/* First cert in chain, check against root_keyring */
> +			key = find_asymmetric_key(spdm_state->root_keyring,
> +						  cert->sig->auth_ids[0],
> +						  cert->sig->auth_ids[1],
> +						  cert->sig->auth_ids[2],
> +						  false);
> +			if (IS_ERR(key)) {
> +				dev_info(spdm_state->dev, "Root certificate "
> +					 "of slot %u not found in %s "
> +					 "keyring: %s\n", slot,
> +					 spdm_state->root_keyring->description,
> +					 cert->issuer);
> +				return PTR_ERR(key);
> +			}
> +
> +			rc = verify_signature(key, cert->sig);
> +			key_put(key);
> +		} else {
> +			/* Subsequent cert in chain, check against previous */
> +			rc = public_key_verify_signature(prev->pub, cert->sig);
> +		}
> +
> +		if (rc) {
> +			dev_err(spdm_state->dev, "Signature validation error "
> +				"%d at slot %u offset %zu\n", rc, slot, offset);
> +			return rc;
> +		}
> +
> +		x509_free_certificate(prev);
> +		prev = cert;
> +		cert = ERR_PTR(-ENOKEY);
> +
> +		offset += length;
> +	} while (offset < total_length);
> +
> +	/* Steal pub pointer ahead of x509_free_certificate() */
> +	spdm_state->leaf_key = prev->pub;
> +	prev->pub = NULL;
> +
> +	return 0;
> +}
> +
> +/**
> + * spdm_challenge_rsp_sz() - Calculate CHALLENGE_AUTH response size
> + *
> + * @spdm_state: SPDM session state
> + * @rsp: CHALLENGE_AUTH response (optional)
> + *
> + * A CHALLENGE_AUTH response contains multiple variable-length fields
> + * as well as optional fields.  This helper eases calculating its size.
> + *
> + * If @rsp is %NULL, assume the maximum OpaqueDataLength of 1024 bytes
> + * (SPDM 1.0.0 table 21).  Otherwise read OpaqueDataLength from @rsp.
> + * OpaqueDataLength can only be > 0 for SPDM 1.0 and 1.1, as they lack
> + * the OtherParamsSupport field in the NEGOTIATE_ALGORITHMS request.
> + * For SPDM 1.2+, we do not offer any Opaque Data Formats in that field,
> + * which forces OpaqueDataLength to 0 (SPDM 1.2.0 margin no 261).
> + */
> +static size_t spdm_challenge_rsp_sz(struct spdm_state *spdm_state,
> +				    struct spdm_challenge_rsp *rsp)
> +{
> +	size_t  size  = sizeof(*rsp)		/* Header */
> +		      + spdm_state->hash_len	/* CertChainHash */
> +		      + SPDM_NONCE_SZ;		/* Nonce */
> +
> +	if (rsp)
> +		/* May be unaligned if hash algorithm has odd length */
> +		size += get_unaligned_le16((u8 *)rsp + size);
> +	else
> +		size += SPDM_MAX_OPAQUE_DATA;	/* OpaqueData */
> +
> +	size += 2;				/* OpaqueDataLength */
> +
> +	if (spdm_state->version >= 0x13)
> +		size += 8;			/* RequesterContext */
> +
> +	return  size  + spdm_state->sig_len;	/* Signature */
> +}
> +
> +static int spdm_challenge(struct spdm_state *spdm_state, u8 slot)
> +{
> +	struct spdm_challenge_rsp *rsp __free(kfree);
> +	struct spdm_challenge_req req = {
> +		.code = SPDM_CHALLENGE,
> +		.param1 = slot,
> +		.param2 = 0, /* No measurement summary hash */
> +	};
> +	size_t req_sz, rsp_sz, rsp_sz_max;
> +	int rc, length;
> +
> +	get_random_bytes(&req.nonce, sizeof(req.nonce));
> +
> +	if (spdm_state->version <= 0x12)
> +		req_sz = offsetofend(typeof(req), nonce);
> +	else
> +		req_sz = sizeof(req);
> +
> +	rsp_sz_max = spdm_challenge_rsp_sz(spdm_state, NULL);
> +	rsp = kzalloc(rsp_sz_max, GFP_KERNEL);
> +	if (!rsp)
> +		return -ENOMEM;
> +
> +	rc = spdm_exchange(spdm_state, &req, req_sz, rsp, rsp_sz_max);
> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	rsp_sz = spdm_challenge_rsp_sz(spdm_state, rsp);
> +	if (length < rsp_sz) {
> +		dev_err(spdm_state->dev, "Truncated challenge_auth response\n");
> +		return -EIO;
> +	}
> +
> +	rc = spdm_append_transcript(spdm_state, &req, req_sz);
> +	if (rc)
> +		return rc;
> +
> +	rc = spdm_append_transcript(spdm_state, rsp, rsp_sz);
> +	if (rc)
> +		return rc;
> +
> +	/* Verify signature at end of transcript against leaf key */
> +	rc = spdm_verify_signature(spdm_state, spdm_context);
> +	if (rc)
> +		dev_err(spdm_state->dev,
> +			"Cannot verify challenge_auth signature: %d\n", rc);
> +	else
> +		dev_info(spdm_state->dev,
> +			 "Authenticated with certificate slot %u\n", slot);
> +
> +	return rc;
> +}
> +
> +/**
> + * spdm_authenticate() - Authenticate device
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES,
> + * NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges.
> + *
> + * Perform internal locking to serialize multiple concurrent invocations.
> + * Can be called repeatedly for reauthentication.
> + *
> + * Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
> + * indicates authentication is not supported by the device.
> + */
> +int spdm_authenticate(struct spdm_state *spdm_state)
> +{
> +	u8 slot;
> +	int rc;
> +
> +	mutex_lock(&spdm_state->lock);
> +	spdm_reset(spdm_state);
> +
> +	rc = spdm_alloc_transcript(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_version(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_capabilities(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_negotiate_algs(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_digests(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
> +		rc = spdm_get_certificate(spdm_state, slot);
> +		if (rc)
> +			goto unlock;
> +	}
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
> +		rc = spdm_validate_cert_chain(spdm_state, slot);
> +		if (rc == 0)
> +			break;
> +	}
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_challenge(spdm_state, slot);
> +
> +unlock:
> +	if (rc)
> +		spdm_reset(spdm_state);
> +	spdm_state->authenticated = !rc;
> +	spdm_free_transcript(spdm_state);
> +	mutex_unlock(&spdm_state->lock);
> +	return rc;
> +}
> +EXPORT_SYMBOL_GPL(spdm_authenticate);
> diff --git a/lib/spdm/spdm.h b/lib/spdm/spdm.h
> new file mode 100644
> index 000000000000..3a104959ad53
> --- /dev/null
> +++ b/lib/spdm/spdm.h
> @@ -0,0 +1,520 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#ifndef _LIB_SPDM_H_
> +#define _LIB_SPDM_H_
> +
> +#undef  DEFAULT_SYMBOL_NAMESPACE
> +#define DEFAULT_SYMBOL_NAMESPACE SPDM
> +
> +#define dev_fmt(fmt) "SPDM: " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/mutex.h>
> +#include <linux/spdm.h>
> +
> +/* SPDM versions supported by this implementation */
> +#define SPDM_MIN_VER 0x10
> +#define SPDM_MAX_VER 0x13
> +
> +/* SPDM capabilities (SPDM 1.1.0 margin no 177, 178) */
> +#define SPDM_CACHE_CAP			BIT(0)		/* 1.0 resp only */
> +#define SPDM_CERT_CAP			BIT(1)		/* 1.0 */
> +#define SPDM_CHAL_CAP			BIT(2)		/* 1.0 */
> +#define SPDM_MEAS_CAP_MASK		GENMASK(4, 3)	/* 1.0 resp only */
> +#define   SPDM_MEAS_CAP_NO		0		/* 1.0 resp only */
> +#define   SPDM_MEAS_CAP_MEAS		1		/* 1.0 resp only */
> +#define   SPDM_MEAS_CAP_MEAS_SIG	2		/* 1.0 resp only */
> +#define SPDM_MEAS_FRESH_CAP		BIT(5)		/* 1.0 resp only */
> +#define SPDM_ENCRYPT_CAP		BIT(6)		/* 1.1 */
> +#define SPDM_MAC_CAP			BIT(7)		/* 1.1 */
> +#define SPDM_MUT_AUTH_CAP		BIT(8)		/* 1.1 */
> +#define SPDM_KEY_EX_CAP			BIT(9)		/* 1.1 */
> +#define SPDM_PSK_CAP_MASK		GENMASK(11, 10)	/* 1.1 */
> +#define   SPDM_PSK_CAP_NO		0		/* 1.1 */
> +#define   SPDM_PSK_CAP_PSK		1		/* 1.1 */
> +#define   SPDM_PSK_CAP_PSK_CTX		2		/* 1.1 resp only */
> +#define SPDM_ENCAP_CAP			BIT(12)		/* 1.1 */
> +#define SPDM_HBEAT_CAP			BIT(13)		/* 1.1 */
> +#define SPDM_KEY_UPD_CAP		BIT(14)		/* 1.1 */
> +#define SPDM_HANDSHAKE_ITC_CAP		BIT(15)		/* 1.1 */
> +#define SPDM_PUB_KEY_ID_CAP		BIT(16)		/* 1.1 */
> +#define SPDM_CHUNK_CAP			BIT(17)		/* 1.2 */
> +#define SPDM_ALIAS_CERT_CAP		BIT(18)		/* 1.2 resp only */
> +#define SPDM_SET_CERT_CAP		BIT(19)		/* 1.2 resp only */
> +#define SPDM_CSR_CAP			BIT(20)		/* 1.2 resp only */
> +#define SPDM_CERT_INST_RESET_CAP	BIT(21)		/* 1.2 resp only */
> +#define SPDM_EP_INFO_CAP_MASK		GENMASK(23, 22) /* 1.3 */
> +#define   SPDM_EP_INFO_CAP_NO		0		/* 1.3 */
> +#define   SPDM_EP_INFO_CAP_RSP		1		/* 1.3 */
> +#define   SPDM_EP_INFO_CAP_RSP_SIG	2		/* 1.3 */
> +#define SPDM_MEL_CAP			BIT(24)		/* 1.3 resp only */
> +#define SPDM_EVENT_CAP			BIT(25)		/* 1.3 */
> +#define SPDM_MULTI_KEY_CAP_MASK		GENMASK(27, 26)	/* 1.3 */
> +#define   SPDM_MULTI_KEY_CAP_NO		0		/* 1.3 */
> +#define   SPDM_MULTI_KEY_CAP_ONLY	1		/* 1.3 */
> +#define   SPDM_MULTI_KEY_CAP_SEL	2		/* 1.3 */
> +#define SPDM_GET_KEY_PAIR_INFO_CAP	BIT(28)		/* 1.3 resp only */
> +#define SPDM_SET_KEY_PAIR_INFO_CAP	BIT(29)		/* 1.3 resp only */
> +
> +/* SPDM capabilities supported by this implementation */
> +#define SPDM_REQ_CAPS			(SPDM_CERT_CAP | SPDM_CHAL_CAP)
> +
> +/* SPDM capabilities required from responders */
> +#define SPDM_RSP_MIN_CAPS		(SPDM_CERT_CAP | SPDM_CHAL_CAP)
> +
> +/*
> + * SPDM cryptographic timeout of this implementation:
> + * Assume calculations may take up to 1 sec on a busy machine, which equals
> + * roughly 1 << 20.  That's within the limits mandated for responders by CMA
> + * (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
> + * Used in GET_CAPABILITIES exchange.
> + */
> +#define SPDM_CTEXPONENT			20
> +
> +/* SPDM asymmetric key signature algorithms (SPDM 1.0.0 table 13) */
> +#define SPDM_ASYM_RSASSA_2048		BIT(0)		/* 1.0 */
> +#define SPDM_ASYM_RSAPSS_2048		BIT(1)		/* 1.0 */
> +#define SPDM_ASYM_RSASSA_3072		BIT(2)		/* 1.0 */
> +#define SPDM_ASYM_RSAPSS_3072		BIT(3)		/* 1.0 */
> +#define SPDM_ASYM_ECDSA_ECC_NIST_P256	BIT(4)		/* 1.0 */
> +#define SPDM_ASYM_RSASSA_4096		BIT(5)		/* 1.0 */
> +#define SPDM_ASYM_RSAPSS_4096		BIT(6)		/* 1.0 */
> +#define SPDM_ASYM_ECDSA_ECC_NIST_P384	BIT(7)		/* 1.0 */
> +#define SPDM_ASYM_ECDSA_ECC_NIST_P521	BIT(8)		/* 1.0 */
> +#define SPDM_ASYM_SM2_ECC_SM2_P256	BIT(9)		/* 1.2 */
> +#define SPDM_ASYM_EDDSA_ED25519		BIT(10)		/* 1.2 */
> +#define SPDM_ASYM_EDDSA_ED448		BIT(11)		/* 1.2 */
> +
> +/* SPDM hash algorithms (SPDM 1.0.0 table 13) */
> +#define SPDM_HASH_SHA_256		BIT(0)		/* 1.0 */
> +#define SPDM_HASH_SHA_384		BIT(1)		/* 1.0 */
> +#define SPDM_HASH_SHA_512		BIT(2)		/* 1.0 */
> +#define SPDM_HASH_SHA3_256		BIT(3)		/* 1.0 */
> +#define SPDM_HASH_SHA3_384		BIT(4)		/* 1.0 */
> +#define SPDM_HASH_SHA3_512		BIT(5)		/* 1.0 */
> +#define SPDM_HASH_SM3_256		BIT(6)		/* 1.2 */
> +
> +#if IS_ENABLED(CONFIG_CRYPTO_RSA)
> +#define SPDM_ASYM_RSA			SPDM_ASYM_RSASSA_2048 |		\
> +					SPDM_ASYM_RSASSA_3072 |		\
> +					SPDM_ASYM_RSASSA_4096
> +#else
> +#define SPDM_ASYM_RSA			0
> +#endif
> +
> +#if IS_ENABLED(CONFIG_CRYPTO_ECDSA)
> +#define SPDM_ASYM_ECDSA			SPDM_ASYM_ECDSA_ECC_NIST_P256 |	\
> +					SPDM_ASYM_ECDSA_ECC_NIST_P384 | \
> +					SPDM_ASYM_ECDSA_ECC_NIST_P521
> +#else
> +#define SPDM_ASYM_ECDSA			0
> +#endif
> +
> +#if IS_ENABLED(CONFIG_CRYPTO_SHA256)
> +#define SPDM_HASH_SHA2_256		SPDM_HASH_SHA_256
> +#else
> +#define SPDM_HASH_SHA2_256		0
> +#endif
> +
> +#if IS_ENABLED(CONFIG_CRYPTO_SHA512)
> +#define SPDM_HASH_SHA2_384_512		SPDM_HASH_SHA_384 |		\
> +					SPDM_HASH_SHA_512
> +#else
> +#define SPDM_HASH_SHA2_384_512		0
> +#endif
> +
> +/* SPDM algorithms supported by this implementation */
> +#define SPDM_ASYM_ALGOS		       (SPDM_ASYM_RSA |			\
> +					SPDM_ASYM_ECDSA)
> +
> +#define SPDM_HASH_ALGOS		       (SPDM_HASH_SHA2_256 |		\
> +					SPDM_HASH_SHA2_384_512)
> +
> +/*
> + * Common header shared by all messages.
> + * Note that the meaning of param1 and param2 is message dependent.
> + */
> +struct spdm_header {
> +	u8 version;
> +	u8 code;  /* RequestResponseCode */
> +	u8 param1;
> +	u8 param2;
> +} __packed;
> +
> +#define SPDM_REQ	 0x80
> +#define SPDM_GET_VERSION 0x84
> +
> +struct spdm_get_version_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1;
> +	u8 param2;
> +} __packed;
> +
> +struct spdm_get_version_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1;
> +	u8 param2;
> +
> +	u8 reserved;
> +	u8 version_number_entry_count;
> +	__le16 version_number_entries[] __counted_by(version_number_entry_count);
> +} __packed;
> +
> +#define SPDM_GET_CAPABILITIES 0xe1
> +#define SPDM_MIN_DATA_TRANSFER_SIZE 42 /* SPDM 1.2.0 margin no 226 */
> +
> +/*
> + * Newer SPDM versions insert fields at the end of messages (enlarging them)
> + * or use reserved space for new fields (leaving message size unchanged).
> + */
> +struct spdm_get_capabilities_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1;
> +	u8 param2;
> +	/* End of SPDM 1.0 structure */
> +
> +	u8 reserved1;					/* 1.1 */
> +	u8 ctexponent;					/* 1.1 */
> +	u16 reserved2;					/* 1.1 */
> +	__le32 flags;					/* 1.1 */
> +	/* End of SPDM 1.1 structure */
> +
> +	__le32 data_transfer_size;			/* 1.2 */
> +	__le32 max_spdm_msg_size;			/* 1.2 */
> +} __packed;
> +
> +struct spdm_get_capabilities_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1;
> +	u8 param2;
> +
> +	u8 reserved1;
> +	u8 ctexponent;
> +	u16 reserved2;
> +	__le32 flags;
> +	/* End of SPDM 1.0 structure */
> +
> +	__le32 data_transfer_size;			/* 1.2 */
> +	__le32 max_spdm_msg_size;			/* 1.2 */
> +	/* End of SPDM 1.2 structure */
> +
> +	/*
> +	 * Additional optional fields at end of this structure:
> +	 * - SupportedAlgorithms: variable size		 * 1.3 *
> +	 */
> +} __packed;
> +
> +#define SPDM_NEGOTIATE_ALGS 0xe3
> +
> +struct spdm_negotiate_algs_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Number of ReqAlgStruct entries at end */
> +	u8 param2;
> +
> +	__le16 length;
> +	u8 measurement_specification;
> +	u8 other_params_support;			/* 1.2 */
> +
> +	__le32 base_asym_algo;
> +	__le32 base_hash_algo;
> +
> +	u8 reserved1[12];
> +	u8 ext_asym_count;
> +	u8 ext_hash_count;
> +	u8 reserved2;
> +	u8 mel_specification;				/* 1.3 */
> +
> +	/*
> +	 * Additional optional fields at end of this structure:
> +	 * - ExtAsym: 4 bytes * ext_asym_count
> +	 * - ExtHash: 4 bytes * ext_hash_count
> +	 * - ReqAlgStruct: variable size * param1	 * 1.1 *
> +	 */
> +} __packed;
> +
> +struct spdm_negotiate_algs_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Number of RespAlgStruct entries at end */
> +	u8 param2;
> +
> +	__le16 length;
> +	u8 measurement_specification_sel;
> +	u8 other_params_sel;				/* 1.2 */
> +
> +	__le32 measurement_hash_algo;
> +	__le32 base_asym_sel;
> +	__le32 base_hash_sel;
> +
> +	u8 reserved1[11];
> +	u8 mel_specification_sel;			/* 1.3 */
> +	u8 ext_asym_sel_count; /* Either 0 or 1 */
> +	u8 ext_hash_sel_count; /* Either 0 or 1 */
> +	u8 reserved2[2];
> +
> +	/*
> +	 * Additional optional fields at end of this structure:
> +	 * - ExtAsym: 4 bytes * ext_asym_count
> +	 * - ExtHash: 4 bytes * ext_hash_count
> +	 * - RespAlgStruct: variable size * param1	 * 1.1 *
> +	 */
> +} __packed;
> +
> +/* Maximum number of ReqAlgStructs sent by this implementation */
> +#define SPDM_MAX_REQ_ALG_STRUCT 0
> +
> +struct spdm_req_alg_struct {
> +	u8 alg_type;
> +	u8 alg_count; /* 0x2K where K is number of alg_external entries */
> +	__le16 alg_supported; /* Size is in alg_count[7:4], always 2 */
> +	__le32 alg_external[];
> +} __packed;
> +
> +#define SPDM_GET_DIGESTS 0x81
> +
> +struct spdm_get_digests_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Reserved */
> +	u8 param2; /* Reserved */
> +} __packed;
> +
> +struct spdm_get_digests_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* SupportedSlotMask */		/* 1.3 */
> +	u8 param2; /* ProvisionedSlotMask */
> +	u8 digests[]; /* Hash of struct spdm_cert_chain for each slot */
> +	/* End of SPDM 1.2 (and earlier) structure */
> +
> +	/*
> +	 * Additional optional fields at end of this structure:
> +	 * (omitted as long as we do not advertise MULTI_KEY_CAP)
> +	 * - KeyPairID: 1 byte for each slot		 * 1.3 *
> +	 * - CertificateInfo: 1 byte for each slot	 * 1.3 *
> +	 * - KeyUsageMask: 2 bytes for each slot	 * 1.3 *
> +	 */
> +} __packed;
> +
> +#define SPDM_GET_CERTIFICATE 0x82
> +#define SPDM_SLOTS 8 /* SPDM 1.0.0 section 4.9.2.1 */
> +
> +struct spdm_get_certificate_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Slot number 0..7 */
> +	u8 param2; /* SlotSizeRequested */		/* 1.3 */
> +	__le16 offset;
> +	__le16 length;
> +} __packed;
> +
> +struct spdm_get_certificate_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Slot number 0..7 */
> +	u8 param2; /* CertificateInfo */		/* 1.3 */
> +	__le16 portion_length;
> +	__le16 remainder_length;
> +	u8 cert_chain[]; /* PortionLength long */
> +} __packed;
> +
> +struct spdm_cert_chain {
> +	__le16 length;
> +	u8 reserved[2];
> +	/*
> +	 * Additional fields at end of this structure:
> +	 * - RootHash: Digest of Root Certificate
> +	 * - Certificates: Chain of ASN.1 DER-encoded X.509 v3 certificates
> +	 */
> +} __packed;
> +
> +#define SPDM_CHALLENGE 0x83
> +#define SPDM_NONCE_SZ 32 /* SPDM 1.0.0 table 20 */
> +#define SPDM_PREFIX_SZ 64 /* SPDM 1.2.0 margin no 803 */
> +#define SPDM_COMBINED_PREFIX_SZ 100 /* SPDM 1.2.0 margin no 806 */
> +#define SPDM_MAX_OPAQUE_DATA 1024 /* SPDM 1.0.0 table 21 */
> +
> +struct spdm_challenge_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Slot number 0..7 */
> +	u8 param2; /* MeasurementSummaryHash type */
> +	u8 nonce[SPDM_NONCE_SZ];
> +	/* End of SPDM 1.2 (and earlier) structure */
> +
> +	u8 context[8];					/* 1.3 */
> +} __packed;
> +
> +struct spdm_challenge_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Slot number 0..7 */
> +	u8 param2; /* Slot mask */
> +	/*
> +	 * Additional fields at end of this structure:
> +	 * - CertChainHash: Hash of struct spdm_cert_chain for selected slot
> +	 * - Nonce: 32 bytes long
> +	 * - MeasurementSummaryHash: Optional hash of selected measurements
> +	 * - OpaqueDataLength: 2 bytes long
> +	 * - OpaqueData: Up to 1024 bytes long
> +	 * - RequesterContext: 8 bytes long		 * 1.3 *
> +	 *   (inserted, moves Signature field)
> +	 * - Signature
> +	 */
> +} __packed;
> +
> +#define SPDM_ERROR 0x7f
> +
> +enum spdm_error_code {
> +	SPDM_INVALID_REQUEST		= 0x01,		/* 1.0 */
> +	SPDM_INVALID_SESSION		= 0x02,		/* 1.1 only */
> +	SPDM_BUSY			= 0x03,		/* 1.0 */
> +	SPDM_UNEXPECTED_REQUEST		= 0x04,		/* 1.0 */
> +	SPDM_UNSPECIFIED		= 0x05,		/* 1.0 */
> +	SPDM_DECRYPT_ERROR		= 0x06,		/* 1.1 */
> +	SPDM_UNSUPPORTED_REQUEST	= 0x07,		/* 1.0 */
> +	SPDM_REQUEST_IN_FLIGHT		= 0x08,		/* 1.1 */
> +	SPDM_INVALID_RESPONSE_CODE	= 0x09,		/* 1.1 */
> +	SPDM_SESSION_LIMIT_EXCEEDED	= 0x0a,		/* 1.1 */
> +	SPDM_SESSION_REQUIRED		= 0x0b,		/* 1.2 */
> +	SPDM_RESET_REQUIRED		= 0x0c,		/* 1.2 */
> +	SPDM_RESPONSE_TOO_LARGE		= 0x0d,		/* 1.2 */
> +	SPDM_REQUEST_TOO_LARGE		= 0x0e,		/* 1.2 */
> +	SPDM_LARGE_RESPONSE		= 0x0f,		/* 1.2 */
> +	SPDM_MESSAGE_LOST		= 0x10,		/* 1.2 */
> +	SPDM_INVALID_POLICY		= 0x11,		/* 1.3 */
> +	SPDM_VERSION_MISMATCH		= 0x41,		/* 1.0 */
> +	SPDM_RESPONSE_NOT_READY		= 0x42,		/* 1.0 */
> +	SPDM_REQUEST_RESYNCH		= 0x43,		/* 1.0 */
> +	SPDM_OPERATION_FAILED		= 0x44,		/* 1.3 */
> +	SPDM_NO_PENDING_REQUESTS	= 0x45,		/* 1.3 */
> +	SPDM_VENDOR_DEFINED_ERROR	= 0xff,		/* 1.0 */
> +};
> +
> +struct spdm_error_rsp {
> +	u8 version;
> +	u8 code;
> +	enum spdm_error_code error_code:8;
> +	u8 error_data;
> +
> +	u8 extended_error_data[];
> +} __packed;
> +
> +/**
> + * struct spdm_state - SPDM session state
> + *
> + * @dev: Responder device.  Used for error reporting and passed to @transport.
> + * @lock: Serializes multiple concurrent spdm_authenticate() calls.
> + * @authenticated: Whether device was authenticated successfully.
> + * @dev: Responder device.  Used for error reporting and passed to @transport.
> + * @transport: Transport function to perform one message exchange.
> + * @transport_priv: Transport private data.
> + * @transport_sz: Maximum message size the transport is capable of (in bytes).
> + *	Used as DataTransferSize in GET_CAPABILITIES exchange.
> + * @version: Maximum common supported version of requester and responder.
> + *	Negotiated during GET_VERSION exchange.
> + * @rsp_caps: Cached capabilities of responder.
> + *	Received during GET_CAPABILITIES exchange.
> + * @base_asym_alg: Asymmetric key algorithm for signature verification of
> + *	CHALLENGE_AUTH messages.
> + *	Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> + * @base_hash_alg: Hash algorithm for signature verification of
> + *	CHALLENGE_AUTH messages.
> + *	Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> + * @provisioned_slots: Bitmask of responder's provisioned certificate slots.
> + *	Received during GET_DIGESTS exchange.
> + * @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
> + *	Passed to crypto subsystem when calling verify_signature().
> + * @sig_len: Signature length of @base_asym_alg (in bytes).
> + *	S or SigLen in SPDM specification.
> + * @base_hash_alg_name: Human-readable name of @base_hash_alg.
> + *	Passed to crypto subsystem when calling crypto_alloc_shash() and
> + *	verify_signature().
> + * @shash: Synchronous hash handle for @base_hash_alg computation.
> + * @desc: Synchronous hash context for @base_hash_alg computation.
> + * @hash_len: Hash length of @base_hash_alg (in bytes).
> + *	H in SPDM specification.
> + * @slot: Certificate chain in each of the 8 slots.  NULL pointer if a slot is
> + *	not populated.  Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
> + * @slot_sz: Certificate chain size (in bytes).
> + * @leaf_key: Public key portion of leaf certificate against which to check
> + *	responder's signatures.
> + * @root_keyring: Keyring against which to check the first certificate in
> + *	responder's certificate chain.
> + * @transcript: Concatenation of all SPDM messages exchanged during an
> + *	authentication sequence.  Used to verify the signature, as it is
> + *	computed over the hashed transcript.
> + * @transcript_end: Pointer into the @transcript buffer.  Marks the current
> + *	end of transcript.  If another message is transmitted, it is appended
> + *	at this position.
> + * @transcript_max: Allocation size of @transcript.  Multiple of PAGE_SIZE.
> + */
> +struct spdm_state {
> +	struct device *dev;
> +	struct mutex lock;
> +	unsigned int authenticated:1;
> +
> +	/* Transport */
> +	spdm_transport *transport;
> +	void *transport_priv;
> +	u32 transport_sz;
> +
> +	/* Negotiated state */
> +	u8 version;
> +	u32 rsp_caps;
> +	u32 base_asym_alg;
> +	u32 base_hash_alg;
> +	unsigned long provisioned_slots;
> +
> +	/* Signature algorithm */
> +	const char *base_asym_enc;
> +	size_t sig_len;
> +
> +	/* Hash algorithm */
> +	const char *base_hash_alg_name;
> +	struct crypto_shash *shash;
> +	struct shash_desc *desc;
> +	size_t hash_len;
> +
> +	/* Certificates */
> +	struct spdm_cert_chain *slot[SPDM_SLOTS];
> +	size_t slot_sz[SPDM_SLOTS];
> +	struct public_key *leaf_key;
> +	struct key *root_keyring;
> +
> +	/* Transcript */
> +	void *transcript;
> +	void *transcript_end;
> +	size_t transcript_max;
> +};
> +
> +ssize_t spdm_exchange(struct spdm_state *spdm_state,
> +		      void *req, size_t req_sz, void *rsp, size_t rsp_sz);
> +
> +int spdm_alloc_transcript(struct spdm_state *spdm_state);
> +void spdm_free_transcript(struct spdm_state *spdm_state);
> +int spdm_append_transcript(struct spdm_state *spdm_state,
> +			   const void *msg, size_t msg_sz);
> +
> +void spdm_create_combined_prefix(u8 version, const char *spdm_context,
> +				 void *buf);
> +int spdm_verify_signature(struct spdm_state *spdm_state,
> +			  const char *spdm_context);
> +
> +void spdm_reset(struct spdm_state *spdm_state);
> +
> +#endif /* _LIB_SPDM_H_ */
Lukas Wunner July 8, 2024, 12:54 p.m. UTC | #3
On Mon, Jul 08, 2024 at 07:57:02PM +1000, Alexey Kardashevskiy wrote:
> > +	rc = spdm_exchange(spdm_state, req, req_sz, rsp, rsp_sz);
> 
> rsp_sz is 36 bytes here. And spdm_exchange() cannot return more than 36
> because this is how pci_doe() works...
> 
> > +	if (rc < 0)
> > +		return rc;
> > +
> > +	length = rc;
> > +	if (length < sizeof(*rsp) ||
> > +	    length < sizeof(*rsp) + rsp->param1 * sizeof(*req_alg_struct)) {
> > +		dev_err(spdm_state->dev, "Truncated algorithms response\n");
> 
> ... but here you expect more than 36 as realistically rsp->param1 > 0.
> How was this tested and what do I miss here?

I assume you tested this patch set against a libspdm responder
and got a "Truncated algorithms response" error.

The short answer is, it's a bug in libspdm and the issue should
go away once you update libspdm to version 3.1.0 or newer.

If you need to stay at an older version, consider cherry-picking
libspdm commits 941f0ae0d24e ("libspdm_rsp_algorithms: fixup spec
conformance") and 065fb17b74c7 ("responder: negotiate algorithms
conformance").

The bug was found and fixed by Wilfred Mallawa when testing the
in-kernel SPDM implementation against libspdm:

https://github.com/l1k/linux/issues/3
https://github.com/DMTF/libspdm/pull/2341
https://github.com/DMTF/libspdm/issues/2344
https://github.com/DMTF/libspdm/pull/2353

Problem is, most SPDM-enabled products right now are based on
libspdm (the DMTF reference implementation) and thus are bug-by-bug
compatible.  However such a software monoculture is dangerous and
having a from-scratch kernel implementation has already proven useful
to identify issues like this which otherwise wouldn't have been noticed.

The in-kernel SPDM implementation currently doesn't send any
ReqAlgStructs and per the spec, the responder isn't supposed to
send any RespAlgStructs which the requester didn't ask for.
Yet libspdm always sent a hardcoded array of RespAlgStructs.

So the *reference* implementation wasn't conforming to the spec. :(

Thanks,

Lukas
Alexey Kardashevskiy July 9, 2024, 12:45 a.m. UTC | #4
On 8/7/24 22:54, Lukas Wunner wrote:
> On Mon, Jul 08, 2024 at 07:57:02PM +1000, Alexey Kardashevskiy wrote:
>>> +	rc = spdm_exchange(spdm_state, req, req_sz, rsp, rsp_sz);
>>
>> rsp_sz is 36 bytes here. And spdm_exchange() cannot return more than 36
>> because this is how pci_doe() works...
>>
>>> +	if (rc < 0)
>>> +		return rc;
>>> +
>>> +	length = rc;
>>> +	if (length < sizeof(*rsp) ||
>>> +	    length < sizeof(*rsp) + rsp->param1 * sizeof(*req_alg_struct)) {
>>> +		dev_err(spdm_state->dev, "Truncated algorithms response\n");
>>
>> ... but here you expect more than 36 as realistically rsp->param1 > 0.
>> How was this tested and what do I miss here?
> 
> I assume you tested this patch set against a libspdm responder
> and got a "Truncated algorithms response" error.

It is against a device with libspdm in its firmware, likely to be older 
than 3.1.0.

> The short answer is, it's a bug in libspdm and the issue should
> go away once you update libspdm to version 3.1.0 or newer.

Easier to hack lib/spdm/req-authenticate.c just to see how far I can get 
with my device, now it is "Malformed certificate at slot 0 offset 0". It 
is just a bit inconvenient that CMA is not a module and requires the 
system reboot after every change.

> If you need to stay at an older version, consider cherry-picking
> libspdm commits 941f0ae0d24e ("libspdm_rsp_algorithms: fixup spec
> conformance") and 065fb17b74c7 ("responder: negotiate algorithms
> conformance").
> 
> The bug was found and fixed by Wilfred Mallawa when testing the
> in-kernel SPDM implementation against libspdm:
> 
> https://github.com/l1k/linux/issues/3
> https://github.com/DMTF/libspdm/pull/2341
> https://github.com/DMTF/libspdm/issues/2344
> https://github.com/DMTF/libspdm/pull/2353
> 
> Problem is, most SPDM-enabled products right now are based on
> libspdm (the DMTF reference implementation) and thus are bug-by-bug
> compatible.  However such a software monoculture is dangerous and
> having a from-scratch kernel implementation has already proven useful
> to identify issues like this which otherwise wouldn't have been noticed.

True and a bit hilarious :)

> The in-kernel SPDM implementation currently doesn't send any
> ReqAlgStructs and per the spec, the responder isn't supposed to
> send any RespAlgStructs which the requester didn't ask for.
> Yet libspdm always sent a hardcoded array of RespAlgStructs.

Uff, I see. So it should probably be "Malformed algorithms response" 
(where param1 is actually checked) than "Truncated algorithms response", 
a minor detail though. Thanks for the explanation.

> So the *reference* implementation wasn't conforming to the spec. :(
> 
> Thanks,
> 
> Lukas
Dan Williams July 9, 2024, 5:09 a.m. UTC | #5
Lukas Wunner wrote:
> From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> The Security Protocol and Data Model (SPDM) allows for device
> authentication, measurement, key exchange and encrypted sessions.
> 
> SPDM was conceived by the Distributed Management Task Force (DMTF).
> Its specification defines a request/response protocol spoken between
> host and attached devices over a variety of transports:
> 
>   https://www.dmtf.org/dsp/DSP0274
> 
> This implementation supports SPDM 1.0 through 1.3 (the latest version).
> It is designed to be transport-agnostic as the kernel already supports
> four different SPDM-capable transports:
> 
> * PCIe Data Object Exchange, which is a mailbox in PCI config space
>   (PCIe r6.2 sec 6.30, drivers/pci/doe.c)
> * Management Component Transport Protocol
>   (MCTP, Documentation/networking/mctp.rst)
> * TCP/IP (in draft stage)
>   https://www.dmtf.org/sites/default/files/standards/documents/DSP0287_1.0.0WIP99.pdf
> * SCSI and ATA (in draft stage)
>   "SECURITY PROTOCOL IN/OUT" and "TRUSTED SEND/RECEIVE" commands
> 
> Use cases for SPDM include, but are not limited to:
> 
> * PCIe Component Measurement and Authentication (PCIe r6.2 sec 6.31)
> * Compute Express Link (CXL r3.0 sec 14.11.6)
> * Open Compute Project (Attestation of System Components v1.0)
>   https://www.opencompute.org/documents/attestation-v1-0-20201104-pdf
> * Open Compute Project (Datacenter NVMe SSD Specification v2.0)
>   https://www.opencompute.org/documents/datacenter-nvme-ssd-specification-v2-0r21-pdf
> 
> The initial focus of this implementation is enabling PCIe CMA device
> authentication.  As such, only a subset of the SPDM specification is
> contained herein, namely the request/response sequence GET_VERSION,
> GET_CAPABILITIES, NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE
> and CHALLENGE.
> 
> This sequence first negotiates the SPDM protocol version, capabilities
> and algorithms with the device.  It then retrieves the up to eight
> certificate chains which may be provisioned on the device.  Finally it
> performs challenge-response authentication with the device using one of
> those eight certificate chains and the algorithms negotiated before.
> The challenge-response authentication comprises computing a hash over
> all exchanged messages to detect modification by a man-in-the-middle
> or media error.  The hash is then signed with the device's private key
> and the resulting signature is verified by the kernel using the device's
> public key from the certificate chain.  Nonces are included in the
> message sequence to protect against replay attacks.
> 
> A simple API is provided for subsystems wishing to authenticate devices:
> spdm_create(), spdm_authenticate() (can be called repeatedly for
> reauthentication) and spdm_destroy().  Certificates presented by devices
> are validated against an in-kernel keyring of trusted root certificates.
> A pointer to the keyring is passed to spdm_create().
> 
> The set of supported cryptographic algorithms is limited to those
> declared mandatory in PCIe r6.2 sec 6.31.3.  Adding more algorithms
> is straightforward as long as the crypto subsystem supports them.
> 
> Future commits will extend this implementation with support for
> measurement, key exchange and encrypted sessions.
> 
> So far, only the SPDM requester role is implemented.  Care was taken to
> allow for effortless addition of the responder role at a later stage.
> This could be needed for a PCIe host bridge operating in endpoint mode.
> The responder role will be able to reuse struct definitions and helpers
> such as spdm_create_combined_prefix().

Nice changelog.

> 
> Credits:  Jonathan wrote a proof-of-concept of this SPDM implementation.
> Lukas reworked it for upstream.
> 
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Co-developed-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
> ---
>  MAINTAINERS                 |  11 +
>  include/linux/spdm.h        |  33 ++
>  lib/Kconfig                 |  15 +
>  lib/Makefile                |   2 +
>  lib/spdm/Makefile           |  10 +
>  lib/spdm/core.c             | 425 ++++++++++++++++++++++
>  lib/spdm/req-authenticate.c | 704 ++++++++++++++++++++++++++++++++++++
>  lib/spdm/spdm.h             | 520 ++++++++++++++++++++++++++
>  8 files changed, 1720 insertions(+)
>  create mode 100644 include/linux/spdm.h
>  create mode 100644 lib/spdm/Makefile
>  create mode 100644 lib/spdm/core.c
>  create mode 100644 lib/spdm/req-authenticate.c
>  create mode 100644 lib/spdm/spdm.h

I only have some quibbles below, but the broad strokes look good to me.

> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d6c90161c7bf..dbe16eea8818 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20145,6 +20145,17 @@ M:	Security Officers <security@kernel.org>
>  S:	Supported
>  F:	Documentation/process/security-bugs.rst
>  
> +SECURITY PROTOCOL AND DATA MODEL (SPDM)
> +M:	Jonathan Cameron <jic23@kernel.org>
> +M:	Lukas Wunner <lukas@wunner.de>
> +L:	linux-coco@lists.linux.dev
> +L:	linux-cxl@vger.kernel.org
> +L:	linux-pci@vger.kernel.org
> +S:	Maintained
> +T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
> +F:	include/linux/spdm.h
> +F:	lib/spdm/
> +
>  SECURITY SUBSYSTEM
>  M:	Paul Moore <paul@paul-moore.com>
>  M:	James Morris <jmorris@namei.org>
> diff --git a/include/linux/spdm.h b/include/linux/spdm.h
> new file mode 100644
> index 000000000000..0da7340020c4
> --- /dev/null
> +++ b/include/linux/spdm.h
> @@ -0,0 +1,33 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#ifndef _SPDM_H_
> +#define _SPDM_H_
> +
> +#include <linux/types.h>
> +
> +struct key;
> +struct device;
> +struct spdm_state;
> +
> +typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
> +				 const void *request, size_t request_sz,
> +				 void *response, size_t response_sz);
> +
> +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
> +			       void *transport_priv, u32 transport_sz,
> +			       struct key *keyring);
> +
> +int spdm_authenticate(struct spdm_state *spdm_state);
> +
> +void spdm_destroy(struct spdm_state *spdm_state);
> +
> +#endif
> diff --git a/lib/Kconfig b/lib/Kconfig
> index d33a268bc256..9011fa32af45 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -782,3 +782,18 @@ config POLYNOMIAL
>  
>  config FIRMWARE_TABLE
>  	bool
> +
> +config SPDM
> +	tristate
> +	select CRYPTO
> +	select KEYS
> +	select ASYMMETRIC_KEY_TYPE
> +	select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> +	select X509_CERTIFICATE_PARSER
> +	help
> +	  The Security Protocol and Data Model (SPDM) allows for device
> +	  authentication, measurement, key exchange and encrypted sessions.
> +
> +	  Crypto algorithms negotiated with SPDM are limited to those enabled
> +	  in .config.  Drivers selecting SPDM therefore need to also select
> +	  any algorithms they deem mandatory.
> diff --git a/lib/Makefile b/lib/Makefile
> index 3b1769045651..b2ef14d1fa71 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -301,6 +301,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
>  obj-$(CONFIG_ASN1) += asn1_decoder.o
>  obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
>  
> +obj-$(CONFIG_SPDM) += spdm/
> +
>  obj-$(CONFIG_FONT_SUPPORT) += fonts/
>  
>  hostprogs	:= gen_crc32table
> diff --git a/lib/spdm/Makefile b/lib/spdm/Makefile
> new file mode 100644
> index 000000000000..f579cc898dbc
> --- /dev/null
> +++ b/lib/spdm/Makefile
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# DMTF Security Protocol and Data Model (SPDM)
> +# https://www.dmtf.org/dsp/DSP0274
> +#
> +# Copyright (C) 2024 Intel Corporation
> +
> +obj-$(CONFIG_SPDM) += spdm.o
> +
> +spdm-y := core.o req-authenticate.o
> diff --git a/lib/spdm/core.c b/lib/spdm/core.c
> new file mode 100644
> index 000000000000..f06402f6d127
> --- /dev/null
> +++ b/lib/spdm/core.c
> @@ -0,0 +1,425 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Core routines for message exchange, message transcript,
> + * signature verification and session state lifecycle
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#include "spdm.h"
> +
> +#include <linux/dev_printk.h>
> +#include <linux/module.h>
> +
> +#include <crypto/hash.h>
> +#include <crypto/public_key.h>
> +
> +static int spdm_err(struct device *dev, struct spdm_error_rsp *rsp)

This approach to error singnaling looks unprecedented, and not in a good
way. I like the idea of a SPDM-error-code to errno converter, and
separate SPDM-error-code to error string, but not an SPDM-error-code to
errno conversion that has a log emitting side effect.

Would this not emit ambiguous messages like:

    cxl_pci 0000:35:00.0: Unexpected request

How does I know that error message is from CXL SPDM functionality and
not some other part of the driver?

What if the SPDM authentication is optional, how does the consumer of
this library avoid log spam? What about rate limiting?

Did you consider leaving all error logging to the caller?

I have less problem if these all become dev_dbg() or tracepoints, but
dev_err() seems awkward.

> +{
> +	switch (rsp->error_code) {
> +	case SPDM_INVALID_REQUEST:
> +		dev_err(dev, "Invalid request\n");
> +		return -EINVAL;
> +	case SPDM_INVALID_SESSION:
> +		if (rsp->version == 0x11) {
> +			dev_err(dev, "Invalid session %#x\n", rsp->error_data);
> +			return -EINVAL;
> +		}
> +		break;
> +	case SPDM_BUSY:
> +		dev_err(dev, "Busy\n");
> +		return -EBUSY;
> +	case SPDM_UNEXPECTED_REQUEST:
> +		dev_err(dev, "Unexpected request\n");
> +		return -EINVAL;
> +	case SPDM_UNSPECIFIED:
> +		dev_err(dev, "Unspecified error\n");
> +		return -EINVAL;
> +	case SPDM_DECRYPT_ERROR:
> +		dev_err(dev, "Decrypt error\n");
> +		return -EIO;
> +	case SPDM_UNSUPPORTED_REQUEST:
> +		dev_err(dev, "Unsupported request %#x\n", rsp->error_data);
> +		return -EINVAL;
> +	case SPDM_REQUEST_IN_FLIGHT:
> +		dev_err(dev, "Request in flight\n");
> +		return -EINVAL;
> +	case SPDM_INVALID_RESPONSE_CODE:
> +		dev_err(dev, "Invalid response code\n");
> +		return -EINVAL;
> +	case SPDM_SESSION_LIMIT_EXCEEDED:
> +		dev_err(dev, "Session limit exceeded\n");
> +		return -EBUSY;
> +	case SPDM_SESSION_REQUIRED:
> +		dev_err(dev, "Session required\n");
> +		return -EINVAL;
> +	case SPDM_RESET_REQUIRED:
> +		dev_err(dev, "Reset required\n");
> +		return -ECONNRESET;
> +	case SPDM_RESPONSE_TOO_LARGE:
> +		dev_err(dev, "Response too large\n");
> +		return -EINVAL;
> +	case SPDM_REQUEST_TOO_LARGE:
> +		dev_err(dev, "Request too large\n");
> +		return -EINVAL;
> +	case SPDM_LARGE_RESPONSE:
> +		dev_err(dev, "Large response\n");
> +		return -EMSGSIZE;
> +	case SPDM_MESSAGE_LOST:
> +		dev_err(dev, "Message lost\n");
> +		return -EIO;
> +	case SPDM_INVALID_POLICY:
> +		dev_err(dev, "Invalid policy\n");
> +		return -EINVAL;
> +	case SPDM_VERSION_MISMATCH:
> +		dev_err(dev, "Version mismatch\n");
> +		return -EINVAL;
> +	case SPDM_RESPONSE_NOT_READY:
> +		dev_err(dev, "Response not ready\n");
> +		return -EINPROGRESS;
> +	case SPDM_REQUEST_RESYNCH:
> +		dev_err(dev, "Request resynchronization\n");
> +		return -ECONNRESET;
> +	case SPDM_OPERATION_FAILED:
> +		dev_err(dev, "Operation failed\n");
> +		return -EINVAL;
> +	case SPDM_NO_PENDING_REQUESTS:
> +		return -ENOENT;
> +	case SPDM_VENDOR_DEFINED_ERROR:
> +		dev_err(dev, "Vendor defined error\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_err(dev, "Undefined error %#x\n", rsp->error_code);
> +	return -EINVAL;
> +}
> +
> +/**
> + * spdm_exchange() - Perform SPDM message exchange with device
> + *
> + * @spdm_state: SPDM session state
> + * @req: Request message
> + * @req_sz: Size of @req
> + * @rsp: Response message
> + * @rsp_sz: Size of @rsp
> + *
> + * Send the request @req to the device via the @transport in @spdm_state and
> + * receive the response into @rsp, respecting the maximum buffer size @rsp_sz.
> + * The request version is automatically populated.
> + *
> + * Return response size on success or a negative errno.  Response size may be
> + * less than @rsp_sz and the caller is responsible for checking that.  It may
> + * also be more than expected (though never more than @rsp_sz), e.g. if the
> + * transport receives only dword-sized chunks.
> + */
> +ssize_t spdm_exchange(struct spdm_state *spdm_state,
> +		      void *req, size_t req_sz, void *rsp, size_t rsp_sz)
> +{
> +	struct spdm_header *request = req;
> +	struct spdm_header *response = rsp;
> +	ssize_t rc, length;

What's the locking assumption with this public function? I see that the
internal to this file usages wrap it in the state lock. Should that
assumption be codified with a:

lockdep_assert_held(&spdm_state->lock)

?

Or can this function be marked static?

> +
> +	if (req_sz < sizeof(struct spdm_header) ||
> +	    rsp_sz < sizeof(struct spdm_header))
> +		return -EINVAL;
> +
> +	request->version = spdm_state->version;
> +
> +	rc = spdm_state->transport(spdm_state->transport_priv, spdm_state->dev,
> +				   req, req_sz, rsp, rsp_sz);
> +	if (rc < 0)
> +		return rc;
> +
> +	length = rc;
> +	if (length < sizeof(struct spdm_header))
> +		return length; /* Truncated response is handled by callers */
> +
> +	if (response->code == SPDM_ERROR)
> +		return spdm_err(spdm_state->dev, (struct spdm_error_rsp *)rsp);
> +
> +	if (response->code != (request->code & ~SPDM_REQ)) {
> +		dev_err(spdm_state->dev,
> +			"Response code %#x does not match request code %#x\n",
> +			response->code, request->code);
> +		return -EPROTO;
> +	}
> +
> +	return length;
> +}
> +
> +/**
> + * spdm_alloc_transcript() - Allocate transcript buffer
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Allocate a buffer to accommodate the concatenation of all SPDM messages
> + * exchanged during an authentication sequence.  Used to verify the signature,
> + * as it is computed over the hashed transcript.
> + *
> + * Transcript size is initially one page.  It grows by additional pages as
> + * needed.  Minimum size of an authentication sequence is 1k (only one slot
> + * occupied, only one ECC P256 certificate in chain, SHA 256 hash selected).
> + * Maximum can be several MBytes.  Between 4k and 64k is probably typical.
> + *
> + * Return 0 on success or a negative errno.
> + */
> +int spdm_alloc_transcript(struct spdm_state *spdm_state)
> +{
> +	spdm_state->transcript = kvmalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (!spdm_state->transcript)
> +		return -ENOMEM;
> +
> +	spdm_state->transcript_end = spdm_state->transcript;
> +	spdm_state->transcript_max = PAGE_SIZE;
> +
> +	return 0;
> +}
> +
> +/**
> + * spdm_free_transcript() - Free transcript buffer
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Free the transcript buffer after performing authentication.  Reset the
> + * pointer to the current end of transcript as well as the allocation size.
> + */
> +void spdm_free_transcript(struct spdm_state *spdm_state)
> +{
> +	kvfree(spdm_state->transcript);
> +	spdm_state->transcript_end = NULL;
> +	spdm_state->transcript_max = 0;

Similar locking context with public functions concern, but also the
cleverness of why it is ok to not set ->transcript to NULL that really
only hold true if spdm_append_transcript() and and
spdm_free_transcript() are guaranteed to be serialized.

> +}
> +
> +/**
> + * spdm_append_transcript() - Append a message to transcript buffer
> + *
> + * @spdm_state: SPDM session state
> + * @msg: SPDM message
> + * @msg_sz: Size of @msg
> + *
> + * Append an SPDM message to the transcript after reception or transmission.
> + * Reallocate a larger transcript buffer if the message exceeds its current
> + * allocation size.
> + *
> + * If the message to be appended is known to fit into the allocation size,
> + * it may be directly received into or transmitted from the transcript buffer
> + * instead of calling this function:  Simply use the @transcript_end pointer in
> + * struct spdm_state as the position to store the message, then advance the
> + * pointer by the message size.
> + *
> + * Return 0 on success or a negative errno.
> + */
> +int spdm_append_transcript(struct spdm_state *spdm_state,
> +			   const void *msg, size_t msg_sz)
> +{
> +	size_t transcript_sz = spdm_state->transcript_end -
> +			       spdm_state->transcript;
> +
> +	if (transcript_sz + msg_sz > spdm_state->transcript_max) {
> +		size_t new_sz = round_up(transcript_sz + msg_sz, PAGE_SIZE);
> +		void *new = kvrealloc(spdm_state->transcript,
> +				      spdm_state->transcript_max,
> +				      new_sz, GFP_KERNEL);
> +		if (!new)
> +			return -ENOMEM;
> +
> +		spdm_state->transcript = new;
> +		spdm_state->transcript_end = new + transcript_sz;
> +		spdm_state->transcript_max = new_sz;
> +	}
> +
> +	memcpy(spdm_state->transcript_end, msg, msg_sz);
> +	spdm_state->transcript_end += msg_sz;
> +
> +	return 0;
> +}
> +
> +/**
> + * spdm_create_combined_prefix() - Create combined_spdm_prefix for a hash
> + *
> + * @version: SPDM version negotiated during GET_VERSION exchange
> + * @spdm_context: SPDM context of signature generation (or verification)
> + * @buf: Buffer to receive combined_spdm_prefix (100 bytes)
> + *
> + * From SPDM 1.2, a hash is prefixed with the SPDM version and context before
> + * a signature is generated (or verified) over the resulting concatenation
> + * (SPDM 1.2.0 section 15).  Create that prefix.
> + */
> +void spdm_create_combined_prefix(u8 version, const char *spdm_context,
> +				 void *buf)
> +{
> +	u8 major = FIELD_GET(0xf0, version);
> +	u8 minor = FIELD_GET(0x0f, version);
> +	size_t len = strlen(spdm_context);
> +	int rc, zero_pad;
> +
> +	rc = snprintf(buf, SPDM_PREFIX_SZ + 1,
> +		      "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*"
> +		      "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*",
> +		      major, minor, major, minor, major, minor, major, minor);
> +	WARN_ON(rc != SPDM_PREFIX_SZ);

Does this need a dynamic runtime check?

> +
> +	zero_pad = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - 1 - len;
> +	WARN_ON(zero_pad < 0);


Worth crashing the system here for panic_on_warn folks?

> +
> +	memset(buf + SPDM_PREFIX_SZ + 1, 0, zero_pad);
> +	memcpy(buf + SPDM_PREFIX_SZ + 1 + zero_pad, spdm_context, len);
> +}
> +
> +/**
> + * spdm_verify_signature() - Verify signature against leaf key
> + *
> + * @spdm_state: SPDM session state
> + * @spdm_context: SPDM context (used to create combined_spdm_prefix)
> + *
> + * Implementation of the abstract SPDMSignatureVerify() function described in
> + * SPDM 1.2.0 section 16:  Compute the hash over @spdm_state->transcript and
> + * verify that the signature at the end of the transcript was generated by
> + * @spdm_state->leaf_key.  Hashing the entire transcript allows detection
> + * of message modification by a man-in-the-middle or media error.
> + *
> + * Return 0 on success or a negative errno.
> + */
> +int spdm_verify_signature(struct spdm_state *spdm_state,
> +			  const char *spdm_context)
> +{
> +	struct public_key_signature sig = {
> +		.s = spdm_state->transcript_end - spdm_state->sig_len,
> +		.s_size = spdm_state->sig_len,
> +		.encoding = spdm_state->base_asym_enc,
> +		.hash_algo = spdm_state->base_hash_alg_name,
> +	};
> +	u8 *mhash __free(kfree) = NULL;
> +	u8 *m __free(kfree);
> +	int rc;
> +
> +	m = kmalloc(SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len, GFP_KERNEL);

I am not a fan of forward declared scoped-based resource management
variables [1], although PeterZ never responded to those suggestions.

[1]: http://lore.kernel.org/171175585714.2192972.12661675876300167762.stgit@dwillia2-xfh.jf.intel.com

> +	if (!m)
> +		return -ENOMEM;
> +
> +	/* Hash the transcript (sans trailing signature) */
> +	rc = crypto_shash_digest(spdm_state->desc, spdm_state->transcript,
> +				 (void *)sig.s - spdm_state->transcript,
> +				 m + SPDM_COMBINED_PREFIX_SZ);
> +	if (rc)
> +		return rc;
> +
> +	if (spdm_state->version <= 0x11) {
> +		/*
> +		 * SPDM 1.0 and 1.1 compute the signature only over the hash
> +		 * (SPDM 1.0.0 section 4.9.2.7).
> +		 */
> +		sig.digest = m + SPDM_COMBINED_PREFIX_SZ;
> +		sig.digest_size = spdm_state->hash_len;
> +	} else {
> +		/*
> +		 * From SPDM 1.2, the hash is prefixed with spdm_context before
> +		 * computing the signature over the resulting message M
> +		 * (SPDM 1.2.0 sec 15).
> +		 */
> +		spdm_create_combined_prefix(spdm_state->version, spdm_context,
> +					    m);
> +
> +		/*
> +		 * RSA and ECDSA algorithms require that M is hashed once more.
> +		 * EdDSA and SM2 algorithms omit that step.
> +		 * The switch statement prepares for their introduction.
> +		 */
> +		switch (spdm_state->base_asym_alg) {
> +		default:
> +			mhash = kmalloc(spdm_state->hash_len, GFP_KERNEL);
> +			if (!mhash)
> +				return -ENOMEM;
> +
> +			rc = crypto_shash_digest(spdm_state->desc, m,
> +				SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len,
> +				mhash);
> +			if (rc)
> +				return rc;
> +
> +			sig.digest = mhash;
> +			sig.digest_size = spdm_state->hash_len;
> +			break;
> +		}
> +	}
> +
> +	return public_key_verify_signature(spdm_state->leaf_key, &sig);
> +}
> +
> +/**
> + * spdm_reset() - Free cryptographic data structures
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Free cryptographic data structures when an SPDM session is destroyed or
> + * when the device is reauthenticated.
> + */
> +void spdm_reset(struct spdm_state *spdm_state)
> +{
> +	public_key_free(spdm_state->leaf_key);
> +	spdm_state->leaf_key = NULL;
> +
> +	kfree(spdm_state->desc);
> +	spdm_state->desc = NULL;
> +
> +	crypto_free_shash(spdm_state->shash);
> +	spdm_state->shash = NULL;
> +}
> +
> +/**
> + * spdm_create() - Allocate SPDM session
> + *
> + * @dev: Responder device
> + * @transport: Transport function to perform one message exchange
> + * @transport_priv: Transport private data
> + * @transport_sz: Maximum message size the transport is capable of (in bytes)
> + * @keyring: Trusted root certificates
> + *
> + * Return a pointer to the allocated SPDM session state or NULL on error.
> + */
> +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
> +			       void *transport_priv, u32 transport_sz,
> +			       struct key *keyring)
> +{
> +	struct spdm_state *spdm_state = kzalloc(sizeof(*spdm_state), GFP_KERNEL);
> +
> +	if (!spdm_state)
> +		return NULL;
> +
> +	spdm_state->dev = dev;
> +	spdm_state->transport = transport;
> +	spdm_state->transport_priv = transport_priv;
> +	spdm_state->transport_sz = transport_sz;
> +	spdm_state->root_keyring = keyring;
> +
> +	mutex_init(&spdm_state->lock);
> +
> +	return spdm_state;
> +}
> +EXPORT_SYMBOL_GPL(spdm_create);
> +
> +/**
> + * spdm_destroy() - Destroy SPDM session
> + *
> + * @spdm_state: SPDM session state
> + */
> +void spdm_destroy(struct spdm_state *spdm_state)
> +{
> +	u8 slot;
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS)
> +		kvfree(spdm_state->slot[slot]);
> +
> +	spdm_reset(spdm_state);
> +	mutex_destroy(&spdm_state->lock);
> +	kfree(spdm_state);
> +}
> +EXPORT_SYMBOL_GPL(spdm_destroy);
> +
> +MODULE_LICENSE("GPL");
> diff --git a/lib/spdm/req-authenticate.c b/lib/spdm/req-authenticate.c
> new file mode 100644
> index 000000000000..51fdb88f519b
> --- /dev/null
> +++ b/lib/spdm/req-authenticate.c
[..]
> +/**
> + * spdm_authenticate() - Authenticate device
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES,
> + * NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges.
> + *
> + * Perform internal locking to serialize multiple concurrent invocations.
> + * Can be called repeatedly for reauthentication.
> + *
> + * Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
> + * indicates authentication is not supported by the device.
> + */
> +int spdm_authenticate(struct spdm_state *spdm_state)
> +{
> +	u8 slot;
> +	int rc;
> +
> +	mutex_lock(&spdm_state->lock);
> +	spdm_reset(spdm_state);
> +
> +	rc = spdm_alloc_transcript(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_version(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_capabilities(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_negotiate_algs(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_digests(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
> +		rc = spdm_get_certificate(spdm_state, slot);
> +		if (rc)
> +			goto unlock;
> +	}
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
> +		rc = spdm_validate_cert_chain(spdm_state, slot);
> +		if (rc == 0)
> +			break;
> +	}
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_challenge(spdm_state, slot);
> +
> +unlock:
> +	if (rc)
> +		spdm_reset(spdm_state);

The above looks like it is asking for an __spdm_authenticate helper.

int spdm_authenticate(struct spdm_state *spdm_state)
{
	guard(mutex)(&spdm_state->lock);
	rc = __spdm_authenticate(spdm_state);
	if (rc)
		spdm_reset(spdm_state);
	spdm_state->authenticated = !rc;
	spdm_free_transcript(spdm_state);
	return rc;
}

Otherwise, looks good to me.
Lukas Wunner July 9, 2024, 8:49 a.m. UTC | #6
On Tue, Jul 09, 2024 at 10:45:27AM +1000, Alexey Kardashevskiy wrote:
> On 8/7/24 22:54, Lukas Wunner wrote:
> > The short answer is, it's a bug in libspdm and the issue should
> > go away once you update libspdm to version 3.1.0 or newer.
> 
> Easier to hack lib/spdm/req-authenticate.c just to see how far I can get
> with my device, now it is "Malformed certificate at slot 0 offset 0".

In that case all (up to 8) certificate chains should have been retrieved
and are available for examination in the certificates/ directory in sysfs
(below the PCI device's directory).

You can use ordinary openssl tooling to examine the certificates and
see what's wrong with them, see the ABI documentation in patch [12/18]
for examples:

https://lore.kernel.org/all/e42905e3e5f1d5be39355e833fefc349acb0b03c.1719771133.git.lukas@wunner.de/

The "Malformed certificate at slot 0 offset 0" message means that the
first certificate in the chain in slot 0 does not comply with
requirements set forth in the SPDM spec.  (E.g. Basic Constraints CA
value shall be false for leaf cert, true for intermediate and root certs
per SPDM 1.3.0 table 42.)

The expectation is that vendors will test their devices and fix issues
like this, so that end users never see those messages.

The error message is emitted by spdm_validate_cert_chain().
The implementation calls that to identify a certificate chain which is
considered valid by the kernel.  The first one found is used for
challenge-response authentication.  If none is found valid, the kernel
will try to perform challenge-response authentication with the first
*provisioned* slot, regardless of its validity.  That is done to
expose a signature in sysfs about which user space can make up its
own mind, see patch [17/18]:

https://lore.kernel.org/all/dff8bcb091a3123e1c7c685f8149595e39bbdb8f.1719771133.git.lukas@wunner.de/

So despite the error message you should see a signature with full SPDM
transcript and other ancillary data in the signatures/ directory in sysfs.

Not sure yet whether that feature (exposing a signature despite
cert chains' invalidity from the kernel POV) makes sense.
We can also discuss adding ABI which allows user space to force
challenge-response with a specific slot, or to declare a specific
slot valid.

Thanks,

Lukas
Jeff Johnson July 9, 2024, 3 p.m. UTC | #7
On 6/30/24 12:42, Lukas Wunner wrote:
...
> diff --git a/lib/spdm/core.c b/lib/spdm/core.c
> new file mode 100644
> index 000000000000..f06402f6d127
> --- /dev/null
> +++ b/lib/spdm/core.c
> @@ -0,0 +1,425 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Core routines for message exchange, message transcript,
> + * signature verification and session state lifecycle
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
...
> +
> +MODULE_LICENSE("GPL");

This is missing a MODULE_DESCRIPTION().

Building a module without a MODULE_DESCRIPTION() will result in a 
warning when building with make W=1.
Jonathan Cameron July 18, 2024, 11:42 a.m. UTC | #8
On Mon, 8 Jul 2024 22:09:47 -0700
Dan Williams <dan.j.williams@intel.com> wrote:

> Lukas Wunner wrote:
> > From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > 
> > The Security Protocol and Data Model (SPDM) allows for device
> > authentication, measurement, key exchange and encrypted sessions.
> > 
> > SPDM was conceived by the Distributed Management Task Force (DMTF).
> > Its specification defines a request/response protocol spoken between
> > host and attached devices over a variety of transports:
> > 
> >   https://www.dmtf.org/dsp/DSP0274
> > 
> > This implementation supports SPDM 1.0 through 1.3 (the latest version).
> > It is designed to be transport-agnostic as the kernel already supports
> > four different SPDM-capable transports:
> > 
> > * PCIe Data Object Exchange, which is a mailbox in PCI config space
> >   (PCIe r6.2 sec 6.30, drivers/pci/doe.c)
> > * Management Component Transport Protocol
> >   (MCTP, Documentation/networking/mctp.rst)
> > * TCP/IP (in draft stage)
> >   https://www.dmtf.org/sites/default/files/standards/documents/DSP0287_1.0.0WIP99.pdf
> > * SCSI and ATA (in draft stage)
> >   "SECURITY PROTOCOL IN/OUT" and "TRUSTED SEND/RECEIVE" commands
> > 
> > Use cases for SPDM include, but are not limited to:
> > 
> > * PCIe Component Measurement and Authentication (PCIe r6.2 sec 6.31)
> > * Compute Express Link (CXL r3.0 sec 14.11.6)
> > * Open Compute Project (Attestation of System Components v1.0)
> >   https://www.opencompute.org/documents/attestation-v1-0-20201104-pdf
> > * Open Compute Project (Datacenter NVMe SSD Specification v2.0)
> >   https://www.opencompute.org/documents/datacenter-nvme-ssd-specification-v2-0r21-pdf
> > 
> > The initial focus of this implementation is enabling PCIe CMA device
> > authentication.  As such, only a subset of the SPDM specification is
> > contained herein, namely the request/response sequence GET_VERSION,
> > GET_CAPABILITIES, NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE
> > and CHALLENGE.
> > 
> > This sequence first negotiates the SPDM protocol version, capabilities
> > and algorithms with the device.  It then retrieves the up to eight
> > certificate chains which may be provisioned on the device.  Finally it
> > performs challenge-response authentication with the device using one of
> > those eight certificate chains and the algorithms negotiated before.
> > The challenge-response authentication comprises computing a hash over
> > all exchanged messages to detect modification by a man-in-the-middle
> > or media error.  The hash is then signed with the device's private key
> > and the resulting signature is verified by the kernel using the device's
> > public key from the certificate chain.  Nonces are included in the
> > message sequence to protect against replay attacks.
> > 
> > A simple API is provided for subsystems wishing to authenticate devices:
> > spdm_create(), spdm_authenticate() (can be called repeatedly for
> > reauthentication) and spdm_destroy().  Certificates presented by devices
> > are validated against an in-kernel keyring of trusted root certificates.
> > A pointer to the keyring is passed to spdm_create().
> > 
> > The set of supported cryptographic algorithms is limited to those
> > declared mandatory in PCIe r6.2 sec 6.31.3.  Adding more algorithms
> > is straightforward as long as the crypto subsystem supports them.
> > 
> > Future commits will extend this implementation with support for
> > measurement, key exchange and encrypted sessions.
> > 
> > So far, only the SPDM requester role is implemented.  Care was taken to
> > allow for effortless addition of the responder role at a later stage.
> > This could be needed for a PCIe host bridge operating in endpoint mode.
> > The responder role will be able to reuse struct definitions and helpers
> > such as spdm_create_combined_prefix().  
> 
> Nice changelog.
> 
> > 
> > Credits:  Jonathan wrote a proof-of-concept of this SPDM implementation.
> > Lukas reworked it for upstream.
> > 
> > Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > Co-developed-by: Lukas Wunner <lukas@wunner.de>
> > Signed-off-by: Lukas Wunner <lukas@wunner.de>
> > ---
> >  MAINTAINERS                 |  11 +
> >  include/linux/spdm.h        |  33 ++
> >  lib/Kconfig                 |  15 +
> >  lib/Makefile                |   2 +
> >  lib/spdm/Makefile           |  10 +
> >  lib/spdm/core.c             | 425 ++++++++++++++++++++++
> >  lib/spdm/req-authenticate.c | 704 ++++++++++++++++++++++++++++++++++++
> >  lib/spdm/spdm.h             | 520 ++++++++++++++++++++++++++
> >  8 files changed, 1720 insertions(+)
> >  create mode 100644 include/linux/spdm.h
> >  create mode 100644 lib/spdm/Makefile
> >  create mode 100644 lib/spdm/core.c
> >  create mode 100644 lib/spdm/req-authenticate.c
> >  create mode 100644 lib/spdm/spdm.h  
> 
> I only have some quibbles below, but the broad strokes look good to me.
> 
> > 
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index d6c90161c7bf..dbe16eea8818 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -20145,6 +20145,17 @@ M:	Security Officers <security@kernel.org>
> >  S:	Supported
> >  F:	Documentation/process/security-bugs.rst
> >  
> > +SECURITY PROTOCOL AND DATA MODEL (SPDM)
> > +M:	Jonathan Cameron <jic23@kernel.org>
> > +M:	Lukas Wunner <lukas@wunner.de>
> > +L:	linux-coco@lists.linux.dev
> > +L:	linux-cxl@vger.kernel.org
> > +L:	linux-pci@vger.kernel.org
> > +S:	Maintained
> > +T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
> > +F:	include/linux/spdm.h
> > +F:	lib/spdm/
> > +
> >  SECURITY SUBSYSTEM
> >  M:	Paul Moore <paul@paul-moore.com>
> >  M:	James Morris <jmorris@namei.org>
> > diff --git a/include/linux/spdm.h b/include/linux/spdm.h
> > new file mode 100644
> > index 000000000000..0da7340020c4
> > --- /dev/null
> > +++ b/include/linux/spdm.h
> > @@ -0,0 +1,33 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * DMTF Security Protocol and Data Model (SPDM)
> > + * https://www.dmtf.org/dsp/DSP0274
> > + *
> > + * Copyright (C) 2021-22 Huawei
> > + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > + *
> > + * Copyright (C) 2022-24 Intel Corporation
> > + */
> > +
> > +#ifndef _SPDM_H_
> > +#define _SPDM_H_
> > +
> > +#include <linux/types.h>
> > +
> > +struct key;
> > +struct device;
> > +struct spdm_state;
> > +
> > +typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
> > +				 const void *request, size_t request_sz,
> > +				 void *response, size_t response_sz);
> > +
> > +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
> > +			       void *transport_priv, u32 transport_sz,
> > +			       struct key *keyring);
> > +
> > +int spdm_authenticate(struct spdm_state *spdm_state);
> > +
> > +void spdm_destroy(struct spdm_state *spdm_state);
> > +
> > +#endif
> > diff --git a/lib/Kconfig b/lib/Kconfig
> > index d33a268bc256..9011fa32af45 100644
> > --- a/lib/Kconfig
> > +++ b/lib/Kconfig
> > @@ -782,3 +782,18 @@ config POLYNOMIAL
> >  
> >  config FIRMWARE_TABLE
> >  	bool
> > +
> > +config SPDM
> > +	tristate
> > +	select CRYPTO
> > +	select KEYS
> > +	select ASYMMETRIC_KEY_TYPE
> > +	select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> > +	select X509_CERTIFICATE_PARSER
> > +	help
> > +	  The Security Protocol and Data Model (SPDM) allows for device
> > +	  authentication, measurement, key exchange and encrypted sessions.
> > +
> > +	  Crypto algorithms negotiated with SPDM are limited to those enabled
> > +	  in .config.  Drivers selecting SPDM therefore need to also select
> > +	  any algorithms they deem mandatory.
> > diff --git a/lib/Makefile b/lib/Makefile
> > index 3b1769045651..b2ef14d1fa71 100644
> > --- a/lib/Makefile
> > +++ b/lib/Makefile
> > @@ -301,6 +301,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
> >  obj-$(CONFIG_ASN1) += asn1_decoder.o
> >  obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
> >  
> > +obj-$(CONFIG_SPDM) += spdm/
> > +
> >  obj-$(CONFIG_FONT_SUPPORT) += fonts/
> >  
> >  hostprogs	:= gen_crc32table
> > diff --git a/lib/spdm/Makefile b/lib/spdm/Makefile
> > new file mode 100644
> > index 000000000000..f579cc898dbc
> > --- /dev/null
> > +++ b/lib/spdm/Makefile
> > @@ -0,0 +1,10 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# DMTF Security Protocol and Data Model (SPDM)
> > +# https://www.dmtf.org/dsp/DSP0274
> > +#
> > +# Copyright (C) 2024 Intel Corporation
> > +
> > +obj-$(CONFIG_SPDM) += spdm.o
> > +
> > +spdm-y := core.o req-authenticate.o
> > diff --git a/lib/spdm/core.c b/lib/spdm/core.c
> > new file mode 100644
> > index 000000000000..f06402f6d127
> > --- /dev/null
> > +++ b/lib/spdm/core.c
> > @@ -0,0 +1,425 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * DMTF Security Protocol and Data Model (SPDM)
> > + * https://www.dmtf.org/dsp/DSP0274
> > + *
> > + * Core routines for message exchange, message transcript,
> > + * signature verification and session state lifecycle
> > + *
> > + * Copyright (C) 2021-22 Huawei
> > + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > + *
> > + * Copyright (C) 2022-24 Intel Corporation
> > + */
> > +
> > +#include "spdm.h"
> > +
> > +#include <linux/dev_printk.h>
> > +#include <linux/module.h>
> > +
> > +#include <crypto/hash.h>
> > +#include <crypto/public_key.h>
> > +
> > +static int spdm_err(struct device *dev, struct spdm_error_rsp *rsp)  
> 
> This approach to error singnaling looks unprecedented, and not in a good
> way. I like the idea of a SPDM-error-code to errno converter, and
> separate SPDM-error-code to error string, but not an SPDM-error-code to
> errno conversion that has a log emitting side effect.

That's fair.  Using a common static const array of structs with string
and error code would also make this pair more readable by concentrating
the error code in one place.

> 
> Would this not emit ambiguous messages like:
> 
>     cxl_pci 0000:35:00.0: Unexpected request
> 
> How does I know that error message is from CXL SPDM functionality and
> not some other part of the driver?
> 
> What if the SPDM authentication is optional, how does the consumer of
> this library avoid log spam? What about rate limiting?
> 
> Did you consider leaving all error logging to the caller?

Problem there is that we've typically lost information because
of conversion to a smaller set of errno.

> 
> I have less problem if these all become dev_dbg() or tracepoints, but
> dev_err() seems awkward.

dev_dbg() seems like an easy solution to me.  Maybe add tracepoints
later...

> 
> > +{
> > +	switch (rsp->error_code) {
> > +	case SPDM_INVALID_REQUEST:
> > +		dev_err(dev, "Invalid request\n");
> > +		return -EINVAL;
> > +	case SPDM_INVALID_SESSION:
> > +		if (rsp->version == 0x11) {
> > +			dev_err(dev, "Invalid session %#x\n", rsp->error_data);
> > +			return -EINVAL;
> > +		}
> > +		break;
> > +	case SPDM_BUSY:
> > +		dev_err(dev, "Busy\n");
> > +		return -EBUSY;
> > +	case SPDM_UNEXPECTED_REQUEST:
> > +		dev_err(dev, "Unexpected request\n");
> > +		return -EINVAL;
> > +	case SPDM_UNSPECIFIED:
> > +		dev_err(dev, "Unspecified error\n");
> > +		return -EINVAL;
> > +	case SPDM_DECRYPT_ERROR:
> > +		dev_err(dev, "Decrypt error\n");
> > +		return -EIO;
> > +	case SPDM_UNSUPPORTED_REQUEST:
> > +		dev_err(dev, "Unsupported request %#x\n", rsp->error_data);
> > +		return -EINVAL;
> > +	case SPDM_REQUEST_IN_FLIGHT:
> > +		dev_err(dev, "Request in flight\n");
> > +		return -EINVAL;
> > +	case SPDM_INVALID_RESPONSE_CODE:
> > +		dev_err(dev, "Invalid response code\n");
> > +		return -EINVAL;
> > +	case SPDM_SESSION_LIMIT_EXCEEDED:
> > +		dev_err(dev, "Session limit exceeded\n");
> > +		return -EBUSY;
> > +	case SPDM_SESSION_REQUIRED:
> > +		dev_err(dev, "Session required\n");
> > +		return -EINVAL;
> > +	case SPDM_RESET_REQUIRED:
> > +		dev_err(dev, "Reset required\n");
> > +		return -ECONNRESET;
> > +	case SPDM_RESPONSE_TOO_LARGE:
> > +		dev_err(dev, "Response too large\n");
> > +		return -EINVAL;
> > +	case SPDM_REQUEST_TOO_LARGE:
> > +		dev_err(dev, "Request too large\n");
> > +		return -EINVAL;
> > +	case SPDM_LARGE_RESPONSE:
> > +		dev_err(dev, "Large response\n");
> > +		return -EMSGSIZE;
> > +	case SPDM_MESSAGE_LOST:
> > +		dev_err(dev, "Message lost\n");
> > +		return -EIO;
> > +	case SPDM_INVALID_POLICY:
> > +		dev_err(dev, "Invalid policy\n");
> > +		return -EINVAL;
> > +	case SPDM_VERSION_MISMATCH:
> > +		dev_err(dev, "Version mismatch\n");
> > +		return -EINVAL;
> > +	case SPDM_RESPONSE_NOT_READY:
> > +		dev_err(dev, "Response not ready\n");
> > +		return -EINPROGRESS;
> > +	case SPDM_REQUEST_RESYNCH:
> > +		dev_err(dev, "Request resynchronization\n");
> > +		return -ECONNRESET;
> > +	case SPDM_OPERATION_FAILED:
> > +		dev_err(dev, "Operation failed\n");
> > +		return -EINVAL;
> > +	case SPDM_NO_PENDING_REQUESTS:
> > +		return -ENOENT;
> > +	case SPDM_VENDOR_DEFINED_ERROR:
> > +		dev_err(dev, "Vendor defined error\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	dev_err(dev, "Undefined error %#x\n", rsp->error_code);
> > +	return -EINVAL;
> > +}
> > +
> > +/**
> > + * spdm_exchange() - Perform SPDM message exchange with device
> > + *
> > + * @spdm_state: SPDM session state
> > + * @req: Request message
> > + * @req_sz: Size of @req
> > + * @rsp: Response message
> > + * @rsp_sz: Size of @rsp
> > + *
> > + * Send the request @req to the device via the @transport in @spdm_state and
> > + * receive the response into @rsp, respecting the maximum buffer size @rsp_sz.
> > + * The request version is automatically populated.
> > + *
> > + * Return response size on success or a negative errno.  Response size may be
> > + * less than @rsp_sz and the caller is responsible for checking that.  It may
> > + * also be more than expected (though never more than @rsp_sz), e.g. if the
> > + * transport receives only dword-sized chunks.
> > + */
> > +ssize_t spdm_exchange(struct spdm_state *spdm_state,
> > +		      void *req, size_t req_sz, void *rsp, size_t rsp_sz)
> > +{
> > +	struct spdm_header *request = req;
> > +	struct spdm_header *response = rsp;
> > +	ssize_t rc, length;  
> 
> What's the locking assumption with this public function? I see that the
> internal to this file usages wrap it in the state lock. Should that
> assumption be codified with a:
> 
> lockdep_assert_held(&spdm_state->lock)
Seems reasonable to scatter those around.

> 
> ?
> 
> Or can this function be marked static?
Not without collapsing the various files into one.
> 
> > +
> > +	if (req_sz < sizeof(struct spdm_header) ||
> > +	    rsp_sz < sizeof(struct spdm_header))
> > +		return -EINVAL;
> > +
> > +	request->version = spdm_state->version;
> > +
> > +	rc = spdm_state->transport(spdm_state->transport_priv, spdm_state->dev,
> > +				   req, req_sz, rsp, rsp_sz);
> > +	if (rc < 0)
> > +		return rc;
> > +
> > +	length = rc;
> > +	if (length < sizeof(struct spdm_header))
> > +		return length; /* Truncated response is handled by callers */
> > +
> > +	if (response->code == SPDM_ERROR)
> > +		return spdm_err(spdm_state->dev, (struct spdm_error_rsp *)rsp);
> > +
> > +	if (response->code != (request->code & ~SPDM_REQ)) {
> > +		dev_err(spdm_state->dev,
> > +			"Response code %#x does not match request code %#x\n",
> > +			response->code, request->code);
> > +		return -EPROTO;
> > +	}
> > +
> > +	return length;
> > +}

...

> > +
> > +/**
> > + * spdm_free_transcript() - Free transcript buffer
> > + *
> > + * @spdm_state: SPDM session state
> > + *
> > + * Free the transcript buffer after performing authentication.  Reset the
> > + * pointer to the current end of transcript as well as the allocation size.
> > + */
> > +void spdm_free_transcript(struct spdm_state *spdm_state)
> > +{
> > +	kvfree(spdm_state->transcript);
> > +	spdm_state->transcript_end = NULL;
> > +	spdm_state->transcript_max = 0;  
> 
> Similar locking context with public functions concern, but also the
> cleverness of why it is ok to not set ->transcript to NULL that really
> only hold true if spdm_append_transcript() and and
> spdm_free_transcript() are guaranteed to be serialized.

If there is any concurrency risk things have gone very very wrong.
However maybe it's worth adding locking/markings just to make that clear.
Any contention will make the transcript garbage but nothing
stops us making that explicit rather that relying on callers doing
the right thing.

> 
> > +}

> > +/**
> > + * spdm_verify_signature() - Verify signature against leaf key
> > + *
> > + * @spdm_state: SPDM session state
> > + * @spdm_context: SPDM context (used to create combined_spdm_prefix)
> > + *
> > + * Implementation of the abstract SPDMSignatureVerify() function described in
> > + * SPDM 1.2.0 section 16:  Compute the hash over @spdm_state->transcript and
> > + * verify that the signature at the end of the transcript was generated by
> > + * @spdm_state->leaf_key.  Hashing the entire transcript allows detection
> > + * of message modification by a man-in-the-middle or media error.
> > + *
> > + * Return 0 on success or a negative errno.
> > + */
> > +int spdm_verify_signature(struct spdm_state *spdm_state,
> > +			  const char *spdm_context)
> > +{
> > +	struct public_key_signature sig = {
> > +		.s = spdm_state->transcript_end - spdm_state->sig_len,
> > +		.s_size = spdm_state->sig_len,
> > +		.encoding = spdm_state->base_asym_enc,
> > +		.hash_algo = spdm_state->base_hash_alg_name,
> > +	};
> > +	u8 *mhash __free(kfree) = NULL;
> > +	u8 *m __free(kfree);
> > +	int rc;
> > +
> > +	m = kmalloc(SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len, GFP_KERNEL);  
> 
> I am not a fan of forward declared scoped-based resource management
> variables [1], although PeterZ never responded to those suggestions.
> 
> [1]: http://lore.kernel.org/171175585714.2192972.12661675876300167762.stgit@dwillia2-xfh.jf.intel.com
> 
Agreed this is better as
	u8 *m __free(kfree) =
		kmalloc(SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len, GFP_KERNEL);

> > +	if (!m)
> > +		return -ENOMEM;
> > +
> > +	/* Hash the transcript (sans trailing signature) */
> > +	rc = crypto_shash_digest(spdm_state->desc, spdm_state->transcript,
> > +				 (void *)sig.s - spdm_state->transcript,
> > +				 m + SPDM_COMBINED_PREFIX_SZ);
> > +	if (rc)
> > +		return rc;
> > +
> > +	if (spdm_state->version <= 0x11) {
> > +		/*
> > +		 * SPDM 1.0 and 1.1 compute the signature only over the hash
> > +		 * (SPDM 1.0.0 section 4.9.2.7).
> > +		 */
> > +		sig.digest = m + SPDM_COMBINED_PREFIX_SZ;
> > +		sig.digest_size = spdm_state->hash_len;
> > +	} else {
> > +		/*
> > +		 * From SPDM 1.2, the hash is prefixed with spdm_context before
> > +		 * computing the signature over the resulting message M
> > +		 * (SPDM 1.2.0 sec 15).
> > +		 */
> > +		spdm_create_combined_prefix(spdm_state->version, spdm_context,
> > +					    m);
> > +
> > +		/*
> > +		 * RSA and ECDSA algorithms require that M is hashed once more.
> > +		 * EdDSA and SM2 algorithms omit that step.
> > +		 * The switch statement prepares for their introduction.
> > +		 */
> > +		switch (spdm_state->base_asym_alg) {
> > +		default:
and
			u8 *mhash __free(kfree) =
				kmalloc(spdm_state->hash_len, GFP_KERNEL);

With added bonus that the scope is reduced for this one.
You probably want to add explicit scope though with {} around the case block
so it's more obvious what the scope is.


> > +			mhash = kmalloc(spdm_state->hash_len, GFP_KERNEL);
> > +			if (!mhash)
> > +				return -ENOMEM;
> > +
> > +			rc = crypto_shash_digest(spdm_state->desc, m,
> > +				SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len,
> > +				mhash);
> > +			if (rc)
> > +				return rc;
> > +
> > +			sig.digest = mhash;
> > +			sig.digest_size = spdm_state->hash_len;
> > +			break;
> > +		}
> > +	}
Jonathan Cameron July 18, 2024, 2:24 p.m. UTC | #9
On Sun, 30 Jun 2024 21:42:00 +0200
Lukas Wunner <lukas@wunner.de> wrote:

> From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> The Security Protocol and Data Model (SPDM) allows for device
> authentication, measurement, key exchange and encrypted sessions.
> 
> SPDM was conceived by the Distributed Management Task Force (DMTF).
> Its specification defines a request/response protocol spoken between
> host and attached devices over a variety of transports:
> 
>   https://www.dmtf.org/dsp/DSP0274
> 
> This implementation supports SPDM 1.0 through 1.3 (the latest version).
> It is designed to be transport-agnostic as the kernel already supports
> four different SPDM-capable transports:
> 
> * PCIe Data Object Exchange, which is a mailbox in PCI config space
>   (PCIe r6.2 sec 6.30, drivers/pci/doe.c)
> * Management Component Transport Protocol
>   (MCTP, Documentation/networking/mctp.rst)
> * TCP/IP (in draft stage)
>   https://www.dmtf.org/sites/default/files/standards/documents/DSP0287_1.0.0WIP99.pdf
> * SCSI and ATA (in draft stage)
>   "SECURITY PROTOCOL IN/OUT" and "TRUSTED SEND/RECEIVE" commands
> 
> Use cases for SPDM include, but are not limited to:
> 
> * PCIe Component Measurement and Authentication (PCIe r6.2 sec 6.31)
> * Compute Express Link (CXL r3.0 sec 14.11.6)
> * Open Compute Project (Attestation of System Components v1.0)
>   https://www.opencompute.org/documents/attestation-v1-0-20201104-pdf
> * Open Compute Project (Datacenter NVMe SSD Specification v2.0)
>   https://www.opencompute.org/documents/datacenter-nvme-ssd-specification-v2-0r21-pdf
> 
> The initial focus of this implementation is enabling PCIe CMA device
> authentication.  As such, only a subset of the SPDM specification is
> contained herein, namely the request/response sequence GET_VERSION,
> GET_CAPABILITIES, NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE
> and CHALLENGE.
> 
> This sequence first negotiates the SPDM protocol version, capabilities
> and algorithms with the device.  It then retrieves the up to eight
> certificate chains which may be provisioned on the device.  Finally it
> performs challenge-response authentication with the device using one of
> those eight certificate chains and the algorithms negotiated before.
> The challenge-response authentication comprises computing a hash over
> all exchanged messages to detect modification by a man-in-the-middle
> or media error.  The hash is then signed with the device's private key
> and the resulting signature is verified by the kernel using the device's
> public key from the certificate chain.  Nonces are included in the
> message sequence to protect against replay attacks.
> 
> A simple API is provided for subsystems wishing to authenticate devices:
> spdm_create(), spdm_authenticate() (can be called repeatedly for
> reauthentication) and spdm_destroy().  Certificates presented by devices
> are validated against an in-kernel keyring of trusted root certificates.
> A pointer to the keyring is passed to spdm_create().
> 
> The set of supported cryptographic algorithms is limited to those
> declared mandatory in PCIe r6.2 sec 6.31.3.  Adding more algorithms
> is straightforward as long as the crypto subsystem supports them.
> 
> Future commits will extend this implementation with support for
> measurement, key exchange and encrypted sessions.
> 
> So far, only the SPDM requester role is implemented.  Care was taken to
> allow for effortless addition of the responder role at a later stage.
> This could be needed for a PCIe host bridge operating in endpoint mode.
> The responder role will be able to reuse struct definitions and helpers
> such as spdm_create_combined_prefix().
> 
> Credits:  Jonathan wrote a proof-of-concept of this SPDM implementation.
> Lukas reworked it for upstream.
> 
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Co-developed-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
They published a 1.3.1 since you sent this (1st July).

Given it happened to be the version I have to hand I'll review against
that.

A few minor things inline.


> diff --git a/lib/spdm/req-authenticate.c b/lib/spdm/req-authenticate.c
> new file mode 100644
> index 000000000000..51fdb88f519b
> --- /dev/null
> +++ b/lib/spdm/req-authenticate.c
> @@ -0,0 +1,704 @@


> +static int spdm_validate_cert_chain(struct spdm_state *spdm_state, u8 slot)
> +{
> +	struct x509_certificate *cert __free(x509_free_certificate) = NULL;
> +	struct x509_certificate *prev __free(x509_free_certificate) = NULL;
> +	size_t header_length, total_length;
> +	bool is_leaf_cert;
> +	size_t offset = 0;
> +	struct key *key;
> +	int rc, length;
> +	u8 *certs;
> +
> +	header_length = sizeof(struct spdm_cert_chain) + spdm_state->hash_len;
> +	total_length = spdm_state->slot_sz[slot] - header_length;
> +	certs = (u8 *)spdm_state->slot[slot] + header_length;
> +
> +	do {
> +		rc = x509_get_certificate_length(certs + offset,
> +						 total_length - offset);
> +		if (rc < 0) {
> +			dev_err(spdm_state->dev, "Invalid certificate length "
> +				"at slot %u offset %zu\n", slot, offset);
> +			return rc;
> +		}
> +
> +		length = rc;
> +		is_leaf_cert = offset + length == total_length;
> +
> +		cert = x509_cert_parse(certs + offset, length);
> +		if (IS_ERR(cert)) {
> +			dev_err(spdm_state->dev, "Certificate parse error %pe "
> +				"at slot %u offset %zu\n", cert, slot, offset);
> +			return PTR_ERR(cert);
> +		}
> +		if (cert->unsupported_sig) {
> +			dev_err(spdm_state->dev, "Unsupported signature "
> +				"at slot %u offset %zu\n", slot, offset);
> +			return -EKEYREJECTED;
> +		}
> +		if (cert->blacklisted)
> +			return -EKEYREJECTED;
> +
> +		/*
> +		 * Basic Constraints CA value shall be false for leaf cert,
> +		 * true for intermediate and root certs (SPDM 1.3.0 table 42).
> +		 * Key Usage bit for digital signature shall be set, except
> +		 * for GenericCert in slot > 0 (SPDM 1.3.0 margin no 354).
> +		 * KeyCertSign bit must be 0 for non-CA (RFC 5280 sec 4.2.1.9).
> +		 */
> +		if ((is_leaf_cert ==
> +		     test_bit(KEY_EFLAG_CA, &cert->pub->key_eflags)) ||
> +		    (is_leaf_cert && slot == 0 &&
> +		     !test_bit(KEY_EFLAG_DIGITALSIG, &cert->pub->key_eflags)) ||
> +		    (is_leaf_cert &&
> +		     test_bit(KEY_EFLAG_KEYCERTSIGN, &cert->pub->key_eflags))) {
> +			dev_err(spdm_state->dev, "Malformed certificate "
> +				"at slot %u offset %zu\n", slot, offset);
> +			return -EKEYREJECTED;
> +		}
> +
> +		if (!prev) {
> +			/* First cert in chain, check against root_keyring */
> +			key = find_asymmetric_key(spdm_state->root_keyring,
> +						  cert->sig->auth_ids[0],
> +						  cert->sig->auth_ids[1],
> +						  cert->sig->auth_ids[2],
> +						  false);
> +			if (IS_ERR(key)) {
> +				dev_info(spdm_state->dev, "Root certificate "
> +					 "of slot %u not found in %s "
> +					 "keyring: %s\n", slot,
> +					 spdm_state->root_keyring->description,
> +					 cert->issuer);
> +				return PTR_ERR(key);
> +			}
> +
> +			rc = verify_signature(key, cert->sig);
> +			key_put(key);
> +		} else {
> +			/* Subsequent cert in chain, check against previous */
> +			rc = public_key_verify_signature(prev->pub, cert->sig);
> +		}
> +
> +		if (rc) {
> +			dev_err(spdm_state->dev, "Signature validation error "
> +				"%d at slot %u offset %zu\n", rc, slot, offset);
> +			return rc;
> +		}
> +
> +		x509_free_certificate(prev);
> +		prev = cert;

I'd steal that pointer with no_free_ptr()

> +		cert = ERR_PTR(-ENOKEY);

I think this is clearer as NULL which you will get from no_free_ptr()


> +
> +		offset += length;
> +	} while (offset < total_length);
> +
> +	/* Steal pub pointer ahead of x509_free_certificate() */
> +	spdm_state->leaf_key = prev->pub;
> +	prev->pub = NULL;
> +
> +	return 0;
> +}
> +

> +/**
> + * spdm_challenge_rsp_sz() - Calculate CHALLENGE_AUTH response size
> + *
> + * @spdm_state: SPDM session state
> + * @rsp: CHALLENGE_AUTH response (optional)
> + *
> + * A CHALLENGE_AUTH response contains multiple variable-length fields
> + * as well as optional fields.  This helper eases calculating its size.
> + *
> + * If @rsp is %NULL, assume the maximum OpaqueDataLength of 1024 bytes
> + * (SPDM 1.0.0 table 21).  Otherwise read OpaqueDataLength from @rsp.
> + * OpaqueDataLength can only be > 0 for SPDM 1.0 and 1.1, as they lack
> + * the OtherParamsSupport field in the NEGOTIATE_ALGORITHMS request.
> + * For SPDM 1.2+, we do not offer any Opaque Data Formats in that field,
> + * which forces OpaqueDataLength to 0 (SPDM 1.2.0 margin no 261).
> + */
> +static size_t spdm_challenge_rsp_sz(struct spdm_state *spdm_state,
> +				    struct spdm_challenge_rsp *rsp)
> +{
> +	size_t  size  = sizeof(*rsp)		/* Header */
> +		      + spdm_state->hash_len	/* CertChainHash */
> +		      + SPDM_NONCE_SZ;		/* Nonce */
> +
> +	if (rsp)
> +		/* May be unaligned if hash algorithm has odd length */
> +		size += get_unaligned_le16((u8 *)rsp + size);
> +	else
> +		size += SPDM_MAX_OPAQUE_DATA;	/* OpaqueData */
> +
> +	size += 2;				/* OpaqueDataLength */
> +
> +	if (spdm_state->version >= 0x13)
> +		size += 8;			/* RequesterContext */
> +
> +	return  size  + spdm_state->sig_len;	/* Signature */

Odd spacing.  Personally I'd not bother trying to align things other
than maybe the comments.


> +}
> +
> +static int spdm_challenge(struct spdm_state *spdm_state, u8 slot)
> +{
> +	struct spdm_challenge_rsp *rsp __free(kfree);
> +	struct spdm_challenge_req req = {
> +		.code = SPDM_CHALLENGE,
> +		.param1 = slot,
> +		.param2 = 0, /* No measurement summary hash */
> +	};
> +	size_t req_sz, rsp_sz, rsp_sz_max;
> +	int rc, length;
> +
> +	get_random_bytes(&req.nonce, sizeof(req.nonce));
> +
> +	if (spdm_state->version <= 0x12)
> +		req_sz = offsetofend(typeof(req), nonce);
> +	else
> +		req_sz = sizeof(req);
> +
> +	rsp_sz_max = spdm_challenge_rsp_sz(spdm_state, NULL);
> +	rsp = kzalloc(rsp_sz_max, GFP_KERNEL);

Pull the declaration down here so we don't have fragility that
an error check might be added before here.
I'm lazy so not digging out the reference but Linus came down
clearly in favor of just using inline declarations to put the
constructor and destructor together.

> +	if (!rsp)
> +		return -ENOMEM;
> +
> +	rc = spdm_exchange(spdm_state, &req, req_sz, rsp, rsp_sz_max);
> +	if (rc < 0)
> +		return rc;
> +
It's probably overly paranoid but we could check some fields in the
response such as the slot..

> +	length = rc;
> +	rsp_sz = spdm_challenge_rsp_sz(spdm_state, rsp);
> +	if (length < rsp_sz) {
> +		dev_err(spdm_state->dev, "Truncated challenge_auth response\n");
> +		return -EIO;
> +	}
> +
> +	rc = spdm_append_transcript(spdm_state, &req, req_sz);
> +	if (rc)
> +		return rc;
> +
> +	rc = spdm_append_transcript(spdm_state, rsp, rsp_sz);
> +	if (rc)
> +		return rc;
> +
> +	/* Verify signature at end of transcript against leaf key */
> +	rc = spdm_verify_signature(spdm_state, spdm_context);
> +	if (rc)
> +		dev_err(spdm_state->dev,
> +			"Cannot verify challenge_auth signature: %d\n", rc);
> +	else
> +		dev_info(spdm_state->dev,
> +			 "Authenticated with certificate slot %u\n", slot);
> +
> +	return rc;
> +}
> +
> +/**
> + * spdm_authenticate() - Authenticate device
> + *
> + * @spdm_state: SPDM session state
> + *
> + * Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES,
> + * NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges.
> + *
> + * Perform internal locking to serialize multiple concurrent invocations.
> + * Can be called repeatedly for reauthentication.
> + *
> + * Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
> + * indicates authentication is not supported by the device.
> + */
> +int spdm_authenticate(struct spdm_state *spdm_state)
> +{
> +	u8 slot;
> +	int rc;
> +
> +	mutex_lock(&spdm_state->lock);
> +	spdm_reset(spdm_state);
> +
> +	rc = spdm_alloc_transcript(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_version(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_capabilities(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_negotiate_algs(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_get_digests(spdm_state);
> +	if (rc)
> +		goto unlock;
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {

I never understood if you were actually allowed to do this without starting
the whole set again. Is there an example of multiple cert requesting
in the spec?  I vaguely recall poking our specification folk on this and
they couldn't give a clear answer so suggested doing whole sequence again
so the transcript matches the ones in the spec.

Logically I'd like it to work this way (and I assume libspdm does) but
I'd really like a reference or other argument for why...
The GET_CERTIFICATE / GET_CERTIFICATE_RESPONSE can definitely be multiple
messages to deal with large cert chains, but can we start again with a new
slot and still have the transcript updated correctly?

> +		rc = spdm_get_certificate(spdm_state, slot);
> +		if (rc)
> +			goto unlock;
> +	}
> +
> +	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
> +		rc = spdm_validate_cert_chain(spdm_state, slot);
> +		if (rc == 0)
> +			break;
> +	}
> +	if (rc)
> +		goto unlock;
> +
> +	rc = spdm_challenge(spdm_state, slot);
> +
> +unlock:
> +	if (rc)
> +		spdm_reset(spdm_state);
> +	spdm_state->authenticated = !rc;
> +	spdm_free_transcript(spdm_state);
> +	mutex_unlock(&spdm_state->lock);

Dan suggested a nice cleanup for this to avoid the
goto dance.

> +	return rc;
> +}
> +EXPORT_SYMBOL_GPL(spdm_authenticate);



> diff --git a/lib/spdm/spdm.h b/lib/spdm/spdm.h
> new file mode 100644
> index 000000000000..3a104959ad53
> --- /dev/null
> +++ b/lib/spdm/spdm.h
> @@ -0,0 +1,520 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * DMTF Security Protocol and Data Model (SPDM)
> + * https://www.dmtf.org/dsp/DSP0274
> + *
> + * Copyright (C) 2021-22 Huawei
> + *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#ifndef _LIB_SPDM_H_
> +#define _LIB_SPDM_H_
> +
> +#undef  DEFAULT_SYMBOL_NAMESPACE
> +#define DEFAULT_SYMBOL_NAMESPACE SPDM
> +
> +#define dev_fmt(fmt) "SPDM: " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/mutex.h>
> +#include <linux/spdm.h>
> +
> +/* SPDM versions supported by this implementation */
> +#define SPDM_MIN_VER 0x10
> +#define SPDM_MAX_VER 0x13
> +
> +/* SPDM capabilities (SPDM 1.1.0 margin no 177, 178) */
> +#define SPDM_CACHE_CAP			BIT(0)		/* 1.0 resp only */
> +#define SPDM_CERT_CAP			BIT(1)		/* 1.0 */
> +#define SPDM_CHAL_CAP			BIT(2)		/* 1.0 */
> +#define SPDM_MEAS_CAP_MASK		GENMASK(4, 3)	/* 1.0 resp only */
> +#define   SPDM_MEAS_CAP_NO		0		/* 1.0 resp only */
> +#define   SPDM_MEAS_CAP_MEAS		1		/* 1.0 resp only */
> +#define   SPDM_MEAS_CAP_MEAS_SIG	2		/* 1.0 resp only */
> +#define SPDM_MEAS_FRESH_CAP		BIT(5)		/* 1.0 resp only */
> +#define SPDM_ENCRYPT_CAP		BIT(6)		/* 1.1 */
> +#define SPDM_MAC_CAP			BIT(7)		/* 1.1 */
> +#define SPDM_MUT_AUTH_CAP		BIT(8)		/* 1.1 */
> +#define SPDM_KEY_EX_CAP			BIT(9)		/* 1.1 */
> +#define SPDM_PSK_CAP_MASK		GENMASK(11, 10)	/* 1.1 */
> +#define   SPDM_PSK_CAP_NO		0		/* 1.1 */
> +#define   SPDM_PSK_CAP_PSK		1		/* 1.1 */
> +#define   SPDM_PSK_CAP_PSK_CTX		2		/* 1.1 resp only */
> +#define SPDM_ENCAP_CAP			BIT(12)		/* 1.1 */
> +#define SPDM_HBEAT_CAP			BIT(13)		/* 1.1 */
> +#define SPDM_KEY_UPD_CAP		BIT(14)		/* 1.1 */
> +#define SPDM_HANDSHAKE_ITC_CAP		BIT(15)		/* 1.1 */
> +#define SPDM_PUB_KEY_ID_CAP		BIT(16)		/* 1.1 */
> +#define SPDM_CHUNK_CAP			BIT(17)		/* 1.2 */
> +#define SPDM_ALIAS_CERT_CAP		BIT(18)		/* 1.2 resp only */
> +#define SPDM_SET_CERT_CAP		BIT(19)		/* 1.2 resp only */
> +#define SPDM_CSR_CAP			BIT(20)		/* 1.2 resp only */
> +#define SPDM_CERT_INST_RESET_CAP	BIT(21)		/* 1.2 resp only */

Some of these 1.2 only entries are non obvious from the 1.3.1 spec.
This one for instance is in the change log as a 1.3.0 addition

> +#define SPDM_EP_INFO_CAP_MASK		GENMASK(23, 22) /* 1.3 */
> +#define   SPDM_EP_INFO_CAP_NO		0		/* 1.3 */
> +#define   SPDM_EP_INFO_CAP_RSP		1		/* 1.3 */
> +#define   SPDM_EP_INFO_CAP_RSP_SIG	2		/* 1.3 */
> +#define SPDM_MEL_CAP			BIT(24)		/* 1.3 resp only */
> +#define SPDM_EVENT_CAP			BIT(25)		/* 1.3 */
> +#define SPDM_MULTI_KEY_CAP_MASK		GENMASK(27, 26)	/* 1.3 */
> +#define   SPDM_MULTI_KEY_CAP_NO		0		/* 1.3 */
> +#define   SPDM_MULTI_KEY_CAP_ONLY	1		/* 1.3 */
> +#define   SPDM_MULTI_KEY_CAP_SEL	2		/* 1.3 */
> +#define SPDM_GET_KEY_PAIR_INFO_CAP	BIT(28)		/* 1.3 resp only */
> +#define SPDM_SET_KEY_PAIR_INFO_CAP	BIT(29)		/* 1.3 resp only */


...

> +
> +#define SPDM_GET_CAPABILITIES 0xe1
> +#define SPDM_MIN_DATA_TRANSFER_SIZE 42 /* SPDM 1.2.0 margin no 226 */
> +
> +/*
> + * Newer SPDM versions insert fields at the end of messages (enlarging them)
> + * or use reserved space for new fields (leaving message size unchanged).
> + */
> +struct spdm_get_capabilities_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1;
> +	u8 param2;
> +	/* End of SPDM 1.0 structure */
> +
> +	u8 reserved1;					/* 1.1 */
> +	u8 ctexponent;					/* 1.1 */
> +	u16 reserved2;					/* 1.1 */

Maybe should be consistent for resered fields in using u8 []
Doesn't matter much though.

> +	__le32 flags;					/* 1.1 */
> +	/* End of SPDM 1.1 structure */
> +
> +	__le32 data_transfer_size;			/* 1.2 */
> +	__le32 max_spdm_msg_size;			/* 1.2 */
> +} __packed;
> +
> +struct spdm_get_capabilities_rsp {
> +	u8 version;
> +	u8 code;
> +	u8 param1;
> +	u8 param2;
> +
> +	u8 reserved1;
> +	u8 ctexponent;
> +	u16 reserved2;

As above.

> +	__le32 flags;
> +	/* End of SPDM 1.0 structure */
> +
> +	__le32 data_transfer_size;			/* 1.2 */
> +	__le32 max_spdm_msg_size;			/* 1.2 */
> +	/* End of SPDM 1.2 structure */
> +
> +	/*
> +	 * Additional optional fields at end of this structure:
> +	 * - SupportedAlgorithms: variable size		 * 1.3 *
> +	 */
> +} __packed;
> +


> +#define SPDM_CHALLENGE 0x83
> +#define SPDM_NONCE_SZ 32 /* SPDM 1.0.0 table 20 */
> +#define SPDM_PREFIX_SZ 64 /* SPDM 1.2.0 margin no 803 */
> +#define SPDM_COMBINED_PREFIX_SZ 100 /* SPDM 1.2.0 margin no 806 */
> +#define SPDM_MAX_OPAQUE_DATA 1024 /* SPDM 1.0.0 table 21 */
> +
> +struct spdm_challenge_req {
> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Slot number 0..7 */
> +	u8 param2; /* MeasurementSummaryHash type */
> +	u8 nonce[SPDM_NONCE_SZ];
> +	/* End of SPDM 1.2 (and earlier) structure */
> +
> +	u8 context[8];					/* 1.3 */
> +} __packed;
> +
> +struct spdm_challenge_rsp {
If we are matching the spec, oddly the response to a challenge request
is a challenge auth response.  Who knows why...

> +	u8 version;
> +	u8 code;
> +	u8 param1; /* Slot number 0..7 */

Do we want to add comment on Basic MutAuthrReq (deprecated) bit 7?
Might in theory bite us at somepoint.

> +	u8 param2; /* Slot mask */
> +	/*
> +	 * Additional fields at end of this structure:
> +	 * - CertChainHash: Hash of struct spdm_cert_chain for selected slot
> +	 * - Nonce: 32 bytes long
> +	 * - MeasurementSummaryHash: Optional hash of selected measurements
> +	 * - OpaqueDataLength: 2 bytes long
> +	 * - OpaqueData: Up to 1024 bytes long
> +	 * - RequesterContext: 8 bytes long		 * 1.3 *
> +	 *   (inserted, moves Signature field)
> +	 * - Signature
> +	 */
> +} __packed;
> +
> +#define SPDM_ERROR 0x7f

> +/**
> + * struct spdm_state - SPDM session state
> + *
> + * @dev: Responder device.  Used for error reporting and passed to @transport.
> + * @lock: Serializes multiple concurrent spdm_authenticate() calls.
> + * @authenticated: Whether device was authenticated successfully.
> + * @dev: Responder device.  Used for error reporting and passed to @transport.
Two dev entries.

Make sure to run the kernel-doc script over the files to catch these little
dos issues.

...

> + */
> +struct spdm_state {
> +	struct device *dev;
> +	struct mutex lock;
> +	unsigned int authenticated:1;
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index d6c90161c7bf..dbe16eea8818 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20145,6 +20145,17 @@  M:	Security Officers <security@kernel.org>
 S:	Supported
 F:	Documentation/process/security-bugs.rst
 
+SECURITY PROTOCOL AND DATA MODEL (SPDM)
+M:	Jonathan Cameron <jic23@kernel.org>
+M:	Lukas Wunner <lukas@wunner.de>
+L:	linux-coco@lists.linux.dev
+L:	linux-cxl@vger.kernel.org
+L:	linux-pci@vger.kernel.org
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F:	include/linux/spdm.h
+F:	lib/spdm/
+
 SECURITY SUBSYSTEM
 M:	Paul Moore <paul@paul-moore.com>
 M:	James Morris <jmorris@namei.org>
diff --git a/include/linux/spdm.h b/include/linux/spdm.h
new file mode 100644
index 000000000000..0da7340020c4
--- /dev/null
+++ b/include/linux/spdm.h
@@ -0,0 +1,33 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Copyright (C) 2021-22 Huawei
+ *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#ifndef _SPDM_H_
+#define _SPDM_H_
+
+#include <linux/types.h>
+
+struct key;
+struct device;
+struct spdm_state;
+
+typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
+				 const void *request, size_t request_sz,
+				 void *response, size_t response_sz);
+
+struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
+			       void *transport_priv, u32 transport_sz,
+			       struct key *keyring);
+
+int spdm_authenticate(struct spdm_state *spdm_state);
+
+void spdm_destroy(struct spdm_state *spdm_state);
+
+#endif
diff --git a/lib/Kconfig b/lib/Kconfig
index d33a268bc256..9011fa32af45 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -782,3 +782,18 @@  config POLYNOMIAL
 
 config FIRMWARE_TABLE
 	bool
+
+config SPDM
+	tristate
+	select CRYPTO
+	select KEYS
+	select ASYMMETRIC_KEY_TYPE
+	select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+	select X509_CERTIFICATE_PARSER
+	help
+	  The Security Protocol and Data Model (SPDM) allows for device
+	  authentication, measurement, key exchange and encrypted sessions.
+
+	  Crypto algorithms negotiated with SPDM are limited to those enabled
+	  in .config.  Drivers selecting SPDM therefore need to also select
+	  any algorithms they deem mandatory.
diff --git a/lib/Makefile b/lib/Makefile
index 3b1769045651..b2ef14d1fa71 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -301,6 +301,8 @@  obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
 obj-$(CONFIG_ASN1) += asn1_decoder.o
 obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
 
+obj-$(CONFIG_SPDM) += spdm/
+
 obj-$(CONFIG_FONT_SUPPORT) += fonts/
 
 hostprogs	:= gen_crc32table
diff --git a/lib/spdm/Makefile b/lib/spdm/Makefile
new file mode 100644
index 000000000000..f579cc898dbc
--- /dev/null
+++ b/lib/spdm/Makefile
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0
+#
+# DMTF Security Protocol and Data Model (SPDM)
+# https://www.dmtf.org/dsp/DSP0274
+#
+# Copyright (C) 2024 Intel Corporation
+
+obj-$(CONFIG_SPDM) += spdm.o
+
+spdm-y := core.o req-authenticate.o
diff --git a/lib/spdm/core.c b/lib/spdm/core.c
new file mode 100644
index 000000000000..f06402f6d127
--- /dev/null
+++ b/lib/spdm/core.c
@@ -0,0 +1,425 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Core routines for message exchange, message transcript,
+ * signature verification and session state lifecycle
+ *
+ * Copyright (C) 2021-22 Huawei
+ *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#include "spdm.h"
+
+#include <linux/dev_printk.h>
+#include <linux/module.h>
+
+#include <crypto/hash.h>
+#include <crypto/public_key.h>
+
+static int spdm_err(struct device *dev, struct spdm_error_rsp *rsp)
+{
+	switch (rsp->error_code) {
+	case SPDM_INVALID_REQUEST:
+		dev_err(dev, "Invalid request\n");
+		return -EINVAL;
+	case SPDM_INVALID_SESSION:
+		if (rsp->version == 0x11) {
+			dev_err(dev, "Invalid session %#x\n", rsp->error_data);
+			return -EINVAL;
+		}
+		break;
+	case SPDM_BUSY:
+		dev_err(dev, "Busy\n");
+		return -EBUSY;
+	case SPDM_UNEXPECTED_REQUEST:
+		dev_err(dev, "Unexpected request\n");
+		return -EINVAL;
+	case SPDM_UNSPECIFIED:
+		dev_err(dev, "Unspecified error\n");
+		return -EINVAL;
+	case SPDM_DECRYPT_ERROR:
+		dev_err(dev, "Decrypt error\n");
+		return -EIO;
+	case SPDM_UNSUPPORTED_REQUEST:
+		dev_err(dev, "Unsupported request %#x\n", rsp->error_data);
+		return -EINVAL;
+	case SPDM_REQUEST_IN_FLIGHT:
+		dev_err(dev, "Request in flight\n");
+		return -EINVAL;
+	case SPDM_INVALID_RESPONSE_CODE:
+		dev_err(dev, "Invalid response code\n");
+		return -EINVAL;
+	case SPDM_SESSION_LIMIT_EXCEEDED:
+		dev_err(dev, "Session limit exceeded\n");
+		return -EBUSY;
+	case SPDM_SESSION_REQUIRED:
+		dev_err(dev, "Session required\n");
+		return -EINVAL;
+	case SPDM_RESET_REQUIRED:
+		dev_err(dev, "Reset required\n");
+		return -ECONNRESET;
+	case SPDM_RESPONSE_TOO_LARGE:
+		dev_err(dev, "Response too large\n");
+		return -EINVAL;
+	case SPDM_REQUEST_TOO_LARGE:
+		dev_err(dev, "Request too large\n");
+		return -EINVAL;
+	case SPDM_LARGE_RESPONSE:
+		dev_err(dev, "Large response\n");
+		return -EMSGSIZE;
+	case SPDM_MESSAGE_LOST:
+		dev_err(dev, "Message lost\n");
+		return -EIO;
+	case SPDM_INVALID_POLICY:
+		dev_err(dev, "Invalid policy\n");
+		return -EINVAL;
+	case SPDM_VERSION_MISMATCH:
+		dev_err(dev, "Version mismatch\n");
+		return -EINVAL;
+	case SPDM_RESPONSE_NOT_READY:
+		dev_err(dev, "Response not ready\n");
+		return -EINPROGRESS;
+	case SPDM_REQUEST_RESYNCH:
+		dev_err(dev, "Request resynchronization\n");
+		return -ECONNRESET;
+	case SPDM_OPERATION_FAILED:
+		dev_err(dev, "Operation failed\n");
+		return -EINVAL;
+	case SPDM_NO_PENDING_REQUESTS:
+		return -ENOENT;
+	case SPDM_VENDOR_DEFINED_ERROR:
+		dev_err(dev, "Vendor defined error\n");
+		return -EINVAL;
+	}
+
+	dev_err(dev, "Undefined error %#x\n", rsp->error_code);
+	return -EINVAL;
+}
+
+/**
+ * spdm_exchange() - Perform SPDM message exchange with device
+ *
+ * @spdm_state: SPDM session state
+ * @req: Request message
+ * @req_sz: Size of @req
+ * @rsp: Response message
+ * @rsp_sz: Size of @rsp
+ *
+ * Send the request @req to the device via the @transport in @spdm_state and
+ * receive the response into @rsp, respecting the maximum buffer size @rsp_sz.
+ * The request version is automatically populated.
+ *
+ * Return response size on success or a negative errno.  Response size may be
+ * less than @rsp_sz and the caller is responsible for checking that.  It may
+ * also be more than expected (though never more than @rsp_sz), e.g. if the
+ * transport receives only dword-sized chunks.
+ */
+ssize_t spdm_exchange(struct spdm_state *spdm_state,
+		      void *req, size_t req_sz, void *rsp, size_t rsp_sz)
+{
+	struct spdm_header *request = req;
+	struct spdm_header *response = rsp;
+	ssize_t rc, length;
+
+	if (req_sz < sizeof(struct spdm_header) ||
+	    rsp_sz < sizeof(struct spdm_header))
+		return -EINVAL;
+
+	request->version = spdm_state->version;
+
+	rc = spdm_state->transport(spdm_state->transport_priv, spdm_state->dev,
+				   req, req_sz, rsp, rsp_sz);
+	if (rc < 0)
+		return rc;
+
+	length = rc;
+	if (length < sizeof(struct spdm_header))
+		return length; /* Truncated response is handled by callers */
+
+	if (response->code == SPDM_ERROR)
+		return spdm_err(spdm_state->dev, (struct spdm_error_rsp *)rsp);
+
+	if (response->code != (request->code & ~SPDM_REQ)) {
+		dev_err(spdm_state->dev,
+			"Response code %#x does not match request code %#x\n",
+			response->code, request->code);
+		return -EPROTO;
+	}
+
+	return length;
+}
+
+/**
+ * spdm_alloc_transcript() - Allocate transcript buffer
+ *
+ * @spdm_state: SPDM session state
+ *
+ * Allocate a buffer to accommodate the concatenation of all SPDM messages
+ * exchanged during an authentication sequence.  Used to verify the signature,
+ * as it is computed over the hashed transcript.
+ *
+ * Transcript size is initially one page.  It grows by additional pages as
+ * needed.  Minimum size of an authentication sequence is 1k (only one slot
+ * occupied, only one ECC P256 certificate in chain, SHA 256 hash selected).
+ * Maximum can be several MBytes.  Between 4k and 64k is probably typical.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int spdm_alloc_transcript(struct spdm_state *spdm_state)
+{
+	spdm_state->transcript = kvmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!spdm_state->transcript)
+		return -ENOMEM;
+
+	spdm_state->transcript_end = spdm_state->transcript;
+	spdm_state->transcript_max = PAGE_SIZE;
+
+	return 0;
+}
+
+/**
+ * spdm_free_transcript() - Free transcript buffer
+ *
+ * @spdm_state: SPDM session state
+ *
+ * Free the transcript buffer after performing authentication.  Reset the
+ * pointer to the current end of transcript as well as the allocation size.
+ */
+void spdm_free_transcript(struct spdm_state *spdm_state)
+{
+	kvfree(spdm_state->transcript);
+	spdm_state->transcript_end = NULL;
+	spdm_state->transcript_max = 0;
+}
+
+/**
+ * spdm_append_transcript() - Append a message to transcript buffer
+ *
+ * @spdm_state: SPDM session state
+ * @msg: SPDM message
+ * @msg_sz: Size of @msg
+ *
+ * Append an SPDM message to the transcript after reception or transmission.
+ * Reallocate a larger transcript buffer if the message exceeds its current
+ * allocation size.
+ *
+ * If the message to be appended is known to fit into the allocation size,
+ * it may be directly received into or transmitted from the transcript buffer
+ * instead of calling this function:  Simply use the @transcript_end pointer in
+ * struct spdm_state as the position to store the message, then advance the
+ * pointer by the message size.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int spdm_append_transcript(struct spdm_state *spdm_state,
+			   const void *msg, size_t msg_sz)
+{
+	size_t transcript_sz = spdm_state->transcript_end -
+			       spdm_state->transcript;
+
+	if (transcript_sz + msg_sz > spdm_state->transcript_max) {
+		size_t new_sz = round_up(transcript_sz + msg_sz, PAGE_SIZE);
+		void *new = kvrealloc(spdm_state->transcript,
+				      spdm_state->transcript_max,
+				      new_sz, GFP_KERNEL);
+		if (!new)
+			return -ENOMEM;
+
+		spdm_state->transcript = new;
+		spdm_state->transcript_end = new + transcript_sz;
+		spdm_state->transcript_max = new_sz;
+	}
+
+	memcpy(spdm_state->transcript_end, msg, msg_sz);
+	spdm_state->transcript_end += msg_sz;
+
+	return 0;
+}
+
+/**
+ * spdm_create_combined_prefix() - Create combined_spdm_prefix for a hash
+ *
+ * @version: SPDM version negotiated during GET_VERSION exchange
+ * @spdm_context: SPDM context of signature generation (or verification)
+ * @buf: Buffer to receive combined_spdm_prefix (100 bytes)
+ *
+ * From SPDM 1.2, a hash is prefixed with the SPDM version and context before
+ * a signature is generated (or verified) over the resulting concatenation
+ * (SPDM 1.2.0 section 15).  Create that prefix.
+ */
+void spdm_create_combined_prefix(u8 version, const char *spdm_context,
+				 void *buf)
+{
+	u8 major = FIELD_GET(0xf0, version);
+	u8 minor = FIELD_GET(0x0f, version);
+	size_t len = strlen(spdm_context);
+	int rc, zero_pad;
+
+	rc = snprintf(buf, SPDM_PREFIX_SZ + 1,
+		      "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*"
+		      "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*",
+		      major, minor, major, minor, major, minor, major, minor);
+	WARN_ON(rc != SPDM_PREFIX_SZ);
+
+	zero_pad = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - 1 - len;
+	WARN_ON(zero_pad < 0);
+
+	memset(buf + SPDM_PREFIX_SZ + 1, 0, zero_pad);
+	memcpy(buf + SPDM_PREFIX_SZ + 1 + zero_pad, spdm_context, len);
+}
+
+/**
+ * spdm_verify_signature() - Verify signature against leaf key
+ *
+ * @spdm_state: SPDM session state
+ * @spdm_context: SPDM context (used to create combined_spdm_prefix)
+ *
+ * Implementation of the abstract SPDMSignatureVerify() function described in
+ * SPDM 1.2.0 section 16:  Compute the hash over @spdm_state->transcript and
+ * verify that the signature at the end of the transcript was generated by
+ * @spdm_state->leaf_key.  Hashing the entire transcript allows detection
+ * of message modification by a man-in-the-middle or media error.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int spdm_verify_signature(struct spdm_state *spdm_state,
+			  const char *spdm_context)
+{
+	struct public_key_signature sig = {
+		.s = spdm_state->transcript_end - spdm_state->sig_len,
+		.s_size = spdm_state->sig_len,
+		.encoding = spdm_state->base_asym_enc,
+		.hash_algo = spdm_state->base_hash_alg_name,
+	};
+	u8 *mhash __free(kfree) = NULL;
+	u8 *m __free(kfree);
+	int rc;
+
+	m = kmalloc(SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len, GFP_KERNEL);
+	if (!m)
+		return -ENOMEM;
+
+	/* Hash the transcript (sans trailing signature) */
+	rc = crypto_shash_digest(spdm_state->desc, spdm_state->transcript,
+				 (void *)sig.s - spdm_state->transcript,
+				 m + SPDM_COMBINED_PREFIX_SZ);
+	if (rc)
+		return rc;
+
+	if (spdm_state->version <= 0x11) {
+		/*
+		 * SPDM 1.0 and 1.1 compute the signature only over the hash
+		 * (SPDM 1.0.0 section 4.9.2.7).
+		 */
+		sig.digest = m + SPDM_COMBINED_PREFIX_SZ;
+		sig.digest_size = spdm_state->hash_len;
+	} else {
+		/*
+		 * From SPDM 1.2, the hash is prefixed with spdm_context before
+		 * computing the signature over the resulting message M
+		 * (SPDM 1.2.0 sec 15).
+		 */
+		spdm_create_combined_prefix(spdm_state->version, spdm_context,
+					    m);
+
+		/*
+		 * RSA and ECDSA algorithms require that M is hashed once more.
+		 * EdDSA and SM2 algorithms omit that step.
+		 * The switch statement prepares for their introduction.
+		 */
+		switch (spdm_state->base_asym_alg) {
+		default:
+			mhash = kmalloc(spdm_state->hash_len, GFP_KERNEL);
+			if (!mhash)
+				return -ENOMEM;
+
+			rc = crypto_shash_digest(spdm_state->desc, m,
+				SPDM_COMBINED_PREFIX_SZ + spdm_state->hash_len,
+				mhash);
+			if (rc)
+				return rc;
+
+			sig.digest = mhash;
+			sig.digest_size = spdm_state->hash_len;
+			break;
+		}
+	}
+
+	return public_key_verify_signature(spdm_state->leaf_key, &sig);
+}
+
+/**
+ * spdm_reset() - Free cryptographic data structures
+ *
+ * @spdm_state: SPDM session state
+ *
+ * Free cryptographic data structures when an SPDM session is destroyed or
+ * when the device is reauthenticated.
+ */
+void spdm_reset(struct spdm_state *spdm_state)
+{
+	public_key_free(spdm_state->leaf_key);
+	spdm_state->leaf_key = NULL;
+
+	kfree(spdm_state->desc);
+	spdm_state->desc = NULL;
+
+	crypto_free_shash(spdm_state->shash);
+	spdm_state->shash = NULL;
+}
+
+/**
+ * spdm_create() - Allocate SPDM session
+ *
+ * @dev: Responder device
+ * @transport: Transport function to perform one message exchange
+ * @transport_priv: Transport private data
+ * @transport_sz: Maximum message size the transport is capable of (in bytes)
+ * @keyring: Trusted root certificates
+ *
+ * Return a pointer to the allocated SPDM session state or NULL on error.
+ */
+struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
+			       void *transport_priv, u32 transport_sz,
+			       struct key *keyring)
+{
+	struct spdm_state *spdm_state = kzalloc(sizeof(*spdm_state), GFP_KERNEL);
+
+	if (!spdm_state)
+		return NULL;
+
+	spdm_state->dev = dev;
+	spdm_state->transport = transport;
+	spdm_state->transport_priv = transport_priv;
+	spdm_state->transport_sz = transport_sz;
+	spdm_state->root_keyring = keyring;
+
+	mutex_init(&spdm_state->lock);
+
+	return spdm_state;
+}
+EXPORT_SYMBOL_GPL(spdm_create);
+
+/**
+ * spdm_destroy() - Destroy SPDM session
+ *
+ * @spdm_state: SPDM session state
+ */
+void spdm_destroy(struct spdm_state *spdm_state)
+{
+	u8 slot;
+
+	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS)
+		kvfree(spdm_state->slot[slot]);
+
+	spdm_reset(spdm_state);
+	mutex_destroy(&spdm_state->lock);
+	kfree(spdm_state);
+}
+EXPORT_SYMBOL_GPL(spdm_destroy);
+
+MODULE_LICENSE("GPL");
diff --git a/lib/spdm/req-authenticate.c b/lib/spdm/req-authenticate.c
new file mode 100644
index 000000000000..51fdb88f519b
--- /dev/null
+++ b/lib/spdm/req-authenticate.c
@@ -0,0 +1,704 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Requester role: Authenticate a device
+ *
+ * Copyright (C) 2021-22 Huawei
+ *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#include "spdm.h"
+
+#include <linux/dev_printk.h>
+#include <linux/key.h>
+#include <linux/random.h>
+
+#include <asm/unaligned.h>
+#include <crypto/hash.h>
+#include <crypto/hash_info.h>
+#include <keys/asymmetric-type.h>
+#include <keys/x509-parser.h>
+
+/* SPDM 1.2.0 margin no 359 and 803 */
+static const char *spdm_context = "responder-challenge_auth signing";
+
+/*
+ * All SPDM messages exchanged during an authentication sequence up to and
+ * including GET_DIGESTS fit into a single page, hence are stored in the
+ * transcript without bounds checking.  Only subsequent GET_CERTIFICATE
+ * and CHALLENGE exchanges may exceed one page.
+ */
+static_assert(PAGE_SIZE >=
+	sizeof(struct spdm_get_version_req) +
+	struct_size_t(struct spdm_get_version_rsp,
+		      version_number_entries, 255) +
+	sizeof(struct spdm_get_capabilities_req) +
+	sizeof(struct spdm_get_capabilities_rsp) +
+	sizeof(struct spdm_negotiate_algs_req) +
+	sizeof(struct spdm_negotiate_algs_rsp) +
+	sizeof(struct spdm_req_alg_struct) * 2 * SPDM_MAX_REQ_ALG_STRUCT +
+	sizeof(struct spdm_get_digests_req) +
+	struct_size_t(struct spdm_get_digests_rsp,
+		      digests, SPDM_SLOTS * SHA512_DIGEST_SIZE));
+
+static int spdm_get_version(struct spdm_state *spdm_state)
+{
+	struct spdm_get_version_req *req = spdm_state->transcript;
+	struct spdm_get_version_rsp *rsp;
+	bool foundver = false;
+	int rc, length, i;
+
+	spdm_state->version = 0x10;
+
+	*req = (struct spdm_get_version_req) {
+		.code = SPDM_GET_VERSION,
+	};
+
+	rsp = spdm_state->transcript_end += sizeof(*req);
+
+	rc = spdm_exchange(spdm_state, req, sizeof(*req), rsp,
+			   struct_size(rsp, version_number_entries, 255));
+	if (rc < 0)
+		return rc;
+
+	length = rc;
+	if (length < sizeof(*rsp) ||
+	    length < struct_size(rsp, version_number_entries,
+				 rsp->version_number_entry_count)) {
+		dev_err(spdm_state->dev, "Truncated version response\n");
+		return -EIO;
+	}
+
+	spdm_state->transcript_end +=
+		     struct_size(rsp, version_number_entries,
+				 rsp->version_number_entry_count);
+
+	for (i = 0; i < rsp->version_number_entry_count; i++) {
+		u8 ver = le16_to_cpu(rsp->version_number_entries[i]) >> 8;
+
+		if (ver >= spdm_state->version && ver <= SPDM_MAX_VER) {
+			spdm_state->version = ver;
+			foundver = true;
+		}
+	}
+	if (!foundver) {
+		dev_err(spdm_state->dev, "No common supported version\n");
+		return -EPROTO;
+	}
+
+	return 0;
+}
+
+static int spdm_get_capabilities(struct spdm_state *spdm_state)
+{
+	struct spdm_get_capabilities_req *req = spdm_state->transcript_end;
+	struct spdm_get_capabilities_rsp *rsp;
+	size_t req_sz, rsp_sz;
+	int rc, length;
+
+	*req = (struct spdm_get_capabilities_req) {
+		.code = SPDM_GET_CAPABILITIES,
+		.ctexponent = SPDM_CTEXPONENT,
+		.flags = cpu_to_le32(SPDM_REQ_CAPS),
+	};
+
+	if (spdm_state->version == 0x10) {
+		req_sz = offsetofend(typeof(*req), param2);
+		rsp_sz = offsetofend(typeof(*rsp), flags);
+	} else if (spdm_state->version == 0x11) {
+		req_sz = offsetofend(typeof(*req), flags);
+		rsp_sz = offsetofend(typeof(*rsp), flags);
+	} else {
+		req_sz = sizeof(*req);
+		rsp_sz = sizeof(*rsp);
+		req->data_transfer_size = cpu_to_le32(spdm_state->transport_sz);
+		req->max_spdm_msg_size = cpu_to_le32(spdm_state->transport_sz);
+	}
+
+	rsp = spdm_state->transcript_end += req_sz;
+
+	rc = spdm_exchange(spdm_state, req, req_sz, rsp, rsp_sz);
+	if (rc < 0)
+		return rc;
+
+	length = rc;
+	if (length < rsp_sz) {
+		dev_err(spdm_state->dev, "Truncated capabilities response\n");
+		return -EIO;
+	}
+
+	spdm_state->transcript_end += rsp_sz;
+
+	spdm_state->rsp_caps = le32_to_cpu(rsp->flags);
+	if ((spdm_state->rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS)
+		return -EPROTONOSUPPORT;
+
+	if (spdm_state->version >= 0x12) {
+		u32 data_transfer_size = le32_to_cpu(rsp->data_transfer_size);
+		if (data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE) {
+			dev_err(spdm_state->dev,
+				"Malformed capabilities response\n");
+			return -EPROTO;
+		}
+		spdm_state->transport_sz = min(spdm_state->transport_sz,
+					       data_transfer_size);
+	}
+
+	return 0;
+}
+
+static int spdm_parse_algs(struct spdm_state *spdm_state)
+{
+	switch (spdm_state->base_asym_alg) {
+	case SPDM_ASYM_RSASSA_2048:
+		spdm_state->sig_len = 256;
+		spdm_state->base_asym_enc = "pkcs1";
+		break;
+	case SPDM_ASYM_RSASSA_3072:
+		spdm_state->sig_len = 384;
+		spdm_state->base_asym_enc = "pkcs1";
+		break;
+	case SPDM_ASYM_RSASSA_4096:
+		spdm_state->sig_len = 512;
+		spdm_state->base_asym_enc = "pkcs1";
+		break;
+	case SPDM_ASYM_ECDSA_ECC_NIST_P256:
+		spdm_state->sig_len = 64;
+		spdm_state->base_asym_enc = "p1363";
+		break;
+	case SPDM_ASYM_ECDSA_ECC_NIST_P384:
+		spdm_state->sig_len = 96;
+		spdm_state->base_asym_enc = "p1363";
+		break;
+	case SPDM_ASYM_ECDSA_ECC_NIST_P521:
+		spdm_state->sig_len = 132;
+		spdm_state->base_asym_enc = "p1363";
+		break;
+	default:
+		dev_err(spdm_state->dev, "Unknown asym algorithm\n");
+		return -EINVAL;
+	}
+
+	switch (spdm_state->base_hash_alg) {
+	case SPDM_HASH_SHA_256:
+		spdm_state->base_hash_alg_name = "sha256";
+		break;
+	case SPDM_HASH_SHA_384:
+		spdm_state->base_hash_alg_name = "sha384";
+		break;
+	case SPDM_HASH_SHA_512:
+		spdm_state->base_hash_alg_name = "sha512";
+		break;
+	default:
+		dev_err(spdm_state->dev, "Unknown hash algorithm\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * shash and desc allocations are reused for subsequent measurement
+	 * retrieval, hence are not freed until spdm_reset().
+	 */
+	spdm_state->shash = crypto_alloc_shash(spdm_state->base_hash_alg_name,
+					       0, 0);
+	if (!spdm_state->shash)
+		return -ENOMEM;
+
+	spdm_state->desc = kzalloc(sizeof(*spdm_state->desc) +
+				   crypto_shash_descsize(spdm_state->shash),
+				   GFP_KERNEL);
+	if (!spdm_state->desc)
+		return -ENOMEM;
+
+	spdm_state->desc->tfm = spdm_state->shash;
+
+	/* Used frequently to compute offsets, so cache H */
+	spdm_state->hash_len = crypto_shash_digestsize(spdm_state->shash);
+
+	return crypto_shash_init(spdm_state->desc);
+}
+
+static int spdm_negotiate_algs(struct spdm_state *spdm_state)
+{
+	struct spdm_negotiate_algs_req *req = spdm_state->transcript_end;
+	struct spdm_negotiate_algs_rsp *rsp;
+	struct spdm_req_alg_struct *req_alg_struct;
+	size_t req_sz = sizeof(*req);
+	size_t rsp_sz = sizeof(*rsp);
+	int rc, length;
+
+	/* Request length shall be <= 128 bytes (SPDM 1.1.0 margin no 185) */
+	BUILD_BUG_ON(req_sz > 128);
+
+	*req = (struct spdm_negotiate_algs_req) {
+		.code = SPDM_NEGOTIATE_ALGS,
+		.length = cpu_to_le16(req_sz),
+		.base_asym_algo = cpu_to_le32(SPDM_ASYM_ALGOS),
+		.base_hash_algo = cpu_to_le32(SPDM_HASH_ALGOS),
+	};
+
+	rsp = spdm_state->transcript_end += req_sz;
+
+	rc = spdm_exchange(spdm_state, req, req_sz, rsp, rsp_sz);
+	if (rc < 0)
+		return rc;
+
+	length = rc;
+	if (length < sizeof(*rsp) ||
+	    length < sizeof(*rsp) + rsp->param1 * sizeof(*req_alg_struct)) {
+		dev_err(spdm_state->dev, "Truncated algorithms response\n");
+		return -EIO;
+	}
+
+	/*
+	 * If request contained a ReqAlgStruct not supported by responder,
+	 * the corresponding RespAlgStruct may be omitted in response.
+	 * Calculate the actual (possibly shorter) response length:
+	 */
+	spdm_state->transcript_end +=
+		     sizeof(*rsp) + rsp->param1 * sizeof(*req_alg_struct);
+
+	spdm_state->base_asym_alg = le32_to_cpu(rsp->base_asym_sel);
+	spdm_state->base_hash_alg = le32_to_cpu(rsp->base_hash_sel);
+
+	if ((spdm_state->base_asym_alg & SPDM_ASYM_ALGOS) == 0 ||
+	    (spdm_state->base_hash_alg & SPDM_HASH_ALGOS) == 0) {
+		dev_err(spdm_state->dev, "No common supported algorithms\n");
+		return -EPROTO;
+	}
+
+	/* Responder shall select exactly 1 alg (SPDM 1.0.0 table 14) */
+	if (hweight32(spdm_state->base_asym_alg) != 1 ||
+	    hweight32(spdm_state->base_hash_alg) != 1 ||
+	    rsp->ext_asym_sel_count != 0 ||
+	    rsp->ext_hash_sel_count != 0 ||
+	    rsp->param1 > req->param1) {
+		dev_err(spdm_state->dev, "Malformed algorithms response\n");
+		return -EPROTO;
+	}
+
+	return spdm_parse_algs(spdm_state);
+}
+
+static int spdm_get_digests(struct spdm_state *spdm_state)
+{
+	struct spdm_get_digests_req *req = spdm_state->transcript_end;
+	struct spdm_get_digests_rsp *rsp;
+	unsigned long deprovisioned_slots;
+	int rc, length;
+	size_t rsp_sz;
+	u8 slot;
+
+	*req = (struct spdm_get_digests_req) {
+		.code = SPDM_GET_DIGESTS,
+	};
+
+	rsp = spdm_state->transcript_end += sizeof(*req);
+
+	/*
+	 * Assume all 8 slots are populated.  We know the hash length (and thus
+	 * the response size) because the responder only returns digests for
+	 * the hash algorithm selected during the NEGOTIATE_ALGORITHMS exchange
+	 * (SPDM 1.1.2 margin no 206).
+	 */
+	rsp_sz = sizeof(*rsp) + SPDM_SLOTS * spdm_state->hash_len;
+
+	rc = spdm_exchange(spdm_state, req, sizeof(*req), rsp, rsp_sz);
+	if (rc < 0)
+		return rc;
+
+	length = rc;
+	if (length < sizeof(*rsp) ||
+	    length < sizeof(*rsp) + hweight8(rsp->param2) *
+				    spdm_state->hash_len) {
+		dev_err(spdm_state->dev, "Truncated digests response\n");
+		return -EIO;
+	}
+
+	spdm_state->transcript_end += sizeof(*rsp) + hweight8(rsp->param2) *
+						     spdm_state->hash_len;
+
+	deprovisioned_slots = spdm_state->provisioned_slots & ~rsp->param2;
+	for_each_set_bit(slot, &deprovisioned_slots, SPDM_SLOTS) {
+		kvfree(spdm_state->slot[slot]);
+		spdm_state->slot_sz[slot] = 0;
+		spdm_state->slot[slot] = NULL;
+	}
+
+	/*
+	 * Authentication-capable endpoints must carry at least 1 cert chain
+	 * (SPDM 1.0.0 section 4.9.2.1).
+	 */
+	spdm_state->provisioned_slots = rsp->param2;
+	if (!spdm_state->provisioned_slots) {
+		dev_err(spdm_state->dev, "No certificates provisioned\n");
+		return -EPROTO;
+	}
+
+	return 0;
+}
+
+static int spdm_get_certificate(struct spdm_state *spdm_state, u8 slot)
+{
+	struct spdm_cert_chain *certs __free(kvfree) = NULL;
+	struct spdm_get_certificate_rsp *rsp __free(kvfree);
+	struct spdm_get_certificate_req req = {
+		.code = SPDM_GET_CERTIFICATE,
+		.param1 = slot,
+	};
+	size_t rsp_sz, total_length, header_length;
+	u16 remainder_length = 0xffff;
+	u16 portion_length;
+	u16 offset = 0;
+	int rc, length;
+
+	/*
+	 * It is legal for the responder to send more bytes than requested.
+	 * (Note the "should" in SPDM 1.0.0 table 19.)  If we allocate a
+	 * too small buffer, we can't calculate the hash over the (truncated)
+	 * response.  Only choice is thus to allocate the maximum possible 64k.
+	 */
+	rsp_sz = min_t(u32, sizeof(*rsp) + 0xffff, spdm_state->transport_sz);
+	rsp = kvmalloc(rsp_sz, GFP_KERNEL);
+	if (!rsp)
+		return -ENOMEM;
+
+	do {
+		/*
+		 * If transport_sz is sufficiently large, first request will be
+		 * for offset 0 and length 0xffff, which means entire cert
+		 * chain (SPDM 1.0.0 table 18).
+		 */
+		req.offset = cpu_to_le16(offset);
+		req.length = cpu_to_le16(min_t(size_t, remainder_length,
+					       rsp_sz - sizeof(*rsp)));
+
+		rc = spdm_exchange(spdm_state, &req, sizeof(req), rsp, rsp_sz);
+		if (rc < 0)
+			return rc;
+
+		length = rc;
+		if (length < sizeof(*rsp) ||
+		    length < sizeof(*rsp) + le16_to_cpu(rsp->portion_length)) {
+			dev_err(spdm_state->dev,
+				"Truncated certificate response\n");
+			return -EIO;
+		}
+
+		portion_length = le16_to_cpu(rsp->portion_length);
+		remainder_length = le16_to_cpu(rsp->remainder_length);
+
+		rc = spdm_append_transcript(spdm_state, &req, sizeof(req));
+		if (rc)
+			return rc;
+
+		rc = spdm_append_transcript(spdm_state, rsp,
+					    sizeof(*rsp) + portion_length);
+		if (rc)
+			return rc;
+
+		/*
+		 * On first response we learn total length of cert chain.
+		 * Should portion_length + remainder_length exceed 0xffff,
+		 * the min() ensures that the malformed check triggers below.
+		 */
+		if (!certs) {
+			total_length = min(portion_length + remainder_length,
+					   0xffff);
+			certs = kvmalloc(total_length, GFP_KERNEL);
+			if (!certs)
+				return -ENOMEM;
+		}
+
+		if (!portion_length ||
+		    (rsp->param1 & 0xf) != slot ||
+		    offset + portion_length + remainder_length != total_length)
+		{
+			dev_err(spdm_state->dev,
+				"Malformed certificate response\n");
+			return -EPROTO;
+		}
+
+		memcpy((u8 *)certs + offset, rsp->cert_chain, portion_length);
+		offset += portion_length;
+	} while (remainder_length > 0);
+
+	header_length = sizeof(struct spdm_cert_chain) + spdm_state->hash_len;
+
+	if (total_length < header_length ||
+	    total_length != le16_to_cpu(certs->length)) {
+		dev_err(spdm_state->dev,
+			"Malformed certificate chain in slot %u\n", slot);
+		return -EPROTO;
+	}
+
+	kvfree(spdm_state->slot[slot]);
+	spdm_state->slot_sz[slot] = total_length;
+	spdm_state->slot[slot] = no_free_ptr(certs);
+
+	return 0;
+}
+
+static int spdm_validate_cert_chain(struct spdm_state *spdm_state, u8 slot)
+{
+	struct x509_certificate *cert __free(x509_free_certificate) = NULL;
+	struct x509_certificate *prev __free(x509_free_certificate) = NULL;
+	size_t header_length, total_length;
+	bool is_leaf_cert;
+	size_t offset = 0;
+	struct key *key;
+	int rc, length;
+	u8 *certs;
+
+	header_length = sizeof(struct spdm_cert_chain) + spdm_state->hash_len;
+	total_length = spdm_state->slot_sz[slot] - header_length;
+	certs = (u8 *)spdm_state->slot[slot] + header_length;
+
+	do {
+		rc = x509_get_certificate_length(certs + offset,
+						 total_length - offset);
+		if (rc < 0) {
+			dev_err(spdm_state->dev, "Invalid certificate length "
+				"at slot %u offset %zu\n", slot, offset);
+			return rc;
+		}
+
+		length = rc;
+		is_leaf_cert = offset + length == total_length;
+
+		cert = x509_cert_parse(certs + offset, length);
+		if (IS_ERR(cert)) {
+			dev_err(spdm_state->dev, "Certificate parse error %pe "
+				"at slot %u offset %zu\n", cert, slot, offset);
+			return PTR_ERR(cert);
+		}
+		if (cert->unsupported_sig) {
+			dev_err(spdm_state->dev, "Unsupported signature "
+				"at slot %u offset %zu\n", slot, offset);
+			return -EKEYREJECTED;
+		}
+		if (cert->blacklisted)
+			return -EKEYREJECTED;
+
+		/*
+		 * Basic Constraints CA value shall be false for leaf cert,
+		 * true for intermediate and root certs (SPDM 1.3.0 table 42).
+		 * Key Usage bit for digital signature shall be set, except
+		 * for GenericCert in slot > 0 (SPDM 1.3.0 margin no 354).
+		 * KeyCertSign bit must be 0 for non-CA (RFC 5280 sec 4.2.1.9).
+		 */
+		if ((is_leaf_cert ==
+		     test_bit(KEY_EFLAG_CA, &cert->pub->key_eflags)) ||
+		    (is_leaf_cert && slot == 0 &&
+		     !test_bit(KEY_EFLAG_DIGITALSIG, &cert->pub->key_eflags)) ||
+		    (is_leaf_cert &&
+		     test_bit(KEY_EFLAG_KEYCERTSIGN, &cert->pub->key_eflags))) {
+			dev_err(spdm_state->dev, "Malformed certificate "
+				"at slot %u offset %zu\n", slot, offset);
+			return -EKEYREJECTED;
+		}
+
+		if (!prev) {
+			/* First cert in chain, check against root_keyring */
+			key = find_asymmetric_key(spdm_state->root_keyring,
+						  cert->sig->auth_ids[0],
+						  cert->sig->auth_ids[1],
+						  cert->sig->auth_ids[2],
+						  false);
+			if (IS_ERR(key)) {
+				dev_info(spdm_state->dev, "Root certificate "
+					 "of slot %u not found in %s "
+					 "keyring: %s\n", slot,
+					 spdm_state->root_keyring->description,
+					 cert->issuer);
+				return PTR_ERR(key);
+			}
+
+			rc = verify_signature(key, cert->sig);
+			key_put(key);
+		} else {
+			/* Subsequent cert in chain, check against previous */
+			rc = public_key_verify_signature(prev->pub, cert->sig);
+		}
+
+		if (rc) {
+			dev_err(spdm_state->dev, "Signature validation error "
+				"%d at slot %u offset %zu\n", rc, slot, offset);
+			return rc;
+		}
+
+		x509_free_certificate(prev);
+		prev = cert;
+		cert = ERR_PTR(-ENOKEY);
+
+		offset += length;
+	} while (offset < total_length);
+
+	/* Steal pub pointer ahead of x509_free_certificate() */
+	spdm_state->leaf_key = prev->pub;
+	prev->pub = NULL;
+
+	return 0;
+}
+
+/**
+ * spdm_challenge_rsp_sz() - Calculate CHALLENGE_AUTH response size
+ *
+ * @spdm_state: SPDM session state
+ * @rsp: CHALLENGE_AUTH response (optional)
+ *
+ * A CHALLENGE_AUTH response contains multiple variable-length fields
+ * as well as optional fields.  This helper eases calculating its size.
+ *
+ * If @rsp is %NULL, assume the maximum OpaqueDataLength of 1024 bytes
+ * (SPDM 1.0.0 table 21).  Otherwise read OpaqueDataLength from @rsp.
+ * OpaqueDataLength can only be > 0 for SPDM 1.0 and 1.1, as they lack
+ * the OtherParamsSupport field in the NEGOTIATE_ALGORITHMS request.
+ * For SPDM 1.2+, we do not offer any Opaque Data Formats in that field,
+ * which forces OpaqueDataLength to 0 (SPDM 1.2.0 margin no 261).
+ */
+static size_t spdm_challenge_rsp_sz(struct spdm_state *spdm_state,
+				    struct spdm_challenge_rsp *rsp)
+{
+	size_t  size  = sizeof(*rsp)		/* Header */
+		      + spdm_state->hash_len	/* CertChainHash */
+		      + SPDM_NONCE_SZ;		/* Nonce */
+
+	if (rsp)
+		/* May be unaligned if hash algorithm has odd length */
+		size += get_unaligned_le16((u8 *)rsp + size);
+	else
+		size += SPDM_MAX_OPAQUE_DATA;	/* OpaqueData */
+
+	size += 2;				/* OpaqueDataLength */
+
+	if (spdm_state->version >= 0x13)
+		size += 8;			/* RequesterContext */
+
+	return  size  + spdm_state->sig_len;	/* Signature */
+}
+
+static int spdm_challenge(struct spdm_state *spdm_state, u8 slot)
+{
+	struct spdm_challenge_rsp *rsp __free(kfree);
+	struct spdm_challenge_req req = {
+		.code = SPDM_CHALLENGE,
+		.param1 = slot,
+		.param2 = 0, /* No measurement summary hash */
+	};
+	size_t req_sz, rsp_sz, rsp_sz_max;
+	int rc, length;
+
+	get_random_bytes(&req.nonce, sizeof(req.nonce));
+
+	if (spdm_state->version <= 0x12)
+		req_sz = offsetofend(typeof(req), nonce);
+	else
+		req_sz = sizeof(req);
+
+	rsp_sz_max = spdm_challenge_rsp_sz(spdm_state, NULL);
+	rsp = kzalloc(rsp_sz_max, GFP_KERNEL);
+	if (!rsp)
+		return -ENOMEM;
+
+	rc = spdm_exchange(spdm_state, &req, req_sz, rsp, rsp_sz_max);
+	if (rc < 0)
+		return rc;
+
+	length = rc;
+	rsp_sz = spdm_challenge_rsp_sz(spdm_state, rsp);
+	if (length < rsp_sz) {
+		dev_err(spdm_state->dev, "Truncated challenge_auth response\n");
+		return -EIO;
+	}
+
+	rc = spdm_append_transcript(spdm_state, &req, req_sz);
+	if (rc)
+		return rc;
+
+	rc = spdm_append_transcript(spdm_state, rsp, rsp_sz);
+	if (rc)
+		return rc;
+
+	/* Verify signature at end of transcript against leaf key */
+	rc = spdm_verify_signature(spdm_state, spdm_context);
+	if (rc)
+		dev_err(spdm_state->dev,
+			"Cannot verify challenge_auth signature: %d\n", rc);
+	else
+		dev_info(spdm_state->dev,
+			 "Authenticated with certificate slot %u\n", slot);
+
+	return rc;
+}
+
+/**
+ * spdm_authenticate() - Authenticate device
+ *
+ * @spdm_state: SPDM session state
+ *
+ * Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES,
+ * NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges.
+ *
+ * Perform internal locking to serialize multiple concurrent invocations.
+ * Can be called repeatedly for reauthentication.
+ *
+ * Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
+ * indicates authentication is not supported by the device.
+ */
+int spdm_authenticate(struct spdm_state *spdm_state)
+{
+	u8 slot;
+	int rc;
+
+	mutex_lock(&spdm_state->lock);
+	spdm_reset(spdm_state);
+
+	rc = spdm_alloc_transcript(spdm_state);
+	if (rc)
+		goto unlock;
+
+	rc = spdm_get_version(spdm_state);
+	if (rc)
+		goto unlock;
+
+	rc = spdm_get_capabilities(spdm_state);
+	if (rc)
+		goto unlock;
+
+	rc = spdm_negotiate_algs(spdm_state);
+	if (rc)
+		goto unlock;
+
+	rc = spdm_get_digests(spdm_state);
+	if (rc)
+		goto unlock;
+
+	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
+		rc = spdm_get_certificate(spdm_state, slot);
+		if (rc)
+			goto unlock;
+	}
+
+	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS) {
+		rc = spdm_validate_cert_chain(spdm_state, slot);
+		if (rc == 0)
+			break;
+	}
+	if (rc)
+		goto unlock;
+
+	rc = spdm_challenge(spdm_state, slot);
+
+unlock:
+	if (rc)
+		spdm_reset(spdm_state);
+	spdm_state->authenticated = !rc;
+	spdm_free_transcript(spdm_state);
+	mutex_unlock(&spdm_state->lock);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(spdm_authenticate);
diff --git a/lib/spdm/spdm.h b/lib/spdm/spdm.h
new file mode 100644
index 000000000000..3a104959ad53
--- /dev/null
+++ b/lib/spdm/spdm.h
@@ -0,0 +1,520 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Copyright (C) 2021-22 Huawei
+ *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#ifndef _LIB_SPDM_H_
+#define _LIB_SPDM_H_
+
+#undef  DEFAULT_SYMBOL_NAMESPACE
+#define DEFAULT_SYMBOL_NAMESPACE SPDM
+
+#define dev_fmt(fmt) "SPDM: " fmt
+
+#include <linux/bitfield.h>
+#include <linux/mutex.h>
+#include <linux/spdm.h>
+
+/* SPDM versions supported by this implementation */
+#define SPDM_MIN_VER 0x10
+#define SPDM_MAX_VER 0x13
+
+/* SPDM capabilities (SPDM 1.1.0 margin no 177, 178) */
+#define SPDM_CACHE_CAP			BIT(0)		/* 1.0 resp only */
+#define SPDM_CERT_CAP			BIT(1)		/* 1.0 */
+#define SPDM_CHAL_CAP			BIT(2)		/* 1.0 */
+#define SPDM_MEAS_CAP_MASK		GENMASK(4, 3)	/* 1.0 resp only */
+#define   SPDM_MEAS_CAP_NO		0		/* 1.0 resp only */
+#define   SPDM_MEAS_CAP_MEAS		1		/* 1.0 resp only */
+#define   SPDM_MEAS_CAP_MEAS_SIG	2		/* 1.0 resp only */
+#define SPDM_MEAS_FRESH_CAP		BIT(5)		/* 1.0 resp only */
+#define SPDM_ENCRYPT_CAP		BIT(6)		/* 1.1 */
+#define SPDM_MAC_CAP			BIT(7)		/* 1.1 */
+#define SPDM_MUT_AUTH_CAP		BIT(8)		/* 1.1 */
+#define SPDM_KEY_EX_CAP			BIT(9)		/* 1.1 */
+#define SPDM_PSK_CAP_MASK		GENMASK(11, 10)	/* 1.1 */
+#define   SPDM_PSK_CAP_NO		0		/* 1.1 */
+#define   SPDM_PSK_CAP_PSK		1		/* 1.1 */
+#define   SPDM_PSK_CAP_PSK_CTX		2		/* 1.1 resp only */
+#define SPDM_ENCAP_CAP			BIT(12)		/* 1.1 */
+#define SPDM_HBEAT_CAP			BIT(13)		/* 1.1 */
+#define SPDM_KEY_UPD_CAP		BIT(14)		/* 1.1 */
+#define SPDM_HANDSHAKE_ITC_CAP		BIT(15)		/* 1.1 */
+#define SPDM_PUB_KEY_ID_CAP		BIT(16)		/* 1.1 */
+#define SPDM_CHUNK_CAP			BIT(17)		/* 1.2 */
+#define SPDM_ALIAS_CERT_CAP		BIT(18)		/* 1.2 resp only */
+#define SPDM_SET_CERT_CAP		BIT(19)		/* 1.2 resp only */
+#define SPDM_CSR_CAP			BIT(20)		/* 1.2 resp only */
+#define SPDM_CERT_INST_RESET_CAP	BIT(21)		/* 1.2 resp only */
+#define SPDM_EP_INFO_CAP_MASK		GENMASK(23, 22) /* 1.3 */
+#define   SPDM_EP_INFO_CAP_NO		0		/* 1.3 */
+#define   SPDM_EP_INFO_CAP_RSP		1		/* 1.3 */
+#define   SPDM_EP_INFO_CAP_RSP_SIG	2		/* 1.3 */
+#define SPDM_MEL_CAP			BIT(24)		/* 1.3 resp only */
+#define SPDM_EVENT_CAP			BIT(25)		/* 1.3 */
+#define SPDM_MULTI_KEY_CAP_MASK		GENMASK(27, 26)	/* 1.3 */
+#define   SPDM_MULTI_KEY_CAP_NO		0		/* 1.3 */
+#define   SPDM_MULTI_KEY_CAP_ONLY	1		/* 1.3 */
+#define   SPDM_MULTI_KEY_CAP_SEL	2		/* 1.3 */
+#define SPDM_GET_KEY_PAIR_INFO_CAP	BIT(28)		/* 1.3 resp only */
+#define SPDM_SET_KEY_PAIR_INFO_CAP	BIT(29)		/* 1.3 resp only */
+
+/* SPDM capabilities supported by this implementation */
+#define SPDM_REQ_CAPS			(SPDM_CERT_CAP | SPDM_CHAL_CAP)
+
+/* SPDM capabilities required from responders */
+#define SPDM_RSP_MIN_CAPS		(SPDM_CERT_CAP | SPDM_CHAL_CAP)
+
+/*
+ * SPDM cryptographic timeout of this implementation:
+ * Assume calculations may take up to 1 sec on a busy machine, which equals
+ * roughly 1 << 20.  That's within the limits mandated for responders by CMA
+ * (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+ * Used in GET_CAPABILITIES exchange.
+ */
+#define SPDM_CTEXPONENT			20
+
+/* SPDM asymmetric key signature algorithms (SPDM 1.0.0 table 13) */
+#define SPDM_ASYM_RSASSA_2048		BIT(0)		/* 1.0 */
+#define SPDM_ASYM_RSAPSS_2048		BIT(1)		/* 1.0 */
+#define SPDM_ASYM_RSASSA_3072		BIT(2)		/* 1.0 */
+#define SPDM_ASYM_RSAPSS_3072		BIT(3)		/* 1.0 */
+#define SPDM_ASYM_ECDSA_ECC_NIST_P256	BIT(4)		/* 1.0 */
+#define SPDM_ASYM_RSASSA_4096		BIT(5)		/* 1.0 */
+#define SPDM_ASYM_RSAPSS_4096		BIT(6)		/* 1.0 */
+#define SPDM_ASYM_ECDSA_ECC_NIST_P384	BIT(7)		/* 1.0 */
+#define SPDM_ASYM_ECDSA_ECC_NIST_P521	BIT(8)		/* 1.0 */
+#define SPDM_ASYM_SM2_ECC_SM2_P256	BIT(9)		/* 1.2 */
+#define SPDM_ASYM_EDDSA_ED25519		BIT(10)		/* 1.2 */
+#define SPDM_ASYM_EDDSA_ED448		BIT(11)		/* 1.2 */
+
+/* SPDM hash algorithms (SPDM 1.0.0 table 13) */
+#define SPDM_HASH_SHA_256		BIT(0)		/* 1.0 */
+#define SPDM_HASH_SHA_384		BIT(1)		/* 1.0 */
+#define SPDM_HASH_SHA_512		BIT(2)		/* 1.0 */
+#define SPDM_HASH_SHA3_256		BIT(3)		/* 1.0 */
+#define SPDM_HASH_SHA3_384		BIT(4)		/* 1.0 */
+#define SPDM_HASH_SHA3_512		BIT(5)		/* 1.0 */
+#define SPDM_HASH_SM3_256		BIT(6)		/* 1.2 */
+
+#if IS_ENABLED(CONFIG_CRYPTO_RSA)
+#define SPDM_ASYM_RSA			SPDM_ASYM_RSASSA_2048 |		\
+					SPDM_ASYM_RSASSA_3072 |		\
+					SPDM_ASYM_RSASSA_4096
+#else
+#define SPDM_ASYM_RSA			0
+#endif
+
+#if IS_ENABLED(CONFIG_CRYPTO_ECDSA)
+#define SPDM_ASYM_ECDSA			SPDM_ASYM_ECDSA_ECC_NIST_P256 |	\
+					SPDM_ASYM_ECDSA_ECC_NIST_P384 | \
+					SPDM_ASYM_ECDSA_ECC_NIST_P521
+#else
+#define SPDM_ASYM_ECDSA			0
+#endif
+
+#if IS_ENABLED(CONFIG_CRYPTO_SHA256)
+#define SPDM_HASH_SHA2_256		SPDM_HASH_SHA_256
+#else
+#define SPDM_HASH_SHA2_256		0
+#endif
+
+#if IS_ENABLED(CONFIG_CRYPTO_SHA512)
+#define SPDM_HASH_SHA2_384_512		SPDM_HASH_SHA_384 |		\
+					SPDM_HASH_SHA_512
+#else
+#define SPDM_HASH_SHA2_384_512		0
+#endif
+
+/* SPDM algorithms supported by this implementation */
+#define SPDM_ASYM_ALGOS		       (SPDM_ASYM_RSA |			\
+					SPDM_ASYM_ECDSA)
+
+#define SPDM_HASH_ALGOS		       (SPDM_HASH_SHA2_256 |		\
+					SPDM_HASH_SHA2_384_512)
+
+/*
+ * Common header shared by all messages.
+ * Note that the meaning of param1 and param2 is message dependent.
+ */
+struct spdm_header {
+	u8 version;
+	u8 code;  /* RequestResponseCode */
+	u8 param1;
+	u8 param2;
+} __packed;
+
+#define SPDM_REQ	 0x80
+#define SPDM_GET_VERSION 0x84
+
+struct spdm_get_version_req {
+	u8 version;
+	u8 code;
+	u8 param1;
+	u8 param2;
+} __packed;
+
+struct spdm_get_version_rsp {
+	u8 version;
+	u8 code;
+	u8 param1;
+	u8 param2;
+
+	u8 reserved;
+	u8 version_number_entry_count;
+	__le16 version_number_entries[] __counted_by(version_number_entry_count);
+} __packed;
+
+#define SPDM_GET_CAPABILITIES 0xe1
+#define SPDM_MIN_DATA_TRANSFER_SIZE 42 /* SPDM 1.2.0 margin no 226 */
+
+/*
+ * Newer SPDM versions insert fields at the end of messages (enlarging them)
+ * or use reserved space for new fields (leaving message size unchanged).
+ */
+struct spdm_get_capabilities_req {
+	u8 version;
+	u8 code;
+	u8 param1;
+	u8 param2;
+	/* End of SPDM 1.0 structure */
+
+	u8 reserved1;					/* 1.1 */
+	u8 ctexponent;					/* 1.1 */
+	u16 reserved2;					/* 1.1 */
+	__le32 flags;					/* 1.1 */
+	/* End of SPDM 1.1 structure */
+
+	__le32 data_transfer_size;			/* 1.2 */
+	__le32 max_spdm_msg_size;			/* 1.2 */
+} __packed;
+
+struct spdm_get_capabilities_rsp {
+	u8 version;
+	u8 code;
+	u8 param1;
+	u8 param2;
+
+	u8 reserved1;
+	u8 ctexponent;
+	u16 reserved2;
+	__le32 flags;
+	/* End of SPDM 1.0 structure */
+
+	__le32 data_transfer_size;			/* 1.2 */
+	__le32 max_spdm_msg_size;			/* 1.2 */
+	/* End of SPDM 1.2 structure */
+
+	/*
+	 * Additional optional fields at end of this structure:
+	 * - SupportedAlgorithms: variable size		 * 1.3 *
+	 */
+} __packed;
+
+#define SPDM_NEGOTIATE_ALGS 0xe3
+
+struct spdm_negotiate_algs_req {
+	u8 version;
+	u8 code;
+	u8 param1; /* Number of ReqAlgStruct entries at end */
+	u8 param2;
+
+	__le16 length;
+	u8 measurement_specification;
+	u8 other_params_support;			/* 1.2 */
+
+	__le32 base_asym_algo;
+	__le32 base_hash_algo;
+
+	u8 reserved1[12];
+	u8 ext_asym_count;
+	u8 ext_hash_count;
+	u8 reserved2;
+	u8 mel_specification;				/* 1.3 */
+
+	/*
+	 * Additional optional fields at end of this structure:
+	 * - ExtAsym: 4 bytes * ext_asym_count
+	 * - ExtHash: 4 bytes * ext_hash_count
+	 * - ReqAlgStruct: variable size * param1	 * 1.1 *
+	 */
+} __packed;
+
+struct spdm_negotiate_algs_rsp {
+	u8 version;
+	u8 code;
+	u8 param1; /* Number of RespAlgStruct entries at end */
+	u8 param2;
+
+	__le16 length;
+	u8 measurement_specification_sel;
+	u8 other_params_sel;				/* 1.2 */
+
+	__le32 measurement_hash_algo;
+	__le32 base_asym_sel;
+	__le32 base_hash_sel;
+
+	u8 reserved1[11];
+	u8 mel_specification_sel;			/* 1.3 */
+	u8 ext_asym_sel_count; /* Either 0 or 1 */
+	u8 ext_hash_sel_count; /* Either 0 or 1 */
+	u8 reserved2[2];
+
+	/*
+	 * Additional optional fields at end of this structure:
+	 * - ExtAsym: 4 bytes * ext_asym_count
+	 * - ExtHash: 4 bytes * ext_hash_count
+	 * - RespAlgStruct: variable size * param1	 * 1.1 *
+	 */
+} __packed;
+
+/* Maximum number of ReqAlgStructs sent by this implementation */
+#define SPDM_MAX_REQ_ALG_STRUCT 0
+
+struct spdm_req_alg_struct {
+	u8 alg_type;
+	u8 alg_count; /* 0x2K where K is number of alg_external entries */
+	__le16 alg_supported; /* Size is in alg_count[7:4], always 2 */
+	__le32 alg_external[];
+} __packed;
+
+#define SPDM_GET_DIGESTS 0x81
+
+struct spdm_get_digests_req {
+	u8 version;
+	u8 code;
+	u8 param1; /* Reserved */
+	u8 param2; /* Reserved */
+} __packed;
+
+struct spdm_get_digests_rsp {
+	u8 version;
+	u8 code;
+	u8 param1; /* SupportedSlotMask */		/* 1.3 */
+	u8 param2; /* ProvisionedSlotMask */
+	u8 digests[]; /* Hash of struct spdm_cert_chain for each slot */
+	/* End of SPDM 1.2 (and earlier) structure */
+
+	/*
+	 * Additional optional fields at end of this structure:
+	 * (omitted as long as we do not advertise MULTI_KEY_CAP)
+	 * - KeyPairID: 1 byte for each slot		 * 1.3 *
+	 * - CertificateInfo: 1 byte for each slot	 * 1.3 *
+	 * - KeyUsageMask: 2 bytes for each slot	 * 1.3 *
+	 */
+} __packed;
+
+#define SPDM_GET_CERTIFICATE 0x82
+#define SPDM_SLOTS 8 /* SPDM 1.0.0 section 4.9.2.1 */
+
+struct spdm_get_certificate_req {
+	u8 version;
+	u8 code;
+	u8 param1; /* Slot number 0..7 */
+	u8 param2; /* SlotSizeRequested */		/* 1.3 */
+	__le16 offset;
+	__le16 length;
+} __packed;
+
+struct spdm_get_certificate_rsp {
+	u8 version;
+	u8 code;
+	u8 param1; /* Slot number 0..7 */
+	u8 param2; /* CertificateInfo */		/* 1.3 */
+	__le16 portion_length;
+	__le16 remainder_length;
+	u8 cert_chain[]; /* PortionLength long */
+} __packed;
+
+struct spdm_cert_chain {
+	__le16 length;
+	u8 reserved[2];
+	/*
+	 * Additional fields at end of this structure:
+	 * - RootHash: Digest of Root Certificate
+	 * - Certificates: Chain of ASN.1 DER-encoded X.509 v3 certificates
+	 */
+} __packed;
+
+#define SPDM_CHALLENGE 0x83
+#define SPDM_NONCE_SZ 32 /* SPDM 1.0.0 table 20 */
+#define SPDM_PREFIX_SZ 64 /* SPDM 1.2.0 margin no 803 */
+#define SPDM_COMBINED_PREFIX_SZ 100 /* SPDM 1.2.0 margin no 806 */
+#define SPDM_MAX_OPAQUE_DATA 1024 /* SPDM 1.0.0 table 21 */
+
+struct spdm_challenge_req {
+	u8 version;
+	u8 code;
+	u8 param1; /* Slot number 0..7 */
+	u8 param2; /* MeasurementSummaryHash type */
+	u8 nonce[SPDM_NONCE_SZ];
+	/* End of SPDM 1.2 (and earlier) structure */
+
+	u8 context[8];					/* 1.3 */
+} __packed;
+
+struct spdm_challenge_rsp {
+	u8 version;
+	u8 code;
+	u8 param1; /* Slot number 0..7 */
+	u8 param2; /* Slot mask */
+	/*
+	 * Additional fields at end of this structure:
+	 * - CertChainHash: Hash of struct spdm_cert_chain for selected slot
+	 * - Nonce: 32 bytes long
+	 * - MeasurementSummaryHash: Optional hash of selected measurements
+	 * - OpaqueDataLength: 2 bytes long
+	 * - OpaqueData: Up to 1024 bytes long
+	 * - RequesterContext: 8 bytes long		 * 1.3 *
+	 *   (inserted, moves Signature field)
+	 * - Signature
+	 */
+} __packed;
+
+#define SPDM_ERROR 0x7f
+
+enum spdm_error_code {
+	SPDM_INVALID_REQUEST		= 0x01,		/* 1.0 */
+	SPDM_INVALID_SESSION		= 0x02,		/* 1.1 only */
+	SPDM_BUSY			= 0x03,		/* 1.0 */
+	SPDM_UNEXPECTED_REQUEST		= 0x04,		/* 1.0 */
+	SPDM_UNSPECIFIED		= 0x05,		/* 1.0 */
+	SPDM_DECRYPT_ERROR		= 0x06,		/* 1.1 */
+	SPDM_UNSUPPORTED_REQUEST	= 0x07,		/* 1.0 */
+	SPDM_REQUEST_IN_FLIGHT		= 0x08,		/* 1.1 */
+	SPDM_INVALID_RESPONSE_CODE	= 0x09,		/* 1.1 */
+	SPDM_SESSION_LIMIT_EXCEEDED	= 0x0a,		/* 1.1 */
+	SPDM_SESSION_REQUIRED		= 0x0b,		/* 1.2 */
+	SPDM_RESET_REQUIRED		= 0x0c,		/* 1.2 */
+	SPDM_RESPONSE_TOO_LARGE		= 0x0d,		/* 1.2 */
+	SPDM_REQUEST_TOO_LARGE		= 0x0e,		/* 1.2 */
+	SPDM_LARGE_RESPONSE		= 0x0f,		/* 1.2 */
+	SPDM_MESSAGE_LOST		= 0x10,		/* 1.2 */
+	SPDM_INVALID_POLICY		= 0x11,		/* 1.3 */
+	SPDM_VERSION_MISMATCH		= 0x41,		/* 1.0 */
+	SPDM_RESPONSE_NOT_READY		= 0x42,		/* 1.0 */
+	SPDM_REQUEST_RESYNCH		= 0x43,		/* 1.0 */
+	SPDM_OPERATION_FAILED		= 0x44,		/* 1.3 */
+	SPDM_NO_PENDING_REQUESTS	= 0x45,		/* 1.3 */
+	SPDM_VENDOR_DEFINED_ERROR	= 0xff,		/* 1.0 */
+};
+
+struct spdm_error_rsp {
+	u8 version;
+	u8 code;
+	enum spdm_error_code error_code:8;
+	u8 error_data;
+
+	u8 extended_error_data[];
+} __packed;
+
+/**
+ * struct spdm_state - SPDM session state
+ *
+ * @dev: Responder device.  Used for error reporting and passed to @transport.
+ * @lock: Serializes multiple concurrent spdm_authenticate() calls.
+ * @authenticated: Whether device was authenticated successfully.
+ * @dev: Responder device.  Used for error reporting and passed to @transport.
+ * @transport: Transport function to perform one message exchange.
+ * @transport_priv: Transport private data.
+ * @transport_sz: Maximum message size the transport is capable of (in bytes).
+ *	Used as DataTransferSize in GET_CAPABILITIES exchange.
+ * @version: Maximum common supported version of requester and responder.
+ *	Negotiated during GET_VERSION exchange.
+ * @rsp_caps: Cached capabilities of responder.
+ *	Received during GET_CAPABILITIES exchange.
+ * @base_asym_alg: Asymmetric key algorithm for signature verification of
+ *	CHALLENGE_AUTH messages.
+ *	Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+ * @base_hash_alg: Hash algorithm for signature verification of
+ *	CHALLENGE_AUTH messages.
+ *	Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+ * @provisioned_slots: Bitmask of responder's provisioned certificate slots.
+ *	Received during GET_DIGESTS exchange.
+ * @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
+ *	Passed to crypto subsystem when calling verify_signature().
+ * @sig_len: Signature length of @base_asym_alg (in bytes).
+ *	S or SigLen in SPDM specification.
+ * @base_hash_alg_name: Human-readable name of @base_hash_alg.
+ *	Passed to crypto subsystem when calling crypto_alloc_shash() and
+ *	verify_signature().
+ * @shash: Synchronous hash handle for @base_hash_alg computation.
+ * @desc: Synchronous hash context for @base_hash_alg computation.
+ * @hash_len: Hash length of @base_hash_alg (in bytes).
+ *	H in SPDM specification.
+ * @slot: Certificate chain in each of the 8 slots.  NULL pointer if a slot is
+ *	not populated.  Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
+ * @slot_sz: Certificate chain size (in bytes).
+ * @leaf_key: Public key portion of leaf certificate against which to check
+ *	responder's signatures.
+ * @root_keyring: Keyring against which to check the first certificate in
+ *	responder's certificate chain.
+ * @transcript: Concatenation of all SPDM messages exchanged during an
+ *	authentication sequence.  Used to verify the signature, as it is
+ *	computed over the hashed transcript.
+ * @transcript_end: Pointer into the @transcript buffer.  Marks the current
+ *	end of transcript.  If another message is transmitted, it is appended
+ *	at this position.
+ * @transcript_max: Allocation size of @transcript.  Multiple of PAGE_SIZE.
+ */
+struct spdm_state {
+	struct device *dev;
+	struct mutex lock;
+	unsigned int authenticated:1;
+
+	/* Transport */
+	spdm_transport *transport;
+	void *transport_priv;
+	u32 transport_sz;
+
+	/* Negotiated state */
+	u8 version;
+	u32 rsp_caps;
+	u32 base_asym_alg;
+	u32 base_hash_alg;
+	unsigned long provisioned_slots;
+
+	/* Signature algorithm */
+	const char *base_asym_enc;
+	size_t sig_len;
+
+	/* Hash algorithm */
+	const char *base_hash_alg_name;
+	struct crypto_shash *shash;
+	struct shash_desc *desc;
+	size_t hash_len;
+
+	/* Certificates */
+	struct spdm_cert_chain *slot[SPDM_SLOTS];
+	size_t slot_sz[SPDM_SLOTS];
+	struct public_key *leaf_key;
+	struct key *root_keyring;
+
+	/* Transcript */
+	void *transcript;
+	void *transcript_end;
+	size_t transcript_max;
+};
+
+ssize_t spdm_exchange(struct spdm_state *spdm_state,
+		      void *req, size_t req_sz, void *rsp, size_t rsp_sz);
+
+int spdm_alloc_transcript(struct spdm_state *spdm_state);
+void spdm_free_transcript(struct spdm_state *spdm_state);
+int spdm_append_transcript(struct spdm_state *spdm_state,
+			   const void *msg, size_t msg_sz);
+
+void spdm_create_combined_prefix(u8 version, const char *spdm_context,
+				 void *buf);
+int spdm_verify_signature(struct spdm_state *spdm_state,
+			  const char *spdm_context);
+
+void spdm_reset(struct spdm_state *spdm_state);
+
+#endif /* _LIB_SPDM_H_ */