SPDM-Utils is an open source Linux application designed to support, test and develop SPDM requesters and responders. SPDM-Utils is written in Rust and uses libspdm as the backend.
It can be used as a requester CLI to interface with SPDM devices. It includes support for the PCIe Data Object Exchange (DOE) Capability and MCTP transport layers.
SPDM-Utils can also be used as a responder. It can be an embedded MCTP responder. It can also be used as a responder running on Linux, and exposed to QEMU or other applications via sockets.
SPDM-Utils can use TCP/IP as well. So you can test it all locally as a requester and responder (localhost).
Copyright (c) 2022 Western Digital
SPDM-Utils source code is dual licensed under the Apache-2.0 license and MIT license. A copy of these licenses can be found either in the LICENSE-APACHE or LICENSE-MIT files. Versions are also available at http://www.apache.org/licenses/LICENSE-2.0 and http://opensource.org/licenses/MIT.
See LICENSE-APACHE, LICENSE-MIT, and COPYRIGHT for details.
First you need to install Rust, instructions for that are available at: https://rustup.rs/
You will also need a few host dependencies, the commands below contain all the
dependencies needed by spdm-utils to enable all supported transports.
However, spdm-utils can be built with only the desired transports enabled,
see the Building section.
Note: dnf commands are for Fedora, and apt is used for Debian/Ubuntu based
distributions.
$ sudo dnf install cmake clang-libs clang-devel pciutils-devel openssl openssl-devel python3-devel systemd-devel libnvme
or
$ sudo apt install cmake clang libclang-dev pciutils libpci-dev openssl libssl-dev libsystemd-dev python3-dev pkg-config libnvmespdm-utils uses the cbor-diag ruby gem for
manifest encoding and decoding. Similar to the implementation of this CBOR parsing
online tool.
You will first need to have gem installed, this is the package manager for ruby.
For example, for Fedora you can install it with:
$ sudo dnf install gem
or
$ sudo apt install gem ruby-devAfter which, you can install cbor-diag
$ gem install cbor-diag
or
$ gem install -i <INSTALL_PATH> cbor-diagThe default binary path should (maybe different across distros) be,
$HOME/bin/, which you may need to add to your PATH. You can test that the
scripts are usable with
$ which cbor2diag.rb
home/<user>/bin/cbor2diag.rbWhen building spdm-utils it will generate a manifest.out.cbor which contains
the serialised cbor manifest, and also a manifest.pretty which is the pretty format
of the manifest (user friendly).
First clone spdm-utils and it's submodules
$ git clone --recurse-submodules -j8 https://github.com/westerndigitalcorporation/spdm-utils.gitInitialise all sub-modules
cd third-party/
git submodule init; git submodule update --recursiveTo build libspdm in the third-party directory
cd libspdm/
mkdir build; cd build
cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug \
-DCRYPTO=openssl -DENABLE_BINARY_BUILD=1 \
-DCOMPILED_LIBCRYPTO_PATH=/usr/lib/ -DCOMPILED_LIBSSL_PATH=/usr/lib/ \
-DDISABLE_TESTS=1 \
-DCMAKE_C_FLAGS="-DLIBSPDM_ENABLE_CAPABILITY_EVENT_CAP=0 \
-DLIBSPDM_ENABLE_CAPABILITY_MEL_CAP=0 \
-DLIBSPDM_HAL_PASS_SPDM_CONTEXT=1 \
-DLIBSPDM_ENABLE_CAPABILITY_GET_KEY_PAIR_INFO_CAP=0 \
-DLIBSPDM_ENABLE_CAPABILITY_SET_KEY_PAIR_INFO_CAP=0 \
-DLIBSPDM_ENABLE_CAPABILITY_ENDPOINT_INFO_CAP=0 \
-DLIBSPDM_RECORD_TRANSCRIPT_DATA_SUPPORT=1 \
-DLIBSPDM_ENABLE_CAPABILITY_SET_CERT_CAP=1 \
-DLIBSPDM_ENABLE_CAPABILITY_CERT_CAP=1 \
-DLIBSPDM_ENABLE_CAPABILITY_KEY_EX_CAP=1" \
..
make -j8Note that we build libspdm with chunking enabled. Chunking allows us to keep the maximum data transferred
in a single burst down by chunking the SPDM message data into frames of digestible size(s).
Then you can build SPDM-Utils with
cargo build --bin spdm_utilsThis is currently a work in progress
cargo build --lib --features=no_stdspdm-utils suppports building the binary with three transport options, doe,
scsi and nvme. Which means you can install only the dependencies needed by
those transports and build the binary as below. Note, we enable other transports
that do not have external dependecies.
# To build with PCI DoE transport support
$ cargo build --features pci
or
# To build with Storage NVMe transport support
$ cargo build --features nvme
# To build with SCSI and NVMe support
$ cargo build --features nvme,scsiSPDM-Utils supports logging. The following log levels are supported:
- trace
- debug
- info
- warn
- error
By default SPDM-Utils will build with trace log level, meaning that the log
outputs are very verbose containing all logs. To change this, set the LOG_LEVEL
environment variable to the desired level when building. The logger also takes a
LOG_STYLE parameter which may be used to set the character style. This
defaults to always but can be changed to one of (see
here for more):
- always
- never
- auto
LOG_LEVEL=info LOG_STYLE=never cargo buildAll changes should go through the Cargo formatter and tests, which can be run with
cargo fmt; cargo clippy; cargo testSetup and build SPDM-Responder-Validator in the third-party directory
cd third-party/
git submodule init; git submodule update --recursive
cd SPDM-Responder-Validator/
rm -rf libspdm/
# This assumes that `third-party/libspdm` is configured correctly as above
# The symlink here ensures that the tests are build against the same version of libspdm
ln -s ../libspdm/ libspdm
mkdir build; cd build
cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug -DCRYPTO=openssl ..
make -j8We can now build SPDM-Utils with
cargo build --features libspdm_testsYou can run SPDM-Utils completely on the host using TCP/IP and localhost. In this case you can run the server side with
$ cargo run -- --tcp-server request get-digestsand the client side with
$ ./target/debug/spdm_utils --tcp-client responseNote that the server must be run first. You can also swap the server/client specification between the request or response side as well.
You can also run the libspdm tests by running tests on the tcp server with:
$ cargo run -- --tcp-server testsAs mentioned before, spdm-utils supports communicating SPDM messages over
TCP/IP. The TCP/IP transport by default conforms to the SPDM TCP Transport
binding. However, a different transport protocol maybe be used with the
--spdm-transport-protocol argument. Note that both the client and the
server will need to be started with this argument specified.
To use SPDM with TCP/IP, first setup a server:
$ ./target/debug/spdm_utils --tcp-server --server-persist responseOn another machine with network access to the server, run:
$ ./target/debug/spdm_utils --tcp-client --ip <ip_addr> request get-versionIf desired, the port option can be used to specify a port on both ends.
spdm-utils can issue a user defined list of SPDM request(s).
It is the responsibility of the user to ensure, the SPDM requests are ordered
in a specification compliant way. It is not checked
by spdm-utils or libspdm.
Usage is as follows, as demonstrated over the network model. Ensure you have an SPDM responder server running in responder mode prior to issuing this command.
$ ./target/debug/spdm_utils --tcp-client --no-session request get-version,get-capabilities,negotiate-algorithmsRequest sub-commands can be specified as follows, refer to usage request --help
for available options.
$ ./target/debug/spdm_utils --tcp-client request --no-session get-version,get-measurement[index=1]spdm-utils can send SPDM request(s) directly, without establishing a session.
This may be useful for development, testing and CI. The --no-session argument
shall be specified to indicate this. In this mode, it is the responsibility of
the user to ensure that the requests are ordered in an SPDM specification
compliant way. spdm-utils or libspdm do not check the request order, instead
directly issues them to the responder.
$ ./target/debug/spdm_utils --tcp-client --no-session request get-version,get-capabilities,negotiate-algorithms,get-digests,get-certificate,challengeThis command will issue the requests listed in the order in which they are listed to the responder.
You can run SPDM-Utils on the host to interact with a real DOE device. To do that you can run the following example to get digest information
The pcie-vid and pcie-devid values can be found by using lspci for the respective device. For example:
$ lspci -nnv
...
0c:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 21/23 HDMI/DP Audio Controller [1002:ab28]
Subsystem: Advanced Micro Devices, Inc. [AMD/ATI] Navi 21/23 HDMI/DP Audio Controller [1002:ab28]
Flags: bus master, fast devsel, latency 0, IRQ 128, IOMMU group 28
Memory at fcd20000 (32-bit, non-prefetchable) [size=16K]
Capabilities: <access denied>
Kernel driver in use: snd_hda_intel
Kernel modules: snd_hda_intel
...Where Vendor ID = 0x1002 and Device ID = 0xab28. spdm-utils can then be
invoked as below:
$ ./target/debug/spdm_utils --pcie-vid <VendorID> --pcie-devid <DeviceID> --doe-pci-cfg request get-digestsSPDM-utils supports the SPDM over storage transport as defined by the DMTF DSP0286. For example, the following command can be used to interact with an NVMe device.
$ ./target/debug/spdm_utils --blk-dev-path /dev/nvme0 --nvme --no-session request get-version,get-capabilitiesYou can retrieve the list of certificates from the device by running
$ ./target/debug/spdm_utils <DeviceOptions> request get-certificateWhich will create a file retrieved_slot_id0 with the full certificate chain.
If the device is 'TCG DICE Concise Evidence Binding for SPDM ' compliant, the chain
can be verified by also including the --tcg-dice-evidence-binding-checks argument.
The certificate chain can be decoded with openssl by running the following:
$ while openssl x509 -noout -text; do :; done < ~/retrieved_slot_id0From a host you can set the certificate of the device. As SPDM-Utils uses
the Alias cert model you can only set the root certificate to the device
certificate with the SET_CERTIFICATE command (see section 117 of the
SPDM spec).
For example to set the certificate run:
$ spdm_utils --pcie-vid <VendorID> --pcie-devid <DeviceID> --doe-pci-cfg request --cert-path ./certs/alias/slot0/immutable.der set-certificateYou can additionally specify --cert-slot-id to specify the target slot number, valid slot numbers range from
0-7.
A requester can get the Certificate Signing Request (CSR) from the device with a command similar to this:
$ spdm_utils --pcie-vid <VendorID> --pcie-devid <DeviceID> --doe-pci-cfg request get-csrWhich will save the file to csr_response.der. You can then verify the CSR
with openssl
$ openssl req -text -noout -inform der -verify -in ./csr_response.derOnce you have a csr_response.der from the responder, you first want to
convert it to a PEM format with
$ openssl req -inform der -in ./csr_response.der -out csr_response.reqYou can now sign the CSR
$ openssl x509 -req -in csr_response.req -out csr_response.cert -CA ./certs/slot0/inter.der -sha384 -days 3650 -set_serial 2 -extensions device_ca -extfile ./certs/alias/openssl.cnfThen convert the certificate back to DER
$ openssl asn1parse -in csr_response.cert -out csr_response.cert.derCombine all of the immutable certs
$ cat ./certs/slot0/ca.cert.der ./certs/slot0/inter.cert.der ./csr_response.cert.der > set-cert.derNow you can set the certificate of a slot
$ spdm_utils --pcie-vid <VendorID> --pcie-devid <DeviceID> --doe-pci-cfg request --cert-slot-id 1 --cert-path ./set-cert.der set-certificateThen you request the certificate back
$ spdm_utils --pcie-vid <VendorID> --pcie-devid <DeviceID> --doe-pci-cfg request --cert-slot-id 1 get-certificateIf you are running the tcp server/client mode you will have to simulate a device reset and certificate re-gen. That can be done by running this
$ cd certs
$ ./setup_certs.sh ../target/debug/spdm_utils
$ cd ../SPDM-Utils supports binding to QEMU to implement an SPDM responder side to an emulated device in QEMU. SPDM support for QEMU is not upstream yet, however, this fork has the necessary changes required to emulate an NVMe device with SPDM support over DOE.
For example, this may be an emulated NVMe device in QEMU that binds to SPDM-Utils for the SPDM responder implementation.
With the current SPDM implementation in QEMU, the only transport layer supported is DOE. SPDM-Utils must be started before QEMU for this to work.
$ ./target/debug/spdm_utils --qemu-server response
[2023-08-29T06:21:47Z DEBUG SPDM-Utils] Logger initialisation [OK]
[2023-08-29T06:21:47Z DEBUG SPDM-Utils::qemu_server] Setting up a server on [port: 2323, ip: 127.0.0.1]
[2023-08-29T06:21:47Z INFO SPDM-Utils::qemu_server] Server started, waiting for qemu on port: 2323Note: You can provide --qemu-port <QEMU_PORT> to specify a port for the server
and also --spdm-transport-protocol <TRANSPORT> to specify the transport layer.
The qemu response server supports doe and storage transport protocols.
This will start SPDM-Utils responder server on port 2323 (default). QEMU can now be started. Once QEMU starts, if the connection is successful, the following logs should show (ensure that INFO log level is enabled in SPDM-Utils).
[2023-08-29T06:22:01Z INFO SPDM-Utils::qemu_server] New connection: 127.0.0.1:40528
[2023-08-29T06:22:01Z INFO SPDM-Utils::responder] Running in a response loopNow QEMU is ready to use SPDM-Utils as an SPDM responder for an emulated device.