signstar_config/
admin_credentials.rs

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