1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Metadata {
9 pub name: String,
11 #[serde(skip_serializing_if = "Option::is_none")]
13 pub description: Option<String>,
14}
15
16pub fn role_type(id: &str) -> &str {
20 id.split_once("__").map_or(id, |(prefix, _)| prefix)
21}
22
23pub fn derive_step_name(id: &str) -> String {
32 id.split(['_', '-'])
33 .map(|word| {
34 let mut chars = word.chars();
35 match chars.next() {
36 None => String::new(),
37 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
38 }
39 })
40 .collect::<Vec<_>>()
41 .join(" ")
42}
43
44pub fn derive_role_name(id: &str) -> String {
49 id.replace("__", " ")
50 .split(['_', '-', ' '])
51 .filter(|s| !s.is_empty())
52 .map(|word| {
53 let mut chars = word.chars();
54 match chars.next() {
55 None => String::new(),
56 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
57 }
58 })
59 .collect::<Vec<_>>()
60 .join(" ")
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66#[non_exhaustive]
67pub enum ActionType {
68 ClockCheck,
73 Confirm,
78 CheckValue,
83 OralReadback,
88 Reveal,
96 MachineInfo,
101 EnterValue,
109 EnterSecret,
119
120 GenerateKey,
123 WrapKey,
132 UnwrapKey,
134 EncryptData,
144 DecryptData,
150 SplitSecret,
161 CombineShares,
169 EnterShare,
178 ImportKey,
184 ExportPublic,
186 SignData,
191 VerifySignature,
198 Attest,
200 GatherEntropy,
207 TpmAttest,
211
212 PivReadCertificate,
217 PivSign,
222 YubikeyAttestSlot,
227
228 IssueCertificate,
234 GenerateCsr,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
249#[serde(rename_all = "snake_case")]
250#[non_exhaustive]
251pub enum BackendUsage {
252 Required,
254 SoftwareUnlessNamed,
258 Unused,
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
269#[serde(rename_all = "snake_case")]
270#[non_exhaustive]
271pub enum CertProfile {
272 RootCa,
274 SubCa,
276 TlsServer,
278 CodeSigning,
280 EndEntity,
282}
283
284impl fmt::Display for CertProfile {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match self {
287 CertProfile::RootCa => write!(f, "root_ca"),
288 CertProfile::SubCa => write!(f, "sub_ca"),
289 CertProfile::TlsServer => write!(f, "tls_server"),
290 CertProfile::CodeSigning => write!(f, "code_signing"),
291 CertProfile::EndEntity => write!(f, "end_entity"),
292 }
293 }
294}
295
296impl std::str::FromStr for CertProfile {
297 type Err = UnknownCertProfile;
298
299 fn from_str(s: &str) -> Result<Self, Self::Err> {
300 match s {
301 "root_ca" => Ok(Self::RootCa),
302 "sub_ca" | "intermediate_ca" => Ok(Self::SubCa),
305 "tls_server" => Ok(Self::TlsServer),
306 "code_signing" => Ok(Self::CodeSigning),
307 "end_entity" => Ok(Self::EndEntity),
308 _ => Err(UnknownCertProfile),
309 }
310 }
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub struct UnknownCertProfile;
316
317impl fmt::Display for UnknownCertProfile {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 write!(
320 f,
321 "supported profiles: root_ca, sub_ca, tls_server, code_signing, end_entity"
322 )
323 }
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
334#[serde(into = "String", try_from = "String")]
335#[non_exhaustive]
336pub enum SharingScheme {
337 RiteSssV1,
340}
341
342impl SharingScheme {
343 pub fn as_str(self) -> &'static str {
345 match self {
346 SharingScheme::RiteSssV1 => "rite-sss/v1",
347 }
348 }
349
350 pub fn max_shares(self) -> u8 {
360 match self {
361 SharingScheme::RiteSssV1 => 100,
362 }
363 }
364
365 #[must_use]
368 pub fn header_len(self) -> usize {
369 match self {
370 SharingScheme::RiteSssV1 => 3,
371 }
372 }
373
374 #[must_use]
378 pub fn share_len(self, secret_len: usize) -> usize {
379 match self {
380 SharingScheme::RiteSssV1 => secret_len.saturating_add(self.header_len()),
381 }
382 }
383}
384
385impl fmt::Display for SharingScheme {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 f.write_str(self.as_str())
388 }
389}
390
391impl From<SharingScheme> for String {
392 fn from(scheme: SharingScheme) -> Self {
393 scheme.as_str().to_string()
394 }
395}
396
397impl std::str::FromStr for SharingScheme {
398 type Err = UnknownSharingScheme;
399
400 fn from_str(s: &str) -> Result<Self, Self::Err> {
401 match s {
402 "rite-sss/v1" => Ok(Self::RiteSssV1),
403 _ => Err(UnknownSharingScheme),
404 }
405 }
406}
407
408impl TryFrom<String> for SharingScheme {
409 type Error = UnknownSharingScheme;
410
411 fn try_from(s: String) -> Result<Self, Self::Error> {
412 s.parse()
413 }
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub struct UnknownSharingScheme;
419
420impl fmt::Display for UnknownSharingScheme {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 write!(f, "supported sharing schemes: rite-sss/v1")
423 }
424}
425
426impl std::error::Error for UnknownSharingScheme {}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
432#[non_exhaustive]
433pub struct ReadsContract {
434 pub required: &'static [&'static str],
436 pub exactly_one_of: &'static [&'static [&'static str]],
439 pub optional: &'static [&'static str],
445 pub with_field_requires: &'static [(&'static str, &'static str)],
453 pub lists: &'static [ListInput],
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465pub struct ListInput {
466 pub name: &'static str,
468 pub at_least: usize,
470}
471
472impl ReadsContract {
473 pub const NONE: Self = Self::required(&[]);
475
476 #[must_use]
479 pub const fn required(fields: &'static [&'static str]) -> Self {
480 Self {
481 required: fields,
482 exactly_one_of: &[],
483 optional: &[],
484 with_field_requires: &[],
485 lists: &[],
486 }
487 }
488
489 pub fn is_list(&self, key: &str) -> bool {
491 self.lists.iter().any(|list| list.name == key)
492 }
493
494 pub fn is_empty(&self) -> bool {
498 self.required.is_empty()
499 && self.exactly_one_of.is_empty()
500 && self.optional.is_empty()
501 && self.lists.is_empty()
502 }
503
504 pub fn accepts(&self, key: &str) -> bool {
506 self.required.contains(&key)
507 || self.optional.contains(&key)
508 || self.exactly_one_of.iter().any(|group| group.contains(&key))
509 || self.lists.iter().any(|list| list.name == key)
510 }
511}
512
513impl ActionType {
514 pub const ALL: &'static [ActionType] = &[
521 ActionType::ClockCheck,
522 ActionType::Confirm,
523 ActionType::CheckValue,
524 ActionType::OralReadback,
525 ActionType::Reveal,
526 ActionType::MachineInfo,
527 ActionType::EnterValue,
528 ActionType::EnterSecret,
529 ActionType::GenerateKey,
530 ActionType::WrapKey,
531 ActionType::UnwrapKey,
532 ActionType::ImportKey,
533 ActionType::EncryptData,
534 ActionType::DecryptData,
535 ActionType::SplitSecret,
536 ActionType::CombineShares,
537 ActionType::EnterShare,
538 ActionType::ExportPublic,
539 ActionType::SignData,
540 ActionType::VerifySignature,
541 ActionType::Attest,
542 ActionType::GatherEntropy,
543 ActionType::TpmAttest,
544 ActionType::PivReadCertificate,
545 ActionType::PivSign,
546 ActionType::YubikeyAttestSlot,
547 ActionType::IssueCertificate,
548 ActionType::GenerateCsr,
549 ];
550
551 pub fn backend_usage(self) -> BackendUsage {
557 match self {
558 ActionType::GenerateKey
559 | ActionType::SignData
560 | ActionType::WrapKey
561 | ActionType::UnwrapKey
562 | ActionType::ExportPublic
563 | ActionType::GenerateCsr
564 | ActionType::IssueCertificate
565 | ActionType::PivReadCertificate
566 | ActionType::PivSign
567 | ActionType::YubikeyAttestSlot
568 | ActionType::ImportKey
569 | ActionType::EncryptData
570 | ActionType::DecryptData
571 | ActionType::SplitSecret
572 | ActionType::TpmAttest => BackendUsage::Required,
573
574 ActionType::VerifySignature => BackendUsage::SoftwareUnlessNamed,
575
576 ActionType::ClockCheck
577 | ActionType::Confirm
578 | ActionType::CheckValue
579 | ActionType::OralReadback
580 | ActionType::Reveal
581 | ActionType::MachineInfo
582 | ActionType::EnterValue
583 | ActionType::EnterSecret
584 | ActionType::Attest
585 | ActionType::CombineShares
586 | ActionType::EnterShare
587 | ActionType::GatherEntropy => BackendUsage::Unused,
588 }
589 }
590
591 pub fn creates_shares(self) -> bool {
595 match self {
596 ActionType::SplitSecret | ActionType::EnterShare => true,
597 ActionType::ClockCheck
598 | ActionType::Confirm
599 | ActionType::CheckValue
600 | ActionType::OralReadback
601 | ActionType::Reveal
602 | ActionType::MachineInfo
603 | ActionType::EnterValue
604 | ActionType::EnterSecret
605 | ActionType::GenerateKey
606 | ActionType::WrapKey
607 | ActionType::UnwrapKey
608 | ActionType::ImportKey
609 | ActionType::EncryptData
610 | ActionType::DecryptData
611 | ActionType::CombineShares
612 | ActionType::ExportPublic
613 | ActionType::SignData
614 | ActionType::VerifySignature
615 | ActionType::GenerateCsr
616 | ActionType::IssueCertificate
617 | ActionType::PivReadCertificate
618 | ActionType::PivSign
619 | ActionType::YubikeyAttestSlot
620 | ActionType::Attest
621 | ActionType::GatherEntropy
622 | ActionType::TpmAttest => false,
623 }
624 }
625
626 pub fn required_with_fields(self) -> &'static [&'static str] {
635 match self {
636 ActionType::CheckValue => &["actual", "expected"],
637 ActionType::GenerateCsr => &["subject"],
638 ActionType::ImportKey => &["algorithm"],
641 ActionType::SplitSecret => &["threshold", "shares"],
642 ActionType::EnterValue
645 | ActionType::EnterSecret
646 | ActionType::Reveal
647 | ActionType::EnterShare => &["message"],
648
649 ActionType::ClockCheck
650 | ActionType::Confirm
651 | ActionType::OralReadback
652 | ActionType::MachineInfo
653 | ActionType::GenerateKey
654 | ActionType::WrapKey
655 | ActionType::UnwrapKey
656 | ActionType::ExportPublic
657 | ActionType::SignData
658 | ActionType::VerifySignature
659 | ActionType::Attest
660 | ActionType::GatherEntropy
661 | ActionType::TpmAttest
662 | ActionType::PivReadCertificate
663 | ActionType::PivSign
664 | ActionType::YubikeyAttestSlot
665 | ActionType::EncryptData
666 | ActionType::DecryptData
667 | ActionType::CombineShares
668 | ActionType::IssueCertificate => &[],
669 }
670 }
671
672 pub fn known_with_fields(self) -> &'static [&'static str] {
686 match self {
687 ActionType::ClockCheck | ActionType::Confirm => &["message"],
688 ActionType::CheckValue => &["actual", "expected", "message", "sensitive"],
689 ActionType::OralReadback => &["value", "format", "characters", "message"],
690 ActionType::Reveal | ActionType::EnterShare => &["message", "format", "note", "length"],
691 ActionType::MachineInfo => &[
692 "include_machine_id",
693 "include_cpu",
694 "include_os",
695 "include_security_features",
696 "message",
697 ],
698 ActionType::EnterValue | ActionType::EnterSecret => {
699 &["message", "format", "length", "min_length", "max_length"]
700 }
701 ActionType::GenerateKey => &["algorithm", "policy", "slot"],
702 ActionType::WrapKey => &["scheme", "expect_recipient"],
703 ActionType::UnwrapKey | ActionType::ImportKey => {
704 &["algorithm", "expect_key", "label", "policy"]
705 }
706 ActionType::EncryptData => &["scheme"],
707 ActionType::SplitSecret => &["scheme", "threshold", "shares"],
708 ActionType::SignData | ActionType::VerifySignature => &["algorithm", "message"],
709 ActionType::Attest => &["statement"],
710 ActionType::GatherEntropy => &["instruction"],
711 ActionType::IssueCertificate => &["profile", "validity_days", "issuer_cn", "path_len"],
712 ActionType::GenerateCsr => &["subject", "san"],
713 ActionType::PivReadCertificate | ActionType::YubikeyAttestSlot => &["slot", "message"],
714 ActionType::PivSign => &["slot", "algorithm", "message"],
715 ActionType::ExportPublic
720 | ActionType::DecryptData
721 | ActionType::CombineShares
722 | ActionType::TpmAttest => &[],
723 }
724 }
725
726 pub fn reads_contract(self) -> ReadsContract {
734 match self {
735 ActionType::WrapKey => ReadsContract {
736 required: &["key_to_wrap"],
737 exactly_one_of: &[&["wrapping_key", "recipient"]],
738 optional: &[],
739 with_field_requires: &[("expect_recipient", "recipient")],
742 lists: &[],
743 },
744 ActionType::UnwrapKey => ReadsContract::required(&["unwrapping_key", "wrapped_data"]),
745 ActionType::ImportKey => ReadsContract {
746 required: &["key_material"],
747 exactly_one_of: &[],
748 optional: &["passphrase"],
752 with_field_requires: &[],
753 lists: &[],
754 },
755 ActionType::EncryptData => ReadsContract::required(&["data", "encryption_key"]),
756 ActionType::DecryptData => {
757 ReadsContract::required(&["encrypted_data", "decryption_key"])
758 }
759 ActionType::SplitSecret => ReadsContract::required(&["secret"]),
760 ActionType::CombineShares => ReadsContract {
763 required: &[],
764 exactly_one_of: &[],
765 optional: &[],
766 with_field_requires: &[],
767 lists: &[ListInput {
768 name: "shares",
769 at_least: 2,
770 }],
771 },
772 ActionType::SignData => ReadsContract::required(&["key", "data"]),
773 ActionType::VerifySignature => ReadsContract::required(&["key", "data", "signature"]),
774 ActionType::IssueCertificate => ReadsContract {
778 required: &["signing_key", "csr"],
779 exactly_one_of: &[],
780 optional: &["issuer_cert"],
781 with_field_requires: &[],
782 lists: &[],
783 },
784 ActionType::GenerateCsr => ReadsContract::required(&["signing_key"]),
785 ActionType::Reveal => ReadsContract::required(&["value"]),
786
787 ActionType::ClockCheck
788 | ActionType::Confirm
789 | ActionType::CheckValue
790 | ActionType::OralReadback
791 | ActionType::MachineInfo
792 | ActionType::EnterValue
793 | ActionType::EnterSecret
794 | ActionType::GenerateKey
795 | ActionType::ExportPublic
796 | ActionType::Attest
797 | ActionType::GatherEntropy
798 | ActionType::TpmAttest
799 | ActionType::PivReadCertificate
800 | ActionType::PivSign
801 | ActionType::YubikeyAttestSlot
802 | ActionType::EnterShare => ReadsContract::NONE,
803 }
804 }
805
806 pub fn describe(&self) -> &'static str {
811 match self {
812 ActionType::ClockCheck => "Verify system clock against a reference time.",
813 ActionType::Confirm => "Confirm readiness or completion of a manual step.",
814 ActionType::CheckValue => "Verify a value matches an expected result.",
815 ActionType::OralReadback => "Read back a value aloud for verification.",
816 ActionType::Reveal => "Show a value for a person to write down.",
817 ActionType::MachineInfo => "Record system and environment information.",
818 ActionType::EnterValue => "Type a value the ceremony records.",
819 ActionType::EnterSecret => "Type a secret the ceremony holds and never records.",
820 ActionType::Attest => "Record a signed attestation from a participant.",
821 ActionType::GatherEntropy => "Fold human-supplied entropy into the ceremony seed.",
822 ActionType::TpmAttest => "Record TPM platform attestation (PCR values).",
823 ActionType::GenerateKey => "Generate a cryptographic key.",
824 ActionType::ExportPublic => "Export the public component of a keypair.",
825 ActionType::SignData => "Sign data with a ceremony key.",
826 ActionType::VerifySignature => "Verify a signature against a public key.",
827 ActionType::WrapKey => "Encrypt a key under another key for transport.",
828 ActionType::UnwrapKey => "Decrypt a wrapped key and import it into a backend.",
829 ActionType::GenerateCsr => "Generate a Certificate Signing Request.",
830 ActionType::IssueCertificate => "Issue an X.509 certificate from a CSR.",
831 ActionType::PivReadCertificate => "Read a certificate from a PIV smart card slot.",
832 ActionType::PivSign => "Sign data using a PIV smart card key.",
833 ActionType::YubikeyAttestSlot => "Attest a YubiKey PIV slot key.",
834 ActionType::ImportKey => "Import a key from material the ceremony holds.",
835 ActionType::EncryptData => "Encrypt content under a key held by a backend.",
836 ActionType::DecryptData => "Decrypt an encrypted-data artifact back to bytes.",
837 ActionType::SplitSecret => {
838 "Split a secret into shares a threshold of which reconstruct it."
839 }
840 ActionType::CombineShares => "Reconstruct a secret from its shares.",
841 ActionType::EnterShare => "Type a share back from its sheet.",
842 }
843 }
844}
845
846impl std::fmt::Display for ActionType {
847 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
848 match self {
849 ActionType::ClockCheck => write!(f, "clock_check"),
850 ActionType::Confirm => write!(f, "confirm"),
851 ActionType::CheckValue => write!(f, "check_value"),
852 ActionType::OralReadback => write!(f, "oral_readback"),
853 ActionType::Reveal => write!(f, "reveal"),
854 ActionType::MachineInfo => write!(f, "machine_info"),
855 ActionType::EnterValue => write!(f, "enter_value"),
856 ActionType::EnterSecret => write!(f, "enter_secret"),
857 ActionType::GenerateKey => write!(f, "generate_key"),
858 ActionType::WrapKey => write!(f, "wrap_key"),
859 ActionType::UnwrapKey => write!(f, "unwrap_key"),
860 ActionType::ImportKey => write!(f, "import_key"),
861 ActionType::EncryptData => write!(f, "encrypt_data"),
862 ActionType::DecryptData => write!(f, "decrypt_data"),
863 ActionType::SplitSecret => write!(f, "split_secret"),
864 ActionType::CombineShares => write!(f, "combine_shares"),
865 ActionType::EnterShare => write!(f, "enter_share"),
866 ActionType::ExportPublic => write!(f, "export_public"),
867 ActionType::SignData => write!(f, "sign_data"),
868 ActionType::VerifySignature => write!(f, "verify_signature"),
869 ActionType::Attest => write!(f, "attest"),
870 ActionType::GatherEntropy => write!(f, "gather_entropy"),
871 ActionType::TpmAttest => write!(f, "tpm_attest"),
872 ActionType::PivReadCertificate => write!(f, "piv_read_certificate"),
873 ActionType::PivSign => write!(f, "piv_sign"),
874 ActionType::YubikeyAttestSlot => write!(f, "yubikey_attest_slot"),
875 ActionType::IssueCertificate => write!(f, "issue_certificate"),
876 ActionType::GenerateCsr => write!(f, "generate_csr"),
877 }
878 }
879}
880
881#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
883#[serde(rename_all = "snake_case")]
884#[non_exhaustive]
885pub enum OutputType {
886 PublicKey,
888 WrappedKey,
890 EncryptedData,
892 Certificate,
894 SignedRrset,
896 Sct,
898
899 Document,
902 CeremonyLog,
904}
905
906impl std::fmt::Display for OutputType {
907 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
908 match self {
909 OutputType::PublicKey => write!(f, "public_key"),
910 OutputType::WrappedKey => write!(f, "wrapped_key"),
911 OutputType::EncryptedData => write!(f, "encrypted_data"),
912 OutputType::Certificate => write!(f, "certificate"),
913 OutputType::SignedRrset => write!(f, "signed_rrset"),
914 OutputType::Sct => write!(f, "sct"),
915 OutputType::Document => write!(f, "document"),
916 OutputType::CeremonyLog => write!(f, "ceremony_log"),
917 }
918 }
919}
920
921impl OutputType {
922 pub fn default_extension(&self) -> &'static str {
924 match self {
925 OutputType::PublicKey | OutputType::Certificate => "pem",
926 OutputType::WrappedKey
927 | OutputType::EncryptedData
928 | OutputType::SignedRrset
929 | OutputType::Sct => "bin",
930 OutputType::Document => "txt",
931 OutputType::CeremonyLog => "json",
932 }
933 }
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize)]
938#[serde(rename_all = "snake_case")]
939#[non_exhaustive]
940pub enum ParameterType {
941 String,
943 Date,
945 Integer,
947 Boolean,
949}
950
951impl std::fmt::Display for ParameterType {
952 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
953 match self {
954 ParameterType::String => write!(f, "string"),
955 ParameterType::Date => write!(f, "date (YYYY-MM-DD)"),
956 ParameterType::Integer => write!(f, "integer"),
957 ParameterType::Boolean => write!(f, "boolean (true/false)"),
958 }
959 }
960}
961
962#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
970#[serde(rename_all = "snake_case")]
971#[non_exhaustive]
972pub enum DutyType {
973 ReturnToVault,
975 DistributeShares,
977 DistributeMedia,
979 ArchiveMaterials,
981 PublishRecord,
983 NotifyStakeholders,
985 ImportKeys,
987 Custom,
989}
990
991impl DutyType {
992 pub fn display_name(&self) -> &'static str {
994 match self {
995 DutyType::ReturnToVault => "Return to Vault",
996 DutyType::DistributeShares => "Distribute Key Shares",
997 DutyType::DistributeMedia => "Distribute Backup Media",
998 DutyType::ArchiveMaterials => "Archive Materials",
999 DutyType::PublishRecord => "Publish Record",
1000 DutyType::NotifyStakeholders => "Notify Stakeholders",
1001 DutyType::ImportKeys => "Import Keys",
1002 DutyType::Custom => "Custom Duty",
1003 }
1004 }
1005
1006 pub fn built_in_prose(&self) -> Option<&'static str> {
1010 match self {
1011 DutyType::ReturnToVault => Some("Return materials to secure storage"),
1012 DutyType::DistributeShares => Some("Distribute key shares to assigned custodians"),
1013 DutyType::DistributeMedia => Some("Distribute backup media to designated recipients"),
1014 DutyType::ArchiveMaterials => {
1015 Some("Archive ceremony materials at designated storage locations")
1016 }
1017 DutyType::PublishRecord => Some("Publish ceremony record and witness attestations"),
1018 DutyType::NotifyStakeholders => Some("Notify stakeholders of ceremony completion"),
1019 DutyType::ImportKeys => Some("Import generated keys into operational system"),
1020 DutyType::Custom => None,
1021 }
1022 }
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028
1029 #[test]
1030 fn role_type_extracts_prefix() {
1031 assert_eq!(role_type("witness__1"), "witness");
1032 assert_eq!(role_type("hsm_operator__primary"), "hsm_operator");
1033 assert_eq!(role_type("operator"), "operator");
1034 assert_eq!(role_type("witness__"), "witness");
1035 }
1036
1037 #[test]
1038 fn derive_step_name_title_cases() {
1039 assert_eq!(derive_step_name("verify_time"), "Verify Time");
1040 assert_eq!(derive_step_name("generate_root_ca"), "Generate Root Ca");
1041 assert_eq!(derive_step_name("wrap_root_ca_key"), "Wrap Root Ca Key");
1042 assert_eq!(derive_step_name("witness1_attest"), "Witness1 Attest");
1043 assert_eq!(derive_step_name("clock-check"), "Clock Check");
1044 assert_eq!(derive_step_name("opening"), "Opening");
1045 }
1046
1047 #[test]
1048 fn derive_role_name_title_cases() {
1049 assert_eq!(derive_role_name("witness__1"), "Witness 1");
1050 assert_eq!(
1051 derive_role_name("hsm_operator__primary"),
1052 "Hsm Operator Primary"
1053 );
1054 assert_eq!(derive_role_name("operator"), "Operator");
1055 assert_eq!(derive_role_name("ceremony-admin"), "Ceremony Admin");
1056 }
1057
1058 #[test]
1059 fn action_type_serde_roundtrip() {
1060 let cases: &[(ActionType, &str)] = &[
1063 (ActionType::ClockCheck, "\"clock_check\""),
1064 (ActionType::Confirm, "\"confirm\""),
1065 (ActionType::CheckValue, "\"check_value\""),
1066 (ActionType::OralReadback, "\"oral_readback\""),
1067 (ActionType::Reveal, "\"reveal\""),
1068 (ActionType::MachineInfo, "\"machine_info\""),
1069 (ActionType::GenerateKey, "\"generate_key\""),
1070 (ActionType::WrapKey, "\"wrap_key\""),
1071 (ActionType::UnwrapKey, "\"unwrap_key\""),
1072 (ActionType::ImportKey, "\"import_key\""),
1073 (ActionType::EncryptData, "\"encrypt_data\""),
1074 (ActionType::DecryptData, "\"decrypt_data\""),
1075 (ActionType::SplitSecret, "\"split_secret\""),
1076 (ActionType::CombineShares, "\"combine_shares\""),
1077 (ActionType::EnterShare, "\"enter_share\""),
1078 (ActionType::ExportPublic, "\"export_public\""),
1079 (ActionType::Attest, "\"attest\""),
1080 (ActionType::TpmAttest, "\"tpm_attest\""),
1081 (ActionType::PivReadCertificate, "\"piv_read_certificate\""),
1082 (ActionType::PivSign, "\"piv_sign\""),
1083 (ActionType::YubikeyAttestSlot, "\"yubikey_attest_slot\""),
1084 (ActionType::IssueCertificate, "\"issue_certificate\""),
1085 (ActionType::GenerateCsr, "\"generate_csr\""),
1086 ];
1087 for &(variant, expected) in cases {
1088 let serialized = serde_json::to_string(&variant).unwrap();
1089 assert_eq!(serialized, expected, "serialize {variant:?}");
1090 let deserialized: ActionType = serde_json::from_str(expected).unwrap();
1091 assert_eq!(deserialized, variant, "deserialize {expected}");
1092 }
1093 }
1094
1095 #[test]
1096 fn action_type_display_matches_serde() {
1097 let actions = [
1100 ActionType::ClockCheck,
1101 ActionType::Confirm,
1102 ActionType::CheckValue,
1103 ActionType::OralReadback,
1104 ActionType::Reveal,
1105 ActionType::MachineInfo,
1106 ActionType::GenerateKey,
1107 ActionType::WrapKey,
1108 ActionType::UnwrapKey,
1109 ActionType::ImportKey,
1110 ActionType::ExportPublic,
1111 ActionType::Attest,
1112 ActionType::TpmAttest,
1113 ActionType::PivReadCertificate,
1114 ActionType::PivSign,
1115 ActionType::YubikeyAttestSlot,
1116 ActionType::IssueCertificate,
1117 ActionType::GenerateCsr,
1118 ];
1119 for action in actions {
1120 let display = action.to_string();
1121 let serde_json = serde_json::to_string(&action).unwrap();
1122 assert_eq!(
1123 display,
1124 serde_json.trim_matches('"'),
1125 "Display and serde disagree for {action:?}"
1126 );
1127 }
1128 }
1129
1130 #[test]
1131 fn output_type_serde_roundtrip() {
1132 let cases: &[(OutputType, &str)] = &[
1135 (OutputType::PublicKey, "\"public_key\""),
1136 (OutputType::WrappedKey, "\"wrapped_key\""),
1137 (OutputType::EncryptedData, "\"encrypted_data\""),
1138 (OutputType::Certificate, "\"certificate\""),
1139 (OutputType::Document, "\"document\""),
1140 (OutputType::CeremonyLog, "\"ceremony_log\""),
1141 ];
1142 for &(variant, expected) in cases {
1143 let serialized = serde_json::to_string(&variant).unwrap();
1144 assert_eq!(serialized, expected, "serialize {variant:?}");
1145 let deserialized: OutputType = serde_json::from_str(expected).unwrap();
1146 assert_eq!(deserialized, variant, "deserialize {expected}");
1147 }
1148 }
1149
1150 #[test]
1151 fn parameter_type_serde_roundtrip() {
1152 let cases: &[(ParameterType, &str)] = &[
1154 (ParameterType::String, "\"string\""),
1155 (ParameterType::Date, "\"date\""),
1156 (ParameterType::Integer, "\"integer\""),
1157 (ParameterType::Boolean, "\"boolean\""),
1158 ];
1159 for (variant, expected) in cases {
1160 let serialized = serde_json::to_string(variant).unwrap();
1161 assert_eq!(serialized, *expected, "serialize {variant:?}");
1162 let deserialized: ParameterType = serde_json::from_str(expected).unwrap();
1163 assert_eq!(
1165 serde_json::to_string(&deserialized).unwrap(),
1166 *expected,
1167 "deserialize {expected}"
1168 );
1169 }
1170 }
1171
1172 #[test]
1173 fn duty_type_serde_roundtrip() {
1174 let cases: &[(DutyType, &str)] = &[
1176 (DutyType::ReturnToVault, "\"return_to_vault\""),
1177 (DutyType::DistributeShares, "\"distribute_shares\""),
1178 (DutyType::DistributeMedia, "\"distribute_media\""),
1179 (DutyType::ArchiveMaterials, "\"archive_materials\""),
1180 (DutyType::PublishRecord, "\"publish_record\""),
1181 (DutyType::NotifyStakeholders, "\"notify_stakeholders\""),
1182 (DutyType::ImportKeys, "\"import_keys\""),
1183 (DutyType::Custom, "\"custom\""),
1184 ];
1185 for (variant, expected) in cases {
1186 let serialized = serde_json::to_string(variant).unwrap();
1187 assert_eq!(serialized, *expected, "serialize {variant:?}");
1188 let deserialized: DutyType = serde_json::from_str(expected).unwrap();
1189 assert_eq!(deserialized, *variant, "deserialize {expected}");
1190 }
1191 }
1192
1193 #[test]
1194 fn duty_type_custom_has_no_built_in_prose() {
1195 assert!(
1198 DutyType::Custom.built_in_prose().is_none(),
1199 "Custom duty must have no built-in prose"
1200 );
1201 for duty in [
1203 DutyType::ReturnToVault,
1204 DutyType::DistributeShares,
1205 DutyType::DistributeMedia,
1206 DutyType::ArchiveMaterials,
1207 DutyType::PublishRecord,
1208 DutyType::NotifyStakeholders,
1209 DutyType::ImportKeys,
1210 ] {
1211 assert!(
1212 duty.built_in_prose().is_some(),
1213 "{duty:?} must have built-in prose"
1214 );
1215 }
1216 }
1217
1218 #[test]
1229 fn every_required_field_is_also_a_known_field() {
1230 for action in ActionType::ALL {
1231 let known = action.known_with_fields();
1232 for field in action.required_with_fields() {
1233 assert!(
1234 known.contains(field),
1235 "{action} requires '{field}' and does not accept it"
1236 );
1237 }
1238 }
1239 }
1240
1241 #[test]
1242 fn all_lists_every_action_type() {
1243 for action in ActionType::ALL {
1244 match action {
1245 ActionType::ClockCheck
1246 | ActionType::Confirm
1247 | ActionType::CheckValue
1248 | ActionType::OralReadback
1249 | ActionType::Reveal
1250 | ActionType::MachineInfo
1251 | ActionType::EnterValue
1252 | ActionType::EnterSecret
1253 | ActionType::GenerateKey
1254 | ActionType::WrapKey
1255 | ActionType::UnwrapKey
1256 | ActionType::ImportKey
1257 | ActionType::EncryptData
1258 | ActionType::DecryptData
1259 | ActionType::SplitSecret
1260 | ActionType::CombineShares
1261 | ActionType::EnterShare
1262 | ActionType::ExportPublic
1263 | ActionType::SignData
1264 | ActionType::VerifySignature
1265 | ActionType::Attest
1266 | ActionType::GatherEntropy
1267 | ActionType::TpmAttest
1268 | ActionType::PivReadCertificate
1269 | ActionType::PivSign
1270 | ActionType::YubikeyAttestSlot
1271 | ActionType::IssueCertificate
1272 | ActionType::GenerateCsr => {}
1273 }
1274 }
1275
1276 let names: std::collections::BTreeSet<String> =
1277 ActionType::ALL.iter().map(ToString::to_string).collect();
1278 assert_eq!(
1279 names.len(),
1280 ActionType::ALL.len(),
1281 "two actions share a DSL name"
1282 );
1283 }
1284}