Conversation
|
I was expecting something like this: struct Report<'a> {
pub body: &'a [u8],
pub algorithm: Algorithm,
pub signature: &'a [u8],
}
struct Body<'a> {
// all fields public
}
impl<'a> Report<'a> {
/// Parses the "outer" portion of the attestation report
fn parse(report: &'a [u8]) -> Result<Self>;
/// Verifies the signature using the key and then
/// parses the verified body.
fn verify(&self, key: &Key) -> Result<Body<'a>> {
// validate signature over body using key...
Body::parse(self.body)
}
}
impl<'a> Body<'a> {
fn parse(body: &'a [u8]) -> Result<Self>;
// no need to serialize
} |
6379b5a to
22693d0
Compare
Introduce a new attestation report parsing flow based on Report and ReportBody. Report represents an unverified attestation report and holds borrowed raw bytes for the report body and its signature. Signature verification can be performed via the Verifiable trait with a Certificate or Chain. ReportBody is a zero-copy view over the report body bytes. All fields are borrowed slices, with getter functions that parse them into typed values. It can be constructed from raw bytes via from_bytes(), or obtained after verification through Report. We keep the legacy AttestationReport for backward compatibility, but users are encouraged to migrate to the Report/ReportBody flow. Signed-off-by: DGonzalezVillal <Diego.GonzalezVillalobos@amd.com>
22693d0 to
b118be6
Compare
npmccallum
left a comment
There was a problem hiding this comment.
Review: ReportBody should carry fully-parsed types, not raw byte slices
The overall design (two-phase: Report for signature verification over raw bytes, then ReportBody for typed access) is solid. But the current ReportBody is a half-measure — it stores &[u8] slices and then has getters that do u32::from_le_bytes() / .try_into().unwrap(). Those unwrap()s are technically safe (lengths are guaranteed by from_bytes), but the conversions should happen at parse time so errors propagate through the Result returned by from_bytes. Then the getters become redundant and can be deleted.
1. Parse TCB fields fully at parse time — derive Generation internally
This is the big one. The current code stores TCB fields as &[u8] and forces callers to pass Generation into getter methods like current_tcb(&self, generation: Generation) -> TcbVersion. But the parser already has everything it needs to derive Generation internally:
- For V2: use the
chip_id_is_turin_likeheuristic (bytes0x1A0..0x1E0) - For V3+: use
cpuid_fam_id/cpuid_mod_idwithGeneration::identify_cpu
This is exactly what AttestationReport::Decoder already does — ReportBody::from_bytes should do the same. Store as TcbVersion directly, not &[u8; 8].
Deferring TCB parsing to getters forces a third level of parsing on the caller and defeats the purpose of a typed report body.
2. Expose ReportVariant as a field
The parser already computes ReportVariant from version_num to determine which optional fields are present. Expose it as pub variant: ReportVariant so callers know the version semantics without re-deriving from the raw version number.
3. Scalar fields → final types at parse time
All of these should be parsed in from_bytes, not in getters:
version→u32guest_svn→u32vmpl→u32sig_algo→u32policy→GuestPolicyplat_info→PlatformInfokey_info→KeyInfocurrent/committed→Versionlaunch_mit_vector/current_mit_vector→Option<u64>cpuid_fam_id/cpuid_mod_id/cpuid_step→Option<u8>- All TCB fields →
TcbVersion(per point 1)
4. Fixed-size byte arrays → &'a [u8; N] via TryFrom
For reference types where zero-copy makes sense, use <&[u8; N]>::try_from(slice)? which is fallible and propagates errors cleanly:
family_id: &'a [u8; 16]image_id: &'a [u8; 16]report_data: &'a [u8; 64]measurement: &'a [u8; 48]host_data: &'a [u8; 32]id_key_digest: &'a [u8; 48]author_key_digest: &'a [u8; 48]report_id: &'a [u8; 32]report_id_ma: &'a [u8; 32]chip_id: &'a [u8; 64]
5. Delete all getters
With the above changes, every field carries its final type. The getters become redundant — callers just access the public fields directly.
Summary
The principle: the parser should do all the parsing. ReportBody fields should be the final typed values, with all conversion errors propagated through from_bytes. Zero-copy &[u8; N] references where it makes sense for large byte arrays, owned scalars/enums for everything else. No post-parse interpretation required by the caller.
Overview
This PR introduces a new attestation report parsing flow, based on the discussion in
#373.
The proposal adds two new structures:
RawAttestationReportVerifiedAttestationReportRawAttestationReport
RawAttestationReportstores the raw bytes of the attestation report and allowssignature verification to be performed independently of parsing.
Key properties:
VerifiedAttestationReport
A
RawAttestationReportcan be converted into aVerifiedAttestationReportbyverifying it against the appropriate certificate chain.
VerifiedAttestationReport:AttestationReportImportantly, this design intentionally prevents direct conversion from raw bytes or
RawAttestationReportintoAttestationReportwithout first performing verification.Parsing Flow
The intended flow is:
This ensures that parsing of structured fields only happens after the report’s
integrity has been validated.
Forward Compatibility and Type Safety
This approach:
Solves forward-compatibility issues by preserving the full raw report
Maintains type safety by reusing the existing strict parsing logic
Avoids lossy parsing of reserved or unknown fields
Prevents unsafe interpretation of future report versions
##Unsafe Parsing (Optional)
I am also considering reintroducing the unsafe_parser feature, which would allow
direct conversion from raw bytes to AttestationReport for users who explicitly opt
into that behavior.
This would remain an opt-in escape hatch and would not be part of the default flow.
Additional Notes
Logic that previously coerced unknown future report versions (e.g. version 6) into
the latest known version has been removed, as this behavior is unsafe.
If this design looks acceptable, I will complete the PR and add appropriate tests.