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    auth::Auth,
7    cli::SubCommand,
8    command::{print_table, CommandError, Tabled},
9    device::{self, Device},
10    handle::Handle,
11    job::Job,
12    key::{
13        format_alg_from_public, KeyError, Tpm2shAlgId, OID_ECDSA_WITH_SHA256,
14        OID_ECDSA_WITH_SHA384, OID_ECDSA_WITH_SHA512, OID_RSA_ENCRYPTION,
15        OID_SHA1_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION,
16        OID_SHA384_WITH_RSA_ENCRYPTION, OID_SHA512_WITH_RSA_ENCRYPTION, SECP_256_R_1, SECP_384_R_1,
17        SECP_521_R_1,
18    },
19};
20use clap::Args;
21use num_bigint::ToBigInt;
22use rasn::{
23    types::{BitString, Integer, ObjectIdentifier, SequenceOf},
24    AsnType, Decode, Decoder,
25};
26use strum::Display;
27use tpm2_protocol::{
28    data::{TpmAlgId, TpmHt, TpmPt},
29    TpmHandle,
30};
31
32#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
33#[strum(serialize_all = "kebab-case")]
34enum MemoryHandleType {
35    Transient,
36    Persistent,
37    Session,
38    Certificate,
39}
40
41struct MemoryRow {
42    handle: String,
43    class: String,
44    details: String,
45}
46
47impl Tabled for MemoryRow {
48    fn headers() -> Vec<String> {
49        vec![
50            "HANDLE".to_string(),
51            "TYPE".to_string(),
52            "DETAILS".to_string(),
53        ]
54    }
55
56    fn row(&self) -> Vec<String> {
57        vec![
58            self.handle.clone(),
59            self.class.clone(),
60            self.details.clone(),
61        ]
62    }
63}
64
65/// Lists active TPM objects.
66#[derive(Args, Debug)]
67#[command(about = "Lists objects inside TPM memory")]
68pub struct Memory {}
69
70impl SubCommand for Memory {
71    fn run(&self, job: &mut Job) -> Result<(), CommandError> {
72        device::with_device(job.device.clone(), |device| {
73            let mut rows: Vec<MemoryRow> = Vec::new();
74            Self::fetch_rows(
75                device,
76                &mut rows,
77                TpmHt::Persistent,
78                MemoryHandleType::Persistent,
79                Self::fetch_details,
80            )?;
81            Self::fetch_rows(
82                device,
83                &mut rows,
84                TpmHt::Transient,
85                MemoryHandleType::Transient,
86                Self::fetch_details,
87            )?;
88            Self::fetch_rows(
89                device,
90                &mut rows,
91                TpmHt::LoadedSession,
92                MemoryHandleType::Session,
93                |_, handle| {
94                    let ht = TpmHt::try_from(handle)?;
95                    let detail = if ht == TpmHt::HmacSession {
96                        "hmac"
97                    } else {
98                        "policy"
99                    };
100                    Ok(detail.to_string())
101                },
102            )?;
103
104            Self::fetch_rows(
105                device,
106                &mut rows,
107                TpmHt::SavedSession,
108                MemoryHandleType::Session,
109                |_, _| Ok("saved".to_string()),
110            )?;
111
112            let max_read_size = device.get_tpm_property(TpmPt::NvBufferMax).unwrap_or(0) as usize;
113
114            if max_read_size > 0 {
115                Self::fetch_rows(
116                    device,
117                    &mut rows,
118                    TpmHt::NvIndex,
119                    MemoryHandleType::Certificate,
120                    |device, handle| {
121                        let handle_val = handle.value();
122                        if !(0x01C0_0000..=0x01C0_FFFF).contains(&handle_val) {
123                            return Err(CommandError::InvalidInput("Not a certificate".into()));
124                        }
125                        let auths = vec![Auth::default()];
126                        let cert_bytes = job
127                            .read_certificate(device, &auths, handle_val, max_read_size)?
128                            .ok_or(CommandError::InvalidInput("No certificate data".into()))?;
129                        if cert_bytes.is_empty() || u32::from(cert_bytes[0]) != (0x30) {
130                            return Err(CommandError::InvalidInput("Not a DER certificate".into()));
131                        }
132                        Ok(Memory::fetch_alg_name(&cert_bytes)?)
133                    },
134                )?;
135            }
136            rows.sort_unstable_by(|a, b| a.handle.cmp(&b.handle));
137            print_table(&mut job.writer, &rows)?;
138            Ok(())
139        })
140    }
141}
142
143fn default_bool_false() -> bool {
144    false
145}
146
147#[derive(AsnType, Decode, Debug)]
148struct AlgorithmIdentifier {
149    algorithm: ObjectIdentifier,
150    parameters: Option<rasn::types::Any>,
151}
152
153#[derive(AsnType, Decode, Debug)]
154struct SubjectPublicKeyInfo {
155    algorithm: AlgorithmIdentifier,
156    subject_public_key: BitString,
157}
158
159#[derive(AsnType, Decode, Debug)]
160struct RsaPublicKey {
161    modulus: Integer,
162    _public_exponent: Integer,
163}
164
165#[derive(AsnType, Decode, Debug)]
166struct Extension {
167    _extn_id: ObjectIdentifier,
168    #[rasn(default = "default_bool_false")]
169    _critical: bool,
170    _extn_value: rasn::types::OctetString,
171}
172
173#[derive(AsnType, Decode, Debug)]
174struct TbsCertificate {
175    #[rasn(tag(explicit(context, 0)))]
176    _version: Option<Integer>,
177    _serial_number: Integer,
178    signature: AlgorithmIdentifier,
179    _issuer: rasn::types::Any,
180    _validity: rasn::types::Any,
181    _subject: rasn::types::Any,
182    subject_public_key_info: SubjectPublicKeyInfo,
183    #[rasn(tag(context, 1))]
184    _issuer_unique_id: Option<BitString>,
185    #[rasn(tag(context, 2))]
186    _subject_unique_id: Option<BitString>,
187    #[rasn(tag(explicit(context, 3)))]
188    _extensions: Option<SequenceOf<Extension>>,
189}
190
191#[derive(AsnType, Decode, Debug)]
192struct Certificate {
193    tbs_cert: TbsCertificate,
194    _signature_algorithm: AlgorithmIdentifier,
195    _signature_value: BitString,
196}
197
198impl Memory {
199    fn fetch_rows<F>(
200        device: &mut Device,
201        rows: &mut Vec<MemoryRow>,
202        class: TpmHt,
203        display_type: MemoryHandleType,
204        mut get_details: F,
205    ) -> Result<(), CommandError>
206    where
207        F: FnMut(&mut Device, Handle) -> Result<String, CommandError>,
208    {
209        for handle in device.fetch_handles((class as u32) << 24)? {
210            match get_details(device, handle) {
211                Ok(details) => {
212                    rows.push(MemoryRow {
213                        handle: format!("{:08x}", handle.value()),
214                        class: display_type.to_string(),
215                        details,
216                    });
217                }
218                Err(e) => log::debug!("{:08x}: {e}", handle.value()),
219            }
220        }
221        Ok(())
222    }
223
224    fn fetch_details(device: &mut Device, handle: Handle) -> Result<String, CommandError> {
225        let tpm_handle = TpmHandle(handle.value());
226        let (public, _) = device.read_public(tpm_handle)?;
227        Ok(format_alg_from_public(&public))
228    }
229
230    fn fetch_hash_alg(oid: &ObjectIdentifier) -> Result<TpmAlgId, KeyError> {
231        if oid == &OID_SHA1_WITH_RSA_ENCRYPTION {
232            Ok(TpmAlgId::Sha1)
233        } else if oid == &OID_SHA256_WITH_RSA_ENCRYPTION || oid == &OID_ECDSA_WITH_SHA256 {
234            Ok(TpmAlgId::Sha256)
235        } else if oid == &OID_SHA384_WITH_RSA_ENCRYPTION || oid == &OID_ECDSA_WITH_SHA384 {
236            Ok(TpmAlgId::Sha384)
237        } else if oid == &OID_SHA512_WITH_RSA_ENCRYPTION || oid == &OID_ECDSA_WITH_SHA512 {
238            Ok(TpmAlgId::Sha512)
239        } else {
240            Err(KeyError::UnsupportedOid(oid.to_string()))
241        }
242    }
243
244    fn fetch_alg_name(cert_der: &[u8]) -> Result<String, KeyError> {
245        let cert: Certificate = rasn::der::decode(cert_der)?;
246        let tbs = cert.tbs_cert;
247        let spki = tbs.subject_public_key_info;
248        let sig_alg = Memory::fetch_hash_alg(&tbs.signature.algorithm)?;
249        let sig_alg_str = Tpm2shAlgId(sig_alg).to_string();
250
251        let key_oid = &spki.algorithm.algorithm;
252        if key_oid == &OID_RSA_ENCRYPTION {
253            let key: RsaPublicKey = rasn::der::decode(spki.subject_public_key.as_raw_slice())?;
254            let modulus = key
255                .modulus
256                .to_bigint()
257                .ok_or_else(|| KeyError::InvalidRsaModulus(key.modulus.to_string()))?;
258            let key_bits = u16::try_from(modulus.bits())
259                .map_err(|_| KeyError::InvalidRsaModulus(modulus.to_string()))?;
260            Ok(format!("rsa-{key_bits}:{sig_alg_str}"))
261        } else if key_oid == &crate::key::OID_EC_PUBLIC_KEY {
262            let curve_param_oid = spki
263                .algorithm
264                .parameters
265                .as_ref()
266                .and_then(|any| rasn::der::decode::<ObjectIdentifier>(any.as_ref()).ok());
267            let curve_str = if curve_param_oid.as_ref() == Some(&SECP_256_R_1) {
268                "nist-p256"
269            } else if curve_param_oid.as_ref() == Some(&SECP_384_R_1) {
270                "nist-p384"
271            } else if curve_param_oid.as_ref() == Some(&SECP_521_R_1) {
272                "nist-p521"
273            } else if let Some(oid) = curve_param_oid.as_ref() {
274                return Err(KeyError::UnsupportedOid(oid.to_string()));
275            } else {
276                return Err(KeyError::InvalidOid);
277            };
278            Ok(format!("ecc-{curve_str}:{sig_alg_str}"))
279        } else {
280            Err(KeyError::UnsupportedOid(key_oid.to_string()))
281        }
282    }
283}