/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-webpki-0.103.8/src/verify_cert.rs
Line | Count | Source |
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 | | #[cfg(feature = "alloc")] |
16 | | use alloc::vec::Vec; |
17 | | use core::fmt; |
18 | | use core::ops::ControlFlow; |
19 | | |
20 | | use pki_types::{CertificateDer, SignatureVerificationAlgorithm, TrustAnchor, UnixTime}; |
21 | | |
22 | | use crate::cert::Cert; |
23 | | use crate::crl::RevocationOptions; |
24 | | use crate::der::{self, FromDer}; |
25 | | use crate::end_entity::EndEntityCert; |
26 | | use crate::error::Error; |
27 | | use crate::{public_values_eq, signed_data, subject_name}; |
28 | | |
29 | | // Use `'a` for lifetimes that we don't care about, `'p` for lifetimes that become a part of |
30 | | // the `VerifiedPath`. |
31 | | pub(crate) struct ChainOptions<'a, 'p, V> { |
32 | | pub(crate) eku: V, |
33 | | pub(crate) supported_sig_algs: &'a [&'a dyn SignatureVerificationAlgorithm], |
34 | | pub(crate) trust_anchors: &'p [TrustAnchor<'p>], |
35 | | pub(crate) intermediate_certs: &'p [CertificateDer<'p>], |
36 | | pub(crate) revocation: Option<RevocationOptions<'a>>, |
37 | | } |
38 | | |
39 | | impl<'a, 'p: 'a, V: ExtendedKeyUsageValidator> ChainOptions<'a, 'p, V> { |
40 | 0 | pub(crate) fn build_chain( |
41 | 0 | &self, |
42 | 0 | end_entity: &'p EndEntityCert<'p>, |
43 | 0 | time: UnixTime, |
44 | 0 | verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, |
45 | 0 | ) -> Result<VerifiedPath<'p>, Error> { |
46 | 0 | let mut path = PartialPath::new(end_entity); |
47 | 0 | match self.build_chain_inner(&mut path, time, verify_path, 0, &mut Budget::default()) { |
48 | 0 | Ok(anchor) => Ok(VerifiedPath::new(end_entity, anchor, path)), |
49 | 0 | Err(ControlFlow::Break(err)) | Err(ControlFlow::Continue(err)) => Err(err), |
50 | | } |
51 | 0 | } Unexecuted instantiation: <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain Unexecuted instantiation: <webpki::verify_cert::ChainOptions<_>>::build_chain |
52 | | |
53 | 0 | fn build_chain_inner( |
54 | 0 | &self, |
55 | 0 | path: &mut PartialPath<'p>, |
56 | 0 | time: UnixTime, |
57 | 0 | verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, |
58 | 0 | sub_ca_count: usize, |
59 | 0 | budget: &mut Budget, |
60 | 0 | ) -> Result<&'p TrustAnchor<'p>, ControlFlow<Error, Error>> { |
61 | 0 | let role = path.node().role(); |
62 | | |
63 | 0 | check_issuer_independent_properties(path.head(), time, role, sub_ca_count, &self.eku)?; |
64 | | |
65 | | // TODO: HPKP checks. |
66 | | |
67 | 0 | let result = |
68 | 0 | loop_while_non_fatal_error(Error::UnknownIssuer, self.trust_anchors, |trust_anchor| { |
69 | 0 | let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject.as_ref()); |
70 | 0 | if !public_values_eq(path.head().issuer, trust_anchor_subject) { |
71 | 0 | return Err(Error::UnknownIssuer.into()); |
72 | 0 | } |
73 | | |
74 | | // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?; |
75 | | |
76 | 0 | let node = path.node(); |
77 | 0 | self.check_signed_chain(&node, time, trust_anchor, budget)?; |
78 | 0 | check_signed_chain_name_constraints(&node, trust_anchor, budget)?; |
79 | | |
80 | 0 | let verify = match verify_path { |
81 | 0 | Some(verify) => verify, |
82 | 0 | None => return Ok(trust_anchor), |
83 | | }; |
84 | | |
85 | 0 | let candidate = VerifiedPath { |
86 | 0 | end_entity: path.end_entity, |
87 | 0 | intermediates: Intermediates::Borrowed(&path.intermediates[..path.used]), |
88 | 0 | anchor: trust_anchor, |
89 | 0 | }; |
90 | | |
91 | 0 | match verify(&candidate) { |
92 | 0 | Ok(()) => Ok(trust_anchor), |
93 | 0 | Err(err) => Err(ControlFlow::Continue(err)), |
94 | | } |
95 | 0 | }); Unexecuted instantiation: <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain_inner::{closure#0}Unexecuted instantiation: <webpki::verify_cert::ChainOptions<_>>::build_chain_inner::{closure#0} |
96 | | |
97 | 0 | let err = match result { |
98 | 0 | Ok(anchor) => return Ok(anchor), |
99 | | // Fatal errors should halt further path building. |
100 | 0 | res @ Err(ControlFlow::Break(_)) => return res, |
101 | | // Non-fatal errors should be carried forward as the default_error for subsequent |
102 | | // loop_while_non_fatal_error processing and only returned once all other path-building |
103 | | // options have been exhausted. |
104 | 0 | Err(ControlFlow::Continue(err)) => err, |
105 | | }; |
106 | | |
107 | 0 | loop_while_non_fatal_error(err, self.intermediate_certs, |cert_der| { |
108 | 0 | let potential_issuer = Cert::from_der(untrusted::Input::from(cert_der))?; |
109 | 0 | if !public_values_eq(potential_issuer.subject, path.head().issuer) { |
110 | 0 | return Err(Error::UnknownIssuer.into()); |
111 | 0 | } |
112 | | |
113 | | // Prevent loops; see RFC 4158 section 5.2. |
114 | 0 | if path.node().iter().any(|prev| { |
115 | 0 | public_values_eq(potential_issuer.spki, prev.cert.spki) |
116 | 0 | && public_values_eq(potential_issuer.subject, prev.cert.subject) |
117 | 0 | }) {Unexecuted instantiation: <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain_inner::{closure#1}::{closure#0}Unexecuted instantiation: <webpki::verify_cert::ChainOptions<_>>::build_chain_inner::{closure#1}::{closure#0} |
118 | 0 | return Err(Error::UnknownIssuer.into()); |
119 | 0 | } |
120 | | |
121 | 0 | let next_sub_ca_count = match role { |
122 | 0 | Role::EndEntity => sub_ca_count, |
123 | 0 | Role::Issuer => sub_ca_count + 1, |
124 | | }; |
125 | | |
126 | 0 | budget.consume_build_chain_call()?; |
127 | 0 | path.push(potential_issuer)?; |
128 | 0 | let result = self.build_chain_inner(path, time, verify_path, next_sub_ca_count, budget); |
129 | 0 | if result.is_err() { |
130 | 0 | path.pop(); |
131 | 0 | } |
132 | | |
133 | 0 | result |
134 | 0 | }) Unexecuted instantiation: <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain_inner::{closure#1}Unexecuted instantiation: <webpki::verify_cert::ChainOptions<_>>::build_chain_inner::{closure#1} |
135 | 0 | } Unexecuted instantiation: <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain_inner Unexecuted instantiation: <webpki::verify_cert::ChainOptions<_>>::build_chain_inner |
136 | | |
137 | 0 | fn check_signed_chain( |
138 | 0 | &self, |
139 | 0 | path: &PathNode<'_>, |
140 | 0 | time: UnixTime, |
141 | 0 | trust_anchor: &TrustAnchor<'_>, |
142 | 0 | budget: &mut Budget, |
143 | 0 | ) -> Result<(), ControlFlow<Error, Error>> { |
144 | 0 | let mut spki_value = untrusted::Input::from(trust_anchor.subject_public_key_info.as_ref()); |
145 | 0 | let mut issuer_subject = untrusted::Input::from(trust_anchor.subject.as_ref()); |
146 | 0 | let mut issuer_key_usage = None; // TODO(XXX): Consider whether to track TrustAnchor KU. |
147 | 0 | for path in path.iter() { |
148 | 0 | signed_data::verify_signed_data( |
149 | 0 | self.supported_sig_algs, |
150 | 0 | spki_value, |
151 | 0 | &path.cert.signed_data, |
152 | 0 | budget, |
153 | 0 | )?; |
154 | | |
155 | 0 | if let Some(revocation_opts) = &self.revocation { |
156 | 0 | revocation_opts.check( |
157 | 0 | &path, |
158 | 0 | issuer_subject, |
159 | 0 | spki_value, |
160 | 0 | issuer_key_usage, |
161 | 0 | self.supported_sig_algs, |
162 | 0 | budget, |
163 | 0 | time, |
164 | 0 | )?; |
165 | 0 | } |
166 | | |
167 | 0 | spki_value = path.cert.spki; |
168 | 0 | issuer_subject = path.cert.subject; |
169 | 0 | issuer_key_usage = path.cert.key_usage; |
170 | | } |
171 | | |
172 | 0 | Ok(()) |
173 | 0 | } Unexecuted instantiation: <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::check_signed_chain Unexecuted instantiation: <webpki::verify_cert::ChainOptions<_>>::check_signed_chain |
174 | | } |
175 | | |
176 | | /// Path from end-entity certificate to trust anchor that's been verified. |
177 | | /// |
178 | | /// See [`EndEntityCert::verify_for_usage()`] for more details on what verification entails. |
179 | | pub struct VerifiedPath<'p> { |
180 | | end_entity: &'p EndEntityCert<'p>, |
181 | | intermediates: Intermediates<'p>, |
182 | | anchor: &'p TrustAnchor<'p>, |
183 | | } |
184 | | |
185 | | impl<'p> VerifiedPath<'p> { |
186 | 0 | fn new( |
187 | 0 | end_entity: &'p EndEntityCert<'p>, |
188 | 0 | anchor: &'p TrustAnchor<'p>, |
189 | 0 | partial: PartialPath<'p>, |
190 | 0 | ) -> Self { |
191 | 0 | Self { |
192 | 0 | end_entity, |
193 | 0 | intermediates: Intermediates::Owned { |
194 | 0 | certs: partial.intermediates, |
195 | 0 | used: partial.used, |
196 | 0 | }, |
197 | 0 | anchor, |
198 | 0 | } |
199 | 0 | } |
200 | | |
201 | | /// Yields a (double-ended) iterator over the intermediate certificates in this path. |
202 | 0 | pub fn intermediate_certificates(&'p self) -> IntermediateIterator<'p> { |
203 | 0 | IntermediateIterator { |
204 | 0 | intermediates: self.intermediates.as_ref(), |
205 | 0 | } |
206 | 0 | } |
207 | | |
208 | | /// Yields the end-entity certificate for this path. |
209 | 0 | pub fn end_entity(&self) -> &'p EndEntityCert<'p> { |
210 | 0 | self.end_entity |
211 | 0 | } |
212 | | |
213 | | /// Yields the trust anchor for this path. |
214 | 0 | pub fn anchor(&self) -> &'p TrustAnchor<'p> { |
215 | 0 | self.anchor |
216 | 0 | } |
217 | | } |
218 | | |
219 | | /// Iterator over a path's intermediate certificates. |
220 | | /// |
221 | | /// Implements [`DoubleEndedIterator`] so it can be traversed in both directions. |
222 | | pub struct IntermediateIterator<'a> { |
223 | | /// Invariant: all of these `Option`s are `Some`. |
224 | | intermediates: &'a [Option<Cert<'a>>], |
225 | | } |
226 | | |
227 | | impl<'a> Iterator for IntermediateIterator<'a> { |
228 | | type Item = &'a Cert<'a>; |
229 | | |
230 | 0 | fn next(&mut self) -> Option<Self::Item> { |
231 | 0 | match self.intermediates.split_first() { |
232 | 0 | Some((head, tail)) => { |
233 | 0 | self.intermediates = tail; |
234 | 0 | Some(head.as_ref().unwrap()) |
235 | | } |
236 | 0 | None => None, |
237 | | } |
238 | 0 | } |
239 | | } |
240 | | |
241 | | impl DoubleEndedIterator for IntermediateIterator<'_> { |
242 | 0 | fn next_back(&mut self) -> Option<Self::Item> { |
243 | 0 | match self.intermediates.split_last() { |
244 | 0 | Some((head, tail)) => { |
245 | 0 | self.intermediates = tail; |
246 | 0 | Some(head.as_ref().unwrap()) |
247 | | } |
248 | 0 | None => None, |
249 | | } |
250 | 0 | } |
251 | | } |
252 | | |
253 | | #[allow(clippy::large_enum_variant)] |
254 | | enum Intermediates<'a> { |
255 | | Owned { |
256 | | certs: [Option<Cert<'a>>; MAX_SUB_CA_COUNT], |
257 | | used: usize, |
258 | | }, |
259 | | Borrowed(&'a [Option<Cert<'a>>]), |
260 | | } |
261 | | |
262 | | impl<'a> AsRef<[Option<Cert<'a>>]> for Intermediates<'a> { |
263 | 0 | fn as_ref(&self) -> &[Option<Cert<'a>>] { |
264 | 0 | match self { |
265 | 0 | Intermediates::Owned { certs, used } => &certs[..*used], |
266 | 0 | Intermediates::Borrowed(certs) => certs, |
267 | | } |
268 | 0 | } |
269 | | } |
270 | | |
271 | 0 | fn check_signed_chain_name_constraints( |
272 | 0 | path: &PathNode<'_>, |
273 | 0 | trust_anchor: &TrustAnchor<'_>, |
274 | 0 | budget: &mut Budget, |
275 | 0 | ) -> Result<(), ControlFlow<Error, Error>> { |
276 | 0 | let mut name_constraints = trust_anchor |
277 | 0 | .name_constraints |
278 | 0 | .as_ref() |
279 | 0 | .map(|der| untrusted::Input::from(der.as_ref())); |
280 | | |
281 | 0 | for path in path.iter() { |
282 | 0 | untrusted::read_all_optional(name_constraints, Error::BadDer, |value| { |
283 | 0 | subject_name::check_name_constraints(value, &path, budget) |
284 | 0 | })?; |
285 | | |
286 | 0 | name_constraints = path.cert.name_constraints; |
287 | | } |
288 | | |
289 | 0 | Ok(()) |
290 | 0 | } |
291 | | |
292 | | pub(crate) struct Budget { |
293 | | signatures: usize, |
294 | | build_chain_calls: usize, |
295 | | name_constraint_comparisons: usize, |
296 | | } |
297 | | |
298 | | impl Budget { |
299 | | #[inline] |
300 | 0 | pub(crate) fn consume_signature(&mut self) -> Result<(), Error> { |
301 | 0 | self.signatures = self |
302 | 0 | .signatures |
303 | 0 | .checked_sub(1) |
304 | 0 | .ok_or(Error::MaximumSignatureChecksExceeded)?; |
305 | 0 | Ok(()) |
306 | 0 | } |
307 | | |
308 | | #[inline] |
309 | 0 | fn consume_build_chain_call(&mut self) -> Result<(), Error> { |
310 | 0 | self.build_chain_calls = self |
311 | 0 | .build_chain_calls |
312 | 0 | .checked_sub(1) |
313 | 0 | .ok_or(Error::MaximumPathBuildCallsExceeded)?; |
314 | 0 | Ok(()) |
315 | 0 | } Unexecuted instantiation: <webpki::verify_cert::Budget>::consume_build_chain_call Unexecuted instantiation: <webpki::verify_cert::Budget>::consume_build_chain_call |
316 | | |
317 | | #[inline] |
318 | 0 | pub(crate) fn consume_name_constraint_comparison(&mut self) -> Result<(), Error> { |
319 | 0 | self.name_constraint_comparisons = self |
320 | 0 | .name_constraint_comparisons |
321 | 0 | .checked_sub(1) |
322 | 0 | .ok_or(Error::MaximumNameConstraintComparisonsExceeded)?; |
323 | 0 | Ok(()) |
324 | 0 | } |
325 | | } |
326 | | |
327 | | impl Default for Budget { |
328 | 0 | fn default() -> Self { |
329 | 0 | Self { |
330 | 0 | // This limit is taken from the remediation for golang CVE-2018-16875. However, |
331 | 0 | // note that golang subsequently implemented AKID matching due to this limit |
332 | 0 | // being hit in real applications (see <https://github.com/spiffe/spire/issues/1004>). |
333 | 0 | // So this may actually be too aggressive. |
334 | 0 | signatures: 100, |
335 | 0 |
|
336 | 0 | // This limit is taken from mozilla::pkix, see: |
337 | 0 | // <https://github.com/nss-dev/nss/blob/bb4a1d38dd9e92923525ac6b5ed0288479f3f3fc/lib/mozpkix/lib/pkixbuild.cpp#L381-L393> |
338 | 0 | build_chain_calls: 200_000, |
339 | 0 |
|
340 | 0 | // This limit is taken from golang crypto/x509's default, see: |
341 | 0 | // <https://github.com/golang/go/blob/ac17bb6f13979f2ab9fcd45f0758b43ed72d0973/src/crypto/x509/verify.go#L588-L592> |
342 | 0 | name_constraint_comparisons: 250_000, |
343 | 0 | } |
344 | 0 | } |
345 | | } |
346 | | |
347 | 0 | fn check_issuer_independent_properties( |
348 | 0 | cert: &Cert<'_>, |
349 | 0 | time: UnixTime, |
350 | 0 | role: Role, |
351 | 0 | sub_ca_count: usize, |
352 | 0 | eku: &impl ExtendedKeyUsageValidator, |
353 | 0 | ) -> Result<(), Error> { |
354 | | // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?; |
355 | | // TODO: Check signature algorithm like mozilla::pkix. |
356 | | // TODO: Check SPKI like mozilla::pkix. |
357 | | // TODO: check for active distrust like mozilla::pkix. |
358 | | |
359 | | // For cert validation, we ignore the KeyUsage extension. For CA |
360 | | // certificates, BasicConstraints.cA makes KeyUsage redundant. Firefox |
361 | | // and other common browsers do not check KeyUsage for end-entities, |
362 | | // though it would be kind of nice to ensure that a KeyUsage without |
363 | | // the keyEncipherment bit could not be used for RSA key exchange. |
364 | | |
365 | 0 | cert.validity |
366 | 0 | .read_all(Error::BadDer, |value| check_validity(value, time))?; Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>::{closure#0}Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<_>::{closure#0} |
367 | 0 | untrusted::read_all_optional(cert.basic_constraints, Error::BadDer, |value| { |
368 | 0 | check_basic_constraints(value, role, sub_ca_count) |
369 | 0 | })?; Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>::{closure#1}Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<_>::{closure#1} |
370 | 0 | untrusted::read_all_optional(cert.eku, Error::BadDer, |input| check_eku(input, eku))?; Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>::{closure#2}Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<_>::{closure#2} |
371 | | |
372 | 0 | Ok(()) |
373 | 0 | } Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator> Unexecuted instantiation: webpki::verify_cert::check_issuer_independent_properties::<_> |
374 | | |
375 | 0 | fn check_eku( |
376 | 0 | input: Option<&mut untrusted::Reader<'_>>, |
377 | 0 | eku: &impl ExtendedKeyUsageValidator, |
378 | 0 | ) -> Result<(), Error> { |
379 | 0 | match input { |
380 | 0 | Some(input) if input.at_end() => Err(Error::EmptyEkuExtension), |
381 | 0 | Some(input) => eku.validate(KeyPurposeIdIter { input }), |
382 | 0 | None => eku.validate(KeyPurposeIdIter { |
383 | 0 | input: &mut untrusted::Reader::new(untrusted::Input::from(&[])), |
384 | 0 | }), |
385 | | } |
386 | 0 | } Unexecuted instantiation: webpki::verify_cert::check_eku::<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator> Unexecuted instantiation: webpki::verify_cert::check_eku::<_> |
387 | | |
388 | | // https://tools.ietf.org/html/rfc5280#section-4.1.2.5 |
389 | 0 | fn check_validity(input: &mut untrusted::Reader<'_>, time: UnixTime) -> Result<(), Error> { |
390 | 0 | let not_before = UnixTime::from_der(input)?; |
391 | 0 | let not_after = UnixTime::from_der(input)?; |
392 | | |
393 | 0 | if not_before > not_after { |
394 | 0 | return Err(Error::InvalidCertValidity); |
395 | 0 | } |
396 | 0 | if time < not_before { |
397 | 0 | return Err(Error::CertNotValidYet { time, not_before }); |
398 | 0 | } |
399 | 0 | if time > not_after { |
400 | 0 | return Err(Error::CertExpired { time, not_after }); |
401 | 0 | } |
402 | | |
403 | | // TODO: mozilla::pkix allows the TrustDomain to check not_before and |
404 | | // not_after, to enforce things like a maximum validity period. We should |
405 | | // do something similar. |
406 | | |
407 | 0 | Ok(()) |
408 | 0 | } |
409 | | |
410 | | // https://tools.ietf.org/html/rfc5280#section-4.2.1.9 |
411 | 0 | fn check_basic_constraints( |
412 | 0 | input: Option<&mut untrusted::Reader<'_>>, |
413 | 0 | role: Role, |
414 | 0 | sub_ca_count: usize, |
415 | 0 | ) -> Result<(), Error> { |
416 | 0 | let (is_ca, path_len_constraint) = match input { |
417 | 0 | Some(input) => { |
418 | 0 | let is_ca = bool::from_der(input)?; |
419 | | |
420 | | // https://bugzilla.mozilla.org/show_bug.cgi?id=985025: RFC 5280 |
421 | | // says that a certificate must not have pathLenConstraint unless |
422 | | // it is a CA certificate, but some real-world end-entity |
423 | | // certificates have pathLenConstraint. |
424 | 0 | let path_len_constraint = if !input.at_end() { |
425 | 0 | Some(usize::from(u8::from_der(input)?)) |
426 | | } else { |
427 | 0 | None |
428 | | }; |
429 | | |
430 | 0 | (is_ca, path_len_constraint) |
431 | | } |
432 | 0 | None => (false, None), |
433 | | }; |
434 | | |
435 | 0 | match (role, is_ca, path_len_constraint) { |
436 | 0 | (Role::EndEntity, true, _) => Err(Error::CaUsedAsEndEntity), |
437 | 0 | (Role::Issuer, false, _) => Err(Error::EndEntityUsedAsCa), |
438 | 0 | (Role::Issuer, true, Some(len)) if sub_ca_count > len => { |
439 | 0 | Err(Error::PathLenConstraintViolated) |
440 | | } |
441 | 0 | _ => Ok(()), |
442 | | } |
443 | 0 | } |
444 | | |
445 | | /// Additional context for the `RequiredEkuNotFoundContext` error variant. |
446 | | /// |
447 | | /// The contents of this type depend on whether the `alloc` feature is enabled. |
448 | | #[derive(Clone, PartialEq, Eq)] |
449 | | pub struct RequiredEkuNotFoundContext { |
450 | | /// The required ExtendedKeyUsage. |
451 | | #[cfg(feature = "alloc")] |
452 | | pub required: KeyUsage, |
453 | | /// The ExtendedKeyUsage OIDs present in the certificate. |
454 | | #[cfg(feature = "alloc")] |
455 | | pub present: Vec<Vec<usize>>, |
456 | | } |
457 | | |
458 | | impl fmt::Debug for RequiredEkuNotFoundContext { |
459 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
460 | 0 | let mut builder = f.debug_struct("RequiredEkuNotFoundContext"); |
461 | | #[cfg(feature = "alloc")] |
462 | 0 | builder.field( |
463 | 0 | "required", |
464 | 0 | match &self.required.inner { |
465 | 0 | ExtendedKeyUsage::Required(inner) => inner, |
466 | 0 | ExtendedKeyUsage::RequiredIfPresent(inner) => inner, |
467 | | }, |
468 | | ); |
469 | | #[cfg(feature = "alloc")] |
470 | 0 | builder.field("present", &EkuListDebug(&self.present)); |
471 | 0 | builder.finish() |
472 | 0 | } |
473 | | } |
474 | | |
475 | | #[cfg(feature = "alloc")] |
476 | | struct EkuListDebug<'a>(&'a [Vec<usize>]); |
477 | | |
478 | | #[cfg(feature = "alloc")] |
479 | | impl fmt::Debug for EkuListDebug<'_> { |
480 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
481 | 0 | write!(f, "[")?; |
482 | 0 | for (i, part) in self.0.iter().enumerate() { |
483 | 0 | if i > 0 { |
484 | 0 | write!(f, ", ")?; |
485 | 0 | } |
486 | | |
487 | 0 | write!(f, "KeyPurposeId(")?; |
488 | 0 | for (j, part) in part.iter().enumerate() { |
489 | 0 | if j > 0 { |
490 | 0 | write!(f, ".")?; |
491 | 0 | } |
492 | 0 | write!(f, "{part}")?; |
493 | | } |
494 | 0 | write!(f, ")")?; |
495 | | } |
496 | 0 | write!(f, "]") |
497 | 0 | } |
498 | | } |
499 | | |
500 | | /// The expected key usage of a certificate. |
501 | | /// |
502 | | /// This type represents the expected key usage of an end entity certificate. Although for most |
503 | | /// kinds of certificates the extended key usage extension is optional (and so certificates |
504 | | /// not carrying a particular value in the EKU extension are acceptable). If the extension |
505 | | /// is present, the certificate MUST only be used for one of the purposes indicated. |
506 | | /// |
507 | | /// <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12> |
508 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
509 | | pub struct KeyUsage { |
510 | | inner: ExtendedKeyUsage, |
511 | | } |
512 | | |
513 | | impl KeyUsage { |
514 | | /// Construct a new [`KeyUsage`] as appropriate for server certificate authentication. |
515 | | /// |
516 | | /// As specified in <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>, this does not require the certificate to specify the eKU extension. |
517 | 0 | pub const fn server_auth() -> Self { |
518 | 0 | Self::required_if_present(EKU_SERVER_AUTH) |
519 | 0 | } |
520 | | |
521 | | /// Construct a new [`KeyUsage`] as appropriate for client certificate authentication. |
522 | | /// |
523 | | /// As specified in <>, this does not require the certificate to specify the eKU extension. |
524 | 0 | pub const fn client_auth() -> Self { |
525 | 0 | Self::required_if_present(EKU_CLIENT_AUTH) |
526 | 0 | } |
527 | | |
528 | | /// Construct a new [`KeyUsage`] requiring a certificate to support the specified OID. |
529 | 0 | pub const fn required(oid: &'static [u8]) -> Self { |
530 | 0 | Self { |
531 | 0 | inner: ExtendedKeyUsage::Required(KeyPurposeId::new(oid)), |
532 | 0 | } |
533 | 0 | } |
534 | | |
535 | | /// Construct a new [`KeyUsage`] requiring a certificate to support the specified OID, if the certificate has EKUs. |
536 | 0 | pub const fn required_if_present(oid: &'static [u8]) -> Self { |
537 | 0 | Self { |
538 | 0 | inner: ExtendedKeyUsage::RequiredIfPresent(KeyPurposeId::new(oid)), |
539 | 0 | } |
540 | 0 | } |
541 | | |
542 | | /// Yield the OID values of the required extended key usage. |
543 | 0 | pub fn oid_values(&self) -> impl Iterator<Item = usize> + '_ { |
544 | 0 | OidDecoder::new( |
545 | 0 | match &self.inner { |
546 | 0 | ExtendedKeyUsage::Required(eku) => eku, |
547 | 0 | ExtendedKeyUsage::RequiredIfPresent(eku) => eku, |
548 | | } |
549 | | .oid_value |
550 | 0 | .as_slice_less_safe(), |
551 | | ) |
552 | 0 | } |
553 | | |
554 | | /// Human-readable representation of the server authentication OID. |
555 | | pub const SERVER_AUTH_REPR: &[usize] = &[1, 3, 6, 1, 5, 5, 7, 3, 1]; |
556 | | /// Human-readable representation of the client authentication OID. |
557 | | pub const CLIENT_AUTH_REPR: &[usize] = &[1, 3, 6, 1, 5, 5, 7, 3, 2]; |
558 | | } |
559 | | |
560 | | impl ExtendedKeyUsageValidator for KeyUsage { |
561 | | // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 |
562 | 0 | fn validate(&self, iter: KeyPurposeIdIter<'_, '_>) -> Result<(), Error> { |
563 | 0 | let mut empty = true; |
564 | | #[cfg(feature = "alloc")] |
565 | 0 | let mut present = Vec::new(); |
566 | | |
567 | 0 | for id in iter { |
568 | 0 | empty = false; |
569 | 0 | let id = id?; |
570 | 0 | if self.inner.id() == id { |
571 | 0 | return Ok(()); |
572 | 0 | } |
573 | | |
574 | | #[cfg(feature = "alloc")] |
575 | 0 | present.push(id.to_decoded_oid()); |
576 | | } |
577 | | |
578 | 0 | match (empty, self.inner) { |
579 | 0 | (true, ExtendedKeyUsage::RequiredIfPresent(_)) => Ok(()), |
580 | 0 | _ => Err(Error::RequiredEkuNotFoundContext( |
581 | 0 | RequiredEkuNotFoundContext { |
582 | 0 | #[cfg(feature = "alloc")] |
583 | 0 | required: Self { inner: self.inner }, |
584 | 0 | #[cfg(feature = "alloc")] |
585 | 0 | present, |
586 | 0 | }, |
587 | 0 | )), |
588 | | } |
589 | 0 | } |
590 | | } |
591 | | |
592 | | impl<V: ExtendedKeyUsageValidator> ExtendedKeyUsageValidator for &V { |
593 | 0 | fn validate(&self, iter: KeyPurposeIdIter<'_, '_>) -> Result<(), Error> { |
594 | 0 | (*self).validate(iter) |
595 | 0 | } |
596 | | } |
597 | | |
598 | | /// A trait for validating the Extended Key Usage (EKU) extensions of a certificate. |
599 | | pub trait ExtendedKeyUsageValidator { |
600 | | /// Validate the EKU values in a certificate. |
601 | | /// |
602 | | /// `iter` yields the EKU OIDs in the certificate, or an error if the EKU extension |
603 | | /// is malformed. `validate()` should yield `Ok(())` if the EKU values match the |
604 | | /// required policy, or an `Error` if they do not. Ideally the `Error` should be |
605 | | /// `Error::RequiredEkuNotFoundContext` if the policy is not met. |
606 | | fn validate(&self, iter: KeyPurposeIdIter<'_, '_>) -> Result<(), Error>; |
607 | | } |
608 | | |
609 | | /// Extended Key Usage (EKU) of a certificate. |
610 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
611 | | enum ExtendedKeyUsage { |
612 | | /// The certificate must contain the specified [`KeyPurposeId`] as EKU. |
613 | | Required(KeyPurposeId<'static>), |
614 | | |
615 | | /// If the certificate has EKUs, then the specified [`KeyPurposeId`] must be included. |
616 | | RequiredIfPresent(KeyPurposeId<'static>), |
617 | | } |
618 | | |
619 | | impl ExtendedKeyUsage { |
620 | 0 | fn id(&self) -> KeyPurposeId<'static> { |
621 | 0 | match self { |
622 | 0 | Self::Required(id) => *id, |
623 | 0 | Self::RequiredIfPresent(id) => *id, |
624 | | } |
625 | 0 | } |
626 | | } |
627 | | |
628 | | /// Iterator over [`KeyPurposeId`]s, for use in [`ExtendedKeyUsageValidator`]. |
629 | | pub struct KeyPurposeIdIter<'a, 'r> { |
630 | | input: &'r mut untrusted::Reader<'a>, |
631 | | } |
632 | | |
633 | | impl<'a, 'r> Iterator for KeyPurposeIdIter<'a, 'r> { |
634 | | type Item = Result<KeyPurposeId<'a>, Error>; |
635 | | |
636 | 0 | fn next(&mut self) -> Option<Self::Item> { |
637 | 0 | if self.input.at_end() { |
638 | 0 | return None; |
639 | 0 | } |
640 | | |
641 | 0 | Some(der::expect_tag(self.input, der::Tag::OID).map(|oid_value| KeyPurposeId { oid_value })) |
642 | 0 | } |
643 | | } |
644 | | |
645 | | impl Drop for KeyPurposeIdIter<'_, '_> { |
646 | 0 | fn drop(&mut self) { |
647 | 0 | self.input.skip_to_end(); |
648 | 0 | } |
649 | | } |
650 | | |
651 | | /// An OID value indicating an Extended Key Usage (EKU) key purpose. |
652 | | #[derive(Clone, Copy)] |
653 | | pub struct KeyPurposeId<'a> { |
654 | | oid_value: untrusted::Input<'a>, |
655 | | } |
656 | | |
657 | | impl<'a> KeyPurposeId<'a> { |
658 | | /// Construct a new [`KeyPurposeId`]. |
659 | | /// |
660 | | /// `oid` is the OBJECT IDENTIFIER in bytes. |
661 | 0 | pub const fn new(oid: &'a [u8]) -> Self { |
662 | 0 | Self { |
663 | 0 | oid_value: untrusted::Input::from(oid), |
664 | 0 | } |
665 | 0 | } |
666 | | |
667 | | /// Yield the OID value as a sequence of `usize` components. |
668 | | #[cfg(feature = "alloc")] |
669 | 0 | pub fn to_decoded_oid(&self) -> Vec<usize> { |
670 | 0 | OidDecoder::new(self.oid_value.as_slice_less_safe()).collect() |
671 | 0 | } |
672 | | } |
673 | | |
674 | | impl fmt::Debug for KeyPurposeId<'_> { |
675 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
676 | 0 | write!(f, "KeyPurposeId(")?; |
677 | 0 | let decoder = OidDecoder::new(self.oid_value.as_slice_less_safe()); |
678 | 0 | for (i, part) in decoder.enumerate() { |
679 | 0 | if i > 0 { |
680 | 0 | write!(f, ".")?; |
681 | 0 | } |
682 | 0 | write!(f, "{part}")?; |
683 | | } |
684 | 0 | write!(f, ")") |
685 | 0 | } |
686 | | } |
687 | | |
688 | | impl PartialEq<Self> for KeyPurposeId<'_> { |
689 | 0 | fn eq(&self, other: &Self) -> bool { |
690 | 0 | public_values_eq(self.oid_value, other.oid_value) |
691 | 0 | } |
692 | | } |
693 | | |
694 | | impl Eq for KeyPurposeId<'_> {} |
695 | | |
696 | | // id-pkix OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 } |
697 | | // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 } |
698 | | |
699 | | // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } |
700 | | const EKU_SERVER_AUTH: &[u8] = &oid!(1, 3, 6, 1, 5, 5, 7, 3, 1); |
701 | | |
702 | | // id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } |
703 | | const EKU_CLIENT_AUTH: &[u8] = &oid!(1, 3, 6, 1, 5, 5, 7, 3, 2); |
704 | | |
705 | | struct OidDecoder<'a> { |
706 | | encoded: &'a [u8], |
707 | | left: Option<usize>, |
708 | | first: bool, |
709 | | } |
710 | | |
711 | | impl<'a> OidDecoder<'a> { |
712 | 0 | fn new(encoded: &'a [u8]) -> Self { |
713 | 0 | Self { |
714 | 0 | encoded, |
715 | 0 | left: None, |
716 | 0 | first: true, |
717 | 0 | } |
718 | 0 | } |
719 | | } |
720 | | |
721 | | impl Iterator for OidDecoder<'_> { |
722 | | type Item = usize; |
723 | | |
724 | 0 | fn next(&mut self) -> Option<Self::Item> { |
725 | 0 | if let Some(next) = self.left.take() { |
726 | 0 | return Some(next); |
727 | 0 | } |
728 | | |
729 | 0 | let mut cur = 0; |
730 | 0 | for (i, &byte) in self.encoded.iter().enumerate() { |
731 | 0 | cur = (cur << 8) + usize::from(byte & 0x7f); |
732 | 0 | if byte & 0x80 > 0 { |
733 | 0 | continue; |
734 | 0 | } |
735 | | |
736 | 0 | if !self.first { |
737 | 0 | self.encoded = &self.encoded[i + 1..]; |
738 | 0 | return Some(cur); |
739 | 0 | } |
740 | | |
741 | 0 | let (cur, next) = match cur { |
742 | 0 | ..=39 => (0, cur), |
743 | 0 | 40..=79 => (1, cur - 40), |
744 | 0 | _ => (2, cur - 80), |
745 | | }; |
746 | | |
747 | 0 | self.encoded = &self.encoded[i + 1..]; |
748 | 0 | self.first = false; |
749 | 0 | self.left = Some(next); |
750 | 0 | return Some(cur); |
751 | | } |
752 | | |
753 | 0 | None |
754 | 0 | } |
755 | | } |
756 | | |
757 | 0 | fn loop_while_non_fatal_error<'a, V: IntoIterator + 'a>( |
758 | 0 | default_error: Error, |
759 | 0 | values: V, |
760 | 0 | mut f: impl FnMut(V::Item) -> Result<&'a TrustAnchor<'a>, ControlFlow<Error, Error>>, |
761 | 0 | ) -> Result<&'a TrustAnchor<'a>, ControlFlow<Error, Error>> { |
762 | 0 | let mut error = default_error; |
763 | 0 | for v in values { |
764 | 0 | match f(v) { |
765 | 0 | Ok(anchor) => return Ok(anchor), |
766 | | // Fatal errors should halt further looping. |
767 | 0 | res @ Err(ControlFlow::Break(_)) => return res, |
768 | | // Non-fatal errors should be ranked by specificity and only returned |
769 | | // once all other path-building options have been exhausted. |
770 | 0 | Err(ControlFlow::Continue(new_error)) => error = error.most_specific(new_error), |
771 | | } |
772 | | } |
773 | 0 | Err(error.into()) |
774 | 0 | } Unexecuted instantiation: webpki::verify_cert::loop_while_non_fatal_error::<&[rustls_pki_types::TrustAnchor], <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain_inner::{closure#0}>Unexecuted instantiation: webpki::verify_cert::loop_while_non_fatal_error::<&[rustls_pki_types::CertificateDer], <webpki::verify_cert::ChainOptions<spdmlib::crypto::spdm_ring::cert_operation_impl::SpdmResponderEkuValidator>>::build_chain_inner::{closure#1}>Unexecuted instantiation: webpki::verify_cert::loop_while_non_fatal_error::<_, _> |
775 | | |
776 | | /// A path for consideration in path building. |
777 | | /// |
778 | | /// This represents a partial path because it does not yet contain the trust anchor. It stores |
779 | | /// the end-entity certificates, and an array of intermediate certificates. |
780 | | pub(crate) struct PartialPath<'a> { |
781 | | end_entity: &'a EndEntityCert<'a>, |
782 | | /// Intermediate certificates, in order from end-entity to trust anchor. |
783 | | /// |
784 | | /// Invariant: all values below `used` are `Some`. |
785 | | intermediates: [Option<Cert<'a>>; MAX_SUB_CA_COUNT], |
786 | | /// The number of `Some` values in `intermediates`. |
787 | | /// |
788 | | /// The next `Cert` passed to `push()` will be placed at `intermediates[used]`. |
789 | | /// If this value is 0, the path contains only the end-entity certificate. |
790 | | used: usize, |
791 | | } |
792 | | |
793 | | impl<'a> PartialPath<'a> { |
794 | 0 | pub(crate) fn new(end_entity: &'a EndEntityCert<'a>) -> Self { |
795 | 0 | Self { |
796 | 0 | end_entity, |
797 | 0 | intermediates: Default::default(), |
798 | 0 | used: 0, |
799 | 0 | } |
800 | 0 | } |
801 | | |
802 | 0 | pub(crate) fn push(&mut self, cert: Cert<'a>) -> Result<(), ControlFlow<Error, Error>> { |
803 | 0 | if self.used >= MAX_SUB_CA_COUNT { |
804 | 0 | return Err(Error::MaximumPathDepthExceeded.into()); |
805 | 0 | } |
806 | | |
807 | 0 | self.intermediates[self.used] = Some(cert); |
808 | 0 | self.used += 1; |
809 | 0 | Ok(()) |
810 | 0 | } |
811 | | |
812 | 0 | fn pop(&mut self) { |
813 | 0 | debug_assert!(self.used > 0); |
814 | 0 | if self.used == 0 { |
815 | 0 | return; |
816 | 0 | } |
817 | | |
818 | 0 | self.used -= 1; |
819 | 0 | self.intermediates[self.used] = None; |
820 | 0 | } |
821 | | |
822 | 0 | pub(crate) fn node(&self) -> PathNode<'_> { |
823 | 0 | PathNode { |
824 | 0 | path: self, |
825 | 0 | index: self.used, |
826 | 0 | cert: self.head(), |
827 | 0 | } |
828 | 0 | } |
829 | | |
830 | | /// Current head of the path. |
831 | 0 | pub(crate) fn head(&self) -> &Cert<'a> { |
832 | 0 | self.get(self.used) |
833 | 0 | } |
834 | | |
835 | | /// Get the certificate at index `idx` in the path. |
836 | | /// |
837 | | // `idx` must be in the range `0..=self.used`; `idx` 0 thus yields the `end_entity`, |
838 | | // while subsequent indexes yield the intermediate at `self.intermediates[idx - 1]`. |
839 | 0 | fn get(&self, idx: usize) -> &Cert<'a> { |
840 | 0 | match idx { |
841 | 0 | 0 => self.end_entity, |
842 | 0 | _ => self.intermediates[idx - 1].as_ref().unwrap(), |
843 | | } |
844 | 0 | } |
845 | | } |
846 | | |
847 | | const MAX_SUB_CA_COUNT: usize = 6; |
848 | | |
849 | | pub(crate) struct PathNode<'a> { |
850 | | /// The path we're iterating. |
851 | | path: &'a PartialPath<'a>, |
852 | | /// The index of the current node in the path (input for `path.get()`). |
853 | | index: usize, |
854 | | /// The [`Cert`] at `index`. |
855 | | pub(crate) cert: &'a Cert<'a>, |
856 | | } |
857 | | |
858 | | impl<'a> PathNode<'a> { |
859 | 0 | pub(crate) fn iter(&self) -> PathIter<'a> { |
860 | 0 | PathIter { |
861 | 0 | path: self.path, |
862 | 0 | next: Some(self.index), |
863 | 0 | } |
864 | 0 | } |
865 | | |
866 | 0 | pub(crate) fn role(&self) -> Role { |
867 | 0 | match self.index { |
868 | 0 | 0 => Role::EndEntity, |
869 | 0 | _ => Role::Issuer, |
870 | | } |
871 | 0 | } |
872 | | } |
873 | | |
874 | | pub(crate) struct PathIter<'a> { |
875 | | path: &'a PartialPath<'a>, |
876 | | next: Option<usize>, |
877 | | } |
878 | | |
879 | | impl<'a> Iterator for PathIter<'a> { |
880 | | type Item = PathNode<'a>; |
881 | | |
882 | 0 | fn next(&mut self) -> Option<Self::Item> { |
883 | 0 | let next = self.next?; |
884 | 0 | self.next = match next { |
885 | 0 | 0 => None, |
886 | 0 | _ => Some(next - 1), |
887 | | }; |
888 | | |
889 | 0 | Some(PathNode { |
890 | 0 | path: self.path, |
891 | 0 | index: next, |
892 | 0 | cert: self.path.get(next), |
893 | 0 | }) |
894 | 0 | } |
895 | | } |
896 | | |
897 | | #[derive(Clone, Copy, PartialEq)] |
898 | | pub(crate) enum Role { |
899 | | Issuer, |
900 | | EndEntity, |
901 | | } |
902 | | |
903 | | #[cfg(all(test, feature = "alloc", any(feature = "ring", feature = "aws-lc-rs")))] |
904 | | mod tests { |
905 | | use super::*; |
906 | | use crate::test_utils; |
907 | | use crate::test_utils::{issuer_params, make_end_entity, make_issuer}; |
908 | | use crate::trust_anchor::anchor_from_trusted_cert; |
909 | | use rcgen::{CertifiedIssuer, Issuer, KeyPair, SigningKey}; |
910 | | use std::dbg; |
911 | | use std::prelude::v1::*; |
912 | | use std::slice; |
913 | | |
914 | | #[test] |
915 | | fn roundtrip() { |
916 | | // 2.999.3 -> 1079.3 -> [0x84, 0x37, 0x3] |
917 | | const ENCODED: &[u8] = &[0x84, 0x37, 0x3]; |
918 | | let decoded = OidDecoder::new(ENCODED); |
919 | | assert_eq!(decoded.collect::<Vec<_>>(), [2, 999, 3]); |
920 | | } |
921 | | |
922 | | #[test] |
923 | | fn oid_decoding() { |
924 | | assert_eq!( |
925 | | KeyUsage::server_auth().oid_values().collect::<Vec<_>>(), |
926 | | KeyUsage::SERVER_AUTH_REPR |
927 | | ); |
928 | | assert_eq!( |
929 | | KeyUsage::client_auth().oid_values().collect::<Vec<_>>(), |
930 | | KeyUsage::CLIENT_AUTH_REPR |
931 | | ); |
932 | | } |
933 | | |
934 | | #[test] |
935 | | fn eku_fail_empty() { |
936 | | let err = KeyUsage::required(EKU_SERVER_AUTH) |
937 | | .validate(KeyPurposeIdIter { |
938 | | input: &mut untrusted::Reader::new(untrusted::Input::from(&[])), |
939 | | }) |
940 | | .unwrap_err(); |
941 | | assert_eq!( |
942 | | err, |
943 | | Error::RequiredEkuNotFoundContext(RequiredEkuNotFoundContext { |
944 | | #[cfg(feature = "alloc")] |
945 | | required: dbg!(KeyUsage::required(EKU_SERVER_AUTH)), // Cover Debug impl |
946 | | #[cfg(feature = "alloc")] |
947 | | present: Vec::new(), |
948 | | }) |
949 | | ); |
950 | | } |
951 | | |
952 | | #[test] |
953 | | fn eku_fail_empty_with_optional() { |
954 | | let mut empty_eku = untrusted::Reader::new(untrusted::Input::from(&[])); |
955 | | let validator = KeyUsage::required_if_present(EKU_SERVER_AUTH); |
956 | | assert_eq!( |
957 | | check_eku(Some(&mut empty_eku), &validator).unwrap_err(), |
958 | | Error::EmptyEkuExtension, |
959 | | ); |
960 | | } |
961 | | |
962 | | #[test] |
963 | | fn eku_key_purpose_id() { |
964 | | assert!( |
965 | | ExtendedKeyUsage::RequiredIfPresent(KeyPurposeId::new(EKU_SERVER_AUTH)).id() |
966 | | == KeyPurposeId::new(EKU_SERVER_AUTH) |
967 | | ) |
968 | | } |
969 | | |
970 | | #[test] |
971 | | fn test_too_many_signatures() { |
972 | | assert!(matches!( |
973 | | build_and_verify_degenerate_chain(5, ChainTrustAnchor::NotInChain), |
974 | | ControlFlow::Break(Error::MaximumSignatureChecksExceeded) |
975 | | )); |
976 | | } |
977 | | |
978 | | #[test] |
979 | | fn test_too_many_path_calls() { |
980 | | assert!(matches!( |
981 | | dbg!(build_and_verify_degenerate_chain( |
982 | | 10, |
983 | | ChainTrustAnchor::InChain |
984 | | )), |
985 | | ControlFlow::Break(Error::MaximumPathBuildCallsExceeded) |
986 | | )); |
987 | | } |
988 | | |
989 | | #[test] |
990 | | fn longest_allowed_path() { |
991 | | assert!(build_and_verify_linear_chain(1).is_ok()); |
992 | | assert!(build_and_verify_linear_chain(2).is_ok()); |
993 | | assert!(build_and_verify_linear_chain(3).is_ok()); |
994 | | assert!(build_and_verify_linear_chain(4).is_ok()); |
995 | | assert!(build_and_verify_linear_chain(5).is_ok()); |
996 | | assert!(build_and_verify_linear_chain(6).is_ok()); |
997 | | } |
998 | | |
999 | | #[test] |
1000 | | fn path_too_long() { |
1001 | | assert!(matches!( |
1002 | | build_and_verify_linear_chain(7), |
1003 | | Err(ControlFlow::Continue(Error::MaximumPathDepthExceeded)) |
1004 | | )); |
1005 | | } |
1006 | | |
1007 | | #[test] |
1008 | | fn name_constraint_budget() { |
1009 | | // Issue a trust anchor that imposes name constraints. The constraint should match |
1010 | | // the end entity certificate SAN. |
1011 | | let mut ca_cert_params = issuer_params("Constrained Root"); |
1012 | | ca_cert_params.name_constraints = Some(rcgen::NameConstraints { |
1013 | | permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName(".com".into())], |
1014 | | excluded_subtrees: vec![], |
1015 | | }); |
1016 | | let ca_key_pair = KeyPair::generate_for(test_utils::RCGEN_SIGNATURE_ALG).unwrap(); |
1017 | | let ca = CertifiedIssuer::self_signed(ca_cert_params, ca_key_pair).unwrap(); |
1018 | | |
1019 | | // Create a series of intermediate issuers. We'll only use one in the actual built path, |
1020 | | // helping demonstrate that the name constraint budget is not expended checking certificates |
1021 | | // that are not part of the path we compute. |
1022 | | let mut intermediates = Vec::with_capacity(5); |
1023 | | for i in 0..5 { |
1024 | | let intermediate = issuer_params(format!("Intermediate {i}")); |
1025 | | let intermediate_key_pair = |
1026 | | KeyPair::generate_for(test_utils::RCGEN_SIGNATURE_ALG).unwrap(); |
1027 | | // Each intermediate should be issued by the trust anchor. |
1028 | | intermediates.push( |
1029 | | CertifiedIssuer::signed_by(intermediate, intermediate_key_pair, &ca).unwrap(), |
1030 | | ); |
1031 | | } |
1032 | | |
1033 | | // Create an end-entity cert that is issued by the last of the intermediates. |
1034 | | let ee_cert = make_end_entity(intermediates.last().unwrap()); |
1035 | | let ee_cert = EndEntityCert::try_from(ee_cert.cert.der()).unwrap(); |
1036 | | |
1037 | | // We use a custom budget to make it easier to write a test, otherwise it is tricky to |
1038 | | // stuff enough names/constraints into the potential chains while staying within the path |
1039 | | // depth limit and the build chain call limit. |
1040 | | let passing_budget = Budget { |
1041 | | // One comparison against the intermediate's distinguished name. |
1042 | | // One comparison against the EE's distinguished name. |
1043 | | // One comparison against the EE's SAN. |
1044 | | // = 3 total comparisons. |
1045 | | name_constraint_comparisons: 3, |
1046 | | ..Budget::default() |
1047 | | }; |
1048 | | |
1049 | | let anchors = &[anchor_from_trusted_cert(ca.der()).unwrap()]; |
1050 | | let intermediates_der = intermediates |
1051 | | .iter() |
1052 | | .map(|issuer| issuer.der().clone()) |
1053 | | .collect::<Vec<_>>(); |
1054 | | |
1055 | | // Validation should succeed with the name constraint comparison budget allocated above. |
1056 | | // This shows that we're not consuming budget on unused intermediates: we didn't budget |
1057 | | // enough comparisons for that to pass the overall chain building. |
1058 | | let path = verify_chain( |
1059 | | anchors, |
1060 | | &intermediates_der, |
1061 | | &ee_cert, |
1062 | | None, |
1063 | | Some(passing_budget), |
1064 | | ) |
1065 | | .unwrap(); |
1066 | | assert_eq!(path.anchor().subject, anchors.first().unwrap().subject); |
1067 | | |
1068 | | let failing_budget = Budget { |
1069 | | // See passing_budget: 2 comparisons is not sufficient. |
1070 | | name_constraint_comparisons: 2, |
1071 | | ..Budget::default() |
1072 | | }; |
1073 | | // Validation should fail when the budget is smaller than the number of comparisons performed |
1074 | | // on the validated path. This demonstrates we properly fail path building when too many |
1075 | | // name constraint comparisons occur. |
1076 | | let result = verify_chain( |
1077 | | anchors, |
1078 | | &intermediates_der, |
1079 | | &ee_cert, |
1080 | | None, |
1081 | | Some(failing_budget), |
1082 | | ); |
1083 | | |
1084 | | assert!(matches!( |
1085 | | result, |
1086 | | Err(ControlFlow::Break( |
1087 | | Error::MaximumNameConstraintComparisonsExceeded |
1088 | | )) |
1089 | | )); |
1090 | | } |
1091 | | |
1092 | | /// This test builds a PKI like the following diagram depicts. We first verify |
1093 | | /// that we can build a path EE -> B -> A -> TA. Next we supply a custom path verification |
1094 | | /// function that rejects the B->A path, and verify that we build a path EE -> B -> C -> TA. |
1095 | | /// |
1096 | | /// ┌───────────┐ |
1097 | | /// │ │ |
1098 | | /// │ TA │ |
1099 | | /// │ │ |
1100 | | /// └───┬───┬───┘ |
1101 | | /// │ │ |
1102 | | /// │ │ |
1103 | | /// ┌────────┐◄┘ └──►┌────────┐ |
1104 | | /// │ │ │ │ |
1105 | | /// │ A │ │ C │ |
1106 | | /// │ │ │ │ |
1107 | | /// └────┬───┘ └───┬────┘ |
1108 | | /// │ │ |
1109 | | /// │ │ |
1110 | | /// │ ┌─────────┐ │ |
1111 | | /// └──►│ │◄──┘ |
1112 | | /// │ B │ |
1113 | | /// │ │ |
1114 | | /// └────┬────┘ |
1115 | | /// │ |
1116 | | /// │ |
1117 | | /// │ |
1118 | | /// ┌────▼────┐ |
1119 | | /// │ │ |
1120 | | /// │ EE │ |
1121 | | /// │ │ |
1122 | | /// └─────────┘ |
1123 | | #[test] |
1124 | | fn test_reject_candidate_path() { |
1125 | | // Create a trust anchor, and use it to issue two distinct intermediate certificates, each |
1126 | | // with a unique subject and keypair. |
1127 | | let trust_anchor = make_issuer("Trust Anchor"); |
1128 | | let trust_anchor_cert = Cert::from_der(untrusted::Input::from(trust_anchor.der())).unwrap(); |
1129 | | let trust_anchors = &[anchor_from_trusted_cert(trust_anchor.der()).unwrap()]; |
1130 | | |
1131 | | let intermediate_a = make_intermediate("Intermediate A", &trust_anchor); |
1132 | | let intermediate_c = make_intermediate("Intermediate C", &trust_anchor); |
1133 | | |
1134 | | // Next, create an intermediate that is issued by both of the intermediates above. |
1135 | | // Both should share the same subject, and key pair, but will differ in the issuer. |
1136 | | let (intermediate_b, intermediate_b_a_cert, intermediate_b_c_cert) = { |
1137 | | let key = KeyPair::generate_for(test_utils::RCGEN_SIGNATURE_ALG).unwrap(); |
1138 | | let params = issuer_params("Intermediate"); |
1139 | | let intermediate_b_a_cert = params.signed_by(&key, &intermediate_a).unwrap(); |
1140 | | let intermediate_b_c_cert = params.signed_by(&key, &intermediate_c).unwrap(); |
1141 | | let issuer = Issuer::new(params, key); |
1142 | | (issuer, intermediate_b_a_cert, intermediate_b_c_cert) |
1143 | | }; |
1144 | | |
1145 | | let intermediates = &[ |
1146 | | intermediate_a.der().clone(), |
1147 | | intermediate_c.der().clone(), |
1148 | | intermediate_b_a_cert.der().clone(), |
1149 | | intermediate_b_c_cert.der().clone(), |
1150 | | ]; |
1151 | | |
1152 | | // Create an end entity certificate signed by the keypair of the intermediates created above. |
1153 | | let ee = make_end_entity(&intermediate_b); |
1154 | | let ee_cert = &EndEntityCert::try_from(ee.cert.der()).unwrap(); |
1155 | | |
1156 | | // We should be able to create a valid path from EE to trust anchor. |
1157 | | let path = verify_chain(trust_anchors, intermediates, ee_cert, None, None).unwrap(); |
1158 | | let path_intermediates = path.intermediate_certificates().collect::<Vec<_>>(); |
1159 | | |
1160 | | // We expect that without applying any additional constraints, that the path will be |
1161 | | // EE -> intermediate_b_a -> intermediate_a -> trust_anchor. |
1162 | | let intermediate_a_cert = |
1163 | | Cert::from_der(untrusted::Input::from(intermediate_a.der())).unwrap(); |
1164 | | assert_eq!(path_intermediates.len(), 2); |
1165 | | assert_eq!( |
1166 | | path_intermediates[0].issuer(), |
1167 | | intermediate_a_cert.subject() |
1168 | | ); |
1169 | | assert_eq!(path_intermediates[1].issuer(), trust_anchor_cert.subject()); |
1170 | | |
1171 | | // Now, we'll create a function that will reject the intermediate_b_a path. |
1172 | | let expected_chain = |path: &VerifiedPath<'_>| { |
1173 | | for intermediate in path.intermediate_certificates() { |
1174 | | // Reject any intermediates issued by intermediate A. |
1175 | | if intermediate.issuer() == intermediate_a_cert.subject() { |
1176 | | return Err(Error::UnknownIssuer); |
1177 | | } |
1178 | | } |
1179 | | |
1180 | | Ok(()) |
1181 | | }; |
1182 | | |
1183 | | // We should still be able to build a valid path. |
1184 | | let path = verify_chain( |
1185 | | trust_anchors, |
1186 | | intermediates, |
1187 | | ee_cert, |
1188 | | Some(&expected_chain), |
1189 | | None, |
1190 | | ) |
1191 | | .unwrap(); |
1192 | | let path_intermediates = path.intermediate_certificates().collect::<Vec<_>>(); |
1193 | | |
1194 | | // We expect that the path will now be |
1195 | | // EE -> intermediate_b_c -> intermediate_c -> trust_anchor. |
1196 | | let intermediate_c_cert = |
1197 | | Cert::from_der(untrusted::Input::from(intermediate_c.der())).unwrap(); |
1198 | | assert_eq!(path_intermediates.len(), 2); |
1199 | | assert_eq!( |
1200 | | path_intermediates[0].issuer(), |
1201 | | intermediate_c_cert.subject() |
1202 | | ); |
1203 | | assert_eq!(path_intermediates[1].issuer(), trust_anchor_cert.subject()); |
1204 | | } |
1205 | | |
1206 | | fn make_intermediate( |
1207 | | org_name: impl Into<String>, |
1208 | | issuer: &Issuer<'_, impl SigningKey>, |
1209 | | ) -> CertifiedIssuer<'static, KeyPair> { |
1210 | | let params = issuer_params(org_name); |
1211 | | let key = KeyPair::generate_for(test_utils::RCGEN_SIGNATURE_ALG).unwrap(); |
1212 | | CertifiedIssuer::signed_by(params, key, issuer).unwrap() |
1213 | | } |
1214 | | |
1215 | | fn build_and_verify_degenerate_chain( |
1216 | | intermediate_count: usize, |
1217 | | trust_anchor: ChainTrustAnchor, |
1218 | | ) -> ControlFlow<Error, Error> { |
1219 | | let ca = make_issuer("Bogus Subject"); |
1220 | | let mut intermediate_chain = IntermediateChain::new(intermediate_count, true, &ca); |
1221 | | |
1222 | | let verify_trust_anchor = match trust_anchor { |
1223 | | ChainTrustAnchor::InChain => make_issuer("Bogus Trust Anchor"), |
1224 | | ChainTrustAnchor::NotInChain => ca, |
1225 | | }; |
1226 | | |
1227 | | let ee_cert = make_end_entity(&intermediate_chain.last_issuer); |
1228 | | let ee_cert = EndEntityCert::try_from(ee_cert.cert.der()).unwrap(); |
1229 | | let webpki_ta = anchor_from_trusted_cert(verify_trust_anchor.der()).unwrap(); |
1230 | | if matches!(trust_anchor, ChainTrustAnchor::InChain) { |
1231 | | // Note: we clone the trust anchor DER here because we can't move it into the chain |
1232 | | // as it's loaned to webpki_ta above. |
1233 | | intermediate_chain |
1234 | | .chain |
1235 | | .insert(0, verify_trust_anchor.der().to_owned()) |
1236 | | } |
1237 | | |
1238 | | verify_chain( |
1239 | | &[webpki_ta], |
1240 | | &intermediate_chain.chain, |
1241 | | &ee_cert, |
1242 | | None, |
1243 | | None, |
1244 | | ) |
1245 | | .map(|_| ()) |
1246 | | .unwrap_err() |
1247 | | } |
1248 | | |
1249 | | #[cfg(feature = "alloc")] |
1250 | | enum ChainTrustAnchor { |
1251 | | NotInChain, |
1252 | | InChain, |
1253 | | } |
1254 | | |
1255 | | fn build_and_verify_linear_chain(chain_length: usize) -> Result<(), ControlFlow<Error, Error>> { |
1256 | | let ca = make_issuer(format!("Bogus Subject {chain_length}")); |
1257 | | let intermediate_chain = IntermediateChain::new(chain_length, false, &ca); |
1258 | | |
1259 | | let anchor = anchor_from_trusted_cert(ca.der()).unwrap(); |
1260 | | let anchors = slice::from_ref(&anchor); |
1261 | | |
1262 | | let ee_cert = make_end_entity(&intermediate_chain.last_issuer); |
1263 | | let ee_cert = EndEntityCert::try_from(ee_cert.cert.der()).unwrap(); |
1264 | | |
1265 | | let expected_chain = |path: &VerifiedPath<'_>| { |
1266 | | assert_eq!(path.anchor().subject, anchor.subject); |
1267 | | assert!(public_values_eq(path.end_entity().subject, ee_cert.subject)); |
1268 | | assert_eq!(path.intermediate_certificates().count(), chain_length); |
1269 | | |
1270 | | let intermediate_certs = intermediate_chain |
1271 | | .chain |
1272 | | .iter() |
1273 | | .map(|der| Cert::from_der(untrusted::Input::from(der)).unwrap()) |
1274 | | .collect::<Vec<_>>(); |
1275 | | |
1276 | | for (cert, expected) in path |
1277 | | .intermediate_certificates() |
1278 | | .rev() |
1279 | | .zip(intermediate_certs.iter()) |
1280 | | { |
1281 | | assert!(public_values_eq(cert.subject, expected.subject)); |
1282 | | assert_eq!(cert.der(), expected.der()); |
1283 | | } |
1284 | | |
1285 | | for (cert, expected) in path |
1286 | | .intermediate_certificates() |
1287 | | .zip(intermediate_certs.iter().rev()) |
1288 | | { |
1289 | | assert!(public_values_eq(cert.subject, expected.subject)); |
1290 | | assert_eq!(cert.der(), expected.der()); |
1291 | | } |
1292 | | |
1293 | | Ok(()) |
1294 | | }; |
1295 | | |
1296 | | verify_chain( |
1297 | | anchors, |
1298 | | &intermediate_chain.chain, |
1299 | | &ee_cert, |
1300 | | Some(&expected_chain), |
1301 | | None, |
1302 | | ) |
1303 | | .map(|_| ()) |
1304 | | } |
1305 | | |
1306 | | struct IntermediateChain { |
1307 | | last_issuer: Issuer<'static, KeyPair>, |
1308 | | chain: Vec<CertificateDer<'static>>, |
1309 | | } |
1310 | | |
1311 | | impl IntermediateChain { |
1312 | | fn new(chain_length: usize, all_same_subject: bool, ca_cert: &Issuer<'_, KeyPair>) -> Self { |
1313 | | let mut chain = Vec::with_capacity(chain_length); |
1314 | | |
1315 | | let mut prev = None; |
1316 | | for i in 0..chain_length { |
1317 | | let issuer = match &prev { |
1318 | | Some(prev) => prev, |
1319 | | None => ca_cert, |
1320 | | }; |
1321 | | |
1322 | | let intermediate = issuer_params(match all_same_subject { |
1323 | | true => "Bogus Subject".to_string(), |
1324 | | false => format!("Bogus Subject {i}"), |
1325 | | }); |
1326 | | |
1327 | | let key_pair = KeyPair::generate_for(test_utils::RCGEN_SIGNATURE_ALG).unwrap(); |
1328 | | let cert = intermediate.signed_by(&key_pair, issuer).unwrap(); |
1329 | | |
1330 | | chain.push(cert.der().clone()); |
1331 | | prev = Some(Issuer::new(intermediate, key_pair)); |
1332 | | } |
1333 | | |
1334 | | Self { |
1335 | | last_issuer: prev.unwrap(), |
1336 | | chain, |
1337 | | } |
1338 | | } |
1339 | | } |
1340 | | |
1341 | | fn verify_chain<'a>( |
1342 | | trust_anchors: &'a [TrustAnchor<'a>], |
1343 | | intermediate_certs: &'a [CertificateDer<'a>], |
1344 | | ee_cert: &'a EndEntityCert<'a>, |
1345 | | verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, |
1346 | | budget: Option<Budget>, |
1347 | | ) -> Result<VerifiedPath<'a>, ControlFlow<Error, Error>> { |
1348 | | use core::time::Duration; |
1349 | | |
1350 | | let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); |
1351 | | let mut path = PartialPath::new(ee_cert); |
1352 | | let opts = ChainOptions { |
1353 | | eku: KeyUsage::server_auth(), |
1354 | | supported_sig_algs: crate::ALL_VERIFICATION_ALGS, |
1355 | | trust_anchors, |
1356 | | intermediate_certs, |
1357 | | revocation: None, |
1358 | | }; |
1359 | | |
1360 | | match opts.build_chain_inner( |
1361 | | &mut path, |
1362 | | time, |
1363 | | verify_path, |
1364 | | 0, |
1365 | | &mut budget.unwrap_or_default(), |
1366 | | ) { |
1367 | | Ok(anchor) => Ok(VerifiedPath::new(ee_cert, anchor, path)), |
1368 | | Err(err) => Err(err), |
1369 | | } |
1370 | | } |
1371 | | } |