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 
15 use core::default::Default;
16 
17 use crate::{
18     budget::Budget,
19     cert::{self, Cert, EndEntityOrCa},
20     der, equal,
21     error::ErrorExt,
22     name, signed_data, time, Error, SignatureAlgorithm, TrustAnchor,
23 };
24 
build_chain( required_eku_if_present: KeyPurposeId, supported_sig_algs: &[&SignatureAlgorithm], trust_anchors: &[TrustAnchor], intermediate_certs: &[&[u8]], cert: &Cert, time: time::Time, ) -> Result<(), ErrorExt>25 pub fn build_chain(
26     required_eku_if_present: KeyPurposeId,
27     supported_sig_algs: &[&SignatureAlgorithm],
28     trust_anchors: &[TrustAnchor],
29     intermediate_certs: &[&[u8]],
30     cert: &Cert,
31     time: time::Time,
32 ) -> Result<(), ErrorExt> {
33     build_chain_inner(
34         required_eku_if_present,
35         supported_sig_algs,
36         trust_anchors,
37         intermediate_certs,
38         cert,
39         time,
40         0,
41         &mut Budget::default(),
42     )
43 }
44 
45 #[allow(clippy::too_many_arguments)]
build_chain_inner( required_eku_if_present: KeyPurposeId, supported_sig_algs: &[&SignatureAlgorithm], trust_anchors: &[TrustAnchor], intermediate_certs: &[&[u8]], cert: &Cert, time: time::Time, sub_ca_count: usize, budget: &mut Budget, ) -> Result<(), ErrorExt>46 fn build_chain_inner(
47     required_eku_if_present: KeyPurposeId,
48     supported_sig_algs: &[&SignatureAlgorithm],
49     trust_anchors: &[TrustAnchor],
50     intermediate_certs: &[&[u8]],
51     cert: &Cert,
52     time: time::Time,
53     sub_ca_count: usize,
54     budget: &mut Budget,
55 ) -> Result<(), ErrorExt> {
56     let used_as_ca = used_as_ca(&cert.ee_or_ca);
57 
58     check_issuer_independent_properties(
59         cert,
60         time,
61         used_as_ca,
62         sub_ca_count,
63         required_eku_if_present,
64     )?;
65 
66     // TODO: HPKP checks.
67 
68     match used_as_ca {
69         UsedAsCa::Yes => {
70             const MAX_SUB_CA_COUNT: usize = 6;
71 
72             if sub_ca_count >= MAX_SUB_CA_COUNT {
73                 return Err(Error::UnknownIssuer.into());
74             }
75         }
76         UsedAsCa::No => {
77             assert_eq!(0, sub_ca_count);
78         }
79     }
80 
81     // TODO: revocation.
82 
83     match loop_while_non_fatal_error(trust_anchors, |trust_anchor: &TrustAnchor| {
84         let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject);
85         if !equal(cert.issuer, trust_anchor_subject) {
86             return Err(Error::UnknownIssuer.into());
87         }
88 
89         let trust_anchor_spki = untrusted::Input::from(trust_anchor.spki);
90 
91         // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
92 
93         check_signatures(supported_sig_algs, cert, trust_anchor_spki, budget)?;
94 
95         check_signed_chain_name_constraints(cert, trust_anchor)?;
96 
97         Ok(())
98     }) {
99         Ok(()) => {
100             return Ok(());
101         }
102         Err(e) => {
103             if e.is_fatal() {
104                 return Err(e);
105             }
106             // If the error is not fatal, then keep going.
107         }
108     }
109 
110     loop_while_non_fatal_error(intermediate_certs, |cert_der| {
111         let potential_issuer =
112             cert::parse_cert(untrusted::Input::from(cert_der), EndEntityOrCa::Ca(cert))?;
113 
114         if !equal(potential_issuer.subject, cert.issuer) {
115             return Err(Error::UnknownIssuer.into());
116         }
117 
118         // Prevent loops; see RFC 4158 section 5.2.
119         let mut prev = cert;
120         loop {
121             if equal(potential_issuer.spki.value(), prev.spki.value())
122                 && equal(potential_issuer.subject, prev.subject)
123             {
124                 return Err(Error::UnknownIssuer.into());
125             }
126             match &prev.ee_or_ca {
127                 EndEntityOrCa::EndEntity => {
128                     break;
129                 }
130                 EndEntityOrCa::Ca(child_cert) => {
131                     prev = child_cert;
132                 }
133             }
134         }
135 
136         let next_sub_ca_count = match used_as_ca {
137             UsedAsCa::No => sub_ca_count,
138             UsedAsCa::Yes => sub_ca_count + 1,
139         };
140 
141         budget.consume_build_chain_call()?;
142         build_chain_inner(
143             required_eku_if_present,
144             supported_sig_algs,
145             trust_anchors,
146             intermediate_certs,
147             &potential_issuer,
148             time,
149             next_sub_ca_count,
150             budget,
151         )
152     })
153 }
154 
check_signatures( supported_sig_algs: &[&SignatureAlgorithm], cert_chain: &Cert, trust_anchor_key: untrusted::Input, budget: &mut Budget, ) -> Result<(), ErrorExt>155 fn check_signatures(
156     supported_sig_algs: &[&SignatureAlgorithm],
157     cert_chain: &Cert,
158     trust_anchor_key: untrusted::Input,
159     budget: &mut Budget,
160 ) -> Result<(), ErrorExt> {
161     let mut spki_value = trust_anchor_key;
162     let mut cert = cert_chain;
163     loop {
164         budget.consume_signature()?;
165         signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data)?;
166 
167         // TODO: check revocation
168 
169         match &cert.ee_or_ca {
170             EndEntityOrCa::Ca(child_cert) => {
171                 spki_value = cert.spki.value();
172                 cert = child_cert;
173             }
174             EndEntityOrCa::EndEntity => {
175                 break;
176             }
177         }
178     }
179 
180     Ok(())
181 }
182 
check_signed_chain_name_constraints( cert_chain: &Cert, trust_anchor: &TrustAnchor, ) -> Result<(), Error>183 fn check_signed_chain_name_constraints(
184     cert_chain: &Cert,
185     trust_anchor: &TrustAnchor,
186 ) -> Result<(), Error> {
187     let mut cert = cert_chain;
188     let mut name_constraints = trust_anchor.name_constraints.map(untrusted::Input::from);
189 
190     loop {
191         untrusted::read_all_optional(name_constraints, Error::BadDer, |value| {
192             name::check_name_constraints(value, cert)
193         })?;
194 
195         match &cert.ee_or_ca {
196             EndEntityOrCa::Ca(child_cert) => {
197                 name_constraints = cert.name_constraints;
198                 cert = child_cert;
199             }
200             EndEntityOrCa::EndEntity => {
201                 break;
202             }
203         }
204     }
205 
206     Ok(())
207 }
208 
check_issuer_independent_properties( cert: &Cert, time: time::Time, used_as_ca: UsedAsCa, sub_ca_count: usize, required_eku_if_present: KeyPurposeId, ) -> Result<(), Error>209 fn check_issuer_independent_properties(
210     cert: &Cert,
211     time: time::Time,
212     used_as_ca: UsedAsCa,
213     sub_ca_count: usize,
214     required_eku_if_present: KeyPurposeId,
215 ) -> Result<(), Error> {
216     // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
217     // TODO: Check signature algorithm like mozilla::pkix.
218     // TODO: Check SPKI like mozilla::pkix.
219     // TODO: check for active distrust like mozilla::pkix.
220 
221     // See the comment in `remember_extension` for why we don't check the
222     // KeyUsage extension.
223 
224     cert.validity
225         .read_all(Error::BadDer, |value| check_validity(value, time))?;
226     untrusted::read_all_optional(cert.basic_constraints, Error::BadDer, |value| {
227         check_basic_constraints(value, used_as_ca, sub_ca_count)
228     })?;
229     untrusted::read_all_optional(cert.eku, Error::BadDer, |value| {
230         check_eku(value, required_eku_if_present)
231     })?;
232 
233     Ok(())
234 }
235 
236 // https://tools.ietf.org/html/rfc5280#section-4.1.2.5
check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error>237 fn check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error> {
238     let not_before = der::time_choice(input)?;
239     let not_after = der::time_choice(input)?;
240 
241     if not_before > not_after {
242         return Err(Error::InvalidCertValidity);
243     }
244     if time < not_before {
245         return Err(Error::CertNotValidYet);
246     }
247     if time > not_after {
248         return Err(Error::CertExpired);
249     }
250 
251     // TODO: mozilla::pkix allows the TrustDomain to check not_before and
252     // not_after, to enforce things like a maximum validity period. We should
253     // do something similar.
254 
255     Ok(())
256 }
257 
258 #[derive(Clone, Copy)]
259 enum UsedAsCa {
260     Yes,
261     No,
262 }
263 
used_as_ca(ee_or_ca: &EndEntityOrCa) -> UsedAsCa264 fn used_as_ca(ee_or_ca: &EndEntityOrCa) -> UsedAsCa {
265     match ee_or_ca {
266         EndEntityOrCa::EndEntity => UsedAsCa::No,
267         EndEntityOrCa::Ca(..) => UsedAsCa::Yes,
268     }
269 }
270 
271 // https://tools.ietf.org/html/rfc5280#section-4.2.1.9
check_basic_constraints( input: Option<&mut untrusted::Reader>, used_as_ca: UsedAsCa, sub_ca_count: usize, ) -> Result<(), Error>272 fn check_basic_constraints(
273     input: Option<&mut untrusted::Reader>,
274     used_as_ca: UsedAsCa,
275     sub_ca_count: usize,
276 ) -> Result<(), Error> {
277     let (is_ca, path_len_constraint) = match input {
278         Some(input) => {
279             let is_ca = der::optional_boolean(input)?;
280 
281             // https://bugzilla.mozilla.org/show_bug.cgi?id=985025: RFC 5280
282             // says that a certificate must not have pathLenConstraint unless
283             // it is a CA certificate, but some real-world end-entity
284             // certificates have pathLenConstraint.
285             let path_len_constraint = if !input.at_end() {
286                 let value = der::small_nonnegative_integer(input)?;
287                 Some(usize::from(value))
288             } else {
289                 None
290             };
291 
292             (is_ca, path_len_constraint)
293         }
294         None => (false, None),
295     };
296 
297     match (used_as_ca, is_ca, path_len_constraint) {
298         (UsedAsCa::No, true, _) => Err(Error::CaUsedAsEndEntity),
299         (UsedAsCa::Yes, false, _) => Err(Error::EndEntityUsedAsCa),
300         (UsedAsCa::Yes, true, Some(len)) if sub_ca_count > len => {
301             Err(Error::PathLenConstraintViolated)
302         }
303         _ => Ok(()),
304     }
305 }
306 
307 #[derive(Clone, Copy)]
308 pub struct KeyPurposeId {
309     oid_value: untrusted::Input<'static>,
310 }
311 
312 // id-pkix            OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 }
313 // id-kp              OBJECT IDENTIFIER ::= { id-pkix 3 }
314 
315 // id-kp-serverAuth   OBJECT IDENTIFIER ::= { id-kp 1 }
316 #[allow(clippy::identity_op)] // TODO: Make this clearer
317 pub static EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId {
318     oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]),
319 };
320 
321 // id-kp-clientAuth   OBJECT IDENTIFIER ::= { id-kp 2 }
322 #[allow(clippy::identity_op)] // TODO: Make this clearer
323 pub static EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId {
324     oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]),
325 };
326 
327 // id-kp-OCSPSigning  OBJECT IDENTIFIER ::= { id-kp 9 }
328 #[allow(clippy::identity_op)] // TODO: Make this clearer
329 pub static EKU_OCSP_SIGNING: KeyPurposeId = KeyPurposeId {
330     oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]),
331 };
332 
333 // https://tools.ietf.org/html/rfc5280#section-4.2.1.12
334 //
335 // Notable Differences from RFC 5280:
336 //
337 // * We follow the convention established by Microsoft's implementation and
338 //   mozilla::pkix of treating the EKU extension in a CA certificate as a
339 //   restriction on the allowable EKUs for certificates issued by that CA. RFC
340 //   5280 doesn't prescribe any meaning to the EKU extension when a certificate
341 //   is being used as a CA certificate.
342 //
343 // * We do not recognize anyExtendedKeyUsage. NSS and mozilla::pkix do not
344 //   recognize it either.
345 //
346 // * We treat id-Netscape-stepUp as being equivalent to id-kp-serverAuth in CA
347 //   certificates (only). Comodo has issued certificates that require this
348 //   behavior that don't expire until June 2020. See https://bugzilla.mozilla.org/show_bug.cgi?id=982292.
check_eku( input: Option<&mut untrusted::Reader>, required_eku_if_present: KeyPurposeId, ) -> Result<(), Error>349 fn check_eku(
350     input: Option<&mut untrusted::Reader>,
351     required_eku_if_present: KeyPurposeId,
352 ) -> Result<(), Error> {
353     match input {
354         Some(input) => {
355             loop {
356                 let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
357                 if equal(value, required_eku_if_present.oid_value) {
358                     input.skip_to_end();
359                     break;
360                 }
361                 if input.at_end() {
362                     return Err(Error::RequiredEkuNotFound);
363                 }
364             }
365             Ok(())
366         }
367         None => {
368             // http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
369             // "OCSP signing delegation SHALL be designated by the inclusion of
370             // id-kp-OCSPSigning in an extended key usage certificate extension
371             // included in the OCSP response signer's certificate."
372             //
373             // A missing EKU extension generally means "any EKU", but it is
374             // important that id-kp-OCSPSigning is explicit so that a normal
375             // end-entity certificate isn't able to sign trusted OCSP responses
376             // for itself or for other certificates issued by its issuing CA.
377             if equal(
378                 required_eku_if_present.oid_value,
379                 EKU_OCSP_SIGNING.oid_value,
380             ) {
381                 return Err(Error::RequiredEkuNotFound);
382             }
383 
384             Ok(())
385         }
386     }
387 }
388 
loop_while_non_fatal_error<V>( values: V, mut f: impl FnMut(V::Item) -> Result<(), ErrorExt>, ) -> Result<(), ErrorExt> where V: IntoIterator,389 fn loop_while_non_fatal_error<V>(
390     values: V,
391     mut f: impl FnMut(V::Item) -> Result<(), ErrorExt>,
392 ) -> Result<(), ErrorExt>
393 where
394     V: IntoIterator,
395 {
396     for v in values {
397         match f(v) {
398             Ok(()) => {
399                 return Ok(());
400             }
401             Err(e) => {
402                 if e.is_fatal() {
403                     return Err(e);
404                 }
405                 // If the error is not fatal, then keep going.
406             }
407         }
408     }
409     Err(Error::UnknownIssuer.into())
410 }
411