From 82862eb18a966f6ff7ee1f0999ea1c24c184ba70 Mon Sep 17 00:00:00 2001 From: Jason LeBrun Date: Wed, 9 Oct 2024 23:40:29 +0000 Subject: [PATCH] Add expiration time to expected values This introduces (but does not yet use) the concept of validity for expected values. Other notes: * rename `into_expected_digests` -> `to_expected_digests` by Rust convention. * Add UnixTimestampMillis helper and use instead of inline 1000 * * Added helper to convert between the Rust validity type and proto type. Fixed: b/333580907 Change-Id: I3f0187668fccf63a4ee46c63a55e9de0dd709f22 --- .../src/endorsement.rs | 9 +- oak_attestation_verification/src/expect.rs | 66 ++- .../src/expect/tests.rs | 29 +- oak_attestation_verification/src/lib.rs | 5 +- oak_attestation_verification/src/test_util.rs | 16 +- oak_attestation_verification/src/util.rs | 32 +- oak_attestation_verification/src/verifier.rs | 7 +- .../generated/oak.attestation.v1.rs | 440 +++++++++--------- proto/attestation/BUILD | 1 + proto/attestation/expected_value.proto | 5 + 10 files changed, 347 insertions(+), 263 deletions(-) diff --git a/oak_attestation_verification/src/endorsement.rs b/oak_attestation_verification/src/endorsement.rs index 19df64ceb81..c4b098b51fc 100644 --- a/oak_attestation_verification/src/endorsement.rs +++ b/oak_attestation_verification/src/endorsement.rs @@ -37,7 +37,10 @@ use time::OffsetDateTime; use crate::{ rekor::{get_rekor_log_entry_body, verify_rekor_log_entry, verify_rekor_log_entry_ecdsa}, - util::{convert_pem_to_raw, equal_keys, verify_signature, verify_signature_ecdsa}, + util::{ + convert_pem_to_raw, equal_keys, verify_signature, verify_signature_ecdsa, + UnixTimestampMillis, + }, }; /// URI representing in-toto statements. We only use V1, earlier and later @@ -327,10 +330,10 @@ pub fn validate_statement( match &statement.predicate.validity { Some(validity) => { - if 1000 * validity.not_before.unix_timestamp() > now_utc_millis.into() { + if validity.not_before.unix_timestamp_millis() > now_utc_millis.into() { anyhow::bail!("the claim is not yet applicable") } - if 1000 * validity.not_after.unix_timestamp() < now_utc_millis.into() { + if validity.not_after.unix_timestamp_millis() < now_utc_millis.into() { anyhow::bail!("the claim is no longer applicable") } } diff --git a/oak_attestation_verification/src/expect.rs b/oak_attestation_verification/src/expect.rs index e61803c459f..78537e96fef 100644 --- a/oak_attestation_verification/src/expect.rs +++ b/oak_attestation_verification/src/expect.rs @@ -42,7 +42,8 @@ use prost::Message; use crate::{ endorsement::{ - get_digest, is_firmware_type, is_kernel_type, parse_statement, verify_binary_endorsement, + self, get_digest, is_firmware_type, is_kernel_type, parse_statement, + verify_binary_endorsement, }, util::{hex_to_raw_digest, is_hex_digest_match, raw_digest_from_contents, raw_to_hex_digest}, }; @@ -389,12 +390,15 @@ pub(crate) fn get_expected_measurement_digest( &public_keys.rekor_public_key, ) .context("verifying binary endorsement")?; - Ok(into_expected_digests(&[hex_to_raw_digest(&get_digest(&parse_statement( - &endorsement.endorsement, - )?)?)?])) + let endorsement_statement = parse_statement(&endorsement.endorsement) + .context("parsing endorsement statement")?; + Ok(to_expected_digests( + &[hex_to_raw_digest(&get_digest(&endorsement_statement)?)?], + endorsement_statement.predicate.validity.as_ref(), + )) } Some(binary_reference_value::Type::Digests(expected_digests)) => { - Ok(into_expected_digests(&expected_digests.digests)) + Ok(to_expected_digests(&expected_digests.digests, None)) } None => Err(anyhow::anyhow!("empty binary reference value")), } @@ -445,24 +449,28 @@ fn get_stage0_expected_values( r#type: Some(expected_digests::Type::Skipped(VerificationSkipped {})), }), Some(binary_reference_value::Type::Endorsement(public_keys)) => { - let firmware_attachment = get_verified_stage0_attachment( - now_utc_millis, - endorsement.context("matching endorsement not found for reference value")?, - public_keys, - ) - .context("getting verified stage0 attachment")?; + let endorsement = + endorsement.context("matching endorsement not found for reference value")?; + + let firmware_attachment = + get_verified_stage0_attachment(now_utc_millis, endorsement, public_keys) + .context("getting verified stage0 attachment")?; + + let endorsement_statement = parse_statement(&endorsement.endorsement) + .context("parsing endorsement statement")?; - Ok(into_expected_digests( + Ok(to_expected_digests( firmware_attachment .configs .values() .map(|digest| hex_to_raw_digest(digest).unwrap()) .collect::>() .as_slice(), + endorsement_statement.predicate.validity.as_ref(), )) } Some(binary_reference_value::Type::Digests(expected_digests)) => { - Ok(into_expected_digests(expected_digests.digests.as_slice())) + Ok(to_expected_digests(expected_digests.digests.as_slice(), None)) } None => Err(anyhow::anyhow!("empty stage0 reference value")), @@ -532,28 +540,38 @@ fn get_kernel_expected_values( .setup_data .ok_or_else(|| anyhow::anyhow!("no setup data digest in kernel attachment"))?; + let endorsement = endorsement.context("No endorsement provided")?; + let parsed_statement = parse_statement(&endorsement.endorsement) + .context("parsing endorsement statement")?; + Ok(KernelExpectedValues { - image: Some(into_expected_digests(&[hex_to_raw_digest(&expected_image)?])), - setup_data: Some(into_expected_digests(&[hex_to_raw_digest( - &expected_setup_data, - )?])), + image: Some(to_expected_digests( + &[hex_to_raw_digest(&expected_image)?], + parsed_statement.predicate.validity.as_ref(), + )), + setup_data: Some(to_expected_digests( + &[hex_to_raw_digest(&expected_setup_data)?], + parsed_statement.predicate.validity.as_ref(), + )), }) } Some(kernel_binary_reference_value::Type::Digests(expected_digests)) => { Ok(KernelExpectedValues { - image: Some(into_expected_digests( + image: Some(to_expected_digests( &expected_digests .image .as_ref() .ok_or_else(|| anyhow::anyhow!("no image digests provided"))? .digests, + None, )), - setup_data: Some(into_expected_digests( + setup_data: Some(to_expected_digests( &expected_digests .setup_data .as_ref() .ok_or_else(|| anyhow::anyhow!("no setup_data digests provided"))? .digests, + None, )), }) } @@ -605,9 +623,15 @@ pub(crate) fn get_text_expected_values( } } -fn into_expected_digests(source: &[RawDigest]) -> ExpectedDigests { +fn to_expected_digests( + source: &[RawDigest], + claim_validity: Option<&endorsement::Validity>, +) -> ExpectedDigests { ExpectedDigests { - r#type: Some(expected_digests::Type::Digests(RawDigests { digests: source.to_vec() })), + r#type: Some(expected_digests::Type::Digests(RawDigests { + digests: source.to_vec(), + validity: claim_validity.map(|cv| cv.into()), + })), } } diff --git a/oak_attestation_verification/src/expect/tests.rs b/oak_attestation_verification/src/expect/tests.rs index febf4f72faa..662fb07c60b 100644 --- a/oak_attestation_verification/src/expect/tests.rs +++ b/oak_attestation_verification/src/expect/tests.rs @@ -26,7 +26,10 @@ use oak_proto_rust::oak::{ use prost::Message; use time::ext::NumericalDuration; -use crate::{test_util, util}; +use crate::{ + test_util::{self, GetValidity}, + util::{self, UnixTimestampMillis}, +}; #[test] fn test_get_expected_measurement_digest_validity() { @@ -34,6 +37,7 @@ fn test_get_expected_measurement_digest_validity() { let measured_content = b"Just some abitrary content"; let content_digests = util::raw_digest_from_contents(measured_content); let endorsement = test_util::fake_endorsement(&content_digests, test_util::Usage::None); + let endorsement_validity = endorsement.predicate.validity.as_ref().expect("no validity"); // Now create the TR endorsement. let (signing_key, public_key) = test_util::new_random_signing_keypair(); @@ -50,9 +54,9 @@ fn test_get_expected_measurement_digest_validity() { // endorsement signing key. let reference_value = test_util::binary_reference_value_for_endorser_pk(public_key); // Pretend it's a week into validity. - let now = - endorsement.predicate.validity.as_ref().unwrap().not_before.saturating_add((7).days()); - let now_utc_millis = 1000 * now.unix_timestamp(); + let now_utc_millis = + endorsement.validity().not_before.saturating_add((7).days()).unix_timestamp_millis(); + let expected_digests = super::get_expected_measurement_digest( now_utc_millis, Some(&tr_endorsement), @@ -61,10 +65,12 @@ fn test_get_expected_measurement_digest_validity() { .expect("failed to get digests"); // Assert that the expected digests are those from the verified endorsement. + assert_eq!( expected_digests, ExpectedDigests { r#type: Some(expected_digests::Type::Digests(RawDigests { + validity: Some(endorsement_validity.into()), digests: vec![content_digests], })), } @@ -89,6 +95,7 @@ fn test_get_stage0_expected_values_validity() { let subject_digests = util::raw_digest_from_contents(&serialized_subject); let (signing_key, public_key) = test_util::new_random_signing_keypair(); let endorsement = test_util::fake_endorsement(&subject_digests, test_util::Usage::Firmware); + let endorsement_validity = endorsement.predicate.validity.as_ref().expect("no validity"); let (serialized_endorsement, endorsement_signature) = test_util::serialize_and_sign_endorsement(&endorsement, signing_key); let tr_endorsement = TransparentReleaseEndorsement { @@ -103,9 +110,8 @@ fn test_get_stage0_expected_values_validity() { // endorsement signing key. let reference_value = test_util::binary_reference_value_for_endorser_pk(public_key); // Pretend it's a week into the validity period. - let now = - endorsement.predicate.validity.as_ref().unwrap().not_before.saturating_add((7).days()); - let now_utc_millis = 1000 * now.unix_timestamp(); + let now_utc_millis = + endorsement.validity().not_before.saturating_add((7).days()).unix_timestamp_millis(); let expected_digests = super::get_stage0_expected_values(now_utc_millis, Some(&tr_endorsement), &reference_value) .expect("failed to get digests"); @@ -115,6 +121,7 @@ fn test_get_stage0_expected_values_validity() { expected_digests, ExpectedDigests { r#type: Some(expected_digests::Type::Digests(RawDigests { + validity: Some(endorsement_validity.into()), digests: vec![content_digests], })), } @@ -140,6 +147,7 @@ fn test_get_kernel_expected_values_validity() { let subject_digests = util::raw_digest_from_contents(&serialized_subject); let (signing_key, public_key) = test_util::new_random_signing_keypair(); let endorsement = test_util::fake_endorsement(&subject_digests, test_util::Usage::Kernel); + let endorsement_validity = endorsement.predicate.validity.as_ref().expect("no validity"); let (serialized_endorsement, endorsement_signature) = test_util::serialize_and_sign_endorsement(&endorsement, signing_key); let tr_endorsement = TransparentReleaseEndorsement { @@ -154,9 +162,8 @@ fn test_get_kernel_expected_values_validity() { // endorsement signing key. let reference_value = test_util::kernel_binary_reference_value_for_endorser_pk(public_key); // Pretend it's a week into the validity period. - let now = - endorsement.predicate.validity.as_ref().unwrap().not_before.saturating_add((7).days()); - let now_utc_millis = 1000 * now.unix_timestamp(); + let now_utc_millis = + endorsement.validity().not_before.saturating_add((7).days()).unix_timestamp_millis(); let expected_digests = super::get_kernel_expected_values(now_utc_millis, Some(&tr_endorsement), &reference_value) .expect("failed to get digests"); @@ -166,6 +173,7 @@ fn test_get_kernel_expected_values_validity() { expected_digests.image, Some(ExpectedDigests { r#type: Some(expected_digests::Type::Digests(RawDigests { + validity: Some(endorsement_validity.into()), digests: vec![image_digests], })), }) @@ -174,6 +182,7 @@ fn test_get_kernel_expected_values_validity() { expected_digests.setup_data, Some(ExpectedDigests { r#type: Some(expected_digests::Type::Digests(RawDigests { + validity: Some(endorsement_validity.into()), digests: vec![setup_digests], })), }) diff --git a/oak_attestation_verification/src/lib.rs b/oak_attestation_verification/src/lib.rs index 3450494709b..481296ab059 100644 --- a/oak_attestation_verification/src/lib.rs +++ b/oak_attestation_verification/src/lib.rs @@ -37,6 +37,7 @@ use oak_proto_rust::oak::attestation::v1::{ }; pub use util::{ convert_pem_to_raw, hex_to_raw_digest, raw_to_hex_digest, reference_values_from_evidence, + UnixTimestampMillis, }; /// Verifies a signed endorsement against a reference value. @@ -57,8 +58,8 @@ pub fn verify_endorsement( Ok(EndorsementDetails { subject_digest: Some(digest), validity: Some(Validity { - not_before: 1000 * validity.not_before.unix_timestamp(), - not_after: 1000 * validity.not_after.unix_timestamp(), + not_before: validity.not_before.unix_timestamp_millis(), + not_after: validity.not_after.unix_timestamp_millis(), }), }) } diff --git a/oak_attestation_verification/src/test_util.rs b/oak_attestation_verification/src/test_util.rs index 83d7bb0aca1..754b2bae0c6 100644 --- a/oak_attestation_verification/src/test_util.rs +++ b/oak_attestation_verification/src/test_util.rs @@ -26,9 +26,7 @@ use oak_proto_rust::oak::{ use p256::{ecdsa::signature::Signer, pkcs8::EncodePublicKey, NistP256, PublicKey}; use time::macros::datetime; -use crate::endorsement::{ - self, DefaultPredicate, DefaultStatement, Subject, Validity as EndorsementValidity, -}; +use crate::endorsement::{self, DefaultPredicate, DefaultStatement, Statement, Subject}; pub enum Usage { None, @@ -56,7 +54,7 @@ pub fn fake_endorsement(digests: &RawDigest, usage: Usage) -> DefaultStatement { predicate: DefaultPredicate { usage: usage.to_string(), issued_on: datetime!(2024-10-01 12:08 UTC), - validity: Some(EndorsementValidity { + validity: Some(endorsement::Validity { not_before: datetime!(2024-09-01 12:00 UTC), not_after: datetime!(2024-12-01 12:00 UTC), }), @@ -141,3 +139,13 @@ fn raw_digest_to_map(h: &RawDigest) -> BTreeMap { map } + +pub trait GetValidity { + fn validity(&self) -> &endorsement::Validity; +} + +impl GetValidity for Statement { + fn validity(&self) -> &endorsement::Validity { + self.predicate.validity.as_ref().expect("missing validity") + } +} diff --git a/oak_attestation_verification/src/util.rs b/oak_attestation_verification/src/util.rs index 0a7bf5dbac6..962222fe28c 100644 --- a/oak_attestation_verification/src/util.rs +++ b/oak_attestation_verification/src/util.rs @@ -28,7 +28,7 @@ use oak_proto_rust::oak::{ KernelBinaryReferenceValue, KernelDigests, KernelLayerData, KernelLayerReferenceValues, KeyType, OakContainersReferenceValues, OakRestrictedKernelReferenceValues, ReferenceValues, RootLayerData, RootLayerReferenceValues, Signature, SkipVerification, StringLiterals, - SystemLayerReferenceValues, TextReferenceValue, VerifyingKeySet, + SystemLayerReferenceValues, TextReferenceValue, Validity, VerifyingKeySet, }, HexDigest, RawDigest, }; @@ -36,6 +36,9 @@ use p256::pkcs8::{der::Decode, DecodePublicKey}; use prost::Message; use prost_types::Any; use sha2::{Digest, Sha256, Sha384, Sha512}; +use time::OffsetDateTime; + +use crate::endorsement; const PUBLIC_KEY_PEM_LABEL: &str = "PUBLIC KEY"; @@ -461,5 +464,32 @@ pub fn decode_protobuf_any( }) } +/// Return a milliseconds-since-the-epoch timestamp value. +/// /// +/// Endorsement validity structures in our JSON-based endorsements use +/// milliseconds resolution, but [`OffsetDateTime`] provides only seconds or +/// nanoseconds since the epoch. +/// +/// This bridges a convenience gap, and helps with readability of code that +/// works with validity times. +pub trait UnixTimestampMillis { + fn unix_timestamp_millis(&self) -> i64; +} + +impl UnixTimestampMillis for OffsetDateTime { + fn unix_timestamp_millis(&self) -> i64 { + self.unix_timestamp() * 1000 + } +} + +impl From<&endorsement::Validity> for Validity { + fn from(value: &endorsement::Validity) -> Validity { + Validity { + not_before: value.not_before.unix_timestamp_millis(), + not_after: value.not_after.unix_timestamp_millis(), + } + } +} + #[cfg(test)] mod tests; diff --git a/oak_attestation_verification/src/verifier.rs b/oak_attestation_verification/src/verifier.rs index 5323f756528..3f0dbbeb887 100644 --- a/oak_attestation_verification/src/verifier.rs +++ b/oak_attestation_verification/src/verifier.rs @@ -83,11 +83,11 @@ pub fn to_attestation_results( /// Verifies entire setup by forwarding to individual setup types. /// -/// This just fetches expected values using [get_expected_values], and then -/// calls [verify_with_expected_values] with those. +/// This just fetches expected values using [`expect::get_expected_values`], +/// and then call [`verify_with_expected_values`] providing those values. /// /// If you'd like to cache and reuse the values, call those two methods -/// indepedently, and cache the results of the first. +/// independently, and cache the results of the first. pub fn verify( now_utc_millis: i64, evidence: &Evidence, @@ -101,7 +101,6 @@ pub fn verify( } /// Verifies entire setup by forwarding to individual setup types. -/// This variant returns expected values along with the extracted evidence. pub fn verify_with_expected_values( now_utc_millis: i64, evidence: &Evidence, diff --git a/oak_proto_rust/generated/oak.attestation.v1.rs b/oak_proto_rust/generated/oak.attestation.v1.rs index 744546e8f94..7bd95d02281 100644 --- a/oak_proto_rust/generated/oak.attestation.v1.rs +++ b/oak_proto_rust/generated/oak.attestation.v1.rs @@ -934,224 +934,6 @@ pub mod endorsements { Cb(super::CbEndorsements), } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct VerificationSkipped {} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct RawDigests { - #[prost(message, repeated, tag = "1")] - pub digests: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct ExpectedDigests { - #[prost(oneof = "expected_digests::Type", tags = "1, 2")] - pub r#type: ::core::option::Option, -} -/// Nested message and enum types in `ExpectedDigests`. -pub mod expected_digests { - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost_derive::Oneof)] - pub enum Type { - /// If the reference value was set to SkipVerification, we represent that - /// here. - #[prost(message, tag = "1")] - Skipped(super::VerificationSkipped), - /// One or more digests that should be considered a valid match against an - /// actual value. - #[prost(message, tag = "2")] - Digests(super::RawDigests), - } -} -/// The expected values for kernel image and setup data, computed from previously -/// provided endorsements and reference values. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct KernelExpectedValues { - /// Allowable digests for the image. - #[prost(message, optional, tag = "1")] - pub image: ::core::option::Option, - /// Allowable digests for the setup data. - #[prost(message, optional, tag = "2")] - pub setup_data: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct AmdSevExpectedValues { - #[prost(message, optional, tag = "1")] - pub stage0_expected: ::core::option::Option, - /// Minimum accepted versions of all TCB components. - #[prost(message, optional, tag = "2")] - pub min_tcb_version: ::core::option::Option, - /// If true, will skip the check that the TEE is not in debug mode. - #[prost(bool, tag = "3")] - pub allow_debug: bool, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct IntelTdxExpectedValues {} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct InsecureExpectedValues {} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct ExpectedRegex { - #[prost(string, tag = "1")] - pub value: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct ExpectedStringLiterals { - #[prost(string, repeated, tag = "1")] - pub value: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct TextExpectedValue { - #[prost(oneof = "text_expected_value::Type", tags = "1, 2, 3")] - pub r#type: ::core::option::Option, -} -/// Nested message and enum types in `TextExpectedValue`. -pub mod text_expected_value { - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost_derive::Oneof)] - pub enum Type { - /// If the reference value was set to SkipVerification, we represent that - /// here. - #[prost(message, tag = "1")] - Skipped(super::VerificationSkipped), - #[prost(message, tag = "2")] - Regex(super::ExpectedRegex), - #[prost(message, tag = "3")] - StringLiterals(super::ExpectedStringLiterals), - } -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct RootLayerExpectedValues { - /// Switches between AMD SEV-SNP and Intel TDX based on TeePlatform value. - /// Verification is skipped when not running in a TEE. - #[prost(message, optional, tag = "1")] - pub amd_sev: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub intel_tdx: ::core::option::Option, - /// When insecure is set no verification of the TEE platform is performed. This - /// can be used when not running in a TEE or when the client is agnostic about - /// the platform and doesn't care about the hardware verification. - #[prost(message, optional, tag = "3")] - pub insecure: ::core::option::Option, -} -/// Reference values of the kernel layer, as measured by stage0. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct KernelLayerExpectedValues { - /// Verifies the kernel based on endorsement. - #[prost(message, optional, tag = "1")] - pub kernel: ::core::option::Option, - /// Verifies the kernel command line, i.e. the parameters passed to the - /// kernel on boot. - #[prost(message, optional, tag = "2")] - pub kernel_cmd_line_text: ::core::option::Option, - /// Verifies the stage1 binary if running as Oak Containers. - #[prost(message, optional, tag = "3")] - pub init_ram_fs: ::core::option::Option, - #[prost(message, optional, tag = "4")] - pub memory_map: ::core::option::Option, - #[prost(message, optional, tag = "5")] - pub acpi: ::core::option::Option, -} -/// The expected binary digests for a system layer image. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct SystemLayerExpectedValues { - /// The allowable digest values for a system layer image. - #[prost(message, optional, tag = "1")] - pub system_image: ::core::option::Option, -} -/// The expected bundle and configuration digests for a container layer. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct ContainerLayerExpectedValues { - /// The allowable digest values for a container bundle. - #[prost(message, optional, tag = "1")] - pub bundle: ::core::option::Option, - /// The allowable digest values for a configuration passed into a container. - #[prost(message, optional, tag = "2")] - pub config: ::core::option::Option, -} -/// The expected binary and configuration digests for an application layer. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct ApplicationLayerExpectedValues { - /// The allowable digest values for an application binary. - #[prost(message, optional, tag = "1")] - pub binary: ::core::option::Option, - /// The allowable digest values for a configuration passed to the application - /// binary. - #[prost(message, optional, tag = "2")] - pub configuration: ::core::option::Option, -} -/// Represents digest of an event. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct EventExpectedValues { - #[prost(message, optional, tag = "1")] - pub event: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct OakRestrictedKernelExpectedValues { - #[prost(message, optional, tag = "1")] - pub root_layer: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub kernel_layer: ::core::option::Option, - #[prost(message, optional, tag = "3")] - pub application_layer: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct OakContainersExpectedValues { - #[prost(message, optional, tag = "1")] - pub root_layer: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub kernel_layer: ::core::option::Option, - #[prost(message, optional, tag = "3")] - pub system_layer: ::core::option::Option, - #[prost(message, optional, tag = "4")] - pub container_layer: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct CbExpectedValues { - #[prost(message, optional, tag = "1")] - pub root_layer: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub kernel_layer: ::core::option::Option, - #[prost(message, optional, tag = "3")] - pub system_layer: ::core::option::Option, - #[prost(message, optional, tag = "4")] - pub application_layer: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost_derive::Message)] -pub struct ExpectedValues { - #[prost(oneof = "expected_values::Type", tags = "1, 2, 3")] - pub r#type: ::core::option::Option, -} -/// Nested message and enum types in `ExpectedValues`. -pub mod expected_values { - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost_derive::Oneof)] - pub enum Type { - #[prost(message, tag = "1")] - OakRestrictedKernel(super::OakRestrictedKernelExpectedValues), - #[prost(message, tag = "2")] - OakContainers(super::OakContainersExpectedValues), - #[prost(message, tag = "3")] - Cb(super::CbExpectedValues), - } -} /// Represents a verification result. Can be extended to return certain /// measurements and other detail to the client for further processing. /// Nomenclature follows RFC 9334. @@ -1482,3 +1264,225 @@ pub struct CbData { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost_derive::Message)] pub struct OakStandaloneData {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct VerificationSkipped {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct RawDigests { + #[prost(message, repeated, tag = "1")] + pub digests: ::prost::alloc::vec::Vec, + /// This field is optional, and only used for some optional + /// optimizations like client-side caching of verified expected values. + #[prost(message, optional, tag = "2")] + pub validity: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct ExpectedDigests { + #[prost(oneof = "expected_digests::Type", tags = "1, 2")] + pub r#type: ::core::option::Option, +} +/// Nested message and enum types in `ExpectedDigests`. +pub mod expected_digests { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost_derive::Oneof)] + pub enum Type { + /// If the reference value was set to SkipVerification, we represent that + /// here. + #[prost(message, tag = "1")] + Skipped(super::VerificationSkipped), + /// One or more digests that should be considered a valid match against an + /// actual value. + #[prost(message, tag = "2")] + Digests(super::RawDigests), + } +} +/// The expected values for kernel image and setup data, computed from previously +/// provided endorsements and reference values. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct KernelExpectedValues { + /// Allowable digests for the image. + #[prost(message, optional, tag = "1")] + pub image: ::core::option::Option, + /// Allowable digests for the setup data. + #[prost(message, optional, tag = "2")] + pub setup_data: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct AmdSevExpectedValues { + #[prost(message, optional, tag = "1")] + pub stage0_expected: ::core::option::Option, + /// Minimum accepted versions of all TCB components. + #[prost(message, optional, tag = "2")] + pub min_tcb_version: ::core::option::Option, + /// If true, will skip the check that the TEE is not in debug mode. + #[prost(bool, tag = "3")] + pub allow_debug: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct IntelTdxExpectedValues {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct InsecureExpectedValues {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct ExpectedRegex { + #[prost(string, tag = "1")] + pub value: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct ExpectedStringLiterals { + #[prost(string, repeated, tag = "1")] + pub value: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct TextExpectedValue { + #[prost(oneof = "text_expected_value::Type", tags = "1, 2, 3")] + pub r#type: ::core::option::Option, +} +/// Nested message and enum types in `TextExpectedValue`. +pub mod text_expected_value { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost_derive::Oneof)] + pub enum Type { + /// If the reference value was set to SkipVerification, we represent that + /// here. + #[prost(message, tag = "1")] + Skipped(super::VerificationSkipped), + #[prost(message, tag = "2")] + Regex(super::ExpectedRegex), + #[prost(message, tag = "3")] + StringLiterals(super::ExpectedStringLiterals), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct RootLayerExpectedValues { + /// Switches between AMD SEV-SNP and Intel TDX based on TeePlatform value. + /// Verification is skipped when not running in a TEE. + #[prost(message, optional, tag = "1")] + pub amd_sev: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub intel_tdx: ::core::option::Option, + /// When insecure is set no verification of the TEE platform is performed. This + /// can be used when not running in a TEE or when the client is agnostic about + /// the platform and doesn't care about the hardware verification. + #[prost(message, optional, tag = "3")] + pub insecure: ::core::option::Option, +} +/// Reference values of the kernel layer, as measured by stage0. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct KernelLayerExpectedValues { + /// Verifies the kernel based on endorsement. + #[prost(message, optional, tag = "1")] + pub kernel: ::core::option::Option, + /// Verifies the kernel command line, i.e. the parameters passed to the + /// kernel on boot. + #[prost(message, optional, tag = "2")] + pub kernel_cmd_line_text: ::core::option::Option, + /// Verifies the stage1 binary if running as Oak Containers. + #[prost(message, optional, tag = "3")] + pub init_ram_fs: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub memory_map: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub acpi: ::core::option::Option, +} +/// The expected binary digests for a system layer image. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct SystemLayerExpectedValues { + /// The allowable digest values for a system layer image. + #[prost(message, optional, tag = "1")] + pub system_image: ::core::option::Option, +} +/// The expected bundle and configuration digests for a container layer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct ContainerLayerExpectedValues { + /// The allowable digest values for a container bundle. + #[prost(message, optional, tag = "1")] + pub bundle: ::core::option::Option, + /// The allowable digest values for a configuration passed into a container. + #[prost(message, optional, tag = "2")] + pub config: ::core::option::Option, +} +/// The expected binary and configuration digests for an application layer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct ApplicationLayerExpectedValues { + /// The allowable digest values for an application binary. + #[prost(message, optional, tag = "1")] + pub binary: ::core::option::Option, + /// The allowable digest values for a configuration passed to the application + /// binary. + #[prost(message, optional, tag = "2")] + pub configuration: ::core::option::Option, +} +/// Represents digest of an event. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct EventExpectedValues { + #[prost(message, optional, tag = "1")] + pub event: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct OakRestrictedKernelExpectedValues { + #[prost(message, optional, tag = "1")] + pub root_layer: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub kernel_layer: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub application_layer: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct OakContainersExpectedValues { + #[prost(message, optional, tag = "1")] + pub root_layer: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub kernel_layer: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub system_layer: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub container_layer: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct CbExpectedValues { + #[prost(message, optional, tag = "1")] + pub root_layer: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub kernel_layer: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub system_layer: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub application_layer: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost_derive::Message)] +pub struct ExpectedValues { + #[prost(oneof = "expected_values::Type", tags = "1, 2, 3")] + pub r#type: ::core::option::Option, +} +/// Nested message and enum types in `ExpectedValues`. +pub mod expected_values { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost_derive::Oneof)] + pub enum Type { + #[prost(message, tag = "1")] + OakRestrictedKernel(super::OakRestrictedKernelExpectedValues), + #[prost(message, tag = "2")] + OakContainers(super::OakContainersExpectedValues), + #[prost(message, tag = "3")] + Cb(super::CbExpectedValues), + } +} diff --git a/proto/attestation/BUILD b/proto/attestation/BUILD index 5c5547ab190..6fba3528b28 100644 --- a/proto/attestation/BUILD +++ b/proto/attestation/BUILD @@ -90,6 +90,7 @@ proto_library( deps = [ ":reference_value_proto", ":tcb_version_proto", + ":verification_proto", "//proto:digest_proto", ], ) diff --git a/proto/attestation/expected_value.proto b/proto/attestation/expected_value.proto index 9252f6f36bb..0f049e821c1 100644 --- a/proto/attestation/expected_value.proto +++ b/proto/attestation/expected_value.proto @@ -21,6 +21,7 @@ package oak.attestation.v1; import "proto/digest.proto"; import "proto/attestation/tcb_version.proto"; +import "proto/attestation/verification.proto"; option go_package = "proto/oak/attestation/v1"; option java_multiple_files = true; @@ -30,6 +31,10 @@ message VerificationSkipped {} message RawDigests { repeated RawDigest digests = 1; + + // This field is optional, and only used for some optional + // optimizations like client-side caching of verified expected values. + Validity validity = 2; } message ExpectedDigests {