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 MachineInfo,
93 EnterValue,
101 EnterSecret,
111
112 GenerateKey,
115 WrapKey,
124 UnwrapKey,
126 EncryptData,
136 DecryptData,
142 SplitSecret,
153 CombineShares,
161 ImportKey,
167 ExportPublic,
169 SignData,
174 VerifySignature,
181 Attest,
183 GatherEntropy,
190 TpmAttest,
194
195 PivReadCertificate,
200 PivSign,
205 YubikeyAttestSlot,
210
211 IssueCertificate,
217 GenerateCsr,
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233#[non_exhaustive]
234pub enum BackendUsage {
235 Required,
237 SoftwareUnlessNamed,
241 Unused,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "snake_case")]
253#[non_exhaustive]
254pub enum CertProfile {
255 RootCa,
257 SubCa,
259 TlsServer,
261 CodeSigning,
263 EndEntity,
265}
266
267impl fmt::Display for CertProfile {
268 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269 match self {
270 CertProfile::RootCa => write!(f, "root_ca"),
271 CertProfile::SubCa => write!(f, "sub_ca"),
272 CertProfile::TlsServer => write!(f, "tls_server"),
273 CertProfile::CodeSigning => write!(f, "code_signing"),
274 CertProfile::EndEntity => write!(f, "end_entity"),
275 }
276 }
277}
278
279impl std::str::FromStr for CertProfile {
280 type Err = UnknownCertProfile;
281
282 fn from_str(s: &str) -> Result<Self, Self::Err> {
283 match s {
284 "root_ca" => Ok(Self::RootCa),
285 "sub_ca" | "intermediate_ca" => Ok(Self::SubCa),
288 "tls_server" => Ok(Self::TlsServer),
289 "code_signing" => Ok(Self::CodeSigning),
290 "end_entity" => Ok(Self::EndEntity),
291 _ => Err(UnknownCertProfile),
292 }
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct UnknownCertProfile;
299
300impl fmt::Display for UnknownCertProfile {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(
303 f,
304 "supported profiles: root_ca, sub_ca, tls_server, code_signing, end_entity"
305 )
306 }
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
317#[serde(into = "String", try_from = "String")]
318#[non_exhaustive]
319pub enum SharingScheme {
320 RiteSssV1,
323}
324
325impl SharingScheme {
326 pub fn as_str(self) -> &'static str {
328 match self {
329 SharingScheme::RiteSssV1 => "rite-sss/v1",
330 }
331 }
332
333 pub fn max_shares(self) -> u8 {
343 match self {
344 SharingScheme::RiteSssV1 => 100,
345 }
346 }
347}
348
349impl fmt::Display for SharingScheme {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 f.write_str(self.as_str())
352 }
353}
354
355impl From<SharingScheme> for String {
356 fn from(scheme: SharingScheme) -> Self {
357 scheme.as_str().to_string()
358 }
359}
360
361impl std::str::FromStr for SharingScheme {
362 type Err = UnknownSharingScheme;
363
364 fn from_str(s: &str) -> Result<Self, Self::Err> {
365 match s {
366 "rite-sss/v1" => Ok(Self::RiteSssV1),
367 _ => Err(UnknownSharingScheme),
368 }
369 }
370}
371
372impl TryFrom<String> for SharingScheme {
373 type Error = UnknownSharingScheme;
374
375 fn try_from(s: String) -> Result<Self, Self::Error> {
376 s.parse()
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub struct UnknownSharingScheme;
383
384impl fmt::Display for UnknownSharingScheme {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 write!(f, "supported sharing schemes: rite-sss/v1")
387 }
388}
389
390impl std::error::Error for UnknownSharingScheme {}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
396#[non_exhaustive]
397pub struct ReadsContract {
398 pub required: &'static [&'static str],
400 pub exactly_one_of: &'static [&'static [&'static str]],
403 pub optional: &'static [&'static str],
409 pub with_field_requires: &'static [(&'static str, &'static str)],
417 pub lists: &'static [ListInput],
425}
426
427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
429pub struct ListInput {
430 pub name: &'static str,
432 pub at_least: usize,
434}
435
436impl ReadsContract {
437 pub const NONE: Self = Self::required(&[]);
439
440 #[must_use]
443 pub const fn required(fields: &'static [&'static str]) -> Self {
444 Self {
445 required: fields,
446 exactly_one_of: &[],
447 optional: &[],
448 with_field_requires: &[],
449 lists: &[],
450 }
451 }
452
453 pub fn is_list(&self, key: &str) -> bool {
455 self.lists.iter().any(|list| list.name == key)
456 }
457
458 pub fn is_empty(&self) -> bool {
462 self.required.is_empty()
463 && self.exactly_one_of.is_empty()
464 && self.optional.is_empty()
465 && self.lists.is_empty()
466 }
467
468 pub fn accepts(&self, key: &str) -> bool {
470 self.required.contains(&key)
471 || self.optional.contains(&key)
472 || self.exactly_one_of.iter().any(|group| group.contains(&key))
473 || self.lists.iter().any(|list| list.name == key)
474 }
475}
476
477impl ActionType {
478 pub const ALL: &'static [ActionType] = &[
485 ActionType::ClockCheck,
486 ActionType::Confirm,
487 ActionType::CheckValue,
488 ActionType::OralReadback,
489 ActionType::MachineInfo,
490 ActionType::EnterValue,
491 ActionType::EnterSecret,
492 ActionType::GenerateKey,
493 ActionType::WrapKey,
494 ActionType::UnwrapKey,
495 ActionType::ImportKey,
496 ActionType::EncryptData,
497 ActionType::DecryptData,
498 ActionType::SplitSecret,
499 ActionType::CombineShares,
500 ActionType::ExportPublic,
501 ActionType::SignData,
502 ActionType::VerifySignature,
503 ActionType::Attest,
504 ActionType::GatherEntropy,
505 ActionType::TpmAttest,
506 ActionType::PivReadCertificate,
507 ActionType::PivSign,
508 ActionType::YubikeyAttestSlot,
509 ActionType::IssueCertificate,
510 ActionType::GenerateCsr,
511 ];
512
513 pub fn backend_usage(self) -> BackendUsage {
519 match self {
520 ActionType::GenerateKey
521 | ActionType::SignData
522 | ActionType::WrapKey
523 | ActionType::UnwrapKey
524 | ActionType::ExportPublic
525 | ActionType::GenerateCsr
526 | ActionType::IssueCertificate
527 | ActionType::PivReadCertificate
528 | ActionType::PivSign
529 | ActionType::YubikeyAttestSlot
530 | ActionType::ImportKey
531 | ActionType::EncryptData
532 | ActionType::DecryptData
533 | ActionType::SplitSecret
534 | ActionType::TpmAttest => BackendUsage::Required,
535
536 ActionType::VerifySignature => BackendUsage::SoftwareUnlessNamed,
537
538 ActionType::ClockCheck
539 | ActionType::Confirm
540 | ActionType::CheckValue
541 | ActionType::OralReadback
542 | ActionType::MachineInfo
543 | ActionType::EnterValue
544 | ActionType::EnterSecret
545 | ActionType::Attest
546 | ActionType::CombineShares
547 | ActionType::GatherEntropy => BackendUsage::Unused,
548 }
549 }
550
551 pub fn required_with_fields(self) -> &'static [&'static str] {
560 match self {
561 ActionType::CheckValue => &["actual", "expected"],
562 ActionType::GenerateCsr => &["subject"],
563 ActionType::ImportKey => &["algorithm"],
566 ActionType::SplitSecret => &["threshold", "shares"],
567 ActionType::EnterValue | ActionType::EnterSecret => &["message"],
570
571 ActionType::ClockCheck
572 | ActionType::Confirm
573 | ActionType::OralReadback
574 | ActionType::MachineInfo
575 | ActionType::GenerateKey
576 | ActionType::WrapKey
577 | ActionType::UnwrapKey
578 | ActionType::ExportPublic
579 | ActionType::SignData
580 | ActionType::VerifySignature
581 | ActionType::Attest
582 | ActionType::GatherEntropy
583 | ActionType::TpmAttest
584 | ActionType::PivReadCertificate
585 | ActionType::PivSign
586 | ActionType::YubikeyAttestSlot
587 | ActionType::EncryptData
588 | ActionType::DecryptData
589 | ActionType::CombineShares
590 | ActionType::IssueCertificate => &[],
591 }
592 }
593
594 pub fn known_with_fields(self) -> &'static [&'static str] {
608 match self {
609 ActionType::ClockCheck | ActionType::Confirm => &["message"],
610 ActionType::CheckValue => &["actual", "expected", "message", "sensitive"],
611 ActionType::OralReadback => &["value", "format", "characters", "message"],
612 ActionType::MachineInfo => &[
613 "include_machine_id",
614 "include_cpu",
615 "include_os",
616 "include_security_features",
617 "message",
618 ],
619 ActionType::EnterValue | ActionType::EnterSecret => {
620 &["message", "format", "length", "min_length", "max_length"]
621 }
622 ActionType::GenerateKey => &["algorithm", "policy", "slot"],
623 ActionType::WrapKey => &["scheme", "expect_recipient"],
624 ActionType::UnwrapKey | ActionType::ImportKey => {
625 &["algorithm", "expect_key", "label", "policy"]
626 }
627 ActionType::EncryptData => &["scheme"],
628 ActionType::SplitSecret => &["scheme", "threshold", "shares"],
629 ActionType::SignData | ActionType::VerifySignature => &["algorithm", "message"],
630 ActionType::Attest => &["statement"],
631 ActionType::GatherEntropy => &["instruction"],
632 ActionType::IssueCertificate => &["profile", "validity_days", "issuer_cn", "path_len"],
633 ActionType::GenerateCsr => &["subject", "san"],
634 ActionType::PivReadCertificate | ActionType::YubikeyAttestSlot => &["slot", "message"],
635 ActionType::PivSign => &["slot", "algorithm", "message"],
636 ActionType::ExportPublic
641 | ActionType::DecryptData
642 | ActionType::CombineShares
643 | ActionType::TpmAttest => &[],
644 }
645 }
646
647 pub fn reads_contract(self) -> ReadsContract {
655 match self {
656 ActionType::WrapKey => ReadsContract {
657 required: &["key_to_wrap"],
658 exactly_one_of: &[&["wrapping_key", "recipient"]],
659 optional: &[],
660 with_field_requires: &[("expect_recipient", "recipient")],
663 lists: &[],
664 },
665 ActionType::UnwrapKey => ReadsContract::required(&["unwrapping_key", "wrapped_data"]),
666 ActionType::ImportKey => ReadsContract {
667 required: &["key_material"],
668 exactly_one_of: &[],
669 optional: &["passphrase"],
673 with_field_requires: &[],
674 lists: &[],
675 },
676 ActionType::EncryptData => ReadsContract::required(&["data", "encryption_key"]),
677 ActionType::DecryptData => {
678 ReadsContract::required(&["encrypted_data", "decryption_key"])
679 }
680 ActionType::SplitSecret => ReadsContract::required(&["secret"]),
681 ActionType::CombineShares => ReadsContract {
684 required: &[],
685 exactly_one_of: &[],
686 optional: &[],
687 with_field_requires: &[],
688 lists: &[ListInput {
689 name: "shares",
690 at_least: 2,
691 }],
692 },
693 ActionType::SignData => ReadsContract::required(&["key", "data"]),
694 ActionType::VerifySignature => ReadsContract::required(&["key", "data", "signature"]),
695 ActionType::IssueCertificate => ReadsContract {
699 required: &["signing_key", "csr"],
700 exactly_one_of: &[],
701 optional: &["issuer_cert"],
702 with_field_requires: &[],
703 lists: &[],
704 },
705 ActionType::GenerateCsr => ReadsContract::required(&["signing_key"]),
706
707 ActionType::ClockCheck
708 | ActionType::Confirm
709 | ActionType::CheckValue
710 | ActionType::OralReadback
711 | ActionType::MachineInfo
712 | ActionType::EnterValue
713 | ActionType::EnterSecret
714 | ActionType::GenerateKey
715 | ActionType::ExportPublic
716 | ActionType::Attest
717 | ActionType::GatherEntropy
718 | ActionType::TpmAttest
719 | ActionType::PivReadCertificate
720 | ActionType::PivSign
721 | ActionType::YubikeyAttestSlot => ReadsContract::NONE,
722 }
723 }
724
725 pub fn describe(&self) -> &'static str {
730 match self {
731 ActionType::ClockCheck => "Verify system clock against a reference time.",
732 ActionType::Confirm => "Confirm readiness or completion of a manual step.",
733 ActionType::CheckValue => "Verify a value matches an expected result.",
734 ActionType::OralReadback => "Read back a value aloud for verification.",
735 ActionType::MachineInfo => "Record system and environment information.",
736 ActionType::EnterValue => "Type a value the ceremony records.",
737 ActionType::EnterSecret => "Type a secret the ceremony holds and never records.",
738 ActionType::Attest => "Record a signed attestation from a participant.",
739 ActionType::GatherEntropy => "Fold human-supplied entropy into the ceremony seed.",
740 ActionType::TpmAttest => "Record TPM platform attestation (PCR values).",
741 ActionType::GenerateKey => "Generate a cryptographic key.",
742 ActionType::ExportPublic => "Export the public component of a keypair.",
743 ActionType::SignData => "Sign data with a ceremony key.",
744 ActionType::VerifySignature => "Verify a signature against a public key.",
745 ActionType::WrapKey => "Encrypt a key under another key for transport.",
746 ActionType::UnwrapKey => "Decrypt a wrapped key and import it into a backend.",
747 ActionType::GenerateCsr => "Generate a Certificate Signing Request.",
748 ActionType::IssueCertificate => "Issue an X.509 certificate from a CSR.",
749 ActionType::PivReadCertificate => "Read a certificate from a PIV smart card slot.",
750 ActionType::PivSign => "Sign data using a PIV smart card key.",
751 ActionType::YubikeyAttestSlot => "Attest a YubiKey PIV slot key.",
752 ActionType::ImportKey => "Import a key from material the ceremony holds.",
753 ActionType::EncryptData => "Encrypt content under a key held by a backend.",
754 ActionType::DecryptData => "Decrypt an encrypted-data artifact back to bytes.",
755 ActionType::SplitSecret => {
756 "Split a secret into shares a threshold of which reconstruct it."
757 }
758 ActionType::CombineShares => "Reconstruct a secret from its shares.",
759 }
760 }
761}
762
763impl std::fmt::Display for ActionType {
764 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
765 match self {
766 ActionType::ClockCheck => write!(f, "clock_check"),
767 ActionType::Confirm => write!(f, "confirm"),
768 ActionType::CheckValue => write!(f, "check_value"),
769 ActionType::OralReadback => write!(f, "oral_readback"),
770 ActionType::MachineInfo => write!(f, "machine_info"),
771 ActionType::EnterValue => write!(f, "enter_value"),
772 ActionType::EnterSecret => write!(f, "enter_secret"),
773 ActionType::GenerateKey => write!(f, "generate_key"),
774 ActionType::WrapKey => write!(f, "wrap_key"),
775 ActionType::UnwrapKey => write!(f, "unwrap_key"),
776 ActionType::ImportKey => write!(f, "import_key"),
777 ActionType::EncryptData => write!(f, "encrypt_data"),
778 ActionType::DecryptData => write!(f, "decrypt_data"),
779 ActionType::SplitSecret => write!(f, "split_secret"),
780 ActionType::CombineShares => write!(f, "combine_shares"),
781 ActionType::ExportPublic => write!(f, "export_public"),
782 ActionType::SignData => write!(f, "sign_data"),
783 ActionType::VerifySignature => write!(f, "verify_signature"),
784 ActionType::Attest => write!(f, "attest"),
785 ActionType::GatherEntropy => write!(f, "gather_entropy"),
786 ActionType::TpmAttest => write!(f, "tpm_attest"),
787 ActionType::PivReadCertificate => write!(f, "piv_read_certificate"),
788 ActionType::PivSign => write!(f, "piv_sign"),
789 ActionType::YubikeyAttestSlot => write!(f, "yubikey_attest_slot"),
790 ActionType::IssueCertificate => write!(f, "issue_certificate"),
791 ActionType::GenerateCsr => write!(f, "generate_csr"),
792 }
793 }
794}
795
796#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
798#[serde(rename_all = "snake_case")]
799#[non_exhaustive]
800pub enum OutputType {
801 PublicKey,
803 WrappedKey,
805 EncryptedData,
807 Certificate,
809 SignedRrset,
811 Sct,
813
814 Document,
817 CeremonyLog,
819}
820
821impl std::fmt::Display for OutputType {
822 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
823 match self {
824 OutputType::PublicKey => write!(f, "public_key"),
825 OutputType::WrappedKey => write!(f, "wrapped_key"),
826 OutputType::EncryptedData => write!(f, "encrypted_data"),
827 OutputType::Certificate => write!(f, "certificate"),
828 OutputType::SignedRrset => write!(f, "signed_rrset"),
829 OutputType::Sct => write!(f, "sct"),
830 OutputType::Document => write!(f, "document"),
831 OutputType::CeremonyLog => write!(f, "ceremony_log"),
832 }
833 }
834}
835
836impl OutputType {
837 pub fn default_extension(&self) -> &'static str {
839 match self {
840 OutputType::PublicKey | OutputType::Certificate => "pem",
841 OutputType::WrappedKey
842 | OutputType::EncryptedData
843 | OutputType::SignedRrset
844 | OutputType::Sct => "bin",
845 OutputType::Document => "txt",
846 OutputType::CeremonyLog => "json",
847 }
848 }
849}
850
851#[derive(Debug, Clone, Serialize, Deserialize)]
853#[serde(rename_all = "snake_case")]
854#[non_exhaustive]
855pub enum ParameterType {
856 String,
858 Date,
860 Integer,
862 Boolean,
864}
865
866impl std::fmt::Display for ParameterType {
867 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
868 match self {
869 ParameterType::String => write!(f, "string"),
870 ParameterType::Date => write!(f, "date (YYYY-MM-DD)"),
871 ParameterType::Integer => write!(f, "integer"),
872 ParameterType::Boolean => write!(f, "boolean (true/false)"),
873 }
874 }
875}
876
877#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
885#[serde(rename_all = "snake_case")]
886#[non_exhaustive]
887pub enum DutyType {
888 ReturnToVault,
890 DistributeShares,
892 DistributeMedia,
894 ArchiveMaterials,
896 PublishRecord,
898 NotifyStakeholders,
900 ImportKeys,
902 Custom,
904}
905
906impl DutyType {
907 pub fn display_name(&self) -> &'static str {
909 match self {
910 DutyType::ReturnToVault => "Return to Vault",
911 DutyType::DistributeShares => "Distribute Key Shares",
912 DutyType::DistributeMedia => "Distribute Backup Media",
913 DutyType::ArchiveMaterials => "Archive Materials",
914 DutyType::PublishRecord => "Publish Record",
915 DutyType::NotifyStakeholders => "Notify Stakeholders",
916 DutyType::ImportKeys => "Import Keys",
917 DutyType::Custom => "Custom Duty",
918 }
919 }
920
921 pub fn built_in_prose(&self) -> Option<&'static str> {
925 match self {
926 DutyType::ReturnToVault => Some("Return materials to secure storage"),
927 DutyType::DistributeShares => Some("Distribute key shares to assigned custodians"),
928 DutyType::DistributeMedia => Some("Distribute backup media to designated recipients"),
929 DutyType::ArchiveMaterials => {
930 Some("Archive ceremony materials at designated storage locations")
931 }
932 DutyType::PublishRecord => Some("Publish ceremony record and witness attestations"),
933 DutyType::NotifyStakeholders => Some("Notify stakeholders of ceremony completion"),
934 DutyType::ImportKeys => Some("Import generated keys into operational system"),
935 DutyType::Custom => None,
936 }
937 }
938}
939
940#[cfg(test)]
941mod tests {
942 use super::*;
943
944 #[test]
945 fn role_type_extracts_prefix() {
946 assert_eq!(role_type("witness__1"), "witness");
947 assert_eq!(role_type("hsm_operator__primary"), "hsm_operator");
948 assert_eq!(role_type("operator"), "operator");
949 assert_eq!(role_type("witness__"), "witness");
950 }
951
952 #[test]
953 fn derive_step_name_title_cases() {
954 assert_eq!(derive_step_name("verify_time"), "Verify Time");
955 assert_eq!(derive_step_name("generate_root_ca"), "Generate Root Ca");
956 assert_eq!(derive_step_name("wrap_root_ca_key"), "Wrap Root Ca Key");
957 assert_eq!(derive_step_name("witness1_attest"), "Witness1 Attest");
958 assert_eq!(derive_step_name("clock-check"), "Clock Check");
959 assert_eq!(derive_step_name("opening"), "Opening");
960 }
961
962 #[test]
963 fn derive_role_name_title_cases() {
964 assert_eq!(derive_role_name("witness__1"), "Witness 1");
965 assert_eq!(
966 derive_role_name("hsm_operator__primary"),
967 "Hsm Operator Primary"
968 );
969 assert_eq!(derive_role_name("operator"), "Operator");
970 assert_eq!(derive_role_name("ceremony-admin"), "Ceremony Admin");
971 }
972
973 #[test]
974 fn action_type_serde_roundtrip() {
975 let cases: &[(ActionType, &str)] = &[
978 (ActionType::ClockCheck, "\"clock_check\""),
979 (ActionType::Confirm, "\"confirm\""),
980 (ActionType::CheckValue, "\"check_value\""),
981 (ActionType::OralReadback, "\"oral_readback\""),
982 (ActionType::MachineInfo, "\"machine_info\""),
983 (ActionType::GenerateKey, "\"generate_key\""),
984 (ActionType::WrapKey, "\"wrap_key\""),
985 (ActionType::UnwrapKey, "\"unwrap_key\""),
986 (ActionType::ImportKey, "\"import_key\""),
987 (ActionType::EncryptData, "\"encrypt_data\""),
988 (ActionType::DecryptData, "\"decrypt_data\""),
989 (ActionType::SplitSecret, "\"split_secret\""),
990 (ActionType::CombineShares, "\"combine_shares\""),
991 (ActionType::ExportPublic, "\"export_public\""),
992 (ActionType::Attest, "\"attest\""),
993 (ActionType::TpmAttest, "\"tpm_attest\""),
994 (ActionType::PivReadCertificate, "\"piv_read_certificate\""),
995 (ActionType::PivSign, "\"piv_sign\""),
996 (ActionType::YubikeyAttestSlot, "\"yubikey_attest_slot\""),
997 (ActionType::IssueCertificate, "\"issue_certificate\""),
998 (ActionType::GenerateCsr, "\"generate_csr\""),
999 ];
1000 for &(variant, expected) in cases {
1001 let serialized = serde_json::to_string(&variant).unwrap();
1002 assert_eq!(serialized, expected, "serialize {variant:?}");
1003 let deserialized: ActionType = serde_json::from_str(expected).unwrap();
1004 assert_eq!(deserialized, variant, "deserialize {expected}");
1005 }
1006 }
1007
1008 #[test]
1009 fn action_type_display_matches_serde() {
1010 let actions = [
1013 ActionType::ClockCheck,
1014 ActionType::Confirm,
1015 ActionType::CheckValue,
1016 ActionType::OralReadback,
1017 ActionType::MachineInfo,
1018 ActionType::GenerateKey,
1019 ActionType::WrapKey,
1020 ActionType::UnwrapKey,
1021 ActionType::ImportKey,
1022 ActionType::ExportPublic,
1023 ActionType::Attest,
1024 ActionType::TpmAttest,
1025 ActionType::PivReadCertificate,
1026 ActionType::PivSign,
1027 ActionType::YubikeyAttestSlot,
1028 ActionType::IssueCertificate,
1029 ActionType::GenerateCsr,
1030 ];
1031 for action in actions {
1032 let display = action.to_string();
1033 let serde_json = serde_json::to_string(&action).unwrap();
1034 assert_eq!(
1035 display,
1036 serde_json.trim_matches('"'),
1037 "Display and serde disagree for {action:?}"
1038 );
1039 }
1040 }
1041
1042 #[test]
1043 fn output_type_serde_roundtrip() {
1044 let cases: &[(OutputType, &str)] = &[
1047 (OutputType::PublicKey, "\"public_key\""),
1048 (OutputType::WrappedKey, "\"wrapped_key\""),
1049 (OutputType::EncryptedData, "\"encrypted_data\""),
1050 (OutputType::Certificate, "\"certificate\""),
1051 (OutputType::Document, "\"document\""),
1052 (OutputType::CeremonyLog, "\"ceremony_log\""),
1053 ];
1054 for &(variant, expected) in cases {
1055 let serialized = serde_json::to_string(&variant).unwrap();
1056 assert_eq!(serialized, expected, "serialize {variant:?}");
1057 let deserialized: OutputType = serde_json::from_str(expected).unwrap();
1058 assert_eq!(deserialized, variant, "deserialize {expected}");
1059 }
1060 }
1061
1062 #[test]
1063 fn parameter_type_serde_roundtrip() {
1064 let cases: &[(ParameterType, &str)] = &[
1066 (ParameterType::String, "\"string\""),
1067 (ParameterType::Date, "\"date\""),
1068 (ParameterType::Integer, "\"integer\""),
1069 (ParameterType::Boolean, "\"boolean\""),
1070 ];
1071 for (variant, expected) in cases {
1072 let serialized = serde_json::to_string(variant).unwrap();
1073 assert_eq!(serialized, *expected, "serialize {variant:?}");
1074 let deserialized: ParameterType = serde_json::from_str(expected).unwrap();
1075 assert_eq!(
1077 serde_json::to_string(&deserialized).unwrap(),
1078 *expected,
1079 "deserialize {expected}"
1080 );
1081 }
1082 }
1083
1084 #[test]
1085 fn duty_type_serde_roundtrip() {
1086 let cases: &[(DutyType, &str)] = &[
1088 (DutyType::ReturnToVault, "\"return_to_vault\""),
1089 (DutyType::DistributeShares, "\"distribute_shares\""),
1090 (DutyType::DistributeMedia, "\"distribute_media\""),
1091 (DutyType::ArchiveMaterials, "\"archive_materials\""),
1092 (DutyType::PublishRecord, "\"publish_record\""),
1093 (DutyType::NotifyStakeholders, "\"notify_stakeholders\""),
1094 (DutyType::ImportKeys, "\"import_keys\""),
1095 (DutyType::Custom, "\"custom\""),
1096 ];
1097 for (variant, expected) in cases {
1098 let serialized = serde_json::to_string(variant).unwrap();
1099 assert_eq!(serialized, *expected, "serialize {variant:?}");
1100 let deserialized: DutyType = serde_json::from_str(expected).unwrap();
1101 assert_eq!(deserialized, *variant, "deserialize {expected}");
1102 }
1103 }
1104
1105 #[test]
1106 fn duty_type_custom_has_no_built_in_prose() {
1107 assert!(
1110 DutyType::Custom.built_in_prose().is_none(),
1111 "Custom duty must have no built-in prose"
1112 );
1113 for duty in [
1115 DutyType::ReturnToVault,
1116 DutyType::DistributeShares,
1117 DutyType::DistributeMedia,
1118 DutyType::ArchiveMaterials,
1119 DutyType::PublishRecord,
1120 DutyType::NotifyStakeholders,
1121 DutyType::ImportKeys,
1122 ] {
1123 assert!(
1124 duty.built_in_prose().is_some(),
1125 "{duty:?} must have built-in prose"
1126 );
1127 }
1128 }
1129
1130 #[test]
1141 fn every_required_field_is_also_a_known_field() {
1142 for action in ActionType::ALL {
1143 let known = action.known_with_fields();
1144 for field in action.required_with_fields() {
1145 assert!(
1146 known.contains(field),
1147 "{action} requires '{field}' and does not accept it"
1148 );
1149 }
1150 }
1151 }
1152
1153 #[test]
1154 fn all_lists_every_action_type() {
1155 for action in ActionType::ALL {
1156 match action {
1157 ActionType::ClockCheck
1158 | ActionType::Confirm
1159 | ActionType::CheckValue
1160 | ActionType::OralReadback
1161 | ActionType::MachineInfo
1162 | ActionType::EnterValue
1163 | ActionType::EnterSecret
1164 | ActionType::GenerateKey
1165 | ActionType::WrapKey
1166 | ActionType::UnwrapKey
1167 | ActionType::ImportKey
1168 | ActionType::EncryptData
1169 | ActionType::DecryptData
1170 | ActionType::SplitSecret
1171 | ActionType::CombineShares
1172 | ActionType::ExportPublic
1173 | ActionType::SignData
1174 | ActionType::VerifySignature
1175 | ActionType::Attest
1176 | ActionType::GatherEntropy
1177 | ActionType::TpmAttest
1178 | ActionType::PivReadCertificate
1179 | ActionType::PivSign
1180 | ActionType::YubikeyAttestSlot
1181 | ActionType::IssueCertificate
1182 | ActionType::GenerateCsr => {}
1183 }
1184 }
1185
1186 let names: std::collections::BTreeSet<String> =
1187 ActionType::ALL.iter().map(ToString::to_string).collect();
1188 assert_eq!(
1189 names.len(),
1190 ActionType::ALL.len(),
1191 "two actions share a DSL name"
1192 );
1193 }
1194}