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