SPDM (Security Protocol and Data Model)
OpenSMA is an SPDM responder. A host-side requester uses SPDM to
authenticate the controller (certificate chain), read signed
measurements of what the device is running, and — internally — to
authenticate firmware images before PLDM activates them. The protocol
engine itself is DMTF’s libspdm, vendored under
corepdk/modules/spdm/; the src/nv/spdm/ layer supplies the RTOS task,
the certificate chain, the measurement providers, and the crypto glue.
| Layer | Path | Responsibility |
|---|---|---|
| Task / responder | src/nv/spdm/task.cpp |
RTOS task, per-interface engine contexts, auth dispatch |
| libspdm wrapper | corepdk/modules/spdm/src/app/ |
SpdmEngineContext, responder callbacks (cert, measurement) |
| Certificate chain | src/nv/spdm/spdm_cert_chain.* |
L1–L5 chain assembly, get_cert_chain |
| DER/ASN.1 helpers | src/nv/spdm/cert_library.* |
signature + public-key parsing/verification |
| Key generation | src/nv/spdm/ik_generate_helper.*, ak_generate_helper.h |
Identity-Key / Attestation-Key cert templates |
| Measurements | src/nv/spdm/spdm_get_measurement.*, spdm_ap_measurement.* |
measurement cache + AP measurement providers |
The SPDM task and its libspdm engines
nv::spdm::Task (src/nv/spdm/task.h:56) derives from the common IPC
task. Unlike a normal message pump, most of its state is the libspdm
context it drives: an array of SpdmEngineContext, one per responder
interface (src/nv/spdm/task.h:104). There are one or two interfaces —
USB always, plus I2C when SpdmI2cResponder is configured
(src/nv/spdm/task.h:36). The event bits at src/nv/spdm/task.h:60
separate the transport paths (UsbRxBit, I2cRxBit) from the internal
firmware-authentication path (AuthenticateRequestBit,
CryptoPrimitiveRequestBit).
Inbound SPDM packets reach the task the same way PLDM does. The MCTP
driver forwards Client::Spdm traffic by calling
nv::spdm::Task::spdm_tx (src/nv/mctp/driver.cpp:429). spdm_tx
(src/nv/spdm/task.cpp:257) drops packets from an unsupported interface,
enqueues the raw packet on SpdmRx, and raises UsbRxBit or I2cRxBit
according to the arrival interface.
Boot-time initialisation
Task::main (src/nv/spdm/task.cpp:314) does the heavy lifting before
serving any request:
- Wait (up to 10 s) for
CertReadyBitso the cert material is available (src/nv/spdm/task.cpp:320). - Build the leaf certificates. Unless dummy certs are configured, it
verifies the L2→L3 link and generates the L4 and L5 certs —
nv::spdm::cert::verify_l2_l3_cert,generate_l4_cert,generate_l5_cert(src/nv/spdm/task.cpp:369). Only if all succeed is the slot marked usable. - Snapshot the assembled chain into the engine’s slot-0 buffer via
get_cert_chainandget_cert_chain_length(src/nv/spdm/task.cpp:388). - Initialise each interface’s libspdm engine by calling
spdm_engine_initialize()in a loop (src/nv/spdm/task.cpp:393).
The measurement cache is seeded even earlier, in Task::make, via
spdm_init_all_measurement_cache (src/nv/spdm/task.cpp:245), and the
debug-token nonce measurement is refreshed at
src/nv/spdm/task.cpp:385.
Wiring libspdm to MCTP
SpdmEngineContext is a thin handle over a libspdm context plus its
scratch buffer (corepdk/modules/spdm/src/app/pdk-spdm-app-res-library.h:54).
spdm_engine_initialize
(corepdk/modules/spdm/src/app/pdk-spdm-app-res-library.cpp:174)
allocates the context, then registers the device and transport callbacks:
libspdm_register_device_io_func(corepdk/modules/spdm/src/app/pdk-spdm-app-res-library.cpp:197) — send/receive into the OpenSMA MCTP path.libspdm_register_transport_layer_funcwithlibspdm_transport_mctp_encode_message/..._decode_message(corepdk/modules/spdm/src/app/pdk-spdm-app-res-library.cpp:199) — so libspdm frames SPDM over MCTP directly.
It then pushes the negotiated versions (1.1/1.2/1.3), capabilities,
hash/asym algorithms, and the certificate chain into the context
with a series of libspdm_set_data calls.
Serving a request
Once initialised, the loop dispatches by event bit. A UsbRxBit calls
spdm_engine_context[0].process_message()
(src/nv/spdm/task.cpp:424), which is a one-liner over
libspdm_responder_dispatch_message
(corepdk/modules/spdm/src/app/pdk-spdm-app-res-library.cpp:171).
libspdm decodes the SPDM request, runs the responder state machine, and
calls back into the app-layer providers (certificate, measurement,
crypto) to build the response — the C++ layer never hand-parses SPDM
opcodes.
sequenceDiagram
participant R as SPDM Requester (host)
participant M as MCTP driver
participant T as spdm::Task
participant L as libspdm engine
R->>M: GET_VERSION / GET_CAPABILITIES / NEGOTIATE_ALGORITHMS
M->>T: spdm_tx() enqueue + set UsbRxBit
T->>L: process_message() -> dispatch
L-->>R: VERSION / CAPABILITIES / ALGORITHMS
R->>M: GET_DIGESTS / GET_CERTIFICATE
M->>T: UsbRxBit
T->>L: process_message()
L-->>R: DIGESTS / CERTIFICATE (L1..L5 chain)
R->>M: CHALLENGE / GET_MEASUREMENTS
M->>T: UsbRxBit
T->>L: process_message()
L-->>R: CHALLENGE_AUTH / MEASUREMENTS (signed)
The certificate chain (L1–L5)
OpenSMA presents a five-level ECDSA-P384 chain, assembled in the
nv::spdm::cert namespace (src/nv/spdm/spdm_cert_chain.h:27). The
libspdm-format chain is prefixed by a header carrying the root hash
(CertchainheaderT, src/nv/spdm/spdm_cert_chain.h:34).
- L1/L2 — the NVIDIA Device Identity CA roots, compiled in as
HardCodeL1Cert/HardCodeL2Cert(with distinct Dev vs. production bytes,src/nv/spdm/spdm_cert_chain.h:55). - L3 — device identity cert read from flash at
L3CertPhyAddr(src/nv/spdm/spdm_cert_chain.h:41); its link to L2 is checked byverify_l2_l3_cert(src/nv/spdm/spdm_cert_chain.h:370). - L4 (Identity Key) and L5 (Attestation Key) — generated at boot
into RAM by
generate_l4_cert(src/nv/spdm/spdm_cert_chain.h:387) andgenerate_l5_cert(src/nv/spdm/spdm_cert_chain.h:392), then returned to the engine byget_cert_chain(src/nv/spdm/spdm_cert_chain.h:350).
The low-level DER work lives in a separate nv::spdm::certlib namespace
(src/nv/spdm/cert_library.h:31). It defines the ASN.1 token constants
and structures (Signature, SubjectPublicKeyInfo) and the parsers:
check_certificate_format_valid (src/nv/spdm/cert_library.h:124),
parse_ecdsa_p384_pubkey, and validate_certificate_signature
(src/nv/spdm/cert_library.cpp:196) — the last hashes a cert with
spdm_hash_data (src/nv/spdm/cert_library.cpp:224) and verifies it
against the preceding cert’s public key with spdm_ecdsa_verify
(src/nv/spdm/cert_library.cpp:234).
The key-cert templates are built from packed structs: the Attestation
Key template DevAkTemplate
(src/nv/spdm/ak_generate_helper.h:28, namespace nv::spdm::ak at
src/nv/spdm/ak_generate_helper.h:23) and the Identity Key helpers in
src/nv/spdm/ik_generate_helper.h / .cpp.
Measurements
When the requester issues GET_MEASUREMENTS, libspdm calls the
app-layer provider libspdm_measurement_collection
(corepdk/modules/spdm/src/app/pdk-spdm-app-res-measurements.cpp:180),
which iterates the platform measurement blocks. The set of measurements
OpenSMA reports is enumerated by the MeasNumT index list
(src/nv/spdm/spdm_get_measurement.h:73) with a parallel type/size table
MeasInfos (src/nv/spdm/spdm_get_measurement.h:152). It spans block
version, AP type, AP firmware / metadata hashes, fuse and rollback /
key-revocation values, MCU active/inactive security versions, the
MCXN556 UUID, boot status, and debug-token configuration.
Values that must survive across the transaction are held in the small
MeasurementCacheT (src/nv/spdm/spdm_get_measurement.h:42). AP-specific
measurements come from the nv::spdm::ap_measurement providers
(src/nv/spdm/spdm_ap_measurement.h:24) — e.g. get_ap_firmware_hash
(src/nv/spdm/spdm_ap_measurement.h:26), rollback / key-revocation
fuses, metadata hash, and firmware version.
Firmware authentication (the PLDM tie-in)
The same SPDM task also authenticates firmware images on behalf of PLDM.
An AuthenticateRequestBit (src/nv/spdm/task.cpp:434) drives
handle_auth_request (src/nv/spdm/task.cpp:456), which dispatches to
handle_mcu_auth_request (src/nv/spdm/task.cpp:41) or
handle_ap_auth_request (src/nv/spdm/task.cpp:56). These call
nv::spdm::crypto::authenticate_mcu_firmware /
authenticate_ap_firmware_with_request_id and post the result back with
send_response_back (src/nv/spdm/task.cpp:468).
Interpretation: concentrating all ECDSA/hash work here — cert chain verification, measurement signing, and image authentication — means the private keys and mbedTLS contexts live in exactly one task, and other subsystems (notably PLDM) request attestation results by message rather than touching crypto themselves.
Where to look next
- The MCTP transport that carries SPDM and the
Client::Spdmrouting: MCTP transport. - The wider trust anchor (vRoT, secure boot, fuses): Security & Root-of-Trust.
- How PLDM consumes the authentication result: PLDM.