signstar_config/yubihsm2/
admin_credentials.rs1use log::info;
4use serde::{Deserialize, Serialize};
5use signstar_crypto::{
6 passphrase::{Passphrase, PassphrasePolicy},
7 traits::UserWithPassphrase,
8};
9use signstar_yubihsm2::{Credentials, object::WrapKey, yubihsm::Id};
10
11use crate::{
12 admin_credentials::{AdminCredentials, Error},
13 config::Config,
14 yubihsm2::YubiHsm2UserMapping,
15};
16
17#[derive(Clone, Debug, Default, Deserialize, Serialize)]
36pub struct YubiHsm2AdminCredentials {
37 iteration: u32,
38 backup_passphrase: Passphrase,
39 administrators: Vec<Credentials>,
40}
41
42impl YubiHsm2AdminCredentials {
43 pub const DEFAULT_ID: Id = 1;
47
48 pub const DEFAULT_PASSPHRASE: &str = "password";
52
53 pub const BACKUP_PASSPHRASE_POLICY: PassphrasePolicy = WrapKey::PASSPHRASE_POLICY;
59
60 pub const ADMIN_PASSPHRASE_POLICY: PassphrasePolicy = PassphrasePolicy { minimum_length: 30 };
62
63 pub fn new(
73 iteration: u32,
74 backup_passphrase: Passphrase,
75 administrators: Vec<Credentials>,
76 ) -> Result<Self, crate::Error> {
77 let creds = Self {
78 iteration,
79 backup_passphrase,
80 administrators,
81 };
82 creds.validate()?;
83
84 Ok(creds)
85 }
86
87 pub fn administrators(&self) -> &[Credentials] {
89 &self.administrators
90 }
91
92 pub fn default_id() -> Id {
96 Self::DEFAULT_ID
97 }
98
99 pub fn default_credentials() -> Credentials {
103 Credentials::new(
104 Self::default_id(),
105 Passphrase::new(Self::DEFAULT_PASSPHRASE.to_string()),
106 )
107 }
108}
109
110impl AdminCredentials for YubiHsm2AdminCredentials {
111 fn validate(&self) -> Result<(), crate::Error> {
121 if self.administrators.is_empty() {
123 return Err(Error::AdministratorMissing.into());
124 }
125
126 for creds in self.administrators.iter() {
128 creds
129 .passphrase()
130 .check_against_policy(&Self::ADMIN_PASSPHRASE_POLICY)?;
131 }
132
133 self.backup_passphrase
135 .check_against_policy(&Self::BACKUP_PASSPHRASE_POLICY)?;
136
137 Ok(())
138 }
139
140 fn iteration(&self) -> u32 {
142 self.iteration
143 }
144
145 fn backup_passphrase(&self) -> &Passphrase {
147 &self.backup_passphrase
148 }
149}
150
151impl TryFrom<&Config> for YubiHsm2AdminCredentials {
152 type Error = crate::Error;
153
154 fn try_from(config: &Config) -> Result<Self, Self::Error> {
168 info!("Create new administrative credentials for the Signstar configuration...");
169
170 let Some(yubihsm2_config) = config.yubihsm2() else {
171 return Err(crate::config::Error::YubiHsm2SectionMissing.into());
172 };
173
174 let administrators = yubihsm2_config
175 .mappings()
176 .iter()
177 .filter_map(|mapping| {
178 let YubiHsm2UserMapping::Admin {
179 authentication_key_id,
180 } = mapping
181 else {
182 return None;
183 };
184 Some(Credentials::new(
185 *authentication_key_id,
186 Passphrase::generate(Some(
187 YubiHsm2AdminCredentials::ADMIN_PASSPHRASE_POLICY.minimum_length,
188 )),
189 ))
190 })
191 .collect::<Vec<_>>();
192
193 YubiHsm2AdminCredentials::new(
194 config.system().iteration(),
195 Passphrase::generate(Some(
196 YubiHsm2AdminCredentials::BACKUP_PASSPHRASE_POLICY.minimum_length,
197 )),
198 administrators,
199 )
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::collections::BTreeSet;
206
207 use signstar_crypto::{AdministrativeSecretHandling, NonAdministrativeSecretHandling};
208 #[cfg(feature = "_yubihsm2-mockhsm")]
209 use signstar_yubihsm2::Connection;
210 use testresult::TestResult;
211
212 use super::*;
213 use crate::config::{ConfigBuilder, SystemConfig};
214 #[cfg(feature = "_yubihsm2-mockhsm")]
215 use crate::yubihsm2::YubiHsm2Config;
216
217 #[test]
218 fn yubihsm2_admin_credentials_new_succeeds() -> TestResult {
219 let _creds = YubiHsm2AdminCredentials::new(
220 1,
221 Passphrase::new("backup-passphrase-really-just-for-testing-i-promise-but-it-is-really-really-long-sufficiently-long-really".to_string()),
222 vec![Credentials::new(
223 "1".parse()?,
224 Passphrase::new("admin-passphrase-really-just-for-testing-i-promise".to_string()),
225 )],
226 )?;
227
228 Ok(())
229 }
230
231 #[test]
232 fn yubihsm2_admin_credentials_new_fails_on_no_admins() -> TestResult {
233 match YubiHsm2AdminCredentials::new(
234 1,
235 Passphrase::new("backup-passphrase-really-just-for-testing-i-promise-but-it-is-really-really-long-sufficiently-long-really".to_string()),
236 Vec::new(),
237 ) {
238 Ok(creds) => {
239 panic!("Expected Error::AdministratorMissing but succeeded instead:\n{creds:?}")
240 }
241
242 Err(crate::Error::AdminSecretHandling(Error::AdministratorMissing)) => {}
243 Err(error) => panic!(
244 "Expected Error::AdministratorMissing but failed differently instead:\n{error}"
245 ),
246 }
247
248 Ok(())
249 }
250
251 #[test]
252 fn yubihsm2_admin_credentials_new_fails_on_admin_passphrase_too_short() -> TestResult {
253 match YubiHsm2AdminCredentials::new(
254 1,
255 Passphrase::new("backup-passphrase-really-just-for-testing-i-promise-but-it-is-really-really-long-sufficiently-long-really".to_string()),
256 vec![Credentials::new(
257 "1".parse()?,
258 Passphrase::new("short".to_string()),
259 )],
260 ) {
261 Ok(creds) => {
262 panic!("Expected Error::PassphraseTooShort but succeeded instead:\n{creds:?}")
263 }
264 Err(crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(
265 signstar_crypto::passphrase::Error::Length { .. },
266 ))) => {}
267 Err(error) => panic!(
268 "Expected crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(
269 signstar_crypto::passphrase::Error::Length)) but failed differently instead:\n{error}"
270 ),
271 }
272
273 Ok(())
274 }
275
276 #[test]
277 fn yubihsm2_admin_credentials_new_fails_on_backup_passphrase_too_short() -> TestResult {
278 match YubiHsm2AdminCredentials::new(
279 1,
280 Passphrase::new("short".to_string()),
281 vec![Credentials::new(
282 "1".parse()?,
283 Passphrase::new("admin-passphrase-really-just-for-testing-i-promise".to_string()),
284 )],
285 ) {
286 Ok(creds) => {
287 panic!("Expected Error::PassphraseTooShort but succeeded instead:\n{creds:?}")
288 }
289 Err(crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(
290 signstar_crypto::passphrase::Error::Length { .. },
291 ))) => {}
292 Err(error) => panic!(
293 "Expected crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(
294 signstar_crypto::passphrase::Error::Length)) but failed differently instead:\n{error}"
295 ),
296 }
297
298 Ok(())
299 }
300
301 #[test]
304 fn yubihsm2_admin_credentials_try_from_config_fails_on_no_yubihsm2_config() -> TestResult {
305 let config = ConfigBuilder::new(SystemConfig::new(
306 1,
307 AdministrativeSecretHandling::Plaintext,
308 NonAdministrativeSecretHandling::Plaintext,
309 BTreeSet::new(),
310 )?)
311 .finish()?;
312
313 match YubiHsm2AdminCredentials::try_from(&config) {
314 Err(crate::Error::Config(crate::config::Error::YubiHsm2SectionMissing)) => {}
315 Err(error) => panic!(
316 "Expected to fail with Error::YubiHsm2SectionMissing but failed differently: {error}"
317 ),
318 Ok(creds) => panic!(
319 "Expected to fail with Error::YubiHsm2SectionMissing but succeeded instead: {creds:?}"
320 ),
321 }
322
323 Ok(())
324 }
325
326 #[cfg(feature = "_yubihsm2-mockhsm")]
329 #[test]
330 fn yubihsm2_admin_credentials_try_from_config_succeeds() -> TestResult {
331 let config = ConfigBuilder::new(SystemConfig::new(
332 1,
333 AdministrativeSecretHandling::Plaintext,
334 NonAdministrativeSecretHandling::Plaintext,
335 BTreeSet::new(),
336 )?)
337 .set_yubihsm2_config(YubiHsm2Config::new(
338 BTreeSet::from_iter([Connection::Mock]),
339 BTreeSet::from_iter([YubiHsm2UserMapping::Admin {
340 authentication_key_id: 1,
341 }]),
342 )?)
343 .finish()?;
344
345 let _ = YubiHsm2AdminCredentials::try_from(&config)?;
346
347 Ok(())
348 }
349}