Skip to content
Open
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ hex = "0.4"
x509-parser = { version="=0.16.0", features=["verify"] }
asn1-rs = "=0.6.2"
rand = "0.8.5"
tss-esapi = { version = "7.2", optional=true }
tss-esapi = { version = "7.6", optional=true }
msru = "0.2.0"
colorful = "0.2.2"
bitfield = "0.15.0"
Expand Down
65 changes: 65 additions & 0 deletions src/hyperv/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Apache-2.0
// This file contains code for checking Hyper-V feature is present.

use std::arch::x86_64::__cpuid;

const CPUID_GET_HIGHEST_FUNCTION: u32 = 0x80000000;
const CPUID_PROCESSOR_INFO_AND_FEATURE_BITS: u32 = 0x1;

const CPUID_FEATURE_HYPERVISOR: u32 = 1 << 31;

const CPUID_HYPERV_SIG: &str = "Microsoft Hv";
const CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS: u32 = 0x40000000;
const CPUID_HYPERV_FEATURES: u32 = 0x40000003;
const CPUID_HYPERV_MIN: u32 = 0x40000005;
const CPUID_HYPERV_MAX: u32 = 0x4000ffff;
const CPUID_HYPERV_ISOLATION: u32 = 1 << 22;
const CPUID_HYPERV_CPU_MANAGEMENT: u32 = 1 << 12;
const CPUID_HYPERV_ISOLATION_CONFIG: u32 = 0x4000000C;
const CPUID_HYPERV_ISOLATION_TYPE_MASK: u32 = 0xf;
const CPUID_HYPERV_ISOLATION_TYPE_SNP: u32 = 2;

pub fn present() -> bool {
let mut cpuid = unsafe { __cpuid(CPUID_PROCESSOR_INFO_AND_FEATURE_BITS) };
if (cpuid.ecx & CPUID_FEATURE_HYPERVISOR) == 0 {
return false;
}

cpuid = unsafe { __cpuid(CPUID_GET_HIGHEST_FUNCTION) };
if cpuid.eax < CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS {
return false;
}

cpuid = unsafe { __cpuid(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS) };
if cpuid.eax < CPUID_HYPERV_MIN || cpuid.eax > CPUID_HYPERV_MAX {
return false;
}

let mut sig: Vec<u8> = vec![];
sig.append(&mut cpuid.ebx.to_le_bytes().to_vec());
sig.append(&mut cpuid.ecx.to_le_bytes().to_vec());
sig.append(&mut cpuid.edx.to_le_bytes().to_vec());

if sig != CPUID_HYPERV_SIG.as_bytes() {
return false;
}

cpuid = unsafe { __cpuid(CPUID_HYPERV_FEATURES) };

let isolated: bool = (cpuid.ebx & CPUID_HYPERV_ISOLATION) != 0;
let managed: bool = (cpuid.ebx & CPUID_HYPERV_CPU_MANAGEMENT) != 0;

if !isolated || managed {
return false;
}

cpuid = unsafe { __cpuid(CPUID_HYPERV_ISOLATION_CONFIG) };
let mask = cpuid.ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK;
let snp = CPUID_HYPERV_ISOLATION_TYPE_SNP;

if mask != snp {
return false;
}

true
}
133 changes: 3 additions & 130 deletions src/hyperv/mod.rs
Original file line number Diff line number Diff line change
@@ -1,132 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
// This file contains code related to Hyper-V integration (Hypervisor). It provides a flag (`hyperv::present`) indicating whether the SNP Guest is running within a Hyper-V guest environment.

use super::*;

use std::arch::x86_64::__cpuid;
use std::mem::size_of;

const CPUID_GET_HIGHEST_FUNCTION: u32 = 0x80000000;
const CPUID_PROCESSOR_INFO_AND_FEATURE_BITS: u32 = 0x1;

const CPUID_FEATURE_HYPERVISOR: u32 = 1 << 31;

const CPUID_HYPERV_SIG: &str = "Microsoft Hv";
const CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS: u32 = 0x40000000;
const CPUID_HYPERV_FEATURES: u32 = 0x40000003;
const CPUID_HYPERV_MIN: u32 = 0x40000005;
const CPUID_HYPERV_MAX: u32 = 0x4000ffff;
const CPUID_HYPERV_ISOLATION: u32 = 1 << 22;
const CPUID_HYPERV_CPU_MANAGEMENT: u32 = 1 << 12;
const CPUID_HYPERV_ISOLATION_CONFIG: u32 = 0x4000000C;
const CPUID_HYPERV_ISOLATION_TYPE_MASK: u32 = 0xf;
const CPUID_HYPERV_ISOLATION_TYPE_SNP: u32 = 2;

const RSV1_SIZE: usize = size_of::<u32>() * 8;
const REPORT_SIZE: usize = 1184;
const RSV2_SIZE: usize = size_of::<u32>() * 5;
const TOTAL_SIZE: usize = RSV1_SIZE + REPORT_SIZE + RSV2_SIZE;
const REPORT_RANGE: std::ops::Range<usize> = RSV1_SIZE..(RSV1_SIZE + REPORT_SIZE);

