signstar_config/nethsm/backend.rs
1//! Backend handling for [`NetHsm`].
2//!
3//! Based on a [`NetHsm`], [`NetHsmAdminCredentials`] and a [`Config`] this module offers
4//! the ability to populate a [`NetHsm`] backend with the help of the [`NetHsmBackend`] struct.
5//!
6//! Using [`NetHsmBackend::sync`] all users and keys configured in a [`Config`]
7//! are created and adapted to changes upon re-run.
8//! The state representation can be found in the [`nethsm::state`][`crate::nethsm::state`] module.
9//!
10//! # Note
11//!
12//! This module only works with data for the same iteration (i.e. the iteration of the
13//! [`NetHsmAdminCredentials`] and those of the [`NetHsm`] backend must match).
14
15use std::{collections::HashSet, str::FromStr};
16
17use log::{debug, trace, warn};
18use nethsm::{
19 CryptographicKeyContext,
20 FullCredentials,
21 KeyId,
22 KeyMechanism,
23 KeyType,
24 NamespaceId,
25 NetHsm,
26 OpenPgpKeyUsageFlags,
27 Passphrase,
28 SystemState,
29 Timestamp,
30 UserId,
31 UserRole,
32};
33use pgp::composed::{Deserializable, SignedPublicKey};
34
35use super::Error;
36use crate::{
37 NetHsmAdminCredentials,
38 config::{
39 Config,
40 UserBackendConnection,
41 UserBackendConnectionFilter,
42 state::{KeyCertificateState, KeyState, UserState},
43 },
44 nethsm::NetHsmUserKeysFilter,
45 state::StateType,
46};
47
48/// Creates all _R-Administrators_ on a [`NetHsm`].
49///
50/// If users exist already, only their passphrase is set.
51///
52/// # Note
53///
54/// Uses the `nethsm` with the [default
55/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`].
56///
57/// # Errors
58///
59/// Returns an error if
60///
61/// - the default [`Administrator`][`UserRole::Administrator`] can not be retrieved from
62/// `admin_credentials`,
63/// - the default [`Administrator`][`UserRole::Administrator`] credentials cannot be used with the
64/// `nethsm`,
65/// - available users of the `nethsm` cannot be retrieved,
66/// - or one of the admin credentials cannot be added, or updated.
67fn add_system_wide_admins(
68 nethsm: &NetHsm,
69 admin_credentials: &NetHsmAdminCredentials,
70) -> Result<(), crate::Error> {
71 debug!(
72 "Setup system-wide administrators (R-Administrators) on NetHSM backend at {}",
73 nethsm.get_url()
74 );
75
76 let default_admin = &admin_credentials.get_default_administrator()?.name;
77 nethsm.use_credentials(default_admin)?;
78 let available_users = nethsm.get_users()?;
79 trace!(
80 "Available users on NetHSM: {}",
81 available_users
82 .iter()
83 .map(|user| user.to_string())
84 .collect::<Vec<_>>()
85 .join(", ")
86 );
87
88 for user in admin_credentials.get_administrators().iter() {
89 // Only add if user doesn't exist yet, else set passphrase
90 if !available_users.contains(&user.name) {
91 nethsm.add_user(
92 format!("System-wide Admin {}", user.name),
93 UserRole::Administrator,
94 user.passphrase.clone(),
95 Some(user.name.clone()),
96 )?;
97 } else {
98 nethsm.set_user_passphrase(user.name.clone(), user.passphrase.clone())?;
99 }
100 }
101 Ok(())
102}
103
104/// Retrieves the first available user in the [`Administrator`][`UserRole::Administrator`]
105/// (*N-Administrator*) role in a namespace.
106///
107/// Derives a list of users in the [`Administrator`][`UserRole::Administrator`] role in `namespace`
108/// from `available_users`.
109/// Ensures that at least one of the users is available on the `nethsm`.
110///
111/// # Errors
112///
113/// Returns an error if
114/// - user information of an *N-Administrator* cannot be retrieved,
115/// - or no *N-Administrator* is available in the `namespace`.
116fn get_first_available_namespace_admin(
117 nethsm: &NetHsm,
118 admin_credentials: &NetHsmAdminCredentials,
119 available_users: &[UserId],
120 namespace: &NamespaceId,
121) -> Result<UserId, crate::Error> {
122 debug!("Get the first available N-Administrator in namespace \"{namespace}\"");
123
124 // Retrieve the list of users that are both in the namespace and match an entry in the list of
125 // N-Administrators in the administrative credentials.
126 let namespace_admins = available_users
127 .iter()
128 .filter(|user| {
129 user.namespace() == Some(namespace)
130 && admin_credentials
131 .get_namespace_administrators()
132 .iter()
133 .any(|creds| &creds.name == *user)
134 })
135 .cloned()
136 .collect::<Vec<UserId>>();
137
138 let mut checked_namespace_admins = Vec::new();
139 for namespace_admin in namespace_admins {
140 if Into::<UserRole>::into(nethsm.get_user(&namespace_admin)?.role)
141 == UserRole::Administrator
142 {
143 checked_namespace_admins.push(namespace_admin);
144 }
145 }
146
147 debug!(
148 "All N-Administrators in namespace \"{namespace}\": {}",
149 checked_namespace_admins
150 .iter()
151 .map(|user| user.to_string())
152 .collect::<Vec<String>>()
153 .join(", ")
154 );
155
156 if checked_namespace_admins.is_empty() {
157 return Err(Error::NamespaceHasNoAdmin {
158 namespace: namespace.clone(),
159 url: nethsm.get_url(),
160 }
161 .into());
162 }
163
164 // Select the first N-Administrator in the namespace.
165 let Some(admin) = checked_namespace_admins.first() else {
166 return Err(Error::NamespaceHasNoAdmin {
167 namespace: namespace.clone(),
168 url: nethsm.get_url(),
169 }
170 .into());
171 };
172
173 Ok(admin.clone())
174}
175
176/// Sets up all _N-Administrators_ and their respective namespaces.
177///
178/// # Note
179///
180/// This function uses the `nethsm` with the [default
181/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
182/// namespace-specific _N-Administrator_ for individual operations.
183/// If this function succeeds, the `nethsm` is guaranteed to use the [default
184/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
185/// If this function fails, the `nethsm` may still use a namespace-specific _N-Administrator_.
186///
187/// # Errors
188///
189/// Returns an error if
190///
191/// - user information cannot be retrieved from the `nethsm`,
192/// - the available namespaces cannot be retrieved from the `nethsm`,
193/// - one of the N-Administrators in the `admin_credentials` is not in a namespace,
194/// - a namespace exists already, but no known N-Administrator is available for it,
195/// - an N-Administrator and its namespace exist already, but that user's passphrase cannot be set,
196/// - an N-Administrator does not yet exist and cannot be added,
197/// - a namespace does not yet exist and cannot be added,
198/// - or switching back to the default R-Administrator credentials fails.
199fn add_namespace_admins(
200 nethsm: &NetHsm,
201 admin_credentials: &NetHsmAdminCredentials,
202) -> Result<(), crate::Error> {
203 debug!(
204 "Setup namespace administrators (N-Administrators) on NetHSM backend at {}",
205 nethsm.get_url()
206 );
207
208 // Use the default R-Administrator for authentication to the backend by default.
209 let default_admin = &admin_credentials.get_default_administrator()?.name;
210 nethsm.use_credentials(default_admin)?;
211
212 let available_users = nethsm.get_users()?;
213 trace!(
214 "The available users on the NetHSM backend at {} are: {}",
215 nethsm.get_url(),
216 available_users
217 .iter()
218 .map(|user| user.to_string())
219 .collect::<Vec<String>>()
220 .join(", ")
221 );
222 let available_namespaces = nethsm.get_namespaces()?;
223 trace!(
224 "The available namespaces on the NetHSM backend at {} are: {}",
225 nethsm.get_url(),
226 available_namespaces
227 .iter()
228 .map(|namespace| namespace.to_string())
229 .collect::<Vec<String>>()
230 .join(", ")
231 );
232
233 // Extract the namespace from each namespace administrator found in the administrative
234 // credentials.
235 for user in admin_credentials.get_namespace_administrators() {
236 let Some(namespace) = user.name.namespace() else {
237 return Err(Error::NamespaceAdminHasNoNamespace {
238 user: user.name.clone(),
239 }
240 .into());
241 };
242
243 let namespace_exists = available_namespaces.contains(namespace);
244 if namespace_exists {
245 // Select the first available N-Administrator credentials for interacting with the
246 // NetHSM backend.
247 // This might be the targeted user itself!
248 nethsm.use_credentials(&get_first_available_namespace_admin(
249 nethsm,
250 admin_credentials,
251 &available_users,
252 namespace,
253 )?)?;
254 }
255
256 // If the list of available users on the NetHSM does not include the given N-Administrator,
257 // we create the user.
258 if available_users.contains(&user.name) {
259 // Set the passphrase of the user.
260 nethsm.set_user_passphrase(user.name.clone(), user.passphrase.clone())?;
261 } else {
262 nethsm.add_user(
263 format!("Namespace Admin {}", user.name),
264 UserRole::Administrator,
265 user.passphrase.clone(),
266 Some(user.name.clone()),
267 )?;
268
269 // If the namespace does not yet exist add the namespace (authenticated as the default
270 // R-Administrator).
271 if !namespace_exists {
272 nethsm.add_namespace(namespace)?;
273 }
274 }
275 // Always use the default R-Administrator again.
276 nethsm.use_credentials(default_admin)?;
277 }
278
279 Ok(())
280}
281
282/// Sets up all system-wide, non-administrative users based on provided credentials.
283///
284/// # Note
285///
286/// It is assumed that the [default
287/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] and system-wide keys are
288/// already set up, before calling this function (see `add_system_wide_admins` and
289/// `add_system_wide_keys`, respectively).
290///
291/// This function uses the `nethsm` with the [default
292/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] and is guaranteed to do
293/// so when it finishes.
294///
295/// # Errors
296///
297/// Returns an error if
298///
299/// - there are no matching credentials in `user_credentials` for a user in the list of all
300/// available system-wide, non-administrative users,
301/// - a user exists already, but its passphrase cannot be set,
302/// - a user does not yet exist and it cannot be added,
303/// - a user has a tag and deleting it fails,
304/// - or adding a tag to a user fails.
305fn add_non_administrative_users(
306 nethsm: &NetHsm,
307 admin_credentials: &NetHsmAdminCredentials,
308 users: &[UserBackendConnection],
309 user_credentials: &[FullCredentials],
310) -> Result<(), crate::Error> {
311 debug!(
312 "Setup non-administrative, system-wide users on NetHSM backend at {}",
313 nethsm.get_url()
314 );
315
316 let default_admin = &admin_credentials.get_default_administrator()?.name;
317 nethsm.use_credentials(default_admin)?;
318 let available_users = nethsm.get_users()?;
319 debug!("Available users: {available_users:?}");
320
321 let user_data_list = users
322 .iter()
323 .filter_map(|user_backend_connection| match user_backend_connection {
324 UserBackendConnection::NetHsm { mapping, .. } => {
325 let mut user_data_set = mapping.nethsm_user_data();
326 // We are only interested in mappings that define at least one system-wide,
327 // non-administrative NetHSM backend user.
328 user_data_set.retain(|data| {
329 !data.user.is_namespaced() && data.role != UserRole::Administrator
330 });
331 if user_data_set.is_empty() {
332 return None;
333 }
334
335 Some(user_data_set)
336 }
337 // We are only interested in user mappings for NetHSM.
338 #[cfg(feature = "yubihsm2")]
339 _ => None,
340 })
341 .flatten()
342 .collect::<Vec<_>>();
343
344 for user_data in user_data_list {
345 let Some(creds) = user_credentials
346 .iter()
347 .find(|creds| &creds.name == user_data.user)
348 else {
349 return Err(Error::UserMissingPassphrase {
350 user: user_data.user.clone(),
351 }
352 .into());
353 };
354
355 if available_users.contains(user_data.user) {
356 nethsm.set_user_passphrase(user_data.user.clone(), creds.passphrase.clone())?;
357 } else {
358 nethsm.add_user(
359 format!("{} user {}", user_data.role, user_data.user),
360 user_data.role,
361 creds.passphrase.clone(),
362 Some(user_data.user.clone()),
363 )?;
364 }
365
366 if user_data.role == UserRole::Operator {
367 // First, delete all existing tags from user.
368 for available_tag in nethsm.get_user_tags(user_data.user)? {
369 nethsm.delete_user_tag(user_data.user, available_tag.as_str())?;
370 }
371 // Then, add optional tag to user.
372 if let Some(tag) = user_data.tag {
373 nethsm.add_user_tag(user_data.user, tag)?;
374 }
375 }
376 }
377
378 Ok(())
379}
380
381/// Sets up all namespaced non-administrative users.
382///
383/// # Note
384///
385/// It is assumed that _N-Administrators_ and namespaced keys are already set up, before calling
386/// this function (see `add_namespace_admins` and `add_namespaced_keys`, respectively).
387///
388/// This function uses the `nethsm` with the [default
389/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
390/// namespace-specific _N-Administrator_ for individual operations.
391/// If this function succeeds, the `nethsm` is guaranteed to use the [default
392/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
393/// If this function fails, the `nethsm` may still use a namespace-specific _N-Administrator_.
394///
395/// # Errors
396///
397/// Returns an error if
398///
399/// - a namespaced user is not in a namespace,
400/// - the namespace of a user does not exist,
401/// - the namespace of a user exists, but no usable *N-Administrator* for it is known,
402/// - there are no matching credentials in `user_credentials` for a user in the list of all,
403/// - a user exists already, but its passphrase cannot be set,
404/// - a user does not yet exist and cannot be created,
405/// - a tag cannot be removed from a user,
406/// - or a tag cannot be added to a user.
407fn add_namespaced_non_administrative_users(
408 nethsm: &NetHsm,
409 admin_credentials: &NetHsmAdminCredentials,
410 users: &[UserBackendConnection],
411 user_credentials: &[FullCredentials],
412) -> Result<(), crate::Error> {
413 debug!(
414 "Setup non-administrative, namespaced users on NetHSM backend at {}",
415 nethsm.get_url()
416 );
417
418 // Use the default R-Administrator for authentication to the backend by default.
419 let default_admin = &admin_credentials.get_default_administrator()?.name;
420 nethsm.use_credentials(default_admin)?;
421
422 let available_users = nethsm.get_users()?;
423 let available_namespaces = nethsm.get_namespaces()?;
424 let user_data_list = users
425 .iter()
426 .filter_map(|user_backend_connection| match user_backend_connection {
427 UserBackendConnection::NetHsm { mapping, .. } => {
428 let mut user_data_set = mapping.nethsm_user_data();
429 // We are only interested in mappings that define at least one namespaced,
430 // non-administrative NetHSM backend user.
431 user_data_set.retain(|data| {
432 data.user.is_namespaced() && data.role != UserRole::Administrator
433 });
434 if user_data_set.is_empty() {
435 return None;
436 }
437
438 Some(user_data_set)
439 }
440 // We are only interested in user mappings for NetHSM.
441 #[cfg(feature = "yubihsm2")]
442 _ => None,
443 })
444 .flatten()
445 .collect::<Vec<_>>();
446
447 for user_data in user_data_list {
448 // Extract the namespace of the user and ensure that the namespace exists already.
449 let Some(namespace) = user_data.user.namespace() else {
450 return Err(Error::NamespaceUserNoNamespace {
451 user: user_data.user.clone(),
452 }
453 .into());
454 };
455 if !available_namespaces.contains(namespace) {
456 return Err(Error::NamespaceMissing {
457 namespace: namespace.clone(),
458 }
459 .into());
460 }
461
462 // Select the first available N-Administrator credentials for interacting with the
463 // NetHSM backend.
464 nethsm.use_credentials(&get_first_available_namespace_admin(
465 nethsm,
466 admin_credentials,
467 &available_users,
468 namespace,
469 )?)?;
470
471 // Retrieve credentials for the specific user.
472 let Some(creds) = user_credentials
473 .iter()
474 .find(|creds| &creds.name == user_data.user)
475 else {
476 return Err(Error::UserMissingPassphrase {
477 user: user_data.user.clone(),
478 }
479 .into());
480 };
481
482 // If the user exists already, only set its passphrase, otherwise create it.
483 if available_users.contains(user_data.user) {
484 nethsm.set_user_passphrase(user_data.user.clone(), creds.passphrase.clone())?;
485 } else {
486 nethsm.add_user(
487 format!("{} user {}", user_data.role, user_data.user),
488 user_data.role,
489 creds.passphrase.clone(),
490 Some(user_data.user.clone()),
491 )?;
492 }
493
494 if user_data.role == UserRole::Operator {
495 // First, delete all existing tags from user.
496 for available_tag in nethsm.get_user_tags(user_data.user)? {
497 nethsm.delete_user_tag(user_data.user, available_tag.as_str())?;
498 }
499 // Then, add optional tag to user.
500 if let Some(tag) = user_data.tag {
501 nethsm.add_user_tag(user_data.user, tag)?;
502 }
503 }
504 }
505
506 // Always use the default R-Administrator again.
507 nethsm.use_credentials(default_admin)?;
508
509 Ok(())
510}
511
512/// Comparable components of a key setup between a [`NetHsm`] backend and a Signstar config.
513struct KeySetupComparison {
514 /// The type of state, that the data originates from.
515 pub state_type: StateType,
516 /// The key type of the setup.
517 pub key_type: KeyType,
518 /// The key mechanisms of the setup.
519 pub key_mechanisms: HashSet<KeyMechanism>,
520}
521
522/// Compares the key setups of a key from a Signstar config and that of a NetHSM backend.
523///
524/// Compares the [`KeyType`] and [`KeyMechanism`]s of `key_setup_a` and `key_setup_b`, which both
525/// have to be identical.
526///
527/// Emits a warning if the [`KeyType`] or list of [`KeyMechanism`]s of `key_setup_a` and
528/// `key_setup_b` do not match.
529fn compare_key_setups(
530 key_id: &KeyId,
531 namespace: Option<&NamespaceId>,
532 key_setup_a: KeySetupComparison,
533 key_setup_b: KeySetupComparison,
534) {
535 let namespace = if let Some(namespace) = namespace {
536 format!(" in namespace \"{namespace}\"")
537 } else {
538 "".to_string()
539 };
540 debug!(
541 "Compare key setup of key \"{key_id}\"{namespace} in {} (A) and {} (B)",
542 key_setup_a.state_type, key_setup_b.state_type
543 );
544
545 // Compare key type and warn about mismatches.
546 if key_setup_b.key_type != key_setup_a.key_type {
547 warn!(
548 "Key type mismatch of key \"{key_id}\"{namespace}:\n{} (A): {}\n{} (B) backend: {}!",
549 key_setup_a.state_type,
550 key_setup_a.key_type,
551 key_setup_b.state_type,
552 key_setup_b.key_type
553 );
554 }
555
556 // Compare key mechanisms and warn about mismatches.
557 if key_setup_b.key_mechanisms != key_setup_a.key_mechanisms {
558 warn!(
559 "Key mechanisms mismatch for key \"{key_id}\"{namespace}:\n{} (A): {}\n{} (B): {}!",
560 key_setup_a.state_type,
561 key_setup_a
562 .key_mechanisms
563 .iter()
564 .map(|mechanism| mechanism.to_string())
565 .collect::<Vec<String>>()
566 .join(", "),
567 key_setup_b.state_type,
568 key_setup_b
569 .key_mechanisms
570 .iter()
571 .map(|mechanism| mechanism.to_string())
572 .collect::<Vec<String>>()
573 .join(", "),
574 );
575 }
576}
577
578/// Sets up all system-wide keys.
579///
580/// Creates any missing keys and adds the configured tags for all of them.
581/// If keys exist already, deletes all tags and adds the configured ones for them.
582///
583/// # Note
584///
585/// It is assumed that all required _R-Administrators_ have already been set up (see
586/// `add_system_wide_admins`) before calling this function.
587///
588/// This function uses the `nethsm` with the [default
589/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`].
590///
591/// This function does not fail on mismatching keys, as it is assumed that keys are added
592/// intentionally and should not be deleted or altered.
593/// However, warnings are emitted if an existing key has a mismatching [`KeyType`] or
594/// [`KeyMechanisms`][`KeyMechanism`] from what is configured in the Signstar configuration file.
595///
596/// # Errors
597///
598/// Returns an error if
599///
600/// - the default system-wide *R-Administrator* cannot be retrieved or used for authentication,
601/// - the list of available keys on the NetHSM backend cannot be retrieved,
602/// - information about a single key cannot be retrieved from the NetHSM backend,
603/// - if a tag cannot be removed from an existing key,
604/// - if a tag cannot be added to an existing key,
605/// - or if a missing key cannot be created.
606fn add_system_wide_keys(
607 nethsm: &NetHsm,
608 admin_credentials: &NetHsmAdminCredentials,
609 users: &[UserBackendConnection],
610) -> Result<(), crate::Error> {
611 debug!(
612 "Setup system-wide cryptographic keys on NetHSM backend at {}",
613 nethsm.get_url()
614 );
615
616 // Use the default R-Administrator for authentication to the backend by default.
617 let default_admin = &admin_credentials.get_default_administrator()?.name;
618 nethsm.use_credentials(default_admin)?;
619
620 let available_keys = nethsm.get_keys(None)?;
621
622 for user_backend_connection in users {
623 let user_key_data = match user_backend_connection {
624 UserBackendConnection::NetHsm { mapping, .. } => {
625 let Some(user_key_data) =
626 mapping.nethsm_user_key_data(NetHsmUserKeysFilter::SystemWide)
627 else {
628 // We are only interested in mappings that define key data.
629 continue;
630 };
631
632 user_key_data
633 }
634 // We are only interested in user mappings for NetHSM.
635 #[cfg(feature = "yubihsm2")]
636 _ => continue,
637 };
638
639 if available_keys.contains(user_key_data.key_id) {
640 // Retrieve information about the key.
641 let info = nethsm.get_key(user_key_data.key_id)?;
642
643 // Compare the key setups.
644 compare_key_setups(
645 user_key_data.key_id,
646 None,
647 KeySetupComparison {
648 state_type: StateType::SignstarConfigNetHsm,
649 key_type: user_key_data.key_setup.key_type(),
650 key_mechanisms: HashSet::from_iter(
651 user_key_data.key_setup.key_mechanisms().to_vec(),
652 ),
653 },
654 KeySetupComparison {
655 state_type: StateType::NetHsm,
656 key_type: info
657 .r#type
658 .try_into()
659 .map_err(nethsm::Error::SignstarCryptoKey)?,
660 key_mechanisms: info
661 .mechanisms
662 .iter()
663 .filter_map(|mechanism| mechanism.try_into().ok())
664 .collect(),
665 },
666 );
667
668 // Remove all existing tags.
669 if let Some(available_tags) = info.restrictions.tags {
670 for available_tag in available_tags {
671 nethsm.delete_key_tag(user_key_data.key_id, available_tag.as_str())?;
672 }
673 }
674 // Add the required tag to the key.
675 nethsm.add_key_tag(user_key_data.key_id, user_key_data.tag)?;
676 } else {
677 // Add the key, including the required tag.
678 nethsm.generate_key(
679 user_key_data.key_setup.key_type(),
680 user_key_data.key_setup.key_mechanisms().to_vec(),
681 user_key_data.key_setup.key_length(),
682 Some(user_key_data.key_id.clone()),
683 Some(vec![user_key_data.tag.to_string()]),
684 )?;
685 }
686 }
687
688 Ok(())
689}
690
691/// Sets up all namespaced keys and tags them.
692///
693/// Creates any missing keys and adds the configured tags for all of them.
694/// If keys exist already, deletes all tags and adds the configured ones for them.
695///
696/// # Note
697///
698/// It is assumed that _N-Administrators_ have already been set up, before calling
699/// this function (see `add_namespace_admins`).
700///
701/// This function uses the `nethsm` with the [default
702/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
703/// namespace-specific _N-Administrator_ for individual operations.
704/// If this function succeeds, the `nethsm` is guaranteed to use the [default
705/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
706/// If this function fails, the `nethsm` may still use a namespace-specific _N-Administrator_.
707///
708/// This function does not fail on mismatching keys, as it is assumed that keys are added
709/// intentionally and should not be deleted/altered.
710/// However, warnings are emitted if an existing key has a mismatching key type or key mechanisms
711/// from what is configured in the Signstar configuration file.
712///
713/// Opposite to the behavior of `add_system_wide_keys`, this function does not delete any tags from
714/// keys.
715/// This is due to [a bug in the NetHSM firmware], which leads to a crash when adding a tag to a
716/// key, trying to remove and then re-adding it again.
717///
718/// # Errors
719///
720/// Returns an error if
721///
722/// - the default system-wide *R-Administrator* cannot be retrieved or used for authentication,
723/// - retrieving the list of available users from the NetHSM backend fails,
724/// - a namespaced user mapped to a key is not in a namespace,
725/// - no usable *N-Administrator* for a namespace is known,
726/// - the available keys in the namespace cannot be retrieved,
727/// - information about a specific key in the namespace cannot be retrieved,
728/// - a tag cannot be added to an already existing key,
729/// - a new key cannot be generated,
730/// - or using the default system-wide administrator again fails.
731///
732/// [a bug in the NetHSM firmware]: https://github.com/Nitrokey/nethsm/issues/13
733fn add_namespaced_keys(
734 nethsm: &NetHsm,
735 admin_credentials: &NetHsmAdminCredentials,
736 users: &[UserBackendConnection],
737) -> Result<(), crate::Error> {
738 debug!(
739 "Setup namespaced cryptographic keys on NetHSM backend at {}",
740 nethsm.get_url()
741 );
742
743 // Use the default R-Administrator for authentication to the backend by default.
744 let default_admin = &admin_credentials.get_default_administrator()?.name;
745 nethsm.use_credentials(default_admin)?;
746
747 let available_users = nethsm.get_users()?;
748
749 for user_backend_connection in users {
750 let user_key_data = match user_backend_connection {
751 UserBackendConnection::NetHsm { mapping, .. } => {
752 let Some(user_key_data) =
753 mapping.nethsm_user_key_data(NetHsmUserKeysFilter::Namespaced)
754 else {
755 // We are only interested in mappings that define key data.
756 continue;
757 };
758
759 user_key_data
760 }
761 // We are only interested in user mappings for NetHSM.
762 #[cfg(feature = "yubihsm2")]
763 _ => continue,
764 };
765
766 debug!(
767 "Set up key \"{}\" with tag {} for user {}",
768 user_key_data.key_id, user_key_data.tag, user_key_data.user
769 );
770
771 // Extract the namespace from the user or return an error.
772 let Some(namespace) = user_key_data.user.namespace() else {
773 // Note: Returning this error is not really possible, as we are explicitly
774 // requesting tuples of namespaced user, key setup and tag.
775 return Err(Error::NamespaceUserNoNamespace {
776 user: user_key_data.user.clone(),
777 }
778 .into());
779 };
780
781 // Select the first available N-Administrator credentials for interacting with the
782 // NetHSM backend.
783 nethsm.use_credentials(&get_first_available_namespace_admin(
784 nethsm,
785 admin_credentials,
786 &available_users,
787 namespace,
788 )?)?;
789
790 let available_keys = nethsm.get_keys(None)?;
791
792 if available_keys.contains(user_key_data.key_id) {
793 let key_info = nethsm.get_key(user_key_data.key_id)?;
794
795 // Compare the key setups.
796 compare_key_setups(
797 user_key_data.key_id,
798 Some(namespace),
799 KeySetupComparison {
800 state_type: StateType::SignstarConfigNetHsm,
801 key_type: user_key_data.key_setup.key_type(),
802 key_mechanisms: HashSet::from_iter(
803 user_key_data.key_setup.key_mechanisms().to_vec(),
804 ),
805 },
806 KeySetupComparison {
807 state_type: StateType::NetHsm,
808 key_type: key_info
809 .r#type
810 .try_into()
811 .map_err(nethsm::Error::SignstarCryptoKey)?,
812 key_mechanisms: key_info
813 .mechanisms
814 .iter()
815 .filter_map(|mechanism| mechanism.try_into().ok())
816 .collect(),
817 },
818 );
819
820 // If there are tags already, check if the tag we are looking for is already set and
821 // if so, skip to the next key.
822 if let Some(available_tags) = key_info.restrictions.tags {
823 debug!(
824 "Available tags for key \"{}\" in namespace {namespace}: {}",
825 user_key_data.key_id,
826 available_tags.join(", ")
827 );
828 // NOTE: If the required tag is already set, continue to the next key.
829 // Without this we otherwise trigger a bug in the NetHSM firmware which
830 // breaks the connection after re-adding the tag for the key further down.
831 // (i.e. "Bad Status: HTTP version did not start with HTTP/")
832 // See https://github.com/Nitrokey/nethsm/issues/13 for details.
833 if available_tags.len() == 1
834 && available_tags
835 .iter()
836 .find(|tag| tag.as_str() == user_key_data.tag)
837 .is_some()
838 {
839 continue;
840 }
841 }
842
843 // Add the tag to the key.
844 nethsm.add_key_tag(user_key_data.key_id, user_key_data.tag)?;
845 } else {
846 // Add the key, including the required tag.
847 nethsm.generate_key(
848 user_key_data.key_setup.key_type(),
849 user_key_data.key_setup.key_mechanisms().to_vec(),
850 user_key_data.key_setup.key_length(),
851 Some(user_key_data.key_id.clone()),
852 Some(vec![user_key_data.tag.to_string()]),
853 )?;
854 }
855 }
856
857 // Always use the default R-Administrator again.
858 nethsm.use_credentials(default_admin)?;
859
860 Ok(())
861}
862
863/// Adds OpenPGP certificates for system-wide keys that are used for OpenPGP signing.
864///
865/// # Note
866///
867/// It is assumed that the [default
868/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], all system-wide keys
869/// and all system-wide non-administrative users are already set up, before calling this function
870/// (see `add_system_wide_admins`, `add_system_wide_keys` and `add_non_administrative_users`,
871/// respectively).
872///
873/// This function uses the `nethsm` with the [default
874/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
875/// system-wide, non-administrative user for individual operations.
876/// If this function succeeds, the `nethsm` is guaranteed to use the [default
877/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
878/// If this function fails, the `nethsm` may still use a system-wide, non-administrative user.
879///
880/// This function does not overwrite or alter existing OpenPGP certificates, as this would introduce
881/// inconsistencies between signatures created with a previous version of a certificate and those
882/// created with a new version of the certificate, which is hard to debug.
883///
884/// # Errors
885///
886/// Returns an error if
887///
888/// - using the default *R-Administrator* fails,
889/// - retrieving the names of all system-wide users fails,
890/// - retrieving the names of all system-wide keys fails,
891/// - a user used for OpenPGP signing does not exist,
892/// - the tags assigned to a user cannot be retrieved from the `nethsm`,
893/// - a user used for OpenPGP signing does not have a required tag,
894/// - a key used for OpenPGP signing does not exist,
895/// - the tags assigned to a key cannot be retrieved from the `nethsm`,
896/// - a key used for OpenPGP signing does not have a required tag,
897/// - the key setup for a key used for OpenPGP signing does not have at least one User ID,
898/// - the user assigned the same tag as the key that is used for OpenPGP signing cannot be used to
899/// create an OpenPGP certificate for the key,
900/// - or the default *R-Administrator* cannot be used to import the generated OpenPGP certificate
901/// for the key.
902fn add_system_wide_openpgp_certificates(
903 nethsm: &NetHsm,
904 admin_credentials: &NetHsmAdminCredentials,
905 users: &[UserBackendConnection],
906) -> Result<(), crate::Error> {
907 debug!(
908 "Setup OpenPGP certificates for system-wide cryptographic keys on NetHSM backend at {}",
909 nethsm.get_url()
910 );
911
912 // Use the default R-Administrator for authentication to the backend by default.
913 let default_admin = &admin_credentials.get_default_administrator()?.name;
914 nethsm.use_credentials(default_admin)?;
915
916 let available_users = nethsm.get_users()?;
917
918 for user_backend_connection in users {
919 let user_key_data = match user_backend_connection {
920 UserBackendConnection::NetHsm { mapping, .. } => {
921 let Some(user_key_data) =
922 mapping.nethsm_user_key_data(NetHsmUserKeysFilter::SystemWide)
923 else {
924 // We are only interested in mappings that define key data.
925 continue;
926 };
927
928 user_key_data
929 }
930 // We are only interested in user mappings for NetHSM.
931 #[cfg(feature = "yubihsm2")]
932 _ => continue,
933 };
934
935 // Get OpenPGP User IDs and version or continue to the next user/key setup if the
936 // mapping is not used for OpenPGP signing.
937 let CryptographicKeyContext::OpenPgp { user_ids, version } =
938 user_key_data.key_setup.key_context()
939 else {
940 debug!(
941 "Skip creating an OpenPGP certificate for the key \"{}\" used by user \"{}\" as it is not used in an OpenPGP context.",
942 user_key_data.key_id, user_key_data.user,
943 );
944 continue;
945 };
946
947 // Ensure the targeted user exists.
948 if !available_users.contains(user_key_data.user) {
949 return Err(Error::UserMissing {
950 user_id: user_key_data.user.clone(),
951 }
952 .into());
953 }
954 // Ensure the required tag is assigned to the targeted user.
955 if nethsm
956 .get_user_tags(user_key_data.user)?
957 .iter()
958 .find(|tag| tag.as_str() == user_key_data.tag)
959 .is_none()
960 {
961 return Err(Error::UserMissingTag {
962 user_id: user_key_data.user.clone(),
963 tag: user_key_data.tag.to_string(),
964 }
965 .into());
966 }
967
968 let available_keys = nethsm.get_keys(None)?;
969
970 // Ensure the targeted key exists.
971 if !available_keys.contains(user_key_data.key_id) {
972 return Err(Error::KeyMissing {
973 key_id: user_key_data.key_id.clone(),
974 }
975 .into());
976 }
977 // Ensure the required tag is assigned to the targeted key.
978 if !nethsm
979 .get_key(user_key_data.key_id)?
980 .restrictions
981 .tags
982 .is_some_and(|tags| {
983 tags.iter()
984 .find(|tag| tag.as_str() == user_key_data.tag)
985 .is_some()
986 })
987 {
988 return Err(Error::KeyIsMissingTag {
989 key_id: user_key_data.key_id.clone(),
990 tag: user_key_data.tag.to_string(),
991 }
992 .into());
993 }
994
995 // Create the OpenPGP certificate if it does not exist yet.
996 if nethsm.get_key_certificate(user_key_data.key_id)?.is_none() {
997 // Ensure the first OpenPGP User ID exists.
998 let Some(user_id) = user_ids.first() else {
999 return Err(Error::OpenPgpUserIdMissing {
1000 key_id: user_key_data.key_id.clone(),
1001 }
1002 .into());
1003 };
1004
1005 // Switch to the dedicated user with access to the key to create an OpenPGP
1006 // certificate for the key.
1007 nethsm.use_credentials(user_key_data.user)?;
1008 let data = nethsm.create_openpgp_cert(
1009 user_key_data.key_id,
1010 OpenPgpKeyUsageFlags::default(),
1011 user_id.clone(),
1012 Timestamp::now(),
1013 *version,
1014 )?;
1015
1016 // Switch back to the default R-Administrator for the import of the OpenPGP
1017 // certificate.
1018 nethsm.use_credentials(default_admin)?;
1019 nethsm.import_key_certificate(user_key_data.key_id, data)?;
1020 }
1021 }
1022
1023 // Always use the default R-Administrator again.
1024 nethsm.use_credentials(default_admin)?;
1025
1026 Ok(())
1027}
1028
1029/// Adds OpenPGP certificates for namespaced keys that are used for OpenPGP signing.
1030///
1031/// # Note
1032///
1033/// It is assumed that the [default
1034/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], all namespaced keys,
1035/// all _N-Administrators_ and all namespaced non-administrative users are already set up, before
1036/// calling this function (see `add_system_wide_admins`, `add_namespaced_keys`,
1037/// `add_namespace_admins` and `add_namespaced_non_administrative_users`, respectively).
1038///
1039/// This function uses the `nethsm` with the [default
1040/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
1041/// namespace-specific _N-Administrator_ or non-administrative user for individual operations.
1042/// If this function succeeds, the `nethsm` is guaranteed to use the [default
1043/// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
1044/// If this function fails, the `nethsm` may still use a namespace-specific _N-Administrator_ or
1045/// non-administrative user.
1046///
1047/// This function does not overwrite or alter existing OpenPGP certificates, as this would introduce
1048/// inconsistencies between signatures created with a previous version of a certificate and those
1049/// created with a new version of the certificate, which is hard to debug.
1050///
1051/// # Errors
1052///
1053/// Returns an error if
1054///
1055/// - using the default *R-Administrator* fails,
1056/// - retrieving the names of all users fails,
1057/// - a namespaced user is not in a namespace,
1058/// - no usable *N-Administrator* for a namespace is known,
1059/// - a user used for OpenPGP signing does not exist,
1060/// - the tags assigned to a user cannot be retrieved from the `nethsm`,
1061/// - a user used for OpenPGP signing does not have a required tag,
1062/// - retrieving the names of all keys in a namespace fails,
1063/// - a key used for OpenPGP signing does not exist,
1064/// - the tags assigned to a key cannot be retrieved from the `nethsm`,
1065/// - a key used for OpenPGP signing does not have a required tag,
1066/// - the key setup for a key used for OpenPGP signing does not have at least one User ID,
1067/// - the user assigned the same tag as the key that is used for OpenPGP signing cannot be used to
1068/// create an OpenPGP certificate for the key,
1069/// - or the *N-Administrator* cannot be used to import the generated OpenPGP certificate for the
1070/// key.
1071fn add_namespaced_openpgp_certificates(
1072 nethsm: &NetHsm,
1073 admin_credentials: &NetHsmAdminCredentials,
1074 users: &[UserBackendConnection],
1075) -> Result<(), crate::Error> {
1076 debug!(
1077 "Setup OpenPGP certificates for namespaced cryptographic keys on NetHSM backend at {}",
1078 nethsm.get_url()
1079 );
1080
1081 // Use the default R-Administrator for authentication to the backend by default.
1082 let default_admin = &admin_credentials.get_default_administrator()?.name;
1083 nethsm.use_credentials(default_admin)?;
1084
1085 let available_users = nethsm.get_users()?;
1086
1087 let nethsm_user_key_data_list = users
1088 .iter()
1089 .filter_map(|user_backend_connection| match user_backend_connection {
1090 UserBackendConnection::NetHsm { mapping, .. } => {
1091 let Some(user_key_data) =
1092 mapping.nethsm_user_key_data(NetHsmUserKeysFilter::Namespaced)
1093 else {
1094 // We are only interested in mappings that define key data.
1095 return None;
1096 };
1097 // We are only interested in mappings that define OpenPGP key data.
1098 if !matches!(
1099 user_key_data.key_setup.key_context(),
1100 CryptographicKeyContext::OpenPgp { .. }
1101 ) {
1102 return None;
1103 }
1104
1105 Some(user_key_data)
1106 }
1107 // We are only interested in user mappings for NetHSM.
1108 #[cfg(feature = "yubihsm2")]
1109 _ => None,
1110 })
1111 .collect::<Vec<_>>();
1112
1113 for user_key_data in nethsm_user_key_data_list {
1114 // Get OpenPGP User IDs and version or continue to the next user/key setup if the
1115 // mapping is not used for OpenPGP signing.
1116 let CryptographicKeyContext::OpenPgp { user_ids, version } =
1117 user_key_data.key_setup.key_context()
1118 else {
1119 continue;
1120 };
1121
1122 // Extract the namespace from the user.
1123 let Some(namespace) = user_key_data.user.namespace() else {
1124 // Note: Returning this error is not really possible, as we are explicitly
1125 // requesting tuples of namespaced user, key setup and tag.
1126 return Err(Error::NamespaceUserNoNamespace {
1127 user: user_key_data.user.clone(),
1128 }
1129 .into());
1130 };
1131
1132 // Select the first available N-Administrator credentials for interacting with the
1133 // NetHSM backend.
1134 let admin = get_first_available_namespace_admin(
1135 nethsm,
1136 admin_credentials,
1137 &available_users,
1138 namespace,
1139 )?;
1140 nethsm.use_credentials(&admin)?;
1141
1142 // Ensure the targeted user exists.
1143 if !available_users.contains(user_key_data.user) {
1144 return Err(Error::NamespaceUserMissing {
1145 user: user_key_data.user.clone(),
1146 namespace: namespace.clone(),
1147 }
1148 .into());
1149 }
1150 // Ensure the required tag is assigned to the targeted user.
1151 let user_tags = nethsm.get_user_tags(user_key_data.user)?;
1152 if user_tags
1153 .iter()
1154 .find(|tag| tag.as_str() == user_key_data.tag)
1155 .is_none()
1156 {
1157 return Err(Error::NamespaceUserMissingTag {
1158 user: user_key_data.user.clone(),
1159 namespace: namespace.clone(),
1160 tag: user_key_data.tag.to_string(),
1161 }
1162 .into());
1163 }
1164
1165 let available_keys = nethsm.get_keys(None)?;
1166
1167 // Ensure the targeted key exists.
1168 if !available_keys.contains(user_key_data.key_id) {
1169 return Err(Error::NamespaceKeyMissing {
1170 key_id: user_key_data.key_id.clone(),
1171 namespace: namespace.clone(),
1172 }
1173 .into());
1174 }
1175 // Ensure the required tag is assigned to the targeted key.
1176 let pubkey = nethsm.get_key(user_key_data.key_id)?;
1177 if !pubkey.restrictions.tags.is_some_and(|tags| {
1178 tags.iter()
1179 .find(|tag| tag.as_str() == user_key_data.tag)
1180 .is_some()
1181 }) {
1182 return Err(Error::NamespaceKeyMissesTag {
1183 key_id: user_key_data.key_id.clone(),
1184 namespace: namespace.clone(),
1185 tag: user_key_data.tag.to_string(),
1186 }
1187 .into());
1188 }
1189
1190 // Create the OpenPGP certificate if it does not exist yet.
1191 if nethsm.get_key_certificate(user_key_data.key_id)?.is_none() {
1192 // Ensure the first OpenPGP User ID exists.
1193 let Some(user_id) = user_ids.first() else {
1194 return Err(Error::NamespaceOpenPgpUserIdMissing {
1195 key_id: user_key_data.key_id.clone(),
1196 namespace: namespace.clone(),
1197 }
1198 .into());
1199 };
1200
1201 // Switch to the dedicated user with access to the key to create an OpenPGP
1202 // certificate for the key.
1203 nethsm.use_credentials(user_key_data.user)?;
1204 let data = nethsm.create_openpgp_cert(
1205 user_key_data.key_id,
1206 OpenPgpKeyUsageFlags::default(),
1207 user_id.clone(),
1208 Timestamp::now(),
1209 *version,
1210 )?;
1211
1212 // Switch back to the N-Administrator for the import of the OpenPGP certificate.
1213 nethsm.use_credentials(&admin)?;
1214 nethsm.import_key_certificate(user_key_data.key_id, data)?;
1215 }
1216 }
1217
1218 // Always use the default R-Administrator again.
1219 nethsm.use_credentials(default_admin)?;
1220
1221 Ok(())
1222}
1223
1224/// A NetHSM backend that provides full control over its data.
1225///
1226/// This backend allows full control over the data in a [`NetHsm`], to the extend that is configured
1227/// by the tracked [`NetHsmAdminCredentials`] and [`Config`].
1228#[derive(Debug)]
1229pub struct NetHsmBackend<'a, 'b> {
1230 nethsm: NetHsm,
1231 admin_credentials: &'a NetHsmAdminCredentials,
1232 signstar_config: &'b Config,
1233}
1234
1235impl<'a, 'b> NetHsmBackend<'a, 'b> {
1236 /// Creates a new [`NetHsmBackend`].
1237 ///
1238 /// # Errors
1239 ///
1240 /// Returns an error if
1241 ///
1242 /// - the iteration of the `admin_credentials` does not match that of the `signstar_config`,
1243 /// - or retrieving the default administrator from the `admin_credentials` fails.
1244 ///
1245 /// # Examples
1246 ///
1247 /// ```
1248 /// use std::{collections::BTreeSet, num::NonZeroUsize};
1249 ///
1250 /// use nethsm::{Connection, ConnectionSecurity, FullCredentials, NetHsm};
1251 /// use signstar_config::{
1252 /// NetHsmAdminCredentials,
1253 /// NetHsmBackend,
1254 /// NetHsmMetricsUsers,
1255 /// config::{ConfigBuilder, SystemConfig, SystemUserMapping},
1256 /// nethsm::{NetHsmConfig, NetHsmUserMapping},
1257 /// };
1258 /// use signstar_crypto::{
1259 /// AdministrativeSecretHandling,
1260 /// NonAdministrativeSecretHandling,
1261 /// key::{CryptographicKeyContext, KeyMechanism, KeyType, SigningKeySetup, SignatureType},
1262 /// openpgp::OpenPgpUserIdList,
1263 /// };
1264 ///
1265 /// # fn main() -> testresult::TestResult {
1266 /// // The NetHSM connection.
1267 /// let nethsm = NetHsm::new(
1268 /// Connection::new(
1269 /// "https://example.org/api/v1".try_into()?,
1270 /// ConnectionSecurity::Unsafe,
1271 /// ),
1272 /// None,
1273 /// None,
1274 /// None,
1275 /// )?;
1276 /// // The administrative credentials.
1277 /// let admin_credentials = NetHsmAdminCredentials::new(
1278 /// 1,
1279 /// "backup-passphrase".parse()?,
1280 /// "unlock-passphrase".parse()?,
1281 /// vec![FullCredentials::new(
1282 /// "admin".parse()?,
1283 /// "admin-passphrase".parse()?,
1284 /// )],
1285 /// vec![FullCredentials::new(
1286 /// "ns1~admin".parse()?,
1287 /// "ns1-admin-passphrase".parse()?,
1288 /// )],
1289 /// )?;
1290 /// // The Signstar config.
1291 /// let signstar_config = ConfigBuilder::new(SystemConfig::new(
1292 /// 1,
1293 /// AdministrativeSecretHandling::ShamirsSecretSharing {
1294 /// number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1295 /// threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1296 /// },
1297 /// NonAdministrativeSecretHandling::SystemdCreds,
1298 /// BTreeSet::from_iter([
1299 /// SystemUserMapping::ShareHolder {
1300 /// system_user: "share-holder1".parse()?,
1301 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
1302 /// },
1303 /// SystemUserMapping::ShareHolder {
1304 /// system_user: "share-holder2".parse()?,
1305 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1306 /// },
1307 /// SystemUserMapping::ShareHolder {
1308 /// system_user: "share-holder3".parse()?,
1309 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
1310 /// },
1311 /// SystemUserMapping::WireGuardDownload {
1312 /// system_user: "wireguard-downloader".parse()?,
1313 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1314 /// },
1315 /// ]),
1316 /// )?)
1317 /// .set_nethsm_config(NetHsmConfig::new(
1318 /// BTreeSet::from_iter([
1319 /// Connection::new("https:///nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1320 /// Connection::new("https:///nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1321 /// ]),
1322 /// BTreeSet::from_iter([
1323 /// NetHsmUserMapping::Admin("admin".parse()?),
1324 /// NetHsmUserMapping::Backup{
1325 /// backend_user: "backup".parse()?,
1326 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1327 /// system_user: "nethsm-backup-user".parse()?,
1328 /// },
1329 /// NetHsmUserMapping::HermeticMetrics {
1330 /// backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1331 /// system_user: "nethsm-hermetic-metrics-user".parse()?,
1332 /// },
1333 /// NetHsmUserMapping::Metrics {
1334 /// backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1335 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1336 /// system_user: "nethsm-metrics-user".parse()?,
1337 /// },
1338 /// NetHsmUserMapping::Signing {
1339 /// backend_user: "signing".parse()?,
1340 /// signing_key_id: "signing1".parse()?,
1341 /// key_setup: SigningKeySetup::new(
1342 /// KeyType::Curve25519,
1343 /// vec![KeyMechanism::EdDsaSignature],
1344 /// None,
1345 /// SignatureType::EdDsa,
1346 /// CryptographicKeyContext::OpenPgp {
1347 /// user_ids: OpenPgpUserIdList::new(vec![
1348 /// "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1349 /// ])?,
1350 /// version: "v4".parse()?,
1351 /// },
1352 /// )?,
1353 /// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1354 /// system_user: "nethsm-signing-user".parse()?,
1355 /// tag: "signing1".to_string(),
1356 /// }
1357 /// ]),
1358 /// )?)
1359 /// .finish()?;
1360 ///
1361 /// let nethsm_backend = NetHsmBackend::new(nethsm, &admin_credentials, &signstar_config)?;
1362 /// # Ok(())
1363 /// # }
1364 /// ```
1365 pub fn new(
1366 nethsm: NetHsm,
1367 admin_credentials: &'a NetHsmAdminCredentials,
1368 signstar_config: &'b Config,
1369 ) -> Result<Self, crate::Error> {
1370 debug!(
1371 "Create a new NetHSM backend for Signstar config at {}",
1372 nethsm.get_url()
1373 );
1374
1375 // Ensure that the iterations of administrative credentials and signstar config match.
1376 if admin_credentials.get_iteration() != signstar_config.system().iteration() {
1377 return Err(Error::IterationMismatch {
1378 admin_creds: admin_credentials.get_iteration(),
1379 signstar_config: signstar_config.system().iteration(),
1380 }
1381 .into());
1382 }
1383
1384 // Add all system-wide Administrators for the connection
1385 for user in admin_credentials.get_administrators() {
1386 nethsm.add_credentials(user.into());
1387 }
1388 // Add all namespace Administrators for the connection
1389 for user in admin_credentials.get_namespace_administrators() {
1390 nethsm.add_credentials(user.into());
1391 }
1392 // Use the default administrator
1393 nethsm.use_credentials(&admin_credentials.get_default_administrator()?.name)?;
1394
1395 Ok(Self {
1396 nethsm,
1397 admin_credentials,
1398 signstar_config,
1399 })
1400 }
1401
1402 /// Returns a reference to the tracked [`NetHsm`].
1403 pub fn nethsm(&self) -> &NetHsm {
1404 &self.nethsm
1405 }
1406
1407 /// Unlocks a locked [`NetHsm`] backend.
1408 pub(crate) fn unlock_nethsm(&self) -> Result<(), crate::Error> {
1409 Ok(self.nethsm.unlock(Passphrase::new(
1410 self.admin_credentials.get_unlock_passphrase().into(),
1411 ))?)
1412 }
1413
1414 /// Retrieves the state for all users on the [`NetHsm`] backend.
1415 ///
1416 /// # Note
1417 ///
1418 /// Uses the `nethsm` with the [default
1419 /// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`].
1420 ///
1421 /// # Errors
1422 ///
1423 /// Returns an error if
1424 ///
1425 /// - using the credentials of the default *R-Administrator* fails,
1426 /// - retrieving all user names of the NetHSM backend fails,
1427 /// - retrieving information about a specific NetHSM user fails,
1428 /// - or retrieving the tags of an *Operator* user fails.
1429 pub(crate) fn user_states(&self) -> Result<Vec<UserState>, crate::Error> {
1430 // Use the default R-Administrator.
1431 self.nethsm
1432 .use_credentials(&self.admin_credentials.get_default_administrator()?.name)?;
1433
1434 let users = {
1435 let mut users: Vec<UserState> = Vec::new();
1436
1437 for user_id in self.nethsm.get_users()? {
1438 let user_data = self.nethsm.get_user(&user_id)?;
1439 let tags = if user_data.role == UserRole::Operator.into() {
1440 self.nethsm.get_user_tags(&user_id)?
1441 } else {
1442 Vec::new()
1443 };
1444
1445 users.push(UserState {
1446 name: user_id,
1447 role: user_data.role.into(),
1448 tags,
1449 });
1450 }
1451
1452 users
1453 };
1454
1455 Ok(users)
1456 }
1457
1458 /// Retrieves the state of a key certificate on the [`NetHsm`] backend.
1459 ///
1460 /// Key certificates may be retrieved for system-wide keys or namespaced keys.
1461 /// Returns a [`KeyCertificateState`], which may also encode reasons for why state cannot be
1462 /// retrieved.
1463 ///
1464 /// # Note
1465 ///
1466 /// It is assumed that the current credentials for the `nethsm` provide access to the key
1467 /// certificate of key `key_id`.
1468 fn key_certificate_state(
1469 &self,
1470 key_id: &KeyId,
1471 namespace: Option<&NamespaceId>,
1472 ) -> KeyCertificateState {
1473 // Provide a dedicated string for log messages in case a namespace is used.
1474 let namespace = if let Some(namespace) = namespace {
1475 format!(" in namespace \"{namespace}\"")
1476 } else {
1477 "".to_string()
1478 };
1479
1480 match self.nethsm.get_key_certificate(key_id) {
1481 Ok(Some(key_cert)) => {
1482 let public_key = match SignedPublicKey::from_reader_single(key_cert.as_slice()) {
1483 Ok((public_key, _armor_header)) => public_key,
1484 Err(error) => {
1485 let message = format!(
1486 "Unable to create OpenPGP certificate from key certificate of key \"{key_id}\"{namespace}:\n{error}"
1487 );
1488 debug!("{message}");
1489 return KeyCertificateState::NotAnOpenPgpCertificate { message };
1490 }
1491 };
1492
1493 match TryInto::<CryptographicKeyContext>::try_into(public_key) {
1494 Ok(key_context) => KeyCertificateState::KeyContext(key_context),
1495 Err(error) => {
1496 let message = format!(
1497 "Unable to convert OpenPGP certificate of key \"{key_id}\"{namespace} to key context:\n{error}"
1498 );
1499 debug!("{message}");
1500 KeyCertificateState::NotACryptographicKeyContext { message }
1501 }
1502 }
1503 }
1504 Ok(None) => KeyCertificateState::Empty,
1505 Err(error) => {
1506 let message = error.to_string();
1507 debug!("{message}");
1508 KeyCertificateState::Error { message }
1509 }
1510 }
1511 }
1512
1513 /// Retrieves the state for all keys on the [`NetHsm`] backend.
1514 ///
1515 /// Collects each key, their [`KeyType`] and list of [`KeyMechanisms`][`KeyMechanism`].
1516 /// Also attempts to derive a [`CryptographicKeyContext`] from the key certificate.
1517 ///
1518 /// # Note
1519 ///
1520 /// This function uses the `nethsm` with the [default
1521 /// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
1522 /// namespace-specific _N-Administrator_ for individual operations.
1523 /// If this function succeeds, the `nethsm` is guaranteed to use the [default
1524 /// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
1525 /// If this function fails, the `nethsm` may still use a namespace-specific _N-Administrator_.
1526 ///
1527 ///
1528 /// # Errors
1529 ///
1530 /// Returns an error if
1531 ///
1532 /// - using the default *R-Administrator* for authentication against the backend fails,
1533 /// - retrieving the names of all system-wide keys on the backend fails,
1534 /// - retrieving information on a specific system-wide key on the backend fails,
1535 /// - an *N-Administrator* in `admin_credentials` is not actually in a namespace,
1536 /// - using the credentials of an *N-Administrator* fails,
1537 /// - retrieving the names of all namespaced keys on the backend fails,
1538 /// - or retrieving information on a specific namespaced key on the backend fails.
1539 pub(crate) fn key_states(&self) -> Result<Vec<KeyState>, crate::Error> {
1540 // Use the default administrator
1541 let default_admin = &self.admin_credentials.get_default_administrator()?.name;
1542 self.nethsm.use_credentials(default_admin)?;
1543
1544 let mut keys = Vec::new();
1545 // Get the state of system-wide keys.
1546 for key_id in self.nethsm.get_keys(None)? {
1547 let key = self.nethsm.get_key(&key_id)?;
1548 let key_context = self.key_certificate_state(&key_id, None);
1549
1550 keys.push(KeyState {
1551 name: key_id,
1552 namespace: None,
1553 tags: key.restrictions.tags.unwrap_or_default(),
1554 key_type: key
1555 .r#type
1556 .try_into()
1557 .map_err(nethsm::Error::SignstarCryptoKey)?,
1558 mechanisms: key
1559 .mechanisms
1560 .iter()
1561 .filter_map(|mechanism| KeyMechanism::try_from(mechanism).ok())
1562 .collect(),
1563 key_cert_state: key_context,
1564 });
1565 }
1566
1567 let mut seen_namespaces = HashSet::new();
1568 // Get the state of namespaced keys.
1569 for user_id in self
1570 .admin_credentials
1571 .get_namespace_administrators()
1572 .iter()
1573 .map(|creds| creds.name.clone())
1574 {
1575 // Extract the namespace of the user and ensure that the namespace exists already.
1576 let Some(namespace) = user_id.namespace() else {
1577 return Err(Error::NamespaceUserNoNamespace {
1578 user: user_id.clone(),
1579 }
1580 .into());
1581 };
1582
1583 // Only extract key information for the namespace if we have not already looked at it.
1584 if seen_namespaces.contains(namespace) {
1585 continue;
1586 }
1587 seen_namespaces.insert(namespace.clone());
1588
1589 self.nethsm.use_credentials(&user_id)?;
1590 for key_id in self.nethsm.get_keys(None)? {
1591 let key = self.nethsm.get_key(&key_id)?;
1592 let key_context = self.key_certificate_state(&key_id, Some(namespace));
1593
1594 keys.push(KeyState {
1595 name: key_id,
1596 namespace: Some(namespace.clone()),
1597 tags: key.restrictions.tags.unwrap_or_default(),
1598 key_type: key
1599 .r#type
1600 .try_into()
1601 .map_err(nethsm::Error::SignstarCryptoKey)?,
1602 mechanisms: key
1603 .mechanisms
1604 .iter()
1605 .filter_map(|mechanism| KeyMechanism::try_from(mechanism).ok())
1606 .collect(),
1607 key_cert_state: key_context,
1608 });
1609 }
1610 }
1611
1612 // Always use the default *R-Administrator* again.
1613 self.nethsm.use_credentials(default_admin)?;
1614
1615 Ok(keys)
1616 }
1617
1618 /// Syncs the state of a Signstar configuration with the backend using credentials for users in
1619 /// non-administrative roles.
1620 ///
1621 /// Provisions unprovisioned NetHSM backends and unlocks locked ones.
1622 /// Then works down the following list to
1623 ///
1624 /// - create _R-Administrators_,
1625 /// - or set their passphrase if they exist already,
1626 /// - create system-wide keys and add tags to them,
1627 /// - or remove all tags from existing keys and only add the configured tags,
1628 /// - create users in the system-wide, non-administrative roles (i.e.
1629 /// [`Backup`][`UserRole::Backup`], [`Metrics`][`UserRole::Metrics`] and
1630 /// [`Operator`][`UserRole::Operator`]),
1631 /// - or set their passphrase if they exist already,
1632 /// - create OpenPGP certificates for system-wide keys,
1633 /// - or do nothing if they exist already,
1634 /// - create _N-Administrators_ and their respective namespaces,
1635 /// - or set their passphrase if they exist already,
1636 /// - create namespaced keys and add tags to them,
1637 /// - or remove all tags from existing keys and only add the configured tags,
1638 /// - create users in the namespaced, non-administrative roles (i.e.
1639 /// [`Operator`][`UserRole::Operator`]),
1640 /// - or set their passphrase if they exist already,
1641 /// - and create OpenPGP certificates for namespaced keys,
1642 /// - or do nothing if they exist already.
1643 ///
1644 /// # Note
1645 ///
1646 /// This function uses the `nethsm` with the [default
1647 /// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`], but may switch to a
1648 /// namespace-specific _N-Administrator_ or non-administrative user for individual operations.
1649 /// If this function succeeds, the `nethsm` is guaranteed to use the [default
1650 /// _R-Administrator_][`NetHsmAdminCredentials::get_default_administrator`] again.
1651 /// If this function fails, the `nethsm` may still use a namespace-specific _N-Administrator_ or
1652 /// non-administrative user.
1653 ///
1654 /// # Errors
1655 ///
1656 /// Returns an error if
1657 ///
1658 /// - retrieving the state of the [`NetHsm`] backend fails,
1659 /// - provisioning an unprovisioned [`NetHsm`] fails,
1660 /// - unlocking a locked [`NetHsm`] backend fails,
1661 /// - adding users in the system-wide [`Administrator`][`UserRole::Administrator`] role fails,
1662 /// - adding system-wide keys fails,
1663 /// - adding system-wide users in the [`Backup`][`UserRole::Backup`],
1664 /// [`Metrics`][`UserRole::Metrics`] or [`Operator`][`UserRole::Operator`] role fails,
1665 /// - adding OpenPGP certificates for system-wide keys fails,
1666 /// - adding namespaced users in the [`Administrator`][`UserRole::Administrator`] role or adding
1667 /// their respective namespace fails,
1668 /// - adding namespaced keys fails,
1669 /// - adding namespaced users in the [`Operator`][`UserRole::Operator`] role fails,
1670 /// - or adding OpenPGP certificates for namespaced keys fails.
1671 pub fn sync(&self, user_credentials: &[FullCredentials]) -> Result<(), crate::Error> {
1672 debug!(
1673 "Synchronize state of users and keys for the NetHSM backend at {} with the Signstar config.",
1674 self.nethsm.get_url()
1675 );
1676
1677 // Extract the user backend connections.
1678 let non_admin_users = self
1679 .signstar_config
1680 .user_backend_connections(UserBackendConnectionFilter::NonAdmin);
1681
1682 match self.nethsm.state()? {
1683 SystemState::Unprovisioned => {
1684 debug!(
1685 "Unprovisioned NetHSM backend detected at {}",
1686 self.nethsm.get_url()
1687 );
1688
1689 self.nethsm.provision(
1690 Passphrase::from_str(self.admin_credentials.get_unlock_passphrase()).map_err(
1691 |source| {
1692 crate::Error::NetHsm(nethsm::Error::SignstarCryptoPassphrase(source))
1693 },
1694 )?,
1695 self.admin_credentials
1696 .get_default_administrator()?
1697 .passphrase
1698 .clone(),
1699 nethsm::Utc::now(),
1700 )?;
1701 }
1702 SystemState::Locked => {
1703 debug!(
1704 "Locked NetHSM backend detected at {}",
1705 self.nethsm.get_url()
1706 );
1707
1708 self.nethsm.unlock(Passphrase::new(
1709 self.admin_credentials.get_unlock_passphrase().into(),
1710 ))?;
1711 }
1712 SystemState::Operational => {
1713 debug!(
1714 "Operational NetHSM backend detected at {}",
1715 self.nethsm.get_url()
1716 );
1717 }
1718 }
1719
1720 // Add any missing users and keys.
1721 add_system_wide_admins(&self.nethsm, self.admin_credentials)?;
1722 add_system_wide_keys(&self.nethsm, self.admin_credentials, &non_admin_users)?;
1723 add_non_administrative_users(
1724 &self.nethsm,
1725 self.admin_credentials,
1726 &non_admin_users,
1727 user_credentials,
1728 )?;
1729 add_system_wide_openpgp_certificates(
1730 &self.nethsm,
1731 self.admin_credentials,
1732 &non_admin_users,
1733 )?;
1734 add_namespace_admins(&self.nethsm, self.admin_credentials)?;
1735 add_namespaced_keys(&self.nethsm, self.admin_credentials, &non_admin_users)?;
1736 add_namespaced_non_administrative_users(
1737 &self.nethsm,
1738 self.admin_credentials,
1739 &non_admin_users,
1740 user_credentials,
1741 )?;
1742 add_namespaced_openpgp_certificates(
1743 &self.nethsm,
1744 self.admin_credentials,
1745 &non_admin_users,
1746 )?;
1747
1748 Ok(())
1749 }
1750}
1751
1752#[cfg(test)]
1753#[cfg(feature = "_test-helpers")]
1754mod tests {
1755 use log::LevelFilter;
1756 use nethsm::{Connection, ConnectionSecurity, FullCredentials, NetHsm};
1757 use signstar_common::logging::setup_logging;
1758 use testresult::TestResult;
1759
1760 use super::*;
1761 use crate::test::{ConfigFileConfig, ConfigFileVariant, SystemPrepareConfig};
1762
1763 /// Ensures that the [`NetHsmBackend::new`] fails on mismatching iterations in
1764 /// [`NetHsmAdminCredentials`] and [`Config`].
1765 #[test]
1766 fn nethsm_backend_new_fails_on_iteration_mismatch() -> TestResult {
1767 setup_logging(LevelFilter::Debug)?;
1768
1769 let prepare_config = SystemPrepareConfig {
1770 machine_id: false,
1771 credentials_socket: false,
1772 signstar_config: ConfigFileConfig {
1773 location: None,
1774 variant: ConfigFileVariant::OnlyNetHsmBackendAdminPlaintextNonAdminSystemdCreds,
1775 system_user_config: None,
1776 },
1777 };
1778 let signstar_config = prepare_config.signstar_config.variant.to_config()?;
1779
1780 let nethsm = NetHsm::new(
1781 Connection::new(
1782 "https://example.org/api/v1".try_into()?,
1783 ConnectionSecurity::Unsafe,
1784 ),
1785 None,
1786 None,
1787 None,
1788 )?;
1789 // The administrative credentials.
1790 let admin_credentials = NetHsmAdminCredentials::new(
1791 // this is different from the one in the Signstar config.
1792 2,
1793 "backup-passphrase".parse()?,
1794 "unlock-passphrase".parse()?,
1795 vec![FullCredentials::new(
1796 "admin".parse()?,
1797 "admin-passphrase".parse()?,
1798 )],
1799 vec![FullCredentials::new(
1800 "ns1~admin".parse()?,
1801 "ns1-admin-passphrase".parse()?,
1802 )],
1803 )?;
1804 let nethsm_backend_result =
1805 NetHsmBackend::new(nethsm, &admin_credentials, &signstar_config);
1806
1807 assert!(
1808 nethsm_backend_result.is_err(),
1809 "Test should have failed, but succeeded"
1810 );
1811 assert!(
1812 matches!(
1813 nethsm_backend_result,
1814 Err(crate::Error::NetHsmBackend(Error::IterationMismatch {
1815 admin_creds: _,
1816 signstar_config: _
1817 }))
1818 ),
1819 "Expected an `Error::IterationMismatch` but got {nethsm_backend_result:?}"
1820 );
1821
1822 Ok(())
1823 }
1824}