signstar_config/yubihsm2/
admin_credentials.rs1use serde::{Deserialize, Serialize};
4use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
5use signstar_yubihsm2::Credentials;
6
7use crate::{AdminCredentials, admin_credentials::Error};
8
9#[derive(Clone, Debug, Default, Deserialize, Serialize)]
30pub struct YubiHsm2AdminCredentials {
31 iteration: u32,
32 backup_passphrase: Passphrase,
33 administrators: Vec<Credentials>,
34}
35
36impl YubiHsm2AdminCredentials {
37 pub const DEFAULT_ID: u16 = 1;
39
40 pub const DEFAULT_PASSPHRASE: &str = "password";
42
43 pub const MINIMUM_PASSPHRASE_LENGTH_USER: usize = 8;
45
46 pub const MINIMUM_PASSPHRASE_LENGTH_BACKUP: usize = 10;
48
49 pub fn new(
59 iteration: u32,
60 backup_passphrase: Passphrase,
61 administrators: Vec<Credentials>,
62 ) -> Result<Self, crate::Error> {
63 let creds = Self {
64 iteration,
65 backup_passphrase,
66 administrators,
67 };
68 creds.validate()?;
69
70 Ok(creds)
71 }
72}
73
74impl AdminCredentials for YubiHsm2AdminCredentials {
75 fn validate(&self) -> Result<(), crate::Error> {
85 if self.administrators.is_empty() {
87 return Err(Error::AdministratorMissing.into());
88 }
89
90 for creds in self.administrators.iter() {
92 if creds.passphrase().expose_borrowed().len() < Self::MINIMUM_PASSPHRASE_LENGTH_USER {
93 return Err(Error::PassphraseTooShort {
94 context: format!("user {}", creds.user()),
95 minimum_length: Self::MINIMUM_PASSPHRASE_LENGTH_USER,
96 }
97 .into());
98 }
99 }
100
101 if self.backup_passphrase.expose_borrowed().len() < Self::MINIMUM_PASSPHRASE_LENGTH_BACKUP {
103 return Err(Error::PassphraseTooShort {
104 context: "backups".to_string(),
105 minimum_length: Self::MINIMUM_PASSPHRASE_LENGTH_USER,
106 }
107 .into());
108 }
109
110 Ok(())
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use testresult::TestResult;
117
118 use super::*;
119
120 #[test]
121 fn yubihsm2_admin_credentials_new_succeeds() -> TestResult {
122 let _creds = YubiHsm2AdminCredentials::new(
123 1,
124 Passphrase::new("backup-passphrase".to_string()),
125 vec![Credentials::new(
126 "1".parse()?,
127 Passphrase::new("password".to_string()),
128 )],
129 )?;
130
131 Ok(())
132 }
133
134 #[test]
135 fn yubihsm2_admin_credentials_new_fails_on_no_admins() -> TestResult {
136 match YubiHsm2AdminCredentials::new(
137 1,
138 Passphrase::new("backup-passphrase".to_string()),
139 Vec::new(),
140 ) {
141 Ok(creds) => {
142 panic!("Expected Error::AdministratorMissing but succeeded instead:\n{creds:?}")
143 }
144
145 Err(crate::Error::AdminSecretHandling(Error::AdministratorMissing)) => {}
146 Err(error) => panic!(
147 "Expected Error::AdministratorMissing but failed differently instead:\n{error}"
148 ),
149 }
150
151 Ok(())
152 }
153
154 #[test]
155 fn yubihsm2_admin_credentials_new_fails_on_admin_passphrase_too_short() -> TestResult {
156 match YubiHsm2AdminCredentials::new(
157 1,
158 Passphrase::new("backup-passphrase".to_string()),
159 vec![Credentials::new(
160 "1".parse()?,
161 Passphrase::new("pass".to_string()),
162 )],
163 ) {
164 Ok(creds) => {
165 panic!("Expected Error::PassphraseTooShort but succeeded instead:\n{creds:?}")
166 }
167 Err(crate::Error::AdminSecretHandling(Error::PassphraseTooShort { .. })) => {}
168 Err(error) => panic!(
169 "Expected Error::PassphraseTooShort but failed differently instead:\n{error}"
170 ),
171 }
172
173 Ok(())
174 }
175
176 #[test]
177 fn yubihsm2_admin_credentials_new_fails_on_backup_passphrase_too_short() -> TestResult {
178 match YubiHsm2AdminCredentials::new(
179 1,
180 Passphrase::new("backup".to_string()),
181 vec![Credentials::new(
182 "1".parse()?,
183 Passphrase::new("password".to_string()),
184 )],
185 ) {
186 Ok(creds) => {
187 panic!("Expected Error::PassphraseTooShort but succeeded instead:\n{creds:?}")
188 }
189 Err(crate::Error::AdminSecretHandling(Error::PassphraseTooShort { .. })) => {}
190 Err(error) => panic!(
191 "Expected Error::PassphraseTooShort but failed differently instead:\n{error}"
192 ),
193 }
194
195 Ok(())
196 }
197}