1pub(crate) mod ocsp;
22mod profile;
23pub(crate) use ocsp::online::{
24 build_request as build_ocsp_request, evaluate as evaluate_ocsp_online,
25 responder_url as ocsp_responder_url, OnlinePolicy as OnlineOcspPolicy,
26 OnlineVerdict as OnlineOcspVerdict,
27};
28pub(crate) use ocsp::{
29 evaluate_verified as evaluate_ocsp_verified,
30 evaluate_verified_with as evaluate_ocsp_verified_with, OcspStatus, MAX_OCSP_RESPONSE_BYTES,
31};
32mod timestamp;
33#[cfg(test)]
35pub(crate) mod timestamp_fixture;
36use std::{
37 collections::{HashMap, HashSet},
38 sync::Arc,
39};
40pub(crate) use timestamp::{
41 describe_timestamp_token, inspect_timestamp_token, token_from_timestamp_response,
42 verify_timestamp_token, verify_timestamp_token_with, TimestampResult, TokenDescription,
43};
44
45use const_oid::ObjectIdentifier;
46use der::{Decode, Encode};
47use ecdsa::signature::hazmat::PrehashVerifier;
48use rsa::pkcs1::DecodeRsaPublicKey;
49use rsa::signature::Verifier as _;
50use sha2::{Digest, Sha256, Sha384, Sha512};
51use thiserror::Error;
52use time::OffsetDateTime;
53use x509_cert::ext::pkix::{BasicConstraints, CertificatePolicies, ExtendedKeyUsage};
54use x509_cert::{certificate::Version, Certificate};
55
56pub const OID_C2PA_CLAIM_SIGNING: &str = "1.3.6.1.4.1.62558.2.1";
62pub const OID_EMAIL_PROTECTION: &str = "1.3.6.1.5.5.7.3.4";
64pub const OID_ADOBE_DOCUMENT_SIGNING: &str = "1.2.840.113583.1.1.5";
66pub const OID_IETF_DOCUMENT_SIGNING: &str = "1.3.6.1.5.5.7.3.36";
68pub const OID_KP_TIME_STAMPING: &str = "1.3.6.1.5.5.7.3.8";
72pub const OID_KP_OCSP_SIGNING: &str = "1.3.6.1.5.5.7.3.9";
75pub const OID_MICROSOFT_C2PA: &str = "1.3.6.1.4.1.311.76.59.1.9";
77
78const OID_EXT_EKU: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.37");
79const OID_EXT_BASIC_CONSTRAINTS: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.19");
80const OID_AT_COMMON_NAME: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.4.3");
81const OID_EXT_KEY_USAGE: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.15");
82const OID_ANY_EKU: &str = "2.5.29.37.0";
84
85const OID_EC_PUBLIC_KEY: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
86const OID_RSA_ENCRYPTION: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
87const OID_ED25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
88
89const OID_CURVE_P256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
90const OID_CURVE_P384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34");
91const OID_CURVE_P521: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.35");
92
93const OID_ECDSA_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
94const OID_ECDSA_SHA384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3");
95const OID_ECDSA_SHA512: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.4");
96
97const OID_RSA_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11");
98const OID_RSA_SHA384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12");
99const OID_RSA_SHA512: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13");
100
101const MAX_CHAIN_DEPTH: usize = 20;
103const MAX_PATH_NODES: usize = 256;
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum CertificateProfile {
118 V2_2,
120 V2_4,
122 V2_5,
126}
127
128#[derive(Debug, Error)]
134pub enum TrustError {
135 #[error("no certificates found in PEM input")]
137 NoCertificates,
138 #[error("failed to decode certificate: {0}")]
140 Decode(String),
141}
142
143#[derive(Clone, Copy)]
148enum SigHash {
149 Sha256,
150 Sha384,
151 Sha512,
152}
153
154fn digest_bytes(hash: SigHash, msg: &[u8]) -> Vec<u8> {
155 match hash {
156 SigHash::Sha256 => Sha256::digest(msg).to_vec(),
157 SigHash::Sha384 => Sha384::digest(msg).to_vec(),
158 SigHash::Sha512 => Sha512::digest(msg).to_vec(),
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum AnchorPurpose {
176 ClaimSigning,
178 TimeStamping,
180 CawgIdentity,
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum CawgTrustSource {
198 SmimeInterim,
202 EncypherVerifiedOrganizations,
204 CallerSupplied,
206}
207
208impl CawgTrustSource {
209 pub fn label(self) -> &'static str {
211 match self {
212 Self::SmimeInterim => "smime_interim",
213 Self::EncypherVerifiedOrganizations => "encypher_verified_organizations",
214 Self::CallerSupplied => "caller_supplied",
215 }
216 }
217
218 pub fn interim(self) -> bool {
220 matches!(self, Self::SmimeInterim)
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
231pub struct TrustSource {
232 pub uri: String,
234 #[serde(rename = "type")]
236 pub source_type: String,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
239 pub identifier: Option<String>,
240}
241
242impl TrustSource {
243 pub fn new(uri: impl Into<String>, source_type: impl Into<String>) -> Self {
245 Self {
246 uri: uri.into(),
247 source_type: source_type.into(),
248 identifier: None,
249 }
250 }
251
252 pub fn with_identifier(mut self, identifier: impl Into<String>) -> Self {
254 self.identifier = Some(identifier.into());
255 self
256 }
257}
258
259#[derive(Debug, Clone)]
268pub struct TrustAnchor {
269 pub certificate: Vec<u8>,
271 pub purpose: AnchorPurpose,
273 pub not_before: Option<OffsetDateTime>,
275 pub not_after: Option<OffsetDateTime>,
277 pub cawg_source: CawgTrustSource,
280 pub source: Option<Arc<TrustSource>>,
283}
284
285impl TrustAnchor {
286 pub fn new(certificate: Vec<u8>, purpose: AnchorPurpose) -> Self {
288 Self {
289 certificate,
290 purpose,
291 not_before: None,
292 not_after: None,
293 cawg_source: CawgTrustSource::SmimeInterim,
294 source: None,
295 }
296 }
297
298 pub fn with_source(mut self, source: TrustSource) -> Self {
300 self.source = Some(Arc::new(source));
301 self
302 }
303
304 pub fn fingerprint(&self) -> String {
306 fingerprint_hex(&self.certificate)
307 }
308
309 pub fn active_at(&self, at: OffsetDateTime) -> bool {
311 self.not_before.is_none_or(|start| at >= start)
312 && self.not_after.is_none_or(|end| at <= end)
313 }
314}
315
316#[derive(Debug, Clone, Default)]
322pub struct TrustList {
323 pub anchors: Vec<TrustAnchor>,
325}
326
327impl TrustList {
328 pub fn from_pem(pem: &str) -> Result<Self, TrustError> {
330 Self::from_pem_for(AnchorPurpose::ClaimSigning, pem)
331 }
332
333 pub fn from_pem_for(purpose: AnchorPurpose, pem: &str) -> Result<Self, TrustError> {
340 if !pem.contains("-----BEGIN CERTIFICATE-----") {
346 return Err(TrustError::NoCertificates);
347 }
348 let certs = Certificate::load_pem_chain(pem.as_bytes())
349 .map_err(|e| TrustError::Decode(e.to_string()))?;
350 if certs.is_empty() {
351 return Err(TrustError::NoCertificates);
352 }
353 let mut anchors = Vec::with_capacity(certs.len());
354 for cert in &certs {
355 let der = cert
356 .to_der()
357 .map_err(|e| TrustError::Decode(e.to_string()))?;
358 anchors.push(TrustAnchor::new(der, purpose));
359 }
360 Ok(Self { anchors })
361 }
362
363 pub fn from_certificates(
365 purpose: AnchorPurpose,
366 certificates: impl IntoIterator<Item = Vec<u8>>,
367 ) -> Self {
368 Self {
369 anchors: certificates
370 .into_iter()
371 .map(|der| TrustAnchor::new(der, purpose))
372 .collect(),
373 }
374 }
375
376 pub fn with_bounds(
378 mut self,
379 not_before: Option<OffsetDateTime>,
380 not_after: Option<OffsetDateTime>,
381 ) -> Self {
382 for anchor in &mut self.anchors {
383 anchor.not_before = not_before;
384 anchor.not_after = not_after;
385 }
386 self
387 }
388
389 pub fn with_cawg_source(mut self, source: CawgTrustSource) -> Self {
392 for anchor in &mut self.anchors {
393 anchor.cawg_source = source;
394 }
395 self
396 }
397
398 pub fn with_source(mut self, source: TrustSource) -> Self {
404 let source = Arc::new(source);
405 for anchor in &mut self.anchors {
406 anchor.source = Some(Arc::clone(&source));
407 }
408 self
409 }
410
411 pub fn find_certificate(&self, der: &[u8]) -> Option<&TrustAnchor> {
414 self.anchors.iter().find(|anchor| anchor.certificate == der)
415 }
416
417 pub fn contains_certificate(&self, der: &[u8]) -> bool {
419 self.find_certificate(der).is_some()
420 }
421
422 pub fn certificates(&self) -> impl Iterator<Item = &[u8]> {
424 self.anchors
425 .iter()
426 .map(|anchor| anchor.certificate.as_slice())
427 }
428
429 pub fn anchor_subjects(&self) -> Vec<String> {
434 self.certificates()
435 .filter_map(|der| {
436 let cert = Certificate::from_der(der).ok()?;
437 common_name(&cert)
438 })
439 .collect()
440 }
441
442 fn active_anchors(
446 &self,
447 purpose: AnchorPurpose,
448 at: OffsetDateTime,
449 admit: impl Fn(&TrustAnchor) -> bool,
450 ) -> Vec<(usize, &TrustAnchor)> {
451 self.anchors
452 .iter()
453 .enumerate()
454 .filter(|(_, anchor)| {
455 anchor.purpose == purpose && anchor.active_at(at) && admit(anchor)
456 })
457 .collect()
458 }
459
460 pub fn split_certificate_authorities(self) -> (Self, Self) {
469 let (authorities, end_entities) = self.anchors.into_iter().partition(|anchor| {
470 Certificate::from_der(&anchor.certificate).is_ok_and(|cert| {
471 is_ca_certificate(&cert)
472 || (cert.tbs_certificate.version == Version::V1
473 && cert.tbs_certificate.subject == cert.tbs_certificate.issuer
474 && !has_extension(&cert, OID_EXT_BASIC_CONSTRAINTS))
475 })
476 });
477 (
478 Self {
479 anchors: authorities,
480 },
481 Self {
482 anchors: end_entities,
483 },
484 )
485 }
486}
487
488#[derive(Debug, Clone)]
495pub struct EkuPolicy {
496 pub allowed_oids: Vec<String>,
498}
499
500impl Default for EkuPolicy {
501 fn default() -> Self {
510 Self {
511 allowed_oids: vec![
512 OID_C2PA_CLAIM_SIGNING.to_string(),
513 OID_ADOBE_DOCUMENT_SIGNING.to_string(),
514 OID_IETF_DOCUMENT_SIGNING.to_string(),
515 OID_EMAIL_PROTECTION.to_string(),
516 OID_MICROSOFT_C2PA.to_string(),
517 ],
518 }
519 }
520}
521
522impl EkuPolicy {
523 pub fn cert_has_required_eku(&self, cert_der: &[u8]) -> bool {
530 if self.allowed_oids.is_empty() {
531 return true;
532 }
533 let Ok(cert) = Certificate::from_der(cert_der) else {
534 return false;
535 };
536 let Some(ekus) = certificate_eku_oids(&cert) else {
537 return false;
538 };
539 ekus.iter()
540 .any(|oid| self.allowed_oids.iter().any(|allowed| allowed == oid))
541 }
542}
543
544fn certificate_eku_oids(cert: &Certificate) -> Option<Vec<String>> {
546 let exts = cert.tbs_certificate.extensions.as_ref()?;
547 let ext = exts.iter().find(|e| e.extn_id == OID_EXT_EKU)?;
548 let eku = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes()).ok()?;
549 Some(eku.0.iter().map(|oid| oid.to_string()).collect())
550}
551
552pub fn certificate_eku_oids_der(cert_der: &[u8]) -> Option<Vec<String>> {
554 let cert = Certificate::from_der(cert_der).ok()?;
555 certificate_eku_oids(&cert)
556}
557
558pub fn certificate_policy_oids_der(cert_der: &[u8]) -> Option<Vec<String>> {
560 let cert = Certificate::from_der(cert_der).ok()?;
561 let exts = cert.tbs_certificate.extensions.as_ref()?;
562 let oid = ObjectIdentifier::new_unwrap("2.5.29.32");
563 let ext = exts.iter().find(|extension| extension.extn_id == oid)?;
564 let policies = CertificatePolicies::from_der(ext.extn_value.as_bytes()).ok()?;
565 Some(
566 policies
567 .0
568 .iter()
569 .map(|policy| policy.policy_identifier.to_string())
570 .collect(),
571 )
572}
573
574pub fn certificate_valid_at(cert_der: &[u8], at: OffsetDateTime) -> bool {
576 Certificate::from_der(cert_der)
577 .map(|cert| valid_at(&cert, at))
578 .unwrap_or(false)
579}
580
581#[derive(Debug, Clone)]
587pub struct ChainResult {
588 pub trusted: bool,
591 pub chain_validity_ok: bool,
596 pub leaf_acceptable: bool,
601 pub reason: Option<String>,
603 pub validated_at: OffsetDateTime,
606 pub anchor: Option<usize>,
613}
614
615impl ChainResult {
616 fn untrusted(reason: impl Into<String>, at: OffsetDateTime) -> Self {
617 Self {
618 trusted: false,
619 chain_validity_ok: true,
620 leaf_acceptable: true,
621 reason: Some(reason.into()),
622 validated_at: at,
623 anchor: None,
624 }
625 }
626
627 pub fn terminating_anchor<'a>(&self, trust: &'a TrustList) -> Option<&'a TrustAnchor> {
630 trust.anchors.get(self.anchor?)
631 }
632
633 pub fn matching_anchor_sources<'a>(&self, trust: &'a TrustList) -> Vec<&'a TrustSource> {
646 let Some(terminating) = self.terminating_anchor(trust) else {
647 return Vec::new();
648 };
649 let mut sources = Vec::new();
650 for anchor in &trust.anchors {
651 if anchor.purpose != terminating.purpose
652 || anchor.certificate != terminating.certificate
653 || !anchor.active_at(self.validated_at)
654 {
655 continue;
656 }
657 if let Some(source) = anchor.source.as_deref() {
658 if !sources.contains(&source) {
659 sources.push(source);
660 }
661 }
662 }
663 sources
664 }
665}
666
667#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
672enum PathOutcome {
673 NoPath,
675 Rejected(&'static str),
677 OutsideValidity,
680 Trusted,
682}
683
684pub fn validate_chain(
702 leaf_der: &[u8],
703 intermediates_der: &[Vec<u8>],
704 trust: &TrustList,
705 purpose: AnchorPurpose,
706 validation_time: Option<OffsetDateTime>,
707) -> ChainResult {
708 validate_chain_with(
709 leaf_der,
710 intermediates_der,
711 trust,
712 purpose,
713 validation_time,
714 CertificateProfile::V2_4,
715 )
716}
717
718pub fn validate_chain_with(
720 leaf_der: &[u8],
721 intermediates_der: &[Vec<u8>],
722 trust: &TrustList,
723 purpose: AnchorPurpose,
724 validation_time: Option<OffsetDateTime>,
725 profile: CertificateProfile,
726) -> ChainResult {
727 validate_chain_admitting_with(
728 leaf_der,
729 intermediates_der,
730 trust,
731 purpose,
732 validation_time,
733 profile,
734 |_| true,
735 )
736}
737
738pub fn validate_chain_admitting(
744 leaf_der: &[u8],
745 intermediates_der: &[Vec<u8>],
746 trust: &TrustList,
747 purpose: AnchorPurpose,
748 validation_time: Option<OffsetDateTime>,
749 admit: impl Fn(&TrustAnchor) -> bool,
750) -> ChainResult {
751 validate_chain_admitting_with(
752 leaf_der,
753 intermediates_der,
754 trust,
755 purpose,
756 validation_time,
757 CertificateProfile::V2_4,
758 admit,
759 )
760}
761
762pub fn validate_chain_admitting_with(
764 leaf_der: &[u8],
765 intermediates_der: &[Vec<u8>],
766 trust: &TrustList,
767 purpose: AnchorPurpose,
768 validation_time: Option<OffsetDateTime>,
769 profile: CertificateProfile,
770 admit: impl Fn(&TrustAnchor) -> bool,
771) -> ChainResult {
772 let at = validation_time.unwrap_or_else(OffsetDateTime::now_utc);
773
774 let leaf = match Certificate::from_der(leaf_der) {
775 Ok(c) => c,
776 Err(e) => return ChainResult::untrusted(format!("invalid leaf certificate: {e}"), at),
777 };
778
779 let leaf_acceptable = leaf_is_acceptable_claim_signer_with(&leaf, profile);
784
785 let mut anchor_indices: HashMap<String, usize> = HashMap::new();
792 let mut candidates: Vec<Certificate> = Vec::new();
793 for der in intermediates_der {
794 if let Ok(c) = Certificate::from_der(der) {
795 candidates.push(c);
796 }
797 }
798 for (index, anchor) in trust.active_anchors(purpose, at, |_| true) {
799 if let Ok(c) = Certificate::from_der(&anchor.certificate) {
800 if admit(anchor) {
801 anchor_indices
802 .entry(fingerprint_hex(&anchor.certificate))
803 .or_insert(index);
804 }
805 candidates.push(c);
806 }
807 }
808
809 let mut path = vec![leaf];
810 let mut seen: HashSet<String> = HashSet::from([fingerprint_hex(leaf_der)]);
811 let mut budget = MAX_PATH_NODES;
812 let mut anchor = None;
813 let search = PathSearchContext {
814 candidates: &candidates,
815 anchor_indices: &anchor_indices,
816 at,
817 profile,
818 rules: profile::ProfileRules::for_purpose(profile, purpose),
819 };
820 let outcome = extend_path(&mut path, &search, &mut seen, &mut budget, &mut anchor);
821
822 let (trusted, chain_validity_ok, reason) = match outcome {
823 PathOutcome::Trusted => (true, true, None),
824 PathOutcome::OutsideValidity => (
825 false,
826 false,
827 Some("a certificate in the chain was outside its validity window".to_string()),
828 ),
829 PathOutcome::Rejected(reason) => (false, true, Some(reason.to_string())),
830 PathOutcome::NoPath => (
831 false,
832 valid_at(path.first().expect("path holds the leaf"), at),
833 Some("certificate does not chain to a trusted anchor".to_string()),
834 ),
835 };
836 ChainResult {
837 trusted,
838 chain_validity_ok,
839 leaf_acceptable,
840 reason,
841 validated_at: at,
842 anchor: trusted.then_some(anchor).flatten(),
843 }
844}
845
846struct PathSearchContext<'a> {
852 candidates: &'a [Certificate],
853 anchor_indices: &'a HashMap<String, usize>,
854 at: OffsetDateTime,
855 profile: CertificateProfile,
856 rules: profile::ProfileRules,
857}
858
859fn extend_path(
872 path: &mut Vec<Certificate>,
873 search: &PathSearchContext<'_>,
874 seen: &mut HashSet<String>,
875 budget: &mut usize,
876 anchor: &mut Option<usize>,
877) -> PathOutcome {
878 let current = path.last().expect("path is never empty").clone();
879 let Ok(current_der) = current.to_der() else {
880 return PathOutcome::NoPath;
881 };
882 if let Some(&index) = search.anchor_indices.get(&fingerprint_hex(¤t_der)) {
883 let outcome = evaluate_path(path, search.at, search.rules, search.profile);
884 if outcome == PathOutcome::Trusted {
885 *anchor = Some(index);
886 }
887 return outcome;
888 }
889 if path.len() >= MAX_CHAIN_DEPTH
890 || (search.profile != CertificateProfile::V2_5
891 && current.tbs_certificate.subject == current.tbs_certificate.issuer)
892 {
893 return PathOutcome::NoPath;
897 }
898
899 let mut best = PathOutcome::NoPath;
900 for candidate in search.candidates {
901 if *budget == 0 {
902 break;
903 }
904 if candidate.tbs_certificate.subject != current.tbs_certificate.issuer
905 || !verify_signature_with(¤t, candidate, search.profile)
906 {
907 continue;
908 }
909 let Ok(der) = candidate.to_der() else {
910 continue;
911 };
912 let fingerprint = fingerprint_hex(&der);
913 if !seen.insert(fingerprint.clone()) {
914 continue;
915 }
916 *budget -= 1;
917 path.push(candidate.clone());
918 let outcome = extend_path(path, search, seen, budget, anchor);
919 path.pop();
920 seen.remove(&fingerprint);
921 best = best.max(outcome);
922 if best == PathOutcome::Trusted {
923 break;
924 }
925 }
926 best
927}
928
929fn evaluate_path(
931 path: &[Certificate],
932 at: OffsetDateTime,
933 rules: profile::ProfileRules,
934 certificate_profile: CertificateProfile,
935) -> PathOutcome {
936 if let Some(reason) = profile::path_violation(path, rules) {
937 return PathOutcome::Rejected(reason);
938 }
939 let validity_path = if certificate_profile == CertificateProfile::V2_5 {
940 &path[..path.len().saturating_sub(1)]
941 } else {
942 path
943 };
944 if validity_path.iter().any(|cert| !valid_at(cert, at)) {
945 return PathOutcome::OutsideValidity;
946 }
947 PathOutcome::Trusted
948}
949
950pub fn resolve_issuer<'a>(
963 leaf_der: &[u8],
964 candidates: impl IntoIterator<Item = &'a [u8]>,
965) -> Option<&'a [u8]> {
966 resolve_issuer_with(leaf_der, candidates, CertificateProfile::V2_4)
967}
968
969pub fn resolve_issuer_with<'a>(
971 leaf_der: &[u8],
972 candidates: impl IntoIterator<Item = &'a [u8]>,
973 profile: CertificateProfile,
974) -> Option<&'a [u8]> {
975 let leaf = Certificate::from_der(leaf_der).ok()?;
976 let leaf_issuer_dn = leaf.tbs_certificate.issuer.to_der().ok()?;
977 for cand_der in candidates {
978 let Ok(cand) = Certificate::from_der(cand_der) else {
979 continue;
980 };
981 let Ok(cand_subject_dn) = cand.tbs_certificate.subject.to_der() else {
982 continue;
983 };
984 if cand_subject_dn == leaf_issuer_dn && verify_signature_with(&leaf, &cand, profile) {
985 return Some(cand_der);
986 }
987 }
988 None
989}
990#[derive(Debug, Clone, Default)]
997pub struct RevocationDenylist {
998 pub serials: HashSet<String>,
1000 pub fingerprints: HashSet<String>,
1002}
1003
1004impl RevocationDenylist {
1005 pub fn new(
1007 serials: impl IntoIterator<Item = String>,
1008 fingerprints: impl IntoIterator<Item = String>,
1009 ) -> Self {
1010 Self {
1011 serials: normalize_tokens(serials),
1012 fingerprints: normalize_tokens(fingerprints),
1013 }
1014 }
1015
1016 pub fn is_revoked(&self, cert_der: &[u8]) -> bool {
1022 if !self.fingerprints.is_empty() && self.fingerprints.contains(&fingerprint_hex(cert_der)) {
1023 return true;
1024 }
1025 if !self.serials.is_empty() {
1026 if let Ok(cert) = Certificate::from_der(cert_der) {
1027 let serial = serial_hex(cert.tbs_certificate.serial_number.as_bytes());
1028 if self.serials.contains(&serial) {
1029 return true;
1030 }
1031 }
1032 }
1033 false
1034 }
1035}
1036
1037fn normalize_tokens(values: impl IntoIterator<Item = String>) -> HashSet<String> {
1038 values
1039 .into_iter()
1040 .filter_map(|v| {
1041 let t = v.trim().to_ascii_lowercase();
1042 if t.is_empty() {
1043 None
1044 } else {
1045 Some(t)
1046 }
1047 })
1048 .collect()
1049}
1050
1051fn fingerprint_hex(der: &[u8]) -> String {
1057 hex::encode(Sha256::digest(der))
1058}
1059
1060fn serial_hex(bytes: &[u8]) -> String {
1066 let mut s = String::with_capacity(bytes.len() * 2);
1067 for b in bytes {
1068 s.push_str(&format!("{b:02x}"));
1069 }
1070 let trimmed = s.trim_start_matches('0');
1071 if trimmed.is_empty() {
1072 "0".to_string()
1073 } else {
1074 trimmed.to_string()
1075 }
1076}
1077
1078fn common_name(cert: &Certificate) -> Option<String> {
1080 for rdn in cert.tbs_certificate.subject.0.iter() {
1081 for atav in rdn.0.iter() {
1082 if atav.oid == OID_AT_COMMON_NAME {
1083 let raw = atav.value.value();
1086 return Some(String::from_utf8_lossy(raw).into_owned());
1087 }
1088 }
1089 }
1090 None
1091}
1092
1093fn valid_at(cert: &Certificate, t: OffsetDateTime) -> bool {
1095 let nb = cert
1096 .tbs_certificate
1097 .validity
1098 .not_before
1099 .to_unix_duration()
1100 .as_secs() as i64;
1101 let na = cert
1102 .tbs_certificate
1103 .validity
1104 .not_after
1105 .to_unix_duration()
1106 .as_secs() as i64;
1107 let now = t.unix_timestamp();
1108 nb <= now && now <= na
1109}
1110
1111fn is_ca_certificate(cert: &Certificate) -> bool {
1113 let Some(exts) = cert.tbs_certificate.extensions.as_ref() else {
1114 return false;
1115 };
1116 let Some(ext) = exts.iter().find(|e| e.extn_id == OID_EXT_BASIC_CONSTRAINTS) else {
1117 return false;
1118 };
1119 BasicConstraints::from_der(ext.extn_value.as_bytes())
1120 .map(|bc| bc.ca)
1121 .unwrap_or(false)
1122}
1123
1124fn has_extension(cert: &Certificate, oid: ObjectIdentifier) -> bool {
1125 cert.tbs_certificate
1126 .extensions
1127 .as_ref()
1128 .is_some_and(|exts| exts.iter().any(|e| e.extn_id == oid))
1129}
1130
1131fn path_len_constraint(cert: &Certificate) -> Option<usize> {
1134 let exts = cert.tbs_certificate.extensions.as_ref()?;
1135 let ext = exts
1136 .iter()
1137 .find(|e| e.extn_id == OID_EXT_BASIC_CONSTRAINTS)?;
1138 let bc = BasicConstraints::from_der(ext.extn_value.as_bytes()).ok()?;
1139 bc.path_len_constraint.map(|n| n as usize)
1140}
1141
1142fn has_key_cert_sign(cert: &Certificate) -> bool {
1144 let Some(exts) = cert.tbs_certificate.extensions.as_ref() else {
1145 return false;
1146 };
1147 let Some(ext) = exts.iter().find(|e| e.extn_id == OID_EXT_KEY_USAGE) else {
1148 return false;
1149 };
1150 use x509_cert::ext::pkix::KeyUsage;
1151 KeyUsage::from_der(ext.extn_value.as_bytes())
1152 .map(|ku| ku.key_cert_sign())
1153 .unwrap_or(false)
1154}
1155
1156fn allows_digital_signature(cert: &Certificate) -> bool {
1160 let Some(exts) = cert.tbs_certificate.extensions.as_ref() else {
1161 return false;
1162 };
1163 let Some(ext) = exts.iter().find(|e| e.extn_id == OID_EXT_KEY_USAGE) else {
1164 return false;
1165 };
1166 use x509_cert::ext::pkix::KeyUsage;
1167 KeyUsage::from_der(ext.extn_value.as_bytes())
1168 .map(|ku| ku.digital_signature())
1169 .unwrap_or(false)
1170}
1171
1172pub fn leaf_profile_acceptable_der(leaf_der: &[u8]) -> bool {
1179 leaf_profile_acceptable_der_with(leaf_der, CertificateProfile::V2_4)
1180}
1181
1182pub fn leaf_profile_acceptable_der_with(leaf_der: &[u8], profile: CertificateProfile) -> bool {
1184 Certificate::from_der(leaf_der).is_ok_and(|leaf| {
1185 profile::profile_violation_with(
1186 &leaf,
1187 profile::CertRole::EndEntity,
1188 profile::ProfileRules::for_purpose(profile, AnchorPurpose::ClaimSigning),
1189 )
1190 .is_none()
1191 })
1192}
1193
1194fn leaf_is_acceptable_claim_signer(leaf: &Certificate) -> bool {
1206 leaf_is_acceptable_claim_signer_with(leaf, CertificateProfile::V2_4)
1207}
1208
1209fn leaf_is_acceptable_claim_signer_with(leaf: &Certificate, profile: CertificateProfile) -> bool {
1210 let rules = profile::ProfileRules::for_purpose(profile, AnchorPurpose::ClaimSigning);
1211 if profile::profile_violation_with(leaf, profile::CertRole::EndEntity, rules).is_some() {
1212 return false;
1213 }
1214 let Some(ekus) = certificate_eku_oids(leaf) else {
1215 return false;
1216 };
1217 if ekus
1224 .iter()
1225 .any(|oid| oid == OID_KP_TIME_STAMPING || oid == OID_KP_OCSP_SIGNING)
1226 {
1227 return false;
1228 }
1229 let policy = EkuPolicy::default();
1230 ekus.iter()
1231 .any(|oid| policy.allowed_oids.iter().any(|allowed| allowed == oid))
1232}
1233
1234pub fn leaf_acceptable_der(leaf_der: &[u8]) -> bool {
1238 leaf_acceptable_der_with(leaf_der, CertificateProfile::V2_4)
1239}
1240
1241pub fn leaf_acceptable_der_with(leaf_der: &[u8], profile: CertificateProfile) -> bool {
1243 Certificate::from_der(leaf_der)
1244 .map(|certificate| leaf_is_acceptable_claim_signer_with(&certificate, profile))
1245 .unwrap_or(false)
1246}
1247
1248fn verify_signature(subject: &Certificate, issuer: &Certificate) -> bool {
1253 let Ok(tbs) = subject.tbs_certificate.to_der() else {
1254 return false;
1255 };
1256 let Some(sig) = subject.signature.as_bytes() else {
1257 return false;
1258 };
1259 let spki = &issuer.tbs_certificate.subject_public_key_info;
1260 let Some(pubkey) = spki.subject_public_key.as_bytes() else {
1261 return false;
1262 };
1263 let sig_alg = subject.signature_algorithm.oid;
1264 let key_alg = spki.algorithm.oid;
1265
1266 if key_alg == OID_EC_PUBLIC_KEY {
1267 let hash = match sig_alg {
1268 OID_ECDSA_SHA256 => SigHash::Sha256,
1269 OID_ECDSA_SHA384 => SigHash::Sha384,
1270 OID_ECDSA_SHA512 => SigHash::Sha512,
1271 _ => return false,
1272 };
1273 let curve = match spki.algorithm.parameters.as_ref() {
1274 Some(p) => match p.decode_as::<ObjectIdentifier>() {
1275 Ok(oid) => oid,
1276 Err(_) => return false,
1277 },
1278 None => return false,
1279 };
1280 verify_ecdsa(curve, hash, pubkey, sig, &tbs)
1281 } else if key_alg == OID_RSA_ENCRYPTION {
1282 let hash = match sig_alg {
1283 OID_RSA_SHA256 => SigHash::Sha256,
1284 OID_RSA_SHA384 => SigHash::Sha384,
1285 OID_RSA_SHA512 => SigHash::Sha512,
1286 _ => return false,
1287 };
1288 verify_rsa(hash, pubkey, sig, &tbs)
1289 } else if key_alg == OID_ED25519 {
1290 verify_ed25519(pubkey, sig, &tbs)
1291 } else {
1292 false
1293 }
1294}
1295fn verify_signature_with(
1297 subject: &Certificate,
1298 issuer: &Certificate,
1299 profile: CertificateProfile,
1300) -> bool {
1301 if matches!(profile, CertificateProfile::V2_2 | CertificateProfile::V2_4) {
1302 return verify_signature(subject, issuer);
1303 }
1304 if subject.signature_algorithm != subject.tbs_certificate.signature {
1305 return false;
1306 }
1307 let (Ok(spki), Ok(algorithm), Ok(tbs), Some(signature)) = (
1308 issuer.tbs_certificate.subject_public_key_info.to_der(),
1309 subject.signature_algorithm.to_der(),
1310 subject.tbs_certificate.to_der(),
1311 subject.signature.as_bytes(),
1312 ) else {
1313 return false;
1314 };
1315 crate::c2pa_crypto::verify_x509_signature(&spki, &algorithm, &tbs, signature).is_ok()
1316}
1317
1318fn verify_ecdsa(
1319 curve: ObjectIdentifier,
1320 hash: SigHash,
1321 pubkey: &[u8],
1322 sig_der: &[u8],
1323 tbs: &[u8],
1324) -> bool {
1325 let prehash = digest_bytes(hash, tbs);
1326 if curve == OID_CURVE_P256 {
1327 let (Ok(vk), Ok(sig)) = (
1328 p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey),
1329 p256::ecdsa::Signature::from_der(sig_der),
1330 ) else {
1331 return false;
1332 };
1333 vk.verify_prehash(&prehash, &sig).is_ok()
1334 } else if curve == OID_CURVE_P384 {
1335 let (Ok(vk), Ok(sig)) = (
1336 p384::ecdsa::VerifyingKey::from_sec1_bytes(pubkey),
1337 p384::ecdsa::Signature::from_der(sig_der),
1338 ) else {
1339 return false;
1340 };
1341 vk.verify_prehash(&prehash, &sig).is_ok()
1342 } else if curve == OID_CURVE_P521 {
1343 let (Ok(vk), Ok(sig)) = (
1344 p521::ecdsa::VerifyingKey::from_sec1_bytes(pubkey),
1345 p521::ecdsa::Signature::from_der(sig_der),
1346 ) else {
1347 return false;
1348 };
1349 vk.verify_prehash(&prehash, &sig).is_ok()
1350 } else {
1351 false
1352 }
1353}
1354
1355fn verify_rsa(hash: SigHash, pubkey_der: &[u8], sig: &[u8], tbs: &[u8]) -> bool {
1356 let Ok(pubkey) = rsa::RsaPublicKey::from_pkcs1_der(pubkey_der) else {
1357 return false;
1358 };
1359 let Ok(signature) = rsa::pkcs1v15::Signature::try_from(sig) else {
1360 return false;
1361 };
1362 match hash {
1363 SigHash::Sha256 => rsa::pkcs1v15::VerifyingKey::<Sha256>::new(pubkey)
1364 .verify(tbs, &signature)
1365 .is_ok(),
1366 SigHash::Sha384 => rsa::pkcs1v15::VerifyingKey::<Sha384>::new(pubkey)
1367 .verify(tbs, &signature)
1368 .is_ok(),
1369 SigHash::Sha512 => rsa::pkcs1v15::VerifyingKey::<Sha512>::new(pubkey)
1370 .verify(tbs, &signature)
1371 .is_ok(),
1372 }
1373}
1374
1375fn verify_ed25519(pubkey: &[u8], sig: &[u8], tbs: &[u8]) -> bool {
1376 let Ok(key_bytes): Result<[u8; 32], _> = pubkey.try_into() else {
1377 return false;
1378 };
1379 let Ok(vk) = ed25519_dalek::VerifyingKey::from_bytes(&key_bytes) else {
1380 return false;
1381 };
1382 let Ok(signature) = ed25519_dalek::Signature::from_slice(sig) else {
1383 return false;
1384 };
1385 vk.verify_strict(tbs, &signature).is_ok()
1386}
1387
1388#[cfg(test)]
1389mod tests;