Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 51 additions & 28 deletions examples/spdm_requester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

//! SPDM Example Responder utilizing the requester library.

use std::fmt::Display;
use std::io::{Error, ErrorKind, Result as IoResult};
use std::net::TcpStream;

Expand All @@ -21,7 +22,7 @@ use spdm_lib::protocol::algorithms::{
MeasurementSpecification, MelSpecification, OtherParamSupport, ReqBaseAsymAlg,
};
use spdm_lib::protocol::signature::NONCE_LEN;
use spdm_lib::protocol::{self, version, BaseHashAlgoType};
use spdm_lib::protocol::{self, version, BaseHashAlgoType, SpdmVersion};
use spdm_lib::protocol::{CapabilityFlags, DeviceCapabilities};

// Import platform implementations - no duplicates!
Expand Down Expand Up @@ -353,6 +354,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
if config.verbose {
println!("DIGESTS: {:x?}", &message_buffer.message_data());
}
println!("Successfully retrieved cert chain digests");

// Get peer certificate chain
loop {
Expand All @@ -362,8 +364,10 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
spdm_context
.requester_send_request(&mut message_buffer, EID)
.unwrap();
println!("requested GET_CERTIFICATE");
println!("state: {:?}", spdm_context.connection_info().state());
if config.verbose {
println!("requested GET_CERTIFICATE");
println!("state: {:?}", spdm_context.connection_info().state());
}

spdm_context
.requester_process_message(&mut message_buffer)
Expand All @@ -389,9 +393,9 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
.unwrap();
let root_hash = store.get_root_hash(0, hash_algo).unwrap();
println!(
"slot 0: Root hash ({hash_algo:?}, {} bytes): {:02x?}",
"slot 0: Root hash ({hash_algo:?}, {} bytes): {}",
root_hash.len(),
root_hash
HexString(root_hash)
);
let cert_chain = store.get_cert_chain(0, hash_algo).unwrap();

Expand Down Expand Up @@ -488,8 +492,8 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {

/// Display configuration information
fn display_info(config: &RequesterConfig) {
println!("Real SPDM Library Integrated DMTF Compatible Responder");
println!("=====================================================");
println!("SPDM Library DMTF Compatible Requester Example Flow");
println!("===================================================");
println!("Configuration:");
println!(" Port: {}", config.port);
println!(" Certificate: {}", config.cert_path);
Expand Down Expand Up @@ -526,15 +530,11 @@ fn display_info(config: &RequesterConfig) {
);
println!();

println!("Clean Platform Implementation Features:");
println!(" SPDM Versions: 1.2, 1.1");
println!(" Protocol Processing: Real SPDM library integration");
println!("Requester Features:");
println!(" SPDM Versions: 1.0, 1.1, 1.2, 1.3");
println!(" Hash Algorithm: SHA-384 (platform module)");
println!(" Signature Algorithm: ECDSA P-384 (platform module)");
println!(" Measurements: Demo device measurements (platform module)");
println!(" Certificates: Static OpenSSL-generated certificate chain (platform module)");
println!(" Transport: TCP socket with DMTF protocol (platform module)");
println!(" ✅ NO CODE DUPLICATION - All implementations from unified platform module");
println!(" Transport: TCP socket with DMTF NONE or MCTP protocol (platform module)");
println!();
}

Expand All @@ -555,9 +555,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Connection from: {}", peer_addr);
}

// Handle client with real SPDM processing using platform implementations
println!("Starting requester command flow...");
full_flow(stream, &config)?;

println!("Request flow finished successfully.");

Ok(())
}

