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