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.
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.
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 */
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 */
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/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/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/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_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++) {
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 */
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 */
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
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;
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 || \