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 rand::{thread_rng, RngCore};
13use std::{
14    cell::RefCell,
15    fs::{File, OpenOptions},
16    io::{Read, Write},
17    os::fd::{AsFd, AsRawFd},
18    path::{Path, PathBuf},
19    rc::Rc,
20    time::{Duration, Instant},
21};
22
23use thiserror::Error;
24use tpm2_crypto::TpmHash;
25use tpm2_protocol::{
26    basic::{TpmHandle, TpmUint32},
27    constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
28    data::{
29        Tpm2bEncryptedSecret, Tpm2bName, Tpm2bNonce, TpmAlgId, TpmCap, TpmCc, TpmEccCurve, TpmHt,
30        TpmPt, TpmRc, TpmRcBase, TpmRh, TpmSe, TpmSt, TpmaSession, TpmsAlgProperty,
31        TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmsPcrSelect, TpmsPcrSelection,
32        TpmtPublic, TpmtSymDefObject, TpmuCapabilities,
33    },
34    frame::{
35        tpm_marshal_command, tpm_unmarshal_response, TpmAuthCommands, TpmAuthResponses, TpmCommand,
36        TpmContextLoadCommand, TpmContextSaveCommand, TpmFlushContextCommand, TpmFrame,
37        TpmGetCapabilityCommand, TpmGetCapabilityResponse, TpmReadPublicCommand, TpmResponse,
38        TpmStartAuthSessionCommand,
39    },
40    TpmWriter,
41};
42use tracing::{debug, trace};
43
44/// Errors that can occur when talking to a TPM device.
45#[derive(Debug, Error)]
46pub enum TpmDeviceError {
47    /// The TPM device is already mutably borrowed.
48    #[error("device is already borrowed")]
49    AlreadyBorrowed,
50
51    /// The requested capability is not available from the TPM.
52    #[error("capability not found: {0}")]
53    CapabilityMissing(TpmCap),
54
55    #[error("operation interrupted by user")]
56    Interrupted,
57
58    /// An invalid command code was used.
59    #[error("invalid CC: {0}")]
60    InvalidCc(tpm2_protocol::data::TpmCc),
61
62    /// The TPM returned an invalid or malformed response.
63    #[error("invalid response")]
64    InvalidResponse,
65
66    /// An I/O error occurred when accessing the TPM device.
67    #[error("I/O: {0}")]
68    Io(#[from] std::io::Error),
69
70    /// Marshaling a TPM protocol encoded object failed.
71    #[error("marshal: {0}")]
72    Marshal(tpm2_protocol::TpmProtocolError),
73
74    /// No TPM device is available.
75    #[error("device not available")]
76    NotAvailable,
77
78    /// The requested operation could not be completed.
79    #[error("operation failed")]
80    OperationFailed,
81
82    /// No PCR banks are available on the TPM.
83    #[error("PCR banks not available")]
84    PcrBanksNotAvailable,
85
86    /// The PCR selection masks differ between active banks.
87    #[error("PCR bank selection mismatch")]
88    PcrBankSelectionMismatch,
89
90    /// The TPM response did not match the expected command code.
91    #[error("response mismatch: {0}")]
92    ResponseMismatch(TpmCc),
93
94    /// The TPM command timed out.
95    #[error("TPM command timed out")]
96    Timeout,
97
98    /// The TPM returned an error code.
99    #[error("TPM return code: {0}")]
100    TpmRc(TpmRc),
101
102    /// Trailing data after the response.
103    #[error("trailing data")]
104    TrailingData,
105
106    /// Unmarshaling a TPM protocol encoded object failed.
107    #[error("unmarshal: {0}")]
108    Unmarshal(tpm2_protocol::TpmProtocolError),
109
110    /// An unexpected end-of-file was encountered.
111    #[error("unexpected EOF")]
112    UnexpectedEof,
113}
114
115impl From<TpmRc> for TpmDeviceError {
116    fn from(rc: TpmRc) -> Self {
117        Self::TpmRc(rc)
118    }
119}
120
121impl From<nix::Error> for TpmDeviceError {
122    fn from(err: nix::Error) -> Self {
123        Self::Io(std::io::Error::from_raw_os_error(err as i32))
124    }
125}
126
127/// Executes a closure with a mutable reference to a `TpmDevice`.
128///
129/// This helper function centralizes the boilerplate for safely acquiring a
130/// mutable borrow of a `TpmDevice` from the shared `Rc<RefCell<...>>`.
131///
132/// # Errors
133///
134/// Returns [`NotAvailable`](crate::TpmDeviceError::NotAvailable) when no device
135/// is present.
136/// Returns [`AlreadyBorrowed`](crate::TpmDeviceError::AlreadyBorrowed) when the
137/// device is already mutably borrowed.
138/// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
139/// on function.
140pub fn with_device<F, T, E>(device: Option<Rc<RefCell<TpmDevice>>>, function: F) -> Result<T, E>
141where
142    F: FnOnce(&mut TpmDevice) -> Result<T, E>,
143    E: From<TpmDeviceError>,
144{
145    let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
146    let mut device_guard = device_rc
147        .try_borrow_mut()
148        .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
149    function(&mut device_guard)
150}
151
152/// A builder for constructing a `TpmDevice`.
153pub struct TpmDeviceBuilder {
154    path: PathBuf,
155    timeout: Duration,
156    interrupted: Box<dyn Fn() -> bool>,
157}
158
159impl Default for TpmDeviceBuilder {
160    fn default() -> Self {
161        Self {
162            path: PathBuf::from("/dev/tpmrm0"),
163            timeout: Duration::from_secs(120),
164            interrupted: Box::new(|| false),
165        }
166    }
167}
168
169impl TpmDeviceBuilder {
170    /// Sets the device file path.
171    #[must_use]
172    pub fn with_path<P: AsRef<Path>>(mut self, path: P) -> Self {
173        self.path = path.as_ref().to_path_buf();
174        self
175    }
176
177    /// Sets the operation timeout.
178    #[must_use]
179    pub fn with_timeout(mut self, timeout: Duration) -> Self {
180        self.timeout = timeout;
181        self
182    }
183
184    /// Sets the interruption check callback.
185    #[must_use]
186    pub fn with_interrupted<F>(mut self, handler: F) -> Self
187    where
188        F: Fn() -> bool + 'static,
189    {
190        self.interrupted = Box::new(handler);
191        self
192    }
193
194    /// Opens the TPM device file and constructs the `TpmDevice`.
195    ///
196    /// # Errors
197    ///
198    /// Returns [`Io`](crate::TpmDeviceError::Io) when the device file cannot be
199    /// opened or when configuring the file descriptor flags fails.
200    pub fn build(self) -> Result<TpmDevice, TpmDeviceError> {
201        let file = OpenOptions::new()
202            .read(true)
203            .write(true)
204            .open(&self.path)
205            .map_err(TpmDeviceError::Io)?;
206
207        let fd = file.as_raw_fd();
208        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
209        let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
210        oflags.insert(fcntl::OFlag::O_NONBLOCK);
211        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
212
213        Ok(TpmDevice {
214            file,
215            interrupted: self.interrupted,
216            timeout: self.timeout,
217            command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
218            response: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
219        })
220    }
221}
222
223pub struct TpmDevice {
224    file: File,
225    interrupted: Box<dyn Fn() -> bool>,
226    timeout: Duration,
227    command: Vec<u8>,
228    response: Vec<u8>,
229}
230
231impl std::fmt::Debug for TpmDevice {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        f.debug_struct("Device")
234            .field("file", &self.file)
235            .field("timeout", &self.timeout)
236            .finish_non_exhaustive()
237    }
238}
239
240impl TpmDevice {
241    const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
242
243    /// Creates a new builder for `TpmDevice`.
244    #[must_use]
245    pub fn builder() -> TpmDeviceBuilder {
246        TpmDeviceBuilder::default()
247    }
248
249    fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
250        let fd = self.file.as_fd();
251        let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
252
253        let num_events = match poll(&mut fds, 100u16) {
254            Ok(num) => num,
255            Err(nix::Error::EINTR) => return Ok(0),
256            Err(e) => return Err(e.into()),
257        };
258
259        if num_events == 0 {
260            return Ok(0);
261        }
262
263        let revents = fds[0].revents().unwrap_or(PollFlags::empty());
264
265        if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
266            return Err(TpmDeviceError::UnexpectedEof);
267        }
268
269        if revents.contains(PollFlags::POLLIN) {
270            match self.file.read(buf) {
271                Ok(0) => Err(TpmDeviceError::UnexpectedEof),
272                Ok(n) => Ok(n),
273                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
274                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
275                Err(e) => Err(e.into()),
276            }
277        } else if revents.contains(PollFlags::POLLHUP) {
278            Err(TpmDeviceError::UnexpectedEof)
279        } else {
280            Ok(0)
281        }
282    }
283
284    /// Performs the whole TPM command transmission process.
285    ///
286    /// # Errors
287    ///
288    /// Returns [`Interrupted`](crate::TpmDeviceError::Interrupted) when the
289    /// interrupt callback requests cancellation.
290    /// Returns [`Io`](crate::TpmDeviceError::Io) when a write, flush, or read
291    /// operation on the device file fails, or when polling the device file
292    /// descriptor fails.
293    /// Returns [`Marshal`](crate::TpmDeviceError::Marshal) when marshal
294    /// operation on TPM protocol compliant data fails.
295    /// Returns [`Timeout`](crate::TpmDeviceError::Timeout) when the TPM does
296    /// not respond within the configured timeout.
297    /// Returns [`TpmRc`](crate::TpmDeviceError::TpmRc) when the TPM returns an
298    /// error code.
299    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
300    /// operation on TPM protocol compliant data fails.
301    pub fn transmit<C: TpmFrame>(
302        &mut self,
303        command: &C,
304        sessions: &[TpmsAuthCommand],
305    ) -> Result<(TpmResponse, TpmAuthResponses), TpmDeviceError> {
306        self.prepare_command(command, sessions)?;
307        let cc = command.cc();
308
309        self.file.write_all(&self.command)?;
310        self.file.flush()?;
311
312        let start_time = Instant::now();
313        self.response.clear();
314        let mut total_size: Option<usize> = None;
315        let mut temp_buf = [0u8; 1024];
316
317        loop {
318            if (self.interrupted)() {
319                return Err(TpmDeviceError::Interrupted);
320            }
321            if start_time.elapsed() > self.timeout {
322                return Err(TpmDeviceError::Timeout);
323            }
324
325            let n = self.receive(&mut temp_buf)?;
326            if n > 0 {
327                self.response.extend_from_slice(&temp_buf[..n]);
328            }
329
330            if total_size.is_none() && self.response.len() >= 10 {
331                let Ok(size_bytes): Result<[u8; 4], _> = self.response[2..6].try_into() else {
332                    return Err(TpmDeviceError::OperationFailed);
333                };
334                let size = u32::from_be_bytes(size_bytes) as usize;
335                if !(10..={ TPM_MAX_COMMAND_SIZE }).contains(&size) {
336                    return Err(TpmDeviceError::OperationFailed);
337                }
338                total_size = Some(size);
339            }
340
341            if let Some(size) = total_size {
342                if self.response.len() == size {
343                    break;
344                }
345                if self.response.len() > size {
346                    return Err(TpmDeviceError::TrailingData);
347                }
348            }
349        }
350
351        let result = tpm_unmarshal_response(cc, &self.response).map_err(TpmDeviceError::Unmarshal);
352        trace!("{} R: {}", cc, hex::encode(&self.response));
353        Ok(result??)
354    }
355
356    fn prepare_command<C: TpmFrame>(
357        &mut self,
358        command: &C,
359        sessions: &[TpmsAuthCommand],
360    ) -> Result<(), TpmDeviceError> {
361        let cc = command.cc();
362        let tag = if sessions.is_empty() {
363            TpmSt::NoSessions
364        } else {
365            TpmSt::Sessions
366        };
367
368        self.command.resize(TPM_MAX_COMMAND_SIZE, 0);
369
370        let len = {
371            let mut writer = TpmWriter::new(&mut self.command);
372            tpm_marshal_command(command, tag, sessions, &mut writer)
373                .map_err(TpmDeviceError::Marshal)?;
374            writer.len()
375        };
376        self.command.truncate(len);
377
378        trace!("{} C: {}", cc, hex::encode(&self.command));
379        Ok(())
380    }
381
382    /// Fetches a complete list of capabilities from the TPM, handling
383    /// pagination.
384    ///
385    /// # Errors
386    ///
387    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
388    /// when receiving unepected TPM response.
389    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
390    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
391    fn get_capability<T, F, N>(
392        &mut self,
393        cap: TpmCap,
394        property_start: u32,
395        count: u32,
396        mut extract: F,
397        next_prop: N,
398    ) -> Result<Vec<T>, TpmDeviceError>
399    where
400        T: Copy,
401        F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
402        N: Fn(&T) -> u32,
403    {
404        let mut results = Vec::new();
405        let mut prop = property_start;
406        loop {
407            let (more_data, cap_data) =
408                self.get_capability_page(cap, TpmUint32(prop), TpmUint32(count))?;
409            let items: &[T] = extract(&cap_data.data)?;
410            results.extend_from_slice(items);
411
412            if more_data {
413                if let Some(last) = items.last() {
414                    prop = next_prop(last);
415                } else {
416                    break;
417                }
418            } else {
419                break;
420            }
421        }
422        Ok(results)
423    }
424
425    /// Retrieves all algorithm properties supported by the TPM.
426    ///
427    /// # Errors
428    ///
429    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
430    /// when receiving unepected TPM response.
431    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
432    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
433    pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
434        self.get_capability(
435            TpmCap::Algs,
436            0,
437            u32::try_from(MAX_HANDLES).map_err(|_| TpmDeviceError::OperationFailed)?,
438            |caps| match caps {
439                TpmuCapabilities::Algs(algs) => Ok(algs),
440                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
441            },
442            |last| last.alg as u32 + 1,
443        )
444    }
445
446    /// Retrieves all handles of a specific type from the TPM.
447    ///
448    /// # Errors
449    ///
450    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
451    /// when receiving unepected TPM response.
452    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
453    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
454    pub fn fetch_handles(&mut self, class: TpmHt) -> Result<Vec<TpmHandle>, TpmDeviceError> {
455        self.get_capability(
456            TpmCap::Handles,
457            (class as u32) << 24,
458            u32::try_from(MAX_HANDLES).map_err(|_| TpmDeviceError::OperationFailed)?,
459            |caps| match caps {
460                TpmuCapabilities::Handles(handles) => Ok(handles),
461                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
462            },
463            |last| last.value() + 1,
464        )
465        .map(|handles| handles.into_iter().collect())
466    }
467
468    /// Retrieves all available ECC curves supported by the TPM.
469    ///
470    /// # Errors
471    ///
472    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
473    /// when receiving unepected TPM response.
474    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
475    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
476    pub fn fetch_ecc_curves(&mut self) -> Result<Vec<TpmEccCurve>, TpmDeviceError> {
477        self.get_capability(
478            TpmCap::EccCurves,
479            0,
480            u32::try_from(MAX_HANDLES).map_err(|_| TpmDeviceError::OperationFailed)?,
481            |caps| match caps {
482                TpmuCapabilities::EccCurves(curves) => Ok(curves),
483                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::EccCurves)),
484            },
485            |last| *last as u32 + 1,
486        )
487    }
488
489    /// Retrieves the list of active PCR banks and the bank selection mask.
490    ///
491    /// # Errors
492    ///
493    /// Returns
494    /// [`PcrBanksNotAvailable`](crate::TpmDeviceError::PcrBanksNotAvailable)
495    /// when no PCR banks are available.
496    /// Return
497    /// [`PcrBankSelectionMismatch`](crate::TpmDeviceError::PcrBankSelectionMismatch)
498    /// when the PCR selection masks differ between active banks.
499    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
500    /// when receiving unepected TPM response.
501    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
502    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
503    pub fn fetch_pcr_bank_list(
504        &mut self,
505    ) -> Result<(Vec<TpmAlgId>, TpmsPcrSelect), TpmDeviceError> {
506        let pcrs: Vec<TpmsPcrSelection> = self.get_capability(
507            TpmCap::Pcrs,
508            0,
509            u32::try_from(MAX_HANDLES).map_err(|_| TpmDeviceError::OperationFailed)?,
510            |caps| match caps {
511                TpmuCapabilities::Pcrs(pcrs) => Ok(pcrs),
512                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Pcrs)),
513            },
514            |last| last.hash as u32 + 1,
515        )?;
516
517        if pcrs.is_empty() {
518            return Err(TpmDeviceError::PcrBanksNotAvailable);
519        }
520
521        let mut common_select: Option<TpmsPcrSelect> = None;
522        let mut algs = Vec::with_capacity(pcrs.len());
523
524        for bank in pcrs {
525            if bank.pcr_select.iter().all(|&b| b == 0) {
526                debug!(
527                    "skipping unallocated bank {:?} (mask: {})",
528                    bank.hash,
529                    hex::encode(&*bank.pcr_select)
530                );
531                continue;
532            }
533
534            if let Some(ref select) = common_select {
535                if bank.pcr_select != *select {
536                    return Err(TpmDeviceError::PcrBankSelectionMismatch);
537                }
538            } else {
539                common_select = Some(bank.pcr_select);
540            }
541            algs.push(bank.hash);
542        }
543
544        let select = common_select.ok_or(TpmDeviceError::PcrBanksNotAvailable)?;
545
546        algs.sort();
547        Ok((algs, select))
548    }
549
550    /// Fetches and returns one page of capabilities of a certain type from the
551    /// TPM.
552    ///
553    /// # Errors
554    ///
555    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
556    /// when receiving unepected TPM response.
557    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
558    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
559    fn get_capability_page(
560        &mut self,
561        cap: TpmCap,
562        property: TpmUint32,
563        property_count: TpmUint32,
564    ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
565        let cmd = TpmGetCapabilityCommand {
566            cap,
567            property,
568            property_count,
569            handles: [],
570        };
571
572        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
573        let TpmGetCapabilityResponse {
574            more_data,
575            capability_data,
576            handles: [],
577        } = resp
578            .GetCapability()
579            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::GetCapability))?;
580
581        Ok((more_data.into(), capability_data))
582    }
583
584    /// Reads a specific TPM property.
585    ///
586    /// # Errors
587    ///
588    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
589    /// when receiving unepected TPM response.
590    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
591    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
592    pub fn get_tpm_property(&mut self, property: TpmPt) -> Result<TpmUint32, TpmDeviceError> {
593        let (_, cap_data) = self.get_capability_page(
594            TpmCap::TpmProperties,
595            TpmUint32(property as u32),
596            TpmUint32(1),
597        )?;
598
599        let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
600            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
601        };
602
603        let Some(prop) = props.iter().find(|prop| prop.property == property) else {
604            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
605        };
606
607        Ok(prop.value)
608    }
609
610    /// Reads the public area of a TPM object.
611    ///
612    /// # Errors
613    ///
614    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
615    /// when receiving unepected TPM response.
616    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
617    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
618    pub fn read_public(
619        &mut self,
620        handle: TpmHandle,
621    ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
622        let cmd = TpmReadPublicCommand { handles: [handle] };
623        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
624        let read_public_resp = resp
625            .ReadPublic()
626            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ReadPublic))?;
627        let public = read_public_resp.out_public.inner;
628        let name = read_public_resp.name;
629        Ok((public, name))
630    }
631
632    /// Finds a persistent handle by its `Tpm2bName`.
633    ///
634    /// # Errors
635    ///
636    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
637    /// when receiving unepected TPM response.
638    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
639    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
640    pub fn find_persistent(
641        &mut self,
642        target_name: &Tpm2bName,
643    ) -> Result<Option<TpmHandle>, TpmDeviceError> {
644        for handle in self.fetch_handles(TpmHt::Persistent)? {
645            match self.read_public(handle) {
646                Ok((_, name)) => {
647                    if name == *target_name {
648                        return Ok(Some(handle));
649                    }
650                }
651                Err(TpmDeviceError::TpmRc(rc)) => {
652                    let base = rc.base();
653                    if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
654                        continue;
655                    }
656                    return Err(TpmDeviceError::TpmRc(rc));
657                }
658                Err(e) => return Err(e),
659            }
660        }
661        Ok(None)
662    }
663
664    /// Saves the context of a transient object or session.
665    ///
666    /// # Errors
667    ///
668    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
669    /// when receiving unepected TPM response.
670    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
671    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
672    pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
673        let cmd = TpmContextSaveCommand {
674            handles: [save_handle],
675        };
676        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
677        let save_resp = resp
678            .ContextSave()
679            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ContextSave))?;
680        Ok(save_resp.context)
681    }
682
683    /// Loads a TPM context and returns the handle.
684    ///
685    /// # Errors
686    ///
687    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
688    /// when receiving unepected TPM response.
689    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
690    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
691    pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
692        let cmd = TpmContextLoadCommand {
693            context,
694            handles: [],
695        };
696        let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
697        let resp_inner = resp
698            .ContextLoad()
699            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ContextLoad))?;
700        Ok(resp_inner.handles[0])
701    }
702
703    /// Flushes a transient object or session from the TPM and removes it from
704    /// the cache.
705    ///
706    /// # Errors
707    ///
708    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
709    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
710    pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
711        let cmd = TpmFlushContextCommand {
712            flush_handle: handle,
713            handles: [],
714        };
715        self.transmit(&cmd, Self::NO_SESSIONS)?;
716        Ok(())
717    }
718
719    /// Loads a session context and then flushes the resulting handle.
720    ///
721    /// # Errors
722    ///
723    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
724    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
725    pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
726        match self.load_context(context) {
727            Ok(handle) => self.flush_context(handle),
728            Err(TpmDeviceError::TpmRc(rc)) => {
729                let base = rc.base();
730                if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
731                    Ok(())
732                } else {
733                    Err(TpmDeviceError::TpmRc(rc))
734                }
735            }
736            Err(e) => Err(e),
737        }
738    }
739}
740
741/// A builder for creating a TPM policy session.
742pub struct TpmPolicySessionBuilder {
743    bind: TpmHandle,
744    tpm_key: TpmHandle,
745    nonce_caller: Option<Tpm2bNonce>,
746    encrypted_salt: Option<Tpm2bEncryptedSecret>,
747    session_type: TpmSe,
748    symmetric: TpmtSymDefObject,
749    auth_hash: TpmAlgId,
750}
751
752impl Default for TpmPolicySessionBuilder {
753    fn default() -> Self {
754        Self {
755            bind: (TpmRh::Null as u32).into(),
756            tpm_key: (TpmRh::Null as u32).into(),
757            nonce_caller: None,
758            encrypted_salt: None,
759            session_type: TpmSe::Policy,
760            symmetric: TpmtSymDefObject::default(),
761            auth_hash: TpmAlgId::Sha256,
762        }
763    }
764}
765
766impl TpmPolicySessionBuilder {
767    #[must_use]
768    pub fn new() -> Self {
769        Self::default()
770    }
771
772    #[must_use]
773    pub fn with_bind(mut self, bind: TpmHandle) -> Self {
774        self.bind = bind;
775        self
776    }
777
778    #[must_use]
779    pub fn with_tpm_key(mut self, tpm_key: TpmHandle) -> Self {
780        self.tpm_key = tpm_key;
781        self
782    }
783
784    #[must_use]
785    pub fn with_nonce_caller(mut self, nonce: Tpm2bNonce) -> Self {
786        self.nonce_caller = Some(nonce);
787        self
788    }
789
790    #[must_use]
791    pub fn with_encrypted_salt(mut self, salt: Tpm2bEncryptedSecret) -> Self {
792        self.encrypted_salt = Some(salt);
793        self
794    }
795
796    #[must_use]
797    pub fn with_session_type(mut self, session_type: TpmSe) -> Self {
798        self.session_type = session_type;
799        self
800    }
801
802    #[must_use]
803    pub fn with_symmetric(mut self, symmetric: TpmtSymDefObject) -> Self {
804        self.symmetric = symmetric;
805        self
806    }
807
808    #[must_use]
809    pub fn with_auth_hash(mut self, auth_hash: TpmAlgId) -> Self {
810        self.auth_hash = auth_hash;
811        self
812    }
813
814    /// Opens the policy session on the provided device.
815    ///
816    /// # Errors
817    ///
818    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch) if
819    /// the TPM response is unexpected.
820    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
821    /// operation on TPM protocol compliant data fails.
822    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
823    /// on function.
824    pub fn open(self, device: &mut TpmDevice) -> Result<TpmPolicySession, TpmDeviceError> {
825        let nonce_caller = if let Some(nonce) = self.nonce_caller {
826            nonce
827        } else {
828            let digest_len = TpmHash::from(self.auth_hash).size();
829            let mut nonce_bytes = vec![0; digest_len];
830            thread_rng().fill_bytes(&mut nonce_bytes);
831            Tpm2bNonce::try_from(nonce_bytes.as_slice()).map_err(TpmDeviceError::Unmarshal)?
832        };
833
834        let cmd = TpmStartAuthSessionCommand {
835            nonce_caller,
836            encrypted_salt: self.encrypted_salt.unwrap_or_default(),
837            session_type: self.session_type,
838            symmetric: self.symmetric,
839            auth_hash: self.auth_hash,
840            handles: [self.tpm_key, self.bind],
841        };
842
843        let (resp, _) = device.transmit(&cmd, TpmDevice::NO_SESSIONS)?;
844        let start_resp = resp
845            .StartAuthSession()
846            .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::StartAuthSession))?;
847
848        Ok(TpmPolicySession {
849            handle: start_resp.handles[0],
850            attributes: TpmaSession::CONTINUE_SESSION,
851            hash_alg: self.auth_hash,
852            nonce_tpm: start_resp.nonce_tpm,
853        })
854    }
855}
856
857/// Represents an active TPM policy session.
858#[derive(Debug, Clone)]
859pub struct TpmPolicySession {
860    handle: TpmHandle,
861    attributes: TpmaSession,
862    hash_alg: TpmAlgId,
863    nonce_tpm: Tpm2bNonce,
864}
865
866impl TpmPolicySession {
867    /// Creates a new builder for `TpmPolicySession`.
868    #[must_use]
869    pub fn builder() -> TpmPolicySessionBuilder {
870        TpmPolicySessionBuilder::new()
871    }
872
873    /// Returns the session handle.
874    #[must_use]
875    pub fn handle(&self) -> TpmHandle {
876        self.handle
877    }
878
879    /// Returns the session attributes.
880    #[must_use]
881    pub fn attributes(&self) -> TpmaSession {
882        self.attributes
883    }
884
885    /// Returns the hash algorithm used by the session.
886    #[must_use]
887    pub fn hash_alg(&self) -> TpmAlgId {
888        self.hash_alg
889    }
890
891    /// Returns the nonce generated by the TPM.
892    #[must_use]
893    pub fn nonce_tpm(&self) -> &Tpm2bNonce {
894        &self.nonce_tpm
895    }
896
897    /// Applies a list of policy commands to this session.
898    ///
899    /// This method iterates through the provided commands, updates the first handle
900    /// of each command (or second for `PolicySecret`) to point to this session,
901    /// and transmits them to the device.
902    ///
903    /// # Errors
904    ///
905    /// Returns [`InvalidCc`](crate::TpmDeviceError::InvalidCc) when a command is not
906    /// a supported policy command.
907    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
908    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
909    pub fn run(
910        &self,
911        device: &mut TpmDevice,
912        commands: Vec<(TpmCommand, TpmAuthCommands)>,
913    ) -> Result<(), TpmDeviceError> {
914        for (mut command_body, auth_sessions) in commands {
915            match &mut command_body {
916                TpmCommand::PolicyPcr(cmd) => cmd.handles[0] = self.handle,
917                TpmCommand::PolicyOr(cmd) => cmd.handles[0] = self.handle,
918                TpmCommand::PolicyRestart(cmd) => {
919                    cmd.handles[0] = self.handle;
920                }
921                TpmCommand::PolicySecret(cmd) => {
922                    cmd.handles[1] = self.handle;
923                }
924                _ => {
925                    return Err(TpmDeviceError::InvalidCc(command_body.cc()));
926                }
927            }
928            device.transmit(&command_body, auth_sessions.as_ref())?;
929        }
930        Ok(())
931    }
932
933    /// Flushes the session context from the TPM.
934    ///
935    /// # Errors
936    ///
937    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
938    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
939    pub fn flush(&self, device: &mut TpmDevice) -> Result<(), TpmDeviceError> {
940        device.flush_context(self.handle)
941    }
942}