Expand Down Expand Up @@ -614,31 +616,52 @@ fn verify_challenge_auth_signature(
signature: Signature,
config: &RequesterConfig,
) -> bool {
use p384::ecdsa::signature::hazmat::PrehashVerifier;
use signature::Verifier;

// since we verify the responder-generated signature, we have to use the same "responder-" context constant.
let sig_combined_context = protocol::signature::create_responder_signing_context(
ctx.connection_info().version_number(),
protocol::ReqRespCode::ChallengeAuth,
)
.unwrap();
if config.verbose {
println!(
"comb_ctx string: '{}'",
String::from_utf8_lossy(&sig_combined_context)
);
let mut sig_combined_context = Vec::new();
if ctx.connection_info().version_number() >= SpdmVersion::V12 {
// since we verify the responder-generated signature, we have to use the same "responder-" context constant.
let sig_ctx = protocol::signature::create_responder_signing_context(
ctx.connection_info().version_number(),
protocol::ReqRespCode::ChallengeAuth,
)
.unwrap();
sig_combined_context.extend_from_slice(&sig_ctx);
if config.verbose {
println!(
"comb_ctx string: '{}'",
String::from_utf8_lossy(&sig_combined_context)
);
}
}

// Get the M1 transcript hash (which is the hash of messages A, B, C) and verify the signature over it.
let mut transcript_hash = [0u8; 48];
ctx.transcript_hash(TranscriptContext::M1, &mut transcript_hash)
.unwrap();
if config.verbose {
println!("M1/2 hash: {transcript_hash:02x?}");
println!("M1/2 hash: {}", HexString(&transcript_hash));
}

// M denotes the message that is signed. M shall be the concatenation of the combined_spdm_prefix and unverified_message_hash.
let m = [sig_combined_context.as_slice(), &transcript_hash].concat();

pubkey.verify(&m, &signature).is_ok()
if ctx.connection_info().version_number() >= SpdmVersion::V12 {
pubkey.verify(&m, &signature).is_ok()
} else {
pubkey.verify_prehash(&m, &signature).is_ok()
}
}

#[derive(Debug)]
struct HexString<'a>(&'a [u8]);

impl Display for HexString<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for x in self.0 {
write!(f, "{:02X}", x)?;
}
Ok(())
}
}
2 changes: 0 additions & 2 deletions src/commands/algorithms/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ pub(crate) fn handle_algorithms_response<'a>(
.set_state(crate::state::ConnectionState::AlgorithmsNegotiated);

ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::Vca)
// ctx.append_message_to_transcript(resp, crate::transcript::TranscriptContext::M1)
}

/// Generate the NEGOTIATE_ALGORITHMS request with all the contexts local information.
Expand Down Expand Up @@ -267,7 +266,6 @@ pub fn generate_negotiate_algorithms_request<'a>(
}

ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::Vca)
// ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::M1)
}

#[cfg(test)]
Expand Down
3 changes: 1 addition & 2 deletions src/commands/capabilities/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,8 @@ fn generate_capabilities_request<'a>(
.push_data(payload_len)
.map_err(|_| (false, CommandError::BufferTooSmall))?;

// Append response to VCA transcript
// Append request to VCA transcript
ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca)
// ctx.append_message_to_transcript(req_buf, TranscriptContext::M1)
}

