Skip to main content

signstar_yubihsm2/automation/
command.rs

1//! Scenario commands.
2
3#[cfg(feature = "cli")]
4use std::{fs::read, path::PathBuf};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8#[cfg(feature = "cli")]
9use signstar_crypto::passphrase::Passphrase;
10use yubihsm::{command::Code, wrap::Message};
11
12use crate::{
13    Credentials,
14    automation::CommandReturnValue,
15    object::{AuthenticationKey, Capabilities, Id, KeyInfo, ObjectId, WrapKey},
16};
17#[cfg(feature = "cli")]
18use crate::{
19    object::{WrapKeyFromPassphrase, WrapKeyKind},
20    user::FileBackedCredentials,
21};
22
23/// Indicates the setting of the auditing.
24#[derive(Clone, Copy, Debug)]
25#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
26#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
27pub enum AuditOption {
28    /// Auditing is enabled but can be disabled.
29    On,
30
31    /// Auditing is disabled.
32    Off,
33
34    /// Auditing is permanently enabled and cannot be disabled.
35    Fix,
36}
37
38impl From<AuditOption> for yubihsm::AuditOption {
39    fn from(value: AuditOption) -> Self {
40        match value {
41            AuditOption::On => Self::On,
42            AuditOption::Off => Self::Off,
43            AuditOption::Fix => Self::Fix,
44        }
45    }
46}
47
48/// The printable name of a [`Command`].
49#[derive(Debug, strum::Display)]
50#[strum(serialize_all = "snake_case")]
51#[cfg_attr(feature = "serde", derive(Serialize))]
52#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
53pub enum CommandName {
54    /// Query the device state.
55    DeviceInfo,
56
57    /// Reset the device to factory settings and reconnect afterwards.
58    ResetDeviceAndReconnect,
59
60    /// Query the command log of the device and print it to standard output.
61    GetLogEntries,
62
63    /// Change audit settings.
64    SetForceAuditOption,
65
66    /// Changes command audit settings.
67    SetCommandAuditOption,
68
69    /// Put authentication key on the device.
70    PutAuthenticationKey,
71
72    /// Generates a new asymmetric key on the device.
73    GenerateAsymmetricKey,
74
75    /// Signs data using a `ed25519` key.
76    SignEd25519,
77
78    /// Puts new wrapping key on the device.
79    PutWrapKey,
80
81    /// Export object under wrap (encrypted).
82    ExportWrapped,
83
84    /// Imports objects under wrap (encrypted).
85    ImportWrapped,
86
87    /// Permanently remove an object from the device.
88    DeleteObject,
89
90    /// Query data about the object and print it to standard output.
91    GetObjectInfo,
92}
93
94impl From<&Command> for CommandName {
95    fn from(value: &Command) -> Self {
96        match value {
97            Command::DeviceInfo => Self::DeviceInfo,
98            Command::ResetDeviceAndReconnect => Self::ResetDeviceAndReconnect,
99            Command::GetLogEntries => Self::GetLogEntries,
100            Command::SetForceAuditOption(_) => Self::SetForceAuditOption,
101            Command::SetCommandAuditOption { .. } => Self::SetCommandAuditOption,
102            Command::PutAuthenticationKey { .. } => Self::PutAuthenticationKey,
103            Command::GenerateAsymmetricKey { .. } => Self::GenerateAsymmetricKey,
104            Command::SignEd25519 { .. } => Self::SignEd25519,
105            Command::PutWrapKey { .. } => Self::PutWrapKey,
106            Command::ExportWrapped { .. } => Self::ExportWrapped,
107            Command::ImportWrapped { .. } => Self::ImportWrapped,
108            Command::DeleteObject(_) => Self::DeleteObject,
109            Command::GetObjectInfo(_) => Self::GetObjectInfo,
110        }
111    }
112}
113
114impl From<&CommandReturnValue> for CommandName {
115    fn from(value: &CommandReturnValue) -> Self {
116        match value {
117            CommandReturnValue::DeviceInfo(_) => Self::DeviceInfo,
118            CommandReturnValue::ResetDeviceAndReconnect => Self::ResetDeviceAndReconnect,
119            CommandReturnValue::GetLogEntries(_) => Self::GetLogEntries,
120            CommandReturnValue::SetForceAuditOption => Self::SetForceAuditOption,
121            CommandReturnValue::SetCommandAuditOption => Self::SetCommandAuditOption,
122            CommandReturnValue::PutAuthenticationKey { .. } => Self::PutAuthenticationKey,
123            CommandReturnValue::GenerateAsymmetricKey { .. } => Self::GenerateAsymmetricKey,
124            CommandReturnValue::SignEd25519 { .. } => Self::SignEd25519,
125            CommandReturnValue::PutWrapKey { .. } => Self::PutWrapKey,
126            CommandReturnValue::ExportWrapped { .. } => Self::ExportWrapped,
127            CommandReturnValue::ImportWrapped { .. } => Self::ImportWrapped,
128            CommandReturnValue::DeleteObject => Self::DeleteObject,
129            CommandReturnValue::GetObjectInfo(_) => Self::GetObjectInfo,
130        }
131    }
132}
133
134#[cfg(feature = "cli")]
135impl From<&FileBackedCommand> for CommandName {
136    fn from(value: &FileBackedCommand) -> Self {
137        match value {
138            FileBackedCommand::DeviceInfo => Self::DeviceInfo,
139            FileBackedCommand::ResetDeviceAndReconnect => Self::ResetDeviceAndReconnect,
140            FileBackedCommand::GetLogEntries => Self::GetLogEntries,
141            FileBackedCommand::SetForceAuditOption(_) => Self::SetForceAuditOption,
142            FileBackedCommand::SetCommandAuditOption { .. } => Self::SetCommandAuditOption,
143            FileBackedCommand::PutAuthenticationKey { .. } => Self::PutAuthenticationKey,
144            FileBackedCommand::GenerateAsymmetricKey { .. } => Self::GenerateAsymmetricKey,
145            FileBackedCommand::SignEd25519 { .. } => Self::SignEd25519,
146            FileBackedCommand::PutWrapKey { .. } => Self::PutWrapKey,
147            FileBackedCommand::ExportWrapped { .. } => Self::ExportWrapped,
148            FileBackedCommand::ImportWrapped { .. } => Self::ImportWrapped,
149            FileBackedCommand::DeleteObject(_) => Self::DeleteObject,
150            FileBackedCommand::GetObjectInfo(_) => Self::GetObjectInfo,
151        }
152    }
153}
154
155/// A single command that is atomically executed against a YubiHSM2.
156#[derive(Debug)]
157pub enum Command {
158    /// Query the device state.
159    DeviceInfo,
160
161    /// Reset the device to factory settings and reconnect afterwards.
162    ///
163    /// Note that this is a destructive operation and the authenticating user will need to have
164    /// appropriate capabilities.
165    ResetDeviceAndReconnect,
166
167    /// Query the command log of the device and print it to standard output.
168    GetLogEntries,
169
170    /// Change audit settings.
171    ///
172    /// This mode prevents the device from performing additional operations when the Logs and Error
173    /// Codes is full.
174    ///
175    /// See [Force Audit](https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#force-audit) for more details.
176    SetForceAuditOption(AuditOption),
177
178    /// Changes command audit settings.
179    ///
180    /// This is used to manage auditing options for specific commands. By default all commands are
181    /// logged.
182    ///
183    /// See [Force Audit](https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#command-audit) for more details.
184    #[allow(clippy::enum_variant_names)]
185    SetCommandAuditOption {
186        /// Command of which the setting should be changed.
187        command: Code,
188
189        /// New setting value.
190        setting: AuditOption,
191    },
192
193    /// Put authentication key on the device.
194    ///
195    /// This command is used to append new authentication keys.
196    PutAuthenticationKey {
197        /// The key identity and capabilities.
198        info: KeyInfo,
199
200        /// Additional delegated capabilities which would apply to objects that are created or
201        /// imported.
202        delegated_caps: Capabilities,
203
204        /// The authentication key to put onto the YubiHSM2.
205        authentication_key: AuthenticationKey,
206    },
207
208    /// Generates new `ed25519` signing key on the device.
209    GenerateAsymmetricKey {
210        /// The key identity and capabilities.
211        info: KeyInfo,
212    },
213
214    /// Signs data using provided `ed25519` key.
215    SignEd25519 {
216        /// The key to be used for signing.
217        key_id: Id,
218
219        /// Raw data blob which should be signed.
220        data: Vec<u8>,
221    },
222
223    /// Puts new wrapping key on the device.
224    ///
225    /// This command is used to append new wrapping keys which serve as encryption keys for other
226    /// objects.
227    PutWrapKey {
228        /// The key identity and capabilities.
229        info: KeyInfo,
230
231        /// Additional delegated capabilities which would apply to objects that are created or
232        /// imported.
233        delegated_caps: Capabilities,
234
235        /// The wrapping key.
236        wrapping_key: WrapKey,
237    },
238
239    /// Export object under wrap (encrypted).
240    ExportWrapped {
241        /// Wrapping key which should encrypt the exported object.
242        wrap_key_id: Id,
243
244        /// Object that will be exported.
245        object: ObjectId,
246    },
247
248    /// Imports objects under wrap (encrypted).
249    ImportWrapped {
250        /// Wrapping key which would decrypt the imported object.
251        wrap_key_id: Id,
252
253        /// The encrypted message which should be imported.
254        message: Message,
255    },
256
257    /// Permanently remove an object from the device.
258    DeleteObject(ObjectId),
259
260    /// Query data about the object and print it to standard output.
261    GetObjectInfo(ObjectId),
262}
263
264#[cfg(feature = "cli")]
265impl TryFrom<&FileBackedCommand> for Command {
266    type Error = crate::Error;
267
268    /// Creates a new [`Command`] from this [`FileBackedCommand`].
269    ///
270    /// # Errors
271    ///
272    /// Returns an error, if reading/creating the required data from input files fails.
273    fn try_from(value: &FileBackedCommand) -> Result<Self, Self::Error> {
274        Ok(match value {
275            FileBackedCommand::DeviceInfo => Command::DeviceInfo,
276            FileBackedCommand::ResetDeviceAndReconnect => Command::ResetDeviceAndReconnect,
277            FileBackedCommand::GetLogEntries => Command::GetLogEntries,
278            FileBackedCommand::SetForceAuditOption(audit_option) => {
279                Command::SetForceAuditOption(*audit_option)
280            }
281            FileBackedCommand::SetCommandAuditOption { command, setting } => {
282                Command::SetCommandAuditOption {
283                    command: (*command),
284                    setting: (*setting),
285                }
286            }
287            FileBackedCommand::PutAuthenticationKey {
288                info,
289                delegated_caps,
290                passphrase_file,
291            } => Command::PutAuthenticationKey {
292                info: info.clone(),
293                delegated_caps: delegated_caps.clone(),
294                authentication_key: AuthenticationKey::try_from(passphrase_file.as_path())?,
295            },
296            FileBackedCommand::GenerateAsymmetricKey { info } => {
297                Command::GenerateAsymmetricKey { info: info.clone() }
298            }
299            FileBackedCommand::SignEd25519 { key_id, data } => Command::SignEd25519 {
300                key_id: (*key_id),
301                data: data.to_vec(),
302            },
303            FileBackedCommand::PutWrapKey {
304                info,
305                delegated_caps,
306                passphrase_file,
307            } => Command::PutWrapKey {
308                info: info.clone(),
309                delegated_caps: delegated_caps.clone(),
310                wrapping_key: WrapKey::try_from(WrapKeyFromPassphrase::new(
311                    &Passphrase::try_from(passphrase_file.as_path())?,
312                    WrapKeyKind::Aes256,
313                )?)?,
314            },
315            FileBackedCommand::ExportWrapped {
316                wrap_key_id,
317                object,
318                wrapped_file: _,
319            } => Command::ExportWrapped {
320                wrap_key_id: (*wrap_key_id),
321                object: (*object),
322            },
323            FileBackedCommand::ImportWrapped {
324                wrap_key_id,
325                wrapped_file,
326            } => {
327                let message =
328                    Message::from_vec(read(wrapped_file.as_path()).map_err(|source| {
329                        Self::Error::IoPath {
330                            path: wrapped_file.clone(),
331                            context: "reading a file under wrap",
332                            source,
333                        }
334                    })?)
335                    .map_err(|source| Self::Error::InvalidWrap {
336                        context: "reading the wrapped file",
337                        source,
338                    })?;
339
340                Command::ImportWrapped {
341                    wrap_key_id: (*wrap_key_id),
342                    message,
343                }
344            }
345            FileBackedCommand::DeleteObject(id) => Command::DeleteObject(*id),
346            FileBackedCommand::GetObjectInfo(id) => Command::GetObjectInfo(*id),
347        })
348    }
349}
350
351/// A single command that is atomically executed against a YubiHSM2.
352///
353/// Different from [`Command`], this enum does not assign data directly in its variants, but instead
354/// relies on paths to files to read from or write to.
355#[derive(Debug)]
356#[cfg(feature = "cli")]
357#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
358#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
359pub enum FileBackedCommand {
360    /// Query the device state.
361    DeviceInfo,
362
363    /// Reset the device to factory settings and reconnect afterwards.
364    ///
365    /// Note that this is a destructive operation and the authenticating user will need to have
366    /// appropriate capabilities.
367    ResetDeviceAndReconnect,
368
369    /// Query the command log of the device and print it to standard output.
370    GetLogEntries,
371
372    /// Change audit settings.
373    ///
374    /// This mode prevents the device from performing additional operations when the Logs and Error
375    /// Codes is full.
376    ///
377    /// See [Force Audit](https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#force-audit) for more details.
378    SetForceAuditOption(AuditOption),
379
380    /// Changes command audit settings.
381    ///
382    /// This is used to manage auditing options for specific commands. By default all commands are
383    /// logged.
384    ///
385    /// See [Force Audit](https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#command-audit) for more details.
386    #[allow(clippy::enum_variant_names)]
387    SetCommandAuditOption {
388        /// Command of which the setting should be changed.
389        command: Code,
390
391        /// New setting value.
392        setting: AuditOption,
393    },
394
395    /// Put authentication key on the device.
396    ///
397    /// This command is used to append new authentication keys.
398    PutAuthenticationKey {
399        /// The key identity and capabilities.
400        #[cfg_attr(feature = "serde", serde(flatten))]
401        info: KeyInfo,
402
403        /// Additional delegated capabilities which would apply to objects that are created or
404        /// imported.
405        delegated_caps: Capabilities,
406
407        /// The file containing passphrase of the authenticating user.
408        passphrase_file: PathBuf,
409    },
410
411    /// Generates new `ed25519` signing key on the device.
412    GenerateAsymmetricKey {
413        /// The key identity and capabilities.
414        #[cfg_attr(feature = "serde", serde(flatten))]
415        info: KeyInfo,
416    },
417
418    /// Signs data using provided `ed25519` key.
419    SignEd25519 {
420        /// The key to be used for signing.
421        key_id: Id,
422
423        /// Raw data blob which should be signed.
424        data: Vec<u8>,
425    },
426
427    /// Puts new wrapping key on the device.
428    ///
429    /// This command is used to append new wrapping keys which serve as encryption keys for other
430    /// objects.
431    PutWrapKey {
432        /// The key identity and capabilities.
433        #[cfg_attr(feature = "serde", serde(flatten))]
434        info: KeyInfo,
435
436        /// Additional delegated capabilities which would apply to objects that are created or
437        /// imported.
438        delegated_caps: Capabilities,
439
440        /// The file containing the passphrase from which the wrapping key is generated.
441        passphrase_file: PathBuf,
442    },
443
444    /// Export object under wrap (encrypted).
445    ExportWrapped {
446        /// Wrapping key which should encrypt the exported object.
447        wrap_key_id: Id,
448
449        /// Object that will be exported.
450        #[cfg_attr(feature = "serde", serde(flatten))]
451        object: ObjectId,
452
453        /// Output file which will contain the exported object encrypted with the wrapping key.
454        wrapped_file: PathBuf,
455    },
456
457    /// Imports objects under wrap (encrypted).
458    ImportWrapped {
459        /// Wrapping key which would decrypt the imported object.
460        wrap_key_id: Id,
461
462        /// Input file which contains the imported object encrypted with the wrapping key.
463        wrapped_file: PathBuf,
464    },
465
466    /// Permanently remove an object from the device.
467    DeleteObject(ObjectId),
468
469    /// Query data about the object and print it to standard output.
470    GetObjectInfo(ObjectId),
471}
472
473/// A list of [`Command`]s that are run with a specific authentication.
474///
475/// A single [`Credentials`] is used for authentication of each command towards the YubiHSM2
476/// backend.
477#[derive(Debug)]
478pub struct AuthenticatedCommandChain {
479    auth: Credentials,
480    commands: Vec<Command>,
481}
482
483impl AuthenticatedCommandChain {
484    /// Creates a new [`AuthenticatedCommandChain`] from authentication data and a list of commands.
485    pub fn new(auth: Credentials, commands: Vec<Command>) -> Self {
486        Self { auth, commands }
487    }
488
489    /// Returns the authentication details for the authenticated commands.
490    pub fn auth(&self) -> &Credentials {
491        &self.auth
492    }
493
494    /// Returns the commands for the authenticated commands.
495    pub fn commands(&self) -> &[Command] {
496        &self.commands
497    }
498}
499
500/// A list of [`Command`]s that are run with a specific authentication.
501///
502/// A single [`FileBackedCredentials`] is used for authentication of each command towards the
503/// YubiHSM2 backend.
504#[cfg(feature = "cli")]
505#[derive(Debug)]
506#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
507pub struct FileBackedAuthenticatedCommandChain {
508    pub(crate) auth: FileBackedCredentials,
509    pub(crate) commands: Vec<FileBackedCommand>,
510}