Skip to content

Commit 0133253

Browse files
committed
Implement MEASUREMENTS response parsing
- Measurement data is currently only parsed and discarded
1 parent 8105bf9 commit 0133253

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
@@ -7,10 +7,10 @@ pub mod request;
77
/// Responder logic for GET_MEASUREMENTS and MEASURMENTS
88
pub mod response;
99

10-
use crate::codec::CommonCodec;
1110
use crate::protocol::*;
11+
use crate::{codec::CommonCodec, error::CommandError};
1212
use bitfield::bitfield;
13-
use zerocopy::{FromBytes, Immutable, IntoBytes};
13+
use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned};
1414

1515
const RESPONSE_FIXED_FIELDS_SIZE: usize = 8;
1616
const MAX_RESPONSE_VARIABLE_FIELDS_SIZE: usize =
@@ -77,3 +77,43 @@ impl Default for MeasurementsRspFixed<[u8; RESPONSE_FIXED_FIELDS_SIZE]> {
7777
}
7878

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

src/commands/measurements/request.rs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ use crate::{
44
codec::{Codec, MessageBuf},
55
commands::measurements::{
66
GetMeasurementsReqAttr, GetMeasurementsReqCommon, GetMeasurementsReqSignature,
7+
MeasurementBlockHeader, MeasurementOperation, MeasurementsRspFixed,
78
},
89
context::SpdmContext,
910
error::{CommandError, CommandResult, PlatformError},
1011
protocol::{ReqRespCode, SpdmMsgHdr, SpdmVersion, NONCE_LEN},
1112
state::ConnectionState,
13+
transcript::TranscriptContext,
1214
};
1315

1416
/// Generate a GET_MEASUREMENTS request
@@ -19,10 +21,12 @@ use crate::{
1921
/// * `raw_bitstream_requested`: Request a raw bit stream (if supported) (SPDM v1.2+)
2022
/// * `new_measurement_requested`: Request new measurement if the responder has pending updates to blocks (SPDM v1.3+)
2123
/// * `meas_op`: Measurement operation
22-
/// (0x00: query total number of available blocks, 0x01-0xFE: query block, 0xFF: query all blocks)
2324
/// * `slot_id`: Request a signed measurement if provided, signed with certificate slot identifier (0-7)
2425
/// * `context`: Append optional 8 byte context (SPDM v1.3+)
2526
///
27+
/// ## Note
28+
/// For SPDM version 1.0 this implementation does **not** supporte signed measurements.
29+
///
2630
/// # Returns
2731
/// - () on success
2832
/// - [CommandError] on failure
@@ -37,7 +41,7 @@ pub fn generate_get_measurements<'a>(
3741
req_buf: &mut MessageBuf<'a>,
3842
raw_bitstream_requested: bool,
3943
new_measurement_requested: bool,
40-
meas_op: u8,
44+
meas_op: MeasurementOperation,
4145
slot_id: Option<u8>,
4246
context: Option<&[u8; 8]>,
4347
) -> CommandResult<()> {
@@ -60,7 +64,11 @@ pub fn generate_get_measurements<'a>(
6064
let mut req_attr = GetMeasurementsReqAttr(0);
6165

6266
// signature requested is available in all versions
67+
// (v1.0 doesn't support slot-ids)
6368
if slot_id.is_some() {
69+
if connection_version == SpdmVersion::V10 {
70+
return Err((true, CommandError::UnsupportedRequest));
71+
}
6472
// Error if the responder doesn't support this
6573
if !responder_supports_signed_measurements(ctx) {
6674
return Err((true, CommandError::UnsupportedRequest));
@@ -84,7 +92,10 @@ pub fn generate_get_measurements<'a>(
8492
}
8593

8694
// Encode request attributes and `Measurement` operation
87-
let get_meas_common = GetMeasurementsReqCommon { req_attr, meas_op };
95+
let get_meas_common = GetMeasurementsReqCommon {
96+
req_attr,
97+
meas_op: meas_op.try_into().map_err(|e| (true, e))?,
98+
};
8899
payload_len += get_meas_common
89100
.encode(req_buf)
90101
.map_err(|e| (false, CommandError::Codec(e)))?;
@@ -125,7 +136,7 @@ pub fn generate_get_measurements<'a>(
125136
.push_data(payload_len)
126137
.map_err(|_| (false, CommandError::BufferTooSmall))?;
127138

128-
ctx.append_message_to_transcript(req_buf, crate::transcript::TranscriptContext::L1)
139+
ctx.append_message_to_transcript(req_buf, TranscriptContext::L1)
129140
}
130141

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

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)