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;
31mod error;
32#[macro_use]
33pub mod r#macro;
34pub mod frame;
35
36pub use self::error::{TpmError, TpmErrorValue, TpmResult};
37
38/// A byte-backed TPM wire view.
39#[repr(transparent)]
40pub struct TpmWire([u8]);
41
42impl TpmWire {
43 /// Casts a byte slice into a TPM wire view.
44 #[must_use]
45 pub fn cast(buf: &[u8]) -> &Self {
46 // SAFETY: `TpmWire` accepts any byte slice as its backing storage.
47 unsafe { Self::cast_unchecked(buf) }
48 }
49
50 /// Casts a byte slice into a TPM wire view without validation.
51 ///
52 /// # Safety
53 ///
54 /// `TpmWire` has no additional validity requirements beyond the validity
55 /// of `buf`. Callers must still ensure any higher-level protocol
56 /// invariants required by later typed accessors have been validated.
57 #[must_use]
58 pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
59 let ptr = core::ptr::from_ref(buf) as *const Self;
60
61 // SAFETY: `TpmWire` is `repr(transparent)` over `[u8]`, so it has the
62 // same layout, metadata, and alignment as the referenced slice.
63 unsafe { &*ptr }
64 }
65
66 /// Casts a mutable byte slice into a mutable TPM wire view.
67 #[must_use]
68 pub fn cast_mut(buf: &mut [u8]) -> &mut Self {
69 // SAFETY: `TpmWire` accepts any mutable byte slice as its backing
70 // storage. The `&mut` input provides exclusive access.
71 unsafe { Self::cast_mut_unchecked(buf) }
72 }
73
74 /// Casts a mutable byte slice into a mutable TPM wire view without validation.
75 ///
76 /// # Safety
77 ///
78 /// `TpmWire` has no additional validity requirements beyond the validity
79 /// of `buf`. Callers must still ensure any higher-level protocol
80 /// invariants required by later typed accessors have been validated. The
81 /// returned reference inherits the exclusive access represented by `buf`.
82 #[must_use]
83 pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
84 let ptr = core::ptr::from_mut(buf) as *mut Self;
85
86 // SAFETY: `TpmWire` is `repr(transparent)` over `[u8]`, so it has the
87 // same layout, metadata, and alignment as the referenced slice.
88 unsafe { &mut *ptr }
89 }
90
91 /// Returns the backing bytes.
92 #[must_use]
93 pub const fn as_bytes(&self) -> &[u8] {
94 &self.0
95 }
96
97 /// Returns the mutable backing bytes.
98 #[must_use]
99 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
100 &mut self.0
101 }
102
103 /// Returns the number of backing bytes.
104 #[must_use]
105 pub const fn len(&self) -> usize {
106 self.0.len()
107 }
108
109 /// Returns `true` when the backing byte slice is empty.
110 #[must_use]
111 pub const fn is_empty(&self) -> bool {
112 self.0.is_empty()
113 }
114}
115
116impl AsRef<[u8]> for TpmWire {
117 fn as_ref(&self) -> &[u8] {
118 self.as_bytes()
119 }
120}
121
122impl AsMut<[u8]> for TpmWire {
123 fn as_mut(&mut self) -> &mut [u8] {
124 self.as_bytes_mut()
125 }
126}
127
128/// A byte-backed TPM wire view with a fixed byte length.
129#[repr(transparent)]
130pub struct TpmWireBytes<const N: usize>([u8; N]);
131
132impl<const N: usize> TpmWireBytes<N> {
133 /// Casts a byte slice into a fixed-size TPM wire view.
134 ///
135 /// # Errors
136 ///
137 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
138 /// `buf` is smaller than `N` bytes.
139 /// Returns [`TrailingData`](crate::TpmError::TrailingData) when
140 /// `buf` is larger than `N` bytes.
141 pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
142 if buf.len() < N {
143 return Err(TpmError::UnexpectedEnd(
144 crate::TpmErrorValue::new(0).size(N, buf.len()),
145 ));
146 }
147
148 if buf.len() > N {
149 return Err(TpmError::TrailingData(
150 crate::TpmErrorValue::new(N).actual(buf.len() - N),
151 ));
152 }
153
154 // SAFETY: The length check above guarantees that `buf` has exactly the
155 // byte length required by `TpmWireBytes<N>`.
156 Ok(unsafe { Self::cast_unchecked(buf) })
157 }
158
159 /// Casts a byte slice into a fixed-size TPM wire view without validation.
160 ///
161 /// # Safety
162 ///
163 /// The caller must ensure that `buf.len() == N`. Callers must also ensure
164 /// any higher-level protocol invariants required by later typed accessors
165 /// have been validated.
166 #[must_use]
167 pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
168 let ptr = buf.as_ptr().cast::<Self>();
169
170 // SAFETY: `TpmWireBytes<N>` is `repr(transparent)` over `[u8; N]`, so it
171 // has the same layout and alignment. The caller guarantees exact size.
172 unsafe { &*ptr }
173 }
174
175 /// Casts a mutable byte slice into a fixed-size mutable TPM wire view.
176 ///
177 /// # Errors
178 ///
179 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
180 /// `buf` is smaller than `N` bytes.
181 /// Returns [`TrailingData`](crate::TpmError::TrailingData) when
182 /// `buf` is larger than `N` bytes.
183 pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
184 if buf.len() < N {
185 return Err(TpmError::UnexpectedEnd(
186 crate::TpmErrorValue::new(0).size(N, buf.len()),
187 ));
188 }
189
190 if buf.len() > N {
191 return Err(TpmError::TrailingData(
192 crate::TpmErrorValue::new(N).actual(buf.len() - N),
193 ));
194 }
195
196 // SAFETY: The length check above guarantees that `buf` has exactly the
197 // byte length required by `TpmWireBytes<N>`. The `&mut` input provides
198 // exclusive access.
199 Ok(unsafe { Self::cast_mut_unchecked(buf) })
200 }
201
202 /// Casts a mutable byte slice into a fixed-size mutable TPM wire view without validation.
203 ///
204 /// # Safety
205 ///
206 /// The caller must ensure that `buf.len() == N`. Callers must also ensure
207 /// any higher-level protocol invariants required by later typed accessors
208 /// have been validated. The returned reference inherits the exclusive
209 /// access represented by `buf`.
210 #[must_use]
211 pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
212 let ptr = buf.as_mut_ptr().cast::<Self>();
213
214 // SAFETY: `TpmWireBytes<N>` is `repr(transparent)` over `[u8; N]`, so it
215 // has the same layout and alignment. The caller guarantees exact size.
216 unsafe { &mut *ptr }
217 }
218
219 /// Returns the backing bytes.
220 #[must_use]
221 pub const fn as_bytes(&self) -> &[u8; N] {
222 &self.0
223 }
224
225 /// Returns the mutable backing bytes.
226 #[must_use]
227 pub fn as_bytes_mut(&mut self) -> &mut [u8; N] {
228 &mut self.0
229 }
230
231 /// Returns the number of backing bytes.
232 #[must_use]
233 pub const fn len(&self) -> usize {
234 N
235 }
236
237 /// Returns `true` when the backing byte array is empty.
238 #[must_use]
239 pub const fn is_empty(&self) -> bool {
240 N == 0
241 }
242}
243
244impl<const N: usize> AsRef<[u8]> for TpmWireBytes<N> {
245 fn as_ref(&self) -> &[u8] {
246 self.as_bytes()
247 }
248}
249
250impl<const N: usize> AsMut<[u8]> for TpmWireBytes<N> {
251 fn as_mut(&mut self) -> &mut [u8] {
252 self.as_bytes_mut()
253 }
254}
255
256/// Casts caller-owned bytes into a TPM wire view.
257pub trait TpmCast {
258 /// Casts `buf` into `Self` after validating the wire-view invariants.
259 ///
260 /// # Errors
261 ///
262 /// Returns `Err(TpmError)` when `buf` does not satisfy the
263 /// invariants for `Self`.
264 fn cast(buf: &[u8]) -> TpmResult<&Self>;
265
266 /// Casts `buf` into `Self` without validating the wire-view invariants.
267 ///
268 /// # Safety
269 ///
270 /// The caller must ensure that `buf` satisfies the same invariants checked
271 /// by [`TpmCast::cast`].
272 unsafe fn cast_unchecked(buf: &[u8]) -> &Self;
273}
274
275/// Casts caller-owned mutable bytes into a mutable TPM wire view.
276pub trait TpmCastMut: TpmCast {
277 /// Casts `buf` into mutable `Self` after validating the wire-view invariants.
278 ///
279 /// # Errors
280 ///
281 /// Returns `Err(TpmError)` when `buf` does not satisfy the
282 /// invariants for `Self`.
283 fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self>;
284
285 /// Casts `buf` into mutable `Self` without validating the wire-view invariants.
286 ///
287 /// # Safety
288 ///
289 /// The caller must ensure that `buf` satisfies the same invariants checked
290 /// by [`TpmCastMut::cast_mut`]. The returned reference inherits the
291 /// exclusive access represented by `buf`.
292 unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self;
293}
294
295impl TpmCast for TpmWire {
296 fn cast(buf: &[u8]) -> TpmResult<&Self> {
297 Ok(Self::cast(buf))
298 }
299
300 unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
301 // SAFETY: The caller upholds the unchecked cast contract for `TpmWire`.
302 unsafe { Self::cast_unchecked(buf) }
303 }
304}
305
306impl TpmCastMut for TpmWire {
307 fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
308 Ok(Self::cast_mut(buf))
309 }
310
311 unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
312 // SAFETY: The caller upholds the unchecked mutable cast contract for
313 // `TpmWire`.
314 unsafe { Self::cast_mut_unchecked(buf) }
315 }
316}
317
318impl<const N: usize> TpmCast for TpmWireBytes<N> {
319 fn cast(buf: &[u8]) -> TpmResult<&Self> {
320 Self::cast(buf)
321 }
322
323 unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
324 // SAFETY: The caller upholds the unchecked cast contract for
325 // `TpmWireBytes<N>`.
326 unsafe { Self::cast_unchecked(buf) }
327 }
328}
329
330impl<const N: usize> TpmCastMut for TpmWireBytes<N> {
331 fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
332 Self::cast_mut(buf)
333 }
334
335 unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
336 // SAFETY: The caller upholds the unchecked mutable cast contract for
337 // `TpmWireBytes<N>`.
338 unsafe { Self::cast_mut_unchecked(buf) }
339 }
340}
341
342/// Builds TPM wire bytes into a caller-provided mutable byte slice.
343pub struct TpmWriter<'a> {
344 buffer: &'a mut [u8],
345 cursor: usize,
346}
347
348impl<'a> TpmWriter<'a> {
349 /// Creates a new writer for the given buffer.
350 #[must_use]
351 pub fn new(buffer: &'a mut [u8]) -> Self {
352 Self { buffer, cursor: 0 }
353 }
354
355 /// Returns the number of bytes written so far.
356 #[must_use]
357 pub fn len(&self) -> usize {
358 self.cursor
359 }
360
361 /// Returns `true` if no bytes have been written.
362 #[must_use]
363 pub fn is_empty(&self) -> bool {
364 self.cursor == 0
365 }
366
367 /// Returns the bytes written so far.
368 #[must_use]
369 pub fn as_bytes(&self) -> &[u8] {
370 &self.buffer[..self.cursor]
371 }
372
373 /// Appends a slice of bytes to the writer.
374 ///
375 /// # Errors
376 ///
377 /// Returns [`BufferOverflow`](crate::TpmError::BufferOverflow) when the
378 /// capacity of the buffer is exceeded.
379 pub fn write_bytes(&mut self, bytes: &[u8]) -> TpmResult<()> {
380 let end = self
381 .cursor
382 .checked_add(bytes.len())
383 .ok_or(TpmError::BufferOverflow(
384 crate::TpmErrorValue::new(self.cursor).size(bytes.len(), 0),
385 ))?;
386
387 if end > self.buffer.len() {
388 return Err(TpmError::BufferOverflow(
389 crate::TpmErrorValue::new(self.cursor)
390 .size(bytes.len(), self.buffer.len().saturating_sub(self.cursor)),
391 ));
392 }
393 self.buffer[self.cursor..end].copy_from_slice(bytes);
394 self.cursor = end;
395 Ok(())
396 }
397}
398
399/// Provides two ways to determine the size of an oBject: a compile-time maximum
400/// and a runtime exact size.
401pub trait TpmSized {
402 /// The estimated size of the object in its serialized form evaluated at
403 /// compile-time (always larger than the realized length).
404 const SIZE: usize;
405
406 /// Returns the exact serialized size of the object.
407 fn len(&self) -> usize;
408
409 /// Returns `true` if the object has a serialized length of zero.
410 fn is_empty(&self) -> bool {
411 self.len() == 0
412 }
413}
414
415pub trait TpmMarshal {
416 /// Marshals the object into the given writer.
417 ///
418 /// # Errors
419 ///
420 /// Returns `Err(TpmError)` on a marshal failure.
421 fn marshal(&self, writer: &mut TpmWriter) -> TpmResult<()>;
422}
423
424pub(crate) trait TpmUnmarshal: Sized + TpmSized {
425 /// Unmarshals an object from the given buffer.
426 ///
427 /// Returns the unmarshald type and the remaining portion of the buffer.
428 ///
429 /// # Errors
430 ///
431 /// Returns `Err(TpmError)` on a unmarshal failure.
432 fn unmarshal(buf: &[u8]) -> TpmResult<(Self, &[u8])>;
433}
434
435/// Types that are composed of a tag and a value e.g., a union.
436pub(crate) trait TpmTagged {
437 /// The type of the tag/discriminant.
438 type Tag: TpmUnmarshal + TpmMarshal + Copy;
439 /// The type of the value/union.
440 type Value;
441}
442
443/// Unmarshals a tagged object from a buffer.
444pub(crate) trait TpmUnmarshalTagged: Sized {
445 /// Unmarshals a tagged object from the given buffer using the provided tag.
446 ///
447 /// # Errors
448 ///
449 /// This method can return any error of the underlying type's `TpmUnmarshal` implementation,
450 /// such as a `TpmError::UnexpectedEnd` if the buffer is too small or a
451 /// `TpmError::VariantNotAvailable` if a tagged variant is unavailable.
452 fn unmarshal_tagged(tag: <Self as TpmTagged>::Tag, buf: &[u8]) -> TpmResult<(Self, &[u8])>
453 where
454 Self: TpmTagged,
455 <Self as TpmTagged>::Tag: TpmUnmarshal + TpmMarshal;
456}