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