Add nameConstraints commonName checking.
authorDr. Stephen Henson <steve@openssl.org>
Sun, 3 Jul 2016 20:41:57 +0000 (21:41 +0100)
committerDr. Stephen Henson <steve@openssl.org>
Mon, 11 Jul 2016 22:30:04 +0000 (23:30 +0100)
New hostname checking function asn1_valid_host()

Check commonName entries against nameConstraints: any CN components in
EE certificate which look like hostnames are checked against
nameConstraints.

Note that RFC5280 et al only require checking subject alt name against
DNS name constraints.

Reviewed-by: Richard Levitte <levitte@openssl.org>
crypto/asn1/a_strex.c
crypto/asn1/charmap.h
crypto/asn1/charmap.pl
crypto/include/internal/asn1_int.h
crypto/x509/x509_vfy.c
crypto/x509v3/v3_ncons.c
include/openssl/x509v3.h

index d419e9d..eb55c6b 100644 (file)
@@ -10,6 +10,7 @@
 #include <stdio.h>
 #include <string.h>
 #include "internal/cryptlib.h"
+#include "internal/asn1_int.h"
 #include <openssl/crypto.h>
 #include <openssl/x509.h>
 #include <openssl/asn1.h>
@@ -592,3 +593,53 @@ int ASN1_STRING_to_UTF8(unsigned char **out, ASN1_STRING *in)
     *out = stmp.data;
     return stmp.length;
 }
+
+/* Return 1 if host is a valid hostname and 0 otherwise */
+int asn1_valid_host(const ASN1_STRING *host)
+{
+    int hostlen = host->length;
+    const unsigned char *hostptr = host->data;
+    int type = host->type;
+    int i;
+    char width = -1;
+    unsigned short chflags = 0, prevchflags;
+
+    if (type > 0 && type < 31)
+        width = tag2nbyte[type];
+    if (width == -1 || hostlen == 0)
+        return 0;
+    /* Treat UTF8String as width 1 as any MSB set is invalid */
+    if (width == 0)
+        width = 1;
+    for (i = 0 ; i < hostlen; i+= width) {
+        prevchflags = chflags;
+        /* Value must be <= 0x7F: check upper bytes are all zeroes */
+        if (width == 4) {
+            if (*hostptr++ != 0 || *hostptr++ != 0 || *hostptr++ != 0)
+                return 0;
+        } else if (width == 2) {
+            if (*hostptr++ != 0)
+                return 0;
+        }
+        if (*hostptr > 0x7f)
+            return 0;
+        chflags = char_type[*hostptr++];
+        if (!(chflags & (CHARTYPE_HOST_ANY | CHARTYPE_HOST_WILD))) {
+            /* Nothing else allowed at start or end of string */
+            if (i == 0 || i == hostlen - 1)
+                return 0;
+            /* Otherwise invalid if not dot or hyphen */
+            if (!(chflags & (CHARTYPE_HOST_DOT | CHARTYPE_HOST_HYPHEN)))
+                return 0;
+            /*
+             * If previous is dot or hyphen then illegal unless both
+             * are hyphens: as .- -. .. are all illegal
+             */
+            if (prevchflags & (CHARTYPE_HOST_DOT | CHARTYPE_HOST_HYPHEN)
+                && ((prevchflags & CHARTYPE_HOST_DOT)
+                    || (chflags & CHARTYPE_HOST_DOT)))
+                return 0;
+        }
+    }
+    return 1;
+}
index 6e42f86..2a75925 100644 (file)
  * https://www.openssl.org/source/license.html
  */
 
