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