signstar_config/admin_credentials.rs
1//! Administrative credentials handling for a NetHSM backend.
2
3use std::{
4 fs::{File, Permissions, set_permissions},
5 io::Write,
6 os::unix::fs::{PermissionsExt, chown},
7 path::{Path, PathBuf},
8 process::{Command, Stdio},
9};
10
11#[cfg(doc)]
12use nethsm::UserId;
13use nethsm::{FullCredentials, Passphrase};
14use nethsm_config::AdministrativeSecretHandling;
15use serde::{Deserialize, Serialize};
16use signstar_common::{
17 admin_credentials::{
18 create_credentials_dir,
19 get_plaintext_credentials_file,
20 get_systemd_creds_credentials_file,
21 },
22 common::SECRET_FILE_MODE,
23};
24
25use crate::utils::{fail_if_not_root, get_command, get_current_system_user};
26
27/// An error that may occur when handling administrative credentials for a NetHSM backend.
28#[derive(Debug, thiserror::Error)]
29pub enum Error {
30 /// There is no top-level administrator.
31 #[error("There is no top-level administrator but at least one is required")]
32 AdministratorMissing,
33
34 /// There is no top-level administrator with the name "admin".
35 #[error("The default top-level administrator \"admin\" is missing")]
36 AdministratorNoDefault,
37
38 /// Deserializing administrative secrets from a TOML string failed.
39 #[error("Deserializing administrative secrets in {path} as TOML string failed:\n{source}")]
40 ConfigFromToml {
41 /// The path to a config file that can not be deserialization as TOML string.
42 path: PathBuf,
43 /// The boxed source error.
44 source: Box<toml::de::Error>,
45 },
46
47 /// Administrative secrets can not be loaded.
48 #[error("Unable to load administrative secrets from {path}:\n{source}")]
49 ConfigLoad {
50 /// The path to a config file from which administrative secrets can not be loaded.
51 path: PathBuf,
52 /// The boxed source error.
53 source: Box<confy::ConfyError>,
54 },
55
56 /// Administrative secrets can not be stored to file.
57 #[error("Unable to store administrative secrets in {path}:\n{source}")]
58 ConfigStore {
59 /// The path to a config file in which administrative secrets can not be stored.
60 path: PathBuf,
61 /// The source error.
62 source: Box<confy::ConfyError>,
63 },
64
65 /// Serializing a Signstar config as TOML string failed.
66 #[error("Serializing administrative secrets as TOML string failed:\n{0}")]
67 ConfigToToml(#[source] toml::ser::Error),
68
69 /// A credentials file can not be created.
70 #[error("The credentials file {path} can not be created:\n{source}")]
71 CredsFileCreate {
72 /// The path to a credentials file administrative secrets can not be stored.
73 path: PathBuf,
74 /// The source error.
75 source: std::io::Error,
76 },
77
78 /// A credentials file does not exist.
79 #[error("The credentials file {path} does not exist")]
80 CredsFileMissing {
81 /// The path to a missing credentials file.
82 path: PathBuf,
83 },
84
85 /// A credentials file is not a file.
86 #[error("The credentials file {path} is not a file")]
87 CredsFileNotAFile {
88 /// The path to a credentials file that is not a file.
89 path: PathBuf,
90 },
91
92 /// A credentials file can not be written to.
93 #[error("The credentials file {path} can not be written to:\n{source}")]
94 CredsFileWrite {
95 /// The path to a credentials file that can not be written to.
96 path: PathBuf,
97 /// The source error
98 source: std::io::Error,
99 },
100
101 /// A passphrase is too short.
102 #[error(
103 "The passphrase for {context} is too short (should be at least {minimum_length} characters)"
104 )]
105 PassphraseTooShort {
106 /// The context in which the passphrase is used.
107 ///
108 /// This is inserted into the sentence "The _context_ passphrase is not long enough"
109 context: String,
110
111 /// The minimum length of a passphrase.
112 minimum_length: usize,
113 },
114}
115
116/// Administrative credentials.
117///
118/// Tracks the following credentials and passphrases:
119/// - the backup passphrase of the backend,
120/// - the unlock passphrase of the backend,
121/// - the top-level administrator credentials of the backend,
122/// - the namespace administrator credentials of the backend.
123///
124/// # Note
125///
126/// The unlock and backup passphrase must be at least 10 characters long.
127/// The passphrases of top-level and namespace administrator accounts must be at least 10 characters
128/// long.
129/// The list of top-level administrator credentials must include an account with the username
130/// "admin".
131#[derive(Clone, Debug, Default, Deserialize, Serialize)]
132pub struct AdminCredentials {
133 iteration: u32,
134 backup_passphrase: Passphrase,
135 unlock_passphrase: Passphrase,
136 administrators: Vec<FullCredentials>,
137 namespace_administrators: Vec<FullCredentials>,
138}
139
140impl AdminCredentials {
141 /// Creates a new [`AdminCredentials`] instance.
142 ///
143 /// # Examples
144 ///
145 /// ```
146 /// use nethsm::FullCredentials;
147 /// use signstar_config::admin_credentials::AdminCredentials;
148 ///
149 /// # fn main() -> testresult::TestResult {
150 /// let creds = AdminCredentials::new(
151 /// 1,
152 /// "backup-passphrase".parse()?,
153 /// "unlock-passphrase".parse()?,
154 /// vec![FullCredentials::new(
155 /// "admin".parse()?,
156 /// "admin-passphrase".parse()?,
157 /// )],
158 /// vec![FullCredentials::new(
159 /// "ns1~admin".parse()?,
160 /// "ns1-admin-passphrase".parse()?,
161 /// )],
162 /// )?;
163 /// # // the backup passphrase is too short
164 /// # assert!(AdminCredentials::new(
165 /// # 1,
166 /// # "short".parse()?,
167 /// # "unlock-passphrase".parse()?,
168 /// # vec![FullCredentials::new("admin".parse()?, "admin-passphrase".parse()?)],
169 /// # vec![FullCredentials::new(
170 /// # "ns1~admin".parse()?,
171 /// # "ns1-admin-passphrase".parse()?,
172 /// # )],
173 /// # ).is_err());
174 /// #
175 /// # // the unlock passphrase is too short
176 /// # assert!(AdminCredentials::new(
177 /// # 1,
178 /// # "backup-passphrase".parse()?,
179 /// # "short".parse()?,
180 /// # vec![FullCredentials::new("admin".parse()?, "admin-passphrase".parse()?)],
181 /// # vec![FullCredentials::new(
182 /// # "ns1~admin".parse()?,
183 /// # "ns1-admin-passphrase".parse()?,
184 /// # )],
185 /// # ).is_err());
186 /// #
187 /// # // there is no top-level administrator
188 /// # assert!(AdminCredentials::new(
189 /// # 1,
190 /// # "backup-passphrase".parse()?,
191 /// # "unlock-passphrase".parse()?,
192 /// # Vec::new(),
193 /// # vec![FullCredentials::new(
194 /// # "ns1~admin".parse()?,
195 /// # "ns1-admin-passphrase".parse()?,
196 /// # )],
197 /// # ).is_err());
198 /// #
199 /// # // there is no default top-level administrator
200 /// # assert!(AdminCredentials::new(
201 /// # 1,
202 /// # "backup-passphrase".parse()?,
203 /// # "unlock-passphrase".parse()?,
204 /// # vec![FullCredentials::new("some".parse()?, "admin-passphrase".parse()?)],
205 /// # vec![FullCredentials::new(
206 /// # "ns1~admin".parse()?,
207 /// # "ns1-admin-passphrase".parse()?,
208 /// # )],
209 /// # ).is_err());
210 /// #
211 /// # // a top-level administrator passphrase is too short
212 /// # assert!(AdminCredentials::new(
213 /// # 1,
214 /// # "backup-passphrase".parse()?,
215 /// # "unlock-passphrase".parse()?,
216 /// # vec![FullCredentials::new("admin".parse()?, "short".parse()?)],
217 /// # vec![FullCredentials::new(
218 /// # "ns1~admin".parse()?,
219 /// # "ns1-admin-passphrase".parse()?,
220 /// # )],
221 /// # ).is_err());
222 /// #
223 /// # // a namespace administrator passphrase is too short
224 /// # assert!(AdminCredentials::new(
225 /// # 1,
226 /// # "backup-passphrase".parse()?,
227 /// # "unlock-passphrase".parse()?,
228 /// # vec![FullCredentials::new("some".parse()?, "admin-passphrase".parse()?)],
229 /// # vec![FullCredentials::new(
230 /// # "ns1~admin".parse()?,
231 /// # "short".parse()?,
232 /// # )],
233 /// # ).is_err());
234 /// # Ok(())
235 /// # }
236 /// ```
237 pub fn new(
238 iteration: u32,
239 backup_passphrase: Passphrase,
240 unlock_passphrase: Passphrase,
241 administrators: Vec<FullCredentials>,
242 namespace_administrators: Vec<FullCredentials>,
243 ) -> Result<Self, crate::Error> {
244 let admin_credentials = Self {
245 iteration,
246 backup_passphrase,
247 unlock_passphrase,
248 administrators,
249 namespace_administrators,
250 };
251 admin_credentials.validate()?;
252
253 Ok(admin_credentials)
254 }
255
256 /// Loads an [`AdminCredentials`] from the default file location.
257 ///
258 /// Depending on `secrets_handling`, the file path and contents differ:
259 ///
260 /// - [`AdministrativeSecretHandling::Plaintext`]: the file path is defined by
261 /// [`get_plaintext_credentials_file`] and the contents are plaintext,
262 /// - [`AdministrativeSecretHandling::SystemdCreds`]: the file path is defined by
263 /// [`get_systemd_creds_credentials_file`] and the contents are [systemd-creds] encrypted.
264 ///
265 /// Delegates to [`AdminCredentials::load_from_file`], providing the specific file path and the
266 /// selected `secrets_handling`.
267 ///
268 /// # Examples
269 ///
270 /// ```no_run
271 /// use nethsm_config::AdministrativeSecretHandling;
272 /// use signstar_config::admin_credentials::AdminCredentials;
273 ///
274 /// # fn main() -> testresult::TestResult {
275 /// // load plaintext credentials from default location
276 /// let plaintext_admin_creds = AdminCredentials::load(AdministrativeSecretHandling::Plaintext)?;
277 ///
278 /// // load systemd-creds encrypted credentials from default location
279 /// let systemd_creds_admin_creds =
280 /// AdminCredentials::load(AdministrativeSecretHandling::SystemdCreds)?;
281 ///
282 /// # Ok(())
283 /// # }
284 /// ```
285 ///
286 /// # Errors
287 ///
288 /// Returns an error if [`AdminCredentials::load_from_file`] fails.
289 ///
290 /// # Panics
291 ///
292 /// This function panics when providing [`AdministrativeSecretHandling::ShamirsSecretSharing`]
293 /// as `secrets_handling`.
294 ///
295 /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
296 pub fn load(secrets_handling: AdministrativeSecretHandling) -> Result<Self, crate::Error> {
297 // fail if not running as root
298 fail_if_not_root(&get_current_system_user()?)?;
299
300 Self::load_from_file(
301 match secrets_handling {
302 AdministrativeSecretHandling::Plaintext => get_plaintext_credentials_file(),
303 AdministrativeSecretHandling::SystemdCreds => get_systemd_creds_credentials_file(),
304 AdministrativeSecretHandling::ShamirsSecretSharing => {
305 unimplemented!("Shamir's Secret Sharing is not yet supported")
306 }
307 },
308 secrets_handling,
309 )
310 }
311
312 /// Loads an [`AdminCredentials`] instance from file.
313 ///
314 /// Depending on `path` and `secrets_handling`, the behavior of this function differs:
315 ///
316 /// - If `secrets_handling` is set to [`AdministrativeSecretHandling::Plaintext`] the contents
317 /// at `path` are considered to be plaintext.
318 /// - If `secrets_handling` is set to [`AdministrativeSecretHandling::SystemdCreds`] the
319 /// contents at `path` are considered to be [systemd-creds] encrypted.
320 ///
321 /// # Examples
322 ///
323 /// ```no_run
324 /// use std::io::Write;
325 ///
326 /// use nethsm_config::AdministrativeSecretHandling;
327 /// use signstar_config::admin_credentials::AdminCredentials;
328 ///
329 /// # fn main() -> testresult::TestResult {
330 /// let admin_creds = r#"iteration = 1
331 /// backup_passphrase = "backup-passphrase"
332 /// unlock_passphrase = "unlock-passphrase"
333 ///
334 /// [[administrators]]
335 /// name = "admin"
336 /// passphrase = "admin-passphrase"
337 ///
338 /// [[namespace_administrators]]
339 /// name = "ns1~admin"
340 /// passphrase = "ns1-admin-passphrase"
341 /// "#;
342 /// let mut tempfile = tempfile::NamedTempFile::new()?;
343 /// write!(tempfile.as_file_mut(), "{admin_creds}");
344 ///
345 /// assert!(
346 /// AdminCredentials::load_from_file(tempfile.path(), AdministrativeSecretHandling::Plaintext)
347 /// .is_ok()
348 /// );
349 /// # Ok(())
350 /// # }
351 /// ```
352 ///
353 /// # Errors
354 ///
355 /// Returns an error if
356 /// - the function is called by a system user that is not root,
357 /// - the file at `path` does not exist,
358 /// - the file at `path` is not a file,
359 /// - the file at `path` is considered as plaintext but can not be loaded,
360 /// - the file at `path` is considered as [systemd-creds] encrypted but can not be decrypted,
361 /// - or the file at `path` is considered as [systemd-creds] encrypted but can not be loaded
362 /// after decryption.
363 ///
364 /// # Panics
365 ///
366 /// This function panics when providing [`AdministrativeSecretHandling::ShamirsSecretSharing`]
367 /// as `secrets_handling`.
368 ///
369 /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
370 pub fn load_from_file(
371 path: impl AsRef<Path>,
372 secrets_handling: AdministrativeSecretHandling,
373 ) -> Result<Self, crate::Error> {
374 let path = path.as_ref();
375 if !path.exists() {
376 return Err(crate::Error::AdminSecretHandling(Error::CredsFileMissing {
377 path: path.to_path_buf(),
378 }));
379 }
380 if !path.is_file() {
381 return Err(crate::Error::AdminSecretHandling(
382 Error::CredsFileNotAFile {
383 path: path.to_path_buf(),
384 },
385 ));
386 }
387
388 let config: Self = match secrets_handling {
389 AdministrativeSecretHandling::Plaintext => {
390 confy::load_path(path).map_err(|source| {
391 crate::Error::AdminSecretHandling(Error::ConfigLoad {
392 path: path.to_path_buf(),
393 source: Box::new(source),
394 })
395 })?
396 }
397 AdministrativeSecretHandling::SystemdCreds => {
398 // Decrypt the credentials using systemd-creds.
399 let creds_command = get_command("systemd-creds")?;
400 let mut command = Command::new(creds_command);
401 let command = command.arg("decrypt").arg(path).arg("-");
402 let command_output =
403 command
404 .output()
405 .map_err(|source| crate::Error::CommandExec {
406 command: format!("{command:?}"),
407 source,
408 })?;
409 if !command_output.status.success() {
410 return Err(crate::Error::CommandNonZero {
411 command: format!("{command:?}"),
412 exit_status: command_output.status,
413 stderr: String::from_utf8_lossy(&command_output.stderr).into_owned(),
414 });
415 }
416
417 // Read the resulting TOML string from stdout and construct an AdminCredentials from
418 // it.
419 let config_str = String::from_utf8(command_output.stdout).map_err(|source| {
420 crate::Error::Utf8String {
421 path: path.to_path_buf(),
422 context: "after decrypting".to_string(),
423 source,
424 }
425 })?;
426 toml::from_str(&config_str).map_err(|source| {
427 crate::Error::AdminSecretHandling(Error::ConfigFromToml {
428 path: path.to_path_buf(),
429 source: Box::new(source),
430 })
431 })?
432 }
433 AdministrativeSecretHandling::ShamirsSecretSharing => {
434 unimplemented!("Shamir's Secret Sharing is not yet supported")
435 }
436 };
437 config.validate()?;
438 Ok(config)
439 }
440
441 /// Stores the [`AdminCredentials`] as a file in the default location.
442 ///
443 /// Depending on `secrets_handling`, the file path and contents differ:
444 ///
445 /// - [`AdministrativeSecretHandling::Plaintext`]: the file path is defined by
446 /// [`get_plaintext_credentials_file`] and the contents are plaintext,
447 /// - [`AdministrativeSecretHandling::SystemdCreds`]: the file path is defined by
448 /// [`get_systemd_creds_credentials_file`] and the contents are [systemd-creds] encrypted.
449 ///
450 /// Automatically creates the directory in which the administrative credentials are created.
451 /// After storing the [`AdminCredentials`] as file, its file permissions and ownership are
452 /// adjusted so that it is only accessible by root.
453 ///
454 /// # Examples
455 ///
456 /// ```no_run
457 /// use nethsm::FullCredentials;
458 /// use nethsm_config::AdministrativeSecretHandling;
459 /// use signstar_config::admin_credentials::AdminCredentials;
460 ///
461 /// # fn main() -> testresult::TestResult {
462 /// let creds = AdminCredentials::new(
463 /// 1,
464 /// "backup-passphrase".parse()?,
465 /// "unlock-passphrase".parse()?,
466 /// vec![FullCredentials::new(
467 /// "admin".parse()?,
468 /// "admin-passphrase".parse()?,
469 /// )],
470 /// vec![FullCredentials::new(
471 /// "ns1~admin".parse()?,
472 /// "ns1-admin-passphrase".parse()?,
473 /// )],
474 /// )?;
475 ///
476 /// // store as plaintext file
477 /// creds.store(AdministrativeSecretHandling::Plaintext)?;
478 ///
479 /// // store as systemd-creds encrypted file
480 /// creds.store(AdministrativeSecretHandling::SystemdCreds)?;
481 /// # Ok(())
482 /// # }
483 /// ```
484 ///
485 /// # Errors
486 ///
487 /// Returns an error if
488 /// - the function is called by a system user that is not root,
489 /// - the directory for administrative credentials cannot be created,
490 /// - `self` cannot be turned into its TOML representation,
491 /// - the [systemd-creds] command is not found,
492 /// - [systemd-creds] fails to encrypt the TOML representation of `self`,
493 /// - the target file can not be created,
494 /// - the plaintext or [systemd-creds] encrypted data can not be written to file,
495 /// - or the ownership or permissions of the target file can not be adjusted.
496 ///
497 /// # Panics
498 ///
499 /// This function panics when providing [`AdministrativeSecretHandling::ShamirsSecretSharing`]
500 /// as `secrets_handling`.
501 ///
502 /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
503 pub fn store(
504 &self,
505 secrets_handling: AdministrativeSecretHandling,
506 ) -> Result<(), crate::Error> {
507 // fail if not running as root
508 fail_if_not_root(&get_current_system_user()?)?;
509
510 create_credentials_dir()?;
511
512 let (config_data, path) = {
513 // Get the TOML string representation of self.
514 let config_data = toml::to_string_pretty(self)
515 .map_err(|source| crate::Error::AdminSecretHandling(Error::ConfigToToml(source)))?;
516 match secrets_handling {
517 AdministrativeSecretHandling::Plaintext => (
518 config_data.as_bytes().to_vec(),
519 get_plaintext_credentials_file(),
520 ),
521 AdministrativeSecretHandling::SystemdCreds => {
522 // Encrypt self as systemd-creds encrypted TOML file.
523 let creds_command = get_command("systemd-creds")?;
524 let mut command = Command::new(creds_command);
525 let command = command.args(["encrypt", "-", "-"]);
526
527 let mut command_child = command
528 .stdin(Stdio::piped())
529 .stdout(Stdio::piped())
530 .spawn()
531 .map_err(|source| crate::Error::CommandBackground {
532 command: format!("{command:?}"),
533 source,
534 })?;
535 let Some(mut stdin) = command_child.stdin.take() else {
536 return Err(crate::Error::CommandAttachToStdin {
537 command: format!("{command:?}"),
538 })?;
539 };
540
541 let handle = std::thread::spawn(move || {
542 stdin.write_all(config_data.as_bytes()).map_err(|source| {
543 crate::Error::CommandWriteToStdin {
544 command: "systemd-creds encrypt - -".to_string(),
545 source,
546 }
547 })
548 });
549
550 let _handle_result = handle.join().map_err(|source| crate::Error::Thread {
551 context: format!(
552 "storing systemd-creds encrypted administrative credentials: {source:?}"
553 ),
554 })?;
555
556 let command_output = command_child.wait_with_output().map_err(|source| {
557 crate::Error::CommandExec {
558 command: format!("{command:?}"),
559 source,
560 }
561 })?;
562 if !command_output.status.success() {
563 return Err(crate::Error::CommandNonZero {
564 command: format!("{command:?}"),
565 exit_status: command_output.status,
566 stderr: String::from_utf8_lossy(&command_output.stderr).into_owned(),
567 });
568 }
569 (command_output.stdout, get_systemd_creds_credentials_file())
570 }
571 AdministrativeSecretHandling::ShamirsSecretSharing => {
572 unimplemented!("Shamir's Secret Sharing is not yet supported")
573 }
574 }
575 };
576
577 // Write administrative credentials to file and adjust permission and ownership
578 // of file
579 {
580 let mut file = File::create(path.as_path()).map_err(|source| {
581 crate::Error::AdminSecretHandling(Error::CredsFileCreate {
582 path: path.clone(),
583 source,
584 })
585 })?;
586 file.write_all(&config_data).map_err(|source| {
587 crate::Error::AdminSecretHandling(Error::CredsFileWrite {
588 path: path.to_path_buf(),
589 source,
590 })
591 })?;
592 }
593 chown(&path, Some(0), Some(0)).map_err(|source| crate::Error::Chown {
594 path: path.clone(),
595 user: "root".to_string(),
596 source,
597 })?;
598 set_permissions(path.as_path(), Permissions::from_mode(SECRET_FILE_MODE)).map_err(
599 |source| crate::Error::ApplyPermissions {
600 path: path.clone(),
601 mode: SECRET_FILE_MODE,
602 source,
603 },
604 )?;
605
606 Ok(())
607 }
608
609 /// Returns the iteration.
610 pub fn get_iteration(&self) -> u32 {
611 self.iteration
612 }
613
614 /// Returns the backup passphrase.
615 pub fn get_backup_passphrase(&self) -> &str {
616 self.backup_passphrase.expose_borrowed()
617 }
618
619 /// Returns the unlock passphrase.
620 pub fn get_unlock_passphrase(&self) -> &str {
621 self.unlock_passphrase.expose_borrowed()
622 }
623
624 /// Returns the list of administrators.
625 pub fn get_administrators(&self) -> &[FullCredentials] {
626 &self.administrators
627 }
628
629 /// Returns the default system-wide administrator "admin".
630 ///
631 /// # Errors
632 ///
633 /// Returns an error if no administrative account with the system-wide [`UserId`] "admin" is
634 /// found.
635 pub fn get_default_administrator(&self) -> Result<&FullCredentials, crate::Error> {
636 let Some(first_admin) = self
637 .administrators
638 .iter()
639 .find(|user| user.name.to_string() == "admin")
640 else {
641 return Err(Error::AdministratorNoDefault.into());
642 };
643 Ok(first_admin)
644 }
645
646 /// Returns the list of namespace administrators.
647 pub fn get_namespace_administrators(&self) -> &[FullCredentials] {
648 &self.namespace_administrators
649 }
650
651 /// Validates the [`AdminCredentials`].
652 ///
653 /// # Errors
654 ///
655 /// Returns an error if
656 /// - there is no top-level administrator user,
657 /// - the default top-level administrator user (with the name "admin") is missing,
658 /// - a user passphrase is too short,
659 /// - the backup passphrase is too short,
660 /// - or the unlock passphrase is too short.
661 fn validate(&self) -> Result<(), crate::Error> {
662 // there is no top-level administrator user
663 if self.get_administrators().is_empty() {
664 return Err(crate::Error::AdminSecretHandling(
665 Error::AdministratorMissing,
666 ));
667 }
668
669 // there is no top-level administrator user with the name "admin"
670 if !self
671 .get_administrators()
672 .iter()
673 .any(|user| user.name.to_string() == "admin")
674 {
675 return Err(crate::Error::AdminSecretHandling(
676 Error::AdministratorNoDefault,
677 ));
678 }
679
680 let minimum_length: usize = 10;
681
682 // a top-level administrator user passphrase is too short
683 for user in self.get_administrators().iter() {
684 if user.passphrase.expose_borrowed().len() < minimum_length {
685 return Err(crate::Error::AdminSecretHandling(
686 Error::PassphraseTooShort {
687 context: format!("user {}", user.name),
688 minimum_length,
689 },
690 ));
691 }
692 }
693
694 // a namespace administrator user passphrase is too short
695 for user in self.get_namespace_administrators().iter() {
696 if user.passphrase.expose_borrowed().len() < minimum_length {
697 return Err(crate::Error::AdminSecretHandling(
698 Error::PassphraseTooShort {
699 context: format!("user {}", user.name),
700 minimum_length,
701 },
702 ));
703 }
704 }
705
706 // the backup passphrase is too short
707 if self.get_backup_passphrase().len() < minimum_length {
708 return Err(crate::Error::AdminSecretHandling(
709 Error::PassphraseTooShort {
710 context: "backups".to_string(),
711 minimum_length,
712 },
713 ));
714 }
715
716 // the unlock passphrase is too short
717 if self.get_unlock_passphrase().len() < minimum_length {
718 return Err(crate::Error::AdminSecretHandling(
719 Error::PassphraseTooShort {
720 context: "unlocking".to_string(),
721 minimum_length,
722 },
723 ));
724 }
725
726 Ok(())
727 }
728}