1#![cfg(feature = "yubihsm2")]
3
4use std::collections::{BTreeSet, HashSet};
5
6use garde::Validate;
7use serde::{Deserialize, Serialize};
8use signstar_crypto::{key::SigningKeySetup, passphrase::Passphrase, traits::UserWithPassphrase};
9use signstar_yubihsm2::{
10 Connection,
11 Credentials,
12 object::{Domain, Id},
13 yubihsm::{Capability, Code},
14};
15
16use crate::config::{
17 AuthorizedKeyEntry,
18 BackendDomainFilter,
19 BackendKeyIdFilter,
20 BackendUserIdFilter,
21 BackendUserIdKind,
22 ConfigAuthorizedKeyEntries,
23 ConfigSystemUserIds,
24 MappingAuthorizedKeyEntry,
25 MappingBackendDomain,
26 MappingBackendKeyId,
27 MappingBackendUserIds,
28 MappingBackendUserSecrets,
29 MappingSystemUserId,
30 SystemUserData,
31 SystemUserId,
32 duplicate_authorized_keys,
33 duplicate_backend_user_ids,
34 duplicate_domains,
35 duplicate_key_ids,
36 duplicate_system_user_ids,
37};
38
39#[derive(Debug, thiserror::Error)]
41pub enum Error {
42 #[error("Expected the YubiHSM2 authentication key ID {expected}, but found {actual} instead")]
44 AuthenticationKeyIdMismatch {
45 expected: String,
47
48 actual: String,
50 },
51
52 #[error("Error while constructing a YubiHSM2 key domain from {key_domain}, because {reason}")]
54 InvalidDomain {
55 reason: String,
60
61 key_domain: String,
63 },
64}
65
66#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
68#[serde(rename_all = "snake_case")]
69pub enum YubiHsm2UserMapping {
70 Admin {
86 authentication_key_id: Id,
88 },
89
90 AuditLog {
110 authentication_key_id: Id,
112
113 ssh_authorized_key: AuthorizedKeyEntry,
115
116 system_user: SystemUserId,
118 },
119
120 Backup {
146 authentication_key_id: Id,
152
153 wrapping_key_id: Id,
164
165 ssh_authorized_key: AuthorizedKeyEntry,
167
168 system_user: SystemUserId,
170 },
171
172 HermeticAuditLog {
192 authentication_key_id: Id,
194
195 system_user: SystemUserId,
197 },
198
199 Signing {
230 authentication_key_id: Id,
232
233 key_setup: SigningKeySetup,
235
236 domain: Domain,
240
241 signing_key_id: Id,
243
244 ssh_authorized_key: AuthorizedKeyEntry,
246
247 system_user: SystemUserId,
249 },
250}
251
252impl YubiHsm2UserMapping {
253 pub const CAP_ADMIN: &[Capability] = &[
287 Capability::CHANGE_AUTHENTICATION_KEY,
288 Capability::DELETE_ASYMMETRIC_KEY,
289 Capability::DELETE_AUTHENTICATION_KEY,
290 Capability::DELETE_HMAC_KEY,
291 Capability::DELETE_OPAQUE,
292 Capability::DELETE_TEMPLATE,
293 Capability::DELETE_WRAP_KEY,
294 Capability::EXPORTABLE_UNDER_WRAP,
295 Capability::GENERATE_ASYMMETRIC_KEY,
296 Capability::GENERATE_HMAC_KEY,
297 Capability::GENERATE_WRAP_KEY,
298 Capability::GET_OPAQUE,
299 Capability::GET_OPTION,
300 Capability::GET_TEMPLATE,
301 Capability::IMPORT_WRAPPED,
302 Capability::PUT_ASYMMETRIC_KEY,
303 Capability::PUT_AUTHENTICATION_KEY,
304 Capability::PUT_HMAC_KEY,
305 Capability::PUT_OPAQUE,
306 Capability::PUT_OPTION,
307 Capability::PUT_TEMPLATE,
308 Capability::PUT_WRAP_KEY,
309 Capability::RESET_DEVICE,
310 Capability::SIGN_HMAC,
311 Capability::UNWRAP_DATA,
312 Capability::VERIFY_HMAC,
313 Capability::WRAP_DATA,
314 ];
315
316 pub const CAP_AUDIT_LOG: &[Capability] = &[Capability::GET_LOG_ENTRIES];
324
325 pub const CAP_BACKUP: &[Capability] = &[Capability::EXPORT_WRAPPED];
333
334 pub const CAP_HERMETIC_AUDIT_LOG: &[Capability] = &[Capability::GET_LOG_ENTRIES];
342
343 pub const CAP_SIGNING: &[Capability] = &[Capability::SIGN_EDDSA];
351
352 pub fn domain(&self) -> Option<&Domain> {
354 match self {
355 Self::Admin { .. }
356 | Self::Backup { .. }
357 | Self::AuditLog { .. }
358 | Self::HermeticAuditLog { .. } => None,
359 Self::Signing {
360 domain: key_domain, ..
361 } => Some(key_domain),
362 }
363 }
364
365 pub fn backend_user_id(&self) -> Id {
367 match self {
368 Self::Admin {
369 authentication_key_id,
370 }
371 | Self::AuditLog {
372 authentication_key_id,
373 ..
374 }
375 | Self::Backup {
376 authentication_key_id,
377 ..
378 }
379 | Self::HermeticAuditLog {
380 authentication_key_id,
381 ..
382 }
383 | Self::Signing {
384 authentication_key_id,
385 ..
386 } => *authentication_key_id,
387 }
388 }
389
390 pub fn capability(&self) -> Capability {
397 match self {
398 Self::Admin { .. } => Self::CAP_ADMIN,
399 Self::AuditLog { .. } => Self::CAP_AUDIT_LOG,
400 Self::Backup { .. } => Self::CAP_BACKUP,
401 Self::HermeticAuditLog { .. } => Self::CAP_HERMETIC_AUDIT_LOG,
402 Self::Signing { .. } => Self::CAP_SIGNING,
403 }
404 .iter()
405 .fold(Capability::empty(), |acc, cap| acc | *cap)
406 }
407}
408
409impl MappingSystemUserId for YubiHsm2UserMapping {
410 fn system_user_id(&self) -> Option<&SystemUserId> {
411 match self {
412 Self::Admin { .. } => None,
413 Self::AuditLog { system_user, .. }
414 | Self::Backup { system_user, .. }
415 | Self::HermeticAuditLog { system_user, .. }
416 | Self::Signing { system_user, .. } => Some(system_user),
417 }
418 }
419}
420
421impl MappingBackendUserIds for YubiHsm2UserMapping {
422 fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
423 match self {
424 Self::Admin {
425 authentication_key_id,
426 } => {
427 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
428 .contains(&filter.backend_user_id_kind)
429 {
430 Some(vec![authentication_key_id.to_string()])
431 } else {
432 None
433 }
434 }
435 Self::AuditLog {
436 authentication_key_id,
437 ..
438 } => {
439 if [
440 BackendUserIdKind::Any,
441 BackendUserIdKind::Metrics,
442 BackendUserIdKind::NonAdmin,
443 ]
444 .contains(&filter.backend_user_id_kind)
445 {
446 Some(vec![authentication_key_id.to_string()])
447 } else {
448 None
449 }
450 }
451 Self::Backup {
452 authentication_key_id,
453 ..
454 } => {
455 if [
456 BackendUserIdKind::Any,
457 BackendUserIdKind::Backup,
458 BackendUserIdKind::NonAdmin,
459 ]
460 .contains(&filter.backend_user_id_kind)
461 {
462 Some(vec![authentication_key_id.to_string()])
463 } else {
464 None
465 }
466 }
467 Self::HermeticAuditLog {
468 authentication_key_id,
469 ..
470 } => {
471 if [
472 BackendUserIdKind::Any,
473 BackendUserIdKind::Metrics,
474 BackendUserIdKind::NonAdmin,
475 ]
476 .contains(&filter.backend_user_id_kind)
477 {
478 Some(vec![authentication_key_id.to_string()])
479 } else {
480 None
481 }
482 }
483 Self::Signing {
484 authentication_key_id,
485 ..
486 } => {
487 if [
488 BackendUserIdKind::Any,
489 BackendUserIdKind::NonAdmin,
490 BackendUserIdKind::Signing,
491 ]
492 .contains(&filter.backend_user_id_kind)
493 {
494 Some(vec![authentication_key_id.to_string()])
495 } else {
496 None
497 }
498 }
499 }
500 .unwrap_or_default()
501 }
502
503 fn backend_user_with_passphrase(
504 &self,
505 name: &str,
506 passphrase: Passphrase,
507 ) -> Result<Box<dyn UserWithPassphrase>, crate::Error> {
508 let backend_user_id = self.backend_user_id();
509 if backend_user_id.to_string() != name {
510 return Err(Error::AuthenticationKeyIdMismatch {
511 expected: name.to_string(),
512 actual: backend_user_id.to_string(),
513 }
514 .into());
515 }
516
517 Ok(Box::new(Credentials::new(backend_user_id, passphrase)))
518 }
519
520 fn backend_users_with_new_passphrase(
521 &self,
522 filter: BackendUserIdFilter,
523 ) -> Vec<Box<dyn UserWithPassphrase>> {
524 if let Some(authentication_key_id) = match self {
525 Self::Admin {
526 authentication_key_id,
527 } => {
528 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
529 .contains(&filter.backend_user_id_kind)
530 {
531 Some(authentication_key_id)
532 } else {
533 None
534 }
535 }
536 Self::AuditLog {
537 authentication_key_id,
538 ..
539 } => {
540 if [
541 BackendUserIdKind::Any,
542 BackendUserIdKind::Metrics,
543 BackendUserIdKind::NonAdmin,
544 ]
545 .contains(&filter.backend_user_id_kind)
546 {
547 Some(authentication_key_id)
548 } else {
549 None
550 }
551 }
552 Self::Backup {
553 authentication_key_id,
554 ..
555 } => {
556 if [
557 BackendUserIdKind::Any,
558 BackendUserIdKind::Backup,
559 BackendUserIdKind::NonAdmin,
560 ]
561 .contains(&filter.backend_user_id_kind)
562 {
563 Some(authentication_key_id)
564 } else {
565 None
566 }
567 }
568 Self::HermeticAuditLog {
569 authentication_key_id,
570 ..
571 } => {
572 if [
573 BackendUserIdKind::Any,
574 BackendUserIdKind::Metrics,
575 BackendUserIdKind::NonAdmin,
576 ]
577 .contains(&filter.backend_user_id_kind)
578 {
579 Some(authentication_key_id)
580 } else {
581 None
582 }
583 }
584 Self::Signing {
585 authentication_key_id,
586 ..
587 } => {
588 if [
589 BackendUserIdKind::Any,
590 BackendUserIdKind::NonAdmin,
591 BackendUserIdKind::Signing,
592 ]
593 .contains(&filter.backend_user_id_kind)
594 {
595 Some(authentication_key_id)
596 } else {
597 None
598 }
599 }
600 } {
601 vec![Box::new(Credentials::new(
602 *authentication_key_id,
603 Passphrase::generate(None),
604 ))]
605 } else {
606 Vec::new()
607 }
608 }
609}
610
611impl MappingAuthorizedKeyEntry for YubiHsm2UserMapping {
612 fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
613 match self {
614 Self::Admin { .. } | Self::HermeticAuditLog { .. } => None,
615 Self::AuditLog {
616 ssh_authorized_key, ..
617 }
618 | Self::Backup {
619 ssh_authorized_key, ..
620 }
621 | Self::Signing {
622 ssh_authorized_key, ..
623 } => Some(ssh_authorized_key),
624 }
625 }
626}
627
628impl<'a> From<&'a YubiHsm2UserMapping> for SystemUserData<'a> {
629 fn from(value: &'a YubiHsm2UserMapping) -> Self {
630 match value {
631 YubiHsm2UserMapping::Admin { .. } => Self::BackendAdmin {
632 system_user: SystemUserId::root(),
633 },
634 YubiHsm2UserMapping::AuditLog {
635 ssh_authorized_key,
636 system_user,
637 ..
638 } => Self::BackendMetrics {
639 system_user,
640 ssh_authorized_key,
641 },
642 YubiHsm2UserMapping::Backup {
643 ssh_authorized_key,
644 system_user,
645 ..
646 } => Self::BackendBackup {
647 system_user,
648 ssh_authorized_key,
649 },
650 YubiHsm2UserMapping::HermeticAuditLog { system_user, .. } => {
651 Self::BackendHermeticMetrics { system_user }
652 }
653 YubiHsm2UserMapping::Signing {
654 ssh_authorized_key,
655 system_user,
656 ..
657 } => Self::BackendSign {
658 system_user,
659 ssh_authorized_key,
660 },
661 }
662 }
663}
664
665#[derive(Clone, Copy, Debug)]
667pub struct YubiHsm2DomainFilter {}
668
669impl BackendDomainFilter for YubiHsm2DomainFilter {}
670
671impl MappingBackendDomain<YubiHsm2DomainFilter> for YubiHsm2UserMapping {
672 fn backend_domain(&self, _filter: Option<&YubiHsm2DomainFilter>) -> Option<String> {
673 self.domain().map(|domain| domain.to_string())
674 }
675}
676
677#[derive(Clone, Copy, Debug, Eq, PartialEq)]
685pub enum KeyObjectType {
686 Signing,
690
691 Wrapping,
695}
696
697#[derive(Clone, Debug)]
699pub struct YubiHsm2BackendKeyIdFilter {
700 pub key_type: KeyObjectType,
704
705 pub key_domain: Option<Domain>,
709}
710
711impl BackendKeyIdFilter for YubiHsm2BackendKeyIdFilter {}
712
713impl MappingBackendKeyId<YubiHsm2BackendKeyIdFilter> for YubiHsm2UserMapping {
714 fn backend_key_id(&self, filter: &YubiHsm2BackendKeyIdFilter) -> Option<String> {
715 match self {
716 Self::Admin { .. } | Self::AuditLog { .. } | Self::HermeticAuditLog { .. } => None,
717 Self::Backup {
718 wrapping_key_id, ..
719 } => {
720 if filter.key_type == KeyObjectType::Wrapping {
721 Some(wrapping_key_id.to_string())
723 } else {
724 None
725 }
726 }
727 Self::Signing {
728 signing_key_id,
729 domain: key_domain,
730 ..
731 } => {
732 if filter.key_type == KeyObjectType::Signing {
733 if let Some(filter_key_domain) = filter.key_domain {
734 if &filter_key_domain == key_domain {
735 Some(signing_key_id.to_string())
736 } else {
737 None
738 }
739 } else {
740 Some(signing_key_id.to_string())
741 }
742 } else {
743 None
744 }
745 }
746 }
747 }
748}
749
750impl MappingBackendUserSecrets for YubiHsm2UserMapping {}
751
752fn validate_yubihsm2_config_connections(
760 value: &BTreeSet<Connection>,
761 _context: &(),
762) -> garde::Result {
763 if value.is_empty() {
764 return Err(garde::Error::new("contains no connections".to_string()));
765 }
766
767 Ok(())
768}
769
770fn validate_yubihsm2_config_mappings(
797 value: &BTreeSet<YubiHsm2UserMapping>,
798 _context: &(),
799) -> garde::Result {
800 if value.is_empty() {
801 return Err(garde::Error::new("contains no user mappings".to_string()));
802 }
803
804 let duplicate_system_user_ids = duplicate_system_user_ids(value);
806
807 let duplicate_authorized_keys = duplicate_authorized_keys(value);
809
810 let missing_admin = {
812 let num_system_admins = value
813 .iter()
814 .filter_map(|mapping| {
815 if let YubiHsm2UserMapping::Admin {
816 authentication_key_id,
817 } = mapping
818 {
819 Some(authentication_key_id)
820 } else {
821 None
822 }
823 })
824 .count();
825
826 if num_system_admins == 0 {
827 Some("no administrator user".to_string())
828 } else {
829 None
830 }
831 };
832
833 let duplicate_backend_user_ids = duplicate_backend_user_ids(value);
835
836 let duplicate_signing_key_ids = duplicate_key_ids(
838 value,
839 &YubiHsm2BackendKeyIdFilter {
840 key_type: KeyObjectType::Signing,
841 key_domain: None,
842 },
843 Some(" signing".to_string()),
844 );
845
846 let duplicate_wrapping_key_ids = duplicate_key_ids(
848 value,
849 &YubiHsm2BackendKeyIdFilter {
850 key_type: KeyObjectType::Wrapping,
851 key_domain: None,
852 },
853 Some(" wrapping".to_string()),
854 );
855
856 let duplicate_domains = duplicate_domains(value, None, None, None);
858
859 let messages = [
860 duplicate_system_user_ids,
861 duplicate_authorized_keys,
862 missing_admin,
863 duplicate_backend_user_ids,
864 duplicate_signing_key_ids,
865 duplicate_wrapping_key_ids,
866 duplicate_domains,
867 ];
868 let error_messages = {
869 let mut error_messages = Vec::new();
870
871 for message in messages.iter().flatten() {
872 error_messages.push(message.as_str());
873 }
874
875 error_messages
876 };
877
878 match error_messages.len() {
879 0 => Ok(()),
880 1 => Err(garde::Error::new(format!(
881 "contains {}",
882 error_messages.join("\n")
883 ))),
884 _ => Err(garde::Error::new(format!(
885 "contains multiple issues:\n⤷ {}",
886 error_messages.join("\n⤷ ")
887 ))),
888 }
889}
890
891#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
896#[serde(rename_all = "snake_case")]
897pub struct YubiHsm2Config {
898 #[garde(custom(validate_yubihsm2_config_connections))]
900 connections: BTreeSet<Connection>,
901
902 #[garde(custom(validate_yubihsm2_config_mappings))]
904 mappings: BTreeSet<YubiHsm2UserMapping>,
905}
906
907impl YubiHsm2Config {
908 pub const AUDIT_COMMANDS: &[Code] = &[
912 Code::AuthenticateSession,
913 Code::ChangeAuthenticationKey,
914 Code::CloseSession,
915 Code::CreateSession,
916 Code::DeleteObject,
917 Code::ExportWrapped,
918 Code::GetObjectInfo,
919 Code::GetLogEntries,
920 Code::GetOpaqueObject,
921 Code::GetOption,
922 Code::GetPublicKey,
923 Code::GetStorageInfo,
924 Code::HsmInitialization,
925 Code::ImportWrapped,
926 Code::PutOpaqueObject,
927 Code::PutWrapKey,
928 Code::ResetDevice,
929 Code::SetOption,
930 Code::SignAttestationCertificate,
931 Code::SignEddsa,
932 ];
933
934 pub fn new(
937 connections: BTreeSet<Connection>,
938 mappings: BTreeSet<YubiHsm2UserMapping>,
939 ) -> Result<Self, crate::Error> {
940 let config = Self {
941 connections,
942 mappings,
943 };
944 config
945 .validate()
946 .map_err(|source| crate::Error::Validation {
947 context: "validating a YubiHSM2 specific configuration item".to_string(),
948 source,
949 })?;
950
951 Ok(config)
952 }
953
954 pub fn connections(&self) -> &BTreeSet<Connection> {
956 &self.connections
957 }
958
959 pub fn mappings(&self) -> &BTreeSet<YubiHsm2UserMapping> {
961 &self.mappings
962 }
963}
964
965impl ConfigAuthorizedKeyEntries for YubiHsm2Config {
966 fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
967 self.mappings
968 .iter()
969 .filter_map(|mapping| mapping.authorized_key_entry())
970 .collect()
971 }
972}
973
974impl ConfigSystemUserIds for YubiHsm2Config {
975 fn system_user_ids(&self) -> HashSet<&SystemUserId> {
976 self.mappings
977 .iter()
978 .filter_map(|mapping| mapping.system_user_id())
979 .collect()
980 }
981}
982
983#[cfg(test)]
984mod tests {
985 use std::thread::current;
986
987 use insta::{assert_snapshot, with_settings};
988 use rstest::{fixture, rstest};
989 use signstar_crypto::{
990 key::{CryptographicKeyContext, KeyMechanism, KeyType, SignatureType, SigningKeySetup},
991 openpgp::OpenPgpUserIdList,
992 };
993 use testresult::TestResult;
994
995 use super::*;
996
997 const SNAPSHOT_PATH: &str = "fixtures/yubihsm2_config/";
998
999 #[rstest]
1000 #[case::admin(YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? })]
1001 #[case::audit_log(
1002 YubiHsm2UserMapping::AuditLog {
1003 authentication_key_id: "1".parse()?,
1004 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1005 system_user: "metrics-user".parse()?,
1006 },
1007 )]
1008 #[case::backup(
1009 YubiHsm2UserMapping::Backup{
1010 authentication_key_id: "1".parse()?,
1011 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1012 system_user: "backup-user".parse()?,
1013 wrapping_key_id: "1".parse()?,
1014 },
1015 )]
1016 #[case::hermetic_audit_log(
1017 YubiHsm2UserMapping::HermeticAuditLog {
1018 authentication_key_id: "1".parse()?,
1019 system_user: "metrics-user".parse()?,
1020 },
1021 )]
1022 #[case::signing(
1023 YubiHsm2UserMapping::Signing {
1024 authentication_key_id: "1".parse()?,
1025 signing_key_id: "1".parse()?,
1026 key_setup: SigningKeySetup::new(
1027 KeyType::Curve25519,
1028 vec![KeyMechanism::EdDsaSignature],
1029 None,
1030 SignatureType::EdDsa,
1031 CryptographicKeyContext::OpenPgp {
1032 user_ids: OpenPgpUserIdList::new(vec![
1033 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1034 ])?,
1035 version: "v4".parse()?,
1036 },
1037 )?,
1038 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1039 system_user: "signing-user".parse()?,
1040 domain: Domain::One,
1041 }
1042 )]
1043 fn yubihsm2_user_mapping_backend_user_id(#[case] mapping: YubiHsm2UserMapping) -> TestResult {
1044 let id: Id = "1".parse()?;
1045 assert_eq!(mapping.backend_user_id(), id);
1046
1047 Ok(())
1048 }
1049
1050 #[rstest]
1052 #[case::admin(
1053 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1054 YubiHsm2UserMapping::CAP_ADMIN,
1055 )]
1056 #[case::audit_log(
1057 YubiHsm2UserMapping::AuditLog {
1058 authentication_key_id: "1".parse()?,
1059 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1060 system_user: "metrics-user".parse()?,
1061 },
1062 YubiHsm2UserMapping::CAP_AUDIT_LOG,
1063 )]
1064 #[case::backup(
1065 YubiHsm2UserMapping::Backup{
1066 authentication_key_id: "1".parse()?,
1067 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1068 system_user: "backup-user".parse()?,
1069 wrapping_key_id: "1".parse()?,
1070 },
1071 YubiHsm2UserMapping::CAP_BACKUP,
1072 )]
1073 #[case::hermetic_audit_log(
1074 YubiHsm2UserMapping::HermeticAuditLog {
1075 authentication_key_id: "1".parse()?,
1076 system_user: "metrics-user".parse()?,
1077 },
1078 YubiHsm2UserMapping::CAP_HERMETIC_AUDIT_LOG,
1079 )]
1080 #[case::signing(
1081 YubiHsm2UserMapping::Signing {
1082 authentication_key_id: "1".parse()?,
1083 signing_key_id: "1".parse()?,
1084 key_setup: SigningKeySetup::new(
1085 KeyType::Curve25519,
1086 vec![KeyMechanism::EdDsaSignature],
1087 None,
1088 SignatureType::EdDsa,
1089 CryptographicKeyContext::OpenPgp {
1090 user_ids: OpenPgpUserIdList::new(vec![
1091 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1092 ])?,
1093 version: "v4".parse()?,
1094 },
1095 )?,
1096 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1097 system_user: "signing-user".parse()?,
1098 domain: Domain::One,
1099 },
1100 YubiHsm2UserMapping::CAP_SIGNING,
1101 )]
1102 fn yubihsm2_user_mapping_capability(
1103 #[case] mapping: YubiHsm2UserMapping,
1104 #[case] expected: &[Capability],
1105 ) -> TestResult {
1106 let expected = expected
1107 .iter()
1108 .fold(Capability::empty(), |acc, cap| acc | *cap);
1109
1110 assert_eq!(mapping.capability(), expected);
1111
1112 Ok(())
1113 }
1114
1115 #[rstest]
1116 #[case::admin_filter_admin(
1117 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1118 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1119 )]
1120 #[case::admin_filter_any(
1121 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1122 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1123 )]
1124 #[case::audit_log_filter_metrics(
1125 YubiHsm2UserMapping::AuditLog {
1126 authentication_key_id: "1".parse()?,
1127 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1128 system_user: "metrics-user".parse()?,
1129 },
1130 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1131 )]
1132 #[case::audit_log_filter_any(
1133 YubiHsm2UserMapping::AuditLog {
1134 authentication_key_id: "1".parse()?,
1135 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1136 system_user: "metrics-user".parse()?,
1137 },
1138 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1139 )]
1140 #[case::audit_log_filter_non_admin(
1141 YubiHsm2UserMapping::AuditLog {
1142 authentication_key_id: "1".parse()?,
1143 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1144 system_user: "metrics-user".parse()?,
1145 },
1146 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1147 )]
1148 #[case::backup_filter_backup(
1149 YubiHsm2UserMapping::Backup{
1150 authentication_key_id: "1".parse()?,
1151 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1152 system_user: "backup-user".parse()?,
1153 wrapping_key_id: "1".parse()?,
1154 },
1155 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1156 )]
1157 #[case::backup_filter_any(
1158 YubiHsm2UserMapping::Backup{
1159 authentication_key_id: "1".parse()?,
1160 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1161 system_user: "backup-user".parse()?,
1162 wrapping_key_id: "1".parse()?,
1163 },
1164 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1165 )]
1166 #[case::backup_filter_non_admin(
1167 YubiHsm2UserMapping::Backup{
1168 authentication_key_id: "1".parse()?,
1169 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1170 system_user: "backup-user".parse()?,
1171 wrapping_key_id: "1".parse()?,
1172 },
1173 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1174 )]
1175 #[case::hermetic_audit_log_filter_metrics(
1176 YubiHsm2UserMapping::HermeticAuditLog {
1177 authentication_key_id: "1".parse()?,
1178 system_user: "metrics-user".parse()?,
1179 },
1180 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1181 )]
1182 #[case::hermetic_audit_log_filter_any(
1183 YubiHsm2UserMapping::HermeticAuditLog {
1184 authentication_key_id: "1".parse()?,
1185 system_user: "metrics-user".parse()?,
1186 },
1187 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1188 )]
1189 #[case::hermetic_audit_log_filter_non_admin(
1190 YubiHsm2UserMapping::HermeticAuditLog {
1191 authentication_key_id: "1".parse()?,
1192 system_user: "metrics-user".parse()?,
1193 },
1194 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1195 )]
1196 #[case::signing_filter_signing(
1197 YubiHsm2UserMapping::Signing {
1198 authentication_key_id: "1".parse()?,
1199 signing_key_id: "1".parse()?,
1200 key_setup: SigningKeySetup::new(
1201 KeyType::Curve25519,
1202 vec![KeyMechanism::EdDsaSignature],
1203 None,
1204 SignatureType::EdDsa,
1205 CryptographicKeyContext::OpenPgp {
1206 user_ids: OpenPgpUserIdList::new(vec![
1207 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1208 ])?,
1209 version: "v4".parse()?,
1210 },
1211 )?,
1212 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1213 system_user: "signing-user".parse()?,
1214 domain: Domain::One,
1215 },
1216 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1217 )]
1218 #[case::signing_filter_any(
1219 YubiHsm2UserMapping::Signing {
1220 authentication_key_id: "1".parse()?,
1221 signing_key_id: "1".parse()?,
1222 key_setup: SigningKeySetup::new(
1223 KeyType::Curve25519,
1224 vec![KeyMechanism::EdDsaSignature],
1225 None,
1226 SignatureType::EdDsa,
1227 CryptographicKeyContext::OpenPgp {
1228 user_ids: OpenPgpUserIdList::new(vec![
1229 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1230 ])?,
1231 version: "v4".parse()?,
1232 },
1233 )?,
1234 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1235 system_user: "signing-user".parse()?,
1236 domain: Domain::One,
1237 },
1238 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1239 )]
1240 #[case::signing_filter_non_admin(
1241 YubiHsm2UserMapping::Signing {
1242 authentication_key_id: "1".parse()?,
1243 signing_key_id: "1".parse()?,
1244 key_setup: SigningKeySetup::new(
1245 KeyType::Curve25519,
1246 vec![KeyMechanism::EdDsaSignature],
1247 None,
1248 SignatureType::EdDsa,
1249 CryptographicKeyContext::OpenPgp {
1250 user_ids: OpenPgpUserIdList::new(vec![
1251 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1252 ])?,
1253 version: "v4".parse()?,
1254 },
1255 )?,
1256 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1257 system_user: "signing-user".parse()?,
1258 domain: Domain::One,
1259 },
1260 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1261 )]
1262 fn yubihsm2_user_mapping_backend_user_ids_filter_matches(
1263 #[case] mapping: YubiHsm2UserMapping,
1264 #[case] filter: BackendUserIdFilter,
1265 ) -> TestResult {
1266 assert_eq!(mapping.backend_user_ids(filter), vec!["1".to_string()]);
1267
1268 Ok(())
1269 }
1270
1271 #[rstest]
1272 #[case::admin_filter_non_admin(
1273 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1274 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1275 )]
1276 #[case::admin_filter_backup(
1277 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1278 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1279 )]
1280 #[case::admin_filter_metrics(
1281 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1282 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1283 )]
1284 #[case::admin_filter_observer(
1285 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1286 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1287 )]
1288 #[case::admin_filter_signing(
1289 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1290 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1291 )]
1292 #[case::audit_log_filter_admin(
1293 YubiHsm2UserMapping::AuditLog {
1294 authentication_key_id: "1".parse()?,
1295 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1296 system_user: "metrics-user".parse()?,
1297 },
1298 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1299 )]
1300 #[case::audit_log_filter_backup(
1301 YubiHsm2UserMapping::AuditLog {
1302 authentication_key_id: "1".parse()?,
1303 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1304 system_user: "metrics-user".parse()?,
1305 },
1306 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1307 )]
1308 #[case::audit_log_filter_observer(
1309 YubiHsm2UserMapping::AuditLog {
1310 authentication_key_id: "1".parse()?,
1311 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1312 system_user: "metrics-user".parse()?,
1313 },
1314 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1315 )]
1316 #[case::audit_log_filter_signing(
1317 YubiHsm2UserMapping::AuditLog {
1318 authentication_key_id: "1".parse()?,
1319 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1320 system_user: "metrics-user".parse()?,
1321 },
1322 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1323 )]
1324 #[case::backup_filter_admin(
1325 YubiHsm2UserMapping::Backup{
1326 authentication_key_id: "1".parse()?,
1327 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1328 system_user: "backup-user".parse()?,
1329 wrapping_key_id: "1".parse()?,
1330 },
1331 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1332 )]
1333 #[case::backup_filter_metrics(
1334 YubiHsm2UserMapping::Backup{
1335 authentication_key_id: "1".parse()?,
1336 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1337 system_user: "backup-user".parse()?,
1338 wrapping_key_id: "1".parse()?,
1339 },
1340 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1341 )]
1342 #[case::backup_filter_observer(
1343 YubiHsm2UserMapping::Backup{
1344 authentication_key_id: "1".parse()?,
1345 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1346 system_user: "backup-user".parse()?,
1347 wrapping_key_id: "1".parse()?,
1348 },
1349 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1350 )]
1351 #[case::backup_filter_signing(
1352 YubiHsm2UserMapping::Backup{
1353 authentication_key_id: "1".parse()?,
1354 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1355 system_user: "backup-user".parse()?,
1356 wrapping_key_id: "1".parse()?,
1357 },
1358 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1359 )]
1360 #[case::hermetic_audit_log_filter_admin(
1361 YubiHsm2UserMapping::HermeticAuditLog {
1362 authentication_key_id: "1".parse()?,
1363 system_user: "metrics-user".parse()?,
1364 },
1365 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1366 )]
1367 #[case::hermetic_audit_log_filter_backup(
1368 YubiHsm2UserMapping::HermeticAuditLog {
1369 authentication_key_id: "1".parse()?,
1370 system_user: "metrics-user".parse()?,
1371 },
1372 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1373 )]
1374 #[case::hermetic_audit_log_filter_observer(
1375 YubiHsm2UserMapping::HermeticAuditLog {
1376 authentication_key_id: "1".parse()?,
1377 system_user: "metrics-user".parse()?,
1378 },
1379 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1380 )]
1381 #[case::hermetic_audit_log_filter_signing(
1382 YubiHsm2UserMapping::HermeticAuditLog {
1383 authentication_key_id: "1".parse()?,
1384 system_user: "metrics-user".parse()?,
1385 },
1386 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1387 )]
1388 #[case::signing_filter_admin(
1389 YubiHsm2UserMapping::Signing {
1390 authentication_key_id: "1".parse()?,
1391 signing_key_id: "1".parse()?,
1392 key_setup: SigningKeySetup::new(
1393 KeyType::Curve25519,
1394 vec![KeyMechanism::EdDsaSignature],
1395 None,
1396 SignatureType::EdDsa,
1397 CryptographicKeyContext::OpenPgp {
1398 user_ids: OpenPgpUserIdList::new(vec![
1399 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1400 ])?,
1401 version: "v4".parse()?,
1402 },
1403 )?,
1404 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1405 system_user: "signing-user".parse()?,
1406 domain: Domain::One,
1407 },
1408 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1409 )]
1410 #[case::signing_filter_backup(
1411 YubiHsm2UserMapping::Signing {
1412 authentication_key_id: "1".parse()?,
1413 signing_key_id: "1".parse()?,
1414 key_setup: SigningKeySetup::new(
1415 KeyType::Curve25519,
1416 vec![KeyMechanism::EdDsaSignature],
1417 None,
1418 SignatureType::EdDsa,
1419 CryptographicKeyContext::OpenPgp {
1420 user_ids: OpenPgpUserIdList::new(vec![
1421 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1422 ])?,
1423 version: "v4".parse()?,
1424 },
1425 )?,
1426 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1427 system_user: "signing-user".parse()?,
1428 domain: Domain::One,
1429 },
1430 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1431 )]
1432 #[case::signing_filter_metrics(
1433 YubiHsm2UserMapping::Signing {
1434 authentication_key_id: "1".parse()?,
1435 signing_key_id: "1".parse()?,
1436 key_setup: SigningKeySetup::new(
1437 KeyType::Curve25519,
1438 vec![KeyMechanism::EdDsaSignature],
1439 None,
1440 SignatureType::EdDsa,
1441 CryptographicKeyContext::OpenPgp {
1442 user_ids: OpenPgpUserIdList::new(vec![
1443 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1444 ])?,
1445 version: "v4".parse()?,
1446 },
1447 )?,
1448 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1449 system_user: "signing-user".parse()?,
1450 domain: Domain::One,
1451 },
1452 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1453 )]
1454 #[case::signing_filter_observer(
1455 YubiHsm2UserMapping::Signing {
1456 authentication_key_id: "1".parse()?,
1457 signing_key_id: "1".parse()?,
1458 key_setup: SigningKeySetup::new(
1459 KeyType::Curve25519,
1460 vec![KeyMechanism::EdDsaSignature],
1461 None,
1462 SignatureType::EdDsa,
1463 CryptographicKeyContext::OpenPgp {
1464 user_ids: OpenPgpUserIdList::new(vec![
1465 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1466 ])?,
1467 version: "v4".parse()?,
1468 },
1469 )?,
1470 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1471 system_user: "signing-user".parse()?,
1472 domain: Domain::One,
1473 },
1474 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1475 )]
1476 fn yubihsm2_user_mapping_backend_user_ids_filter_mismatches(
1477 #[case] mapping: YubiHsm2UserMapping,
1478 #[case] filter: BackendUserIdFilter,
1479 ) -> TestResult {
1480 assert!(mapping.backend_user_ids(filter).is_empty());
1481
1482 Ok(())
1483 }
1484
1485 #[test]
1486 fn yubihsm2_user_mapping_backend_user_with_passphrase_succeeds() -> TestResult {
1487 let mapping = YubiHsm2UserMapping::Admin {
1488 authentication_key_id: "1".parse()?,
1489 };
1490 let passphrase = Passphrase::generate(None);
1491 let creds = mapping.backend_user_with_passphrase("1", passphrase.clone())?;
1492
1493 assert_eq!(creds.user(), "1");
1494 assert_eq!(
1495 creds.passphrase().expose_borrowed(),
1496 passphrase.expose_borrowed()
1497 );
1498
1499 Ok(())
1500 }
1501
1502 #[test]
1503 fn yubihsm2_user_mapping_backend_user_with_passphrase_fails() -> TestResult {
1504 let mapping = YubiHsm2UserMapping::Admin {
1505 authentication_key_id: "1".parse()?,
1506 };
1507 assert!(
1508 mapping
1509 .backend_user_with_passphrase("2", Passphrase::generate(None))
1510 .is_err()
1511 );
1512
1513 Ok(())
1514 }
1515
1516 #[rstest]
1517 #[case::admin_filter_admin(
1518 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1519 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1520 )]
1521 #[case::admin_filter_any(
1522 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1523 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1524 )]
1525 #[case::audit_log_filter_metrics(
1526 YubiHsm2UserMapping::AuditLog {
1527 authentication_key_id: "1".parse()?,
1528 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1529 system_user: "metrics-user".parse()?,
1530 },
1531 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1532 )]
1533 #[case::audit_log_filter_any(
1534 YubiHsm2UserMapping::AuditLog {
1535 authentication_key_id: "1".parse()?,
1536 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1537 system_user: "metrics-user".parse()?,
1538 },
1539 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1540 )]
1541 #[case::audit_log_filter_non_admin(
1542 YubiHsm2UserMapping::AuditLog {
1543 authentication_key_id: "1".parse()?,
1544 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1545 system_user: "metrics-user".parse()?,
1546 },
1547 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1548 )]
1549 #[case::backup_filter_backup(
1550 YubiHsm2UserMapping::Backup{
1551 authentication_key_id: "1".parse()?,
1552 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1553 system_user: "backup-user".parse()?,
1554 wrapping_key_id: "1".parse()?,
1555 },
1556 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1557 )]
1558 #[case::backup_filter_any(
1559 YubiHsm2UserMapping::Backup{
1560 authentication_key_id: "1".parse()?,
1561 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1562 system_user: "backup-user".parse()?,
1563 wrapping_key_id: "1".parse()?,
1564 },
1565 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1566 )]
1567 #[case::backup_filter_non_admin(
1568 YubiHsm2UserMapping::Backup{
1569 authentication_key_id: "1".parse()?,
1570 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1571 system_user: "backup-user".parse()?,
1572 wrapping_key_id: "1".parse()?,
1573 },
1574 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1575 )]
1576 #[case::hermetic_audit_log_filter_metrics(
1577 YubiHsm2UserMapping::HermeticAuditLog {
1578 authentication_key_id: "1".parse()?,
1579 system_user: "metrics-user".parse()?,
1580 },
1581 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1582 )]
1583 #[case::hermetic_audit_log_filter_any(
1584 YubiHsm2UserMapping::HermeticAuditLog {
1585 authentication_key_id: "1".parse()?,
1586 system_user: "metrics-user".parse()?,
1587 },
1588 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1589 )]
1590 #[case::hermetic_audit_log_filter_non_admin(
1591 YubiHsm2UserMapping::HermeticAuditLog {
1592 authentication_key_id: "1".parse()?,
1593 system_user: "metrics-user".parse()?,
1594 },
1595 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1596 )]
1597 #[case::signing_filter_signing(
1598 YubiHsm2UserMapping::Signing {
1599 authentication_key_id: "1".parse()?,
1600 signing_key_id: "1".parse()?,
1601 key_setup: SigningKeySetup::new(
1602 KeyType::Curve25519,
1603 vec![KeyMechanism::EdDsaSignature],
1604 None,
1605 SignatureType::EdDsa,
1606 CryptographicKeyContext::OpenPgp {
1607 user_ids: OpenPgpUserIdList::new(vec![
1608 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1609 ])?,
1610 version: "v4".parse()?,
1611 },
1612 )?,
1613 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1614 system_user: "signing-user".parse()?,
1615 domain: Domain::One,
1616 },
1617 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1618 )]
1619 #[case::signing_filter_any(
1620 YubiHsm2UserMapping::Signing {
1621 authentication_key_id: "1".parse()?,
1622 signing_key_id: "1".parse()?,
1623 key_setup: SigningKeySetup::new(
1624 KeyType::Curve25519,
1625 vec![KeyMechanism::EdDsaSignature],
1626 None,
1627 SignatureType::EdDsa,
1628 CryptographicKeyContext::OpenPgp {
1629 user_ids: OpenPgpUserIdList::new(vec![
1630 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1631 ])?,
1632 version: "v4".parse()?,
1633 },
1634 )?,
1635 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1636 system_user: "signing-user".parse()?,
1637 domain: Domain::One,
1638 },
1639 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1640 )]
1641 #[case::signing_filter_non_admin(
1642 YubiHsm2UserMapping::Signing {
1643 authentication_key_id: "1".parse()?,
1644 signing_key_id: "1".parse()?,
1645 key_setup: SigningKeySetup::new(
1646 KeyType::Curve25519,
1647 vec![KeyMechanism::EdDsaSignature],
1648 None,
1649 SignatureType::EdDsa,
1650 CryptographicKeyContext::OpenPgp {
1651 user_ids: OpenPgpUserIdList::new(vec![
1652 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1653 ])?,
1654 version: "v4".parse()?,
1655 },
1656 )?,
1657 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1658 system_user: "signing-user".parse()?,
1659 domain: Domain::One,
1660 },
1661 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1662 )]
1663 fn yubihsm2_user_mapping_backend_users_with_new_passphrase_filter_matches(
1664 #[case] mapping: YubiHsm2UserMapping,
1665 #[case] filter: BackendUserIdFilter,
1666 ) -> TestResult {
1667 let creds = mapping.backend_users_with_new_passphrase(filter);
1668 assert!(creds.first().is_some_and(|creds| creds.user() == "1"));
1669
1670 Ok(())
1671 }
1672
1673 #[rstest]
1674 #[case::admin_filter_non_admin(
1675 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1676 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1677 )]
1678 #[case::admin_filter_backup(
1679 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1680 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1681 )]
1682 #[case::admin_filter_metrics(
1683 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1684 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1685 )]
1686 #[case::admin_filter_observer(
1687 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1688 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1689 )]
1690 #[case::admin_filter_signing(
1691 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1692 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1693 )]
1694 #[case::audit_log_filter_admin(
1695 YubiHsm2UserMapping::AuditLog {
1696 authentication_key_id: "1".parse()?,
1697 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1698 system_user: "metrics-user".parse()?,
1699 },
1700 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1701 )]
1702 #[case::audit_log_filter_backup(
1703 YubiHsm2UserMapping::AuditLog {
1704 authentication_key_id: "1".parse()?,
1705 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1706 system_user: "metrics-user".parse()?,
1707 },
1708 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1709 )]
1710 #[case::audit_log_filter_observer(
1711 YubiHsm2UserMapping::AuditLog {
1712 authentication_key_id: "1".parse()?,
1713 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1714 system_user: "metrics-user".parse()?,
1715 },
1716 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1717 )]
1718 #[case::audit_log_filter_signing(
1719 YubiHsm2UserMapping::AuditLog {
1720 authentication_key_id: "1".parse()?,
1721 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1722 system_user: "metrics-user".parse()?,
1723 },
1724 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1725 )]
1726 #[case::backup_filter_admin(
1727 YubiHsm2UserMapping::Backup{
1728 authentication_key_id: "1".parse()?,
1729 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1730 system_user: "backup-user".parse()?,
1731 wrapping_key_id: "1".parse()?,
1732 },
1733 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1734 )]
1735 #[case::backup_filter_metrics(
1736 YubiHsm2UserMapping::Backup{
1737 authentication_key_id: "1".parse()?,
1738 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1739 system_user: "backup-user".parse()?,
1740 wrapping_key_id: "1".parse()?,
1741 },
1742 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1743 )]
1744 #[case::backup_filter_observer(
1745 YubiHsm2UserMapping::Backup{
1746 authentication_key_id: "1".parse()?,
1747 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1748 system_user: "backup-user".parse()?,
1749 wrapping_key_id: "1".parse()?,
1750 },
1751 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1752 )]
1753 #[case::backup_filter_signing(
1754 YubiHsm2UserMapping::Backup{
1755 authentication_key_id: "1".parse()?,
1756 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1757 system_user: "backup-user".parse()?,
1758 wrapping_key_id: "1".parse()?,
1759 },
1760 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1761 )]
1762 #[case::hermetic_audit_log_filter_admin(
1763 YubiHsm2UserMapping::HermeticAuditLog {
1764 authentication_key_id: "1".parse()?,
1765 system_user: "metrics-user".parse()?,
1766 },
1767 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1768 )]
1769 #[case::hermetic_audit_log_filter_backup(
1770 YubiHsm2UserMapping::HermeticAuditLog {
1771 authentication_key_id: "1".parse()?,
1772 system_user: "metrics-user".parse()?,
1773 },
1774 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1775 )]
1776 #[case::hermetic_audit_log_filter_observer(
1777 YubiHsm2UserMapping::HermeticAuditLog {
1778 authentication_key_id: "1".parse()?,
1779 system_user: "metrics-user".parse()?,
1780 },
1781 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1782 )]
1783 #[case::hermetic_audit_log_filter_signing(
1784 YubiHsm2UserMapping::HermeticAuditLog {
1785 authentication_key_id: "1".parse()?,
1786 system_user: "metrics-user".parse()?,
1787 },
1788 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1789 )]
1790 #[case::signing_filter_admin(
1791 YubiHsm2UserMapping::Signing {
1792 authentication_key_id: "1".parse()?,
1793 signing_key_id: "1".parse()?,
1794 key_setup: SigningKeySetup::new(
1795 KeyType::Curve25519,
1796 vec![KeyMechanism::EdDsaSignature],
1797 None,
1798 SignatureType::EdDsa,
1799 CryptographicKeyContext::OpenPgp {
1800 user_ids: OpenPgpUserIdList::new(vec![
1801 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1802 ])?,
1803 version: "v4".parse()?,
1804 },
1805 )?,
1806 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1807 system_user: "signing-user".parse()?,
1808 domain: Domain::One,
1809 },
1810 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1811 )]
1812 #[case::signing_filter_backup(
1813 YubiHsm2UserMapping::Signing {
1814 authentication_key_id: "1".parse()?,
1815 signing_key_id: "1".parse()?,
1816 key_setup: SigningKeySetup::new(
1817 KeyType::Curve25519,
1818 vec![KeyMechanism::EdDsaSignature],
1819 None,
1820 SignatureType::EdDsa,
1821 CryptographicKeyContext::OpenPgp {
1822 user_ids: OpenPgpUserIdList::new(vec![
1823 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1824 ])?,
1825 version: "v4".parse()?,
1826 },
1827 )?,
1828 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1829 system_user: "signing-user".parse()?,
1830 domain: Domain::One,
1831 },
1832 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1833 )]
1834 #[case::signing_filter_metrics(
1835 YubiHsm2UserMapping::Signing {
1836 authentication_key_id: "1".parse()?,
1837 signing_key_id: "1".parse()?,
1838 key_setup: SigningKeySetup::new(
1839 KeyType::Curve25519,
1840 vec![KeyMechanism::EdDsaSignature],
1841 None,
1842 SignatureType::EdDsa,
1843 CryptographicKeyContext::OpenPgp {
1844 user_ids: OpenPgpUserIdList::new(vec![
1845 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1846 ])?,
1847 version: "v4".parse()?,
1848 },
1849 )?,
1850 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1851 system_user: "signing-user".parse()?,
1852 domain: Domain::One,
1853 },
1854 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1855 )]
1856 #[case::signing_filter_observer(
1857 YubiHsm2UserMapping::Signing {
1858 authentication_key_id: "1".parse()?,
1859 signing_key_id: "1".parse()?,
1860 key_setup: SigningKeySetup::new(
1861 KeyType::Curve25519,
1862 vec![KeyMechanism::EdDsaSignature],
1863 None,
1864 SignatureType::EdDsa,
1865 CryptographicKeyContext::OpenPgp {
1866 user_ids: OpenPgpUserIdList::new(vec![
1867 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1868 ])?,
1869 version: "v4".parse()?,
1870 },
1871 )?,
1872 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1873 system_user: "signing-user".parse()?,
1874 domain: Domain::One,
1875 },
1876 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1877 )]
1878 fn yubihsm2_user_mapping_backend_users_with_new_passphrase_filter_mismatches(
1879 #[case] mapping: YubiHsm2UserMapping,
1880 #[case] filter: BackendUserIdFilter,
1881 ) -> TestResult {
1882 assert!(mapping.backend_users_with_new_passphrase(filter).is_empty());
1883
1884 Ok(())
1885 }
1886
1887 #[rstest]
1888 #[case::backup_filter_wrapping_no_domain(
1889 YubiHsm2UserMapping::Backup{
1890 authentication_key_id: "1".parse()?,
1891 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1892 system_user: "backup-user".parse()?,
1893 wrapping_key_id: "1".parse()?,
1894 },
1895 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: None },
1896 )]
1897 #[case::backup_filter_wrapping_some_domain(
1898 YubiHsm2UserMapping::Backup{
1899 authentication_key_id: "1".parse()?,
1900 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1901 system_user: "backup-user".parse()?,
1902 wrapping_key_id: "1".parse()?,
1903 },
1904 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: Some(Domain::One) },
1905 )]
1906 #[case::signing_filter_signing_matching_domain(
1907 YubiHsm2UserMapping::Signing {
1908 authentication_key_id: "1".parse()?,
1909 signing_key_id: "1".parse()?,
1910 key_setup: SigningKeySetup::new(
1911 KeyType::Curve25519,
1912 vec![KeyMechanism::EdDsaSignature],
1913 None,
1914 SignatureType::EdDsa,
1915 CryptographicKeyContext::OpenPgp {
1916 user_ids: OpenPgpUserIdList::new(vec![
1917 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1918 ])?,
1919 version: "v4".parse()?,
1920 },
1921 )?,
1922 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1923 system_user: "signing-user".parse()?,
1924 domain: Domain::One,
1925 },
1926 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: Some(Domain::One) },
1927 )]
1928 fn yubihsm2_user_mapping_backend_key_id_filter_matches(
1929 #[case] mapping: YubiHsm2UserMapping,
1930 #[case] filter: YubiHsm2BackendKeyIdFilter,
1931 ) -> TestResult {
1932 assert!(mapping.backend_key_id(&filter).is_some_and(|id| id == "1"));
1933
1934 Ok(())
1935 }
1936
1937 #[rstest]
1938 #[case::backup_filter_signing_no_domain(
1939 YubiHsm2UserMapping::Backup{
1940 authentication_key_id: "1".parse()?,
1941 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1942 system_user: "backup-user".parse()?,
1943 wrapping_key_id: "1".parse()?,
1944 },
1945 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: None },
1946 )]
1947 #[case::backup_filter_signing_some_domain(
1948 YubiHsm2UserMapping::Backup{
1949 authentication_key_id: "1".parse()?,
1950 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1951 system_user: "backup-user".parse()?,
1952 wrapping_key_id: "1".parse()?,
1953 },
1954 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: Some(Domain::One) },
1955 )]
1956 #[case::signing_filter_signing_wrong_domain(
1957 YubiHsm2UserMapping::Signing {
1958 authentication_key_id: "1".parse()?,
1959 signing_key_id: "1".parse()?,
1960 key_setup: SigningKeySetup::new(
1961 KeyType::Curve25519,
1962 vec![KeyMechanism::EdDsaSignature],
1963 None,
1964 SignatureType::EdDsa,
1965 CryptographicKeyContext::OpenPgp {
1966 user_ids: OpenPgpUserIdList::new(vec![
1967 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1968 ])?,
1969 version: "v4".parse()?,
1970 },
1971 )?,
1972 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1973 system_user: "signing-user".parse()?,
1974 domain: Domain::One,
1975 },
1976 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: Some(Domain::Two) },
1977 )]
1978 #[case::signing_filter_wrapping_same_domain(
1979 YubiHsm2UserMapping::Signing {
1980 authentication_key_id: "1".parse()?,
1981 signing_key_id: "1".parse()?,
1982 key_setup: SigningKeySetup::new(
1983 KeyType::Curve25519,
1984 vec![KeyMechanism::EdDsaSignature],
1985 None,
1986 SignatureType::EdDsa,
1987 CryptographicKeyContext::OpenPgp {
1988 user_ids: OpenPgpUserIdList::new(vec![
1989 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1990 ])?,
1991 version: "v4".parse()?,
1992 },
1993 )?,
1994 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1995 system_user: "signing-user".parse()?,
1996 domain: Domain::One,
1997 },
1998 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: Some(Domain::One) },
1999 )]
2000 #[case::signing_filter_wrapping_wrong_domain(
2001 YubiHsm2UserMapping::Signing {
2002 authentication_key_id: "1".parse()?,
2003 signing_key_id: "1".parse()?,
2004 key_setup: SigningKeySetup::new(
2005 KeyType::Curve25519,
2006 vec![KeyMechanism::EdDsaSignature],
2007 None,
2008 SignatureType::EdDsa,
2009 CryptographicKeyContext::OpenPgp {
2010 user_ids: OpenPgpUserIdList::new(vec![
2011 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2012 ])?,
2013 version: "v4".parse()?,
2014 },
2015 )?,
2016 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2017 system_user: "signing-user".parse()?,
2018 domain: Domain::One,
2019 },
2020 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: Some(Domain::Two) },
2021 )]
2022 #[case::signing_filter_wrapping_no_domain(
2023 YubiHsm2UserMapping::Signing {
2024 authentication_key_id: "1".parse()?,
2025 signing_key_id: "1".parse()?,
2026 key_setup: SigningKeySetup::new(
2027 KeyType::Curve25519,
2028 vec![KeyMechanism::EdDsaSignature],
2029 None,
2030 SignatureType::EdDsa,
2031 CryptographicKeyContext::OpenPgp {
2032 user_ids: OpenPgpUserIdList::new(vec![
2033 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2034 ])?,
2035 version: "v4".parse()?,
2036 },
2037 )?,
2038 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2039 system_user: "signing-user".parse()?,
2040 domain: Domain::One,
2041 },
2042 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: None },
2043 )]
2044 fn yubihsm2_user_mapping_backend_key_id_filter_mismatches(
2045 #[case] mapping: YubiHsm2UserMapping,
2046 #[case] filter: YubiHsm2BackendKeyIdFilter,
2047 ) -> TestResult {
2048 assert!(mapping.backend_key_id(&filter).is_none());
2049
2050 Ok(())
2051 }
2052
2053 #[fixture]
2054 fn yubihsm2_yubihsm_connections() -> TestResult<[Connection; 2]> {
2055 Ok([
2056 Connection::Usb {
2057 serial_number: "0012345678".parse()?,
2058 },
2059 Connection::Usb {
2060 serial_number: "0087654321".parse()?,
2061 },
2062 ])
2063 }
2064
2065 #[fixture]
2066 fn yubihsm2_mappings() -> TestResult<[YubiHsm2UserMapping; 5]> {
2067 Ok([
2068 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2069 YubiHsm2UserMapping::Backup{
2070 authentication_key_id: "2".parse()?,
2071 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2072 system_user: "backup-user".parse()?,
2073 wrapping_key_id: "1".parse()?,
2074 },
2075 YubiHsm2UserMapping::AuditLog {
2076 authentication_key_id: "3".parse()?,
2077 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2078 system_user: "metrics-user".parse()?,
2079 },
2080 YubiHsm2UserMapping::HermeticAuditLog {
2081 authentication_key_id: "4".parse()?,
2082 system_user: "hermetic-metrics".parse()?,
2083 },
2084 YubiHsm2UserMapping::Signing {
2085 authentication_key_id: "5".parse()?,
2086 signing_key_id: "1".parse()?,
2087 key_setup: SigningKeySetup::new(
2088 KeyType::Curve25519,
2089 vec![KeyMechanism::EdDsaSignature],
2090 None,
2091 SignatureType::EdDsa,
2092 CryptographicKeyContext::OpenPgp {
2093 user_ids: OpenPgpUserIdList::new(vec![
2094 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2095 ])?,
2096 version: "v4".parse()?,
2097 },
2098 )?,
2099 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2100 system_user: "signing-user".parse()?,
2101 domain: Domain::One,
2102 }
2103 ])
2104 }
2105
2106 #[fixture]
2107 fn yubihsm2_config(
2108 yubihsm2_yubihsm_connections: TestResult<[Connection; 2]>,
2109 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2110 ) -> TestResult<YubiHsm2Config> {
2111 let yubihsm2_yubihsm_connections = yubihsm2_yubihsm_connections?;
2112 let yubihsm2_mappings = yubihsm2_mappings?;
2113 let config = YubiHsm2Config::new(
2114 BTreeSet::from_iter(yubihsm2_yubihsm_connections),
2115 BTreeSet::from_iter(yubihsm2_mappings),
2116 )?;
2117
2118 Ok(config)
2119 }
2120
2121 #[rstest]
2122 fn yubihsm2_config_connections(
2123 yubihsm2_yubihsm_connections: TestResult<[Connection; 2]>,
2124 yubihsm2_config: TestResult<YubiHsm2Config>,
2125 ) -> TestResult {
2126 let yubihsm2_config = yubihsm2_config?;
2127 let yubihsm2_yubihsm_connections = yubihsm2_yubihsm_connections?;
2128 let connections = yubihsm2_config.connections();
2129
2130 assert_eq!(connections.len(), 2);
2131 assert!(
2132 connections
2133 .first()
2134 .is_some_and(|connection| connection == &yubihsm2_yubihsm_connections[0]),
2135 );
2136 assert!(
2137 connections
2138 .last()
2139 .is_some_and(|connection| connection == &yubihsm2_yubihsm_connections[1]),
2140 );
2141
2142 Ok(())
2143 }
2144
2145 #[rstest]
2146 fn yubihsm2_config_mappings(
2147 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2148 yubihsm2_config: TestResult<YubiHsm2Config>,
2149 ) -> TestResult {
2150 let yubihsm2_config = yubihsm2_config?;
2151 let yubihsm2_mappings = yubihsm2_mappings?;
2152 let mappings = yubihsm2_config.mappings();
2153
2154 assert_eq!(mappings.len(), 5);
2155 for mapping in yubihsm2_mappings.iter() {
2156 assert!(mappings.contains(mapping));
2157 }
2158
2159 Ok(())
2160 }
2161
2162 #[rstest]
2163 fn yubihsm2_config_authorized_key_entries(
2164 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2165 yubihsm2_config: TestResult<YubiHsm2Config>,
2166 ) -> TestResult {
2167 let yubihsm2_config = yubihsm2_config?;
2168 let authorized_key_entries = yubihsm2_config.authorized_key_entries();
2169
2170 let yubihsm2_mappings = yubihsm2_mappings?;
2171 let initial_entries = yubihsm2_mappings
2172 .iter()
2173 .filter_map(|mapping| mapping.authorized_key_entry())
2174 .collect::<HashSet<_>>();
2175
2176 assert_eq!(initial_entries, authorized_key_entries);
2177
2178 Ok(())
2179 }
2180
2181 #[rstest]
2182 fn yubihsm2_config_system_user_ids(
2183 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2184 yubihsm2_config: TestResult<YubiHsm2Config>,
2185 ) -> TestResult {
2186 let yubihsm2_config = yubihsm2_config?;
2187 let system_user_ids = yubihsm2_config.system_user_ids();
2188
2189 let yubihsm2_mappings = yubihsm2_mappings?;
2190 let initial_entries = yubihsm2_mappings
2191 .iter()
2192 .filter_map(|mapping| mapping.system_user_id())
2193 .collect::<HashSet<_>>();
2194
2195 assert_eq!(initial_entries, system_user_ids);
2196
2197 Ok(())
2198 }
2199
2200 #[rstest]
2201 #[case::no_connection(
2202 "Error message for YubiHsm2Config::new with no connection",
2203 BTreeSet::new(),
2204 BTreeSet::from_iter([
2205 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2206 YubiHsm2UserMapping::Backup{
2207 authentication_key_id: "2".parse()?,
2208 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2209 system_user: "backup-user".parse()?,
2210 wrapping_key_id: "1".parse()?,
2211 },
2212 YubiHsm2UserMapping::AuditLog {
2213 authentication_key_id: "3".parse()?,
2214 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2215 system_user: "metrics-user".parse()?,
2216 },
2217 YubiHsm2UserMapping::Signing {
2218 authentication_key_id: "4".parse()?,
2219 signing_key_id: "1".parse()?,
2220 key_setup: SigningKeySetup::new(
2221 KeyType::Curve25519,
2222 vec![KeyMechanism::EdDsaSignature],
2223 None,
2224 SignatureType::EdDsa,
2225 CryptographicKeyContext::OpenPgp {
2226 user_ids: OpenPgpUserIdList::new(vec![
2227 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2228 ])?,
2229 version: "v4".parse()?,
2230 },
2231 )?,
2232 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2233 system_user: "signing-user".parse()?,
2234 domain: Domain::One,
2235 }
2236 ]),
2237 )]
2238 #[case::no_mappings(
2239 "Error message for YubiHsm2Config::new with no user mappings",
2240 BTreeSet::from_iter([
2241 Connection::Usb {serial_number: "0012345678".parse()? },
2242 Connection::Usb {serial_number: "0087654321".parse()? },
2243 ]),
2244 BTreeSet::new(),
2245 )]
2246 #[case::duplicate_system_user_ids(
2247 "Error message for YubiHsm2Config::new with two duplicate system user IDs",
2248 BTreeSet::from_iter([
2249 Connection::Usb {serial_number: "0012345678".parse()? },
2250 Connection::Usb {serial_number: "0087654321".parse()? },
2251 ]),
2252 BTreeSet::from_iter([
2253 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2254 YubiHsm2UserMapping::Backup{
2255 authentication_key_id: "2".parse()?,
2256 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2257 system_user: "backup-user".parse()?,
2258 wrapping_key_id: "1".parse()?,
2259 },
2260 YubiHsm2UserMapping::AuditLog {
2261 authentication_key_id: "3".parse()?,
2262 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2263 system_user: "backup-user".parse()?,
2264 },
2265 YubiHsm2UserMapping::Signing {
2266 authentication_key_id: "4".parse()?,
2267 signing_key_id: "1".parse()?,
2268 key_setup: SigningKeySetup::new(
2269 KeyType::Curve25519,
2270 vec![KeyMechanism::EdDsaSignature],
2271 None,
2272 SignatureType::EdDsa,
2273 CryptographicKeyContext::OpenPgp {
2274 user_ids: OpenPgpUserIdList::new(vec![
2275 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2276 ])?,
2277 version: "v4".parse()?,
2278 },
2279 )?,
2280 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2281 system_user: "signing-user".parse()?,
2282 domain: Domain::One,
2283 }
2284 ]),
2285 )]
2286 #[case::duplicate_ssh_public_keys(
2287 "Error message for YubiHsm2Config::new with two duplicate SSH public keys as authorized keys",
2288 BTreeSet::from_iter([
2289 Connection::Usb {serial_number: "0012345678".parse()? },
2290 Connection::Usb {serial_number: "0087654321".parse()? },
2291 ]),
2292 BTreeSet::from_iter([
2293 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2294 YubiHsm2UserMapping::Backup{
2295 authentication_key_id: "2".parse()?,
2296 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2297 system_user: "backup-user".parse()?,
2298 wrapping_key_id: "1".parse()?,
2299 },
2300 YubiHsm2UserMapping::AuditLog {
2301 authentication_key_id: "3".parse()?,
2302 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2303 system_user: "metrics-user".parse()?,
2304 },
2305 YubiHsm2UserMapping::Signing {
2306 authentication_key_id: "4".parse()?,
2307 signing_key_id: "1".parse()?,
2308 key_setup: SigningKeySetup::new(
2309 KeyType::Curve25519,
2310 vec![KeyMechanism::EdDsaSignature],
2311 None,
2312 SignatureType::EdDsa,
2313 CryptographicKeyContext::OpenPgp {
2314 user_ids: OpenPgpUserIdList::new(vec![
2315 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2316 ])?,
2317 version: "v4".parse()?,
2318 },
2319 )?,
2320 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2321 system_user: "signing-user".parse()?,
2322 domain: Domain::One,
2323 }
2324 ]),
2325 )]
2326 #[case::no_administrator(
2327 "Error message for YubiHsm2Config::new with no administrator",
2328 BTreeSet::from_iter([
2329 Connection::Usb {serial_number: "0012345678".parse()? },
2330 Connection::Usb {serial_number: "0087654321".parse()? },
2331 ]),
2332 BTreeSet::from_iter([
2333 YubiHsm2UserMapping::Backup{
2334 authentication_key_id: "2".parse()?,
2335 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2336 system_user: "backup-user".parse()?,
2337 wrapping_key_id: "1".parse()?,
2338 },
2339 YubiHsm2UserMapping::AuditLog {
2340 authentication_key_id: "3".parse()?,
2341 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2342 system_user: "metrics-user".parse()?,
2343 },
2344 YubiHsm2UserMapping::Signing {
2345 authentication_key_id: "4".parse()?,
2346 signing_key_id: "1".parse()?,
2347 key_setup: SigningKeySetup::new(
2348 KeyType::Curve25519,
2349 vec![KeyMechanism::EdDsaSignature],
2350 None,
2351 SignatureType::EdDsa,
2352 CryptographicKeyContext::OpenPgp {
2353 user_ids: OpenPgpUserIdList::new(vec![
2354 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2355 ])?,
2356 version: "v4".parse()?,
2357 },
2358 )?,
2359 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2360 system_user: "signing-user".parse()?,
2361 domain: Domain::One,
2362 }
2363 ]),
2364 )]
2365 #[case::duplicate_backend_user_ids(
2366 "Error message for YubiHsm2Config::new with two duplicate backend user IDs",
2367 BTreeSet::from_iter([
2368 Connection::Usb {serial_number: "0012345678".parse()? },
2369 Connection::Usb {serial_number: "0087654321".parse()? },
2370 ]),
2371 BTreeSet::from_iter([
2372 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2373 YubiHsm2UserMapping::Backup{
2374 authentication_key_id: "2".parse()?,
2375 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2376 system_user: "backup-user".parse()?,
2377 wrapping_key_id: "1".parse()?,
2378 },
2379 YubiHsm2UserMapping::AuditLog {
2380 authentication_key_id: "3".parse()?,
2381 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2382 system_user: "metrics-user".parse()?,
2383 },
2384 YubiHsm2UserMapping::Signing {
2385 authentication_key_id: "3".parse()?,
2386 signing_key_id: "1".parse()?,
2387 key_setup: SigningKeySetup::new(
2388 KeyType::Curve25519,
2389 vec![KeyMechanism::EdDsaSignature],
2390 None,
2391 SignatureType::EdDsa,
2392 CryptographicKeyContext::OpenPgp {
2393 user_ids: OpenPgpUserIdList::new(vec![
2394 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2395 ])?,
2396 version: "v4".parse()?,
2397 },
2398 )?,
2399 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2400 system_user: "signing-user".parse()?,
2401 domain: Domain::One,
2402 }
2403 ]),
2404 )]
2405 #[case::duplicate_signing_key_ids(
2406 "Error message for YubiHsm2Config::new with two duplicate signing key IDs",
2407 BTreeSet::from_iter([
2408 Connection::Usb {serial_number: "0012345678".parse()? },
2409 Connection::Usb {serial_number: "0087654321".parse()? },
2410 ]),
2411 BTreeSet::from_iter([
2412 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2413 YubiHsm2UserMapping::Backup{
2414 authentication_key_id: "2".parse()?,
2415 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2416 system_user: "backup-user".parse()?,
2417 wrapping_key_id: "1".parse()?,
2418 },
2419 YubiHsm2UserMapping::AuditLog {
2420 authentication_key_id: "3".parse()?,
2421 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2422 system_user: "metrics-user".parse()?,
2423 },
2424 YubiHsm2UserMapping::Signing {
2425 authentication_key_id: "4".parse()?,
2426 signing_key_id: "1".parse()?,
2427 key_setup: SigningKeySetup::new(
2428 KeyType::Curve25519,
2429 vec![KeyMechanism::EdDsaSignature],
2430 None,
2431 SignatureType::EdDsa,
2432 CryptographicKeyContext::OpenPgp {
2433 user_ids: OpenPgpUserIdList::new(vec![
2434 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2435 ])?,
2436 version: "v4".parse()?,
2437 },
2438 )?,
2439 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2440 system_user: "signing-user".parse()?,
2441 domain: Domain::One,
2442 },
2443 YubiHsm2UserMapping::Signing {
2444 authentication_key_id: "5".parse()?,
2445 signing_key_id: "1".parse()?,
2446 key_setup: SigningKeySetup::new(
2447 KeyType::Curve25519,
2448 vec![KeyMechanism::EdDsaSignature],
2449 None,
2450 SignatureType::EdDsa,
2451 CryptographicKeyContext::OpenPgp {
2452 user_ids: OpenPgpUserIdList::new(vec![
2453 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2454 ])?,
2455 version: "v4".parse()?,
2456 },
2457 )?,
2458 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2459 system_user: "signing-user2".parse()?,
2460 domain: Domain::Two,
2461 },
2462 ]),
2463 )]
2464 #[case::duplicate_wrapping_key_ids(
2465 "Error message for YubiHsm2Config::new with two duplicate wrapping key IDs",
2466 BTreeSet::from_iter([
2467 Connection::Usb {serial_number: "0012345678".parse()? },
2468 Connection::Usb {serial_number: "0087654321".parse()? },
2469 ]),
2470 BTreeSet::from_iter([
2471 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2472 YubiHsm2UserMapping::Backup{
2473 authentication_key_id: "2".parse()?,
2474 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2475 system_user: "backup-user".parse()?,
2476 wrapping_key_id: "1".parse()?,
2477 },
2478 YubiHsm2UserMapping::Backup{
2479 authentication_key_id: "3".parse()?,
2480 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2481 system_user: "backup-user2".parse()?,
2482 wrapping_key_id: "1".parse()?,
2483 },
2484 YubiHsm2UserMapping::AuditLog {
2485 authentication_key_id: "4".parse()?,
2486 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2487 system_user: "metrics-user".parse()?,
2488 },
2489 YubiHsm2UserMapping::Signing {
2490 authentication_key_id: "5".parse()?,
2491 signing_key_id: "1".parse()?,
2492 key_setup: SigningKeySetup::new(
2493 KeyType::Curve25519,
2494 vec![KeyMechanism::EdDsaSignature],
2495 None,
2496 SignatureType::EdDsa,
2497 CryptographicKeyContext::OpenPgp {
2498 user_ids: OpenPgpUserIdList::new(vec![
2499 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2500 ])?,
2501 version: "v4".parse()?,
2502 },
2503 )?,
2504 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2505 system_user: "signing-user".parse()?,
2506 domain: Domain::One,
2507 },
2508 ]),
2509 )]
2510 #[case::duplicate_domains(
2511 "Error message for YubiHsm2Config::new with two duplicate domains",
2512 BTreeSet::from_iter([
2513 Connection::Usb {serial_number: "0012345678".parse()? },
2514 Connection::Usb {serial_number: "0087654321".parse()? },
2515 ]),
2516 BTreeSet::from_iter([
2517 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2518 YubiHsm2UserMapping::Backup{
2519 authentication_key_id: "2".parse()?,
2520 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2521 system_user: "backup-user".parse()?,
2522 wrapping_key_id: "1".parse()?,
2523 },
2524 YubiHsm2UserMapping::AuditLog {
2525 authentication_key_id: "3".parse()?,
2526 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2527 system_user: "metrics-user".parse()?,
2528 },
2529 YubiHsm2UserMapping::Signing {
2530 authentication_key_id: "4".parse()?,
2531 signing_key_id: "1".parse()?,
2532 key_setup: SigningKeySetup::new(
2533 KeyType::Curve25519,
2534 vec![KeyMechanism::EdDsaSignature],
2535 None,
2536 SignatureType::EdDsa,
2537 CryptographicKeyContext::OpenPgp {
2538 user_ids: OpenPgpUserIdList::new(vec![
2539 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2540 ])?,
2541 version: "v4".parse()?,
2542 },
2543 )?,
2544 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2545 system_user: "signing-user".parse()?,
2546 domain: Domain::One,
2547 },
2548 YubiHsm2UserMapping::Signing {
2549 authentication_key_id: "5".parse()?,
2550 signing_key_id: "2".parse()?,
2551 key_setup: SigningKeySetup::new(
2552 KeyType::Curve25519,
2553 vec![KeyMechanism::EdDsaSignature],
2554 None,
2555 SignatureType::EdDsa,
2556 CryptographicKeyContext::OpenPgp {
2557 user_ids: OpenPgpUserIdList::new(vec![
2558 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2559 ])?,
2560 version: "v4".parse()?,
2561 },
2562 )?,
2563 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2564 system_user: "signing-user2".parse()?,
2565 domain: Domain::One,
2566 },
2567 ]),
2568 )]
2569 #[case::all_the_issues(
2570 "Error message for YubiHsm2Config::new with multiple validation issues (connections and mappings)",
2571 BTreeSet::new(),
2572 BTreeSet::from_iter([
2573 YubiHsm2UserMapping::Backup{
2574 authentication_key_id: "2".parse()?,
2575 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2576 system_user: "backup-user".parse()?,
2577 wrapping_key_id: "1".parse()?,
2578 },
2579 YubiHsm2UserMapping::Backup{
2580 authentication_key_id: "3".parse()?,
2581 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2582 system_user: "backup-user".parse()?,
2583 wrapping_key_id: "1".parse()?,
2584 },
2585 YubiHsm2UserMapping::AuditLog {
2586 authentication_key_id: "3".parse()?,
2587 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2588 system_user: "metrics-backupuser".parse()?,
2589 },
2590 YubiHsm2UserMapping::Signing {
2591 authentication_key_id: "5".parse()?,
2592 signing_key_id: "1".parse()?,
2593 key_setup: SigningKeySetup::new(
2594 KeyType::Curve25519,
2595 vec![KeyMechanism::EdDsaSignature],
2596 None,
2597 SignatureType::EdDsa,
2598 CryptographicKeyContext::OpenPgp {
2599 user_ids: OpenPgpUserIdList::new(vec![
2600 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2601 ])?,
2602 version: "v4".parse()?,
2603 },
2604 )?,
2605 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2606 system_user: "signing-user".parse()?,
2607 domain: Domain::One,
2608 },
2609 YubiHsm2UserMapping::Signing {
2610 authentication_key_id: "5".parse()?,
2611 signing_key_id: "1".parse()?,
2612 key_setup: SigningKeySetup::new(
2613 KeyType::Curve25519,
2614 vec![KeyMechanism::EdDsaSignature],
2615 None,
2616 SignatureType::EdDsa,
2617 CryptographicKeyContext::OpenPgp {
2618 user_ids: OpenPgpUserIdList::new(vec![
2619 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2620 ])?,
2621 version: "v4".parse()?,
2622 },
2623 )?,
2624 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2625 system_user: "signing-user2".parse()?,
2626 domain: Domain::One,
2627 },
2628 ]),
2629 )]
2630 fn yubihsm2_config_new_fails_validation(
2631 #[case] description: &str,
2632 #[case] connections: BTreeSet<Connection>,
2633 #[case] mappings: BTreeSet<YubiHsm2UserMapping>,
2634 ) -> TestResult {
2635 let error_msg = match YubiHsm2Config::new(connections, mappings) {
2636 Err(crate::Error::Validation { source, .. }) => source.to_string(),
2637 Ok(config) => {
2638 panic!("Expected to fail with Error::Validation, but succeeded instead: {config:?}")
2639 }
2640 Err(error) => panic!(
2641 "Expected to fail with Error::Validation, but failed with a different error instead: {error}"
2642 ),
2643 };
2644
2645 with_settings!({
2646 description => description,
2647 snapshot_path => SNAPSHOT_PATH,
2648 prepend_module_to_snapshot => false,
2649 }, {
2650 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_msg);
2651 });
2652 Ok(())
2653 }
2654}