pub fn present() -> bool {
let mut cpuid = unsafe { __cpuid(CPUID_PROCESSOR_INFO_AND_FEATURE_BITS) };
if (cpuid.ecx & CPUID_FEATURE_HYPERVISOR) == 0 {
return false;
}

cpuid = unsafe { __cpuid(CPUID_GET_HIGHEST_FUNCTION) };
if cpuid.eax < CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS {
return false;
}

cpuid = unsafe { __cpuid(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS) };
if cpuid.eax < CPUID_HYPERV_MIN || cpuid.eax > CPUID_HYPERV_MAX {
return false;
}

let mut sig: Vec<u8> = vec![];
sig.append(&mut cpuid.ebx.to_le_bytes().to_vec());
sig.append(&mut cpuid.ecx.to_le_bytes().to_vec());
sig.append(&mut cpuid.edx.to_le_bytes().to_vec());

if sig != CPUID_HYPERV_SIG.as_bytes() {
return false;
}

cpuid = unsafe { __cpuid(CPUID_HYPERV_FEATURES) };

let isolated: bool = (cpuid.ebx & CPUID_HYPERV_ISOLATION) != 0;
let managed: bool = (cpuid.ebx & CPUID_HYPERV_CPU_MANAGEMENT) != 0;

if !isolated || managed {
return false;
}

cpuid = unsafe { __cpuid(CPUID_HYPERV_ISOLATION_CONFIG) };
let mask = cpuid.ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK;
let snp = CPUID_HYPERV_ISOLATION_TYPE_SNP;

if mask != snp {
return false;
}

true
}

pub mod report {
use super::*;

use anyhow::{anyhow, Context};
use serde::{Deserialize, Serialize};
use sev::firmware::guest::AttestationReport;
use tss_esapi::{
abstraction::nv,
handles::NvIndexTpmHandle,
interface_types::{resource_handles::NvAuth, session_handles::AuthSession},
tcti_ldr::{DeviceConfig, TctiNameConf},
};

const VTPM_HCL_REPORT_NV_INDEX: u32 = 0x01400001;

#[repr(C)]
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
struct Hcl {
rsv1: [u32; 8],
report: AttestationReport,
rsv2: [u32; 5],
}

pub fn get(vmpl: u32) -> Result<AttestationReport> {
if vmpl > 0 {
eprintln!("Warning: --vmpl argument was ignored because attestation report is pre-fetched at VMPL 0 and stored in vTPM.");
}
let bytes = tpm2_read().context("unable to read attestation report bytes from vTPM")?;

hcl_report(&bytes)
}

fn tpm2_read() -> Result<Vec<u8>> {
let handle = NvIndexTpmHandle::new(VTPM_HCL_REPORT_NV_INDEX)
.context("unable to initialize TPM handle")?;
let mut ctx = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
ctx.set_sessions((Some(AuthSession::Password), None, None));

nv::read_full(&mut ctx, NvAuth::Owner, handle)
.context("unable to read non-volatile vTPM data")
}

fn hcl_report(bytes: &[u8]) -> Result<AttestationReport> {
if bytes.len() < TOTAL_SIZE {
return Err(anyhow!(
"HCL report size mismatch: expected at least {}, got {}",
TOTAL_SIZE,
bytes.len()
));
}

let report_bytes = &bytes[REPORT_RANGE];

AttestationReport::from_bytes(report_bytes)
.context("Unable to convert HCL report bytes to AttestationReport")
}
}
pub mod check;
pub mod report;
pub(crate) mod tpm;
69 changes: 69 additions & 0 deletions src/hyperv/report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: Apache-2.0
// This file contains code for requesting attestation reports from vTPMs on Azure Confidential VMs.

use super::tpm;
use anyhow::{anyhow, Context, Result};
use sev::{firmware::guest::AttestationReport, parser::ByteParser};
use tss_esapi::{
abstraction::nv,
handles::NvIndexTpmHandle,
interface_types::{resource_handles::NvAuth, session_handles::AuthSession},
tcti_ldr::{DeviceConfig, TctiNameConf},
};

const VTPM_HCL_REPORT_NV_INDEX: u32 = 0x01400001;
const VTPM_USER_DATA_NV_INDEX: u32 = 0x01400002;
const VTPM_USER_DATA_SIZE: usize = 64;

const HCL_REPORT_HEADER_SIZE: usize = 32;
const HW_REPORT_SIZE: usize = 1184;
const REPORT_RANGE: std::ops::Range<usize> =
HCL_REPORT_HEADER_SIZE..(HCL_REPORT_HEADER_SIZE + HW_REPORT_SIZE);

pub fn get(data: [u8; VTPM_USER_DATA_SIZE]) -> Result<AttestationReport> {
write_user_data_to_vtpm(data).context("unable to write user data to vTPM")?;
let hcl_report_bytes =
read_hcl_report_from_vtpm().context("unable to read attestation report bytes from vTPM")?;
if hcl_report_bytes.len() < HCL_REPORT_HEADER_SIZE + HW_REPORT_SIZE {
return Err(anyhow!(
"HCL report size mismatch: expected at least {}, got {}",
HCL_REPORT_HEADER_SIZE + HW_REPORT_SIZE,
hcl_report_bytes.len()
));
}
let hw_report_bytes = &hcl_report_bytes[REPORT_RANGE];
AttestationReport::from_bytes(hw_report_bytes)
.context("unable to convert HCL report bytes to AttestationReport")
}

