@@ -33,9 +33,35 @@ pub enum ValidationError {
3333 CandidatesExhausted ( Box < ValidationError > ) ,
3434 Malformed ( asn1:: ParseError ) ,
3535 DuplicateExtension ( DuplicateExtensionsError ) ,
36+ FatalError ( & ' static str ) ,
3637 Other ( String ) ,
3738}
3839
40+ struct Budget {
41+ name_constraint_checks : usize ,
42+ }
43+
44+ impl Budget {
45+ // Same limit as other validators
46+ const DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT : usize = 1 << 20 ;
47+
48+ fn new ( ) -> Budget {
49+ Budget {
50+ name_constraint_checks : Self :: DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT ,
51+ }
52+ }
53+
54+ fn name_constraint_check ( & mut self ) -> Result < ( ) , ValidationError > {
55+ self . name_constraint_checks =
56+ self . name_constraint_checks
57+ . checked_sub ( 1 )
58+ . ok_or ( ValidationError :: FatalError (
59+ "Exceeded maximum name constraint check limit" ,
60+ ) ) ?;
61+ Ok ( ( ) )
62+ }
63+ }
64+
3965impl From < asn1:: ParseError > for ValidationError {
4066 fn from ( value : asn1:: ParseError ) -> Self {
4167 Self :: Malformed ( value)
@@ -76,7 +102,10 @@ impl<'a, 'chain> NameChain<'a, 'chain> {
76102 & self ,
77103 constraint : & GeneralName < ' chain > ,
78104 san : & GeneralName < ' chain > ,
105+ budget : & mut Budget ,
79106 ) -> Result < ApplyNameConstraintStatus , ValidationError > {
107+ budget. name_constraint_check ( ) ?;
108+
80109 match ( constraint, san) {
81110 ( GeneralName :: DNSName ( pattern) , GeneralName :: DNSName ( name) ) => {
82111 match ( DNSConstraint :: new ( pattern. 0 ) , DNSName :: new ( name. 0 ) ) {
@@ -114,17 +143,18 @@ impl<'a, 'chain> NameChain<'a, 'chain> {
114143 fn evaluate_constraints (
115144 & self ,
116145 constraints : & NameConstraints < ' chain > ,
146+ budget : & mut Budget ,
117147 ) -> Result < ( ) , ValidationError > {
118148 if let Some ( child) = self . child {
119- child. evaluate_constraints ( constraints) ?;
149+ child. evaluate_constraints ( constraints, budget ) ?;
120150 }
121151
122152 for san in self . sans . clone ( ) {
123153 // If there are no applicable constraints, the SAN is considered valid so the default is true.
124154 let mut permit = true ;
125155 if let Some ( permitted_subtrees) = & constraints. permitted_subtrees {
126156 for p in permitted_subtrees. unwrap_read ( ) . clone ( ) {
127- let status = self . evaluate_single_constraint ( & p. base , & san) ?;
157+ let status = self . evaluate_single_constraint ( & p. base , & san, budget ) ?;
128158 if status. is_applied ( ) {
129159 permit = status. is_match ( ) ;
130160 if permit {
@@ -142,7 +172,7 @@ impl<'a, 'chain> NameChain<'a, 'chain> {
142172
143173 if let Some ( excluded_subtrees) = & constraints. excluded_subtrees {
144174 for e in excluded_subtrees. unwrap_read ( ) . clone ( ) {
145- let status = self . evaluate_single_constraint ( & e. base , & san) ?;
175+ let status = self . evaluate_single_constraint ( & e. base , & san, budget ) ?;
146176 if status. is_match ( ) {
147177 return Err ( ValidationError :: Other (
148178 "excluded name constraint matched SAN" . into ( ) ,
@@ -166,7 +196,8 @@ pub fn verify<'chain, B: CryptoOps>(
166196) -> Result < Chain < ' chain , B > , ValidationError > {
167197 let builder = ChainBuilder :: new ( intermediates. into_iter ( ) . collect ( ) , policy, store) ;
168198
169- builder. build_chain ( leaf)
199+ let mut budget = Budget :: new ( ) ;
200+ builder. build_chain ( leaf, & mut budget)
170201}
171202
172203struct ChainBuilder < ' a , ' chain , B : CryptoOps > {
@@ -227,9 +258,10 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
227258 current_depth : u8 ,
228259 working_cert_extensions : & Extensions < ' chain > ,
229260 name_chain : NameChain < ' _ , ' chain > ,
261+ budget : & mut Budget ,
230262 ) -> Result < Chain < ' chain , B > , ValidationError > {
231263 if let Some ( nc) = working_cert_extensions. get_extension ( & NAME_CONSTRAINTS_OID ) {
232- name_chain. evaluate_constraints ( & nc. value ( ) ?) ?;
264+ name_chain. evaluate_constraints ( & nc. value ( ) ?, budget ) ?;
233265 }
234266
235267 // Look in the store's root set to see if the working cert is listed.
@@ -295,11 +327,14 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
295327 // candidate (which is a non-leaf by definition) isn't self-issued.
296328 cert_is_self_issued ( issuing_cert_candidate. certificate ( ) ) ,
297329 ) ?,
330+ budget,
298331 ) {
299332 Ok ( mut chain) => {
300333 chain. push ( working_cert. clone ( ) ) ;
301334 return Ok ( chain) ;
302335 }
336+ // Immediately return on fatal error.
337+ Err ( e @ ValidationError :: FatalError ( ..) ) => return Err ( e) ,
303338 Err ( e) => last_err = Some ( e) ,
304339 } ;
305340 }
@@ -326,6 +361,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
326361 fn build_chain (
327362 & self ,
328363 leaf : & VerificationCertificate < ' chain , B > ,
364+ budget : & mut Budget ,
329365 ) -> Result < Chain < ' chain , B > , ValidationError > {
330366 // Before anything else, check whether the given leaf cert
331367 // is well-formed according to our policy (and its underlying
@@ -342,6 +378,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
342378 0 ,
343379 & leaf_extensions,
344380 NameChain :: new ( None , & leaf_extensions, false ) ?,
381+ budget,
345382 ) ?;
346383 // We build the chain in reverse order, fix it now.
347384 chain. reverse ( ) ;
0 commit comments