1use crate::{
6 auth::{Auth, AuthClass, AuthError},
7 crypto::{crypto_hash_size, crypto_make_name, CryptoError},
8 device::{with_device, Device, DeviceError, TpmCommandObject},
9 handle::{Handle, HandleClass},
10 key::{AnyKey, KeyError, TpmKey},
11 vtpm::{build_password_session, create_auth, VtpmCache, VtpmContext, VtpmError},
12 write_object,
13};
14use rand::{thread_rng, RngCore};
15use std::{cell::RefCell, collections::HashSet, io, io::Write, num::TryFromIntError, rc::Rc};
16use thiserror::Error;
17use tpm2_protocol::{
18 data::{Tpm2bNonce, TpmAlgId, TpmCc, TpmRcBase, TpmRh, TpmaNv, TpmaSession, TpmsAuthCommand},
19 message::{
20 TpmAuthResponses, TpmEvictControlCommand, TpmNvReadCommand, TpmNvReadPublicCommand,
21 TpmResponseBody,
22 },
23 TpmError, TpmHandle,
24};
25
26#[derive(Debug, Error)]
27pub enum JobError {
28 #[error("handle not found: {0}{1:08x}")]
29 HandleNotFound(&'static str, u32),
30 #[error("invalid auth")]
31 InvalidAuth,
32 #[error("invalid key format")]
33 InvalidFormat,
34 #[error("invalid parent: {0}{1:08x}")]
35 InvalidParent(&'static str, u32),
36 #[error("malformed data")]
37 MalformedData,
38 #[error("parent not found")]
39 ParentNotFound,
40 #[error("response mismatch: {0}")]
41 ResponseMismatch(TpmCc),
42 #[error("trailing authorizations")]
43 TrailingAuthorizations,
44 #[error("I/O: {0}")]
45 Io(#[from] io::Error),
46 #[error("key error: {0}")]
47 Key(#[from] KeyError),
48 #[error("cache: {0}")]
49 Vtpm(#[from] VtpmError),
50 #[error("device: {0}")]
51 Device(#[from] DeviceError),
52 #[error("auth error: {0}")]
53 Auth(#[from] AuthError),
54 #[error("crypto: {0}")]
55 Crypto(#[from] CryptoError),
56 #[error("int decode: {0}")]
57 IntDecode(#[from] TryFromIntError),
58}
59
60impl From<TpmError> for JobError {
61 fn from(err: TpmError) -> Self {
62 Self::Device(DeviceError::from(err))
63 }
64}
65
66pub struct Job<'a> {
67 pub device: Option<Rc<RefCell<Device>>>,
68 pub cache: &'a mut VtpmCache<'a>,
69 pub auth_list: &'a [Auth],
70 pub writer: &'a mut dyn Write,
71}
72
73impl<'a> Job<'a> {
74 #[must_use]
76 pub fn new(
77 device: Option<Rc<RefCell<Device>>>,
78 cache: &'a mut VtpmCache<'a>,
79 auth_list: &'a [Auth],
80 writer: &'a mut dyn Write,
81 ) -> Self {
82 Self {
83 device,
84 cache,
85 auth_list,
86 writer,
87 }
88 }
89
90 fn fetch_ancestor_chain(
111 &self,
112 target_vhandle: u32,
113 device: &mut Device,
114 ) -> Result<Vec<Handle>, JobError> {
115 let mut current_vhandle = target_vhandle;
116 let mut vtp_chain: Vec<Handle> = Vec::new();
117 let mut physical_primary: Option<Handle> = None;
118
119 loop {
120 let key = self.cache.find_by_vhandle(current_vhandle)?;
121
122 if key.parent.inner.object_type == TpmAlgId::Null {
123 break;
124 }
125
126 if let Some(parent_key) = self.cache.find_by_public(&key.parent.inner) {
127 let parent_vhandle = parent_key.handle();
128 vtp_chain.push(Handle((HandleClass::Vtpm, current_vhandle)));
129 current_vhandle = parent_vhandle;
130 } else {
131 match device.find_persistent(&key.parent.inner)? {
132 Some((phandle, _)) => {
133 physical_primary = Some(Handle((HandleClass::Tpm, phandle.0)));
134 break;
135 }
136 None => {
137 return Err(JobError::ParentNotFound);
138 }
139 }
140 }
141 }
142
143 vtp_chain.push(Handle((HandleClass::Vtpm, current_vhandle)));
144 vtp_chain.reverse();
145
146 if let Some(root_handle) = physical_primary {
147 let mut final_chain = vec![root_handle];
148 final_chain.extend(vtp_chain);
149 Ok(final_chain)
150 } else {
151 Ok(vtp_chain)
152 }
153 }
154
155 pub fn load_context(
171 &mut self,
172 device: &mut Device,
173 target: &Handle,
174 ) -> Result<TpmHandle, JobError> {
175 if target.class() == HandleClass::Tpm {
176 return Ok(TpmHandle(target.value()));
177 }
178
179 let target_vhandle = target.value();
180 let chain = self.fetch_ancestor_chain(target_vhandle, device)?;
181
182 if chain.is_empty() {
183 return Err(JobError::HandleNotFound("vtpm:", target_vhandle));
184 }
185
186 let mut phandle: Option<TpmHandle> = None;
187 let mut chain_iter = chain.into_iter();
188
189 if let Some(first_handle) = chain_iter.next() {
190 match first_handle.class() {
191 HandleClass::Tpm => {
192 phandle = Some(TpmHandle(first_handle.value()));
193 }
194 HandleClass::Vtpm => {
195 let key = self.cache.find_by_vhandle(first_handle.value())?;
196 let loaded_phandle = device.load_context(key.context.clone())?;
197 self.cache.track(loaded_phandle)?;
198 phandle = Some(loaded_phandle);
199 }
200 }
201 }
202
203 for handle in chain_iter {
204 let vhandle = handle.value();
205 let key = self.cache.find_by_vhandle(vhandle)?;
206
207 let parent_phandle = phandle.ok_or(JobError::ParentNotFound)?;
208 let loaded_phandle = device.load_context(key.context.clone())?;
209
210 if device.read_public(parent_phandle)?.1 != crypto_make_name(&key.parent.inner)? {
211 self.cache.untrack(loaded_phandle.0);
212 device.flush_context(loaded_phandle)?;
213 return Err(JobError::InvalidParent("vtpm:", vhandle));
214 }
215
216 self.cache.track(loaded_phandle)?;
217 phandle = Some(loaded_phandle);
218 }
219
220 phandle.ok_or(JobError::HandleNotFound("vtpm:", target_vhandle))
221 }
222
223 fn build_auth_area<C: TpmCommandObject>(
242 &self,
243 device: &mut Device,
244 command: &C,
245 handles: &[u32],
246 auth_list: &[Auth],
247 ) -> Result<Vec<TpmsAuthCommand>, JobError> {
248 let mut built_auths = Vec::new();
249 let params = write_object(command).map_err(DeviceError::TpmProtocol)?;
250
251 let mut nonce_decrypt: Option<Tpm2bNonce> = None;
252 let mut nonce_encrypt: Option<Tpm2bNonce> = None;
253
254 for auth in auth_list {
255 if auth.class() == AuthClass::Session {
256 let vhandle = auth.session()?;
257 if let Some(session) = self.cache.get_session(vhandle) {
258 if session.attributes.contains(TpmaSession::DECRYPT) {
259 nonce_decrypt = Some(session.nonce_tpm);
260 }
261 if session.attributes.contains(TpmaSession::ENCRYPT) {
262 nonce_encrypt = Some(session.nonce_tpm);
263 }
264 }
265 if nonce_decrypt.is_some() && nonce_encrypt.is_some() {
266 break;
267 }
268 }
269 }
270
271 for (i, auth) in auth_list.iter().enumerate() {
272 let handle_param = handles.get(i).ok_or(JobError::TrailingAuthorizations)?;
273
274 match auth.class() {
275 AuthClass::Password => {
276 built_auths.push(build_password_session(auth.value())?);
277 }
278 AuthClass::Session => {
279 let vhandle = auth.session()?;
280 let session = self
281 .cache
282 .get_session(vhandle)
283 .ok_or(JobError::HandleNotFound("vtpm:", vhandle))?;
284 let nonce_size =
285 crypto_hash_size(session.auth_hash).ok_or(JobError::MalformedData)?;
286 let mut nonce_bytes = vec![0; nonce_size];
287 thread_rng().fill_bytes(&mut nonce_bytes);
288 let nonce_caller = Tpm2bNonce::try_from(nonce_bytes.as_slice())
289 .map_err(DeviceError::TpmProtocol)?;
290 let (current_nonce_decrypt, current_nonce_encrypt) = if i == 0 {
291 (nonce_decrypt.as_ref(), nonce_encrypt.as_ref())
292 } else {
293 (None, None)
294 };
295
296 let result = create_auth(
297 device,
298 session,
299 &nonce_caller,
300 &[],
301 C::CC,
302 &[*handle_param],
303 ¶ms,
304 current_nonce_decrypt,
305 current_nonce_encrypt,
306 )?;
307 built_auths.push(result);
308 }
309 AuthClass::Policy => return Err(JobError::InvalidAuth),
310 }
311 }
312 Ok(built_auths)
313 }
314
315 pub fn execute<C: TpmCommandObject>(
326 &mut self,
327 device: &mut Device,
328 command: &C,
329 handles: &[u32],
330 auth_list: &[Auth],
331 ) -> Result<(TpmResponseBody, TpmAuthResponses), JobError> {
332 let auth_handles = self.cache.prepare_sessions(device, auth_list)?;
333 for &handle in &auth_handles {
334 self.cache.track(handle)?;
335 }
336
337 let sessions = self.build_auth_area(device, command, handles, auth_list)?;
338 let (resp, auth_responses) = match device.execute(command, &sessions) {
339 Ok((resp, auth_responses)) => (resp, auth_responses),
340 Err(DeviceError::TpmRc(rc)) => {
341 if rc.base() == TpmRcBase::PolicyFail {
342 for auth in auth_list {
343 if auth.class() == AuthClass::Session {
344 let vhandle = auth.session()?;
345 log::debug!("vtpm:{vhandle} is stale");
346 self.cache.remove(device, vhandle)?;
347 }
348 }
349 }
350 return Err(JobError::Device(DeviceError::TpmRc(rc)));
351 }
352 Err(err) => return Err(JobError::Device(err)),
353 };
354
355 let mut used_auth_list = HashSet::new();
356 for auth in auth_list {
357 if auth.class() == AuthClass::Session {
358 let handle = auth.session()?;
359 used_auth_list.insert(handle);
360 }
361 }
362
363 self.cache
364 .teardown_sessions(device, &used_auth_list, &auth_responses)?;
365
366 for handle in auth_handles {
367 self.cache.untrack(handle.0);
368 }
369
370 Ok((resp, auth_responses))
371 }
372
373 pub fn evict_control(
383 &mut self,
384 auth_handle: TpmHandle,
385 object_to_evict: TpmHandle,
386 persistent_handle: TpmHandle,
387 auths: &[Auth],
388 ) -> Result<(), JobError> {
389 with_device(self.device.clone(), |device| {
390 let cmd = TpmEvictControlCommand {
391 auth: auth_handle,
392 object_handle: object_to_evict.0.into(),
393 persistent_handle,
394 };
395 let handles_for_session = [auth_handle.0];
396
397 let (resp, _) = self.execute(device, &cmd, &handles_for_session, auths)?;
398
399 resp.EvictControl()
400 .map_err(|_| JobError::ResponseMismatch(TpmCc::EvictControl))?;
401 Ok(())
402 })
403 }
404
405 pub fn import_key(
416 &mut self,
417 device: &mut Device,
418 parent_handle: TpmHandle,
419 input_bytes: &[u8],
420 auths: &[Auth],
421 ) -> Result<TpmKey, JobError> {
422 let external_key = match AnyKey::try_from(input_bytes)? {
423 AnyKey::Tpm(_) => {
424 return Err(JobError::InvalidFormat);
425 }
426 AnyKey::External(key) => key,
427 };
428 let mut rng = rand::thread_rng();
429 Ok(TpmKey::from_external_key(
430 device,
431 self,
432 parent_handle,
433 &external_key,
434 &mut rng,
435 &[parent_handle.0],
436 auths,
437 )?)
438 }
439
440 pub fn read_certificate(
450 &mut self,
451 device: &mut Device,
452 auths: &[Auth],
453 handle: u32,
454 max_read_size: usize,
455 ) -> Result<Option<Vec<u8>>, JobError> {
456 let nv_read_public_cmd = TpmNvReadPublicCommand {
457 nv_index: handle.into(),
458 };
459 let (resp, _) = self.execute(device, &nv_read_public_cmd, &[], &[])?;
460 let read_public_resp = resp
461 .NvReadPublic()
462 .map_err(|_| JobError::ResponseMismatch(TpmCc::NvReadPublic))?;
463 let nv_public = read_public_resp.nv_public;
464 let data_size = nv_public.data_size as usize;
465
466 if data_size == 0 {
467 return Ok(None);
468 }
469
470 let auth_handle_val = if nv_public.attributes.contains(TpmaNv::AUTHREAD) {
471 handle
472 } else if nv_public.attributes.contains(TpmaNv::PPREAD) {
473 TpmRh::Platform as u32
474 } else if nv_public.attributes.contains(TpmaNv::OWNERREAD) {
475 TpmRh::Owner as u32
476 } else {
477 handle
478 };
479
480 let mut cert_bytes = Vec::with_capacity(data_size);
481 let mut offset = 0;
482 while offset < data_size {
483 let chunk_size = std::cmp::min(max_read_size, data_size - offset);
484
485 let nv_read_cmd = TpmNvReadCommand {
486 auth_handle: auth_handle_val.into(),
487 nv_index: handle.into(),
488 size: u16::try_from(chunk_size)?,
489 offset: u16::try_from(offset)?,
490 };
491
492 let flags_to_check = TpmaNv::AUTHREAD | TpmaNv::OWNERREAD | TpmaNv::PPREAD;
493 let needs_auth = (nv_public.attributes.bits() & flags_to_check.bits()) != 0;
494
495 let effective_auths: &[Auth] = if needs_auth { auths } else { &[] };
496
497 let (resp, _) =
498 self.execute(device, &nv_read_cmd, &[auth_handle_val], effective_auths)?;
499
500 let read_resp = resp
501 .NvRead()
502 .map_err(|_| JobError::ResponseMismatch(TpmCc::NvRead))?;
503 cert_bytes.extend_from_slice(read_resp.data.as_ref());
504 offset += chunk_size;
505 }
506
507 Ok(Some(cert_bytes))
508 }
509}
510
511impl Drop for Job<'_> {
512 fn drop(&mut self) {
513 self.cache.teardown(self.device.clone());
514 }
515}