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 building and parsing TCG TPM 2.0 protocol messages.
8//!
9//! ## Implementation constraints
10//!
11//! 1. The use of `alloc::*` is denied.
12//! 2. Minimize unsafe code but can be used for appropriate reasons.
13//! 3. Panics are not allowed. For unresolvable scenarios use
14//!    `TpmError::InternalError`.
15//! 4. Only Rustdoc comments, no inline comments allowed. I.e. only comments
16//!    that are related on generating documentation are allowed.
17//! 5. SPDX and Copyright lines are required and exempt from this rule.
18//! 6. Enum variants and struct fields follow idiomatic Rust naming conventions
19//!    (e.g., `PascalCase` and `snake_case` respectively).
20//! 7. The string representation for all enums, used for serialization and
21//!    parsing, must strictly follow the TCG specification's `UPPER_SNAKE_CASE`
22//!    naming (e.g., "`TPM_ALG_SHA256`").
23
24#![cfg_attr(not(test), no_std)]
25#![deny(unsafe_code)]
26#![deny(clippy::all)]
27#![deny(clippy::pedantic)]
28
29#[macro_use]
30pub mod r#macro;
31pub mod data;
32pub mod message;
33
34use crate::data::TpmAlgId;
35use core::{convert::TryFrom, fmt, mem::size_of, ops::Deref, result::Result};
36
37tpm_handle!(
38    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
39    TpmTransient
40);
41tpm_handle!(
42    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
43    TpmSession
44);
45tpm_handle!(
46    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
47    TpmPersistent
48);
49
50/// The maximum size of a TPM command or response buffer.
51pub const TPM_MAX_COMMAND_SIZE: usize = 4096;
52
53#[derive(Debug, PartialEq, Eq)]
54pub enum TpmErrorKind {
55    /// Insufficient amount of bytes available
56    Boundary,
57    /// Trailing data after parsing
58    TrailingData,
59    /// Not a valid discriminant for the target enum
60    InvalidDiscriminant { type_name: &'static str, value: u64 },
61    /// Invalid magic number for the data
62    InvalidMagic { expected: u32, got: u32 },
63    /// Invalid tag for the data
64    InvalidTag {
65        type_name: &'static str,
66        expected: u16,
67        got: u16,
68    },
69    /// Invalid value
70    InvalidValue,
71    /// A size or count in the buffer is larger than the maximum allowed value
72    ValueTooLarge,
73    /// A command requires an authorization session but none was provided
74    AuthMissing,
75    /// An unexpected internal error
76    InternalError,
77}
78
79impl fmt::Display for TpmErrorKind {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            Self::Boundary => write!(f, "Insufficient data in buffer"),
83            Self::TrailingData => write!(f, "Buffer has unexpected trailing data after parsing"),
84            Self::InvalidDiscriminant { type_name, value } => {
85                write!(f, "Invalid discriminant 0x{value:x} for type '{type_name}'")
86            }
87            Self::InvalidMagic { expected, got } => {
88                write!(
89                    f,
90                    "Invalid magic number: expected 0x{expected:x}, got 0x{got:x}"
91                )
92            }
93            Self::InvalidTag {
94                type_name,
95                expected,
96                got,
97            } => {
98                write!(
99                    f,
100                    "Invalid tag for {type_name}: expected 0x{expected:x}, got 0x{got:x}"
101                )
102            }
103            Self::InvalidValue => write!(f, "A value is invalid or out of the expected range"),
104            Self::ValueTooLarge => {
105                write!(
106                    f,
107                    "A size or count is larger than the maximum allowed value"
108                )
109            }
110            Self::AuthMissing => write!(f, "Command requires authorization but none was provided"),
111            Self::InternalError => write!(f, "An unexpected internal error occurred"),
112        }
113    }
114}
115
116impl From<core::num::TryFromIntError> for TpmErrorKind {
117    fn from(_: core::num::TryFromIntError) -> Self {
118        Self::InternalError
119    }
120}
121
122pub type TpmResult<T> = Result<T, TpmErrorKind>;
123
124/// Writes into a mutable byte slice.
125pub struct TpmWriter<'a> {
126    buffer: &'a mut [u8],
127    cursor: usize,
128}
129
130impl<'a> TpmWriter<'a> {
131    /// Creates a new writer for the given buffer.
132    #[must_use]
133    pub fn new(buffer: &'a mut [u8]) -> Self {
134        Self { buffer, cursor: 0 }
135    }
136
137    /// Returns the number of bytes written so far.
138    #[must_use]
139    pub fn len(&self) -> usize {
140        self.cursor
141    }
142
143    /// Returns `true` if no bytes have been written.
144    #[must_use]
145    pub fn is_empty(&self) -> bool {
146        self.cursor == 0
147    }
148
149    /// Appends a slice of bytes to the writer.
150    ///
151    /// # Errors
152    ///
153    /// Returns `TpmErrorKind::Boundary` if the writer does not have enough
154    /// capacity to hold the new bytes.
155    pub fn write_bytes(&mut self, bytes: &[u8]) -> TpmResult<()> {
156        let end = self.cursor + bytes.len();
157        if end > self.buffer.len() {
158            return Err(TpmErrorKind::Boundary);
159        }
160        self.buffer[self.cursor..end].copy_from_slice(bytes);
161        self.cursor = end;
162        Ok(())
163    }
164}
165
166/// Provides two ways to determine the size of an object: a compile-time maximum
167/// and a runtime exact size.
168pub trait TpmSized {
169    /// The estimated size of the object in its serialized form evaluated at
170    /// compile-time (always larger than the realized length).
171    const SIZE: usize;
172
173    /// Returns the exact serialized size of the object.
174    fn len(&self) -> usize;
175
176    /// Returns `true` if the object has a serialized length of zero.
177    fn is_empty(&self) -> bool {
178        self.len() == 0
179    }
180}
181
182pub trait TpmBuild {
183    /// Builds the object into the given writer.
184    ///
185    /// # Errors
186    ///
187    /// * `TpmErrorKind::ValueTooLarge` if the object contains a value that cannot be built.
188    /// * `TpmErrorKind::Boundary` if the writer runs out of space.
189    fn build(&self, writer: &mut TpmWriter) -> TpmResult<()>;
190}
191
192pub trait TpmParse<'a>: Sized {
193    /// Parses an object from the given buffer.
194    ///
195    /// Returns the parsed type and the remaining portion of the buffer.
196    ///
197    /// # Errors
198    ///
199    /// * `TpmErrorKind::Boundary` if the buffer is too small to contain the object.
200    /// * `TpmErrorKind::InvalidDiscriminant` if a value in the buffer is invalid for the target type.
201    fn parse(buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])>;
202}
203
204/// Types that are composed of a tag and a value e.g., a union.
205pub trait TpmTagged {
206    /// The type of the tag/discriminant.
207    type Tag: TpmBuild + TpmParse<'static> + Copy;
208    /// The type of the value/union.
209    type Value;
210}
211
212/// Parses a tagged object from a buffer.
213pub trait TpmParseTagged<'a>: Sized {
214    /// Parses a tagged object from the given buffer using the provided tag.
215    ///
216    /// # Errors
217    ///
218    /// This method can return any error of the underlying type's `TpmParse` implementation,
219    /// such as a `TpmErrorKind::Boundary` if the buffer is too small or an
220    /// `TpmErrorKind::InvalidValue` if the data is malformed.
221    fn parse_tagged(tag: <Self as TpmTagged>::Tag, buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])>
222    where
223        Self: TpmTagged,
224        <Self as TpmTagged>::Tag: TpmParse<'a>;
225}
226
227impl TpmSized for u8 {
228    const SIZE: usize = 1;
229    fn len(&self) -> usize {
230        1
231    }
232}
233
234impl TpmBuild for u8 {
235    fn build(&self, writer: &mut TpmWriter) -> TpmResult<()> {
236        writer.write_bytes(&[*self])
237    }
238}
239
240impl<'a> TpmParse<'a> for u8 {
241    fn parse(buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])> {
242        let (val, buf) = buf.split_first().ok_or(TpmErrorKind::Boundary)?;
243        Ok((*val, buf))
244    }
245}
246
247macro_rules! tpm_integer {
248    ($ty:ty) => {
249        impl<'a> TpmParse<'a> for $ty {
250            fn parse(buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])> {
251                let size = size_of::<$ty>();
252                if buf.len() < size {
253                    return Err(TpmErrorKind::Boundary);
254                }
255                let (bytes, buf) = buf.split_at(size);
256                let array = bytes.try_into().map_err(|_| TpmErrorKind::InternalError)?;
257                let val = <$ty>::from_be_bytes(array);
258                Ok((val, buf))
259            }
260        }
261
262        impl TpmBuild for $ty {
263            fn build(&self, writer: &mut TpmWriter) -> TpmResult<()> {
264                writer.write_bytes(&self.to_be_bytes())
265            }
266        }
267
268        impl TpmSized for $ty {
269            const SIZE: usize = size_of::<$ty>();
270            fn len(&self) -> usize {
271                Self::SIZE
272            }
273        }
274    };
275}
276
277tpm_integer!(i32);
278tpm_integer!(u16);
279tpm_integer!(u32);
280tpm_integer!(u64);
281
282/// Builds a TPM2B sized buffer.
283///
284/// # Errors
285///
286/// * `TpmErrorKind::ValueTooLarge` if the data slice is too large to fit in a `u16` length.
287pub fn build_tpm2b(writer: &mut TpmWriter, data: &[u8]) -> TpmResult<()> {
288    u16::try_from(data.len())
289        .map_err(|_| TpmErrorKind::ValueTooLarge)?
290        .build(writer)?;
291    writer.write_bytes(data)
292}
293
294/// Parses a TPM2B sized buffer.
295///
296/// # Errors
297///
298/// * `TpmErrorKind::Boundary` if the buffer is too small.
299/// * `TpmErrorKind::ValueTooLarge` if the size prefix exceeds `TPM_MAX_COMMAND_SIZE`.
300pub fn parse_tpm2b(buf: &[u8]) -> TpmResult<(&[u8], &[u8])> {
301    let (size, buf) = u16::parse(buf)?;
302    let size = size as usize;
303
304    if size > TPM_MAX_COMMAND_SIZE {
305        return Err(TpmErrorKind::ValueTooLarge);
306    }
307
308    if buf.len() < size {
309        return Err(TpmErrorKind::Boundary);
310    }
311    Ok(buf.split_at(size))
312}
313
314/// Returns the size of a hash digest in bytes for a given hash algorithm.
315#[must_use]
316pub const fn tpm_hash_size(alg_id: &TpmAlgId) -> Option<usize> {
317    match alg_id {
318        TpmAlgId::Sha1 => Some(20),
319        TpmAlgId::Sha256 | TpmAlgId::Sm3_256 => Some(32),
320        TpmAlgId::Sha384 => Some(48),
321        TpmAlgId::Sha512 => Some(64),
322        _ => None,
323    }
324}
325
326#[derive(Clone, Copy, PartialEq, Eq)]
327pub struct TpmBuffer<const CAPACITY: usize> {
328    bytes: [u8; CAPACITY],
329    len: u16,
330}
331
332impl<const CAPACITY: usize> TpmBuffer<CAPACITY> {
333    #[must_use]
334    pub const fn new() -> Self {
335        Self {
336            bytes: [0; CAPACITY],
337            len: 0,
338        }
339    }
340
341    /// Returns true if the buffer has a length of zero.
342    #[must_use]
343    pub fn is_empty(&self) -> bool {
344        self.len == 0
345    }
346}
347
348impl<const CAPACITY: usize> Deref for TpmBuffer<CAPACITY> {
349    type Target = [u8];
350
351    fn deref(&self) -> &Self::Target {
352        &self.bytes[..self.len as usize]
353    }
354}
355
356impl<const CAPACITY: usize> Default for TpmBuffer<CAPACITY> {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362impl<const CAPACITY: usize> TpmSized for TpmBuffer<CAPACITY> {
363    const SIZE: usize = size_of::<u16>() + CAPACITY;
364    fn len(&self) -> usize {
365        size_of::<u16>() + self.len as usize
366    }
367}
368
369impl<const CAPACITY: usize> TpmBuild for TpmBuffer<CAPACITY> {
370    fn build(&self, writer: &mut crate::TpmWriter) -> TpmResult<()> {
371        build_tpm2b(writer, self)
372    }
373}
374
375impl<'a, const CAPACITY: usize> TpmParse<'a> for TpmBuffer<CAPACITY> {
376    fn parse(buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])> {
377        let (bytes, remainder) = parse_tpm2b(buf)?;
378        if bytes.len() > CAPACITY {
379            return Err(TpmErrorKind::ValueTooLarge);
380        }
381        let mut buffer = Self::new();
382        buffer.bytes[..bytes.len()].copy_from_slice(bytes);
383        buffer.len = u16::try_from(bytes.len()).map_err(|_| TpmErrorKind::InternalError)?;
384        Ok((buffer, remainder))
385    }
386}
387
388impl<const CAPACITY: usize> TryFrom<&[u8]> for TpmBuffer<CAPACITY> {
389    type Error = TpmErrorKind;
390
391    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
392        if slice.len() > CAPACITY {
393            return Err(TpmErrorKind::ValueTooLarge);
394        }
395        let mut buffer = Self::new();
396        buffer.bytes[..slice.len()].copy_from_slice(slice);
397        buffer.len = u16::try_from(slice.len()).map_err(|_| TpmErrorKind::InternalError)?;
398        Ok(buffer)
399    }
400}
401
402impl<const CAPACITY: usize> AsRef<[u8]> for TpmBuffer<CAPACITY> {
403    fn as_ref(&self) -> &[u8] {
404        &self.bytes[..self.len as usize]
405    }
406}
407
408impl<const CAPACITY: usize> core::fmt::Debug for TpmBuffer<CAPACITY> {
409    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
410        write!(f, "TpmBuffer(")?;
411        for byte in &**self {
412            write!(f, "{byte:02x}")?;
413        }
414        write!(f, ")")
415    }
416}
417
418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419pub struct TpmList<T: Copy + Default, const CAPACITY: usize> {
420    items: [T; CAPACITY],
421    len: u32,
422}
423
424impl<T: Copy + Default, const CAPACITY: usize> TpmList<T, CAPACITY> {
425    #[must_use]
426    pub fn new() -> Self {
427        Self {
428            items: [T::default(); CAPACITY],
429            len: 0,
430        }
431    }
432
433    /// Returns `true` if the list contains no elements.
434    #[must_use]
435    pub fn is_empty(&self) -> bool {
436        self.len == 0
437    }
438
439    /// Appends an element to the back of the list.
440    ///
441    /// # Errors
442    ///
443    /// Returns a `TpmErrorKind::ValueTooLarge` error if the list is already at
444    /// full capacity.
445    pub fn try_push(&mut self, item: T) -> Result<(), TpmErrorKind> {
446        if self.len as usize >= CAPACITY {
447            return Err(TpmErrorKind::ValueTooLarge);
448        }
449        let index = self.len as usize;
450        self.items[index] = item;
451        self.len += 1;
452        Ok(())
453    }
454}
455
456impl<T: Copy + Default, const CAPACITY: usize> Deref for TpmList<T, CAPACITY> {
457    type Target = [T];
458
459    fn deref(&self) -> &Self::Target {
460        &self.items[..self.len as usize]
461    }
462}
463
464impl<T: Copy + Default, const CAPACITY: usize> Default for TpmList<T, CAPACITY> {
465    fn default() -> Self {
466        Self::new()
467    }
468}
469
470impl<T: TpmSized + Copy + Default, const CAPACITY: usize> TpmSized for TpmList<T, CAPACITY> {
471    const SIZE: usize = size_of::<u32>() + (T::SIZE * CAPACITY);
472    fn len(&self) -> usize {
473        size_of::<u32>() + self.iter().map(TpmSized::len).sum::<usize>()
474    }
475}
476
477impl<T: TpmBuild + Copy + Default, const CAPACITY: usize> TpmBuild for TpmList<T, CAPACITY> {
478    fn build(&self, writer: &mut crate::TpmWriter) -> TpmResult<()> {
479        self.len.build(writer)?;
480        for item in &**self {
481            item.build(writer)?;
482        }
483        Ok(())
484    }
485}
486
487impl<'a, T: TpmParse<'a> + Copy + Default, const CAPACITY: usize> TpmParse<'a>
488    for TpmList<T, CAPACITY>
489{
490    fn parse(buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])> {
491        let (count, mut buf) = u32::parse(buf)?;
492        if count as usize > CAPACITY {
493            return Err(TpmErrorKind::ValueTooLarge);
494        }
495
496        let mut list = Self::new();
497        for i in 0..(count as usize) {
498            let (item, rest) = T::parse(buf)?;
499            list.items[i] = item;
500            list.len += 1;
501            buf = rest;
502        }
503
504        Ok((list, buf))
505    }
506}
507
508/// A helper for parsing data from a TPM parameter buffer, which is
509/// prefixed with a u32 size.
510pub struct TpmParameters<'a> {
511    buf: &'a [u8],
512}
513
514impl<'a> TpmParameters<'a> {
515    /// Creates a new parameter buffer from a slice.
516    ///
517    /// It reads a `u32` size prefix, slices the buffer accordingly, and returns
518    /// the parameter buffer and the remainder of the original buffer.
519    ///
520    /// # Errors
521    ///
522    /// Returns `TpmErrorKind::Boundary` if the buffer is too small to contain
523    /// the size prefix or the data described by the size prefix.
524    /// Returns `TpmErrorKind::ValueTooLarge` if the size prefix exceeds `TPM_MAX_COMMAND_SIZE`.
525    pub fn new(buf: &'a [u8]) -> TpmResult<(Self, &'a [u8])> {
526        let (size, buf) = u32::parse(buf)?;
527        let size = size as usize;
528
529        if size > crate::TPM_MAX_COMMAND_SIZE {
530            return Err(TpmErrorKind::ValueTooLarge);
531        }
532
533        if buf.len() < size {
534            return Err(TpmErrorKind::Boundary);
535        }
536        let (param_data, buf) = buf.split_at(size);
537        Ok((Self { buf: param_data }, buf))
538    }
539
540    /// Parses a single value from the buffer, advancing the internal cursor.
541    ///
542    /// # Errors
543    ///
544    /// Returns any error encountered during the parsing of the inner type `T`.
545    pub fn parse<T: TpmParse<'a>>(&mut self) -> TpmResult<T> {
546        let (value, rest) = T::parse(self.buf)?;
547        self.buf = rest;
548        Ok(value)
549    }
550
551    /// Checks if the entire parameter buffer has been consumed.
552    #[must_use]
553    pub fn is_empty(&self) -> bool {
554        self.buf.is_empty()
555    }
556}
557
558#[cfg(test)]
559mod test;