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