Skip to main content

substrate/model/
trust.rs

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}