From 84a13d16ac8fe2459accd414ccb5fe10fd2c3510 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Fri, 19 Nov 2021 17:13:16 -0500 Subject: [PATCH 1/5] Start integrating SPDM into sled-agent This code only represents the negotiation phase of the protocol. A follow up commit will contain the responder identification (digests + cert retrieval) code, as well as challenge response, which is the first part of the protocol used to begin securing a SPDM channel. These are the parts of the SPDM implementation that exist so far. More will be added over time. It's expected that `run` for each of a requester and responder will be used by a caller establishing a secure channel. That usage will come in a follow up commit. It's likely that when the SPDM protocol is complete a secure channel will be returned for writing to. Until then, callers will pretend that run sets up a fully secure channel, and then use the existing TCP stream when run returns. --- Cargo.lock | 33 +++- sled-agent/Cargo.toml | 5 + sled-agent/src/bootstrap/agent.rs | 4 + sled-agent/src/bootstrap/mod.rs | 1 + sled-agent/src/bootstrap/spdm/error.rs | 30 ++++ sled-agent/src/bootstrap/spdm/mod.rs | 36 +++++ sled-agent/src/bootstrap/spdm/requester.rs | 174 +++++++++++++++++++++ sled-agent/src/bootstrap/spdm/responder.rs | 129 +++++++++++++++ 8 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 sled-agent/src/bootstrap/spdm/error.rs create mode 100644 sled-agent/src/bootstrap/spdm/mod.rs create mode 100644 sled-agent/src/bootstrap/spdm/requester.rs create mode 100644 sled-agent/src/bootstrap/spdm/responder.rs diff --git a/Cargo.lock b/Cargo.lock index 68b89bcb2cb..be9bb2db9e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,7 +996,7 @@ dependencies = [ "rustls", "tokio", "tokio-rustls", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -1512,6 +1512,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "bytes", "cfg-if", "chrono", "dropshot", @@ -1534,13 +1535,17 @@ dependencies = [ "serde_json", "serial_test", "slog", + "slog-async", + "slog-term", "smf", + "spdm", "structopt", "subprocess", "tar", "tempfile", "thiserror", "tokio", + "tokio-util", "toml", "uuid", "zone", @@ -2471,7 +2476,7 @@ dependencies = [ "log", "ring", "sct", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -2917,6 +2922,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "spdm" +version = "0.1.0" +dependencies = [ + "bitflags", + "rand", + "ring", + "webpki 0.22.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -3362,7 +3377,7 @@ checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", "tokio", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -3819,13 +3834,23 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", ] [[package]] diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index 756e04a3246..ecf146a3390 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] anyhow = "1.0.44" async-trait = "0.1.51" +bytes = "1.1" cfg-if = "1.0" chrono = { version = "0.4", features = [ "serde" ] } dropshot = { git = "https://github.com/oxidecomputer/dropshot", branch = "main" } @@ -23,11 +24,13 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" slog = { version = "2.5", features = [ "max_level_trace", "release_max_level_debug" ] } smf = "0.2" +spdm = { path = "../../spdm" } structopt = "0.3" tar = "0.4" tempfile = "3.2" thiserror = "1.0" tokio = { version = "1.13", features = [ "full" ] } +tokio-util = { version = "0.6", features = ["codec"] } toml = "0.5.6" uuid = { version = "0.8", features = [ "serde", "v4" ] } zone = "0.1" @@ -40,6 +43,8 @@ openapi-lint = { git = "https://github.com/oxidecomputer/openapi-lint", branch = openapiv3 = "0.5.0" serial_test = "0.5" subprocess = "0.2.8" +slog-async = "2.6" +slog-term = "2.8" # # Disable doc builds by default for our binaries to work around issue diff --git a/sled-agent/src/bootstrap/agent.rs b/sled-agent/src/bootstrap/agent.rs index 01483bdfcfb..54ce52657d2 100644 --- a/sled-agent/src/bootstrap/agent.rs +++ b/sled-agent/src/bootstrap/agent.rs @@ -2,6 +2,7 @@ use super::client::types as bootstrap_types; use super::client::Client as BootstrapClient; +use super::spdm::SpdmError; use super::views::ShareResponse; use omicron_common::api::external::Error; use omicron_common::packaging::sha256_digest; @@ -35,6 +36,9 @@ pub enum BootstrapError { #[error("Error making HTTP request")] Api(#[from] anyhow::Error), + + #[error("Error running SPDM protocol")] + Spdm(#[from] SpdmError) } /// The entity responsible for bootstrapping an Oxide rack. diff --git a/sled-agent/src/bootstrap/mod.rs b/sled-agent/src/bootstrap/mod.rs index 7b0be883aa9..81a2b9c7caf 100644 --- a/sled-agent/src/bootstrap/mod.rs +++ b/sled-agent/src/bootstrap/mod.rs @@ -7,3 +7,4 @@ mod http_entrypoints; mod params; pub mod server; mod views; +mod spdm; diff --git a/sled-agent/src/bootstrap/spdm/error.rs b/sled-agent/src/bootstrap/spdm/error.rs new file mode 100644 index 00000000000..534a295c821 --- /dev/null +++ b/sled-agent/src/bootstrap/spdm/error.rs @@ -0,0 +1,30 @@ +use spdm::{requester::RequesterError, responder::ResponderError}; +use thiserror::Error; + +/// Describes errors that arise from use of the SPDM protocol library +#[derive(Error, Debug)] +pub enum SpdmError { + #[error("requester error: {0}")] + Requester(RequesterError), + + #[error("responder error: {0}")] + Responder(ResponderError), + + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error("connection closed")] + ConnectionClosed +} + +impl From for SpdmError { + fn from(e: RequesterError) -> Self { + SpdmError::Requester(e) + } +} + +impl From for SpdmError { + fn from(e: ResponderError) -> Self { + SpdmError::Responder(e) + } +} diff --git a/sled-agent/src/bootstrap/spdm/mod.rs b/sled-agent/src/bootstrap/spdm/mod.rs new file mode 100644 index 00000000000..8e4263caa32 --- /dev/null +++ b/sled-agent/src/bootstrap/spdm/mod.rs @@ -0,0 +1,36 @@ +mod error; +mod requester; +mod responder; + +use bytes::BytesMut; +use futures::StreamExt; +use slog::Logger; +use tokio::net::TcpStream; +use tokio_util::codec::{Framed, LengthDelimitedCodec}; + +// We use 2-byte size framed headers. +pub const HEADER_LEN: usize = 2; +pub const MAX_BUF_LEN: usize = 65536; + +pub use error::SpdmError; + +type Transport = Framed; + +pub fn framed_transport(sock: TcpStream) -> Transport { + LengthDelimitedCodec::builder() + .length_field_length(HEADER_LEN) + .new_framed(sock) +} + +pub async fn recv( + log: &Logger, + transport: &mut Transport, +) -> Result { + if let Some(rsp) = transport.next().await { + let rsp = rsp?; + debug!(log, "Received {:x?}", &rsp[..]); + Ok(rsp) + } else { + Err(SpdmError::ConnectionClosed) + } +} diff --git a/sled-agent/src/bootstrap/spdm/requester.rs b/sled-agent/src/bootstrap/spdm/requester.rs new file mode 100644 index 00000000000..3b21806f0a5 --- /dev/null +++ b/sled-agent/src/bootstrap/spdm/requester.rs @@ -0,0 +1,174 @@ +use bytes::BytesMut; + +use futures::SinkExt; +use slog::Logger; + +use spdm::msgs::algorithms::*; +use spdm::msgs::capabilities::{GetCapabilities, ReqFlags}; +use spdm::requester::{self, algorithms, capabilities, id_auth}; +use spdm::{ + config::{MAX_CERT_CHAIN_SIZE, NUM_SLOTS}, + Transcript, +}; + +use super::{recv, SpdmError, Transport}; + +/// Run the requester side of the SPDM protocol. +/// +/// The protocol operates over a TCP stream framed with a 2 byte size +/// header. Requesters and Responders are decoupled from whether the endpoint of +/// a socket is a TCP client or server. +pub async fn run( + log: Logger, + mut transport: Transport, +) -> Result<(), SpdmError> { + let mut transcript = Transcript::new(); + + info!(log, "Requester starting version negotiation"); + let state = + negotiate_version(&log, &mut transport, &mut transcript).await?; + + info!(log, "Requester starting capabilities negotiation"); + let state = + negotiate_capabilities(&log, state, &mut transport, &mut transcript) + .await?; + + info!(log, "Requester starting algorithms negotiation"); + let _state = + negotiate_algorithms(&log, state, &mut transport, &mut transcript) + .await?; + + info!(log, "Requester completed negotiation phase"); + debug!(log, "Requester transcript: {:x?}", transcript.get()); + + Ok(()) +} + +async fn negotiate_version( + log: &Logger, + transport: &mut Transport, + transcript: &mut Transcript, +) -> Result { + let mut buf = BytesMut::with_capacity(10); + buf.resize(10, 0); + let state = requester::start(); + let size = state.write_get_version(&mut buf, transcript)?; + + debug!(log, "Requester sending GET_VERSION"); + let data = buf.split_to(size).freeze(); + transport.send(data).await?; + let rsp = recv(log, transport).await?; + debug!(log, "Requester received VERSION"); + + state.handle_msg(&rsp[..], transcript).map_err(|e| e.into()) +} + +async fn negotiate_capabilities( + log: &Logger, + mut state: capabilities::State, + transport: &mut Transport, + transcript: &mut Transcript, +) -> Result { + let req = GetCapabilities { + ct_exponent: 12, + flags: ReqFlags::CERT_CAP + | ReqFlags::CHAL_CAP + | ReqFlags::ENCRYPT_CAP + | ReqFlags::MAC_CAP + | ReqFlags::MUT_AUTH_CAP + | ReqFlags::KEY_EX_CAP + | ReqFlags::ENCAP_CAP + | ReqFlags::HBEAT_CAP + | ReqFlags::KEY_UPD_CAP, + }; + + let mut buf = BytesMut::with_capacity(64); + buf.resize(64, 0); + + debug!(log, "Requester sending GET_CAPABILITIES"); + let size = state.write_msg(&req, &mut buf, transcript)?; + let data = buf.split_to(size).freeze(); + transport.send(data).await?; + debug!(log, "Requester received CAPABILITIES"); + let rsp = recv(log, transport).await?; + state.handle_msg(&rsp, transcript).map_err(|e| e.into()) +} + +async fn negotiate_algorithms( + log: &Logger, + mut state: algorithms::State, + transport: &mut Transport, + transcript: &mut Transcript, +) -> Result { + let req = NegotiateAlgorithms { + measurement_spec: MeasurementSpec::DMTF, + base_asym_algo: BaseAsymAlgo::ECDSA_ECC_NIST_P384, + base_hash_algo: BaseHashAlgo::SHA_256 | BaseHashAlgo::SHA3_256, + num_algorithm_requests: 4, + algorithm_requests: [ + AlgorithmRequest::Dhe(DheAlgorithm { + supported: DheFixedAlgorithms::FFDHE_3072 + | DheFixedAlgorithms::SECP_384_R1, + }), + AlgorithmRequest::Aead(AeadAlgorithm { + supported: AeadFixedAlgorithms::AES_256_GCM + | AeadFixedAlgorithms::CHACHA20_POLY1305, + }), + AlgorithmRequest::ReqBaseAsym(ReqBaseAsymAlgorithm { + supported: ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P384 + | ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P256, + }), + AlgorithmRequest::KeySchedule(KeyScheduleAlgorithm { + supported: KeyScheduleFixedAlgorithms::SPDM, + }), + ], + }; + + let mut buf = BytesMut::with_capacity(64); + buf.resize(64, 0); + debug!(log, "Requester sending NEGOTIATE_ALGORITHMS"); + let size = state.write_msg(req, &mut buf, transcript)?; + let data = buf.split_to(size).freeze(); + transport.send(data).await?; + debug!(log, "Requester received ALGORITHMS"); + + let rsp = recv(log, transport).await?; + + state + .handle_msg::(&rsp, transcript) + .map_err(|e| e.into()) +} + +#[cfg(test)] +mod tests { + use std::net::SocketAddr; + + use slog::Drain; + use tokio::net::{TcpListener, TcpStream}; + + use super::super::framed_transport; + use super::super::responder; + use super::*; + + #[tokio::test] + async fn negotiation() { + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::FullFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + let log = slog::Logger::root(drain, o!("component" => "spdm")); + let log2 = log.clone(); + + let addr: SocketAddr = "127.0.0.1:9999".parse().unwrap(); + let listener = TcpListener::bind(addr.clone()).await.unwrap(); + + tokio::spawn(async move { + let (sock, _) = listener.accept().await.unwrap(); + let transport = framed_transport(sock); + responder::run(&log, transport).await.unwrap(); + }); + + let sock = TcpStream::connect(addr).await.unwrap(); + let transport = framed_transport(sock); + run(log2, transport).await.unwrap(); + } +} diff --git a/sled-agent/src/bootstrap/spdm/responder.rs b/sled-agent/src/bootstrap/spdm/responder.rs new file mode 100644 index 00000000000..52cbc847196 --- /dev/null +++ b/sled-agent/src/bootstrap/spdm/responder.rs @@ -0,0 +1,129 @@ +use bytes::BytesMut; + +use futures::SinkExt; +use slog::Logger; + +use spdm::msgs::algorithms::*; +use spdm::msgs::capabilities::{Capabilities, RspFlags}; +use spdm::responder::{self, algorithms, capabilities, id_auth}; +use spdm::Transcript; + +use super::{recv, SpdmError, Transport}; + +/// Run the responder side of the SPDM protocol. +/// +/// The protocol operates over a TCP stream framed with a 2 byte size +/// header. Requesters and Responders are decoupled from whether the endpoint of +/// a socket is a TCP client or server. +pub async fn run( + log: &Logger, + mut transport: Transport, +) -> Result<(), SpdmError> { + let mut transcript = Transcript::new(); + + info!(log, "Responder starting version negotiation"); + let state = negotiate_version(log, &mut transport, &mut transcript).await?; + + info!(log, "Responder starting capabilities negotiation"); + let state = + negotiate_capabilities(log, state, &mut transport, &mut transcript) + .await?; + + println!("Responder starting algorithms selection"); + let _state = + select_algorithms(log, state, &mut transport, &mut transcript).await?; + + info!(log, "Responder completed negotiation phase"); + debug!(log, "Responder transcript: {:x?}\n", transcript.get()); + Ok(()) +} + +async fn negotiate_version( + log: &Logger, + transport: &mut Transport, + transcript: &mut Transcript, +) -> Result { + let mut buf = BytesMut::with_capacity(10); + buf.resize(10, 0); + + let state = responder::start(); + let req = recv(log, transport).await?; + + let (size, state) = state.handle_msg(&req[..], &mut buf, transcript)?; + debug!(log, "Responder received GET_VERSION"); + + let data = buf.split_to(size).freeze(); + transport.send(data).await?; + debug!(log, "Responder sent VERSION"); + Ok(state) +} + +async fn negotiate_capabilities( + log: &Logger, + state: capabilities::State, + transport: &mut Transport, + transcript: &mut Transcript, +) -> Result { + let supported = Capabilities { + ct_exponent: 14, + flags: RspFlags::CERT_CAP + | RspFlags::CHAL_CAP + | RspFlags::ENCRYPT_CAP + | RspFlags::MAC_CAP + | RspFlags::MUT_AUTH_CAP + | RspFlags::KEY_EX_CAP + | RspFlags::ENCAP_CAP + | RspFlags::HBEAT_CAP + | RspFlags::KEY_UPD_CAP, + }; + + let req = recv(log, transport).await?; + + let mut rsp = BytesMut::with_capacity(64); + rsp.resize(64, 0); + let (size, transition) = + state.handle_msg(supported, &req[..], &mut rsp, transcript)?; + debug!(log, "Responder received GET_CAPABILITIES"); + + // We expect to transition to the Algorithms state + // TODO: Handle both states + use responder::capabilities::Transition; + let state = match transition { + Transition::Algorithms(state) => state, + _ => panic!("Expected transition to Algorithms state"), + }; + + let data = rsp.split_to(size).freeze(); + transport.send(data).await?; + debug!(log, "Responder sent CAPABILITIES"); + Ok(state) +} + +async fn select_algorithms( + log: &Logger, + state: algorithms::State, + transport: &mut Transport, + transcript: &mut Transcript, +) -> Result { + let req = recv(log, transport).await?; + + let mut buf = BytesMut::with_capacity(64); + buf.resize(64, 0); + + let (size, transition) = + state.handle_msg(&req[..], &mut buf, transcript)?; + debug!(log, "Responder received NEGOTIATE_ALGORITHMS"); + + // We expect to transition to the Algorithms state + // TODO: Handle both states + use responder::algorithms::Transition; + let state = match transition { + Transition::IdAuth(state) => state, + _ => panic!("Expected transition to Algorithms state"), + }; + + let data = buf.split_to(size).freeze(); + transport.send(data).await?; + debug!(log, "Responder sent ALGORITHMS"); + Ok(state) +} From bf211eab4fd821647844d6d683a80ac19bfc1a99 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Wed, 24 Nov 2021 10:15:54 -0500 Subject: [PATCH 2/5] Print SPDM error - thanks Sean Co-authored-by: Sean Klein --- sled-agent/src/bootstrap/agent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sled-agent/src/bootstrap/agent.rs b/sled-agent/src/bootstrap/agent.rs index 54ce52657d2..68dc1f99273 100644 --- a/sled-agent/src/bootstrap/agent.rs +++ b/sled-agent/src/bootstrap/agent.rs @@ -37,7 +37,7 @@ pub enum BootstrapError { #[error("Error making HTTP request")] Api(#[from] anyhow::Error), - #[error("Error running SPDM protocol")] + #[error("Error running SPDM protocol: {0}")] Spdm(#[from] SpdmError) } From 69eb92dd6e263b7d898bdb51df8678bb5d57982c Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Wed, 24 Nov 2021 19:42:00 -0500 Subject: [PATCH 3/5] Use public spdm crate and fix dead code warnings --- Cargo.lock | 1 + sled-agent/Cargo.toml | 2 +- sled-agent/src/bootstrap/agent.rs | 2 +- sled-agent/src/bootstrap/mod.rs | 2 +- sled-agent/src/bootstrap/spdm/error.rs | 3 --- sled-agent/src/bootstrap/spdm/mod.rs | 8 +++++++- sled-agent/src/bootstrap/spdm/requester.rs | 1 + sled-agent/src/bootstrap/spdm/responder.rs | 2 +- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be9bb2db9e8..aae6515cf80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2925,6 +2925,7 @@ dependencies = [ [[package]] name = "spdm" version = "0.1.0" +source = "git+https://github.com/oxidecomputer/spdm?rev=94899d6#94899d6403d8ec4b444e81b8ff48818facadf20f" dependencies = [ "bitflags", "rand", diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index ecf146a3390..6905b9da08f 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" slog = { version = "2.5", features = [ "max_level_trace", "release_max_level_debug" ] } smf = "0.2" -spdm = { path = "../../spdm" } +spdm = { git = "https://github.com/oxidecomputer/spdm", rev = "94899d6" } structopt = "0.3" tar = "0.4" tempfile = "3.2" diff --git a/sled-agent/src/bootstrap/agent.rs b/sled-agent/src/bootstrap/agent.rs index 68dc1f99273..ae56e9d294f 100644 --- a/sled-agent/src/bootstrap/agent.rs +++ b/sled-agent/src/bootstrap/agent.rs @@ -38,7 +38,7 @@ pub enum BootstrapError { Api(#[from] anyhow::Error), #[error("Error running SPDM protocol: {0}")] - Spdm(#[from] SpdmError) + Spdm(#[from] SpdmError), } /// The entity responsible for bootstrapping an Oxide rack. diff --git a/sled-agent/src/bootstrap/mod.rs b/sled-agent/src/bootstrap/mod.rs index 81a2b9c7caf..841fa5a7bb7 100644 --- a/sled-agent/src/bootstrap/mod.rs +++ b/sled-agent/src/bootstrap/mod.rs @@ -6,5 +6,5 @@ pub mod config; mod http_entrypoints; mod params; pub mod server; -mod views; mod spdm; +mod views; diff --git a/sled-agent/src/bootstrap/spdm/error.rs b/sled-agent/src/bootstrap/spdm/error.rs index 534a295c821..c9995a6c10e 100644 --- a/sled-agent/src/bootstrap/spdm/error.rs +++ b/sled-agent/src/bootstrap/spdm/error.rs @@ -12,9 +12,6 @@ pub enum SpdmError { #[error(transparent)] Io(#[from] std::io::Error), - - #[error("connection closed")] - ConnectionClosed } impl From for SpdmError { diff --git a/sled-agent/src/bootstrap/spdm/mod.rs b/sled-agent/src/bootstrap/spdm/mod.rs index 8e4263caa32..38cdd18b783 100644 --- a/sled-agent/src/bootstrap/spdm/mod.rs +++ b/sled-agent/src/bootstrap/spdm/mod.rs @@ -2,6 +2,8 @@ mod error; mod requester; mod responder; +use std::io::{Error, ErrorKind}; + use bytes::BytesMut; use futures::StreamExt; use slog::Logger; @@ -9,13 +11,16 @@ use tokio::net::TcpStream; use tokio_util::codec::{Framed, LengthDelimitedCodec}; // We use 2-byte size framed headers. +#[allow(dead_code)] pub const HEADER_LEN: usize = 2; +#[allow(dead_code)] pub const MAX_BUF_LEN: usize = 65536; pub use error::SpdmError; type Transport = Framed; +#[allow(dead_code)] pub fn framed_transport(sock: TcpStream) -> Transport { LengthDelimitedCodec::builder() .length_field_length(HEADER_LEN) @@ -31,6 +36,7 @@ pub async fn recv( debug!(log, "Received {:x?}", &rsp[..]); Ok(rsp) } else { - Err(SpdmError::ConnectionClosed) + Err(Error::new(ErrorKind::ConnectionAborted, "SPDM channel closed") + .into()) } } diff --git a/sled-agent/src/bootstrap/spdm/requester.rs b/sled-agent/src/bootstrap/spdm/requester.rs index 3b21806f0a5..2843a73a06c 100644 --- a/sled-agent/src/bootstrap/spdm/requester.rs +++ b/sled-agent/src/bootstrap/spdm/requester.rs @@ -18,6 +18,7 @@ use super::{recv, SpdmError, Transport}; /// The protocol operates over a TCP stream framed with a 2 byte size /// header. Requesters and Responders are decoupled from whether the endpoint of /// a socket is a TCP client or server. +#[allow(dead_code)] pub async fn run( log: Logger, mut transport: Transport, diff --git a/sled-agent/src/bootstrap/spdm/responder.rs b/sled-agent/src/bootstrap/spdm/responder.rs index 52cbc847196..e89990f000a 100644 --- a/sled-agent/src/bootstrap/spdm/responder.rs +++ b/sled-agent/src/bootstrap/spdm/responder.rs @@ -3,7 +3,6 @@ use bytes::BytesMut; use futures::SinkExt; use slog::Logger; -use spdm::msgs::algorithms::*; use spdm::msgs::capabilities::{Capabilities, RspFlags}; use spdm::responder::{self, algorithms, capabilities, id_auth}; use spdm::Transcript; @@ -15,6 +14,7 @@ use super::{recv, SpdmError, Transport}; /// The protocol operates over a TCP stream framed with a 2 byte size /// header. Requesters and Responders are decoupled from whether the endpoint of /// a socket is a TCP client or server. +#[allow(dead_code)] pub async fn run( log: &Logger, mut transport: Transport, From ef73dbea2eacc561e058ffae1edf1514d04bf47a Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Mon, 29 Nov 2021 15:08:49 -0500 Subject: [PATCH 4/5] Simplify SPDM sending and receiving of messages * Wrap requester and responder data in a `Ctx` * Bump `spdm` crate so that slices instead of sizes are returned * Wrap `Framed` to allow sending slices, and converting internally to `Bytes` so the callers. don't have to do this. --- Cargo.lock | 2 +- sled-agent/Cargo.toml | 2 +- sled-agent/src/bootstrap/spdm/mod.rs | 59 ++--- sled-agent/src/bootstrap/spdm/requester.rs | 240 ++++++++++----------- sled-agent/src/bootstrap/spdm/responder.rs | 218 +++++++++---------- 5 files changed, 262 insertions(+), 259 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aae6515cf80..9a1a4f4aa91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2925,7 +2925,7 @@ dependencies = [ [[package]] name = "spdm" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/spdm?rev=94899d6#94899d6403d8ec4b444e81b8ff48818facadf20f" +source = "git+https://github.com/oxidecomputer/spdm?rev=9742f6e#9742f6eae7b86cc8bc8bc2fb0feeb44f770a1fb6" dependencies = [ "bitflags", "rand", diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index 6905b9da08f..7c1e9a87f6a 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" slog = { version = "2.5", features = [ "max_level_trace", "release_max_level_debug" ] } smf = "0.2" -spdm = { git = "https://github.com/oxidecomputer/spdm", rev = "94899d6" } +spdm = { git = "https://github.com/oxidecomputer/spdm", rev = "9742f6e" } structopt = "0.3" tar = "0.4" tempfile = "3.2" diff --git a/sled-agent/src/bootstrap/spdm/mod.rs b/sled-agent/src/bootstrap/spdm/mod.rs index 38cdd18b783..500a1f56b08 100644 --- a/sled-agent/src/bootstrap/spdm/mod.rs +++ b/sled-agent/src/bootstrap/spdm/mod.rs @@ -4,39 +4,48 @@ mod responder; use std::io::{Error, ErrorKind}; -use bytes::BytesMut; -use futures::StreamExt; +use bytes::{Bytes, BytesMut}; +use futures::{SinkExt, StreamExt}; use slog::Logger; use tokio::net::TcpStream; use tokio_util::codec::{Framed, LengthDelimitedCodec}; -// We use 2-byte size framed headers. -#[allow(dead_code)] -pub const HEADER_LEN: usize = 2; -#[allow(dead_code)] -pub const MAX_BUF_LEN: usize = 65536; +// 2^16 - 2 bytes for a header +const MAX_BUF_SIZE: usize = 65534; pub use error::SpdmError; -type Transport = Framed; - -#[allow(dead_code)] -pub fn framed_transport(sock: TcpStream) -> Transport { - LengthDelimitedCodec::builder() - .length_field_length(HEADER_LEN) - .new_framed(sock) +pub struct Transport { + framed: Framed, } -pub async fn recv( - log: &Logger, - transport: &mut Transport, -) -> Result { - if let Some(rsp) = transport.next().await { - let rsp = rsp?; - debug!(log, "Received {:x?}", &rsp[..]); - Ok(rsp) - } else { - Err(Error::new(ErrorKind::ConnectionAborted, "SPDM channel closed") - .into()) +impl Transport { + // We use 2-byte size framed headers. + #[allow(dead_code)] + pub const HEADER_LEN: usize = 2; + + #[allow(dead_code)] + pub fn new(sock: TcpStream) -> Transport { + Transport { + framed: LengthDelimitedCodec::builder() + .length_field_length(Self::HEADER_LEN) + .new_framed(sock), + } + } + + pub async fn send(&mut self, data: &[u8]) -> Result<(), SpdmError> { + let data = Bytes::copy_from_slice(data); + self.framed.send(data).await.map_err(|e| e.into()) + } + + pub async fn recv(&mut self, log: &Logger) -> Result { + if let Some(rsp) = self.framed.next().await { + let rsp = rsp?; + debug!(log, "Received {:x?}", &rsp[..]); + Ok(rsp) + } else { + Err(Error::new(ErrorKind::ConnectionAborted, "SPDM channel closed") + .into()) + } } } diff --git a/sled-agent/src/bootstrap/spdm/requester.rs b/sled-agent/src/bootstrap/spdm/requester.rs index 2843a73a06c..85cf1c16ac7 100644 --- a/sled-agent/src/bootstrap/spdm/requester.rs +++ b/sled-agent/src/bootstrap/spdm/requester.rs @@ -1,6 +1,3 @@ -use bytes::BytesMut; - -use futures::SinkExt; use slog::Logger; use spdm::msgs::algorithms::*; @@ -11,7 +8,112 @@ use spdm::{ Transcript, }; -use super::{recv, SpdmError, Transport}; +use super::{SpdmError, Transport, MAX_BUF_SIZE}; + +// A `Ctx` contains shared types for use by a requester task +struct Ctx { + buf: [u8; MAX_BUF_SIZE], + log: Logger, + transport: Transport, + transcript: Transcript, +} + +impl Ctx { + fn new(log: Logger, transport: Transport) -> Ctx { + Ctx { + buf: [0u8; MAX_BUF_SIZE], + log, + transport, + transcript: Transcript::new(), + } + } + + async fn negotiate_version( + &mut self, + ) -> Result { + let state = requester::start(); + let data = + state.write_get_version(&mut self.buf, &mut self.transcript)?; + + debug!(self.log, "Requester sending GET_VERSION"); + self.transport.send(data).await?; + + let rsp = self.transport.recv(&self.log).await?; + debug!(self.log, "Requester received VERSION"); + + state.handle_msg(&rsp[..], &mut self.transcript).map_err(|e| e.into()) + } + + async fn negotiate_capabilities( + &mut self, + mut state: capabilities::State, + ) -> Result { + let req = GetCapabilities { + ct_exponent: 12, + flags: ReqFlags::CERT_CAP + | ReqFlags::CHAL_CAP + | ReqFlags::ENCRYPT_CAP + | ReqFlags::MAC_CAP + | ReqFlags::MUT_AUTH_CAP + | ReqFlags::KEY_EX_CAP + | ReqFlags::ENCAP_CAP + | ReqFlags::HBEAT_CAP + | ReqFlags::KEY_UPD_CAP, + }; + + debug!(self.log, "Requester sending GET_CAPABILITIES"); + let data = + state.write_msg(&req, &mut self.buf, &mut self.transcript)?; + self.transport.send(data).await?; + + let rsp = self.transport.recv(&self.log).await?; + debug!(self.log, "Requester received CAPABILITIES"); + state.handle_msg(&rsp, &mut self.transcript).map_err(|e| e.into()) + } + + async fn negotiate_algorithms( + &mut self, + mut state: algorithms::State, + ) -> Result { + let req = NegotiateAlgorithms { + measurement_spec: MeasurementSpec::DMTF, + base_asym_algo: BaseAsymAlgo::ECDSA_ECC_NIST_P384, + base_hash_algo: BaseHashAlgo::SHA_256 | BaseHashAlgo::SHA3_256, + num_algorithm_requests: 4, + algorithm_requests: [ + AlgorithmRequest::Dhe(DheAlgorithm { + supported: DheFixedAlgorithms::FFDHE_3072 + | DheFixedAlgorithms::SECP_384_R1, + }), + AlgorithmRequest::Aead(AeadAlgorithm { + supported: AeadFixedAlgorithms::AES_256_GCM + | AeadFixedAlgorithms::CHACHA20_POLY1305, + }), + AlgorithmRequest::ReqBaseAsym(ReqBaseAsymAlgorithm { + supported: ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P384 + | ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P256, + }), + AlgorithmRequest::KeySchedule(KeyScheduleAlgorithm { + supported: KeyScheduleFixedAlgorithms::SPDM, + }), + ], + }; + + debug!(self.log, "Requester sending NEGOTIATE_ALGORITHMS"); + let data = state.write_msg(req, &mut self.buf, &mut self.transcript)?; + self.transport.send(data).await?; + + let rsp = self.transport.recv(&self.log).await?; + debug!(self.log, "Requester received ALGORITHMS"); + + state + .handle_msg::( + &rsp, + &mut self.transcript, + ) + .map_err(|e| e.into()) + } +} /// Run the requester side of the SPDM protocol. /// @@ -19,127 +121,24 @@ use super::{recv, SpdmError, Transport}; /// header. Requesters and Responders are decoupled from whether the endpoint of /// a socket is a TCP client or server. #[allow(dead_code)] -pub async fn run( - log: Logger, - mut transport: Transport, -) -> Result<(), SpdmError> { - let mut transcript = Transcript::new(); +pub async fn run(log: Logger, transport: Transport) -> Result<(), SpdmError> { + let mut ctx = Ctx::new(log, transport); - info!(log, "Requester starting version negotiation"); - let state = - negotiate_version(&log, &mut transport, &mut transcript).await?; + info!(ctx.log, "Requester starting version negotiation"); + let state = ctx.negotiate_version().await?; - info!(log, "Requester starting capabilities negotiation"); - let state = - negotiate_capabilities(&log, state, &mut transport, &mut transcript) - .await?; + info!(ctx.log, "Requester starting capabilities negotiation"); + let state = ctx.negotiate_capabilities(state).await?; - info!(log, "Requester starting algorithms negotiation"); - let _state = - negotiate_algorithms(&log, state, &mut transport, &mut transcript) - .await?; + info!(ctx.log, "Requester starting algorithms negotiation"); + let _state = ctx.negotiate_algorithms(state).await?; - info!(log, "Requester completed negotiation phase"); - debug!(log, "Requester transcript: {:x?}", transcript.get()); + info!(ctx.log, "Requester completed negotiation phase"); + debug!(ctx.log, "Requester transcript: {:x?}", ctx.transcript.get()); Ok(()) } -async fn negotiate_version( - log: &Logger, - transport: &mut Transport, - transcript: &mut Transcript, -) -> Result { - let mut buf = BytesMut::with_capacity(10); - buf.resize(10, 0); - let state = requester::start(); - let size = state.write_get_version(&mut buf, transcript)?; - - debug!(log, "Requester sending GET_VERSION"); - let data = buf.split_to(size).freeze(); - transport.send(data).await?; - let rsp = recv(log, transport).await?; - debug!(log, "Requester received VERSION"); - - state.handle_msg(&rsp[..], transcript).map_err(|e| e.into()) -} - -async fn negotiate_capabilities( - log: &Logger, - mut state: capabilities::State, - transport: &mut Transport, - transcript: &mut Transcript, -) -> Result { - let req = GetCapabilities { - ct_exponent: 12, - flags: ReqFlags::CERT_CAP - | ReqFlags::CHAL_CAP - | ReqFlags::ENCRYPT_CAP - | ReqFlags::MAC_CAP - | ReqFlags::MUT_AUTH_CAP - | ReqFlags::KEY_EX_CAP - | ReqFlags::ENCAP_CAP - | ReqFlags::HBEAT_CAP - | ReqFlags::KEY_UPD_CAP, - }; - - let mut buf = BytesMut::with_capacity(64); - buf.resize(64, 0); - - debug!(log, "Requester sending GET_CAPABILITIES"); - let size = state.write_msg(&req, &mut buf, transcript)?; - let data = buf.split_to(size).freeze(); - transport.send(data).await?; - debug!(log, "Requester received CAPABILITIES"); - let rsp = recv(log, transport).await?; - state.handle_msg(&rsp, transcript).map_err(|e| e.into()) -} - -async fn negotiate_algorithms( - log: &Logger, - mut state: algorithms::State, - transport: &mut Transport, - transcript: &mut Transcript, -) -> Result { - let req = NegotiateAlgorithms { - measurement_spec: MeasurementSpec::DMTF, - base_asym_algo: BaseAsymAlgo::ECDSA_ECC_NIST_P384, - base_hash_algo: BaseHashAlgo::SHA_256 | BaseHashAlgo::SHA3_256, - num_algorithm_requests: 4, - algorithm_requests: [ - AlgorithmRequest::Dhe(DheAlgorithm { - supported: DheFixedAlgorithms::FFDHE_3072 - | DheFixedAlgorithms::SECP_384_R1, - }), - AlgorithmRequest::Aead(AeadAlgorithm { - supported: AeadFixedAlgorithms::AES_256_GCM - | AeadFixedAlgorithms::CHACHA20_POLY1305, - }), - AlgorithmRequest::ReqBaseAsym(ReqBaseAsymAlgorithm { - supported: ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P384 - | ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P256, - }), - AlgorithmRequest::KeySchedule(KeyScheduleAlgorithm { - supported: KeyScheduleFixedAlgorithms::SPDM, - }), - ], - }; - - let mut buf = BytesMut::with_capacity(64); - buf.resize(64, 0); - debug!(log, "Requester sending NEGOTIATE_ALGORITHMS"); - let size = state.write_msg(req, &mut buf, transcript)?; - let data = buf.split_to(size).freeze(); - transport.send(data).await?; - debug!(log, "Requester received ALGORITHMS"); - - let rsp = recv(log, transport).await?; - - state - .handle_msg::(&rsp, transcript) - .map_err(|e| e.into()) -} - #[cfg(test)] mod tests { use std::net::SocketAddr; @@ -147,7 +146,6 @@ mod tests { use slog::Drain; use tokio::net::{TcpListener, TcpStream}; - use super::super::framed_transport; use super::super::responder; use super::*; @@ -164,12 +162,12 @@ mod tests { tokio::spawn(async move { let (sock, _) = listener.accept().await.unwrap(); - let transport = framed_transport(sock); - responder::run(&log, transport).await.unwrap(); + let transport = Transport::new(sock); + responder::run(log, transport).await.unwrap(); }); let sock = TcpStream::connect(addr).await.unwrap(); - let transport = framed_transport(sock); + let transport = Transport::new(sock); run(log2, transport).await.unwrap(); } } diff --git a/sled-agent/src/bootstrap/spdm/responder.rs b/sled-agent/src/bootstrap/spdm/responder.rs index e89990f000a..3815c77bbb3 100644 --- a/sled-agent/src/bootstrap/spdm/responder.rs +++ b/sled-agent/src/bootstrap/spdm/responder.rs @@ -1,13 +1,105 @@ -use bytes::BytesMut; - -use futures::SinkExt; use slog::Logger; use spdm::msgs::capabilities::{Capabilities, RspFlags}; use spdm::responder::{self, algorithms, capabilities, id_auth}; use spdm::Transcript; -use super::{recv, SpdmError, Transport}; +use super::{SpdmError, Transport, MAX_BUF_SIZE}; + +// A `Ctx` contains shared types for use by a responder task +struct Ctx { + buf: [u8; MAX_BUF_SIZE], + log: Logger, + transport: Transport, + transcript: Transcript, +} + +impl Ctx { + fn new(log: Logger, transport: Transport) -> Ctx { + Ctx { + buf: [0u8; MAX_BUF_SIZE], + log, + transport, + transcript: Transcript::new(), + } + } + + async fn negotiate_version( + &mut self, + ) -> Result { + let state = responder::start(); + let req = self.transport.recv(&self.log).await?; + + let (data, state) = + state.handle_msg(&req[..], &mut self.buf, &mut self.transcript)?; + debug!(self.log, "Responder received GET_VERSION"); + + self.transport.send(data).await?; + debug!(self.log, "Responder sent VERSION"); + Ok(state) + } + + async fn negotiate_capabilities( + &mut self, + state: capabilities::State, + ) -> Result { + let supported = Capabilities { + ct_exponent: 14, + flags: RspFlags::CERT_CAP + | RspFlags::CHAL_CAP + | RspFlags::ENCRYPT_CAP + | RspFlags::MAC_CAP + | RspFlags::MUT_AUTH_CAP + | RspFlags::KEY_EX_CAP + | RspFlags::ENCAP_CAP + | RspFlags::HBEAT_CAP + | RspFlags::KEY_UPD_CAP, + }; + + let req = self.transport.recv(&self.log).await?; + let (data, transition) = state.handle_msg( + supported, + &req[..], + &mut self.buf, + &mut self.transcript, + )?; + debug!(self.log, "Responder received GET_CAPABILITIES"); + + // We expect to transition to the Algorithms state + // TODO: Handle both states + use responder::capabilities::Transition; + let state = match transition { + Transition::Algorithms(state) => state, + _ => panic!("Expected transition to Algorithms state"), + }; + + self.transport.send(data).await?; + debug!(self.log, "Responder sent CAPABILITIES"); + Ok(state) + } + + async fn select_algorithms( + &mut self, + state: algorithms::State, + ) -> Result { + let req = self.transport.recv(&self.log).await?; + let (data, transition) = + state.handle_msg(&req[..], &mut self.buf, &mut self.transcript)?; + debug!(self.log, "Responder received NEGOTIATE_ALGORITHMS"); + + // We expect to transition to the Algorithms state + // TODO: Handle both states + use responder::algorithms::Transition; + let state = match transition { + Transition::IdAuth(state) => state, + _ => panic!("Expected transition to Algorithms state"), + }; + + self.transport.send(data).await?; + debug!(self.log, "Responder sent ALGORITHMS"); + Ok(state) + } +} /// Run the responder side of the SPDM protocol. /// @@ -15,115 +107,19 @@ use super::{recv, SpdmError, Transport}; /// header. Requesters and Responders are decoupled from whether the endpoint of /// a socket is a TCP client or server. #[allow(dead_code)] -pub async fn run( - log: &Logger, - mut transport: Transport, -) -> Result<(), SpdmError> { - let mut transcript = Transcript::new(); - - info!(log, "Responder starting version negotiation"); - let state = negotiate_version(log, &mut transport, &mut transcript).await?; - - info!(log, "Responder starting capabilities negotiation"); - let state = - negotiate_capabilities(log, state, &mut transport, &mut transcript) - .await?; - - println!("Responder starting algorithms selection"); - let _state = - select_algorithms(log, state, &mut transport, &mut transcript).await?; - - info!(log, "Responder completed negotiation phase"); - debug!(log, "Responder transcript: {:x?}\n", transcript.get()); - Ok(()) -} - -async fn negotiate_version( - log: &Logger, - transport: &mut Transport, - transcript: &mut Transcript, -) -> Result { - let mut buf = BytesMut::with_capacity(10); - buf.resize(10, 0); - - let state = responder::start(); - let req = recv(log, transport).await?; +pub async fn run(log: Logger, transport: Transport) -> Result<(), SpdmError> { + let mut ctx = Ctx::new(log, transport); - let (size, state) = state.handle_msg(&req[..], &mut buf, transcript)?; - debug!(log, "Responder received GET_VERSION"); + info!(ctx.log, "Responder starting version negotiation"); + let state = ctx.negotiate_version().await?; - let data = buf.split_to(size).freeze(); - transport.send(data).await?; - debug!(log, "Responder sent VERSION"); - Ok(state) -} + info!(ctx.log, "Responder starting capabilities negotiation"); + let state = ctx.negotiate_capabilities(state).await?; -async fn negotiate_capabilities( - log: &Logger, - state: capabilities::State, - transport: &mut Transport, - transcript: &mut Transcript, -) -> Result { - let supported = Capabilities { - ct_exponent: 14, - flags: RspFlags::CERT_CAP - | RspFlags::CHAL_CAP - | RspFlags::ENCRYPT_CAP - | RspFlags::MAC_CAP - | RspFlags::MUT_AUTH_CAP - | RspFlags::KEY_EX_CAP - | RspFlags::ENCAP_CAP - | RspFlags::HBEAT_CAP - | RspFlags::KEY_UPD_CAP, - }; - - let req = recv(log, transport).await?; - - let mut rsp = BytesMut::with_capacity(64); - rsp.resize(64, 0); - let (size, transition) = - state.handle_msg(supported, &req[..], &mut rsp, transcript)?; - debug!(log, "Responder received GET_CAPABILITIES"); - - // We expect to transition to the Algorithms state - // TODO: Handle both states - use responder::capabilities::Transition; - let state = match transition { - Transition::Algorithms(state) => state, - _ => panic!("Expected transition to Algorithms state"), - }; - - let data = rsp.split_to(size).freeze(); - transport.send(data).await?; - debug!(log, "Responder sent CAPABILITIES"); - Ok(state) -} + info!(ctx.log, "Responder starting algorithms selection"); + let _state = ctx.select_algorithms(state).await?; -async fn select_algorithms( - log: &Logger, - state: algorithms::State, - transport: &mut Transport, - transcript: &mut Transcript, -) -> Result { - let req = recv(log, transport).await?; - - let mut buf = BytesMut::with_capacity(64); - buf.resize(64, 0); - - let (size, transition) = - state.handle_msg(&req[..], &mut buf, transcript)?; - debug!(log, "Responder received NEGOTIATE_ALGORITHMS"); - - // We expect to transition to the Algorithms state - // TODO: Handle both states - use responder::algorithms::Transition; - let state = match transition { - Transition::IdAuth(state) => state, - _ => panic!("Expected transition to Algorithms state"), - }; - - let data = buf.split_to(size).freeze(); - transport.send(data).await?; - debug!(log, "Responder sent ALGORITHMS"); - Ok(state) + info!(ctx.log, "Responder completed negotiation phase"); + debug!(ctx.log, "Responder transcript: {:x?}\n", ctx.transcript.get()); + Ok(()) } From 968a28ba6c89c540d6c42108386b74cff94d6543 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Mon, 29 Nov 2021 16:32:11 -0500 Subject: [PATCH 5/5] Fixes for rest of review comments --- sled-agent/src/bootstrap/spdm/error.rs | 5 +++++ sled-agent/src/bootstrap/spdm/mod.rs | 6 ++++++ sled-agent/src/bootstrap/spdm/requester.rs | 17 ++++++++--------- sled-agent/src/bootstrap/spdm/responder.rs | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/sled-agent/src/bootstrap/spdm/error.rs b/sled-agent/src/bootstrap/spdm/error.rs index c9995a6c10e..9e2477ced19 100644 --- a/sled-agent/src/bootstrap/spdm/error.rs +++ b/sled-agent/src/bootstrap/spdm/error.rs @@ -1,3 +1,5 @@ +//! Wrap errors returned from the `spdm` crate and std::io::Error. + use spdm::{requester::RequesterError, responder::ResponderError}; use thiserror::Error; @@ -12,6 +14,9 @@ pub enum SpdmError { #[error(transparent)] Io(#[from] std::io::Error), + + #[error("invalid state transition: expected {expected}, got {got}")] + InvalidState { expected: &'static str, got: &'static str }, } impl From for SpdmError { diff --git a/sled-agent/src/bootstrap/spdm/mod.rs b/sled-agent/src/bootstrap/spdm/mod.rs index 500a1f56b08..3303249eeed 100644 --- a/sled-agent/src/bootstrap/spdm/mod.rs +++ b/sled-agent/src/bootstrap/spdm/mod.rs @@ -1,3 +1,9 @@ +//! Instantiate a SPDM requester and responder with particular capabilities, +//! algorithms, and credentials. +//! +//! Sled agents run the SPDM protocol over a tokio TCP stream with a 2 byte size +//! header for framing. + mod error; mod requester; mod responder; diff --git a/sled-agent/src/bootstrap/spdm/requester.rs b/sled-agent/src/bootstrap/spdm/requester.rs index 85cf1c16ac7..32974754f88 100644 --- a/sled-agent/src/bootstrap/spdm/requester.rs +++ b/sled-agent/src/bootstrap/spdm/requester.rs @@ -77,21 +77,18 @@ impl Ctx { ) -> Result { let req = NegotiateAlgorithms { measurement_spec: MeasurementSpec::DMTF, - base_asym_algo: BaseAsymAlgo::ECDSA_ECC_NIST_P384, - base_hash_algo: BaseHashAlgo::SHA_256 | BaseHashAlgo::SHA3_256, + base_asym_algo: BaseAsymAlgo::ECDSA_ECC_NIST_P256, + base_hash_algo: BaseHashAlgo::SHA_256, num_algorithm_requests: 4, algorithm_requests: [ AlgorithmRequest::Dhe(DheAlgorithm { - supported: DheFixedAlgorithms::FFDHE_3072 - | DheFixedAlgorithms::SECP_384_R1, + supported: DheFixedAlgorithms::SECP_256_R1, }), AlgorithmRequest::Aead(AeadAlgorithm { - supported: AeadFixedAlgorithms::AES_256_GCM - | AeadFixedAlgorithms::CHACHA20_POLY1305, + supported: AeadFixedAlgorithms::AES_256_GCM, }), AlgorithmRequest::ReqBaseAsym(ReqBaseAsymAlgorithm { - supported: ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P384 - | ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P256, + supported: ReqBaseAsymFixedAlgorithms::ECDSA_ECC_NIST_P256, }), AlgorithmRequest::KeySchedule(KeyScheduleAlgorithm { supported: KeyScheduleFixedAlgorithms::SPDM, @@ -160,7 +157,7 @@ mod tests { let addr: SocketAddr = "127.0.0.1:9999".parse().unwrap(); let listener = TcpListener::bind(addr.clone()).await.unwrap(); - tokio::spawn(async move { + let handle = tokio::spawn(async move { let (sock, _) = listener.accept().await.unwrap(); let transport = Transport::new(sock); responder::run(log, transport).await.unwrap(); @@ -169,5 +166,7 @@ mod tests { let sock = TcpStream::connect(addr).await.unwrap(); let transport = Transport::new(sock); run(log2, transport).await.unwrap(); + + handle.await.unwrap(); } } diff --git a/sled-agent/src/bootstrap/spdm/responder.rs b/sled-agent/src/bootstrap/spdm/responder.rs index 3815c77bbb3..780fb4dd1cd 100644 --- a/sled-agent/src/bootstrap/spdm/responder.rs +++ b/sled-agent/src/bootstrap/spdm/responder.rs @@ -70,7 +70,12 @@ impl Ctx { use responder::capabilities::Transition; let state = match transition { Transition::Algorithms(state) => state, - _ => panic!("Expected transition to Algorithms state"), + _ => { + return Err(SpdmError::InvalidState { + expected: "Algorithms", + got: "Capabilities", + }) + } }; self.transport.send(data).await?; @@ -92,7 +97,12 @@ impl Ctx { use responder::algorithms::Transition; let state = match transition { Transition::IdAuth(state) => state, - _ => panic!("Expected transition to Algorithms state"), + _ => { + return Err(SpdmError::InvalidState { + expected: "IdAuth", + got: "Capabilities", + }) + } }; self.transport.send(data).await?;