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}