From 884b969c4dbe0ae0f161098cd29c84017ba639eb Mon Sep 17 00:00:00 2001 From: siweicai Date: Mon, 20 Oct 2025 06:53:47 +0800 Subject: [PATCH] Add Spdm Attestation support for migtd Alternative to rustls, the migtd could config spdm to do mutual attestation. --- .gitmodules | 3 + Cargo.lock | 185 ++++++- Cargo.toml | 5 +- config/spdm_config.json | 23 + deps/spdm-rs | 1 + deps/td-shim | 2 +- sh_script/preparation.sh | 5 + src/async/async_io/src/lib.rs | 4 +- src/attestation/src/attest.rs | 38 +- src/crypto/src/rustls_impl/ecdsa.rs | 26 +- src/crypto/src/x509.rs | 43 ++ src/devices/vsock/src/stream.rs | 4 +- src/migtd/Cargo.toml | 11 + src/migtd/src/lib.rs | 10 + src/migtd/src/migration/session.rs | 41 +- src/migtd/src/spdm/mod.rs | 57 +++ src/migtd/src/spdm/spdm_req.rs | 735 ++++++++++++++++++++++++++++ src/migtd/src/spdm/spdm_rsp.rs | 546 +++++++++++++++++++++ src/migtd/src/spdm/spdm_vdm.rs | 283 +++++++++++ src/migtd/src/spdm/vmcall_msg.rs | 191 ++++++++ src/std-support/sys_time/Cargo.toml | 2 +- 21 files changed, 2171 insertions(+), 44 deletions(-) create mode 100644 config/spdm_config.json create mode 160000 deps/spdm-rs create mode 100644 src/migtd/src/spdm/mod.rs create mode 100644 src/migtd/src/spdm/spdm_req.rs create mode 100644 src/migtd/src/spdm/spdm_rsp.rs create mode 100644 src/migtd/src/spdm/spdm_vdm.rs create mode 100644 src/migtd/src/spdm/vmcall_msg.rs diff --git a/.gitmodules b/.gitmodules index 288ea77a..41a15a77 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "deps/td-shim"] path = deps/td-shim url = https://github.com/confidential-containers/td-shim +[submodule "deps/spdm-rs"] + path = deps/spdm-rs + url = https://github.com/ccc-spdm-tools/spdm-rs diff --git a/Cargo.lock b/Cargo.lock index 2899ac9d..d82d242f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "async_io" version = "0.1.0" @@ -217,6 +228,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cc" version = "1.2.16" @@ -289,6 +306,10 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +[[package]] +name = "codec" +version = "0.2.2" + [[package]] name = "colorchoice" version = "1.0.0" @@ -346,7 +367,7 @@ dependencies = [ "rustls", "rustls-pemfile", "rustls-pki-types", - "sys_time", + "sys_time 0.1.1", "zeroize", ] @@ -497,12 +518,48 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -516,6 +573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", @@ -725,6 +783,17 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "memchr" version = "2.6.4" @@ -736,22 +805,27 @@ name = "migtd" version = "0.5.1" dependencies = [ "anyhow", + "async-trait", "async_io", "async_runtime", "attestation", "bitfield", "cc-measurement", "chrono", + "codec", "crypto", "futures-util", "lazy_static", "log", + "maybe-async", "minicov", "pci", "policy", "r-efi", + "ring", "rust_std_stub", "scroll", + "spdmlib", "spin 0.9.8", "td-benchmark", "td-layout", @@ -766,8 +840,9 @@ dependencies = [ "vmcall_raw", "vsock", "x86", - "x86_64", + "x86_64 0.14.9", "zerocopy 0.7.32", + "zeroize", ] [[package]] @@ -936,6 +1011,18 @@ dependencies = [ "x86", ] +[[package]] +name = "pcidoe_transport" +version = "0.1.0" +dependencies = [ + "async-trait", + "codec", + "futures", + "maybe-async", + "spdmlib", + "spin 0.9.8", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1065,7 +1152,7 @@ version = "0.1.0" dependencies = [ "hashbrown 0.14.5", "spin 0.5.2", - "sys_time", + "sys_time 0.1.1", ] [[package]] @@ -1099,7 +1186,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -1115,9 +1202,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" @@ -1130,6 +1220,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -1256,6 +1357,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "spdmlib" +version = "0.1.0" +dependencies = [ + "async-trait", + "bit_field", + "bitflags 1.3.2", + "byteorder", + "bytes", + "codec", + "conquer-once", + "env_logger", + "futures", + "lazy_static", + "log", + "maybe-async", + "pcidoe_transport", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.7", + "serde", + "serde_json", + "spin 0.9.8", + "sys_time 0.1.0", + "untrusted", + "zeroize", +] + [[package]] name = "spin" version = "0.5.2" @@ -1335,10 +1464,18 @@ dependencies = [ [[package]] name = "sys_time" version = "0.1.0" +dependencies = [ + "time", + "x86_64 0.15.2", +] + +[[package]] +name = "sys_time" +version = "0.1.1" dependencies = [ "lazy_static", "time", - "x86_64", + "x86_64 0.14.9", ] [[package]] @@ -1360,7 +1497,7 @@ dependencies = [ "log", "spin 0.9.8", "tdx-tdcall", - "x86_64", + "x86_64 0.14.9", ] [[package]] @@ -1399,7 +1536,7 @@ dependencies = [ "spin 0.9.8", "td-layout", "x86", - "x86_64", + "x86_64 0.14.9", ] [[package]] @@ -1424,7 +1561,7 @@ dependencies = [ "td-shim-interface", "tdx-tdcall", "x86", - "x86_64", + "x86_64 0.14.9", "zerocopy 0.7.32", ] @@ -1492,7 +1629,7 @@ dependencies = [ "log", "scroll", "spin 0.9.8", - "x86_64", + "x86_64 0.14.9", ] [[package]] @@ -1979,6 +2116,18 @@ dependencies = [ "volatile 0.4.6", ] +[[package]] +name = "x86_64" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f042214de98141e9c8706e8192b73f56494087cc55ebec28ce10f26c5c364ae" +dependencies = [ + "bit_field", + "bitflags 2.4.1", + "rustversion", + "volatile 0.4.6", +] + [[package]] name = "xshell" version = "0.2.5" @@ -2052,3 +2201,17 @@ 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 2.0.106", +] diff --git a/Cargo.toml b/Cargo.toml index f7bfab2e..e96b9c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,8 @@ default-members = [ exclude = [ "deps/td-shim", - "deps/rustls" + "deps/rustls", + "deps/spdm-rs/sys_time" ] resolver = "2" @@ -47,3 +48,5 @@ lto = true [patch.crates-io] ring = { path = "deps/td-shim/library/ring" } +sys_time = { path = "src/std-support/sys_time" } + diff --git a/config/spdm_config.json b/config/spdm_config.json new file mode 100644 index 00000000..02654478 --- /dev/null +++ b/config/spdm_config.json @@ -0,0 +1,23 @@ +{ + "__usage": "This helps generate compile-time constant sizes for SPDM arrays. See src/config.rs generated for details.", + "cert_config": { + "max_cert_chain_data_size": 4096 + }, + "measurement_config": { + "max_measurement_record_size": 4000, + "max_measurement_val_len": 1024 + }, + "psk_config": { + "max_psk_context_size": 64, + "max_psk_hint_size": 32 + }, + "max_opaque_list_elements_count": 3, + "max_session_count": 4, + "transport_config": { + "sender_buffer_size": 4160, + "receiver_buffer_size": 4160 + }, + "max_spdm_msg_size": 65536, + "heartbeat_period_value": 0, + "max_root_cert_support": 10 +} diff --git a/deps/spdm-rs b/deps/spdm-rs new file mode 160000 index 00000000..c606cca7 --- /dev/null +++ b/deps/spdm-rs @@ -0,0 +1 @@ +Subproject commit c606cca78364d2ac2007ef6ce832bca964d7d70d diff --git a/deps/td-shim b/deps/td-shim index 99708f17..8d6d27b8 160000 --- a/deps/td-shim +++ b/deps/td-shim @@ -1 +1 @@ -Subproject commit 99708f174f5a3e50fcb9d2f7a955c85b18603b55 +Subproject commit 8d6d27b85441a7bdde026ec9fa26000cbc21b9a4 diff --git a/sh_script/preparation.sh b/sh_script/preparation.sh index 18126c77..c3f474ea 100755 --- a/sh_script/preparation.sh +++ b/sh_script/preparation.sh @@ -4,6 +4,11 @@ preparation() { pushd deps/td-shim bash sh_script/preparation.sh popd + + pushd deps/spdm-rs + bash sh_script/pre-build.sh + export SPDM_CONFIG=../../../config/spdm_config.json + popd } preparation diff --git a/src/async/async_io/src/lib.rs b/src/async/async_io/src/lib.rs index 487e51ad..cdfcf4db 100644 --- a/src/async/async_io/src/lib.rs +++ b/src/async/async_io/src/lib.rs @@ -32,11 +32,11 @@ pub use io::{Error, ErrorKind, IoSlice, IoSliceMut, Result}; /// Read bytes asynchronously. pub trait AsyncRead { /// Attempt to read from the `AsyncRead` into `buf`. - fn read(&mut self, buf: &mut [u8]) -> impl core::future::Future>; + fn read(&mut self, buf: &mut [u8]) -> impl core::future::Future> + Send; } /// Write bytes asynchronously. pub trait AsyncWrite { /// Attempt to write the `buf` into `AsyncWrite`. - fn write(&mut self, buf: &[u8]) -> impl core::future::Future>; + fn write(&mut self, buf: &[u8]) -> impl core::future::Future> + Send; } diff --git a/src/attestation/src/attest.rs b/src/attestation/src/attest.rs index 573908cb..d2ae3edf 100644 --- a/src/attestation/src/attest.rs +++ b/src/attestation/src/attest.rs @@ -35,27 +35,27 @@ pub struct Collateral { pub qe_identity: CString, } -impl Into for &Collateral { - fn into(self) -> QveCollateral { +impl From<&Collateral> for QveCollateral { + fn from(val: &Collateral) -> Self { QveCollateral { - major_version: self.major_version, - minor_version: self.minor_version, - tee_type: self.tee_type, - pck_crl_issuer_chain: self.pck_crl_issuer_chain.as_ptr(), - pck_crl_issuer_chain_size: self.pck_crl_issuer_chain.as_bytes_with_nul().len() as u32, - root_ca_crl: self.root_ca_crl.as_ptr(), - root_ca_crl_size: self.root_ca_crl.as_bytes_with_nul().len() as u32, - pck_crl: self.pck_crl.as_ptr(), - pck_crl_size: self.pck_crl.as_bytes_with_nul().len() as u32, - tcb_info_issuer_chain: self.tcb_info_issuer_chain.as_ptr(), - tcb_info_issuer_chain_size: self.tcb_info_issuer_chain.as_bytes_with_nul().len() as u32, - tcb_info: self.tcb_info.as_ptr(), - tcb_info_size: self.tcb_info.as_bytes_with_nul().len() as u32, - qe_identity_issuer_chain: self.qe_identity_issuer_chain.as_ptr(), - qe_identity_issuer_chain_size: self.qe_identity_issuer_chain.as_bytes_with_nul().len() + major_version: val.major_version, + minor_version: val.minor_version, + tee_type: val.tee_type, + pck_crl_issuer_chain: val.pck_crl_issuer_chain.as_ptr(), + pck_crl_issuer_chain_size: val.pck_crl_issuer_chain.as_bytes_with_nul().len() as u32, + root_ca_crl: val.root_ca_crl.as_ptr(), + root_ca_crl_size: val.root_ca_crl.as_bytes_with_nul().len() as u32, + pck_crl: val.pck_crl.as_ptr(), + pck_crl_size: val.pck_crl.as_bytes_with_nul().len() as u32, + tcb_info_issuer_chain: val.tcb_info_issuer_chain.as_ptr(), + tcb_info_issuer_chain_size: val.tcb_info_issuer_chain.as_bytes_with_nul().len() as u32, + tcb_info: val.tcb_info.as_ptr(), + tcb_info_size: val.tcb_info.as_bytes_with_nul().len() as u32, + qe_identity_issuer_chain: val.qe_identity_issuer_chain.as_ptr(), + qe_identity_issuer_chain_size: val.qe_identity_issuer_chain.as_bytes_with_nul().len() as u32, - qe_identity: self.qe_identity.as_ptr(), - qe_identity_size: self.qe_identity.as_bytes_with_nul().len() as u32, + qe_identity: val.qe_identity.as_ptr(), + qe_identity_size: val.qe_identity.as_bytes_with_nul().len() as u32, } } } diff --git a/src/crypto/src/rustls_impl/ecdsa.rs b/src/crypto/src/rustls_impl/ecdsa.rs index fa9f6bb3..b00c1087 100644 --- a/src/crypto/src/rustls_impl/ecdsa.rs +++ b/src/crypto/src/rustls_impl/ecdsa.rs @@ -3,13 +3,14 @@ // SPDX-License-Identifier: BSD-2-Clause-Patent use alloc::vec::Vec; +use pki_types::alg_id; use ring::pkcs8::Document; use ring::rand::SystemRandom; use ring::signature::{self, EcdsaKeyPair, KeyPair, UnparsedPublicKey}; use rustls_pemfile::Item; use zeroize::Zeroize; -use crate::{Error, Result}; +use crate::{x509, Error, Result}; // Re-export ring's ECDSA verification algorithms for convenient access pub use ring::signature::{ @@ -65,6 +66,20 @@ impl EcdsaPk { .map_err(|_| Error::GenerateKeyPair)?; Ok(ecdsa_key.public_key().as_ref().to_vec()) } + + pub fn public_key_spki(&self) -> Vec { + let rand: SystemRandom = SystemRandom::new(); + let ecdsa_key = EcdsaKeyPair::from_pkcs8( + &signature::ECDSA_P384_SHA384_ASN1_SIGNING, + self.pk.as_ref(), + &rand, + ) + .expect("public_key_spki: failed to get public key"); + let pub_key = ecdsa_key.public_key(); + let mut spki_inner = x509::wrap_in_sequence(&alg_id::ECDSA_P384); + spki_inner.extend(&x509::wrap_in_bit_string(pub_key.as_ref())); + x509::wrap_in_sequence(&spki_inner) + } } impl Drop for EcdsaPk { @@ -129,3 +144,12 @@ fn sensitive_data_cleanup(t: &mut T) { }; bytes.zeroize(); } + +#[test] +fn test_ecdsa() { + let ecdsa = EcdsaPk::new().unwrap(); + let data = b"test_data"; + let sig = ecdsa.sign(data).unwrap(); + let pub_key = ecdsa.public_key().unwrap(); + assert!(ecdsa_verify(&pub_key, data, &sig).is_ok()); +} diff --git a/src/crypto/src/x509.rs b/src/crypto/src/x509.rs index d9e92447..bfca2d61 100644 --- a/src/crypto/src/x509.rs +++ b/src/crypto/src/x509.rs @@ -453,3 +453,46 @@ impl<'a> Extension<'a> { } pub type ExtendedKeyUsage = Vec; + +/// Prepend stuff to `bytes` to put it in a DER SEQUENCE. +pub(crate) fn wrap_in_sequence(bytes: &[u8]) -> Vec { + asn1_wrap(DER_SEQUENCE_TAG, bytes, &[]) +} + +/// Prepend stuff to `bytes` to put it in a DER BIT STRING. +pub(crate) fn wrap_in_bit_string(bytes: &[u8]) -> Vec { + asn1_wrap(DER_BIT_STRING_TAG, &[0u8], bytes) +} + +fn asn1_wrap(tag: u8, bytes_a: &[u8], bytes_b: &[u8]) -> Vec { + let len = bytes_a.len() + bytes_b.len(); + + if len <= 0x7f { + // Short form + let mut ret = Vec::with_capacity(2 + len); + ret.push(tag); + ret.push(len as u8); + ret.extend_from_slice(bytes_a); + ret.extend_from_slice(bytes_b); + ret + } else { + // Long form + let size = len.to_be_bytes(); + let leading_zero_bytes = size.iter().position(|&x| x != 0).unwrap_or(size.len()); + assert!(leading_zero_bytes < size.len()); + let encoded_bytes = size.len() - leading_zero_bytes; + + let mut ret = Vec::with_capacity(2 + encoded_bytes + len); + ret.push(tag); + + ret.push(0x80 + encoded_bytes as u8); + ret.extend_from_slice(&size[leading_zero_bytes..]); + + ret.extend_from_slice(bytes_a); + ret.extend_from_slice(bytes_b); + ret + } +} + +const DER_SEQUENCE_TAG: u8 = 0x30; +const DER_BIT_STRING_TAG: u8 = 0x03; diff --git a/src/devices/vsock/src/stream.rs b/src/devices/vsock/src/stream.rs index 568efe39..e07da0bb 100644 --- a/src/devices/vsock/src/stream.rs +++ b/src/devices/vsock/src/stream.rs @@ -276,9 +276,7 @@ impl VsockStream { // Determine how much data to send in this packet let remaining = total_len - bytes_sent; let available_space = self.peer_free_space() as usize; - let chunk_size = remaining - .min(MAX_VSOCK_PKT_DATA_LEN as usize) - .min(available_space); + let chunk_size = remaining.min(MAX_VSOCK_PKT_DATA_LEN).min(available_space); let mut header_buf = [0u8; HEADER_LEN]; let mut packet = Packet::new_unchecked(&mut header_buf[..]); diff --git a/src/migtd/Cargo.toml b/src/migtd/Cargo.toml index 29ff1291..5a3a9da2 100644 --- a/src/migtd/Cargo.toml +++ b/src/migtd/Cargo.toml @@ -47,6 +47,13 @@ zerocopy = { version = "0.7", features = ["derive"] } minicov = { version = "0.2", default-features = false, optional = true } td-benchmark = { path = "../../deps/td-shim/devtools/td-benchmark", default-features = false, optional = true } +spdmlib = {path = "../../deps/spdm-rs/spdmlib", default-features = false, features = ["spdm-ring", "chunk-cap", "mut-auth", "hashed-transcript-data"]} +codec = {path= "../../deps/spdm-rs/codec"} +maybe-async = "0.2.7" +async-trait = "0.1.71" +zeroize = { version = "1.5.0", features = ["zeroize_derive"]} +ring = { version = "0.17.14" } + [features] default = ["virtio-vsock"] cet-shstk = ["td-payload/cet-shstk"] @@ -63,3 +70,7 @@ oneshot-apic = [] test_heap_size = ["td-benchmark", "td-payload/test_heap_size"] test_stack_size = ["td-benchmark"] test_disable_ra_and_accept_all = ["attestation/test"] # Dangerous: can only be used for test purpose to bypass the remote attestation +spdm_attestation = ["main"] + +[patch.crates-io] +sys_time = { path = "src/std-support/sys_time" } \ No newline at end of file diff --git a/src/migtd/src/lib.rs b/src/migtd/src/lib.rs index f3a22189..a719a6a0 100644 --- a/src/migtd/src/lib.rs +++ b/src/migtd/src/lib.rs @@ -14,6 +14,8 @@ pub mod event_log; pub mod mig_policy; pub mod migration; pub mod ratls; +#[cfg(feature = "spdm_attestation")] +pub mod spdm; /// The entry point of MigTD-Core /// @@ -25,8 +27,16 @@ pub extern "C" fn _start(hob: u64, payload: u64) -> ! { use td_payload::arch; use td_payload::mm::layout::*; + #[cfg(not(feature = "spdm_attestation"))] const STACK_SIZE: usize = 0x1_0000; + #[cfg(not(feature = "spdm_attestation"))] const HEAP_SIZE: usize = 0x20_0000; + + #[cfg(feature = "spdm_attestation")] + const STACK_SIZE: usize = 0x40_0000; + #[cfg(feature = "spdm_attestation")] + const HEAP_SIZE: usize = 0x40_0000; + const PT_SIZE: usize = 0x8_0000; extern "C" { diff --git a/src/migtd/src/migration/session.rs b/src/migtd/src/migration/session.rs index fe6e6b37..4ae83d8d 100644 --- a/src/migtd/src/migration/session.rs +++ b/src/migtd/src/migration/session.rs @@ -29,7 +29,10 @@ use zerocopy::AsBytes; type Result = core::result::Result; use super::{data::*, *}; +#[cfg(not(feature = "spdm_attestation"))] use crate::ratls; +#[cfg(feature = "spdm_attestation")] +use crate::spdm; const TDCALL_STATUS_SUCCESS: u64 = 0; #[cfg(feature = "vmcall-raw")] @@ -98,10 +101,10 @@ lazy_static! { pub static ref REQUESTS: Mutex> = Mutex::new(BTreeSet::new()); } -struct ExchangeInformation { - min_ver: u16, - max_ver: u16, - key: MigrationSessionKey, +pub struct ExchangeInformation { + pub min_ver: u16, + pub max_ver: u16, + pub key: MigrationSessionKey, } impl Default for ExchangeInformation { @@ -114,6 +117,7 @@ impl Default for ExchangeInformation { } } +#[cfg(not(feature = "spdm_attestation"))] impl ExchangeInformation { fn as_bytes(&self) -> &[u8] { unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, size_of::()) } @@ -700,9 +704,12 @@ async fn pre_session_data_exchange( #[cfg(feature = "main")] pub async fn exchange_msk(info: &MigrationInformation) -> Result<()> { + #[cfg(not(feature = "spdm_attestation"))] use crate::driver::ticks::with_timeout; + #[cfg(not(feature = "spdm_attestation"))] use core::time::Duration; + #[cfg(not(feature = "spdm_attestation"))] const TLS_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds #[cfg(feature = "policy_v2")] @@ -758,13 +765,14 @@ pub async fn exchange_msk(info: &MigrationInformation) -> Result<()> { }; let mut remote_information = ExchangeInformation::default(); - let mut exchange_information = exchange_info(&info)?; + let mut exchange_information = exchange_info(info)?; // Exchange policy firstly because of the message size limitation of TLS protocol #[cfg(feature = "policy_v2")] let remote_policy = pre_session_data_exchange(&mut transport).await?; // Establish TLS layer connection and negotiate the MSK + #[cfg(not(feature = "spdm_attestation"))] if info.is_src() { // TLS client let mut ratls_client = ratls::client( @@ -830,6 +838,29 @@ pub async fn exchange_msk(info: &MigrationInformation) -> Result<()> { .map_err(|_e| MigrationResult::InvalidParameter)?; } + #[cfg(feature = "spdm_attestation")] + if info.is_src() { + let mut spdm_requester = + spdm::spdm_requester(transport).map_err(|_| MigrationResult::SecureSessionError)?; + + let _res = spdm::spdm_requester_transfer_msk( + &mut spdm_requester, + &exchange_information, + &mut remote_information, + ) + .await; + } else { + let mut spdm_responder = + spdm::spdm_responder(transport).map_err(|_| MigrationResult::SecureSessionError)?; + + let _res = spdm::spdm_responder_transfer_msk( + &mut spdm_responder, + &exchange_information, + &mut remote_information, + ) + .await; + } + let mig_ver = cal_mig_version(info.is_src(), &exchange_information, &remote_information)?; set_mig_version(info, mig_ver)?; write_msk(&info.mig_info, &remote_information.key)?; diff --git a/src/migtd/src/spdm/mod.rs b/src/migtd/src/spdm/mod.rs new file mode 100644 index 00000000..16a1f7a7 --- /dev/null +++ b/src/migtd/src/spdm/mod.rs @@ -0,0 +1,57 @@ +#[cfg(feature = "spdm_attestation")] +mod spdm_req; +#[cfg(feature = "spdm_attestation")] +mod spdm_rsp; +#[cfg(feature = "spdm_attestation")] +mod spdm_vdm; +#[cfg(feature = "spdm_attestation")] +mod vmcall_msg; + +#[cfg(feature = "spdm_attestation")] +use alloc::vec::Vec; +#[cfg(feature = "spdm_attestation")] +use core::time::Duration; + +#[cfg(feature = "spdm_attestation")] +use async_io::AsyncRead; +#[cfg(feature = "spdm_attestation")] +use async_io::AsyncWrite; +#[cfg(feature = "spdm_attestation")] +use crypto::hash::digest_sha384; +#[cfg(feature = "spdm_attestation")] +pub use spdm_req::spdm_requester; +#[cfg(feature = "spdm_attestation")] +pub use spdm_req::spdm_requester_transfer_msk; +#[cfg(feature = "spdm_attestation")] +pub use spdm_rsp::spdm_responder; + +#[cfg(feature = "spdm_attestation")] +pub use spdm_rsp::*; +#[cfg(feature = "spdm_attestation")] +pub use spdm_vdm::*; + +#[cfg(feature = "spdm_attestation")] +use crate::migration::MigrationResult; + +#[cfg(feature = "spdm_attestation")] +const SPDM_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds + +#[cfg(feature = "spdm_attestation")] +pub struct MigtdTransport { + pub transport: T, +} +#[cfg(feature = "spdm_attestation")] +unsafe impl Send for MigtdTransport {} + +#[cfg(feature = "spdm_attestation")] +pub fn gen_quote_spdm(report_data: &[u8]) -> Result, MigrationResult> { + let hash = digest_sha384(report_data)?; + + // Generate the TD Report that contains the public key hash as nonce + let mut additional_data = [0u8; 64]; + additional_data[..hash.len()].copy_from_slice(hash.as_ref()); + let td_report = tdx_tdcall::tdreport::tdcall_report(&additional_data)?; + + let res = attestation::get_quote(td_report.as_bytes()).unwrap(); + Ok(res) +} diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs new file mode 100644 index 00000000..e01c55fd --- /dev/null +++ b/src/migtd/src/spdm/spdm_req.rs @@ -0,0 +1,735 @@ +// Spdm Requester is the MigTD src side implementation. + +use async_io::{AsyncRead, AsyncWrite}; +use async_trait::async_trait; +use codec::{Codec, Reader, Writer}; +use crypto::{ecdsa::EcdsaPk, hash::digest_sha384}; +use spdmlib::{ + common::{self, *}, + config, + error::*, + message::*, + protocol::*, + requester::RequesterContext, +}; +use spin::Mutex; +extern crate alloc; +use alloc::boxed::Box; +use alloc::sync::Arc; +use log::error; +use spdmlib::common::SpdmDeviceIo; + +use crate::{ + config::get_policy, + driver::ticks::with_timeout, + event_log::get_event_log, + migration::session::ExchangeInformation, + spdm::{vmcall_msg::VmCallTransportEncap, *}, +}; + +#[async_trait] +impl SpdmDeviceIo for MigtdTransport { + async fn send(&mut self, buffer: Arc<&[u8]>) -> SpdmResult { + let mut sent = 0; + while sent < buffer.len() { + match self.transport.write(&buffer[sent..]).await { + Ok(len) => sent += len, + Err(_) => return Err(SPDM_STATUS_SEND_FAIL), + } + } + Ok(()) + } + + async fn receive( + &mut self, + buffer: Arc>, + _timeout: usize, + ) -> Result { + let mut buffer = buffer.lock(); + let mut received = 0; + while received == 0 { + match self.transport.read(&mut buffer).await { + Ok(len) => received += len, + Err(_) => return Err(0), + } + } + Ok(received) + } + + async fn flush_all(&mut self) -> SpdmResult { + Ok(()) + } +} + +pub fn spdm_requester( + stream: T, +) -> Result { + let transport = MigtdTransport { transport: stream }; + let device_io = Arc::new(Mutex::new(transport)); + + let req_capabilities = SpdmRequestCapabilityFlags::ENCRYPT_CAP + | SpdmRequestCapabilityFlags::MAC_CAP + | SpdmRequestCapabilityFlags::MUT_AUTH_CAP + | SpdmRequestCapabilityFlags::KEY_EX_CAP + | SpdmRequestCapabilityFlags::PUB_KEY_ID_CAP + | SpdmRequestCapabilityFlags::HANDSHAKE_IN_THE_CLEAR_CAP + | SpdmRequestCapabilityFlags::CHUNK_CAP; + + let config_info = common::SpdmConfigInfo { + spdm_version: [None, None, Some(SpdmVersion::SpdmVersion12), None, None], + req_capabilities, + req_ct_exponent: 0, + measurement_specification: SpdmMeasurementSpecification::default(), + base_asym_algo: SpdmBaseAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384, + base_hash_algo: SpdmBaseHashAlgo::TPM_ALG_SHA_384, + dhe_algo: SpdmDheAlgo::SECP_384_R1, + aead_algo: SpdmAeadAlgo::AES_256_GCM, + req_asym_algo: SpdmReqAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384, + key_schedule_algo: SpdmKeyScheduleAlgo::SPDM_KEY_SCHEDULE, + other_params_support: SpdmAlgoOtherParams::OPAQUE_DATA_FMT1, + data_transfer_size: config::SPDM_DATA_TRANSFER_SIZE as u32, + max_spdm_msg_size: config::MAX_SPDM_MSG_SIZE as u32, + secure_spdm_version: [ + None, + None, + Some(SecuredMessageVersion::try_from(0x12u8).unwrap()), + ], + ..Default::default() + }; + + let provision_info = SpdmProvisionInfo { + ..Default::default() + }; + + // Create a transport layer + let transport_encap = Arc::new(Mutex::new(VmCallTransportEncap {})); + + // Initialize the RequesterContext + let requester_context = + RequesterContext::new(device_io, transport_encap, config_info, provision_info); + + spdmlib::secret::asym_sign::register(SECRET_ASYM_IMPL_INSTANCE.clone()); + + Ok(requester_context) +} + +pub async fn spdm_requester_transfer_msk( + spdm_requester: &mut RequesterContext, + exchange_info: &ExchangeInformation, + remote_info: &mut ExchangeInformation, +) -> Result<(), SpdmStatus> { + let res = with_timeout(SPDM_TIMEOUT, spdm_requester.send_receive_spdm_version()).await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res = with_timeout(SPDM_TIMEOUT, spdm_requester.send_receive_spdm_capability()).await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res = with_timeout(SPDM_TIMEOUT, spdm_requester.send_receive_spdm_algorithm()).await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res = with_timeout(SPDM_TIMEOUT, send_and_receive_pub_key(spdm_requester)).await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res = with_timeout( + SPDM_TIMEOUT, + spdm_requester.send_receive_spdm_key_exchange( + 0xff, + SpdmMeasurementSummaryHashType::SpdmMeasurementSummaryHashTypeNone, + ), + ) + .await; + let session_id = match res { + Ok(Ok(sid)) => sid, + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res: Result, crate::driver::ticks::TimeoutError> = with_timeout( + SPDM_TIMEOUT, + send_and_receive_sdm_migration_attest_info(spdm_requester, session_id), + ) + .await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + let res = with_timeout( + SPDM_TIMEOUT, + spdm_requester.send_receive_spdm_finish(Some(0xff), session_id), + ) + .await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res = with_timeout( + SPDM_TIMEOUT, + send_and_receive_sdm_exchange_migration_info( + spdm_requester, + exchange_info, + remote_info, + Some(session_id), + ), + ) + .await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + + let res = with_timeout( + SPDM_TIMEOUT, + spdm_requester.send_receive_spdm_end_session(session_id), + ) + .await; + match res { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + return Err(e); + } + Err(_) => { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + }; + Ok(()) +} + +async fn send_and_receive_pub_key(spdm_requester: &mut RequesterContext) -> SpdmResult { + let signing_key = EcdsaPk::new().unwrap(); + let mut key_bytes = SIGNING_KEY.lock(); + let private_key = signing_key.private_key(); + key_bytes[..private_key.len()].copy_from_slice(private_key); + let pub_key = signing_key.public_key_spki(); + + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; + vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); + let vendor_id = VendorIDStruct { len: 4, vendor_id }; + + let mut payload = [0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_pub_key = VdmMessage { + major_version: 0, + op_code: VdmMessageOpCode::ExchangePubKeyReq, + element_count: 1, + }; + cnt += vdm_exchange_pub_key + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + let pub_key_element = VdmMessageElement { + element_type: VdmMessageElementType::PubKeyMy, + length: pub_key.len() as u16, // to be updated + }; + cnt += pub_key_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(pub_key.as_slice()).unwrap(); + + let vdm_payload = VendorDefinedReqPayloadStruct { + req_length: cnt as u32, + vendor_defined_req_payload: payload, + }; + + spdm_requester.common.reset_buffer_via_request_code( + SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + None, + ); + + let mut send_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let mut writer = Writer::init(&mut send_buffer); + let request = SpdmMessage { + header: SpdmMessageHeader { + version: spdm_requester.common.negotiate_info.spdm_version_sel, + request_response_code: SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + }, + payload: SpdmMessagePayload::SpdmVendorDefinedRequest(SpdmVendorDefinedRequestPayload { + standard_id: RegistryOrStandardsBodyID::IANA, + vendor_id, + req_payload: vdm_payload, + }), + }; + let used = request.spdm_encode(&mut spdm_requester.common, &mut writer)?; + + spdm_requester + .send_message(None, &send_buffer[..used], false) + .await?; + + let mut receive_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let receive_used = spdm_requester + .receive_message(None, &mut receive_buffer, false) + .await?; + + let vdm_payload = spdm_requester + .handle_spdm_vendor_defined_respond(None, &receive_buffer[..receive_used]) + .unwrap(); + + // Format checks and save the received public key + let mut reader = + Reader::init(&vdm_payload.vendor_defined_rsp_payload[..vdm_payload.rsp_length as usize]); + let vdm_message = VdmMessage::read(&mut reader).unwrap(); + if vdm_message.op_code != VdmMessageOpCode::ExchangePubKeyRsp { + error!("Invalid VDM message op_code: {:x?}\n", vdm_message.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.element_count != 1 { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_message.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let vdm_element = VdmMessageElement::read(&mut reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::PubKeyMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_element.length as usize != reader.left() { + error!( + "Invalid VDM message element length: {:x?}, left: {:x?}\n", + vdm_element.length, + reader.left() + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let pub_key_peer = reader.take(vdm_element.length as usize).unwrap(); + + let mut my_pub_key = SpdmCertChainData { + data_size: pub_key.len() as u32, + data: [0u8; config::MAX_SPDM_CERT_CHAIN_DATA_SIZE], + }; + my_pub_key.data[..pub_key.len()].copy_from_slice(&pub_key); + spdm_requester.common.provision_info.my_pub_key = Some(my_pub_key); + + let mut peer_pub_key = SpdmCertChainData { + data_size: pub_key_peer.len() as u32, + data: [0u8; config::MAX_SPDM_CERT_CHAIN_DATA_SIZE], + }; + peer_pub_key.data[..pub_key_peer.len()].copy_from_slice(pub_key_peer); + spdm_requester.common.provision_info.peer_pub_key = Some(peer_pub_key); + + let vdm_pub_key_src_hash = digest_sha384(&send_buffer[..used]).unwrap(); + let vdm_pub_key_dst_hash = digest_sha384(&receive_buffer[..receive_used]).unwrap(); + let mut transcript_before_key_exchange = ManagedVdmBuffer::default(); + transcript_before_key_exchange + .append_message(vdm_pub_key_src_hash.as_slice()) + .unwrap(); + transcript_before_key_exchange + .append_message(vdm_pub_key_dst_hash.as_slice()) + .unwrap(); + + spdm_requester + .common + .runtime_info + .vdm_message_transcript_before_key_exchange = Some(transcript_before_key_exchange); + + Ok(()) +} + +pub async fn send_and_receive_sdm_migration_attest_info( + spdm_requester: &mut RequesterContext, + session_id: u32, +) -> SpdmResult { + if spdm_requester.common.provision_info.my_pub_key.is_none() + || spdm_requester.common.provision_info.peer_pub_key.is_none() + { + error!("Cannot transfer attestation info without provisioning pub_key.\n"); + return Err(SPDM_STATUS_UNSUPPORTED_CAP); + } + + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; + vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); + let vendor_id = VendorIDStruct { len: 4, vendor_id }; + + let mut payload = [0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_attest_info = VdmMessage { + major_version: 0, + op_code: VdmMessageOpCode::ExchangeMigrationAttestInfoReq, + element_count: 3, + }; + + cnt += vdm_exchange_attest_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + let th1 = if let Some(s) = spdm_requester.common.get_session_via_id(session_id) { + s.get_th1() + } else { + error!("Cannot get session id. Attestation failed.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + }; + + let report_data_prefix = "MigTDReq".as_bytes(); + let report_data_prefix_len = report_data_prefix.len(); + // Build concatenated slice: "MigTDRsp" || th1 + let th1_len = th1.data_size as usize; + // th1 for SHA-384 should be 48 bytes; allocate 8 (prefix) + 48 digest + let mut report_data = [0u8; "MigTDReq".len() + SPDM_MAX_HASH_SIZE]; + // Copy prefix + report_data[..report_data_prefix_len].copy_from_slice(report_data_prefix); + report_data[report_data_prefix_len..report_data_prefix_len + th1_len] + .copy_from_slice(&th1.data[..th1_len]); + + //quote src + let quote_src = gen_quote_spdm(&report_data[..report_data_prefix_len + th1_len]) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + let quote_element = VdmMessageElement { + element_type: VdmMessageElementType::QuoteMy, + length: quote_src.len() as u16, + }; + cnt += quote_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(quote_src.as_slice()).unwrap(); + + //event log src + let event_log = get_event_log().ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + let event_log_element = VdmMessageElement { + element_type: VdmMessageElementType::EventLogMy, + length: event_log.len() as u16, + }; + cnt += event_log_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(event_log).unwrap(); + + //mig policy src + let mig_policy = get_policy().unwrap(); + let mig_policy_hash = digest_sha384(mig_policy).unwrap(); + + let mig_policy_element = VdmMessageElement { + element_type: VdmMessageElementType::MigPolicyMy, + length: mig_policy_hash.len() as u16, + }; + cnt += mig_policy_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(&mig_policy_hash).unwrap(); + + let vdm_payload = VendorDefinedReqPayloadStruct { + req_length: cnt as u32, + vendor_defined_req_payload: payload, + }; + + spdm_requester.common.reset_buffer_via_request_code( + SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + None, + ); + + let mut send_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let mut writer = Writer::init(&mut send_buffer); + let request = SpdmMessage { + header: SpdmMessageHeader { + version: spdm_requester.common.negotiate_info.spdm_version_sel, + request_response_code: SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + }, + payload: SpdmMessagePayload::SpdmVendorDefinedRequest(SpdmVendorDefinedRequestPayload { + standard_id: RegistryOrStandardsBodyID::IANA, + vendor_id, + req_payload: vdm_payload, + }), + }; + let used = request.spdm_encode(&mut spdm_requester.common, &mut writer)?; + + spdm_requester + .send_message(None, &send_buffer[..used], false) + .await?; + + //receive + let mut receive_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let receive_used = spdm_requester + .receive_message(None, &mut receive_buffer, false) + .await?; + + let vdm_payload = spdm_requester + .handle_spdm_vendor_defined_respond(None, &receive_buffer[..receive_used]) + .unwrap(); + // Todo data received check. + let reader = &mut Reader::init( + &vdm_payload.vendor_defined_rsp_payload[..vdm_payload.rsp_length as usize], + ); + let vdm_message = VdmMessage::read(reader).unwrap(); + if vdm_message.op_code != VdmMessageOpCode::ExchangeMigrationAttestInfoRsp { + error!("Invalid VDM message op_code: {:x?}\n", vdm_message.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.element_count != 3 { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_message.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + //quote dst + { + let vdm_element = VdmMessageElement::read(reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::QuoteMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let quote_dst = reader.take(vdm_element.length as usize).unwrap(); + let res = attestation::verify_quote(quote_dst); + if res.is_err() { + error!("mutual attestation failed, end the session!\n"); + let session = spdm_requester + .common + .get_session_via_id(session_id) + .unwrap(); + session.teardown(); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + } + { + //event log dst + let vdm_element = VdmMessageElement::read(reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::EventLogMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let _event_log_dst = reader.take(vdm_element.length as usize).unwrap(); + } + { + //mig policy dst + let vdm_element = VdmMessageElement::read(reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::MigPolicyMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let _mig_policy_dst = reader.take(vdm_element.length as usize).unwrap(); + } + + let vdm_attest_info_src_hash = digest_sha384(&send_buffer[..used]).unwrap(); + let vdm_attest_info_dst_hash = digest_sha384(&receive_buffer[..receive_used]).unwrap(); + let mut transcript_before_finish = ManagedVdmBuffer::default(); + transcript_before_finish + .append_message(vdm_attest_info_src_hash.as_slice()) + .unwrap(); + transcript_before_finish + .append_message(vdm_attest_info_dst_hash.as_slice()) + .unwrap(); + if let Some(s) = spdm_requester.common.get_session_via_id(session_id) { + s.runtime_info.vdm_message_transcript_before_finish = Some(transcript_before_finish); + } else { + error!("Cannot get session id. Attestation failed.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + Ok(()) +} + +async fn send_and_receive_sdm_exchange_migration_info( + spdm_requester: &mut RequesterContext, + exchange_info: &ExchangeInformation, + remote_info: &mut ExchangeInformation, + session_id: Option, +) -> SpdmResult { + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; + vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); + let vendor_id = VendorIDStruct { len: 4, vendor_id }; + + let mut payload = [0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_migration_info = VdmMessage { + major_version: 0, + op_code: VdmMessageOpCode::ExchangeMigrationInfoReq, + element_count: 2, + }; + + cnt += vdm_exchange_migration_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + //Migration Export Version + let mig_export_version_element = VdmMessageElement { + element_type: VdmMessageElementType::MigrationExportVersion, + length: 4, + }; + cnt += mig_export_version_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += exchange_info + .min_ver + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += exchange_info + .max_ver + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + //Forward Migration Session Key + let mig_session_key_element = VdmMessageElement { + element_type: VdmMessageElementType::ForwardMigrationSessionKey, + length: 32, + }; + cnt += mig_session_key_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += exchange_info + .key + .fields + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + let vdm_payload = VendorDefinedReqPayloadStruct { + req_length: cnt as u32, + vendor_defined_req_payload: payload, + }; + + spdm_requester.common.reset_buffer_via_request_code( + SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + None, + ); + + let mut send_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let mut writer = Writer::init(&mut send_buffer); + let request = SpdmMessage { + header: SpdmMessageHeader { + version: spdm_requester.common.negotiate_info.spdm_version_sel, + request_response_code: SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + }, + payload: SpdmMessagePayload::SpdmVendorDefinedRequest(SpdmVendorDefinedRequestPayload { + standard_id: RegistryOrStandardsBodyID::IANA, + vendor_id, + req_payload: vdm_payload, + }), + }; + let used = request.spdm_encode(&mut spdm_requester.common, &mut writer)?; + + spdm_requester + .send_message(session_id, &send_buffer[..used], false) + .await?; + + let mut receive_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let receive_used = spdm_requester + .receive_message(session_id, &mut receive_buffer, false) + .await?; + + let vdm_payload = spdm_requester + .handle_spdm_vendor_defined_respond(session_id, &receive_buffer[..receive_used]) + .unwrap(); + // Todo data received check. + + let reader = &mut Reader::init( + &vdm_payload.vendor_defined_rsp_payload[..vdm_payload.rsp_length as usize], + ); + let vdm_exchange_migration_info = VdmMessage::read(reader).unwrap(); + if vdm_exchange_migration_info.op_code != VdmMessageOpCode::ExchangeMigrationInfoRsp { + error!( + "Invalid VDM message op_code: {:x?}\n", + vdm_exchange_migration_info.op_code + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_exchange_migration_info.element_count != 2 { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_exchange_migration_info.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let mig_export_version_element = VdmMessageElement::read(reader).unwrap(); + if mig_export_version_element.element_type != VdmMessageElementType::MigrationImportVersion { + error!( + "Invalid VDM message element_type: {:x?}\n", + mig_export_version_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if mig_export_version_element.length != 4 { + error!( + "Invalid VDM message element length: {:x?}\n", + mig_export_version_element.length + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + remote_info.min_ver = u16::read(reader).unwrap(); + remote_info.max_ver = u16::read(reader).unwrap(); + let mig_session_key_element = VdmMessageElement::read(reader).unwrap(); + if mig_session_key_element.element_type != VdmMessageElementType::BackwardMigrationSessionKey { + error!( + "Invalid VDM message element_type: {:x?}\n", + mig_session_key_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if mig_session_key_element.length != 32 { + error!( + "Invalid VDM message element length: {:x?}\n", + mig_session_key_element.length + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + remote_info.key.fields = <[u64; 4]>::read(reader).unwrap(); + + Ok(()) +} diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs new file mode 100644 index 00000000..1333016c --- /dev/null +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -0,0 +1,546 @@ +// Spdm Responder is the MigTD dst side implementation. 11 + +use crate::{ + config::get_policy, driver::ticks::with_timeout, event_log::get_event_log, + migration::session::ExchangeInformation, +}; +use alloc::sync::Arc; +use async_io::{AsyncRead, AsyncWrite}; +use codec::{Codec, Reader, Writer}; +use core::ops::DerefMut; +use crypto::{ecdsa::EcdsaPk, hash::digest_sha384}; +use log::error; + +use crate::spdm::{vmcall_msg::VmCallTransportEncap, *}; +use spdmlib::{ + common::{self, *}, + config, + error::*, + message::*, + protocol::*, + responder::ResponderContext, + secret::SpdmSecretAsymSign, +}; +use spin::Mutex; +use zerocopy::AsBytes; +use zeroize::Zeroize; + +extern crate alloc; + +const ECDSA_P384_SHA384_PRIVATE_KEY_LENGTH: usize = 0xb9; +const ECDSA_P384_SHA384_PUBLIC_KEY_LENGTH: usize = 0x78; +pub static SIGNING_KEY: Mutex<[u8; ECDSA_P384_SHA384_PRIVATE_KEY_LENGTH]> = + Mutex::new([0u8; ECDSA_P384_SHA384_PRIVATE_KEY_LENGTH]); + +const EXCHANGE_INFO_BUFFER_SIZE: usize = 36; +pub static EXCHANGE_INFO_LOCAL: Mutex<[u8; 36]> = Mutex::new([0u8; EXCHANGE_INFO_BUFFER_SIZE]); +pub static EXCHANGE_INFO_PEER: Mutex<[u8; 36]> = Mutex::new([0u8; EXCHANGE_INFO_BUFFER_SIZE]); + +pub fn spdm_responder( + stream: T, +) -> Result { + let transport = MigtdTransport { transport: stream }; + let device_io = Arc::new(Mutex::new(transport)); + + let rsp_capabilities = SpdmResponseCapabilityFlags::ENCRYPT_CAP + | SpdmResponseCapabilityFlags::MAC_CAP + | SpdmResponseCapabilityFlags::MUT_AUTH_CAP + | SpdmResponseCapabilityFlags::KEY_EX_CAP + | SpdmResponseCapabilityFlags::PUB_KEY_ID_CAP + | SpdmResponseCapabilityFlags::HANDSHAKE_IN_THE_CLEAR_CAP + | SpdmResponseCapabilityFlags::CHUNK_CAP; + + let config_info = common::SpdmConfigInfo { + spdm_version: [None, None, Some(SpdmVersion::SpdmVersion12), None, None], + rsp_capabilities, + req_ct_exponent: 0, + measurement_specification: SpdmMeasurementSpecification::default(), + base_asym_algo: SpdmBaseAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384, + base_hash_algo: SpdmBaseHashAlgo::TPM_ALG_SHA_384, + dhe_algo: SpdmDheAlgo::SECP_384_R1, + aead_algo: SpdmAeadAlgo::AES_256_GCM, + req_asym_algo: SpdmReqAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384, + key_schedule_algo: SpdmKeyScheduleAlgo::SPDM_KEY_SCHEDULE, + other_params_support: SpdmAlgoOtherParams::OPAQUE_DATA_FMT1, + data_transfer_size: config::SPDM_DATA_TRANSFER_SIZE as u32, + max_spdm_msg_size: config::MAX_SPDM_MSG_SIZE as u32, + secure_spdm_version: [ + None, + None, + Some(SecuredMessageVersion::try_from(0x12u8).unwrap()), + ], + ..Default::default() + }; + + let provision_info = SpdmProvisionInfo { + ..Default::default() + }; + + // Create a transport layer + let transport_encap = Arc::new(Mutex::new(VmCallTransportEncap {})); + + // Initialize the RequesterContext + let mut responder_context = + ResponderContext::new(device_io, transport_encap, config_info, provision_info); + responder_context.common.encap_context.mut_auth_requested = + SpdmKeyExchangeMutAuthAttributes::MUT_AUTH_REQ; + responder_context.common.encap_context.req_slot_id = SPDM_PUB_KEY_SLOT_ID_KEY_EXCHANGE_RSP; + + spdmlib::message::vendor::register_vendor_defined_struct_ex(VendorDefinedStructEx { + vendor_defined_request_handler_ex: migtd_vdm_msg_rsp_dispatcher_ex, + vdm_handle: 0, + }); + spdmlib::secret::asym_sign::register(SECRET_ASYM_IMPL_INSTANCE.clone()); + + Ok(responder_context) +} + +pub async fn spdm_responder_transfer_msk( + spdm_responder: &mut ResponderContext, + exchange_info: &ExchangeInformation, + remote_info: &mut ExchangeInformation, +) -> Result<(), SpdmStatus> { + { + let mut exchange_info_local = EXCHANGE_INFO_LOCAL.lock(); + let len = exchange_info_local.len(); + let mut writer = Writer::init(&mut exchange_info_local[..len]); + exchange_info + .min_ver + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_TOO_SMALL)?; + exchange_info + .max_ver + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_TOO_SMALL)?; + exchange_info + .key + .fields + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_TOO_SMALL)?; + } + + let _res = with_timeout(SPDM_TIMEOUT, rsp_handle_message(spdm_responder)).await; + + { + let mut exchange_info_peer = EXCHANGE_INFO_PEER.lock(); + let len = exchange_info_peer.len(); + let mut reader = Reader::init(&exchange_info_peer[..len]); + remote_info.min_ver = u16::read(&mut reader).unwrap(); + remote_info.max_ver = u16::read(&mut reader).unwrap(); + remote_info.key.fields = <[u64; 4]>::read(&mut reader).unwrap(); + exchange_info_peer[..len].zeroize(); + } + + Ok(()) +} + +pub async fn rsp_handle_message(spdm_responder: &mut ResponderContext) -> Result<(), SpdmStatus> { + let mut sid = None; + loop { + let raw_packet = Arc::new(Mutex::new([0u8; config::RECEIVER_BUFFER_SIZE])); + let mut raw_packet = raw_packet.lock(); + let raw_packet = raw_packet.deref_mut(); + raw_packet.zeroize(); + let res = spdm_responder.process_message(false, 0, raw_packet).await; + + let session_id = spdm_responder.common.runtime_info.get_last_session_id(); + if session_id.is_some() { + sid = session_id; + } + if sid.is_some() + && spdm_responder + .common + .get_session_via_id(sid.unwrap()) + .is_none() + { + //Terminate the responder upon end_session received. + break; + } + if res.is_err() { + return Err(SPDM_STATUS_RECEIVE_FAIL); + } + } + Ok(()) +} + +pub fn handle_exchange_pub_key_req( + spdm_responder: &mut ResponderContext, + vdm_request: &VdmMessage, + reader: &mut Reader<'_>, +) -> SpdmResult { + if spdm_responder + .common + .runtime_info + .get_connection_state() + .get_u8() + < SpdmConnectionState::SpdmConnectionNegotiated.get_u8() + { + error!("Cannot negotiate pub_key before connection established.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + let peer_pub_key = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + if peer_pub_key.element_type != VdmMessageElementType::PubKeyMy + || vdm_request.element_count != 1 + { + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let peer_pub_key_length = peer_pub_key.length as usize; + let mut pub_key_buf = [0u8; ECDSA_P384_SHA384_PUBLIC_KEY_LENGTH]; + for d in pub_key_buf.iter_mut().take(peer_pub_key_length) { + *d = u8::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + } + + let mut peer_pub_key_prov = SpdmCertChainData { + data_size: peer_pub_key_length as u32, + data: [0u8; config::MAX_SPDM_CERT_CHAIN_DATA_SIZE], + }; + peer_pub_key_prov.data[..peer_pub_key_length] + .copy_from_slice(&pub_key_buf[..peer_pub_key_length]); + spdm_responder.common.provision_info.peer_pub_key = Some(peer_pub_key_prov); + + let signing_key = EcdsaPk::new().unwrap(); + let my_pub_key = signing_key.public_key_spki(); + + // Save my private key to SIGNING_KEY for signing + let mut key_bytes = SIGNING_KEY.lock(); + let private_key = signing_key.private_key(); + key_bytes[..private_key.len()].copy_from_slice(private_key); + + let mut my_pub_key_prov = SpdmCertChainData { + data_size: my_pub_key.len() as u32, + data: [0u8; config::MAX_SPDM_CERT_CHAIN_DATA_SIZE], + }; + my_pub_key_prov.data[..my_pub_key.len()].copy_from_slice(&my_pub_key); + spdm_responder.common.provision_info.my_pub_key = Some(my_pub_key_prov); + + let mut payload = [0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_pub_key = VdmMessage { + major_version: 0, + op_code: VdmMessageOpCode::ExchangePubKeyRsp, + element_count: 1, + }; + + cnt += vdm_exchange_pub_key + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + let pub_key_element = VdmMessageElement { + element_type: VdmMessageElementType::PubKeyMy, + length: my_pub_key.len() as u16, + }; + + cnt += pub_key_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(my_pub_key.as_bytes()).unwrap(); + + Ok(VendorDefinedRspPayloadStruct { + rsp_length: cnt as u32, + vendor_defined_rsp_payload: payload, + }) +} + +pub fn handle_exchange_mig_attest_info_req( + responder_context: &mut ResponderContext, + session_id: Option, + vdm_request: &VdmMessage, + reader: &mut Reader<'_>, +) -> SpdmResult { + let session_id = if session_id.is_some() { + session_id + } else { + responder_context.common.runtime_info.get_last_session_id() + }; + if session_id.is_none() { + error!("Cannot transfer attestation info before key exchange.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + if responder_context.common.provision_info.my_pub_key.is_none() + || responder_context + .common + .provision_info + .peer_pub_key + .is_none() + { + error!("Cannot transfer attestation info without provisioning pub_key.\n"); + return Err(SPDM_STATUS_UNSUPPORTED_CAP); + } + + if vdm_request.op_code != VdmMessageOpCode::ExchangeMigrationAttestInfoReq { + error!("Invalid VDM message op_code: {:x?}\n", vdm_request.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.element_count != 3 { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_request.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let vdm_element = VdmMessageElement::read(reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::QuoteMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let quote_dst = reader.take(vdm_element.length as usize).unwrap(); + let res = attestation::verify_quote(quote_dst); + + // The session MUST be terminated immediately, if the mutual attestation failure + if res.is_err() { + error!("mutual attestation failed, end the session!\n"); + let session = responder_context + .common + .get_session_via_id(session_id.unwrap()) + .unwrap(); + session.teardown(); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + //event log dst + let vdm_element = VdmMessageElement::read(reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::EventLogMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let _event_log_dst = reader.take(vdm_element.length as usize).unwrap(); + + //mig policy dst + let vdm_element = VdmMessageElement::read(reader).unwrap(); + if vdm_element.element_type != VdmMessageElementType::MigPolicyMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let _mig_policy_dst = reader.take(vdm_element.length as usize).unwrap(); + + let mut payload = [0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_attest_info = VdmMessage { + major_version: 0, + op_code: VdmMessageOpCode::ExchangeMigrationAttestInfoRsp, + element_count: 3, + }; + + cnt += vdm_exchange_attest_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + let th1 = if let Some(sid) = session_id { + if let Some(s) = responder_context.common.get_session_via_id(sid) { + s.get_th1() + } else { + SpdmDigestStruct::default() + } + } else { + SpdmDigestStruct::default() + }; + + let report_data_prefix = "MigTDRsp".as_bytes(); + let report_data_prefix_len = report_data_prefix.len(); + // Build concatenated slice: "MigTDRsp" || th1 + let th1_len = th1.data_size as usize; + // th1 for SHA-384 should be 48 bytes; allocate 8 (prefix) + 48 digest + let mut report_data = [0u8; "MigTDRsp".len() + SPDM_MAX_HASH_SIZE]; + // Copy prefix + report_data[..report_data_prefix_len].copy_from_slice(report_data_prefix); + report_data[report_data_prefix_len..report_data_prefix_len + th1_len] + .copy_from_slice(&th1.data[..th1_len]); + + //quote dst + let quote_dst = gen_quote_spdm(&report_data[..report_data_prefix_len + th1_len]) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + let quote_element = VdmMessageElement { + element_type: VdmMessageElementType::QuoteMy, + length: quote_dst.len() as u16, + }; + cnt += quote_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(quote_dst.as_slice()).unwrap(); + + //event log dst + let event_log = get_event_log().ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + let event_log_element = VdmMessageElement { + element_type: VdmMessageElementType::EventLogMy, + length: event_log.len() as u16, + }; + cnt += event_log_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(event_log).unwrap(); + + //mig policy dst + let mig_policy = get_policy().unwrap(); + let mig_policy_hash = digest_sha384(mig_policy.as_bytes()).unwrap(); + let mig_policy_element = VdmMessageElement { + element_type: VdmMessageElementType::MigPolicyMy, + length: mig_policy_hash.len() as u16, + }; + cnt += mig_policy_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(&mig_policy_hash).unwrap(); + + Ok(VendorDefinedRspPayloadStruct { + rsp_length: cnt as u32, + vendor_defined_rsp_payload: payload, + }) +} + +pub fn handle_exchange_mig_info_req( + responder_context: &mut ResponderContext, + session_id: Option, + vdm_request: &VdmMessage, + reader: &mut Reader<'_>, +) -> SpdmResult { + // The VDM message for secret migration info exchange MUST be sent after mutual attested session establishment. + // Open: How to make sure attestion is done before mig info? + if session_id.is_none() { + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let session = responder_context + .common + .get_session_via_id(session_id.unwrap()) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if session.get_session_state() + != spdmlib::common::session::SpdmSessionState::SpdmSessionEstablished + { + error!("Migration info received while session is not established!\n"); + session.teardown(); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + let mut exchange_info_peer = EXCHANGE_INFO_PEER.lock(); + let len = exchange_info_peer.len(); + let mut writer = Writer::init(&mut exchange_info_peer[..len]); + + let mig_export_version = + VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + let min_export_version = u16::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + let max_export_version = u16::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + if mig_export_version.element_type != VdmMessageElementType::MigrationExportVersion + || mig_export_version.length != 4 + || vdm_request.element_count != 2 + { + error!("invalid migration info payload!\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let mig_session_key = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + if mig_session_key.element_type != VdmMessageElementType::ForwardMigrationSessionKey + || mig_session_key.length != 32 + { + error!("invalid forward migration session key!\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let mut mig_session_key_buf = [0u8; 32]; + for d in mig_session_key_buf.iter_mut() { + *d = u8::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + } + + min_export_version.encode(&mut writer).unwrap(); + max_export_version.encode(&mut writer).unwrap(); + writer.extend_from_slice(&mig_session_key_buf).unwrap(); + + let mut exchange_info_local = EXCHANGE_INFO_LOCAL.lock(); + let len = exchange_info_local.len(); + let reader = &mut Reader::init(&exchange_info_local[..len]); + let min_import_version = u16::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + let max_import_version = u16::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + let mut mig_session_key_buf = [0u8; 32]; + for d in mig_session_key_buf.iter_mut() { + *d = u8::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_FIELD)?; + } + exchange_info_local[..len].zeroize(); + + let mut payload = [0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + let vdm_exchange_mig_info = VdmMessage { + major_version: 0, + op_code: VdmMessageOpCode::ExchangeMigrationInfoRsp, + element_count: 2, + }; + cnt += vdm_exchange_mig_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + let mig_import_version_element = VdmMessageElement { + element_type: VdmMessageElementType::MigrationImportVersion, + length: 4, + }; + cnt += mig_import_version_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += min_import_version + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += max_import_version + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + let mig_session_key_element = VdmMessageElement { + element_type: VdmMessageElementType::BackwardMigrationSessionKey, + length: 32, + }; + cnt += mig_session_key_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer.extend_from_slice(&mig_session_key_buf).unwrap(); + + Ok(VendorDefinedRspPayloadStruct { + rsp_length: cnt as u32, + vendor_defined_rsp_payload: payload, + }) +} + +pub static SECRET_ASYM_IMPL_INSTANCE: SpdmSecretAsymSign = + SpdmSecretAsymSign { sign_cb: asym_sign }; + +fn asym_sign( + base_hash_algo: SpdmBaseHashAlgo, + base_asym_algo: SpdmBaseAsymAlgo, + data: &[u8], +) -> Option { + match (base_hash_algo, base_asym_algo) { + (SpdmBaseHashAlgo::TPM_ALG_SHA_384, SpdmBaseAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384) => { + sign_ecdsa_asym_algo(&ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, data) + } + _ => None, + } +} + +fn sign_ecdsa_asym_algo( + algorithm: &'static ring::signature::EcdsaSigningAlgorithm, + data: &[u8], +) -> Option { + let key_bytes = SIGNING_KEY.lock(); + let key_bytes: &[u8] = key_bytes.as_bytes(); + let rng = ring::rand::SystemRandom::new(); + let key_pair: ring::signature::EcdsaKeyPair = + ring::signature::EcdsaKeyPair::from_pkcs8(algorithm, key_bytes, &rng).ok()?; + let rng = ring::rand::SystemRandom::new(); + + let signature = key_pair.sign(&rng, data).ok()?; + let signature = signature.as_ref(); + + let mut full_signature: [u8; SPDM_MAX_ASYM_SIG_SIZE] = [0u8; SPDM_MAX_ASYM_SIG_SIZE]; + full_signature[..signature.len()].copy_from_slice(signature); + + Some(SpdmSignatureStruct { + data_size: signature.len() as u16, + data: full_signature, + }) +} diff --git a/src/migtd/src/spdm/spdm_vdm.rs b/src/migtd/src/spdm/spdm_vdm.rs new file mode 100644 index 00000000..b6f3ed00 --- /dev/null +++ b/src/migtd/src/spdm/spdm_vdm.rs @@ -0,0 +1,283 @@ +use codec::{enum_builder, Codec, Reader, Writer}; +use crypto::hash::digest_sha384; +use spdmlib::{ + common::{ManagedVdmBuffer, SpdmCodec}, + error::{SpdmResult, SPDM_STATUS_INVALID_MSG_FIELD, SPDM_STATUS_INVALID_STATE_LOCAL}, + message::*, + responder::ResponderContext, +}; + +use crate::spdm::*; + +// Intel(343) +pub const VDM_MESSAGE_VENDOR_ID: [u8; 4] = [0x57, 0x1, 0x0, 0x0]; +pub const VDM_MESSAGE_VENDOR_ID_LEN: usize = 4; + +enum_builder! { + @U8 + EnumName: VdmMessageOpCode; + EnumVal{ + OpCodeUnknown => 0x00, + ExchangePubKeyReq => 0x01, + ExchangePubKeyRsp => 0x02, + ExchangeMigrationAttestInfoReq => 0x03, + ExchangeMigrationAttestInfoRsp => 0x04, + ExchangeMigrationInfoReq => 0x05, + ExchangeMigrationInfoRsp => 0x06 +// ExchangeRebindAttestInfoReq => 0x07, +// ExchangeRebindAttestInfoRsp => 0x08, +// ExchangeRebindInfoReq => 0x09, +// ExchangeRebindInfoRsp => 0x0A + } +} +impl VdmMessageOpCode { + pub fn encode_minor_version(&self, bytes: &mut Writer) -> Result { + let mut cnt = 0usize; + cnt += 0u8.encode(bytes)?; // minor version is 0 + Ok(cnt) + } + + pub fn check_minor_version(&self, _minor_version: u8) -> bool { + true + } +} + +#[allow(clippy::derivable_impls)] +impl Default for VdmMessageOpCode { + fn default() -> VdmMessageOpCode { + VdmMessageOpCode::OpCodeUnknown + } +} + +#[derive(Debug)] +pub struct VdmMessage { + pub major_version: u8, + pub op_code: VdmMessageOpCode, + pub element_count: u8, +} + +impl Codec for VdmMessage { + fn encode(&self, bytes: &mut Writer) -> Result { + let mut cnt = 0usize; + cnt += self.major_version.encode(bytes)?; + cnt += self.op_code.encode_minor_version(bytes)?; + cnt += self.op_code.encode(bytes)?; + cnt += self.element_count.encode(bytes)?; + Ok(cnt) + } + + fn read(r: &mut Reader<'_>) -> Option { + let major_version = u8::read(r)?; + let minor_version = u8::read(r)?; + let op_code = VdmMessageOpCode::read(r)?; + if !op_code.check_minor_version(minor_version) { + return None; + } + let element_count = u8::read(r)?; + Some(VdmMessage { + major_version, + op_code, + element_count, + }) + } +} + +enum_builder! { + @U16 + EnumName: VdmMessageElementType; + EnumVal{ + ElementUnknown => 0x00, + PubKeyMy => 0x01, + TdReportMy => 0x02, + QuoteMy => 0x03, + EventLogMy => 0x04, + MigPolicyMy => 0x05, +// SerVtdExt => 0x10, +// TdReportInit => 0x12, +// EventLogInit => 0x14, +// MigPolicyInit => 0x15, + MigrationExportVersion => 0x81, + MigrationImportVersion => 0x82, + ForwardMigrationSessionKey => 0x83, + BackwardMigrationSessionKey => 0x84 +// RebindSessionToken => 0x85, + } +} + +#[allow(clippy::derivable_impls)] +impl Default for VdmMessageElementType { + fn default() -> VdmMessageElementType { + VdmMessageElementType::ElementUnknown + } +} + +#[derive(Debug)] +pub struct VdmMessageElement { + pub element_type: VdmMessageElementType, + pub length: u16, +} + +impl Codec for VdmMessageElement { + fn encode(&self, bytes: &mut Writer) -> Result { + let mut cnt = 0usize; + cnt += self.element_type.encode(bytes)?; + cnt += self.length.encode(bytes)?; + Ok(cnt) + } + + fn read(r: &mut Reader<'_>) -> Option { + let element_type = VdmMessageElementType::read(r)?; + let length = u16::read(r)?; + Some(VdmMessageElement { + element_type, + length, + }) + } +} + +pub fn migtd_vdm_msg_rsp_dispatcher_ex<'a>( + responder_context: &mut ResponderContext, + session_id: Option, + req_bytes: &[u8], + rsp_bytes: &'a mut [u8], +) -> (SpdmResult, Option<&'a [u8]>) { + let mut writer = Writer::init(rsp_bytes); + + let mut reader = Reader::init(req_bytes); + let message_header = SpdmMessageHeader::read(&mut reader); + if let Some(message_header) = message_header { + if message_header.version != responder_context.common.negotiate_info.spdm_version_sel { + responder_context.write_spdm_error( + SpdmErrorCode::SpdmErrorVersionMismatch, + 0, + &mut writer, + ); + let used = writer.used(); + return (Err(SPDM_STATUS_INVALID_MSG_FIELD), Some(&rsp_bytes[..used])); + } + } else { + responder_context.write_spdm_error(SpdmErrorCode::SpdmErrorInvalidRequest, 0, &mut writer); + let used = writer.used(); + return (Err(SPDM_STATUS_INVALID_MSG_FIELD), Some(&rsp_bytes[..used])); + } + + let vendor_defined_request_payload = + SpdmVendorDefinedRequestPayload::spdm_read(&mut responder_context.common, &mut reader); + if vendor_defined_request_payload.is_none() { + responder_context.write_spdm_error(SpdmErrorCode::SpdmErrorInvalidRequest, 0, &mut writer); + let used = writer.used(); + return (Err(SPDM_STATUS_INVALID_MSG_FIELD), Some(&rsp_bytes[..used])); + } + + let vendor_defined_request_payload = vendor_defined_request_payload.unwrap(); + let standard_id = vendor_defined_request_payload.standard_id; + let vendor_id = vendor_defined_request_payload.vendor_id; + let req_payload = vendor_defined_request_payload.req_payload; + + if vendor_id.len != VDM_MESSAGE_VENDOR_ID_LEN as u8 + || vendor_id.vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN] != VDM_MESSAGE_VENDOR_ID + { + responder_context.write_spdm_error(SpdmErrorCode::SpdmErrorInvalidRequest, 0, &mut writer); + let used = writer.used(); + return (Err(SPDM_STATUS_INVALID_MSG_FIELD), Some(&rsp_bytes[..used])); + } + + let mut reader = + Reader::init(&req_payload.vendor_defined_req_payload[0..req_payload.req_length as usize]); + let vdm_request = VdmMessage::read(&mut reader).unwrap(); + + let rsp_payload = match vdm_request.op_code { + VdmMessageOpCode::ExchangePubKeyReq => { + handle_exchange_pub_key_req(responder_context, &vdm_request, &mut reader) + } + VdmMessageOpCode::ExchangeMigrationAttestInfoReq => handle_exchange_mig_attest_info_req( + responder_context, + session_id, + &vdm_request, + &mut reader, + ), + VdmMessageOpCode::ExchangeMigrationInfoReq => { + handle_exchange_mig_info_req(responder_context, session_id, &vdm_request, &mut reader) + } + _ => Err(SPDM_STATUS_INVALID_MSG_FIELD), + }; + + let rsp_payload = if let Ok(payload) = rsp_payload { + payload + } else { + responder_context.write_spdm_error(SpdmErrorCode::SpdmErrorUnspecified, 0, &mut writer); + let used = writer.used(); + return ( + Err(SPDM_STATUS_INVALID_STATE_LOCAL), + Some(&rsp_bytes[..used]), + ); + }; + + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; + vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); + let vendor_id = VendorIDStruct { len: 4, vendor_id }; + + let response = SpdmMessage { + header: SpdmMessageHeader { + version: responder_context.common.negotiate_info.spdm_version_sel, + request_response_code: SpdmRequestResponseCode::SpdmResponseVendorDefinedResponse, + }, + payload: SpdmMessagePayload::SpdmVendorDefinedResponse(SpdmVendorDefinedResponsePayload { + standard_id, + vendor_id, + rsp_payload, + }), + }; + + let res = response.spdm_encode(&mut responder_context.common, &mut writer); + if res.is_err() { + responder_context.write_spdm_error(SpdmErrorCode::SpdmErrorUnspecified, 0, &mut writer); + let used = writer.used(); + return ( + Err(SPDM_STATUS_INVALID_STATE_LOCAL), + Some(&rsp_bytes[..used]), + ); + } + + let len = writer.used(); + + match vdm_request.op_code { + VdmMessageOpCode::ExchangePubKeyReq => { + let vdm_pub_key_src_hash = digest_sha384(req_bytes).unwrap(); + let vdm_pub_key_dst_hash = digest_sha384(writer.used_slice()).unwrap(); + let mut transcript_before_key_exchange = ManagedVdmBuffer::default(); + transcript_before_key_exchange + .append_message(vdm_pub_key_src_hash.as_slice()) + .unwrap(); + transcript_before_key_exchange + .append_message(vdm_pub_key_dst_hash.as_slice()) + .unwrap(); + responder_context + .common + .runtime_info + .vdm_message_transcript_before_key_exchange = Some(transcript_before_key_exchange); + } + VdmMessageOpCode::ExchangeMigrationAttestInfoReq => { + let vdm_attest_info_src_hash = digest_sha384(req_bytes).unwrap(); + let vdm_attest_info_dst_hash = digest_sha384(writer.used_slice()).unwrap(); + let mut transcript_before_finish = ManagedVdmBuffer::default(); + transcript_before_finish + .append_message(vdm_attest_info_src_hash.as_slice()) + .unwrap(); + transcript_before_finish + .append_message(vdm_attest_info_dst_hash.as_slice()) + .unwrap(); + let session_id = responder_context.common.runtime_info.get_last_session_id(); + if let Some(sid) = session_id { + if let Some(s) = responder_context.common.get_session_via_id(sid) { + s.runtime_info.vdm_message_transcript_before_finish = + Some(transcript_before_finish); + } + } + } + VdmMessageOpCode::ExchangeMigrationInfoReq => {} + _ => {} + }; + + (Ok(()), Some(&rsp_bytes[..len])) +} diff --git a/src/migtd/src/spdm/vmcall_msg.rs b/src/migtd/src/spdm/vmcall_msg.rs new file mode 100644 index 00000000..d0696106 --- /dev/null +++ b/src/migtd/src/spdm/vmcall_msg.rs @@ -0,0 +1,191 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use codec::{Codec, EncodeErr, Reader, Writer}; +use core::ops::DerefMut; +use spdmlib::error::SpdmResult; +use spdmlib::{ + common::SpdmTransportEncap, + error::{SPDM_STATUS_DECAP_FAIL, SPDM_STATUS_ENCAP_FAIL}, +}; +use spin::Mutex; + +pub const VMCALL_SPDM_SIGNATURE: u32 = 0x4D445053; // 'SPDM' +pub const VMCALL_SPDM_VERSION: u16 = 0x0100; // Version 1.0 + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum VmCallMessageType { + SpdmMessage, + SecuredSpdmMessage, +} + +impl Default for VmCallMessageType { + fn default() -> Self { + Self::SpdmMessage + } +} + +impl From<&VmCallMessageType> for u8 { + fn from(value: &VmCallMessageType) -> Self { + match value { + VmCallMessageType::SpdmMessage => 1, + VmCallMessageType::SecuredSpdmMessage => 2, + } + } +} + +impl TryFrom for VmCallMessageType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(VmCallMessageType::SpdmMessage), + 2 => Ok(VmCallMessageType::SecuredSpdmMessage), + _ => Err(()), + } + } +} + +impl Codec for VmCallMessageType { + fn encode(&self, bytes: &mut codec::Writer<'_>) -> Result { + u8::from(self).encode(bytes) + } + + fn read(r: &mut codec::Reader<'_>) -> Option { + let spdm_version = u8::read(r)?; + Self::try_from(spdm_version).ok() + } +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct VmCallMessageHeader { + pub version: u16, + pub msg_type: VmCallMessageType, + pub length: u32, +} + +impl Codec for VmCallMessageHeader { + fn encode(&self, bytes: &mut Writer) -> Result { + let mut cnt = 0usize; + let signature = VMCALL_SPDM_SIGNATURE; // 'SPDM' as uint32 (little-endian) + cnt += signature.encode(bytes)?; + cnt += self.version.encode(bytes)?; + cnt += self.msg_type.encode(bytes)?; + cnt += 0u8.encode(bytes)?; // reserved byte + cnt += self.length.encode(bytes)?; + Ok(cnt) + } + + fn read(r: &mut Reader) -> Option { + let signature = u32::read(r)?; + if signature != VMCALL_SPDM_SIGNATURE { + // Verify 'SPDM' signature + return None; + } + let version = u16::read(r)?; + if version != VMCALL_SPDM_VERSION { + return None; + } + let msg_type = VmCallMessageType::read(r)?; + let _ = u8::read(r)?; // reserved byte + let length = u32::read(r)?; + + Some(Self { + version, + msg_type, + length, + }) + } +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct VmCallTransportEncap {} + +#[maybe_async::maybe_async] +impl SpdmTransportEncap for VmCallTransportEncap { + async fn encap( + &mut self, + spdm_buffer: Arc<&[u8]>, + transport_buffer: Arc>, + secured_message: bool, + ) -> SpdmResult { + let mut transport_buffer = transport_buffer.lock(); + let transport_buffer: &mut &'life2 mut [u8] = transport_buffer.deref_mut(); + let mut writer = Writer::init(transport_buffer); + let msg_type = if secured_message { + VmCallMessageType::SecuredSpdmMessage + } else { + VmCallMessageType::SpdmMessage + }; + + let vmcall_msg_header = VmCallMessageHeader { + version: VMCALL_SPDM_VERSION, + msg_type, + length: spdm_buffer.len() as u32, + }; + vmcall_msg_header + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_ENCAP_FAIL)?; + let header_size = writer.used(); + + if transport_buffer.len() < header_size + spdm_buffer.len() { + return Err(SPDM_STATUS_ENCAP_FAIL); + } + transport_buffer[header_size..(header_size + spdm_buffer.len())] + .copy_from_slice(&spdm_buffer); + Ok(header_size + spdm_buffer.len()) + } + + async fn decap( + &mut self, + transport_buffer: Arc<&[u8]>, + spdm_buffer: Arc>, + ) -> SpdmResult<(usize, bool)> { + let mut reader = Reader::init(&transport_buffer); + let vmcall_msg_header = + VmCallMessageHeader::read(&mut reader).ok_or(SPDM_STATUS_DECAP_FAIL)?; + let header_size = reader.used(); + let payload_size = vmcall_msg_header.length as usize; + if transport_buffer.len() < header_size + payload_size { + return Err(SPDM_STATUS_DECAP_FAIL); + } + let mut spdm_buffer = spdm_buffer.lock(); + let spdm_buffer = spdm_buffer.deref_mut(); + if spdm_buffer.len() < payload_size { + return Err(SPDM_STATUS_DECAP_FAIL); + } + let payload = &transport_buffer[header_size..(header_size + payload_size)]; + spdm_buffer[..payload_size].copy_from_slice(payload); + let secured_message = vmcall_msg_header.msg_type == VmCallMessageType::SecuredSpdmMessage; + + Ok((payload_size, secured_message)) + } + + async fn encap_app( + &mut self, + spdm_buffer: Arc<&[u8]>, + app_buffer: Arc>, + _is_app_message: bool, + ) -> SpdmResult { + let mut app_buffer = app_buffer.lock(); + app_buffer[0..spdm_buffer.len()].copy_from_slice(&spdm_buffer); + Ok(spdm_buffer.len()) + } + + async fn decap_app( + &mut self, + app_buffer: Arc<&[u8]>, + spdm_buffer: Arc>, + ) -> SpdmResult<(usize, bool)> { + let mut spdm_buffer = spdm_buffer.lock(); + spdm_buffer[0..app_buffer.len()].copy_from_slice(&app_buffer); + Ok((app_buffer.len(), false)) + } + + // Sequence Number Length: 8 Bytes + fn get_sequence_number_count(&mut self) -> u8 { + 8 + } + fn get_max_random_count(&mut self) -> u16 { + 0 + } +} diff --git a/src/std-support/sys_time/Cargo.toml b/src/std-support/sys_time/Cargo.toml index 55b029d0..cb2f976f 100644 --- a/src/std-support/sys_time/Cargo.toml +++ b/src/std-support/sys_time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sys_time" -version = "0.1.0" +version = "0.1.1" authors = ["Xiaoyu Lu "] edition = "2018"