1#![deny(clippy::all)]
6#![deny(clippy::pedantic)]
7
8use nix::{
9 fcntl,
10 poll::{PollFd, PollFlags, poll},
11};
12use rand::{RngCore, thread_rng};
13use std::{
14 cell::RefCell,
15 fs::{File, OpenOptions},
16 io::{Read, Write},
17 os::fd::{AsFd, AsRawFd},
18 path::{Path, PathBuf},
19 rc::Rc,
20 time::{Duration, Instant},
21};
22
23use core::fmt;
24use tpm2_crypto::TpmHash;
25use tpm2_protocol::{
26 TpmError, TpmWriter,
27 basic::{TpmHandle, TpmUint32},
28 constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
29 data::{
30 Tpm2bEncryptedSecret, Tpm2bName, Tpm2bNonce, TpmAlgId, TpmCap, TpmCc, TpmEccCurve, TpmHt,
31 TpmPt, TpmRc, TpmRcBase, TpmRh, TpmSe, TpmSt, TpmaSession, TpmsAlgProperty,
32 TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmsPcrSelect, TpmsPcrSelection,
33 TpmtPublic, TpmtSymDefObject, TpmuCapabilities,
34 },
35 frame::{
36 TpmAuthCommands, TpmCommandValue as TpmCommand, TpmContextLoadCommand,
37 TpmContextLoadResponse, TpmContextSaveCommand, TpmContextSaveResponse,
38 TpmFlushContextCommand, TpmFrame, TpmGetCapabilityCommand, TpmGetCapabilityResponse,
39 TpmReadPublicCommand, TpmReadPublicResponse, TpmResponse, TpmResponseOutcome,
40 TpmResponseView, TpmStartAuthSessionCommand, TpmStartAuthSessionResponse,
41 tpm_marshal_command,
42 },
43};
44use tracing::{debug, trace};
45
46#[derive(Debug, strum::AsRefStr)]
51#[strum(serialize_all = "title_case")]
52#[non_exhaustive]
53pub enum TpmDeviceError {
54 AlreadyBorrowed,
56
57 CapabilityMissing(TpmCap),
59
60 Interrupted,
62
63 InvalidCc(tpm2_protocol::data::TpmCc),
65
66 InvalidResponse,
68
69 Io(std::io::Error),
71
72 Marshal(TpmError),
74
75 NotAvailable,
77
78 PcrBanksNotAvailable,
80
81 PcrBankSelectionMismatch,
83
84 ResponseMismatch(TpmCc),
86
87 Timeout,
89
90 TpmRc(TpmRc),
92
93 TrailingData,
95
96 Unmarshal(TpmError),
98
99 UnexpectedEof,
101
102 UnsupportedAlgorithm(TpmAlgId),
104}
105
106impl fmt::Display for TpmDeviceError {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "{}", self.as_ref().to_lowercase())
109 }
110}
111
112impl std::error::Error for TpmDeviceError {}
113
114impl PartialEq for TpmDeviceError {
115 fn eq(&self, other: &Self) -> bool {
116 match (self, other) {
117 (Self::CapabilityMissing(a), Self::CapabilityMissing(b)) => a == b,
118 (Self::InvalidCc(a), Self::InvalidCc(b))
119 | (Self::ResponseMismatch(a), Self::ResponseMismatch(b)) => a == b,
120 (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
121 (Self::Marshal(a), Self::Marshal(b)) | (Self::Unmarshal(a), Self::Unmarshal(b)) => {
122 a == b
123 }
124 (Self::TpmRc(a), Self::TpmRc(b)) => a == b,
125 (Self::UnsupportedAlgorithm(a), Self::UnsupportedAlgorithm(b)) => a == b,
126 (Self::AlreadyBorrowed, Self::AlreadyBorrowed)
127 | (Self::Interrupted, Self::Interrupted)
128 | (Self::InvalidResponse, Self::InvalidResponse)
129 | (Self::NotAvailable, Self::NotAvailable)
130 | (Self::PcrBanksNotAvailable, Self::PcrBanksNotAvailable)
131 | (Self::PcrBankSelectionMismatch, Self::PcrBankSelectionMismatch)
132 | (Self::Timeout, Self::Timeout)
133 | (Self::TrailingData, Self::TrailingData)
134 | (Self::UnexpectedEof, Self::UnexpectedEof) => true,
135 _ => false,
136 }
137 }
138}
139
140impl Eq for TpmDeviceError {}
141
142impl From<TpmRc> for TpmDeviceError {
143 fn from(rc: TpmRc) -> Self {
144 Self::TpmRc(rc)
145 }
146}
147
148impl From<std::io::Error> for TpmDeviceError {
149 fn from(err: std::io::Error) -> Self {
150 Self::Io(err)
151 }
152}
153
154impl From<nix::Error> for TpmDeviceError {
155 fn from(err: nix::Error) -> Self {
156 Self::Io(std::io::Error::from_raw_os_error(err as i32))
157 }
158}
159
160pub fn with_device<F, T, E>(device: Option<&Rc<RefCell<TpmDevice>>>, function: F) -> Result<T, E>
174where
175 F: FnOnce(&mut TpmDevice) -> Result<T, E>,
176 E: From<TpmDeviceError>,
177{
178 let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
179 let mut device_guard = device_rc
180 .try_borrow_mut()
181 .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
182 function(&mut device_guard)
183}
184
185pub trait TpmTransport {
192 fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError>;
198
199 fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError>;
211}
212
213const TPM_HEADER_SIZE: usize = 10;
214
215fn frame_size(header: &[u8]) -> Result<usize, TpmDeviceError> {
223 let Some(size_bytes) = header.get(2..6) else {
224 return Err(TpmDeviceError::InvalidResponse);
225 };
226 let Ok(size_bytes): Result<[u8; 4], _> = size_bytes.try_into() else {
227 return Err(TpmDeviceError::InvalidResponse);
228 };
229 let size = u32::from_be_bytes(size_bytes) as usize;
230 if !(TPM_HEADER_SIZE..=TPM_MAX_COMMAND_SIZE).contains(&size) {
231 return Err(TpmDeviceError::InvalidResponse);
232 }
233 Ok(size)
234}
235
236fn fill_exact<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<(), TpmDeviceError> {
237 match reader.read_exact(buf) {
238 Ok(()) => Ok(()),
239 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
240 Err(TpmDeviceError::UnexpectedEof)
241 }
242 Err(e) => Err(TpmDeviceError::Io(e)),
243 }
244}
245
246pub fn read_frame<R: Read>(reader: &mut R, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
261 buf.clear();
262 buf.resize(TPM_HEADER_SIZE, 0);
263 fill_exact(reader, buf)?;
264
265 let size = frame_size(buf)?;
266 buf.resize(size, 0);
267 fill_exact(reader, &mut buf[TPM_HEADER_SIZE..])?;
268
269 Ok(())
270}
271
272pub fn write_frame<W: Write>(writer: &mut W, frame: &[u8]) -> Result<(), TpmDeviceError> {
278 writer.write_all(frame).map_err(TpmDeviceError::Io)?;
279 writer.flush().map_err(TpmDeviceError::Io)?;
280 Ok(())
281}
282
283pub struct TpmStreamTransport<S: Read + Write> {
288 stream: S,
289}
290
291impl<S: Read + Write> TpmStreamTransport<S> {
292 #[must_use]
294 pub fn new(stream: S) -> Self {
295 Self { stream }
296 }
297
298 #[must_use]
300 pub fn into_inner(self) -> S {
301 self.stream
302 }
303}
304
305impl<S: Read + Write> TpmTransport for TpmStreamTransport<S> {
306 fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
307 write_frame(&mut self.stream, frame)
308 }
309
310 fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
311 read_frame(&mut self.stream, buf)
312 }
313}
314
315pub struct TpmPosixDevice {
317 file: File,
318 interrupted: Box<dyn Fn() -> bool>,
319 timeout: Duration,
320}
321
322impl TpmPosixDevice {
323 #[must_use]
325 pub fn builder() -> TpmPosixDeviceBuilder {
326 TpmPosixDeviceBuilder::default()
327 }
328
329 fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
330 let fd = self.file.as_fd();
331 let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
332
333 let num_events = match poll(&mut fds, 100u16) {
334 Ok(num) => num,
335 Err(nix::Error::EINTR) => return Ok(0),
336 Err(e) => return Err(e.into()),
337 };
338
339 if num_events == 0 {
340 return Ok(0);
341 }
342
343 let revents = fds[0].revents().unwrap_or(PollFlags::empty());
344
345 if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
346 return Err(TpmDeviceError::UnexpectedEof);
347 }
348
349 if revents.contains(PollFlags::POLLIN) {
350 match self.file.read(buf) {
351 Ok(0) => Err(TpmDeviceError::UnexpectedEof),
352 Ok(n) => Ok(n),
353 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
354 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
355 Err(e) => Err(e.into()),
356 }
357 } else if revents.contains(PollFlags::POLLHUP) {
358 Err(TpmDeviceError::UnexpectedEof)
359 } else {
360 Ok(0)
361 }
362 }
363}
364
365impl TpmTransport for TpmPosixDevice {
366 fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
367 self.file.write_all(frame)?;
368 self.file.flush()?;
369 Ok(())
370 }
371
372 fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
373 buf.clear();
374 let start_time = Instant::now();
375 let mut total_size: Option<usize> = None;
376 let mut temp_buf = [0u8; 1024];
377
378 loop {
379 if (self.interrupted)() {
380 return Err(TpmDeviceError::Interrupted);
381 }
382 if start_time.elapsed() > self.timeout {
383 return Err(TpmDeviceError::Timeout);
384 }
385
386 let n = self.receive(&mut temp_buf)?;
387 if n > 0 {
388 buf.extend_from_slice(&temp_buf[..n]);
389 }
390
391 if total_size.is_none() && buf.len() >= TPM_HEADER_SIZE {
392 total_size = Some(frame_size(buf)?);
393 }
394
395 if let Some(size) = total_size {
396 if buf.len() == size {
397 break;
398 }
399 if buf.len() > size {
400 return Err(TpmDeviceError::TrailingData);
401 }
402 }
403 }
404
405 Ok(())
406 }
407}
408
409pub struct TpmPosixDeviceBuilder {
411 path: PathBuf,
412 timeout: Duration,
413 interrupted: Box<dyn Fn() -> bool>,
414}
415
416impl Default for TpmPosixDeviceBuilder {
417 fn default() -> Self {
418 Self {
419 path: PathBuf::from("/dev/tpmrm0"),
420 timeout: Duration::from_mins(2),
421 interrupted: Box::new(|| false),
422 }
423 }
424}
425
426impl TpmPosixDeviceBuilder {
427 #[must_use]
429 pub fn with_path<P: AsRef<Path>>(mut self, path: P) -> Self {
430 self.path = path.as_ref().to_path_buf();
431 self
432 }
433
434 #[must_use]
436 pub fn with_timeout(mut self, timeout: Duration) -> Self {
437 self.timeout = timeout;
438 self
439 }
440
441 #[must_use]
443 pub fn with_interrupted<F>(mut self, handler: F) -> Self
444 where
445 F: Fn() -> bool + 'static,
446 {
447 self.interrupted = Box::new(handler);
448 self
449 }
450
451 pub fn build(self) -> Result<TpmPosixDevice, TpmDeviceError> {
458 let file = OpenOptions::new()
459 .read(true)
460 .write(true)
461 .open(&self.path)
462 .map_err(TpmDeviceError::Io)?;
463
464 let fd = file.as_raw_fd();
465 let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
466 let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
467 oflags.insert(fcntl::OFlag::O_NONBLOCK);
468 fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
469
470 Ok(TpmPosixDevice {
471 file,
472 interrupted: self.interrupted,
473 timeout: self.timeout,
474 })
475 }
476}
477
478pub struct TpmDevice {
479 transport: Box<dyn TpmTransport>,
480 command: Vec<u8>,
481 response: Vec<u8>,
482}
483
484impl std::fmt::Debug for TpmDevice {
485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486 f.debug_struct("TpmDevice").finish_non_exhaustive()
487 }
488}
489
490impl TpmDevice {
491 const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
492
493 #[allow(clippy::cast_possible_truncation)]
495 const CAPABILITY_PAGE_SIZE: u32 = MAX_HANDLES as u32;
496
497 #[must_use]
499 pub fn new(transport: Box<dyn TpmTransport>) -> Self {
500 Self {
501 transport,
502 command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
503 response: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
504 }
505 }
506
507 pub fn transmit<C: TpmFrame>(
525 &mut self,
526 command: &C,
527 sessions: &[TpmsAuthCommand],
528 ) -> Result<&TpmResponse, TpmDeviceError> {
529 self.prepare_command(command, sessions)?;
530 let cc = command.cc();
531
532 self.transport.send(&self.command)?;
533 self.transport.recv(&mut self.response)?;
534
535 let response = TpmResponse::cast(&self.response).map_err(TpmDeviceError::Unmarshal)?;
536 let outcome = TpmResponseView::cast(cc, response).map_err(TpmDeviceError::Unmarshal)?;
537 trace!("{} R: {}", cc, hex::encode(&self.response));
538 match outcome {
539 TpmResponseOutcome::Dispatched(_) => Ok(response),
540 TpmResponseOutcome::Rejected(rc) => Err(TpmDeviceError::TpmRc(rc)),
541 }
542 }
543
544 fn prepare_command<C: TpmFrame>(
545 &mut self,
546 command: &C,
547 sessions: &[TpmsAuthCommand],
548 ) -> Result<(), TpmDeviceError> {
549 let cc = command.cc();
550 let tag = if sessions.is_empty() {
551 TpmSt::NoSessions
552 } else {
553 TpmSt::Sessions
554 };
555
556 self.command.resize(TPM_MAX_COMMAND_SIZE, 0);
557
558 let len = {
559 let mut writer = TpmWriter::new(&mut self.command);
560 tpm_marshal_command(command, tag, sessions, &mut writer)
561 .map_err(TpmDeviceError::Marshal)?;
562 writer.len()
563 };
564 self.command.truncate(len);
565
566 trace!("{} C: {}", cc, hex::encode(&self.command));
567 Ok(())
568 }
569
570 fn get_capability<T, F, N>(
580 &mut self,
581 cap: TpmCap,
582 property_start: u32,
583 count: u32,
584 mut extract: F,
585 next_prop: N,
586 ) -> Result<Vec<T>, TpmDeviceError>
587 where
588 T: Copy,
589 F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
590 N: Fn(&T) -> u32,
591 {
592 let mut results = Vec::new();
593 let mut prop = property_start;
594 loop {
595 let (more_data, cap_data) =
596 self.get_capability_page(cap, TpmUint32::from(prop), TpmUint32::from(count))?;
597 let items: &[T] = extract(&cap_data.data)?;
598 results.extend_from_slice(items);
599
600 if !more_data {
601 break;
602 }
603
604 let Some(last) = items.last() else {
605 break;
606 };
607
608 let next = next_prop(last);
609 if next <= prop {
610 break;
611 }
612 prop = next;
613 }
614 Ok(results)
615 }
616
617 pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
626 self.get_capability(
627 TpmCap::Algs,
628 0,
629 Self::CAPABILITY_PAGE_SIZE,
630 |caps| match caps {
631 TpmuCapabilities::Algs(algs) => Ok(algs),
632 _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
633 },
634 |last| u32::from(last.alg.value()) + 1,
635 )
636 }
637
638 pub fn fetch_handles(&mut self, class: TpmHt) -> Result<Vec<TpmHandle>, TpmDeviceError> {
647 let class_prefix = (class as u32) << 24;
648
649 self.get_capability(
650 TpmCap::Handles,
651 class_prefix,
652 Self::CAPABILITY_PAGE_SIZE,
653 |caps| match caps {
654 TpmuCapabilities::Handles(handles) => Ok(handles),
655 _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
656 },
657 |last| last.value().saturating_add(1),
658 )
659 .map(|handles| {
660 handles
661 .into_iter()
662 .filter(|handle| handle.value() >> 24 == class as u32)
663 .collect()
664 })
665 }
666
667 pub fn fetch_ecc_curves(&mut self) -> Result<Vec<TpmEccCurve>, TpmDeviceError> {
676 self.get_capability(
677 TpmCap::EccCurves,
678 0,
679 Self::CAPABILITY_PAGE_SIZE,
680 |caps| match caps {
681 TpmuCapabilities::EccCurves(curves) => Ok(curves),
682 _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::EccCurves)),
683 },
684 |last| u32::from(last.value()) + 1,
685 )
686 }
687
688 pub fn fetch_pcr_bank_list(
703 &mut self,
704 ) -> Result<(Vec<TpmAlgId>, TpmsPcrSelect), TpmDeviceError> {
705 let pcrs: Vec<TpmsPcrSelection> = self.get_capability(
706 TpmCap::Pcrs,
707 0,
708 Self::CAPABILITY_PAGE_SIZE,
709 |caps| match caps {
710 TpmuCapabilities::Pcrs(pcrs) => Ok(pcrs),
711 _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Pcrs)),
712 },
713 |last| last.hash as u32 + 1,
714 )?;
715
716 if pcrs.is_empty() {
717 return Err(TpmDeviceError::PcrBanksNotAvailable);
718 }
719
720 let mut common_select: Option<TpmsPcrSelect> = None;
721 let mut algs = Vec::with_capacity(pcrs.len());
722
723 for bank in pcrs {
724 if bank.pcr_select.iter().all(|&b| b == 0) {
725 debug!(
726 "skipping unallocated bank {:?} (mask: {})",
727 bank.hash,
728 hex::encode(&*bank.pcr_select)
729 );
730 continue;
731 }
732
733 if let Some(ref select) = common_select {
734 if bank.pcr_select != *select {
735 return Err(TpmDeviceError::PcrBankSelectionMismatch);
736 }
737 } else {
738 common_select = Some(bank.pcr_select);
739 }
740 algs.push(bank.hash);
741 }
742
743 let select = common_select.ok_or(TpmDeviceError::PcrBanksNotAvailable)?;
744
745 algs.sort();
746 Ok((algs, select))
747 }
748
749 fn get_capability_page(
759 &mut self,
760 cap: TpmCap,
761 property: TpmUint32,
762 property_count: TpmUint32,
763 ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
764 let cmd = TpmGetCapabilityCommand {
765 cap,
766 property,
767 property_count,
768 handles: [],
769 };
770
771 let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
772 let body = response
773 .unmarshal::<TpmGetCapabilityResponse>()
774 .map_err(TpmDeviceError::Unmarshal)?;
775
776 Ok((body.more_data.into(), body.capability_data))
777 }
778
779 pub fn fetch_tpm_property(&mut self, property: TpmPt) -> Result<u32, TpmDeviceError> {
788 let (_, cap_data) = self.get_capability_page(
789 TpmCap::TpmProperties,
790 TpmUint32::from(property as u32),
791 TpmUint32::from(1),
792 )?;
793
794 let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
795 return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
796 };
797
798 let Some(prop) = props.iter().find(|prop| prop.property == property) else {
799 return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
800 };
801
802 Ok(prop.value.value())
803 }
804
805 pub fn read_public(
814 &mut self,
815 handle: TpmHandle,
816 ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
817 let cmd = TpmReadPublicCommand { handles: [handle] };
818 let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
819 let body = response
820 .unmarshal::<TpmReadPublicResponse>()
821 .map_err(TpmDeviceError::Unmarshal)?;
822
823 Ok((body.out_public.inner, body.name))
824 }
825
826 pub fn find_persistent(
835 &mut self,
836 target_name: &Tpm2bName,
837 ) -> Result<Option<TpmHandle>, TpmDeviceError> {
838 for handle in self.fetch_handles(TpmHt::Persistent)? {
839 match self.read_public(handle) {
840 Ok((_, name)) => {
841 if name == *target_name {
842 return Ok(Some(handle));
843 }
844 }
845 Err(TpmDeviceError::TpmRc(rc)) => {
846 let base = rc.base();
847 if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
848 continue;
849 }
850 return Err(TpmDeviceError::TpmRc(rc));
851 }
852 Err(e) => return Err(e),
853 }
854 }
855 Ok(None)
856 }
857
858 pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
867 let cmd = TpmContextSaveCommand {
868 handles: [save_handle],
869 };
870 let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
871 let body = response
872 .unmarshal::<TpmContextSaveResponse>()
873 .map_err(TpmDeviceError::Unmarshal)?;
874
875 Ok(body.context)
876 }
877
878 pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
887 let cmd = TpmContextLoadCommand {
888 context,
889 handles: [],
890 };
891 let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
892 let body = response
893 .unmarshal::<TpmContextLoadResponse>()
894 .map_err(TpmDeviceError::Unmarshal)?;
895 let [handle] = body.handles;
896
897 Ok(handle)
898 }
899
900 pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
908 let cmd = TpmFlushContextCommand {
909 flush_handle: handle,
910 handles: [],
911 };
912 self.transmit(&cmd, Self::NO_SESSIONS)?;
913 Ok(())
914 }
915
916 pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
923 match self.load_context(context) {
924 Ok(handle) => self.flush_context(handle),
925 Err(TpmDeviceError::TpmRc(rc)) => {
926 let base = rc.base();
927 if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
928 Ok(())
929 } else {
930 Err(TpmDeviceError::TpmRc(rc))
931 }
932 }
933 Err(e) => Err(e),
934 }
935 }
936}
937
938pub struct TpmResponder {
948 transport: Box<dyn TpmTransport>,
949 command: Vec<u8>,
950}
951
952impl std::fmt::Debug for TpmResponder {
953 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
954 f.debug_struct("TpmResponder").finish_non_exhaustive()
955 }
956}
957
958impl TpmResponder {
959 #[must_use]
961 pub fn new(transport: Box<dyn TpmTransport>) -> Self {
962 Self {
963 transport,
964 command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
965 }
966 }
967
968 pub fn recv_command(&mut self) -> Result<&[u8], TpmDeviceError> {
976 self.transport.recv(&mut self.command)?;
977 Ok(&self.command)
978 }
979
980 pub fn send_response(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
987 self.transport.send(frame)
988 }
989
990 pub fn serve<H>(&mut self, mut handler: H) -> Result<(), TpmDeviceError>
1002 where
1003 H: FnMut(&[u8]) -> Vec<u8>,
1004 {
1005 loop {
1006 match self.transport.recv(&mut self.command) {
1007 Ok(()) => {}
1008 Err(TpmDeviceError::UnexpectedEof) => return Ok(()),
1009 Err(e) => return Err(e),
1010 }
1011 let response = handler(&self.command);
1012 self.transport.send(&response)?;
1013 }
1014 }
1015}
1016
1017pub struct TpmPolicySessionBuilder {
1019 bind: TpmHandle,
1020 tpm_key: TpmHandle,
1021 nonce_caller: Option<Tpm2bNonce>,
1022 encrypted_salt: Option<Tpm2bEncryptedSecret>,
1023 session_type: TpmSe,
1024 symmetric: TpmtSymDefObject,
1025 auth_hash: TpmAlgId,
1026}
1027
1028impl Default for TpmPolicySessionBuilder {
1029 fn default() -> Self {
1030 Self {
1031 bind: (TpmRh::Null as u32).into(),
1032 tpm_key: (TpmRh::Null as u32).into(),
1033 nonce_caller: None,
1034 encrypted_salt: None,
1035 session_type: TpmSe::Policy,
1036 symmetric: TpmtSymDefObject::default(),
1037 auth_hash: TpmAlgId::Sha256,
1038 }
1039 }
1040}
1041
1042impl TpmPolicySessionBuilder {
1043 #[must_use]
1044 pub fn new() -> Self {
1045 Self::default()
1046 }
1047
1048 #[must_use]
1049 pub fn with_bind(mut self, bind: TpmHandle) -> Self {
1050 self.bind = bind;
1051 self
1052 }
1053
1054 #[must_use]
1055 pub fn with_tpm_key(mut self, tpm_key: TpmHandle) -> Self {
1056 self.tpm_key = tpm_key;
1057 self
1058 }
1059
1060 #[must_use]
1061 pub fn with_nonce_caller(mut self, nonce: Tpm2bNonce) -> Self {
1062 self.nonce_caller = Some(nonce);
1063 self
1064 }
1065
1066 #[must_use]
1067 pub fn with_encrypted_salt(mut self, salt: Tpm2bEncryptedSecret) -> Self {
1068 self.encrypted_salt = Some(salt);
1069 self
1070 }
1071
1072 #[must_use]
1073 pub fn with_session_type(mut self, session_type: TpmSe) -> Self {
1074 self.session_type = session_type;
1075 self
1076 }
1077
1078 #[must_use]
1079 pub fn with_symmetric(mut self, symmetric: TpmtSymDefObject) -> Self {
1080 self.symmetric = symmetric;
1081 self
1082 }
1083
1084 #[must_use]
1085 pub fn with_auth_hash(mut self, auth_hash: TpmAlgId) -> Self {
1086 self.auth_hash = auth_hash;
1087 self
1088 }
1089
1090 pub fn open(self, device: &mut TpmDevice) -> Result<TpmPolicySession, TpmDeviceError> {
1101 let nonce_caller = if let Some(nonce) = self.nonce_caller {
1102 nonce
1103 } else {
1104 let digest_len = TpmHash::try_from(self.auth_hash)
1105 .map_err(|_| TpmDeviceError::UnsupportedAlgorithm(self.auth_hash))?
1106 .size();
1107 let mut nonce_bytes = vec![0; digest_len];
1108 thread_rng().fill_bytes(&mut nonce_bytes);
1109 Tpm2bNonce::try_from(nonce_bytes.as_slice()).map_err(TpmDeviceError::Unmarshal)?
1110 };
1111
1112 let cmd = TpmStartAuthSessionCommand {
1113 nonce_caller,
1114 encrypted_salt: self.encrypted_salt.unwrap_or_default(),
1115 session_type: self.session_type,
1116 symmetric: self.symmetric,
1117 auth_hash: self.auth_hash,
1118 handles: [self.tpm_key, self.bind],
1119 };
1120
1121 let response = device.transmit(&cmd, TpmDevice::NO_SESSIONS)?;
1122 let body = response
1123 .unmarshal::<TpmStartAuthSessionResponse>()
1124 .map_err(TpmDeviceError::Unmarshal)?;
1125 let [handle] = body.handles;
1126 let nonce_tpm = body.nonce_tpm;
1127
1128 Ok(TpmPolicySession {
1129 handle,
1130 attributes: TpmaSession::CONTINUE_SESSION,
1131 hash_alg: self.auth_hash,
1132 nonce_tpm,
1133 })
1134 }
1135}
1136
1137#[derive(Debug, Clone)]
1139pub struct TpmPolicySession {
1140 handle: TpmHandle,
1141 attributes: TpmaSession,
1142 hash_alg: TpmAlgId,
1143 nonce_tpm: Tpm2bNonce,
1144}
1145
1146impl TpmPolicySession {
1147 #[must_use]
1149 pub fn builder() -> TpmPolicySessionBuilder {
1150 TpmPolicySessionBuilder::new()
1151 }
1152
1153 #[must_use]
1155 pub fn handle(&self) -> TpmHandle {
1156 self.handle
1157 }
1158
1159 #[must_use]
1161 pub fn attributes(&self) -> TpmaSession {
1162 self.attributes
1163 }
1164
1165 #[must_use]
1167 pub fn hash_alg(&self) -> TpmAlgId {
1168 self.hash_alg
1169 }
1170
1171 #[must_use]
1173 pub fn nonce_tpm(&self) -> &Tpm2bNonce {
1174 &self.nonce_tpm
1175 }
1176
1177 pub fn run(
1190 &self,
1191 device: &mut TpmDevice,
1192 commands: impl IntoIterator<Item = (TpmCommand, TpmAuthCommands)>,
1193 ) -> Result<(), TpmDeviceError> {
1194 for (mut command_body, auth_sessions) in commands {
1195 let session_handle = self.handle;
1200 match &mut command_body {
1201 TpmCommand::PolicyPcr(cmd) => cmd.handles[0] = session_handle,
1202 TpmCommand::PolicyOr(cmd) => cmd.handles[0] = session_handle,
1203 TpmCommand::PolicyRestart(cmd) => cmd.handles[0] = session_handle,
1204 TpmCommand::PolicyAuthorize(cmd) => cmd.handles[0] = session_handle,
1205 TpmCommand::PolicyAuthValue(cmd) => cmd.handles[0] = session_handle,
1206 TpmCommand::PolicyCommandCode(cmd) => cmd.handles[0] = session_handle,
1207 TpmCommand::PolicyCounterTimer(cmd) => cmd.handles[0] = session_handle,
1208 TpmCommand::PolicyCpHash(cmd) => cmd.handles[0] = session_handle,
1209 TpmCommand::PolicyLocality(cmd) => cmd.handles[0] = session_handle,
1210 TpmCommand::PolicyNameHash(cmd) => cmd.handles[0] = session_handle,
1211 TpmCommand::PolicyTicket(cmd) => cmd.handles[0] = session_handle,
1212 TpmCommand::PolicyPhysicalPresence(cmd) => cmd.handles[0] = session_handle,
1213 TpmCommand::PolicyDuplicationSelect(cmd) => cmd.handles[0] = session_handle,
1214 TpmCommand::PolicyGetDigest(cmd) => cmd.handles[0] = session_handle,
1215 TpmCommand::PolicyPassword(cmd) => cmd.handles[0] = session_handle,
1216 TpmCommand::PolicyNvWritten(cmd) => cmd.handles[0] = session_handle,
1217 TpmCommand::PolicyTemplate(cmd) => cmd.handles[0] = session_handle,
1218 TpmCommand::PolicyCapability(cmd) => cmd.handles[0] = session_handle,
1219 TpmCommand::PolicyParameters(cmd) => cmd.handles[0] = session_handle,
1220 TpmCommand::PolicyTransportSpdm(cmd) => cmd.handles[0] = session_handle,
1221 TpmCommand::PolicySecret(cmd) => cmd.handles[1] = session_handle,
1222 TpmCommand::PolicySigned(cmd) => cmd.handles[1] = session_handle,
1223 TpmCommand::PolicyNv(cmd) => cmd.handles[2] = session_handle,
1224 TpmCommand::PolicyAuthorizeNv(cmd) => cmd.handles[2] = session_handle,
1225 TpmCommand::PolicyAcSendSelect(cmd) => cmd.handles[2] = session_handle,
1226 _ => {
1227 return Err(TpmDeviceError::InvalidCc(command_body.cc()));
1228 }
1229 }
1230 device.transmit(&command_body, auth_sessions.as_ref())?;
1231 }
1232 Ok(())
1233 }
1234
1235 pub fn flush(&self, device: &mut TpmDevice) -> Result<(), TpmDeviceError> {
1242 device.flush_context(self.handle)
1243 }
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248 use super::*;
1249 use std::cell::RefCell;
1250 use std::io::Cursor;
1251 use std::rc::Rc;
1252
1253 struct Duplex {
1254 input: Cursor<Vec<u8>>,
1255 output: Rc<RefCell<Vec<u8>>>,
1256 }
1257
1258 impl Read for Duplex {
1259 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1260 self.input.read(buf)
1261 }
1262 }
1263
1264 impl Write for Duplex {
1265 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1266 self.output.borrow_mut().extend_from_slice(buf);
1267 Ok(buf.len())
1268 }
1269
1270 fn flush(&mut self) -> std::io::Result<()> {
1271 Ok(())
1272 }
1273 }
1274
1275 fn frame(body: &[u8]) -> Vec<u8> {
1276 let size = u32::try_from(TPM_HEADER_SIZE + body.len()).unwrap();
1277 let mut frame = vec![0x80, 0x01];
1278 frame.extend_from_slice(&size.to_be_bytes());
1279 frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
1280 frame.extend_from_slice(body);
1281 frame
1282 }
1283
1284 #[test]
1285 fn read_frame_reads_exactly_one_frame() {
1286 let first = frame(&[0xAA, 0xBB]);
1287 let mut bytes = first.clone();
1288 bytes.extend_from_slice(&frame(&[0xCC]));
1289 let mut reader = Cursor::new(bytes);
1290
1291 let mut buf = Vec::new();
1292 read_frame(&mut reader, &mut buf).unwrap();
1293
1294 assert_eq!(buf, first);
1295 }
1296
1297 #[test]
1298 fn write_then_read_round_trips() {
1299 let expected = frame(&[1, 2, 3, 4]);
1300 let mut stream = Cursor::new(Vec::new());
1301 write_frame(&mut stream, &expected).unwrap();
1302 stream.set_position(0);
1303
1304 let mut buf = Vec::new();
1305 read_frame(&mut stream, &mut buf).unwrap();
1306
1307 assert_eq!(buf, expected);
1308 }
1309
1310 #[test]
1311 fn stream_transport_sends_and_receives() {
1312 let response = frame(&[0x11, 0x22]);
1313 let command = frame(&[0x33]);
1314 let output = Rc::new(RefCell::new(Vec::new()));
1315 let mut transport = TpmStreamTransport::new(Duplex {
1316 input: Cursor::new(response.clone()),
1317 output: Rc::clone(&output),
1318 });
1319
1320 transport.send(&command).unwrap();
1321 let mut buf = Vec::new();
1322 transport.recv(&mut buf).unwrap();
1323
1324 assert_eq!(buf, response);
1325 assert_eq!(*output.borrow(), command);
1326 }
1327
1328 #[test]
1329 fn responder_serves_commands_until_disconnect() {
1330 let command = frame(&[0x01]);
1331 let response = frame(&[0x02, 0x03]);
1332 let output = Rc::new(RefCell::new(Vec::new()));
1333 let transport = TpmStreamTransport::new(Duplex {
1334 input: Cursor::new(command.clone()),
1335 output: Rc::clone(&output),
1336 });
1337 let mut responder = TpmResponder::new(Box::new(transport));
1338
1339 let reply = response.clone();
1340 let mut served = 0;
1341 responder
1342 .serve(|cmd| {
1343 assert_eq!(cmd, command.as_slice());
1344 served += 1;
1345 reply.clone()
1346 })
1347 .unwrap();
1348
1349 assert_eq!(served, 1);
1350 assert_eq!(*output.borrow(), response);
1351 }
1352
1353 #[test]
1354 fn frame_size_rejects_short_header() {
1355 assert!(frame_size(&[0x80, 0x01, 0x00]).is_err());
1356 }
1357
1358 #[test]
1359 fn read_frame_reports_unexpected_eof_on_truncation() {
1360 let mut reader = Cursor::new(vec![0x80, 0x01]);
1361 let mut buf = Vec::new();
1362 assert_eq!(
1363 read_frame(&mut reader, &mut buf),
1364 Err(TpmDeviceError::UnexpectedEof)
1365 );
1366 }
1367}