tpm2_device/
lib.rs

1//! SPDX-License-Identifier: GPL-3-0-or-later
2//! Copyright (c) 2025 Opinsys Oy
3//! Copyright (c) 2024-2025 Jarkko Sakkinen
4
5#![deny(clippy::all)]
6#![deny(clippy::pedantic)]
7
8use nix::{
9    fcntl,
10    poll::{poll, PollFd, PollFlags},
11};
12use std::{
13    cell::RefCell,
14    collections::HashMap,
15    fs::{File, OpenOptions},
16    io::{Read, Write},
17    num::TryFromIntError,
18    os::fd::{AsFd, AsRawFd},
19    path::Path,
20    rc::Rc,
21    time::Instant,
22};
23
24use thiserror::Error;
25use tpm2_crypto::{tpm_make_name, Error as CryptoError};
26use tpm2_policy_language::{TpmHandleClass, TpmHandleRef};
27use tpm2_protocol::{
28    constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
29    data::{
30        Tpm2bName, TpmCap, TpmCc, TpmHt, TpmPt, TpmRc, TpmRcBase, TpmSt, TpmsAlgProperty,
31        TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmtPublic, TpmuCapabilities,
32    },
33    frame::{
34        tpm_marshal_command, tpm_unmarshal_response, TpmAuthResponses, TpmContextLoadCommand,
35        TpmContextSaveCommand, TpmEvictControlCommand, TpmFlushContextCommand, TpmFrame,
36        TpmGetCapabilityCommand, TpmGetCapabilityResponse, TpmReadPublicCommand, TpmResponse,
37    },
38    TpmHandle, TpmWriter,
39};
40use tracing::trace;
41
42/// A type-erased object safe TPM command object.
43pub trait TpmCommandObject: TpmFrame {}
44impl<T> TpmCommandObject for T where T: TpmFrame {}
45
46/// Errors that can occur when talking to a TPM device.
47#[derive(Debug, Error)]
48pub enum TpmDeviceError {
49    #[error("device is already borrowed")]
50    AlreadyBorrowed,
51    #[error("capability not found: {0}")]
52    CapabilityMissing(TpmCap),
53    #[error("operation interrupted by user")]
54    Interrupted,
55    #[error("invalid response")]
56    InvalidResponse,
57    #[error("device not available")]
58    NotAvailable,
59
60    /// Marshaling a TPM protocol encoded object failed.
61    #[error("marshal: {0}")]
62    Marshal(tpm2_protocol::TpmProtocolError),
63
64    /// Unmarshaling a TPM protocol encoded object failed.
65    #[error("unmarshal: {0}")]
66    Unmarshal(tpm2_protocol::TpmProtocolError),
67
68    #[error("response mismatch: {0}")]
69    ResponseMismatch(TpmCc),
70    #[error("TPM command timed out")]
71    Timeout,
72    #[error("unexpected EOF")]
73    UnexpectedEof,
74    #[error("int decode: {0}")]
75    IntDecode(#[from] TryFromIntError),
76    #[error("I/O: {0}")]
77    Io(#[from] std::io::Error),
78    #[error("syscall: {0}")]
79    Nix(#[from] nix::Error),
80    #[error("crypto: {0}")]
81    InvalidCrypto(#[from] CryptoError),
82    #[error("TPM return code: {0}")]
83    TpmRc(TpmRc),
84}
85
86impl From<TpmRc> for TpmDeviceError {
87    fn from(rc: TpmRc) -> Self {
88        Self::TpmRc(rc)
89    }
90}
91
92/// Executes a closure with a mutable reference to a `TpmDevice`.
93///
94/// This helper function centralizes the boilerplate for safely acquiring a
95/// mutable borrow of a `TpmDevice` from the shared `Rc<RefCell<...>>`.
96///
97/// # Errors
98///
99/// Returns [`TpmDeviceError::NotAvailable`] when no device is present and
100/// [`TpmDeviceError::AlreadyBorrowed`] when the device is already mutably
101/// borrowed, both converted into the caller's error type `E`.
102/// Propagates any error returned by the closure `f`.
103pub fn with_device<F, T, E>(device: Option<Rc<RefCell<TpmDevice>>>, f: F) -> Result<T, E>
104where
105    F: FnOnce(&mut TpmDevice) -> Result<T, E>,
106    E: From<TpmDeviceError>,
107{
108    let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
109    let mut device_guard = device_rc
110        .try_borrow_mut()
111        .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
112    f(&mut device_guard)
113}
114
115pub struct TpmDevice {
116    file: File,
117    name_cache: HashMap<u32, (TpmtPublic, Tpm2bName)>,
118    interrupt_check: Box<dyn Fn() -> bool>,
119    resp_buf: Vec<u8>,
120}
121
122impl std::fmt::Debug for TpmDevice {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        f.debug_struct("Device")
125            .field("file", &self.file)
126            .field("name_cache", &self.name_cache)
127            .finish_non_exhaustive()
128    }
129}
130
131impl TpmDevice {
132    const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
133
134    /// Opens the TPM device file and sets it to non-blocking mode.
135    ///
136    /// # Errors
137    ///
138    /// Returns [`TpmDeviceError::Io`] if the device file cannot be opened and
139    /// [`TpmDeviceError::Nix`] if configuring the file descriptor flags fails.
140    pub fn open(
141        path: &Path,
142        interrupt_check: Box<dyn Fn() -> bool>,
143    ) -> Result<Self, TpmDeviceError> {
144        let file = OpenOptions::new()
145            .read(true)
146            .write(true)
147            .open(path)
148            .map_err(TpmDeviceError::Io)?;
149
150        let fd = file.as_raw_fd();
151        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
152        let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
153        oflags.insert(fcntl::OFlag::O_NONBLOCK);
154        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
155
156        Ok(Self {
157            file,
158            name_cache: HashMap::new(),
159            interrupt_check,
160            resp_buf: Vec::with_capacity(TPM_MAX_COMMAND_SIZE as usize),
161        })
162    }
163
164    fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
165        let fd = self.file.as_fd();
166        let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
167
168        let num_events = match poll(&mut fds, 100u16) {
169            Ok(num) => num,
170            Err(nix::Error::EINTR) => return Ok(0),
171            Err(e) => return Err(e.into()),
172        };
173
174        if num_events == 0 {
175            return Ok(0);
176        }
177
178        let revents = fds[0].revents().unwrap_or(PollFlags::empty());
179
180        if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
181            return Err(TpmDeviceError::UnexpectedEof);
182        }
183
184        if revents.contains(PollFlags::POLLIN) {
185            match self.file.read(buf) {
186                Ok(0) => Err(TpmDeviceError::UnexpectedEof),
187                Ok(n) => Ok(n),
188                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
189                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
190                Err(e) => Err(e.into()),
191            }
192        } else if revents.contains(PollFlags::POLLHUP) {
193            Err(TpmDeviceError::UnexpectedEof)
194        } else {
195            Ok(0)
196        }
197    }
198
199    /// Performs the whole TPM command transmission process.
200    ///
201    /// # Errors
202    ///
203    /// Returns [`TpmDeviceError::Interrupted`] when the interrupt callback
204    /// requests cancellation.
205    /// Returns [`TpmDeviceError::Timeout`] when the TPM does not respond within
206    /// the configured timeout.
207    /// Returns [`TpmDeviceError::Io`] when a write, flush, or read operation on
208    /// the device file fails.
209    /// Returns [`TpmDeviceError::Nix`] when polling the device file descriptor
210    /// fails.
211    /// Returns [`TpmDeviceError::InvalidResponse`] or
212    /// [`TpmDeviceError::UnexpectedEof`] when the TPM reply is malformed,
213    /// truncated, or longer than the announced size.
214    /// Returns [`TpmDeviceError::Marshal`] or [`TpmDeviceError::Unmarshal`]
215    /// when encoding the command or decoding the response fails.
216    /// Returns [`TpmDeviceError::TpmRc`] when the TPM returns an error code.
217    pub fn transmit<C: TpmCommandObject>(
218        &mut self,
219        command: &C,
220        sessions: &[TpmsAuthCommand],
221    ) -> Result<(TpmResponse, TpmAuthResponses), TpmDeviceError> {
222        let command_vec = TpmDevice::build_command_buffer(command, sessions)?;
223        let cc = command.cc();
224
225        self.file.write_all(&command_vec)?;
226        self.file.flush()?;
227
228        let start_time = Instant::now();
229        self.resp_buf.clear();
230        let mut total_size: Option<usize> = None;
231        let mut temp_buf = [0u8; 1024];
232
233        loop {
234            if (self.interrupt_check)() {
235                return Err(TpmDeviceError::Interrupted);
236            }
237            if start_time.elapsed() > std::time::Duration::from_secs(120) {
238                return Err(TpmDeviceError::Timeout);
239            }
240
241            let n = self.receive(&mut temp_buf)?;
242            if n > 0 {
243                self.resp_buf.extend_from_slice(&temp_buf[..n]);
244            }
245
246            if total_size.is_none() && self.resp_buf.len() >= 10 {
247                let Ok(size_bytes): Result<[u8; 4], _> = self.resp_buf[2..6].try_into() else {
248                    return Err(TpmDeviceError::InvalidResponse);
249                };
250                let size = u32::from_be_bytes(size_bytes) as usize;
251                if !(10..=TPM_MAX_COMMAND_SIZE as usize).contains(&size) {
252                    return Err(TpmDeviceError::InvalidResponse);
253                }
254                total_size = Some(size);
255            }
256
257            if let Some(size) = total_size {
258                if self.resp_buf.len() == size {
259                    break;
260                }
261                if self.resp_buf.len() > size {
262                    return Err(TpmDeviceError::InvalidResponse);
263                }
264            }
265        }
266
267        let result = tpm_unmarshal_response(cc, &self.resp_buf).map_err(TpmDeviceError::Unmarshal);
268        trace!("{} R: {}", cc, hex::encode(&self.resp_buf));
269        Ok(result??)
270    }
271
272    fn build_command_buffer<C: TpmCommandObject>(
273        command: &C,
274        sessions: &[TpmsAuthCommand],
275    ) -> Result<Vec<u8>, TpmDeviceError> {
276        let cc = command.cc();
277        let tag = if sessions.is_empty() {
278            TpmSt::NoSessions
279        } else {
280            TpmSt::Sessions
281        };
282        let mut buf = vec![0u8; TPM_MAX_COMMAND_SIZE as usize];
283        let len = {
284            let mut writer = TpmWriter::new(&mut buf);
285            tpm_marshal_command(command, tag, sessions, &mut writer)
286                .map_err(TpmDeviceError::Marshal)?;
287            writer.len()
288        };
289        buf.truncate(len);
290
291        trace!("{} C: {}", cc, hex::encode(&buf));
292        Ok(buf)
293    }
294
295    /// Fetches a complete list of capabilities from the TPM, handling pagination.
296    ///
297    /// # Errors
298    ///
299    /// Propagates any [`TpmDeviceError`] returned by
300    /// [`TpmDevice::get_capability_page`] or by the `extract` closure.
301    pub fn get_capability<T, F, N>(
302        &mut self,
303        cap: TpmCap,
304        property_start: u32,
305        count: u32,
306        mut extract: F,
307        next_prop: N,
308    ) -> Result<Vec<T>, TpmDeviceError>
309    where
310        T: Copy,
311        F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
312        N: Fn(&T) -> u32,
313    {
314        let mut results = Vec::new();
315        let mut prop = property_start;
316        loop {
317            let (more_data, cap_data) = self.get_capability_page(cap, prop, count)?;
318            let items: &[T] = extract(&cap_data.data)?;
319            results.extend_from_slice(items);
320
321            if more_data {
322                if let Some(last) = items.last() {
323                    prop = next_prop(last);
324                } else {
325                    break;
326                }
327            } else {
328                break;
329            }
330        }
331        Ok(results)
332    }
333
334    /// Retrieves all algorithm properties supported by the TPM.
335    ///
336    /// # Errors
337    ///
338    /// Returns [`TpmDeviceError::IntDecode`] if the handle count cannot be
339    /// represented as `u32`. Propagates any [`TpmDeviceError`] from
340    /// [`TpmDevice::get_capability`], including
341    /// [`TpmDeviceError::CapabilityMissing`] when the TPM does not report
342    /// algorithm properties.
343    pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
344        self.get_capability(
345            TpmCap::Algs,
346            0,
347            u32::try_from(MAX_HANDLES)?,
348            |caps| match caps {
349                TpmuCapabilities::Algs(algs) => Ok(algs),
350                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
351            },
352            |last| last.alg as u32 + 1,
353        )
354    }
355
356    /// Retrieves all handles of a specific type from the TPM.
357    ///
358    /// # Errors
359    ///
360    /// Returns [`TpmDeviceError::IntDecode`] if the handle count cannot be
361    /// represented as `u32`. Propagates any [`TpmDeviceError`] from
362    /// [`TpmDevice::get_capability`], including
363    /// [`TpmDeviceError::CapabilityMissing`] when the TPM does not report
364    /// handles of the requested class.
365    pub fn fetch_handles(&mut self, class: u32) -> Result<Vec<TpmHandleRef>, TpmDeviceError> {
366        self.get_capability(
367            TpmCap::Handles,
368            class,
369            u32::try_from(MAX_HANDLES)?,
370            |caps| match caps {
371                TpmuCapabilities::Handles(handles) => Ok(handles),
372                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
373            },
374            |last| *last + 1,
375        )
376        .map(|handles| {
377            handles
378                .into_iter()
379                .map(|h| TpmHandleRef::new(TpmHandleClass::Tpm, h))
380                .collect()
381        })
382    }
383
384    /// Fetches and returns one page of capabilities of a certain type from the
385    /// TPM.
386    ///
387    /// # Errors
388    ///
389    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::transmit`]. Returns
390    /// [`TpmDeviceError::ResponseMismatch`] when the TPM response does not
391    /// contain `TPM2_GetCapability` data.
392    pub fn get_capability_page(
393        &mut self,
394        cap: TpmCap,
395        property: u32,
396        count: u32,
397    ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
398        let cmd = TpmGetCapabilityCommand {
399            cap,
400            property,
401            property_count: count,
402        };
403
404        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
405        let TpmGetCapabilityResponse {
406            more_data,
407            capability_data,
408        } = resp
409            .GetCapability()
410            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::GetCapability))?;
411
412        Ok((more_data.into(), capability_data))
413    }
414
415    /// Reads a specific TPM property.
416    ///
417    /// # Errors
418    ///
419    /// Returns [`TpmDeviceError::CapabilityMissing`] if the TPM does not report
420    /// the requested property. Propagates any [`TpmDeviceError`] from
421    /// [`TpmDevice::get_capability_page`].
422    pub fn get_tpm_property(&mut self, property: TpmPt) -> Result<u32, TpmDeviceError> {
423        let (_, cap_data) = self.get_capability_page(TpmCap::TpmProperties, property as u32, 1)?;
424
425        let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
426            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
427        };
428
429        let Some(prop) = props.first() else {
430            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
431        };
432
433        Ok(prop.value)
434    }
435
436    /// Reads the public area of a TPM object.
437    ///
438    /// # Errors
439    ///
440    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::transmit`]. Returns
441    /// [`TpmDeviceError::ResponseMismatch`] when the TPM response does not
442    /// contain `TPM2_ReadPublic` data.
443    pub fn read_public(
444        &mut self,
445        handle: TpmHandle,
446    ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
447        if let Some(cached) = self.name_cache.get(&handle.0) {
448            return Ok(cached.clone());
449        }
450
451        let cmd = TpmReadPublicCommand {
452            object_handle: handle,
453        };
454        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
455
456        let read_public_resp = resp
457            .ReadPublic()
458            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ReadPublic))?;
459
460        let public = read_public_resp.out_public.inner;
461        let name = read_public_resp.name;
462
463        self.name_cache.insert(handle.0, (public.clone(), name));
464        Ok((public, name))
465    }
466
467    /// Finds a persistent handle by its public area.
468    ///
469    /// # Errors
470    ///
471    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::fetch_handles`] and
472    /// [`TpmDevice::read_public`], except for TPM reference and handle errors
473    /// with base [`TpmRcBase::ReferenceH0`] or [`TpmRcBase::Handle`], which are
474    /// treated as invalid handles and skipped.
475    pub fn find_persistent(
476        &mut self,
477        target: &TpmtPublic,
478    ) -> Result<Option<(TpmHandle, Tpm2bName)>, TpmDeviceError> {
479        let handles = self.fetch_handles((TpmHt::Persistent as u32) << 24)?;
480        for handle in handles {
481            if let Some(handle_val) = handle.value() {
482                match self.read_public(handle_val.into()) {
483                    Ok((public, name)) => {
484                        if public == *target {
485                            return Ok(Some((handle_val.into(), name)));
486                        }
487                    }
488                    Err(TpmDeviceError::TpmRc(rc)) => {
489                        let base = rc.base();
490                        if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
491                            continue;
492                        }
493                        return Err(TpmDeviceError::TpmRc(rc));
494                    }
495                    Err(e) => return Err(e),
496                }
497            }
498        }
499        Ok(None)
500    }
501
502    /// Finds a persistent handle by its `Tpm2bName`.
503    ///
504    /// # Errors
505    ///
506    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::fetch_handles`] and
507    /// [`TpmDevice::read_public`], except for TPM reference and handle errors
508    /// with base [`TpmRcBase::ReferenceH0`] or [`TpmRcBase::Handle`], which are
509    /// treated as invalid handles and skipped. Returns
510    /// [`TpmDeviceError::InvalidCrypto`] when computing the calculated name
511    /// with [`tpm_make_name`] fails.
512    pub fn find_persistent_by_name(
513        &mut self,
514        target_name: &Tpm2bName,
515    ) -> Result<Option<TpmHandle>, TpmDeviceError> {
516        let handles = self.fetch_handles((TpmHt::Persistent as u32) << 24)?;
517        for handle in handles {
518            if let Some(handle_val) = handle.value() {
519                match self.read_public(handle_val.into()) {
520                    Ok((public, name)) => {
521                        if name == *target_name {
522                            return Ok(Some(handle_val.into()));
523                        }
524                        let calculated_name = tpm_make_name(&public)?;
525                        if calculated_name == *target_name {
526                            return Ok(Some(handle_val.into()));
527                        }
528                    }
529                    Err(TpmDeviceError::TpmRc(rc)) => {
530                        let base = rc.base();
531                        if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
532                            continue;
533                        }
534                        return Err(TpmDeviceError::TpmRc(rc));
535                    }
536                    Err(e) => return Err(e),
537                }
538            }
539        }
540        Ok(None)
541    }
542
543    /// Saves the context of a transient object or session.
544    ///
545    /// # Errors
546    ///
547    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::transmit`]. Returns
548    /// [`TpmDeviceError::ResponseMismatch`] when the TPM response does not
549    /// contain `TPM2_ContextSave` data.
550    pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
551        let cmd = TpmContextSaveCommand { save_handle };
552        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
553        let save_resp = resp
554            .ContextSave()
555            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ContextSave))?;
556        Ok(save_resp.context)
557    }
558
559    /// Loads a TPM context and returns the handle.
560    ///
561    /// # Errors
562    ///
563    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::transmit`]. Returns
564    /// [`TpmDeviceError::ResponseMismatch`] when the TPM response does not
565    /// contain `TPM2_ContextLoad` data.
566    pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
567        let cmd = TpmContextLoadCommand { context };
568        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
569        let resp_inner = resp
570            .ContextLoad()
571            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ContextLoad))?;
572        Ok(resp_inner.loaded_handle)
573    }
574
575    /// Flushes a transient object or session from the TPM and removes it from the
576    /// cache.
577    ///
578    /// # Errors
579    ///
580    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::transmit`].
581    pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
582        self.name_cache.remove(&handle.0);
583        let cmd = TpmFlushContextCommand {
584            flush_handle: handle,
585        };
586        self.transmit(&cmd, Self::NO_SESSIONS)?;
587        Ok(())
588    }
589
590    /// Loads a session context and then flushes the resulting handle.
591    ///
592    /// # Errors
593    ///
594    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::load_context`] or
595    /// [`TpmDevice::flush_context`] except for TPM reference errors with base
596    /// [`TpmRcBase::ReferenceH0`] or [`TpmRcBase::Handle`], which are treated
597    /// as a successful no-op.
598    pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
599        match self.load_context(context) {
600            Ok(handle) => self.flush_context(handle),
601            Err(TpmDeviceError::TpmRc(rc)) => {
602                let base = rc.base();
603                if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
604                    Ok(())
605                } else {
606                    Err(TpmDeviceError::TpmRc(rc))
607                }
608            }
609            Err(e) => Err(e),
610        }
611    }
612
613    /// Evicts a persistent object or makes a transient object persistent.
614    ///
615    /// # Errors
616    ///
617    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::transmit`]. Returns
618    /// [`TpmDeviceError::ResponseMismatch`] when the TPM response does not
619    /// contain `TPM2_EvictControl` data.
620    pub fn evict_control(
621        &mut self,
622        auth: TpmHandle,
623        object_handle: TpmHandle,
624        persistent_handle: TpmHandle,
625        sessions: &[TpmsAuthCommand],
626    ) -> Result<(), TpmDeviceError> {
627        let cmd = TpmEvictControlCommand {
628            auth,
629            object_handle: object_handle.0.into(),
630            persistent_handle,
631        };
632        let (resp, _) = self.transmit(&cmd, sessions)?;
633
634        resp.EvictControl()
635            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::EvictControl))?;
636        Ok(())
637    }
638
639    /// Refreshes a key context. Returns `true` if the context is still valid,
640    /// and `false` if it is stale.
641    ///
642    /// # Errors
643    ///
644    /// Propagates any [`TpmDeviceError`] from [`TpmDevice::load_context`] or
645    /// [`TpmDevice::flush_context`] except for TPM reference errors with base
646    /// [`TpmRcBase::ReferenceH0`], which are treated as a stale context and
647    /// reported as `Ok(false)`.
648    pub fn refresh_key(&mut self, context: TpmsContext) -> Result<bool, TpmDeviceError> {
649        match self.load_context(context) {
650            Ok(handle) => match self.flush_context(handle) {
651                Ok(()) => Ok(true),
652                Err(e) => Err(e),
653            },
654            Err(TpmDeviceError::TpmRc(rc)) if rc.base() == TpmRcBase::ReferenceH0 => Ok(false),
655            Err(e) => Err(e),
656        }
657    }
658}