diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/az-cvm-vtpm/Cargo.toml b/az-cvm-vtpm/Cargo.toml index 5405ed8..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" @@ -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] 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-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/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 diff --git a/az-cvm-vtpm/src/vtpm/mod.rs b/az-cvm-vtpm/src/vtpm/mod.rs index 6cdef21..5f577dd 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; @@ -101,6 +102,7 @@ pub fn get_ak_pub() -> Result { Ok(pkey) } +#[non_exhaustive] #[derive(Error, Debug)] pub enum QuoteError { #[error("tpm error")] @@ -111,12 +113,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 { @@ -126,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 @@ -152,8 +164,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,18 +181,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)?; -#[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, + 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..454060f 100644 --- a/az-cvm-vtpm/src/vtpm/verify.rs +++ b/az-cvm-vtpm/src/vtpm/verify.rs @@ -5,8 +5,12 @@ 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; +#[non_exhaustive] #[derive(Error, Debug)] pub enum VerifyError { #[error("tss error")] @@ -19,6 +23,8 @@ pub enum VerifyError { NonceMismatch, #[error("quote error")] Quote(#[from] QuoteError), + #[error("pcr mismatch")] + PcrMismatch, } impl Quote { @@ -36,6 +42,9 @@ impl Quote { if nonce != quote_nonce { return Err(VerifyError::NonceMismatch); } + + self.verify_pcrs()?; + Ok(()) } @@ -53,29 +62,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 +156,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 0000000..41c8937 Binary files /dev/null and b/az-cvm-vtpm/test/quote.bin differ diff --git a/az-cvm-vtpm/test/quote_msg b/az-cvm-vtpm/test/quote_msg deleted file mode 100644 index 943b8bc..0000000 Binary files a/az-cvm-vtpm/test/quote_msg and /dev/null differ diff --git a/az-cvm-vtpm/test/quote_sig b/az-cvm-vtpm/test/quote_sig deleted file mode 100644 index 6bb6b86..0000000 Binary files a/az-cvm-vtpm/test/quote_sig and /dev/null differ