From 066eacbfbde995611d4d812855de47bee1ff767e Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:13 +0800 Subject: [PATCH 01/10] industry_standard/spdm: Add SLOT_MANAGEMENT definitions Add the SPDM 1.4 SLOT_MANAGEMENT / SLOT_MANAGEMENT_RESP request and response structures, the SubCode value defines, the SLOT_MGMT_CAP extended capability flag, the SPDM_MAX_BANK_COUNT define (BankID is 0 to 239), and the SubCode-dependent structures for all defined SubCodes: SupportedSubCodes, SlotAddress, BankInfo/BankElement, BankDetails/SlotElement, GetCertificateChain, ManageBank, ManageSlot, GetCSR, CSR, and SetCertificate. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- include/industry_standard/spdm.h | 181 +++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/include/industry_standard/spdm.h b/include/industry_standard/spdm.h index 49be2b97878..32d1ccd6788 100644 --- a/include/industry_standard/spdm.h +++ b/include/industry_standard/spdm.h @@ -1824,6 +1824,187 @@ typedef struct { #define SPDM_SET_KEY_PAIR_INFO_ERASE_OPERATION 1 #define SPDM_SET_KEY_PAIR_INFO_GENERATE_OPERATION 2 +/* SPDM SLOT_MANAGEMENT request */ +typedef struct { + spdm_message_header_t header; + /* param1 == SubCode + * param2 == RSVD*/ + uint16_t mgmt_struct_offset; + uint16_t reserved; + /* uint8_t slot_mgmt_req_struct[]; (SubCode dependent, at mgmt_struct_offset) */ +} spdm_slot_management_request_t; + +/* SPDM SLOT_MANAGEMENT_RESP response */ +typedef struct { + spdm_message_header_t header; + /* param1 == SubCode + * param2 == RSVD*/ + uint16_t mgmt_struct_offset; + uint16_t reserved; + /* uint8_t slot_mgmt_resp_struct[]; (SubCode dependent, at mgmt_struct_offset) */ +} spdm_slot_management_response_t; + +/* SPDM SLOT_MANAGEMENT SubCodes */ +#define SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES 0x00 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO 0x01 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS 0x02 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN 0x03 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR 0x04 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK 0x20 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT 0x21 +#define SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE 0x22 + +/* SPDM SLOT_MANAGEMENT SupportedSubCodes response structure */ +typedef struct { + uint16_t resp_length; + uint16_t reserved; + uint8_t sub_code_bitmap[8]; + uint8_t reserved2[24]; +} spdm_slot_management_supported_subcodes_struct_t; + +/* SPDM SLOT_MANAGEMENT SlotAddress request structure (used by multiple SubCodes) */ +typedef struct { + uint16_t req_length; + uint16_t reserved; + uint8_t bank_id; + uint8_t slot_id; + uint16_t reserved2; +} spdm_slot_management_slot_address_struct_t; + +/* For this version of the specification, ReqLength of SlotAddress shall be 8. */ +#define SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH 8 + +/* The slot_id field of SlotAddress carries the SlotID in bits [3:0]. */ +#define SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK 0x0F + +/* BankIDs are numbered consecutively from 0 to a maximum of 239, so a Responder can have at + * most 240 Banks. */ +#define SPDM_MAX_BANK_COUNT 240 + +/* SPDM SLOT_MANAGEMENT GetBankInfo response structures */ +typedef struct { + uint8_t element_length; + uint8_t bank_id; + uint8_t slot_mask; + uint8_t modifiable_slot_mask; +} spdm_slot_management_bank_element_struct_t; + +/* For this version of the specification, ElementLength of BankElement shall be 4. */ +#define SPDM_SLOT_MANAGEMENT_BANK_ELEMENT_LENGTH 4 + +typedef struct { + uint16_t resp_length; + uint16_t reserved; + uint8_t num_bank_elements; + uint8_t reserved2[3]; + /* spdm_slot_management_bank_element_struct_t bank_elements[num_bank_elements]; */ +} spdm_slot_management_bank_info_struct_t; + +/* SPDM SLOT_MANAGEMENT GetBankDetails response structures */ +typedef struct { + uint16_t element_length; + uint8_t slot_id; + uint8_t reserved; + uint8_t slot_attributes; + uint8_t key_pair_id; + uint8_t certificate_info; + uint8_t reserved2; + uint16_t key_usage; + uint16_t reserved3; + uint32_t slot_size; + /* uint8_t digest[H]; */ +} spdm_slot_management_slot_element_struct_t; + +/* SlotElement attributes */ +#define SPDM_SLOT_MANAGEMENT_SLOT_ATTRIBUTE_PROVISIONED 0x01 +#define SPDM_SLOT_MANAGEMENT_SLOT_ATTRIBUTE_WRITE_PROTECTED 0x02 + +typedef struct { + uint16_t resp_length; + uint8_t bank_id; + uint8_t reserved; + uint16_t num_slot_elements; + uint8_t bank_attributes; + uint8_t reserved2; + uint32_t asym_algo_capabilities; + uint32_t current_asym_algo; + uint32_t available_asym_algo; + /* uint8_t pqc_asym_algo_cap_len; + * uint8_t pqc_asym_algo_capabilities[pqc_asym_algo_cap_len]; + * uint8_t current_pqc_asym_algo_len; + * uint8_t current_pqc_asym_algo[current_pqc_asym_algo_len]; + * uint8_t available_pqc_asym_algo_len; + * uint8_t available_pqc_asym_algo[available_pqc_asym_algo_len]; + * uint8_t reserved3[4]; + * spdm_slot_management_slot_element_struct_t slot_elements[num_slot_elements]; */ +} spdm_slot_management_bank_details_struct_t; + +/* BankDetails attributes */ +#define SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_SELECTED 0x01 +#define SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_CONFIG_ALGO 0x02 + +/* SPDM SLOT_MANAGEMENT GetCertificateChain response structure */ +typedef struct { + uint32_t cc_length; + uint32_t reserved; + /* uint8_t cert_chain[cc_length]; */ +} spdm_slot_management_get_certificate_chain_struct_t; + +/* SPDM SLOT_MANAGEMENT ManageBank request structure (no response structure) */ +typedef struct { + spdm_slot_management_slot_address_struct_t slot_address; + uint8_t operation; + uint8_t reserved[3]; + uint32_t select_asym_algo; + /* uint8_t select_pqc_asym_algo_len; + * uint8_t select_pqc_asym_algo[select_pqc_asym_algo_len]; */ +} spdm_slot_management_manage_bank_struct_t; + +/* ManageBank operation */ +#define SPDM_SLOT_MANAGEMENT_MANAGE_BANK_OPERATION_CONFIG_ALGO 0x01 + +/* SPDM SLOT_MANAGEMENT ManageSlot request structure (no response structure) */ +typedef struct { + spdm_slot_management_slot_address_struct_t slot_address; + uint8_t operation; + uint8_t reserved[3]; +} spdm_slot_management_manage_slot_struct_t; + +/* ManageSlot operation */ +#define SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE 0x01 + +/* SPDM SLOT_MANAGEMENT GetCSR request structure */ +typedef struct { + spdm_slot_management_slot_address_struct_t slot_address; + uint8_t key_pair_id; + uint8_t request_attributes; + uint16_t reserved; + uint16_t requester_info_length; + uint16_t opaque_data_length; + /* uint8_t requester_info[requester_info_length]; + * uint8_t opaque_data[opaque_data_length]; */ +} spdm_slot_management_get_csr_struct_t; + +/* SPDM SLOT_MANAGEMENT CSR response structure */ +typedef struct { + uint32_t csr_length; + uint32_t reserved; + /* uint8_t csr[csr_length]; */ +} spdm_slot_management_csr_struct_t; + +/* SPDM SLOT_MANAGEMENT SetCertificate request structure (no response structure) */ +typedef struct { + spdm_slot_management_slot_address_struct_t slot_address; + uint32_t cert_length; + uint8_t cert_attributes; + uint8_t reserved[3]; + /* uint8_t certificate[cert_length]; */ +} spdm_slot_management_set_certificate_struct_t; + +/* SetCertificate attributes: Bit[2:0] is the certificate model (see SPDM_CERTIFICATE_INFO_ + * CERT_MODEL_*). */ +#define SPDM_SLOT_MANAGEMENT_SET_CERTIFICATE_ATTRIBUTE_CERT_MODEL_MASK 0x07 + #pragma pack() #endif /* SPDM_H */ From 26a4383c1eef9d99b15b7ac183c5e2aedd284539 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:17 +0800 Subject: [PATCH 02/10] hal/responder: Add slot_management HAL hooks Add the responder HAL hooks for the SLOT_MANAGEMENT SubCodes: libspdm_read_slot_management_supported_subcodes, libspdm_read_slot_management_bank_info (writes the BankElements directly into the response buffer, so the responder imposes no fixed maximum Bank count), libspdm_read_slot_management_bank_details (per-slot info for a Bank, including the certificate chain digest), libspdm_read_slot_management_certificate_chain (per Bank+slot; the certificate chain is selected by the Bank's configured algorithm and need not be provisioned into the SPDM context), and the write hooks libspdm_write_slot_management_bank (ManageBank) and libspdm_write_slot_management_slot (ManageSlot). The GetCSR and SetCertificate SubCodes reuse the existing GET_CSR / SET_CERTIFICATE HAL hooks. To address a Bank, libspdm_gen_csr_ex and libspdm_write_certificate_to_nvm gain a bank_id parameter; the legacy GET_CSR / SET_CERTIFICATE flows pass LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID (BankID 0 is valid, so it cannot serve as the sentinel). Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- include/hal/library/responder/csrlib.h | 14 ++ include/hal/library/responder/key_pair_info.h | 7 +- include/hal/library/responder/setcertlib.h | 23 +- include/hal/library/responder/slot_mgmt.h | 201 ++++++++++++++++++ 4 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 include/hal/library/responder/slot_mgmt.h diff --git a/include/hal/library/responder/csrlib.h b/include/hal/library/responder/csrlib.h index a32c952b086..416be216329 100644 --- a/include/hal/library/responder/csrlib.h +++ b/include/hal/library/responder/csrlib.h @@ -13,6 +13,15 @@ #include "industry_standard/spdm.h" #if LIBSPDM_ENABLE_CAPABILITY_CSR_CAP + +/* bank_id sentinel passed to the CSR/SET_CERTIFICATE HAL hooks meaning "no Bank addressing". + * The legacy GET_CSR and SET_CERTIFICATE flows have no Bank concept and pass this value; only + * the SLOT_MANAGEMENT GetCSR/SetCertificate SubCodes pass a real BankID. BankID 0 is a valid + * Bank, so it cannot be used as the sentinel. */ +#ifndef LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID +#define LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID 0xFF +#endif + /** * Generate a PKCS #10 certificate signing request. * @@ -56,6 +65,10 @@ * CSRTrackingTag is used and req_key_pair_id / overwrite * carry their pre-1.3 defaults (0 / false). * @param[in] req_key_pair_id Indicates the desired key pair associated with the CSR. + * @param[in] bank_id The Bank to generate the CSR for, when invoked via the + * SLOT_MANAGEMENT GetCSR SubCode. The legacy GET_CSR flow + * passes LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID to + * indicate no Bank addressing. * @param[in] overwrite If set, the Responder shall stop processing any existing * GET_CSR request and overwrite it with this request. * @param[out] is_busy If true, indicates that the CSR cannot be generated at @@ -80,6 +93,7 @@ extern bool libspdm_gen_csr( uint8_t req_cert_model, uint8_t *req_csr_tracking_tag, uint8_t req_key_pair_id, + uint8_t bank_id, bool overwrite, bool *is_busy, bool *unexpected_request); #endif /* LIBSPDM_ENABLE_CAPABILITY_CSR_CAP */ diff --git a/include/hal/library/responder/key_pair_info.h b/include/hal/library/responder/key_pair_info.h index 7449cb70629..0bb37561528 100644 --- a/include/hal/library/responder/key_pair_info.h +++ b/include/hal/library/responder/key_pair_info.h @@ -11,7 +11,9 @@ #include "internal/libspdm_lib_config.h" #include "industry_standard/spdm.h" -#if LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP +/* The key pair read functions are also used by the SLOT_MANAGEMENT feature, which maps one + * Bank per key pair, so they are exposed when either capability is enabled. */ +#if LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP /** * return the total key pairs number. @@ -55,7 +57,8 @@ extern bool libspdm_read_key_pair_info( uint8_t *assoc_cert_slot_mask, uint16_t *public_key_info_len, uint8_t *public_key_info); -#endif /* LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP */ +#endif /* LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP || + * LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ #if LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP /** diff --git a/include/hal/library/responder/setcertlib.h b/include/hal/library/responder/setcertlib.h index c2382060a8c..5011c1376f5 100644 --- a/include/hal/library/responder/setcertlib.h +++ b/include/hal/library/responder/setcertlib.h @@ -11,7 +11,10 @@ #include "internal/libspdm_lib_config.h" #include "industry_standard/spdm.h" -#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP +/* The trusted-environment query is used both by SET_CERTIFICATE and by the SLOT_MANAGEMENT + * access-control checks, so it is available when either capability is enabled. */ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + /** * return if current code is running in a trusted environment. * @@ -22,12 +25,28 @@ **/ extern bool libspdm_is_in_trusted_environment(void *spdm_context); +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP + +/* bank_id sentinel passed to the SET_CERTIFICATE/CSR HAL hooks meaning "no Bank addressing". + * The legacy SET_CERTIFICATE flow has no Bank concept and passes this value; only the + * SLOT_MANAGEMENT SetCertificate SubCode passes a real BankID. BankID 0 is a valid Bank, so it + * cannot be used as the sentinel. */ +#ifndef LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID +#define LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID 0xFF +#endif + /** * Stores or erase a certificate chain in non-volatile memory. * If the cert_chain is NULL and cert_chain_size is 0, * the feature is to erase the certificate chain. * * @param[in] spdm_context A pointer to the SPDM context. + * @param[in] bank_id The Bank for the certificate chain, when invoked via the + * SLOT_MANAGEMENT SetCertificate SubCode. The legacy + * SET_CERTIFICATE flow passes LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID + * to indicate no Bank addressing. * @param[in] slot_id The number of slot for the certificate chain. * @param[in] cert_chain The pointer for the certificate chain to set. * @param[in] cert_chain_size The size of the certificate chain to set. @@ -44,7 +63,7 @@ extern bool libspdm_is_in_trusted_environment(void *spdm_context); **/ extern bool libspdm_write_certificate_to_nvm( void *spdm_context, - uint8_t slot_id, const void * cert_chain, + uint8_t bank_id, uint8_t slot_id, const void * cert_chain, size_t cert_chain_size, uint32_t base_hash_algo, uint32_t base_asym_algo, uint32_t pqc_asym_algo, bool *need_reset, bool *is_busy); diff --git a/include/hal/library/responder/slot_mgmt.h b/include/hal/library/responder/slot_mgmt.h new file mode 100644 index 00000000000..9d180084320 --- /dev/null +++ b/include/hal/library/responder/slot_mgmt.h @@ -0,0 +1,201 @@ +/** + * Copyright Notice: + * Copyright 2026 DMTF. All rights reserved. + * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md + **/ + +#ifndef RESPONDER_SLOT_MGMT_H +#define RESPONDER_SLOT_MGMT_H + +#include "hal/base.h" +#include "internal/libspdm_lib_config.h" +#include "industry_standard/spdm.h" + +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + +/* bank_id sentinel meaning "no Bank addressing". The legacy GET_CSR and SET_CERTIFICATE flows have + * no Bank concept and pass this value; only the SLOT_MANAGEMENT GetCSR/SetCertificate SubCodes pass + * a real BankID. BankID 0 is a valid Bank, so it cannot be used as the sentinel. Also defined in + * csrlib.h / setcertlib.h (guarded by CSR_CAP / SET_CERT_CAP); defined here too so it is available + * whenever SLOT_MANAGEMENT is compiled, independent of those capabilities. */ +#ifndef LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID +#define LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID 0xFF +#endif + +/** + * read the SLOT_MANAGEMENT supported SubCodes bitmap. + * + * The Responder shall set a bit in the bit position of the SubCode value for every + * SLOT_MANAGEMENT SubCode that the Responder supports. The bits corresponding to the + * required SubCodes (SupportedSubCodes, GetBankInfo, GetBankDetails, GetCertificateChain) + * shall always be set. + * + * @param spdm_context A pointer to the SPDM context. + * @param sub_code_bitmap A pointer to a 8-byte destination buffer to store the + * supported SubCodes bitmap. The bit position corresponds to + * the SubCode value. + * + * @retval true get supported SubCodes successfully. + * @retval false get supported SubCodes failed. + **/ +extern bool libspdm_read_slot_management_supported_subcodes( + void *spdm_context, + uint8_t *sub_code_bitmap); + +/** + * read the SLOT_MANAGEMENT GetBankInfo information. + * + * The BankElements are written directly into the response buffer (the destination is bounded + * by the response buffer size, so there is no fixed maximum Bank count in the responder). + * + * @param spdm_context A pointer to the SPDM context. + * @param num_bank_elements On input, the capacity, in BankElements, of the bank_elements + * array. On output, the number of Banks reported. + * @param bank_elements A pointer to a destination array to store the BankElements. + * + * @retval true get bank info successfully. + * @retval false get bank info failed (e.g. the destination is too small). + **/ +extern bool libspdm_read_slot_management_bank_info( + void *spdm_context, + uint8_t *num_bank_elements, + spdm_slot_management_bank_element_struct_t *bank_elements); + +/** + * read the SLOT_MANAGEMENT GetBankDetails information for one Bank. + * + * Each slot's fixed fields are written into the slot_elements array (the on-wire SlotElement + * structure). The element_length field is set by the Responder, not this hook. The digest of + * each slot's certificate chain is returned separately, in slot_digests: slot N's digest is + * stored at slot_digests + N * (*slot_digest_size). The digest is over the certificate chain + * for the Bank's configured algorithm (the same chain that GetCertificateChain returns), using + * the connection's negotiated hash algorithm; the chain need not be provisioned into the SPDM + * context. + * + * @param spdm_context A pointer to the SPDM context. + * @param bank_id The Bank to retrieve details for. + * @param bank_attributes The attributes of the Bank. + * @param asym_algo_capabilities The asymmetric algorithms the Responder supports for this Bank. + * @param current_asym_algo The currently configured asymmetric algorithm for this Bank. + * @param available_asym_algo The currently available asymmetric algorithms for this Bank. + * @param pqc_asym_algo_capabilities The PQC asymmetric algorithms the Responder supports for this Bank. + * @param current_pqc_asym_algo The currently configured PQC asymmetric algorithm for this Bank. + * @param available_pqc_asym_algo The currently available PQC asymmetric algorithms for this Bank. + * @param num_slot_elements On input, the capacity of the slot_elements array. + * On output, the number of slots reported for this Bank. + * @param slot_elements A pointer to a destination array to store the per-slot + * SlotElement structures (element_length is set by the + * Responder). + * @param slot_digest_size On input, the stride, in bytes, of each digest in the + * slot_digests buffer (the negotiated hash size). On output, + * the size of each digest written. + * @param slot_digests A pointer to a destination buffer to store the slots' + * certificate chain digests, one per slot at a stride of + * slot_digest_size. + * + * @retval true get bank details successfully. + * @retval false get bank details failed (e.g. unknown bank_id). + **/ +extern bool libspdm_read_slot_management_bank_details( + void *spdm_context, + uint8_t bank_id, + uint8_t *bank_attributes, + uint32_t *asym_algo_capabilities, + uint32_t *current_asym_algo, + uint32_t *available_asym_algo, + uint32_t *pqc_asym_algo_capabilities, + uint32_t *current_pqc_asym_algo, + uint32_t *available_pqc_asym_algo, + uint8_t *num_slot_elements, + spdm_slot_management_slot_element_struct_t *slot_elements, + uint32_t *slot_digest_size, + uint8_t *slot_digests); + +/** + * read the certificate chain in a slot of a Bank (the SLOT_MANAGEMENT GetCertificateChain + * SubCode). + * + * The certificate chain is not required to be provisioned into the SPDM context; the + * Responder may manage it independently and return it through this hook. + * + * @param spdm_context A pointer to the SPDM context. + * @param bank_id The Bank that contains the slot. + * @param slot_id The slot to read the certificate chain from. + * @param cert_chain_size On input, the capacity in bytes of the cert_chain buffer. + * On output, the number of bytes written. + * @param cert_chain A pointer to a destination buffer to store the certificate chain. + * + * @retval true the certificate chain was read successfully. + * @retval false read failed (e.g. unknown bank_id/slot_id, empty slot, buffer too small). + **/ +extern bool libspdm_read_slot_management_certificate_chain( + void *spdm_context, + uint8_t bank_id, + uint8_t slot_id, + size_t *cert_chain_size, + void *cert_chain); + +/* Result of a ManageBank operation, returned in the bank_result out-parameter of + * libspdm_write_slot_management_bank. The Responder maps these to the SLOT_MANAGEMENT_RESP or to + * the spec-mandated ERROR codes for ManageBank. */ +#define LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_OK 0 +/* Generic rejection (e.g. unknown bank_id, unsupported algorithm) -> ERROR(InvalidRequest). */ +#define LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_INVALID 1 +/* A slot in the Bank already has a certificate provisioned -> ERROR(InvalidState). */ +#define LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_INVALID_STATE 2 +/* The Bank reconfiguration requires a device reset -> ERROR(ResetRequired). */ +#define LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_RESET_REQUIRED 3 + +/** + * configure the asymmetric algorithm of a Bank (the SLOT_MANAGEMENT ManageBank SubCode). + * + * @param spdm_context A pointer to the SPDM context. + * @param bank_id The Bank to configure. + * @param operation The Bank management operation (e.g. ConfigAlgo). + * @param select_asym_algo The asymmetric algorithm to configure for the Bank. + * @param select_pqc_asym_algo The PQC asymmetric algorithm to configure for the Bank. + * At most one of select_asym_algo and select_pqc_asym_algo is set. + * @param bank_result On output, one of LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_*. The + * Responder uses this to select the response or the ERROR code + * mandated by the specification (InvalidState when a slot in the Bank + * is provisioned, ResetRequired when a reset is needed). + * + * @retval true manage bank successfully (bank_result is *_OK). + * @retval false manage bank failed; bank_result indicates which ERROR the Responder shall send. + **/ +extern bool libspdm_write_slot_management_bank( + void *spdm_context, + uint8_t bank_id, + uint8_t operation, + uint32_t select_asym_algo, + uint32_t select_pqc_asym_algo, + uint8_t *bank_result); + +/** + * perform a management operation on a slot in a Bank (the SLOT_MANAGEMENT ManageSlot SubCode). + * + * @param spdm_context A pointer to the SPDM context. + * @param bank_id The Bank that contains the slot. + * @param slot_id The slot to operate on. + * @param operation The slot management operation (e.g. Erase). + * @param need_reset On input, whether the Responder advertises CERT_INSTALL_RESET_CAP. + * On output, set to true if the device requires a reset to complete + * the operation. Only honored by the Responder when + * CERT_INSTALL_RESET_CAP is advertised. + * @param is_busy On output, set to true if the device cannot perform the operation + * at this time; the Responder shall return ErrorCode=Busy. + * + * @retval true manage slot successfully. + * @retval false manage slot failed (e.g. unknown bank_id/slot_id, unsupported operation, or busy). + **/ +extern bool libspdm_write_slot_management_slot( + void *spdm_context, + uint8_t bank_id, + uint8_t slot_id, + uint8_t operation, + bool *need_reset, + bool *is_busy); + +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + +#endif /* RESPONDER_SLOT_MANAGEMENT_H */ From 65f4b7a7d528d4f0165453090f7ea59aeca6149a Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:21 +0800 Subject: [PATCH 03/10] spdm_common_lib: Add SLOT_MANAGEMENT support Add the LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP config switch, the internal common-lib declaration, and the SLOT_MANAGEMENT / SLOT_MANAGEMENT_RESP command-name map entries. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- include/internal/libspdm_common_lib.h | 1 + include/library/spdm_lib_config.h | 5 +++++ library/spdm_common_lib/libspdm_com_support.c | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/include/internal/libspdm_common_lib.h b/include/internal/libspdm_common_lib.h index cb116703ba1..16ec9fd9046 100644 --- a/include/internal/libspdm_common_lib.h +++ b/include/internal/libspdm_common_lib.h @@ -20,6 +20,7 @@ #include "hal/library/responder/measlib.h" #include "hal/library/responder/keyexlib.h" #include "hal/library/responder/key_pair_info.h" +#include "hal/library/responder/slot_mgmt.h" #include "hal/library/responder/psklib.h" #include "hal/library/responder/setcertlib.h" #include "hal/library/endpointinfolib.h" diff --git a/include/library/spdm_lib_config.h b/include/library/spdm_lib_config.h index d20f8a86b50..df57335af2f 100644 --- a/include/library/spdm_lib_config.h +++ b/include/library/spdm_lib_config.h @@ -86,6 +86,11 @@ #define LIBSPDM_ENABLE_CAPABILITY_ENDPOINT_INFO_CAP 1 #endif +/* SPDM 1.4 capabilities. */ +#ifndef LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP +#define LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP 1 +#endif + /* If 1 then endpoint supports sending GET_CERTIFICATE and GET_DIGESTS requests. * If enabled and endpoint is a Responder then LIBSPDM_ENABLE_CAPABILITY_ENCAP_CAP * must also be enabled. diff --git a/library/spdm_common_lib/libspdm_com_support.c b/library/spdm_common_lib/libspdm_com_support.c index b97a042eae5..29bc9b6ff88 100644 --- a/library/spdm_common_lib/libspdm_com_support.c +++ b/library/spdm_common_lib/libspdm_com_support.c @@ -50,6 +50,8 @@ const char *libspdm_get_code_str(uint8_t request_code) { SPDM_KEY_PAIR_INFO, "SPDM_KEY_PAIR_INFO" }, { SPDM_SET_KEY_PAIR_INFO_ACK, "SPDM_SET_KEY_PAIR_INFO_ACK" }, { SPDM_ENDPOINT_INFO, "SPDM_ENDPOINT_INFO" }, + /* SPDM response code (1.4) */ + { SPDM_SLOT_MANAGEMENT_RESP, "SPDM_SLOT_MANAGEMENT_RESP" }, /* SPDM request code (1.0) */ { SPDM_GET_DIGESTS, "SPDM_GET_DIGESTS" }, { SPDM_GET_CERTIFICATE, "SPDM_GET_CERTIFICATE" }, @@ -83,6 +85,8 @@ const char *libspdm_get_code_str(uint8_t request_code) { SPDM_GET_KEY_PAIR_INFO, "SPDM_GET_KEY_PAIR_INFO" }, { SPDM_SET_KEY_PAIR_INFO, "SPDM_SET_KEY_PAIR_INFO" }, { SPDM_GET_ENDPOINT_INFO, "SPDM_GET_ENDPOINT_INFO" }, + /* SPDM request code (1.4) */ + { SPDM_SLOT_MANAGEMENT, "SPDM_SLOT_MANAGEMENT" }, }; for (index = 0; index < LIBSPDM_ARRAY_SIZE(code_str_struct); index++) { From f0c78139fccbfd159e0d6178a60c9ae037def853 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:24 +0800 Subject: [PATCH 04/10] spdm_requester_lib: Add SLOT_MANAGEMENT Add the requester functions for the SLOT_MANAGEMENT SubCodes: libspdm_slot_management_get_supported_subcodes, _get_bank_info, _get_bank_details, _get_certificate_chain, _manage_bank, _manage_slot, _get_csr, and _set_certificate. Each sends SLOT_MANAGEMENT with its SubCode and parses the SLOT_MANAGEMENT_RESP, gated by SLOT_MGMT_CAP. The GetCSR and SetCertificate APIs mirror libspdm_get_csr_ex and libspdm_set_certificate, with the added ability to address a Bank. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- include/library/spdm_requester_lib.h | 196 +++ library/spdm_requester_lib/CMakeLists.txt | 1 + .../libspdm_req_slot_management.c | 1255 +++++++++++++++++ 3 files changed, 1452 insertions(+) create mode 100644 library/spdm_requester_lib/libspdm_req_slot_management.c diff --git a/include/library/spdm_requester_lib.h b/include/library/spdm_requester_lib.h index 6ec6e2a61ce..e8eae9119c3 100644 --- a/include/library/spdm_requester_lib.h +++ b/include/library/spdm_requester_lib.h @@ -470,6 +470,202 @@ libspdm_return_t libspdm_set_key_pair_info(void *spdm_context, const uint32_t *s ); #endif /* LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP +/** + * This function sends SLOT_MANAGEMENT with the SupportedSubCodes SubCode to retrieve the + * bit map of SLOT_MANAGEMENT SubCodes supported by the Responder. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM session. + * If session_id is NULL, it is a normal message. + * If session_id is not NULL, it is a secured message. + * @param sub_code_bitmap A pointer to a 8-byte destination buffer to store the supported + * SubCodes bit map. The bit position corresponds to the SubCode value. + * + * @retval LIBSPDM_STATUS_SUCCESS the supported SubCodes were retrieved successfully. + **/ +libspdm_return_t libspdm_slot_management_get_supported_subcodes(void *spdm_context, + const uint32_t *session_id, + uint8_t *sub_code_bitmap); + +/** + * This function sends SLOT_MANAGEMENT with the GetBankInfo SubCode to retrieve the array + * of BankElements describing the Banks supported by the Responder. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM session. + * If session_id is NULL, it is a normal message. + * @param num_bank_elements On input, the capacity of the bank_elements array. + * On output, the number of BankElements returned. + * @param bank_elements A pointer to a destination array to store the BankElements. + * + * @retval LIBSPDM_STATUS_SUCCESS the BankInfo was retrieved successfully. + **/ +libspdm_return_t libspdm_slot_management_get_bank_info( + void *spdm_context, const uint32_t *session_id, + uint8_t *num_bank_elements, + spdm_slot_management_bank_element_struct_t *bank_elements); + +/** + * This function sends SLOT_MANAGEMENT with the GetBankDetails SubCode to retrieve detailed + * information about the slots in one specified Bank. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM + * session. If session_id is NULL, it is a normal message. + * @param bank_id The Bank to retrieve details for. + * @param bank_attributes A pointer to store the Bank attributes. It can be NULL. + * @param asym_algo_capabilities A pointer to store the asymmetric algorithm capabilities. + * It can be NULL. + * @param current_asym_algo A pointer to store the currently configured asymmetric + * algorithm. It can be NULL. + * @param available_asym_algo A pointer to store the available asymmetric algorithms. + * It can be NULL. + * @param pqc_asym_algo_capabilities A pointer to store the PQC asymmetric algorithm + * capabilities. It can be NULL. The Responder may report this + * field with any length; only the leading bytes that fit in a + * uint32_t are returned. + * @param current_pqc_asym_algo A pointer to store the currently configured PQC asymmetric + * algorithm. It can be NULL. See pqc_asym_algo_capabilities for + * the field-length handling. + * @param available_pqc_asym_algo A pointer to store the available PQC asymmetric algorithms. + * It can be NULL. See pqc_asym_algo_capabilities for the + * field-length handling. + * @param num_slot_elements A pointer to store the number of SlotElements returned. + * It can be NULL. + * @param slot_elements_size On input, the capacity in bytes of the slot_elements + * buffer. On output, the number of bytes written. It can be + * NULL if slot_elements is NULL. + * @param slot_elements A pointer to a destination buffer to store the raw + * SlotElement array. It can be NULL. + * + * @retval LIBSPDM_STATUS_SUCCESS the BankDetails was retrieved successfully. + **/ +libspdm_return_t libspdm_slot_management_get_bank_details( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, + uint8_t *bank_attributes, + uint32_t *asym_algo_capabilities, + uint32_t *current_asym_algo, + uint32_t *available_asym_algo, + uint32_t *pqc_asym_algo_capabilities, + uint32_t *current_pqc_asym_algo, + uint32_t *available_pqc_asym_algo, + uint16_t *num_slot_elements, + size_t *slot_elements_size, + void *slot_elements); + +/** + * This function sends SLOT_MANAGEMENT with the GetCertificateChain SubCode to retrieve an + * individual certificate chain. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM session. + * If session_id is NULL, it is a normal message. + * @param bank_id The Bank that contains the slot. + * @param slot_id The slot to retrieve the certificate chain from. + * @param cert_chain_size On input, the capacity in bytes of the cert_chain buffer. + * On output, the number of bytes written. + * @param cert_chain A pointer to a destination buffer to store the certificate chain. + * + * @retval LIBSPDM_STATUS_SUCCESS the certificate chain was retrieved successfully. + **/ +libspdm_return_t libspdm_slot_management_get_certificate_chain( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, + size_t *cert_chain_size, + void *cert_chain); + +/** + * This function sends SLOT_MANAGEMENT with the ManageBank SubCode to configure the asymmetric + * algorithm of a Bank. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM + * session. If session_id is NULL, it is a normal message. + * @param bank_id The Bank to configure. + * @param operation The Bank management operation (e.g. ConfigAlgo). + * @param select_asym_algo The asymmetric algorithm to configure for the Bank. + * @param select_pqc_asym_algo The PQC asymmetric algorithm to configure for the Bank. + * At most one of the two algorithm parameters may be set. + * + * @retval LIBSPDM_STATUS_SUCCESS the Bank was configured successfully. + **/ +libspdm_return_t libspdm_slot_management_manage_bank( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t operation, + uint32_t select_asym_algo, uint32_t select_pqc_asym_algo); + +/** + * This function sends SLOT_MANAGEMENT with the ManageSlot SubCode to perform a management + * operation on a slot in a Bank. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM session. + * If session_id is NULL, it is a normal message. + * @param bank_id The Bank that contains the slot. + * @param slot_id The slot to operate on. + * @param operation The slot management operation (e.g. Erase). + * + * @retval LIBSPDM_STATUS_SUCCESS the slot operation completed successfully. + **/ +libspdm_return_t libspdm_slot_management_manage_slot( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t operation); + +/** + * This function sends SLOT_MANAGEMENT with the GetCSR SubCode to read a certificate signing + * request from a Bank+slot. This mirrors libspdm_get_csr_ex, with the added ability to + * address a Bank. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM + * session. If session_id is NULL, it is a normal message. + * @param bank_id The Bank to generate the CSR for. + * @param slot_id The slot to generate the CSR for. + * @param key_pair_id The key pair ID to use in generating the CSR. + * @param request_attributes The GetCSR request attributes (CSRCertModel, CSRTrackingTag, + * Overwrite). + * @param requester_info Requester info to generate the CSR. + * @param requester_info_length The length of requester_info. + * @param opaque_data Opaque data from the requester. + * @param opaque_data_length The length of opaque_data. + * @param csr A pointer to a destination buffer to store the CSR. + * @param csr_len On input, the capacity in bytes of the csr buffer. + * On output, the number of bytes written. + * + * @retval LIBSPDM_STATUS_SUCCESS the CSR was retrieved successfully. + **/ +libspdm_return_t libspdm_slot_management_get_csr( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t key_pair_id, uint8_t request_attributes, + void *requester_info, uint16_t requester_info_length, + void *opaque_data, uint16_t opaque_data_length, + void *csr, size_t *csr_len); + +/** + * This function sends SLOT_MANAGEMENT with the SetCertificate SubCode to write a certificate + * chain to a Bank+slot. This mirrors libspdm_set_certificate, with the added ability to + * address a Bank. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM session. + * If session_id is NULL, it is a normal message. + * @param bank_id The Bank to write the certificate chain to. + * @param slot_id The slot to write the certificate chain to. + * @param cert_attributes The SetCertificate attributes (certificate model in Bit[2:0]). + * @param cert_chain The certificate chain to set. It is a full SPDM certificate chain, + * including Length and Root Cert Hash. + * @param cert_chain_size The size of the certificate chain to set. + * + * @retval LIBSPDM_STATUS_SUCCESS the certificate chain was set successfully. + **/ +libspdm_return_t libspdm_slot_management_set_certificate( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t cert_attributes, + const void *cert_chain, size_t cert_chain_size); +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #if (LIBSPDM_ENABLE_CAPABILITY_KEY_EX_CAP) || (LIBSPDM_ENABLE_CAPABILITY_PSK_CAP) /** * This function sends KEY_EXCHANGE/FINISH or PSK_EXCHANGE/PSK_FINISH diff --git a/library/spdm_requester_lib/CMakeLists.txt b/library/spdm_requester_lib/CMakeLists.txt index 3cda286ca18..45b1dce9a25 100644 --- a/library/spdm_requester_lib/CMakeLists.txt +++ b/library/spdm_requester_lib/CMakeLists.txt @@ -47,4 +47,5 @@ target_sources(spdm_requester_lib libspdm_req_get_measurement_extension_log.c libspdm_req_get_key_pair_info.c libspdm_req_set_key_pair_info.c + libspdm_req_slot_management.c ) diff --git a/library/spdm_requester_lib/libspdm_req_slot_management.c b/library/spdm_requester_lib/libspdm_req_slot_management.c new file mode 100644 index 00000000000..59e0c92dd0e --- /dev/null +++ b/library/spdm_requester_lib/libspdm_req_slot_management.c @@ -0,0 +1,1255 @@ +/** + * Copyright Notice: + * Copyright 2026 DMTF. All rights reserved. + * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md + **/ + +#include "internal/libspdm_requester_lib.h" + +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + +/** + * This function sends SLOT_MANAGEMENT (SupportedSubCodes) and receives SLOT_MANAGEMENT_RESP. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_id Indicates if it is a secured message protected via SPDM session. + * @param sub_code_bitmap A pointer to a 8-byte destination buffer to store the supported + * SubCodes bitmap. + **/ +static libspdm_return_t libspdm_try_slot_management_get_supported_subcodes( + libspdm_context_t *spdm_context, + const uint32_t *session_id, + uint8_t *sub_code_bitmap) +{ + libspdm_return_t status; + spdm_slot_management_request_t *spdm_request; + size_t spdm_request_size; + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + uint8_t *message; + size_t message_size; + size_t transport_header_size; + libspdm_session_info_t *session_info; + libspdm_session_state_t session_state; + spdm_slot_management_supported_subcodes_struct_t *resp_struct; + + /* -=[Check Parameters Phase]=- */ + if (libspdm_get_connection_version(spdm_context) < SPDM_MESSAGE_VERSION_14) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + + /* -=[Verify State Phase]=- */ + if (!libspdm_is_capabilities_ext_flag_supported( + spdm_context, true, 0, + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP)) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + if (spdm_context->connection_info.connection_state < LIBSPDM_CONNECTION_STATE_NEGOTIATED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + + session_info = NULL; + if (session_id != NULL) { + session_info = libspdm_get_session_info_via_session_id(spdm_context, *session_id); + if (session_info == NULL) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + session_state = libspdm_secured_message_get_session_state( + session_info->secured_message_context); + if (session_state != LIBSPDM_SESSION_STATE_ESTABLISHED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + } + + /* -=[Construct Request Phase]=- */ + transport_header_size = spdm_context->local_context.capability.transport_header_size; + status = libspdm_acquire_sender_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size + + spdm_context->local_context.capability.transport_tail_size); + spdm_request = (void *)(message + transport_header_size); + spdm_request_size = message_size - transport_header_size - + spdm_context->local_context.capability.transport_tail_size; + + LIBSPDM_ASSERT(spdm_request_size >= sizeof(spdm_slot_management_request_t)); + spdm_request->header.spdm_version = libspdm_get_connection_version (spdm_context); + spdm_request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + spdm_request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES; + spdm_request->header.param2 = 0; + /* SupportedSubCodes does not use a request structure. */ + spdm_request->mgmt_struct_offset = 0; + spdm_request->reserved = 0; + spdm_request_size = sizeof(spdm_slot_management_request_t); + + /* -=[Send Request Phase]=- */ + status = libspdm_send_spdm_request(spdm_context, session_id, spdm_request_size, spdm_request); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + libspdm_release_sender_buffer (spdm_context); + return status; + } + libspdm_release_sender_buffer (spdm_context); + spdm_request = (void *)spdm_context->last_spdm_request; + + /* -=[Receive Response Phase]=- */ + status = libspdm_acquire_receiver_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size); + spdm_response = (void *)(message); + spdm_response_size = message_size; + + status = libspdm_receive_spdm_response( + spdm_context, session_id, &spdm_response_size, (void **)&spdm_response); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + + /* -=[Validate Response Phase]=- */ + if (spdm_response_size < sizeof(spdm_message_header_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (spdm_response->header.request_response_code == SPDM_ERROR) { + status = libspdm_handle_error_response_main( + spdm_context, session_id, + &spdm_response_size, + (void **)&spdm_response, SPDM_SLOT_MANAGEMENT, SPDM_SLOT_MANAGEMENT_RESP); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + } else if (spdm_response->header.request_response_code != SPDM_SLOT_MANAGEMENT_RESP) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.spdm_version != spdm_request->header.spdm_version) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + + if (spdm_response->header.param1 != SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + + if (spdm_response_size < sizeof(spdm_slot_management_response_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + + /* MgmtStructOffset shall point at the SlotMgmtRespStruct and the struct shall fit. */ + if ((spdm_response->mgmt_struct_offset < sizeof(spdm_slot_management_response_t)) || + (spdm_response->mgmt_struct_offset + + sizeof(spdm_slot_management_supported_subcodes_struct_t) > spdm_response_size)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + + resp_struct = (void *)((uint8_t *)spdm_response + + spdm_response->mgmt_struct_offset); + if (resp_struct->resp_length < + sizeof(spdm_slot_management_supported_subcodes_struct_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + + /* -=[Process Response Phase]=- */ + libspdm_copy_mem(sub_code_bitmap, 8, + resp_struct->sub_code_bitmap, sizeof(resp_struct->sub_code_bitmap)); + + status = LIBSPDM_STATUS_SUCCESS; + + /* -=[Log Message Phase]=- */ + #if LIBSPDM_ENABLE_MSG_LOG + libspdm_append_msg_log(spdm_context, spdm_response, spdm_response_size); + #endif /* LIBSPDM_ENABLE_MSG_LOG */ + +receive_done: + libspdm_release_receiver_buffer (spdm_context); + return status; +} + +libspdm_return_t libspdm_slot_management_get_supported_subcodes(void *spdm_context, + const uint32_t *session_id, + uint8_t *sub_code_bitmap) +{ + libspdm_context_t *context; + size_t retry; + uint64_t retry_delay_time; + libspdm_return_t status; + + context = spdm_context; + context->crypto_request = false; + retry = context->retry_times; + retry_delay_time = context->retry_delay_time; + do { + status = libspdm_try_slot_management_get_supported_subcodes(context, session_id, + sub_code_bitmap); + if (status != LIBSPDM_STATUS_BUSY_PEER) { + return status; + } + + libspdm_sleep(retry_delay_time); + } while (retry-- != 0); + + return status; +} + +/** + * Send a SLOT_MANAGEMENT request with the given SubCode and optional request structure, and + * copy the SlotMgmtRespStruct from the SLOT_MANAGEMENT_RESP response into resp_struct. + * + * @param resp_struct_size On input, the capacity of resp_struct. On output, the number of + * bytes copied. + **/ +static libspdm_return_t libspdm_try_slot_management_command( + libspdm_context_t *spdm_context, + const uint32_t *session_id, + uint8_t sub_code, + const void *req_struct, + size_t req_struct_size, + uint8_t *resp_struct, + size_t *resp_struct_size) +{ + libspdm_return_t status; + spdm_slot_management_request_t *spdm_request; + size_t spdm_request_size; + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + uint8_t *message; + size_t message_size; + size_t transport_header_size; + libspdm_session_info_t *session_info; + libspdm_session_state_t session_state; + size_t mgmt_struct_size; + + /* -=[Check Parameters Phase]=- */ + if (libspdm_get_connection_version(spdm_context) < SPDM_MESSAGE_VERSION_14) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + + /* -=[Verify State Phase]=- */ + if (!libspdm_is_capabilities_ext_flag_supported( + spdm_context, true, 0, + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP)) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + if (spdm_context->connection_info.connection_state < LIBSPDM_CONNECTION_STATE_NEGOTIATED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + + session_info = NULL; + if (session_id != NULL) { + session_info = libspdm_get_session_info_via_session_id(spdm_context, *session_id); + if (session_info == NULL) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + session_state = libspdm_secured_message_get_session_state( + session_info->secured_message_context); + if (session_state != LIBSPDM_SESSION_STATE_ESTABLISHED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + } + + /* -=[Construct Request Phase]=- */ + transport_header_size = spdm_context->local_context.capability.transport_header_size; + status = libspdm_acquire_sender_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size + + spdm_context->local_context.capability.transport_tail_size); + spdm_request = (void *)(message + transport_header_size); + spdm_request_size = message_size - transport_header_size - + spdm_context->local_context.capability.transport_tail_size; + + LIBSPDM_ASSERT(spdm_request_size >= sizeof(spdm_slot_management_request_t) + req_struct_size); + spdm_request->header.spdm_version = libspdm_get_connection_version (spdm_context); + spdm_request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + spdm_request->header.param1 = sub_code; + spdm_request->header.param2 = 0; + spdm_request->reserved = 0; + if ((req_struct != NULL) && (req_struct_size != 0)) { + spdm_request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + libspdm_copy_mem((uint8_t *)spdm_request + sizeof(spdm_slot_management_request_t), + spdm_request_size - sizeof(spdm_slot_management_request_t), + req_struct, req_struct_size); + spdm_request_size = sizeof(spdm_slot_management_request_t) + req_struct_size; + } else { + spdm_request->mgmt_struct_offset = 0; + spdm_request_size = sizeof(spdm_slot_management_request_t); + } + + /* -=[Send Request Phase]=- */ + status = libspdm_send_spdm_request(spdm_context, session_id, spdm_request_size, spdm_request); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + libspdm_release_sender_buffer (spdm_context); + return status; + } + libspdm_release_sender_buffer (spdm_context); + spdm_request = (void *)spdm_context->last_spdm_request; + + /* -=[Receive Response Phase]=- */ + status = libspdm_acquire_receiver_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size); + spdm_response = (void *)(message); + spdm_response_size = message_size; + + status = libspdm_receive_spdm_response( + spdm_context, session_id, &spdm_response_size, (void **)&spdm_response); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + + /* -=[Validate Response Phase]=- */ + if (spdm_response_size < sizeof(spdm_message_header_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (spdm_response->header.request_response_code == SPDM_ERROR) { + status = libspdm_handle_error_response_main( + spdm_context, session_id, + &spdm_response_size, + (void **)&spdm_response, SPDM_SLOT_MANAGEMENT, SPDM_SLOT_MANAGEMENT_RESP); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + } else if (spdm_response->header.request_response_code != SPDM_SLOT_MANAGEMENT_RESP) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.spdm_version != spdm_request->header.spdm_version) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.param1 != sub_code) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response_size < sizeof(spdm_slot_management_response_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + + /* A MgmtStructOffset of 0 indicates the SubCode has no response structure. Otherwise it + * shall point at the SlotMgmtRespStruct and the struct shall fit. */ + if (spdm_response->mgmt_struct_offset == 0) { + mgmt_struct_size = 0; + } else { + if ((spdm_response->mgmt_struct_offset < sizeof(spdm_slot_management_response_t)) || + (spdm_response->mgmt_struct_offset > spdm_response_size)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + mgmt_struct_size = spdm_response_size - spdm_response->mgmt_struct_offset; + } + + /* -=[Process Response Phase]=- */ + if (mgmt_struct_size > *resp_struct_size) { + status = LIBSPDM_STATUS_BUFFER_TOO_SMALL; + goto receive_done; + } + if (mgmt_struct_size != 0) { + libspdm_copy_mem(resp_struct, *resp_struct_size, + (uint8_t *)spdm_response + spdm_response->mgmt_struct_offset, + mgmt_struct_size); + } + *resp_struct_size = mgmt_struct_size; + + status = LIBSPDM_STATUS_SUCCESS; + + /* -=[Log Message Phase]=- */ + #if LIBSPDM_ENABLE_MSG_LOG + libspdm_append_msg_log(spdm_context, spdm_response, spdm_response_size); + #endif /* LIBSPDM_ENABLE_MSG_LOG */ + +receive_done: + libspdm_release_receiver_buffer (spdm_context); + return status; +} + +static libspdm_return_t libspdm_slot_management_command( + libspdm_context_t *context, + const uint32_t *session_id, + uint8_t sub_code, + const void *req_struct, + size_t req_struct_size, + uint8_t *resp_struct, + size_t *resp_struct_size) +{ + size_t retry; + uint64_t retry_delay_time; + libspdm_return_t status; + size_t resp_capacity; + + context->crypto_request = false; + retry = context->retry_times; + retry_delay_time = context->retry_delay_time; + resp_capacity = *resp_struct_size; + do { + *resp_struct_size = resp_capacity; + status = libspdm_try_slot_management_command(context, session_id, sub_code, + req_struct, req_struct_size, + resp_struct, resp_struct_size); + if (status != LIBSPDM_STATUS_BUSY_PEER) { + return status; + } + + libspdm_sleep(retry_delay_time); + } while (retry-- != 0); + + return status; +} + +libspdm_return_t libspdm_slot_management_get_bank_info( + void *spdm_context, const uint32_t *session_id, + uint8_t *num_bank_elements, + spdm_slot_management_bank_element_struct_t *bank_elements) +{ + libspdm_return_t status; + uint8_t resp_buffer[sizeof(spdm_slot_management_bank_info_struct_t) + + 255 * sizeof(spdm_slot_management_bank_element_struct_t)]; + size_t resp_size; + const spdm_slot_management_bank_info_struct_t *resp_struct; + const spdm_slot_management_bank_element_struct_t *element; + uint8_t count; + uint8_t index; + + if ((num_bank_elements == NULL) || (bank_elements == NULL)) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } + + resp_size = sizeof(resp_buffer); + status = libspdm_slot_management_command(spdm_context, session_id, + SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO, + NULL, 0, resp_buffer, &resp_size); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + + if (resp_size < sizeof(spdm_slot_management_bank_info_struct_t)) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + resp_struct = (const void *)resp_buffer; + count = resp_struct->num_bank_elements; + if (sizeof(spdm_slot_management_bank_info_struct_t) + + (size_t)count * sizeof(spdm_slot_management_bank_element_struct_t) > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + if (count > *num_bank_elements) { + return LIBSPDM_STATUS_BUFFER_TOO_SMALL; + } + + element = (const void *)(resp_buffer + sizeof(spdm_slot_management_bank_info_struct_t)); + for (index = 0; index < count; index++) { + bank_elements[index] = element[index]; + } + *num_bank_elements = count; + + return LIBSPDM_STATUS_SUCCESS; +} + +libspdm_return_t libspdm_slot_management_get_bank_details( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, + uint8_t *bank_attributes, + uint32_t *asym_algo_capabilities, + uint32_t *current_asym_algo, + uint32_t *available_asym_algo, + uint32_t *pqc_asym_algo_capabilities, + uint32_t *current_pqc_asym_algo, + uint32_t *available_pqc_asym_algo, + uint16_t *num_slot_elements, + size_t *slot_elements_size, + void *slot_elements) +{ + libspdm_return_t status; + spdm_slot_management_slot_address_struct_t req_struct; + /* Bank details: header + three (length byte + uint32) PQC fields + 4 reserved + one + * SlotElement (incl. its digest) per certificate slot. */ + uint8_t resp_buffer[sizeof(spdm_slot_management_bank_details_struct_t) + + 3 * (sizeof(uint8_t) + sizeof(uint32_t)) + 4 + + SPDM_MAX_SLOT_COUNT * + (sizeof(spdm_slot_management_slot_element_struct_t) + + LIBSPDM_MAX_HASH_SIZE)]; + size_t resp_size; + const spdm_slot_management_bank_details_struct_t *resp_struct; + const uint8_t *ptr; + size_t offset; + uint8_t pqc_cap_len; + uint8_t current_pqc_len; + uint8_t available_pqc_len; + uint32_t rsp_pqc_asym_algo_capabilities; + uint32_t rsp_current_pqc_asym_algo; + uint32_t rsp_available_pqc_asym_algo; + + libspdm_zero_mem(&req_struct, sizeof(req_struct)); + req_struct.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + req_struct.bank_id = bank_id; + req_struct.slot_id = 0; + + resp_size = sizeof(resp_buffer); + status = libspdm_slot_management_command(spdm_context, session_id, + SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS, + &req_struct, sizeof(req_struct), + resp_buffer, &resp_size); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + + if (resp_size < sizeof(spdm_slot_management_bank_details_struct_t)) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + resp_struct = (const void *)resp_buffer; + + if (bank_attributes != NULL) { + *bank_attributes = resp_struct->bank_attributes; + } + if (asym_algo_capabilities != NULL) { + *asym_algo_capabilities = resp_struct->asym_algo_capabilities; + } + if (current_asym_algo != NULL) { + *current_asym_algo = resp_struct->current_asym_algo; + } + if (available_asym_algo != NULL) { + *available_asym_algo = resp_struct->available_asym_algo; + } + if (num_slot_elements != NULL) { + *num_slot_elements = resp_struct->num_slot_elements; + } + + /* Read the variable-length PQC algorithm fields to reach the SlotElement array. The + * Requester shall not assume the Responder uses any particular field size: each field is + * preceded by a length byte, only the leading bytes that fit in a uint32_t are captured + * (the rest is ignored), and the cursor always advances by the full reported length. This + * mirrors libspdm_get_key_pair_info(). */ + rsp_pqc_asym_algo_capabilities = 0; + rsp_current_pqc_asym_algo = 0; + rsp_available_pqc_asym_algo = 0; + + offset = sizeof(spdm_slot_management_bank_details_struct_t); + if (offset + 1 > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + pqc_cap_len = resp_buffer[offset]; + if (offset + 1 + pqc_cap_len > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + libspdm_copy_mem(&rsp_pqc_asym_algo_capabilities, sizeof(rsp_pqc_asym_algo_capabilities), + resp_buffer + offset + 1, + (size_t)LIBSPDM_MIN(pqc_cap_len, sizeof(uint32_t))); + offset += 1 + pqc_cap_len; + + if (offset + 1 > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + current_pqc_len = resp_buffer[offset]; + if (offset + 1 + current_pqc_len > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + libspdm_copy_mem(&rsp_current_pqc_asym_algo, sizeof(rsp_current_pqc_asym_algo), + resp_buffer + offset + 1, + (size_t)LIBSPDM_MIN(current_pqc_len, sizeof(uint32_t))); + offset += 1 + current_pqc_len; + + if (offset + 1 > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + available_pqc_len = resp_buffer[offset]; + if (offset + 1 + available_pqc_len > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + libspdm_copy_mem(&rsp_available_pqc_asym_algo, sizeof(rsp_available_pqc_asym_algo), + resp_buffer + offset + 1, + (size_t)LIBSPDM_MIN(available_pqc_len, sizeof(uint32_t))); + offset += 1 + available_pqc_len; + + /* Reserved (4 bytes) precedes the SlotElement array. */ + offset += 4; + if (offset > resp_size) { + return LIBSPDM_STATUS_INVALID_MSG_SIZE; + } + + if (pqc_asym_algo_capabilities != NULL) { + *pqc_asym_algo_capabilities = rsp_pqc_asym_algo_capabilities; + } + if (current_pqc_asym_algo != NULL) { + *current_pqc_asym_algo = rsp_current_pqc_asym_algo; + } + if (available_pqc_asym_algo != NULL) { + *available_pqc_asym_algo = rsp_available_pqc_asym_algo; + } + + if (slot_elements != NULL) { + if (slot_elements_size == NULL) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } + ptr = resp_buffer + offset; + if ((resp_size - offset) > *slot_elements_size) { + return LIBSPDM_STATUS_BUFFER_TOO_SMALL; + } + libspdm_copy_mem(slot_elements, *slot_elements_size, ptr, resp_size - offset); + *slot_elements_size = resp_size - offset; + } else if (slot_elements_size != NULL) { + *slot_elements_size = resp_size - offset; + } + + return LIBSPDM_STATUS_SUCCESS; +} + +static libspdm_return_t libspdm_try_slot_management_get_certificate_chain( + libspdm_context_t *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, + size_t *cert_chain_size, void *cert_chain) +{ + libspdm_return_t status; + spdm_slot_management_request_t *spdm_request; + spdm_slot_management_slot_address_struct_t *req_struct; + size_t spdm_request_size; + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + uint8_t *message; + size_t message_size; + size_t transport_header_size; + libspdm_session_info_t *session_info; + libspdm_session_state_t session_state; + const spdm_slot_management_get_certificate_chain_struct_t *resp_struct; + uint32_t cc_length; + + /* -=[Check Parameters Phase]=- */ + if (libspdm_get_connection_version(spdm_context) < SPDM_MESSAGE_VERSION_14) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + + /* -=[Verify State Phase]=- */ + if (!libspdm_is_capabilities_ext_flag_supported( + spdm_context, true, 0, + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP)) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + if (spdm_context->connection_info.connection_state < LIBSPDM_CONNECTION_STATE_NEGOTIATED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + + session_info = NULL; + if (session_id != NULL) { + session_info = libspdm_get_session_info_via_session_id(spdm_context, *session_id); + if (session_info == NULL) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + session_state = libspdm_secured_message_get_session_state( + session_info->secured_message_context); + if (session_state != LIBSPDM_SESSION_STATE_ESTABLISHED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + } + + /* -=[Construct Request Phase]=- */ + transport_header_size = spdm_context->local_context.capability.transport_header_size; + status = libspdm_acquire_sender_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size + + spdm_context->local_context.capability.transport_tail_size); + spdm_request = (void *)(message + transport_header_size); + spdm_request_size = message_size - transport_header_size - + spdm_context->local_context.capability.transport_tail_size; + + LIBSPDM_ASSERT(spdm_request_size >= sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)); + spdm_request->header.spdm_version = libspdm_get_connection_version (spdm_context); + spdm_request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + spdm_request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + spdm_request->header.param2 = 0; + spdm_request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + spdm_request->reserved = 0; + req_struct = (void *)((uint8_t *)spdm_request + sizeof(spdm_slot_management_request_t)); + libspdm_zero_mem(req_struct, sizeof(*req_struct)); + req_struct->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + req_struct->bank_id = bank_id; + req_struct->slot_id = slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + spdm_request_size = sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t); + + /* -=[Send Request Phase]=- */ + status = libspdm_send_spdm_request(spdm_context, session_id, spdm_request_size, spdm_request); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + libspdm_release_sender_buffer (spdm_context); + return status; + } + libspdm_release_sender_buffer (spdm_context); + spdm_request = (void *)spdm_context->last_spdm_request; + + /* -=[Receive Response Phase]=- */ + status = libspdm_acquire_receiver_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size); + spdm_response = (void *)(message); + spdm_response_size = message_size; + + status = libspdm_receive_spdm_response( + spdm_context, session_id, &spdm_response_size, (void **)&spdm_response); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + + /* -=[Validate Response Phase]=- */ + if (spdm_response_size < sizeof(spdm_message_header_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (spdm_response->header.request_response_code == SPDM_ERROR) { + status = libspdm_handle_error_response_main( + spdm_context, session_id, + &spdm_response_size, + (void **)&spdm_response, SPDM_SLOT_MANAGEMENT, SPDM_SLOT_MANAGEMENT_RESP); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + } else if (spdm_response->header.request_response_code != SPDM_SLOT_MANAGEMENT_RESP) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.spdm_version != spdm_request->header.spdm_version) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.param1 != SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response_size < sizeof(spdm_slot_management_response_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if ((spdm_response->mgmt_struct_offset < sizeof(spdm_slot_management_response_t)) || + ((size_t)spdm_response->mgmt_struct_offset + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) > spdm_response_size)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + + resp_struct = (const void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + cc_length = resp_struct->cc_length; + if ((size_t)spdm_response->mgmt_struct_offset + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) + + (size_t)cc_length > spdm_response_size) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (cc_length > *cert_chain_size) { + status = LIBSPDM_STATUS_BUFFER_TOO_SMALL; + goto receive_done; + } + + /* -=[Process Response Phase]=- */ + libspdm_copy_mem(cert_chain, *cert_chain_size, + (const uint8_t *)resp_struct + + sizeof(spdm_slot_management_get_certificate_chain_struct_t), + cc_length); + *cert_chain_size = cc_length; + + status = LIBSPDM_STATUS_SUCCESS; + + /* -=[Log Message Phase]=- */ + #if LIBSPDM_ENABLE_MSG_LOG + libspdm_append_msg_log(spdm_context, spdm_response, spdm_response_size); + #endif /* LIBSPDM_ENABLE_MSG_LOG */ + +receive_done: + libspdm_release_receiver_buffer (spdm_context); + return status; +} + +libspdm_return_t libspdm_slot_management_get_certificate_chain( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, + size_t *cert_chain_size, + void *cert_chain) +{ + libspdm_context_t *context; + size_t retry; + uint64_t retry_delay_time; + libspdm_return_t status; + size_t cert_chain_capacity; + + if ((cert_chain_size == NULL) || (cert_chain == NULL)) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } + + context = spdm_context; + context->crypto_request = false; + retry = context->retry_times; + retry_delay_time = context->retry_delay_time; + cert_chain_capacity = *cert_chain_size; + do { + *cert_chain_size = cert_chain_capacity; + status = libspdm_try_slot_management_get_certificate_chain( + context, session_id, bank_id, slot_id, cert_chain_size, cert_chain); + if (status != LIBSPDM_STATUS_BUSY_PEER) { + return status; + } + + libspdm_sleep(retry_delay_time); + } while (retry-- != 0); + + return status; +} + +libspdm_return_t libspdm_slot_management_manage_bank( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t operation, + uint32_t select_asym_algo, uint32_t select_pqc_asym_algo) +{ + uint8_t req_buffer[sizeof(spdm_slot_management_manage_bank_struct_t) + + sizeof(uint8_t) + sizeof(uint32_t)]; + spdm_slot_management_manage_bank_struct_t *req_struct; + uint8_t *ptr; + size_t resp_size; + + libspdm_zero_mem(req_buffer, sizeof(req_buffer)); + req_struct = (void *)req_buffer; + req_struct->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + req_struct->slot_address.bank_id = bank_id; + req_struct->operation = operation; + req_struct->select_asym_algo = select_asym_algo; + /* SelectPqcAsymAlgo uses a fixed 4-byte length (matching GET_KEY_PAIR_INFO). */ + ptr = req_buffer + sizeof(spdm_slot_management_manage_bank_struct_t); + *ptr = sizeof(uint32_t); + ptr += sizeof(uint8_t); + libspdm_write_uint32(ptr, select_pqc_asym_algo); + + resp_size = 0; + return libspdm_slot_management_command(spdm_context, session_id, + SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK, + req_buffer, sizeof(req_buffer), NULL, &resp_size); +} + +libspdm_return_t libspdm_slot_management_manage_slot( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t operation) +{ + spdm_slot_management_manage_slot_struct_t req_struct; + size_t resp_size; + + libspdm_zero_mem(&req_struct, sizeof(req_struct)); + req_struct.slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + req_struct.slot_address.bank_id = bank_id; + req_struct.slot_address.slot_id = slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + req_struct.operation = operation; + + resp_size = 0; + return libspdm_slot_management_command(spdm_context, session_id, + SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT, + &req_struct, sizeof(req_struct), NULL, &resp_size); +} + +static libspdm_return_t libspdm_try_slot_management_get_csr( + libspdm_context_t *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t key_pair_id, uint8_t request_attributes, + const void *requester_info, uint16_t requester_info_length, + const void *opaque_data, uint16_t opaque_data_length, + void *csr, size_t *csr_len) +{ + libspdm_return_t status; + spdm_slot_management_request_t *spdm_request; + spdm_slot_management_get_csr_struct_t *req_struct; + size_t spdm_request_size; + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + uint8_t *message; + size_t message_size; + size_t transport_header_size; + libspdm_session_info_t *session_info; + libspdm_session_state_t session_state; + const spdm_slot_management_csr_struct_t *resp_struct; + uint8_t *ptr; + uint32_t csr_length; + + /* -=[Check Parameters Phase]=- */ + if (libspdm_get_connection_version(spdm_context) < SPDM_MESSAGE_VERSION_14) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + + /* -=[Verify State Phase]=- */ + if (!libspdm_is_capabilities_ext_flag_supported( + spdm_context, true, 0, + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP)) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + if (spdm_context->connection_info.connection_state < LIBSPDM_CONNECTION_STATE_NEGOTIATED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + + session_info = NULL; + if (session_id != NULL) { + session_info = libspdm_get_session_info_via_session_id(spdm_context, *session_id); + if (session_info == NULL) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + session_state = libspdm_secured_message_get_session_state( + session_info->secured_message_context); + if (session_state != LIBSPDM_SESSION_STATE_ESTABLISHED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + } + + /* -=[Construct Request Phase]=- */ + transport_header_size = spdm_context->local_context.capability.transport_header_size; + status = libspdm_acquire_sender_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size + + spdm_context->local_context.capability.transport_tail_size); + spdm_request = (void *)(message + transport_header_size); + spdm_request_size = message_size - transport_header_size - + spdm_context->local_context.capability.transport_tail_size; + + LIBSPDM_ASSERT(spdm_request_size >= sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_get_csr_struct_t) + + requester_info_length + opaque_data_length); + spdm_request->header.spdm_version = libspdm_get_connection_version (spdm_context); + spdm_request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + spdm_request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR; + spdm_request->header.param2 = 0; + spdm_request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + spdm_request->reserved = 0; + req_struct = (void *)((uint8_t *)spdm_request + sizeof(spdm_slot_management_request_t)); + libspdm_zero_mem(req_struct, sizeof(*req_struct)); + req_struct->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + req_struct->slot_address.bank_id = bank_id; + req_struct->slot_address.slot_id = slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + req_struct->key_pair_id = key_pair_id; + req_struct->request_attributes = request_attributes; + req_struct->requester_info_length = requester_info_length; + req_struct->opaque_data_length = opaque_data_length; + ptr = (uint8_t *)req_struct + sizeof(spdm_slot_management_get_csr_struct_t); + if (requester_info_length != 0) { + libspdm_copy_mem(ptr, requester_info_length, requester_info, requester_info_length); + ptr += requester_info_length; + } + if (opaque_data_length != 0) { + libspdm_copy_mem(ptr, opaque_data_length, opaque_data, opaque_data_length); + ptr += opaque_data_length; + } + spdm_request_size = sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_get_csr_struct_t) + + requester_info_length + opaque_data_length; + + /* -=[Send Request Phase]=- */ + status = libspdm_send_spdm_request(spdm_context, session_id, spdm_request_size, spdm_request); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + libspdm_release_sender_buffer (spdm_context); + return status; + } + libspdm_release_sender_buffer (spdm_context); + spdm_request = (void *)spdm_context->last_spdm_request; + + /* -=[Receive Response Phase]=- */ + status = libspdm_acquire_receiver_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size); + spdm_response = (void *)(message); + spdm_response_size = message_size; + + status = libspdm_receive_spdm_response( + spdm_context, session_id, &spdm_response_size, (void **)&spdm_response); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + + /* -=[Validate Response Phase]=- */ + if (spdm_response_size < sizeof(spdm_message_header_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (spdm_response->header.request_response_code == SPDM_ERROR) { + status = libspdm_handle_error_response_main( + spdm_context, session_id, + &spdm_response_size, + (void **)&spdm_response, SPDM_SLOT_MANAGEMENT, SPDM_SLOT_MANAGEMENT_RESP); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + } else if (spdm_response->header.request_response_code != SPDM_SLOT_MANAGEMENT_RESP) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.spdm_version != spdm_request->header.spdm_version) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.param1 != SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response_size < sizeof(spdm_slot_management_response_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if ((spdm_response->mgmt_struct_offset < sizeof(spdm_slot_management_response_t)) || + ((size_t)spdm_response->mgmt_struct_offset + + sizeof(spdm_slot_management_csr_struct_t) > spdm_response_size)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + + resp_struct = (const void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + csr_length = resp_struct->csr_length; + if ((size_t)spdm_response->mgmt_struct_offset + + sizeof(spdm_slot_management_csr_struct_t) + (size_t)csr_length > spdm_response_size) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (csr_length > *csr_len) { + status = LIBSPDM_STATUS_BUFFER_TOO_SMALL; + goto receive_done; + } + + /* -=[Process Response Phase]=- */ + libspdm_copy_mem(csr, *csr_len, + (const uint8_t *)resp_struct + sizeof(spdm_slot_management_csr_struct_t), + csr_length); + *csr_len = csr_length; + + status = LIBSPDM_STATUS_SUCCESS; + + /* -=[Log Message Phase]=- */ + #if LIBSPDM_ENABLE_MSG_LOG + libspdm_append_msg_log(spdm_context, spdm_response, spdm_response_size); + #endif /* LIBSPDM_ENABLE_MSG_LOG */ + +receive_done: + libspdm_release_receiver_buffer (spdm_context); + return status; +} + +libspdm_return_t libspdm_slot_management_get_csr( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t key_pair_id, uint8_t request_attributes, + void *requester_info, uint16_t requester_info_length, + void *opaque_data, uint16_t opaque_data_length, + void *csr, size_t *csr_len) +{ + libspdm_context_t *context; + size_t retry; + uint64_t retry_delay_time; + libspdm_return_t status; + size_t csr_capacity; + + if ((csr_len == NULL) || (csr == NULL)) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } + + context = spdm_context; + context->crypto_request = false; + retry = context->retry_times; + retry_delay_time = context->retry_delay_time; + csr_capacity = *csr_len; + do { + *csr_len = csr_capacity; + status = libspdm_try_slot_management_get_csr( + context, session_id, bank_id, slot_id, key_pair_id, request_attributes, + requester_info, requester_info_length, opaque_data, opaque_data_length, + csr, csr_len); + if (status != LIBSPDM_STATUS_BUSY_PEER) { + return status; + } + + libspdm_sleep(retry_delay_time); + } while (retry-- != 0); + + return status; +} + +static libspdm_return_t libspdm_try_slot_management_set_certificate( + libspdm_context_t *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t cert_attributes, + const void *cert_chain, size_t cert_chain_size) +{ + libspdm_return_t status; + spdm_slot_management_request_t *spdm_request; + spdm_slot_management_set_certificate_struct_t *req_struct; + size_t spdm_request_size; + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + uint8_t *message; + size_t message_size; + size_t transport_header_size; + libspdm_session_info_t *session_info; + libspdm_session_state_t session_state; + + /* -=[Check Parameters Phase]=- */ + if (libspdm_get_connection_version(spdm_context) < SPDM_MESSAGE_VERSION_14) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + + /* -=[Verify State Phase]=- */ + if (!libspdm_is_capabilities_ext_flag_supported( + spdm_context, true, 0, + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP)) { + return LIBSPDM_STATUS_UNSUPPORTED_CAP; + } + if (spdm_context->connection_info.connection_state < LIBSPDM_CONNECTION_STATE_NEGOTIATED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + + session_info = NULL; + if (session_id != NULL) { + session_info = libspdm_get_session_info_via_session_id(spdm_context, *session_id); + if (session_info == NULL) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + session_state = libspdm_secured_message_get_session_state( + session_info->secured_message_context); + if (session_state != LIBSPDM_SESSION_STATE_ESTABLISHED) { + return LIBSPDM_STATUS_INVALID_STATE_LOCAL; + } + } + + /* -=[Construct Request Phase]=- */ + transport_header_size = spdm_context->local_context.capability.transport_header_size; + status = libspdm_acquire_sender_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size + + spdm_context->local_context.capability.transport_tail_size); + spdm_request = (void *)(message + transport_header_size); + spdm_request_size = message_size - transport_header_size - + spdm_context->local_context.capability.transport_tail_size; + + if (spdm_request_size < sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_set_certificate_struct_t) + cert_chain_size) { + libspdm_release_sender_buffer (spdm_context); + return LIBSPDM_STATUS_BUFFER_TOO_SMALL; + } + spdm_request->header.spdm_version = libspdm_get_connection_version (spdm_context); + spdm_request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + spdm_request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE; + spdm_request->header.param2 = 0; + spdm_request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + spdm_request->reserved = 0; + req_struct = (void *)((uint8_t *)spdm_request + sizeof(spdm_slot_management_request_t)); + libspdm_zero_mem(req_struct, sizeof(*req_struct)); + req_struct->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + req_struct->slot_address.bank_id = bank_id; + req_struct->slot_address.slot_id = slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + req_struct->cert_length = (uint32_t)cert_chain_size; + req_struct->cert_attributes = cert_attributes; + libspdm_copy_mem((uint8_t *)req_struct + + sizeof(spdm_slot_management_set_certificate_struct_t), + cert_chain_size, cert_chain, cert_chain_size); + spdm_request_size = sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_set_certificate_struct_t) + cert_chain_size; + + /* -=[Send Request Phase]=- */ + status = libspdm_send_spdm_request(spdm_context, session_id, spdm_request_size, spdm_request); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + libspdm_release_sender_buffer (spdm_context); + return status; + } + libspdm_release_sender_buffer (spdm_context); + spdm_request = (void *)spdm_context->last_spdm_request; + + /* -=[Receive Response Phase]=- */ + status = libspdm_acquire_receiver_buffer (spdm_context, &message_size, (void **)&message); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return status; + } + LIBSPDM_ASSERT (message_size >= transport_header_size); + spdm_response = (void *)(message); + spdm_response_size = message_size; + + status = libspdm_receive_spdm_response( + spdm_context, session_id, &spdm_response_size, (void **)&spdm_response); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + + /* -=[Validate Response Phase]=- */ + if (spdm_response_size < sizeof(spdm_message_header_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + if (spdm_response->header.request_response_code == SPDM_ERROR) { + status = libspdm_handle_error_response_main( + spdm_context, session_id, + &spdm_response_size, + (void **)&spdm_response, SPDM_SLOT_MANAGEMENT, SPDM_SLOT_MANAGEMENT_RESP); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + goto receive_done; + } + } else if (spdm_response->header.request_response_code != SPDM_SLOT_MANAGEMENT_RESP) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.spdm_version != spdm_request->header.spdm_version) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response->header.param1 != SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } + if (spdm_response_size < sizeof(spdm_slot_management_response_t)) { + status = LIBSPDM_STATUS_INVALID_MSG_SIZE; + goto receive_done; + } + + status = LIBSPDM_STATUS_SUCCESS; + + /* -=[Log Message Phase]=- */ + #if LIBSPDM_ENABLE_MSG_LOG + libspdm_append_msg_log(spdm_context, spdm_response, spdm_response_size); + #endif /* LIBSPDM_ENABLE_MSG_LOG */ + +receive_done: + libspdm_release_receiver_buffer (spdm_context); + return status; +} + +libspdm_return_t libspdm_slot_management_set_certificate( + void *spdm_context, const uint32_t *session_id, + uint8_t bank_id, uint8_t slot_id, uint8_t cert_attributes, + const void *cert_chain, size_t cert_chain_size) +{ + libspdm_context_t *context; + size_t retry; + uint64_t retry_delay_time; + libspdm_return_t status; + + if ((cert_chain == NULL) || (cert_chain_size == 0)) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } + + context = spdm_context; + context->crypto_request = false; + retry = context->retry_times; + retry_delay_time = context->retry_delay_time; + do { + status = libspdm_try_slot_management_set_certificate( + context, session_id, bank_id, slot_id, cert_attributes, + cert_chain, cert_chain_size); + if (status != LIBSPDM_STATUS_BUSY_PEER) { + return status; + } + + libspdm_sleep(retry_delay_time); + } while (retry-- != 0); + + return status; +} + +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ From ffbb7ee61abb1cd1151d354a2380dbf29d3bce00 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:29 +0800 Subject: [PATCH 05/10] spdm_responder_lib: Add SLOT_MANAGEMENT Add libspdm_get_response_slot_management, which validates the request and dispatches on the SLOT_MANAGEMENT SubCode to a dedicated per-SubCode handler: SupportedSubCodes, GetBankInfo, GetBankDetails, GetCertificateChain, ManageBank, ManageSlot, GetCSR, and SetCertificate. Other SubCodes return ERROR(UnsupportedRequest). GetCertificateChain reads the certificate chain through the HAL, and the GetBankDetails slot digest is provided by the HAL over that same chain. GetCSR and SetCertificate reuse the GET_CSR / SET_CERTIFICATE HAL hooks, passing the addressed BankID; the legacy GET_CSR / SET_CERTIFICATE responders pass the no-Bank-addressing sentinel. Register the handler in the responder dispatch table, gated by SLOT_MGMT_CAP. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- include/internal/libspdm_responder_lib.h | 6 + library/spdm_responder_lib/CMakeLists.txt | 1 + library/spdm_responder_lib/libspdm_rsp_csr.c | 3 +- .../libspdm_rsp_receive_send.c | 4 + .../libspdm_rsp_set_certificate_rsp.c | 2 + .../libspdm_rsp_slot_management.c | 1074 +++++++++++++++++ 6 files changed, 1089 insertions(+), 1 deletion(-) create mode 100644 library/spdm_responder_lib/libspdm_rsp_slot_management.c diff --git a/include/internal/libspdm_responder_lib.h b/include/internal/libspdm_responder_lib.h index d68bb759645..be0a0278e8a 100644 --- a/include/internal/libspdm_responder_lib.h +++ b/include/internal/libspdm_responder_lib.h @@ -1011,6 +1011,12 @@ libspdm_return_t libspdm_get_response_set_key_pair_info_ack(libspdm_context_t *s void *response); #endif /* LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP +libspdm_return_t libspdm_get_response_slot_management(libspdm_context_t *spdm_context, + size_t request_size, const void *request, + size_t *response_size, void *response); +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #if LIBSPDM_ENABLE_CAPABILITY_ENDPOINT_INFO_CAP libspdm_return_t libspdm_get_response_endpoint_info(libspdm_context_t *spdm_context, diff --git a/library/spdm_responder_lib/CMakeLists.txt b/library/spdm_responder_lib/CMakeLists.txt index d26c9221d70..fea3d658860 100644 --- a/library/spdm_responder_lib/CMakeLists.txt +++ b/library/spdm_responder_lib/CMakeLists.txt @@ -48,4 +48,5 @@ target_sources(spdm_responder_lib libspdm_rsp_measurement_extension_log.c libspdm_rsp_key_pair_info.c libspdm_rsp_set_key_pair_info_ack.c + libspdm_rsp_slot_management.c ) diff --git a/library/spdm_responder_lib/libspdm_rsp_csr.c b/library/spdm_responder_lib/libspdm_rsp_csr.c index 965a7fc8d38..1611d99a747 100644 --- a/library/spdm_responder_lib/libspdm_rsp_csr.c +++ b/library/spdm_responder_lib/libspdm_rsp_csr.c @@ -234,7 +234,8 @@ libspdm_return_t libspdm_get_response_csr(libspdm_context_t *spdm_context, requester_info, requester_info_length, opaque_data, opaque_data_length, &csr_len, csr_p, req_cert_model, - req_csr_tracking_tag, key_pair_id, overwrite, + req_csr_tracking_tag, key_pair_id, + LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID, overwrite, &is_busy, &unexpected_request); LIBSPDM_ASSERT(!(is_busy && unexpected_request)); diff --git a/library/spdm_responder_lib/libspdm_rsp_receive_send.c b/library/spdm_responder_lib/libspdm_rsp_receive_send.c index e2cdde1a612..a2836aa343e 100644 --- a/library/spdm_responder_lib/libspdm_rsp_receive_send.c +++ b/library/spdm_responder_lib/libspdm_rsp_receive_send.c @@ -89,6 +89,10 @@ libspdm_get_spdm_response_func libspdm_get_response_func_via_request_code(uint8_ { SPDM_SET_KEY_PAIR_INFO, libspdm_get_response_set_key_pair_info_ack }, #endif /*LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP*/ + #if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + { SPDM_SLOT_MANAGEMENT, libspdm_get_response_slot_management }, + #endif /*LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP*/ + #if LIBSPDM_ENABLE_CAPABILITY_CHUNK_CAP { SPDM_CHUNK_GET, libspdm_get_response_chunk_get}, { SPDM_CHUNK_SEND, libspdm_get_response_chunk_send}, diff --git a/library/spdm_responder_lib/libspdm_rsp_set_certificate_rsp.c b/library/spdm_responder_lib/libspdm_rsp_set_certificate_rsp.c index 0ded427c7c2..d3fa75a7f1c 100644 --- a/library/spdm_responder_lib/libspdm_rsp_set_certificate_rsp.c +++ b/library/spdm_responder_lib/libspdm_rsp_set_certificate_rsp.c @@ -219,6 +219,7 @@ libspdm_return_t libspdm_get_response_set_certificate(libspdm_context_t *spdm_co /* erase slot_id cert_chain*/ result = libspdm_write_certificate_to_nvm( spdm_context, + LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID, slot_id, NULL, 0, 0, 0, 0, &need_reset, &is_busy); if (!result) { @@ -290,6 +291,7 @@ libspdm_return_t libspdm_get_response_set_certificate(libspdm_context_t *spdm_co /* set certificate to NV*/ result = libspdm_write_certificate_to_nvm( spdm_context, + LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID, slot_id, cert_chain, cert_chain_size, spdm_context->connection_info.algorithm.base_hash_algo, diff --git a/library/spdm_responder_lib/libspdm_rsp_slot_management.c b/library/spdm_responder_lib/libspdm_rsp_slot_management.c new file mode 100644 index 00000000000..d111938d2ce --- /dev/null +++ b/library/spdm_responder_lib/libspdm_rsp_slot_management.c @@ -0,0 +1,1074 @@ +/** + * Copyright Notice: + * Copyright 2026 DMTF. All rights reserved. + * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md + **/ + +#include "internal/libspdm_responder_lib.h" + +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + +/** + * Validate a SlotAddress request structure. + * + * Per DSP0274 Table 143, the ReqLength field of a SlotAddress shall be 8 for this version of the + * specification. + * + * @retval true the SlotAddress is well-formed. + * @retval false the SlotAddress shall be rejected with ERROR(InvalidRequest). + **/ +static bool libspdm_slot_management_slot_address_valid( + const spdm_slot_management_slot_address_struct_t *slot_address) +{ + return slot_address->req_length == SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; +} + +/** + * Check whether a certificate slot management operation on the given SlotID is permitted. + * + * Per DSP0274 "Certificate slot management", for slots 1-7 these commands shall only be issued + * in a secure session or a trusted environment. (Slot 0 has no such requirement here; it is + * recommended to be managed in a trusted environment, which is outside this check.) A secure + * session is an accepted alternative to a trusted environment, matching the base SET_CERTIFICATE + * handler. + * + * @retval true the operation on slot_id is allowed. + * @retval false the operation shall be rejected with ERROR(UnexpectedRequest). + **/ +static bool libspdm_slot_management_access_allowed(libspdm_context_t *spdm_context, + uint8_t slot_id) +{ + if (slot_id == 0) { + return true; + } + if (libspdm_is_in_trusted_environment(spdm_context)) { + return true; + } + if (spdm_context->last_spdm_request_session_id_valid) { + return true; + } + return false; +} + +/** + * Return whether a SubCode value is listed in DSP0274 Table 142 (i.e. a defined SubCode). + * + * A listed-but-unadvertised SubCode is answered with UnsupportedRequest, whereas an unlisted + * (reserved) SubCode is answered with InvalidRequest, so the two cases must be distinguished. + **/ +static bool libspdm_slot_management_subcode_is_listed(uint8_t sub_code) +{ + switch (sub_code) { + case SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES: + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO: + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS: + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN: + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR: + case SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK: + case SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT: + case SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE: + return true; + default: + return false; + } +} + +/** + * Process the SLOT_MANAGEMENT SupportedSubCodes SubCode. + * + * SupportedSubCodes does not use a request structure. The response carries the bit map of + * SubCodes the Responder supports. + **/ +static libspdm_return_t libspdm_get_response_slot_management_supported_subcodes( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_supported_subcodes_struct_t *resp_struct; + uint8_t sub_code_bitmap[8]; + + spdm_request = request; + + /* SupportedSubCodes has no request structure, so per DSP0274 MgmtStructOffset shall be 0. */ + if (spdm_request->mgmt_struct_offset != 0) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + if (!libspdm_read_slot_management_supported_subcodes(spdm_context, sub_code_bitmap)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + LIBSPDM_ASSERT(*response_size >= sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_supported_subcodes_struct_t)); + libspdm_zero_mem(response, *response_size); + *response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_supported_subcodes_struct_t); + + spdm_response = response; + spdm_response->header.spdm_version = spdm_request->header.spdm_version; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = spdm_request->header.param1; + spdm_response->header.param2 = 0; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + spdm_response->reserved = 0; + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->resp_length = sizeof(spdm_slot_management_supported_subcodes_struct_t); + resp_struct->reserved = 0; + libspdm_copy_mem(resp_struct->sub_code_bitmap, sizeof(resp_struct->sub_code_bitmap), + sub_code_bitmap, sizeof(sub_code_bitmap)); + + return LIBSPDM_STATUS_SUCCESS; +} + +/** + * Process the SLOT_MANAGEMENT GetBankInfo SubCode. + * + * GetBankInfo does not use a request structure. The response carries an array of BankElements. + **/ +static libspdm_return_t libspdm_get_response_slot_management_get_bank_info( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_bank_info_struct_t *resp_struct; + spdm_slot_management_bank_element_struct_t *element; + uint8_t num_bank_elements; + size_t element_capacity; + size_t resp_struct_size; + + spdm_request = request; + + /* GetBankInfo has no request structure, so per DSP0274 MgmtStructOffset shall be 0. */ + if (spdm_request->mgmt_struct_offset != 0) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + LIBSPDM_ASSERT(*response_size >= sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_bank_info_struct_t)); + libspdm_zero_mem(response, *response_size); + + spdm_response = response; + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + element = (void *)((uint8_t *)resp_struct + + sizeof(spdm_slot_management_bank_info_struct_t)); + + /* The BankElements are written by the HAL directly into the response buffer, bounded by + * the response buffer size, so the responder does not impose a fixed maximum Bank count. + * BankID is limited to 0-239 by the specification, so at most SPDM_MAX_BANK_COUNT Banks. */ + element_capacity = (*response_size - sizeof(spdm_slot_management_response_t) - + sizeof(spdm_slot_management_bank_info_struct_t)) / + sizeof(spdm_slot_management_bank_element_struct_t); + if (element_capacity > SPDM_MAX_BANK_COUNT) { + element_capacity = SPDM_MAX_BANK_COUNT; + } + num_bank_elements = (uint8_t)element_capacity; + if (!libspdm_read_slot_management_bank_info(spdm_context, &num_bank_elements, element)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + resp_struct_size = sizeof(spdm_slot_management_bank_info_struct_t) + + (size_t)num_bank_elements * + sizeof(spdm_slot_management_bank_element_struct_t); + *response_size = sizeof(spdm_slot_management_response_t) + resp_struct_size; + + spdm_response->header.spdm_version = spdm_request->header.spdm_version; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = spdm_request->header.param1; + spdm_response->header.param2 = 0; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + spdm_response->reserved = 0; + + resp_struct->resp_length = (uint16_t)resp_struct_size; + resp_struct->reserved = 0; + resp_struct->num_bank_elements = num_bank_elements; + + return LIBSPDM_STATUS_SUCCESS; +} + +/** + * Process the SLOT_MANAGEMENT GetBankDetails SubCode. + * + * GetBankDetails uses a SlotAddress request structure. The response carries the Bank's + * algorithm fields followed by an array of SlotElements (each with its certificate digest). + **/ +static libspdm_return_t libspdm_get_response_slot_management_get_bank_details( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + spdm_slot_management_response_t *spdm_response; + const spdm_slot_management_slot_address_struct_t *slot_address; + spdm_slot_management_bank_details_struct_t *resp_struct; + spdm_slot_management_slot_element_struct_t slot_elements[SPDM_MAX_SLOT_COUNT]; + uint8_t slot_digests[SPDM_MAX_SLOT_COUNT * LIBSPDM_MAX_HASH_SIZE]; + uint32_t slot_digest_size; + uint8_t bank_id; + uint8_t bank_attributes; + uint32_t asym_algo_capabilities; + uint32_t current_asym_algo; + uint32_t available_asym_algo; + uint32_t pqc_asym_algo_capabilities; + uint32_t current_pqc_asym_algo; + uint32_t available_pqc_asym_algo; + uint8_t num_slot_elements; + uint8_t slot_index; + uint32_t hash_size; + uint8_t *ptr; + size_t resp_struct_size; + + spdm_request = request; + + /* GetBankDetails uses a SlotAddress request structure. */ + if ((spdm_request->mgmt_struct_offset < sizeof(spdm_slot_management_request_t)) || + ((size_t)spdm_request->mgmt_struct_offset + + sizeof(spdm_slot_management_slot_address_struct_t) > request_size)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + slot_address = (const void *)((const uint8_t *)spdm_request + + spdm_request->mgmt_struct_offset); + if (!libspdm_slot_management_slot_address_valid(slot_address)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + bank_id = slot_address->bank_id; + + hash_size = libspdm_get_hash_size(spdm_context->connection_info.algorithm.base_hash_algo); + + /* The slots' fixed fields are returned in slot_elements; their certificate chain digests + * are returned separately in slot_digests, one per slot at a stride of slot_digest_size. */ + num_slot_elements = SPDM_MAX_SLOT_COUNT; + slot_digest_size = hash_size; + if (!libspdm_read_slot_management_bank_details( + spdm_context, bank_id, &bank_attributes, + &asym_algo_capabilities, ¤t_asym_algo, &available_asym_algo, + &pqc_asym_algo_capabilities, ¤t_pqc_asym_algo, &available_pqc_asym_algo, + &num_slot_elements, slot_elements, &slot_digest_size, slot_digests)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + /* The PQC algorithm fields use a fixed 4-byte length (matching GET_KEY_PAIR_INFO): + * three (length byte + uint32 value) fields, followed by the 4 reserved bytes, + * precede the SlotElement array. */ + resp_struct_size = sizeof(spdm_slot_management_bank_details_struct_t) + + 3 * (sizeof(uint8_t) + sizeof(uint32_t)) + 4 + + (size_t)num_slot_elements * + (sizeof(spdm_slot_management_slot_element_struct_t) + hash_size); + + LIBSPDM_ASSERT(*response_size >= sizeof(spdm_slot_management_response_t) + resp_struct_size); + libspdm_zero_mem(response, *response_size); + *response_size = sizeof(spdm_slot_management_response_t) + resp_struct_size; + + spdm_response = response; + spdm_response->header.spdm_version = spdm_request->header.spdm_version; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = spdm_request->header.param1; + spdm_response->header.param2 = 0; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + spdm_response->reserved = 0; + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->resp_length = (uint16_t)resp_struct_size; + resp_struct->bank_id = bank_id; + resp_struct->reserved = 0; + resp_struct->num_slot_elements = num_slot_elements; + resp_struct->bank_attributes = bank_attributes; + resp_struct->reserved2 = 0; + resp_struct->asym_algo_capabilities = asym_algo_capabilities; + resp_struct->current_asym_algo = current_asym_algo; + resp_struct->available_asym_algo = available_asym_algo; + + ptr = (uint8_t *)resp_struct + sizeof(spdm_slot_management_bank_details_struct_t); + /* PqcAsymAlgoCapabilities (cap_len + value). */ + *ptr = sizeof(uint32_t); + ptr += sizeof(uint8_t); + libspdm_write_uint32(ptr, pqc_asym_algo_capabilities); + ptr += sizeof(uint32_t); + /* CurrentPqcAsymAlgo (len + value). */ + *ptr = sizeof(uint32_t); + ptr += sizeof(uint8_t); + libspdm_write_uint32(ptr, current_pqc_asym_algo); + ptr += sizeof(uint32_t); + /* AvailablePqcAsymAlgo (len + value). */ + *ptr = sizeof(uint32_t); + ptr += sizeof(uint8_t); + libspdm_write_uint32(ptr, available_pqc_asym_algo); + ptr += sizeof(uint32_t); + /* Reserved (4 bytes). */ + ptr += 4; + + for (slot_index = 0; slot_index < num_slot_elements; slot_index++) { + spdm_slot_management_slot_element_struct_t *slot_element; + + /* The HAL filled the fixed SlotElement fields; the Responder sets element_length. */ + slot_element = (void *)ptr; + libspdm_copy_mem(slot_element, sizeof(spdm_slot_management_slot_element_struct_t), + &slot_elements[slot_index], + sizeof(spdm_slot_management_slot_element_struct_t)); + slot_element->element_length = + (uint16_t)(sizeof(spdm_slot_management_slot_element_struct_t) + hash_size); + + ptr += sizeof(spdm_slot_management_slot_element_struct_t); + /* Digest of the certificate chain in this slot, returned separately by the HAL. The + * digest is over the certificate chain for the Bank's configured algorithm, the same + * chain that GetCertificateChain returns. */ + libspdm_copy_mem(ptr, hash_size, + slot_digests + (size_t)slot_index * slot_digest_size, + slot_digest_size); + ptr += hash_size; + } + + return LIBSPDM_STATUS_SUCCESS; +} + +/** + * Process the SLOT_MANAGEMENT GetCertificateChain SubCode. + * + * GetCertificateChain uses a SlotAddress request structure. The certificate chain for the + * addressed Bank+slot is read through the HAL; it need not be provisioned into the SPDM + * context. + **/ +static libspdm_return_t libspdm_get_response_slot_management_get_certificate_chain( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + spdm_slot_management_response_t *spdm_response; + const spdm_slot_management_slot_address_struct_t *slot_address; + spdm_slot_management_get_certificate_chain_struct_t *resp_struct; + uint8_t bank_id; + uint8_t slot_id; + uint8_t *cert_chain; + size_t cert_chain_size; + size_t cert_chain_capacity; + + spdm_request = request; + + /* GetCertificateChain uses a SlotAddress request structure. */ + if ((spdm_request->mgmt_struct_offset < sizeof(spdm_slot_management_request_t)) || + ((size_t)spdm_request->mgmt_struct_offset + + sizeof(spdm_slot_management_slot_address_struct_t) > request_size)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + slot_address = (const void *)((const uint8_t *)spdm_request + + spdm_request->mgmt_struct_offset); + if (!libspdm_slot_management_slot_address_valid(slot_address)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + bank_id = slot_address->bank_id; + slot_id = slot_address->slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + + /* For slots 1-7 this command shall only be issued in a secure session or a trusted + * environment. */ + if (!libspdm_slot_management_access_allowed(spdm_context, slot_id)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } + + LIBSPDM_ASSERT(*response_size >= sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_get_certificate_chain_struct_t)); + libspdm_zero_mem(response, *response_size); + + spdm_response = response; + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + cert_chain = (uint8_t *)resp_struct + + sizeof(spdm_slot_management_get_certificate_chain_struct_t); + cert_chain_capacity = *response_size - sizeof(spdm_slot_management_response_t) - + sizeof(spdm_slot_management_get_certificate_chain_struct_t); + + cert_chain_size = cert_chain_capacity; + if (!libspdm_read_slot_management_certificate_chain( + spdm_context, bank_id, slot_id, &cert_chain_size, cert_chain)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + *response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) + + cert_chain_size; + + spdm_response->header.spdm_version = spdm_request->header.spdm_version; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = spdm_request->header.param1; + spdm_response->header.param2 = 0; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + spdm_response->reserved = 0; + + resp_struct->cc_length = (uint32_t)cert_chain_size; + resp_struct->reserved = 0; + + return LIBSPDM_STATUS_SUCCESS; +} + +/** + * Emit a SLOT_MANAGEMENT_RESP that has no SubCode response structure (MgmtStructOffset = 0), + * used by the ManageBank and ManageSlot SubCodes. + **/ +static libspdm_return_t libspdm_slot_management_generate_empty_response( + libspdm_context_t *spdm_context, uint8_t spdm_version, uint8_t sub_code, + size_t *response_size, void *response) +{ + spdm_slot_management_response_t *spdm_response; + + LIBSPDM_ASSERT(*response_size >= sizeof(spdm_slot_management_response_t)); + libspdm_zero_mem(response, *response_size); + *response_size = sizeof(spdm_slot_management_response_t); + + spdm_response = response; + spdm_response->header.spdm_version = spdm_version; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = sub_code; + spdm_response->header.param2 = 0; + /* These SubCodes have no response structure. */ + spdm_response->mgmt_struct_offset = 0; + spdm_response->reserved = 0; + + return LIBSPDM_STATUS_SUCCESS; +} + +/** + * Process the SLOT_MANAGEMENT ManageBank SubCode. + * + * ManageBank uses a ManageBank request structure and has no response structure. + **/ +static libspdm_return_t libspdm_get_response_slot_management_manage_bank( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + const spdm_slot_management_manage_bank_struct_t *req_struct; + uint32_t select_asym_algo; + uint32_t select_pqc_asym_algo; + uint8_t select_pqc_asym_algo_len; + size_t fixed_size; + uint8_t bank_result; + + spdm_request = request; + + /* The fixed portion is the ManageBank structure plus the SelectPqcAsymAlgoLen byte. */ + fixed_size = sizeof(spdm_slot_management_manage_bank_struct_t) + sizeof(uint8_t); + if ((spdm_request->mgmt_struct_offset < sizeof(spdm_slot_management_request_t)) || + ((size_t)spdm_request->mgmt_struct_offset + fixed_size > request_size)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + req_struct = (const void *)((const uint8_t *)spdm_request + + spdm_request->mgmt_struct_offset); + if (!libspdm_slot_management_slot_address_valid(&req_struct->slot_address)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + select_asym_algo = req_struct->select_asym_algo; + select_pqc_asym_algo_len = + *((const uint8_t *)req_struct + sizeof(spdm_slot_management_manage_bank_struct_t)); + if ((size_t)spdm_request->mgmt_struct_offset + fixed_size + + select_pqc_asym_algo_len > request_size) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + /* The Responder shall not assume the Requester uses any particular SelectPqcAsymAlgo field + * size: copy only the leading bytes that fit in a uint32_t and ignore the rest, matching + * the SET_KEY_PAIR_INFO handling. */ + select_pqc_asym_algo = 0; + libspdm_copy_mem(&select_pqc_asym_algo, sizeof(select_pqc_asym_algo), + (const uint8_t *)req_struct + + sizeof(spdm_slot_management_manage_bank_struct_t) + sizeof(uint8_t), + (size_t)LIBSPDM_MIN(select_pqc_asym_algo_len, sizeof(uint32_t))); + + /* The only Operation defined by DSP0274 Table 145 is ConfigAlgo. */ + if (req_struct->operation != SPDM_SLOT_MANAGEMENT_MANAGE_BANK_OPERATION_CONFIG_ALGO) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + /* Per Table 145, ConfigAlgo configures the Bank for one asymmetric algorithm: the total number + * of bits set in SelectAsymAlgo and SelectPqcAsymAlgo shall be exactly one. An all-zero + * selection does not name an algorithm and is rejected. */ + if (!libspdm_onehot0(select_asym_algo) || !libspdm_onehot0(select_pqc_asym_algo) || + ((select_asym_algo != 0) == (select_pqc_asym_algo != 0))) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + bank_result = LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_OK; + if (!libspdm_write_slot_management_bank(spdm_context, req_struct->slot_address.bank_id, + req_struct->operation, + select_asym_algo, select_pqc_asym_algo, + &bank_result)) { + /* Map the HAL result to the ERROR code the specification mandates for ManageBank. */ + switch (bank_result) { + case LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_INVALID_STATE: + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_STATE, 0, + response_size, response); + case LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_RESET_REQUIRED: + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_RESET_REQUIRED, 0, + response_size, response); + default: + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + } + + return libspdm_slot_management_generate_empty_response( + spdm_context, spdm_request->header.spdm_version, spdm_request->header.param1, + response_size, response); +} + +/** + * Process the SLOT_MANAGEMENT ManageSlot SubCode. + * + * ManageSlot uses a ManageSlot request structure and has no response structure. + **/ +static libspdm_return_t libspdm_get_response_slot_management_manage_slot( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + const spdm_slot_management_manage_slot_struct_t *req_struct; + uint8_t slot_id; + bool need_reset; + bool is_busy; + + spdm_request = request; + + if ((spdm_request->mgmt_struct_offset < sizeof(spdm_slot_management_request_t)) || + ((size_t)spdm_request->mgmt_struct_offset + + sizeof(spdm_slot_management_manage_slot_struct_t) > request_size)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + req_struct = (const void *)((const uint8_t *)spdm_request + + spdm_request->mgmt_struct_offset); + if (!libspdm_slot_management_slot_address_valid(&req_struct->slot_address)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + slot_id = req_struct->slot_address.slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + + /* ManageSlot (e.g. Erase) is destructive. For slots 1-7 it shall only be issued in a secure + * session or a trusted environment. */ + if (!libspdm_slot_management_access_allowed(spdm_context, slot_id)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } + + /* The device might require a reset to complete the operation, but only if it advertises + * CERT_INSTALL_RESET_CAP. Pre-set need_reset to that capability and let the HAL hook decide, + * mirroring the SET_CERTIFICATE flow. */ + need_reset = libspdm_is_capabilities_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP); + is_busy = false; + + if (!libspdm_write_slot_management_slot(spdm_context, req_struct->slot_address.bank_id, + slot_id, req_struct->operation, + &need_reset, &is_busy)) { + if (is_busy) { + return libspdm_generate_error_response(spdm_context, SPDM_ERROR_CODE_BUSY, 0, + response_size, response); + } + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + if (libspdm_is_capabilities_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP) && need_reset) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_RESET_REQUIRED, 0, + response_size, response); + } + + return libspdm_slot_management_generate_empty_response( + spdm_context, spdm_request->header.spdm_version, spdm_request->header.param1, + response_size, response); +} + +/** + * Process the SLOT_MANAGEMENT GetCSR SubCode. + * + * GetCSR uses a GetCSR request structure and returns a CSR response structure. It reuses the + * GET_CSR HAL hook (libspdm_gen_csr), with the added ability to address a Bank. + **/ +static libspdm_return_t libspdm_get_response_slot_management_get_csr( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ +#if LIBSPDM_ENABLE_CAPABILITY_CSR_CAP + const spdm_slot_management_request_t *spdm_request; + spdm_slot_management_response_t *spdm_response; + const spdm_slot_management_get_csr_struct_t *req_struct; + spdm_slot_management_csr_struct_t *resp_struct; + bool result; + size_t req_struct_offset; + uint16_t requester_info_length; + uint16_t opaque_data_length; + uint8_t *requester_info; + uint8_t *opaque_data; + uint8_t *csr_p; + size_t csr_len; + bool need_reset; + bool is_busy; + bool unexpected_request; + uint8_t csr_tracking_tag; + uint8_t key_pair_id; + uint8_t req_cert_model; + bool overwrite; + + spdm_request = request; + + /* GetCSR uses a GetCSR request structure (SlotAddress + KeyPairID + RequestAttributes + + * RequesterInfo + OpaqueData). */ + if ((spdm_request->mgmt_struct_offset < sizeof(spdm_slot_management_request_t)) || + ((size_t)spdm_request->mgmt_struct_offset + + sizeof(spdm_slot_management_get_csr_struct_t) > request_size)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + req_struct_offset = spdm_request->mgmt_struct_offset; + req_struct = (const void *)((const uint8_t *)spdm_request + req_struct_offset); + + if (!libspdm_slot_management_slot_address_valid(&req_struct->slot_address)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + requester_info_length = req_struct->requester_info_length; + opaque_data_length = req_struct->opaque_data_length; + + if (opaque_data_length > SPDM_MAX_OPAQUE_DATA_SIZE) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if (((spdm_context->connection_info.algorithm.other_params_support & + SPDM_ALGORITHMS_OPAQUE_DATA_FORMAT_MASK) == SPDM_ALGORITHMS_OPAQUE_DATA_FORMAT_NONE) + && (opaque_data_length != 0)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if ((size_t)opaque_data_length > + request_size - req_struct_offset - sizeof(spdm_slot_management_get_csr_struct_t)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if ((size_t)requester_info_length > + request_size - req_struct_offset - sizeof(spdm_slot_management_get_csr_struct_t) - + opaque_data_length) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + requester_info = (uint8_t *)((size_t)(req_struct + 1)); + opaque_data = requester_info + requester_info_length; + if (opaque_data_length != 0) { + if (!libspdm_process_general_opaque_data_check(spdm_context, opaque_data_length, + opaque_data)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + } + if (!libspdm_verify_req_info(requester_info, requester_info_length)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + LIBSPDM_ASSERT(*response_size >= sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_csr_struct_t)); + libspdm_zero_mem(response, *response_size); + + spdm_response = response; + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + csr_p = (uint8_t *)resp_struct + sizeof(spdm_slot_management_csr_struct_t); + csr_len = *response_size - sizeof(spdm_slot_management_response_t) - + sizeof(spdm_slot_management_csr_struct_t); + + need_reset = libspdm_is_capabilities_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP); + is_busy = false; + unexpected_request = false; + + key_pair_id = req_struct->key_pair_id; + req_cert_model = + req_struct->request_attributes & SPDM_GET_CSR_REQUEST_ATTRIBUTES_CERT_MODEL_MASK; + overwrite = + (req_struct->request_attributes & SPDM_GET_CSR_REQUEST_ATTRIBUTES_OVERWRITE) != 0; + csr_tracking_tag = + (req_struct->request_attributes & SPDM_GET_CSR_REQUEST_ATTRIBUTES_CSR_TRACKING_TAG_MASK) >> + SPDM_GET_CSR_REQUEST_ATTRIBUTES_CSR_TRACKING_TAG_OFFSET; + + if ((overwrite && (csr_tracking_tag != 0)) || + ((!need_reset) && (csr_tracking_tag != 0))) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if (spdm_context->connection_info.multi_key_conn_rsp) { + if (key_pair_id == 0) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if ((req_cert_model == SPDM_CERTIFICATE_INFO_CERT_MODEL_NONE) || + (req_cert_model > SPDM_CERTIFICATE_INFO_CERT_MODEL_GENERIC_CERT)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + } else { + if ((key_pair_id != 0) || (req_cert_model != 0)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + } + + result = libspdm_gen_csr( + spdm_context, + spdm_context->connection_info.algorithm.base_hash_algo, + spdm_context->connection_info.algorithm.base_asym_algo, + spdm_context->connection_info.algorithm.pqc_asym_algo, + &need_reset, request, request_size, + requester_info, requester_info_length, + opaque_data, opaque_data_length, + &csr_len, csr_p, req_cert_model, + &csr_tracking_tag, key_pair_id, + req_struct->slot_address.bank_id, overwrite, + &is_busy, &unexpected_request); + + if (!result) { + if (is_busy) { + return libspdm_generate_error_response(spdm_context, SPDM_ERROR_CODE_BUSY, 0, + response_size, response); + } else if (unexpected_request) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } else { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + } + + if (libspdm_is_capabilities_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP) && need_reset) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_RESET_REQUIRED, csr_tracking_tag, + response_size, response); + } + + *response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_csr_struct_t) + csr_len; + + spdm_response->header.spdm_version = spdm_request->header.spdm_version; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = spdm_request->header.param1; + spdm_response->header.param2 = 0; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + spdm_response->reserved = 0; + + resp_struct->csr_length = (uint32_t)csr_len; + resp_struct->reserved = 0; + + return LIBSPDM_STATUS_SUCCESS; +#else /* LIBSPDM_ENABLE_CAPABILITY_CSR_CAP */ + return libspdm_generate_error_response( + spdm_context, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST, + SPDM_SLOT_MANAGEMENT, response_size, response); +#endif /* LIBSPDM_ENABLE_CAPABILITY_CSR_CAP */ +} + +/** + * Process the SLOT_MANAGEMENT SetCertificate SubCode. + * + * SetCertificate uses a SetCertificate request structure and has no response structure. It + * reuses the SET_CERTIFICATE HAL hook (libspdm_write_certificate_to_nvm), with the added + * ability to address a Bank. + **/ +static libspdm_return_t libspdm_get_response_slot_management_set_certificate( + libspdm_context_t *spdm_context, size_t request_size, const void *request, + size_t *response_size, void *response) +{ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP + const spdm_slot_management_request_t *spdm_request; + const spdm_slot_management_set_certificate_struct_t *req_struct; + const uint8_t *cert_chain; + uint32_t cert_length; + uint8_t bank_id; + uint8_t slot_id; + bool need_reset; + bool is_busy; + size_t req_struct_offset; + + spdm_request = request; + + if ((spdm_request->mgmt_struct_offset < sizeof(spdm_slot_management_request_t)) || + ((size_t)spdm_request->mgmt_struct_offset + + sizeof(spdm_slot_management_set_certificate_struct_t) > request_size)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + req_struct_offset = spdm_request->mgmt_struct_offset; + req_struct = (const void *)((const uint8_t *)spdm_request + req_struct_offset); + + if (!libspdm_slot_management_slot_address_valid(&req_struct->slot_address)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + bank_id = req_struct->slot_address.bank_id; + slot_id = req_struct->slot_address.slot_id & SPDM_SLOT_MANAGEMENT_SLOT_ID_MASK; + cert_length = req_struct->cert_length; + + if (slot_id >= SPDM_MAX_SLOT_COUNT) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if ((size_t)cert_length > + request_size - req_struct_offset - sizeof(spdm_slot_management_set_certificate_struct_t)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if (cert_length == 0) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + cert_chain = (const uint8_t *)(req_struct + 1); + + /* Per the spec, for slots 1-7 the certificate slot management commands shall only be issued + * in a secure session or a trusted environment. The restriction is per-slot (SlotID), and a + * secure session is an accepted alternative to a trusted environment. This matches the base + * SET_CERTIFICATE handler. */ + if (!libspdm_slot_management_access_allowed(spdm_context, slot_id)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } + + need_reset = libspdm_is_capabilities_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP); + is_busy = false; + + if (!libspdm_write_certificate_to_nvm( + spdm_context, bank_id, slot_id, cert_chain, cert_length, + spdm_context->connection_info.algorithm.base_hash_algo, + spdm_context->connection_info.algorithm.base_asym_algo, + spdm_context->connection_info.algorithm.pqc_asym_algo, + &need_reset, &is_busy)) { + if (is_busy) { + return libspdm_generate_error_response(spdm_context, SPDM_ERROR_CODE_BUSY, 0, + response_size, response); + } + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + if (libspdm_is_capabilities_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP) && need_reset) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_RESET_REQUIRED, 0, + response_size, response); + } + + return libspdm_slot_management_generate_empty_response( + spdm_context, spdm_request->header.spdm_version, spdm_request->header.param1, + response_size, response); +#else /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP */ + return libspdm_generate_error_response( + spdm_context, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST, + SPDM_SLOT_MANAGEMENT, response_size, response); +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP */ +} + +libspdm_return_t libspdm_get_response_slot_management(libspdm_context_t *spdm_context, + size_t request_size, const void *request, + size_t *response_size, void *response) +{ + const spdm_slot_management_request_t *spdm_request; + + libspdm_session_info_t *session_info; + libspdm_session_state_t session_state; + + uint8_t sub_code; + + spdm_request = request; + + /* -=[Check Parameters Phase]=- */ + LIBSPDM_ASSERT(spdm_request->header.request_response_code == SPDM_SLOT_MANAGEMENT); + + if (libspdm_get_connection_version(spdm_context) < SPDM_MESSAGE_VERSION_14) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_UNSUPPORTED_REQUEST, + SPDM_SLOT_MANAGEMENT, + response_size, response); + } + + if (spdm_request->header.spdm_version != libspdm_get_connection_version(spdm_context)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_VERSION_MISMATCH, 0, + response_size, response); + } + + if (spdm_context->response_state != LIBSPDM_RESPONSE_STATE_NORMAL) { + return libspdm_responder_handle_response_state(spdm_context, + spdm_request->header.request_response_code, + response_size, response); + } + + if (request_size < sizeof(spdm_slot_management_request_t)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + + if (spdm_context->connection_info.connection_state < + LIBSPDM_CONNECTION_STATE_NEGOTIATED) { + return libspdm_generate_error_response( + spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } + + if (spdm_context->last_spdm_request_session_id_valid) { + session_info = libspdm_get_session_info_via_session_id( + spdm_context, + spdm_context->last_spdm_request_session_id); + if (session_info == NULL) { + return libspdm_generate_error_response( + spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } + session_state = libspdm_secured_message_get_session_state( + session_info->secured_message_context); + if (session_state != LIBSPDM_SESSION_STATE_ESTABLISHED) { + return libspdm_generate_error_response( + spdm_context, + SPDM_ERROR_CODE_UNEXPECTED_REQUEST, 0, + response_size, response); + } + } + + if (!libspdm_is_capabilities_ext_flag_supported( + spdm_context, false, 0, + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP)) { + return libspdm_generate_error_response( + spdm_context, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST, + SPDM_SLOT_MANAGEMENT, response_size, response); + } + + /* -=[Dispatch on SubCode Phase]=- */ + sub_code = spdm_request->header.param1; + + /* Per DSP0274, a valid SubCode that is not present in the Responder's SupportedSubCodes + * response shall be answered with ERROR(UnsupportedRequest). Cross-check the requested + * SubCode against the advertised bitmap here. Reserved/unlisted SubCodes (bit values with no + * Table 142 entry) fall through to the switch default below, which returns InvalidRequest. */ + if (libspdm_slot_management_subcode_is_listed(sub_code)) { + uint8_t sub_code_bitmap[8]; + + if (!libspdm_read_slot_management_supported_subcodes(spdm_context, sub_code_bitmap)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } + if ((sub_code_bitmap[sub_code / 8] & (uint8_t)(1 << (sub_code % 8))) == 0) { + return libspdm_generate_error_response( + spdm_context, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST, + SPDM_SLOT_MANAGEMENT, response_size, response); + } + } + + switch (sub_code) { + case SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES: + return libspdm_get_response_slot_management_supported_subcodes( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO: + return libspdm_get_response_slot_management_get_bank_info( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS: + return libspdm_get_response_slot_management_get_bank_details( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN: + return libspdm_get_response_slot_management_get_certificate_chain( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK: + return libspdm_get_response_slot_management_manage_bank( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT: + return libspdm_get_response_slot_management_manage_slot( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR: + return libspdm_get_response_slot_management_get_csr( + spdm_context, request_size, request, response_size, response); + case SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE: + return libspdm_get_response_slot_management_set_certificate( + spdm_context, request_size, request, response_size, response); + default: + /* All SubCodes defined in Table 142 have explicit cases above, so this is reached only + * for a reserved or otherwise unlisted SubCode value. Per DSP0274, a SubCode that is not + * listed in Table 142 shall be answered with ERROR(InvalidRequest). (A valid-but- + * unadvertised SubCode is a different case, answered with UnsupportedRequest.) */ + return libspdm_generate_error_response( + spdm_context, SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } +} + +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ From abeca300168316dda7ea6def10a019e984cc3431 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:36 +0800 Subject: [PATCH 06/10] os_stub/device_secret_lib: Add slot_management HAL implementation Add a sample implementation of the SLOT_MANAGEMENT HAL hooks. The Banks are modeled per the specification: a Bank is the set of certificate slots that use one asymmetric algorithm, and each slot is associated with a key pair (KeyPairID). The Banks are derived from the key pairs reported by GET_KEY_PAIR_INFO, grouped by their configured algorithm, so a Bank can contain multiple slots whose key pairs all use the Bank's algorithm. The Bank's certificate chain and slot digest are read on demand via the per-slot certificate read, selected by the Bank's algorithm (traditional or PQC); a slot is reported only if its certificate chain is readable. Per-slot KeyPairID is reported when MULTI_KEY_CONN_RSP is set. ManageBank ConfigAlgo is an idempotent no-op for the Bank's own algorithm and rejects a different algorithm (the Bank's slots are provisioned). ManageSlot Erase marks a slot's certificate chain removed. ConfigAlgo is reported, the Selected attribute is computed from the negotiated algorithm, and the supported SubCode bit map and Bank attributes are overridable globals. The GET_CSR / SET_CERTIFICATE HAL implementations gain the bank_id parameter. Null stubs are also added/updated. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- os_stub/spdm_device_secret_lib_null/lib.c | 84 +- .../CMakeLists.txt | 1 + os_stub/spdm_device_secret_lib_sample/csr.c | 8 + .../spdm_device_secret_lib_sample/key_pair.c | 7 +- .../spdm_device_secret_lib_sample/set_cert.c | 29 +- .../slot_management.c | 983 ++++++++++++++++++ os_stub/spdm_device_secret_lib_tpm/csr.c | 5 + 7 files changed, 1106 insertions(+), 11 deletions(-) create mode 100644 os_stub/spdm_device_secret_lib_sample/slot_management.c diff --git a/os_stub/spdm_device_secret_lib_null/lib.c b/os_stub/spdm_device_secret_lib_null/lib.c index 30b7c0a3f07..ebee834aae4 100644 --- a/os_stub/spdm_device_secret_lib_null/lib.c +++ b/os_stub/spdm_device_secret_lib_null/lib.c @@ -13,6 +13,7 @@ #include "hal/library/responder/csrlib.h" #include "hal/library/responder/measlib.h" #include "hal/library/responder/key_pair_info.h" +#include "hal/library/responder/slot_mgmt.h" #include "hal/library/responder/psklib.h" #include "hal/library/responder/setcertlib.h" #include "hal/library/requester/reqasymsignlib.h" @@ -244,15 +245,19 @@ bool libspdm_psk_finish_rsp_opaque_data( } #endif /* LIBSPDM_ENABLE_CAPABILITY_PSK_CAP */ -#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP +/* The trusted-environment query is used both by SET_CERTIFICATE and by the SLOT_MANAGEMENT + * access-control checks, so it is provided when either capability is enabled. */ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP bool libspdm_is_in_trusted_environment(void *spdm_context) { return false; } +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP bool libspdm_write_certificate_to_nvm( void *spdm_context, - uint8_t slot_id, const void * cert_chain, + uint8_t bank_id, uint8_t slot_id, const void * cert_chain, size_t cert_chain_size, uint32_t base_hash_algo, uint32_t base_asym_algo, uint32_t pqc_asym_algo, bool *need_reset, bool *is_busy) @@ -279,6 +284,7 @@ bool libspdm_gen_csr( uint8_t req_cert_model, uint8_t *req_csr_tracking_tag, uint8_t req_key_pair_id, + uint8_t bank_id, bool overwrite, bool *is_busy, bool *unexpected_request) { @@ -322,7 +328,9 @@ bool libspdm_generate_event_list( } #endif /* LIBSPDM_ENABLE_CAPABILITY_EVENT_CAP */ -#if LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP +/* The key pair read functions are also used by the SLOT_MANAGEMENT feature, so they are + * compiled when either capability is enabled. */ +#if LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP uint8_t libspdm_read_total_key_pairs (void *spdm_context) { @@ -366,7 +374,8 @@ bool libspdm_read_key_pair_info( { return false; } -#endif /* LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP */ +#endif /* LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP || + * LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ #if LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP bool libspdm_write_key_pair_info( @@ -383,6 +392,73 @@ bool libspdm_write_key_pair_info( } #endif /* #if LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP +bool libspdm_read_slot_management_supported_subcodes( + void *spdm_context, + uint8_t *sub_code_bitmap) +{ + return false; +} + +bool libspdm_read_slot_management_bank_info( + void *spdm_context, + uint8_t *num_bank_elements, + spdm_slot_management_bank_element_struct_t *bank_elements) +{ + return false; +} + +bool libspdm_read_slot_management_bank_details( + void *spdm_context, + uint8_t bank_id, + uint8_t *bank_attributes, + uint32_t *asym_algo_capabilities, + uint32_t *current_asym_algo, + uint32_t *available_asym_algo, + uint32_t *pqc_asym_algo_capabilities, + uint32_t *current_pqc_asym_algo, + uint32_t *available_pqc_asym_algo, + uint8_t *num_slot_elements, + spdm_slot_management_slot_element_struct_t *slot_elements, + uint32_t *slot_digest_size, + uint8_t *slot_digests) +{ + return false; +} + +bool libspdm_read_slot_management_certificate_chain( + void *spdm_context, + uint8_t bank_id, + uint8_t slot_id, + size_t *cert_chain_size, + void *cert_chain) +{ + return false; +} + +bool libspdm_write_slot_management_bank( + void *spdm_context, + uint8_t bank_id, + uint8_t operation, + uint32_t select_asym_algo, + uint32_t select_pqc_asym_algo, + uint8_t *bank_result) +{ + return false; +} + +bool libspdm_write_slot_management_slot( + void *spdm_context, + uint8_t bank_id, + uint8_t slot_id, + uint8_t operation, + bool *need_reset, + bool *is_busy) +{ + return false; +} +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #ifdef LIBSPDM_ENABLE_CAPABILITY_ENDPOINT_INFO_CAP libspdm_return_t libspdm_generate_device_endpoint_info( void *spdm_context, diff --git a/os_stub/spdm_device_secret_lib_sample/CMakeLists.txt b/os_stub/spdm_device_secret_lib_sample/CMakeLists.txt index da8b33cefe4..cc58963565c 100644 --- a/os_stub/spdm_device_secret_lib_sample/CMakeLists.txt +++ b/os_stub/spdm_device_secret_lib_sample/CMakeLists.txt @@ -31,6 +31,7 @@ target_sources(spdm_device_secret_lib_sample read_special_cert.c set_cert.c sign.c + slot_management.c ) if ((ARCH STREQUAL "arm") OR (ARCH STREQUAL "aarch64")) diff --git a/os_stub/spdm_device_secret_lib_sample/csr.c b/os_stub/spdm_device_secret_lib_sample/csr.c index 1dcf1384c9d..a13ddac6204 100644 --- a/os_stub/spdm_device_secret_lib_sample/csr.c +++ b/os_stub/spdm_device_secret_lib_sample/csr.c @@ -254,6 +254,7 @@ bool libspdm_gen_csr( uint8_t req_cert_model, uint8_t *req_csr_tracking_tag, uint8_t req_key_pair_id, + uint8_t bank_id, bool overwrite, bool *is_busy, bool *unexpected_request) { @@ -271,6 +272,13 @@ bool libspdm_gen_csr( is_device_cert_model = (req_cert_model == SPDM_CERTIFICATE_INFO_CERT_MODEL_DEVICE_CERT); + + /* This sample generates the CSR from the connection's negotiated algorithm and does not + * maintain Bank-specific CSR state, so bank_id is not used. A device with per-Bank key + * material would select it here (bank_id == LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID means + * the legacy GET_CSR flow with no Bank addressing). */ + (void)bank_id; + csr_buffer_size = *csr_len; /* Pre-1.3 connections do not carry a CSRTrackingTag (req_csr_tracking_tag is diff --git a/os_stub/spdm_device_secret_lib_sample/key_pair.c b/os_stub/spdm_device_secret_lib_sample/key_pair.c index 77527629f11..0fb7603e471 100644 --- a/os_stub/spdm_device_secret_lib_sample/key_pair.c +++ b/os_stub/spdm_device_secret_lib_sample/key_pair.c @@ -18,7 +18,9 @@ #include "internal/libspdm_device_secret_lib.h" #include "internal/libspdm_common_lib.h" -#if LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP +/* The key pair information is also consumed by the SLOT_MANAGEMENT feature, which maps one + * Bank per key pair, so the read path is compiled when either capability is enabled. */ +#if LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP #define LIBSPDM_SUPPORTED_KEY_PAIR_ASYM_ALGO_CAP_MASK SPDM_KEY_PAIR_ASYM_ALGO_CAP_MASK @@ -462,7 +464,8 @@ bool libspdm_read_key_pair_info( return true; } -#endif /* LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP */ +#endif /* LIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP || + * LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ #if LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP diff --git a/os_stub/spdm_device_secret_lib_sample/set_cert.c b/os_stub/spdm_device_secret_lib_sample/set_cert.c index 14a19f74b3b..c9f79040cf2 100644 --- a/os_stub/spdm_device_secret_lib_sample/set_cert.c +++ b/os_stub/spdm_device_secret_lib_sample/set_cert.c @@ -28,15 +28,19 @@ bool g_in_trusted_environment = false; bool g_set_cert_is_busy = false; -#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP +/* The trusted-environment query is used both by SET_CERTIFICATE and by the SLOT_MANAGEMENT + * access-control checks, so it is provided when either capability is enabled. */ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP bool libspdm_is_in_trusted_environment(void *spdm_context) { return g_in_trusted_environment; } +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP bool libspdm_write_certificate_to_nvm( void *spdm_context, - uint8_t slot_id, const void * cert_chain, + uint8_t bank_id, uint8_t slot_id, const void * cert_chain, size_t cert_chain_size, uint32_t base_hash_algo, uint32_t base_asym_algo, uint32_t pqc_asym_algo, bool *need_reset, bool *is_busy) @@ -53,9 +57,24 @@ bool libspdm_write_certificate_to_nvm( int64_t fp_out; #endif - char file_name[] = "slot_id_0_cert_chain.der"; - /*change the file name, for example: slot_id_1_cert_chain.der*/ - file_name[8] = (char)(slot_id+'0'); + /* The certificate store is keyed by Bank and slot. The legacy SET_CERTIFICATE flow has + * no Bank addressing and passes LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID; for it the + * original per-slot file name is kept. The SLOT_MANAGEMENT SetCertificate SubCode passes + * a real BankID, which is included in the file name so each Bank has its own store. */ + char legacy_file_name[] = "slot_id_0_cert_chain.der"; + char bank_file_name[] = "bank_id_000_slot_id_0_cert_chain.der"; + char *file_name; + + if (bank_id == LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID) { + legacy_file_name[8] = (char)(slot_id + '0'); + file_name = legacy_file_name; + } else { + bank_file_name[8] = (char)('0' + (bank_id / 100) % 10); + bank_file_name[9] = (char)('0' + (bank_id / 10) % 10); + bank_file_name[10] = (char)('0' + bank_id % 10); + bank_file_name[20] = (char)(slot_id + '0'); + file_name = bank_file_name; + } /*check the input parameter*/ if ((cert_chain == NULL) ^ (cert_chain_size == 0) ) { diff --git a/os_stub/spdm_device_secret_lib_sample/slot_management.c b/os_stub/spdm_device_secret_lib_sample/slot_management.c new file mode 100644 index 00000000000..11537288b3c --- /dev/null +++ b/os_stub/spdm_device_secret_lib_sample/slot_management.c @@ -0,0 +1,983 @@ +/** + * Copyright Notice: + * Copyright 2026 DMTF. All rights reserved. + * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md + **/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "library/memlib.h" +#include "internal/libspdm_device_secret_lib.h" +#include "internal/libspdm_common_lib.h" +#include "hal/library/responder/slot_mgmt.h" + +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + +/* This sample models the SLOT_MANAGEMENT Banks per the specification's concepts: + * + * - A Bank is a set of certificate slots that all use one asymmetric algorithm. + * - A Bank can contain multiple slots. Each slot has a SlotID in the range 0-7 that is the + * value carried on the wire (DSP0274 Table 152), not a packed index. A slot is associated + * with a key pair (KeyPairID). + * - SlotID is scoped within a Bank. + * + * The Banks are derived from the key pairs reported by GET_KEY_PAIR_INFO: the key pairs are + * grouped by their currently configured asymmetric algorithm, one Bank per distinct algorithm. + * The sample certificate store provides two certificate chains per algorithm + * (bundle_responder.certchain.der for SlotID 0 and bundle_responder.certchain1.der for SlotID + * 1), so each Bank exposes those two wire SlotIDs. Slot 0 records the KeyPairID of the Bank's + * key pair; slot 1 shares the same key pair in this sample. This keeps the SLOT_MANAGEMENT + * responses consistent with the certificate store while modeling SlotID as the true wire + * value. */ + +/* Certificate slot states (DSP0274 "Certificate slots"). A slot is in exactly one of four states, + * observable through the SupportedSlotMask/ProvisionedSlotMask and the BankDetails SlotElement + * fields. This sample represents them as follows: + * + * State Wire (masks / SlotElement) Sample representation + * --------------------- -------------------------------------------- ---------------------------- + * 1. Does not exist Not in SupportedSlotMask; no SlotElement Slot absent from the Bank's + * emitted for it. slots[] table. + * 2. Exists and empty Supported=1, Provisioned=0; Slot in slots[]; no cert + * SlotAttributes.Provisioned=0, chain readable for the slot + * CertificateInfo=0, digest all-zero. (e.g. erased: zero-length + * NVM file). + * 3. Exists with key Supported=1, Provisioned=1, NOT currently reachable in + * CertificateInfo=0 (key pair associated, this sample. No cert-side + * no certificate chain). operation produces a + * key-only slot (see below). + * 4. Exists with key Supported=1, Provisioned=1, Slot in slots[]; a cert + * and cert CertificateInfo!=0; digest set. chain is readable (NVM file + * or static bundle). + * + * Whether a slot is populated (state 4) or empty (state 2) is NOT cached in slots[]; it is derived + * on demand from the certificate store, the same single source of truth the base SET_CERTIFICATE + * flow uses (libspdm_write_certificate_to_nvm): a per-(Bank,slot) NVM file if present, else the + * static bundle. A ManageSlot/SET_CERTIFICATE Erase writes a zero-length NVM file, which the read + * path reports as empty (state 2) without falling back to the static bundle. + * + * State 3 ("Exists with key") is defined by the spec but is not reachable here: GenerateKeyPair + * (SET_KEY_PAIR_INFO) requires a key pair with no associated certificate slot, and the cert-side + * commands only move a slot between "has cert" (4) and "no cert" (2) without removing the key + * (DSP0274 erase does not erase the key). Reaching state 3 would require an internal key-only + * provisioning hook, which this sample does not wire up. + * + * Note: like the base SET_CERTIFICATE flow, this sample does not update the live + * local_cert_chain_provision[] store on SET_CERTIFICATE/Erase; the model is write NVM -> reset -> + * integrator reloads on next boot. Within a single boot a change to the in-use slot is only + * observed after that reload. */ + +/* The number of wire SlotIDs each Bank exposes in this sample. The sample certificate store has + * two chains per algorithm (SlotID 0 and SlotID 1). */ +#define LIBSPDM_SAMPLE_SLOT_MANAGEMENT_SLOTS_PER_BANK 2 + +/* The supported SubCodes bit map. By default this sample supports the required SubCodes plus + * the optional GetCSR, ManageBank, ManageSlot, and SetCertificate SubCodes. The Integrator can + * override this global to advertise a different set of SubCodes. The bit position corresponds + * to the SubCode value. */ +uint8_t m_libspdm_slot_management_sub_code_bitmap[8] = { + /* byte 0: SubCodes 0x00 - 0x07, containing the required SubCodes and GetCSR (0x04). */ + (uint8_t)((1 << SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR)), + 0, 0, 0, + /* byte 4: SubCodes 0x20 - 0x27, containing ManageBank (0x20), ManageSlot (0x21), and + * SetCertificate (0x22). */ + (uint8_t)((1 << (SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK % 8)) | + (1 << (SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT % 8)) | + (1 << (SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE % 8))), + 0, 0, 0 +}; + +/* The non-Selected Bank attribute bits the sample reports. By default this sample supports + * configuration of the asymmetric algorithm for a Bank via the ManageBank SubCode, so + * ConfigAlgo is set. The Selected attribute is computed per Bank (see below) and is not part + * of this value. The Integrator can override this global to advertise different attributes. */ +uint8_t m_libspdm_slot_management_bank_attributes = + SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_CONFIG_ALGO; + +/* A single slot within a Bank: the key pair bound to it. Whether the slot is populated with a + * certificate chain is not cached here; it is derived on demand from the certificate store (NVM + * file then static bundle), the same single source of truth the base SET_CERTIFICATE flow uses. */ +typedef struct { + uint8_t slot_id; /* Wire SlotID (0-7), as carried in SLOT_MANAGEMENT messages. */ + uint8_t key_pair_id; /* KeyPairID of the key pair associated with this slot. */ +} libspdm_slot_management_sample_slot_t; + +/* A Bank: one asymmetric algorithm plus the slots (key pairs) that use that algorithm. */ +typedef struct { + uint32_t asym_algo; /* The Bank's traditional algorithm (Table 115 encoding), or 0. */ + uint32_t pqc_asym_algo; /* The Bank's PQC algorithm (Table 116 encoding), or 0. */ + uint8_t num_slots; + libspdm_slot_management_sample_slot_t slots[SPDM_MAX_SLOT_COUNT]; +} libspdm_slot_management_sample_bank_t; + +/* The maximum number of Banks this sample reports. The sample maps one Bank per distinct key + * pair algorithm, so this must be at least the number of distinct algorithms among the key + * pairs. */ +#define LIBSPDM_SAMPLE_SLOT_MANAGEMENT_BANK_COUNT 16 + +static libspdm_slot_management_sample_bank_t + m_slot_management_bank[LIBSPDM_SAMPLE_SLOT_MANAGEMENT_BANK_COUNT]; +static uint8_t m_slot_management_bank_count = 0; +static bool m_slot_management_bank_initialized = false; + +/* Build the Bank table by grouping the key pairs reported by GET_KEY_PAIR_INFO according to + * their currently configured asymmetric algorithm. */ +static void libspdm_slot_management_init_banks(void *spdm_context) +{ + uint8_t total_key_pairs; + uint8_t key_pair_index; + uint8_t bank_index; + + if (m_slot_management_bank_initialized) { + return; + } + + m_slot_management_bank_count = 0; + total_key_pairs = libspdm_read_total_key_pairs(spdm_context); + + for (key_pair_index = 0; key_pair_index < total_key_pairs; key_pair_index++) { + uint16_t capabilities; + uint16_t key_usage_capabilities; + uint16_t current_key_usage; + uint32_t asym_algo_capabilities; + uint32_t current_asym_algo; + uint32_t pqc_asym_algo_capabilities; + uint32_t current_pqc_asym_algo; + uint8_t assoc_cert_slot_mask; + libspdm_slot_management_sample_bank_t *bank; + + if (!libspdm_read_key_pair_info( + spdm_context, (uint8_t)(key_pair_index + 1), + &capabilities, &key_usage_capabilities, ¤t_key_usage, + &asym_algo_capabilities, ¤t_asym_algo, + &pqc_asym_algo_capabilities, ¤t_pqc_asym_algo, + &assoc_cert_slot_mask, NULL, NULL)) { + continue; + } + + /* A key pair with no configured algorithm does not belong to any Bank. */ + if ((current_asym_algo == 0) && (current_pqc_asym_algo == 0)) { + continue; + } + + /* Find an existing Bank for this algorithm, or create a new one. */ + bank = NULL; + for (bank_index = 0; bank_index < m_slot_management_bank_count; bank_index++) { + if ((m_slot_management_bank[bank_index].asym_algo == current_asym_algo) && + (m_slot_management_bank[bank_index].pqc_asym_algo == current_pqc_asym_algo)) { + bank = &m_slot_management_bank[bank_index]; + break; + } + } + if (bank == NULL) { + uint8_t slot_index; + + if (m_slot_management_bank_count >= LIBSPDM_SAMPLE_SLOT_MANAGEMENT_BANK_COUNT) { + continue; + } + bank = &m_slot_management_bank[m_slot_management_bank_count]; + bank->asym_algo = current_asym_algo; + bank->pqc_asym_algo = current_pqc_asym_algo; + bank->num_slots = 0; + m_slot_management_bank_count++; + + /* Expose the wire SlotIDs the sample certificate store can back (0 and 1). The + * SlotID stored here is the value carried on the wire, not a packed index. The + * Bank's key pair is associated with each slot. */ + for (slot_index = 0; + slot_index < LIBSPDM_SAMPLE_SLOT_MANAGEMENT_SLOTS_PER_BANK; + slot_index++) { + bank->slots[slot_index].slot_id = slot_index; + bank->slots[slot_index].key_pair_id = (uint8_t)(key_pair_index + 1); + bank->num_slots++; + } + } + } + + m_slot_management_bank_initialized = true; +} + +/* Return the Bank with the given bank_id, or NULL if it does not exist. */ +static libspdm_slot_management_sample_bank_t *libspdm_slot_management_get_bank( + void *spdm_context, uint8_t bank_id) +{ + libspdm_slot_management_init_banks(spdm_context); + if (bank_id >= m_slot_management_bank_count) { + return NULL; + } + return &m_slot_management_bank[bank_id]; +} + +/* Return the slot with the given Bank-local slot_id in the Bank, or NULL if it does not + * exist. */ +static libspdm_slot_management_sample_slot_t *libspdm_slot_management_get_slot( + libspdm_slot_management_sample_bank_t *bank, uint8_t slot_id) +{ + uint8_t index; + for (index = 0; index < bank->num_slots; index++) { + if (bank->slots[index].slot_id == slot_id) { + return &bank->slots[index]; + } + } + return NULL; +} + +bool libspdm_read_slot_management_supported_subcodes( + void *spdm_context, + uint8_t *sub_code_bitmap) +{ + if (sub_code_bitmap == NULL) { + return false; + } + + libspdm_copy_mem(sub_code_bitmap, 8, + m_libspdm_slot_management_sub_code_bitmap, + sizeof(m_libspdm_slot_management_sub_code_bitmap)); + + return true; +} + +/* Map a key pair AsymAlgoCapabilities bit (Table 115) to a negotiated BaseAsymAlgo value + * (Table 113), used to select the certificate file for the Bank's algorithm. RSA maps to the + * RSASSA encoding. Returns 0 if there is no match. */ +static uint32_t libspdm_slot_management_key_pair_asym_to_base_asym(uint32_t key_pair_asym_algo) +{ + switch (key_pair_asym_algo) { + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_RSA2048: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSASSA_2048; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_RSA3072: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSASSA_3072; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_RSA4096: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSASSA_4096; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_ECDSA_ECC_NIST_P256; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC384: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_ECDSA_ECC_NIST_P384; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC521: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_ECDSA_ECC_NIST_P521; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_SM2: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_SM2_ECC_SM2_P256; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_ED25519: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_EDDSA_ED25519; + case SPDM_KEY_PAIR_ASYM_ALGO_CAP_ED448: + return SPDM_ALGORITHMS_BASE_ASYM_ALGO_EDDSA_ED448; + default: + return 0; + } +} + +/* Map a negotiated BaseAsymAlgo value (Table 113) to the corresponding key pair + * AsymAlgoCapabilities bit (Table 115). Returns 0 if there is no match. */ +static uint32_t libspdm_slot_management_base_asym_to_key_pair_asym(uint32_t base_asym_algo) +{ + switch (base_asym_algo) { + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSASSA_2048: + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSAPSS_2048: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_RSA2048; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSASSA_3072: + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSAPSS_3072: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_RSA3072; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSASSA_4096: + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_RSAPSS_4096: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_RSA4096; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_ECDSA_ECC_NIST_P256: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_ECDSA_ECC_NIST_P384: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC384; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_ECDSA_ECC_NIST_P521: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC521; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_TPM_ALG_SM2_ECC_SM2_P256: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_SM2; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_EDDSA_ED25519: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_ED25519; + case SPDM_ALGORITHMS_BASE_ASYM_ALGO_EDDSA_ED448: + return SPDM_KEY_PAIR_ASYM_ALGO_CAP_ED448; + default: + return 0; + } +} + +/* Map a key pair PqcAsymAlgoCapabilities bit (Table 116) to a negotiated PqcAsymAlgo value + * (Table 117), used to select the certificate file for the Bank's PQC algorithm. The two + * encodings happen to coincide today, but the specification does not guarantee that, so this + * sample maps them explicitly rather than assuming they are equal. Returns 0 if there is no + * match. */ +static uint32_t libspdm_slot_management_key_pair_pqc_asym_to_pqc_asym( + uint32_t key_pair_pqc_asym_algo) +{ + switch (key_pair_pqc_asym_algo) { + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_44: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_ML_DSA_44; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_65: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_ML_DSA_65; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_87: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_ML_DSA_87; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_128S: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_128S; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_128S: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_128S; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_128F: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_128F; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_128F: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_128F; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_192S: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_192S; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_192S: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_192S; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_192F: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_192F; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_192F: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_192F; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_256S: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_256S; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_256S: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_256S; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_256F: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_256F; + case SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_256F: + return SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_256F; + default: + return 0; + } +} + +/* Map a negotiated PqcAsymAlgo value (Table 117) to the corresponding key pair + * PqcAsymAlgoCapabilities bit (Table 116). The two encodings happen to coincide today, but the + * specification does not guarantee that, so this sample maps them explicitly. Returns 0 if there + * is no match. */ +static uint32_t libspdm_slot_management_pqc_asym_to_key_pair_pqc_asym(uint32_t pqc_asym_algo) +{ + switch (pqc_asym_algo) { + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_ML_DSA_44: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_44; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_ML_DSA_65: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_65; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_ML_DSA_87: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_87; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_128S: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_128S; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_128S: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_128S; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_128F: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_128F; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_128F: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_128F; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_192S: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_192S; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_192S: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_192S; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_192F: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_192F; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_192F: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_192F; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_256S: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_256S; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_256S: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_256S; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHA2_256F: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHA2_256F; + case SPDM_ALGORITHMS_PQC_ASYM_ALGO_SLH_DSA_SHAKE_256F: + return SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_SLH_DSA_SHAKE_256F; + default: + return 0; + } +} + +/* A Bank is "Selected" if its algorithm matches the algorithm negotiated in the last + * successful ALGORITHMS response. Both the traditional and the PQC algorithm encodings (the + * Bank stores the key pair encoding) must be mapped to the negotiated encoding to compare. */ +static bool libspdm_slot_management_bank_is_selected( + libspdm_context_t *context, const libspdm_slot_management_sample_bank_t *bank) +{ + if (bank->pqc_asym_algo != 0) { + return (bank->pqc_asym_algo == libspdm_slot_management_pqc_asym_to_key_pair_pqc_asym( + context->connection_info.algorithm.pqc_asym_algo)); + } + if (bank->asym_algo != 0) { + return (bank->asym_algo == libspdm_slot_management_base_asym_to_key_pair_asym( + context->connection_info.algorithm.base_asym_algo)); + } + return false; +} + +/* Build the certificate-chain NVM file name for a (Bank, slot), matching the naming scheme used + * by libspdm_write_certificate_to_nvm (set_cert.c). When bank_id is + * LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID the legacy per-slot name (the one the base + * SET_CERTIFICATE flow writes) is produced; otherwise the Bank-qualified name is produced. */ +static void libspdm_slot_management_cert_nvm_file_name( + uint8_t bank_id, uint8_t slot_id, char *file_name, size_t file_name_size) +{ + if (bank_id == LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID) { + snprintf(file_name, file_name_size, "slot_id_%u_cert_chain.der", + (unsigned)(slot_id & 0xF)); + } else { + snprintf(file_name, file_name_size, "bank_id_%03u_slot_id_%u_cert_chain.der", + (unsigned)bank_id, (unsigned)(slot_id & 0xF)); + } +} + +/* Outcome of looking up a runtime-provisioned certificate chain in NVM for a (Bank, slot). */ +typedef enum { + /* No NVM file exists: the slot was never provisioned at runtime. The caller should fall back + * to the static certificate store (the factory-provisioned chain). */ + LIBSPDM_SLOT_MGMT_NVM_ABSENT, + /* A zero-length NVM file exists: the slot was erased (SET_CERTIFICATE / ManageSlot Erase writes + * a zero-length file, as libspdm_write_certificate_to_nvm does for an erase). The slot is + * unpopulated and the caller shall NOT fall back to the static store. */ + LIBSPDM_SLOT_MGMT_NVM_ERASED, + /* A non-empty NVM file exists and a full certificate chain was reconstructed from it. */ + LIBSPDM_SLOT_MGMT_NVM_FOUND, +} libspdm_slot_management_nvm_result_t; + +/* Erase any runtime-provisioned certificate chain for a (Bank, slot) by writing a zero-length NVM + * file. This matches the sample SET_CERTIFICATE erase, which calls + * libspdm_write_certificate_to_nvm(.., NULL, 0, ..) and so leaves a zero-length file. + * libspdm_slot_management_read_provisioned_cert_chain then reports the slot as erased. */ +static void libspdm_slot_management_erase_provisioned_cert_chain(uint8_t bank_id, uint8_t slot_id) +{ + char file_name[40]; + + libspdm_slot_management_cert_nvm_file_name(bank_id, slot_id, file_name, sizeof(file_name)); + libspdm_write_output_file(file_name, NULL, 0); +} + +/* Try to read a runtime-provisioned certificate chain for a (Bank, slot) from NVM, i.e. one that + * was written by a SET_CERTIFICATE (legacy, BankID-less) or a SLOT_MANAGEMENT SetCertificate + * (Bank-qualified) operation. The NVM file holds the raw certificate chain (concatenated DER + * certificates), the same input format as the static bundle files, so the full SPDM certificate + * chain (Table 39: spdm_cert_chain_t header + root hash + certs) is reconstructed here. + * + * Returns one of libspdm_slot_management_nvm_result_t: ABSENT (no file -> caller falls back to the + * static store), ERASED (zero-length file -> slot unpopulated, no fallback), or FOUND (chain + * reconstructed into *cert_chain, which the caller owns and must free). A reconstruction failure on + * a non-empty file is reported as ERASED so the caller does not serve a stale static chain for a + * slot that was provisioned with an unreadable chain. */ +static libspdm_slot_management_nvm_result_t libspdm_slot_management_read_provisioned_cert_chain( + uint32_t base_hash_algo, uint32_t base_asym_algo, uint32_t pqc_asym_algo, + uint8_t bank_id, uint8_t slot_id, void **cert_chain, size_t *cert_chain_size) +{ + char file_name[40]; + bool res; + void *file_data; + size_t file_size; + spdm_cert_chain_t *chain; + size_t chain_size; + size_t digest_size; + const uint8_t *root_cert; + size_t root_cert_len; + + *cert_chain = NULL; + *cert_chain_size = 0; + + libspdm_slot_management_cert_nvm_file_name(bank_id, slot_id, file_name, sizeof(file_name)); + + res = libspdm_read_input_file(file_name, &file_data, &file_size); + if (!res) { + /* No runtime-provisioned chain for this (Bank, slot): fall back to the static store. */ + return LIBSPDM_SLOT_MGMT_NVM_ABSENT; + } + /* A zero-length provisioned file represents an erased slot (no certificate chain). */ + if (file_size == 0) { + free(file_data); + return LIBSPDM_SLOT_MGMT_NVM_ERASED; + } + + digest_size = libspdm_get_hash_size(base_hash_algo); + + chain_size = sizeof(spdm_cert_chain_t) + digest_size + file_size; + chain = (void *)malloc(chain_size); + if (chain == NULL) { + free(file_data); + return LIBSPDM_SLOT_MGMT_NVM_ERASED; + } + chain->length = (uint32_t)chain_size; + + res = libspdm_x509_get_cert_from_cert_chain(file_data, file_size, 0, &root_cert, + &root_cert_len); + if (!res) { + free(file_data); + free(chain); + return LIBSPDM_SLOT_MGMT_NVM_ERASED; + } + res = libspdm_hash_all(base_hash_algo, root_cert, root_cert_len, (uint8_t *)(chain + 1)); + if (!res) { + free(file_data); + free(chain); + return LIBSPDM_SLOT_MGMT_NVM_ERASED; + } + libspdm_copy_mem((uint8_t *)chain + sizeof(spdm_cert_chain_t) + digest_size, + chain_size - (sizeof(spdm_cert_chain_t) + digest_size), + file_data, file_size); + free(file_data); + + *cert_chain = chain; + *cert_chain_size = chain_size; + return LIBSPDM_SLOT_MGMT_NVM_FOUND; +} + +/* Read the certificate chain for a Bank slot, selected by the Bank's asymmetric algorithm and + * the Bank-local SlotID. The caller takes ownership of *cert_chain and must free it. + * + * Per DSP0274, the slots in the Bank selected during algorithm negotiation are the same slots + * that GET_CERTIFICATE/SET_CERTIFICATE operate on, so a runtime SET_CERTIFICATE (or SLOT_MANAGEMENT + * SetCertificate) must be visible here. The lookup therefore prefers a runtime-provisioned chain + * in NVM and only falls back to the static certificate store when none has been provisioned: + * 1. the Bank-qualified NVM file (SLOT_MANAGEMENT SetCertificate for this Bank), + * 2. for the Selected Bank, the legacy NVM file (base SET_CERTIFICATE, which has no BankID), + * 3. the static bundle for the Bank's algorithm. */ +static bool libspdm_slot_management_read_slot_cert_chain( + libspdm_context_t *context, const libspdm_slot_management_sample_bank_t *bank, + const libspdm_slot_management_sample_slot_t *slot, + void **cert_chain, size_t *cert_chain_size) +{ + uint32_t base_hash_algo; + uint32_t base_asym_algo = 0; + uint32_t pqc_asym_algo = 0; + uint8_t bank_id; + libspdm_slot_management_nvm_result_t nvm_result; + + *cert_chain = NULL; + *cert_chain_size = 0; + + base_hash_algo = context->connection_info.algorithm.base_hash_algo; + + /* Map the Bank's stored key-pair algorithm encoding to the negotiated encoding used by the + * certificate read APIs. */ + if (bank->pqc_asym_algo != 0) { + pqc_asym_algo = libspdm_slot_management_key_pair_pqc_asym_to_pqc_asym(bank->pqc_asym_algo); + if (pqc_asym_algo == 0) { + return false; + } + } else if (bank->asym_algo != 0) { + base_asym_algo = libspdm_slot_management_key_pair_asym_to_base_asym(bank->asym_algo); + if (base_asym_algo == 0) { + return false; + } + } else { + return false; + } + + /* The Bank index is the BankID used to qualify the NVM file name. */ + bank_id = (uint8_t)(bank - m_slot_management_bank); + + /* 1. Bank-qualified runtime-provisioned chain (SLOT_MANAGEMENT SetCertificate). FOUND returns + * the chain; ERASED reports the slot unpopulated (no fallback); ABSENT tries the next source. */ + nvm_result = libspdm_slot_management_read_provisioned_cert_chain( + base_hash_algo, base_asym_algo, pqc_asym_algo, bank_id, slot->slot_id, + cert_chain, cert_chain_size); + if (nvm_result == LIBSPDM_SLOT_MGMT_NVM_FOUND) { + return true; + } + if (nvm_result == LIBSPDM_SLOT_MGMT_NVM_ERASED) { + return false; + } + + /* 2. For the Selected Bank, the legacy SET_CERTIFICATE chain (written with no BankID) is the + * in-use slot's chain and must be reflected here. */ + if (libspdm_slot_management_bank_is_selected(context, bank)) { + nvm_result = libspdm_slot_management_read_provisioned_cert_chain( + base_hash_algo, base_asym_algo, pqc_asym_algo, + LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID, slot->slot_id, + cert_chain, cert_chain_size); + if (nvm_result == LIBSPDM_SLOT_MGMT_NVM_FOUND) { + return true; + } + if (nvm_result == LIBSPDM_SLOT_MGMT_NVM_ERASED) { + return false; + } + } + + /* 3. Fall back to the static certificate store for the Bank's algorithm (base_asym_algo / + * pqc_asym_algo were already mapped to the negotiated encoding above). */ + if (pqc_asym_algo != 0) { + return libspdm_read_pqc_responder_public_certificate_chain_per_slot( + slot->slot_id, base_hash_algo, pqc_asym_algo, + cert_chain, cert_chain_size, NULL, NULL); + } + if (base_asym_algo != 0) { + return libspdm_read_responder_public_certificate_chain_per_slot( + slot->slot_id, base_hash_algo, base_asym_algo, + cert_chain, cert_chain_size, NULL, NULL); + } + + return false; +} + +bool libspdm_read_slot_management_bank_info( + void *spdm_context, + uint8_t *num_bank_elements, + spdm_slot_management_bank_element_struct_t *bank_elements) +{ + libspdm_context_t *context; + uint8_t bank_index; + + if ((num_bank_elements == NULL) || (bank_elements == NULL)) { + return false; + } + + context = spdm_context; + libspdm_slot_management_init_banks(context); + if (*num_bank_elements < m_slot_management_bank_count) { + return false; + } + + for (bank_index = 0; bank_index < m_slot_management_bank_count; bank_index++) { + const libspdm_slot_management_sample_bank_t *bank = + &m_slot_management_bank[bank_index]; + uint8_t slot_mask = 0; + uint8_t index; + + for (index = 0; index < bank->num_slots; index++) { + void *cert_chain; + size_t cert_chain_size; + + /* A slot exists in the Bank only if its certificate chain is actually readable + * for the Bank's algorithm (the build may not support every algorithm). */ + if (libspdm_slot_management_read_slot_cert_chain( + context, bank, &bank->slots[index], &cert_chain, &cert_chain_size)) { + slot_mask |= (uint8_t)(1 << bank->slots[index].slot_id); + free(cert_chain); + } + } + + bank_elements[bank_index].element_length = SPDM_SLOT_MANAGEMENT_BANK_ELEMENT_LENGTH; + bank_elements[bank_index].bank_id = bank_index; + bank_elements[bank_index].slot_mask = slot_mask; + /* This sample does not allow slots to be modified by the Requester. */ + bank_elements[bank_index].modifiable_slot_mask = 0; + } + + *num_bank_elements = m_slot_management_bank_count; + + return true; +} + +bool libspdm_read_slot_management_bank_details( + void *spdm_context, + uint8_t bank_id, + uint8_t *bank_attributes, + uint32_t *asym_algo_capabilities, + uint32_t *current_asym_algo, + uint32_t *available_asym_algo, + uint32_t *pqc_asym_algo_capabilities, + uint32_t *current_pqc_asym_algo, + uint32_t *available_pqc_asym_algo, + uint8_t *num_slot_elements, + spdm_slot_management_slot_element_struct_t *slot_elements, + uint32_t *slot_digest_size, + uint8_t *slot_digests) +{ + libspdm_context_t *context; + libspdm_slot_management_sample_bank_t *bank; + uint8_t slot_index; + uint8_t slot_count; + uint32_t hash_size; + + if ((bank_attributes == NULL) || (asym_algo_capabilities == NULL) || + (current_asym_algo == NULL) || (available_asym_algo == NULL) || + (pqc_asym_algo_capabilities == NULL) || (current_pqc_asym_algo == NULL) || + (available_pqc_asym_algo == NULL) || (num_slot_elements == NULL) || + (slot_elements == NULL) || (slot_digest_size == NULL) || (slot_digests == NULL)) { + return false; + } + + context = spdm_context; + bank = libspdm_slot_management_get_bank(context, bank_id); + if (bank == NULL) { + return false; + } + + /* The Bank's currently configured algorithm. */ + *current_asym_algo = bank->asym_algo; + *current_pqc_asym_algo = bank->pqc_asym_algo; + + /* The Bank attributes are taken from the (overridable) global, plus the Selected bit which + * is computed per Bank. */ + *bank_attributes = m_libspdm_slot_management_bank_attributes; + if (libspdm_slot_management_bank_is_selected(context, bank)) { + *bank_attributes |= SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_SELECTED; + } else { + *bank_attributes &= ~SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_SELECTED; + } + + /* Per the specification, when ConfigAlgo is 0 the Responder shall set the *Capabilities and + * Available* algorithm fields to 0. When ConfigAlgo is 1, this sample reports the Bank's + * own algorithm as the only supported and available algorithm. */ + if ((*bank_attributes & SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_CONFIG_ALGO) == 0) { + *asym_algo_capabilities = 0; + *available_asym_algo = 0; + *pqc_asym_algo_capabilities = 0; + *available_pqc_asym_algo = 0; + } else { + *asym_algo_capabilities = bank->asym_algo; + *available_asym_algo = bank->asym_algo; + *pqc_asym_algo_capabilities = bank->pqc_asym_algo; + *available_pqc_asym_algo = bank->pqc_asym_algo; + } + + hash_size = libspdm_get_hash_size(context->connection_info.algorithm.base_hash_algo); + + slot_count = 0; + for (slot_index = 0; slot_index < bank->num_slots; slot_index++) { + const libspdm_slot_management_sample_slot_t *slot = &bank->slots[slot_index]; + spdm_slot_management_slot_element_struct_t *slot_element; + uint8_t *digest; + void *cert_chain; + size_t cert_chain_size; + + /* A slot is reported as populated only if its certificate chain is readable for the Bank's + * algorithm. An erased slot (zero-length NVM file) and an unreadable slot both report no + * chain, so they are skipped, matching the SlotMask from GetBankInfo. */ + if (!libspdm_slot_management_read_slot_cert_chain( + context, bank, slot, &cert_chain, &cert_chain_size)) { + continue; + } + if (slot_count >= *num_slot_elements) { + free(cert_chain); + return false; + } + + /* Fill the fixed SlotElement fields (the Responder sets element_length). */ + slot_element = &slot_elements[slot_count]; + slot_element->element_length = 0; + slot_element->slot_id = slot->slot_id; + slot_element->reserved = 0; + slot_element->slot_attributes = SPDM_SLOT_MANAGEMENT_SLOT_ATTRIBUTE_PROVISIONED; + /* The KeyPairID of the key pair associated with this slot. Per Table 152 this is only + * populated when MULTI_KEY_CONN_RSP is true; otherwise it is 0. Reporting distinct + * KeyPairIDs for slots in the same Bank is how the multi-key feature is shown. */ + if (context->connection_info.multi_key_conn_rsp) { + slot_element->key_pair_id = slot->key_pair_id; + } else { + slot_element->key_pair_id = 0; + } + slot_element->certificate_info = 0; + slot_element->reserved2 = 0; + slot_element->key_usage = 0; + slot_element->reserved3 = 0; + slot_element->slot_size = (uint32_t)cert_chain_size; + + /* The digest of the slot's certificate chain, returned separately at a stride of + * *slot_digest_size (the negotiated hash size). */ + digest = slot_digests + (size_t)slot_count * (*slot_digest_size); + if (!libspdm_hash_all(context->connection_info.algorithm.base_hash_algo, + cert_chain, cert_chain_size, digest)) { + free(cert_chain); + return false; + } + free(cert_chain); + slot_count++; + } + + *num_slot_elements = slot_count; + *slot_digest_size = hash_size; + + return true; +} + +bool libspdm_read_slot_management_certificate_chain( + void *spdm_context, + uint8_t bank_id, + uint8_t slot_id, + size_t *cert_chain_size, + void *cert_chain) +{ + libspdm_context_t *context; + libspdm_slot_management_sample_bank_t *bank; + libspdm_slot_management_sample_slot_t *slot; + void *slot_cert_chain; + size_t slot_cert_chain_size; + + if ((cert_chain_size == NULL) || (cert_chain == NULL)) { + return false; + } + + context = spdm_context; + bank = libspdm_slot_management_get_bank(context, bank_id); + if (bank == NULL) { + return false; + } + slot = libspdm_slot_management_get_slot(bank, slot_id); + if (slot == NULL) { + return false; + } + + /* The certificate chain is selected by the Bank's algorithm and the Bank-local SlotID. It + * is read on demand and does not need to be provisioned into the SPDM context. */ + if (!libspdm_slot_management_read_slot_cert_chain( + context, bank, slot, &slot_cert_chain, &slot_cert_chain_size)) { + return false; + } + + if (slot_cert_chain_size > *cert_chain_size) { + free(slot_cert_chain); + return false; + } + + libspdm_copy_mem(cert_chain, *cert_chain_size, slot_cert_chain, slot_cert_chain_size); + *cert_chain_size = slot_cert_chain_size; + + free(slot_cert_chain); + + return true; +} + +bool libspdm_write_slot_management_bank( + void *spdm_context, + uint8_t bank_id, + uint8_t operation, + uint32_t select_asym_algo, + uint32_t select_pqc_asym_algo, + uint8_t *bank_result) +{ + libspdm_context_t *context; + libspdm_slot_management_sample_bank_t *bank; + uint8_t index; + + context = spdm_context; + + if (bank_result != NULL) { + *bank_result = LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_INVALID; + } + + bank = libspdm_slot_management_get_bank(spdm_context, bank_id); + if (bank == NULL) { + return false; + } + + if (operation != SPDM_SLOT_MANAGEMENT_MANAGE_BANK_OPERATION_CONFIG_ALGO) { + return false; + } + + /* At most one asymmetric algorithm (traditional or PQC) may be selected. */ + if ((select_asym_algo != 0) && (select_pqc_asym_algo != 0)) { + return false; + } + + /* Selecting the Bank's existing algorithm is an idempotent no-op and always succeeds. */ + if ((select_asym_algo == bank->asym_algo) && + (select_pqc_asym_algo == bank->pqc_asym_algo)) { + if (bank_result != NULL) { + *bank_result = LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_OK; + } + return true; + } + + /* Reconfiguring the Bank to a different algorithm requires its slots to be unprovisioned. A + * slot is provisioned until its certificate chain is removed (a ManageSlot Erase writes a + * zero-length NVM file), so any slot whose chain is still readable blocks the reconfiguration. + * Per the specification, that condition is reported as InvalidState. */ + for (index = 0; index < bank->num_slots; index++) { + void *cert_chain; + size_t cert_chain_size; + + if (libspdm_slot_management_read_slot_cert_chain( + context, bank, &bank->slots[index], &cert_chain, &cert_chain_size)) { + free(cert_chain); + if (bank_result != NULL) { + *bank_result = LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_INVALID_STATE; + } + return false; + } + } + + /* The Bank is unprovisioned, so its algorithm may be reconfigured. Accept only an algorithm + * the device can actually back, i.e. one that maps to a certificate the sample store can + * provide. Exactly one of select_asym_algo / select_pqc_asym_algo is non-zero here. */ + if (select_pqc_asym_algo != 0) { + if (libspdm_slot_management_key_pair_pqc_asym_to_pqc_asym(select_pqc_asym_algo) == 0) { + return false; + } + bank->asym_algo = 0; + bank->pqc_asym_algo = select_pqc_asym_algo; + } else if (select_asym_algo != 0) { + if (libspdm_slot_management_key_pair_asym_to_base_asym(select_asym_algo) == 0) { + return false; + } + bank->asym_algo = select_asym_algo; + bank->pqc_asym_algo = 0; + } else { + /* Neither algorithm selected: nothing to configure. */ + return false; + } + + if (bank_result != NULL) { + *bank_result = LIBSPDM_SLOT_MANAGEMENT_BANK_RESULT_OK; + } + return true; +} + +bool libspdm_write_slot_management_slot( + void *spdm_context, + uint8_t bank_id, + uint8_t slot_id, + uint8_t operation, + bool *need_reset, + bool *is_busy) +{ + libspdm_context_t *context; + libspdm_slot_management_sample_bank_t *bank; + libspdm_slot_management_sample_slot_t *slot; + + context = spdm_context; + + if (is_busy != NULL) { + *is_busy = false; + } + /* Mirror the sample SET_CERTIFICATE behavior: the Responder pre-sets *need_reset to whether it + * advertises CERT_INSTALL_RESET_CAP, and this sample leaves it unchanged. So the Erase requires + * a reset to complete exactly when CERT_INSTALL_RESET_CAP is advertised; otherwise it completes + * immediately. */ + + /* Defense in depth: ManageSlot is destructive, so this sample enforces the access-control + * rule for slots 1-7 (DSP0274 "Certificate slot management") here as well as in the + * Responder. Slots 1-7 may only be managed in a trusted environment or a secure session; a + * secure session is an accepted alternative to a trusted environment. */ + if (slot_id != 0) { + if (!libspdm_is_in_trusted_environment(spdm_context) && + !context->last_spdm_request_session_id_valid) { + return false; + } + } + + bank = libspdm_slot_management_get_bank(spdm_context, bank_id); + if (bank == NULL) { + return false; + } + slot = libspdm_slot_management_get_slot(bank, slot_id); + if (slot == NULL) { + return false; + } + + if (operation != SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE) { + return false; + } + + /* Erase: remove the slot's certificate chain by writing a zero-length NVM file, exactly as the + * base SET_CERTIFICATE erase does (libspdm_write_certificate_to_nvm with a NULL chain). + * Subsequent GetBankInfo/GetBankDetails/GetCertificateChain for this (Bank, slot) read no chain + * and report it as unpopulated. Per DSP0274, the slots in the Selected Bank are the same slots + * that GET_CERTIFICATE operates on, so for the Selected Bank also erase the BankID-less legacy + * file the base SET_CERTIFICATE flow uses, keeping the two views consistent. */ + libspdm_slot_management_erase_provisioned_cert_chain(bank_id, slot_id); + if (libspdm_slot_management_bank_is_selected(context, bank)) { + libspdm_slot_management_erase_provisioned_cert_chain( + LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID, slot_id); + } + + return true; +} + +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ diff --git a/os_stub/spdm_device_secret_lib_tpm/csr.c b/os_stub/spdm_device_secret_lib_tpm/csr.c index 1abbd0c19ea..2a814ea91ec 100644 --- a/os_stub/spdm_device_secret_lib_tpm/csr.c +++ b/os_stub/spdm_device_secret_lib_tpm/csr.c @@ -160,6 +160,7 @@ bool libspdm_gen_csr( uint8_t req_cert_model, uint8_t *req_csr_tracking_tag, uint8_t req_key_pair_id, + uint8_t bank_id, bool overwrite, bool *is_busy, bool *unexpected_request) { @@ -177,6 +178,10 @@ bool libspdm_gen_csr( is_device_cert_model = (req_cert_model == SPDM_CERTIFICATE_INFO_CERT_MODEL_DEVICE_CERT); + + /* This sample does not maintain Bank-specific CSR state, so bank_id is not used. */ + (void)bank_id; + csr_buffer_size = *csr_len; /* Pre-1.3 connections do not carry a CSRTrackingTag (req_csr_tracking_tag is From 2682bb30d997d9832f835302dd5895a633f3453f Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:40 +0800 Subject: [PATCH 07/10] unit_test/test_spdm_requester: Add SLOT_MANAGEMENT tests Add requester tests for the SLOT_MANAGEMENT SubCodes: SupportedSubCodes (success and ERROR paths), GetBankInfo, GetBankDetails, GetCertificateChain, ManageBank, ManageSlot, GetCSR, and SetCertificate. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- unit_test/test_spdm_requester/CMakeLists.txt | 1 + .../test_spdm_requester/slot_management.c | 746 ++++++++++++++++++ .../test_spdm_requester/test_spdm_requester.c | 10 + 3 files changed, 757 insertions(+) create mode 100644 unit_test/test_spdm_requester/slot_management.c diff --git a/unit_test/test_spdm_requester/CMakeLists.txt b/unit_test/test_spdm_requester/CMakeLists.txt index f1d158f1051..f0e0114d749 100644 --- a/unit_test/test_spdm_requester/CMakeLists.txt +++ b/unit_test/test_spdm_requester/CMakeLists.txt @@ -50,6 +50,7 @@ target_sources(test_spdm_requester vendor_defined_request.c get_key_pair_info.c set_key_pair_info.c + slot_management.c get_endpoint_info.c send_event.c error_test/get_version_err.c diff --git a/unit_test/test_spdm_requester/slot_management.c b/unit_test/test_spdm_requester/slot_management.c new file mode 100644 index 00000000000..661cacbd775 --- /dev/null +++ b/unit_test/test_spdm_requester/slot_management.c @@ -0,0 +1,746 @@ +/** + * Copyright Notice: + * Copyright 2026 DMTF. All rights reserved. + * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md + **/ + +#include "spdm_unit_test.h" +#include "internal/libspdm_requester_lib.h" +#include "internal/libspdm_secured_message_lib.h" + +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + +static libspdm_return_t send_message( + void *spdm_context, size_t request_size, const void *request, uint64_t timeout) +{ + libspdm_test_context_t *spdm_test_context; + + spdm_test_context = libspdm_get_test_context(); + switch (spdm_test_context->case_id) { + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: + case 0x8: + case 0x9: + case 0xA: + case 0xB: + return LIBSPDM_STATUS_SUCCESS; + default: + return LIBSPDM_STATUS_SEND_FAIL; + } +} + +static libspdm_return_t receive_message( + void *spdm_context, size_t *response_size, void **response, uint64_t timeout) +{ + libspdm_test_context_t *spdm_test_context; + + spdm_test_context = libspdm_get_test_context(); + switch (spdm_test_context->case_id) { + case 0x1: { + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_supported_subcodes_struct_t *resp_struct; + size_t spdm_response_size; + size_t transport_header_size; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_supported_subcodes_struct_t); + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES; + spdm_response->header.param2 = 0; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + spdm_response->reserved = 0; + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->resp_length = + sizeof(spdm_slot_management_supported_subcodes_struct_t); + resp_struct->sub_code_bitmap[0] = + (uint8_t)((1 << SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS) | + (1 << SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN)); + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x2: { + spdm_error_response_t *spdm_response; + size_t spdm_response_size; + size_t transport_header_size; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_error_response_t); + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_ERROR; + spdm_response->header.param1 = SPDM_ERROR_CODE_UNSUPPORTED_REQUEST; + spdm_response->header.param2 = SPDM_SLOT_MANAGEMENT; + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x3: { + /* GetBankInfo: two BankElements. */ + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_bank_info_struct_t *resp_struct; + spdm_slot_management_bank_element_struct_t *element; + size_t spdm_response_size; + size_t transport_header_size; + size_t resp_struct_size; + + resp_struct_size = sizeof(spdm_slot_management_bank_info_struct_t) + + 2 * sizeof(spdm_slot_management_bank_element_struct_t); + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t) + resp_struct_size; + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->resp_length = (uint16_t)resp_struct_size; + resp_struct->num_bank_elements = 2; + element = (void *)((uint8_t *)resp_struct + + sizeof(spdm_slot_management_bank_info_struct_t)); + element[0].element_length = SPDM_SLOT_MANAGEMENT_BANK_ELEMENT_LENGTH; + element[0].bank_id = 0; + element[0].slot_mask = 0x01; + element[1].element_length = SPDM_SLOT_MANAGEMENT_BANK_ELEMENT_LENGTH; + element[1].bank_id = 1; + element[1].slot_mask = 0x01; + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x4: { + /* GetBankDetails: one SlotElement. The PQC fields deliberately use non-uniform lengths + * (PqcAsymAlgoCapabilities = 4 bytes, CurrentPqcAsymAlgo = 4 bytes, + * AvailablePqcAsymAlgo = 8 bytes) so the test exercises a Requester that does not assume + * the Responder uses a 4-byte field. */ + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_bank_details_struct_t *resp_struct; + spdm_slot_management_slot_element_struct_t *slot_element; + uint8_t *ptr; + size_t spdm_response_size; + size_t transport_header_size; + size_t resp_struct_size; + uint32_t hash_size; + const uint8_t available_pqc_len = 8; + + hash_size = libspdm_get_hash_size(m_libspdm_use_hash_algo); + resp_struct_size = sizeof(spdm_slot_management_bank_details_struct_t) + + (sizeof(uint8_t) + sizeof(uint32_t)) + + (sizeof(uint8_t) + sizeof(uint32_t)) + + (sizeof(uint8_t) + available_pqc_len) + 4 + + (sizeof(spdm_slot_management_slot_element_struct_t) + hash_size); + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t) + resp_struct_size; + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->resp_length = (uint16_t)resp_struct_size; + resp_struct->bank_id = 0; + resp_struct->num_slot_elements = 1; + resp_struct->bank_attributes = SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_SELECTED; + resp_struct->current_asym_algo = SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256; + + ptr = (uint8_t *)resp_struct + sizeof(spdm_slot_management_bank_details_struct_t); + /* PqcAsymAlgoCapabilities: length byte 4, value ML_DSA_44 (a non-zero capability). */ + *ptr = sizeof(uint32_t); + libspdm_write_uint32(ptr + sizeof(uint8_t), SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_44); + ptr += sizeof(uint8_t) + sizeof(uint32_t); + /* CurrentPqcAsymAlgo: length byte 4, value 0 (this Bank uses a traditional algorithm). */ + *ptr = sizeof(uint32_t); + ptr += sizeof(uint8_t) + sizeof(uint32_t); + /* AvailablePqcAsymAlgo: length byte 8, value 0 (8-byte field to probe robustness). */ + *ptr = available_pqc_len; + ptr += sizeof(uint8_t) + available_pqc_len; + ptr += 4; + slot_element = (void *)ptr; + slot_element->element_length = + (uint16_t)(sizeof(spdm_slot_management_slot_element_struct_t) + hash_size); + slot_element->slot_id = 0; + slot_element->slot_attributes = SPDM_SLOT_MANAGEMENT_SLOT_ATTRIBUTE_PROVISIONED; + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x5: { + /* GetCertificateChain: a 0x100-byte certificate chain. */ + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_get_certificate_chain_struct_t *resp_struct; + size_t spdm_response_size; + size_t transport_header_size; + const uint32_t cc_length = 0x100; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) + + cc_length; + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->cc_length = cc_length; + libspdm_set_mem((uint8_t *)resp_struct + + sizeof(spdm_slot_management_get_certificate_chain_struct_t), + cc_length, (uint8_t)(0xaa)); + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x6: + case 0x7: { + /* ManageBank (0x6) / ManageSlot (0x7): SLOT_MANAGEMENT_RESP with no response + * structure (MgmtStructOffset = 0). */ + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + size_t transport_header_size; + uint8_t sub_code; + + sub_code = (spdm_test_context->case_id == 0x6) ? + SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK : + SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t); + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = sub_code; + spdm_response->mgmt_struct_offset = 0; + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x8: { + /* GetCSR: SLOT_MANAGEMENT_RESP with a CSR response structure (0x80-byte CSR). */ + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_csr_struct_t *resp_struct; + size_t spdm_response_size; + size_t transport_header_size; + const uint32_t csr_length = 0x80; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_csr_struct_t) + csr_length; + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->csr_length = csr_length; + libspdm_set_mem((uint8_t *)resp_struct + sizeof(spdm_slot_management_csr_struct_t), + csr_length, (uint8_t)(0xbb)); + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0x9: { + /* SetCertificate: SLOT_MANAGEMENT_RESP with no response structure. */ + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + size_t transport_header_size; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t); + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE; + spdm_response->mgmt_struct_offset = 0; + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0xA: { + /* GetCertificateChain with a malformed MgmtStructOffset that points past the end of the + * response. The Requester must reject this rather than read out of bounds. */ + spdm_slot_management_response_t *spdm_response; + size_t spdm_response_size; + size_t transport_header_size; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t); + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + /* Offset points beyond the actual response size. */ + spdm_response->mgmt_struct_offset = (uint16_t)(spdm_response_size + 0x100); + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + case 0xB: { + /* GetCertificateChain returning a 0x100-byte chain, used to exercise the caller's + * too-small destination buffer (BUFFER_TOO_SMALL). */ + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_get_certificate_chain_struct_t *resp_struct; + size_t spdm_response_size; + size_t transport_header_size; + const uint32_t cc_length = 0x100; + + transport_header_size = LIBSPDM_TEST_TRANSPORT_HEADER_SIZE; + spdm_response = (void *)((uint8_t *)*response + transport_header_size); + spdm_response_size = sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) + + cc_length; + + libspdm_zero_mem(spdm_response, spdm_response_size); + spdm_response->header.spdm_version = SPDM_MESSAGE_VERSION_14; + spdm_response->header.request_response_code = SPDM_SLOT_MANAGEMENT_RESP; + spdm_response->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + spdm_response->mgmt_struct_offset = sizeof(spdm_slot_management_response_t); + + resp_struct = (void *)((uint8_t *)spdm_response + + sizeof(spdm_slot_management_response_t)); + resp_struct->cc_length = cc_length; + libspdm_set_mem((uint8_t *)resp_struct + + sizeof(spdm_slot_management_get_certificate_chain_struct_t), + cc_length, (uint8_t)(0xaa)); + + libspdm_transport_test_encode_message(spdm_context, NULL, false, + false, spdm_response_size, + spdm_response, response_size, + response); + } + return LIBSPDM_STATUS_SUCCESS; + + default: + return LIBSPDM_STATUS_RECEIVE_FAIL; + } +} + +/** + * Test 1: Successful SLOT_MANAGEMENT SupportedSubCodes exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS and the four required SubCode bits set. + **/ +static void req_slot_management_case1(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t sub_code_bitmap[8]; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x1; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + libspdm_zero_mem(sub_code_bitmap, sizeof(sub_code_bitmap)); + status = libspdm_slot_management_get_supported_subcodes(spdm_context, NULL, sub_code_bitmap); + + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(sub_code_bitmap[0] & 0x0F, 0x0F); +} + +/** + * Test 2: Responder returns ERROR(UnsupportedRequest). + * Expected Behavior: an error return code (not SUCCESS). + **/ +static void req_slot_management_case2(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t sub_code_bitmap[8]; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x2; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + libspdm_zero_mem(sub_code_bitmap, sizeof(sub_code_bitmap)); + status = libspdm_slot_management_get_supported_subcodes(spdm_context, NULL, sub_code_bitmap); + + assert_int_not_equal(status, LIBSPDM_STATUS_SUCCESS); +} + +/** + * Test 3: Successful SLOT_MANAGEMENT GetBankInfo exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS and two BankElements returned. + **/ +static void req_slot_management_case3(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + spdm_slot_management_bank_element_struct_t bank_elements[8]; + uint8_t num_bank_elements; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x3; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + num_bank_elements = 8; + status = libspdm_slot_management_get_bank_info(spdm_context, NULL, + &num_bank_elements, bank_elements); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(num_bank_elements, 2); + assert_int_equal(bank_elements[0].bank_id, 0); + assert_int_equal(bank_elements[1].bank_id, 1); +} + +/** + * Test 4: Successful SLOT_MANAGEMENT GetBankDetails exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS and one SlotElement reported for Bank 0. + **/ +static void req_slot_management_case4(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t bank_attributes; + uint32_t current_asym_algo; + uint32_t pqc_asym_algo_capabilities; + uint32_t current_pqc_asym_algo; + uint32_t available_pqc_asym_algo; + uint16_t num_slot_elements; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x4; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + num_slot_elements = 0; + bank_attributes = 0; + current_asym_algo = 0; + pqc_asym_algo_capabilities = 0; + current_pqc_asym_algo = 0xFFFFFFFF; + available_pqc_asym_algo = 0xFFFFFFFF; + status = libspdm_slot_management_get_bank_details( + spdm_context, NULL, 0, &bank_attributes, NULL, + ¤t_asym_algo, NULL, &pqc_asym_algo_capabilities, ¤t_pqc_asym_algo, + &available_pqc_asym_algo, &num_slot_elements, NULL, NULL); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(num_slot_elements, 1); + assert_int_equal(bank_attributes & SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_SELECTED, + SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_SELECTED); + assert_int_equal(current_asym_algo, SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256); + /* PqcAsymAlgoCapabilities was reported (4-byte field) as ML_DSA_44. */ + assert_int_equal(pqc_asym_algo_capabilities, SPDM_KEY_PAIR_PQC_ASYM_ALGO_CAP_ML_DSA_44); + /* This Bank uses a traditional algorithm, so CurrentPqcAsymAlgo is 0. The Responder reported + * AvailablePqcAsymAlgo with an 8-byte field whose value is 0; the Requester must decode it + * without assuming a 4-byte field. */ + assert_int_equal(current_pqc_asym_algo, 0); + assert_int_equal(available_pqc_asym_algo, 0); +} + +/** + * Test 5: Successful SLOT_MANAGEMENT GetCertificateChain exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS and the certificate chain returned. + **/ +static void req_slot_management_case5(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t cert_chain[0x200]; + size_t cert_chain_size; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x5; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + cert_chain_size = sizeof(cert_chain); + status = libspdm_slot_management_get_certificate_chain( + spdm_context, NULL, 0, 0, &cert_chain_size, cert_chain); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(cert_chain_size, 0x100); +} + +/** + * Test 6: Successful SLOT_MANAGEMENT ManageBank exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS. + **/ +static void req_slot_management_case6(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x6; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + status = libspdm_slot_management_manage_bank( + spdm_context, NULL, 0, + SPDM_SLOT_MANAGEMENT_MANAGE_BANK_OPERATION_CONFIG_ALGO, + SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256, 0); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); +} + +/** + * Test 7: Successful SLOT_MANAGEMENT ManageSlot exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS. + **/ +static void req_slot_management_case7(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x7; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + status = libspdm_slot_management_manage_slot( + spdm_context, NULL, 0, 0, + SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); +} + +/** + * Test 8: Successful SLOT_MANAGEMENT GetCSR exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS and the CSR returned. + **/ +static void req_slot_management_case8(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t csr[0x200]; + size_t csr_len; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x8; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + csr_len = sizeof(csr); + status = libspdm_slot_management_get_csr( + spdm_context, NULL, 0, 0, 0, 0, NULL, 0, NULL, 0, csr, &csr_len); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(csr_len, 0x80); +} + +/** + * Test 9: Successful SLOT_MANAGEMENT SetCertificate exchange. + * Expected Behavior: LIBSPDM_STATUS_SUCCESS. + **/ +static void req_slot_management_case9(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t cert_chain[0x100]; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x9; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + libspdm_set_mem(cert_chain, sizeof(cert_chain), (uint8_t)(0xcc)); + status = libspdm_slot_management_set_certificate( + spdm_context, NULL, 0, 0, SPDM_CERTIFICATE_INFO_CERT_MODEL_DEVICE_CERT, + cert_chain, sizeof(cert_chain)); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); +} + +/** + * Test 10: SLOT_MANAGEMENT GetCertificateChain response with a malformed MgmtStructOffset that + * points past the end of the response. + * Expected Behavior: the Requester rejects the response (not LIBSPDM_STATUS_SUCCESS) rather than + * reading out of bounds. + **/ +static void req_slot_management_case10(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t cert_chain[0x200]; + size_t cert_chain_size; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xA; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + cert_chain_size = sizeof(cert_chain); + status = libspdm_slot_management_get_certificate_chain( + spdm_context, NULL, 0, 0, &cert_chain_size, cert_chain); + assert_int_not_equal(status, LIBSPDM_STATUS_SUCCESS); +} + +/** + * Test 11: SLOT_MANAGEMENT GetCertificateChain where the caller's destination buffer is too + * small for the returned certificate chain. + * Expected Behavior: the Requester returns LIBSPDM_STATUS_BUFFER_TOO_SMALL. + **/ +static void req_slot_management_case11(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + uint8_t cert_chain[0x10]; + size_t cert_chain_size; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xB; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + /* The response carries a 0x100-byte chain, larger than this 0x10-byte buffer. */ + cert_chain_size = sizeof(cert_chain); + status = libspdm_slot_management_get_certificate_chain( + spdm_context, NULL, 0, 0, &cert_chain_size, cert_chain); + assert_int_equal(status, LIBSPDM_STATUS_BUFFER_TOO_SMALL); +} + +int libspdm_req_slot_management_test(void) +{ + const struct CMUnitTest test_cases[] = { + cmocka_unit_test(req_slot_management_case1), + cmocka_unit_test(req_slot_management_case2), + cmocka_unit_test(req_slot_management_case3), + cmocka_unit_test(req_slot_management_case4), + cmocka_unit_test(req_slot_management_case5), + cmocka_unit_test(req_slot_management_case6), + cmocka_unit_test(req_slot_management_case7), + cmocka_unit_test(req_slot_management_case8), + cmocka_unit_test(req_slot_management_case9), + cmocka_unit_test(req_slot_management_case10), + cmocka_unit_test(req_slot_management_case11), + }; + + libspdm_test_context_t test_context = { + LIBSPDM_TEST_CONTEXT_VERSION, + true, + send_message, + receive_message, + }; + + libspdm_setup_test_context(&test_context); + + return cmocka_run_group_tests(test_cases, + libspdm_unit_test_group_setup, + libspdm_unit_test_group_teardown); +} + +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ diff --git a/unit_test/test_spdm_requester/test_spdm_requester.c b/unit_test/test_spdm_requester/test_spdm_requester.c index b8f44863d0b..8b98a38bf65 100644 --- a/unit_test/test_spdm_requester/test_spdm_requester.c +++ b/unit_test/test_spdm_requester/test_spdm_requester.c @@ -110,6 +110,10 @@ int libspdm_req_set_key_pair_info_test(void); int libspdm_req_set_key_pair_info_error_test(void); #endif /* LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP +int libspdm_req_slot_management_test(void); +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #if LIBSPDM_SEND_GET_ENDPOINT_INFO_SUPPORT int libspdm_req_get_endpoint_info_test(void); int libspdm_req_get_endpoint_info_error_test(void); @@ -334,6 +338,12 @@ int main(void) } #endif /* LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP */ + #if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + if (libspdm_req_slot_management_test() != 0) { + return_value = 1; + } + #endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #if LIBSPDM_ENABLE_VENDOR_DEFINED_MESSAGES if (libspdm_req_vendor_defined_request_test() != 0) { return_value = 1; From 0d3f9cae2e7071d8d870f7cf42402f6aedde6786 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:43 +0800 Subject: [PATCH 08/10] unit_test/test_spdm_responder: Add SLOT_MANAGEMENT tests Add responder tests for the SLOT_MANAGEMENT SubCodes: SupportedSubCodes (success and error paths), GetBankInfo, GetBankDetails, GetCertificateChain, ManageBank (incl. consistency with GET_KEY_PAIR_INFO), ManageSlot (Erase), GetCSR, and SetCertificate. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- unit_test/test_spdm_responder/CMakeLists.txt | 1 + .../test_spdm_responder/slot_management.c | 1351 +++++++++++++++++ .../test_spdm_responder/test_spdm_responder.c | 10 + 3 files changed, 1362 insertions(+) create mode 100644 unit_test/test_spdm_responder/slot_management.c diff --git a/unit_test/test_spdm_responder/CMakeLists.txt b/unit_test/test_spdm_responder/CMakeLists.txt index 967cd8d1e27..d880a54763b 100644 --- a/unit_test/test_spdm_responder/CMakeLists.txt +++ b/unit_test/test_spdm_responder/CMakeLists.txt @@ -61,6 +61,7 @@ target_sources(test_spdm_responder chunk_send_ack.c key_pair_info.c set_key_pair_info_ack.c + slot_management.c endpoint_info.c ${LIBSPDM_DIR}/unit_test/spdm_unit_test_common/common.c ${LIBSPDM_DIR}/unit_test/spdm_unit_test_common/algo.c diff --git a/unit_test/test_spdm_responder/slot_management.c b/unit_test/test_spdm_responder/slot_management.c new file mode 100644 index 00000000000..5a6f8d24703 --- /dev/null +++ b/unit_test/test_spdm_responder/slot_management.c @@ -0,0 +1,1351 @@ +/** + * Copyright Notice: + * Copyright 2026 DMTF. All rights reserved. + * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md + **/ + +#include "spdm_unit_test.h" +#include "internal/libspdm_responder_lib.h" +#include "internal/libspdm_requester_lib.h" + +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + +/* The sample device_secret_lib stores runtime-provisioned certificate chains in NVM files named + * "bank_id_NNN_slot_id_M_cert_chain.der" (SLOT_MANAGEMENT SetCertificate/Erase) and + * "slot_id_M_cert_chain.der" (base SET_CERTIFICATE). Those files persist across test runs, so a + * test that provisions or erases a slot would otherwise leak state into later cases (and later + * runs). Remove all such files so each test starts from the static (factory) certificate store. + * Used as a cmocka per-test setup. */ +static int libspdm_slot_management_test_setup(void **state) +{ + char file_name[40]; + uint16_t bank_id; + uint8_t slot_id; + + for (slot_id = 0; slot_id < SPDM_MAX_SLOT_COUNT; slot_id++) { + snprintf(file_name, sizeof(file_name), "slot_id_%u_cert_chain.der", (unsigned)slot_id); + (void)remove(file_name); + for (bank_id = 0; bank_id < 256; bank_id++) { + snprintf(file_name, sizeof(file_name), "bank_id_%03u_slot_id_%u_cert_chain.der", + (unsigned)bank_id, (unsigned)slot_id); + (void)remove(file_name); + } + } + return 0; +} + +spdm_slot_management_request_t m_libspdm_slot_management_request1 = { + { SPDM_MESSAGE_VERSION_14, SPDM_SLOT_MANAGEMENT, + SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES, 0 }, + 0 +}; +size_t m_libspdm_slot_management_request1_size = sizeof(m_libspdm_slot_management_request1); + +/** + * Test 1: Successful response to SLOT_MANAGEMENT SupportedSubCodes. + * Expected Behavior: get a LIBSPDM_STATUS_SUCCESS return code, SLOT_MANAGEMENT_RESP message with + * the SupportedSubCodes structure containing the required SubCode bits. + **/ +static void rsp_slot_management_case1(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_supported_subcodes_struct_t *resp_struct; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x1; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + m_libspdm_slot_management_request1.header.param1 = + SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES; + + response_size = sizeof(response); + + status = libspdm_get_response_slot_management( + spdm_context, m_libspdm_slot_management_request1_size, + &m_libspdm_slot_management_request1, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(response_size, + sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_supported_subcodes_struct_t)); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, + SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES); + assert_int_equal(spdm_response->mgmt_struct_offset, + sizeof(spdm_slot_management_response_t)); + + resp_struct = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + assert_int_equal(resp_struct->resp_length, + sizeof(spdm_slot_management_supported_subcodes_struct_t)); + /* The four required SubCode bits shall be set. */ + assert_int_equal(resp_struct->sub_code_bitmap[0] & 0x0F, 0x0F); +} + +/** + * Test 2: SLOT_MGMT_CAP is not set. + * Expected Behavior: Generate error response message with UNSUPPORTED_REQUEST. + **/ +static void rsp_slot_management_case2(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x2; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->local_context.capability.ext_flags &= + ~SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + m_libspdm_slot_management_request1.header.param1 = + SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES; + + response_size = sizeof(response); + + status = libspdm_get_response_slot_management( + spdm_context, m_libspdm_slot_management_request1_size, + &m_libspdm_slot_management_request1, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST); + assert_int_equal(spdm_response->header.param2, SPDM_SLOT_MANAGEMENT); +} + +/** + * Test 3: A reserved (unlisted) SubCode is requested. + * Expected Behavior: Generate error response message with INVALID_REQUEST. Per DSP0274 a SubCode + * that is not listed in Table 142 shall be answered with ERROR(InvalidRequest). + **/ +static void rsp_slot_management_case3(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x3; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + /* 0x10 is a reserved SubCode that is not listed in Table 142. */ + m_libspdm_slot_management_request1.header.param1 = 0x10; + + response_size = sizeof(response); + + status = libspdm_get_response_slot_management( + spdm_context, m_libspdm_slot_management_request1_size, + &m_libspdm_slot_management_request1, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_REQUEST); + + /* restore request for subsequent runs */ + m_libspdm_slot_management_request1.header.param1 = + SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES; +} + +/** + * Test 4: Connection version is lower than 1.4. + * Expected Behavior: Generate error response message with UNSUPPORTED_REQUEST. + **/ +static void rsp_slot_management_case4(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x4; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_13 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + m_libspdm_slot_management_request1.header.spdm_version = SPDM_MESSAGE_VERSION_13; + m_libspdm_slot_management_request1.header.param1 = + SPDM_SLOT_MANAGEMENT_SUBCODE_SUPPORTED_SUBCODES; + + response_size = sizeof(response); + + status = libspdm_get_response_slot_management( + spdm_context, m_libspdm_slot_management_request1_size, + &m_libspdm_slot_management_request1, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST); + assert_int_equal(spdm_response->header.param2, SPDM_SLOT_MANAGEMENT); + + /* restore request version for subsequent runs */ + m_libspdm_slot_management_request1.header.spdm_version = SPDM_MESSAGE_VERSION_14; +} + +/* Find, by querying GetBankInfo, a Bank that has at least one slot. Returns the BankID, the + * lowest SlotID present in that Bank, and the number of slots in that Bank. Also reports the + * Bank that has the most slots, which is used to locate the multi-key Bank when present. */ +static bool libspdm_slot_management_find_bank( + libspdm_context_t *spdm_context, + uint8_t *bank_id, uint8_t *slot_id, uint8_t *max_slot_bank_id, uint8_t *max_slot_count) +{ + uint8_t num_bank_elements; + /* BankID is limited to 0-239 by the specification, so SPDM_MAX_BANK_COUNT BankElements can + * hold every possible Bank. */ + spdm_slot_management_bank_element_struct_t banks[SPDM_MAX_BANK_COUNT]; + uint8_t index; + uint8_t slot_index; + uint8_t best_count = 0; + bool found = false; + + num_bank_elements = SPDM_MAX_BANK_COUNT; + if (!libspdm_read_slot_management_bank_info(spdm_context, &num_bank_elements, banks)) { + return false; + } + + for (index = 0; index < num_bank_elements; index++) { + uint8_t mask = banks[index].slot_mask; + uint8_t count = 0; + + for (slot_index = 0; slot_index < SPDM_MAX_SLOT_COUNT; slot_index++) { + if ((mask & (1 << slot_index)) != 0) { + count++; + } + } + if ((count > 0) && !found) { + found = true; + *bank_id = banks[index].bank_id; + for (slot_index = 0; slot_index < SPDM_MAX_SLOT_COUNT; slot_index++) { + if ((mask & (1 << slot_index)) != 0) { + *slot_id = slot_index; + break; + } + } + } + if (count > best_count) { + best_count = count; + *max_slot_bank_id = banks[index].bank_id; + } + } + *max_slot_count = best_count; + return found; +} + +/** + * Test 5: Successful response to SLOT_MANAGEMENT GetBankInfo. + * Expected Behavior: SLOT_MANAGEMENT_RESP with a BankInfo structure. The Banks are grouped by + * algorithm; at least one Bank with at least one slot is reported. + **/ +static void rsp_slot_management_case5(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_bank_info_struct_t *resp_struct; + spdm_slot_management_request_t request; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x5; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + libspdm_zero_mem(&request, sizeof(request)); + request.header.spdm_version = SPDM_MESSAGE_VERSION_14; + request.header.request_response_code = SPDM_SLOT_MANAGEMENT; + request.header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(spdm_slot_management_request_t), + &request, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_INFO); + resp_struct = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + assert_true(resp_struct->num_bank_elements >= 1); + assert_int_equal(response_size, + sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_bank_info_struct_t) + + (size_t)resp_struct->num_bank_elements * + sizeof(spdm_slot_management_bank_element_struct_t)); + /* At least one Bank exposes a slot. */ + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); +} + +/** + * Test 6: Successful response to SLOT_MANAGEMENT GetBankDetails. + * Expected Behavior: BankDetails reports the Bank's slots, with ConfigAlgo set and the Bank's + * algorithm in the CurrentAsymAlgo field. + **/ +static void rsp_slot_management_case6(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_bank_details_struct_t *resp_struct; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_slot_address_struct_t *slot_address; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x6; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + slot_address->bank_id = bank_id; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS); + resp_struct = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + assert_int_equal(resp_struct->bank_id, bank_id); + /* The sample exposes two wire SlotIDs (0 and 1) per Bank. */ + assert_int_equal(resp_struct->num_slot_elements, 2); + /* The Bank supports algorithm configuration (ConfigAlgo) and reports its own algorithm. */ + assert_int_equal(resp_struct->bank_attributes & + SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_CONFIG_ALGO, + SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_CONFIG_ALGO); + assert_int_not_equal(resp_struct->current_asym_algo, 0); + + /* Verify the reported SlotIDs are the true wire values 0 and 1 (not a packed index). The + * SlotElement array follows the fixed BankDetails fields and the variable-length PQC fields; + * skip those to reach it. */ + { + size_t offset; + uint8_t pqc_cap_len; + uint8_t current_pqc_len; + uint8_t available_pqc_len; + const spdm_slot_management_slot_element_struct_t *slot_element; + uint32_t hash_size; + uint8_t slot_index; + uint8_t seen_slot_mask; + + hash_size = libspdm_get_hash_size(m_libspdm_use_hash_algo); + offset = sizeof(spdm_slot_management_bank_details_struct_t); + pqc_cap_len = ((const uint8_t *)resp_struct)[offset]; + offset += 1 + pqc_cap_len; + current_pqc_len = ((const uint8_t *)resp_struct)[offset]; + offset += 1 + current_pqc_len; + available_pqc_len = ((const uint8_t *)resp_struct)[offset]; + offset += 1 + available_pqc_len; + offset += 4; /* reserved */ + + seen_slot_mask = 0; + for (slot_index = 0; slot_index < resp_struct->num_slot_elements; slot_index++) { + slot_element = (const void *)((const uint8_t *)resp_struct + offset); + assert_true(slot_element->slot_id < SPDM_MAX_SLOT_COUNT); + seen_slot_mask |= (uint8_t)(1 << slot_element->slot_id); + offset += sizeof(spdm_slot_management_slot_element_struct_t) + hash_size; + } + /* SlotIDs 0 and 1 are present. */ + assert_int_equal(seen_slot_mask, 0x03); + } +} + +/** + * Test 7: Successful response to SLOT_MANAGEMENT GetCertificateChain. + * Expected Behavior: SLOT_MANAGEMENT_RESP with the requested certificate chain, matching the + * chain the HAL provides for the same Bank+slot. + **/ +static void rsp_slot_management_case7(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_get_certificate_chain_struct_t *resp_struct; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_slot_address_struct_t *slot_address; + uint8_t expected_cert_chain[LIBSPDM_MAX_CERT_CHAIN_SIZE]; + size_t expected_cert_chain_size; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x7; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + /* Use the first Bank+slot that exists. Read the expected chain through the same HAL so the + * test is independent of the cert file size. */ + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); + expected_cert_chain_size = sizeof(expected_cert_chain); + assert_true(libspdm_read_slot_management_certificate_chain( + spdm_context, bank_id, slot_id, &expected_cert_chain_size, + expected_cert_chain)); + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + slot_address->bank_id = bank_id; + slot_address->slot_id = slot_id; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, + SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN); + resp_struct = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + assert_int_equal(resp_struct->cc_length, expected_cert_chain_size); + assert_int_equal(response_size, + sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) + + expected_cert_chain_size); + /* The returned chain shall match what the HAL provides for this Bank+slot. */ + assert_memory_equal((uint8_t *)resp_struct + + sizeof(spdm_slot_management_get_certificate_chain_struct_t), + expected_cert_chain, expected_cert_chain_size); +} + +/** + * Test 8: SLOT_MANAGEMENT ManageBank. Configuring the Bank to its own algorithm is an + * idempotent success; configuring a Bank with provisioned slots to a different algorithm is + * rejected with InvalidState; after erasing all of the Bank's slots, configuring it to a + * different (device-supported) algorithm succeeds and changes the Bank's CurrentAsymAlgo. + **/ +static void rsp_slot_management_case8(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_bank_details_struct_t *details; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_manage_bank_struct_t) + + sizeof(uint8_t) + sizeof(uint32_t)]; + uint8_t detail_request[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_manage_bank_struct_t *manage_bank; + spdm_slot_management_slot_address_struct_t *slot_address; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + uint32_t bank_asym_algo; + uint32_t other_asym_algo; + uint8_t *ptr; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x8; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); + + /* Read the Bank's own algorithm via GetBankDetails. */ + libspdm_zero_mem(detail_request, sizeof(detail_request)); + request = (void *)detail_request; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(detail_request + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + slot_address->bank_id = bank_id; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(detail_request), detail_request, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + details = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + bank_asym_algo = details->current_asym_algo; + assert_int_not_equal(bank_asym_algo, 0); + /* Choose a different (any other) asymmetric algorithm to attempt a reconfiguration. */ + other_asym_algo = (bank_asym_algo == SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256) ? + SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC384 : + SPDM_KEY_PAIR_ASYM_ALGO_CAP_ECC256; + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + manage_bank = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + manage_bank->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + manage_bank->slot_address.bank_id = bank_id; + manage_bank->operation = SPDM_SLOT_MANAGEMENT_MANAGE_BANK_OPERATION_CONFIG_ALGO; + ptr = (uint8_t *)manage_bank + sizeof(spdm_slot_management_manage_bank_struct_t); + *ptr = sizeof(uint32_t); /* select_pqc_asym_algo_len, value 0 */ + + /* Configuring the Bank to its own algorithm is an idempotent success. */ + manage_bank->select_asym_algo = bank_asym_algo; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK); + assert_int_equal(spdm_response->mgmt_struct_offset, 0); + assert_int_equal(response_size, sizeof(spdm_slot_management_response_t)); + + /* Configuring the Bank to a different algorithm while it has provisioned slots is rejected + * with InvalidState, per DSP0274 Table 141 (ManageBank). The Bank located above has at least + * one provisioned slot. */ + manage_bank->select_asym_algo = other_asym_algo; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_STATE); + + /* Now erase every slot in the Bank, making it unprovisioned, then reconfigure it to the + * other algorithm. With no provisioned slots the reconfiguration succeeds and the Bank's + * CurrentAsymAlgo changes (DSP0274 Table 141 ManageBank ConfigAlgo). Erasing slots 1-7 + * requires a trusted environment or secure session, so enter a trusted environment. */ + { + extern bool g_in_trusted_environment; + bool saved_trusted = g_in_trusted_environment; + uint8_t erase_request[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_manage_slot_struct_t)]; + spdm_slot_management_manage_slot_struct_t *manage_slot; + uint8_t slot_iter; + + g_in_trusted_environment = true; + + for (slot_iter = 0; slot_iter < SPDM_MAX_SLOT_COUNT; slot_iter++) { + libspdm_zero_mem(erase_request, sizeof(erase_request)); + request = (void *)erase_request; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + manage_slot = (void *)(erase_request + sizeof(spdm_slot_management_request_t)); + manage_slot->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + manage_slot->slot_address.bank_id = bank_id; + manage_slot->slot_address.slot_id = slot_iter; + manage_slot->operation = SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE; + response_size = sizeof(response); + /* Slots that do not exist simply fail; existing slots are erased. */ + (void)libspdm_get_response_slot_management( + spdm_context, sizeof(erase_request), erase_request, &response_size, response); + } + + g_in_trusted_environment = saved_trusted; + } + + /* Reconfigure the now-unprovisioned Bank to the other algorithm: this succeeds. */ + manage_bank->select_asym_algo = other_asym_algo; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_BANK); + assert_int_equal(spdm_response->mgmt_struct_offset, 0); + + /* GetBankDetails now reports the new algorithm for this Bank. */ + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(detail_request), detail_request, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + details = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + assert_int_equal(details->current_asym_algo, other_asym_algo); + + /* An all-zero ConfigAlgo (no bit set in SelectAsymAlgo or SelectPqcAsymAlgo) does not name an + * algorithm: ConfigAlgo requires exactly one bit, so it is rejected with InvalidRequest. */ + manage_bank->select_asym_algo = 0; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_REQUEST); +} + +/** + * Test 9: SLOT_MANAGEMENT ManageSlot (Erase). After erasing a slot, GetCertificateChain for + * that Bank+slot shall fail. + **/ +static void rsp_slot_management_case9(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_manage_slot_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_manage_slot_struct_t *manage_slot; + uint8_t cert_request[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_slot_address_struct_t *slot_address; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x9; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + /* Erase the first existing Bank+slot. */ + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + manage_slot = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + manage_slot->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + manage_slot->slot_address.bank_id = bank_id; + manage_slot->slot_address.slot_id = slot_id; + manage_slot->operation = SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT); + assert_int_equal(spdm_response->mgmt_struct_offset, 0); + assert_int_equal(response_size, sizeof(spdm_slot_management_response_t)); + + /* GetCertificateChain for the erased slot now fails with InvalidRequest. */ + libspdm_zero_mem(cert_request, sizeof(cert_request)); + request = (void *)cert_request; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(cert_request + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + slot_address->bank_id = bank_id; + slot_address->slot_id = slot_id; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(cert_request), cert_request, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_REQUEST); +} + +#if LIBSPDM_ENABLE_CAPABILITY_CSR_CAP +/** + * Test 10: Successful response to SLOT_MANAGEMENT GetCSR. + * Expected Behavior: SLOT_MANAGEMENT_RESP with a CSR response structure. + **/ +static void rsp_slot_management_case10(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_csr_struct_t *resp_struct; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_get_csr_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_get_csr_struct_t *get_csr; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xA; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->connection_info.algorithm.base_asym_algo = m_libspdm_use_asym_algo; + /* Single-key connection: key_pair_id and cert model in the request shall be 0. */ + spdm_context->connection_info.multi_key_conn_rsp = false; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + /* The Responder generates the CSR without requiring a reset. */ + spdm_context->local_context.capability.flags &= + ~SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP; + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + get_csr = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + get_csr->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + get_csr->slot_address.bank_id = 0; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CSR); + resp_struct = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + assert_int_not_equal(resp_struct->csr_length, 0); + assert_int_equal(response_size, + sizeof(spdm_slot_management_response_t) + + sizeof(spdm_slot_management_csr_struct_t) + resp_struct->csr_length); +} +#endif /* LIBSPDM_ENABLE_CAPABILITY_CSR_CAP */ + +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP +/** + * Test 11: Successful response to SLOT_MANAGEMENT SetCertificate. + * Expected Behavior: SLOT_MANAGEMENT_RESP with no response structure. + **/ +static void rsp_slot_management_case11(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_set_certificate_struct_t) + 0x100]; + spdm_slot_management_request_t *request; + spdm_slot_management_set_certificate_struct_t *set_cert; + const uint32_t cert_length = 0x100; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xB; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->connection_info.algorithm.base_asym_algo = m_libspdm_use_asym_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + /* The Responder writes the certificate without requiring a reset, and is in a trusted + * environment so a non-zero Bank is allowed. */ + spdm_context->local_context.capability.flags &= + ~SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP; + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + set_cert = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + set_cert->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + set_cert->slot_address.bank_id = 0; + set_cert->slot_address.slot_id = 0; + set_cert->cert_length = cert_length; + set_cert->cert_attributes = SPDM_CERTIFICATE_INFO_CERT_MODEL_DEVICE_CERT; + libspdm_set_mem((uint8_t *)set_cert + + sizeof(spdm_slot_management_set_certificate_struct_t), + cert_length, (uint8_t)(0xcc)); + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE); + assert_int_equal(spdm_response->mgmt_struct_offset, 0); + assert_int_equal(response_size, sizeof(spdm_slot_management_response_t)); +} +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP */ + +/* The sample HAL's overridable SupportedSubCodes bitmap. */ +extern uint8_t m_libspdm_slot_management_sub_code_bitmap[8]; + +/** + * Test 12: A valid SubCode that is not advertised in SupportedSubCodes. + * Expected Behavior: Generate error response message with UNSUPPORTED_REQUEST. Per DSP0274 a + * valid SubCode that is not in the Responder's SupportedSubCodes response shall be answered with + * ERROR(UnsupportedRequest), distinct from the InvalidRequest used for unlisted SubCodes. + **/ +static void rsp_slot_management_case12(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_manage_slot_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_manage_slot_struct_t *manage_slot; + uint8_t saved_byte; + const uint8_t bitmap_index = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT / 8; + const uint8_t bitmap_bit = + (uint8_t)(1 << (SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT % 8)); + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xC; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + /* Clear the ManageSlot bit so the (valid) SubCode is no longer advertised. */ + saved_byte = m_libspdm_slot_management_sub_code_bitmap[bitmap_index]; + m_libspdm_slot_management_sub_code_bitmap[bitmap_index] &= (uint8_t)(~bitmap_bit); + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + manage_slot = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + manage_slot->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + manage_slot->slot_address.bank_id = 0; + manage_slot->slot_address.slot_id = 0; + manage_slot->operation = SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + + /* Restore the bitmap before asserting so a failure does not leak into later cases. */ + m_libspdm_slot_management_sub_code_bitmap[bitmap_index] = saved_byte; + + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_UNSUPPORTED_REQUEST); +} + +/** + * Test 13: GetBankDetails with BankID = 0xFF (a non-existent Bank). + * Expected Behavior: Generate error response message with INVALID_REQUEST. 0xFF is no longer a + * reserved "all Banks" value, so it is treated as a literal, non-existent Bank and rejected. + **/ +static void rsp_slot_management_case13(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_slot_address_struct_t *slot_address; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xD; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + slot_address->bank_id = 0xFF; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_REQUEST); +} + + + +/** + * Test 14: SLOT_MANAGEMENT access control for slots 1-7 (ManageSlot Erase). + * Expected Behavior: An erase of a non-zero SlotID outside a secure session and outside a + * trusted environment is rejected with UnexpectedRequest. The same request issued in a trusted + * environment succeeds. (DSP0274 "Certificate slot management": for slots 1-7 these commands + * shall only be issued in a secure session or a trusted environment.) + **/ +static void rsp_slot_management_case14(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_manage_slot_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_manage_slot_struct_t *manage_slot; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + extern bool g_in_trusted_environment; + bool saved_trusted; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xE; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + /* No session is in use. */ + spdm_context->last_spdm_request_session_id_valid = false; + + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); + + /* Target a non-zero SlotID (slot 1, which the sample exposes). */ + slot_id = 1; + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + manage_slot = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + manage_slot->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + manage_slot->slot_address.bank_id = bank_id; + manage_slot->slot_address.slot_id = slot_id; + manage_slot->operation = SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE; + + saved_trusted = g_in_trusted_environment; + g_in_trusted_environment = false; + + /* Not trusted, no session -> UnexpectedRequest. */ + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_UNEXPECTED_REQUEST); + + /* In a trusted environment the same request succeeds. */ + g_in_trusted_environment = true; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + g_in_trusted_environment = saved_trusted; + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT); + assert_int_equal(spdm_response->mgmt_struct_offset, 0); +} + +/** + * Test 15: SLOT_MANAGEMENT negative cases for malformed requests. + * Expected Behavior: A request whose MgmtStructOffset points past the end of the request, and a + * GetBankDetails whose SlotAddress.ReqLength is not 8, are both rejected with InvalidRequest. + **/ +static void rsp_slot_management_case15(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_slot_address_struct_t *slot_address; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0xF; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + + /* Sub-case 1: GetBankDetails with MgmtStructOffset pointing past the end of the request. */ + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS; + request->mgmt_struct_offset = sizeof(request_buffer); /* points at/after the end */ + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_REQUEST); + + /* Sub-case 2: GetBankDetails with an invalid SlotAddress.ReqLength (must be 8). */ + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_BANK_DETAILS; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = 4; /* wrong: shall be 8 */ + slot_address->bank_id = 0; + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_INVALID_REQUEST); +} + +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP +/** + * Test 16: A SLOT_MANAGEMENT SetCertificate followed by GetCertificateChain returns the chain that + * was just provisioned (not the static factory chain). This exercises that the read path reflects + * a runtime SET_CERTIFICATE, per DSP0274 (the selected Bank's slots are the GET_CERTIFICATE slots). + **/ +static void rsp_slot_management_case16(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + spdm_slot_management_get_certificate_chain_struct_t *resp_struct; + void *cert_chain_data; + size_t cert_chain_data_size; + uint8_t *raw_cert; + size_t raw_cert_size; + size_t digest_size; + uint8_t *request_buffer; + size_t request_buffer_size; + spdm_slot_management_request_t *request; + spdm_slot_management_set_certificate_struct_t *set_cert; + uint8_t cert_request[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_slot_address_struct_t)]; + spdm_slot_management_slot_address_struct_t *slot_address; + const uint8_t bank_id = 0; + const uint8_t slot_id = 0; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x10; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_NEGOTIATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->connection_info.algorithm.base_asym_algo = m_libspdm_use_asym_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + /* No reset required: the SetCertificate completes immediately. */ + spdm_context->local_context.capability.flags &= + ~SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP; + + /* Obtain a real certificate chain and extract its raw (DER) portion to provision. */ + assert_true(libspdm_read_responder_public_certificate_chain( + m_libspdm_use_hash_algo, m_libspdm_use_asym_algo, + &cert_chain_data, &cert_chain_data_size, NULL, NULL)); + digest_size = libspdm_get_hash_size(m_libspdm_use_hash_algo); + raw_cert = (uint8_t *)cert_chain_data + sizeof(spdm_cert_chain_t) + digest_size; + raw_cert_size = cert_chain_data_size - sizeof(spdm_cert_chain_t) - digest_size; + + /* SLOT_MANAGEMENT SetCertificate of the raw chain into (bank 0, slot 0). */ + request_buffer_size = sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_set_certificate_struct_t) + raw_cert_size; + request_buffer = malloc(request_buffer_size); + assert_non_null(request_buffer); + libspdm_zero_mem(request_buffer, request_buffer_size); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + set_cert = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + set_cert->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + set_cert->slot_address.bank_id = bank_id; + set_cert->slot_address.slot_id = slot_id; + set_cert->cert_length = (uint32_t)raw_cert_size; + set_cert->cert_attributes = SPDM_CERTIFICATE_INFO_CERT_MODEL_DEVICE_CERT; + libspdm_copy_mem((uint8_t *)set_cert + sizeof(spdm_slot_management_set_certificate_struct_t), + raw_cert_size, raw_cert, raw_cert_size); + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, request_buffer_size, request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, SPDM_SLOT_MANAGEMENT_SUBCODE_SET_CERTIFICATE); + free(request_buffer); + + /* GetCertificateChain now returns a full chain reconstructed from the provisioned raw chain. */ + libspdm_zero_mem(cert_request, sizeof(cert_request)); + request = (void *)cert_request; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + slot_address = (void *)(cert_request + sizeof(spdm_slot_management_request_t)); + slot_address->req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + slot_address->bank_id = bank_id; + slot_address->slot_id = slot_id; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(cert_request), cert_request, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_SLOT_MANAGEMENT_RESP); + assert_int_equal(spdm_response->header.param1, + SPDM_SLOT_MANAGEMENT_SUBCODE_GET_CERTIFICATE_CHAIN); + resp_struct = (void *)((uint8_t *)spdm_response + spdm_response->mgmt_struct_offset); + /* The reconstructed chain has the same size as the source chain, and its raw certificate + * portion matches what was provisioned. */ + assert_int_equal(resp_struct->cc_length, cert_chain_data_size); + assert_memory_equal((uint8_t *)resp_struct + + sizeof(spdm_slot_management_get_certificate_chain_struct_t) + + sizeof(spdm_cert_chain_t) + digest_size, + raw_cert, raw_cert_size); + + free(cert_chain_data); +} +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP */ + +/** + * Test 17: A SLOT_MANAGEMENT ManageSlot Erase when the Responder advertises CERT_INSTALL_RESET_CAP + * shall return ERROR(ResetRequired), mirroring the base SET_CERTIFICATE reset flow. + **/ +static void rsp_slot_management_case17(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t response_size; + uint8_t response[LIBSPDM_MAX_SPDM_MSG_SIZE]; + spdm_slot_management_response_t *spdm_response; + uint8_t request_buffer[sizeof(spdm_slot_management_request_t) + + sizeof(spdm_slot_management_manage_slot_struct_t)]; + spdm_slot_management_request_t *request; + spdm_slot_management_manage_slot_struct_t *manage_slot; + uint8_t bank_id; + uint8_t slot_id; + uint8_t max_bank_id; + uint8_t max_slot_count; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x11; + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_14 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->connection_info.connection_state = LIBSPDM_CONNECTION_STATE_AUTHENTICATED; + spdm_context->connection_info.algorithm.base_hash_algo = m_libspdm_use_hash_algo; + spdm_context->local_context.capability.ext_flags |= + SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; + /* The device requires a reset to complete a certificate-install operation. */ + spdm_context->local_context.capability.flags |= + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_CERT_INSTALL_RESET_CAP; + + assert_true(libspdm_slot_management_find_bank( + spdm_context, &bank_id, &slot_id, &max_bank_id, &max_slot_count)); + + libspdm_zero_mem(request_buffer, sizeof(request_buffer)); + request = (void *)request_buffer; + request->header.spdm_version = SPDM_MESSAGE_VERSION_14; + request->header.request_response_code = SPDM_SLOT_MANAGEMENT; + request->header.param1 = SPDM_SLOT_MANAGEMENT_SUBCODE_MANAGE_SLOT; + request->mgmt_struct_offset = sizeof(spdm_slot_management_request_t); + manage_slot = (void *)(request_buffer + sizeof(spdm_slot_management_request_t)); + manage_slot->slot_address.req_length = SPDM_SLOT_MANAGEMENT_SLOT_ADDRESS_REQ_LENGTH; + manage_slot->slot_address.bank_id = bank_id; + manage_slot->slot_address.slot_id = slot_id; + manage_slot->operation = SPDM_SLOT_MANAGEMENT_MANAGE_SLOT_OPERATION_ERASE; + + response_size = sizeof(response); + status = libspdm_get_response_slot_management( + spdm_context, sizeof(request_buffer), request_buffer, &response_size, response); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + spdm_response = (void *)response; + assert_int_equal(spdm_response->header.request_response_code, SPDM_ERROR); + assert_int_equal(spdm_response->header.param1, SPDM_ERROR_CODE_RESET_REQUIRED); +} + +int libspdm_rsp_slot_management_test(void) +{ + /* Every case runs libspdm_slot_management_test_setup first, which clears any runtime + * provisioned/erased certificate NVM files so each test starts from the static factory store + * and does not leak slot state into later cases or later runs. */ + const struct CMUnitTest test_cases[] = { + /* Success case for SupportedSubCodes */ + cmocka_unit_test_setup(rsp_slot_management_case1, libspdm_slot_management_test_setup), + /* SLOT_MGMT_CAP not set */ + cmocka_unit_test_setup(rsp_slot_management_case2, libspdm_slot_management_test_setup), + /* Unsupported SubCode */ + cmocka_unit_test_setup(rsp_slot_management_case3, libspdm_slot_management_test_setup), + /* Connection version < 1.4 */ + cmocka_unit_test_setup(rsp_slot_management_case4, libspdm_slot_management_test_setup), + /* Success case for GetBankInfo */ + cmocka_unit_test_setup(rsp_slot_management_case5, libspdm_slot_management_test_setup), + /* Success case for GetBankDetails */ + cmocka_unit_test_setup(rsp_slot_management_case6, libspdm_slot_management_test_setup), + /* Success case for GetCertificateChain */ + cmocka_unit_test_setup(rsp_slot_management_case7, libspdm_slot_management_test_setup), + /* Success case for ManageBank (+ consistency with GET_KEY_PAIR_INFO) */ + cmocka_unit_test_setup(rsp_slot_management_case8, libspdm_slot_management_test_setup), + /* Success case for ManageSlot (Erase) */ + cmocka_unit_test_setup(rsp_slot_management_case9, libspdm_slot_management_test_setup), +#if LIBSPDM_ENABLE_CAPABILITY_CSR_CAP + /* Success case for GetCSR */ + cmocka_unit_test_setup(rsp_slot_management_case10, libspdm_slot_management_test_setup), +#endif /* LIBSPDM_ENABLE_CAPABILITY_CSR_CAP */ +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP + /* Success case for SetCertificate */ + cmocka_unit_test_setup(rsp_slot_management_case11, libspdm_slot_management_test_setup), +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP */ + /* Valid-but-unadvertised SubCode -> UnsupportedRequest */ + cmocka_unit_test_setup(rsp_slot_management_case12, libspdm_slot_management_test_setup), + /* GetBankDetails BankID=0xFF (all Banks) -> UnsupportedRequest */ + cmocka_unit_test_setup(rsp_slot_management_case13, libspdm_slot_management_test_setup), + /* Access control for slots 1-7 (ManageSlot Erase) */ + cmocka_unit_test_setup(rsp_slot_management_case14, libspdm_slot_management_test_setup), + /* Malformed request negatives (MgmtStructOffset, SlotAddress.ReqLength) */ + cmocka_unit_test_setup(rsp_slot_management_case15, libspdm_slot_management_test_setup), +#if LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP + /* SetCertificate then GetCertificateChain reflects the provisioned chain */ + cmocka_unit_test_setup(rsp_slot_management_case16, libspdm_slot_management_test_setup), +#endif /* LIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP */ + /* ManageSlot Erase with CERT_INSTALL_RESET_CAP -> ResetRequired */ + cmocka_unit_test_setup(rsp_slot_management_case17, libspdm_slot_management_test_setup), + }; + + libspdm_test_context_t test_context = { + LIBSPDM_TEST_CONTEXT_VERSION, + false, + }; + libspdm_setup_test_context(&test_context); + + return cmocka_run_group_tests(test_cases, + libspdm_unit_test_group_setup, + libspdm_unit_test_group_teardown); +} + +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ diff --git a/unit_test/test_spdm_responder/test_spdm_responder.c b/unit_test/test_spdm_responder/test_spdm_responder.c index 986ef2ec916..f85ecaf85a1 100644 --- a/unit_test/test_spdm_responder/test_spdm_responder.c +++ b/unit_test/test_spdm_responder/test_spdm_responder.c @@ -44,6 +44,10 @@ int libspdm_rsp_set_key_pair_info_ack_test(void); int libspdm_rsp_set_key_pair_info_ack_error_test(void); #endif /* LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP*/ +#if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP +int libspdm_rsp_slot_management_test(void); +#endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #if LIBSPDM_RESPOND_IF_READY_SUPPORT #if (LIBSPDM_ENABLE_CAPABILITY_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_CHAL_CAP || \ LIBSPDM_ENABLE_CAPABILITY_MEAS_CAP || LIBSPDM_ENABLE_CAPABILITY_KEY_EX_CAP || \ @@ -190,6 +194,12 @@ int main(void) } #endif /* LIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP*/ + #if LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP + if (libspdm_rsp_slot_management_test() != 0) { + return_value = 1; + } + #endif /* LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP */ + #if LIBSPDM_RESPOND_IF_READY_SUPPORT #if (LIBSPDM_ENABLE_CAPABILITY_CERT_CAP || LIBSPDM_ENABLE_CAPABILITY_CHAL_CAP || \ LIBSPDM_ENABLE_CAPABILITY_MEAS_CAP || LIBSPDM_ENABLE_CAPABILITY_KEY_EX_CAP || \ From da13b4d217058162dfb17f69634cf10832bb3c13 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Thu, 4 Jun 2026 21:51:45 +0800 Subject: [PATCH 09/10] doc: Add SLOT_MANAGEMENT guide Add doc/slot_management.md describing the SPDM 1.4 SLOT_MANAGEMENT feature in libspdm: the Algorithm/Bank/Slot/KeyPair concepts and their relationships, how to enable SLOT_MGMT_CAP, the requester API, the responder HAL hooks, and the sample implementation. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- doc/slot_management.md | 320 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 doc/slot_management.md diff --git a/doc/slot_management.md b/doc/slot_management.md new file mode 100644 index 00000000000..f0ae6ad1e68 --- /dev/null +++ b/doc/slot_management.md @@ -0,0 +1,320 @@ +# Slot Management Guide + +SPDM 1.4 introduces the `SLOT_MANAGEMENT` request and `SLOT_MANAGEMENT_RESP` response (gated by +the `SLOT_MGMT_CAP` extended capability). These messages manage an endpoint's certificate slots as +storage elements, independent of the algorithm negotiated for the current connection. Unlike +`GET_CERTIFICATE`/`GET_DIGESTS`, which can only reach the slots of the currently selected algorithm, +slot management can reach the slots of every supported algorithm. + +The slot management commands are not part of any transcript hash. Per the specification, for slot 0 +they should only be issued in a trusted environment (such as secure manufacturing); for slots 1-7 +they shall only be issued in a secure session or a trusted environment. + +## Concepts: Algorithm, Bank, Slot, and Key Pair + +Slot management is built around four related but independent concepts. + +* **Algorithm** — an asymmetric signing algorithm (for example RSA2048, ECC384, or ML-DSA-65). One + algorithm is negotiated per SPDM connection in the `ALGORITHMS` response. +* **Bank** — a set of certificate slots that all use one asymmetric algorithm. A Bank is addressed + by a `BankID` (0 to 239, numbered consecutively from 0). The Responder selects a Bank from the + negotiated algorithm; `GET_CERTIFICATE` and `CHALLENGE` operate only on the selected Bank. +* **Slot** — a storage element that holds one certificate chain. A `SlotID` (0 to 7) is scoped + **within a Bank**, so a slot is addressed by the `(BankID, SlotID)` pair. The same `SlotID` in two + different Banks refers to two different slots. +* **Key pair** — an asymmetric private/public key pair identified by a `KeyPairID` (1 to + `TotalKeyPairs`), reported by `GET_KEY_PAIR_INFO`. A key pair has its own algorithm, and one key + pair can be bound to more than one slot. + +The relationships are: + +``` + negotiated algorithm ──selects──▶ Bank (BankID) + │ one asymmetric algorithm + │ contains + ▼ + Slots (SlotID, scoped to the Bank) + │ each slot holds a certificate chain + │ and is associated with a + ▼ + Key pair (KeyPairID) ──has──▶ algorithm +``` + +A Bank fixes the algorithm, but it can contain multiple slots whose key pairs all use that same +algorithm. This is the [Multiple Asymmetric Key (multikey)](multikey.md) feature: the `SlotElement` +in a `GetBankDetails` response reports the `KeyPairID` associated with each slot (populated only when +`MULTI_KEY_CONN_RSP` is `true`). + +## Enabling Slot Management + +Slot management is compiled when `LIBSPDM_ENABLE_CAPABILITY_SLOT_MGMT_CAP` is set (the default). The +responder does not impose a fixed maximum Bank count; the `GetBankInfo` response is bounded only by +the response buffer size (and `BankID` is limited to 0-239 by the specification). + +`SLOT_MGMT_CAP` is an SPDM 1.4 *extended* capability flag, negotiated over `GET_CAPABILITIES`. Set it +on the Responder before the connection with `libspdm_set_data` and `LIBSPDM_DATA_CAPABILITY_EXT_FLAGS`: + +```c +data16 = SPDM_GET_CAPABILITIES_EXTENDED_RESPONSE_FLAGS_SLOT_MGMT_CAP; +libspdm_set_data(spdm_context, LIBSPDM_DATA_CAPABILITY_EXT_FLAGS, ¶meter, + &data16, sizeof(data16)); +``` + +## Requester API + +After the connection has transitioned to `LIBSPDM_CONNECTION_STATE_NEGOTIATED` (SPDM 1.4 with +`SLOT_MGMT_CAP` negotiated), the Requester can call the following functions. Each takes the SPDM +context and an optional `session_id` (`NULL` for a normal message, non-`NULL` for a secured message). + +| Function | SubCode | +| -------- | ------- | +| `libspdm_slot_management_get_supported_subcodes` | `SupportedSubCodes` | +| `libspdm_slot_management_get_bank_info` | `GetBankInfo` | +| `libspdm_slot_management_get_bank_details` | `GetBankDetails` | +| `libspdm_slot_management_get_certificate_chain` | `GetCertificateChain` | +| `libspdm_slot_management_get_csr` | `GetCSR` | +| `libspdm_slot_management_manage_bank` | `ManageBank` | +| `libspdm_slot_management_manage_slot` | `ManageSlot` | +| `libspdm_slot_management_set_certificate` | `SetCertificate` | + +### Typical Requester flow + +1. Call `libspdm_init_connection` and check that it is successful and that the negotiated version is + at least 1.4. +2. Call `libspdm_slot_management_get_supported_subcodes` to discover which SubCodes the Responder + supports. Each subsequent SubCode should be issued only if its bit is set in the returned bit map + (the bit position is the SubCode value). +3. Call `libspdm_slot_management_get_bank_info` to enumerate the Banks and obtain each Bank's + `SlotMask`. +4. For each Bank, call `libspdm_slot_management_get_bank_details` to read the Bank's algorithm fields + and the per-slot `SlotElement`s (including the `KeyPairID` for each slot when the connection is + multikey). +5. For each existing `(BankID, SlotID)`, call `libspdm_slot_management_get_certificate_chain` to read + the certificate chain. +6. Optionally call `libspdm_slot_management_get_csr`, `libspdm_slot_management_manage_bank`, + `libspdm_slot_management_manage_slot`, or `libspdm_slot_management_set_certificate`. + +## Responder HAL + +The Responder dispatches `SLOT_MANAGEMENT` in `libspdm_get_response_slot_management` and obtains the +device-specific information through the following HAL hooks (in +`hal/library/responder/slot_mgmt.h`). An Integrator implements these for the device. + +| Hook | Purpose | +| ---- | ------- | +| `libspdm_read_slot_management_supported_subcodes` | Return the supported SubCode bit map. | +| `libspdm_read_slot_management_bank_info` | Return the per-Bank info (BankID, SlotMask). | +| `libspdm_read_slot_management_bank_details` | Return a Bank's algorithm fields and per-slot info, including each slot's certificate chain digest. | +| `libspdm_read_slot_management_certificate_chain` | Return the certificate chain for a `(BankID, SlotID)`. The chain need not be provisioned into the SPDM context. | +| `libspdm_write_slot_management_bank` | Configure a Bank (`ManageBank`). | +| `libspdm_write_slot_management_slot` | Perform a slot operation such as erase (`ManageSlot`). | + +The `GetCSR` and `SetCertificate` SubCodes reuse the existing `GET_CSR` and `SET_CERTIFICATE` HAL +hooks (`libspdm_gen_csr_ex` and `libspdm_write_certificate_to_nvm`). To address a Bank, these hooks +take a `bank_id` parameter. The legacy `GET_CSR` and `SET_CERTIFICATE` flows, which have no Bank +concept, pass `LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID` to indicate that no Bank is addressed. + +## Sample Implementation + +The `spdm_device_secret_lib_sample` provides a reference implementation in +`os_stub/spdm_device_secret_lib_sample/slot_management.c`. It models the Banks per the specification: + +* The Banks are derived from the key pairs reported by `GET_KEY_PAIR_INFO`, grouped by their + configured asymmetric algorithm — one Bank per distinct algorithm. This keeps the slot management + responses consistent with `GET_KEY_PAIR_INFO`. +* A Bank can contain multiple slots, each associated with a different `KeyPairID`, as long as all of + those key pairs use the Bank's algorithm. +* A slot's certificate chain and digest are read on demand for the Bank's algorithm; a slot is + reported only if its certificate chain is readable in the current build. +* The supported SubCode bit map (`m_libspdm_slot_management_sub_code_bitmap`) and the Bank attributes + (`m_libspdm_slot_management_bank_attributes`) are non-`static` globals that the Integrator can + override. +* `ManageBank` `ConfigAlgo` succeeds (idempotently) when the Requester selects the Bank's existing + algorithm, and is rejected for a different algorithm because the Bank's slots are provisioned. +* `ManageSlot` `Erase` removes a slot's certificate chain by writing a zero-length certificate NVM + file (exactly as the base `SET_CERTIFICATE` erase does via `libspdm_write_certificate_to_nvm`), + after which the slot no longer appears in `GetBankInfo`/`GetBankDetails` and `GetCertificateChain` + for it fails. When `CERT_INSTALL_RESET_CAP` is advertised, the erase returns + `ERROR(ResetRequired)`, mirroring `SET_CERTIFICATE`. +* A slot's populated/empty state is the single source of truth shared with `SET_CERTIFICATE`: it is + derived on demand from the certificate store (a runtime-provisioned NVM file if present, otherwise + the static certificate bundle), not cached in a separate flag. See [Command Sync](#command-sync). + +## Command Sync + +Several commands read or write the same underlying certificate/key state, so an implementation must +keep them consistent. The relevant commands are `GET_DIGESTS`, `GET_CERTIFICATE`, `SET_CERTIFICATE`, +`GET_CSR`, `GET_KEY_PAIR_INFO`/`SET_KEY_PAIR_INFO`, and the `SLOT_MANAGEMENT` SubCodes +(`GetBankInfo`, `GetBankDetails`, `GetCertificateChain`, `GetCSR`, `ManageBank`, `ManageSlot`, +`SetCertificate`). + +The shared state is, per DSP0274: + +* **Certificate chain per slot.** For the Bank selected by the negotiated algorithm, the + `SLOT_MANAGEMENT` slots are the *same* slots that `GET_CERTIFICATE`/`GET_DIGESTS`/`CHALLENGE` + operate on. A `SET_CERTIFICATE` (legacy or the `SLOT_MANAGEMENT` `SetCertificate` SubCode) or a + `ManageSlot`/`SET_CERTIFICATE` `Erase` therefore changes what every one of those commands reports + for that slot. +* **Key pair per slot.** `GET_KEY_PAIR_INFO` reports the key pairs and their associated certificate + slots; `SET_KEY_PAIR_INFO` (`GenerateKeyPair`/`KeyPairErase`) changes them. The `SLOT_MANAGEMENT` + Bank model is derived from `GET_KEY_PAIR_INFO` (one Bank per configured key-pair algorithm), so a + key-pair change is reflected in the Bank/slot enumeration. +* **CSR state.** `GET_CSR` (legacy and the `SLOT_MANAGEMENT` `GetCSR` SubCode) share one outstanding + CSR / `CSRTrackingTag` space, managed by the Responder as a device-global pool (1..7). A CSR is + **not slot-scoped**: the `SLOT_MANAGEMENT` `GetCSR` ignores the `SlotID` field and the legacy + `GET_CSR` has no slot address; a CSR is for a `KeyPairID`, not a slot. + +### Slot state machine + +A certificate slot is in exactly one of four states (DSP0274 "Certificate slots"). The state is +observable through `GET_DIGESTS` (`SupportedSlotMask`/`ProvisionedSlotMask`) and the `SLOT_MANAGEMENT` +`GetBankInfo`/`GetBankDetails` responses (`SlotMask`, `SlotAttributes.Provisioned`, +`CertificateInfo`). (`CHALLENGE_AUTH` carries a slot mask in `Param2`, but its bit is set only for the +"Exists with key and cert" state, not the `ProvisionedSlotMask` definition.) Several of these reported +values are **multikey-dependent** — see the per-field, +per-mode breakdown in the [slot management data model](slot_management_database.md#slot-states), +which is the authoritative reference for what each wire field reports in each state. + +``` + +-------------------------+ + | 1. Does not exist | + | Supported=0 | (fixed for the + +-------------------------+ connection) + + ------------------------------------------------------------------------ + exists (Supported=1) + + +-------------------------+ + | 2. Exists and empty | + | Provisioned=0 | + | no key, no cert | + +-------------------------+ + | ^ + GenerateKeyPair | | KeyPairErase + (SET_KEY_PAIR_INFO) v | (SET_KEY_PAIR_INFO) + +-------------------------+ + | 3. Exists with key | + | Provisioned=1 (mk)/0 | + | CertificateInfo=0 | + | AssocCertSlotMask=1 | + | key, no cert | + +-------------------------+ + | ^ + SetCertificate | | Erase + SET_CERTIFICATE v | ManageSlot Erase / SET_CERTIFICATE Erase + +-------------------------+ + | 4. Exists with key | + | and cert | + | Provisioned=1 | + | CertificateInfo!=0(mk)| + | /0 (non-mk) | + | key + cert | + +-------------------------+ + + Notes: + - "(mk)" marks a value that holds only in a multikey connection; the + non-multikey value follows it. See the state table below and the data + model for the full per-mode breakdown. + - SetCertificate may also go directly 2 -> 4 (install cert when no separate + key step is modeled). Erase keeps the key (4 -> 3); it only reaches 2 if + the slot has no key. + - In the sample, states 1, 2, and 4 are reachable; state 3 is not + (see Known gaps, #3645). +``` + +| State | `DIGESTS.SupportedSlotMask` /
`SLOT_MANAGEMENT.BankInfo.SlotMask` | `DIGESTS.ProvisionedSlotMask` | `DIGESTS.CertificateInfo` /
`SLOT_MANAGEMENT.BankDetails.CertificateInfo` | `CHALLENGE_AUTH.Param2` /
`SLOT_MANAGEMENT.BankDetails.SlotAttributes.Provisioned` | `KEY_PAIR_INFO.AssocCertSlotMask` | +| ----- | --------------------------- | ----------------------------- | --------------------------------------------- | -------------------------------------------------------- | --------------------------------- | +| 1. Does not exist | 0 | 0 | 0 | 0 | 0 | +| 2. Exists and empty | 1 | 0 | 0 | 0 | 0 | +| 3. Exists with key | 1 | 1 (multikey) / 0 (non-multikey) | 0 | 0 | 1 | +| 4. Exists with key and cert | 1 | 1 | non-zero (multikey) / 0 (non-multikey) | 1 | 1 | + +The mode-dependent cells follow DSP0274: `DIGESTS.ProvisionedSlotMask` sets a slot's bit for an +associated key (state 3) only in multikey (Table 41); `CertificateInfo` (`CertModel`) is 0 in a +non-multikey connection regardless of the stored cert (Table 42); `BankDetails.SlotAttributes.Provisioned` +is cert-only (set only when a certificate chain is present, so 0 for state 3 — Table 152); and +`KEY_PAIR_INFO.AssocCertSlotMask` carries the key↔slot association with no multikey gate, so it is 1 +for states 3 and 4 in both modes. See the [data model](slot_management_database.md#slot-states) for +the full table. + +The commands that read or change the state: + +| Command | Effect on the slot state | +| ------- | ------------------------ | +| `GET_DIGESTS` | Read only. Reports a digest (and per-slot `KeyPairID`) for states 3-4; reserved/zero for 1-2. | +| `GET_CERTIFICATE` | Read only. Returns the chain in state 4; `ERROR(InvalidRequest)` otherwise. | +| `SLOT_MANAGEMENT` `GetBankInfo` / `GetBankDetails` / `GetCertificateChain` | Read only. Enumerate existing slots (states 2-4) and report each slot's attributes / chain. | +| `SLOT_MANAGEMENT` `GetCSR`, `GET_CSR` | Read only with respect to slot state (operate on CSR/key state, not the slot's certificate). | +| `SET_CERTIFICATE` (write), `SLOT_MANAGEMENT` `SetCertificate` | Install a certificate chain: 2 or 3 -> 4. | +| `SET_CERTIFICATE` (Erase), `SLOT_MANAGEMENT` `ManageSlot` `Erase` | Remove the certificate chain, keeping the key: 4 -> 3 (or -> 2 if the slot has no key). Does not erase the key. | +| `SET_KEY_PAIR_INFO` `GenerateKeyPair` | Associate/generate a key for a slot: 2 -> 3. Requires the key pair to have no associated certificate slot, so it does not act on a slot already in state 4. | +| `SET_KEY_PAIR_INFO` `KeyPairErase` | Remove the key: 3 -> 2. Requires the key pair to have no associated certificate slot. | +| `SLOT_MANAGEMENT` `ManageBank` `ConfigAlgo` | Does not change a slot's state directly, but requires no slot in the Bank to have a certificate provisioned (no slot in state 4; states 1/2/3 are allowed) to change the Bank's algorithm — else `ERROR(InvalidState)` (DSP0274 Table 141). On success the Responder clears all slot settings in the Bank. `ConfigAlgo` must select exactly one algorithm. | + +State 1 is fixed for the life of the connection: a slot that does not exist stays non-existent. In +the sample, states 1, 2, and 4 are reachable; state 3 is not (see +[Known gaps](#known-gaps), [#3645](https://github.com/DMTF/libspdm/issues/3645)). + +### Current status + +The sample implementation (`spdm_device_secret_lib_sample`) keeps these consistent as follows: + +* **One certificate store.** `SET_CERTIFICATE`, the `SLOT_MANAGEMENT` `SetCertificate` SubCode, and + `ManageSlot` `Erase` all go through `libspdm_write_certificate_to_nvm`, which writes a per-slot NVM + file (`slot_id__cert_chain.der` for the BankID-less legacy flow, or + `bank_id__slot_id__cert_chain.der` for a Bank-qualified write). The `SLOT_MANAGEMENT` read + path (`GetCertificateChain`, and the slot enumeration in `GetBankInfo`/`GetBankDetails`) reads the + same NVM file first and only falls back to the static certificate bundle when no runtime file + exists. An erase writes a zero-length file, which the read path reports as empty without falling + back. There is no separate in-memory "erased" flag, so the read and write paths cannot disagree. +* **Selected Bank aliases the legacy slot.** For the selected Bank, the read and erase paths also + consult the BankID-less legacy file, so a base `SET_CERTIFICATE`/`Erase` on the in-use slot and a + `SLOT_MANAGEMENT` read of that slot stay consistent. +* **Reset semantics.** Both `SET_CERTIFICATE` and `ManageSlot` `Erase` return `ERROR(ResetRequired)` + only when `CERT_INSTALL_RESET_CAP` is advertised, using the same `need_reset`/`is_busy` HAL + signaling. +* **Banks track key pairs.** The Bank table is rebuilt from `GET_KEY_PAIR_INFO`, so the algorithms + and slot associations reported by `SLOT_MANAGEMENT` match `GET_KEY_PAIR_INFO`. +* **Shared CSR path.** The `SLOT_MANAGEMENT` `GetCSR` SubCode reuses the `GET_CSR` HAL hook + (`libspdm_gen_csr_ex`), so both share one CSR/`CSRTrackingTag` state. + +### Known gaps + +These are known limitations in the current libspdm/sample behavior. They are recorded here and are +not addressed by the slot management implementation. Each links to its tracking issue. + +* **`SET_CERTIFICATE` does not update the live served certificate** + ([#873](https://github.com/DMTF/libspdm/issues/873)). A successful `SET_CERTIFICATE` (or `Erase`) + writes the certificate NVM but does not update the in-memory `local_cert_chain_provision[]` store + that `GET_CERTIFICATE`/`CHALLENGE`/`KEY_EXCHANGE` actually serve from. The intended model is: + write NVM → `ResetRequired` → device reset → the Integrator reloads `local_cert_chain_provision[]` + from NVM on the next boot. Consequently, when `CERT_INSTALL_RESET_CAP` is *not* advertised, a + `SET_CERTIFICATE`/`Erase` returns success but has no effect on what `GET_CERTIFICATE` serves + within the same boot. (The AliasCert model adds a further wrinkle: a complete chain may need the + Alias cert from slot 0 appended.) The same NVM file is what the `SLOT_MANAGEMENT` read path reads, + so the two are consistent at the NVM level, but neither updates the live served chain without a + reload. +* **The key-pair-to-slot association is inconsistent across commands in the sample** + ([#3638](https://github.com/DMTF/libspdm/issues/3638)). The association between a `KeyPairID` and a + certificate slot should be one consistent device property, but the sample/emu fixtures expose three + contradictory views of it: + 1. `GET_KEY_PAIR_INFO` reports each key pair's `AssocCertSlotMask` (one bit per associated slot); + 2. `GET_DIGESTS` reports a per-slot `KeyPairID` from a separate array (the emu uses `0xA0 + slot` + on the responder and `0xB0 + slot` on the requester, which are not valid indices into the + key-pair table); + 3. each populated slot holds a certificate chain for the single negotiated algorithm, which can + conflict with the per-key-pair algorithm asserted by `GET_KEY_PAIR_INFO`. + For consistency, for every slot X, `GET_DIGESTS.KeyPairID[X]` should equal the `KeyPairID` whose + `AssocCertSlotMask` has bit X set, and the slot's certificate algorithm should match that key + pair's algorithm. The values are format-legal (no library "shall" is violated), but the + inconsistency makes the sample unsuitable as a reference for the association semantics and can + mislead Integrators. The `SLOT_MANAGEMENT` Bank model derives its per-slot `KeyPairID` from + `GET_KEY_PAIR_INFO`, so it inherits whatever the sample's key-pair table reports. +* **The "Exists with key" slot state is not reachable** + ([#3645](https://github.com/DMTF/libspdm/issues/3645)). DSP0274 defines a slot state where a key + pair is associated with a slot but no certificate chain is present. No certificate-side command + produces this state (an `Erase` removes the certificate but not the key, and `GenerateKeyPair` + requires a key pair with no associated certificate slot), so a certificate slot reaches only the + "does not exist", "exists and empty", and "exists with key and cert" states in the sample. + Reaching the "exists with key" state would require an internal key-only provisioning hook, which + the sample does not wire up. From 53d2dc8ebe9785906f5280c4e55a472b12bb10d9 Mon Sep 17 00:00:00 2001 From: Jiewen Yao Date: Sat, 6 Jun 2026 15:38:33 +0800 Subject: [PATCH 10/10] doc: Add slot management relational data model proposal Add doc/slot_management_database.md, a reference data model for the device certificate/key/slot store, expressed as C structures. It normalizes the state shared by GET_DIGESTS, GET_CERTIFICATE, SET_CERTIFICATE, GET_CSR, GET_KEY_PAIR_INFO/SET_KEY_PAIR_INFO, and the SLOT_MANAGEMENT SubCodes into one authoritative copy of each fact (banks, slots, key pairs, slot-key associations, certificate chains, CSRs), so a single write propagates to every command's view. The document defines the four slot states, maps every field of the relevant DSP0274 wire structures to its schema source, and shows how the model resolves the recorded gaps (#873, #3638, #3645). It is a device-backend data model, not an SPDM wire change. Signed-off-by: Jiewen Yao Assisted-by: Claude Code:claude-opus-4-8 --- doc/slot_management_database.md | 1496 +++++++++++++++++++++++++++++++ 1 file changed, 1496 insertions(+) create mode 100644 doc/slot_management_database.md diff --git a/doc/slot_management_database.md b/doc/slot_management_database.md new file mode 100644 index 00000000000..338faca5ec1 --- /dev/null +++ b/doc/slot_management_database.md @@ -0,0 +1,1496 @@ +# Slot Management — Relational Data Model Proposal + +## Purpose + +The SPDM 1.4 certificate/key/slot state is exposed through several commands that today read and +write **independent stores** in the libspdm sample (`GET_DIGESTS`, `GET_CERTIFICATE`, +`SET_CERTIFICATE`, `GET_CSR`, `GET_KEY_PAIR_INFO`/`SET_KEY_PAIR_INFO`, and the `SLOT_MANAGEMENT` +SubCodes). Because each command has its own view, a change made through one command is not always +reflected by the others — see the [Known gaps](slot_management.md#known-gaps) in the slot management +guide (issues [#873](https://github.com/DMTF/libspdm/issues/873), +[#3638](https://github.com/DMTF/libspdm/issues/3638), +[#3645](https://github.com/DMTF/libspdm/issues/3645)). + +This document proposes a single normalized data model — a small relational schema, expressed as C +structures — for the device secret store. With one schema and a set of integrity constraints, **any +change propagates to every command's view automatically**, because every command reads the same +tables. The C structures are implementation-ready: an Integrator (or the libspdm sample) can use +them directly. It is a device-backend data model, not an SPDM wire change. + +## Why a relational model + +The SPDM concepts map cleanly onto relations: + +* a device has many **key pairs** (`KeyPairID`); +* a device has many **banks** (`BankID`), each fixing one asymmetric algorithm; +* a bank has many **slots** (`SlotID`, scoped to the bank); +* a slot may hold one **certificate chain**, and may have an outstanding **CSR**; +* a slot is associated with at most one key pair, and a key pair may back many slots. + +The contradictions in the gaps are exactly the symptoms of an **un-normalized** store: the same fact +(which key pair backs a slot, what algorithm a slot uses, whether a slot has a cert) is recorded in +three places that can drift. Normalizing to one authoritative copy of each fact, with foreign keys +and triggers, removes the drift by construction. + +## Entities and relationships + +``` + +-------------+ +----------------+ + | bank | algorithm-agreement | key_pair | + | (BankID, |- - - - - - - - - - - - - - - - | (KeyPairID, | + | algo) | bank.algo == key_pair algo | current algo) | + +-------------+ +----------------+ + | 1 | 1 + | has | backs + | * | * (Shareable) + +-------------+ 1 0..1 +------------------+| + | slot |---------------------| slot_key_assoc |+ + | (BankID, | has key assoc | (BankID, SlotID, | + | SlotID) | | KeyPairID) | + +-------------+ +------------------+ + | 1 + has cert| + (0..1) | + v + +-----------+ +-------------------------------+ + | cert_chain| | csr (device-global pool, | + +-----------+ | keyed by CSRTrackingTag 1..7;| + | identifies key by KeyPairID) | + +-------------------------------+ + : not slot-scoped -- GetCSR ignores SlotID + (legacy GET_CSR has no slot address); + bank_id retention is open (see #1) + + (algorithm is a value type, libspdm_db_algo_t, embedded in bank.algo and + key_pair.current_*_algo; it is not a separate stored table.) +``` + +* `bank` 1—* `slot` : a slot belongs to exactly one bank. +* `slot` 1—0..1 `slot_key_assoc` *—1 `key_pair` : a slot is associated with at most one key pair; a + key pair may back many slots (`ShareableCap`). Modeled as an association table so the multikey + many-to-one is explicit and constrained. +* `slot` 1—0..1 `cert_chain` : a slot holds at most one certificate chain. +* `csr` is a device-global pool keyed by `CSRTrackingTag` (1..7), not a per-slot relation; an entry + identifies its key material by `KeyPairID`. It is **not slot-scoped** — `SLOT_MANAGEMENT GetCSR` + ignores `SlotID` and the legacy `GET_CSR` has no slot address. Whether an entry should retain a + `bank_id` is an [open spec question](#open-spec-questions) (#1). +* `bank.algo` (the bank's configured algorithm) and `key_pair`'s current algorithm + (`current_asym_algo` / `current_pqc_asym_algo`) are `libspdm_db_algo_t` values, not rows in a + catalog table; a slot's key pair must use its bank's algorithm (the algorithm-agreement + constraint, enforced by `libspdm_db_associate_slot_key()`). + +## Slot states + +This document refers to a certificate slot's "state 1..4" throughout. These are the four states +DSP0274 ("Certificate slots") defines for a slot, observable through the `SupportedSlotMask` / +`ProvisionedSlotMask` of `GET_DIGESTS`, the `Param2` slot mask of `CHALLENGE_AUTH` (set only for the +"Exists with key and cert" state), and the `SLOT_MANAGEMENT` `GetBankInfo`/`GetBankDetails` responses. +They are defined here so this document is self-contained: + +A slot is in one of four states. The state itself is a device fact and is the **same in both +multikey and non-multikey** connections; the columns below give the value each wire field reports for +that state, naming each field by its full path. Columns whose value is identical across every state +are merged into one. The `DIGESTS.ProvisionedSlotMask` and the merged `CertificateInfo` columns show +where the *reported* value differs by mode (see the per-mode notes after the table): + +| State | Name | Meaning | `DIGESTS.SupportedSlotMask` /
`SLOT_MANAGEMENT.BankInfo.SlotMask` | `DIGESTS.ProvisionedSlotMask` | `DIGESTS.CertificateInfo` /
`SLOT_MANAGEMENT.BankDetails.CertificateInfo` | `CHALLENGE_AUTH.Param2` /
`SLOT_MANAGEMENT.BankDetails.SlotAttributes.PROVISIONED` | `KEY_PAIR_INFO.AssocCertSlotMask` | +| ----- | ---- | ------- | --------------------------- | ----------------------------- | --------------------------------------------- | -------------------------------------------------------- | --------------------------------- | +| 1 | Does not exist | the slot is not present in the bank | 0 | 0 | 0 | 0 | 0 | +| 2 | Exists and empty | the slot exists but has no key and no certificate | 1 | 0 | 0 | 0 | 0 | +| 3 | Exists with key | a key pair is associated, but no certificate chain | 1 | 1 (multikey) / 0 (non-multikey) | 0 | 0 | 1 | +| 4 | Exists with key and cert | fully provisioned: key pair associated and a certificate chain present | 1 | 1 | non-zero (multikey) / 0 (non-multikey) | 1 | 1 | + +> NOTE: The state-3 `DIGESTS.ProvisionedSlotMask` value `1 (multikey) / 0 (non-multikey)` is derived +> from the SPDM specification's `ProvisionedSlotMask` field (the `Param2` row of Table 41 — Successful +> DIGESTS response message format). That field sets the slot's bit if the slot has a certificate +> chain, or — only when `MULTI_KEY_CONN_REQ`/`RSP` is true — if it has an associated key pair. A +> state-3 slot has a key but no certificate, so the bit is set only in multikey mode. Note this +> differs from `SLOT_MANAGEMENT.BankDetails.SlotAttributes.PROVISIONED`, which is cert-only ("set to 1 +> if the slot contains a certificate chain", Table 152) and so is 0 for state 3 in both modes. +> `KEY_PAIR_INFO.AssocCertSlotMask` carries the key↔slot association with no multikey gate, so it is +> 1 for states 3 and 4 in both modes. + +DSP0274 §"Certificate slots" names this four-state model in the context of multikey, but the +underlying states exist regardless of mode; what changes is which states are *reachable* and how each +is *reported*: + +* **Multikey** (`MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` true): all four states are reachable and + fully reported. `KeyPairID` and `CertModel` are reported per slot, and `Provisioned` is 1 for + state 3 (the associated-key clause of `ProvisionedSlotMask` applies). +* **Non-multikey** (both false): only states 1, 3, and 4 are reachable — **state 2 ("exists and + empty") does not occur**, because a non-multikey endpoint has a single key pair per supported + algorithm (DSP0274 §"Certificates and certificate chains") that implicitly backs every existing + slot, so an existing slot always has a key. The per-slot association is **not reported in + `DIGESTS`/`SlotElement`** — `KeyPairID` and `CertModel` are forced to 0 (DSP0274 Tables 41, 42, 152) + and `ProvisionedSlotMask` reflects only the certificate, so a state-3 slot reports `Provisioned = 0` + and is indistinguishable from state 2 in the `DIGESTS` view. The state-3 association is still + observable, though, via `KEY_PAIR_INFO`.`AssocCertSlotMask`, which DSP0274 does not gate on multikey. + +In this schema the state is **derived** from which `libspdm_db_t` array elements are present for the +slot (see [The slot state is derived, never stored](#the-slot-state-is-derived-never-stored)): +`slot[bank_id][slot_id]` not present = state 1; `slot` present only = state 2; `slot` + +`slot_key_assoc[bank_id][slot_id]` = state 3; `slot` + `slot_key_assoc` + `cert_chain[bank_id][slot_id]` += state 4. This stored state is independent of multikey mode; what multikey changes is only how the +association is reported on the wire (see that section). + +## Schema (C structures) + +The schema is given as C structures so it can be used by an implementation directly. Each structure +is one "table"; the device store is a set of fixed-capacity arrays of these rows. Primary keys, +foreign keys, and constraints are stated in comments and enforced by the access functions (see +[Access API](#access-api)); the relational semantics are what matter, not the storage form. + +Field names follow the DSP0274 wire field names where one exists, so the mapping in +[Mapping wire structures to the schema](#mapping-wire-structures-to-the-schema) is one-to-one. Bit +masks reuse the existing libspdm encodings (`SPDM_KEY_PAIR_CAP_*`, `SPDM_KEY_USAGE_CAPABILITIES_*`, +the Table 113/114 algorithm bitmaps, `SPDM_CERTIFICATE_INFO_CERT_MODEL_*`). + +**Indexing and presence conventions** (used throughout): + +* Rows are stored in dense arrays indexed by their key: `bank[bank_id]`, `slot[bank_id][slot_id]`, + and (1-based key) `key_pair[key_pair_id - 1]`. The `csr` pool is indexed by `tracking_tag - 1`. +* A row also stores its own key (e.g. `slot.bank_id`/`slot.slot_id`) for serialization clarity and + cross-checking; the stored key **must equal** the array index. It is redundant by construction, + not a second source of truth — access functions never look a row up by its stored key. +* "Row exists" is the `present` flag for the sparse relations (`slot`, `slot_key_assoc`, + `cert_chain`, `csr`) and the count (`num_banks`, `total_key_pairs`) for the dense contiguous ones + (`bank`, `key_pair`). + +```c +#define LIBSPDM_DB_FORMAT_VERSION 1 /* on-storage layout version of libspdm_db_t */ +#define LIBSPDM_DB_MAX_KEY_PAIRS 16 /* device-defined: 1..TotalKeyPairs */ +#define LIBSPDM_DB_MAX_BANKS 16 /* BankID 0..239; capacity is device-defined */ +#define LIBSPDM_DB_MAX_SLOTS SPDM_MAX_SLOT_COUNT /* SlotID 0..7 per bank */ +#define LIBSPDM_DB_MAX_CSR 7 /* CSRTrackingTag pool, 1..7 (DSP0274) */ +/* For simplicity this model treats a PQC algorithm bitmap (Table 114) as 4 bytes, like the + * traditional algorithm bitmap (Table 113), so both use uint32_t and need no length field. */ + +/* CSR transaction state (libspdm_db_csr_t.state) for the reset-required GET_CSR flow. */ +#define LIBSPDM_DB_CSR_STATE_FREE 0 /* entry unused (present == false) */ +#define LIBSPDM_DB_CSR_STATE_PENDING_RESET 1 /* tag reserved, GET_CSR returned ResetRequired; + * der not yet generated (awaiting device reset) */ +#define LIBSPDM_DB_CSR_STATE_READY 2 /* der generated and retrievable by tracking_tag */ + +/* ---- libspdm_db_algo_t: an asymmetric algorithm value ------------------------------------- + * This is a value TYPE, not a stored catalog table: an algorithm is a spec-defined wire bit + * (DSP0274 Table 113 traditional / Table 114 PQC), not a device-specific row, so there is nothing to + * normalize into a separate table and the schema embeds this value where an algorithm is needed. + * At most one of asym_algo / pqc_asym_algo is non-zero; all-zero means "no/unconfigured algorithm". + * Two libspdm_db_algo_t values are equal iff both fields are equal, which is how the bank<->key_pair + * algorithm-agreement check compares them. */ +typedef struct { + uint32_t asym_algo; /* Table 113 traditional bit, or 0 */ + uint32_t pqc_asym_algo; /* Table 114 PQC bit, or 0 */ +} libspdm_db_algo_t; + +/* ---- key_pair: one row per KeyPairID (GET_KEY_PAIR_INFO, Table 111) ------------------------- + * KeyPairIDs are contiguous 1..total_key_pairs (0 is invalid: it means "no key pair"), so the store + * uses a COUNT (total_key_pairs) rather than a per-row presence flag. Index this array by + * (key_pair_id - 1) ONLY after checking key_pair_id != 0; a key_pair_id of 0 (e.g. an unassociated + * slot) has no key_pair row and must not be used to index this array. */ +typedef struct { + uint8_t key_pair_id; /* [RO] PK. Table 111 KeyPairID (== index + 1) */ + /* capabilities (Table 112) - [RO] immutable device facts */ + uint16_t capabilities; /* [RO] SPDM_KEY_PAIR_CAP_* (GenKey/Erasable/CertAssoc/ + * KeyUsage/AsymAlgo/Shareable ...) */ + uint16_t key_usage_capabilities; /* [RO] Table 111 KeyUsageCapabilities (key-usage bitmask) */ + uint32_t asym_algo_capabilities; /* [RO] Table 111 AsymAlgoCapabilities (Table 113 bitmap) */ + uint32_t pqc_asym_algo_capabilities; /* [RO] Table 111 PqcAsymAlgoCapabilities (Table 114 bitmap)*/ + /* current configuration - [RW] mutable via SET_KEY_PAIR_INFO */ + uint16_t current_key_usage; /* [RW] Table 111 CurrentKeyUsage (subset of *_capabilities)*/ + uint32_t current_asym_algo; /* [RW] Table 111 CurrentAsymAlgo (Table 113), 0 = unset */ + uint32_t current_pqc_asym_algo; /* [RW] Table 111 CurrentPqcAsymAlgo (Table 114), 0 = unset */ + /* key material (state of the key itself) - [RW] via SET_KEY_PAIR_INFO GenerateKeyPair/KeyPairErase */ + uint16_t public_key_info_len; /* [RW] Table 111 PublicKeyInfoLen; 0 => key absent/ungen */ + uint8_t public_key_info[LIBSPDM_DB_PUBKEY_INFO_MAX]; /* [RW] Table 111 PublicKeyInfo (DER) */ + /* INVARIANTS (enforced by access fns): + * - current_key_usage is a subset of key_usage_capabilities + * - at most one bit total across current_asym_algo + current_pqc_asym_algo + * - that one current bit is within asym/pqc_asym_algo_capabilities + * - public_key_info_len == 0 <=> no slot_key_assoc may reference this key pair + * - a key pair maps to AT MOST ONE bank: the bank whose algo equals the key pair's current + * algorithm. This holds because configured-bank algorithms are unique (see bank). An + * unconfigured key pair (current_*_algo all-zero) maps to NO bank, and "the key pair's + * bank" (used by AssocCertSlotMask) is undefined for it -- such a key pair must have no + * slot_key_assoc row, which is guaranteed by the bank-algorithm-match check in + * libspdm_db_associate_slot_key() (an all-zero key-pair algo cannot match a configured + * bank, so association is rejected). + * NOTE: AssocCertSlotMask is NOT stored here; it is derived from slot_key_assoc + * (see mapping), so it cannot drift from the slot view. The bank for that derivation is + * the (unique) bank whose algo matches this key pair's current algorithm. */ +} libspdm_db_key_pair_t; + +/* ---- bank: one row per BankID (GetBankInfo Table 150 / GetBankDetails Table 151) ----------- + * Per DSP0274, BankIDs are contiguous 0..(num_banks-1) (max 239), so the store uses a COUNT + * (num_banks, the analogue of GetBankInfo.NumBankElements) rather than a per-row presence flag. + * Index this array by bank_id. + * + * Uniqueness: at most one bank per algorithm (DSP0274 AvailableAsymAlgo clears bits already assigned + * to another bank). This applies only to CONFIGURED banks: an all-zero algo means "not yet + * configured" and is the NULL sentinel, so it is EXCLUDED from the uniqueness rule -- any number of + * banks may be unconfigured (algo all-zero) at once. Concretely: for any two banks i != j with + * non-all-zero algo, algo[i] != algo[j]. */ +typedef struct { + uint8_t bank_id; /* [RO] PK. Table 150/151 BankID (== index) */ + libspdm_db_algo_t algo; /* [RW] the bank's CurrentAsymAlgo/CurrentPqcAsymAlgo + * (libspdm_db_algo_t value); all-zero => not configured. + * Written by ManageBank ConfigAlgo. */ + uint8_t bank_attributes; /* [RO] Table 151 BankAttributes (SPDM_SLOT_MANAGEMENT_BANK_ + * ATTRIBUTE_*). Only the non-derived CONFIG_ALGO bit is + * stored; the SELECTED bit is NOT stored (derived per + * connection as bank.algo == negotiated algo) and is + * OR'd in at read time. */ +} libspdm_db_bank_t; + +/* ---- slot: one row per existing (BankID, SlotID) ------------------------------------------- + * Primary key: (bank_id, slot_id). FK: bank_id -> bank. + * Row EXISTS <=> slot state is "exists" (states 2/3/4). No row <=> state 1 "does not exist". */ +typedef struct { + uint8_t bank_id; /* [RO] PK part, FK->bank */ + uint8_t slot_id; /* [RO] PK part (0..7) */ + bool present; /* [RO] false => slot does not exist in this bank (state 1); + * slot existence is a device fact, fixed for the connection */ + uint8_t slot_attributes; /* [RO] Table 152 SlotAttributes (SPDM_SLOT_MANAGEMENT_SLOT_ + * ATTRIBUTE_*). Only the non-derived WRITE_PROTECTED bit + * is stored; the PROVISIONED bit is NOT stored (derived + * as cert_chain[bank_id][slot_id].present) and is OR'd in at read. */ + bool modifiable; /* [RO] BankElement.ModifiableSlotMask bit: erasable+writable */ +} libspdm_db_slot_t; + +/* ---- slot_key_assoc: slot <-> key pair (the multikey relation) ----------------------------- + * Primary key: (bank_id, slot_id) -> a slot has AT MOST ONE key pair. + * FK: (bank_id, slot_id) -> slot ; key_pair_id -> key_pair. + * A key pair may back MANY slots (Shareable). This single table is the ONE authoritative copy + * of the association; all three wire views are projections of it (see mapping), so: + * AssocCertSlotMask(key_pair_id) = { slot_id : assoc row has this key_pair_id, same bank } + * SlotElement.KeyPairID(slot) = assoc row's key_pair_id + * GET_DIGESTS.KeyPairID[slot] = assoc row's key_pair_id + * They cannot disagree because they read the same rows (resolves #3638). + * + * Key usage is NOT stored here. Per DSP0274, key usage is configured per key pair + * (KEY_PAIR_INFO.CurrentKeyUsage; the only writer is SET_KEY_PAIR_INFO.DesiredKeyUsage). A slot's + * key usage is therefore the CurrentKeyUsage of its associated key pair, so SlotElement.KeyUsage and + * GET_DIGESTS.KeyUsageMask[slot_id] are DERIVED from key_pair[key_pair_id-1].current_key_usage. Storing a + * per-slot copy would be redundant and could drift (especially with ShareableCap, where many slots + * share one key pair and must report that one key usage). */ +typedef struct { + uint8_t bank_id; /* [RO] PK part, FK->slot (echoes the array index) */ + uint8_t slot_id; /* [RO] PK part, FK->slot (echoes the array index) */ + bool present; /* [RW] false => slot has no associated key (state 2) */ + uint8_t key_pair_id; /* [RW] FK->key_pair. SlotElement/GET_DIGESTS KeyPairID */ +} libspdm_db_slot_key_assoc_t; + +/* ---- cert_chain: the certificate chain in a slot (0..1 per slot) --------------------------- + * Primary key: (bank_id, slot_id). FK: (bank_id, slot_id) -> slot. + * Row EXISTS <=> slot is provisioned (state 4 / ProvisionedSlotMask bit set). + * + * certificate_info follows the rule: STORE REAL, GATE AT READ, WRITER VALIDATES. Multikey is a + * per-CONNECTION fact, not device state, and one store serves both multikey and non-multikey peers, + * so the row holds the REAL cert model unconditionally; the multikey gate is applied per read (a + * non-multikey reader reports CertModel 0 per Table 42, a multikey reader reports the stored value). + * Storing the gated (0-in-non-multikey) value would be lossy and mode-dependent and could not serve + * both readers. The WRITER validates the incoming SetCertModel against the WRITING connection's mode + * (Table 147: non-zero in multikey; the wire forces 0 in non-multikey) but stores it as-is. */ +typedef struct { + uint8_t bank_id; /* [RO] PK part, FK->slot (echoes the array index) */ + uint8_t slot_id; /* [RO] PK part, FK->slot (echoes the array index) */ + bool present; /* [RW] false => no certificate chain (state 2/3) */ + uint8_t certificate_info; /* [RW] Table 42 CertificateInfo; stored CertModel in bits + * [2:0] (SPDM_CERTIFICATE_INFO_CERT_MODEL_* 1/2/3); 0 when + * absent. Reported as SlotElement/DIGESTS CertificateInfo + * via the multikey gate (see struct header). */ + size_t der_size; /* [RW] size of the raw chain */ + uint8_t der[LIBSPDM_DB_CERT_CHAIN_MAX]; /* [RW] raw chain (concatenated DER certs) */ + /* digest is derived on read for the negotiated hash; an optional cache may be added. */ +} libspdm_db_cert_chain_t; + +/* ---- csr: a GET_CSR transaction -- request inputs AND generated CSR (part C) ----------------- + * A CSR is TRANSIENT peer-driven state, not durable provisioned state: per DSP0274 the Responder + * "can discard any associated CSR data and reuse the CSRTrackingTag" once the Requester retrieves + * it. It also has a reset-required flow: if the device requires a reset to complete a GET_CSR, it + * returns ERROR(ResetRequired) carrying a Responder-assigned CSRTrackingTag, generates the CSR + * after the reset, and the Requester retrieves it later by that tag. Both halves of the transaction + * must therefore survive the reset -- the REQUEST inputs (so the CSR can be generated post-reset) + * and the RETURN value (so it can be retrieved). This is why csr lives in the staging area (part C), + * not the provisioned info (part B). See "Other ResetRequired flows" and Open spec question #1. + * + * Per DSP0274, CSRTrackingTag is a Responder-managed pool of values 1..7 allocated ACROSS the + * device (not per slot), and the legacy GET_CSR request has no slot address at all. So an entry is + * keyed by its tracking_tag, not by (bank_id, slot_id): + * - tracking_tag is the PRIMARY KEY (1..7; 0 is reserved for "new request" and is never stored); + * - bank_id/slot_id are OPTIONAL (the SLOT_MANAGEMENT GetCSR targets a bank+slot; the legacy + * GET_CSR does not -- bank_id = LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID then). + * + * [RO]/[RW] below mark the PK identity ([RO], echoes the array index) vs the per-transaction state + * the Responder fills in ([RW]). Note this is part C (transient), so [RW] here means "Responder- + * written transaction state", not the part-B "peer write effective immediately". */ +typedef struct { + bool present; /* [RW] false => this tracking_tag slot is free */ + uint8_t state; /* [RW] LIBSPDM_DB_CSR_STATE_* (FREE/PENDING_RESET/READY) */ + uint8_t tracking_tag; /* [RO] PK: responder-assigned CSRTrackingTag, 1..7 */ + /* ---- request half (GET_CSR, Table 98/99) retained to (re)generate the CSR ---- */ + uint8_t bank_id; /* [RW] target bank, or LIBSPDM_SLOT_MANAGEMENT_BANK_ID_ + * INVALID for the legacy (slot-less) GET_CSR */ + uint8_t slot_id; /* [RW] target slot when bank_id is valid */ + uint8_t key_pair_id; /* [RW] Param1 KeyPairID (FK->key_pair) */ + uint8_t request_attributes; /* [RW] Param2 RequestAttributes (CSRCertModel in [2:0]) */ + size_t requester_info_len; /* [RW] Table 98 RequesterInfoLength */ + uint8_t requester_info[LIBSPDM_DB_REQUESTER_INFO_MAX]; /* [RW] Table 98 RequesterInfo (DER) */ + size_t opaque_data_len; /* [RW] Table 98 OpaqueDataLength */ + uint8_t opaque_data[LIBSPDM_DB_OPAQUE_DATA_MAX]; /* [RW] Table 98 OpaqueData */ + /* ---- return half (CSR response, Table 100); valid only in state READY ---- */ + size_t der_size; /* [RW] */ + uint8_t der[LIBSPDM_DB_CSR_MAX_SIZE]; /* [RW] the generated CSR */ +} libspdm_db_csr_t; + +/* ---- db config: the array capacities, so the store is self-descriptive ---------------------- + * These record the LIBSPDM_DB_MAX_* values the arrays in libspdm_db_t were sized with, so a reader + * of a stored/serialized db knows the dimensions without depending on the build-time macros. + * All [RO]: this is the part-A header, written once when the store is created and only read after. */ +typedef struct { + uint8_t db_max_key_pairs; /* [RO] == LIBSPDM_DB_MAX_KEY_PAIRS */ + uint8_t db_max_banks; /* [RO] == LIBSPDM_DB_MAX_BANKS */ + uint8_t db_max_slots; /* [RO] == LIBSPDM_DB_MAX_SLOTS */ +} libspdm_db_config_t; + +/* ---- the device store: the set of "tables" -------------------------------------------------- + * The store is organized in THREE parts, each with a different lifecycle: + * + * A) STRUCTURE INFO - self-descriptive header (format version + array capacities). Set once when + * the store is created and only read thereafter; lets a consumer of a persisted/serialized + * store validate the layout before interpreting parts B and C. + * + * B) PROVISIONED INFO - the DURABLE, committed device state that every command READS. It holds + * the single authoritative copy of each fact -- the whole point of the model (see Purpose). + * All reads come from here (GET_DIGESTS/GET_CERTIFICATE/GET_KEY_PAIR_INFO/SLOT_MANAGEMENT). The + * two contiguous dimensions carry a count (total_key_pairs, num_banks); the sparse slot + * dimension is represented by per-slot presence (slot[*][*].present) plus the SlotMask + * projection. Each part-B member (and each member of the structs it contains) is tagged: + * [RO] immutable device fact, set by the Integrator at manufacture; no SPDM command changes + * it. These describe the device's structure and capabilities: which key pairs, banks, + * and slots exist (total_key_pairs, num_banks, slot[], the key_pair capability fields, + * bank_id/bank_attributes). + * [RW] mutable provisioned state; a peer write takes effect IMMEDIATELY (the no-reset path of + * SET_CERTIFICATE/ManageSlot/SET_KEY_PAIR_INFO/ManageBank) or is promoted from part C on + * reset for a reset-required write. The writer is named in the member comment. + * key_pair[] and bank[] are MIXED structs: some members are [RO], some [RW] (tagged per member + * in their struct definitions above); slot[] is wholly [RO], slot_key_assoc[]/cert_chain[] are + * wholly [RW]. + * + * C) STAGING AREA - peer-driven state that is NOT (yet) durable part-B state. Two kinds live here: + * - reset-deferred shadows (the *_pending fields): when a *_RESET_CAP is advertised, a write + * lands HERE instead of part B and does NOT change any read until libspdm_db_apply_pending() + * promotes it into part B on the next device reset (so a read in the same boot still returns + * the part-B value); + * - the CSR transaction (csr[]): a GET_CSR's request inputs and the generated CSR. A CSR is + * transient -- the Responder may discard it once retrieved -- and never becomes durable + * part-B state, so it lives here regardless of whether its own reset flow is used. + * See "Other ResetRequired flows" (#873/D1) and Open spec question #1. + * + * Splitting B from C is what makes the reset/reload behavior explicit rather than NVM-vs-memory + * skew: part B is "what is durably served", part C is "pending or transient peer state". */ +typedef struct { + /* ---- A) structure info (write-once header, read-only thereafter; all [RO]) ---- */ + uint8_t version; /* [RO] == LIBSPDM_DB_FORMAT_VERSION */ + libspdm_db_config_t config; /* [RO] array capacities (see above) */ + + /* ---- B) provisioned info (durable committed state; the only thing reads come from) ---- */ + uint8_t total_key_pairs; /* [RO] Table 111 TotalKeyPairs; + * key pairs are key_pair[0..n-1] */ + uint8_t num_banks; /* [RO] GetBankInfo NumBankElements; + * banks are bank[0..num_banks-1] */ + libspdm_db_key_pair_t key_pair[LIBSPDM_DB_MAX_KEY_PAIRS]; /* [RO]+[RW] mixed (see struct) */ + libspdm_db_bank_t bank[LIBSPDM_DB_MAX_BANKS]; /* [RO]+[RW] mixed (see struct) */ + libspdm_db_slot_t slot[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS]; /* [RO] */ + libspdm_db_slot_key_assoc_t slot_key_assoc[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS];/* [RW] SET_KEY_PAIR_INFO */ + libspdm_db_cert_chain_t cert_chain[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS]; /* [RW] SET_CERTIFICATE/Erase */ + + /* ---- C) staging area (pending shadows + transient CSR transactions) ---- */ + /* All [RW], but written into the staging area -- NOT the part-B "effective immediately" sense. + * Each *_pending field shadows the like-named part-B field; *_pending_valid/_erase says whether + * a commit is queued. Used only when the corresponding *_RESET_CAP is advertised. */ + libspdm_db_cert_chain_t cert_chain_pending[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS]; /* [RW] */ + bool cert_chain_pending_erase[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS];/* [RW] */ + libspdm_db_algo_t bank_algo_pending[LIBSPDM_DB_MAX_BANKS]; /* [RW] ManageBank */ + bool bank_algo_pending_valid[LIBSPDM_DB_MAX_BANKS]; /* [RW] */ + libspdm_db_key_pair_t key_pair_pending[LIBSPDM_DB_MAX_KEY_PAIRS]; /* [RW] SET_KEY_PAIR_INFO */ + bool key_pair_pending_valid[LIBSPDM_DB_MAX_KEY_PAIRS];/* [RW] */ + /* CSR transactions: a device-global pool keyed by CSRTrackingTag (1..7), NOT per slot. Index + * this array by (tracking_tag - 1); tracking_tag is unique because each entry holds a distinct + * tag. Transient peer state (see libspdm_db_csr_t), not durable part-B provisioned info. */ + libspdm_db_csr_t csr[LIBSPDM_DB_MAX_CSR]; /* [RW] (see struct) */ +} libspdm_db_t; +``` + +The `version`/`config` header is initialized to `{ LIBSPDM_DB_FORMAT_VERSION, { LIBSPDM_DB_MAX_KEY_PAIRS, +LIBSPDM_DB_MAX_BANKS, LIBSPDM_DB_MAX_SLOTS } }`; a consumer of a persisted store checks `version` +and `config` before interpreting the arrays. + +## The slot state is *derived*, never stored + +The four slot states defined in [Slot states](#slot-states) are the **stored** state, computed from +which `libspdm_db_t` array elements are present, so they can never contradict the underlying data. +Each column below is the `present` flag of one array element (all indexed by `[bank_id][slot_id]`): +`present` means the `present` flag is set, `absent` means it is clear. + +| State | `slot[bank_id][slot_id]` | `slot_key_assoc[bank_id][slot_id]` | `cert_chain[bank_id][slot_id]` | +| ----- | ------------------------ | ---------------------------------- | ------------------------------ | +| 1. Does not exist | absent | absent | absent | +| 2. Exists and empty | present | absent | absent | +| 3. Exists with key | present | present | absent | +| 4. Exists with key and cert | present | present | present | + +This stored state is a device-storage fact, **independent of any connection's multikey mode**. The +key↔slot association (`slot_key_assoc`) exists in storage in both modes and is reported by +`KEY_PAIR_INFO`.`AssocCertSlotMask` (which DSP0274 does not gate on multikey). The combination `slot` +present + `slot_key_assoc` absent + `cert_chain` present is **invalid** — a provisioned slot must +have its key associated — and an implementation should assert/reject it rather than treat it as a +state. + +The three arrays are `libspdm_db_t` members of types `libspdm_db_slot_t`, +`libspdm_db_slot_key_assoc_t`, and `libspdm_db_cert_chain_t` (see [Schema](#schema-c-structures)). +The `slot_key_assoc` element is always present in the struct; "present"/"absent" here is its +`present` flag, and `present` carries the associated `key_pair_id` (1..`total_key_pairs`) while +`absent` leaves `key_pair_id` unused (0). + +What multikey mode changes is only how the association is **reported in `DIGESTS` and the +`SLOT_MANAGEMENT` `SlotElement`**, not whether it exists: `DIGESTS.KeyPairID[X]` and +`SlotElement.KeyPairID` are present/populated only when `MULTI_KEY_CONN_REQ`/`RSP` is true and are +otherwise forced to 0 (DSP0274 Tables 41, 152); likewise the `ProvisionedSlotMask` "associated key" +clause is multikey-only. So in a non-multikey connection a state-3 slot (key, no cert) is still +visible via `KEY_PAIR_INFO`.`AssocCertSlotMask`, but `DIGESTS` reports it with `Provisioned = 0` +(indistinguishable from state 2 in the `DIGESTS` view). + +Because every view is a query over the same structures, a single write propagates everywhere at +once. The exhaustive field-by-field mapping is below. + +## Mapping wire structures to the schema + +Every field of every SLOT_MANAGEMENT / KEY_PAIR_INFO / DIGESTS wire structure is listed, with the +schema source it is read from or written to. `bank_id`, `slot_id`, and `key_pair_id` are the +addressed Bank, Slot, and KeyPair; "bit N" of a mask is the bit for slot N. "derived (connection)" +means the value depends on the negotiated algorithm/hash of the current connection, not on stored +device state. + +### BankInfo (Table 149 — GetBankInfo) + +| Wire field | Schema source | +| ---------- | ------------- | +| `RespLength` | structural (computed from `NumBankElements`) | +| `NumBankElements` | `num_banks` | +| `BankElements[]` | one `BankElement` per `bank[0..num_banks-1]` (see below) | + +### BankElement (Table 150 — within BankInfo) + +| Wire field | Schema source | +| ---------- | ------------- | +| `ElementLength` | constant (4); structural | +| `BankID` | `bank[bank_id].bank_id` | +| `SlotMask` | bit slot_id set ⇔ `slot[bank_id][slot_id].present` (slot exists) | +| `ModifiableSlotMask` | bit slot_id set ⇔ `slot[bank_id][slot_id].present && slot[bank_id][slot_id].modifiable` (and, per spec, the `SlotMask` bit is set) | + +### BankDetails (Table 151 — GetBankDetails) + +| Wire field | Schema source | +| ---------- | ------------- | +| `RespLength` | structural (computed from `NumSlotElements`) | +| `BankID` | `bank[bank_id].bank_id` | +| `NumSlotElements` | count of `slot[bank_id][*].present` | +| `BankAttributes` | `bank[bank_id].bank_attributes` with the derived `SELECTED` bit OR'd in: `SELECTED` ⇔ `bank[bank_id].algo == negotiated algo`; `CONFIG_ALGO` from the stored byte | +| `AsymAlgoCapabilities` | union of `key_pair[*].asym_algo_capabilities` over key pairs eligible for the bank (0 if `CONFIG_ALGO` clear in `bank[bank_id].bank_attributes`) | +| `CurrentAsymAlgo` | `bank[bank_id].algo.asym_algo` | +| `AvailableAsymAlgo` | `AsymAlgoCapabilities` minus algorithms already assigned to another bank (`bank[*].algo`) | +| `PqcAsymAlgoCapLen` / `PqcAsymAlgoCapabilities` | length is constant (4); value is the union of `key_pair[*].pqc_asym_algo_capabilities` over key pairs eligible for the bank (0 if `CONFIG_ALGO` clear in `bank[bank_id].bank_attributes`) | +| `CurrentPqcAsymAlgoLen` / `CurrentPqcAsymAlgo` | length is constant (4); value is `bank[bank_id].algo.pqc_asym_algo` | +| `AvailablePqcAsymAlgoLen` / `AvailablePqcAsymAlgo` | length is constant (4); value is `PqcAsymAlgoCapabilities` minus PQC algorithms already assigned to another bank (`bank[*].algo.pqc_asym_algo`) | +| `SlotElements[]` | one entry per `slot[bank_id][slot_id].present` (see below) | + +### SlotElement (Table 152 — within BankDetails) + +| Wire field | Schema source | +| ---------- | ------------- | +| `ElementLength` | constant (16 + H); structural | +| `SlotID` | `slot[bank_id][slot_id].slot_id` | +| `SlotAttributes` | `slot[bank_id][slot_id].slot_attributes` with the derived `PROVISIONED` bit OR'd in: `PROVISIONED` ⇔ `cert_chain[bank_id][slot_id].present`; `WRITE_PROTECTED` from the stored byte | +| `KeyPairID` | `slot_key_assoc[bank_id][slot_id].key_pair_id` (0 if `!MULTI_KEY_CONN_RSP` or no assoc) | +| `CertificateInfo` | `cert_chain[bank_id][slot_id].certificate_info` (0 if no cert; also 0 when `!MULTI_KEY_CONN_RSP` — per Table 42 `CertModel` is 0 in a non-multikey connection regardless of the stored cert) | +| `KeyUsage` | derived: if `slot_key_assoc[bank_id][slot_id].present`, then `key_pair[slot_key_assoc[bank_id][slot_id].key_pair_id - 1].current_key_usage` (key_pair_id is 1-based, so it is non-zero here); otherwise 0 (no associated key pair) | +| `SlotSize` | capacity for `cert_chain[bank_id][slot_id].der` (device/store constant) | +| `Digest` | derived (connection): hash of `cert_chain[bank_id][slot_id].der` under the negotiated hash; all-zero if not provisioned | + +### GetCertificateChain structure (Table 153 — GetCertificateChain) + +| Wire field | Schema source | +| ---------- | ------------- | +| `CCLength` | `cert_chain[bank_id][slot_id].der_size` (+ Table 39 header/root-hash, assembled on read) | +| `CertChain` | `cert_chain[bank_id][slot_id].der` assembled into the Table 39 chain (header + root hash + DER) | + +### SupportedSubCodes (Table 148 — SupportedSubCodes) + +| Wire field | Schema source | +| ---------- | ------------- | +| `RespLength` | constant (36); structural | +| `SubCodeBitmap` | device capability constant (not in these tables; the supported-SubCode set) | + +### KEY_PAIR_INFO response (Table 111 — GET_KEY_PAIR_INFO) + +| Wire field | Schema source | +| ---------- | ------------- | +| `TotalKeyPairs` | `total_key_pairs` | +| `KeyPairID` | `key_pair[key_pair_id - 1].key_pair_id` | +| `Capabilities` | `key_pair[key_pair_id - 1].capabilities` (Table 112 bits) | +| `KeyUsageCapabilities` | `key_pair[key_pair_id - 1].key_usage_capabilities` | +| `CurrentKeyUsage` | `key_pair[key_pair_id - 1].current_key_usage` | +| `AsymAlgoCapabilities` | `key_pair[key_pair_id - 1].asym_algo_capabilities` | +| `CurrentAsymAlgo` | `key_pair[key_pair_id - 1].current_asym_algo` | +| `PublicKeyInfoLen` | `key_pair[key_pair_id - 1].public_key_info_len` (0 ⇒ key absent/ungenerated) | +| `AssocCertSlotMask` | **derived**: bit slot_id set ⇔ `slot_key_assoc[bank_id][slot_id].key_pair_id == key_pair_id`, where bank_id is the unique bank whose `algo` equals this key pair's current algorithm (see the key_pair "maps to at most one bank" invariant; all-zero for an unconfigured key pair, which has no associations). Not stored — the inverse of `slot_key_assoc` | +| `PublicKeyInfo` | `key_pair[key_pair_id - 1].public_key_info` | +| `PqcAsymAlgoCapLen` / `PqcAsymAlgoCapabilities` | length is constant (4); value is `key_pair[key_pair_id - 1].pqc_asym_algo_capabilities` | +| `CurrentPqcAsymAlgoLen` / `CurrentPqcAsymAlgo` | length is constant (4); value is `key_pair[key_pair_id - 1].current_pqc_asym_algo` | + +### GET_DIGESTS / DIGESTS (Table for digests) + +| Wire field | Schema source | +| ---------- | ------------- | +| `SupportedSlotMask` (Param1) | bit slot_id ⇔ `slot[bank_id][slot_id].present`, bank_id = selected bank | +| `ProvisionedSlotMask` (Param2) | bit slot_id ⇔ `cert_chain[bank_id][slot_id].present` **OR** ((`MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP`) and `slot_key_assoc[bank_id][slot_id].present`), bank_id = selected bank | +| `Digest[slot_id]` | derived (connection): hash of `cert_chain[bank_id][slot_id].der`; all-zero if not provisioned | +| `KeyPairID[slot_id]` | `slot_key_assoc[bank_id][slot_id].key_pair_id` | +| `CertificateInfo[slot_id]` | `cert_chain[bank_id][slot_id].certificate_info`; present only when `MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` (Table 41) — absent otherwise, and per Table 42 its `CertModel` would be 0 in a non-multikey connection regardless | +| `KeyUsageMask[slot_id]` | derived: if `slot_key_assoc[bank_id][slot_id].present`, then `key_pair[slot_key_assoc[bank_id][slot_id].key_pair_id - 1].current_key_usage` (key_pair_id is non-zero here); otherwise 0 (no associated key pair) | + +> NOTE: Per DSP0274 Table 41 the spec qualifies both `ProvisionedSlotMask` terms with "supports the +> currently negotiated algorithms"; that qualifier is automatically met here because the reported +> bank is the one *selected by* the negotiated algorithm, so every cert/key in it matches by +> construction. So in multikey mode a slot with an associated key pair (state 3) is also reported +> provisioned — unlike `SlotElement.SlotAttributes.Provisioned`, which is cert-only ("set to 1 if the +> slot contains a certificate chain", Table 152). + +### GET_CERTIFICATE / CERTIFICATE (selected bank) + +| Wire field | Schema source | +| ---------- | ------------- | +| `CertChain` (for slot `slot_id`) | `cert_chain[bank_id][slot_id].der` assembled into the Table 39 chain, bank_id = selected bank; `ERROR(InvalidRequest)` if `!cert_chain[bank_id][slot_id].present` | + +### CHALLENGE / CHALLENGE_AUTH (selected bank) + +| Wire field | Schema source | +| ---------- | ------------- | +| `Param2` (slot mask) | bit slot_id ⇔ `cert_chain[bank_id][slot_id].present`, bank_id = selected bank. Per DSP0274 Table 51 this bit is set only for the "Exists with key and cert" state (state 4) — i.e. the slot has a key provisioned **and** a certificate chain; it is reserved if the Responder's public key was provisioned to the Requester in a trusted environment. Unlike `DIGESTS.ProvisionedSlotMask`, it is not multikey-gated and never reflects a key-only (state 3) slot | + +### Write requests (which structure each command writes) + +An asset may be updatable by **more than one path** (e.g. `cert_chain` via the legacy `SET_CERTIFICATE` +*or* the `SLOT_MANAGEMENT SetCertificate` SubCode). Each path is listed as its own row and is gated +**only by its own path capability** — the legacy and `SLOT_MANAGEMENT` capabilities are independent +and are never AND-ed together. The gate column combines two kinds of condition: + +* **Path capability** — gates this path only: `CAPABILITIES.Flags.*` for a legacy message, or + `CAPABILITIES.ExtFlags.SLOT_MGMT_CAP == 1 && SLOT_MANAGEMENT.SupportedSubCodes.SubCodeBitmap[] == 1` + for a `SLOT_MANAGEMENT` SubCode (bit position = SubCode value). +* **Per-asset device gate** — a device fact about the target asset (slot attribute, key-pair + capability, bank attribute) that applies on **any** path that reaches the asset. + +| Asset | Request (path) | Path capability | Per-asset device gate | Effect on the schema | +| ----- | -------------- | --------------- | --------------------- | -------------------- | +| `cert_chain` (slot) | `SET_CERTIFICATE` (legacy, Table 147) | `CAPABILITIES.Flags.SET_CERT_CAP == 1` | `SLOT_MANAGEMENT.BankDetails.SlotElement.SlotAttributes.WRITE_PROTECTED == 0` && `SLOT_MANAGEMENT.BankInfo.BankElement.ModifiableSlotMask[slot_id] == 1` | upsert `cert_chain[bank_id][slot_id]` (`der`, `certificate_info`); creates `slot`/`slot_key_assoc` rows if needed | +| `cert_chain` (slot) | `SLOT_MANAGEMENT SetCertificate` (Table 147) | `CAPABILITIES.ExtFlags.SLOT_MGMT_CAP == 1` && `SLOT_MANAGEMENT.SupportedSubCodes.SubCodeBitmap[0x22] == 1` | `SLOT_MANAGEMENT.BankDetails.SlotElement.SlotAttributes.WRITE_PROTECTED == 0` && `SLOT_MANAGEMENT.BankInfo.BankElement.ModifiableSlotMask[slot_id] == 1` | upsert `cert_chain[bank_id][slot_id]` (`der`, `certificate_info`); creates `slot`/`slot_key_assoc` rows if needed | +| `cert_chain` (slot) | `SET_CERTIFICATE` `Erase` (legacy) | `CAPABILITIES.Flags.SET_CERT_CAP == 1` | `SLOT_MANAGEMENT.BankDetails.SlotElement.SlotAttributes.WRITE_PROTECTED == 0` && `SLOT_MANAGEMENT.BankInfo.BankElement.ModifiableSlotMask[slot_id] == 1` | clear `cert_chain[bank_id][slot_id].present`; **keep** `slot_key_assoc[bank_id][slot_id]` | +| `cert_chain` (slot) | `SLOT_MANAGEMENT ManageSlot` `Erase` (Table 146) | `CAPABILITIES.ExtFlags.SLOT_MGMT_CAP == 1` && `SLOT_MANAGEMENT.SupportedSubCodes.SubCodeBitmap[0x21] == 1` | `SLOT_MANAGEMENT.BankDetails.SlotElement.SlotAttributes.WRITE_PROTECTED == 0` && `SLOT_MANAGEMENT.BankInfo.BankElement.ModifiableSlotMask[slot_id] == 1` | clear `cert_chain[bank_id][slot_id].present`; **keep** `slot_key_assoc[bank_id][slot_id]` | +| `key_pair` | `SET_KEY_PAIR_INFO` `GenerateKeyPair` | `CAPABILITIES.Flags.SET_KEY_PAIR_INFO_CAP == 1` | `KEY_PAIR_INFO.Capabilities.GenKeyCap == 1` | set `key_pair[key_pair_id - 1].public_key_info*`, `current_*`; (association via a separate change) | +| `key_pair` | `SET_KEY_PAIR_INFO` `KeyPairErase` | `CAPABILITIES.Flags.SET_KEY_PAIR_INFO_CAP == 1` | `KEY_PAIR_INFO.Capabilities.ErasableCap == 1` | clear `key_pair[key_pair_id - 1]` key material; allowed only if no `slot_key_assoc` references it | +| `slot_key_assoc` + `key_pair` | `SET_KEY_PAIR_INFO` (associate / set usage / set algo) | `CAPABILITIES.Flags.SET_KEY_PAIR_INFO_CAP == 1` | one of:
• associate: `KEY_PAIR_INFO.Capabilities.CertAssocCap == 1` (+ `KEY_PAIR_INFO.Capabilities.ShareableCap == 1` for cardinality > 1)
• set-usage: `KEY_PAIR_INFO.Capabilities.KeyUsageCap == 1`
• set-algo: `KEY_PAIR_INFO.Capabilities.AsymAlgoCap == 1` | Set `slot_key_assoc[bank_id][slot_id].key_pair_id` and/or `key_pair[key_pair_id - 1].current_key_usage` / `current_*_algo`. Key usage is set on the key pair, never per slot. | +| `bank` (+ its slots) | `SLOT_MANAGEMENT ManageBank` `ConfigAlgo` (Table 145) | `CAPABILITIES.ExtFlags.SLOT_MGMT_CAP == 1` && `SLOT_MANAGEMENT.SupportedSubCodes.SubCodeBitmap[0x20] == 1` | `SLOT_MANAGEMENT.BankDetails.BankAttributes.CONFIG_ALGO == 1` | allowed only if no slot in the bank has a certificate provisioned — i.e. no slot is in state 4 (else `InvalidState`, DSP0274 Table 141: "If any slots in the Bank … have a certificate provisioned … `ErrorCode=InvalidState`"). States 1/2/3 are permitted. Set `bank[bank_id].algo` **and clear all slot settings** in that bank — drop every `slot_key_assoc`/`csr` row (and any `slot` attributes) for the bank (DSP0274: "When the Bank configuration changes, the Responder shall clear all slot settings"). An all-zero `SelectAsymAlgo`/`SelectPqcAsymAlgo` (zero algorithm bits) is rejected with `InvalidRequest` — `ConfigAlgo` must select exactly one algorithm | +| `csr` | `GET_CSR` (legacy, Table 144) | `CAPABILITIES.Flags.CSR_CAP == 1` | — | allocate a free `CSRTrackingTag` (1..7) and fill that `csr[tag-1]` entry with the request inputs (`key_pair_id`, `request_attributes`, `requester_info`, `opaque_data`); legacy `GET_CSR` leaves `bank_id = LIBSPDM_SLOT_MANAGEMENT_BANK_ID_INVALID`. Reset flow: if a reset is required, set `state = PENDING_RESET` and return `ResetRequired` with the tag (`der` generated, `state = READY` after reset); else generate `der` immediately. Freed once the Requester retrieves the CSR | +| `csr` | `SLOT_MANAGEMENT GetCSR` (Table 144) | `CAPABILITIES.ExtFlags.SLOT_MGMT_CAP == 1` && `SLOT_MANAGEMENT.SupportedSubCodes.SubCodeBitmap[0x04] == 1` | — | as the legacy `GET_CSR` row, but `bank_id` is the addressed Bank | + +A write is accepted only if **both** its path capability and its per-asset device gate hold +(`UnsupportedRequest` if the path capability is absent; `InvalidRequest`/`InvalidState` otherwise). +Because the two paths to one asset carry independent capabilities, disabling one path (e.g. clearing +`SET_CERT_CAP`) does not disable the other (`SLOT_MANAGEMENT SetCertificate`), and vice versa. + +#### State preconditions (beyond the capability gates) + +Capabilities say a write is *allowed*; these **ordering/state preconditions** say it is *valid right +now*, given what the asset currently holds. They are enforced in addition to the gates above, and a +violation is rejected with the listed `ErrorCode`. They come from DSP0274 §"Key pair ID modification +error handling" and §"SLOT_MANAGEMENT" (Table 141). + +| Operation | Precondition (must hold) | Schema test | Error if violated | +| --------- | ------------------------ | ----------- | ----------------- | +| `SET_KEY_PAIR_INFO` `GenerateKeyPair` | an asymmetric algorithm is already selected for the key pair (generate is bound to `CurrentAsymAlgo`/`CurrentPqcAsymAlgo`) | `key_pair[id-1].current_asym_algo != 0 \|\| current_pqc_asym_algo != 0` | `OperationFailed` | +| `SET_KEY_PAIR_INFO` `GenerateKeyPair` | the key pair is **not associated with any certificate slot** | for all banks: `libspdm_db_assoc_cert_slot_mask(db, bank, id) == 0` | `OperationFailed` | +| `SET_KEY_PAIR_INFO` `KeyPairErase` | the key pair is **not associated with any certificate slot** (erase the certificate chains first) | for all banks: `libspdm_db_assoc_cert_slot_mask(db, bank, id) == 0` | `OperationFailed` | +| `SET_KEY_PAIR_INFO` `ParameterChange` (set algo) | the asymmetric algorithm is **not changed once a key pair has been generated** (the key is bound to the algorithm) | reject a different `DesiredAsymAlgo`/`DesiredPqcAsymAlgo` when `public_key_info_len != 0` (a key exists) | discard or `InvalidRequest` | +| `SET_KEY_PAIR_INFO` `ParameterChange` (associate) | a slot may be associated only with a key pair whose algorithm matches the slot's **bank algorithm** (algorithm-agreement) | `key_pair[id-1].current_*_algo == bank[bank_id].algo` | `InvalidRequest` | +| `SET_CERTIFICATE` / `SetCertificate` (install) | the slot already has an associated key pair of the bank's algorithm (a cert is verifiable under exactly one algorithm) | `slot_key_assoc[bank][slot].present == 1` | `InvalidRequest` | +| `ManageBank` `ConfigAlgo` | **no slot in the bank has a certificate provisioned** (no slot in state 4); states 1/2/3 are allowed (a permitted state-3 slot's key↔slot association is dropped — see [Open spec questions](#open-spec-questions) #2) | for all slots in bank: `cert_chain[bank][slot].present == 0` | `InvalidState` | +| `ManageBank` `ConfigAlgo` | exactly one algorithm bit is selected (all-zero is rejected) | `popcount(SelectAsymAlgo) + popcount(SelectPqcAsymAlgo) == 1` | `InvalidRequest` | + +> NOTE: the "no association" precondition for `GenerateKeyPair`/`KeyPairErase` is the inverse of the +> normal provisioning order: a key pair must be **dissociated from every slot** (which in turn +> requires the slots' certificate chains to be erased first) before its key material can be generated +> or erased. This is why returning a key pair to default values is "erase certs → dissociate → erase +> key", and provisioning is the reverse. + +Because every read field above resolves to exactly one schema location, and every write updates that +one location, a single write is immediately visible to **all** of the reads — there is no second +copy to update. + +## Integrity constraints that resolve the gaps + +The gaps are eliminated by declarative constraints rather than by hoping each command writer keeps +the copies in sync. + +### Gap #3638 — key-pair ↔ slot association consistency + +Root cause: three independent arrays (`AssocCertSlotMask`, per-slot `KeyPairID` in `GET_DIGESTS`, +and the per-slot certificate's algorithm) are written separately and drift. + +Resolution: there is exactly **one** association fact — a row in `slot_key_assoc`. All three views +are queries over it, so: + +* `GET_DIGESTS.KeyPairID[slot_id]` ≡ `GetBankDetails.SlotElement(slot_id).KeyPairID` ≡ the + `key_pair_id` in `slot_key_assoc` for `(bank_id, slot_id)` — identical by construction. +* `AssocCertSlotMask(key_pair_id)` is the inverse projection of the same rows, so + "`KeyPairID[slot_id]` equals the key pair whose `AssocCertSlotMask` has bit `slot_id` set" holds automatically. + +Plus an algorithm-agreement constraint so the slot's certificate matches its key pair's algorithm, +and the key pair's algorithm matches the bank's. These checks live in `libspdm_db_associate_slot_key()` +(see [Access API implementations](#access-api-implementations)): an association whose key-pair +algorithm differs from the bank's is rejected, so a slot's certificate is always verifiable under the +single algorithm reached through `slot_key_assoc` → `key_pair` — there is no second algorithm copy +that could disagree. + +### Gap #3645 — the "exists with key" state must be reachable + +Root cause: there was no representation of "key present, no cert", so `GenerateKeyPair` / +`KeyPairErase` had no slot-level effect and the state was unreachable. + +Resolution: state 3 is simply a `slot` row + a `slot_key_assoc` row + **no** `cert_chain` row, which +the model represents natively. The transitions become ordinary row operations: + +* `GenerateKeyPair` for a key pair, then associate it to a slot → `INSERT slot_key_assoc` → state 2→3. +* `SetCertificate` → `INSERT cert_chain` → state 3→4 (or 2→4, also inserting the assoc). +* `Erase` (ManageSlot / SET_CERTIFICATE) → `DELETE cert_chain`, **keep** `slot_key_assoc` → 4→3. +* `KeyPairErase` → `DELETE slot_key_assoc` (allowed only when no `cert_chain` row) → 3→2. + +The "Erase keeps the key" rule (DSP0274 §3115) is expressed exactly: Erase deletes only the +`cert_chain` row, never the `slot_key_assoc` row. + +### Gap #873 — one write, one served value (no reset/reload divergence) + +Root cause: `SET_CERTIFICATE` writes NVM but `GET_CERTIFICATE` serves from a separate in-memory +`local_cert_chain_provision[]`; they diverge until a reset+reload. + +Resolution: `GET_CERTIFICATE`, `CHALLENGE`, `KEY_EXCHANGE`, and the `SLOT_MANAGEMENT` reads all read +the **same** `cert_chain[bank_id][slot_id]` row. A `SET_CERTIFICATE` is a single write to that row (or a clear of +`present` for erase); every reader sees it immediately. The `CERT_INSTALL_RESET_CAP` / +`ResetRequired` flow becomes an explicit two-phase commit modeled by the `cert_chain_pending` rows +already in `libspdm_db_t`, not a divergence: + +* If `CERT_INSTALL_RESET_CAP` is **not** advertised, `SET_CERTIFICATE` commits directly to + `cert_chain[bank_id][slot_id]` and is effective live (closes the "no effect without reset" half of the gap). +* If it **is** advertised, the write lands in `cert_chain_pending[bank_id][slot_id]`, the Responder returns + `ResetRequired`, and the reset handler applies `cert_chain_pending` → `cert_chain` atomically. A + read in the same boot still reflects the committed (pre-pending) chain, and the pending state is + explicit rather than implied by NVM-vs-memory skew. + +For the AliasCert wrinkle noted in #873, chain assembly (appending the slot-0 Alias cert to form a +complete chain) is a read-time assembly over `cert_chain`, deterministic from the same store. + +### Other ResetRequired flows + +`ResetRequired` is not specific to certificate install. DSP0274 also allows it for: + +* **`ManageBank` `ConfigAlgo`** — "If a Responder requires a reset before a reconfigured Bank can be + used … `ResetRequired`" (Table 141). +* **`SET_KEY_PAIR_INFO`** when `SET_KEY_PAIR_RESET_CAP` is advertised. + +The same two-phase pattern applies: the write lands in a pending field, the Responder returns +`ResetRequired`, and the reset handler (`libspdm_db_apply_pending()`) commits it. The store therefore +carries a pending shadow for each reset-capable write, not just certificates: + +```c +/* pending (reset-required) shadows, all committed by libspdm_db_apply_pending() */ +libspdm_db_cert_chain_t cert_chain_pending[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS]; +bool cert_chain_pending_erase[LIBSPDM_DB_MAX_BANKS][LIBSPDM_DB_MAX_SLOTS]; +libspdm_db_algo_t bank_algo_pending[LIBSPDM_DB_MAX_BANKS]; /* ManageBank ConfigAlgo */ +bool bank_algo_pending_valid[LIBSPDM_DB_MAX_BANKS]; +libspdm_db_key_pair_t key_pair_pending[LIBSPDM_DB_MAX_KEY_PAIRS]; /* SET_KEY_PAIR_INFO */ +bool key_pair_pending_valid[LIBSPDM_DB_MAX_KEY_PAIRS]; +``` + +When the corresponding `*_RESET_CAP` is not advertised, each write commits directly and these +pending shadows are unused. + +## Worked propagation example + +`SET_CERTIFICATE(bank=0, slot=1, chain C, model=DeviceCert)` with no reset required: + +```c +/* ensure the slot exists and is associated to a key pair of the bank's algorithm, then install */ +db->slot[0][1].present = true; /* slot exists (state >= 2) */ +/* slot_key_assoc[0][1] must already exist, or be created via libspdm_db_associate_slot_key() */ +db->cert_chain[0][1].present = true; /* provisioned (state 4) */ +db->cert_chain[0][1].certificate_info = SPDM_CERTIFICATE_INFO_CERT_MODEL_DEVICE_CERT; +db->cert_chain[0][1].der_size = C_size; +libspdm_copy_mem(db->cert_chain[0][1].der, sizeof(db->cert_chain[0][1].der), C, C_size); +``` + +Immediately afterwards, **without any extra code in the other command handlers**: + +* `GET_CERTIFICATE(slot=1)` (if bank 0 is the selected bank) returns `C` — reads `cert_chain[0][1]`. +* `GET_DIGESTS` reports `ProvisionedSlotMask` bit 1 set and a digest of `C`. +* `SLOT_MANAGEMENT GetBankDetails(bank=0)` reports slot 1 as `Provisioned=1`, + `CertificateInfo=DeviceCert`, `KeyPairID` = `slot_key_assoc[0][1].key_pair_id`. +* `GET_KEY_PAIR_INFO` for that key pair shows bit 1 set in `AssocCertSlotMask` (inverse of + `slot_key_assoc`). + +A subsequent `ManageSlot Erase(bank=0, slot=1)` is `db->cert_chain[0][1].present = false;` — every +view drops to "exists with key" (state 3) at once, and the key pair association (`slot_key_assoc`, +hence the `AssocCertSlotMask` bit) is retained. + +## Access API + +The structures are mutated only through a small set of access functions, so the invariants live in +one place. Reads compose the wire structures from the tables; writes touch exactly one row. + +**Referential cascades.** The FKs (`slot_key_assoc`/`cert_chain`/`csr` → `slot`, `slot` → `bank`) +imply `ON DELETE CASCADE`: clearing a `slot` row clears its `slot_key_assoc`, `cert_chain`, and any +`csr` entry that targets it; reconfiguring/removing a `bank` clears all of its slots' rows (this is +also the ManageBank "clear all slot settings" cascade). The access functions perform these cascades +so no dangling child row can outlive its parent. + +```c +/* readers (compose wire views; the *selected bank* readers take the negotiated algo/hash) */ +uint8_t libspdm_db_slot_state(const libspdm_db_t *db, + uint8_t bank_id, uint8_t slot_id); /* returns 1..4 */ +uint8_t libspdm_db_supported_slot_mask(const libspdm_db_t *db, uint8_t bank_id); +uint8_t libspdm_db_provisioned_slot_mask(const libspdm_db_t *db, uint8_t bank_id, + bool multi_key); /* multi_key gates the assoc clause */ +uint8_t libspdm_db_assoc_cert_slot_mask(const libspdm_db_t *db, + uint8_t bank_id, uint8_t key_pair_id); +/* count slots (other than the given one) currently associated with key_pair_id; used to enforce + * the ShareableCap cardinality in libspdm_db_associate_slot_key(). */ +size_t libspdm_db_count_key_pair_assoc_other(const libspdm_db_t *db, uint8_t key_pair_id, + uint8_t except_bank_id, uint8_t except_slot_id); +bool libspdm_db_get_cert_chain(const libspdm_db_t *db, + uint8_t bank_id, uint8_t slot_id, + void *chain, size_t *chain_size); /* Table 39 chain */ + +/* writers (each enforces the invariants and touches exactly one row) */ +bool libspdm_db_set_certificate(libspdm_db_t *db, + uint8_t bank_id, uint8_t slot_id, + uint8_t certificate_info, const void *der, size_t der_size, + bool reset_required); /* -> cert_chain or cert_chain_pending */ +bool libspdm_db_erase_certificate(libspdm_db_t *db, + uint8_t bank_id, uint8_t slot_id); /* keep the key */ +bool libspdm_db_associate_slot_key(libspdm_db_t *db, + uint8_t bank_id, uint8_t slot_id, uint8_t key_pair_id); +bool libspdm_db_generate_key_pair(libspdm_db_t *db, uint8_t key_pair_id, + const libspdm_db_algo_t *algo, uint16_t key_usage, + const void *public_key_info, uint16_t public_key_info_len); + /* requires GenKeyCap; algo already selected; + * only if no assoc (OperationFailed else) */ +bool libspdm_db_erase_key_pair(libspdm_db_t *db, uint8_t key_pair_id); + /* requires ErasableCap; only if no assoc */ +bool libspdm_db_set_key_usage(libspdm_db_t *db, uint8_t key_pair_id, uint16_t key_usage); + /* requires KeyUsageCap; subset of caps */ +bool libspdm_db_set_key_algo(libspdm_db_t *db, uint8_t key_pair_id, + const libspdm_db_algo_t *algo); /* requires AsymAlgoCap; cannot change + * algo once a key is generated */ +bool libspdm_db_config_bank_algo(libspdm_db_t *db, + uint8_t bank_id, const libspdm_db_algo_t *algo); + /* requires CONFIG_ALGO; algo must select + * exactly one algorithm (all-zero is + * rejected); no slot in the bank may have a + * certificate (state 4); clears all slot + * settings on success */ +bool libspdm_db_apply_pending(libspdm_db_t *db); /* reset handler: pending -> cert_chain */ +``` + +## Access API implementations + +Reference implementations of every function in [Access API](#access-api). They depend only on the +[Schema](#schema-c-structures) and the standard libspdm encodings (`SPDM_KEY_PAIR_CAP_*`, +`SPDM_SLOT_MANAGEMENT_*`, `SPDM_CERTIFICATE_INFO_CERT_MODEL_*`). Each writer enforces its invariants +and touches exactly one row (plus any cascade); each reader composes a wire view without mutating the +store. They are intentionally small — the value is that the invariants live in one place. + +### Readers + +```c +/* slot state 1..4, derived purely from row presence (see "The slot state is derived, never + * stored"). The invalid combination (slot present, no assoc, but cert present) is asserted. */ +uint8_t libspdm_db_slot_state(const libspdm_db_t *db, uint8_t bank_id, uint8_t slot_id) +{ + LIBSPDM_ASSERT(db != NULL); + + if ((bank_id >= db->num_banks) || (slot_id >= db->config.db_max_slots) || + !db->slot[bank_id][slot_id].present) { + return 1; /* does not exist */ + } + if (!db->slot_key_assoc[bank_id][slot_id].present) { + /* a provisioned slot must have its key associated; cert-without-assoc is invalid. */ + LIBSPDM_ASSERT(!db->cert_chain[bank_id][slot_id].present); + return 2; /* exists and empty */ + } + if (!db->cert_chain[bank_id][slot_id].present) { + return 3; /* exists with key */ + } + return 4; /* exists with key and cert */ +} + +/* SupportedSlotMask (Param1 of GET_DIGESTS, GetBankInfo SlotMask): bit slot_id set <=> slot exists + * (state >= 2). */ +uint8_t libspdm_db_supported_slot_mask(const libspdm_db_t *db, uint8_t bank_id) +{ + uint8_t mask; + uint8_t slot_id; + + LIBSPDM_ASSERT(db != NULL); + + mask = 0; + if (bank_id >= db->num_banks) { + return 0; + } + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + if (db->slot[bank_id][slot_id].present) { + mask |= (uint8_t)(1u << slot_id); + } + } + return mask; +} + +/* ProvisionedSlotMask (Param2 of GET_DIGESTS): bit set <=> the slot has a certificate chain, OR -- + * only in a multikey connection -- it has an associated key pair (DSP0274 Table 41; see A1). The + * caller passes multi_key = (MULTI_KEY_CONN_REQ || MULTI_KEY_CONN_RSP). */ +uint8_t libspdm_db_provisioned_slot_mask(const libspdm_db_t *db, uint8_t bank_id, bool multi_key) +{ + uint8_t mask; + uint8_t slot_id; + + LIBSPDM_ASSERT(db != NULL); + + mask = 0; + if (bank_id >= db->num_banks) { + return 0; + } + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + if (db->cert_chain[bank_id][slot_id].present || + (multi_key && db->slot_key_assoc[bank_id][slot_id].present)) { + mask |= (uint8_t)(1u << slot_id); + } + } + return mask; +} + +/* AssocCertSlotMask (KEY_PAIR_INFO Table 111): the inverse projection of slot_key_assoc -- bit + * slot_id set <=> slot (bank_id, slot_id) is associated with key_pair_id. bank_id is the key pair's + * (unique) bank; not gated on multikey. */ +uint8_t libspdm_db_assoc_cert_slot_mask(const libspdm_db_t *db, + uint8_t bank_id, uint8_t key_pair_id) +{ + uint8_t mask; + uint8_t slot_id; + + LIBSPDM_ASSERT(db != NULL); + + mask = 0; + if ((bank_id >= db->num_banks) || (key_pair_id == 0)) { + return 0; + } + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + const libspdm_db_slot_key_assoc_t *assoc = &db->slot_key_assoc[bank_id][slot_id]; + + if (assoc->present && (assoc->key_pair_id == key_pair_id)) { + mask |= (uint8_t)(1u << slot_id); + } + } + return mask; +} + +/* Count the slots associated with key_pair_id, EXCLUDING (except_bank_id, except_slot_id). Used by + * libspdm_db_associate_slot_key() to enforce ShareableCap cardinality: a non-shareable key pair may + * back at most one slot, so the count of OTHER slots must be 0 before a new association. Excluding + * the target slot keeps re-association of the same slot idempotent. (A key pair maps to one bank, so + * a consistent store has all its associations in that bank; scanning all banks is a cheap defensive + * read that cannot under-count.) */ +size_t libspdm_db_count_key_pair_assoc_other(const libspdm_db_t *db, uint8_t key_pair_id, + uint8_t except_bank_id, uint8_t except_slot_id) +{ + size_t count; + uint8_t bank_id; + uint8_t slot_id; + + LIBSPDM_ASSERT(db != NULL); + + count = 0; + for (bank_id = 0; bank_id < db->num_banks; bank_id++) { + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + const libspdm_db_slot_key_assoc_t *assoc = &db->slot_key_assoc[bank_id][slot_id]; + + if (!assoc->present || (assoc->key_pair_id != key_pair_id)) { + continue; + } + if ((bank_id == except_bank_id) && (slot_id == except_slot_id)) { + continue; /* skip the slot we are (re-)associating */ + } + count++; + } + } + return count; +} + +/* Read a slot's certificate chain (Table 39). Returns false if the slot is not provisioned (no + * cert_chain row) or the caller buffer is too small. */ +bool libspdm_db_get_cert_chain(const libspdm_db_t *db, uint8_t bank_id, uint8_t slot_id, + void *chain, size_t *chain_size) +{ + const libspdm_db_cert_chain_t *cert; + + LIBSPDM_ASSERT((db != NULL) && (chain_size != NULL)); + + if ((bank_id >= db->num_banks) || (slot_id >= db->config.db_max_slots) || + !db->cert_chain[bank_id][slot_id].present) { + return false; + } + cert = &db->cert_chain[bank_id][slot_id]; + if (*chain_size < cert->der_size) { + *chain_size = cert->der_size; /* report the required size */ + return false; + } + libspdm_copy_mem(chain, *chain_size, cert->der, cert->der_size); + *chain_size = cert->der_size; + return true; +} +``` + +### Writers + +```c +/* Install (or stage) a certificate chain: state 2/3 -> 4. The slot must already exist and be + * associated with a key pair of the bank's algorithm (so the chain is verifiable under exactly one + * algorithm). When reset_required is true the write lands in cert_chain_pending and is committed by + * libspdm_db_apply_pending(); otherwise it commits to cert_chain directly (Gap #873). */ +bool libspdm_db_set_certificate(libspdm_db_t *db, uint8_t bank_id, uint8_t slot_id, + uint8_t certificate_info, const void *der, size_t der_size, + bool reset_required) +{ + libspdm_db_cert_chain_t *target; + + LIBSPDM_ASSERT((db != NULL) && ((der != NULL) || (der_size == 0))); + + if ((bank_id >= db->num_banks) || (slot_id >= db->config.db_max_slots) || + !db->slot[bank_id][slot_id].present || + !db->slot_key_assoc[bank_id][slot_id].present || + (der_size > LIBSPDM_DB_CERT_CHAIN_MAX)) { + return false; + } + + target = reset_required ? &db->cert_chain_pending[bank_id][slot_id] + : &db->cert_chain[bank_id][slot_id]; + target->bank_id = bank_id; + target->slot_id = slot_id; + target->present = true; + target->certificate_info = certificate_info; + target->der_size = der_size; + libspdm_copy_mem(target->der, sizeof(target->der), der, der_size); + if (reset_required) { + db->cert_chain_pending_erase[bank_id][slot_id] = false; + } + return true; +} + +/* Erase a slot's certificate chain, KEEPING the key: state 4 -> 3 (DSP0274 "Erase keeps the key"). + * Only the cert_chain row is cleared; slot_key_assoc is untouched. */ +bool libspdm_db_erase_certificate(libspdm_db_t *db, uint8_t bank_id, uint8_t slot_id) +{ + LIBSPDM_ASSERT(db != NULL); + + if ((bank_id >= db->num_banks) || (slot_id >= db->config.db_max_slots) || + !db->slot[bank_id][slot_id].present) { + return false; + } + libspdm_zero_mem(&db->cert_chain[bank_id][slot_id], sizeof(db->cert_chain[bank_id][slot_id])); + db->cert_chain[bank_id][slot_id].bank_id = bank_id; + db->cert_chain[bank_id][slot_id].slot_id = slot_id; + db->cert_chain[bank_id][slot_id].present = false; + return true; +} + +/* Associate a slot with a key pair: state 2 -> 3. A slot's key pair must use the bank's algorithm + * (Gap #3638), the key pair must allow association (CertAssocCap) and exist (public_key_info_len), + * and ShareableCap cardinality must hold. This is the single authoritative association write. */ +bool libspdm_db_associate_slot_key(libspdm_db_t *db, uint8_t bank_id, uint8_t slot_id, + uint8_t key_pair_id) +{ + libspdm_db_key_pair_t *key_pair; + + LIBSPDM_ASSERT(db != NULL); + + if ((bank_id >= db->num_banks) || (slot_id >= db->config.db_max_slots) || + !db->slot[bank_id][slot_id].present || + (key_pair_id == 0) || (key_pair_id > db->total_key_pairs)) { + return false; + } + /* the key pair's current algorithm must equal the bank's algorithm (key_pair is indexed by + * key_pair_id - 1). Both traditional and PQC are uint32_t, so this is a plain comparison. */ + key_pair = &db->key_pair[key_pair_id - 1]; + if ((key_pair->current_asym_algo != db->bank[bank_id].algo.asym_algo) || + (key_pair->current_pqc_asym_algo != db->bank[bank_id].algo.pqc_asym_algo)) { + return false; /* key pair algorithm does not match bank algorithm */ + } + /* CertAssocCap: the Responder must allow changing the key-pair/slot association. */ + if ((key_pair->capabilities & SPDM_KEY_PAIR_CAP_CERT_ASSOC_CAP) == 0) { + return false; + } + /* the key pair must actually exist (key material generated) before it can back a slot. This is + * the "public_key_info_len == 0 => no slot_key_assoc may reference this key pair" invariant. */ + if (key_pair->public_key_info_len == 0) { + return false; + } + /* ShareableCap cardinality (DSP0274 Table 112 / AssocCertSlotMask): a key pair without + * ShareableCap may be associated with at most one slot. If it is not shareable and is already + * associated with a different (bank, slot), reject the second association. */ + if ((key_pair->capabilities & SPDM_KEY_PAIR_CAP_SHAREABLE_CAP) == 0) { + if (libspdm_db_count_key_pair_assoc_other(db, key_pair_id, bank_id, slot_id) != 0) { + return false; + } + } + db->slot_key_assoc[bank_id][slot_id].bank_id = bank_id; + db->slot_key_assoc[bank_id][slot_id].slot_id = slot_id; + db->slot_key_assoc[bank_id][slot_id].present = true; + db->slot_key_assoc[bank_id][slot_id].key_pair_id = key_pair_id; + return true; +} + +/* GenerateKeyPair (SET_KEY_PAIR_INFO): generate key material and set the current algorithm/usage of + * a key pair. Requires GenKeyCap. The current algorithm must be a single bit within the key pair's + * AsymAlgoCapabilities/PqcAsymAlgoCapabilities and the usage a subset of KeyUsageCapabilities. (The + * actual key generation is a HAL call; here we record the resulting public key and configuration.) + * + * Per DSP0274 "Key pair ID modification error handling", GenerateKeyPair has two STATE preconditions + * beyond the capability gate (both -> OperationFailed if violated, here reported as false): + * - the key pair must NOT be associated with any certificate slot; and + * - an asymmetric algorithm must already be selected (generate is bound to the current algorithm). + * The caller passes that already-selected algorithm in *algo; this function records it and the key. */ +bool libspdm_db_generate_key_pair(libspdm_db_t *db, uint8_t key_pair_id, + const libspdm_db_algo_t *algo, uint16_t key_usage, + const void *public_key_info, uint16_t public_key_info_len) +{ + libspdm_db_key_pair_t *key_pair; + uint8_t bank_id; + + LIBSPDM_ASSERT((db != NULL) && (algo != NULL)); + + if ((key_pair_id == 0) || (key_pair_id > db->total_key_pairs) || + (public_key_info_len == 0) || (public_key_info_len > LIBSPDM_DB_PUBKEY_INFO_MAX)) { + return false; + } + key_pair = &db->key_pair[key_pair_id - 1]; + if ((key_pair->capabilities & SPDM_KEY_PAIR_CAP_GEN_KEY_CAP) == 0) { + return false; + } + /* State precondition: an asymmetric algorithm must already be selected (OperationFailed). A + * GenerateKeyPair with no algorithm selected has nothing to bind the key to. */ + if ((algo->asym_algo == 0) && (algo->pqc_asym_algo == 0)) { + return false; + } + /* exactly one algorithm bit total, within the corresponding capability bitmap. */ + if (!libspdm_db_algo_is_single_supported(algo, key_pair->asym_algo_capabilities, + key_pair->pqc_asym_algo_capabilities)) { + return false; + } + /* State precondition: the key pair must not be associated with any certificate slot + * (OperationFailed) -- the certs must be erased and the slots dissociated first. */ + for (bank_id = 0; bank_id < db->num_banks; bank_id++) { + if (libspdm_db_assoc_cert_slot_mask(db, bank_id, key_pair_id) != 0) { + return false; + } + } + /* current usage must be a subset of the advertised key-usage capabilities. */ + if ((key_usage & ~key_pair->key_usage_capabilities) != 0) { + return false; + } + key_pair->current_asym_algo = algo->asym_algo; + key_pair->current_pqc_asym_algo = algo->pqc_asym_algo; + key_pair->current_key_usage = key_usage; + key_pair->public_key_info_len = public_key_info_len; + libspdm_copy_mem(key_pair->public_key_info, sizeof(key_pair->public_key_info), + public_key_info, public_key_info_len); + return true; +} + +/* KeyPairErase (SET_KEY_PAIR_INFO): remove a key pair's key material: state 3 -> 2 for every slot it + * backs. Requires ErasableCap and -- per DSP0274 -- that no slot is still associated with it (the + * caller dissociates first), preserving the "public_key_info_len == 0 => no assoc" invariant. */ +bool libspdm_db_erase_key_pair(libspdm_db_t *db, uint8_t key_pair_id) +{ + libspdm_db_key_pair_t *key_pair; + uint8_t bank_id; + + LIBSPDM_ASSERT(db != NULL); + + if ((key_pair_id == 0) || (key_pair_id > db->total_key_pairs)) { + return false; + } + key_pair = &db->key_pair[key_pair_id - 1]; + if ((key_pair->capabilities & SPDM_KEY_PAIR_CAP_ERASABLE_CAP) == 0) { + return false; + } + /* must not be associated with any slot. */ + for (bank_id = 0; bank_id < db->num_banks; bank_id++) { + if (libspdm_db_assoc_cert_slot_mask(db, bank_id, key_pair_id) != 0) { + return false; + } + } + key_pair->current_asym_algo = 0; + key_pair->current_pqc_asym_algo = 0; + key_pair->current_key_usage = 0; + key_pair->public_key_info_len = 0; + libspdm_zero_mem(key_pair->public_key_info, sizeof(key_pair->public_key_info)); + return true; +} + +/* Set a key pair's CurrentKeyUsage (SET_KEY_PAIR_INFO). Requires KeyUsageCap; the new usage must be + * a subset of KeyUsageCapabilities. Usage is per key pair, never per slot. */ +bool libspdm_db_set_key_usage(libspdm_db_t *db, uint8_t key_pair_id, uint16_t key_usage) +{ + libspdm_db_key_pair_t *key_pair; + + LIBSPDM_ASSERT(db != NULL); + + if ((key_pair_id == 0) || (key_pair_id > db->total_key_pairs)) { + return false; + } + key_pair = &db->key_pair[key_pair_id - 1]; + if ((key_pair->capabilities & SPDM_KEY_PAIR_CAP_KEY_USAGE_CAP) == 0) { + return false; + } + if ((key_usage & ~key_pair->key_usage_capabilities) != 0) { + return false; + } + key_pair->current_key_usage = key_usage; + return true; +} + +/* Set a key pair's CurrentAsymAlgo/CurrentPqcAsymAlgo (SET_KEY_PAIR_INFO ParameterChange). Requires + * AsymAlgoCap; the algorithm must be a single bit within the capability bitmaps. + * + * Per DSP0274 "Key pair ID modification error handling", once a key pair has been GENERATED the + * Responder shall not change a parameter that affects the generated key, such as the asymmetric + * algorithm (the key value is bound to CurrentAsymAlgo). So a *change* of algorithm is rejected when + * key material exists (public_key_info_len != 0); re-selecting the same algorithm is a no-op and + * allowed. (A key pair is also never associated to a slot without key material, so the generated + * check subsumes the old association check.) */ +bool libspdm_db_set_key_algo(libspdm_db_t *db, uint8_t key_pair_id, const libspdm_db_algo_t *algo) +{ + libspdm_db_key_pair_t *key_pair; + + LIBSPDM_ASSERT((db != NULL) && (algo != NULL)); + + if ((key_pair_id == 0) || (key_pair_id > db->total_key_pairs)) { + return false; + } + key_pair = &db->key_pair[key_pair_id - 1]; + if ((key_pair->capabilities & SPDM_KEY_PAIR_CAP_ASYM_ALGO_CAP) == 0) { + return false; + } + if (!libspdm_db_algo_is_single_supported(algo, key_pair->asym_algo_capabilities, + key_pair->pqc_asym_algo_capabilities)) { + return false; + } + /* A generated key is bound to its algorithm: reject a *different* algorithm once key material + * exists; re-selecting the current algorithm is an allowed no-op. */ + if ((key_pair->public_key_info_len != 0) && + ((key_pair->current_asym_algo != algo->asym_algo) || + (key_pair->current_pqc_asym_algo != algo->pqc_asym_algo))) { + return false; + } + key_pair->current_asym_algo = algo->asym_algo; + key_pair->current_pqc_asym_algo = algo->pqc_asym_algo; + return true; +} + +/* ManageBank ConfigAlgo: set a bank's algorithm. Requires the bank's CONFIG_ALGO attribute, the new + * algorithm to be unique across configured banks, and -- per DSP0274 Table 141 -- NO slot in the + * bank to have a certificate provisioned (i.e. no slot in state 4; states 1/2/3 are allowed, and + * their key associations are dropped by the cascade below). On success it clears all slot settings + * in the bank (DSP0274 "clear all slot settings" cascade). NOTE: dropping a state-3 slot's + * association also changes the affected key pair's derived AssocCertSlotMask -- a KEY_PAIR_INFO- + * visible side effect whose scope is an open spec question (see "Open spec questions" #2). + * + * ConfigAlgo must select EXACTLY ONE algorithm: an all-zero algo (no SelectAsymAlgo/SelectPqcAsymAlgo + * bit set) is rejected. Table 145's "no more than one bit" bounds the upper count (one algorithm per + * Bank); it does not permit zero. There is no "unconfigure the bank" operation -- a bank is in the + * Table 151 "not yet selected" state only before it has ever been configured. */ +bool libspdm_db_config_bank_algo(libspdm_db_t *db, uint8_t bank_id, const libspdm_db_algo_t *algo) +{ + uint8_t other; + uint8_t slot_id; + bool has_asym; + bool has_pqc; + + LIBSPDM_ASSERT((db != NULL) && (algo != NULL)); + + if (bank_id >= db->num_banks) { + return false; + } + if ((db->bank[bank_id].bank_attributes & SPDM_SLOT_MANAGEMENT_BANK_ATTRIBUTE_CONFIG_ALGO) == 0) { + return false; + } + /* exactly one algorithm bit total, in exactly one of the two fields (all-zero is rejected; + * "no more than one" never permits zero). */ + has_asym = (algo->asym_algo != 0); + has_pqc = (algo->pqc_asym_algo != 0); + if (has_asym == has_pqc) { + return false; /* neither set (all-zero), or both set */ + } + if (has_asym && ((algo->asym_algo & (algo->asym_algo - 1)) != 0)) { + return false; /* more than one traditional bit */ + } + if (has_pqc && ((algo->pqc_asym_algo & (algo->pqc_asym_algo - 1)) != 0)) { + return false; /* more than one PQC bit */ + } + /* no slot in the bank may have a certificate provisioned (state 4). A state-3 slot (key, no + * cert) is allowed; its association is dropped by the clear-all-slot-settings cascade below. */ + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + if (db->cert_chain[bank_id][slot_id].present) { + return false; + } + } + /* uniqueness across configured banks (algo is guaranteed non-all-zero by the check above). */ + for (other = 0; other < db->num_banks; other++) { + if ((other != bank_id) && + (db->bank[other].algo.asym_algo == algo->asym_algo) && + (db->bank[other].algo.pqc_asym_algo == algo->pqc_asym_algo)) { + return false; + } + } + db->bank[bank_id].algo = *algo; + /* clear all slot settings in the bank (the slots themselves remain, but lose assoc/cert/csr). */ + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + libspdm_db_clear_slot(db, bank_id, slot_id); + } + return true; +} + +/* Reset handler: commit every pending (ResetRequired) write atomically. Called once after a device + * reset, before serving any command. */ +bool libspdm_db_apply_pending(libspdm_db_t *db) +{ + uint8_t bank_id; + uint8_t slot_id; + uint8_t kp; + + LIBSPDM_ASSERT(db != NULL); + + for (bank_id = 0; bank_id < db->num_banks; bank_id++) { + for (slot_id = 0; slot_id < db->config.db_max_slots; slot_id++) { + if (db->cert_chain_pending_erase[bank_id][slot_id]) { + libspdm_db_erase_certificate(db, bank_id, slot_id); + } else if (db->cert_chain_pending[bank_id][slot_id].present) { + db->cert_chain[bank_id][slot_id] = db->cert_chain_pending[bank_id][slot_id]; + } + libspdm_zero_mem(&db->cert_chain_pending[bank_id][slot_id], + sizeof(db->cert_chain_pending[bank_id][slot_id])); + db->cert_chain_pending_erase[bank_id][slot_id] = false; + } + if (db->bank_algo_pending_valid[bank_id]) { + db->bank[bank_id].algo = db->bank_algo_pending[bank_id]; + db->bank_algo_pending_valid[bank_id] = false; + } + } + for (kp = 0; kp < db->total_key_pairs; kp++) { + if (db->key_pair_pending_valid[kp]) { + db->key_pair[kp] = db->key_pair_pending[kp]; + db->key_pair_pending_valid[kp] = false; + } + } + return true; +} +``` + +### Internal helpers + +Two small helpers keep the writers above readable; they are not part of the public Access API. + +```c +/* true iff algo names exactly one algorithm (a single bit, in either the traditional OR the PQC + * field but not both) and that bit is within the supplied capability bitmaps. Used by + * generate_key_pair / set_key_algo to enforce the "at most one current bit, within capabilities" + * invariant. */ +static bool libspdm_db_algo_is_single_supported(const libspdm_db_algo_t *algo, + uint32_t asym_caps, uint32_t pqc_caps) +{ + bool has_asym = (algo->asym_algo != 0); + bool has_pqc = (algo->pqc_asym_algo != 0); + + if (has_asym == has_pqc) { + return false; /* neither set, or both set */ + } + if (has_asym) { + return ((algo->asym_algo & (algo->asym_algo - 1)) == 0) && /* single bit */ + ((algo->asym_algo & ~asym_caps) == 0); /* within caps */ + } + return ((algo->pqc_asym_algo & (algo->pqc_asym_algo - 1)) == 0) && + ((algo->pqc_asym_algo & ~pqc_caps) == 0); +} + +/* Clear one slot's child rows (the ON DELETE CASCADE for slot's children): drop its key + * association, certificate chain, pending cert, and any CSR that targets it. The slot row itself is + * left intact; pass a separate erase of slot[].present to remove the slot entirely. */ +static void libspdm_db_clear_slot(libspdm_db_t *db, uint8_t bank_id, uint8_t slot_id) +{ + size_t i; + + libspdm_zero_mem(&db->slot_key_assoc[bank_id][slot_id], + sizeof(db->slot_key_assoc[bank_id][slot_id])); + libspdm_zero_mem(&db->cert_chain[bank_id][slot_id], + sizeof(db->cert_chain[bank_id][slot_id])); + libspdm_zero_mem(&db->cert_chain_pending[bank_id][slot_id], + sizeof(db->cert_chain_pending[bank_id][slot_id])); + db->cert_chain_pending_erase[bank_id][slot_id] = false; + for (i = 0; i < LIBSPDM_ARRAY_SIZE(db->csr); i++) { + if (db->csr[i].present && (db->csr[i].bank_id == bank_id) && + (db->csr[i].slot_id == slot_id)) { + libspdm_zero_mem(&db->csr[i], sizeof(db->csr[i])); + } + } +} +``` + +## Mapping to the libspdm sample + +The sample can realize the same model with the `libspdm_db_t` arrays above (or keep its current +fixed arrays, extended with the one authoritative association): + +| Relation | Sample realization | +| -------- | ------------------ | +| `libspdm_db_algo_t` | a value type (Table 113/114 wire bits); the existing algorithm encode/decode helpers, not a stored table | +| `key_pair` | the `GET_KEY_PAIR_INFO` table (already present) | +| `bank` | `m_slot_management_bank[]` (derived from key pairs today) | +| `slot` | `bank->slots[]` (existence == in the array) | +| `slot_key_assoc` | a `key_pair_id` field on the slot, with the inverse computed for `AssocCertSlotMask` | +| `cert_chain` | the per-slot certificate NVM file (already the single source of truth) | +| `csr` | the existing CSR NVM / `CSRTrackingTag` state | + +The key change to resolve the gaps is to make the **association** (`slot_key_assoc`) and the +**certificate presence** (`cert_chain`) single authoritative facts that `GET_DIGESTS`, +`GET_KEY_PAIR_INFO`, and the `SLOT_MANAGEMENT` SubCodes all *read*, instead of each command keeping +its own array. This document is the schema to converge them on. + +## Capacity and fidelity limitations + +The capacities here are **deliberate sample limits**, recorded in the self-descriptive `config` +header so a consumer knows the dimensions. They are below the spec maxima and may be raised (the +arrays and `config` scale directly); the relational design does not depend on the specific values. + +| Item | Sample value | Spec allows | Note | +| ---- | ------------ | ----------- | ---- | +| `LIBSPDM_DB_MAX_BANKS` | 16 | `BankID` 0–239 | A real responder need not impose a fixed bank maximum; this is a sample storage cap, not a protocol limit. | +| `LIBSPDM_DB_MAX_KEY_PAIRS` | 16 | `TotalKeyPairs` is `uint8` (up to 255) | Sample storage cap. | +| `SlotSize` | one `LIBSPDM_DB_CERT_CHAIN_MAX` for all slots | `SlotElement.SlotSize` is per-slot (Table 152) | The model reports the same capacity for every slot; heterogeneous per-slot sizes would need a per-slot capacity field. | +| dense arrays + full `cert_chain_pending` | `[16][8]` materialized; two cert buffers/slot | — | Sparse data in fully-materialized arrays and a full pending shadow; an implementation cost, not a logical limit. | + +## Open spec questions + +These are points in DSP0274 that this review found ambiguous; they are recorded here as open +questions for a spec clarification. The data model takes the most consistent reading available but +the resolution may change some details above. + +1. **Should a `csr` entry retain a `BankID`?** The `SLOT_MANAGEMENT` `GetCSR` `SlotAddress` + (Table 143) carries a `(BankID, SlotID)`, but the spec states that for `GetCSR` "the value of the + `SlotID` field shall be zero. The SPDM Responder shall ignore the value of this field" — so a CSR + is never slot-scoped (the legacy `GET_CSR`, Table 100, has no slot address either). The model + accordingly keys a CSR by `CSRTrackingTag` alone and identifies the key material by `KeyPairID`, + not by a slot. What remains open is whether the entry should also retain a `bank_id`. + + Keeping `bank_id` would be **redundant with `KeyPairID`** and could drift — the exact + denormalization this proposal exists to remove. A key pair maps to at most one bank (the unique + bank whose `algo` equals the key pair's current algorithm — see the key_pair "maps to at most one + bank" invariant), so the bank a CSR belongs to is already determined by its `key_pair_id`. Nothing + *reads* a stored `csr.bank_id`: the `ManageBank ConfigAlgo` clear-CSR cascade can resolve affected + CSRs by `key_pair_id → bank`. At most the request's `BankID` is a value to *validate* against the + key pair's bank (reject on mismatch), which does not require *storing* it. Two consistent options, + pending confirmation: + * **Drop `bank_id`:** key the `csr` by `CSRTrackingTag` + `KeyPairID`; derive the bank from the key + pair wherever needed (recommended — no second copy to drift). + * **Keep `bank_id` as a derived cache:** store it only as a cache of "the key pair's bank" with an + asserted invariant (`csr.bank_id == bank_of(key_pair_id)`), redundant by construction and never a + second source of truth — the same treatment the schema gives a row's stored-key-equals-index. + +2. **What does `ManageBank ConfigAlgo` "clear all slot settings" mean, and may it implicitly drop a + key↔slot association?** Table 141 says only "When the Bank configuration changes, the Responder + shall clear all slot settings" — it does not **define "slot settings"**, and it allows `ConfigAlgo` + for any slot that has no certificate provisioned (only a state-4 slot, with a cert, gives + `InvalidState`). A state-3 slot (key associated, no cert) is therefore permitted, and the model's + reading of "slot settings" includes the per-slot key↔slot association (`slot_key_assoc`), so the + cascade drops it. + + Two concerns with that reading: + * **It changes the key-pair view, not just a "slot" view.** `KEY_PAIR_INFO.AssocCertSlotMask` is + the inverse of the association, so clearing `slot_key_assoc[bank][slot]` removes that slot's bit + from the affected `KeyPairID`'s `AssocCertSlotMask`. A `GET_KEY_PAIR_INFO` issued after the + `ConfigAlgo` thus reports a **different `AssocCertSlotMask`** even though no `SET_KEY_PAIR_INFO` + was sent. So a `ManageBank` operation has a side effect on `KEY_PAIR_INFO` state — arguably + beyond "slot settings", which reads as slot-local. + * **It bypasses the `SET_KEY_PAIR_INFO` ordering rules.** Normally an association can only be + removed via `SET_KEY_PAIR_INFO ParameterChange` (gated on `CertAssocCap`), and a key pair is + dissociated *before* its key is erased. `ConfigAlgo` dropping the association sidesteps that path + and its capability gate. + + Open questions: (a) Does "slot settings" include the key↔slot association, or only the certificate + chain (and CSR)? (b) If it includes the association, is the resulting `AssocCertSlotMask` change an + intended, spec-sanctioned side effect of `ManageBank` on `KEY_PAIR_INFO`? (c) Should a state-3 slot + (key, no cert) instead **block** `ConfigAlgo` (e.g. `InvalidState`), requiring the Requester to + dissociate the key via `SET_KEY_PAIR_INFO` first — symmetric with the state-4 (cert) rejection? The + model currently takes reading (a)=includes-association, (b)=treated-as-intended, (c)=allowed-and- + dropped; a stricter responder that rejects a state-3 slot would also be consistent with Table 141 + as written. + +3. **What happens to an outstanding CSR when its key pair is erased by `SET_KEY_PAIR_INFO` + `KeyPairErase`?** A `GET_CSR` transaction is for a `KeyPairID` (it stores `key_pair_id`), and on the + reset-required flow it persists across a reset as a `PENDING_RESET` `CSRTrackingTag` awaiting later + retrieval. DSP0274 defines the `GET_CSR` flow and the `KeyPairErase` operation separately and does + **not** state their interaction: the `KeyPairErase` preconditions in §"Key pair ID modification + error handling" mention only certificate-slot association, not an outstanding CSR. So whether a + `KeyPairErase` is allowed while a CSR for that key pair is still outstanding is unspecified. + + The spec does give the Responder latitude here — "the Responder can discard any associated CSR data + and reuse the `CSRTrackingTag`" — so a Responder *may* legally drop the pending CSR on erase. But a + recommended behavior would aid interoperability, because a silently discarded `PENDING_RESET` CSR + turns the Requester's later retrieval into `UnexpectedRequest`/drop. Two consistent options: + * **Reject the erase** while a CSR for the key pair is outstanding (`OperationFailed`) — symmetric + with the existing "no certificate-slot association" precondition, and it does not break an + in-flight reset-required CSR transaction. + * **Cascade-discard** the pending CSR(s) on erase — consistent with the model's referential-cascade + rule (a `csr` row's `key_pair_id` is an FK → `key_pair`, so the child should not outlive its + parent), and with the transient-CSR latitude above. + + **The model does not currently address this:** `libspdm_db_erase_key_pair()` checks only slot + association and never inspects the `csr` pool, so today a `KeyPairErase` can leave a `csr` entry + whose `key_pair_id` dangles at erased key material. This is an unhandled gap, not a chosen reading; + the resolution (reject vs. cascade-discard) is pending. + +## Notes and non-goals + +* "Selected bank" and the negotiated algorithm are **connection** state, not device state, so they + are not stored in these tables; they are parameters of the read queries. The selected-bank readers + (`slot_state`, `*_slot_mask`, `Digest`) take no algorithm parameter because the bank is *selected + by* the negotiated algorithm — within the selected bank everything matches by construction, so the + connection-dependence of `ProvisionedSlotMask`/`Digest` is discharged by the bank=algo identity. +* The legacy `SET_CERTIFICATE`/`GET_CERTIFICATE` "selected bank aliases the BankID-less slot" + behavior is **out of scope** here: this model addresses everything as `(bank_id, slot_id)` and + treats selected-bank aliasing as connection-layer behavior, not device state. +* `GenerateKeyPair` may carry `DesiredAssocCertSlotMask` in the same request (generate-and-associate + atomically). The write table lists generate and associate as separate steps for clarity; an + implementation may fuse them, applying the same `CertAssocCap`/`ShareableCap` checks. +* This is a device-backend data model, not an SPDM wire change. No message format is altered. +* Capability bits (in `key_pair.capabilities` and the `CONFIG_ALGO` bit of `bank.bank_attributes`) + are immutable device facts; + only the `current_*` fields and the `slot`/`slot_key_assoc`/`cert_chain`/`csr` rows are mutated by + `SET_*` commands.