Skip to content

Commit

Permalink
attester: tdx: Support TDX CVM on Azure
Browse files Browse the repository at this point in the history
To get TDX attestation to work on Azure there are several changes
needed:
- the device node is called /dev/tdx_guest (upstream kernel name)
- quote generation uses the IMDS (instance metadata service) instead
  of tdvmcall or vsock. This also means we can't use tdx_att_get_quote
  which combines quote and report fetching
- no CCEL

Implement the evidence gathering in a sub-module attester, but keep it
within the TDX module because the evidence is fully compatible with the
existing TDX verifier.

It would be possible to use tdx_att_get_report, but calling an ioctl is
easy enough that it doesn't make sense to add the dependency on the
native library. The Intel library also seems to have an outdated
definition of the ioctl.

Signed-off-by: Jeremi Piotrowski <jpiotrowski@microsoft.com>
  • Loading branch information
jepio committed Jul 7, 2023
1 parent 2cd372e commit 1b2ec06
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 9 deletions.
5 changes: 4 additions & 1 deletion attestation-agent/attester/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ anyhow.workspace = true
async-trait.workspace = true
az-snp-vtpm = { git = "https://github.com/kinvolk/azure-cvm-tooling", rev = "2c2e411", default-features = false, features = ["attester"], optional = true }
base64.workspace = true
ioctl-sys = { version = "0.8.0", optional = true }
log.workspace = true
occlum_dcap = { git = "https://github.com/occlum/occlum", rev = "dbe404f", optional = true }
raw-cpuid = { version = "11", optional = true }
reqwest = { version = "0.11", features = ["json"], optional = true }
serde.workspace = true
serde_json.workspace = true
sev = { git = "https://github.com/virtee/sev", rev = "3dca05d2c93388cb00534ad18f5928fd812e99cc", optional = true }
Expand All @@ -25,7 +28,7 @@ tokio = { version = "1", features = ["macros"] }
default = ["all-attesters"]
all-attesters = ["tdx-attester", "occlum-attester", "az-snp-vtpm-attester", "snp-attester"]

