cli/auth.rs
1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5//! Handles parsing and representation of authorization data for TPM commands.
6
7use std::{num::ParseIntError, str::FromStr};
8use thiserror::Error;
9use tpm2_protocol::{data::TpmHt, TpmBuild, TpmError, TpmHandle, TpmParse, TpmSized, TpmWriter};
10
11/// Maximum size for password or policy authorization data.
12const MAX_AUTH_SIZE: usize = 64;
13
14/// Errors related to parsing or using authorization data.
15#[derive(Debug, Error)]
16pub enum AuthError {
17 /// The provided authorization string is invalid or malformed.
18 #[error("invalid auth string")]
19 InvalidAuth,
20 /// The structure or format of the authorization data is incorrect.
21 #[error("malformed auth value")]
22 MalformedAuth,
23 /// The provided authorization value (password or policy) exceeds `MAX_AUTH_SIZE`.
24 #[error("auth value too large")]
25 ValueTooLarge,
26 /// Failed to decode a hexadecimal string.
27 #[error("hex decode: {0}")]
28 HexDecode(#[from] hex::FromHexError),
29 /// Failed to parse a hexadecimal string as a handle (u32).
30 #[error("handle decode: {0}")]
31 IntDecode(#[from] ParseIntError),
32}
33
34impl From<TpmError> for AuthError {
35 fn from(_: TpmError) -> Self {
36 Self::MalformedAuth
37 }
38}
39
40/// Specifies the type or method of authorization.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum AuthClass {
43 /// Authorization using a password (or empty password represented by `Auth::default()`).
44 Password,
45 /// Authorization based on a policy digest.
46 Policy,
47 /// Authorization using a session handle (HMAC or policy session).
48 Session,
49}
50
51/// Represents authorization data, holding the class and the actual value.
52///
53/// It uses a fixed-size array internally for efficiency and predictability,
54/// tracking the actual length of the data used.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Auth {
57 /// The class (type) of authorization.
58 pub class: AuthClass,
59 /// Fixed-size buffer holding the authorization data (password, policy digest, or handle bytes).
60 pub data: [u8; MAX_AUTH_SIZE],
61 /// The actual number of bytes used in the `data` buffer.
62 pub len: usize,
63}
64
65/// Helper function to build a handle byte array from a u32 value.
66/// Serializes a `TpmHandle` into a byte array suitable for the `Auth::data` field.
67///
68/// # Errors
69///
70/// Returns a `TpmError` if serialization fails.
71fn build_handle_array(handle_val: u32) -> Result<([u8; MAX_AUTH_SIZE], usize), TpmError> {
72 let handle = TpmHandle(handle_val);
73 let mut array = [0u8; MAX_AUTH_SIZE];
74 let mut writer = TpmWriter::new(&mut array[0..TpmHandle::SIZE]);
75 handle.build(&mut writer)?;
76 Ok((array, TpmHandle::SIZE))
77}
78
79/// Helper function to parse hex string, validate size, and copy to fixed array.
80///
81/// Decodes a hexadecimal string, checks if it exceeds `MAX_AUTH_SIZE`, and
82/// copies the resulting bytes into the fixed-size array format used by `Auth`.
83///
84/// # Errors
85///
86/// Returns `AuthError::HexDecode` if the string is not valid hex.
87/// Returns `AuthError::ValueTooLarge` if the decoded bytes exceed `MAX_AUTH_SIZE`.
88fn parse_auth_hex(s: &str) -> Result<([u8; MAX_AUTH_SIZE], usize), AuthError> {
89 let bytes = hex::decode(s)?;
90 if bytes.len() > MAX_AUTH_SIZE {
91 return Err(AuthError::ValueTooLarge);
92 }
93 let mut array = [0u8; MAX_AUTH_SIZE];
94 array[..bytes.len()].copy_from_slice(&bytes);
95 Ok((array, bytes.len()))
96}
97
98impl Auth {
99 /// Returns the authorization class (`Password`, `Policy`, or `Session`).
100 #[must_use]
101 pub fn class(&self) -> AuthClass {
102 self.class
103 }
104
105 /// Returns the raw authorization value as a byte slice.
106 ///
107 /// The length of the slice depends on the `AuthClass`:
108 /// * `Password`, `Policy`: Length determined by parsed hex data (`self.len`).
109 /// * `Session`: Fixed length equal to `TpmHandle::SIZE`.
110 #[must_use]
111 pub fn value(&self) -> &[u8] {
112 &self.data[0..self.len]
113 }
114
115 /// Extracts the session handle (u32) if the class is `Session`.
116 ///
117 /// # Errors
118 ///
119 /// Returns [`InvalidAuth`](AuthError::InvalidAuth) if the class is not
120 /// `AuthClass::Session`.
121 /// Returns [`MalformedAuth`](AuthError::MalformedAuth) if the stored bytes
122 /// are not a valid `TpmHandle`.
123 pub fn session(&self) -> Result<u32, AuthError> {
124 if self.class() != AuthClass::Session {
125 return Err(AuthError::InvalidAuth);
126 }
127 let (handle, remainder) = TpmHandle::parse(&self.data[0..TpmHandle::SIZE])?;
128 if !remainder.is_empty() {
129 return Err(AuthError::MalformedAuth);
130 }
131 Ok(handle.0)
132 }
133}
134
135impl Default for Auth {
136 /// Creates a default `Auth` instance representing an empty password.
137 fn default() -> Self {
138 Self {
139 class: AuthClass::Password,
140 data: [0u8; MAX_AUTH_SIZE],
141 len: 0,
142 }
143 }
144}
145
146impl std::fmt::Display for Auth {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 if *self == Self::default() {
149 return write!(f, "empty");
150 }
151
152 match self.class {
153 AuthClass::Password => write!(f, "password:<sensitive>"),
154 AuthClass::Policy => write!(f, "policy:{}", hex::encode(self.value())),
155 AuthClass::Session => match self.session() {
156 Ok(handle_val) => write!(f, "vtpm:{handle_val:08x}"),
157 Err(_) => Err(std::fmt::Error),
158 },
159 }
160 }
161}
162
163impl FromStr for Auth {
164 type Err = AuthError;
165
166 /// Parses an authorization string into an `Auth` structure.
167 ///
168 /// Valid formats:
169 ///
170 /// * `empty` - Represents an empty password.
171 /// * `password:<hex>` - Password specified as a hex string.
172 /// * `policy:<hex>` - Policy digest specified as a hex string.
173 /// * `vtpm:<hex>` - Session handle specified as a hex string.
174 ///
175 /// # Errors
176 ///
177 /// Returns [`InvalidAuth`](AuthError::InvalidAuth) when the string does not
178 /// match any valid format.
179 /// Returns [`ValueTooLarge`](AuthError::ValueTooLarge) when the password or
180 /// policy hex data exceeds `MAX_AUTH_SIZE`.
181 fn from_str(auth_str: &str) -> Result<Self, Self::Err> {
182 if auth_str == "empty" {
183 return Ok(Self::default());
184 }
185
186 let (prefix, value) = auth_str.split_once(':').ok_or(AuthError::InvalidAuth)?;
187
188 match prefix {
189 "password" => {
190 let (data, len) = parse_auth_hex(value)?;
191 Ok(Auth {
192 class: AuthClass::Password,
193 data,
194 len,
195 })
196 }
197 "policy" => {
198 let (data, len) = parse_auth_hex(value)?;
199 Ok(Auth {
200 class: AuthClass::Policy,
201 data,
202 len,
203 })
204 }
205 "vtpm" => {
206 let handle_val = u32::from_str_radix(value, 16)?;
207 let ht = (handle_val >> 24) as u8;
208
209 if ht == TpmHt::PolicySession as u8 || ht == TpmHt::HmacSession as u8 {
210 let (data, len) =
211 build_handle_array(handle_val).map_err(|_| AuthError::MalformedAuth)?;
212 Ok(Auth {
213 class: AuthClass::Session,
214 data,
215 len,
216 })
217 } else {
218 Err(AuthError::InvalidAuth)
219 }
220 }
221 _ => Err(AuthError::InvalidAuth),
222 }
223 }
224}