use std::{
fmt::{self, Display},
fs::{create_dir, read_to_string, File},
hash::{DefaultHasher, Hash, Hasher},
io::{ErrorKind, Read, Write},
num::ParseIntError,
path::PathBuf,
};
const CONFIGFS_TSM_PATH: &str = "/sys/kernel/config/tsm/report";
pub fn create_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
let quote_name = create_quote_name(&input);
let mut quote = OpenQuote::new("e_name)?;
quote.write_input(input)?;
quote.read_output()
}
pub fn create_quote_with_providers(
input: [u8; 64],
accepted_providers: Vec<&str>,
) -> Result<Vec<u8>, QuoteGenerationError> {
let quote_name = create_quote_name(&input);
let mut quote = OpenQuote::new("e_name)?;
quote.check_provider(accepted_providers)?;
quote.write_input(input)?;
quote.read_output()
}
pub fn create_tdx_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
create_quote_with_providers(input, vec!["tdx_guest"])
}
pub struct OpenQuote {
path: PathBuf,
expected_generation: u32,
}
impl OpenQuote {
pub fn new(quote_name: &str) -> Result<Self, QuoteGenerationError> {
let mut quote_path = PathBuf::from(CONFIGFS_TSM_PATH);
quote_path.push(quote_name);
if let Err(error) = create_dir(quote_path.clone()) {
match error.kind() {
ErrorKind::AlreadyExists => {}
ErrorKind::NotFound => return Err(QuoteGenerationError::CannotFindTsmDir),
_ => return Err(QuoteGenerationError::IO(error)),
}
}
Ok(Self {
path: quote_path,
expected_generation: 0,
})
}
pub fn write_input(&mut self, input: [u8; 64]) -> Result<(), QuoteGenerationError> {
self.update_generation()?;
let mut inblob_path = self.path.clone();
inblob_path.push("inblob");
let mut inblob_file = File::create(inblob_path)?;
inblob_file.write_all(&input)?;
self.expected_generation += 1;
Ok(())
}
pub fn read_output(&self) -> Result<Vec<u8>, QuoteGenerationError> {
let mut outblob_path = self.path.clone();
outblob_path.push("outblob");
let mut outblob_file = File::open(outblob_path)?;
let mut output = Vec::new();
outblob_file.read_to_end(&mut output)?;
let actual = self.read_generation()?;
if self.expected_generation != actual {
return Err(QuoteGenerationError::Generation(
self.expected_generation,
actual,
));
}
Ok(output)
}
pub fn read_generation(&self) -> Result<u32, QuoteGenerationError> {
let mut generation_path = self.path.clone();
generation_path.push("generation");
let mut current_generation = read_to_string(generation_path)?;
trim_newline(&mut current_generation);
Ok(current_generation.parse()?)
}
pub fn check_provider(&self, accepted_values: Vec<&str>) -> Result<(), QuoteGenerationError> {
let mut provider_path = self.path.clone();
provider_path.push("provider");
let mut provider = read_to_string(provider_path)?;
trim_newline(&mut provider);
if !accepted_values.contains(&provider.as_str()) {
return Err(QuoteGenerationError::BadProvider(provider));
}
Ok(())
}
fn update_generation(&mut self) -> Result<(), QuoteGenerationError> {
self.expected_generation = self.read_generation()?;
Ok(())
}
}
fn create_quote_name(input: &[u8]) -> String {
let mut s = DefaultHasher::new();
input.hash(&mut s);
let hash_bytes = s.finish().to_le_bytes();
bytes_to_hex(&hash_bytes)
}
fn bytes_to_hex(input: &[u8]) -> String {
input
.iter()
.map(|b| format!("{:02x}", b).to_string())
.collect::<Vec<String>>()
.join("")
}
fn trim_newline(input: &mut String) {
if input.ends_with('\n') {
input.pop();
if input.ends_with('\r') {
input.pop();
}
}
}
#[derive(Debug)]
pub enum QuoteGenerationError {
Generation(u32, u32),
IO(std::io::Error),
ParseInt,
BadProvider(String),
CannotFindTsmDir,
}
impl Display for QuoteGenerationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
QuoteGenerationError::Generation(expected, actual) => f.write_str(&format!(
"Wrong generation number - possible conflict. Expected: {} Actual: {}",
expected, actual
)),
QuoteGenerationError::IO(error) => f.write_str(&error.to_string()),
QuoteGenerationError::ParseInt => {
f.write_str("Could not parse integer when reading generation value")
}
QuoteGenerationError::BadProvider(provider) => f.write_str(&format!(
"Quote has provider which is not allowed: {}",
provider
)),
QuoteGenerationError::CannotFindTsmDir => f.write_str(
"Cannot find configfs-tsm directory - maybe your hardware does not support it",
),
}
}
}
impl From<std::io::Error> for QuoteGenerationError {
fn from(error: std::io::Error) -> QuoteGenerationError {
QuoteGenerationError::IO(error)
}
}
impl From<ParseIntError> for QuoteGenerationError {
fn from(_: ParseIntError) -> QuoteGenerationError {
QuoteGenerationError::ParseInt
}
}