signstar_sign/
main.rs

1//! Application for the creation of signatures from signing requests.
2
3use std::process::ExitCode;
4
5use clap::Parser;
6use clap_verbosity_flag::Verbosity;
7use nethsm::{KeyId, NetHsm};
8use nethsm_config::UserMapping;
9use signstar_config::{CredentialsLoading, Error as ConfigError};
10use signstar_request_signature::{Request, Response, Sha512};
11
12/// Signstar signing error.
13#[derive(Debug, thiserror::Error)]
14enum Error {
15    /// Configuration does not contain key ID for the operator user.
16    #[error("No key ID set for the operator user")]
17    NoKeyId,
18
19    /// Loading configuration encountered errors.
20    #[error("Loading credentials encountered errors")]
21    HasUserIdErrors,
22
23    /// No credentials found for current system user.
24    #[error("No credentials for the system user")]
25    NoCredentials,
26
27    /// Parameters of the signing request are unsupported.
28    #[error("Unsupported signing request parameters")]
29    UnsupportedParameters,
30
31    /// Configuration error.
32    #[error("Config error")]
33    Config(#[from] ConfigError),
34
35    /// NetHSM error.
36    #[error("NetHsm error")]
37    NetHsm(#[from] nethsm::Error),
38
39    /// Signing request processing error.
40    #[error("Signing request error: {0}")]
41    SigningRequest(#[from] signstar_request_signature::Error),
42
43    /// A signstar-common logging error.
44    #[error(transparent)]
45    SignstarCommonLogging(#[from] signstar_common::logging::Error),
46}
47
48/// Creates a new [`NetHsm`] object with correct connection and user settings and returns it
49/// alongside with the [`KeyId`] that should be used for signing.
50///
51/// # Errors
52///
53/// Returns an error if configuration:
54/// - loading encounters errors
55/// - does not contain any key ID settings
56/// - does not contain NetHSM connections
57/// - does not contain credentials with a passphrase
58fn load_nethsm_keyid() -> Result<(NetHsm, KeyId), Error> {
59    let credentials_loading = CredentialsLoading::from_system_user()?;
60
61    if credentials_loading.has_userid_errors() {
62        return Err(Error::HasUserIdErrors);
63    }
64
65    if !credentials_loading.has_signing_user() {
66        return Err(Error::NoCredentials);
67    }
68
69    let key_id = if let UserMapping::SystemNetHsmOperatorSigning {
70        nethsm_key_setup, ..
71    } = credentials_loading.get_mapping().get_user_mapping()
72    {
73        nethsm_key_setup.get_key_id().clone()
74    } else {
75        return Err(Error::NoKeyId);
76    };
77
78    // Currently, this picks the first connection found.
79    // The Signstar setup assumes, that multiple backends are used in a round-robin fashion, but
80    // this is not yet implemented.
81    let connection = if let Some(connection) = credentials_loading
82        .get_mapping()
83        .get_connections()
84        .iter()
85        .next()
86    {
87        connection.clone()
88    } else {
89        return Err(Error::NoCredentials);
90    };
91
92    let credentials = credentials_loading.credentials_for_signing_user()?;
93
94    Ok((
95        NetHsm::new(connection, Some(credentials.into()), None, None)?,
96        key_id,
97    ))
98}
99
100/// Signs the signing request in `reader` and write the response to the `writer`.
101///
102/// # Errors
103///
104/// Returns an error if:
105///
106/// - logging cannot be set up,
107/// - a [`Request`] cannot be created from `reader`,
108/// - the [`Request`] does not use OpenPGP v4,
109/// - the [`Request`] is not version 1,
110/// - a [`Sha512`] hasher state can not be created from the [`Request`],
111/// - no [`NetHsm`] and [`KeyId`] can be retrieved for the calling user,
112/// - a signature can not be created over the hasher state,
113/// - or the [`Response`] can not be written to the `writer`.
114fn sign_request(reader: impl std::io::Read, writer: impl std::io::Write) -> Result<(), Error> {
115    let req = Request::from_reader(reader)?;
116
117    if !req.required.output.is_openpgp_v4() {
118        Err(Error::UnsupportedParameters)?;
119    }
120
121    if req.version.major != 1 {
122        Err(Error::UnsupportedParameters)?;
123    }
124
125    let hasher: Sha512 = req.required.input.try_into()?;
126
127    let (nethsm, key_id) = load_nethsm_keyid()?;
128
129    let signature = nethsm.openpgp_sign_state(&key_id, hasher)?;
130
131    Response::v1(signature).to_writer(writer)?;
132
133    Ok(())
134}
135
136#[derive(Debug, Parser)]
137struct Cli {
138    #[command(flatten)]
139    verbosity: Verbosity,
140}
141
142/// Signs the signing request on standard input and returns a signing response on standard output.
143fn main() -> ExitCode {
144    let args = Cli::parse();
145
146    if let Err(error) = signstar_common::logging::setup_logging(args.verbosity) {
147        eprintln!("{error}");
148        return ExitCode::FAILURE;
149    }
150
151    let result = sign_request(std::io::stdin(), std::io::stdout());
152
153    if let Err(error) = result {
154        log::error!(error:err; "Processing signing request failed: {error:#?}");
155        ExitCode::FAILURE
156    } else {
157        ExitCode::SUCCESS
158    }
159}