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}