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