1#![deny(clippy::all)]
6#![deny(clippy::pedantic)]
7
8use nix::{
9 fcntl,
10 poll::{poll, PollFd, PollFlags},
11};
12use std::{
13 cell::RefCell,
14 collections::HashMap,
15 fs::{File, OpenOptions},
16 io::{Read, Write},
17 num::TryFromIntError,
18 os::fd::{AsFd, AsRawFd},
19 path::Path,
20 rc::Rc,
21 time::Instant,
22};
23
24use thiserror::Error;
25use tpm2_crypto::{tpm_make_name, Error as CryptoError};
26use tpm2_policy_language::{TpmHandleClass, TpmHandleRef};
27use tpm2_protocol::{
28 constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
29 data::{
30 Tpm2bName, TpmCap, TpmCc, TpmHt, TpmPt, TpmRc, TpmRcBase, TpmSt, TpmsAlgProperty,
31 TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmtPublic, TpmuCapabilities,
32 },
33 frame::{
34 tpm_marshal_command, tpm_unmarshal_response, TpmAuthResponses, TpmContextLoadCommand,
35 TpmContextSaveCommand, TpmEvictControlCommand, TpmFlushContextCommand, TpmFrame,
36 TpmGetCapabilityCommand, TpmGetCapabilityResponse, TpmReadPublicCommand, TpmResponse,
37 },
38 TpmHandle, TpmWriter,
39};
40use tracing::trace;
41
42pub trait TpmCommandObject: TpmFrame {}
44impl<T> TpmCommandObject for T where T: TpmFrame {}
45
46#[derive(Debug, Error)]
48pub enum TpmDeviceError {
49 #[error("device is already borrowed")]
50 AlreadyBorrowed,
51 #[error("capability not found: {0}")]
52 CapabilityMissing(TpmCap),
53 #[error("operation interrupted by user")]
54 Interrupted,
55 #[error("invalid response")]
56 InvalidResponse,
57 #[error("device not available")]
58 NotAvailable,
59
60 #[error("marshal: {0}")]
62 Marshal(tpm2_protocol::TpmProtocolError),
63
64 #[error("unmarshal: {0}")]
66 Unmarshal(tpm2_protocol::TpmProtocolError),
67
68 #[error("response mismatch: {0}")]
69 ResponseMismatch(TpmCc),
70 #[error("TPM command timed out")]
71 Timeout,
72 #[error("unexpected EOF")]
73 UnexpectedEof,
74 #[error("int decode: {0}")]
75 IntDecode(#[from] TryFromIntError),
76 #[error("I/O: {0}")]
77 Io(#[from] std::io::Error),
78 #[error("syscall: {0}")]
79 Nix(#[from] nix::Error),
80 #[error("crypto: {0}")]
81 InvalidCrypto(#[from] CryptoError),
82 #[error("TPM return code: {0}")]
83 TpmRc(TpmRc),
84}
85
86impl From<TpmRc> for TpmDeviceError {
87 fn from(rc: TpmRc) -> Self {
88 Self::TpmRc(rc)
89 }
90}
91
92pub fn with_device<F, T, E>(device: Option<Rc<RefCell<TpmDevice>>>, f: F) -> Result<T, E>
104where
105 F: FnOnce(&mut TpmDevice) -> Result<T, E>,
106 E: From<TpmDeviceError>,
107{
108 let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
109 let mut device_guard = device_rc
110 .try_borrow_mut()
111 .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
112 f(&mut device_guard)
113}
114
115pub struct TpmDevice {
116 file: File,
117 name_cache: HashMap<u32, (TpmtPublic, Tpm2bName)>,
118 interrupt_check: Box<dyn Fn() -> bool>,
119 resp_buf: Vec<u8>,
120}
121
122impl std::fmt::Debug for TpmDevice {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("Device")
125 .field("file", &self.file)
126 .field("name_cache", &self.name_cache)
127 .finish_non_exhaustive()
128 }
129}
130
131impl TpmDevice {
132 const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
133
134 pub fn open(
141 path: &Path,
142 interrupt_check: Box<dyn Fn() -> bool>,
143 ) -> Result<Self, TpmDeviceError> {
144 let file = OpenOptions::new()
145 .read(true)
146 .write(true)
147 .open(path)
148 .map_err(TpmDeviceError::Io)?;
149
150 let fd = file.as_raw_fd();
151 let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
152 let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
153 oflags.insert(fcntl::OFlag::O_NONBLOCK);
154 fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
155
156 Ok(Self {
157 file,
158 name_cache: HashMap::new(),
159 interrupt_check,
160 resp_buf: Vec::with_capacity(TPM_MAX_COMMAND_SIZE as usize),
161 })
162 }
163
164 fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
165 let fd = self.file.as_fd();
166 let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
167
168 let num_events = match poll(&mut fds, 100u16) {
169 Ok(num) => num,
170 Err(nix::Error::EINTR) => return Ok(0),
171 Err(e) => return Err(e.into()),
172 };
173
174 if num_events == 0 {
175 return Ok(0);
176 }
177
178 let revents = fds[0].revents().unwrap_or(PollFlags::empty());
179
180 if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
181 return Err(TpmDeviceError::UnexpectedEof);
182 }
183
184 if revents.contains(PollFlags::POLLIN) {
185 match self.file.read(buf) {
186 Ok(0) => Err(TpmDeviceError::UnexpectedEof),
187 Ok(n) => Ok(n),
188 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
189 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
190 Err(e) => Err(e.into()),
191 }
192 } else if revents.contains(PollFlags::POLLHUP) {
193 Err(TpmDeviceError::UnexpectedEof)
194 } else {
195 Ok(0)
196 }
197 }
198
199 pub fn transmit<C: TpmCommandObject>(
218 &mut self,
219 command: &C,
220 sessions: &[TpmsAuthCommand],
221 ) -> Result<(TpmResponse, TpmAuthResponses), TpmDeviceError> {
222 let command_vec = TpmDevice::build_command_buffer(command, sessions)?;
223 let cc = command.cc();
224
225 self.file.write_all(&command_vec)?;
226 self.file.flush()?;
227
228 let start_time = Instant::now();
229 self.resp_buf.clear();
230 let mut total_size: Option<usize> = None;
231 let mut temp_buf = [0u8; 1024];
232
233 loop {
234 if (self.interrupt_check)() {
235 return Err(TpmDeviceError::Interrupted);
236 }
237 if start_time.elapsed() > std::time::Duration::from_secs(120) {
238 return Err(TpmDeviceError::Timeout);
239 }
240
241 let n = self.receive(&mut temp_buf)?;
242 if n > 0 {
243 self.resp_buf.extend_from_slice(&temp_buf[..n]);
244 }
245
246 if total_size.is_none() && self.resp_buf.len() >= 10 {
247 let Ok(size_bytes): Result<[u8; 4], _> = self.resp_buf[2..6].try_into() else {
248 return Err(TpmDeviceError::InvalidResponse);
249 };
250 let size = u32::from_be_bytes(size_bytes) as usize;
251 if !(10..=TPM_MAX_COMMAND_SIZE as usize).contains(&size) {
252 return Err(TpmDeviceError::InvalidResponse);
253 }
254 total_size = Some(size);
255 }
256
257 if let Some(size) = total_size {
258 if self.resp_buf.len() == size {
259 break;
260 }
261 if self.resp_buf.len() > size {
262 return Err(TpmDeviceError::InvalidResponse);
263 }
264 }
265 }
266
267 let result = tpm_unmarshal_response(cc, &self.resp_buf).map_err(TpmDeviceError::Unmarshal);
268 trace!("{} R: {}", cc, hex::encode(&self.resp_buf));
269 Ok(result??)
270 }
271
272 fn build_command_buffer<C: TpmCommandObject>(
273 command: &C,
274 sessions: &[TpmsAuthCommand],
275 ) -> Result<Vec<u8>, TpmDeviceError> {
276 let cc = command.cc();
277 let tag = if sessions.is_empty() {
278 TpmSt::NoSessions
279 } else {
280 TpmSt::Sessions
281 };
282 let mut buf = vec![0u8; TPM_MAX_COMMAND_SIZE as usize];
283 let len = {
284 let mut writer = TpmWriter::new(&mut buf);
285 tpm_marshal_command(command, tag, sessions, &mut writer)
286 .map_err(TpmDeviceError::Marshal)?;
287 writer.len()
288 };
289 buf.truncate(len);
290
291 trace!("{} C: {}", cc, hex::encode(&buf));
292 Ok(buf)
293 }
294
295 pub fn get_capability<T, F, N>(
302 &mut self,
303 cap: TpmCap,
304 property_start: u32,
305 count: u32,
306 mut extract: F,
307 next_prop: N,
308 ) -> Result<Vec<T>, TpmDeviceError>
309 where
310 T: Copy,
311 F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
312 N: Fn(&T) -> u32,
313 {
314 let mut results = Vec::new();
315 let mut prop = property_start;
316 loop {
317 let (more_data, cap_data) = self.get_capability_page(cap, prop, count)?;
318 let items: &[T] = extract(&cap_data.data)?;
319 results.extend_from_slice(items);
320
321 if more_data {
322 if let Some(last) = items.last() {
323 prop = next_prop(last);
324 } else {
325 break;
326 }
327 } else {
328 break;
329 }
330 }
331 Ok(results)
332 }
333
334 pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
344 self.get_capability(
345 TpmCap::Algs,
346 0,
347 u32::try_from(MAX_HANDLES)?,
348 |caps| match caps {
349 TpmuCapabilities::Algs(algs) => Ok(algs),
350 _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
351 },
352 |last| last.alg as u32 + 1,
353 )
354 }
355
356 pub fn fetch_handles(&mut self, class: u32) -> Result<Vec<TpmHandleRef>, TpmDeviceError> {
366 self.get_capability(
367 TpmCap::Handles,
368 class,
369 u32::try_from(MAX_HANDLES)?,
370 |caps| match caps {
371 TpmuCapabilities::Handles(handles) => Ok(handles),
372 _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
373 },
374 |last| *last + 1,
375 )
376 .map(|handles| {
377 handles
378 .into_iter()
379 .map(|h| TpmHandleRef::new(TpmHandleClass::Tpm, h))
380 .collect()
381 })
382 }
383
384 pub fn get_capability_page(
393 &mut self,
394 cap: TpmCap,
395 property: u32,
396 count: u32,
397 ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
398 let cmd = TpmGetCapabilityCommand {
399 cap,
400 property,
401 property_count: count,
402 };
403
404 let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
405 let TpmGetCapabilityResponse {
406 more_data,
407 capability_data,
408 } = resp
409 .GetCapability()
410 .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::GetCapability))?;
411
412 Ok((more_data.into(), capability_data))
413 }
414
415 pub fn get_tpm_property(&mut self, property: TpmPt) -> Result<u32, TpmDeviceError> {
423 let (_, cap_data) = self.get_capability_page(TpmCap::TpmProperties, property as u32, 1)?;
424
425 let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
426 return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
427 };
428
429 let Some(prop) = props.first() else {
430 return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
431 };
432
433 Ok(prop.value)
434 }
435
436 pub fn read_public(
444 &mut self,
445 handle: TpmHandle,
446 ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
447 if let Some(cached) = self.name_cache.get(&handle.0) {
448 return Ok(cached.clone());
449 }
450
451 let cmd = TpmReadPublicCommand {
452 object_handle: handle,
453 };
454 let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
455
456 let read_public_resp = resp
457 .ReadPublic()
458 .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ReadPublic))?;
459
460 let public = read_public_resp.out_public.inner;
461 let name = read_public_resp.name;
462
463 self.name_cache.insert(handle.0, (public.clone(), name));
464 Ok((public, name))
465 }
466
467 pub fn find_persistent(
476 &mut self,
477 target: &TpmtPublic,
478 ) -> Result<Option<(TpmHandle, Tpm2bName)>, TpmDeviceError> {
479 let handles = self.fetch_handles((TpmHt::Persistent as u32) << 24)?;
480 for handle in handles {
481 if let Some(handle_val) = handle.value() {
482 match self.read_public(handle_val.into()) {
483 Ok((public, name)) => {
484 if public == *target {
485 return Ok(Some((handle_val.into(), name)));
486 }
487 }
488 Err(TpmDeviceError::TpmRc(rc)) => {
489 let base = rc.base();
490 if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
491 continue;
492 }
493 return Err(TpmDeviceError::TpmRc(rc));
494 }
495 Err(e) => return Err(e),
496 }
497 }
498 }
499 Ok(None)
500 }
501
502 pub fn find_persistent_by_name(
513 &mut self,
514 target_name: &Tpm2bName,
515 ) -> Result<Option<TpmHandle>, TpmDeviceError> {
516 let handles = self.fetch_handles((TpmHt::Persistent as u32) << 24)?;
517 for handle in handles {
518 if let Some(handle_val) = handle.value() {
519 match self.read_public(handle_val.into()) {
520 Ok((public, name)) => {
521 if name == *target_name {
522 return Ok(Some(handle_val.into()));
523 }
524 let calculated_name = tpm_make_name(&public)?;
525 if calculated_name == *target_name {
526 return Ok(Some(handle_val.into()));
527 }
528 }
529 Err(TpmDeviceError::TpmRc(rc)) => {
530 let base = rc.base();
531 if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
532 continue;
533 }
534 return Err(TpmDeviceError::TpmRc(rc));
535 }
536 Err(e) => return Err(e),
537 }
538 }
539 }
540 Ok(None)
541 }
542
543 pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
551 let cmd = TpmContextSaveCommand { save_handle };
552 let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
553 let save_resp = resp
554 .ContextSave()
555 .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ContextSave))?;
556 Ok(save_resp.context)
557 }
558
559 pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
567 let cmd = TpmContextLoadCommand { context };
568 let (resp, _) = self.transmit(&cmd, Self::NO_SESSIONS)?;
569 let resp_inner = resp
570 .ContextLoad()
571 .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::ContextLoad))?;
572 Ok(resp_inner.loaded_handle)
573 }
574
575 pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
582 self.name_cache.remove(&handle.0);
583 let cmd = TpmFlushContextCommand {
584 flush_handle: handle,
585 };
586 self.transmit(&cmd, Self::NO_SESSIONS)?;
587 Ok(())
588 }
589
590 pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
599 match self.load_context(context) {
600 Ok(handle) => self.flush_context(handle),
601 Err(TpmDeviceError::TpmRc(rc)) => {
602 let base = rc.base();
603 if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
604 Ok(())
605 } else {
606 Err(TpmDeviceError::TpmRc(rc))
607 }
608 }
609 Err(e) => Err(e),
610 }
611 }
612
613 pub fn evict_control(
621 &mut self,
622 auth: TpmHandle,
623 object_handle: TpmHandle,
624 persistent_handle: TpmHandle,
625 sessions: &[TpmsAuthCommand],
626 ) -> Result<(), TpmDeviceError> {
627 let cmd = TpmEvictControlCommand {
628 auth,
629 object_handle: object_handle.0.into(),
630 persistent_handle,
631 };
632 let (resp, _) = self.transmit(&cmd, sessions)?;
633
634 resp.EvictControl()
635 .map_err(|_| TpmDeviceError::ResponseMismatch(TpmCc::EvictControl))?;
636 Ok(())
637 }
638
639 pub fn refresh_key(&mut self, context: TpmsContext) -> Result<bool, TpmDeviceError> {
649 match self.load_context(context) {
650 Ok(handle) => match self.flush_context(handle) {
651 Ok(()) => Ok(true),
652 Err(e) => Err(e),
653 },
654 Err(TpmDeviceError::TpmRc(rc)) if rc.base() == TpmRcBase::ReferenceH0 => Ok(false),
655 Err(e) => Err(e),
656 }
657 }
658}