@@ -35,7 +35,9 @@ obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_PCI_DOE) += doe.o
obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
-obj-$(CONFIG_PCI_CMA) += cma.o
+obj-$(CONFIG_PCI_CMA) += cma.o cma.asn1.o
+$(obj)/cma.o: $(obj)/cma.asn1.h
+$(obj)/cma.asn1.o: $(obj)/cma.asn1.c $(obj)/cma.asn1.h
# Endpoint library must be initialized before its users
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
new file mode 100644
@@ -0,0 +1,41 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Component Measurement and Authentication (CMA-SPDM, PCIe r6.1 sec 6.31.3)
+-- X.509 Subject Alternative Name (RFC 5280 sec 4.2.1.6)
+--
+-- Copyright (C) 2008 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.6
+--
+-- The ASN.1 module in RFC 5280 appendix A.1 uses EXPLICIT TAGS whereas the one
+-- in appendix A.2 uses IMPLICIT TAGS. The kernel's simplified asn1_compiler.c
+-- always uses EXPLICIT TAGS, hence this ASN.1 module differs from RFC 5280 in
+-- that it adds IMPLICIT to definitions from appendix A.2 (such as GeneralName)
+-- and omits EXPLICIT in those definitions.
+
+SubjectAltName ::= GeneralNames
+
+GeneralNames ::= SEQUENCE OF GeneralName
+
+GeneralName ::= CHOICE {
+ otherName [0] IMPLICIT OtherName,
+ rfc822Name [1] IMPLICIT IA5String,
+ dNSName [2] IMPLICIT IA5String,
+ x400Address [3] ANY,
+ directoryName [4] ANY,
+ ediPartyName [5] IMPLICIT EDIPartyName,
+ uniformResourceIdentifier [6] IMPLICIT IA5String,
+ iPAddress [7] IMPLICIT OCTET STRING,
+ registeredID [8] IMPLICIT OBJECT IDENTIFIER
+ }
+
+OtherName ::= SEQUENCE {
+ type-id OBJECT IDENTIFIER ({ pci_cma_note_oid }),
+ value [0] ANY ({ pci_cma_note_san })
+ }
+
+EDIPartyName ::= SEQUENCE {
+ nameAssigner [0] ANY OPTIONAL,
+ partyName [1] ANY
+ }
@@ -10,16 +10,137 @@
#define dev_fmt(fmt) "CMA: " fmt
+#include <keys/x509-parser.h>
+#include <linux/asn1_decoder.h>
+#include <linux/oid_registry.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/pm_runtime.h>
#include <linux/spdm.h>
+#include "cma.asn1.h"
#include "pci.h"
/* Keyring that userspace can poke certs into */
static struct key *pci_cma_keyring;
+/*
+ * The spdm_requester.c library calls pci_cma_validate() to check requirements
+ * for Leaf Certificates per PCIe r6.1 sec 6.31.3.
+ *
+ * pci_cma_validate() parses the Subject Alternative Name using the ASN.1
+ * module cma.asn1, which calls pci_cma_note_oid() and pci_cma_note_san()
+ * to compare an OtherName against the expected name.
+ *
+ * The expected name is constructed beforehand by pci_cma_construct_san().
+ *
+ * PCIe r6.2 drops the Subject Alternative Name spec language, even though
+ * it continues to require "the leaf certificate to include the information
+ * typically used by system software for device driver binding". Use the
+ * Subject Alternative Name per PCIe r6.1 for lack of a replacement and
+ * because it is the de facto standard among existing products.
+ */
+#define CMA_NAME_MAX sizeof("Vendor=1234:Device=1234:CC=123456:" \
+ "REV=12:SSVID=1234:SSID=1234:1234567890123456")
+
+struct pci_cma_x509_context {
+ struct pci_dev *pdev;
+ u8 slot;
+ enum OID last_oid;
+ char expected_name[CMA_NAME_MAX];
+ unsigned int expected_len;
+ unsigned int found:1;
+};
+
+int pci_cma_note_oid(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pci_cma_x509_context *ctx = context;
+
+ ctx->last_oid = look_up_OID(value, vlen);
+
+ return 0;
+}
+
+int pci_cma_note_san(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pci_cma_x509_context *ctx = context;
+
+ /* These aren't the drOIDs we're looking for. */
+ if (ctx->last_oid != OID_CMA)
+ return 0;
+
+ if (tag != ASN1_UTF8STR ||
+ vlen != ctx->expected_len ||
+ memcmp(value, ctx->expected_name, vlen) != 0) {
+ pci_err(ctx->pdev, "Leaf certificate of slot %u "
+ "has invalid Subject Alternative Name\n", ctx->slot);
+ return -EINVAL;
+ }
+
+ ctx->found = true;
+
+ return 0;
+}
+
+static unsigned int pci_cma_construct_san(struct pci_dev *pdev, char *name)
+{
+ unsigned int len;
+ u64 serial;
+
+ len = snprintf(name, CMA_NAME_MAX,
+ "Vendor=%04hx:Device=%04hx:CC=%06x:REV=%02hhx",
+ pdev->vendor, pdev->device, pdev->class, pdev->revision);
+
+ if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL)
+ len += snprintf(name + len, CMA_NAME_MAX - len,
+ ":SSVID=%04hx:SSID=%04hx",
+ pdev->subsystem_vendor, pdev->subsystem_device);
+
+ serial = pci_get_dsn(pdev);
+ if (serial)
+ len += snprintf(name + len, CMA_NAME_MAX - len,
+ ":%016llx", serial);
+
+ return len;
+}
+
+static int pci_cma_validate(struct device *dev, u8 slot,
+ struct x509_certificate *leaf_cert)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct pci_cma_x509_context ctx;
+ int ret;
+
+ if (!leaf_cert->raw_san) {
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has no Subject Alternative Name\n", slot);
+ return -EINVAL;
+ }
+
+ ctx.pdev = pdev;
+ ctx.slot = slot;
+ ctx.found = false;
+ ctx.expected_len = pci_cma_construct_san(pdev, ctx.expected_name);
+
+ ret = asn1_ber_decoder(&cma_decoder, &ctx, leaf_cert->raw_san,
+ leaf_cert->raw_san_size);
+ if (ret == -EBADMSG || ret == -EMSGSIZE)
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has malformed Subject Alternative Name\n", slot);
+ if (ret < 0)
+ return ret;
+
+ if (!ctx.found) {
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has no OtherName with CMA OID\n", slot);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
#define PCI_DOE_FEATURE_CMA 1
static ssize_t pci_doe_transport(void *priv, struct device *dev,
@@ -62,7 +183,8 @@ void pci_cma_init(struct pci_dev *pdev)
return;
pdev->spdm_state = spdm_create(&pdev->dev, pci_doe_transport, doe,
- PCI_DOE_MAX_PAYLOAD, pci_cma_keyring);
+ PCI_DOE_MAX_PAYLOAD, pci_cma_keyring,
+ pci_cma_validate);
if (!pdev->spdm_state)
return;
@@ -145,6 +145,9 @@ enum OID {
OID_id_rsassa_pkcs1_v1_5_with_sha3_384, /* 2.16.840.1.101.3.4.3.15 */
OID_id_rsassa_pkcs1_v1_5_with_sha3_512, /* 2.16.840.1.101.3.4.3.16 */
+ /* PCI */
+ OID_CMA, /* 2.23.147 */
+
OID__NR
};
@@ -17,14 +17,18 @@
struct key;
struct device;
struct spdm_state;
+struct x509_certificate;
typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
const void *request, size_t request_sz,
void *response, size_t response_sz);
+typedef int (spdm_validate)(struct device *dev, u8 slot,
+ struct x509_certificate *leaf_cert);
+
struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
void *transport_priv, u32 transport_sz,
- struct key *keyring);
+ struct key *keyring, spdm_validate *validate);
int spdm_authenticate(struct spdm_state *spdm_state);
@@ -380,12 +380,14 @@ void spdm_reset(struct spdm_state *spdm_state)
* @transport_priv: Transport private data
* @transport_sz: Maximum message size the transport is capable of (in bytes)
* @keyring: Trusted root certificates
+ * @validate: Function to validate additional leaf certificate requirements
+ * (optional, may be %NULL)
*
* 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 key *keyring, spdm_validate *validate)
{
struct spdm_state *spdm_state = kzalloc(sizeof(*spdm_state), GFP_KERNEL);
@@ -397,6 +399,7 @@ struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
spdm_state->transport_priv = transport_priv;
spdm_state->transport_sz = transport_sz;
spdm_state->root_keyring = keyring;
+ spdm_state->validate = validate;
mutex_init(&spdm_state->lock);
@@ -537,6 +537,12 @@ static int spdm_validate_cert_chain(struct spdm_state *spdm_state, u8 slot)
offset += length;
} while (offset < total_length);
+ if (spdm_state->validate) {
+ rc = spdm_state->validate(spdm_state->dev, slot, prev);
+ if (rc)
+ return rc;
+ }
+
/* Steal pub pointer ahead of x509_free_certificate() */
spdm_state->leaf_key = prev->pub;
prev->pub = NULL;
@@ -455,6 +455,7 @@ struct spdm_error_rsp {
* responder's signatures.
* @root_keyring: Keyring against which to check the first certificate in
* responder's certificate chain.
+ * @validate: Function to validate additional leaf certificate requirements.
* @transcript: Concatenation of all SPDM messages exchanged during an
* authentication sequence. Used to verify the signature, as it is
* computed over the hashed transcript.
@@ -495,6 +496,7 @@ struct spdm_state {
size_t slot_sz[SPDM_SLOTS];
struct public_key *leaf_key;
struct key *root_keyring;
+ spdm_validate *validate;
/* Transcript */
void *transcript;