sequoia_wot/
revocation.rs

1use std::cmp;
2use std::time;
3
4use sequoia_openpgp as openpgp;
5use openpgp::types::RevocationStatus as OpenPgpRevocationStatus;
6use openpgp::packet::Signature;
7use openpgp::types::ReasonForRevocation;
8
9use crate::Result;
10use crate::Error;
11
12/// A summary type for OpenPGP's RevocationStatus.
13///
14/// This type is a summary of OpenPGP's [`RevocationStatus`] type that
15/// holds the information that is relevant to web of trust
16/// calculations.
17///
18///   [`RevocationStatus`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/types/enum.RevocationStatus.html
19///
20/// `Soft` and `Hard` refer to the two different types of revocations.
21/// This mapping is according to
22/// [`ReasonForRevocation::revocation_type`].  The `Soft` variant
23/// includes the revocation's creation time.  Note: a revocation's
24/// expiration time is ignored.
25///
26///   [`ReasonForRevocation::revocation_type`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/types/enum.ReasonForRevocation.html#method.revocation_type
27///
28/// This type implements `PartialEq` in the following way: the
29/// stronger a revocation is, the later it sorts.  Thus,
30/// `RevocationStatus::NotAsFarAsWeKnow` sorts first and
31/// `RevocationStatus::Hard` sorts last.  Two `RevocationStatus::Soft`
32/// are ordered by the reverse of their creation time.  Thus, for `t1
33/// < t2`, `Soft(t1) > Soft(t2)`.  This is what we want, since
34/// `Soft(t1)` invalidates at least as much as `Soft(t2)`.  A
35/// consequence of this is that it is possible to use
36/// [`std::cmp::max`] to find the most restrictive revocation.
37///
38/// This type also implements `Default` (it returns
39/// `RevocationStatus::NotAsFarAsWeKnow`), `From<[RevocationStatus]>`
40/// and `TryFrom<[Signature]>`.
41#[derive(Debug, Clone, Eq)]
42pub enum RevocationStatus {
43    NotAsFarAsWeKnow,
44    Soft(time::SystemTime),
45    Hard,
46}
47
48impl RevocationStatus {
49    /// Returns whether the revocation is active as of the reference
50    /// time.
51    ///
52    /// Returns false if this is `RevocationStatus::NotAsFarAsWeKnow`.
53    pub fn in_effect(&self, t: time::SystemTime) -> bool {
54        match self {
55            RevocationStatus::NotAsFarAsWeKnow => false,
56            RevocationStatus::Soft(rev_t) => t >= *rev_t,
57            RevocationStatus::Hard => true,
58        }
59    }
60}
61
62impl Default for RevocationStatus {
63    fn default() -> Self {
64        RevocationStatus::NotAsFarAsWeKnow
65    }
66}
67
68impl Ord for RevocationStatus {
69    /// Order so that strong revocations come later.  This means that a
70    /// soft revocation with an earlier time sorts after a soft
71    /// revocation with a later time.
72    fn cmp(&self, other: &Self) -> cmp::Ordering {
73        use cmp::Ordering::*;
74        use RevocationStatus::*;
75
76        match (self, other) {
77            (NotAsFarAsWeKnow, NotAsFarAsWeKnow) => Equal,
78            (NotAsFarAsWeKnow, Soft(_)) => Less,
79            (NotAsFarAsWeKnow, Hard) => Less,
80
81            (Soft(_), NotAsFarAsWeKnow) => Greater,
82            (Soft(t1), Soft(t2)) => t1.cmp(t2).reverse(),
83            (Soft(_), Hard) => Less,
84
85            (Hard, NotAsFarAsWeKnow) => Greater,
86            (Hard, Soft(_)) => Greater,
87            (Hard, Hard) => Equal,
88        }
89    }
90}
91
92impl PartialOrd for RevocationStatus {
93    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
94        Some(self.cmp(other))
95    }
96}
97
98impl PartialEq for RevocationStatus {
99    fn eq(&self, other: &Self) -> bool {
100        self.cmp(other) == cmp::Ordering::Equal
101    }
102}
103
104impl<'a> From<&OpenPgpRevocationStatus<'a>> for RevocationStatus {
105    fn from(rs: &OpenPgpRevocationStatus<'a>) -> Self {
106        match rs {
107            OpenPgpRevocationStatus::Revoked(sigs) => {
108                sigs.into_iter()
109                    .map(|sig| {
110                        RevocationStatus::try_from(*sig).expect("revocation")
111                    })
112                    .max()
113                    .expect("revoked, but no revocation certificates")
114            }
115            OpenPgpRevocationStatus::CouldBe(_) => {
116                RevocationStatus::NotAsFarAsWeKnow
117            }
118            OpenPgpRevocationStatus::NotAsFarAsWeKnow => {
119                RevocationStatus::NotAsFarAsWeKnow
120            }
121        }
122    }
123}
124
125impl<'a> From<OpenPgpRevocationStatus<'a>> for RevocationStatus {
126    fn from(rs: OpenPgpRevocationStatus<'a>) -> Self {
127        RevocationStatus::from(&rs)
128    }
129}
130
131impl TryFrom<&Signature> for RevocationStatus {
132    type Error = anyhow::Error;
133
134    fn try_from(sig: &Signature) -> Result<Self> {
135        use openpgp::types::SignatureType;
136        use openpgp::types::RevocationType;
137
138        let rev_type = match sig.typ() {
139            SignatureType::KeyRevocation
140            | SignatureType::SubkeyRevocation
141            | SignatureType::CertificationRevocation => {
142                let r: Option<ReasonForRevocation>
143                    = sig.reason_for_revocation().map(|(r, _msg)| r);
144                match r {
145                    None => RevocationType::Hard,
146                    Some(reason) => reason.revocation_type(),
147                }
148            }
149
150            // Not a revocation.
151            _ => return Err(Error::NotARevocationCertificate.into()),
152        };
153
154        let rs = match rev_type {
155            RevocationType::Hard => RevocationStatus::Hard,
156            RevocationType::Soft =>
157                RevocationStatus::Soft(
158                    sig.signature_creation_time()
159                        .unwrap_or(time::UNIX_EPOCH)),
160        };
161
162        Ok(rs)
163    }
164}