Skip to main content

tss_esapi/abstraction/
nv.rs

1// Copyright 2020 Contributors to the Parsec project.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    convert::{TryFrom, TryInto},
6    io::Read,
7};
8
9use crate::{
10    constants::{tss::*, CapabilityType, PropertyTag},
11    handles::{AuthHandle, NvIndexHandle, NvIndexTpmHandle, TpmHandle},
12    interface_types::resource_handles::NvAuth,
13    structures::{CapabilityData, MaxNvBuffer, Name, NvPublic},
14    Context, Error, Result, WrapperErrorKind,
15};
16
17/// Allows reading an NV Index completely, regardless of the max TPM NV buffer size
18pub fn read_full(
19    context: &mut Context,
20    auth_handle: NvAuth,
21    nv_index_handle: NvIndexTpmHandle,
22) -> Result<Vec<u8>> {
23    let mut rw = NvOpenOptions::ExistingIndex {
24        auth_handle,
25        nv_index_handle,
26    }
27    .open(context)?;
28    let mut result = Vec::with_capacity(rw.size());
29
30    let _ = rw.read_to_end(&mut result).map_err(|e| {
31        // Try to convert the error back into a tss-esapi::Error if it was one originally
32        match e.into_inner() {
33            None => Error::WrapperError(WrapperErrorKind::InvalidParam),
34            Some(e) => match e.downcast::<Error>() {
35                Ok(e) => *e,
36                Err(_) => Error::WrapperError(WrapperErrorKind::InvalidParam),
37            },
38        }
39    })?;
40
41    Ok(result)
42}
43
44/// Returns the NvPublic and Name associated with an NV index TPM handle
45///
46/// NOTE: This call _may_ close existing ESYS handles to the NV Index.
47fn get_nv_index_info(
48    context: &mut Context,
49    nv_index_tpm_handle: NvIndexTpmHandle,
50) -> Result<(NvPublic, Name)> {
51    context
52        .tr_from_tpm_public(nv_index_tpm_handle.into())
53        .and_then(|mut object_handle| {
54            context
55                .nv_read_public(NvIndexHandle::from(object_handle))
56                .inspect_err(|_| {
57                    let _ = context.tr_close(&mut object_handle);
58                })
59                .and_then(|(nv_public, name)| {
60                    context.tr_close(&mut object_handle)?;
61                    Ok((nv_public, name))
62                })
63        })
64}
65
66/// Lists all the currently defined NV Indexes' names and public components
67///
68/// NOTE: This call _may_ close existing ESYS handles to the existing NV Indexes.
69pub fn list(context: &mut Context) -> Result<Vec<(NvPublic, Name)>> {
70    context.execute_without_session(|ctx| {
71        ctx.get_capability(
72            CapabilityType::Handles,
73            TPM2_NV_INDEX_FIRST,
74            TPM2_PT_NV_INDEX_MAX,
75        )
76        .and_then(|(capability_data, _)| match capability_data {
77            CapabilityData::Handles(tpm_handles) => Ok(tpm_handles),
78            _ => Err(Error::local_error(WrapperErrorKind::WrongValueFromTpm)),
79        })
80        .and_then(|tpm_handles| {
81            tpm_handles
82                .iter()
83                .map(|&tpm_handle| get_nv_index_info(ctx, NvIndexTpmHandle::try_from(tpm_handle)?))
84                .collect()
85        })
86    })
87}
88
89/// Options and flags which can be used to determine how a non-volatile storage index is opened.
90#[non_exhaustive]
91#[derive(Debug, Clone)]
92pub enum NvOpenOptions {
93    /// Define a new NV space with given auth
94    NewIndex {
95        nv_public: NvPublic,
96        auth_handle: NvAuth,
97    },
98    /// Open the NV space at the given handle, with the given auth
99    ExistingIndex {
100        nv_index_handle: NvIndexTpmHandle,
101        auth_handle: NvAuth,
102    },
103}
104
105impl NvOpenOptions {
106    /// Opens a non-volatile storage index using the options specified by `self`
107    ///
108    /// The non-volatile storage index may be used for reading or writing or both.
109    pub fn open<'a>(&self, context: &'a mut Context) -> Result<NvReaderWriter<'a>> {
110        let buffer_size = max_nv_buffer_size(context)?;
111
112        let (data_size, nv_idx, auth_handle) = match self {
113            NvOpenOptions::ExistingIndex {
114                nv_index_handle,
115                auth_handle,
116            } => {
117                let nv_idx = TpmHandle::NvIndex(*nv_index_handle);
118                let nv_idx = context
119                    .execute_without_session(|ctx| ctx.tr_from_tpm_public(nv_idx))?
120                    .into();
121                (
122                    context
123                        .execute_without_session(|ctx| ctx.nv_read_public(nv_idx))
124                        .map(|(nvpub, _)| nvpub.data_size())?,
125                    nv_idx,
126                    auth_handle,
127                )
128            }
129            NvOpenOptions::NewIndex {
130                nv_public,
131                auth_handle,
132            } => (
133                nv_public.data_size(),
134                context.nv_define_space(
135                    AuthHandle::from(*auth_handle).try_into()?,
136                    None,
137                    nv_public.clone(),
138                )?,
139                auth_handle,
140            ),
141        };
142
143        Ok(NvReaderWriter {
144            context,
145            auth_handle: *auth_handle,
146            buffer_size,
147            nv_idx,
148            data_size,
149            offset: 0,
150        })
151    }
152}
153
154/// Get the maximum buffer size for an NV space.
155pub fn max_nv_buffer_size(ctx: &mut Context) -> Result<usize> {
156    Ok(ctx
157        .get_tpm_property(PropertyTag::NvBufferMax)?
158        .map(usize::try_from)
159        .transpose()
160        .map_err(|_| {
161            log::error!("Failed to obtain valid maximum NV buffer size");
162            Error::WrapperError(WrapperErrorKind::InternalError)
163        })?
164        .unwrap_or(MaxNvBuffer::MAX_SIZE))
165}
166
167/// Non-volatile storage index reader/writer
168///
169/// Provides methods and trait implementations to interact with a non-volatile storage index that has been opened.
170///
171/// Use [`NvOpenOptions::open`] to obtain an [`NvReaderWriter`] object.
172///
173/// NOTE: When the `NvReaderWriter` is dropped, any existing ESYS handles to NV Indexes _may_ be closed.
174#[derive(Debug)]
175pub struct NvReaderWriter<'a> {
176    context: &'a mut Context,
177    auth_handle: NvAuth,
178
179    buffer_size: usize,
180    nv_idx: NvIndexHandle,
181    data_size: usize,
182    offset: usize,
183}
184
185impl NvReaderWriter<'_> {
186    /// The size of the data in the non-volatile storage index
187    pub fn size(&self) -> usize {
188        self.data_size
189    }
190}
191
192impl Read for NvReaderWriter<'_> {
193    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
194        if self.data_size <= self.offset {
195            return Ok(0);
196        }
197
198        let desired_size = std::cmp::min(buf.len(), self.data_size - self.offset);
199        let size: u16 = std::cmp::min(self.buffer_size, desired_size) as u16;
200
201        let res = self
202            .context
203            .nv_read(self.auth_handle, self.nv_idx, size, self.offset as u16)
204            .map_err(std::io::Error::other)?;
205        buf[0..size as usize].copy_from_slice(&res);
206        self.offset += size as usize;
207
208        Ok(size.into())
209    }
210}
211
212impl std::io::Write for NvReaderWriter<'_> {
213    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
214        if self.data_size < self.offset {
215            return Ok(0);
216        }
217
218        let desired_size = std::cmp::min(buf.len(), self.data_size - self.offset);
219        let size = std::cmp::min(self.buffer_size, desired_size) as u16;
220
221        let data = buf[0..size.into()]
222            .try_into()
223            .map_err(std::io::Error::other)?;
224        self.context
225            .nv_write(self.auth_handle, self.nv_idx, data, self.offset as u16)
226            .map_err(std::io::Error::other)?;
227        self.offset += size as usize;
228
229        Ok(size.into())
230    }
231
232    fn flush(&mut self) -> std::io::Result<()> {
233        // Data isn't buffered
234        Ok(())
235    }
236}
237
238impl std::io::Seek for NvReaderWriter<'_> {
239    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
240        let inv_input_err = |_| {
241            std::io::Error::new(
242                std::io::ErrorKind::InvalidInput,
243                "invalid seek to a negative or overflowing position",
244            )
245        };
246        let (base, offset) = match pos {
247            std::io::SeekFrom::Start(offset) => {
248                (usize::try_from(offset).map_err(inv_input_err)?, 0)
249            }
250            std::io::SeekFrom::End(offset) => (self.data_size, offset),
251            std::io::SeekFrom::Current(offset) => (self.offset, offset),
252        };
253        let new_offset = i64::try_from(base)
254            .map_err(inv_input_err)?
255            .checked_add(offset)
256            .ok_or_else(|| {
257                std::io::Error::new(
258                    std::io::ErrorKind::InvalidInput,
259                    "invalid seek to a negative or overflowing position",
260                )
261            })?;
262        self.offset = new_offset.try_into().map_err(inv_input_err)?;
263        self.offset.try_into().map_err(inv_input_err)
264    }
265}
266
267impl Drop for NvReaderWriter<'_> {
268    fn drop(&mut self) {
269        let mut obj_handle = self.nv_idx.into();
270        let _ = self
271            .context
272            .execute_without_session(|ctx| ctx.tr_close(&mut obj_handle));
273    }
274}