Skip to main content

sequoia_wot/
certification.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::collections::HashMap;
4use std::collections::hash_map::Entry;
5use std::fmt;
6use std::time::SystemTime;
7use std::time::Duration;
8
9use sequoia_openpgp as openpgp;
10use openpgp::cert::prelude::*;
11use openpgp::KeyHandle;
12use openpgp::packet::UserID;
13use openpgp::regex::RegexSet;
14use openpgp::packet::Signature;
15use openpgp::policy::HashAlgoSecurity;
16
17use crate::CertSynopsis;
18use crate::format_time;
19use crate::Result;
20use crate::RevocationStatus;
21
22use crate::TRACE;
23
24/// [`Certification`] specific error codes.
25#[non_exhaustive]
26#[derive(thiserror::Error, Debug)]
27pub enum CertificationError {
28    /// No creation time.
29    ///
30    /// The certification is invalid, because it does not include the
31    /// required CreationTime subpacket.
32    #[error("{0}: invalid, missing creation time")]
33    MissingCreationTime(Certification),
34
35    /// The certification violates the policy.
36    #[error("{0}: policy violation")]
37    InvalidCertification(Certification, #[source] anyhow::Error),
38
39    #[error("{0}: issuer revoked the certification")]
40    IssuerRevoked(Certification),
41
42    #[error("{0}: certification created after reference time ({time})",
43            time=format_time(.1))]
44    BornLater(Certification, SystemTime),
45
46    /// The certification is expired (1) as of the reference time (2).
47    #[error("{0}: certification expired ({time1}) as of reference time ({time2})",
48            time1=format_time(.1), time2=format_time(.2))]
49    CertificationExpired(Certification, SystemTime, SystemTime),
50
51    #[error("{0}: target is not live \
52             as of the certification time ({time})",
53            time=format_time(.1))]
54    TargetNotLive(Certification, SystemTime, #[source] anyhow::Error),
55
56    #[error("{0}: target certificate is not valid \
57             as of the certification time ({time})",
58            time=format_time(.1))]
59    TargetNotValid(Certification, SystemTime, #[source] anyhow::Error),
60
61    #[error("{0}: issuer certificate is hard revoked: {1} ({msg})",
62            msg=String::from_utf8_lossy(.2))]
63    IssuerHardRevoked(Certification,
64                      openpgp::types::ReasonForRevocation, Vec<u8>),
65
66    #[error("{0}: issuer certificate is soft revoked \
67             as of the certification time ({time}): {2} ({msg})",
68            time=format_time(.1),
69            msg=String::from_utf8_lossy(.3))]
70    IssuerSoftRevoked(Certification, SystemTime,
71                      openpgp::types::ReasonForRevocation, Vec<u8>),
72
73    #[error("{0}: target certificate is hard revoked: {1} ({msg})",
74            msg=String::from_utf8_lossy(.2))]
75    TargetHardRevoked(Certification,
76                      openpgp::types::ReasonForRevocation, Vec<u8>),
77
78    #[error("{0}: target certificate is soft revoked \
79             as of the certification time ({time}): {2} ({msg})",
80            time=format_time(.1),
81            msg=String::from_utf8_lossy(.3))]
82    TargetSoftRevoked(Certification, SystemTime,
83                      openpgp::types::ReasonForRevocation, Vec<u8>),
84}
85
86/// Trust depth.
87///
88/// A certification may include a [trust signature subpacket], which
89/// specifies that the issuer not only considers the target binding to
90/// be correct, but that they are also willing to rely on certifications
91/// that the target certificate makes.  That is, if Alice designates
92/// Bob as a trusted introducer, than if Carol is willing to rely on
93/// Alice's certifications, she should also be willing to rely on
94/// Bob's.
95///
96/// The trust depth is one of two parameters stored in the trust
97/// signature subpacket, and indicates how far that capability may be
98/// delegated.  A value of zero means that the certification is
99/// actually a normal certification, and the target is *not* a trusted
100/// introducer.  A value of one means that the target certificate
101/// should be considered a trusted introducer, but it may not further
102/// delegate that capability.  A value of two means that the target
103/// certificate should be considered a trusted introducer, and that it
104/// may delegate that capability to another certificate, but they may
105/// not further delegate it.  In short, a value of `n` means that
106/// there may be up to `n` intervening trusted introducers between the
107/// issuer and the target binding:
108///
109///   - 0: Normal certification.
110///   - 1: Trusted introducer.
111///   - 2: Meta-introducer.
112///   - etc.
113///
114/// A value of 255, the maximum value that can be stored in the
115/// OpenPGP data structure, has a special meaning: the issuer does not
116/// impose a constraint on the number of delegations.
117///
118/// This data structure does not automatically convert a value of 255
119/// to `Depth::Unconstrained`; the caller must specify
120/// `Depth::Unconstrained` explicitly.
121///
122///   [trust signature subpacket]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.13
123#[derive(Debug, Clone, Copy, Eq)]
124pub enum Depth {
125    Unconstrained,
126    Limit(usize),
127}
128
129impl Depth {
130    pub fn new<I>(depth: I) -> Self
131        where I: Into<Option<usize>>
132    {
133        if let Some(d) = depth.into() {
134            Depth::Limit(d)
135        } else {
136            Depth::Unconstrained
137        }
138    }
139
140    /// Returns an unconstrained `Depth`.
141    pub fn unconstrained() -> Self {
142        Depth::Unconstrained
143    }
144
145    /// Returns whether this `Depth` is unconstrained.
146    pub fn is_unconstrained(&self) -> bool {
147        matches!(self, Depth::Unconstrained)
148    }
149
150    /// Returns whether this `Depth` allows introducing.
151    pub fn can_introduce(&self) -> bool {
152        match self {
153            Depth::Unconstrained => true,
154            Depth::Limit(d) if *d > 0 => true,
155            _ => false
156        }
157    }
158
159    /// Converts the `Depth` to an `Option<usize>`.
160    ///
161    /// An unconstrained depth is converted to `None`.  A constrained
162    /// depth of `d` is converted to `Some(d)`.
163    pub fn limit(&self) -> Option<usize> {
164        match self {
165            Depth::Unconstrained => None,
166            Depth::Limit(d) => Some(*d),
167        }
168    }
169
170    /// Decreases the depth by `value`.
171    ///
172    /// The depth must be at least as large as `value`.  If the depth
173    /// is unconstrained, decreasing the depth doesn't do anything.
174    pub fn decrease(&self, value: usize) -> Depth {
175        match self {
176            Depth::Unconstrained => {
177                // Still unconstrained.
178                Depth::Unconstrained
179            }
180            Depth::Limit(d) => {
181                assert!(*d >= value);
182                Depth::Limit(d - value)
183            }
184        }
185    }
186}
187
188impl fmt::Display for Depth {
189    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190        match self {
191            Depth::Unconstrained => write!(f, "unconstrained"),
192            Depth::Limit(d) => write!(f, "{}", d),
193        }
194    }
195}
196
197impl From<usize> for Depth {
198    fn from(d: usize) -> Self {
199        Depth::new(d)
200    }
201}
202
203impl From<Option<usize>> for Depth {
204    fn from(d: Option<usize>) -> Self {
205        Depth::new(d)
206    }
207}
208
209impl PartialOrd for Depth {
210    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
211        Some(self.cmp(other))
212    }
213}
214
215impl Ord for Depth {
216    fn cmp(&self, other: &Self) -> Ordering {
217        match (self, other) {
218            (Depth::Unconstrained, Depth::Unconstrained) => Ordering::Equal,
219            (Depth::Limit(_), Depth::Unconstrained) => Ordering::Less,
220            (Depth::Unconstrained, Depth::Limit(_)) => Ordering::Greater,
221            (Depth::Limit(x), Depth::Limit(y)) => x.cmp(&y),
222        }
223    }
224}
225
226impl PartialEq for Depth {
227    fn eq(&self, other: &Self) -> bool {
228        self.cmp(other) == Ordering::Equal
229    }
230}
231
232/// Encapsulates a certification.
233///
234/// This data structure holds the information about a certification
235/// that is relevant to web of trust calculations.  Note: this data
236/// structure includes the certification's context (the issuer and the
237/// certified binding), which is not included in an OpenPGP signature.
238///
239/// If the User ID is None, then this is a delegation.
240#[derive(Clone)]
241pub struct Certification {
242    issuer: CertSynopsis,
243    target: CertSynopsis,
244    // If None, it's a delegation.
245    userid: Option<UserID>,
246
247    creation_time: SystemTime,
248    expiration_time: Option<SystemTime>,
249
250    exportable: bool,
251
252    /// 60: partial trust.
253    /// 120: complete trust.
254    amount: usize,
255
256    /// Trust depth.
257    depth: Depth,
258
259    /// Scope.  If None, then the Regexes are invalid and nothing
260    /// should match.
261    re_set: Option<RegexSet>,
262    /// RegexSet doesn't implement PartialEq.  To allow Certification
263    /// to implement PartialEq, we store the bytes.
264    re_bytes: Vec<Vec<u8>>,
265
266    /// The digest prefix.
267    digest_prefix: Option<[u8; 2]>,
268}
269
270impl<'a> From<(&'a ValidCert<'a>, &'a ValidCert<'a>, &'a Signature)>
271    for Certification
272{
273    fn from(x: (&ValidCert, &ValidCert, &Signature)) -> Self {
274        Certification::from_signature(
275            x.0,
276            x.1.primary_userid().ok().map(|ua| ua.userid().clone()),
277            x.1,
278            x.2)
279    }
280}
281
282impl PartialEq for Certification {
283    fn eq(&self, other: &Self) -> bool {
284        self.issuer.fingerprint() == other.issuer.fingerprint()
285            && self.target.fingerprint() == other.target.fingerprint()
286            && self.userid == other.userid
287            && self.creation_time == other.creation_time
288            && self.expiration_time == other.expiration_time
289            && self.exportable == other.exportable
290            && self.amount == other.amount
291            && self.depth == other.depth
292            // RegexSet doesn't implement eq.
293            && self.re_bytes == other.re_bytes
294            // We explicitly don't check digest_prefix as we may not
295            // have it.  This isn't bad as if everything else the
296            // same, they are the same!
297    }
298}
299
300impl fmt::Display for Certification {
301    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
302        write!(f, "{}by {} on {} at {}",
303               if let Some(digest_prefix) = self.digest_prefix {
304                   format!("{:02X}{:02X} ",
305                           digest_prefix[0], digest_prefix[1])
306               } else {
307                   "".to_string()
308               },
309               self.issuer.keyid(),
310               self.target.keyid(),
311               format_time(&self.creation_time))
312    }
313}
314
315impl fmt::Debug for Certification {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        f.debug_struct("Certification")
318            .field("issuer", &self.issuer.fingerprint())
319            .field("target", &self.target)
320            .field("userid",
321                   &self.userid.as_ref().map(|uid| {
322                       String::from_utf8_lossy(uid.value()).into_owned()
323                   })
324                   .unwrap_or_else(|| "<None>".into()))
325            .field("creation time",
326                   &self.creation_time
327                       .duration_since(SystemTime::UNIX_EPOCH)
328                       .unwrap_or_else(|_| Duration::new(0, 0)))
329            .field("expiration time",
330                   &if let Some(e) = self.expiration_time {
331                       format!("{:?}",
332                                e
333                                .duration_since(SystemTime::UNIX_EPOCH)
334                                .unwrap_or_else(|_| Duration::new(0, 0)))
335                   } else {
336                       "never".to_string()
337                   })
338            .field("amount", &self.amount)
339            .field("depth", &self.depth)
340            .field("regexes",
341                   &if let Some(re_set) = self.re_set.as_ref() {
342                       if re_set.matches_everything() {
343                           String::from("*")
344                       } else {
345                           format!("{:?}", &re_set)
346                       }
347                   } else {
348                       String::from("<invalid RE>")
349                   })
350            .finish()
351    }
352}
353
354impl Certification {
355    /// Returns a `Certification`.
356    ///
357    /// The returned certification's amount is set to 120 (fully
358    /// trusted), its depth to 0 (it's not a trusted introducer), and
359    /// no regular expression is set.
360    ///
361    /// # Examples
362    ///
363    /// `0xAA` (bob@example.org) certifies the binding `<0xBB,
364    /// bob@example.org>`.
365    ///
366    /// ```
367    /// use std::iter;
368    /// use std::time::SystemTime;
369    ///
370    /// use sequoia_openpgp as openpgp;
371    /// use openpgp::Fingerprint;
372    /// use openpgp::parse::Parse;
373    ///
374    /// use sequoia_wot::CertSynopsis;
375    /// use sequoia_wot::UserIDSynopsis;
376    /// use sequoia_wot::Certification;
377    /// use sequoia_wot::RevocationStatus;
378    ///
379    /// let alice_fpr: Fingerprint =
380    ///     "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
381    ///    .parse().expect("valid fingerprint");
382    /// let alice_uid
383    ///     = UserIDSynopsis::from("<alice@example.org>");
384    ///
385    /// let bob_fpr: Fingerprint =
386    ///     "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
387    ///    .parse().expect("valid fingerprint");
388    /// let bob_uid
389    ///     = UserIDSynopsis::from("<bob@example.org>");
390    ///
391    /// let alice = CertSynopsis::new(
392    ///     alice_fpr, None, RevocationStatus::NotAsFarAsWeKnow,
393    ///     iter::once(alice_uid));
394    /// let bob = CertSynopsis::new(
395    ///     bob_fpr, None, RevocationStatus::NotAsFarAsWeKnow,
396    ///     iter::once(bob_uid.clone()));
397    ///
398    /// let c = Certification::new(
399    ///     alice, Some(bob_uid.userid().clone()), bob,
400    ///     SystemTime::now());
401    /// ```
402    pub fn new<C1, U, C2>(issuer: C1,
403                          userid: Option<U>,
404                          target: C2,
405                          creation_time: SystemTime)
406        -> Self
407        where C1: Into<CertSynopsis>,
408              U: Into<UserID>,
409              C2: Into<CertSynopsis>,
410    {
411        let issuer = issuer.into();
412        let target = target.into();
413
414        Certification {
415            issuer: issuer,
416            userid: userid.map(Into::into),
417            target: target,
418            creation_time: creation_time,
419            expiration_time: None,
420            exportable: true,
421            depth: Depth::new(0),
422            amount: 120,
423            re_set: Some(RegexSet::everything()),
424            re_bytes: Vec::new(),
425            digest_prefix: None,
426        }
427    }
428
429    /// Creates a `Certification` from a `Signature`.
430    ///
431    /// `userid` and `target` are the certified binding.  If no User
432    /// ID is supplied, this is interpreted as a delegation.
433    ///
434    /// The signature is assumed to be valid.
435    ///
436    /// If the signature does not have a signature creation time
437    /// (which technically makes the signature [invalid]), it defaults
438    /// to the [Unix epoch].
439    ///
440    ///   [invalid]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.4
441    ///   [Unix epoch]: https://en.wikipedia.org/wiki/Unix_time
442    pub fn from_signature<C1, U, C2>(issuer: C1,
443                                     userid: Option<U>,
444                                     target: C2,
445                                     sig: &Signature)
446        -> Self
447        where C1: Into<CertSynopsis>,
448              U: Into<UserID>,
449              C2: Into<CertSynopsis>,
450    {
451        let (d, a, r) = if let Some((d, a)) = sig.trust_signature()
452        {
453            (d as usize,
454             a as usize,
455             Some(sig.regular_expressions()))
456        } else {
457            (0, 120, None)
458        };
459
460        let mut c = Self::new(issuer, userid, target,
461                              sig.signature_creation_time()
462                                  .unwrap_or(std::time::UNIX_EPOCH))
463            .set_amount(a)
464            .set_depth(Depth::new(if d == 255 { None } else { Some(d) }));
465        if let Some(r) = r {
466            let r: Vec<&[u8]> = r.collect();
467            c = c.set_regular_expressions(r.iter().cloned());
468            c.re_bytes = r.into_iter().map(<[u8]>::to_vec).collect();
469        }
470        if let Some(e) = sig.signature_expiration_time() {
471            c = c.set_expiration_time(Some(e));
472        }
473        c = c.set_exportable(sig.exportable_certification().unwrap_or(true));
474
475        c.digest_prefix = Some(*sig.digest_prefix());
476
477        c
478    }
479
480    /// Returns a certification, if the certification is valid.
481    ///
482    /// This function is different from
483    /// `Certification::from_signature`, which works with Synopses,
484    /// and assumes the certification is valid.  This function checks
485    /// that the certification is valid, and only returns a
486    /// `Certification` if that is the case.
487    ///
488    /// Note a signature may have multiple [Issuer] or Issuer
489    /// Fingerprint packets.  This function ignores those and checks
490    /// whether the provided certificate issued the certificate.
491    /// Normally, you'll want to call this function in a loop, once
492    /// for each of the alleged issuers.
493    ///
494    /// [Issuer]: https://www.rfc-editor.org/rfc/rfc4880#section-5.2.3.5
495    pub fn try_from_signature(possible_issuer: &ValidCert,
496                              ua: Option<&UserIDAmalgamation>,
497                              target: &ValidCert,
498                              certification: &Signature)
499        -> Result<Self>
500    {
501        tracer!(TRACE, "Certification::try_from_signature");
502
503        let reference_time = target.time();
504
505        let certification_time =
506            if let Some(t) = certification.signature_creation_time() {
507                t
508            } else {
509                return Err(CertificationError::MissingCreationTime(
510                    (possible_issuer, target, certification).into()).into());
511            };
512
513        let verify = |possible_issuer: &ValidCert| -> Result<Certification>
514        {
515            if let Err(err) = target.policy()
516                .signature(
517                    certification, HashAlgoSecurity::CollisionResistance)
518            {
519                return Err(CertificationError::InvalidCertification(
520                    (possible_issuer, target, certification).into(),
521                    err).into());
522            }
523
524            certification
525                .clone()
526                .verify_signature(possible_issuer.primary_key().key())?;
527
528            // Ignore if the issuer is not alive at the
529            // certification time (not the reference time!).
530            let possible_issuer_then
531                = possible_issuer.clone().with_policy(
532                    possible_issuer.policy(), certification_time)?;
533
534            if let Err(err) = possible_issuer_then.alive() {
535                t!("Skipping certification {:02X}{:02X}: issuer \
536                    was not alive at certification time.",
537                   certification.digest_prefix()[0],
538                   certification.digest_prefix()[1]);
539
540                return Err(err.context(
541                    "issuer not alive at certification time"));
542            }
543
544            // Ignore if the issuer was revoked at the
545            // certification time (not the reference time!).
546            let rs = possible_issuer_then.revocation_status();
547            if let openpgp::types::RevocationStatus::Revoked(ref revs) = rs {
548                // We know we have at least one revocation.
549                let reason = revs.iter().next().expect("have one")
550                    .reason_for_revocation();
551                let msg = reason
552                    .map(|r| r.1.to_vec())
553                    .unwrap_or(Vec::new());
554                let code = reason
555                    .map(|r| r.0)
556                    .unwrap_or(openpgp::types::ReasonForRevocation::Unspecified);
557
558                match RevocationStatus::from(rs) {
559                    RevocationStatus::Hard => {
560                        t!("Skipping certification {:02X}{:02X}: issuer \
561                            was hard revoked.",
562                           certification.digest_prefix()[0],
563                           certification.digest_prefix()[1]);
564                        return Err(CertificationError::IssuerHardRevoked(
565                            (possible_issuer, target, certification).into(),
566                            code, msg).into());
567                    }
568                    RevocationStatus::Soft(rev_time) => {
569                        if rev_time <= certification_time {
570                            t!("Skipping certification {:02X}{:02X}: issuer \
571                                was soft revoked at certification time.",
572                               certification.digest_prefix()[0],
573                               certification.digest_prefix()[1]);
574                            return Err(CertificationError::IssuerSoftRevoked(
575                                (possible_issuer, target, certification).into(),
576                                certification_time, code, msg).into());
577                        }
578                    }
579                    RevocationStatus::NotAsFarAsWeKnow => unreachable!(),
580                }
581            }
582
583            let issuer: KeyHandle
584                = possible_issuer.fingerprint().into();
585
586            // Ignore if the issuer later revoked the
587            // certification.
588            if let Some(ua) = ua {
589                for rev in ua.other_revocations() {
590                    // We have a UserIDAmalgamation, not a
591                    // ValidUserIDAmalgamation, so we need to check
592                    // that the revocation is valid under the current
593                    // policy ourselves.
594                    if target.policy()
595                        .signature(
596                            // XXX: Is this right for revocations?
597                            rev, HashAlgoSecurity::CollisionResistance)
598                        .is_err()
599                    {
600                        continue;
601                    }
602
603                    // All User ID revocations are soft
604                    // revocations.  So if the User ID was later
605                    // recertified, that's okay.
606                    if let Some(rev_time) = rev.signature_creation_time() {
607                        if rev_time > reference_time {
608                            // Revocation is not yet live.  Ignore it.
609                            continue;
610                        }
611                        if rev_time <= certification_time {
612                            // Certification is newer than the
613                            // revocation.  Ignore the revocation.
614                            continue;
615                        }
616                    } else {
617                        // Invalid signature.
618                        continue;
619                    };
620
621                    // We explicitly ignore any expiration on
622                    // revocations.
623
624                    // Check that the issuer actually issued this
625                    // revocation.
626                    if rev.get_issuers().iter().any(|kh| {
627                        kh.aliases(&issuer)
628                    }) {
629                        if let Ok(()) = rev
630                            .clone()
631                            .verify_signature(possible_issuer.primary_key().key())
632                        {
633                            t!("issuer revoked certification, ignoring");
634                            return Err(
635                                CertificationError::IssuerRevoked((
636                                    possible_issuer, target, certification).into())
637                                    .into());
638                        }
639                    }
640                }
641            }
642
643
644            let (depth, amount, re_set) = if let Some((d, a))
645                = certification.trust_signature()
646            {
647                (d, a, RegexSet::from_signature(certification)
648                 .expect("internal error"))
649            } else {
650                (0, 120, RegexSet::everything())
651            };
652
653            t!("<{}, {}> {} <{}, {}> \
654                (depth: {}, amount: {}, scope: {:?})",
655               possible_issuer.cert().keyid(),
656               possible_issuer
657               .primary_userid()
658               .map(|ua| {
659                   String::from_utf8_lossy(ua.userid().value()).into_owned()
660               })
661               .unwrap_or("[no User ID]".into()),
662               if depth > 0 {
663                   "tsigned"
664               } else {
665                   "certified"
666               },
667               target.keyid(),
668               ua.map(|ua| String::from_utf8_lossy(ua.userid().value()))
669                   .unwrap_or(Cow::Borrowed("(delegation)")),
670               depth,
671               amount,
672               if re_set.matches_everything() {
673                   "*".into()
674               } else {
675                   format!("{:?}", re_set)
676               });
677
678            Ok(Certification::from_signature(
679                possible_issuer, ua.map(|ua| ua.userid().clone()), target,
680                certification))
681        };
682
683        // Ignore if the certification is not alive at the
684        // reference time.
685        if reference_time < certification_time {
686            t!("Skipping certification {:02X}{:02X}: \
687                created ({:?}) after reference time ({:?}).",
688               certification.digest_prefix()[0],
689               certification.digest_prefix()[1],
690               certification_time, reference_time);
691            return Err(CertificationError::BornLater(
692                (possible_issuer, target, certification).into(),
693                reference_time).into());
694        }
695        if let Some(e) = certification.signature_expiration_time() {
696            if e <= reference_time {
697                t!("Skipping certification {:02X}{:02X}: \
698                    expired ({:?}) as of reference time ({:?}).",
699                   certification.digest_prefix()[0],
700                   certification.digest_prefix()[1],
701                   e, reference_time);
702                return Err(CertificationError::CertificationExpired(
703                    (possible_issuer, target, certification).into(),
704                    e, reference_time).into());
705            }
706        }
707
708        let target_then =
709            match target.clone()
710            .with_policy(target.policy(), certification_time)
711        {
712            Ok(vc) => vc,
713            Err(err) => {
714                t!("Skipping certification {:02X}{:02X}: target \
715                    was not valid at certification time: {}.",
716                   certification.digest_prefix()[0],
717                   certification.digest_prefix()[1],
718                   err);
719                return Err(CertificationError::TargetNotValid(
720                    (possible_issuer, target, certification).into(),
721                    certification_time, err).into());
722            }
723        };
724
725        // Ignore if the target is not alive at the
726        // certification time (not the reference time!).
727        if let Err(err) = target_then.alive() {
728            t!("Skipping certification {:02X}{:02X}: target \
729                not alive at certification time: {}.",
730               certification.digest_prefix()[0],
731               certification.digest_prefix()[1],
732               err);
733            return Err(CertificationError::TargetNotLive(
734                (possible_issuer, target, certification).into(),
735                certification_time, err).into());
736        }
737
738        // Ignore if the target was revoked at the
739        // certification time (not the reference time!).
740        let rs = target_then.revocation_status();
741        if let openpgp::types::RevocationStatus::Revoked(ref revs) = rs {
742            // We know we have at least one revocation.
743            let reason = revs.iter().next().expect("have one")
744                .reason_for_revocation();
745            let msg = reason
746                .map(|r| r.1.to_vec())
747                .unwrap_or(Vec::new());
748            let code = reason
749                .map(|r| r.0)
750                .unwrap_or(openpgp::types::ReasonForRevocation::Unspecified);
751
752            match RevocationStatus::from(rs) {
753                RevocationStatus::Hard => {
754                    t!("Skipping certification {:02X}{:02X}: target \
755                        was hard revoked at certification time.",
756                       certification.digest_prefix()[0],
757                       certification.digest_prefix()[1]);
758                    return Err(CertificationError::TargetHardRevoked(
759                        (possible_issuer, target, certification).into(),
760                        code, msg).into());
761                }
762                RevocationStatus::Soft(rev_time) => {
763                    if rev_time <= certification_time {
764                        t!("Skipping certification {:02X}{:02X}: target \
765                            was soft revoked at certification time.",
766                           certification.digest_prefix()[0],
767                           certification.digest_prefix()[1]);
768                        return Err(CertificationError::TargetSoftRevoked(
769                            (possible_issuer, target, certification).into(),
770                            certification_time, code, msg).into());
771                    }
772                }
773                RevocationStatus::NotAsFarAsWeKnow => unreachable!(),
774            }
775        }
776
777        match verify(&possible_issuer) {
778            Ok(certification) => {
779                t!("Using certification \
780                    by {} for <{:?}, {}> at {:?}: \
781                    {}/{}.",
782                   possible_issuer,
783                   ua.map(|ua| String::from_utf8_lossy(ua.userid().value()))
784                       .unwrap_or(Cow::Borrowed("(delegation)")),
785                   target.keyid(),
786                   certification.creation_time(),
787                   certification.depth(),
788                   certification.amount());
789
790                Ok(certification)
791            }
792            Err(err) => {
793                t!("Invalid certification {:02X}{:02X} \
794                    by {} for <{:?}, {}>: {}",
795                   certification.digest_prefix()[0],
796                   certification.digest_prefix()[1],
797                   possible_issuer,
798                   ua.map(|ua| String::from_utf8_lossy(ua.userid().value()))
799                       .unwrap_or(Cow::Borrowed("(delegation)")),
800                   target.keyid(),
801                   err);
802                Err(err)
803            }
804        }
805    }
806
807    /// Returns the certification's issuer.
808    pub fn issuer(&self) -> &CertSynopsis {
809        &self.issuer
810    }
811
812    /// Returns the certification's target certificate.
813    pub fn target(&self) -> &CertSynopsis {
814        &self.target
815    }
816
817    /// Returns the certification's target UserID, if any.
818    pub fn userid(&self) -> Option<&UserID> {
819        self.userid.as_ref()
820    }
821
822    /// Returns the certification's creation time.
823    pub fn creation_time(&self) -> SystemTime {
824        self.creation_time
825    }
826
827    /// Returns the certification's expiration time, if any.
828    pub fn expiration_time(&self) -> Option<SystemTime> {
829        self.expiration_time
830    }
831
832    /// Sets the certification's expiration time.
833    pub fn set_expiration_time<I>(mut self, expiration_time: I) -> Self
834        where I: Into<Option<SystemTime>>
835    {
836        self.expiration_time = expiration_time.into();
837        self
838    }
839
840    /// Returns whether the certification is marked as exportable (i.e.,
841    /// not a so-called local signature).
842    pub fn exportable(&self) -> bool {
843        self.exportable
844    }
845
846    /// Sets whether the certification is marked as exportable (i.e.,
847    /// not a so-called local signature).
848    pub fn set_exportable(mut self, exportable: bool) -> Self {
849        self.exportable = exportable;
850        self
851    }
852
853    /// Returns the certification's trust amount.
854    pub fn amount(&self) -> usize {
855        self.amount
856    }
857
858    /// Sets the certification's trust amount.
859    pub fn set_amount(mut self, amount: usize) -> Self {
860        self.amount = amount;
861        self
862    }
863
864    /// Returns the certification's trust depth.
865    pub fn depth(&self) -> Depth {
866        self.depth
867    }
868
869    /// Sets the certification's trust depth.
870    ///
871    /// Note: this function does not automatically convert the value
872    /// `255` to `Depth::Unconstrained`.
873    pub fn set_depth<I>(mut self, depth: I) -> Self
874    where I: Into<Depth>
875    {
876        self.depth = depth.into();
877        self
878    }
879
880    /// Returns the certification's regular expressions.
881    ///
882    /// If the signature has none, this returns a regular expression
883    /// that matches everything.
884    ///
885    /// If any of the regular expressions were invalid, this returns
886    /// `None`.
887    pub fn regular_expressions(&self) -> Option<&RegexSet> {
888        self.re_set.as_ref()
889    }
890
891    /// Returns the certification's regular expressions as a slice of
892    /// byte strings.
893    pub fn regular_expressions_bytes(&self) -> &[Vec<u8>] {
894        &self.re_bytes[..]
895    }
896
897    /// Sets the certification's regular expressions.
898    pub fn set_regular_expressions<'a>(mut self,
899                                       re_set: impl Iterator<Item=&'a [u8]>)
900        -> Self
901    {
902        let regexes: Vec<&[u8]> = re_set.collect();
903        self.re_set = RegexSet::from_bytes(&regexes).ok();
904        self.re_bytes = regexes.into_iter().map(Into::into).collect();
905        self
906    }
907
908    /// Returns the certification's digest prefix, if it is known.
909    pub fn digest_prefix(&self) -> Option<&[u8; 2]> {
910        self.digest_prefix.as_ref()
911    }
912}
913
914/// All active certifications that one certificate made on another.
915///
916/// Encapsulates the *active* certifications with respect to a
917/// reference time that a certificate made on another certificate.
918/// For instance, if the certificate 0xB has two User IDs: B and B'
919/// and 0xA signed both, then this contains the latest certification
920/// for <0xA, B:0xB> and the latest certification for <0xA, B':0xB>.
921#[derive(Clone)]
922pub struct CertificationSet {
923    // The certificate that issued the certifications.
924    issuer: CertSynopsis,
925    // The certificate that was signed.
926    target: CertSynopsis,
927
928    reference_time: SystemTime,
929
930    // The certifications, keyed by the certified (target) User ID.
931    // It is reasonable to have multiple certifications over the same
932    // User ID if they all have the same timestamp.
933    certifications: HashMap<Option<UserID>, Vec<Certification>>,
934}
935
936impl CertificationSet {
937    /// Returns an empty CertificationSet.
938    pub(crate) fn empty<I, T>(issuer: I, target: T,
939                              reference_time: SystemTime)
940        -> Self
941    where I: Into<CertSynopsis>,
942          T: Into<CertSynopsis>,
943    {
944        Self {
945            issuer: issuer.into(),
946            target: target.into(),
947            reference_time: reference_time,
948            certifications: HashMap::new(),
949        }
950    }
951
952    /// Returns a new CertificationSet with the supplied
953    /// certification.
954    pub fn from_certification(certification: Certification,
955                              reference_time: SystemTime) -> Self
956    {
957        let mut cs = CertificationSet::empty(
958            certification.issuer.clone(),
959            certification.target.clone(),
960            reference_time);
961        cs.add(certification);
962        cs
963    }
964
965    /// Splits the supplied certifications into CertificationSets.
966    ///
967    /// This function splits the supplied [`Certification`]s into
968    /// `CertificationSet`s.  The certifications may come from
969    /// different issuers, be for different targets, and over
970    /// different User IDs (or just be delegations), and they will be
971    /// added to an appropriate `CertificationSet`.
972    ///
973    /// Any invalid or unneeded certifications are silently pruned.
974    /// For instance, this function discards certifications that were
975    /// created after the reference time, or are expired as of the
976    /// reference time.  It also only keeps the most recent
977    /// certifications for a given `<issuer, target, userid>` tuple.
978    /// (Sometimes there are multiple certifications with the same
979    /// timestamp.  In this case, all of those certificates are kept.)
980    pub fn from_certifications(mut certifications: Vec<Certification>,
981                               reference_time: SystemTime) -> Vec<Self>
982    {
983        if certifications.is_empty() {
984            return Vec::new();
985        }
986
987        certifications.retain(|c| {
988            // Keep it if it is born at or before the reference time
989            c.creation_time <= reference_time
990                // and it expires after the reference time.
991                && c.expiration_time.map(|e| e > reference_time).unwrap_or(true)
992        });
993
994        // Sort them so that they are grouped by <issuer, target, User
995        // ID>, and with each group sorted so that the most recent
996        // certification comes first.
997        certifications.sort_unstable_by(|a, b| {
998            a.issuer().fingerprint().cmp(&b.issuer().fingerprint())
999                .then(a.target().fingerprint().cmp(&b.target().fingerprint()))
1000                .then(a.userid().cmp(&b.userid()))
1001                .then(a.creation_time().cmp(&b.creation_time()).reverse())
1002        });
1003
1004        // Create the CertificationSets.
1005
1006        // The finished CertificationSets.
1007        let mut cs = Vec::new();
1008        // The CertificationSet under construction.
1009        let mut acc: Vec<(Option<UserID>, Vec<Certification>)>
1010            = Vec::with_capacity(certifications.len().min(4));
1011
1012        for certification in certifications.into_iter() {
1013            let group = if let Some(last) = acc.last() {
1014                last
1015            } else {
1016                // First time through.
1017                acc.push((certification.userid().map(Clone::clone),
1018                          vec![ certification ]));
1019                continue;
1020            };
1021
1022            let group_issuer = group.1[0].issuer();
1023            let group_target = group.1[0].target();
1024            let group_userid = group.0.as_ref();
1025            let group_certification_time = group.1[0].creation_time();
1026
1027            if group_issuer.fingerprint()
1028                == certification.issuer().fingerprint()
1029                && group_target.fingerprint()
1030                   == certification.target().fingerprint()
1031            {
1032                // Same CertificationSet.
1033
1034                if group_userid == certification.userid() {
1035                    // Same User ID.
1036
1037                    if group_certification_time
1038                        == certification.creation_time()
1039                    {
1040                        // Same creation time, keep it.
1041                        acc.last_mut().unwrap().1.push(certification);
1042                    } else {
1043                        // The certification is older than what we
1044                        // have; ignore it.
1045                        assert!(certification.creation_time()
1046                                < group_certification_time);
1047                    }
1048                } else {
1049                    // Different User ID.  Start a new group.
1050                    acc.push((certification.userid().map(Clone::clone),
1051                              vec![ certification ]));
1052                }
1053            } else {
1054                // New CertificationSet.
1055                let issuer = acc[0].1[0].issuer().clone();
1056                let target = acc[0].1[0].target().clone();
1057
1058                cs.push(
1059                    CertificationSet {
1060                        issuer,
1061                        target,
1062                        reference_time,
1063                        certifications: HashMap::from_iter(acc),
1064                    });
1065
1066                // Reset the accumulator and start a new group.
1067                acc = vec![(certification.userid().map(Clone::clone),
1068                    vec![ certification ])];
1069            }
1070        }
1071
1072        // Don't forget to add the pending CertificationSet.
1073        let issuer = acc[0].1[0].issuer().clone();
1074        let target = acc[0].1[0].target().clone();
1075
1076        cs.push(
1077            CertificationSet {
1078                issuer,
1079                target,
1080                reference_time,
1081                certifications: HashMap::from_iter(acc),
1082            });
1083
1084        for cs in cs.iter() {
1085            for (userid, certifications) in cs.certifications.iter() {
1086                for certification in certifications.iter() {
1087                    assert_eq!(userid, &certification.userid,
1088                               "Certification with user ID {:?} \
1089                                added to wrong group (user ID: {:?}",
1090                               certification.userid, userid);
1091                }
1092            }
1093        }
1094
1095        cs
1096    }
1097
1098    /// Returns the issuer's certificate.
1099    pub fn issuer(&self) -> &CertSynopsis {
1100        &self.issuer
1101    }
1102
1103    /// Returns the target's certificate.
1104    pub fn target(&self) -> &CertSynopsis {
1105        &self.target
1106    }
1107
1108    /// Returns the reference time.
1109    pub fn reference_time(&self) -> SystemTime {
1110        self.reference_time
1111    }
1112
1113    /// Adds a certification to the CertificationSet.
1114    ///
1115    /// All certifications in a `CertificationSet` must be issued by
1116    /// the same certificate, and have the same reference time.
1117    ///
1118    /// Note: if there are multiple certifications for the same User
1119    /// ID, all are considered.  Normally only the newest
1120    /// certification should be considered.  But, there may be
1121    /// multiple such certifications.
1122    pub(crate) fn add(&mut self, certification: Certification) {
1123        // certification must be over the same certificate.
1124        if let Some((_, cs)) = self.certifications.iter().next() {
1125            for c in cs {
1126                assert_eq!(certification.issuer.fingerprint(),
1127                           c.issuer.fingerprint());
1128                assert_eq!(certification.target.fingerprint(),
1129                           c.target.fingerprint());
1130            }
1131        }
1132
1133        match self.certifications.entry(certification.userid.clone()) {
1134            e @ Entry::Occupied(_) => {
1135                e.and_modify(|e| e.push(certification));
1136            }
1137            e @ Entry::Vacant(_) => {
1138                e.or_insert([ certification ].into());
1139            }
1140        }
1141    }
1142
1143    /// Merges other into self.
1144    ///
1145    /// This function asserts that `self` and `other` are for the same
1146    /// issuer and target certificates.
1147    ///
1148    /// Note: if there are multiple certifications for the same User
1149    /// ID, all are considered.  Normally only the newest
1150    /// certification should be considered.  But, there may be
1151    /// multiple such certifications.
1152    pub(crate) fn merge(&mut self, other: Self) {
1153        assert_eq!(self.issuer.fingerprint(), other.issuer.fingerprint());
1154        assert_eq!(self.target.fingerprint(), other.target.fingerprint());
1155        assert_eq!(self.reference_time, other.reference_time);
1156
1157        for (_, cs) in other.certifications.into_iter() {
1158            for c in cs {
1159                self.add(c);
1160            }
1161        }
1162    }
1163
1164    /// Returns an iterator over all of the user ids, and their
1165    /// certifications.
1166    pub fn certifications(&self)
1167        -> impl Iterator<Item=(Option<&UserID>, &[Certification])>
1168    {
1169        self.certifications.iter().map(|(userid, c)| (userid.as_ref(), &c[..]))
1170    }
1171
1172    /// Returns an iterator over the certifications.
1173    pub fn into_certifications(self)
1174        -> impl Iterator<Item=Certification>
1175    {
1176        self.certifications.into_iter()
1177            .flat_map(|(_userid, c)| c.into_iter())
1178    }
1179}
1180
1181#[cfg(test)]
1182mod test {
1183    use super::*;
1184
1185    use std::iter;
1186    use std::time::Duration;
1187
1188    use sequoia_openpgp as openpgp;
1189    use openpgp::Fingerprint;
1190    use openpgp::Result;
1191
1192    use crate::CertSynopsis;
1193
1194    use crate::Depth;
1195
1196    #[test]
1197    fn depth() -> Result<()> {
1198        assert_eq!(Depth::new(0), Depth::new(0));
1199        assert_eq!(Depth::new(10), Depth::new(10));
1200        assert_eq!(Depth::new(None), Depth::new(None));
1201
1202        assert!(Depth::new(0) < Depth::new(1));
1203        assert!(Depth::new(1) < Depth::new(10));
1204        assert!(Depth::new(10) < Depth::new(None));
1205        assert!(Depth::new(255) < Depth::new(None));
1206        assert!(Depth::new(1000) < Depth::new(None));
1207
1208        assert!(Depth::new(1) > Depth::new(0));
1209        assert!(Depth::new(10) > Depth::new(1));
1210        assert!(Depth::new(None) > Depth::new(10));
1211        assert!(Depth::new(None) > Depth::new(255));
1212        assert!(Depth::new(None) > Depth::new(1000));
1213
1214        assert_eq!(std::cmp::min(Depth::new(0), Depth::new(10)),
1215                   Depth::new(0));
1216        assert_eq!(std::cmp::min(Depth::new(0), Depth::new(None)),
1217                   Depth::new(0));
1218        assert_eq!(std::cmp::min(Depth::new(1000), Depth::new(None)),
1219                   Depth::new(1000));
1220
1221        assert_eq!(std::cmp::min(Depth::new(10), Depth::new(0)),
1222                   Depth::new(0));
1223        assert_eq!(std::cmp::min(Depth::new(None), Depth::new(0)),
1224                   Depth::new(0));
1225        assert_eq!(std::cmp::min(Depth::new(None), Depth::new(1000)),
1226                   Depth::new(1000));
1227
1228        assert_eq!(std::cmp::min(Depth::new(None), Depth::new(None)),
1229                   Depth::new(None));
1230
1231        Ok(())
1232    }
1233
1234    #[test]
1235    fn certification_set_from_certifications() -> Result<()> {
1236        use openpgp::types::RevocationStatus;
1237
1238        let alice_fpr: Fingerprint =
1239            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1240            .parse().expect("valid fingerprint");
1241        let alice_uid = UserID::from("<alice@example.org>");
1242
1243        let alice = CertSynopsis::new(
1244            alice_fpr.clone(), None,
1245            RevocationStatus::NotAsFarAsWeKnow.into(),
1246            iter::once((alice_uid.clone(), crate::now())));
1247
1248        let bob_fpr: Fingerprint =
1249            "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
1250            .parse().expect("valid fingerprint");
1251        let bob_uid = UserID::from("<bob@example.org>");
1252
1253        let bob = CertSynopsis::new(
1254            bob_fpr.clone(), None,
1255            RevocationStatus::NotAsFarAsWeKnow.into(),
1256            iter::once((bob_uid.clone(), crate::now())));
1257
1258        let carol_fpr: Fingerprint =
1259            "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
1260            .parse().expect("valid fingerprint");
1261        let carol_uid = UserID::from("<carol@example.org>");
1262
1263        let carol = CertSynopsis::new(
1264            carol_fpr.clone(), None,
1265            RevocationStatus::NotAsFarAsWeKnow.into(),
1266            iter::once((carol_uid.clone(), crate::now())));
1267
1268        let t = crate::now();
1269
1270        let certifications = vec![
1271            // Alice certifies Bob.
1272            Certification::new(alice.clone(),
1273                               Some(bob_uid.clone()),
1274                               bob.clone(),
1275                               t),
1276            // Alice certifies Bob a second time at the same time.
1277            Certification::new(alice.clone(),
1278                               Some(bob_uid.clone()),
1279                               bob.clone(),
1280                               t),
1281            // Alice certifies Bob a third time at an earlier time
1282            // (this should be ignore because the other two signatures
1283            // are valid).
1284            Certification::new(alice.clone(),
1285                               Some(bob_uid.clone()),
1286                               bob.clone(),
1287                               t - Duration::new(1, 0)),
1288            // Alice certifies Bob a fourth time, but in the future
1289            // (this should be ignore, because it is after the
1290            // reference time).
1291            Certification::new(alice.clone(),
1292                               Some(bob_uid.clone()),
1293                               bob.clone(),
1294                               t + Duration::new(1, 0)),
1295
1296            // Alice certifies Carol.
1297            Certification::new(alice.clone(),
1298                               Some(carol_uid.clone()),
1299                               carol.clone(),
1300                               t),
1301
1302            // Bob certifies Carol.
1303            Certification::new(bob.clone(),
1304                               Some(carol_uid.clone()),
1305                               carol.clone(),
1306                               t),
1307
1308            // Bob certifies Carol for "alice", which Carol did not
1309            // self sign.
1310            Certification::new(bob.clone(),
1311                               Some(alice_uid.clone()),
1312                               carol.clone(),
1313                               t),
1314        ];
1315
1316        let mut cs = CertificationSet::from_certifications(certifications, t);
1317        // We should have 3 CertificationSets:
1318        //
1319        //   - Alice -> Bob
1320        //   - Alice -> Carol
1321        //   - Bob -> Carol
1322        assert_eq!(cs.len(), 3);
1323
1324        cs.sort_by_key(|c| {
1325            (c.issuer().fingerprint(), c.target().fingerprint())
1326        });
1327
1328        //   - Alice -> Bob
1329        assert_eq!(cs[0].issuer().fingerprint(), alice_fpr);
1330        assert_eq!(cs[0].target().fingerprint(), bob_fpr);
1331        // Two active certifications on one User ID.
1332        assert_eq!(cs[0].certifications().count(), 1);
1333        assert_eq!(cs[0].certifications().next().unwrap().1.len(), 2);
1334
1335        //   - Alice -> Carol
1336        assert_eq!(cs[1].issuer().fingerprint(), alice_fpr);
1337        assert_eq!(cs[1].target().fingerprint(), carol_fpr);
1338        // One active certifications on one User ID.
1339        assert_eq!(cs[1].certifications().count(), 1);
1340        assert_eq!(cs[1].certifications().next().unwrap().1.len(), 1);
1341
1342        //   - Bob -> Carol
1343        assert_eq!(cs[2].issuer().fingerprint(), bob_fpr);
1344        assert_eq!(cs[2].target().fingerprint(), carol_fpr);
1345        // One active certifications on each of two User IDs.
1346        assert_eq!(cs[2].certifications().count(), 2);
1347        assert_eq!(cs[2].certifications().next().unwrap().1.len(), 1);
1348        assert_eq!(cs[2].certifications().nth(1).unwrap().1.len(), 1);
1349
1350        Ok(())
1351    }
1352
1353    #[test]
1354    fn certification_set_group() -> Result<()> {
1355        // CertificationSet::from_certifications would add a
1356        // certification to the first group if a certification's user
1357        // ID and creation time matched another group's
1358        // certification's user ID and creation time instead of that
1359        // group.
1360        //
1361        // See YesWeHack report #176.
1362
1363        let ct = crate::now();
1364
1365        let alice_fpr: Fingerprint =
1366            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1367            .parse().expect("valid fingerprint");
1368        let alice_uid = UserID::from("<alice@example.org>");
1369
1370        let alice = CertSynopsis::new(
1371            alice_fpr.clone(), None,
1372            RevocationStatus::NotAsFarAsWeKnow.into(),
1373            iter::once((alice_uid.clone(), ct)));
1374
1375        let bob_fpr: Fingerprint =
1376            "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
1377            .parse().expect("valid fingerprint");
1378        let bob1_uid = UserID::from("<bob1@example.org>");
1379        let bob2_uid = UserID::from("<bob2@example.org>");
1380
1381        let bob = CertSynopsis::new(
1382            bob_fpr.clone(), None,
1383            RevocationStatus::NotAsFarAsWeKnow.into(),
1384            [(bob1_uid.clone(), ct), (bob2_uid.clone(), ct)].into_iter());
1385
1386        let ct = std::time::UNIX_EPOCH + std::time::Duration::from_secs(1);
1387
1388        let certification = Certification {
1389            issuer: alice.clone(),
1390            target: bob.clone(),
1391            userid: Some(bob1_uid.clone()),
1392            creation_time: ct,
1393            expiration_time: None,
1394            exportable: true,
1395            amount: 120,
1396            depth: 0.into(),
1397            re_set: None,
1398            re_bytes: Vec::new(),
1399            digest_prefix: None,
1400        };
1401
1402        let certifications = vec![
1403            certification.clone(),
1404            {
1405                let mut c = certification.clone();
1406                c.userid = Some(bob2_uid.clone());
1407                c
1408            },
1409            {
1410                let mut c = certification.clone();
1411                c.userid = Some(bob2_uid.clone());
1412                c
1413            },
1414        ];
1415
1416        let cs = CertificationSet::from_certifications(
1417            certifications,
1418            crate::now());
1419
1420        for cs in cs.into_iter() {
1421            for (userid, certifications) in cs.certifications.iter() {
1422                for certification in certifications.iter() {
1423                    assert_eq!(userid, &certification.userid,
1424                               "Certification with user ID {:?} \
1425                                added to wrong group (user ID: {:?}",
1426                               certification.userid, userid);
1427                }
1428            }
1429        }
1430
1431        Ok(())
1432    }
1433}