Skip to main content

signstar_yubihsm2/automation/
command.rs

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