Skip to main content

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::{PollFd, PollFlags, poll},
11};
12use rand::{RngCore, thread_rng};
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 core::fmt;
24use tpm2_crypto::TpmHash;
25use tpm2_protocol::{
26    TpmCast, TpmError, TpmField, TpmWriter,
27    basic::{Tpm2b as Tpm2bWire, TpmBuffer, TpmHandle, TpmList, TpmUint16, TpmUint32, TpmUint64},
28    constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
29    data::{
30        Tpm2bDigest, Tpm2bEccParameter, Tpm2bEncryptedSecret, Tpm2bName, Tpm2bNonce,
31        Tpm2bPublicKeyRsa, Tpm2bSymKey, TpmAlgId, TpmCap, TpmCc, TpmEccCurve, TpmHt, TpmPt, TpmRc,
32        TpmRcBase, TpmRh, TpmSe, TpmSt, TpmaAlgorithm, TpmaCc, TpmaObject, TpmaSession, TpmiYesNo,
33        TpmsAlgProperty, TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmsEccParms,
34        TpmsEccPoint, TpmsKeyedhashParms, TpmsPcrSelect, TpmsPcrSelection, TpmsRsaParms,
35        TpmsSchemeHash, TpmsSchemeXor, TpmsSymcipherParms, TpmsTaggedProperty, TpmtEccScheme,
36        TpmtKdfScheme, TpmtKeyedhashScheme, TpmtPublic, TpmtRsaScheme, TpmtSymDefObject,
37        TpmuAsymScheme, TpmuCapabilities, TpmuKdfScheme, TpmuKeyedhashScheme, TpmuPublicId,
38        TpmuPublicParms, TpmuSymKeyBits, TpmuSymMode,
39    },
40    frame::{
41        TpmAuthCommands, TpmCommandValue as TpmCommand, TpmContextLoadCommand,
42        TpmContextSaveCommand, TpmFlushContextCommand, TpmFrame, TpmGetCapabilityCommand,
43        TpmReadPublicCommand, TpmResponse, TpmResponseView, TpmStartAuthSessionCommand,
44        tpm_marshal_command,
45    },
46};
47use tracing::{debug, trace};
48
49/// Errors that can occur when talking to a TPM device.
50///
51/// `Display` renders only the variant name as lowercase space-separated words
52/// (e.g. `UnexpectedEof` becomes `unexpected eof`).
53#[derive(Debug, strum::AsRefStr)]
54#[strum(serialize_all = "title_case")]
55#[non_exhaustive]
56pub enum TpmDeviceError {
57    /// The TPM device is already mutably borrowed.
58    AlreadyBorrowed,
59
60    /// The requested capability is not available from the TPM.
61    CapabilityMissing(TpmCap),
62
63    /// The operation was interrupted by the caller.
64    Interrupted,
65
66    /// An invalid command code was used.
67    InvalidCc(tpm2_protocol::data::TpmCc),
68
69    /// The TPM returned an invalid or malformed response.
70    InvalidResponse,
71
72    /// An I/O error occurred when accessing the TPM device.
73    Io(std::io::Error),
74
75    /// Marshaling a TPM protocol encoded object failed.
76    Marshal(TpmError),
77
78    /// No TPM device is available.
79    NotAvailable,
80
81    /// No PCR banks are available on the TPM.
82    PcrBanksNotAvailable,
83
84    /// The PCR selection masks differ between active banks.
85    PcrBankSelectionMismatch,
86
87    /// The TPM response did not match the expected command code.
88    ResponseMismatch(TpmCc),
89
90    /// The TPM command timed out.
91    Timeout,
92
93    /// The TPM returned an error code.
94    TpmRc(TpmRc),
95
96    /// Trailing data after the response.
97    TrailingData,
98
99    /// Unmarshaling a TPM protocol encoded object failed.
100    Unmarshal(TpmError),
101
102    /// An unexpected end-of-file was encountered.
103    UnexpectedEof,
104
105    /// The requested algorithm is not supported.
106    UnsupportedAlgorithm(TpmAlgId),
107}
108
109impl fmt::Display for TpmDeviceError {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(f, "{}", self.as_ref().to_lowercase())
112    }
113}
114
115impl std::error::Error for TpmDeviceError {}
116
117impl PartialEq for TpmDeviceError {
118    fn eq(&self, other: &Self) -> bool {
119        match (self, other) {
120            (Self::CapabilityMissing(a), Self::CapabilityMissing(b)) => a == b,
121            (Self::InvalidCc(a), Self::InvalidCc(b))
122            | (Self::ResponseMismatch(a), Self::ResponseMismatch(b)) => a == b,
123            (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
124            (Self::Marshal(a), Self::Marshal(b)) | (Self::Unmarshal(a), Self::Unmarshal(b)) => {
125                a == b
126            }
127            (Self::TpmRc(a), Self::TpmRc(b)) => a == b,
128            (Self::UnsupportedAlgorithm(a), Self::UnsupportedAlgorithm(b)) => a == b,
129            (Self::AlreadyBorrowed, Self::AlreadyBorrowed)
130            | (Self::Interrupted, Self::Interrupted)
131            | (Self::InvalidResponse, Self::InvalidResponse)
132            | (Self::NotAvailable, Self::NotAvailable)
133            | (Self::PcrBanksNotAvailable, Self::PcrBanksNotAvailable)
134            | (Self::PcrBankSelectionMismatch, Self::PcrBankSelectionMismatch)
135            | (Self::Timeout, Self::Timeout)
136            | (Self::TrailingData, Self::TrailingData)
137            | (Self::UnexpectedEof, Self::UnexpectedEof) => true,
138            _ => false,
139        }
140    }
141}
142
143impl Eq for TpmDeviceError {}
144
145impl From<TpmRc> for TpmDeviceError {
146    fn from(rc: TpmRc) -> Self {
147        Self::TpmRc(rc)
148    }
149}
150
151impl From<std::io::Error> for TpmDeviceError {
152    fn from(err: std::io::Error) -> Self {
153        Self::Io(err)
154    }
155}
156
157impl From<nix::Error> for TpmDeviceError {
158    fn from(err: nix::Error) -> Self {
159        Self::Io(std::io::Error::from_raw_os_error(err as i32))
160    }
161}
162
163/// Executes a closure with a mutable reference to a `TpmDevice`.
164///
165/// This helper function centralizes the boilerplate for safely acquiring a
166/// mutable borrow of a `TpmDevice` from the shared `Rc<RefCell<...>>`.
167///
168/// # Errors
169///
170/// Returns [`NotAvailable`](crate::TpmDeviceError::NotAvailable) when no device
171/// is present.
172/// Returns [`AlreadyBorrowed`](crate::TpmDeviceError::AlreadyBorrowed) when the
173/// device is already mutably borrowed.
174/// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
175/// on function.
176pub fn with_device<F, T, E>(device: Option<&Rc<RefCell<TpmDevice>>>, function: F) -> Result<T, E>
177where
178    F: FnOnce(&mut TpmDevice) -> Result<T, E>,
179    E: From<TpmDeviceError>,
180{
181    let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
182    let mut device_guard = device_rc
183        .try_borrow_mut()
184        .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
185    function(&mut device_guard)
186}
187
188/// A builder for constructing a `TpmDevice`.
189pub struct TpmDeviceBuilder {
190    path: PathBuf,
191    timeout: Duration,
192    interrupted: Box<dyn Fn() -> bool>,
193}
194
195impl Default for TpmDeviceBuilder {
196    fn default() -> Self {
197        Self {
198            path: PathBuf::from("/dev/tpmrm0"),
199            timeout: Duration::from_secs(120),
200            interrupted: Box::new(|| false),
201        }
202    }
203}
204
205impl TpmDeviceBuilder {
206    /// Sets the device file path.
207    #[must_use]
208    pub fn with_path<P: AsRef<Path>>(mut self, path: P) -> Self {
209        self.path = path.as_ref().to_path_buf();
210        self
211    }
212
213    /// Sets the operation timeout.
214    #[must_use]
215    pub fn with_timeout(mut self, timeout: Duration) -> Self {
216        self.timeout = timeout;
217        self
218    }
219
220    /// Sets the interruption check callback.
221    #[must_use]
222    pub fn with_interrupted<F>(mut self, handler: F) -> Self
223    where
224        F: Fn() -> bool + 'static,
225    {
226        self.interrupted = Box::new(handler);
227        self
228    }
229
230    /// Opens the TPM device file and constructs the `TpmDevice`.
231    ///
232    /// # Errors
233    ///
234    /// Returns [`Io`](crate::TpmDeviceError::Io) when the device file cannot be
235    /// opened or when configuring the file descriptor flags fails.
236    pub fn build(self) -> Result<TpmDevice, TpmDeviceError> {
237        let file = OpenOptions::new()
238            .read(true)
239            .write(true)
240            .open(&self.path)
241            .map_err(TpmDeviceError::Io)?;
242
243        let fd = file.as_raw_fd();
244        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
245        let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
246        oflags.insert(fcntl::OFlag::O_NONBLOCK);
247        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
248
249        Ok(TpmDevice {
250            file,
251            interrupted: self.interrupted,
252            timeout: self.timeout,
253            command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
254            response: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
255        })
256    }
257}
258
259pub struct TpmDevice {
260    file: File,
261    interrupted: Box<dyn Fn() -> bool>,
262    timeout: Duration,
263    command: Vec<u8>,
264    response: Vec<u8>,
265}
266
267impl std::fmt::Debug for TpmDevice {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        f.debug_struct("Device")
270            .field("file", &self.file)
271            .field("timeout", &self.timeout)
272            .finish_non_exhaustive()
273    }
274}
275
276impl TpmDevice {
277    const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
278
279    /// Number of items requested per paginated `GetCapability` query.
280    #[allow(clippy::cast_possible_truncation)]
281    const CAPABILITY_PAGE_SIZE: u32 = MAX_HANDLES as u32;
282
283    /// Creates a new builder for `TpmDevice`.
284    #[must_use]
285    pub fn builder() -> TpmDeviceBuilder {
286        TpmDeviceBuilder::default()
287    }
288
289    fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
290        let fd = self.file.as_fd();
291        let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
292
293        let num_events = match poll(&mut fds, 100u16) {
294            Ok(num) => num,
295            Err(nix::Error::EINTR) => return Ok(0),
296            Err(e) => return Err(e.into()),
297        };
298
299        if num_events == 0 {
300            return Ok(0);
301        }
302
303        let revents = fds[0].revents().unwrap_or(PollFlags::empty());
304
305        if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
306            return Err(TpmDeviceError::UnexpectedEof);
307        }
308
309        if revents.contains(PollFlags::POLLIN) {
310            match self.file.read(buf) {
311                Ok(0) => Err(TpmDeviceError::UnexpectedEof),
312                Ok(n) => Ok(n),
313                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
314                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
315                Err(e) => Err(e.into()),
316            }
317        } else if revents.contains(PollFlags::POLLHUP) {
318            Err(TpmDeviceError::UnexpectedEof)
319        } else {
320            Ok(0)
321        }
322    }
323
324    /// Performs the whole TPM command transmission process.
325    ///
326    /// # Errors
327    ///
328    /// Returns [`Interrupted`](crate::TpmDeviceError::Interrupted) when the
329    /// interrupt callback requests cancellation.
330    /// Returns [`Io`](crate::TpmDeviceError::Io) when a write, flush, or read
331    /// operation on the device file fails, or when polling the device file
332    /// descriptor fails.
333    /// Returns [`Marshal`](crate::TpmDeviceError::Marshal) when marshal
334    /// operation on TPM protocol compliant data fails.
335    /// Returns [`Timeout`](crate::TpmDeviceError::Timeout) when the TPM does
336    /// not respond within the configured timeout.
337    /// Returns [`TpmRc`](crate::TpmDeviceError::TpmRc) when the TPM returns an
338    /// error code.
339    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
340    /// operation on TPM protocol compliant data fails.
341    pub fn transmit<C: TpmFrame>(
342        &mut self,
343        command: &C,
344        sessions: &[TpmsAuthCommand],
345    ) -> Result<&TpmResponse, TpmDeviceError> {
346        self.prepare_command(command, sessions)?;
347        let cc = command.cc();
348
349        self.file.write_all(&self.command)?;
350        self.file.flush()?;
351
352        let start_time = Instant::now();
353        self.response.clear();
354        let mut total_size: Option<usize> = None;
355        let mut temp_buf = [0u8; 1024];
356
357        loop {
358            if (self.interrupted)() {
359                return Err(TpmDeviceError::Interrupted);
360            }
361            if start_time.elapsed() > self.timeout {
362                return Err(TpmDeviceError::Timeout);
363            }
364
365            let n = self.receive(&mut temp_buf)?;
366            if n > 0 {
367                self.response.extend_from_slice(&temp_buf[..n]);
368            }
369
370            if total_size.is_none() && self.response.len() >= 10 {
371                let Ok(size_bytes): Result<[u8; 4], _> = self.response[2..6].try_into() else {
372                    return Err(TpmDeviceError::InvalidResponse);
373                };
374                let size = u32::from_be_bytes(size_bytes) as usize;
375                if !(10..={ TPM_MAX_COMMAND_SIZE }).contains(&size) {
376                    return Err(TpmDeviceError::InvalidResponse);
377                }
378                total_size = Some(size);
379            }
380
381            if let Some(size) = total_size {
382                if self.response.len() == size {
383                    break;
384                }
385                if self.response.len() > size {
386                    return Err(TpmDeviceError::TrailingData);
387                }
388            }
389        }
390
391        let response = TpmResponse::cast(&self.response).map_err(TpmDeviceError::Unmarshal)?;
392        let result = TpmResponseView::cast(cc, response).map_err(TpmDeviceError::Unmarshal)?;
393        trace!("{} R: {}", cc, hex::encode(&self.response));
394        result.map(|_| response).map_err(TpmDeviceError::TpmRc)
395    }
396
397    fn prepare_command<C: TpmFrame>(
398        &mut self,
399        command: &C,
400        sessions: &[TpmsAuthCommand],
401    ) -> Result<(), TpmDeviceError> {
402        let cc = command.cc();
403        let tag = if sessions.is_empty() {
404            TpmSt::NoSessions
405        } else {
406            TpmSt::Sessions
407        };
408
409        self.command.resize(TPM_MAX_COMMAND_SIZE, 0);
410
411        let len = {
412            let mut writer = TpmWriter::new(&mut self.command);
413            tpm_marshal_command(command, tag, sessions, &mut writer)
414                .map_err(TpmDeviceError::Marshal)?;
415            writer.len()
416        };
417        self.command.truncate(len);
418
419        trace!("{} C: {}", cc, hex::encode(&self.command));
420        Ok(())
421    }
422
423    /// Fetches a complete list of capabilities from the TPM, handling
424    /// pagination.
425    ///
426    /// # Errors
427    ///
428    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
429    /// when receiving unepected TPM response.
430    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
431    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
432    fn get_capability<T, F, N>(
433        &mut self,
434        cap: TpmCap,
435        property_start: u32,
436        count: u32,
437        mut extract: F,
438        next_prop: N,
439    ) -> Result<Vec<T>, TpmDeviceError>
440    where
441        T: Copy,
442        F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
443        N: Fn(&T) -> u32,
444    {
445        let mut results = Vec::new();
446        let mut prop = property_start;
447        loop {
448            let (more_data, cap_data) =
449                self.get_capability_page(cap, TpmUint32::from(prop), TpmUint32::from(count))?;
450            let items: &[T] = extract(&cap_data.data)?;
451            results.extend_from_slice(items);
452
453            if more_data {
454                if let Some(last) = items.last() {
455                    prop = next_prop(last);
456                } else {
457                    break;
458                }
459            } else {
460                break;
461            }
462        }
463        Ok(results)
464    }
465
466    /// Retrieves all algorithm properties supported by the TPM.
467    ///
468    /// # Errors
469    ///
470    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
471    /// when receiving unepected TPM response.
472    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
473    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
474    pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
475        self.get_capability(
476            TpmCap::Algs,
477            0,
478            Self::CAPABILITY_PAGE_SIZE,
479            |caps| match caps {
480                TpmuCapabilities::Algs(algs) => Ok(algs),
481                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
482            },
483            |last| u32::from(last.alg.value()) + 1,
484        )
485    }
486
487    /// Retrieves all handles of a specific type from the TPM.
488    ///
489    /// # Errors
490    ///
491    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
492    /// when receiving unepected TPM response.
493    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
494    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
495    pub fn fetch_handles(&mut self, class: TpmHt) -> Result<Vec<TpmHandle>, TpmDeviceError> {
496        self.get_capability(
497            TpmCap::Handles,
498            (class as u32) << 24,
499            Self::CAPABILITY_PAGE_SIZE,
500            |caps| match caps {
501                TpmuCapabilities::Handles(handles) => Ok(handles),
502                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
503            },
504            |last| last.value() + 1,
505        )
506        .map(|handles| handles.into_iter().collect())
507    }
508
509    /// Retrieves all available ECC curves supported by the TPM.
510    ///
511    /// # Errors
512    ///
513    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
514    /// when receiving unepected TPM response.
515    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
516    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
517    pub fn fetch_ecc_curves(&mut self) -> Result<Vec<TpmEccCurve>, TpmDeviceError> {
518        self.get_capability(
519            TpmCap::EccCurves,
520            0,
521            Self::CAPABILITY_PAGE_SIZE,
522            |caps| match caps {
523                TpmuCapabilities::EccCurves(curves) => Ok(curves),
524                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::EccCurves)),
525            },
526            |last| u32::from(last.value()) + 1,
527        )
528    }
529
530    /// Retrieves the list of active PCR banks and the bank selection mask.
531    ///
532    /// # Errors
533    ///
534    /// Returns
535    /// [`PcrBanksNotAvailable`](crate::TpmDeviceError::PcrBanksNotAvailable)
536    /// when no PCR banks are available.
537    /// Return
538    /// [`PcrBankSelectionMismatch`](crate::TpmDeviceError::PcrBankSelectionMismatch)
539    /// when the PCR selection masks differ between active banks.
540    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
541    /// when receiving unepected TPM response.
542    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
543    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
544    pub fn fetch_pcr_bank_list(
545        &mut self,
546    ) -> Result<(Vec<TpmAlgId>, TpmsPcrSelect), TpmDeviceError> {
547        let pcrs: Vec<TpmsPcrSelection> = self.get_capability(
548            TpmCap::Pcrs,
549            0,
550            Self::CAPABILITY_PAGE_SIZE,
551            |caps| match caps {
552                TpmuCapabilities::Pcrs(pcrs) => Ok(pcrs),
553                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Pcrs)),
554            },
555            |last| last.hash as u32 + 1,
556        )?;
557
558        if pcrs.is_empty() {
559            return Err(TpmDeviceError::PcrBanksNotAvailable);
560        }
561
562        let mut common_select: Option<TpmsPcrSelect> = None;
563        let mut algs = Vec::with_capacity(pcrs.len());
564
565        for bank in pcrs {
566            if bank.pcr_select.iter().all(|&b| b == 0) {
567                debug!(
568                    "skipping unallocated bank {:?} (mask: {})",
569                    bank.hash,
570                    hex::encode(&*bank.pcr_select)
571                );
572                continue;
573            }
574
575            if let Some(ref select) = common_select {
576                if bank.pcr_select != *select {
577                    return Err(TpmDeviceError::PcrBankSelectionMismatch);
578                }
579            } else {
580                common_select = Some(bank.pcr_select);
581            }
582            algs.push(bank.hash);
583        }
584
585        let select = common_select.ok_or(TpmDeviceError::PcrBanksNotAvailable)?;
586
587        algs.sort();
588        Ok((algs, select))
589    }
590
591    /// Fetches and returns one page of capabilities of a certain type from the
592    /// TPM.
593    ///
594    /// # Errors
595    ///
596    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
597    /// when receiving unepected TPM response.
598    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
599    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
600    fn get_capability_page(
601        &mut self,
602        cap: TpmCap,
603        property: TpmUint32,
604        property_count: TpmUint32,
605    ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
606        let cmd = TpmGetCapabilityCommand {
607            cap,
608            property,
609            property_count,
610            handles: [],
611        };
612
613        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
614        let (_, parameters) = response_parts(response, 0)?;
615        let (more_data, parameters) = parse_field_value::<TpmiYesNo>(parameters)?;
616        let (capability_data, rest) = parse_capability_data(parameters)?;
617        ensure_empty(rest)?;
618
619        Ok((more_data.into(), capability_data))
620    }
621
622    /// Reads a specific TPM property.
623    ///
624    /// # Errors
625    ///
626    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
627    /// when receiving unepected TPM response.
628    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
629    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
630    pub fn fetch_tpm_property(&mut self, property: TpmPt) -> Result<u32, TpmDeviceError> {
631        let (_, cap_data) = self.get_capability_page(
632            TpmCap::TpmProperties,
633            TpmUint32::from(property as u32),
634            TpmUint32::from(1),
635        )?;
636
637        let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
638            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
639        };
640
641        let Some(prop) = props.iter().find(|prop| prop.property == property) else {
642            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
643        };
644
645        Ok(prop.value.value())
646    }
647
648    /// Reads the public area of a TPM object.
649    ///
650    /// # Errors
651    ///
652    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
653    /// when receiving unepected TPM response.
654    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
655    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
656    pub fn read_public(
657        &mut self,
658        handle: TpmHandle,
659    ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
660        let cmd = TpmReadPublicCommand { handles: [handle] };
661        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
662        let (_, parameters) = response_parts(response, 0)?;
663        let (public, parameters) = parse_tpm2b_public(parameters)?;
664        let (name, parameters): (Tpm2bName, _) = parse_tpm2b_buffer(parameters)?;
665        let (_qualified_name, rest): (Tpm2bName, _) = parse_tpm2b_buffer(parameters)?;
666        ensure_empty(rest)?;
667
668        Ok((public, name))
669    }
670
671    /// Finds a persistent handle by its `Tpm2bName`.
672    ///
673    /// # Errors
674    ///
675    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
676    /// when receiving unepected TPM response.
677    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
678    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
679    pub fn find_persistent(
680        &mut self,
681        target_name: &Tpm2bName,
682    ) -> Result<Option<TpmHandle>, TpmDeviceError> {
683        for handle in self.fetch_handles(TpmHt::Persistent)? {
684            match self.read_public(handle) {
685                Ok((_, name)) => {
686                    if name == *target_name {
687                        return Ok(Some(handle));
688                    }
689                }
690                Err(TpmDeviceError::TpmRc(rc)) => {
691                    let base = rc.base();
692                    if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
693                        continue;
694                    }
695                    return Err(TpmDeviceError::TpmRc(rc));
696                }
697                Err(e) => return Err(e),
698            }
699        }
700        Ok(None)
701    }
702
703    /// Saves the context of a transient object or session.
704    ///
705    /// # Errors
706    ///
707    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
708    /// when receiving unepected TPM response.
709    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
710    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
711    pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
712        let cmd = TpmContextSaveCommand {
713            handles: [save_handle],
714        };
715        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
716        let (_, parameters) = response_parts(response, 0)?;
717        let (context, rest) = parse_tpms_context(parameters)?;
718        ensure_empty(rest)?;
719
720        Ok(context)
721    }
722
723    /// Loads a TPM context and returns the handle.
724    ///
725    /// # Errors
726    ///
727    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
728    /// when receiving unepected TPM response.
729    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
730    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
731    pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
732        let cmd = TpmContextLoadCommand {
733            context,
734            handles: [],
735        };
736        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
737        let (handles, parameters) = response_parts(response, 1)?;
738        let (handle, rest) = parse_wire_copy::<TpmHandle>(handles)?;
739        ensure_empty(rest)?;
740        ensure_empty(parameters)?;
741
742        Ok(handle)
743    }
744
745    /// Flushes a transient object or session from the TPM and removes it from
746    /// the cache.
747    ///
748    /// # Errors
749    ///
750    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
751    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
752    pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
753        let cmd = TpmFlushContextCommand {
754            flush_handle: handle,
755            handles: [],
756        };
757        self.transmit(&cmd, Self::NO_SESSIONS)?;
758        Ok(())
759    }
760
761    /// Loads a session context and then flushes the resulting handle.
762    ///
763    /// # Errors
764    ///
765    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
766    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
767    pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
768        match self.load_context(context) {
769            Ok(handle) => self.flush_context(handle),
770            Err(TpmDeviceError::TpmRc(rc)) => {
771                let base = rc.base();
772                if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
773                    Ok(())
774                } else {
775                    Err(TpmDeviceError::TpmRc(rc))
776                }
777            }
778            Err(e) => Err(e),
779        }
780    }
781}
782
783fn ensure_empty(buf: &[u8]) -> Result<(), TpmDeviceError> {
784    if buf.is_empty() {
785        Ok(())
786    } else {
787        Err(TpmDeviceError::TrailingData)
788    }
789}
790
791fn response_parts(
792    response: &TpmResponse,
793    response_handles: usize,
794) -> Result<(&[u8], &[u8]), TpmDeviceError> {
795    let handle_len = response_handles
796        .checked_mul(core::mem::size_of::<TpmHandle>())
797        .ok_or(TpmDeviceError::InvalidResponse)?;
798    let body = response.body();
799    if body.len() < handle_len {
800        return Err(TpmDeviceError::InvalidResponse);
801    }
802
803    let (handles, after_handles) = body.split_at(handle_len);
804    if response.tag().map_err(TpmDeviceError::Unmarshal)? != TpmSt::Sessions {
805        return Ok((handles, after_handles));
806    }
807
808    let (parameter_size, after_size) = parse_wire_copy::<TpmUint32>(after_handles)?;
809    let parameter_size =
810        usize::try_from(parameter_size.value()).map_err(|_| TpmDeviceError::InvalidResponse)?;
811    if after_size.len() < parameter_size {
812        return Err(TpmDeviceError::InvalidResponse);
813    }
814
815    let (parameters, _auth_area) = after_size.split_at(parameter_size);
816    Ok((handles, parameters))
817}
818
819fn parse_wire_copy<'a, T>(buf: &'a [u8]) -> Result<(T, &'a [u8]), TpmDeviceError>
820where
821    T: TpmCast + Copy + 'a,
822{
823    let (value, rest) = T::cast_prefix(buf).map_err(TpmDeviceError::Unmarshal)?;
824    Ok((*value, rest))
825}
826
827fn parse_field_value<'a, T>(buf: &'a [u8]) -> Result<(T, &'a [u8]), TpmDeviceError>
828where
829    T: TpmField<'a, View = T>,
830{
831    <T as TpmField<'a>>::cast_prefix_field(buf).map_err(TpmDeviceError::Unmarshal)
832}
833
834fn parse_tpm2b_buffer<const CAPACITY: usize>(
835    buf: &[u8],
836) -> Result<(TpmBuffer<CAPACITY>, &[u8]), TpmDeviceError> {
837    let (value, rest) =
838        Tpm2bWire::<CAPACITY>::cast_prefix(buf).map_err(TpmDeviceError::Unmarshal)?;
839    let value = TpmBuffer::<CAPACITY>::try_from(value.data()).map_err(TpmDeviceError::Unmarshal)?;
840
841    Ok((value, rest))
842}
843
844fn parse_tpms_scheme_hash(buf: &[u8]) -> Result<(TpmsSchemeHash, &[u8]), TpmDeviceError> {
845    let (hash_alg, rest) = parse_field_value::<TpmAlgId>(buf)?;
846
847    Ok((TpmsSchemeHash { hash_alg }, rest))
848}
849
850fn parse_tpmt_kdf_scheme(buf: &[u8]) -> Result<(TpmtKdfScheme, &[u8]), TpmDeviceError> {
851    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
852    let (details, rest) = parse_tpmu_kdf_scheme(scheme, buf)?;
853
854    Ok((TpmtKdfScheme { scheme, details }, rest))
855}
856
857fn parse_tpmu_kdf_scheme(
858    scheme: TpmAlgId,
859    buf: &[u8],
860) -> Result<(TpmuKdfScheme, &[u8]), TpmDeviceError> {
861    match scheme {
862        TpmAlgId::Mgf1 => {
863            let (details, rest) = parse_tpms_scheme_hash(buf)?;
864            Ok((TpmuKdfScheme::Mgf1(details), rest))
865        }
866        TpmAlgId::Kdf1Sp800_56A => {
867            let (details, rest) = parse_tpms_scheme_hash(buf)?;
868            Ok((TpmuKdfScheme::Kdf1Sp800_56a(details), rest))
869        }
870        TpmAlgId::Kdf2 => {
871            let (details, rest) = parse_tpms_scheme_hash(buf)?;
872            Ok((TpmuKdfScheme::Kdf2(details), rest))
873        }
874        TpmAlgId::Kdf1Sp800_108 => {
875            let (details, rest) = parse_tpms_scheme_hash(buf)?;
876            Ok((TpmuKdfScheme::Kdf1Sp800_108(details), rest))
877        }
878        TpmAlgId::Null => Ok((TpmuKdfScheme::Null, buf)),
879        _ => Err(TpmDeviceError::InvalidResponse),
880    }
881}
882
883fn parse_tpms_scheme_xor(buf: &[u8]) -> Result<(TpmsSchemeXor, &[u8]), TpmDeviceError> {
884    let (hash_alg, buf) = parse_field_value::<TpmAlgId>(buf)?;
885    let (kdf, rest) = parse_tpmt_kdf_scheme(buf)?;
886
887    Ok((TpmsSchemeXor { hash_alg, kdf }, rest))
888}
889
890fn parse_tpmu_keyedhash_scheme(
891    scheme: TpmAlgId,
892    buf: &[u8],
893) -> Result<(TpmuKeyedhashScheme, &[u8]), TpmDeviceError> {
894    match scheme {
895        TpmAlgId::Hmac => {
896            let (details, rest) = parse_tpms_scheme_hash(buf)?;
897            Ok((TpmuKeyedhashScheme::Hmac(details), rest))
898        }
899        TpmAlgId::Xor => {
900            let (details, rest) = parse_tpms_scheme_xor(buf)?;
901            Ok((TpmuKeyedhashScheme::Xor(details), rest))
902        }
903        TpmAlgId::Null => Ok((TpmuKeyedhashScheme::Null, buf)),
904        _ => Err(TpmDeviceError::InvalidResponse),
905    }
906}
907
908fn parse_tpmt_keyedhash_scheme(buf: &[u8]) -> Result<(TpmtKeyedhashScheme, &[u8]), TpmDeviceError> {
909    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
910    let (details, rest) = parse_tpmu_keyedhash_scheme(scheme, buf)?;
911
912    Ok((TpmtKeyedhashScheme { scheme, details }, rest))
913}
914
915fn parse_tpmu_asym_scheme(
916    scheme: TpmAlgId,
917    buf: &[u8],
918) -> Result<(TpmuAsymScheme, &[u8]), TpmDeviceError> {
919    match scheme {
920        TpmAlgId::Rsassa
921        | TpmAlgId::Rsapss
922        | TpmAlgId::Ecdsa
923        | TpmAlgId::Ecdaa
924        | TpmAlgId::Sm2
925        | TpmAlgId::Ecschnorr
926        | TpmAlgId::Oaep
927        | TpmAlgId::Ecdh
928        | TpmAlgId::Ecmqv => {
929            let (details, rest) = parse_tpms_scheme_hash(buf)?;
930            Ok((TpmuAsymScheme::Hash(details), rest))
931        }
932        TpmAlgId::Rsaes | TpmAlgId::Null => Ok((TpmuAsymScheme::Null, buf)),
933        _ => Err(TpmDeviceError::InvalidResponse),
934    }
935}
936
937fn parse_tpmt_rsa_scheme(buf: &[u8]) -> Result<(TpmtRsaScheme, &[u8]), TpmDeviceError> {
938    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
939    let (details, rest) = parse_tpmu_asym_scheme(scheme, buf)?;
940
941    Ok((TpmtRsaScheme { scheme, details }, rest))
942}
943
944fn parse_tpmt_ecc_scheme(buf: &[u8]) -> Result<(TpmtEccScheme, &[u8]), TpmDeviceError> {
945    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
946    let (details, rest) = parse_tpmu_asym_scheme(scheme, buf)?;
947
948    Ok((TpmtEccScheme { scheme, details }, rest))
949}
950
951fn parse_tpmu_sym_key_bits(
952    algorithm: TpmAlgId,
953    buf: &[u8],
954) -> Result<(TpmuSymKeyBits, &[u8]), TpmDeviceError> {
955    match algorithm {
956        TpmAlgId::Aes => {
957            let (value, rest) = parse_wire_copy::<TpmUint16>(buf)?;
958            Ok((TpmuSymKeyBits::Aes(value), rest))
959        }
960        TpmAlgId::Sm4 => {
961            let (value, rest) = parse_wire_copy::<TpmUint16>(buf)?;
962            Ok((TpmuSymKeyBits::Sm4(value), rest))
963        }
964        TpmAlgId::Camellia => {
965            let (value, rest) = parse_wire_copy::<TpmUint16>(buf)?;
966            Ok((TpmuSymKeyBits::Camellia(value), rest))
967        }
968        TpmAlgId::Xor => {
969            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
970            Ok((TpmuSymKeyBits::Xor(value), rest))
971        }
972        TpmAlgId::Null => Ok((TpmuSymKeyBits::Null, buf)),
973        _ => Err(TpmDeviceError::InvalidResponse),
974    }
975}
976
977fn parse_tpmu_sym_mode(
978    algorithm: TpmAlgId,
979    buf: &[u8],
980) -> Result<(TpmuSymMode, &[u8]), TpmDeviceError> {
981    match algorithm {
982        TpmAlgId::Aes => {
983            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
984            Ok((TpmuSymMode::Aes(value), rest))
985        }
986        TpmAlgId::Sm4 => {
987            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
988            Ok((TpmuSymMode::Sm4(value), rest))
989        }
990        TpmAlgId::Camellia => {
991            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
992            Ok((TpmuSymMode::Camellia(value), rest))
993        }
994        TpmAlgId::Xor => {
995            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
996            Ok((TpmuSymMode::Xor(value), rest))
997        }
998        TpmAlgId::Null => Ok((TpmuSymMode::Null, buf)),
999        _ => Err(TpmDeviceError::InvalidResponse),
1000    }
1001}
1002
1003fn parse_tpmt_sym_def(buf: &[u8]) -> Result<(TpmtSymDefObject, &[u8]), TpmDeviceError> {
1004    let (algorithm, buf) = parse_field_value::<TpmAlgId>(buf)?;
1005    if algorithm == TpmAlgId::Null {
1006        return Ok((TpmtSymDefObject::default(), buf));
1007    }
1008
1009    let (key_bits, buf) = parse_tpmu_sym_key_bits(algorithm, buf)?;
1010    let (mode, rest) = parse_tpmu_sym_mode(algorithm, buf)?;
1011
1012    Ok((
1013        TpmtSymDefObject {
1014            algorithm,
1015            key_bits,
1016            mode,
1017        },
1018        rest,
1019    ))
1020}
1021
1022fn parse_tpmu_public_parms(
1023    object_type: TpmAlgId,
1024    buf: &[u8],
1025) -> Result<(TpmuPublicParms, &[u8]), TpmDeviceError> {
1026    match object_type {
1027        TpmAlgId::KeyedHash => {
1028            let (scheme, rest) = parse_tpmt_keyedhash_scheme(buf)?;
1029            Ok((
1030                TpmuPublicParms::KeyedHash(TpmsKeyedhashParms { scheme }),
1031                rest,
1032            ))
1033        }
1034        TpmAlgId::SymCipher => {
1035            let (sym, rest) = parse_tpmt_sym_def(buf)?;
1036            Ok((TpmuPublicParms::SymCipher(TpmsSymcipherParms { sym }), rest))
1037        }
1038        TpmAlgId::Rsa => {
1039            let (symmetric, buf) = parse_tpmt_sym_def(buf)?;
1040            let (scheme, buf) = parse_tpmt_rsa_scheme(buf)?;
1041            let (key_bits, buf) = parse_wire_copy::<TpmUint16>(buf)?;
1042            let (exponent, rest) = parse_wire_copy::<TpmUint32>(buf)?;
1043            Ok((
1044                TpmuPublicParms::Rsa(TpmsRsaParms {
1045                    symmetric,
1046                    scheme,
1047                    key_bits,
1048                    exponent,
1049                }),
1050                rest,
1051            ))
1052        }
1053        TpmAlgId::Ecc => {
1054            let (symmetric, buf) = parse_tpmt_sym_def(buf)?;
1055            let (scheme, buf) = parse_tpmt_ecc_scheme(buf)?;
1056            let (curve_id, buf) = parse_field_value::<TpmEccCurve>(buf)?;
1057            let (kdf, rest) = parse_tpmt_kdf_scheme(buf)?;
1058            Ok((
1059                TpmuPublicParms::Ecc(TpmsEccParms {
1060                    symmetric,
1061                    scheme,
1062                    curve_id,
1063                    kdf,
1064                }),
1065                rest,
1066            ))
1067        }
1068        TpmAlgId::Null => Ok((TpmuPublicParms::Null, buf)),
1069        _ => Err(TpmDeviceError::InvalidResponse),
1070    }
1071}
1072
1073fn parse_tpms_ecc_point(buf: &[u8]) -> Result<(TpmsEccPoint, &[u8]), TpmDeviceError> {
1074    let (x, buf): (Tpm2bEccParameter, _) = parse_tpm2b_buffer(buf)?;
1075    let (y, rest): (Tpm2bEccParameter, _) = parse_tpm2b_buffer(buf)?;
1076
1077    Ok((TpmsEccPoint { x, y }, rest))
1078}
1079
1080fn parse_tpmu_public_id(
1081    object_type: TpmAlgId,
1082    buf: &[u8],
1083) -> Result<(TpmuPublicId, &[u8]), TpmDeviceError> {
1084    match object_type {
1085        TpmAlgId::KeyedHash => {
1086            let (value, rest): (Tpm2bDigest, _) = parse_tpm2b_buffer(buf)?;
1087            Ok((TpmuPublicId::KeyedHash(value), rest))
1088        }
1089        TpmAlgId::SymCipher => {
1090            let (value, rest): (Tpm2bSymKey, _) = parse_tpm2b_buffer(buf)?;
1091            Ok((TpmuPublicId::SymCipher(value), rest))
1092        }
1093        TpmAlgId::Rsa => {
1094            let (value, rest): (Tpm2bPublicKeyRsa, _) = parse_tpm2b_buffer(buf)?;
1095            Ok((TpmuPublicId::Rsa(value), rest))
1096        }
1097        TpmAlgId::Ecc => {
1098            let (value, rest) = parse_tpms_ecc_point(buf)?;
1099            Ok((TpmuPublicId::Ecc(value), rest))
1100        }
1101        TpmAlgId::Null => Ok((TpmuPublicId::Null, buf)),
1102        _ => Err(TpmDeviceError::InvalidResponse),
1103    }
1104}
1105
1106fn parse_tpmt_public(buf: &[u8]) -> Result<(TpmtPublic, &[u8]), TpmDeviceError> {
1107    let (object_type, buf) = parse_field_value::<TpmAlgId>(buf)?;
1108    let (name_alg, buf) = parse_field_value::<TpmAlgId>(buf)?;
1109    let (object_attributes, buf) = parse_field_value::<TpmaObject>(buf)?;
1110    let (auth_policy, buf): (Tpm2bDigest, _) = parse_tpm2b_buffer(buf)?;
1111    let (parameters, buf) = parse_tpmu_public_parms(object_type, buf)?;
1112    let (unique, rest) = parse_tpmu_public_id(object_type, buf)?;
1113
1114    Ok((
1115        TpmtPublic {
1116            object_type,
1117            name_alg,
1118            object_attributes,
1119            auth_policy,
1120            parameters,
1121            unique,
1122        },
1123        rest,
1124    ))
1125}
1126
1127fn parse_tpm2b_public(buf: &[u8]) -> Result<(TpmtPublic, &[u8]), TpmDeviceError> {
1128    let (size, buf) = parse_wire_copy::<TpmUint16>(buf)?;
1129    let size = usize::from(size.value());
1130    if buf.len() < size {
1131        return Err(TpmDeviceError::InvalidResponse);
1132    }
1133
1134    let (public, rest) = buf.split_at(size);
1135    let (public, public_rest) = parse_tpmt_public(public)?;
1136    ensure_empty(public_rest)?;
1137
1138    Ok((public, rest))
1139}
1140
1141fn parse_tpms_context(buf: &[u8]) -> Result<(TpmsContext, &[u8]), TpmDeviceError> {
1142    let (sequence, buf) = parse_wire_copy::<TpmUint64>(buf)?;
1143    let (saved_handle, buf) = parse_wire_copy::<TpmHandle>(buf)?;
1144    let (hierarchy, buf) = parse_field_value::<TpmRh>(buf)?;
1145    let (context_blob, rest): (TpmBuffer<TPM_MAX_COMMAND_SIZE>, _) = parse_tpm2b_buffer(buf)?;
1146
1147    Ok((
1148        TpmsContext {
1149            sequence,
1150            saved_handle,
1151            hierarchy,
1152            context_blob,
1153        },
1154        rest,
1155    ))
1156}
1157
1158fn parse_list<'a, T, const CAPACITY: usize>(
1159    buf: &'a [u8],
1160    mut parse_item: impl FnMut(&'a [u8]) -> Result<(T, &'a [u8]), TpmDeviceError>,
1161) -> Result<(TpmList<T, CAPACITY>, &'a [u8]), TpmDeviceError>
1162where
1163    T: Copy,
1164{
1165    let (count, mut cursor) = parse_wire_copy::<TpmUint32>(buf)?;
1166    let mut list = TpmList::<T, CAPACITY>::new();
1167
1168    for _ in 0..count.value() {
1169        let (item, rest) = parse_item(cursor)?;
1170        list.try_push(item).map_err(TpmDeviceError::Unmarshal)?;
1171        cursor = rest;
1172    }
1173
1174    Ok((list, cursor))
1175}
1176
1177fn parse_tpms_alg_property(buf: &[u8]) -> Result<(TpmsAlgProperty, &[u8]), TpmDeviceError> {
1178    let (alg, buf) = parse_field_value::<TpmAlgId>(buf)?;
1179    let (alg_properties, rest) = parse_field_value::<TpmaAlgorithm>(buf)?;
1180
1181    Ok((
1182        TpmsAlgProperty {
1183            alg,
1184            alg_properties,
1185        },
1186        rest,
1187    ))
1188}
1189
1190fn parse_tpms_tagged_property(buf: &[u8]) -> Result<(TpmsTaggedProperty, &[u8]), TpmDeviceError> {
1191    let (property, buf) = parse_field_value::<TpmPt>(buf)?;
1192    let (value, rest) = parse_wire_copy::<TpmUint32>(buf)?;
1193
1194    Ok((TpmsTaggedProperty { property, value }, rest))
1195}
1196
1197fn parse_tpms_pcr_selection(buf: &[u8]) -> Result<(TpmsPcrSelection, &[u8]), TpmDeviceError> {
1198    let (hash, buf) = parse_field_value::<TpmAlgId>(buf)?;
1199    let (pcr_select, rest) =
1200        <TpmsPcrSelect as TpmField>::cast_prefix_field(buf).map_err(TpmDeviceError::Unmarshal)?;
1201    let pcr_select = TpmsPcrSelect::try_from(pcr_select).map_err(TpmDeviceError::Unmarshal)?;
1202
1203    Ok((TpmsPcrSelection { hash, pcr_select }, rest))
1204}
1205
1206fn parse_capability_data(buf: &[u8]) -> Result<(TpmsCapabilityData, &[u8]), TpmDeviceError> {
1207    let (capability, buf) = parse_field_value::<TpmCap>(buf)?;
1208    let (data, rest) = match capability {
1209        TpmCap::Algs => {
1210            let (list, rest) = parse_list::<TpmsAlgProperty, 64>(buf, parse_tpms_alg_property)?;
1211            (TpmuCapabilities::Algs(list), rest)
1212        }
1213        TpmCap::Handles => {
1214            let (list, rest) = parse_list::<TpmHandle, 128>(buf, parse_wire_copy::<TpmHandle>)?;
1215            (TpmuCapabilities::Handles(list), rest)
1216        }
1217        TpmCap::Pcrs => {
1218            let (list, rest) = parse_list::<TpmsPcrSelection, 8>(buf, parse_tpms_pcr_selection)?;
1219            (TpmuCapabilities::Pcrs(list), rest)
1220        }
1221        TpmCap::Commands => {
1222            let (list, rest) = parse_list::<TpmaCc, 256>(buf, parse_field_value::<TpmaCc>)?;
1223            (TpmuCapabilities::Commands(list), rest)
1224        }
1225        TpmCap::TpmProperties => {
1226            let (list, rest) =
1227                parse_list::<TpmsTaggedProperty, 64>(buf, parse_tpms_tagged_property)?;
1228            (TpmuCapabilities::TpmProperties(list), rest)
1229        }
1230        TpmCap::EccCurves => {
1231            let (list, rest) =
1232                parse_list::<TpmEccCurve, 64>(buf, parse_field_value::<TpmEccCurve>)?;
1233            (TpmuCapabilities::EccCurves(list), rest)
1234        }
1235        TpmCap::PpCommands | TpmCap::AuditCommands | TpmCap::AuthPolicies | TpmCap::Act => {
1236            return Err(TpmDeviceError::InvalidResponse);
1237        }
1238    };
1239
1240    Ok((TpmsCapabilityData { capability, data }, rest))
1241}
1242
1243/// A builder for creating a TPM policy session.
1244pub struct TpmPolicySessionBuilder {
1245    bind: TpmHandle,
1246    tpm_key: TpmHandle,
1247    nonce_caller: Option<Tpm2bNonce>,
1248    encrypted_salt: Option<Tpm2bEncryptedSecret>,
1249    session_type: TpmSe,
1250    symmetric: TpmtSymDefObject,
1251    auth_hash: TpmAlgId,
1252}
1253
1254impl Default for TpmPolicySessionBuilder {
1255    fn default() -> Self {
1256        Self {
1257            bind: (TpmRh::Null as u32).into(),
1258            tpm_key: (TpmRh::Null as u32).into(),
1259            nonce_caller: None,
1260            encrypted_salt: None,
1261            session_type: TpmSe::Policy,
1262            symmetric: TpmtSymDefObject::default(),
1263            auth_hash: TpmAlgId::Sha256,
1264        }
1265    }
1266}
1267
1268impl TpmPolicySessionBuilder {
1269    #[must_use]
1270    pub fn new() -> Self {
1271        Self::default()
1272    }
1273
1274    #[must_use]
1275    pub fn with_bind(mut self, bind: TpmHandle) -> Self {
1276        self.bind = bind;
1277        self
1278    }
1279
1280    #[must_use]
1281    pub fn with_tpm_key(mut self, tpm_key: TpmHandle) -> Self {
1282        self.tpm_key = tpm_key;
1283        self
1284    }
1285
1286    #[must_use]
1287    pub fn with_nonce_caller(mut self, nonce: Tpm2bNonce) -> Self {
1288        self.nonce_caller = Some(nonce);
1289        self
1290    }
1291
1292    #[must_use]
1293    pub fn with_encrypted_salt(mut self, salt: Tpm2bEncryptedSecret) -> Self {
1294        self.encrypted_salt = Some(salt);
1295        self
1296    }
1297
1298    #[must_use]
1299    pub fn with_session_type(mut self, session_type: TpmSe) -> Self {
1300        self.session_type = session_type;
1301        self
1302    }
1303
1304    #[must_use]
1305    pub fn with_symmetric(mut self, symmetric: TpmtSymDefObject) -> Self {
1306        self.symmetric = symmetric;
1307        self
1308    }
1309
1310    #[must_use]
1311    pub fn with_auth_hash(mut self, auth_hash: TpmAlgId) -> Self {
1312        self.auth_hash = auth_hash;
1313        self
1314    }
1315
1316    /// Opens the policy session on the provided device.
1317    ///
1318    /// # Errors
1319    ///
1320    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch) if
1321    /// the TPM response is unexpected.
1322    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
1323    /// operation on TPM protocol compliant data fails.
1324    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
1325    /// on function.
1326    pub fn open(self, device: &mut TpmDevice) -> Result<TpmPolicySession, TpmDeviceError> {
1327        let nonce_caller = if let Some(nonce) = self.nonce_caller {
1328            nonce
1329        } else {
1330            let digest_len = TpmHash::try_from(self.auth_hash)
1331                .map_err(|_| TpmDeviceError::UnsupportedAlgorithm(self.auth_hash))?
1332                .size();
1333            let mut nonce_bytes = vec![0; digest_len];
1334            thread_rng().fill_bytes(&mut nonce_bytes);
1335            Tpm2bNonce::try_from(nonce_bytes.as_slice()).map_err(TpmDeviceError::Unmarshal)?
1336        };
1337
1338        let cmd = TpmStartAuthSessionCommand {
1339            nonce_caller,
1340            encrypted_salt: self.encrypted_salt.unwrap_or_default(),
1341            session_type: self.session_type,
1342            symmetric: self.symmetric,
1343            auth_hash: self.auth_hash,
1344            handles: [self.tpm_key, self.bind],
1345        };
1346
1347        let response = device.transmit(&cmd, TpmDevice::NO_SESSIONS)?;
1348        let (handles, parameters) = response_parts(response, 1)?;
1349        let (handle, rest) = parse_wire_copy::<TpmHandle>(handles)?;
1350        ensure_empty(rest)?;
1351        let (nonce_tpm, rest): (Tpm2bNonce, _) = parse_tpm2b_buffer(parameters)?;
1352        ensure_empty(rest)?;
1353
1354        Ok(TpmPolicySession {
1355            handle,
1356            attributes: TpmaSession::CONTINUE_SESSION,
1357            hash_alg: self.auth_hash,
1358            nonce_tpm,
1359        })
1360    }
1361}
1362
1363/// Represents an active TPM policy session.
1364#[derive(Debug, Clone)]
1365pub struct TpmPolicySession {
1366    handle: TpmHandle,
1367    attributes: TpmaSession,
1368    hash_alg: TpmAlgId,
1369    nonce_tpm: Tpm2bNonce,
1370}
1371
1372impl TpmPolicySession {
1373    /// Creates a new builder for `TpmPolicySession`.
1374    #[must_use]
1375    pub fn builder() -> TpmPolicySessionBuilder {
1376        TpmPolicySessionBuilder::new()
1377    }
1378
1379    /// Returns the session handle.
1380    #[must_use]
1381    pub fn handle(&self) -> TpmHandle {
1382        self.handle
1383    }
1384
1385    /// Returns the session attributes.
1386    #[must_use]
1387    pub fn attributes(&self) -> TpmaSession {
1388        self.attributes
1389    }
1390
1391    /// Returns the hash algorithm used by the session.
1392    #[must_use]
1393    pub fn hash_alg(&self) -> TpmAlgId {
1394        self.hash_alg
1395    }
1396
1397    /// Returns the nonce generated by the TPM.
1398    #[must_use]
1399    pub fn nonce_tpm(&self) -> &Tpm2bNonce {
1400        &self.nonce_tpm
1401    }
1402
1403    /// Applies a list of policy commands to this session.
1404    ///
1405    /// This method iterates through the provided commands, updates the first handle
1406    /// of each command (or second for `PolicySecret`) to point to this session,
1407    /// and transmits them to the device.
1408    ///
1409    /// # Errors
1410    ///
1411    /// Returns [`InvalidCc`](crate::TpmDeviceError::InvalidCc) when a command is not
1412    /// a supported policy command.
1413    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
1414    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
1415    pub fn run(
1416        &self,
1417        device: &mut TpmDevice,
1418        commands: impl IntoIterator<Item = (TpmCommand, TpmAuthCommands)>,
1419    ) -> Result<(), TpmDeviceError> {
1420        for (mut command_body, auth_sessions) in commands {
1421            match &mut command_body {
1422                TpmCommand::PolicyPcr(cmd) => cmd.handles[0] = self.handle,
1423                TpmCommand::PolicyOr(cmd) => cmd.handles[0] = self.handle,
1424                TpmCommand::PolicyRestart(cmd) => {
1425                    cmd.handles[0] = self.handle;
1426                }
1427                TpmCommand::PolicySecret(cmd) => {
1428                    cmd.handles[1] = self.handle;
1429                }
1430                _ => {
1431                    return Err(TpmDeviceError::InvalidCc(command_body.cc()));
1432                }
1433            }
1434            device.transmit(&command_body, auth_sessions.as_ref())?;
1435        }
1436        Ok(())
1437    }
1438
1439    /// Flushes the session context from the TPM.
1440    ///
1441    /// # Errors
1442    ///
1443    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
1444    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
1445    pub fn flush(&self, device: &mut TpmDevice) -> Result<(), TpmDeviceError> {
1446        device.flush_context(self.handle)
1447    }
1448}