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