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(into = "String", try_from = "String")]
270#[non_exhaustive]
271pub enum CertProfile {
272 RootCa,
274 SubCa,
277 TlsServer,
279 CodeSigning,
281 EndEntity,
283}
284
285crate::types::vocabulary!(CertProfile {
286 RootCa => "root_ca",
287 SubCa => "sub_ca" | "intermediate_ca",
288 TlsServer => "tls_server",
289 CodeSigning => "code_signing",
290 EndEntity => "end_entity",
291});
292
293impl fmt::Display for CertProfile {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.write_str(self.as_str())
296 }
297}
298
299impl std::str::FromStr for CertProfile {
300 type Err = UnknownCertProfile;
301
302 fn from_str(s: &str) -> Result<Self, Self::Err> {
303 Self::from_name(s).ok_or(UnknownCertProfile)
304 }
305}
306
307impl From<CertProfile> for String {
308 fn from(profile: CertProfile) -> Self {
309 profile.as_str().to_string()
310 }
311}
312
313impl TryFrom<String> for CertProfile {
314 type Error = UnknownCertProfile;
315
316 fn try_from(s: String) -> Result<Self, Self::Error> {
317 s.parse()
318 }
319}
320
321impl schemars::JsonSchema for CertProfile {
322 fn schema_name() -> std::borrow::Cow<'static, str> {
323 "CertProfile".into()
324 }
325
326 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
327 Self::names_schema()
328 }
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub struct UnknownCertProfile;
334
335impl fmt::Display for UnknownCertProfile {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 let names: Vec<&str> = CertProfile::ALL.iter().map(|p| p.as_str()).collect();
338 write!(f, "supported profiles: {}", names.join(", "))
339 }
340}
341
342pub(crate) trait Vocabulary: Copy + 'static {
347 const VALUES: &'static [Self];
349 const ALIASES: &'static [(&'static str, Self)] = &[];
351
352 fn name(self) -> &'static str;
354
355 fn from_name(name: &str) -> Option<Self> {
357 Self::VALUES
358 .iter()
359 .map(|&value| (value.name(), value))
360 .chain(Self::ALIASES.iter().copied())
361 .find(|(candidate, _)| *candidate == name)
362 .map(|(_, value)| value)
363 }
364
365 fn names_schema() -> schemars::Schema {
367 crate::params::names(
368 Self::VALUES
369 .iter()
370 .map(|value| value.name())
371 .chain(Self::ALIASES.iter().map(|(alias, _)| *alias)),
372 )
373 }
374}
375
376macro_rules! vocabulary {
382 ($ty:ident { $($variant:ident => $name:literal $(| $alias:literal)*,)+ }) => {
383 impl $ty {
384 pub const ALL: &'static [$ty] = &[$($ty::$variant),+];
386
387 #[must_use]
389 pub fn as_str(self) -> &'static str {
390 match self {
391 $($ty::$variant => $name,)+
392 }
393 }
394 }
395
396 impl $crate::types::Vocabulary for $ty {
397 const VALUES: &'static [Self] = Self::ALL;
398 const ALIASES: &'static [(&'static str, Self)] =
399 &[$($(($alias, $ty::$variant),)*)+];
400
401 fn name(self) -> &'static str {
402 self.as_str()
403 }
404 }
405 };
406}
407
408pub(crate) use vocabulary;
409
410pub(crate) fn either<'a>(names: impl Iterator<Item = &'a str>) -> String {
412 let names: Vec<&str> = names.collect();
413 match names.split_last() {
414 Some((last, rest)) if !rest.is_empty() => format!("{} or {last}", rest.join(", ")),
415 Some((last, _)) => (*last).to_string(),
416 None => String::new(),
417 }
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
428#[serde(into = "String", try_from = "String")]
429#[non_exhaustive]
430pub enum SharingScheme {
431 RiteSssV1,
434}
435
436crate::types::vocabulary!(SharingScheme {
437 RiteSssV1 => "rite-sss/v1",
438});
439
440impl SharingScheme {
441 pub fn max_shares(self) -> u8 {
451 match self {
452 SharingScheme::RiteSssV1 => 100,
453 }
454 }
455
456 #[must_use]
459 pub fn header_len(self) -> usize {
460 match self {
461 SharingScheme::RiteSssV1 => 3,
462 }
463 }
464
465 #[must_use]
469 pub fn share_len(self, secret_len: usize) -> usize {
470 match self {
471 SharingScheme::RiteSssV1 => secret_len.saturating_add(self.header_len()),
472 }
473 }
474}
475
476impl fmt::Display for SharingScheme {
477 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 f.write_str(self.as_str())
479 }
480}
481
482impl From<SharingScheme> for String {
483 fn from(scheme: SharingScheme) -> Self {
484 scheme.as_str().to_string()
485 }
486}
487
488impl std::str::FromStr for SharingScheme {
489 type Err = UnknownSharingScheme;
490
491 fn from_str(s: &str) -> Result<Self, Self::Err> {
492 Self::from_name(s).ok_or(UnknownSharingScheme)
493 }
494}
495
496impl schemars::JsonSchema for SharingScheme {
497 fn schema_name() -> std::borrow::Cow<'static, str> {
498 "SharingScheme".into()
499 }
500
501 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
502 Self::names_schema()
503 }
504}
505
506impl TryFrom<String> for SharingScheme {
507 type Error = UnknownSharingScheme;
508
509 fn try_from(s: String) -> Result<Self, Self::Error> {
510 s.parse()
511 }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
516pub struct UnknownSharingScheme;
517
518impl fmt::Display for UnknownSharingScheme {
519 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
520 let names: Vec<&str> = SharingScheme::ALL.iter().map(|s| s.as_str()).collect();
521 write!(f, "supported sharing schemes: {}", names.join(", "))
522 }
523}
524
525impl std::error::Error for UnknownSharingScheme {}
526
527#[derive(Debug, Clone, Copy, PartialEq, Eq)]
531#[non_exhaustive]
532pub struct ReadsContract {
533 pub required: &'static [&'static str],
535 pub exactly_one_of: &'static [&'static [&'static str]],
538 pub optional: &'static [&'static str],
544 pub with_field_requires: &'static [(&'static str, &'static str)],
552 pub lists: &'static [ListInput],
560}
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
564pub struct ListInput {
565 pub name: &'static str,
567 pub at_least: usize,
569}
570
571impl ReadsContract {
572 pub const NONE: Self = Self::required(&[]);
574
575 #[must_use]
578 pub const fn required(fields: &'static [&'static str]) -> Self {
579 Self {
580 required: fields,
581 exactly_one_of: &[],
582 optional: &[],
583 with_field_requires: &[],
584 lists: &[],
585 }
586 }
587
588 pub fn is_list(&self, key: &str) -> bool {
590 self.lists.iter().any(|list| list.name == key)
591 }
592
593 pub fn is_empty(&self) -> bool {
597 self.required.is_empty()
598 && self.exactly_one_of.is_empty()
599 && self.optional.is_empty()
600 && self.lists.is_empty()
601 }
602
603 pub fn accepts(&self, key: &str) -> bool {
605 self.required.contains(&key)
606 || self.optional.contains(&key)
607 || self.exactly_one_of.iter().any(|group| group.contains(&key))
608 || self.lists.iter().any(|list| list.name == key)
609 }
610}
611
612impl ActionType {
613 pub const ALL: &'static [ActionType] = &[
620 ActionType::ClockCheck,
621 ActionType::Confirm,
622 ActionType::CheckValue,
623 ActionType::OralReadback,
624 ActionType::Reveal,
625 ActionType::MachineInfo,
626 ActionType::EnterValue,
627 ActionType::EnterSecret,
628 ActionType::GenerateKey,
629 ActionType::WrapKey,
630 ActionType::UnwrapKey,
631 ActionType::ImportKey,
632 ActionType::EncryptData,
633 ActionType::DecryptData,
634 ActionType::SplitSecret,
635 ActionType::CombineShares,
636 ActionType::EnterShare,
637 ActionType::ExportPublic,
638 ActionType::SignData,
639 ActionType::VerifySignature,
640 ActionType::Attest,
641 ActionType::GatherEntropy,
642 ActionType::TpmAttest,
643 ActionType::PivReadCertificate,
644 ActionType::PivSign,
645 ActionType::YubikeyAttestSlot,
646 ActionType::IssueCertificate,
647 ActionType::GenerateCsr,
648 ];
649
650 pub fn backend_usage(self) -> BackendUsage {
656 match self {
657 ActionType::GenerateKey
658 | ActionType::SignData
659 | ActionType::WrapKey
660 | ActionType::UnwrapKey
661 | ActionType::ExportPublic
662 | ActionType::GenerateCsr
663 | ActionType::IssueCertificate
664 | ActionType::PivReadCertificate
665 | ActionType::PivSign
666 | ActionType::YubikeyAttestSlot
667 | ActionType::ImportKey
668 | ActionType::EncryptData
669 | ActionType::DecryptData
670 | ActionType::SplitSecret
671 | ActionType::TpmAttest => BackendUsage::Required,
672
673 ActionType::VerifySignature => BackendUsage::SoftwareUnlessNamed,
674
675 ActionType::ClockCheck
676 | ActionType::Confirm
677 | ActionType::CheckValue
678 | ActionType::OralReadback
679 | ActionType::Reveal
680 | ActionType::MachineInfo
681 | ActionType::EnterValue
682 | ActionType::EnterSecret
683 | ActionType::Attest
684 | ActionType::CombineShares
685 | ActionType::EnterShare
686 | ActionType::GatherEntropy => BackendUsage::Unused,
687 }
688 }
689
690 pub fn creates_shares(self) -> bool {
694 match self {
695 ActionType::SplitSecret | ActionType::EnterShare => true,
696 ActionType::ClockCheck
697 | ActionType::Confirm
698 | ActionType::CheckValue
699 | ActionType::OralReadback
700 | ActionType::Reveal
701 | ActionType::MachineInfo
702 | ActionType::EnterValue
703 | ActionType::EnterSecret
704 | ActionType::GenerateKey
705 | ActionType::WrapKey
706 | ActionType::UnwrapKey
707 | ActionType::ImportKey
708 | ActionType::EncryptData
709 | ActionType::DecryptData
710 | ActionType::CombineShares
711 | ActionType::ExportPublic
712 | ActionType::SignData
713 | ActionType::VerifySignature
714 | ActionType::GenerateCsr
715 | ActionType::IssueCertificate
716 | ActionType::PivReadCertificate
717 | ActionType::PivSign
718 | ActionType::YubikeyAttestSlot
719 | ActionType::Attest
720 | ActionType::GatherEntropy
721 | ActionType::TpmAttest => false,
722 }
723 }
724
725 pub fn reads_contract(self) -> ReadsContract {
733 match self {
734 ActionType::WrapKey => ReadsContract {
735 required: &["key_to_wrap"],
736 exactly_one_of: &[&["wrapping_key", "recipient"]],
737 optional: &[],
738 with_field_requires: &[("expect_recipient", "recipient")],
741 lists: &[],
742 },
743 ActionType::UnwrapKey => ReadsContract::required(&["unwrapping_key", "wrapped_data"]),
744 ActionType::ImportKey => ReadsContract {
745 required: &["key_material"],
746 exactly_one_of: &[],
747 optional: &["passphrase"],
751 with_field_requires: &[],
752 lists: &[],
753 },
754 ActionType::EncryptData => ReadsContract::required(&["data", "encryption_key"]),
755 ActionType::DecryptData => {
756 ReadsContract::required(&["encrypted_data", "decryption_key"])
757 }
758 ActionType::SplitSecret => ReadsContract::required(&["secret"]),
759 ActionType::CombineShares => ReadsContract {
762 required: &[],
763 exactly_one_of: &[],
764 optional: &[],
765 with_field_requires: &[],
766 lists: &[ListInput {
767 name: "shares",
768 at_least: 2,
769 }],
770 },
771 ActionType::SignData => ReadsContract::required(&["key", "data"]),
772 ActionType::VerifySignature => ReadsContract::required(&["key", "data", "signature"]),
773 ActionType::IssueCertificate => ReadsContract {
777 required: &["signing_key", "csr"],
778 exactly_one_of: &[],
779 optional: &["issuer_cert"],
780 with_field_requires: &[],
781 lists: &[],
782 },
783 ActionType::GenerateCsr => ReadsContract::required(&["signing_key"]),
784 ActionType::Reveal => ReadsContract::required(&["value"]),
785
786 ActionType::ClockCheck
787 | ActionType::Confirm
788 | ActionType::CheckValue
789 | ActionType::OralReadback
790 | ActionType::MachineInfo
791 | ActionType::EnterValue
792 | ActionType::EnterSecret
793 | ActionType::GenerateKey
794 | ActionType::ExportPublic
795 | ActionType::Attest
796 | ActionType::GatherEntropy
797 | ActionType::TpmAttest
798 | ActionType::PivReadCertificate
799 | ActionType::PivSign
800 | ActionType::YubikeyAttestSlot
801 | ActionType::EnterShare => ReadsContract::NONE,
802 }
803 }
804
805 pub fn describe(&self) -> &'static str {
810 match self {
811 ActionType::ClockCheck => "Verify system clock against a reference time.",
812 ActionType::Confirm => "Confirm readiness or completion of a manual step.",
813 ActionType::CheckValue => "Verify a value matches an expected result.",
814 ActionType::OralReadback => "Read back a value aloud for verification.",
815 ActionType::Reveal => "Show a value for a person to write down.",
816 ActionType::MachineInfo => "Record system and environment information.",
817 ActionType::EnterValue => "Type a value the ceremony records.",
818 ActionType::EnterSecret => "Type a secret the ceremony holds and never records.",
819 ActionType::Attest => "Record a signed attestation from a participant.",
820 ActionType::GatherEntropy => "Fold human-supplied entropy into the ceremony seed.",
821 ActionType::TpmAttest => "Record TPM platform attestation (PCR values).",
822 ActionType::GenerateKey => "Generate a cryptographic key.",
823 ActionType::ExportPublic => "Export the public component of a keypair.",
824 ActionType::SignData => "Sign data with a ceremony key.",
825 ActionType::VerifySignature => "Verify a signature against a public key.",
826 ActionType::WrapKey => "Encrypt a key under another key for transport.",
827 ActionType::UnwrapKey => "Decrypt a wrapped key and import it into a backend.",
828 ActionType::GenerateCsr => "Generate a Certificate Signing Request.",
829 ActionType::IssueCertificate => "Issue an X.509 certificate from a CSR.",
830 ActionType::PivReadCertificate => "Read a certificate from a PIV smart card slot.",
831 ActionType::PivSign => "Sign data using a PIV smart card key.",
832 ActionType::YubikeyAttestSlot => "Attest a YubiKey PIV slot key.",
833 ActionType::ImportKey => "Import a key from material the ceremony holds.",
834 ActionType::EncryptData => "Encrypt content under a key held by a backend.",
835 ActionType::DecryptData => "Decrypt an encrypted-data artifact back to bytes.",
836 ActionType::SplitSecret => {
837 "Split a secret into shares a threshold of which reconstruct it."
838 }
839 ActionType::CombineShares => "Reconstruct a secret from its shares.",
840 ActionType::EnterShare => "Type a share back from its sheet.",
841 }
842 }
843}
844
845impl std::fmt::Display for ActionType {
846 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
847 match self {
848 ActionType::ClockCheck => write!(f, "clock_check"),
849 ActionType::Confirm => write!(f, "confirm"),
850 ActionType::CheckValue => write!(f, "check_value"),
851 ActionType::OralReadback => write!(f, "oral_readback"),
852 ActionType::Reveal => write!(f, "reveal"),
853 ActionType::MachineInfo => write!(f, "machine_info"),
854 ActionType::EnterValue => write!(f, "enter_value"),
855 ActionType::EnterSecret => write!(f, "enter_secret"),
856 ActionType::GenerateKey => write!(f, "generate_key"),
857 ActionType::WrapKey => write!(f, "wrap_key"),
858 ActionType::UnwrapKey => write!(f, "unwrap_key"),
859 ActionType::ImportKey => write!(f, "import_key"),
860 ActionType::EncryptData => write!(f, "encrypt_data"),
861 ActionType::DecryptData => write!(f, "decrypt_data"),
862 ActionType::SplitSecret => write!(f, "split_secret"),
863 ActionType::CombineShares => write!(f, "combine_shares"),
864 ActionType::EnterShare => write!(f, "enter_share"),
865 ActionType::ExportPublic => write!(f, "export_public"),
866 ActionType::SignData => write!(f, "sign_data"),
867 ActionType::VerifySignature => write!(f, "verify_signature"),
868 ActionType::Attest => write!(f, "attest"),
869 ActionType::GatherEntropy => write!(f, "gather_entropy"),
870 ActionType::TpmAttest => write!(f, "tpm_attest"),
871 ActionType::PivReadCertificate => write!(f, "piv_read_certificate"),
872 ActionType::PivSign => write!(f, "piv_sign"),
873 ActionType::YubikeyAttestSlot => write!(f, "yubikey_attest_slot"),
874 ActionType::IssueCertificate => write!(f, "issue_certificate"),
875 ActionType::GenerateCsr => write!(f, "generate_csr"),
876 }
877 }
878}
879
880#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
882#[serde(rename_all = "snake_case")]
883#[non_exhaustive]
884pub enum OutputType {
885 PublicKey,
887 WrappedKey,
889 EncryptedData,
891 Certificate,
893 SignedRrset,
895 Sct,
897
898 Document,
901 CeremonyLog,
903}
904
905impl std::fmt::Display for OutputType {
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 match self {
908 OutputType::PublicKey => write!(f, "public_key"),
909 OutputType::WrappedKey => write!(f, "wrapped_key"),
910 OutputType::EncryptedData => write!(f, "encrypted_data"),
911 OutputType::Certificate => write!(f, "certificate"),
912 OutputType::SignedRrset => write!(f, "signed_rrset"),
913 OutputType::Sct => write!(f, "sct"),
914 OutputType::Document => write!(f, "document"),
915 OutputType::CeremonyLog => write!(f, "ceremony_log"),
916 }
917 }
918}
919
920impl OutputType {
921 pub fn default_extension(&self) -> &'static str {
923 match self {
924 OutputType::PublicKey | OutputType::Certificate => "pem",
925 OutputType::WrappedKey
926 | OutputType::EncryptedData
927 | OutputType::SignedRrset
928 | OutputType::Sct => "bin",
929 OutputType::Document => "txt",
930 OutputType::CeremonyLog => "json",
931 }
932 }
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize)]
937#[serde(rename_all = "snake_case")]
938#[non_exhaustive]
939pub enum ParameterType {
940 String,
942 Date,
944 Integer,
946 Boolean,
948}
949
950impl std::fmt::Display for ParameterType {
951 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
952 match self {
953 ParameterType::String => write!(f, "string"),
954 ParameterType::Date => write!(f, "date (YYYY-MM-DD)"),
955 ParameterType::Integer => write!(f, "integer"),
956 ParameterType::Boolean => write!(f, "boolean (true/false)"),
957 }
958 }
959}
960
961#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
969#[serde(rename_all = "snake_case")]
970#[non_exhaustive]
971pub enum DutyType {
972 ReturnToVault,
974 DistributeShares,
976 DistributeMedia,
978 ArchiveMaterials,
980 PublishRecord,
982 NotifyStakeholders,
984 ImportKeys,
986 Custom,
988}
989
990impl DutyType {
991 pub fn display_name(&self) -> &'static str {
993 match self {
994 DutyType::ReturnToVault => "Return to Vault",
995 DutyType::DistributeShares => "Distribute Key Shares",
996 DutyType::DistributeMedia => "Distribute Backup Media",
997 DutyType::ArchiveMaterials => "Archive Materials",
998 DutyType::PublishRecord => "Publish Record",
999 DutyType::NotifyStakeholders => "Notify Stakeholders",
1000 DutyType::ImportKeys => "Import Keys",
1001 DutyType::Custom => "Custom Duty",
1002 }
1003 }
1004
1005 pub fn built_in_prose(&self) -> Option<&'static str> {
1009 match self {
1010 DutyType::ReturnToVault => Some("Return materials to secure storage"),
1011 DutyType::DistributeShares => Some("Distribute key shares to assigned custodians"),
1012 DutyType::DistributeMedia => Some("Distribute backup media to designated recipients"),
1013 DutyType::ArchiveMaterials => {
1014 Some("Archive ceremony materials at designated storage locations")
1015 }
1016 DutyType::PublishRecord => Some("Publish ceremony record and witness attestations"),
1017 DutyType::NotifyStakeholders => Some("Notify stakeholders of ceremony completion"),
1018 DutyType::ImportKeys => Some("Import generated keys into operational system"),
1019 DutyType::Custom => None,
1020 }
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn role_type_extracts_prefix() {
1030 assert_eq!(role_type("witness__1"), "witness");
1031 assert_eq!(role_type("hsm_operator__primary"), "hsm_operator");
1032 assert_eq!(role_type("operator"), "operator");
1033 assert_eq!(role_type("witness__"), "witness");
1034 }
1035
1036 #[test]
1037 fn derive_step_name_title_cases() {
1038 assert_eq!(derive_step_name("verify_time"), "Verify Time");
1039 assert_eq!(derive_step_name("generate_root_ca"), "Generate Root Ca");
1040 assert_eq!(derive_step_name("wrap_root_ca_key"), "Wrap Root Ca Key");
1041 assert_eq!(derive_step_name("witness1_attest"), "Witness1 Attest");
1042 assert_eq!(derive_step_name("clock-check"), "Clock Check");
1043 assert_eq!(derive_step_name("opening"), "Opening");
1044 }
1045
1046 #[test]
1047 fn derive_role_name_title_cases() {
1048 assert_eq!(derive_role_name("witness__1"), "Witness 1");
1049 assert_eq!(
1050 derive_role_name("hsm_operator__primary"),
1051 "Hsm Operator Primary"
1052 );
1053 assert_eq!(derive_role_name("operator"), "Operator");
1054 assert_eq!(derive_role_name("ceremony-admin"), "Ceremony Admin");
1055 }
1056
1057 #[test]
1058 fn action_type_serde_roundtrip() {
1059 let cases: &[(ActionType, &str)] = &[
1062 (ActionType::ClockCheck, "\"clock_check\""),
1063 (ActionType::Confirm, "\"confirm\""),
1064 (ActionType::CheckValue, "\"check_value\""),
1065 (ActionType::OralReadback, "\"oral_readback\""),
1066 (ActionType::Reveal, "\"reveal\""),
1067 (ActionType::MachineInfo, "\"machine_info\""),
1068 (ActionType::GenerateKey, "\"generate_key\""),
1069 (ActionType::WrapKey, "\"wrap_key\""),
1070 (ActionType::UnwrapKey, "\"unwrap_key\""),
1071 (ActionType::ImportKey, "\"import_key\""),
1072 (ActionType::EncryptData, "\"encrypt_data\""),
1073 (ActionType::DecryptData, "\"decrypt_data\""),
1074 (ActionType::SplitSecret, "\"split_secret\""),
1075 (ActionType::CombineShares, "\"combine_shares\""),
1076 (ActionType::EnterShare, "\"enter_share\""),
1077 (ActionType::ExportPublic, "\"export_public\""),
1078 (ActionType::Attest, "\"attest\""),
1079 (ActionType::TpmAttest, "\"tpm_attest\""),
1080 (ActionType::PivReadCertificate, "\"piv_read_certificate\""),
1081 (ActionType::PivSign, "\"piv_sign\""),
1082 (ActionType::YubikeyAttestSlot, "\"yubikey_attest_slot\""),
1083 (ActionType::IssueCertificate, "\"issue_certificate\""),
1084 (ActionType::GenerateCsr, "\"generate_csr\""),
1085 ];
1086 for &(variant, expected) in cases {
1087 let serialized = serde_json::to_string(&variant).unwrap();
1088 assert_eq!(serialized, expected, "serialize {variant:?}");
1089 let deserialized: ActionType = serde_json::from_str(expected).unwrap();
1090 assert_eq!(deserialized, variant, "deserialize {expected}");
1091 }
1092 }
1093
1094 #[test]
1095 fn action_type_display_matches_serde() {
1096 let actions = [
1099 ActionType::ClockCheck,
1100 ActionType::Confirm,
1101 ActionType::CheckValue,
1102 ActionType::OralReadback,
1103 ActionType::Reveal,
1104 ActionType::MachineInfo,
1105 ActionType::GenerateKey,
1106 ActionType::WrapKey,
1107 ActionType::UnwrapKey,
1108 ActionType::ImportKey,
1109 ActionType::ExportPublic,
1110 ActionType::Attest,
1111 ActionType::TpmAttest,
1112 ActionType::PivReadCertificate,
1113 ActionType::PivSign,
1114 ActionType::YubikeyAttestSlot,
1115 ActionType::IssueCertificate,
1116 ActionType::GenerateCsr,
1117 ];
1118 for action in actions {
1119 let display = action.to_string();
1120 let serde_json = serde_json::to_string(&action).unwrap();
1121 assert_eq!(
1122 display,
1123 serde_json.trim_matches('"'),
1124 "Display and serde disagree for {action:?}"
1125 );
1126 }
1127 }
1128
1129 #[test]
1130 fn output_type_serde_roundtrip() {
1131 let cases: &[(OutputType, &str)] = &[
1134 (OutputType::PublicKey, "\"public_key\""),
1135 (OutputType::WrappedKey, "\"wrapped_key\""),
1136 (OutputType::EncryptedData, "\"encrypted_data\""),
1137 (OutputType::Certificate, "\"certificate\""),
1138 (OutputType::Document, "\"document\""),
1139 (OutputType::CeremonyLog, "\"ceremony_log\""),
1140 ];
1141 for &(variant, expected) in cases {
1142 let serialized = serde_json::to_string(&variant).unwrap();
1143 assert_eq!(serialized, expected, "serialize {variant:?}");
1144 let deserialized: OutputType = serde_json::from_str(expected).unwrap();
1145 assert_eq!(deserialized, variant, "deserialize {expected}");
1146 }
1147 }
1148
1149 #[test]
1150 fn parameter_type_serde_roundtrip() {
1151 let cases: &[(ParameterType, &str)] = &[
1153 (ParameterType::String, "\"string\""),
1154 (ParameterType::Date, "\"date\""),
1155 (ParameterType::Integer, "\"integer\""),
1156 (ParameterType::Boolean, "\"boolean\""),
1157 ];
1158 for (variant, expected) in cases {
1159 let serialized = serde_json::to_string(variant).unwrap();
1160 assert_eq!(serialized, *expected, "serialize {variant:?}");
1161 let deserialized: ParameterType = serde_json::from_str(expected).unwrap();
1162 assert_eq!(
1164 serde_json::to_string(&deserialized).unwrap(),
1165 *expected,
1166 "deserialize {expected}"
1167 );
1168 }
1169 }
1170
1171 #[test]
1172 fn duty_type_serde_roundtrip() {
1173 let cases: &[(DutyType, &str)] = &[
1175 (DutyType::ReturnToVault, "\"return_to_vault\""),
1176 (DutyType::DistributeShares, "\"distribute_shares\""),
1177 (DutyType::DistributeMedia, "\"distribute_media\""),
1178 (DutyType::ArchiveMaterials, "\"archive_materials\""),
1179 (DutyType::PublishRecord, "\"publish_record\""),
1180 (DutyType::NotifyStakeholders, "\"notify_stakeholders\""),
1181 (DutyType::ImportKeys, "\"import_keys\""),
1182 (DutyType::Custom, "\"custom\""),
1183 ];
1184 for (variant, expected) in cases {
1185 let serialized = serde_json::to_string(variant).unwrap();
1186 assert_eq!(serialized, *expected, "serialize {variant:?}");
1187 let deserialized: DutyType = serde_json::from_str(expected).unwrap();
1188 assert_eq!(deserialized, *variant, "deserialize {expected}");
1189 }
1190 }
1191
1192 #[test]
1193 fn duty_type_custom_has_no_built_in_prose() {
1194 assert!(
1197 DutyType::Custom.built_in_prose().is_none(),
1198 "Custom duty must have no built-in prose"
1199 );
1200 for duty in [
1202 DutyType::ReturnToVault,
1203 DutyType::DistributeShares,
1204 DutyType::DistributeMedia,
1205 DutyType::ArchiveMaterials,
1206 DutyType::PublishRecord,
1207 DutyType::NotifyStakeholders,
1208 DutyType::ImportKeys,
1209 ] {
1210 assert!(
1211 duty.built_in_prose().is_some(),
1212 "{duty:?} must have built-in prose"
1213 );
1214 }
1215 }
1216
1217 #[test]
1221 fn every_vocabulary_lists_every_value_under_its_serde_name() {
1222 use crate::{Format, RevealFormat};
1223
1224 fn check<T>(all: &[T])
1225 where
1226 T: Copy + PartialEq + std::fmt::Debug + std::fmt::Display + Serialize,
1227 T: std::str::FromStr,
1228 {
1229 for &value in all {
1230 let name = value.to_string();
1231 assert!(
1232 matches!(name.parse::<T>(), Ok(parsed) if parsed == value),
1233 "{name}"
1234 );
1235 assert_eq!(serde_json::to_value(value).unwrap(), name.as_str());
1236 }
1237 }
1238
1239 check(CertProfile::ALL);
1240 check(SharingScheme::ALL);
1241 check(RevealFormat::ALL);
1242 check(Format::ALL);
1243 }
1244
1245 #[test]
1248 fn a_vocabulary_schema_accepts_what_serde_reads() {
1249 use crate::params::ReadbackFormat;
1250
1251 fn check<T>()
1252 where
1253 T: Vocabulary + std::fmt::Debug + PartialEq + schemars::JsonSchema,
1254 T: serde::de::DeserializeOwned,
1255 {
1256 let schema = schemars::schema_for!(T);
1257 let listed: Vec<&str> = schema
1258 .get("enum")
1259 .and_then(|names| names.as_array())
1260 .unwrap()
1261 .iter()
1262 .filter_map(|name| name.as_str())
1263 .collect();
1264 let named = T::VALUES.iter().map(|&value| (value.name(), value));
1265 let mut expected = Vec::new();
1266 for (name, value) in named.chain(T::ALIASES.iter().copied()) {
1267 assert_eq!(
1268 serde_json::from_value::<T>(serde_json::json!(name)).unwrap(),
1269 value
1270 );
1271 expected.push(name);
1272 }
1273 assert_eq!(listed, expected);
1274 }
1275
1276 check::<CertProfile>();
1277 check::<SharingScheme>();
1278 check::<ReadbackFormat>();
1279 assert_eq!("intermediate_ca".parse(), Ok(CertProfile::SubCa));
1280 assert_eq!(
1281 serde_json::from_value::<ReadbackFormat>(serde_json::json!("nato")).unwrap(),
1282 ReadbackFormat::NatoPhonetic
1283 );
1284 }
1285
1286 #[test]
1294 fn all_lists_every_action_type() {
1295 for action in ActionType::ALL {
1296 match action {
1297 ActionType::ClockCheck
1298 | ActionType::Confirm
1299 | ActionType::CheckValue
1300 | ActionType::OralReadback
1301 | ActionType::Reveal
1302 | ActionType::MachineInfo
1303 | ActionType::EnterValue
1304 | ActionType::EnterSecret
1305 | ActionType::GenerateKey
1306 | ActionType::WrapKey
1307 | ActionType::UnwrapKey
1308 | ActionType::ImportKey
1309 | ActionType::EncryptData
1310 | ActionType::DecryptData
1311 | ActionType::SplitSecret
1312 | ActionType::CombineShares
1313 | ActionType::EnterShare
1314 | ActionType::ExportPublic
1315 | ActionType::SignData
1316 | ActionType::VerifySignature
1317 | ActionType::Attest
1318 | ActionType::GatherEntropy
1319 | ActionType::TpmAttest
1320 | ActionType::PivReadCertificate
1321 | ActionType::PivSign
1322 | ActionType::YubikeyAttestSlot
1323 | ActionType::IssueCertificate
1324 | ActionType::GenerateCsr => {}
1325 }
1326 }
1327
1328 let names: std::collections::BTreeSet<String> =
1329 ActionType::ALL.iter().map(ToString::to_string).collect();
1330 assert_eq!(
1331 names.len(),
1332 ActionType::ALL.len(),
1333 "two actions share a DSL name"
1334 );
1335 }
1336}