1#![deny(clippy::all)]
18#![deny(clippy::pedantic)]
19
20pub mod error;
21pub mod expression;
22
23pub use error::*;
24pub use expression::*;
25
26use std::{collections::HashMap, fmt, iter::Peekable, slice::Iter};
27
28use tpm2_crypto::TpmHash;
29use tpm2_protocol::{
30 basic::TpmHandle,
31 constant::TPM_PCR_SELECT_MAX,
32 data::{
33 Tpm2bDigest, Tpm2bName, Tpm2bNonce, TpmAlgId, TpmCc, TpmHt, TpmlPcrSelection,
34 TpmsPcrSelect, TpmsPcrSelection,
35 },
36 frame::{TpmPolicyOrCommand, TpmPolicyPcrCommand},
37 TpmMarshal, TpmSized, TpmWriter,
38};
39
40#[derive(Debug, Clone, Default)]
45pub struct TpmPolicyState {
46 names: HashMap<TpmHandle, Tpm2bName>,
47 pcrs: HashMap<TpmAlgId, HashMap<u32, Tpm2bDigest>>,
48 pcr_count: usize,
49}
50
51impl TpmPolicyState {
52 pub fn new(
59 names: HashMap<TpmHandle, Tpm2bName>,
60 pcrs: HashMap<TpmAlgId, HashMap<u32, Tpm2bDigest>>,
61 ) -> Result<Self, TpmPolicyError> {
62 let mut pcr_count = 0;
63
64 for bank in pcrs.values() {
65 if pcr_count == 0 {
66 pcr_count = bank.len();
67 }
68
69 if pcr_count != bank.len() {
70 return Err(TpmPolicyError::PcrCountMismatch);
71 }
72 }
73
74 Ok(Self {
75 names,
76 pcrs,
77 pcr_count,
78 })
79 }
80
81 #[must_use]
82 pub fn names(&self) -> &HashMap<TpmHandle, Tpm2bName> {
83 &self.names
84 }
85}
86
87fn parse_tpml_pcr_selection_str(
90 selection_str: &str,
91 context: &TpmPolicyState,
92) -> Result<TpmlPcrSelection, TpmPolicyError> {
93 let mut list = TpmlPcrSelection::new();
94 let pcr_select_size = context.pcr_count.div_ceil(8);
95 if pcr_select_size > TPM_PCR_SELECT_MAX as usize {
96 return Err(TpmPolicyError::PcrSelectionTooLarge);
97 }
98
99 for part in selection_str.split('+') {
100 let (alg_str, indices_str) = part
101 .split_once(':')
102 .ok_or(TpmPolicyError::InvalidPcrSelection)?;
103
104 let alg = alg_str
105 .parse::<TpmHash>()
106 .map_err(|_| TpmPolicyError::InvalidPcrDigestAlgorithm)?;
107
108 if !context.pcrs.contains_key(&alg.into()) {
109 return Err(TpmPolicyError::PcrBankNotAvailable(alg));
110 }
111
112 let indices: Vec<u32> = indices_str
113 .split(',')
114 .map(str::parse)
115 .collect::<Result<_, _>>()
116 .map_err(|_| TpmPolicyError::InvalidPcrSelection)?;
117
118 let mut pcr_select_bytes = vec![0u8; pcr_select_size];
119 for &pcr_index in &indices {
120 let pcr_index = pcr_index as usize;
121 if pcr_index >= context.pcr_count {
122 return Err(TpmPolicyError::PcrIndexTooLarge);
123 }
124 pcr_select_bytes[pcr_index / 8] |= 1 << (pcr_index % 8);
125 }
126
127 list.try_push(TpmsPcrSelection {
128 hash: alg.into(),
129 pcr_select: TpmsPcrSelect::try_from(pcr_select_bytes.as_slice())
130 .map_err(|_| TpmPolicyError::PcrDigestTooLarge)?,
131 })
132 .map_err(|_| TpmPolicyError::PcrSelectionTooLarge)?;
133 }
134 Ok(list)
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138enum Token<'a> {
139 And,
140 Or,
141 LParen,
142 RParen,
143 Comma,
144 Ident(&'a str),
145}
146
147impl fmt::Display for Token<'_> {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self {
150 Token::And => write!(f, "'and'"),
151 Token::Or => write!(f, "'or'"),
152 Token::LParen => write!(f, "'('"),
153 Token::RParen => write!(f, "')'"),
154 Token::Comma => write!(f, "','"),
155 Token::Ident(s) => write!(f, "'{s}'"),
156 }
157 }
158}
159
160fn tokenize(input: &str) -> Vec<Token<'_>> {
161 let mut tokens = Vec::new();
162 let mut chars = input.char_indices().peekable();
163
164 while let Some((i, c)) = chars.next() {
165 match c {
166 '(' => tokens.push(Token::LParen),
167 ')' => tokens.push(Token::RParen),
168 ',' => tokens.push(Token::Comma),
169 c if c.is_whitespace() => {}
170 _ => {
171 let start = i;
172 let mut end = i + c.len_utf8();
173
174 while let Some(&(_, next_char)) = chars.peek() {
175 if next_char.is_whitespace() || "(),".contains(next_char) {
176 break;
177 }
178 let (next_i, _) = chars.next().unwrap();
179 end = next_i + next_char.len_utf8();
180 }
181
182 let ident_slice = &input[start..end];
183 match ident_slice {
184 "and" => tokens.push(Token::And),
185 "or" => tokens.push(Token::Or),
186 _ => tokens.push(Token::Ident(ident_slice)),
187 }
188 }
189 }
190 }
191 tokens
192}
193
194fn parse_expression<'a>(
195 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
196 context: &TpmPolicyState,
197) -> Result<TpmPolicyExpression, TpmPolicyError> {
198 parse_or(tokens, context)
199}
200
201fn parse_binary_expression<'a, F, G>(
202 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
203 mut operand_parser: F,
204 operator: &Token,
205 mut expression_combiner: G,
206 context: &TpmPolicyState,
207) -> Result<TpmPolicyExpression, TpmPolicyError>
208where
209 F: FnMut(
210 &mut Peekable<Iter<'a, Token<'a>>>,
211 &TpmPolicyState,
212 ) -> Result<TpmPolicyExpression, TpmPolicyError>,
213 G: FnMut(TpmPolicyExpression, TpmPolicyExpression) -> TpmPolicyExpression,
214{
215 let mut node = operand_parser(tokens, context)?;
216 while tokens.peek() == Some(&operator) {
217 tokens.next();
218 let rhs = operand_parser(tokens, context)?;
219 node = expression_combiner(node, rhs);
220 }
221 Ok(node)
222}
223
224fn parse_or<'a>(
225 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
226 context: &TpmPolicyState,
227) -> Result<TpmPolicyExpression, TpmPolicyError> {
228 parse_binary_expression(
229 tokens,
230 parse_and,
231 &Token::Or,
232 |lhs, rhs| match lhs {
233 TpmPolicyExpression::Or(mut terms) => {
234 terms.push(rhs);
235 TpmPolicyExpression::Or(terms)
236 }
237 _ => TpmPolicyExpression::Or(vec![lhs, rhs]),
238 },
239 context,
240 )
241}
242
243fn parse_and<'a>(
244 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
245 context: &TpmPolicyState,
246) -> Result<TpmPolicyExpression, TpmPolicyError> {
247 parse_binary_expression(
248 tokens,
249 parse_primary,
250 &Token::And,
251 |lhs, rhs| match lhs {
252 TpmPolicyExpression::And(mut factors) => {
253 factors.push(rhs);
254 TpmPolicyExpression::And(factors)
255 }
256 _ => TpmPolicyExpression::And(vec![lhs, rhs]),
257 },
258 context,
259 )
260}
261
262fn parse_primary<'a>(
263 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
264 context: &TpmPolicyState,
265) -> Result<TpmPolicyExpression, TpmPolicyError> {
266 let token = tokens.next().ok_or(TpmPolicyError::UnexpectedEnd)?;
267
268 match token {
269 Token::LParen => {
270 let expr = parse_or(tokens, context)?;
271 if tokens.next() != Some(&Token::RParen) {
272 return Err(TpmPolicyError::ParenthesisMismatch);
273 }
274 Ok(expr)
275 }
276 Token::Ident(name) => match *name {
277 "pcr" => Ok(parse_pcr_call(tokens, context)?),
278 "secret" => Ok(parse_secret_call(tokens, context)?),
279 _ => parse_literal(name),
280 },
281 _ => Err(TpmPolicyError::InvalidToken(token.to_string())),
282 }
283}
284
285fn parse_literal(s: &str) -> Result<TpmPolicyExpression, TpmPolicyError> {
286 if s.len() != 8 {
287 return Err(TpmPolicyError::InvalidToken(s.to_string()));
288 }
289
290 let raw =
291 u32::from_str_radix(s, 16).map_err(|_| TpmPolicyError::InvalidToken(s.to_string()))?;
292
293 let ht_byte = (raw >> 24) as u8;
294 TpmHt::try_from(ht_byte).map_err(|_| TpmPolicyError::InvalidHandleType(ht_byte))?;
295
296 Ok(TpmPolicyExpression::Handle(TpmHandle::from(raw)))
297}
298
299fn parse_pcr_call<'a>(
300 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
301 context: &TpmPolicyState,
302) -> Result<TpmPolicyExpression, TpmPolicyError> {
303 match tokens.next() {
304 Some(Token::LParen) => {}
305 Some(actual_token) => {
306 return Err(TpmPolicyError::InvalidToken(actual_token.to_string()));
307 }
308 None => {
309 return Err(TpmPolicyError::UnexpectedEnd);
310 }
311 }
312
313 let mut buf = String::new();
314 loop {
315 match tokens.next() {
316 Some(Token::RParen) => break,
317 Some(Token::Ident(s)) => buf.push_str(s),
318 Some(Token::Comma) => buf.push(','),
319 Some(tok @ (Token::And | Token::Or | Token::LParen)) => {
320 return Err(TpmPolicyError::InvalidToken(tok.to_string()));
321 }
322 None => return Err(TpmPolicyError::UnexpectedEnd),
323 }
324 }
325
326 if let Some((selection_part, digest_part)) = buf.rsplit_once(':') {
327 if let Ok(selections) = parse_tpml_pcr_selection_str(selection_part, context) {
328 if let Ok(digest_bytes) = hex::decode(digest_part) {
329 if let Ok(digest) = Tpm2bDigest::try_from(digest_bytes.as_slice()) {
330 return Ok(TpmPolicyExpression::Pcr {
331 selections,
332 digest: Some(digest),
333 });
334 }
335 }
336 }
337 }
338
339 let selections = parse_tpml_pcr_selection_str(&buf, context)?;
340 Ok(TpmPolicyExpression::Pcr {
341 selections,
342 digest: None,
343 })
344}
345
346fn parse_secret_call<'a>(
347 tokens: &mut Peekable<Iter<'a, Token<'a>>>,
348 context: &TpmPolicyState,
349) -> Result<TpmPolicyExpression, TpmPolicyError> {
350 match tokens.next() {
351 Some(Token::LParen) => {}
352 Some(actual_token) => {
353 return Err(TpmPolicyError::InvalidToken(actual_token.to_string()));
354 }
355 None => {
356 return Err(TpmPolicyError::UnexpectedEnd);
357 }
358 }
359
360 let auth_handle =
361 parse_or(tokens, context).map_err(|e| TpmPolicyError::InvalidToken(e.to_string()))?;
362
363 let copy_ref = match tokens.peek() {
364 Some(Token::Comma) => {
365 tokens.next();
366
367 let copy_ref_ident = match tokens.next() {
368 Some(Token::Ident(s)) => s,
369 Some(actual_token) => {
370 return Err(TpmPolicyError::InvalidToken(actual_token.to_string()));
371 }
372 None => {
373 return Err(TpmPolicyError::UnexpectedEnd);
374 }
375 };
376
377 let (key, value) = copy_ref_ident
378 .split_once(':')
379 .ok_or_else(|| TpmPolicyError::InvalidToken((*copy_ref_ident).to_string()))?;
380
381 if key != "copy_ref" {
382 return Err(TpmPolicyError::InvalidToken((*copy_ref_ident).to_string()));
383 }
384
385 let bytes = hex::decode(value)
386 .map_err(|_| TpmPolicyError::InvalidToken((*copy_ref_ident).to_string()))?;
387
388 if bytes.is_empty() {
389 None
390 } else {
391 let digest = Tpm2bDigest::try_from(bytes.as_slice())
392 .map_err(|_| TpmPolicyError::InvalidToken((*copy_ref_ident).to_string()))?;
393 Some(digest)
394 }
395 }
396 Some(Token::RParen) => None,
397 Some(actual_token) => {
398 return Err(TpmPolicyError::InvalidToken(actual_token.to_string()));
399 }
400 None => {
401 return Err(TpmPolicyError::UnexpectedEnd);
402 }
403 };
404
405 match tokens.next() {
406 Some(Token::RParen) => {}
407 Some(actual_token) => {
408 return Err(TpmPolicyError::InvalidToken(actual_token.to_string()));
409 }
410 None => {
411 return Err(TpmPolicyError::UnexpectedEnd);
412 }
413 }
414
415 Ok(TpmPolicyExpression::Secret {
416 auth_handle: Box::new(auth_handle),
417 copy_ref,
418 })
419}
420
421struct TpmPolicySession {
423 digest: Tpm2bDigest,
424 hash_alg: TpmAlgId,
425 digest_size: usize,
426}
427
428impl TpmPolicySession {
429 fn new(hash_alg: TpmAlgId) -> Result<Self, TpmPolicyError> {
431 let digest_size = TpmHash::from(hash_alg).size();
432 let digest = Tpm2bDigest::try_from(vec![0; digest_size].as_slice())
433 .map_err(TpmPolicyError::Marshal)?;
434 Ok(Self {
435 digest,
436 hash_alg,
437 digest_size,
438 })
439 }
440
441 fn policy_pcr(&mut self, cmd: &TpmPolicyPcrCommand) -> Result<(), TpmPolicyError> {
443 let mut pcrs_bytes = vec![0u8; TpmlPcrSelection::SIZE];
444 let pcrs_bytes_len = {
445 let mut writer = TpmWriter::new(&mut pcrs_bytes);
446 cmd.pcrs
447 .marshal(&mut writer)
448 .map_err(TpmPolicyError::Marshal)?;
449 writer.len()
450 };
451 pcrs_bytes.truncate(pcrs_bytes_len);
452
453 let cc_bytes = (TpmCc::PolicyPcr as u32).to_be_bytes();
454 let chunks: Vec<&[u8]> = vec![
455 self.digest.as_ref(),
456 &cc_bytes,
457 &pcrs_bytes,
458 cmd.pcr_digest.as_ref(),
459 ];
460
461 let new_digest_bytes = TpmHash::from(self.hash_alg)
462 .digest(&chunks)
463 .map_err(TpmPolicyError::Crypto)?;
464 self.digest =
465 Tpm2bDigest::try_from(new_digest_bytes.as_slice()).map_err(TpmPolicyError::Marshal)?;
466 Ok(())
467 }
468
469 fn policy_or(&mut self, cmd: &TpmPolicyOrCommand) -> Result<(), TpmPolicyError> {
471 let mut digests_as_bytes = Vec::with_capacity(cmd.p_hash_list.len() * self.digest_size);
472 for digest in cmd.p_hash_list.iter() {
473 digests_as_bytes.extend_from_slice(digest.as_ref());
474 }
475
476 let zero_digest = Tpm2bDigest::try_from(vec![0; self.digest_size].as_slice())
477 .map_err(TpmPolicyError::Marshal)?;
478 self.digest = zero_digest;
479
480 let cc_bytes = (TpmCc::PolicyOr as u32).to_be_bytes();
481 let chunks: Vec<&[u8]> = vec![self.digest.as_ref(), &cc_bytes, digests_as_bytes.as_slice()];
482
483 let new_digest_bytes = TpmHash::from(self.hash_alg)
484 .digest(&chunks)
485 .map_err(TpmPolicyError::Crypto)?;
486 self.digest =
487 Tpm2bDigest::try_from(new_digest_bytes.as_slice()).map_err(TpmPolicyError::Marshal)?;
488 Ok(())
489 }
490
491 fn policy_secret(
493 &mut self,
494 auth_handle_name: &Tpm2bName,
495 policy_ref: &Tpm2bNonce,
496 ) -> Result<(), TpmPolicyError> {
497 let cc_bytes = (TpmCc::PolicySecret as u32).to_be_bytes();
498
499 let first_chunks: Vec<&[u8]> =
500 vec![self.digest.as_ref(), &cc_bytes, auth_handle_name.as_ref()];
501
502 let first_digest_bytes = TpmHash::from(self.hash_alg)
503 .digest(&first_chunks)
504 .map_err(TpmPolicyError::Crypto)?;
505 let first_digest = Tpm2bDigest::try_from(first_digest_bytes.as_slice())
506 .map_err(TpmPolicyError::Marshal)?;
507
508 let second_chunks: Vec<&[u8]> = vec![first_digest.as_ref(), policy_ref.as_ref()];
509
510 let new_digest_bytes = TpmHash::from(self.hash_alg)
511 .digest(&second_chunks)
512 .map_err(TpmPolicyError::Crypto)?;
513 self.digest =
514 Tpm2bDigest::try_from(new_digest_bytes.as_slice()).map_err(TpmPolicyError::Marshal)?;
515 Ok(())
516 }
517
518 fn policy_restart(&mut self) -> Result<(), TpmPolicyError> {
520 self.digest = Tpm2bDigest::try_from(vec![0; self.digest_size].as_slice())
521 .map_err(TpmPolicyError::Marshal)?;
522 Ok(())
523 }
524
525 fn get_digest(&self) -> Tpm2bDigest {
527 self.digest
528 }
529}
530
531fn build_and_branch(mut branch: Vec<TpmPolicyExpression>) -> TpmPolicyExpression {
534 if branch.len() == 1 {
535 match branch.pop() {
536 Some(expr) => expr,
537 None => TpmPolicyExpression::And(Vec::new()),
538 }
539 } else {
540 TpmPolicyExpression::And(branch)
541 }
542}