use std::{
convert::{TryFrom, TryInto},
io::Read,
};
use crate::{
constants::{tss::*, CapabilityType, PropertyTag},
handles::{AuthHandle, NvIndexHandle, NvIndexTpmHandle, TpmHandle},
interface_types::resource_handles::NvAuth,
structures::{CapabilityData, MaxNvBuffer, Name, NvPublic},
Context, Error, Result, WrapperErrorKind,
};
pub fn read_full(
context: &mut Context,
auth_handle: NvAuth,
nv_index_handle: NvIndexTpmHandle,
) -> Result<Vec<u8>> {
let mut rw = NvOpenOptions::ExistingIndex {
auth_handle,
nv_index_handle,
}
.open(context)?;
let mut result = Vec::with_capacity(rw.size());
let _ = rw.read_to_end(&mut result).map_err(|e| {
match e.into_inner() {
None => Error::WrapperError(WrapperErrorKind::InvalidParam),
Some(e) => match e.downcast::<Error>() {
Ok(e) => *e,
Err(_) => Error::WrapperError(WrapperErrorKind::InvalidParam),
},
}
})?;
Ok(result)
}
fn get_nv_index_info(
context: &mut Context,
nv_index_tpm_handle: NvIndexTpmHandle,
) -> Result<(NvPublic, Name)> {
context
.tr_from_tpm_public(nv_index_tpm_handle.into())
.and_then(|mut object_handle| {
context
.nv_read_public(NvIndexHandle::from(object_handle))
.map_err(|e| {
let _ = context.tr_close(&mut object_handle);
e
})
.and_then(|(nv_public, name)| {
context.tr_close(&mut object_handle)?;
Ok((nv_public, name))
})
})
}
pub fn list(context: &mut Context) -> Result<Vec<(NvPublic, Name)>> {
context.execute_without_session(|ctx| {
ctx.get_capability(
CapabilityType::Handles,
TPM2_NV_INDEX_FIRST,
TPM2_PT_NV_INDEX_MAX,
)
.and_then(|(capability_data, _)| match capability_data {
CapabilityData::Handles(tpm_handles) => Ok(tpm_handles),
_ => Err(Error::local_error(WrapperErrorKind::WrongValueFromTpm)),
})
.and_then(|tpm_handles| {
tpm_handles
.iter()
.map(|&tpm_handle| get_nv_index_info(ctx, NvIndexTpmHandle::try_from(tpm_handle)?))
.collect()
})
})
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum NvOpenOptions {
NewIndex {
nv_public: NvPublic,
auth_handle: NvAuth,
},
ExistingIndex {
nv_index_handle: NvIndexTpmHandle,
auth_handle: NvAuth,
},
}
impl NvOpenOptions {
pub fn open<'a>(&self, context: &'a mut Context) -> Result<NvReaderWriter<'a>> {
let buffer_size = max_nv_buffer_size(context)?;
let (data_size, nv_idx, auth_handle) = match self {
NvOpenOptions::ExistingIndex {
nv_index_handle,
auth_handle,
} => {
let nv_idx = TpmHandle::NvIndex(*nv_index_handle);
let nv_idx = context
.execute_without_session(|ctx| ctx.tr_from_tpm_public(nv_idx))?
.into();
(
context
.execute_without_session(|ctx| ctx.nv_read_public(nv_idx))
.map(|(nvpub, _)| nvpub.data_size())?,
nv_idx,
auth_handle,
)
}
NvOpenOptions::NewIndex {
nv_public,
auth_handle,
} => (
nv_public.data_size(),
context.nv_define_space(
AuthHandle::from(*auth_handle).try_into()?,
None,
nv_public.clone(),
)?,
auth_handle,
),
};
Ok(NvReaderWriter {
context,
auth_handle: *auth_handle,
buffer_size,
nv_idx,
data_size,
offset: 0,
})
}
}
pub fn max_nv_buffer_size(ctx: &mut Context) -> Result<usize> {
Ok(ctx
.get_tpm_property(PropertyTag::NvBufferMax)?
.map(usize::try_from)
.transpose()
.map_err(|_| {
log::error!("Failed to obtain valid maximum NV buffer size");
Error::WrapperError(WrapperErrorKind::InternalError)
})?
.unwrap_or(MaxNvBuffer::MAX_SIZE))
}
#[derive(Debug)]
pub struct NvReaderWriter<'a> {
context: &'a mut Context,
auth_handle: NvAuth,
buffer_size: usize,
nv_idx: NvIndexHandle,
data_size: usize,
offset: usize,
}
impl NvReaderWriter<'_> {
pub fn size(&self) -> usize {
self.data_size
}
}
impl Read for NvReaderWriter<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.data_size <= self.offset {
return Ok(0);
}
let desired_size = std::cmp::min(buf.len(), self.data_size - self.offset);
let size: u16 = std::cmp::min(self.buffer_size, desired_size) as u16;
let res = self
.context
.nv_read(self.auth_handle, self.nv_idx, size, self.offset as u16)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
buf[0..size as usize].copy_from_slice(&res);
self.offset += size as usize;
Ok(size.into())
}
}
impl std::io::Write for NvReaderWriter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.data_size < self.offset {
return Ok(0);
}
let desired_size = std::cmp::min(buf.len(), self.data_size - self.offset);
let size = std::cmp::min(self.buffer_size, desired_size) as u16;
let data = buf[0..size.into()]
.try_into()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
self.context
.nv_write(self.auth_handle, self.nv_idx, data, self.offset as u16)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
self.offset += size as usize;
Ok(size.into())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl std::io::Seek for NvReaderWriter<'_> {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
let inv_input_err = |_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"invalid seek to a negative or overflowing position",
)
};
let (base, offset) = match pos {
std::io::SeekFrom::Start(offset) => {
(usize::try_from(offset).map_err(inv_input_err)?, 0)
}
std::io::SeekFrom::End(offset) => (self.data_size, offset),
std::io::SeekFrom::Current(offset) => (self.offset, offset),
};
let new_offset = i64::try_from(base)
.map_err(inv_input_err)?
.checked_add(offset)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"invalid seek to a negative or overflowing position",
)
})?;
self.offset = new_offset.try_into().map_err(inv_input_err)?;
self.offset.try_into().map_err(inv_input_err)
}
}
impl Drop for NvReaderWriter<'_> {
fn drop(&mut self) {
let mut obj_handle = self.nv_idx.into();
let _ = self
.context
.execute_without_session(|ctx| ctx.tr_close(&mut obj_handle));
}
}