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}