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