hsmtool/commands/
mod.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use cryptoki::session::Session;
7use serde::{Deserialize, Serialize};
8use serde_annotate::{Annotate, ColorProfile};
9use std::any::Any;
10use std::io::IsTerminal;
11
12use crate::module::Module;
13use crate::util::attribute::AttrData;
14
15mod aes;
16mod ecdsa;
17mod exec;
18mod kdf;
19mod mldsa;
20mod object;
21mod rsa;
22mod spx;
23mod token;
24
25#[typetag::serde(tag = "command")]
26pub trait Dispatch {
27    fn run(
28        &self,
29        context: &dyn Any,
30        hsm: &Module,
31        session: Option<&Session>,
32    ) -> Result<Box<dyn erased_serde::Serialize>>;
33
34    fn leaf(&self) -> &dyn Dispatch
35    where
36        Self: Sized,
37    {
38        self
39    }
40}
41
42#[derive(clap::Subcommand, Debug, Serialize, Deserialize)]
43pub enum Commands {
44    #[command(subcommand)]
45    Aes(aes::Aes),
46    #[command(subcommand)]
47    Ecdsa(ecdsa::Ecdsa),
48    Exec(exec::Exec),
49    #[command(subcommand)]
50    Kdf(kdf::Kdf),
51    #[command(subcommand)]
52    Mldsa(mldsa::Mldsa),
53    #[command(subcommand)]
54    Object(object::Object),
55    #[command(subcommand)]
56    Rsa(rsa::Rsa),
57    #[command(subcommand)]
58    Spx(spx::Spx),
59    #[command(subcommand)]
60    Token(token::Token),
61}
62
63#[typetag::serde(name = "__commands__")]
64impl Dispatch for Commands {
65    fn run(
66        &self,
67        context: &dyn Any,
68        hsm: &Module,
69        session: Option<&Session>,
70    ) -> Result<Box<dyn erased_serde::Serialize>> {
71        match self {
72            Commands::Aes(x) => x.run(context, hsm, session),
73            Commands::Ecdsa(x) => x.run(context, hsm, session),
74            Commands::Exec(x) => x.run(context, hsm, session),
75            Commands::Kdf(x) => x.run(context, hsm, session),
76            Commands::Mldsa(x) => x.run(context, hsm, session),
77            Commands::Object(x) => x.run(context, hsm, session),
78            Commands::Rsa(x) => x.run(context, hsm, session),
79            Commands::Spx(x) => x.run(context, hsm, session),
80            Commands::Token(x) => x.run(context, hsm, session),
81        }
82    }
83
84    fn leaf(&self) -> &dyn Dispatch
85    where
86        Self: Sized,
87    {
88        match self {
89            Commands::Aes(x) => x.leaf(),
90            Commands::Ecdsa(x) => x.leaf(),
91            Commands::Exec(x) => x.leaf(),
92            Commands::Kdf(x) => x.leaf(),
93            Commands::Mldsa(x) => x.leaf(),
94            Commands::Object(x) => x.leaf(),
95            Commands::Rsa(x) => x.leaf(),
96            Commands::Spx(x) => x.leaf(),
97            Commands::Token(x) => x.leaf(),
98        }
99    }
100}
101
102#[derive(Debug, Annotate)]
103pub struct BasicResult {
104    success: bool,
105    #[serde(skip_serializing_if = "AttrData::is_none")]
106    id: AttrData,
107    #[serde(skip_serializing_if = "AttrData::is_none")]
108    label: AttrData,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    #[annotate(format = block)]
111    value: Option<String>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    #[annotate(format = block)]
114    error: Option<String>,
115}
116
117#[derive(Debug, Annotate)]
118pub struct SignResult {
119    #[serde(with = "serde_bytes")]
120    #[annotate(format = hexstr)]
121    pub digest: Vec<u8>,
122    #[serde(with = "serde_bytes")]
123    #[annotate(format = hexstr)]
124    pub signature: Vec<u8>,
125}
126
127impl Default for BasicResult {
128    fn default() -> Self {
129        BasicResult {
130            success: true,
131            id: AttrData::None,
132            label: AttrData::None,
133            value: None,
134            error: None,
135        }
136    }
137}
138
139impl BasicResult {
140    pub fn from_error(e: &anyhow::Error) -> Box<dyn erased_serde::Serialize> {
141        Box::new(BasicResult {
142            success: false,
143            id: AttrData::None,
144            label: AttrData::None,
145            value: None,
146            error: Some(format!("{:?}", e)),
147        })
148    }
149}
150
151#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152pub enum Format {
153    Json,
154    Json5,
155    HJson,
156    Yaml,
157}
158
159pub fn print_result(
160    format: Format,
161    color: Option<bool>,
162    quiet: bool,
163    result: Result<Box<dyn erased_serde::Serialize>>,
164) -> Result<()> {
165    let (doc, result) = match result {
166        Ok(value) => {
167            let doc = serde_annotate::serialize(value.as_ref())?;
168            if quiet {
169                return Ok(());
170            }
171            (doc, Ok(()))
172        }
173        Err(e) => {
174            let doc = if let Some(exerr) = e.downcast_ref::<exec::ExecError>() {
175                exerr.result.clone()
176            } else {
177                let value = BasicResult::from_error(&e);
178                serde_annotate::serialize(value.as_ref())?
179            };
180            (doc, Err(e))
181        }
182    };
183
184    let profile = if std::io::stdout().is_terminal() && color.unwrap_or(true) {
185        ColorProfile::basic()
186    } else {
187        ColorProfile::default()
188    };
189    let string = match format {
190        Format::Json => doc.to_json().color(profile).to_string(),
191        Format::Json5 => doc.to_json5().color(profile).to_string(),
192        Format::HJson => doc.to_hjson().color(profile).to_string(),
193        Format::Yaml => doc.to_yaml().color(profile).to_string(),
194    };
195    println!("{}", string);
196    result
197}
198
199pub fn print_command(format: Format, color: Option<bool>, command: &dyn Dispatch) -> Result<()> {
200    let doc = serde_annotate::serialize(command)?;
201    let profile = if std::io::stdout().is_terminal() && color.unwrap_or(true) {
202        ColorProfile::basic()
203    } else {
204        ColorProfile::default()
205    };
206    let string = match format {
207        Format::Json => doc.to_json().color(profile).to_string(),
208        Format::Json5 => doc.to_json5().color(profile).to_string(),
209        Format::HJson => doc.to_hjson().color(profile).to_string(),
210        Format::Yaml => doc.to_yaml().color(profile).to_string(),
211    };
212    println!("{}", string);
213    Ok(())
214}