@@ -28,9 +28,11 @@ static int do_i2r_name_constraints(const X509V3_EXT_METHOD *method,
2828static int print_nc_ipadd (BIO * bp , const ASN1_OCTET_STRING * ip );
2929
3030static int nc_match (GENERAL_NAME * gen , NAME_CONSTRAINTS * nc );
31- static int nc_match_single (GENERAL_NAME * sub , GENERAL_NAME * gen );
31+ static int nc_match_single (GENERAL_NAME * sub , GENERAL_NAME * gen ,
32+ int excluding );
3233static int nc_dn (X509_NAME * sub , X509_NAME * nm );
33- static int nc_dns (const ASN1_IA5STRING * sub , const ASN1_IA5STRING * dns );
34+ static int nc_dns (const ASN1_IA5STRING * sub , const ASN1_IA5STRING * dns ,
35+ int excluding );
3436static int nc_email (const ASN1_IA5STRING * sub , const ASN1_IA5STRING * eml );
3537static int nc_uri (const ASN1_IA5STRING * uri , const ASN1_IA5STRING * base );
3638static int nc_ip (const ASN1_OCTET_STRING * ip , const ASN1_OCTET_STRING * base );
@@ -295,24 +297,46 @@ int cn2dnsid(ASN1_STRING *cn, unsigned char **dnsid, size_t *idlen) {
295297 }
296298
297299 int isdnsname = 0 ;
300+ int has_non_dns_char = 0 ;
301+
302+ // Per RFC 6125 Section 6.4.3, a wildcard DNS-ID uses a leading "*." covering
303+ // the first label. Skip past it for validation, but return the full value so
304+ // |nc_dns| can match it against name constraints.
305+ int check_start = 0 ;
306+ if (utf8_length > 2 && utf8_value [0 ] == '*' && utf8_value [1 ] == '.' ) {
307+ check_start = 2 ;
308+ }
298309
299- // XXX: Deviation from strict DNS name syntax, also check names with '_'
300- // Check DNS name syntax, any '-' or '.' must be internal,
301- // and on either side of each '.' we can't have a '-' or '.' .
310+ // Check DNS name syntax. Any '-' or '.' must be internal, and on either side
311+ // of each '.' we can't have a '-' or '.'. Names with '_' are also accepted
312+ // as a deviation from strict DNS syntax .
302313 //
303- // If the name has just one label, we don't consider it a DNS name. This
314+ // If the name has just one label, we don't consider it a DNS name. This
304315 // means that "CN=sometld" cannot be precluded by DNS name constraints, but
305- // that is not a problem.
306- for (int i = 0 ; i < utf8_length ; ++ i ) {
316+ // that is not a problem. Single-label CNs may contain non-ASCII characters
317+ // (e.g. "CN=Ünternehmen") and are silently skipped.
318+ //
319+ // Multi-label CNs that resemble DNS names must be ASCII-only. Per RFC 6125
320+ // Section 6.4.2, internationalized domain names should appear in A-label
321+ // (punycode) form. A multi-label CN containing non-ASCII bytes or control
322+ // characters is rejected with |X509_V_ERR_UNSUPPORTED_NAME_SYNTAX| to
323+ // prevent it from bypassing name constraints while still being accepted by
324+ // hostname verification.
325+ for (int i = check_start ; i < utf8_length ; ++ i ) {
307326 const unsigned char c = utf8_value [i ];
308327
309328 if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) ||
310329 (c >= '0' && c <= '9' ) || c == '_' ) {
311330 continue ;
312331 }
313332
333+ if (c >= 0x80 || c <= 0x20 || c == 0x7F ) {
334+ has_non_dns_char = 1 ;
335+ continue ;
336+ }
337+
314338 // Dot and hyphen cannot be first or last.
315- if (i > 0 && i < utf8_length - 1 ) {
339+ if (i > check_start && i < utf8_length - 1 ) {
316340 if (c == '-' ) {
317341 continue ;
318342 }
@@ -330,6 +354,13 @@ int cn2dnsid(ASN1_STRING *cn, unsigned char **dnsid, size_t *idlen) {
330354 break ;
331355 }
332356
357+ if (isdnsname && has_non_dns_char ) {
358+ // Multi-label CN with non-ASCII bytes or control characters. This
359+ // resembles a DNS name but contains characters not permitted in DNS.
360+ OPENSSL_free (utf8_value );
361+ return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX ;
362+ }
363+
333364 if (isdnsname ) {
334365 * dnsid = utf8_value ;
335366 * idlen = (size_t )utf8_length ;
@@ -402,7 +433,7 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc) {
402433 if (match == 0 ) {
403434 match = 1 ;
404435 }
405- r = nc_match_single (gen , sub -> base );
436+ r = nc_match_single (gen , sub -> base , /*excluding=*/ 0 );
406437 if (r == X509_V_OK ) {
407438 match = 2 ;
408439 } else if (r != X509_V_ERR_PERMITTED_VIOLATION ) {
@@ -425,7 +456,7 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc) {
425456 return X509_V_ERR_SUBTREE_MINMAX ;
426457 }
427458
428- r = nc_match_single (gen , sub -> base );
459+ r = nc_match_single (gen , sub -> base , /*excluding=*/ 1 );
429460 if (r == X509_V_OK ) {
430461 return X509_V_ERR_EXCLUDED_VIOLATION ;
431462 } else if (r != X509_V_ERR_PERMITTED_VIOLATION ) {
@@ -436,13 +467,14 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc) {
436467 return X509_V_OK ;
437468}
438469
439- static int nc_match_single (GENERAL_NAME * gen , GENERAL_NAME * base ) {
470+ static int nc_match_single (GENERAL_NAME * gen , GENERAL_NAME * base ,
471+ int excluding ) {
440472 switch (base -> type ) {
441473 case GEN_DIRNAME :
442474 return nc_dn (gen -> d .directoryName , base -> d .directoryName );
443475
444476 case GEN_DNS :
445- return nc_dns (gen -> d .dNSName , base -> d .dNSName );
477+ return nc_dns (gen -> d .dNSName , base -> d .dNSName , excluding );
446478
447479 case GEN_EMAIL :
448480 return nc_email (gen -> d .rfc822Name , base -> d .rfc822Name );
@@ -484,6 +516,15 @@ static int starts_with(const CBS *cbs, uint8_t c) {
484516 return CBS_len (cbs ) > 0 && CBS_data (cbs )[0 ] == c ;
485517}
486518
519+ static int starts_with_str (const CBS * cbs , const char * str , size_t str_len ) {
520+ return CBS_len (cbs ) >= str_len &&
521+ !OPENSSL_memcmp (CBS_data (cbs ), str , str_len );
522+ }
523+
524+ static int ends_with_byte (const CBS * cbs , uint8_t c ) {
525+ return CBS_len (cbs ) > 0 && CBS_data (cbs )[CBS_len (cbs ) - 1 ] == c ;
526+ }
527+
487528static int equal_case (const CBS * a , const CBS * b ) {
488529 if (CBS_len (a ) != CBS_len (b )) {
489530 return 0 ;
@@ -508,7 +549,8 @@ static int has_suffix_case(const CBS *a, const CBS *b) {
508549 return equal_case (& copy , b );
509550}
510551
511- static int nc_dns (const ASN1_IA5STRING * dns , const ASN1_IA5STRING * base ) {
552+ static int nc_dns (const ASN1_IA5STRING * dns , const ASN1_IA5STRING * base ,
553+ int excluding ) {
512554 CBS dns_cbs , base_cbs ;
513555 CBS_init (& dns_cbs , dns -> data , dns -> length );
514556 CBS_init (& base_cbs , base -> data , base -> length );
@@ -518,6 +560,34 @@ static int nc_dns(const ASN1_IA5STRING *dns, const ASN1_IA5STRING *base) {
518560 return X509_V_OK ;
519561 }
520562
563+ // Normalize absolute DNS names by removing the trailing dot, if any.
564+ if (ends_with_byte (& dns_cbs , '.' )) {
565+ uint8_t unused ;
566+ CBS_get_last_u8 (& dns_cbs , & unused );
567+ }
568+ if (ends_with_byte (& base_cbs , '.' )) {
569+ uint8_t unused ;
570+ CBS_get_last_u8 (& base_cbs , & unused );
571+ }
572+
573+ // Wildcard partial-match handling ("*.bar.com" matching name constraint
574+ // "foo.bar.com"). This only handles the case where the dnsname and the
575+ // constraint match after removing the leftmost label, otherwise it is handled
576+ // by falling through to the check of whether the dnsname is fully within or
577+ // fully outside of the constraint.
578+ if (excluding && starts_with_str (& dns_cbs , "*." , 2 )) {
579+ CBS unused ;
580+ CBS base_parent_cbs = base_cbs ;
581+ CBS dns_parent_cbs = dns_cbs ;
582+ CBS_skip (& dns_parent_cbs , 2 );
583+ if (CBS_get_until_first (& base_parent_cbs , & unused , '.' ) &&
584+ CBS_skip (& base_parent_cbs , 1 )) {
585+ if (equal_case (& dns_parent_cbs , & base_parent_cbs )) {
586+ return X509_V_OK ;
587+ }
588+ }
589+ }
590+
521591 // If |base_cbs| begins with a '.', do a simple suffix comparison. This is
522592 // not part of RFC5280, but is part of OpenSSL's original behavior.
523593 if (starts_with (& base_cbs , '.' )) {
0 commit comments