Skip to main content

attest_prove/
azure.rs

1//! Azure vTPM attestation generation: package a TDX quote together with the
2//! HCL report and a vTPM-signed quote of the AK certificate
3
4use az_tdx_vtpm::{hcl, imds, vtpm};
5use base64::{Engine as _, engine::general_purpose::URL_SAFE as BASE64_URL_SAFE};
6use serde::Serialize;
7use thiserror::Error;
8use tss_esapi::{
9    Context,
10    handles::NvIndexTpmHandle,
11    interface_types::{resource_handles::NvAuth, session_handles::AuthSession},
12    tcti_ldr::{DeviceConfig, TctiNameConf},
13};
14
15/// NV index where the AK certificate lives in the Azure vTPM
16const TPM_AK_CERT_IDX: u32 = 0x1C101D0;
17
18#[derive(Serialize)]
19struct AttestationDocument {
20    tdx_quote_base64: String,
21    hcl_report_base64: String,
22    tpm_attestation: TpmAttest,
23}
24
25#[derive(Serialize)]
26struct TpmAttest {
27    ak_certificate_pem: String,
28    quote: vtpm::Quote,
29    event_log: Vec<u8>,
30    instance_info: Option<Vec<u8>>,
31}
32
33pub fn create_quote(input_data: [u8; 64]) -> Result<Vec<u8>, AzureError> {
34    let hcl_report_bytes = vtpm::get_report_with_report_data(&input_data)?;
35    let hcl_report = hcl::HclReport::new(hcl_report_bytes.clone())?;
36    let td_report = hcl_report.try_into()?;
37    let td_quote_bytes = imds::get_td_quote(&td_report)?;
38    let ak_certificate_der = read_ak_certificate_from_tpm()?;
39
40    let document = AttestationDocument {
41        tdx_quote_base64: BASE64_URL_SAFE.encode(&td_quote_bytes),
42        hcl_report_base64: BASE64_URL_SAFE.encode(&hcl_report_bytes),
43        tpm_attestation: TpmAttest {
44            ak_certificate_pem: pem_rfc7468::encode_string(
45                "CERTIFICATE",
46                pem_rfc7468::LineEnding::default(),
47                &ak_certificate_der,
48            )?,
49            quote: vtpm::get_quote(&input_data[..32])?,
50            event_log: Vec::new(),
51            instance_info: None,
52        },
53    };
54    Ok(serde_json::to_vec(&document)?)
55}
56
57fn read_ak_certificate_from_tpm() -> Result<Vec<u8>, tss_esapi::Error> {
58    let mut context = Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
59    context.set_sessions((Some(AuthSession::Password), None, None));
60    let nv_handle = NvIndexTpmHandle::new(TPM_AK_CERT_IDX)?;
61    let buf = tss_esapi::abstraction::nv::read_full(&mut context, NvAuth::Owner, nv_handle)?;
62    Ok(buf.to_vec())
63}
64
65#[derive(Error, Debug)]
66pub enum AzureError {
67    #[error("HCL report: {0}")]
68    Report(#[from] az_tdx_vtpm::report::ReportError),
69    #[error("IMDS: {0}")]
70    Imds(#[from] imds::ImdsError),
71    #[error("vTPM report: {0}")]
72    VtpmReport(#[from] az_tdx_vtpm::vtpm::ReportError),
73    #[error("HCL: {0}")]
74    Hcl(#[from] hcl::HclError),
75    #[error("vTPM quote: {0}")]
76    VtpmQuote(#[from] vtpm::QuoteError),
77    #[error("vTPM read: {0}")]
78    TssEsapi(#[from] tss_esapi::Error),
79    #[error("PEM encode: {0}")]
80    Pem(#[from] pem_rfc7468::Error),
81    #[error("JSON: {0}")]
82    Json(#[from] serde_json::Error),
83}