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