1use crate::{
6 cli::SubCommand,
7 command::CommandError,
8 device::with_device,
9 job::Job,
10 pcr::{pcr_composite_digest, pcr_get_bank_list, pcr_read},
11 policy::{
12 execute_policy, parse, visit_pcr_expressions_mut, Expression, PolicyError,
13 SoftwarePolicySession, TpmPolicySession,
14 },
15 vtpm::VtpmSession,
16};
17use clap::Args;
18use std::collections::HashSet;
19use strum::{Display, EnumString};
20use tpm2_protocol::{
21 data::{TpmAlgId, TpmRh, TpmSe},
22 TpmHandle,
23};
24
25#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, EnumString)]
27#[strum(serialize_all = "kebab-case")]
28pub enum PolicyMode {
29 #[default]
30 Resolve,
31 Software,
32 Tpm,
33 Session,
34}
35
36#[derive(Args, Debug, Default)]
38#[command()]
39pub struct Policy {
40 #[arg(short = 'm', long = "mode", default_value_t = PolicyMode::default(), value_parser = clap::value_parser!(PolicyMode))]
42 pub mode: PolicyMode,
43
44 pub expression: String,
46}
47
48fn resolve_pcr_digests(
50 device: &mut crate::device::Device,
51 ast: &mut Expression,
52 session_hash_alg: TpmAlgId,
53) -> Result<(), CommandError> {
54 let mut required_selections = HashSet::new();
55 visit_pcr_expressions_mut(ast, &mut |expr| -> Result<(), PolicyError> {
56 if let Expression::Pcr {
57 selections,
58 digest: None,
59 ..
60 } = expr
61 {
62 for s in selections {
63 required_selections.insert(s.clone());
64 }
65 }
66 Ok(())
67 })?;
68
69 if !required_selections.is_empty() {
70 let banks = pcr_get_bank_list(device)?;
71 let selections: Vec<_> = required_selections.into_iter().collect();
72 let tpml_selection = crate::pcr::pcr_selection_vec_to_tpml(&selections, &banks)?;
73 let (pcr_values, _) = pcr_read(device, &tpml_selection)?;
74
75 let mut populator = |expr: &mut Expression| -> Result<(), PolicyError> {
76 if let Expression::Pcr {
77 selections, digest, ..
78 } = expr
79 {
80 if digest.is_none() {
81 let pcr_subset: Vec<crate::pcr::Pcr> = pcr_values
82 .iter()
83 .filter(|pcr| {
84 selections
85 .iter()
86 .any(|sel| sel.alg == pcr.bank && sel.indices.contains(&pcr.index))
87 })
88 .cloned()
89 .collect();
90
91 let composite_digest = pcr_composite_digest(&pcr_subset, session_hash_alg)?;
92 *digest = Some(hex::encode(composite_digest));
93 }
94 }
95 Ok(())
96 };
97 visit_pcr_expressions_mut(ast, &mut populator)?;
98 }
99 Ok(())
100}
101
102impl SubCommand for Policy {
103 fn run(&self, job: &mut Job) -> Result<(), CommandError> {
104 with_device(job.device.clone(), |device| {
105 let mut ast = parse(&self.expression)?;
106 match ast {
107 Expression::Auth(_) | Expression::Handle(_) => {
108 return Err(CommandError::InvalidInput(
109 "not a valid policy expression".to_string(),
110 ));
111 }
112 _ => {}
113 }
114 let session_hash_alg = TpmAlgId::Sha256;
115
116 resolve_pcr_digests(device, &mut ast, session_hash_alg)?;
117
118 match self.mode {
119 PolicyMode::Resolve => {
120 writeln!(job.writer, "{ast}")?;
121 }
122 PolicyMode::Software => {
123 let mut session = SoftwarePolicySession::new(session_hash_alg, device)?;
124 let final_digest = execute_policy(&ast, &mut session)?;
125 writeln!(job.writer, "{}", hex::encode(&*final_digest))?;
126 }
127 PolicyMode::Tpm => {
128 let session_handle =
129 start_trial_session(device, TpmSe::Trial, session_hash_alg)?;
130 let final_digest = {
131 let mut session =
132 TpmPolicySession::new(device, session_handle, session_hash_alg);
133 execute_policy(&ast, &mut session)?
134 };
135 let flush_result = device.flush_context(session_handle);
136 flush_result?;
137 writeln!(job.writer, "{}", hex::encode(&*final_digest))?;
138 }
139 PolicyMode::Session => {
140 let (resp, nonce_caller) = device.start_session(
141 TpmSe::Policy,
142 session_hash_alg,
143 (TpmRh::Null as u32).into(),
144 )?;
145 let mut tpm_policy_session =
146 TpmPolicySession::new(device, resp.session_handle, session_hash_alg);
147 match execute_policy(&ast, &mut tpm_policy_session) {
148 Ok(_) => {
149 let mut session =
150 VtpmSession::new(session_hash_alg, nonce_caller, &resp, &[])?;
151 session.context = device.save_context(resp.session_handle)?;
152 let vhandle = job.cache.add_session(session);
153 job.cache.save()?;
154 writeln!(job.writer, "vtpm:{vhandle:08x}")?;
155 }
156 Err(e) => {
157 let _ = device.flush_context(resp.session_handle);
158 return Err(e.into());
159 }
160 }
161 }
162 }
163 Ok(())
164 })
165 }
166}
167
168pub fn start_trial_session(
174 device: &mut crate::device::Device,
175 session_type: tpm2_protocol::data::TpmSe,
176 hash_alg: TpmAlgId,
177) -> Result<TpmHandle, PolicyError> {
178 let (resp, _) = device.start_session(session_type, hash_alg, (TpmRh::Null as u32).into())?;
179 Ok(resp.session_handle)
180}