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 || \