Don't use strcasecmp and strncasecmp for IA5 strings
authorMatt Caswell <matt@openssl.org>
Fri, 20 Oct 2017 16:11:03 +0000 (17:11 +0100)
committerMatt Caswell <matt@openssl.org>
Thu, 26 Oct 2017 13:43:57 +0000 (14:43 +0100)
The functions strcasecmp() and strncasecmp() will use locale specific rules
when performing comparison. This could cause some problems in certain
locales. For example in the Turkish locale an 'I' character is not the
uppercase version of 'i'. However IA5 strings should not use locale specific
rules, i.e. for an IA5 string 'I' is uppercase 'i' even if using the
Turkish locale.

This fixes a bug in name constraints checking reported by Thomas Pornin
(NCCGroup).

This is not considered a security issue because it would require both a
Turkish locale (or other locale with similar issues) and malfeasance by
a trusted name-constrained CA for a certificate to pass name constraints
in error. The constraints also have to be for excluded sub-trees which are
extremely rare. Failure to match permitted subtrees is a bug, not a
vulnerability.

Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/4569)

crypto/x509v3/v3_ncons.c

index 561128e3faddeb8bef86fd90a8e5e135e4373db5..1f1996b68ef0aeb771c745977c5adfa62895ed21 100644 (file)
@@ -7,9 +7,8 @@
  * https://www.openssl.org/source/license.html
  */
 
  * https://www.openssl.org/source/license.html
  */
 
-#include "e_os.h"               /* for strncasecmp */
 #include "internal/cryptlib.h"
 #include "internal/cryptlib.h"
-#include <limits.h>
+#include "internal/numbers.h"
 #include <stdio.h>
 #include "internal/asn1_int.h"
 #include <openssl/asn1t.h>
 #include <stdio.h>
 #include "internal/asn1_int.h"
 #include <openssl/asn1t.h>
@@ -64,6 +63,46 @@ ASN1_SEQUENCE(NAME_CONSTRAINTS) = {
 IMPLEMENT_ASN1_ALLOC_FUNCTIONS(GENERAL_SUBTREE)
 IMPLEMENT_ASN1_ALLOC_FUNCTIONS(NAME_CONSTRAINTS)
 
 IMPLEMENT_ASN1_ALLOC_FUNCTIONS(GENERAL_SUBTREE)
 IMPLEMENT_ASN1_ALLOC_FUNCTIONS(NAME_CONSTRAINTS)
 
+/*
+ * We cannot use strncasecmp here because that applies locale specific rules.
+ * For example in Turkish 'I' is not the uppercase character for 'i'. We need to
+ * do a simple ASCII case comparison ignoring the locale (that is why we use
+ * numeric constants below).
+ */
+static int ia5ncasecmp(const char *s1, const char *s2, size_t n)
+{
+    for (; n > 0; n--, s1++, s2++) {
+        if (*s1 != *s2) {
+            unsigned char c1 = (unsigned char)*s1, c2 = (unsigned char)*s2;
+
+            /* Convert to lower case */
+            if (c1 >= 0x41 /* A */ && c1 <= 0x5A /* Z */)
+                c1 += 0x20;
+            if (c2 >= 0x41 /* A */ && c2 <= 0x5A /* Z */)
+                c2 += 0x20;
+
+            if (c1 == c2)
+                continue;
+
+            if (c1 < c2)
+                return -1;
+
+            /* c1 > c2 */
+            return 1;
+        } else if (*s1 == 0) {
+            /* If we get here we know that *s2 == 0 too */
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+static int ia5casecmp(const char *s1, const char *s2)
+{
+    return ia5ncasecmp(s1, s2, SIZE_MAX);
+}
+
 static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method,
                                   X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval)
 {
 static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method,
                                   X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval)
 {
@@ -418,7 +457,7 @@ static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base)
             return X509_V_ERR_PERMITTED_VIOLATION;
     }
 
             return X509_V_ERR_PERMITTED_VIOLATION;
     }
 
-    if (strcasecmp(baseptr, dnsptr))
+    if (ia5casecmp(baseptr, dnsptr))
         return X509_V_ERR_PERMITTED_VIOLATION;
 
     return X509_V_OK;
         return X509_V_ERR_PERMITTED_VIOLATION;
 
     return X509_V_OK;
@@ -438,7 +477,7 @@ static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
     if (!baseat && (*baseptr == '.')) {
         if (eml->length > base->length) {
             emlptr += eml->length - base->length;
     if (!baseat && (*baseptr == '.')) {
         if (eml->length > base->length) {
             emlptr += eml->length - base->length;
-            if (strcasecmp(baseptr, emlptr) == 0)
+            if (ia5casecmp(baseptr, emlptr) == 0)
                 return X509_V_OK;
         }
         return X509_V_ERR_PERMITTED_VIOLATION;
                 return X509_V_OK;
         }
         return X509_V_ERR_PERMITTED_VIOLATION;
@@ -459,7 +498,7 @@ static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
     }
     emlptr = emlat + 1;
     /* Just have hostname left to match: case insensitive */
     }
     emlptr = emlat + 1;
     /* Just have hostname left to match: case insensitive */
-    if (strcasecmp(baseptr, emlptr))
+    if (ia5casecmp(baseptr, emlptr))
         return X509_V_ERR_PERMITTED_VIOLATION;
 
     return X509_V_OK;
         return X509_V_ERR_PERMITTED_VIOLATION;
 
     return X509_V_OK;
@@ -498,14 +537,14 @@ static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base)
     if (*baseptr == '.') {
         if (hostlen > base->length) {
             p = hostptr + hostlen - base->length;
     if (*baseptr == '.') {
         if (hostlen > base->length) {
             p = hostptr + hostlen - base->length;
-            if (strncasecmp(p, baseptr, base->length) == 0)
+            if (ia5ncasecmp(p, baseptr, base->length) == 0)
                 return X509_V_OK;
         }
         return X509_V_ERR_PERMITTED_VIOLATION;
     }
 
     if ((base->length != (int)hostlen)
                 return X509_V_OK;
         }
         return X509_V_ERR_PERMITTED_VIOLATION;
     }
 
     if ((base->length != (int)hostlen)
-        || strncasecmp(hostptr, baseptr, hostlen))
+        || ia5ncasecmp(hostptr, baseptr, hostlen))
         return X509_V_ERR_PERMITTED_VIOLATION;
 
     return X509_V_OK;
         return X509_V_ERR_PERMITTED_VIOLATION;
 
     return X509_V_OK;