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#[non_exhaustive]
26#[derive(thiserror::Error, Debug)]
27pub enum CertificationError {
28 #[error("{0}: invalid, missing creation time")]
33 MissingCreationTime(Certification),
34
35 #[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 #[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#[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 pub fn unconstrained() -> Self {
142 Depth::Unconstrained
143 }
144
145 pub fn is_unconstrained(&self) -> bool {
147 matches!(self, Depth::Unconstrained)
148 }
149
150 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 pub fn limit(&self) -> Option<usize> {
164 match self {
165 Depth::Unconstrained => None,
166 Depth::Limit(d) => Some(*d),
167 }
168 }
169
170 pub fn decrease(&self, value: usize) -> Depth {
175 match self {
176 Depth::Unconstrained => {
177 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#[derive(Clone)]
241pub struct Certification {
242 issuer: CertSynopsis,
243 target: CertSynopsis,
244 userid: Option<UserID>,
246
247 creation_time: SystemTime,
248 expiration_time: Option<SystemTime>,
249
250 exportable: bool,
251
252 amount: usize,
255
256 depth: Depth,
258
259 re_set: Option<RegexSet>,
262 re_bytes: Vec<Vec<u8>>,
265
266 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 && self.re_bytes == other.re_bytes
294 }
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 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 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 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 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 let rs = possible_issuer_then.revocation_status();
547 if let openpgp::types::RevocationStatus::Revoked(ref revs) = rs {
548 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 if let Some(ua) = ua {
589 for rev in ua.other_revocations() {
590 if target.policy()
595 .signature(
596 rev, HashAlgoSecurity::CollisionResistance)
598 .is_err()
599 {
600 continue;
601 }
602
603 if let Some(rev_time) = rev.signature_creation_time() {
607 if rev_time > reference_time {
608 continue;
610 }
611 if rev_time <= certification_time {
612 continue;
615 }
616 } else {
617 continue;
619 };
620
621 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 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 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 let rs = target_then.revocation_status();
741 if let openpgp::types::RevocationStatus::Revoked(ref revs) = rs {
742 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 pub fn issuer(&self) -> &CertSynopsis {
809 &self.issuer
810 }
811
812 pub fn target(&self) -> &CertSynopsis {
814 &self.target
815 }
816
817 pub fn userid(&self) -> Option<&UserID> {
819 self.userid.as_ref()
820 }
821
822 pub fn creation_time(&self) -> SystemTime {
824 self.creation_time
825 }
826
827 pub fn expiration_time(&self) -> Option<SystemTime> {
829 self.expiration_time
830 }
831
832 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 pub fn exportable(&self) -> bool {
843 self.exportable
844 }
845
846 pub fn set_exportable(mut self, exportable: bool) -> Self {
849 self.exportable = exportable;
850 self
851 }
852
853 pub fn amount(&self) -> usize {
855 self.amount
856 }
857
858 pub fn set_amount(mut self, amount: usize) -> Self {
860 self.amount = amount;
861 self
862 }
863
864 pub fn depth(&self) -> Depth {
866 self.depth
867 }
868
869 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 pub fn regular_expressions(&self) -> Option<&RegexSet> {
888 self.re_set.as_ref()
889 }
890
891 pub fn regular_expressions_bytes(&self) -> &[Vec<u8>] {
894 &self.re_bytes[..]
895 }
896
897 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(®exes).ok();
904 self.re_bytes = regexes.into_iter().map(Into::into).collect();
905 self
906 }
907
908 pub fn digest_prefix(&self) -> Option<&[u8; 2]> {
910 self.digest_prefix.as_ref()
911 }
912}
913
914#[derive(Clone)]
922pub struct CertificationSet {
923 issuer: CertSynopsis,
925 target: CertSynopsis,
927
928 reference_time: SystemTime,
929
930 certifications: HashMap<Option<UserID>, Vec<Certification>>,
934}
935
936impl CertificationSet {
937 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 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 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 c.creation_time <= reference_time
990 && c.expiration_time.map(|e| e > reference_time).unwrap_or(true)
992 });
993
994 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 let mut cs = Vec::new();
1008 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 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 if group_userid == certification.userid() {
1035 if group_certification_time
1038 == certification.creation_time()
1039 {
1040 acc.last_mut().unwrap().1.push(certification);
1042 } else {
1043 assert!(certification.creation_time()
1046 < group_certification_time);
1047 }
1048 } else {
1049 acc.push((certification.userid().map(Clone::clone),
1051 vec![ certification ]));
1052 }
1053 } else {
1054 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 acc = vec![(certification.userid().map(Clone::clone),
1068 vec![ certification ])];
1069 }
1070 }
1071
1072 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 pub fn issuer(&self) -> &CertSynopsis {
1100 &self.issuer
1101 }
1102
1103 pub fn target(&self) -> &CertSynopsis {
1105 &self.target
1106 }
1107
1108 pub fn reference_time(&self) -> SystemTime {
1110 self.reference_time
1111 }
1112
1113 pub(crate) fn add(&mut self, certification: Certification) {
1123 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 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 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 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 Certification::new(alice.clone(),
1273 Some(bob_uid.clone()),
1274 bob.clone(),
1275 t),
1276 Certification::new(alice.clone(),
1278 Some(bob_uid.clone()),
1279 bob.clone(),
1280 t),
1281 Certification::new(alice.clone(),
1285 Some(bob_uid.clone()),
1286 bob.clone(),
1287 t - Duration::new(1, 0)),
1288 Certification::new(alice.clone(),
1292 Some(bob_uid.clone()),
1293 bob.clone(),
1294 t + Duration::new(1, 0)),
1295
1296 Certification::new(alice.clone(),
1298 Some(carol_uid.clone()),
1299 carol.clone(),
1300 t),
1301
1302 Certification::new(bob.clone(),
1304 Some(carol_uid.clone()),
1305 carol.clone(),
1306 t),
1307
1308 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 assert_eq!(cs.len(), 3);
1323
1324 cs.sort_by_key(|c| {
1325 (c.issuer().fingerprint(), c.target().fingerprint())
1326 });
1327
1328 assert_eq!(cs[0].issuer().fingerprint(), alice_fpr);
1330 assert_eq!(cs[0].target().fingerprint(), bob_fpr);
1331 assert_eq!(cs[0].certifications().count(), 1);
1333 assert_eq!(cs[0].certifications().next().unwrap().1.len(), 2);
1334
1335 assert_eq!(cs[1].issuer().fingerprint(), alice_fpr);
1337 assert_eq!(cs[1].target().fingerprint(), carol_fpr);
1338 assert_eq!(cs[1].certifications().count(), 1);
1340 assert_eq!(cs[1].certifications().next().unwrap().1.len(), 1);
1341
1342 assert_eq!(cs[2].issuer().fingerprint(), bob_fpr);
1344 assert_eq!(cs[2].target().fingerprint(), carol_fpr);
1345 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 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}