1use crate::{
8 crypto::{crypto_digest, CryptoError},
9 device::{Device, DeviceError},
10 key::Tpm2shAlgId,
11};
12use std::{convert::TryFrom, fmt};
13use thiserror::Error;
14use tpm2_protocol::{
15 constant::TPM_PCR_SELECT_MAX,
16 data::{
17 TpmAlgId, TpmCap, TpmCc, TpmlPcrSelection, TpmsPcrSelect, TpmsPcrSelection,
18 TpmuCapabilities,
19 },
20 message::TpmPcrReadCommand,
21 TpmError,
22};
23
24#[derive(Debug, Error)]
25pub enum PcrError {
26 #[error("device: {0}")]
27 Device(#[from] DeviceError),
28 #[error("invalid algorithm: {0:?}")]
29 InvalidAlgorithm(TpmAlgId),
30 #[error("invalid PCR selection: {0}")]
31 InvalidPcrSelection(String),
32 #[error("TPM: {0}")]
33 Tpm(TpmError),
34 #[error("crypto: {0}")]
35 Crypto(#[from] CryptoError),
36}
37
38impl From<TpmError> for PcrError {
39 fn from(err: TpmError) -> Self {
40 Self::Tpm(err)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Pcr {
47 pub bank: TpmAlgId,
48 pub index: u32,
49 pub value: Vec<u8>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct PcrBank {
55 pub alg: TpmAlgId,
56 pub count: usize,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub struct PcrSelection {
62 pub alg: TpmAlgId,
63 pub indices: Vec<u32>,
64}
65
66impl fmt::Display for PcrSelection {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 let indices_str = self
69 .indices
70 .iter()
71 .map(ToString::to_string)
72 .collect::<Vec<_>>()
73 .join(",");
74 write!(f, "{}:{}", crate::key::Tpm2shAlgId(self.alg), indices_str)
75 }
76}
77
78pub fn pcr_get_bank_list(device: &mut Device) -> Result<Vec<PcrBank>, PcrError> {
85 let (_, cap_data) = device.get_capability_page(TpmCap::Pcrs, 0, 1)?;
86 let mut banks = Vec::new();
87 if let TpmuCapabilities::Pcrs(pcrs) = cap_data.data {
88 for bank in pcrs.iter() {
89 banks.push(PcrBank {
90 alg: bank.hash,
91 count: bank.pcr_select.len() * 8,
92 });
93 }
94 }
95 if banks.is_empty() {
96 return Err(PcrError::InvalidPcrSelection(
97 "TPM reported no active PCR banks.".to_string(),
98 ));
99 }
100 banks.sort_by_key(|b| b.alg);
101 Ok(banks)
102}
103
104pub fn pcr_selection_vec_from_str(selection_str: &str) -> Result<Vec<PcrSelection>, PcrError> {
112 selection_str
113 .split('+')
114 .map(|part| {
115 let (alg_str, indices_str) = part
116 .split_once(':')
117 .ok_or_else(|| PcrError::InvalidPcrSelection(part.to_string()))?;
118
119 let alg = Tpm2shAlgId::try_from(alg_str)
120 .map_err(|e| PcrError::InvalidPcrSelection(e.to_string()))?
121 .0;
122
123 let indices: Vec<u32> = indices_str
124 .split(',')
125 .map(|s| {
126 s.parse::<u32>()
127 .map_err(|_| PcrError::InvalidPcrSelection(indices_str.to_string()))
128 })
129 .collect::<Result<_, _>>()?;
130
131 Ok(PcrSelection { alg, indices })
132 })
133 .collect()
134}
135
136pub fn pcr_selection_vec_to_tpml(
144 selections: &[PcrSelection],
145 banks: &[PcrBank],
146) -> Result<TpmlPcrSelection, PcrError> {
147 let mut list = TpmlPcrSelection::new();
148 for selection in selections {
149 let bank = banks
150 .iter()
151 .find(|b| b.alg == selection.alg)
152 .ok_or_else(|| {
153 PcrError::InvalidPcrSelection(format!(
154 "PCR bank for algorithm {:?} not found or supported by TPM",
155 selection.alg
156 ))
157 })?;
158 let pcr_select_size = bank.count.div_ceil(8);
159 if pcr_select_size > TPM_PCR_SELECT_MAX {
160 return Err(PcrError::InvalidPcrSelection(format!(
161 "invalid select size {pcr_select_size} (> {TPM_PCR_SELECT_MAX})"
162 )));
163 }
164 let mut pcr_select_bytes = vec![0u8; pcr_select_size];
165 for &pcr_index in &selection.indices {
166 let pcr_index = pcr_index as usize;
167 if pcr_index >= bank.count {
168 return Err(PcrError::InvalidPcrSelection(format!(
169 "invalid index {pcr_index} for {:?} bank (max is {})",
170 bank.alg,
171 bank.count - 1
172 )));
173 }
174 pcr_select_bytes[pcr_index / 8] |= 1 << (pcr_index % 8);
175 }
176 list.try_push(TpmsPcrSelection {
177 hash: selection.alg,
178 pcr_select: TpmsPcrSelect::try_from(pcr_select_bytes.as_slice())?,
179 })?;
180 }
181 Ok(list)
182}
183
184pub fn pcr_read(
191 device: &mut Device,
192 pcr_selection_in: &TpmlPcrSelection,
193) -> Result<(Vec<Pcr>, u32), PcrError> {
194 let cmd = TpmPcrReadCommand {
195 pcr_selection_in: *pcr_selection_in,
196 };
197 let (resp, _) = device.execute(&cmd, &[])?;
198 let pcr_read_resp = resp
199 .PcrRead()
200 .map_err(|_| DeviceError::ResponseMismatch(TpmCc::PcrRead))?;
201 let mut pcrs = Vec::new();
202 let mut digest_iter = pcr_read_resp.pcr_values.iter();
203 for selection in pcr_read_resp.pcr_selection_out.iter() {
204 for (byte_idx, &byte) in selection.pcr_select.iter().enumerate() {
205 if byte == 0 {
206 continue;
207 }
208 for bit_idx in 0..8 {
209 if (byte >> bit_idx) & 1 == 1 {
210 let pcr_index = u32::try_from(byte_idx * 8 + bit_idx)
211 .map_err(|_| PcrError::InvalidPcrSelection("PCR index overflow".into()))?;
212 let value = digest_iter.next().ok_or_else(|| {
213 PcrError::InvalidPcrSelection("PCR selection mismatch".to_string())
214 })?;
215 pcrs.push(Pcr {
216 bank: selection.hash,
217 index: pcr_index,
218 value: value.to_vec(),
219 });
220 }
221 }
222 }
223 }
224 Ok((pcrs, pcr_read_resp.pcr_update_counter))
225}
226
227pub fn pcr_composite_digest(pcrs: &[Pcr], alg: TpmAlgId) -> Result<Vec<u8>, PcrError> {
234 let digests: Vec<&[u8]> = pcrs.iter().map(|p| p.value.as_slice()).collect();
235 Ok(crypto_digest(alg, &digests)?)
236}