1use rite_sdk::{
26 AttestationBackend, Backend, BackendConfig, BackendError, CertStoreBackend, KeyStoreBackend,
27 KeyTransportBackend, PivBackend, Pkcs11AdminBackend, Pkcs11Backend, RandomBackend, SignBackend,
28 TpmBackend, YubikeyBackend,
29};
30use std::collections::HashMap;
31
32pub type BackendFactory =
39 Box<dyn Fn(String, &BackendConfig) -> Result<Box<dyn Backend>, BackendError> + Send + Sync>;
40
41enum BackendState {
42 Uninitialized(BackendConfig),
45 Ready(Box<dyn Backend>),
47}
48
49pub struct BackendRegistry {
60 factory: BackendFactory,
61 backends: HashMap<String, BackendState>,
62}
63
64impl BackendRegistry {
65 pub fn with_factory(factory: BackendFactory) -> Self {
82 Self {
83 factory,
84 backends: HashMap::new(),
85 }
86 }
87
88 pub fn new() -> Self {
95 Self {
96 factory: Box::new(|name, _config| Err(BackendError::NotFound(name))),
97 backends: HashMap::new(),
98 }
99 }
100
101 pub fn declare(&mut self, name: String, config: BackendConfig) {
107 self.backends
108 .insert(name, BackendState::Uninitialized(config));
109 }
110
111 pub fn register(&mut self, backend: Box<dyn Backend>) {
115 let name = backend.name().to_string();
116 self.backends.insert(name, BackendState::Ready(backend));
117 }
118
119 pub fn get(&self, name: &str) -> Option<&dyn Backend> {
124 match self.backends.get(name) {
125 Some(BackendState::Ready(b)) => Some(b.as_ref()),
126 _ => None,
127 }
128 }
129
130 pub fn get_mut(&mut self, name: &str) -> Result<&mut dyn Backend, BackendError> {
136 self.get_ready(name)
137 }
138
139 pub fn get_keystore(&mut self, name: &str) -> Result<&mut dyn KeyStoreBackend, BackendError> {
141 self.get_ready(name)?
142 .as_keystore_mut()
143 .ok_or_else(|| unsupported(name, "KeyStoreBackend"))
144 }
145
146 pub fn get_sign(&mut self, name: &str) -> Result<&mut dyn SignBackend, BackendError> {
148 self.get_ready(name)?
149 .as_sign_mut()
150 .ok_or_else(|| unsupported(name, "SignBackend"))
151 }
152
153 pub fn get_attest_mut(
155 &mut self,
156 name: &str,
157 ) -> Result<&mut dyn AttestationBackend, BackendError> {
158 self.get_ready(name)?
159 .as_attest_mut()
160 .ok_or_else(|| unsupported(name, "AttestationBackend"))
161 }
162
163 pub fn get_piv(&mut self, name: &str) -> Result<&mut dyn PivBackend, BackendError> {
165 self.get_ready(name)?
166 .as_piv_mut()
167 .ok_or_else(|| unsupported(name, "PivBackend"))
168 }
169
170 pub fn get_yubikey(&mut self, name: &str) -> Result<&mut dyn YubikeyBackend, BackendError> {
172 self.get_ready(name)?
173 .as_yubikey_mut()
174 .ok_or_else(|| unsupported(name, "YubikeyBackend"))
175 }
176
177 pub fn get_transport(
179 &mut self,
180 name: &str,
181 ) -> Result<&mut dyn KeyTransportBackend, BackendError> {
182 self.get_ready(name)?
183 .as_transport_mut()
184 .ok_or_else(|| unsupported(name, "KeyTransportBackend"))
185 }
186
187 pub fn get_certstore(&mut self, name: &str) -> Result<&mut dyn CertStoreBackend, BackendError> {
189 self.get_ready(name)?
190 .as_certstore_mut()
191 .ok_or_else(|| unsupported(name, "CertStoreBackend"))
192 }
193
194 pub fn get_random(&mut self, name: &str) -> Result<&mut dyn RandomBackend, BackendError> {
196 self.get_ready(name)?
197 .as_random_mut()
198 .ok_or_else(|| unsupported(name, "RandomBackend"))
199 }
200
201 pub fn get_pkcs11(&mut self, name: &str) -> Result<&mut dyn Pkcs11Backend, BackendError> {
203 self.get_ready(name)?
204 .as_pkcs11_mut()
205 .ok_or_else(|| unsupported(name, "Pkcs11Backend"))
206 }
207
208 pub fn get_pkcs11_admin(
210 &mut self,
211 name: &str,
212 ) -> Result<&mut dyn Pkcs11AdminBackend, BackendError> {
213 self.get_ready(name)?
214 .as_pkcs11_admin_mut()
215 .ok_or_else(|| unsupported(name, "Pkcs11AdminBackend"))
216 }
217
218 pub fn get_tpm(&mut self, name: &str) -> Result<&mut dyn TpmBackend, BackendError> {
220 self.get_ready(name)?
221 .as_tpm_mut()
222 .ok_or_else(|| unsupported(name, "TpmBackend"))
223 }
224
225 pub fn backend_names(&self) -> impl Iterator<Item = &String> {
227 self.backends.keys()
228 }
229
230 fn get_ready(&mut self, name: &str) -> Result<&mut dyn Backend, BackendError> {
236 self.try_initialize(name)?;
237 match self.backends.get_mut(name) {
238 Some(BackendState::Ready(b)) => Ok(b.as_mut()),
239 None => Err(BackendError::NotFound(name.to_string())),
240 Some(BackendState::Uninitialized(_)) => {
241 unreachable!("backend should be Ready after try_initialize")
242 }
243 }
244 }
245
246 fn try_initialize(&mut self, name: &str) -> Result<(), BackendError> {
256 let config = match self.backends.get(name) {
258 Some(BackendState::Uninitialized(config)) => config.clone(),
259 Some(BackendState::Ready(_)) => return Ok(()),
260 None => return Err(BackendError::NotFound(name.to_string())),
261 };
262
263 let backend = (self.factory)(name.to_string(), &config)?;
265 if let Some(state) = self.backends.get_mut(name) {
267 *state = BackendState::Ready(backend);
268 }
269 Ok(())
270 }
271}
272
273impl Default for BackendRegistry {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279fn unsupported(name: &str, trait_name: &str) -> BackendError {
280 BackendError::UnsupportedOperation(format!("Backend '{name}' does not implement {trait_name}"))
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use rite_sdk::{Backend, BackendError, KeyId, KeyMetadata, KeySpec, KeyStoreBackend};
287
288 struct MinimalMock {
293 name: String,
294 }
295
296 impl Backend for MinimalMock {
297 fn name(&self) -> &str {
298 &self.name
299 }
300 fn provider(&self) -> &'static str {
301 "mock"
302 }
303 fn fingerprint(&self) -> String {
304 format!("mock={}", self.name)
305 }
306 fn as_keystore_mut(&mut self) -> Option<&mut dyn KeyStoreBackend> {
307 Some(self)
308 }
309 }
310
311 impl KeyStoreBackend for MinimalMock {
312 fn generate_key(&mut self, spec: KeySpec) -> Result<KeyMetadata, BackendError> {
313 Ok(KeyMetadata {
314 key_id: KeyId::new("mock-key-1"),
315 algorithm: spec.algorithm,
316 label: spec.label,
317 public_key: None,
318 attestation: None,
319 })
320 }
321 fn import_private_key(
322 &mut self,
323 spec: KeySpec,
324 _key_bytes: &[u8],
325 ) -> Result<KeyMetadata, BackendError> {
326 Ok(KeyMetadata {
327 key_id: KeyId::new("mock-imported-1"),
328 algorithm: spec.algorithm,
329 label: spec.label,
330 public_key: None,
331 attestation: None,
332 })
333 }
334 fn export_public_key(
335 &self,
336 key_id: &KeyId,
337 ) -> Result<rite_sdk::PublicKeyDer, BackendError> {
338 Err(BackendError::KeyNotFound(key_id.to_string()))
339 }
340 fn list_keys(&self) -> Result<Vec<KeyMetadata>, BackendError> {
341 Ok(vec![])
342 }
343 fn delete_key(&mut self, _key_id: &KeyId) -> Result<(), BackendError> {
344 Ok(())
345 }
346 }
347
348 #[test]
353 fn registry_not_found_returns_none() {
354 let registry = BackendRegistry::new();
355 assert!(registry.get("nonexistent").is_none());
356 }
357
358 #[test]
359 fn registry_lists_backend_names_empty() {
360 let registry = BackendRegistry::new();
361 assert_eq!(registry.backend_names().count(), 0);
362 }
363
364 #[test]
365 fn register_then_get_returns_backend() {
366 let mut registry = BackendRegistry::new();
367 registry.register(Box::new(MinimalMock {
368 name: "mock".to_string(),
369 }));
370 assert!(registry.get("mock").is_some());
371 assert_eq!(registry.get("mock").unwrap().name(), "mock");
372 }
373
374 #[test]
375 fn register_then_get_keystore_succeeds() {
376 let mut registry = BackendRegistry::new();
377 registry.register(Box::new(MinimalMock {
378 name: "mock".to_string(),
379 }));
380 assert!(registry.get_keystore("mock").is_ok());
381 }
382
383 #[test]
384 fn declare_and_lazy_init_via_factory() {
385 let mut registry = BackendRegistry::with_factory(Box::new(|name, _config| {
386 Ok(Box::new(MinimalMock { name }) as Box<dyn Backend>)
387 }));
388
389 let config = BackendConfig {
390 provider: "mock".to_string(),
391 extra: serde_json::json!({}),
392 };
393
394 registry.declare("lazy".to_string(), config);
396
397 assert!(registry.get("lazy").is_none());
399
400 let result = registry.get_keystore("lazy");
402 assert!(result.is_ok(), "Expected Ok, got: {:?}", result.err());
403
404 assert!(registry.get("lazy").is_some());
406 }
407
408 #[test]
409 fn failed_initialization_is_probed_again() {
410 use std::sync::Arc;
411 use std::sync::atomic::{AtomicU32, Ordering};
412
413 let calls = Arc::new(AtomicU32::new(0));
416 let calls_in_factory = Arc::clone(&calls);
417 let mut registry = BackendRegistry::with_factory(Box::new(move |name, _config| {
418 if calls_in_factory.fetch_add(1, Ordering::SeqCst) == 0 {
419 Err(BackendError::HardwareFailure("no device".to_string()))
420 } else {
421 Ok(Box::new(MinimalMock { name }) as Box<dyn Backend>)
422 }
423 }));
424 registry.declare(
425 "token".to_string(),
426 BackendConfig {
427 provider: "mock".to_string(),
428 extra: serde_json::json!({}),
429 },
430 );
431
432 let first = registry.get_mut("token");
433 assert!(matches!(first, Err(BackendError::HardwareFailure(_))));
434
435 let second = registry.get_mut("token");
436 assert!(second.is_ok(), "second access probes the hardware again");
437 assert_eq!(calls.load(Ordering::SeqCst), 2);
438 }
439
440 #[test]
441 fn unsupported_trait_returns_error() {
442 let mut registry = BackendRegistry::new();
444 registry.register(Box::new(MinimalMock {
445 name: "mock".to_string(),
446 }));
447
448 let result = registry.get_sign("mock");
449 assert!(result.is_err());
450 match result.err().unwrap() {
451 BackendError::UnsupportedOperation(msg) => {
452 assert!(
453 msg.contains("SignBackend"),
454 "Expected UnsupportedOperation for SignBackend, got: {msg}"
455 );
456 }
457 e => panic!("Expected UnsupportedOperation, got: {e}"),
458 }
459 }
460
461 #[test]
462 fn get_mut_undeclared_returns_not_found() {
463 let mut registry = BackendRegistry::new();
464 let result = registry.get_mut("nonexistent");
465 assert!(result.is_err());
466 match result.err().unwrap() {
467 BackendError::NotFound(name) => assert_eq!(name, "nonexistent"),
468 e => panic!("Expected NotFound, got: {e}"),
469 }
470 }
471
472 #[test]
473 fn lazy_init_unsupported_trait_returns_error() {
474 let mut registry = BackendRegistry::with_factory(Box::new(|name, _config| {
477 Ok(Box::new(MinimalMock { name }) as Box<dyn Backend>)
478 }));
479
480 let config = BackendConfig {
481 provider: "yubikey".to_string(),
482 extra: serde_json::json!({}),
483 };
484 registry.declare("yk".to_string(), config);
485
486 let result = registry.get_attest_mut("yk");
487 match result {
488 Err(BackendError::UnsupportedOperation(msg)) => {
489 assert!(
490 msg.contains("AttestationBackend"),
491 "Expected AttestationBackend error, got: {msg}"
492 );
493 }
494 Ok(_) => panic!("Expected UnsupportedOperation, got Ok"),
495 Err(e) => panic!("Expected UnsupportedOperation, got: {e:?}"),
496 }
497 }
498}