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}