Skip to content

Commit

Permalink
as: re-use snp verification logic in az_snp_vtpm
Browse files Browse the repository at this point in the history
Since the generic SNP verifier has been merged it makes sense to
consolidate the verification logic and re-use as much as possible
in the azure specific verifier.

Signed-off-by: Magnus Kulke <[email protected]>
  • Loading branch information
mkulke authored and fitzthum committed Nov 21, 2023
1 parent f350942 commit c205952
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 195 deletions.
2 changes: 1 addition & 1 deletion attestation-service/attestation-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ default = [ "rvps-native", "all-verifier" ]
all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier", "csv-verifier", "cca-verifier" ]
tdx-verifier = [ "eventlog-rs", "scroll", "sgx-dcap-quoteverify-rs" ]
sgx-verifier = [ "scroll", "sgx-dcap-quoteverify-rs" ]
az-snp-vtpm-verifier = [ "az-snp-vtpm", "sev" ]
az-snp-vtpm-verifier = [ "az-snp-vtpm", "sev", "snp-verifier" ]
snp-verifier = [ "asn1-rs", "openssl", "sev", "x509-parser" ]
csv-verifier = [ "openssl", "csv-rs", "codicon" ]
cca-verifier = [ "cbor-diag", "veraison-apiclient" ]
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@
// SPDX-License-Identifier: Apache-2.0
//

use super::snp::{parse_tee_evidence, verify_report_signature};
use super::{Attestation, TeeEvidenceParsedClaim, Verifier};
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use az_snp_vtpm::certs::{AmdChain, Vcek, X509};
use az_snp_vtpm::certs::Vcek;
use az_snp_vtpm::hcl::HclData;
use az_snp_vtpm::report::Validateable;
use az_snp_vtpm::vtpm::{Quote, VerifyVTpmQuote};
use base64::Engine;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sev::firmware::guest::AttestationReport;
use sev::firmware::host::TcbVersion;
use sev::firmware::host::{CertTableEntry, CertType};
use sha2::{Digest, Sha384};
use std::collections::BTreeMap;

const HCL_VMPL_VALUE: u32 = 0;

Expand Down Expand Up @@ -45,8 +42,8 @@ impl Verifier for AzSnpVtpm {
let vcek = Vcek::from_pem(&evidence.vcek)?;

let hashed_quote = nonced_pub_key_hash(attestation, &nonce);

verify_quote(&evidence.quote, &hcl_data, &hashed_quote)?;

verify_snp_report(snp_report, &vcek)?;
let var_data = hcl_data.var_data();
hcl_data.report().verify_report_data(var_data)?;
Expand All @@ -66,26 +63,10 @@ fn verify_quote(quote: &Quote, hcl_data: &HclData, hashed_nonce: &[u8]) -> Resul
Ok(())
}

fn build_amd_chain() -> Result<AmdChain> {
let bytes = include_bytes!("./milan_ask_ark.pem");
let certs = X509::stack_from_pem(bytes)?;
let ask = certs[0].clone();
let ark = certs[1].clone();
let chain = AmdChain { ask, ark };
Ok(chain)
}

