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}