+#define CHARTYPE_HOST_ANY 4096
+#define CHARTYPE_HOST_DOT 8192
+#define CHARTYPE_HOST_HYPHEN 16384
+#define CHARTYPE_HOST_WILD 32768
+
 /*
  * Mask of various character properties
  */
 
 static const unsigned short char_type[] = {
-    1026,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
-     2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
-    120,  0,  1, 40,  0,  0,  0, 16, 1040, 1040, 1024, 25, 25, 16, 16, 16,
-    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,  9,  9, 16,  9, 16,
-     0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
-    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,  0, 1025,  0,  0,  0,
-     0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
-    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,  0,  0,  0,  0,  2
+    1026,    2,    2,    2,    2,    2,    2,    2,    2,    2,    2,    2,
+       2,    2,    2,    2,    2,    2,    2,    2,    2,    2,    2,    2,
+       2,    2,    2,    2,    2,    2,    2,    2,  120,    0,    1,   40,
+       0,    0,    0,   16, 1040, 1040, 33792,   25,   25, 16400, 8208,   16,
+    4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112,   16,    9,
+       9,   16,    9,   16,    0, 4112, 4112, 4112, 4112, 4112, 4112, 4112,
+    4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112,
+    4112, 4112, 4112, 4112, 4112, 4112, 4112,    0, 1025,    0,    0,    0,
+       0, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112,
+    4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112, 4112,
+    4112, 4112, 4112,    0,    0,    0,    0,    2
 };
index a3511da..26ca325 100644 (file)
@@ -22,6 +22,10 @@ my $PSTRING_CHAR = 0x10;     # Valid PrintableString character
 my $RFC2253_FIRST_ESC = 0x20; # Escaped with \ if first character
 my $RFC2253_LAST_ESC = 0x40;  # Escaped with \ if last character
 my $RFC2254_ESC = 0x400;       # Character escaped \XX
