Skip to content

Commit

Permalink
Merge pull request #47 from surajssd/hash-extraction-api
Browse files Browse the repository at this point in the history
vtpm: Add support to include PCR values
  • Loading branch information
surajssd authored Jan 16, 2024
2 parents b353d34 + ba22c78 commit 29874be
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 38 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode
4 changes: 2 additions & 2 deletions az-cvm-vtpm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions az-cvm-vtpm/az-snp-vtpm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion az-cvm-vtpm/az-snp-vtpm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fn main() -> Result<(), Box<dyn Error>> {
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());
}
}

Expand Down
4 changes: 2 additions & 2 deletions az-cvm-vtpm/az-tdx-vtpm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down
53 changes: 36 additions & 17 deletions az-cvm-vtpm/src/vtpm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -101,6 +102,7 @@ pub fn get_ak_pub() -> Result<RsaPublicKey, AKPubError> {
Ok(pkey)
}

#[non_exhaustive]
#[derive(Error, Debug)]
pub enum QuoteError {
#[error("tpm error")]
Expand All @@ -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<u8>,
pub message: Vec<u8>,
signature: Vec<u8>,
message: Vec<u8>,
pcrs: Vec<Vec<u8>>,
}

impl Quote {
Expand All @@ -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<u8> {
self.message.clone()
}
}

/// Get a signed vTPM Quote
Expand All @@ -152,8 +164,12 @@ pub fn get_quote(data: &[u8]) -> Result<Quote, QuoteError> {
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);
Expand All @@ -165,18 +181,21 @@ pub fn get_quote(data: &[u8]) -> Result<Quote, QuoteError> {
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,
})
}
80 changes: 73 additions & 7 deletions az-cvm-vtpm/src/vtpm/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -19,6 +23,8 @@ pub enum VerifyError {
NonceMismatch,
#[error("quote error")]
Quote(#[from] QuoteError),
#[error("pcr mismatch")]
PcrMismatch,
}

impl Quote {
Expand All @@ -36,6 +42,9 @@ impl Quote {
if nonce != quote_nonce {
return Err(VerifyError::NonceMismatch);
}

self.verify_pcrs()?;

Ok(())
}

Expand All @@ -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<dyn Error>> {
// // 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<u8> = bincode::serialize(&quote).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");

Expand All @@ -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");
}
}
14 changes: 7 additions & 7 deletions az-cvm-vtpm/test/akpub.pem
Original file line number Diff line number Diff line change
@@ -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-----
Binary file added az-cvm-vtpm/test/quote.bin
Binary file not shown.
Binary file removed az-cvm-vtpm/test/quote_msg
Binary file not shown.
Binary file removed az-cvm-vtpm/test/quote_sig
Binary file not shown.

0 comments on commit 29874be

Please sign in to comment.