fn verify_snp_report(snp_report: &AttestationReport, vcek: &Vcek) -> Result<()> {
let amd_chain = build_amd_chain()?;

amd_chain
.validate()
.context("Verification of AMD certificate chain failed")?;
vcek.validate(&amd_chain)
.context("Verification of VCEK evidence failed")?;
snp_report
.validate(vcek)
.context("Verification of SEV-SNP report failed")?;
let vcek_data = vcek.0.to_der().context("Failed to get raw VCEK data")?;
let cert_chain = [CertTableEntry::new(CertType::VCEK, vcek_data)];
verify_report_signature(snp_report, &cert_chain)?;

if snp_report.vmpl != HCL_VMPL_VALUE {
return Err(anyhow!("VMPL of SNP report is not {HCL_VMPL_VALUE}"));
Expand All @@ -94,45 +75,6 @@ fn verify_snp_report(snp_report: &AttestationReport, vcek: &Vcek) -> Result<()>
Ok(())
}

fn parse_tee_evidence(report: &AttestationReport) -> TeeEvidenceParsedClaim {
let TcbVersion {
bootloader,
tee,
snp,
microcode,
..
} = report.reported_tcb;
let policy = report.policy;

let num_values = [
("policy_abi_major", policy.abi_major()),
("policy_abi_minor", policy.abi_minor()),
("policy_smt_allowed", policy.smt_allowed()),
("policy_migrate_ma", policy.migrate_ma_allowed()),
("policy_debug_allowed", policy.debug_allowed()),
("policy_single_socket", policy.single_socket_required()),
// versioning info
("reported_tcb_bootloader", bootloader as u64),
("reported_tcb_tee", tee as u64),
("reported_tcb_snp", snp as u64),
("reported_tcb_microcode", microcode as u64),
// platform info
("platform_tsme_enabled", report.plat_info.tsme_enabled()),
("platform_smt_enabled", report.plat_info.smt_enabled()),
];

let mut string_map: BTreeMap<_, _> = num_values
.iter()
.map(|(k, v)| (*k, v.to_string()))
.collect();
string_map.insert(
"measurement",
base64::engine::general_purpose::STANDARD.encode(report.measurement),
);

json!(string_map) as TeeEvidenceParsedClaim
}

fn nonced_pub_key_hash(attestation: &Attestation, nonce: &str) -> Vec<u8> {
let mut hasher = Sha384::new();
hasher.update(nonce);
Expand All @@ -153,6 +95,7 @@ mod tests {
verify_snp_report(hcl_data.report().snp_report(), &vcek).unwrap();

let mut wrong_report = *report;

// messing with snp report
wrong_report[0x00b0] = 0;
let wrong_hcl_data: HclData = wrong_report.as_slice().try_into().unwrap();
Expand Down Expand Up @@ -187,29 +130,4 @@ mod tests {
let wrong_hcl_data: HclData = wrong_report.as_slice().try_into().unwrap();
verify_quote(&quote, &wrong_hcl_data, nonce).unwrap_err();
}

#[test]
fn test_parse_evidence() {
let report = include_bytes!("../../../../test_data/az-hcl-data.bin");
let hcl_data: HclData = report.as_slice().try_into().unwrap();
let snp_report = hcl_data.report().snp_report();
let claim = parse_tee_evidence(snp_report);

let reference = json!({
"measurement": "ofOTBBMke7OM/BcVeeo8EtX+SQHwx5L2P9ddmPHvgnwjUAZE4OaS5r6Rf5BQ09OM",
"platform_smt_enabled": "0",
"platform_tsme_enabled": "1",
"policy_abi_major": "0",
"policy_abi_minor": "31",
"policy_debug_allowed": "0",
"policy_migrate_ma": "0",
"policy_single_socket": "0",
"policy_smt_allowed": "1",
"reported_tcb_bootloader": "3",
"reported_tcb_microcode": "115",
"reported_tcb_snp": "8",
"reported_tcb_tee": "0"
});
assert!(claim == reference);
}
}
52 changes: 22 additions & 30 deletions attestation-service/attestation-service/src/verifier/snp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use sha2::{Digest, Sha384};
use x509_parser::prelude::*;

#[derive(Serialize, Deserialize)]
struct SnpEvidence {
pub struct SnpEvidence {
attestation_report: AttestationReport,
cert_chain: Vec<CertTableEntry>,
}
Expand All @@ -41,12 +41,13 @@ impl Verifier for Snp {
nonce: String,
attestation: &Attestation,
) -> Result<TeeEvidenceParsedClaim> {
let tee_evidence = serde_json::from_str::<SnpEvidence>(&attestation.tee_evidence)
.context("Deserialize Quote failed.")?;
let SnpEvidence {
attestation_report: report,
cert_chain,
} = serde_json::from_str(&attestation.tee_evidence).context("Deserialize Quote failed.")?;

verify_report_signature(&tee_evidence)?;
verify_report_signature(&report, &cert_chain)?;

let report = tee_evidence.attestation_report;
if report.version != 2 {
return Err(anyhow!("Unexpected report version"));
}
Expand Down Expand Up @@ -98,9 +99,12 @@ fn get_oid_int(vcek: &x509_parser::certificate::TbsCertificate, oid: Oid) -> Res
val_int.as_u8().context("Unexpected data size")
}

fn verify_report_signature(evidence: &SnpEvidence) -> Result<()> {
pub(crate) fn verify_report_signature(
report: &AttestationReport,
cert_chain: &[CertTableEntry],
) -> Result<()> {
// check cert chain
let vcek = verify_cert_chain(&evidence.cert_chain)?;
let vcek = verify_cert_chain(cert_chain)?;

// OpenSSL bindings do not expose custom extensions
// Parse the vcek using x509_parser
Expand All @@ -109,35 +113,31 @@ fn verify_report_signature(evidence: &SnpEvidence) -> Result<()> {

// verify vcek fields
// chip id
if get_oid_octets::<64>(&parsed_vcek, HW_ID_OID)? != evidence.attestation_report.chip_id {
if get_oid_octets::<64>(&parsed_vcek, HW_ID_OID)? != report.chip_id {
return Err(anyhow!("Chip ID mismatch"));
}

// tcb version
// these integer extensions are 3 bytes with the last byte as the data
if get_oid_int(&parsed_vcek, UCODE_SPL_OID)?
!= evidence.attestation_report.reported_tcb.microcode
{
if get_oid_int(&parsed_vcek, UCODE_SPL_OID)? != report.reported_tcb.microcode {
return Err(anyhow!("Microcode verion mismatch"));
}

if get_oid_int(&parsed_vcek, SNP_SPL_OID)? != evidence.attestation_report.reported_tcb.snp {
if get_oid_int(&parsed_vcek, SNP_SPL_OID)? != report.reported_tcb.snp {
return Err(anyhow!("SNP verion mismatch"));
}

if get_oid_int(&parsed_vcek, TEE_SPL_OID)? != evidence.attestation_report.reported_tcb.tee {
if get_oid_int(&parsed_vcek, TEE_SPL_OID)? != report.reported_tcb.tee {
return Err(anyhow!("TEE verion mismatch"));
}

if get_oid_int(&parsed_vcek, LOADER_SPL_OID)?
!= evidence.attestation_report.reported_tcb.bootloader
{
if get_oid_int(&parsed_vcek, LOADER_SPL_OID)? != report.reported_tcb.bootloader {
return Err(anyhow!("Boot loader verion mismatch"));
}

// verify report signature
let sig = ecdsa::EcdsaSig::try_from(&evidence.attestation_report.signature)?;
let data = &bincode::serialize(&evidence.attestation_report)?[..=0x29f];
let sig = ecdsa::EcdsaSig::try_from(&report.signature)?;
let data = &bincode::serialize(&report)?[..=0x29f];

let pub_key = EcKey::try_from(vcek.public_key()?)?;
let signed = sig.verify(&sha384(data), &pub_key)?;
Expand All @@ -148,7 +148,7 @@ fn verify_report_signature(evidence: &SnpEvidence) -> Result<()> {
Ok(())
}

fn load_milan_cert_chain() -> Result<(x509::X509, x509::X509)> {
pub fn load_milan_cert_chain() -> Result<(x509::X509, x509::X509)> {
let certs = x509::X509::stack_from_pem(include_bytes!("milan_ask_ark.pem"))?;
if certs.len() != 2 {
bail!("Malformed Milan ASK/ARK");
Expand Down Expand Up @@ -197,7 +197,7 @@ fn calculate_expected_report_data(nonce: &String, tee_pubkey: &TeePubKey) -> [u8
hash
}

fn parse_tee_evidence(report: &AttestationReport) -> TeeEvidenceParsedClaim {
pub(crate) fn parse_tee_evidence(report: &AttestationReport) -> TeeEvidenceParsedClaim {
let claims_map = json!({
// policy fields
"policy_abi_major": format!("{}",report.policy.abi_major()),
Expand Down Expand Up @@ -326,11 +326,7 @@ mod tests {
let bytes = include_bytes!("test-report.bin");
let attestation_report = bincode::deserialize::<AttestationReport>(bytes).unwrap();
let cert_chain = vec![CertTableEntry::new(CertType::VCEK, vcek)];
let evidence = SnpEvidence {
attestation_report,
cert_chain,
};
verify_report_signature(&evidence).unwrap();
verify_report_signature(&attestation_report, &cert_chain).unwrap();
}

#[test]
Expand All @@ -343,10 +339,6 @@ mod tests {

let attestation_report = bincode::deserialize::<AttestationReport>(&bytes).unwrap();
let cert_chain = vec![CertTableEntry::new(CertType::VCEK, vcek)];
let evidence = SnpEvidence {
attestation_report,
cert_chain,
};
verify_report_signature(&evidence).unwrap_err();
verify_report_signature(&attestation_report, &cert_chain).unwrap_err();
}
}

0 comments on commit c205952

Please sign in to comment.