+my $HOST_ANY = 0x1000;      # Valid hostname character anywhere in label
+my $HOST_DOT = 0x2000;  # Dot: hostname label separator
+my $HOST_HYPHEN = 0x4000; # Hyphen: not valid at start or end.
+my $HOST_WILD = 0x8000; # Wildcard character
 
 for($i = 0; $i < 128; $i++) {
        # Set the RFC2253 escape characters (control)
@@ -34,7 +38,7 @@ for($i = 0; $i < 128; $i++) {
        if(                ( ( $i >= ord("a")) && ( $i <= ord("z")) )
                        || (  ( $i >= ord("A")) && ( $i <= ord("Z")) )
                        || (  ( $i >= ord("0")) && ( $i <= ord("9")) )  ) {
-               $arr[$i] |= $PSTRING_CHAR;
+               $arr[$i] |= $PSTRING_CHAR | $HOST_ANY;
        }
 }
 
@@ -58,7 +62,7 @@ $arr[ord(";")] |= $NOESC_QUOTE | $RFC2253_ESC;
 $arr[0] |= $RFC2254_ESC;
 $arr[ord("(")] |= $RFC2254_ESC;
 $arr[ord(")")] |= $RFC2254_ESC;
-$arr[ord("*")] |= $RFC2254_ESC;
+$arr[ord("*")] |= $RFC2254_ESC | $HOST_WILD;
 $arr[ord("\\")] |= $RFC2254_ESC;
 
 # Remaining PrintableString characters
@@ -69,8 +73,8 @@ $arr[ord("(")] |= $PSTRING_CHAR;
 $arr[ord(")")] |= $PSTRING_CHAR;
 $arr[ord("+")] |= $PSTRING_CHAR;
 $arr[ord(",")] |= $PSTRING_CHAR;
-$arr[ord("-")] |= $PSTRING_CHAR;
-$arr[ord(".")] |= $PSTRING_CHAR;
+$arr[ord("-")] |= $PSTRING_CHAR | $HOST_HYPHEN;
+$arr[ord(".")] |= $PSTRING_CHAR | $HOST_DOT;
 $arr[ord("/")] |= $PSTRING_CHAR;
 $arr[ord(":")] |= $PSTRING_CHAR;
 $arr[ord("=")] |= $PSTRING_CHAR;
@@ -91,6 +95,11 @@ print <<EOF;
  * https://www.openssl.org/source/license.html
  */
 
+#define CHARTYPE_HOST_ANY $HOST_ANY
+#define CHARTYPE_HOST_DOT $HOST_DOT
+#define CHARTYPE_HOST_HYPHEN $HOST_HYPHEN
+#define CHARTYPE_HOST_WILD $HOST_WILD
+
 /*
  * Mask of various character properties
  */
@@ -100,8 +109,8 @@ EOF
 
 print "   ";
 for($i = 0; $i < 128; $i++) {
-       print("\n   ") if($i && (($i % 16) == 0));
-       printf(" %2d", $arr[$i]);
+       print("\n   ") if($i && (($i % 12) == 0));
+       printf(" %4d", $arr[$i]);
        print(",") if ($i != 127);
 }
 print("\n};\n");
index 1bd1fab..aad047e 100644 (file)
@@ -89,3 +89,5 @@ struct asn1_pctx_st {
     unsigned long oid_flags;
     unsigned long str_flags;
 } /* ASN1_PCTX */ ;
+
+int asn1_valid_host(const ASN1_STRING *host);
index c8ebc50..469a0a8 100644 (file)
@@ -651,6 +651,10 @@ static int check_name_constraints(X509_STORE_CTX *ctx)
             if (nc) {
                 int rv = NAME_CONSTRAINTS_check(x, nc);
 
+                /* If EE certificate check commonName too */
+                if (rv == X509_V_OK && i == 0)
+                    rv = NAME_CONSTRAINTS_check_CN(x, nc);
+
                 switch (rv) {
                 case X509_V_OK:
                     break;
index 413d9e9..fe3a907 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <stdio.h>
 #include "internal/cryptlib.h"
+#include "internal/asn1_int.h"
 #include <openssl/asn1t.h>
 #include <openssl/conf.h>
 #include <openssl/x509v3.h>
@@ -226,6 +227,51 @@ int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc)
 
 }
 
+int NAME_CONSTRAINTS_check_CN(X509 *x, NAME_CONSTRAINTS *nc)
+{
+    int r, i;
+    X509_NAME *nm;
+
+    ASN1_STRING stmp;
+    GENERAL_NAME gntmp;
+    stmp.flags = 0;
+    stmp.type = V_ASN1_IA5STRING;
+    gntmp.type = GEN_DNS;
+    gntmp.d.dNSName = &stmp;
+
+    nm = X509_get_subject_name(x);
+
+    /* Process any commonName attributes in subject name */
+
+    for (i = -1;;) {
+        X509_NAME_ENTRY *ne;
+        ASN1_STRING *hn;
+        i = X509_NAME_get_index_by_NID(nm, NID_commonName, i);
+        if (i == -1)
+            break;
+        ne = X509_NAME_get_entry(nm, i);
+        hn = X509_NAME_ENTRY_get_data(ne);
+        /* Only process attributes that look like host names */
+        if (asn1_valid_host(hn)) {
+            unsigned char *h;
+            int hlen = ASN1_STRING_to_UTF8(&h, hn);
+            if (hlen <= 0)
+                return X509_V_ERR_OUT_OF_MEM;
+
+            stmp.length = hlen;
+            stmp.data = h;
+
+            r = nc_match(&gntmp, nc);
+
+            OPENSSL_free(h);
+
+            if (r != X509_V_OK)
+                    return r;
+        }
+    }
+    return X509_V_OK;
+}
+
 static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc)
 {
     GENERAL_SUBTREE *sub;
index 14e25d7..e6053c5 100644 (file)
@@ -524,6 +524,7 @@ DECLARE_ASN1_FUNCTIONS(ISSUING_DIST_POINT)
 int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn, X509_NAME *iname);
 
 int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc);
+int NAME_CONSTRAINTS_check_CN(X509 *x, NAME_CONSTRAINTS *nc);
 
 DECLARE_ASN1_FUNCTIONS(ACCESS_DESCRIPTION)
 DECLARE_ASN1_FUNCTIONS(AUTHORITY_INFO_ACCESS)