1use core::time::Duration;
5use serde::{Deserialize, Serialize};
6use std::thread;
7use thiserror::Error;
8use tss_esapi::abstraction::{nv, pcr, public::DecodedKey};
9use tss_esapi::attributes::NvIndexAttributesBuilder;
10use tss_esapi::handles::{NvIndexHandle, NvIndexTpmHandle, PcrHandle, TpmHandle};
11use tss_esapi::interface_types::algorithm::HashingAlgorithm;
12use tss_esapi::interface_types::resource_handles::{NvAuth, Provision};
13use tss_esapi::interface_types::session_handles::AuthSession;
14use tss_esapi::structures::pcr_selection_list::PcrSelectionListBuilder;
15use tss_esapi::structures::pcr_slot::PcrSlot;
16use tss_esapi::structures::{
17 Attest, AttestInfo, Data, DigestValues, MaxNvBuffer, NvPublicBuilder, Signature,
18 SignatureScheme,
19};
20use tss_esapi::tcti_ldr::{DeviceConfig, TctiNameConf};
21use tss_esapi::traits::{Marshall, UnMarshall};
22use tss_esapi::Context;
23
24#[cfg(feature = "verifier")]
25mod verify;
26
27#[cfg(feature = "verifier")]
28pub use verify::VerifyError;
29
30const VTPM_HCL_REPORT_NV_INDEX: u32 = 0x01400001;
31const INDEX_REPORT_DATA: u32 = 0x01400002;
32const VTPM_AK_HANDLE: u32 = 0x81000003;
33const VTPM_QUOTE_PCR_SLOTS: [PcrSlot; 24] = [
34 PcrSlot::Slot0,
35 PcrSlot::Slot1,
36 PcrSlot::Slot2,
37 PcrSlot::Slot3,
38 PcrSlot::Slot4,
39 PcrSlot::Slot5,
40 PcrSlot::Slot6,
41 PcrSlot::Slot7,
42 PcrSlot::Slot8,
43 PcrSlot::Slot9,
44 PcrSlot::Slot10,
45 PcrSlot::Slot11,
46 PcrSlot::Slot12,
47 PcrSlot::Slot13,
48 PcrSlot::Slot14,
49 PcrSlot::Slot15,
50 PcrSlot::Slot16,
51 PcrSlot::Slot17,
52 PcrSlot::Slot18,
53 PcrSlot::Slot19,
54 PcrSlot::Slot20,
55 PcrSlot::Slot21,
56 PcrSlot::Slot22,
57 PcrSlot::Slot23,
58];
59
60fn to_pcr_handle(pcr: u8) -> Result<PcrHandle, ExtendError> {
61 match pcr {
62 0 => Ok(PcrHandle::Pcr0),
63 1 => Ok(PcrHandle::Pcr1),
64 2 => Ok(PcrHandle::Pcr2),
65 3 => Ok(PcrHandle::Pcr3),
66 4 => Ok(PcrHandle::Pcr4),
67 5 => Ok(PcrHandle::Pcr5),
68 6 => Ok(PcrHandle::Pcr6),
69 7 => Ok(PcrHandle::Pcr7),
70 8 => Ok(PcrHandle::Pcr8),
71 9 => Ok(PcrHandle::Pcr9),
72 10 => Ok(PcrHandle::Pcr10),
73 11 => Ok(PcrHandle::Pcr11),
74 12 => Ok(PcrHandle::Pcr12),
75 13 => Ok(PcrHandle::Pcr13),
76 14 => Ok(PcrHandle::Pcr14),
77 15 => Ok(PcrHandle::Pcr15),
78 16 => Ok(PcrHandle::Pcr16),
79 17 => Ok(PcrHandle::Pcr17),
80 18 => Ok(PcrHandle::Pcr18),
81 19 => Ok(PcrHandle::Pcr19),
82 20 => Ok(PcrHandle::Pcr20),
83 21 => Ok(PcrHandle::Pcr21),
84 22 => Ok(PcrHandle::Pcr22),
85 23 => Ok(PcrHandle::Pcr23),
86 _ => Err(ExtendError::InvalidPcr),
87 }
88}
89
90#[derive(Error, Debug)]
91pub enum ReportError {
92 #[error("tpm error")]
93 Tpm(#[from] tss_esapi::Error),
94 #[error("Failed to write value to nvindex")]
95 NvWriteFailed,
96}
97
98pub fn get_report() -> Result<Vec<u8>, ReportError> {
100 let nv_index = NvIndexTpmHandle::new(VTPM_HCL_REPORT_NV_INDEX)?;
101 let mut context = get_session_context()?;
102
103 let report = nv::read_full(&mut context, NvAuth::Owner, nv_index)?;
104 Ok(report)
105}
106
107pub fn get_report_with_report_data(report_data: &[u8]) -> Result<Vec<u8>, ReportError> {
111 let mut context = get_session_context()?;
112
113 let nv_index_report_data = NvIndexTpmHandle::new(INDEX_REPORT_DATA)?;
114 write_nv_index(&mut context, nv_index_report_data, report_data)?;
115
116 thread::sleep(Duration::new(3, 0));
117
118 let nv_index = NvIndexTpmHandle::new(VTPM_HCL_REPORT_NV_INDEX)?;
119 let report = nv::read_full(&mut context, NvAuth::Owner, nv_index)?;
120 Ok(report)
121}
122
123fn get_session_context() -> Result<Context, ReportError> {
124 let conf: TctiNameConf = TctiNameConf::Device(DeviceConfig::default());
125 let mut context = Context::new(conf)?;
126 let auth_session = AuthSession::Password;
127 context.set_sessions((Some(auth_session), None, None));
128 Ok(context)
129}
130
131enum NvSearchResult {
132 Found,
133 NotFound,
134 SizeMismatch,
135}
136
137fn find_index(
138 context: &mut Context,
139 nv_index: NvIndexTpmHandle,
140 len: usize,
141) -> Result<NvSearchResult, ReportError> {
142 let list = nv::list(context)?;
143 let result = list
144 .iter()
145 .find(|(public, _)| public.nv_index() == nv_index);
146 let Some((public, _)) = result else {
147 return Ok(NvSearchResult::NotFound);
148 };
149 if public.data_size() != len {
150 return Ok(NvSearchResult::SizeMismatch);
151 }
152
153 Ok(NvSearchResult::Found)
154}
155
156fn create_index(
157 context: &mut Context,
158 handle: NvIndexTpmHandle,
159 len: usize,
160) -> Result<NvIndexHandle, ReportError> {
161 let attributes = NvIndexAttributesBuilder::new()
162 .with_owner_write(true)
163 .with_owner_read(true)
164 .build()?;
165
166 let owner = NvPublicBuilder::new()
167 .with_nv_index(handle)
168 .with_index_name_algorithm(HashingAlgorithm::Sha256)
169 .with_index_attributes(attributes)
170 .with_data_area_size(len)
171 .build()?;
172
173 let index = context.nv_define_space(Provision::Owner, None, owner)?;
174 Ok(index)
175}
176
177fn resolve_handle(
178 context: &mut Context,
179 handle: NvIndexTpmHandle,
180) -> Result<NvIndexHandle, ReportError> {
181 let key_handle = context.execute_without_session(|c| c.tr_from_tpm_public(handle.into()))?;
182 Ok(key_handle.into())
183}
184
185fn delete_index(context: &mut Context, handle: NvIndexTpmHandle) -> Result<(), ReportError> {
186 let index = resolve_handle(context, handle)?;
187 context.nv_undefine_space(Provision::Owner, index)?;
188 Ok(())
189}
190
191fn write_nv_index(
192 context: &mut Context,
193 handle: NvIndexTpmHandle,
194 data: &[u8],
195) -> Result<(), ReportError> {
196 let buffer = MaxNvBuffer::try_from(data)?;
197 let result = find_index(context, handle, data.len())?;
198 let index = match result {
199 NvSearchResult::NotFound => create_index(context, handle, data.len())?,
200 NvSearchResult::SizeMismatch => {
201 delete_index(context, handle)?;
202 create_index(context, handle, data.len())?
203 }
204 NvSearchResult::Found => resolve_handle(context, handle)?,
205 };
206 context.nv_write(NvAuth::Owner, index, buffer, 0)?;
207 Ok(())
208}
209
210#[derive(Error, Debug)]
211pub enum ExtendError {
212 #[error("tpm error")]
213 Tpm(#[from] tss_esapi::Error),
214 #[error("invalid pcr number (expected 0-23)")]
215 InvalidPcr,
216}
217
218pub fn extend_pcr(pcr: u8, digest: &[u8; 32]) -> Result<(), ExtendError> {
220 let pcr_handle = to_pcr_handle(pcr)?;
221
222 let mut vals = DigestValues::new();
223 let sha256_digest = digest.to_vec().try_into()?;
224 vals.set(HashingAlgorithm::Sha256, sha256_digest);
225
226 let conf: TctiNameConf = TctiNameConf::Device(DeviceConfig::default());
227 let mut context = Context::new(conf)?;
228
229 let auth_session = AuthSession::Password;
230 context.set_sessions((Some(auth_session), None, None));
231 context.pcr_extend(pcr_handle, vals)?;
232
233 Ok(())
234}
235
236#[derive(Error, Debug)]
237pub enum AKPubError {
238 #[error("tpm error")]
239 Tpm(#[from] tss_esapi::Error),
240 #[error("asn1 der error")]
241 WrongKeyType,
242}
243
244#[derive(Serialize, Deserialize, Debug)]
245pub struct PublicKey {
246 n: Vec<u8>,
247 e: Vec<u8>,
248}
249
250impl PublicKey {
251 pub fn modulus(&self) -> &[u8] {
253 &self.n
254 }
255
256 pub fn exponent(&self) -> &[u8] {
258 &self.e
259 }
260}
261
262pub fn get_ak_pub() -> Result<PublicKey, AKPubError> {
264 let conf: TctiNameConf = TctiNameConf::Device(DeviceConfig::default());
265 let mut context = Context::new(conf)?;
266 let tpm_handle: TpmHandle = VTPM_AK_HANDLE.try_into()?;
267 let key_handle = context.tr_from_tpm_public(tpm_handle)?;
268 let (pk, _, _) = context.read_public(key_handle.into())?;
269
270 let decoded_key: DecodedKey = pk.try_into()?;
271 let DecodedKey::RsaPublicKey(rsa_pk) = decoded_key else {
272 return Err(AKPubError::WrongKeyType);
273 };
274
275 let bytes_n = rsa_pk.modulus.as_unsigned_bytes_be();
276 let bytes_e = rsa_pk.public_exponent.as_unsigned_bytes_be();
277 let pkey = PublicKey {
278 n: bytes_n.into(),
279 e: bytes_e.into(),
280 };
281 Ok(pkey)
282}
283
284#[non_exhaustive]
285#[derive(Error, Debug)]
286pub enum QuoteError {
287 #[error("tpm error")]
288 Tpm(#[from] tss_esapi::Error),
289 #[error("data too large")]
290 DataTooLarge,
291 #[error("Not a quote, that should not occur")]
292 NotAQuote,
293 #[error("Wrong signature, that should not occur")]
294 WrongSignature,
295 #[error("PCR bank not found")]
296 PcrBankNotFound,
297 #[error("PCR reading error")]
298 PcrRead,
299}
300
301#[derive(Serialize, Deserialize, Debug, Clone)]
302pub struct Quote {
303 signature: Vec<u8>,
304 message: Vec<u8>,
305 pcrs: Vec<[u8; 32]>,
306}
307
308impl Quote {
309 pub fn pcrs_sha256(&self) -> impl Iterator<Item = &[u8; 32]> {
311 self.pcrs.iter()
312 }
313
314 pub fn signature(&self) -> Vec<u8> {
316 self.signature.clone()
317 }
318
319 pub fn nonce(&self) -> Result<Vec<u8>, QuoteError> {
321 let attest = Attest::unmarshall(&self.message)?;
322 let nonce = attest.extra_data().to_vec();
323 Ok(nonce)
324 }
325
326 pub fn message(&self) -> Vec<u8> {
328 self.message.clone()
329 }
330}
331
332pub fn get_quote(data: &[u8]) -> Result<Quote, QuoteError> {
338 if data.len() > Data::MAX_SIZE {
339 return Err(QuoteError::DataTooLarge);
340 }
341 let conf: TctiNameConf = TctiNameConf::Device(DeviceConfig::default());
342 let mut context = Context::new(conf)?;
343 let tpm_handle: TpmHandle = VTPM_AK_HANDLE.try_into()?;
344 let key_handle = context.tr_from_tpm_public(tpm_handle)?;
345
346 let quote_data: Data = data.try_into()?;
347 let scheme = SignatureScheme::Null;
348 let hash_algo = HashingAlgorithm::Sha256;
349 let selection_list = PcrSelectionListBuilder::new()
350 .with_selection(hash_algo, &VTPM_QUOTE_PCR_SLOTS)
351 .build()?;
352
353 let auth_session = AuthSession::Password;
354 context.set_sessions((Some(auth_session), None, None));
355
356 let (attest, signature) = context.quote(
357 key_handle.into(),
358 quote_data,
359 scheme,
360 selection_list.clone(),
361 )?;
362
363 let AttestInfo::Quote { .. } = attest.attested() else {
364 return Err(QuoteError::NotAQuote);
365 };
366 let Signature::RsaSsa(rsa_sig) = signature else {
367 return Err(QuoteError::WrongSignature);
368 };
369
370 let signature = rsa_sig.signature().to_vec();
371 let message = attest.marshall()?;
372
373 context.clear_sessions();
374 let pcr_data = pcr::read_all(&mut context, selection_list)?;
375
376 let pcr_bank = pcr_data
377 .pcr_bank(hash_algo)
378 .ok_or(QuoteError::PcrBankNotFound)?;
379
380 let pcrs: Result<Vec<[u8; 32]>, _> = pcr_bank
381 .into_iter()
382 .map(|(_, digest)| digest.clone().try_into().map_err(|_| QuoteError::PcrRead))
383 .collect();
384 let pcrs = pcrs?;
385
386 Ok(Quote {
387 signature,
388 message,
389 pcrs,
390 })
391}