/// Generate the GET_CAPABILITIES command using the local capabilities from the context.
Expand Down
20 changes: 11 additions & 9 deletions src/commands/challenge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,29 @@ impl TryFrom<u8> for MeasurementSummaryHashType {

#[derive(FromBytes, IntoBytes, Immutable)]
#[repr(C)]
// TODO: check backwards compatibility of this struct with the original ChallengeReq struct
/// CHALLENGE request message base
///
/// # Version specific fields for CHALLENGE:
/// Following fields have to be appended, depending on the SPDM version.
/// ## >= v1.3
/// - `Context`: 8-byte application specific context.
/// Should be all zeros if no context is provided.
struct ChallengeReq {
/// `Param1`: `SlotID`
///
/// Slot number of the Responder certificate chain that shall be used for authentication.
/// If the public key of the Responder was provisioned to the Requester in a
/// trusted environment, the value in this field shall be 0xFF ; otherwise it
/// shall be between 0 and 7 inclusive.
slot_id: u8,

/// `Param2`: Requested measurement summary hash
///
/// Shall be the type of measurement summary hash requested.
measurement_hash_type: u8,

/// The Requester should choose a random value.
nonce: [u8; NONCE_LEN],

/// The Requester can include application-specific information in Context.
/// The Requester should fill this field with zeros if it has no context to provide.
context: [u8; CONTEXT_LEN],
}
impl CommonCodec for ChallengeReq {}

Expand All @@ -74,19 +80,15 @@ impl ChallengeReq {
/// * `measurement_hash_type` - The type of measurement summary hash requested from the
/// Responder.
/// * `nonce` - A random 32-byte value chosen by the Requester for freshness.
/// * `context` - Optional 8-byte application-specific context. Defaults to all zeros when
/// `None`.
pub fn new(
slot_id: u8,
measurement_hash_type: MeasurementSummaryHashType,
nonce: [u8; NONCE_LEN],
context: Option<[u8; CONTEXT_LEN]>,
) -> Self {
Self {
slot_id,
measurement_hash_type: measurement_hash_type as u8,
nonce,
context: context.unwrap_or([0; CONTEXT_LEN]),
}
}
}
Expand Down
37 changes: 25 additions & 12 deletions src/commands/challenge/request.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Licensed under the Apache-2.0 license
use crate::codec::{Codec, MessageBuf};
use crate::codec::{encode_u8_slice, Codec, MessageBuf};
use crate::commands::challenge::{
ChallengeAuthRspBase, ChallengeReq, MeasurementSummaryHashType, CONTEXT_LEN, NONCE_LEN,
OPAQUE_DATA_MAX,
Expand All @@ -25,7 +25,7 @@ use crate::transcript::TranscriptContext;
/// Responder (`None`, `Tcb`, or `All`).
/// * `nonce` - A 32-byte random value chosen by the Requester for freshness.
/// * `context` - Optional 8-byte application-specific context. Defaults to all zeros when
/// `None`.
/// `None`, ignored for spdm versions < v1.3.
///
/// # Errors
///
Expand All @@ -45,10 +45,20 @@ pub fn generate_challenge_request<'a>(
.encode(message_buffer)
.map_err(|e| (false, CommandError::Codec(e)))?;

ChallengeReq::new(slot_id, measurement_hash_type.clone(), nonce, context)
ChallengeReq::new(slot_id, measurement_hash_type.clone(), nonce)
.encode(message_buffer)
.map_err(|e| (false, CommandError::Codec(e)))?;

// Encode 8-byte context string if version >= v1.3
if ctx.connection_info().version_number() >= SpdmVersion::V13 {
if let Some(ctx_str) = context {
encode_u8_slice(&ctx_str, message_buffer)
.map_err(|e| (true, CommandError::Codec(e)))?;
} else {
encode_u8_slice(&[0; 8], message_buffer).map_err(|e| (true, CommandError::Codec(e)))?;
}
}

ctx.state
.peer_cert_store
.as_mut()
Expand Down Expand Up @@ -161,16 +171,19 @@ pub(crate) fn handle_challenge_auth_response<'a>(
.map_err(|e| (true, CommandError::Codec(e)))?;
}

// This field shall be identical to the Context field of the corresponding request message.
// TODO: compare it to the context we sent.
// See: src/protocol/common.rs [RequesterContext]
let _requester_context = resp_payload
.data(8)
.map_err(|e| (true, CommandError::Codec(e)))?;
// In v1.3 a 8-byte request context was added before the signature field
if ctx.connection_info().version_number() >= SpdmVersion::V13 {
// This field shall be identical to the Context field of the corresponding request message.
// TODO: compare it to the context we sent.
// See: src/protocol/common.rs [RequesterContext]
let _requester_context = resp_payload
.data(8)
.map_err(|e| (true, CommandError::Codec(e)))?;

resp_payload
.pull_data(8)
.map_err(|e| (true, CommandError::Codec(e)))?;
resp_payload
.pull_data(8)
.map_err(|e| (true, CommandError::Codec(e)))?;
}

// We have to use this ugly hack to bring the message buffer into the right form to exclude the signature.
// This message buffer thing is totally fucked up...
Expand Down
14 changes: 11 additions & 3 deletions src/commands/digests/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,18 @@ pub(crate) fn handle_digests_response<'a>(
.as_mut()
.ok_or((true, CommandError::InvalidResponse))?;

peer_cert_store
.set_supported_slots(digests_resp_common.supported_slot_mask)
.map_err(|e| (true, CommandError::CertStore(e)))?;
if version >= SpdmVersion::V13 {
peer_cert_store
.set_supported_slots(digests_resp_common.supported_slot_mask)
.map_err(|e| (true, CommandError::CertStore(e)))?;
} else {
// Set all slots as supported, if supported_slot_mask isn't supported (v1.2 and prior)
peer_cert_store
.set_supported_slots(0xFF)
.map_err(|e| (true, CommandError::CertStore(e)))?;
}

// TODO: Was this intended to do something?
for b in 0..digests_resp_common.supported_slot_mask.count_ones() {
if (digests_resp_common.supported_slot_mask & (1 << b)) == 1 {}
}
Expand Down
2 changes: 0 additions & 2 deletions src/commands/version/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub fn generate_get_version<'a>(
.map_err(|_| (false, CommandError::BufferTooSmall))?;

ctx.append_message_to_transcript(req_buf, TranscriptContext::Vca)
// ctx.append_message_to_transcript(req_buf, TranscriptContext::M1)
}

/// Requester function for processing a VERSION response
Expand Down Expand Up @@ -118,7 +117,6 @@ pub(crate) fn handle_version_response<'a>(
.set_state(ConnectionState::AfterVersion);

ctx.append_message_to_transcript(resp, TranscriptContext::Vca)
// ctx.append_message_to_transcript(resp, TranscriptContext::M1)
}

// tests
Expand Down
Loading