Skip to content

Commit 0e10400

Browse files
committed
Implement MEASUREMENTS response parsing
- Measurement data is currently only parsed and discarded
1 parent 8dfb988 commit 0e10400

File tree

4 files changed

+115
-12
lines changed

4 files changed

+115
-12
lines changed

examples/spdm_requester.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use spdm_lib::commands::challenge::{
2727
request::generate_challenge_request, MeasurementSummaryHashType,
2828
};
2929
use spdm_lib::commands::measurements::request::generate_get_measurements;
30+
use spdm_lib::commands::measurements::MeasurementOperation;
3031
use spdm_lib::context::SpdmContext;
3132
use spdm_lib::error::SpdmError;
3233
use spdm_lib::protocol::algorithms::{
@@ -504,7 +505,7 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
504505
&mut message_buffer,
505506
false,
506507
false,
507-
0x01,
508+
MeasurementOperation::RequestAllMeasBlocks,
508509
Some(0),
509510
None,
510511
)
@@ -514,15 +515,15 @@ fn full_flow(stream: TcpStream, config: &RequesterConfig) -> IoResult<()> {
514515
.unwrap();
515516

516517
if config.verbose {
517-
println!("GET_MEASUREMENTS: {:?}", &message_buffer.message_data());
518+
println!("GET_MEASUREMENTS: {:x?}", &message_buffer.message_data());
518519
}
519520

520521
spdm_context
521522
.requester_process_message(&mut message_buffer)
522523
.unwrap();
523524

524525
if config.verbose {
525-
println!("CHALLENGE_AUTH: {:x?}", &message_buffer.message_data());
526+
println!("MEASUREMENTS: {:x?}", &message_buffer.message_data());
526527
}
527528

528529
Ok(())

src/commands/measurements/mod.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ pub mod request;
1919
/// Responder logic for GET_MEASUREMENTS and MEASURMENTS
2020
pub mod response;
2121

22-
use crate::codec::CommonCodec;
2322
use crate::protocol::*;
23+
use crate::{codec::CommonCodec, error::CommandError};
2424
use bitfield::bitfield;
25-
use zerocopy::{FromBytes, Immutable, IntoBytes};
25+
use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned};
2626

2727
const RESPONSE_FIXED_FIELDS_SIZE: usize = 8;
2828
const MAX_RESPONSE_VARIABLE_FIELDS_SIZE: usize =
@@ -89,3 +89,43 @@ impl Default for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> {
8989
}
9090

9191
impl CommonCodec for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> {}
92+
93+
/// Measurement Operation request field
94+
pub enum MeasurementOperation {
95+
/// Query the Responder for the total number of measurement blocks available
96+
ReportMeasBlockCount,
97+
/// Request the measurement block at a specific index
98+
///
99+
/// Index has to be between `0x01` and `0xFE`, inclusively.
100+
RequestSingleMeasBlock(u8),
101+
/// Request all measurement blocks
102+
RequestAllMeasBlocks,
103+
}
104+
105+
impl TryInto<u8> for MeasurementOperation {
106+
type Error = CommandError;
107+
108+
fn try_into(self) -> Result<u8, Self::Error> {
109+
match self {
110+
MeasurementOperation::ReportMeasBlockCount => Ok(0x01),
111+
MeasurementOperation::RequestSingleMeasBlock(x) => {
112+
if matches!(x, 0x00 | 0xFF) {
113+
Err(CommandError::UnsupportedRequest)
114+
} else {
115+
Ok(x)
116+
}
117+
}
118+
MeasurementOperation::RequestAllMeasBlocks => Ok(0xFF),
119+
}
120+
}
121+
}
122+
123+
#[derive(Debug, FromBytes, IntoBytes, Immutable, Unaligned)]
124+
#[repr(C)]
125+
struct MeasurementBlockHeader {
126+
index: u8,
127+
measurement_spec: MeasurementSpecification,
128+
measurement_size: zerocopy::little_endian::U16,
129+
}
130+
131+
impl CommonCodec for MeasurementBlockHeader {}

src/commands/measurements/request.rs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ use crate::{
1616
codec::{Codec, MessageBuf},
1717
commands::measurements::{
1818
GetMeasurementsReqAttr, GetMeasurementsReqCommon, GetMeasurementsReqSignature,
19+
MeasurementBlockHeader, MeasurementOperation, MeasurementsRspFixed,
1920
},
2021
context::SpdmContext,
2122
error::{CommandError, CommandResult, PlatformError},
2223
protocol::{ReqRespCode, SpdmMsgHdr, SpdmVersion, NONCE_LEN},
2324
state::ConnectionState,
25+
transcript::TranscriptContext,
2426
};
2527

