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//! ## 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_code)]
23#![deny(clippy::all)]
24#![deny(clippy::pedantic)]
25
26#[macro_use]
27pub mod r#macro;
28pub mod buffer;
29pub mod data;
30pub mod list;
31pub mod message;
32pub mod parameters;
33
34use crate::data::TpmAlgId;
35use core::{convert::TryFrom, fmt, mem::size_of, result::Result};
36
37pub use buffer::TpmBuffer;
38pub use list::TpmList;
39pub use parameters::TpmParameters;
40
41tpm_handle! {
42    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
43    TpmTransient
44}
45tpm_handle! {
46    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
47    TpmSession
48}
49tpm_handle! {
50    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
51    TpmPersistent
52}
53
54/// The maximum size of a TPM command or response buffer.
55pub const TPM_MAX_COMMAND_SIZE: usize = 4096;
56
57#[derive(Debug, PartialEq, Eq)]
58pub enum TpmErrorKind {
59    /// Insufficient amount of bytes available
60    Boundary,
61    /// Trailing data after parsing
62    TrailingData,
63    /// Not a valid discriminant for the target enum
64    InvalidDiscriminant { type_name: &'static str, value: u64 },
65    /// Invalid magic number for the data
66    InvalidMagic { expected: u32, got: u32 },
67    /// Invalid tag for the data
68    InvalidTag {
69        type_name: &'static str,
70        expected: u16,
71        got: u16,
72    },
73    /// Invalid value
74    InvalidValue,
75    /// A size or count in the buffer is larger than the maximum allowed value
76    ValueTooLarge,
77    /// An operation would exceed the fixed capacity of a container
78    CapacityExceeded,
79    /// A command requires an authorization session but none was provided
80    AuthMissing,
81    /// An unexpected internal error
82    InternalError,
83}
84
85impl fmt::Display for TpmErrorKind {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            Self::Boundary => write!(f, "Insufficient data in buffer"),
89            Self::TrailingData => write!(f, "Buffer has unexpected trailing data after parsing"),
90            Self::InvalidDiscriminant { type_name, value } => {
91                write!(f, "Invalid discriminant 0x{value:x} for type '{type_name}'")
92            }
93            Self::InvalidMagic { expected, got } => {
94                write!(
95                    f,
96                    "Invalid magic number: expected 0x{expected:x}, got 0x{got:x}"
97                )
98            }
99            Self::InvalidTag {
100                type_name,
101                expected,
102                got,
103            } => {
104                write!(
105                    f,
106                    "Invalid tag for {type_name}: expected 0x{expected:x}, got 0x{got:x}"
107                )
108            }
109            Self::InvalidValue => write!(f, "A value is invalid or out of the expected range"),
110            Self::ValueTooLarge => {
111                write!(
112                    f,
113                    "A size or count is larger than the maximum allowed value"
114                )
115            }
116            Self::CapacityExceeded => write!(f, "An operation would exceed a container's capacity"),
117            Self::AuthMissing => write!(f, "Command requires authorization but none was provided"),
118            Self::InternalError => write!(f, "An unexpected internal error occurred"),
119        }
120    }
121}
122
123impl From<core::num::TryFromIntError> for TpmErrorKind {
124    fn from(_: core::num::TryFromIntError) -> Self {
125        Self::InternalError
126    }
127}
128
129pub type TpmResult<T> = Result<T, TpmErrorKind>;
130
131/// Writes into a mutable byte slice.
132pub struct TpmWriter<'a> {
133    buffer: &'a mut [u8],
134    cursor: usize,
135}
136
137impl<'a> TpmWriter<'a> {
138    /// Creates a new writer for the given buffer.
139    #[must_use]
140    pub fn new(buffer: &'a mut [u8]) -> Self {
141        Self { buffer, cursor: 0 }
142    }
143
144    /// Returns the number of bytes written so far.
145    #[must_use]
146    pub fn len(&self) -> usize {
147        self.cursor
148    }
149
150    /// Returns `true` if no bytes have been written.
151    #[must_use]
152    pub fn is_empty(&self) -> bool {
153        self.cursor == 0
154    }
155
156    /// Appends a slice of bytes to the writer.
157    ///
158    /// # Errors
159    ///
160    /// Returns `TpmErrorKind::Boundary` if the writer does not have enough
161    /// capacity to hold the new bytes.
162    pub fn write_bytes(&mut self, bytes: &[u8]) -> TpmResult<()> {
163        let end = self.cursor + bytes.len();
164        if end > self.buffer.len() {
165            return Err(TpmErrorKind::Boundary);
166        }
167        self.buffer[self.cursor..end].copy_from_slice(bytes);
168        self.cursor = end;
169        Ok(())
170    }
171}
172
173/// Provides two ways to determine the size of an object: a compile-time maximum
174/// and a runtime exact size.
175pub trait TpmSized {
176    /// The estimated size of the object in its serialized form evaluated at
177    /// compile-time (always larger than the realized length).
178    const SIZE: usize;
179
180    /// Returns the exact serialized size of the object.
181    fn len(&self) -> usize;
182
183    /// Returns `true` if the object has a serialized length of zero.
184    fn is_empty(&self) -> bool {
185        self.len() == 0
186    }
187}
188
189pub trait TpmBuild: TpmSized {
190    /// Builds the object into the given writer.
191    ///
192    /// # Errors
193    ///
194    /// * `TpmErrorKind::ValueTooLarge` if the object contains a value that cannot be built.
195    /// * `TpmErrorKind::Boundary` if the writer runs out of space.
196    fn build(&self, writer: &mut TpmWriter) -> TpmResult<()>;
197}
198
199pub trait TpmParse: Sized + TpmSized {
200    /// Parses an object from the given buffer.
201    ///
202    /// Returns the parsed type and the remaining portion of the buffer.
203    ///
204    /// # Errors
205    ///
206    /// * `TpmErrorKind::Boundary` if the buffer is too small to contain the object.
207    /// * `TpmErrorKind::InvalidDiscriminant` if a value in the buffer is invalid for the target type.
208    fn parse(buf: &[u8]) -> TpmResult<(Self, &[u8])>;
209}
210
211/// Types that are composed of a tag and a value e.g., a union.
212pub trait TpmTagged {
213    /// The type of the tag/discriminant.
214    type Tag: TpmParse + TpmBuild + Copy;
215    /// The type of the value/union.
216    type Value;
217}
218
219/// Parses a tagged object from a buffer.
220pub trait TpmParseTagged: Sized {
221    /// Parses a tagged object from the given buffer using the provided tag.
222    ///
223    /// # Errors
224    ///
225    /// This method can return any error of the underlying type's `TpmParse` implementation,
226    /// such as a `TpmErrorKind::Boundary` if the buffer is too small or an
227    /// `TpmErrorKind::InvalidValue` if the data is malformed.
228    fn parse_tagged(tag: <Self as TpmTagged>::Tag, buf: &[u8]) -> TpmResult<(Self, &[u8])>
229    where
230        Self: TpmTagged,
231        <Self as TpmTagged>::Tag: TpmParse + TpmBuild;
232}
233
234impl TpmSized for u8 {
235    const SIZE: usize = 1;
236    fn len(&self) -> usize {
237        1
238    }
239}
240
241impl TpmBuild for u8 {
242    fn build(&self, writer: &mut TpmWriter) -> TpmResult<()> {
243        writer.write_bytes(&[*self])
244    }
245}
246
247impl TpmParse for u8 {
248    fn parse(buf: &[u8]) -> TpmResult<(Self, &[u8])> {
249        let (val, buf) = buf.split_first().ok_or(TpmErrorKind::Boundary)?;
250        Ok((*val, buf))
251    }
252}
253
254macro_rules! tpm_integer {
255    ($ty:ty) => {
256        impl TpmParse for $ty {
257            fn parse(buf: &[u8]) -> TpmResult<(Self, &[u8])> {
258                let size = size_of::<$ty>();
259                if buf.len() < size {
260                    return Err(TpmErrorKind::Boundary);
261                }
262                let (bytes, buf) = buf.split_at(size);
263                let array = bytes.try_into().map_err(|_| TpmErrorKind::InternalError)?;
264                let val = <$ty>::from_be_bytes(array);
265                Ok((val, buf))
266            }
267        }
268
269        impl TpmBuild for $ty {
270            fn build(&self, writer: &mut TpmWriter) -> TpmResult<()> {
271                writer.write_bytes(&self.to_be_bytes())
272            }
273        }
274
275        impl TpmSized for $ty {
276            const SIZE: usize = size_of::<$ty>();
277            fn len(&self) -> usize {
278                Self::SIZE
279            }
280        }
281    };
282}
283
284tpm_integer!(i32);
285tpm_integer!(u16);
286tpm_integer!(u32);
287tpm_integer!(u64);
288
289/// Builds a TPM2B sized buffer.
290///
291/// # Errors
292///
293/// * `TpmErrorKind::ValueTooLarge` if the data slice is too large to fit in a `u16` length.
294pub fn build_tpm2b(writer: &mut TpmWriter, data: &[u8]) -> TpmResult<()> {
295    let len_u16 = u16::try_from(data.len()).map_err(|_| TpmErrorKind::ValueTooLarge)?;
296    TpmBuild::build(&len_u16, writer)?;
297    writer.write_bytes(data)
298}
299
300/// Parses a TPM2B sized buffer.
301///
302/// # Errors
303///
304/// * `TpmErrorKind::Boundary` if the buffer is too small.
305/// * `TpmErrorKind::ValueTooLarge` if the size prefix exceeds `TPM_MAX_COMMAND_SIZE`.
306pub fn parse_tpm2b(buf: &[u8]) -> TpmResult<(&[u8], &[u8])> {
307    let (size, buf) = u16::parse(buf)?;
308    let size = size as usize;
309
310    if size > TPM_MAX_COMMAND_SIZE {
311        return Err(TpmErrorKind::ValueTooLarge);
312    }
313
314    if buf.len() < size {
315        return Err(TpmErrorKind::Boundary);
316    }
317    Ok(buf.split_at(size))
318}
319
320/// Returns the size of a hash digest in bytes for a given hash algorithm.
321#[must_use]
322pub const fn tpm_hash_size(alg_id: &TpmAlgId) -> Option<usize> {
323    match alg_id {
324        TpmAlgId::Sha1 => Some(20),
325        TpmAlgId::Sha256 | TpmAlgId::Sm3_256 => Some(32),
326        TpmAlgId::Sha384 => Some(48),
327        TpmAlgId::Sha512 => Some(64),
328        _ => None,
329    }
330}