From 292b4f0bffe1beeb0c80522c771044cd0f96282b Mon Sep 17 00:00:00 2001 From: Ivan Petrov Date: Sun, 20 Oct 2024 17:15:57 +0100 Subject: [PATCH] Add DICE Attestation Verifier Bug: 374589992 Change-Id: I5d5d3b32754445647eda409051b6c7e7600b45ad --- .../tests/attester_tests.rs | 5 +- .../tests/verifier_tests.rs | 12 +- oak_attestation_verification/src/policy.rs | 3 +- .../src/policy/application.rs | 4 +- .../src/policy/binary.rs | 7 +- .../src/policy/kernel.rs | 4 +- .../src/policy/system.rs | 4 +- oak_attestation_verification/src/verifier.rs | 104 ++++++++++++++++- .../tests/verifier_tests.rs | 105 ++++++++++++------ .../src/policy.rs | 2 + .../src/util.rs | 2 +- oak_client/src/verifier.rs | 8 +- .../orchestrator/src/key_provisioning.rs | 9 +- 13 files changed, 204 insertions(+), 65 deletions(-) diff --git a/oak_attestation_integration_tests/tests/attester_tests.rs b/oak_attestation_integration_tests/tests/attester_tests.rs index 41bdab09f32..bd0f38f4363 100644 --- a/oak_attestation_integration_tests/tests/attester_tests.rs +++ b/oak_attestation_integration_tests/tests/attester_tests.rs @@ -67,8 +67,5 @@ fn dice_attester_generates_correct_dice_chain() { let test_evidence = dice_attester.quote().expect("couldn't generate the evidence"); let result = verify_dice_chain(&test_evidence); - // TODO: b/368030563 - Make the test check for `Ok` once keys are being stored - // in the Event. Since currently `verify_dice_chain` returns `Err("no - // application keys in evidence")`. - assert!(result.is_err()); + assert!(result.is_ok()); } diff --git a/oak_attestation_integration_tests/tests/verifier_tests.rs b/oak_attestation_integration_tests/tests/verifier_tests.rs index ba0c23b2435..e4eb7699aa9 100644 --- a/oak_attestation_integration_tests/tests/verifier_tests.rs +++ b/oak_attestation_integration_tests/tests/verifier_tests.rs @@ -15,7 +15,9 @@ // use oak_attestation_integration_tests::{create, Snapshot, SnapshotPath}; -use oak_attestation_verification::verifier::{to_attestation_results, verify, verify_dice_chain}; +use oak_attestation_verification::verifier::{ + to_attestation_results, verify, verify_dice_chain_and_extract_evidence, +}; use oak_proto_rust::oak::attestation::{ self, v1::{ @@ -41,7 +43,7 @@ fn verify_mock_dice_chain() { .expect("failed to create mock attester"); let mock_evidence = mock_attester.quote().expect("couldn't get evidence"); - let result = verify_dice_chain(&mock_evidence); + let result = verify_dice_chain_and_extract_evidence(&mock_evidence); assert!(result.is_ok()); let evidence_values: attestation::v1::extracted_evidence::EvidenceValues = @@ -60,7 +62,9 @@ fn get_restricted_kernel_evidence_proto_with_eventlog() -> attestation::v1::Evid #[test] fn verify_mock_dice_chain_with_valid_event_log() { - let result = verify_dice_chain(&get_restricted_kernel_evidence_proto_with_eventlog()); + let result = verify_dice_chain_and_extract_evidence( + &get_restricted_kernel_evidence_proto_with_eventlog(), + ); assert!(result.is_ok()); let evidence_values: attestation::v1::extracted_evidence::EvidenceValues = @@ -98,7 +102,7 @@ fn verify_mock_dice_chain_with_invalid_event_log() { stage0.kernel_cmdline = format!("evil modification {}", stage0.kernel_cmdline); *encoded_stage0_event = stage0.encode_to_vec(); - let result = verify_dice_chain(&evidence); + let result = verify_dice_chain_and_extract_evidence(&evidence); assert!(result.is_err()); } diff --git a/oak_attestation_verification/src/policy.rs b/oak_attestation_verification/src/policy.rs index 0414748c263..7251407f160 100644 --- a/oak_attestation_verification/src/policy.rs +++ b/oak_attestation_verification/src/policy.rs @@ -52,6 +52,7 @@ impl Policy for CombinedPolicy { &self, event_log: &EventLog, event_endorsements: &EventEndorsements, + milliseconds_since_epoch: i64, ) -> anyhow::Result { if event_log.encoded_events.len() != event_endorsements.encoded_event_endorsements.len() { anyhow::bail!( @@ -75,7 +76,7 @@ impl Policy for CombinedPolicy { ); let event_attestation_results = verification_iterator .map(|(event_policy, event, event_endorsements)| { - event_policy.verify(event, event_endorsements).unwrap_or( + event_policy.verify(event, event_endorsements, milliseconds_since_epoch).unwrap_or( // TODO: b/366186091 - Use Rust error types for failed attestation. EventAttestationResults {}, ) diff --git a/oak_attestation_verification/src/policy/application.rs b/oak_attestation_verification/src/policy/application.rs index d8d7bb0f8b2..cf10f145703 100644 --- a/oak_attestation_verification/src/policy/application.rs +++ b/oak_attestation_verification/src/policy/application.rs @@ -42,6 +42,7 @@ impl EventPolicy for ApplicationPolicy { &self, encoded_event: &[u8], encoded_event_endorsement: &[u8], + milliseconds_since_epoch: i64, ) -> anyhow::Result { let event = decode_event_proto::( "type.googleapis.com/oak.attestation.v1.ApplicationLayerData", @@ -53,8 +54,7 @@ impl EventPolicy for ApplicationPolicy { )?; let expected_values = get_application_layer_expected_values( - // TODO: b/369821273 - Add clocks to policies. - 0i64, + milliseconds_since_epoch, Some(&event_endorsements), &self.reference_values, ) diff --git a/oak_attestation_verification/src/policy/binary.rs b/oak_attestation_verification/src/policy/binary.rs index b17d4bacb93..10d9fb07f30 100644 --- a/oak_attestation_verification/src/policy/binary.rs +++ b/oak_attestation_verification/src/policy/binary.rs @@ -40,15 +40,16 @@ impl EventPolicy for BinaryPolicy { &self, encoded_event: &[u8], _encoded_event_endorsement: &[u8], + milliseconds_since_epoch: i64, ) -> anyhow::Result { let event = decode_event_proto::( "type.googleapis.com/oak.attestation.v1.EventData", encoded_event, )?; - // TODO: b/369821273 - Add clocks to policies. - let expected_values = get_event_expected_values(0i64, &self.reference_values) - .context("couldn't verify event endosements")?; + let expected_values = + get_event_expected_values(milliseconds_since_epoch, &self.reference_values) + .context("couldn't verify event endosements")?; compare_event_measurement_digests(&event, &expected_values) .context("couldn't verify generic event")?; diff --git a/oak_attestation_verification/src/policy/kernel.rs b/oak_attestation_verification/src/policy/kernel.rs index bb77c6994b1..e9693bc916f 100644 --- a/oak_attestation_verification/src/policy/kernel.rs +++ b/oak_attestation_verification/src/policy/kernel.rs @@ -41,6 +41,7 @@ impl EventPolicy for KernelPolicy { &self, encoded_event: &[u8], encoded_event_endorsement: &[u8], + milliseconds_since_epoch: i64, ) -> anyhow::Result { let event = decode_event_proto::( "type.googleapis.com/oak.attestation.v1.KernelLayerData", @@ -52,8 +53,7 @@ impl EventPolicy for KernelPolicy { )?; let expected_values = get_kernel_layer_expected_values( - // TODO: b/369821273 - Add clocks to policies. - 0i64, + milliseconds_since_epoch, Some(&event_endorsements), &self.reference_values, ) diff --git a/oak_attestation_verification/src/policy/system.rs b/oak_attestation_verification/src/policy/system.rs index dfeccd4512f..55f0326d81f 100644 --- a/oak_attestation_verification/src/policy/system.rs +++ b/oak_attestation_verification/src/policy/system.rs @@ -41,6 +41,7 @@ impl EventPolicy for SystemPolicy { &self, encoded_event: &[u8], encoded_event_endorsement: &[u8], + milliseconds_since_epoch: i64, ) -> anyhow::Result { let event = decode_event_proto::( "type.googleapis.com/oak.attestation.v1.SystemLayerData", @@ -52,8 +53,7 @@ impl EventPolicy for SystemPolicy { )?; let expected_values = get_system_layer_expected_values( - // TODO: b/369821273 - Add clocks to policies. - 0i64, + milliseconds_since_epoch, Some(&event_endorsements), &self.reference_values, ) diff --git a/oak_attestation_verification/src/verifier.rs b/oak_attestation_verification/src/verifier.rs index 824db30bce7..2da045ff80f 100644 --- a/oak_attestation_verification/src/verifier.rs +++ b/oak_attestation_verification/src/verifier.rs @@ -16,16 +16,20 @@ //! Provides verification based on evidence, endorsements and reference values. -use alloc::format; +use alloc::{boxed::Box, format}; use anyhow::Context; use coset::{cwt::ClaimsSet, CborSerializable, CoseKey}; use ecdsa::{signature::Verifier, Signature}; +use oak_attestation_verification_types::{ + policy::Policy, util::Clock, verifier::AttestationVerifier, +}; use oak_dice::cert::{cose_key_to_verifying_key, get_public_key_from_claims_set}; use oak_proto_rust::oak::attestation::v1::{ attestation_results::Status, endorsements, AttestationResults, Endorsements, EventLog, Evidence, ExpectedValues, ExtractedEvidence, LayerEvidence, ReferenceValues, }; +use p256::ecdsa::VerifyingKey; use crate::{ compare::compare_expected_values, @@ -57,6 +61,39 @@ pub fn to_attestation_results( } } +pub struct AmdSevSnpDiceAttestationVerifier { + policy: Box, + clock: Box, +} + +impl AmdSevSnpDiceAttestationVerifier { + pub fn new(policy: Box, clock: Box) -> Self { + Self { policy, clock } + } +} + +impl AttestationVerifier for AmdSevSnpDiceAttestationVerifier { + fn verify( + &self, + evidence: &Evidence, + endorsements: &Endorsements, + ) -> anyhow::Result { + // Last layer's certificate authority key is not used to sign anything. + let _ = verify_dice_chain(evidence).context("couldn't verify DICE chain")?; + + // Verify event log and event endorsements with corresponding policy. + let event_log = &evidence + .event_log + .as_ref() + .ok_or_else(|| anyhow::anyhow!("event log was not provided"))?; + let event_endorsements = &endorsements + .event_endorsements + .as_ref() + .ok_or_else(|| anyhow::anyhow!("event endorsements were not provided"))?; + self.policy.verify(event_log, event_endorsements, self.clock.get_milliseconds_since_epoch()) + } +} + /// Verifies entire setup by forwarding to individual setup types. /// /// This just fetches expected values using [`expect::get_expected_values`], @@ -114,7 +151,8 @@ pub fn verify_with_expected_values( // Ensure the DICE chain signatures are valid and extract the measurements, // public keys and other attestation-related data from the DICE chain. - let extracted_evidence = verify_dice_chain(evidence).context("invalid DICE chain")?; + let extracted_evidence = + verify_dice_chain_and_extract_evidence(evidence).context("invalid DICE chain")?; compare_expected_values(&extracted_evidence, expected_values) .context("comparing expected values to evidence")?; @@ -122,9 +160,66 @@ pub fn verify_with_expected_values( Ok(extracted_evidence) } +/// Verifies signatures of the certificates in the DICE chain and returns last +/// layer's Certificate Authority key if the verification is successful. +pub fn verify_dice_chain(evidence: &Evidence) -> anyhow::Result { + let root_layer_verifying_key = { + let cose_key = { + let root_layer = evidence + .root_layer + .as_ref() + .ok_or_else(|| anyhow::anyhow!("no root layer evidence"))?; + CoseKey::from_slice(&root_layer.eca_public_key).map_err(|_cose_err| { + anyhow::anyhow!("couldn't deserialize root layer public key") + })? + }; + cose_key_to_verifying_key(&cose_key).map_err(|msg| anyhow::anyhow!(msg))? + }; + + // Sequentially verify the layers, eventually retrieving the verifying key of + // the last layer. + let last_layer_verifying_key = evidence + .layers + .iter() + .try_fold(root_layer_verifying_key, |previous_layer_verifying_key, current_layer| { + let cert = coset::CoseSign1::from_slice(¤t_layer.eca_certificate) + .map_err(|_cose_err| anyhow::anyhow!("couldn't parse certificate"))?; + cert.verify_signature(ADDITIONAL_DATA, |signature, contents| { + let sig = Signature::from_slice(signature)?; + previous_layer_verifying_key.verify(contents, &sig) + }) + .map_err(|error| anyhow::anyhow!(error))?; + let payload = cert.payload.ok_or_else(|| anyhow::anyhow!("no cert payload"))?; + let claims = ClaimsSet::from_slice(&payload) + .map_err(|_cose_err| anyhow::anyhow!("couldn't parse claims set"))?; + let cose_key = get_public_key_from_claims_set(&claims) + .map_err(|msg| anyhow::anyhow!(msg)) + .context("couldn't get a public key from claims")?; + cose_key_to_verifying_key(&cose_key) + .map_err(|msg| anyhow::anyhow!(msg)) + .context("couldn't convert cose key") + }) + .context("couldn't verify DICE chain")?; + + // Verify the event log claim for this layer if it exists. This is done for all + // layers here, since the event log is tied uniquely closely to the DICE chain. + if let Some(event_log) = &evidence.event_log { + validate_that_event_log_is_captured_in_dice_layers(event_log, &evidence.layers) + .context("events in log do not match the digests in the dice chain")? + } else { + anyhow::bail!("event log is not present in the evidence"); + } + + Ok(last_layer_verifying_key) +} + /// Verifies signatures of the certificates in the DICE chain and extracts the /// evidence values from the certificates if the verification is successful. -pub fn verify_dice_chain(evidence: &Evidence) -> anyhow::Result { +// TODO: b/356631464 - Remove this function once all clients use verification +// policies. +pub fn verify_dice_chain_and_extract_evidence( + evidence: &Evidence, +) -> anyhow::Result { let root_layer_verifying_key = { let cose_key = { let root_layer = evidence @@ -163,8 +258,7 @@ pub fn verify_dice_chain(evidence: &Evidence) -> anyhow::Result anyhow::Result; } @@ -39,5 +40,6 @@ pub trait EventPolicy: Send + Sync { &self, encoded_event: &[u8], encoded_event_endorsement: &[u8], + milliseconds_since_epoch: i64, ) -> anyhow::Result; } diff --git a/oak_attestation_verification_types/src/util.rs b/oak_attestation_verification_types/src/util.rs index 30efb032379..503bee38eac 100644 --- a/oak_attestation_verification_types/src/util.rs +++ b/oak_attestation_verification_types/src/util.rs @@ -17,5 +17,5 @@ /// Trait for the time related functionality. pub trait Clock: Send + Sync { /// Return time in milliseconds since epoch. - fn get_current_time_ms(&self) -> i64; + fn get_milliseconds_since_epoch(&self) -> i64; } diff --git a/oak_client/src/verifier.rs b/oak_client/src/verifier.rs index 0c9240bd710..cfb951420fc 100644 --- a/oak_client/src/verifier.rs +++ b/oak_client/src/verifier.rs @@ -15,7 +15,7 @@ // use anyhow::Context; -use oak_attestation_verification::verifier::verify_dice_chain; +use oak_attestation_verification::verifier::verify_dice_chain_and_extract_evidence; use oak_proto_rust::oak::attestation::v1::{Endorsements, Evidence, ExtractedEvidence}; pub trait AttestationVerifier { @@ -33,12 +33,12 @@ pub struct InsecureAttestationVerifier; impl AttestationVerifier for InsecureAttestationVerifier { fn verify(&self, evidence: &Evidence, _: &Endorsements) -> anyhow::Result { - verify_dice_chain(evidence).context("couldn't verify the DICE chain") + verify_dice_chain_and_extract_evidence(evidence).context("couldn't verify the DICE chain") } } pub fn extract_encryption_public_key(evidence: &Evidence) -> anyhow::Result> { - let attestation_results = - verify_dice_chain(evidence).context("couldn't verify the DICE chain")?; + let attestation_results = verify_dice_chain_and_extract_evidence(evidence) + .context("couldn't verify the DICE chain")?; Ok(attestation_results.encryption_public_key) } diff --git a/oak_containers/orchestrator/src/key_provisioning.rs b/oak_containers/orchestrator/src/key_provisioning.rs index d4d38ec10b5..30c869caa10 100644 --- a/oak_containers/orchestrator/src/key_provisioning.rs +++ b/oak_containers/orchestrator/src/key_provisioning.rs @@ -15,7 +15,7 @@ use std::sync::Arc; -use oak_attestation_verification::verifier::verify_dice_chain; +use oak_attestation_verification::verifier::verify_dice_chain_and_extract_evidence; use oak_containers_attestation::GroupKeys; use oak_grpc::oak::key_provisioning::v1::key_provisioning_server::{ KeyProvisioning, KeyProvisioningServer, @@ -56,9 +56,10 @@ impl KeyProvisioning for KeyProvisioningService { ))?; // TODO(#4442): Provide reference values by the hostlib and use `verify` // function. - let attestation_results = verify_dice_chain(&evidence).map_err(|err| { - tonic::Status::invalid_argument(format!("couldn't verify endorsed evidence: {err}")) - })?; + let attestation_results = + verify_dice_chain_and_extract_evidence(&evidence).map_err(|err| { + tonic::Status::invalid_argument(format!("couldn't verify endorsed evidence: {err}")) + })?; // Encrypt group keys. let encrypted_encryption_private_key = self