tdx-attester = ["tdx-attest-rs"]
tdx-attester = ["tdx-attest-rs", "dep:reqwest", "dep:ioctl-sys", "dep:raw-cpuid"]
occlum-attester = ["occlum_dcap"]
az-snp-vtpm-attester = ["az-snp-vtpm"]
snp-attester = ["sev"]
2 changes: 1 addition & 1 deletion attestation-agent/attester/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Tee {
match self {
Tee::Sample => Ok(Box::<sample::SampleAttester>::default()),
#[cfg(feature = "tdx-attester")]
Tee::Tdx => Ok(Box::<tdx::TdxAttester>::default()),
Tee::Tdx => Ok(tdx::make_attester()),
#[cfg(feature = "occlum-attester")]
Tee::SgxOcclum => Ok(Box::<sgx_occlum::SgxOcclumAttester>::default()),
#[cfg(feature = "az-snp-vtpm-attester")]
Expand Down
105 changes: 105 additions & 0 deletions attestation-agent/attester/src/tdx/az.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
//
// SPDX-License-Identifier: Apache-2.0
//

use anyhow::*;
use ioctl_sys::ioctl;
use raw_cpuid::{CpuId, Hypervisor};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Error;
use std::os::unix::io::AsRawFd;

use crate::Attester;

// Length of the REPORTDATA used in TDG.MR.REPORT TDCALL
const TDX_REPORTDATA_LEN: usize = 64;

// Length of TDREPORT used in TDG.MR.REPORT TDCALL
const TDX_REPORT_LEN: usize = 1024;

#[repr(C)]
#[derive(Debug)]
pub struct TdxReportReq {
reportdata: [u8; TDX_REPORTDATA_LEN],
tdreport: [u8; TDX_REPORT_LEN],
}

impl TdxReportReq {
pub fn new(reportdata: [u8; TDX_REPORTDATA_LEN]) -> Self {
Self {
reportdata,
tdreport: [0; TDX_REPORT_LEN],
}
}
}

#[derive(Serialize, Deserialize)]
struct AzTdQuoteRequest {
report: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct AzTdQuoteResponse {
quote: String,
}

impl Default for TdxReportReq {
fn default() -> Self {
Self {
reportdata: [0; TDX_REPORTDATA_LEN],
tdreport: [0; TDX_REPORT_LEN],
}
}
}

ioctl!(readwrite tdx_cmd_get_report0 with b'T', 0x01; TdxReportReq);

pub(super) async fn get_hyperv_tdx_evidence(report_data: &[u8]) -> Result<String> {
let file = OpenOptions::new().write(true).open("/dev/tdx_guest")?;
let fd = file.as_raw_fd();
let mut tdx_req = TdxReportReq::new(report_data.try_into()?);
unsafe {
let err = tdx_cmd_get_report0(fd, &mut tdx_req);
if err != 0 {
bail!("TDX Attester: ioctl failed: {}", Error::last_os_error());
}
}
let report = base64::encode_config(tdx_req.tdreport, base64::URL_SAFE_NO_PAD);
let tdquotereq = AzTdQuoteRequest { report };
let req = reqwest::Client::new()
.post("http://169.254.169.254/acc/tdquote")
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header(reqwest::header::ACCEPT, "application/json")
.body(serde_json::to_string(&tdquotereq)?)
.send()
.await?;
let tdquoteresp = req.json::<AzTdQuoteResponse>().await?;
let quote = base64::decode_config(tdquoteresp.quote, base64::URL_SAFE_NO_PAD)?;
let evidence = super::TdxEvidence {
cc_eventlog: None,
quote: base64::encode(quote),
};
serde_json::to_string(&evidence).context("TDX Attester: Failed to serialize evidence")
}

pub(super) fn detect_platform() -> bool {
// check cpuid if we are in a Hyper-V guest
let cpuid = CpuId::new();
if let Some(hypervisor) = cpuid.get_hypervisor_info() {
hypervisor.identify() == Hypervisor::HyperV
} else {
false
}
}

#[derive(Debug, Default)]
pub struct TdxAttester {}

#[async_trait::async_trait]
impl Attester for TdxAttester {
async fn get_evidence(&self, report_data: String) -> Result<String> {
let report_data_bin = super::convert_report_data(report_data)?;
get_hyperv_tdx_evidence(&report_data_bin).await
}
}
27 changes: 20 additions & 7 deletions attestation-agent/attester/src/tdx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use raw_cpuid::cpuid;
use serde::{Deserialize, Serialize};
use tdx_attest_rs;

mod az;

const CCEL_PATH: &str = "/sys/firmware/acpi/tables/data/CCEL";

pub fn detect_platform() -> bool {
Expand All @@ -28,19 +30,30 @@ struct TdxEvidence {
quote: String,
}

pub fn make_attester() -> Box<dyn Attester + Sync + Send> {
if az::detect_platform() {
Box::<az::TdxAttester>::default()
} else {
Box::<TdxAttester>::default()
}
}

fn convert_report_data(report_data: String) -> Result<Vec<u8>> {
let mut report_data_bin = base64::decode(report_data)?;
if report_data_bin.len() != 48 {
bail!("TDX Attester: Report data should be SHA384 base64 String");
}
report_data_bin.extend([0; 16]);
Ok(report_data_bin)
}

#[derive(Debug, Default)]
pub struct TdxAttester {}

#[async_trait::async_trait]
impl Attester for TdxAttester {
async fn get_evidence(&self, report_data: String) -> Result<String> {
let mut report_data_bin = base64::decode(report_data)?;
if report_data_bin.len() != 48 {
return Err(anyhow!(
"TDX Attester: Report data should be SHA384 base64 String"
));
}
report_data_bin.extend([0; 16]);
let report_data_bin = convert_report_data(report_data)?;

let tdx_report_data = tdx_attest_rs::tdx_report_data_t {
d: report_data_bin.as_slice().try_into()?,
Expand Down

0 comments on commit 1b2ec06

Please sign in to comment.