1use crate::{
6 cli::SubCommand,
7 command::CommandError,
8 context::ContextCache,
9 device::{self, Auth, Device},
10 pcr::{
11 pcr_composite_digest, pcr_get_bank_list, pcr_read, pcr_selection_vec_from_str,
12 pcr_selection_vec_to_tpml, Pcr,
13 },
14 policy::{
15 execute_policy, parse, Expression, PolicyError, SoftwarePolicySession, TpmPolicySession,
16 },
17 uri::Uri,
18};
19use argh::FromArgs;
20use std::{cell::RefCell, collections::HashSet, rc::Rc, str::FromStr};
21use strum::{Display, EnumString};
22use tpm2_protocol::{data::TpmAlgId, TpmHandle};
23
24#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, EnumString)]
26#[strum(serialize_all = "kebab-case")]
27pub enum PolicyMode {
28 #[default]
29 Resolve,
30 Software,
31 Tpm,
32}
33
34#[derive(FromArgs, Debug, Default)]
36#[argh(
37 subcommand,
38 name = "policy",
39 note = "A policy expression for the digest is defined with an expression language
40e.g, 'sha256:0,...' or 'secret(\"tpm://...\")'."
41)]
42pub struct Policy {
43 #[argh(option, long = "mode", default = "Default::default()")]
45 pub mode: PolicyMode,
46
47 #[argh(option)]
49 pub auth: Option<String>,
50
51 #[argh(positional)]
53 pub expression: String,
54}
55
56fn try_visit_pcr_expressions_mut<F>(
58 ast: &mut Expression,
59 visitor: &mut F,
60) -> Result<(), CommandError>
61where
62 F: FnMut(&mut Expression) -> Result<(), CommandError>,
63{
64 match ast {
65 Expression::Pcr {
66 selection: _,
67 digest: _,
68 count: _,
69 } => visitor(ast)?,
70 Expression::Or(branches) => {
71 for branch in branches.iter_mut() {
72 try_visit_pcr_expressions_mut(branch, visitor)?;
73 }
74 }
75 Expression::Secret {
76 auth_handle_uri, ..
77 } => {
78 try_visit_pcr_expressions_mut(auth_handle_uri, visitor)?;
79 }
80 Expression::Uri(_) => {}
81 }
82 Ok(())
83}
84
85impl SubCommand for Policy {
86 fn run(
87 &self,
88 device: Option<Rc<RefCell<Device>>>,
89 context: &mut ContextCache,
90 _plain: bool,
91 ) -> Result<(), CommandError> {
92 device::with_device(device, |device| {
93 let mut ast = parse(&self.expression)?;
94 let session_hash_alg = TpmAlgId::Sha256;
95
96 let mut required_selections = HashSet::new();
97 try_visit_pcr_expressions_mut(&mut ast, &mut |expr| {
98 if let Expression::Pcr {
99 selection,
100 digest: None,
101 ..
102 } = expr
103 {
104 required_selections.insert(selection.clone());
105 }
106 Ok(())
107 })?;
108
109 if !required_selections.is_empty() {
110 let banks = pcr_get_bank_list(device)?;
111 let selections_str = required_selections
112 .into_iter()
113 .collect::<Vec<_>>()
114 .join("+");
115 let selections = pcr_selection_vec_from_str(&selections_str)?;
116 let tpml_selection = pcr_selection_vec_to_tpml(&selections, &banks)?;
117 let (pcr_values, _) = pcr_read(device, &tpml_selection)?;
118
119 let mut populator = |expr: &mut Expression| -> Result<(), CommandError> {
120 if let Expression::Pcr {
121 selection, digest, ..
122 } = expr
123 {
124 if digest.is_none() {
125 let selections_for_node = pcr_selection_vec_from_str(selection)?;
126 let pcr_subset: Vec<Pcr> = pcr_values
127 .iter()
128 .filter(|pcr| {
129 selections_for_node.iter().any(|sel| {
130 sel.alg == pcr.bank && sel.indices.contains(&pcr.index)
131 })
132 })
133 .cloned()
134 .collect();
135
136 let composite_digest =
137 pcr_composite_digest(&pcr_subset, session_hash_alg)?;
138 *digest = Some(hex::encode(composite_digest));
139 }
140 }
141 Ok(())
142 };
143 try_visit_pcr_expressions_mut(&mut ast, &mut populator)?;
144 }
145
146 if let Some(session_uri_str) = &self.auth {
147 let session_uri = Uri::from_str(session_uri_str)?;
148
149 let Uri::Session(session_handle) = session_uri else {
150 return Err(CommandError::InvalidInput(
151 "Session must be a session:// URI".to_string(),
152 ));
153 };
154
155 context
156 .session_map
157 .prepare_sessions(device, &[Auth::Tracked(session_handle)])?;
158
159 let live_handle = context
160 .session_map
161 .get(&Uri::Session(session_handle).to_string())?
162 .handle;
163
164 let mut session = TpmPolicySession::new(device, live_handle, session_hash_alg);
165 execute_policy(&ast, &mut session)?;
166
167 let new_context = device.save_context(live_handle.0)?;
168 let session_to_update = context.session_map.get_mut(session_uri_str)?;
169 session_to_update.context = new_context;
170 session_to_update.handle = tpm2_protocol::TpmHandle(0);
171 } else {
172 match self.mode {
173 PolicyMode::Resolve => {
174 writeln!(context.writer, "{ast}")?;
175 }
176 PolicyMode::Software => {
177 let mut session = SoftwarePolicySession::new(session_hash_alg, device)?;
178 let final_digest = execute_policy(&ast, &mut session)?;
179 writeln!(context.writer, "{}", hex::encode(&*final_digest))?;
180 }
181 PolicyMode::Tpm => {
182 let session_handle = start_trial_session(
183 device,
184 tpm2_protocol::data::TpmSe::Trial,
185 session_hash_alg,
186 )?;
187 let final_digest = {
188 let mut session =
189 TpmPolicySession::new(device, session_handle, session_hash_alg);
190 execute_policy(&ast, &mut session)?
191 };
192 device.flush_context(session_handle.0)?;
193 writeln!(context.writer, "{}", hex::encode(&*final_digest))?;
194 }
195 }
196 }
197 Ok(())
198 })
199 }
200}
201
202pub fn start_trial_session(
208 device: &mut Device,
209 session_type: tpm2_protocol::data::TpmSe,
210 hash_alg: TpmAlgId,
211) -> Result<TpmHandle, PolicyError> {
212 let (resp, _) = device.start_session(session_type, hash_alg)?;
213 Ok(resp.session_handle)
214}