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