2628
/// Generate a GET_MEASUREMENTS request
@@ -31,10 +33,12 @@ use crate::{
3133
/// * `raw_bitstream_requested`: Request a raw bit stream (if supported) (SPDM v1.2+)
3234
/// * `new_measurement_requested`: Request new measurement if the responder has pending updates to blocks (SPDM v1.3+)
3335
/// * `meas_op`: Measurement operation
34-
/// (0x00: query total number of available blocks, 0x01-0xFE: query block, 0xFF: query all blocks)
3536
/// * `slot_id`: Request a signed measurement if provided, signed with certificate slot identifier (0-7)
3637
/// * `context`: Append optional 8 byte context (SPDM v1.3+)
3738
///
39+
/// ## Note
40+
/// For SPDM version 1.0 this implementation does **not** supporte signed measurements.
41+
///
3842
/// # Returns
3943
/// - () on success
4044
/// - [CommandError] on failure
@@ -49,7 +53,7 @@ pub fn generate_get_measurements<'a>(
4953
req_buf: &mut MessageBuf<'a>,
5054
raw_bitstream_requested: bool,
5155
new_measurement_requested: bool,
52-
meas_op: u8,
56+
meas_op: MeasurementOperation,
5357
slot_id: Option<u8>,
5458
context: Option<&[u8; 8]>,
5559
) -> CommandResult<()> {
@@ -72,7 +76,11 @@ pub fn generate_get_measurements<'a>(
7276
let mut req_attr = GetMeasurementsReqAttr(0);
7377

7478
// signature requested is available in all versions
79+
// (v1.0 doesn't support slot-ids)
7580
if slot_id.is_some() {
81+
if connection_version == SpdmVersion::V10 {
82+
return Err((true, CommandError::UnsupportedRequest));
83+
}
7684
// Error if the responder doesn't support this
7785
if !responder_supports_signed_measurements(ctx) {
7886
return Err((true, CommandError::UnsupportedRequest));
@@ -96,7 +104,10 @@ pub fn generate_get_measurements<'a>(
96104
}
97105

98106
// Encode request attributes and `Measurement` operation
99-
let get_meas_common = GetMeasurementsReqCommon { req_attr, meas_op };
107+
let get_meas_common = GetMeasurementsReqCommon {
108+
req_attr,
109+
meas_op: meas_op.try_into().map_err(|e| (true, e))?,
110+
};
100111
payload_len += get_meas_common
101112
.encode(req_buf)
102113
.map_err(|e| (false, CommandError::Codec(e)))?;
@@ -137,7 +148,7 @@ pub fn generate_get_measurements<'a>(
137148
.push_data(payload_len)
138149
.map_err(|_| (false, CommandError::BufferTooSmall))?;
139150

140-
ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::L1)
151+
ctx.append_message_to_transcript(req_buf, TranscriptContext::L1)
141152
}
142153

143154
/// Check if the responder supports signing its measurements
@@ -165,5 +176,56 @@ pub(crate) fn handle_measurements_response<'a>(
165176
resp_header: SpdmMsgHdr,
166177
resp: &mut MessageBuf<'a>,
167178
) -> CommandResult<()> {
168-
todo!()
179+
// Validate connection state - algorithms must be negotiated
180+
if ctx.state.connection_info.state() < ConnectionState::AlgorithmsNegotiated {
181+
return Err((true, CommandError::UnsupportedResponse));
182+
}
183+
184+
// Validate version matches connection version
185+
let connection_version = ctx.state.connection_info.version_number();
186+
if resp_header.version().ok() != Some(connection_version) {
187+
return Err((true, CommandError::InvalidResponse));
188+
}
189+
190+
// Include the already parsed header again to parse `MeasurementsRspFixed`
191+
resp.push_data(size_of::<SpdmMsgHdr>())
192+
.map_err(|e| (false, e.into()))?;
193+
194+
let fixed_fields = MeasurementsRspFixed::decode(resp).map_err(|e| (true, e.into()))?;
195+
196+
// Convert 3-byte measurement record length to u32
197+
let _meas_record_length = u32::from_le_bytes([
198+
fixed_fields.measurement_record_len_byte0(),
199+
fixed_fields.measurement_record_len_byte1(),
200+
fixed_fields.measurement_record_len_byte2(),
201+
0,
202+
]);
203+
204+
// Decode all measurement blocks
205+
for _ in 0..fixed_fields.num_blocks() {
206+
let block_header = MeasurementBlockHeader::decode(resp).map_err(|e| (true, e.into()))?;
207+
resp.pull_data(block_header.measurement_size.get() as usize)
208+
.map_err(|e| (true, e.into()))?;
209+
}
210+
211+
// Decode Nonce
212+
let _nonce = resp.data(32).map_err(|e| (true, e.into()))?;
213+
resp.pull_data(32).map_err(|e| (true, e.into()))?;
214+
215+
// Decode opaque data
216+
let mut len_bytes = [0; 2];
217+
len_bytes.copy_from_slice(resp.data(2).map_err(|e| (true, e.into()))?);
218+
let opaque_data_len = u16::from_le_bytes(len_bytes);
219+
resp.pull_data(2).map_err(|e| (true, e.into()))?;
220+
resp.pull_data(opaque_data_len as usize)
221+
.map_err(|e| (true, e.into()))?;
222+
223+
// Decode requester context
224+
let _requester_ctx = resp.data(8).map_err(|e| (true, e.into()))?;
225+
resp.pull_data(8).map_err(|e| (true, e.into()))?;
226+
227+
// Remaining is the signature, if requested by the GET_MEASUREMENTS request
228+
229+
// Append response to transcript (L1 context for measurements)
230+
ctx.append_message_to_transcript(resp, TranscriptContext::L1)
169231
}

src/protocol/algorithms.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
use crate::error::{SpdmError, SpdmResult};
1616
use bitfield::bitfield;
17-
use zerocopy::{FromBytes, Immutable, IntoBytes};
17+
use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned};
1818

1919
pub const SHA384_HASH_SIZE: usize = 48;
2020
pub const ECC_P384_SIGNATURE_SIZE: usize = 96;
@@ -120,7 +120,7 @@ where
120120

121121
// Measurement Specification field
122122
bitfield! {
123-
#[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy)]
123+
#[derive(FromBytes, IntoBytes, Immutable, Default, Clone, Copy, Unaligned)]
124124
#[repr(C)]
125125
pub struct MeasurementSpecification(u8);
126126
impl Debug;

0 commit comments

Comments
 (0)