@@ -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>
new file mode 100644
@@ -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
@@ -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.
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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");
new file mode 100644
@@ -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);
new file mode 100644
@@ -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_ */