Skip to content

New report parsing #378

Draft
DGonzalezVillal wants to merge 1 commit intovirtee:mainfrom
DGonzalezVillal:raw_parser
Draft

New report parsing #378
DGonzalezVillal wants to merge 1 commit intovirtee:mainfrom
DGonzalezVillal:raw_parser

Conversation

@DGonzalezVillal
Copy link
Member

@DGonzalezVillal DGonzalezVillal commented Feb 4, 2026

Overview

This PR introduces a new attestation report parsing flow, based on the discussion in
#373.

The proposal adds two new structures:

  • RawAttestationReport
  • VerifiedAttestationReport

RawAttestationReport

RawAttestationReport stores the raw bytes of the attestation report and allows
signature verification to be performed independently of parsing.

Key properties:

  • Preserves the attestation report exactly as received
  • Can be transferred between intermediaries without requiring parsing
  • Supports signature verification without interpreting untrusted fields
  • Enables forward compatibility when new report versions or reserved fields appear

VerifiedAttestationReport

A RawAttestationReport can be converted into a VerifiedAttestationReport by
verifying it against the appropriate certificate chain.

VerifiedAttestationReport:

  • Still contains the raw report bytes
  • Represents a report whose signature has been successfully verified
  • Can be safely parsed into the typed AttestationReport

Importantly, this design intentionally prevents direct conversion from raw bytes or
RawAttestationReport into AttestationReport without first performing verification.

Parsing Flow

The intended flow is:

raw bytes
   ↓
RawAttestationReport
   ↓  (signature verification)
VerifiedAttestationReport
   ↓  (typed parsing)
AttestationReport

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.

@DGonzalezVillal
Copy link
Member Author

DGonzalezVillal commented Feb 4, 2026

@npmccallum
Copy link
Contributor

npmccallum commented Feb 5, 2026

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
}

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>
Copy link
Contributor

@npmccallum npmccallum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_like heuristic (bytes 0x1A0..0x1E0)
  • For V3+: use cpuid_fam_id/cpuid_mod_id with Generation::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:

  • versionu32
  • guest_svnu32
  • vmplu32
  • sig_algou32
  • policyGuestPolicy
  • plat_infoPlatformInfo
  • key_infoKeyInfo
  • current / committedVersion
  • launch_mit_vector / current_mit_vectorOption<u64>
  • cpuid_fam_id / cpuid_mod_id / cpuid_stepOption<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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments