x509_parser/validate/
extensions.rs

1use crate::extensions::*;
2use crate::validate::*;
3use std::collections::HashSet;
4
5// extra-pedantic checks
6
7const WARN_SHOULD_BE_CRITICAL: bool = false;
8
9macro_rules! test_critical {
10    (MUST $ext:ident, $l:ident, $name:expr) => {
11        if !$ext.critical {
12            $l.err(&format!("Extension {} MUST be critical, but is not", $name));
13        }
14    };
15    (MUST NOT $ext:ident, $l:ident, $name:expr) => {
16        if $ext.critical {
17            $l.err(&format!("Extension {} MUST NOT be critical, but is", $name));
18        }
19    };
20    (SHOULD $ext:ident, $l:ident, $name:expr) => {
21        if WARN_SHOULD_BE_CRITICAL && !$ext.critical {
22            $l.warn(&format!(
23                "Extension {} SHOULD be critical, but is not",
24                $name
25            ));
26        }
27    };
28    (SHOULD NOT $ext:ident, $l:ident, $name:expr) => {
29        if WARN_SHOULD_BE_CRITICAL && $ext.critical {
30            $l.warn(&format!(
31                "Extension {} SHOULD NOT be critical, but is",
32                $name
33            ));
34        }
35    };
36}
37
38#[derive(Debug)]
39pub struct X509ExtensionsValidator;
40
41impl<'a> Validator<'a> for X509ExtensionsValidator {
42    type Item = &'a [X509Extension<'a>];
43
44    fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool {
45        let mut res = true;
46        // check for duplicate extensions
47        {
48            let mut m = HashSet::new();
49            for ext in item.iter() {
50                if m.contains(&ext.oid) {
51                    l.err(&format!("Duplicate extension {}", ext.oid));
52                    res = false;
53                } else {
54                    m.insert(ext.oid.clone());
55                }
56            }
57        }
58
59        for ext in item.iter() {
60            // specific extension checks
61            match ext.parsed_extension() {
62                ParsedExtension::AuthorityKeyIdentifier(aki) => {
63                    // Conforming CAs MUST mark this extension as non-critical
64                    test_critical!(MUST NOT ext, l, "AKI");
65                    // issuer or serial is present must be either both present or both absent
66                    if aki.authority_cert_issuer.is_some() ^ aki.authority_cert_serial.is_some() {
67                        l.warn("AKI: only one of Issuer and Serial is present");
68                    }
69                }
70                ParsedExtension::CertificatePolicies(policies) => {
71                    // A certificate policy OID MUST NOT appear more than once in a
72                    // certificate policies extension.
73                    let mut policy_oids = HashSet::new();
74                    for policy_info in policies {
75                        if policy_oids.contains(&policy_info.policy_id) {
76                            l.err(&format!(
77                                "Certificate Policies: duplicate policy {}",
78                                policy_info.policy_id
79                            ));
80                            res = false;
81                        } else {
82                            policy_oids.insert(policy_info.policy_id.clone());
83                        }
84                    }
85                }
86                ParsedExtension::KeyUsage(ku) => {
87                    // SHOULD be critical
88                    test_critical!(SHOULD ext, l, "KeyUsage");
89                    // When the keyUsage extension appears in a certificate, at least one of the bits
90                    // MUST be set to 1.
91                    if ku.flags == 0 {
92                        l.err("KeyUsage: all flags are set to 0");
93                    }
94                }
95                ParsedExtension::SubjectAlternativeName(san) => {
96                    // SHOULD be non-critical
97                    test_critical!(SHOULD NOT ext, l, "SubjectAltName");
98                    for name in &san.general_names {
99                        match name {
100                            GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => {
101                                // should be an ia5string
102                                if !s.as_bytes().iter().all(u8::is_ascii) {
103                                    l.warn(&format!("Invalid charset in 'SAN' entry '{}'", s));
104                                }
105                            }
106                            _ => (),
107                        }
108                    }
109                }
110                _ => (),
111            }
112        }
113        res
114    }
115}