Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vtpm-quote: Add SHA256 PCR value extraction API #47

Merged
merged 7 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode
6 changes: 3 additions & 3 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 All @@ -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"
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.
Loading