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