Skip to content
159 changes: 151 additions & 8 deletions examples/spdm_requester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//! SPDM Example Responder utilizing the requester library.

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

use clap::Parser;
Expand All @@ -26,6 +26,10 @@ use spdm_lib::commands::certificate::request::generate_get_certificate;
use spdm_lib::commands::challenge::{
request::generate_challenge_request, MeasurementSummaryHashType,
};
use spdm_lib::commands::measurements::request::{
generate_get_measurements, parse_measurements_response,
};
use spdm_lib::commands::measurements::MeasurementOperation;
use spdm_lib::context::SpdmContext;
use spdm_lib::error::SpdmError;
use spdm_lib::protocol::algorithms::{
Expand Down Expand Up @@ -200,7 +204,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
Ok(ctx) => ctx,
Err(e) => {
eprintln!("Failed to create SPDM context: {:?}", e);
return Err(Error::new(ErrorKind::Other, "SPDM context creation failed"));
return Err(Error::other("SPDM context creation failed"));
}
};

Expand All @@ -214,7 +218,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
if config.transport_type == platform::socket_transport::SocketTransportType::None {
spdm_context.transport_init_sequence().map_err(|e| {
eprintln!("Handshake failed: {:?}", e);
Error::new(ErrorKind::Other, "SPDM handshake failed")
Error::other("SPDM handshake failed")
})?;
}

Expand Down Expand Up @@ -469,7 +473,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data());
}

if let Some(cert) = peer_leaf_cert {
if let Some(cert) = &peer_leaf_cert {
let pub_key = VerifyingKey::from_sec1_bytes(
cert.tbs_certificate()
.subject_public_key_info()
Expand All @@ -488,14 +492,103 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {

if !verify_challenge_auth_signature(&mut spdm_context, pub_key, sig, config) {
eprintln!("CHALLENGE_AUTH signature verification failed");
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
return Err(std::io::Error::other(
"CHALLENGE_AUTH signature verification failed",
));
}
spdm_context.set_authenticated();
println!("CHALLENGE_AUTH signature verification successfull");
}

// GET_MEASUREMENTS
message_buffer.reset();
generate_get_measurements(
&mut spdm_context,
&mut message_buffer,
false,
false,
MeasurementOperation::RequestAllMeasBlocks,
Some(0),
None,
)
.unwrap();
spdm_context
.requester_send_request(&mut message_buffer, EID)
.unwrap();

if config.verbose {
println!("GET_MEASUREMENTS: {:x?}", &message_buffer.message_data());
}

spdm_context
.requester_process_message(&mut message_buffer)
.unwrap();

if config.verbose {
println!("MEASUREMENTS: {:x?}", &message_buffer.message_data());
}

let measurements = parse_measurements_response(message_buffer.message_data().unwrap())
.expect("Failed to parse measurement response");

if config.verbose {
println!(
"Measurements block count: {}",
measurements.total_measurement_blocks()
);
println!("Measurements Nonce: {}", HexString(measurements.nonce));
if let Some(sig) = measurements.signature {
println!(
"Measurements Signature ({} bytes): {}",
sig.len(),
HexString(sig)
);
}
println!(
"Measurement content change status: {:?}",
measurements.content_changed()
);
}

if let Some(sig_raw) = measurements.signature {
if let Some(cert) = &peer_leaf_cert {
let pub_key = VerifyingKey::from_sec1_bytes(
cert.tbs_certificate()
.subject_public_key_info()
.subject_public_key
.as_bytes()
.unwrap(),
)
.unwrap();

let sig = Signature::from_slice(sig_raw).unwrap();
if !verify_measurements_signature(&mut spdm_context, pub_key, sig, config) {
eprintln!("MEASUREMENTS signature verification failed");
return Err(std::io::Error::other(
"MEASUREMENTS signature verification failed",
));
}
println!("L1/L2 log verification successfull");
}
}

let mut meas_count = 0;
for measurement in measurements.iter() {
meas_count += 1;
if config.verbose {
println!(
"Parsed {:?} measurement with index {}",
measurement.measurement_spec, measurement.index
);
}
}

if meas_count != measurements.total_measurement_blocks() {
println!("[WARNING] measurement block count and parsed measurment count mismatch ({} expected, {} parsed)", measurements.total_measurement_blocks(), meas_count);
} else {
println!("Measurements retrieved successfully")
}

Ok(())
}

Expand Down Expand Up @@ -593,9 +686,9 @@ fn verify_cert_chain(chain: &[Certificate]) -> bool {
.unwrap();
for cert in chain.iter() {
let sig = Signature::from_der(cert.signature().as_bytes().unwrap()).unwrap();
if !pub_key
if pub_key
.verify(&cert.tbs_certificate().to_der().unwrap(), &sig)
.is_ok()
.is_err()
{
return false;
}
Expand Down Expand Up @@ -663,6 +756,56 @@ fn verify_challenge_auth_signature(
}
}

/// Currently only p384 support required
/// Here we verify that the responder and we created the same L1/L2 transcript and
/// that the signature is correct.
///
/// The transcript hash will be retrieved from the context.
/// The signature will be verified using the public key from the responder's certificate chain (which we already verified).
fn verify_measurements_signature(
ctx: &mut SpdmContext,
pubkey: VerifyingKey,
signature: Signature,
config: &RequesterConfig,
) -> bool {
use p384::ecdsa::signature::hazmat::PrehashVerifier;
use signature::Verifier;

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

// Get the L1 transcript hash and verify the signature over it.
let mut transcript_hash = [0u8; 48];
ctx.transcript_hash(TranscriptContext::L1, &mut transcript_hash)
.unwrap();
if config.verbose {
println!("L1/2 hash: {}", HexString(&transcript_hash));
}

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

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

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

Expand Down
2 changes: 1 addition & 1 deletion src/chunk_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::commands::measurements_rsp::MeasurementsResponse;
use crate::commands::measurements::response::MeasurementsResponse;

#[derive(Debug, PartialEq)]
pub enum ChunkError {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/challenge/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ pub(crate) fn handle_challenge_auth_response<'a>(
// Append the entire message (excluding the signature) to the transcript before signature verification, as required by SPDM 1.2 and later.
ctx.append_message_to_transcript(resp_payload, TranscriptContext::M1)?;
resp_payload
.trim(tail - resp_payload.data_len())
.put_data(tail)
.map_err(|e| (true, CommandError::Codec(e)))?;

Ok(())
Expand Down
Loading
Loading