cli/command/
delete.rs

1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2024-2025 Jarkko Sakkinen
3// Copyright (c) 2025 Opinsys Oy
4
5use crate::{
6    cli::SubCommand,
7    command::CommandError,
8    device::{with_device, Device},
9    handle::HandlePattern,
10    job::Job,
11    vtpm::VtpmKey,
12};
13use clap::Args;
14use std::collections::VecDeque;
15use tpm2_protocol::{
16    data::{TpmHt, TpmRh, TpmtPublic},
17    TpmHandle,
18};
19
20/// Deletes active and cached objects.
21#[derive(Args, Debug)]
22pub struct Delete {
23    /// Input: 'tpm:<handle pattern>', or 'vtpm:<handle pattern>'
24    pub input: String,
25}
26
27impl Delete {
28    /// Iteratively finds and removes child keys from the cache using BFS.
29    fn delete_vtpm_children(
30        job: &mut Job,
31        dev: &mut Device,
32        first_public: &TpmtPublic,
33        deleted: &mut Vec<u32>,
34    ) -> Result<(), CommandError> {
35        let mut ancestor_list = VecDeque::new();
36        ancestor_list.push_back(first_public.clone());
37
38        while let Some(parent_public) = ancestor_list.pop_front() {
39            let children_to_process: Vec<(u32, TpmtPublic)> = job
40                .cache
41                .key_iter()
42                .filter(|(_, key)| key.parent.inner == parent_public)
43                .map(|(vhandle, key)| (*vhandle, key.public.inner.clone()))
44                .collect();
45
46            for (child_vhandle, child_public) in children_to_process {
47                if deleted.contains(&child_vhandle)
48                    || !job.cache.contexts.contains_key(&child_vhandle)
49                {
50                    continue;
51                }
52                job.cache.remove(dev, child_vhandle)?;
53                deleted.push(child_vhandle);
54                ancestor_list.push_back(child_public);
55            }
56        }
57        Ok(())
58    }
59}
60
61impl SubCommand for Delete {
62    fn run(&self, job: &mut Job) -> Result<(), CommandError> {
63        if let Some(pattern) = self.input.strip_prefix("tpm:") {
64            delete_tpm_handles(job, pattern)
65        } else if let Some(pattern) = self.input.strip_prefix("vtpm:") {
66            delete_vtpm_handles(job, pattern)
67        } else {
68            Err(CommandError::InvalidInput(self.input.to_string()))
69        }
70    }
71}
72
73/// Deletes TPM objects matching a pattern across sessions, transient, and persistent handles.
74fn delete_tpm_handles(job: &mut Job, pattern_str: &str) -> Result<(), CommandError> {
75    with_device(job.device.clone(), |dev| {
76        let pattern = HandlePattern::new(pattern_str)?;
77
78        for class in [
79            TpmHt::HmacSession,
80            TpmHt::PolicySession,
81            TpmHt::Transient,
82            TpmHt::Persistent,
83        ] {
84            let handles = dev.fetch_handles((class as u32) << 24)?;
85            for handle in handles.into_iter().filter(|&h| pattern.matches(h.value())) {
86                match class {
87                    TpmHt::HmacSession | TpmHt::PolicySession | TpmHt::Transient => {
88                        dev.flush_context(TpmHandle(handle.value()))?;
89                        if class == TpmHt::Transient {
90                            job.cache.untrack(handle.value());
91                        }
92                    }
93                    TpmHt::Persistent => {
94                        let persistent_handle = TpmHandle(handle.value());
95                        let auth_handle: TpmHandle =
96                            if (handle.value() & 0x00FF_FFFF) <= 0x007F_FFFF {
97                                (TpmRh::Owner as u32).into()
98                            } else {
99                                (TpmRh::Platform as u32).into()
100                            };
101                        let auths = vec![job.auth_list.first().cloned().unwrap_or_default()];
102                        job.evict_control(
103                            auth_handle,
104                            persistent_handle,
105                            persistent_handle,
106                            &auths,
107                        )?;
108                    }
109                    _ => {}
110                }
111                writeln!(job.writer, "{handle}")?;
112            }
113        }
114        Ok(())
115    })
116}
117
118/// Deletes vTPM objects (keys and sessions) matching the pattern.
119fn delete_vtpm_handles(job: &mut Job, pattern_str: &str) -> Result<(), CommandError> {
120    let pattern = HandlePattern::new(pattern_str)?;
121
122    let matched_handles: Vec<u32> = job
123        .cache
124        .contexts
125        .keys()
126        .copied()
127        .filter(|&h| pattern.matches(h))
128        .collect();
129
130    if matched_handles.is_empty() {
131        return Ok(());
132    }
133
134    with_device(job.device.clone(), |dev| {
135        let mut deleted_vhandles = Vec::new();
136
137        for vhandle in matched_handles {
138            if deleted_vhandles.contains(&vhandle) || !job.cache.contexts.contains_key(&vhandle) {
139                continue;
140            }
141
142            let maybe_public = if let Some(context) = job.cache.contexts.get(&vhandle) {
143                context
144                    .as_any()
145                    .downcast_ref::<VtpmKey>()
146                    .map(|key| key.public.inner.clone())
147            } else {
148                continue;
149            };
150
151            job.cache.remove(dev, vhandle)?;
152            writeln!(job.writer, "vtpm:{vhandle:08x}")?;
153            deleted_vhandles.push(vhandle);
154
155            if let Some(public_key) = maybe_public {
156                Delete::delete_vtpm_children(job, dev, &public_key, &mut deleted_vhandles)?;
157
158                let deleted_children: Vec<u32> = deleted_vhandles
159                    .iter()
160                    .filter(|&&h| {
161                        let vhandle_pos = deleted_vhandles.iter().position(|&x| x == vhandle);
162                        let h_pos = deleted_vhandles.iter().position(|&x| x == h);
163                        if let (Some(vp), Some(hp)) = (vhandle_pos, h_pos) {
164                            hp > vp
165                        } else {
166                            false
167                        }
168                    })
169                    .copied()
170                    .collect();
171
172                for child_vhandle in deleted_children {
173                    writeln!(job.writer, "vtpm:{child_vhandle:08x}")?;
174                }
175            }
176        }
177        Ok(())
178    })
179}