1use std::collections::{BTreeSet, HashSet};
3
4use garde::Validate;
5use serde::{Deserialize, Serialize};
6use signstar_crypto::{key::SigningKeySetup, passphrase::Passphrase, traits::UserWithPassphrase};
7use signstar_yubihsm2::{
8 Connection,
9 Credentials,
10 automation::OpaqueData,
11 backup::Label,
12 object::{Capabilities, Capability, Domain, Domains, KeyInfo},
13 yubihsm::{Code, Id},
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 {
113 authentication_key_id: Id,
115
116 ssh_authorized_key: AuthorizedKeyEntry,
118
119 system_user: SystemUserId,
121 },
122
123 Backup {
152 authentication_key_id: Id,
158
159 ssh_authorized_key: AuthorizedKeyEntry,
161
162 system_user: SystemUserId,
164 },
165
166 CertificateRetrieval {
189 authentication_key_id: Id,
191
192 ssh_authorized_key: AuthorizedKeyEntry,
194
195 system_user: SystemUserId,
197 },
198
199 HermeticAuditLog {
222 authentication_key_id: Id,
224
225 system_user: SystemUserId,
227 },
228
229 Signing {
260 authentication_key_id: Id,
262
263 key_setup: SigningKeySetup,
265
266 domain: Domain,
270
271 signing_key_id: Id,
273
274 ssh_authorized_key: AuthorizedKeyEntry,
276
277 system_user: SystemUserId,
279 },
280}
281
282impl YubiHsm2UserMapping {
283 pub const CAP_ADMIN: &[Capability] = &[
317 Capability::ChangeAuthenticationKey,
318 Capability::DeleteAsymmetricKey,
319 Capability::DeleteAuthenticationKey,
320 Capability::DeleteHmacKey,
321 Capability::DeleteOpaque,
322 Capability::DeleteTemplate,
323 Capability::DeleteWrapKey,
324 Capability::ExportableUnderWrap,
325 Capability::ExportWrapped,
326 Capability::GenerateAsymmetricKey,
327 Capability::GenerateHmacKey,
328 Capability::GenerateWrapKey,
329 Capability::GetLogEntries,
330 Capability::GetOpaque,
331 Capability::GetOption,
332 Capability::GetTemplate,
333 Capability::ImportWrapped,
334 Capability::PutAsymmetricKey,
335 Capability::PutAuthenticationKey,
336 Capability::PutHmacKey,
337 Capability::PutOpaque,
338 Capability::SetOption,
339 Capability::PutTemplate,
340 Capability::PutWrapKey,
341 Capability::ResetDevice,
342 Capability::SignHmac,
343 Capability::SignEddsa,
344 Capability::UnwrapData,
345 Capability::VerifyHmac,
346 Capability::WrapData,
347 ];
348
349 pub const CAP_AUDIT_LOG: &[Capability] = &[Capability::GetLogEntries];
357
358 pub const CAP_BACKUP: &[Capability] = &[Capability::ExportWrapped];
366
367 pub const CAP_CERTIFICATE_RETRIEVAL: &[Capability] = &[Capability::GetOpaque];
375
376 pub const CAP_HERMETIC_AUDIT_LOG: &[Capability] = &[Capability::GetLogEntries];
384
385 pub const CAP_SIGNING: &[Capability] = &[Capability::GetOpaque, Capability::SignEddsa];
394
395 pub fn domains(&self) -> Domains {
397 match self {
398 Self::Admin { .. }
399 | Self::Backup { .. }
400 | Self::CertificateRetrieval { .. }
401 | Self::AuditLog { .. }
402 | Self::HermeticAuditLog { .. } => Domains::all(),
403 Self::Signing {
404 domain: key_domain, ..
405 } => Domains::from(*key_domain),
406 }
407 }
408
409 pub fn backend_user_id(&self) -> Id {
411 match self {
412 Self::Admin {
413 authentication_key_id,
414 }
415 | Self::AuditLog {
416 authentication_key_id,
417 ..
418 }
419 | Self::Backup {
420 authentication_key_id,
421 ..
422 }
423 | Self::CertificateRetrieval {
424 authentication_key_id,
425 ..
426 }
427 | Self::HermeticAuditLog {
428 authentication_key_id,
429 ..
430 }
431 | Self::Signing {
432 authentication_key_id,
433 ..
434 } => *authentication_key_id,
435 }
436 }
437
438 pub fn capabilities(&self) -> Capabilities {
445 Capabilities::from(match self {
446 Self::Admin { .. } => Self::CAP_ADMIN,
447 Self::AuditLog { .. } => Self::CAP_AUDIT_LOG,
448 Self::Backup { .. } => Self::CAP_BACKUP,
449 Self::CertificateRetrieval { .. } => Self::CAP_CERTIFICATE_RETRIEVAL,
450 Self::HermeticAuditLog { .. } => Self::CAP_HERMETIC_AUDIT_LOG,
451 Self::Signing { .. } => Self::CAP_SIGNING,
452 })
453 }
454
455 pub fn label(&self) -> Label {
457 Label::from_truncated_str(match self {
458 Self::Admin { .. } => "admin",
459 Self::AuditLog { .. } => "audit log",
460 Self::Backup { .. } => "backup",
461 Self::CertificateRetrieval { .. } => "certificate retrieval",
462 Self::HermeticAuditLog { .. } => "hermetic audit log",
463 Self::Signing { .. } => "signing",
464 })
465 }
466
467 pub fn authentication_key_info(&self) -> KeyInfo {
469 KeyInfo {
470 key_id: self.backend_user_id(),
471 domains: self.domains(),
472 caps: self.capabilities(),
473 label: self.label(),
474 }
475 }
476}
477
478impl MappingSystemUserId for YubiHsm2UserMapping {
479 fn system_user_id(&self) -> Option<&SystemUserId> {
480 match self {
481 Self::Admin { .. } => None,
482 Self::AuditLog { system_user, .. }
483 | Self::Backup { system_user, .. }
484 | Self::CertificateRetrieval { system_user, .. }
485 | Self::HermeticAuditLog { system_user, .. }
486 | Self::Signing { system_user, .. } => Some(system_user),
487 }
488 }
489}
490
491impl MappingBackendUserIds for YubiHsm2UserMapping {
492 fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
493 match self {
494 Self::Admin {
495 authentication_key_id,
496 } => {
497 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
498 .contains(&filter.backend_user_id_kind)
499 {
500 Some(vec![authentication_key_id.to_string()])
501 } else {
502 None
503 }
504 }
505 Self::AuditLog {
506 authentication_key_id,
507 ..
508 } => {
509 if [
510 BackendUserIdKind::Any,
511 BackendUserIdKind::Metrics,
512 BackendUserIdKind::NonAdmin,
513 ]
514 .contains(&filter.backend_user_id_kind)
515 {
516 Some(vec![authentication_key_id.to_string()])
517 } else {
518 None
519 }
520 }
521 Self::Backup {
522 authentication_key_id,
523 ..
524 } => {
525 if [
526 BackendUserIdKind::Any,
527 BackendUserIdKind::Backup,
528 BackendUserIdKind::NonAdmin,
529 ]
530 .contains(&filter.backend_user_id_kind)
531 {
532 Some(vec![authentication_key_id.to_string()])
533 } else {
534 None
535 }
536 }
537 Self::CertificateRetrieval {
538 authentication_key_id,
539 ..
540 } => {
541 if [
542 BackendUserIdKind::Any,
543 BackendUserIdKind::NonAdmin,
544 BackendUserIdKind::Observer,
545 ]
546 .contains(&filter.backend_user_id_kind)
547 {
548 Some(vec![authentication_key_id.to_string()])
549 } else {
550 None
551 }
552 }
553 Self::HermeticAuditLog {
554 authentication_key_id,
555 ..
556 } => {
557 if [
558 BackendUserIdKind::Any,
559 BackendUserIdKind::Metrics,
560 BackendUserIdKind::NonAdmin,
561 ]
562 .contains(&filter.backend_user_id_kind)
563 {
564 Some(vec![authentication_key_id.to_string()])
565 } else {
566 None
567 }
568 }
569 Self::Signing {
570 authentication_key_id,
571 ..
572 } => {
573 if [
574 BackendUserIdKind::Any,
575 BackendUserIdKind::NonAdmin,
576 BackendUserIdKind::Signing,
577 ]
578 .contains(&filter.backend_user_id_kind)
579 {
580 Some(vec![authentication_key_id.to_string()])
581 } else {
582 None
583 }
584 }
585 }
586 .unwrap_or_default()
587 }
588
589 fn backend_user_with_passphrase(
590 &self,
591 name: &str,
592 passphrase: Passphrase,
593 ) -> Result<Box<dyn UserWithPassphrase>, crate::Error> {
594 let backend_user_id = self.backend_user_id();
595 if backend_user_id.to_string() != name {
596 return Err(Error::AuthenticationKeyIdMismatch {
597 expected: name.to_string(),
598 actual: backend_user_id.to_string(),
599 }
600 .into());
601 }
602
603 Ok(Box::new(Credentials::new(backend_user_id, passphrase)))
604 }
605
606 fn backend_users_with_new_passphrase(
607 &self,
608 filter: BackendUserIdFilter,
609 ) -> Vec<Box<dyn UserWithPassphrase>> {
610 if let Some(authentication_key_id) = match self {
611 Self::Admin {
612 authentication_key_id,
613 } => {
614 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
615 .contains(&filter.backend_user_id_kind)
616 {
617 Some(authentication_key_id)
618 } else {
619 None
620 }
621 }
622 Self::AuditLog {
623 authentication_key_id,
624 ..
625 } => {
626 if [
627 BackendUserIdKind::Any,
628 BackendUserIdKind::Metrics,
629 BackendUserIdKind::NonAdmin,
630 ]
631 .contains(&filter.backend_user_id_kind)
632 {
633 Some(authentication_key_id)
634 } else {
635 None
636 }
637 }
638 Self::Backup {
639 authentication_key_id,
640 ..
641 } => {
642 if [
643 BackendUserIdKind::Any,
644 BackendUserIdKind::Backup,
645 BackendUserIdKind::NonAdmin,
646 ]
647 .contains(&filter.backend_user_id_kind)
648 {
649 Some(authentication_key_id)
650 } else {
651 None
652 }
653 }
654 Self::CertificateRetrieval {
655 authentication_key_id,
656 ..
657 } => {
658 if [
659 BackendUserIdKind::Any,
660 BackendUserIdKind::NonAdmin,
661 BackendUserIdKind::Observer,
662 ]
663 .contains(&filter.backend_user_id_kind)
664 {
665 Some(authentication_key_id)
666 } else {
667 None
668 }
669 }
670 Self::HermeticAuditLog {
671 authentication_key_id,
672 ..
673 } => {
674 if [
675 BackendUserIdKind::Any,
676 BackendUserIdKind::Metrics,
677 BackendUserIdKind::NonAdmin,
678 ]
679 .contains(&filter.backend_user_id_kind)
680 {
681 Some(authentication_key_id)
682 } else {
683 None
684 }
685 }
686 Self::Signing {
687 authentication_key_id,
688 ..
689 } => {
690 if [
691 BackendUserIdKind::Any,
692 BackendUserIdKind::NonAdmin,
693 BackendUserIdKind::Signing,
694 ]
695 .contains(&filter.backend_user_id_kind)
696 {
697 Some(authentication_key_id)
698 } else {
699 None
700 }
701 }
702 } {
703 vec![Box::new(Credentials::new(
704 *authentication_key_id,
705 Passphrase::generate(None),
706 ))]
707 } else {
708 Vec::new()
709 }
710 }
711}
712
713impl MappingAuthorizedKeyEntry for YubiHsm2UserMapping {
714 fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
715 match self {
716 Self::Admin { .. } | Self::HermeticAuditLog { .. } => None,
717 Self::AuditLog {
718 ssh_authorized_key, ..
719 }
720 | Self::Backup {
721 ssh_authorized_key, ..
722 }
723 | Self::CertificateRetrieval {
724 ssh_authorized_key, ..
725 }
726 | Self::Signing {
727 ssh_authorized_key, ..
728 } => Some(ssh_authorized_key),
729 }
730 }
731}
732
733impl<'a> From<&'a YubiHsm2UserMapping> for SystemUserData<'a> {
734 fn from(value: &'a YubiHsm2UserMapping) -> Self {
735 match value {
736 YubiHsm2UserMapping::Admin { .. } => Self::BackendAdmin {
737 system_user: SystemUserId::root(),
738 },
739 YubiHsm2UserMapping::AuditLog {
740 ssh_authorized_key,
741 system_user,
742 ..
743 } => Self::BackendMetrics {
744 system_user,
745 ssh_authorized_key,
746 },
747 YubiHsm2UserMapping::Backup {
748 ssh_authorized_key,
749 system_user,
750 ..
751 } => Self::BackendBackup {
752 system_user,
753 ssh_authorized_key,
754 },
755 YubiHsm2UserMapping::CertificateRetrieval {
756 ssh_authorized_key,
757 system_user,
758 ..
759 } => Self::BackendCertificateRetrieval {
760 system_user,
761 ssh_authorized_key,
762 },
763 YubiHsm2UserMapping::HermeticAuditLog { system_user, .. } => {
764 Self::BackendHermeticMetrics { system_user }
765 }
766 YubiHsm2UserMapping::Signing {
767 ssh_authorized_key,
768 system_user,
769 ..
770 } => Self::BackendSign {
771 system_user,
772 ssh_authorized_key,
773 },
774 }
775 }
776}
777
778#[derive(Clone, Copy, Debug)]
780pub struct YubiHsm2DomainFilter {}
781
782impl BackendDomainFilter for YubiHsm2DomainFilter {}
783
784impl MappingBackendDomain<YubiHsm2DomainFilter> for YubiHsm2UserMapping {
785 fn backend_domain(&self, _filter: Option<&YubiHsm2DomainFilter>) -> Option<String> {
786 Some(self.domains().bits().to_string())
787 }
788}
789
790#[derive(Clone, Copy, Debug, Eq, PartialEq)]
798pub enum KeyObjectType {
799 Signing,
803
804 Wrapping,
808}
809
810#[derive(Clone, Debug)]
812pub struct YubiHsm2BackendKeyIdFilter {
813 pub key_type: KeyObjectType,
817
818 pub key_domain: Option<Domain>,
822}
823
824impl BackendKeyIdFilter for YubiHsm2BackendKeyIdFilter {}
825
826impl MappingBackendKeyId<YubiHsm2BackendKeyIdFilter> for YubiHsm2UserMapping {
827 fn backend_key_id(&self, filter: &YubiHsm2BackendKeyIdFilter) -> Option<String> {
828 match self {
829 Self::Admin { .. }
830 | Self::AuditLog { .. }
831 | Self::CertificateRetrieval { .. }
832 | Self::HermeticAuditLog { .. } => None,
833 Self::Backup { .. } => {
834 if filter.key_type == KeyObjectType::Wrapping {
835 Some(YubiHsm2Config::WRAP_KEY_ID.to_string())
837 } else {
838 None
839 }
840 }
841 Self::Signing {
842 signing_key_id,
843 domain: key_domain,
844 ..
845 } => {
846 if filter.key_type == KeyObjectType::Signing {
847 if let Some(filter_key_domain) = filter.key_domain {
848 if &filter_key_domain == key_domain {
849 Some(signing_key_id.to_string())
850 } else {
851 None
852 }
853 } else {
854 Some(signing_key_id.to_string())
855 }
856 } else {
857 None
858 }
859 }
860 }
861 }
862}
863
864impl MappingBackendUserSecrets for YubiHsm2UserMapping {}
865
866fn validate_yubihsm2_config_connections(
874 value: &BTreeSet<Connection>,
875 _context: &(),
876) -> garde::Result {
877 if value.is_empty() {
878 return Err(garde::Error::new("contains no connections".to_string()));
879 }
880
881 Ok(())
882}
883
884fn validate_yubihsm2_config_mappings(
911 value: &BTreeSet<YubiHsm2UserMapping>,
912 _context: &(),
913) -> garde::Result {
914 if value.is_empty() {
915 return Err(garde::Error::new("contains no user mappings".to_string()));
916 }
917
918 let duplicate_system_user_ids = duplicate_system_user_ids(value);
920
921 let duplicate_authorized_keys = duplicate_authorized_keys(value);
923
924 let missing_admin = {
926 let num_system_admins = value
927 .iter()
928 .filter_map(|mapping| {
929 if let YubiHsm2UserMapping::Admin {
930 authentication_key_id,
931 } = mapping
932 {
933 Some(authentication_key_id)
934 } else {
935 None
936 }
937 })
938 .count();
939
940 if num_system_admins == 0 {
941 Some("no administrator user".to_string())
942 } else {
943 None
944 }
945 };
946
947 let size_estimations = value.iter().filter_map(|mapping| {
948 if let YubiHsm2UserMapping::Signing { key_setup, .. } = mapping {
949 match key_setup.key_context().openpgp_cert_size() {
950 Err(source) =>
951 Some(format!(
952 "dummy certificate creation for size estimation failed because of: {source:?}"
953 )),
954 Ok(Some(cert_size)) if cert_size > OpaqueData::MAX_DATA_SIZE => Some(format!("estimated certificate size {cert_size} exceeds the storage limit of {max_size}", max_size = OpaqueData::MAX_DATA_SIZE)),
955 _ => None
956 }
957 } else {
958 None
959 }
960 }).fold(None, |error, item| {
961 if let Some(error) = error {
962 Some(error + item.as_ref())
963 } else {
964 Some(item)
965 }
966 });
967
968 let duplicate_backend_user_ids = duplicate_backend_user_ids(value);
970
971 let duplicate_signing_key_ids = duplicate_key_ids(
973 value,
974 &YubiHsm2BackendKeyIdFilter {
975 key_type: KeyObjectType::Signing,
976 key_domain: None,
977 },
978 Some(" signing".to_string()),
979 );
980
981 let duplicate_domains = duplicate_domains(
986 &value
987 .iter()
988 .filter(|mapping| matches!(mapping, YubiHsm2UserMapping::Signing { .. }))
989 .collect::<BTreeSet<_>>(),
990 None,
991 None,
992 None,
993 );
994
995 let messages = [
996 duplicate_system_user_ids,
997 duplicate_authorized_keys,
998 missing_admin,
999 duplicate_backend_user_ids,
1000 duplicate_signing_key_ids,
1001 duplicate_domains,
1002 size_estimations,
1003 ];
1004 let error_messages = {
1005 let mut error_messages = Vec::new();
1006
1007 for message in messages.iter().flatten() {
1008 error_messages.push(message.as_str());
1009 }
1010
1011 error_messages
1012 };
1013
1014 match error_messages.len() {
1015 0 => Ok(()),
1016 1 => Err(garde::Error::new(format!(
1017 "contains {}",
1018 error_messages.join("\n")
1019 ))),
1020 _ => Err(garde::Error::new(format!(
1021 "contains multiple issues:\n⤷ {}",
1022 error_messages.join("\n⤷ ")
1023 ))),
1024 }
1025}
1026
1027#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
1032#[serde(rename_all = "snake_case")]
1033pub struct YubiHsm2Config {
1034 #[garde(custom(validate_yubihsm2_config_connections))]
1036 connections: BTreeSet<Connection>,
1037
1038 #[garde(custom(validate_yubihsm2_config_mappings))]
1040 mappings: BTreeSet<YubiHsm2UserMapping>,
1041}
1042
1043impl YubiHsm2Config {
1044 pub const AUDIT_COMMANDS: &[Code] = &[
1048 Code::AuthenticateSession,
1049 Code::ChangeAuthenticationKey,
1050 Code::CloseSession,
1051 Code::CreateSession,
1052 Code::DeleteObject,
1053 Code::ExportWrapped,
1054 Code::GetObjectInfo,
1055 Code::GetLogEntries,
1056 Code::GetOpaqueObject,
1057 Code::GetOption,
1058 Code::GetPublicKey,
1059 Code::GetStorageInfo,
1060 Code::HsmInitialization,
1061 Code::ImportWrapped,
1062 Code::PutOpaqueObject,
1063 Code::PutWrapKey,
1064 Code::ResetDevice,
1065 Code::SetOption,
1066 Code::SignAttestationCertificate,
1067 Code::SignEddsa,
1068 ];
1069
1070 pub const WRAP_KEY_ID: Id = 1;
1074
1075 pub const WRAP_KEY_LABEL: &str = "wrap key";
1077
1078 pub const OPENPGP_CERTIFICATE_LABEL: &str = "OpenPGP certificate";
1080
1081 pub fn new(
1084 connections: BTreeSet<Connection>,
1085 mappings: BTreeSet<YubiHsm2UserMapping>,
1086 ) -> Result<Self, crate::Error> {
1087 let config = Self {
1088 connections,
1089 mappings,
1090 };
1091 config
1092 .validate()
1093 .map_err(|source| crate::Error::Validation {
1094 context: "validating a YubiHSM2 specific configuration item".to_string(),
1095 source,
1096 })?;
1097
1098 Ok(config)
1099 }
1100
1101 pub fn connections(&self) -> &BTreeSet<Connection> {
1103 &self.connections
1104 }
1105
1106 pub fn mappings(&self) -> &BTreeSet<YubiHsm2UserMapping> {
1108 &self.mappings
1109 }
1110
1111 pub fn wrap_key_label() -> Label {
1113 Label::from_truncated_str(Self::WRAP_KEY_LABEL)
1114 }
1115
1116 pub fn openpgp_certificate_label() -> Label {
1118 Label::from_truncated_str(Self::OPENPGP_CERTIFICATE_LABEL)
1119 }
1120}
1121
1122impl ConfigAuthorizedKeyEntries for YubiHsm2Config {
1123 fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
1124 self.mappings
1125 .iter()
1126 .filter_map(|mapping| mapping.authorized_key_entry())
1127 .collect()
1128 }
1129}
1130
1131impl ConfigSystemUserIds for YubiHsm2Config {
1132 fn system_user_ids(&self) -> HashSet<&SystemUserId> {
1133 self.mappings
1134 .iter()
1135 .filter_map(|mapping| mapping.system_user_id())
1136 .collect()
1137 }
1138}
1139
1140#[derive(Clone, Copy, Debug, strum::Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
1142#[strum(serialize_all = "snake_case")]
1143pub enum AuthType {
1144 Admin,
1146
1147 AuditLog,
1149
1150 Backup,
1152
1153 CertificateRetrieval,
1155
1156 HermeticAuditLog,
1158
1159 Signing,
1161}
1162
1163impl From<&YubiHsm2UserMapping> for AuthType {
1164 fn from(value: &YubiHsm2UserMapping) -> Self {
1165 match value {
1166 YubiHsm2UserMapping::Admin { .. } => Self::Admin,
1167 YubiHsm2UserMapping::AuditLog { .. } => Self::AuditLog,
1168 YubiHsm2UserMapping::Backup { .. } => Self::Backup,
1169 YubiHsm2UserMapping::CertificateRetrieval { .. } => Self::CertificateRetrieval,
1170 YubiHsm2UserMapping::HermeticAuditLog { .. } => Self::HermeticAuditLog,
1171 YubiHsm2UserMapping::Signing { .. } => Self::Signing,
1172 }
1173 }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use std::thread::current;
1179
1180 use insta::{assert_snapshot, with_settings};
1181 use rstest::{fixture, rstest};
1182 use signstar_crypto::{
1183 key::{CryptographicKeyContext, KeyMechanism, KeyType, SignatureType, SigningKeySetup},
1184 openpgp::OpenPgpUserIdList,
1185 };
1186 use testresult::TestResult;
1187
1188 use super::*;
1189
1190 const SNAPSHOT_PATH: &str = "fixtures/yubihsm2_config/";
1191
1192 #[rstest]
1193 #[case::admin(YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? })]
1194 #[case::audit_log(
1195 YubiHsm2UserMapping::AuditLog {
1196 authentication_key_id: "1".parse()?,
1197 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1198 system_user: "metrics-user".parse()?,
1199 },
1200 )]
1201 #[case::backup(
1202 YubiHsm2UserMapping::Backup{
1203 authentication_key_id: "1".parse()?,
1204 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1205 system_user: "backup-user".parse()?,
1206 },
1207 )]
1208 #[case::hermetic_audit_log(
1209 YubiHsm2UserMapping::HermeticAuditLog {
1210 authentication_key_id: "1".parse()?,
1211 system_user: "metrics-user".parse()?,
1212 },
1213 )]
1214 #[case::signing(
1215 YubiHsm2UserMapping::Signing {
1216 authentication_key_id: "1".parse()?,
1217 signing_key_id: "1".parse()?,
1218 key_setup: SigningKeySetup::new(
1219 KeyType::Curve25519,
1220 vec![KeyMechanism::EdDsaSignature],
1221 None,
1222 SignatureType::EdDsa,
1223 CryptographicKeyContext::OpenPgp {
1224 user_ids: OpenPgpUserIdList::new(vec![
1225 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1226 ])?,
1227 version: "v4".parse()?,
1228 notations: Default::default(),
1229 },
1230 )?,
1231 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1232 system_user: "signing-user".parse()?,
1233 domain: Domain::One,
1234 }
1235 )]
1236 fn yubihsm2_user_mapping_backend_user_id(#[case] mapping: YubiHsm2UserMapping) -> TestResult {
1237 let id: Id = "1".parse()?;
1238 assert_eq!(mapping.backend_user_id(), id);
1239
1240 Ok(())
1241 }
1242
1243 #[rstest]
1245 #[case::admin(
1246 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1247 YubiHsm2UserMapping::CAP_ADMIN,
1248 )]
1249 #[case::audit_log(
1250 YubiHsm2UserMapping::AuditLog {
1251 authentication_key_id: "1".parse()?,
1252 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1253 system_user: "metrics-user".parse()?,
1254 },
1255 YubiHsm2UserMapping::CAP_AUDIT_LOG,
1256 )]
1257 #[case::backup(
1258 YubiHsm2UserMapping::Backup{
1259 authentication_key_id: "1".parse()?,
1260 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1261 system_user: "backup-user".parse()?,
1262 },
1263 YubiHsm2UserMapping::CAP_BACKUP,
1264 )]
1265 #[case::hermetic_audit_log(
1266 YubiHsm2UserMapping::HermeticAuditLog {
1267 authentication_key_id: "1".parse()?,
1268 system_user: "metrics-user".parse()?,
1269 },
1270 YubiHsm2UserMapping::CAP_HERMETIC_AUDIT_LOG,
1271 )]
1272 #[case::signing(
1273 YubiHsm2UserMapping::Signing {
1274 authentication_key_id: "1".parse()?,
1275 signing_key_id: "1".parse()?,
1276 key_setup: SigningKeySetup::new(
1277 KeyType::Curve25519,
1278 vec![KeyMechanism::EdDsaSignature],
1279 None,
1280 SignatureType::EdDsa,
1281 CryptographicKeyContext::OpenPgp {
1282 user_ids: OpenPgpUserIdList::new(vec![
1283 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1284 ])?,
1285 version: "v4".parse()?,
1286 notations: Default::default(),
1287 },
1288 )?,
1289 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1290 system_user: "signing-user".parse()?,
1291 domain: Domain::One,
1292 },
1293 YubiHsm2UserMapping::CAP_SIGNING,
1294 )]
1295 fn yubihsm2_user_mapping_capability(
1296 #[case] mapping: YubiHsm2UserMapping,
1297 #[case] expected: &[Capability],
1298 ) -> TestResult {
1299 let expected = Capabilities::from(expected);
1300 assert_eq!(mapping.capabilities(), expected);
1301
1302 Ok(())
1303 }
1304
1305 #[rstest]
1306 #[case::admin_filter_admin(
1307 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1308 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1309 )]
1310 #[case::admin_filter_any(
1311 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1312 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1313 )]
1314 #[case::audit_log_filter_metrics(
1315 YubiHsm2UserMapping::AuditLog {
1316 authentication_key_id: "1".parse()?,
1317 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1318 system_user: "metrics-user".parse()?,
1319 },
1320 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1321 )]
1322 #[case::audit_log_filter_any(
1323 YubiHsm2UserMapping::AuditLog {
1324 authentication_key_id: "1".parse()?,
1325 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1326 system_user: "metrics-user".parse()?,
1327 },
1328 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1329 )]
1330 #[case::audit_log_filter_non_admin(
1331 YubiHsm2UserMapping::AuditLog {
1332 authentication_key_id: "1".parse()?,
1333 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1334 system_user: "metrics-user".parse()?,
1335 },
1336 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1337 )]
1338 #[case::backup_filter_backup(
1339 YubiHsm2UserMapping::Backup{
1340 authentication_key_id: "1".parse()?,
1341 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1342 system_user: "backup-user".parse()?,
1343 },
1344 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1345 )]
1346 #[case::backup_filter_any(
1347 YubiHsm2UserMapping::Backup{
1348 authentication_key_id: "1".parse()?,
1349 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1350 system_user: "backup-user".parse()?,
1351 },
1352 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1353 )]
1354 #[case::backup_filter_non_admin(
1355 YubiHsm2UserMapping::Backup{
1356 authentication_key_id: "1".parse()?,
1357 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1358 system_user: "backup-user".parse()?,
1359 },
1360 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1361 )]
1362 #[case::hermetic_audit_log_filter_metrics(
1363 YubiHsm2UserMapping::HermeticAuditLog {
1364 authentication_key_id: "1".parse()?,
1365 system_user: "metrics-user".parse()?,
1366 },
1367 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1368 )]
1369 #[case::hermetic_audit_log_filter_any(
1370 YubiHsm2UserMapping::HermeticAuditLog {
1371 authentication_key_id: "1".parse()?,
1372 system_user: "metrics-user".parse()?,
1373 },
1374 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1375 )]
1376 #[case::hermetic_audit_log_filter_non_admin(
1377 YubiHsm2UserMapping::HermeticAuditLog {
1378 authentication_key_id: "1".parse()?,
1379 system_user: "metrics-user".parse()?,
1380 },
1381 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1382 )]
1383 #[case::signing_filter_signing(
1384 YubiHsm2UserMapping::Signing {
1385 authentication_key_id: "1".parse()?,
1386 signing_key_id: "1".parse()?,
1387 key_setup: SigningKeySetup::new(
1388 KeyType::Curve25519,
1389 vec![KeyMechanism::EdDsaSignature],
1390 None,
1391 SignatureType::EdDsa,
1392 CryptographicKeyContext::OpenPgp {
1393 user_ids: OpenPgpUserIdList::new(vec![
1394 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1395 ])?,
1396 version: "v4".parse()?,
1397 notations: Default::default(),
1398 },
1399 )?,
1400 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1401 system_user: "signing-user".parse()?,
1402 domain: Domain::One,
1403 },
1404 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1405 )]
1406 #[case::signing_filter_any(
1407 YubiHsm2UserMapping::Signing {
1408 authentication_key_id: "1".parse()?,
1409 signing_key_id: "1".parse()?,
1410 key_setup: SigningKeySetup::new(
1411 KeyType::Curve25519,
1412 vec![KeyMechanism::EdDsaSignature],
1413 None,
1414 SignatureType::EdDsa,
1415 CryptographicKeyContext::OpenPgp {
1416 user_ids: OpenPgpUserIdList::new(vec![
1417 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1418 ])?,
1419 version: "v4".parse()?,
1420 notations: Default::default(),
1421 },
1422 )?,
1423 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1424 system_user: "signing-user".parse()?,
1425 domain: Domain::One,
1426 },
1427 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1428 )]
1429 #[case::signing_filter_non_admin(
1430 YubiHsm2UserMapping::Signing {
1431 authentication_key_id: "1".parse()?,
1432 signing_key_id: "1".parse()?,
1433 key_setup: SigningKeySetup::new(
1434 KeyType::Curve25519,
1435 vec![KeyMechanism::EdDsaSignature],
1436 None,
1437 SignatureType::EdDsa,
1438 CryptographicKeyContext::OpenPgp {
1439 user_ids: OpenPgpUserIdList::new(vec![
1440 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1441 ])?,
1442 version: "v4".parse()?,
1443 notations: Default::default(),
1444 },
1445 )?,
1446 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1447 system_user: "signing-user".parse()?,
1448 domain: Domain::One,
1449 },
1450 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1451 )]
1452 fn yubihsm2_user_mapping_backend_user_ids_filter_matches(
1453 #[case] mapping: YubiHsm2UserMapping,
1454 #[case] filter: BackendUserIdFilter,
1455 ) -> TestResult {
1456 assert_eq!(mapping.backend_user_ids(filter), vec!["1".to_string()]);
1457
1458 Ok(())
1459 }
1460
1461 #[rstest]
1462 #[case::admin_filter_non_admin(
1463 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1464 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1465 )]
1466 #[case::admin_filter_backup(
1467 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1468 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1469 )]
1470 #[case::admin_filter_metrics(
1471 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1472 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1473 )]
1474 #[case::admin_filter_observer(
1475 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1476 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1477 )]
1478 #[case::admin_filter_signing(
1479 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1480 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1481 )]
1482 #[case::audit_log_filter_admin(
1483 YubiHsm2UserMapping::AuditLog {
1484 authentication_key_id: "1".parse()?,
1485 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1486 system_user: "metrics-user".parse()?,
1487 },
1488 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1489 )]
1490 #[case::audit_log_filter_backup(
1491 YubiHsm2UserMapping::AuditLog {
1492 authentication_key_id: "1".parse()?,
1493 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1494 system_user: "metrics-user".parse()?,
1495 },
1496 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1497 )]
1498 #[case::audit_log_filter_observer(
1499 YubiHsm2UserMapping::AuditLog {
1500 authentication_key_id: "1".parse()?,
1501 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1502 system_user: "metrics-user".parse()?,
1503 },
1504 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1505 )]
1506 #[case::audit_log_filter_signing(
1507 YubiHsm2UserMapping::AuditLog {
1508 authentication_key_id: "1".parse()?,
1509 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1510 system_user: "metrics-user".parse()?,
1511 },
1512 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1513 )]
1514 #[case::backup_filter_admin(
1515 YubiHsm2UserMapping::Backup{
1516 authentication_key_id: "1".parse()?,
1517 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1518 system_user: "backup-user".parse()?,
1519 },
1520 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1521 )]
1522 #[case::backup_filter_metrics(
1523 YubiHsm2UserMapping::Backup{
1524 authentication_key_id: "1".parse()?,
1525 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1526 system_user: "backup-user".parse()?,
1527 },
1528 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1529 )]
1530 #[case::backup_filter_observer(
1531 YubiHsm2UserMapping::Backup{
1532 authentication_key_id: "1".parse()?,
1533 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1534 system_user: "backup-user".parse()?,
1535 },
1536 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1537 )]
1538 #[case::backup_filter_signing(
1539 YubiHsm2UserMapping::Backup{
1540 authentication_key_id: "1".parse()?,
1541 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1542 system_user: "backup-user".parse()?,
1543 },
1544 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1545 )]
1546 #[case::hermetic_audit_log_filter_admin(
1547 YubiHsm2UserMapping::HermeticAuditLog {
1548 authentication_key_id: "1".parse()?,
1549 system_user: "metrics-user".parse()?,
1550 },
1551 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1552 )]
1553 #[case::hermetic_audit_log_filter_backup(
1554 YubiHsm2UserMapping::HermeticAuditLog {
1555 authentication_key_id: "1".parse()?,
1556 system_user: "metrics-user".parse()?,
1557 },
1558 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1559 )]
1560 #[case::hermetic_audit_log_filter_observer(
1561 YubiHsm2UserMapping::HermeticAuditLog {
1562 authentication_key_id: "1".parse()?,
1563 system_user: "metrics-user".parse()?,
1564 },
1565 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1566 )]
1567 #[case::hermetic_audit_log_filter_signing(
1568 YubiHsm2UserMapping::HermeticAuditLog {
1569 authentication_key_id: "1".parse()?,
1570 system_user: "metrics-user".parse()?,
1571 },
1572 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1573 )]
1574 #[case::signing_filter_admin(
1575 YubiHsm2UserMapping::Signing {
1576 authentication_key_id: "1".parse()?,
1577 signing_key_id: "1".parse()?,
1578 key_setup: SigningKeySetup::new(
1579 KeyType::Curve25519,
1580 vec![KeyMechanism::EdDsaSignature],
1581 None,
1582 SignatureType::EdDsa,
1583 CryptographicKeyContext::OpenPgp {
1584 user_ids: OpenPgpUserIdList::new(vec![
1585 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1586 ])?,
1587 version: "v4".parse()?,
1588 notations: Default::default(),
1589 },
1590 )?,
1591 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1592 system_user: "signing-user".parse()?,
1593 domain: Domain::One,
1594 },
1595 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1596 )]
1597 #[case::signing_filter_backup(
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 notations: Default::default(),
1612 },
1613 )?,
1614 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1615 system_user: "signing-user".parse()?,
1616 domain: Domain::One,
1617 },
1618 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1619 )]
1620 #[case::signing_filter_metrics(
1621 YubiHsm2UserMapping::Signing {
1622 authentication_key_id: "1".parse()?,
1623 signing_key_id: "1".parse()?,
1624 key_setup: SigningKeySetup::new(
1625 KeyType::Curve25519,
1626 vec![KeyMechanism::EdDsaSignature],
1627 None,
1628 SignatureType::EdDsa,
1629 CryptographicKeyContext::OpenPgp {
1630 user_ids: OpenPgpUserIdList::new(vec![
1631 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1632 ])?,
1633 version: "v4".parse()?,
1634 notations: Default::default(),
1635 },
1636 )?,
1637 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1638 system_user: "signing-user".parse()?,
1639 domain: Domain::One,
1640 },
1641 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1642 )]
1643 #[case::signing_filter_observer(
1644 YubiHsm2UserMapping::Signing {
1645 authentication_key_id: "1".parse()?,
1646 signing_key_id: "1".parse()?,
1647 key_setup: SigningKeySetup::new(
1648 KeyType::Curve25519,
1649 vec![KeyMechanism::EdDsaSignature],
1650 None,
1651 SignatureType::EdDsa,
1652 CryptographicKeyContext::OpenPgp {
1653 user_ids: OpenPgpUserIdList::new(vec![
1654 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1655 ])?,
1656 version: "v4".parse()?,
1657 notations: Default::default(),
1658 },
1659 )?,
1660 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1661 system_user: "signing-user".parse()?,
1662 domain: Domain::One,
1663 },
1664 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1665 )]
1666 fn yubihsm2_user_mapping_backend_user_ids_filter_mismatches(
1667 #[case] mapping: YubiHsm2UserMapping,
1668 #[case] filter: BackendUserIdFilter,
1669 ) -> TestResult {
1670 assert!(mapping.backend_user_ids(filter).is_empty());
1671
1672 Ok(())
1673 }
1674
1675 #[test]
1676 fn yubihsm2_user_mapping_backend_user_with_passphrase_succeeds() -> TestResult {
1677 let mapping = YubiHsm2UserMapping::Admin {
1678 authentication_key_id: "1".parse()?,
1679 };
1680 let passphrase = Passphrase::generate(None);
1681 let creds = mapping.backend_user_with_passphrase("1", passphrase.clone())?;
1682
1683 assert_eq!(creds.user(), "1");
1684 assert_eq!(
1685 creds.passphrase().expose_borrowed(),
1686 passphrase.expose_borrowed()
1687 );
1688
1689 Ok(())
1690 }
1691
1692 #[test]
1693 fn yubihsm2_user_mapping_backend_user_with_passphrase_fails() -> TestResult {
1694 let mapping = YubiHsm2UserMapping::Admin {
1695 authentication_key_id: "1".parse()?,
1696 };
1697 assert!(
1698 mapping
1699 .backend_user_with_passphrase("2", Passphrase::generate(None))
1700 .is_err()
1701 );
1702
1703 Ok(())
1704 }
1705
1706 #[rstest]
1707 #[case::admin_filter_admin(
1708 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1709 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1710 )]
1711 #[case::admin_filter_any(
1712 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1713 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1714 )]
1715 #[case::audit_log_filter_metrics(
1716 YubiHsm2UserMapping::AuditLog {
1717 authentication_key_id: "1".parse()?,
1718 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1719 system_user: "metrics-user".parse()?,
1720 },
1721 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1722 )]
1723 #[case::audit_log_filter_any(
1724 YubiHsm2UserMapping::AuditLog {
1725 authentication_key_id: "1".parse()?,
1726 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1727 system_user: "metrics-user".parse()?,
1728 },
1729 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1730 )]
1731 #[case::audit_log_filter_non_admin(
1732 YubiHsm2UserMapping::AuditLog {
1733 authentication_key_id: "1".parse()?,
1734 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1735 system_user: "metrics-user".parse()?,
1736 },
1737 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1738 )]
1739 #[case::backup_filter_backup(
1740 YubiHsm2UserMapping::Backup{
1741 authentication_key_id: "1".parse()?,
1742 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1743 system_user: "backup-user".parse()?,
1744 },
1745 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1746 )]
1747 #[case::backup_filter_any(
1748 YubiHsm2UserMapping::Backup{
1749 authentication_key_id: "1".parse()?,
1750 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1751 system_user: "backup-user".parse()?,
1752 },
1753 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1754 )]
1755 #[case::backup_filter_non_admin(
1756 YubiHsm2UserMapping::Backup{
1757 authentication_key_id: "1".parse()?,
1758 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1759 system_user: "backup-user".parse()?,
1760 },
1761 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1762 )]
1763 #[case::hermetic_audit_log_filter_metrics(
1764 YubiHsm2UserMapping::HermeticAuditLog {
1765 authentication_key_id: "1".parse()?,
1766 system_user: "metrics-user".parse()?,
1767 },
1768 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1769 )]
1770 #[case::hermetic_audit_log_filter_any(
1771 YubiHsm2UserMapping::HermeticAuditLog {
1772 authentication_key_id: "1".parse()?,
1773 system_user: "metrics-user".parse()?,
1774 },
1775 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1776 )]
1777 #[case::hermetic_audit_log_filter_non_admin(
1778 YubiHsm2UserMapping::HermeticAuditLog {
1779 authentication_key_id: "1".parse()?,
1780 system_user: "metrics-user".parse()?,
1781 },
1782 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1783 )]
1784 #[case::signing_filter_signing(
1785 YubiHsm2UserMapping::Signing {
1786 authentication_key_id: "1".parse()?,
1787 signing_key_id: "1".parse()?,
1788 key_setup: SigningKeySetup::new(
1789 KeyType::Curve25519,
1790 vec![KeyMechanism::EdDsaSignature],
1791 None,
1792 SignatureType::EdDsa,
1793 CryptographicKeyContext::OpenPgp {
1794 user_ids: OpenPgpUserIdList::new(vec![
1795 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1796 ])?,
1797 version: "v4".parse()?,
1798 notations: Default::default(),
1799 },
1800 )?,
1801 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1802 system_user: "signing-user".parse()?,
1803 domain: Domain::One,
1804 },
1805 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1806 )]
1807 #[case::signing_filter_any(
1808 YubiHsm2UserMapping::Signing {
1809 authentication_key_id: "1".parse()?,
1810 signing_key_id: "1".parse()?,
1811 key_setup: SigningKeySetup::new(
1812 KeyType::Curve25519,
1813 vec![KeyMechanism::EdDsaSignature],
1814 None,
1815 SignatureType::EdDsa,
1816 CryptographicKeyContext::OpenPgp {
1817 user_ids: OpenPgpUserIdList::new(vec![
1818 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1819 ])?,
1820 version: "v4".parse()?,
1821 notations: Default::default(),
1822 },
1823 )?,
1824 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1825 system_user: "signing-user".parse()?,
1826 domain: Domain::One,
1827 },
1828 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1829 )]
1830 #[case::signing_filter_non_admin(
1831 YubiHsm2UserMapping::Signing {
1832 authentication_key_id: "1".parse()?,
1833 signing_key_id: "1".parse()?,
1834 key_setup: SigningKeySetup::new(
1835 KeyType::Curve25519,
1836 vec![KeyMechanism::EdDsaSignature],
1837 None,
1838 SignatureType::EdDsa,
1839 CryptographicKeyContext::OpenPgp {
1840 user_ids: OpenPgpUserIdList::new(vec![
1841 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1842 ])?,
1843 version: "v4".parse()?,
1844 notations: Default::default(),
1845 },
1846 )?,
1847 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1848 system_user: "signing-user".parse()?,
1849 domain: Domain::One,
1850 },
1851 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1852 )]
1853 fn yubihsm2_user_mapping_backend_users_with_new_passphrase_filter_matches(
1854 #[case] mapping: YubiHsm2UserMapping,
1855 #[case] filter: BackendUserIdFilter,
1856 ) -> TestResult {
1857 let creds = mapping.backend_users_with_new_passphrase(filter);
1858 assert!(creds.first().is_some_and(|creds| creds.user() == "1"));
1859
1860 Ok(())
1861 }
1862
1863 #[rstest]
1864 #[case::admin_filter_non_admin(
1865 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1866 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1867 )]
1868 #[case::admin_filter_backup(
1869 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1870 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1871 )]
1872 #[case::admin_filter_metrics(
1873 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1874 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1875 )]
1876 #[case::admin_filter_observer(
1877 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1878 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1879 )]
1880 #[case::admin_filter_signing(
1881 YubiHsm2UserMapping::Admin{ authentication_key_id: "1".parse()? },
1882 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1883 )]
1884 #[case::audit_log_filter_admin(
1885 YubiHsm2UserMapping::AuditLog {
1886 authentication_key_id: "1".parse()?,
1887 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1888 system_user: "metrics-user".parse()?,
1889 },
1890 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1891 )]
1892 #[case::audit_log_filter_backup(
1893 YubiHsm2UserMapping::AuditLog {
1894 authentication_key_id: "1".parse()?,
1895 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1896 system_user: "metrics-user".parse()?,
1897 },
1898 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1899 )]
1900 #[case::audit_log_filter_observer(
1901 YubiHsm2UserMapping::AuditLog {
1902 authentication_key_id: "1".parse()?,
1903 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1904 system_user: "metrics-user".parse()?,
1905 },
1906 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1907 )]
1908 #[case::audit_log_filter_signing(
1909 YubiHsm2UserMapping::AuditLog {
1910 authentication_key_id: "1".parse()?,
1911 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1912 system_user: "metrics-user".parse()?,
1913 },
1914 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1915 )]
1916 #[case::backup_filter_admin(
1917 YubiHsm2UserMapping::Backup{
1918 authentication_key_id: "1".parse()?,
1919 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1920 system_user: "backup-user".parse()?,
1921 },
1922 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1923 )]
1924 #[case::backup_filter_metrics(
1925 YubiHsm2UserMapping::Backup{
1926 authentication_key_id: "1".parse()?,
1927 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1928 system_user: "backup-user".parse()?,
1929 },
1930 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1931 )]
1932 #[case::backup_filter_observer(
1933 YubiHsm2UserMapping::Backup{
1934 authentication_key_id: "1".parse()?,
1935 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1936 system_user: "backup-user".parse()?,
1937 },
1938 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1939 )]
1940 #[case::backup_filter_signing(
1941 YubiHsm2UserMapping::Backup{
1942 authentication_key_id: "1".parse()?,
1943 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1944 system_user: "backup-user".parse()?,
1945 },
1946 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1947 )]
1948 #[case::hermetic_audit_log_filter_admin(
1949 YubiHsm2UserMapping::HermeticAuditLog {
1950 authentication_key_id: "1".parse()?,
1951 system_user: "metrics-user".parse()?,
1952 },
1953 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1954 )]
1955 #[case::hermetic_audit_log_filter_backup(
1956 YubiHsm2UserMapping::HermeticAuditLog {
1957 authentication_key_id: "1".parse()?,
1958 system_user: "metrics-user".parse()?,
1959 },
1960 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1961 )]
1962 #[case::hermetic_audit_log_filter_observer(
1963 YubiHsm2UserMapping::HermeticAuditLog {
1964 authentication_key_id: "1".parse()?,
1965 system_user: "metrics-user".parse()?,
1966 },
1967 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1968 )]
1969 #[case::hermetic_audit_log_filter_signing(
1970 YubiHsm2UserMapping::HermeticAuditLog {
1971 authentication_key_id: "1".parse()?,
1972 system_user: "metrics-user".parse()?,
1973 },
1974 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1975 )]
1976 #[case::signing_filter_admin(
1977 YubiHsm2UserMapping::Signing {
1978 authentication_key_id: "1".parse()?,
1979 signing_key_id: "1".parse()?,
1980 key_setup: SigningKeySetup::new(
1981 KeyType::Curve25519,
1982 vec![KeyMechanism::EdDsaSignature],
1983 None,
1984 SignatureType::EdDsa,
1985 CryptographicKeyContext::OpenPgp {
1986 user_ids: OpenPgpUserIdList::new(vec![
1987 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1988 ])?,
1989 version: "v4".parse()?,
1990 notations: Default::default(),
1991 },
1992 )?,
1993 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1994 system_user: "signing-user".parse()?,
1995 domain: Domain::One,
1996 },
1997 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1998 )]
1999 #[case::signing_filter_backup(
2000 YubiHsm2UserMapping::Signing {
2001 authentication_key_id: "1".parse()?,
2002 signing_key_id: "1".parse()?,
2003 key_setup: SigningKeySetup::new(
2004 KeyType::Curve25519,
2005 vec![KeyMechanism::EdDsaSignature],
2006 None,
2007 SignatureType::EdDsa,
2008 CryptographicKeyContext::OpenPgp {
2009 user_ids: OpenPgpUserIdList::new(vec![
2010 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2011 ])?,
2012 version: "v4".parse()?,
2013 notations: Default::default(),
2014 },
2015 )?,
2016 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2017 system_user: "signing-user".parse()?,
2018 domain: Domain::One,
2019 },
2020 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2021 )]
2022 #[case::signing_filter_metrics(
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 notations: Default::default(),
2037 },
2038 )?,
2039 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2040 system_user: "signing-user".parse()?,
2041 domain: Domain::One,
2042 },
2043 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2044 )]
2045 #[case::signing_filter_observer(
2046 YubiHsm2UserMapping::Signing {
2047 authentication_key_id: "1".parse()?,
2048 signing_key_id: "1".parse()?,
2049 key_setup: SigningKeySetup::new(
2050 KeyType::Curve25519,
2051 vec![KeyMechanism::EdDsaSignature],
2052 None,
2053 SignatureType::EdDsa,
2054 CryptographicKeyContext::OpenPgp {
2055 user_ids: OpenPgpUserIdList::new(vec![
2056 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2057 ])?,
2058 version: "v4".parse()?,
2059 notations: Default::default(),
2060 },
2061 )?,
2062 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2063 system_user: "signing-user".parse()?,
2064 domain: Domain::One,
2065 },
2066 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2067 )]
2068 fn yubihsm2_user_mapping_backend_users_with_new_passphrase_filter_mismatches(
2069 #[case] mapping: YubiHsm2UserMapping,
2070 #[case] filter: BackendUserIdFilter,
2071 ) -> TestResult {
2072 assert!(mapping.backend_users_with_new_passphrase(filter).is_empty());
2073
2074 Ok(())
2075 }
2076
2077 #[rstest]
2078 #[case::backup_filter_wrapping_no_domain(
2079 YubiHsm2UserMapping::Backup{
2080 authentication_key_id: "1".parse()?,
2081 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2082 system_user: "backup-user".parse()?,
2083 },
2084 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: None },
2085 )]
2086 #[case::backup_filter_wrapping_some_domain(
2087 YubiHsm2UserMapping::Backup{
2088 authentication_key_id: "1".parse()?,
2089 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2090 system_user: "backup-user".parse()?,
2091 },
2092 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: Some(Domain::One) },
2093 )]
2094 #[case::signing_filter_signing_matching_domain(
2095 YubiHsm2UserMapping::Signing {
2096 authentication_key_id: "1".parse()?,
2097 signing_key_id: "1".parse()?,
2098 key_setup: SigningKeySetup::new(
2099 KeyType::Curve25519,
2100 vec![KeyMechanism::EdDsaSignature],
2101 None,
2102 SignatureType::EdDsa,
2103 CryptographicKeyContext::OpenPgp {
2104 user_ids: OpenPgpUserIdList::new(vec![
2105 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2106 ])?,
2107 version: "v4".parse()?,
2108 notations: Default::default(),
2109 },
2110 )?,
2111 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2112 system_user: "signing-user".parse()?,
2113 domain: Domain::One,
2114 },
2115 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: Some(Domain::One) },
2116 )]
2117 fn yubihsm2_user_mapping_backend_key_id_filter_matches(
2118 #[case] mapping: YubiHsm2UserMapping,
2119 #[case] filter: YubiHsm2BackendKeyIdFilter,
2120 ) -> TestResult {
2121 assert!(mapping.backend_key_id(&filter).is_some_and(|id| id == "1"));
2122
2123 Ok(())
2124 }
2125
2126 #[rstest]
2127 #[case::backup_filter_signing_no_domain(
2128 YubiHsm2UserMapping::Backup{
2129 authentication_key_id: "1".parse()?,
2130 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2131 system_user: "backup-user".parse()?,
2132 },
2133 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: None },
2134 )]
2135 #[case::backup_filter_signing_some_domain(
2136 YubiHsm2UserMapping::Backup{
2137 authentication_key_id: "1".parse()?,
2138 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2139 system_user: "backup-user".parse()?,
2140 },
2141 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: Some(Domain::One) },
2142 )]
2143 #[case::signing_filter_signing_wrong_domain(
2144 YubiHsm2UserMapping::Signing {
2145 authentication_key_id: "1".parse()?,
2146 signing_key_id: "1".parse()?,
2147 key_setup: SigningKeySetup::new(
2148 KeyType::Curve25519,
2149 vec![KeyMechanism::EdDsaSignature],
2150 None,
2151 SignatureType::EdDsa,
2152 CryptographicKeyContext::OpenPgp {
2153 user_ids: OpenPgpUserIdList::new(vec![
2154 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2155 ])?,
2156 version: "v4".parse()?,
2157 notations: Default::default(),
2158 },
2159 )?,
2160 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2161 system_user: "signing-user".parse()?,
2162 domain: Domain::One,
2163 },
2164 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Signing, key_domain: Some(Domain::Two) },
2165 )]
2166 #[case::signing_filter_wrapping_same_domain(
2167 YubiHsm2UserMapping::Signing {
2168 authentication_key_id: "1".parse()?,
2169 signing_key_id: "1".parse()?,
2170 key_setup: SigningKeySetup::new(
2171 KeyType::Curve25519,
2172 vec![KeyMechanism::EdDsaSignature],
2173 None,
2174 SignatureType::EdDsa,
2175 CryptographicKeyContext::OpenPgp {
2176 user_ids: OpenPgpUserIdList::new(vec![
2177 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2178 ])?,
2179 version: "v4".parse()?,
2180 notations: Default::default(),
2181 },
2182 )?,
2183 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2184 system_user: "signing-user".parse()?,
2185 domain: Domain::One,
2186 },
2187 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: Some(Domain::One) },
2188 )]
2189 #[case::signing_filter_wrapping_wrong_domain(
2190 YubiHsm2UserMapping::Signing {
2191 authentication_key_id: "1".parse()?,
2192 signing_key_id: "1".parse()?,
2193 key_setup: SigningKeySetup::new(
2194 KeyType::Curve25519,
2195 vec![KeyMechanism::EdDsaSignature],
2196 None,
2197 SignatureType::EdDsa,
2198 CryptographicKeyContext::OpenPgp {
2199 user_ids: OpenPgpUserIdList::new(vec![
2200 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2201 ])?,
2202 version: "v4".parse()?,
2203 notations: Default::default(),
2204 },
2205 )?,
2206 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2207 system_user: "signing-user".parse()?,
2208 domain: Domain::One,
2209 },
2210 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: Some(Domain::Two) },
2211 )]
2212 #[case::signing_filter_wrapping_no_domain(
2213 YubiHsm2UserMapping::Signing {
2214 authentication_key_id: "1".parse()?,
2215 signing_key_id: "1".parse()?,
2216 key_setup: SigningKeySetup::new(
2217 KeyType::Curve25519,
2218 vec![KeyMechanism::EdDsaSignature],
2219 None,
2220 SignatureType::EdDsa,
2221 CryptographicKeyContext::OpenPgp {
2222 user_ids: OpenPgpUserIdList::new(vec![
2223 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2224 ])?,
2225 version: "v4".parse()?,
2226 notations: Default::default(),
2227 },
2228 )?,
2229 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2230 system_user: "signing-user".parse()?,
2231 domain: Domain::One,
2232 },
2233 YubiHsm2BackendKeyIdFilter{ key_type: KeyObjectType::Wrapping, key_domain: None },
2234 )]
2235 fn yubihsm2_user_mapping_backend_key_id_filter_mismatches(
2236 #[case] mapping: YubiHsm2UserMapping,
2237 #[case] filter: YubiHsm2BackendKeyIdFilter,
2238 ) -> TestResult {
2239 assert!(mapping.backend_key_id(&filter).is_none());
2240
2241 Ok(())
2242 }
2243
2244 #[fixture]
2245 fn yubihsm2_yubihsm_connections() -> TestResult<[Connection; 2]> {
2246 Ok([
2247 Connection::Usb {
2248 serial_number: "0012345678".parse()?,
2249 },
2250 Connection::Usb {
2251 serial_number: "0087654321".parse()?,
2252 },
2253 ])
2254 }
2255
2256 #[fixture]
2257 fn yubihsm2_mappings() -> TestResult<[YubiHsm2UserMapping; 5]> {
2258 Ok([
2259 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2260 YubiHsm2UserMapping::Backup{
2261 authentication_key_id: "2".parse()?,
2262 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2263 system_user: "backup-user".parse()?,
2264 },
2265 YubiHsm2UserMapping::AuditLog {
2266 authentication_key_id: "3".parse()?,
2267 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2268 system_user: "metrics-user".parse()?,
2269 },
2270 YubiHsm2UserMapping::HermeticAuditLog {
2271 authentication_key_id: "4".parse()?,
2272 system_user: "hermetic-metrics".parse()?,
2273 },
2274 YubiHsm2UserMapping::Signing {
2275 authentication_key_id: "5".parse()?,
2276 signing_key_id: "1".parse()?,
2277 key_setup: SigningKeySetup::new(
2278 KeyType::Curve25519,
2279 vec![KeyMechanism::EdDsaSignature],
2280 None,
2281 SignatureType::EdDsa,
2282 CryptographicKeyContext::OpenPgp {
2283 user_ids: OpenPgpUserIdList::new(vec![
2284 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2285 ])?,
2286 version: "v4".parse()?,
2287 notations: Default::default(),
2288 },
2289 )?,
2290 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2291 system_user: "signing-user".parse()?,
2292 domain: Domain::One,
2293 }
2294 ])
2295 }
2296
2297 #[fixture]
2298 fn yubihsm2_config(
2299 yubihsm2_yubihsm_connections: TestResult<[Connection; 2]>,
2300 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2301 ) -> TestResult<YubiHsm2Config> {
2302 let yubihsm2_yubihsm_connections = yubihsm2_yubihsm_connections?;
2303 let yubihsm2_mappings = yubihsm2_mappings?;
2304 let config = YubiHsm2Config::new(
2305 BTreeSet::from_iter(yubihsm2_yubihsm_connections),
2306 BTreeSet::from_iter(yubihsm2_mappings),
2307 )?;
2308
2309 Ok(config)
2310 }
2311
2312 #[rstest]
2313 fn yubihsm2_config_connections(
2314 yubihsm2_yubihsm_connections: TestResult<[Connection; 2]>,
2315 yubihsm2_config: TestResult<YubiHsm2Config>,
2316 ) -> TestResult {
2317 let yubihsm2_config = yubihsm2_config?;
2318 let yubihsm2_yubihsm_connections = yubihsm2_yubihsm_connections?;
2319 let connections = yubihsm2_config.connections();
2320
2321 assert_eq!(connections.len(), 2);
2322 assert!(
2323 connections
2324 .first()
2325 .is_some_and(|connection| connection == &yubihsm2_yubihsm_connections[0]),
2326 );
2327 assert!(
2328 connections
2329 .last()
2330 .is_some_and(|connection| connection == &yubihsm2_yubihsm_connections[1]),
2331 );
2332
2333 Ok(())
2334 }
2335
2336 #[rstest]
2337 fn yubihsm2_config_mappings(
2338 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2339 yubihsm2_config: TestResult<YubiHsm2Config>,
2340 ) -> TestResult {
2341 let yubihsm2_config = yubihsm2_config?;
2342 let yubihsm2_mappings = yubihsm2_mappings?;
2343 let mappings = yubihsm2_config.mappings();
2344
2345 assert_eq!(mappings.len(), 5);
2346 for mapping in yubihsm2_mappings.iter() {
2347 assert!(mappings.contains(mapping));
2348 }
2349
2350 Ok(())
2351 }
2352
2353 #[rstest]
2354 fn yubihsm2_config_authorized_key_entries(
2355 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2356 yubihsm2_config: TestResult<YubiHsm2Config>,
2357 ) -> TestResult {
2358 let yubihsm2_config = yubihsm2_config?;
2359 let authorized_key_entries = yubihsm2_config.authorized_key_entries();
2360
2361 let yubihsm2_mappings = yubihsm2_mappings?;
2362 let initial_entries = yubihsm2_mappings
2363 .iter()
2364 .filter_map(|mapping| mapping.authorized_key_entry())
2365 .collect::<HashSet<_>>();
2366
2367 assert_eq!(initial_entries, authorized_key_entries);
2368
2369 Ok(())
2370 }
2371
2372 #[rstest]
2373 fn yubihsm2_config_system_user_ids(
2374 yubihsm2_mappings: TestResult<[YubiHsm2UserMapping; 5]>,
2375 yubihsm2_config: TestResult<YubiHsm2Config>,
2376 ) -> TestResult {
2377 let yubihsm2_config = yubihsm2_config?;
2378 let system_user_ids = yubihsm2_config.system_user_ids();
2379
2380 let yubihsm2_mappings = yubihsm2_mappings?;
2381 let initial_entries = yubihsm2_mappings
2382 .iter()
2383 .filter_map(|mapping| mapping.system_user_id())
2384 .collect::<HashSet<_>>();
2385
2386 assert_eq!(initial_entries, system_user_ids);
2387
2388 Ok(())
2389 }
2390
2391 #[rstest]
2392 #[case::no_connection(
2393 "Error message for YubiHsm2Config::new with no connection",
2394 BTreeSet::new(),
2395 BTreeSet::from_iter([
2396 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2397 YubiHsm2UserMapping::Backup{
2398 authentication_key_id: "2".parse()?,
2399 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2400 system_user: "backup-user".parse()?,
2401 },
2402 YubiHsm2UserMapping::AuditLog {
2403 authentication_key_id: "3".parse()?,
2404 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2405 system_user: "metrics-user".parse()?,
2406 },
2407 YubiHsm2UserMapping::Signing {
2408 authentication_key_id: "4".parse()?,
2409 signing_key_id: "1".parse()?,
2410 key_setup: SigningKeySetup::new(
2411 KeyType::Curve25519,
2412 vec![KeyMechanism::EdDsaSignature],
2413 None,
2414 SignatureType::EdDsa,
2415 CryptographicKeyContext::OpenPgp {
2416 user_ids: OpenPgpUserIdList::new(vec![
2417 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2418 ])?,
2419 version: "v4".parse()?,
2420 notations: Default::default(),
2421 },
2422 )?,
2423 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2424 system_user: "signing-user".parse()?,
2425 domain: Domain::One,
2426 }
2427 ]),
2428 )]
2429 #[case::no_mappings(
2430 "Error message for YubiHsm2Config::new with no user mappings",
2431 BTreeSet::from_iter([
2432 Connection::Usb {serial_number: "0012345678".parse()? },
2433 Connection::Usb {serial_number: "0087654321".parse()? },
2434 ]),
2435 BTreeSet::new(),
2436 )]
2437 #[case::duplicate_system_user_ids(
2438 "Error message for YubiHsm2Config::new with two duplicate system user IDs",
2439 BTreeSet::from_iter([
2440 Connection::Usb {serial_number: "0012345678".parse()? },
2441 Connection::Usb {serial_number: "0087654321".parse()? },
2442 ]),
2443 BTreeSet::from_iter([
2444 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2445 YubiHsm2UserMapping::Backup{
2446 authentication_key_id: "2".parse()?,
2447 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2448 system_user: "backup-user".parse()?,
2449 },
2450 YubiHsm2UserMapping::AuditLog {
2451 authentication_key_id: "3".parse()?,
2452 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2453 system_user: "backup-user".parse()?,
2454 },
2455 YubiHsm2UserMapping::Signing {
2456 authentication_key_id: "4".parse()?,
2457 signing_key_id: "1".parse()?,
2458 key_setup: SigningKeySetup::new(
2459 KeyType::Curve25519,
2460 vec![KeyMechanism::EdDsaSignature],
2461 None,
2462 SignatureType::EdDsa,
2463 CryptographicKeyContext::OpenPgp {
2464 user_ids: OpenPgpUserIdList::new(vec![
2465 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2466 ])?,
2467 version: "v4".parse()?,
2468 notations: Default::default(),
2469 },
2470 )?,
2471 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2472 system_user: "signing-user".parse()?,
2473 domain: Domain::One,
2474 }
2475 ]),
2476 )]
2477 #[case::duplicate_ssh_public_keys(
2478 "Error message for YubiHsm2Config::new with two duplicate SSH public keys as authorized keys",
2479 BTreeSet::from_iter([
2480 Connection::Usb {serial_number: "0012345678".parse()? },
2481 Connection::Usb {serial_number: "0087654321".parse()? },
2482 ]),
2483 BTreeSet::from_iter([
2484 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2485 YubiHsm2UserMapping::Backup{
2486 authentication_key_id: "2".parse()?,
2487 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2488 system_user: "backup-user".parse()?,
2489 },
2490 YubiHsm2UserMapping::AuditLog {
2491 authentication_key_id: "3".parse()?,
2492 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2493 system_user: "metrics-user".parse()?,
2494 },
2495 YubiHsm2UserMapping::Signing {
2496 authentication_key_id: "4".parse()?,
2497 signing_key_id: "1".parse()?,
2498 key_setup: SigningKeySetup::new(
2499 KeyType::Curve25519,
2500 vec![KeyMechanism::EdDsaSignature],
2501 None,
2502 SignatureType::EdDsa,
2503 CryptographicKeyContext::OpenPgp {
2504 user_ids: OpenPgpUserIdList::new(vec![
2505 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2506 ])?,
2507 version: "v4".parse()?,
2508 notations: Default::default(),
2509 },
2510 )?,
2511 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2512 system_user: "signing-user".parse()?,
2513 domain: Domain::One,
2514 }
2515 ]),
2516 )]
2517 #[case::no_administrator(
2518 "Error message for YubiHsm2Config::new with no administrator",
2519 BTreeSet::from_iter([
2520 Connection::Usb {serial_number: "0012345678".parse()? },
2521 Connection::Usb {serial_number: "0087654321".parse()? },
2522 ]),
2523 BTreeSet::from_iter([
2524 YubiHsm2UserMapping::Backup{
2525 authentication_key_id: "2".parse()?,
2526 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2527 system_user: "backup-user".parse()?,
2528 },
2529 YubiHsm2UserMapping::AuditLog {
2530 authentication_key_id: "3".parse()?,
2531 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2532 system_user: "metrics-user".parse()?,
2533 },
2534 YubiHsm2UserMapping::Signing {
2535 authentication_key_id: "4".parse()?,
2536 signing_key_id: "1".parse()?,
2537 key_setup: SigningKeySetup::new(
2538 KeyType::Curve25519,
2539 vec![KeyMechanism::EdDsaSignature],
2540 None,
2541 SignatureType::EdDsa,
2542 CryptographicKeyContext::OpenPgp {
2543 user_ids: OpenPgpUserIdList::new(vec![
2544 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2545 ])?,
2546 version: "v4".parse()?,
2547 notations: Default::default(),
2548 },
2549 )?,
2550 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2551 system_user: "signing-user".parse()?,
2552 domain: Domain::One,
2553 }
2554 ]),
2555 )]
2556 #[case::duplicate_backend_user_ids(
2557 "Error message for YubiHsm2Config::new with two duplicate backend user IDs",
2558 BTreeSet::from_iter([
2559 Connection::Usb {serial_number: "0012345678".parse()? },
2560 Connection::Usb {serial_number: "0087654321".parse()? },
2561 ]),
2562 BTreeSet::from_iter([
2563 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2564 YubiHsm2UserMapping::Backup{
2565 authentication_key_id: "2".parse()?,
2566 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2567 system_user: "backup-user".parse()?,
2568 },
2569 YubiHsm2UserMapping::AuditLog {
2570 authentication_key_id: "3".parse()?,
2571 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2572 system_user: "metrics-user".parse()?,
2573 },
2574 YubiHsm2UserMapping::Signing {
2575 authentication_key_id: "3".parse()?,
2576 signing_key_id: "1".parse()?,
2577 key_setup: SigningKeySetup::new(
2578 KeyType::Curve25519,
2579 vec![KeyMechanism::EdDsaSignature],
2580 None,
2581 SignatureType::EdDsa,
2582 CryptographicKeyContext::OpenPgp {
2583 user_ids: OpenPgpUserIdList::new(vec![
2584 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2585 ])?,
2586 version: "v4".parse()?,
2587 notations: Default::default(),
2588 },
2589 )?,
2590 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2591 system_user: "signing-user".parse()?,
2592 domain: Domain::One,
2593 }
2594 ]),
2595 )]
2596 #[case::duplicate_signing_key_ids(
2597 "Error message for YubiHsm2Config::new with two duplicate signing key IDs",
2598 BTreeSet::from_iter([
2599 Connection::Usb {serial_number: "0012345678".parse()? },
2600 Connection::Usb {serial_number: "0087654321".parse()? },
2601 ]),
2602 BTreeSet::from_iter([
2603 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2604 YubiHsm2UserMapping::Backup{
2605 authentication_key_id: "2".parse()?,
2606 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2607 system_user: "backup-user".parse()?,
2608 },
2609 YubiHsm2UserMapping::AuditLog {
2610 authentication_key_id: "3".parse()?,
2611 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2612 system_user: "metrics-user".parse()?,
2613 },
2614 YubiHsm2UserMapping::Signing {
2615 authentication_key_id: "4".parse()?,
2616 signing_key_id: "1".parse()?,
2617 key_setup: SigningKeySetup::new(
2618 KeyType::Curve25519,
2619 vec![KeyMechanism::EdDsaSignature],
2620 None,
2621 SignatureType::EdDsa,
2622 CryptographicKeyContext::OpenPgp {
2623 user_ids: OpenPgpUserIdList::new(vec![
2624 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2625 ])?,
2626 version: "v4".parse()?,
2627 notations: Default::default(),
2628 },
2629 )?,
2630 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2631 system_user: "signing-user".parse()?,
2632 domain: Domain::One,
2633 },
2634 YubiHsm2UserMapping::Signing {
2635 authentication_key_id: "5".parse()?,
2636 signing_key_id: "1".parse()?,
2637 key_setup: SigningKeySetup::new(
2638 KeyType::Curve25519,
2639 vec![KeyMechanism::EdDsaSignature],
2640 None,
2641 SignatureType::EdDsa,
2642 CryptographicKeyContext::OpenPgp {
2643 user_ids: OpenPgpUserIdList::new(vec![
2644 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2645 ])?,
2646 version: "v4".parse()?,
2647 notations: Default::default(),
2648 },
2649 )?,
2650 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2651 system_user: "signing-user2".parse()?,
2652 domain: Domain::Two,
2653 },
2654 ]),
2655 )]
2656 #[case::duplicate_domains(
2657 "Error message for YubiHsm2Config::new with two duplicate domains",
2658 BTreeSet::from_iter([
2659 Connection::Usb {serial_number: "0012345678".parse()? },
2660 Connection::Usb {serial_number: "0087654321".parse()? },
2661 ]),
2662 BTreeSet::from_iter([
2663 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2664 YubiHsm2UserMapping::Backup{
2665 authentication_key_id: "2".parse()?,
2666 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2667 system_user: "backup-user".parse()?,
2668 },
2669 YubiHsm2UserMapping::AuditLog {
2670 authentication_key_id: "3".parse()?,
2671 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2672 system_user: "metrics-user".parse()?,
2673 },
2674 YubiHsm2UserMapping::Signing {
2675 authentication_key_id: "4".parse()?,
2676 signing_key_id: "1".parse()?,
2677 key_setup: SigningKeySetup::new(
2678 KeyType::Curve25519,
2679 vec![KeyMechanism::EdDsaSignature],
2680 None,
2681 SignatureType::EdDsa,
2682 CryptographicKeyContext::OpenPgp {
2683 user_ids: OpenPgpUserIdList::new(vec![
2684 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2685 ])?,
2686 version: "v4".parse()?,
2687 notations: Default::default(),
2688 },
2689 )?,
2690 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2691 system_user: "signing-user".parse()?,
2692 domain: Domain::One,
2693 },
2694 YubiHsm2UserMapping::Signing {
2695 authentication_key_id: "5".parse()?,
2696 signing_key_id: "2".parse()?,
2697 key_setup: SigningKeySetup::new(
2698 KeyType::Curve25519,
2699 vec![KeyMechanism::EdDsaSignature],
2700 None,
2701 SignatureType::EdDsa,
2702 CryptographicKeyContext::OpenPgp {
2703 user_ids: OpenPgpUserIdList::new(vec![
2704 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2705 ])?,
2706 version: "v4".parse()?,
2707 notations: Default::default(),
2708 },
2709 )?,
2710 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2711 system_user: "signing-user2".parse()?,
2712 domain: Domain::One,
2713 },
2714 ]),
2715 )]
2716 #[case::certificate_too_large_for_backend(
2717 "Error message for YubiHsm2Config::new with size estimation",
2718 BTreeSet::from_iter([
2719 Connection::Usb {serial_number: "0012345678".parse()? },
2720 ]),
2721 BTreeSet::from_iter([
2722 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2723 YubiHsm2UserMapping::Signing {
2724 authentication_key_id: "4".parse()?,
2725 signing_key_id: "1".parse()?,
2726 key_setup: SigningKeySetup::new(
2727 KeyType::Curve25519,
2728 vec![KeyMechanism::EdDsaSignature],
2729 None,
2730 SignatureType::EdDsa,
2731 CryptographicKeyContext::OpenPgp {
2732 user_ids: OpenPgpUserIdList::new(vec![
2733 "Foobar McFooface 1 <foobar@example.org>".parse()?,
2734 "Foobar McFooface 2 <foobar@example.org>".parse()?,
2735 "Foobar McFooface 3 <foobar@example.org>".parse()?,
2736 "Foobar McFooface 4 <foobar@example.org>".parse()?,
2737 "Foobar McFooface 5 <foobar@example.org>".parse()?,
2738 "Foobar McFooface 6 <foobar@example.org>".parse()?,
2739 "Foobar McFooface 7 <foobar@example.org>".parse()?,
2740 "Foobar McFooface 8 <foobar@example.org>".parse()?,
2741 "Foobar McFooface 9 <foobar@example.org>".parse()?,
2742 "Foobar McFooface 10 <foobar@example.org>".parse()?,
2743 "Foobar McFooface 11 <foobar@example.org>".parse()?,
2744 "Foobar McFooface 12 <foobar@example.org>".parse()?,
2745 "Foobar McFooface 13 <foobar@example.org>".parse()?,
2746 "Foobar McFooface 14 <foobar@example.org>".parse()?,
2747 "Foobar McFooface 15 <foobar@example.org>".parse()?,
2748 "Foobar McFooface 16 <foobar@example.org>".parse()?,
2749 "Foobar McFooface 17 <foobar@example.org>".parse()?,
2750 "Foobar McFooface 18 <foobar@example.org>".parse()?,
2751 "Foobar McFooface 19 <foobar@example.org>".parse()?,
2752 "Foobar McFooface 20 <foobar@example.org>".parse()?,
2753 ])?,
2754 version: "v4".parse()?,
2755 notations: Default::default()
2756 },
2757 )?,
2758 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2759 system_user: "signing-user".parse()?,
2760 domain: Domain::One,
2761 },
2762 ])
2763 )]
2764 #[case::all_the_issues(
2765 "Error message for YubiHsm2Config::new with multiple validation issues (connections and mappings)",
2766 BTreeSet::new(),
2767 BTreeSet::from_iter([
2768 YubiHsm2UserMapping::Backup{
2769 authentication_key_id: "2".parse()?,
2770 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2771 system_user: "backup-user".parse()?,
2772 },
2773 YubiHsm2UserMapping::Backup{
2774 authentication_key_id: "3".parse()?,
2775 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2776 system_user: "backup-user".parse()?,
2777 },
2778 YubiHsm2UserMapping::AuditLog {
2779 authentication_key_id: "3".parse()?,
2780 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2781 system_user: "metrics-backupuser".parse()?,
2782 },
2783 YubiHsm2UserMapping::Signing {
2784 authentication_key_id: "5".parse()?,
2785 signing_key_id: "1".parse()?,
2786 key_setup: SigningKeySetup::new(
2787 KeyType::Curve25519,
2788 vec![KeyMechanism::EdDsaSignature],
2789 None,
2790 SignatureType::EdDsa,
2791 CryptographicKeyContext::OpenPgp {
2792 user_ids: OpenPgpUserIdList::new(vec![
2793 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2794 ])?,
2795 version: "v4".parse()?,
2796 notations: Default::default(),
2797 },
2798 )?,
2799 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2800 system_user: "signing-user".parse()?,
2801 domain: Domain::One,
2802 },
2803 YubiHsm2UserMapping::Signing {
2804 authentication_key_id: "5".parse()?,
2805 signing_key_id: "1".parse()?,
2806 key_setup: SigningKeySetup::new(
2807 KeyType::Curve25519,
2808 vec![KeyMechanism::EdDsaSignature],
2809 None,
2810 SignatureType::EdDsa,
2811 CryptographicKeyContext::OpenPgp {
2812 user_ids: OpenPgpUserIdList::new(vec![
2813 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2814 ])?,
2815 version: "v4".parse()?,
2816 notations: Default::default(),
2817 },
2818 )?,
2819 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2820 system_user: "signing-user2".parse()?,
2821 domain: Domain::One,
2822 },
2823 YubiHsm2UserMapping::Signing {
2824 authentication_key_id: "4".parse()?,
2825 signing_key_id: "1".parse()?,
2826 key_setup: SigningKeySetup::new(
2827 KeyType::Curve25519,
2828 vec![KeyMechanism::EdDsaSignature],
2829 None,
2830 SignatureType::EdDsa,
2831 CryptographicKeyContext::OpenPgp {
2832 notations: Default::default(),
2833 user_ids: OpenPgpUserIdList::new(vec![
2834 "Foobar McFooface 1 <foobar@example.org>".parse()?,
2835 "Foobar McFooface 2 <foobar@example.org>".parse()?,
2836 "Foobar McFooface 3 <foobar@example.org>".parse()?,
2837 "Foobar McFooface 4 <foobar@example.org>".parse()?,
2838 "Foobar McFooface 5 <foobar@example.org>".parse()?,
2839 "Foobar McFooface 6 <foobar@example.org>".parse()?,
2840 "Foobar McFooface 7 <foobar@example.org>".parse()?,
2841 "Foobar McFooface 8 <foobar@example.org>".parse()?,
2842 "Foobar McFooface 9 <foobar@example.org>".parse()?,
2843 "Foobar McFooface 10 <foobar@example.org>".parse()?,
2844 "Foobar McFooface 11 <foobar@example.org>".parse()?,
2845 "Foobar McFooface 12 <foobar@example.org>".parse()?,
2846 "Foobar McFooface 13 <foobar@example.org>".parse()?,
2847 "Foobar McFooface 14 <foobar@example.org>".parse()?,
2848 "Foobar McFooface 15 <foobar@example.org>".parse()?,
2849 "Foobar McFooface 16 <foobar@example.org>".parse()?,
2850 "Foobar McFooface 17 <foobar@example.org>".parse()?,
2851 "Foobar McFooface 18 <foobar@example.org>".parse()?,
2852 "Foobar McFooface 19 <foobar@example.org>".parse()?,
2853 "Foobar McFooface 20 <foobar@example.org>".parse()?,
2854 ])?,
2855 version: "v4".parse()?,
2856 },
2857 )?,
2858 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2859 system_user: "signing-user".parse()?,
2860 domain: Domain::One,
2861 },
2862 ]),
2863 )]
2864 fn yubihsm2_config_new_fails_validation(
2865 #[case] description: &str,
2866 #[case] connections: BTreeSet<Connection>,
2867 #[case] mappings: BTreeSet<YubiHsm2UserMapping>,
2868 ) -> TestResult {
2869 let error_msg = match YubiHsm2Config::new(connections, mappings) {
2870 Err(crate::Error::Validation { source, .. }) => source.to_string(),
2871 Ok(config) => {
2872 panic!("Expected to fail with Error::Validation, but succeeded instead: {config:?}")
2873 }
2874 Err(error) => panic!(
2875 "Expected to fail with Error::Validation, but failed with a different error instead: {error}"
2876 ),
2877 };
2878
2879 with_settings!({
2880 description => description,
2881 snapshot_path => SNAPSHOT_PATH,
2882 prepend_module_to_snapshot => false,
2883 }, {
2884 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_msg);
2885 });
2886 Ok(())
2887 }
2888}