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//! ## Zero-Copy Contract
22//!
23//! Read-side protocol APIs operate on caller-owned byte slices and return
24//! borrowed wire views into those slices. Implementations must not copy payload
25//! bytes to inspect frames or nested TPM values. Scalar fields may be read by
26//! value from their big-endian wire representation.
27//!
28//! Validation must prove all exposed borrowed views are bounded by the original
29//! input slice. Any malformed length, tag, selector, or trailing byte condition
30//! must be reported as [`TpmError`] instead of panicking.
31//!
32//! The crate does not use the external `zerocopy` crate.
33
34#![cfg_attr(not(test), no_std)]
35#![deny(unsafe_op_in_unsafe_fn)]
36#![deny(clippy::all)]
37#![deny(clippy::undocumented_unsafe_blocks)]
38#![deny(clippy::pedantic)]
39#![recursion_limit = "256"]
40
41pub mod basic;
42pub mod constant;
43pub mod data;
44mod error;
45#[macro_use]
46pub mod r#macro;
47pub mod frame;
48
49pub use self::error::{TpmError, TpmErrorValue, TpmResult};
50
51/// A byte-backed TPM wire view.
52#[repr(transparent)]
53pub struct TpmWire([u8]);
54
55impl TpmWire {
56 /// Casts a byte slice into a TPM wire view.
57 #[must_use]
58 pub fn cast(buf: &[u8]) -> &Self {
59 // SAFETY: `TpmWire` accepts any byte slice as its backing storage.
60 unsafe { Self::cast_unchecked(buf) }
61 }
62
63 /// Casts a byte slice into a TPM wire view and returns no remainder.
64 #[must_use]
65 pub fn cast_prefix(buf: &[u8]) -> (&Self, &[u8]) {
66 (Self::cast(buf), &buf[buf.len()..])
67 }
68
69 /// Casts a byte slice into a TPM wire view without validation.
70 ///
71 /// # Safety
72 ///
73 /// `TpmWire` has no additional validity requirements beyond the validity
74 /// of `buf`. Callers must still ensure any higher-level protocol
75 /// invariants required by later typed accessors have been validated.
76 #[must_use]
77 pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
78 let ptr = core::ptr::from_ref(buf) as *const Self;
79
80 // SAFETY: `TpmWire` is `repr(transparent)` over `[u8]`, so it has the
81 // same layout, metadata, and alignment as the referenced slice.
82 unsafe { &*ptr }
83 }
84
85 /// Casts a mutable byte slice into a mutable TPM wire view.
86 #[must_use]
87 pub fn cast_mut(buf: &mut [u8]) -> &mut Self {
88 // SAFETY: `TpmWire` accepts any mutable byte slice as its backing
89 // storage. The `&mut` input provides exclusive access.
90 unsafe { Self::cast_mut_unchecked(buf) }
91 }
92
93 /// Casts a mutable byte slice into a mutable TPM wire view and returns no remainder.
94 #[must_use]
95 pub fn cast_prefix_mut(buf: &mut [u8]) -> (&mut Self, &mut [u8]) {
96 let len = buf.len();
97 let (head, tail) = buf.split_at_mut(len);
98
99 (Self::cast_mut(head), tail)
100 }
101
102 /// Casts a mutable byte slice into a mutable TPM wire view without validation.
103 ///
104 /// # Safety
105 ///
106 /// `TpmWire` has no additional validity requirements beyond the validity
107 /// of `buf`. Callers must still ensure any higher-level protocol
108 /// invariants required by later typed accessors have been validated. The
109 /// returned reference inherits the exclusive access represented by `buf`.
110 #[must_use]
111 pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
112 let ptr = core::ptr::from_mut(buf) as *mut Self;
113
114 // SAFETY: `TpmWire` is `repr(transparent)` over `[u8]`, so it has the
115 // same layout, metadata, and alignment as the referenced slice.
116 unsafe { &mut *ptr }
117 }
118
119 /// Returns the backing bytes.
120 #[must_use]
121 pub const fn as_bytes(&self) -> &[u8] {
122 &self.0
123 }
124
125 /// Returns the mutable backing bytes.
126 #[must_use]
127 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
128 &mut self.0
129 }
130
131 /// Returns the number of backing bytes.
132 #[must_use]
133 pub const fn len(&self) -> usize {
134 self.0.len()
135 }
136
137 /// Returns `true` when the backing byte slice is empty.
138 #[must_use]
139 pub const fn is_empty(&self) -> bool {
140 self.0.is_empty()
141 }
142}
143
144impl AsRef<[u8]> for TpmWire {
145 fn as_ref(&self) -> &[u8] {
146 self.as_bytes()
147 }
148}
149
150impl AsMut<[u8]> for TpmWire {
151 fn as_mut(&mut self) -> &mut [u8] {
152 self.as_bytes_mut()
153 }
154}
155
156/// A byte-backed TPM wire view with a fixed byte length.
157#[repr(transparent)]
158pub struct TpmWireBytes<const N: usize>([u8; N]);
159
160impl<const N: usize> TpmWireBytes<N> {
161 /// Casts a byte slice into a fixed-size TPM wire view.
162 ///
163 /// # Errors
164 ///
165 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
166 /// `buf` is smaller than `N` bytes.
167 /// Returns [`TrailingData`](crate::TpmError::TrailingData) when
168 /// `buf` is larger than `N` bytes.
169 pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
170 Self::validate(buf)?;
171
172 // SAFETY: The validation above guarantees that `buf` has exactly the
173 // byte length required by `TpmWireBytes<N>`.
174 Ok(unsafe { Self::cast_unchecked(buf) })
175 }
176
177 /// Validates an exact fixed-size TPM wire view.
178 ///
179 /// # Errors
180 ///
181 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
182 /// `buf` is smaller than `N` bytes.
183 /// Returns [`TrailingData`](crate::TpmError::TrailingData) when
184 /// `buf` is larger than `N` bytes.
185 pub fn validate(buf: &[u8]) -> TpmResult<()> {
186 Self::validate_prefix(buf)?;
187
188 if buf.len() > N {
189 return Err(TpmError::TrailingData(
190 crate::TpmErrorValue::new(N).actual(buf.len() - N),
191 ));
192 }
193
194 Ok(())
195 }
196
197 /// Validates that `buf` starts with a fixed-size TPM wire view.
198 ///
199 /// # Errors
200 ///
201 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
202 /// `buf` is smaller than `N` bytes.
203 pub fn validate_prefix(buf: &[u8]) -> TpmResult<()> {
204 if buf.len() < N {
205 return Err(TpmError::UnexpectedEnd(
206 crate::TpmErrorValue::new(0).size(N, buf.len()),
207 ));
208 }
209
210 Ok(())
211 }
212
213 /// Casts the first `N` bytes into a fixed-size TPM wire view.
214 ///
215 /// # Errors
216 ///
217 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
218 /// `buf` is smaller than `N` bytes.
219 pub fn cast_prefix(buf: &[u8]) -> TpmResult<(&Self, &[u8])> {
220 Self::validate_prefix(buf)?;
221
222 let (head, tail) = buf.split_at(N);
223
224 // SAFETY: The validation above guarantees that `head` has exactly
225 // the byte length required by `TpmWireBytes<N>`.
226 Ok((unsafe { Self::cast_unchecked(head) }, tail))
227 }
228
229 /// Casts a byte slice into a fixed-size TPM wire view without validation.
230 ///
231 /// # Safety
232 ///
233 /// The caller must ensure that `buf.len() == N`. Callers must also ensure
234 /// any higher-level protocol invariants required by later typed accessors
235 /// have been validated.
236 #[must_use]
237 pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
238 let ptr = buf.as_ptr().cast::<Self>();
239
240 // SAFETY: `TpmWireBytes<N>` is `repr(transparent)` over `[u8; N]`, so it
241 // has the same layout and alignment. The caller guarantees exact size.
242 unsafe { &*ptr }
243 }
244
245 /// Casts a mutable byte slice into a fixed-size mutable TPM wire view.
246 ///
247 /// # Errors
248 ///
249 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
250 /// `buf` is smaller than `N` bytes.
251 /// Returns [`TrailingData`](crate::TpmError::TrailingData) when
252 /// `buf` is larger than `N` bytes.
253 pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
254 Self::validate(buf)?;
255
256 // SAFETY: The validation above guarantees that `buf` has exactly the
257 // byte length required by `TpmWireBytes<N>`. The `&mut` input provides
258 // exclusive access.
259 Ok(unsafe { Self::cast_mut_unchecked(buf) })
260 }
261
262 /// Casts the first `N` mutable bytes into a fixed-size TPM wire view.
263 ///
264 /// # Errors
265 ///
266 /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
267 /// `buf` is smaller than `N` bytes.
268 pub fn cast_prefix_mut(buf: &mut [u8]) -> TpmResult<(&mut Self, &mut [u8])> {
269 Self::validate_prefix(buf)?;
270
271 let (head, tail) = buf.split_at_mut(N);
272
273 // SAFETY: The validation above guarantees that `head` has exactly
274 // the byte length required by `TpmWireBytes<N>`.
275 Ok((unsafe { Self::cast_mut_unchecked(head) }, tail))
276 }
277
278 /// Casts a mutable byte slice into a fixed-size mutable TPM wire view without validation.
279 ///
280 /// # Safety
281 ///
282 /// The caller must ensure that `buf.len() == N`. Callers must also ensure
283 /// any higher-level protocol invariants required by later typed accessors
284 /// have been validated. The returned reference inherits the exclusive
285 /// access represented by `buf`.
286 #[must_use]
287 pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
288 let ptr = buf.as_mut_ptr().cast::<Self>();
289
290 // SAFETY: `TpmWireBytes<N>` is `repr(transparent)` over `[u8; N]`, so it
291 // has the same layout and alignment. The caller guarantees exact size.
292 unsafe { &mut *ptr }
293 }
294
295 /// Returns the backing bytes.
296 #[must_use]
297 pub const fn as_bytes(&self) -> &[u8; N] {
298 &self.0
299 }
300
301 /// Returns the mutable backing bytes.
302 #[must_use]
303 pub fn as_bytes_mut(&mut self) -> &mut [u8; N] {
304 &mut self.0
305 }
306
307 /// Returns the number of backing bytes.
308 #[must_use]
309 pub const fn len(&self) -> usize {
310 N
311 }
312
313 /// Returns `true` when the backing byte array is empty.
314 #[must_use]
315 pub const fn is_empty(&self) -> bool {
316 N == 0
317 }
318}
319
320impl<const N: usize> AsRef<[u8]> for TpmWireBytes<N> {
321 fn as_ref(&self) -> &[u8] {
322 self.as_bytes()
323 }
324}
325
326impl<const N: usize> AsMut<[u8]> for TpmWireBytes<N> {
327 fn as_mut(&mut self) -> &mut [u8] {
328 self.as_bytes_mut()
329 }
330}
331
332/// Casts caller-owned bytes into a TPM wire view.
333pub trait TpmCast {
334 /// Casts `buf` into `Self` after validating the wire-view invariants.
335 ///
336 /// # Errors
337 ///
338 /// Returns `Err(TpmError)` when `buf` does not satisfy the
339 /// invariants for `Self`.
340 fn cast(buf: &[u8]) -> TpmResult<&Self>;
341
342 /// Casts the first wire value in `buf` into `Self` and returns the remainder.
343 ///
344 /// # Errors
345 ///
346 /// Returns `Err(TpmError)` when `buf` does not start with a valid `Self`.
347 fn cast_prefix(buf: &[u8]) -> TpmResult<(&Self, &[u8])> {
348 let value = Self::cast(buf)?;
349
350 Ok((value, &buf[buf.len()..]))
351 }
352
353 /// Casts `buf` into `Self` without validating the wire-view invariants.
354 ///
355 /// # Safety
356 ///
357 /// The caller must ensure that `buf` satisfies the same invariants checked
358 /// by [`TpmCast::cast`].
359 unsafe fn cast_unchecked(buf: &[u8]) -> &Self;
360}
361
362/// Casts caller-owned mutable bytes into a mutable TPM wire view.
363pub trait TpmCastMut: TpmCast {
364 /// Casts `buf` into mutable `Self` after validating the wire-view invariants.
365 ///
366 /// # Errors
367 ///
368 /// Returns `Err(TpmError)` when `buf` does not satisfy the
369 /// invariants for `Self`.
370 fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self>;
371
372 /// Casts the first mutable wire value in `buf` into `Self` and returns the remainder.
373 ///
374 /// # Errors
375 ///
376 /// Returns `Err(TpmError)` when `buf` does not start with a valid `Self`.
377 fn cast_prefix_mut(buf: &mut [u8]) -> TpmResult<(&mut Self, &mut [u8])> {
378 let len = buf.len();
379 let (head, tail) = buf.split_at_mut(len);
380 let value = Self::cast_mut(head)?;
381
382 Ok((value, tail))
383 }
384
385 /// Casts `buf` into mutable `Self` without validating the wire-view invariants.
386 ///
387 /// # Safety
388 ///
389 /// The caller must ensure that `buf` satisfies the same invariants checked
390 /// by [`TpmCastMut::cast_mut`]. The returned reference inherits the
391 /// exclusive access represented by `buf`.
392 unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self;
393}
394
395/// Reads one field from a TPM wire structure.
396pub trait TpmField<'a> {
397 type View;
398
399 /// Reads the first field from `buf` and returns the remaining bytes.
400 ///
401 /// # Errors
402 ///
403 /// Returns `Err(TpmError)` when `buf` does not start with a valid field.
404 fn cast_prefix_field(buf: &'a [u8]) -> TpmResult<(Self::View, &'a [u8])>;
405}
406
407/// Reads a union field selected by a previously-read tag.
408pub trait TpmTaggedField<'a, Tag> {
409 type View;
410
411 /// Reads the tagged field from `buf` and returns the remaining bytes.
412 ///
413 /// # Errors
414 ///
415 /// Returns `Err(TpmError)` when `tag` does not select a valid variant or
416 /// `buf` does not start with a valid selected field.
417 fn cast_tagged_prefix_field(tag: Tag, buf: &'a [u8]) -> TpmResult<(Self::View, &'a [u8])>;
418}
419
420impl<'a, T: TpmCast + ?Sized + 'a> TpmField<'a> for T {
421 type View = &'a T;
422
423 fn cast_prefix_field(buf: &'a [u8]) -> TpmResult<(Self::View, &'a [u8])> {
424 T::cast_prefix(buf)
425 }
426}
427
428impl TpmCast for TpmWire {
429 fn cast(buf: &[u8]) -> TpmResult<&Self> {
430 Ok(Self::cast(buf))
431 }
432
433 fn cast_prefix(buf: &[u8]) -> TpmResult<(&Self, &[u8])> {
434 Ok(Self::cast_prefix(buf))
435 }
436
437 unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
438 // SAFETY: The caller upholds the unchecked cast contract for `TpmWire`.
439 unsafe { Self::cast_unchecked(buf) }
440 }
441}
442
443impl TpmCastMut for TpmWire {
444 fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
445 Ok(Self::cast_mut(buf))
446 }
447
448 fn cast_prefix_mut(buf: &mut [u8]) -> TpmResult<(&mut Self, &mut [u8])> {
449 Ok(Self::cast_prefix_mut(buf))
450 }
451
452 unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
453 // SAFETY: The caller upholds the unchecked mutable cast contract for
454 // `TpmWire`.
455 unsafe { Self::cast_mut_unchecked(buf) }
456 }
457}
458
459impl<const N: usize> TpmCast for TpmWireBytes<N> {
460 fn cast(buf: &[u8]) -> TpmResult<&Self> {
461 Self::cast(buf)
462 }
463
464 fn cast_prefix(buf: &[u8]) -> TpmResult<(&Self, &[u8])> {
465 Self::cast_prefix(buf)
466 }
467
468 unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
469 // SAFETY: The caller upholds the unchecked cast contract for
470 // `TpmWireBytes<N>`.
471 unsafe { Self::cast_unchecked(buf) }
472 }
473}
474
475impl<const N: usize> TpmCastMut for TpmWireBytes<N> {
476 fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
477 Self::cast_mut(buf)
478 }
479
480 fn cast_prefix_mut(buf: &mut [u8]) -> TpmResult<(&mut Self, &mut [u8])> {
481 Self::cast_prefix_mut(buf)
482 }
483
484 unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
485 // SAFETY: The caller upholds the unchecked mutable cast contract for
486 // `TpmWireBytes<N>`.
487 unsafe { Self::cast_mut_unchecked(buf) }
488 }
489}
490
491/// Builds TPM wire bytes into a caller-provided mutable byte slice.
492pub struct TpmWriter<'a> {
493 buffer: &'a mut [u8],
494 cursor: usize,
495}
496
497impl<'a> TpmWriter<'a> {
498 /// Creates a new writer for the given buffer.
499 #[must_use]
500 pub fn new(buffer: &'a mut [u8]) -> Self {
501 Self { buffer, cursor: 0 }
502 }
503
504 /// Returns the number of bytes written so far.
505 #[must_use]
506 pub fn len(&self) -> usize {
507 self.cursor
508 }
509
510 /// Returns `true` if no bytes have been written.
511 #[must_use]
512 pub fn is_empty(&self) -> bool {
513 self.cursor == 0
514 }
515
516 /// Returns the bytes written so far.
517 #[must_use]
518 pub fn as_bytes(&self) -> &[u8] {
519 &self.buffer[..self.cursor]
520 }
521
522 /// Appends a slice of bytes to the writer.
523 ///
524 /// # Errors
525 ///
526 /// Returns [`BufferOverflow`](crate::TpmError::BufferOverflow) when the
527 /// capacity of the buffer is exceeded.
528 pub fn write_bytes(&mut self, bytes: &[u8]) -> TpmResult<()> {
529 let end = self
530 .cursor
531 .checked_add(bytes.len())
532 .ok_or(TpmError::BufferOverflow(
533 crate::TpmErrorValue::new(self.cursor).size(bytes.len(), 0),
534 ))?;
535
536 if end > self.buffer.len() {
537 return Err(TpmError::BufferOverflow(
538 crate::TpmErrorValue::new(self.cursor)
539 .size(bytes.len(), self.buffer.len().saturating_sub(self.cursor)),
540 ));
541 }
542 self.buffer[self.cursor..end].copy_from_slice(bytes);
543 self.cursor = end;
544 Ok(())
545 }
546}
547
548/// Provides two ways to determine the size of an oBject: a compile-time maximum
549/// and a runtime exact size.
550pub trait TpmSized {
551 /// The estimated size of the object in its serialized form evaluated at
552 /// compile-time (always larger than the realized length).
553 const SIZE: usize;
554
555 /// Returns the exact serialized size of the object.
556 fn len(&self) -> usize;
557
558 /// Returns `true` if the object has a serialized length of zero.
559 fn is_empty(&self) -> bool {
560 self.len() == 0
561 }
562}
563
564pub trait TpmMarshal {
565 /// Marshals the object into the given writer.
566 ///
567 /// # Errors
568 ///
569 /// Returns `Err(TpmError)` on a marshal failure.
570 fn marshal(&self, writer: &mut TpmWriter) -> TpmResult<()>;
571}