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, 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 
check_signatures( supported_sig_algs: &[&SignatureAlgorithm], cert_chain: &Cert, trust_anchor_key: untrusted::Input, ) -> Result<(), Error>127 fn 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 
check_issuer_independent_properties( cert: &Cert, time: time::Time, used_as_ca: UsedAsCA, sub_ca_count: usize, required_eku_if_present: KeyPurposeId, ) -> Result<(), Error>152 fn 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
check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error>177 fn 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)]
199 enum UsedAsCA {
200     Yes,
201     No,
202 }
203 
used_as_ca(ee_or_ca: &EndEntityOrCA) -> UsedAsCA204 fn 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
check_basic_constraints( input: Option<&mut untrusted::Reader>, used_as_ca: UsedAsCA, sub_ca_count: usize, ) -> Result<(), Error>212 fn 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)]
245 pub 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 }
253 pub 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 }
258 pub 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 }
263 pub 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.
check_eku( input: Option<&mut untrusted::Reader>, required_eku_if_present: KeyPurposeId, ) -> Result<(), Error>283 fn 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 
loop_while_non_fatal_error<V, F>(values: V, f: F) -> Result<(), Error> where V: IntoIterator, F: Fn(V::Item) -> Result<(), Error>,319 fn loop_while_non_fatal_error<V, F>(values: V, f: F) -> Result<(), Error>
320 where
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 }
336