fn read_hcl_report_from_vtpm() -> Result<Vec<u8>> {
let handle = NvIndexTpmHandle::new(VTPM_HCL_REPORT_NV_INDEX)
.context("unable to initialize TPM handle")?;
let mut ctx = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
ctx.set_sessions((Some(AuthSession::Password), None, None));

nv::read_full(&mut ctx, NvAuth::Owner, handle).context("unable to read non-volatile vTPM data")
}

fn write_user_data_to_vtpm(data: [u8; VTPM_USER_DATA_SIZE]) -> Result<()> {
let mut ctx = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
ctx.set_sessions((Some(AuthSession::Password), None, None));

let handle = NvIndexTpmHandle::new(VTPM_USER_DATA_NV_INDEX)
.context("unable to initialize TPM handle")?;

let result = tpm::find_nv_index(&mut ctx, handle)?;

if let Some((public, _)) = result {
if public.data_size() != VTPM_USER_DATA_SIZE {
tpm::nv_undefine(&mut ctx, handle)?;
tpm::nv_define(&mut ctx, handle, VTPM_USER_DATA_SIZE)?;
}
} else {
tpm::nv_define(&mut ctx, handle, VTPM_USER_DATA_SIZE)?;
}

tpm::nv_write(&mut ctx, handle, &data).context("unable to write data to NV index")?;

Ok(())
}
76 changes: 76 additions & 0 deletions src/hyperv/tpm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// This file contains code for handling TPM 2.0.

use anyhow::{Context, Result};
use tss_esapi::{
abstraction::nv,
attributes::NvIndexAttributesBuilder,
handles::{NvIndexHandle, NvIndexTpmHandle},
interface_types::{
algorithm::HashingAlgorithm,
resource_handles::{NvAuth, Provision},
},
structures::{MaxNvBuffer, Name, NvPublic, NvPublicBuilder},
};

/// Find an NV index
pub fn find_nv_index(
ctx: &mut tss_esapi::Context,
nv_index: NvIndexTpmHandle,
) -> Result<Option<(NvPublic, Name)>> {
let list = nv::list(ctx).context("unable to list NV indices")?;

let entry = list
.into_iter()
.find(|(public, _)| public.nv_index() == nv_index);

Ok(entry)
}

/// Define a new NV index with the specified size
pub fn nv_define(
ctx: &mut tss_esapi::Context,
handle: NvIndexTpmHandle,
len: usize,
) -> Result<NvIndexHandle> {
let attributes = NvIndexAttributesBuilder::new()
.with_owner_read(true)
.with_owner_write(true)
.build()
.context("unable to build NV index attributes")?;

let nv_public = NvPublicBuilder::new()
.with_nv_index(handle)
.with_index_attributes(attributes)
.with_index_name_algorithm(HashingAlgorithm::Sha256)
.with_data_area_size(len)
.build()
.context("unable to build NV public structure")?;

let index = ctx
.nv_define_space(Provision::Owner, None, nv_public)
.context("unable to define NV index")?;

Ok(index)
}

/// Undefine an existing NV index
pub fn nv_undefine(ctx: &mut tss_esapi::Context, handle: NvIndexTpmHandle) -> Result<()> {
let key_handle = ctx
.execute_without_session(|c| c.tr_from_tpm_public(handle.into()))
.context("unable to resolve NV index handle")?;
let index = key_handle.into();
ctx.nv_undefine_space(Provision::Owner, index)
.context("unable to undefine NV index")
}

/// Write data to an NV index
pub fn nv_write(ctx: &mut tss_esapi::Context, handle: NvIndexTpmHandle, data: &[u8]) -> Result<()> {
let buffer = MaxNvBuffer::try_from(data).context("unable to create MaxNvBuffer from data")?;
let key_handle = ctx
.execute_without_session(|c| c.tr_from_tpm_public(handle.into()))
.context("unable to resolve NV index handle")?;
let index = key_handle.into();
ctx.nv_write(NvAuth::Owner, index, buffer, 0)
.context("unable to write data to NV index")
}
6 changes: 3 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ fn main() -> Result<()> {
let snpguest = SnpGuest::parse();

#[cfg(feature = "hyperv")]
let hv = hyperv::present();
let azcvm_present = hyperv::check::present();

#[cfg(not(feature = "hyperv"))]
let hv = false;
let azcvm_present = false;

let status = match snpguest.cmd {
SnpGuestCmd::Report(args) => report::get_report(args, hv),
SnpGuestCmd::Report(args) => report::get_report(args, azcvm_present),
SnpGuestCmd::Certificates(args) => certs::get_ext_certs(args),
SnpGuestCmd::Fetch(subcmd) => fetch::cmd(subcmd),
SnpGuestCmd::Verify(subcmd) => verify::cmd(subcmd, snpguest.quiet),
Expand Down
Loading