Skip to main content

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