1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(rename_all = "snake_case")]
5pub enum CapsuleHostingKind {
6 SelfHosted,
7 Replicate,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum KeyTrustRefreshPolicy {
13 Expired,
14 Always,
15 Offline,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum KeyTrustWitnessPolicy {
21 Allow,
22 RequireDomain,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum DomainKeyConfirmation {
28 Confirmed,
29 Rejected,
30 Unreachable,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum KeyTrustClass {
36 FirstClass,
37 SecondClass,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum KeyTrustWarning {
43 SynapseSource,
44 SynapseWitness,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum KeyTrustFailure {
50 OfflineCacheRequired,
51 DomainRejected,
52 DomainUnreachableWitnessDisabled,
53 DomainUnreachableWitnessRejected,
54 DomainUnreachableNoSynapse,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum KeyTrustDecision {
59 Trusted {
60 trust_class: KeyTrustClass,
61 cache_key: bool,
62 warning: Option<KeyTrustWarning>,
63 },
64 Untrusted {
65 reason: KeyTrustFailure,
66 },
67}
68
69pub fn classify_capsule_hosting(uri_domain: &str, core_domain: &str) -> CapsuleHostingKind {
70 if uri_domain == core_domain {
71 CapsuleHostingKind::SelfHosted
72 } else {
73 CapsuleHostingKind::Replicate
74 }
75}
76
77pub fn evaluate_signed_capsule_validity(
78 core_signature_valid: bool,
79 capsule_signature_valid: bool,
80) -> bool {
81 core_signature_valid && capsule_signature_valid
82}
83
84pub fn needs_key_trust_refresh(
85 key_trusted_in_cache: bool,
86 refresh_policy: KeyTrustRefreshPolicy,
87) -> std::result::Result<bool, KeyTrustFailure> {
88 match refresh_policy {
89 KeyTrustRefreshPolicy::Expired => Ok(!key_trusted_in_cache),
90 KeyTrustRefreshPolicy::Always => Ok(true),
91 KeyTrustRefreshPolicy::Offline if key_trusted_in_cache => Ok(false),
92 KeyTrustRefreshPolicy::Offline => Err(KeyTrustFailure::OfflineCacheRequired),
93 }
94}
95
96pub fn decide_key_trust(
97 domain_confirmation: DomainKeyConfirmation,
98 witness_policy: KeyTrustWitnessPolicy,
99 from_synapse: bool,
100 synapse_confirms_key: Option<bool>,
101) -> KeyTrustDecision {
102 match domain_confirmation {
103 DomainKeyConfirmation::Confirmed => KeyTrustDecision::Trusted {
104 trust_class: KeyTrustClass::FirstClass,
105 cache_key: true,
106 warning: None,
107 },
108 DomainKeyConfirmation::Rejected => KeyTrustDecision::Untrusted {
109 reason: KeyTrustFailure::DomainRejected,
110 },
111 DomainKeyConfirmation::Unreachable => {
112 if matches!(witness_policy, KeyTrustWitnessPolicy::RequireDomain) {
113 return KeyTrustDecision::Untrusted {
114 reason: KeyTrustFailure::DomainUnreachableWitnessDisabled,
115 };
116 }
117
118 if from_synapse {
119 return KeyTrustDecision::Trusted {
120 trust_class: KeyTrustClass::SecondClass,
121 cache_key: false,
122 warning: Some(KeyTrustWarning::SynapseSource),
123 };
124 }
125
126 match synapse_confirms_key {
127 Some(true) => KeyTrustDecision::Trusted {
128 trust_class: KeyTrustClass::SecondClass,
129 cache_key: false,
130 warning: Some(KeyTrustWarning::SynapseWitness),
131 },
132 Some(false) => KeyTrustDecision::Untrusted {
133 reason: KeyTrustFailure::DomainUnreachableWitnessRejected,
134 },
135 None => KeyTrustDecision::Untrusted {
136 reason: KeyTrustFailure::DomainUnreachableNoSynapse,
137 },
138 }
139 }
140 }
141}
142
143#[cfg(test)]
144#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
145mod tests {
146
147 use super::*;
148
149 #[test]
150 fn test_classify_capsule_hosting() {
151 assert_eq!(
152 classify_capsule_hosting("cmn.dev", "cmn.dev"),
153 CapsuleHostingKind::SelfHosted
154 );
155 assert_eq!(
156 classify_capsule_hosting("mirror.dev", "cmn.dev"),
157 CapsuleHostingKind::Replicate
158 );
159 }
160
161 #[test]
162 fn test_needs_key_trust_refresh() {
163 assert!(!needs_key_trust_refresh(true, KeyTrustRefreshPolicy::Expired).unwrap());
164 assert!(needs_key_trust_refresh(false, KeyTrustRefreshPolicy::Always).unwrap());
165 assert_eq!(
166 needs_key_trust_refresh(false, KeyTrustRefreshPolicy::Offline),
167 Err(KeyTrustFailure::OfflineCacheRequired)
168 );
169 }
170
171 #[test]
172 fn test_decide_key_trust() {
173 assert_eq!(
174 decide_key_trust(
175 DomainKeyConfirmation::Confirmed,
176 KeyTrustWitnessPolicy::Allow,
177 false,
178 None
179 ),
180 KeyTrustDecision::Trusted {
181 trust_class: KeyTrustClass::FirstClass,
182 cache_key: true,
183 warning: None,
184 }
185 );
186 assert_eq!(
187 decide_key_trust(
188 DomainKeyConfirmation::Unreachable,
189 KeyTrustWitnessPolicy::Allow,
190 false,
191 Some(true)
192 ),
193 KeyTrustDecision::Trusted {
194 trust_class: KeyTrustClass::SecondClass,
195 cache_key: false,
196 warning: Some(KeyTrustWarning::SynapseWitness),
197 }
198 );
199 assert_eq!(
200 decide_key_trust(
201 DomainKeyConfirmation::Rejected,
202 KeyTrustWitnessPolicy::Allow,
203 false,
204 None
205 ),
206 KeyTrustDecision::Untrusted {
207 reason: KeyTrustFailure::DomainRejected,
208 }
209 );
210 }
211}