cli/command/
pcr_event.rs

1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2025 Opinsys Oy
3
4use super::CommandError;
5use crate::{
6    cli::{get_auth, SubCommand},
7    context::ContextCache,
8    convert::{from_input_to_bytes, from_str_to_handle},
9    device::{self, Auth, Device, DeviceError},
10    key::Tpm2shAlgId,
11    pcr::pcr_get_bank_list,
12    uri::Uri,
13};
14use argh::FromArgs;
15use std::{cell::RefCell, rc::Rc};
16use tpm2_protocol::{
17    data::{Tpm2bEvent, TpmCc, TpmSe, TpmuHa},
18    message::TpmPcrEventCommand,
19    TpmHandle,
20};
21
22/// Extends a PCR with an event.
23#[derive(FromArgs, Debug)]
24#[argh(
25    subcommand,
26    name = "pcr-event",
27    note = "Extends a Platform Configuration Register (PCR) with data.
28
29This command computes the digest of the provided data and uses it to
30extend the state of the specified PCR. This is a one-way operation.
31
32The output is a single, reusable policy expression that can be used
33in other commands.
34
35Example:
36  # Extend PCR 16 with the SHA256 digest of the string \"my-event\"
37  echo -n \"my-event\" | tpm2sh pcr-event 16"
38)]
39pub struct PcrEvent {
40    /// PCR index
41    #[argh(positional, arg_name = "pcr-index", from_str_fn(from_str_to_handle))]
42    pub pcr_index: TpmHandle,
43
44    /// data file to be hashed (reads from stdin if not provided)
45    #[argh(positional)]
46    pub input: Option<Uri>,
47
48    /// auth for the PCR: 'password://<hex>' or 'session://<handle>'
49    /// Uses TPM2SH_AUTH environment variable if not set.
50    #[argh(option, arg_name = "auth", short = 'a')]
51    pub auth: Option<String>,
52
53    /// hmac auth: 'password://<hex>' or 'session://<handle>'
54    /// Uses TPM2SH_HMAC_AUTH environment variable if not set.
55    #[argh(option, arg_name = "auth", short = 'm', long = "hmac-auth")]
56    pub hmac_auth: Option<String>,
57}
58
59impl SubCommand for PcrEvent {
60    fn run(
61        &self,
62        device: Option<Rc<RefCell<Device>>>,
63        context: &mut ContextCache,
64        _plain: bool,
65    ) -> Result<(), CommandError> {
66        let auth = match (self.auth.as_ref(), self.hmac_auth.as_ref()) {
67            (Some(_), Some(_)) => {
68                return Err(CommandError::InvalidInput(
69                    "Cannot use --auth and --hmac-auth at the same time".to_string(),
70                ));
71            }
72            (Some(auth_str), None) => get_auth(
73                Some(auth_str),
74                "TPM2SH_AUTH",
75                &context.session_map,
76                &[TpmSe::Policy],
77            )?,
78            (None, Some(hmac_auth_str)) => get_auth(
79                Some(hmac_auth_str),
80                "TPM2SH_HMAC_AUTH",
81                &context.session_map,
82                &[TpmSe::Hmac],
83            )?,
84            (None, None) => {
85                let auth = get_auth(None, "TPM2SH_AUTH", &context.session_map, &[TpmSe::Policy])?;
86                if matches!(&auth, Auth::Password(p) if p.is_empty()) {
87                    get_auth(
88                        None,
89                        "TPM2SH_HMAC_AUTH",
90                        &context.session_map,
91                        &[TpmSe::Hmac],
92                    )?
93                } else {
94                    auth
95                }
96            }
97        };
98        device::with_device(device, |device| {
99            let banks = pcr_get_bank_list(device)?;
100            let handles = [self.pcr_index.0];
101            let auths = &[auth];
102
103            let data_bytes = from_input_to_bytes(self.input.as_ref())?;
104
105            let event_data = Tpm2bEvent::try_from(data_bytes.as_slice())?;
106            let command = TpmPcrEventCommand {
107                pcr_handle: handles[0].into(),
108                event_data,
109            };
110
111            let (resp, _) = context.execute(device, &command, &handles, auths)?;
112
113            let pcr_resp = resp
114                .PcrEvent()
115                .map_err(|_| DeviceError::ResponseMismatch(TpmCc::PcrEvent))?;
116
117            let clauses: Vec<String> = banks
118                .iter()
119                .zip(pcr_resp.digests.iter())
120                .filter_map(|(bank, digest_struct)| {
121                    if let TpmuHa::Digest(bytes) = digest_struct.digest {
122                        Some(format!(
123                            "{}:{}:{}",
124                            Tpm2shAlgId(bank.alg),
125                            self.pcr_index.0,
126                            hex::encode(bytes)
127                        ))
128                    } else {
129                        None
130                    }
131                })
132                .collect();
133
134            writeln!(context.writer, "{}", clauses.join("+"))?;
135
136            Ok(())
137        })
138    }
139}