use super::*;
use std::{
fs::{self, File, OpenOptions},
io::{Read, Write},
path::PathBuf,
};
use anyhow::{anyhow, Result};
use rand::{thread_rng, RngCore};
use sev::firmware::guest::{AttestationReport, Firmware};
pub fn read_report(att_report_path: PathBuf) -> Result<AttestationReport, anyhow::Error> {
let mut attestation_file = fs::File::open(att_report_path)?;
let mut report_bytes = Vec::new();
attestation_file
.read_to_end(&mut report_bytes)
.context("Failed to read the report bytes.")?;
let attestation_report = AttestationReport::from_bytes(&report_bytes)
.context("Failed to build report from the raw bytes. Report could be malformed.")?;
Ok(attestation_report)
}
pub fn create_random_request() -> [u8; 64] {
let mut data = [0u8; 64];
thread_rng().fill_bytes(&mut data);
data
}
#[derive(Parser)]
pub struct ReportArgs {
#[arg(value_name = "att-report-path", required = true)]
pub att_report_path: PathBuf,
#[arg(short, long, default_value_t = false, conflicts_with = "platform")]
pub random: bool,
#[arg(short, long, default_value = "1", value_name = "vmpl")]
pub vmpl: Option<u32>,
#[arg(value_name = "request-file", required = true)]
pub request_file: PathBuf,
#[arg(short, long, conflicts_with = "random")]
pub platform: bool,
}
impl ReportArgs {
pub fn verify(&self, hyperv: bool) -> Result<()> {
if self.random && self.platform {
return Err(anyhow!(
"--random and --platform both enabled (not allowed). Consult man page."
));
}
if self.platform && !hyperv {
#[cfg(feature = "hyperv")]
let msg = "--platform enabled yet Hyper-V guest with SEV-SNP isolation not detected (not allowed). Consult man page.";
#[cfg(not(feature = "hyperv"))]
let msg =
"--platform requires a binary built with --features hyperv. Consult man page.";
return Err(anyhow!(msg));
}
Ok(())
}
}
fn request_hardware_report(
data: Option<[u8; 64]>,
vmpl: Option<u32>,
_platform: bool,
) -> Result<AttestationReport> {
#[cfg(feature = "hyperv")]
if _platform {
return hyperv::report::get(vmpl.unwrap_or(0));
}
let mut fw = Firmware::open().context("unable to open /dev/sev-guest")?;
Ok(AttestationReport::from_bytes(
fw.get_report(None, data, vmpl)
.context("unable to fetch attestation report")?
.as_slice(),
)?)
}
pub fn get_report(args: ReportArgs, hv: bool) -> Result<()> {
args.verify(hv)?;
let data: Option<[u8; 64]> = if args.random {
Some(create_random_request())
} else if args.platform {
None
} else {
let mut bytes = [0u8; 64];
let mut file = File::open(&args.request_file)?;
file.read_exact(&mut bytes)
.context("unable to read 64 bytes from REQUEST_FILE")?;
Some(bytes)
};
let report = request_hardware_report(data, args.vmpl, args.platform)?;
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&args.att_report_path)?;
report.write_bytes(&mut file)?;
if args.random {
if let Some(data) = data {
reqdata_write(args.request_file, &data)
.context("unable to write random request data to specified file")?;
} else {
return Err(anyhow!("unable to write empty buffer to specified file."));
}
} else if args.platform {
reqdata_write(args.request_file, &*report.report_data)
.context("unable to write platform request data")?;
}
Ok(())
}
fn reqdata_write(name: PathBuf, report_data: &[u8]) -> Result<()> {
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(name)
.context("unable to create or write to request data file")?;
file.write_all(report_data)
.context("unable to write report data to REQUEST_FILE")
}