Add support in the intel_sdsi tool to perform SPDM GET_DIGESTS and GET_CERTIFICATE commands. Output is sent to stdout. Example reading the certificate chain from socket 0: intel_sdsi -d 1 -attest get_certificate | openssl x509 -inform DER -nout -text Signed-off-by: David E. Box <david.e.box@xxxxxxxxxxxxxxx> --- V3 - No change V2 - Remove unnecessary struct packing - Remove newline from perror() - Add message options in --help output - Use new SDSI_SPDM_BUF_SIZE from uapi header - In spdm_get_certificate: - Initialize remainder length to the minimum of the actual size or the maximum buffer size. - Add old_remainder to test that the remaining certificate length is less than the previous length tools/arch/x86/intel_sdsi/Makefile | 11 +- tools/arch/x86/intel_sdsi/intel_sdsi.c | 72 +++- tools/arch/x86/intel_sdsi/spdm.c | 476 +++++++++++++++++++++++++ tools/arch/x86/intel_sdsi/spdm.h | 13 + 4 files changed, 567 insertions(+), 5 deletions(-) create mode 100644 tools/arch/x86/intel_sdsi/spdm.c create mode 100644 tools/arch/x86/intel_sdsi/spdm.h diff --git a/tools/arch/x86/intel_sdsi/Makefile b/tools/arch/x86/intel_sdsi/Makefile index 47b6fd98372c..0d59a0299bc7 100644 --- a/tools/arch/x86/intel_sdsi/Makefile +++ b/tools/arch/x86/intel_sdsi/Makefile @@ -3,20 +3,23 @@ include ../../../scripts/Makefile.include BINDIR ?= /usr/sbin -SRCS = intel_sdsi.c +SRCS = intel_sdsi.c spdm.c OBJS = $(SRCS:.c=.o) -override CFLAGS += -O2 -Wall -Wextra +override CFLAGS += -O2 -Wall -Wextra -I../../../include -intel_sdsi: $(OBJS) +intel_sdsi: intel_sdsi.h $(OBJS) $(CC) $(CFLAGS) $(OBJS) -o $@ +intel_sdsi.h: ../../../../include/uapi/linux/intel_sdsi.h + ln -sf ../../../../include/uapi/linux/intel_sdsi.h $@ + %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean : - @rm -f intel_sdsi $(OBJS) + @rm -f intel_sdsi intel_sdsi.h $(OBJS) install : intel_sdsi install -d $(DESTDIR)$(BINDIR) diff --git a/tools/arch/x86/intel_sdsi/intel_sdsi.c b/tools/arch/x86/intel_sdsi/intel_sdsi.c index 766a5d26f534..b3c13e4696f4 100644 --- a/tools/arch/x86/intel_sdsi/intel_sdsi.c +++ b/tools/arch/x86/intel_sdsi/intel_sdsi.c @@ -22,6 +22,9 @@ #include <sys/types.h> +#include "spdm.h" +#include "intel_sdsi.h" + #ifndef __packed #define __packed __attribute__((packed)) #endif @@ -179,6 +182,7 @@ struct sdsi_dev { struct state_certificate sc; char *dev_name; char *dev_path; + int dev_no; uint32_t guid; }; @@ -189,6 +193,12 @@ enum command { CMD_STATE_CERT, CMD_PROV_AKC, CMD_PROV_CAP, + CMD_ATTESTATION, +}; + +enum spdm_message { + GET_DIGESTS, + GET_CERTIFICATE, }; static void sdsi_list_devices(void) @@ -647,6 +657,41 @@ static int sdsi_provision_cap(struct sdsi_dev *s, char *bin_file) return sdsi_provision(s, bin_file, CMD_PROV_CAP); } +static int sdsi_attestation(struct sdsi_dev *s, enum spdm_message message) +{ + struct cert_chain c; + uint8_t digest[TPM_ALG_SHA_384_SIZE]; + size_t size; + int ret, i; + + switch (message) { + case GET_CERTIFICATE: + ret = spdm_get_certificate(s->dev_no, &c); + if (ret) + return ret; + + size = fwrite(c.chain, sizeof(uint8_t), c.len, stdout); + if (size != c.len) { + fprintf(stderr, "Unable to write complete certificate chain\n"); + ret = -1; + } + + free(c.chain); + break; + case GET_DIGESTS: + ret = spdm_get_digests(s->dev_no, digest); + if (ret) + return ret; + + for (i = 0; i < TPM_ALG_SHA_384_SIZE; i++) + printf("%02x", digest[i]); + printf("\n"); + break; + } + + return ret; +} + static int read_sysfs_data(const char *file, int *value) { char buff[16]; @@ -728,6 +773,7 @@ static struct sdsi_dev *sdsi_create_dev(char *dev_no) } s->guid = guid; + s->dev_no = atoi(dev_no); return s; } @@ -742,6 +788,7 @@ static void sdsi_free_dev(struct sdsi_dev *s) static void usage(char *prog) { printf("Usage: %s [-l] [-d DEVNO [-i] [-s] [-m | -C] [-a FILE] [-c FILE]\n", prog); + printf(" [-attest MESSAGE]\n"); } static void show_help(void) @@ -754,12 +801,17 @@ static void show_help(void) printf(" %-18s\t%s\n", "-m, --meter", "show meter certificate data"); printf(" %-18s\t%s\n", "-C, --meter_current", "show live unattested meter data"); printf(" %-18s\t%s\n", "-a, --akc FILE", "provision socket with AKC FILE"); - printf(" %-18s\t%s\n", "-c, --cap FILE>", "provision socket with CAP FILE"); + printf(" %-18s\t%s\n", "-c, --cap FILE", "provision socket with CAP FILE"); + printf(" %-18s\t%s\n", "--attest MESSAGE", "send attestion MESSAGE. Valid"); + printf(" %-18s\t%s\n", "", "messages are:"); + printf(" %-18s\t%s\n", "", " get_digests"); + printf(" %-18s\t%s\n", "", " get_certificate"); } int main(int argc, char *argv[]) { char bin_file[PATH_MAX], *dev_no = NULL; + enum spdm_message message = GET_DIGESTS; bool device_selected = false; char *progname; enum command command = -1; @@ -769,6 +821,7 @@ int main(int argc, char *argv[]) static struct option long_options[] = { {"akc", required_argument, 0, 'a'}, + {"attest", required_argument, 0, 0}, {"cap", required_argument, 0, 'c'}, {"devno", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, @@ -820,6 +873,20 @@ int main(int argc, char *argv[]) command = (opt == 'a') ? CMD_PROV_AKC : CMD_PROV_CAP; break; + case 0: + if (strcmp(long_options[option_index].name, "attest") == 0) { + command = CMD_ATTESTATION; + + if (strcmp(optarg, "get_digests") == 0) + message = GET_DIGESTS; + else if (strcmp(optarg, "get_certificate") == 0) + message = GET_CERTIFICATE; + else { + fprintf(stderr, "Unrecognized attestation command\n"); + return -1; + } + } + break; case 'h': usage(progname); show_help(); @@ -854,6 +921,9 @@ int main(int argc, char *argv[]) case CMD_PROV_CAP: ret = sdsi_provision_cap(s, bin_file); break; + case CMD_ATTESTATION: + ret = sdsi_attestation(s, message); + break; default: fprintf(stderr, "No command specified\n"); return -1; diff --git a/tools/arch/x86/intel_sdsi/spdm.c b/tools/arch/x86/intel_sdsi/spdm.c new file mode 100644 index 000000000000..71ac1e91a6d8 --- /dev/null +++ b/tools/arch/x86/intel_sdsi/spdm.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * spdm: Lightweight Security Protocol and Data Model (SPDM) specification + * support code for performing attestation commands using the Intel On + * Demand driver ioctl interface. Intel On Demand currently supports + * SPDM version 1.0 + * + * See the SPDM v1.0 specification at: + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.0.1.pdf + * + * Copyright (C) 2024 Intel Corporation. All rights reserved. + */ + +#include<linux/bits.h> + +#include<fcntl.h> +#include<stdio.h> +#include<stdlib.h> +#include<stdint.h> +#include<string.h> +#include<unistd.h> +#include<sys/ioctl.h> +#include "spdm.h" +#include "intel_sdsi.h" + +// SPDM constants +#define SPDM_VERSION 0x10 +#define SPDM_REQUEST 0x80 +#define SPDM_ERROR 0x7f + +// SPDM request codes +#define SPDM_GET_VERSION 0x84 +#define SPDM_GET_CAPABILITIES 0xE1 +#define SPDM_NEGOTIATE_ALGORITHMS 0xE3 +#define SPDM_GET_DIGESTS 0x81 +#define SPDM_GET_CERTIFICATE 0x82 +#define SPDM_CHALLENGE 0x83 +#define SPDM_GET_MEASUREMENTS 0xE0 + +#define SDSI_DEV_PATH "/dev/isdsi" + +#define SPDM_RSVD 0 + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct spdm_header { + uint8_t version; + uint8_t code; + uint8_t param1; + uint8_t param2; +}; + +static int error_response(struct spdm_header *response) +{ + if (response->code != SPDM_ERROR) + fprintf(stderr, "ERROR: Unrecognized SPDM response\n"); + + switch (response->param1) { + case 0x00: + case 0x02: + case 0x06: + case 0x08 ... 0x40: + case 0x44 ... 0xfe: + fprintf(stderr, "SPDM RSP ERROR: Reserved.\n"); + break; + case 0x01: + fprintf(stderr, "SPDM RSP ERROR: One or more request fields are invalid.\n"); + break; + case 0x03: + fprintf(stderr, "SPDM RSP ERROR: The Responder received the request message\n"); + fprintf(stderr, "and the Responder decided to ignore the request message\n"); + fprintf(stderr, "but the Responder may be able to process the request message\n"); + fprintf(stderr, "if the request message is sent again in the future.\n"); + break; + case 0x04: + fprintf(stderr, "SPDM RSP ERROR: The Responder received an unexpected request\n"); + fprintf(stderr, "message. For example, CHALLENGE before NEGOTIATE_ALGORITHMS.\n"); + break; + case 0x05: + fprintf(stderr, "SPDM RSP ERROR: Unspecified error occurred.\n"); + break; + case 0x07: + fprintf(stderr, "SPDM RSP ERROR: The RequestResponseCode in the request\n"); + fprintf(stderr, "message is unsupported.\n"); + break; + case 0x41: + fprintf(stderr, "SPDM RSP ERROR: Requested SPDM Major Version is not\n"); + fprintf(stderr, "supported.\n"); + break; + case 0x42: + fprintf(stderr, "SPDM RSP ERROR: See the RESPONSE_IF_READY request message.\n"); + break; + case 0x43: + fprintf(stderr, "SPDM RSP ERROR: Responder is requesting Requester to reissue\n"); + fprintf(stderr, "GET_VERSION to resynchronize.\n"); + break; + case 0xFF: + fprintf(stderr, "SPDM RSP ERROR: Vendor or Other Standards defined.\n"); + break; + } + + return -1; +} + +static int sdsi_process_ioctl(int ioctl_no, void *info, uint8_t dev_no) +{ + char pathname[14]; + int fd, ret; + + ret = snprintf(pathname, 14, "%s%d", SDSI_DEV_PATH, dev_no); + if (ret < 0) + return ret; + + fd = open(pathname, O_RDONLY); + if (fd < 0) + return fd; + + ret = ioctl(fd, ioctl_no, info); + if (ret) + perror("Failed to process ioctl"); + + close(fd); + + return ret; +} + +static int +sdsi_process_spdm(void *request, void *response, int req_size, uint32_t rsp_size, + int dev_no) +{ + struct sdsi_spdm_command *command; + struct sdsi_spdm_message *message = request; + uint8_t request_code; + int ret; + + command = malloc(sizeof(*command)); + if (!command) { + perror("malloc"); + return -1; + } + + command->size = req_size; + command->message = *message; + request_code = command->message.request_response_code; + + ret = sdsi_process_ioctl(SDSI_IF_SPDM_COMMAND, command, dev_no); + if (ret) + goto free_command; + + if (command->size < sizeof(struct spdm_header)) { + fprintf(stderr, "Bad SPDM message size\n"); + ret = -1; + goto free_command; + } + + if (command->message.request_response_code != (request_code & ~SPDM_REQUEST)) { + ret = error_response((struct spdm_header *)&command->message); + goto free_command; + } + + if (response) { + if (command->size > rsp_size) { + fprintf(stderr, "SPDM response buffer too small\n"); + ret = -1; + goto free_command; + } + + memcpy(response, &command->message, command->size); + } + +free_command: + free(command); + return ret; +} + +struct version_number_entry { + uint8_t alpha:4; + uint8_t update_version_number:4; + union { + uint8_t version; + struct { + uint8_t minor:4; + uint8_t major:4; + }; + }; +} __packed; + +struct get_version_response { + struct spdm_header header; + uint16_t reserved:8; + uint16_t version_number_entry_count:8; + struct version_number_entry entry[10]; +} __packed; + +static int spdm_get_version(int dev_no) +{ + struct spdm_header request = {}; + struct get_version_response response = {}; + uint8_t version; + int ret; + + request.version = SPDM_VERSION; + request.code = SPDM_GET_VERSION; + request.param1 = SPDM_RSVD; + request.param2 = SPDM_RSVD; + + ret = sdsi_process_spdm(&request, &response, sizeof(request), + sizeof(response), dev_no); + if (ret) { + fprintf(stderr, "Failed GET_VERSION\n"); + return ret; + } + + if (!response.version_number_entry_count) { + fprintf(stderr, "Bad GET_VERSION entry count\n"); + return -1; + } + + version = response.entry[0].version; + + if (version != SPDM_VERSION) { + fprintf(stderr, "Unsupported version 0x%x\n", SPDM_VERSION); + return -1; + } + + return 0; +} + +static int spdm_get_capabilities(int dev_no) +{ + struct spdm_header request = {}; + int ret; + + request.version = SPDM_VERSION; + request.code = SPDM_GET_CAPABILITIES; + request.param1 = SPDM_RSVD; + request.param2 = SPDM_RSVD; + + ret = sdsi_process_spdm(&request, NULL, sizeof(request), 0, dev_no); + if (ret) { + fprintf(stderr, "Failed GET_CAPABILITIES\n"); + return ret; + } + + return 0; +} + +struct spdm_negotiate_alg { + struct spdm_header header; + uint32_t length:16; + uint32_t measurement_specification:8; + uint32_t reserved:8; + uint32_t base_asym_algo; + uint32_t base_hash_algo; + uint32_t reserved2[3]; + uint32_t ext_asym_count:8; + uint32_t ext_hash_count:8; + uint32_t reserved3:16; +}; + +#define MEASUREMENT_SPEC_DMTF BIT(0) +#define BASE_ASYM_ALG_ECDSA_ECC_NIST_P384 BIT(7) +#define BASE_HASH_ALG_SHA_384 BIT(1) + +static int spdm_negotiate_algorithms(int dev_no) +{ + struct spdm_negotiate_alg request = {}; + int ret; + + request.header.version = SPDM_VERSION; + request.header.code = SPDM_NEGOTIATE_ALGORITHMS; + request.header.param1 = SPDM_RSVD; + request.header.param2 = SPDM_RSVD; + + request.length = sizeof(request); + request.measurement_specification = MEASUREMENT_SPEC_DMTF; + request.base_asym_algo = BASE_ASYM_ALG_ECDSA_ECC_NIST_P384; + request.base_hash_algo = BASE_HASH_ALG_SHA_384; + + ret = sdsi_process_spdm(&request, NULL, sizeof(request), 0, dev_no); + if (ret) { + fprintf(stderr, "Failed NEGOTIATE_ALGORITHMS\n"); + return ret; + } + + return 0; +} + +static int spdm_negotiate(int dev_no) +{ + int ret; + + ret = spdm_get_version(dev_no); + if (ret) + return ret; + + ret = spdm_get_capabilities(dev_no); + if (ret) + return ret; + + return spdm_negotiate_algorithms(dev_no); +} + +struct get_digests_response { + struct spdm_header header; + uint8_t digest[TPM_ALG_SHA_384_SIZE]; +}; + +#define SLOT_MASK(slot) BIT(slot) + +int spdm_get_digests(int dev_no, uint8_t digest[TPM_ALG_SHA_384_SIZE]) +{ + struct spdm_header request = {}; + struct get_digests_response response = {}; + int ret; + + ret = spdm_negotiate(dev_no); + if (ret) + return ret; + + request.version = SPDM_VERSION; + request.code = SPDM_GET_DIGESTS; + request.param1 = SPDM_RSVD; + request.param2 = SPDM_RSVD; + + ret = sdsi_process_spdm(&request, &response, sizeof(request), + sizeof(response), dev_no); + if (ret) { + fprintf(stderr, "Failed GET_DIGESTS\n"); + return ret; + } + + if (!(response.header.param2 & SLOT_MASK(0))) { + fprintf(stderr, "Error, Slot 0 not selected in GET_DIGESTS\n"); + return -1; + } + + if (digest) + memcpy(digest, response.digest, TPM_ALG_SHA_384_SIZE); + + return 0; +} + +#define CERT_SLOT 0 + +struct get_cert_request { + struct spdm_header header; + uint16_t offset; + uint16_t length; +}; + +struct get_cert_response { + struct spdm_header header; + uint16_t portion_length; + uint16_t remainder_length; + uint8_t certificate_chain[SDSI_SPDM_BUF_SIZE]; +}; + +static int get_certificate_size(int dev_no) +{ + struct get_cert_request request = {}; + struct get_cert_response response = {}; + int ret; + + request.header.version = SPDM_VERSION; + request.header.code = SPDM_GET_CERTIFICATE; + request.header.param1 = CERT_SLOT; + request.header.param2 = SPDM_RSVD; + request.offset = 0; + request.length = SDSI_SPDM_BUF_SIZE; + + ret = sdsi_process_spdm(&request, &response, sizeof(request), + sizeof(response), dev_no); + if (ret) { + fprintf(stderr, "Error getting size during GET_CERTIFICATE\n"); + return ret; + } + + return response.portion_length + response.remainder_length; +} + +static int get_certificate_portion(int dev_no, uint16_t offset, uint16_t length, + uint16_t *portion_length, uint16_t *remainder_length, + uint8_t *cert_chain) +{ + struct get_cert_request request = {}; + struct get_cert_response response = {}; + int ret; + + request.header.version = SPDM_VERSION; + request.header.code = SPDM_GET_CERTIFICATE; + request.header.param1 = CERT_SLOT; + request.header.param2 = SPDM_RSVD; + request.offset = offset; + request.length = length; + + ret = sdsi_process_spdm(&request, &response, sizeof(request), + sizeof(response), dev_no); + if (ret) { + fprintf(stderr, "Failed GET_CERTIFICATE\n"); + return ret; + } + + *portion_length = response.portion_length; + *remainder_length = response.remainder_length; + + memcpy(cert_chain + offset, response.certificate_chain, *portion_length); + + return 0; +} + +int spdm_get_certificate(int dev_no, struct cert_chain *c) +{ + uint16_t remainder_length; + uint16_t old_remainder; + uint16_t portion_length = 0; + uint16_t offset = 0; + uint16_t size; + int ret; + + ret = spdm_negotiate(dev_no); + if (ret) + return ret; + + ret = spdm_get_digests(dev_no, NULL); + if (ret) + return ret; + + ret = get_certificate_size(dev_no); + if (ret < 0) + return ret; + + size = ret; + + c->chain = malloc(size); + if (!c->chain) { + perror("malloc"); + return -1; + } + + remainder_length = size < SDSI_SPDM_BUF_SIZE ? size : SDSI_SPDM_BUF_SIZE; + old_remainder = remainder_length; + + while (remainder_length) { + uint16_t length; + + length = remainder_length < SDSI_SPDM_BUF_SIZE ? + remainder_length : SDSI_SPDM_BUF_SIZE; + offset += portion_length; + + ret = get_certificate_portion(dev_no, offset, length, + &portion_length, + &remainder_length, + c->chain); + if (ret < 0) + goto free_cert_chain; + + if (!(remainder_length < old_remainder)) { + fprintf(stderr, "Bad GET_CERTIFICATE length\n"); + ret = -1; + goto free_cert_chain; + } + + old_remainder = remainder_length; + } + + c->len = offset + portion_length; + return 0; + +free_cert_chain: + free(c->chain); + c->chain = NULL; + return ret; +} diff --git a/tools/arch/x86/intel_sdsi/spdm.h b/tools/arch/x86/intel_sdsi/spdm.h new file mode 100644 index 000000000000..aa7e08ffb872 --- /dev/null +++ b/tools/arch/x86/intel_sdsi/spdm.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <stdint.h> + +#define TPM_ALG_SHA_384_SIZE 48 + +struct cert_chain { + void *chain; + size_t len; +}; + +int spdm_get_digests(int dev_no, uint8_t digest[TPM_ALG_SHA_384_SIZE]); +int spdm_get_certificate(int dev_no, struct cert_chain *c); + -- 2.34.1