cli/command/
memory.rs

1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5use crate::{
6    cli::{get_auth, SubCommand},
7    command::CommandError,
8    context::ContextCache,
9    device::{self, Device},
10    key, x509,
11};
12use argh::FromArgs;
13use rasn::types::Tag;
14use std::{cell::RefCell, rc::Rc};
15use strum::Display;
16use tabled::Tabled;
17use tpm2_protocol::data::{TpmHt, TpmPt, TpmSe};
18
19#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
20#[strum(serialize_all = "kebab-case")]
21enum MemoryHandleType {
22    Transient,
23    Persistent,
24    Session,
25    Certificate,
26}
27
28#[derive(Tabled)]
29struct MemoryRow {
30    #[tabled(rename = "HANDLE")]
31    handle: String,
32    #[tabled(rename = "TYPE")]
33    handle_type: String,
34    #[tabled(rename = "DETAILS")]
35    details: String,
36}
37
38/// Lists objects inside TPM memory.
39#[derive(FromArgs, Debug)]
40#[argh(subcommand, name = "memory", note = "Lists objects inside TPM memory")]
41pub struct Memory {
42    /// parent auth: 'password://<hex>' or 'session://<handle>'
43    /// Uses TPM2SH_PARENT_AUTH environment variable if not set.
44    #[argh(option, arg_name = "auth", short = 'p')]
45    pub parent_auth: Option<String>,
46
47    /// auth for the object: 'password://<hex>' or 'session://<handle>'
48    /// Uses TPM2SH_AUTH environment variable if not set.
49    #[argh(option, arg_name = "auth", short = 'a')]
50    pub auth: Option<String>,
51
52    /// hmac auth: 'password://<hex>' or 'session://<handle>'
53    /// Uses TPM2SH_HMAC_AUTH environment variable if not set.
54    #[argh(option, arg_name = "auth", short = 'm', long = "hmac-auth")]
55    pub hmac_auth: Option<String>,
56}
57
58impl Memory {
59    /// Fetches handles of a specific type and adds them as rows to the table.
60    ///
61    /// This helper function abstracts the repetitive logic of querying handles,
62    /// processing each one to get its details, and adding it to the final list.
63    fn fetch_rows<F>(
64        device: &mut Device,
65        rows: &mut Vec<MemoryRow>,
66        handle_type_to_query: u32,
67        display_type: MemoryHandleType,
68        mut get_details: F,
69    ) -> Result<(), CommandError>
70    where
71        F: FnMut(&mut Device, u32) -> Result<String, CommandError>,
72    {
73        for handle in device.get_all_handles(handle_type_to_query << 24)? {
74            match get_details(device, handle) {
75                Ok(details) => {
76                    rows.push(MemoryRow {
77                        handle: format!("{handle:08x}"),
78                        handle_type: display_type.to_string(),
79                        details,
80                    });
81                }
82                Err(e) => {
83                    log::debug!("Could not retrieve details for handle {handle:08x}: {e}");
84                }
85            }
86        }
87        Ok(())
88    }
89
90    /// Fetches the public part of a key and formats its algorithm details.
91    fn fetch_details(device: &mut Device, handle: u32) -> Result<String, CommandError> {
92        let (public, _) = device.read_public(handle.into())?;
93        Ok(key::format_alg_from_public(&public))
94    }
95}
96
97impl SubCommand for Memory {
98    fn run(
99        &self,
100        device: Option<Rc<RefCell<Device>>>,
101        context: &mut ContextCache,
102        plain: bool,
103    ) -> Result<(), CommandError> {
104        let nv_auth = get_auth(
105            self.parent_auth.as_ref(),
106            "TPM2SH_PARENT_AUTH",
107            &context.session_map,
108            &[TpmSe::Policy],
109        )?;
110        device::with_device(device, |device| {
111            let mut rows: Vec<MemoryRow> = Vec::new();
112
113            Self::fetch_rows(
114                device,
115                &mut rows,
116                TpmHt::Persistent as u32,
117                MemoryHandleType::Persistent,
118                Self::fetch_details,
119            )?;
120
121            Self::fetch_rows(
122                device,
123                &mut rows,
124                TpmHt::Transient as u32,
125                MemoryHandleType::Transient,
126                Self::fetch_details,
127            )?;
128
129            Self::fetch_rows(
130                device,
131                &mut rows,
132                TpmHt::HmacSession as u32,
133                MemoryHandleType::Session,
134                |_, handle| {
135                    let mso = (handle >> 24) as u8;
136                    let detail = if mso == TpmHt::HmacSession as u8 {
137                        "hmac"
138                    } else {
139                        "policy"
140                    };
141                    Ok(detail.to_string())
142                },
143            )?;
144
145            Self::fetch_rows(
146                device,
147                &mut rows,
148                TpmHt::PolicySession as u32,
149                MemoryHandleType::Session,
150                |_, _| Ok("saved".to_string()),
151            )?;
152
153            let max_read_size = device.get_tpm_property(TpmPt::NvBufferMax).unwrap_or(0) as usize;
154
155            if max_read_size > 0 {
156                Self::fetch_rows(
157                    device,
158                    &mut rows,
159                    TpmHt::NvIndex as u32,
160                    MemoryHandleType::Certificate,
161                    |device, handle| {
162                        if !(0x01C0_0000..=0x01C0_FFFF).contains(&handle) {
163                            return Err(CommandError::InvalidInput("Not a certificate".into()));
164                        }
165                        let cert_bytes = context
166                            .read_certificate(
167                                device,
168                                std::slice::from_ref(&nv_auth),
169                                handle,
170                                max_read_size,
171                            )?
172                            .ok_or(CommandError::InvalidInput("No certificate data".into()))?;
173
174                        if cert_bytes.is_empty()
175                            || u32::from(cert_bytes[0]) != (0x20 | Tag::SEQUENCE.value)
176                        {
177                            return Err(CommandError::InvalidInput("Not a DER certificate".into()));
178                        }
179                        Ok(x509::get_algorithm(&cert_bytes)?)
180                    },
181                )?;
182            }
183
184            rows.sort_unstable_by(|a, b| a.handle.cmp(&b.handle));
185
186            super::print_table(&mut context.writer, rows, plain)?;
187
188            Ok(())
189        })
190    }
191}