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}