-
Notifications
You must be signed in to change notification settings - Fork 37
feat: integrate the TEE attestation verifier contract into mpc-contract #3540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
e3dc354
refactor(attestation): split DCAP verification from post-DCAP checks
pbeza 2bdfcf1
feat(attestation): lower attestation expiry window from 7 days to 1 day
pbeza 1c31292
feat(contract): add trusted-verifier state and participant voting (do…
pbeza 097f177
feat(contract): verify Dstack attestations via the tee-verifier contract
pbeza 326f38e
test(contract): stub tee-verifier + sandbox tests for async attestati…
pbeza 28de2f6
test: fix attestation-cli expiry assertion + silence stub enum lint
pbeza 332cb46
docs(attestation-verifier): add implemented-status banner with remain…
pbeza f4f29e5
fix(contract): use plain borsh for PendingAttestation/FinalOutcome
pbeza 3fa3535
fix(contract): satisfy --all-features ABI build for verifier stub + F…
pbeza d1d1486
test(contract): update borsh-schema snapshot for new verifier state
pbeza 9bcd696
fix(contract): shadow old Config layout in v3_11_2 migration state
pbeza a6e5ad3
fix: conform two TODO(#NNNN) prose references to the TODO-format check
pbeza 21033ef
fix: hoist use statements out of function bodies (no-use-in-fn lint)
pbeza f898a48
test(contract): update ABI snapshot for the verifier-integration methods
pbeza be7ced6
style: apply nightly rustfmt formatting
pbeza 377eba8
docs: fix intra-doc links broken by the verify split + conversions move
pbeza e773072
test(contract): sync ABI snapshot with the resolve_verification doc fix
pbeza 1722caf
test(contract): assert observable state, not yield-resume tx flags
pbeza 2cdd6fc
Merge remote-tracking branch 'origin/main' into feat/integrate-tee-ve…
pbeza 186eb40
fix(contract): correct async attestation callback edge cases
pbeza 0d16721
test(contract): sync ABI snapshot with async-callback review fixes
pbeza bdd2eb4
Merge remote-tracking branch 'origin/main' into feat/integrate-tee-ve…
pbeza cc18ebb
fix(contract): guard verifier vote against placeholder; pin FinalOutc…
pbeza 61690fb
Merge remote-tracking branch 'origin/main' into feat/integrate-tee-ve…
pbeza 1567afe
Merge remote-tracking branch 'origin/main' into feat/integrate-tee-ve…
pbeza e01f2c3
Merge remote-tracking branch 'origin/main' into feat/integrate-tee-ve…
pbeza File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,10 @@ use core::fmt; | |
| use derive_more::Constructor; | ||
| use serde::{Deserialize, Serialize}; | ||
| use sha2::{Digest as _, Sha256, Sha384}; | ||
| use tee_verifier_interface::{TDReport10, VerifiedReport}; | ||
|
|
||
| #[cfg(feature = "local-verify")] | ||
| use crate::dcap_conversions::{IntoDcapType as _, IntoInterfaceType as _}; | ||
|
|
||
| /// Expected TCB status for a successfully verified TEE quote. | ||
| const EXPECTED_QUOTE_STATUS: &str = "UpToDate"; | ||
|
|
@@ -30,14 +34,19 @@ pub(crate) const KEY_PROVIDER_EVENT: &str = "key-provider"; | |
|
|
||
| const RTMR3_INDEX: u32 = 3; | ||
|
|
||
| // `quote` and `collateral` are the `tee-verifier-interface` mirrors; their | ||
| // serde impls come from that crate's off-by-default `serde` feature, which | ||
| // `attestation` enables. Serde is needed because `DstackAttestation` is | ||
| // embedded (via `mpc_attestation::Attestation`) in the node's serde-serialized | ||
| // `/public_data` HTTP payload. | ||
| #[derive(Clone, Constructor, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] | ||
| pub struct DstackAttestation { | ||
| pub quote: QuoteBytes, | ||
| pub collateral: Collateral, | ||
| pub tcb_info: TcbInfo, | ||
| } | ||
|
|
||
| /// Result of a successful [`DstackAttestation::verify`] call. | ||
| /// Result of a successful [`DstackAttestation::verify_with_report`] call. | ||
| #[derive(Clone, Debug)] | ||
| pub struct AcceptedDstackAttestation { | ||
| pub measurements: ExpectedMeasurements, | ||
|
|
@@ -123,33 +132,30 @@ impl fmt::Debug for DstackAttestation { | |
| } | ||
|
|
||
| impl DstackAttestation { | ||
| /// Checks whether this attestation is valid | ||
| /// with respect to expected values of: | ||
| /// - report_data: must be measured correctly in RTMR3 | ||
| /// - timestamp_seconds: current UNIX time in seconds | ||
| /// - accepted_measurements: set of accepted RTMRs and key-provider event digest. | ||
| /// If any element in the set is valid, the function accepts the attestation as | ||
| /// valid. | ||
| /// Runs the post-DCAP checks against an already-verified report. | ||
| /// | ||
| /// Pure: no `dcap-qvl`, no host calls. The DCAP cryptographic verification | ||
| /// (`dcap_qvl::verify::verify`) is done elsewhere — by the `tee-verifier` | ||
| /// contract on-chain, or by [`verify_locally`](Self::verify_locally) | ||
| /// off-chain — and its `VerifiedReport` is passed in here. This is the | ||
| /// function both the contract and the off-chain helper share. | ||
| /// | ||
| /// On success, returns the matched measurements along with any informational | ||
| /// advisory IDs surfaced alongside an `UpToDate` TCB status. | ||
| pub fn verify( | ||
| /// `report` must be the verifier's output for *this* attestation's quote | ||
| /// and collateral; the checks below bind it to the expected report data, | ||
| /// the embedded TCB info, and the accepted measurement sets. | ||
| pub fn verify_with_report( | ||
| &self, | ||
| report: &VerifiedReport, | ||
| expected_report_data: ReportData, | ||
| timestamp_seconds: u64, | ||
| accepted_measurements: &[ExpectedMeasurements], | ||
| ) -> Result<AcceptedDstackAttestation, VerificationError> { | ||
| let verification_result = | ||
| dcap_qvl::verify::verify(&self.quote, &self.collateral, timestamp_seconds) | ||
| .map_err(|e| VerificationError::DcapVerification(e.to_string()))?; | ||
|
|
||
| let report_data = verification_result | ||
| let report_data = report | ||
| .report | ||
| .as_td10() | ||
| .ok_or(VerificationError::ReportNotTd10)?; | ||
|
|
||
| // Verify all attestation components | ||
| let advisory_ids = Self::verify_tcb_status(&verification_result)?; | ||
| let advisory_ids = Self::verify_tcb_status(report)?; | ||
| self.verify_report_data(&expected_report_data, report_data)?; | ||
|
|
||
| self.verify_rtmr3(report_data, &self.tcb_info)?; | ||
|
|
@@ -163,6 +169,43 @@ impl DstackAttestation { | |
| }) | ||
| } | ||
|
|
||
| /// Full local verification: runs `dcap_qvl::verify::verify` and then the | ||
| /// post-DCAP checks via [`verify_with_report`](Self::verify_with_report). | ||
| /// | ||
| /// Off-chain only (the `local-verify` feature pulls in `dcap-qvl`). Used by | ||
| /// the node, `tee-authority`, and `attestation-cli` to verify an | ||
| /// attestation end-to-end without the verifier contract. On-chain, | ||
| /// `mpc-contract` instead calls the verifier contract for the DCAP step and | ||
| /// then `verify_with_report` directly. | ||
| #[cfg(feature = "local-verify")] | ||
| pub fn verify_locally( | ||
| &self, | ||
| expected_report_data: ReportData, | ||
| timestamp_seconds: u64, | ||
| accepted_measurements: &[ExpectedMeasurements], | ||
| ) -> Result<AcceptedDstackAttestation, VerificationError> { | ||
| let report = self.dcap_report(timestamp_seconds)?; | ||
| self.verify_with_report(&report, expected_report_data, accepted_measurements) | ||
| } | ||
|
|
||
| /// Runs only the DCAP step (`dcap_qvl::verify::verify`) and returns the | ||
| /// resulting report as the `tee-verifier-interface` mirror — the same value | ||
| /// the `tee-verifier` contract returns on-chain. Off-chain only. | ||
| /// | ||
| /// This is the boundary between the DCAP verification (which the contract | ||
| /// offloads to the verifier) and the post-DCAP checks | ||
| /// ([`verify_with_report`](Self::verify_with_report)). | ||
| #[cfg(feature = "local-verify")] | ||
| pub fn dcap_report(&self, timestamp_seconds: u64) -> Result<VerifiedReport, VerificationError> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe call it dcap_verify_report or dcap_verify_quote? |
||
| let quote: Vec<u8> = self.quote.clone().into_dcap_type(); | ||
| let collateral = self.collateral.clone().into_dcap_type(); | ||
| Ok( | ||
| dcap_qvl::verify::verify("e, &collateral, timestamp_seconds) | ||
| .map_err(|e| VerificationError::DcapVerification(e.to_string()))? | ||
| .into_interface_type(), | ||
| ) | ||
| } | ||
|
|
||
| /// Replays RTMR3 from the event log by hashing all relevant events together and verifies all | ||
| /// digests are correct | ||
| fn verify_event_log_rtmr3( | ||
|
|
@@ -241,21 +284,18 @@ impl DstackAttestation { | |
| /// after a product's Extended Servicing Updates date). These may appear with | ||
| /// `UpToDate` and do not indicate a vulnerability; they are returned so the | ||
| /// caller can log/expose them. | ||
| fn verify_tcb_status( | ||
| verification_result: &dcap_qvl::verify::VerifiedReport, | ||
| ) -> Result<Vec<String>, VerificationError> { | ||
| (verification_result.status == EXPECTED_QUOTE_STATUS).or_err(|| { | ||
| VerificationError::TcbStatusNotUpToDate(verification_result.status.clone()) | ||
| })?; | ||
| fn verify_tcb_status(report: &VerifiedReport) -> Result<Vec<String>, VerificationError> { | ||
| (report.status == EXPECTED_QUOTE_STATUS) | ||
| .or_err(|| VerificationError::TcbStatusNotUpToDate(report.status.clone()))?; | ||
|
|
||
| Ok(verification_result.advisory_ids.clone()) | ||
| Ok(report.advisory_ids.clone()) | ||
| } | ||
|
|
||
| /// Verifies report data matches expected values. | ||
| fn verify_report_data( | ||
| &self, | ||
| expected: &ReportData, | ||
| actual: &dcap_qvl::quote::TDReport10, | ||
| actual: &TDReport10, | ||
| ) -> Result<(), VerificationError> { | ||
| // Check if sha384(tls_public_key) matches the hash in report_data. This check effectively | ||
| // proves that tls_public_key was included in the quote's report_data by an app running | ||
|
|
@@ -267,7 +307,7 @@ impl DstackAttestation { | |
| /// On success, returns the matched measurements. | ||
| fn verify_any_measurements( | ||
| &self, | ||
| report_data: &dcap_qvl::quote::TDReport10, | ||
| report_data: &TDReport10, | ||
| tcb_info: &TcbInfo, | ||
| accepted_measurements: &[ExpectedMeasurements], | ||
| ) -> Result<ExpectedMeasurements, VerificationError> { | ||
|
|
@@ -292,7 +332,7 @@ impl DstackAttestation { | |
| /// Verifies static RTMRs match expected values. | ||
| fn verify_static_rtmrs( | ||
| &self, | ||
| report_data: &dcap_qvl::quote::TDReport10, | ||
| report_data: &TDReport10, | ||
| tcb_info: &TcbInfo, | ||
| expected_measurements: &ExpectedMeasurements, | ||
| ) -> Result<(), VerificationError> { | ||
|
|
@@ -346,7 +386,7 @@ impl DstackAttestation { | |
| /// Verifies RTMR3 by replaying event log. | ||
| fn verify_rtmr3( | ||
| &self, | ||
| report_data: &dcap_qvl::quote::TDReport10, | ||
| report_data: &TDReport10, | ||
| tcb_info: &TcbInfo, | ||
| ) -> Result<(), VerificationError> { | ||
| compare_hashes("rtmr3", tcb_info.rtmr3.as_slice(), &report_data.rt_mr3)?; | ||
|
|
@@ -489,10 +529,8 @@ mod tests { | |
| use super::*; | ||
|
|
||
| use alloc::{string::ToString, vec, vec::Vec}; | ||
| use dcap_qvl::{ | ||
| quote::{EnclaveReport, Report}, | ||
| tcb_info::{TcbStatus, TcbStatusWithAdvisory}, | ||
| verify::VerifiedReport, | ||
| use tee_verifier_interface::{ | ||
| EnclaveReport, Report, TcbStatus, TcbStatusWithAdvisory, VerifiedReport, | ||
| }; | ||
|
|
||
| fn verified_report(status: &str, advisory_ids: Vec<String>) -> VerifiedReport { | ||
|
|
@@ -516,8 +554,14 @@ mod tests { | |
| report_data: [0u8; 64], | ||
| }), | ||
| ppid: Vec::new(), | ||
| qe_status: TcbStatusWithAdvisory::new(TcbStatus::UpToDate, Vec::new()), | ||
| platform_status: TcbStatusWithAdvisory::new(TcbStatus::UpToDate, Vec::new()), | ||
| qe_status: TcbStatusWithAdvisory { | ||
| status: TcbStatus::UpToDate, | ||
| advisory_ids: Vec::new(), | ||
| }, | ||
| platform_status: TcbStatusWithAdvisory { | ||
| status: TcbStatus::UpToDate, | ||
| advisory_ids: Vec::new(), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there is a better name, I had to read the description to understand what "with_report" means.
but I don't have any suggestions :-(