tss_esapi/
context.rs

1// Copyright 2020 Contributors to the Parsec project.
2// SPDX-License-Identifier: Apache-2.0
3mod handle_manager;
4use crate::{
5    attributes::SessionAttributesBuilder,
6    constants::{CapabilityType, PropertyTag, SessionType},
7    handles::{ObjectHandle, SessionHandle},
8    interface_types::{algorithm::HashingAlgorithm, session_handles::AuthSession},
9    structures::{CapabilityData, SymmetricDefinition},
10    tcti_ldr::{TabrmdConfig, TctiContext, TctiNameConf},
11    tss2_esys::*,
12    Error, Result, WrapperErrorKind as ErrorKind,
13};
14use handle_manager::HandleManager;
15use log::{error, info};
16use mbox::MBox;
17use std::collections::HashMap;
18use std::ptr::null_mut;
19
20/// Safe abstraction over an ESYS_CONTEXT.
21///
22/// Serves as a low-level abstraction interface to the TPM, providing a thin wrapper around the
23/// `unsafe` FFI calls. It is meant for more advanced uses of the TSS where control over all
24/// parameters is necessary or important.
25///
26/// The methods it exposes take the parameters advertised by the specification, with some of the
27/// parameters being passed as generated by `bindgen` and others in a more convenient/Rust-efficient
28/// way.
29///
30/// The context also keeps track of all object allocated and deallocated through it and, before
31/// being dropped, will attempt to close all outstanding handles. However, care must be taken by
32/// the client to not exceed the maximum number of slots available from the RM.
33///
34/// Code safety-wise, the methods should cover the two kinds of problems that might arise:
35/// * in terms of memory safety, all parameters passed down to the TSS are verified and the library
36///   stack is then trusted to provide back valid outputs
37/// * in terms of thread safety, all methods require a mutable reference to the context object,
38///   ensuring that no two threads can use the context at the same time for an operation (barring use
39///   of `unsafe` constructs on the client side)
40///   More testing and verification will be added to ensure this.
41///
42/// For most methods, if the wrapped TSS call fails and returns a non-zero `TPM2_RC`, a
43/// corresponding `Tss2ResponseCode` will be created and returned as an `Error`. Wherever this is
44/// not the case or additional error types can be returned, the method definition should mention
45/// it.
46#[derive(Debug)]
47pub struct Context {
48    /// Handle for the ESYS context object owned through an Mbox.
49    /// Wrapping the handle in an optional Mbox is done to allow the `Context` to be closed properly when the `Context` structure is dropped.
50    esys_context: Option<MBox<ESYS_CONTEXT>>,
51    sessions: (
52        Option<AuthSession>,
53        Option<AuthSession>,
54        Option<AuthSession>,
55    ),
56    /// TCTI context handle associated with the ESYS context.
57    /// As with the ESYS context, an optional Mbox wrapper allows the context to be deallocated.
58    _tcti_context: TctiContext,
59    /// Handle manager that keep tracks of the state of the handles and how they are to be
60    /// disposed.
61    handle_manager: HandleManager,
62    /// A cache of determined TPM limits
63    cached_tpm_properties: HashMap<PropertyTag, u32>,
64}
65
66// Implementation of the TPM commands
67mod tpm_commands;
68// Implementation of the ESAPI session administration
69// functions.
70mod session_administration;
71// Implementation of the general ESAPI ESYS_TR functions
72mod general_esys_tr;
73
74impl Context {
75    /// Create a new ESYS context based on the desired TCTI
76    ///
77    /// # Warning
78    /// The client is responsible for ensuring that the context can be initialized safely,
79    /// threading-wise. Some TCTI are not safe to execute with multiple commands in parallel.
80    /// If the sequence of commands to the TPM is interrupted by another application, commands
81    /// might fail unexpectedly.
82    /// If multiple applications are using the TPM in parallel, make sure to use the TABRMD TCTI
83    /// which will offer multi-user support to a single TPM device.
84    /// See the
85    /// [specification](https://trustedcomputinggroup.org/wp-content/uploads/TSS-TAB-and-Resource-Manager-ver1.0-rev16_Public_Review.pdf) for more information.
86    ///
87    /// # Errors
88    /// * if either `Tss2_TctiLdr_Initiialize` or `Esys_Initialize` fail, a corresponding
89    ///   Tss2ResponseCode will be returned
90    pub fn new(tcti_name_conf: TctiNameConf) -> Result<Self> {
91        let mut esys_context = null_mut();
92
93        let mut _tcti_context = TctiContext::initialize(tcti_name_conf)?;
94
95        let ret = unsafe {
96            Esys_Initialize(
97                &mut esys_context,
98                _tcti_context.tcti_context_ptr(),
99                null_mut(),
100            )
101        };
102        let ret = Error::from_tss_rc(ret);
103
104        if ret.is_success() {
105            let esys_context = unsafe { Some(MBox::from_raw(esys_context)) };
106            let context = Context {
107                esys_context,
108                sessions: (None, None, None),
109                _tcti_context,
110                handle_manager: HandleManager::new(),
111                cached_tpm_properties: HashMap::new(),
112            };
113            Ok(context)
114        } else {
115            error!("Error when creating a new context: {}", ret);
116            Err(ret)
117        }
118    }
119
120    /// Create a new ESYS context based on the TAB Resource Manager Daemon.
121    /// The TABRMD will make sure that multiple users can use the TPM safely.
122    ///
123    /// # Errors
124    /// * if either `Tss2_TctiLdr_Initiialize` or `Esys_Initialize` fail, a corresponding
125    ///   Tss2ResponseCode will be returned
126    pub fn new_with_tabrmd(tabrmd_conf: TabrmdConfig) -> Result<Self> {
127        Context::new(TctiNameConf::Tabrmd(tabrmd_conf))
128    }
129
130    /// Set the sessions to be used in calls to ESAPI.
131    ///
132    /// # Details
133    /// In some calls these sessions are optional and in others
134    /// they are required.
135    ///
136    /// # Example
137    ///
138    /// ```rust
139    /// # use tss_esapi::{Context, tcti_ldr::TctiNameConf,
140    /// #     constants::SessionType,
141    /// #     interface_types::algorithm::HashingAlgorithm,
142    /// #     structures::SymmetricDefinition,
143    /// # };
144    /// # // Create context
145    /// # let mut context =
146    /// #     Context::new(
147    /// #        TctiNameConf::from_environment_variable().expect("Failed to get TCTI"),
148    /// #     ).expect("Failed to create Context");
149    /// // Create auth session without key_handle, bind_handle
150    /// // and Nonce
151    /// let auth_session = context
152    ///     .start_auth_session(
153    ///         None,
154    ///         None,
155    ///         None,
156    ///         SessionType::Hmac,
157    ///         SymmetricDefinition::AES_256_CFB,
158    ///         HashingAlgorithm::Sha256,
159    ///     )
160    ///     .expect("Failed to create session");
161    ///
162    /// // Set auth_session as the first handle to be
163    /// // used in calls to ESAPI no matter if it None
164    /// // or not.
165    /// context.set_sessions((auth_session, None, None));
166    /// ```
167    pub fn set_sessions(
168        &mut self,
169        session_handles: (
170            Option<AuthSession>,
171            Option<AuthSession>,
172            Option<AuthSession>,
173        ),
174    ) {
175        self.sessions = session_handles;
176    }
177
178    /// Clears any sessions that have been set
179    ///
180    /// This will result in the None handle being
181    /// used in all calls to ESAPI.
182    ///
183    /// # Example
184    ///
185    /// ```rust
186    /// # use tss_esapi::{Context, tcti_ldr::TctiNameConf, interface_types::session_handles::AuthSession};
187    /// # // Create context
188    /// # let mut context =
189    /// #     Context::new(
190    /// #         TctiNameConf::from_environment_variable().expect("Failed to get TCTI"),
191    /// #     ).expect("Failed to create Context");
192    /// // Use password session for auth
193    /// context.set_sessions((Some(AuthSession::Password), None, None));
194    ///
195    /// // Clear auth sessions
196    /// context.clear_sessions();
197    /// ```
198    pub fn clear_sessions(&mut self) {
199        self.sessions = (None, None, None)
200    }
201
202    /// Returns the sessions that are currently set.
203    ///
204    /// # Example
205    ///
206    /// ```rust
207    /// # use tss_esapi::{Context, tcti_ldr::TctiNameConf, interface_types::session_handles::AuthSession};
208    /// # // Create context
209    /// # let mut context =
210    /// #     Context::new(
211    /// #         TctiNameConf::from_environment_variable().expect("Failed to get TCTI"),
212    /// #     ).expect("Failed to create Context");
213    /// // Use password session for auth
214    /// context.set_sessions((Some(AuthSession::Password), None, None));
215    ///
216    /// // Retrieve sessions in use
217    /// let (session_1, session_2, session_3) = context.sessions();
218    /// assert_eq!(Some(AuthSession::Password), session_1);
219    /// assert_eq!(None, session_2);
220    /// assert_eq!(None, session_3);
221    /// ```
222    pub fn sessions(
223        &self,
224    ) -> (
225        Option<AuthSession>,
226        Option<AuthSession>,
227        Option<AuthSession>,
228    ) {
229        self.sessions
230    }
231
232    /// Execute the closure in f with the specified set of sessions, and sets the original sessions back afterwards
233    pub fn execute_with_sessions<F, T>(
234        &mut self,
235        session_handles: (
236            Option<AuthSession>,
237            Option<AuthSession>,
238            Option<AuthSession>,
239        ),
240        f: F,
241    ) -> T
242    where
243        // We only need to call f once, so it can be FnOnce
244        F: FnOnce(&mut Context) -> T,
245    {
246        let oldses = self.sessions();
247        self.set_sessions(session_handles);
248
249        let res = f(self);
250
251        self.set_sessions(oldses);
252
253        res
254    }
255
256    /// Executes the closure with a single session set, and the others set to None
257    pub fn execute_with_session<F, T>(&mut self, session_handle: Option<AuthSession>, f: F) -> T
258    where
259        // We only need to call f once, so it can be FnOnce
260        F: FnOnce(&mut Context) -> T,
261    {
262        self.execute_with_sessions((session_handle, None, None), f)
263    }
264
265    /// Executes the closure without any sessions,
266    pub fn execute_without_session<F, T>(&mut self, f: F) -> T
267    where
268        // We only need to call f once, so it can be FnOnce
269        F: FnOnce(&mut Context) -> T,
270    {
271        self.execute_with_sessions((None, None, None), f)
272    }
273
274    /// Executes the closure with a newly generated empty session
275    ///
276    /// # Details
277    /// The session attributes for the generated empty session that
278    /// is used to execute closure will have the attributes decrypt
279    /// and encrypt set.
280    pub fn execute_with_nullauth_session<F, T, E>(&mut self, f: F) -> std::result::Result<T, E>
281    where
282        // We only need to call f once, so it can be FnOnce
283        F: FnOnce(&mut Context) -> std::result::Result<T, E>,
284        E: From<Error>,
285    {
286        let auth_session = match self.start_auth_session(
287            None,
288            None,
289            None,
290            SessionType::Hmac,
291            SymmetricDefinition::AES_128_CFB,
292            HashingAlgorithm::Sha256,
293        )? {
294            Some(ses) => ses,
295            None => return Err(E::from(Error::local_error(ErrorKind::WrongValueFromTpm))),
296        };
297
298        let (session_attributes, session_attributes_mask) = SessionAttributesBuilder::new()
299            .with_decrypt(true)
300            .with_encrypt(true)
301            .build();
302        self.tr_sess_set_attributes(auth_session, session_attributes, session_attributes_mask)?;
303
304        let res = self.execute_with_session(Some(auth_session), f);
305
306        self.flush_context(SessionHandle::from(auth_session).into())?;
307
308        res
309    }
310
311    /// Execute the closure in f, and clear up the object after it's done before returning the result
312    /// This is a convenience function that ensures object is always closed, even if an error occurs
313    pub fn execute_with_temporary_object<F, T>(&mut self, object: ObjectHandle, f: F) -> Result<T>
314    where
315        F: FnOnce(&mut Context, ObjectHandle) -> Result<T>,
316    {
317        let res = f(self, object);
318
319        self.flush_context(object)?;
320
321        res
322    }
323
324    /// Determine a TPM property
325    ///
326    /// # Details
327    /// Returns the value of the provided `TpmProperty` if
328    /// the TPM has a value for it else None will be returned.
329    /// If None is returned then use default from specification.
330    ///
331    /// # Errors
332    /// If the TPM returns a value that is wrong when
333    /// its capabilities is being retrieved then a
334    /// `WrongValueFromTpm` is returned.
335    ///
336    /// # Example
337    ///
338    /// ```rust
339    /// # use tss_esapi::{Context, tcti_ldr::TctiNameConf, constants::PropertyTag};
340    /// # use std::str::FromStr;
341    /// # // Create context
342    /// # let mut context =
343    /// #     Context::new(
344    /// #         TctiNameConf::from_environment_variable().expect("Failed to get TCTI"),
345    /// #     ).expect("Failed to create Context");
346    /// let rev = context
347    ///     .get_tpm_property(PropertyTag::Revision)
348    ///     .expect("Wrong value from TPM")
349    ///     .expect("Value is not supported");
350    /// ```
351    pub fn get_tpm_property(&mut self, property: PropertyTag) -> Result<Option<u32>> {
352        // Return cached value if it exists
353        if let Some(&val) = self.cached_tpm_properties.get(&property) {
354            return Ok(Some(val));
355        }
356
357        let (capabs, _) = self.execute_without_session(|ctx| {
358            ctx.get_capability(CapabilityType::TpmProperties, property.into(), 4)
359        })?;
360
361        let props = match capabs {
362            CapabilityData::TpmProperties(props) => props,
363            _ => return Err(Error::WrapperError(ErrorKind::WrongValueFromTpm)),
364        };
365
366        for tagged_property in props {
367            // If we are returned a property we don't know, just ignore it
368            let _ = self
369                .cached_tpm_properties
370                .insert(tagged_property.property(), tagged_property.value());
371        }
372
373        if let Some(val) = self.cached_tpm_properties.get(&property) {
374            return Ok(Some(*val));
375        }
376        Ok(None)
377    }
378
379    // ////////////////////////////////////////////////////////////////////////
380    //  Private Methods Section
381    // ////////////////////////////////////////////////////////////////////////
382
383    /// Returns a mutable reference to the native ESYS context handle.
384    fn mut_context(&mut self) -> *mut ESYS_CONTEXT {
385        self.esys_context
386            .as_mut()
387            .map(MBox::<ESYS_CONTEXT>::as_mut_ptr)
388            .unwrap() // will only fail if called from Drop after .take()
389    }
390
391    /// Private method for retrieving the ESYS session handle for
392    /// the optional session 1.
393    fn optional_session_1(&self) -> ESYS_TR {
394        SessionHandle::from(self.sessions.0).into()
395    }
396
397    /// Private method for retrieving the ESYS session handle for
398    /// the optional session 2.
399    fn optional_session_2(&self) -> ESYS_TR {
400        SessionHandle::from(self.sessions.1).into()
401    }
402
403    /// Private method for retrieving the ESYS session handle for
404    /// the optional session 3.
405    fn optional_session_3(&self) -> ESYS_TR {
406        SessionHandle::from(self.sessions.2).into()
407    }
408
409    /// Private method that returns the required
410    /// session handle 1 if it is available else
411    /// returns an error.
412    fn required_session_1(&self) -> Result<ESYS_TR> {
413        self.sessions
414            .0
415            .map(|v| SessionHandle::from(v).into())
416            .ok_or_else(|| {
417                error!("Missing session handle for authorization (authSession1 = None)");
418                Error::local_error(ErrorKind::MissingAuthSession)
419            })
420    }
421
422    /// Private method that returns the required
423    /// session handle 2 if it is available else
424    /// returns an error.
425    fn required_session_2(&self) -> Result<ESYS_TR> {
426        self.sessions
427            .1
428            .map(|v| SessionHandle::from(v).into())
429            .ok_or_else(|| {
430                error!("Missing session handle for authorization (authSession2 = None)");
431                Error::local_error(ErrorKind::MissingAuthSession)
432            })
433    }
434
435    /// Private function for handling that has been allocated with
436    /// C memory allocation functions in TSS.
437    fn ffi_data_to_owned<T>(data_ptr: *mut T) -> T {
438        MBox::into_inner(unsafe { MBox::from_raw(data_ptr) })
439    }
440}
441
442impl Drop for Context {
443    fn drop(&mut self) {
444        info!("Closing context.");
445
446        // Flush handles
447        for handle in self.handle_manager.handles_to_flush() {
448            info!("Flushing handle {}", ESYS_TR::from(handle));
449            if let Err(e) = self.flush_context(handle) {
450                error!("Error when dropping the context: {}", e);
451            }
452        }
453
454        // Close handles
455        for handle in self.handle_manager.handles_to_close().iter_mut() {
456            info!("Closing handle {}", ESYS_TR::from(*handle));
457            if let Err(e) = self.tr_close(handle) {
458                error!("Error when dropping context: {}.", e);
459            }
460        }
461
462        // Check if all handles have been cleaned up proeprly.
463        if self.handle_manager.has_open_handles() {
464            error!("Not all handles have had their resources successfully released");
465        }
466
467        // Close the context.
468        unsafe {
469            Esys_Finalize(
470                &mut self
471                    .esys_context
472                    .take()
473                    .map(MBox::<ESYS_CONTEXT>::into_raw)
474                    .unwrap(), // should not fail based on how the context is initialised/used
475            )
476        };
477        info!("Context closed.");
478    }
479}