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:

  1. Wait (up to 10 s) for CertReadyBit so the cert material is available (src/nv/spdm/task.cpp:320).
  2. 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.
  3. Snapshot the assembled chain into the engine’s slot-0 buffer via get_cert_chain and get_cert_chain_length (src/nv/spdm/task.cpp:388).
  4. 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_func with libspdm_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 by verify_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) and generate_l5_cert (src/nv/spdm/spdm_cert_chain.h:392), then returned to the engine by get_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::Spdm routing: MCTP transport.
  • The wider trust anchor (vRoT, secure boot, fuses): Security & Root-of-Trust.
  • How PLDM consumes the authentication result: PLDM.

Built with Just the Docs. Source-traced against NVIDIA/OpenSMA at 9b183d3 (v02.0020.0000). Methodology under appendices/trace-methodology.

This site uses Just the Docs, a documentation theme for Jekyll.