webpki/
verify_cert.rs

1// Copyright 2015 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
10// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15use crate::{
16    cert::{self, Cert, EndEntityOrCA},
17    der, name, signed_data, time, Error, SignatureAlgorithm, TrustAnchor,
18};
19
20pub fn build_chain(
21    required_eku_if_present: KeyPurposeId, supported_sig_algs: &[&SignatureAlgorithm],
22    trust_anchors: &[TrustAnchor], intermediate_certs: &[&[u8]], cert: &Cert, time: time::Time,
23    sub_ca_count: usize,
24) -> Result<(), Error> {
25    let used_as_ca = used_as_ca(&cert.ee_or_ca);
26
27    check_issuer_independent_properties(
28        cert,
29        time,
30        used_as_ca,
31        sub_ca_count,
32        required_eku_if_present,
33    )?;
34
35    // TODO: HPKP checks.
36
37    match used_as_ca {
38        UsedAsCA::Yes => {
39            const MAX_SUB_CA_COUNT: usize = 6;
40
41            if sub_ca_count >= MAX_SUB_CA_COUNT {
42                return Err(Error::UnknownIssuer);
43            }
44        },
45        UsedAsCA::No => {
46            assert_eq!(0, sub_ca_count);
47        },
48    }
49
50    // TODO: revocation.
51
52    match loop_while_non_fatal_error(trust_anchors, |trust_anchor: &TrustAnchor| {
53        let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject);
54        if cert.issuer != trust_anchor_subject {
55            return Err(Error::UnknownIssuer);
56        }
57
58        let name_constraints = trust_anchor.name_constraints.map(untrusted::Input::from);
59
60        untrusted::read_all_optional(name_constraints, Error::BadDER, |value| {
61            name::check_name_constraints(value, &cert)
62        })?;
63
64        let trust_anchor_spki = untrusted::Input::from(trust_anchor.spki);
65
66        // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
67
68        check_signatures(supported_sig_algs, cert, trust_anchor_spki)?;
69
70        Ok(())
71    }) {
72        Ok(()) => {
73            return Ok(());
74        },
75        Err(..) => {
76            // If the error is not fatal, then keep going.
77        },
78    }
79
80    loop_while_non_fatal_error(intermediate_certs, |cert_der| {
81        let potential_issuer =
82            cert::parse_cert(untrusted::Input::from(*cert_der), EndEntityOrCA::CA(&cert))?;
83
84        if potential_issuer.subject != cert.issuer {
85            return Err(Error::UnknownIssuer);
86        }
87
88        // Prevent loops; see RFC 4158 section 5.2.
89        let mut prev = cert;
90        loop {
91            if potential_issuer.spki.value() == prev.spki.value()
92                && potential_issuer.subject == prev.subject
93            {
94                return Err(Error::UnknownIssuer);
95            }
96            match &prev.ee_or_ca {
97                &EndEntityOrCA::EndEntity => {
98                    break;
99                },
100                &EndEntityOrCA::CA(child_cert) => {
101                    prev = child_cert;
102                },
103            }
104        }
105
106        untrusted::read_all_optional(potential_issuer.name_constraints, Error::BadDER, |value| {
107            name::check_name_constraints(value, &cert)
108        })?;
109
110        let next_sub_ca_count = match used_as_ca {
111            UsedAsCA::No => sub_ca_count,
112            UsedAsCA::Yes => sub_ca_count + 1,
113        };
114
115        build_chain(
116            required_eku_if_present,
117            supported_sig_algs,
118            trust_anchors,
119            intermediate_certs,
120            &potential_issuer,
121            time,
122            next_sub_ca_count,
123        )
124    })
125}
126
127fn check_signatures(
128    supported_sig_algs: &[&SignatureAlgorithm], cert_chain: &Cert,
129    trust_anchor_key: untrusted::Input,
130) -> Result<(), Error> {
131    let mut spki_value = trust_anchor_key;
132    let mut cert = cert_chain;
133    loop {
134        signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data)?;
135
136        // TODO: check revocation
137
138        match &cert.ee_or_ca {
139            &EndEntityOrCA::CA(child_cert) => {
140                spki_value = cert.spki.value();
141                cert = child_cert;
142            },
143            &EndEntityOrCA::EndEntity => {
144                break;
145            },
146        }
147    }
148
149    Ok(())
150}
151
152fn check_issuer_independent_properties(
153    cert: &Cert, time: time::Time, used_as_ca: UsedAsCA, sub_ca_count: usize,
154    required_eku_if_present: KeyPurposeId,
155) -> Result<(), Error> {
156    // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
157    // TODO: Check signature algorithm like mozilla::pkix.
158    // TODO: Check SPKI like mozilla::pkix.
159    // TODO: check for active distrust like mozilla::pkix.
160
161    // See the comment in `remember_extension` for why we don't check the
162    // KeyUsage extension.
163
164    cert.validity
165        .read_all(Error::BadDER, |value| check_validity(value, time))?;
166    untrusted::read_all_optional(cert.basic_constraints, Error::BadDER, |value| {
167        check_basic_constraints(value, used_as_ca, sub_ca_count)
168    })?;
169    untrusted::read_all_optional(cert.eku, Error::BadDER, |value| {
170        check_eku(value, required_eku_if_present)
171    })?;
172
173    Ok(())
174}
175
176// https://tools.ietf.org/html/rfc5280#section-4.1.2.5
177fn check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error> {
178    let not_before = der::time_choice(input)?;
179    let not_after = der::time_choice(input)?;
180
181    if not_before > not_after {
182        return Err(Error::InvalidCertValidity);
183    }
184    if time < not_before {
185        return Err(Error::CertNotValidYet);
186    }
187    if time > not_after {
188        return Err(Error::CertExpired);
189    }
190
191    // TODO: mozilla::pkix allows the TrustDomain to check not_before and
192    // not_after, to enforce things like a maximum validity period. We should
193    // do something similar.
194
195    Ok(())
196}
197
198#[derive(Clone, Copy)]
199enum UsedAsCA {
200    Yes,
201    No,
202}
203
204fn used_as_ca(ee_or_ca: &EndEntityOrCA) -> UsedAsCA {
205    match ee_or_ca {
206        &EndEntityOrCA::EndEntity => UsedAsCA::No,
207        &EndEntityOrCA::CA(..) => UsedAsCA::Yes,
208    }
209}
210
211// https://tools.ietf.org/html/rfc5280#section-4.2.1.9
212fn check_basic_constraints(
213    input: Option<&mut untrusted::Reader>, used_as_ca: UsedAsCA, sub_ca_count: usize,
214) -> Result<(), Error> {
215    let (is_ca, path_len_constraint) = match input {
216        Some(input) => {
217            let is_ca = der::optional_boolean(input)?;
218
219            // https://bugzilla.mozilla.org/show_bug.cgi?id=985025: RFC 5280
220            // says that a certificate must not have pathLenConstraint unless
221            // it is a CA certificate, but some real-world end-entity
222            // certificates have pathLenConstraint.
223            let path_len_constraint = if !input.at_end() {
224                let value = der::small_nonnegative_integer(input)?;
225                Some(value as usize)
226            } else {
227                None
228            };
229
230            (is_ca, path_len_constraint)
231        },
232        None => (false, None),
233    };
234
235    match (used_as_ca, is_ca, path_len_constraint) {
236        (UsedAsCA::No, true, _) => Err(Error::CAUsedAsEndEntity),
237        (UsedAsCA::Yes, false, _) => Err(Error::EndEntityUsedAsCA),
238        (UsedAsCA::Yes, true, Some(len)) if sub_ca_count > len =>
239            Err(Error::PathLenConstraintViolated),
240        _ => Ok(()),
241    }
242}
243
244#[derive(Clone, Copy)]
245pub struct KeyPurposeId {
246    oid_value: untrusted::Input<'static>,
247}
248
249// id-pkix            OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 }
250// id-kp              OBJECT IDENTIFIER ::= { id-pkix 3 }
251
252// id-kp-serverAuth   OBJECT IDENTIFIER ::= { id-kp 1 }
253pub static EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId {
254    oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]),
255};
256
257// id-kp-clientAuth   OBJECT IDENTIFIER ::= { id-kp 2 }
258pub static EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId {
259    oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]),
260};
261
262// id-kp-OCSPSigning  OBJECT IDENTIFIER ::= { id-kp 9 }
263pub static EKU_OCSP_SIGNING: KeyPurposeId = KeyPurposeId {
264    oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]),
265};
266
267// https://tools.ietf.org/html/rfc5280#section-4.2.1.12
268//
269// Notable Differences from RFC 5280:
270//
271// * We follow the convention established by Microsoft's implementation and
272//   mozilla::pkix of treating the EKU extension in a CA certificate as a
273//   restriction on the allowable EKUs for certificates issued by that CA. RFC
274//   5280 doesn't prescribe any meaning to the EKU extension when a certificate
275//   is being used as a CA certificate.
276//
277// * We do not recognize anyExtendedKeyUsage. NSS and mozilla::pkix do not
278//   recognize it either.
279//
280// * We treat id-Netscape-stepUp as being equivalent to id-kp-serverAuth in CA
281//   certificates (only). Comodo has issued certificates that require this
282//   behavior that don't expire until June 2020. See https://bugzilla.mozilla.org/show_bug.cgi?id=982292.
283fn check_eku(
284    input: Option<&mut untrusted::Reader>, required_eku_if_present: KeyPurposeId,
285) -> Result<(), Error> {
286    match input {
287        Some(input) => {
288            loop {
289                let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
290                if value == required_eku_if_present.oid_value {
291                    input.skip_to_end();
292                    break;
293                }
294                if input.at_end() {
295                    return Err(Error::RequiredEKUNotFound);
296                }
297            }
298            Ok(())
299        },
300        None => {
301            // http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
302            // "OCSP signing delegation SHALL be designated by the inclusion of
303            // id-kp-OCSPSigning in an extended key usage certificate extension
304            // included in the OCSP response signer's certificate."
305            //
306            // A missing EKU extension generally means "any EKU", but it is
307            // important that id-kp-OCSPSigning is explicit so that a normal
308            // end-entity certificate isn't able to sign trusted OCSP responses
309            // for itself or for other certificates issued by its issuing CA.
310            if required_eku_if_present.oid_value == EKU_OCSP_SIGNING.oid_value {
311                return Err(Error::RequiredEKUNotFound);
312            }
313
314            Ok(())
315        },
316    }
317}
318
319fn loop_while_non_fatal_error<V, F>(values: V, f: F) -> Result<(), Error>
320where
321    V: IntoIterator,
322    F: Fn(V::Item) -> Result<(), Error>,
323{
324    for v in values {
325        match f(v) {
326            Ok(()) => {
327                return Ok(());
328            },
329            Err(..) => {
330                // If the error is not fatal, then keep going.
331            },
332        }
333    }
334    Err(Error::UnknownIssuer)
335}