From 860f5e1d0eed205340eebd0e082fa31dc3f1bfe3 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:42:55 +0000 Subject: [PATCH 01/39] SDK fairing for handling attestation context and CC flag enabling --- Cargo.lock | 78 +++++++++++++++++++ attestation-server/Cargo.toml | 3 + attestation-server/src/main.rs | 10 +-- attestation-server/src/nvidia_api.rs | 108 ++++++++++++++++++++++++++- nvidia-attest/src/lib.rs | 10 +++ nvidia-attest/src/types.rs | 3 + 6 files changed, 206 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e32137b..5c9e8fff 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,6 +187,8 @@ dependencies = [ "libattest", "log", "nvat", + "nvidia-attest", + "nvml-wrapper", "rocket", "serde", "serde_json", @@ -620,6 +622,41 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.10.0" @@ -1566,6 +1603,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -2113,6 +2156,29 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "nvml-wrapper" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f049ae562349fefb8e837eb15443da1e7c6dcbd8a11f52a228f92220c2e5c85e" +dependencies = [ + "bitflags", + "libloading", + "nvml-wrapper-sys", + "static_assertions", + "thiserror 1.0.69", + "wrapcenum-derive", +] + +[[package]] +name = "nvml-wrapper-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4d594420fcda43b1c2c4bd44d48974aa3c7a9ab2cbf10dc18e35265767bf0b" +dependencies = [ + "libloading", +] + [[package]] name = "oid" version = "0.2.1" @@ -4668,6 +4734,18 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wrapcenum-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/attestation-server/Cargo.toml b/attestation-server/Cargo.toml index 28557b2e..cab23970 100644 --- a/attestation-server/Cargo.toml +++ b/attestation-server/Cargo.toml @@ -16,9 +16,12 @@ base64 = "0.22.1" hex = "0.4.3" thiserror = "2" nvat = { git = "ssh://git@github.com/prem-research/nvat-rs.git", branch = "main" } + libattest = { path = "../libattest" } +nvidia-attest = { path = "../nvidia-attest" } snp-attest = { path = "../snp-attest", default-features = false } env_logger = "0.11.8" log = "0.4.29" configfs-tsm = { version = "0.0.2" } +nvml-wrapper = "0.12.1" diff --git a/attestation-server/src/main.rs b/attestation-server/src/main.rs index aafe3315..f64280ec 100644 --- a/attestation-server/src/main.rs +++ b/attestation-server/src/main.rs @@ -16,7 +16,7 @@ use rocket::{State, routes}; use sev::firmware::guest::Firmware; use tokio::sync::Mutex; -use crate::{modules::ModuleDetector, response::ApiJsonResult}; +use crate::{modules::ModuleDetector, nvidia_api::SdkFairing, response::ApiJsonResult}; #[rocket::get("/modules")] fn get_modules(modules: &State) -> ApiJsonResult<&Modules> { @@ -56,12 +56,12 @@ async fn main() -> Result<(), anyhow::Error> { }; if let Some(GpuModule::Nvidia) = modules.gpu() { - use nvat::SdkHandle; - - let sdk = SdkHandle::get_handle()?; + // attach the nvidia fairing responsible for first attestation + // and enable gpus for confidential computing operations + let sdk = SdkFairing::init()?; + rocket = rocket.attach(sdk); routes.extend(routes![nvidia_api::nvidia_attestation]); - rocket = rocket.manage(sdk); }; rocket.mount("/attestation", routes).launch().await?; diff --git a/attestation-server/src/nvidia_api.rs b/attestation-server/src/nvidia_api.rs index c86b67b5..9bd507ef 100644 --- a/attestation-server/src/nvidia_api.rs +++ b/attestation-server/src/nvidia_api.rs @@ -1,10 +1,116 @@ +use std::collections::HashSet; +use std::ops::Deref; + use anyhow::Context; use nvat::{AttestationBuilder, SdkHandle, nonce::NvatNonce}; -use rocket::State; +use nvidia_attest::EATToken; +use nvidia_attest::keychain::KeyChain; +use nvidia_attest::nonce::NvidiaNonce; +use nvml_wrapper::Nvml; +use nvml_wrapper::enum_wrappers::device::Brand::Nvidia; +use rocket::fairing::{Fairing, Info, Kind}; +use rocket::{Build, Rocket, State}; use crate::nonce::NonceParam; use crate::response::ApiError; +pub struct SdkFairing { + handle: SdkHandle, + nvml: Nvml, + + // set of UUIDs of gpus enabled for confidential computing. + // must turn off confidential computing on exit. + attestation_enabled: HashSet, +} + +impl SdkFairing { + pub fn init() -> anyhow::Result { + let handle = SdkHandle::get_handle()?; + let nvml = Nvml::init()?; + let attestation_enabled = HashSet::new(); + + Ok(SdkFairing { + handle, + nvml, + attestation_enabled, + }) + } + + async fn attest_and_init(&self) -> anyhow::Result<()> { + let keychain = KeyChain::fetch_keychain().await?; + + let nonce = NvidiaNonce::generate(); + let nvat_nonce = NvatNonce::from_hex(&self.handle, &nonce.to_hex())?; + + let attestation = AttestationBuilder::new(&self.handle) + .context("cannot create attestation context")? + .gpu() + .verifier_remote() + .build() + .attest_device(&nvat_nonce)?; + + // NvidiaNonce:: + let claims = + EATToken::parse(attestation.detached_eat.as_str()?)?.verify(&keychain, &nonce)?; + + // we gather the device uuids from the claims of the attested gpus + // so we don't accidentally turn on confidential computing for + // gpus we didn't explicitly verify + let uuids: HashSet = claims + .gpu_claims() + .values() + .map(|gpu| &gpu.ueid) + .cloned() + .collect(); + + for uuid in uuids { + let device = self.nvml.device_by_uuid(uuid.deref()).with_context(|| { + format!("Device with UUID: {uuid} was attested but not found by nvml") + })?; + + // once we attest the device we can activate + // confidential compute state + device + .set_confidential_compute_state(true) + .with_context(|| { + format!("cannot activate confidential computing for gpu uuid:{uuid}") + })?; + } + + let count = self.nvml.device_count()? as usize; + if count != claims.gpu_claims().iter().count() { + log::warn!( + "there are still devices on this machine for which confidential computing wasn't enabled." + ) + } + + Ok(()) + } +} + +#[rocket::async_trait] +impl Fairing for SdkFairing { + fn info(&self) -> rocket::fairing::Info { + Info { + name: "Nvidia SDK initializer", + kind: Kind::Ignite, + } + } + + async fn on_ignite(&self, rocket: Rocket) -> Result, Rocket> { + let result = self.attest_and_init().await; + if let Err(error) = result { + log::error!("Error returned from SDK initialization fairing: {error:?}"); + return Err(rocket); + } + + // if initialization succeeds then attach the sdk handle + let rocket = rocket.manage(self.handle.clone()); + + Ok(rocket) + } +} + #[rocket::get("/nvidia?")] pub async fn nvidia_attestation( nonce: NonceParam, 32>, diff --git a/nvidia-attest/src/lib.rs b/nvidia-attest/src/lib.rs index cceea6cb..a74db0fd 100644 --- a/nvidia-attest/src/lib.rs +++ b/nvidia-attest/src/lib.rs @@ -31,6 +31,16 @@ pub struct DecodedClaims { gpu_claims: HashMap, } +impl DecodedClaims { + pub fn overall_claims(&self) -> &OverallClaims { + &self.overall_claims + } + + pub fn gpu_claims(&self) -> &HashMap { + &self.gpu_claims + } +} + #[derive(PartialEq, Debug)] #[cfg_attr(target_family = "wasm", wasm_bindgen)] pub struct EATToken { diff --git a/nvidia-attest/src/types.rs b/nvidia-attest/src/types.rs index cc0b297a..fd396890 100644 --- a/nvidia-attest/src/types.rs +++ b/nvidia-attest/src/types.rs @@ -83,6 +83,9 @@ pub struct GpuClaims { #[serde(rename = "eat_nonce", deserialize_with = "hex::serde::deserialize")] pub eat_nonce: [u8; 32], + /// Universal entity id of the GPU + pub ueid: String, + /// Secure boot status. #[serde(default, skip_serializing_if = "Option::is_none")] pub secboot: Option, From ffb5aac3ddca317047c960cd8766dd125e4a1a4a Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:28:09 +0000 Subject: [PATCH 02/39] first azure tpm commit --- Cargo.lock | 314 ++++++++++++++++++++++++------- Cargo.toml | 2 +- azure-attest/Cargo.toml | 25 +++ azure-attest/README.md | 4 + azure-attest/src/bin/generate.rs | 127 +++++++++++++ azure-attest/src/lib.rs | 78 ++++++++ azure-attest/src/serde_cert.rs | 25 +++ 7 files changed, 502 insertions(+), 73 deletions(-) create mode 100644 azure-attest/Cargo.toml create mode 100644 azure-attest/README.md create mode 100644 azure-attest/src/bin/generate.rs create mode 100644 azure-attest/src/lib.rs create mode 100644 azure-attest/src/serde_cert.rs diff --git a/Cargo.lock b/Cargo.lock index 5c9e8fff..26e78fe6 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ name = "attestation-server" version = "0.1.0" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "configfs-tsm", "env_logger", "hex", @@ -226,6 +226,22 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "azure-attest" +version = "0.1.0" +dependencies = [ + "anyhow", + "libattest", + "rsa 0.10.0-rc.18", + "serde", + "sha2 0.11.0", + "snp-attest", + "tdx-attest", + "tpm2-protocol", + "tss-esapi", + "x509-cert 0.3.0-rc.4", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -233,10 +249,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "base64" -version = "0.21.7" +name = "base16ct" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64" @@ -276,12 +292,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bitfield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" - [[package]] name = "bitfield" version = "0.19.4" @@ -317,6 +327,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -465,6 +484,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" @@ -555,6 +580,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -585,6 +616,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97bb4a855e3b10f84c4e7e895a7de01db7f9a7b7eb7f73ed9773fd52ac686451" +dependencies = [ + "cpubits", + "ctutils", + "num-traits", + "rand_core 0.10.0", + "serdect", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -595,6 +640,34 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "crypto-primes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" +dependencies = [ + "crypto-bigint 0.7.4", + "rand_core 0.10.0", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -604,7 +677,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -773,12 +846,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid 0.9.6", - "crypto-common", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", +] + [[package]] name = "dirs" version = "6.0.0" @@ -824,10 +908,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der 0.7.10", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature", + "signature 2.2.0", "spki 0.7.3", ] @@ -837,8 +921,8 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", - "signature", + "pkcs8 0.10.2", + "signature 2.2.0", ] [[package]] @@ -850,7 +934,7 @@ dependencies = [ "curve25519-dalek", "ed25519", "serde", - "sha2", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -867,15 +951,15 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", - "digest", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", "ff", "generic-array", "group", "hkdf", "pem-rfc7468 0.7.0", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", "sec1", "subtle", @@ -1313,7 +1397,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1389,6 +1473,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -1472,7 +1565,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -1798,7 +1891,7 @@ version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ - "base64 0.22.1", + "base64", "ed25519-dalek", "getrandom 0.2.16", "hmac", @@ -1807,11 +1900,11 @@ dependencies = [ "p384", "pem", "rand 0.8.5", - "rsa", + "rsa 0.9.10", "serde", "serde_json", - "sha2", - "signature", + "sha2 0.10.9", + "signature 2.2.0", "simple_asn1", ] @@ -2149,7 +2242,7 @@ dependencies = [ "reqwest 0.12.28", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 2.0.18", "tokio", "wasm-bindgen", @@ -2277,7 +2370,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2289,7 +2382,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2344,7 +2437,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] @@ -2374,9 +2467,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "picky-asn1" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" +checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" dependencies = [ "oid", "serde", @@ -2385,9 +2478,9 @@ dependencies = [ [[package]] name = "picky-asn1-der" -version = "0.4.1" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" +checksum = "d413165e4bf7f808b9a27cbaba657657a2921f0965db833f488c4d4be96dcd2e" dependencies = [ "picky-asn1", "serde", @@ -2396,11 +2489,11 @@ dependencies = [ [[package]] name = "picky-asn1-x509" -version = "0.12.0" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" +checksum = "859d4117bd1b1dc5646359ee7243c50c5000c0920ea2d1fb120335a2f4c684b8" dependencies = [ - "base64 0.21.7", + "base64", "oid", "picky-asn1", "picky-asn1-der", @@ -2420,10 +2513,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der 0.7.10", - "pkcs8", + "pkcs8 0.10.2", "spki 0.7.3", ] +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2434,6 +2537,16 @@ dependencies = [ "spki 0.7.3", ] +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -2780,7 +2893,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -2822,7 +2935,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", @@ -2992,19 +3105,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid 0.9.6", - "digest", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", - "pkcs1", - "pkcs8", + "pkcs1 0.7.5", + "pkcs8 0.10.2", "rand_core 0.6.4", - "signature", + "signature 2.2.0", "spki 0.7.3", "subtle", "zeroize", ] +[[package]] +name = "rsa" +version = "0.10.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" +dependencies = [ + "const-oid 0.10.2", + "crypto-bigint 0.7.4", + "crypto-primes", + "digest 0.11.3", + "pkcs1 0.8.0-rc.4", + "pkcs8 0.11.0", + "rand_core 0.10.0", + "serde", + "serdect", + "signature 3.0.0", + "spki 0.8.0", + "zeroize", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3165,10 +3298,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", + "base16ct 0.2.0", "der 0.7.10", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -3296,13 +3429,23 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct 1.0.0", + "serde", +] + [[package]] name = "sev" version = "7.1.0" source = "git+https://github.com/BRA1L0R/sev?branch=prem#a0efc6be4c961cc614af1c2a11baa6fbce6c0f53" dependencies = [ - "base64 0.22.1", - "bitfield 0.19.4", + "base64", + "bitfield", "bitflags", "byteorder", "dirs", @@ -3311,11 +3454,11 @@ dependencies = [ "lazy_static", "libc", "p384", - "rsa", + "rsa 0.9.10", "serde", "serde-big-array", "serde_bytes", - "sha2", + "sha2 0.10.9", "static_assertions", "uuid", "x509-cert 0.2.5", @@ -3329,7 +3472,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -3363,10 +3517,20 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.0", +] + [[package]] name = "simple_asn1" version = "0.6.4" @@ -3403,12 +3567,12 @@ dependencies = [ "log", "p384", "reqwest 0.12.28", - "rsa", + "rsa 0.9.10", "serde", "serde_json", "sev", - "sha2", - "signature", + "sha2 0.10.9", + "signature 2.2.0", "thiserror 2.0.18", "tss-esapi", "url", @@ -3463,9 +3627,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.8.0-rc.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", "der 0.8.0", @@ -3586,9 +3750,9 @@ dependencies = [ "sec1", "serde", "serde_json", - "sha2", - "signature", - "spki 0.8.0-rc.4", + "sha2 0.10.9", + "signature 2.2.0", + "spki 0.8.0", "thiserror 2.0.18", "tokio", "urlencoding", @@ -3896,6 +4060,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tpm2-protocol" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75452b7cfe993c1867e27ccbc9cd9bc2f5608fc6f142b592448b5fa089cfa73d" + [[package]] name = "tracing" version = "0.1.44" @@ -3965,13 +4135,13 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tss-esapi" -version = "7.6.0" +version = "7.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ea9ccde878b029392ac97b5be1f470173d06ea41d18ad0bb3c92794c16a0f2" +checksum = "3f10b25a84912b894d0e6d68f4a3771c923e9c44ddaaed7920cde92ed28aa84e" dependencies = [ - "bitfield 0.14.0", + "bitfield", "enumflags2", - "getrandom 0.2.16", + "getrandom 0.4.2", "hostname-validator", "log", "mbox", @@ -3988,9 +4158,9 @@ dependencies = [ [[package]] name = "tss-esapi-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535cd192581c2ec4d5f82e670b1d3fbba6a23ccce8c85de387642051d7cad5b5" +checksum = "a7f972672926a3d3d18ecc04524720e4d20b7d1664a3fb73dbf7d4274196dbd9" dependencies = [ "pkg-config", "target-lexicon", @@ -3998,9 +4168,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ubyte" @@ -4760,7 +4930,7 @@ checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ "const-oid 0.9.6", "der 0.7.10", - "signature", + "signature 2.2.0", "spki 0.7.3", "tls_codec", ] @@ -4773,7 +4943,7 @@ checksum = "1e21aad3a769f25f3d2d0cbf30ea8b50a1d602354bd6ab687fad112821608ba6" dependencies = [ "const-oid 0.10.2", "der 0.8.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", "tls_codec", ] diff --git a/Cargo.toml b/Cargo.toml index 48360413..2626711d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["libattest", "nvidia-attest", "reticle", "snp-attest", "tdx-attest", "attestation-server"] +members = ["libattest", "nvidia-attest", "reticle", "snp-attest", "tdx-attest", "attestation-server", "azure-attest"] [patch.crates-io] sev = { git = "https://github.com/BRA1L0R/sev", branch = "prem" } diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml new file mode 100644 index 00000000..aac63963 --- /dev/null +++ b/azure-attest/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "azure-attest" +version = "0.1.0" +edition = "2024" + +[features] +cli = ["dep:tss-esapi", "dep:anyhow"] + +[dependencies] +tpm2-protocol = "0.16.0" +snp-attest = { path = "../snp-attest" } +tdx-attest = { path = "../tdx-attest" } +x509-cert = "0.3.0-rc.4" +rsa = { version = "0.10.0-rc.18", features = ["serde"] } +libattest = { path = "../libattest" } +serde = { version = "1.0.228", features = ["derive"] } +sha2 = "0.11.0" + +tss-esapi = { version = "7.7.0", optional = true } +anyhow = { version = "*", optional = true } + +[[bin]] +name = "generate-report" +path = "src/bin/generate.rs" +required-features = ["cli"] diff --git a/azure-attest/README.md b/azure-attest/README.md new file mode 100644 index 00000000..02c8357b --- /dev/null +++ b/azure-attest/README.md @@ -0,0 +1,4 @@ +vtpm2 verification consists of 3 different reads on the machine side: +- standard tpm 2.0 quote +- ak endorsement key from the host +- hardware report attesting the ak endorsement key diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs new file mode 100644 index 00000000..b9643231 --- /dev/null +++ b/azure-attest/src/bin/generate.rs @@ -0,0 +1,127 @@ +use libattest::error::Context; +use rsa::RsaPublicKey; +use tss_esapi::{ + abstraction::nv, + handles::{KeyHandle, NvIndexTpmHandle}, + interface_types::{algorithm::HashingAlgorithm, resource_handles::NvAuth}, + structures::{ + Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, Signature, SignatureScheme, + }, + tcti_ldr::{DeviceConfig, TctiNameConf}, +}; +use x509_cert::der::Decode; + +struct AzureTpm { + context: tss_esapi::Context, +} + +impl AzureTpm { + fn new(context: tss_esapi::Context) -> Self { + Self { context } + } + + const AK_CERT_IDX: u32 = 0x01C101D0; + const VTPM_HCL_AKPUB_PERSISTENT_HANDLE: u32 = 0x81000003; + + pub const VTPM_DEFAULT_PCR_SLOTS: [PcrSlot; 24] = [ + PcrSlot::Slot0, + PcrSlot::Slot1, + PcrSlot::Slot2, + PcrSlot::Slot3, + PcrSlot::Slot4, + PcrSlot::Slot5, + PcrSlot::Slot6, + PcrSlot::Slot7, + PcrSlot::Slot8, + PcrSlot::Slot9, + PcrSlot::Slot10, + PcrSlot::Slot11, + PcrSlot::Slot12, + PcrSlot::Slot13, + PcrSlot::Slot14, + PcrSlot::Slot15, + PcrSlot::Slot16, + PcrSlot::Slot17, + PcrSlot::Slot18, + PcrSlot::Slot19, + PcrSlot::Slot20, + PcrSlot::Slot21, + PcrSlot::Slot22, + PcrSlot::Slot23, + ]; + + fn tpm_read(&mut self, index: u32) -> Result, tss_esapi::Error> { + let handle = NvIndexTpmHandle::new(index)?; + let read = nv::read_full(&mut self.context, NvAuth::Owner, handle)?; + + Ok(read) + } + + pub fn ak_cert(&mut self) -> anyhow::Result { + let ak_cert = self + .tpm_read(Self::AK_CERT_IDX) + .context("unable to read ak_cert from tpm")?; + + let cert = x509_cert::Certificate::from_der(&ak_cert) + .context("unable to decode certificate from DER")?; + + Ok(cert) + } + + fn ak_handle(&mut self) -> Result { + let public_key_handle = self + .context + .tr_from_tpm_public(Self::VTPM_HCL_AKPUB_PERSISTENT_HANDLE.try_into()?)? + .into(); + + Ok(public_key_handle) + } + + pub fn ak(&mut self) -> anyhow::Result { + let public_key_handle = self.ak_handle()?; + let (public, _, _) = self.context.read_public(public_key_handle)?; + + let Public::Rsa { parameters, .. } = public else { + anyhow::bail!("received public key that was not rsa"); + }; + + let pk = rsa::RsaPublicKey::new( + u16::from(parameters.key_bits()).into(), + parameters.exponent().value().into(), + ) + .context("unable to decode public key components from tpm")?; + + Ok(pk) + } + + pub fn quote(&mut self, nonce: impl Into>) -> anyhow::Result<(Attest, Signature)> { + let key_handle = self.ak_handle()?; + + let pcr_list = PcrSelectionListBuilder::new() + .with_selection(HashingAlgorithm::Sha256, &Self::VTPM_DEFAULT_PCR_SLOTS) + .with_size_of_select(PcrSelectSize::default()) + .build() + .unwrap(); + + let (quote, signature) = self + .context + .quote( + key_handle, + nonce.into().try_into()?, + SignatureScheme::Null, + pcr_list, + ) + .context("unable to request quote from tpm")?; + + todo!() + } +} + +fn main() { + // ContextGap + let context = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default())).unwrap(); + let mut tpm = AzureTpm::new(context); + + let quote = tpm.quote([1, 2, 3, 4]).unwrap(); + println!("{quote:?}"); +} diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs new file mode 100644 index 00000000..a3ea3afc --- /dev/null +++ b/azure-attest/src/lib.rs @@ -0,0 +1,78 @@ +mod serde_cert; + +use rsa::signature::Verifier; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use snp_attest::ParsedAttestation; +use tdx_attest::TdxQuote; +use tpm2_protocol::{TpmMarshal, TpmUnmarshal, TpmWriter, data::TpmsAttest}; + +#[derive(Deserialize)] +enum HardwareReportType { + Tdx(Vec), + Sev(Vec), +} + +#[derive(Deserialize)] +pub struct AzureQuoteData { + quote: Vec, + hardware_report: HardwareReportType, + + trust: AzureTrust, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct AzureTrust { + quote_signature: rsa::pkcs1v15::Signature, + ak_key: rsa::pkcs1v15::VerifyingKey, + + #[serde(with = "serde_cert")] + ak_cert: x509_cert::Certificate, +} + +pub enum AzureHardwareReport { + Tdx(TdxQuote), + Sev(ParsedAttestation), +} + +pub struct AzureQuote { + quote: TpmsAttest, + hardware_report: AzureHardwareReport, + + trust: AzureTrust, +} + +impl AzureQuote { + pub fn parse(data: &AzureQuoteData) -> libattest::Result { + let (attestation, _) = TpmsAttest::unmarshal(&data.quote)?; + + let hardware_report = match &data.hardware_report { + HardwareReportType::Tdx(data) => AzureHardwareReport::Tdx(TdxQuote::from_bytes(data)?), + HardwareReportType::Sev(data) => { + AzureHardwareReport::Sev(ParsedAttestation::new(data)?) + } + }; + + Ok(Self { + quote: attestation, + hardware_report, + trust: data.trust.clone(), + }) + } + + pub async fn verify(&self) -> libattest::Result<()> { + let mut buffer = Box::new([0u8; 2048]); + + let mut writer = TpmWriter::new(buffer.as_mut_slice()); + self.quote.attested.marshal(&mut writer)?; + let written = writer.len(); + + let marshaled = &buffer[..written]; + + self.trust + .ak_key + .verify(marshaled, &self.trust.quote_signature)?; + + todo!() + } +} diff --git a/azure-attest/src/serde_cert.rs b/azure-attest/src/serde_cert.rs new file mode 100644 index 00000000..e5b73792 --- /dev/null +++ b/azure-attest/src/serde_cert.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Deserializer, Serializer}; +use x509_cert::der::{DecodePem, EncodePem}; + +pub fn serialize(certificate: &x509_cert::Certificate, serializer: S) -> Result +where + S: Serializer, +{ + let serialized = certificate + .to_pem(rsa::pkcs8::LineEnding::LF) + .map_err(::custom)?; + + serializer.serialize_str(&serialized) +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let pem = String::deserialize(deserializer)?; + + let certificate = + x509_cert::Certificate::from_pem(pem).map_err(::custom)?; + + Ok(certificate) +} From cf73e5d80ae06abfaa3b839bbb80fe7a6b6478db Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:41:17 +0000 Subject: [PATCH 03/39] initialize bin/generate with session --- azure-attest/src/bin/generate.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index b9643231..0f1f5f75 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -3,7 +3,9 @@ use rsa::RsaPublicKey; use tss_esapi::{ abstraction::nv, handles::{KeyHandle, NvIndexTpmHandle}, - interface_types::{algorithm::HashingAlgorithm, resource_handles::NvAuth}, + interface_types::{ + algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, + }, structures::{ Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, Signature, SignatureScheme, }, @@ -119,7 +121,11 @@ impl AzureTpm { fn main() { // ContextGap - let context = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default())).unwrap(); + let mut context = + tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default())).unwrap(); + + context.set_sessions((Some(AuthSession::Password), None, None)); + let mut tpm = AzureTpm::new(context); let quote = tpm.quote([1, 2, 3, 4]).unwrap(); From 1c8cb4f8e51e08a8368370beeb7b9534650bccf7 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:29:49 +0000 Subject: [PATCH 04/39] fix generate report command --- azure-attest/src/bin/generate.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 0f1f5f75..93f4f4a1 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -71,9 +71,11 @@ impl AzureTpm { } fn ak_handle(&mut self) -> Result { + let handle = Self::VTPM_HCL_AKPUB_PERSISTENT_HANDLE.try_into()?; + let public_key_handle = self .context - .tr_from_tpm_public(Self::VTPM_HCL_AKPUB_PERSISTENT_HANDLE.try_into()?)? + .execute_without_session(|ctx| ctx.tr_from_tpm_public(handle))? .into(); Ok(public_key_handle) @@ -96,7 +98,7 @@ impl AzureTpm { Ok(pk) } - pub fn quote(&mut self, nonce: impl Into>) -> anyhow::Result<(Attest, Signature)> { + pub fn quote(mut self, nonce: impl Into>) -> anyhow::Result<(Attest, Signature)> { let key_handle = self.ak_handle()?; let pcr_list = PcrSelectionListBuilder::new() @@ -105,7 +107,7 @@ impl AzureTpm { .build() .unwrap(); - let (quote, signature) = self + let res = self .context .quote( key_handle, @@ -115,7 +117,7 @@ impl AzureTpm { ) .context("unable to request quote from tpm")?; - todo!() + Ok(res) } } @@ -128,6 +130,9 @@ fn main() { let mut tpm = AzureTpm::new(context); - let quote = tpm.quote([1, 2, 3, 4]).unwrap(); - println!("{quote:?}"); + let cert = tpm.ak_cert().unwrap(); + let key = tpm.ak().unwrap(); + + let quote = tpm.quote([0u8; 32]).unwrap(); + println!("{cert:?} {key:?} {quote:?}"); } From d5b46aa8b824e2674037f21c41890ac7bcea9eaa Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:33:39 +0000 Subject: [PATCH 05/39] fix leading bytes error --- azure-attest/src/bin/generate.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 93f4f4a1..5134e3b4 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -11,7 +11,7 @@ use tss_esapi::{ }, tcti_ldr::{DeviceConfig, TctiNameConf}, }; -use x509_cert::der::Decode; +use x509_cert::der::{Decode, Reader, SliceReader}; struct AzureTpm { context: tss_esapi::Context, @@ -64,10 +64,13 @@ impl AzureTpm { .tpm_read(Self::AK_CERT_IDX) .context("unable to read ak_cert from tpm")?; - let cert = x509_cert::Certificate::from_der(&ak_cert) + let mut reader = SliceReader::new(&ak_cert)?; + let certificate = x509_cert::Certificate::decode(&mut reader) .context("unable to decode certificate from DER")?; - Ok(cert) + reader.finish().ok(); // ignore if there are leading bytes. x509_cert::Certificate::from_der returns error if that's the case + + Ok(certificate) } fn ak_handle(&mut self) -> Result { From 24161afc24426dababbb014edd66cce83d8064a8 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:47:31 +0000 Subject: [PATCH 06/39] fix public key creation --- azure-attest/src/bin/generate.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 5134e3b4..15e95566 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -1,7 +1,9 @@ +use std::ops::Deref; + use libattest::error::Context; -use rsa::RsaPublicKey; +use rsa::{BoxedUint, RsaPublicKey}; use tss_esapi::{ - abstraction::nv, + abstraction::{nv, public::DecodedKey}, handles::{KeyHandle, NvIndexTpmHandle}, interface_types::{ algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, @@ -86,14 +88,19 @@ impl AzureTpm { pub fn ak(&mut self) -> anyhow::Result { let public_key_handle = self.ak_handle()?; - let (public, _, _) = self.context.read_public(public_key_handle)?; + let (public, _, _) = self + .context + .execute_without_session(|ctx| ctx.read_public(public_key_handle))?; - let Public::Rsa { parameters, .. } = public else { + let Public::Rsa { + unique, parameters, .. + } = public + else { anyhow::bail!("received public key that was not rsa"); }; let pk = rsa::RsaPublicKey::new( - u16::from(parameters.key_bits()).into(), + BoxedUint::from_be_slice_vartime(unique.deref()), parameters.exponent().value().into(), ) .context("unable to decode public key components from tpm")?; From 12f78bd76c47810e406d6909ccf7de9a12bd51e0 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:50:08 +0000 Subject: [PATCH 07/39] fix cryptographic exponent --- azure-attest/src/bin/generate.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 15e95566..79bda4b6 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -9,7 +9,8 @@ use tss_esapi::{ algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, }, structures::{ - Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, Signature, SignatureScheme, + Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, RsaExponent, Signature, + SignatureScheme, }, tcti_ldr::{DeviceConfig, TctiNameConf}, }; @@ -99,9 +100,14 @@ impl AzureTpm { anyhow::bail!("received public key that was not rsa"); }; + let exponent = match parameters.exponent() { + RsaExponent::ZERO_EXPONENT => 65537, + exp => exp.value(), + }; + let pk = rsa::RsaPublicKey::new( BoxedUint::from_be_slice_vartime(unique.deref()), - parameters.exponent().value().into(), + exponent.into(), ) .context("unable to decode public key components from tpm")?; From 8304a261010af0e67d7d12ad7dbc4b2929f0d847 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:35:41 +0000 Subject: [PATCH 08/39] add report parsing --- Cargo.lock | 4 + azure-attest/Cargo.toml | 4 + azure-attest/src/bin/generate.rs | 14 +- azure-attest/src/lib.rs | 1 + azure-attest/src/report.rs | 311 +++++++++++++++++++++++++++++++ 5 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 azure-attest/src/report.rs diff --git a/Cargo.lock b/Cargo.lock index 26e78fe6..471978d3 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,15 +231,19 @@ name = "azure-attest" version = "0.1.0" dependencies = [ "anyhow", + "jsonwebtoken", "libattest", "rsa 0.10.0-rc.18", "serde", + "serde_json", "sha2 0.11.0", "snp-attest", "tdx-attest", + "thiserror 2.0.18", "tpm2-protocol", "tss-esapi", "x509-cert 0.3.0-rc.4", + "zerocopy", ] [[package]] diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index aac63963..c540db6c 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -13,8 +13,12 @@ tdx-attest = { path = "../tdx-attest" } x509-cert = "0.3.0-rc.4" rsa = { version = "0.10.0-rc.18", features = ["serde"] } libattest = { path = "../libattest" } +jsonwebtoken = { version = "10.3.0", default-features = false } serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" sha2 = "0.11.0" +thiserror = "2.0.17" +zerocopy = { version = "0.8.48", features = ["derive"] } tss-esapi = { version = "7.7.0", optional = true } anyhow = { version = "*", optional = true } diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 79bda4b6..c76cdf3a 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -1,5 +1,6 @@ use std::ops::Deref; +use azure_attest::report::AttestationReport; use libattest::error::Context; use rsa::{BoxedUint, RsaPublicKey}; use tss_esapi::{ @@ -27,6 +28,7 @@ impl AzureTpm { const AK_CERT_IDX: u32 = 0x01C101D0; const VTPM_HCL_AKPUB_PERSISTENT_HANDLE: u32 = 0x81000003; + const HARDWARE_REPORT_IDX: u32 = 0x01400001; pub const VTPM_DEFAULT_PCR_SLOTS: [PcrSlot; 24] = [ PcrSlot::Slot0, @@ -62,6 +64,13 @@ impl AzureTpm { Ok(read) } + pub fn hardware_report(&mut self) -> anyhow::Result { + let read = self.tpm_read(Self::HARDWARE_REPORT_IDX)?; + let report = azure_attest::report::deserialize_attestation_report(&read)?; + + Ok(report) + } + pub fn ak_cert(&mut self) -> anyhow::Result { let ak_cert = self .tpm_read(Self::AK_CERT_IDX) @@ -148,7 +157,8 @@ fn main() { let cert = tpm.ak_cert().unwrap(); let key = tpm.ak().unwrap(); - + let report = tpm.hardware_report().unwrap(); let quote = tpm.quote([0u8; 32]).unwrap(); - println!("{cert:?} {key:?} {quote:?}"); + + println!("{report:?} {cert:?} {key:?} {quote:?}"); } diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index a3ea3afc..2c3b3536 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -1,3 +1,4 @@ +pub mod report; mod serde_cert; use rsa::signature::Verifier; diff --git a/azure-attest/src/report.rs b/azure-attest/src/report.rs new file mode 100644 index 00000000..ee83d3de --- /dev/null +++ b/azure-attest/src/report.rs @@ -0,0 +1,311 @@ +//! Azure confidential VM attestation report types. + +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256, Sha384, Sha512}; +use thiserror::Error; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; + +pub use jsonwebtoken::jwk::Jwk; + +/// Fixed Azure-defined attestation report header. +#[repr(C, packed)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Serialize, + Deserialize, + FromBytes, + IntoBytes, + KnownLayout, + Immutable, + Unaligned, +)] +pub struct AttestationReportHeader { + /// Embedded signature. Expected value: [`ATTESTATION_REPORT_SIGNATURE`]. + pub signature: u32, + /// Format version. Expected value: [`ATTESTATION_REPORT_VERSION`]. + pub version: u32, + /// Size of the Azure-defined attestation report. + pub report_size: u32, + /// Azure-specific usage. Expected value: [`ATTESTATION_REPORT_REQUEST_TYPE`]. + pub request_type: u32, + /// Reserved status field. + pub status: u32, + /// Reserved bytes. + pub reserved: [u8; 12], +} + +impl AttestationReportHeader { + /// Azure attestation report magic value for ASCII `HCLA`. + pub const ATTESTATION_REPORT_SIGNATURE: u32 = 0x414c_4348; + /// Azure attestation report header version. + pub const ATTESTATION_REPORT_VERSION: u32 = 2; + /// Azure attestation report request type. + pub const ATTESTATION_REPORT_REQUEST_TYPE: u32 = 2; + /// Azure attestation report header size in bytes. + pub const ATTESTATION_REPORT_HEADER_SIZE: usize = 32; + /// Azure hardware report payload size in bytes. + pub const REPORT_PAYLOAD_SIZE: usize = 1184; + /// AMD SEV-SNP hardware report size in bytes. + pub const SEV_REPORT_PAYLOAD_SIZE: usize = 1184; + /// Intel TDX hardware report size in bytes. + pub const TDX_REPORT_PAYLOAD_SIZE: usize = 1024; + /// Offset of runtime data in the Azure report. + pub const RUNTIME_DATA_OFFSET: usize = + Self::ATTESTATION_REPORT_HEADER_SIZE + Self::REPORT_PAYLOAD_SIZE; +} + +/// Fixed runtime data header. +#[repr(C, packed)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Serialize, + Deserialize, + FromBytes, + IntoBytes, + KnownLayout, + Immutable, + Unaligned, +)] +pub struct RuntimeDataHeader { + /// Size of runtime data, including this header. + pub data_size: u32, + /// Format version. Expected value: [`RuntimeDataHeader::RUNTIME_DATA_VERSION`]. + pub version: u32, + /// Hardware report type. + pub report_type: u32, + /// Runtime claims hash algorithm. + pub hash_type: u32, + /// Runtime claims JSON size. + pub claim_size: u32, +} + +impl RuntimeDataHeader { + /// Azure runtime data header size in bytes. + pub const RUNTIME_DATA_HEADER_SIZE: usize = 20; + /// Azure runtime data version. + pub const RUNTIME_DATA_VERSION: u32 = 1; + /// AMD SEV-SNP hardware report type. + pub const REPORT_TYPE_SEV: u32 = 2; + /// Intel TDX hardware report type. + pub const REPORT_TYPE_TDX: u32 = 4; + /// SHA-256 runtime claims hash type. + pub const HASH_TYPE_SHA256: u32 = 1; + /// SHA-384 runtime claims hash type. + pub const HASH_TYPE_SHA384: u32 = 2; + /// SHA-512 runtime claims hash type. + pub const HASH_TYPE_SHA512: u32 = 3; +} + +/// Owned Azure attestation report. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AttestationReport { + /// Fixed Azure-defined report header. + pub header: AttestationReportHeader, + /// Hardware report payload. + pub payload: HardwareReport, + /// Runtime data and runtime claims. + pub runtime: RuntimeData, +} + +impl AttestationReport { + /// Hashes the measured runtime claims JSON using the algorithm declared by + /// [`RuntimeDataHeader::hash_type`]. + pub fn hash_runtime_data(&self) -> Result, AttestationReportError> { + match self.runtime.header.hash_type { + RuntimeDataHeader::HASH_TYPE_SHA256 => { + Ok(Sha256::digest(&self.runtime.claims_json).to_vec()) + } + RuntimeDataHeader::HASH_TYPE_SHA384 => { + Ok(Sha384::digest(&self.runtime.claims_json).to_vec()) + } + RuntimeDataHeader::HASH_TYPE_SHA512 => { + Ok(Sha512::digest(&self.runtime.claims_json).to_vec()) + } + hash_type => Err(AttestationReportError::UnsupportedHashType(hash_type)), + } + } +} + +/// Owned hardware report payload. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum HardwareReport { + /// Intel TDX hardware report payload. + Tdx(Vec), + /// AMD SEV-SNP hardware report payload. + Sev(Vec), +} + +/// Owned runtime data. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimeData { + /// Fixed runtime data header. + pub header: RuntimeDataHeader, + /// Raw runtime claims JSON bytes. + pub claims_json: Vec, +} + +impl RuntimeData { + /// Deserializes runtime claims from [`RuntimeData::claims_json`]. + pub fn claims(&self) -> Result { + serde_json::from_slice(&self.claims_json).map_err(AttestationReportError::from) + } +} + +/// Measured JSON runtime claims. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimeClaims { + /// vTPM public keys in JWK format. + pub keys: Vec, + /// Selective Azure confidential VM configuration. + #[serde(rename = "vm-configuration")] + pub vm_configuration: VmConfiguration, + /// 64-byte hex string read from TPM NV index `0x01400002`. + #[serde(rename = "user-data")] + pub user_data: String, +} + +/// Selective Azure confidential VM configuration runtime claim. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct VmConfiguration { + /// Root certificate thumbprint, when configured. + #[serde(rename = "root-cert-thumbprint")] + pub root_cert_thumbprint: String, + /// Whether VM console access is enabled. + #[serde(rename = "console-enabled")] + pub console_enabled: bool, + /// Whether secure boot is enabled. + #[serde(rename = "secure-boot")] + pub secure_boot: bool, + /// Whether TPM is enabled. + #[serde(rename = "tpm-enabled")] + pub tpm_enabled: bool, + /// Whether TPM state is persisted. + #[serde(rename = "tpm-persisted")] + pub tpm_persisted: bool, + /// Azure VM unique ID. + #[serde(rename = "vmUniqueId")] + pub vm_unique_id: String, +} + +/// Deserializes an owned Azure attestation report from TPM NV index bytes. +pub fn deserialize_attestation_report( + bytes: &[u8], +) -> Result { + let header_bytes = bytes + .get(..AttestationReportHeader::ATTESTATION_REPORT_HEADER_SIZE) + .ok_or(AttestationReportError::Truncated { + needed: AttestationReportHeader::ATTESTATION_REPORT_HEADER_SIZE, + actual: bytes.len(), + })?; + let header = *AttestationReportHeader::ref_from_bytes(header_bytes) + .map_err(|_| AttestationReportError::InvalidLayout("attestation report header"))?; + + let report_size = header.report_size as usize; + if bytes.len() < report_size { + return Err(AttestationReportError::Truncated { + needed: report_size, + actual: bytes.len(), + }); + } + + let report = &bytes[..report_size]; + let payload_bytes = report + .get( + AttestationReportHeader::ATTESTATION_REPORT_HEADER_SIZE + ..AttestationReportHeader::RUNTIME_DATA_OFFSET, + ) + .ok_or(AttestationReportError::Truncated { + needed: AttestationReportHeader::RUNTIME_DATA_OFFSET, + actual: report.len(), + })?; + + let runtime = + deserialize_runtime_data(&report[AttestationReportHeader::RUNTIME_DATA_OFFSET..])?; + let payload = match runtime.header.report_type { + RuntimeDataHeader::REPORT_TYPE_TDX => HardwareReport::Tdx( + payload_bytes[..AttestationReportHeader::TDX_REPORT_PAYLOAD_SIZE].to_vec(), + ), + RuntimeDataHeader::REPORT_TYPE_SEV => HardwareReport::Sev( + payload_bytes[..AttestationReportHeader::SEV_REPORT_PAYLOAD_SIZE].to_vec(), + ), + report_type => return Err(AttestationReportError::UnsupportedReportType(report_type)), + }; + + Ok(AttestationReport { + header, + payload, + runtime, + }) +} + +fn deserialize_runtime_data(bytes: &[u8]) -> Result { + let header_bytes = bytes + .get(..RuntimeDataHeader::RUNTIME_DATA_HEADER_SIZE) + .ok_or(AttestationReportError::Truncated { + needed: RuntimeDataHeader::RUNTIME_DATA_HEADER_SIZE, + actual: bytes.len(), + })?; + let header = *RuntimeDataHeader::ref_from_bytes(header_bytes) + .map_err(|_| AttestationReportError::InvalidLayout("runtime data header"))?; + + let data_size = header.data_size as usize; + if bytes.len() < data_size { + return Err(AttestationReportError::Truncated { + needed: data_size, + actual: bytes.len(), + }); + } + + let runtime_data = &bytes[..data_size]; + let claims_start = RuntimeDataHeader::RUNTIME_DATA_HEADER_SIZE; + let claims_end = claims_start + .checked_add(header.claim_size as usize) + .ok_or(AttestationReportError::SizeOverflow)?; + let claims_json = runtime_data + .get(claims_start..claims_end) + .ok_or(AttestationReportError::Truncated { + needed: claims_end, + actual: runtime_data.len(), + })? + .to_vec(); + Ok(RuntimeData { + header, + claims_json, + }) +} + +/// Azure attestation report deserialization error. +#[derive(Debug, Error)] +pub enum AttestationReportError { + /// The input ended before the requested section. + #[error("truncated attestation report: needed {needed} bytes, got {actual}")] + Truncated { + /// Required byte count. + needed: usize, + /// Actual byte count. + actual: usize, + }, + /// Bytes could not be viewed as the requested fixed layout. + #[error("invalid {0} layout")] + InvalidLayout(&'static str), + /// Unsupported hardware report type. + #[error("unsupported hardware report type: {0}")] + UnsupportedReportType(u32), + /// Unsupported runtime hash type. + #[error("unsupported runtime hash type: {0}")] + UnsupportedHashType(u32), + /// Integer overflow while calculating a section boundary. + #[error("section size calculation overflowed")] + SizeOverflow, + /// Runtime claims JSON could not be deserialized. + #[error("invalid runtime claims: {0}")] + RuntimeClaims(#[from] serde_json::Error), +} From 7cb0675f6038d1919bfbccdd32fa2cbfa19a1804 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:25:01 +0000 Subject: [PATCH 09/39] build azure attestation structure binside --- azure-attest/src/bin/generate.rs | 60 ++++++++++++++++++---------- azure-attest/src/lib.rs | 68 ++++++++++++++++---------------- azure-attest/src/serde_tpm.rs | 34 ++++++++++++++++ 3 files changed, 108 insertions(+), 54 deletions(-) create mode 100644 azure-attest/src/serde_tpm.rs diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index c76cdf3a..c7afb081 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -1,10 +1,12 @@ use std::ops::Deref; -use azure_attest::report::AttestationReport; +use azure_attest::{AzureQuote, AzureTrust, report::AttestationReport}; use libattest::error::Context; -use rsa::{BoxedUint, RsaPublicKey}; +use rsa::{BoxedUint, pkcs1v15::VerifyingKey}; +use sha2::Sha256; +use tpm2_protocol::{TpmUnmarshal, data::TpmsAttest}; use tss_esapi::{ - abstraction::{nv, public::DecodedKey}, + abstraction::nv, handles::{KeyHandle, NvIndexTpmHandle}, interface_types::{ algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, @@ -14,8 +16,9 @@ use tss_esapi::{ SignatureScheme, }, tcti_ldr::{DeviceConfig, TctiNameConf}, + traits::Marshall, }; -use x509_cert::der::{Decode, Reader, SliceReader}; +use x509_cert::der::{Decode, SliceReader}; struct AzureTpm { context: tss_esapi::Context, @@ -77,12 +80,10 @@ impl AzureTpm { .context("unable to read ak_cert from tpm")?; let mut reader = SliceReader::new(&ak_cert)?; - let certificate = x509_cert::Certificate::decode(&mut reader) - .context("unable to decode certificate from DER")?; - reader.finish().ok(); // ignore if there are leading bytes. x509_cert::Certificate::from_der returns error if that's the case - - Ok(certificate) + x509_cert::Certificate::decode(&mut reader) + .context("unable to decode certificate from DER") + .map_err(Into::into) } fn ak_handle(&mut self) -> Result { @@ -96,7 +97,7 @@ impl AzureTpm { Ok(public_key_handle) } - pub fn ak(&mut self) -> anyhow::Result { + pub fn ak(&mut self) -> anyhow::Result> { let public_key_handle = self.ak_handle()?; let (public, _, _) = self .context @@ -114,13 +115,13 @@ impl AzureTpm { exp => exp.value(), }; - let pk = rsa::RsaPublicKey::new( + rsa::RsaPublicKey::new( BoxedUint::from_be_slice_vartime(unique.deref()), exponent.into(), ) - .context("unable to decode public key components from tpm")?; - - Ok(pk) + .context("unable to decode public key components from tpm") + .map(VerifyingKey::new) + .map_err(Into::into) } pub fn quote(mut self, nonce: impl Into>) -> anyhow::Result<(Attest, Signature)> { @@ -146,6 +147,29 @@ impl AzureTpm { } } +fn build_azure_attestation(mut tpm: AzureTpm) -> libattest::Result { + let cert = tpm.ak_cert().unwrap(); + let key = tpm.ak().unwrap(); + let report = tpm.hardware_report().unwrap(); + let (attest, signature) = tpm.quote([0u8; 32]).unwrap(); + + // convert tpm structure to wire compatible format + let marshaled = attest.marshall()?; + let (attest, _) = TpmsAttest::unmarshal(&marshaled)?; + + let signature = match signature { + Signature::RsaSsa(ref signature) => signature.signature().value(), + _ => libattest::bail!("unsupported signature algorithm"), + }; + + let signature = rsa::pkcs1v15::Signature::try_from(signature)?; + + let azure_trust = AzureTrust::new(signature, key, cert); + let azure_quote = AzureQuote::new(attest, report, azure_trust); + + Ok(azure_quote) +} + fn main() { // ContextGap let mut context = @@ -154,11 +178,7 @@ fn main() { context.set_sessions((Some(AuthSession::Password), None, None)); let mut tpm = AzureTpm::new(context); + let attestation = build_azure_attestation(tpm).unwrap(); - let cert = tpm.ak_cert().unwrap(); - let key = tpm.ak().unwrap(); - let report = tpm.hardware_report().unwrap(); - let quote = tpm.quote([0u8; 32]).unwrap(); - - println!("{report:?} {cert:?} {key:?} {quote:?}"); + println!("{attestation:?}"); } diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 2c3b3536..8d285e57 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -1,28 +1,15 @@ pub mod report; mod serde_cert; +mod serde_tpm; use rsa::signature::Verifier; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use snp_attest::ParsedAttestation; -use tdx_attest::TdxQuote; use tpm2_protocol::{TpmMarshal, TpmUnmarshal, TpmWriter, data::TpmsAttest}; -#[derive(Deserialize)] -enum HardwareReportType { - Tdx(Vec), - Sev(Vec), -} - -#[derive(Deserialize)] -pub struct AzureQuoteData { - quote: Vec, - hardware_report: HardwareReportType, - - trust: AzureTrust, -} +use crate::report::AttestationReport; -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct AzureTrust { quote_signature: rsa::pkcs1v15::Signature, ak_key: rsa::pkcs1v15::VerifyingKey, @@ -31,36 +18,49 @@ pub struct AzureTrust { ak_cert: x509_cert::Certificate, } -pub enum AzureHardwareReport { - Tdx(TdxQuote), - Sev(ParsedAttestation), +impl AzureTrust { + pub fn new( + quote_signature: rsa::pkcs1v15::Signature, + ak_key: rsa::pkcs1v15::VerifyingKey, + ak_cert: x509_cert::Certificate, + ) -> Self { + Self { + quote_signature, + ak_key, + ak_cert, + } + } } +#[derive(Serialize, Deserialize, Debug)] pub struct AzureQuote { + #[serde(with = "serde_tpm")] quote: TpmsAttest, - hardware_report: AzureHardwareReport, + + hardware_report: AttestationReport, trust: AzureTrust, } impl AzureQuote { - pub fn parse(data: &AzureQuoteData) -> libattest::Result { - let (attestation, _) = TpmsAttest::unmarshal(&data.quote)?; - - let hardware_report = match &data.hardware_report { - HardwareReportType::Tdx(data) => AzureHardwareReport::Tdx(TdxQuote::from_bytes(data)?), - HardwareReportType::Sev(data) => { - AzureHardwareReport::Sev(ParsedAttestation::new(data)?) - } - }; - - Ok(Self { - quote: attestation, + pub fn new(quote: TpmsAttest, hardware_report: AttestationReport, trust: AzureTrust) -> Self { + Self { + quote, hardware_report, - trust: data.trust.clone(), - }) + trust, + } } + // pub fn parse(data: &AzureQuoteData) -> libattest::Result { + // let (attestation, _) = TpmsAttest::unmarshal(&data.quote)?; + + // Ok(Self { + // quote: attestation, + // hardware_report: data.hardware_report.clone(), + // trust: data.trust.clone(), + // }) + // } + pub async fn verify(&self) -> libattest::Result<()> { let mut buffer = Box::new([0u8; 2048]); diff --git a/azure-attest/src/serde_tpm.rs b/azure-attest/src/serde_tpm.rs new file mode 100644 index 00000000..8201f5d6 --- /dev/null +++ b/azure-attest/src/serde_tpm.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use tpm2_protocol::{TpmMarshal, TpmSized, TpmUnmarshal, TpmWriter}; + +pub fn serialize(obj: &T, serializer: S) -> Result +where + S: Serializer, + T: TpmMarshal + TpmSized, +{ + let mut buf = vec![0; T::SIZE]; + let mut writer = TpmWriter::new(&mut buf); + + obj.marshal(&mut writer) + .map_err(::custom)?; + + buf.serialize(serializer) +} + +pub fn deserialize<'de, T, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: TpmUnmarshal + TpmSized, +{ + let buf = Vec::::deserialize(deserializer)?; + + if buf.len() != T::SIZE { + return Err(serde::de::Error::custom( + "too many bytes to deserialize tpm type", + )); + } + + let (unmarshaled, _) = T::unmarshal(&buf).map_err(::custom)?; + + Ok(unmarshaled) +} From 6540749aec069b22ca59a23f533037ce621c7c6c Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:37:49 +0000 Subject: [PATCH 10/39] output tpm in json --- azure-attest/Cargo.toml | 4 +++- azure-attest/src/bin/generate.rs | 2 ++ azure-attest/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index c540db6c..94555b18 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -15,14 +15,16 @@ rsa = { version = "0.10.0-rc.18", features = ["serde"] } libattest = { path = "../libattest" } jsonwebtoken = { version = "10.3.0", default-features = false } serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" sha2 = "0.11.0" +serde_json = "1.0.149" thiserror = "2.0.17" zerocopy = { version = "0.8.48", features = ["derive"] } tss-esapi = { version = "7.7.0", optional = true } anyhow = { version = "*", optional = true } +[dev-dependencies] + [[bin]] name = "generate-report" path = "src/bin/generate.rs" diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index c7afb081..dedc0d44 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -180,5 +180,7 @@ fn main() { let mut tpm = AzureTpm::new(context); let attestation = build_azure_attestation(tpm).unwrap(); + let attestation = serde_json::to_string(&attestation).unwrap(); + println!("{attestation:?}"); } diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 8d285e57..577cb165 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -61,7 +61,7 @@ impl AzureQuote { // }) // } - pub async fn verify(&self) -> libattest::Result<()> { + pub fn verify(&self) -> libattest::Result<()> { let mut buffer = Box::new([0u8; 2048]); let mut writer = TpmWriter::new(buffer.as_mut_slice()); From 9e12a1f23e4df9084d3bd5de22569909a96f161f Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:46:16 +0000 Subject: [PATCH 11/39] fix json output --- azure-attest/src/bin/generate.rs | 4 ++-- azure-attest/test/attestation.json | 0 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 azure-attest/test/attestation.json diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index dedc0d44..257019ae 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -177,10 +177,10 @@ fn main() { context.set_sessions((Some(AuthSession::Password), None, None)); - let mut tpm = AzureTpm::new(context); + let tpm = AzureTpm::new(context); let attestation = build_azure_attestation(tpm).unwrap(); let attestation = serde_json::to_string(&attestation).unwrap(); - println!("{attestation:?}"); + println!("{attestation}"); } diff --git a/azure-attest/test/attestation.json b/azure-attest/test/attestation.json new file mode 100644 index 00000000..e69de29b From dab63c725622bfc6d3898e861c47816f1feeb2d0 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:22:51 +0000 Subject: [PATCH 12/39] progress towards tpm verification --- azure-attest/README.md | 2 +- azure-attest/src/bin/generate.rs | 9 +- azure-attest/src/lib.rs | 23 +- azure-attest/test/attestation.json | 0 azure-attest/tests/attestation.json | 6794 +++++++++++++++++++++++++++ azure-attest/tests/decode.rs | 12 + 6 files changed, 6829 insertions(+), 11 deletions(-) delete mode 100644 azure-attest/test/attestation.json create mode 100644 azure-attest/tests/attestation.json create mode 100644 azure-attest/tests/decode.rs diff --git a/azure-attest/README.md b/azure-attest/README.md index 02c8357b..90f5aa9a 100644 --- a/azure-attest/README.md +++ b/azure-attest/README.md @@ -1,4 +1,4 @@ vtpm2 verification consists of 3 different reads on the machine side: - standard tpm 2.0 quote -- ak endorsement key from the host +- ak endorsement key and ak certificate from the host - hardware report attesting the ak endorsement key diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 257019ae..40545824 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -147,11 +147,14 @@ impl AzureTpm { } } -fn build_azure_attestation(mut tpm: AzureTpm) -> libattest::Result { +fn build_azure_attestation( + mut tpm: AzureTpm, + nonce: &[u8; 32], +) -> libattest::Result { let cert = tpm.ak_cert().unwrap(); let key = tpm.ak().unwrap(); let report = tpm.hardware_report().unwrap(); - let (attest, signature) = tpm.quote([0u8; 32]).unwrap(); + let (attest, signature) = tpm.quote(nonce).unwrap(); // convert tpm structure to wire compatible format let marshaled = attest.marshall()?; @@ -178,7 +181,7 @@ fn main() { context.set_sessions((Some(AuthSession::Password), None, None)); let tpm = AzureTpm::new(context); - let attestation = build_azure_attestation(tpm).unwrap(); + let attestation = build_azure_attestation(tpm, &[0u8; 32]).unwrap(); let attestation = serde_json::to_string(&attestation).unwrap(); diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 577cb165..56494c53 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -4,8 +4,11 @@ mod serde_tpm; use rsa::signature::Verifier; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use tpm2_protocol::{TpmMarshal, TpmUnmarshal, TpmWriter, data::TpmsAttest}; +use sha2::Sha256; +use tpm2_protocol::{ + TpmMarshal, TpmWriter, + data::{TpmsAttest, TpmuAttest}, +}; use crate::report::AttestationReport; @@ -36,7 +39,6 @@ impl AzureTrust { pub struct AzureQuote { #[serde(with = "serde_tpm")] quote: TpmsAttest, - hardware_report: AttestationReport, trust: AzureTrust, @@ -53,7 +55,6 @@ impl AzureQuote { // pub fn parse(data: &AzureQuoteData) -> libattest::Result { // let (attestation, _) = TpmsAttest::unmarshal(&data.quote)?; - // Ok(Self { // quote: attestation, // hardware_report: data.hardware_report.clone(), @@ -62,10 +63,10 @@ impl AzureQuote { // } pub fn verify(&self) -> libattest::Result<()> { - let mut buffer = Box::new([0u8; 2048]); + let mut buffer = Box::new([0u8; 512]); let mut writer = TpmWriter::new(buffer.as_mut_slice()); - self.quote.attested.marshal(&mut writer)?; + self.quote.marshal(&mut writer)?; let written = writer.len(); let marshaled = &buffer[..written]; @@ -74,6 +75,14 @@ impl AzureQuote { .ak_key .verify(marshaled, &self.trust.quote_signature)?; - todo!() + let TpmuAttest::Quote(ref quote) = self.quote.attested else { + libattest::bail!("wrong type of attestation in TpmsQuote"); + }; + + Ok(()) } } + +// struct AzureReport { + +// } diff --git a/azure-attest/test/attestation.json b/azure-attest/test/attestation.json deleted file mode 100644 index e69de29b..00000000 diff --git a/azure-attest/tests/attestation.json b/azure-attest/tests/attestation.json new file mode 100644 index 00000000..ebd7f460 --- /dev/null +++ b/azure-attest/tests/attestation.json @@ -0,0 +1,6794 @@ +{ + "quote": [ + 255, + 84, + 67, + 71, + 128, + 24, + 0, + 34, + 0, + 11, + 90, + 200, + 132, + 215, + 172, + 123, + 199, + 249, + 34, + 156, + 61, + 105, + 73, + 243, + 46, + 248, + 147, + 122, + 70, + 5, + 81, + 194, + 38, + 251, + 244, + 37, + 115, + 89, + 54, + 86, + 77, + 144, + 0, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 18, + 67, + 14, + 102, + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 0, + 1, + 32, + 32, + 3, + 18, + 0, + 18, + 0, + 3, + 0, + 0, + 0, + 1, + 0, + 11, + 3, + 255, + 255, + 255, + 0, + 32, + 192, + 56, + 127, + 196, + 203, + 67, + 219, + 26, + 10, + 250, + 113, + 156, + 77, + 175, + 141, + 77, + 232, + 134, + 112, + 76, + 208, + 54, + 195, + 208, + 205, + 39, + 240, + 0, + 92, + 45, + 41, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "hardware_report": { + "header": { + "signature": 1095517000, + "version": 1, + "report_size": 2346, + "request_type": 2, + "status": 0, + "reserved": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "payload": { + "Sev": [ + 3, + 0, + 0, + 0, + 10, + 0, + 0, + 0, + 31, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 2, + 33, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 24, + 219, + 37, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 33, + 172, + 119, + 120, + 188, + 125, + 93, + 10, + 240, + 105, + 106, + 67, + 155, + 55, + 254, + 153, + 250, + 92, + 251, + 75, + 60, + 43, + 233, + 18, + 174, + 160, + 100, + 210, + 140, + 218, + 120, + 128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65, + 247, + 127, + 229, + 193, + 65, + 99, + 67, + 248, + 77, + 190, + 237, + 237, + 80, + 78, + 180, + 162, + 196, + 80, + 134, + 19, + 23, + 237, + 62, + 78, + 70, + 205, + 119, + 28, + 121, + 36, + 58, + 76, + 190, + 179, + 215, + 94, + 198, + 99, + 230, + 167, + 164, + 123, + 209, + 244, + 250, + 181, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 148, + 47, + 217, + 62, + 189, + 230, + 234, + 122, + 150, + 239, + 173, + 234, + 252, + 96, + 241, + 198, + 179, + 209, + 14, + 112, + 59, + 29, + 175, + 215, + 85, + 91, + 146, + 247, + 243, + 211, + 45, + 14, + 0, + 103, + 103, + 100, + 140, + 186, + 91, + 16, + 42, + 243, + 214, + 87, + 86, + 175, + 65, + 119, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 52, + 124, + 217, + 237, + 167, + 213, + 98, + 123, + 128, + 33, + 79, + 174, + 14, + 154, + 111, + 89, + 114, + 115, + 223, + 188, + 45, + 93, + 202, + 53, + 37, + 189, + 180, + 238, + 165, + 146, + 117, + 83, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 4, + 0, + 0, + 0, + 0, + 0, + 24, + 219, + 25, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9, + 39, + 161, + 223, + 114, + 74, + 200, + 211, + 37, + 190, + 82, + 111, + 118, + 72, + 52, + 240, + 184, + 239, + 254, + 204, + 152, + 8, + 40, + 186, + 141, + 113, + 177, + 205, + 121, + 121, + 0, + 145, + 22, + 63, + 59, + 199, + 218, + 113, + 51, + 139, + 35, + 51, + 61, + 211, + 36, + 7, + 108, + 33, + 0, + 64, + 23, + 32, + 74, + 246, + 208, + 154, + 117, + 212, + 173, + 155, + 70, + 151, + 51, + 229, + 4, + 0, + 0, + 0, + 0, + 0, + 24, + 219, + 29, + 55, + 1, + 0, + 29, + 55, + 1, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 24, + 219, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 22, + 208, + 158, + 143, + 77, + 80, + 177, + 128, + 75, + 44, + 80, + 112, + 159, + 149, + 152, + 83, + 223, + 108, + 4, + 41, + 233, + 117, + 29, + 145, + 2, + 181, + 217, + 212, + 220, + 14, + 241, + 96, + 165, + 59, + 192, + 80, + 36, + 24, + 34, + 154, + 175, + 49, + 41, + 8, + 68, + 12, + 195, + 126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 253, + 124, + 152, + 42, + 241, + 56, + 26, + 29, + 232, + 84, + 141, + 172, + 201, + 173, + 54, + 118, + 115, + 53, + 167, + 164, + 221, + 201, + 75, + 8, + 121, + 140, + 138, + 19, + 79, + 215, + 230, + 221, + 12, + 163, + 174, + 1, + 221, + 102, + 95, + 79, + 41, + 26, + 6, + 196, + 143, + 25, + 204, + 253, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "runtime": { + "header": { + "data_size": 1130, + "version": 1, + "report_type": 2, + "hash_type": 1, + "claim_size": 1110 + }, + "claims_json": [ + 123, + 34, + 107, + 101, + 121, + 115, + 34, + 58, + 91, + 123, + 34, + 107, + 105, + 100, + 34, + 58, + 34, + 72, + 67, + 76, + 65, + 107, + 80, + 117, + 98, + 34, + 44, + 34, + 107, + 101, + 121, + 95, + 111, + 112, + 115, + 34, + 58, + 91, + 34, + 115, + 105, + 103, + 110, + 34, + 93, + 44, + 34, + 107, + 116, + 121, + 34, + 58, + 34, + 82, + 83, + 65, + 34, + 44, + 34, + 101, + 34, + 58, + 34, + 65, + 81, + 65, + 66, + 34, + 44, + 34, + 110, + 34, + 58, + 34, + 52, + 80, + 122, + 116, + 102, + 65, + 65, + 65, + 95, + 65, + 113, + 50, + 53, + 69, + 82, + 50, + 113, + 65, + 49, + 104, + 51, + 122, + 77, + 99, + 50, + 54, + 114, + 90, + 75, + 121, + 82, + 74, + 120, + 112, + 74, + 57, + 97, + 117, + 118, + 70, + 116, + 57, + 75, + 121, + 106, + 90, + 98, + 83, + 77, + 112, + 88, + 108, + 56, + 109, + 119, + 65, + 89, + 102, + 105, + 109, + 109, + 52, + 84, + 67, + 103, + 107, + 83, + 84, + 72, + 73, + 112, + 99, + 48, + 82, + 106, + 110, + 104, + 115, + 67, + 76, + 98, + 48, + 84, + 76, + 68, + 90, + 49, + 99, + 88, + 97, + 51, + 80, + 86, + 65, + 76, + 54, + 84, + 52, + 106, + 119, + 110, + 108, + 79, + 45, + 100, + 79, + 45, + 72, + 116, + 110, + 115, + 70, + 78, + 120, + 107, + 81, + 52, + 49, + 98, + 86, + 115, + 112, + 95, + 51, + 67, + 114, + 122, + 80, + 52, + 68, + 98, + 121, + 48, + 119, + 65, + 72, + 99, + 66, + 69, + 69, + 119, + 87, + 115, + 83, + 77, + 119, + 100, + 110, + 86, + 66, + 76, + 117, + 83, + 99, + 50, + 90, + 97, + 111, + 68, + 66, + 108, + 100, + 82, + 102, + 110, + 99, + 106, + 50, + 53, + 117, + 75, + 86, + 74, + 65, + 99, + 57, + 78, + 90, + 106, + 78, + 104, + 74, + 50, + 83, + 71, + 51, + 49, + 69, + 69, + 80, + 100, + 77, + 66, + 67, + 116, + 87, + 72, + 90, + 49, + 109, + 57, + 105, + 87, + 79, + 68, + 101, + 95, + 81, + 109, + 73, + 54, + 87, + 108, + 119, + 114, + 76, + 71, + 98, + 112, + 72, + 120, + 45, + 45, + 108, + 102, + 85, + 104, + 69, + 101, + 99, + 115, + 111, + 90, + 102, + 72, + 113, + 106, + 102, + 106, + 75, + 120, + 45, + 49, + 114, + 78, + 53, + 82, + 87, + 112, + 102, + 114, + 118, + 104, + 112, + 81, + 115, + 77, + 81, + 112, + 73, + 78, + 57, + 113, + 80, + 110, + 107, + 69, + 73, + 98, + 84, + 111, + 100, + 82, + 79, + 109, + 88, + 79, + 87, + 65, + 50, + 50, + 112, + 55, + 122, + 109, + 72, + 102, + 89, + 114, + 75, + 84, + 106, + 118, + 85, + 57, + 111, + 67, + 69, + 115, + 82, + 79, + 121, + 51, + 45, + 118, + 76, + 49, + 102, + 105, + 45, + 119, + 67, + 103, + 80, + 112, + 70, + 101, + 75, + 83, + 56, + 57, + 105, + 110, + 78, + 89, + 105, + 108, + 79, + 104, + 103, + 54, + 79, + 103, + 85, + 75, + 89, + 79, + 50, + 120, + 87, + 51, + 81, + 34, + 125, + 44, + 123, + 34, + 107, + 105, + 100, + 34, + 58, + 34, + 72, + 67, + 76, + 69, + 107, + 80, + 117, + 98, + 34, + 44, + 34, + 107, + 101, + 121, + 95, + 111, + 112, + 115, + 34, + 58, + 91, + 34, + 101, + 110, + 99, + 114, + 121, + 112, + 116, + 34, + 93, + 44, + 34, + 107, + 116, + 121, + 34, + 58, + 34, + 82, + 83, + 65, + 34, + 44, + 34, + 101, + 34, + 58, + 34, + 65, + 81, + 65, + 66, + 34, + 44, + 34, + 110, + 34, + 58, + 34, + 52, + 118, + 79, + 112, + 114, + 103, + 65, + 66, + 83, + 88, + 90, + 57, + 103, + 119, + 90, + 81, + 117, + 119, + 75, + 99, + 102, + 75, + 104, + 80, + 74, + 89, + 103, + 71, + 106, + 71, + 100, + 84, + 54, + 88, + 110, + 74, + 73, + 76, + 74, + 86, + 76, + 122, + 103, + 120, + 87, + 50, + 85, + 68, + 84, + 79, + 87, + 67, + 121, + 114, + 116, + 75, + 54, + 48, + 90, + 99, + 45, + 84, + 111, + 79, + 101, + 82, + 110, + 74, + 95, + 113, + 69, + 116, + 117, + 113, + 54, + 100, + 110, + 105, + 73, + 100, + 66, + 75, + 115, + 52, + 80, + 57, + 87, + 53, + 68, + 78, + 106, + 100, + 52, + 54, + 81, + 55, + 118, + 101, + 56, + 88, + 116, + 120, + 70, + 101, + 54, + 109, + 99, + 45, + 106, + 51, + 97, + 86, + 48, + 119, + 106, + 90, + 90, + 112, + 53, + 85, + 68, + 68, + 90, + 85, + 110, + 106, + 121, + 45, + 107, + 105, + 71, + 87, + 77, + 73, + 84, + 104, + 67, + 120, + 72, + 87, + 83, + 49, + 109, + 83, + 69, + 95, + 78, + 102, + 52, + 118, + 66, + 107, + 114, + 121, + 114, + 100, + 100, + 72, + 101, + 56, + 78, + 88, + 81, + 117, + 115, + 85, + 50, + 77, + 104, + 118, + 72, + 57, + 85, + 69, + 71, + 119, + 90, + 114, + 76, + 88, + 67, + 54, + 53, + 111, + 77, + 105, + 48, + 114, + 105, + 88, + 49, + 84, + 83, + 53, + 113, + 111, + 75, + 107, + 89, + 100, + 90, + 108, + 89, + 78, + 69, + 51, + 105, + 85, + 113, + 113, + 106, + 112, + 81, + 106, + 109, + 53, + 68, + 98, + 120, + 77, + 87, + 72, + 86, + 120, + 73, + 100, + 75, + 110, + 78, + 80, + 116, + 71, + 48, + 53, + 114, + 79, + 49, + 101, + 110, + 85, + 87, + 79, + 102, + 113, + 97, + 119, + 118, + 86, + 45, + 105, + 99, + 113, + 65, + 102, + 116, + 77, + 77, + 51, + 110, + 73, + 54, + 110, + 66, + 54, + 97, + 75, + 77, + 103, + 56, + 106, + 75, + 100, + 113, + 119, + 120, + 67, + 122, + 112, + 77, + 53, + 87, + 121, + 45, + 112, + 71, + 106, + 113, + 106, + 48, + 45, + 101, + 70, + 112, + 85, + 53, + 75, + 103, + 65, + 78, + 74, + 99, + 54, + 50, + 115, + 121, + 70, + 101, + 79, + 103, + 110, + 116, + 95, + 75, + 50, + 118, + 85, + 53, + 100, + 65, + 86, + 48, + 85, + 68, + 79, + 85, + 45, + 71, + 87, + 107, + 98, + 116, + 82, + 122, + 57, + 104, + 97, + 99, + 51, + 56, + 75, + 95, + 81, + 34, + 125, + 93, + 44, + 34, + 118, + 109, + 45, + 99, + 111, + 110, + 102, + 105, + 103, + 117, + 114, + 97, + 116, + 105, + 111, + 110, + 34, + 58, + 123, + 34, + 99, + 111, + 110, + 115, + 111, + 108, + 101, + 45, + 101, + 110, + 97, + 98, + 108, + 101, + 100, + 34, + 58, + 116, + 114, + 117, + 101, + 44, + 34, + 115, + 101, + 99, + 117, + 114, + 101, + 45, + 98, + 111, + 111, + 116, + 34, + 58, + 116, + 114, + 117, + 101, + 44, + 34, + 116, + 112, + 109, + 45, + 101, + 110, + 97, + 98, + 108, + 101, + 100, + 34, + 58, + 116, + 114, + 117, + 101, + 44, + 34, + 118, + 109, + 85, + 110, + 105, + 113, + 117, + 101, + 73, + 100, + 34, + 58, + 34, + 54, + 70, + 65, + 68, + 54, + 56, + 48, + 51, + 45, + 55, + 67, + 70, + 65, + 45, + 52, + 55, + 70, + 54, + 45, + 65, + 56, + 51, + 51, + 45, + 49, + 52, + 49, + 70, + 50, + 67, + 55, + 68, + 48, + 65, + 67, + 67, + 34, + 125, + 44, + 34, + 117, + 115, + 101, + 114, + 45, + 100, + 97, + 116, + 97, + 34, + 58, + 34, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 34, + 125 + ] + } + }, + "trust": { + "quote_signature": "4908a696e37ac0b22c6fa8c178e9def32228bd2697b3bd4cd6856fc04b14cda904f76583bfa1660cdc3c222fa38bd5dbfbe8dd8e4415ac31a4dea1f4b46c302a97f3dddd03d8e46498e59c86e40738bcb431495e86debdb31253c00dfbc0cb61088d2bda2d40b470483ec2a235dd6d10b6f058bb134e85371d4b5e6764919412c9bd6ca878ed9574b335e99f0b97fb37b9ee6b3de3f45ff6b10a820fa77130e50dd9ef720029692138beac2f64cf45072aca9f5b95c4ebdf29151bc48d0dd7dd89d27f925aa5cac7e6fc20e39746eccd212ea6fa8f8fe61afe7899e15cfdb2f2acee7d11fe67445d45b5542ac27d1bfdb4b336ce2afda198b6c5ceefbf2873d0", + "ak_key": "30820122300d06092a864886f70d01010105000382010f003082010a0282010100e0fced7c0000fc0ab6e44476a80d61df331cdbaad92b2449c6927d6aebc5b7d2b28d96d23295e5f26c0061f8a69b84c28244931c8a5cd118e786c08b6f44cb0d9d5c5dadcf5402fa4f88f09e53be74ef87b67b05371910e356d5b29ff70abccfe036f2d30007701104c16b123307675412ee49cd996a80c195d45f9dc8f6e6e29524073d3598cd849d921b7d4410f74c042b561d9d66f6258e0defd0988e96970acb19ba47c7efa57d484479cb2865f1ea8df8cac7ed6b379456a5faef86942c310a4837da8f9e41086d3a1d44e997396036da9ef39877d8aca4e3bd4f68084b113b2dfebcbd5f8bec0280fa4578a4bcf629cd62294e860e8e8142983b6c56dd0203010001", + "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7TCCAtWgAwIBAgIQCbL+a6sfjNW6W4YMksPqfTANBgkqhkiG9w0BAQsFADAl\nMSMwIQYDVQQDExpHbG9iYWwgVmlydHVhbCBUUE0gQ0EgLSAwMzAeFw0yNjA2MjIw\nMDAwMDBaFw0yNzA0MjEwMDAwMDBaMDgxNjA0BgNVBAMTLWRiOTZmYzY4NDI3MS5D\nb25maWRlbnRpYWxWTS5BenVyZS53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOD87XwAAPwKtuREdqgNYd8zHNuq2SskScaSfWrrxbfS\nso2W0jKV5fJsAGH4ppuEwoJEkxyKXNEY54bAi29Eyw2dXF2tz1QC+k+I8J5TvnTv\nh7Z7BTcZEONW1bKf9wq8z+A28tMAB3ARBMFrEjMHZ1QS7knNmWqAwZXUX53I9ubi\nlSQHPTWYzYSdkht9RBD3TAQrVh2dZvYljg3v0JiOlpcKyxm6R8fvpX1IRHnLKGXx\n6o34ysftazeUVqX674aULDEKSDfaj55BCG06HUTplzlgNtqe85h32Kyk471PaAhL\nETst/ry9X4vsAoD6RXikvPYpzWIpToYOjoFCmDtsVt0CAwEAAaOCAQQwggEAMA4G\nA1UdDwEB/wQEAwIHgDAYBgNVHSAEETAPMA0GCysGAQQBgjdsgUgBMBwGA1UdJQQV\nMBMGCisGAQQBgjcKAwwGBWeBBQgDMB0GA1UdDgQWBBSvntEWzk1aLnxd0a06AG7c\nUqrbMTAaBgorBgEEAYI3DQIDBAwWCjYuMi45MjAwLjIwWgYJKwYBBAGCNxUUBE0w\nSwIBBQwPQU1TMDkxMDcyNzEwMDA1DBpXT1JLR1JPVVBcQU1TMDkxMDcyNzEwMDA1\nJAwZVnRwbU1hbmFnZW1lbnRTZXJ2aWNlLmV4ZTAfBgNVHSMEGDAWgBRnCYb4+YFe\nk635yWTnFAramcsKLTANBgkqhkiG9w0BAQsFAAOCAQEAmho7+METB8uSNn/tNSzj\nut6xAqeprztQY+aUUd1BJzT+AXJqm6Ikqq5q7ASmxObcbpI6nxbSDyBuAR0vJeWY\nTMUuN7NxvyDz0TaredKmiGSmw61JgMEmxlQvzsWRJa6m9vV+TKs0opnmjYUR1XtT\nbhg9KRSNMAWP7VDPx9EIJEOpGQYcr+rhhj1YJy8nWwXiNQoYmKBFTdOxyWjgHs18\nU+f8WgneS1FCn0becjq/dFK/GRN/PsFhinb4aljVIdIMwAHE3U4MoVbjtW2SZqqi\nzl77FdvDTOhzSCaL81BgjX1tkZaOF9EnrfOPjugdl1S/3pCU/TKCHR6iFU71NfZk\ndw==\n-----END CERTIFICATE-----\n" + } +} diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs new file mode 100644 index 00000000..13d0ba9b --- /dev/null +++ b/azure-attest/tests/decode.rs @@ -0,0 +1,12 @@ +const ATTESTATION: &'static str = include_str!("./attestation.json"); + +#[test] +fn decode_attestation() { + let attestation: azure_attest::AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); +} + +#[test] +fn verify_attestation() { + let attestation: azure_attest::AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); + attestation.verify().unwrap(); +} From 374f58a054318013288e1bab4734b22092c9a86c Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:45:32 +0000 Subject: [PATCH 13/39] refactor verification flow --- Cargo.lock | 1 + azure-attest/Cargo.toml | 1 + azure-attest/src/collateral.rs | 87 ++++++++++++++++++++++++++++ azure-attest/src/lib.rs | 51 ++++++++-------- azure-attest/src/verify.rs | 71 +++++++++++++++++++++++ azure-attest/tests/decode.rs | 28 ++++++++- libattest/src/lib.rs | 1 + libattest/src/quote.rs | 6 ++ reticle/src/lib.rs | 19 +++--- snp-attest/src/attestation/kds.rs | 9 +-- snp-attest/src/attestation/mod.rs | 11 ++-- snp-attest/src/attestation/verify.rs | 22 +++++++ snp-attest/src/claims.rs | 4 +- snp-attest/src/nonce.rs | 70 ++-------------------- tdx-attest/examples/parse.rs | 4 +- tdx-attest/src/nonce.rs | 4 ++ tdx-attest/src/verify.rs | 21 ++++--- 17 files changed, 281 insertions(+), 129 deletions(-) create mode 100644 azure-attest/src/collateral.rs create mode 100644 azure-attest/src/verify.rs create mode 100644 libattest/src/quote.rs create mode 100644 snp-attest/src/attestation/verify.rs diff --git a/Cargo.lock b/Cargo.lock index 471978d3..dfa0c19e 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,6 +240,7 @@ dependencies = [ "snp-attest", "tdx-attest", "thiserror 2.0.18", + "tokio", "tpm2-protocol", "tss-esapi", "x509-cert 0.3.0-rc.4", diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index 94555b18..c363b01c 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -24,6 +24,7 @@ tss-esapi = { version = "7.7.0", optional = true } anyhow = { version = "*", optional = true } [dev-dependencies] +tokio = { version = "1.49.0", features = ["full"] } [[bin]] name = "generate-report" diff --git a/azure-attest/src/collateral.rs b/azure-attest/src/collateral.rs new file mode 100644 index 00000000..66f4954f --- /dev/null +++ b/azure-attest/src/collateral.rs @@ -0,0 +1,87 @@ +use snp_attest::{SevQuote, nonce::SevNonce, verify::SevQuoteVerifier}; +use tdx_attest::{TdxQuote, nonce::TdxNonce, verify::TdxQuoteVerifier}; + +use crate::AzureQuote; + +pub struct AzureVerifierBuilder { + fetch_tdx: T, + fetch_sev: S, +} + +impl AzureVerifierBuilder<(), ()> { + pub fn new() -> AzureVerifierBuilder<(), ()> { + AzureVerifierBuilder { + fetch_sev: (), + fetch_tdx: (), + } + } +} + +impl AzureVerifierBuilder { + pub fn tdx(self, fetch_tdx: N) -> AzureVerifierBuilder + where + N: AsyncFnOnce(&TdxQuote) -> libattest::Result, + { + AzureVerifierBuilder { + fetch_tdx, + fetch_sev: self.fetch_sev, + } + } + + pub fn sev(self, fetch_sev: N) -> AzureVerifierBuilder + where + N: AsyncFnOnce(&SevQuote) -> libattest::Result, + { + AzureVerifierBuilder { + fetch_tdx: self.fetch_tdx, + fetch_sev, + } + } +} + +impl AzureVerifierBuilder +where + T: AsyncFnOnce(&TdxQuote) -> libattest::Result, + S: AsyncFnOnce(&SevQuote) -> libattest::Result, +{ + pub async fn fetch_collateral( + self, + quote: &AzureQuote, + ) -> libattest::Result { + // let report = quote.parse_hardware_report()?; + + let verifier = match quote.hardware_report.payload { + ParserdHardwareReport::Tdx(ref tdx_quote) => { + let verifier = (self.fetch_tdx)(tdx_quote).await?; + AzureQuoteVerifier::Tdx(Box::new(verifier)) + } + ParserdHardwareReport::Sev(ref sev_quote) => { + let verifier = (self.fetch_sev)(sev_quote).await?; + AzureQuoteVerifier::Sev(Box::new(verifier)) + } + }; + + Ok(verifier) + } +} + +pub enum AzureQuoteVerifier { + Tdx(Box), + Sev(Box), +} + +impl AzureQuoteVerifier { + pub fn tdx(&self) -> Option<&TdxQuoteVerifier> { + match self { + AzureQuoteVerifier::Tdx(tdx_quote_verifier) => Some(tdx_quote_verifier), + _ => None, + } + } + + pub fn sev(&self) -> Option<&SevQuoteVerifier> { + match self { + AzureQuoteVerifier::Sev(sev) => Some(sev), + _ => None, + } + } +} diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 56494c53..7f375f93 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -1,16 +1,21 @@ +pub mod collateral; pub mod report; mod serde_cert; mod serde_tpm; +pub mod verify; +use libattest::{ByteNonce, error::Context, quote::QuoteVerifier}; use rsa::signature::Verifier; use serde::{Deserialize, Serialize}; use sha2::Sha256; +use snp_attest::{SevQuote, nonce::SevNonce, verify::SevQuoteVerifier}; +use tdx_attest::{TdxQuote, nonce::TdxNonce, verify::TdxQuoteVerifier}; use tpm2_protocol::{ TpmMarshal, TpmWriter, data::{TpmsAttest, TpmuAttest}, }; -use crate::report::AttestationReport; +use crate::report::{AttestationReport, HardwareReport}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct AzureTrust { @@ -53,36 +58,28 @@ impl AzureQuote { } } - // pub fn parse(data: &AzureQuoteData) -> libattest::Result { - // let (attestation, _) = TpmsAttest::unmarshal(&data.quote)?; - // Ok(Self { - // quote: attestation, - // hardware_report: data.hardware_report.clone(), - // trust: data.trust.clone(), - // }) - // } - - pub fn verify(&self) -> libattest::Result<()> { - let mut buffer = Box::new([0u8; 512]); - - let mut writer = TpmWriter::new(buffer.as_mut_slice()); - self.quote.marshal(&mut writer)?; - let written = writer.len(); - - let marshaled = &buffer[..written]; - - self.trust - .ak_key - .verify(marshaled, &self.trust.quote_signature)?; - - let TpmuAttest::Quote(ref quote) = self.quote.attested else { - libattest::bail!("wrong type of attestation in TpmsQuote"); + fn parse_hardware_report(&self) -> libattest::Result { + let report = match &self.hardware_report.payload { + HardwareReport::Tdx(items) => ParsedHardwareReport::Sev(SevQuote::new(&items)?), + HardwareReport::Sev(items) => ParsedHardwareReport::Tdx(TdxQuote::from_bytes(&items)?), }; - Ok(()) + Ok(report) } + + // pub fn verify(self) -> libattest::Result {} +} + +enum ParsedHardwareReport { + Tdx(TdxQuote), + Sev(SevQuote), } -// struct AzureReport { +// pub struct AzureReport { +// quote: AzureQuote, +// report: ParserdHardwareReport, +// } +// impl AzureReport { +// pub fn verify(self, verifier: &AzureQuoteVerifier) -> libattest::Result<()> {} // } diff --git a/azure-attest/src/verify.rs b/azure-attest/src/verify.rs new file mode 100644 index 00000000..531fa85b --- /dev/null +++ b/azure-attest/src/verify.rs @@ -0,0 +1,71 @@ +use libattest::{ByteNonce, error::Context}; +use rsa::signature::Verifier; + +use crate::{AzureQuote, ParsedHardwareReport, collateral::AzureQuoteVerifier}; + +pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { + use tpm2_protocol::{TpmMarshal, TpmWriter}; + + let mut buffer = Box::new([0u8; 512]); + let mut writer = TpmWriter::new(buffer.as_mut_slice()); + + quote.quote.marshal(&mut writer)?; + + let written = writer.len(); + let marshaled = &buffer[..written]; + + quote + .trust + .ak_key + .verify(marshaled, "e.trust.quote_signature)?; + + Ok(()) +} + +pub fn verify_report_digest( + quote: &AzureQuote, + verifier: &AzureQuoteVerifier, +) -> libattest::Result<()> { + use libattest::quote::QuoteVerifier; + use snp_attest::nonce::SevNonce; + use tdx_attest::nonce::TdxNonce; + + let report = quote + .parse_hardware_report() + .context("unable to parse hardware report")?; + + let mut nonce = quote.hardware_report.hash_runtime_data()?; + + let padding = &[0u8; 64][..(64usize.saturating_sub(nonce.len()))]; + nonce.extend_from_slice(padding); // pad nonce to cover all 64 bytes of nonces + + // convert to fixed size nonce + let nonce = Box::<[u8; 64]>::try_from(nonce) + .ok() + .map(ByteNonce::from) + .context("nonce didn't fit")?; + + match report { + ParsedHardwareReport::Tdx(tdx_quote) => { + let verifier = verifier.tdx().context("didn't receive tdx verifier")?; + let nonce = TdxNonce::new(nonce); + + verifier.verify(&tdx_quote, &nonce)?; + } + ParsedHardwareReport::Sev(sev_quote) => { + let verifier = verifier.sev().context("didn't receive sev verifier")?; + let nonce = SevNonce::new(nonce); + + verifier.verify(&sev_quote, &nonce)?; + } + }; + + Ok(()) +} + +pub fn verify(azure_quote: AzureQuote, verifier: AzureQuoteVerifier) -> libattest::Result<()> { + verify_quote_signature(&azure_quote)?; + verify_report_digest(&azure_quote, &verifier)?; + + Ok(()) +} diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs index 13d0ba9b..ea739c7b 100644 --- a/azure-attest/tests/decode.rs +++ b/azure-attest/tests/decode.rs @@ -1,3 +1,7 @@ +use azure_attest::collateral::AzureVerifierBuilder; +use libattest::error::Context; +use snp_attest::{kds::Kds, verify::SevQuoteVerifier}; + const ATTESTATION: &'static str = include_str!("./attestation.json"); #[test] @@ -5,8 +9,26 @@ fn decode_attestation() { let attestation: azure_attest::AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); } -#[test] -fn verify_attestation() { +#[tokio::test] +async fn verify_attestation() { let attestation: azure_attest::AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); - attestation.verify().unwrap(); + let report = attestation.verify().unwrap(); + + let kds = Kds::default(); + + let verifier = AzureVerifierBuilder::new() + .sev(async |quote| { + kds.fetch_certificates(quote) + .await + .map(SevQuoteVerifier::new) + .context("lol") + }) + .tdx(async |_| todo!()) + .fetch_collateral(&report) + .await + .unwrap(); + + report.verify(&verifier).unwrap(); + + todo!() } diff --git a/libattest/src/lib.rs b/libattest/src/lib.rs index 4756c5be..db100c54 100644 --- a/libattest/src/lib.rs +++ b/libattest/src/lib.rs @@ -1,5 +1,6 @@ pub mod error; pub mod modules; +pub mod quote; pub mod validation; pub use modules::*; diff --git a/libattest/src/quote.rs b/libattest/src/quote.rs new file mode 100644 index 00000000..6bc3a7b9 --- /dev/null +++ b/libattest/src/quote.rs @@ -0,0 +1,6 @@ +pub trait QuoteVerifier { + type Nonce; + type Quote; + + fn verify(&self, quote: &Self::Quote, nonce: &Self::Nonce) -> crate::Result<()>; +} diff --git a/reticle/src/lib.rs b/reticle/src/lib.rs index b684abee..fc3accfc 100644 --- a/reticle/src/lib.rs +++ b/reticle/src/lib.rs @@ -11,10 +11,11 @@ use futures::future::{Either, OptionFuture}; use libattest::{ CpuModule, GpuModule, Modules, bail, error::{AttestationError, Context, Expose}, + quote::QuoteVerifier, validation::{Validator, WithPolicy}, }; use nvidia_attest::{EATToken, keychain::KeyChain, nonce::NvidiaNonce}; -use snp_attest::{ParsedAttestation, kds::Kds, nonce::SevNonce}; +use snp_attest::{SevQuote, kds::Kds, nonce::SevNonce, verify::SevQuoteVerifier}; pub use nvidia_attest; use reqwest::{ @@ -23,7 +24,7 @@ use reqwest::{ }; pub use snp_attest; -use tdx_attest::{TdxQuote, nonce::TdxNonce, pcs::Pcs, verify::QuoteVerifier}; +use tdx_attest::{TdxQuote, nonce::TdxNonce, pcs::Pcs, verify::TdxQuoteVerifier}; #[cfg(target_family = "wasm")] use wasm_bindgen::prelude::*; @@ -205,15 +206,12 @@ impl Client { /// This method exposes core functionality and does not perform cryptographic /// or measurement checks on the attestation. If you want to perform end-to-end attestation /// please refer to [`Self::attest_sev`] - pub async fn request_sev( - &self, - nonce: &SevNonce, - ) -> Result { + pub async fn request_sev(&self, nonce: &SevNonce) -> Result { let url = self.url.join("/attestation/sev").unwrap(); let query = [("nonce", &nonce.to_hex())]; let response = self.request(url, &query).await?.bytes().await?; - let attestation = ParsedAttestation::new(&response)?; + let attestation = SevQuote::new(&response)?; Ok(attestation) } @@ -266,7 +264,8 @@ impl Client { let attestation = self.request_sev(&nonce).await?; let keychain = self.kds.fetch_certificates(&attestation).await?; - attestation.verify(&keychain, &nonce)?; + let verifier = SevQuoteVerifier::new(keychain); + verifier.verify(&attestation, &nonce)?; self.policy_validator .verify_claim(&attestation)? @@ -290,9 +289,9 @@ impl Client { let claims = quote.body().clone(); - let verifier = QuoteVerifier::new(collateral, quote); + let verifier = TdxQuoteVerifier::new(collateral); verifier - .verify(&nonce) + .verify("e, &nonce) .context("TDX quote verification has failed") .expose_error()?; diff --git a/snp-attest/src/attestation/kds.rs b/snp-attest/src/attestation/kds.rs index 66ccfdcb..ceed6f2e 100644 --- a/snp-attest/src/attestation/kds.rs +++ b/snp-attest/src/attestation/kds.rs @@ -7,7 +7,7 @@ use x509_cert::certificate::{CertificateInner, Rfc5280}; use wasm_bindgen::prelude::*; use crate::{ - ParsedAttestation, + SevQuote, chain::{CRL, VerifiedChain}, }; @@ -107,7 +107,7 @@ impl Kds { pub async fn fetch_certificates( &self, - attestation: &ParsedAttestation, + attestation: &SevQuote, ) -> Result { log::info!("Fetching the chain from KDS"); let chain = self @@ -124,10 +124,7 @@ impl Kds { } /// Fetches the certificate revocation list from AMD's KDS - pub async fn fetch_crl( - &self, - attestation: &ParsedAttestation, - ) -> Result { + pub async fn fetch_crl(&self, attestation: &SevQuote) -> Result { let client = Client::new(); let req = client.get(format!( "{}/crl", diff --git a/snp-attest/src/attestation/mod.rs b/snp-attest/src/attestation/mod.rs index 4b10deab..fce72319 100644 --- a/snp-attest/src/attestation/mod.rs +++ b/snp-attest/src/attestation/mod.rs @@ -2,6 +2,7 @@ pub mod chain; /// Methods for interacting with AMD's keyserver pub mod kds; +pub mod verify; // pub mod nonce; @@ -23,7 +24,7 @@ use self::chain::VerifiedChain; /// Represents a parsed attestation report with some already /// parsed commonly accessed fields #[allow(unused)] -pub struct ParsedAttestation { +pub struct SevQuote { cpu_fam_id: CpuFamily, cpu_mod_id: CpuModel, generation: Generation, @@ -32,7 +33,7 @@ pub struct ParsedAttestation { } #[cfg_attr(target_family = "wasm", wasm_bindgen)] -impl ParsedAttestation { +impl SevQuote { /// Parses and constructs a new attestation report from a stream of binary data pub fn new(bytes: &[u8]) -> Result { let report = @@ -48,7 +49,7 @@ impl ParsedAttestation { let generation = sev::Generation::identify_cpu(cpu_fam_id, cpu_mod_id) .context("could not identify cpu from attestation report")?; - Ok(ParsedAttestation { + Ok(SevQuote { cpu_fam_id, cpu_mod_id, generation, @@ -65,7 +66,7 @@ impl ParsedAttestation { } /// Verifies the attestation report against a certificate chain - pub fn verify(&self, chain: &VerifiedChain, nonce: &SevNonce) -> Result<(), AttestationError> { + fn verify(&self, chain: &VerifiedChain, nonce: &SevNonce) -> Result<(), AttestationError> { // let certificates = chain.parse_certificates()?; // TODO: unify everything and use either x509-cert or x509-parser @@ -119,7 +120,7 @@ impl ParsedAttestation { } } -impl ParsedAttestation { +impl SevQuote { pub fn generation(&self) -> Generation { self.generation } diff --git a/snp-attest/src/attestation/verify.rs b/snp-attest/src/attestation/verify.rs new file mode 100644 index 00000000..1b1dd9d3 --- /dev/null +++ b/snp-attest/src/attestation/verify.rs @@ -0,0 +1,22 @@ +use libattest::quote::QuoteVerifier; + +use crate::{SevQuote, chain::VerifiedChain, nonce::SevNonce}; + +pub struct SevQuoteVerifier { + collateral: VerifiedChain, +} + +impl SevQuoteVerifier { + pub fn new(collateral: VerifiedChain) -> Self { + Self { collateral } + } +} + +impl QuoteVerifier for SevQuoteVerifier { + type Nonce = SevNonce; + type Quote = SevQuote; + + fn verify(&self, quote: &SevQuote, nonce: &SevNonce) -> libattest::Result<()> { + quote.verify(&self.collateral, nonce) + } +} diff --git a/snp-attest/src/claims.rs b/snp-attest/src/claims.rs index bdef074e..d06833b3 100644 --- a/snp-attest/src/claims.rs +++ b/snp-attest/src/claims.rs @@ -2,7 +2,7 @@ use libattest::validation::WithPolicy; use serde::Serialize; use sev::firmware::guest::{AttestationReport, GuestPolicy}; -use crate::ParsedAttestation; +use crate::SevQuote; #[derive(Debug, Serialize)] pub struct GuestPolicyClaims { @@ -61,7 +61,7 @@ impl From<&AttestationReport> for SevClaims { } } -impl libattest::validation::IntoClaims for &ParsedAttestation { +impl libattest::validation::IntoClaims for &SevQuote { type Claims = WithPolicy; fn into_claims(self) -> WithPolicy { WithPolicy::new("sev.allow", SevClaims::from(self.report())) diff --git a/snp-attest/src/nonce.rs b/snp-attest/src/nonce.rs index 644ddf44..700af105 100644 --- a/snp-attest/src/nonce.rs +++ b/snp-attest/src/nonce.rs @@ -16,6 +16,10 @@ impl Deref for SevNonce { #[cfg_attr(target_family = "wasm", wasm_bindgen)] impl SevNonce { + pub fn new(byte_nonce: libattest::ByteNonce<64>) -> Self { + Self(byte_nonce) + } + pub fn generate() -> Self { Self(libattest::ByteNonce::generate()) } @@ -24,69 +28,3 @@ impl SevNonce { self.0.to_hex() } } -// #[cfg_attr(target_family = "wasm", wasm_bindgen(constructor))] -// pub fn new() -> Self { -// let mut bytes = Box::new([0u8; 64]); - -// getrandom::getrandom(bytes.as_mut_slice()).unwrap(); - -// SevNonce(bytes) -// } - -// pub fn generate() -> Self { -// Self::new() -// } - -// pub fn to_hex(&self) -> String { -// hex::encode_upper(self.0.as_ref()) -// } -// } - -// impl Deref for SevNonce { -// type Target = [u8; 64]; - -// fn deref(&self) -> &Self::Target { -// &self.0 -// } -// } - -// impl SevNonce { -// pub fn get_bytes(&self) -> &[u8; 64] { -// &self.0 -// } -// } - -// impl Default for SevNonce { -// fn default() -> Self { -// Self::new() -// } -// } - -// impl From> for SevNonce { -// fn from(value: Box<[u8; 64]>) -> Self { -// Self(value) -// } -// } - -// impl TryFrom for SevNonce { -// type Error = anyhow::Error; - -// fn try_from(value: std::string::String) -> anyhow::Result { -// let mut b_arr: [u8; 64] = [0u8; 64]; - -// hex::decode(&value) -// .expect("invalid hex") -// .into_iter() -// .take(64) -// .enumerate() -// .for_each(|(i, val)| b_arr[i] = val); - -// Ok(SevNonce { bytes: b_arr }) -// } -// } - -// impl From<[u8; 64]> for SevNonce { -// fn from(value: [u8; 64]) -> Self { -// SevNonce { bytes: value } -// } -// } diff --git a/tdx-attest/examples/parse.rs b/tdx-attest/examples/parse.rs index df5c1eb8..a4eee551 100644 --- a/tdx-attest/examples/parse.rs +++ b/tdx-attest/examples/parse.rs @@ -3,7 +3,7 @@ use tdx_attest::{ TdxQuote, dcap::parser::ParseErrorExt, pcs::Pcs, - verify::{self, QuoteVerifier}, + verify::{self, TdxQuoteVerifier}, }; // static DATA: [u8; 64] = [ @@ -24,7 +24,7 @@ async fn main() -> anyhow::Result<()> { let collateral = pcs.fetch_collateral("e).await?; let nonce = ByteNonce::from(DATA).into(); - let verifier = QuoteVerifier::new(collateral, quote); + let verifier = TdxQuoteVerifier::new(collateral, quote); verifier.verify(&nonce)?; diff --git a/tdx-attest/src/nonce.rs b/tdx-attest/src/nonce.rs index 648100e4..867ebc06 100644 --- a/tdx-attest/src/nonce.rs +++ b/tdx-attest/src/nonce.rs @@ -18,6 +18,10 @@ impl Deref for TdxNonce { #[cfg_attr(target_family = "wasm", wasm_bindgen)] impl TdxNonce { + pub fn new(byte_nonce: libattest::ByteNonce<64>) -> Self { + Self(byte_nonce) + } + #[cfg_attr(target_family = "wasm", wasm_bindgen(constructor))] pub fn generate() -> Self { Self(libattest::ByteNonce::generate()) diff --git a/tdx-attest/src/verify.rs b/tdx-attest/src/verify.rs index 580738a5..85ce89f1 100644 --- a/tdx-attest/src/verify.rs +++ b/tdx-attest/src/verify.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use libattest::error::Context; +use libattest::{error::Context, quote::QuoteVerifier}; use sha2::{Digest, Sha256, digest::Update}; use signature::Verifier; use zerocopy::IntoBytes; @@ -262,25 +262,30 @@ fn verify_mask(quote: &[u8; N], expected: &[u8; N], mask: &[u8; } #[cfg_attr(target_family = "wasm", wasm_bindgen)] -pub struct QuoteVerifier { +pub struct TdxQuoteVerifier { collateral: Collateral, - quote: TdxQuote, + // quote: TdxQuote, minimum_tcb_level: TcbStatus, } #[cfg_attr(target_family = "wasm", wasm_bindgen)] -impl QuoteVerifier { +impl TdxQuoteVerifier { #[cfg_attr(target_family = "wasm", wasm_bindgen(constructor))] - pub fn new(collateral: Collateral, quote: TdxQuote) -> Self { + #[must_use] + pub fn new(collateral: Collateral) -> Self { Self { collateral, - quote, minimum_tcb_level: TcbStatus::UpToDate, } } +} + +impl QuoteVerifier for TdxQuoteVerifier { + type Nonce = TdxNonce; + type Quote = TdxQuote; - pub fn verify(&self, nonce: &TdxNonce) -> Result<(), TdxError> { - let tcb_levels = verify(&self.quote, &self.collateral, nonce)?; + fn verify(&self, quote: &TdxQuote, nonce: &TdxNonce) -> Result<(), TdxError> { + let tcb_levels = verify(quote, &self.collateral, nonce)?; let minimum_tcb = &self.minimum_tcb_level; if tcb_levels.qe_tcb.tcb_status < self.minimum_tcb_level { From 58f89191a36e8b708d111fe1e90299eac2fa1372 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:49:21 +0000 Subject: [PATCH 14/39] fix example --- azure-attest/src/collateral.rs | 23 ++++++++++++----------- tdx-attest/examples/parse.rs | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/azure-attest/src/collateral.rs b/azure-attest/src/collateral.rs index 66f4954f..db62a71f 100644 --- a/azure-attest/src/collateral.rs +++ b/azure-attest/src/collateral.rs @@ -50,18 +50,19 @@ where ) -> libattest::Result { // let report = quote.parse_hardware_report()?; - let verifier = match quote.hardware_report.payload { - ParserdHardwareReport::Tdx(ref tdx_quote) => { - let verifier = (self.fetch_tdx)(tdx_quote).await?; - AzureQuoteVerifier::Tdx(Box::new(verifier)) - } - ParserdHardwareReport::Sev(ref sev_quote) => { - let verifier = (self.fetch_sev)(sev_quote).await?; - AzureQuoteVerifier::Sev(Box::new(verifier)) - } - }; + // let verifier = match quote.hardware_report.payload { + // ParserdHardwareReport::Tdx(ref tdx_quote) => { + // let verifier = (self.fetch_tdx)(tdx_quote).await?; + // AzureQuoteVerifier::Tdx(Box::new(verifier)) + // } + // ParserdHardwareReport::Sev(ref sev_quote) => { + // let verifier = (self.fetch_sev)(sev_quote).await?; + // AzureQuoteVerifier::Sev(Box::new(verifier)) + // } + // }; - Ok(verifier) + // Ok(verifier) + todo!() } } diff --git a/tdx-attest/examples/parse.rs b/tdx-attest/examples/parse.rs index a4eee551..27ea6a44 100644 --- a/tdx-attest/examples/parse.rs +++ b/tdx-attest/examples/parse.rs @@ -24,9 +24,9 @@ async fn main() -> anyhow::Result<()> { let collateral = pcs.fetch_collateral("e).await?; let nonce = ByteNonce::from(DATA).into(); - let verifier = TdxQuoteVerifier::new(collateral, quote); + let verifier = TdxQuoteVerifier::new(collateral); - verifier.verify(&nonce)?; + verifier.verify("e, &nonce)?; println!("Verification success"); // println!("{identity:?}"); From 1e41bf801e3a0afea87845c4df6378b46c219e4d Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:17:48 +0000 Subject: [PATCH 15/39] More implemented pieces of the attestation chain --- Cargo.lock | 1 + azure-attest/Cargo.toml | 1 + azure-attest/src/collateral.rs | 60 +++++++++++++++---------------- azure-attest/src/lib.rs | 4 +-- azure-attest/src/report.rs | 4 +-- azure-attest/src/verify.rs | 64 +++++++++++++++++++++++++++++++--- azure-attest/tests/decode.rs | 14 +++----- tdx-attest/examples/parse.rs | 2 +- 8 files changed, 99 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfa0c19e..ce32be13 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,6 +231,7 @@ name = "azure-attest" version = "0.1.0" dependencies = [ "anyhow", + "base64", "jsonwebtoken", "libattest", "rsa 0.10.0-rc.18", diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index c363b01c..2ae71bb4 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -22,6 +22,7 @@ zerocopy = { version = "0.8.48", features = ["derive"] } tss-esapi = { version = "7.7.0", optional = true } anyhow = { version = "*", optional = true } +base64 = "0.22.1" [dev-dependencies] tokio = { version = "1.49.0", features = ["full"] } diff --git a/azure-attest/src/collateral.rs b/azure-attest/src/collateral.rs index db62a71f..c56423d0 100644 --- a/azure-attest/src/collateral.rs +++ b/azure-attest/src/collateral.rs @@ -1,87 +1,83 @@ use snp_attest::{SevQuote, nonce::SevNonce, verify::SevQuoteVerifier}; use tdx_attest::{TdxQuote, nonce::TdxNonce, verify::TdxQuoteVerifier}; -use crate::AzureQuote; +use crate::{AzureQuote, ParsedHardwareReport}; -pub struct AzureVerifierBuilder { +pub struct ReportVerifierBuilder { fetch_tdx: T, fetch_sev: S, } -impl AzureVerifierBuilder<(), ()> { - pub fn new() -> AzureVerifierBuilder<(), ()> { - AzureVerifierBuilder { +impl ReportVerifierBuilder<(), ()> { + pub fn new() -> ReportVerifierBuilder<(), ()> { + ReportVerifierBuilder { fetch_sev: (), fetch_tdx: (), } } } -impl AzureVerifierBuilder { - pub fn tdx(self, fetch_tdx: N) -> AzureVerifierBuilder +impl ReportVerifierBuilder { + pub fn tdx(self, fetch_tdx: N) -> ReportVerifierBuilder where N: AsyncFnOnce(&TdxQuote) -> libattest::Result, { - AzureVerifierBuilder { + ReportVerifierBuilder { fetch_tdx, fetch_sev: self.fetch_sev, } } - pub fn sev(self, fetch_sev: N) -> AzureVerifierBuilder + pub fn sev(self, fetch_sev: N) -> ReportVerifierBuilder where N: AsyncFnOnce(&SevQuote) -> libattest::Result, { - AzureVerifierBuilder { + ReportVerifierBuilder { fetch_tdx: self.fetch_tdx, fetch_sev, } } } -impl AzureVerifierBuilder +impl ReportVerifierBuilder where T: AsyncFnOnce(&TdxQuote) -> libattest::Result, S: AsyncFnOnce(&SevQuote) -> libattest::Result, { - pub async fn fetch_collateral( - self, - quote: &AzureQuote, - ) -> libattest::Result { - // let report = quote.parse_hardware_report()?; + pub async fn fetch_collateral(self, quote: &AzureQuote) -> libattest::Result { + let report = quote.parse_hardware_report()?; - // let verifier = match quote.hardware_report.payload { - // ParserdHardwareReport::Tdx(ref tdx_quote) => { - // let verifier = (self.fetch_tdx)(tdx_quote).await?; - // AzureQuoteVerifier::Tdx(Box::new(verifier)) - // } - // ParserdHardwareReport::Sev(ref sev_quote) => { - // let verifier = (self.fetch_sev)(sev_quote).await?; - // AzureQuoteVerifier::Sev(Box::new(verifier)) - // } - // }; + let verifier = match report { + ParsedHardwareReport::Tdx(ref tdx_quote) => { + let verifier = (self.fetch_tdx)(tdx_quote).await?; + ReportVerifier::Tdx(Box::new(verifier)) + } + ParsedHardwareReport::Sev(ref sev_quote) => { + let verifier = (self.fetch_sev)(sev_quote).await?; + ReportVerifier::Sev(Box::new(verifier)) + } + }; - // Ok(verifier) - todo!() + Ok(verifier) } } -pub enum AzureQuoteVerifier { +pub enum ReportVerifier { Tdx(Box), Sev(Box), } -impl AzureQuoteVerifier { +impl ReportVerifier { pub fn tdx(&self) -> Option<&TdxQuoteVerifier> { match self { - AzureQuoteVerifier::Tdx(tdx_quote_verifier) => Some(tdx_quote_verifier), + ReportVerifier::Tdx(tdx_quote_verifier) => Some(tdx_quote_verifier), _ => None, } } pub fn sev(&self) -> Option<&SevQuoteVerifier> { match self { - AzureQuoteVerifier::Sev(sev) => Some(sev), + ReportVerifier::Sev(sev) => Some(sev), _ => None, } } diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 7f375f93..7e04bdf6 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -60,8 +60,8 @@ impl AzureQuote { fn parse_hardware_report(&self) -> libattest::Result { let report = match &self.hardware_report.payload { - HardwareReport::Tdx(items) => ParsedHardwareReport::Sev(SevQuote::new(&items)?), - HardwareReport::Sev(items) => ParsedHardwareReport::Tdx(TdxQuote::from_bytes(&items)?), + HardwareReport::Tdx(items) => ParsedHardwareReport::Tdx(TdxQuote::from_bytes(items)?), + HardwareReport::Sev(items) => ParsedHardwareReport::Sev(SevQuote::new(items)?), }; Ok(report) diff --git a/azure-attest/src/report.rs b/azure-attest/src/report.rs index ee83d3de..16d5f9ae 100644 --- a/azure-attest/src/report.rs +++ b/azure-attest/src/report.rs @@ -177,7 +177,7 @@ pub struct RuntimeClaims { pub struct VmConfiguration { /// Root certificate thumbprint, when configured. #[serde(rename = "root-cert-thumbprint")] - pub root_cert_thumbprint: String, + pub root_cert_thumbprint: Option, /// Whether VM console access is enabled. #[serde(rename = "console-enabled")] pub console_enabled: bool, @@ -188,7 +188,7 @@ pub struct VmConfiguration { #[serde(rename = "tpm-enabled")] pub tpm_enabled: bool, /// Whether TPM state is persisted. - #[serde(rename = "tpm-persisted")] + #[serde(default, rename = "tpm-persisted")] pub tpm_persisted: bool, /// Azure VM unique ID. #[serde(rename = "vmUniqueId")] diff --git a/azure-attest/src/verify.rs b/azure-attest/src/verify.rs index 531fa85b..f0875521 100644 --- a/azure-attest/src/verify.rs +++ b/azure-attest/src/verify.rs @@ -1,7 +1,9 @@ +use base64::Engine; +use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet}; use libattest::{ByteNonce, error::Context}; -use rsa::signature::Verifier; +use rsa::{BoxedUint, signature::Verifier}; -use crate::{AzureQuote, ParsedHardwareReport, collateral::AzureQuoteVerifier}; +use crate::{AzureQuote, ParsedHardwareReport, collateral::ReportVerifier}; pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { use tpm2_protocol::{TpmMarshal, TpmWriter}; @@ -22,9 +24,56 @@ pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { Ok(()) } +fn decode_base64_component(base64: &str) -> Result { + use base64::engine::general_purpose::URL_SAFE_NO_PAD; + + let decoded = URL_SAFE_NO_PAD.decode(base64)?; + Ok(BoxedUint::from_be_slice_vartime(&decoded)) +} + +pub fn verify_quote_chain(quote: &AzureQuote) -> libattest::Result<()> { + let claims = quote + .hardware_report + .runtime + .claims() + .context("failed parsing Runtime Data claims")?; + + // Gather keys from runtime claims to enstablish trust into the keys used to sign + // the vTPM quote. + + let set = JwkSet { keys: claims.keys }; + + let hclakpub = set + .find("HCLAkPub") + .context("Missing HCLAkPub from Runtime Claims")?; + + // Verify that keys match + let AlgorithmParameters::RSA(ref hclakpub) = hclakpub.algorithm else { + libattest::bail!("Received wrong key type in Report Data JWT for HCKLAkPub"); + }; + + // we decode the components of the rsa key from + // base64 encoded format of jsonwebkeys + let modulus = decode_base64_component(&hclakpub.n).context("failed parsing modulus")?; + let exponent = decode_base64_component(&hclakpub.e).context("failed parsing exponent")?; + + // convert the key into something we can compare + let report_ak = rsa::RsaPublicKey::new(modulus, exponent) + .map(rsa::pkcs1v15::VerifyingKey::::new) + .context("failed constructing rsa public key from report data")?; + + // Finally, we check if the vendored ak key and the trusted ak key + // whose trust we derive from the hardware report match + if report_ak != quote.trust.ak_key { + libattest::bail!(exposed: "TPM read key and Hardware Report verified key do not match"); + } + + Ok(()) +} + pub fn verify_report_digest( quote: &AzureQuote, - verifier: &AzureQuoteVerifier, + verifier: &ReportVerifier, ) -> libattest::Result<()> { use libattest::quote::QuoteVerifier; use snp_attest::nonce::SevNonce; @@ -63,9 +112,14 @@ pub fn verify_report_digest( Ok(()) } -pub fn verify(azure_quote: AzureQuote, verifier: AzureQuoteVerifier) -> libattest::Result<()> { +pub fn verify(azure_quote: AzureQuote, report_verifier: ReportVerifier) -> libattest::Result<()> { + // 1: verify that AK signs Quote through signature verify_quote_signature(&azure_quote)?; - verify_report_digest(&azure_quote, &verifier)?; + // 2: verify that the hardware report signs report data + verify_report_digest(&azure_quote, &report_verifier)?; + // 3: verify that report data contains the correct ak key, + // sprouting azure trust from SEV/TDX + verify_quote_chain(&azure_quote)?; Ok(()) } diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs index ea739c7b..ead9884f 100644 --- a/azure-attest/tests/decode.rs +++ b/azure-attest/tests/decode.rs @@ -1,4 +1,4 @@ -use azure_attest::collateral::AzureVerifierBuilder; +use azure_attest::collateral::ReportVerifierBuilder; use libattest::error::Context; use snp_attest::{kds::Kds, verify::SevQuoteVerifier}; @@ -11,24 +11,20 @@ fn decode_attestation() { #[tokio::test] async fn verify_attestation() { - let attestation: azure_attest::AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); - let report = attestation.verify().unwrap(); + let quote: azure_attest::AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); let kds = Kds::default(); - let verifier = AzureVerifierBuilder::new() + let verifier = ReportVerifierBuilder::new() .sev(async |quote| { kds.fetch_certificates(quote) .await .map(SevQuoteVerifier::new) - .context("lol") }) .tdx(async |_| todo!()) - .fetch_collateral(&report) + .fetch_collateral("e) .await .unwrap(); - report.verify(&verifier).unwrap(); - - todo!() + azure_attest::verify::verify(quote, verifier).unwrap(); } diff --git a/tdx-attest/examples/parse.rs b/tdx-attest/examples/parse.rs index 27ea6a44..428b66ab 100644 --- a/tdx-attest/examples/parse.rs +++ b/tdx-attest/examples/parse.rs @@ -1,4 +1,4 @@ -use libattest::ByteNonce; +use libattest::{ByteNonce, quote::QuoteVerifier}; use tdx_attest::{ TdxQuote, dcap::parser::ParseErrorExt, From 12ec10e4623f4a6a78aa42cd50671272691de38a Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:29:13 +0000 Subject: [PATCH 16/39] cleanup and remove unused dependencies --- azure-attest/src/collateral.rs | 16 ++++++++++++++-- azure-attest/src/lib.rs | 22 +++------------------- azure-attest/src/verify.rs | 12 +++++++----- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/azure-attest/src/collateral.rs b/azure-attest/src/collateral.rs index c56423d0..b4c699ee 100644 --- a/azure-attest/src/collateral.rs +++ b/azure-attest/src/collateral.rs @@ -1,8 +1,12 @@ -use snp_attest::{SevQuote, nonce::SevNonce, verify::SevQuoteVerifier}; -use tdx_attest::{TdxQuote, nonce::TdxNonce, verify::TdxQuoteVerifier}; +use snp_attest::{SevQuote, verify::SevQuoteVerifier}; +use tdx_attest::{TdxQuote, verify::TdxQuoteVerifier}; use crate::{AzureQuote, ParsedHardwareReport}; +/// Construct the builder providing two async functions +/// that fetch collateral and build a quote verifier +/// based on the type of hardware report contained in the +/// vtpm report data. pub struct ReportVerifierBuilder { fetch_tdx: T, fetch_sev: S, @@ -17,6 +21,12 @@ impl ReportVerifierBuilder<(), ()> { } } +impl Default for ReportVerifierBuilder<(), ()> { + fn default() -> Self { + Self::new() + } +} + impl ReportVerifierBuilder { pub fn tdx(self, fetch_tdx: N) -> ReportVerifierBuilder where @@ -44,6 +54,8 @@ where T: AsyncFnOnce(&TdxQuote) -> libattest::Result, S: AsyncFnOnce(&SevQuote) -> libattest::Result, { + /// Chooses the right function to collect evidence depending + /// on the type of report data (sev or tdx) contained in the azure quote pub async fn fetch_collateral(self, quote: &AzureQuote) -> libattest::Result { let report = quote.parse_hardware_report()?; diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 7e04bdf6..60ab791c 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -4,16 +4,11 @@ mod serde_cert; mod serde_tpm; pub mod verify; -use libattest::{ByteNonce, error::Context, quote::QuoteVerifier}; -use rsa::signature::Verifier; use serde::{Deserialize, Serialize}; use sha2::Sha256; -use snp_attest::{SevQuote, nonce::SevNonce, verify::SevQuoteVerifier}; -use tdx_attest::{TdxQuote, nonce::TdxNonce, verify::TdxQuoteVerifier}; -use tpm2_protocol::{ - TpmMarshal, TpmWriter, - data::{TpmsAttest, TpmuAttest}, -}; +use snp_attest::SevQuote; +use tdx_attest::TdxQuote; +use tpm2_protocol::data::TpmsAttest; use crate::report::{AttestationReport, HardwareReport}; @@ -66,20 +61,9 @@ impl AzureQuote { Ok(report) } - - // pub fn verify(self) -> libattest::Result {} } enum ParsedHardwareReport { Tdx(TdxQuote), Sev(SevQuote), } - -// pub struct AzureReport { -// quote: AzureQuote, -// report: ParserdHardwareReport, -// } - -// impl AzureReport { -// pub fn verify(self, verifier: &AzureQuoteVerifier) -> libattest::Result<()> {} -// } diff --git a/azure-attest/src/verify.rs b/azure-attest/src/verify.rs index f0875521..74cd51b0 100644 --- a/azure-attest/src/verify.rs +++ b/azure-attest/src/verify.rs @@ -3,7 +3,7 @@ use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet}; use libattest::{ByteNonce, error::Context}; use rsa::{BoxedUint, signature::Verifier}; -use crate::{AzureQuote, ParsedHardwareReport, collateral::ReportVerifier}; +use crate::{AzureQuote, ParsedHardwareReport, collateral::ReportVerifier, report::RuntimeClaims}; pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { use tpm2_protocol::{TpmMarshal, TpmWriter}; @@ -31,7 +31,7 @@ fn decode_base64_component(base64: &str) -> Result libattest::Result<()> { +pub fn verify_runtime_data(quote: &AzureQuote) -> libattest::Result { let claims = quote .hardware_report .runtime @@ -41,7 +41,9 @@ pub fn verify_quote_chain(quote: &AzureQuote) -> libattest::Result<()> { // Gather keys from runtime claims to enstablish trust into the keys used to sign // the vTPM quote. - let set = JwkSet { keys: claims.keys }; + let set = JwkSet { + keys: claims.keys.clone(), + }; let hclakpub = set .find("HCLAkPub") @@ -68,7 +70,7 @@ pub fn verify_quote_chain(quote: &AzureQuote) -> libattest::Result<()> { libattest::bail!(exposed: "TPM read key and Hardware Report verified key do not match"); } - Ok(()) + Ok(claims) } pub fn verify_report_digest( @@ -119,7 +121,7 @@ pub fn verify(azure_quote: AzureQuote, report_verifier: ReportVerifier) -> libat verify_report_digest(&azure_quote, &report_verifier)?; // 3: verify that report data contains the correct ak key, // sprouting azure trust from SEV/TDX - verify_quote_chain(&azure_quote)?; + verify_runtime_data(&azure_quote)?; Ok(()) } From 2d414c8c995cdf004f9cb0b75d1e29caf0f7a24c Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Thu, 25 Jun 2026 12:08:54 +0000 Subject: [PATCH 17/39] refactor of azure-attest, unified behaviour of nonce across all crates --- azure-attest/Cargo.toml | 1 + azure-attest/src/bin/generate.rs | 175 +-------------------- azure-attest/src/collateral.rs | 2 +- azure-attest/src/host.rs | 35 +++++ azure-attest/src/host/vtpm.rs | 145 +++++++++++++++++ azure-attest/src/lib.rs | 71 +-------- azure-attest/src/nonce.rs | 1 + azure-attest/src/quote.rs | 65 ++++++++ azure-attest/src/{ => quote}/verify.rs | 34 +++- azure-attest/src/serde.rs | 2 + azure-attest/src/{ => serde}/serde_cert.rs | 0 azure-attest/src/{ => serde}/serde_tpm.rs | 0 azure-attest/tests/decode.rs | 2 +- libattest/src/lib.rs | 74 +++++++-- libattest/src/validation.rs | 5 +- nvidia-attest/src/nonce.rs | 28 +--- snp-attest/examples/read-report.rs | 4 +- snp-attest/src/nonce.rs | 31 +--- tdx-attest/src/nonce.rs | 40 +---- 19 files changed, 366 insertions(+), 349 deletions(-) create mode 100644 azure-attest/src/host.rs create mode 100644 azure-attest/src/host/vtpm.rs create mode 100644 azure-attest/src/nonce.rs create mode 100644 azure-attest/src/quote.rs rename azure-attest/src/{ => quote}/verify.rs (80%) create mode 100644 azure-attest/src/serde.rs rename azure-attest/src/{ => serde}/serde_cert.rs (100%) rename azure-attest/src/{ => serde}/serde_tpm.rs (100%) diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index 2ae71bb4..7f25a483 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [features] +host = ["dep:tss-esapi"] cli = ["dep:tss-esapi", "dep:anyhow"] [dependencies] diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 40545824..9327fcc5 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -1,177 +1,6 @@ use std::ops::Deref; -use azure_attest::{AzureQuote, AzureTrust, report::AttestationReport}; -use libattest::error::Context; -use rsa::{BoxedUint, pkcs1v15::VerifyingKey}; -use sha2::Sha256; -use tpm2_protocol::{TpmUnmarshal, data::TpmsAttest}; -use tss_esapi::{ - abstraction::nv, - handles::{KeyHandle, NvIndexTpmHandle}, - interface_types::{ - algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, - }, - structures::{ - Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, RsaExponent, Signature, - SignatureScheme, - }, - tcti_ldr::{DeviceConfig, TctiNameConf}, - traits::Marshall, -}; -use x509_cert::der::{Decode, SliceReader}; - -struct AzureTpm { - context: tss_esapi::Context, -} - -impl AzureTpm { - fn new(context: tss_esapi::Context) -> Self { - Self { context } - } - - const AK_CERT_IDX: u32 = 0x01C101D0; - const VTPM_HCL_AKPUB_PERSISTENT_HANDLE: u32 = 0x81000003; - const HARDWARE_REPORT_IDX: u32 = 0x01400001; - - pub const VTPM_DEFAULT_PCR_SLOTS: [PcrSlot; 24] = [ - PcrSlot::Slot0, - PcrSlot::Slot1, - PcrSlot::Slot2, - PcrSlot::Slot3, - PcrSlot::Slot4, - PcrSlot::Slot5, - PcrSlot::Slot6, - PcrSlot::Slot7, - PcrSlot::Slot8, - PcrSlot::Slot9, - PcrSlot::Slot10, - PcrSlot::Slot11, - PcrSlot::Slot12, - PcrSlot::Slot13, - PcrSlot::Slot14, - PcrSlot::Slot15, - PcrSlot::Slot16, - PcrSlot::Slot17, - PcrSlot::Slot18, - PcrSlot::Slot19, - PcrSlot::Slot20, - PcrSlot::Slot21, - PcrSlot::Slot22, - PcrSlot::Slot23, - ]; - - fn tpm_read(&mut self, index: u32) -> Result, tss_esapi::Error> { - let handle = NvIndexTpmHandle::new(index)?; - let read = nv::read_full(&mut self.context, NvAuth::Owner, handle)?; - - Ok(read) - } - - pub fn hardware_report(&mut self) -> anyhow::Result { - let read = self.tpm_read(Self::HARDWARE_REPORT_IDX)?; - let report = azure_attest::report::deserialize_attestation_report(&read)?; - - Ok(report) - } - - pub fn ak_cert(&mut self) -> anyhow::Result { - let ak_cert = self - .tpm_read(Self::AK_CERT_IDX) - .context("unable to read ak_cert from tpm")?; - - let mut reader = SliceReader::new(&ak_cert)?; - - x509_cert::Certificate::decode(&mut reader) - .context("unable to decode certificate from DER") - .map_err(Into::into) - } - - fn ak_handle(&mut self) -> Result { - let handle = Self::VTPM_HCL_AKPUB_PERSISTENT_HANDLE.try_into()?; - - let public_key_handle = self - .context - .execute_without_session(|ctx| ctx.tr_from_tpm_public(handle))? - .into(); - - Ok(public_key_handle) - } - - pub fn ak(&mut self) -> anyhow::Result> { - let public_key_handle = self.ak_handle()?; - let (public, _, _) = self - .context - .execute_without_session(|ctx| ctx.read_public(public_key_handle))?; - - let Public::Rsa { - unique, parameters, .. - } = public - else { - anyhow::bail!("received public key that was not rsa"); - }; - - let exponent = match parameters.exponent() { - RsaExponent::ZERO_EXPONENT => 65537, - exp => exp.value(), - }; - - rsa::RsaPublicKey::new( - BoxedUint::from_be_slice_vartime(unique.deref()), - exponent.into(), - ) - .context("unable to decode public key components from tpm") - .map(VerifyingKey::new) - .map_err(Into::into) - } - - pub fn quote(mut self, nonce: impl Into>) -> anyhow::Result<(Attest, Signature)> { - let key_handle = self.ak_handle()?; - - let pcr_list = PcrSelectionListBuilder::new() - .with_selection(HashingAlgorithm::Sha256, &Self::VTPM_DEFAULT_PCR_SLOTS) - .with_size_of_select(PcrSelectSize::default()) - .build() - .unwrap(); - - let res = self - .context - .quote( - key_handle, - nonce.into().try_into()?, - SignatureScheme::Null, - pcr_list, - ) - .context("unable to request quote from tpm")?; - - Ok(res) - } -} - -fn build_azure_attestation( - mut tpm: AzureTpm, - nonce: &[u8; 32], -) -> libattest::Result { - let cert = tpm.ak_cert().unwrap(); - let key = tpm.ak().unwrap(); - let report = tpm.hardware_report().unwrap(); - let (attest, signature) = tpm.quote(nonce).unwrap(); - - // convert tpm structure to wire compatible format - let marshaled = attest.marshall()?; - let (attest, _) = TpmsAttest::unmarshal(&marshaled)?; - - let signature = match signature { - Signature::RsaSsa(ref signature) => signature.signature().value(), - _ => libattest::bail!("unsupported signature algorithm"), - }; - - let signature = rsa::pkcs1v15::Signature::try_from(signature)?; - - let azure_trust = AzureTrust::new(signature, key, cert); - let azure_quote = AzureQuote::new(attest, report, azure_trust); - - Ok(azure_quote) -} +use azure_attest::host::vtpm::AzureTpm; fn main() { // ContextGap @@ -181,7 +10,7 @@ fn main() { context.set_sessions((Some(AuthSession::Password), None, None)); let tpm = AzureTpm::new(context); - let attestation = build_azure_attestation(tpm, &[0u8; 32]).unwrap(); + let attestation = azure_attest::host::azure_attest(tpm, &[0u8; 32]).unwrap(); let attestation = serde_json::to_string(&attestation).unwrap(); diff --git a/azure-attest/src/collateral.rs b/azure-attest/src/collateral.rs index b4c699ee..46c44e17 100644 --- a/azure-attest/src/collateral.rs +++ b/azure-attest/src/collateral.rs @@ -1,7 +1,7 @@ use snp_attest::{SevQuote, verify::SevQuoteVerifier}; use tdx_attest::{TdxQuote, verify::TdxQuoteVerifier}; -use crate::{AzureQuote, ParsedHardwareReport}; +use crate::quote::{AzureQuote, ParsedHardwareReport}; /// Construct the builder providing two async functions /// that fetch collateral and build a quote verifier diff --git a/azure-attest/src/host.rs b/azure-attest/src/host.rs new file mode 100644 index 00000000..35738f74 --- /dev/null +++ b/azure-attest/src/host.rs @@ -0,0 +1,35 @@ +use std::ops::Deref; + +use tpm2_protocol::{TpmUnmarshal, data::TpmsAttest}; +use tss_esapi::{structures::Signature, traits::Marshall}; + +use crate::{AzureQuote, nonce::AzureNonce, quote::AzureTrust}; + +pub mod vtpm; + +/// Performs attestation on an AzureTpm object +pub fn azure_attest( + mut tpm: vtpm::AzureTpm, + nonce: &AzureNonce, +) -> libattest::Result { + let cert = tpm.ak_cert().unwrap(); + let key = tpm.ak().unwrap(); + let report = tpm.hardware_report().unwrap(); + let (attest, signature) = tpm.quote(nonce.deref()).unwrap(); + + // convert tpm structure to wire compatible format + let marshaled = attest.marshall()?; + let (attest, _) = TpmsAttest::unmarshal(&marshaled)?; + + let signature = match signature { + Signature::RsaSsa(ref signature) => signature.signature().value(), + _ => libattest::bail!("unsupported signature algorithm"), + }; + + let signature = rsa::pkcs1v15::Signature::try_from(signature)?; + + let azure_trust = AzureTrust::new(signature, key, cert); + let azure_quote = AzureQuote::new(attest, report, azure_trust); + + Ok(azure_quote) +} diff --git a/azure-attest/src/host/vtpm.rs b/azure-attest/src/host/vtpm.rs new file mode 100644 index 00000000..9c039c95 --- /dev/null +++ b/azure-attest/src/host/vtpm.rs @@ -0,0 +1,145 @@ +use std::ops::Deref; + +use libattest::error::Context; +use rsa::{BoxedUint, pkcs1v15::VerifyingKey}; +use sha2::Sha256; +use tss_esapi::{ + abstraction::nv, + handles::{KeyHandle, NvIndexTpmHandle}, + interface_types::{algorithm::HashingAlgorithm, resource_handles::NvAuth}, + structures::{ + Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, RsaExponent, Signature, + SignatureScheme, + }, +}; + +use x509_cert::der::{Decode, SliceReader}; + +use crate::report::AttestationReport; + +pub struct AzureTpm { + context: tss_esapi::Context, +} + +impl AzureTpm { + pub fn new(context: tss_esapi::Context) -> Self { + Self { context } + } + + const AK_CERT_IDX: u32 = 0x01C101D0; + const VTPM_HCL_AKPUB_PERSISTENT_HANDLE: u32 = 0x81000003; + const HARDWARE_REPORT_IDX: u32 = 0x01400001; + + pub const VTPM_DEFAULT_PCR_SLOTS: [PcrSlot; 24] = [ + PcrSlot::Slot0, + PcrSlot::Slot1, + PcrSlot::Slot2, + PcrSlot::Slot3, + PcrSlot::Slot4, + PcrSlot::Slot5, + PcrSlot::Slot6, + PcrSlot::Slot7, + PcrSlot::Slot8, + PcrSlot::Slot9, + PcrSlot::Slot10, + PcrSlot::Slot11, + PcrSlot::Slot12, + PcrSlot::Slot13, + PcrSlot::Slot14, + PcrSlot::Slot15, + PcrSlot::Slot16, + PcrSlot::Slot17, + PcrSlot::Slot18, + PcrSlot::Slot19, + PcrSlot::Slot20, + PcrSlot::Slot21, + PcrSlot::Slot22, + PcrSlot::Slot23, + ]; + + fn tpm_read(&mut self, index: u32) -> Result, tss_esapi::Error> { + let handle = NvIndexTpmHandle::new(index)?; + let read = nv::read_full(&mut self.context, NvAuth::Owner, handle)?; + + Ok(read) + } + + pub(super) fn hardware_report(&mut self) -> anyhow::Result { + let read = self.tpm_read(Self::HARDWARE_REPORT_IDX)?; + let report = crate::report::deserialize_attestation_report(&read)?; + + Ok(report) + } + + pub(super) fn ak_cert(&mut self) -> anyhow::Result { + let ak_cert = self + .tpm_read(Self::AK_CERT_IDX) + .context("unable to read ak_cert from tpm")?; + + let mut reader = SliceReader::new(&ak_cert)?; + + x509_cert::Certificate::decode(&mut reader) + .context("unable to decode certificate from DER") + .map_err(Into::into) + } + + fn ak_handle(&mut self) -> Result { + let handle = Self::VTPM_HCL_AKPUB_PERSISTENT_HANDLE.try_into()?; + + let public_key_handle = self + .context + .execute_without_session(|ctx| ctx.tr_from_tpm_public(handle))? + .into(); + + Ok(public_key_handle) + } + + pub(super) fn ak(&mut self) -> anyhow::Result> { + let public_key_handle = self.ak_handle()?; + let (public, _, _) = self + .context + .execute_without_session(|ctx| ctx.read_public(public_key_handle))?; + + let Public::Rsa { + unique, parameters, .. + } = public + else { + anyhow::bail!("received public key that was not rsa"); + }; + + let exponent = match parameters.exponent() { + RsaExponent::ZERO_EXPONENT => 65537, + exp => exp.value(), + }; + + rsa::RsaPublicKey::new( + BoxedUint::from_be_slice_vartime(unique.deref()), + exponent.into(), + ) + .context("unable to decode public key components from tpm") + .map(VerifyingKey::new) + .map_err(Into::into) + } + + pub(super) fn quote(mut self, nonce: impl AsRef<[u8]>) -> anyhow::Result<(Attest, Signature)> { + let key_handle = self.ak_handle()?; + + let pcr_list = PcrSelectionListBuilder::new() + .with_selection(HashingAlgorithm::Sha256, &Self::VTPM_DEFAULT_PCR_SLOTS) + .with_size_of_select(PcrSelectSize::default()) + .build() + .unwrap(); + + let res = self + .context + .quote( + key_handle, + nonce.as_ref().try_into()?, + SignatureScheme::Null, + pcr_list, + ) + .context("unable to request quote from tpm")?; + + Ok(res) + } +} diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 60ab791c..1645c501 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -1,69 +1,12 @@ pub mod collateral; +pub mod nonce; +pub mod quote; pub mod report; -mod serde_cert; -mod serde_tpm; -pub mod verify; -use serde::{Deserialize, Serialize}; -use sha2::Sha256; -use snp_attest::SevQuote; -use tdx_attest::TdxQuote; -use tpm2_protocol::data::TpmsAttest; +#[cfg(feature = "host")] +pub mod host; -use crate::report::{AttestationReport, HardwareReport}; +mod serde; -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct AzureTrust { - quote_signature: rsa::pkcs1v15::Signature, - ak_key: rsa::pkcs1v15::VerifyingKey, - - #[serde(with = "serde_cert")] - ak_cert: x509_cert::Certificate, -} - -impl AzureTrust { - pub fn new( - quote_signature: rsa::pkcs1v15::Signature, - ak_key: rsa::pkcs1v15::VerifyingKey, - ak_cert: x509_cert::Certificate, - ) -> Self { - Self { - quote_signature, - ak_key, - ak_cert, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct AzureQuote { - #[serde(with = "serde_tpm")] - quote: TpmsAttest, - hardware_report: AttestationReport, - - trust: AzureTrust, -} - -impl AzureQuote { - pub fn new(quote: TpmsAttest, hardware_report: AttestationReport, trust: AzureTrust) -> Self { - Self { - quote, - hardware_report, - trust, - } - } - - fn parse_hardware_report(&self) -> libattest::Result { - let report = match &self.hardware_report.payload { - HardwareReport::Tdx(items) => ParsedHardwareReport::Tdx(TdxQuote::from_bytes(items)?), - HardwareReport::Sev(items) => ParsedHardwareReport::Sev(SevQuote::new(items)?), - }; - - Ok(report) - } -} - -enum ParsedHardwareReport { - Tdx(TdxQuote), - Sev(SevQuote), -} +pub use quote::AzureQuote; +pub use quote::verify::verify; diff --git a/azure-attest/src/nonce.rs b/azure-attest/src/nonce.rs new file mode 100644 index 00000000..54d56c09 --- /dev/null +++ b/azure-attest/src/nonce.rs @@ -0,0 +1 @@ +libattest::define_nonce_type!(#[derive(Debug, PartialEq, Eq)] pub AzureNonce, 64); diff --git a/azure-attest/src/quote.rs b/azure-attest/src/quote.rs new file mode 100644 index 00000000..638637d6 --- /dev/null +++ b/azure-attest/src/quote.rs @@ -0,0 +1,65 @@ +pub mod verify; + +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use snp_attest::SevQuote; +use tdx_attest::TdxQuote; +use tpm2_protocol::data::TpmsAttest; + +use crate::report::{AttestationReport, HardwareReport}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct AzureTrust { + quote_signature: rsa::pkcs1v15::Signature, + ak_key: rsa::pkcs1v15::VerifyingKey, + + #[serde(with = "crate::serde::serde_cert")] + ak_cert: x509_cert::Certificate, +} + +impl AzureTrust { + pub fn new( + quote_signature: rsa::pkcs1v15::Signature, + ak_key: rsa::pkcs1v15::VerifyingKey, + ak_cert: x509_cert::Certificate, + ) -> Self { + Self { + quote_signature, + ak_key, + ak_cert, + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AzureQuote { + #[serde(with = "crate::serde::serde_tpm")] + quote: TpmsAttest, + hardware_report: AttestationReport, + + trust: AzureTrust, +} + +impl AzureQuote { + pub fn new(quote: TpmsAttest, hardware_report: AttestationReport, trust: AzureTrust) -> Self { + Self { + quote, + hardware_report, + trust, + } + } + + pub(crate) fn parse_hardware_report(&self) -> libattest::Result { + let report = match &self.hardware_report.payload { + HardwareReport::Tdx(items) => ParsedHardwareReport::Tdx(TdxQuote::from_bytes(items)?), + HardwareReport::Sev(items) => ParsedHardwareReport::Sev(SevQuote::new(items)?), + }; + + Ok(report) + } +} + +pub(crate) enum ParsedHardwareReport { + Tdx(TdxQuote), + Sev(SevQuote), +} diff --git a/azure-attest/src/verify.rs b/azure-attest/src/quote/verify.rs similarity index 80% rename from azure-attest/src/verify.rs rename to azure-attest/src/quote/verify.rs index 74cd51b0..8888dad3 100644 --- a/azure-attest/src/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -1,9 +1,16 @@ +use std::ops::Deref; + use base64::Engine; use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet}; use libattest::{ByteNonce, error::Context}; use rsa::{BoxedUint, signature::Verifier}; -use crate::{AzureQuote, ParsedHardwareReport, collateral::ReportVerifier, report::RuntimeClaims}; +use crate::{ + collateral::ReportVerifier, + nonce::AzureNonce, + quote::{AzureQuote, ParsedHardwareReport}, + report::RuntimeClaims, +}; pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { use tpm2_protocol::{TpmMarshal, TpmWriter}; @@ -114,7 +121,28 @@ pub fn verify_report_digest( Ok(()) } -pub fn verify(azure_quote: AzureQuote, report_verifier: ReportVerifier) -> libattest::Result<()> { +fn verify_quote_nonce(azure_quote: &AzureQuote, nonce: &AzureNonce) -> libattest::Result<()> { + let tpm_nonce = azure_quote.quote.extra_data; + + // try to fit the tpm nonce into our azurenonce type. + // tpm nonces can be arbitrary sized (MAX 64) so we + // have to check manually + let tpm_nonce = <&[u8; 64]>::try_from(tpm_nonce.deref()) + .map(AzureNonce::from) + .context("received nonce does not fit into defined AzureNonce type")?; + + if !nonce.eq(&tpm_nonce) { + libattest::bail!(exposed: "Mismatched vTPM nonce"); + } + + Ok(()) +} + +pub fn verify( + azure_quote: AzureQuote, + report_verifier: ReportVerifier, + nonce: &AzureNonce, +) -> libattest::Result<()> { // 1: verify that AK signs Quote through signature verify_quote_signature(&azure_quote)?; // 2: verify that the hardware report signs report data @@ -122,6 +150,8 @@ pub fn verify(azure_quote: AzureQuote, report_verifier: ReportVerifier) -> libat // 3: verify that report data contains the correct ak key, // sprouting azure trust from SEV/TDX verify_runtime_data(&azure_quote)?; + // 4: Verify that user supplied nonce and received quote nonce match + verify_quote_nonce(&azure_quote, nonce)?; Ok(()) } diff --git a/azure-attest/src/serde.rs b/azure-attest/src/serde.rs new file mode 100644 index 00000000..8eba2048 --- /dev/null +++ b/azure-attest/src/serde.rs @@ -0,0 +1,2 @@ +pub mod serde_cert; +pub mod serde_tpm; diff --git a/azure-attest/src/serde_cert.rs b/azure-attest/src/serde/serde_cert.rs similarity index 100% rename from azure-attest/src/serde_cert.rs rename to azure-attest/src/serde/serde_cert.rs diff --git a/azure-attest/src/serde_tpm.rs b/azure-attest/src/serde/serde_tpm.rs similarity index 100% rename from azure-attest/src/serde_tpm.rs rename to azure-attest/src/serde/serde_tpm.rs diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs index ead9884f..30ad86ac 100644 --- a/azure-attest/tests/decode.rs +++ b/azure-attest/tests/decode.rs @@ -26,5 +26,5 @@ async fn verify_attestation() { .await .unwrap(); - azure_attest::verify::verify(quote, verifier).unwrap(); + azure_attest::verify(quote, verifier).unwrap(); } diff --git a/libattest/src/lib.rs b/libattest/src/lib.rs index db100c54..b446e7df 100644 --- a/libattest/src/lib.rs +++ b/libattest/src/lib.rs @@ -7,6 +7,68 @@ pub use modules::*; pub type Result = std::result::Result; +#[macro_export] +macro_rules! define_nonce_type { + ($(#[$meta:meta])* $vis:vis $name:ident, $size:literal $(,)?) => { + $(#[$meta])* + #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen)] + $vis struct $name($crate::ByteNonce<$size>); + + impl $name { + pub const SIZE: usize = $size; + + pub fn new(byte_nonce: $crate::ByteNonce<$size>) -> Self { + Self(byte_nonce) + } + } + + impl From<$crate::ByteNonce<$size>> for $name { + fn from(value: $crate::ByteNonce<$size>) -> Self { + Self::new(value) + } + } + + impl From<&[u8; $size]> for $name { + fn from(value: &[u8; $size]) -> Self { + Self::new(value.into()) + } + } + + impl From> for $name { + fn from(value: Box<[u8; $size]>) -> Self { + Self::new(value.into()) + } + } + + impl std::ops::Deref for $name { + type Target = $crate::ByteNonce<$size>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #[cfg(not(target_family = "wasm"))] + impl $name { + pub fn generate() -> Self { + Self($crate::ByteNonce::<$size>::generate()) + } + } + + #[cfg(target_family = "wasm")] + #[wasm_bindgen::prelude::wasm_bindgen] + impl $name { + pub fn generate() -> Self { + Self($crate::ByteNonce::<$size>::generate()) + } + + pub fn to_hex(&self) -> String { + self.0.to_hex() + } + } + }; +} + #[derive(Debug, PartialEq, Eq)] pub struct ByteNonce(Box<[u8; N]>); @@ -24,9 +86,9 @@ impl ByteNonce { } } -impl From<[u8; N]> for ByteNonce { - fn from(value: [u8; N]) -> Self { - Self(Box::new(value)) +impl From<&[u8; N]> for ByteNonce { + fn from(value: &[u8; N]) -> Self { + Self(Box::new(*value)) } } @@ -55,9 +117,3 @@ impl AsRef<[u8; N]> for ByteNonce { self.0.as_ref() } } - -// impl From> for ByteNonce { -// fn from(value: Box<[u8; N]>) -> Self { -// Self(value) -// } -// } diff --git a/libattest/src/validation.rs b/libattest/src/validation.rs index cc00088c..6e0e27c4 100644 --- a/libattest/src/validation.rs +++ b/libattest/src/validation.rs @@ -130,7 +130,10 @@ impl Validator { /// gets the rego query and input from `impl Claim` and then /// drives the engine to verify the query - pub fn verify_claim(&self, claims: impl IntoClaims) -> Result { + pub fn verify_claim( + &self, + claims: impl IntoClaims, + ) -> Result { let claims = claims.into_claims(); // avois polluting the engine for further verifications // and allows us to have this method &self diff --git a/nvidia-attest/src/nonce.rs b/nvidia-attest/src/nonce.rs index bfe26d8f..7e64cda4 100644 --- a/nvidia-attest/src/nonce.rs +++ b/nvidia-attest/src/nonce.rs @@ -1,27 +1 @@ -use std::ops::Deref; - -#[cfg(target_family = "wasm")] -use wasm_bindgen::prelude::*; - -#[derive(Debug, PartialEq, Eq)] -#[cfg_attr(target_family = "wasm", wasm_bindgen)] -pub struct NvidiaNonce(libattest::ByteNonce<32>); - -impl Deref for NvidiaNonce { - type Target = libattest::ByteNonce<32>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg_attr(target_family = "wasm", wasm_bindgen)] -impl NvidiaNonce { - pub fn generate() -> Self { - Self(libattest::ByteNonce::generate()) - } - - pub fn to_hex(&self) -> String { - self.0.to_hex() - } -} +libattest::define_nonce_type!(#[derive(Debug, PartialEq, Eq)] pub NvidiaNonce, 32); diff --git a/snp-attest/examples/read-report.rs b/snp-attest/examples/read-report.rs index d57ea698..4c7b9b6f 100644 --- a/snp-attest/examples/read-report.rs +++ b/snp-attest/examples/read-report.rs @@ -2,7 +2,7 @@ use sev::parser::ByteParser; fn main() { let report = sev::firmware::guest::AttestationReport::from_bytes( - &std::fs::read(std::env::args().nth(1).unwrap()).unwrap() + &std::fs::read(std::env::args().nth(1).unwrap()).unwrap(), ); println!("{:?}", report); -} \ No newline at end of file +} diff --git a/snp-attest/src/nonce.rs b/snp-attest/src/nonce.rs index 700af105..be51f24c 100644 --- a/snp-attest/src/nonce.rs +++ b/snp-attest/src/nonce.rs @@ -1,30 +1 @@ -use std::ops::Deref; - -#[cfg(target_family = "wasm")] -use wasm_bindgen::prelude::*; - -#[cfg_attr(target_family = "wasm", wasm_bindgen)] -pub struct SevNonce(libattest::ByteNonce<64>); - -impl Deref for SevNonce { - type Target = libattest::ByteNonce<64>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg_attr(target_family = "wasm", wasm_bindgen)] -impl SevNonce { - pub fn new(byte_nonce: libattest::ByteNonce<64>) -> Self { - Self(byte_nonce) - } - - pub fn generate() -> Self { - Self(libattest::ByteNonce::generate()) - } - - pub fn to_hex(&self) -> String { - self.0.to_hex() - } -} +libattest::define_nonce_type!(pub SevNonce, 64); diff --git a/tdx-attest/src/nonce.rs b/tdx-attest/src/nonce.rs index 867ebc06..bb4cd977 100644 --- a/tdx-attest/src/nonce.rs +++ b/tdx-attest/src/nonce.rs @@ -1,39 +1 @@ -use std::ops::Deref; - -use libattest::ByteNonce; - -#[cfg(target_family = "wasm")] -use wasm_bindgen::prelude::*; - -#[cfg_attr(target_family = "wasm", wasm_bindgen)] -pub struct TdxNonce(libattest::ByteNonce<64>); - -impl Deref for TdxNonce { - type Target = libattest::ByteNonce<64>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg_attr(target_family = "wasm", wasm_bindgen)] -impl TdxNonce { - pub fn new(byte_nonce: libattest::ByteNonce<64>) -> Self { - Self(byte_nonce) - } - - #[cfg_attr(target_family = "wasm", wasm_bindgen(constructor))] - pub fn generate() -> Self { - Self(libattest::ByteNonce::generate()) - } - - pub fn to_hex(&self) -> String { - self.0.to_hex() - } -} - -impl From> for TdxNonce { - fn from(value: ByteNonce<64>) -> Self { - Self(value) - } -} +libattest::define_nonce_type!(pub TdxNonce, 64); From 28f0f86b323ba59d5cea1f39a1b82bfbc9107eb3 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:35:14 +0000 Subject: [PATCH 18/39] fixed nonce, work on host module --- azure-attest/src/bin/generate.rs | 14 ++-- azure-attest/src/host.rs | 2 +- azure-attest/src/host/vtpm.rs | 19 +++++- azure-attest/tests/decode.rs | 5 +- libattest/src/lib.rs | 113 +------------------------------ libattest/src/nonce.rs | 108 +++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 127 deletions(-) create mode 100644 libattest/src/nonce.rs diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index 9327fcc5..dea71ed2 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -1,16 +1,10 @@ -use std::ops::Deref; - -use azure_attest::host::vtpm::AzureTpm; +use azure_attest::{host::vtpm::AzureTpmCtx, nonce::AzureNonce}; fn main() { - // ContextGap - let mut context = - tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default())).unwrap(); - - context.set_sessions((Some(AuthSession::Password), None, None)); + let tpm = AzureTpmCtx::default_context().unwrap(); + let nonce = AzureNonce::generate(); - let tpm = AzureTpm::new(context); - let attestation = azure_attest::host::azure_attest(tpm, &[0u8; 32]).unwrap(); + let attestation = azure_attest::host::azure_attest(tpm, &nonce).unwrap(); let attestation = serde_json::to_string(&attestation).unwrap(); diff --git a/azure-attest/src/host.rs b/azure-attest/src/host.rs index 35738f74..cd301ff0 100644 --- a/azure-attest/src/host.rs +++ b/azure-attest/src/host.rs @@ -9,7 +9,7 @@ pub mod vtpm; /// Performs attestation on an AzureTpm object pub fn azure_attest( - mut tpm: vtpm::AzureTpm, + mut tpm: vtpm::AzureTpmCtx, nonce: &AzureNonce, ) -> libattest::Result { let cert = tpm.ak_cert().unwrap(); diff --git a/azure-attest/src/host/vtpm.rs b/azure-attest/src/host/vtpm.rs index 9c039c95..7bd1b46a 100644 --- a/azure-attest/src/host/vtpm.rs +++ b/azure-attest/src/host/vtpm.rs @@ -6,7 +6,9 @@ use sha2::Sha256; use tss_esapi::{ abstraction::nv, handles::{KeyHandle, NvIndexTpmHandle}, - interface_types::{algorithm::HashingAlgorithm, resource_handles::NvAuth}, + interface_types::{ + algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, + }, structures::{ Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, RsaExponent, Signature, SignatureScheme, @@ -17,15 +19,26 @@ use x509_cert::der::{Decode, SliceReader}; use crate::report::AttestationReport; -pub struct AzureTpm { +pub struct AzureTpmCtx { context: tss_esapi::Context, } -impl AzureTpm { +impl AzureTpmCtx { pub fn new(context: tss_esapi::Context) -> Self { Self { context } } + pub fn default_context() -> tss_esapi::Result { + use tss_esapi::tcti_ldr::DeviceConfig; + use tss_esapi::tcti_ldr::TctiNameConf; + + let mut context = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?; + + context.set_sessions((Some(AuthSession::Password), None, None)); + + Ok(Self { context }) + } + const AK_CERT_IDX: u32 = 0x01C101D0; const VTPM_HCL_AKPUB_PERSISTENT_HANDLE: u32 = 0x81000003; const HARDWARE_REPORT_IDX: u32 = 0x01400001; diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs index 30ad86ac..959990f3 100644 --- a/azure-attest/tests/decode.rs +++ b/azure-attest/tests/decode.rs @@ -1,4 +1,4 @@ -use azure_attest::collateral::ReportVerifierBuilder; +use azure_attest::{collateral::ReportVerifierBuilder, nonce::AzureNonce}; use libattest::error::Context; use snp_attest::{kds::Kds, verify::SevQuoteVerifier}; @@ -26,5 +26,6 @@ async fn verify_attestation() { .await .unwrap(); - azure_attest::verify(quote, verifier).unwrap(); + let nonce = AzureNonce::from([0u8; 64]); + azure_attest::verify(quote, verifier, &nonce).unwrap(); } diff --git a/libattest/src/lib.rs b/libattest/src/lib.rs index b446e7df..f59c85a5 100644 --- a/libattest/src/lib.rs +++ b/libattest/src/lib.rs @@ -1,119 +1,10 @@ pub mod error; pub mod modules; +pub mod nonce; pub mod quote; pub mod validation; pub use modules::*; +pub use nonce::*; pub type Result = std::result::Result; - -#[macro_export] -macro_rules! define_nonce_type { - ($(#[$meta:meta])* $vis:vis $name:ident, $size:literal $(,)?) => { - $(#[$meta])* - #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen)] - $vis struct $name($crate::ByteNonce<$size>); - - impl $name { - pub const SIZE: usize = $size; - - pub fn new(byte_nonce: $crate::ByteNonce<$size>) -> Self { - Self(byte_nonce) - } - } - - impl From<$crate::ByteNonce<$size>> for $name { - fn from(value: $crate::ByteNonce<$size>) -> Self { - Self::new(value) - } - } - - impl From<&[u8; $size]> for $name { - fn from(value: &[u8; $size]) -> Self { - Self::new(value.into()) - } - } - - impl From> for $name { - fn from(value: Box<[u8; $size]>) -> Self { - Self::new(value.into()) - } - } - - impl std::ops::Deref for $name { - type Target = $crate::ByteNonce<$size>; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[cfg(not(target_family = "wasm"))] - impl $name { - pub fn generate() -> Self { - Self($crate::ByteNonce::<$size>::generate()) - } - } - - #[cfg(target_family = "wasm")] - #[wasm_bindgen::prelude::wasm_bindgen] - impl $name { - pub fn generate() -> Self { - Self($crate::ByteNonce::<$size>::generate()) - } - - pub fn to_hex(&self) -> String { - self.0.to_hex() - } - } - }; -} - -#[derive(Debug, PartialEq, Eq)] -pub struct ByteNonce(Box<[u8; N]>); - -impl ByteNonce { - pub fn generate() -> Self { - let mut bytes = Box::new([0u8; N]); - - getrandom::fill(bytes.as_mut_slice()).unwrap(); - - Self(bytes) - } - - pub fn to_hex(&self) -> String { - hex::encode_upper(self.0.as_ref()) - } -} - -impl From<&[u8; N]> for ByteNonce { - fn from(value: &[u8; N]) -> Self { - Self(Box::new(*value)) - } -} - -impl From> for ByteNonce { - fn from(value: Box<[u8; N]>) -> Self { - Self(value) - } -} - -impl std::ops::Deref for ByteNonce { - type Target = [u8; N]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef<[u8]> for ByteNonce { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsRef<[u8; N]> for ByteNonce { - fn as_ref(&self) -> &[u8; N] { - self.0.as_ref() - } -} diff --git a/libattest/src/nonce.rs b/libattest/src/nonce.rs new file mode 100644 index 00000000..e4535c84 --- /dev/null +++ b/libattest/src/nonce.rs @@ -0,0 +1,108 @@ +#[macro_export] +macro_rules! define_nonce_type { + ($(#[$meta:meta])* $vis:vis $name:ident, $size:literal $(,)?) => { + $(#[$meta])* + #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen)] + $vis struct $name($crate::ByteNonce<$size>); + + impl $name { + pub const SIZE: usize = $size; + + pub fn new(byte_nonce: $crate::ByteNonce<$size>) -> Self { + Self(byte_nonce) + } + } + + impl From<$crate::ByteNonce<$size>> for $name { + fn from(value: $crate::ByteNonce<$size>) -> Self { + Self::new(value) + } + } + + impl From<[u8; $size]> for $name { + fn from(value: [u8; $size]) -> Self { + Self::new((&value).into()) + } + } + + impl From<&[u8; $size]> for $name { + fn from(value: &[u8; $size]) -> Self { + Self::new(value.into()) + } + } + + impl From> for $name { + fn from(value: Box<[u8; $size]>) -> Self { + Self::new(value.into()) + } + } + + impl std::ops::Deref for $name { + type Target = $crate::ByteNonce<$size>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen)] + impl $name { + pub fn generate() -> Self { + Self($crate::ByteNonce::<$size>::generate()) + } + + pub fn to_hex(&self) -> String { + self.0.to_hex() + } + } + }; +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ByteNonce(Box<[u8; N]>); + +impl ByteNonce { + pub fn generate() -> Self { + let mut bytes = Box::new([0u8; N]); + + getrandom::fill(bytes.as_mut_slice()).unwrap(); + + Self(bytes) + } + + pub fn to_hex(&self) -> String { + hex::encode_upper(self.0.as_ref()) + } +} + +impl From<&[u8; N]> for ByteNonce { + fn from(value: &[u8; N]) -> Self { + Self(Box::new(*value)) + } +} + +impl From> for ByteNonce { + fn from(value: Box<[u8; N]>) -> Self { + Self(value) + } +} + +impl std::ops::Deref for ByteNonce { + type Target = [u8; N]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for ByteNonce { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsRef<[u8; N]> for ByteNonce { + fn as_ref(&self) -> &[u8; N] { + self.0.as_ref() + } +} From 16631bdbc223f9c29debc0c0b21ca50b4d44387a Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:39:18 +0000 Subject: [PATCH 19/39] undersized nonce to 32 bytes --- azure-attest/src/nonce.rs | 2 +- azure-attest/src/quote/verify.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-attest/src/nonce.rs b/azure-attest/src/nonce.rs index 54d56c09..dfc3699f 100644 --- a/azure-attest/src/nonce.rs +++ b/azure-attest/src/nonce.rs @@ -1 +1 @@ -libattest::define_nonce_type!(#[derive(Debug, PartialEq, Eq)] pub AzureNonce, 64); +libattest::define_nonce_type!(#[derive(Debug, PartialEq, Eq)] pub AzureNonce, 32); diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index 8888dad3..2314c450 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -127,7 +127,7 @@ fn verify_quote_nonce(azure_quote: &AzureQuote, nonce: &AzureNonce) -> libattest // try to fit the tpm nonce into our azurenonce type. // tpm nonces can be arbitrary sized (MAX 64) so we // have to check manually - let tpm_nonce = <&[u8; 64]>::try_from(tpm_nonce.deref()) + let tpm_nonce = <&[u8; _]>::try_from(tpm_nonce.deref()) .map(AzureNonce::from) .context("received nonce does not fit into defined AzureNonce type")?; From d7e3132ee8f38de434c3cc81e692fdde574379b3 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Thu, 25 Jun 2026 15:00:33 +0000 Subject: [PATCH 20/39] fixed attestation tests --- azure-attest/tests/attestation.json | 188 ++++++++++++++-------------- azure-attest/tests/decode.rs | 3 +- 2 files changed, 95 insertions(+), 96 deletions(-) diff --git a/azure-attest/tests/attestation.json b/azure-attest/tests/attestation.json index ebd7f460..405c9a32 100644 --- a/azure-attest/tests/attestation.json +++ b/azure-attest/tests/attestation.json @@ -80,10 +80,10 @@ 0, 0, 0, - 18, - 67, - 14, - 102, + 32, + 65, + 69, + 23, 0, 0, 0, @@ -5150,54 +5150,54 @@ 0, 0, 0, - 22, - 208, - 158, - 143, - 77, - 80, + 12, + 101, + 219, + 216, + 137, + 73, + 231, + 149, 177, - 128, - 75, - 44, - 80, - 112, + 42, + 38, + 251, + 248, + 19, + 214, + 124, + 56, + 23, + 245, + 186, + 62, + 179, + 247, + 9, + 132, + 132, 159, - 149, - 152, - 83, - 223, - 108, - 4, - 41, - 233, - 117, - 29, - 145, - 2, + 129, + 203, + 13, + 98, + 54, + 89, + 249, + 86, 181, - 217, - 212, - 220, - 14, - 241, - 96, - 165, - 59, - 192, - 80, - 36, - 24, - 34, - 154, - 175, - 49, - 41, - 8, - 68, - 12, - 195, - 126, + 242, + 21, + 209, + 48, + 210, + 159, + 194, + 117, + 67, + 91, + 47, + 131, 0, 0, 0, @@ -5222,54 +5222,54 @@ 0, 0, 0, - 253, - 124, + 33, + 177, + 132, + 103, + 140, + 25, + 230, + 225, + 27, + 57, + 68, + 52, 152, - 42, - 241, - 56, - 26, + 1, + 215, 29, - 232, - 84, - 141, - 172, - 201, - 173, - 54, - 118, - 115, - 53, - 167, - 164, - 221, - 201, - 75, - 8, + 207, + 209, + 61, + 210, + 43, + 42, + 24, + 36, 121, + 226, + 90, + 87, + 127, + 161, + 74, + 244, 140, - 138, - 19, - 79, - 215, - 230, - 221, - 12, - 163, - 174, - 1, - 221, - 102, - 95, - 79, + 51, + 236, + 145, + 217, + 133, + 158, + 154, + 51, + 45, 41, - 26, - 6, - 196, - 143, - 25, - 204, - 253, + 21, + 218, + 228, + 105, + 7, 0, 0, 0, @@ -6787,8 +6787,8 @@ } }, "trust": { - "quote_signature": "4908a696e37ac0b22c6fa8c178e9def32228bd2697b3bd4cd6856fc04b14cda904f76583bfa1660cdc3c222fa38bd5dbfbe8dd8e4415ac31a4dea1f4b46c302a97f3dddd03d8e46498e59c86e40738bcb431495e86debdb31253c00dfbc0cb61088d2bda2d40b470483ec2a235dd6d10b6f058bb134e85371d4b5e6764919412c9bd6ca878ed9574b335e99f0b97fb37b9ee6b3de3f45ff6b10a820fa77130e50dd9ef720029692138beac2f64cf45072aca9f5b95c4ebdf29151bc48d0dd7dd89d27f925aa5cac7e6fc20e39746eccd212ea6fa8f8fe61afe7899e15cfdb2f2acee7d11fe67445d45b5542ac27d1bfdb4b336ce2afda198b6c5ceefbf2873d0", + "quote_signature": "725193221d096d2ad86112329c8e9c75722d425ae9a26b6c140ed1a1acd359e9446c0fe4c3ae4c01047dfba3a0291fc501b446ac3cfb8fc898589c7d6284145e89175e5ab95ef8f8fdf2dde440ced80f33d3a3b8d2a93143aefeec3e8cef810d81c9d20379dc3296c5ed8b1aa7abc2254534a6fd62fa0bc922ae842fa22f1c190d7688b56f064a3dba36dbc5315a64ae72a67de08ca1982af46e21c88117bf373fc642b7e365d8f1c2f9e970fcd8502993757f82d5f21d23b7dd042a185bb741d0927e291e07a2ded674b412411a061a2fe28cae4718d4c6cef3dda9134950161fdbfdbeec7c573b9a3fe5b902fe0dc0b76f00ab15f374560fa51474ab1dbf2d", "ak_key": "30820122300d06092a864886f70d01010105000382010f003082010a0282010100e0fced7c0000fc0ab6e44476a80d61df331cdbaad92b2449c6927d6aebc5b7d2b28d96d23295e5f26c0061f8a69b84c28244931c8a5cd118e786c08b6f44cb0d9d5c5dadcf5402fa4f88f09e53be74ef87b67b05371910e356d5b29ff70abccfe036f2d30007701104c16b123307675412ee49cd996a80c195d45f9dc8f6e6e29524073d3598cd849d921b7d4410f74c042b561d9d66f6258e0defd0988e96970acb19ba47c7efa57d484479cb2865f1ea8df8cac7ed6b379456a5faef86942c310a4837da8f9e41086d3a1d44e997396036da9ef39877d8aca4e3bd4f68084b113b2dfebcbd5f8bec0280fa4578a4bcf629cd62294e860e8e8142983b6c56dd0203010001", - "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7TCCAtWgAwIBAgIQCbL+a6sfjNW6W4YMksPqfTANBgkqhkiG9w0BAQsFADAl\nMSMwIQYDVQQDExpHbG9iYWwgVmlydHVhbCBUUE0gQ0EgLSAwMzAeFw0yNjA2MjIw\nMDAwMDBaFw0yNzA0MjEwMDAwMDBaMDgxNjA0BgNVBAMTLWRiOTZmYzY4NDI3MS5D\nb25maWRlbnRpYWxWTS5BenVyZS53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOD87XwAAPwKtuREdqgNYd8zHNuq2SskScaSfWrrxbfS\nso2W0jKV5fJsAGH4ppuEwoJEkxyKXNEY54bAi29Eyw2dXF2tz1QC+k+I8J5TvnTv\nh7Z7BTcZEONW1bKf9wq8z+A28tMAB3ARBMFrEjMHZ1QS7knNmWqAwZXUX53I9ubi\nlSQHPTWYzYSdkht9RBD3TAQrVh2dZvYljg3v0JiOlpcKyxm6R8fvpX1IRHnLKGXx\n6o34ysftazeUVqX674aULDEKSDfaj55BCG06HUTplzlgNtqe85h32Kyk471PaAhL\nETst/ry9X4vsAoD6RXikvPYpzWIpToYOjoFCmDtsVt0CAwEAAaOCAQQwggEAMA4G\nA1UdDwEB/wQEAwIHgDAYBgNVHSAEETAPMA0GCysGAQQBgjdsgUgBMBwGA1UdJQQV\nMBMGCisGAQQBgjcKAwwGBWeBBQgDMB0GA1UdDgQWBBSvntEWzk1aLnxd0a06AG7c\nUqrbMTAaBgorBgEEAYI3DQIDBAwWCjYuMi45MjAwLjIwWgYJKwYBBAGCNxUUBE0w\nSwIBBQwPQU1TMDkxMDcyNzEwMDA1DBpXT1JLR1JPVVBcQU1TMDkxMDcyNzEwMDA1\nJAwZVnRwbU1hbmFnZW1lbnRTZXJ2aWNlLmV4ZTAfBgNVHSMEGDAWgBRnCYb4+YFe\nk635yWTnFAramcsKLTANBgkqhkiG9w0BAQsFAAOCAQEAmho7+METB8uSNn/tNSzj\nut6xAqeprztQY+aUUd1BJzT+AXJqm6Ikqq5q7ASmxObcbpI6nxbSDyBuAR0vJeWY\nTMUuN7NxvyDz0TaredKmiGSmw61JgMEmxlQvzsWRJa6m9vV+TKs0opnmjYUR1XtT\nbhg9KRSNMAWP7VDPx9EIJEOpGQYcr+rhhj1YJy8nWwXiNQoYmKBFTdOxyWjgHs18\nU+f8WgneS1FCn0becjq/dFK/GRN/PsFhinb4aljVIdIMwAHE3U4MoVbjtW2SZqqi\nzl77FdvDTOhzSCaL81BgjX1tkZaOF9EnrfOPjugdl1S/3pCU/TKCHR6iFU71NfZk\ndw==\n-----END CERTIFICATE-----\n" + "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7jCCAtagAwIBAgIRAKgcqw7JzCPoTCtF847wqgEwDQYJKoZIhvcNAQELBQAw\nJTEjMCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDMwHhcNMjYwNjI1\nMDAwMDAwWhcNMjcwNDIxMDAwMDAwWjA4MTYwNAYDVQQDEy02MGY5YmFmNjEyZmQu\nQ29uZmlkZW50aWFsVk0uQXp1cmUud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDg/O18AAD8CrbkRHaoDWHfMxzbqtkrJEnGkn1q68W3\n0rKNltIyleXybABh+KabhMKCRJMcilzRGOeGwItvRMsNnVxdrc9UAvpPiPCeU750\n74e2ewU3GRDjVtWyn/cKvM/gNvLTAAdwEQTBaxIzB2dUEu5JzZlqgMGV1F+dyPbm\n4pUkBz01mM2EnZIbfUQQ90wEK1YdnWb2JY4N79CYjpaXCssZukfH76V9SER5yyhl\n8eqN+MrH7Ws3lFal+u+GlCwxCkg32o+eQQhtOh1E6Zc5YDbanvOYd9ispOO9T2gI\nSxE7Lf68vV+L7AKA+kV4pLz2Kc1iKU6GDo6BQpg7bFbdAgMBAAGjggEEMIIBADAO\nBgNVHQ8BAf8EBAMCB4AwGAYDVR0gBBEwDzANBgsrBgEEAYI3bIFIATAcBgNVHSUE\nFTATBgorBgEEAYI3CgMMBgVngQUIAzAdBgNVHQ4EFgQUr57RFs5NWi58XdGtOgBu\n3FKq2zEwGgYKKwYBBAGCNw0CAwQMFgo2LjIuOTIwMC4yMFoGCSsGAQQBgjcVFARN\nMEsCAQUMD0FNUzA5MTA3MjcxMDAwNQwaV09SS0dST1VQXEFNUzA5MTA3MjcxMDAw\nNSQMGVZ0cG1NYW5hZ2VtZW50U2VydmljZS5leGUwHwYDVR0jBBgwFoAUZwmG+PmB\nXpOt+clk5xQK2pnLCi0wDQYJKoZIhvcNAQELBQADggEBAE4DCMj4gZ6enCQ6MKXi\nrjjfXPJNGeNTEHka5A1hklkA37lCXEQ1GVTQOuYJ+PYNmJF1q6x7YkLTJe/rrGPD\nmTlhkVmXXwEnU3W6KCWbgo3oiDk8sZ/O5VQaxy3i1DRVEiNf1xrjkntgRcOei51T\nV1YaJ3LwzoAaMGoNMmWQfX5GO1ZNao83I2HGDPRO4OdoahZhReq5LrdlwkMLB/95\nk8JEpn3YGPEkZgNPVrVWNqBo0HfQWb+SV0sTvu3TjCbHB16lW9fs8GMN0c39UOfW\n8uF+oU1r0epwScUpfOKVkyQAqkQnem4aUUGqYYpOux6JYKHp7i100uHKR6bPCBy5\nEfA=\n-----END CERTIFICATE-----\n" } } diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs index 959990f3..7f601ec7 100644 --- a/azure-attest/tests/decode.rs +++ b/azure-attest/tests/decode.rs @@ -1,5 +1,4 @@ use azure_attest::{collateral::ReportVerifierBuilder, nonce::AzureNonce}; -use libattest::error::Context; use snp_attest::{kds::Kds, verify::SevQuoteVerifier}; const ATTESTATION: &'static str = include_str!("./attestation.json"); @@ -26,6 +25,6 @@ async fn verify_attestation() { .await .unwrap(); - let nonce = AzureNonce::from([0u8; 64]); + let nonce = AzureNonce::from([0u8; 32]); azure_attest::verify(quote, verifier, &nonce).unwrap(); } From 73fbb9a83f996e46f9db9426300ff4373e9d41be Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:23:26 +0000 Subject: [PATCH 21/39] unify certificate chain behaviour under libattest --- Cargo.lock | 28 ++- azure-attest/Cargo.toml | 4 + azure-attest/src/ca/Azure.crt | 33 +++ azure-attest/src/ca/mod.rs | 23 ++ azure-attest/src/lib.rs | 1 + azure-attest/src/quote/verify.rs | 33 ++- libattest/Cargo.toml | 10 + libattest/src/certificates.rs | 81 +++++++ libattest/src/certificates/IntelCA.der | Bin 0 -> 659 bytes .../src/certificates/crl.rs | 32 ++- .../src/certificates/error.rs | 9 + libattest/src/certificates/format.rs | 60 +++++ libattest/src/certificates/format/ecdsa.rs | 136 +++++++++++ libattest/src/certificates/format/rsa.rs | 91 +++++++ libattest/src/certificates/test/ca_cert.pem | 13 + libattest/src/certificates/test/ca_crl.pem | 9 + .../src/certificates/test/revoked_cert.pem | 14 ++ libattest/src/lib.rs | 15 ++ tdx-attest/Cargo.toml | 4 +- tdx-attest/examples/parse.rs | 18 +- tdx-attest/src/certificates.rs | 228 +----------------- tdx-attest/src/certificates/ca.rs | 8 +- tdx-attest/src/certificates/extensions.rs | 42 ++-- tdx-attest/src/error.rs | 27 --- tdx-attest/src/lib.rs | 5 +- tdx-attest/src/pcs.rs | 11 +- tdx-attest/src/pcs/signed_response.rs | 16 +- tdx-attest/src/quote.rs | 17 +- tdx-attest/src/verify.rs | 9 +- 29 files changed, 641 insertions(+), 336 deletions(-) create mode 100644 azure-attest/src/ca/Azure.crt create mode 100644 azure-attest/src/ca/mod.rs create mode 100644 libattest/src/certificates.rs create mode 100644 libattest/src/certificates/IntelCA.der rename {tdx-attest => libattest}/src/certificates/crl.rs (79%) rename {tdx-attest => libattest}/src/certificates/error.rs (77%) create mode 100644 libattest/src/certificates/format.rs create mode 100644 libattest/src/certificates/format/ecdsa.rs create mode 100644 libattest/src/certificates/format/rsa.rs create mode 100644 libattest/src/certificates/test/ca_cert.pem create mode 100644 libattest/src/certificates/test/ca_crl.pem create mode 100644 libattest/src/certificates/test/revoked_cert.pem diff --git a/Cargo.lock b/Cargo.lock index ce32be13..d43c8ccf 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,6 +244,8 @@ dependencies = [ "tokio", "tpm2-protocol", "tss-esapi", + "wasm-bindgen", + "wasm-bindgen-futures", "x509-cert 0.3.0-rc.4", "zerocopy", ] @@ -967,7 +969,7 @@ dependencies = [ "pem-rfc7468 0.7.0", "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -1934,14 +1936,24 @@ name = "libattest" version = "0.1.0" dependencies = [ "anyhow", + "der 0.8.0", "getrandom 0.4.2", "hex", "js-sys", + "p256", "regorus", + "rsa 0.10.0-rc.18", + "sec1 0.8.1", "serde", "serde-value", + "sha2 0.11.0", + "signature 2.2.0", + "spki 0.8.0", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", + "web-time", + "x509-cert 0.3.0-rc.4", ] [[package]] @@ -3312,6 +3324,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct 1.0.0", + "der 0.8.0", + "hybrid-array", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -3753,7 +3777,7 @@ dependencies = [ "libattest", "p256", "reqwest 0.13.2", - "sec1", + "sec1 0.7.3", "serde", "serde_json", "sha2 0.10.9", diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index 7f25a483..05405a70 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -25,6 +25,10 @@ tss-esapi = { version = "7.7.0", optional = true } anyhow = { version = "*", optional = true } base64 = "0.22.1" +[target.'cfg(target_family = "wasm")'.dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" + [dev-dependencies] tokio = { version = "1.49.0", features = ["full"] } diff --git a/azure-attest/src/ca/Azure.crt b/azure-attest/src/ca/Azure.crt new file mode 100644 index 00000000..35a0eac3 --- /dev/null +++ b/azure-attest/src/ca/Azure.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQUfQx2iySCIpOKeDZKd5KpzANBgkqhkiG9w0BAQwFADBp +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTow +OAYDVQQDEzFBenVyZSBWaXJ0dWFsIFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhv +cml0eSAyMDIzMB4XDTIzMDYwMTE4MDg1M1oXDTQ4MDYwMTE4MTU0MVowaTELMAkG +A1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE6MDgGA1UE +AxMxQXp1cmUgVmlydHVhbCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkg +MjAyMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALoMMwvdRJ7+bW00 +adKE1VemNqJS+268Ure8QcfZXVOsVO22+PL9WRoPnWo0r5dVoomYGbobh4HC72s9 +sGY6BGRe+Ui2LMwuWnirBtOjaJ34r1ZieNMcVNJT/dXW5HN/HLlm/gSKlWzqCEx6 +gFFAQTvyYl/5jYI4Oe05zJ7ojgjK/6ZHXpFysXnyUITJ9qgjn546IJh/G5OMC3mD +fFU7A/GAi+LYaOHSzXj69Lk1vCftNq9DcQHtB7otO0VxFkRLaULcfu/AYHM7FC/S +q6cJb9Au8K/IUhw/5lJSXZawLJwHpcEYzETm2blad0VHsACaLNucZL5wBi8GEusQ +9Wo8W1p1rUCMp89pufxa3Ar9sYZvWeJlvKggWcQVUlhvvIZEnT+fteEvwTdoajl5 +qSvZbDPGCPjb91rSznoiLq8XqgQBBFjnEiTL+ViaZmyZPYUsBvBY3lKXB1l2hgga +hfBIag4j0wcgqlL82SL7pAdGjq0Fou6SKgHnkkrV5CNxUBBVMNCwUoj5mvEjd5mF +7XPgfM98qNABb2Aqtfl+VuCkU/G1XvFoTqS9AkwbLTGFMS9+jCEU2rw6wnKuGv1T +x9iuSdNvsXt8stx4fkVeJvnFpJeAIwBZVgKRSTa3w3099k0mW8qGiMnwCI5SfdZ2 +SJyD4uEmszsnieE6wAWd1tLLg1jvAgMBAAGjVDBSMA4GA1UdDwEB/wQEAwIBhjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRL/iZalMH2M8ODSCbd8+WwZLKqlTAQ +BgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQwFAAOCAgEALgNAyg8I0ANNO/8I +2BhpTOsbywN2YSmShAmig5h4sCtaJSM1dRXwA+keY6PCXQEt/PRAQAiHNcOF5zbu +OU1Bw/Z5Z7k9okt04eu8CsS2Bpc+POg9js6lBtmigM5LWJCH1goMD0kJYpzkaCzx +1TdD3yjo0xSxgGhabk5Iu1soD3OxhUyIFcxaluhwkiVINt3Jhy7G7VJTlEwkk21A +oOrQxUsJH0f2GXjYShS1r9qLPzLf7ykcOm62jHGmLZVZujBzLIdNk1bljP9VuGW+ +cISBwzkNeEMMFufcL2xh6s/oiUnXicFWvG7E6ioPnayYXrHy3Rh68XLnhfpzeCzv +bz/I4yMV38qGo/cAY2OJpXUuuD/ZbI5rT+lRBEkDW1kxHP8cpwkRwGopV8+gX2KS +UucIIN4l8/rrNDEX8T0b5U+BUqiO7Z5YnxCya/H0ZIwmQnTlLRTU2fW+OGG+xyIr +jMi/0l6/yWPUkIAkNtvS/yO7USRVLPbtGVk3Qre6HcqacCXzEjINcJhGEVg83Y8n +M+Y+a9J0lUnHytMSFZE85h88OseRS2QwqjozUo2j1DowmhSSUv9Na5Ae22ycciBk +EZSq8a4rSlwqthaELNpeoTLUk6iVoUkK/iLvaMvrkdj9yJY1O/gvlfN2aiNTST/2 +bd+PA4RBToG9rXn6vNkUWdbLibU= +-----END CERTIFICATE----- diff --git a/azure-attest/src/ca/mod.rs b/azure-attest/src/ca/mod.rs new file mode 100644 index 00000000..424cee7a --- /dev/null +++ b/azure-attest/src/ca/mod.rs @@ -0,0 +1,23 @@ +use std::sync::LazyLock; + +use libattest::certificates::format::{CertFormat, rsa::RsaCert}; +use x509_cert::{Certificate, der::DecodePem}; + +static AZURE_CA_PEM: &[u8] = include_bytes!("./Azure.crt"); + +pub static AZURE_CA: LazyLock = LazyLock::new(|| { + RsaCert::from_certificate(Certificate::from_pem(AZURE_CA_PEM).unwrap()).unwrap() +}); + +#[cfg(test)] +mod test { + use libattest::certificates::format::{CertFormat, rsa::RsaCert}; + + use crate::ca::AZURE_CA; + + #[test] + fn read_parse_ca() { + let ca: &RsaCert = &AZURE_CA; + ca.cetificate(); + } +} diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 1645c501..7bd85ddd 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -6,6 +6,7 @@ pub mod report; #[cfg(feature = "host")] pub mod host; +pub mod ca; mod serde; pub use quote::AzureQuote; diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index 2314c450..831e5691 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -3,7 +3,17 @@ use std::ops::Deref; use base64::Engine; use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet}; use libattest::{ByteNonce, error::Context}; -use rsa::{BoxedUint, signature::Verifier}; +use rsa::{ + BoxedUint, + pkcs1::DecodeRsaPublicKey, + pkcs1v15::{Signature, VerifyingKey}, + signature::Verifier, +}; +use sha2::Sha256; +use x509_cert::der::{ + Encode, + oid::db::{rfc5912::SHA_256_WITH_RSA_ENCRYPTION, rfc9688::RSA_ENCRYPTION}, +}; use crate::{ collateral::ReportVerifier, @@ -31,6 +41,27 @@ pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { Ok(()) } +pub fn verify_quote_certificate(quote: &AzureQuote) -> libattest::Result<()> { + let pk = quote + .trust + .ak_cert + .tbs_certificate() + .subject_public_key_info(); + + pk.algorithm + .assert_algorithm_oid(RSA_ENCRYPTION) + .context("invalid AK cert public key encryption algorithm")?; + + let public_key = rsa::RsaPublicKey::from_pkcs1_der(pk.subject_public_key.raw_bytes())?; + let public_key: VerifyingKey = VerifyingKey::new(public_key); + + if quote.trust.ak_key != public_key { + libattest::bail!(exposed: "mismatched ak key between certificate and evidence"); + } + + Ok(()) +} + fn decode_base64_component(base64: &str) -> Result { use base64::engine::general_purpose::URL_SAFE_NO_PAD; diff --git a/libattest/Cargo.toml b/libattest/Cargo.toml index 1abef54d..cfe18ae5 100644 --- a/libattest/Cargo.toml +++ b/libattest/Cargo.toml @@ -11,9 +11,19 @@ anyhow = "1.0" regorus = { git = "https://github.com/microsoft/regorus.git", default-features = false, features = ["arc", "rvm", "std"] } # serde_json = "1.0.149" serde-value = "0.7.0" +x509-cert = "0.3.0-rc.4" +sha2 = "0.11.0" +p256 = "0.13.2" +signature = "2.2.0" +sec1 = "0.8.1" +spki = "0.8.0" +thiserror = "2.0.18" +der = "0.8.0" +rsa = { version = "0.10.0-rc.18", features = ["serde"] } [target.'cfg(target_family = "wasm")'.dependencies] wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" getrandom = { version = "0.4.2", features = ["wasm_js"] } js-sys = "0.3.93" +web-time = "1.1" diff --git a/libattest/src/certificates.rs b/libattest/src/certificates.rs new file mode 100644 index 00000000..965bfa19 --- /dev/null +++ b/libattest/src/certificates.rs @@ -0,0 +1,81 @@ +pub mod crl; +pub mod error; +pub mod format; + +pub use error::CertificateError; + +use crate::certificates::format::{Cert, Verify}; + +pub type PinnedCertificate = &'static C; + +#[derive(Debug)] +/// represents a cryptographically verified +/// certificate chain +pub struct CertificateChain { + /// optional trust anchor of a pinned certificate + anchor: Option>, + /// a chain of certificates. Last one is leaf. + chain: Vec, +} + +impl CertificateChain { + #[must_use] + pub fn with_anchor(anchor: PinnedCertificate) -> Self { + Self { + anchor: Some(anchor), + chain: vec![], + } + } + + #[must_use] + /// Returns the leaf certificate or None if this certificate chain is empty + /// and does not have any anchor of trust + pub fn leaf(&self) -> Option<&C> { + self.chain.last() + } + + /// Inserts a new leaf into this certificate chain. The certificate is first + /// verified by the previous certificates + pub fn push_certificate(&mut self, other: C) -> Result<(), CertificateError> { + let verifier = self.chain.last().or(self.anchor); + + match verifier { + Some(verifier) => verifier.verify_cert(&other)?, + None => other.verify_self()?, + }; + + self.chain.push(other); + + Ok(()) + } + + pub fn parse_pem_chain(mut self, chain: &[u8]) -> Result { + let chain = chain.strip_suffix(b"\0").unwrap_or(chain); // chain from tdx could be 0 terminated so we do a little sanitization + + let chain: Vec = x509_cert::Certificate::load_pem_chain(chain)? + .into_iter() + .map(C::from_certificate) + .collect::>()?; + + let mut chain = chain.into_iter().rev(); + + if let Some(_anchor) = self.anchor { + // discard the root certificate from the pem chain if we already have our own embedded trust + let _root = chain.next().ok_or(CertificateError::BadChain)?; + + // todo: maybe verify they match? + } + + chain.try_for_each(|cert| self.push_certificate(cert))?; + + Ok(self) + } +} + +impl Verify for CertificateChain { + fn verify_signature(&self, msg: &[u8], sig: &C::Signature) -> Result<(), CertificateError> { + self.leaf() + .ok_or(CertificateError::NoLeaf)? + .verify_signature(msg, sig) + } +} diff --git a/libattest/src/certificates/IntelCA.der b/libattest/src/certificates/IntelCA.der new file mode 100644 index 0000000000000000000000000000000000000000..768806c673709fb71537020abbabad2b1dc23773 GIT binary patch literal 659 zcmXqLV(K?&Vlr94%*4pVB%+kcb1iDFN$2P0EmMQ`2S{70v#l}UV&l+i^EhYA!pvll zVJKxF!NwfQ!ptM+nOBmUqY&&Kp%9dxU!vgb2$SQ2$vNj2733EsmSpDV8HyMPf%LKS za0e&ml_V-S=Oh*-8gd(OfM-Ch~kvMtrKjmv1bZ!Z(*nQf5%loS+O>FXz_7L{bCWhN(hi5#{Wtmy%j!APX{+k420HyOn$(X?{k= z|17|0XJi9Mo~$s4&tbp@q?i~PkrM{9Cxd|-lOn^@{ZBasf9gARD%>&aGSGW`QSWG5 z{WqmIDbv3SH3&@mV{?50sw!g B#yS81 literal 0 HcmV?d00001 diff --git a/tdx-attest/src/certificates/crl.rs b/libattest/src/certificates/crl.rs similarity index 79% rename from tdx-attest/src/certificates/crl.rs rename to libattest/src/certificates/crl.rs index 530edde2..4c84d665 100644 --- a/tdx-attest/src/certificates/crl.rs +++ b/libattest/src/certificates/crl.rs @@ -1,14 +1,11 @@ -use crate::certificates::now; - use der::oid::db::rfc5912::ECDSA_WITH_SHA_256; use der::{Decode, Encode}; use p256::ecdsa::Signature; -use signature::Verifier; +use x509_cert::crl::CertificateList; use x509_cert::der::DecodePem; -use x509_cert::time::Time; -use x509_cert::{certificate::Rfc5280, crl::CertificateList}; -use crate::certificates::{CertificateChain, CertificateError, EcdsaCert}; +use crate::certificates::format::{Cert, CertFormat, Verify}; +use crate::certificates::{CertificateChain, CertificateError}; mod sealed { pub trait Sealed {} @@ -24,14 +21,15 @@ impl sealed::Sealed for Crl {} impl Crl { pub fn from_pem( - verifier: impl Verifier, + verifier: impl Verify, pem: impl AsRef<[u8]>, ) -> Result { let list = CertificateList::from_pem(pem)?; Self::from_certificate_list(verifier, list) } + pub fn from_der( - verifier: impl Verifier, + verifier: impl Verify, der: impl AsRef<[u8]>, ) -> Result { let list = CertificateList::from_der(der.as_ref())?; @@ -39,7 +37,7 @@ impl Crl { } pub fn from_certificate_list( - verifier: impl Verifier, + verifier: impl Verify, list: CertificateList, ) -> Result { // let list: CertificateList = CertificateList::from_pem(pem)?; @@ -48,7 +46,7 @@ impl Crl { let expired = list .tbs_cert_list .next_update - .is_some_and(|a| a.to_system_time() < now()); + .is_some_and(|a| a.to_system_time() < crate::now()); if expired { return Err(CertificateError::Expired); @@ -72,9 +70,7 @@ impl Crl { Signature::from_der(signature).expect("could not re-decode an encoded signature"); // verify the signature of the certificate chain - verifier - .verify(&tbs_list, &signature) - .map_err(CertificateError::BadSignature)?; + verifier.verify_signature(&tbs_list, &signature)?; // ok! Ok(Self { list, signature }) @@ -85,16 +81,16 @@ pub trait VerifyCrl: sealed::Sealed { fn check_revoked(&self, tbs: &Tbs) -> Result<(), CertificateError>; } -impl VerifyCrl for Crl { - fn check_revoked(&self, tbs: &CertificateChain) -> Result<(), CertificateError> { +impl VerifyCrl> for Crl { + fn check_revoked(&self, tbs: &CertificateChain) -> Result<(), CertificateError> { tbs.chain .iter() .try_for_each(|cert| self.check_revoked(cert)) } } -impl VerifyCrl for Crl { - fn check_revoked(&self, tbs: &EcdsaCert) -> Result<(), CertificateError> { +impl VerifyCrl for Crl { + fn check_revoked(&self, tbs: &C) -> Result<(), CertificateError> { let certificates = self .list .tbs_cert_list @@ -103,7 +99,7 @@ impl VerifyCrl for Crl { .flatten(); // if this serial number is found in the list, the certificate has been revoked - let serial_number = tbs.certificate.tbs_certificate().serial_number().clone(); + let serial_number = tbs.cetificate().serial_number().clone(); let found = certificates .map(|cert| &cert.serial_number) diff --git a/tdx-attest/src/certificates/error.rs b/libattest/src/certificates/error.rs similarity index 77% rename from tdx-attest/src/certificates/error.rs rename to libattest/src/certificates/error.rs index 836ca810..467344cd 100644 --- a/tdx-attest/src/certificates/error.rs +++ b/libattest/src/certificates/error.rs @@ -21,6 +21,15 @@ pub enum CertificateError { #[error("certificate extension is malformed")] Extension, + #[error("this certificate chain does not contain any leaf certificates")] + NoLeaf, + + #[error("signature error: {0}")] + Signature(#[from] signature::Error), + + #[error("rsa signature error: {0}")] + RsaSignature(#[from] rsa::signature::Error), + #[error("an error occurred while parsing the certificate: {0}")] Der(#[from] der::Error), diff --git a/libattest/src/certificates/format.rs b/libattest/src/certificates/format.rs new file mode 100644 index 00000000..941ae71b --- /dev/null +++ b/libattest/src/certificates/format.rs @@ -0,0 +1,60 @@ +use x509_cert::certificate::TbsCertificateInner; + +use crate::certificates::CertificateError; + +pub mod ecdsa; +pub mod rsa; + +mod sealed { + pub trait Sealed {} +} + +pub trait CertFormat: Sized + sealed::Sealed { + type Signature; + + /// Parse this certificate format from the generix `x509_cert::Certificate` type + fn from_certificate(cert: x509_cert::Certificate) -> Result; + + /// Get inner signed data of this certificate + fn cetificate(&self) -> &TbsCertificateInner; +} + +pub trait Verify { + /// Uses the public component of this certificate to verify another signature + /// over arbitrary data. + fn verify_signature(&self, msg: &[u8], signature: &Signature) -> Result<(), CertificateError>; +} + +pub trait Cert: CertFormat + Verify { + /// Verifies that the public component of this certificate signs another certificate's TBS + fn verify_cert(&self, other: &Self) -> Result<(), CertificateError>; + + /// Verifies that the public component of this certificate signs its own TBS. + /// This is the case for Certificate Authorities (root of trust) + fn verify_self(&self) -> Result<(), CertificateError> { + self.verify_cert(self) + } +} + +fn verify_cert_common(certificate: &x509_cert::Certificate) -> Result<(), CertificateError> { + // check for certificate validity + let not_after = certificate + .tbs_certificate() + .validity() + .not_after + .to_system_time(); + + let not_before = certificate + .tbs_certificate() + .validity() + .not_before + .to_system_time(); + + let now = crate::now(); + + if not_before > now || not_after < now { + return Err(CertificateError::Expired); + } + + Ok(()) +} diff --git a/libattest/src/certificates/format/ecdsa.rs b/libattest/src/certificates/format/ecdsa.rs new file mode 100644 index 00000000..c5ae6456 --- /dev/null +++ b/libattest/src/certificates/format/ecdsa.rs @@ -0,0 +1,136 @@ +use der::{ + Encode, + oid::db::{rfc5753::ID_EC_PUBLIC_KEY, rfc5912::ECDSA_WITH_SHA_256}, +}; +use p256::ecdsa::{Signature, VerifyingKey}; +use signature::Verifier; +use x509_cert::certificate::TbsCertificateInner; + +use crate::certificates::{ + CertificateError, + format::{Cert, CertFormat, Verify, verify_cert_common}, +}; + +#[derive(Debug)] +pub struct EcdsaCert { + /// the original certificate where public_key and signature were derived from + certificate: x509_cert::Certificate, + /// this is the public key the certificate is trying to attest + public_key: VerifyingKey, + /// this is the signature attesting the authenticity and trustworthyness of the public key + signature: Signature, +} + +impl super::sealed::Sealed for EcdsaCert {} + +impl CertFormat for EcdsaCert { + type Signature = Signature; + + fn from_certificate(cert: x509_cert::Certificate) -> Result { + Self::from_cert(cert) + } + + fn cetificate(&self) -> &TbsCertificateInner { + self.certificate.tbs_certificate() + } +} + +impl Verify for EcdsaCert { + fn verify_signature(&self, msg: &[u8], signature: &Signature) -> Result<(), CertificateError> { + self.public_key.verify(msg, signature).map_err(Into::into) + } +} + +impl Cert for EcdsaCert { + /// verifies that this certificate (`self`) contains a signed public key + /// that attests for the authenticity of the signature of another certificate (`other`) + /// + /// # Errors + /// Returns error if the two certificates cannot be linked by cryptographical means + fn verify_cert(&self, other: &Self) -> Result<(), CertificateError> { + let other_tbs = other.certificate.tbs_certificate().to_der()?; + self.public_key + .verify(&other_tbs, &other.signature) + .map_err(CertificateError::BadSignature)?; + + Ok(()) + } +} + +pub type PinnedCertificate = &'static EcdsaCert; + +impl EcdsaCert { + #[must_use] + pub fn cert(&self) -> &TbsCertificateInner { + self.certificate.tbs_certificate() + } + + /// verifies if the certificate is self-signed + /// + /// # Errors + /// Returns error if trust cannot be enstablished + /// within self + pub fn verify_self(&self) -> Result<(), CertificateError> { + self.verify_cert(self) + } + + /// Steps: + /// - decode signature and public key + /// - check for certificate validity + fn from_cert(certificate: x509_cert::Certificate) -> Result { + // Check that the signature and public key are in the + // format supported by the library (elliptic curve certificates) + certificate + .signature_algorithm() + .assert_algorithm_oid(ECDSA_WITH_SHA_256) + .map_err(CertificateError::WrongAlgorithm)?; + + certificate + .tbs_certificate() + .subject_public_key_info() + .algorithm + .assert_algorithm_oid(ID_EC_PUBLIC_KEY) + .map_err(CertificateError::WrongAlgorithm)?; + + // signature + let signature = certificate + .signature() + .as_bytes() + .ok_or(CertificateError::WrongFormat)?; + + let signature = + Signature::from_der(signature).expect("could not re-decode an encoded signature"); + + // signed public key + let public_key = certificate + .tbs_certificate() + .subject_public_key_info() + .subject_public_key + .as_bytes() + .ok_or(CertificateError::WrongFormat)?; + + let public_key = VerifyingKey::from_sec1_bytes(public_key) + .expect("could not re-decode an encoded public key"); + + verify_cert_common(&certificate)?; + + Ok(Self { + certificate, + public_key, + signature, + }) + } +} + +// impl Verifier for EcdsaCert { +// fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { +// self.public_key.verify(msg, signature) +// } +// } + +impl TryFrom for EcdsaCert { + type Error = CertificateError; + fn try_from(value: x509_cert::Certificate) -> Result { + Self::from_cert(value) + } +} diff --git a/libattest/src/certificates/format/rsa.rs b/libattest/src/certificates/format/rsa.rs new file mode 100644 index 00000000..53812f1d --- /dev/null +++ b/libattest/src/certificates/format/rsa.rs @@ -0,0 +1,91 @@ +use der::{ + Encode, + oid::db::rfc5912::{RSA_ENCRYPTION, SHA_384_WITH_RSA_ENCRYPTION}, +}; +use rsa::{ + RsaPublicKey, + pkcs1::DecodeRsaPublicKey, + pkcs1v15::{Signature, VerifyingKey}, + signature::Verifier, +}; +use sha2::Sha384; +use x509_cert::certificate::TbsCertificateInner; + +use crate::certificates::{ + CertificateError, + format::{Cert, CertFormat, Verify, verify_cert_common}, +}; + +#[derive(Debug)] +pub struct RsaCert { + certificate: x509_cert::Certificate, + public_key: VerifyingKey, + signature: Signature, +} + +impl super::sealed::Sealed for RsaCert {} + +impl CertFormat for RsaCert { + type Signature = Signature; + + fn from_certificate(cert: x509_cert::Certificate) -> Result { + Self::from_cert(cert) + } + + fn cetificate(&self) -> &TbsCertificateInner { + self.certificate.tbs_certificate() + } +} + +impl Verify for RsaCert { + fn verify_signature(&self, msg: &[u8], signature: &Signature) -> Result<(), CertificateError> { + self.public_key.verify(msg, signature).map_err(Into::into) + } +} + +impl Cert for RsaCert { + fn verify_cert(&self, other: &Self) -> Result<(), CertificateError> { + let other_tbs = other.certificate.tbs_certificate().to_der()?; + self.verify_signature(&other_tbs, &other.signature) + } +} + +impl RsaCert { + fn from_cert(certificate: x509_cert::Certificate) -> Result { + certificate + .signature_algorithm() + .assert_algorithm_oid(SHA_384_WITH_RSA_ENCRYPTION) + .map_err(CertificateError::WrongAlgorithm)?; + + let pk = certificate.tbs_certificate().subject_public_key_info(); + pk.algorithm + .assert_algorithm_oid(RSA_ENCRYPTION) + .map_err(CertificateError::WrongAlgorithm)?; + + let signature = certificate + .signature() + .as_bytes() + .ok_or(CertificateError::WrongFormat) + .and_then(|sig| Signature::try_from(sig).map_err(Into::into))?; + + let public_key = RsaPublicKey::from_pkcs1_der(pk.subject_public_key.raw_bytes()) + .map(VerifyingKey::new) + .map_err(|_| CertificateError::WrongFormat)?; + + verify_cert_common(&certificate)?; + + Ok(Self { + certificate, + public_key, + signature, + }) + } +} + +impl TryFrom for RsaCert { + type Error = CertificateError; + + fn try_from(value: x509_cert::Certificate) -> Result { + Self::from_cert(value) + } +} diff --git a/libattest/src/certificates/test/ca_cert.pem b/libattest/src/certificates/test/ca_cert.pem new file mode 100644 index 00000000..d37ca072 --- /dev/null +++ b/libattest/src/certificates/test/ca_cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7TCCAZKgAwIBAgIUEcpkZPPXHIC+2XRnoigNGxXX9b0wCgYIKoZIzj0EAwIw +VDELMAkGA1UEBhMCWFgxGDAWBgNVBAoMD0V4YW1wbGUgVGVzdCBDQTErMCkGA1UE +AwwiRXhhbXBsZSBUZXN0IENBIFJvb3QgKEVDRFNBIFAtMjU2KTAeFw0yNjAyMTcy +MjM5NTdaFw0zNjAyMTUyMjQwNTdaMFQxCzAJBgNVBAYTAlhYMRgwFgYDVQQKDA9F +eGFtcGxlIFRlc3QgQ0ExKzApBgNVBAMMIkV4YW1wbGUgVGVzdCBDQSBSb290IChF +Q0RTQSBQLTI1NikwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT3IfU2Nwsv43yz +1mW5gEY2zcQkYz60HCJMcaweVsKletVFYhwFmejanyHf4Em88j+BcOLS5OVP9Yhn +iIHJRZhjo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNV +HQ4EFgQUvmll4rii1/H8OJslwnBcVlPzyOAwCgYIKoZIzj0EAwIDSQAwRgIhAOC+ +tloHkQVQQI3inbesJMvYWpM8Vy68FlWQrqWQXz2AAiEA+dT5QF58RhQzQfSCc0uz +kbHNfkHmk4hhEFoHcLug0f0= +-----END CERTIFICATE----- diff --git a/libattest/src/certificates/test/ca_crl.pem b/libattest/src/certificates/test/ca_crl.pem new file mode 100644 index 00000000..d63fd068 --- /dev/null +++ b/libattest/src/certificates/test/ca_crl.pem @@ -0,0 +1,9 @@ +-----BEGIN X509 CRL----- +MIIBRDCB6wIBATAKBggqhkjOPQQDAjBUMQswCQYDVQQGEwJYWDEYMBYGA1UECgwP +RXhhbXBsZSBUZXN0IENBMSswKQYDVQQDDCJFeGFtcGxlIFRlc3QgQ0EgUm9vdCAo +RUNEU0EgUC0yNTYpFw0yNjAyMTcyMjM5NTdaFw0yNjAzMTkyMjQwNTdaMDUwMwIU +Zw5n0aeNdF9mB0Egovmdk1l0D4oXDTI2MDIxNzIyNDA1N1owDDAKBgNVHRUEAwoB +AaAvMC0wCgYDVR0UBAMCAQEwHwYDVR0jBBgwFoAUvmll4rii1/H8OJslwnBcVlPz +yOAwCgYIKoZIzj0EAwIDSAAwRQIhAIFn1xA5dGibpcTZvfu3aYqHR9th+MRy11ZO +4CgZ/fX/AiB+o6QBYKaYLLBJreQIj74FwV46yjtWlMB32ptaj2nX5Q== +-----END X509 CRL----- diff --git a/libattest/src/certificates/test/revoked_cert.pem b/libattest/src/certificates/test/revoked_cert.pem new file mode 100644 index 00000000..b2c7bc4b --- /dev/null +++ b/libattest/src/certificates/test/revoked_cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICFjCCAbygAwIBAgIUZw5n0aeNdF9mB0Egovmdk1l0D4owCgYIKoZIzj0EAwIw +VDELMAkGA1UEBhMCWFgxGDAWBgNVBAoMD0V4YW1wbGUgVGVzdCBDQTErMCkGA1UE +AwwiRXhhbXBsZSBUZXN0IENBIFJvb3QgKEVDRFNBIFAtMjU2KTAeFw0yNjAyMTcy +MjM5NTdaFw0yNzAyMTcyMjQwNTdaMEsxCzAJBgNVBAYTAlhYMRowGAYDVQQKDBFF +eGFtcGxlIFRlc3QgTGVhZjEgMB4GA1UEAwwXcmV2b2tlZC5leGFtcGxlLmludmFs +aWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASQsdqcM6MSGhU0OS7NpYlaw0nA +CoQrHqSZGWDo47feoO5TFQ4uqS0x5Wj+HKX2SJK5JuQf6lPjhG73jwIhgLJEo3Uw +czAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIDiDATBgNVHSUEDDAKBggrBgEF +BQcDATAdBgNVHQ4EFgQUIXlAyoGCLtBkMSKZC+94Pcw4y9owHwYDVR0jBBgwFoAU +vmll4rii1/H8OJslwnBcVlPzyOAwCgYIKoZIzj0EAwIDSAAwRQIgHi9hCRKfepRE +rjInOJymO+iXZRFxLv6uYdxbupIwctUCIQCumpP9fHkY1Ua+Ey7S/dwbFRyWb3t0 +SRYBH25lTh+AEQ== +-----END CERTIFICATE----- diff --git a/libattest/src/lib.rs b/libattest/src/lib.rs index f59c85a5..c2f5bca7 100644 --- a/libattest/src/lib.rs +++ b/libattest/src/lib.rs @@ -1,3 +1,4 @@ +pub mod certificates; pub mod error; pub mod modules; pub mod nonce; @@ -8,3 +9,17 @@ pub use modules::*; pub use nonce::*; pub type Result = std::result::Result; +pub use error::AttestationError; + +#[cfg(target_arch = "wasm32")] +pub(crate) fn now() -> std::time::SystemTime { + std::time::SystemTime::UNIX_EPOCH + + web_time::SystemTime::now() + .duration_since(web_time::SystemTime::UNIX_EPOCH) + .unwrap_or_default() +} + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn now() -> std::time::SystemTime { + std::time::SystemTime::now() +} diff --git a/tdx-attest/Cargo.toml b/tdx-attest/Cargo.toml index 38b7f7fa..3af945d7 100644 --- a/tdx-attest/Cargo.toml +++ b/tdx-attest/Cargo.toml @@ -6,8 +6,6 @@ edition = "2024" [dependencies] # dcap-qvl = "0.3" # tdx-quote = "0.0.5" -wasm-bindgen.workspace = true -wasm-bindgen-futures.workspace = true libattest = { path = "../libattest" } thiserror = "2" zerocopy = { version = "0.8.39", features = ["derive"] } @@ -29,6 +27,8 @@ const-oid = "0.10.2" sha2 = "0.10.9" [target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen.workspace = true +wasm-bindgen-futures.workspace = true web-time = "1.1" [dev-dependencies] diff --git a/tdx-attest/examples/parse.rs b/tdx-attest/examples/parse.rs index 428b66ab..16c94c55 100644 --- a/tdx-attest/examples/parse.rs +++ b/tdx-attest/examples/parse.rs @@ -17,19 +17,19 @@ static DATA: [u8; 64] = [0; 64]; #[tokio::main] async fn main() -> anyhow::Result<()> { - let input = std::fs::read("./examples/quote.test").unwrap(); - let pcs = Pcs::new("https://pccs.prem.io/")?; + // let input = std::fs::read("./examples/quote.test").unwrap(); + // let pcs = Pcs::new("https://pccs.prem.io/")?; - let quote = TdxQuote::from_bytes(&input)?; - let collateral = pcs.fetch_collateral("e).await?; + // let quote = TdxQuote::from_bytes(&input)?; + // let collateral = pcs.fetch_collateral("e).await?; - let nonce = ByteNonce::from(DATA).into(); - let verifier = TdxQuoteVerifier::new(collateral); + // let nonce = ByteNonce::from(DATA).into(); + // let verifier = TdxQuoteVerifier::new(collateral); - verifier.verify("e, &nonce)?; + // verifier.verify("e, &nonce)?; - println!("Verification success"); - // println!("{identity:?}"); + // println!("Verification success"); + // // println!("{identity:?}"); Ok(()) } diff --git a/tdx-attest/src/certificates.rs b/tdx-attest/src/certificates.rs index 3ea181ae..ef134d9c 100644 --- a/tdx-attest/src/certificates.rs +++ b/tdx-attest/src/certificates.rs @@ -1,26 +1,9 @@ pub mod ca; -pub mod crl; -pub mod error; pub mod extensions; -pub use error::CertificateError; - use std::fmt::Display; use std::time::SystemTime; -#[cfg(target_arch = "wasm32")] -pub(crate) fn now() -> SystemTime { - SystemTime::UNIX_EPOCH - + web_time::SystemTime::now() - .duration_since(web_time::SystemTime::UNIX_EPOCH) - .unwrap_or_default() -} - -#[cfg(not(target_arch = "wasm32"))] -pub(crate) fn now() -> SystemTime { - SystemTime::now() -} - use der::{ Encode, oid::db::rfc5912::{ECDSA_WITH_SHA_256, ID_EC_PUBLIC_KEY}, @@ -51,213 +34,4 @@ impl IntermediateCa { } } -#[derive(Debug)] -pub struct EcdsaCert { - /// the original certificate where public_key and signature were derived from - certificate: x509_cert::Certificate, - /// this is the public key the certificate is trying to attest - public_key: VerifyingKey, - /// this is the signature attesting the authenticity and trustworthyness of the public key - signature: Signature, -} - -pub type PinnedCertificate = &'static EcdsaCert; - -impl EcdsaCert { - /// verifies that this certificate (`self`) contains a signed public key - /// that attests for the authenticity of the signature of another certificate (`other`) - /// - /// # Errors - /// Returns error if the two certificates cannot be linked by cryptographical means - pub fn verify_cert(&self, other: &Self) -> Result<(), CertificateError> { - let other_tbs = other.certificate.tbs_certificate().to_der()?; - self.public_key - .verify(&other_tbs, &other.signature) - .map_err(CertificateError::BadSignature)?; - - Ok(()) - } - - #[must_use] - pub fn cert(&self) -> &TbsCertificateInner { - self.certificate.tbs_certificate() - } - - /// verifies if the certificate is self-signed - /// - /// # Errors - /// Returns error if trust cannot be enstablished - /// within self - pub fn verify_self(&self) -> Result<(), CertificateError> { - self.verify_cert(self) - } - - /// Steps: - /// - decode signature and public key - /// - check for certificate validity - fn from_cert(certificate: x509_cert::Certificate) -> Result { - // Check that the signature and public key are in the - // format supported by the library (elliptic curve certificates) - certificate - .signature_algorithm() - .assert_algorithm_oid(ECDSA_WITH_SHA_256) - .map_err(CertificateError::WrongAlgorithm)?; - - certificate - .tbs_certificate() - .subject_public_key_info() - .algorithm - .assert_algorithm_oid(ID_EC_PUBLIC_KEY) - .map_err(CertificateError::WrongAlgorithm)?; - - // signature - let signature = certificate - .signature() - .as_bytes() - .ok_or(CertificateError::WrongFormat)?; - - let signature = - Signature::from_der(signature).expect("could not re-decode an encoded signature"); - - // signed public key - let public_key = certificate - .tbs_certificate() - .subject_public_key_info() - .subject_public_key - .as_bytes() - .ok_or(CertificateError::WrongFormat)?; - - let public_key = VerifyingKey::from_sec1_bytes(public_key) - .expect("could not re-decode an encoded public key"); - - // check for certificate validity - let not_after = certificate - .tbs_certificate() - .validity() - .not_after - .to_system_time(); - - let not_before = certificate - .tbs_certificate() - .validity() - .not_before - .to_system_time(); - - let now = now(); - - if not_before > now || not_after < now { - return Err(CertificateError::Expired); - } - - Ok(Self { - certificate, - public_key, - signature, - }) - } -} - -impl Verifier for EcdsaCert { - fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - self.public_key.verify(msg, signature) - } -} - -impl TryFrom for EcdsaCert { - type Error = CertificateError; - fn try_from(value: x509_cert::Certificate) -> Result { - Self::from_cert(value) - } -} - -#[derive(Debug)] -/// represents a cryptographically verified -/// certificate chain -pub struct CertificateChain { - /// optional trust anchor of a pinned certificate - anchor: Option, - /// a chain of certificates. Last one is leaf. - chain: Vec, -} - -impl Verifier for CertificateChain { - // #[must_use] - fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - let certificate = self.chain.last().or(self.anchor).unwrap(); - certificate.verify(msg, signature) - } -} - -impl Verifier for &CertificateChain { - fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - (*self).verify(msg, signature) - } -} - -impl CertificateChain { - #[must_use] - pub fn with_anchor(anchor: PinnedCertificate) -> Self { - Self { - anchor: Some(anchor), - chain: vec![], - } - } - - #[must_use] - /// Returns the leaf certificate or None if this certificate chain is empty - /// and does not have any anchor of trust - pub fn leaf(&self) -> Option<&EcdsaCert> { - self.chain.last().or(self.anchor) - } - - /// Inserts a new leaf into this certificate chain. The certificate is first - /// verified by the previous certificates - pub fn push_certificate(&mut self, other: EcdsaCert) -> Result<(), CertificateError> { - let verifier = self.chain.last().or(self.anchor); - - match verifier { - Some(verifier) => verifier.verify_cert(&other)?, - None => other.verify_self()?, - }; - - self.chain.push(other); - - Ok(()) - } - - pub fn parse_pem_chain(mut self, chain: &[u8]) -> Result { - let chain = chain.strip_suffix(b"\0").unwrap_or(chain); // chain from tdx could be 0 terminated so we do a little sanitization - - let chain: Vec = x509_cert::Certificate::load_pem_chain(chain)? - .into_iter() - .map(EcdsaCert::from_cert) - .collect::>()?; - - let mut chain = chain.into_iter().rev(); - - if let Some(anchor) = self.anchor { - // discard the root certificate from the pem chain if we already have our own embedded trust - let _root = chain.next().ok_or(CertificateError::BadChain)?; - } - - chain.try_for_each(|cert| self.push_certificate(cert))?; - - Ok(self) - } - - // verifies the current certificate chain with an out of bounds - // certificate, by veryfying it signs our root of trust - // fn verify_oob(&self, oob_root: &EcdsaCert) -> Result<(), CertificateError> { - // let mut chain = self.chain.iter(); - // let chain_root = chain.next().unwrap(); - - // oob_root.verify_cert(chain_root)?; - // if let Some(certificate) = chain.next() { - // // if there's an intermediate right after the root - // // certificate optionally verify that as well - // oob_root.verify_cert(certificate); - // } - - // Ok(()) - // } -} +// pub type CertificateL diff --git a/tdx-attest/src/certificates/ca.rs b/tdx-attest/src/certificates/ca.rs index 9b15bea7..20e3e660 100644 --- a/tdx-attest/src/certificates/ca.rs +++ b/tdx-attest/src/certificates/ca.rs @@ -1,11 +1,11 @@ use std::sync::LazyLock; use der::Decode; +use libattest::certificates::format::{CertFormat, ecdsa::EcdsaCert}; use x509_cert::Certificate; -use crate::certificates::EcdsaCert; - static INTEL_CA_DER: &[u8; 659] = include_bytes!("./IntelCA.der"); -pub static INTEL_CA: LazyLock = - LazyLock::new(|| EcdsaCert::from_cert(Certificate::from_der(INTEL_CA_DER).unwrap()).unwrap()); +pub static INTEL_CA: LazyLock = LazyLock::new(|| { + EcdsaCert::from_certificate(Certificate::from_der(INTEL_CA_DER).unwrap()).unwrap() +}); diff --git a/tdx-attest/src/certificates/extensions.rs b/tdx-attest/src/certificates/extensions.rs index 48431948..a3d4e1cb 100644 --- a/tdx-attest/src/certificates/extensions.rs +++ b/tdx-attest/src/certificates/extensions.rs @@ -7,9 +7,10 @@ use der::{ asn1::{Int, ObjectIdentifier, OctetString, OctetStringRef, SequenceRef}, oid::{self, AssociatedOid, ObjectIdentifierRef}, }; +use libattest::error::Context; // use sec1::der::asn1::SequenceOf; -use crate::{certificates::CertificateError, dcap::types}; +use crate::dcap::types; #[derive(Sequence, Debug)] struct TaggedField<'a> { @@ -24,10 +25,10 @@ pub struct TcbExtension { } impl TcbExtension { - fn decode<'a>(mut from: AnyRef<'a>) -> Result { - let fields: Vec = from.decode_as()?; + fn decode<'a>(mut from: AnyRef<'a>) -> Option { + let fields: Vec = from.decode_as().ok()?; - let cpu_svn = fields.get(..16).ok_or(CertificateError::WrongFormat)?; + let cpu_svn = fields.get(..16)?; let cpu_svn = cpu_svn .iter() .map(|a| { @@ -35,23 +36,17 @@ impl TcbExtension { .then(|| a.data.decode_as::().ok()) .flatten() }) - .collect::>>() - .ok_or(CertificateError::WrongFormat)?; + .collect::>>()?; - let cpu_svn = cpu_svn - .try_into() - .map_err(|_| CertificateError::WrongFormat)?; - - let pce_svn = fields - .get(16) - .and_then(|TaggedField { data, .. }| { - (data.tag() == Tag::Integer) - .then(|| data.decode_as::().ok()) - .flatten() - }) - .ok_or(CertificateError::WrongFormat)?; + let cpu_svn = Box::<[u32; 16]>::try_from(cpu_svn).ok()?; + + let pce_svn = fields.get(16).and_then(|TaggedField { data, .. }| { + (data.tag() == Tag::Integer) + .then(|| data.decode_as::().ok()) + .flatten() + })?; - Ok(TcbExtension { cpu_svn, pce_svn }) + Some(TcbExtension { cpu_svn, pce_svn }) } } @@ -83,14 +78,17 @@ impl SgxExtension<'_> { } impl<'a> Decode<'a> for SgxExtension<'a> { - type Error = CertificateError; + type Error = der::Error; fn decode>(decoder: &mut R) -> Result { let sequence: TaggedField = decoder.decode()?; let extension = match sequence.object_identifier { Self::FMSPC_OID => Self::Fmspc(sequence.data.decode_as()?), - Self::TCB_OID => Self::Tcb(TcbExtension::decode(sequence.data)?), + Self::TCB_OID => Self::Tcb( + TcbExtension::decode(sequence.data) + .ok_or(der::Error::new(der::ErrorKind::Failed, Length::new(0)))?, + ), _ => Self::Unknown { identifier: sequence.object_identifier, value: sequence.data, @@ -126,7 +124,7 @@ impl AssociatedOid for SgxExtensions<'_> { } impl<'a> der::Decode<'a> for SgxExtensions<'a> { - type Error = CertificateError; + type Error = der::Error; fn decode>(decoder: &mut R) -> Result { let extensions: Vec> = decoder.decode()?; diff --git a/tdx-attest/src/error.rs b/tdx-attest/src/error.rs index e30b12bd..6949605f 100644 --- a/tdx-attest/src/error.rs +++ b/tdx-attest/src/error.rs @@ -1,30 +1,3 @@ -use std::{backtrace::Backtrace, convert::Infallible, fmt::Display}; - use libattest::error::AttestationError; -use crate::{certificates::CertificateError, dcap::parser::ParseError}; - pub type TdxError = AttestationError; -// #[derive(Error, Debug)] -// pub enum Error { -// #[error("certificate error: {0}")] -// Certificate(#[from] CertificateError), - -// #[error("error parsing the dcap quote: {0}")] -// Parse(#[from] ParseError), - -// #[error("cryptographic error: {0}")] -// Crypto(#[from] p256::ecdsa::Error), - -// #[error("this error")] -// SignatureFormat(#[from] sec1::Error), - -// #[error("error returned from pcs: {0}")] -// Reqwest(#[from] reqwest::Error), - -// #[error("failed parsing json data: {0}")] -// Json(#[from] serde_json::Error), - -// #[error("pcs response did not include a certificate chain header")] -// MissingCertChain, -// } diff --git a/tdx-attest/src/lib.rs b/tdx-attest/src/lib.rs index 8f1bacf4..476212fc 100644 --- a/tdx-attest/src/lib.rs +++ b/tdx-attest/src/lib.rs @@ -11,10 +11,7 @@ use crate::dcap::TdQuote; use crate::dcap::parser::Parse; use crate::error::TdxError; -use crate::{ - certificates::CertificateChain, - dcap::types::{EnclaveReport, TdxQuoteBody, TdxQuoteHeader}, -}; +use crate::dcap::types::{EnclaveReport, TdxQuoteBody, TdxQuoteHeader}; pub(crate) mod certificates; pub mod dcap; diff --git a/tdx-attest/src/pcs.rs b/tdx-attest/src/pcs.rs index 5534c702..6adfe9e3 100644 --- a/tdx-attest/src/pcs.rs +++ b/tdx-attest/src/pcs.rs @@ -4,7 +4,10 @@ pub mod tcb; use std::str::FromStr; -use libattest::error::Context; +use libattest::{ + certificates::{CertificateChain, crl::Crl, format::ecdsa::EcdsaCert}, + error::Context, +}; use p256::ecdsa::Signature; use reqwest::{Client, IntoUrl, Url}; use serde::Deserialize; @@ -12,7 +15,7 @@ use x509_cert::Certificate; use crate::{ TdxQuote, - certificates::{CertificateChain, IntermediateCa, ca::INTEL_CA, crl::Crl}, + certificates::{IntermediateCa, ca::INTEL_CA}, dcap::types::Fmspc, error::TdxError, pcs::{qe::EnclaveIdentity, signed_response::ParseSignedResponse, tcb::TcbInfo}, @@ -60,7 +63,7 @@ impl Pcs { .get("SGX-PCK-CRL-Issuer-Chain") .context("crl response does not contain a certificate chain")?; - let chain = CertificateChain::with_anchor(&INTEL_CA) + let chain = CertificateChain::::with_anchor(&INTEL_CA) .parse_pem_chain(&urlencoding::decode_binary(certificate_chain.as_bytes())) .context("failed parsing crl certificate chain")?; @@ -68,7 +71,7 @@ impl Pcs { // TODO: review this very bad thing. We really should build our own pccs already. let crl = hex::decode(crl)?; - let crl = Crl::from_der(&chain, crl).context("failed parsing and verifying crl")?; + let crl = Crl::from_der(chain, crl).context("failed parsing and verifying crl")?; Ok(crl) } diff --git a/tdx-attest/src/pcs/signed_response.rs b/tdx-attest/src/pcs/signed_response.rs index 5412e17b..5afa60e5 100644 --- a/tdx-attest/src/pcs/signed_response.rs +++ b/tdx-attest/src/pcs/signed_response.rs @@ -3,14 +3,18 @@ use std::marker::PhantomData; use crate::{ca::INTEL_CA, error::TdxError}; use anyhow::bail; use chrono::Utc; -use libattest::error::Context; +use libattest::{ + certificates::{ + CertificateChain, + format::{Verify, ecdsa::EcdsaCert}, + }, + error::Context, +}; use p256::ecdsa::Signature; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::Value; use signature::Verifier; -use crate::certificates::CertificateChain; - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct Header { @@ -20,7 +24,7 @@ struct Header { /// Every signed response from the PCS has these 3 things. pub struct SignedResponse { - chain: CertificateChain, + chain: CertificateChain, signature: Signature, header: Header, @@ -57,7 +61,7 @@ impl SignedResponse { // verify signature and message using certificate chain self.chain - .verify(&msg, &self.signature) + .verify_signature(&msg, &self.signature) .context("signed response has a bad signature")?; let data = T::deserialize(&self.data).context("response data is in the wrong format")?; @@ -90,7 +94,7 @@ impl ParseSignedResponse for reqwest::Response { // anchor our trust in embedded intel_ca, still parsing pem chain from // response - let chain = CertificateChain::with_anchor(&INTEL_CA) + let chain = CertificateChain::::with_anchor(&INTEL_CA) .parse_pem_chain(&chain) .context("failed parsing certificate chain from header")?; diff --git a/tdx-attest/src/quote.rs b/tdx-attest/src/quote.rs index 4a3c4c4e..71aa8370 100644 --- a/tdx-attest/src/quote.rs +++ b/tdx-attest/src/quote.rs @@ -1,8 +1,11 @@ -use libattest::error::Context; +use libattest::{ + certificates::{CertificateChain, format::ecdsa::EcdsaCert}, + error::Context, +}; use p256::ecdsa::{Signature, VerifyingKey}; use crate::{ - certificates::{CertificateChain, ca, extensions::SgxExtensions}, + certificates::{ca, extensions::SgxExtensions}, dcap::{ self, TdQuote, parser::Parse, @@ -27,7 +30,7 @@ pub enum CertificationData { PlainText(Vec), EncryptedCpuSvnsRSA2048(Vec), EncryptedCpuSvnsRSA3072(Vec), - PckChain(CertificateChain), + PckChain(CertificateChain), QeReportCertificationData(Box), } @@ -37,7 +40,7 @@ impl CertificationData { /// /// Returns Some if the leaf of this data structure has a certificate chain, None if /// trust is based on other identifiers - pub fn pck_chain(&self) -> Option<&CertificateChain> { + pub fn pck_chain(&self) -> Option<&CertificateChain> { match self { Self::QeReportCertificationData(data) => data.certification_data.pck_chain(), Self::PckChain(chain) => Some(chain), @@ -169,9 +172,9 @@ impl TryFrom> for CertificationData { PlainText(x) => Self::PlainText(x.to_owned()), EncryptedCpuSvnsRSA2048(x) => Self::EncryptedCpuSvnsRSA2048(x.to_owned()), EncryptedCpuSvnsRSA3072(x) => Self::EncryptedCpuSvnsRSA3072(x.to_owned()), - PckChain(chain) => { - Self::PckChain(CertificateChain::with_anchor(&ca::INTEL_CA).parse_pem_chain(chain)?) - } + PckChain(chain) => Self::PckChain( + CertificateChain::::with_anchor(&ca::INTEL_CA).parse_pem_chain(chain)?, + ), QeReportCertificationData(report) => { Self::QeReportCertificationData(Box::new((*report).try_into()?)) } diff --git a/tdx-attest/src/verify.rs b/tdx-attest/src/verify.rs index 85ce89f1..ee9aa47b 100644 --- a/tdx-attest/src/verify.rs +++ b/tdx-attest/src/verify.rs @@ -1,13 +1,16 @@ use std::ops::Deref; -use libattest::{error::Context, quote::QuoteVerifier}; +use libattest::{ + certificates::{crl::VerifyCrl, format::Verify}, + error::Context, + quote::QuoteVerifier, +}; use sha2::{Digest, Sha256, digest::Update}; use signature::Verifier; use zerocopy::IntoBytes; use crate::{ TdxCertification, TdxQuote, - certificates::crl::VerifyCrl, dcap::types::{ReportData, TdxQuoteBody}, error::TdxError, nonce::TdxNonce, @@ -47,7 +50,7 @@ fn verify_qe_report(quote: &TdxQuote, collateral: &Collateral) -> Result<(), Tdx .context("qe report is not in the first level of nesting in dcap quote")?; let qe_report_data = qe_report.qe_report.as_bytes(); - pck_chain.verify(qe_report_data, &qe_report.qe_report_signature)?; + pck_chain.verify_signature(qe_report_data, &qe_report.qe_report_signature)?; // now verify hashes let attestation_key = certification.attestation_key.to_sec1_bytes(); From 84cc245d3eb212bebc2961817d26176256d11cf4 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:14:08 +0000 Subject: [PATCH 22/39] structurally sound certificate chain --- Cargo.lock | 174 +++++++++++++++--- azure-attest/src/ca/ICA01.crt | 32 ++++ azure-attest/src/ca/ICA03.crt | 32 ++++ azure-attest/src/ca/intermediate.rs | 73 ++++++++ azure-attest/src/ca/mod.rs | 10 +- azure-attest/src/quote/verify.rs | 57 ++++-- libattest/Cargo.toml | 5 +- libattest/src/certificates/IntelCA.der | Bin 659 -> 0 bytes libattest/src/certificates/format/rsa.rs | 91 --------- libattest/src/crypto.rs | 10 + .../format.rs => crypto/algorithms.rs} | 14 +- .../format => crypto/algorithms}/ecdsa.rs | 27 +-- libattest/src/crypto/algorithms/rsa.rs | 91 +++++++++ .../src/{certificates.rs => crypto/chain.rs} | 24 ++- .../src/{certificates => crypto/chain}/crl.rs | 15 +- .../src/{certificates => crypto}/error.rs | 6 +- libattest/src/crypto/signature.rs | 8 + libattest/src/crypto/signature/rsa.rs | 32 ++++ .../{certificates => crypto}/test/ca_cert.pem | 0 .../{certificates => crypto}/test/ca_crl.pem | 0 .../test/revoked_cert.pem | 0 libattest/src/lib.rs | 4 +- tdx-attest/Cargo.toml | 3 +- tdx-attest/src/certificates.rs | 4 - tdx-attest/src/certificates/ca.rs | 2 +- tdx-attest/src/lib.rs | 4 - tdx-attest/src/pcs.rs | 3 +- tdx-attest/src/pcs/signed_response.rs | 9 +- tdx-attest/src/quote.rs | 3 +- tdx-attest/src/verify.rs | 8 +- 30 files changed, 527 insertions(+), 214 deletions(-) create mode 100644 azure-attest/src/ca/ICA01.crt create mode 100644 azure-attest/src/ca/ICA03.crt create mode 100644 azure-attest/src/ca/intermediate.rs delete mode 100644 libattest/src/certificates/IntelCA.der delete mode 100644 libattest/src/certificates/format/rsa.rs create mode 100644 libattest/src/crypto.rs rename libattest/src/{certificates/format.rs => crypto/algorithms.rs} (76%) rename libattest/src/{certificates/format => crypto/algorithms}/ecdsa.rs (81%) create mode 100644 libattest/src/crypto/algorithms/rsa.rs rename libattest/src/{certificates.rs => crypto/chain.rs} (81%) rename libattest/src/{certificates => crypto/chain}/crl.rs (89%) rename libattest/src/{certificates => crypto}/error.rs (91%) create mode 100644 libattest/src/crypto/signature.rs create mode 100644 libattest/src/crypto/signature/rsa.rs rename libattest/src/{certificates => crypto}/test/ca_cert.pem (100%) rename libattest/src/{certificates => crypto}/test/ca_crl.pem (100%) rename libattest/src/{certificates => crypto}/test/revoked_cert.pem (100%) diff --git a/Cargo.lock b/Cargo.lock index d43c8ccf..cabbc203 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,15 +626,18 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97bb4a855e3b10f84c4e7e895a7de01db7f9a7b7eb7f73ed9773fd52ac686451" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" dependencies = [ "cpubits", "ctutils", + "getrandom 0.4.2", + "hybrid-array", "num-traits", "rand_core 0.10.0", "serdect", + "subtle", "zeroize", ] @@ -654,7 +657,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.2", "hybrid-array", + "rand_core 0.10.0", ] [[package]] @@ -663,7 +668,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" dependencies = [ - "crypto-bigint 0.7.4", + "crypto-bigint 0.7.5", "rand_core 0.10.0", ] @@ -674,6 +679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ "cmov", + "subtle", ] [[package]] @@ -869,6 +875,7 @@ dependencies = [ "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -917,12 +924,27 @@ checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der 0.7.10", "digest 0.10.7", - "elliptic-curve", - "rfc6979", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", "signature 2.2.0", "spki 0.7.3", ] +[[package]] +name = "ecdsa" +version = "0.17.0-rc.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c72d1455753a703ad4b90ed2a759f2bc4562024a303176439cf6e593b5ade4" +dependencies = [ + "der 0.8.0", + "digest 0.11.3", + "elliptic-curve 0.14.0", + "rfc6979 0.6.0-pre.0", + "signature 3.0.0", + "spki 0.8.0", + "zeroize", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -962,9 +984,9 @@ dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.5", "digest 0.10.7", - "ff", + "ff 0.13.1", "generic-array", - "group", + "group 0.13.0", "hkdf", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", @@ -974,6 +996,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "elliptic-curve" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3273f1195b6f6253ebda493d6742c8baa9b26a291674cd96d92a0f09e90e9b46" +dependencies = [ + "base16ct 1.0.0", + "crypto-bigint 0.7.5", + "crypto-common 0.2.2", + "digest 0.11.3", + "ff 0.14.0", + "group 0.14.0", + "hybrid-array", + "pem-rfc7468 1.0.0", + "pkcs8 0.11.0", + "rand_core 0.10.0", + "sec1 0.8.1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -1070,6 +1113,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.0", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1311,11 +1364,22 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.1", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff 0.14.0", + "rand_core 0.10.0", + "subtle", +] + [[package]] name = "h2" version = "0.3.27" @@ -1396,7 +1460,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -1408,6 +1472,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "hostname-validator" version = "1.1.1" @@ -1487,7 +1560,9 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ + "subtle", "typenum", + "zeroize", ] [[package]] @@ -1902,9 +1977,9 @@ dependencies = [ "base64", "ed25519-dalek", "getrandom 0.2.16", - "hmac", + "hmac 0.12.1", "js-sys", - "p256", + "p256 0.13.2", "p384", "pem", "rand 0.8.5", @@ -1937,17 +2012,18 @@ version = "0.1.0" dependencies = [ "anyhow", "der 0.8.0", + "digest 0.11.3", "getrandom 0.4.2", "hex", "js-sys", - "p256", + "p256 0.14.0-rc.14", "regorus", "rsa 0.10.0-rc.18", "sec1 0.8.1", "serde", "serde-value", "sha2 0.11.0", - "signature 2.2.0", + "signature 3.0.0", "spki 0.8.0", "thiserror 2.0.18", "wasm-bindgen", @@ -2385,21 +2461,34 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", "sha2 0.10.9", ] +[[package]] +name = "p256" +version = "0.14.0-rc.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c855a8d2ffd346aa03122626f22e96e3aa75e3bfe64e6bf6cb82f71821ed6ae7" +dependencies = [ + "ecdsa 0.17.0-rc.22", + "elliptic-curve 0.14.0", + "primefield", + "primeorder 0.14.0-rc.14", + "sha2 0.11.0", +] + [[package]] name = "p384" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", "sha2 0.10.9", ] @@ -2632,13 +2721,39 @@ dependencies = [ "syn", ] +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint 0.7.5", + "crypto-common 0.2.2", + "ff 0.14.0", + "rand_core 0.10.0", + "subtle", + "zeroize", +] + [[package]] name = "primeorder" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.13.8", +] + +[[package]] +name = "primeorder" +version = "0.14.0-rc.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56e6d67fdf5744e9e245ae571450fe584b91f5af261d0e40163b618e53a1f6" +dependencies = [ + "elliptic-curve 0.14.0", + "once_cell", + "primefield", + "serdect", ] [[package]] @@ -3016,10 +3131,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] +[[package]] +name = "rfc6979" +version = "0.6.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9935425142ac6e252364413291d96c8bc9898d0876a801824c7af4eae397b689" +dependencies = [ + "ctutils", + "hmac 0.13.0", +] + [[package]] name = "ring" version = "0.17.14" @@ -3143,7 +3268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" dependencies = [ "const-oid 0.10.2", - "crypto-bigint 0.7.4", + "crypto-bigint 0.7.5", "crypto-primes", "digest 0.11.3", "pkcs1 0.8.0-rc.4", @@ -3331,8 +3456,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct 1.0.0", + "ctutils", "der 0.8.0", "hybrid-array", + "subtle", "zeroize", ] @@ -3775,13 +3902,12 @@ dependencies = [ "der 0.8.0", "hex", "libattest", - "p256", "reqwest 0.13.2", "sec1 0.7.3", "serde", "serde_json", "sha2 0.10.9", - "signature 2.2.0", + "signature 3.0.0", "spki 0.8.0", "thiserror 2.0.18", "tokio", diff --git a/azure-attest/src/ca/ICA01.crt b/azure-attest/src/ca/ICA01.crt new file mode 100644 index 00000000..ad2abe31 --- /dev/null +++ b/azure-attest/src/ca/ICA01.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgITMwAAAALA0XtLj5ecNQAAAAAAAjANBgkqhkiG9w0BAQwF +ADBpMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTowOAYDVQQDEzFBenVyZSBWaXJ0dWFsIFRQTSBSb290IENlcnRpZmljYXRlIEF1 +dGhvcml0eSAyMDIzMB4XDTIzMDYwODE3NTMwNFoXDTI1MTEwMzE3NTMwNFowJTEj +MCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC/NqouHGBovTadw1GLMQYNxWrEciPSh7gE7Vxs +fbbPCE0SfTO80OF2raLdRYN2gEWE2Dr8+TDUuAb/WBFyczhU1aHFPssg8B3/DT6p +TXlDlohLLPWkjYU+OuT1/Ls7RzjQBe2se/MJyPaIJXI6KgCwePw7EcWFChe8aCTU +MHYBG0Ju4xNlMTUd/tcu6a53CXn6Nq48umwlaJelRh+i1f0vcwB2CY/v+Rliwb/8 +DM5Ed9FUHZOKyp5Vnaw9GWloM46sLQT/fdHB0jmugfNZzafkkhQAYiNL3jYNYFZH +5/IgUfYJ/yybwnwoxOdV2NV0Q2i+P5Pcb0WNGaJY47aqOj8BAgMBAAGjggF/MIIB +ezASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwICBDAXBgNVHSUEEDAO +BgVngQUIAQYFZ4EFCAMwHQYDVR0OBBYEFP/2zueowUhpKMuKS/LYgYG1bYCBMB8G +A1UdIwQYMBaAFEv+JlqUwfYzw4NIJt3z5bBksqqVMHYGA1UdHwRvMG0wa6BpoGeG +ZWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL0F6dXJlJTIwVmly +dHVhbCUyMFRQTSUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIw +MjMuY3JsMIGDBggrBgEFBQcBAQR3MHUwcwYIKwYBBQUHMAKGZ2h0dHA6Ly93d3cu +bWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvQXp1cmUlMjBWaXJ0dWFsJTIwVFBN +JTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAyMy5jcnQwDQYJ +KoZIhvcNAQEMBQADggIBAEhTwx26W+Xap7zXExbAwnHYtN6kB4dIGXdgQIiQy5OQ +ltsSj2jx7qZ/af5n5OnTBQ+gXM8siVipgaAIdBkbGgOjCb6b6MTe1YpFAH4fQv8e +VwTVDziBMD0EKI30h0JbFKLdSdKe48O9Lw+T2b0PBXvMFJOSdZT7meGIkpNxSqmA +Z+RNyreLxil9knNjF5ymPT0RcGK52+MwGlElBb/jc+snhr+ZJZ1grjFky9NzjCTi +E5SG+6H3YgiHCqfXr0L3NRt/QZ5IgkuGkNPeMvn4JjevFwAhXFxBqJYJ7mY61MJu +WTdyhhoUJgzmZo1hS+GNyuMKaKBLreUwtc1y7LRH3YGGed57HbQ9bmyMdhO7x8KZ +NrBDX2/cRLzrCmpSUldmKMu9G4dpzXpde4pFMObiVFrGRq8/9HMOJwZlQvzhwQp0 +PUMY/gIU5rf23n1M1M36tM5g5CEzxQUGtVaG9ABTJQ2zijD5wDo840vbznyKt3ih +imrUs+LqpPDNXyxbwvibcZidwSdhu0QmUoyYsgSP2Zff5E8ks53h2xQSM3zz2qaW +VS1vVqG4zC0EfRnO65ogPPfrtK6ZiFmVHSWP9vPkFcUNYDnYQXW/TArO/JCe2I++ +GClM7AcDQwWLxcopzskGQdHNM1zMsprRRwYaVpTJH67xeNda6+Y7IOPJYTvyoXHP +-----END CERTIFICATE----- diff --git a/azure-attest/src/ca/ICA03.crt b/azure-attest/src/ca/ICA03.crt new file mode 100644 index 00000000..a8753d05 --- /dev/null +++ b/azure-attest/src/ca/ICA03.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgITMwAAAAknQOWscnsOpgAAAAAACTANBgkqhkiG9w0BAQwF +ADBpMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTowOAYDVQQDEzFBenVyZSBWaXJ0dWFsIFRQTSBSb290IENlcnRpZmljYXRlIEF1 +dGhvcml0eSAyMDIzMB4XDTI1MDQyNDE4MDExN1oXDTI3MDQyNDE4MDExN1owJTEj +MCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDYGYtis5ka0cxQkhU11jslgX6wzjR/UXQIFdUn +8juTUMJl91VokwUPX3WfXeog7mtbWyYWD8SI0BSnchRGlV8u3AhcW61/HetHqmIL +tD0c75UATi+gsTQnpwKPA/m38MGGyXFETr3xHXjilUPfIhmxO4ImuNJ0R95bZYhx +bLYmOZpVUcj8oz980An8HlIqSzrskQR6NiuEmikHkHc1/CpoNunrr8kQNPF6gxex +IrvXsKLUAuUqnNtcQWc/8Er5EN9+TdX6AOjUmKriVGbCInP1m/aC+DWH/+aJ/8aD +pKze6fe7OHh2BL9hxqIsmJAStIh4siRdLYTt8hKGmkdzOWnRAgMBAAGjggF/MIIB +ezASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwICBDAXBgNVHSUEEDAO +BgVngQUIAQYFZ4EFCAMwHQYDVR0OBBYEFGcJhvj5gV6TrfnJZOcUCtqZywotMB8G +A1UdIwQYMBaAFEv+JlqUwfYzw4NIJt3z5bBksqqVMHYGA1UdHwRvMG0wa6BpoGeG +ZWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL0F6dXJlJTIwVmly +dHVhbCUyMFRQTSUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIw +MjMuY3JsMIGDBggrBgEFBQcBAQR3MHUwcwYIKwYBBQUHMAKGZ2h0dHA6Ly93d3cu +bWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvQXp1cmUlMjBWaXJ0dWFsJTIwVFBN +JTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAyMy5jcnQwDQYJ +KoZIhvcNAQEMBQADggIBAJPP3Z2z1zhzUS3qSRVgyoUVnaxCGuMHzPQAZuoPBVpz +wKnv4HqyjMgT8pBtQqxkqAsg7KiqbPfO97bMCHcuqkkfHjw8yg6IYt01RjUjVPKq +lrsY2iw7hFWNWr8SGMa10JdNYNyf5dxob5+mKAwEOhLzKNwq9rM/uIvZky77pNly +RLt55XEPfBMYdI9I8uQ5Uqmrw7mVJfERMfTBhSQF9BrcajAsaLcs7qEUyj0yUdJf +cgZkfCoUEUSPr3OwLHaYeV1J6VidhIYsYo53sXXal91d60NspYgei2nJFei/+R3E +SWnGbPBW+EQ4FbvZXxu57zUMX9mM7lC+GoXLvA6/vtKShEi9ZXl2PSnBQ/R2A7b3 +AXyg4fmMLFausEk6OiuU8E/bvp+gPLOJ8YrX7SAJVuEn+koJaK5G7os5DMIh7/KM +l9cI9WxPwqoWjp4VBfrF4hDOCmKWrqtFUDQCML8qD8RTxlQKQtgeGAcNDfoAuL9K +VtSG5/iIhuyBEFYEHa3vRWbSaHCUzaHJsTmLcz4cp1VDdepzqZRVuErBzJKFnBXb +zRNW32EFmcAUKZImIsE5dgB7y7eiijf33VWNfWmK05fxzQziWFWRYlET4SVc3jMn +PBiY3N8BfK8EBOYbLvzo0qn2n3SAmPhYX3Ag6vbbIHd4Qc8DQKHRV0PB8D3jPGmD +-----END CERTIFICATE----- diff --git a/azure-attest/src/ca/intermediate.rs b/azure-attest/src/ca/intermediate.rs new file mode 100644 index 00000000..fae8db6b --- /dev/null +++ b/azure-attest/src/ca/intermediate.rs @@ -0,0 +1,73 @@ +use std::sync::LazyLock; + +use libattest::{ + crypto::algorithms::{CertFormat, rsa::RsaCert}, + error::Context, +}; +use x509_cert::{ + der::DecodePem, + ext::pkix::{AuthorityKeyIdentifier, SubjectKeyIdentifier}, +}; + +pub struct IntermediateDb { + certs: Vec<(SubjectKeyIdentifier, RsaCert)>, +} + +impl IntermediateDb { + fn new() -> Self { + Self { certs: vec![] } + } + + fn add_pem(mut self, pem: &str) -> Self { + let cert = x509_cert::Certificate::from_pem(pem).unwrap(); + let cert = RsaCert::from_certificate(cert).unwrap(); + + let (_, ski) = cert + .certificate() + .get_extension::() + .unwrap() + .expect("missing intermediate SKI from intermediate certificate"); + + self.certs.push((ski, cert)); + self + } + + pub fn find_intermediate(&self, leaf: &RsaCert) -> libattest::Result<&RsaCert> { + let (_, leaf_issuer_id) = leaf + .certificate() + .get_extension::()? + .context("AuthorityKeyIdentifier extension not found in leaf certificate")?; + + let leaf_issuer_id = leaf_issuer_id + .key_identifier + .context("missing key identifier from leaf certificate")?; + + let (_, cert) = self + .certs + .iter() + .find(|(ski, _)| ski.0 == leaf_issuer_id) + .context("couldn't find appropriate intermediate certificate")?; + + Ok(cert) + } +} + +static INTERMEDIATE_DB: LazyLock = LazyLock::new(|| { + IntermediateDb::new() + // .add_pem(include_str!("./ICA01.crt")) + .add_pem(include_str!("./ICA03.crt")) +}); + +pub fn intermediate_db() -> &'static IntermediateDb { + &INTERMEDIATE_DB +} + +#[cfg(test)] +mod test { + use crate::ca::intermediate_db; + + #[test] + fn parse_intermediate_cas() { + let db = intermediate_db(); + } +} diff --git a/azure-attest/src/ca/mod.rs b/azure-attest/src/ca/mod.rs index 424cee7a..d2da5407 100644 --- a/azure-attest/src/ca/mod.rs +++ b/azure-attest/src/ca/mod.rs @@ -1,6 +1,8 @@ +mod intermediate; + use std::sync::LazyLock; -use libattest::certificates::format::{CertFormat, rsa::RsaCert}; +use libattest::crypto::algorithms::{CertFormat, rsa::RsaCert}; use x509_cert::{Certificate, der::DecodePem}; static AZURE_CA_PEM: &[u8] = include_bytes!("./Azure.crt"); @@ -9,15 +11,17 @@ pub static AZURE_CA: LazyLock = LazyLock::new(|| { RsaCert::from_certificate(Certificate::from_pem(AZURE_CA_PEM).unwrap()).unwrap() }); +pub use intermediate::intermediate_db; + #[cfg(test)] mod test { - use libattest::certificates::format::{CertFormat, rsa::RsaCert}; + use libattest::crypto::algorithms::{CertFormat, rsa::RsaCert}; use crate::ca::AZURE_CA; #[test] fn read_parse_ca() { let ca: &RsaCert = &AZURE_CA; - ca.cetificate(); + ca.certificate(); } } diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index 831e5691..2dc37463 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -2,7 +2,15 @@ use std::ops::Deref; use base64::Engine; use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet}; -use libattest::{ByteNonce, error::Context}; +use libattest::{ + ByteNonce, + crypto::{ + CertificateChain, + algorithms::{CertFormat, rsa::RsaCert}, + signature::rsa::RsaSignature, + }, + error::Context, +}; use rsa::{ BoxedUint, pkcs1::DecodeRsaPublicKey, @@ -16,13 +24,17 @@ use x509_cert::der::{ }; use crate::{ + ca::AZURE_CA, collateral::ReportVerifier, nonce::AzureNonce, quote::{AzureQuote, ParsedHardwareReport}, report::RuntimeClaims, }; -pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { +pub fn verify_quote_signature( + quote: &AzureQuote, + trust_chain: CertificateChain, +) -> libattest::Result<()> { use tpm2_protocol::{TpmMarshal, TpmWriter}; let mut buffer = Box::new([0u8; 512]); @@ -33,15 +45,15 @@ pub fn verify_quote_signature(quote: &AzureQuote) -> libattest::Result<()> { let written = writer.len(); let marshaled = &buffer[..written]; - quote - .trust - .ak_key - .verify(marshaled, "e.trust.quote_signature)?; + let signature = RsaSignature::::new(quote.trust.quote_signature.clone()); + trust_chain.verify(marshaled, &signature)?; Ok(()) } -pub fn verify_quote_certificate(quote: &AzureQuote) -> libattest::Result<()> { +pub fn verify_quote_trust_chain( + quote: &AzureQuote, +) -> libattest::Result> { let pk = quote .trust .ak_cert @@ -59,7 +71,18 @@ pub fn verify_quote_certificate(quote: &AzureQuote) -> libattest::Result<()> { libattest::bail!(exposed: "mismatched ak key between certificate and evidence"); } - Ok(()) + let cert = RsaCert::from_certificate(quote.trust.ak_cert.clone()) + .context("failed validating certificate")?; + + let intermediate = crate::ca::intermediate_db() + .find_intermediate(&cert) + .context("failed finding matching intermediate certificate for ak_cert")?; + + let chain = CertificateChain::::with_anchor(&AZURE_CA) + .with_certificate(intermediate.clone())? + .with_certificate(cert)?; + + Ok(chain) } fn decode_base64_component(base64: &str) -> Result { @@ -174,15 +197,19 @@ pub fn verify( report_verifier: ReportVerifier, nonce: &AzureNonce, ) -> libattest::Result<()> { + // 2: verify quote certificate + let trust_chain = + verify_quote_trust_chain(&azure_quote).context("while verifying quote certificates")?; // 1: verify that AK signs Quote through signature - verify_quote_signature(&azure_quote)?; - // 2: verify that the hardware report signs report data - verify_report_digest(&azure_quote, &report_verifier)?; - // 3: verify that report data contains the correct ak key, + verify_quote_signature(&azure_quote, trust_chain).context("while verifying quote signature")?; + // 3: verify that the hardware report signs report data + verify_report_digest(&azure_quote, &report_verifier) + .context("while verifying report digest")?; + // 4: verify that report data contains the correct ak key, // sprouting azure trust from SEV/TDX - verify_runtime_data(&azure_quote)?; - // 4: Verify that user supplied nonce and received quote nonce match - verify_quote_nonce(&azure_quote, nonce)?; + verify_runtime_data(&azure_quote).context("while verifying runtime data")?; + // 5: Verify that user supplied nonce and received quote nonce match + verify_quote_nonce(&azure_quote, nonce).context("while verifying quote nonce")?; Ok(()) } diff --git a/libattest/Cargo.toml b/libattest/Cargo.toml index cfe18ae5..72d94cef 100644 --- a/libattest/Cargo.toml +++ b/libattest/Cargo.toml @@ -13,13 +13,14 @@ regorus = { git = "https://github.com/microsoft/regorus.git", default-features = serde-value = "0.7.0" x509-cert = "0.3.0-rc.4" sha2 = "0.11.0" -p256 = "0.13.2" -signature = "2.2.0" +p256 = "0.14.0-rc.14" +signature = "3.0" sec1 = "0.8.1" spki = "0.8.0" thiserror = "2.0.18" der = "0.8.0" rsa = { version = "0.10.0-rc.18", features = ["serde"] } +digest = "0.11.3" [target.'cfg(target_family = "wasm")'.dependencies] wasm-bindgen = "0.2" diff --git a/libattest/src/certificates/IntelCA.der b/libattest/src/certificates/IntelCA.der deleted file mode 100644 index 768806c673709fb71537020abbabad2b1dc23773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmXqLV(K?&Vlr94%*4pVB%+kcb1iDFN$2P0EmMQ`2S{70v#l}UV&l+i^EhYA!pvll zVJKxF!NwfQ!ptM+nOBmUqY&&Kp%9dxU!vgb2$SQ2$vNj2733EsmSpDV8HyMPf%LKS za0e&ml_V-S=Oh*-8gd(OfM-Ch~kvMtrKjmv1bZ!Z(*nQf5%loS+O>FXz_7L{bCWhN(hi5#{Wtmy%j!APX{+k420HyOn$(X?{k= z|17|0XJi9Mo~$s4&tbp@q?i~PkrM{9Cxd|-lOn^@{ZBasf9gARD%>&aGSGW`QSWG5 z{WqmIDbv3SH3&@mV{?50sw!g B#yS81 diff --git a/libattest/src/certificates/format/rsa.rs b/libattest/src/certificates/format/rsa.rs deleted file mode 100644 index 53812f1d..00000000 --- a/libattest/src/certificates/format/rsa.rs +++ /dev/null @@ -1,91 +0,0 @@ -use der::{ - Encode, - oid::db::rfc5912::{RSA_ENCRYPTION, SHA_384_WITH_RSA_ENCRYPTION}, -}; -use rsa::{ - RsaPublicKey, - pkcs1::DecodeRsaPublicKey, - pkcs1v15::{Signature, VerifyingKey}, - signature::Verifier, -}; -use sha2::Sha384; -use x509_cert::certificate::TbsCertificateInner; - -use crate::certificates::{ - CertificateError, - format::{Cert, CertFormat, Verify, verify_cert_common}, -}; - -#[derive(Debug)] -pub struct RsaCert { - certificate: x509_cert::Certificate, - public_key: VerifyingKey, - signature: Signature, -} - -impl super::sealed::Sealed for RsaCert {} - -impl CertFormat for RsaCert { - type Signature = Signature; - - fn from_certificate(cert: x509_cert::Certificate) -> Result { - Self::from_cert(cert) - } - - fn cetificate(&self) -> &TbsCertificateInner { - self.certificate.tbs_certificate() - } -} - -impl Verify for RsaCert { - fn verify_signature(&self, msg: &[u8], signature: &Signature) -> Result<(), CertificateError> { - self.public_key.verify(msg, signature).map_err(Into::into) - } -} - -impl Cert for RsaCert { - fn verify_cert(&self, other: &Self) -> Result<(), CertificateError> { - let other_tbs = other.certificate.tbs_certificate().to_der()?; - self.verify_signature(&other_tbs, &other.signature) - } -} - -impl RsaCert { - fn from_cert(certificate: x509_cert::Certificate) -> Result { - certificate - .signature_algorithm() - .assert_algorithm_oid(SHA_384_WITH_RSA_ENCRYPTION) - .map_err(CertificateError::WrongAlgorithm)?; - - let pk = certificate.tbs_certificate().subject_public_key_info(); - pk.algorithm - .assert_algorithm_oid(RSA_ENCRYPTION) - .map_err(CertificateError::WrongAlgorithm)?; - - let signature = certificate - .signature() - .as_bytes() - .ok_or(CertificateError::WrongFormat) - .and_then(|sig| Signature::try_from(sig).map_err(Into::into))?; - - let public_key = RsaPublicKey::from_pkcs1_der(pk.subject_public_key.raw_bytes()) - .map(VerifyingKey::new) - .map_err(|_| CertificateError::WrongFormat)?; - - verify_cert_common(&certificate)?; - - Ok(Self { - certificate, - public_key, - signature, - }) - } -} - -impl TryFrom for RsaCert { - type Error = CertificateError; - - fn try_from(value: x509_cert::Certificate) -> Result { - Self::from_cert(value) - } -} diff --git a/libattest/src/crypto.rs b/libattest/src/crypto.rs new file mode 100644 index 00000000..1c3cf435 --- /dev/null +++ b/libattest/src/crypto.rs @@ -0,0 +1,10 @@ +pub mod algorithms; +pub mod chain; +pub mod error; +pub mod signature; + +pub use error::CertificateError; + +pub use algorithms::Cert; +pub use chain::CertificateChain; +pub use signature::DigestSignature; diff --git a/libattest/src/certificates/format.rs b/libattest/src/crypto/algorithms.rs similarity index 76% rename from libattest/src/certificates/format.rs rename to libattest/src/crypto/algorithms.rs index 941ae71b..782048a7 100644 --- a/libattest/src/certificates/format.rs +++ b/libattest/src/crypto/algorithms.rs @@ -1,6 +1,6 @@ use x509_cert::certificate::TbsCertificateInner; -use crate::certificates::CertificateError; +use crate::crypto::CertificateError; pub mod ecdsa; pub mod rsa; @@ -10,22 +10,14 @@ mod sealed { } pub trait CertFormat: Sized + sealed::Sealed { - type Signature; - /// Parse this certificate format from the generix `x509_cert::Certificate` type fn from_certificate(cert: x509_cert::Certificate) -> Result; /// Get inner signed data of this certificate - fn cetificate(&self) -> &TbsCertificateInner; -} - -pub trait Verify { - /// Uses the public component of this certificate to verify another signature - /// over arbitrary data. - fn verify_signature(&self, msg: &[u8], signature: &Signature) -> Result<(), CertificateError>; + fn certificate(&self) -> &TbsCertificateInner; } -pub trait Cert: CertFormat + Verify { +pub trait Cert: CertFormat { /// Verifies that the public component of this certificate signs another certificate's TBS fn verify_cert(&self, other: &Self) -> Result<(), CertificateError>; diff --git a/libattest/src/certificates/format/ecdsa.rs b/libattest/src/crypto/algorithms/ecdsa.rs similarity index 81% rename from libattest/src/certificates/format/ecdsa.rs rename to libattest/src/crypto/algorithms/ecdsa.rs index c5ae6456..d5369ee5 100644 --- a/libattest/src/certificates/format/ecdsa.rs +++ b/libattest/src/crypto/algorithms/ecdsa.rs @@ -6,9 +6,9 @@ use p256::ecdsa::{Signature, VerifyingKey}; use signature::Verifier; use x509_cert::certificate::TbsCertificateInner; -use crate::certificates::{ +use crate::crypto::{ CertificateError, - format::{Cert, CertFormat, Verify, verify_cert_common}, + algorithms::{Cert, CertFormat, verify_cert_common}, }; #[derive(Debug)] @@ -24,20 +24,18 @@ pub struct EcdsaCert { impl super::sealed::Sealed for EcdsaCert {} impl CertFormat for EcdsaCert { - type Signature = Signature; - fn from_certificate(cert: x509_cert::Certificate) -> Result { Self::from_cert(cert) } - fn cetificate(&self) -> &TbsCertificateInner { + fn certificate(&self) -> &TbsCertificateInner { self.certificate.tbs_certificate() } } -impl Verify for EcdsaCert { - fn verify_signature(&self, msg: &[u8], signature: &Signature) -> Result<(), CertificateError> { - self.public_key.verify(msg, signature).map_err(Into::into) +impl signature::Verifier for EcdsaCert { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + self.public_key.verify(msg, signature) } } @@ -121,16 +119,3 @@ impl EcdsaCert { }) } } - -// impl Verifier for EcdsaCert { -// fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { -// self.public_key.verify(msg, signature) -// } -// } - -impl TryFrom for EcdsaCert { - type Error = CertificateError; - fn try_from(value: x509_cert::Certificate) -> Result { - Self::from_cert(value) - } -} diff --git a/libattest/src/crypto/algorithms/rsa.rs b/libattest/src/crypto/algorithms/rsa.rs new file mode 100644 index 00000000..d6cf898e --- /dev/null +++ b/libattest/src/crypto/algorithms/rsa.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use der::{ + Encode, + oid::db::rfc5912::{RSA_ENCRYPTION, SHA_256_WITH_RSA_ENCRYPTION, SHA_384_WITH_RSA_ENCRYPTION}, +}; +use rsa::{RsaPublicKey, pkcs1::DecodeRsaPublicKey, pkcs1v15::Signature, signature::Verifier}; +use sha2::{Sha256, Sha384}; +use x509_cert::certificate::TbsCertificateInner; + +use crate::crypto::{ + CertificateError, DigestSignature, + algorithms::{Cert, CertFormat, verify_cert_common}, + signature::rsa::RsaSignature, +}; + +#[derive(Clone)] +pub struct RsaCert { + certificate: x509_cert::Certificate, + + /// verifyingkey algorithm is dictated by the format + /// of the signature, not by our public key, so we keep generic + public_key: RsaPublicKey, + + // this should be Signature. + signature: Arc, +} + +impl super::sealed::Sealed for RsaCert {} + +impl CertFormat for RsaCert { + fn from_certificate(cert: x509_cert::Certificate) -> Result { + Self::from_cert(cert) + } + + fn certificate(&self) -> &TbsCertificateInner { + self.certificate.tbs_certificate() + } +} + +impl Verifier for RsaCert { + fn verify(&self, msg: &[u8], signature: &D) -> Result<(), signature::Error> { + signature.verify(msg, &self.public_key.clone()) + } +} + +impl Cert for RsaCert { + fn verify_cert(&self, other: &Self) -> Result<(), CertificateError> { + let other_tbs = other.certificate.tbs_certificate().to_der()?; + + let public_key = self.public_key.clone(); + + other + .signature + .verify(&other_tbs, &public_key) + .map_err(CertificateError::BadSignature) + } +} + +impl RsaCert { + fn from_cert(certificate: x509_cert::Certificate) -> Result { + let pk = certificate.tbs_certificate().subject_public_key_info(); + pk.algorithm + .assert_algorithm_oid(RSA_ENCRYPTION) + .map_err(CertificateError::WrongAlgorithm)?; + + let signature = certificate + .signature() + .as_bytes() + .ok_or(CertificateError::WrongFormat) + .and_then(|sig| Signature::try_from(sig).map_err(Into::into))?; + + let public_key = RsaPublicKey::from_pkcs1_der(pk.subject_public_key.raw_bytes()) + .map_err(|_| CertificateError::WrongFormat)?; + + verify_cert_common(&certificate)?; + + let signature: Arc = + match certificate.signature_algorithm().oid { + SHA_384_WITH_RSA_ENCRYPTION => Arc::new(RsaSignature::::new(signature)), + SHA_256_WITH_RSA_ENCRYPTION => Arc::new(RsaSignature::::new(signature)), + _ => return Err(CertificateError::Unsupported), + }; + + Ok(Self { + certificate, + public_key, + signature, + }) + } +} diff --git a/libattest/src/certificates.rs b/libattest/src/crypto/chain.rs similarity index 81% rename from libattest/src/certificates.rs rename to libattest/src/crypto/chain.rs index 965bfa19..817cce0c 100644 --- a/libattest/src/certificates.rs +++ b/libattest/src/crypto/chain.rs @@ -1,10 +1,6 @@ pub mod crl; -pub mod error; -pub mod format; -pub use error::CertificateError; - -use crate::certificates::format::{Cert, Verify}; +use crate::crypto::{CertificateError, algorithms::Cert}; pub type PinnedCertificate = &'static C; @@ -34,11 +30,18 @@ impl CertificateChain { self.chain.last() } + pub fn with_certificate(mut self, other: C) -> Result { + self.push_certificate(other)?; + Ok(self) + } + /// Inserts a new leaf into this certificate chain. The certificate is first /// verified by the previous certificates pub fn push_certificate(&mut self, other: C) -> Result<(), CertificateError> { let verifier = self.chain.last().or(self.anchor); + // todo: add match between SKI and AKI + match verifier { Some(verifier) => verifier.verify_cert(&other)?, None => other.verify_self()?, @@ -72,10 +75,11 @@ impl CertificateChain { } } -impl Verify for CertificateChain { - fn verify_signature(&self, msg: &[u8], sig: &C::Signature) -> Result<(), CertificateError> { - self.leaf() - .ok_or(CertificateError::NoLeaf)? - .verify_signature(msg, sig) +impl signature::Verifier for CertificateChain +where + C: Cert + signature::Verifier, +{ + fn verify(&self, msg: &[u8], sig: &S) -> Result<(), signature::Error> { + self.leaf().unwrap().verify(msg, sig) } } diff --git a/libattest/src/certificates/crl.rs b/libattest/src/crypto/chain/crl.rs similarity index 89% rename from libattest/src/certificates/crl.rs rename to libattest/src/crypto/chain/crl.rs index 4c84d665..70581f5c 100644 --- a/libattest/src/certificates/crl.rs +++ b/libattest/src/crypto/chain/crl.rs @@ -4,8 +4,9 @@ use p256::ecdsa::Signature; use x509_cert::crl::CertificateList; use x509_cert::der::DecodePem; -use crate::certificates::format::{Cert, CertFormat, Verify}; -use crate::certificates::{CertificateChain, CertificateError}; +use crate::crypto::CertificateError; +use crate::crypto::algorithms::{Cert, CertFormat}; +use crate::crypto::chain::CertificateChain; mod sealed { pub trait Sealed {} @@ -21,7 +22,7 @@ impl sealed::Sealed for Crl {} impl Crl { pub fn from_pem( - verifier: impl Verify, + verifier: impl signature::Verifier, pem: impl AsRef<[u8]>, ) -> Result { let list = CertificateList::from_pem(pem)?; @@ -29,7 +30,7 @@ impl Crl { } pub fn from_der( - verifier: impl Verify, + verifier: impl signature::Verifier, der: impl AsRef<[u8]>, ) -> Result { let list = CertificateList::from_der(der.as_ref())?; @@ -37,7 +38,7 @@ impl Crl { } pub fn from_certificate_list( - verifier: impl Verify, + verifier: impl signature::Verifier, list: CertificateList, ) -> Result { // let list: CertificateList = CertificateList::from_pem(pem)?; @@ -70,7 +71,7 @@ impl Crl { Signature::from_der(signature).expect("could not re-decode an encoded signature"); // verify the signature of the certificate chain - verifier.verify_signature(&tbs_list, &signature)?; + verifier.verify(&tbs_list, &signature)?; // ok! Ok(Self { list, signature }) @@ -99,7 +100,7 @@ impl VerifyCrl for Crl { .flatten(); // if this serial number is found in the list, the certificate has been revoked - let serial_number = tbs.cetificate().serial_number().clone(); + let serial_number = tbs.certificate().serial_number().clone(); let found = certificates .map(|cert| &cert.serial_number) diff --git a/libattest/src/certificates/error.rs b/libattest/src/crypto/error.rs similarity index 91% rename from libattest/src/certificates/error.rs rename to libattest/src/crypto/error.rs index 467344cd..a0f7f2ad 100644 --- a/libattest/src/certificates/error.rs +++ b/libattest/src/crypto/error.rs @@ -6,6 +6,9 @@ pub enum CertificateError { #[error("tried parsing a certificate with an unsupported algorithm: {0}")] WrongAlgorithm(spki::Error), + #[error("the signature or protocol used by this certificated is not supported")] + Unsupported, + #[error("a signature or key was serialized in the wrong format in the certificate")] WrongFormat, @@ -27,9 +30,6 @@ pub enum CertificateError { #[error("signature error: {0}")] Signature(#[from] signature::Error), - #[error("rsa signature error: {0}")] - RsaSignature(#[from] rsa::signature::Error), - #[error("an error occurred while parsing the certificate: {0}")] Der(#[from] der::Error), diff --git a/libattest/src/crypto/signature.rs b/libattest/src/crypto/signature.rs new file mode 100644 index 00000000..41bf5439 --- /dev/null +++ b/libattest/src/crypto/signature.rs @@ -0,0 +1,8 @@ +use ::rsa::RsaPublicKey; + +pub mod rsa; + +pub trait DigestSignature { + /// automatically converts this public key into an appropriate + fn verify(&self, msg: &[u8], pk: &RsaPublicKey) -> signature::Result<()>; +} diff --git a/libattest/src/crypto/signature/rsa.rs b/libattest/src/crypto/signature/rsa.rs new file mode 100644 index 00000000..d6aef3ba --- /dev/null +++ b/libattest/src/crypto/signature/rsa.rs @@ -0,0 +1,32 @@ +use std::marker::PhantomData; + +use digest::FixedOutputReset; +use rsa::{ + RsaPublicKey, + pkcs1v15::{Signature, VerifyingKey}, +}; +use signature::Verifier; + +/// PKCS1v15 signature, digest agnostic +pub struct RsaSignature { + signature: Signature, + _digest: PhantomData, +} + +impl RsaSignature { + pub fn new(signature: Signature) -> Self { + Self { + signature, + _digest: PhantomData, + } + } +} + +impl super::DigestSignature + for RsaSignature +{ + fn verify(&self, msg: &[u8], pk: &RsaPublicKey) -> signature::Result<()> { + let verifying = VerifyingKey::::new(pk.clone()); + verifying.verify(msg, &self.signature) + } +} diff --git a/libattest/src/certificates/test/ca_cert.pem b/libattest/src/crypto/test/ca_cert.pem similarity index 100% rename from libattest/src/certificates/test/ca_cert.pem rename to libattest/src/crypto/test/ca_cert.pem diff --git a/libattest/src/certificates/test/ca_crl.pem b/libattest/src/crypto/test/ca_crl.pem similarity index 100% rename from libattest/src/certificates/test/ca_crl.pem rename to libattest/src/crypto/test/ca_crl.pem diff --git a/libattest/src/certificates/test/revoked_cert.pem b/libattest/src/crypto/test/revoked_cert.pem similarity index 100% rename from libattest/src/certificates/test/revoked_cert.pem rename to libattest/src/crypto/test/revoked_cert.pem diff --git a/libattest/src/lib.rs b/libattest/src/lib.rs index c2f5bca7..ac40a37d 100644 --- a/libattest/src/lib.rs +++ b/libattest/src/lib.rs @@ -1,4 +1,4 @@ -pub mod certificates; +pub mod crypto; pub mod error; pub mod modules; pub mod nonce; @@ -11,6 +11,8 @@ pub use nonce::*; pub type Result = std::result::Result; pub use error::AttestationError; +pub use p256; + #[cfg(target_arch = "wasm32")] pub(crate) fn now() -> std::time::SystemTime { std::time::SystemTime::UNIX_EPOCH diff --git a/tdx-attest/Cargo.toml b/tdx-attest/Cargo.toml index 3af945d7..a4c9fcbb 100644 --- a/tdx-attest/Cargo.toml +++ b/tdx-attest/Cargo.toml @@ -9,12 +9,11 @@ edition = "2024" libattest = { path = "../libattest" } thiserror = "2" zerocopy = { version = "0.8.39", features = ["derive"] } -p256 = "0.13.2" x509-cert = { version = "0.3.0-rc.4", features = ["pem"] } spki = "0.8.0-rc.4" der = { version = "0.8.0-rc.10", features = ["derive", "oid", "std"] } # x509-verify = { version = "0.4.8", features = ["ed25519", "p256", "sha2", "x509"], default-features = false } -signature = "2.2.0" +signature = "3.0" sec1 = "0.7.3" reqwest = { version = "0.13.2", features = ["json", "query"] } chrono = { version = "0.4.44", features = ["serde"] } diff --git a/tdx-attest/src/certificates.rs b/tdx-attest/src/certificates.rs index ef134d9c..af43a4e8 100644 --- a/tdx-attest/src/certificates.rs +++ b/tdx-attest/src/certificates.rs @@ -8,10 +8,6 @@ use der::{ Encode, oid::db::rfc5912::{ECDSA_WITH_SHA_256, ID_EC_PUBLIC_KEY}, }; -use p256::{ - PublicKey, - ecdsa::{DerSignature, Signature, VerifyingKey, signature::Verifier}, -}; use spki::{DecodePublicKey, ObjectIdentifier}; use thiserror::Error; use x509_cert::{ diff --git a/tdx-attest/src/certificates/ca.rs b/tdx-attest/src/certificates/ca.rs index 20e3e660..9ad4a70f 100644 --- a/tdx-attest/src/certificates/ca.rs +++ b/tdx-attest/src/certificates/ca.rs @@ -1,7 +1,7 @@ use std::sync::LazyLock; use der::Decode; -use libattest::certificates::format::{CertFormat, ecdsa::EcdsaCert}; +use libattest::crypto::algorithms::{CertFormat, ecdsa::EcdsaCert}; use x509_cert::Certificate; static INTEL_CA_DER: &[u8; 659] = include_bytes!("./IntelCA.der"); diff --git a/tdx-attest/src/lib.rs b/tdx-attest/src/lib.rs index 476212fc..0f88b27e 100644 --- a/tdx-attest/src/lib.rs +++ b/tdx-attest/src/lib.rs @@ -1,10 +1,6 @@ #![warn(clippy::pedantic)] #![allow(unused, clippy::missing_panics_doc)] -use p256::ecdsa::VerifyingKey; -use p256::{EncodedPoint, elliptic_curve}; -use p256::{PublicKey, ecdsa::Signature}; - use crate::certificates::ca; use crate::certificates::extensions::{SgxExtension, SgxExtensions}; use crate::dcap::TdQuote; diff --git a/tdx-attest/src/pcs.rs b/tdx-attest/src/pcs.rs index 6adfe9e3..84a20679 100644 --- a/tdx-attest/src/pcs.rs +++ b/tdx-attest/src/pcs.rs @@ -5,10 +5,9 @@ pub mod tcb; use std::str::FromStr; use libattest::{ - certificates::{CertificateChain, crl::Crl, format::ecdsa::EcdsaCert}, + crypto::{CertificateChain, algorithms::ecdsa::EcdsaCert, chain::crl::Crl}, error::Context, }; -use p256::ecdsa::Signature; use reqwest::{Client, IntoUrl, Url}; use serde::Deserialize; use x509_cert::Certificate; diff --git a/tdx-attest/src/pcs/signed_response.rs b/tdx-attest/src/pcs/signed_response.rs index 5afa60e5..bef74276 100644 --- a/tdx-attest/src/pcs/signed_response.rs +++ b/tdx-attest/src/pcs/signed_response.rs @@ -3,14 +3,11 @@ use std::marker::PhantomData; use crate::{ca::INTEL_CA, error::TdxError}; use anyhow::bail; use chrono::Utc; +use libattest::p256::ecdsa::Signature; use libattest::{ - certificates::{ - CertificateChain, - format::{Verify, ecdsa::EcdsaCert}, - }, + crypto::{CertificateChain, algorithms::ecdsa::EcdsaCert}, error::Context, }; -use p256::ecdsa::Signature; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::Value; use signature::Verifier; @@ -61,7 +58,7 @@ impl SignedResponse { // verify signature and message using certificate chain self.chain - .verify_signature(&msg, &self.signature) + .verify(&msg, &self.signature) .context("signed response has a bad signature")?; let data = T::deserialize(&self.data).context("response data is in the wrong format")?; diff --git a/tdx-attest/src/quote.rs b/tdx-attest/src/quote.rs index 71aa8370..ccbb4943 100644 --- a/tdx-attest/src/quote.rs +++ b/tdx-attest/src/quote.rs @@ -1,6 +1,7 @@ use libattest::{ - certificates::{CertificateChain, format::ecdsa::EcdsaCert}, + crypto::{CertificateChain, algorithms::ecdsa::EcdsaCert}, error::Context, + p256, }; use p256::ecdsa::{Signature, VerifyingKey}; diff --git a/tdx-attest/src/verify.rs b/tdx-attest/src/verify.rs index ee9aa47b..8ebf9e4c 100644 --- a/tdx-attest/src/verify.rs +++ b/tdx-attest/src/verify.rs @@ -1,10 +1,6 @@ use std::ops::Deref; -use libattest::{ - certificates::{crl::VerifyCrl, format::Verify}, - error::Context, - quote::QuoteVerifier, -}; +use libattest::{crypto::chain::crl::VerifyCrl, error::Context, quote::QuoteVerifier}; use sha2::{Digest, Sha256, digest::Update}; use signature::Verifier; use zerocopy::IntoBytes; @@ -50,7 +46,7 @@ fn verify_qe_report(quote: &TdxQuote, collateral: &Collateral) -> Result<(), Tdx .context("qe report is not in the first level of nesting in dcap quote")?; let qe_report_data = qe_report.qe_report.as_bytes(); - pck_chain.verify_signature(qe_report_data, &qe_report.qe_report_signature)?; + pck_chain.verify(qe_report_data, &qe_report.qe_report_signature)?; // now verify hashes let attestation_key = certification.attestation_key.to_sec1_bytes(); From 824544d4f6e2b064cbd38c05a1349aecc685e53b Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:29:20 +0000 Subject: [PATCH 23/39] pcr bank and digest code --- Cargo.lock | 1 + azure-attest/Cargo.toml | 1 + azure-attest/src/host.rs | 9 +++-- azure-attest/src/host/vtpm.rs | 29 ++++++++++++-- azure-attest/src/quote.rs | 25 +++++++++--- azure-attest/src/quote/claims.rs | 33 +++++++++++++++ azure-attest/src/quote/pcr.rs | 69 ++++++++++++++++++++++++++++++++ azure-attest/src/quote/verify.rs | 20 ++++++++- 8 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 azure-attest/src/quote/claims.rs create mode 100644 azure-attest/src/quote/pcr.rs diff --git a/Cargo.lock b/Cargo.lock index cabbc203..20aae23d 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,7 @@ version = "0.1.0" dependencies = [ "anyhow", "base64", + "hex", "jsonwebtoken", "libattest", "rsa 0.10.0-rc.18", diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index 05405a70..33ba1369 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -24,6 +24,7 @@ zerocopy = { version = "0.8.48", features = ["derive"] } tss-esapi = { version = "7.7.0", optional = true } anyhow = { version = "*", optional = true } base64 = "0.22.1" +hex = { version = "0.4.3", features = ["serde"] } [target.'cfg(target_family = "wasm")'.dependencies] wasm-bindgen = "0.2" diff --git a/azure-attest/src/host.rs b/azure-attest/src/host.rs index cd301ff0..57c005be 100644 --- a/azure-attest/src/host.rs +++ b/azure-attest/src/host.rs @@ -11,10 +11,11 @@ pub mod vtpm; pub fn azure_attest( mut tpm: vtpm::AzureTpmCtx, nonce: &AzureNonce, -) -> libattest::Result { - let cert = tpm.ak_cert().unwrap(); +) -> anyhow::Result { + let cert = tpm.ak_cert()?; let key = tpm.ak().unwrap(); let report = tpm.hardware_report().unwrap(); + let pcr = tpm.read_pcr_bank()?; let (attest, signature) = tpm.quote(nonce.deref()).unwrap(); // convert tpm structure to wire compatible format @@ -23,13 +24,13 @@ pub fn azure_attest( let signature = match signature { Signature::RsaSsa(ref signature) => signature.signature().value(), - _ => libattest::bail!("unsupported signature algorithm"), + _ => anyhow::bail!("unsupported signature algorithm"), }; let signature = rsa::pkcs1v15::Signature::try_from(signature)?; let azure_trust = AzureTrust::new(signature, key, cert); - let azure_quote = AzureQuote::new(attest, report, azure_trust); + let azure_quote = AzureQuote::new(attest, pcr, report, azure_trust); Ok(azure_quote) } diff --git a/azure-attest/src/host/vtpm.rs b/azure-attest/src/host/vtpm.rs index 7bd1b46a..bd320efe 100644 --- a/azure-attest/src/host/vtpm.rs +++ b/azure-attest/src/host/vtpm.rs @@ -4,20 +4,20 @@ use libattest::error::Context; use rsa::{BoxedUint, pkcs1v15::VerifyingKey}; use sha2::Sha256; use tss_esapi::{ - abstraction::nv, + abstraction::{nv, pcr::PcrData}, handles::{KeyHandle, NvIndexTpmHandle}, interface_types::{ algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, }, structures::{ Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, RsaExponent, Signature, - SignatureScheme, + SignatureScheme, pcr_selection_list, }, }; use x509_cert::der::{Decode, SliceReader}; -use crate::report::AttestationReport; +use crate::{quote::pcr::PcrBankReading, report::AttestationReport}; pub struct AzureTpmCtx { context: tss_esapi::Context, @@ -134,7 +134,7 @@ impl AzureTpmCtx { .map_err(Into::into) } - pub(super) fn quote(mut self, nonce: impl AsRef<[u8]>) -> anyhow::Result<(Attest, Signature)> { + pub(super) fn quote(&mut self, nonce: impl AsRef<[u8]>) -> anyhow::Result<(Attest, Signature)> { let key_handle = self.ak_handle()?; let pcr_list = PcrSelectionListBuilder::new() @@ -155,4 +155,25 @@ impl AzureTpmCtx { Ok(res) } + + pub(super) fn read_pcr_bank(&mut self) -> anyhow::Result> { + let pcr_list = PcrSelectionListBuilder::new() + .with_selection(HashingAlgorithm::Sha256, &Self::VTPM_DEFAULT_PCR_SLOTS) + .with_size_of_select(PcrSelectSize::default()) + .build() + .unwrap(); + + // pcr_list. + + let list = self + .context + .execute_without_session(|ctx| tss_esapi::abstraction::pcr::read_all(ctx, pcr_list))?; + + let bank: PcrBankReading = list + .pcr_bank(HashingAlgorithm::Sha256) + .expect("we just requested this exact bank") + .into(); + + Ok(bank) + } } diff --git a/azure-attest/src/quote.rs b/azure-attest/src/quote.rs index 638637d6..78cf6b65 100644 --- a/azure-attest/src/quote.rs +++ b/azure-attest/src/quote.rs @@ -1,12 +1,19 @@ +pub mod claims; +pub mod pcr; pub mod verify; +use std::collections::BTreeMap; + use serde::{Deserialize, Serialize}; -use sha2::Sha256; +use sha2::{Sha256, digest}; use snp_attest::SevQuote; use tdx_attest::TdxQuote; -use tpm2_protocol::data::TpmsAttest; +use tpm2_protocol::data::{TpmAlgId, TpmsAttest}; -use crate::report::{AttestationReport, HardwareReport}; +use crate::{ + quote::pcr::PcrBankReading, + report::{AttestationReport, HardwareReport}, +}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct AzureTrust { @@ -35,16 +42,24 @@ impl AzureTrust { pub struct AzureQuote { #[serde(with = "crate::serde::serde_tpm")] quote: TpmsAttest, + /// we store a single bank in the quote as we are requesting a single bank + /// from the vtpm (sha256) + pcr_bank: PcrBankReading, hardware_report: AttestationReport, - trust: AzureTrust, } impl AzureQuote { - pub fn new(quote: TpmsAttest, hardware_report: AttestationReport, trust: AzureTrust) -> Self { + pub fn new( + quote: TpmsAttest, + pcr_bank: PcrBankReading, + hardware_report: AttestationReport, + trust: AzureTrust, + ) -> Self { Self { quote, hardware_report, + pcr_bank, trust, } } diff --git a/azure-attest/src/quote/claims.rs b/azure-attest/src/quote/claims.rs new file mode 100644 index 00000000..df87287d --- /dev/null +++ b/azure-attest/src/quote/claims.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; +use snp_attest::claims::SevClaims; +use tdx_attest::dcap::types::TdxQuoteBody; + +#[derive(Serialize)] +pub enum HardwareClaims { + Sev(SevClaims), + Tdx(TdxQuoteBody), +} + +#[derive(Serialize)] +pub struct TpmClockInfo { + clock: u64, + reset_count: u32, + restart_count: u32, + safe: bool, +} + +// #[derive(Serialize)] +// pub struct TpmAttestEvidence { +// qualified_signer: String, +// #[serde(with = "hex::serde")] +// extra_data: Vec, +// clock_info: TpmClockInfo, +// firmware_version: u64, + +// quote: +// } + +#[derive(Serialize)] +pub struct AzureClaims { + hardware_claims: HardwareClaims, +} diff --git a/azure-attest/src/quote/pcr.rs b/azure-attest/src/quote/pcr.rs new file mode 100644 index 00000000..21b11ef3 --- /dev/null +++ b/azure-attest/src/quote/pcr.rs @@ -0,0 +1,69 @@ +use std::collections::BTreeMap; +use std::marker::PhantomData; +use std::ops::Deref; + +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "host")] +use sha2::Digest; +use sha2::digest; +#[cfg(feature = "host")] +use tss_esapi::abstraction::pcr::PcrBank; +#[cfg(feature = "host")] +use tss_esapi::structures::PcrSlot; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Pcr { + #[serde(with = "hex::serde")] + digest: Vec, +} + +impl Pcr { + pub fn new(digest: impl Into>) -> Self { + Self { + digest: digest.into(), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PcrBankReading { + pcr_list: BTreeMap, + _digest: PhantomData, +} + +impl PcrBankReading { + pub fn pcr_digest(&self) -> digest::Output { + // Hash( PCR[0] | PCR[1] | ... ) <- concatenated + let data: Vec = self + .pcr_list + .values() + .flat_map(|pcr| pcr.digest.deref()) + .copied() + .collect(); + + D::digest(data) + } +} + +#[cfg(feature = "host")] +impl From<&PcrBank> for PcrBankReading { + fn from(value: &PcrBank) -> Self { + let pcr_list = value + .into_iter() + .map(|(slot, digest)| (pcr_slot_to_number(*slot), digest.to_vec())) + .map(|(slot, digest)| (slot, Pcr::new(digest))) + .collect(); + + PcrBankReading { + pcr_list, + _digest: PhantomData::default(), + } + } +} + +#[cfg(feature = "host")] +fn pcr_slot_to_number(slot: PcrSlot) -> u32 { + let slot: u32 = slot.into(); + slot.ilog2() + 1 +} diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index 2dc37463..74731f49 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -18,6 +18,7 @@ use rsa::{ signature::Verifier, }; use sha2::Sha256; +use tpm2_protocol::data::TpmuAttest; use x509_cert::der::{ Encode, oid::db::{rfc5912::SHA_256_WITH_RSA_ENCRYPTION, rfc9688::RSA_ENCRYPTION}, @@ -192,15 +193,28 @@ fn verify_quote_nonce(azure_quote: &AzureQuote, nonce: &AzureNonce) -> libattest Ok(()) } +fn verify_pcr_digest(azure_quote: &AzureQuote) -> libattest::Result<()> { + let digest = azure_quote.pcr_bank.pcr_digest(); + let TpmuAttest::Quote(ref info) = azure_quote.quote.attested else { + libattest::bail!("wrong type of attested data in quote"); + }; + + if info.pcr_digest.deref() != digest.deref() { + libattest::bail!(exposed: "PCR digest mismatch"); + } + + Ok(()) +} + pub fn verify( azure_quote: AzureQuote, report_verifier: ReportVerifier, nonce: &AzureNonce, ) -> libattest::Result<()> { - // 2: verify quote certificate + // 1: verify quote certificate chain against pinned trust let trust_chain = verify_quote_trust_chain(&azure_quote).context("while verifying quote certificates")?; - // 1: verify that AK signs Quote through signature + // 2: verify that AK signs Quote through leaf certificate verify_quote_signature(&azure_quote, trust_chain).context("while verifying quote signature")?; // 3: verify that the hardware report signs report data verify_report_digest(&azure_quote, &report_verifier) @@ -210,6 +224,8 @@ pub fn verify( verify_runtime_data(&azure_quote).context("while verifying runtime data")?; // 5: Verify that user supplied nonce and received quote nonce match verify_quote_nonce(&azure_quote, nonce).context("while verifying quote nonce")?; + // 6: Verify pcr digest + verify_pcr_digest(&azure_quote).context("failed verifying pcr bank digest")?; Ok(()) } From a4c8b6490642aacb67ce525f7dfa78e60821fe34 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:36:04 +0000 Subject: [PATCH 24/39] convert byte types to hex representation for space eficiency --- azure-attest/src/quote/verify.rs | 12 +- azure-attest/src/report.rs | 1 + azure-attest/src/serde/serde_tpm.rs | 6 +- azure-attest/tests/attestation.json | 387 +++++++++++++++++----------- 4 files changed, 238 insertions(+), 168 deletions(-) diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index 74731f49..423d6c58 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -11,18 +11,10 @@ use libattest::{ }, error::Context, }; -use rsa::{ - BoxedUint, - pkcs1::DecodeRsaPublicKey, - pkcs1v15::{Signature, VerifyingKey}, - signature::Verifier, -}; +use rsa::{BoxedUint, pkcs1::DecodeRsaPublicKey, pkcs1v15::VerifyingKey, signature::Verifier}; use sha2::Sha256; use tpm2_protocol::data::TpmuAttest; -use x509_cert::der::{ - Encode, - oid::db::{rfc5912::SHA_256_WITH_RSA_ENCRYPTION, rfc9688::RSA_ENCRYPTION}, -}; +use x509_cert::der::oid::db::rfc9688::RSA_ENCRYPTION; use crate::{ ca::AZURE_CA, diff --git a/azure-attest/src/report.rs b/azure-attest/src/report.rs index 16d5f9ae..624f3840 100644 --- a/azure-attest/src/report.rs +++ b/azure-attest/src/report.rs @@ -149,6 +149,7 @@ pub struct RuntimeData { /// Fixed runtime data header. pub header: RuntimeDataHeader, /// Raw runtime claims JSON bytes. + #[serde(with = "hex::serde")] pub claims_json: Vec, } diff --git a/azure-attest/src/serde/serde_tpm.rs b/azure-attest/src/serde/serde_tpm.rs index 8201f5d6..82907964 100644 --- a/azure-attest/src/serde/serde_tpm.rs +++ b/azure-attest/src/serde/serde_tpm.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserializer, Serializer}; use tpm2_protocol::{TpmMarshal, TpmSized, TpmUnmarshal, TpmWriter}; pub fn serialize(obj: &T, serializer: S) -> Result @@ -12,7 +12,7 @@ where obj.marshal(&mut writer) .map_err(::custom)?; - buf.serialize(serializer) + hex::serde::serialize(buf, serializer) } pub fn deserialize<'de, T, D>(deserializer: D) -> Result @@ -20,7 +20,7 @@ where D: Deserializer<'de>, T: TpmUnmarshal + TpmSized, { - let buf = Vec::::deserialize(deserializer)?; + let buf: Vec = hex::serde::deserialize(deserializer)?; if buf.len() != T::SIZE { return Err(serde::de::Error::custom( diff --git a/azure-attest/tests/attestation.json b/azure-attest/tests/attestation.json index 405c9a32..e8dc1d64 100644 --- a/azure-attest/tests/attestation.json +++ b/azure-attest/tests/attestation.json @@ -80,14 +80,14 @@ 0, 0, 0, - 32, - 65, - 69, - 23, + 47, + 155, + 93, + 50, 0, 0, 0, - 5, + 6, 0, 0, 0, @@ -113,38 +113,38 @@ 255, 0, 32, - 192, - 56, - 127, - 196, - 203, - 67, - 219, - 26, - 10, - 250, - 113, - 156, - 77, - 175, - 141, - 77, - 232, - 134, - 112, - 76, - 208, - 54, + 103, + 149, + 7, + 215, + 207, + 145, 195, - 208, - 205, - 39, - 240, - 0, - 92, - 45, - 41, + 165, + 36, + 85, + 236, + 125, + 102, + 164, + 197, + 212, + 69, 6, + 44, + 109, + 9, + 45, + 38, + 74, + 200, + 124, + 194, + 21, + 80, + 143, + 67, + 224, 0, 0, 0, @@ -4454,6 +4454,83 @@ 0, 0 ], + "pcr_bank": { + "pcr_list": { + "1": { + "digest": "84275b2f4312cd4fc6cbe6b152ad3c3683e513d9f1e23c34fca160c8cca7a6a7" + }, + "2": { + "digest": "5fc7f27b7963bbcdbe47d5c87f856a6cb875bd05009b8bba228b191d067eef73" + }, + "3": { + "digest": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969" + }, + "4": { + "digest": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969" + }, + "5": { + "digest": "933b0e2ab92e51216205b8033f4bf1d38984dd63ae556fe3f9f2c89d97e46fb0" + }, + "6": { + "digest": "b9f3e9f84a0130697af06f94ee652dec810b921eda0d4ef364a7b551532cd23a" + }, + "7": { + "digest": "b96cb94856feefa1cd24669ec724d7f7b8001ab792b09de85f1423668cccb937" + }, + "8": { + "digest": "3b20e022416fdf61d72e4da32b4354781be3de0608116976d28ffdad8c341d2a" + }, + "9": { + "digest": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "10": { + "digest": "b14b6ddea210eee8f9bc505c001d1410dd1d6e54cf06b6821e109be79e80ae46" + }, + "11": { + "digest": "18dbff9d7c37f2b9e9c080256711f81059a8e2887ac01f5c2debc3b0979b2d4d" + }, + "12": { + "digest": "560c99b9bd329ef3c37b82ffaced660fc4f1f23bc8bb95c84e96b7aefa458d3d" + }, + "13": { + "digest": "f1a142c53586e7e2223ec74e5f4d1a4942956b1fd9ac78fafcdf85117aa345da" + }, + "14": { + "digest": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "15": { + "digest": "306f9d8b94f17d93dc6e7cf8f5c79d652eb4c6c4d13de2dddc24af416e13ecaf" + }, + "16": { + "digest": "8157e23afdd6fbcb95118fb299823143d37d41cbcf4d4cf033e894cb060e99eb" + }, + "17": { + "digest": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "18": { + "digest": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "19": { + "digest": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "20": { + "digest": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "21": { + "digest": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "22": { + "digest": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "23": { + "digest": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "24": { + "digest": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "_digest": null + }, "hardware_report": { "header": { "signature": 1095517000, @@ -4798,38 +4875,38 @@ 0, 0, 0, - 52, - 124, + 198, + 41, + 56, + 211, + 205, + 121, + 132, + 192, + 23, + 239, + 171, + 16, + 176, + 162, + 211, + 181, + 49, + 230, + 147, + 231, 217, - 237, - 167, - 213, - 98, - 123, - 128, - 33, - 79, - 174, - 14, - 154, 111, - 89, - 114, - 115, - 223, - 188, - 45, - 93, - 202, - 53, - 37, - 189, - 180, - 238, - 165, - 146, - 117, - 83, + 15, + 118, + 9, + 161, + 58, + 101, + 4, + 80, + 187, + 231, 255, 255, 255, @@ -5150,54 +5227,54 @@ 0, 0, 0, - 12, - 101, + 144, + 200, + 8, + 33, + 249, + 71, + 192, + 234, + 9, + 119, + 145, + 99, + 34, + 192, + 46, + 243, 219, - 216, - 137, - 73, - 231, - 149, - 177, - 42, - 38, - 251, - 248, - 19, - 214, - 124, - 56, + 8, + 140, + 227, + 168, + 220, + 101, + 142, + 93, + 134, + 110, 23, - 245, - 186, + 103, + 253, + 134, + 21, + 130, + 27, 62, - 179, - 247, - 9, - 132, - 132, 159, - 129, - 203, - 13, 98, - 54, - 89, - 249, - 86, - 181, - 242, - 21, - 209, - 48, - 210, - 159, - 194, - 117, + 68, + 233, + 128, + 142, + 229, + 20, + 223, + 110, 67, - 91, - 47, - 131, + 230, + 103, 0, 0, 0, @@ -5222,54 +5299,54 @@ 0, 0, 0, - 33, - 177, - 132, - 103, - 140, - 25, - 230, - 225, - 27, - 57, - 68, - 52, + 46, 152, - 1, + 150, + 194, + 57, + 76, + 172, + 3, + 53, + 83, + 131, + 181, + 135, + 113, + 222, + 115, + 248, + 92, + 201, + 216, + 102, + 14, + 13, + 136, + 191, + 245, + 102, + 31, + 168, + 28, + 111, 215, - 29, - 207, - 209, - 61, - 210, - 43, - 42, - 24, - 36, - 121, - 226, - 90, - 87, - 127, - 161, - 74, - 244, - 140, - 51, - 236, - 145, - 217, - 133, - 158, - 154, - 51, - 45, - 41, - 21, - 218, - 228, - 105, - 7, + 179, + 131, + 170, + 134, + 178, + 178, + 235, + 198, + 20, + 167, + 68, + 57, + 200, + 143, + 159, + 139, 0, 0, 0, @@ -6787,8 +6864,8 @@ } }, "trust": { - "quote_signature": "725193221d096d2ad86112329c8e9c75722d425ae9a26b6c140ed1a1acd359e9446c0fe4c3ae4c01047dfba3a0291fc501b446ac3cfb8fc898589c7d6284145e89175e5ab95ef8f8fdf2dde440ced80f33d3a3b8d2a93143aefeec3e8cef810d81c9d20379dc3296c5ed8b1aa7abc2254534a6fd62fa0bc922ae842fa22f1c190d7688b56f064a3dba36dbc5315a64ae72a67de08ca1982af46e21c88117bf373fc642b7e365d8f1c2f9e970fcd8502993757f82d5f21d23b7dd042a185bb741d0927e291e07a2ded674b412411a061a2fe28cae4718d4c6cef3dda9134950161fdbfdbeec7c573b9a3fe5b902fe0dc0b76f00ab15f374560fa51474ab1dbf2d", + "quote_signature": "cc210dbc94a37c68427ce703431d090c67ece097f3ba91e922a8093f97b0654b15d5df8092e0fd6733f3a30b4425c64d954794143b55daa522f8a530ab912a42d9e4e3264b433b370416c94f20241fc38d9df4275e0984a2c6998cfdbbf2b1944285ddc3f048ec4d682f8b338d70e6828bab4fd6be869445da4a85aaca0e682f0608627acdf7d27cb429a7f21cbb091a29fa9dc3f30031346fa9f0a25d60e20aa69e0aaf3ab579a679cdaa472600389b83ba742bef41196df09084874441ca0950691d35b3041008d0ecd88988ef1bfab4093c247d68265e311566fff05cd2766eee1f1395643c246a133ec676eec36c21cb5f82e2386e35125f3b90a52169f5", "ak_key": "30820122300d06092a864886f70d01010105000382010f003082010a0282010100e0fced7c0000fc0ab6e44476a80d61df331cdbaad92b2449c6927d6aebc5b7d2b28d96d23295e5f26c0061f8a69b84c28244931c8a5cd118e786c08b6f44cb0d9d5c5dadcf5402fa4f88f09e53be74ef87b67b05371910e356d5b29ff70abccfe036f2d30007701104c16b123307675412ee49cd996a80c195d45f9dc8f6e6e29524073d3598cd849d921b7d4410f74c042b561d9d66f6258e0defd0988e96970acb19ba47c7efa57d484479cb2865f1ea8df8cac7ed6b379456a5faef86942c310a4837da8f9e41086d3a1d44e997396036da9ef39877d8aca4e3bd4f68084b113b2dfebcbd5f8bec0280fa4578a4bcf629cd62294e860e8e8142983b6c56dd0203010001", - "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7jCCAtagAwIBAgIRAKgcqw7JzCPoTCtF847wqgEwDQYJKoZIhvcNAQELBQAw\nJTEjMCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDMwHhcNMjYwNjI1\nMDAwMDAwWhcNMjcwNDIxMDAwMDAwWjA4MTYwNAYDVQQDEy02MGY5YmFmNjEyZmQu\nQ29uZmlkZW50aWFsVk0uQXp1cmUud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDg/O18AAD8CrbkRHaoDWHfMxzbqtkrJEnGkn1q68W3\n0rKNltIyleXybABh+KabhMKCRJMcilzRGOeGwItvRMsNnVxdrc9UAvpPiPCeU750\n74e2ewU3GRDjVtWyn/cKvM/gNvLTAAdwEQTBaxIzB2dUEu5JzZlqgMGV1F+dyPbm\n4pUkBz01mM2EnZIbfUQQ90wEK1YdnWb2JY4N79CYjpaXCssZukfH76V9SER5yyhl\n8eqN+MrH7Ws3lFal+u+GlCwxCkg32o+eQQhtOh1E6Zc5YDbanvOYd9ispOO9T2gI\nSxE7Lf68vV+L7AKA+kV4pLz2Kc1iKU6GDo6BQpg7bFbdAgMBAAGjggEEMIIBADAO\nBgNVHQ8BAf8EBAMCB4AwGAYDVR0gBBEwDzANBgsrBgEEAYI3bIFIATAcBgNVHSUE\nFTATBgorBgEEAYI3CgMMBgVngQUIAzAdBgNVHQ4EFgQUr57RFs5NWi58XdGtOgBu\n3FKq2zEwGgYKKwYBBAGCNw0CAwQMFgo2LjIuOTIwMC4yMFoGCSsGAQQBgjcVFARN\nMEsCAQUMD0FNUzA5MTA3MjcxMDAwNQwaV09SS0dST1VQXEFNUzA5MTA3MjcxMDAw\nNSQMGVZ0cG1NYW5hZ2VtZW50U2VydmljZS5leGUwHwYDVR0jBBgwFoAUZwmG+PmB\nXpOt+clk5xQK2pnLCi0wDQYJKoZIhvcNAQELBQADggEBAE4DCMj4gZ6enCQ6MKXi\nrjjfXPJNGeNTEHka5A1hklkA37lCXEQ1GVTQOuYJ+PYNmJF1q6x7YkLTJe/rrGPD\nmTlhkVmXXwEnU3W6KCWbgo3oiDk8sZ/O5VQaxy3i1DRVEiNf1xrjkntgRcOei51T\nV1YaJ3LwzoAaMGoNMmWQfX5GO1ZNao83I2HGDPRO4OdoahZhReq5LrdlwkMLB/95\nk8JEpn3YGPEkZgNPVrVWNqBo0HfQWb+SV0sTvu3TjCbHB16lW9fs8GMN0c39UOfW\n8uF+oU1r0epwScUpfOKVkyQAqkQnem4aUUGqYYpOux6JYKHp7i100uHKR6bPCBy5\nEfA=\n-----END CERTIFICATE-----\n" + "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7jCCAtagAwIBAgIRAIoBT94NClWkSBy3wIuaEXEwDQYJKoZIhvcNAQELBQAw\nJTEjMCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDMwHhcNMjYwNjI3\nMDAwMDAwWhcNMjcwNDIxMDAwMDAwWjA4MTYwNAYDVQQDEy0wMmU1ODg2OWEyYjYu\nQ29uZmlkZW50aWFsVk0uQXp1cmUud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDg/O18AAD8CrbkRHaoDWHfMxzbqtkrJEnGkn1q68W3\n0rKNltIyleXybABh+KabhMKCRJMcilzRGOeGwItvRMsNnVxdrc9UAvpPiPCeU750\n74e2ewU3GRDjVtWyn/cKvM/gNvLTAAdwEQTBaxIzB2dUEu5JzZlqgMGV1F+dyPbm\n4pUkBz01mM2EnZIbfUQQ90wEK1YdnWb2JY4N79CYjpaXCssZukfH76V9SER5yyhl\n8eqN+MrH7Ws3lFal+u+GlCwxCkg32o+eQQhtOh1E6Zc5YDbanvOYd9ispOO9T2gI\nSxE7Lf68vV+L7AKA+kV4pLz2Kc1iKU6GDo6BQpg7bFbdAgMBAAGjggEEMIIBADAO\nBgNVHQ8BAf8EBAMCB4AwGAYDVR0gBBEwDzANBgsrBgEEAYI3bIFIATAcBgNVHSUE\nFTATBgorBgEEAYI3CgMMBgVngQUIAzAdBgNVHQ4EFgQUr57RFs5NWi58XdGtOgBu\n3FKq2zEwGgYKKwYBBAGCNw0CAwQMFgo2LjIuOTIwMC4yMFoGCSsGAQQBgjcVFARN\nMEsCAQUMD0FNUzA5MTA3MjcxMDAwNQwaV09SS0dST1VQXEFNUzA5MTA3MjcxMDAw\nNSQMGVZ0cG1NYW5hZ2VtZW50U2VydmljZS5leGUwHwYDVR0jBBgwFoAUZwmG+PmB\nXpOt+clk5xQK2pnLCi0wDQYJKoZIhvcNAQELBQADggEBAJoddxcTOfWimIJvejrK\n8k9SpJBu5FQ3S9gAoPNsO2jZ7PxK4FeSp39WHl2ks9pJIJiopuSDV+G1rPccY5zZ\nL9c4G3kffhgFNwvIj0+JCcudPF8vn/Vs4JRYc/VBvzG+bjQo+S+GWi49akWiR3Eg\nXbgZDA4sDyLQEtraP0Z2JOaJTVVvM/ldUjIl3rFpo8Dijehd8auJe8EObmXkS7AU\n63jI7jT6sU91y3s6X360esdmKN+/Z7i01HhnaBjUgROcs6y5kCXn7g1NO7PLqkpF\nFHsvHZFG9Jf+zGLORS60JLNLDzz5yl6hFq1zzETH2Pw68bjfbxiStS4jyiz6E17k\nG9I=\n-----END CERTIFICATE-----\n" } } From ecf3c567edc5ad1f3bb0bb685c1ff68d90f834f5 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:39:16 +0000 Subject: [PATCH 25/39] encode attestation reports in hex too --- azure-attest/src/report.rs | 4 +- azure-attest/tests/attestation.json | 5751 +-------------------------- 2 files changed, 95 insertions(+), 5660 deletions(-) diff --git a/azure-attest/src/report.rs b/azure-attest/src/report.rs index 624f3840..1221841b 100644 --- a/azure-attest/src/report.rs +++ b/azure-attest/src/report.rs @@ -138,9 +138,9 @@ impl AttestationReport { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum HardwareReport { /// Intel TDX hardware report payload. - Tdx(Vec), + Tdx(#[serde(with = "hex::serde")] Vec), /// AMD SEV-SNP hardware report payload. - Sev(Vec), + Sev(#[serde(with = "hex::serde")] Vec), } /// Owned runtime data. diff --git a/azure-attest/tests/attestation.json b/azure-attest/tests/attestation.json index e8dc1d64..ee1aebf0 100644 --- a/azure-attest/tests/attestation.json +++ b/azure-attest/tests/attestation.json @@ -1,4459 +1,5 @@ { - "quote": [ - 255, - 84, - 67, - 71, - 128, - 24, - 0, - 34, - 0, - 11, - 90, - 200, - 132, - 215, - 172, - 123, - 199, - 249, - 34, - 156, - 61, - 105, - 73, - 243, - 46, - 248, - 147, - 122, - 70, - 5, - 81, - 194, - 38, - 251, - 244, - 37, - 115, - 89, - 54, - 86, - 77, - 144, - 0, - 32, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 47, - 155, - 93, - 50, - 0, - 0, - 0, - 6, - 0, - 0, - 0, - 0, - 1, - 32, - 32, - 3, - 18, - 0, - 18, - 0, - 3, - 0, - 0, - 0, - 1, - 0, - 11, - 3, - 255, - 255, - 255, - 0, - 32, - 103, - 149, - 7, - 215, - 207, - 145, - 195, - 165, - 36, - 85, - 236, - 125, - 102, - 164, - 197, - 212, - 69, - 6, - 44, - 109, - 9, - 45, - 38, - 74, - 200, - 124, - 194, - 21, - 80, - 143, - 67, - 224, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], + "quote": "ff54434780180022000b5ac884d7ac7bc7f9229c3d6949f32ef8937a460551c226fbf425735936564d9000200000000000000000000000000000000000000000000000000000000000000000000000002f9f37b0000000060000000001202003120012000300000001000b03ffffff0020679507d7cf91c3a52455ec7d66a4c5d445062c6d092d264ac87cc215508f43e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "pcr_bank": { "pcr_list": { "1": { @@ -5227,54 +773,54 @@ 0, 0, 0, - 144, - 200, - 8, - 33, - 249, - 71, - 192, - 234, - 9, - 119, - 145, - 99, - 34, - 192, - 46, - 243, - 219, - 8, - 140, - 227, - 168, - 220, - 101, - 142, + 111, + 53, + 222, + 17, + 24, + 156, + 218, 93, - 134, - 110, - 23, - 103, - 253, - 134, - 21, - 130, - 27, - 62, - 159, - 98, - 68, - 233, - 128, - 142, + 93, + 138, + 211, + 1, + 50, + 145, + 59, + 185, + 47, + 242, + 195, 229, - 20, - 223, - 110, - 67, + 252, 230, - 103, + 120, + 63, + 64, + 6, + 116, + 245, + 114, + 215, + 18, + 240, + 26, + 123, + 219, + 231, + 59, + 201, + 82, + 251, + 146, + 67, + 200, + 12, + 27, + 179, + 59, + 253, 0, 0, 0, @@ -5299,54 +845,54 @@ 0, 0, 0, - 46, - 152, - 150, + 252, 194, - 57, - 76, - 172, - 3, - 53, - 83, - 131, - 181, - 135, - 113, - 222, - 115, + 33, + 19, + 96, + 27, + 70, + 86, + 137, + 54, + 187, + 149, + 108, + 221, + 122, + 1, + 243, + 90, + 5, + 188, + 65, + 221, + 93, + 128, + 180, + 23, + 235, + 196, + 96, + 153, + 129, + 208, + 144, + 33, + 160, + 176, + 64, + 9, + 4, + 206, + 182, + 99, + 165, + 106, + 69, + 243, 248, - 92, - 201, - 216, - 102, - 14, - 13, 136, - 191, - 245, - 102, - 31, - 168, - 28, - 111, - 215, - 179, - 131, - 170, - 134, - 178, - 178, - 235, - 198, - 20, - 167, - 68, - 57, - 200, - 143, - 159, - 139, 0, 0, 0, @@ -5749,1122 +1295,11 @@ "hash_type": 1, "claim_size": 1110 }, - "claims_json": [ - 123, - 34, - 107, - 101, - 121, - 115, - 34, - 58, - 91, - 123, - 34, - 107, - 105, - 100, - 34, - 58, - 34, - 72, - 67, - 76, - 65, - 107, - 80, - 117, - 98, - 34, - 44, - 34, - 107, - 101, - 121, - 95, - 111, - 112, - 115, - 34, - 58, - 91, - 34, - 115, - 105, - 103, - 110, - 34, - 93, - 44, - 34, - 107, - 116, - 121, - 34, - 58, - 34, - 82, - 83, - 65, - 34, - 44, - 34, - 101, - 34, - 58, - 34, - 65, - 81, - 65, - 66, - 34, - 44, - 34, - 110, - 34, - 58, - 34, - 52, - 80, - 122, - 116, - 102, - 65, - 65, - 65, - 95, - 65, - 113, - 50, - 53, - 69, - 82, - 50, - 113, - 65, - 49, - 104, - 51, - 122, - 77, - 99, - 50, - 54, - 114, - 90, - 75, - 121, - 82, - 74, - 120, - 112, - 74, - 57, - 97, - 117, - 118, - 70, - 116, - 57, - 75, - 121, - 106, - 90, - 98, - 83, - 77, - 112, - 88, - 108, - 56, - 109, - 119, - 65, - 89, - 102, - 105, - 109, - 109, - 52, - 84, - 67, - 103, - 107, - 83, - 84, - 72, - 73, - 112, - 99, - 48, - 82, - 106, - 110, - 104, - 115, - 67, - 76, - 98, - 48, - 84, - 76, - 68, - 90, - 49, - 99, - 88, - 97, - 51, - 80, - 86, - 65, - 76, - 54, - 84, - 52, - 106, - 119, - 110, - 108, - 79, - 45, - 100, - 79, - 45, - 72, - 116, - 110, - 115, - 70, - 78, - 120, - 107, - 81, - 52, - 49, - 98, - 86, - 115, - 112, - 95, - 51, - 67, - 114, - 122, - 80, - 52, - 68, - 98, - 121, - 48, - 119, - 65, - 72, - 99, - 66, - 69, - 69, - 119, - 87, - 115, - 83, - 77, - 119, - 100, - 110, - 86, - 66, - 76, - 117, - 83, - 99, - 50, - 90, - 97, - 111, - 68, - 66, - 108, - 100, - 82, - 102, - 110, - 99, - 106, - 50, - 53, - 117, - 75, - 86, - 74, - 65, - 99, - 57, - 78, - 90, - 106, - 78, - 104, - 74, - 50, - 83, - 71, - 51, - 49, - 69, - 69, - 80, - 100, - 77, - 66, - 67, - 116, - 87, - 72, - 90, - 49, - 109, - 57, - 105, - 87, - 79, - 68, - 101, - 95, - 81, - 109, - 73, - 54, - 87, - 108, - 119, - 114, - 76, - 71, - 98, - 112, - 72, - 120, - 45, - 45, - 108, - 102, - 85, - 104, - 69, - 101, - 99, - 115, - 111, - 90, - 102, - 72, - 113, - 106, - 102, - 106, - 75, - 120, - 45, - 49, - 114, - 78, - 53, - 82, - 87, - 112, - 102, - 114, - 118, - 104, - 112, - 81, - 115, - 77, - 81, - 112, - 73, - 78, - 57, - 113, - 80, - 110, - 107, - 69, - 73, - 98, - 84, - 111, - 100, - 82, - 79, - 109, - 88, - 79, - 87, - 65, - 50, - 50, - 112, - 55, - 122, - 109, - 72, - 102, - 89, - 114, - 75, - 84, - 106, - 118, - 85, - 57, - 111, - 67, - 69, - 115, - 82, - 79, - 121, - 51, - 45, - 118, - 76, - 49, - 102, - 105, - 45, - 119, - 67, - 103, - 80, - 112, - 70, - 101, - 75, - 83, - 56, - 57, - 105, - 110, - 78, - 89, - 105, - 108, - 79, - 104, - 103, - 54, - 79, - 103, - 85, - 75, - 89, - 79, - 50, - 120, - 87, - 51, - 81, - 34, - 125, - 44, - 123, - 34, - 107, - 105, - 100, - 34, - 58, - 34, - 72, - 67, - 76, - 69, - 107, - 80, - 117, - 98, - 34, - 44, - 34, - 107, - 101, - 121, - 95, - 111, - 112, - 115, - 34, - 58, - 91, - 34, - 101, - 110, - 99, - 114, - 121, - 112, - 116, - 34, - 93, - 44, - 34, - 107, - 116, - 121, - 34, - 58, - 34, - 82, - 83, - 65, - 34, - 44, - 34, - 101, - 34, - 58, - 34, - 65, - 81, - 65, - 66, - 34, - 44, - 34, - 110, - 34, - 58, - 34, - 52, - 118, - 79, - 112, - 114, - 103, - 65, - 66, - 83, - 88, - 90, - 57, - 103, - 119, - 90, - 81, - 117, - 119, - 75, - 99, - 102, - 75, - 104, - 80, - 74, - 89, - 103, - 71, - 106, - 71, - 100, - 84, - 54, - 88, - 110, - 74, - 73, - 76, - 74, - 86, - 76, - 122, - 103, - 120, - 87, - 50, - 85, - 68, - 84, - 79, - 87, - 67, - 121, - 114, - 116, - 75, - 54, - 48, - 90, - 99, - 45, - 84, - 111, - 79, - 101, - 82, - 110, - 74, - 95, - 113, - 69, - 116, - 117, - 113, - 54, - 100, - 110, - 105, - 73, - 100, - 66, - 75, - 115, - 52, - 80, - 57, - 87, - 53, - 68, - 78, - 106, - 100, - 52, - 54, - 81, - 55, - 118, - 101, - 56, - 88, - 116, - 120, - 70, - 101, - 54, - 109, - 99, - 45, - 106, - 51, - 97, - 86, - 48, - 119, - 106, - 90, - 90, - 112, - 53, - 85, - 68, - 68, - 90, - 85, - 110, - 106, - 121, - 45, - 107, - 105, - 71, - 87, - 77, - 73, - 84, - 104, - 67, - 120, - 72, - 87, - 83, - 49, - 109, - 83, - 69, - 95, - 78, - 102, - 52, - 118, - 66, - 107, - 114, - 121, - 114, - 100, - 100, - 72, - 101, - 56, - 78, - 88, - 81, - 117, - 115, - 85, - 50, - 77, - 104, - 118, - 72, - 57, - 85, - 69, - 71, - 119, - 90, - 114, - 76, - 88, - 67, - 54, - 53, - 111, - 77, - 105, - 48, - 114, - 105, - 88, - 49, - 84, - 83, - 53, - 113, - 111, - 75, - 107, - 89, - 100, - 90, - 108, - 89, - 78, - 69, - 51, - 105, - 85, - 113, - 113, - 106, - 112, - 81, - 106, - 109, - 53, - 68, - 98, - 120, - 77, - 87, - 72, - 86, - 120, - 73, - 100, - 75, - 110, - 78, - 80, - 116, - 71, - 48, - 53, - 114, - 79, - 49, - 101, - 110, - 85, - 87, - 79, - 102, - 113, - 97, - 119, - 118, - 86, - 45, - 105, - 99, - 113, - 65, - 102, - 116, - 77, - 77, - 51, - 110, - 73, - 54, - 110, - 66, - 54, - 97, - 75, - 77, - 103, - 56, - 106, - 75, - 100, - 113, - 119, - 120, - 67, - 122, - 112, - 77, - 53, - 87, - 121, - 45, - 112, - 71, - 106, - 113, - 106, - 48, - 45, - 101, - 70, - 112, - 85, - 53, - 75, - 103, - 65, - 78, - 74, - 99, - 54, - 50, - 115, - 121, - 70, - 101, - 79, - 103, - 110, - 116, - 95, - 75, - 50, - 118, - 85, - 53, - 100, - 65, - 86, - 48, - 85, - 68, - 79, - 85, - 45, - 71, - 87, - 107, - 98, - 116, - 82, - 122, - 57, - 104, - 97, - 99, - 51, - 56, - 75, - 95, - 81, - 34, - 125, - 93, - 44, - 34, - 118, - 109, - 45, - 99, - 111, - 110, - 102, - 105, - 103, - 117, - 114, - 97, - 116, - 105, - 111, - 110, - 34, - 58, - 123, - 34, - 99, - 111, - 110, - 115, - 111, - 108, - 101, - 45, - 101, - 110, - 97, - 98, - 108, - 101, - 100, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 115, - 101, - 99, - 117, - 114, - 101, - 45, - 98, - 111, - 111, - 116, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 116, - 112, - 109, - 45, - 101, - 110, - 97, - 98, - 108, - 101, - 100, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 118, - 109, - 85, - 110, - 105, - 113, - 117, - 101, - 73, - 100, - 34, - 58, - 34, - 54, - 70, - 65, - 68, - 54, - 56, - 48, - 51, - 45, - 55, - 67, - 70, - 65, - 45, - 52, - 55, - 70, - 54, - 45, - 65, - 56, - 51, - 51, - 45, - 49, - 52, - 49, - 70, - 50, - 67, - 55, - 68, - 48, - 65, - 67, - 67, - 34, - 125, - 44, - 34, - 117, - 115, - 101, - 114, - 45, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 48, - 34, - 125 - ] + "claims_json": "7b226b657973223a5b7b226b6964223a2248434c416b507562222c226b65795f6f7073223a5b227369676e225d2c226b7479223a22525341222c2265223a2241514142222c226e223a2234507a74664141415f4171323545523271413168337a4d633236725a4b79524a78704a396175764674394b796a5a62534d70586c386d77415966696d6d345443676b53544849706330526a6e6873434c6230544c445a31635861335056414c3654346a776e6c4f2d644f2d48746e73464e786b513431625673705f3343727a50344462793077414863424545775773534d77646e56424c755363325a616f44426c6452666e636a3235754b564a4163394e5a6a4e684a3253473331454550644d42437457485a316d3969574f44655f516d4936576c77724c47627048782d2d6c665568456563736f5a6648716a666a4b782d31724e35525770667276687051734d5170494e3971506e6b454962546f64524f6d584f5741323270377a6d486659724b546a7655396f434573524f79332d764c3166692d774367507046654b533839696e4e59696c4f6867364f67554b594f3278573351227d2c7b226b6964223a2248434c456b507562222c226b65795f6f7073223a5b22656e6372797074225d2c226b7479223a22525341222c2265223a2241514142222c226e223a2234764f707267414253585a3967775a5175774b63664b68504a5967476a47645436586e4a494c4a564c7a677857325544544f57437972744b36305a632d546f4f65526e4a5f714574757136646e694964424b733450395735444e6a64343651377665385874784665366d632d6a33615630776a5a5a70355544445a556e6a792d6b6947574d4954684378485753316d53455f4e663476426b72797264644865384e5851757355324d68764839554547775a724c584336356f4d693072695831545335716f4b6b59645a6c594e4533695571716a70516a6d354462784d5748567849644b6e4e5074473035724f31656e55574f6671617776562d6963714166744d4d336e49366e4236614b4d67386a4b64717778437a704d3557792d70476a716a302d65467055354b67414e4a633632737946654f676e745f4b327655356441563055444f552d47576b6274527a3968616333384b5f51227d5d2c22766d2d636f6e66696775726174696f6e223a7b22636f6e736f6c652d656e61626c6564223a747275652c227365637572652d626f6f74223a747275652c2274706d2d656e61626c6564223a747275652c22766d556e697175654964223a2236464144363830332d374346412d343746362d413833332d313431463243374430414343227d2c22757365722d64617461223a223030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d" } }, "trust": { - "quote_signature": "cc210dbc94a37c68427ce703431d090c67ece097f3ba91e922a8093f97b0654b15d5df8092e0fd6733f3a30b4425c64d954794143b55daa522f8a530ab912a42d9e4e3264b433b370416c94f20241fc38d9df4275e0984a2c6998cfdbbf2b1944285ddc3f048ec4d682f8b338d70e6828bab4fd6be869445da4a85aaca0e682f0608627acdf7d27cb429a7f21cbb091a29fa9dc3f30031346fa9f0a25d60e20aa69e0aaf3ab579a679cdaa472600389b83ba742bef41196df09084874441ca0950691d35b3041008d0ecd88988ef1bfab4093c247d68265e311566fff05cd2766eee1f1395643c246a133ec676eec36c21cb5f82e2386e35125f3b90a52169f5", + "quote_signature": "2ad997ed0037479b95b99b4c93e92fbf79fe51c924134aee2a513be51625f35a86c6d5fe6fbf80069d7c63301f4bda1fda079b4205593ea15af66b6b885f5f9d2c622f4115f1e20f1cb0bcb55fb22a5b80159882c608129275d9636d4c4b36ba92858a4113c12f2ef7c084037afa4122e41c43d536af114ed59b5d983016e3762c3523df1d87b17e423454a38c11234f9b97ec827253adcdb73eb63b985fe06ef55efb4129ecdc96374e12ac4aa650bcd885ff264030e679d4e5594e8865e3f79bb40d7cdaaabf4b1d138c65f16eff92b9cd647eebc00541fc624057dcfa1f0cc8489ecebc2643aebceccb8f302c0899ae05e6fca752bf164ffab6485d15679e", "ak_key": "30820122300d06092a864886f70d01010105000382010f003082010a0282010100e0fced7c0000fc0ab6e44476a80d61df331cdbaad92b2449c6927d6aebc5b7d2b28d96d23295e5f26c0061f8a69b84c28244931c8a5cd118e786c08b6f44cb0d9d5c5dadcf5402fa4f88f09e53be74ef87b67b05371910e356d5b29ff70abccfe036f2d30007701104c16b123307675412ee49cd996a80c195d45f9dc8f6e6e29524073d3598cd849d921b7d4410f74c042b561d9d66f6258e0defd0988e96970acb19ba47c7efa57d484479cb2865f1ea8df8cac7ed6b379456a5faef86942c310a4837da8f9e41086d3a1d44e997396036da9ef39877d8aca4e3bd4f68084b113b2dfebcbd5f8bec0280fa4578a4bcf629cd62294e860e8e8142983b6c56dd0203010001", "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7jCCAtagAwIBAgIRAIoBT94NClWkSBy3wIuaEXEwDQYJKoZIhvcNAQELBQAw\nJTEjMCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDMwHhcNMjYwNjI3\nMDAwMDAwWhcNMjcwNDIxMDAwMDAwWjA4MTYwNAYDVQQDEy0wMmU1ODg2OWEyYjYu\nQ29uZmlkZW50aWFsVk0uQXp1cmUud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDg/O18AAD8CrbkRHaoDWHfMxzbqtkrJEnGkn1q68W3\n0rKNltIyleXybABh+KabhMKCRJMcilzRGOeGwItvRMsNnVxdrc9UAvpPiPCeU750\n74e2ewU3GRDjVtWyn/cKvM/gNvLTAAdwEQTBaxIzB2dUEu5JzZlqgMGV1F+dyPbm\n4pUkBz01mM2EnZIbfUQQ90wEK1YdnWb2JY4N79CYjpaXCssZukfH76V9SER5yyhl\n8eqN+MrH7Ws3lFal+u+GlCwxCkg32o+eQQhtOh1E6Zc5YDbanvOYd9ispOO9T2gI\nSxE7Lf68vV+L7AKA+kV4pLz2Kc1iKU6GDo6BQpg7bFbdAgMBAAGjggEEMIIBADAO\nBgNVHQ8BAf8EBAMCB4AwGAYDVR0gBBEwDzANBgsrBgEEAYI3bIFIATAcBgNVHSUE\nFTATBgorBgEEAYI3CgMMBgVngQUIAzAdBgNVHQ4EFgQUr57RFs5NWi58XdGtOgBu\n3FKq2zEwGgYKKwYBBAGCNw0CAwQMFgo2LjIuOTIwMC4yMFoGCSsGAQQBgjcVFARN\nMEsCAQUMD0FNUzA5MTA3MjcxMDAwNQwaV09SS0dST1VQXEFNUzA5MTA3MjcxMDAw\nNSQMGVZ0cG1NYW5hZ2VtZW50U2VydmljZS5leGUwHwYDVR0jBBgwFoAUZwmG+PmB\nXpOt+clk5xQK2pnLCi0wDQYJKoZIhvcNAQELBQADggEBAJoddxcTOfWimIJvejrK\n8k9SpJBu5FQ3S9gAoPNsO2jZ7PxK4FeSp39WHl2ks9pJIJiopuSDV+G1rPccY5zZ\nL9c4G3kffhgFNwvIj0+JCcudPF8vn/Vs4JRYc/VBvzG+bjQo+S+GWi49akWiR3Eg\nXbgZDA4sDyLQEtraP0Z2JOaJTVVvM/ldUjIl3rFpo8Dijehd8auJe8EObmXkS7AU\n63jI7jT6sU91y3s6X360esdmKN+/Z7i01HhnaBjUgROcs6y5kCXn7g1NO7PLqkpF\nFHsvHZFG9Jf+zGLORS60JLNLDzz5yl6hFq1zzETH2Pw68bjfbxiStS4jyiz6E17k\nG9I=\n-----END CERTIFICATE-----\n" } From 3cd3328bf64b5a608cf6b083139e34fe60ff6982 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Sun, 28 Jun 2026 17:18:50 +0000 Subject: [PATCH 26/39] refactor and rework of claim traits, output verifiable claims --- azure-attest/src/lib.rs | 1 - azure-attest/src/quote.rs | 22 +++++++- azure-attest/src/quote/claims.rs | 88 +++++++++++++++++++++++++------ azure-attest/src/quote/verify.rs | 36 +++++++++---- azure-attest/tests/decode.rs | 4 +- libattest/src/validation.rs | 61 ++++++++++++--------- nvidia-attest/src/lib.rs | 13 ++++- reticle/src/lib.rs | 11 ++-- snp-attest/src/attestation/mod.rs | 18 ++++++- snp-attest/src/claims.rs | 10 ---- tdx-attest/src/quote.rs | 9 ++++ 11 files changed, 201 insertions(+), 72 deletions(-) diff --git a/azure-attest/src/lib.rs b/azure-attest/src/lib.rs index 7bd85ddd..e83f8fc5 100644 --- a/azure-attest/src/lib.rs +++ b/azure-attest/src/lib.rs @@ -10,4 +10,3 @@ pub mod ca; mod serde; pub use quote::AzureQuote; -pub use quote::verify::verify; diff --git a/azure-attest/src/quote.rs b/azure-attest/src/quote.rs index 78cf6b65..91f12897 100644 --- a/azure-attest/src/quote.rs +++ b/azure-attest/src/quote.rs @@ -11,7 +11,12 @@ use tdx_attest::TdxQuote; use tpm2_protocol::data::{TpmAlgId, TpmsAttest}; use crate::{ - quote::pcr::PcrBankReading, + collateral::ReportVerifier, + nonce::AzureNonce, + quote::{ + claims::{AzureClaims, TpmAttestEvidence}, + pcr::PcrBankReading, + }, report::{AttestationReport, HardwareReport}, }; @@ -50,7 +55,20 @@ pub struct AzureQuote { } impl AzureQuote { - pub fn new( + /// performs cryptographic verification of the whole azure stack + /// and returns a set of verified claims on which the client can apply policies + pub fn verify( + &self, + report_verifier: ReportVerifier, + nonce: &AzureNonce, + ) -> libattest::Result { + verify::verify_impl(self, report_verifier, nonce) + } +} + +// create only implementations +impl AzureQuote { + pub(crate) fn new( quote: TpmsAttest, pcr_bank: PcrBankReading, hardware_report: AttestationReport, diff --git a/azure-attest/src/quote/claims.rs b/azure-attest/src/quote/claims.rs index df87287d..0ee3d418 100644 --- a/azure-attest/src/quote/claims.rs +++ b/azure-attest/src/quote/claims.rs @@ -1,14 +1,19 @@ -use serde::{Deserialize, Serialize}; +use libattest::validation::Verifiable; +use serde::Serialize; +use sha2::Sha256; use snp_attest::claims::SevClaims; use tdx_attest::dcap::types::TdxQuoteBody; +use tpm2_protocol::data::{TpmsAttest, TpmsClockInfo}; -#[derive(Serialize)] +use crate::{quote::pcr::PcrBankReading, report::RuntimeClaims}; + +#[derive(Serialize, Debug)] pub enum HardwareClaims { - Sev(SevClaims), - Tdx(TdxQuoteBody), + Sev(Box), + Tdx(Box), } -#[derive(Serialize)] +#[derive(Serialize, Debug)] pub struct TpmClockInfo { clock: u64, reset_count: u32, @@ -16,18 +21,69 @@ pub struct TpmClockInfo { safe: bool, } -// #[derive(Serialize)] -// pub struct TpmAttestEvidence { -// qualified_signer: String, -// #[serde(with = "hex::serde")] -// extra_data: Vec, -// clock_info: TpmClockInfo, -// firmware_version: u64, +impl From for TpmClockInfo { + fn from(value: TpmsClockInfo) -> Self { + Self { + clock: value.clock.value(), + reset_count: value.reset_count.value(), + restart_count: value.restart_count.value(), + safe: value.safe.0, + } + } +} + +#[derive(Serialize, Debug)] +pub struct TpmAttestEvidence<'a> { + qualified_signer: &'a [u8], + #[serde(with = "hex::serde")] + extra_data: &'a [u8], + clock_info: TpmClockInfo, + firmware_version: u64, +} + +impl<'a> From<&'a TpmsAttest> for TpmAttestEvidence<'a> { + fn from(value: &'a TpmsAttest) -> Self { + Self { + qualified_signer: &value.qualified_signer, + extra_data: &value.extra_data, + clock_info: value.clock_info.into(), + firmware_version: value.firmware_version.0, + } + } +} -// quote: -// } +#[derive(Serialize, Debug)] +pub struct AzureClaims<'a> { + pcr_bank: &'a PcrBankReading, + tpm_evidence: TpmAttestEvidence<'a>, -#[derive(Serialize)] -pub struct AzureClaims { hardware_claims: HardwareClaims, + runtime_claims: RuntimeClaims, +} + +impl Verifiable for AzureClaims<'_> { + type Claims<'x> + = &'x Self + where + Self: 'x; + + fn claims<'a>(&'a self) -> Self::Claims<'a> { + self + } +} + +impl<'a> AzureClaims<'a> { + pub(super) fn new( + pcr_bank: &'a PcrBankReading, + hardware_claims: HardwareClaims, + tpm_evidence: TpmAttestEvidence<'a>, + runtime_claims: RuntimeClaims, + ) -> Self { + Self { + pcr_bank, + hardware_claims, + tpm_evidence, + runtime_claims, + } + } } diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index 423d6c58..fa867378 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -10,6 +10,7 @@ use libattest::{ signature::rsa::RsaSignature, }, error::Context, + validation::Verifiable, }; use rsa::{BoxedUint, pkcs1::DecodeRsaPublicKey, pkcs1v15::VerifyingKey, signature::Verifier}; use sha2::Sha256; @@ -20,7 +21,10 @@ use crate::{ ca::AZURE_CA, collateral::ReportVerifier, nonce::AzureNonce, - quote::{AzureQuote, ParsedHardwareReport}, + quote::{ + AzureQuote, ParsedHardwareReport, + claims::{AzureClaims, HardwareClaims, TpmAttestEvidence}, + }, report::RuntimeClaims, }; @@ -130,7 +134,7 @@ pub fn verify_runtime_data(quote: &AzureQuote) -> libattest::Result libattest::Result<()> { +) -> libattest::Result { use libattest::quote::QuoteVerifier; use snp_attest::nonce::SevNonce; use tdx_attest::nonce::TdxNonce; @@ -150,22 +154,24 @@ pub fn verify_report_digest( .map(ByteNonce::from) .context("nonce didn't fit")?; - match report { + let claims = match report { ParsedHardwareReport::Tdx(tdx_quote) => { let verifier = verifier.tdx().context("didn't receive tdx verifier")?; let nonce = TdxNonce::new(nonce); verifier.verify(&tdx_quote, &nonce)?; + HardwareClaims::Tdx(tdx_quote.body().clone().into()) } ParsedHardwareReport::Sev(sev_quote) => { let verifier = verifier.sev().context("didn't receive sev verifier")?; let nonce = SevNonce::new(nonce); verifier.verify(&sev_quote, &nonce)?; + HardwareClaims::Sev(sev_quote.claims().into()) } }; - Ok(()) + Ok(claims) } fn verify_quote_nonce(azure_quote: &AzureQuote, nonce: &AzureNonce) -> libattest::Result<()> { @@ -198,26 +204,36 @@ fn verify_pcr_digest(azure_quote: &AzureQuote) -> libattest::Result<()> { Ok(()) } -pub fn verify( - azure_quote: AzureQuote, +pub fn verify_impl<'a>( + azure_quote: &'a AzureQuote, report_verifier: ReportVerifier, nonce: &AzureNonce, -) -> libattest::Result<()> { +) -> libattest::Result> { // 1: verify quote certificate chain against pinned trust let trust_chain = verify_quote_trust_chain(&azure_quote).context("while verifying quote certificates")?; // 2: verify that AK signs Quote through leaf certificate verify_quote_signature(&azure_quote, trust_chain).context("while verifying quote signature")?; // 3: verify that the hardware report signs report data - verify_report_digest(&azure_quote, &report_verifier) + let hardware_claims = verify_report_digest(&azure_quote, &report_verifier) .context("while verifying report digest")?; // 4: verify that report data contains the correct ak key, // sprouting azure trust from SEV/TDX - verify_runtime_data(&azure_quote).context("while verifying runtime data")?; + let runtime_claims = + verify_runtime_data(&azure_quote).context("while verifying runtime data")?; // 5: Verify that user supplied nonce and received quote nonce match verify_quote_nonce(&azure_quote, nonce).context("while verifying quote nonce")?; // 6: Verify pcr digest verify_pcr_digest(&azure_quote).context("failed verifying pcr bank digest")?; - Ok(()) + let tpm_evidence = TpmAttestEvidence::from(&azure_quote.quote); + + let azure_claims = AzureClaims::new( + &azure_quote.pcr_bank, + hardware_claims, + tpm_evidence, + runtime_claims, + ); + + Ok(azure_claims) } diff --git a/azure-attest/tests/decode.rs b/azure-attest/tests/decode.rs index 7f601ec7..ca6a3bbb 100644 --- a/azure-attest/tests/decode.rs +++ b/azure-attest/tests/decode.rs @@ -26,5 +26,7 @@ async fn verify_attestation() { .unwrap(); let nonce = AzureNonce::from([0u8; 32]); - azure_attest::verify(quote, verifier, &nonce).unwrap(); + let claims = quote.verify(verifier, &nonce).unwrap(); + + println!("{claims:?}"); } diff --git a/libattest/src/validation.rs b/libattest/src/validation.rs index 6e0e27c4..930888b4 100644 --- a/libattest/src/validation.rs +++ b/libattest/src/validation.rs @@ -5,20 +5,23 @@ use serde::{Deserialize, Serialize}; use crate::error::{AttestationError, Context}; -pub trait Claim: AssignedPolicy { +pub trait Verifiable { + type Claims<'a>: Serialize + where + Self: 'a; + /// Converts this set of claims into a rego engine compatible format - fn rego_repr(&self) -> impl Serialize; + fn claims<'a>(&'a self) -> Self::Claims<'a>; } -pub trait IntoClaims { - type Claims: Claim; - fn into_claims(self) -> Self::Claims; -} +impl Verifiable for &V { + type Claims<'a> + = V::Claims<'a> + where + Self: 'a; -impl IntoClaims for C { - type Claims = C; - fn into_claims(self) -> C { - self + fn claims<'a>(&'a self) -> Self::Claims<'a> { + (*self).claims() } } @@ -33,13 +36,19 @@ pub trait AssignedPolicy { fn policy(&self) -> Cow<'static, str>; } -pub struct WithPolicy { - claims: T, +impl AssignedPolicy for &A { + fn policy(&self) -> Cow<'static, str> { + (*self).policy() + } +} + +pub struct WithPolicy { + claims: C, policy: Cow<'static, str>, } -impl WithPolicy { - pub fn new(policy: impl Into>, claims: T) -> Self { +impl WithPolicy { + pub fn new(policy: impl Into>, claims: C) -> Self { Self { claims, policy: policy.into(), @@ -47,13 +56,18 @@ impl WithPolicy { } } -impl Claim for WithPolicy { - fn rego_repr(&self) -> impl Serialize { - &self.claims +impl Verifiable for WithPolicy { + type Claims<'a> + = C::Claims<'a> + where + Self: 'a; + + fn claims<'a>(&'a self) -> Self::Claims<'a> { + self.claims.claims() } } -impl AssignedPolicy for WithPolicy { +impl AssignedPolicy for WithPolicy { fn policy(&self) -> Cow<'static, str> { self.policy.clone() } @@ -130,17 +144,16 @@ impl Validator { /// gets the rego query and input from `impl Claim` and then /// drives the engine to verify the query - pub fn verify_claim( - &self, - claims: impl IntoClaims, - ) -> Result { - let claims = claims.into_claims(); + pub fn verify_claim(&self, claims: C) -> Result + where + C: Verifiable + AssignedPolicy, + { // avois polluting the engine for further verifications // and allows us to have this method &self let mut engine = self.engine.clone(); // convert claims to rego compatible format - let value = serde_value::to_value(claims.rego_repr())?; + let value = serde_value::to_value(claims.claims())?; let value = regorus::Value::deserialize(value)?; // here we set what input. will be in rego engine.set_input(value); diff --git a/nvidia-attest/src/lib.rs b/nvidia-attest/src/lib.rs index a74db0fd..4b6ae491 100644 --- a/nvidia-attest/src/lib.rs +++ b/nvidia-attest/src/lib.rs @@ -8,7 +8,7 @@ use jsonwebtoken::{DecodingKey, Validation}; use libattest::{ bail, error::{AttestationError, Context, Expose}, - // validation::AssignedPolicy, + validation::Verifiable, }; use serde::Serialize; use serde_json::Value; @@ -31,6 +31,17 @@ pub struct DecodedClaims { gpu_claims: HashMap, } +impl Verifiable for DecodedClaims { + type Claims<'a> + = &'a DecodedClaims + where + Self: 'a; + + fn claims<'a>(&'a self) -> Self::Claims<'a> { + self + } +} + impl DecodedClaims { pub fn overall_claims(&self) -> &OverallClaims { &self.overall_claims diff --git a/reticle/src/lib.rs b/reticle/src/lib.rs index fc3accfc..8913ebfd 100644 --- a/reticle/src/lib.rs +++ b/reticle/src/lib.rs @@ -267,8 +267,10 @@ impl Client { let verifier = SevQuoteVerifier::new(keychain); verifier.verify(&attestation, &nonce)?; + let claims = WithPolicy::new("sev.allow", attestation); + self.policy_validator - .verify_claim(&attestation)? + .verify_claim(&claims)? .or_err("sev claims did not match specified OPA policy") .expose_error()?; @@ -287,17 +289,16 @@ impl Client { .context("failed fetching collateral from pcs server") .expose_error()?; - let claims = quote.body().clone(); - let verifier = TdxQuoteVerifier::new(collateral); verifier .verify("e, &nonce) .context("TDX quote verification has failed") .expose_error()?; - let claims = WithPolicy::new("tdx.allow", claims); + let claims = WithPolicy::new("tdx.allow", quote); + self.policy_validator - .verify_claim(claims)? + .verify_claim(&claims)? .or_err("tdx claims did not match specified OPA policy") .expose_error()?; diff --git a/snp-attest/src/attestation/mod.rs b/snp-attest/src/attestation/mod.rs index fce72319..12fd4d20 100644 --- a/snp-attest/src/attestation/mod.rs +++ b/snp-attest/src/attestation/mod.rs @@ -6,12 +6,15 @@ pub mod verify; // pub mod nonce; -use libattest::error::{AttestationError, Context, Expose}; +use libattest::{ + error::{AttestationError, Context, Expose}, + validation::Verifiable, +}; #[cfg(target_family = "wasm")] use wasm_bindgen::prelude::*; -use crate::{kds::chipid_from_gen, nonce::SevNonce, oid}; +use crate::{claims::SevClaims, kds::chipid_from_gen, nonce::SevNonce, oid}; use der::Encode; use sev::{ CpuFamily, CpuModel, Generation, firmware::guest::AttestationReport, parser::ByteParser, @@ -129,3 +132,14 @@ impl SevQuote { &self.report } } + +impl Verifiable for SevQuote { + fn claims(&self) -> SevClaims { + SevClaims::from(self.report()) + } + + type Claims<'a> + = SevClaims + where + Self: 'a; +} diff --git a/snp-attest/src/claims.rs b/snp-attest/src/claims.rs index d06833b3..d60aaabb 100644 --- a/snp-attest/src/claims.rs +++ b/snp-attest/src/claims.rs @@ -1,9 +1,6 @@ -use libattest::validation::WithPolicy; use serde::Serialize; use sev::firmware::guest::{AttestationReport, GuestPolicy}; -use crate::SevQuote; - #[derive(Debug, Serialize)] pub struct GuestPolicyClaims { pub abi_minor: u64, @@ -60,10 +57,3 @@ impl From<&AttestationReport> for SevClaims { } } } - -impl libattest::validation::IntoClaims for &SevQuote { - type Claims = WithPolicy; - fn into_claims(self) -> WithPolicy { - WithPolicy::new("sev.allow", SevClaims::from(self.report())) - } -} diff --git a/tdx-attest/src/quote.rs b/tdx-attest/src/quote.rs index ccbb4943..c914f4b3 100644 --- a/tdx-attest/src/quote.rs +++ b/tdx-attest/src/quote.rs @@ -2,6 +2,7 @@ use libattest::{ crypto::{CertificateChain, algorithms::ecdsa::EcdsaCert}, error::Context, p256, + validation::Verifiable, }; use p256::ecdsa::{Signature, VerifyingKey}; @@ -123,6 +124,14 @@ impl TdxQuote { } } +impl Verifiable for TdxQuote { + type Claims<'a> = &'a TdxQuoteBody; + + fn claims(&self) -> Self::Claims<'_> { + &self.body + } +} + /// public keys are encoded in dcap without the header for sec1 (0x04) /// so we have to add it manually fn decode_public_key(public_key: &[u8; 64]) -> Result { From 5b02c2b54f99402005cb52b7199b317e4c7ff19d Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:16:42 +0000 Subject: [PATCH 27/39] attestation server implementation --- Cargo.lock | 10 +- attestation-server/Cargo.toml | 2 + attestation-server/src/azure_api.rs | 17 + attestation-server/src/main.rs | 5 + attestation-server/src/modules.rs | 39 +- attestation-server/src/nvidia_api.rs | 12 +- azure-attest/Cargo.toml | 2 +- azure-attest/tests/attestation.json | 1191 +------------------------- libattest/src/modules.rs | 1 + 9 files changed, 75 insertions(+), 1204 deletions(-) create mode 100644 attestation-server/src/azure_api.rs diff --git a/Cargo.lock b/Cargo.lock index 20aae23d..f6c89e1d 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,7 @@ name = "attestation-server" version = "0.1.0" dependencies = [ "anyhow", + "azure-attest", "base64", "configfs-tsm", "env_logger", @@ -189,6 +190,7 @@ dependencies = [ "nvat", "nvidia-attest", "nvml-wrapper", + "reqwest 0.13.4", "rocket", "serde", "serde_json", @@ -3065,14 +3067,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", + "futures-util", "h2 0.4.13", "http 1.4.0", "http-body 1.0.1", @@ -3903,7 +3907,7 @@ dependencies = [ "der 0.8.0", "hex", "libattest", - "reqwest 0.13.2", + "reqwest 0.13.4", "sec1 0.7.3", "serde", "serde_json", diff --git a/attestation-server/Cargo.toml b/attestation-server/Cargo.toml index cab23970..d512c4c0 100644 --- a/attestation-server/Cargo.toml +++ b/attestation-server/Cargo.toml @@ -19,9 +19,11 @@ nvat = { git = "ssh://git@github.com/prem-research/nvat-rs.git", branch = "main" libattest = { path = "../libattest" } nvidia-attest = { path = "../nvidia-attest" } +azure-attest = { path = "../azure-attest", features = ["host"] } snp-attest = { path = "../snp-attest", default-features = false } env_logger = "0.11.8" log = "0.4.29" configfs-tsm = { version = "0.0.2" } nvml-wrapper = "0.12.1" +reqwest = { version = "0.13.4", features = ["blocking", "json"] } diff --git a/attestation-server/src/azure_api.rs b/attestation-server/src/azure_api.rs new file mode 100644 index 00000000..811fcbdc --- /dev/null +++ b/attestation-server/src/azure_api.rs @@ -0,0 +1,17 @@ +use anyhow::Context; +use azure_attest::{AzureQuote, host::vtpm::AzureTpmCtx, nonce::AzureNonce}; + +use crate::{nonce::NonceParam, response::ApiJsonResult}; + +#[rocket::get("/azure?")] +pub async fn azure_attestation( + nonce: NonceParam, 32>, +) -> ApiJsonResult { + let NonceParam(nonce) = nonce; + let tpm = AzureTpmCtx::default_context().context("failed creating tpm context")?; + let nonce = AzureNonce::new(nonce); + + let quote = azure_attest::host::azure_attest(tpm, &nonce)?; + + Ok(quote.into()) +} diff --git a/attestation-server/src/main.rs b/attestation-server/src/main.rs index f64280ec..1773237c 100644 --- a/attestation-server/src/main.rs +++ b/attestation-server/src/main.rs @@ -1,6 +1,7 @@ mod response; // pub mod modules; +mod azure_api; pub mod modules; mod nonce; mod nvidia_api; @@ -52,6 +53,10 @@ async fn main() -> Result<(), anyhow::Error> { routes.extend(routes![tdx_api::tdx_attestation]); rocket } + CpuModule::Azure => { + routes.extend(routes![azure_api::azure_attestation]); + rocket + } _ => bail!("cpu module not yet supported by attestation-server"), }; diff --git a/attestation-server/src/modules.rs b/attestation-server/src/modules.rs index 38f80aa3..f3ab7774 100644 --- a/attestation-server/src/modules.rs +++ b/attestation-server/src/modules.rs @@ -1,6 +1,36 @@ use anyhow::Context; use libattest::{CpuModule, GpuModule, Modules, ModulesBuilder}; -use std::path::Path; +use serde::Deserialize; +use std::{path::Path, time::Duration}; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SecurityProfile { + security_type: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ComputeMetadata { + // name: String, + security_profile: SecurityProfile, +} + +struct AzureDetector; + +impl AzureDetector { + pub fn fetch_imds() -> anyhow::Result { + let res: ComputeMetadata = reqwest::blocking::ClientBuilder::new() + .timeout(Duration::from_secs(2)) + .build()? + .get("http://169.254.169.254/metadata/instance/compute") + .send()? + .error_for_status()? + .json()?; + + Ok(res) + } +} pub struct ModuleDetector; @@ -13,11 +43,18 @@ impl ModuleDetector { path.as_ref().exists() } + fn detect_azure(&self) -> Option<()> { + let imds = AzureDetector::fetch_imds().ok()?; + (imds.security_profile.security_type == "ConfidentialVM").then_some(()) + } + fn detect_cpu(&self) -> Option { if self.path_exists(Self::SEV_PATH) { Some(CpuModule::Sev) } else if self.path_exists(Self::TDX_PATH) { Some(CpuModule::Tdx) + } else if self.detect_azure().is_some() { + Some(CpuModule::Azure) } else { None } diff --git a/attestation-server/src/nvidia_api.rs b/attestation-server/src/nvidia_api.rs index 9bd507ef..a51109fa 100644 --- a/attestation-server/src/nvidia_api.rs +++ b/attestation-server/src/nvidia_api.rs @@ -7,7 +7,6 @@ use nvidia_attest::EATToken; use nvidia_attest::keychain::KeyChain; use nvidia_attest::nonce::NvidiaNonce; use nvml_wrapper::Nvml; -use nvml_wrapper::enum_wrappers::device::Brand::Nvidia; use rocket::fairing::{Fairing, Info, Kind}; use rocket::{Build, Rocket, State}; @@ -17,23 +16,14 @@ use crate::response::ApiError; pub struct SdkFairing { handle: SdkHandle, nvml: Nvml, - - // set of UUIDs of gpus enabled for confidential computing. - // must turn off confidential computing on exit. - attestation_enabled: HashSet, } impl SdkFairing { pub fn init() -> anyhow::Result { let handle = SdkHandle::get_handle()?; let nvml = Nvml::init()?; - let attestation_enabled = HashSet::new(); - Ok(SdkFairing { - handle, - nvml, - attestation_enabled, - }) + Ok(SdkFairing { handle, nvml }) } async fn attest_and_init(&self) -> anyhow::Result<()> { diff --git a/azure-attest/Cargo.toml b/azure-attest/Cargo.toml index 33ba1369..d2a14880 100644 --- a/azure-attest/Cargo.toml +++ b/azure-attest/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [features] -host = ["dep:tss-esapi"] +host = ["dep:tss-esapi", "dep:anyhow"] cli = ["dep:tss-esapi", "dep:anyhow"] [dependencies] diff --git a/azure-attest/tests/attestation.json b/azure-attest/tests/attestation.json index ee1aebf0..d9a20d34 100644 --- a/azure-attest/tests/attestation.json +++ b/azure-attest/tests/attestation.json @@ -1,5 +1,5 @@ { - "quote": "ff54434780180022000b5ac884d7ac7bc7f9229c3d6949f32ef8937a460551c226fbf425735936564d9000200000000000000000000000000000000000000000000000000000000000000000000000002f9f37b0000000060000000001202003120012000300000001000b03ffffff0020679507d7cf91c3a52455ec7d66a4c5d445062c6d092d264ac87cc215508f43e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "quote": "ff54434780180022000b5ac884d7ac7bc7f9229c3d6949f32ef8937a460551c226fbf425735936564d90002000000000000000000000000000000000000000000000000000000000000000000000000030344cd4000000060000000001202003120012000300000001000b03ffffff0020679507d7cf91c3a52455ec7d66a4c5d445062c6d092d264ac87cc215508f43e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "pcr_bank": { "pcr_list": { "1": { @@ -100,1192 +100,7 @@ ] }, "payload": { - "Sev": [ - 3, - 0, - 0, - 0, - 10, - 0, - 0, - 0, - 31, - 0, - 3, - 0, - 0, - 0, - 0, - 0, - 2, - 33, - 32, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 2, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 4, - 0, - 0, - 0, - 0, - 0, - 24, - 219, - 37, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 33, - 172, - 119, - 120, - 188, - 125, - 93, - 10, - 240, - 105, - 106, - 67, - 155, - 55, - 254, - 153, - 250, - 92, - 251, - 75, - 60, - 43, - 233, - 18, - 174, - 160, - 100, - 210, - 140, - 218, - 120, - 128, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 65, - 247, - 127, - 229, - 193, - 65, - 99, - 67, - 248, - 77, - 190, - 237, - 237, - 80, - 78, - 180, - 162, - 196, - 80, - 134, - 19, - 23, - 237, - 62, - 78, - 70, - 205, - 119, - 28, - 121, - 36, - 58, - 76, - 190, - 179, - 215, - 94, - 198, - 99, - 230, - 167, - 164, - 123, - 209, - 244, - 250, - 181, - 3, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 148, - 47, - 217, - 62, - 189, - 230, - 234, - 122, - 150, - 239, - 173, - 234, - 252, - 96, - 241, - 198, - 179, - 209, - 14, - 112, - 59, - 29, - 175, - 215, - 85, - 91, - 146, - 247, - 243, - 211, - 45, - 14, - 0, - 103, - 103, - 100, - 140, - 186, - 91, - 16, - 42, - 243, - 214, - 87, - 86, - 175, - 65, - 119, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 198, - 41, - 56, - 211, - 205, - 121, - 132, - 192, - 23, - 239, - 171, - 16, - 176, - 162, - 211, - 181, - 49, - 230, - 147, - 231, - 217, - 111, - 15, - 118, - 9, - 161, - 58, - 101, - 4, - 80, - 187, - 231, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 4, - 0, - 0, - 0, - 0, - 0, - 24, - 219, - 25, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 9, - 39, - 161, - 223, - 114, - 74, - 200, - 211, - 37, - 190, - 82, - 111, - 118, - 72, - 52, - 240, - 184, - 239, - 254, - 204, - 152, - 8, - 40, - 186, - 141, - 113, - 177, - 205, - 121, - 121, - 0, - 145, - 22, - 63, - 59, - 199, - 218, - 113, - 51, - 139, - 35, - 51, - 61, - 211, - 36, - 7, - 108, - 33, - 0, - 64, - 23, - 32, - 74, - 246, - 208, - 154, - 117, - 212, - 173, - 155, - 70, - 151, - 51, - 229, - 4, - 0, - 0, - 0, - 0, - 0, - 24, - 219, - 29, - 55, - 1, - 0, - 29, - 55, - 1, - 0, - 4, - 0, - 0, - 0, - 0, - 0, - 24, - 219, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 111, - 53, - 222, - 17, - 24, - 156, - 218, - 93, - 93, - 138, - 211, - 1, - 50, - 145, - 59, - 185, - 47, - 242, - 195, - 229, - 252, - 230, - 120, - 63, - 64, - 6, - 116, - 245, - 114, - 215, - 18, - 240, - 26, - 123, - 219, - 231, - 59, - 201, - 82, - 251, - 146, - 67, - 200, - 12, - 27, - 179, - 59, - 253, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 252, - 194, - 33, - 19, - 96, - 27, - 70, - 86, - 137, - 54, - 187, - 149, - 108, - 221, - 122, - 1, - 243, - 90, - 5, - 188, - 65, - 221, - 93, - 128, - 180, - 23, - 235, - 196, - 96, - 153, - 129, - 208, - 144, - 33, - 160, - 176, - 64, - 9, - 4, - 206, - 182, - 99, - 165, - 106, - 69, - 243, - 248, - 136, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] + "Sev": "030000000a0000001f000300000000000221200000000000000000000000000002000000000000000000000000000000000000000100000004000000000018db2500000000000000000000000000000021ac7778bc7d5d0af0696a439b37fe99fa5cfb4b3c2be912aea064d28cda7880000000000000000000000000000000000000000000000000000000000000000041f77fe5c1416343f84dbeeded504eb4a2c450861317ed3e4e46cd771c79243a4cbeb3d75ec663e6a7a47bd1f4fab5030000000000000000000000000000000000000000000000000000000000000000942fd93ebde6ea7a96efadeafc60f1c6b3d10e703b1dafd7555b92f7f3d32d0e006767648cba5b102af3d65756af4177000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c62938d3cd7984c017efab10b0a2d3b531e693e7d96f0f7609a13a650450bbe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04000000000018db1901010000000000000000000000000000000000000000000927a1df724ac8d325be526f764834f0b8effecc980828ba8d71b1cd79790091163f3bc7da71338b23333dd324076c21004017204af6d09a75d4ad9b469733e504000000000018db1d3701001d37010004000000000018db0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001882f8e127b75c1a0b9b7fbf8b1caa4700ef75c04fcb00d38325cdfffd9240c0762a2cbe4e64c3ed8a4ba8d7e44ce299000000000000000000000000000000000000000000000000703abc7f9fb7232f64bd3087c7e5f9bbc4b59d706f872fc7a3323d724c5eb05ba77d225adccaba5f9e454854325e61d30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, "runtime": { "header": { @@ -1299,7 +114,7 @@ } }, "trust": { - "quote_signature": "2ad997ed0037479b95b99b4c93e92fbf79fe51c924134aee2a513be51625f35a86c6d5fe6fbf80069d7c63301f4bda1fda079b4205593ea15af66b6b885f5f9d2c622f4115f1e20f1cb0bcb55fb22a5b80159882c608129275d9636d4c4b36ba92858a4113c12f2ef7c084037afa4122e41c43d536af114ed59b5d983016e3762c3523df1d87b17e423454a38c11234f9b97ec827253adcdb73eb63b985fe06ef55efb4129ecdc96374e12ac4aa650bcd885ff264030e679d4e5594e8865e3f79bb40d7cdaaabf4b1d138c65f16eff92b9cd647eebc00541fc624057dcfa1f0cc8489ecebc2643aebceccb8f302c0899ae05e6fca752bf164ffab6485d15679e", + "quote_signature": "40e0580c3983253594777cd9f07defb26e8f3a6533953d512c33e1cb31e0bdd33b71d3eab2de70a24b73957dadd7b19e5ebd69cc310f75da869a4801b8e48b5517525de13715750e44259d8a9b82361d8426b843e88446a6b0d96ad6b64ee8d35b5ac9f48be73527e7958b6149eeae6155edf9aa38d3750dba4063113472314604e0ec632ced6b33c41374c80eb5c3b33e7cda0259335c2b5fdcbb62f4daf6e16592429c5198bf399527142fcbb3821763de3fcd9990eb44e2394412f91d4a35242839fc4e385b40ea9c90bba13e72be9441241c57779ca27556a6ed3cb3f518ec12c85cb8b96f584fa62f7f874723a91383727c62431eeede3653eb075fbe47", "ak_key": "30820122300d06092a864886f70d01010105000382010f003082010a0282010100e0fced7c0000fc0ab6e44476a80d61df331cdbaad92b2449c6927d6aebc5b7d2b28d96d23295e5f26c0061f8a69b84c28244931c8a5cd118e786c08b6f44cb0d9d5c5dadcf5402fa4f88f09e53be74ef87b67b05371910e356d5b29ff70abccfe036f2d30007701104c16b123307675412ee49cd996a80c195d45f9dc8f6e6e29524073d3598cd849d921b7d4410f74c042b561d9d66f6258e0defd0988e96970acb19ba47c7efa57d484479cb2865f1ea8df8cac7ed6b379456a5faef86942c310a4837da8f9e41086d3a1d44e997396036da9ef39877d8aca4e3bd4f68084b113b2dfebcbd5f8bec0280fa4578a4bcf629cd62294e860e8e8142983b6c56dd0203010001", "ak_cert": "-----BEGIN CERTIFICATE-----\nMIID7jCCAtagAwIBAgIRAIoBT94NClWkSBy3wIuaEXEwDQYJKoZIhvcNAQELBQAw\nJTEjMCEGA1UEAxMaR2xvYmFsIFZpcnR1YWwgVFBNIENBIC0gMDMwHhcNMjYwNjI3\nMDAwMDAwWhcNMjcwNDIxMDAwMDAwWjA4MTYwNAYDVQQDEy0wMmU1ODg2OWEyYjYu\nQ29uZmlkZW50aWFsVk0uQXp1cmUud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDg/O18AAD8CrbkRHaoDWHfMxzbqtkrJEnGkn1q68W3\n0rKNltIyleXybABh+KabhMKCRJMcilzRGOeGwItvRMsNnVxdrc9UAvpPiPCeU750\n74e2ewU3GRDjVtWyn/cKvM/gNvLTAAdwEQTBaxIzB2dUEu5JzZlqgMGV1F+dyPbm\n4pUkBz01mM2EnZIbfUQQ90wEK1YdnWb2JY4N79CYjpaXCssZukfH76V9SER5yyhl\n8eqN+MrH7Ws3lFal+u+GlCwxCkg32o+eQQhtOh1E6Zc5YDbanvOYd9ispOO9T2gI\nSxE7Lf68vV+L7AKA+kV4pLz2Kc1iKU6GDo6BQpg7bFbdAgMBAAGjggEEMIIBADAO\nBgNVHQ8BAf8EBAMCB4AwGAYDVR0gBBEwDzANBgsrBgEEAYI3bIFIATAcBgNVHSUE\nFTATBgorBgEEAYI3CgMMBgVngQUIAzAdBgNVHQ4EFgQUr57RFs5NWi58XdGtOgBu\n3FKq2zEwGgYKKwYBBAGCNw0CAwQMFgo2LjIuOTIwMC4yMFoGCSsGAQQBgjcVFARN\nMEsCAQUMD0FNUzA5MTA3MjcxMDAwNQwaV09SS0dST1VQXEFNUzA5MTA3MjcxMDAw\nNSQMGVZ0cG1NYW5hZ2VtZW50U2VydmljZS5leGUwHwYDVR0jBBgwFoAUZwmG+PmB\nXpOt+clk5xQK2pnLCi0wDQYJKoZIhvcNAQELBQADggEBAJoddxcTOfWimIJvejrK\n8k9SpJBu5FQ3S9gAoPNsO2jZ7PxK4FeSp39WHl2ks9pJIJiopuSDV+G1rPccY5zZ\nL9c4G3kffhgFNwvIj0+JCcudPF8vn/Vs4JRYc/VBvzG+bjQo+S+GWi49akWiR3Eg\nXbgZDA4sDyLQEtraP0Z2JOaJTVVvM/ldUjIl3rFpo8Dijehd8auJe8EObmXkS7AU\n63jI7jT6sU91y3s6X360esdmKN+/Z7i01HhnaBjUgROcs6y5kCXn7g1NO7PLqkpF\nFHsvHZFG9Jf+zGLORS60JLNLDzz5yl6hFq1zzETH2Pw68bjfbxiStS4jyiz6E17k\nG9I=\n-----END CERTIFICATE-----\n" } diff --git a/libattest/src/modules.rs b/libattest/src/modules.rs index 7cb2f9d6..2ed33f04 100644 --- a/libattest/src/modules.rs +++ b/libattest/src/modules.rs @@ -9,6 +9,7 @@ use wasm_bindgen::prelude::wasm_bindgen; pub enum CpuModule { Sev, Tdx, + Azure, } #[cfg_attr(target_family = "wasm", wasm_bindgen)] From d749e6a068e64496ff8f130656467d4ff9dbd6d5 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:32:45 +0000 Subject: [PATCH 28/39] dockerfile download and build tpm2 --- .devcontainer/Dockerfile | 39 ++++++++++++++++------------- Cargo.lock | 1 + reticle/Cargo.toml | 1 + reticle/src/lib.rs | 54 ++++++++++++++++++++++++++++++++++------ 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 45832de5..d1956c4a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -10,7 +10,7 @@ ENV LIBTOOL_VER=2.4.7 ENV LIBXML2_VER=2.9.14 ENV LIBXSLT_VER=1.1.39 ENV XMLSEC_VER=1.3.9 -#ENV TPM2TSS_VER="4.1.3" +ENV TPM2TSS_VER=4.1.3 RUN apt-get update \ && apt-get install -qyy --no-install-recommends \ @@ -131,22 +131,27 @@ RUN \ /tmp/xmlsec1-${XMLSEC_VER} \ /tmp/xmlsec1-${XMLSEC_VER}.tar.gz -# RUN git clone \ -# https://github.com/tpm2-software/tpm2-tss.git \ -# -b ${TPM2TSS_VER} --depth 1 /tmp/tpm2-tss \ -# && cd /tmp/tpm2-tss \ -# && ./bootstrap \ -# && ./configure \ -# --disable-fapi \ -# --disable-policy \ -# --prefix="${SYSROOT}" \ -# --target=${TARGET_TRIPLE}gnu \ -# --host=${TARGET_TRIPLE}gnu \ -# CC=/usr/bin/${TARGET_TRIPLE}gnu-gcc \ -# CXX=/usr/bin/${TARGET_TRIPLE}gnu-cpp \ -# && make -j$(nproc) \ -# && make install \ -# && rm -rf /tmp/tpm2-tss +RUN \ + wget \ + https://github.com/tpm2-software/tpm2-tss/releases/download/${TPM2TSS_VER}/tpm2-tss-${TPM2TSS_VER}.tar.gz \ + -O /tmp/tpm2-tss-${TPM2TSS_VER}.tar.gz \ + && cd /tmp \ + && tar xvf tpm2-tss-${TPM2TSS_VER}.tar.gz \ + && cd tpm2-tss-${TPM2TSS_VER} \ + && CFLAGS=-fPIC ./configure \ + --prefix=${SYSROOT} \ + --host=${TARGET_TRIPLE}gnu \ + --with-crypto=ossl \ + --disable-fapi \ + --disable-policy \ + --disable-doxygen-doc \ + --enable-static \ + --disable-shared \ + CC=/usr/bin/${TARGET_TRIPLE}gnu-gcc \ + CXX=/usr/bin/${TARGET_TRIPLE}gnu-g++ \ + && make -j$(nproc) \ + && make install \ + && rm -rf /tmp/tpm2-tss-${TPM2TSS_VER} /tmp/tpm2-tss-${TPM2TSS_VER}.tar.gz RUN rustup target add \ ${RUST_TARGET} diff --git a/Cargo.lock b/Cargo.lock index f6c89e1d..d7235970 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -3113,6 +3113,7 @@ name = "reticle" version = "0.4.8" dependencies = [ "async-once-cell", + "azure-attest", "bytes", "console_error_panic_hook", "futures", diff --git a/reticle/Cargo.toml b/reticle/Cargo.toml index 2ec70af4..0c78d7e1 100755 --- a/reticle/Cargo.toml +++ b/reticle/Cargo.toml @@ -12,6 +12,7 @@ nvidia-attest = { path = "../nvidia-attest" } reqwest = "0.12.25" snp-attest = { path = "../snp-attest" } tdx-attest = { path = "../tdx-attest" } +azure-attest = { path = "../azure-attest" } thiserror = "2.0.17" wasm-bindgen = "0.2.106" wasm-bindgen-futures = "0.4.56" diff --git a/reticle/src/lib.rs b/reticle/src/lib.rs index 8913ebfd..cd344a08 100644 --- a/reticle/src/lib.rs +++ b/reticle/src/lib.rs @@ -5,9 +5,10 @@ pub mod gateway; pub mod query; pub mod rego; -use std::borrow::Cow; +use std::{borrow::Cow, pin::Pin}; -use futures::future::{Either, OptionFuture}; +use azure_attest::{AzureQuote, collateral::ReportVerifierBuilder, nonce::AzureNonce}; +use futures::future::OptionFuture; use libattest::{ CpuModule, GpuModule, Modules, bail, error::{AttestationError, Context, Expose}, @@ -258,6 +259,43 @@ impl Client { Ok(quote) } + /// Requests and parses an Azure Quote + /// + pub async fn request_azure(&self, nonce: &AzureNonce) -> Result { + let url = self.url.join("/attestation/azure").unwrap(); + let query = [("nonce", &nonce.to_hex())]; + + let response: AzureQuote = self.request(url, &query).await?.json().await?; + + Ok(response) + } + + pub async fn attest_azure(&self) -> Result<(), AttestationError> { + let nonce = AzureNonce::generate(); + + let quote = self.request_azure(&nonce).await?; + let verifier = ReportVerifierBuilder::default() + .sev(async |quote| { + self.kds + .fetch_certificates(quote) + .await + .map(SevQuoteVerifier::new) + }) + .tdx(async |_| unimplemented!()) + .fetch_collateral("e) + .await?; + + let evidence = quote.verify(verifier, &nonce)?; + let claims = WithPolicy::new("azure.allow", evidence); + + self.policy_validator + .verify_claim(claims)? + .or_err("azure claims did not match specified opa policy") + .expose_error()?; + + Ok(()) + } + /// Performs end-to-end sev-snp attestation. Generates nonce and validates claims all in one pub async fn attest_sev(&self) -> Result<(), AttestationError> { let nonce = SevNonce::generate(); @@ -335,11 +373,13 @@ impl Client { .context("failed to request modules from attestation server") .expose_error()?; - let cpu_attest = match modules.cpu() { - CpuModule::Sev => Either::Left(self.attest_sev()), - CpuModule::Tdx => Either::Right(self.attest_tdx()), - _ => bail!("we do not yet support the advertised cpu platform"), - }; + let cpu_attest: Pin>>> = + match modules.cpu() { + CpuModule::Sev => Box::pin(self.attest_sev()), + CpuModule::Tdx => Box::pin(self.attest_tdx()), + CpuModule::Azure => Box::pin(self.attest_azure()), + _ => bail!("we do not yet support the advertised cpu platform"), + }; let gpu_attest = match modules.gpu() { None => None, From 6072219a4397b4b5d2049d3f4ae99fa2945f7c64 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:54:11 +0000 Subject: [PATCH 29/39] fix wasm compilation error --- azure-attest/src/quote.rs | 17 ++++++++--------- libattest/src/crypto/chain/crl.rs | 8 +++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/azure-attest/src/quote.rs b/azure-attest/src/quote.rs index 91f12897..cafc902d 100644 --- a/azure-attest/src/quote.rs +++ b/azure-attest/src/quote.rs @@ -2,21 +2,19 @@ pub mod claims; pub mod pcr; pub mod verify; -use std::collections::BTreeMap; - use serde::{Deserialize, Serialize}; -use sha2::{Sha256, digest}; +use sha2::Sha256; use snp_attest::SevQuote; use tdx_attest::TdxQuote; -use tpm2_protocol::data::{TpmAlgId, TpmsAttest}; +use tpm2_protocol::data::TpmsAttest; + +#[cfg(target_family = "wasm")] +use wasm_bindgen::prelude::*; use crate::{ collateral::ReportVerifier, nonce::AzureNonce, - quote::{ - claims::{AzureClaims, TpmAttestEvidence}, - pcr::PcrBankReading, - }, + quote::{claims::AzureClaims, pcr::PcrBankReading}, report::{AttestationReport, HardwareReport}, }; @@ -44,6 +42,7 @@ impl AzureTrust { } #[derive(Serialize, Deserialize, Debug)] +#[cfg_attr(target_family = "wasm", wasm_bindgen)] pub struct AzureQuote { #[serde(with = "crate::serde::serde_tpm")] quote: TpmsAttest, @@ -61,7 +60,7 @@ impl AzureQuote { &self, report_verifier: ReportVerifier, nonce: &AzureNonce, - ) -> libattest::Result { + ) -> libattest::Result> { verify::verify_impl(self, report_verifier, nonce) } } diff --git a/libattest/src/crypto/chain/crl.rs b/libattest/src/crypto/chain/crl.rs index 70581f5c..8fc216c6 100644 --- a/libattest/src/crypto/chain/crl.rs +++ b/libattest/src/crypto/chain/crl.rs @@ -14,8 +14,7 @@ mod sealed { pub struct Crl { list: CertificateList, - - signature: Signature, + _signature: Signature, } impl sealed::Sealed for Crl {} @@ -74,7 +73,10 @@ impl Crl { verifier.verify(&tbs_list, &signature)?; // ok! - Ok(Self { list, signature }) + Ok(Self { + list, + _signature: signature, + }) } } From 7075ef5e5a9f5c91ad540dc6aa2abe36785c20b2 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:19:43 +0000 Subject: [PATCH 30/39] Linking fix --- .devcontainer/Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d1956c4a..8955b4d6 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -151,6 +151,12 @@ RUN \ CXX=/usr/bin/${TARGET_TRIPLE}gnu-g++ \ && make -j$(nproc) \ && make install \ + && test -f ${SYSROOT}/lib/libtss2-esys.a \ + && test -f ${SYSROOT}/lib/pkgconfig/tss2-esys.pc \ + && mv ${SYSROOT}/lib/libtss2-esys.a ${SYSROOT}/lib/libtss2-esys-real.a \ + && printf '%s\n' \ + 'GROUP ( libtss2-esys-real.a libtss2-sys.a libtss2-mu.a libtss2-tctildr.a libtss2-rc.a )' \ + > ${SYSROOT}/lib/libtss2-esys.a \ && rm -rf /tmp/tpm2-tss-${TPM2TSS_VER} /tmp/tpm2-tss-${TPM2TSS_VER}.tar.gz RUN rustup target add \ From b1a47be4a58fc13095f651f603f60d1688623816 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 30 Jun 2026 19:11:45 +0000 Subject: [PATCH 31/39] add api version to imds call --- attestation-server/src/modules.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/attestation-server/src/modules.rs b/attestation-server/src/modules.rs index f3ab7774..f5110603 100644 --- a/attestation-server/src/modules.rs +++ b/attestation-server/src/modules.rs @@ -24,6 +24,7 @@ impl AzureDetector { .timeout(Duration::from_secs(2)) .build()? .get("http://169.254.169.254/metadata/instance/compute") + .query(&[("api-version", "2025-04-07")]) .send()? .error_for_status()? .json()?; @@ -44,7 +45,10 @@ impl ModuleDetector { } fn detect_azure(&self) -> Option<()> { + log::info!("Trying to detect for Azure..."); let imds = AzureDetector::fetch_imds().ok()?; + + log::debug!("Got security_type {}", imds.security_profile.security_type); (imds.security_profile.security_type == "ConfidentialVM").then_some(()) } From 524277e8c3b0cbdccdbc3f42fac894451cc4669a Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:39:03 +0000 Subject: [PATCH 32/39] Add Metadata header and log errors from imds --- attestation-server/src/modules.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/attestation-server/src/modules.rs b/attestation-server/src/modules.rs index f5110603..6b563087 100644 --- a/attestation-server/src/modules.rs +++ b/attestation-server/src/modules.rs @@ -24,6 +24,7 @@ impl AzureDetector { .timeout(Duration::from_secs(2)) .build()? .get("http://169.254.169.254/metadata/instance/compute") + .header("Metadata", "true") .query(&[("api-version", "2025-04-07")]) .send()? .error_for_status()? @@ -44,12 +45,20 @@ impl ModuleDetector { path.as_ref().exists() } - fn detect_azure(&self) -> Option<()> { + fn detect_azure(&self) -> bool { log::info!("Trying to detect for Azure..."); - let imds = AzureDetector::fetch_imds().ok()?; + let imds = AzureDetector::fetch_imds(); + + let imds = match imds { + Ok(imds) => imds, + Err(err) => { + log::error!("Got error from imds: {err}"); + return false; + } + }; log::debug!("Got security_type {}", imds.security_profile.security_type); - (imds.security_profile.security_type == "ConfidentialVM").then_some(()) + (imds.security_profile.security_type == "ConfidentialVM") } fn detect_cpu(&self) -> Option { @@ -57,7 +66,7 @@ impl ModuleDetector { Some(CpuModule::Sev) } else if self.path_exists(Self::TDX_PATH) { Some(CpuModule::Tdx) - } else if self.detect_azure().is_some() { + } else if self.detect_azure() { Some(CpuModule::Azure) } else { None From 1d71a87d6a172134c2abafe61a0f1c3bd767cbbb Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:32:24 +0000 Subject: [PATCH 33/39] fix tcti build --- .devcontainer/Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8955b4d6..0568c2fc 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -33,6 +33,7 @@ ENV SYSROOT="/x86_64-sysroot" ENV PKG_CONFIG_ALLOW_CROSS=1 ENV PKG_CONFIG_PATH="${SYSROOT}/lib/pkgconfig:${SYSROOT}/share/pkgconfig" ENV PKG_CONFIG_SYSROOT_DIR="$SYSROOT" +ENV LD_LIBRARY_PATH="${SYSROOT}/lib" RUN wget \ -O/tmp/openssl-${OPENSSL_VER}.tar.gz \ @@ -146,13 +147,15 @@ RUN \ --disable-policy \ --disable-doxygen-doc \ --enable-static \ - --disable-shared \ + --enable-shared \ CC=/usr/bin/${TARGET_TRIPLE}gnu-gcc \ CXX=/usr/bin/${TARGET_TRIPLE}gnu-g++ \ && make -j$(nproc) \ && make install \ && test -f ${SYSROOT}/lib/libtss2-esys.a \ && test -f ${SYSROOT}/lib/pkgconfig/tss2-esys.pc \ + && test -f ${SYSROOT}/lib/libtss2-tcti-device.so \ + && test -f ${SYSROOT}/lib/libtss2-tcti-device.so.0 \ && mv ${SYSROOT}/lib/libtss2-esys.a ${SYSROOT}/lib/libtss2-esys-real.a \ && printf '%s\n' \ 'GROUP ( libtss2-esys-real.a libtss2-sys.a libtss2-mu.a libtss2-tctildr.a libtss2-rc.a )' \ From a2f4e56633b6a12fec32995a1159f1acb1a3829d Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:59:34 +0000 Subject: [PATCH 34/39] add regorus coverage and regex modules --- Cargo.lock | 1 + azure-attest/src/quote/claims.rs | 1 + libattest/Cargo.toml | 2 +- libattest/src/validation.rs | 27 +++++++++++++++++++++++---- reticle/examples/endtoend.rs | 8 +++++++- reticle/src/lib.rs | 1 + snp-attest/src/attestation/kds.rs | 2 +- 7 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7235970..c9d80bd9 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -3017,6 +3017,7 @@ dependencies = [ "parking_lot", "postcard", "rand 0.10.0", + "regex", "serde", "serde_json", "spin 0.10.0", diff --git a/azure-attest/src/quote/claims.rs b/azure-attest/src/quote/claims.rs index 0ee3d418..df91287b 100644 --- a/azure-attest/src/quote/claims.rs +++ b/azure-attest/src/quote/claims.rs @@ -8,6 +8,7 @@ use tpm2_protocol::data::{TpmsAttest, TpmsClockInfo}; use crate::{quote::pcr::PcrBankReading, report::RuntimeClaims}; #[derive(Serialize, Debug)] +#[serde(tag = "type", content = "report")] pub enum HardwareClaims { Sev(Box), Tdx(Box), diff --git a/libattest/Cargo.toml b/libattest/Cargo.toml index 72d94cef..f71142f7 100644 --- a/libattest/Cargo.toml +++ b/libattest/Cargo.toml @@ -8,7 +8,7 @@ getrandom = "0.4.1" hex = "0.4.3" serde = { version = "1.0.228", features = ["derive"] } anyhow = "1.0" -regorus = { git = "https://github.com/microsoft/regorus.git", default-features = false, features = ["arc", "rvm", "std"] } +regorus = { git = "https://github.com/microsoft/regorus.git", default-features = false, features = ["arc", "rvm", "regex", "std", "coverage"] } # serde_json = "1.0.149" serde-value = "0.7.0" x509-cert = "0.3.0-rc.4" diff --git a/libattest/src/validation.rs b/libattest/src/validation.rs index 930888b4..49e0af8f 100644 --- a/libattest/src/validation.rs +++ b/libattest/src/validation.rs @@ -159,6 +159,8 @@ impl Validator { engine.set_input(value); let query = format!("data.{}", claims.policy()); + + engine.set_enable_coverage(true); let result = engine .eval_rule(query) .map_err(AttestationError::from_anyhow) @@ -170,17 +172,34 @@ impl Validator { _ => return AttestationError::internal("rego policy returned a non boolean result"), }; - Ok(ValidationResult(result)) + let res = if result { + ValidationResult::Success + } else { + let coverage = engine + .get_coverage_report() + .and_then(|report| report.to_string_pretty()) + .map_err(AttestationError::from_anyhow)?; + + ValidationResult::Failure { coverage } + }; + + Ok(res) } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] #[must_use] -pub struct ValidationResult(pub bool); +pub enum ValidationResult { + Success, + Failure { coverage: String }, +} impl ValidationResult { pub fn or_err(self, msg: &'static str) -> Result<(), AttestationError> { - self.0.then_some(()).context(msg) + match self { + Self::Success => Ok(()), + Self::Failure { coverage } => Err(AttestationError::new(coverage)).context(msg), + } } } diff --git a/reticle/examples/endtoend.rs b/reticle/examples/endtoend.rs index 5563096d..8166dfc6 100644 --- a/reticle/examples/endtoend.rs +++ b/reticle/examples/endtoend.rs @@ -1,4 +1,5 @@ use reticle::{ClientBuilder, query::QueryParams}; +use snp_attest::kds::Kds; #[tokio::main] async fn main() { @@ -6,7 +7,12 @@ async fn main() { .nth(1) .expect("must supply api url as first argument"); - let client = ClientBuilder::new(&api_url).build().await.unwrap(); + let kds = Kds::default(); + let client = ClientBuilder::new(&api_url) + .with_kds(kds) + .build() + .await + .unwrap(); let result = client.attest().await.unwrap(); println!("{result:?}"); diff --git a/reticle/src/lib.rs b/reticle/src/lib.rs index cd344a08..d065378b 100644 --- a/reticle/src/lib.rs +++ b/reticle/src/lib.rs @@ -286,6 +286,7 @@ impl Client { .await?; let evidence = quote.verify(verifier, &nonce)?; + let claims = WithPolicy::new("azure.allow", evidence); self.policy_validator diff --git a/snp-attest/src/attestation/kds.rs b/snp-attest/src/attestation/kds.rs index ceed6f2e..c29df892 100644 --- a/snp-attest/src/attestation/kds.rs +++ b/snp-attest/src/attestation/kds.rs @@ -86,7 +86,7 @@ impl Kds { )) .query(&query); - let resp = req.send().await?.bytes().await?; + let resp = req.send().await?.error_for_status()?.bytes().await?; Ok(sev::certs::snp::Certificate::from_der(&resp).expect("invalid vcek from AMD KDS")) } From e72369dc9efb5fccf1ddf569fcba62618df4d22d Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:08:34 +0000 Subject: [PATCH 35/39] change default tpm device to /dev/tpmrm0 --- attestation-server/src/azure_api.rs | 2 +- azure-attest/src/bin/generate.rs | 2 +- azure-attest/src/host/vtpm.rs | 17 +++++++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/attestation-server/src/azure_api.rs b/attestation-server/src/azure_api.rs index 811fcbdc..6667940f 100644 --- a/attestation-server/src/azure_api.rs +++ b/attestation-server/src/azure_api.rs @@ -8,7 +8,7 @@ pub async fn azure_attestation( nonce: NonceParam, 32>, ) -> ApiJsonResult { let NonceParam(nonce) = nonce; - let tpm = AzureTpmCtx::default_context().context("failed creating tpm context")?; + let tpm = AzureTpmCtx::default_device().context("failed creating tpm context")?; let nonce = AzureNonce::new(nonce); let quote = azure_attest::host::azure_attest(tpm, &nonce)?; diff --git a/azure-attest/src/bin/generate.rs b/azure-attest/src/bin/generate.rs index dea71ed2..c2158711 100644 --- a/azure-attest/src/bin/generate.rs +++ b/azure-attest/src/bin/generate.rs @@ -1,7 +1,7 @@ use azure_attest::{host::vtpm::AzureTpmCtx, nonce::AzureNonce}; fn main() { - let tpm = AzureTpmCtx::default_context().unwrap(); + let tpm = AzureTpmCtx::default_device().unwrap(); let nonce = AzureNonce::generate(); let attestation = azure_attest::host::azure_attest(tpm, &nonce).unwrap(); diff --git a/azure-attest/src/host/vtpm.rs b/azure-attest/src/host/vtpm.rs index bd320efe..f25b953e 100644 --- a/azure-attest/src/host/vtpm.rs +++ b/azure-attest/src/host/vtpm.rs @@ -1,18 +1,19 @@ -use std::ops::Deref; +use std::{ops::Deref, str::FromStr}; use libattest::error::Context; use rsa::{BoxedUint, pkcs1v15::VerifyingKey}; use sha2::Sha256; use tss_esapi::{ - abstraction::{nv, pcr::PcrData}, + abstraction::nv, handles::{KeyHandle, NvIndexTpmHandle}, interface_types::{ algorithm::HashingAlgorithm, resource_handles::NvAuth, session_handles::AuthSession, }, structures::{ Attest, PcrSelectSize, PcrSelectionListBuilder, PcrSlot, Public, RsaExponent, Signature, - SignatureScheme, pcr_selection_list, + SignatureScheme, }, + tcti_ldr::DeviceConfig, }; use x509_cert::der::{Decode, SliceReader}; @@ -28,17 +29,21 @@ impl AzureTpmCtx { Self { context } } - pub fn default_context() -> tss_esapi::Result { - use tss_esapi::tcti_ldr::DeviceConfig; + pub fn with_device(device: &'static str) -> tss_esapi::Result { use tss_esapi::tcti_ldr::TctiNameConf; - let mut context = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?; + let mut context = + tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::from_str(device)?))?; context.set_sessions((Some(AuthSession::Password), None, None)); Ok(Self { context }) } + pub fn default_device() -> tss_esapi::Result { + Self::with_device("/dev/tpmrm0") + } + const AK_CERT_IDX: u32 = 0x01C101D0; const VTPM_HCL_AKPUB_PERSISTENT_HANDLE: u32 = 0x81000003; const HARDWARE_REPORT_IDX: u32 = 0x01400001; From 40467588cf9b2a389f3968114355072fb73629e0 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:19:47 +0000 Subject: [PATCH 36/39] tpm detection and startup fairing --- attestation-server/src/azure_api.rs | 83 +++++++++++++++++++++++++++- attestation-server/src/main.rs | 14 ++++- attestation-server/src/nvidia_api.rs | 8 +-- azure-attest/src/host/vtpm.rs | 5 +- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/attestation-server/src/azure_api.rs b/attestation-server/src/azure_api.rs index 6667940f..c61a5125 100644 --- a/attestation-server/src/azure_api.rs +++ b/attestation-server/src/azure_api.rs @@ -1,16 +1,95 @@ -use anyhow::Context; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; + use azure_attest::{AzureQuote, host::vtpm::AzureTpmCtx, nonce::AzureNonce}; +use rocket::{ + Build, Rocket, State, + fairing::{Fairing, Kind}, +}; use crate::{nonce::NonceParam, response::ApiJsonResult}; +#[derive(Clone)] +pub struct TpmDevice { + tpm_path: Cow<'static, Path>, +} + +impl TpmDevice { + const TPM_PATH: &'static str = "/dev/tpm0"; + const TPMRM_PATH: &'static str = "/dev/tpmrm0"; + + pub fn with_path(device: impl Into) -> Self { + Self { + tpm_path: device.into().into(), + } + } + + pub fn auto_detect() -> anyhow::Result { + let tpm_path = Path::new(Self::TPM_PATH); + let tpmrm_path = Path::new(Self::TPMRM_PATH); + + let tpm_path = if tpmrm_path.exists() { + tpmrm_path.into() + } else if tpm_path.exists() { + tpm_path.into() + } else { + anyhow::bail!("Could not find a tpm device at /dev/tpm0 or /dev/tpmrm0"); + }; + + Ok(Self { tpm_path }) + } +} + +impl TpmDevice { + pub fn create_context(&self) -> anyhow::Result { + let ctx = AzureTpmCtx::with_device(&self.tpm_path)?; + Ok(ctx) + } +} + +pub struct AzureFairing { + device: TpmDevice, +} + +impl AzureFairing { + pub fn new(device: TpmDevice) -> Self { + Self { device } + } +} + +#[rocket::async_trait] +impl Fairing for AzureFairing { + fn info(&self) -> rocket::fairing::Info { + rocket::fairing::Info { + name: "Azure TPM Fairing", + kind: Kind::Ignite, + } + } + + async fn on_ignite(&self, rocket: Rocket) -> Result, Rocket> { + let res_context = self.device.create_context(); + if let Err(error) = res_context { + let device = self.device.tpm_path.as_ref(); + log::error!("Creating a tpm context with [{device:?}] failed with error: {error}"); + + return Err(rocket); + } + + Ok(rocket.manage(self.device.clone())) + } +} + #[rocket::get("/azure?")] pub async fn azure_attestation( nonce: NonceParam, 32>, + tpm: &State, ) -> ApiJsonResult { let NonceParam(nonce) = nonce; - let tpm = AzureTpmCtx::default_device().context("failed creating tpm context")?; let nonce = AzureNonce::new(nonce); + let tpm = tpm.create_context()?; let quote = azure_attest::host::azure_attest(tpm, &nonce)?; Ok(quote.into()) diff --git a/attestation-server/src/main.rs b/attestation-server/src/main.rs index 1773237c..272716a5 100644 --- a/attestation-server/src/main.rs +++ b/attestation-server/src/main.rs @@ -17,7 +17,12 @@ use rocket::{State, routes}; use sev::firmware::guest::Firmware; use tokio::sync::Mutex; -use crate::{modules::ModuleDetector, nvidia_api::SdkFairing, response::ApiJsonResult}; +use crate::{ + azure_api::{AzureFairing, TpmDevice}, + modules::ModuleDetector, + nvidia_api::NvidiaFairing, + response::ApiJsonResult, +}; #[rocket::get("/modules")] fn get_modules(modules: &State) -> ApiJsonResult<&Modules> { @@ -54,8 +59,11 @@ async fn main() -> Result<(), anyhow::Error> { rocket } CpuModule::Azure => { + let tpm = TpmDevice::auto_detect().context("could not detect default tpm device")?; + let tpm = AzureFairing::new(tpm); + routes.extend(routes![azure_api::azure_attestation]); - rocket + rocket.attach(tpm) } _ => bail!("cpu module not yet supported by attestation-server"), }; @@ -63,7 +71,7 @@ async fn main() -> Result<(), anyhow::Error> { if let Some(GpuModule::Nvidia) = modules.gpu() { // attach the nvidia fairing responsible for first attestation // and enable gpus for confidential computing operations - let sdk = SdkFairing::init()?; + let sdk = NvidiaFairing::init()?; rocket = rocket.attach(sdk); routes.extend(routes![nvidia_api::nvidia_attestation]); diff --git a/attestation-server/src/nvidia_api.rs b/attestation-server/src/nvidia_api.rs index a51109fa..e83b0e4b 100644 --- a/attestation-server/src/nvidia_api.rs +++ b/attestation-server/src/nvidia_api.rs @@ -13,17 +13,17 @@ use rocket::{Build, Rocket, State}; use crate::nonce::NonceParam; use crate::response::ApiError; -pub struct SdkFairing { +pub struct NvidiaFairing { handle: SdkHandle, nvml: Nvml, } -impl SdkFairing { +impl NvidiaFairing { pub fn init() -> anyhow::Result { let handle = SdkHandle::get_handle()?; let nvml = Nvml::init()?; - Ok(SdkFairing { handle, nvml }) + Ok(NvidiaFairing { handle, nvml }) } async fn attest_and_init(&self) -> anyhow::Result<()> { @@ -79,7 +79,7 @@ impl SdkFairing { } #[rocket::async_trait] -impl Fairing for SdkFairing { +impl Fairing for NvidiaFairing { fn info(&self) -> rocket::fairing::Info { Info { name: "Nvidia SDK initializer", diff --git a/azure-attest/src/host/vtpm.rs b/azure-attest/src/host/vtpm.rs index f25b953e..bb54dc38 100644 --- a/azure-attest/src/host/vtpm.rs +++ b/azure-attest/src/host/vtpm.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, str::FromStr}; +use std::{ops::Deref, path::Path, str::FromStr}; use libattest::error::Context; use rsa::{BoxedUint, pkcs1v15::VerifyingKey}; @@ -29,9 +29,10 @@ impl AzureTpmCtx { Self { context } } - pub fn with_device(device: &'static str) -> tss_esapi::Result { + pub fn with_device(device: impl AsRef) -> tss_esapi::Result { use tss_esapi::tcti_ldr::TctiNameConf; + let device = device.as_ref().to_str().unwrap(); let mut context = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::from_str(device)?))?; From 88eeb48945b24b942f4b7f285c335b2c8d51973e Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:20:04 +0000 Subject: [PATCH 37/39] verify that quote contains right banks --- azure-attest/src/quote/pcr.rs | 2 +- azure-attest/src/quote/verify.rs | 41 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/azure-attest/src/quote/pcr.rs b/azure-attest/src/quote/pcr.rs index 21b11ef3..1f148d5e 100644 --- a/azure-attest/src/quote/pcr.rs +++ b/azure-attest/src/quote/pcr.rs @@ -65,5 +65,5 @@ impl From<&PcrBank> for PcrBankReading { #[cfg(feature = "host")] fn pcr_slot_to_number(slot: PcrSlot) -> u32 { let slot: u32 = slot.into(); - slot.ilog2() + 1 + slot.ilog2() } diff --git a/azure-attest/src/quote/verify.rs b/azure-attest/src/quote/verify.rs index fa867378..3030aefb 100644 --- a/azure-attest/src/quote/verify.rs +++ b/azure-attest/src/quote/verify.rs @@ -14,7 +14,7 @@ use libattest::{ }; use rsa::{BoxedUint, pkcs1::DecodeRsaPublicKey, pkcs1v15::VerifyingKey, signature::Verifier}; use sha2::Sha256; -use tpm2_protocol::data::TpmuAttest; +use tpm2_protocol::data::{TpmAlgId, TpmuAttest}; use x509_cert::der::oid::db::rfc9688::RSA_ENCRYPTION; use crate::{ @@ -193,10 +193,13 @@ fn verify_quote_nonce(azure_quote: &AzureQuote, nonce: &AzureNonce) -> libattest fn verify_pcr_digest(azure_quote: &AzureQuote) -> libattest::Result<()> { let digest = azure_quote.pcr_bank.pcr_digest(); + let TpmuAttest::Quote(ref info) = azure_quote.quote.attested else { libattest::bail!("wrong type of attested data in quote"); }; + verify_pcr_selection(info)?; + if info.pcr_digest.deref() != digest.deref() { libattest::bail!(exposed: "PCR digest mismatch"); } @@ -204,6 +207,25 @@ fn verify_pcr_digest(azure_quote: &AzureQuote) -> libattest::Result<()> { Ok(()) } +fn verify_pcr_selection(info: &tpm2_protocol::data::TpmsQuoteInfo) -> libattest::Result<()> { + const EXPECTED_PCR_SELECT: &[u8] = &[0xff, 0xff, 0xff]; + + if info.pcr_select.len() != 1 { + libattest::bail!(exposed: "TPM quote must contain exactly one PCR selection"); + } + + let selection = &info.pcr_select[0]; + if selection.hash != TpmAlgId::Sha256 { + libattest::bail!(exposed: "TPM quote PCR selection must use SHA-256"); + } + + if selection.pcr_select.deref() != EXPECTED_PCR_SELECT { + libattest::bail!(exposed: "TPM quote must select PCRs 0 through 23"); + } + + Ok(()) +} + pub fn verify_impl<'a>( azure_quote: &'a AzureQuote, report_verifier: ReportVerifier, @@ -237,3 +259,20 @@ pub fn verify_impl<'a>( Ok(azure_claims) } + +#[cfg(test)] +mod tests { + use super::*; + + const ATTESTATION: &str = include_str!("../../tests/attestation.json"); + + #[test] + fn bundled_quote_selects_sha256_pcrs_0_through_23() { + let quote: AzureQuote = serde_json::from_str(ATTESTATION).unwrap(); + let TpmuAttest::Quote(ref info) = quote.quote.attested else { + panic!("bundled attestation is not a TPM quote"); + }; + + verify_pcr_selection(info).unwrap(); + } +} From 4972868ecc3e628d7945c05a9f19b708218a41d1 Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:08:43 +0000 Subject: [PATCH 38/39] bump major version fof reticle to 0.5.0 --- Cargo.lock | 2 +- reticle/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9d80bd9..6a63c599 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -3111,7 +3111,7 @@ dependencies = [ [[package]] name = "reticle" -version = "0.4.8" +version = "0.5.0" dependencies = [ "async-once-cell", "azure-attest", diff --git a/reticle/Cargo.toml b/reticle/Cargo.toml index 0c78d7e1..b873dc30 100755 --- a/reticle/Cargo.toml +++ b/reticle/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reticle" -version = "0.4.8" +version = "0.5.0" edition = "2024" [lib] From 2e31f3988b258c00c6fec9fc1d2efab1e7df14df Mon Sep 17 00:00:00 2001 From: Pietro T <17928339+BRA1L0R@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:19:35 +0000 Subject: [PATCH 39/39] fix attestation-server --- attestation-server/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/attestation-server/src/main.rs b/attestation-server/src/main.rs index 5e82b8b2..6e089419 100644 --- a/attestation-server/src/main.rs +++ b/attestation-server/src/main.rs @@ -29,6 +29,11 @@ fn get_modules(modules: &State) -> ApiJsonResult<&Modules> { response::ok(modules.deref()) } +#[catch(404)] +fn not_found() -> &'static str { + "route not found" +} + #[tokio::main] async fn main() -> Result<(), anyhow::Error> { env_logger::builder()