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