@@ -138,19 +138,6 @@ impl<'a> DNSPattern<'a> {
138138 } ,
139139 }
140140 }
141-
142- /// Returns the inner `DNSName` within this `DNSPattern`, e.g.
143- /// `foo.com` for `*.foo.com` or `example.com` for `example.com`.
144- ///
145- /// This API must not be used to bypass pattern matching; it exists
146- /// solely to enable checks that only require the inner name, such
147- /// as Name Constraint checks.
148- pub fn inner_name ( & self ) -> & DNSName < ' a > {
149- match self {
150- DNSPattern :: Exact ( dnsname) => dnsname,
151- DNSPattern :: Wildcard ( dnsname) => dnsname,
152- }
153- }
154141}
155142
156143/// A `DNSConstraint` represents a DNS name constraint as defined in [RFC 5280 4.2.1.10].
@@ -163,35 +150,45 @@ impl<'a> DNSConstraint<'a> {
163150 DNSName :: new ( pattern) . map ( Self )
164151 }
165152
166- /// Returns true if this `DNSConstraint` matches the given name .
153+ /// Returns true if this `DNSConstraint` matches the given `DNSPattern` .
167154 ///
168155 /// Constraint matching is defined by RFC 5280: any DNS name that can
169156 /// be constructed by simply adding zero or more labels to the left-hand
170157 /// side of the name satisfies the name constraint.
171158 ///
172- /// ```rust
173- /// # use cryptography_x509_verification::types::{DNSConstraint, DNSName};
174- /// let example_com = DNSName::new("example.com").unwrap();
175- /// let badexample_com = DNSName::new("badexample.com").unwrap();
176- /// let foo_example_com = DNSName::new("foo.example.com").unwrap();
177- /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&example_com));
178- /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&foo_example_com));
179- /// assert!(!DNSConstraint::new(example_com.as_str()).unwrap().matches(&badexample_com));
180- /// ```
181- pub fn matches ( & self , name : & DNSName < ' _ > ) -> bool {
182- // NOTE: This may seem like an obtuse way to perform label matching,
183- // but it saves us a few allocations: doing a substring check instead
184- // would require us to clone each string and do case normalization.
185- // Note also that we check the length in advance: Rust's zip
186- // implementation terminates with the shorter iterator, so we need
187- // to first check that the candidate name is at least as long as
188- // the constraint it's matching against.
189- name. as_str ( ) . len ( ) >= self . 0 . as_str ( ) . len ( )
190- && self
191- . 0
192- . rlabels ( )
193- . zip ( name. rlabels ( ) )
194- . all ( |( a, o) | a. eq_ignore_ascii_case ( o) )
159+ /// On top of what RFC 5280 specifies, we define behavior for wildcard
160+ /// patterns (which are not covered by RFC 5280): a wildcard pattern
161+ /// matches a constraint if the pattern matches the constraint's inner name,
162+ /// _or_ if the pattern's inner name matches the constraint.
163+ /// This allows us to reject DNS names like `*.example.com` when
164+ /// the constraint is `example.com` or `bar.example.com`.
165+ pub fn matches ( & self , name : & DNSPattern < ' _ > ) -> bool {
166+ match name {
167+ DNSPattern :: Exact ( name) => {
168+ // NOTE: This may seem like an obtuse way to perform label matching,
169+ // but it saves us a few allocations: doing a substring check instead
170+ // would require us to clone each string and do case normalization.
171+ // Note also that we check the length in advance: Rust's zip
172+ // implementation terminates with the shorter iterator, so we need
173+ // to first check that the candidate name is at least as long as
174+ // the constraint it's matching against.
175+ name. as_str ( ) . len ( ) >= self . 0 . as_str ( ) . len ( )
176+ && self
177+ . 0
178+ . rlabels ( )
179+ . zip ( name. rlabels ( ) )
180+ . all ( |( a, o) | a. eq_ignore_ascii_case ( o) )
181+ }
182+ DNSPattern :: Wildcard ( inner) => {
183+ // NOTE: This check is not as simple as a single pattern match,
184+ // since we need two subtly distinct cases here:
185+ // 1. Constraint `bar.example.com` on `*.example.com`
186+ // 2. Constraint `example.com` on `*.example.com`
187+ // The first cases is handled by `DNSPattern::matches`, and the second is handled
188+ // by `DNSConstraint::matches`.
189+ name. matches ( & self . 0 ) || self . matches ( & DNSPattern :: Exact ( inner. clone ( ) ) )
190+ }
191+ }
195192 }
196193}
197194
@@ -597,14 +594,33 @@ mod tests {
597594 let example_com = DNSConstraint :: new ( "example.com" ) . unwrap ( ) ;
598595
599596 // Exact domain and arbitrary subdomains match.
600- assert ! ( example_com. matches( & DNSName :: new( "example.com" ) . unwrap( ) ) ) ;
601- assert ! ( example_com. matches( & DNSName :: new( "foo.example.com" ) . unwrap( ) ) ) ;
602- assert ! ( example_com. matches( & DNSName :: new( "foo.bar.baz.quux.example.com" ) . unwrap( ) ) ) ;
597+ assert ! ( example_com. matches( & DNSPattern :: new( "example.com" ) . unwrap( ) ) ) ;
598+ assert ! ( example_com. matches( & DNSPattern :: new( "foo.example.com" ) . unwrap( ) ) ) ;
599+ assert ! ( example_com. matches( & DNSPattern :: new( "foo.bar.baz.quux.example.com" ) . unwrap( ) ) ) ;
603600
604601 // Parent domains, distinct domains, and substring domains do not match.
605- assert ! ( !example_com. matches( & DNSName :: new( "com" ) . unwrap( ) ) ) ;
606- assert ! ( !example_com. matches( & DNSName :: new( "badexample.com" ) . unwrap( ) ) ) ;
607- assert ! ( !example_com. matches( & DNSName :: new( "wrong.com" ) . unwrap( ) ) ) ;
602+ assert ! ( !example_com. matches( & DNSPattern :: new( "com" ) . unwrap( ) ) ) ;
603+ assert ! ( !example_com. matches( & DNSPattern :: new( "badexample.com" ) . unwrap( ) ) ) ;
604+ assert ! ( !example_com. matches( & DNSPattern :: new( "wrong.com" ) . unwrap( ) ) ) ;
605+ }
606+
607+ #[ test]
608+ fn test_dnsconstraint_matches_wildcard ( ) {
609+ let com = DNSConstraint :: new ( "com" ) . unwrap ( ) ;
610+ let example_com = DNSConstraint :: new ( "example.com" ) . unwrap ( ) ;
611+ let bar_example_com = DNSConstraint :: new ( "bar.example.com" ) . unwrap ( ) ;
612+ let baz_bar_example_com = DNSConstraint :: new ( "baz.bar.example.com" ) . unwrap ( ) ;
613+ let any_example_com = DNSPattern :: new ( "*.example.com" ) . unwrap ( ) ;
614+
615+ assert ! ( com. matches( & any_example_com) ) ;
616+ assert ! ( example_com. matches( & any_example_com) ) ;
617+ assert ! ( bar_example_com. matches( & any_example_com) ) ;
618+
619+ // A constraint on `baz.bar.example.com` doesn't match `*.example.com`,
620+ // since `baz.bar.example.com` matches zero or more sublabels of
621+ // `baz.bar.example.com` while `*.example.com` matches exactly one
622+ // sublabel of `example.com`.
623+ assert ! ( !baz_bar_example_com. matches( & any_example_com) ) ;
608624 }
609625
610626 #[ test]
0 commit comments