Skip to main content

tpm2_protocol/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5//! # TPM 2.0 Protocol
6//!
7//! A library for marshaling and unmarshaling TCG TPM 2.0 protocol messages.
8//!
9//! ## Constraints
10//!
11//! * `alloc` is disallowed.
12//! * Dependencies are disallowed.
13//! * Developer dependencies are disallowed.
14//! * Panics are disallowed.
15//!
16//! ## Design Goals
17//!
18//! * The crate must compile with GNU make and rustc without any external
19//!   dependencies.
20
21#![cfg_attr(not(test), no_std)]
22#![deny(unsafe_op_in_unsafe_fn)]
23#![deny(clippy::all)]
24#![deny(clippy::undocumented_unsafe_blocks)]
25#![deny(clippy::pedantic)]
26#![recursion_limit = "256"]
27
28pub mod basic;
29pub mod constant;
30pub mod data;
31#[macro_use]
32pub mod r#macro;
33pub mod frame;
34
35/// A byte-backed TPM wire view.
36#[repr(transparent)]
37pub struct TpmWire([u8]);
38
39impl TpmWire {
40    /// Casts a byte slice into a TPM wire view.
41    #[must_use]
42    pub fn cast(buf: &[u8]) -> &Self {
43        // SAFETY: `TpmWire` accepts any byte slice as its backing storage.
44        unsafe { Self::cast_unchecked(buf) }
45    }
46
47    /// Casts a byte slice into a TPM wire view without validation.
48    ///
49    /// # Safety
50    ///
51    /// `TpmWire` has no additional validity requirements beyond the validity
52    /// of `buf`. Callers must still ensure any higher-level protocol
53    /// invariants required by later typed accessors have been validated.
54    #[must_use]
55    pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
56        let ptr = core::ptr::from_ref(buf) as *const Self;
57
58        // SAFETY: `TpmWire` is `repr(transparent)` over `[u8]`, so it has the
59        // same layout, metadata, and alignment as the referenced slice.
60        unsafe { &*ptr }
61    }
62
63    /// Casts a mutable byte slice into a mutable TPM wire view.
64    #[must_use]
65    pub fn cast_mut(buf: &mut [u8]) -> &mut Self {
66        // SAFETY: `TpmWire` accepts any mutable byte slice as its backing
67        // storage. The `&mut` input provides exclusive access.
68        unsafe { Self::cast_mut_unchecked(buf) }
69    }
70
71    /// Casts a mutable byte slice into a mutable TPM wire view without validation.
72    ///
73    /// # Safety
74    ///
75    /// `TpmWire` has no additional validity requirements beyond the validity
76    /// of `buf`. Callers must still ensure any higher-level protocol
77    /// invariants required by later typed accessors have been validated. The
78    /// returned reference inherits the exclusive access represented by `buf`.
79    #[must_use]
80    pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
81        let ptr = core::ptr::from_mut(buf) as *mut Self;
82
83        // SAFETY: `TpmWire` is `repr(transparent)` over `[u8]`, so it has the
84        // same layout, metadata, and alignment as the referenced slice.
85        unsafe { &mut *ptr }
86    }
87
88    /// Returns the backing bytes.
89    #[must_use]
90    pub const fn as_bytes(&self) -> &[u8] {
91        &self.0
92    }
93
94    /// Returns the mutable backing bytes.
95    #[must_use]
96    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
97        &mut self.0
98    }
99
100    /// Returns the number of backing bytes.
101    #[must_use]
102    pub const fn len(&self) -> usize {
103        self.0.len()
104    }
105
106    /// Returns `true` when the backing byte slice is empty.
107    #[must_use]
108    pub const fn is_empty(&self) -> bool {
109        self.0.is_empty()
110    }
111}
112
113impl AsRef<[u8]> for TpmWire {
114    fn as_ref(&self) -> &[u8] {
115        self.as_bytes()
116    }
117}
118
119impl AsMut<[u8]> for TpmWire {
120    fn as_mut(&mut self) -> &mut [u8] {
121        self.as_bytes_mut()
122    }
123}
124
125/// A byte-backed TPM wire view with a fixed byte length.
126#[repr(transparent)]
127pub struct TpmWireBytes<const N: usize>([u8; N]);
128
129impl<const N: usize> TpmWireBytes<N> {
130    /// Casts a byte slice into a fixed-size TPM wire view.
131    ///
132    /// # Errors
133    ///
134    /// Returns [`UnexpectedEnd`](crate::TpmProtocolError::UnexpectedEnd) when
135    /// `buf` is smaller than `N` bytes.
136    /// Returns [`TrailingData`](crate::TpmProtocolError::TrailingData) when
137    /// `buf` is larger than `N` bytes.
138    pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
139        if buf.len() < N {
140            return Err(TpmProtocolError::UnexpectedEnd);
141        }
142
143        if buf.len() > N {
144            return Err(TpmProtocolError::TrailingData);
145        }
146
147        // SAFETY: The length check above guarantees that `buf` has exactly the
148        // byte length required by `TpmWireBytes<N>`.
149        Ok(unsafe { Self::cast_unchecked(buf) })
150    }
151
152    /// Casts a byte slice into a fixed-size TPM wire view without validation.
153    ///
154    /// # Safety
155    ///
156    /// The caller must ensure that `buf.len() == N`. Callers must also ensure
157    /// any higher-level protocol invariants required by later typed accessors
158    /// have been validated.
159    #[must_use]
160    pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
161        let ptr = buf.as_ptr().cast::<Self>();
162
163        // SAFETY: `TpmWireBytes<N>` is `repr(transparent)` over `[u8; N]`, so it
164        // has the same layout and alignment. The caller guarantees exact size.
165        unsafe { &*ptr }
166    }
167
168    /// Casts a mutable byte slice into a fixed-size mutable TPM wire view.
169    ///
170    /// # Errors
171    ///
172    /// Returns [`UnexpectedEnd`](crate::TpmProtocolError::UnexpectedEnd) when
173    /// `buf` is smaller than `N` bytes.
174    /// Returns [`TrailingData`](crate::TpmProtocolError::TrailingData) when
175    /// `buf` is larger than `N` bytes.
176    pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
177        if buf.len() < N {
178            return Err(TpmProtocolError::UnexpectedEnd);
179        }
180
181        if buf.len() > N {
182            return Err(TpmProtocolError::TrailingData);
183        }
184
185        // SAFETY: The length check above guarantees that `buf` has exactly the
186        // byte length required by `TpmWireBytes<N>`. The `&mut` input provides
187        // exclusive access.
188        Ok(unsafe { Self::cast_mut_unchecked(buf) })
189    }
190
191    /// Casts a mutable byte slice into a fixed-size mutable TPM wire view without validation.
192    ///
193    /// # Safety
194    ///
195    /// The caller must ensure that `buf.len() == N`. Callers must also ensure
196    /// any higher-level protocol invariants required by later typed accessors
197    /// have been validated. The returned reference inherits the exclusive
198    /// access represented by `buf`.
199    #[must_use]
200    pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
201        let ptr = buf.as_mut_ptr().cast::<Self>();
202
203        // SAFETY: `TpmWireBytes<N>` is `repr(transparent)` over `[u8; N]`, so it
204        // has the same layout and alignment. The caller guarantees exact size.
205        unsafe { &mut *ptr }
206    }
207
208    /// Returns the backing bytes.
209    #[must_use]
210    pub const fn as_bytes(&self) -> &[u8; N] {
211        &self.0
212    }
213
214    /// Returns the mutable backing bytes.
215    #[must_use]
216    pub fn as_bytes_mut(&mut self) -> &mut [u8; N] {
217        &mut self.0
218    }
219
220    /// Returns the number of backing bytes.
221    #[must_use]
222    pub const fn len(&self) -> usize {
223        N
224    }
225
226    /// Returns `true` when the backing byte array is empty.
227    #[must_use]
228    pub const fn is_empty(&self) -> bool {
229        N == 0
230    }
231}
232
233impl<const N: usize> AsRef<[u8]> for TpmWireBytes<N> {
234    fn as_ref(&self) -> &[u8] {
235        self.as_bytes()
236    }
237}
238
239impl<const N: usize> AsMut<[u8]> for TpmWireBytes<N> {
240    fn as_mut(&mut self) -> &mut [u8] {
241        self.as_bytes_mut()
242    }
243}
244
245/// Casts caller-owned bytes into a TPM wire view.
246pub trait TpmCast {
247    /// Casts `buf` into `Self` after validating the wire-view invariants.
248    ///
249    /// # Errors
250    ///
251    /// Returns `Err(TpmProtocolError)` when `buf` does not satisfy the
252    /// invariants for `Self`.
253    fn cast(buf: &[u8]) -> TpmResult<&Self>;
254
255    /// Casts `buf` into `Self` without validating the wire-view invariants.
256    ///
257    /// # Safety
258    ///
259    /// The caller must ensure that `buf` satisfies the same invariants checked
260    /// by [`TpmCast::cast`].
261    unsafe fn cast_unchecked(buf: &[u8]) -> &Self;
262}
263
264/// Casts caller-owned mutable bytes into a mutable TPM wire view.
265pub trait TpmCastMut: TpmCast {
266    /// Casts `buf` into mutable `Self` after validating the wire-view invariants.
267    ///
268    /// # Errors
269    ///
270    /// Returns `Err(TpmProtocolError)` when `buf` does not satisfy the
271    /// invariants for `Self`.
272    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self>;
273
274    /// Casts `buf` into mutable `Self` without validating the wire-view invariants.
275    ///
276    /// # Safety
277    ///
278    /// The caller must ensure that `buf` satisfies the same invariants checked
279    /// by [`TpmCastMut::cast_mut`]. The returned reference inherits the
280    /// exclusive access represented by `buf`.
281    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self;
282}
283
284impl TpmCast for TpmWire {
285    fn cast(buf: &[u8]) -> TpmResult<&Self> {
286        Ok(Self::cast(buf))
287    }
288
289    unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
290        // SAFETY: The caller upholds the unchecked cast contract for `TpmWire`.
291        unsafe { Self::cast_unchecked(buf) }
292    }
293}
294
295impl TpmCastMut for TpmWire {
296    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
297        Ok(Self::cast_mut(buf))
298    }
299
300    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
301        // SAFETY: The caller upholds the unchecked mutable cast contract for
302        // `TpmWire`.
303        unsafe { Self::cast_mut_unchecked(buf) }
304    }
305}
306
307impl<const N: usize> TpmCast for TpmWireBytes<N> {
308    fn cast(buf: &[u8]) -> TpmResult<&Self> {
309        Self::cast(buf)
310    }
311
312    unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
313        // SAFETY: The caller upholds the unchecked cast contract for
314        // `TpmWireBytes<N>`.
315        unsafe { Self::cast_unchecked(buf) }
316    }
317}
318
319impl<const N: usize> TpmCastMut for TpmWireBytes<N> {
320    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
321        Self::cast_mut(buf)
322    }
323
324    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
325        // SAFETY: The caller upholds the unchecked mutable cast contract for
326        // `TpmWireBytes<N>`.
327        unsafe { Self::cast_mut_unchecked(buf) }
328    }
329}
330
331/// TPM frame marshaling and unmarshaling error type containing variants
332/// for all the possible error conditions.
333#[derive(Debug, PartialEq, Eq, Copy, Clone)]
334pub enum TpmProtocolError {
335    /// Trying to marshal more bytes than buffer has space. This is unexpected
336    /// situation, and should be considered possible bug in the crate itself.
337    BufferOverflow,
338
339    /// Integer overflow while converting to an integer of a different size.
340    IntegerTooLarge,
341
342    /// Boolean value was expected but the value is neither `0` nor `1`.
343    InvalidBoolean,
344
345    /// Non-existent command code encountered.
346    InvalidCc,
347
348    /// An [`TpmAttest`](crate::data::TpmAttest) instance contains an invalid
349    /// magic value.
350    InvalidMagicNumber,
351
352    /// Tag is neither [`Sessions`](crate::data::TpmSt::Sessions) nor
353    /// [`NoSessions`](crate::data::TpmSt::NoSessions).
354    InvalidTag,
355
356    /// Buffer contains more bytes than allowed by the TCG specifications.
357    TooManyBytes,
358
359    /// List contains more items than allowed by the TCG specifications.
360    TooManyItems,
361
362    /// Trailing data left after unmarshaling.
363    TrailingData,
364
365    /// Run out of bytes while unmarshaling.
366    UnexpectedEnd,
367
368    /// The variant accessed is not available.
369    VariantNotAvailable,
370}
371
372impl core::fmt::Display for TpmProtocolError {
373    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
374        match self {
375            Self::BufferOverflow => write!(f, "buffer overflow"),
376            Self::InvalidBoolean => write!(f, "invalid boolean value"),
377            Self::InvalidCc => write!(f, "invalid command code"),
378            Self::InvalidMagicNumber => write!(f, "invalid magic number"),
379            Self::InvalidTag => write!(f, "invalid tag"),
380            Self::IntegerTooLarge => write!(f, "integer overflow"),
381            Self::TooManyBytes => write!(f, "buffer capacity surpassed"),
382            Self::TooManyItems => write!(f, "list capaacity surpassed"),
383            Self::TrailingData => write!(f, "trailing data"),
384            Self::UnexpectedEnd => write!(f, "unexpected end"),
385            Self::VariantNotAvailable => write!(f, "enum variant is not available"),
386        }
387    }
388}
389
390impl core::error::Error for TpmProtocolError {}
391
392pub type TpmResult<T> = Result<T, TpmProtocolError>;
393
394/// Builds TPM wire bytes into a caller-provided mutable byte slice.
395pub struct TpmWriter<'a> {
396    buffer: &'a mut [u8],
397    cursor: usize,
398}
399
400impl<'a> TpmWriter<'a> {
401    /// Creates a new writer for the given buffer.
402    #[must_use]
403    pub fn new(buffer: &'a mut [u8]) -> Self {
404        Self { buffer, cursor: 0 }
405    }
406
407    /// Returns the number of bytes written so far.
408    #[must_use]
409    pub fn len(&self) -> usize {
410        self.cursor
411    }
412
413    /// Returns `true` if no bytes have been written.
414    #[must_use]
415    pub fn is_empty(&self) -> bool {
416        self.cursor == 0
417    }
418
419    /// Returns the bytes written so far.
420    #[must_use]
421    pub fn as_bytes(&self) -> &[u8] {
422        &self.buffer[..self.cursor]
423    }
424
425    /// Appends a slice of bytes to the writer.
426    ///
427    /// # Errors
428    ///
429    /// Returns [`OutOfMemory`](crate::TpmProtocolError::OutOfMemory)
430    /// when the capacity of the buffer is exceeded.
431    pub fn write_bytes(&mut self, bytes: &[u8]) -> TpmResult<()> {
432        let end = self
433            .cursor
434            .checked_add(bytes.len())
435            .ok_or(TpmProtocolError::BufferOverflow)?;
436
437        if end > self.buffer.len() {
438            return Err(TpmProtocolError::BufferOverflow);
439        }
440        self.buffer[self.cursor..end].copy_from_slice(bytes);
441        self.cursor = end;
442        Ok(())
443    }
444}
445
446/// Provides two ways to determine the size of an oBject: a compile-time maximum
447/// and a runtime exact size.
448pub trait TpmSized {
449    /// The estimated size of the object in its serialized form evaluated at
450    /// compile-time (always larger than the realized length).
451    const SIZE: usize;
452
453    /// Returns the exact serialized size of the object.
454    fn len(&self) -> usize;
455
456    /// Returns `true` if the object has a serialized length of zero.
457    fn is_empty(&self) -> bool {
458        self.len() == 0
459    }
460}
461
462pub trait TpmMarshal {
463    /// Marshals the object into the given writer.
464    ///
465    /// # Errors
466    ///
467    /// Returns `Err(TpmProtocolError)` on a marshal failure.
468    fn marshal(&self, writer: &mut TpmWriter) -> TpmResult<()>;
469}
470
471pub(crate) trait TpmUnmarshal: Sized + TpmSized {
472    /// Unmarshals an object from the given buffer.
473    ///
474    /// Returns the unmarshald type and the remaining portion of the buffer.
475    ///
476    /// # Errors
477    ///
478    /// Returns `Err(TpmProtocolError)` on a unmarshal failure.
479    fn unmarshal(buf: &[u8]) -> TpmResult<(Self, &[u8])>;
480}
481
482/// Types that are composed of a tag and a value e.g., a union.
483pub(crate) trait TpmTagged {
484    /// The type of the tag/discriminant.
485    type Tag: TpmUnmarshal + TpmMarshal + Copy;
486    /// The type of the value/union.
487    type Value;
488}
489
490/// Unmarshals a tagged object from a buffer.
491pub(crate) trait TpmUnmarshalTagged: Sized {
492    /// Unmarshals a tagged object from the given buffer using the provided tag.
493    ///
494    /// # Errors
495    ///
496    /// This method can return any error of the underlying type's `TpmUnmarshal` implementation,
497    /// such as a `TpmProtocolError::UnexpectedEnd` if the buffer is too small or an
498    /// `TpmProtocolError::MalformedValue` if the data is malformed.
499    fn unmarshal_tagged(tag: <Self as TpmTagged>::Tag, buf: &[u8]) -> TpmResult<(Self, &[u8])>
500    where
501        Self: TpmTagged,
502        <Self as TpmTagged>::Tag: TpmUnmarshal + TpmMarshal;
503}