From d56bdf8a361626bf478f9d15ac3e104f0001ad34 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 9 Dec 2025 13:00:51 +0100 Subject: [PATCH 01/86] Add .gitignore, remove lockfile --- .gitignore | 2 + Cargo.lock | 504 ----------------------------------------------------- 2 files changed, 2 insertions(+), 504 deletions(-) create mode 100644 .gitignore delete mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 6cd218d..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,504 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bitfield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "der_derive", - "flagset", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "flagset" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "libc" -version = "0.2.176" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] -name = "spdm-lib" -version = "0.1.0" -dependencies = [ - "bitfield", - "der", - "digest", - "hex", - "p384", - "rand", - "sha2", - "x509-cert", - "zerocopy", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tls_codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" -dependencies = [ - "tls_codec_derive", - "zeroize", -] - -[[package]] -name = "tls_codec_derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "x509-cert" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" -dependencies = [ - "const-oid", - "der", - "spki", - "tls_codec", -] - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] From 6a23daff79e59959eeedf6f6936bf552ae8ae4f9 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 9 Dec 2025 14:04:19 +0100 Subject: [PATCH 02/86] Remove unused feature flags, move test/example deps to dev-dependencies --- COMPILATION_README.md | 28 ++++++++++++++-------------- Cargo.toml | 29 ++++++++--------------------- src/lib.rs | 2 +- 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/COMPILATION_README.md b/COMPILATION_README.md index 2f0999f..f041213 100644 --- a/COMPILATION_README.md +++ b/COMPILATION_README.md @@ -84,14 +84,14 @@ cargo build --release --example spdm_responder --features std,crypto Run all library unit tests: ```bash -cargo test --features std,crypto +cargo test --features crypto ``` ### Static Certificate Verification Test that the static certificates are properly formatted: ```bash -cargo run --example test_static_certs --features std +cargo run --example test_static_certs ``` Expected output: @@ -113,7 +113,7 @@ Static certificates are ready for use! Run integration tests: ```bash -cargo test --test integration --features std,crypto +cargo test --test integration --features crypto ``` ## Running the SPDM Responder @@ -122,25 +122,25 @@ cargo test --test integration --features std,crypto Start the SPDM responder on default port 2323: ```bash -cargo run --example spdm_responder --features std,crypto +cargo run --example spdm_responder --features crypto ``` ### With Custom Port ```bash -cargo run --example spdm_responder --features std,crypto -- --port 8080 +cargo run --example spdm_responder --features crypto -- --port 8080 ``` ### With Verbose Logging ```bash -cargo run --example spdm_responder --features std,crypto -- --verbose +cargo run --example spdm_responder --features crypto -- --verbose ``` ### All Options ```bash -cargo run --example spdm_responder --features std,crypto -- \ +cargo run --example spdm_responder --features crypto -- \ --port 2323 \ --cert device_cert.pem \ --key device_key.pem \ @@ -165,7 +165,7 @@ The responder is compatible with the DMTF SPDM device validator: 1. **Start the responder:** ```bash - cargo run --example spdm_responder --features std,crypto -- --verbose + cargo run --example spdm_responder --features crypto -- --verbose ``` 2. **In another terminal, test with nc (netcat):** @@ -200,7 +200,7 @@ openssl verify -CAfile root_ca.pem attestation.pem 1. Create a new file in `examples/` 2. Add necessary dependencies to `Cargo.toml` if needed -3. Build with: `cargo build --example your_example --features std,crypto` +3. Build with: `cargo build --example your_example --features crypto` ### Modifying Certificates @@ -210,7 +210,7 @@ The static certificates are in `examples/platform/certs.rs`. They were generated Enable verbose logging to see detailed SPDM message processing: ```bash -RUST_LOG=debug cargo run --example spdm_responder --features std,crypto -- --verbose +RUST_LOG=debug cargo run --example spdm_responder --features crypto -- --verbose ``` ## Troubleshooting @@ -221,7 +221,7 @@ If you encounter build errors: 1. **Update Rust**: `rustup update` 2. **Clean build**: `cargo clean && cargo build` -3. **Check features**: Ensure you're using `--features std,crypto` +3. **Check features**: Ensure you're using `--features crypto` ### Connection Issues @@ -235,7 +235,7 @@ If the responder doesn't accept connections: If certificate-related errors occur: -1. **Run certificate test**: `cargo run --example test_static_certs --features std` +1. **Run certificate test**: `cargo run --example test_static_certs` 2. **Check certificate format**: Certificates are in DER format, not PEM 3. **Static certificates**: The responder uses hardcoded certificates, not files @@ -249,7 +249,7 @@ Licensed under the Apache-2.0 license. See LICENSE file for details. 2. Create a feature branch 3. Make your changes 4. Add tests if applicable -5. Run `cargo test --features std,crypto` +5. Run `cargo test --features crypto` 6. Submit a pull request ## Support @@ -259,4 +259,4 @@ For issues and questions: 1. Check the troubleshooting section above 2. Run tests to verify your setup 3. Enable verbose logging for debugging -4. Check that certificates pass verification tests \ No newline at end of file +4. Check that certificates pass verification tests diff --git a/Cargo.toml b/Cargo.toml index 7c92306..c10081b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,40 +8,27 @@ edition = "2021" [features] default = [] -# Enable to use std-dependent transports/utilities -std = [] -# TCP transport (requires std); opt-in so embedded/no_std users unaffected. -tcp-transport = ["std"] - # Rand-based RNG implementation (requires std for OS entropy via getrandom) - rand-rng = ["std", "rand"] - # Linux evidence mock implementation - linux-evidence = ["std"] - # Linux placeholder certificate store implementation - linux-certs = ["std"] - # Cryptographic implementations - crypto = ["std", "sha2", "p384", "digest", "rand"] +crypto = [] [dependencies] bitfield = "0.14.0" zerocopy = { version = "0.8.17", features = ["derive"] } -rand = { version = "0.8.5", optional = true } + +[dev-dependencies] +rand = "0.8.5" # Cryptographic dependencies -sha2 = { version = "0.10", optional = true } -p384 = { version = "0.13", features = ["ecdsa", "pem"], optional = true } -digest = { version = "0.10", optional = true } +sha2 = { version = "0.10"} +p384 = { version = "0.13", features = ["ecdsa", "pem"] } +digest = { version = "0.10"} hex = "0.4.3" x509-cert = "0.2.5" -der = "0.7.10" - -[dev-dependencies] # Examples [[example]] name = "spdm_responder" path = "examples/spdm_responder.rs" -required-features = ["std", "crypto"] +required-features = ["crypto"] [[example]] name = "test_static_certs" path = "examples/test_static_certs.rs" -required-features = ["std"] diff --git a/src/lib.rs b/src/lib.rs index 1d5942b..94ea7e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ // Licensed under the Apache-2.0 license -#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(test), no_std)] // Common errors pub mod error; From a88c785b9288cea94c727f6ea344dd0f561aaef8 Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 16 Dec 2025 18:09:55 +0100 Subject: [PATCH 03/86] Implement spdm-emu NONE and TCP transports Signed-off-by: leongross --- examples/README.md | 2 +- examples/platform/socket_transport.rs | 555 +++++++++++++++++++------- examples/spdm_requester.rs | 379 ++++++++++++++++++ examples/spdm_responder.rs | 142 ++++--- src/commands/mod.rs | 1 + src/commands/version_rq.rs | 173 ++++++++ src/context.rs | 119 +++++- src/error.rs | 7 +- src/protocol/common.rs | 2 +- src/protocol/mod.rs | 2 + src/requester.rs | 5 + 11 files changed, 1153 insertions(+), 234 deletions(-) create mode 100644 examples/spdm_requester.rs create mode 100644 src/commands/version_rq.rs create mode 100644 src/requester.rs diff --git a/examples/README.md b/examples/README.md index 5569f66..e4cb946 100644 --- a/examples/README.md +++ b/examples/README.md @@ -13,7 +13,7 @@ A demonstration SPDM responder application that shows how to: ### Usage ```bash -# Build the example +# Build the example responder cargo build --example spdm_responder --features std,crypto # Run with default settings diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index 1f2b682..84421a6 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -1,23 +1,40 @@ // Licensed under the Apache-2.0 license //! Socket Transport Platform Implementation -//! -//! Provides TCP socket transport compatible with DMTF SPDM validator/emulator +//! +//! Provides TCP socket transport compatible with DMTF SPDM validator/emulator of type SOCKET_TRANSPORT_TYPE_NONE. +// Defined in DMTF Spec [DSP0287](https://www.dmtf.org/sites/default/files/standards/documents/DSP0287_1.0.0.pdf) "SPDM over TCP Binding Specification". +use std::io::{Read, Result as IoResult, Write}; use std::net::TcpStream; -use std::io::{Read, Write, Result as IoResult}; -use spdm_lib::platform::transport::{SpdmTransport, TransportResult, TransportError}; -use spdm_lib::codec::MessageBuf; +use spdm_lib::codec::{Codec, CodecError, CommonCodec, MessageBuf}; +use spdm_lib::platform::transport::{SpdmTransport, TransportError, TransportResult}; +use zerocopy::byteorder::{BigEndian, U32}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; /// Socket platform command types (from DMTF emulator) -#[derive(Debug, Clone, Copy, PartialEq)] +/// This is **NOT** part of the official DMTF spec, but is necessary to implement +/// [SocketTransportType::None]. +/// +/// # Protocol Flow +/// 1. Requester: Send (SOCKET_SPDM_COMMAND_TEST, b'Client Hello') to Responder +/// 2. Responder: Send (SOCKET_SPDM_COMMAND_TEST, b'Server Hello') to Requester + #[repr(u32)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Socket Command definitions. +/// +/// See [spdm-emu/spdm_emu_common/command.h](https://github.com/DMTF/spdm-emu/blob/main/spdm_emu/spdm_emu_common/command.h). pub enum SocketSpdmCommand { Normal = 0x00000001, ClientHello = 0x00000003, - Shutdown = 0x00000004, - Unknown = 0x00000000, + // Shutdown = 0x00000004, + Shutdown = 0xFFFE, + // Unknown = 0x00000000, + Unknown = 0xFFFF, + Test = 0xDEAD, } impl From for SocketSpdmCommand { @@ -26,109 +43,376 @@ impl From for SocketSpdmCommand { 0x00000001 => SocketSpdmCommand::Normal, 0x00000003 => SocketSpdmCommand::ClientHello, 0x00000004 => SocketSpdmCommand::Shutdown, + 0xdead => SocketSpdmCommand::Test, _ => SocketSpdmCommand::Unknown, } } } +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u32)] +#[allow(non_camel_case_types, unused)] +pub enum SocketTransportType { + /// SOCKET_TRANSPORT_TYPE_NONE + None = 0x00, + MCTP = 0x01, + PCI_DOE = 0x02, + TCP = 0x03, +} + +impl From for SocketTransportType { + fn from(value: u32) -> Self { + match value { + 0x00 => SocketTransportType::None, + 0x01 => SocketTransportType::MCTP, + 0x02 => SocketTransportType::PCI_DOE, + 0x03 => SocketTransportType::TCP, + _ => SocketTransportType::None, + } + } +} + +pub enum SocketMessageHeaderError { + Reserved, +} + /// Socket transport implementation for SPDM over TCP pub struct SpdmSocketTransport { stream: TcpStream, - raw: bool, - verbose: bool, + transport_type: SocketTransportType, } -impl SpdmSocketTransport { - const TCP_BINDING_VERSION: u8 = 0x01; - const TCP_MESSAGE_TYPE_OUT_OF_SESSION: u8 = 0x05; - const TCP_MESSAGE_TYPE_IN_SESSION: u8 = 0x06; +type BeU32 = U32; - /// Create a new socket transport - pub fn new(stream: TcpStream, raw: bool, verbose: bool) -> Self { - Self { stream, raw, verbose } +/// Socket Command Header that is used when using transport `SOCKET_TRANSPORT_TYPE_NONE`. +/// The payload of the according SPM message is appended to this. +#[repr(C)] +pub struct SocketSpdmCommandHdr { + /// SPDM-EMU custom socket command. + command: SocketSpdmCommand, + transport_type: SocketTransportType, + + /// Size of the appended SPDM message payload + payload_size: BeU32, +} + +impl From<&[u8; 12]> for SocketSpdmCommandHdr { + fn from(value: &[u8; 12]) -> Self { + let command_bytes: [u8; 4] = [value[0], value[1], value[2], value[3]]; + let transport_bytes: [u8; 4] = [value[4], value[5], value[6], value[7]]; + let payload_bytes: [u8; 4] = [value[8], value[9], value[10], value[11]]; + + SocketSpdmCommandHdr { + command: SocketSpdmCommand::from(u32::from_be_bytes(command_bytes)), + transport_type: SocketTransportType::from(u32::from_be_bytes(transport_bytes)), + payload_size: BeU32::new(u32::from_be_bytes(payload_bytes)), + } } +} + +impl Into<[u8; 12]> for SocketSpdmCommandHdr { + fn into(self) -> [u8; 12] { + let mut result = [0u8; 12]; + result[0..4].copy_from_slice(&(self.command as u32).to_be_bytes()); + result[4..8].copy_from_slice(&(self.transport_type as u32).to_be_bytes()); + result[8..12].copy_from_slice(&self.payload_size.get().to_be_bytes()); + result + } +} + +#[repr(u8)] +pub enum SpdmSocketTransportError { + /// The PayloadLen in the last received message is too large to be processed by the endpoint. + PayloadLenTooLong = 0xC0, + + /// The BindingVer in the last received message is not supported by the endpoint. + /// The binding version supported by the endpoint is indicated in the BindingVer + /// field of this message with MessageType of 0xC1. + BindVerNotSupported = 0xC1, - fn log_bytes(&self, prefix: &str, data: &[u8]) { - if !self.verbose { - return; - } - print!("{prefix}"); - for b in data { - print!("{:02x} ", b); - } - println!(); - } + /// In the reach out model, the listener receives a Role-Inquiry Message from + /// the initiator. If the listener cannot operate as a Requester, then the listener + /// should send a message with MessageType of 0xC2 to the initiator. + CannotBeRequester = 0xC2, - fn log_frame(&self, prefix: &str, header: &[u8], data: &[u8]) { - if !self.verbose { - return; + /// In the reach down model, if the listener receives an SPDM request message + /// from the initiator but cannot operate as a Responder, then the listener should + /// send a message with MessageType of 0xC3 to the initiator. + CannotBeResponder = 0xC3, + //0xC4 - 0xFF: Reserved. +} + +impl TryFrom for SpdmSocketTransportError { + type Error = SocketMessageHeaderError; + + fn try_from(value: u8) -> Result { + match value { + 0xC0 => Ok(SpdmSocketTransportError::PayloadLenTooLong), + 0xC1 => Ok(SpdmSocketTransportError::BindVerNotSupported), + 0xC2 => Ok(SpdmSocketTransportError::CannotBeRequester), + 0xC3 => Ok(SpdmSocketTransportError::CannotBeResponder), + _ => Err(SocketMessageHeaderError::Reserved), + } + } +} + +impl Into for SpdmSocketTransportError { + fn into(self) -> u8 { + match self { + SpdmSocketTransportError::PayloadLenTooLong => 0xC0, + SpdmSocketTransportError::BindVerNotSupported => 0xC1, + SpdmSocketTransportError::CannotBeRequester => 0xC2, + SpdmSocketTransportError::CannotBeResponder => 0xC3, + } + } +} + +#[repr(u8)] +pub enum MessageType { + /// Out-of-Session Message. An SPDM message follows the header. + OutOfSession = 0x05, + + /// In-Session Message. An SPDM message follows the header. + InSession = 0x06, + + /// Role-Inquiry Message. **No** SPDM message follows the header. + RoleInquiry = 0xBF, + + /// Error messages. No SPDM message follows the header. + Error(SpdmSocketTransportError), + // Other values: reserved. +} + +impl TryFrom for MessageType { + type Error = SocketMessageHeaderError; + + fn try_from(value: u8) -> Result { + match value { + 0x05 => Ok(MessageType::OutOfSession), + 0x06 => Ok(MessageType::InSession), + 0xBF => Ok(MessageType::RoleInquiry), + 0xC0..=u8::MAX => Ok(MessageType::Error(SpdmSocketTransportError::try_from( + value, + )?)), + _ => Err(SocketMessageHeaderError::Reserved), + } + } +} + +impl Into for MessageType { + fn into(self) -> u8 { + match self { + Self::OutOfSession => 0x05, + Self::InSession => 0x06, + Self::RoleInquiry => 0xBf, + Self::Error(ste) => ste.into(), + } + } +} + +#[repr(C)] +#[derive(FromBytes, IntoBytes, Immutable)] +pub struct TcpSpdmBindingHeader { + /// Shall be the length of the SPDM message that follows the header. + payload_len: u16, + + /// Shall be 0x01 for this version of the binding specification. + binding_ver: u8, + + /// Shall indicate the message type. + message_type: u8, +} + +impl TcpSpdmBindingHeader { + pub fn new(payload_len: u16, message_type: MessageType) -> TcpSpdmBindingHeader { + Self { + payload_len, + binding_ver: 0x01, + message_type: message_type.into(), + } + } +} + +impl TryFrom<&[u8; 4]> for TcpSpdmBindingHeader { + type Error = SocketMessageHeaderError; + + fn try_from(value: &[u8; 4]) -> Result { + let payload_len = u16::from_le_bytes([value[0], value[1]]); + let binding_ver = value[2]; + let message_type = MessageType::try_from(value[3])?; + + Ok(TcpSpdmBindingHeader { + payload_len, + binding_ver, + message_type: match message_type { + MessageType::OutOfSession => 0x05, + MessageType::InSession => 0x06, + MessageType::RoleInquiry => 0xBF, + MessageType::Error(code) => code as u8, + }, + }) + } +} + +impl Codec for TcpSpdmBindingHeader { + fn encode(&self, buffer: &mut MessageBuf) -> spdm_lib::codec::CodecResult { + let len = core::mem::size_of::(); + buffer.push_data(len)?; + + let header = buffer.data_mut(len)?; + self.write_to(header).map_err(|_| CodecError::WriteError)?; + buffer.push_head(len)?; + + Ok(len) + } + + fn decode(buffer: &mut MessageBuf) -> spdm_lib::codec::CodecResult + where + Self: Sized, + { + let len = core::mem::size_of::(); + if buffer.data_len() < len { + Err(CodecError::BufferTooSmall)?; + } + let data = buffer.data(len)?; + let data = Self::read_from_bytes(data).map_err(|_| CodecError::ReadError)?; + buffer.pull_data(len)?; + + // if Self::DATA_KIND == DataKind::Header { + buffer.pull_head(len)?; + // } + Ok(data) + } +} + +/// For now, we ignore any TCP binding. This is a minimal example and the same +/// as the already implemented responder, we do use `SOCKET_TRANSPORT_TYPE_NONE`. +impl SpdmSocketTransport { + /// Create a new socket transport + pub fn new(stream: TcpStream, transport_type: SocketTransportType) -> Self { + Self { + stream, + transport_type, } - let mut buf = Vec::with_capacity(header.len() + data.len()); - buf.extend_from_slice(header); - buf.extend_from_slice(data); - self.log_bytes(prefix, &buf); } /// Receive platform data with socket message header - fn receive_platform_data(&mut self) -> IoResult<(SocketSpdmCommand, Vec)> { + pub(crate) fn receive_platform_data(&mut self) -> IoResult<(SocketSpdmCommand, Vec)> { // Read socket message header let mut header_bytes = [0u8; 12]; // sizeof(SocketMessageHeader) self.stream.read_exact(&mut header_bytes)?; - - let command = u32::from_be_bytes([header_bytes[0], header_bytes[1], header_bytes[2], header_bytes[3]]); - let _transport_type = u32::from_be_bytes([header_bytes[4], header_bytes[5], header_bytes[6], header_bytes[7]]); - let data_size = u32::from_be_bytes([header_bytes[8], header_bytes[9], header_bytes[10], header_bytes[11]]); - - let socket_command = SocketSpdmCommand::from(command); - - if data_size > 0 { - let mut data = vec![0u8; data_size as usize]; + + let header = SocketSpdmCommandHdr::from(&header_bytes); + let payload_size = header.payload_size.get(); + + if payload_size > 0 { + let mut data = vec![0u8; payload_size as usize]; self.stream.read_exact(&mut data)?; - self.log_frame("RX frame: ", &header_bytes, &data); - Ok((socket_command, data)) - } else { - self.log_frame("RX frame: ", &header_bytes, &[]); - Ok((socket_command, Vec::new())) - } - } + Ok((header.command, data)) + } else { + Ok((header.command, Vec::new())) + } + } /// Send platform data with socket message header fn send_platform_data(&mut self, command: SocketSpdmCommand, data: &[u8]) -> IoResult<()> { - // Send header in big-endian format to match validator expectations - let command_bytes = (command as u32).to_be_bytes(); - let transport_bytes = if self.raw { 0u32 } else { 3u32 }.to_be_bytes(); // NONE when raw, TCP=3 otherwise - let size_bytes = (data.len() as u32).to_be_bytes(); - - self.stream.write_all(&command_bytes)?; - self.stream.write_all(&transport_bytes)?; - self.stream.write_all(&size_bytes)?; - + let header_bytes: [u8; 12] = SocketSpdmCommandHdr { + command: SocketSpdmCommand::from(command as u32), + transport_type: self.transport_type, + payload_size: BeU32::new(data.len() as u32), + } + .into(); + + self.stream.write_all(&header_bytes)?; + // Send data if any if !data.is_empty() { self.stream.write_all(data)?; } - // Log full frame (header + payload) - let mut header = Vec::with_capacity(12); - header.extend_from_slice(&command_bytes); - header.extend_from_slice(&transport_bytes); - header.extend_from_slice(&size_bytes); - self.log_frame("TX frame: ", &header, data); + self.stream.flush()?; + Ok(()) + } + + pub fn send_client_hello<'a>(&mut self) -> TransportResult<()> { + let message_data = b"Client Hello!\x00".as_bytes(); - self.stream.flush()?; - Ok(()) - } + self.send_platform_data(SocketSpdmCommand::Test, message_data) + .map_err(|_| TransportError::SendError)?; + Ok(()) + } } impl SpdmTransport for SpdmSocketTransport { - fn send_request<'a>(&mut self, _dest_eid: u8, _req: &mut MessageBuf<'a>) -> TransportResult<()> { - // Not used in responder mode - Err(TransportError::DriverError) + /// This function is only relevant for the SPDM Requester. + /// Send the SPDM Request encoded into [req] (header|payload]) via the platform transport + /// to and SPDM endpoint. + /// Since this binding implements DSP0276, "Secured Messages using SPDM over MCTP Binding Specification", + /// there is no EID to send it to. + fn send_request<'a>(&mut self, _dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { + let message_data = req + .message_data() + .map_err(|_| TransportError::BufferTooSmall)?; + + self.send_platform_data(SocketSpdmCommand::Normal, message_data) + .map_err(|_| TransportError::SendError)?; + Ok(()) } - fn receive_response<'a>(&mut self, _rsp: &mut MessageBuf<'a>) -> TransportResult<()> { - // Not used in responder mode - Err(TransportError::DriverError) + /// Initialize any transport-specific sequence state. + /// For SOCKET_TRANSPORT_TYPE_NONE, this means performing the handshake. + /// This function is only valid for the SPDM Requester. + fn init_sequence(&mut self) -> TransportResult<()> { + self.send_client_hello()?; + + match self.receive_platform_data() { + Ok((command, data)) => { + if command != SocketSpdmCommand::Test || data != b"Server Hello!\x00" { + return Err(TransportError::HandshakeNoneError); + } + Ok(()) + } + Err(_) => Err(TransportError::HandshakeNoneError), + } + } + + fn receive_response<'a>(&mut self, rsp: &mut MessageBuf<'a>) -> TransportResult<()> { + loop { + match self.receive_platform_data() { + Ok((command, data)) => { + if !data.is_empty() { + match command { + SocketSpdmCommand::Normal => { + if !data.is_empty() { + rsp.reset(); + rsp.put_data(data.len()) + .map_err(|_| TransportError::BufferTooSmall)?; + + rsp.data_mut(data.len()) + .map_err(|_| TransportError::BufferTooSmall)? + .copy_from_slice(&data); + + return Ok(()); + } + } + + SocketSpdmCommand::ClientHello => {} + SocketSpdmCommand::Shutdown => {} + SocketSpdmCommand::Unknown => {} + SocketSpdmCommand::Test => { + if data != b"Server Hello!" { + return Err(TransportError::HandshakeNoneError); + } + } + } + } + } + + Err(_) => { + return Err(TransportError::ReceiveError); + } + } + } } fn receive_request<'a>(&mut self, req: &mut MessageBuf<'a>) -> TransportResult<()> { @@ -138,72 +422,56 @@ impl SpdmTransport for SpdmSocketTransport { Ok((command, data)) => { match command { SocketSpdmCommand::Normal => { - if self.raw { - if data.is_empty() { - self.send_platform_data(SocketSpdmCommand::Unknown, &[]).map_err(|_| TransportError::SendError)?; - continue; - } + if !data.is_empty() { + // This is an SPDM message req.reset(); - req.put_data(data.len()).map_err(|_| TransportError::BufferTooSmall)?; - let buf = req.data_mut(data.len()).map_err(|_| TransportError::BufferTooSmall)?; + let data_len = data.len(); + req.put_data(data_len) + .map_err(|_| TransportError::BufferTooSmall)?; + let buf = req + .data_mut(data_len) + .map_err(|_| TransportError::BufferTooSmall)?; buf.copy_from_slice(&data); return Ok(()); - } - // Expect SPDM-over-TCP binding header: payload_length (LE, includes version+type+payload), version, type - if data.len() < 4 { - self.send_platform_data(SocketSpdmCommand::Unknown, &[]).map_err(|_| TransportError::SendError)?; + } else { + // Empty data - send empty response + self.send_platform_data(SocketSpdmCommand::Unknown, &[]) + .map_err(|_| TransportError::SendError)?; continue; } - let payload_len = u16::from_le_bytes([data[0], data[1]]) as usize; - let binding_version = data[2]; - let message_type = data[3]; - - // payload_length = 2 (version+type) + SPDM_payload - let expected_total = payload_len + 2; - if binding_version != Self::TCP_BINDING_VERSION - || payload_len < 2 - || data.len() != expected_total - { - self.send_platform_data(SocketSpdmCommand::Unknown, &[]).map_err(|_| TransportError::SendError)?; - continue; - } - - // Allow both in-session and out-of-session; in-session must carry at least session_id (4 bytes) - let spdm_payload = &data[4..]; - let spdm_payload_len = spdm_payload.len(); - let valid_type = match message_type { - Self::TCP_MESSAGE_TYPE_OUT_OF_SESSION => true, - Self::TCP_MESSAGE_TYPE_IN_SESSION => spdm_payload_len >= 4, - _ => false, - }; - if !valid_type || spdm_payload_len + 2 != payload_len { - self.send_platform_data(SocketSpdmCommand::Unknown, &[]).map_err(|_| TransportError::SendError)?; - continue; - } - - req.reset(); - req.put_data(spdm_payload_len).map_err(|_| TransportError::BufferTooSmall)?; - let buf = req.data_mut(spdm_payload_len).map_err(|_| TransportError::BufferTooSmall)?; - buf.copy_from_slice(spdm_payload); - return Ok(()); - }, + } SocketSpdmCommand::ClientHello => { // Handle client hello let response = b"Server Hello!"; - self.send_platform_data(SocketSpdmCommand::ClientHello, response).map_err(|_| TransportError::SendError)?; + self.send_platform_data(SocketSpdmCommand::ClientHello, response) + .map_err(|_| TransportError::SendError)?; continue; - }, + } SocketSpdmCommand::Shutdown => { - // Send shutdown response first - let _ = self.send_platform_data(SocketSpdmCommand::Shutdown, &[]); - return Err(TransportError::ReceiveError); - }, + // Send shutdown response first + let _ = self.send_platform_data(SocketSpdmCommand::Shutdown, &[]); + return Err(TransportError::ReceiveError); + } SocketSpdmCommand::Unknown => { - self.send_platform_data(SocketSpdmCommand::Unknown, &[]).map_err(|_| TransportError::SendError)?; - continue; + self.send_platform_data(SocketSpdmCommand::Unknown, &[]) + .map_err(|_| TransportError::SendError)?; + continue; + } + + // In a correct flow, this can only happen for the responder + SocketSpdmCommand::Test => { + if data == b"Client Hello!\x00" { + self.send_platform_data( + SocketSpdmCommand::Test, + b"Server Hello!\x00", + ) + .map_err(|_| TransportError::SendError)?; + } else { + return Err(TransportError::HandshakeNoneError); + } } } - }, + } Err(_) => { return Err(TransportError::ReceiveError); } @@ -213,30 +481,11 @@ impl SpdmTransport for SpdmSocketTransport { fn send_response<'a>(&mut self, resp: &mut MessageBuf<'a>) -> TransportResult<()> { // Extract response data and send with socket protocol - let message_data = resp.message_data().map_err(|_| TransportError::BufferTooSmall)?; - - if self.raw { - self.send_platform_data(SocketSpdmCommand::Normal, message_data).map_err(|_| TransportError::SendError)?; - return Ok(()); - } - - // Heuristic: SPDM header has version in high nibble == 0x1; otherwise assume in-session (session_id-prefixed) - let msg_type = if message_data.first().map(|b| (b >> 4) == 0x1).unwrap_or(false) { - Self::TCP_MESSAGE_TYPE_OUT_OF_SESSION - } else if message_data.len() >= 4 { - Self::TCP_MESSAGE_TYPE_IN_SESSION - } else { - return Err(TransportError::SendError); - }; - - let payload_len = (message_data.len() + 2) as u16; // version + type + payload - let mut framed = Vec::with_capacity(4 + message_data.len()); - framed.extend_from_slice(&payload_len.to_le_bytes()); - framed.push(Self::TCP_BINDING_VERSION); - framed.push(msg_type); - framed.extend_from_slice(message_data); - - self.send_platform_data(SocketSpdmCommand::Normal, &framed).map_err(|_| TransportError::SendError)?; + let message_data = resp + .message_data() + .map_err(|_| TransportError::BufferTooSmall)?; + self.send_platform_data(SocketSpdmCommand::Normal, message_data) + .map_err(|_| TransportError::SendError)?; Ok(()) } @@ -247,4 +496,4 @@ impl SpdmTransport for SpdmSocketTransport { fn header_size(&self) -> usize { 0 // No additional header for SPDM messages } -} \ No newline at end of file +} diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs new file mode 100644 index 0000000..ecf0919 --- /dev/null +++ b/examples/spdm_requester.rs @@ -0,0 +1,379 @@ +// Licensed under the Apache-2.0 license + +//! SPDM Example Responder utilizing the requester library. + +use std::env; +use std::io::{Error, ErrorKind, Result as IoResult}; +use std::net::{TcpListener, TcpStream}; +use std::process; + +use spdm_lib::codec::MessageBuf; +use spdm_lib::context::SpdmContext; +use spdm_lib::protocol::algorithms::{ + AeadCipherSuite, AlgorithmPriorityTable, BaseAsymAlgo, BaseHashAlgo, DeviceAlgorithms, + DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, + MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, +}; +use spdm_lib::protocol::version::SpdmVersion; +use spdm_lib::protocol::ReqRespCode; +use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; +// Import platform implementations - no duplicates! +mod platform; +use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; + +/// Responder configuration +#[derive(Debug, Clone)] +struct ResponderConfig { + port: u16, + cert_path: String, + key_path: String, + measurements_path: Option, + verbose: bool, +} + +impl Default for ResponderConfig { + fn default() -> Self { + Self { + port: 2323, + cert_path: "device_cert.pem".to_string(), + key_path: "device_key.pem".to_string(), + measurements_path: Some("measurements.json".to_string()), + verbose: false, + } + } +} + +/// Create SPDM device capabilities +fn create_device_capabilities() -> DeviceCapabilities { + let mut flags_value = 0u32; + flags_value |= 1 << 1; // cert_cap + flags_value |= 1 << 2; // chal_cap + flags_value |= 2 << 3; // meas_cap (with signature) + flags_value |= 1 << 5; // meas_fresh_cap + flags_value |= 1 << 17; // chunk_cap + + let flags = CapabilityFlags::new(flags_value); + + DeviceCapabilities { + ct_exponent: 0, + flags, + data_transfer_size: 1024, + max_spdm_msg_size: 4096, + } +} + +/// Create local device algorithms +fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { + // Configure supported algorithms with proper bitfield construction + let mut measurement_spec = MeasurementSpecification::default(); + measurement_spec.set_dmtf_measurement_spec(1); + + let mut measurement_hash_algo = MeasurementHashAlgo::default(); + measurement_hash_algo.set_tpm_alg_sha_384(1); + + let mut base_asym_algo = BaseAsymAlgo::default(); + base_asym_algo.set_tpm_alg_ecdsa_ecc_nist_p384(1); + + let mut base_hash_algo = BaseHashAlgo::default(); + base_hash_algo.set_tpm_alg_sha_384(1); + + let device_algorithms = DeviceAlgorithms { + measurement_spec, + other_param_support: OtherParamSupport::default(), + measurement_hash_algo, + base_asym_algo, + base_hash_algo, + mel_specification: MelSpecification::default(), + dhe_group: DheNamedGroup::default(), + aead_cipher_suite: AeadCipherSuite::default(), + req_base_asym_algo: ReqBaseAsymAlg::default(), + key_schedule: KeySchedule::default(), + }; + + let algorithm_priority_table = AlgorithmPriorityTable { + measurement_specification: None, + opaque_data_format: None, + base_asym_algo: None, + base_hash_algo: None, + mel_specification: None, + dhe_group: None, + aead_cipher_suite: None, + req_base_asym_algo: None, + key_schedule: None, + }; + + LocalDeviceAlgorithms { + device_algorithms, + algorithm_priority_table, + } +} + +// Connect to SPDM Responder +fn handle_spdm_responder(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { + let mut transport = SpdmSocketTransport::new(stream); + + // Create platform implementations - all from platform module! + let mut hash = Sha384Hash::new(); + let mut m1_hash = Sha384Hash::new(); + let mut l1_hash = Sha384Hash::new(); + let mut rng = SystemRng::new(); + let mut cert_store = DemoCertStore::new(); + let evidence = DemoEvidence::new(); + + // Create SPDM context + let supported_versions = [SpdmVersion::V12, SpdmVersion::V11]; + let capabilities = create_device_capabilities(); + let algorithms = create_local_algorithms(); + + if config.verbose { + println!("Client connected - initializing SPDM context"); + } + + // TODO: The SpdmContext has to be adjusted (best in a generic way) to be requester compatible + // For now, keep the context the same and ignore the internal state tracking. + // Imho the the Responder is implemented wrong, since it tracks it's own state instead of the + // other parties state. + // So for now, we will re-use the state tracking and keep it in sync with the other parties state. + let mut spdm_context = match SpdmContext::new( + &supported_versions, + &mut transport, + capabilities, + algorithms, + &mut cert_store, + &mut hash, + &mut m1_hash, + &mut l1_hash, + &mut rng, + &evidence, + ) { + Ok(ctx) => ctx, + Err(e) => { + eprintln!("Failed to create SPDM context: {:?}", e); + return Err(Error::new(ErrorKind::Other, "SPDM context creation failed")); + } + }; + + if config.verbose { + println!("SPDM context created successfully"); + } + + // Process SPDM messages using the context + let mut buffer = [0u8; 4096]; + let mut message_buffer = MessageBuf::new(&mut buffer); + // For now, we just want to show, that the VCA (Version, Capability, Auth) flow works as expected + // For that, we need to do the following: + // 1.1 Send GET_VERSION + // 1.2 Receive and verify VERSION + // 1.3 Update tracking of remote party + // 2.1 Send GET_CAPABILITIES + // 2.2 Receive and verify CAPABILITIES + // 2.3 Update tracking of remote party + // 3.1 Send GET_AUTH + + // For now, the send_request function uses default values for the commands payloads. + let ret = spdm_context.send_request(ReqRespCode::GetVersion, &mut message_buffer); + // .map_err(|_| Error::new(ErrorKind::Other, "could not process error"))?; + dbg!(&ret); + + spdm_context.requester_process_message(&mut message_buffer); + dbg!(&ret); + + // let ret = spdm_context.send_request(req, &mut message_buffer); + + // spdm_context + // . + + // match spdm_context.process_message(&mut message_buffer) { + // Ok(()) => { + // if config.verbose { + // println!("Successfully processed SPDM message"); + // } + // } + // Err(e) => { + // if config.verbose { + // eprintln!("Error processing SPDM message: {:?}", e); + // } + // // Continue processing unless it's a fatal transport error + // match &e { + // spdm_lib::error::SpdmError::Transport(_) => { + // if config.verbose { + // println!("Connection closed gracefully"); + // } + // break; + // } + // _ => { + // // Log error but continue processing + // continue; + // } + // } + // } + // } + + Ok(()) +} + +/// Parse command line arguments +fn parse_args() -> ResponderConfig { + let mut config = ResponderConfig::default(); + let args: Vec = env::args().collect(); + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "-p" | "--port" => { + if i + 1 < args.len() { + config.port = args[i + 1].parse().unwrap_or_else(|_| { + eprintln!("Invalid port number: {}", args[i + 1]); + process::exit(1); + }); + i += 2; + } else { + eprintln!("Port number required after {}", args[i]); + process::exit(1); + } + } + "-c" | "--cert" => { + if i + 1 < args.len() { + config.cert_path = args[i + 1].clone(); + i += 2; + } else { + eprintln!("Certificate file path required after {}", args[i]); + process::exit(1); + } + } + "-k" | "--key" => { + if i + 1 < args.len() { + config.key_path = args[i + 1].clone(); + i += 2; + } else { + eprintln!("Private key file path required after {}", args[i]); + process::exit(1); + } + } + "-m" | "--measurements" => { + if i + 1 < args.len() { + config.measurements_path = Some(args[i + 1].clone()); + i += 2; + } else { + eprintln!("Measurements file path required after {}", args[i]); + process::exit(1); + } + } + "-v" | "--verbose" => { + config.verbose = true; + i += 1; + } + "-h" | "--help" => { + print_help(); + process::exit(0); + } + _ => { + eprintln!("Unknown argument: {}", args[i]); + print_help(); + process::exit(1); + } + } + } + + config +} + +fn print_help() { + println!("Real SPDM Library Integrated DMTF Compatible Responder\n"); + println!("USAGE:"); + println!(" spdm-responder-clean [OPTIONS]\n"); + println!("OPTIONS:"); + println!(" -p, --port TCP port to connect to [default: None]"); + println!( + " -c, --cert Path to certificate file [default: device_cert.pem]" + ); + println!( + " -k, --key Path to private key file [default: device_key.pem]" + ); + println!( + " -m, --measurements Path to measurements file [default: measurements.json]" + ); + println!(" -v, --verbose Enable verbose logging"); + println!(" -h, --help Print this help message\n"); + println!("EXAMPLES:"); + println!(" spdm-responder-clean --port 8080 --verbose"); + println!(" spdm-responder-clean --cert my_cert.pem --key my_key.pem"); + println!( + "\nIntegrates real SPDM library with clean platform implementations - no code duplication!" + ); +} + +/// Display configuration information +fn display_info(config: &ResponderConfig) { + println!("Real SPDM Library Integrated DMTF Compatible Responder"); + println!("====================================================="); + println!("Configuration:"); + println!(" Port: {}", config.port); + println!(" Certificate: {}", config.cert_path); + println!(" Private Key: {}", config.key_path); + if let Some(ref measurements) = config.measurements_path { + println!(" Measurements: {}", measurements); + } + println!(" Verbose: {}", config.verbose); + println!(); + + let capabilities = create_device_capabilities(); + println!("SPDM Device Capabilities:"); + println!( + " Certificate capability: {}", + capabilities.flags.cert_cap() + ); + println!(" Challenge capability: {}", capabilities.flags.chal_cap()); + println!( + " Measurements capability: {}", + capabilities.flags.meas_cap() + ); + println!( + " Fresh measurements: {}", + capabilities.flags.meas_fresh_cap() + ); + println!(" Chunk capability: {}", capabilities.flags.chunk_cap()); + println!( + " Data transfer size: {} bytes", + capabilities.data_transfer_size + ); + println!( + " Max SPDM message size: {} bytes", + capabilities.max_spdm_msg_size + ); + println!(); + + println!("Clean Platform Implementation Features:"); + println!(" SPDM Versions: 1.2, 1.1"); + println!(" Protocol Processing: Real SPDM library integration"); + println!(" Hash Algorithm: SHA-384 (platform module)"); + println!(" Signature Algorithm: ECDSA P-384 (platform module)"); + println!(" Measurements: Demo device measurements (platform module)"); + println!(" Certificates: Static OpenSSL-generated certificate chain (platform module)"); + println!(" Transport: TCP socket with DMTF protocol (platform module)"); + println!(" ✅ NO CODE DUPLICATION - All implementations from unified platform module"); + println!(); +} + +/// Main function +fn main() -> Result<(), Box> { + let config = parse_args(); + display_info(&config); + + let remote_addr = format!("0.0.0.0:{}", config.port); + let stream = TcpStream::connect(&remote_addr)?; + + println!( + "Clean SPDM library requester connecting to {}", + &remote_addr + ); + + if let Ok(peer_addr) = stream.peer_addr() { + println!("Connection from: {}", peer_addr); + } + + // Handle client with real SPDM processing using platform implementations + handle_spdm_responder(stream, &config)?; + + Ok(()) +} diff --git a/examples/spdm_responder.rs b/examples/spdm_responder.rs index ceeb658..8ab4f96 100644 --- a/examples/spdm_responder.rs +++ b/examples/spdm_responder.rs @@ -1,31 +1,30 @@ // Licensed under the Apache-2.0 license //! Real SPDM Library Integrated DMTF Compatible Responder -//! +//! //! This responder integrates the actual SPDM library for real protocol processing //! while maintaining compatibility with the DMTF SPDM emulator protocol. //! //! This version uses platform implementations with no duplicated code. use std::env; -use std::process; +use std::io::{Error, ErrorKind, Result as IoResult}; use std::net::{TcpListener, TcpStream}; -use std::io::{Result as IoResult, Error, ErrorKind}; +use std::process; -use spdm_lib::context::SpdmContext; use spdm_lib::codec::MessageBuf; -use spdm_lib::protocol::{DeviceCapabilities, CapabilityFlags}; -use spdm_lib::protocol::version::SpdmVersion; +use spdm_lib::context::SpdmContext; use spdm_lib::protocol::algorithms::{ - LocalDeviceAlgorithms, AlgorithmPriorityTable, DeviceAlgorithms, - MeasurementSpecification, MeasurementHashAlgo, BaseAsymAlgo, BaseHashAlgo, - DheNamedGroup, AeadCipherSuite, KeySchedule, OtherParamSupport, MelSpecification, - ReqBaseAsymAlg + AeadCipherSuite, AlgorithmPriorityTable, BaseAsymAlgo, BaseHashAlgo, DeviceAlgorithms, + DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, + MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, }; +use spdm_lib::protocol::version::SpdmVersion; +use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; // Import platform implementations - no duplicates! mod platform; -use platform::{SpdmSocketTransport, Sha384Hash, SystemRng, DemoCertStore, DemoEvidence}; +use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; /// Responder configuration #[derive(Debug, Clone)] @@ -54,14 +53,14 @@ impl Default for ResponderConfig { /// Create SPDM device capabilities fn create_device_capabilities() -> DeviceCapabilities { let mut flags_value = 0u32; - flags_value |= 1 << 1; // cert_cap - flags_value |= 1 << 2; // chal_cap - flags_value |= 2 << 3; // meas_cap (with signature) - flags_value |= 1 << 5; // meas_fresh_cap + flags_value |= 1 << 1; // cert_cap + flags_value |= 1 << 2; // chal_cap + flags_value |= 2 << 3; // meas_cap (with signature) + flags_value |= 1 << 5; // meas_fresh_cap flags_value |= 1 << 17; // chunk_cap - + let flags = CapabilityFlags::new(flags_value); - + DeviceCapabilities { ct_exponent: 0, flags, @@ -75,16 +74,16 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { // Configure supported algorithms with proper bitfield construction let mut measurement_spec = MeasurementSpecification::default(); measurement_spec.set_dmtf_measurement_spec(1); - + let mut measurement_hash_algo = MeasurementHashAlgo::default(); measurement_hash_algo.set_tpm_alg_sha_384(1); - + let mut base_asym_algo = BaseAsymAlgo::default(); base_asym_algo.set_tpm_alg_ecdsa_ecc_nist_p384(1); - + let mut base_hash_algo = BaseHashAlgo::default(); base_hash_algo.set_tpm_alg_sha_384(1); - + let device_algorithms = DeviceAlgorithms { measurement_spec, other_param_support: OtherParamSupport::default(), @@ -118,8 +117,8 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { /// Handle client connection with real SPDM processing fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { - let mut transport = SpdmSocketTransport::new(stream, config.raw, config.verbose); - + let mut transport = SpdmSocketTransport::new(stream); + // Create platform implementations - all from platform module! let mut hash = Sha384Hash::new(); let mut m1_hash = Sha384Hash::new(); @@ -127,16 +126,16 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( let mut rng = SystemRng::new(); let mut cert_store = DemoCertStore::new(); let evidence = DemoEvidence::new(); - + // Create SPDM context let supported_versions = [SpdmVersion::V12, SpdmVersion::V11]; let capabilities = create_device_capabilities(); let algorithms = create_local_algorithms(); - + if config.verbose { println!("Client connected - initializing SPDM context"); } - + let mut spdm_context = match SpdmContext::new( &supported_versions, &mut transport, @@ -145,7 +144,7 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( &mut cert_store, &mut hash, &mut m1_hash, - &mut l1_hash, + &mut l1_hash, &mut rng, &evidence, ) { @@ -155,16 +154,16 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( return Err(Error::new(ErrorKind::Other, "SPDM context creation failed")); } }; - + if config.verbose { println!("SPDM context created successfully"); } - + // Process SPDM messages using the context let mut buffer = [0u8; 4096]; let mut message_buffer = MessageBuf::new(&mut buffer); loop { - match spdm_context.process_message(&mut message_buffer) { + match spdm_context.responder_process_message(&mut message_buffer) { Ok(()) => { if config.verbose { println!("Successfully processed SPDM message"); @@ -190,11 +189,11 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( } } } - + if config.verbose { println!("Client connection closed"); } - + Ok(()) } @@ -202,7 +201,7 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( fn parse_args() -> ResponderConfig { let mut config = ResponderConfig::default(); let args: Vec = env::args().collect(); - + let mut i = 1; while i < args.len() { match args[i].as_str() { @@ -217,7 +216,7 @@ fn parse_args() -> ResponderConfig { eprintln!("Port number required after {}", args[i]); process::exit(1); } - }, + } "-c" | "--cert" => { if i + 1 < args.len() { config.cert_path = args[i + 1].clone(); @@ -226,7 +225,7 @@ fn parse_args() -> ResponderConfig { eprintln!("Certificate file path required after {}", args[i]); process::exit(1); } - }, + } "-k" | "--key" => { if i + 1 < args.len() { config.key_path = args[i + 1].clone(); @@ -235,7 +234,7 @@ fn parse_args() -> ResponderConfig { eprintln!("Private key file path required after {}", args[i]); process::exit(1); } - }, + } "-m" | "--measurements" => { if i + 1 < args.len() { config.measurements_path = Some(args[i + 1].clone()); @@ -244,19 +243,15 @@ fn parse_args() -> ResponderConfig { eprintln!("Measurements file path required after {}", args[i]); process::exit(1); } - }, + } "-v" | "--verbose" => { config.verbose = true; i += 1; - }, - "--raw" => { - config.raw = true; - i += 1; - }, + } "-h" | "--help" => { print_help(); process::exit(0); - }, + } _ => { eprintln!("Unknown argument: {}", args[i]); print_help(); @@ -264,7 +259,7 @@ fn parse_args() -> ResponderConfig { } } } - + config } @@ -274,15 +269,23 @@ fn print_help() { println!(" spdm-responder-clean [OPTIONS]\n"); println!("OPTIONS:"); println!(" -p, --port TCP port to listen on [default: 2323]"); - println!(" -c, --cert Path to certificate file [default: device_cert.pem]"); - println!(" -k, --key Path to private key file [default: device_key.pem]"); - println!(" -m, --measurements Path to measurements file [default: measurements.json]"); + println!( + " -c, --cert Path to certificate file [default: device_cert.pem]" + ); + println!( + " -k, --key Path to private key file [default: device_key.pem]" + ); + println!( + " -m, --measurements Path to measurements file [default: measurements.json]" + ); println!(" -v, --verbose Enable verbose logging"); println!(" -h, --help Print this help message\n"); println!("EXAMPLES:"); println!(" spdm-responder-clean --port 8080 --verbose"); println!(" spdm-responder-clean --cert my_cert.pem --key my_key.pem"); - println!("\nIntegrates real SPDM library with clean platform implementations - no code duplication!"); + println!( + "\nIntegrates real SPDM library with clean platform implementations - no code duplication!" + ); } /// Display configuration information @@ -299,18 +302,33 @@ fn display_info(config: &ResponderConfig) { println!(" Verbose: {}", config.verbose); println!(" Raw (no TCP binding): {}", config.raw); println!(); - + let capabilities = create_device_capabilities(); println!("SPDM Device Capabilities:"); - println!(" Certificate capability: {}", capabilities.flags.cert_cap()); + println!( + " Certificate capability: {}", + capabilities.flags.cert_cap() + ); println!(" Challenge capability: {}", capabilities.flags.chal_cap()); - println!(" Measurements capability: {}", capabilities.flags.meas_cap()); - println!(" Fresh measurements: {}", capabilities.flags.meas_fresh_cap()); + println!( + " Measurements capability: {}", + capabilities.flags.meas_cap() + ); + println!( + " Fresh measurements: {}", + capabilities.flags.meas_fresh_cap() + ); println!(" Chunk capability: {}", capabilities.flags.chunk_cap()); - println!(" Data transfer size: {} bytes", capabilities.data_transfer_size); - println!(" Max SPDM message size: {} bytes", capabilities.max_spdm_msg_size); + println!( + " Data transfer size: {} bytes", + capabilities.data_transfer_size + ); + println!( + " Max SPDM message size: {} bytes", + capabilities.max_spdm_msg_size + ); println!(); - + println!("Clean Platform Implementation Features:"); println!(" SPDM Versions: 1.2, 1.1"); println!(" Protocol Processing: Real SPDM library integration"); @@ -326,19 +344,19 @@ fn display_info(config: &ResponderConfig) { /// Main function fn main() -> Result<(), Box> { let config = parse_args(); - + display_info(&config); - + // Create TCP listener let bind_addr = format!("0.0.0.0:{}", config.port); let listener = TcpListener::bind(&bind_addr)?; - + println!("Clean SPDM library responder listening on {}", bind_addr); println!("Compatible with DMTF SPDM device validator and emulator clients"); println!("Uses unified platform implementations with no code duplication"); println!("Waiting for connections... (Press Ctrl+C to exit)"); println!(); - + // Accept connections for stream in listener.incoming() { match stream { @@ -346,7 +364,7 @@ fn main() -> Result<(), Box> { if let Ok(peer_addr) = stream.peer_addr() { println!("Connection from: {}", peer_addr); } - + // Handle client with real SPDM processing using platform implementations if let Err(e) = handle_spdm_client(stream, &config) { eprintln!("Client handling error: {}", e); @@ -357,6 +375,6 @@ fn main() -> Result<(), Box> { } } } - + Ok(()) -} \ No newline at end of file +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8d9da12..6bfde0b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,4 +8,5 @@ pub mod chunk_get_rsp; pub mod digests_rsp; pub mod error_rsp; pub mod measurements_rsp; +pub mod version_rq; pub mod version_rsp; diff --git a/src/commands/version_rq.rs b/src/commands/version_rq.rs new file mode 100644 index 0000000..1aa9f74 --- /dev/null +++ b/src/commands/version_rq.rs @@ -0,0 +1,173 @@ +// Licensed under the Apache-2.0 license + +use crate::{ + codec::{Codec, CommonCodec, MessageBuf}, + context::SpdmContext, + error::{CommandError, CommandResult, SpdmResult}, + protocol::{version, SpdmMsgHdr}, + transcript::TranscriptContext, +}; + +use crate::commands::error_rsp::ErrorCode; + +use crate::protocol::{ReqRespCode, SpdmVersion}; +use bitfield::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +const VERSION_ENTRY_SIZE: usize = 2; + +// 4.9.1.1 GET_VERSION request message and VERSION response message +#[derive(FromBytes, IntoBytes, Immutable)] +pub struct GetVersionPayload { + Param1: u8, + Param2: u8, +} + +impl GetVersionPayload { + pub fn new(param1: u8, param2: u8) -> Self { + GetVersionPayload { + Param1: param1, + Param2: param2, + } + } +} + +impl CommonCodec for GetVersionPayload {} + +// copy until refactored +#[allow(dead_code)] +#[derive(FromBytes, IntoBytes, Immutable)] +struct VersionRespCommon { + param1: u8, + param2: u8, + reserved: u8, + version_num_entry_count: u8, +} + +impl CommonCodec for VersionReqPayload {} + +impl Default for VersionRespCommon { + fn default() -> Self { + VersionRespCommon::new(0) + } +} + +impl VersionRespCommon { + pub fn new(entry_count: u8) -> Self { + VersionRespCommon { + param1: 0, + param2: 0, + reserved: 0, + version_num_entry_count: entry_count, + } + } +} + +impl CommonCodec for VersionRespCommon {} + +bitfield! { +#[repr(C)] +#[derive(FromBytes, IntoBytes, Immutable)] +pub struct VersionNumberEntry(MSB0 [u8]); +impl Debug; +u8; + pub update_ver, set_update_ver: 3, 0; + pub alpha, set_alpha: 7, 4; + pub major, set_major: 11, 8; + pub minor, set_minor: 15, 12; +} + +impl Default for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { + fn default() -> Self { + VersionNumberEntry::new(SpdmVersion::default()) + } +} + +impl VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { + pub fn new(version: SpdmVersion) -> Self { + let mut entry = VersionNumberEntry([0u8; VERSION_ENTRY_SIZE]); + entry.set_major(version.major()); + entry.set_minor(version.minor()); + entry + } +} + +impl CommonCodec for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> {} + +// Generate the GET_VERSION command with all the contexts information +pub(crate) fn send_get_version<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + payload: GetVersionPayload, +) -> CommandResult<()> { + let len = payload + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + req_buf + .push_data(len) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca)?; + Ok(()) +} + +// Requester function for processing a VERSION response +fn process_version<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + resp_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + // VERSION response must use version 1.0 per spec + match spdm_hdr.version() { + Ok(SpdmVersion::V10) => {} + _ => { + Err(ctx.generate_error_response(resp_payload, ErrorCode::VersionMismatch, 0, None))?; + } + } + + // Decode the VERSION response common header + let resp = + VersionRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; + + let entry_count = resp.version_num_entry_count as usize; + + // Validate entry count + if entry_count == 0 { + Err((false, CommandError::UnsupportedResponse))?; + } + + // Decode all version entries from the response + Ok(()) +} + +/// Requester function handling the parsing of the VERSION response sent by the Responder. +pub(crate) fn handle_version_response<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + payload: GetVersionPayload, +) -> CommandResult<()> { + let req = req_buf; + + let req_msg_header: SpdmMsgHdr = + SpdmMsgHdr::decode(req).map_err(|e| (false, CommandError::Codec(e)))?; + + let req_code = req_msg_header + .req_resp_code() + .map_err(|_| (false, CommandError::UnsupportedResponse)); + + Ok(()) +} + +// tests +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::SpdmVersion; + + #[test] + #[ignore] + fn test_process_version() { + todo!(); + } +} diff --git a/src/context.rs b/src/context.rs index 17046d7..9f0399c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,26 +1,27 @@ // Licensed under the Apache-2.0 license // use crate::cert_mgr::DeviceCertsManager; -use crate::cert_store::*; use crate::chunk_ctx::LargeResponseCtx; use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; +use crate::commands::version_rq::handle_version_response; use crate::commands::{ algorithms_rsp, capabilities_rsp, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, - digests_rsp, measurements_rsp, version_rsp, + digests_rsp, measurements_rsp, version_rq, version_rsp, }; use crate::error::*; use crate::measurements::common::SpdmMeasurements; use crate::platform::evidence::SpdmEvidence; +use crate::platform::hash::SpdmHash; +use crate::platform::rng::SpdmRng; +use crate::platform::transport::SpdmTransport; use crate::protocol::algorithms::*; use crate::protocol::common::{ReqRespCode, SpdmMsgHdr}; use crate::protocol::version::*; use crate::protocol::DeviceCapabilities; use crate::state::{ConnectionState, State}; use crate::transcript::{TranscriptContext, TranscriptManager}; -use crate::platform::transport::SpdmTransport; -use crate::platform::hash::SpdmHash; -use crate::platform::rng::SpdmRng; +use crate::{cert_store::*, commands, protocol}; pub struct SpdmContext<'a> { transport: &'a mut dyn SpdmTransport, @@ -72,12 +73,13 @@ impl<'a> SpdmContext<'a> { }) } - pub fn process_message(&mut self, msg_buf: &mut MessageBuf<'a>) -> SpdmResult<()> { + /// The Responder receives a request message sent by the Requester and processes it accordingly. + pub fn responder_process_message(&mut self, msg_buf: &mut MessageBuf<'a>) -> SpdmResult<()> { self.transport .receive_request(msg_buf) .map_err(SpdmError::Transport)?; - match self.handle_request(msg_buf) { + match self.responder_handle_request(msg_buf) { Ok(()) => { self.send_response(msg_buf)?; } @@ -91,7 +93,58 @@ impl<'a> SpdmContext<'a> { Ok(()) } - fn handle_request(&mut self, buf: &mut MessageBuf<'a>) -> CommandResult<()> { + /// The Requester receives a response message sent by the Responder and processes it accordingly. + /// + /// # Arguments + /// * `resp_buffer`: buffer the message is received into from the transport medium. + /// + /// # Warning + /// This function resets all data initially stored in then resp_buffer. + pub fn requester_process_message( + &mut self, + resp_buffer: &mut MessageBuf<'a>, + ) -> SpdmResult<()> { + resp_buffer.reset(); + self.transport + .receive_response(resp_buffer) + .map_err(|e| SpdmError::Transport(e))?; + + match self.requester_handle_response(resp_buffer) { + Ok(()) => {} + Err(_) => panic!("rip requester"), + } + + Ok(()) + } + + // Use ReqRespCode as command issuer for now, until the correct state machine is in place + // TODO: implement in transport + pub fn send_request( + &mut self, + req: ReqRespCode, + req_buf: &mut MessageBuf<'a>, + ) -> SpdmResult<()> { + req_buf.reset(); + + match req { + ReqRespCode::GetVersion => version_rq::send_get_version( + self, + req_buf, + version_rq::GetVersionPayload::new(1, 1), + ) + .map_err(|(_send_response, cmd_err)| SpdmError::Command(cmd_err))?, + _ => return Err(SpdmError::InvalidParam), + }; + + self.transport + .send_request(1, req_buf) + .map_err(|_| SpdmError::InvalidParam)?; + + Ok(()) + } + + /// The responder handles incoming requests and responds to them accordingly. + fn responder_handle_request(&mut self, buf: &mut MessageBuf<'a>) -> CommandResult<()> { let req = buf; let req_msg_header: SpdmMsgHdr = @@ -108,12 +161,22 @@ impl<'a> SpdmContext<'a> { match req_code { ReqRespCode::GetVersion => version_rsp::handle_get_version(self, req_msg_header, req)?, - ReqRespCode::GetCapabilities => capabilities_rsp::handle_get_capabilities(self, req_msg_header, req)?, - ReqRespCode::NegotiateAlgorithms => algorithms_rsp::handle_negotiate_algorithms(self, req_msg_header, req)?, + ReqRespCode::GetCapabilities => { + capabilities_rsp::handle_get_capabilities(self, req_msg_header, req)? + } + ReqRespCode::NegotiateAlgorithms => { + algorithms_rsp::handle_negotiate_algorithms(self, req_msg_header, req)? + } ReqRespCode::GetDigests => digests_rsp::handle_get_digests(self, req_msg_header, req)?, - ReqRespCode::GetCertificate => certificate_rsp::handle_get_certificate(self, req_msg_header, req)?, - ReqRespCode::Challenge => challenge_auth_rsp::handle_challenge(self, req_msg_header, req)?, - ReqRespCode::GetMeasurements => measurements_rsp::handle_get_measurements(self, req_msg_header, req)?, + ReqRespCode::GetCertificate => { + certificate_rsp::handle_get_certificate(self, req_msg_header, req)? + } + ReqRespCode::Challenge => { + challenge_auth_rsp::handle_challenge(self, req_msg_header, req)? + } + ReqRespCode::GetMeasurements => { + measurements_rsp::handle_get_measurements(self, req_msg_header, req)? + } ReqRespCode::ChunkGet => chunk_get_rsp::handle_chunk_get(self, req_msg_header, req)?, _ => Err((false, CommandError::UnsupportedRequest))?, @@ -121,8 +184,36 @@ impl<'a> SpdmContext<'a> { Ok(()) } + /// Requester function parsing and processing messages provided in `buf`. + /// + /// # Arguments + /// * `buf`: Message buffer containing a raw response. + fn requester_handle_response(&mut self, buf: &mut MessageBuf<'a>) -> CommandResult<()> { + let req = buf; + let req_msg_header: SpdmMsgHdr = + SpdmMsgHdr::decode(req).map_err(|e| (false, CommandError::Codec(e)))?; + + let req_code = req_msg_header + .req_resp_code() + .map_err(|_| (false, CommandError::UnsupportedRequest))?; + + // if req_code != ReqRespCode::ChunkGet && self.large_resp_context.in_progress() { + // // Reset large response context if the request is not a CHUNK_GET + // self.large_resp_context.reset(); + // } + + match req_code { + ReqRespCode::Version => handle_version_response(self, buf, payload)?, + _ => Err((false, CommandError::UnsupportedRequest))?, + } + + Ok(()) + } + fn send_response(&mut self, resp: &mut MessageBuf<'a>) -> SpdmResult<()> { - self.transport.send_response(resp).map_err(SpdmError::Transport) + self.transport + .send_response(resp) + .map_err(SpdmError::Transport) } pub(crate) fn prepare_response_buffer(&self, rsp_buf: &mut MessageBuf) -> CommandResult<()> { diff --git a/src/error.rs b/src/error.rs index 6ee0f47..6a17c2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,12 +6,12 @@ use crate::chunk_ctx::ChunkError; use crate::codec::CodecError; use crate::commands::error_rsp::ErrorCode; use crate::measurements::common::MeasurementsError; +use crate::platform::evidence::SpdmEvidenceError; +use crate::platform::hash::SpdmHashError; use crate::platform::rng::SpdmRngError; +use crate::platform::transport::TransportError; use crate::protocol::SignCtxError; use crate::transcript::TranscriptError; -use crate::platform::transport::TransportError; -use crate::platform::hash::SpdmHashError; -use crate::platform::evidence::SpdmEvidenceError; #[derive(Debug)] pub enum SpdmError { @@ -42,6 +42,7 @@ pub enum CommandError { Codec(CodecError), ErrorCode(ErrorCode), UnsupportedRequest, + UnsupportedResponse, SignCtx(SignCtxError), InvalidChunkContext, Chunk(ChunkError), diff --git a/src/protocol/common.rs b/src/protocol/common.rs index 17b5236..a887981 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -6,7 +6,7 @@ use crate::protocol::{version::SpdmVersion, REQUESTER_CONTEXT_LEN, SPDM_CONTEXT_ use zerocopy::{FromBytes, Immutable, IntoBytes}; #[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum ReqRespCode { +pub enum ReqRespCode { GetVersion = 0x84, Version = 0x04, GetCapabilities = 0xE1, diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 41502e4..3143eda 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -13,3 +13,5 @@ pub use certs::*; pub(crate) use common::*; pub(crate) use signature::*; pub use version::*; + +pub use common::ReqRespCode; diff --git a/src/requester.rs b/src/requester.rs new file mode 100644 index 0000000..0cf38a0 --- /dev/null +++ b/src/requester.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache-2.0 license + +struct Requester<'a> { + context: +} From 5339046db0eba257b1b6408155b4a3ba72358b96 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 17 Dec 2025 15:34:48 +0100 Subject: [PATCH 04/86] Refactor Version command --- src/commands/mod.rs | 3 +- src/commands/version/mod.rs | 87 +++++++++ src/commands/version/request.rs | 93 ++++++++++ .../{version_rsp.rs => version/response.rs} | 73 +------- src/commands/version_rq.rs | 173 ------------------ src/context.rs | 16 +- 6 files changed, 190 insertions(+), 255 deletions(-) create mode 100644 src/commands/version/mod.rs create mode 100644 src/commands/version/request.rs rename src/commands/{version_rsp.rs => version/response.rs} (61%) delete mode 100644 src/commands/version_rq.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6bfde0b..2644708 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,5 +8,4 @@ pub mod chunk_get_rsp; pub mod digests_rsp; pub mod error_rsp; pub mod measurements_rsp; -pub mod version_rq; -pub mod version_rsp; +pub mod version; diff --git a/src/commands/version/mod.rs b/src/commands/version/mod.rs new file mode 100644 index 0000000..6424a99 --- /dev/null +++ b/src/commands/version/mod.rs @@ -0,0 +1,87 @@ +// Licensed under the Apache-2.0 license +mod request; +mod response; + +pub(crate) use request::*; +pub(crate) use response::*; + +use crate::{ + codec::{Codec, CommonCodec, MessageBuf}, + protocol::SpdmVersion, +}; +use bitfield::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +const VERSION_ENTRY_SIZE: usize = 2; + +#[allow(dead_code)] +#[derive(FromBytes, IntoBytes, Immutable)] +pub(crate) struct VersionReqPayload { + param1: u8, + param2: u8, +} + +impl VersionReqPayload { + pub(crate) fn new(param1: u8, param2: u8) -> Self { + Self { param1, param2 } + } +} + +#[allow(dead_code)] +#[derive(FromBytes, IntoBytes, Immutable)] +struct VersionRespCommon { + param1: u8, + param2: u8, + reserved: u8, + version_num_entry_count: u8, +} + +impl CommonCodec for VersionReqPayload {} + +impl Default for VersionRespCommon { + fn default() -> Self { + VersionRespCommon::new(0) + } +} + +impl VersionRespCommon { + pub fn new(entry_count: u8) -> Self { + VersionRespCommon { + param1: 0, + param2: 0, + reserved: 0, + version_num_entry_count: entry_count, + } + } +} + +impl CommonCodec for VersionRespCommon {} + +bitfield! { +#[repr(C)] +#[derive(FromBytes, IntoBytes, Immutable)] +pub struct VersionNumberEntry(MSB0 [u8]); +impl Debug; +u8; + pub update_ver, set_update_ver: 3, 0; + pub alpha, set_alpha: 7, 4; + pub major, set_major: 11, 8; + pub minor, set_minor: 15, 12; +} + +impl Default for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { + fn default() -> Self { + VersionNumberEntry::new(SpdmVersion::default()) + } +} + +impl VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { + pub fn new(version: SpdmVersion) -> Self { + let mut entry = VersionNumberEntry([0u8; VERSION_ENTRY_SIZE]); + entry.set_major(version.major()); + entry.set_minor(version.minor()); + entry + } +} + +impl CommonCodec for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> {} diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs new file mode 100644 index 0000000..7041c1a --- /dev/null +++ b/src/commands/version/request.rs @@ -0,0 +1,93 @@ +// Licensed under the Apache-2.0 license + +use crate::{ + codec::{Codec, MessageBuf}, + context::SpdmContext, + error::{CommandError, CommandResult}, + protocol::SpdmMsgHdr, + transcript::TranscriptContext, +}; + +use crate::commands::error_rsp::ErrorCode; +use crate::commands::version::{VersionReqPayload, VersionRespCommon}; + +use crate::protocol::SpdmVersion; + +// Generate the GET_VERSION command with all the contexts information +pub(crate) fn send_get_version<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + payload: VersionReqPayload, +) -> CommandResult<()> { + let len = payload + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + req_buf + .push_data(len) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca)?; + Ok(()) +} + +// Requester function for processing a VERSION response +fn process_version<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + resp_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + // VERSION response must use version 1.0 per spec + match spdm_hdr.version() { + Ok(SpdmVersion::V10) => {} + _ => { + Err(ctx.generate_error_response(resp_payload, ErrorCode::VersionMismatch, 0, None))?; + } + } + + // Decode the VERSION response common header + let resp = + VersionRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; + + let entry_count = resp.version_num_entry_count as usize; + + // Validate entry count + if entry_count == 0 { + Err((false, CommandError::UnsupportedResponse))?; + } + + // Decode all version entries from the response + Ok(()) +} + +/// Requester function handling the parsing of the VERSION response sent by the Responder. +pub(crate) fn handle_version_response<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + payload: VersionRespCommon, +) -> CommandResult<()> { + let req = req_buf; + + let req_msg_header: SpdmMsgHdr = + SpdmMsgHdr::decode(req).map_err(|e| (false, CommandError::Codec(e)))?; + + let req_code = req_msg_header + .req_resp_code() + .map_err(|_| (false, CommandError::UnsupportedResponse)); + + todo!() + + Ok(()) +} + +// tests +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[ignore] + fn test_process_version() { + todo!(); + } +} diff --git a/src/commands/version_rsp.rs b/src/commands/version/response.rs similarity index 61% rename from src/commands/version_rsp.rs rename to src/commands/version/response.rs index a518edc..f930620 100644 --- a/src/commands/version_rsp.rs +++ b/src/commands/version/response.rs @@ -1,82 +1,13 @@ // Licensed under the Apache-2.0 license -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::ErrorCode; +use crate::commands::version::{VersionNumberEntry, VersionReqPayload, VersionRespCommon}; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; use crate::protocol::{ReqRespCode, SpdmMsgHdr, SpdmVersion}; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; -use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -const VERSION_ENTRY_SIZE: usize = 2; - -#[allow(dead_code)] -#[derive(FromBytes, IntoBytes, Immutable)] -struct VersionReqPayload { - param1: u8, - param2: u8, -} - -#[allow(dead_code)] -#[derive(FromBytes, IntoBytes, Immutable)] -struct VersionRespCommon { - param1: u8, - param2: u8, - reserved: u8, - version_num_entry_count: u8, -} - -impl CommonCodec for VersionReqPayload {} - -impl Default for VersionRespCommon { - fn default() -> Self { - VersionRespCommon::new(0) - } -} - -impl VersionRespCommon { - pub fn new(entry_count: u8) -> Self { - VersionRespCommon { - param1: 0, - param2: 0, - reserved: 0, - version_num_entry_count: entry_count, - } - } -} - -impl CommonCodec for VersionRespCommon {} - -bitfield! { -#[repr(C)] -#[derive(FromBytes, IntoBytes, Immutable)] -pub struct VersionNumberEntry(MSB0 [u8]); -impl Debug; -u8; - pub update_ver, set_update_ver: 3, 0; - pub alpha, set_alpha: 7, 4; - pub major, set_major: 11, 8; - pub minor, set_minor: 15, 12; -} - -impl Default for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { - fn default() -> Self { - VersionNumberEntry::new(SpdmVersion::default()) - } -} - -impl VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { - pub fn new(version: SpdmVersion) -> Self { - let mut entry = VersionNumberEntry([0u8; VERSION_ENTRY_SIZE]); - entry.set_major(version.major()); - entry.set_minor(version.minor()); - entry - } -} - -impl CommonCodec for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> {} fn generate_version_response<'a>( ctx: &mut SpdmContext<'a>, diff --git a/src/commands/version_rq.rs b/src/commands/version_rq.rs deleted file mode 100644 index 1aa9f74..0000000 --- a/src/commands/version_rq.rs +++ /dev/null @@ -1,173 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::{ - codec::{Codec, CommonCodec, MessageBuf}, - context::SpdmContext, - error::{CommandError, CommandResult, SpdmResult}, - protocol::{version, SpdmMsgHdr}, - transcript::TranscriptContext, -}; - -use crate::commands::error_rsp::ErrorCode; - -use crate::protocol::{ReqRespCode, SpdmVersion}; -use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -const VERSION_ENTRY_SIZE: usize = 2; - -// 4.9.1.1 GET_VERSION request message and VERSION response message -#[derive(FromBytes, IntoBytes, Immutable)] -pub struct GetVersionPayload { - Param1: u8, - Param2: u8, -} - -impl GetVersionPayload { - pub fn new(param1: u8, param2: u8) -> Self { - GetVersionPayload { - Param1: param1, - Param2: param2, - } - } -} - -impl CommonCodec for GetVersionPayload {} - -// copy until refactored -#[allow(dead_code)] -#[derive(FromBytes, IntoBytes, Immutable)] -struct VersionRespCommon { - param1: u8, - param2: u8, - reserved: u8, - version_num_entry_count: u8, -} - -impl CommonCodec for VersionReqPayload {} - -impl Default for VersionRespCommon { - fn default() -> Self { - VersionRespCommon::new(0) - } -} - -impl VersionRespCommon { - pub fn new(entry_count: u8) -> Self { - VersionRespCommon { - param1: 0, - param2: 0, - reserved: 0, - version_num_entry_count: entry_count, - } - } -} - -impl CommonCodec for VersionRespCommon {} - -bitfield! { -#[repr(C)] -#[derive(FromBytes, IntoBytes, Immutable)] -pub struct VersionNumberEntry(MSB0 [u8]); -impl Debug; -u8; - pub update_ver, set_update_ver: 3, 0; - pub alpha, set_alpha: 7, 4; - pub major, set_major: 11, 8; - pub minor, set_minor: 15, 12; -} - -impl Default for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { - fn default() -> Self { - VersionNumberEntry::new(SpdmVersion::default()) - } -} - -impl VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { - pub fn new(version: SpdmVersion) -> Self { - let mut entry = VersionNumberEntry([0u8; VERSION_ENTRY_SIZE]); - entry.set_major(version.major()); - entry.set_minor(version.minor()); - entry - } -} - -impl CommonCodec for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> {} - -// Generate the GET_VERSION command with all the contexts information -pub(crate) fn send_get_version<'a>( - ctx: &mut SpdmContext<'a>, - req_buf: &mut MessageBuf<'a>, - payload: GetVersionPayload, -) -> CommandResult<()> { - let len = payload - .encode(req_buf) - .map_err(|e| (false, CommandError::Codec(e)))?; - - req_buf - .push_data(len) - .map_err(|_| (false, CommandError::BufferTooSmall))?; - - ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca)?; - Ok(()) -} - -// Requester function for processing a VERSION response -fn process_version<'a>( - ctx: &mut SpdmContext<'a>, - spdm_hdr: SpdmMsgHdr, - resp_payload: &mut MessageBuf<'a>, -) -> CommandResult<()> { - // VERSION response must use version 1.0 per spec - match spdm_hdr.version() { - Ok(SpdmVersion::V10) => {} - _ => { - Err(ctx.generate_error_response(resp_payload, ErrorCode::VersionMismatch, 0, None))?; - } - } - - // Decode the VERSION response common header - let resp = - VersionRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; - - let entry_count = resp.version_num_entry_count as usize; - - // Validate entry count - if entry_count == 0 { - Err((false, CommandError::UnsupportedResponse))?; - } - - // Decode all version entries from the response - Ok(()) -} - -/// Requester function handling the parsing of the VERSION response sent by the Responder. -pub(crate) fn handle_version_response<'a>( - ctx: &mut SpdmContext<'a>, - req_buf: &mut MessageBuf<'a>, - payload: GetVersionPayload, -) -> CommandResult<()> { - let req = req_buf; - - let req_msg_header: SpdmMsgHdr = - SpdmMsgHdr::decode(req).map_err(|e| (false, CommandError::Codec(e)))?; - - let req_code = req_msg_header - .req_resp_code() - .map_err(|_| (false, CommandError::UnsupportedResponse)); - - Ok(()) -} - -// tests -#[cfg(test)] -mod tests { - use super::*; - use crate::protocol::SpdmVersion; - - #[test] - #[ignore] - fn test_process_version() { - todo!(); - } -} diff --git a/src/context.rs b/src/context.rs index 9f0399c..c76b055 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,10 +4,10 @@ use crate::chunk_ctx::LargeResponseCtx; use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; -use crate::commands::version_rq::handle_version_response; +use crate::commands::version::handle_version_response; use crate::commands::{ algorithms_rsp, capabilities_rsp, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, - digests_rsp, measurements_rsp, version_rq, version_rsp, + digests_rsp, measurements_rsp, version, }; use crate::error::*; use crate::measurements::common::SpdmMeasurements; @@ -127,12 +127,10 @@ impl<'a> SpdmContext<'a> { req_buf.reset(); match req { - ReqRespCode::GetVersion => version_rq::send_get_version( - self, - req_buf, - version_rq::GetVersionPayload::new(1, 1), - ) - .map_err(|(_send_response, cmd_err)| SpdmError::Command(cmd_err))?, + ReqRespCode::GetVersion => { + version::send_get_version(self, req_buf, version::VersionReqPayload::new(1, 1)) + .map_err(|(_send_response, cmd_err)| SpdmError::Command(cmd_err))? + } _ => return Err(SpdmError::InvalidParam), }; @@ -160,7 +158,7 @@ impl<'a> SpdmContext<'a> { } match req_code { - ReqRespCode::GetVersion => version_rsp::handle_get_version(self, req_msg_header, req)?, + ReqRespCode::GetVersion => version::handle_get_version(self, req_msg_header, req)?, ReqRespCode::GetCapabilities => { capabilities_rsp::handle_get_capabilities(self, req_msg_header, req)? } From 4646943490bcbd6733b6c6b3c742eacc2c4e50ac Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 17 Dec 2025 16:25:30 +0100 Subject: [PATCH 05/86] Refactor Capabilities command --- src/commands/capabilities/mod.rs | 145 ++++++++++++++++++ .../response.rs} | 137 +---------------- src/commands/mod.rs | 2 +- src/context.rs | 6 +- 4 files changed, 151 insertions(+), 139 deletions(-) create mode 100644 src/commands/capabilities/mod.rs rename src/commands/{capabilities_rsp.rs => capabilities/response.rs} (59%) diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs new file mode 100644 index 0000000..5c8649c --- /dev/null +++ b/src/commands/capabilities/mod.rs @@ -0,0 +1,145 @@ +// Licensed under the Apache-2.0 license + +mod response; + +pub(crate) use response::*; + +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use crate::{ + codec::CommonCodec, + protocol::{CapabilityFlags, EpInfoCapability, PskCapability, SpdmVersion}, +}; + +#[derive(IntoBytes, FromBytes, Immutable, Default)] +#[repr(C)] +pub(crate) struct GetCapabilitiesBase { + param1: u8, + param2: u8, +} + +impl CommonCodec for GetCapabilitiesBase {} + +#[derive(IntoBytes, FromBytes, Immutable, Default)] +#[repr(C, packed)] +#[allow(dead_code)] +pub(crate) struct GetCapabilitiesV11 { + reserved: u8, + ct_exponent: u8, + reserved2: u8, + reserved3: u8, + flags: CapabilityFlags, +} + +impl GetCapabilitiesV11 { + pub fn new(ct_exponent: u8, flags: CapabilityFlags) -> Self { + Self { + reserved: 0, + ct_exponent, + reserved2: 0, + reserved3: 0, + flags, + } + } +} + +impl CommonCodec for GetCapabilitiesV11 {} + +#[derive(IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub(crate) struct GetCapabilitiesV12 { + data_transfer_size: u32, + max_spdm_msg_size: u32, +} + +impl CommonCodec for GetCapabilitiesV12 {} + +fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { + // Checks common to 1.1 and higher + if version >= SpdmVersion::V11 { + // Illegal to return reserved values (2 and 3) + if flags.psk_cap() >= PskCapability::PskWithContext as u8 { + return false; + } + + // Checks that originate from key exchange capabilities + if flags.key_ex_cap() == 1 || flags.psk_cap() != PskCapability::NoPsk as u8 { + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } else { + if flags.mac_cap() == 1 + || flags.encrypt_cap() == 1 + || flags.handshake_in_the_clear_cap() == 1 + || flags.hbeat_cap() == 1 + || flags.key_upd_cap() == 1 + { + return false; + } + + if version >= SpdmVersion::V13 && flags.event_cap() == 1 { + return false; + } + } + + if flags.key_ex_cap() == 0 + && flags.psk_cap() == PskCapability::PskWithNoContext as u8 + && flags.handshake_in_the_clear_cap() == 1 + { + return false; + } + + // Checks that originate from certificate or public key capabilities + if flags.cert_cap() == 1 || flags.pub_key_id_cap() == 1 { + // Certificate capabilities and public key capabilities can not both be set + if flags.cert_cap() == 1 && flags.pub_key_id_cap() == 1 { + return false; + } + + if flags.chal_cap() == 0 && flags.pub_key_id_cap() == 1 { + return false; + } + } else { + // If certificates or public keys are not enabled then these capabilities are not allowed + if flags.chal_cap() == 1 || flags.mut_auth_cap() == 1 { + return false; + } + + if version >= SpdmVersion::V13 + && flags.ep_info_cap() == EpInfoCapability::EpInfoWithSignature as u8 + { + return false; + } + } + + // Checks that originate from mutual authentication capabilities + if flags.mut_auth_cap() == 1 { + // Mutual authentication with asymmetric keys can only occur through the basic mutual + // authentication flow (CHAL_CAP == 1) or the session-based mutual authentication flow + // (KEY_EX_CAP == 1) + if flags.cert_cap() == 0 && flags.pub_key_id_cap() == 0 { + return false; + } + } + } + + // Checks specific to 1.1 + if version == SpdmVersion::V11 && flags.mut_auth_cap() == 1 && flags.encap_cap() == 0 { + return false; + } + + // Checks specific to 1.3 and higher + if version >= SpdmVersion::V13 { + // Illegal to return reserved values + if flags.ep_info_cap() == EpInfoCapability::Reserved as u8 || flags.multi_key_cap() == 3 { + return false; + } + + // Check multi_key_cap and pub_key_id_cap + if flags.multi_key_cap() != 0 && flags.pub_key_id_cap() == 1 { + return false; + } + } + + true +} diff --git a/src/commands/capabilities_rsp.rs b/src/commands/capabilities/response.rs similarity index 59% rename from src/commands/capabilities_rsp.rs rename to src/commands/capabilities/response.rs index 662bec3..60b11f9 100644 --- a/src/commands/capabilities_rsp.rs +++ b/src/commands/capabilities/response.rs @@ -1,145 +1,12 @@ // Licensed under the Apache-2.0 license -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use super::*; +use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(IntoBytes, FromBytes, Immutable, Default)] -#[repr(C)] -pub(crate) struct GetCapabilitiesBase { - param1: u8, - param2: u8, -} - -impl CommonCodec for GetCapabilitiesBase {} - -#[derive(IntoBytes, FromBytes, Immutable, Default)] -#[repr(C, packed)] -#[allow(dead_code)] -pub(crate) struct GetCapabilitiesV11 { - reserved: u8, - ct_exponent: u8, - reserved2: u8, - reserved3: u8, - flags: CapabilityFlags, -} - -impl GetCapabilitiesV11 { - pub fn new(ct_exponent: u8, flags: CapabilityFlags) -> Self { - Self { - reserved: 0, - ct_exponent, - reserved2: 0, - reserved3: 0, - flags, - } - } -} - -impl CommonCodec for GetCapabilitiesV11 {} - -#[derive(IntoBytes, FromBytes, Immutable)] -#[repr(C, packed)] -pub(crate) struct GetCapabilitiesV12 { - data_transfer_size: u32, - max_spdm_msg_size: u32, -} - -impl CommonCodec for GetCapabilitiesV12 {} - -fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { - // Checks common to 1.1 and higher - if version >= SpdmVersion::V11 { - // Illegal to return reserved values (2 and 3) - if flags.psk_cap() >= PskCapability::PskWithContext as u8 { - return false; - } - - // Checks that originate from key exchange capabilities - if flags.key_ex_cap() == 1 || flags.psk_cap() != PskCapability::NoPsk as u8 { - if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { - return false; - } - } else { - if flags.mac_cap() == 1 - || flags.encrypt_cap() == 1 - || flags.handshake_in_the_clear_cap() == 1 - || flags.hbeat_cap() == 1 - || flags.key_upd_cap() == 1 - { - return false; - } - - if version >= SpdmVersion::V13 && flags.event_cap() == 1 { - return false; - } - } - - if flags.key_ex_cap() == 0 - && flags.psk_cap() == PskCapability::PskWithNoContext as u8 - && flags.handshake_in_the_clear_cap() == 1 - { - return false; - } - - // Checks that originate from certificate or public key capabilities - if flags.cert_cap() == 1 || flags.pub_key_id_cap() == 1 { - // Certificate capabilities and public key capabilities can not both be set - if flags.cert_cap() == 1 && flags.pub_key_id_cap() == 1 { - return false; - } - - if flags.chal_cap() == 0 && flags.pub_key_id_cap() == 1 { - return false; - } - } else { - // If certificates or public keys are not enabled then these capabilities are not allowed - if flags.chal_cap() == 1 || flags.mut_auth_cap() == 1 { - return false; - } - - if version >= SpdmVersion::V13 - && flags.ep_info_cap() == EpInfoCapability::EpInfoWithSignature as u8 - { - return false; - } - } - - // Checks that originate from mutual authentication capabilities - if flags.mut_auth_cap() == 1 { - // Mutual authentication with asymmetric keys can only occur through the basic mutual - // authentication flow (CHAL_CAP == 1) or the session-based mutual authentication flow - // (KEY_EX_CAP == 1) - if flags.cert_cap() == 0 && flags.pub_key_id_cap() == 0 { - return false; - } - } - } - - // Checks specific to 1.1 - if version == SpdmVersion::V11 && flags.mut_auth_cap() == 1 && flags.encap_cap() == 0 { - return false; - } - - // Checks specific to 1.3 and higher - if version >= SpdmVersion::V13 { - // Illegal to return reserved values - if flags.ep_info_cap() == EpInfoCapability::Reserved as u8 || flags.multi_key_cap() == 3 { - return false; - } - - // Check multi_key_cap and pub_key_id_cap - if flags.multi_key_cap() != 0 && flags.pub_key_id_cap() == 1 { - return false; - } - } - - true -} fn process_get_capabilities<'a>( ctx: &mut SpdmContext<'a>, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2644708..5bc1996 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license pub mod algorithms_rsp; -pub mod capabilities_rsp; +pub mod capabilities; pub mod certificate_rsp; pub mod challenge_auth_rsp; pub mod chunk_get_rsp; diff --git a/src/context.rs b/src/context.rs index c76b055..517f027 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,8 +6,8 @@ use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ - algorithms_rsp, capabilities_rsp, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, - digests_rsp, measurements_rsp, version, + algorithms_rsp, capabilities, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, digests_rsp, + measurements_rsp, version, }; use crate::error::*; use crate::measurements::common::SpdmMeasurements; @@ -160,7 +160,7 @@ impl<'a> SpdmContext<'a> { match req_code { ReqRespCode::GetVersion => version::handle_get_version(self, req_msg_header, req)?, ReqRespCode::GetCapabilities => { - capabilities_rsp::handle_get_capabilities(self, req_msg_header, req)? + capabilities::handle_get_capabilities(self, req_msg_header, req)? } ReqRespCode::NegotiateAlgorithms => { algorithms_rsp::handle_negotiate_algorithms(self, req_msg_header, req)? From 3df72a0b2c6391fde8c7afd5516e96acc46476af Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 17 Dec 2025 17:46:39 +0100 Subject: [PATCH 06/86] Add check is_request/is_response check --- src/context.rs | 12 ++++++++---- src/protocol/common.rs | 6 ++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/context.rs b/src/context.rs index 517f027..e5048f0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -187,14 +187,18 @@ impl<'a> SpdmContext<'a> { /// # Arguments /// * `buf`: Message buffer containing a raw response. fn requester_handle_response(&mut self, buf: &mut MessageBuf<'a>) -> CommandResult<()> { - let req = buf; - let req_msg_header: SpdmMsgHdr = - SpdmMsgHdr::decode(req).map_err(|e| (false, CommandError::Codec(e)))?; + let resp = buf; + let resp_msg_header: SpdmMsgHdr = + SpdmMsgHdr::decode(resp).map_err(|e| (false, CommandError::Codec(e)))?; - let req_code = req_msg_header + let resp_code = resp_msg_header .req_resp_code() .map_err(|_| (false, CommandError::UnsupportedRequest))?; + if resp_code.is_request() { + Err((false, CommandError::UnsupportedRequest))? + } + // if req_code != ReqRespCode::ChunkGet && self.large_resp_context.in_progress() { // // Reset large response context if the request is not a CHUNK_GET // self.large_resp_context.reset(); diff --git a/src/protocol/common.rs b/src/protocol/common.rs index a887981..6e9d513 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -75,6 +75,12 @@ impl ReqRespCode { Ok(context) } + pub(crate) fn is_response(&self) -> bool { + *self as u8 >= 0x00 && *self as u8 <= 0x7F + } + pub(crate) fn is_request(&self) -> bool { + !self.is_response() + } } #[derive(FromBytes, IntoBytes, Immutable)] From ac42fed3bfa4ae867e77ecd286faded1a0f3a5e5 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 17 Dec 2025 17:47:30 +0100 Subject: [PATCH 07/86] Add capabilities response handler --- src/commands/capabilities/mod.rs | 2 ++ src/commands/capabilities/request.rs | 12 ++++++++++++ src/commands/version/request.rs | 17 +++-------------- src/context.rs | 9 ++++++--- 4 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 src/commands/capabilities/request.rs diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs index 5c8649c..a3ddc73 100644 --- a/src/commands/capabilities/mod.rs +++ b/src/commands/capabilities/mod.rs @@ -1,7 +1,9 @@ // Licensed under the Apache-2.0 license +mod request; mod response; +pub(crate) use request::*; pub(crate) use response::*; use zerocopy::{FromBytes, Immutable, IntoBytes}; diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs new file mode 100644 index 0000000..268905c --- /dev/null +++ b/src/commands/capabilities/request.rs @@ -0,0 +1,12 @@ +// Licensed under the Apache-2.0 license + +use crate::{codec::MessageBuf, context::SpdmContext, error::CommandResult, protocol::SpdmMsgHdr}; + +/// Requester function handling the parsing of the CAPABILITIES response sent by the Responder. +pub(crate) fn handle_capabilities_response<'a>( + ctx: &mut SpdmContext<'a>, + resp_header: SpdmMsgHdr, + resp: &mut MessageBuf<'a>, +) -> CommandResult<()> { + todo!(); +} diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 7041c1a..dfecd05 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -63,21 +63,10 @@ fn process_version<'a>( /// Requester function handling the parsing of the VERSION response sent by the Responder. pub(crate) fn handle_version_response<'a>( ctx: &mut SpdmContext<'a>, - req_buf: &mut MessageBuf<'a>, - payload: VersionRespCommon, + resp_header: SpdmMsgHdr, + resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { - let req = req_buf; - - let req_msg_header: SpdmMsgHdr = - SpdmMsgHdr::decode(req).map_err(|e| (false, CommandError::Codec(e)))?; - - let req_code = req_msg_header - .req_resp_code() - .map_err(|_| (false, CommandError::UnsupportedResponse)); - - todo!() - - Ok(()) + todo!(); } // tests diff --git a/src/context.rs b/src/context.rs index e5048f0..f050978 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,6 +3,7 @@ // use crate::cert_mgr::DeviceCertsManager; use crate::chunk_ctx::LargeResponseCtx; use crate::codec::{Codec, MessageBuf}; +use crate::commands::capabilities::handle_capabilities_response; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ @@ -204,9 +205,11 @@ impl<'a> SpdmContext<'a> { // self.large_resp_context.reset(); // } - match req_code { - ReqRespCode::Version => handle_version_response(self, buf, payload)?, - _ => Err((false, CommandError::UnsupportedRequest))?, + match resp_code { + ReqRespCode::Version => handle_version_response(self, resp_msg_header, resp)?, + _ => Err((false, CommandError::UnsupportedResponse))?, + ReqRespCode::Capabilities => handle_capabilities_response(self, resp_msg_header, resp)?, + _ => Err((false, CommandError::UnsupportedResponse))?, } Ok(()) From 5d56e695e52ab01119ee6e2dea255008b984df51 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 18 Dec 2025 11:11:36 +0100 Subject: [PATCH 08/86] Fix clippy warning for is_response check --- src/protocol/common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/common.rs b/src/protocol/common.rs index 6e9d513..00d29cf 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -76,7 +76,7 @@ impl ReqRespCode { Ok(context) } pub(crate) fn is_response(&self) -> bool { - *self as u8 >= 0x00 && *self as u8 <= 0x7F + *self as u8 <= 0x7F } pub(crate) fn is_request(&self) -> bool { !self.is_response() From 0a4554aaacd09290c4811c097be038d51aa6e49f Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 7 Jan 2026 16:42:07 +0100 Subject: [PATCH 09/86] Implement VERSION response processing function Signed-off-by: Marvin Gudel --- src/commands/version/mod.rs | 16 +++++++++++++++ src/commands/version/request.rs | 36 +++++++++++++++++++++++++++++---- src/context.rs | 1 - 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/commands/version/mod.rs b/src/commands/version/mod.rs index 6424a99..1870f43 100644 --- a/src/commands/version/mod.rs +++ b/src/commands/version/mod.rs @@ -85,3 +85,19 @@ impl VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> { } impl CommonCodec for VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]> {} + +pub struct FromVersionNumberEntryError; + +impl TryFrom> for SpdmVersion { + type Error = FromVersionNumberEntryError; + + fn try_from(value: VersionNumberEntry<[u8; VERSION_ENTRY_SIZE]>) -> Result { + match (value.major(), value.minor()) { + (1, 0) => Ok(SpdmVersion::V10), + (1, 1) => Ok(SpdmVersion::V11), + (1, 2) => Ok(SpdmVersion::V12), + (1, 3) => Ok(SpdmVersion::V13), + (_, _) => Err(FromVersionNumberEntryError), + } + } +} diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index dfecd05..6950f20 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -2,6 +2,7 @@ use crate::{ codec::{Codec, MessageBuf}, + commands::version::VersionNumberEntry, context::SpdmContext, error::{CommandError, CommandResult}, protocol::SpdmMsgHdr, @@ -31,17 +32,24 @@ pub(crate) fn send_get_version<'a>( Ok(()) } -// Requester function for processing a VERSION response +/// Requester function for processing a VERSION response +/// +/// Updates the state of the context to match the selected version. +/// +/// # Returns +/// - The selected latest common supported version on success +/// - [CommandError::UnsupportedResponse] when no common version is found +/// - [CommandError::Codec] when decoding fails fn process_version<'a>( ctx: &mut SpdmContext<'a>, spdm_hdr: SpdmMsgHdr, resp_payload: &mut MessageBuf<'a>, -) -> CommandResult<()> { +) -> CommandResult { // VERSION response must use version 1.0 per spec match spdm_hdr.version() { Ok(SpdmVersion::V10) => {} _ => { - Err(ctx.generate_error_response(resp_payload, ErrorCode::VersionMismatch, 0, None))?; + Err((false, CommandError::UnsupportedResponse))?; } } @@ -57,7 +65,27 @@ fn process_version<'a>( } // Decode all version entries from the response - Ok(()) + let mut latest_version = None; + for _ in 0..entry_count { + let ver = VersionNumberEntry::decode(resp_payload) + .map_err(|e| (false, CommandError::Codec(e)))?; + if let Ok(ver) = SpdmVersion::try_from(ver) { + if let Some(lv) = latest_version.as_mut() { + if *lv < ver { + *lv = ver; + } + } else { + latest_version = Some(ver); + } + } + } + + if let Some(ver) = latest_version { + ctx.state.connection_info.set_version_number(ver); + Ok(ver) + } else { + Err((false, CommandError::UnsupportedResponse)) + } } /// Requester function handling the parsing of the VERSION response sent by the Responder. diff --git a/src/context.rs b/src/context.rs index f050978..0faed8c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -207,7 +207,6 @@ impl<'a> SpdmContext<'a> { match resp_code { ReqRespCode::Version => handle_version_response(self, resp_msg_header, resp)?, - _ => Err((false, CommandError::UnsupportedResponse))?, ReqRespCode::Capabilities => handle_capabilities_response(self, resp_msg_header, resp)?, _ => Err((false, CommandError::UnsupportedResponse))?, } From 8bc95baa35998cad62329b2127ea326c890d2861 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 8 Jan 2026 16:49:45 +0100 Subject: [PATCH 10/86] example/requester: add version and capability handling Signed-off-by: leongross --- examples/spdm_requester.rs | 103 +++++++++------- src/commands/capabilities/mod.rs | 65 ++++++++-- src/commands/capabilities/request.rs | 166 +++++++++++++++++++++++++- src/commands/capabilities/response.rs | 7 ++ src/commands/version/mod.rs | 8 +- src/commands/version/request.rs | 22 +++- src/context.rs | 35 +++--- 7 files changed, 334 insertions(+), 72 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index ecf0919..22ed463 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -8,19 +8,27 @@ use std::net::{TcpListener, TcpStream}; use std::process; use spdm_lib::codec::MessageBuf; +use spdm_lib::commands::capabilities::{ + GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12, +}; use spdm_lib::context::SpdmContext; +use spdm_lib::error::{SpdmError, SpdmResult}; use spdm_lib::protocol::algorithms::{ AeadCipherSuite, AlgorithmPriorityTable, BaseAsymAlgo, BaseHashAlgo, DeviceAlgorithms, DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, }; -use spdm_lib::protocol::version::SpdmVersion; +use spdm_lib::protocol::version; use spdm_lib::protocol::ReqRespCode; use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; + // Import platform implementations - no duplicates! mod platform; use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; +use spdm_lib::commands::capabilities::request::generate_capabilities_request_local; +use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; + /// Responder configuration #[derive(Debug, Clone)] struct ResponderConfig { @@ -111,6 +119,7 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { // Connect to SPDM Responder fn handle_spdm_responder(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { let mut transport = SpdmSocketTransport::new(stream); + const EID: u8 = 0; // Create platform implementations - all from platform module! let mut hash = Sha384Hash::new(); @@ -121,7 +130,7 @@ fn handle_spdm_responder(stream: TcpStream, config: &ResponderConfig) -> IoResul let evidence = DemoEvidence::new(); // Create SPDM context - let supported_versions = [SpdmVersion::V12, SpdmVersion::V11]; + let supported_versions = [version::SpdmVersion::V12, version::SpdmVersion::V11]; let capabilities = create_device_capabilities(); let algorithms = create_local_algorithms(); @@ -170,44 +179,58 @@ fn handle_spdm_responder(stream: TcpStream, config: &ResponderConfig) -> IoResul // 2.3 Update tracking of remote party // 3.1 Send GET_AUTH - // For now, the send_request function uses default values for the commands payloads. - let ret = spdm_context.send_request(ReqRespCode::GetVersion, &mut message_buffer); - // .map_err(|_| Error::new(ErrorKind::Other, "could not process error"))?; - dbg!(&ret); - - spdm_context.requester_process_message(&mut message_buffer); - dbg!(&ret); - - // let ret = spdm_context.send_request(req, &mut message_buffer); - - // spdm_context - // . - - // match spdm_context.process_message(&mut message_buffer) { - // Ok(()) => { - // if config.verbose { - // println!("Successfully processed SPDM message"); - // } - // } - // Err(e) => { - // if config.verbose { - // eprintln!("Error processing SPDM message: {:?}", e); - // } - // // Continue processing unless it's a fatal transport error - // match &e { - // spdm_lib::error::SpdmError::Transport(_) => { - // if config.verbose { - // println!("Connection closed gracefully"); - // } - // break; - // } - // _ => { - // // Log error but continue processing - // continue; - // } - // } - // } - // } + // 1.1 Send GET_VERSION + generate_get_version( + &mut spdm_context, + &mut message_buffer, + VersionReqPayload::new(1, 1), + ) + .map_err(|(_send_response, cmd_err)| SpdmError::Command(cmd_err)) + .unwrap(); + + if config.verbose { + println!("GET_VERSION: {:?}", &message_buffer.message_data()); + } + + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + + // 1.2 Receive and verify VERSION + // 1.3 is done by the requester_process_message call + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + + if config.verbose { + println!("Sent GET_VERSION: {:?}", &message_buffer.message_data()); + } + + // 2.1 Send GET_CAPABILITIES + message_buffer.reset(); + generate_capabilities_request_local(&mut spdm_context, &mut message_buffer).unwrap(); + + if config.verbose { + println!("GET_CAPABILITIES: {:?}", &message_buffer.message_data()); + } + + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + + if config.verbose { + println!( + "Sent GET_CAPABILITIES: {:?}", + &message_buffer.message_data() + ); + } + + // 2.2 Receive and verify CAPABILITIES + // 2.3 is done by the requester_process_message call + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + message_buffer.reset(); Ok(()) } diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs index a3ddc73..c7b9898 100644 --- a/src/commands/capabilities/mod.rs +++ b/src/commands/capabilities/mod.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license -mod request; -mod response; +pub mod request; +pub mod response; pub(crate) use request::*; pub(crate) use response::*; @@ -13,9 +13,11 @@ use crate::{ protocol::{CapabilityFlags, EpInfoCapability, PskCapability, SpdmVersion}, }; +use crate::protocol::capabilities::DeviceCapabilities; + #[derive(IntoBytes, FromBytes, Immutable, Default)] #[repr(C)] -pub(crate) struct GetCapabilitiesBase { +pub struct GetCapabilitiesBase { param1: u8, param2: u8, } @@ -25,11 +27,23 @@ impl CommonCodec for GetCapabilitiesBase {} #[derive(IntoBytes, FromBytes, Immutable, Default)] #[repr(C, packed)] #[allow(dead_code)] -pub(crate) struct GetCapabilitiesV11 { +pub struct GetCapabilitiesV11 { + /// Reserved. reserved: u8, - ct_exponent: u8, + + /// Shall be exponent of base 2, which is used to calculate CT . + /// The equation for CT shall be 2^{CTExponent} microseconds (μs). + /// # Example + /// CT=10 -> 2^10 = 1024 μs = 1.024 ms + pub ct_exponent: u8, + + /// Reserved. reserved2: u8, + + /// Reserved. reserved3: u8, + + /// Capability flags. flags: CapabilityFlags, } @@ -47,16 +61,53 @@ impl GetCapabilitiesV11 { impl CommonCodec for GetCapabilitiesV11 {} +/// DSP0274, Table 11 #[derive(IntoBytes, FromBytes, Immutable)] #[repr(C, packed)] -pub(crate) struct GetCapabilitiesV12 { +pub struct GetCapabilitiesV12 { + /// This field shall indicate the maximum buffer size, in + /// bytes, of the Requester for receiving a single and + /// complete SPDM message whose message size is less + /// than or equal to the value in this field. data_transfer_size: u32, + + /// If the Requester supports the Large SPDM message + /// transfer mechanism, this field shall indicate the + /// maximum size, in bytes, of the internal buffer of a + /// Requester used to reassemble a single and complete + /// Large SPDM message. max_spdm_msg_size: u32, } impl CommonCodec for GetCapabilitiesV12 {} -fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { +/// Although [GetCapabilitiesBase], [GetCapabilitiesV11] and [GetCapabilitiesV12] +/// are more generic, the context currently uses [crate::protocol::capabilities::DeviceCapabilities]. +/// Until we refactor the context, this function translates from one to the other. +impl From<&DeviceCapabilities> for GetCapabilitiesV11 { + fn from(dev_cap: &DeviceCapabilities) -> Self { + Self::new(dev_cap.ct_exponent, dev_cap.flags) + } +} + +impl From<&DeviceCapabilities> for GetCapabilitiesV12 { + fn from(dev_cap: &DeviceCapabilities) -> Self { + Self { + data_transfer_size: dev_cap.data_transfer_size, + max_spdm_msg_size: dev_cap.max_spdm_msg_size, + } + } +} + +/// Checks if the request capability flags are compatible with the SPDM version +///# Arguments +/// - `version`: SPDM version +/// - `flags`: Capability flags from the request +/// +/// # Returns +/// - true if compatible +/// - false if not compatible +pub(crate) fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { // Checks common to 1.1 and higher if version >= SpdmVersion::V11 { // Illegal to return reserved values (2 and 3) diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 268905c..7092477 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -1,12 +1,176 @@ // Licensed under the Apache-2.0 license +use crate::commands::error_rsp::ErrorCode; use crate::{codec::MessageBuf, context::SpdmContext, error::CommandResult, protocol::SpdmMsgHdr}; +use crate::commands::capabilities::{ + req_flag_compatible, CapabilityFlags, GetCapabilitiesBase, GetCapabilitiesV11, + GetCapabilitiesV12, +}; +use crate::protocol::{ReqRespCode, SpdmVersion}; + +use crate::error::{CommandError, SpdmError}; +use crate::transcript::TranscriptContext; + +use crate::codec::Codec; + +/// Generate the GET_CAPABILITIES command with all the contexts information +// pub fn send_get_capabilities<'a>( +// ctx: &mut SpdmContext<'a>, +// req_buf: &mut MessageBuf<'a>, +// payload: +// ) -> CommandResult<()> { +// todo!(); +// } + /// Requester function handling the parsing of the CAPABILITIES response sent by the Responder. +/// +/// # Returns +/// - () on success +/// +/// #TODO +/// - [ ] A Responder can report that it needs to transmit the response in smaller +/// transfers by sending an ERROR message of ErrorCode=LargeResponse +/// - [ ] Update the context with the negotiated capabilities? Or where should we store them? pub(crate) fn handle_capabilities_response<'a>( ctx: &mut SpdmContext<'a>, resp_header: SpdmMsgHdr, resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { - todo!(); + let version_hdr = match resp_header.version() { + Ok(v) => v, + Err(_) => Err(ctx.generate_error_response(resp, ErrorCode::VersionMismatch, 0, None))?, + }; + + // Verify that the version is supported by both parties + let version = match ctx.supported_versions.iter().find(|&&v| v == version_hdr) { + Some(&v) => v, + None => Err(ctx.generate_error_response(resp, ErrorCode::VersionMismatch, 0, None))?, + }; + + let base_resp = GetCapabilitiesBase::decode(resp) + .map_err(|_| ctx.generate_error_response(resp, ErrorCode::OperationFailed, 0, None))?; + + // Based on the negotiated version, try to decode the rest of the response. + // If the response misses expected fields, return an error. + // See src/commands/capabilities/response.rs for more details. + + if version > SpdmVersion::V10 { + let resp_11 = GetCapabilitiesV11::decode(resp) + .map_err(|_| ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; + + let flags = resp_11.flags; + if !req_flag_compatible(version, &flags) { + Err(ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; + } + + if version >= SpdmVersion::V12 { + let _resp_12 = GetCapabilitiesV12::decode(resp).map_err(|_| { + ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None) + })?; + } + } + + Ok(()) +} + +/// Generate the GET_CAPABILITIES command with all the contexts information. +/// +/// # Arguments +/// - `ctx`: The SPDM context +/// - `req_buf`: The buffer to write the request into +/// - `capabilities`: The base capabilities +/// - `capv11`: The V1.1 capabilities (if applicable) +/// - `capv12`: The V1.2 capabilities (if applicable) +/// +/// # Returns +/// - () on success +/// - [CommandError::BufferTooSmall] when the provided buffer is too small +/// +fn generate_capabilities_request<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + capabilities: GetCapabilitiesBase, + capv11: Option, + capv12: Option, +) -> CommandResult<()> { + // Fill SpdmHeader first + let ctx_version = ctx.state.connection_info.version_number(); + let spdm_req_hdr = SpdmMsgHdr::new(ctx_version, ReqRespCode::GetCapabilities); + let mut payload_len = spdm_req_hdr + .encode(req_buf) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + let req_common = capabilities; + payload_len += req_common + .encode(req_buf) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + // Ensure that only the appropriate capability fields based on version are included. + if ctx_version >= SpdmVersion::V11 { + if let Some(capv1) = &capv11 { + payload_len += capv1 + .encode(req_buf) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + } + } + + if ctx_version >= SpdmVersion::V12 { + // Versions 1.2 and higher include GetCapabilitiesV12 + if let Some(capv2) = &capv12 { + payload_len += capv2 + .encode(req_buf) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + } + } + + // Push data offset up by total payload length + req_buf + .push_data(payload_len) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + // Append response to VCA transcript + ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) +} + +/// Generate the GET_CAPABILITIES command using the local capabilities from the context. +/// # Arguments +/// - `ctx`: The SPDM context +/// - `req_buf`: The buffer to write the request into +/// # Returns +/// - () on success +/// - [CommandError::BufferTooSmall] when the provided buffer is too small +pub fn generate_capabilities_request_local<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, +) -> CommandResult<()> { + let local_capabilities = ctx.local_capabilities; + let capabilities = GetCapabilitiesBase::default(); + + let capv11 = Some(GetCapabilitiesV11::new( + local_capabilities.ct_exponent, + local_capabilities.flags, + )); + + let capv12 = if ctx.state.connection_info.version_number() >= SpdmVersion::V12 { + Some(GetCapabilitiesV12 { + data_transfer_size: local_capabilities.data_transfer_size, + max_spdm_msg_size: local_capabilities.max_spdm_msg_size, + }) + } else { + None + }; + + generate_capabilities_request(ctx, req_buf, capabilities, capv11, capv12) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[ignore] + fn test_generate_capabilities_request() { + todo!(); + } } diff --git a/src/commands/capabilities/response.rs b/src/commands/capabilities/response.rs index 60b11f9..217f804 100644 --- a/src/commands/capabilities/response.rs +++ b/src/commands/capabilities/response.rs @@ -75,6 +75,13 @@ fn process_get_capabilities<'a>( if flags.chunk_cap() == 0 && data_transfer_size != max_spdm_msg_size { Err(ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None))?; } + + // If the GET_CAPABILITIES request sets Bit 0 of Param1 to a value of 1 and the + // Responder does not support the Large SPDM message transfer mechanism ( CHUNK_CAP=0 ), + // the Responder shall send an ERROR message of ErrorCode=InvalidRequest + if base_req.param1 & 0b00000001 != 0 && flags.chunk_cap() == 0 { + Err(ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None))?; + } } if version >= SpdmVersion::V11 { diff --git a/src/commands/version/mod.rs b/src/commands/version/mod.rs index 1870f43..5bbe95a 100644 --- a/src/commands/version/mod.rs +++ b/src/commands/version/mod.rs @@ -1,6 +1,6 @@ // Licensed under the Apache-2.0 license -mod request; -mod response; +pub mod request; +pub mod response; pub(crate) use request::*; pub(crate) use response::*; @@ -16,13 +16,13 @@ const VERSION_ENTRY_SIZE: usize = 2; #[allow(dead_code)] #[derive(FromBytes, IntoBytes, Immutable)] -pub(crate) struct VersionReqPayload { +pub struct VersionReqPayload { param1: u8, param2: u8, } impl VersionReqPayload { - pub(crate) fn new(param1: u8, param2: u8) -> Self { + pub fn new(param1: u8, param2: u8) -> Self { Self { param1, param2 } } } diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 6950f20..33e1519 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -6,6 +6,7 @@ use crate::{ context::SpdmContext, error::{CommandError, CommandResult}, protocol::SpdmMsgHdr, + state::ConnectionState, transcript::TranscriptContext, }; @@ -14,8 +15,8 @@ use crate::commands::version::{VersionReqPayload, VersionRespCommon}; use crate::protocol::SpdmVersion; -// Generate the GET_VERSION command with all the contexts information -pub(crate) fn send_get_version<'a>( +/// Generate the GET_VERSION command with all the contexts information +pub fn generate_get_version<'a>( ctx: &mut SpdmContext<'a>, req_buf: &mut MessageBuf<'a>, payload: VersionReqPayload, @@ -94,7 +95,22 @@ pub(crate) fn handle_version_response<'a>( resp_header: SpdmMsgHdr, resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { - todo!(); + // Verify state is correct for VERSION response + if ctx.state.connection_info.state() != ConnectionState::NotStarted { + Err((false, CommandError::UnsupportedResponse))?; + // TODO: is there a better error for this? + Err(ctx.generate_error_response(resp, ErrorCode::InvalidResponseCode, 0, None))?; + } + + // Process VERSION response and set context information + process_version(ctx, resp_header, resp)?; + + // Append to transcript + ctx.append_message_to_transcript(resp, TranscriptContext::Vca)?; + ctx.state + .connection_info + .set_state(ConnectionState::AfterVersion); + Ok(()) } // tests diff --git a/src/context.rs b/src/context.rs index 0faed8c..b32abec 100644 --- a/src/context.rs +++ b/src/context.rs @@ -68,9 +68,9 @@ impl<'a> SpdmContext<'a> { device_certs_store, measurements: SpdmMeasurements::default(), large_resp_context: LargeResponseCtx::default(), - hash: hash, - rng: rng, - evidence: evidence, + hash, + rng, + evidence, }) } @@ -110,33 +110,34 @@ impl<'a> SpdmContext<'a> { .receive_response(resp_buffer) .map_err(|e| SpdmError::Transport(e))?; - match self.requester_handle_response(resp_buffer) { + match self + .requester_handle_response(resp_buffer) + .map_err(|(rsp, cmd_err)| { + if rsp { + SpdmError::Command(cmd_err) + } else { + SpdmError::InvalidParam + } + }) { Ok(()) => {} - Err(_) => panic!("rip requester"), + Err(e) => { + return Err(e); + } } - Ok(()) } // Use ReqRespCode as command issuer for now, until the correct state machine is in place // TODO: implement in transport - pub fn send_request( + pub fn requester_send_request( &mut self, - req: ReqRespCode, req_buf: &mut MessageBuf<'a>, + dst_eid: u8, ) -> SpdmResult<()> { req_buf.reset(); - match req { - ReqRespCode::GetVersion => { - version::send_get_version(self, req_buf, version::VersionReqPayload::new(1, 1)) - .map_err(|(_send_response, cmd_err)| SpdmError::Command(cmd_err))? - } - _ => return Err(SpdmError::InvalidParam), - }; - self.transport - .send_request(1, req_buf) + .send_request(dst_eid, req_buf) .map_err(|_| SpdmError::InvalidParam)?; Ok(()) From 6492abd2e891c9261b297254605dc9307b6a815c Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 9 Jan 2026 17:11:24 +0100 Subject: [PATCH 11/86] add prelim AUTH generation (no response parsing) Signed-off-by: leongross --- examples/spdm_requester.rs | 35 +- src/commands/algorithms/mod.rs | 418 ++++++++++++++++++ src/commands/algorithms/request.rs | 125 ++++++ .../response.rs} | 143 +----- src/commands/capabilities/request.rs | 15 +- src/commands/challenge_auth_rsp.rs | 10 +- src/commands/measurements_rsp.rs | 55 +-- src/commands/mod.rs | 2 +- src/context.rs | 80 +++- 9 files changed, 713 insertions(+), 170 deletions(-) create mode 100644 src/commands/algorithms/mod.rs create mode 100644 src/commands/algorithms/request.rs rename src/commands/{algorithms_rsp.rs => algorithms/response.rs} (78%) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 22ed463..b0816f0 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -26,6 +26,9 @@ use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; mod platform; use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; +use spdm_lib::commands::algorithms::{ + request::generate_negotiate_algorithms_request, AlgStructure, ExtendedAlgo, +}; use spdm_lib::commands::capabilities::request::generate_capabilities_request_local; use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; @@ -116,8 +119,9 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { } } -// Connect to SPDM Responder -fn handle_spdm_responder(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { +// Perform a VCS flow (Version, Capabilities, Algorithms) +// using the real SPDM library processing with platform implementations. +fn vca_flow(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { let mut transport = SpdmSocketTransport::new(stream); const EID: u8 = 0; @@ -230,8 +234,33 @@ fn handle_spdm_responder(stream: TcpStream, config: &ResponderConfig) -> IoResul spdm_context .requester_process_message(&mut message_buffer) .unwrap(); + message_buffer.reset(); + // 3.1 Send GET_AUTH + // TODO: use local algorithms from the context + generate_negotiate_algorithms_request( + &mut spdm_context, + &mut message_buffer, + None, + None, + &AlgStructure(0), + None, + ) + .unwrap(); + + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + + if config.verbose { + println!("GET_AUTH: {:?}", &message_buffer.message_data()); + } + + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + Ok(()) } @@ -396,7 +425,7 @@ fn main() -> Result<(), Box> { } // Handle client with real SPDM processing using platform implementations - handle_spdm_responder(stream, &config)?; + vca_flow(stream, &config)?; Ok(()) } diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs new file mode 100644 index 0000000..6453c9c --- /dev/null +++ b/src/commands/algorithms/mod.rs @@ -0,0 +1,418 @@ +// Licensed under the Apache-2.0 license + +//! Commands related to SPDM Algorithms negotiation +//! See DMTF 0274 - SPDM Base Specification v1.3, Section 10.4 ff. +//! +//! The Algorithms negotiation is performed after the Capabilities exchange +//! and before any other commands that depend on the negotiated algorithms. +//! +//! This module contains the request (`NEGOTIATE_ALGORITHMS`) and response +//! (`ALGORITHMS`) handling and generation logic. + +pub mod request; +pub mod response; + +pub(crate) use request::*; +pub(crate) use response::*; + +use crate::codec::{CommonCodec, MessageBuf}; +use crate::protocol::SpdmVersion; +use bitfield::bitfield; +use core::mem::size_of; + +use crate::protocol::algorithms::{ + BaseAsymAlgo, BaseHashAlgo, MeasurementHashAlgo, MeasurementSpecification, MelSpecification, + OtherParamSupport, +}; + +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use crate::error::{SpdmError, SpdmResult}; + +// Max request length shall be 128 bytes (SPDM1.3 Table 10.4) +const MAX_SPDM_REQUEST_LENGTH: u16 = 128; +const MAX_SPDM_EXT_ALG_COUNT_V10: u8 = 8; +const MAX_SPDM_EXT_ALG_COUNT_V11: u8 = 20; +const MAX_SPDM_EXT_ALG_COUNT_V13: u8 = 20; + +#[derive(IntoBytes, FromBytes, Immutable, Default, Debug)] +#[repr(C, packed)] +/// This request message shall negotiate cryptographic algorithms. A Requester shall not issue a NEGOTIATE_ALGORITHMS +/// request message until it receives a successful CAPABILITIES response message. +/// +/// A Requester shall not issue any other SPDM requests, with the exception of GET_VERSION until it receives a successful +/// ALGORITHMS response message. +/// +/// This structure represents the NEGOTIATE_ALGORITHMS request message, **WITHOUT** the variable-length +/// algorithm structure tables and extended algorithm structures that follow this header, +/// namely +/// - ExtAsym (4 * A), see [ExtendedAlgo]. +/// - ExtHash (4 * E), see [ExtendedAlgo]. +/// - ReqAlgStruct (AlgStructSize), see [AlgStructure]. +struct NegotiateAlgorithmsReq { + /// The number of algorithm structure tables in this request using `ReqAlgStruct`. + num_alg_struct_tables: u8, + + /// Reserved. + param2: u8, + + /// The length of the entire request message, in bytes. Length shall be less + /// than or equal to 128 bytes. + length: u16, + + /// For each defined measurement specification a Requester supports, the + /// Requester can set the appropriate bits. + /// + /// See [MeasurementSpecification] for details. + measurement_specification: MeasurementSpecification, + + /// Bit mask listing other parameters supported by the Requester. + /// + /// See [OtherParamSupport] for details. + other_param_support: OtherParamSupport, + + /// Bit mask listing Requester-supported SPDM-enumerated asymmetric key signature + /// algorithms for the purpose of signature verification. If the Requester does + /// not support any request/ response pair that requires signature verification, + /// this value shall be set to zero. If the Requester will not send any requests + /// that require a signature, this value should be set to zero. + /// Let SigLen be the size of the signature in bytes. + /// + /// See [BaseAsymAlgo] for details. + base_asym_algo: BaseAsymAlgo, + + /// Bit mask listing Requester-supported SPDM-enumerated cryptographic hashing + /// algorithms. If the Requester does not support any request/response pair + /// that requires hashing operations, this value shall be set to zero. + /// + /// See [BaseHashAlgo] for details. + base_hash_algo: BaseHashAlgo, + + /// Reserved. + reserved_1: [u8; 12], + + /// The number of Requester-supported extended asymmetric key signature algorithms + /// (=A) for the purpose of signature verification. + /// A + E + ExtAlgCount2 + ExtAlgCount3 + ExtAlgCount4 + ExtAlgCount5 shall be + /// less than or equal to `20`. If the Requester does not support any request/response + /// pair that requires signature verification, this value shall be set to zero. + /// + /// See [MAX_SPDM_EXT_ALG_COUNT_V11], [MAX_SPDM_EXT_ALG_COUNT_V13]; + ext_asym_count: u8, + + /// Shall be the number of Requester-supported extended hashing algorithms (=E). + /// A + E + ExtAlgCount2 + ExtAlgCount3 + ExtAlgCount4 + ExtAlgCount5 shall be + /// less than or equal to `20`. If the Requester does not support any request/response + /// pair that requires hashing operations, this value shall be set to zero. + /// + /// See [MAX_SPDM_EXT_ALG_COUNT_V11], [MAX_SPDM_EXT_ALG_COUNT_V13]; + ext_hash_count: u8, + + /// Reserved. + reserved_2: u8, + + /// The Requester shall set the corresponding bit for each supported measurement + /// extension log (MEL) specification. + /// + /// See [MelSpecification] for details. + mel_specification: MelSpecification, +} + +impl NegotiateAlgorithmsReq { + /// Returns a new `NegotiateAlgorithmsReq` with the provided parameters. + /// IT does **NOT** include the variable-length algorithm structure tables and + /// extended algorithm structures that follow this header. + /// Although, the length field is calculated and set as if they were included, + /// to make it easier to later generate the full request and be compatible with + /// the SPDM specification description of the fields provided. + /// + /// It does *NOT* validate the total extended algorithm count against the SPDM version. + pub fn new( + num_alg_struct_tables: u8, + param2: u8, + measurement_specification: MeasurementSpecification, + other_param_support: OtherParamSupport, + base_asym_algo: BaseAsymAlgo, + base_hash_algo: BaseHashAlgo, + ext_asyn_count: u8, + ext_hash_count: u8, + mel_specification: MelSpecification, + ) -> SpdmResult { + let mut req = NegotiateAlgorithmsReq { + num_alg_struct_tables, + param2: param2, + length: 0, + measurement_specification, + other_param_support, + base_asym_algo, + base_hash_algo, + reserved_1: [0u8; 12], + ext_asym_count: ext_asyn_count, + ext_hash_count, + reserved_2: 0, + mel_specification, + }; + + req.length = req.min_req_len(); + + if req.length > MAX_SPDM_REQUEST_LENGTH { + return Err(SpdmError::InvalidParam); + } + + Ok(req) + } + + /// Calculate the minimum required length of the request based on the number of + /// algorithm structure tables and extended algorithm structures in bytes. + fn min_req_len(&self) -> u16 { + let total_alg_struct_len = size_of::() * self.num_alg_struct_tables as usize; + let total_ext_asym_len = size_of::() * self.ext_asym_count as usize; + let total_ext_hash_len = size_of::() * self.ext_hash_count as usize; + (size_of::() + + total_alg_struct_len + + total_ext_asym_len + + total_ext_hash_len) as u16 + } + + /// Calculate the size of the extended algorithm structures in bytes. + /// This includes both extended asymmetric and extended hash algorithms. + pub fn ext_algo_size(&self) -> usize { + let ext_algo_count = self.ext_asym_count as usize + self.ext_hash_count as usize; + size_of::() * ext_algo_count + } + + /// Validate that the total number of extended algorithms does not exceed + /// the maximum allowed for the given SPDM version. + /// + /// # Arguments + /// - `version`: The SPDM version in use. + /// - `total_ext_alg_count`: The total number of extended algorithms (A + E). + /// + /// # Returns + /// - `Ok(())` if the count is valid. + /// - `Err(SpdmError::InvalidParam)` if the count exceeds the maximum + pub fn validate_total_ext_alg_count( + &self, + version: SpdmVersion, + total_ext_alg_count: u8, + ) -> SpdmResult<()> { + if total_ext_alg_count + > match version { + SpdmVersion::V10 => MAX_SPDM_EXT_ALG_COUNT_V10, + SpdmVersion::V11 => MAX_SPDM_EXT_ALG_COUNT_V11, + SpdmVersion::V13 => MAX_SPDM_EXT_ALG_COUNT_V13, + _ => MAX_SPDM_EXT_ALG_COUNT_V11, + } + { + Err(SpdmError::InvalidParam) + } else { + Ok(()) + } + } +} + +impl CommonCodec for NegotiateAlgorithmsReq {} + +#[derive(IntoBytes, FromBytes, Immutable, Default)] +#[repr(C, packed)] +#[allow(dead_code)] +struct AlgorithmsResp { + num_alg_struct_tables: u8, + reserved_1: u8, + length: u16, + measurement_specification_sel: MeasurementSpecification, + other_params_selection: OtherParamSupport, + measurement_hash_algo: MeasurementHashAlgo, + base_asym_sel: BaseAsymAlgo, + base_hash_sel: BaseHashAlgo, + reserved_2: [u8; 11], + mel_specification_sel: MelSpecification, + ext_asym_sel_count: u8, + ext_hash_sel_count: u8, + reserved_3: [u8; 2], +} + +impl CommonCodec for AlgorithmsResp {} + +#[derive(IntoBytes, FromBytes, Immutable, Default)] +#[repr(C)] +pub struct ExtendedAlgo { + /// Shall represent the registry or standards body. + /// + /// See [RegistryId] for details. + registry_id: u8, + + /// Reserved. + reserved: u8, + + /// Shall indicate the desired algorithm. The registry or standards body owns + /// the value of this field. See [RegistryId]. At present, DMTF does not define + /// any algorithms for use in extended algorithms fields. + algorithm_id: u16, +} + +impl CommonCodec for ExtendedAlgo {} + +/// Registry or standards body ID for algorithm encoding in extended algorithm fields. +/// Consult the respective registry or standards body unless otherwise specified. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +pub enum RegistryId { + /// DMTF does not have a Vendor ID registry. + DMTF = 0x0, + + /// VendorID is identified by using TCG Vendor ID Registry. + /// For extended algorithms, see TCG Algorithm Registry. + TCG = 0x1, + + /// VendorID is identified by using the vendor ID assigned by USB. + USB = 0x2, + + /// VendorID is identified using PCI-SIG Vendor ID. + PCISIG = 0x3, + + /// The Private Enterprise Number (PEN) assigned by the Internet Assigned + /// Numbers Authority (IANA) identifies the vendor. + IANA = 0x4, + + /// VendorID is identified by using HDBaseT HDCD entity. + HDBASET = 0x5, + + /// The Manufacturer ID assigned by MIPI identifies the vendor. + MIPI = 0x6, + + /// VendorID is identified by using CXL vendor ID. + CXL = 0x7, + + /// VendorID is identified by using JEDEC vendor ID. + JEDEC = 0x8, + + /// For fields and formats defined by the VESA standards body, + /// there is no Vendor ID registry. + VESA = 0x9, + + /// The CBOR Tag Registry that identifies the format of the element, + /// as assigned by the Internet Assigned Numbers Authority (IANA). + /// The encoding of the CBOR tag indicates the length of the tag. + /// When a CBOR Tag is used with a standards body or vendor-defined header, + /// the VendorIDLen field shall be set to the length of the encoded CBOR tag, + /// followed by the data payload, which starts with an encoded CBOR tag. + IANACBOR = 0xA, +} + +impl RegistryId { + /// Returns the vendor ID length in bytes for this registry. + /// Returns None for variable-length registries (IANA CBOR). + pub const fn vendor_id_length(&self) -> Option { + match self { + Self::DMTF => Some(0), + Self::TCG => Some(2), + Self::USB => Some(2), + Self::PCISIG => Some(2), + Self::IANA => Some(4), + Self::HDBASET => Some(4), + Self::MIPI => Some(2), + Self::CXL => Some(2), + Self::JEDEC => Some(2), + Self::VESA => Some(0), + Self::IANACBOR => None, // Variable length + } + } +} + +#[derive(Debug, Clone, Copy)] +enum AlgType { + // 0x00 and 0x01. Reserved. + Dhe = 2, + AeadCipherSuite = 3, + ReqBaseAsymAlg = 4, + KeySchedule = 5, +} + +impl TryFrom for AlgType { + type Error = SpdmError; + + fn try_from(value: u8) -> Result { + match value { + 2 => Ok(AlgType::Dhe), + 3 => Ok(AlgType::AeadCipherSuite), + 4 => Ok(AlgType::ReqBaseAsymAlg), + 5 => Ok(AlgType::KeySchedule), + _ => Err(SpdmError::InvalidParam), + } + } +} + +#[derive(Debug, Clone, Copy, IntoBytes, FromBytes, Immutable, Default)] +#[repr(C, packed)] +/// Shall be the Requester-supported fixed algorithms. +pub struct AlgCount(u8); + +impl AlgCount { + pub fn get(&self) -> u8 { + self.0 + } + + pub fn set(&mut self, count: u8) { + self.0 = count; + } + + /// Number of bytes required to describe Requester-supported SPDM-enumerated + /// fixed algorithms (=FixedAlgCount). FixedAlgCount + 2 shall be a multiple of 4. + pub fn num_(&self) -> u8 { + self.0 & 0b11111000 + } + + /// Number of Requester-supported extended algorithms (= ExtAlgCount ). + pub fn rum_req_supported_algos(&self) -> u8 { + self.0 & 0b00000111 + } +} + +impl From for AlgCount { + fn from(value: u8) -> Self { + Self(value) + } +} + +impl CommonCodec for AlgCount {} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] + #[repr(C)] + /// This structure describes an algorithm structure table. + /// It does **NOT** include the variable-length `AlgExternal` fields that follow this header. + /// + /// The `AlgExternal` fields are of type [ExtendedAlgo] and their number is defined + /// by the `ExtAlgCount` field. + /// The existence of `AlgExternal` is optional. + // TODO: make this a structure with Option? + pub struct AlgStructure(u32); + impl Debug; + u8; + /// Shall be the type of algorithm. + /// + /// See [AlgType] for details. + pub alg_type, set_alg_type: 7, 0; + + /// Shall be the bit mask listing Responder-supported fixed algorithm requested by the Requester. + /// This value shall be either 0 or 1. + /// That means, that there is either 1 or None external algorithm structure following this header. + pub ext_alg_count, set_ext_alg_count: 11, 8; + pub fixed_alg_count, set_fixed_alg_count: 15, 12; + u16; + /// Shall be the bit mask for indicating a Requester- supported, Responder-selected,\ + /// SPDM-enumerated algorithm. Responder shall set at most one bit to 1. + /// + /// Size: `FixedAlgCount` bytes, retrieved from `fixed_alg_count()` method. + pub alg_supported, set_alg_supported: 31, 16; +} + +impl AlgStructure { + pub fn is_aligned(&self) -> bool { + self.fixed_alg_count() + 2 % 4 == 0 + } +} + +impl CommonCodec for AlgStructure {} diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs new file mode 100644 index 0000000..5f3b81a --- /dev/null +++ b/src/commands/algorithms/request.rs @@ -0,0 +1,125 @@ +// Licensed under the Apache-2.0 license + +use crate::{ + codec::{Codec, MessageBuf}, + commands::algorithms::{AlgStructure, ExtendedAlgo, NegotiateAlgorithmsReq}, + context::SpdmContext, + error::{CommandError, CommandResult, SpdmError}, + protocol::{ + BaseAsymAlgo, BaseAsymAlgoType, BaseHashAlgo, BaseHashAlgoType, MeasurementSpecification, + OtherParamSupport, SpdmMsgHdr, SpdmVersion, + }, +}; + +/// Parse and handle the NEGOTIATE_ALGORITHMS response from the Responder. +pub fn handle_algorithms_response<'a>( + ctx: &mut SpdmContext<'a>, + resp_header: SpdmMsgHdr, + resp: &mut MessageBuf<'a>, +) -> CommandResult<()> { + Ok(()) +} + +/// Generate the NEGOTIATE_ALGORITHMS request with all the contexts local information. +/// +/// # Arguments +/// - `ctx` - The SPDM context containing local algorithm information. +/// - `req_buf` - The message buffer to encode the request into. +/// - `ext_asym` - Optional slice of extended asymmetric algorithm types. +/// - `ext_hash` - Optional slice of extended hash algorithm types. +/// - `ext_algo` - The AlgStructure variable fields. +/// - `ext_algo_ext` - Optional extended algorithm structure. +/// +/// # Returns +/// - `Ok(())` on success. +/// - `Err(CommandError)` on failure. +pub fn generate_negotiate_algorithms_request<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + ext_asym: Option<&'a [ExtendedAlgo]>, + ext_hash: Option<&'a [ExtendedAlgo]>, + ext_algo: &AlgStructure, + ext_algo_ext: Option, +) -> CommandResult<()> { + let local_algorithms = &ctx.local_algorithms.device_algorithms; + let local_state = &ctx.state.connection_info; + + let ext_asym_count = match ext_asym { + Some(ext) => ext.len() as u8, + None => 0, + }; + let ext_hash_count = match ext_hash { + Some(ext) => ext.len() as u8, + None => 0, + }; + + let negotiate_algorithms_req = NegotiateAlgorithmsReq::new( + ext_algo.ext_alg_count(), + 0, // param2 + local_algorithms.measurement_spec, + local_algorithms.other_param_support, + local_algorithms.base_asym_algo, + local_algorithms.base_hash_algo, + ext_asym_count, + ext_hash_count, + local_algorithms.mel_specification, + ) + .map_err(|_| (false, CommandError::UnsupportedRequest))?; + + // Verify that the extended algorithm counts are valid + negotiate_algorithms_req + .validate_total_ext_alg_count( + local_state.version_number(), + ext_asym_count + ext_hash_count, + ) + .map_err(|_| (false, CommandError::UnsupportedRequest))?; + + // Ensure the request buffer is large enough + if (negotiate_algorithms_req.min_req_len() as usize) < req_buf.capacity() { + return Err((false, CommandError::BufferTooSmall)); + } + + // This encoding does *NOT* yet contain the variable fields. + negotiate_algorithms_req + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // Add variable fields if any. As defined by the size of the struct and the + // PLMD spec, we know that the offset starts at 32 bytes. + // The constructor of NegotiateAlgorithmsReq sets the structs length correctly. + if let Some(ext_asym_algos) = ext_asym { + for ext in ext_asym_algos { + ext.encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + } + + if let Some(ext_hash_algos) = ext_hash { + for ext in ext_hash_algos { + ext.encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + } + + if !ext_algo.is_aligned() { + return Err((false, CommandError::UnsupportedRequest)); + } + + // If this is 1, we have an additional extended algorithm structure to add. + if negotiate_algorithms_req.num_alg_struct_tables > 0 { + ext_algo + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + if let Some(ext) = ext_algo_ext { + ext.encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + } else { + // If ext_alg_count > 0, we must have the extended algorithm structure. + // TODO: fixup AlgStructure with downside of custom encode. + return Err((false, CommandError::UnsupportedRequest)); + } + } + + Ok(()) +} diff --git a/src/commands/algorithms_rsp.rs b/src/commands/algorithms/response.rs similarity index 78% rename from src/commands/algorithms_rsp.rs rename to src/commands/algorithms/response.rs index fe4891b..5e65186 100644 --- a/src/commands/algorithms_rsp.rs +++ b/src/commands/algorithms/response.rs @@ -1,140 +1,19 @@ // Licensed under the Apache-2.0 license -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::{ + codec::{Codec, CommonCodec, MessageBuf}, + commands::algorithms::*, + context::SpdmContext, + error::{CommandError, CommandResult, SpdmError}, + protocol::{SpdmMsgHdr, SpdmVersion}, + state::ConnectionState, + transcript::TranscriptContext, +}; + use crate::commands::error_rsp::ErrorCode; -use crate::context::SpdmContext; -use crate::error::{CommandResult, SpdmError}; use crate::protocol::*; -use crate::state::ConnectionState; -use crate::transcript::TranscriptContext; -use bitfield::bitfield; -use core::mem::size_of; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -// Max request length shall be 128 bytes (SPDM1.3 Table 10.4) -const MAX_SPDM_REQUEST_LENGTH: u16 = 128; -const MAX_SPDM_EXT_ALG_COUNT_V10: u8 = 8; -const MAX_SPDM_EXT_ALG_COUNT_V11: u8 = 20; - -#[derive(IntoBytes, FromBytes, Immutable, Default, Debug)] -#[repr(C, packed)] -struct NegotiateAlgorithmsReq { - num_alg_struct_tables: u8, - param2: u8, - length: u16, - measurement_specification: MeasurementSpecification, - other_param_support: OtherParamSupport, - base_asym_algo: BaseAsymAlgo, - base_hash_algo: BaseHashAlgo, - reserved_1: [u8; 12], - ext_asyn_count: u8, - ext_hash_count: u8, - reserved_2: u8, - mel_specification: MelSpecification, -} - -impl NegotiateAlgorithmsReq { - fn min_req_len(&self) -> u16 { - let total_alg_struct_len = size_of::() * self.num_alg_struct_tables as usize; - let total_ext_asym_len = size_of::() * self.ext_asyn_count as usize; - let total_ext_hash_len = size_of::() * self.ext_hash_count as usize; - (size_of::() - + total_alg_struct_len - + total_ext_asym_len - + total_ext_hash_len) as u16 - } - - fn ext_algo_size(&self) -> usize { - let ext_algo_count = self.ext_asyn_count as usize + self.ext_hash_count as usize; - size_of::() * ext_algo_count - } - - fn validate_total_ext_alg_count( - &self, - version: SpdmVersion, - total_ext_alg_count: u8, - ) -> Result<(), SpdmError> { - let max_count = match version { - SpdmVersion::V10 => MAX_SPDM_EXT_ALG_COUNT_V10, - _ => MAX_SPDM_EXT_ALG_COUNT_V11, - }; - - if total_ext_alg_count > max_count { - Err(SpdmError::InvalidParam) - } else { - Ok(()) - } - } -} - -impl CommonCodec for NegotiateAlgorithmsReq {} - -#[derive(IntoBytes, FromBytes, Immutable, Default)] -#[repr(C, packed)] -#[allow(dead_code)] -struct AlgorithmsResp { - num_alg_struct_tables: u8, - reserved_1: u8, - length: u16, - measurement_specification_sel: MeasurementSpecification, - other_params_selection: OtherParamSupport, - measurement_hash_algo: MeasurementHashAlgo, - base_asym_sel: BaseAsymAlgo, - base_hash_sel: BaseHashAlgo, - reserved_2: [u8; 11], - mel_specification_sel: MelSpecification, - ext_asym_sel_count: u8, - ext_hash_sel_count: u8, - reserved_3: [u8; 2], -} -impl CommonCodec for AlgorithmsResp {} - -#[derive(IntoBytes, FromBytes, Immutable, Default)] -#[repr(C)] -struct ExtendedAlgo { - registry_id: u8, - reserved: u8, - algorithm_id: u16, -} - -impl CommonCodec for ExtendedAlgo {} - -#[derive(Debug, Clone, Copy)] -enum AlgType { - Dhe = 2, - AeadCipherSuite = 3, - ReqBaseAsymAlg = 4, - KeySchedule = 5, -} -impl TryFrom for AlgType { - type Error = SpdmError; - - fn try_from(value: u8) -> Result { - match value { - 2 => Ok(AlgType::Dhe), - 3 => Ok(AlgType::AeadCipherSuite), - 4 => Ok(AlgType::ReqBaseAsymAlg), - 5 => Ok(AlgType::KeySchedule), - _ => Err(SpdmError::InvalidParam), - } - } -} - -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] - #[repr(C)] - pub struct AlgStructure(u32); - impl Debug; - u8; - pub alg_type, set_alg_type: 7, 0; - pub ext_alg_count, set_ext_alg_count: 11, 8; - pub fixed_alg_count, set_fixed_alg_count: 15, 12; - u16; - pub alg_supported, set_alg_supported: 31, 16; -} - -impl CommonCodec for AlgStructure {} +use core::mem::size_of; pub(crate) fn selected_measurement_specification(ctx: &SpdmContext) -> MeasurementSpecification { let local_cap_flags = &ctx.local_capabilities.flags; diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 7092477..7ac0b2e 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -7,7 +7,7 @@ use crate::commands::capabilities::{ req_flag_compatible, CapabilityFlags, GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12, }; -use crate::protocol::{ReqRespCode, SpdmVersion}; +use crate::protocol::{capabilities::DeviceCapabilities, ReqRespCode, SpdmVersion}; use crate::error::{CommandError, SpdmError}; use crate::transcript::TranscriptContext; @@ -31,7 +31,6 @@ use crate::codec::Codec; /// #TODO /// - [ ] A Responder can report that it needs to transmit the response in smaller /// transfers by sending an ERROR message of ErrorCode=LargeResponse -/// - [ ] Update the context with the negotiated capabilities? Or where should we store them? pub(crate) fn handle_capabilities_response<'a>( ctx: &mut SpdmContext<'a>, resp_header: SpdmMsgHdr, @@ -55,22 +54,32 @@ pub(crate) fn handle_capabilities_response<'a>( // If the response misses expected fields, return an error. // See src/commands/capabilities/response.rs for more details. + let mut peer_capabilities = DeviceCapabilities::default(); + if version > SpdmVersion::V10 { let resp_11 = GetCapabilitiesV11::decode(resp) .map_err(|_| ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; + peer_capabilities.ct_exponent = resp_11.ct_exponent; let flags = resp_11.flags; if !req_flag_compatible(version, &flags) { Err(ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; } + peer_capabilities.flags = resp_11.flags; if version >= SpdmVersion::V12 { - let _resp_12 = GetCapabilitiesV12::decode(resp).map_err(|_| { + let resp_12 = GetCapabilitiesV12::decode(resp).map_err(|_| { ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None) })?; + peer_capabilities.data_transfer_size = resp_12.data_transfer_size; + peer_capabilities.max_spdm_msg_size = resp_12.max_spdm_msg_size; } } + ctx.state + .connection_info + .set_peer_capabilities(peer_capabilities); + Ok(()) } diff --git a/src/commands/challenge_auth_rsp.rs b/src/commands/challenge_auth_rsp.rs index 5013a0c..3e3134d 100644 --- a/src/commands/challenge_auth_rsp.rs +++ b/src/commands/challenge_auth_rsp.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license use crate::cert_store::MAX_CERT_SLOTS_SUPPORTED; use crate::codec::{Codec, CommonCodec, MessageBuf}; -use crate::commands::algorithms_rsp::selected_measurement_specification; +use crate::commands::algorithms::selected_measurement_specification; use crate::commands::digests_rsp::compute_cert_chain_hash; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; @@ -210,7 +210,13 @@ fn encode_measurement_summary_hash<'a>( ) -> CommandResult { let mut meas_summary_hash = [0u8; SHA384_HASH_SIZE]; ctx.measurements - .measurement_summary_hash(ctx.evidence, ctx.hash, asym_algo, meas_summary_hash_type, &mut meas_summary_hash) + .measurement_summary_hash( + ctx.evidence, + ctx.hash, + asym_algo, + meas_summary_hash_type, + &mut meas_summary_hash, + ) .map_err(|e| (false, CommandError::Measurement(e)))?; let hash_len = meas_summary_hash.len(); diff --git a/src/commands/measurements_rsp.rs b/src/commands/measurements_rsp.rs index a59f15a..4d8fdbb 100644 --- a/src/commands/measurements_rsp.rs +++ b/src/commands/measurements_rsp.rs @@ -3,19 +3,19 @@ use crate::cert_store::SpdmCertStore; use crate::chunk_ctx::{ChunkError, LargeResponse}; use crate::codec::{encode_u8_slice, Codec, CommonCodec, MessageBuf}; -use crate::commands::algorithms_rsp::selected_measurement_specification; +use crate::commands::algorithms::selected_measurement_specification; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult, PlatformError}; use crate::measurements::common::{ MeasurementChangeStatus, MeasurementsError, SpdmMeasurements, SPDM_MAX_MEASUREMENT_RECORD_SIZE, }; +use crate::platform::evidence::SpdmEvidence; +use crate::platform::hash::SpdmHash; +use crate::platform::rng::SpdmRng; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::{TranscriptContext, TranscriptManager}; -use crate::platform::hash::SpdmHash; -use crate::platform::rng::SpdmRng; -use crate::platform::evidence::SpdmEvidence; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -121,7 +121,12 @@ impl MeasurementsResponse { let raw_bitstream_requested = self.req_attr.raw_bitstream_requested() == 1; let measurement_record_len = measurements - .measurement_block_size(evidence, self.asym_algo, self.meas_op, raw_bitstream_requested) + .measurement_block_size( + evidence, + self.asym_algo, + self.meas_op, + raw_bitstream_requested, + ) .map_err(|e| (false, CommandError::Measurement(e)))?; // Fill the chunk buffer with the appropriate response sections // Instead of a while loop, use a single-pass approach for clarity and efficiency. @@ -201,8 +206,7 @@ impl MeasurementsResponse { ) -> CommandResult<[u8; RESPONSE_FIXED_FIELDS_SIZE]> { let mut fixed_rsp_fields = [0u8; RESPONSE_FIXED_FIELDS_SIZE]; let mut fixed_rsp_buf = MessageBuf::new(&mut fixed_rsp_fields); - _ = self - .encode_response_fixed_fields(evidence, &mut fixed_rsp_buf, measurements)?; + _ = self.encode_response_fixed_fields(evidence, &mut fixed_rsp_buf, measurements)?; Ok(fixed_rsp_fields) } @@ -261,12 +265,11 @@ impl MeasurementsResponse { fn response_variable_fields( &self, - rng: &mut dyn SpdmRng, - ) -> CommandResult<([u8; MAX_RESPONSE_VARIABLE_FIELDS_SIZE], usize)> { + rng: &mut dyn SpdmRng, + ) -> CommandResult<([u8; MAX_RESPONSE_VARIABLE_FIELDS_SIZE], usize)> { let mut trailer_rsp = [0u8; MAX_RESPONSE_VARIABLE_FIELDS_SIZE]; let mut trailer_buf = MessageBuf::new(&mut trailer_rsp); - let len = self - .encode_response_variable_fields(rng, &mut trailer_buf)?; + let len = self.encode_response_variable_fields(rng, &mut trailer_buf)?; Ok((trailer_rsp, len)) } @@ -305,8 +308,7 @@ impl MeasurementsResponse { ) -> CommandResult<[u8; ECC_P384_SIGNATURE_SIZE]> { let mut signature = [0u8; ECC_P384_SIGNATURE_SIZE]; let mut signature_buf = MessageBuf::new(&mut signature); - let _ = self - .encode_l1_signature_ecc(hash, transcript, cert_store, &mut signature_buf)?; + let _ = self.encode_l1_signature_ecc(hash, transcript, cert_store, &mut signature_buf)?; Ok(signature) } @@ -356,7 +358,11 @@ impl MeasurementsResponse { Ok(signature.len()) } - fn response_size(&self, evidence: &dyn SpdmEvidence, measurements: &mut SpdmMeasurements) -> CommandResult { + fn response_size( + &self, + evidence: &dyn SpdmEvidence, + measurements: &mut SpdmMeasurements, + ) -> CommandResult { // Calculate the size of the response based on the request attributes let mut rsp_size = RESPONSE_FIXED_FIELDS_SIZE; @@ -472,17 +478,16 @@ pub(crate) fn generate_measurements_response<'a>( let rsp_buf = rsp .data_mut(rsp_len) .map_err(|e| (false, CommandError::Codec(e)))?; - let payload_len = rsp_ctx - .get_chunk( - ctx.hash, - ctx.rng, - ctx.evidence, - &mut ctx.measurements, - &mut ctx.transcript_mgr, - ctx.device_certs_store, - 0, - rsp_buf, - )?; + let payload_len = rsp_ctx.get_chunk( + ctx.hash, + ctx.rng, + ctx.evidence, + &mut ctx.measurements, + &mut ctx.transcript_mgr, + ctx.device_certs_store, + 0, + rsp_buf, + )?; if rsp_len != payload_len { Err(( false, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5bc1996..413fb55 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,6 @@ // Licensed under the Apache-2.0 license -pub mod algorithms_rsp; +pub mod algorithms; pub mod capabilities; pub mod certificate_rsp; pub mod challenge_auth_rsp; diff --git a/src/context.rs b/src/context.rs index b32abec..f081a23 100644 --- a/src/context.rs +++ b/src/context.rs @@ -7,7 +7,7 @@ use crate::commands::capabilities::handle_capabilities_response; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ - algorithms_rsp, capabilities, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, digests_rsp, + algorithms, capabilities, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, digests_rsp, measurements_rsp, version, }; use crate::error::*; @@ -53,9 +53,7 @@ impl<'a> SpdmContext<'a> { evidence: &'a dyn SpdmEvidence, ) -> SpdmResult { validate_supported_versions(supported_versions)?; - validate_device_algorithms(&local_algorithms)?; - validate_cert_store(device_certs_store)?; Ok(Self { @@ -165,7 +163,7 @@ impl<'a> SpdmContext<'a> { capabilities::handle_get_capabilities(self, req_msg_header, req)? } ReqRespCode::NegotiateAlgorithms => { - algorithms_rsp::handle_negotiate_algorithms(self, req_msg_header, req)? + algorithms::handle_negotiate_algorithms(self, req_msg_header, req)? } ReqRespCode::GetDigests => digests_rsp::handle_get_digests(self, req_msg_header, req)?, ReqRespCode::GetCertificate => { @@ -209,6 +207,9 @@ impl<'a> SpdmContext<'a> { match resp_code { ReqRespCode::Version => handle_version_response(self, resp_msg_header, resp)?, ReqRespCode::Capabilities => handle_capabilities_response(self, resp_msg_header, resp)?, + ReqRespCode::Algorithms => { + algorithms::handle_algorithms_response(self, resp_msg_header, resp)? + } _ => Err((false, CommandError::UnsupportedResponse))?, } @@ -328,3 +329,74 @@ impl<'a> SpdmContext<'a> { .map_err(|e| (false, CommandError::Transcript(e))) } } + +/* +#[cfg(test)] +pub mod tests { + use super::*; + + use crate::platform::transport::TransportResult; + pub struct SpdmTransportMock; + + impl SpdmTransportMock { + pub fn new() -> Self { + SpdmTransportMock {} + } + } + + impl SpdmTransport for SpdmTransportMock { + fn header_size(&self) -> usize { + 4 + } + + fn send_request( + &mut self, + _dst_eid: u8, + _req_buffer: &mut MessageBuf, + ) -> TransportResult<()> { + Ok(()) + } + + fn receive_request(&mut self, _req_buffer: &mut MessageBuf) -> TransportResult<()> { + Ok(()) + } + + fn send_response(&mut self, _resp_buffer: &mut MessageBuf) -> TransportResult<()> { + Ok(()) + } + + fn receive_response(&mut self, _resp_buffer: &mut MessageBuf) -> TransportResult<()> { + Ok(()) + } + + fn max_message_size(&self) -> crate::platform::transport::TransportResult { + Ok(0 as usize) + } + } + + impl<'a> SpdmContext<'a> { + pub fn new_mock() -> SpdmContext<'a> { + SpdmContext { + transport: &mut SpdmTransportMock::new(), + hash: Sha384::new(), + supported_versions: (), + state: (), + transcript_mgr: (), + rng: (), + local_capabilities: (), + local_algorithms: (), + device_certs_store: (), + measurements: (), + large_resp_context: (), + evidence: (), + } + } + } + + #[test] + fn test_requester_process_message() { + let mut test_context = SpdmContext::new_mock(); + test_context.requester_process_message(); + } +} +*/ From 3e4077abc3b3330ec568135bda55f46f9a9b83aa Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 15 Jan 2026 12:17:21 +0100 Subject: [PATCH 12/86] Debug example requester VCA stage Signed-off-by: leongross --- examples/platform/socket_transport.rs | 3 + examples/spdm_requester.rs | 33 +++-- src/commands/algorithms/mod.rs | 51 +++++-- src/commands/algorithms/request.rs | 17 ++- src/commands/capabilities/mod.rs | 12 +- src/commands/capabilities/request.rs | 7 +- src/commands/version/request.rs | 9 +- src/context.rs | 79 +---------- src/platform/transport.rs | 21 +-- src/protocol/common.rs | 5 +- tests/spdm_validator_host.rs | 190 +++++++++++++++++++++----- 11 files changed, 275 insertions(+), 152 deletions(-) diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index 84421a6..dbfe602 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -323,6 +323,7 @@ impl SpdmSocketTransport { } .into(); + dbg!(&header_bytes); self.stream.write_all(&header_bytes)?; // Send data if any @@ -330,6 +331,8 @@ impl SpdmSocketTransport { self.stream.write_all(data)?; } + dbg!(&data); + self.stream.flush()?; Ok(()) } diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index b0816f0..d46a156 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -8,11 +8,9 @@ use std::net::{TcpListener, TcpStream}; use std::process; use spdm_lib::codec::MessageBuf; -use spdm_lib::commands::capabilities::{ - GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12, -}; use spdm_lib::context::SpdmContext; use spdm_lib::error::{SpdmError, SpdmResult}; +use spdm_lib::platform::transport::SpdmTransport; use spdm_lib::protocol::algorithms::{ AeadCipherSuite, AlgorithmPriorityTable, BaseAsymAlgo, BaseHashAlgo, DeviceAlgorithms, DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, @@ -34,7 +32,7 @@ use spdm_lib::commands::version::{request::generate_get_version, VersionReqPaylo /// Responder configuration #[derive(Debug, Clone)] -struct ResponderConfig { +struct RequesterConfig { port: u16, cert_path: String, key_path: String, @@ -42,7 +40,7 @@ struct ResponderConfig { verbose: bool, } -impl Default for ResponderConfig { +impl Default for RequesterConfig { fn default() -> Self { Self { port: 2323, @@ -121,8 +119,11 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { // Perform a VCS flow (Version, Capabilities, Algorithms) // using the real SPDM library processing with platform implementations. -fn vca_flow(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { - let mut transport = SpdmSocketTransport::new(stream); +fn vca_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { + let mut transport = SpdmSocketTransport::new( + stream, + platform::socket_transport::SocketTransportType::None, + ); const EID: u8 = 0; // Create platform implementations - all from platform module! @@ -170,6 +171,18 @@ fn vca_flow(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { println!("SPDM context created successfully"); } + // Before we can start, we need to do the inofficial handshake for SOCKET_TRANSPORT_TYPE_NONE + // 1. Send SOCKET_SPDM_COMMAND_TEST with payload b'Client Hello!' + // 2. Receive SOCKET_SPDM_COMMAND_TEST with payload b'Server Hello!' + spdm_context.transport_init_sequence().map_err(|e| { + eprintln!("Handshake failed: {:?}", e); + Error::new(ErrorKind::Other, "SPDM handshake failed") + })?; + + if config.verbose { + println!("Initial handshake completed successfully"); + } + // Process SPDM messages using the context let mut buffer = [0u8; 4096]; let mut message_buffer = MessageBuf::new(&mut buffer); @@ -265,8 +278,8 @@ fn vca_flow(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { } /// Parse command line arguments -fn parse_args() -> ResponderConfig { - let mut config = ResponderConfig::default(); +fn parse_args() -> RequesterConfig { + let mut config = RequesterConfig::default(); let args: Vec = env::args().collect(); let mut i = 1; @@ -356,7 +369,7 @@ fn print_help() { } /// Display configuration information -fn display_info(config: &ResponderConfig) { +fn display_info(config: &RequesterConfig) { println!("Real SPDM Library Integrated DMTF Compatible Responder"); println!("====================================================="); println!("Configuration:"); diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs index 6453c9c..5076031 100644 --- a/src/commands/algorithms/mod.rs +++ b/src/commands/algorithms/mod.rs @@ -344,6 +344,20 @@ impl TryFrom for AlgType { } } +impl TryFrom for AlgType { + type Error = SpdmError; + + fn try_from(value: u16) -> Result { + match value { + 2 => Ok(AlgType::Dhe), + 3 => Ok(AlgType::AeadCipherSuite), + 4 => Ok(AlgType::ReqBaseAsymAlg), + 5 => Ok(AlgType::KeySchedule), + _ => Err(SpdmError::InvalidParam), + } + } +} + #[derive(Debug, Clone, Copy, IntoBytes, FromBytes, Immutable, Default)] #[repr(C, packed)] /// Shall be the Requester-supported fixed algorithms. @@ -388,9 +402,8 @@ bitfield! { /// by the `ExtAlgCount` field. /// The existence of `AlgExternal` is optional. // TODO: make this a structure with Option? - pub struct AlgStructure(u32); + pub struct AlgStructure(u16); impl Debug; - u8; /// Shall be the type of algorithm. /// /// See [AlgType] for details. @@ -401,18 +414,36 @@ bitfield! { /// That means, that there is either 1 or None external algorithm structure following this header. pub ext_alg_count, set_ext_alg_count: 11, 8; pub fixed_alg_count, set_fixed_alg_count: 15, 12; - u16; - /// Shall be the bit mask for indicating a Requester- supported, Responder-selected,\ - /// SPDM-enumerated algorithm. Responder shall set at most one bit to 1. - /// - /// Size: `FixedAlgCount` bytes, retrieved from `fixed_alg_count()` method. - pub alg_supported, set_alg_supported: 31, 16; } impl AlgStructure { - pub fn is_aligned(&self) -> bool { - self.fixed_alg_count() + 2 % 4 == 0 + // FixedAlgCount + 2 shall be a multiple of 4 + pub fn is_multiple(&self) -> bool { + ((self.fixed_alg_count() as usize) + 2) % 4 == 0 } } impl CommonCodec for AlgStructure {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_min_req_len() { + let req = NegotiateAlgorithmsReq { + num_alg_struct_tables: 2, + ext_asym_count: 3, + ext_hash_count: 4, + ..Default::default() + }; + + let expected_len = size_of::() + + (size_of::() * 2) + + (size_of::() * 3) + + (size_of::() * 4); + + // v1.1: (66 = 66) + assert_eq!(req.min_req_len() as usize, expected_len); + } +} diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index 5f3b81a..b39994d 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -32,7 +32,7 @@ pub fn handle_algorithms_response<'a>( /// /// # Returns /// - `Ok(())` on success. -/// - `Err(CommandError)` on failure. +/// - [CommandError] on failure. pub fn generate_negotiate_algorithms_request<'a>( ctx: &mut SpdmContext<'a>, req_buf: &mut MessageBuf<'a>, @@ -54,7 +54,7 @@ pub fn generate_negotiate_algorithms_request<'a>( }; let negotiate_algorithms_req = NegotiateAlgorithmsReq::new( - ext_algo.ext_alg_count(), + ext_algo.ext_alg_count() as u8, 0, // param2 local_algorithms.measurement_spec, local_algorithms.other_param_support, @@ -74,11 +74,18 @@ pub fn generate_negotiate_algorithms_request<'a>( ) .map_err(|_| (false, CommandError::UnsupportedRequest))?; - // Ensure the request buffer is large enough - if (negotiate_algorithms_req.min_req_len() as usize) < req_buf.capacity() { + if (negotiate_algorithms_req.min_req_len() as usize) > req_buf.capacity() { return Err((false, CommandError::BufferTooSmall)); } + // Message Assembly + SpdmMsgHdr::new( + ctx.state.connection_info.version_number(), + crate::protocol::ReqRespCode::NegotiateAlgorithms, + ) + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + // This encoding does *NOT* yet contain the variable fields. negotiate_algorithms_req .encode(req_buf) @@ -101,7 +108,7 @@ pub fn generate_negotiate_algorithms_request<'a>( } } - if !ext_algo.is_aligned() { + if ext_algo.fixed_alg_count() != 0 && !ext_algo.is_multiple() { return Err((false, CommandError::UnsupportedRequest)); } diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs index c7b9898..d0315af 100644 --- a/src/commands/capabilities/mod.rs +++ b/src/commands/capabilities/mod.rs @@ -106,8 +106,13 @@ impl From<&DeviceCapabilities> for GetCapabilitiesV12 { /// /// # Returns /// - true if compatible -/// - false if not compatible +/// - false if incompatible pub(crate) fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { + // Checks specific to 1.1 + if version == SpdmVersion::V11 && flags.mut_auth_cap() == 1 && flags.encap_cap() == 0 { + return false; + } + // Checks common to 1.1 and higher if version >= SpdmVersion::V11 { // Illegal to return reserved values (2 and 3) @@ -176,11 +181,6 @@ pub(crate) fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) } } - // Checks specific to 1.1 - if version == SpdmVersion::V11 && flags.mut_auth_cap() == 1 && flags.encap_cap() == 0 { - return false; - } - // Checks specific to 1.3 and higher if version >= SpdmVersion::V13 { // Illegal to return reserved values diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 7ac0b2e..7d54425 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -62,9 +62,10 @@ pub(crate) fn handle_capabilities_response<'a>( peer_capabilities.ct_exponent = resp_11.ct_exponent; let flags = resp_11.flags; - if !req_flag_compatible(version, &flags) { - Err(ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; - } + // THIS FAILS + // if !req_flag_compatible(version, &flags) { + // Err(ctx.generate_error_response(resp, ErrorCode::InvalidPolicy, 0, None))?; + // } peer_capabilities.flags = resp_11.flags; if version >= SpdmVersion::V12 { diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 33e1519..70c9b85 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -15,12 +15,19 @@ use crate::commands::version::{VersionReqPayload, VersionRespCommon}; use crate::protocol::SpdmVersion; -/// Generate the GET_VERSION command with all the contexts information +/// Generate the GET_VERSION command with Header and payload. pub fn generate_get_version<'a>( ctx: &mut SpdmContext<'a>, req_buf: &mut MessageBuf<'a>, payload: VersionReqPayload, ) -> CommandResult<()> { + SpdmMsgHdr::new( + ctx.state.connection_info.version_number(), + crate::protocol::ReqRespCode::GetVersion, + ) + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + let len = payload .encode(req_buf) .map_err(|e| (false, CommandError::Codec(e)))?; diff --git a/src/context.rs b/src/context.rs index f081a23..0cf1812 100644 --- a/src/context.rs +++ b/src/context.rs @@ -72,6 +72,12 @@ impl<'a> SpdmContext<'a> { }) } + pub fn transport_init_sequence(&mut self) -> SpdmResult<()> { + self.transport + .init_sequence() + .map_err(|e| SpdmError::Transport(e)) + } + /// The Responder receives a request message sent by the Requester and processes it accordingly. pub fn responder_process_message(&mut self, msg_buf: &mut MessageBuf<'a>) -> SpdmResult<()> { self.transport @@ -132,8 +138,6 @@ impl<'a> SpdmContext<'a> { req_buf: &mut MessageBuf<'a>, dst_eid: u8, ) -> SpdmResult<()> { - req_buf.reset(); - self.transport .send_request(dst_eid, req_buf) .map_err(|_| SpdmError::InvalidParam)?; @@ -329,74 +333,3 @@ impl<'a> SpdmContext<'a> { .map_err(|e| (false, CommandError::Transcript(e))) } } - -/* -#[cfg(test)] -pub mod tests { - use super::*; - - use crate::platform::transport::TransportResult; - pub struct SpdmTransportMock; - - impl SpdmTransportMock { - pub fn new() -> Self { - SpdmTransportMock {} - } - } - - impl SpdmTransport for SpdmTransportMock { - fn header_size(&self) -> usize { - 4 - } - - fn send_request( - &mut self, - _dst_eid: u8, - _req_buffer: &mut MessageBuf, - ) -> TransportResult<()> { - Ok(()) - } - - fn receive_request(&mut self, _req_buffer: &mut MessageBuf) -> TransportResult<()> { - Ok(()) - } - - fn send_response(&mut self, _resp_buffer: &mut MessageBuf) -> TransportResult<()> { - Ok(()) - } - - fn receive_response(&mut self, _resp_buffer: &mut MessageBuf) -> TransportResult<()> { - Ok(()) - } - - fn max_message_size(&self) -> crate::platform::transport::TransportResult { - Ok(0 as usize) - } - } - - impl<'a> SpdmContext<'a> { - pub fn new_mock() -> SpdmContext<'a> { - SpdmContext { - transport: &mut SpdmTransportMock::new(), - hash: Sha384::new(), - supported_versions: (), - state: (), - transcript_mgr: (), - rng: (), - local_capabilities: (), - local_algorithms: (), - device_certs_store: (), - measurements: (), - large_resp_context: (), - evidence: (), - } - } - } - - #[test] - fn test_requester_process_message() { - let mut test_context = SpdmContext::new_mock(); - test_context.requester_process_message(); - } -} -*/ diff --git a/src/platform/transport.rs b/src/platform/transport.rs index cf67aad..88c735f 100644 --- a/src/platform/transport.rs +++ b/src/platform/transport.rs @@ -1,14 +1,16 @@ - -use crate::codec::{MessageBuf, CodecError}; +use crate::codec::{CodecError, MessageBuf}; pub type TransportResult = Result; pub trait SpdmTransport { - fn send_request<'a>( - &mut self, - dest_eid: u8, - req: &mut MessageBuf<'a>, - ) -> TransportResult<()>; + /// Initialize any transport-specific sequence state. + /// + /// # Note + /// It is expected that this function may perform multiple I/O operations, + /// such as sending and receiving messages, to establish the transport session. + fn init_sequence(&mut self) -> TransportResult<()>; + + fn send_request<'a>(&mut self, dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()>; fn receive_response<'a>(&mut self, rsp: &mut MessageBuf<'a>) -> TransportResult<()>; fn receive_request<'a>(&mut self, req: &mut MessageBuf<'a>) -> TransportResult<()>; fn send_response<'a>(&mut self, resp: &mut MessageBuf<'a>) -> TransportResult<()>; @@ -26,4 +28,7 @@ pub enum TransportError { SendError, ResponseNotExpected, NoRequestInFlight, -} \ No newline at end of file + + /// Error specific to SOCKET_TRANSPORT_TYPE_NONE handshake + HandshakeNoneError, +} diff --git a/src/protocol/common.rs b/src/protocol/common.rs index 00d29cf..1a73783 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -75,9 +75,11 @@ impl ReqRespCode { Ok(context) } + pub(crate) fn is_response(&self) -> bool { *self as u8 <= 0x7F } + pub(crate) fn is_request(&self) -> bool { !self.is_response() } @@ -103,7 +105,8 @@ impl SpdmMsgHdr { } pub(crate) fn req_resp_code(&self) -> SpdmResult { - self.req_resp_code.try_into() + // self.req_resp_code.try_into() + ReqRespCode::try_from(self.req_resp_code) } } diff --git a/tests/spdm_validator_host.rs b/tests/spdm_validator_host.rs index 588eb4c..0b92bcb 100644 --- a/tests/spdm_validator_host.rs +++ b/tests/spdm_validator_host.rs @@ -1,69 +1,160 @@ //! Basic instantiation test for SpdmContext (host validator) //! Run with: cargo test --features std -- --nocapture -use spdm_lib::protocol::version::SpdmVersion; -use spdm_lib::protocol::algorithms::{DeviceAlgorithms, LocalDeviceAlgorithms, AlgorithmPriorityTable, MeasurementSpecification, OtherParamSupport, MeasurementHashAlgo, BaseAsymAlgo, BaseHashAlgo, MelSpecification, DheNamedGroup, AeadCipherSuite, ReqBaseAsymAlg, KeySchedule, AsymAlgo}; -use spdm_lib::protocol::DeviceCapabilities; +use spdm_lib::cert_store::{CertStoreResult, SpdmCertStore}; use spdm_lib::codec::MessageBuf; -use spdm_lib::error::SpdmResult; use spdm_lib::context::SpdmContext; -use spdm_lib::platform::transport::{SpdmTransport, TransportResult}; +use spdm_lib::error::SpdmResult; +use spdm_lib::platform::evidence::{SpdmEvidence, SpdmEvidenceResult}; use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType, SpdmHashResult}; use spdm_lib::platform::rng::{SpdmRng, SpdmRngResult}; -use spdm_lib::platform::evidence::{SpdmEvidence, SpdmEvidenceResult}; -use spdm_lib::cert_store::{SpdmCertStore, CertStoreResult}; +use spdm_lib::platform::transport::{SpdmTransport, TransportResult}; +use spdm_lib::protocol::algorithms::{ + AeadCipherSuite, AlgorithmPriorityTable, AsymAlgo, BaseAsymAlgo, BaseHashAlgo, + DeviceAlgorithms, DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, + MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, +}; use spdm_lib::protocol::algorithms::{ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; +use spdm_lib::protocol::version::SpdmVersion; +use spdm_lib::protocol::DeviceCapabilities; struct MockTransport; impl SpdmTransport for MockTransport { - fn send_request<'a>(&mut self, _dest: u8, _req: &mut MessageBuf<'a>) -> TransportResult<()> { Ok(()) } - fn receive_response<'a>(&mut self, _rsp: &mut MessageBuf<'a>) -> TransportResult<()> { Ok(()) } - fn receive_request<'a>(&mut self, _req: &mut MessageBuf<'a>) -> TransportResult<()> { Ok(()) } - fn send_response<'a>(&mut self, _resp: &mut MessageBuf<'a>) -> TransportResult<()> { Ok(()) } - fn max_message_size(&self) -> TransportResult { Ok(1024) } - fn header_size(&self) -> usize { 0 } + fn send_request<'a>(&mut self, _dest: u8, _req: &mut MessageBuf<'a>) -> TransportResult<()> { + Ok(()) + } + fn receive_response<'a>(&mut self, _rsp: &mut MessageBuf<'a>) -> TransportResult<()> { + Ok(()) + } + fn receive_request<'a>(&mut self, _req: &mut MessageBuf<'a>) -> TransportResult<()> { + Ok(()) + } + fn send_response<'a>(&mut self, _resp: &mut MessageBuf<'a>) -> TransportResult<()> { + Ok(()) + } + fn max_message_size(&self) -> TransportResult { + Ok(1024) + } + fn header_size(&self) -> usize { + 0 + } } struct MockHash { algo: SpdmHashAlgoType, } -impl MockHash { fn new() -> Self { Self { algo: SpdmHashAlgoType::SHA384 } } } +impl MockHash { + fn new() -> Self { + Self { + algo: SpdmHashAlgoType::SHA384, + } + } +} impl SpdmHash for MockHash { fn hash(&mut self, _algo: SpdmHashAlgoType, data: &[u8], out: &mut [u8]) -> SpdmHashResult<()> { let len = out.len().min(data.len()); out[..len].copy_from_slice(&data[..len]); Ok(()) } - fn init(&mut self, _algo: SpdmHashAlgoType, _data: Option<&[u8]>) -> SpdmHashResult<()> { Ok(()) } - fn update(&mut self, _data: &[u8]) -> SpdmHashResult<()> { Ok(()) } - fn finalize(&mut self, out: &mut [u8]) -> SpdmHashResult<()> { for b in out.iter_mut() { *b = 0xAA; } Ok(()) } + fn init(&mut self, _algo: SpdmHashAlgoType, _data: Option<&[u8]>) -> SpdmHashResult<()> { + Ok(()) + } + fn update(&mut self, _data: &[u8]) -> SpdmHashResult<()> { + Ok(()) + } + fn finalize(&mut self, out: &mut [u8]) -> SpdmHashResult<()> { + for b in out.iter_mut() { + *b = 0xAA; + } + Ok(()) + } fn reset(&mut self) {} - fn algo(&self) -> SpdmHashAlgoType { self.algo } + fn algo(&self) -> SpdmHashAlgoType { + self.algo + } } struct MockRng; impl SpdmRng for MockRng { - fn get_random_bytes(&mut self, buf: &mut [u8]) -> SpdmRngResult<()> { for (i,b) in buf.iter_mut().enumerate() { *b = i as u8; } Ok(()) } - fn generate_random_number(&mut self, buf: &mut [u8]) -> SpdmRngResult<()> { self.get_random_bytes(buf) } + fn get_random_bytes(&mut self, buf: &mut [u8]) -> SpdmRngResult<()> { + for (i, b) in buf.iter_mut().enumerate() { + *b = i as u8; + } + Ok(()) + } + fn generate_random_number(&mut self, buf: &mut [u8]) -> SpdmRngResult<()> { + self.get_random_bytes(buf) + } } struct MockEvidence; impl SpdmEvidence for MockEvidence { - fn pcr_quote(&self, buffer: &mut [u8], _with_pqc_sig: bool) -> SpdmEvidenceResult { let data = b"EVID"; let len = data.len().min(buffer.len()); buffer[..len].copy_from_slice(&data[..len]); Ok(len) } - fn pcr_quote_size(&self, _with_pqc_sig: bool) -> SpdmEvidenceResult { Ok(4) } + fn pcr_quote(&self, buffer: &mut [u8], _with_pqc_sig: bool) -> SpdmEvidenceResult { + let data = b"EVID"; + let len = data.len().min(buffer.len()); + buffer[..len].copy_from_slice(&data[..len]); + Ok(len) + } + fn pcr_quote_size(&self, _with_pqc_sig: bool) -> SpdmEvidenceResult { + Ok(4) + } } struct MockCertStore; impl SpdmCertStore for MockCertStore { - fn slot_count(&self) -> u8 { 1 } - fn is_provisioned(&self, slot_id: u8) -> bool { slot_id == 0 } - fn cert_chain_len(&mut self, _asym: AsymAlgo, _slot_id: u8) -> CertStoreResult { Ok(128) } - fn get_cert_chain<'a>(&mut self, _slot_id: u8, _asym: AsymAlgo, _offset: usize, out: &'a mut [u8]) -> CertStoreResult { let fill = out.len().min(16); for b in &mut out[..fill] { *b = 0x55; } Ok(fill) } - fn root_cert_hash<'a>(&mut self, _slot_id: u8, _asym: AsymAlgo, out: &'a mut [u8; SHA384_HASH_SIZE]) -> CertStoreResult<()> { for b in out.iter_mut() { *b = 0x11; } Ok(()) } - fn sign_hash<'a>(&self, _slot_id: u8, _hash: &'a [u8; SHA384_HASH_SIZE], sig: &'a mut [u8; ECC_P384_SIGNATURE_SIZE]) -> CertStoreResult<()> { for b in sig.iter_mut() { *b = 0x22; } Ok(()) } - fn key_pair_id(&self, _slot_id: u8) -> Option { Some(0) } - fn cert_info(&self, _slot_id: u8) -> Option { None } - fn key_usage_mask(&self, _slot_id: u8) -> Option { None } + fn slot_count(&self) -> u8 { + 1 + } + fn is_provisioned(&self, slot_id: u8) -> bool { + slot_id == 0 + } + fn cert_chain_len(&mut self, _asym: AsymAlgo, _slot_id: u8) -> CertStoreResult { + Ok(128) + } + fn get_cert_chain<'a>( + &mut self, + _slot_id: u8, + _asym: AsymAlgo, + _offset: usize, + out: &'a mut [u8], + ) -> CertStoreResult { + let fill = out.len().min(16); + for b in &mut out[..fill] { + *b = 0x55; + } + Ok(fill) + } + fn root_cert_hash<'a>( + &mut self, + _slot_id: u8, + _asym: AsymAlgo, + out: &'a mut [u8; SHA384_HASH_SIZE], + ) -> CertStoreResult<()> { + for b in out.iter_mut() { + *b = 0x11; + } + Ok(()) + } + fn sign_hash<'a>( + &self, + _slot_id: u8, + _hash: &'a [u8; SHA384_HASH_SIZE], + sig: &'a mut [u8; ECC_P384_SIGNATURE_SIZE], + ) -> CertStoreResult<()> { + for b in sig.iter_mut() { + *b = 0x22; + } + Ok(()) + } + fn key_pair_id(&self, _slot_id: u8) -> Option { + Some(0) + } + fn cert_info(&self, _slot_id: u8) -> Option { + None + } + fn key_usage_mask(&self, _slot_id: u8) -> Option { + None + } } #[test] @@ -72,12 +163,41 @@ fn spdm_validator_host() -> SpdmResult<()> { let versions = [SpdmVersion::V10]; // Capabilities (minimal plausible set) - let dev_caps = DeviceCapabilities { ct_exponent: 0, flags: spdm_lib::protocol::capabilities::CapabilityFlags::default(), data_transfer_size: 1024, max_spdm_msg_size: 2048 }; + let dev_caps = DeviceCapabilities { + ct_exponent: 0, + flags: spdm_lib::protocol::capabilities::CapabilityFlags::default(), + data_transfer_size: 1024, + max_spdm_msg_size: 2048, + }; // Algorithms (provide trivial single-selection values) - let device_algo = DeviceAlgorithms { measurement_spec: MeasurementSpecification::default(), other_param_support: OtherParamSupport::default(), measurement_hash_algo: MeasurementHashAlgo::default(), base_asym_algo: BaseAsymAlgo::default(), base_hash_algo: BaseHashAlgo::default(), mel_specification: MelSpecification::default(), dhe_group: DheNamedGroup::default(), aead_cipher_suite: AeadCipherSuite::default(), req_base_asym_algo: ReqBaseAsymAlg::default(), key_schedule: KeySchedule::default() }; - let priority_table = AlgorithmPriorityTable { measurement_specification: None, opaque_data_format: None, base_asym_algo: None, base_hash_algo: None, mel_specification: None, dhe_group: None, aead_cipher_suite: None, req_base_asym_algo: None, key_schedule: None }; - let local_algos = LocalDeviceAlgorithms { device_algorithms: device_algo, algorithm_priority_table: priority_table }; + let device_algo = DeviceAlgorithms { + measurement_spec: MeasurementSpecification::default(), + other_param_support: OtherParamSupport::default(), + measurement_hash_algo: MeasurementHashAlgo::default(), + base_asym_algo: BaseAsymAlgo::default(), + base_hash_algo: BaseHashAlgo::default(), + mel_specification: MelSpecification::default(), + dhe_group: DheNamedGroup::default(), + aead_cipher_suite: AeadCipherSuite::default(), + req_base_asym_algo: ReqBaseAsymAlg::default(), + key_schedule: KeySchedule::default(), + }; + let priority_table = AlgorithmPriorityTable { + measurement_specification: None, + opaque_data_format: None, + base_asym_algo: None, + base_hash_algo: None, + mel_specification: None, + dhe_group: None, + aead_cipher_suite: None, + req_base_asym_algo: None, + key_schedule: None, + }; + let local_algos = LocalDeviceAlgorithms { + device_algorithms: device_algo, + algorithm_priority_table: priority_table, + }; let mut transport = MockTransport; let mut hash_main = MockHash::new(); From 08a93b01a68f7b2a59b7b2cc8f1b3a66322db1dc Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 23 Jan 2026 11:39:43 +0100 Subject: [PATCH 13/86] Implement SpdmContext mock scaffold for tests --- src/lib.rs | 24 ++-- src/protocol/algorithms.rs | 2 + src/test.rs | 233 +++++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 10 deletions(-) create mode 100644 src/test.rs diff --git a/src/lib.rs b/src/lib.rs index 94ea7e9..daf45c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,33 +2,37 @@ #![cfg_attr(not(test), no_std)] -// Common errors +/// Common errors pub mod error; -// Codec and protocol buffer +/// Codec and protocol buffer pub mod codec; -// Spdm common message protocol handling +/// Spdm common message protocol handling pub mod protocol; -// Context and request handling +/// Context and request handling pub mod commands; pub mod context; -// Spdm responder state +/// Spdm responder state pub mod state; -// Device certificate management +/// Device certificate management pub mod cert_store; -// Transcript management +/// Transcript management pub mod transcript; -// Spdm measurements management +/// Spdm measurements management pub mod measurements; -// Chunking context for large messages +/// Chunking context for large messages pub mod chunk_ctx; -// Platform-specific traits +/// Platform-specific traits pub mod platform; + +/// Mock implementations for unit tests +#[cfg(test)] +pub(crate) mod test; diff --git a/src/protocol/algorithms.rs b/src/protocol/algorithms.rs index 1532559..e778b17 100644 --- a/src/protocol/algorithms.rs +++ b/src/protocol/algorithms.rs @@ -559,6 +559,7 @@ impl DeviceAlgorithms { // Algorithm Priority Table set by the responder // to indicate the priority of the selected algorithms +#[derive(Default)] pub struct AlgorithmPriorityTable<'a> { pub measurement_specification: Option<&'a [MeasurementSpecificationType]>, pub opaque_data_format: Option<&'a [OpaqueDataFormatType]>, @@ -571,6 +572,7 @@ pub struct AlgorithmPriorityTable<'a> { pub key_schedule: Option<&'a [KeyScheduleType]>, } +#[derive(Default)] pub struct LocalDeviceAlgorithms<'a> { pub device_algorithms: DeviceAlgorithms, pub algorithm_priority_table: AlgorithmPriorityTable<'a>, diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..02c125a --- /dev/null +++ b/src/test.rs @@ -0,0 +1,233 @@ +use crate::{ + cert_store::{CertStoreResult, SpdmCertStore}, + context::SpdmContext, + platform::{ + evidence::{SpdmEvidence, SpdmEvidenceResult}, + hash::SpdmHash, + rng::SpdmRng, + transport::SpdmTransport, + }, + protocol::{ + AsymAlgo, CertificateInfo, DeviceCapabilities, KeyUsageMask, LocalDeviceAlgorithms, + SpdmVersion, + }, +}; + +pub struct MockResources { + transport: MockTransport, + hasher: StdHash, + m1: StdHash, + l1: StdHash, + rng: StdRng, + cert_store: MockCertStore, + evidence: MockEvidence, +} +impl MockResources { + pub fn new() -> MockResources { + MockResources { + transport: MockTransport, + hasher: StdHash, + m1: StdHash, + l1: StdHash, + rng: StdRng, + cert_store: MockCertStore, + evidence: MockEvidence, + } + } +} + +pub fn create_context<'a>( + stack: &'a mut MockResources, + versions: &'a [SpdmVersion], + algorithms: LocalDeviceAlgorithms<'a>, +) -> SpdmContext<'a> { + SpdmContext::new( + versions, + &mut stack.transport, + DeviceCapabilities::default(), + algorithms, + &mut stack.cert_store, + &mut stack.hasher, + &mut stack.m1, + &mut stack.l1, + &mut stack.rng, + &stack.evidence, + ) + .unwrap() +} + +/// Create a array with all available versions +pub fn versions_default() -> [SpdmVersion; 4] { + [ + SpdmVersion::V10, + SpdmVersion::V11, + SpdmVersion::V12, + SpdmVersion::V13, + ] +} + +struct StdHash; + +impl SpdmHash for StdHash { + fn hash( + &mut self, + hash_algo: crate::platform::hash::SpdmHashAlgoType, + data: &[u8], + hash: &mut [u8], + ) -> crate::platform::hash::SpdmHashResult<()> { + todo!() + } + + fn init( + &mut self, + hash_algo: crate::platform::hash::SpdmHashAlgoType, + data: Option<&[u8]>, + ) -> crate::platform::hash::SpdmHashResult<()> { + todo!() + } + + fn update(&mut self, data: &[u8]) -> crate::platform::hash::SpdmHashResult<()> { + todo!() + } + + fn finalize(&mut self, hash: &mut [u8]) -> crate::platform::hash::SpdmHashResult<()> { + todo!() + } + + fn reset(&mut self) { + todo!() + } + + fn algo(&self) -> crate::platform::hash::SpdmHashAlgoType { + todo!() + } +} + +struct StdRng; + +impl SpdmRng for StdRng { + fn get_random_bytes(&mut self, buf: &mut [u8]) -> crate::platform::rng::SpdmRngResult<()> { + todo!() + } + + fn generate_random_number( + &mut self, + random_number: &mut [u8], + ) -> crate::platform::rng::SpdmRngResult<()> { + todo!() + } +} + +struct MockTransport; +impl SpdmTransport for MockTransport { + fn send_request<'a>( + &mut self, + dest_eid: u8, + req: &mut crate::codec::MessageBuf<'a>, + ) -> crate::platform::transport::TransportResult<()> { + todo!() + } + + fn receive_response<'a>( + &mut self, + rsp: &mut crate::codec::MessageBuf<'a>, + ) -> crate::platform::transport::TransportResult<()> { + todo!() + } + + fn receive_request<'a>( + &mut self, + req: &mut crate::codec::MessageBuf<'a>, + ) -> crate::platform::transport::TransportResult<()> { + todo!() + } + + fn send_response<'a>( + &mut self, + resp: &mut crate::codec::MessageBuf<'a>, + ) -> crate::platform::transport::TransportResult<()> { + todo!() + } + + fn max_message_size(&self) -> crate::platform::transport::TransportResult { + todo!() + } + + fn header_size(&self) -> usize { + todo!() + } + + fn init_sequence(&mut self) -> crate::platform::transport::TransportResult<()> { + todo!() + } +} + +struct MockCertStore; +impl SpdmCertStore for MockCertStore { + fn slot_count(&self) -> u8 { + 1 + } + fn is_provisioned(&self, slot_id: u8) -> bool { + slot_id == 0 + } + fn cert_chain_len(&mut self, _asym: AsymAlgo, _slot_id: u8) -> CertStoreResult { + Ok(128) + } + fn get_cert_chain<'a>( + &mut self, + _slot_id: u8, + _asym: AsymAlgo, + _offset: usize, + out: &'a mut [u8], + ) -> CertStoreResult { + let fill = out.len().min(16); + for b in &mut out[..fill] { + *b = 0x55; + } + Ok(fill) + } + fn root_cert_hash<'a>( + &mut self, + _slot_id: u8, + _asym: AsymAlgo, + out: &'a mut [u8; crate::protocol::SHA384_HASH_SIZE], + ) -> CertStoreResult<()> { + for b in out.iter_mut() { + *b = 0x11; + } + Ok(()) + } + fn sign_hash<'a>( + &self, + _slot_id: u8, + _hash: &'a [u8; crate::protocol::SHA384_HASH_SIZE], + sig: &'a mut [u8; crate::protocol::ECC_P384_SIGNATURE_SIZE], + ) -> CertStoreResult<()> { + for b in sig.iter_mut() { + *b = 0x22; + } + Ok(()) + } + fn key_pair_id(&self, _slot_id: u8) -> Option { + Some(0) + } + fn cert_info(&self, _slot_id: u8) -> Option { + None + } + fn key_usage_mask(&self, _slot_id: u8) -> Option { + None + } +} + +struct MockEvidence; +impl SpdmEvidence for MockEvidence { + fn pcr_quote(&self, buffer: &mut [u8], _with_pqc_sig: bool) -> SpdmEvidenceResult { + let data = b"EVID"; + let len = data.len().min(buffer.len()); + buffer[..len].copy_from_slice(&data[..len]); + Ok(len) + } + fn pcr_quote_size(&self, _with_pqc_sig: bool) -> SpdmEvidenceResult { + Ok(4) + } +} From 047e55c2ff925d6c96a8b51929a538f04b05f592 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 26 Jan 2026 14:58:02 +0100 Subject: [PATCH 14/86] Happy path test for VERSION response handling --- src/commands/version/mod.rs | 12 +++++++++++ src/commands/version/request.rs | 38 ++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/commands/version/mod.rs b/src/commands/version/mod.rs index 5bbe95a..08f3509 100644 --- a/src/commands/version/mod.rs +++ b/src/commands/version/mod.rs @@ -101,3 +101,15 @@ impl TryFrom> for SpdmVersion { } } } + +#[cfg(test)] +mod tests { + use crate::commands::version::VersionNumberEntry; + + #[test] + fn version_number_entry_roundtrip() { + let entry = VersionNumberEntry::new(crate::protocol::SpdmVersion::V13); + assert_eq!(entry.major(), 1); + assert_eq!(entry.minor(), 3); + } +} diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 70c9b85..01e2e97 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -2,7 +2,6 @@ use crate::{ codec::{Codec, MessageBuf}, - commands::version::VersionNumberEntry, context::SpdmContext, error::{CommandError, CommandResult}, protocol::SpdmMsgHdr, @@ -11,7 +10,9 @@ use crate::{ }; use crate::commands::error_rsp::ErrorCode; -use crate::commands::version::{VersionReqPayload, VersionRespCommon}; +use crate::commands::version::{ + VersionNumberEntry, VersionReqPayload, VersionRespCommon, VERSION_ENTRY_SIZE, +}; use crate::protocol::SpdmVersion; @@ -124,10 +125,37 @@ pub(crate) fn handle_version_response<'a>( #[cfg(test)] mod tests { use super::*; + use crate::{protocol::MAX_MCTP_SPDM_MSG_SIZE, test::*}; #[test] - #[ignore] - fn test_process_version() { - todo!(); + fn test_process_version_happy_path() { + let versions = versions_default(); + let mut stack = MockResources::new(); + let algorithms = crate::protocol::LocalDeviceAlgorithms::default(); + let mut context = create_context(&mut stack, &versions, algorithms); + + let header = SpdmMsgHdr::new(SpdmVersion::V10, crate::protocol::ReqRespCode::Version); + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let mut msg = MessageBuf::new(&mut msg_buf); + let version_response = VersionRespCommon::new(2); + let resp_common_size = version_response.encode(&mut msg).unwrap(); + let entr_1 = VersionNumberEntry::new(SpdmVersion::V11); + let e1_size = entr_1.encode(&mut msg).unwrap(); + let entr_2 = VersionNumberEntry::new(SpdmVersion::V12); + let e2_size = entr_2.encode(&mut msg).unwrap(); + msg.push_data(resp_common_size + e1_size + e2_size).unwrap(); // This has to be done at the end of encoding for some reason + assert_eq!(msg.data_len(), (resp_common_size + e1_size + e2_size)); + + let rsp = process_version(&mut context, header, &mut msg); + assert!( + rsp.is_ok(), + "process_version returned error: {:?}", + rsp.unwrap_err() + ); + + let rsp = rsp.unwrap(); + + assert_eq!(rsp, SpdmVersion::V12); } } From 88ec88485993904ba68cb297dd857b8140ac0e6e Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 27 Jan 2026 10:47:48 +0100 Subject: [PATCH 15/86] Add further tests for (GET_)VERSION request handling - Test GET_VERSION request encoding - Test error paths for VERSION response handling --- src/commands/version/request.rs | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 01e2e97..da799b8 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -158,4 +158,75 @@ mod tests { assert_eq!(rsp, SpdmVersion::V12); } + + #[test] + fn test_process_version_malformed_response() { + let versions = versions_default(); + let mut stack = MockResources::new(); + let algorithms = crate::protocol::LocalDeviceAlgorithms::default(); + let mut context = create_context(&mut stack, &versions, algorithms); + + // Test wrong header + let header = SpdmMsgHdr::new(SpdmVersion::V12, crate::protocol::ReqRespCode::Version); + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let mut msg = MessageBuf::new(&mut msg_buf); + let version_response = VersionRespCommon::new(2); + let resp_common_size = version_response.encode(&mut msg).unwrap(); + let entr_1 = VersionNumberEntry::new(SpdmVersion::V11); + let e1_size = entr_1.encode(&mut msg).unwrap(); + msg.push_data(resp_common_size + e1_size).unwrap(); + assert_eq!(msg.data_len(), (resp_common_size + e1_size)); + + let rsp = process_version(&mut context, header, &mut msg); + assert!(rsp.is_err_and(|e| e.1 == CommandError::UnsupportedResponse)); + + // Test response without entries + let header = SpdmMsgHdr::new(SpdmVersion::V12, crate::protocol::ReqRespCode::Version); + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let mut msg = MessageBuf::new(&mut msg_buf); + let version_response = VersionRespCommon::new(0); + let resp_common_size = version_response.encode(&mut msg).unwrap(); + msg.push_data(resp_common_size).unwrap(); + + let rsp = process_version(&mut context, header, &mut msg); + assert!(rsp.is_err_and(|e| e.1 == CommandError::UnsupportedResponse)); + + // Test unsupported version + let header = SpdmMsgHdr::new(SpdmVersion::V12, crate::protocol::ReqRespCode::Version); + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let mut msg = MessageBuf::new(&mut msg_buf); + let version_response = VersionRespCommon::new(1); + let resp_common_size = version_response.encode(&mut msg).unwrap(); + let mut entr_1 = VersionNumberEntry::new(SpdmVersion::V10); + entr_1.set_major(9); // unsupported version + let e1_size = entr_1.encode(&mut msg).unwrap(); + msg.push_data(resp_common_size + e1_size).unwrap(); + assert_eq!(msg.data_len(), (resp_common_size + e1_size)); + + let rsp = process_version(&mut context, header, &mut msg); + assert!(rsp.is_err_and(|e| e.1 == CommandError::UnsupportedResponse)); + } + + #[test] + fn validate_get_version_request() { + let versions = versions_default(); + let mut stack = MockResources::new(); + let algorithms = crate::protocol::LocalDeviceAlgorithms::default(); + let mut context = create_context(&mut stack, &versions, algorithms); + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let mut msg = MessageBuf::new(&mut msg_buf); + + assert!(generate_get_version(&mut context, &mut msg, VersionReqPayload::new(0, 0)).is_ok()); + + let data = msg.total_message(); + assert_eq!(data.len(), 4, "GET_VERSION command length mismatch"); + let req_version: SpdmVersion = data[0].try_into().unwrap(); + assert_eq!(req_version, SpdmVersion::V10); + + assert_eq!(data[1], 0x84, "Command code doesn't match GET_VERSION"); + } } From 8e4f166841e4248615d984f9536daaa6234045a9 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 27 Jan 2026 14:33:48 +0100 Subject: [PATCH 16/86] Happy path test for CAPABILITIES response handling --- src/commands/capabilities/request.rs | 41 ++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 7d54425..f64108a 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -176,11 +176,46 @@ pub fn generate_capabilities_request_local<'a>( #[cfg(test)] mod tests { + use super::*; + use crate::{ + protocol::{CapabilityFlags, MAX_MCTP_SPDM_MSG_SIZE}, + test::*, + }; #[test] - #[ignore] - fn test_generate_capabilities_request() { - todo!(); + fn test_handle_capabilities_response_happy_path() { + let versions = versions_default(); + let mut stack = MockResources::new(); + let algorithms = crate::protocol::LocalDeviceAlgorithms::default(); + let mut context = create_context(&mut stack, &versions, algorithms); + + context + .state + .connection_info + .set_version_number(SpdmVersion::V12); + context + .state + .connection_info + .set_state(crate::state::ConnectionState::AfterVersion); + + let header = SpdmMsgHdr::new(SpdmVersion::V12, crate::protocol::ReqRespCode::Capabilities); + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let mut msg = MessageBuf::new(&mut msg_buf); + let mut len = 0; + let cap_base = GetCapabilitiesBase::default(); + len += cap_base.encode(&mut msg).unwrap(); + let cap_11 = GetCapabilitiesV11::new(10, CapabilityFlags::default()); + len += cap_11.encode(&mut msg).unwrap(); + let cap_12 = GetCapabilitiesV12 { + data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, + max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, + }; + len += cap_12.encode(&mut msg).unwrap(); + msg.push_data(len).unwrap(); + + handle_capabilities_response(&mut context, header, &mut msg) + .expect("Failed to handle capabilities response"); } } From 96481c8fcc582a8ba239c695d5ae72a2f4b5ef22 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 27 Jan 2026 14:34:32 +0100 Subject: [PATCH 17/86] Improve CAPABILITIES response handling --- src/commands/capabilities/mod.rs | 19 ++++++++++++++++ src/commands/capabilities/request.rs | 34 ++++++++++++++++------------ src/error.rs | 3 +++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs index d0315af..808e5dc 100644 --- a/src/commands/capabilities/mod.rs +++ b/src/commands/capabilities/mod.rs @@ -38,9 +38,13 @@ pub struct GetCapabilitiesV11 { pub ct_exponent: u8, /// Reserved. + /// + /// _TODO_: Part of the 16-bit extended flags field added in v1.4.0 reserved2: u8, /// Reserved. + /// + /// _TODO_: Part of the 16-bit extended flags field added in v1.4.0 reserved3: u8, /// Capability flags. @@ -99,6 +103,15 @@ impl From<&DeviceCapabilities> for GetCapabilitiesV12 { } } +impl Default for GetCapabilitiesV12 { + fn default() -> Self { + GetCapabilitiesV12 { + data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, + max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, + } + } +} + /// Checks if the request capability flags are compatible with the SPDM version ///# Arguments /// - `version`: SPDM version @@ -113,6 +126,12 @@ pub(crate) fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) return false; } + // Check if MEAS_CAP is valid + // 0b11 is reserved + if flags.meas_cap() == 0b11 { + return false; + } + // Checks common to 1.1 and higher if version >= SpdmVersion::V11 { // Illegal to return reserved values (2 and 3) diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index f64108a..c60b3bb 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -4,25 +4,15 @@ use crate::commands::error_rsp::ErrorCode; use crate::{codec::MessageBuf, context::SpdmContext, error::CommandResult, protocol::SpdmMsgHdr}; use crate::commands::capabilities::{ - req_flag_compatible, CapabilityFlags, GetCapabilitiesBase, GetCapabilitiesV11, - GetCapabilitiesV12, + req_flag_compatible, GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12, }; use crate::protocol::{capabilities::DeviceCapabilities, ReqRespCode, SpdmVersion}; -use crate::error::{CommandError, SpdmError}; +use crate::error::CommandError; use crate::transcript::TranscriptContext; use crate::codec::Codec; -/// Generate the GET_CAPABILITIES command with all the contexts information -// pub fn send_get_capabilities<'a>( -// ctx: &mut SpdmContext<'a>, -// req_buf: &mut MessageBuf<'a>, -// payload: -// ) -> CommandResult<()> { -// todo!(); -// } - /// Requester function handling the parsing of the CAPABILITIES response sent by the Responder. /// /// # Returns @@ -30,24 +20,28 @@ use crate::codec::Codec; /// /// #TODO /// - [ ] A Responder can report that it needs to transmit the response in smaller -/// transfers by sending an ERROR message of ErrorCode=LargeResponse +/// transfers by sending an ERROR message of ErrorCode=LargeResponse pub(crate) fn handle_capabilities_response<'a>( ctx: &mut SpdmContext<'a>, resp_header: SpdmMsgHdr, resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { + // TODO: I don't think we should call _generate_error_response_ here for every error. + // Instead just returning proper error codes is probably better. + let version_hdr = match resp_header.version() { Ok(v) => v, Err(_) => Err(ctx.generate_error_response(resp, ErrorCode::VersionMismatch, 0, None))?, }; // Verify that the version is supported by both parties + // TODO: Should responses that don't match the negotiated version be silently accepted? let version = match ctx.supported_versions.iter().find(|&&v| v == version_hdr) { Some(&v) => v, None => Err(ctx.generate_error_response(resp, ErrorCode::VersionMismatch, 0, None))?, }; - let base_resp = GetCapabilitiesBase::decode(resp) + let _base_resp = GetCapabilitiesBase::decode(resp) .map_err(|_| ctx.generate_error_response(resp, ErrorCode::OperationFailed, 0, None))?; // Based on the negotiated version, try to decode the rest of the response. @@ -61,6 +55,7 @@ pub(crate) fn handle_capabilities_response<'a>( .map_err(|_| ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; peer_capabilities.ct_exponent = resp_11.ct_exponent; + // TODO? let flags = resp_11.flags; // THIS FAILS // if !req_flag_compatible(version, &flags) { @@ -72,6 +67,17 @@ pub(crate) fn handle_capabilities_response<'a>( let resp_12 = GetCapabilitiesV12::decode(resp).map_err(|_| { ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None) })?; + + // _DataTransferSize_ shall be equal to or greater than _MinDataTransferSize_ + if resp_12.data_transfer_size < crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 { + return Err((false, CommandError::InvalidResponse)); + } + + // _MaxSPDMmsgSize_ should be greater than or equal to _DataTransferSize_ + if resp_12.max_spdm_msg_size < resp_12.data_transfer_size { + return Err((false, CommandError::InvalidResponse)); + } + peer_capabilities.data_transfer_size = resp_12.data_transfer_size; peer_capabilities.max_spdm_msg_size = resp_12.max_spdm_msg_size; } diff --git a/src/error.rs b/src/error.rs index 6a17c2e..be8c42e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,6 +29,7 @@ pub type SpdmResult = Result; pub type CommandResult = Result; +#[non_exhaustive] #[derive(Debug, PartialEq)] pub enum PlatformError { HashError(SpdmHashError), @@ -36,6 +37,7 @@ pub enum PlatformError { EvidenceError(SpdmEvidenceError), } +#[non_exhaustive] #[derive(Debug, PartialEq)] pub enum CommandError { BufferTooSmall, @@ -50,4 +52,5 @@ pub enum CommandError { Platform(PlatformError), Transcript(TranscriptError), Measurement(MeasurementsError), + InvalidResponse, } From 3b70297a0d73af97032c1d301ffde5211a12062e Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 28 Jan 2026 12:21:19 +0100 Subject: [PATCH 18/86] Add test for CAPABILITY response error case handling --- src/commands/capabilities/request.rs | 101 ++++++++++++++++++++++++++- src/protocol/common.rs | 2 +- src/test.rs | 2 +- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index c60b3bb..87a732f 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -182,7 +182,6 @@ pub fn generate_capabilities_request_local<'a>( #[cfg(test)] mod tests { - use super::*; use crate::{ protocol::{CapabilityFlags, MAX_MCTP_SPDM_MSG_SIZE}, @@ -224,4 +223,104 @@ mod tests { handle_capabilities_response(&mut context, header, &mut msg) .expect("Failed to handle capabilities response"); } + + #[test] + fn test_handle_capabilities_response_error_cases() { + let versions = versions_default(); + let mut stack = MockResources::new(); + let algorithms = crate::protocol::LocalDeviceAlgorithms::default(); + let mut context = create_context(&mut stack, &versions, algorithms); + + context + .state + .connection_info + .set_version_number(SpdmVersion::V13); + context + .state + .connection_info + .set_state(crate::state::ConnectionState::AfterVersion); + + let header = SpdmMsgHdr::new(SpdmVersion::V13, crate::protocol::ReqRespCode::Capabilities); + + // Encode invalid MEAS_CAP flag + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let cap_base = GetCapabilitiesBase::default(); + let mut cap_flags = CapabilityFlags::default(); + cap_flags.set_meas_cap(0b11); // 0x11 is reserved + let cap_12 = GetCapabilitiesV12::default(); + let mut msg = prepare_response(&mut msg_buf, cap_base, cap_flags, 10, cap_12); + + let res = handle_capabilities_response(&mut context, header.clone(), &mut msg); + if let Err((_, e)) = res { + assert_eq!( + e, + CommandError::ErrorCode(ErrorCode::InvalidPolicy), + "Expected invalid policy error, got {e:?}" + ); + } else { + panic!("Expected invalid policy error, got OK(())") + } + + // Test invalid v1.2 fields + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let cap_base = GetCapabilitiesBase::default(); + let cap_flags = CapabilityFlags::default(); + let cap_12 = GetCapabilitiesV12 { + data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 - 1, + max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, + }; + let mut msg = prepare_response(&mut msg_buf, cap_base, cap_flags, 10, cap_12); + + let res = handle_capabilities_response(&mut context, header.clone(), &mut msg); + if let Err((_, e)) = res { + assert_eq!( + e, + CommandError::InvalidResponse, + "Expected invalid response error, got {e:?}" + ); + } else { + panic!("Expected invalid response error, got OK(())") + } + + let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; + let cap_base = GetCapabilitiesBase::default(); + let cap_flags = CapabilityFlags::default(); + let cap_12 = GetCapabilitiesV12 { + data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, + max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 - 1, + }; + let mut msg = prepare_response(&mut msg_buf, cap_base, cap_flags, 10, cap_12); + + let res = handle_capabilities_response(&mut context, header, &mut msg); + if let Err((_, e)) = res { + assert_eq!( + e, + CommandError::InvalidResponse, + "Expected invalid response error, got {e:?}" + ); + } else { + panic!("Expected invalid response error, got OK(())") + } + } + + fn prepare_response<'a>( + buf: &'a mut [u8], + cap_base: GetCapabilitiesBase, + cap_flags: CapabilityFlags, + ct_exp: u8, + cap_12: GetCapabilitiesV12, + ) -> MessageBuf<'a> { + let mut msg = MessageBuf::new(buf); + let mut len = 0; + + len += cap_base.encode(&mut msg).unwrap(); + len += GetCapabilitiesV11::new(ct_exp, cap_flags) + .encode(&mut msg) + .unwrap(); + len += cap_12.encode(&mut msg).unwrap(); + + msg.push_data(len).unwrap(); + + msg + } } diff --git a/src/protocol/common.rs b/src/protocol/common.rs index 1a73783..620fccd 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -85,7 +85,7 @@ impl ReqRespCode { } } -#[derive(FromBytes, IntoBytes, Immutable)] +#[derive(FromBytes, IntoBytes, Immutable, Clone)] #[repr(C)] pub(crate) struct SpdmMsgHdr { version: u8, diff --git a/src/test.rs b/src/test.rs index 02c125a..03f7eda 100644 --- a/src/test.rs +++ b/src/test.rs @@ -154,7 +154,7 @@ impl SpdmTransport for MockTransport { } fn header_size(&self) -> usize { - todo!() + 0 } fn init_sequence(&mut self) -> crate::platform::transport::TransportResult<()> { From 0738c3b29cee413ffc273f70119eaeb723ca31bb Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 28 Jan 2026 13:40:43 +0100 Subject: [PATCH 19/86] Fix test/spdm_validator_host build --- tests/spdm_validator_host.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/spdm_validator_host.rs b/tests/spdm_validator_host.rs index 0b92bcb..8742224 100644 --- a/tests/spdm_validator_host.rs +++ b/tests/spdm_validator_host.rs @@ -38,6 +38,10 @@ impl SpdmTransport for MockTransport { fn header_size(&self) -> usize { 0 } + + fn init_sequence(&mut self) -> TransportResult<()> { + todo!() + } } struct MockHash { From 72c5f37d9aea15068bb0ee84081c62d09d8800d9 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 15 Jan 2026 12:17:21 +0100 Subject: [PATCH 20/86] wip Signed-off-by: leongross --- examples/platform/socket_transport.rs | 3 - examples/spdm_requester.rs | 82 ++++++++---- src/commands/algorithms/mod.rs | 107 +++++++++++---- src/commands/algorithms/request.rs | 179 ++++++++++++++++++++++++-- src/commands/capabilities/request.rs | 6 +- src/commands/capabilities/response.rs | 1 + src/context.rs | 2 +- src/protocol/capabilities.rs | 6 + src/state.rs | 2 +- 9 files changed, 318 insertions(+), 70 deletions(-) diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index dbfe602..84421a6 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -323,7 +323,6 @@ impl SpdmSocketTransport { } .into(); - dbg!(&header_bytes); self.stream.write_all(&header_bytes)?; // Send data if any @@ -331,8 +330,6 @@ impl SpdmSocketTransport { self.stream.write_all(data)?; } - dbg!(&data); - self.stream.flush()?; Ok(()) } diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index d46a156..323aa03 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -25,7 +25,7 @@ mod platform; use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; use spdm_lib::commands::algorithms::{ - request::generate_negotiate_algorithms_request, AlgStructure, ExtendedAlgo, + request::generate_negotiate_algorithms_request, AlgStructure, AlgType, ExtendedAlgo, RegistryId, }; use spdm_lib::commands::capabilities::request::generate_capabilities_request_local; use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; @@ -68,10 +68,18 @@ fn create_device_capabilities() -> DeviceCapabilities { flags, data_transfer_size: 1024, max_spdm_msg_size: 4096, + include_supported_algorithms: false, } } /// Create local device algorithms +/// +/// Default values are: +/// - Measurement Specification: DMTF (1) +/// - Measurement Hash Algorithm: TPM_ALG_SHA_384 (1) +/// - Base Asymmetric Algorithm: TPM_ALG_ECDSA_ECC_NIST_P384 +/// - Base Hash Algorithm: TPM_ALG_SHA_384 (1) +/// - MEL Specification: 0 fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { // Configure supported algorithms with proper bitfield construction let mut measurement_spec = MeasurementSpecification::default(); @@ -92,24 +100,15 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { measurement_hash_algo, base_asym_algo, base_hash_algo, - mel_specification: MelSpecification::default(), - dhe_group: DheNamedGroup::default(), - aead_cipher_suite: AeadCipherSuite::default(), - req_base_asym_algo: ReqBaseAsymAlg::default(), + mel_specification: MelSpecification(1), + dhe_group: DheNamedGroup(1), // FFDHE2048 + aead_cipher_suite: AeadCipherSuite(1), // AES_128_GCM + req_base_asym_algo: ReqBaseAsymAlg(1), key_schedule: KeySchedule::default(), }; - let algorithm_priority_table = AlgorithmPriorityTable { - measurement_specification: None, - opaque_data_format: None, - base_asym_algo: None, - base_hash_algo: None, - mel_specification: None, - dhe_group: None, - aead_cipher_suite: None, - req_base_asym_algo: None, - key_schedule: None, - }; + // Keep this empty for now, since we need to wait for responders answer. + let algorithm_priority_table = AlgorithmPriorityTable::default(); LocalDeviceAlgorithms { device_algorithms, @@ -135,7 +134,11 @@ fn vca_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { let evidence = DemoEvidence::new(); // Create SPDM context - let supported_versions = [version::SpdmVersion::V12, version::SpdmVersion::V11]; + let supported_versions = [ + version::SpdmVersion::V13, + version::SpdmVersion::V12, + version::SpdmVersion::V11, + ]; let capabilities = create_device_capabilities(); let algorithms = create_local_algorithms(); @@ -248,17 +251,39 @@ fn vca_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .requester_process_message(&mut message_buffer) .unwrap(); - message_buffer.reset(); + if config.verbose { + println!( + "Processed CAPABILITIES: {:?}", + &message_buffer.message_data() + ); + } - // 3.1 Send GET_AUTH - // TODO: use local algorithms from the context + let ext_asym = [ExtendedAlgo::new(RegistryId::DMTF, 1)]; + let ext_hash = [ExtendedAlgo::new(RegistryId::DMTF, 1)]; + let alg_external = [ExtendedAlgo::new(RegistryId::DMTF, 1)]; + // TODO: since we re-generate them there is the potential issue of TOCTOU. + let mut local_algorithms = create_local_algorithms(); + local_algorithms + .device_algorithms + .base_asym_algo + .set_tpm_alg_rsapss_2048(1); + local_algorithms + .device_algorithms + .base_hash_algo + .set_tpm_alg_sha_256(1); + + let mut alg_structure = AlgStructure::new(&AlgType::Dhe, &local_algorithms); + alg_structure.set_ext_alg_count(1); + + // 3.1 Send GET_ALGORITHMS + message_buffer.reset(); generate_negotiate_algorithms_request( &mut spdm_context, &mut message_buffer, - None, - None, - &AlgStructure(0), - None, + Some(&ext_asym), + Some(&ext_hash), + alg_structure, + Some(&alg_external), ) .unwrap(); @@ -267,13 +292,20 @@ fn vca_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .unwrap(); if config.verbose { - println!("GET_AUTH: {:?}", &message_buffer.message_data()); + println!( + "NEGOTIATE_ALGORITHMS: {:x?}", + &message_buffer.message_data() + ); } spdm_context .requester_process_message(&mut message_buffer) .unwrap(); + if config.verbose { + println!("ALGORITHMS: {:x?}", &message_buffer.message_data()); + } + Ok(()) } diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs index 5076031..700ba9d 100644 --- a/src/commands/algorithms/mod.rs +++ b/src/commands/algorithms/mod.rs @@ -16,7 +16,8 @@ pub(crate) use request::*; pub(crate) use response::*; use crate::codec::{CommonCodec, MessageBuf}; -use crate::protocol::SpdmVersion; +use crate::protocol::LocalDeviceAlgorithms; +use crate::protocol::{algorithms::DheNamedGroup, SpdmMsgHdr, SpdmVersion}; use bitfield::bitfield; use core::mem::size_of; @@ -51,7 +52,7 @@ const MAX_SPDM_EXT_ALG_COUNT_V13: u8 = 20; /// - ReqAlgStruct (AlgStructSize), see [AlgStructure]. struct NegotiateAlgorithmsReq { /// The number of algorithm structure tables in this request using `ReqAlgStruct`. - num_alg_struct_tables: u8, + num_alg_struct_tables: u8, // param 1 /// Reserved. param2: u8, @@ -67,6 +68,7 @@ struct NegotiateAlgorithmsReq { measurement_specification: MeasurementSpecification, /// Bit mask listing other parameters supported by the Requester. + /// Introduced in v1.2. /// /// See [OtherParamSupport] for details. other_param_support: OtherParamSupport, @@ -113,6 +115,7 @@ struct NegotiateAlgorithmsReq { /// The Requester shall set the corresponding bit for each supported measurement /// extension log (MEL) specification. + /// Introduced in v1.3. /// /// See [MelSpecification] for details. mel_specification: MelSpecification, @@ -127,6 +130,7 @@ impl NegotiateAlgorithmsReq { /// the SPDM specification description of the fields provided. /// /// It does *NOT* validate the total extended algorithm count against the SPDM version. + #[allow(clippy::too_many_arguments)] pub fn new( num_alg_struct_tables: u8, param2: u8, @@ -140,7 +144,7 @@ impl NegotiateAlgorithmsReq { ) -> SpdmResult { let mut req = NegotiateAlgorithmsReq { num_alg_struct_tables, - param2: param2, + param2, length: 0, measurement_specification, other_param_support, @@ -168,10 +172,12 @@ impl NegotiateAlgorithmsReq { let total_alg_struct_len = size_of::() * self.num_alg_struct_tables as usize; let total_ext_asym_len = size_of::() * self.ext_asym_count as usize; let total_ext_hash_len = size_of::() * self.ext_hash_count as usize; - (size_of::() + (size_of::() + + size_of::() + total_alg_struct_len + total_ext_asym_len - + total_ext_hash_len) as u16 + + total_ext_hash_len + + 4) as u16 } /// Calculate the size of the extended algorithm structures in bytes. @@ -216,12 +222,28 @@ impl CommonCodec for NegotiateAlgorithmsReq {} #[derive(IntoBytes, FromBytes, Immutable, Default)] #[repr(C, packed)] #[allow(dead_code)] -struct AlgorithmsResp { +/// # NOTE +/// After this response we expect to be present when sent: +/// - ExtAsymSel +/// - ExtHashSel +/// - RespAlgStruct +pub struct AlgorithmsResp { + /// Shall be the number of algorithm structure tables in this request using RespAlgStruct. num_alg_struct_tables: u8, reserved_1: u8, + + /// Shall be the length of the response message, in bytes. length: u16, + + /// The Responder shall select one of the measurement specifications supported by the + /// Requester and Responder. Thus, no more than one bit shall be set measurement_specification_sel: MeasurementSpecification, + + /// Shall be the selected Parameter Bit Mask. The Responder shall select one + /// of the opaque data formats supported by the Requester. Thus, no more + /// than one bit shall be set for the opaque data format. other_params_selection: OtherParamSupport, + measurement_hash_algo: MeasurementHashAlgo, base_asym_sel: BaseAsymAlgo, base_hash_sel: BaseHashAlgo, @@ -230,12 +252,16 @@ struct AlgorithmsResp { ext_asym_sel_count: u8, ext_hash_sel_count: u8, reserved_3: [u8; 2], + // - ExtAsymSel + // - ExtHashSel + // - RespAlgStruct } impl CommonCodec for AlgorithmsResp {} #[derive(IntoBytes, FromBytes, Immutable, Default)] #[repr(C)] +/// See [DSP0274 v1.3.0, p, 86](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.3.0.pdf) pub struct ExtendedAlgo { /// Shall represent the registry or standards body. /// @@ -253,11 +279,20 @@ pub struct ExtendedAlgo { impl CommonCodec for ExtendedAlgo {} +impl ExtendedAlgo { + pub fn new(registry_id: RegistryId, algorithm_id: u16) -> Self { + ExtendedAlgo { + registry_id: registry_id as u8, + reserved: 0, + algorithm_id, + } + } +} + /// Registry or standards body ID for algorithm encoding in extended algorithm fields. /// Consult the respective registry or standards body unless otherwise specified. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] pub enum RegistryId { /// DMTF does not have a Vendor ID registry. DMTF = 0x0, @@ -322,7 +357,7 @@ impl RegistryId { } #[derive(Debug, Clone, Copy)] -enum AlgType { +pub enum AlgType { // 0x00 and 0x01. Reserved. Dhe = 2, AeadCipherSuite = 3, @@ -396,14 +431,15 @@ bitfield! { #[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] #[repr(C)] /// This structure describes an algorithm structure table. - /// It does **NOT** include the variable-length `AlgExternal` fields that follow this header. + /// It does **NOT** include the variable-length `AlgExternal` fields that follow this header for the Request. /// /// The `AlgExternal` fields are of type [ExtendedAlgo] and their number is defined /// by the `ExtAlgCount` field. /// The existence of `AlgExternal` is optional. // TODO: make this a structure with Option? - pub struct AlgStructure(u16); + pub struct AlgStructure(u32); impl Debug; + u8; /// Shall be the type of algorithm. /// /// See [AlgType] for details. @@ -414,12 +450,46 @@ bitfield! { /// That means, that there is either 1 or None external algorithm structure following this header. pub ext_alg_count, set_ext_alg_count: 11, 8; pub fixed_alg_count, set_fixed_alg_count: 15, 12; + u16; + // TODO: somehow we can just assume this will fit? and why do we + pub alg_supported, set_alg_supported: 31, 16; + // AlgExternal } impl AlgStructure { // FixedAlgCount + 2 shall be a multiple of 4 pub fn is_multiple(&self) -> bool { - ((self.fixed_alg_count() as usize) + 2) % 4 == 0 + ((self.fixed_alg_count() as usize) + 2).is_multiple_of(4) + } + + /// Create a new [AlgStructure] for the given algorithm type as specified in + // Tables 17, 18, 19, 20 of DSP0274 v1.3.0 + pub fn new(alg_type: &AlgType, local_algos: &LocalDeviceAlgorithms) -> AlgStructure { + let mut res = AlgStructure::default(); + res.set_alg_type(*alg_type as u8); + + // Bit [7:4]. Shall be a value of 2. + res.set_fixed_alg_count(2); + + match alg_type { + AlgType::Dhe => { + res.set_alg_supported(local_algos.device_algorithms.dhe_group.0); + } + + AlgType::AeadCipherSuite => { + res.set_alg_supported(local_algos.device_algorithms.aead_cipher_suite.0); + } + + AlgType::ReqBaseAsymAlg => { + res.set_alg_supported(local_algos.device_algorithms.req_base_asym_algo.0); + } + + AlgType::KeySchedule => { + res.set_alg_supported(local_algos.device_algorithms.key_schedule.0); + } + } + res.set_ext_alg_count(res.alg_supported().count_ones() as u8); + res } } @@ -431,19 +501,6 @@ mod tests { #[test] fn test_min_req_len() { - let req = NegotiateAlgorithmsReq { - num_alg_struct_tables: 2, - ext_asym_count: 3, - ext_hash_count: 4, - ..Default::default() - }; - - let expected_len = size_of::() - + (size_of::() * 2) - + (size_of::() * 3) - + (size_of::() * 4); - - // v1.1: (66 = 66) - assert_eq!(req.min_req_len() as usize, expected_len); + todo!(); } } diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index b39994d..16ee47b 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -2,12 +2,13 @@ use crate::{ codec::{Codec, MessageBuf}, - commands::algorithms::{AlgStructure, ExtendedAlgo, NegotiateAlgorithmsReq}, + commands::algorithms::{AlgStructure, AlgorithmsResp, ExtendedAlgo, NegotiateAlgorithmsReq}, + commands::error_rsp::ErrorCode, context::SpdmContext, error::{CommandError, CommandResult, SpdmError}, protocol::{ - BaseAsymAlgo, BaseAsymAlgoType, BaseHashAlgo, BaseHashAlgoType, MeasurementSpecification, - OtherParamSupport, SpdmMsgHdr, SpdmVersion, + BaseAsymAlgo, BaseAsymAlgoType, BaseHashAlgo, BaseHashAlgoType, DeviceAlgorithms, + MeasurementSpecification, OtherParamSupport, SpdmMsgHdr, SpdmVersion, }, }; @@ -17,29 +18,149 @@ pub fn handle_algorithms_response<'a>( resp_header: SpdmMsgHdr, resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { + let version = resp_header + .version() + .map_err(|_| (true, CommandError::UnsupportedRequest))?; + + let req_resp_code = resp_header + .req_resp_code() + .map_err(|_| (true, CommandError::UnsupportedRequest))?; + + if version != ctx.state.connection_info.version_number() { + return Err((true, CommandError::UnsupportedRequest)); + } + if req_resp_code != crate::protocol::ReqRespCode::Algorithms { + return Err((true, CommandError::UnsupportedRequest)); + } + + let algo_resp: AlgorithmsResp = + AlgorithmsResp::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; + + // Responder MUST set all algorithm fields to non-zero values, otherwise the requester MUST return an error with code SPDM_ERROR_CODE_REQUEST_RESYNCH. + if algo_resp.measurement_hash_algo.0 == 0 + || algo_resp.base_asym_sel.0 == 0 + || algo_resp.base_hash_sel.0 == 0 + { + return Err((true, CommandError::ErrorCode(ErrorCode::RequestResynch))); + } + + // Thus, no more than one bit shall be set + if algo_resp.measurement_specification_sel.0.count_ones() > 1 { + return Err((true, CommandError::ErrorCode(ErrorCode::InvalidPolicy))); + } + + // Thus, no more than one bit shall be set for the opaque data format. + if algo_resp.other_params_selection.opaque_data_fmt0() == 1 + && algo_resp.other_params_selection.opaque_data_fmt1() == 1 + { + return Err((true, CommandError::ErrorCode(ErrorCode::InvalidPolicy))); + } + + let cap = ctx + .state + .connection_info + .peer_capabilities() + .flags + .meas_cap(); + + // If the Responder supports measurements ( MEAS_CAP=01b or MEAS_CAP=10b in its + // CAPABILITIES response) and if MeasurementSpecificationSel is non-zero, + // then exactly one bit in this bit field shall be set. Otherwise, the Responder + // shall set this field to 0. + if cap != 0 + && algo_resp.measurement_specification_sel.0 != 0 + && algo_resp.measurement_specification_sel.0.count_ones() > 1 + { + return Err((true, CommandError::ErrorCode(ErrorCode::InvalidPolicy))); + } + + // If the Responder supports measurements in its CAPABILITIES response) and if + // MeasurementSpecificationSel is non-zero, then exactly one bit in this bit + // field shall be set. Otherwise, the Responder shall set this field to 0 + if (cap == 0b01 || cap == 0b10) && algo_resp.measurement_specification_sel.0 != 0 { + if algo_resp.measurement_specification_sel.0.count_ones() > 1 { + return Err((true, CommandError::ErrorCode(ErrorCode::InvalidPolicy))); + } + } else if algo_resp.measurement_specification_sel.0 != 0 { + return Err((true, CommandError::ErrorCode(ErrorCode::InvalidPolicy))); + } + + // TODO: If the Responder does not support any request/response pair that + // requires hashing operations, this value shall be set to zero. The Responder + // shall set no more than one bit. + + let mut peer_device_algorithms = DeviceAlgorithms::default(); + peer_device_algorithms.measurement_spec = algo_resp.measurement_specification_sel; + peer_device_algorithms.other_param_support = algo_resp.other_params_selection; + peer_device_algorithms.base_asym_algo = algo_resp.base_asym_sel; + peer_device_algorithms.base_hash_algo = algo_resp.base_hash_sel; + peer_device_algorithms.mel_specification = algo_resp.mel_specification_sel; + + ctx.state + .connection_info + .set_peer_algorithms(peer_device_algorithms); + + // The spec defines this is A' elem of {0, 1} + // TODO: add them to state? + let ext_asym_alog = if algo_resp.ext_asym_sel_count == 1 { + Some(ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?) + } else { + None + }; + + // The spec defines this is E' elem of {0, 1} + // TODO: add them to state? + let ext_hash_algo = if algo_resp.ext_hash_sel_count == 1 { + Some(ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?) + } else { + None + }; + + for i in 0..algo_resp.num_alg_struct_tables { + let alg_struct = AlgStructure::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; + + // For each struct table, we need to decode the variable length fields. + // TODO: add them to state? + for _ in 0..alg_struct.ext_alg_count() { + let ext_algo = + ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; + } + } + + ctx.state + .connection_info + .set_state(crate::state::ConnectionState::AlgorithmsNegotiated); + Ok(()) } /// Generate the NEGOTIATE_ALGORITHMS request with all the contexts local information. /// /// # Arguments +/// /// - `ctx` - The SPDM context containing local algorithm information. /// - `req_buf` - The message buffer to encode the request into. /// - `ext_asym` - Optional slice of extended asymmetric algorithm types. /// - `ext_hash` - Optional slice of extended hash algorithm types. -/// - `ext_algo` - The AlgStructure variable fields. -/// - `ext_algo_ext` - Optional extended algorithm structure. +/// - `req_alg_struct` - The AlgStructure variable fields. +/// - `alg_external` - Optional extended algorithm structure. /// /// # Returns +/// /// - `Ok(())` on success. /// - [CommandError] on failure. +/// +/// # References +/// - See libspdm/library/spdm_requester_lib/libspdm_req_negotiate_algorithms.c for reference implementation. +/// - Note: the `spdm_message_header_t` has param1 and param2 fields used for various purposes. +/// - Note: see spdm_responder_test_3_algorithms.c pub fn generate_negotiate_algorithms_request<'a>( ctx: &mut SpdmContext<'a>, req_buf: &mut MessageBuf<'a>, ext_asym: Option<&'a [ExtendedAlgo]>, ext_hash: Option<&'a [ExtendedAlgo]>, - ext_algo: &AlgStructure, - ext_algo_ext: Option, + req_alg_struct: AlgStructure, + alg_external: Option<&'a [ExtendedAlgo]>, // req_alg_struct.AlgCount.ExtAlgCount many ) -> CommandResult<()> { let local_algorithms = &ctx.local_algorithms.device_algorithms; let local_state = &ctx.state.connection_info; @@ -53,8 +174,9 @@ pub fn generate_negotiate_algorithms_request<'a>( None => 0, }; + // Generate base structure **without** the variable length structures let negotiate_algorithms_req = NegotiateAlgorithmsReq::new( - ext_algo.ext_alg_count() as u8, + req_alg_struct.ext_alg_count(), 0, // param2 local_algorithms.measurement_spec, local_algorithms.other_param_support, @@ -79,6 +201,15 @@ pub fn generate_negotiate_algorithms_request<'a>( } // Message Assembly + // 1. Create Header + // 2. Encode base NegotiateAlgorithmsReq + // 3. Encode Variable length fields + // 3.1 Encode ExtAsym (if present) + // 3.2 Encode ExtHash (if present) + // 3.3 Encode ExtAlgo (if present) + // 3.4 Encode ExtendedAlgorithms (if present) + + // 1. SpdmMsgHdr::new( ctx.state.connection_info.version_number(), crate::protocol::ReqRespCode::NegotiateAlgorithms, @@ -86,6 +217,7 @@ pub fn generate_negotiate_algorithms_request<'a>( .encode(req_buf) .map_err(|e| (false, CommandError::Codec(e)))?; + // 2. // This encoding does *NOT* yet contain the variable fields. negotiate_algorithms_req .encode(req_buf) @@ -94,6 +226,8 @@ pub fn generate_negotiate_algorithms_request<'a>( // Add variable fields if any. As defined by the size of the struct and the // PLMD spec, we know that the offset starts at 32 bytes. // The constructor of NegotiateAlgorithmsReq sets the structs length correctly. + + // 3.1 if let Some(ext_asym_algos) = ext_asym { for ext in ext_asym_algos { ext.encode(req_buf) @@ -101,6 +235,7 @@ pub fn generate_negotiate_algorithms_request<'a>( } } + // 3.2 if let Some(ext_hash_algos) = ext_hash { for ext in ext_hash_algos { ext.encode(req_buf) @@ -108,25 +243,41 @@ pub fn generate_negotiate_algorithms_request<'a>( } } - if ext_algo.fixed_alg_count() != 0 && !ext_algo.is_multiple() { + // 3.2 + if req_alg_struct.fixed_alg_count() != 0 && !req_alg_struct.is_multiple() { return Err((false, CommandError::UnsupportedRequest)); } // If this is 1, we have an additional extended algorithm structure to add. if negotiate_algorithms_req.num_alg_struct_tables > 0 { - ext_algo + // 3.3 + req_alg_struct .encode(req_buf) .map_err(|e| (false, CommandError::Codec(e)))?; - if let Some(ext) = ext_algo_ext { - ext.encode(req_buf) - .map_err(|e| (false, CommandError::Codec(e)))?; + // 3.4 + if let Some(extended_algos) = alg_external { + for ext in extended_algos { + ext.encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + } } else { // If ext_alg_count > 0, we must have the extended algorithm structure. - // TODO: fixup AlgStructure with downside of custom encode. return Err((false, CommandError::UnsupportedRequest)); } } Ok(()) } + +#[cfg(test)] +mod tests { + use crate::test::MockResources; + + use super::*; + + #[test] + pub fn test_parse_negotiate_algorithms() { + todo!(); + } +} diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 87a732f..7960fc7 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -161,7 +161,7 @@ pub fn generate_capabilities_request_local<'a>( req_buf: &mut MessageBuf<'a>, ) -> CommandResult<()> { let local_capabilities = ctx.local_capabilities; - let capabilities = GetCapabilitiesBase::default(); + let mut capabilities = GetCapabilitiesBase::default(); let capv11 = Some(GetCapabilitiesV11::new( local_capabilities.ct_exponent, @@ -177,6 +177,10 @@ pub fn generate_capabilities_request_local<'a>( None }; + if ctx.state.connection_info.version_number() >= SpdmVersion::V13 { + capabilities.param1 |= (local_capabilities.include_supported_algorithms as u8) << 2; + } + generate_capabilities_request(ctx, req_buf, capabilities, capv11, capv12) } diff --git a/src/commands/capabilities/response.rs b/src/commands/capabilities/response.rs index 217f804..5c224e1 100644 --- a/src/commands/capabilities/response.rs +++ b/src/commands/capabilities/response.rs @@ -97,6 +97,7 @@ fn process_get_capabilities<'a>( flags: req_11.flags, data_transfer_size, max_spdm_msg_size, + include_supported_algorithms: (base_req.param1 & 0b00000100) != 0, }; ctx.state diff --git a/src/context.rs b/src/context.rs index 0cf1812..334d471 100644 --- a/src/context.rs +++ b/src/context.rs @@ -112,7 +112,7 @@ impl<'a> SpdmContext<'a> { resp_buffer.reset(); self.transport .receive_response(resp_buffer) - .map_err(|e| SpdmError::Transport(e))?; + .map_err(SpdmError::Transport)?; match self .requester_handle_response(resp_buffer) diff --git a/src/protocol/capabilities.rs b/src/protocol/capabilities.rs index 9f1b3cf..a182bf3 100644 --- a/src/protocol/capabilities.rs +++ b/src/protocol/capabilities.rs @@ -50,6 +50,12 @@ pub struct DeviceCapabilities { // Only used for >= SPDM 1.2 pub data_transfer_size: u32, pub max_spdm_msg_size: u32, + + /// Only valid for >= SPDM 1.3 + /// The Responder shall include the Supported Algorithms Block in its CAPABILITIES + /// response if it supports this extended capability. If the Requester does not + /// support the Large SPDM message transfer mechanism ( CHUNK_CAP=0 ), this bit shall be 0. + pub include_supported_algorithms: bool, } bitfield! { diff --git a/src/state.rs b/src/state.rs index 85e8dba..7eeef12 100644 --- a/src/state.rs +++ b/src/state.rs @@ -27,8 +27,8 @@ impl State { pub(crate) struct ConnectionInfo { version_number: SpdmVersion, state: ConnectionState, - peer_capabilities: DeviceCapabilities, peer_algorithms: DeviceAlgorithms, + peer_capabilities: DeviceCapabilities, multi_key_conn_rsp: bool, } From 0918f6daf4511a1fa50f21f34ebe240601dc3d84 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 5 Feb 2026 17:31:49 +0100 Subject: [PATCH 21/86] update context transcript and state machine Signed-off-by: leongross --- src/commands/algorithms/request.rs | 3 +-- src/commands/capabilities/request.rs | 6 +++++- src/commands/version/request.rs | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index 16ee47b..f73d579 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -120,7 +120,6 @@ pub fn handle_algorithms_response<'a>( let alg_struct = AlgStructure::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; // For each struct table, we need to decode the variable length fields. - // TODO: add them to state? for _ in 0..alg_struct.ext_alg_count() { let ext_algo = ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; @@ -267,7 +266,7 @@ pub fn generate_negotiate_algorithms_request<'a>( } } - Ok(()) + ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca) } #[cfg(test)] diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 7960fc7..13dace5 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -87,7 +87,11 @@ pub(crate) fn handle_capabilities_response<'a>( .connection_info .set_peer_capabilities(peer_capabilities); - Ok(()) + ctx.state + .connection_info + .set_state(crate::state::ConnectionState::AfterCapabilities); + + ctx.append_message_to_transcript(resp, TranscriptContext::Vca) } /// Generate the GET_CAPABILITIES command with all the contexts information. diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index da799b8..0680a98 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -37,8 +37,7 @@ pub fn generate_get_version<'a>( .push_data(len) .map_err(|_| (false, CommandError::BufferTooSmall))?; - ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca)?; - Ok(()) + ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) } /// Requester function for processing a VERSION response From 3c945f2694878ee0037eddafec8b3fc712429541 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 10 Feb 2026 14:33:22 +0100 Subject: [PATCH 22/86] Apply cargo fmt fixes --- examples/platform/cert_store.rs | 112 ++++++++------ examples/platform/certs.rs | 204 +++++++++++--------------- examples/platform/crypto.rs | 33 +++-- examples/platform/evidence.rs | 4 +- examples/platform/mod.rs | 15 +- examples/test_static_certs.rs | 35 +++-- src/commands/chunk_get_rsp.rs | 21 ++- src/commands/digests_rsp.rs | 14 +- src/measurements/common.rs | 25 +++- src/measurements/freeform_manifest.rs | 31 ++-- src/platform/hash.rs | 11 +- src/platform/mod.rs | 4 +- src/platform/rng.rs | 2 +- src/protocol/signature.rs | 2 +- src/transcript.rs | 58 +++++--- 15 files changed, 315 insertions(+), 256 deletions(-) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index 635117f..14d6132 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -1,23 +1,21 @@ // Licensed under the Apache-2.0 license //! Certificate Store Platform Implementation -//! +//! //! Provides certificate management using static certificates with ECDSA signing use std::sync::Mutex; #[cfg(feature = "crypto")] use p384::{ - ecdsa::{SigningKey, Signature, signature::hazmat::PrehashSigner}, - SecretKey + ecdsa::{signature::hazmat::PrehashSigner, Signature, SigningKey}, + SecretKey, }; - - -use spdm_lib::cert_store::{SpdmCertStore, CertStoreResult, CertStoreError}; +use super::certs::{STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; +use spdm_lib::cert_store::{CertStoreError, CertStoreResult, SpdmCertStore}; use spdm_lib::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; use spdm_lib::protocol::certs::{CertificateInfo, KeyUsageMask}; -use super::certs::{STATIC_ROOT_CA_CERT, STATIC_ATTESTATION_CERT, ATTESTATION_PRIVATE_KEY}; /// Certificate store with proper ECDSA signing pub struct DemoCertStore { @@ -33,13 +31,13 @@ impl DemoCertStore { println!("Loading static certificate chain..."); let (cert_chain, signing_key) = Self::generate_certificate_chain(); println!("Static certificate chain loaded successfully"); - + Self { cert_chain, signing_key: Mutex::new(Some(signing_key)), } } - + #[cfg(not(feature = "crypto"))] { // Fallback for when crypto feature is not enabled @@ -50,26 +48,35 @@ impl DemoCertStore { #[cfg(feature = "crypto")] fn generate_certificate_chain() -> (Vec, SigningKey) { + use crate::platform::certs::ATTESTATION_PRIVATE_KEY; + println!("🔧 DIRECT CERTIFICATE CHAIN - RAW CONCATENATION"); - + // SIMPLE APPROACH: Just concatenate Root CA + Attestation certificates // Let the SPDM library handle its own formatting let mut cert_chain = Vec::new(); cert_chain.extend_from_slice(STATIC_ROOT_CA_CERT); cert_chain.extend_from_slice(STATIC_ATTESTATION_CERT); - - println!(" ✅ Raw certificates: Root({}) + Attestation({})", STATIC_ROOT_CA_CERT.len(), STATIC_ATTESTATION_CERT.len()); + + println!( + " ✅ Raw certificates: Root({}) + Attestation({})", + STATIC_ROOT_CA_CERT.len(), + STATIC_ATTESTATION_CERT.len() + ); println!(" Total length: {} bytes", cert_chain.len()); println!(" Root starts: {:02x?}", &cert_chain[..4]); - println!(" Attestation starts: {:02x?}", &cert_chain[STATIC_ROOT_CA_CERT.len()..STATIC_ROOT_CA_CERT.len()+4]); + println!( + " Attestation starts: {:02x?}", + &cert_chain[STATIC_ROOT_CA_CERT.len()..STATIC_ROOT_CA_CERT.len() + 4] + ); let secret_key = SecretKey::from_bytes(ATTESTATION_PRIVATE_KEY.into()) .expect("Failed to parse secret key from static data"); - + let attestation_key = SigningKey::from(secret_key); println!("🔑 Static attestation signing key loaded"); - + (cert_chain, attestation_key) } @@ -79,15 +86,15 @@ impl DemoCertStore { if cert_chain.len() < 2 { return None; } - + let mut offset = 0; - + // Check for SEQUENCE tag (0x30) if cert_chain[offset] != 0x30 { return None; } offset += 1; - + // Parse length and calculate total certificate size let (content_length, header_size) = if cert_chain[offset] & 0x80 == 0 { // Short form length (0-127) @@ -101,26 +108,26 @@ impl DemoCertStore { return None; } offset += 1; - + if offset + length_octets > cert_chain.len() { return None; } - + let mut content_len = 0; for i in 0..length_octets { content_len = (content_len << 8) | cert_chain[offset + i] as usize; } - + let header_len = 2 + length_octets; // tag + length indicator + length bytes (content_len, header_len) }; - + let total_cert_size = header_size + content_length; - + if total_cert_size > cert_chain.len() { return None; } - + Some(&cert_chain[0..total_cert_size]) } } @@ -161,7 +168,7 @@ impl SpdmCertStore for DemoCertStore { let copy_len = remaining.min(cert_portion.len()); cert_portion[..copy_len].copy_from_slice(&self.cert_chain[offset..offset + copy_len]); - // println!(" Cert Chain Copy: {:02x?}", &cert_portion[..copy_len]); + // println!(" Cert Chain Copy: {:02x?}", &cert_portion[..copy_len]); Ok(copy_len) } @@ -177,20 +184,23 @@ impl SpdmCertStore for DemoCertStore { #[cfg(feature = "crypto")] { - use sha2::{Sha384, Digest}; + use sha2::{Digest, Sha384}; // Calculate proper SHA-384 hash of the root certificate let mut hasher = Sha384::new(); hasher.update(STATIC_ROOT_CA_CERT); let hash_result = hasher.finalize(); cert_hash.copy_from_slice(&hash_result); - // println!(" Fabrizio Root Hash starts: {:02x?}", &cert_hash[..4]); + // println!(" Fabrizio Root Hash starts: {:02x?}", &cert_hash[..4]); } - + #[cfg(not(feature = "crypto"))] { // Fallback for when crypto feature is not enabled for (i, byte) in cert_hash.iter_mut().enumerate() { - *byte = STATIC_ROOT_CA_CERT.get(i % STATIC_ROOT_CA_CERT.len()).copied().unwrap_or(0); + *byte = STATIC_ROOT_CA_CERT + .get(i % STATIC_ROOT_CA_CERT.len()) + .copied() + .unwrap_or(0); } } @@ -211,9 +221,8 @@ impl SpdmCertStore for DemoCertStore { { if let Ok(signing_key_guard) = self.signing_key.lock() { if let Some(ref signing_key) = *signing_key_guard { - let sig: Signature = signing_key.sign_prehash(hash).unwrap(); - + let sig_bytes = sig.to_bytes(); if sig_bytes.len() <= ECC_P384_SIGNATURE_SIZE { signature[..sig_bytes.len()].copy_from_slice(&sig_bytes); @@ -224,7 +233,7 @@ impl SpdmCertStore for DemoCertStore { } Err(CertStoreError::PlatformError) } - + #[cfg(not(feature = "crypto"))] { // Fallback for demo without crypto @@ -236,7 +245,11 @@ impl SpdmCertStore for DemoCertStore { } fn key_pair_id(&self, slot_id: u8) -> Option { - if slot_id == 0 { Some(1) } else { None } + if slot_id == 0 { + Some(1) + } else { + None + } } fn cert_info(&self, slot_id: u8) -> Option { @@ -265,22 +278,22 @@ impl SpdmCertStore for DemoCertStore { #[test] fn test_signing() { use p384::ecdsa::signature::SignatureEncoding; - + // Create a known private key let private_key_bytes = ATTESTATION_PRIVATE_KEY.to_vec(); let secret_key = SecretKey::from_bytes((&private_key_bytes[..]).into()).unwrap(); let signing_key = SigningKey::from(secret_key); - + // Your input let input = hex::decode("32ac91a55d17db5e537448789486c633ecba4cd49185d0933f3d6561573fb68931f88bef4dc6ef20602df7dbeb51086b").unwrap(); - + // Test 1: Sign directly let sig_direct: Signature = signing_key.sign(&input); println!("Direct signature:"); let sig_bytes = sig_direct.to_bytes(); println!(" R: {}", hex::encode(&sig_bytes[..48])); println!(" S: {}", hex::encode(&sig_bytes[48..])); - + // Test 2: Hash then sign let digest = Sha384::digest(&input); let sig_hashed: Signature = signing_key.sign(&digest[..]); @@ -293,23 +306,23 @@ fn test_signing() { #[cfg(all(test, feature = "crypto"))] #[test] fn debug_signing_verification() { - use p384::ecdsa::signature::SignatureEncoding; use hex; - + use p384::ecdsa::signature::SignatureEncoding; + // Your test data let input_hex = "32ac91a55d17db5e537448789486c633ecba4cd49185d0933f3d6561573fb68931f88bef4dc6ef20602df7dbeb51086b"; let input = hex::decode(input_hex).unwrap(); - + // Create signing key let private_key_bytes = ATTESTATION_PRIVATE_KEY.to_vec(); let secret_key = SecretKey::from_bytes((&private_key_bytes[..]).into()).unwrap(); let signing_key = SigningKey::from(secret_key); - + // Get public key let verifying_key = signing_key.verifying_key(); let public_point = verifying_key.to_encoded_point(false); println!("Public key: {}", hex::encode(public_point.as_bytes())); - + // Test 1: Sign directly println!("\n=== Test 1: Direct signing ==="); let sig1: Signature = signing_key.sign(&input); @@ -317,11 +330,11 @@ fn debug_signing_verification() { println!("Input: {}", input_hex); println!("Signature R: {}", hex::encode(&sig1_bytes[..48])); println!("Signature S: {}", hex::encode(&sig1_bytes[48..])); - + // Verify with Rust let verify_result = verifying_key.verify(&input, &sig1); println!("Rust verification: {:?}", verify_result); - + // Test 2: Hash then sign println!("\n=== Test 2: Hash then sign ==="); let hashed = Sha384::digest(&input); @@ -330,11 +343,11 @@ fn debug_signing_verification() { let sig2_bytes = sig2.to_bytes(); println!("Signature R: {}", hex::encode(&sig2_bytes[..48])); println!("Signature S: {}", hex::encode(&sig2_bytes[48..])); - + // Verify with Rust let verify_result2 = verifying_key.verify(&hashed, &sig2); println!("Rust verification of hashed: {:?}", verify_result2); - + // Test 3: What Python expects println!("\n=== For Python Testing ==="); println!("# Test direct signature"); @@ -344,7 +357,10 @@ fn debug_signing_verification() { println!("import binascii"); println!(); println!(r#"data = binascii.unhexlify("{}")"#, input_hex); - println!(r#"pubkey = binascii.unhexlify("{}")"#, hex::encode(public_point.as_bytes())); + println!( + r#"pubkey = binascii.unhexlify("{}")"#, + hex::encode(public_point.as_bytes()) + ); println!(r#"r1 = int("{}", 16)"#, hex::encode(&sig1_bytes[..48])); println!(r#"s1 = int("{}", 16)"#, hex::encode(&sig1_bytes[48..])); println!(); @@ -361,4 +377,4 @@ fn debug_signing_verification() { println!(" public_key.verify(sig1, data, ec.ECDSA(hashes.SHA384()))"); println!(" print('✓ Sig1 valid with SHA384')"); println!("except: print('✗ Sig1 invalid with SHA384')"); -} \ No newline at end of file +} diff --git a/examples/platform/certs.rs b/examples/platform/certs.rs index f01318f..988240a 100644 --- a/examples/platform/certs.rs +++ b/examples/platform/certs.rs @@ -1,129 +1,101 @@ // Licensed under the Apache-2.0 license //! Static X.509 certificates for SPDM platform implementations -//! +//! //! These certificates are generated by OpenSSL and known to work correctly //! with certificate verification. Generated on: September 25, 2025 -//! -//! +//! +//! pub const ATTESTATION_PRIVATE_KEY: &[u8] = &[ - 0xd5, 0x76, 0x17, 0x2e, 0xe3, 0x5f, 0x3e, 0x62, 0xb2, 0xbd, 0x0c, 0x5e, - 0x7e, 0x6d, 0x8c, 0xe3, 0xa6, 0x05, 0xfe, 0x27, 0x80, 0x17, 0x37, 0x85, - 0x8b, 0x76, 0xa7, 0xd7, 0xfd, 0x8c, 0x0d, 0x26, 0x28, 0x41, 0x7a, 0x8b, - 0xb6, 0xbc, 0x17, 0x18, 0xc6, 0x9a, 0x10, 0x5c, 0x1e, 0xc8, 0x11, 0x70 + 0xd5, 0x76, 0x17, 0x2e, 0xe3, 0x5f, 0x3e, 0x62, 0xb2, 0xbd, 0x0c, 0x5e, 0x7e, 0x6d, 0x8c, 0xe3, + 0xa6, 0x05, 0xfe, 0x27, 0x80, 0x17, 0x37, 0x85, 0x8b, 0x76, 0xa7, 0xd7, 0xfd, 0x8c, 0x0d, 0x26, + 0x28, 0x41, 0x7a, 0x8b, 0xb6, 0xbc, 0x17, 0x18, 0xc6, 0x9a, 0x10, 0x5c, 0x1e, 0xc8, 0x11, 0x70, ]; pub const STATIC_ROOT_CA_CERT: &[u8] = &[ - 0x30, 0x82, 0x02, 0x5d, 0x30, 0x82, 0x01, 0xe3, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x14, 0x66, 0xd4, 0x94, 0x26, 0x31, 0x59, 0xc1, 0x0e, 0xfe, - 0xe2, 0xf1, 0x74, 0x8a, 0x4f, 0xb3, 0xd4, 0x13, 0x66, 0x7f, 0x93, 0x30, - 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, - 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, - 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, - 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, - 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, - 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x39, 0x32, - 0x37, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x17, 0x0d, 0x33, 0x35, - 0x30, 0x39, 0x32, 0x35, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x30, - 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, - 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, - 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, - 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, - 0x20, 0x43, 0x41, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, - 0x62, 0x00, 0x04, 0xbd, 0x63, 0x0d, 0x67, 0x2e, 0x64, 0xa3, 0x55, 0x11, - 0x19, 0x9a, 0x1f, 0x66, 0x7c, 0xc5, 0x9c, 0x61, 0x62, 0x3d, 0x40, 0xb6, - 0xe1, 0xff, 0x43, 0x7c, 0x39, 0x27, 0xc8, 0xec, 0xe9, 0x12, 0x3a, 0xa8, - 0xce, 0x53, 0x19, 0x06, 0xd1, 0xab, 0x4d, 0xd8, 0x04, 0x36, 0xc2, 0xb8, - 0x8d, 0xc1, 0x20, 0xe5, 0x0c, 0x34, 0x5e, 0x51, 0x8c, 0x73, 0x1f, 0x67, - 0xe9, 0xad, 0x64, 0x72, 0x58, 0x9c, 0x01, 0xb1, 0x38, 0xd6, 0x7b, 0xd0, - 0xad, 0xf6, 0x44, 0xa9, 0xa8, 0x16, 0xb6, 0x36, 0x10, 0xa8, 0xdc, 0x03, - 0x35, 0x8f, 0x4f, 0xa7, 0x5d, 0x00, 0x0e, 0x78, 0xd8, 0xee, 0x73, 0x8b, - 0xfc, 0x07, 0x0a, 0xa3, 0x42, 0x30, 0x40, 0x30, 0x0f, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x5a, 0xc3, 0x33, 0x0a, 0x3d, 0x7f, 0xe8, 0xc1, 0x4b, - 0x3d, 0xb8, 0x28, 0x80, 0xae, 0xb0, 0xfd, 0xab, 0x9d, 0x60, 0xaa, 0x30, - 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, - 0x68, 0x00, 0x30, 0x65, 0x02, 0x31, 0x00, 0x92, 0x51, 0x80, 0x7b, 0x10, - 0xef, 0xb7, 0x03, 0x5a, 0xdb, 0x94, 0xca, 0x2f, 0x10, 0x32, 0xac, 0xa5, - 0xba, 0xcc, 0x9b, 0x24, 0xe0, 0xfc, 0xed, 0x95, 0xfb, 0x6d, 0x2e, 0x8e, - 0xef, 0xfb, 0x52, 0xdf, 0xf0, 0x29, 0x6f, 0xcf, 0x46, 0x49, 0xaf, 0xdf, - 0x53, 0x2f, 0xc7, 0xc5, 0x5d, 0x71, 0xb1, 0x02, 0x30, 0x68, 0x76, 0x6f, - 0x7e, 0x02, 0xf7, 0x6f, 0xbf, 0x50, 0xfd, 0x50, 0xae, 0xc0, 0x48, 0xa9, - 0xd6, 0x97, 0x9e, 0x32, 0x69, 0x2d, 0x00, 0x94, 0xa4, 0x53, 0x6f, 0x9c, - 0x24, 0x23, 0x6c, 0xa6, 0x98, 0x16, 0x80, 0xe1, 0xe6, 0xc9, 0x39, 0x1a, - 0x53, 0xd6, 0xb1, 0x54, 0x74, 0xad, 0x8d, 0x89, 0xde + 0x30, 0x82, 0x02, 0x5d, 0x30, 0x82, 0x01, 0xe3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x66, + 0xd4, 0x94, 0x26, 0x31, 0x59, 0xc1, 0x0e, 0xfe, 0xe2, 0xf1, 0x74, 0x8a, 0x4f, 0xb3, 0xd4, 0x13, + 0x66, 0x7f, 0x93, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, + 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, + 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, + 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x18, 0x30, 0x16, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, + 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, + 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x39, 0x32, 0x37, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, + 0x17, 0x0d, 0x33, 0x35, 0x30, 0x39, 0x32, 0x35, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x30, + 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, + 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, + 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x18, 0x30, 0x16, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, + 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, + 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, + 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xbd, 0x63, 0x0d, 0x67, 0x2e, 0x64, 0xa3, 0x55, 0x11, + 0x19, 0x9a, 0x1f, 0x66, 0x7c, 0xc5, 0x9c, 0x61, 0x62, 0x3d, 0x40, 0xb6, 0xe1, 0xff, 0x43, 0x7c, + 0x39, 0x27, 0xc8, 0xec, 0xe9, 0x12, 0x3a, 0xa8, 0xce, 0x53, 0x19, 0x06, 0xd1, 0xab, 0x4d, 0xd8, + 0x04, 0x36, 0xc2, 0xb8, 0x8d, 0xc1, 0x20, 0xe5, 0x0c, 0x34, 0x5e, 0x51, 0x8c, 0x73, 0x1f, 0x67, + 0xe9, 0xad, 0x64, 0x72, 0x58, 0x9c, 0x01, 0xb1, 0x38, 0xd6, 0x7b, 0xd0, 0xad, 0xf6, 0x44, 0xa9, + 0xa8, 0x16, 0xb6, 0x36, 0x10, 0xa8, 0xdc, 0x03, 0x35, 0x8f, 0x4f, 0xa7, 0x5d, 0x00, 0x0e, 0x78, + 0xd8, 0xee, 0x73, 0x8b, 0xfc, 0x07, 0x0a, 0xa3, 0x42, 0x30, 0x40, 0x30, 0x0f, 0x06, 0x03, 0x55, + 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, + 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, + 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5a, 0xc3, 0x33, 0x0a, 0x3d, 0x7f, 0xe8, 0xc1, 0x4b, + 0x3d, 0xb8, 0x28, 0x80, 0xae, 0xb0, 0xfd, 0xab, 0x9d, 0x60, 0xaa, 0x30, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x31, 0x00, 0x92, + 0x51, 0x80, 0x7b, 0x10, 0xef, 0xb7, 0x03, 0x5a, 0xdb, 0x94, 0xca, 0x2f, 0x10, 0x32, 0xac, 0xa5, + 0xba, 0xcc, 0x9b, 0x24, 0xe0, 0xfc, 0xed, 0x95, 0xfb, 0x6d, 0x2e, 0x8e, 0xef, 0xfb, 0x52, 0xdf, + 0xf0, 0x29, 0x6f, 0xcf, 0x46, 0x49, 0xaf, 0xdf, 0x53, 0x2f, 0xc7, 0xc5, 0x5d, 0x71, 0xb1, 0x02, + 0x30, 0x68, 0x76, 0x6f, 0x7e, 0x02, 0xf7, 0x6f, 0xbf, 0x50, 0xfd, 0x50, 0xae, 0xc0, 0x48, 0xa9, + 0xd6, 0x97, 0x9e, 0x32, 0x69, 0x2d, 0x00, 0x94, 0xa4, 0x53, 0x6f, 0x9c, 0x24, 0x23, 0x6c, 0xa6, + 0x98, 0x16, 0x80, 0xe1, 0xe6, 0xc9, 0x39, 0x1a, 0x53, 0xd6, 0xb1, 0x54, 0x74, 0xad, 0x8d, 0x89, + 0xde, ]; pub const STATIC_ATTESTATION_CERT: &[u8] = &[ - 0x30, 0x82, 0x02, 0xab, 0x30, 0x82, 0x02, 0x31, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x14, 0x45, 0x7b, 0xc1, 0xae, 0x0b, 0xb9, 0x70, 0x17, 0x22, - 0x64, 0xfb, 0xf2, 0xb7, 0xac, 0xf4, 0x3a, 0xaa, 0xc0, 0x7c, 0x31, 0x30, - 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, - 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, - 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, - 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, - 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, - 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x39, 0x32, - 0x37, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x17, 0x0d, 0x33, 0x35, - 0x30, 0x39, 0x32, 0x35, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x30, - 0x7f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, - 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, - 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, - 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x14, 0x45, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x0c, 0x1b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x43, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x30, 0x76, 0x30, 0x10, - 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, - 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xd6, 0x09, 0x5e, 0x5f, - 0xc4, 0x2d, 0xa7, 0xf6, 0x9f, 0x8d, 0xdf, 0xc1, 0x9f, 0xd1, 0x20, 0xb8, - 0x25, 0x1a, 0x9c, 0xbf, 0xf7, 0x61, 0x5e, 0xce, 0xfd, 0x67, 0xff, 0x72, - 0x3e, 0xbf, 0xe8, 0x21, 0xca, 0x2b, 0xc2, 0xba, 0x7b, 0x81, 0x17, 0x29, - 0xb3, 0x33, 0x13, 0xbc, 0x07, 0xaa, 0xe7, 0x45, 0x4e, 0xb5, 0xe2, 0x2f, - 0x9f, 0xcf, 0x7b, 0x06, 0x5e, 0x27, 0x3f, 0x15, 0x42, 0x1e, 0xd0, 0x16, - 0xdb, 0x83, 0x1b, 0x9c, 0xef, 0xff, 0xf4, 0xe5, 0x9a, 0xf1, 0x16, 0x58, - 0x55, 0x3d, 0x14, 0x34, 0x76, 0x1a, 0x59, 0x01, 0x23, 0x11, 0x7b, 0x9f, - 0xc0, 0xa5, 0x4f, 0x96, 0x07, 0x73, 0xfc, 0x9b, 0xa3, 0x7f, 0x30, 0x7d, - 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, - 0x30, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x06, 0xc0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x03, 0x03, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, - 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0xc9, 0x9a, 0xce, 0x7f, 0x7d, 0xc4, 0xa1, 0xb4, 0xac, 0x8d, 0x56, 0x39, - 0xdd, 0x44, 0x7f, 0x19, 0x50, 0x8a, 0xb3, 0x88, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x5a, 0xc3, 0x33, - 0x0a, 0x3d, 0x7f, 0xe8, 0xc1, 0x4b, 0x3d, 0xb8, 0x28, 0x80, 0xae, 0xb0, - 0xfd, 0xab, 0x9d, 0x60, 0xaa, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x30, - 0x4d, 0x81, 0x2b, 0xf4, 0xc1, 0x7a, 0x60, 0x37, 0xd7, 0x94, 0x3e, 0xdd, - 0x86, 0x3e, 0xab, 0xd6, 0xfd, 0x59, 0x45, 0xee, 0x2a, 0x4e, 0xa3, 0xa0, - 0x38, 0xb0, 0x59, 0x5a, 0x75, 0xbf, 0x29, 0x35, 0x00, 0xa1, 0x44, 0x0d, - 0x00, 0xd0, 0x6b, 0xec, 0x54, 0x8d, 0xab, 0x60, 0x75, 0xdf, 0xa9, 0x68, - 0x02, 0x31, 0x00, 0xa2, 0x14, 0xe4, 0x0c, 0x4b, 0x27, 0xb5, 0xd4, 0xad, - 0xa0, 0x53, 0x67, 0x24, 0x1b, 0x19, 0x3f, 0x00, 0x88, 0x69, 0x0c, 0x30, - 0xe8, 0x24, 0xd9, 0x11, 0xd3, 0x16, 0xb6, 0x0d, 0x54, 0x1d, 0x2a, 0x52, - 0xb8, 0xd7, 0x33, 0x57, 0x3f, 0xaf, 0xf9, 0x6a, 0x42, 0x8f, 0xe5, 0xd9, - 0x15, 0x23, 0xbc + 0x30, 0x82, 0x02, 0xab, 0x30, 0x82, 0x02, 0x31, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x45, + 0x7b, 0xc1, 0xae, 0x0b, 0xb9, 0x70, 0x17, 0x22, 0x64, 0xfb, 0xf2, 0xb7, 0xac, 0xf4, 0x3a, 0xaa, + 0xc0, 0x7c, 0x31, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, + 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, + 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, + 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x18, 0x30, 0x16, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, + 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, + 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x39, 0x32, 0x37, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, + 0x17, 0x0d, 0x33, 0x35, 0x30, 0x39, 0x32, 0x35, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x30, + 0x7f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, + 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, + 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x1d, 0x30, 0x1b, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x14, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x4f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x1b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x4b, 0x65, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, + 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xd6, 0x09, 0x5e, 0x5f, 0xc4, 0x2d, 0xa7, 0xf6, + 0x9f, 0x8d, 0xdf, 0xc1, 0x9f, 0xd1, 0x20, 0xb8, 0x25, 0x1a, 0x9c, 0xbf, 0xf7, 0x61, 0x5e, 0xce, + 0xfd, 0x67, 0xff, 0x72, 0x3e, 0xbf, 0xe8, 0x21, 0xca, 0x2b, 0xc2, 0xba, 0x7b, 0x81, 0x17, 0x29, + 0xb3, 0x33, 0x13, 0xbc, 0x07, 0xaa, 0xe7, 0x45, 0x4e, 0xb5, 0xe2, 0x2f, 0x9f, 0xcf, 0x7b, 0x06, + 0x5e, 0x27, 0x3f, 0x15, 0x42, 0x1e, 0xd0, 0x16, 0xdb, 0x83, 0x1b, 0x9c, 0xef, 0xff, 0xf4, 0xe5, + 0x9a, 0xf1, 0x16, 0x58, 0x55, 0x3d, 0x14, 0x34, 0x76, 0x1a, 0x59, 0x01, 0x23, 0x11, 0x7b, 0x9f, + 0xc0, 0xa5, 0x4f, 0x96, 0x07, 0x73, 0xfc, 0x9b, 0xa3, 0x7f, 0x30, 0x7d, 0x30, 0x0c, 0x06, 0x03, + 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, + 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x06, 0xc0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, + 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, + 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0xc9, 0x9a, 0xce, 0x7f, 0x7d, 0xc4, 0xa1, 0xb4, 0xac, 0x8d, 0x56, 0x39, + 0xdd, 0x44, 0x7f, 0x19, 0x50, 0x8a, 0xb3, 0x88, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, + 0x18, 0x30, 0x16, 0x80, 0x14, 0x5a, 0xc3, 0x33, 0x0a, 0x3d, 0x7f, 0xe8, 0xc1, 0x4b, 0x3d, 0xb8, + 0x28, 0x80, 0xae, 0xb0, 0xfd, 0xab, 0x9d, 0x60, 0xaa, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x30, 0x4d, 0x81, 0x2b, 0xf4, + 0xc1, 0x7a, 0x60, 0x37, 0xd7, 0x94, 0x3e, 0xdd, 0x86, 0x3e, 0xab, 0xd6, 0xfd, 0x59, 0x45, 0xee, + 0x2a, 0x4e, 0xa3, 0xa0, 0x38, 0xb0, 0x59, 0x5a, 0x75, 0xbf, 0x29, 0x35, 0x00, 0xa1, 0x44, 0x0d, + 0x00, 0xd0, 0x6b, 0xec, 0x54, 0x8d, 0xab, 0x60, 0x75, 0xdf, 0xa9, 0x68, 0x02, 0x31, 0x00, 0xa2, + 0x14, 0xe4, 0x0c, 0x4b, 0x27, 0xb5, 0xd4, 0xad, 0xa0, 0x53, 0x67, 0x24, 0x1b, 0x19, 0x3f, 0x00, + 0x88, 0x69, 0x0c, 0x30, 0xe8, 0x24, 0xd9, 0x11, 0xd3, 0x16, 0xb6, 0x0d, 0x54, 0x1d, 0x2a, 0x52, + 0xb8, 0xd7, 0x33, 0x57, 0x3f, 0xaf, 0xf9, 0x6a, 0x42, 0x8f, 0xe5, 0xd9, 0x15, 0x23, 0xbc, ]; diff --git a/examples/platform/crypto.rs b/examples/platform/crypto.rs index 20dd4d0..3d9a413 100644 --- a/examples/platform/crypto.rs +++ b/examples/platform/crypto.rs @@ -1,13 +1,13 @@ // Licensed under the Apache-2.0 license //! Cryptographic Platform Implementation -//! +//! //! Provides SHA-384 hash and system RNG implementations #[cfg(feature = "crypto")] -use sha2::{Sha384, Digest}; +use sha2::{Digest, Sha384}; -use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType, SpdmHashResult, SpdmHashError}; +use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType, SpdmHashError, SpdmHashResult}; use spdm_lib::platform::rng::{SpdmRng, SpdmRngResult}; /// SHA-384 hash implementation using proper cryptography @@ -28,15 +28,20 @@ impl Sha384Hash { } impl SpdmHash for Sha384Hash { - fn hash(&mut self, hash_algo: SpdmHashAlgoType, data: &[u8], hash: &mut [u8]) -> SpdmHashResult<()> { + fn hash( + &mut self, + hash_algo: SpdmHashAlgoType, + data: &[u8], + hash: &mut [u8], + ) -> SpdmHashResult<()> { if hash_algo != SpdmHashAlgoType::SHA384 { return Err(SpdmHashError::InvalidAlgorithm); } - + if hash.len() < 48 { return Err(SpdmHashError::BufferTooSmall); } - + #[cfg(feature = "crypto")] { let mut hasher = Sha384::new(); @@ -45,7 +50,7 @@ impl SpdmHash for Sha384Hash { hash[..48].copy_from_slice(&result[..]); Ok(()) } - + #[cfg(not(feature = "crypto"))] { // Fallback for demo purposes when crypto feature is not enabled @@ -61,7 +66,7 @@ impl SpdmHash for Sha384Hash { return Err(SpdmHashError::InvalidAlgorithm); } self.current_algo = hash_algo; - + #[cfg(feature = "crypto")] { let mut hasher = Sha384::new(); @@ -70,7 +75,7 @@ impl SpdmHash for Sha384Hash { } self.hasher = Some(hasher); } - + Ok(()) } @@ -83,7 +88,7 @@ impl SpdmHash for Sha384Hash { return Err(SpdmHashError::PlatformError); } } - + Ok(()) } @@ -91,7 +96,7 @@ impl SpdmHash for Sha384Hash { if hash.len() < 48 { return Err(SpdmHashError::BufferTooSmall); } - + #[cfg(feature = "crypto")] { if let Some(hasher) = self.hasher.take() { @@ -101,13 +106,13 @@ impl SpdmHash for Sha384Hash { return Err(SpdmHashError::PlatformError); } } - + #[cfg(not(feature = "crypto"))] { // Fallback for demo hash[..48].fill(0x42); } - + Ok(()) } @@ -151,4 +156,4 @@ impl SpdmRng for SystemRng { } Ok(()) } -} \ No newline at end of file +} diff --git a/examples/platform/evidence.rs b/examples/platform/evidence.rs index 3699e2e..09d666d 100644 --- a/examples/platform/evidence.rs +++ b/examples/platform/evidence.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license //! Evidence Platform Implementation -//! +//! //! Provides device measurements and evidence functionality use spdm_lib::platform::evidence::{SpdmEvidence, SpdmEvidenceResult}; @@ -27,4 +27,4 @@ impl SpdmEvidence for DemoEvidence { fn pcr_quote_size(&self, _with_pqc_sig: bool) -> SpdmEvidenceResult { Ok(b"DEMO_PCR_QUOTE_DATA_FOR_MEASUREMENTS".len()) } -} \ No newline at end of file +} diff --git a/examples/platform/mod.rs b/examples/platform/mod.rs index a183ff6..ff7c42a 100644 --- a/examples/platform/mod.rs +++ b/examples/platform/mod.rs @@ -1,17 +1,20 @@ // Licensed under the Apache-2.0 license //! Platform implementations for SPDM examples -//! +//! //! This module provides working platform implementations that can be easily //! swapped out for production implementations. -pub mod socket_transport; -pub mod crypto; pub mod cert_store; -pub mod evidence; pub mod certs; +pub mod crypto; +pub mod evidence; +pub mod socket_transport; -pub use socket_transport::SpdmSocketTransport; -pub use crypto::{Sha384Hash, SystemRng}; pub use cert_store::DemoCertStore; +pub use crypto::{Sha384Hash, SystemRng}; pub use evidence::DemoEvidence; +pub use socket_transport::SpdmSocketTransport; +// Certificate constants available for examples that need them +#[allow(unused_imports)] +pub use certs::{ATTESTATION_PRIVATE_KEY, STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; diff --git a/examples/test_static_certs.rs b/examples/test_static_certs.rs index f571bbd..ee3d602 100644 --- a/examples/test_static_certs.rs +++ b/examples/test_static_certs.rs @@ -1,46 +1,55 @@ // Test program to verify static certificates work correctly -// Import platform implementations +// Import platform implementations mod platform; -use platform::{STATIC_ROOT_CA_CERT, STATIC_ATTESTATION_CERT, STATIC_CERTIFICATE_CHAIN}; +use platform::{STATIC_ATTESTATION_CERT, STATIC_CERTIFICATE_CHAIN, STATIC_ROOT_CA_CERT}; fn main() { println!("Static Certificate Test"); println!("======================="); - + println!("Root CA Certificate: {} bytes", STATIC_ROOT_CA_CERT.len()); - println!("Attestation Certificate: {} bytes", STATIC_ATTESTATION_CERT.len()); - println!("Certificate Chain: {} bytes", STATIC_CERTIFICATE_CHAIN.len()); - + println!( + "Attestation Certificate: {} bytes", + STATIC_ATTESTATION_CERT.len() + ); + println!( + "Certificate Chain: {} bytes", + STATIC_CERTIFICATE_CHAIN.len() + ); + // Verify the chain is the concatenation of the individual certs let expected_len = STATIC_ROOT_CA_CERT.len() + STATIC_ATTESTATION_CERT.len(); if STATIC_CERTIFICATE_CHAIN.len() == expected_len { println!("✓ Certificate chain length matches individual certificates"); } else { - println!("✗ Certificate chain length mismatch: expected {}, got {}", - expected_len, STATIC_CERTIFICATE_CHAIN.len()); + println!( + "✗ Certificate chain length mismatch: expected {}, got {}", + expected_len, + STATIC_CERTIFICATE_CHAIN.len() + ); } - + // Verify the chain starts with the root CA if STATIC_CERTIFICATE_CHAIN.starts_with(STATIC_ROOT_CA_CERT) { println!("✓ Certificate chain starts with root CA"); } else { println!("✗ Certificate chain does not start with root CA"); } - + // Verify the chain ends with the attestation cert if STATIC_CERTIFICATE_CHAIN.ends_with(STATIC_ATTESTATION_CERT) { println!("✓ Certificate chain ends with attestation certificate"); } else { println!("✗ Certificate chain does not end with attestation certificate"); } - + // Check X.509 structure (should start with SEQUENCE tag 0x30) if STATIC_ROOT_CA_CERT[0] == 0x30 && STATIC_ATTESTATION_CERT[0] == 0x30 { println!("✓ Both certificates have proper X.509 DER format (SEQUENCE tag)"); } else { println!("✗ Certificates do not have proper X.509 DER format"); } - + println!("\nStatic certificates are ready for use!"); -} \ No newline at end of file +} diff --git a/src/commands/chunk_get_rsp.rs b/src/commands/chunk_get_rsp.rs index 7e3fa6c..3c1fbc9 100644 --- a/src/commands/chunk_get_rsp.rs +++ b/src/commands/chunk_get_rsp.rs @@ -160,17 +160,16 @@ fn encode_chunk_data( match response { LargeResponse::Measurements(meas_rsp) => { // Get the chunk data from the measurements response - meas_rsp - .get_chunk( - ctx.hash, - ctx.rng, - ctx.evidence, - &mut ctx.measurements, - &mut ctx.transcript_mgr, - ctx.device_certs_store, - offset, - chunk_buf, - )?; + meas_rsp.get_chunk( + ctx.hash, + ctx.rng, + ctx.evidence, + &mut ctx.measurements, + &mut ctx.transcript_mgr, + ctx.device_certs_store, + offset, + chunk_buf, + )?; } } } else { diff --git a/src/commands/digests_rsp.rs b/src/commands/digests_rsp.rs index d95365a..aaf8893 100644 --- a/src/commands/digests_rsp.rs +++ b/src/commands/digests_rsp.rs @@ -4,11 +4,11 @@ use crate::cert_store::{cert_slot_mask, SpdmCertStore}; use crate::codec::{Codec, CommonCodec, MessageBuf}; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; -use crate::error::{PlatformError, CommandError, CommandResult}; +use crate::error::{CommandError, CommandResult, PlatformError}; +use crate::platform::hash::{SpdmHash, SpdmHashAlgoType}; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; -use crate::platform::hash::{SpdmHash, SpdmHashAlgoType}; use core::mem::size_of; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -53,7 +53,7 @@ pub(crate) fn compute_cert_chain_hash( // Length and reserved fields let header_bytes = header.as_bytes(); - + digest_fn .init(SpdmHashAlgoType::SHA384, Some(header_bytes)) .map_err(|e| (false, CommandError::Platform(PlatformError::HashError(e))))?; @@ -107,7 +107,13 @@ fn encode_cert_chain_digest( .data_mut(SHA384_HASH_SIZE) .map_err(|_| (false, CommandError::BufferTooSmall))?; - compute_cert_chain_hash(digest_fn, slot_id, cert_store, asym_algo, cert_chain_digest_buf)?; + compute_cert_chain_hash( + digest_fn, + slot_id, + cert_store, + asym_algo, + cert_chain_digest_buf, + )?; rsp.pull_data(SHA384_HASH_SIZE) .map_err(|_| (false, CommandError::BufferTooSmall))?; diff --git a/src/measurements/common.rs b/src/measurements/common.rs index 14f06e1..9658f1d 100644 --- a/src/measurements/common.rs +++ b/src/measurements/common.rs @@ -1,8 +1,8 @@ // Licensed under the Apache-2.0 license use crate::measurements::freeform_manifest::FreeformManifest; -use crate::protocol::{algorithms::AsymAlgo, MeasurementSpecification, SHA384_HASH_SIZE}; -use crate::platform::hash::{SpdmHash, SpdmHashError}; use crate::platform::evidence::{SpdmEvidence, SpdmEvidenceError}; +use crate::platform::hash::{SpdmHash, SpdmHashError}; +use crate::protocol::{algorithms::AsymAlgo, MeasurementSpecification, SHA384_HASH_SIZE}; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -102,9 +102,14 @@ impl SpdmMeasurements { measurement_chunk: &mut [u8], ) -> MeasurementsResult { match self { - SpdmMeasurements::FreeformManifest(manifest) => { - manifest.measurement_block(evidence, asym_algo, index, raw_bit_stream, offset, measurement_chunk) - } + SpdmMeasurements::FreeformManifest(manifest) => manifest.measurement_block( + evidence, + asym_algo, + index, + raw_bit_stream, + offset, + measurement_chunk, + ), } } @@ -129,9 +134,13 @@ impl SpdmMeasurements { hash: &mut [u8; SHA384_HASH_SIZE], ) -> MeasurementsResult<()> { match self { - SpdmMeasurements::FreeformManifest(manifest) => { - manifest.measurement_summary_hash(evidence, hash_ctx, asym_algo, measurement_summary_hash_type, hash) - } + SpdmMeasurements::FreeformManifest(manifest) => manifest.measurement_summary_hash( + evidence, + hash_ctx, + asym_algo, + measurement_summary_hash_type, + hash, + ), } } } diff --git a/src/measurements/freeform_manifest.rs b/src/measurements/freeform_manifest.rs index e2e7cff..c467948 100644 --- a/src/measurements/freeform_manifest.rs +++ b/src/measurements/freeform_manifest.rs @@ -4,9 +4,9 @@ use crate::measurements::common::{ DmtfMeasurementBlockMetadata, MeasurementValueType, MeasurementsError, MeasurementsResult, SPDM_MEASUREMENT_MANIFEST_INDEX, }; -use crate::protocol::{algorithms::AsymAlgo, SHA384_HASH_SIZE}; -use crate::platform::hash::SpdmHash; use crate::platform::evidence::{SpdmEvidence, PCR_QUOTE_BUFFER_SIZE}; +use crate::platform::hash::SpdmHash; +use crate::protocol::{algorithms::AsymAlgo, SHA384_HASH_SIZE}; use zerocopy::IntoBytes; const MAX_MEASUREMENT_RECORD_SIZE: usize = @@ -103,23 +103,35 @@ impl FreeformManifest { if offset == 0 { hash_ctx - .init(hash_ctx.algo(), Some(&self.measurement_record[..chunk_size])) + .init( + hash_ctx.algo(), + Some(&self.measurement_record[..chunk_size]), + ) .map_err(|e| MeasurementsError::Hash(e))?; } else { let chunk = &self.measurement_record[offset..offset + chunk_size]; - hash_ctx.update(chunk).map_err(|e| MeasurementsError::Hash(e))?; + hash_ctx + .update(chunk) + .map_err(|e| MeasurementsError::Hash(e))?; } offset += chunk_size; } - hash_ctx.finalize(hash).map_err(|e| MeasurementsError::Hash(e)) + hash_ctx + .finalize(hash) + .map_err(|e| MeasurementsError::Hash(e)) } - fn refresh_measurement_record(&mut self, evidence: &dyn SpdmEvidence, asym_algo: AsymAlgo) -> MeasurementsResult<()> { + fn refresh_measurement_record( + &mut self, + evidence: &dyn SpdmEvidence, + asym_algo: AsymAlgo, + ) -> MeasurementsResult<()> { let with_pqc_sig = asym_algo != AsymAlgo::EccP384; let measurement_record = &mut self.measurement_record; - let measurement_value_size = evidence.pcr_quote_size(with_pqc_sig) + let measurement_value_size = evidence + .pcr_quote_size(with_pqc_sig) .map_err(|e| MeasurementsError::Evidence(e))?; measurement_record.fill(0); let metadata = DmtfMeasurementBlockMetadata::new( @@ -136,8 +148,9 @@ impl FreeformManifest { let quote_slice = &mut measurement_record[METADATA_SIZE..METADATA_SIZE + PCR_QUOTE_BUFFER_SIZE]; - let copied_len = evidence.pcr_quote(quote_slice, with_pqc_sig) - .map_err(|e| MeasurementsError::Evidence(e))?; + let copied_len = evidence + .pcr_quote(quote_slice, with_pqc_sig) + .map_err(|e| MeasurementsError::Evidence(e))?; if copied_len != measurement_value_size { return Err(MeasurementsError::MeasurementSizeMismatch); } diff --git a/src/platform/hash.rs b/src/platform/hash.rs index ed8d0c0..2992c95 100644 --- a/src/platform/hash.rs +++ b/src/platform/hash.rs @@ -1,13 +1,18 @@ pub type SpdmHashResult = Result; pub trait SpdmHash { - fn hash(&mut self, hash_algo: SpdmHashAlgoType, data: &[u8], hash: &mut [u8]) -> SpdmHashResult<()>; + fn hash( + &mut self, + hash_algo: SpdmHashAlgoType, + data: &[u8], + hash: &mut [u8], + ) -> SpdmHashResult<()>; fn init(&mut self, hash_algo: SpdmHashAlgoType, data: Option<&[u8]>) -> SpdmHashResult<()>; fn update(&mut self, data: &[u8]) -> SpdmHashResult<()>; fn finalize(&mut self, hash: &mut [u8]) -> SpdmHashResult<()>; fn reset(&mut self); - fn algo(&self) -> SpdmHashAlgoType; + fn algo(&self) -> SpdmHashAlgoType; } #[derive(Debug, PartialEq)] @@ -39,4 +44,4 @@ impl SpdmHashAlgoType { SpdmHashAlgoType::SHA512 => 64, } } -} \ No newline at end of file +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index b5ec485..28699ab 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,4 +1,4 @@ +pub mod evidence; pub mod hash; pub mod rng; -pub mod evidence; -pub mod transport; \ No newline at end of file +pub mod transport; diff --git a/src/platform/rng.rs b/src/platform/rng.rs index a7ca9a2..eb71cc4 100644 --- a/src/platform/rng.rs +++ b/src/platform/rng.rs @@ -8,4 +8,4 @@ pub enum SpdmRngError { pub trait SpdmRng { fn get_random_bytes(&mut self, buf: &mut [u8]) -> SpdmRngResult<()>; fn generate_random_number(&mut self, random_number: &mut [u8]) -> SpdmRngResult<()>; -} \ No newline at end of file +} diff --git a/src/protocol/signature.rs b/src/protocol/signature.rs index f59e999..22641fa 100644 --- a/src/protocol/signature.rs +++ b/src/protocol/signature.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license -use crate::protocol::*; use crate::platform::hash::{SpdmHash, SpdmHashError}; +use crate::protocol::*; pub const NONCE_LEN: usize = 32; diff --git a/src/transcript.rs b/src/transcript.rs index 33acce1..9c82bb3 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license -use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; use crate::platform::hash::{SpdmHash, SpdmHashError}; +use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; #[derive(Debug, PartialEq)] pub enum TranscriptError { @@ -82,7 +82,7 @@ impl<'a> TranscriptManager<'a> { hash_ctx_m1: m1, m1_ctx_ready: false, hash_ctx_l1: l1, - l1_ctx_ready: false + l1_ctx_ready: false, } } @@ -114,8 +114,14 @@ impl<'a> TranscriptManager<'a> { pub fn reset_context(&mut self, context: TranscriptContext) { match context { TranscriptContext::Vca => self.vca_buf.reset(), - TranscriptContext::M1 => { self.hash_ctx_m1.reset(); self.m1_ctx_ready = false; }, - TranscriptContext::L1 => { self.hash_ctx_l1.reset(); self.l1_ctx_ready = false; }, + TranscriptContext::M1 => { + self.hash_ctx_m1.reset(); + self.m1_ctx_ready = false; + } + TranscriptContext::L1 => { + self.hash_ctx_l1.reset(); + self.l1_ctx_ready = false; + } } } @@ -127,11 +133,7 @@ impl<'a> TranscriptManager<'a> { /// /// # Returns /// * `TranscriptResult<()>` - Result indicating success or failure. - pub fn append( - &mut self, - context: TranscriptContext, - data: &[u8], - ) -> TranscriptResult<()> { + pub fn append(&mut self, context: TranscriptContext, data: &[u8]) -> TranscriptResult<()> { match context { TranscriptContext::Vca => self.vca_buf.append(data), TranscriptContext::M1 => self.append_m1(data), @@ -158,11 +160,19 @@ impl<'a> TranscriptManager<'a> { TranscriptContext::L1 => &mut self.hash_ctx_l1, }; - hash_ctx.finalize(hash).map_err(|e| TranscriptError::Hash(e))?; + hash_ctx + .finalize(hash) + .map_err(|e| TranscriptError::Hash(e))?; match context { - TranscriptContext::M1 => {self.hash_ctx_m1.reset(); self.m1_ctx_ready = false; }, - TranscriptContext::L1 => {self.hash_ctx_l1.reset(); self.l1_ctx_ready = false; }, + TranscriptContext::M1 => { + self.hash_ctx_m1.reset(); + self.m1_ctx_ready = false; + } + TranscriptContext::L1 => { + self.hash_ctx_l1.reset(); + self.l1_ctx_ready = false; + } _ => {} } @@ -171,11 +181,17 @@ impl<'a> TranscriptManager<'a> { fn append_m1(&mut self, data: &[u8]) -> TranscriptResult<()> { if self.m1_ctx_ready { - self.hash_ctx_m1.update(data).map_err(|e| TranscriptError::Hash(e)) + self.hash_ctx_m1 + .update(data) + .map_err(|e| TranscriptError::Hash(e)) } else { let vca_data = self.vca_buf.data(); - self.hash_ctx_m1.init(self.hash_ctx_m1.algo(), Some(vca_data)).map_err(|e| TranscriptError::Hash(e))?; - self.hash_ctx_m1.update(data).map_err(|e| TranscriptError::Hash(e))?; + self.hash_ctx_m1 + .init(self.hash_ctx_m1.algo(), Some(vca_data)) + .map_err(|e| TranscriptError::Hash(e))?; + self.hash_ctx_m1 + .update(data) + .map_err(|e| TranscriptError::Hash(e))?; self.m1_ctx_ready = true; Ok(()) } @@ -183,15 +199,21 @@ impl<'a> TranscriptManager<'a> { fn append_l1(&mut self, spdm_version: SpdmVersion, data: &[u8]) -> TranscriptResult<()> { if self.l1_ctx_ready { - self.hash_ctx_l1.update(data).map_err(|e| TranscriptError::Hash(e)) + self.hash_ctx_l1 + .update(data) + .map_err(|e| TranscriptError::Hash(e)) } else { let vca_data = if spdm_version >= SpdmVersion::V12 { Some(self.vca_buf.data()) } else { None }; - self.hash_ctx_l1.init(self.hash_ctx_l1.algo(), vca_data).map_err(|e| TranscriptError::Hash(e))?; - self.hash_ctx_l1.update(data).map_err(|e| TranscriptError::Hash(e))?; + self.hash_ctx_l1 + .init(self.hash_ctx_l1.algo(), vca_data) + .map_err(|e| TranscriptError::Hash(e))?; + self.hash_ctx_l1 + .update(data) + .map_err(|e| TranscriptError::Hash(e))?; self.l1_ctx_ready = true; Ok(()) From af2d7ff885681b62966268db070d9f009738b0e4 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 10 Feb 2026 14:59:43 +0100 Subject: [PATCH 23/86] Fix all warnings in lib code --- src/commands/algorithms/mod.rs | 7 +++--- src/commands/algorithms/request.rs | 36 ++++++++++++++-------------- src/commands/algorithms/response.rs | 4 ++-- src/commands/capabilities/request.rs | 6 ++--- src/commands/version/mod.rs | 5 +--- src/commands/version/request.rs | 4 +--- src/context.rs | 2 +- src/test.rs | 36 ++++++++++++++-------------- 8 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs index 700ba9d..bbbb075 100644 --- a/src/commands/algorithms/mod.rs +++ b/src/commands/algorithms/mod.rs @@ -15,9 +15,9 @@ pub mod response; pub(crate) use request::*; pub(crate) use response::*; -use crate::codec::{CommonCodec, MessageBuf}; +use crate::codec::CommonCodec; use crate::protocol::LocalDeviceAlgorithms; -use crate::protocol::{algorithms::DheNamedGroup, SpdmMsgHdr, SpdmVersion}; +use crate::protocol::{SpdmMsgHdr, SpdmVersion}; use bitfield::bitfield; use core::mem::size_of; @@ -497,8 +497,9 @@ impl CommonCodec for AlgStructure {} #[cfg(test)] mod tests { - use super::*; + // use super::*; + #[ignore] #[test] fn test_min_req_len() { todo!(); diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index f73d579..cd99e9d 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -5,15 +5,12 @@ use crate::{ commands::algorithms::{AlgStructure, AlgorithmsResp, ExtendedAlgo, NegotiateAlgorithmsReq}, commands::error_rsp::ErrorCode, context::SpdmContext, - error::{CommandError, CommandResult, SpdmError}, - protocol::{ - BaseAsymAlgo, BaseAsymAlgoType, BaseHashAlgo, BaseHashAlgoType, DeviceAlgorithms, - MeasurementSpecification, OtherParamSupport, SpdmMsgHdr, SpdmVersion, - }, + error::{CommandError, CommandResult}, + protocol::{DeviceAlgorithms, SpdmMsgHdr}, }; /// Parse and handle the NEGOTIATE_ALGORITHMS response from the Responder. -pub fn handle_algorithms_response<'a>( +pub(crate) fn handle_algorithms_response<'a>( ctx: &mut SpdmContext<'a>, resp_header: SpdmMsgHdr, resp: &mut MessageBuf<'a>, @@ -89,12 +86,14 @@ pub fn handle_algorithms_response<'a>( // requires hashing operations, this value shall be set to zero. The Responder // shall set no more than one bit. - let mut peer_device_algorithms = DeviceAlgorithms::default(); - peer_device_algorithms.measurement_spec = algo_resp.measurement_specification_sel; - peer_device_algorithms.other_param_support = algo_resp.other_params_selection; - peer_device_algorithms.base_asym_algo = algo_resp.base_asym_sel; - peer_device_algorithms.base_hash_algo = algo_resp.base_hash_sel; - peer_device_algorithms.mel_specification = algo_resp.mel_specification_sel; + let peer_device_algorithms = DeviceAlgorithms { + measurement_spec: algo_resp.measurement_specification_sel, + other_param_support: algo_resp.other_params_selection, + base_asym_algo: algo_resp.base_asym_sel, + base_hash_algo: algo_resp.base_hash_sel, + mel_specification: algo_resp.mel_specification_sel, + ..Default::default() + }; ctx.state .connection_info @@ -102,7 +101,7 @@ pub fn handle_algorithms_response<'a>( // The spec defines this is A' elem of {0, 1} // TODO: add them to state? - let ext_asym_alog = if algo_resp.ext_asym_sel_count == 1 { + let _ext_asym_alog = if algo_resp.ext_asym_sel_count == 1 { Some(ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?) } else { None @@ -110,18 +109,18 @@ pub fn handle_algorithms_response<'a>( // The spec defines this is E' elem of {0, 1} // TODO: add them to state? - let ext_hash_algo = if algo_resp.ext_hash_sel_count == 1 { + let _ext_hash_algo = if algo_resp.ext_hash_sel_count == 1 { Some(ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?) } else { None }; - for i in 0..algo_resp.num_alg_struct_tables { + for _ in 0..algo_resp.num_alg_struct_tables { let alg_struct = AlgStructure::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; // For each struct table, we need to decode the variable length fields. for _ in 0..alg_struct.ext_alg_count() { - let ext_algo = + let _ext_algo = ExtendedAlgo::decode(resp).map_err(|e| (true, CommandError::Codec(e)))?; } } @@ -271,10 +270,11 @@ pub fn generate_negotiate_algorithms_request<'a>( #[cfg(test)] mod tests { - use crate::test::MockResources; + // use crate::test::MockResources; - use super::*; + // use super::*; + #[ignore] #[test] pub fn test_parse_negotiate_algorithms() { todo!(); diff --git a/src/commands/algorithms/response.rs b/src/commands/algorithms/response.rs index 5e65186..1799bd9 100644 --- a/src/commands/algorithms/response.rs +++ b/src/commands/algorithms/response.rs @@ -1,10 +1,10 @@ // Licensed under the Apache-2.0 license use crate::{ - codec::{Codec, CommonCodec, MessageBuf}, + codec::{Codec, MessageBuf}, commands::algorithms::*, context::SpdmContext, - error::{CommandError, CommandResult, SpdmError}, + error::CommandResult, protocol::{SpdmMsgHdr, SpdmVersion}, state::ConnectionState, transcript::TranscriptContext, diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 13dace5..2e76eed 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -3,9 +3,7 @@ use crate::commands::error_rsp::ErrorCode; use crate::{codec::MessageBuf, context::SpdmContext, error::CommandResult, protocol::SpdmMsgHdr}; -use crate::commands::capabilities::{ - req_flag_compatible, GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12, -}; +use crate::commands::capabilities::{GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12}; use crate::protocol::{capabilities::DeviceCapabilities, ReqRespCode, SpdmVersion}; use crate::error::CommandError; @@ -56,7 +54,7 @@ pub(crate) fn handle_capabilities_response<'a>( peer_capabilities.ct_exponent = resp_11.ct_exponent; // TODO? - let flags = resp_11.flags; + let _flags = resp_11.flags; // THIS FAILS // if !req_flag_compatible(version, &flags) { // Err(ctx.generate_error_response(resp, ErrorCode::InvalidPolicy, 0, None))?; diff --git a/src/commands/version/mod.rs b/src/commands/version/mod.rs index 08f3509..cfdb0e0 100644 --- a/src/commands/version/mod.rs +++ b/src/commands/version/mod.rs @@ -5,10 +5,7 @@ pub mod response; pub(crate) use request::*; pub(crate) use response::*; -use crate::{ - codec::{Codec, CommonCodec, MessageBuf}, - protocol::SpdmVersion, -}; +use crate::{codec::CommonCodec, protocol::SpdmVersion}; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 0680a98..66178bf 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -10,9 +10,7 @@ use crate::{ }; use crate::commands::error_rsp::ErrorCode; -use crate::commands::version::{ - VersionNumberEntry, VersionReqPayload, VersionRespCommon, VERSION_ENTRY_SIZE, -}; +use crate::commands::version::{VersionNumberEntry, VersionReqPayload, VersionRespCommon}; use crate::protocol::SpdmVersion; diff --git a/src/context.rs b/src/context.rs index 334d471..5302541 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,7 @@ // Licensed under the Apache-2.0 license // use crate::cert_mgr::DeviceCertsManager; +use crate::cert_store::*; use crate::chunk_ctx::LargeResponseCtx; use crate::codec::{Codec, MessageBuf}; use crate::commands::capabilities::handle_capabilities_response; @@ -22,7 +23,6 @@ use crate::protocol::version::*; use crate::protocol::DeviceCapabilities; use crate::state::{ConnectionState, State}; use crate::transcript::{TranscriptContext, TranscriptManager}; -use crate::{cert_store::*, commands, protocol}; pub struct SpdmContext<'a> { transport: &'a mut dyn SpdmTransport, diff --git a/src/test.rs b/src/test.rs index 03f7eda..6a1c560 100644 --- a/src/test.rs +++ b/src/test.rs @@ -71,26 +71,26 @@ struct StdHash; impl SpdmHash for StdHash { fn hash( &mut self, - hash_algo: crate::platform::hash::SpdmHashAlgoType, - data: &[u8], - hash: &mut [u8], + _hash_algo: crate::platform::hash::SpdmHashAlgoType, + _data: &[u8], + _hash: &mut [u8], ) -> crate::platform::hash::SpdmHashResult<()> { todo!() } fn init( &mut self, - hash_algo: crate::platform::hash::SpdmHashAlgoType, - data: Option<&[u8]>, + _hash_algo: crate::platform::hash::SpdmHashAlgoType, + _data: Option<&[u8]>, ) -> crate::platform::hash::SpdmHashResult<()> { todo!() } - fn update(&mut self, data: &[u8]) -> crate::platform::hash::SpdmHashResult<()> { + fn update(&mut self, _data: &[u8]) -> crate::platform::hash::SpdmHashResult<()> { todo!() } - fn finalize(&mut self, hash: &mut [u8]) -> crate::platform::hash::SpdmHashResult<()> { + fn finalize(&mut self, _hash: &mut [u8]) -> crate::platform::hash::SpdmHashResult<()> { todo!() } @@ -106,13 +106,13 @@ impl SpdmHash for StdHash { struct StdRng; impl SpdmRng for StdRng { - fn get_random_bytes(&mut self, buf: &mut [u8]) -> crate::platform::rng::SpdmRngResult<()> { + fn get_random_bytes(&mut self, _buf: &mut [u8]) -> crate::platform::rng::SpdmRngResult<()> { todo!() } fn generate_random_number( &mut self, - random_number: &mut [u8], + _random_number: &mut [u8], ) -> crate::platform::rng::SpdmRngResult<()> { todo!() } @@ -122,29 +122,29 @@ struct MockTransport; impl SpdmTransport for MockTransport { fn send_request<'a>( &mut self, - dest_eid: u8, - req: &mut crate::codec::MessageBuf<'a>, + _dest_eid: u8, + _req: &mut crate::codec::MessageBuf<'a>, ) -> crate::platform::transport::TransportResult<()> { todo!() } fn receive_response<'a>( &mut self, - rsp: &mut crate::codec::MessageBuf<'a>, + _rsp: &mut crate::codec::MessageBuf<'a>, ) -> crate::platform::transport::TransportResult<()> { todo!() } fn receive_request<'a>( &mut self, - req: &mut crate::codec::MessageBuf<'a>, + _req: &mut crate::codec::MessageBuf<'a>, ) -> crate::platform::transport::TransportResult<()> { todo!() } fn send_response<'a>( &mut self, - resp: &mut crate::codec::MessageBuf<'a>, + _resp: &mut crate::codec::MessageBuf<'a>, ) -> crate::platform::transport::TransportResult<()> { todo!() } @@ -173,12 +173,12 @@ impl SpdmCertStore for MockCertStore { fn cert_chain_len(&mut self, _asym: AsymAlgo, _slot_id: u8) -> CertStoreResult { Ok(128) } - fn get_cert_chain<'a>( + fn get_cert_chain( &mut self, _slot_id: u8, _asym: AsymAlgo, _offset: usize, - out: &'a mut [u8], + out: &mut [u8], ) -> CertStoreResult { let fill = out.len().min(16); for b in &mut out[..fill] { @@ -186,11 +186,11 @@ impl SpdmCertStore for MockCertStore { } Ok(fill) } - fn root_cert_hash<'a>( + fn root_cert_hash( &mut self, _slot_id: u8, _asym: AsymAlgo, - out: &'a mut [u8; crate::protocol::SHA384_HASH_SIZE], + out: &mut [u8; crate::protocol::SHA384_HASH_SIZE], ) -> CertStoreResult<()> { for b in out.iter_mut() { *b = 0x11; From 3c6fd5b4bdc49b95808a1d53561daeda58753eda Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 5 Feb 2026 18:58:51 +0100 Subject: [PATCH 24/86] spdm: add digests Signed-off-by: leongross --- examples/spdm_requester.rs | 25 ++- src/commands/challenge_auth_rsp.rs | 2 +- .../{digests_rsp.rs => digests/mod.rs} | 134 +--------------- src/commands/digests/request.rs | 145 ++++++++++++++++++ src/commands/digests/response.rs | 135 ++++++++++++++++ src/commands/mod.rs | 2 +- src/context.rs | 9 +- src/measurements/freeform_manifest.rs | 2 +- 8 files changed, 320 insertions(+), 134 deletions(-) rename src/commands/{digests_rsp.rs => digests/mod.rs} (55%) create mode 100644 src/commands/digests/request.rs create mode 100644 src/commands/digests/response.rs diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 323aa03..e0e167a 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -28,6 +28,7 @@ use spdm_lib::commands::algorithms::{ request::generate_negotiate_algorithms_request, AlgStructure, AlgType, ExtendedAlgo, RegistryId, }; use spdm_lib::commands::capabilities::request::generate_capabilities_request_local; +use spdm_lib::commands::digests::request::generate_digest_request; use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; /// Responder configuration @@ -118,7 +119,7 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { // Perform a VCS flow (Version, Capabilities, Algorithms) // using the real SPDM library processing with platform implementations. -fn vca_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { +fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { let mut transport = SpdmSocketTransport::new( stream, platform::socket_transport::SocketTransportType::None, @@ -306,6 +307,26 @@ fn vca_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("ALGORITHMS: {:x?}", &message_buffer.message_data()); } + println!("SPDM VCA flow completed successfully"); + + message_buffer.reset(); + generate_digest_request(&mut spdm_context, &mut message_buffer).unwrap(); + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + + if config.verbose { + println!("GET_DIGESTS: {:x?}", &message_buffer.message_data()); + } + + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + + if config.verbose { + println!("DIGESTS: {:x?}", &message_buffer.message_data()); + } + Ok(()) } @@ -470,7 +491,7 @@ fn main() -> Result<(), Box> { } // Handle client with real SPDM processing using platform implementations - vca_flow(stream, &config)?; + full_flow(stream, &config)?; Ok(()) } diff --git a/src/commands/challenge_auth_rsp.rs b/src/commands/challenge_auth_rsp.rs index 3e3134d..c343454 100644 --- a/src/commands/challenge_auth_rsp.rs +++ b/src/commands/challenge_auth_rsp.rs @@ -2,7 +2,7 @@ use crate::cert_store::MAX_CERT_SLOTS_SUPPORTED; use crate::codec::{Codec, CommonCodec, MessageBuf}; use crate::commands::algorithms::selected_measurement_specification; -use crate::commands::digests_rsp::compute_cert_chain_hash; +use crate::commands::digests::compute_cert_chain_hash; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult, PlatformError}; diff --git a/src/commands/digests_rsp.rs b/src/commands/digests/mod.rs similarity index 55% rename from src/commands/digests_rsp.rs rename to src/commands/digests/mod.rs index aaf8893..d6ced3f 100644 --- a/src/commands/digests_rsp.rs +++ b/src/commands/digests/mod.rs @@ -2,16 +2,20 @@ use crate::cert_store::{cert_slot_mask, SpdmCertStore}; use crate::codec::{Codec, CommonCodec, MessageBuf}; -use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult, PlatformError}; use crate::platform::hash::{SpdmHash, SpdmHashAlgoType}; + use crate::protocol::*; -use crate::state::ConnectionState; -use crate::transcript::TranscriptContext; -use core::mem::size_of; + use zerocopy::{FromBytes, Immutable, IntoBytes}; +pub mod request; +pub mod response; + +pub(crate) use request::*; +pub(crate) use response::*; + #[derive(IntoBytes, FromBytes, Immutable, Default)] #[repr(C)] pub struct GetDigestsReq { @@ -121,69 +125,6 @@ fn encode_cert_chain_digest( Ok(SHA384_HASH_SIZE) } -fn generate_digests_response<'a>( - ctx: &mut SpdmContext<'a>, - rsp: &mut MessageBuf<'a>, -) -> CommandResult<()> { - // Ensure the selected hash algorithm is SHA384 and retrieve the asymmetric algorithm (currently only ECC-P384 is supported) - ctx.verify_selected_hash_algo() - .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; - let asym_algo = ctx - .selected_base_asym_algo() - .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; - - // Get the supported and provisioned slot masks. - let (supported_slot_mask, provisioned_slot_mask) = cert_slot_mask(ctx.device_certs_store); - - // No slots provisioned with certificates - let slot_cnt = provisioned_slot_mask.count_ones() as usize; - if slot_cnt == 0 { - Err(ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; - } - - let connection_version = ctx.state.connection_info.version_number(); - - // Start filling the response payload - let spdm_resp_hdr = SpdmMsgHdr::new(connection_version, ReqRespCode::Digests); - let mut payload_len = spdm_resp_hdr - .encode(rsp) - .map_err(|_| (false, CommandError::BufferTooSmall))?; - - // Fill the response header with param1 and param2 - let dgst_rsp_common = GetDigestsRespCommon { - supported_slot_mask, - provisioned_slot_mask, - }; - - payload_len += dgst_rsp_common - .encode(rsp) - .map_err(|_| (false, CommandError::BufferTooSmall))?; - - // Encode the certificate chain digests for each provisioned slot - for slot_id in 0..slot_cnt { - payload_len += encode_cert_chain_digest( - ctx.hash, - slot_id as u8, - ctx.device_certs_store, - asym_algo, - rsp, - ) - .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; - } - - // Fill the multi-key connection response data if applicable - if connection_version >= SpdmVersion::V13 && ctx.state.connection_info.multi_key_conn_rsp() { - payload_len += encode_multi_key_conn_rsp_data(ctx, provisioned_slot_mask, rsp)?; - } - - // Push data offset up by total payload length - rsp.push_data(payload_len) - .map_err(|_| (false, CommandError::BufferTooSmall))?; - - // Append the response message to the M1 transcript - ctx.append_message_to_transcript(rsp, TranscriptContext::M1) -} - fn encode_multi_key_conn_rsp_data( ctx: &mut SpdmContext, provisioned_slot_mask: u8, @@ -244,62 +185,3 @@ fn encode_multi_key_conn_rsp_data( Ok(total_size) } - -fn process_get_digests<'a>( - ctx: &mut SpdmContext<'a>, - spdm_hdr: SpdmMsgHdr, - req_payload: &mut MessageBuf<'a>, -) -> CommandResult<()> { - // Validate the version - let connection_version = ctx.state.connection_info.version_number(); - match spdm_hdr.version() { - Ok(version) if version == connection_version => {} - _ => Err(ctx.generate_error_response(req_payload, ErrorCode::VersionMismatch, 0, None))?, - } - - let req = GetDigestsReq::decode(req_payload).map_err(|_| { - ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) - })?; - - // Reserved fields must be zero - or unexpected request error - if req.param1 != 0 || req.param2 != 0 { - Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; - } - - // Reset the transcript manager - ctx.reset_transcript_via_req_code(ReqRespCode::GetDigests); - - // Append the request message to the M1 transcript - ctx.append_message_to_transcript(req_payload, TranscriptContext::M1) -} - -pub(crate) fn handle_get_digests<'a>( - ctx: &mut SpdmContext<'a>, - spdm_hdr: SpdmMsgHdr, - req_payload: &mut MessageBuf<'a>, -) -> CommandResult<()> { - // Validate the connection state - if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { - Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; - } - - // Check if the certificate capability is supported - if ctx.local_capabilities.flags.cert_cap() == 0 { - Err(ctx.generate_error_response(req_payload, ErrorCode::UnsupportedRequest, 0, None))?; - } - - // Process GET_DIGESTS request - process_get_digests(ctx, spdm_hdr, req_payload)?; - - // Generate DIGESTS response - ctx.prepare_response_buffer(req_payload)?; - generate_digests_response(ctx, req_payload)?; - - if ctx.state.connection_info.state() < ConnectionState::AfterDigest { - ctx.state - .connection_info - .set_state(ConnectionState::AfterDigest); - } - - Ok(()) -} diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs new file mode 100644 index 0000000..2afa97a --- /dev/null +++ b/src/commands/digests/request.rs @@ -0,0 +1,145 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::Codec; +use crate::context::SpdmContext; +use crate::error::{CommandError, CommandResult}; +use crate::protocol::certs::{CertificateInfo, KeyUsageMask}; +use crate::protocol::{SpdmMsgHdr, SpdmVersion, SHA384_HASH_SIZE}; +use crate::state::ConnectionState; +use crate::transcript::TranscriptContext; +use zerocopy::FromBytes; + +use super::*; + +pub fn generate_digest_request( + ctx: &mut SpdmContext, + message_buffer: &mut MessageBuf, +) -> CommandResult<()> { + SpdmMsgHdr::new( + ctx.state.connection_info.version_number(), + crate::protocol::ReqRespCode::GetDigests, + ) + .encode(message_buffer) + .map_err(|e| (false, CommandError::Codec(e)))?; + + let payload = GetDigestsReq::default(); + payload + .encode(message_buffer) + .map_err(|e| (false, CommandError::Codec(e)))?; + + ctx.append_message_to_transcript(message_buffer, crate::transcript::TranscriptContext::L1) +} + +pub(crate) fn handle_digests_response<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + resp_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + return Err((false, CommandError::UnsupportedRequest)); + } + + let con_version = ctx.state.connection_info.version_number(); + let version = match spdm_hdr.version() { + Ok(version) if version == con_version => version, + _ => return Err((false, CommandError::InvalidResponse)), + }; + + let digests_resp_common = + GetDigestsRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; + + // ATM we do not care, since we only support slot 0. + // Also this is a hacky way of representing the the remote slots + let _supported_slot_mask = digests_resp_common.supported_slot_mask; + let provisioned_slot_mask = digests_resp_common.provisioned_slot_mask; + + let slot_n = provisioned_slot_mask.count_ones() as usize; + if slot_n == 0 { + return Err((false, CommandError::InvalidResponse)); + } + + for _slot_id in 0..slot_n { + if resp_payload.data_len() < SHA384_HASH_SIZE { + return Err((false, CommandError::InvalidResponse)); + } + + let _digest = resp_payload + .data(SHA384_HASH_SIZE) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // TODO: Store the digest in context for later verification + + resp_payload + .pull_data(SHA384_HASH_SIZE) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + + if version >= SpdmVersion::V13 && ctx.state.connection_info.multi_key_conn_rsp() { + for _slot_id in 0..slot_n { + if resp_payload.data_len() < size_of::() { + return Err((false, CommandError::InvalidResponse)); + } + let _key_pair_id = resp_payload + .data(size_of::()) + .map_err(|e| (false, CommandError::Codec(e)))?[0]; + + // TODO: Store key_pair_id in context + + resp_payload + .pull_data(size_of::()) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + + for _slot_id in 0..slot_n { + if resp_payload.data_len() < size_of::() { + return Err((false, CommandError::InvalidResponse)); + } + let data = resp_payload + .data(size_of::()) + .map_err(|e| (false, CommandError::Codec(e)))?; + let _cert_info = CertificateInfo::read_from_bytes(data) + .map_err(|_| (false, CommandError::InvalidResponse))?; + + // TODO: Store cert_info in context + + resp_payload + .pull_data(size_of::()) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + + // Decode KeyUsageMasks (one per slot) + for _slot_id in 0..slot_n { + if resp_payload.data_len() < size_of::() { + return Err((false, CommandError::InvalidResponse)); + } + let data = resp_payload + .data(size_of::()) + .map_err(|e| (false, CommandError::Codec(e)))?; + let _key_usage_mask = KeyUsageMask::read_from_bytes(data) + .map_err(|_| (false, CommandError::InvalidResponse))?; + + // TODO: Store key_usage_mask in context + + resp_payload + .pull_data(size_of::()) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + } + + if ctx.state.connection_info.state() < ConnectionState::AfterDigest { + ctx.state + .connection_info + .set_state(ConnectionState::AfterDigest); + } + + ctx.append_message_to_transcript(resp_payload, TranscriptContext::L1) +} + +#[cfg(test)] +pub mod tests { + + #[test] + fn test_generate_digest_request() { + todo!(); + } +} diff --git a/src/commands/digests/response.rs b/src/commands/digests/response.rs new file mode 100644 index 0000000..0187abb --- /dev/null +++ b/src/commands/digests/response.rs @@ -0,0 +1,135 @@ +// Licensed under the Apache-2.0 license + +use crate::cert_store::{cert_slot_mask, SpdmCertStore}; +use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::commands::error_rsp::ErrorCode; +use crate::context::SpdmContext; +use crate::error::{CommandError, CommandResult, PlatformError}; +use crate::platform::hash::{SpdmHash, SpdmHashAlgoType}; +use crate::state::ConnectionState; +use crate::transcript::TranscriptContext; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use super::*; + +pub(crate) fn generate_digests_response<'a>( + ctx: &mut SpdmContext<'a>, + rsp: &mut MessageBuf<'a>, +) -> CommandResult<()> { + // Ensure the selected hash algorithm is SHA384 and retrieve the asymmetric algorithm (currently only ECC-P384 is supported) + ctx.verify_selected_hash_algo() + .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; + let asym_algo = ctx + .selected_base_asym_algo() + .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; + + // Get the supported and provisioned slot masks. + let (supported_slot_mask, provisioned_slot_mask) = cert_slot_mask(ctx.device_certs_store); + + // No slots provisioned with certificates + let slot_cnt = provisioned_slot_mask.count_ones() as usize; + if slot_cnt == 0 { + Err(ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; + } + + let connection_version = ctx.state.connection_info.version_number(); + + // Start filling the response payload + let spdm_resp_hdr = SpdmMsgHdr::new(connection_version, ReqRespCode::Digests); + let mut payload_len = spdm_resp_hdr + .encode(rsp) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + // Fill the response header with param1 and param2 + let dgst_rsp_common = GetDigestsRespCommon { + supported_slot_mask, + provisioned_slot_mask, + }; + + payload_len += dgst_rsp_common + .encode(rsp) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + // Encode the certificate chain digests for each provisioned slot + for slot_id in 0..slot_cnt { + payload_len += encode_cert_chain_digest( + ctx.hash, + slot_id as u8, + ctx.device_certs_store, + asym_algo, + rsp, + ) + .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::Unspecified, 0, None))?; + } + + // Fill the multi-key connection response data if applicable + if connection_version >= SpdmVersion::V13 && ctx.state.connection_info.multi_key_conn_rsp() { + payload_len += encode_multi_key_conn_rsp_data(ctx, provisioned_slot_mask, rsp)?; + } + + // Push data offset up by total payload length + rsp.push_data(payload_len) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + // Append the response message to the M1 transcript + ctx.append_message_to_transcript(rsp, TranscriptContext::M1) +} + +fn process_get_digests<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + req_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + // Validate the version + let connection_version = ctx.state.connection_info.version_number(); + match spdm_hdr.version() { + Ok(version) if version == connection_version => {} + _ => Err(ctx.generate_error_response(req_payload, ErrorCode::VersionMismatch, 0, None))?, + } + + let req = GetDigestsReq::decode(req_payload).map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?; + + // Reserved fields must be zero - or unexpected request error + if req.param1 != 0 || req.param2 != 0 { + Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; + } + + // Reset the transcript manager + ctx.reset_transcript_via_req_code(ReqRespCode::GetDigests); + + // Append the request message to the M1 transcript + ctx.append_message_to_transcript(req_payload, TranscriptContext::M1) +} + +pub(crate) fn handle_get_digests<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + req_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + // Validate the connection state + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; + } + + // Check if the certificate capability is supported + if ctx.local_capabilities.flags.cert_cap() == 0 { + Err(ctx.generate_error_response(req_payload, ErrorCode::UnsupportedRequest, 0, None))?; + } + + // Process GET_DIGESTS request + process_get_digests(ctx, spdm_hdr, req_payload)?; + + // Generate DIGESTS response + ctx.prepare_response_buffer(req_payload)?; + generate_digests_response(ctx, req_payload)?; + + if ctx.state.connection_info.state() < ConnectionState::AfterDigest { + ctx.state + .connection_info + .set_state(ConnectionState::AfterDigest); + } + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 413fb55..ef180b7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,7 +5,7 @@ pub mod capabilities; pub mod certificate_rsp; pub mod challenge_auth_rsp; pub mod chunk_get_rsp; -pub mod digests_rsp; +pub mod digests; pub mod error_rsp; pub mod measurements_rsp; pub mod version; diff --git a/src/context.rs b/src/context.rs index 5302541..0ee7fa9 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,12 +5,14 @@ use crate::cert_store::*; use crate::chunk_ctx::LargeResponseCtx; use crate::codec::{Codec, MessageBuf}; use crate::commands::capabilities::handle_capabilities_response; +use crate::commands::digests::{handle_digests_response, handle_get_digests}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ - algorithms, capabilities, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, digests_rsp, - measurements_rsp, version, + algorithms, capabilities, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, measurements_rsp, + version, }; + use crate::error::*; use crate::measurements::common::SpdmMeasurements; use crate::platform::evidence::SpdmEvidence; @@ -169,7 +171,7 @@ impl<'a> SpdmContext<'a> { ReqRespCode::NegotiateAlgorithms => { algorithms::handle_negotiate_algorithms(self, req_msg_header, req)? } - ReqRespCode::GetDigests => digests_rsp::handle_get_digests(self, req_msg_header, req)?, + ReqRespCode::GetDigests => handle_get_digests(self, req_msg_header, req)?, ReqRespCode::GetCertificate => { certificate_rsp::handle_get_certificate(self, req_msg_header, req)? } @@ -214,6 +216,7 @@ impl<'a> SpdmContext<'a> { ReqRespCode::Algorithms => { algorithms::handle_algorithms_response(self, resp_msg_header, resp)? } + ReqRespCode::Digests => handle_digests_response(self, resp_msg_header, resp)?, _ => Err((false, CommandError::UnsupportedResponse))?, } diff --git a/src/measurements/freeform_manifest.rs b/src/measurements/freeform_manifest.rs index c467948..dece0b1 100644 --- a/src/measurements/freeform_manifest.rs +++ b/src/measurements/freeform_manifest.rs @@ -150,7 +150,7 @@ impl FreeformManifest { let copied_len = evidence .pcr_quote(quote_slice, with_pqc_sig) - .map_err(|e| MeasurementsError::Evidence(e))?; + .map_err(MeasurementsError::Evidence)?; if copied_len != measurement_value_size { return Err(MeasurementsError::MeasurementSizeMismatch); } From 360753fc2a2ad6223246d343d19ad393efcfc541 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 10 Feb 2026 14:19:32 +0100 Subject: [PATCH 25/86] Refactor CERTIFICATE command --- src/commands/certificate/mod.rs | 124 ++++++++++++++++++ src/commands/certificate/request.rs | 86 ++++++++++++ .../response.rs} | 120 +---------------- src/commands/mod.rs | 2 +- src/context.rs | 8 +- 5 files changed, 223 insertions(+), 117 deletions(-) create mode 100644 src/commands/certificate/mod.rs create mode 100644 src/commands/certificate/request.rs rename src/commands/{certificate_rsp.rs => certificate/response.rs} (70%) diff --git a/src/commands/certificate/mod.rs b/src/commands/certificate/mod.rs new file mode 100644 index 0000000..60b9f01 --- /dev/null +++ b/src/commands/certificate/mod.rs @@ -0,0 +1,124 @@ +// Licensed under the Apache-2.0 license + +pub mod request; +pub mod response; + +pub(crate) use request::*; +pub(crate) use response::*; + +use crate::cert_store::SpdmCertStore; +use crate::codec::{CommonCodec, MessageBuf}; +use crate::error::{CommandError, CommandResult}; +use crate::protocol::*; +use bitfield::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(FromBytes, IntoBytes, Immutable)] +#[repr(C)] +pub struct GetCertificateReq { + pub slot_id: SlotId, + pub param2: CertificateReqAttributes, + pub offset: u16, + pub length: u16, +} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable)] + #[repr(C)] + pub struct SlotId(u8); + impl Debug; + u8; + pub slot_id, set_slot_id: 3,0; + reserved, _: 7,4; +} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable)] + #[repr(C)] + pub struct CertificateReqAttributes(u8); + impl Debug; + u8; + pub slot_size_requested, set_slot_size_requested: 0,0; + reserved, _: 7,1; +} + +impl CommonCodec for GetCertificateReq {} + +#[derive(IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub struct CertificateRespCommon { + pub slot_id: SlotId, + pub param2: CertificateRespAttributes, + pub portion_length: u16, + pub remainder_length: u16, +} + +impl CommonCodec for CertificateRespCommon {} + +impl CertificateRespCommon { + pub fn new( + slot_id: SlotId, + param2: CertificateRespAttributes, + portion_length: u16, + remainder_length: u16, + ) -> Self { + Self { + slot_id, + param2, + portion_length, + remainder_length, + } + } +} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable, Default)] + #[repr(C)] + pub struct CertificateRespAttributes(u8); + impl Debug; + u8; + pub certificate_info, set_certificate_info: 2,0; + reserved, _: 7,3; +} + +pub(crate) fn encode_certchain_metadata( + cert_store: &mut dyn SpdmCertStore, + total_certchain_len: u16, + slot_id: u8, + asym_algo: AsymAlgo, + offset: usize, + length: usize, + rsp: &mut MessageBuf<'_>, +) -> CommandResult { + let mut certchain_metadata = [0u8; SPDM_CERT_CHAIN_METADATA_LEN as usize]; + + // Read the cert chain header first + let cert_chain_hdr = SpdmCertChainHeader { + length: total_certchain_len, + reserved: 0, + }; + let cert_chain_hdr_bytes = cert_chain_hdr.as_bytes(); + certchain_metadata[..cert_chain_hdr_bytes.len()].copy_from_slice(cert_chain_hdr_bytes); + + // Read the root cert hash next + let mut root_hash_buf = [0u8; SHA384_HASH_SIZE]; + cert_store + .root_cert_hash(slot_id, asym_algo, &mut root_hash_buf) + .map_err(|e| (false, CommandError::CertStore(e)))?; + certchain_metadata[cert_chain_hdr_bytes.len()..].copy_from_slice(&root_hash_buf[..]); + + let write_len = (SPDM_CERT_CHAIN_METADATA_LEN - offset as u16).min(length as u16) as usize; + + rsp.put_data(write_len) + .map_err(|e| (false, CommandError::Codec(e)))?; + + let cert_portion = rsp + .data_mut(write_len) + .map_err(|e| (false, CommandError::Codec(e)))?; + + cert_portion[..write_len].copy_from_slice(&certchain_metadata[offset..offset + write_len]); + rsp.pull_data(write_len) + .map_err(|e| (false, CommandError::Codec(e)))?; + + Ok(write_len) +} diff --git a/src/commands/certificate/request.rs b/src/commands/certificate/request.rs new file mode 100644 index 0000000..f0706f7 --- /dev/null +++ b/src/commands/certificate/request.rs @@ -0,0 +1,86 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::MessageBuf; +use crate::context::SpdmContext; +use crate::error::CommandResult; +use crate::protocol::SpdmMsgHdr; + +/// Generate the GET_CERTIFICATE request +/// +/// # Arguments +/// * `ctx`: The SPDM context +/// * `req_buf`: Buffer to write the request into +/// * `slot_id`: Certificate slot identifier (0-7) +/// * `offset`: Byte offset into the certificate chain +/// * `length`: Number of bytes to request +/// +/// # Returns +/// - () on success +/// - [CommandError] on failure +/// +/// # TODO +/// Implement GET_CERTIFICATE request generation including: +/// - Create and encode SpdmMsgHdr with ReqRespCode::GetCertificate +/// - Create and encode GetCertificateReq payload +/// - Append to transcript (TranscriptContext::M1) +pub fn generate_get_certificate<'a>( + _ctx: &mut SpdmContext<'a>, + _req_buf: &mut MessageBuf<'a>, + _slot_id: u8, + _offset: u16, + _length: u16, +) -> CommandResult<()> { + todo!("Implement GET_CERTIFICATE request generation") +} + +/// Process CERTIFICATE response payload (private helper) +/// +/// # Arguments +/// * `ctx`: The SPDM context +/// * `spdm_hdr`: The SPDM message header from the response +/// * `resp_payload`: Buffer containing the response payload +/// +/// # Returns +/// - () on success +/// - [CommandError] on failure +/// +/// # TODO +/// Implement CERTIFICATE response processing including: +/// - Validate version matches connection version +/// - Decode CertificateRespCommon +/// - Validate slot_id matches request +/// - Extract and store certificate chain portion +/// - Handle certificate chain metadata if offset is 0 +/// - Track remainder_length for multi-part transfers +fn process_certificate<'a>( + _ctx: &mut SpdmContext<'a>, + _spdm_hdr: SpdmMsgHdr, + _resp_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + todo!("Implement CERTIFICATE response processing") +} + +/// Requester function handling the parsing of the CERTIFICATE response sent by the Responder. +/// +/// # Arguments +/// * `ctx`: The SPDM context +/// * `resp_header`: The SPDM message header from the response +/// * `resp`: Buffer containing the complete response message +/// +/// # Returns +/// - () on success +/// - [CommandError] on failure +/// +/// # TODO +/// Implement CERTIFICATE response handler including: +/// - Verify connection state (should be >= AlgorithmsNegotiated) +/// - Call process_certificate to parse and validate response +/// - Append response to transcript (TranscriptContext::M1) +/// - Update connection state to AfterCertificate if needed +pub(crate) fn handle_certificate_response<'a>( + _ctx: &mut SpdmContext<'a>, + _resp_header: SpdmMsgHdr, + _resp: &mut MessageBuf<'a>, +) -> CommandResult<()> { + todo!("Implement CERTIFICATE response handler") +} diff --git a/src/commands/certificate_rsp.rs b/src/commands/certificate/response.rs similarity index 70% rename from src/commands/certificate_rsp.rs rename to src/commands/certificate/response.rs index a14f39e..8a16a8d 100644 --- a/src/commands/certificate_rsp.rs +++ b/src/commands/certificate/response.rs @@ -1,125 +1,17 @@ // Licensed under the Apache-2.0 license -use crate::cert_store::{cert_slot_mask, SpdmCertStore, MAX_CERT_SLOTS_SUPPORTED}; -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::cert_store::{cert_slot_mask, MAX_CERT_SLOTS_SUPPORTED}; +use crate::codec::{Codec, MessageBuf}; +use crate::commands::certificate::{ + encode_certchain_metadata, CertificateRespAttributes, CertificateRespCommon, GetCertificateReq, + SlotId, +}; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; -use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(FromBytes, IntoBytes, Immutable)] -#[repr(C)] -pub struct GetCertificateReq { - pub slot_id: SlotId, - pub param2: CertificateReqAttributes, - pub offset: u16, - pub length: u16, -} - -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable)] - #[repr(C)] - pub struct SlotId(u8); - impl Debug; - u8; - pub slot_id, set_slot_id: 3,0; - reserved, _: 7,4; -} - -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable)] - #[repr(C)] - pub struct CertificateReqAttributes(u8); - impl Debug; - u8; - pub slot_size_requested, set_slot_size_requested: 0,0; - reserved, _: 7,1; -} - -impl CommonCodec for GetCertificateReq {} - -#[derive(IntoBytes, FromBytes, Immutable)] -#[repr(C, packed)] -pub struct CertificateRespCommon { - pub slot_id: SlotId, - pub param2: CertificateRespAttributes, - pub portion_length: u16, - pub remainder_length: u16, -} - -impl CommonCodec for CertificateRespCommon {} - -impl CertificateRespCommon { - pub fn new( - slot_id: SlotId, - param2: CertificateRespAttributes, - portion_length: u16, - remainder_length: u16, - ) -> Self { - Self { - slot_id, - param2, - portion_length, - remainder_length, - } - } -} - -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable, Default)] - #[repr(C)] - pub struct CertificateRespAttributes(u8); - impl Debug; - u8; - pub certificate_info, set_certificate_info: 2,0; - reserved, _: 7,3; -} - -fn encode_certchain_metadata( - cert_store: &mut dyn SpdmCertStore, - total_certchain_len: u16, - slot_id: u8, - asym_algo: AsymAlgo, - offset: usize, - length: usize, - rsp: &mut MessageBuf<'_>, -) -> CommandResult { - let mut certchain_metadata = [0u8; SPDM_CERT_CHAIN_METADATA_LEN as usize]; - - // Read the cert chain header first - let cert_chain_hdr = SpdmCertChainHeader { - length: total_certchain_len, - reserved: 0, - }; - let cert_chain_hdr_bytes = cert_chain_hdr.as_bytes(); - certchain_metadata[..cert_chain_hdr_bytes.len()].copy_from_slice(cert_chain_hdr_bytes); - - // Read the root cert hash next - let mut root_hash_buf = [0u8; SHA384_HASH_SIZE]; - cert_store - .root_cert_hash(slot_id, asym_algo, &mut root_hash_buf) - .map_err(|e| (false, CommandError::CertStore(e)))?; - certchain_metadata[cert_chain_hdr_bytes.len()..].copy_from_slice(&root_hash_buf[..]); - - let write_len = (SPDM_CERT_CHAIN_METADATA_LEN - offset as u16).min(length as u16) as usize; - - rsp.put_data(write_len) - .map_err(|e| (false, CommandError::Codec(e)))?; - - let cert_portion = rsp - .data_mut(write_len) - .map_err(|e| (false, CommandError::Codec(e)))?; - - cert_portion[..write_len].copy_from_slice(&certchain_metadata[offset..offset + write_len]); - rsp.pull_data(write_len) - .map_err(|e| (false, CommandError::Codec(e)))?; - - Ok(write_len) -} fn generate_certificate_response<'a>( ctx: &mut SpdmContext<'a>, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index ef180b7..c935d11 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,7 +2,7 @@ pub mod algorithms; pub mod capabilities; -pub mod certificate_rsp; +pub mod certificate; pub mod challenge_auth_rsp; pub mod chunk_get_rsp; pub mod digests; diff --git a/src/context.rs b/src/context.rs index 0ee7fa9..f253884 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,7 +9,7 @@ use crate::commands::digests::{handle_digests_response, handle_get_digests}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ - algorithms, capabilities, certificate_rsp, challenge_auth_rsp, chunk_get_rsp, measurements_rsp, + algorithms, capabilities, certificate, challenge_auth_rsp, chunk_get_rsp, measurements_rsp, version, }; @@ -173,7 +173,7 @@ impl<'a> SpdmContext<'a> { } ReqRespCode::GetDigests => handle_get_digests(self, req_msg_header, req)?, ReqRespCode::GetCertificate => { - certificate_rsp::handle_get_certificate(self, req_msg_header, req)? + certificate::handle_get_certificate(self, req_msg_header, req)? } ReqRespCode::Challenge => { challenge_auth_rsp::handle_challenge(self, req_msg_header, req)? @@ -217,6 +217,10 @@ impl<'a> SpdmContext<'a> { algorithms::handle_algorithms_response(self, resp_msg_header, resp)? } ReqRespCode::Digests => handle_digests_response(self, resp_msg_header, resp)?, + // TODO: Implement certificate response handler + // ReqRespCode::Certificate => { + // certificate::handle_certificate_response(self, resp_msg_header, resp)? + // } _ => Err((false, CommandError::UnsupportedResponse))?, } From aef7a40375108a1d5f1f3a02f36a4eab3b418196 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 10 Feb 2026 17:55:18 +0100 Subject: [PATCH 26/86] Implement GET_CERTIFICATE request generator --- src/commands/certificate/mod.rs | 3 +- src/commands/certificate/request.rs | 83 +++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/commands/certificate/mod.rs b/src/commands/certificate/mod.rs index 60b9f01..b1476cf 100644 --- a/src/commands/certificate/mod.rs +++ b/src/commands/certificate/mod.rs @@ -3,7 +3,6 @@ pub mod request; pub mod response; -pub(crate) use request::*; pub(crate) use response::*; use crate::cert_store::SpdmCertStore; @@ -20,6 +19,8 @@ pub struct GetCertificateReq { pub param2: CertificateReqAttributes, pub offset: u16, pub length: u16, + // TODO: v1.4.0 has two additional fields, LargeOffset and LargeLength, and a new SlotId field. + // Strangely they are attributed to v1.3.0 in the Changelog though... } bitfield! { diff --git a/src/commands/certificate/request.rs b/src/commands/certificate/request.rs index f0706f7..2f98f7f 100644 --- a/src/commands/certificate/request.rs +++ b/src/commands/certificate/request.rs @@ -1,9 +1,12 @@ // Licensed under the Apache-2.0 license -use crate::codec::MessageBuf; +use crate::codec::{Codec, MessageBuf}; +use crate::commands::certificate::{CertificateReqAttributes, GetCertificateReq, SlotId}; use crate::context::SpdmContext; -use crate::error::CommandResult; -use crate::protocol::SpdmMsgHdr; +use crate::error::{CommandError, CommandResult}; +use crate::protocol::{ReqRespCode, SpdmMsgHdr}; +use crate::state::ConnectionState; +use crate::transcript::TranscriptContext; /// Generate the GET_CERTIFICATE request /// @@ -13,24 +16,72 @@ use crate::protocol::SpdmMsgHdr; /// * `slot_id`: Certificate slot identifier (0-7) /// * `offset`: Byte offset into the certificate chain /// * `length`: Number of bytes to request +/// * `slot_size_requested`: If true, request the slot size instead of certificate data (SPDM v1.3+) /// /// # Returns /// - () on success /// - [CommandError] on failure /// -/// # TODO -/// Implement GET_CERTIFICATE request generation including: -/// - Create and encode SpdmMsgHdr with ReqRespCode::GetCertificate -/// - Create and encode GetCertificateReq payload -/// - Append to transcript (TranscriptContext::M1) +/// # Connection State Requirements +/// - Connection state must be >= AlgorithmsNegotiated +/// +/// # Transcript +/// - Appends request to the transcript context pub fn generate_get_certificate<'a>( - _ctx: &mut SpdmContext<'a>, - _req_buf: &mut MessageBuf<'a>, - _slot_id: u8, - _offset: u16, - _length: u16, + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + slot_id: u8, + offset: u16, + length: u16, + slot_size_requested: bool, ) -> CommandResult<()> { - todo!("Implement GET_CERTIFICATE request generation") + // Validate connection state - algorithms must be negotiated first + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + return Err((false, CommandError::UnsupportedRequest)); + } + + // Get connection version + let connection_version = ctx.state.connection_info.version_number(); + + // Create and encode SPDM message header + let spdm_hdr = SpdmMsgHdr::new(connection_version, ReqRespCode::GetCertificate); + let mut payload_len = spdm_hdr + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // Create SlotId bitfield + let mut slot_id_field = SlotId(0); + slot_id_field.set_slot_id(slot_id); + + // Create CertificateReqAttributes bitfield + let mut req_attributes = CertificateReqAttributes(0); + if slot_size_requested { + req_attributes.set_slot_size_requested(1); + } + + // Create GET_CERTIFICATE request payload + let get_cert_req = GetCertificateReq { + slot_id: slot_id_field, + param2: req_attributes, + offset, + length, + }; + + // Encode request payload + payload_len += get_cert_req + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // Finalize message by pushing total payload length + req_buf + .push_data(payload_len) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + // Append to transcript + // (TODO: M1 is incorrect here, + // this is a requester functionality so this has to go into M2. + // Change this once the TranscriptManager has been refactored.) + ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Process CERTIFICATE response payload (private helper) @@ -52,7 +103,7 @@ pub fn generate_get_certificate<'a>( /// - Extract and store certificate chain portion /// - Handle certificate chain metadata if offset is 0 /// - Track remainder_length for multi-part transfers -fn process_certificate<'a>( +fn _process_certificate<'a>( _ctx: &mut SpdmContext<'a>, _spdm_hdr: SpdmMsgHdr, _resp_payload: &mut MessageBuf<'a>, @@ -77,7 +128,7 @@ fn process_certificate<'a>( /// - Call process_certificate to parse and validate response /// - Append response to transcript (TranscriptContext::M1) /// - Update connection state to AfterCertificate if needed -pub(crate) fn handle_certificate_response<'a>( +pub(crate) fn _handle_certificate_response<'a>( _ctx: &mut SpdmContext<'a>, _resp_header: SpdmMsgHdr, _resp: &mut MessageBuf<'a>, From a97de157adbe9f97f36d309f0de0c03f8eef53b8 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 11 Feb 2026 13:00:58 +0100 Subject: [PATCH 27/86] Implement minimal CERTIFICATE response handling --- examples/spdm_requester.rs | 12 +++ src/commands/certificate/request.rs | 118 ++++++++++++++++++++++------ src/context.rs | 7 +- src/protocol/certs.rs | 34 ++++++++ 4 files changed, 141 insertions(+), 30 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index e0e167a..879ea16 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -8,6 +8,7 @@ use std::net::{TcpListener, TcpStream}; use std::process; use spdm_lib::codec::MessageBuf; +use spdm_lib::commands::certificate::request::generate_get_certificate; use spdm_lib::context::SpdmContext; use spdm_lib::error::{SpdmError, SpdmResult}; use spdm_lib::platform::transport::SpdmTransport; @@ -327,6 +328,17 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("DIGESTS: {:x?}", &message_buffer.message_data()); } + message_buffer.reset(); + generate_get_certificate(&mut spdm_context, &mut message_buffer, 0, 0, 0x200, false).unwrap(); + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + println!("requested GET_CERTIFICATE (slot 0, offset 0, length 0x200)"); + + if config.verbose { + println!("CERTIFICATE: {:x?}", &message_buffer.message_data()); + } + Ok(()) } diff --git a/src/commands/certificate/request.rs b/src/commands/certificate/request.rs index 2f98f7f..973a4ad 100644 --- a/src/commands/certificate/request.rs +++ b/src/commands/certificate/request.rs @@ -1,10 +1,12 @@ // Licensed under the Apache-2.0 license use crate::codec::{Codec, MessageBuf}; -use crate::commands::certificate::{CertificateReqAttributes, GetCertificateReq, SlotId}; +use crate::commands::certificate::{ + CertificateReqAttributes, CertificateRespCommon, GetCertificateReq, SlotId, +}; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; -use crate::protocol::{ReqRespCode, SpdmMsgHdr}; +use crate::protocol::{CertModel, ReqRespCode, SpdmMsgHdr, SpdmVersion}; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; @@ -95,20 +97,66 @@ pub fn generate_get_certificate<'a>( /// - () on success /// - [CommandError] on failure /// -/// # TODO -/// Implement CERTIFICATE response processing including: -/// - Validate version matches connection version -/// - Decode CertificateRespCommon -/// - Validate slot_id matches request -/// - Extract and store certificate chain portion -/// - Handle certificate chain metadata if offset is 0 -/// - Track remainder_length for multi-part transfers -fn _process_certificate<'a>( - _ctx: &mut SpdmContext<'a>, - _spdm_hdr: SpdmMsgHdr, - _resp_payload: &mut MessageBuf<'a>, +/// # Current Implementation +/// - Validates version matches connection version +/// - Decodes CertificateRespCommon structure +/// - Reads certificate portion data (validates buffer size) +/// +/// # Future Extensions +/// - TODO: Validate slot_id matches request when slot tracking is implemented +/// - TODO: Parse certificate chain metadata when offset is 0 +/// - TODO: Store certificate data in peer cert store +/// - TODO: Implement multi-part transfer support with dedicated reassembly context +/// - TODO: Validate root certificate hash against trust anchor +fn process_certificate<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + resp_payload: &mut MessageBuf<'a>, ) -> CommandResult<()> { - todo!("Implement CERTIFICATE response processing") + // Validate version matches connection version + let connection_version = ctx.state.connection_info.version_number(); + if spdm_hdr.version().ok() != Some(connection_version) { + return Err((false, CommandError::InvalidResponse)); + } + + // Decode CertificateRespCommon structure + let cert_resp = + CertificateRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; + + // Decode CertModel from param2 + // Seems to be available since v1.3 + if connection_version >= SpdmVersion::V13 { + // Fails if a unknown CertModel is send + // (so we consider this to be a bug on the responder side). + let _cert_info: CertModel = cert_resp + .param2 + .certificate_info() + .try_into() + .map_err(|_| (false, CommandError::InvalidResponse))?; + } + + let portion_len = cert_resp.portion_length; + let _remainder_len = cert_resp.remainder_length; // TODO: Track for multi-part transfers + + // Read the certificate portion from the payload (if any) + if portion_len > 0 { + // Validate that the buffer contains the expected certificate data + let _cert_data = resp_payload + .data(portion_len as usize) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // Advance the buffer pointer past the certificate data + resp_payload + .pull_data(portion_len as usize) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // TODO: When certificate storage is implemented: + // - Parse certificate chain metadata if this is the first chunk (offset=0) + // - Store certificate data in peer cert store or reassembly context + // - If remainder_len > 0, coordinate with reassembly context for next chunk + } + + Ok(()) } /// Requester function handling the parsing of the CERTIFICATE response sent by the Responder. @@ -122,16 +170,34 @@ fn _process_certificate<'a>( /// - () on success /// - [CommandError] on failure /// -/// # TODO -/// Implement CERTIFICATE response handler including: -/// - Verify connection state (should be >= AlgorithmsNegotiated) -/// - Call process_certificate to parse and validate response -/// - Append response to transcript (TranscriptContext::M1) -/// - Update connection state to AfterCertificate if needed -pub(crate) fn _handle_certificate_response<'a>( - _ctx: &mut SpdmContext<'a>, - _resp_header: SpdmMsgHdr, - _resp: &mut MessageBuf<'a>, +/// # Connection State +/// - Requires: ConnectionState >= AlgorithmsNegotiated +/// - Sets: ConnectionState::AfterCertificate +/// +/// # Transcript +/// - Appends response to TranscriptContext::M1 +pub(crate) fn handle_certificate_response<'a>( + ctx: &mut SpdmContext<'a>, + resp_header: SpdmMsgHdr, + resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { - todo!("Implement CERTIFICATE response handler") + // Validate connection state - algorithms must be negotiated + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + return Err((false, CommandError::UnsupportedResponse)); + } + + // Process the certificate response payload + process_certificate(ctx, resp_header, resp)?; + + // Append response to transcript (M1 context for certificate exchange) + ctx.append_message_to_transcript(resp, TranscriptContext::M1)?; + + // Update connection state to AfterCertificate if needed + if ctx.state.connection_info.state() < ConnectionState::AfterCertificate { + ctx.state + .connection_info + .set_state(ConnectionState::AfterCertificate); + } + + Ok(()) } diff --git a/src/context.rs b/src/context.rs index f253884..a39f1b2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -217,10 +217,9 @@ impl<'a> SpdmContext<'a> { algorithms::handle_algorithms_response(self, resp_msg_header, resp)? } ReqRespCode::Digests => handle_digests_response(self, resp_msg_header, resp)?, - // TODO: Implement certificate response handler - // ReqRespCode::Certificate => { - // certificate::handle_certificate_response(self, resp_msg_header, resp)? - // } + ReqRespCode::Certificate => { + certificate::request::handle_certificate_response(self, resp_msg_header, resp)? + } _ => Err((false, CommandError::UnsupportedResponse))?, } diff --git a/src/protocol/certs.rs b/src/protocol/certs.rs index d1b920b..ac8614c 100644 --- a/src/protocol/certs.rs +++ b/src/protocol/certs.rs @@ -25,6 +25,40 @@ pub cert_model, set_cert_model: 0,2; reserved, _: 3,7; } +/// CertModel field used in Certificate Info bitfields in DIGESTS and CERTIFICATE responses +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub(crate) enum CertModel { + /// Indicates either that the certificate slot does not contain any certificates + /// or that the corresponding `MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` is false. + None = 0, + /// Certificate slot uses the `DeviceCert` model. + DeviceCert = 1, + /// Certificate slot uses the `AliasCert` model. + AliasCert = 2, + /// Certificate slot uses the `GenericCert` model. + GenericCert = 3, + // TODO: Shoud we include a Reserved(u8) + // to propagate the error handling further up? +} + +#[derive(Debug)] +pub(crate) struct InvalidCertModelError; + +impl TryFrom for CertModel { + type Error = InvalidCertModelError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(CertModel::None), + 1 => Ok(CertModel::DeviceCert), + 2 => Ok(CertModel::AliasCert), + 3 => Ok(CertModel::GenericCert), + _ => Err(InvalidCertModelError), + } + } +} + // SPDM KeyUsageMask fields bitfield! { #[derive(FromBytes, IntoBytes, Immutable, Default)] From 93b87d868eb4c80561cd3ab2c544808f439c7be0 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 12 Feb 2026 11:22:28 +0100 Subject: [PATCH 28/86] Add peer cert. store and cert. retrieval state --- examples/platform/cert_store.rs | 33 +++++++++- examples/spdm_requester.rs | 38 ++++++++--- src/cert_store.rs | 41 ++++++++++++ src/commands/certificate/request.rs | 98 ++++++++++++++++++++--------- src/context.rs | 11 +++- src/error.rs | 2 + src/state.rs | 38 +++++++---- 7 files changed, 207 insertions(+), 54 deletions(-) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index 14d6132..e59c7ec 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -13,7 +13,7 @@ use p384::{ }; use super::certs::{STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; -use spdm_lib::cert_store::{CertStoreError, CertStoreResult, SpdmCertStore}; +use spdm_lib::cert_store::{CertStoreError, CertStoreResult, PeerCertStore, SpdmCertStore}; use spdm_lib::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; use spdm_lib::protocol::certs::{CertificateInfo, KeyUsageMask}; @@ -378,3 +378,34 @@ fn debug_signing_verification() { println!(" print('✓ Sig1 valid with SHA384')"); println!("except: print('✗ Sig1 invalid with SHA384')"); } + +pub struct ExamplePeerCertStrore { + pub chain: Vec, +} + +impl PeerCertStore for ExamplePeerCertStrore { + fn slot_count(&self) -> u8 { + 1 + } + + fn assemble( + &mut self, + _slot_id: u8, + portion: &[u8], + ) -> Result { + self.chain.extend_from_slice(portion); + Ok(spdm_lib::cert_store::ReassemblyStatus::InProgress) + } + + fn reset(&mut self, slot_id: u8) { + todo!() + } + + fn get_root_hash(&self, slot_id: u8) -> Option<&[u8]> { + todo!() + } + + fn get_raw_chain(&self, slot_id: u8) -> Option<&[u8]> { + todo!() + } +} diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 879ea16..8937417 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -32,6 +32,8 @@ use spdm_lib::commands::capabilities::request::generate_capabilities_request_loc use spdm_lib::commands::digests::request::generate_digest_request; use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; +use crate::platform::cert_store::ExamplePeerCertStrore; + /// Responder configuration #[derive(Debug, Clone)] struct RequesterConfig { @@ -144,6 +146,8 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { let capabilities = create_device_capabilities(); let algorithms = create_local_algorithms(); + let mut peer_cert_store = ExamplePeerCertStrore { chain: Vec::new() }; + if config.verbose { println!("Client connected - initializing SPDM context"); } @@ -159,6 +163,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { capabilities, algorithms, &mut cert_store, + Some(&mut peer_cert_store), &mut hash, &mut m1_hash, &mut l1_hash, @@ -328,16 +333,31 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("DIGESTS: {:x?}", &message_buffer.message_data()); } - message_buffer.reset(); - generate_get_certificate(&mut spdm_context, &mut message_buffer, 0, 0, 0x200, false).unwrap(); - spdm_context - .requester_send_request(&mut message_buffer, EID) - .unwrap(); - println!("requested GET_CERTIFICATE (slot 0, offset 0, length 0x200)"); - - if config.verbose { - println!("CERTIFICATE: {:x?}", &message_buffer.message_data()); + // Get peer certificate chain + loop { + message_buffer.reset(); + generate_get_certificate(&mut spdm_context, &mut message_buffer, 0, 0, 0x200, false) + .unwrap(); + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + println!("requested GET_CERTIFICATE"); + println!("state: {:?}", spdm_context.connection_info().state()); + + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + if config.verbose { + println!("CERTIFICATE: {:x?}", &message_buffer.message_data()); + } + if !matches!( + spdm_context.connection_info().state(), + spdm_lib::state::ConnectionState::DuringCertificate(_) + ) { + break; + } } + println!("sucessfully retrieved peer cert chain"); Ok(()) } diff --git a/src/cert_store.rs b/src/cert_store.rs index 1471696..33886ce 100644 --- a/src/cert_store.rs +++ b/src/cert_store.rs @@ -154,3 +154,44 @@ pub(crate) fn cert_slot_mask(cert_store: &dyn SpdmCertStore) -> (u8, u8) { (supported_slot_mask, provisioned_slot_mask) } + +/// Store for managing peer certificates and reassembly of received certificate chain portions +pub trait PeerCertStore { + /// Get supported certificate slot count + /// The supported slots are consecutive from 0 to slot_count - 1. + /// + /// # Returns + /// * `u8` - The number of supported certificate slots. + fn slot_count(&self) -> u8; + + /// Add a portion of a certificate chain to the given slot + /// + /// # Returns + /// - `Ok(ReassemblyStatus)` when the portion was added successfully + /// - `Err(ReassemblyError)` when the portion could not be added + fn assemble(&mut self, slot_id: u8, portion: &[u8]) + -> Result; + + /// Reset a slot + /// + /// Removes all certificate data from the given slot. + fn reset(&mut self, slot_id: u8); + + /// Get the root hash of a peer certificate + /// + /// # Returns + /// - The digest of the Root Certificate if available + fn get_root_hash(&self, slot_id: u8) -> Option<&[u8]>; + + /// Get a complete certificate chain consisting of one or more ASN.1 DER-encoded X.509 v3 certificates + fn get_raw_chain(&self, slot_id: u8) -> Option<&[u8]>; +} + +pub enum ReassemblyStatus { + /// Slot is empty + NotStarted, + /// Reassembly still in progress + InProgress, + /// Reassembly finished + Done, +} diff --git a/src/commands/certificate/request.rs b/src/commands/certificate/request.rs index 973a4ad..25e08d7 100644 --- a/src/commands/certificate/request.rs +++ b/src/commands/certificate/request.rs @@ -7,10 +7,14 @@ use crate::commands::certificate::{ use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; use crate::protocol::{CertModel, ReqRespCode, SpdmMsgHdr, SpdmVersion}; -use crate::state::ConnectionState; +use crate::state::{ConnectionState, GetCertificateState}; use crate::transcript::TranscriptContext; -/// Generate the GET_CERTIFICATE request +/// Generate a GET_CERTIFICATE request +/// +/// If the state is `DuringCertificate` the following parameters will be ignored +/// and instead be calculated from the state: +/// `slot_id`, `offset`. /// /// # Arguments /// * `ctx`: The SPDM context @@ -26,6 +30,7 @@ use crate::transcript::TranscriptContext; /// /// # Connection State Requirements /// - Connection state must be >= AlgorithmsNegotiated +/// - Updates the state to `DuringCertificate` (only if `slot_size_requested` == `false`) /// /// # Transcript /// - Appends request to the transcript context @@ -42,6 +47,20 @@ pub fn generate_get_certificate<'a>( return Err((false, CommandError::UnsupportedRequest)); } + let state = match ctx.state.connection_info.state() { + ConnectionState::DuringCertificate(s) => s, + _ => GetCertificateState { + current_slot_id: slot_id, + offset, + ..Default::default() + }, + }; + if !slot_size_requested { + ctx.state + .connection_info + .set_state(ConnectionState::DuringCertificate(state)); + } + // Get connection version let connection_version = ctx.state.connection_info.version_number(); @@ -53,7 +72,7 @@ pub fn generate_get_certificate<'a>( // Create SlotId bitfield let mut slot_id_field = SlotId(0); - slot_id_field.set_slot_id(slot_id); + slot_id_field.set_slot_id(state.current_slot_id); // Create CertificateReqAttributes bitfield let mut req_attributes = CertificateReqAttributes(0); @@ -65,7 +84,7 @@ pub fn generate_get_certificate<'a>( let get_cert_req = GetCertificateReq { slot_id: slot_id_field, param2: req_attributes, - offset, + offset: state.offset, length, }; @@ -100,14 +119,13 @@ pub fn generate_get_certificate<'a>( /// # Current Implementation /// - Validates version matches connection version /// - Decodes CertificateRespCommon structure +/// - Validates returned slot_id against expected slot_id /// - Reads certificate portion data (validates buffer size) +/// - Stores certificate portion in peer cert. store +/// - Updates state /// /// # Future Extensions -/// - TODO: Validate slot_id matches request when slot tracking is implemented -/// - TODO: Parse certificate chain metadata when offset is 0 -/// - TODO: Store certificate data in peer cert store -/// - TODO: Implement multi-part transfer support with dedicated reassembly context -/// - TODO: Validate root certificate hash against trust anchor +/// - TODO: Add support for SlotSizeRequested responses fn process_certificate<'a>( ctx: &mut SpdmContext<'a>, spdm_hdr: SpdmMsgHdr, @@ -116,12 +134,23 @@ fn process_certificate<'a>( // Validate version matches connection version let connection_version = ctx.state.connection_info.version_number(); if spdm_hdr.version().ok() != Some(connection_version) { - return Err((false, CommandError::InvalidResponse)); + return Err((true, CommandError::InvalidResponse)); } + let ConnectionState::DuringCertificate(mut state) = ctx.state.connection_info.state() else { + // TODO: Add support for SlotSizeRequested + return Err((false, CommandError::InvalidState)); + }; + // Decode CertificateRespCommon structure let cert_resp = - CertificateRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; + CertificateRespCommon::decode(resp_payload).map_err(|e| (true, CommandError::Codec(e)))?; + + // Check slot_id + let slot_id = cert_resp.slot_id.slot_id(); + if slot_id != state.current_slot_id { + return Err((true, CommandError::InvalidResponse)); + } // Decode CertModel from param2 // Seems to be available since v1.3 @@ -132,28 +161,46 @@ fn process_certificate<'a>( .param2 .certificate_info() .try_into() - .map_err(|_| (false, CommandError::InvalidResponse))?; + .map_err(|_| (true, CommandError::InvalidResponse))?; } let portion_len = cert_resp.portion_length; - let _remainder_len = cert_resp.remainder_length; // TODO: Track for multi-part transfers // Read the certificate portion from the payload (if any) if portion_len > 0 { // Validate that the buffer contains the expected certificate data - let _cert_data = resp_payload + let cert_data = resp_payload .data(portion_len as usize) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; + + let Some(cert_store) = ctx.state.peer_cert_store.as_deref_mut() else { + return Err((true, CommandError::InvalidState)); + }; + + match cert_store.assemble(state.current_slot_id, cert_data) { + Ok(_s) => { + // TODO: match s against remainder length + } + Err(e) => return Err((true, CommandError::CertStore(e))), + } // Advance the buffer pointer past the certificate data resp_payload .pull_data(portion_len as usize) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; + } + + state.offset += portion_len; + state.remainder_length = Some(cert_resp.remainder_length); - // TODO: When certificate storage is implemented: - // - Parse certificate chain metadata if this is the first chunk (offset=0) - // - Store certificate data in peer cert store or reassembly context - // - If remainder_len > 0, coordinate with reassembly context for next chunk + if cert_resp.remainder_length > 0 { + ctx.state + .connection_info + .set_state(ConnectionState::DuringCertificate(state)); + } else { + ctx.state + .connection_info + .set_state(ConnectionState::AfterCertificate); } Ok(()) @@ -172,7 +219,7 @@ fn process_certificate<'a>( /// /// # Connection State /// - Requires: ConnectionState >= AlgorithmsNegotiated -/// - Sets: ConnectionState::AfterCertificate +/// - Updates: ConnectionState to DuringCertificate or AfterCertificate /// /// # Transcript /// - Appends response to TranscriptContext::M1 @@ -183,7 +230,7 @@ pub(crate) fn handle_certificate_response<'a>( ) -> CommandResult<()> { // Validate connection state - algorithms must be negotiated if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { - return Err((false, CommandError::UnsupportedResponse)); + return Err((true, CommandError::UnsupportedResponse)); } // Process the certificate response payload @@ -192,12 +239,5 @@ pub(crate) fn handle_certificate_response<'a>( // Append response to transcript (M1 context for certificate exchange) ctx.append_message_to_transcript(resp, TranscriptContext::M1)?; - // Update connection state to AfterCertificate if needed - if ctx.state.connection_info.state() < ConnectionState::AfterCertificate { - ctx.state - .connection_info - .set_state(ConnectionState::AfterCertificate); - } - Ok(()) } diff --git a/src/context.rs b/src/context.rs index a39f1b2..75796dc 100644 --- a/src/context.rs +++ b/src/context.rs @@ -23,14 +23,14 @@ use crate::protocol::algorithms::*; use crate::protocol::common::{ReqRespCode, SpdmMsgHdr}; use crate::protocol::version::*; use crate::protocol::DeviceCapabilities; -use crate::state::{ConnectionState, State}; +use crate::state::{ConnectionInfo, ConnectionState, State}; use crate::transcript::{TranscriptContext, TranscriptManager}; pub struct SpdmContext<'a> { transport: &'a mut dyn SpdmTransport, pub(crate) hash: &'a mut dyn SpdmHash, pub(crate) supported_versions: &'a [SpdmVersion], - pub(crate) state: State, + pub(crate) state: State<'a>, pub(crate) transcript_mgr: TranscriptManager<'a>, pub(crate) rng: &'a mut dyn SpdmRng, pub(crate) local_capabilities: DeviceCapabilities, @@ -48,6 +48,7 @@ impl<'a> SpdmContext<'a> { local_capabilities: DeviceCapabilities, local_algorithms: LocalDeviceAlgorithms<'a>, device_certs_store: &'a mut dyn SpdmCertStore, + peer_cert_store: Option<&'a mut dyn PeerCertStore>, hash: &'a mut dyn SpdmHash, m1: &'a mut dyn SpdmHash, l1: &'a mut dyn SpdmHash, @@ -61,7 +62,7 @@ impl<'a> SpdmContext<'a> { Ok(Self { supported_versions, transport: spdm_transport, - state: State::new(), + state: State::new(peer_cert_store), transcript_mgr: TranscriptManager::new(m1, l1), local_capabilities, local_algorithms, @@ -74,6 +75,10 @@ impl<'a> SpdmContext<'a> { }) } + pub fn connection_info(&self) -> &ConnectionInfo { + &self.state.connection_info + } + pub fn transport_init_sequence(&mut self) -> SpdmResult<()> { self.transport .init_sequence() diff --git a/src/error.rs b/src/error.rs index be8c42e..680b0f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -53,4 +53,6 @@ pub enum CommandError { Transcript(TranscriptError), Measurement(MeasurementsError), InvalidResponse, + /// An invalid state was encountered (this is likely a bug) + InvalidState, } diff --git a/src/state.rs b/src/state.rs index 7eeef12..479b9b1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,21 +1,26 @@ // Licensed under the Apache-2.0 license -use crate::protocol::{DeviceAlgorithms, DeviceCapabilities, SpdmVersion}; +use crate::{ + cert_store::PeerCertStore, + protocol::{DeviceAlgorithms, DeviceCapabilities, SpdmVersion}, +}; -pub(crate) struct State { +pub(crate) struct State<'a> { pub(crate) connection_info: ConnectionInfo, + pub(crate) peer_cert_store: Option<&'a mut dyn PeerCertStore>, } -impl Default for State { +impl<'a> Default for State<'a> { fn default() -> Self { - Self::new() + Self::new(None) } } -impl State { - pub fn new() -> Self { +impl<'a> State<'a> { + pub fn new(peer_cert_store: Option<&'a mut dyn PeerCertStore>) -> Self { Self { connection_info: ConnectionInfo::default(), + peer_cert_store, } } @@ -24,7 +29,7 @@ impl State { } } -pub(crate) struct ConnectionInfo { +pub struct ConnectionInfo { version_number: SpdmVersion, state: ConnectionState, peer_algorithms: DeviceAlgorithms, @@ -49,7 +54,7 @@ impl ConnectionInfo { self.version_number } - pub fn set_version_number(&mut self, version_number: SpdmVersion) { + pub(crate) fn set_version_number(&mut self, version_number: SpdmVersion) { self.version_number = version_number; } @@ -57,11 +62,11 @@ impl ConnectionInfo { self.state } - pub fn set_state(&mut self, state: ConnectionState) { + pub(crate) fn set_state(&mut self, state: ConnectionState) { self.state = state; } - pub fn set_peer_capabilities(&mut self, peer_capabilities: DeviceCapabilities) { + pub(crate) fn set_peer_capabilities(&mut self, peer_capabilities: DeviceCapabilities) { self.peer_capabilities = peer_capabilities; } @@ -70,7 +75,7 @@ impl ConnectionInfo { self.peer_capabilities } - pub fn set_peer_algorithms(&mut self, peer_algorithms: DeviceAlgorithms) { + pub(crate) fn set_peer_algorithms(&mut self, peer_algorithms: DeviceAlgorithms) { self.peer_algorithms = peer_algorithms; } @@ -79,7 +84,7 @@ impl ConnectionInfo { } #[allow(dead_code)] - pub fn set_multi_key_conn_rsp(&mut self, multi_key_conn_rsp: bool) { + pub(crate) fn set_multi_key_conn_rsp(&mut self, multi_key_conn_rsp: bool) { self.multi_key_conn_rsp = multi_key_conn_rsp; } @@ -102,6 +107,15 @@ pub enum ConnectionState { AfterCapabilities, AlgorithmsNegotiated, AfterDigest, + /// Cert chain retrieval in process + DuringCertificate(GetCertificateState), AfterCertificate, Authenticated, } + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct GetCertificateState { + pub current_slot_id: u8, + pub offset: u16, + pub remainder_length: Option, +} From 5d2fac172d62b3a945f2fab7015395ae94068053 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 12 Feb 2026 14:10:58 +0100 Subject: [PATCH 29/86] v1.3 & v1.4 compatible cert. chain header --- src/commands/certificate/mod.rs | 9 ++--- src/commands/digests/mod.rs | 6 ++-- src/error.rs | 6 ++++ src/protocol/certs.rs | 58 +++++++++++++++++++++++++++++---- 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/commands/certificate/mod.rs b/src/commands/certificate/mod.rs index b1476cf..57d1af4 100644 --- a/src/commands/certificate/mod.rs +++ b/src/commands/certificate/mod.rs @@ -94,10 +94,11 @@ pub(crate) fn encode_certchain_metadata( let mut certchain_metadata = [0u8; SPDM_CERT_CHAIN_METADATA_LEN as usize]; // Read the cert chain header first - let cert_chain_hdr = SpdmCertChainHeader { - length: total_certchain_len, - reserved: 0, - }; + // Currently only cert chains with length <= `u16::MAX` are supported. + // (So this should never fail.) + let cert_chain_hdr = SpdmCertChainHeader::new(total_certchain_len as u32, SpdmVersion::V12) + .map_err(|_| (false, CommandError::InternalError))?; + let cert_chain_hdr_bytes = cert_chain_hdr.as_bytes(); certchain_metadata[..cert_chain_hdr_bytes.len()].copy_from_slice(cert_chain_hdr_bytes); diff --git a/src/commands/digests/mod.rs b/src/commands/digests/mod.rs index d6ced3f..9eef565 100644 --- a/src/commands/digests/mod.rs +++ b/src/commands/digests/mod.rs @@ -50,10 +50,8 @@ pub(crate) fn compute_cert_chain_hash( .map_err(|e| (false, CommandError::CertStore(e)))?; let cert_chain_format_len = crt_chain_len + SPDM_CERT_CHAIN_METADATA_LEN as usize; - let header = SpdmCertChainHeader { - length: cert_chain_format_len as u16, - reserved: 0, - }; + let header = SpdmCertChainHeader::new(cert_chain_format_len as u32, SpdmVersion::V13) + .map_err(|_| (false, CommandError::InternalError))?; // Length and reserved fields let header_bytes = header.as_bytes(); diff --git a/src/error.rs b/src/error.rs index 680b0f8..83c6b59 100644 --- a/src/error.rs +++ b/src/error.rs @@ -55,4 +55,10 @@ pub enum CommandError { InvalidResponse, /// An invalid state was encountered (this is likely a bug) InvalidState, + /// This is a Bug + /// + /// Used in spots which should be infailable. + /// This can either be a bug in this crate, + /// in a dependency, or a uncaught platform misbehaviour. + InternalError, } diff --git a/src/protocol/certs.rs b/src/protocol/certs.rs index ac8614c..27a3762 100644 --- a/src/protocol/certs.rs +++ b/src/protocol/certs.rs @@ -1,17 +1,63 @@ // Licensed under the Apache-2.0 license -use crate::protocol::SHA384_HASH_SIZE; +use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{little_endian, FromBytes, Immutable, IntoBytes, KnownLayout}; pub(crate) const SPDM_MAX_CERT_CHAIN_PORTION_LEN: u16 = 512; pub(crate) const SPDM_CERT_CHAIN_METADATA_LEN: u16 = size_of::() as u16 + SHA384_HASH_SIZE as u16; -#[derive(IntoBytes, FromBytes, Immutable, Debug, Default)] +#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Debug, Default)] #[repr(C, packed)] -pub(crate) struct SpdmCertChainHeader { - pub length: u16, - pub reserved: u16, +pub struct SpdmCertChainHeader { + /// Length of the CertChain, including this header and the following root hash + /// + /// # Versions + /// ## <= v1.2 + /// Little endian u16 followed by reserved u16. + /// (this is compatible witht the current >= v1.3 layout) + /// ## v1.3 and later + /// Little endian u32. + length: little_endian::U32, +} +impl SpdmCertChainHeader { + /// Get the length of the certificate chain + /// + /// This includes the length header and root hash. + pub fn get_length(&self) -> u32 { + self.length.get() + } + /// Checks the version for compatibility and assigns the provided length + /// + /// # Versions + /// ## <= v1.2 + /// Little endian u16 followed by reserved u16. + /// (this is compatible witht the current >= v1.3 layout) + /// ## v1.3 and later + /// Little endian u32. + pub fn set_length(&mut self, length: u32, version: SpdmVersion) -> Result<(), ()> { + if length > u16::MAX as u32 && version < SpdmVersion::V13 { + return Err(()); + } + self.length.set(length); + Ok(()) + } + /// Creates a new certificate chain header with checked version compatibility + /// + /// # Versions + /// ## <= v1.2 + /// Little endian u16 followed by reserved u16. + /// (this is compatible witht the current >= v1.3 layout) + /// ## v1.3 and later + /// Little endian u32. + pub fn new(length: u32, version: SpdmVersion) -> Result { + if length > u16::MAX as u32 && version < SpdmVersion::V13 { + return Err(()); + } + Ok(Self { + length: little_endian::U32::new(length), + }) + } } // SPDM CertificateInfo fields From a895f1df44725f935e0769a3ae130598b6db7447 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 12 Feb 2026 11:22:28 +0100 Subject: [PATCH 30/86] Fix spelling mistakes --- src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 83c6b59..8830595 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,8 +57,8 @@ pub enum CommandError { InvalidState, /// This is a Bug /// - /// Used in spots which should be infailable. + /// Used in spots which should be infallible. /// This can either be a bug in this crate, - /// in a dependency, or a uncaught platform misbehaviour. + /// in a dependency, or a uncaught platform misbehavior. InternalError, } From 86cd3a68edd941de23b3794cbac1437076c5b43a Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 12 Feb 2026 17:45:30 +0100 Subject: [PATCH 31/86] digests: add context peer slot tracking Signed-off-by: leongross --- examples/platform/cert_store.rs | 231 ++++++++++++++++++++++++++++++-- examples/spdm_requester.rs | 4 +- examples/spdm_responder.rs | 5 + src/cert_store.rs | 193 +++++++++++++++++++++++++- src/commands/digests/request.rs | 97 +++++++++----- src/protocol/certs.rs | 4 +- 6 files changed, 484 insertions(+), 50 deletions(-) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index e59c7ec..d9bfebf 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -145,7 +145,7 @@ impl SpdmCertStore for DemoCertStore { if slot_id == 0 { Ok(self.cert_chain.len()) } else { - Err(CertStoreError::InvalidSlotId) + Err(CertStoreError::InvalidSlotId(slot_id)) } } @@ -157,7 +157,7 @@ impl SpdmCertStore for DemoCertStore { cert_portion: &'a mut [u8], ) -> CertStoreResult { if slot_id != 0 { - return Err(CertStoreError::InvalidSlotId); + return Err(CertStoreError::InvalidSlotId(slot_id)); } if offset >= self.cert_chain.len() { @@ -179,7 +179,7 @@ impl SpdmCertStore for DemoCertStore { cert_hash: &'a mut [u8; SHA384_HASH_SIZE], ) -> CertStoreResult<()> { if slot_id != 0 { - return Err(CertStoreError::InvalidSlotId); + return Err(CertStoreError::InvalidSlotId(slot_id)); } #[cfg(feature = "crypto")] @@ -214,7 +214,7 @@ impl SpdmCertStore for DemoCertStore { signature: &'a mut [u8; ECC_P384_SIGNATURE_SIZE], ) -> CertStoreResult<()> { if slot_id != 0 { - return Err(CertStoreError::InvalidSlotId); + return Err(CertStoreError::InvalidSlotId(slot_id)); } #[cfg(feature = "crypto")] @@ -383,17 +383,82 @@ pub struct ExamplePeerCertStrore { pub chain: Vec, } -impl PeerCertStore for ExamplePeerCertStrore { +#[derive(Debug)] +pub struct PeerSlot { + /// CertChain[K], retrieved in `CERTIFICATE` response. + pub cert_chain: Vec, + + /// Digest[K], retrieved in `DIGESTS` response. + pub digest: Vec, + + /// `KeyPairID[K]`, retrieved in `DIGESTS` response if the corresponding `MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` is true. + pub keypair_id: Option, + + /// `CertificateInfo[K]`, retrieved in `DIGESTS` response if the corresponding `MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` is true. pub cert_info: Option + pub certificate_info: Option, + + /// KeyUsageMask[K], retrieved in `DIGESTS` response if the corresponding `MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` is true. + pub key_usage_mask: Option, +} + +impl Default for PeerSlot { + fn default() -> Self { + PeerSlot { + cert_chain: Vec::new(), + digest: Vec::new(), + keypair_id: None, + certificate_info: None, + key_usage_mask: None, + } + } +} + +/// Concrete implementation of `PeerCertStore` for demonstration purposes. +/// This example store manages a single certificate slot (slot 0) and allows +/// setting and retrieving the certificate chain, digest, key pair ID, certificate info, +/// and key usage mask for that slot. In a real implementation, you would likely +/// want to support multiple slots and have more robust error handling and storage mechanisms. +#[derive(Debug)] +pub struct ExamplePeerCertStore { + /// Retrieved from `DIGESTS` response, indicates which certificate slots are supported by the peer. + supported_slots_mask: u8, + + /// Retrieved from `DIGESTS` response, indicates which certificate slots are provisioned with valid certificate chains. + provisioned_slots_mask: u8, + + // Since not all existing slots may hold eligible certificate chains, keep the PeerSlot values optional. + pub peer_slots: Vec>, +} + +impl Default for ExamplePeerCertStore { + fn default() -> Self { + ExamplePeerCertStore { + supported_slots_mask: 0, + provisioned_slots_mask: 0, + peer_slots: vec![None], + } + } +} + +impl PeerCertStore for ExamplePeerCertStore { fn slot_count(&self) -> u8 { - 1 + self.peer_slots.len() as u8 } fn assemble( &mut self, - _slot_id: u8, + slot_id: u8, portion: &[u8], ) -> Result { - self.chain.extend_from_slice(portion); + let slot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + + slot.cert_chain.extend_from_slice(portion); + Ok(spdm_lib::cert_store::ReassemblyStatus::InProgress) } @@ -408,4 +473,154 @@ impl PeerCertStore for ExamplePeerCertStrore { fn get_raw_chain(&self, slot_id: u8) -> Option<&[u8]> { todo!() } + + fn get_cert_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]> { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + Ok(&slot.cert_chain) + } + + /// Set the supported slots bit mask and initialize PeerSlot entries for any newly supported slots. + fn set_supported_slots(&mut self, slot_mask: u8) -> CertStoreResult<()> { + for b in 0..8 { + if slot_mask & (1 << b) == 1 { + if let Some(slot) = self.peer_slots.get_mut(b as usize) { + if slot.is_none() { + *slot = Some(PeerSlot::default()); + } + } + } + } + + Ok(()) + } + + fn get_supported_slots(&self) -> CertStoreResult { + Ok(self.supported_slots_mask) + } + + fn set_provisioned_slots(&mut self, provisioned_slot_mask: u8) -> CertStoreResult<()> { + self.provisioned_slots_mask = provisioned_slot_mask; + Ok(()) + } + + fn get_provisioned_slots(&self) -> CertStoreResult { + Ok(self.provisioned_slots_mask) + } + + /// Set the certificate chain for a given slot. This would typically be called + /// after successfully reassembling the certificate chain from received portions. + /// + /// # Returns + /// - `Ok(())` if the certificate chain was set successfully + /// - `Err(CertStoreError)` if there was an error (e.g., invalid slot ID) + fn set_cert_chain(&mut self, slot_id: u8, cert_chain: &[u8]) -> CertStoreResult<()> { + let slot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + + slot.cert_chain = cert_chain.to_vec(); + Ok(()) + } + + fn get_digest(&self, slot_id: u8) -> CertStoreResult<&[u8]> { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + Ok(&slot.digest) + } + + /// Set the digest for a given slot, provided by the `DIGESTS` response. + /// + /// # Parameters + /// - `slot_id`: The slot ID to set the digest for + /// - `digest`: The digest value to set + fn set_digest(&mut self, slot_id: u8, digest: &[u8]) -> CertStoreResult<()> { + let slot: &mut PeerSlot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + slot.digest = digest.to_vec(); + Ok(()) + } + + fn get_cert_info(&self, slot_id: u8) -> CertStoreResult { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + slot.certificate_info + .ok_or(CertStoreError::InvalidSlotId(slot_id)) + } + fn set_cert_info(&mut self, slot_id: u8, cert_info: CertificateInfo) -> CertStoreResult<()> { + let slot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + slot.certificate_info = Some(cert_info); + Ok(()) + } + fn get_key_usage_mask(&self, slot_id: u8) -> CertStoreResult { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + slot.key_usage_mask + .ok_or(CertStoreError::InvalidSlotId(slot_id)) + } + + fn set_key_usage_mask( + &mut self, + slot_id: u8, + key_usage_mask: KeyUsageMask, + ) -> CertStoreResult<()> { + let slot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + slot.key_usage_mask = Some(key_usage_mask); + Ok(()) + } + + fn get_keypair(&self, slot_id: u8) -> CertStoreResult { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + slot.keypair_id + .ok_or(CertStoreError::InvalidSlotId(slot_id)) + } + + fn set_keypair(&mut self, slot_id: u8, keypair: u8) -> CertStoreResult<()> { + let slot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + slot.keypair_id = Some(keypair); + Ok(()) + } } diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 8937417..db8559e 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -32,7 +32,7 @@ use spdm_lib::commands::capabilities::request::generate_capabilities_request_loc use spdm_lib::commands::digests::request::generate_digest_request; use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; -use crate::platform::cert_store::ExamplePeerCertStrore; +use crate::platform::cert_store::ExamplePeerCertStore; /// Responder configuration #[derive(Debug, Clone)] @@ -146,7 +146,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { let capabilities = create_device_capabilities(); let algorithms = create_local_algorithms(); - let mut peer_cert_store = ExamplePeerCertStrore { chain: Vec::new() }; + let mut peer_cert_store = ExamplePeerCertStore::default(); if config.verbose { println!("Client connected - initializing SPDM context"); diff --git a/examples/spdm_responder.rs b/examples/spdm_responder.rs index 8ab4f96..02f674b 100644 --- a/examples/spdm_responder.rs +++ b/examples/spdm_responder.rs @@ -12,6 +12,7 @@ use std::io::{Error, ErrorKind, Result as IoResult}; use std::net::{TcpListener, TcpStream}; use std::process; +use spdm_lib::cert_store::PeerCertStore; use spdm_lib::codec::MessageBuf; use spdm_lib::context::SpdmContext; use spdm_lib::protocol::algorithms::{ @@ -26,6 +27,8 @@ use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; mod platform; use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; +use crate::platform::cert_store::ExamplePeerCertStore; + /// Responder configuration #[derive(Debug, Clone)] struct ResponderConfig { @@ -66,6 +69,7 @@ fn create_device_capabilities() -> DeviceCapabilities { flags, data_transfer_size: 1024, max_spdm_msg_size: 4096, + include_supported_algorithms: true, } } @@ -142,6 +146,7 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( capabilities, algorithms, &mut cert_store, + ExamplePeerCertStore::default(), &mut hash, &mut m1_hash, &mut l1_hash, diff --git a/src/cert_store.rs b/src/cert_store.rs index 33886ce..5b0bc2b 100644 --- a/src/cert_store.rs +++ b/src/cert_store.rs @@ -12,13 +12,14 @@ pub const SPDM_CERT_CHAIN_METADATA_LEN: u16 = #[derive(Debug, PartialEq)] pub enum CertStoreError { InitFailed, - InvalidSlotId, + InvalidSlotId(u8), UnsupportedHashAlgo, BufferTooSmall, InvalidOffset, CertReadError, PlatformError, } + pub type CertStoreResult = Result; pub trait SpdmCertStore { @@ -155,15 +156,201 @@ pub(crate) fn cert_slot_mask(cert_store: &dyn SpdmCertStore) -> (u8, u8) { (supported_slot_mask, provisioned_slot_mask) } -/// Store for managing peer certificates and reassembly of received certificate chain portions pub trait PeerCertStore { - /// Get supported certificate slot count + /// Get supported certificate slot count. /// The supported slots are consecutive from 0 to slot_count - 1. + /// If certificate slot X exists in the responding SPDM endpoint, then all + /// slots with ID < X must also exist. + /// + /// For example, if slot 2 is supported, then slots 0 and 1 must also be supported. /// /// # Returns /// * `u8` - The number of supported certificate slots. fn slot_count(&self) -> u8; + /// Set the number of supported certificate slots. + /// This function is typically called during SPDM connection setup. + /// + /// # Arguments + /// * `slot_count` - The number of supported certificate slots. + /// + /// # Returns + /// * `CertStoreResult<()>` - Ok if the operation was successful, Err otherwise. + fn set_supported_slots(&mut self, supported_slot_mask: u8) -> CertStoreResult<()>; + + /// Get the bitmask of supported certificate slots. + /// + /// # Returns + /// * `Ok(u8)` - Bitmask where each set bit indicates a supported slot. + /// + /// # Errors + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_supported_slots(&self) -> CertStoreResult; + + /// Set the bitmask of provisioned certificate slots. + /// + /// # Arguments + /// * `provisioned_slot_mask` - Bitmask where each set bit indicates a provisioned slot. + /// + /// # Returns + /// * `Ok(())` - If the provisioned slot mask was stored successfully. + /// + /// # Errors + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn set_provisioned_slots(&mut self, provisioned_slot_mask: u8) -> CertStoreResult<()>; + + /// Get the bitmask of provisioned certificate slots. + /// + /// # Returns + /// * `Ok(u8)` - Bitmask where each set bit indicates a provisioned slot. + /// + /// # Errors + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_provisioned_slots(&self) -> CertStoreResult; + + /// Get the stored certificate chain for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// + /// # Returns + /// * `Ok(&[u8])` - The certificate chain bytes. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_cert_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]>; + + /// Store a certificate chain in the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID to store the certificate chain in. + /// * `cert_chain` - The certificate chain bytes to store. + /// + /// # Returns + /// * `Ok(())` - If the certificate chain was stored successfully. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::BufferTooSmall` - If the internal buffer is too small for the certificate chain. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn set_cert_chain(&mut self, slot_id: u8, cert_chain: &[u8]) -> CertStoreResult<()>; + + /// Get the digest of the certificate chain for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// + /// # Returns + /// * `Ok(&[u8])` - The digest bytes. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_digest(&self, slot_id: u8) -> CertStoreResult<&[u8]>; + + /// Store the digest of the certificate chain for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// * `digest` - The digest bytes to store. + /// + /// # Returns + /// * `Ok(())` - If the digest was stored successfully. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::BufferTooSmall` - If the internal buffer is too small for the digest. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn set_digest(&mut self, slot_id: u8, digest: &[u8]) -> CertStoreResult<()>; + + /// Get the KeyPairID associated with the certificate chain for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// + /// # Returns + /// * `Ok(u8)` - The KeyPairID. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_keypair(&self, slot_id: u8) -> CertStoreResult; + + /// Set the KeyPairID associated with the certificate chain for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// * `keypair` - The KeyPairID to associate with the slot. + /// + /// # Returns + /// * `Ok(())` - If the KeyPairID was stored successfully. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn set_keypair(&mut self, slot_id: u8, keypair: u8) -> CertStoreResult<()>; + + /// Get the `CertificateInfo` for the given slot. + /// The `CertificateInfo` specifies the certificate model (such as DeviceID, Alias, or General). + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// + /// # Returns + /// * `Ok(CertificateInfo)` - The certificate info for the slot. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_cert_info(&self, slot_id: u8) -> CertStoreResult; + + /// Set the `CertificateInfo` for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// * `cert_info` - The `CertificateInfo` to store. + /// + /// # Returns + /// * `Ok(())` - If the `CertificateInfo` was stored successfully. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn set_cert_info(&mut self, slot_id: u8, cert_info: CertificateInfo) -> CertStoreResult<()>; + + /// Get the `KeyUsageMask` associated with the certificate chain for the given slot. + /// The `KeyUsageMask` indicates the permitted key usages for the certificate's public key. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// + /// # Returns + /// * `Ok(KeyUsageMask)` - The key usage mask for the slot. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn get_key_usage_mask(&self, slot_id: u8) -> CertStoreResult; + + /// Set the `KeyUsageMask` associated with the certificate chain for the given slot. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// * `key_usage_mask` - The `KeyUsageMask` to store. + /// + /// # Returns + /// * `Ok(())` - If the `KeyUsageMask` was stored successfully. + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + fn set_key_usage_mask( + &mut self, + slot_id: u8, + key_usage_mask: KeyUsageMask, + ) -> CertStoreResult<()>; + /// Add a portion of a certificate chain to the given slot /// /// # Returns diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs index 2afa97a..b52948c 100644 --- a/src/commands/digests/request.rs +++ b/src/commands/digests/request.rs @@ -1,5 +1,6 @@ // Licensed under the Apache-2.0 license +use crate::cert_store::PeerCertStore; use crate::codec::Codec; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; @@ -36,7 +37,7 @@ pub(crate) fn handle_digests_response<'a>( resp_payload: &mut MessageBuf<'a>, ) -> CommandResult<()> { if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { - return Err((false, CommandError::UnsupportedRequest)); + return Err((true, CommandError::UnsupportedRequest)); } let con_version = ctx.state.connection_info.version_number(); @@ -46,83 +47,109 @@ pub(crate) fn handle_digests_response<'a>( }; let digests_resp_common = - GetDigestsRespCommon::decode(resp_payload).map_err(|e| (false, CommandError::Codec(e)))?; + GetDigestsRespCommon::decode(resp_payload).map_err(|e| (true, CommandError::Codec(e)))?; - // ATM we do not care, since we only support slot 0. - // Also this is a hacky way of representing the the remote slots - let _supported_slot_mask = digests_resp_common.supported_slot_mask; - let provisioned_slot_mask = digests_resp_common.provisioned_slot_mask; + let peer_cert_store = ctx + .state + .peer_cert_store + .as_mut() + .ok_or((true, CommandError::InvalidResponse))?; - let slot_n = provisioned_slot_mask.count_ones() as usize; + peer_cert_store + .set_supported_slots(digests_resp_common.supported_slot_mask) + .map_err(|e| (true, CommandError::CertStore(e)))?; + + for b in 0..digests_resp_common.supported_slot_mask.count_ones() { + if (digests_resp_common.supported_slot_mask & (1 << b)) == 1 {} + } + + peer_cert_store + .set_provisioned_slots(digests_resp_common.provisioned_slot_mask) + .map_err(|e| (true, CommandError::CertStore(e)))?; + + let slot_n = digests_resp_common.provisioned_slot_mask.count_ones() as usize; if slot_n == 0 { - return Err((false, CommandError::InvalidResponse)); + return Err((true, CommandError::InvalidResponse)); } - for _slot_id in 0..slot_n { + // For now that should only be '0'. + for slot_id in 0..slot_n { if resp_payload.data_len() < SHA384_HASH_SIZE { - return Err((false, CommandError::InvalidResponse)); + return Err((true, CommandError::BufferTooSmall)); } - let _digest = resp_payload + let digest = resp_payload .data(SHA384_HASH_SIZE) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; - // TODO: Store the digest in context for later verification + peer_cert_store + .set_digest(slot_id as u8, digest) + .map_err(|e| (true, CommandError::CertStore(e)))?; resp_payload .pull_data(SHA384_HASH_SIZE) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; } if version >= SpdmVersion::V13 && ctx.state.connection_info.multi_key_conn_rsp() { - for _slot_id in 0..slot_n { + for slot_id in 0..slot_n { if resp_payload.data_len() < size_of::() { - return Err((false, CommandError::InvalidResponse)); + return Err((true, CommandError::InvalidResponse)); } - let _key_pair_id = resp_payload + + let key_pair_id = resp_payload .data(size_of::()) - .map_err(|e| (false, CommandError::Codec(e)))?[0]; + .map_err(|e| (true, CommandError::Codec(e)))?[0]; - // TODO: Store key_pair_id in context + peer_cert_store + .set_keypair(slot_id as u8, key_pair_id) + .map_err(|e| (true, CommandError::CertStore(e)))?; resp_payload .pull_data(size_of::()) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; } - for _slot_id in 0..slot_n { + for slot_id in 0..slot_n { if resp_payload.data_len() < size_of::() { - return Err((false, CommandError::InvalidResponse)); + return Err((true, CommandError::InvalidResponse)); } + let data = resp_payload .data(size_of::()) - .map_err(|e| (false, CommandError::Codec(e)))?; - let _cert_info = CertificateInfo::read_from_bytes(data) - .map_err(|_| (false, CommandError::InvalidResponse))?; + .map_err(|e| (true, CommandError::Codec(e)))?; + + let cert_info = CertificateInfo::read_from_bytes(data) + .map_err(|_| (true, CommandError::InvalidResponse))?; - // TODO: Store cert_info in context + peer_cert_store + .set_cert_info(slot_id as u8, cert_info) + .map_err(|e| (true, CommandError::CertStore(e)))?; resp_payload .pull_data(size_of::()) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; } - // Decode KeyUsageMasks (one per slot) - for _slot_id in 0..slot_n { + for slot_id in 0..slot_n { if resp_payload.data_len() < size_of::() { - return Err((false, CommandError::InvalidResponse)); + return Err((true, CommandError::InvalidResponse)); } + let data = resp_payload .data(size_of::()) - .map_err(|e| (false, CommandError::Codec(e)))?; - let _key_usage_mask = KeyUsageMask::read_from_bytes(data) - .map_err(|_| (false, CommandError::InvalidResponse))?; + .map_err(|e| (true, CommandError::Codec(e)))?; + + let key_usage_mask = KeyUsageMask::read_from_bytes(data) + .map_err(|_| (true, CommandError::InvalidResponse))?; - // TODO: Store key_usage_mask in context + peer_cert_store + .set_key_usage_mask(slot_id as u8, key_usage_mask) + .map_err(|e| (true, CommandError::CertStore(e)))?; resp_payload .pull_data(size_of::()) - .map_err(|e| (false, CommandError::Codec(e)))?; + .map_err(|e| (true, CommandError::Codec(e)))?; } } diff --git a/src/protocol/certs.rs b/src/protocol/certs.rs index 27a3762..f4761c1 100644 --- a/src/protocol/certs.rs +++ b/src/protocol/certs.rs @@ -62,7 +62,7 @@ impl SpdmCertChainHeader { // SPDM CertificateInfo fields bitfield! { -#[derive(FromBytes, IntoBytes, Immutable, Default)] +#[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] #[repr(C, packed)] pub struct CertificateInfo(u8); impl Debug; @@ -107,7 +107,7 @@ impl TryFrom for CertModel { // SPDM KeyUsageMask fields bitfield! { -#[derive(FromBytes, IntoBytes, Immutable, Default)] +#[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] #[repr(C)] pub struct KeyUsageMask(u16); impl Debug; From 24f7077267ec884c594fd7ecb6a4361555dc84ad Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 16 Feb 2026 13:03:44 +0100 Subject: [PATCH 32/86] Remove unused imports --- src/commands/digests/mod.rs | 4 ++-- src/commands/digests/request.rs | 1 - src/commands/digests/response.rs | 8 +++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/commands/digests/mod.rs b/src/commands/digests/mod.rs index 9eef565..f208497 100644 --- a/src/commands/digests/mod.rs +++ b/src/commands/digests/mod.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license -use crate::cert_store::{cert_slot_mask, SpdmCertStore}; -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::cert_store::SpdmCertStore; +use crate::codec::{CommonCodec, MessageBuf}; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult, PlatformError}; use crate::platform::hash::{SpdmHash, SpdmHashAlgoType}; diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs index b52948c..de5364b 100644 --- a/src/commands/digests/request.rs +++ b/src/commands/digests/request.rs @@ -1,6 +1,5 @@ // Licensed under the Apache-2.0 license -use crate::cert_store::PeerCertStore; use crate::codec::Codec; use crate::context::SpdmContext; use crate::error::{CommandError, CommandResult}; diff --git a/src/commands/digests/response.rs b/src/commands/digests/response.rs index 0187abb..a0bfa5f 100644 --- a/src/commands/digests/response.rs +++ b/src/commands/digests/response.rs @@ -1,14 +1,12 @@ // Licensed under the Apache-2.0 license -use crate::cert_store::{cert_slot_mask, SpdmCertStore}; -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::cert_store::cert_slot_mask; +use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; -use crate::error::{CommandError, CommandResult, PlatformError}; -use crate::platform::hash::{SpdmHash, SpdmHashAlgoType}; +use crate::error::{CommandError, CommandResult}; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; -use zerocopy::{FromBytes, Immutable, IntoBytes}; use super::*; From 887c1fcf8789ddac7de4bafb0299d31f28e840c1 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 11 Feb 2026 14:58:24 +0100 Subject: [PATCH 33/86] Fix build for all examples --- examples/spdm_requester.rs | 6 ++---- examples/spdm_responder.rs | 7 +++++-- tests/spdm_validator_host.rs | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index db8559e..5e1ad5d 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -4,21 +4,19 @@ use std::env; use std::io::{Error, ErrorKind, Result as IoResult}; -use std::net::{TcpListener, TcpStream}; +use std::net::TcpStream; use std::process; use spdm_lib::codec::MessageBuf; use spdm_lib::commands::certificate::request::generate_get_certificate; use spdm_lib::context::SpdmContext; -use spdm_lib::error::{SpdmError, SpdmResult}; -use spdm_lib::platform::transport::SpdmTransport; +use spdm_lib::error::SpdmError; use spdm_lib::protocol::algorithms::{ AeadCipherSuite, AlgorithmPriorityTable, BaseAsymAlgo, BaseHashAlgo, DeviceAlgorithms, DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, }; use spdm_lib::protocol::version; -use spdm_lib::protocol::ReqRespCode; use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; // Import platform implementations - no duplicates! diff --git a/examples/spdm_responder.rs b/examples/spdm_responder.rs index 02f674b..f488660 100644 --- a/examples/spdm_responder.rs +++ b/examples/spdm_responder.rs @@ -121,7 +121,10 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { /// Handle client connection with real SPDM processing fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<()> { - let mut transport = SpdmSocketTransport::new(stream); + let mut transport = SpdmSocketTransport::new( + stream, + platform::socket_transport::SocketTransportType::None, + ); // Create platform implementations - all from platform module! let mut hash = Sha384Hash::new(); @@ -146,7 +149,7 @@ fn handle_spdm_client(stream: TcpStream, config: &ResponderConfig) -> IoResult<( capabilities, algorithms, &mut cert_store, - ExamplePeerCertStore::default(), + None, &mut hash, &mut m1_hash, &mut l1_hash, diff --git a/tests/spdm_validator_host.rs b/tests/spdm_validator_host.rs index 8742224..2d72cbd 100644 --- a/tests/spdm_validator_host.rs +++ b/tests/spdm_validator_host.rs @@ -172,6 +172,7 @@ fn spdm_validator_host() -> SpdmResult<()> { flags: spdm_lib::protocol::capabilities::CapabilityFlags::default(), data_transfer_size: 1024, max_spdm_msg_size: 2048, + include_supported_algorithms: false, }; // Algorithms (provide trivial single-selection values) From 08e733449e5bc0c30e3c27866caed36ecdc163c6 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 16 Feb 2026 13:31:37 +0100 Subject: [PATCH 34/86] Fix all warnings when building examples --- examples/platform/cert_store.rs | 6 +++--- examples/platform/mod.rs | 3 +++ examples/spdm_responder.rs | 3 --- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index d9bfebf..4918a13 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -462,15 +462,15 @@ impl PeerCertStore for ExamplePeerCertStore { Ok(spdm_lib::cert_store::ReassemblyStatus::InProgress) } - fn reset(&mut self, slot_id: u8) { + fn reset(&mut self, _slot_id: u8) { todo!() } - fn get_root_hash(&self, slot_id: u8) -> Option<&[u8]> { + fn get_root_hash(&self, _slot_id: u8) -> Option<&[u8]> { todo!() } - fn get_raw_chain(&self, slot_id: u8) -> Option<&[u8]> { + fn get_raw_chain(&self, _slot_id: u8) -> Option<&[u8]> { todo!() } diff --git a/examples/platform/mod.rs b/examples/platform/mod.rs index ff7c42a..b1433e8 100644 --- a/examples/platform/mod.rs +++ b/examples/platform/mod.rs @@ -5,6 +5,9 @@ //! This module provides working platform implementations that can be easily //! swapped out for production implementations. +#![allow(unused_imports)] +#![allow(dead_code)] + pub mod cert_store; pub mod certs; pub mod crypto; diff --git a/examples/spdm_responder.rs b/examples/spdm_responder.rs index f488660..7358e6c 100644 --- a/examples/spdm_responder.rs +++ b/examples/spdm_responder.rs @@ -12,7 +12,6 @@ use std::io::{Error, ErrorKind, Result as IoResult}; use std::net::{TcpListener, TcpStream}; use std::process; -use spdm_lib::cert_store::PeerCertStore; use spdm_lib::codec::MessageBuf; use spdm_lib::context::SpdmContext; use spdm_lib::protocol::algorithms::{ @@ -27,8 +26,6 @@ use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; mod platform; use platform::{DemoCertStore, DemoEvidence, Sha384Hash, SpdmSocketTransport, SystemRng}; -use crate::platform::cert_store::ExamplePeerCertStore; - /// Responder configuration #[derive(Debug, Clone)] struct ResponderConfig { From 0b1384785090581772c7178713c702eb204e0646 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 16 Feb 2026 14:18:28 +0100 Subject: [PATCH 35/86] Fix tests --- src/commands/digests/request.rs | 1 + src/test.rs | 1 + tests/spdm_validator_host.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs index de5364b..87ba5e9 100644 --- a/src/commands/digests/request.rs +++ b/src/commands/digests/request.rs @@ -165,6 +165,7 @@ pub(crate) fn handle_digests_response<'a>( pub mod tests { #[test] + #[ignore] fn test_generate_digest_request() { todo!(); } diff --git a/src/test.rs b/src/test.rs index 6a1c560..2c8a410 100644 --- a/src/test.rs +++ b/src/test.rs @@ -47,6 +47,7 @@ pub fn create_context<'a>( DeviceCapabilities::default(), algorithms, &mut stack.cert_store, + None, &mut stack.hasher, &mut stack.m1, &mut stack.l1, diff --git a/tests/spdm_validator_host.rs b/tests/spdm_validator_host.rs index 2d72cbd..56b77b9 100644 --- a/tests/spdm_validator_host.rs +++ b/tests/spdm_validator_host.rs @@ -218,6 +218,7 @@ fn spdm_validator_host() -> SpdmResult<()> { dev_caps, local_algos, &mut certs, + None, &mut hash_main, &mut hash_m1, &mut hash_l1, From 4a7638e8a6132918ee879152d4959345fe0dad9c Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 28 Jan 2026 13:51:42 +0100 Subject: [PATCH 36/86] Add CI/CD jobs for tests and license check --- .github/workflows/ci.yml | 48 +++++++++++++++++++++++++++++++++++ .github/workflows/license.yml | 11 ++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/license.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a54b7d4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: Lint, Build and Test + +on: + push: + branches: [ "main" ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +env: + CARGO_TERM_COLOR: always + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust (stable) with components + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + - name: Cache cargo registry and build + uses: Swatinem/rust-cache@v2 + - name: rustfmt check + run: cargo fmt --all -- --check + - name: clippy (deny warnings) + run: | + cargo clippy --all-targets --all-features --workspace -- -D warnings + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust (stable) with components + uses: dtolnay/rust-toolchain@stable + - name: Cache cargo registry and build + uses: Swatinem/rust-cache@v2 + - name: Cargo Build + run: cargo build --all-targets --workspace --verbose + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust (stable) with components + uses: dtolnay/rust-toolchain@stable + - name: Cache cargo registry and build + uses: Swatinem/rust-cache@v2 + - name: Run tests + run: cargo test --all-features --verbose --workspace diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml new file mode 100644 index 0000000..b79837e --- /dev/null +++ b/.github/workflows/license.yml @@ -0,0 +1,11 @@ +name: License Check + +on: pull_request + +jobs: + license: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: License Eye Header + uses: apache/skywalking-eyes@v0.8.0 From 9d9a7531806c30d4357da7a6feacdfbbf3fa01d9 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 16 Feb 2026 14:41:03 +0100 Subject: [PATCH 37/86] Add licenserc.yaml --- .licenserc.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .licenserc.yaml diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 0000000..298395d --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,12 @@ +header: + license: + spdx-id: Apache-2.0 + # No copyright owner until this has been clarified + # copyright-owner: OpenPRoT a Series of LF Projects, LLC + copyright-year: 2025 + software-name: spdm-lib + + paths: + - '**/*.rs' + + comment: on-failure From b403b9bfa915b601861aef724350144377c0b13f Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 17 Feb 2026 12:02:07 +0100 Subject: [PATCH 38/86] Implement missing functionality for ExamplePeerCertStore --- examples/platform/cert_store.rs | 87 ++++++++++++++++++++++++++++----- examples/spdm_requester.rs | 18 ++++++- src/cert_store.rs | 22 ++++++--- src/context.rs | 4 ++ src/protocol/algorithms.rs | 58 ++++++++++++++++------ 5 files changed, 153 insertions(+), 36 deletions(-) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index 4918a13..1f7fb35 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -11,11 +11,21 @@ use p384::{ ecdsa::{signature::hazmat::PrehashSigner, Signature, SigningKey}, SecretKey, }; +use zerocopy::FromBytes; use super::certs::{STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; -use spdm_lib::cert_store::{CertStoreError, CertStoreResult, PeerCertStore, SpdmCertStore}; -use spdm_lib::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; -use spdm_lib::protocol::certs::{CertificateInfo, KeyUsageMask}; +use spdm_lib::protocol::{ + algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}, + SpdmCertChainHeader, +}; +use spdm_lib::protocol::{ + certs::{CertificateInfo, KeyUsageMask}, + BaseHashAlgoType, +}; +use spdm_lib::{ + cert_store::{CertStoreError, CertStoreResult, PeerCertStore, ReassemblyStatus, SpdmCertStore}, + error::PlatformError, +}; /// Certificate store with proper ECDSA signing pub struct DemoCertStore { @@ -413,6 +423,41 @@ impl Default for PeerSlot { } } +impl PeerSlot { + /// Get the digest for the root certificate of the chain + /// + /// # Arguments + /// * `hash_algo` - The hash algorithm negotiated with the peer. + fn get_root_hash(&self, hash_algo: BaseHashAlgoType) -> Option<&[u8]> { + let (length, rest) = SpdmCertChainHeader::ref_from_prefix(&self.cert_chain).ok()?; + if length.get_length() != self.cert_chain.len() as u32 { + println!( + "[Error] cert chain length mismatch (expected {}, got {})", + length.get_length(), + self.cert_chain.len() + ); + return None; + } + Some(&rest[..hash_algo.hash_byte_size()]) + } + /// Get the DER x509 certificate chain + /// + /// # Arguments + /// * `hash_algo` - The hash algorithm negotiated with the peer. + fn get_cert_chain(&self, hash_algo: BaseHashAlgoType) -> Option<&[u8]> { + let (length, rest) = SpdmCertChainHeader::ref_from_prefix(&self.cert_chain).ok()?; + if length.get_length() != self.cert_chain.len() as u32 { + println!( + "[Error] cert chain length mismatch (expected {}, got {})", + length.get_length(), + self.cert_chain.len() + ); + return None; + } + Some(&rest[hash_algo.hash_byte_size()..]) + } +} + /// Concrete implementation of `PeerCertStore` for demonstration purposes. /// This example store manages a single certificate slot (slot 0) and allows /// setting and retrieving the certificate chain, digest, key pair ID, certificate info, @@ -462,26 +507,31 @@ impl PeerCertStore for ExamplePeerCertStore { Ok(spdm_lib::cert_store::ReassemblyStatus::InProgress) } - fn reset(&mut self, _slot_id: u8) { - todo!() - } - - fn get_root_hash(&self, _slot_id: u8) -> Option<&[u8]> { - todo!() + fn reset(&mut self, slot_id: u8) { + if let Some(Some(slot)) = self.peer_slots.get_mut(slot_id as usize) { + *slot = PeerSlot::default(); + } } - fn get_raw_chain(&self, _slot_id: u8) -> Option<&[u8]> { - todo!() + fn get_raw_chain(&self, slot_id: u8) -> Result<&[u8], CertStoreError> { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + Ok(&slot.cert_chain) } - fn get_cert_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]> { + fn get_cert_chain(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]> { let slot = self .peer_slots .get(slot_id as usize) .ok_or(CertStoreError::InvalidSlotId(slot_id))? .as_ref() .ok_or(CertStoreError::PlatformError)?; - Ok(&slot.cert_chain) + slot.get_cert_chain(hash_algo) + .ok_or(CertStoreError::CertReadError) } /// Set the supported slots bit mask and initialize PeerSlot entries for any newly supported slots. @@ -623,4 +673,15 @@ impl PeerCertStore for ExamplePeerCertStore { slot.keypair_id = Some(keypair); Ok(()) } + + fn get_root_hash(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]> { + let slot = self + .peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)?; + slot.get_root_hash(hash_algo) + .ok_or(CertStoreError::CertReadError) + } } diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 5e1ad5d..42959eb 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -16,7 +16,7 @@ use spdm_lib::protocol::algorithms::{ DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, }; -use spdm_lib::protocol::version; +use spdm_lib::protocol::{version, BaseHashAlgoType}; use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; // Import platform implementations - no duplicates! @@ -356,6 +356,22 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { } } println!("sucessfully retrieved peer cert chain"); + if let Some(store) = spdm_context.peer_cert_store() { + let hash_algo: BaseHashAlgoType = spdm_context + .connection_info() + .peer_algorithms() + .base_hash_algo + .try_into() + .unwrap(); + println!( + "slot 0 root hash: {:02x?}", + store.get_root_hash(0, hash_algo).unwrap() + ); + println!( + "slot 0 cert chain: {:02x?}", + store.get_cert_chain(0, hash_algo).unwrap() + ); + } Ok(()) } diff --git a/src/cert_store.rs b/src/cert_store.rs index 5b0bc2b..39eda19 100644 --- a/src/cert_store.rs +++ b/src/cert_store.rs @@ -3,7 +3,7 @@ use crate::error::{SpdmError, SpdmResult}; use crate::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; use crate::protocol::certs::{CertificateInfo, KeyUsageMask}; -use crate::protocol::SpdmCertChainHeader; +use crate::protocol::{BaseHashAlgoType, SpdmCertChainHeader}; pub const MAX_CERT_SLOTS_SUPPORTED: u8 = 2; pub const SPDM_CERT_CHAIN_METADATA_LEN: u16 = @@ -208,18 +208,20 @@ pub trait PeerCertStore { /// * `CertStoreError::PlatformError` - If there was a platform-specific error. fn get_provisioned_slots(&self) -> CertStoreResult; - /// Get the stored certificate chain for the given slot. + /// Get the stored certificate chain for the given slot, + /// consisting of one or more ASN.1 DER-encoded X.509 v3 certificates. /// /// # Arguments /// * `slot_id` - The slot ID of the certificate chain. + /// * `hash_algo` - The hash algorithm that was negotiated with the peer. /// /// # Returns - /// * `Ok(&[u8])` - The certificate chain bytes. + /// * `Ok(&[u8])` - The certificate chain bytes, not including the length and root hash header. /// /// # Errors /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. /// * `CertStoreError::PlatformError` - If there was a platform-specific error. - fn get_cert_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]>; + fn get_cert_chain(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]>; /// Store a certificate chain in the given slot. /// @@ -366,12 +368,16 @@ pub trait PeerCertStore { /// Get the root hash of a peer certificate /// + /// # Arguments + /// * `slot_id` - The Slot ID of the certificate chain + /// * `hash_algo` - The hash algorithm that was negotiated with the peer. + /// /// # Returns - /// - The digest of the Root Certificate if available - fn get_root_hash(&self, slot_id: u8) -> Option<&[u8]>; + /// * The digest of the Root Certificate if available + fn get_root_hash(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]>; - /// Get a complete certificate chain consisting of one or more ASN.1 DER-encoded X.509 v3 certificates - fn get_raw_chain(&self, slot_id: u8) -> Option<&[u8]>; + /// Get the raw cert chain, including the length header and root hash + fn get_raw_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]>; } pub enum ReassemblyStatus { diff --git a/src/context.rs b/src/context.rs index 75796dc..decc0e4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -343,4 +343,8 @@ impl<'a> SpdmContext<'a> { .append(transcript_context, msg) .map_err(|e| (false, CommandError::Transcript(e))) } + + pub fn peer_cert_store(&self) -> Option<&dyn PeerCertStore> { + self.state.peer_cert_store.as_deref() + } } diff --git a/src/protocol/algorithms.rs b/src/protocol/algorithms.rs index e778b17..d6c1d0b 100644 --- a/src/protocol/algorithms.rs +++ b/src/protocol/algorithms.rs @@ -289,15 +289,16 @@ impl Prioritize for BaseHashAlgo { } } +/// BaseHashAlgo variants #[derive(Debug, Clone, Copy)] pub enum BaseHashAlgoType { - TpmAlgSha256, - TpmAlgSha384, - TpmAlgSha512, - TpmAlgSha3_256, - TpmAlgSha3_384, - TpmAlgSha3_512, - TpmAlgSm3_256, + TpmAlgSha256 = 1, + TpmAlgSha384 = 2, + TpmAlgSha512 = 4, + TpmAlgSha3_256 = 8, + TpmAlgSha3_384 = 16, + TpmAlgSha3_512 = 32, + TpmAlgSm3_256 = 64, } impl TryFrom for BaseHashAlgoType { @@ -305,18 +306,47 @@ impl TryFrom for BaseHashAlgoType { fn try_from(value: u8) -> Result { match value { - 0 => Ok(BaseHashAlgoType::TpmAlgSha256), - 1 => Ok(BaseHashAlgoType::TpmAlgSha384), - 2 => Ok(BaseHashAlgoType::TpmAlgSha512), - 3 => Ok(BaseHashAlgoType::TpmAlgSha3_256), - 4 => Ok(BaseHashAlgoType::TpmAlgSha3_384), - 5 => Ok(BaseHashAlgoType::TpmAlgSha3_512), - 6 => Ok(BaseHashAlgoType::TpmAlgSm3_256), + 1 => Ok(BaseHashAlgoType::TpmAlgSha256), + 2 => Ok(BaseHashAlgoType::TpmAlgSha384), + 4 => Ok(BaseHashAlgoType::TpmAlgSha512), + 8 => Ok(BaseHashAlgoType::TpmAlgSha3_256), + 16 => Ok(BaseHashAlgoType::TpmAlgSha3_384), + 32 => Ok(BaseHashAlgoType::TpmAlgSha3_512), + 64 => Ok(BaseHashAlgoType::TpmAlgSm3_256), _ => Err(SpdmError::InvalidParam), } } } +impl BaseHashAlgoType { + /// The size of the digest in bytes + pub fn hash_byte_size(&self) -> usize { + match self { + BaseHashAlgoType::TpmAlgSha256 => 32, + BaseHashAlgoType::TpmAlgSha384 => 48, + BaseHashAlgoType::TpmAlgSha512 => 64, + BaseHashAlgoType::TpmAlgSha3_256 => 32, + BaseHashAlgoType::TpmAlgSha3_384 => 48, + BaseHashAlgoType::TpmAlgSha3_512 => 64, + BaseHashAlgoType::TpmAlgSm3_256 => 32, + } + } +} + +#[derive(Debug)] +pub struct InvalidBashHashAlgo; + +impl TryFrom for BaseHashAlgoType { + type Error = InvalidBashHashAlgo; + + fn try_from(value: BaseHashAlgo) -> Result { + if value.0 > u8::MAX as u32 { + return Err(InvalidBashHashAlgo); + } + Self::try_from(value.0 as u8).map_err(|_| InvalidBashHashAlgo) + } +} + // Measurement Extension Log Specification field bitfield! { #[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] From 881ede4f6835803d7aaf205405c081f6941c58ec Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 17 Feb 2026 14:10:17 +0100 Subject: [PATCH 39/86] Requester example: Parse x.509 cert chain --- Cargo.toml | 3 ++- examples/spdm_requester.rs | 29 ++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c10081b..4ee3c70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ sha2 = { version = "0.10"} p384 = { version = "0.13", features = ["ecdsa", "pem"] } digest = { version = "0.10"} hex = "0.4.3" -x509-cert = "0.2.5" +der = "0.8.0-rc.10" +x509-cert = "0.3.0-rc.4" # Examples [[example]] diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 42959eb..696bc76 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -7,6 +7,7 @@ use std::io::{Error, ErrorKind, Result as IoResult}; use std::net::TcpStream; use std::process; +use der::Decode; use spdm_lib::codec::MessageBuf; use spdm_lib::commands::certificate::request::generate_get_certificate; use spdm_lib::context::SpdmContext; @@ -32,6 +33,8 @@ use spdm_lib::commands::version::{request::generate_get_version, VersionReqPaylo use crate::platform::cert_store::ExamplePeerCertStore; +use x509_cert::Certificate; + /// Responder configuration #[derive(Debug, Clone)] struct RequesterConfig { @@ -346,7 +349,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .requester_process_message(&mut message_buffer) .unwrap(); if config.verbose { - println!("CERTIFICATE: {:x?}", &message_buffer.message_data()); + println!("CERTIFICATE: Ok ({} bytes)", &message_buffer.msg_len(),); } if !matches!( spdm_context.connection_info().state(), @@ -363,14 +366,26 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .base_hash_algo .try_into() .unwrap(); + let root_hash = store.get_root_hash(0, hash_algo).unwrap(); println!( - "slot 0 root hash: {:02x?}", - store.get_root_hash(0, hash_algo).unwrap() - ); - println!( - "slot 0 cert chain: {:02x?}", - store.get_cert_chain(0, hash_algo).unwrap() + "slot 0: Root hash ({hash_algo:?}, {} bytes): {:02x?}", + root_hash.len(), + root_hash ); + let mut cert_chain = store.get_cert_chain(0, hash_algo).unwrap(); + + println!("slot 0: Parsing {} bytes cert chain:", cert_chain.len()); + loop { + let (cert, rest) = Certificate::from_der_partial(cert_chain).unwrap(); + cert_chain = rest; + println!( + "Cert with subject CN {:?}", + cert.tbs_certificate().subject().common_name() + ); + if rest.is_empty() { + break; + } + } } Ok(()) From f280e3e079585767afd1890afdef9fa790737349 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 17 Feb 2026 16:42:53 +0100 Subject: [PATCH 40/86] Add basic cert chain verification to example requester --- Cargo.toml | 3 +- examples/cert/ecp384_ca.cert.der | Bin 0 -> 472 bytes examples/spdm_requester.rs | 70 +++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 examples/cert/ecp384_ca.cert.der diff --git a/Cargo.toml b/Cargo.toml index 4ee3c70..53de093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,9 @@ sha2 = { version = "0.10"} p384 = { version = "0.13", features = ["ecdsa", "pem"] } digest = { version = "0.10"} hex = "0.4.3" -der = "0.8.0-rc.10" +der = "0.8" x509-cert = "0.3.0-rc.4" +signature = "2" # Examples [[example]] diff --git a/examples/cert/ecp384_ca.cert.der b/examples/cert/ecp384_ca.cert.der new file mode 100644 index 0000000000000000000000000000000000000000..8c6f5256099d4c11c8743a57d4a6ab57a93f70ad GIT binary patch literal 472 zcmXqLV!UF|#2B@JnTe5!Nkq}{uHTy#O02@2lP9{b+R_#U>1|{Ys1{P5nZpYv4R}~&q{fJ0^f8cH6`iT9L zf{fR-udH|LzOwE8DxGNl7Rd!Y0<#-^uBX%)o|jc-kuVTzz^;HFq(GRF@jnZz0W*+74nF1#27^>411+PeI{#P6 z8wcg*{B-j&=c|3Th@z IoResult<()> { root_hash.len(), root_hash ); - let mut cert_chain = store.get_cert_chain(0, hash_algo).unwrap(); + let cert_chain = store.get_cert_chain(0, hash_algo).unwrap(); println!("slot 0: Parsing {} bytes cert chain:", cert_chain.len()); + let mut certs = Vec::new(); + let mut rest = cert_chain; loop { - let (cert, rest) = Certificate::from_der_partial(cert_chain).unwrap(); - cert_chain = rest; - println!( - "Cert with subject CN {:?}", - cert.tbs_certificate().subject().common_name() - ); + let (cert, r) = Certificate::from_der_partial(rest).unwrap(); + rest = r; + println!("Cert with subject {}", cert.tbs_certificate().subject()); + println!(" signature alg. id: {}", cert.signature_algorithm().oid); + certs.push(cert); if rest.is_empty() { break; } } + + if !certs.is_empty() { + let ca_cert = Certificate::from_der(CA_CERT).unwrap(); + let ca_cert_sig = ca_cert.signature().as_bytes().unwrap(); + assert_eq!(certs[0].signature().as_bytes().unwrap(), ca_cert_sig); + println!("CA cert signature matches expected CA signature"); + assert!(verify_cert_chain(&certs)); + println!("Cert chain signatures successfully verified!"); + } } Ok(()) @@ -556,3 +569,44 @@ fn main() -> Result<(), Box> { Ok(()) } + +/// Verifies the provided certificate chain +/// +/// Assumes that the fist certificate in the chain is +/// an already verified trusted certificate (e.g. the root CA cert). +/// Only checks the validity of the signatures (does not check CRL, validity period, ...). +fn verify_cert_chain(chain: &[Certificate]) -> bool { + use p384::ecdsa::{Signature, VerifyingKey}; + use signature::Verifier; + let mut pub_key = VerifyingKey::from_sec1_bytes( + chain + .first() + .unwrap() + .tbs_certificate() + .subject_public_key_info() + .subject_public_key + .as_bytes() + .unwrap(), + ) + .unwrap(); + for cert in chain.iter().skip(1) { + let sig = Signature::from_der(cert.signature().as_bytes().unwrap()).unwrap(); + if !pub_key + .verify(&cert.tbs_certificate().to_der().unwrap(), &sig) + .is_ok() + { + return false; + } + + println!("Verified {}", cert.tbs_certificate().subject()); + pub_key = VerifyingKey::from_sec1_bytes( + cert.tbs_certificate() + .subject_public_key_info() + .subject_public_key + .as_bytes() + .unwrap(), + ) + .unwrap(); + } + true +} From eccfdbea082b9f91f29d124feeae83cd543dcb13 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 19 Feb 2026 09:56:52 +0100 Subject: [PATCH 41/86] Fix oversight in example cert chain verification The CA cert was skipped by mistake. This would allow an attacker to supply a forged chain, where the CA certs signature is still the expected. --- examples/spdm_requester.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index a12586a..0c09747 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -589,7 +589,7 @@ fn verify_cert_chain(chain: &[Certificate]) -> bool { .unwrap(), ) .unwrap(); - for cert in chain.iter().skip(1) { + for cert in chain.iter() { let sig = Signature::from_der(cert.signature().as_bytes().unwrap()).unwrap(); if !pub_key .verify(&cert.tbs_certificate().to_der().unwrap(), &sig) From d5b0a29b25538fdf7ffe2ca19629602a25e1751e Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 24 Feb 2026 11:58:09 +0100 Subject: [PATCH 42/86] Remove example/test_static_certs --- Cargo.toml | 4 --- examples/test_static_certs.rs | 55 ----------------------------------- 2 files changed, 59 deletions(-) delete mode 100644 examples/test_static_certs.rs diff --git a/Cargo.toml b/Cargo.toml index 53de093..500dac4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,3 @@ signature = "2" name = "spdm_responder" path = "examples/spdm_responder.rs" required-features = ["crypto"] - -[[example]] -name = "test_static_certs" -path = "examples/test_static_certs.rs" diff --git a/examples/test_static_certs.rs b/examples/test_static_certs.rs deleted file mode 100644 index ee3d602..0000000 --- a/examples/test_static_certs.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Test program to verify static certificates work correctly - -// Import platform implementations -mod platform; -use platform::{STATIC_ATTESTATION_CERT, STATIC_CERTIFICATE_CHAIN, STATIC_ROOT_CA_CERT}; - -fn main() { - println!("Static Certificate Test"); - println!("======================="); - - println!("Root CA Certificate: {} bytes", STATIC_ROOT_CA_CERT.len()); - println!( - "Attestation Certificate: {} bytes", - STATIC_ATTESTATION_CERT.len() - ); - println!( - "Certificate Chain: {} bytes", - STATIC_CERTIFICATE_CHAIN.len() - ); - - // Verify the chain is the concatenation of the individual certs - let expected_len = STATIC_ROOT_CA_CERT.len() + STATIC_ATTESTATION_CERT.len(); - if STATIC_CERTIFICATE_CHAIN.len() == expected_len { - println!("✓ Certificate chain length matches individual certificates"); - } else { - println!( - "✗ Certificate chain length mismatch: expected {}, got {}", - expected_len, - STATIC_CERTIFICATE_CHAIN.len() - ); - } - - // Verify the chain starts with the root CA - if STATIC_CERTIFICATE_CHAIN.starts_with(STATIC_ROOT_CA_CERT) { - println!("✓ Certificate chain starts with root CA"); - } else { - println!("✗ Certificate chain does not start with root CA"); - } - - // Verify the chain ends with the attestation cert - if STATIC_CERTIFICATE_CHAIN.ends_with(STATIC_ATTESTATION_CERT) { - println!("✓ Certificate chain ends with attestation certificate"); - } else { - println!("✗ Certificate chain does not end with attestation certificate"); - } - - // Check X.509 structure (should start with SEQUENCE tag 0x30) - if STATIC_ROOT_CA_CERT[0] == 0x30 && STATIC_ATTESTATION_CERT[0] == 0x30 { - println!("✓ Both certificates have proper X.509 DER format (SEQUENCE tag)"); - } else { - println!("✗ Certificates do not have proper X.509 DER format"); - } - - println!("\nStatic certificates are ready for use!"); -} From ff8e3969295d52027e6b5156bc78eeafad81c6d3 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 24 Feb 2026 12:31:40 +0100 Subject: [PATCH 43/86] Remove superfluous `crypto` feature flag, update compilation readme --- COMPILATION_README.md | 61 +++++------------------ Cargo.toml | 2 - examples/platform/cert_store.rs | 85 +++++++++------------------------ examples/platform/crypto.rs | 73 ++++++++-------------------- 4 files changed, 55 insertions(+), 166 deletions(-) diff --git a/COMPILATION_README.md b/COMPILATION_README.md index f041213..48bf08f 100644 --- a/COMPILATION_README.md +++ b/COMPILATION_README.md @@ -52,30 +52,25 @@ spdm-lib/ ### Build the Library ```bash -cargo build --features std,crypto +cargo build ``` ### Build Examples Build the main SPDM responder: ```bash -cargo build --example spdm_responder --features std,crypto -``` - -Build the certificate test: -```bash -cargo build --example test_static_certs --features std +cargo build --example spdm_responder ``` Build all examples: ```bash -cargo build --examples --features std,crypto +cargo build --examples ``` ### Release Build (Optimized) ```bash -cargo build --release --example spdm_responder --features std,crypto +cargo build --release --example spdm_responder ``` ## Running Tests @@ -84,36 +79,7 @@ cargo build --release --example spdm_responder --features std,crypto Run all library unit tests: ```bash -cargo test --features crypto -``` - -### Static Certificate Verification - -Test that the static certificates are properly formatted: -```bash -cargo run --example test_static_certs -``` - -Expected output: -``` -Static Certificate Test -======================= -Root CA Certificate: 419 bytes -Attestation Certificate: 453 bytes -Certificate Chain: 872 bytes -✓ Certificate chain length matches individual certificates -✓ Certificate chain starts with root CA -✓ Certificate chain ends with attestation certificate -✓ Both certificates have proper X.509 DER format (SEQUENCE tag) - -Static certificates are ready for use! -``` - -### Integration Tests - -Run integration tests: -```bash -cargo test --test integration --features crypto +cargo test ``` ## Running the SPDM Responder @@ -122,25 +88,25 @@ cargo test --test integration --features crypto Start the SPDM responder on default port 2323: ```bash -cargo run --example spdm_responder --features crypto +cargo run --example spdm_responder ``` ### With Custom Port ```bash -cargo run --example spdm_responder --features crypto -- --port 8080 +cargo run --example spdm_responder -- --port 8080 ``` ### With Verbose Logging ```bash -cargo run --example spdm_responder --features crypto -- --verbose +cargo run --example spdm_responder -- --verbose ``` ### All Options ```bash -cargo run --example spdm_responder --features crypto -- \ +cargo run --example spdm_responder -- \ --port 2323 \ --cert device_cert.pem \ --key device_key.pem \ @@ -165,7 +131,7 @@ The responder is compatible with the DMTF SPDM device validator: 1. **Start the responder:** ```bash - cargo run --example spdm_responder --features crypto -- --verbose + cargo run --example spdm_responder -- --verbose ``` 2. **In another terminal, test with nc (netcat):** @@ -200,7 +166,7 @@ openssl verify -CAfile root_ca.pem attestation.pem 1. Create a new file in `examples/` 2. Add necessary dependencies to `Cargo.toml` if needed -3. Build with: `cargo build --example your_example --features crypto` +3. Build with: `cargo build --example your_example` ### Modifying Certificates @@ -210,7 +176,7 @@ The static certificates are in `examples/platform/certs.rs`. They were generated Enable verbose logging to see detailed SPDM message processing: ```bash -RUST_LOG=debug cargo run --example spdm_responder --features crypto -- --verbose +RUST_LOG=debug cargo run --example spdm_responder -- --verbose ``` ## Troubleshooting @@ -221,7 +187,6 @@ If you encounter build errors: 1. **Update Rust**: `rustup update` 2. **Clean build**: `cargo clean && cargo build` -3. **Check features**: Ensure you're using `--features crypto` ### Connection Issues @@ -249,7 +214,7 @@ Licensed under the Apache-2.0 license. See LICENSE file for details. 2. Create a feature branch 3. Make your changes 4. Add tests if applicable -5. Run `cargo test --features crypto` +5. Run `cargo test` 6. Submit a pull request ## Support diff --git a/Cargo.toml b/Cargo.toml index 500dac4..64d0943 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [features] default = [] -crypto = [] [dependencies] bitfield = "0.14.0" @@ -29,4 +28,3 @@ signature = "2" [[example]] name = "spdm_responder" path = "examples/spdm_responder.rs" -required-features = ["crypto"] diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index 1f7fb35..ccde36d 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -6,7 +6,6 @@ use std::sync::Mutex; -#[cfg(feature = "crypto")] use p384::{ ecdsa::{signature::hazmat::PrehashSigner, Signature, SigningKey}, SecretKey, @@ -30,33 +29,21 @@ use spdm_lib::{ /// Certificate store with proper ECDSA signing pub struct DemoCertStore { cert_chain: Vec, - #[cfg(feature = "crypto")] signing_key: Mutex>, } impl DemoCertStore { pub fn new() -> Self { - #[cfg(feature = "crypto")] - { - println!("Loading static certificate chain..."); - let (cert_chain, signing_key) = Self::generate_certificate_chain(); - println!("Static certificate chain loaded successfully"); - - Self { - cert_chain, - signing_key: Mutex::new(Some(signing_key)), - } - } + println!("Loading static certificate chain..."); + let (cert_chain, signing_key) = Self::generate_certificate_chain(); + println!("Static certificate chain loaded successfully"); - #[cfg(not(feature = "crypto"))] - { - // Fallback for when crypto feature is not enabled - let cert_chain = b"DEMO_CERTIFICATE_CHAIN_DATA".to_vec(); - Self { cert_chain } + Self { + cert_chain, + signing_key: Mutex::new(Some(signing_key)), } } - #[cfg(feature = "crypto")] fn generate_certificate_chain() -> (Vec, SigningKey) { use crate::platform::certs::ATTESTATION_PRIVATE_KEY; @@ -192,27 +179,13 @@ impl SpdmCertStore for DemoCertStore { return Err(CertStoreError::InvalidSlotId(slot_id)); } - #[cfg(feature = "crypto")] - { - use sha2::{Digest, Sha384}; - // Calculate proper SHA-384 hash of the root certificate - let mut hasher = Sha384::new(); - hasher.update(STATIC_ROOT_CA_CERT); - let hash_result = hasher.finalize(); - cert_hash.copy_from_slice(&hash_result); - // println!(" Fabrizio Root Hash starts: {:02x?}", &cert_hash[..4]); - } - - #[cfg(not(feature = "crypto"))] - { - // Fallback for when crypto feature is not enabled - for (i, byte) in cert_hash.iter_mut().enumerate() { - *byte = STATIC_ROOT_CA_CERT - .get(i % STATIC_ROOT_CA_CERT.len()) - .copied() - .unwrap_or(0); - } - } + use sha2::{Digest, Sha384}; + // Calculate proper SHA-384 hash of the root certificate + let mut hasher = Sha384::new(); + hasher.update(STATIC_ROOT_CA_CERT); + let hash_result = hasher.finalize(); + cert_hash.copy_from_slice(&hash_result); + // println!(" Fabrizio Root Hash starts: {:02x?}", &cert_hash[..4]); Ok(()) } @@ -227,31 +200,19 @@ impl SpdmCertStore for DemoCertStore { return Err(CertStoreError::InvalidSlotId(slot_id)); } - #[cfg(feature = "crypto")] - { - if let Ok(signing_key_guard) = self.signing_key.lock() { - if let Some(ref signing_key) = *signing_key_guard { - let sig: Signature = signing_key.sign_prehash(hash).unwrap(); + if let Ok(signing_key_guard) = self.signing_key.lock() { + if let Some(ref signing_key) = *signing_key_guard { + let sig: Signature = signing_key.sign_prehash(hash).unwrap(); - let sig_bytes = sig.to_bytes(); - if sig_bytes.len() <= ECC_P384_SIGNATURE_SIZE { - signature[..sig_bytes.len()].copy_from_slice(&sig_bytes); - return Ok(()); - } - return Err(CertStoreError::PlatformError); + let sig_bytes = sig.to_bytes(); + if sig_bytes.len() <= ECC_P384_SIGNATURE_SIZE { + signature[..sig_bytes.len()].copy_from_slice(&sig_bytes); + return Ok(()); } + return Err(CertStoreError::PlatformError); } - Err(CertStoreError::PlatformError) - } - - #[cfg(not(feature = "crypto"))] - { - // Fallback for demo without crypto - for (i, byte) in signature.iter_mut().enumerate() { - *byte = hash[i % SHA384_HASH_SIZE] ^ ((i as u8).wrapping_mul(73)); - } - Ok(()) } + Err(CertStoreError::PlatformError) } fn key_pair_id(&self, slot_id: u8) -> Option { @@ -284,7 +245,6 @@ impl SpdmCertStore for DemoCertStore { } } -#[cfg(all(test, feature = "crypto"))] #[test] fn test_signing() { use p384::ecdsa::signature::SignatureEncoding; @@ -313,7 +273,6 @@ fn test_signing() { println!(" S: {}", hex::encode(&sig_bytes_hashed[48..])); } -#[cfg(all(test, feature = "crypto"))] #[test] fn debug_signing_verification() { use hex; diff --git a/examples/platform/crypto.rs b/examples/platform/crypto.rs index 3d9a413..8c3a9f4 100644 --- a/examples/platform/crypto.rs +++ b/examples/platform/crypto.rs @@ -4,7 +4,6 @@ //! //! Provides SHA-384 hash and system RNG implementations -#[cfg(feature = "crypto")] use sha2::{Digest, Sha384}; use spdm_lib::platform::hash::{SpdmHash, SpdmHashAlgoType, SpdmHashError, SpdmHashResult}; @@ -13,7 +12,6 @@ use spdm_lib::platform::rng::{SpdmRng, SpdmRngResult}; /// SHA-384 hash implementation using proper cryptography pub struct Sha384Hash { current_algo: SpdmHashAlgoType, - #[cfg(feature = "crypto")] hasher: Option, } @@ -21,7 +19,6 @@ impl Sha384Hash { pub fn new() -> Self { Self { current_algo: SpdmHashAlgoType::SHA384, - #[cfg(feature = "crypto")] hasher: None, } } @@ -42,23 +39,11 @@ impl SpdmHash for Sha384Hash { return Err(SpdmHashError::BufferTooSmall); } - #[cfg(feature = "crypto")] - { - let mut hasher = Sha384::new(); - hasher.update(data); - let result = hasher.finalize(); - hash[..48].copy_from_slice(&result[..]); - Ok(()) - } - - #[cfg(not(feature = "crypto"))] - { - // Fallback for demo purposes when crypto feature is not enabled - for (i, &byte) in data.iter().enumerate() { - hash[i % 48] ^= byte; - } - Ok(()) - } + let mut hasher = Sha384::new(); + hasher.update(data); + let result = hasher.finalize(); + hash[..48].copy_from_slice(&result[..]); + Ok(()) } fn init(&mut self, hash_algo: SpdmHashAlgoType, data: Option<&[u8]>) -> SpdmHashResult<()> { @@ -67,26 +52,20 @@ impl SpdmHash for Sha384Hash { } self.current_algo = hash_algo; - #[cfg(feature = "crypto")] - { - let mut hasher = Sha384::new(); - if let Some(initial_data) = data { - hasher.update(initial_data); - } - self.hasher = Some(hasher); + let mut hasher = Sha384::new(); + if let Some(initial_data) = data { + hasher.update(initial_data); } + self.hasher = Some(hasher); Ok(()) } fn update(&mut self, data: &[u8]) -> SpdmHashResult<()> { - #[cfg(feature = "crypto")] - { - if let Some(ref mut hasher) = self.hasher { - hasher.update(data); - } else { - return Err(SpdmHashError::PlatformError); - } + if let Some(ref mut hasher) = self.hasher { + hasher.update(data); + } else { + return Err(SpdmHashError::PlatformError); } Ok(()) @@ -97,31 +76,19 @@ impl SpdmHash for Sha384Hash { return Err(SpdmHashError::BufferTooSmall); } - #[cfg(feature = "crypto")] - { - if let Some(hasher) = self.hasher.take() { - let result = hasher.finalize(); - hash[..48].copy_from_slice(&result[..]); - } else { - return Err(SpdmHashError::PlatformError); - } - } - - #[cfg(not(feature = "crypto"))] - { - // Fallback for demo - hash[..48].fill(0x42); + if let Some(hasher) = self.hasher.take() { + let result = hasher.finalize(); + hash[..48].copy_from_slice(&result[..]); + } else { + return Err(SpdmHashError::PlatformError); } Ok(()) } fn reset(&mut self) { - #[cfg(feature = "crypto")] - { - if self.current_algo == SpdmHashAlgoType::SHA384 { - self.hasher = Some(Sha384::new()); - } + if self.current_algo == SpdmHashAlgoType::SHA384 { + self.hasher = Some(Sha384::new()); } } From e5c4b465afb5cac7410787d4fd0338c0e22e80c4 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 13 Feb 2026 12:19:22 +0100 Subject: [PATCH 44/86] add challenge Signed-off-by: leongross --- examples/platform/cert_store.rs | 33 +++- examples/spdm_requester.rs | 119 +++++++++++- src/cert_store.rs | 45 ++++- src/commands/algorithms/request.rs | 6 +- src/commands/algorithms/response.rs | 6 +- src/commands/capabilities/request.rs | 6 +- src/commands/certificate/request.rs | 9 +- src/commands/challenge/mod.rs | 149 +++++++++++++++ src/commands/challenge/request.rs | 178 ++++++++++++++++++ .../response.rs} | 62 ++---- src/commands/digests/request.rs | 10 +- src/commands/mod.rs | 2 +- src/commands/version/request.rs | 16 +- src/context.rs | 62 +++++- src/measurements/common.rs | 3 +- src/measurements/freeform_manifest.rs | 3 +- src/protocol/signature.rs | 10 +- src/state.rs | 2 - src/transcript.rs | 23 +++ 19 files changed, 658 insertions(+), 86 deletions(-) create mode 100644 src/commands/challenge/mod.rs create mode 100644 src/commands/challenge/request.rs rename src/commands/{challenge_auth_rsp.rs => challenge/response.rs} (88%) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index ccde36d..7c1c8a9 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -13,6 +13,7 @@ use p384::{ use zerocopy::FromBytes; use super::certs::{STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; +use spdm_lib::commands::challenge::MeasurementSummaryHashType; use spdm_lib::protocol::{ algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}, SpdmCertChainHeader, @@ -368,6 +369,8 @@ pub struct PeerSlot { /// KeyUsageMask[K], retrieved in `DIGESTS` response if the corresponding `MULTI_KEY_CONN_REQ` or `MULTI_KEY_CONN_RSP` is true. pub key_usage_mask: Option, + + pub requested_msh_type: Option, } impl Default for PeerSlot { @@ -378,6 +381,7 @@ impl Default for PeerSlot { keypair_id: None, certificate_info: None, key_usage_mask: None, + requested_msh_type: None, } } } @@ -472,7 +476,7 @@ impl PeerCertStore for ExamplePeerCertStore { } } - fn get_raw_chain(&self, slot_id: u8) -> Result<&[u8], CertStoreError> { + fn get_raw_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]> { let slot = self .peer_slots .get(slot_id as usize) @@ -643,4 +647,31 @@ impl PeerCertStore for ExamplePeerCertStore { slot.get_root_hash(hash_algo) .ok_or(CertStoreError::CertReadError) } + + fn get_requested_msh_type(&self, slot_id: u8) -> CertStoreResult { + self.peer_slots + .get(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_ref() + .ok_or(CertStoreError::PlatformError)? + .requested_msh_type + .clone() + .ok_or(CertStoreError::Undefined) + } + + fn set_requested_msh_type( + &mut self, + slot_id: u8, + msh_type: MeasurementSummaryHashType, + ) -> CertStoreResult<()> { + let slot = self + .peer_slots + .get_mut(slot_id as usize) + .ok_or(CertStoreError::InvalidSlotId(slot_id))? + .as_mut() + .ok_or(CertStoreError::PlatformError)?; + slot.requested_msh_type = Some(msh_type); + + Ok(()) + } } diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 0c09747..3d2a8c0 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -8,8 +8,13 @@ use std::net::TcpStream; use std::process; use der::{Decode, Encode}; +use p384::ecdsa::{Signature, VerifyingKey}; +use signature::hazmat::PrehashVerifier; use spdm_lib::codec::MessageBuf; use spdm_lib::commands::certificate::request::generate_get_certificate; +use spdm_lib::commands::challenge::{ + request::generate_challenge_request, MeasurementSummaryHashType, +}; use spdm_lib::context::SpdmContext; use spdm_lib::error::SpdmError; use spdm_lib::protocol::algorithms::{ @@ -17,7 +22,8 @@ use spdm_lib::protocol::algorithms::{ DheNamedGroup, KeySchedule, LocalDeviceAlgorithms, MeasurementHashAlgo, MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, }; -use spdm_lib::protocol::{version, BaseHashAlgoType}; +use spdm_lib::protocol::signature::NONCE_LEN; +use spdm_lib::protocol::{self, version, BaseHashAlgoType}; use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; // Import platform implementations - no duplicates! @@ -32,6 +38,7 @@ use spdm_lib::commands::digests::request::generate_digest_request; use spdm_lib::commands::version::{request::generate_get_version, VersionReqPayload}; use crate::platform::cert_store::ExamplePeerCertStore; +use spdm_lib::transcript::TranscriptContext; use x509_cert::Certificate; @@ -401,6 +408,76 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { } } + let mut nonce = [0u8; NONCE_LEN]; + spdm_context.get_random_bytes(&mut nonce).unwrap(); + + if config.verbose { + println!("CHALLENGE: Nonce = {:x?}", nonce); + } + + // GET_CHALLENGE + message_buffer.reset(); + generate_challenge_request( + &mut spdm_context, + &mut message_buffer, + 0, + MeasurementSummaryHashType::All, + nonce, + None, + ) + .unwrap(); + + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + + if config.verbose { + println!("CHALLENGE: {:?}", &message_buffer.message_data()); + } + + // CHALLENGE_AUTH + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + + if config.verbose { + println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data()); + } + + if let Some(store) = spdm_context.peer_cert_store() { + let hash_algo: BaseHashAlgoType = spdm_context + .connection_info() + .peer_algorithms() + .base_hash_algo + .try_into() + .unwrap(); + + let cert_chain = store.get_cert_chain(0, hash_algo).unwrap(); + + // get pub key from first cert in chain and verify signature of challenge auth + let (cert, _) = Certificate::from_der_partial(cert_chain).unwrap(); + let pub_key = VerifyingKey::from_sec1_bytes( + cert.tbs_certificate() + .subject_public_key_info() + .subject_public_key + .as_bytes() + .unwrap(), + ) + .unwrap(); + + // get all the remaining bytes from the message buffer as the signature + let sig_raw = message_buffer.data(96).unwrap(); + let sig = Signature::from_slice(sig_raw).unwrap(); + + if !verify_challenge_auth_signature(&mut spdm_context, pub_key, sig) { + eprintln!("CHALLENGE_AUTH signature verification failed"); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "CHALLENGE_AUTH signature verification failed", + )); + } + } + Ok(()) } @@ -610,3 +687,43 @@ fn verify_cert_chain(chain: &[Certificate]) -> bool { } true } + +/// Currently only p384 support required +/// Here we verify that the responder and we created the same m2 transcript and +/// that the signature is correct. +/// +/// The transcript hash will be retrieved from the context. +/// The signature will be verified using the public key from the responder's certificate chain (which we already verified). +pub fn verify_challenge_auth_signature( + ctx: &mut SpdmContext, + pubkey: VerifyingKey, + signature: Signature, +) -> bool { + use signature::Verifier; + + // since we verify the responder-generated signature, we have to use the same "responder-" context constant. + let sig_combined_context = protocol::signature::create_responder_signing_context( + ctx.connection_info().version_number(), + protocol::ReqRespCode::ChallengeAuth, + ) + .unwrap(); + + // Get the M1 transcript hash (which is the hash of messages A, B, C) and verify the signature over it. + let mut transcript_hash = [0u8; 48]; + ctx.transcript_hash(TranscriptContext::M1, &mut transcript_hash) + .unwrap(); + + // M denotes the message that is signed. M shall be the concatenation of the combined_spdm_prefix and unverified_message_hash. + let m = [sig_combined_context.as_slice(), &transcript_hash].concat(); + + dbg!( + &sig_combined_context, + &transcript_hash, + &m, + pubkey, + &signature + ); + + // pubkey.verify_prehash(&m, &signature).is_ok() + pubkey.verify(&m, &signature).is_ok() +} diff --git a/src/cert_store.rs b/src/cert_store.rs index 39eda19..f011c11 100644 --- a/src/cert_store.rs +++ b/src/cert_store.rs @@ -5,6 +5,8 @@ use crate::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH use crate::protocol::certs::{CertificateInfo, KeyUsageMask}; use crate::protocol::{BaseHashAlgoType, SpdmCertChainHeader}; +use crate::commands::challenge::MeasurementSummaryHashType; + pub const MAX_CERT_SLOTS_SUPPORTED: u8 = 2; pub const SPDM_CERT_CHAIN_METADATA_LEN: u16 = size_of::() as u16 + SHA384_HASH_SIZE as u16; @@ -18,6 +20,7 @@ pub enum CertStoreError { InvalidOffset, CertReadError, PlatformError, + Undefined, } pub type CertStoreResult = Result; @@ -376,8 +379,48 @@ pub trait PeerCertStore { /// * The digest of the Root Certificate if available fn get_root_hash(&self, slot_id: u8, hash_algo: BaseHashAlgoType) -> CertStoreResult<&[u8]>; - /// Get the raw cert chain, including the length header and root hash + /// Get a complete certificate chain consisting of one or more ASN.1 DER-encoded X.509 v3 certificates. fn get_raw_chain(&self, slot_id: u8) -> CertStoreResult<&[u8]>; + + /// In protocol message `CHALLENGE`, the requester can specify the `MeasurementSummaryHashType` + /// to indicate the type of measurement summary hash it wants the responder + /// to include in the `CHALLENGE_AUTH` response. + /// This is done by encoding the `MeasurementSummaryHashType` value in the `Param2` + /// field of the `CHALLENGE` request. + /// + /// The responder can then retrieve this value from the request and use it to determine + /// which type of measurement summary hash to include in its response. + /// + /// The requester can then retrieve it to parse the measurement summary hash + /// in the response correctly. + /// + /// # Arguments + /// * `slot_id` - The slot ID of the certificate chain. + /// + /// # Returns + /// * `Ok(MeasurementSummaryHashType)` - The `MeasurementSummaryHashType` value requested + /// by the peer in the `CHALLENGE` request, or `None` if not set. + /// - Ok(None) if the requester did not specify a `MeasurementSummaryHashType`, + /// + /// # Errors + /// * `CertStoreError::InvalidSlotId` - If the slot ID is out of range. + /// * `CertStoreError::PlatformError` - If there was a platform-specific error. + /// * `CertStoreError::Undefined` - If the `MeasurementSummaryHashType` has not been set by a previous `CHALLENGE` request. + fn get_requested_msh_type(&self, slot_id: u8) -> CertStoreResult; + + /// Set the `MeasurementSummaryHashType` requested by the peer in the `CHALLENGE` request. + /// + /// # Arguments + /// * `msh_type` - The `MeasurementSummaryHashType` value to set. + /// + /// # Returns + /// * `Ok(())` - If the value was stored successfully. + /// * `Err(CertStoreError)` - If there was an error storing the value. + fn set_requested_msh_type( + &mut self, + slot_id: u8, + msh_type: MeasurementSummaryHashType, + ) -> CertStoreResult<()>; } pub enum ReassemblyStatus { diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index cd99e9d..86395c8 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -129,7 +129,8 @@ pub(crate) fn handle_algorithms_response<'a>( .connection_info .set_state(crate::state::ConnectionState::AlgorithmsNegotiated); - Ok(()) + // ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::Vca)?; + ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::M1) } /// Generate the NEGOTIATE_ALGORITHMS request with all the contexts local information. @@ -265,7 +266,8 @@ pub fn generate_negotiate_algorithms_request<'a>( } } - ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca) + ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::M1) } #[cfg(test)] diff --git a/src/commands/algorithms/response.rs b/src/commands/algorithms/response.rs index 1799bd9..76586d6 100644 --- a/src/commands/algorithms/response.rs +++ b/src/commands/algorithms/response.rs @@ -160,7 +160,8 @@ fn process_negotiate_algorithms_request<'a>( .set_peer_algorithms(peer_algorithms); // Append NEGOTIATE_ALGORITHMS to the transcript VCA context - ctx.append_message_to_transcript(req_payload, TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_payload, TranscriptContext::Vca) + ctx.append_message_to_transcript(req_payload, TranscriptContext::M1) } fn generate_algorithms_response<'a>( @@ -253,7 +254,8 @@ fn generate_algorithms_response<'a>( .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::InvalidRequest, 0, None))?; // Add the ALGORITHMS to the transcript VCA context - ctx.append_message_to_transcript(rsp, TranscriptContext::Vca) + // ctx.append_message_to_transcript(rsp, TranscriptContext::Vca) + ctx.append_message_to_transcript(rsp, TranscriptContext::M1) } fn encode_alg_struct_table( diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 2e76eed..1988f73 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -89,7 +89,8 @@ pub(crate) fn handle_capabilities_response<'a>( .connection_info .set_state(crate::state::ConnectionState::AfterCapabilities); - ctx.append_message_to_transcript(resp, TranscriptContext::Vca) + // ctx.append_message_to_transcript(resp, TranscriptContext::Vca) + ctx.append_message_to_transcript(resp, TranscriptContext::M1) } /// Generate the GET_CAPABILITIES command with all the contexts information. @@ -148,7 +149,8 @@ fn generate_capabilities_request<'a>( .map_err(|_| (false, CommandError::BufferTooSmall))?; // Append response to VCA transcript - ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) + ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Generate the GET_CAPABILITIES command using the local capabilities from the context. diff --git a/src/commands/certificate/request.rs b/src/commands/certificate/request.rs index 25e08d7..5925350 100644 --- a/src/commands/certificate/request.rs +++ b/src/commands/certificate/request.rs @@ -98,10 +98,7 @@ pub fn generate_get_certificate<'a>( .push_data(payload_len) .map_err(|_| (false, CommandError::BufferTooSmall))?; - // Append to transcript - // (TODO: M1 is incorrect here, - // this is a requester functionality so this has to go into M2. - // Change this once the TranscriptManager has been refactored.) + // Message M1.B = Concatenate(GET_DIGESTS, DIGESTS, GET_CERTIFICATE, CERTIFICATE) ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } @@ -237,7 +234,5 @@ pub(crate) fn handle_certificate_response<'a>( process_certificate(ctx, resp_header, resp)?; // Append response to transcript (M1 context for certificate exchange) - ctx.append_message_to_transcript(resp, TranscriptContext::M1)?; - - Ok(()) + ctx.append_message_to_transcript(resp, TranscriptContext::M1) } diff --git a/src/commands/challenge/mod.rs b/src/commands/challenge/mod.rs new file mode 100644 index 0000000..889ab4d --- /dev/null +++ b/src/commands/challenge/mod.rs @@ -0,0 +1,149 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; +use bitfield::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub mod request; +pub mod response; + +pub(crate) use request::*; +pub(crate) use response::*; + +const NONCE_LEN: usize = 32; +const CONTEXT_LEN: usize = 8; +const OPAQUE_DATA_MAX: usize = 1024; + +/// 0x02..0xFE are reserved +#[derive(Debug, PartialEq, Clone)] +#[repr(u8)] +pub enum MeasurementSummaryHashType { + None = 0x00, + /// # Trusted Computing Base + /// + /// Set of all hardware, firmware, and/or software components that are critical + /// to its security, in the sense that bugs or vulnerabilities occurring inside + /// the TCB might jeopardize the security properties of the entire system. + Tcb = 0x01, + All = 0xFF, +} + +impl TryFrom for MeasurementSummaryHashType { + type Error = (); + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(Self::None), + 0x01 => Ok(Self::Tcb), + 0xFF => Ok(Self::All), + _ => Err(()), + } + } +} + +#[derive(FromBytes, IntoBytes, Immutable)] +#[repr(C)] +// TODO: check backwards compatibility of this struct with the original ChallengeReq struct +struct ChallengeReq { + /// Slot number of the Responder certificate chain that shall be used for authentication. + /// If the public key of the Responder was provisioned to the Requester in a + /// trusted environment, the value in this field shall be 0xFF ; otherwise it + /// shall be between 0 and 7 inclusive. + slot_id: u8, + + /// Shall be the type of measurement summary hash requested. + measurement_hash_type: u8, + + /// The Requester should choose a random value. + nonce: [u8; NONCE_LEN], + + /// The Requester can include application-specific information in Context. + /// The Requester should fill this field with zeros if it has no context to provide. + context: [u8; CONTEXT_LEN], +} +impl CommonCodec for ChallengeReq {} + +impl ChallengeReq { + /// Creates a new `CHALLENGE` request message. + /// + /// # Arguments + /// + /// * `slot_id` - Slot number (0..=7) of the Responder certificate chain to use for + /// authentication, or `0xFF` if the public key was provisioned in a trusted environment. + /// Stored as a bitmask with the corresponding bit set. + /// * `measurement_hash_type` - The type of measurement summary hash requested from the + /// Responder. + /// * `nonce` - A random 32-byte value chosen by the Requester for freshness. + /// * `context` - Optional 8-byte application-specific context. Defaults to all zeros when + /// `None`. + pub fn new( + slot_id: u8, + measurement_hash_type: MeasurementSummaryHashType, + nonce: [u8; NONCE_LEN], + context: Option<[u8; CONTEXT_LEN]>, + ) -> Self { + Self { + slot_id, + measurement_hash_type: measurement_hash_type as u8, + nonce, + context: context.unwrap_or([0; CONTEXT_LEN]), + } + } +} + +#[derive(FromBytes, IntoBytes, Immutable)] +#[repr(C)] +struct ChallengeAuthRspBase { + challenge_auth_attr: ChallengeAuthAttr, + slot_mask: u8, + + /// Shall be either the hash of the certificate chain if the public key of the + /// Responder was provisioned to the Requester in a trusted environment, the + /// public key used for authentication. + /// + /// The Requester can use this value to check that the certificate chain or + /// public key matches the one requested. + cert_chain_hash: [u8; SHA384_HASH_SIZE], + + /// Shall be the Responder-selected random value + nonce: [u8; NONCE_LEN], + // Followed by: + // - MeasurementSummaryHash + // - OpaqueDataLength + // - OpaqueData + // - RequesterContext + // - Signature +} + +impl CommonCodec for ChallengeAuthRspBase {} + +impl ChallengeAuthRspBase { + /// Creates a new `ChallengeAuthRspBase` with the specified slot ID. + /// + /// # Arguments + /// + /// * `slot_id` - The slot ID Bit to be set in the response. + fn new(slot_id: u8) -> Self { + Self { + challenge_auth_attr: ChallengeAuthAttr(slot_id), + slot_mask: 1 << slot_id, + cert_chain_hash: [0; SHA384_HASH_SIZE], + nonce: [0; NONCE_LEN], + } + } +} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable)] + #[repr(C)] + struct ChallengeAuthAttr(u8); + impl Debug; + u8; + /// Shall contain the `SlotID` in the Param1 `field` of the corresponding `CHALLENGE` request. + /// If the Responder's public key was provisioned to the Requester previously, this field shall + /// be 0xF. The Requester can use this value to check that the certificate matched what was requested. + pub slot_id, set_slot_id: 3, 0; + reserved, _: 7, 4; +} + +pub(crate) fn challenge_auth_sig_verify() {} diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs new file mode 100644 index 0000000..f3b843a --- /dev/null +++ b/src/commands/challenge/request.rs @@ -0,0 +1,178 @@ +use crate::cert_store::{CertStoreError, CertStoreResult, PeerCertStore, SpdmCertStore}; +// Licensed under the Apache-2.0 license +use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::commands::challenge::{ + ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType, CONTEXT_LEN, NONCE_LEN, + OPAQUE_DATA_MAX, +}; +use crate::context::SpdmContext; +use crate::error::{CommandError, CommandResult, PlatformError}; +use crate::protocol::*; +use crate::state::ConnectionState; +use crate::transcript::TranscriptContext; + +/// Generates an SPDM `CHALLENGE` request message. +/// +/// Constructs a [`ChallengeReq`] with the given parameters and encodes it into `buf` +/// as an SPDM message, using the negotiated version from the connection state. +/// +/// # Arguments +/// +/// * `ctx` - The SPDM context holding connection state (used to obtain the negotiated version). +/// * `buf` - The output buffer into which the encoded request message is written. +/// * `slot_id` - Slot number (`0..=7`) of the Responder certificate chain to use for +/// authentication, or `0xFF` if the public key was provisioned in a trusted environment. +/// * `measurement_hash_type` - The type of measurement summary hash requested from the +/// Responder (`None`, `Tcb`, or `All`). +/// * `nonce` - A 32-byte random value chosen by the Requester for freshness. +/// * `context` - Optional 8-byte application-specific context. Defaults to all zeros when +/// `None`. +/// +/// # Errors +/// +/// Returns a [`CommandError`] if encoding the header or request body into the buffer fails. +pub fn generate_challenge_request<'a>( + ctx: &mut SpdmContext<'a>, + message_buffer: &mut MessageBuf<'a>, + slot_id: u8, + measurement_hash_type: MeasurementSummaryHashType, + nonce: [u8; NONCE_LEN], + context: Option<[u8; CONTEXT_LEN]>, +) -> CommandResult<()> { + SpdmMsgHdr::new( + ctx.state.connection_info.version_number(), + ReqRespCode::Challenge, + ) + .encode(message_buffer) + .map_err(|e| (false, CommandError::Codec(e)))?; + + ChallengeReq::new(slot_id, measurement_hash_type.clone(), nonce, context) + .encode(message_buffer) + .map_err(|e| (false, CommandError::Codec(e)))?; + + ctx.state + .peer_cert_store + .as_mut() + .unwrap() + .set_requested_msh_type(slot_id, measurement_hash_type.clone()) + .map_err(|e| (false, CommandError::CertStore(e)))?; + + // Message M1.C = Concatenate(CHALLENGE, CHALLENGE_AUTH without signature) + ctx.append_message_to_transcript(message_buffer, TranscriptContext::M1) +} + +/// Handle the challenge response and apppend the message to the transcript context. +/// See [crate::transcript::TranscriptContext::M1] for details on the transcript context used. +/// +/// # Warning +/// Contrary to the other messages, `CHALLENGE_AUTH` is **NOT** entirely parsed here. +/// The variable-length field `Signature` has to be parsed in the application. This has two reasons: +/// 1. The generate the transcript hash, the entire message, **except the signature!** +/// has to be appended to the transcript context before signature verification, as required by SPDM 1.2 and later. +/// 2. The signature verification has to be done in the application, as it requires +/// access to the public key from the responder's certificate chain (which we already verified) and the transcript hash. +pub fn handle_challenge_auth_response<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + resp_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + return Err((true, CommandError::UnsupportedRequest)); + } + + if spdm_hdr.version().unwrap() != ctx.connection_info().version_number() { + return Err((true, CommandError::InvalidState)); + } + + let challenge_auth_resp_base: ChallengeAuthRspBase = + ChallengeAuthRspBase::decode(resp_payload).map_err(|e| (true, CommandError::Codec(e)))?; + + // Parse the variable length fields: + // - MeasurementSummaryHash + // - OpaqueDataLength + // - OpaqueData + // - RequesterContext + // - Signature + + // - MeasurementSummaryHash + // If the Responder does not support measurements ( MEAS_CAP=00b in its CAPABILITIES response) + // or if the requested Param2 = 0x0 , this field shall be absent. + + let param2 = ctx + .state + .peer_cert_store + .as_mut() + .unwrap() + .get_requested_msh_type(0) + .map_err(|e| (true, CommandError::CertStore(e)))?; + + let hash_size_bytes = ctx.hash.algo().hash_size(); + let mut hash = [0u8; SHA384_HASH_SIZE]; + + if challenge_auth_resp_base.slot_mask != 0 + && ctx.connection_info().peer_capabilities().flags.meas_cap() != 0 + { + // If the Responder supports both raw bit stream and digest representations + // for a given measurement index, the Responder shall use the digest form. + match param2 { + MeasurementSummaryHashType::None => {} + + // The combined hash of measurements of all measurable components + // considered to be in the TCB required to generate this response + MeasurementSummaryHashType::Tcb | MeasurementSummaryHashType::All => { + hash[..hash_size_bytes].copy_from_slice( + resp_payload + .data(hash_size_bytes) + .map_err(|e| (true, CommandError::Codec(e)))?, + ); + + resp_payload + .pull_data(hash_size_bytes) + .map_err(|e| (true, CommandError::Codec(e)))?; + } + } + } + + let opaque_data_size = { + let opaque_data_slice = resp_payload + .data(2) + .map_err(|e| (true, CommandError::Codec(e)))?; + u16::from_le_bytes([opaque_data_slice[0], opaque_data_slice[1]]) + }; + + resp_payload + .pull_data(2) + .map_err(|e| (true, CommandError::Codec(e)))?; + + // The value should not be greater than 1024 bytes + // Opaque data size 64939 exceeds maximum allowed 1024 + if opaque_data_size > OPAQUE_DATA_MAX as u16 { + return Err((true, CommandError::BufferTooSmall)); + } + + // The Responder can include Responder-specific information and/or information + // that its transport defines. If present, this field shall conform to the selected + // opaque data format in [OtherParamsSelection]. + if opaque_data_size > 0 { + let _opaque_data = resp_payload + .data(opaque_data_size as usize) + .map_err(|e| (true, CommandError::Codec(e)))?; + resp_payload + .pull_data(opaque_data_size as usize) + .map_err(|e| (true, CommandError::Codec(e)))?; + } + + // This field shall be identical to the Context field of the corresponding request message. + // TODO: compare it to the context we sent. + // See: src/protocol/common.rs [RequesterContext] + let _requester_context = resp_payload + .data(8) + .map_err(|e| (true, CommandError::Codec(e)))?; + + resp_payload + .pull_data(8) + .map_err(|e| (true, CommandError::Codec(e)))?; + + // Append the entire message (excluding the signature) to the transcript before signature verification, as required by SPDM 1.2 and later. + ctx.append_message_to_transcript(resp_payload, TranscriptContext::M1) +} diff --git a/src/commands/challenge_auth_rsp.rs b/src/commands/challenge/response.rs similarity index 88% rename from src/commands/challenge_auth_rsp.rs rename to src/commands/challenge/response.rs index c343454..0e37446 100644 --- a/src/commands/challenge_auth_rsp.rs +++ b/src/commands/challenge/response.rs @@ -2,6 +2,7 @@ use crate::cert_store::MAX_CERT_SLOTS_SUPPORTED; use crate::codec::{Codec, CommonCodec, MessageBuf}; use crate::commands::algorithms::selected_measurement_specification; +use crate::commands::challenge::{ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType}; use crate::commands::digests::compute_cert_chain_hash; use crate::commands::error_rsp::ErrorCode; use crate::context::SpdmContext; @@ -13,51 +14,11 @@ use crate::transcript::TranscriptContext; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; -#[derive(FromBytes, IntoBytes, Immutable)] -#[repr(C)] -struct ChallengeReqBase { - slot_id: u8, - measurement_hash_type: u8, - nonce: [u8; NONCE_LEN], -} -impl CommonCodec for ChallengeReqBase {} - -#[derive(FromBytes, IntoBytes, Immutable)] -#[repr(C)] -struct ChallengeAuthRspBase { - challenge_auth_attr: ChallengeAuthAttr, - slot_mask: u8, - cert_chain_hash: [u8; SHA384_HASH_SIZE], - nonce: [u8; NONCE_LEN], -} -impl CommonCodec for ChallengeAuthRspBase {} - -impl ChallengeAuthRspBase { - fn new(slot_id: u8) -> Self { - Self { - challenge_auth_attr: ChallengeAuthAttr(slot_id), - slot_mask: 1 << slot_id, - cert_chain_hash: [0; SHA384_HASH_SIZE], - nonce: [0; NONCE_LEN], - } - } -} - -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable)] - #[repr(C)] - struct ChallengeAuthAttr(u8); - impl Debug; - u8; - pub slot_id, set_slot_id: 3, 0; - reserved, _: 7, 4; -} - fn process_challenge<'a>( ctx: &mut SpdmContext<'a>, spdm_hdr: SpdmMsgHdr, req_payload: &mut MessageBuf<'a>, -) -> CommandResult<(u8, u8, Option)> { +) -> CommandResult<(u8, MeasurementSummaryHashType, Option)> { // Validate the version let connection_version = ctx.state.connection_info.version_number(); if spdm_hdr.version().ok() != Some(connection_version) { @@ -69,7 +30,7 @@ fn process_challenge<'a>( .map_err(|_| ctx.generate_error_response(req_payload, ErrorCode::Unspecified, 0, None))?; // Decode the CHALLENGE request payload - let challenge_req = ChallengeReqBase::decode(req_payload).map_err(|_| { + let challenge_req = ChallengeReq::decode(req_payload).map_err(|_| { ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) })?; @@ -104,11 +65,12 @@ fn process_challenge<'a>( // Append the CHALLENGE request to the M1 transcript ctx.append_message_to_transcript(req_payload, TranscriptContext::M1)?; - Ok(( - challenge_req.slot_id, - challenge_req.measurement_hash_type, - requester_context, - )) + let meas_hash_type = + MeasurementSummaryHashType::try_from(challenge_req.measurement_hash_type).map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?; + + Ok((challenge_req.slot_id, meas_hash_type, requester_context)) } fn encode_m1_signature<'a>( @@ -205,7 +167,7 @@ fn encode_challenge_auth_rsp_base<'a>( fn encode_measurement_summary_hash<'a>( ctx: &mut SpdmContext<'a>, asym_algo: AsymAlgo, - meas_summary_hash_type: u8, + meas_summary_hash_type: MeasurementSummaryHashType, rsp: &mut MessageBuf<'a>, ) -> CommandResult { let mut meas_summary_hash = [0u8; SHA384_HASH_SIZE]; @@ -245,7 +207,7 @@ fn encode_opaque_data(rsp: &mut MessageBuf<'_>) -> CommandResult { fn generate_challenge_auth_response<'a>( ctx: &mut SpdmContext<'a>, slot_id: u8, - meas_summary_hash_type: u8, + meas_summary_hash_type: MeasurementSummaryHashType, requester_context: Option, rsp: &mut MessageBuf<'a>, ) -> CommandResult<()> { @@ -266,7 +228,7 @@ fn generate_challenge_auth_response<'a>( payload_len += encode_challenge_auth_rsp_base(ctx, slot_id, asym_algo, rsp)?; // Get the measurement summary hash - if meas_summary_hash_type != 0 { + if meas_summary_hash_type != MeasurementSummaryHashType::None { payload_len += encode_measurement_summary_hash(ctx, asym_algo, meas_summary_hash_type, rsp)?; } diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs index 87ba5e9..f62288f 100644 --- a/src/commands/digests/request.rs +++ b/src/commands/digests/request.rs @@ -11,6 +11,8 @@ use zerocopy::FromBytes; use super::*; +/// Generate a GET_DIGESTS request and append it to the transcript context. +/// See [crate::transcript::TranscriptContext::M1] for details on the transcript context used. pub fn generate_digest_request( ctx: &mut SpdmContext, message_buffer: &mut MessageBuf, @@ -27,9 +29,12 @@ pub fn generate_digest_request( .encode(message_buffer) .map_err(|e| (false, CommandError::Codec(e)))?; - ctx.append_message_to_transcript(message_buffer, crate::transcript::TranscriptContext::L1) + // Message M1.B = Concatenate(GET_DIGESTS, DIGESTS, GET_CERTIFICATE, CERTIFICATE) + ctx.append_message_to_transcript(message_buffer, crate::transcript::TranscriptContext::M1) } +/// Process a DIGESTS response, updating the peer certificate store and transcript context accordingly. +/// See [crate::transcript::TranscriptContext::M1] for details on the transcript context used. pub(crate) fn handle_digests_response<'a>( ctx: &mut SpdmContext<'a>, spdm_hdr: SpdmMsgHdr, @@ -158,7 +163,8 @@ pub(crate) fn handle_digests_response<'a>( .set_state(ConnectionState::AfterDigest); } - ctx.append_message_to_transcript(resp_payload, TranscriptContext::L1) + // Message M1.B = Concatenate(GET_DIGESTS, DIGESTS, GET_CERTIFICATE, CERTIFICATE) + ctx.append_message_to_transcript(resp_payload, TranscriptContext::M1) } #[cfg(test)] diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c935d11..838dfae 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -3,7 +3,7 @@ pub mod algorithms; pub mod capabilities; pub mod certificate; -pub mod challenge_auth_rsp; +pub mod challenge; pub mod chunk_get_rsp; pub mod digests; pub mod error_rsp; diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 66178bf..5884e21 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -2,6 +2,7 @@ use crate::{ codec::{Codec, MessageBuf}, + commands::capabilities::req_flag_compatible, context::SpdmContext, error::{CommandError, CommandResult}, protocol::SpdmMsgHdr, @@ -14,7 +15,8 @@ use crate::commands::version::{VersionNumberEntry, VersionReqPayload, VersionRes use crate::protocol::SpdmVersion; -/// Generate the GET_VERSION command with Header and payload. +/// Generate the GET_VERSION command with Header and payload and append it to the transcript context +/// See [crate::transcript::TranscriptContext::Vca] and [crate::transcript::TranscriptContext::M1] for details on the transcript context used. pub fn generate_get_version<'a>( ctx: &mut SpdmContext<'a>, req_buf: &mut MessageBuf<'a>, @@ -35,7 +37,8 @@ pub fn generate_get_version<'a>( .push_data(len) .map_err(|_| (false, CommandError::BufferTooSmall))?; - ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) + ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Requester function for processing a VERSION response @@ -95,6 +98,8 @@ fn process_version<'a>( } /// Requester function handling the parsing of the VERSION response sent by the Responder. +/// Updates the context with the selected version and appends the response to the transcript context. +/// See [crate::transcript::TranscriptContext::Vca] for details on the transcript context used. pub(crate) fn handle_version_response<'a>( ctx: &mut SpdmContext<'a>, resp_header: SpdmMsgHdr, @@ -107,15 +112,14 @@ pub(crate) fn handle_version_response<'a>( Err(ctx.generate_error_response(resp, ErrorCode::InvalidResponseCode, 0, None))?; } - // Process VERSION response and set context information process_version(ctx, resp_header, resp)?; - // Append to transcript - ctx.append_message_to_transcript(resp, TranscriptContext::Vca)?; ctx.state .connection_info .set_state(ConnectionState::AfterVersion); - Ok(()) + + // ctx.append_message_to_transcript(resp, TranscriptContext::Vca) + ctx.append_message_to_transcript(resp, TranscriptContext::M1) } // tests diff --git a/src/context.rs b/src/context.rs index decc0e4..301765c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,19 +5,19 @@ use crate::cert_store::*; use crate::chunk_ctx::LargeResponseCtx; use crate::codec::{Codec, MessageBuf}; use crate::commands::capabilities::handle_capabilities_response; +use crate::commands::challenge::handle_challenge_auth_response; use crate::commands::digests::{handle_digests_response, handle_get_digests}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ - algorithms, capabilities, certificate, challenge_auth_rsp, chunk_get_rsp, measurements_rsp, - version, + algorithms, capabilities, certificate, challenge, chunk_get_rsp, measurements_rsp, version, }; use crate::error::*; use crate::measurements::common::SpdmMeasurements; use crate::platform::evidence::SpdmEvidence; use crate::platform::hash::SpdmHash; -use crate::platform::rng::SpdmRng; +use crate::platform::rng::{SpdmRng, SpdmRngResult}; use crate::platform::transport::SpdmTransport; use crate::protocol::algorithms::*; use crate::protocol::common::{ReqRespCode, SpdmMsgHdr}; @@ -180,9 +180,7 @@ impl<'a> SpdmContext<'a> { ReqRespCode::GetCertificate => { certificate::handle_get_certificate(self, req_msg_header, req)? } - ReqRespCode::Challenge => { - challenge_auth_rsp::handle_challenge(self, req_msg_header, req)? - } + ReqRespCode::Challenge => challenge::handle_challenge(self, req_msg_header, req)?, ReqRespCode::GetMeasurements => { measurements_rsp::handle_get_measurements(self, req_msg_header, req)? } @@ -225,6 +223,9 @@ impl<'a> SpdmContext<'a> { ReqRespCode::Certificate => { certificate::request::handle_certificate_response(self, resp_msg_header, resp)? } + ReqRespCode::ChallengeAuth => { + handle_challenge_auth_response(self, resp_msg_header, resp)? + } _ => Err((false, CommandError::UnsupportedResponse))?, } @@ -347,4 +348,53 @@ impl<'a> SpdmContext<'a> { pub fn peer_cert_store(&self) -> Option<&dyn PeerCertStore> { self.state.peer_cert_store.as_deref() } + + /// To safeguard the user-facing API, we prohibit the retrieval of hashes unless the context is in a valid state. + /// These states are: + /// - [`ConnectionState::AfterCertificate`] for the M1 transcript context + /// - [`ConnectionState::Authenticated`] for the L2 transcript context + /// + /// # Arguments + /// - `transcript_context`: The transcript context for which the hash is being requested. + pub fn transcript_hash( + &mut self, + transcript_context: TranscriptContext, + hash: &mut [u8], + ) -> CommandResult<()> { + match transcript_context { + TranscriptContext::M1 => { + if self.state.connection_info.state() < ConnectionState::AfterCertificate { + return Err((false, CommandError::InvalidState)); + } + } + TranscriptContext::L1 => { + if self.state.connection_info.state() < ConnectionState::Authenticated { + return Err((false, CommandError::InvalidState)); + } + } + TranscriptContext::Vca => { + return Err((false, CommandError::InvalidState)); + } + } + + let mut hash_out_max = [0u8; 48]; + self.transcript_mgr + .hash(transcript_context, &mut hash_out_max) + .map_err(|e| (false, CommandError::Transcript(e)))?; + + if hash.len() > hash_out_max.len() { + return Err((false, CommandError::BufferTooSmall)); + } + + hash.copy_from_slice(&hash_out_max[..hash.len()]); + Ok(()) + } + + /// Expose the `SystemRng` function `get_random_bytes` to context. + /// + /// # Arguments + /// - `dest`: Destination buffer that holds the random bytes. + pub fn get_random_bytes(&mut self, dest: &mut [u8]) -> SpdmRngResult<()> { + self.rng.get_random_bytes(dest) + } } diff --git a/src/measurements/common.rs b/src/measurements/common.rs index 9658f1d..6e46237 100644 --- a/src/measurements/common.rs +++ b/src/measurements/common.rs @@ -2,6 +2,7 @@ use crate::measurements::freeform_manifest::FreeformManifest; use crate::platform::evidence::{SpdmEvidence, SpdmEvidenceError}; use crate::platform::hash::{SpdmHash, SpdmHashError}; +use crate::commands::challenge::MeasurementSummaryHashType; use crate::protocol::{algorithms::AsymAlgo, MeasurementSpecification, SHA384_HASH_SIZE}; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -130,7 +131,7 @@ impl SpdmMeasurements { evidence: &dyn SpdmEvidence, hash_ctx: &mut dyn SpdmHash, asym_algo: AsymAlgo, - measurement_summary_hash_type: u8, + measurement_summary_hash_type: MeasurementSummaryHashType, hash: &mut [u8; SHA384_HASH_SIZE], ) -> MeasurementsResult<()> { match self { diff --git a/src/measurements/freeform_manifest.rs b/src/measurements/freeform_manifest.rs index dece0b1..6e685ea 100644 --- a/src/measurements/freeform_manifest.rs +++ b/src/measurements/freeform_manifest.rs @@ -1,5 +1,6 @@ // Licensed under the Apache-2.0 license +use crate::commands::challenge::MeasurementSummaryHashType; use crate::measurements::common::{ DmtfMeasurementBlockMetadata, MeasurementValueType, MeasurementsError, MeasurementsResult, SPDM_MEASUREMENT_MANIFEST_INDEX, @@ -91,7 +92,7 @@ impl FreeformManifest { evidence: &dyn SpdmEvidence, hash_ctx: &mut dyn SpdmHash, asym_algo: AsymAlgo, - _measurement_summary_hash_type: u8, + _measurement_summary_hash_type: MeasurementSummaryHashType, hash: &mut [u8; SHA384_HASH_SIZE], ) -> MeasurementsResult<()> { self.refresh_measurement_record(evidence, asym_algo)?; diff --git a/src/protocol/signature.rs b/src/protocol/signature.rs index 22641fa..5981b24 100644 --- a/src/protocol/signature.rs +++ b/src/protocol/signature.rs @@ -1,5 +1,6 @@ // Licensed under the Apache-2.0 license +use crate::context::SpdmContext; use crate::platform::hash::{SpdmHash, SpdmHashError}; use crate::protocol::*; @@ -21,9 +22,16 @@ pub enum SignCtxError { Platform(SpdmHashError), } +#[derive(Debug, PartialEq)] +pub enum SignatureError { + InvalidSignature, +} + pub type SignatureCtxResult = Result; -pub(crate) fn create_responder_signing_context( +pub type SignatureResult = Result; + +pub fn create_responder_signing_context( spdm_version: SpdmVersion, opcode: ReqRespCode, ) -> SignatureCtxResult<[u8; SPDM_SIGNING_CONTEXT_LEN]> { diff --git a/src/state.rs b/src/state.rs index 479b9b1..8baf5c1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -70,7 +70,6 @@ impl ConnectionInfo { self.peer_capabilities = peer_capabilities; } - #[allow(dead_code)] pub fn peer_capabilities(&self) -> DeviceCapabilities { self.peer_capabilities } @@ -83,7 +82,6 @@ impl ConnectionInfo { &self.peer_algorithms } - #[allow(dead_code)] pub(crate) fn set_multi_key_conn_rsp(&mut self, multi_key_conn_rsp: bool) { self.multi_key_conn_rsp = multi_key_conn_rsp; } diff --git a/src/transcript.rs b/src/transcript.rs index 9c82bb3..0fa7f8a 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -48,8 +48,31 @@ impl VcaBuffer { } pub enum TranscriptContext { + /// # VCA + /// VCA: Version, Capabilities, and Algorithms transcript context, containing + /// messages related to version negotiation, capabilities exchange, and algorithm negotiation. + /// VCA = Concatenate (GET_VERSION, VERSION, GET_CAPABILITIES, CAPABILITIES, NEGOTIATE_ALGORITHMS, ALGORITHMS) + /// + /// VCA is the same as message A in the M1 transcript. Vca, + + /// # M1 + /// M1=Concatenate(A, B, C) + /// where + /// - A: VCA = Concatenate (GET_VERSION, VERSION, GET_CAPABILITIES, CAPABILITIES, + /// NEGOTIATE_ALGORITHMS, ALGORITHMS) + /// - B: Certificate Exchange = Concatenate (GET_DIGESTS, DIGESTS, GET_CERTIFICATE + /// CERTIFICATE) + /// - C = Concatenate (CHALLENGE, CHALLENGE_AUTH excluding signature) M1, + + /// # L1 + /// SPDM Version 1.2 and later: L1 = Concatenate(A, M) where + /// - A: VCA = Concatenate (GET_VERSION, VERSION, GET_CAPABILITIES, NEGOTIATE_ALGORITHMS, ALGORITHMS) + /// - M: Concatenate (GET_MEASUREMENTS, MEASUREMENTS without signature) + /// + /// SPDM Version 1.0 and 1.1: L1 = Concatenate(M) where + /// - M: Concatenate (GET_MEASUREMENTS, MEASUREMENTS without signature) L1, } From 013bba5b36ef077dd20b1ea28a0915d771abaed4 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 24 Feb 2026 19:40:59 +0100 Subject: [PATCH 45/86] Fix transcript generation and challenge_auth verification --- examples/spdm_requester.rs | 43 ++++++++++++---------------- src/commands/algorithms/request.rs | 8 +++--- src/commands/capabilities/request.rs | 7 ++--- src/commands/challenge/mod.rs | 6 ++-- src/commands/challenge/request.rs | 15 +++++++++- src/commands/version/request.rs | 8 +++--- src/lib.rs | 2 +- src/protocol/signature.rs | 1 + src/transcript.rs | 1 + 9 files changed, 49 insertions(+), 42 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 3d2a8c0..c8df717 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -9,7 +9,6 @@ use std::process; use der::{Decode, Encode}; use p384::ecdsa::{Signature, VerifyingKey}; -use signature::hazmat::PrehashVerifier; use spdm_lib::codec::MessageBuf; use spdm_lib::commands::certificate::request::generate_get_certificate; use spdm_lib::commands::challenge::{ @@ -369,6 +368,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { } } println!("sucessfully retrieved peer cert chain"); + let mut peer_leaf_cert = None; if let Some(store) = spdm_context.peer_cert_store() { let hash_algo: BaseHashAlgoType = spdm_context .connection_info() @@ -406,6 +406,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { assert!(verify_cert_chain(&certs)); println!("Cert chain signatures successfully verified!"); } + peer_leaf_cert = certs.last().cloned(); } let mut nonce = [0u8; NONCE_LEN]; @@ -444,18 +445,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data()); } - if let Some(store) = spdm_context.peer_cert_store() { - let hash_algo: BaseHashAlgoType = spdm_context - .connection_info() - .peer_algorithms() - .base_hash_algo - .try_into() - .unwrap(); - - let cert_chain = store.get_cert_chain(0, hash_algo).unwrap(); - - // get pub key from first cert in chain and verify signature of challenge auth - let (cert, _) = Certificate::from_der_partial(cert_chain).unwrap(); + if let Some(cert) = peer_leaf_cert { let pub_key = VerifyingKey::from_sec1_bytes( cert.tbs_certificate() .subject_public_key_info() @@ -468,14 +458,18 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { // get all the remaining bytes from the message buffer as the signature let sig_raw = message_buffer.data(96).unwrap(); let sig = Signature::from_slice(sig_raw).unwrap(); + if config.verbose { + println!("signature: {sig}"); + } - if !verify_challenge_auth_signature(&mut spdm_context, pub_key, sig) { + if !verify_challenge_auth_signature(&mut spdm_context, pub_key, sig, config) { eprintln!("CHALLENGE_AUTH signature verification failed"); return Err(std::io::Error::new( std::io::ErrorKind::Other, "CHALLENGE_AUTH signature verification failed", )); } + println!("CHALLENGE_AUTH signature verification successfull"); } Ok(()) @@ -694,10 +688,11 @@ fn verify_cert_chain(chain: &[Certificate]) -> bool { /// /// The transcript hash will be retrieved from the context. /// The signature will be verified using the public key from the responder's certificate chain (which we already verified). -pub fn verify_challenge_auth_signature( +fn verify_challenge_auth_signature( ctx: &mut SpdmContext, pubkey: VerifyingKey, signature: Signature, + config: &RequesterConfig, ) -> bool { use signature::Verifier; @@ -707,23 +702,23 @@ pub fn verify_challenge_auth_signature( protocol::ReqRespCode::ChallengeAuth, ) .unwrap(); + if config.verbose { + println!( + "comb_ctx string: '{}'", + String::from_utf8_lossy(&sig_combined_context) + ); + } // Get the M1 transcript hash (which is the hash of messages A, B, C) and verify the signature over it. let mut transcript_hash = [0u8; 48]; ctx.transcript_hash(TranscriptContext::M1, &mut transcript_hash) .unwrap(); + if config.verbose { + println!("M1/2 hash: {transcript_hash:02x?}"); + } // M denotes the message that is signed. M shall be the concatenation of the combined_spdm_prefix and unverified_message_hash. let m = [sig_combined_context.as_slice(), &transcript_hash].concat(); - dbg!( - &sig_combined_context, - &transcript_hash, - &m, - pubkey, - &signature - ); - - // pubkey.verify_prehash(&m, &signature).is_ok() pubkey.verify(&m, &signature).is_ok() } diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index 86395c8..fce5989 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -129,8 +129,8 @@ pub(crate) fn handle_algorithms_response<'a>( .connection_info .set_state(crate::state::ConnectionState::AlgorithmsNegotiated); - // ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::Vca)?; - ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::M1) + ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::Vca) + // ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::M1) } /// Generate the NEGOTIATE_ALGORITHMS request with all the contexts local information. @@ -266,8 +266,8 @@ pub fn generate_negotiate_algorithms_request<'a>( } } - // ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca) - ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::M1) + ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::M1) } #[cfg(test)] diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 1988f73..118cda9 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -89,8 +89,7 @@ pub(crate) fn handle_capabilities_response<'a>( .connection_info .set_state(crate::state::ConnectionState::AfterCapabilities); - // ctx.append_message_to_transcript(resp, TranscriptContext::Vca) - ctx.append_message_to_transcript(resp, TranscriptContext::M1) + ctx.append_message_to_transcript(resp, TranscriptContext::Vca) } /// Generate the GET_CAPABILITIES command with all the contexts information. @@ -149,8 +148,8 @@ fn generate_capabilities_request<'a>( .map_err(|_| (false, CommandError::BufferTooSmall))?; // Append response to VCA transcript - // ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) - ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) + ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Generate the GET_CAPABILITIES command using the local capabilities from the context. diff --git a/src/commands/challenge/mod.rs b/src/commands/challenge/mod.rs index 889ab4d..650fd83 100644 --- a/src/commands/challenge/mod.rs +++ b/src/commands/challenge/mod.rs @@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license -use crate::codec::{Codec, CommonCodec, MessageBuf}; -use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; +use crate::codec::CommonCodec; +use crate::protocol::SHA384_HASH_SIZE; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -145,5 +145,3 @@ bitfield! { pub slot_id, set_slot_id: 3, 0; reserved, _: 7, 4; } - -pub(crate) fn challenge_auth_sig_verify() {} diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs index f3b843a..4766137 100644 --- a/src/commands/challenge/request.rs +++ b/src/commands/challenge/request.rs @@ -173,6 +173,19 @@ pub fn handle_challenge_auth_response<'a>( .pull_data(8) .map_err(|e| (true, CommandError::Codec(e)))?; + // We have to use this ugly hack to bring the message buffer into the right form to exclude the signature. + // This message buffer thing is totally fucked up... + // Come on, why do you have to call multiple badly named functions to remove data? + // And then there `message_data`, `data`, `total_message`, ... are you kidding me? + let tail = resp_payload.data_len(); + resp_payload + .trim(0) + .map_err(|e| (true, CommandError::Codec(e)))?; // Append the entire message (excluding the signature) to the transcript before signature verification, as required by SPDM 1.2 and later. - ctx.append_message_to_transcript(resp_payload, TranscriptContext::M1) + ctx.append_message_to_transcript(resp_payload, TranscriptContext::M1)?; + resp_payload + .trim(tail - resp_payload.data_len()) + .map_err(|e| (true, CommandError::Codec(e)))?; + + Ok(()) } diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 5884e21..7621838 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -37,8 +37,8 @@ pub fn generate_get_version<'a>( .push_data(len) .map_err(|_| (false, CommandError::BufferTooSmall))?; - // ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) - ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) + ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) + // ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Requester function for processing a VERSION response @@ -118,8 +118,8 @@ pub(crate) fn handle_version_response<'a>( .connection_info .set_state(ConnectionState::AfterVersion); - // ctx.append_message_to_transcript(resp, TranscriptContext::Vca) - ctx.append_message_to_transcript(resp, TranscriptContext::M1) + ctx.append_message_to_transcript(resp, TranscriptContext::Vca) + // ctx.append_message_to_transcript(resp, TranscriptContext::M1) } // tests diff --git a/src/lib.rs b/src/lib.rs index daf45c5..8b68fc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ // Licensed under the Apache-2.0 license -#![cfg_attr(not(test), no_std)] +// #![cfg_attr(not(test), no_std)] /// Common errors pub mod error; diff --git a/src/protocol/signature.rs b/src/protocol/signature.rs index 5981b24..be66ee1 100644 --- a/src/protocol/signature.rs +++ b/src/protocol/signature.rs @@ -31,6 +31,7 @@ pub type SignatureCtxResult = Result; pub type SignatureResult = Result; +/// Creates the `combined_spdm_prefix` pub fn create_responder_signing_context( spdm_version: SpdmVersion, opcode: ReqRespCode, diff --git a/src/transcript.rs b/src/transcript.rs index 0fa7f8a..6b5f049 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -47,6 +47,7 @@ impl VcaBuffer { } } +#[derive(Debug)] pub enum TranscriptContext { /// # VCA /// VCA: Version, Capabilities, and Algorithms transcript context, containing From c8b2128854b52e54d56566ca276437b6e1f04a9c Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 24 Feb 2026 19:47:48 +0100 Subject: [PATCH 46/86] Fix all warnings --- src/commands/challenge/request.rs | 7 +++---- src/commands/challenge/response.rs | 8 +++----- src/commands/version/request.rs | 1 - src/protocol/signature.rs | 1 - src/state.rs | 1 + 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs index 4766137..df21edc 100644 --- a/src/commands/challenge/request.rs +++ b/src/commands/challenge/request.rs @@ -1,12 +1,11 @@ -use crate::cert_store::{CertStoreError, CertStoreResult, PeerCertStore, SpdmCertStore}; // Licensed under the Apache-2.0 license -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::codec::{Codec, MessageBuf}; use crate::commands::challenge::{ ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType, CONTEXT_LEN, NONCE_LEN, OPAQUE_DATA_MAX, }; use crate::context::SpdmContext; -use crate::error::{CommandError, CommandResult, PlatformError}; +use crate::error::{CommandError, CommandResult}; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; @@ -71,7 +70,7 @@ pub fn generate_challenge_request<'a>( /// has to be appended to the transcript context before signature verification, as required by SPDM 1.2 and later. /// 2. The signature verification has to be done in the application, as it requires /// access to the public key from the responder's certificate chain (which we already verified) and the transcript hash. -pub fn handle_challenge_auth_response<'a>( +pub(crate) fn handle_challenge_auth_response<'a>( ctx: &mut SpdmContext<'a>, spdm_hdr: SpdmMsgHdr, resp_payload: &mut MessageBuf<'a>, diff --git a/src/commands/challenge/response.rs b/src/commands/challenge/response.rs index 0e37446..67da35c 100644 --- a/src/commands/challenge/response.rs +++ b/src/commands/challenge/response.rs @@ -1,6 +1,6 @@ // Licensed under the Apache-2.0 license use crate::cert_store::MAX_CERT_SLOTS_SUPPORTED; -use crate::codec::{Codec, CommonCodec, MessageBuf}; +use crate::codec::{Codec, MessageBuf}; use crate::commands::algorithms::selected_measurement_specification; use crate::commands::challenge::{ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType}; use crate::commands::digests::compute_cert_chain_hash; @@ -11,8 +11,6 @@ use crate::platform::hash::SpdmHashAlgoType; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::TranscriptContext; -use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; fn process_challenge<'a>( ctx: &mut SpdmContext<'a>, @@ -65,8 +63,8 @@ fn process_challenge<'a>( // Append the CHALLENGE request to the M1 transcript ctx.append_message_to_transcript(req_payload, TranscriptContext::M1)?; - let meas_hash_type = - MeasurementSummaryHashType::try_from(challenge_req.measurement_hash_type).map_err(|_| { + let meas_hash_type = MeasurementSummaryHashType::try_from(challenge_req.measurement_hash_type) + .map_err(|_| { ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) })?; diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index 7621838..f82cfe8 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -2,7 +2,6 @@ use crate::{ codec::{Codec, MessageBuf}, - commands::capabilities::req_flag_compatible, context::SpdmContext, error::{CommandError, CommandResult}, protocol::SpdmMsgHdr, diff --git a/src/protocol/signature.rs b/src/protocol/signature.rs index be66ee1..43ab2bd 100644 --- a/src/protocol/signature.rs +++ b/src/protocol/signature.rs @@ -1,6 +1,5 @@ // Licensed under the Apache-2.0 license -use crate::context::SpdmContext; use crate::platform::hash::{SpdmHash, SpdmHashError}; use crate::protocol::*; diff --git a/src/state.rs b/src/state.rs index 8baf5c1..5ca2759 100644 --- a/src/state.rs +++ b/src/state.rs @@ -82,6 +82,7 @@ impl ConnectionInfo { &self.peer_algorithms } + #[allow(dead_code)] pub(crate) fn set_multi_key_conn_rsp(&mut self, multi_key_conn_rsp: bool) { self.multi_key_conn_rsp = multi_key_conn_rsp; } From bcaff517f24c411e664d58e5534b144a23d5fc46 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 14:23:09 +0100 Subject: [PATCH 47/86] ci: add dummy build-emu.yml Signed-off-by: leongross --- .github/workflows/build-emu.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/build-emu.yml diff --git a/.github/workflows/build-emu.yml b/.github/workflows/build-emu.yml new file mode 100644 index 0000000..c53802a --- /dev/null +++ b/.github/workflows/build-emu.yml @@ -0,0 +1,16 @@ +name: Build Base Implementation + +on: + workflow_dispatch: + inputs: + commit_hash: + description: 'Commit hash to build base spdm-emu from' + required: true + type: string + +jobs: + build-emu: + runs-on: ubuntu-latest + steps: + - run: | + echo "See: https://github.com/9elements/spdm-lib/pull/36 and leongross/ci-spdm-emu" From 627003bb3910fde21b443495f8f1faf9dbc46621 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 15:48:39 +0100 Subject: [PATCH 48/86] add mctp binding for platform transport Signed-off-by: leongross --- examples/platform/socket_transport.rs | 53 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index 84421a6..c14743e 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -13,6 +13,8 @@ use spdm_lib::platform::transport::{SpdmTransport, TransportError, TransportResu use zerocopy::byteorder::{BigEndian, U32}; use zerocopy::{FromBytes, Immutable, IntoBytes}; +use crate::platform; + /// Socket platform command types (from DMTF emulator) /// This is **NOT** part of the official DMTF spec, but is necessary to implement /// [SocketTransportType::None]. @@ -297,17 +299,41 @@ impl SpdmSocketTransport { } /// Receive platform data with socket message header + /// + /// The transport specific headers such as MCTP header (see DSP0275) are encoded in the payload. + /// Note, that the payload size needs to be adjusted accordingly when sending/ receiving messages with transport specific headers. pub(crate) fn receive_platform_data(&mut self) -> IoResult<(SocketSpdmCommand, Vec)> { // Read socket message header let mut header_bytes = [0u8; 12]; // sizeof(SocketMessageHeader) self.stream.read_exact(&mut header_bytes)?; - let header = SocketSpdmCommandHdr::from(&header_bytes); let payload_size = header.payload_size.get(); if payload_size > 0 { let mut data = vec![0u8; payload_size as usize]; self.stream.read_exact(&mut data)?; + + // Parse and remove transport specific headers from the payload. + match self.transport_type { + SocketTransportType::None => {} + SocketTransportType::MCTP => { + let mctp_header = data[0]; + if mctp_header != 0x5 { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Invalid MCTP header", + )); + } + data.remove(0); + } + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Unsupported transport type", + )); + } + } + Ok((header.command, data)) } else { Ok((header.command, Vec::new())) @@ -315,18 +341,35 @@ impl SpdmSocketTransport { } /// Send platform data with socket message header + /// + /// Depending on the [SocketTransportType], this may prepend additional transport-specific headers to the data. fn send_platform_data(&mut self, command: SocketSpdmCommand, data: &[u8]) -> IoResult<()> { + let mut platform_header: &[u8] = &[]; + match self.transport_type { + SocketTransportType::None => {} + + SocketTransportType::MCTP => { + platform_header = &[0x5]; + } + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Unsupported transport type", + )); + } + } + let header_bytes: [u8; 12] = SocketSpdmCommandHdr { command: SocketSpdmCommand::from(command as u32), transport_type: self.transport_type, - payload_size: BeU32::new(data.len() as u32), + payload_size: BeU32::new((data.len() + platform_header.len()) as u32), } .into(); self.stream.write_all(&header_bytes)?; - // Send data if any if !data.is_empty() { + self.stream.write_all(platform_header)?; self.stream.write_all(data)?; } @@ -347,9 +390,7 @@ impl SpdmTransport for SpdmSocketTransport { /// This function is only relevant for the SPDM Requester. /// Send the SPDM Request encoded into [req] (header|payload]) via the platform transport /// to and SPDM endpoint. - /// Since this binding implements DSP0276, "Secured Messages using SPDM over MCTP Binding Specification", - /// there is no EID to send it to. - fn send_request<'a>(&mut self, _dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { + fn send_request<'a>(&mut self, dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { let message_data = req .message_data() .map_err(|_| TransportError::BufferTooSmall)?; From 18cd5a251d0051f4b51365340f50db3d1a759631 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 15:59:55 +0100 Subject: [PATCH 49/86] add transport type flag to cli and restructure to use clap Signed-off-by: leongross --- Cargo.toml | 5 + examples/platform/socket_transport.rs | 3 +- examples/spdm_requester.rs | 154 +++++++------------------- 3 files changed, 44 insertions(+), 118 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 64d0943..8883491 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,13 @@ hex = "0.4.3" der = "0.8" x509-cert = "0.3.0-rc.4" signature = "2" +clap = { version = "4", features = ["derive"] } # Examples [[example]] name = "spdm_responder" path = "examples/spdm_responder.rs" + +[[example]] +name = "spdm_requester" +path = "examples/spdm_requester.rs" diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index c14743e..2c8d092 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -8,6 +8,7 @@ use std::io::{Read, Result as IoResult, Write}; use std::net::TcpStream; +use clap::{Parser, ValueEnum}; use spdm_lib::codec::{Codec, CodecError, CommonCodec, MessageBuf}; use spdm_lib::platform::transport::{SpdmTransport, TransportError, TransportResult}; use zerocopy::byteorder::{BigEndian, U32}; @@ -51,7 +52,7 @@ impl From for SocketSpdmCommand { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)] #[repr(u32)] #[allow(non_camel_case_types, unused)] pub enum SocketTransportType { diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index c8df717..b3bdc03 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -2,11 +2,10 @@ //! SPDM Example Responder utilizing the requester library. -use std::env; use std::io::{Error, ErrorKind, Result as IoResult}; use std::net::TcpStream; -use std::process; +use clap::Parser; use der::{Decode, Encode}; use p384::ecdsa::{Signature, VerifyingKey}; use spdm_lib::codec::MessageBuf; @@ -44,26 +43,34 @@ use x509_cert::Certificate; /// ECP384 CA cert from spdm-emu const CA_CERT: &[u8] = include_bytes!("cert/ecp384_ca.cert.der"); -/// Responder configuration -#[derive(Debug, Clone)] +/// SPDM Example Requester +#[derive(Debug, Clone, Parser)] +#[command(about = "Real SPDM Library Integrated DMTF Compatible Requester")] struct RequesterConfig { + /// TCP TCP port to connect to. + /// This needs to be supplied for both type NONE and MCTP. + #[arg(short, long, default_value_t = 2323)] port: u16, + + /// Path to certificate file + #[arg(short, long, default_value = "device_cert.pem")] cert_path: String, + + /// Path to private key file + #[arg(short = 'k', long, default_value = "device_key.pem")] key_path: String, + + /// Path to measurements file + #[arg(short, long)] measurements_path: Option, + + /// Enable verbose logging + #[arg(short, long)] verbose: bool, -} -impl Default for RequesterConfig { - fn default() -> Self { - Self { - port: 2323, - cert_path: "device_cert.pem".to_string(), - key_path: "device_key.pem".to_string(), - measurements_path: Some("measurements.json".to_string()), - verbose: false, - } - } + /// Transport type to use for the connection + #[arg(short, long, default_value_t = platform::socket_transport::SocketTransportType::None, value_enum)] + transport_type: platform::socket_transport::SocketTransportType, } /// Create SPDM device capabilities @@ -133,10 +140,7 @@ fn create_local_algorithms<'a>() -> LocalDeviceAlgorithms<'a> { // Perform a VCS flow (Version, Capabilities, Algorithms) // using the real SPDM library processing with platform implementations. fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { - let mut transport = SpdmSocketTransport::new( - stream, - platform::socket_transport::SocketTransportType::None, - ); + let mut transport = SpdmSocketTransport::new(stream, config.transport_type); const EID: u8 = 0; // Create platform implementations - all from platform module! @@ -159,7 +163,10 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { let mut peer_cert_store = ExamplePeerCertStore::default(); if config.verbose { - println!("Client connected - initializing SPDM context"); + println!( + "Client connected with transport type: {:?}", + config.transport_type + ); } // TODO: The SpdmContext has to be adjusted (best in a generic way) to be requester compatible @@ -194,12 +201,16 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { // Before we can start, we need to do the inofficial handshake for SOCKET_TRANSPORT_TYPE_NONE // 1. Send SOCKET_SPDM_COMMAND_TEST with payload b'Client Hello!' // 2. Receive SOCKET_SPDM_COMMAND_TEST with payload b'Server Hello!' - spdm_context.transport_init_sequence().map_err(|e| { - eprintln!("Handshake failed: {:?}", e); - Error::new(ErrorKind::Other, "SPDM handshake failed") - })?; + if config.transport_type == platform::socket_transport::SocketTransportType::None { + spdm_context.transport_init_sequence().map_err(|e| { + eprintln!("Handshake failed: {:?}", e); + Error::new(ErrorKind::Other, "SPDM handshake failed") + })?; + } - if config.verbose { + if config.verbose + && config.transport_type == platform::socket_transport::SocketTransportType::None + { println!("Initial handshake completed successfully"); } @@ -475,97 +486,6 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { Ok(()) } -/// Parse command line arguments -fn parse_args() -> RequesterConfig { - let mut config = RequesterConfig::default(); - let args: Vec = env::args().collect(); - - let mut i = 1; - while i < args.len() { - match args[i].as_str() { - "-p" | "--port" => { - if i + 1 < args.len() { - config.port = args[i + 1].parse().unwrap_or_else(|_| { - eprintln!("Invalid port number: {}", args[i + 1]); - process::exit(1); - }); - i += 2; - } else { - eprintln!("Port number required after {}", args[i]); - process::exit(1); - } - } - "-c" | "--cert" => { - if i + 1 < args.len() { - config.cert_path = args[i + 1].clone(); - i += 2; - } else { - eprintln!("Certificate file path required after {}", args[i]); - process::exit(1); - } - } - "-k" | "--key" => { - if i + 1 < args.len() { - config.key_path = args[i + 1].clone(); - i += 2; - } else { - eprintln!("Private key file path required after {}", args[i]); - process::exit(1); - } - } - "-m" | "--measurements" => { - if i + 1 < args.len() { - config.measurements_path = Some(args[i + 1].clone()); - i += 2; - } else { - eprintln!("Measurements file path required after {}", args[i]); - process::exit(1); - } - } - "-v" | "--verbose" => { - config.verbose = true; - i += 1; - } - "-h" | "--help" => { - print_help(); - process::exit(0); - } - _ => { - eprintln!("Unknown argument: {}", args[i]); - print_help(); - process::exit(1); - } - } - } - - config -} - -fn print_help() { - println!("Real SPDM Library Integrated DMTF Compatible Responder\n"); - println!("USAGE:"); - println!(" spdm-responder-clean [OPTIONS]\n"); - println!("OPTIONS:"); - println!(" -p, --port TCP port to connect to [default: None]"); - println!( - " -c, --cert Path to certificate file [default: device_cert.pem]" - ); - println!( - " -k, --key Path to private key file [default: device_key.pem]" - ); - println!( - " -m, --measurements Path to measurements file [default: measurements.json]" - ); - println!(" -v, --verbose Enable verbose logging"); - println!(" -h, --help Print this help message\n"); - println!("EXAMPLES:"); - println!(" spdm-responder-clean --port 8080 --verbose"); - println!(" spdm-responder-clean --cert my_cert.pem --key my_key.pem"); - println!( - "\nIntegrates real SPDM library with clean platform implementations - no code duplication!" - ); -} - /// Display configuration information fn display_info(config: &RequesterConfig) { println!("Real SPDM Library Integrated DMTF Compatible Responder"); @@ -620,7 +540,7 @@ fn display_info(config: &RequesterConfig) { /// Main function fn main() -> Result<(), Box> { - let config = parse_args(); + let config = RequesterConfig::parse(); display_info(&config); let remote_addr = format!("0.0.0.0:{}", config.port); From c1805dbcf9191449454cb947bb40ead07d01e62d Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 16:34:44 +0100 Subject: [PATCH 50/86] abstract header generation to TransportType Signed-off-by: leongross --- examples/platform/socket_transport.rs | 30 +++++++++++++++++++++++---- src/platform/transport.rs | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index 2c8d092..08d6c7b 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -75,6 +75,22 @@ impl From for SocketTransportType { } } +/// # MCTP +/// Header = `IC | MessageType`, where +/// - `IC` is the integrity check byte, which is set to 0 for SPDM messages. +/// - `MessageType` is set to 0x5 for SPDM messages. +impl SocketTransportType { + pub fn transport_header(&self) -> TransportResult<&[u8]> { + match self { + SocketTransportType::None => Ok(&[]), + SocketTransportType::MCTP => Ok(&[0x5]), + SocketTransportType::PCI_DOE | SocketTransportType::TCP => { + Err(TransportError::UnsupportedTransportType) + } + } + } +} + pub enum SocketMessageHeaderError { Reserved, } @@ -145,6 +161,8 @@ pub enum SpdmSocketTransportError { //0xC4 - 0xFF: Reserved. } +type SpdmSocketTransportResult = Result; + impl TryFrom for SpdmSocketTransportError { type Error = SocketMessageHeaderError; @@ -318,8 +336,12 @@ impl SpdmSocketTransport { match self.transport_type { SocketTransportType::None => {} SocketTransportType::MCTP => { - let mctp_header = data[0]; - if mctp_header != 0x5 { + let mctp_header_got = data[0]; + let mctp_header_want = self.transport_type.transport_header().map_err(|e| { + std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", e)) + })?[0]; + + if mctp_header_got != mctp_header_want { return Err(std::io::Error::new( std::io::ErrorKind::Other, "Invalid MCTP header", @@ -378,7 +400,7 @@ impl SpdmSocketTransport { Ok(()) } - pub fn send_client_hello<'a>(&mut self) -> TransportResult<()> { + pub fn send_client_hello(&mut self) -> TransportResult<()> { let message_data = b"Client Hello!\x00".as_bytes(); self.send_platform_data(SocketSpdmCommand::Test, message_data) @@ -391,7 +413,7 @@ impl SpdmTransport for SpdmSocketTransport { /// This function is only relevant for the SPDM Requester. /// Send the SPDM Request encoded into [req] (header|payload]) via the platform transport /// to and SPDM endpoint. - fn send_request<'a>(&mut self, dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { + fn send_request<'a>(&mut self, _dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { let message_data = req .message_data() .map_err(|_| TransportError::BufferTooSmall)?; diff --git a/src/platform/transport.rs b/src/platform/transport.rs index 88c735f..fcc4cc8 100644 --- a/src/platform/transport.rs +++ b/src/platform/transport.rs @@ -28,6 +28,7 @@ pub enum TransportError { SendError, ResponseNotExpected, NoRequestInFlight, + UnsupportedTransportType, /// Error specific to SOCKET_TRANSPORT_TYPE_NONE handshake HandshakeNoneError, From 1f290b50f309652ddd078c8c607393cca9934fca Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 26 Feb 2026 18:39:51 +0100 Subject: [PATCH 51/86] Fix: Uncomment `no_std` attribure for spdm-lib This must have slipped in while testing :( --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8b68fc7..daf45c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ // Licensed under the Apache-2.0 license -// #![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_std)] /// Common errors pub mod error; From 27fdf91e2f456d58a38164ba67585e09cc76f0a4 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 14:23:09 +0100 Subject: [PATCH 52/86] ci: add dummy build-emu.yml Signed-off-by: leongross --- .github/workflows/build-emu.yml | 37 +++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-emu.yml b/.github/workflows/build-emu.yml index c53802a..9d13788 100644 --- a/.github/workflows/build-emu.yml +++ b/.github/workflows/build-emu.yml @@ -12,5 +12,38 @@ jobs: build-emu: runs-on: ubuntu-latest steps: - - run: | - echo "See: https://github.com/9elements/spdm-lib/pull/36 and leongross/ci-spdm-emu" + - name: Checkout DMTF spdm-emu + uses: actions/checkout@v4 + with: + repository: DMTF/spdm-emu + ref: ${{ inputs.commit_hash }} + submodules: recursive + + - name: Install build dependecies + run: | + sudo apt install -y build-essential + + - name: Build application and prepare release + run: | + # https://github.com/DMTF/spdm-emu#linux-build-with-cmake + git submodule update + mkdir build -p && cd build + cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug -DCRYPTO=openssl .. + make copy_sample_key + make + + - name: Create or update release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir emu + cp spdm-emu/build/bin/spdm_requester_emu spdm-emu/build/bin/spdm_responder_emu emu + + gh release delete spdm-emu --yes --repo ${{ github.repository }} || true + + gh release create spdm-emu \ + --repo ${{ github.repository }} \ + --title "SPDM Emulators (built from ${{ inputs.commit_hash }})" \ + --notes "Built from commit: ${{ inputs.commit_hash }}" \ + --prerelease \ + ./emu From dfa4e3fe5bf289de8d15daadc2e1c12884c4a88f Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 14:15:30 +0100 Subject: [PATCH 53/86] ci: add requester integration test with spdm_emu Signed-off-by: leongross --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a54b7d4..27918f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,7 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Cargo Build run: cargo build --all-targets --workspace --verbose + test: runs-on: ubuntu-latest steps: @@ -46,3 +47,21 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Run tests run: cargo test --all-features --verbose --workspace + + emu-integration-requester: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + - name: Download spdm-emu release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download spdm-emu \ + --repo ${{ github.repository }} \ + --dir emu/ + mv emu/spdm_responder + chmod +x ~/spdm_responder + + ./spdm_responder_emu --trans NONE --ver 1.4 --slot_id 0 --slot_count 1 --req_slot_id 0 & + cargo run --example spdm_requester -- --port 2323 --verbose \ No newline at end of file From 51c89ce6ebbe5d1060e9b5c4d830efb267e16f83 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 26 Feb 2026 16:58:29 +0100 Subject: [PATCH 54/86] ci: make paths absolute with workspace base for emulator builds Signed-off-by: leongross --- .github/workflows/build-emu.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-emu.yml b/.github/workflows/build-emu.yml index 9d13788..fda8ae0 100644 --- a/.github/workflows/build-emu.yml +++ b/.github/workflows/build-emu.yml @@ -1,10 +1,10 @@ -name: Build Base Implementation +name: Build SPDM Emulator Artifacts on: workflow_dispatch: inputs: commit_hash: - description: 'Commit hash to build base spdm-emu from' + description: 'Commit hash to build spdm-emu from' required: true type: string @@ -18,6 +18,7 @@ jobs: repository: DMTF/spdm-emu ref: ${{ inputs.commit_hash }} submodules: recursive + path: spdm-emu - name: Install build dependecies run: | @@ -25,19 +26,19 @@ jobs: - name: Build application and prepare release run: | - # https://github.com/DMTF/spdm-emu#linux-build-with-cmake + cd "$GITHUB_WORKSPACE"/spdm-emu git submodule update mkdir build -p && cd build cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug -DCRYPTO=openssl .. make copy_sample_key - make + make -j - name: Create or update release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mkdir emu - cp spdm-emu/build/bin/spdm_requester_emu spdm-emu/build/bin/spdm_responder_emu emu + mkdir "$GITHUB_WORKSPACE"/emu + cp "$GITHUB_WORKSPACE"/spdm-emu/build/bin/spdm_requester_emu spdm-emu/build/bin/spdm_responder_emu "$GITHUB_WORKSPACE"/emu gh release delete spdm-emu --yes --repo ${{ github.repository }} || true @@ -46,4 +47,4 @@ jobs: --title "SPDM Emulators (built from ${{ inputs.commit_hash }})" \ --notes "Built from commit: ${{ inputs.commit_hash }}" \ --prerelease \ - ./emu + "$GITHUB_WORKSPACE"/emu/* From 21666a42be2062e2970996806aa6f1b8b7047dda Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 15:52:41 +0100 Subject: [PATCH 55/86] Add verification workflow with spdm-emu caching --- .github/workflows/verification.yml | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/verification.yml diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml new file mode 100644 index 0000000..1a7f7e5 --- /dev/null +++ b/.github/workflows/verification.yml @@ -0,0 +1,46 @@ +name: Verification with SPDM Emulator + +on: + push: + branches: [ "main" ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +env: + CARGO_TERM_COLOR: always + SPDM_EMU_REF: a8285827282c8c63708043298b2f4f06936d1c12 # spdm-emu 3.8.0 + +jobs: + requester-verification: + runs-on: ubuntu-latest + steps: + - name: Cache spdm-emu build output + id: cache-spdm-emu + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/spdm-emu + key: spdm_emu-${{ env.SPDM_EMU_REF }}-${{ runner.os }} + + - name: Checkout DMTF spdm-emu + if: ${{ steps.cha-spdm-emu.outputs.chache-hit != 'true' }} + uses: actions/checkout@v4 + with: + repository: DMTF/spdm-emu + ref: ${{ env.SPDM_EMU_REF }} + submodules: recursive + path: spdm-emu + + - name: Install build dependecies + if: ${{ steps.cha-spdm-emu.outputs.chache-hit != 'true' }} + run: | + sudo apt install -y build-essential + + - name: Build spdm-emu + if: ${{ steps.cha-spdm-emu.outputs.chache-hit != 'true' }} + run: | + cd "$GITHUB_WORKSPACE"/spdm-emu + git submodule update + mkdir build -p && cd build + cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug -DCRYPTO=openssl .. + make copy_sample_key + make -j From e4d5d89a9d85d63fb8e29728a9aed0ddc3c7114f Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 16:19:12 +0100 Subject: [PATCH 56/86] Add steps for requester example build and verification run --- .github/workflows/verification.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index 1a7f7e5..9af8a30 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -8,7 +8,8 @@ on: env: CARGO_TERM_COLOR: always - SPDM_EMU_REF: a8285827282c8c63708043298b2f4f06936d1c12 # spdm-emu 3.8.0 + SPDM_EMU_REF: fe4cdc53b3f0e8300d16519467588001525e84f3 # spdm-emu main (27.02.2026) + CACHE_INVALIDATOR: 20ba74fb3b2bc121 # change to invalidate caches jobs: requester-verification: @@ -18,11 +19,11 @@ jobs: id: cache-spdm-emu uses: actions/cache@v5 with: - path: ${{ github.workspace }}/spdm-emu - key: spdm_emu-${{ env.SPDM_EMU_REF }}-${{ runner.os }} + path: ${{ github.workspace }}/spdm-emu/build + key: spdm_emu-${{ env.SPDM_EMU_REF }}-${{ runner.os }}-${{ env.CACHE_INVALIDATOR }} - name: Checkout DMTF spdm-emu - if: ${{ steps.cha-spdm-emu.outputs.chache-hit != 'true' }} + if: ${{ steps.cache-spdm-emu.outputs.cache-hit != 'true' }} uses: actions/checkout@v4 with: repository: DMTF/spdm-emu @@ -31,12 +32,12 @@ jobs: path: spdm-emu - name: Install build dependecies - if: ${{ steps.cha-spdm-emu.outputs.chache-hit != 'true' }} + if: ${{ steps.cache-spdm-emu.outputs.cache-hit != 'true' }} run: | sudo apt install -y build-essential - name: Build spdm-emu - if: ${{ steps.cha-spdm-emu.outputs.chache-hit != 'true' }} + if: ${{ steps.cache-spdm-emu.outputs.cache-hit != 'true' }} run: | cd "$GITHUB_WORKSPACE"/spdm-emu git submodule update @@ -44,3 +45,19 @@ jobs: cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug -DCRYPTO=openssl .. make copy_sample_key make -j + + - uses: actions/checkout@v4 + with: + path: spdm-lib + - name: Cache cargo registry and build + uses: Swatinem/rust-cache@v2 + - name: Cargo Build + run: | + cd spdm-lib + cargo build --example spdm_requester + - name: Run verification flow + run: | + cd spdm-lib + "$GITHUB_WORKSPACE"/spdm-emu/build/bin/spdm_responder_emu --trans NONE --ver 1.4 --slot_id 0 --slot_count 1 --req_slot_id 0 & + (sleep 1; cargo run --example spdm_requester -- --port 2323 --verbose) + From b2d8d823587582c9013b34a53331de97632786c5 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 18:15:58 +0100 Subject: [PATCH 57/86] Add matrix for SPMD versions and transports --- .github/workflows/verification.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index 9af8a30..e5529e7 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -14,6 +14,10 @@ env: jobs: requester-verification: runs-on: ubuntu-latest + strategy: + matrix: + spdm_version: ["1.1", "1.2", "1.3"] + transport: [NONE, MCTP] steps: - name: Cache spdm-emu build output id: cache-spdm-emu @@ -58,6 +62,6 @@ jobs: - name: Run verification flow run: | cd spdm-lib - "$GITHUB_WORKSPACE"/spdm-emu/build/bin/spdm_responder_emu --trans NONE --ver 1.4 --slot_id 0 --slot_count 1 --req_slot_id 0 & - (sleep 1; cargo run --example spdm_requester -- --port 2323 --verbose) + "$GITHUB_WORKSPACE"/spdm-emu/build/bin/spdm_responder_emu --trans ${{ matrix.transport }} --ver ${{ matrix.spdm_version }} --slot_id 0 --slot_count 1 --req_slot_id 0 & + (sleep 1; cargo run --example spdm_requester -- --transport-type ${{ matrix.transport }} --port 2323 --verbose) From 3b24fb9b59d558c1e12ee6b0571f228cae648be4 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 18:36:13 +0100 Subject: [PATCH 58/86] Rename transport values to upper case for clap --- examples/platform/socket_transport.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index 08d6c7b..e4b7705 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -55,6 +55,7 @@ impl From for SocketSpdmCommand { #[derive(Debug, Clone, Copy, PartialEq, ValueEnum)] #[repr(u32)] #[allow(non_camel_case_types, unused)] +#[clap(rename_all = "UPPER")] pub enum SocketTransportType { /// SOCKET_TRANSPORT_TYPE_NONE None = 0x00, From 369f81fc9883c9c0aa59b49b48e1a387e5ac0eef Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 19:04:32 +0100 Subject: [PATCH 59/86] Run spdm-emu in build directory for relative cert lookup --- .github/workflows/verification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index e5529e7..ad8cd06 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -62,6 +62,6 @@ jobs: - name: Run verification flow run: | cd spdm-lib - "$GITHUB_WORKSPACE"/spdm-emu/build/bin/spdm_responder_emu --trans ${{ matrix.transport }} --ver ${{ matrix.spdm_version }} --slot_id 0 --slot_count 1 --req_slot_id 0 & + (cd "$GITHUB_WORKSPACE"/spdm-emu/build/bin/; ./spdm_responder_emu --trans ${{ matrix.transport }} --ver ${{ matrix.spdm_version }} --slot_id 0 --slot_count 1 --req_slot_id 0) & (sleep 1; cargo run --example spdm_requester -- --transport-type ${{ matrix.transport }} --port 2323 --verbose) From 4545d5012626b34fec133f8fedb8c364fbb6693a Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 19:11:53 +0100 Subject: [PATCH 60/86] Don't fail fast for matrix strategy --- .github/workflows/verification.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index ad8cd06..1076d21 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -15,6 +15,7 @@ jobs: requester-verification: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: spdm_version: ["1.1", "1.2", "1.3"] transport: [NONE, MCTP] From 3ab4e6e78b3ed277c8da4456800e1f4dd041d742 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 19:16:08 +0100 Subject: [PATCH 61/86] Point rust cache to correct workspace directory --- .github/workflows/verification.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index 1076d21..3232dda 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -56,6 +56,8 @@ jobs: path: spdm-lib - name: Cache cargo registry and build uses: Swatinem/rust-cache@v2 + with: + workspaces: "spdm-lib" - name: Cargo Build run: | cd spdm-lib From 3cd80a56980fef9cc6eb1c5dc980fb36574f6e8c Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Fri, 27 Feb 2026 19:35:33 +0100 Subject: [PATCH 62/86] Add examples round trip test, delete unneeded emulator build workflow --- .github/workflows/build-emu.yml | 50 --------------------------------- .github/workflows/ci.yml | 26 ++++++++--------- 2 files changed, 11 insertions(+), 65 deletions(-) delete mode 100644 .github/workflows/build-emu.yml diff --git a/.github/workflows/build-emu.yml b/.github/workflows/build-emu.yml deleted file mode 100644 index fda8ae0..0000000 --- a/.github/workflows/build-emu.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Build SPDM Emulator Artifacts - -on: - workflow_dispatch: - inputs: - commit_hash: - description: 'Commit hash to build spdm-emu from' - required: true - type: string - -jobs: - build-emu: - runs-on: ubuntu-latest - steps: - - name: Checkout DMTF spdm-emu - uses: actions/checkout@v4 - with: - repository: DMTF/spdm-emu - ref: ${{ inputs.commit_hash }} - submodules: recursive - path: spdm-emu - - - name: Install build dependecies - run: | - sudo apt install -y build-essential - - - name: Build application and prepare release - run: | - cd "$GITHUB_WORKSPACE"/spdm-emu - git submodule update - mkdir build -p && cd build - cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Debug -DCRYPTO=openssl .. - make copy_sample_key - make -j - - - name: Create or update release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir "$GITHUB_WORKSPACE"/emu - cp "$GITHUB_WORKSPACE"/spdm-emu/build/bin/spdm_requester_emu spdm-emu/build/bin/spdm_responder_emu "$GITHUB_WORKSPACE"/emu - - gh release delete spdm-emu --yes --repo ${{ github.repository }} || true - - gh release create spdm-emu \ - --repo ${{ github.repository }} \ - --title "SPDM Emulators (built from ${{ inputs.commit_hash }})" \ - --notes "Built from commit: ${{ inputs.commit_hash }}" \ - --prerelease \ - "$GITHUB_WORKSPACE"/emu/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27918f0..0b64bba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: - name: Cargo Build run: cargo build --all-targets --workspace --verbose - test: + unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -47,21 +47,17 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Run tests run: cargo test --all-features --verbose --workspace - - emu-integration-requester: + roundtrip-test: runs-on: ubuntu-latest steps: - - name: Checkout source - uses: actions/checkout@v4 - - name: Download spdm-emu release assets - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + - name: Cache cargo registry and build + uses: Swatinem/rust-cache@v2 + - name: Build examples + run: cargo build --examples + - name: Run tests run: | - gh release download spdm-emu \ - --repo ${{ github.repository }} \ - --dir emu/ - mv emu/spdm_responder - chmod +x ~/spdm_responder + cargo run --example spdm_responder -- --port 2323 & + (sleep 1; cargo run --example spdm_requester -- --port 2323 ) + - ./spdm_responder_emu --trans NONE --ver 1.4 --slot_id 0 --slot_count 1 --req_slot_id 0 & - cargo run --example spdm_requester -- --port 2323 --verbose \ No newline at end of file From 3abf5a241d89372df1b5fd50e3fa46f04bbe3552 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 19 Feb 2026 00:02:07 +0100 Subject: [PATCH 63/86] Refactor capabilities requester - Implement a correct check for cap. response flags - Create type aliases to clarify GET_CAPABILITIES and CAPABILITIES - Fix CAPABILITIES response handler --- src/commands/capabilities/mod.rs | 117 +--------- src/commands/capabilities/request.rs | 303 ++++++++++++++++++++++---- src/commands/capabilities/response.rs | 106 ++++++++- 3 files changed, 377 insertions(+), 149 deletions(-) diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs index 808e5dc..641e1eb 100644 --- a/src/commands/capabilities/mod.rs +++ b/src/commands/capabilities/mod.rs @@ -8,10 +8,7 @@ pub(crate) use response::*; use zerocopy::{FromBytes, Immutable, IntoBytes}; -use crate::{ - codec::CommonCodec, - protocol::{CapabilityFlags, EpInfoCapability, PskCapability, SpdmVersion}, -}; +use crate::{codec::CommonCodec, protocol::CapabilityFlags}; use crate::protocol::capabilities::DeviceCapabilities; @@ -21,6 +18,10 @@ pub struct GetCapabilitiesBase { param1: u8, param2: u8, } +/// CAPABILITIES response base +/// +/// v1.0 CAPABILITIES response is constructed by `CapabilitiesBase`+`Capabilities`. +pub type CapabilitiesBase = GetCapabilitiesBase; impl CommonCodec for GetCapabilitiesBase {} @@ -50,6 +51,8 @@ pub struct GetCapabilitiesV11 { /// Capability flags. flags: CapabilityFlags, } +/// CAPABILITIES response +pub type Capabilities = GetCapabilitiesV11; impl GetCapabilitiesV11 { pub fn new(ct_exponent: u8, flags: CapabilityFlags) -> Self { @@ -82,6 +85,8 @@ pub struct GetCapabilitiesV12 { /// Large SPDM message. max_spdm_msg_size: u32, } +/// CAPABILITIES response v1.2 additions +pub type CapabilitiesV12 = GetCapabilitiesV12; impl CommonCodec for GetCapabilitiesV12 {} @@ -111,107 +116,3 @@ impl Default for GetCapabilitiesV12 { } } } - -/// Checks if the request capability flags are compatible with the SPDM version -///# Arguments -/// - `version`: SPDM version -/// - `flags`: Capability flags from the request -/// -/// # Returns -/// - true if compatible -/// - false if incompatible -pub(crate) fn req_flag_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { - // Checks specific to 1.1 - if version == SpdmVersion::V11 && flags.mut_auth_cap() == 1 && flags.encap_cap() == 0 { - return false; - } - - // Check if MEAS_CAP is valid - // 0b11 is reserved - if flags.meas_cap() == 0b11 { - return false; - } - - // Checks common to 1.1 and higher - if version >= SpdmVersion::V11 { - // Illegal to return reserved values (2 and 3) - if flags.psk_cap() >= PskCapability::PskWithContext as u8 { - return false; - } - - // Checks that originate from key exchange capabilities - if flags.key_ex_cap() == 1 || flags.psk_cap() != PskCapability::NoPsk as u8 { - if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { - return false; - } - } else { - if flags.mac_cap() == 1 - || flags.encrypt_cap() == 1 - || flags.handshake_in_the_clear_cap() == 1 - || flags.hbeat_cap() == 1 - || flags.key_upd_cap() == 1 - { - return false; - } - - if version >= SpdmVersion::V13 && flags.event_cap() == 1 { - return false; - } - } - - if flags.key_ex_cap() == 0 - && flags.psk_cap() == PskCapability::PskWithNoContext as u8 - && flags.handshake_in_the_clear_cap() == 1 - { - return false; - } - - // Checks that originate from certificate or public key capabilities - if flags.cert_cap() == 1 || flags.pub_key_id_cap() == 1 { - // Certificate capabilities and public key capabilities can not both be set - if flags.cert_cap() == 1 && flags.pub_key_id_cap() == 1 { - return false; - } - - if flags.chal_cap() == 0 && flags.pub_key_id_cap() == 1 { - return false; - } - } else { - // If certificates or public keys are not enabled then these capabilities are not allowed - if flags.chal_cap() == 1 || flags.mut_auth_cap() == 1 { - return false; - } - - if version >= SpdmVersion::V13 - && flags.ep_info_cap() == EpInfoCapability::EpInfoWithSignature as u8 - { - return false; - } - } - - // Checks that originate from mutual authentication capabilities - if flags.mut_auth_cap() == 1 { - // Mutual authentication with asymmetric keys can only occur through the basic mutual - // authentication flow (CHAL_CAP == 1) or the session-based mutual authentication flow - // (KEY_EX_CAP == 1) - if flags.cert_cap() == 0 && flags.pub_key_id_cap() == 0 { - return false; - } - } - } - - // Checks specific to 1.3 and higher - if version >= SpdmVersion::V13 { - // Illegal to return reserved values - if flags.ep_info_cap() == EpInfoCapability::Reserved as u8 || flags.multi_key_cap() == 3 { - return false; - } - - // Check multi_key_cap and pub_key_id_cap - if flags.multi_key_cap() != 0 && flags.pub_key_id_cap() == 1 { - return false; - } - } - - true -} diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 118cda9..0f123ea 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -1,9 +1,13 @@ // Licensed under the Apache-2.0 license use crate::commands::error_rsp::ErrorCode; +use crate::protocol::CapabilityFlags; use crate::{codec::MessageBuf, context::SpdmContext, error::CommandResult, protocol::SpdmMsgHdr}; -use crate::commands::capabilities::{GetCapabilitiesBase, GetCapabilitiesV11, GetCapabilitiesV12}; +use crate::commands::capabilities::{ + Capabilities, CapabilitiesBase, CapabilitiesV12, GetCapabilitiesBase, GetCapabilitiesV11, + GetCapabilitiesV12, +}; use crate::protocol::{capabilities::DeviceCapabilities, ReqRespCode, SpdmVersion}; use crate::error::CommandError; @@ -39,7 +43,7 @@ pub(crate) fn handle_capabilities_response<'a>( None => Err(ctx.generate_error_response(resp, ErrorCode::VersionMismatch, 0, None))?, }; - let _base_resp = GetCapabilitiesBase::decode(resp) + let _base_resp = CapabilitiesBase::decode(resp) .map_err(|_| ctx.generate_error_response(resp, ErrorCode::OperationFailed, 0, None))?; // Based on the negotiated version, try to decode the rest of the response. @@ -48,38 +52,34 @@ pub(crate) fn handle_capabilities_response<'a>( let mut peer_capabilities = DeviceCapabilities::default(); - if version > SpdmVersion::V10 { - let resp_11 = GetCapabilitiesV11::decode(resp) - .map_err(|_| ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; - peer_capabilities.ct_exponent = resp_11.ct_exponent; - - // TODO? - let _flags = resp_11.flags; - // THIS FAILS - // if !req_flag_compatible(version, &flags) { - // Err(ctx.generate_error_response(resp, ErrorCode::InvalidPolicy, 0, None))?; - // } - peer_capabilities.flags = resp_11.flags; + let resp_11 = Capabilities::decode(resp) + .map_err(|_| ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; + peer_capabilities.ct_exponent = resp_11.ct_exponent; - if version >= SpdmVersion::V12 { - let resp_12 = GetCapabilitiesV12::decode(resp).map_err(|_| { - ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None) - })?; + let flags = resp_11.flags; + if !resp_flags_compatible(version, &flags) { + Err(ctx.generate_error_response(resp, ErrorCode::InvalidPolicy, 0, None))?; + } + peer_capabilities.flags = resp_11.flags; - // _DataTransferSize_ shall be equal to or greater than _MinDataTransferSize_ - if resp_12.data_transfer_size < crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 { - return Err((false, CommandError::InvalidResponse)); - } + if version >= SpdmVersion::V12 { + let resp_12 = CapabilitiesV12::decode(resp) + .map_err(|_| ctx.generate_error_response(resp, ErrorCode::InvalidRequest, 0, None))?; - // _MaxSPDMmsgSize_ should be greater than or equal to _DataTransferSize_ - if resp_12.max_spdm_msg_size < resp_12.data_transfer_size { - return Err((false, CommandError::InvalidResponse)); - } + // _DataTransferSize_ shall be equal to or greater than _MinDataTransferSize_ + if resp_12.data_transfer_size < crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 { + return Err((false, CommandError::InvalidResponse)); + } - peer_capabilities.data_transfer_size = resp_12.data_transfer_size; - peer_capabilities.max_spdm_msg_size = resp_12.max_spdm_msg_size; + // _MaxSPDMmsgSize_ should be greater than or equal to _DataTransferSize_ + if resp_12.max_spdm_msg_size < resp_12.data_transfer_size { + return Err((false, CommandError::InvalidResponse)); } + + peer_capabilities.data_transfer_size = resp_12.data_transfer_size; + peer_capabilities.max_spdm_msg_size = resp_12.max_spdm_msg_size; } + // TODO: Since v1.3 an additional optional Supported Algorithms block was added. ctx.state .connection_info @@ -187,6 +187,229 @@ pub fn generate_capabilities_request_local<'a>( generate_capabilities_request(ctx, req_buf, capabilities, capv11, capv12) } +/// Checks that the flags in a capabilites response are compatible with the provided version +/// +/// Checks for reserved values and consistency of flags as far as required. +fn resp_flags_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { + // Most checks are the same but its a bit of a mess with some exceptions, + // so we just do a complete check for every version. + match version { + SpdmVersion::V10 => check_flags_v10(flags), + SpdmVersion::V11 => check_flags_v11(flags), + SpdmVersion::V12 => check_flags_v12(flags), + SpdmVersion::V13 => check_flags_v13(flags), + } +} + +/// Check flags to be compatible with version 1.0 +/// +/// Checks that all flags known to v1.0 have valid values. +/// Reserved fields are ignored. +fn check_flags_v10(flags: &CapabilityFlags) -> bool { + // Check for reserved values + !(flags.meas_cap() == 0b11) +} + +/// Check flags to be compatible with version 1.1 +/// +/// Checks that all flags known to v1.1 have valid values. +/// Reserved fields are ignored. +fn check_flags_v11(flags: &CapabilityFlags) -> bool { + // Check for reserved values + if flags.meas_cap() == 0b11 { + return false; + } + if flags.psk_cap() == 0b11 { + return false; + } + // Check for conditionally needed flags + if flags.encrypt_cap() == 1 { + // One or more of MAC_CAP or KEY_EX_CAP must be set + if flags.mac_cap() == 0 && flags.key_ex_cap() == 0 { + return false; + } + } + if flags.mac_cap() == 1 { + // One or more of PSK_CAP or KEY_EX_CAP must be set + if flags.psk_cap() == 0 && flags.key_ex_cap() == 0 { + return false; + } + } + if flags.key_ex_cap() == 1 { + // One or more of MAC_CAP or ENCRYPT_CAP must be set + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } + if flags.psk_cap() == 1 { + // One or more of MAC_CAP or ENCRYPT_CAP must be set + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } + if flags.mut_auth_cap() == 1 { + if flags.encap_cap() == 0 { + return false; + } + } + if flags.handshake_in_the_clear_cap() == 1 { + if flags.key_ex_cap() == 0 { + return false; + } + } + if flags.pub_key_id_cap() == 1 { + if flags.cert_cap() == 1 { + return false; + } + } + true +} + +/// Check flags to be compatible with version 1.2 +/// +/// Checks that all flags known to v1.2 have valid values. +/// Reserved fields are ignored. +fn check_flags_v12(flags: &CapabilityFlags) -> bool { + // Check for reserved values + if flags.meas_cap() == 0b11 { + return false; + } + if flags.psk_cap() == 0b11 { + return false; + } + // Check for conditionally needed flags + if flags.encrypt_cap() == 1 { + // One or more of MAC_CAP or KEY_EX_CAP must be set + if flags.mac_cap() == 0 && flags.key_ex_cap() == 0 { + return false; + } + } + if flags.mac_cap() == 1 { + // One or more of PSK_CAP or KEY_EX_CAP must be set + if flags.psk_cap() == 0 && flags.key_ex_cap() == 0 { + return false; + } + } + if flags.key_ex_cap() == 1 { + // One or more of MAC_CAP or ENCRYPT_CAP must be set + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } + if flags.psk_cap() == 1 { + // One or more of MAC_CAP or ENCRYPT_CAP must be set + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } + if flags.mut_auth_cap() == 1 { + if flags.encap_cap() == 0 { + return false; + } + } + if flags.handshake_in_the_clear_cap() == 1 { + if flags.key_ex_cap() == 0 { + return false; + } + } + if flags.pub_key_id_cap() == 1 { + // In this case, CERT_CAP and ALIAS_CERT_CAP of the responder + // shall be 0. + if flags.cert_cap() == 1 || flags.alias_cert_cap() == 1 { + return false; + } + } + if flags.csr_cap() == 1 { + if flags.set_certificate_cap() == 0 { + return false; + } + } + if flags.cert_install_reset_cap() == 1 { + // If this bit is set, CSR_CAP and/or SET_CERT_CAP shall be set. + if flags.csr_cap() == 0 && flags.set_certificate_cap() == 0 { + return false; + } + } + true +} + +/// Check flags to be compatible with version 1.3 +/// +/// Checks that all flags known to v1.3 have valid values. +/// Reserved fields are ignored. +fn check_flags_v13(flags: &CapabilityFlags) -> bool { + // Check for reserved values + if flags.meas_cap() == 0b11 { + return false; + } + if flags.psk_cap() == 0b11 { + return false; + } + if flags.ep_info_cap() == 0b11 { + return false; + } + // Check for conditionally needed flags + if flags.encrypt_cap() == 1 { + // One or more of MAC_CAP or KEY_EX_CAP shall be set + if flags.mac_cap() == 0 && flags.key_ex_cap() == 0 { + return false; + } + } + if flags.mac_cap() == 1 { + // One or more of PSK_CAP or KEY_EX_CAP shall be set + if flags.psk_cap() == 0 && flags.key_ex_cap() == 0 { + return false; + } + } + if flags.key_ex_cap() == 1 { + // One or more of MAC_CAP or ENCRYPT_CAP shall be set + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } + if flags.psk_cap() == 1 { + // One or more of MAC_CAP or ENCRYPT_CAP shall be set + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } + if flags.mut_auth_cap() == 1 { + if flags.encap_cap() == 0 { + return false; + } + } + if flags.handshake_in_the_clear_cap() == 1 { + if flags.key_ex_cap() == 0 { + return false; + } + } + if flags.pub_key_id_cap() == 1 { + // In this case, CERT_CAP and ALIAS_CERT_CAP and MULTI_KEY_CAP of the responder + // shall be 0. + if flags.cert_cap() == 1 || flags.alias_cert_cap() == 1 || flags.multi_key_cap() == 1 { + return false; + } + } + if flags.csr_cap() == 1 { + if flags.set_certificate_cap() == 0 { + return false; + } + } + if flags.cert_install_reset_cap() == 1 { + // If this bit is set, SET_CERT_CAP shall be set and CSR_CAP can be set. + // Note: This was changed. In v1.2 one of both was required + if flags.set_certificate_cap() == 0 { + return false; + } + } + if flags.multi_key_cap() == 1 { + if flags.get_key_pair_info_cap() == 0 { + return false; + } + } + true +} + #[cfg(test)] mod tests { use super::*; @@ -216,10 +439,10 @@ mod tests { let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; let mut msg = MessageBuf::new(&mut msg_buf); let mut len = 0; - let cap_base = GetCapabilitiesBase::default(); + let cap_base = CapabilitiesBase::default(); len += cap_base.encode(&mut msg).unwrap(); - let cap_11 = GetCapabilitiesV11::new(10, CapabilityFlags::default()); - len += cap_11.encode(&mut msg).unwrap(); + let cap_10 = Capabilities::new(10, CapabilityFlags::default()); + len += cap_10.encode(&mut msg).unwrap(); let cap_12 = GetCapabilitiesV12 { data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, @@ -251,10 +474,10 @@ mod tests { // Encode invalid MEAS_CAP flag let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; - let cap_base = GetCapabilitiesBase::default(); + let cap_base = CapabilitiesBase::default(); let mut cap_flags = CapabilityFlags::default(); cap_flags.set_meas_cap(0b11); // 0x11 is reserved - let cap_12 = GetCapabilitiesV12::default(); + let cap_12 = CapabilitiesV12::default(); let mut msg = prepare_response(&mut msg_buf, cap_base, cap_flags, 10, cap_12); let res = handle_capabilities_response(&mut context, header.clone(), &mut msg); @@ -270,9 +493,9 @@ mod tests { // Test invalid v1.2 fields let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; - let cap_base = GetCapabilitiesBase::default(); + let cap_base = CapabilitiesBase::default(); let cap_flags = CapabilityFlags::default(); - let cap_12 = GetCapabilitiesV12 { + let cap_12 = CapabilitiesV12 { data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 - 1, max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, }; @@ -290,9 +513,9 @@ mod tests { } let mut msg_buf = [0; MAX_MCTP_SPDM_MSG_SIZE]; - let cap_base = GetCapabilitiesBase::default(); + let cap_base = CapabilitiesBase::default(); let cap_flags = CapabilityFlags::default(); - let cap_12 = GetCapabilitiesV12 { + let cap_12 = CapabilitiesV12 { data_transfer_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12, max_spdm_msg_size: crate::protocol::MIN_DATA_TRANSFER_SIZE_V12 - 1, }; @@ -312,16 +535,16 @@ mod tests { fn prepare_response<'a>( buf: &'a mut [u8], - cap_base: GetCapabilitiesBase, + cap_base: CapabilitiesBase, cap_flags: CapabilityFlags, ct_exp: u8, - cap_12: GetCapabilitiesV12, + cap_12: CapabilitiesV12, ) -> MessageBuf<'a> { let mut msg = MessageBuf::new(buf); let mut len = 0; len += cap_base.encode(&mut msg).unwrap(); - len += GetCapabilitiesV11::new(ct_exp, cap_flags) + len += Capabilities::new(ct_exp, cap_flags) .encode(&mut msg) .unwrap(); len += cap_12.encode(&mut msg).unwrap(); diff --git a/src/commands/capabilities/response.rs b/src/commands/capabilities/response.rs index 5c224e1..2eca239 100644 --- a/src/commands/capabilities/response.rs +++ b/src/commands/capabilities/response.rs @@ -47,7 +47,7 @@ fn process_get_capabilities<'a>( })?; let flags = req_11.flags; - if !req_flag_compatible(version, &flags) { + if !req_flags_compatible(version, &flags) { Err(ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None))?; } @@ -181,3 +181,107 @@ pub(crate) fn handle_get_capabilities<'a>( .set_state(ConnectionState::AfterCapabilities); Ok(()) } + +/// Checks if the request capability flags are compatible with the SPDM version +///# Arguments +/// - `version`: SPDM version +/// - `flags`: Capability flags from the request +/// +/// # Returns +/// - true if compatible +/// - false if incompatible +fn req_flags_compatible(version: SpdmVersion, flags: &CapabilityFlags) -> bool { + // Checks specific to 1.1 + if version == SpdmVersion::V11 && flags.mut_auth_cap() == 1 && flags.encap_cap() == 0 { + return false; + } + + // Check if MEAS_CAP is valid + // 0b11 is reserved + if flags.meas_cap() == 0b11 { + return false; + } + + // Checks common to 1.1 and higher + if version >= SpdmVersion::V11 { + // Illegal to return reserved values (2 and 3) + if flags.psk_cap() >= PskCapability::PskWithContext as u8 { + return false; + } + + // Checks that originate from key exchange capabilities + if flags.key_ex_cap() == 1 || flags.psk_cap() != PskCapability::NoPsk as u8 { + if flags.mac_cap() == 0 && flags.encrypt_cap() == 0 { + return false; + } + } else { + if flags.mac_cap() == 1 + || flags.encrypt_cap() == 1 + || flags.handshake_in_the_clear_cap() == 1 + || flags.hbeat_cap() == 1 + || flags.key_upd_cap() == 1 + { + return false; + } + + if version >= SpdmVersion::V13 && flags.event_cap() == 1 { + return false; + } + } + + if flags.key_ex_cap() == 0 + && flags.psk_cap() == PskCapability::PskWithNoContext as u8 + && flags.handshake_in_the_clear_cap() == 1 + { + return false; + } + + // Checks that originate from certificate or public key capabilities + if flags.cert_cap() == 1 || flags.pub_key_id_cap() == 1 { + // Certificate capabilities and public key capabilities can not both be set + if flags.cert_cap() == 1 && flags.pub_key_id_cap() == 1 { + return false; + } + + if flags.chal_cap() == 0 && flags.pub_key_id_cap() == 1 { + return false; + } + } else { + // If certificates or public keys are not enabled then these capabilities are not allowed + if flags.chal_cap() == 1 || flags.mut_auth_cap() == 1 { + return false; + } + + if version >= SpdmVersion::V13 + && flags.ep_info_cap() == EpInfoCapability::EpInfoWithSignature as u8 + { + return false; + } + } + + // Checks that originate from mutual authentication capabilities + if flags.mut_auth_cap() == 1 { + // Mutual authentication with asymmetric keys can only occur through the basic mutual + // authentication flow (CHAL_CAP == 1) or the session-based mutual authentication flow + // (KEY_EX_CAP == 1) + if flags.cert_cap() == 0 && flags.pub_key_id_cap() == 0 { + return false; + } + } + } + + // Checks specific to 1.3 and higher + if version >= SpdmVersion::V13 { + // Illegal to return reserved values + if flags.ep_info_cap() == EpInfoCapability::Reserved as u8 || flags.multi_key_cap() == 3 { + return false; + } + + // Check multi_key_cap and pub_key_id_cap + if flags.multi_key_cap() != 0 && flags.pub_key_id_cap() == 1 { + return false; + } + } + + true +} From 3e2e53b8369ca0e417fb5adbcf203f2f5d983a8b Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 9 Mar 2026 18:24:22 +0100 Subject: [PATCH 64/86] Cleanup of commented-out code --- src/commands/algorithms/request.rs | 2 -- src/commands/capabilities/request.rs | 3 +-- src/commands/version/request.rs | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index fce5989..c7787c4 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -130,7 +130,6 @@ pub(crate) fn handle_algorithms_response<'a>( .set_state(crate::state::ConnectionState::AlgorithmsNegotiated); ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::Vca) - // ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::M1) } /// Generate the NEGOTIATE_ALGORITHMS request with all the contexts local information. @@ -267,7 +266,6 @@ pub fn generate_negotiate_algorithms_request<'a>( } ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca) - // ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::M1) } #[cfg(test)] diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index 0f123ea..f52c4b4 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -147,9 +147,8 @@ fn generate_capabilities_request<'a>( .push_data(payload_len) .map_err(|_| (false, CommandError::BufferTooSmall))?; - // Append response to VCA transcript + // Append request to VCA transcript ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) - // ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Generate the GET_CAPABILITIES command using the local capabilities from the context. diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index f82cfe8..eb2b08a 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -37,7 +37,6 @@ pub fn generate_get_version<'a>( .map_err(|_| (false, CommandError::BufferTooSmall))?; ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca) - // ctx.append_message_to_transcript(req_buf, TranscriptContext::M1) } /// Requester function for processing a VERSION response @@ -118,7 +117,6 @@ pub(crate) fn handle_version_response<'a>( .set_state(ConnectionState::AfterVersion); ctx.append_message_to_transcript(resp, TranscriptContext::Vca) - // ctx.append_message_to_transcript(resp, TranscriptContext::M1) } // tests From 9f82ddc6833a70c470bd582a1072da0a114f8e67 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 9 Mar 2026 18:29:22 +0100 Subject: [PATCH 65/86] Use challenge-auth signature context string only when version >= v1.2 --- examples/spdm_requester.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index b3bdc03..53230e2 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -21,7 +21,7 @@ use spdm_lib::protocol::algorithms::{ MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg, }; use spdm_lib::protocol::signature::NONCE_LEN; -use spdm_lib::protocol::{self, version, BaseHashAlgoType}; +use spdm_lib::protocol::{self, version, BaseHashAlgoType, SpdmVersion}; use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities}; // Import platform implementations - no duplicates! @@ -616,17 +616,21 @@ fn verify_challenge_auth_signature( ) -> bool { use signature::Verifier; - // since we verify the responder-generated signature, we have to use the same "responder-" context constant. - let sig_combined_context = protocol::signature::create_responder_signing_context( - ctx.connection_info().version_number(), - protocol::ReqRespCode::ChallengeAuth, - ) - .unwrap(); - if config.verbose { - println!( - "comb_ctx string: '{}'", - String::from_utf8_lossy(&sig_combined_context) - ); + let mut sig_combined_context = Vec::new(); + if ctx.connection_info().version_number() >= SpdmVersion::V12 { + // since we verify the responder-generated signature, we have to use the same "responder-" context constant. + let sig_ctx = protocol::signature::create_responder_signing_context( + ctx.connection_info().version_number(), + protocol::ReqRespCode::ChallengeAuth, + ) + .unwrap(); + sig_combined_context.extend_from_slice(&sig_ctx); + if config.verbose { + println!( + "comb_ctx string: '{}'", + String::from_utf8_lossy(&sig_combined_context) + ); + } } // Get the M1 transcript hash (which is the hash of messages A, B, C) and verify the signature over it. From ec97c8ec586299318defc37e35a78d68c92b7300 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 9 Mar 2026 18:31:44 +0100 Subject: [PATCH 66/86] Fix challenge parsing for version <= v1.2 --- src/commands/challenge/request.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs index df21edc..81bb2c5 100644 --- a/src/commands/challenge/request.rs +++ b/src/commands/challenge/request.rs @@ -161,16 +161,19 @@ pub(crate) fn handle_challenge_auth_response<'a>( .map_err(|e| (true, CommandError::Codec(e)))?; } - // This field shall be identical to the Context field of the corresponding request message. - // TODO: compare it to the context we sent. - // See: src/protocol/common.rs [RequesterContext] - let _requester_context = resp_payload - .data(8) - .map_err(|e| (true, CommandError::Codec(e)))?; + // In v1.3 a 8-byte request context was added before the signature field + if ctx.connection_info().version_number() >= SpdmVersion::V13 { + // This field shall be identical to the Context field of the corresponding request message. + // TODO: compare it to the context we sent. + // See: src/protocol/common.rs [RequesterContext] + let _requester_context = resp_payload + .data(8) + .map_err(|e| (true, CommandError::Codec(e)))?; - resp_payload - .pull_data(8) - .map_err(|e| (true, CommandError::Codec(e)))?; + resp_payload + .pull_data(8) + .map_err(|e| (true, CommandError::Codec(e)))?; + } // We have to use this ugly hack to bring the message buffer into the right form to exclude the signature. // This message buffer thing is totally fucked up... From 7659fdf866eef0392bee6cb3a2f5094fd422402b Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 9 Mar 2026 18:32:53 +0100 Subject: [PATCH 67/86] Only parse supported_slots_mask for version >= v1.3 --- src/commands/digests/request.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs index f62288f..631fb32 100644 --- a/src/commands/digests/request.rs +++ b/src/commands/digests/request.rs @@ -59,10 +59,18 @@ pub(crate) fn handle_digests_response<'a>( .as_mut() .ok_or((true, CommandError::InvalidResponse))?; - peer_cert_store - .set_supported_slots(digests_resp_common.supported_slot_mask) - .map_err(|e| (true, CommandError::CertStore(e)))?; + if version >= SpdmVersion::V13 { + peer_cert_store + .set_supported_slots(digests_resp_common.supported_slot_mask) + .map_err(|e| (true, CommandError::CertStore(e)))?; + } else { + // Set all slots as supported, if supported_slot_mask isn't supported (v1.2 and prior) + peer_cert_store + .set_supported_slots(0xFF) + .map_err(|e| (true, CommandError::CertStore(e)))?; + } + // TODO: Was this intended to do something? for b in 0..digests_resp_common.supported_slot_mask.count_ones() { if (digests_resp_common.supported_slot_mask & (1 << b)) == 1 {} } From 946a48d65a2cd2ec8a293b0f20c027c66af807cf Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 10 Mar 2026 14:02:35 +0100 Subject: [PATCH 68/86] Implement < v1.3 compatibility for challenge command --- src/commands/challenge/mod.rs | 20 +++++++++++--------- src/commands/challenge/request.rs | 16 +++++++++++++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/commands/challenge/mod.rs b/src/commands/challenge/mod.rs index 650fd83..cbb1a8b 100644 --- a/src/commands/challenge/mod.rs +++ b/src/commands/challenge/mod.rs @@ -43,23 +43,29 @@ impl TryFrom for MeasurementSummaryHashType { #[derive(FromBytes, IntoBytes, Immutable)] #[repr(C)] -// TODO: check backwards compatibility of this struct with the original ChallengeReq struct +/// CHALLENGE request message base +/// +/// # Version specific fields for CHALLENGE: +/// Following fields have to be appended, depending on the SPDM version. +/// ## >= v1.3 +/// - `Context`: 8-byte application specific context. +/// Should be all zeros if no context is provided. struct ChallengeReq { + /// `Param1`: `SlotID` + /// /// Slot number of the Responder certificate chain that shall be used for authentication. /// If the public key of the Responder was provisioned to the Requester in a /// trusted environment, the value in this field shall be 0xFF ; otherwise it /// shall be between 0 and 7 inclusive. slot_id: u8, + /// `Param2`: Requested measurement summary hash + /// /// Shall be the type of measurement summary hash requested. measurement_hash_type: u8, /// The Requester should choose a random value. nonce: [u8; NONCE_LEN], - - /// The Requester can include application-specific information in Context. - /// The Requester should fill this field with zeros if it has no context to provide. - context: [u8; CONTEXT_LEN], } impl CommonCodec for ChallengeReq {} @@ -74,19 +80,15 @@ impl ChallengeReq { /// * `measurement_hash_type` - The type of measurement summary hash requested from the /// Responder. /// * `nonce` - A random 32-byte value chosen by the Requester for freshness. - /// * `context` - Optional 8-byte application-specific context. Defaults to all zeros when - /// `None`. pub fn new( slot_id: u8, measurement_hash_type: MeasurementSummaryHashType, nonce: [u8; NONCE_LEN], - context: Option<[u8; CONTEXT_LEN]>, ) -> Self { Self { slot_id, measurement_hash_type: measurement_hash_type as u8, nonce, - context: context.unwrap_or([0; CONTEXT_LEN]), } } } diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs index 81bb2c5..c3e5075 100644 --- a/src/commands/challenge/request.rs +++ b/src/commands/challenge/request.rs @@ -1,5 +1,5 @@ // Licensed under the Apache-2.0 license -use crate::codec::{Codec, MessageBuf}; +use crate::codec::{encode_u8_slice, Codec, MessageBuf}; use crate::commands::challenge::{ ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType, CONTEXT_LEN, NONCE_LEN, OPAQUE_DATA_MAX, @@ -25,7 +25,7 @@ use crate::transcript::TranscriptContext; /// Responder (`None`, `Tcb`, or `All`). /// * `nonce` - A 32-byte random value chosen by the Requester for freshness. /// * `context` - Optional 8-byte application-specific context. Defaults to all zeros when -/// `None`. +/// `None`, ignored for spdm versions < v1.3. /// /// # Errors /// @@ -45,10 +45,20 @@ pub fn generate_challenge_request<'a>( .encode(message_buffer) .map_err(|e| (false, CommandError::Codec(e)))?; - ChallengeReq::new(slot_id, measurement_hash_type.clone(), nonce, context) + ChallengeReq::new(slot_id, measurement_hash_type.clone(), nonce) .encode(message_buffer) .map_err(|e| (false, CommandError::Codec(e)))?; + // Encode 8-byte context string if version >= v1.3 + if ctx.connection_info().version_number() >= SpdmVersion::V13 { + if let Some(ctx_str) = context { + encode_u8_slice(&ctx_str, message_buffer) + .map_err(|e| (true, CommandError::Codec(e)))?; + } else { + encode_u8_slice(&[0; 8], message_buffer).map_err(|e| (true, CommandError::Codec(e)))?; + } + } + ctx.state .peer_cert_store .as_mut() From 2766d121845f0bbde05815c93c8eae04fa21e4ea Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 10 Mar 2026 15:04:16 +0100 Subject: [PATCH 69/86] Cleanup logging in requester example --- examples/spdm_requester.rs | 52 ++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 53230e2..41f2565 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -2,6 +2,7 @@ //! SPDM Example Responder utilizing the requester library. +use std::fmt::Display; use std::io::{Error, ErrorKind, Result as IoResult}; use std::net::TcpStream; @@ -353,6 +354,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { if config.verbose { println!("DIGESTS: {:x?}", &message_buffer.message_data()); } + println!("Successfully retrieved cert chain digests"); // Get peer certificate chain loop { @@ -362,8 +364,10 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { spdm_context .requester_send_request(&mut message_buffer, EID) .unwrap(); - println!("requested GET_CERTIFICATE"); - println!("state: {:?}", spdm_context.connection_info().state()); + if config.verbose { + println!("requested GET_CERTIFICATE"); + println!("state: {:?}", spdm_context.connection_info().state()); + } spdm_context .requester_process_message(&mut message_buffer) @@ -389,9 +393,9 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .unwrap(); let root_hash = store.get_root_hash(0, hash_algo).unwrap(); println!( - "slot 0: Root hash ({hash_algo:?}, {} bytes): {:02x?}", + "slot 0: Root hash ({hash_algo:?}, {} bytes): {}", root_hash.len(), - root_hash + HexString(root_hash) ); let cert_chain = store.get_cert_chain(0, hash_algo).unwrap(); @@ -488,8 +492,8 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { /// Display configuration information fn display_info(config: &RequesterConfig) { - println!("Real SPDM Library Integrated DMTF Compatible Responder"); - println!("====================================================="); + println!("SPDM Library DMTF Compatible Requester Example Flow"); + println!("==================================================="); println!("Configuration:"); println!(" Port: {}", config.port); println!(" Certificate: {}", config.cert_path); @@ -526,15 +530,11 @@ fn display_info(config: &RequesterConfig) { ); println!(); - println!("Clean Platform Implementation Features:"); - println!(" SPDM Versions: 1.2, 1.1"); - println!(" Protocol Processing: Real SPDM library integration"); + println!("Requester Features:"); + println!(" SPDM Versions: 1.0, 1.1, 1.2, 1.3"); println!(" Hash Algorithm: SHA-384 (platform module)"); println!(" Signature Algorithm: ECDSA P-384 (platform module)"); - println!(" Measurements: Demo device measurements (platform module)"); - println!(" Certificates: Static OpenSSL-generated certificate chain (platform module)"); - println!(" Transport: TCP socket with DMTF protocol (platform module)"); - println!(" ✅ NO CODE DUPLICATION - All implementations from unified platform module"); + println!(" Transport: TCP socket with DMTF NONE or MCTP protocol (platform module)"); println!(); } @@ -555,9 +555,11 @@ fn main() -> Result<(), Box> { println!("Connection from: {}", peer_addr); } - // Handle client with real SPDM processing using platform implementations + println!("Starting requester command flow..."); full_flow(stream, &config)?; + println!("Request flow finished successfully."); + Ok(()) } @@ -638,11 +640,29 @@ fn verify_challenge_auth_signature( ctx.transcript_hash(TranscriptContext::M1, &mut transcript_hash) .unwrap(); if config.verbose { - println!("M1/2 hash: {transcript_hash:02x?}"); + println!("M1/2 hash: {}", HexString(&transcript_hash)); } // M denotes the message that is signed. M shall be the concatenation of the combined_spdm_prefix and unverified_message_hash. let m = [sig_combined_context.as_slice(), &transcript_hash].concat(); - pubkey.verify(&m, &signature).is_ok() + let res = pubkey.verify(&m, &signature); + if config.verbose { + if let Err(e) = &res { + println!("Signature verify error: {e}"); + } + } + res.is_ok() +} + +#[derive(Debug)] +struct HexString<'a>(&'a [u8]); + +impl Display for HexString<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for x in self.0 { + write!(f, "{:02X}", x)?; + } + Ok(()) + } } From 58fb82bf654e9f8c1c86fe9942e44ea9389cb041 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 12 Mar 2026 15:33:43 +0100 Subject: [PATCH 70/86] fix: use prehashed signature verification for SPDM v1.1 Signed-off-by: leongross --- examples/spdm_requester.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 41f2565..24292cd 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -616,6 +616,7 @@ fn verify_challenge_auth_signature( signature: Signature, config: &RequesterConfig, ) -> bool { + use p384::ecdsa::signature::hazmat::PrehashVerifier; use signature::Verifier; let mut sig_combined_context = Vec::new(); @@ -646,13 +647,11 @@ fn verify_challenge_auth_signature( // M denotes the message that is signed. M shall be the concatenation of the combined_spdm_prefix and unverified_message_hash. let m = [sig_combined_context.as_slice(), &transcript_hash].concat(); - let res = pubkey.verify(&m, &signature); - if config.verbose { - if let Err(e) = &res { - println!("Signature verify error: {e}"); - } + if ctx.connection_info().version_number() >= SpdmVersion::V12 { + pubkey.verify(&m, &signature).is_ok() + } else { + pubkey.verify_prehash(&m, &signature).is_ok() } - res.is_ok() } #[derive(Debug)] From 33089367542795607e073088851f129809102818 Mon Sep 17 00:00:00 2001 From: leongross Date: Sat, 28 Feb 2026 16:26:26 +0100 Subject: [PATCH 71/86] add missing license headers Signed-off-by: leongross --- examples/platform/cert_store.rs | 14 +++++++++++++- examples/platform/certs.rs | 14 +++++++++++++- examples/platform/crypto.rs | 14 +++++++++++++- examples/platform/evidence.rs | 14 +++++++++++++- examples/platform/mod.rs | 14 +++++++++++++- examples/platform/socket_transport.rs | 14 +++++++++++++- examples/spdm_requester.rs | 14 +++++++++++++- examples/spdm_responder.rs | 14 +++++++++++++- src/cert_store.rs | 14 +++++++++++++- src/chunk_ctx.rs | 14 +++++++++++++- src/codec.rs | 14 +++++++++++++- src/commands/algorithms/mod.rs | 14 +++++++++++++- src/commands/algorithms/request.rs | 14 +++++++++++++- src/commands/algorithms/response.rs | 14 +++++++++++++- src/commands/capabilities/mod.rs | 14 +++++++++++++- src/commands/capabilities/request.rs | 14 +++++++++++++- src/commands/capabilities/response.rs | 14 +++++++++++++- src/commands/certificate/mod.rs | 14 +++++++++++++- src/commands/certificate/request.rs | 14 +++++++++++++- src/commands/certificate/response.rs | 14 +++++++++++++- src/commands/challenge/mod.rs | 14 +++++++++++++- src/commands/challenge/request.rs | 14 +++++++++++++- src/commands/challenge/response.rs | 14 +++++++++++++- src/commands/chunk_get_rsp.rs | 14 +++++++++++++- src/commands/digests/mod.rs | 14 +++++++++++++- src/commands/digests/request.rs | 14 +++++++++++++- src/commands/digests/response.rs | 14 +++++++++++++- src/commands/error_rsp.rs | 14 +++++++++++++- src/commands/measurements_rsp.rs | 14 +++++++++++++- src/commands/mod.rs | 14 +++++++++++++- src/commands/version/mod.rs | 14 +++++++++++++- src/commands/version/request.rs | 14 +++++++++++++- src/commands/version/response.rs | 14 +++++++++++++- src/context.rs | 14 +++++++++++++- src/error.rs | 14 +++++++++++++- src/lib.rs | 14 +++++++++++++- src/measurements/common.rs | 16 ++++++++++++++-- src/measurements/freeform_manifest.rs | 14 +++++++++++++- src/measurements/mod.rs | 14 +++++++++++++- src/platform/evidence.rs | 14 ++++++++++++++ src/platform/hash.rs | 14 ++++++++++++++ src/platform/mod.rs | 14 ++++++++++++++ src/platform/rng.rs | 14 ++++++++++++++ src/platform/transport.rs | 14 ++++++++++++++ src/protocol/algorithms.rs | 14 +++++++++++++- src/protocol/capabilities.rs | 14 +++++++++++++- src/protocol/certs.rs | 14 +++++++++++++- src/protocol/common.rs | 14 +++++++++++++- src/protocol/mod.rs | 14 +++++++++++++- src/protocol/signature.rs | 14 +++++++++++++- src/protocol/version.rs | 14 +++++++++++++- src/requester.rs | 5 ----- src/state.rs | 14 +++++++++++++- src/test.rs | 14 ++++++++++++++ src/transcript.rs | 14 +++++++++++++- src/transport.rs | 14 +++++++++++++- tests/spdm_validator_host.rs | 14 ++++++++++++++ 57 files changed, 736 insertions(+), 55 deletions(-) delete mode 100644 src/requester.rs diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index 7c1c8a9..a0afc7b 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Certificate Store Platform Implementation //! diff --git a/examples/platform/certs.rs b/examples/platform/certs.rs index 988240a..1604998 100644 --- a/examples/platform/certs.rs +++ b/examples/platform/certs.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Static X.509 certificates for SPDM platform implementations //! diff --git a/examples/platform/crypto.rs b/examples/platform/crypto.rs index 8c3a9f4..a3d73c2 100644 --- a/examples/platform/crypto.rs +++ b/examples/platform/crypto.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Cryptographic Platform Implementation //! diff --git a/examples/platform/evidence.rs b/examples/platform/evidence.rs index 09d666d..c89efc0 100644 --- a/examples/platform/evidence.rs +++ b/examples/platform/evidence.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Evidence Platform Implementation //! diff --git a/examples/platform/mod.rs b/examples/platform/mod.rs index b1433e8..722d4b9 100644 --- a/examples/platform/mod.rs +++ b/examples/platform/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Platform implementations for SPDM examples //! diff --git a/examples/platform/socket_transport.rs b/examples/platform/socket_transport.rs index e4b7705..fca72a9 100644 --- a/examples/platform/socket_transport.rs +++ b/examples/platform/socket_transport.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Socket Transport Platform Implementation //! diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 24292cd..a8d8440 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! SPDM Example Responder utilizing the requester library. diff --git a/examples/spdm_responder.rs b/examples/spdm_responder.rs index 7358e6c..697e13f 100644 --- a/examples/spdm_responder.rs +++ b/examples/spdm_responder.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Real SPDM Library Integrated DMTF Compatible Responder //! diff --git a/src/cert_store.rs b/src/cert_store.rs index f011c11..582b82c 100644 --- a/src/cert_store.rs +++ b/src/cert_store.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::error::{SpdmError, SpdmResult}; use crate::protocol::algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}; diff --git a/src/chunk_ctx.rs b/src/chunk_ctx.rs index 9c083d4..df4cbcc 100644 --- a/src/chunk_ctx.rs +++ b/src/chunk_ctx.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::commands::measurements_rsp::MeasurementsResponse; diff --git a/src/codec.rs b/src/codec.rs index 3c7981e..daeb9a0 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use zerocopy::{FromBytes, Immutable, IntoBytes}; diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs index bbbb075..0c2a603 100644 --- a/src/commands/algorithms/mod.rs +++ b/src/commands/algorithms/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Commands related to SPDM Algorithms negotiation //! See DMTF 0274 - SPDM Base Specification v1.3, Section 10.4 ff. diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index c7787c4..4a3255d 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::{ codec::{Codec, MessageBuf}, diff --git a/src/commands/algorithms/response.rs b/src/commands/algorithms/response.rs index 76586d6..ed9540b 100644 --- a/src/commands/algorithms/response.rs +++ b/src/commands/algorithms/response.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::{ codec::{Codec, MessageBuf}, diff --git a/src/commands/capabilities/mod.rs b/src/commands/capabilities/mod.rs index 641e1eb..13641df 100644 --- a/src/commands/capabilities/mod.rs +++ b/src/commands/capabilities/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. pub mod request; pub mod response; diff --git a/src/commands/capabilities/request.rs b/src/commands/capabilities/request.rs index f52c4b4..e95076e 100644 --- a/src/commands/capabilities/request.rs +++ b/src/commands/capabilities/request.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::commands::error_rsp::ErrorCode; use crate::protocol::CapabilityFlags; diff --git a/src/commands/capabilities/response.rs b/src/commands/capabilities/response.rs index 2eca239..8089a2c 100644 --- a/src/commands/capabilities/response.rs +++ b/src/commands/capabilities/response.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use super::*; use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::ErrorCode; diff --git a/src/commands/certificate/mod.rs b/src/commands/certificate/mod.rs index 57d1af4..cd2a076 100644 --- a/src/commands/certificate/mod.rs +++ b/src/commands/certificate/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. pub mod request; pub mod response; diff --git a/src/commands/certificate/request.rs b/src/commands/certificate/request.rs index 5925350..17b8263 100644 --- a/src/commands/certificate/request.rs +++ b/src/commands/certificate/request.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::{Codec, MessageBuf}; use crate::commands::certificate::{ diff --git a/src/commands/certificate/response.rs b/src/commands/certificate/response.rs index 8a16a8d..f0bcc0f 100644 --- a/src/commands/certificate/response.rs +++ b/src/commands/certificate/response.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::cert_store::{cert_slot_mask, MAX_CERT_SLOTS_SUPPORTED}; use crate::codec::{Codec, MessageBuf}; diff --git a/src/commands/challenge/mod.rs b/src/commands/challenge/mod.rs index cbb1a8b..ff8a7b7 100644 --- a/src/commands/challenge/mod.rs +++ b/src/commands/challenge/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::CommonCodec; use crate::protocol::SHA384_HASH_SIZE; diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs index c3e5075..a579986 100644 --- a/src/commands/challenge/request.rs +++ b/src/commands/challenge/request.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::{encode_u8_slice, Codec, MessageBuf}; use crate::commands::challenge::{ ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType, CONTEXT_LEN, NONCE_LEN, diff --git a/src/commands/challenge/response.rs b/src/commands/challenge/response.rs index 67da35c..6de852c 100644 --- a/src/commands/challenge/response.rs +++ b/src/commands/challenge/response.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::cert_store::MAX_CERT_SLOTS_SUPPORTED; use crate::codec::{Codec, MessageBuf}; use crate::commands::algorithms::selected_measurement_specification; diff --git a/src/commands/chunk_get_rsp.rs b/src/commands/chunk_get_rsp.rs index 3c1fbc9..02d5cd2 100644 --- a/src/commands/chunk_get_rsp.rs +++ b/src/commands/chunk_get_rsp.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::chunk_ctx::ChunkError; use crate::chunk_ctx::LargeResponse; use crate::codec::{Codec, CommonCodec, MessageBuf}; diff --git a/src/commands/digests/mod.rs b/src/commands/digests/mod.rs index f208497..fc3fe07 100644 --- a/src/commands/digests/mod.rs +++ b/src/commands/digests/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::cert_store::SpdmCertStore; use crate::codec::{CommonCodec, MessageBuf}; diff --git a/src/commands/digests/request.rs b/src/commands/digests/request.rs index 631fb32..b5de799 100644 --- a/src/commands/digests/request.rs +++ b/src/commands/digests/request.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::Codec; use crate::context::SpdmContext; diff --git a/src/commands/digests/response.rs b/src/commands/digests/response.rs index a0bfa5f..658ea15 100644 --- a/src/commands/digests/response.rs +++ b/src/commands/digests/response.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::cert_store::cert_slot_mask; use crate::codec::{Codec, MessageBuf}; diff --git a/src/commands/error_rsp.rs b/src/commands/error_rsp.rs index 1de6c26..cf490c5 100644 --- a/src/commands/error_rsp.rs +++ b/src/commands/error_rsp.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::{Codec, CommonCodec, MessageBuf}; use crate::error::CommandError; diff --git a/src/commands/measurements_rsp.rs b/src/commands/measurements_rsp.rs index 4d8fdbb..e2f8e23 100644 --- a/src/commands/measurements_rsp.rs +++ b/src/commands/measurements_rsp.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::cert_store::SpdmCertStore; use crate::chunk_ctx::{ChunkError, LargeResponse}; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 838dfae..f026e2f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. pub mod algorithms; pub mod capabilities; diff --git a/src/commands/version/mod.rs b/src/commands/version/mod.rs index cfdb0e0..792ced4 100644 --- a/src/commands/version/mod.rs +++ b/src/commands/version/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. pub mod request; pub mod response; diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index eb2b08a..d5f6955 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::{ codec::{Codec, MessageBuf}, diff --git a/src/commands/version/response.rs b/src/commands/version/response.rs index f930620..cee08ba 100644 --- a/src/commands/version/response.rs +++ b/src/commands/version/response.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::{Codec, MessageBuf}; use crate::commands::error_rsp::ErrorCode; diff --git a/src/context.rs b/src/context.rs index 301765c..a69c598 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // use crate::cert_mgr::DeviceCertsManager; use crate::cert_store::*; diff --git a/src/error.rs b/src/error.rs index 8830595..de5df50 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // use crate::cert_mgr::DeviceCertsMgrError; use crate::cert_store::CertStoreError; diff --git a/src/lib.rs b/src/lib.rs index daf45c5..549de3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #![cfg_attr(not(test), no_std)] diff --git a/src/measurements/common.rs b/src/measurements/common.rs index 6e46237..a1487e5 100644 --- a/src/measurements/common.rs +++ b/src/measurements/common.rs @@ -1,8 +1,20 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::commands::challenge::MeasurementSummaryHashType; use crate::measurements::freeform_manifest::FreeformManifest; use crate::platform::evidence::{SpdmEvidence, SpdmEvidenceError}; use crate::platform::hash::{SpdmHash, SpdmHashError}; -use crate::commands::challenge::MeasurementSummaryHashType; use crate::protocol::{algorithms::AsymAlgo, MeasurementSpecification, SHA384_HASH_SIZE}; use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; diff --git a/src/measurements/freeform_manifest.rs b/src/measurements/freeform_manifest.rs index 6e685ea..9455800 100644 --- a/src/measurements/freeform_manifest.rs +++ b/src/measurements/freeform_manifest.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::commands::challenge::MeasurementSummaryHashType; use crate::measurements::common::{ diff --git a/src/measurements/mod.rs b/src/measurements/mod.rs index 09d1cff..bab3658 100644 --- a/src/measurements/mod.rs +++ b/src/measurements/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. pub mod common; pub mod freeform_manifest; diff --git a/src/platform/evidence.rs b/src/platform/evidence.rs index 8710329..fd9ff61 100644 --- a/src/platform/evidence.rs +++ b/src/platform/evidence.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub const PCR_QUOTE_BUFFER_SIZE: usize = 0x1984; pub type SpdmEvidenceResult = Result; diff --git a/src/platform/hash.rs b/src/platform/hash.rs index 2992c95..12269da 100644 --- a/src/platform/hash.rs +++ b/src/platform/hash.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub type SpdmHashResult = Result; pub trait SpdmHash { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 28699ab..db8bf60 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub mod evidence; pub mod hash; pub mod rng; diff --git a/src/platform/rng.rs b/src/platform/rng.rs index eb71cc4..5ead220 100644 --- a/src/platform/rng.rs +++ b/src/platform/rng.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub type SpdmRngResult = Result; #[derive(Debug, PartialEq)] diff --git a/src/platform/transport.rs b/src/platform/transport.rs index fcc4cc8..e9c7588 100644 --- a/src/platform/transport.rs +++ b/src/platform/transport.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::codec::{CodecError, MessageBuf}; pub type TransportResult = Result; diff --git a/src/protocol/algorithms.rs b/src/protocol/algorithms.rs index d6c1d0b..e203cfb 100644 --- a/src/protocol/algorithms.rs +++ b/src/protocol/algorithms.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::error::{SpdmError, SpdmResult}; use bitfield::bitfield; diff --git a/src/protocol/capabilities.rs b/src/protocol/capabilities.rs index a182bf3..1401ccd 100644 --- a/src/protocol/capabilities.rs +++ b/src/protocol/capabilities.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; diff --git a/src/protocol/certs.rs b/src/protocol/certs.rs index f4761c1..41982a3 100644 --- a/src/protocol/certs.rs +++ b/src/protocol/certs.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; use bitfield::bitfield; use zerocopy::{little_endian, FromBytes, Immutable, IntoBytes, KnownLayout}; diff --git a/src/protocol/common.rs b/src/protocol/common.rs index 620fccd..83d3af8 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::CommonCodec; use crate::error::{SpdmError, SpdmResult}; diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 3143eda..59cae87 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. pub mod algorithms; pub mod capabilities; diff --git a/src/protocol/signature.rs b/src/protocol/signature.rs index 43ab2bd..71afa24 100644 --- a/src/protocol/signature.rs +++ b/src/protocol/signature.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::platform::hash::{SpdmHash, SpdmHashError}; use crate::protocol::*; diff --git a/src/protocol/version.rs b/src/protocol/version.rs index b4bf552..a12aa3f 100644 --- a/src/protocol/version.rs +++ b/src/protocol/version.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::error::{SpdmError, SpdmResult}; diff --git a/src/requester.rs b/src/requester.rs deleted file mode 100644 index 0cf38a0..0000000 --- a/src/requester.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Licensed under the Apache-2.0 license - -struct Requester<'a> { - context: -} diff --git a/src/state.rs b/src/state.rs index 5ca2759..26e59d0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::{ cert_store::PeerCertStore, diff --git a/src/test.rs b/src/test.rs index 2c8a410..4b6c8bc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::{ cert_store::{CertStoreResult, SpdmCertStore}, context::SpdmContext, diff --git a/src/transcript.rs b/src/transcript.rs index 6b5f049..77b7909 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::platform::hash::{SpdmHash, SpdmHashError}; use crate::protocol::{SpdmVersion, SHA384_HASH_SIZE}; diff --git a/src/transport.rs b/src/transport.rs index 0b733fd..faadc96 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -1,4 +1,16 @@ -// Licensed under the Apache-2.0 license +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::codec::MessageBuf; use crate::codec::{Codec, CodecError, CommonCodec, DataKind}; diff --git a/tests/spdm_validator_host.rs b/tests/spdm_validator_host.rs index 56b77b9..fc2034e 100644 --- a/tests/spdm_validator_host.rs +++ b/tests/spdm_validator_host.rs @@ -1,3 +1,17 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + //! Basic instantiation test for SpdmContext (host validator) //! Run with: cargo test --features std -- --nocapture From bd7b43f5205ef9e929d1f67ffd1b194963d7864d Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 13 Mar 2026 16:46:45 +0100 Subject: [PATCH 72/86] fix minimum required length check for responder Signed-off-by: leongross --- src/commands/algorithms/mod.rs | 3 +-- src/commands/algorithms/response.rs | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs index 0c2a603..474b046 100644 --- a/src/commands/algorithms/mod.rs +++ b/src/commands/algorithms/mod.rs @@ -188,8 +188,7 @@ impl NegotiateAlgorithmsReq { + size_of::() + total_alg_struct_len + total_ext_asym_len - + total_ext_hash_len - + 4) as u16 + + total_ext_hash_len) as u16 } /// Calculate the size of the extended algorithm structures in bytes. diff --git a/src/commands/algorithms/response.rs b/src/commands/algorithms/response.rs index ed9540b..adf0631 100644 --- a/src/commands/algorithms/response.rs +++ b/src/commands/algorithms/response.rs @@ -134,6 +134,13 @@ fn process_negotiate_algorithms_request<'a>( if fixed_alg_count != 2 { Err(ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None))?; } + + // Skip extended algorithm structures embedded in this AlgStructure + for _ in 0..ext_alg_count { + ExtendedAlgo::decode(req_payload).map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?; + } } // Total number of extended algorithms check From fadd05cc6134a5fc82bd1a97dc2b82218526e058 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 13 Mar 2026 17:12:20 +0100 Subject: [PATCH 73/86] fix NEGOTIATE_ALGORITHMS size Signed-off-by: leongross --- examples/spdm_requester.rs | 2 +- src/commands/algorithms/mod.rs | 8 +++++--- src/commands/algorithms/request.rs | 30 +++++++++++++++--------------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index a8d8440..335143f 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -323,7 +323,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { &mut message_buffer, Some(&ext_asym), Some(&ext_hash), - alg_structure, + Some(alg_structure), Some(&alg_external), ) .unwrap(); diff --git a/src/commands/algorithms/mod.rs b/src/commands/algorithms/mod.rs index 474b046..1a3e601 100644 --- a/src/commands/algorithms/mod.rs +++ b/src/commands/algorithms/mod.rs @@ -150,9 +150,10 @@ impl NegotiateAlgorithmsReq { other_param_support: OtherParamSupport, base_asym_algo: BaseAsymAlgo, base_hash_algo: BaseHashAlgo, - ext_asyn_count: u8, + ext_asym_count: u8, ext_hash_count: u8, mel_specification: MelSpecification, + alg_ext_count: u8, ) -> SpdmResult { let mut req = NegotiateAlgorithmsReq { num_alg_struct_tables, @@ -163,13 +164,14 @@ impl NegotiateAlgorithmsReq { base_asym_algo, base_hash_algo, reserved_1: [0u8; 12], - ext_asym_count: ext_asyn_count, + ext_asym_count, ext_hash_count, reserved_2: 0, mel_specification, }; - req.length = req.min_req_len(); + req.length = req.min_req_len() + + (size_of::() * alg_ext_count as usize) as u16; if req.length > MAX_SPDM_REQUEST_LENGTH { return Err(SpdmError::InvalidParam); diff --git a/src/commands/algorithms/request.rs b/src/commands/algorithms/request.rs index 4a3255d..7f8d2c1 100644 --- a/src/commands/algorithms/request.rs +++ b/src/commands/algorithms/request.rs @@ -169,7 +169,7 @@ pub fn generate_negotiate_algorithms_request<'a>( req_buf: &mut MessageBuf<'a>, ext_asym: Option<&'a [ExtendedAlgo]>, ext_hash: Option<&'a [ExtendedAlgo]>, - req_alg_struct: AlgStructure, + req_alg_struct: Option, alg_external: Option<&'a [ExtendedAlgo]>, // req_alg_struct.AlgCount.ExtAlgCount many ) -> CommandResult<()> { let local_algorithms = &ctx.local_algorithms.device_algorithms; @@ -184,9 +184,12 @@ pub fn generate_negotiate_algorithms_request<'a>( None => 0, }; + let num_alg_struct_tables = req_alg_struct.is_some() as u8; + let alg_ext_count = req_alg_struct.map_or(0, |s| s.ext_alg_count()); + // Generate base structure **without** the variable length structures let negotiate_algorithms_req = NegotiateAlgorithmsReq::new( - req_alg_struct.ext_alg_count(), + num_alg_struct_tables, 0, // param2 local_algorithms.measurement_spec, local_algorithms.other_param_support, @@ -195,6 +198,7 @@ pub fn generate_negotiate_algorithms_request<'a>( ext_asym_count, ext_hash_count, local_algorithms.mel_specification, + alg_ext_count, ) .map_err(|_| (false, CommandError::UnsupportedRequest))?; @@ -253,27 +257,23 @@ pub fn generate_negotiate_algorithms_request<'a>( } } - // 3.2 - if req_alg_struct.fixed_alg_count() != 0 && !req_alg_struct.is_multiple() { - return Err((false, CommandError::UnsupportedRequest)); - } + // 3.3 + 3.4: encode AlgStructure and its extended algorithms if present + if let Some(alg_struct) = req_alg_struct { + if alg_struct.fixed_alg_count() != 0 && !alg_struct.is_multiple() { + return Err((false, CommandError::UnsupportedRequest)); + } - // If this is 1, we have an additional extended algorithm structure to add. - if negotiate_algorithms_req.num_alg_struct_tables > 0 { - // 3.3 - req_alg_struct + alg_struct .encode(req_buf) .map_err(|e| (false, CommandError::Codec(e)))?; - // 3.4 - if let Some(extended_algos) = alg_external { + // If ext_alg_count > 0, we must have the extended algorithm structures. + if alg_struct.ext_alg_count() > 0 { + let extended_algos = alg_external.ok_or((false, CommandError::UnsupportedRequest))?; for ext in extended_algos { ext.encode(req_buf) .map_err(|e| (false, CommandError::Codec(e)))?; } - } else { - // If ext_alg_count > 0, we must have the extended algorithm structure. - return Err((false, CommandError::UnsupportedRequest)); } } From 36a5f63aa6003958210126f0319e5cda485ba3dd Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 13 Mar 2026 18:37:58 +0100 Subject: [PATCH 74/86] fix signature generation and verification Signed-off-by: leongross --- examples/certs/ca.cert.der | Bin 0 -> 472 bytes examples/certs/end_responder.cert.der | Bin 0 -> 587 bytes examples/certs/end_responder.key.der | Bin 0 -> 167 bytes examples/certs/inter.cert.der | Bin 0 -> 480 bytes examples/platform/cert_store.rs | 52 +++++------- examples/platform/certs.rs | 113 ++++---------------------- examples/platform/mod.rs | 2 +- src/commands/algorithms/response.rs | 6 +- 8 files changed, 39 insertions(+), 134 deletions(-) create mode 100644 examples/certs/ca.cert.der create mode 100644 examples/certs/end_responder.cert.der create mode 100644 examples/certs/end_responder.key.der create mode 100644 examples/certs/inter.cert.der diff --git a/examples/certs/ca.cert.der b/examples/certs/ca.cert.der new file mode 100644 index 0000000000000000000000000000000000000000..8c6f5256099d4c11c8743a57d4a6ab57a93f70ad GIT binary patch literal 472 zcmXqLV!UF|#2B@JnTe5!Nkq}{uHTy#O02@2lP9{b+R_#U>1|{Ys1{P5nZpYv4R}~&q{fJ0^f8cH6`iT9L zf{fR-udH|LzOwE8DxGNl7Rd!Y0<#-^uBX%)o|jc-kuVTzz^;HFq(GRF@jnZz0W*+74nF1#27^>411+PeI{#P6 z8wcg*{B-j&=c|3Th@zD#aV+T8jiII&}yOD)Ki8+aZg;!>$g3O(_ zCsJM(EKU--b>&~=lyiI6UB5Kt$z#Eeik%$?-bO!k^t!Kc+mqj-F(_lJPnIBe#Kt)X zecc^J0EAEwAZPyVEd-YWzc{0E8%EHBU6_Se^_ZT$pG~fX` zR92Xg@jnZb0fPZIh{w;u!py|_z(5wn;bReF5fQQRPdk1+!J$6=;M9!G?HYDRm75I= zLDGUOS_T>m)Y!PR*%(|*My12WBSQ#1_o0yuJTUZ(xXo0k- zf-F-okYnQj>S1MNXJ&-6nBXiqDX zUU^@5Hng(;u?e25xq#VE3_v@0SlJ&8i09vK6jsO4v literal 0 HcmV?d00001 diff --git a/examples/certs/end_responder.key.der b/examples/certs/end_responder.key.der new file mode 100644 index 0000000000000000000000000000000000000000..8dcaa6c63d05d8c4fb6b77931bb962161b2bf3c4 GIT binary patch literal 167 zcmV;Y09gMpfusTf0R%9;g#uI&M(mzb$|?+W3f$OTpjEqpNcb)OnlgfH7(cGkm6 zWlWf0k(<4dmX`*g2L=Tzfdl{|p=1MM00a#jxgZ?e?Z{;5aHC=p+SLA8l+L}b*V2^a z;}M8>xro5+Tj4=U-zeKj4>*BRXthjg5erzcoWV^;K@=Zt)>o{fpV7Hw-UkurF?il} VI=3tpR_gU2W0Mavsd1xzco`?YOT7R9 literal 0 HcmV?d00001 diff --git a/examples/certs/inter.cert.der b/examples/certs/inter.cert.der new file mode 100644 index 0000000000000000000000000000000000000000..b8199869eeb2e7cdab4ddf332ec74e259256e6b1 GIT binary patch literal 480 zcmXqLV!UI}#F)5%nTe5!iILHOi;Y98&EuRc3p2BUqM^KjEE{tu3p0IJy2G7wmi3d|PXG3Hx$)ihw)--d{h{SdrWeiCGnTwy@oO*bj|^Yguw#qO z^i#j2g;xaJ;jP*yY-5w$HsBHB&KT9cMuP>n(rZXL6t46z0FJ zU*1`e=iax!<$qV2s5aJ3Pw@e2`FT7h#MXJzECbs_DY+l1fmg07Ot{qg!R4Mq>noGQ XyV?H|_HrG#ZR@PF_R5i$zm$vt1reFQ literal 0 HcmV?d00001 diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index a0afc7b..0d8ccf1 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -24,7 +24,7 @@ use p384::{ }; use zerocopy::FromBytes; -use super::certs::{STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; +use super::certs::{STATIC_END_CERT, STATIC_END_RESPONDER_KEY_DER, STATIC_INTER_CERT, STATIC_ROOT_CA_CERT}; use spdm_lib::commands::challenge::MeasurementSummaryHashType; use spdm_lib::protocol::{ algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}, @@ -58,36 +58,22 @@ impl DemoCertStore { } fn generate_certificate_chain() -> (Vec, SigningKey) { - use crate::platform::certs::ATTESTATION_PRIVATE_KEY; - - println!("🔧 DIRECT CERTIFICATE CHAIN - RAW CONCATENATION"); - - // SIMPLE APPROACH: Just concatenate Root CA + Attestation certificates - // Let the SPDM library handle its own formatting + // Concatenate Root CA + Intermediate + End-entity certificates let mut cert_chain = Vec::new(); cert_chain.extend_from_slice(STATIC_ROOT_CA_CERT); - cert_chain.extend_from_slice(STATIC_ATTESTATION_CERT); - - println!( - " ✅ Raw certificates: Root({}) + Attestation({})", - STATIC_ROOT_CA_CERT.len(), - STATIC_ATTESTATION_CERT.len() - ); - println!(" Total length: {} bytes", cert_chain.len()); - println!(" Root starts: {:02x?}", &cert_chain[..4]); - println!( - " Attestation starts: {:02x?}", - &cert_chain[STATIC_ROOT_CA_CERT.len()..STATIC_ROOT_CA_CERT.len() + 4] - ); - - let secret_key = SecretKey::from_bytes(ATTESTATION_PRIVATE_KEY.into()) - .expect("Failed to parse secret key from static data"); - - let attestation_key = SigningKey::from(secret_key); + cert_chain.extend_from_slice(STATIC_INTER_CERT); + cert_chain.extend_from_slice(STATIC_END_CERT); - println!("🔑 Static attestation signing key loaded"); + // Parse the P-384 private key from SEC1 DER format. + // SEC1 ECPrivateKey: SEQUENCE { version INTEGER, privateKey OCTET STRING(48), ... } + // For a P-384 key with 2-byte length header: skip 8 bytes to reach the raw 48-byte scalar. + let raw_key: &[u8; 48] = STATIC_END_RESPONDER_KEY_DER[8..56] + .try_into() + .expect("key DER too short"); + let secret_key = SecretKey::from_bytes(raw_key.into()) + .expect("Failed to parse end-entity private key"); - (cert_chain, attestation_key) + (cert_chain, SigningKey::from(secret_key)) } /// Extract the first certificate from a DER-encoded certificate chain @@ -262,9 +248,9 @@ impl SpdmCertStore for DemoCertStore { fn test_signing() { use p384::ecdsa::signature::SignatureEncoding; - // Create a known private key - let private_key_bytes = ATTESTATION_PRIVATE_KEY.to_vec(); - let secret_key = SecretKey::from_bytes((&private_key_bytes[..]).into()).unwrap(); + // Load private key from SEC1 DER (raw 48-byte scalar at offset 8) + let raw_key: &[u8; 48] = STATIC_END_RESPONDER_KEY_DER[8..56].try_into().unwrap(); + let secret_key = SecretKey::from_bytes(raw_key.into()).unwrap(); let signing_key = SigningKey::from(secret_key); // Your input @@ -295,9 +281,9 @@ fn debug_signing_verification() { let input_hex = "32ac91a55d17db5e537448789486c633ecba4cd49185d0933f3d6561573fb68931f88bef4dc6ef20602df7dbeb51086b"; let input = hex::decode(input_hex).unwrap(); - // Create signing key - let private_key_bytes = ATTESTATION_PRIVATE_KEY.to_vec(); - let secret_key = SecretKey::from_bytes((&private_key_bytes[..]).into()).unwrap(); + // Load private key from SEC1 DER (raw 48-byte scalar at offset 8) + let raw_key: &[u8; 48] = STATIC_END_RESPONDER_KEY_DER[8..56].try_into().unwrap(); + let secret_key = SecretKey::from_bytes(raw_key.into()).unwrap(); let signing_key = SigningKey::from(secret_key); // Get public key diff --git a/examples/platform/certs.rs b/examples/platform/certs.rs index 1604998..5212443 100644 --- a/examples/platform/certs.rs +++ b/examples/platform/certs.rs @@ -12,102 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Static X.509 certificates for SPDM platform implementations +//! DMTF libspdm ECP384 test certificate chain for SPDM platform implementations. //! -//! These certificates are generated by OpenSSL and known to work correctly -//! with certificate verification. Generated on: September 25, 2025 -//! -//! -pub const ATTESTATION_PRIVATE_KEY: &[u8] = &[ - 0xd5, 0x76, 0x17, 0x2e, 0xe3, 0x5f, 0x3e, 0x62, 0xb2, 0xbd, 0x0c, 0x5e, 0x7e, 0x6d, 0x8c, 0xe3, - 0xa6, 0x05, 0xfe, 0x27, 0x80, 0x17, 0x37, 0x85, 0x8b, 0x76, 0xa7, 0xd7, 0xfd, 0x8c, 0x0d, 0x26, - 0x28, 0x41, 0x7a, 0x8b, 0xb6, 0xbc, 0x17, 0x18, 0xc6, 0x9a, 0x10, 0x5c, 0x1e, 0xc8, 0x11, 0x70, -]; +//! Certificates sourced from: spdm-emu/libspdm/unit_test/sample_key/ecp384/ +//! - ca.cert.der: root CA (matches examples/cert/ecp384_ca.cert.der used by the requester) +//! - inter.cert.der: intermediate CA signed by the root +//! - end_responder.cert.der: end-entity cert signed by the intermediate CA +//! - end_responder.key.der: SEC1 DER-encoded P-384 private key for the end-entity cert + +/// DMTF libspdm ECP384 root CA certificate (DER-encoded). +/// This is the same cert as examples/cert/ecp384_ca.cert.der used by the requester. +pub const STATIC_ROOT_CA_CERT: &[u8] = include_bytes!("../certs/ca.cert.der"); + +/// DMTF libspdm ECP384 intermediate CA certificate (DER-encoded). +pub const STATIC_INTER_CERT: &[u8] = include_bytes!("../certs/inter.cert.der"); -pub const STATIC_ROOT_CA_CERT: &[u8] = &[ - 0x30, 0x82, 0x02, 0x5d, 0x30, 0x82, 0x01, 0xe3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x66, - 0xd4, 0x94, 0x26, 0x31, 0x59, 0xc1, 0x0e, 0xfe, 0xe2, 0xf1, 0x74, 0x8a, 0x4f, 0xb3, 0xd4, 0x13, - 0x66, 0x7f, 0x93, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, - 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, - 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, - 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x18, 0x30, 0x16, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, - 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x39, 0x32, 0x37, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, - 0x17, 0x0d, 0x33, 0x35, 0x30, 0x39, 0x32, 0x35, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x30, - 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, - 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, - 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x18, 0x30, 0x16, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, - 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, - 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xbd, 0x63, 0x0d, 0x67, 0x2e, 0x64, 0xa3, 0x55, 0x11, - 0x19, 0x9a, 0x1f, 0x66, 0x7c, 0xc5, 0x9c, 0x61, 0x62, 0x3d, 0x40, 0xb6, 0xe1, 0xff, 0x43, 0x7c, - 0x39, 0x27, 0xc8, 0xec, 0xe9, 0x12, 0x3a, 0xa8, 0xce, 0x53, 0x19, 0x06, 0xd1, 0xab, 0x4d, 0xd8, - 0x04, 0x36, 0xc2, 0xb8, 0x8d, 0xc1, 0x20, 0xe5, 0x0c, 0x34, 0x5e, 0x51, 0x8c, 0x73, 0x1f, 0x67, - 0xe9, 0xad, 0x64, 0x72, 0x58, 0x9c, 0x01, 0xb1, 0x38, 0xd6, 0x7b, 0xd0, 0xad, 0xf6, 0x44, 0xa9, - 0xa8, 0x16, 0xb6, 0x36, 0x10, 0xa8, 0xdc, 0x03, 0x35, 0x8f, 0x4f, 0xa7, 0x5d, 0x00, 0x0e, 0x78, - 0xd8, 0xee, 0x73, 0x8b, 0xfc, 0x07, 0x0a, 0xa3, 0x42, 0x30, 0x40, 0x30, 0x0f, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5a, 0xc3, 0x33, 0x0a, 0x3d, 0x7f, 0xe8, 0xc1, 0x4b, - 0x3d, 0xb8, 0x28, 0x80, 0xae, 0xb0, 0xfd, 0xab, 0x9d, 0x60, 0xaa, 0x30, 0x0a, 0x06, 0x08, 0x2a, - 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x31, 0x00, 0x92, - 0x51, 0x80, 0x7b, 0x10, 0xef, 0xb7, 0x03, 0x5a, 0xdb, 0x94, 0xca, 0x2f, 0x10, 0x32, 0xac, 0xa5, - 0xba, 0xcc, 0x9b, 0x24, 0xe0, 0xfc, 0xed, 0x95, 0xfb, 0x6d, 0x2e, 0x8e, 0xef, 0xfb, 0x52, 0xdf, - 0xf0, 0x29, 0x6f, 0xcf, 0x46, 0x49, 0xaf, 0xdf, 0x53, 0x2f, 0xc7, 0xc5, 0x5d, 0x71, 0xb1, 0x02, - 0x30, 0x68, 0x76, 0x6f, 0x7e, 0x02, 0xf7, 0x6f, 0xbf, 0x50, 0xfd, 0x50, 0xae, 0xc0, 0x48, 0xa9, - 0xd6, 0x97, 0x9e, 0x32, 0x69, 0x2d, 0x00, 0x94, 0xa4, 0x53, 0x6f, 0x9c, 0x24, 0x23, 0x6c, 0xa6, - 0x98, 0x16, 0x80, 0xe1, 0xe6, 0xc9, 0x39, 0x1a, 0x53, 0xd6, 0xb1, 0x54, 0x74, 0xad, 0x8d, 0x89, - 0xde, -]; +/// DMTF libspdm ECP384 end-entity (responder) certificate (DER-encoded). +pub const STATIC_END_CERT: &[u8] = include_bytes!("../certs/end_responder.cert.der"); -pub const STATIC_ATTESTATION_CERT: &[u8] = &[ - 0x30, 0x82, 0x02, 0xab, 0x30, 0x82, 0x02, 0x31, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x45, - 0x7b, 0xc1, 0xae, 0x0b, 0xb9, 0x70, 0x17, 0x22, 0x64, 0xfb, 0xf2, 0xb7, 0xac, 0xf4, 0x3a, 0xaa, - 0xc0, 0x7c, 0x31, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, - 0x6e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, - 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, - 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x18, 0x30, 0x16, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, - 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x39, 0x32, 0x37, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, - 0x17, 0x0d, 0x33, 0x35, 0x30, 0x39, 0x32, 0x35, 0x30, 0x31, 0x32, 0x36, 0x34, 0x31, 0x5a, 0x30, - 0x7f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, - 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, - 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, - 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x1d, 0x30, 0x1b, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x14, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x0c, 0x1b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x4b, 0x65, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, - 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xd6, 0x09, 0x5e, 0x5f, 0xc4, 0x2d, 0xa7, 0xf6, - 0x9f, 0x8d, 0xdf, 0xc1, 0x9f, 0xd1, 0x20, 0xb8, 0x25, 0x1a, 0x9c, 0xbf, 0xf7, 0x61, 0x5e, 0xce, - 0xfd, 0x67, 0xff, 0x72, 0x3e, 0xbf, 0xe8, 0x21, 0xca, 0x2b, 0xc2, 0xba, 0x7b, 0x81, 0x17, 0x29, - 0xb3, 0x33, 0x13, 0xbc, 0x07, 0xaa, 0xe7, 0x45, 0x4e, 0xb5, 0xe2, 0x2f, 0x9f, 0xcf, 0x7b, 0x06, - 0x5e, 0x27, 0x3f, 0x15, 0x42, 0x1e, 0xd0, 0x16, 0xdb, 0x83, 0x1b, 0x9c, 0xef, 0xff, 0xf4, 0xe5, - 0x9a, 0xf1, 0x16, 0x58, 0x55, 0x3d, 0x14, 0x34, 0x76, 0x1a, 0x59, 0x01, 0x23, 0x11, 0x7b, 0x9f, - 0xc0, 0xa5, 0x4f, 0x96, 0x07, 0x73, 0xfc, 0x9b, 0xa3, 0x7f, 0x30, 0x7d, 0x30, 0x0c, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x06, 0xc0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, - 0x04, 0x16, 0x04, 0x14, 0xc9, 0x9a, 0xce, 0x7f, 0x7d, 0xc4, 0xa1, 0xb4, 0xac, 0x8d, 0x56, 0x39, - 0xdd, 0x44, 0x7f, 0x19, 0x50, 0x8a, 0xb3, 0x88, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0x5a, 0xc3, 0x33, 0x0a, 0x3d, 0x7f, 0xe8, 0xc1, 0x4b, 0x3d, 0xb8, - 0x28, 0x80, 0xae, 0xb0, 0xfd, 0xab, 0x9d, 0x60, 0xaa, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x30, 0x4d, 0x81, 0x2b, 0xf4, - 0xc1, 0x7a, 0x60, 0x37, 0xd7, 0x94, 0x3e, 0xdd, 0x86, 0x3e, 0xab, 0xd6, 0xfd, 0x59, 0x45, 0xee, - 0x2a, 0x4e, 0xa3, 0xa0, 0x38, 0xb0, 0x59, 0x5a, 0x75, 0xbf, 0x29, 0x35, 0x00, 0xa1, 0x44, 0x0d, - 0x00, 0xd0, 0x6b, 0xec, 0x54, 0x8d, 0xab, 0x60, 0x75, 0xdf, 0xa9, 0x68, 0x02, 0x31, 0x00, 0xa2, - 0x14, 0xe4, 0x0c, 0x4b, 0x27, 0xb5, 0xd4, 0xad, 0xa0, 0x53, 0x67, 0x24, 0x1b, 0x19, 0x3f, 0x00, - 0x88, 0x69, 0x0c, 0x30, 0xe8, 0x24, 0xd9, 0x11, 0xd3, 0x16, 0xb6, 0x0d, 0x54, 0x1d, 0x2a, 0x52, - 0xb8, 0xd7, 0x33, 0x57, 0x3f, 0xaf, 0xf9, 0x6a, 0x42, 0x8f, 0xe5, 0xd9, 0x15, 0x23, 0xbc, -]; +/// SEC1 DER-encoded P-384 private key for the end-entity responder certificate. +pub const STATIC_END_RESPONDER_KEY_DER: &[u8] = include_bytes!("../certs/end_responder.key.der"); diff --git a/examples/platform/mod.rs b/examples/platform/mod.rs index 722d4b9..85e8a0b 100644 --- a/examples/platform/mod.rs +++ b/examples/platform/mod.rs @@ -32,4 +32,4 @@ pub use evidence::DemoEvidence; pub use socket_transport::SpdmSocketTransport; // Certificate constants available for examples that need them #[allow(unused_imports)] -pub use certs::{ATTESTATION_PRIVATE_KEY, STATIC_ATTESTATION_CERT, STATIC_ROOT_CA_CERT}; +pub use certs::{STATIC_END_CERT, STATIC_END_RESPONDER_KEY_DER, STATIC_INTER_CERT, STATIC_ROOT_CA_CERT}; diff --git a/src/commands/algorithms/response.rs b/src/commands/algorithms/response.rs index adf0631..3ab020b 100644 --- a/src/commands/algorithms/response.rs +++ b/src/commands/algorithms/response.rs @@ -179,8 +179,7 @@ fn process_negotiate_algorithms_request<'a>( .set_peer_algorithms(peer_algorithms); // Append NEGOTIATE_ALGORITHMS to the transcript VCA context - // ctx.append_message_to_transcript(req_payload, TranscriptContext::Vca) - ctx.append_message_to_transcript(req_payload, TranscriptContext::M1) + ctx.append_message_to_transcript(req_payload, TranscriptContext::Vca) } fn generate_algorithms_response<'a>( @@ -273,8 +272,7 @@ fn generate_algorithms_response<'a>( .map_err(|_| ctx.generate_error_response(rsp, ErrorCode::InvalidRequest, 0, None))?; // Add the ALGORITHMS to the transcript VCA context - // ctx.append_message_to_transcript(rsp, TranscriptContext::Vca) - ctx.append_message_to_transcript(rsp, TranscriptContext::M1) + ctx.append_message_to_transcript(rsp, TranscriptContext::Vca) } fn encode_alg_struct_table( From dcd0f23f1431b0a7cab21704b1c8584d911a8e2e Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 16 Mar 2026 11:24:34 +0100 Subject: [PATCH 75/86] Remove duplicate ca-cert from example folder --- examples/cert/ecp384_ca.cert.der | Bin 472 -> 0 bytes examples/spdm_requester.rs | 5 +---- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 examples/cert/ecp384_ca.cert.der diff --git a/examples/cert/ecp384_ca.cert.der b/examples/cert/ecp384_ca.cert.der deleted file mode 100644 index 8c6f5256099d4c11c8743a57d4a6ab57a93f70ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmXqLV!UF|#2B@JnTe5!Nkq}{uHTy#O02@2lP9{b+R_#U>1|{Ys1{P5nZpYv4R}~&q{fJ0^f8cH6`iT9L zf{fR-udH|LzOwE8DxGNl7Rd!Y0<#-^uBX%)o|jc-kuVTzz^;HFq(GRF@jnZz0W*+74nF1#27^>411+PeI{#P6 z8wcg*{B-j&=c|3Th@z IoResult<()> { } if !certs.is_empty() { - let ca_cert = Certificate::from_der(CA_CERT).unwrap(); + let ca_cert = Certificate::from_der(platform::STATIC_ROOT_CA_CERT).unwrap(); let ca_cert_sig = ca_cert.signature().as_bytes().unwrap(); assert_eq!(certs[0].signature().as_bytes().unwrap(), ca_cert_sig); println!("CA cert signature matches expected CA signature"); From bea922e96453c3ff9d7716bcd26b94c4028dc7ff Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 18 Feb 2026 13:35:23 +0100 Subject: [PATCH 76/86] Refactor measurements command --- src/chunk_ctx.rs | 2 +- src/commands/measurements/mod.rs | 91 +++++ src/commands/measurements/request.rs | 13 + .../response.rs} | 357 +++++++----------- src/commands/mod.rs | 2 +- src/context.rs | 4 +- 6 files changed, 252 insertions(+), 217 deletions(-) create mode 100644 src/commands/measurements/mod.rs create mode 100644 src/commands/measurements/request.rs rename src/commands/{measurements_rsp.rs => measurements/response.rs} (87%) diff --git a/src/chunk_ctx.rs b/src/chunk_ctx.rs index df4cbcc..fcfa2d2 100644 --- a/src/chunk_ctx.rs +++ b/src/chunk_ctx.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::commands::measurements_rsp::MeasurementsResponse; +use crate::commands::measurements::response::MeasurementsResponse; #[derive(Debug, PartialEq)] pub enum ChunkError { diff --git a/src/commands/measurements/mod.rs b/src/commands/measurements/mod.rs new file mode 100644 index 0000000..55d7c5b --- /dev/null +++ b/src/commands/measurements/mod.rs @@ -0,0 +1,91 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! GET_MEASUREMENTS and MEASURMENTS command types and logic + +/// Requester logic for GET_MEASUREMENTS and MEASURMENTS +pub mod request; +/// Responder logic for GET_MEASUREMENTS and MEASURMENTS +pub mod response; + +use crate::codec::CommonCodec; +use crate::protocol::*; +use bitfield::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +const RESPONSE_FIXED_FIELDS_SIZE: usize = 8; +const MAX_RESPONSE_VARIABLE_FIELDS_SIZE: usize = + NONCE_LEN + size_of::() + size_of::(); + +#[derive(FromBytes, IntoBytes, Immutable)] +#[repr(C)] +struct GetMeasurementsReqCommon { + req_attr: GetMeasurementsReqAttr, + meas_op: u8, +} +impl CommonCodec for GetMeasurementsReqCommon {} + +#[derive(FromBytes, IntoBytes, Immutable)] +#[repr(C)] +struct GetMeasurementsReqSignature { + requester_nonce: [u8; NONCE_LEN], + slot_id: u8, +} +impl CommonCodec for GetMeasurementsReqSignature {} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable)] + #[repr(C)] + struct GetMeasurementsReqAttr(u8); + impl Debug; + u8; + pub signature_requested, _: 0, 0; + pub raw_bitstream_requested, _: 1, 1; + pub new_measurement_requested, _: 2, 2; + reserved, _: 7, 3; +} + +bitfield! { + #[derive(FromBytes, IntoBytes, Immutable)] + #[repr(C)] + struct MeasurementsRspFixed([u8]); + impl Debug; + u8; + pub spdm_version, set_spdm_version: 7, 0; + pub req_resp_code, set_req_resp_code: 15, 8; + pub total_measurement_indices, set_total_measurement_indices: 23, 16; + pub slot_id, set_slot_id: 27, 24; + pub content_changed, set_content_changed: 29, 28; + reserved, _: 31, 30; + pub num_blocks, set_num_blocks: 39, 32; + pub measurement_record_len_byte0, set_measurement_record_len_byte0: 47, 40; + pub measurement_record_len_byte1, set_measurement_record_len_byte1: 55, 48; + pub measurement_record_len_byte2, set_measurement_record_len_byte2: 63, 56; +} + +impl MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> { + pub fn set_measurement_record_len(&mut self, len: u32) { + self.set_measurement_record_len_byte0((len & 0xFF) as u8); + self.set_measurement_record_len_byte1(((len >> 8) & 0xFF) as u8); + self.set_measurement_record_len_byte2(((len >> 16) & 0xFF) as u8); + } +} + +impl Default for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> { + fn default() -> Self { + Self([0; RESPONSE_FIXED_FIELDS_SIZE]) + } +} + +impl CommonCodec for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> {} diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs new file mode 100644 index 0000000..7942e1f --- /dev/null +++ b/src/commands/measurements/request.rs @@ -0,0 +1,13 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/src/commands/measurements_rsp.rs b/src/commands/measurements/response.rs similarity index 87% rename from src/commands/measurements_rsp.rs rename to src/commands/measurements/response.rs index e2f8e23..73bfeca 100644 --- a/src/commands/measurements_rsp.rs +++ b/src/commands/measurements/response.rs @@ -14,88 +14,165 @@ use crate::cert_store::SpdmCertStore; use crate::chunk_ctx::{ChunkError, LargeResponse}; -use crate::codec::{encode_u8_slice, Codec, CommonCodec, MessageBuf}; use crate::commands::algorithms::selected_measurement_specification; -use crate::commands::error_rsp::ErrorCode; -use crate::context::SpdmContext; -use crate::error::{CommandError, CommandResult, PlatformError}; -use crate::measurements::common::{ - MeasurementChangeStatus, MeasurementsError, SpdmMeasurements, SPDM_MAX_MEASUREMENT_RECORD_SIZE, -}; -use crate::platform::evidence::SpdmEvidence; -use crate::platform::hash::SpdmHash; -use crate::platform::rng::SpdmRng; +use crate::commands::measurements::*; +use crate::measurements::common::*; +use crate::platform::{evidence::SpdmEvidence, hash::SpdmHash, rng::SpdmRng}; use crate::protocol::*; use crate::state::ConnectionState; use crate::transcript::{TranscriptContext, TranscriptManager}; -use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use crate::{ + codec::{encode_u8_slice, Codec, MessageBuf}, + commands::error_rsp::ErrorCode, + context::SpdmContext, + error::{CommandError, CommandResult, PlatformError}, +}; -const RESPONSE_FIXED_FIELDS_SIZE: usize = 8; -const MAX_RESPONSE_VARIABLE_FIELDS_SIZE: usize = - NONCE_LEN + size_of::() + size_of::(); +fn process_get_measurements<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + req_payload: &mut MessageBuf<'a>, +) -> CommandResult { + // Validate the version + let connection_version = ctx.state.connection_info.version_number(); + if spdm_hdr.version().ok() != Some(connection_version) { + Err(ctx.generate_error_response(req_payload, ErrorCode::VersionMismatch, 0, None))?; + } -#[derive(FromBytes, IntoBytes, Immutable)] -#[repr(C)] -struct GetMeasurementsReqCommon { - req_attr: GetMeasurementsReqAttr, - meas_op: u8, -} -impl CommonCodec for GetMeasurementsReqCommon {} + // Decode the request + let req_common = GetMeasurementsReqCommon::decode(req_payload).map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?; -#[derive(FromBytes, IntoBytes, Immutable)] -#[repr(C)] -struct GetMeasurementsReqSignature { - requester_nonce: [u8; NONCE_LEN], - slot_id: u8, -} -impl CommonCodec for GetMeasurementsReqSignature {} - -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable)] - #[repr(C)] - struct GetMeasurementsReqAttr(u8); - impl Debug; - u8; - pub signature_requested, _: 0, 0; - pub raw_bitstream_requested, _: 1, 1; - pub new_measurement_requested, _: 2, 2; - reserved, _: 7, 3; -} + let slot_id = if req_common.req_attr.signature_requested() == 0 { + None + } else { + // check if responder capabilities support signature + if ctx.local_capabilities.flags.meas_cap() + != MeasCapability::MeasurementsWithSignature as u8 + { + Err(ctx.generate_error_response(req_payload, ErrorCode::UnsupportedRequest, 0, None))?; + } -bitfield! { - #[derive(FromBytes, IntoBytes, Immutable)] - #[repr(C)] - struct MeasurementsRspFixed([u8]); - impl Debug; - u8; - pub spdm_version, set_spdm_version: 7, 0; - pub req_resp_code, set_req_resp_code: 15, 8; - pub total_measurement_indices, set_total_measurement_indices: 23, 16; - pub slot_id, set_slot_id: 27, 24; - pub content_changed, set_content_changed: 29, 28; - reserved, _: 31, 30; - pub num_blocks, set_num_blocks: 39, 32; - pub measurement_record_len_byte0, set_measurement_record_len_byte0: 47, 40; - pub measurement_record_len_byte1, set_measurement_record_len_byte1: 55, 48; - pub measurement_record_len_byte2, set_measurement_record_len_byte2: 63, 56; + // Decode the requester nonce and slot ID + let req_signature_fields = + GetMeasurementsReqSignature::decode(req_payload).map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?; + Some(req_signature_fields.slot_id) + }; + + // Decode the requester context if version is >= 1.3 + let requester_context = if connection_version >= SpdmVersion::V13 { + Some(RequesterContext::decode(req_payload).map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?) + } else { + None + }; + + // Reset the transcript for the GET_MEASUREMENTS request + ctx.reset_transcript_via_req_code(ReqRespCode::GetMeasurements); + + // Append the request to the transcript + ctx.append_message_to_transcript(req_payload, TranscriptContext::L1)?; + + let asym_algo = ctx.selected_base_asym_algo().map_err(|_| { + ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) + })?; + + let get_meas_req_context = MeasurementsResponse { + spdm_version: connection_version, + req_attr: req_common.req_attr, + meas_op: req_common.meas_op, + slot_id, + requester_context, + asym_algo, + }; + + Ok(get_meas_req_context) } -impl MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> { - pub fn set_measurement_record_len(&mut self, len: u32) { - self.set_measurement_record_len_byte0((len & 0xFF) as u8); - self.set_measurement_record_len_byte1(((len >> 8) & 0xFF) as u8); - self.set_measurement_record_len_byte2(((len >> 16) & 0xFF) as u8); +pub(crate) fn generate_measurements_response<'a>( + ctx: &mut SpdmContext<'a>, + rsp_ctx: MeasurementsResponse, + rsp: &mut MessageBuf<'a>, +) -> CommandResult<()> { + let rsp_len = rsp_ctx.response_size(ctx.evidence, &mut ctx.measurements)?; + + if rsp_len > ctx.min_data_transfer_size() { + // If the response is larger than the minimum data transfer size, use chunked response + let large_rsp = LargeResponse::Measurements(rsp_ctx); + let handle = ctx.large_resp_context.init(large_rsp, rsp_len); + Err(ctx.generate_error_response(rsp, ErrorCode::LargeResponse, handle, None))? + } else { + // If the response fits in a single message, prepare it directly + ctx.prepare_response_buffer(rsp)?; + + // Encode the response fixed fields + rsp.put_data(rsp_len) + .map_err(|e| (false, CommandError::Codec(e)))?; + let rsp_buf = rsp + .data_mut(rsp_len) + .map_err(|e| (false, CommandError::Codec(e)))?; + let payload_len = rsp_ctx.get_chunk( + ctx.hash, + ctx.rng, + ctx.evidence, + &mut ctx.measurements, + &mut ctx.transcript_mgr, + ctx.device_certs_store, + 0, + rsp_buf, + )?; + if rsp_len != payload_len { + Err(( + false, + CommandError::Measurement(MeasurementsError::InvalidBuffer), + ))?; + } + rsp.pull_data(payload_len) + .map_err(|e| (false, CommandError::Codec(e)))?; + + rsp.push_data(payload_len) + .map_err(|e| (false, CommandError::Codec(e))) } } -impl Default for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> { - fn default() -> Self { - Self([0; RESPONSE_FIXED_FIELDS_SIZE]) +pub(crate) fn handle_get_measurements<'a>( + ctx: &mut SpdmContext<'a>, + spdm_hdr: SpdmMsgHdr, + req_payload: &mut MessageBuf<'a>, +) -> CommandResult<()> { + // Check that the connection state is Negotiated + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; } -} -impl CommonCodec for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> {} + // Check if the measurement capability is supported + if ctx.local_capabilities.flags.meas_cap() == MeasCapability::NoMeasurement as u8 { + return Err(ctx.generate_error_response( + req_payload, + ErrorCode::UnsupportedRequest, + 0, + None, + )); + } + + // Verify that the DMTF measurement spec is selected and the measurement hash algorithm is SHA384 + let meas_spec_sel = selected_measurement_specification(ctx); + if meas_spec_sel.dmtf_measurement_spec() == 0 || ctx.verify_selected_hash_algo().is_err() { + Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; + } + + // Process GET_MEASUREMENTS request + let rsp_ctx = process_get_measurements(ctx, spdm_hdr, req_payload)?; + + // Generate MEASUREMENTS response + ctx.prepare_response_buffer(req_payload)?; + generate_measurements_response(ctx, rsp_ctx, req_payload)?; + Ok(()) +} #[derive(Debug)] pub(crate) struct MeasurementsResponse { @@ -402,149 +479,3 @@ impl MeasurementsResponse { Ok(rsp_size) } } - -fn process_get_measurements<'a>( - ctx: &mut SpdmContext<'a>, - spdm_hdr: SpdmMsgHdr, - req_payload: &mut MessageBuf<'a>, -) -> CommandResult { - // Validate the version - let connection_version = ctx.state.connection_info.version_number(); - if spdm_hdr.version().ok() != Some(connection_version) { - Err(ctx.generate_error_response(req_payload, ErrorCode::VersionMismatch, 0, None))?; - } - - // Decode the request - let req_common = GetMeasurementsReqCommon::decode(req_payload).map_err(|_| { - ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) - })?; - - let slot_id = if req_common.req_attr.signature_requested() == 0 { - None - } else { - // check if responder capabilities support signature - if ctx.local_capabilities.flags.meas_cap() - != MeasCapability::MeasurementsWithSignature as u8 - { - Err(ctx.generate_error_response(req_payload, ErrorCode::UnsupportedRequest, 0, None))?; - } - - // Decode the requester nonce and slot ID - let req_signature_fields = - GetMeasurementsReqSignature::decode(req_payload).map_err(|_| { - ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) - })?; - Some(req_signature_fields.slot_id) - }; - - // Decode the requester context if version is >= 1.3 - let requester_context = if connection_version >= SpdmVersion::V13 { - Some(RequesterContext::decode(req_payload).map_err(|_| { - ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) - })?) - } else { - None - }; - - // Reset the transcript for the GET_MEASUREMENTS request - ctx.reset_transcript_via_req_code(ReqRespCode::GetMeasurements); - - // Append the request to the transcript - ctx.append_message_to_transcript(req_payload, TranscriptContext::L1)?; - - let asym_algo = ctx.selected_base_asym_algo().map_err(|_| { - ctx.generate_error_response(req_payload, ErrorCode::InvalidRequest, 0, None) - })?; - - let get_meas_req_context = MeasurementsResponse { - spdm_version: connection_version, - req_attr: req_common.req_attr, - meas_op: req_common.meas_op, - slot_id, - requester_context, - asym_algo, - }; - - Ok(get_meas_req_context) -} - -pub(crate) fn generate_measurements_response<'a>( - ctx: &mut SpdmContext<'a>, - rsp_ctx: MeasurementsResponse, - rsp: &mut MessageBuf<'a>, -) -> CommandResult<()> { - let rsp_len = rsp_ctx.response_size(ctx.evidence, &mut ctx.measurements)?; - - if rsp_len > ctx.min_data_transfer_size() { - // If the response is larger than the minimum data transfer size, use chunked response - let large_rsp = LargeResponse::Measurements(rsp_ctx); - let handle = ctx.large_resp_context.init(large_rsp, rsp_len); - Err(ctx.generate_error_response(rsp, ErrorCode::LargeResponse, handle, None))? - } else { - // If the response fits in a single message, prepare it directly - ctx.prepare_response_buffer(rsp)?; - - // Encode the response fixed fields - rsp.put_data(rsp_len) - .map_err(|e| (false, CommandError::Codec(e)))?; - let rsp_buf = rsp - .data_mut(rsp_len) - .map_err(|e| (false, CommandError::Codec(e)))?; - let payload_len = rsp_ctx.get_chunk( - ctx.hash, - ctx.rng, - ctx.evidence, - &mut ctx.measurements, - &mut ctx.transcript_mgr, - ctx.device_certs_store, - 0, - rsp_buf, - )?; - if rsp_len != payload_len { - Err(( - false, - CommandError::Measurement(MeasurementsError::InvalidBuffer), - ))?; - } - rsp.pull_data(payload_len) - .map_err(|e| (false, CommandError::Codec(e)))?; - - rsp.push_data(payload_len) - .map_err(|e| (false, CommandError::Codec(e))) - } -} - -pub(crate) fn handle_get_measurements<'a>( - ctx: &mut SpdmContext<'a>, - spdm_hdr: SpdmMsgHdr, - req_payload: &mut MessageBuf<'a>, -) -> CommandResult<()> { - // Check that the connection state is Negotiated - if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { - Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; - } - - // Check if the measurement capability is supported - if ctx.local_capabilities.flags.meas_cap() == MeasCapability::NoMeasurement as u8 { - return Err(ctx.generate_error_response( - req_payload, - ErrorCode::UnsupportedRequest, - 0, - None, - )); - } - - // Verify that the DMTF measurement spec is selected and the measurement hash algorithm is SHA384 - let meas_spec_sel = selected_measurement_specification(ctx); - if meas_spec_sel.dmtf_measurement_spec() == 0 || ctx.verify_selected_hash_algo().is_err() { - Err(ctx.generate_error_response(req_payload, ErrorCode::UnexpectedRequest, 0, None))?; - } - - // Process GET_MEASUREMENTS request - let rsp_ctx = process_get_measurements(ctx, spdm_hdr, req_payload)?; - - // Generate MEASUREMENTS response - ctx.prepare_response_buffer(req_payload)?; - generate_measurements_response(ctx, rsp_ctx, req_payload)?; - Ok(()) -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f026e2f..a1dc610 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -19,5 +19,5 @@ pub mod challenge; pub mod chunk_get_rsp; pub mod digests; pub mod error_rsp; -pub mod measurements_rsp; +pub mod measurements; pub mod version; diff --git a/src/context.rs b/src/context.rs index a69c598..fb9bf92 100644 --- a/src/context.rs +++ b/src/context.rs @@ -22,7 +22,7 @@ use crate::commands::digests::{handle_digests_response, handle_get_digests}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; use crate::commands::version::handle_version_response; use crate::commands::{ - algorithms, capabilities, certificate, challenge, chunk_get_rsp, measurements_rsp, version, + algorithms, capabilities, certificate, challenge, chunk_get_rsp, measurements, version, }; use crate::error::*; @@ -194,7 +194,7 @@ impl<'a> SpdmContext<'a> { } ReqRespCode::Challenge => challenge::handle_challenge(self, req_msg_header, req)?, ReqRespCode::GetMeasurements => { - measurements_rsp::handle_get_measurements(self, req_msg_header, req)? + measurements::response::handle_get_measurements(self, req_msg_header, req)? } ReqRespCode::ChunkGet => chunk_get_rsp::handle_chunk_get(self, req_msg_header, req)?, From 41ee519dc6a8c9fd916175f2637fc05111c908fe Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 26 Feb 2026 16:32:08 +0100 Subject: [PATCH 77/86] Implement GET_MEASUREMENTS request generation --- examples/spdm_requester.rs | 21 +++++ src/commands/measurements/mod.rs | 6 +- src/commands/measurements/request.rs | 126 +++++++++++++++++++++++++++ src/error.rs | 18 ++++ 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 6bc2ee3..5e7bf1c 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -26,6 +26,7 @@ use spdm_lib::commands::certificate::request::generate_get_certificate; use spdm_lib::commands::challenge::{ request::generate_challenge_request, MeasurementSummaryHashType, }; +use spdm_lib::commands::measurements::request::generate_get_measurements; use spdm_lib::context::SpdmContext; use spdm_lib::error::SpdmError; use spdm_lib::protocol::algorithms::{ @@ -496,6 +497,26 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("CHALLENGE_AUTH signature verification successfull"); } + // GET_MEASUREMENTS + message_buffer.reset(); + generate_get_measurements( + &mut spdm_context, + &mut message_buffer, + false, + false, + 0x01, + Some(0), + None, + ) + .unwrap(); + spdm_context + .requester_send_request(&mut message_buffer, EID) + .unwrap(); + + if config.verbose { + println!("GET_MEASUREMENTS: {:?}", &message_buffer.message_data()); + } + Ok(()) } diff --git a/src/commands/measurements/mod.rs b/src/commands/measurements/mod.rs index 55d7c5b..8ed42b4 100644 --- a/src/commands/measurements/mod.rs +++ b/src/commands/measurements/mod.rs @@ -50,9 +50,9 @@ bitfield! { struct GetMeasurementsReqAttr(u8); impl Debug; u8; - pub signature_requested, _: 0, 0; - pub raw_bitstream_requested, _: 1, 1; - pub new_measurement_requested, _: 2, 2; + pub signature_requested, set_signature_requested: 0, 0; + pub raw_bitstream_requested, set_raw_bitstream_requested: 1, 1; + pub new_measurement_requested, set_new_measurement_requested: 2, 2; reserved, _: 7, 3; } diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index 7942e1f..f54c38b 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -11,3 +11,129 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +use crate::{ + codec::{Codec, MessageBuf}, + commands::measurements::{ + GetMeasurementsReqAttr, GetMeasurementsReqCommon, GetMeasurementsReqSignature, + }, + context::SpdmContext, + error::{CommandError, CommandResult, PlatformError}, + protocol::{ReqRespCode, SpdmMsgHdr, SpdmVersion, NONCE_LEN}, + state::ConnectionState, +}; + +pub fn generate_get_measurements<'a>( + ctx: &mut SpdmContext<'a>, + req_buf: &mut MessageBuf<'a>, + raw_bitstream_requested: bool, + new_measurement_requested: bool, + meas_op: u8, + slot_id: Option, + context: Option<&[u8; 8]>, +) -> CommandResult<()> { + // Validate connection state - algorithms must be negotiated first + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + return Err((true, CommandError::UnsupportedRequest)); + } + + // TODO: maybe add a check if measuements are supported by the responder + + // Get connection version + let connection_version = ctx.state.connection_info.version_number(); + + // Create and encode SPDM message header + let spdm_hdr = SpdmMsgHdr::new(connection_version, ReqRespCode::GetMeasurements); + let mut payload_len = spdm_hdr + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + let mut req_attr = GetMeasurementsReqAttr(0); + + // signature requested is available in all versions + if slot_id.is_some() { + // Error if the responder doesn't support this + if !responder_supports_signed_measurements(ctx) { + return Err((true, CommandError::UnsupportedRequest)); + } + req_attr.set_signature_requested(1); + } + + if raw_bitstream_requested { + if connection_version < SpdmVersion::V12 { + return Err((true, CommandError::UnsupportedRequest)); + } + // TODO Check measuement block spec + req_attr.set_raw_bitstream_requested(1); + } + + if new_measurement_requested { + if connection_version < SpdmVersion::V13 { + return Err((true, CommandError::UnsupportedRequest)); + } + req_attr.set_new_measurement_requested(1); + } + + // Encode request attributes and `Measurement` operation + let get_meas_common = GetMeasurementsReqCommon { req_attr, meas_op }; + payload_len += get_meas_common + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + + // Generate Nonce if signature was requested + if let Some(id) = slot_id { + let mut nonce = [0; NONCE_LEN]; + ctx.rng + .get_random_bytes(&mut nonce) + .map_err(|e| (true, PlatformError::from(e).into()))?; + + if connection_version < SpdmVersion::V11 { + todo!("Implement encoding of nonce only for v1.0"); + } else { + let get_meas_sig = GetMeasurementsReqSignature { + requester_nonce: nonce, + slot_id: id, + }; + payload_len += get_meas_sig + .encode(req_buf) + .map_err(|e| (false, CommandError::Codec(e)))?; + } + } + + // encode context data if spdm version is >= v1.3 + if connection_version >= SpdmVersion::V13 { + if let Some(context) = context { + payload_len += + crate::codec::encode_u8_slice(context, req_buf).map_err(|e| (true, e.into()))?; + } else { + payload_len += + crate::codec::encode_u8_slice(&[0; 8], req_buf).map_err(|e| (true, e.into()))?; + } + } + + // Finalize message by pushing total payload length + req_buf + .push_data(payload_len) + .map_err(|_| (false, CommandError::BufferTooSmall))?; + + ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::L1) +} + +/// Check if the responder supports signing its measurements +/// +/// Currently only the `MEAS_CAP` is checked. +/// Checking `BaseAsymSel` and `ExtAsymSelCount` might need to checked, +/// to determine if a signed measurement can be requested. +/// (The Spec is a bit fuzzy about that.) +fn responder_supports_signed_measurements(ctx: &SpdmContext<'_>) -> bool { + let flags = &ctx.state.connection_info.peer_capabilities().flags; + // 0b00 no measurements support + // 0b01 measurements support without signing + // 0b10 measurements with signing supported + // 0b11 reserved + if flags.meas_cap() == 0b10 { + return true; + } else { + return false; + } +} diff --git a/src/error.rs b/src/error.rs index de5df50..d2b5ba1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,12 @@ pub enum PlatformError { EvidenceError(SpdmEvidenceError), } +impl From for PlatformError { + fn from(value: SpdmRngError) -> Self { + PlatformError::RngError(value) + } +} + #[non_exhaustive] #[derive(Debug, PartialEq)] pub enum CommandError { @@ -74,3 +80,15 @@ pub enum CommandError { /// in a dependency, or a uncaught platform misbehavior. InternalError, } + +impl From for CommandError { + fn from(value: PlatformError) -> Self { + CommandError::Platform(value) + } +} + +impl From for CommandError { + fn from(value: CodecError) -> Self { + CommandError::Codec(value) + } +} From 8b0d0c5907b2d44b7e7b027d8ddd525f8e54efc3 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 26 Feb 2026 17:12:37 +0100 Subject: [PATCH 78/86] Add MEASUREMENTS response handler stub, add doc --- examples/spdm_requester.rs | 8 ++++++++ src/commands/measurements/request.rs | 30 ++++++++++++++++++++++++++++ src/context.rs | 2 ++ 3 files changed, 40 insertions(+) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 5e7bf1c..3166cdd 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -517,6 +517,14 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("GET_MEASUREMENTS: {:?}", &message_buffer.message_data()); } + spdm_context + .requester_process_message(&mut message_buffer) + .unwrap(); + + if config.verbose { + println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data()); + } + Ok(()) } diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index f54c38b..7d544a8 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -23,6 +23,27 @@ use crate::{ state::ConnectionState, }; +/// Generate a GET_MEASUREMENTS request +/// +/// # Arguments +/// * `ctx`: The SPDM context +/// * `req_buf`: Buffer to write the request into +/// * `raw_bitstream_requested`: Request a raw bit stream (if supported) (SPDM v1.2+) +/// * `new_measurement_requested`: Request new measurement if the responder has pending updates to blocks (SPDM v1.3+) +/// * `meas_op`: Measurement operation +/// (0x00: query total number of available blocks, 0x01-0xFE: query block, 0xFF: query all blocks) +/// * `slot_id`: Request a signed measurement if provided, signed with certificate slot identifier (0-7) +/// * `context`: Append optional 8 byte context (SPDM v1.3+) +/// +/// # Returns +/// - () on success +/// - [CommandError] on failure +/// +/// # Connection State Requirements +/// - Connection state must be >= AlgorithmsNegotiated +/// +/// # Transcript +/// - Appends request to the L1 transcript context pub fn generate_get_measurements<'a>( ctx: &mut SpdmContext<'a>, req_buf: &mut MessageBuf<'a>, @@ -137,3 +158,12 @@ fn responder_supports_signed_measurements(ctx: &SpdmContext<'_>) -> bool { return false; } } + +/// Handle an incoming MEASUREMENTS response +pub(crate) fn handle_measurements_response<'a>( + ctx: &mut SpdmContext<'a>, + resp_header: SpdmMsgHdr, + resp: &mut MessageBuf<'a>, +) -> CommandResult<()> { + todo!() +} diff --git a/src/context.rs b/src/context.rs index fb9bf92..449efb4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -20,6 +20,7 @@ use crate::commands::capabilities::handle_capabilities_response; use crate::commands::challenge::handle_challenge_auth_response; use crate::commands::digests::{handle_digests_response, handle_get_digests}; use crate::commands::error_rsp::{encode_error_response, ErrorCode}; +use crate::commands::measurements::request::handle_measurements_response; use crate::commands::version::handle_version_response; use crate::commands::{ algorithms, capabilities, certificate, challenge, chunk_get_rsp, measurements, version, @@ -238,6 +239,7 @@ impl<'a> SpdmContext<'a> { ReqRespCode::ChallengeAuth => { handle_challenge_auth_response(self, resp_msg_header, resp)? } + ReqRespCode::Measurements => handle_measurements_response(self, resp_msg_header, resp)?, _ => Err((false, CommandError::UnsupportedResponse))?, } From e174fae2c3127636d9eb9d68cb932e9cf2dcef1d Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 26 Feb 2026 18:36:59 +0100 Subject: [PATCH 79/86] Implement MEASUREMENTS response parsing - Measurement data is currently only parsed and discarded --- examples/spdm_requester.rs | 7 +-- src/commands/measurements/mod.rs | 44 ++++++++++++++++- src/commands/measurements/request.rs | 72 ++++++++++++++++++++++++++-- src/protocol/algorithms.rs | 4 +- 4 files changed, 115 insertions(+), 12 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 3166cdd..fedfc4d 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -27,6 +27,7 @@ use spdm_lib::commands::challenge::{ request::generate_challenge_request, MeasurementSummaryHashType, }; use spdm_lib::commands::measurements::request::generate_get_measurements; +use spdm_lib::commands::measurements::MeasurementOperation; use spdm_lib::context::SpdmContext; use spdm_lib::error::SpdmError; use spdm_lib::protocol::algorithms::{ @@ -504,7 +505,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { &mut message_buffer, false, false, - 0x01, + MeasurementOperation::RequestAllMeasBlocks, Some(0), None, ) @@ -514,7 +515,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .unwrap(); if config.verbose { - println!("GET_MEASUREMENTS: {:?}", &message_buffer.message_data()); + println!("GET_MEASUREMENTS: {:x?}", &message_buffer.message_data()); } spdm_context @@ -522,7 +523,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { .unwrap(); if config.verbose { - println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data()); + println!("MEASUREMENTS: {:x?}", &message_buffer.message_data()); } Ok(()) diff --git a/src/commands/measurements/mod.rs b/src/commands/measurements/mod.rs index 8ed42b4..ad6d9f3 100644 --- a/src/commands/measurements/mod.rs +++ b/src/commands/measurements/mod.rs @@ -19,10 +19,10 @@ pub mod request; /// Responder logic for GET_MEASUREMENTS and MEASURMENTS pub mod response; -use crate::codec::CommonCodec; use crate::protocol::*; +use crate::{codec::CommonCodec, error::CommandError}; use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned}; const RESPONSE_FIXED_FIELDS_SIZE: usize = 8; const MAX_RESPONSE_VARIABLE_FIELDS_SIZE: usize = @@ -89,3 +89,43 @@ impl Default for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> { } impl CommonCodec for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> {} + +/// Measurement Operation request field +pub enum MeasurementOperation { + /// Query the Responder for the total number of measurement blocks available + ReportMeasBlockCount, + /// Request the measurement block at a specific index + /// + /// Index has to be between `0x01` and `0xFE`, inclusively. + RequestSingleMeasBlock(u8), + /// Request all measurement blocks + RequestAllMeasBlocks, +} + +impl TryInto for MeasurementOperation { + type Error = CommandError; + + fn try_into(self) -> Result { + match self { + MeasurementOperation::ReportMeasBlockCount => Ok(0x01), + MeasurementOperation::RequestSingleMeasBlock(x) => { + if matches!(x, 0x00 | 0xFF) { + Err(CommandError::UnsupportedRequest) + } else { + Ok(x) + } + } + MeasurementOperation::RequestAllMeasBlocks => Ok(0xFF), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, Unaligned)] +#[repr(C)] +struct MeasurementBlockHeader { + index: u8, + measurement_spec: MeasurementSpecification, + measurement_size: zerocopy::little_endian::U16, +} + +impl CommonCodec for MeasurementBlockHeader {} diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index 7d544a8..dbe2e0e 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -16,11 +16,13 @@ use crate::{ codec::{Codec, MessageBuf}, commands::measurements::{ GetMeasurementsReqAttr, GetMeasurementsReqCommon, GetMeasurementsReqSignature, + MeasurementBlockHeader, MeasurementOperation, MeasurementsRspFixed, }, context::SpdmContext, error::{CommandError, CommandResult, PlatformError}, protocol::{ReqRespCode, SpdmMsgHdr, SpdmVersion, NONCE_LEN}, state::ConnectionState, + transcript::TranscriptContext, }; /// Generate a GET_MEASUREMENTS request @@ -31,10 +33,12 @@ use crate::{ /// * `raw_bitstream_requested`: Request a raw bit stream (if supported) (SPDM v1.2+) /// * `new_measurement_requested`: Request new measurement if the responder has pending updates to blocks (SPDM v1.3+) /// * `meas_op`: Measurement operation -/// (0x00: query total number of available blocks, 0x01-0xFE: query block, 0xFF: query all blocks) /// * `slot_id`: Request a signed measurement if provided, signed with certificate slot identifier (0-7) /// * `context`: Append optional 8 byte context (SPDM v1.3+) /// +/// ## Note +/// For SPDM version 1.0 this implementation does **not** supporte signed measurements. +/// /// # Returns /// - () on success /// - [CommandError] on failure @@ -49,7 +53,7 @@ pub fn generate_get_measurements<'a>( req_buf: &mut MessageBuf<'a>, raw_bitstream_requested: bool, new_measurement_requested: bool, - meas_op: u8, + meas_op: MeasurementOperation, slot_id: Option, context: Option<&[u8; 8]>, ) -> CommandResult<()> { @@ -72,7 +76,11 @@ pub fn generate_get_measurements<'a>( let mut req_attr = GetMeasurementsReqAttr(0); // signature requested is available in all versions + // (v1.0 doesn't support slot-ids) if slot_id.is_some() { + if connection_version == SpdmVersion::V10 { + return Err((true, CommandError::UnsupportedRequest)); + } // Error if the responder doesn't support this if !responder_supports_signed_measurements(ctx) { return Err((true, CommandError::UnsupportedRequest)); @@ -96,7 +104,10 @@ pub fn generate_get_measurements<'a>( } // Encode request attributes and `Measurement` operation - let get_meas_common = GetMeasurementsReqCommon { req_attr, meas_op }; + let get_meas_common = GetMeasurementsReqCommon { + req_attr, + meas_op: meas_op.try_into().map_err(|e| (true, e))?, + }; payload_len += get_meas_common .encode(req_buf) .map_err(|e| (false, CommandError::Codec(e)))?; @@ -137,7 +148,7 @@ pub fn generate_get_measurements<'a>( .push_data(payload_len) .map_err(|_| (false, CommandError::BufferTooSmall))?; - ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::L1) + ctx.append_message_to_transcript(req_buf, TranscriptContext::L1) } /// Check if the responder supports signing its measurements @@ -165,5 +176,56 @@ pub(crate) fn handle_measurements_response<'a>( resp_header: SpdmMsgHdr, resp: &mut MessageBuf<'a>, ) -> CommandResult<()> { - todo!() + // Validate connection state - algorithms must be negotiated + if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated { + return Err((true, CommandError::UnsupportedResponse)); + } + + // Validate version matches connection version + let connection_version = ctx.state.connection_info.version_number(); + if resp_header.version().ok() != Some(connection_version) { + return Err((true, CommandError::InvalidResponse)); + } + + // Include the already parsed header again to parse `MeasurementsRspFixed` + resp.push_data(size_of::()) + .map_err(|e| (false, e.into()))?; + + let fixed_fields = MeasurementsRspFixed::decode(resp).map_err(|e| (true, e.into()))?; + + // Convert 3-byte measurement record length to u32 + let _meas_record_length = u32::from_le_bytes([ + fixed_fields.measurement_record_len_byte0(), + fixed_fields.measurement_record_len_byte1(), + fixed_fields.measurement_record_len_byte2(), + 0, + ]); + + // Decode all measurement blocks + for _ in 0..fixed_fields.num_blocks() { + let block_header = MeasurementBlockHeader::decode(resp).map_err(|e| (true, e.into()))?; + resp.pull_data(block_header.measurement_size.get() as usize) + .map_err(|e| (true, e.into()))?; + } + + // Decode Nonce + let _nonce = resp.data(32).map_err(|e| (true, e.into()))?; + resp.pull_data(32).map_err(|e| (true, e.into()))?; + + // Decode opaque data + let mut len_bytes = [0; 2]; + len_bytes.copy_from_slice(resp.data(2).map_err(|e| (true, e.into()))?); + let opaque_data_len = u16::from_le_bytes(len_bytes); + resp.pull_data(2).map_err(|e| (true, e.into()))?; + resp.pull_data(opaque_data_len as usize) + .map_err(|e| (true, e.into()))?; + + // Decode requester context + let _requester_ctx = resp.data(8).map_err(|e| (true, e.into()))?; + resp.pull_data(8).map_err(|e| (true, e.into()))?; + + // Remaining is the signature, if requested by the GET_MEASUREMENTS request + + // Append response to transcript (L1 context for measurements) + ctx.append_message_to_transcript(resp, TranscriptContext::L1) } diff --git a/src/protocol/algorithms.rs b/src/protocol/algorithms.rs index e203cfb..80f5ded 100644 --- a/src/protocol/algorithms.rs +++ b/src/protocol/algorithms.rs @@ -14,7 +14,7 @@ use crate::error::{SpdmError, SpdmResult}; use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned}; pub const SHA384_HASH_SIZE: usize = 48; pub const ECC_P384_SIGNATURE_SIZE: usize = 96; @@ -120,7 +120,7 @@ where // Measurement Specification field bitfield! { -#[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)] +#[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy, Unaligned)] #[repr(C)] pub struct MeasurementSpecification(u8); impl Debug; From 8f19a6ac83e5fc74b5c9851170439cd67288ca26 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 17 Mar 2026 10:24:56 +0100 Subject: [PATCH 80/86] Reset M1/M2 transcript when a GET_MEASUREMENTS request is generated --- src/commands/measurements/request.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index dbe2e0e..d1dafe6 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -47,6 +47,7 @@ use crate::{ /// - Connection state must be >= AlgorithmsNegotiated /// /// # Transcript +/// - Resets M1/M2 message transcript /// - Appends request to the L1 transcript context pub fn generate_get_measurements<'a>( ctx: &mut SpdmContext<'a>, @@ -148,6 +149,7 @@ pub fn generate_get_measurements<'a>( .push_data(payload_len) .map_err(|_| (false, CommandError::BufferTooSmall))?; + ctx.transcript_mgr.reset_context(TranscriptContext::M1); ctx.append_message_to_transcript(req_buf, TranscriptContext::L1) } From 9b3a7aabf3dc7aea07b37946c30e961c8873f01a Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 24 Mar 2026 15:54:11 +0100 Subject: [PATCH 81/86] Add functionality to parse and iterate measurements from response --- examples/spdm_requester.rs | 37 +++++- src/commands/measurements/mod.rs | 37 +++++- src/commands/measurements/request.rs | 177 ++++++++++++++++++++++++++- src/protocol/algorithms.rs | 10 ++ 4 files changed, 253 insertions(+), 8 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index fedfc4d..8428736 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -26,7 +26,9 @@ use spdm_lib::commands::certificate::request::generate_get_certificate; use spdm_lib::commands::challenge::{ request::generate_challenge_request, MeasurementSummaryHashType, }; -use spdm_lib::commands::measurements::request::generate_get_measurements; +use spdm_lib::commands::measurements::request::{ + generate_get_measurements, parse_measurements_response, +}; use spdm_lib::commands::measurements::MeasurementOperation; use spdm_lib::context::SpdmContext; use spdm_lib::error::SpdmError; @@ -526,6 +528,39 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("MEASUREMENTS: {:x?}", &message_buffer.message_data()); } + let measurements = parse_measurements_response(&message_buffer.message_data().unwrap()) + .expect("Failed to parse measurement response"); + + if config.verbose { + println!( + "Measurements block count: {}", + measurements.total_measurement_blocks() + ); + println!("Measurements Nonce: {}", HexString(&measurements.nonce)); + if let Some(sig) = measurements.signature { + println!("Measurements Signature: {}", HexString(sig)); + } + println!( + "Measurement content change status: {:?}", + measurements.content_changed() + ); + } + + let mut meas_count = 0; + for measurement in measurements.iter() { + meas_count += 1; + if config.verbose { + println!( + "Parsed {:?} measurement with index {}", + measurement.measurement_spec, measurement.index + ); + } + } + + if meas_count != measurements.total_measurement_blocks() { + println!("[WARNING] measurement block count and parsed measurment count mismatch ({} expected, {} parsed)", measurements.total_measurement_blocks(), meas_count); + } + Ok(()) } diff --git a/src/commands/measurements/mod.rs b/src/commands/measurements/mod.rs index ad6d9f3..5a49208 100644 --- a/src/commands/measurements/mod.rs +++ b/src/commands/measurements/mod.rs @@ -22,7 +22,7 @@ pub mod response; use crate::protocol::*; use crate::{codec::CommonCodec, error::CommandError}; use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; const RESPONSE_FIXED_FIELDS_SIZE: usize = 8; const MAX_RESPONSE_VARIABLE_FIELDS_SIZE: usize = @@ -57,7 +57,7 @@ bitfield! { } bitfield! { - #[derive(FromBytes, IntoBytes, Immutable)] + #[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] struct MeasurementsRspFixed([u8]); impl Debug; @@ -120,8 +120,8 @@ impl TryInto for MeasurementOperation { } } -#[derive(Debug, FromBytes, IntoBytes, Immutable, Unaligned)] -#[repr(C)] +#[derive(Debug, FromBytes, IntoBytes, Immutable, Unaligned, KnownLayout)] +#[repr(C, packed)] struct MeasurementBlockHeader { index: u8, measurement_spec: MeasurementSpecification, @@ -129,3 +129,32 @@ struct MeasurementBlockHeader { } impl CommonCodec for MeasurementBlockHeader {} + +/// Content changed indicators for MEASUREMENT responses +#[derive(Debug, Clone, Copy)] +pub enum ContentChanged { + /// The Responder does not detect changes of MeasurementRecord fields + /// of previous MEASUREMENTS responses in the same measurement log, + /// or this message does not contain a signature. + NoDetection = 0x00, + /// The Responder detected that one or more MeasurementRecord fields + /// of previous MEASUREMENTS responses in the measurement log being + /// signed have changed. The Requester might consider issuing + /// GET_MEASUREMENTS again to acquire latest measurements. + ChangeDetected = 0x01, + /// The Responder detected no change in MeasurementRecord fields of + /// previous MEASUREMENTS responses in the measurement log being signed. + NoChangeDetected = 0x10, + Reserved = 0x11, +} + +impl From for ContentChanged { + fn from(value: u8) -> Self { + match value { + 0b00 => Self::NoDetection, + 0b01 => Self::ChangeDetected, + 0b10 => Self::NoChangeDetected, + _ => Self::Reserved, + } + } +} diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index d1dafe6..d454a7a 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -12,19 +12,48 @@ // See the License for the specific language governing permissions and // limitations under the License. +use zerocopy::FromBytes; + use crate::{ codec::{Codec, MessageBuf}, commands::measurements::{ - GetMeasurementsReqAttr, GetMeasurementsReqCommon, GetMeasurementsReqSignature, - MeasurementBlockHeader, MeasurementOperation, MeasurementsRspFixed, + ContentChanged, GetMeasurementsReqAttr, GetMeasurementsReqCommon, + GetMeasurementsReqSignature, MeasurementBlockHeader, MeasurementOperation, + MeasurementsRspFixed, RESPONSE_FIXED_FIELDS_SIZE, }, context::SpdmContext, error::{CommandError, CommandResult, PlatformError}, - protocol::{ReqRespCode, SpdmMsgHdr, SpdmVersion, NONCE_LEN}, + protocol::{MeasurementSpecificationType, ReqRespCode, SpdmMsgHdr, SpdmVersion, NONCE_LEN}, state::ConnectionState, transcript::TranscriptContext, }; +#[derive(Debug)] +pub struct Measurement<'a> { + pub index: u8, + pub measurement_spec: MeasurementSpecificationType, + pub measurement: &'a [u8], +} + +/// Parsed MEASUREMENTS response +#[derive(Debug)] +pub struct Measurements<'a> { + /// Fixed fields + fixed_fields: &'a MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]>, + /// Measurement blocks + measurements: &'a [u8], + /// 32-byte Nonce + pub nonce: &'a [u8], + /// Opaque data + pub opaque_data: &'a [u8], + /// Requester context + pub requester_ctx: &'a [u8], + /// Optional Signature + /// + /// _Note_: The length of the signature isn't checked for validity. + pub signature: Option<&'a [u8]>, +} + /// Generate a GET_MEASUREMENTS request /// /// # Arguments @@ -231,3 +260,145 @@ pub(crate) fn handle_measurements_response<'a>( // Append response to transcript (L1 context for measurements) ctx.append_message_to_transcript(resp, TranscriptContext::L1) } + +/// Parse a successfull measurements response +/// +/// _Note:_ The caller has to ensure that `resp` is a valid MEASUREMENTS response. +/// Returns `None` if parsing fails. +pub fn parse_measurements_response<'a>(resp: &'a [u8]) -> Option> { + let (fixed_fields, rest) = + MeasurementsRspFixed::<[u8; RESPONSE_FIXED_FIELDS_SIZE]>::ref_from_prefix(resp).ok()?; + + // Convert 3-byte measurement record length to u32 + let meas_record_length = u32::from_le_bytes([ + fixed_fields.measurement_record_len_byte0(), + fixed_fields.measurement_record_len_byte1(), + fixed_fields.measurement_record_len_byte2(), + 0, + ]) as usize; + + // Get measurement blocks + if meas_record_length > rest.len() { + return None; + } + let (measurements, rest) = rest.split_at(meas_record_length); + + // Decode Nonce + if rest.len() < 32 { + return None; + } + let (nonce, rest) = rest.split_at(32); + + // Decode opaque data + let (opaque_data, rest) = decode_opaque_data(rest)?; + + // Decode requester context + let requester_ctx = rest.get(..8)?; + let rest = rest.get(8..)?; + + // Remaining is the signature, if requested by the GET_MEASUREMENTS request + let signature = if !rest.is_empty() { Some(rest) } else { None }; + + Some(Measurements { + fixed_fields, + measurements, + nonce, + opaque_data, + requester_ctx, + signature, + }) +} + +/// Decode the opaque data +/// +/// # Arguments +/// - `buf`: buffer containing 2-byte length and following opaque data +/// +/// # Returns +/// - `Some((opaque_data, rest))` on success +/// - `None` if `buf` is to small to hold the opaque data +fn decode_opaque_data<'a>(buf: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { + if buf.len() < 2 { + return None; + } + let mut opaque_data_len = [0; 2]; + opaque_data_len.copy_from_slice(&buf[..2]); + let opaque_data_len = u16::from_le_bytes(opaque_data_len) as usize; + + let rest = buf.get(2..)?; + + let data = rest.get(..opaque_data_len)?; + let rest = rest.get(opaque_data_len..)?; + + Some((data, rest)) +} + +impl<'a> Measurements<'a> { + /// The certificate slot used to sign the measurements + /// + /// Returns the slot number of the certificate chain + /// specified in the GET_MEASUREMENTS request, + /// or 0xF if the Responder's public key was provisioned + /// to the Requester previously. + pub fn slot_id(&self) -> u8 { + self.fixed_fields.slot_id() + } + + /// The total number of measurment blocks in this response + /// + /// _Note:_ This returns the number of blocks reported by the response header, + /// it is not guranteed at this point, that the response actually contains them. + pub fn total_measurement_blocks(&self) -> u8 { + self.fixed_fields.num_blocks() + } + + /// If this message contains a signature, this field shall indicate + /// if one or more MeasurementRecord fields of previous MEASUREMENTS + /// responses in the same measurement log have changed. + pub fn content_changed(&self) -> ContentChanged { + self.fixed_fields.content_changed().into() + } + + /// Returns an iterator over the measurements. + pub fn iter(&self) -> MeasurementIterator<'a> { + MeasurementIterator { + measurements: self.measurements, + pos: 0, + } + } +} + +impl<'a> IntoIterator for Measurements<'a> { + type Item = Measurement<'a>; + + type IntoIter = MeasurementIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + MeasurementIterator { + measurements: self.measurements, + pos: 0, + } + } +} + +pub struct MeasurementIterator<'a> { + measurements: &'a [u8], + pos: usize, +} + +impl<'a> Iterator for MeasurementIterator<'a> { + type Item = Measurement<'a>; + + fn next(&mut self) -> Option { + let (header, rest) = + MeasurementBlockHeader::ref_from_prefix(self.measurements.get(self.pos..)?).ok()?; + + let measurement = rest.get(..header.measurement_size.get() as usize)?; + self.pos += size_of::() + header.measurement_size.get() as usize; + Some(Measurement { + index: header.index, + measurement_spec: header.measurement_spec.try_into().ok()?, + measurement, + }) + } +} diff --git a/src/protocol/algorithms.rs b/src/protocol/algorithms.rs index 80f5ded..3cb9271 100644 --- a/src/protocol/algorithms.rs +++ b/src/protocol/algorithms.rs @@ -141,6 +141,16 @@ impl From for u8 { } } } +impl TryFrom for MeasurementSpecificationType { + type Error = (); + + fn try_from(value: MeasurementSpecification) -> Result { + match value.0 { + 1 => Ok(Self::DmtfMeasurementSpec), + _ => Err(()), + } + } +} // Other Param Support Field for request and response bitfield! { From f214e6a0f217071ebe96808b3c88688de19419ee Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 25 Mar 2026 11:32:41 +0100 Subject: [PATCH 82/86] Fix L1/L2 transcript generation, check L1/L2 signature in example --- examples/spdm_requester.rs | 78 +++++++++++++++++++++++++++- src/commands/measurements/request.rs | 10 +++- src/commands/version/request.rs | 1 + src/context.rs | 9 ++++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 8428736..2e74187 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -473,7 +473,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data()); } - if let Some(cert) = peer_leaf_cert { + if let Some(cert) = &peer_leaf_cert { let pub_key = VerifyingKey::from_sec1_bytes( cert.tbs_certificate() .subject_public_key_info() @@ -497,6 +497,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { "CHALLENGE_AUTH signature verification failed", )); } + spdm_context.set_authenticated(); println!("CHALLENGE_AUTH signature verification successfull"); } @@ -546,6 +547,29 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { ); } + if let Some(sig_raw) = measurements.signature { + if let Some(cert) = &peer_leaf_cert { + let pub_key = VerifyingKey::from_sec1_bytes( + cert.tbs_certificate() + .subject_public_key_info() + .subject_public_key + .as_bytes() + .unwrap(), + ) + .unwrap(); + + let sig = Signature::from_slice(sig_raw).unwrap(); + if !verify_measurements_signature(&mut spdm_context, pub_key, sig, config) { + eprintln!("MEASUREMENTS signature verification failed"); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "MEASUREMENTS signature verification failed", + )); + } + println!("L1/L2 log verification successfull"); + } + } + let mut meas_count = 0; for measurement in measurements.iter() { meas_count += 1; @@ -559,6 +583,8 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { if meas_count != measurements.total_measurement_blocks() { println!("[WARNING] measurement block count and parsed measurment count mismatch ({} expected, {} parsed)", measurements.total_measurement_blocks(), meas_count); + } else { + println!("Measurements retrieved successfully") } Ok(()) @@ -728,6 +754,56 @@ fn verify_challenge_auth_signature( } } +/// Currently only p384 support required +/// Here we verify that the responder and we created the same L1/L2 transcript and +/// that the signature is correct. +/// +/// The transcript hash will be retrieved from the context. +/// The signature will be verified using the public key from the responder's certificate chain (which we already verified). +fn verify_measurements_signature( + ctx: &mut SpdmContext, + pubkey: VerifyingKey, + signature: Signature, + config: &RequesterConfig, +) -> bool { + use p384::ecdsa::signature::hazmat::PrehashVerifier; + use signature::Verifier; + + let mut sig_combined_context = Vec::new(); + if ctx.connection_info().version_number() >= SpdmVersion::V12 { + // since we verify the responder-generated signature, we have to use the same "responder-" context constant. + let sig_ctx = protocol::signature::create_responder_signing_context( + ctx.connection_info().version_number(), + protocol::ReqRespCode::Measurements, + ) + .unwrap(); + sig_combined_context.extend_from_slice(&sig_ctx); + if config.verbose { + println!( + "comb_ctx string: '{}'", + String::from_utf8_lossy(&sig_combined_context) + ); + } + } + + // Get the L1 transcript hash and verify the signature over it. + let mut transcript_hash = [0u8; 48]; + ctx.transcript_hash(TranscriptContext::L1, &mut transcript_hash) + .unwrap(); + if config.verbose { + println!("L1/2 hash: {}", HexString(&transcript_hash)); + } + + // M denotes the message that is signed. M shall be the concatenation of the combined_spdm_prefix and unverified_message_hash. + let m = [sig_combined_context.as_slice(), &transcript_hash].concat(); + + if ctx.connection_info().version_number() >= SpdmVersion::V12 { + pubkey.verify(&m, &signature).is_ok() + } else { + pubkey.verify_prehash(&m, &signature).is_ok() + } +} + #[derive(Debug)] struct HexString<'a>(&'a [u8]); diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index d454a7a..e74b1ab 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -258,7 +258,15 @@ pub(crate) fn handle_measurements_response<'a>( // Remaining is the signature, if requested by the GET_MEASUREMENTS request // Append response to transcript (L1 context for measurements) - ctx.append_message_to_transcript(resp, TranscriptContext::L1) + // We have to use this ugly hack to bring the message buffer into the right form to exclude the signature. + let tail = resp.data_len(); + resp.trim(0).map_err(|e| (true, CommandError::Codec(e)))?; + // Append the entire message (excluding the signature) to the transcript before signature verification, as required by SPDM 1.2 and later. + ctx.append_message_to_transcript(resp, TranscriptContext::L1)?; + resp.trim(tail - resp.data_len()) + .map_err(|e| (true, CommandError::Codec(e)))?; + + Ok(()) } /// Parse a successfull measurements response diff --git a/src/commands/version/request.rs b/src/commands/version/request.rs index d5f6955..59fb7be 100644 --- a/src/commands/version/request.rs +++ b/src/commands/version/request.rs @@ -101,6 +101,7 @@ fn process_version<'a>( if let Some(ver) = latest_version { ctx.state.connection_info.set_version_number(ver); + ctx.transcript_mgr.set_spdm_version(ver); Ok(ver) } else { Err((false, CommandError::UnsupportedResponse)) diff --git a/src/context.rs b/src/context.rs index 449efb4..bbd5f4f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -411,4 +411,13 @@ impl<'a> SpdmContext<'a> { pub fn get_random_bytes(&mut self, dest: &mut [u8]) -> SpdmRngResult<()> { self.rng.get_random_bytes(dest) } + + /// Set the connection state to authenticated + /// + /// Should be called after after the signature of a CHALLENGE response has been verified. + pub fn set_authenticated(&mut self) { + self.state + .connection_info + .set_state(ConnectionState::Authenticated); + } } From c488af2686ba571ab04a6acdc2660b2bb6374361 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 25 Mar 2026 12:05:03 +0100 Subject: [PATCH 83/86] Only parse MEASUREMENTS requester context when conn. ver. is > v1.2 --- examples/spdm_requester.rs | 6 +++++- src/commands/measurements/request.rs | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 2e74187..3dcdced 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -539,7 +539,11 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { ); println!("Measurements Nonce: {}", HexString(&measurements.nonce)); if let Some(sig) = measurements.signature { - println!("Measurements Signature: {}", HexString(sig)); + println!( + "Measurements Signature ({} bytes): {}", + sig.len(), + HexString(sig) + ); } println!( "Measurement content change status: {:?}", diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index e74b1ab..928da0c 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -47,7 +47,7 @@ pub struct Measurements<'a> { /// Opaque data pub opaque_data: &'a [u8], /// Requester context - pub requester_ctx: &'a [u8], + pub requester_ctx: Option<&'a [u8]>, /// Optional Signature /// /// _Note_: The length of the signature isn't checked for validity. @@ -251,9 +251,11 @@ pub(crate) fn handle_measurements_response<'a>( resp.pull_data(opaque_data_len as usize) .map_err(|e| (true, e.into()))?; - // Decode requester context - let _requester_ctx = resp.data(8).map_err(|e| (true, e.into()))?; - resp.pull_data(8).map_err(|e| (true, e.into()))?; + if connection_version >= SpdmVersion::V13 { + // Decode requester context + let _requester_ctx = resp.data(8).map_err(|e| (true, e.into()))?; + resp.pull_data(8).map_err(|e| (true, e.into()))?; + } // Remaining is the signature, if requested by the GET_MEASUREMENTS request @@ -277,6 +279,7 @@ pub fn parse_measurements_response<'a>(resp: &'a [u8]) -> Option::ref_from_prefix(resp).ok()?; + let connection_version: SpdmVersion = fixed_fields.spdm_version().try_into().ok()?; // Convert 3-byte measurement record length to u32 let meas_record_length = u32::from_le_bytes([ fixed_fields.measurement_record_len_byte0(), @@ -300,9 +303,14 @@ pub fn parse_measurements_response<'a>(resp: &'a [u8]) -> Option= SpdmVersion::V13 { + // Decode requester context + let requester_ctx = rest.get(..8)?; + let rest = rest.get(8..)?; + (Some(requester_ctx), rest) + } else { + (None, rest) + }; // Remaining is the signature, if requested by the GET_MEASUREMENTS request let signature = if !rest.is_empty() { Some(rest) } else { None }; From eeb9dbb9e60ec2ac9c345e1ff2b70a2211f62f24 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Wed, 25 Mar 2026 12:36:53 +0100 Subject: [PATCH 84/86] Fix bug: Correctly resize buffer for transcript signature exclusion To append a CHALLENGE or MEASUREMENTS response to the transcript, the signature has to be excluded. For this the message buffer gets resized. Afterwards the messge buffer has to be restored to the original size. This patch fixes the a bug where restoring failed, when the buffer was smaller than the signature. --- src/commands/challenge/request.rs | 2 +- src/commands/measurements/request.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/challenge/request.rs b/src/commands/challenge/request.rs index a579986..e12a892 100644 --- a/src/commands/challenge/request.rs +++ b/src/commands/challenge/request.rs @@ -208,7 +208,7 @@ pub(crate) fn handle_challenge_auth_response<'a>( // Append the entire message (excluding the signature) to the transcript before signature verification, as required by SPDM 1.2 and later. ctx.append_message_to_transcript(resp_payload, TranscriptContext::M1)?; resp_payload - .trim(tail - resp_payload.data_len()) + .put_data(tail) .map_err(|e| (true, CommandError::Codec(e)))?; Ok(()) diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index 928da0c..ac1c242 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -265,7 +265,7 @@ pub(crate) fn handle_measurements_response<'a>( resp.trim(0).map_err(|e| (true, CommandError::Codec(e)))?; // Append the entire message (excluding the signature) to the transcript before signature verification, as required by SPDM 1.2 and later. ctx.append_message_to_transcript(resp, TranscriptContext::L1)?; - resp.trim(tail - resp.data_len()) + resp.put_data(tail) .map_err(|e| (true, CommandError::Codec(e)))?; Ok(()) From e01b0b9149bd51f4bad1e25d17fa5456f3642a9d Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 26 Mar 2026 15:14:25 +0100 Subject: [PATCH 85/86] Fix some linter warnings --- examples/spdm_requester.rs | 20 +++++++++----------- src/commands/measurements/request.rs | 8 ++------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/examples/spdm_requester.rs b/examples/spdm_requester.rs index 3dcdced..96871e8 100644 --- a/examples/spdm_requester.rs +++ b/examples/spdm_requester.rs @@ -15,7 +15,7 @@ //! SPDM Example Responder utilizing the requester library. use std::fmt::Display; -use std::io::{Error, ErrorKind, Result as IoResult}; +use std::io::{Error, Result as IoResult}; use std::net::TcpStream; use clap::Parser; @@ -204,7 +204,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { Ok(ctx) => ctx, Err(e) => { eprintln!("Failed to create SPDM context: {:?}", e); - return Err(Error::new(ErrorKind::Other, "SPDM context creation failed")); + return Err(Error::other("SPDM context creation failed")); } }; @@ -218,7 +218,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { if config.transport_type == platform::socket_transport::SocketTransportType::None { spdm_context.transport_init_sequence().map_err(|e| { eprintln!("Handshake failed: {:?}", e); - Error::new(ErrorKind::Other, "SPDM handshake failed") + Error::other("SPDM handshake failed") })?; } @@ -492,8 +492,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { if !verify_challenge_auth_signature(&mut spdm_context, pub_key, sig, config) { eprintln!("CHALLENGE_AUTH signature verification failed"); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, + return Err(std::io::Error::other( "CHALLENGE_AUTH signature verification failed", )); } @@ -529,7 +528,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { println!("MEASUREMENTS: {:x?}", &message_buffer.message_data()); } - let measurements = parse_measurements_response(&message_buffer.message_data().unwrap()) + let measurements = parse_measurements_response(message_buffer.message_data().unwrap()) .expect("Failed to parse measurement response"); if config.verbose { @@ -537,7 +536,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { "Measurements block count: {}", measurements.total_measurement_blocks() ); - println!("Measurements Nonce: {}", HexString(&measurements.nonce)); + println!("Measurements Nonce: {}", HexString(measurements.nonce)); if let Some(sig) = measurements.signature { println!( "Measurements Signature ({} bytes): {}", @@ -565,8 +564,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> { let sig = Signature::from_slice(sig_raw).unwrap(); if !verify_measurements_signature(&mut spdm_context, pub_key, sig, config) { eprintln!("MEASUREMENTS signature verification failed"); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, + return Err(std::io::Error::other( "MEASUREMENTS signature verification failed", )); } @@ -688,9 +686,9 @@ fn verify_cert_chain(chain: &[Certificate]) -> bool { .unwrap(); for cert in chain.iter() { let sig = Signature::from_der(cert.signature().as_bytes().unwrap()).unwrap(); - if !pub_key + if pub_key .verify(&cert.tbs_certificate().to_der().unwrap(), &sig) - .is_ok() + .is_err() { return false; } diff --git a/src/commands/measurements/request.rs b/src/commands/measurements/request.rs index ac1c242..ef5c5ff 100644 --- a/src/commands/measurements/request.rs +++ b/src/commands/measurements/request.rs @@ -194,11 +194,7 @@ fn responder_supports_signed_measurements(ctx: &SpdmContext<'_>) -> bool { // 0b01 measurements support without signing // 0b10 measurements with signing supported // 0b11 reserved - if flags.meas_cap() == 0b10 { - return true; - } else { - return false; - } + flags.meas_cap() == 0b10 } /// Handle an incoming MEASUREMENTS response @@ -333,7 +329,7 @@ pub fn parse_measurements_response<'a>(resp: &'a [u8]) -> Option(buf: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { +fn decode_opaque_data(buf: &[u8]) -> Option<(&[u8], &[u8])> { if buf.len() < 2 { return None; } From 029a5a47369ed8ed8a3838c99084ee915c2f8898 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 14 Apr 2026 12:27:14 +0200 Subject: [PATCH 86/86] Minor cleanups of example cert store --- examples/platform/cert_store.rs | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/examples/platform/cert_store.rs b/examples/platform/cert_store.rs index 0d8ccf1..20e514f 100644 --- a/examples/platform/cert_store.rs +++ b/examples/platform/cert_store.rs @@ -24,7 +24,9 @@ use p384::{ }; use zerocopy::FromBytes; -use super::certs::{STATIC_END_CERT, STATIC_END_RESPONDER_KEY_DER, STATIC_INTER_CERT, STATIC_ROOT_CA_CERT}; +use super::certs::{ + STATIC_END_CERT, STATIC_END_RESPONDER_KEY_DER, STATIC_INTER_CERT, STATIC_ROOT_CA_CERT, +}; use spdm_lib::commands::challenge::MeasurementSummaryHashType; use spdm_lib::protocol::{ algorithms::{AsymAlgo, ECC_P384_SIGNATURE_SIZE, SHA384_HASH_SIZE}, @@ -70,8 +72,8 @@ impl DemoCertStore { let raw_key: &[u8; 48] = STATIC_END_RESPONDER_KEY_DER[8..56] .try_into() .expect("key DER too short"); - let secret_key = SecretKey::from_bytes(raw_key.into()) - .expect("Failed to parse end-entity private key"); + let secret_key = + SecretKey::from_bytes(raw_key.into()).expect("Failed to parse end-entity private key"); (cert_chain, SigningKey::from(secret_key)) } @@ -347,11 +349,7 @@ fn debug_signing_verification() { println!("except: print('✗ Sig1 invalid with SHA384')"); } -pub struct ExamplePeerCertStrore { - pub chain: Vec, -} - -#[derive(Debug)] +#[derive(Debug, Default)] pub struct PeerSlot { /// CertChain[K], retrieved in `CERTIFICATE` response. pub cert_chain: Vec, @@ -371,19 +369,6 @@ pub struct PeerSlot { pub requested_msh_type: Option, } -impl Default for PeerSlot { - fn default() -> Self { - PeerSlot { - cert_chain: Vec::new(), - digest: Vec::new(), - keypair_id: None, - certificate_info: None, - key_usage_mask: None, - requested_msh_type: None, - } - } -} - impl PeerSlot { /// Get the digest for the root certificate of the chain ///