RFC 8398: Name constraints validation
authorDmitry Belyavskiy <beldmit@gmail.com>
Wed, 21 Aug 2019 15:33:14 +0000 (18:33 +0300)
committerDmitry Belyavskiy <beldmit@gmail.com>
Wed, 26 Aug 2020 11:04:17 +0000 (14:04 +0300)
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/9654)

crypto/x509/v3_ncons.c

index 4543ec2e11000d55df28a0262a89752792eb74bf..8da9cca24dba301c55da21fca1e93d3034a7163c 100644 (file)
@@ -17,6 +17,7 @@
 #include <openssl/bn.h>
 
 #include "crypto/x509.h"
+#include "crypto/punycode.h"
 #include "ext_dat.h"
 
 DEFINE_STACK_OF(CONF_VALUE)
@@ -38,6 +39,7 @@ static int nc_match_single(GENERAL_NAME *sub, GENERAL_NAME *gen);
 static int nc_dn(const X509_NAME *sub, const X509_NAME *nm);
 static int nc_dns(ASN1_IA5STRING *sub, ASN1_IA5STRING *dns);
 static int nc_email(ASN1_IA5STRING *sub, ASN1_IA5STRING *eml);
+static int nc_email_eai(ASN1_UTF8STRING *sub, ASN1_IA5STRING *eml);
 static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base);
 static int nc_ip(ASN1_OCTET_STRING *ip, ASN1_OCTET_STRING *base);
 
@@ -459,6 +461,14 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc)
 {
     GENERAL_SUBTREE *sub;
     int i, r, match = 0;
+    /*
+     * We need to compare not gen->type field but an "effective" type because
+     * the otherName field may contain EAI email address treated specially
+     * according to RFC 8398, section 6
+     */
+    int effective_type = ((gen->type == GEN_OTHERNAME) &&
+                          (OBJ_obj2nid(gen->d.otherName->type_id) ==
+                           NID_id_on_SmtpUTF8Mailbox)) ? GEN_EMAIL : gen->type;
 
     /*
      * Permitted subtrees: if any subtrees exist of matching the type at
@@ -467,7 +477,7 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc)
 
     for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) {
         sub = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i);
-        if (gen->type != sub->base->type)
+        if (effective_type != sub->base->type)
             continue;
         if (!nc_minmax_valid(sub))
             return X509_V_ERR_SUBTREE_MINMAX;
@@ -490,7 +500,7 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc)
 
     for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); i++) {
         sub = sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i);
-        if (gen->type != sub->base->type)
+        if (effective_type != sub->base->type)
             continue;
         if (!nc_minmax_valid(sub))
             return X509_V_ERR_SUBTREE_MINMAX;
@@ -509,7 +519,14 @@ static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc)
 
 static int nc_match_single(GENERAL_NAME *gen, GENERAL_NAME *base)
 {
-    switch (base->type) {
+    switch (gen->type) {
+    case GEN_OTHERNAME:
+        /*
+         * We are here only when we have SmtpUTF8 name,
+         * so we match the value of othername with base->d.rfc822Name
+         */
+        return nc_email_eai(gen->d.otherName->value->value.utf8string,
+                            base->d.rfc822Name);
     case GEN_DIRNAME:
         return nc_dn(gen->d.directoryName, base->d.directoryName);
 
@@ -577,13 +594,59 @@ static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base)
 
 }
 
+/*
+ * This function implements comparison between ASCII/U-label in eml
+ * and A-label in base according to RFC 8398, section 6.
+ * Convert base to U-label and ASCII-parts of domain names, for base
+ * Octet-to-octet comparison of `eml` and `base` hostname parts
+ * (ASCII-parts should be compared in case-insensitive manner)
+ */
+static int nc_email_eai(ASN1_UTF8STRING *eml, ASN1_IA5STRING *base)
+{
+    const char *baseptr = (char *)base->data;
+    const char *emlptr = (char *)eml->data;
+    const char *emlat = strrchr(emlptr, '@');
+
+    char ulabel[256];
+    size_t size = sizeof(ulabel) - 1;
+
+    if (emlat == NULL)
+        return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
+
+    memset(ulabel, 0, sizeof(ulabel));
+    /* Special case: initial '.' is RHS match */
+    if (*baseptr == '.') {
+        ulabel[0] = '.';
+        size -= 1;
+        if (ossl_a2ulabel(baseptr, ulabel + 1, &size) <= 0)
+            return X509_V_ERR_UNSPECIFIED;
+
+        if ((size_t)eml->length > size + 1) {
+            emlptr += eml->length - (size + 1);
+            if (ia5casecmp(ulabel, emlptr) == 0)
+                return X509_V_OK;
+        }
+        return X509_V_ERR_PERMITTED_VIOLATION;
+    }
+
+    emlptr = emlat + 1;
+    if (ossl_a2ulabel(baseptr, ulabel, &size) <= 0)
+        return X509_V_ERR_UNSPECIFIED;
+    /* Just have hostname left to match: case insensitive */
+    if (ia5casecmp(ulabel, emlptr))
+        return X509_V_ERR_PERMITTED_VIOLATION;
+
+    return X509_V_OK;
+
+}
+
 static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
 {
     const char *baseptr = (char *)base->data;
     const char *emlptr = (char *)eml->data;
 
-    const char *baseat = strchr(baseptr, '@');
-    const char *emlat = strchr(emlptr, '@');
+    const char *baseat = strrchr(baseptr, '@');
+    const char *emlat = strrchr(emlptr, '@');
     if (!emlat)
         return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
     /* Special case: initial '.' is RHS match */