From ce245993c6fe442c002ddcf1b616cf95aa3519ec Mon Sep 17 00:00:00 2001 From: Suraj Deshmukh Date: Thu, 4 Jan 2024 11:02:29 +0000 Subject: [PATCH 1/7] Add .gitignore at root level Ignore .vscode directory. Signed-off-by: Suraj Deshmukh --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode From 217b6434222aa87810760f8f6699c5f530ceaf54 Mon Sep 17 00:00:00 2001 From: Suraj Deshmukh Date: Mon, 15 Jan 2024 13:09:17 +0000 Subject: [PATCH 2/7] refactor(vtpm): Remove unused VerifyError This commit removes unused code in the `vtpm` module. This enum is defined elaborately in `verfiy.rs`. Signed-off-by: Suraj Deshmukh --- az-cvm-vtpm/src/vtpm/mod.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/az-cvm-vtpm/src/vtpm/mod.rs b/az-cvm-vtpm/src/vtpm/mod.rs index 6cdef21..da63f88 100644 --- a/az-cvm-vtpm/src/vtpm/mod.rs +++ b/az-cvm-vtpm/src/vtpm/mod.rs @@ -167,16 +167,3 @@ pub fn get_quote(data: &[u8]) -> Result { Ok(Quote { signature, message }) } - -#[cfg(feature = "verifier")] -#[derive(Error, Debug)] -pub enum VerifyError { - #[error("tss error")] - Tss(#[from] tss_esapi::Error), - #[error("openssl error")] - OpenSsl(#[from] openssl::error::ErrorStack), - #[error("nonce mismatch")] - NonceMismatch, - #[error("quote is not signed by the public key")] - SignatureMismatch, -} From 5115d80c7e715f23bbfe86ccef7e39515b152c7b Mon Sep 17 00:00:00 2001 From: Suraj Deshmukh Date: Mon, 15 Jan 2024 13:24:20 +0000 Subject: [PATCH 3/7] deps: Update `rsa` crate to 0.9.6 - Also fix the formatting from spaces to tabs. Signed-off-by: Suraj Deshmukh --- az-cvm-vtpm/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/az-cvm-vtpm/Cargo.toml b/az-cvm-vtpm/Cargo.toml index 4f8a839..9e95b2c 100644 --- a/az-cvm-vtpm/Cargo.toml +++ b/az-cvm-vtpm/Cargo.toml @@ -12,7 +12,7 @@ description = "Package with shared code for Azure Confidential VMs" members = [ "az-snp-vtpm", "az-tdx-vtpm", - "az-snp-vtpm/example", + "az-snp-vtpm/example", ] [lib] @@ -23,7 +23,7 @@ bincode.workspace = true jsonwebkey = { version = "0.3.5", features = ["pkcs-convert"] } memoffset = "0.9.0" openssl = { workspace = true, optional = true } -rsa = { version = "0.8.2", features = ["pkcs5", "sha2"] } +rsa = { version = "0.9.6", features = ["pkcs5", "sha2"] } serde.workspace = true serde_json.workspace = true serde-big-array = "0.5.1" From 0bbf708859d0800954dfb999f2a353d656f67600 Mon Sep 17 00:00:00 2001 From: Suraj Deshmukh Date: Mon, 15 Jan 2024 15:04:33 +0000 Subject: [PATCH 4/7] vtpm: Add support to include PCR values This commit add support to include PCR SHA256 bank values in the Quote struct. Here are details of minor changes as a part of this commit: - Make the Quote parameters private. - Add a field called `pcrs` to Quote which will hold the PCR SHA256 bank values. - Add verification logic to the see if hash of all the PCRs match the PCR digest from the message. - Add test for verify_pcrs - Update the existing test to read the whole quote instead of individual message and signature from the fixutres. - Generate new fixtures for the test so that the PCR values are also incorporated. Signed-off-by: Suraj Deshmukh --- az-cvm-vtpm/src/vtpm/mod.rs | 36 ++++++++++++--- az-cvm-vtpm/src/vtpm/verify.rs | 79 ++++++++++++++++++++++++++++++--- az-cvm-vtpm/test/akpub.pem | 14 +++--- az-cvm-vtpm/test/quote.bin | Bin 0 -> 1362 bytes az-cvm-vtpm/test/quote_msg | Bin 116 -> 0 bytes az-cvm-vtpm/test/quote_sig | Bin 256 -> 0 bytes 6 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 az-cvm-vtpm/test/quote.bin delete mode 100644 az-cvm-vtpm/test/quote_msg delete mode 100644 az-cvm-vtpm/test/quote_sig diff --git a/az-cvm-vtpm/src/vtpm/mod.rs b/az-cvm-vtpm/src/vtpm/mod.rs index da63f88..fb2e729 100644 --- a/az-cvm-vtpm/src/vtpm/mod.rs +++ b/az-cvm-vtpm/src/vtpm/mod.rs @@ -5,6 +5,7 @@ use rsa::{BigUint, RsaPublicKey}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tss_esapi::abstraction::nv; +use tss_esapi::abstraction::pcr; use tss_esapi::abstraction::public::DecodedKey; use tss_esapi::handles::TpmHandle; use tss_esapi::interface_types::algorithm::HashingAlgorithm; @@ -111,12 +112,17 @@ pub enum QuoteError { NotAQuote, #[error("Wrong signature, that should not occur")] WrongSignature, + #[error("PCR bank not found")] + PcrBankNotFound, + #[error("PCR reading error")] + PcrRead, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Quote { - pub signature: Vec, - pub message: Vec, + signature: Vec, + message: Vec, + pcrs: Vec>, } impl Quote { @@ -152,8 +158,12 @@ pub fn get_quote(data: &[u8]) -> Result { let auth_session = AuthSession::Password; context.set_sessions((Some(auth_session), None, None)); - let (attest, signature) = - context.quote(key_handle.into(), quote_data, scheme, selection_list)?; + let (attest, signature) = context.quote( + key_handle.into(), + quote_data, + scheme, + selection_list.clone(), + )?; let AttestInfo::Quote { .. } = attest.attested() else { return Err(QuoteError::NotAQuote); @@ -165,5 +175,21 @@ pub fn get_quote(data: &[u8]) -> Result { let signature = rsa_sig.signature().to_vec(); let message = attest.marshall()?; - Ok(Quote { signature, message }) + context.clear_sessions(); + let pcr_data = pcr::read_all(&mut context, selection_list)?; + + let pcr_bank = pcr_data + .pcr_bank(hash_algo) + .ok_or(QuoteError::PcrBankNotFound)?; + + let pcrs = pcr_bank + .into_iter() + .map(|(_, x)| x.value().to_vec()) + .collect(); + + Ok(Quote { + signature, + message, + pcrs, + }) } diff --git a/az-cvm-vtpm/src/vtpm/verify.rs b/az-cvm-vtpm/src/vtpm/verify.rs index f5cd04b..8a28c16 100644 --- a/az-cvm-vtpm/src/vtpm/verify.rs +++ b/az-cvm-vtpm/src/vtpm/verify.rs @@ -5,7 +5,10 @@ use super::{Quote, QuoteError}; use openssl::hash::MessageDigest; use openssl::pkey::{PKey, Public}; use openssl::sign::Verifier; +use sha2::{Digest, Sha256}; use thiserror::Error; +use tss_esapi::structures::{Attest, AttestInfo}; +use tss_esapi::traits::UnMarshall; #[derive(Error, Debug)] pub enum VerifyError { @@ -19,6 +22,8 @@ pub enum VerifyError { NonceMismatch, #[error("quote error")] Quote(#[from] QuoteError), + #[error("pcr mismatch")] + PcrMismatch, } impl Quote { @@ -36,6 +41,9 @@ impl Quote { if nonce != quote_nonce { return Err(VerifyError::NonceMismatch); } + + self.verify_pcrs()?; + Ok(()) } @@ -53,29 +61,78 @@ impl Quote { } Ok(()) } + + /// Verify that the TPM Quote's PCR digest matches the digest of the bundled PCR values + /// + pub fn verify_pcrs(&self) -> Result<(), VerifyError> { + let attest = Attest::unmarshall(&self.message)?; + let AttestInfo::Quote { info } = attest.attested() else { + return Err(VerifyError::Quote(QuoteError::NotAQuote)); + }; + + let pcr_digest = info.pcr_digest(); + + // Read hashes of all the PCRs. + let mut hasher = Sha256::new(); + for pcr in self.pcrs.iter() { + hasher.update(pcr); + } + + let digest = hasher.finalize(); + if digest[..] != pcr_digest[..] { + return Err(VerifyError::PcrMismatch); + } + + Ok(()) + } } #[cfg(test)] mod tests { use super::*; + // // Use this code to generate the scriptures for the test on an AMD CVM. + // + // use az_snp_vtpm::vtpm; + // use bincode; + // use rsa; + // use rsa::pkcs8::EncodePublicKey; + // use std::error::Error; + // use std::fs; + // + // fn main() -> Result<(), Box> { + // // Extract the AK public key. + // let foo = vtpm::get_ak_pub()?.to_public_key_pem(rsa::pkcs8::LineEnding::LF)?; + // fs::write("/tmp/akpub.pem", foo)?; + // + // // Save the PCRs into binary file. + // let nonce = "challenge".as_bytes().to_vec(); + // let quote = vtpm::get_quote(&nonce)?; + // let quote_encoded: Vec = bincode::serialize("e).unwrap(); + // fs::write("/tmp/quote.bin", quote_encoded)?; + // + // Ok(()) + // } + #[cfg(feature = "verifier")] #[test] fn test_quote_validation() { // Can be retrieved by `get_ak_pub()` or via tpm2-tools: - // `tpm2_readpublic -c 0x81000003 -f pem -o akpub.pem` - + // sudo tpm2_readpublic -c 0x81000003 -f pem -o akpub.pem let pem = include_bytes!("../../test/akpub.pem"); let pkey = PKey::public_key_from_pem(pem).unwrap(); // Can be retrieved by `get_quote()` or via tpm2-tools: - // `tpm2_quote -c 0x81000003 -l sha256:5,8 -q cafe -m quote_msg -s quote_sig` - let message = include_bytes!("../../test/quote_msg").to_vec(); - let signature = include_bytes!("../../test/quote_sig").to_vec(); - let quote = Quote { signature, message }; + // For message and signature: + // sudo tpm2_quote -c 0x81000003 -l sha256:5,8 -q challenge -m quote_msg -s quote_sig + // + // For PCR values: + // sudo tpm2_pcrread sha256:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 + let quote_bytes = include_bytes!("../../test/quote.bin"); + let quote: Quote = bincode::deserialize(quote_bytes).unwrap(); // proper nonce in message - let nonce = vec![1, 2, 3]; + let nonce = "challenge".as_bytes().to_vec(); let result = quote.verify(&pkey, &nonce); assert!(result.is_ok(), "Quote verification should not fail"); @@ -98,4 +155,12 @@ mod tests { "Expected nonce verification error" ); } + + #[test] + fn test_pcr_values() { + let quote_bytes = include_bytes!("../../test/quote.bin"); + let quote: Quote = bincode::deserialize(quote_bytes).unwrap(); + let result = quote.verify_pcrs(); + assert!(result.is_ok(), "PCR verification should not fail"); + } } diff --git a/az-cvm-vtpm/test/akpub.pem b/az-cvm-vtpm/test/akpub.pem index 6ce0594..7cab27d 100644 --- a/az-cvm-vtpm/test/akpub.pem +++ b/az-cvm-vtpm/test/akpub.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxfSaJAABoi7dSNwLgxab -Qj0Ag+3u74ioHzP/JKk7urkxwFyPN95+ofKhBIp63mfOxTVIfjPFhiYhnYGKJvQY -drTg1slNSIR5MRcjwDhHlTwK4BefiwcIiQMsEwdjbWEcHVfKIjrIFLX6HgXwftGU -mdItDBJuZaGT08F1kGMz7K6hH1ZjQBKnwmGih5p/P4pBDD4ccNapUtaIraCgQ+4f -YguTMACZAl7ZZxISS1yxxudHbJ8cI7viijk1TmuauJN+GAn7hkEauOdWd6xpv8jf -NHEm24kTh/TgNhTlyZpfafEbwXNVNVk7TZ3HRNl0Uou4FNCQw8eb+PG3q/IxMv7h -oQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxJlHggAAGWfX9uqSq3js +wJ9PGrEGyurECyTMfptLwI5Ca1JEwocKXHsTfdAEUVIi9GVWcNuBGpr5Dbd8reoE +l6/p5IoxQsXyPSC6LZ7HdisORYOo8tQU/fqcuRky1InLJnsKG0o91XEP1MBo5/J7 +MxUAkkWPOiA6wPo+k7Wo3X3TB1NxxqohqAN+sRQ3Useqlzg7sViw+us0nrPb5gbz +1M8PMlLj4UW6j2j+XNQMsPtZEJ5qAwOmtqstFqT16qBkqFd/ey+NQBNINQAYlaHT +Vh2cwzq17i2Cru0KSHGQVa2YcUPZhDu4eAQdy+fdVE/uTjxf7Sac5WXefK2YXxyw +VQIDAQAB -----END PUBLIC KEY----- diff --git a/az-cvm-vtpm/test/quote.bin b/az-cvm-vtpm/test/quote.bin new file mode 100644 index 0000000000000000000000000000000000000000..41c893751118daf5defeb19b354e967d8762178b GIT binary patch literal 1362 zcmZQzWB`NSi4n5@@)xIVkiB!<5XOhTV~e@21^v z@fP`^9r=1`^1VfwM|Zg$oUpYD5)2IgL!8|kBp8$!xP`nV_A^~rmb9d{b8Admmct$m zODVQZ994>{JC*()V>ryfnVgZBlard4o(j^-Csw@?NO6GJj0y_OLJUF-%pf)cH}n7h z{}~k0!*)(S?Q1mgRzT#dB52ns1K9q05nnnH^TF;qVm;0LU%)a;_ z`gCgaH331MrZ2z$z|`5g_WTPy!~9C##a=XN-<#gAGNli_=AWNcWU|m+vad3eRCV*k zJ?(^LzD1tsWXW4KvDu+6L02g8^NSCkH)_n@r7O+?Gtam^Tk4oVIluO9BPG`9E3|`e zIBmFT`#bo(|Lf_2vR;fZb>@yz@-16M#bTn2Pexl_nsw|^`eNe)B_~Uwg`a+V#&ZFt z?sbN({ZpN|%~r`whgW3m6o0*WxeWh|eP_?yUU>S1Q}1e+ItBs&rY`4Oacb7iyGciF zn?Ihg1kKd0AosOEeJ%6NRcib=DSIX0AYPS5%H5I@9+;0`ETI_m@RP!Fs zl&-z&zOQalQC72VM#)i=*+JVofAQ|v8gXp?E6s2}Qq3dQAHc+o7r^Yrs|YALRMZg) F7XXMxFo6I7 literal 0 HcmV?d00001 diff --git a/az-cvm-vtpm/test/quote_msg b/az-cvm-vtpm/test/quote_msg deleted file mode 100644 index 943b8bce51c65396d90e9384dbbbf7d92f57da96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116 zcmew#;_Tia!Jx#zebhbw`<6;8uZn3mc3hBG&D&es|6n@9#dE*ATXyPAcTZqoW@KV! z00OSr5k^1?%w|+jU>0H!0&^G{xS1Im92pb>&;Av4vnteG+~fJulDAF3;8D-VkMs7; NkLu23TzNm`4*<;+CdU8( diff --git a/az-cvm-vtpm/test/quote_sig b/az-cvm-vtpm/test/quote_sig deleted file mode 100644 index 6bb6b865c307f6d15c2b05e9ee9e7ee51107cd00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssC4u`@|ny{iM21ppO>6N!)r=9I;cmWHFu?w#{kDB==Uc^XlvIMzJp@SyT! zseOms^^I}LtEp1=POs*&5X7nyhgcsqZiZG70)<(d&4K7i;R2mmM~N> zr77Tvc)|YZw8bYLc=z#g0~{?E7;z6NO+8*!bbA2D5CVHpd0FaKB9fOvc?i7W`_E#M z-}#*UH137-t Date: Mon, 15 Jan 2024 15:07:56 +0000 Subject: [PATCH 5/7] vtpm: Make QuoteError and VerifyError non_exhaustive Make these enums non-exhaustive so that we can make non-breaking changes in subsequent updates. Signed-off-by: Suraj Deshmukh --- az-cvm-vtpm/src/vtpm/mod.rs | 1 + az-cvm-vtpm/src/vtpm/verify.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/az-cvm-vtpm/src/vtpm/mod.rs b/az-cvm-vtpm/src/vtpm/mod.rs index fb2e729..8b90fd3 100644 --- a/az-cvm-vtpm/src/vtpm/mod.rs +++ b/az-cvm-vtpm/src/vtpm/mod.rs @@ -102,6 +102,7 @@ pub fn get_ak_pub() -> Result { Ok(pkey) } +#[non_exhaustive] #[derive(Error, Debug)] pub enum QuoteError { #[error("tpm error")] diff --git a/az-cvm-vtpm/src/vtpm/verify.rs b/az-cvm-vtpm/src/vtpm/verify.rs index 8a28c16..454060f 100644 --- a/az-cvm-vtpm/src/vtpm/verify.rs +++ b/az-cvm-vtpm/src/vtpm/verify.rs @@ -10,6 +10,7 @@ use thiserror::Error; use tss_esapi::structures::{Attest, AttestInfo}; use tss_esapi::traits::UnMarshall; +#[non_exhaustive] #[derive(Error, Debug)] pub enum VerifyError { #[error("tss error")] From a2ad73d15563000560078963a5c272896f279e4a Mon Sep 17 00:00:00 2001 From: Suraj Deshmukh Date: Tue, 16 Jan 2024 03:32:33 +0000 Subject: [PATCH 6/7] vtpm: Add a getter method for `message` Since the `Quote` struct's fields were made private, to access the field `message` this commit adds a getter method `message()`. Signed-off-by: Suraj Deshmukh --- az-cvm-vtpm/az-snp-vtpm/src/main.rs | 2 +- az-cvm-vtpm/src/vtpm/mod.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/az-cvm-vtpm/az-snp-vtpm/src/main.rs b/az-cvm-vtpm/az-snp-vtpm/src/main.rs index 564f840..9005c4e 100644 --- a/az-cvm-vtpm/az-snp-vtpm/src/main.rs +++ b/az-cvm-vtpm/az-snp-vtpm/src/main.rs @@ -74,7 +74,7 @@ fn main() -> Result<(), Box> { Action::Quote { nonce } => { println!("quote byte size: {}", nonce.as_bytes().len()); let quote = vtpm::get_quote(nonce.as_bytes())?; - println!("{:02X?}", quote.message); + println!("{:02X?}", quote.message()); } } diff --git a/az-cvm-vtpm/src/vtpm/mod.rs b/az-cvm-vtpm/src/vtpm/mod.rs index 8b90fd3..5f577dd 100644 --- a/az-cvm-vtpm/src/vtpm/mod.rs +++ b/az-cvm-vtpm/src/vtpm/mod.rs @@ -133,6 +133,11 @@ impl Quote { let nonce = attest.extra_data().to_vec(); Ok(nonce) } + + /// Extract message from a Quote + pub fn message(&self) -> Vec { + self.message.clone() + } } /// Get a signed vTPM Quote From ba22c78c11e2ef29c076141de015ad97b7f885b1 Mon Sep 17 00:00:00 2001 From: Suraj Deshmukh Date: Mon, 15 Jan 2024 15:08:58 +0000 Subject: [PATCH 7/7] version: Bump the crate to 0.5.0 Signed-off-by: Suraj Deshmukh --- az-cvm-vtpm/Cargo.toml | 2 +- az-cvm-vtpm/az-snp-vtpm/Cargo.toml | 4 ++-- az-cvm-vtpm/az-tdx-vtpm/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/az-cvm-vtpm/Cargo.toml b/az-cvm-vtpm/Cargo.toml index 9e95b2c..ae07219 100644 --- a/az-cvm-vtpm/Cargo.toml +++ b/az-cvm-vtpm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "az-cvm-vtpm" -version = "0.4.1" +version = "0.5.0" edition = "2021" repository = "https://github.com/kinvolk/azure-cvm-tooling/" license = "MIT" diff --git a/az-cvm-vtpm/az-snp-vtpm/Cargo.toml b/az-cvm-vtpm/az-snp-vtpm/Cargo.toml index 47f3fae..c04f9a1 100644 --- a/az-cvm-vtpm/az-snp-vtpm/Cargo.toml +++ b/az-cvm-vtpm/az-snp-vtpm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "az-snp-vtpm" -version = "0.4.1" +version = "0.5.0" edition = "2021" repository = "https://github.com/kinvolk/azure-cvm-tooling/" license = "MIT" @@ -17,7 +17,7 @@ path = "src/main.rs" required-features = ["attester", "verifier"] [dependencies] -az-cvm-vtpm = { path = "..", version = "0.4.1" } +az-cvm-vtpm = { path = "..", version = "0.5.0" } bincode.workspace = true clap.workspace = true openssl = { workspace = true, optional = true } diff --git a/az-cvm-vtpm/az-tdx-vtpm/Cargo.toml b/az-cvm-vtpm/az-tdx-vtpm/Cargo.toml index 2fa78c3..52bb46a 100644 --- a/az-cvm-vtpm/az-tdx-vtpm/Cargo.toml +++ b/az-cvm-vtpm/az-tdx-vtpm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "az-tdx-vtpm" -version = "0.4.1" +version = "0.5.0" edition = "2021" repository = "https://github.com/kinvolk/azure-cvm-tooling/" license = "MIT" @@ -16,7 +16,7 @@ name = "tdx-vtpm" path = "src/main.rs" [dependencies] -az-cvm-vtpm = { path = "..", version = "0.4.1" } +az-cvm-vtpm = { path = "..", version = "0.5.0" } base64-url = "2.0.0" bincode.workspace = true serde.workspace = true