cli/command/
mod.rs

1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2024-2025 Jarkko Sakkinen
3// Copyright (c) 2025 Opinsys Oy
4
5#![allow(clippy::doc_markdown)]
6
7pub mod algorithm;
8pub mod cache;
9pub mod certificate;
10pub mod common;
11pub mod convert;
12pub mod create;
13pub mod create_primary;
14pub mod delete;
15pub mod evict;
16pub mod load;
17pub mod memory;
18pub mod pcr_event;
19pub mod policy;
20pub mod reset_lock;
21pub mod return_code;
22pub mod unseal;
23
24pub use algorithm::*;
25pub use cache::*;
26pub use certificate::*;
27pub use common::*;
28pub use convert::*;
29pub use create::*;
30pub use create_primary::*;
31pub use delete::*;
32pub use evict::*;
33pub use load::*;
34pub use memory::*;
35pub use pcr_event::*;
36pub use policy::*;
37pub use reset_lock::*;
38pub use return_code::*;
39pub use unseal::*;
40
41use crate::{
42    auth::AuthError,
43    crypto::CryptoError,
44    device::DeviceError,
45    handle::{HandleError, HandlePatternError},
46    job::JobError,
47    key::{AlgInfo, KeyError},
48    pcr::PcrError,
49    policy::PolicyError,
50    vtpm::VtpmError,
51};
52use clap::builder::styling::Style as AnsiStyle;
53use std::{
54    io::{IsTerminal, Write},
55    num::TryFromIntError,
56};
57use thiserror::Error;
58use tpm2_protocol::{
59    data::{TpmCc, TpmRcBase},
60    TpmError,
61};
62
63/// A trait for data structures that can be represented as a table row.
64pub trait Tabled {
65    /// Returns the headers for the table.
66    fn headers() -> Vec<String>;
67    /// Returns the data for a single row.
68    fn row(&self) -> Vec<String>;
69}
70
71/// Creates, styles, and prints a table from a vector of `Tabled` items.
72///
73/// # Errors
74///
75/// Returns an I/O error if writing to the writer fails.
76pub fn print_table<T>(writer: &mut dyn Write, items: &[T]) -> Result<(), std::io::Error>
77where
78    T: Tabled,
79{
80    if items.is_empty() {
81        return Ok(());
82    }
83
84    let headers = T::headers();
85    let rows: Vec<Vec<String>> = items.iter().map(T::row).collect();
86    let num_columns = headers.len();
87    let mut max_widths = vec![0; num_columns];
88
89    for i in 0..num_columns {
90        max_widths[i] = headers[i].len();
91    }
92    for row in &rows {
93        for (i, cell) in row.iter().enumerate() {
94            if cell.len() > max_widths[i] {
95                max_widths[i] = cell.len();
96            }
97        }
98    }
99
100    let bold = AnsiStyle::new().bold();
101    let header_line = headers
102        .iter()
103        .zip(&max_widths)
104        .map(|(header, &width)| format!("{header:<width$}"))
105        .collect::<Vec<String>>()
106        .join("  ");
107
108    if std::io::stdout().is_terminal() {
109        writeln!(writer, "{bold}{header_line}{bold:#}")?;
110    } else {
111        writeln!(writer, "{header_line}")?;
112    }
113
114    for row in &rows {
115        let row_line = row
116            .iter()
117            .zip(&max_widths)
118            .map(|(cell, &width)| format!("{cell:<width$}"))
119            .collect::<Vec<String>>()
120            .join("  ");
121        writeln!(writer, "{row_line}")?;
122    }
123
124    Ok(())
125}
126
127/// Returns an error if the provided algorithm is `KeyedHash`.
128///
129/// # Errors
130///
131/// Returns `CommandError::UnsupportedKeyAlgorithm` if the algorithm is keyedhash.
132pub fn deny_keyedhash(algorithm: &crate::key::Alg) -> Result<(), CommandError> {
133    if algorithm.params == AlgInfo::KeyedHash {
134        Err(CommandError::UnsupportedKeyAlgorithm(algorithm.clone()))
135    } else {
136        Ok(())
137    }
138}
139
140#[derive(Debug, Error)]
141pub enum CommandError {
142    #[error("authentication denied")]
143    AuthenticationDenied,
144    #[error("dictionary attack lockout is active")]
145    DictionaryAttackLocked,
146    #[error("invalid key format")]
147    InvalidFormat,
148    #[error("invalid input: {0}")]
149    InvalidInput(String),
150    #[error("invalid output: {0}")]
151    InvalidOutput(String),
152    #[error("invalid parent: {0}{1:08x}")]
153    InvalidParent(&'static str, u32),
154    #[error("parent missing")]
155    ParentMissing,
156    #[error("response mismatch: {0}")]
157    ResponseMismatch(TpmCc),
158    #[error("sensitive data denied")]
159    SensitiveDataDenied,
160    #[error("sensitive data missing")]
161    SensitiveDataMissing,
162    #[error("unknown parent")]
163    UnknownParent,
164    #[error("unsupported key algorithm: '{0}'")]
165    UnsupportedKeyAlgorithm(crate::key::Alg),
166    #[error("auth: {0}")]
167    Auth(#[from] AuthError),
168    #[error("cache: {0}")]
169    Cache(VtpmError),
170    #[error("job: {0}")]
171    Job(JobError),
172    #[error("device: {0}")]
173    Device(DeviceError),
174    #[error("crypto: {0}")]
175    Crypto(#[from] CryptoError),
176    #[error("handle: {0}")]
177    Handle(#[from] HandleError),
178    #[error("handle pattern: {0}")]
179    HandlePattern(#[from] HandlePatternError),
180    #[error("key error: {0}")]
181    Key(#[from] KeyError),
182    #[error("pcr: {0}")]
183    Pcr(#[from] PcrError),
184    #[error("policy: {0}")]
185    Policy(#[from] PolicyError),
186    #[error("hex decode: {0}")]
187    HexDecode(#[from] hex::FromHexError),
188    #[error("int decode: {0}")]
189    IntDecode(#[from] TryFromIntError),
190    #[error("I/O: {0}")]
191    Io(#[from] std::io::Error),
192    #[error("protocol: {0}")]
193    TpmProtocol(TpmError),
194}
195
196impl From<JobError> for CommandError {
197    fn from(err: JobError) -> Self {
198        match err {
199            JobError::InvalidParent(prefix, handle) => Self::InvalidParent(prefix, handle),
200            JobError::Device(dev_err) => Self::from(dev_err),
201            _ => Self::Job(err),
202        }
203    }
204}
205
206impl From<VtpmError> for CommandError {
207    fn from(err: VtpmError) -> Self {
208        Self::Cache(err)
209    }
210}
211
212impl From<DeviceError> for CommandError {
213    fn from(err: DeviceError) -> Self {
214        if let DeviceError::TpmRc(rc) = &err {
215            let base = rc.base();
216            if base == TpmRcBase::AuthFail || base == TpmRcBase::AuthMissing {
217                return Self::AuthenticationDenied;
218            }
219            if base == TpmRcBase::Lockout {
220                return Self::DictionaryAttackLocked;
221            }
222        }
223        Self::Device(err)
224    }
225}
226
227impl From<TpmError> for CommandError {
228    fn from(err: TpmError) -> Self {
229        Self::TpmProtocol(err)
230    }
231}