On Thu, Mar 03, 2022 at 11:06:45AM +1000, Alex Wilson wrote:
> On 2/3/22 18:21, Theo Buehler wrote:
> > 
> > At this point it would probably make more sense to use two switches, one
> > for is_nc and one for !is_nc, and perhaps factor them into two
> > helper functions.
> 
> I stared at this code a bit longer and realised we could just call
> x509_constraints_validate() if it was declared in the internal header, so
> I've done that instead. That way our behaviour here when generating new
> certs matches our behaviour exactly when we're processing constraints during
> verification. Let me know what you think.

Cool.

> > A regress would be great. I would probably look into extending
> > regress/lib/libcrypto/certs rather than the CA test, but whichever works
> > best for you. If you want to do something new, it should probably go
> > into regress/lib/libcrypto/x509.
> > 
> 
> This mostly affects generating new certificates, not parsing existing ones
> (since it's in v2i_GENERAL_NAME_ex).
>
> I've modified the CA test to cover generating a bunch of different name
> constraints on the intermediate, at any rate.

Ah yes, I forgot that makecerts.sh isn't run everytime, so indeed,
modifying the CA test makes much more sense.

> In the process of doing this I also discovered that
> x509_constraints_dirname() was requiring an exact match of the DN rather
> than allowing a subtree (as the RFC says to do). It's a very straightforward
> fix, so I've put that in.

Agreed.

> The "@domain" email constraint syntax actually turns out to not be in the
> RFC, it seems to be something someone did later. OpenSSL and MS both support
> it, though, so I've modified x509_constraints_validate() to allow it (it was
> more straightforward than I thought it would be).

Nice. This also makes sense.

Below is your diff with a few minor stylistic tweaks to avoid the churn
in x509_alt.c.

One thing I did not change is the name->name == NULL error check. It
should probably be done inside each part of the if else if cascade.

I am going to land this in a few smaller steps.


There is one issue visible in this diff that pervades the constraints
code: the code assumes in various places that bytes is a C string, while
it actually is some sort of deserialized ASN.1 thing, so it is only
NUL-terminated because someone once thought it would be a good idea to
do that. We should therefore avoid calling strlen and strdup on it or
printing it with %s.  I will send a diff that fixes (some of) this in a
follow-up.

Index: lib/libcrypto/x509/x509_alt.c
===================================================================
RCS file: /cvs/src/lib/libcrypto/x509/x509_alt.c,v
retrieving revision 1.8
diff -u -p -r1.8 x509_alt.c
--- lib/libcrypto/x509/x509_alt.c       11 Feb 2022 17:41:55 -0000      1.8
+++ lib/libcrypto/x509/x509_alt.c       3 Mar 2022 02:45:33 -0000
@@ -652,9 +652,27 @@ v2i_GENERAL_NAME_ex(GENERAL_NAME *out, c
        if (ret == NULL)
                return NULL;
 
-       /* Validate what we have for sanity */
+       /*
+        * Validate what we have for sanity
+        */
+
+       if (is_nc) {
+               struct x509_constraints_name cname;
+               int errc = 0;
+
+               memset(&cname, 0, sizeof(cname));
+               type = x509_constraints_validate(ret, &cname, &errc);
+               if (type == 0 || errc != 0) {
+                       X509V3error(X509V3_R_BAD_OBJECT);
+                       ERR_asprintf_error_data("name=%s", name);
+                       goto err;
+               }
+               x509_constraints_name_clear(&cname);
+               return ret;
+       }
+
        type = x509_constraints_general_to_bytes(ret, &bytes, &len);
-       switch(type) {
+       switch (type) {
        case GEN_DNS:
                if (!x509_constraints_valid_sandns(bytes, len)) {
                        X509V3error(X509V3_R_BAD_OBJECT);
@@ -677,8 +695,7 @@ v2i_GENERAL_NAME_ex(GENERAL_NAME *out, c
                }
                break;
        case GEN_IPADD:
-               if ((!is_nc && len != 4 && len != 16) ||
-                   (is_nc && len != 8 && len != 32)) {
+               if (len != 4 && len != 16) {
                        X509V3error(X509V3_R_BAD_IP_ADDRESS);
                        ERR_asprintf_error_data("name=%s len=%zu", name, len);
                        goto err;
Index: lib/libcrypto/x509/x509_constraints.c
===================================================================
RCS file: /cvs/src/lib/libcrypto/x509/x509_constraints.c,v
retrieving revision 1.20
diff -u -p -r1.20 x509_constraints.c
--- lib/libcrypto/x509/x509_constraints.c       2 Mar 2022 17:53:03 -0000       
1.20
+++ lib/libcrypto/x509/x509_constraints.c       3 Mar 2022 02:45:29 -0000
@@ -636,7 +636,11 @@ int
 x509_constraints_dirname(uint8_t *dirname, size_t dlen,
     uint8_t *constraint, size_t len)
 {
-       if (len != dlen)
+       /*
+        * Allow constraint shorter than name to check (it has to be a prefix
+        * of it in DER format).
+        */
+       if (len > dlen)
                return 0;
        return (memcmp(constraint, dirname, len) == 0);
 }
@@ -921,19 +925,40 @@ x509_constraints_validate(GENERAL_NAME *
                name->type = GEN_DNS;
                break;
        case GEN_EMAIL:
-               if (memchr(bytes, '@', len) != NULL) {
+               if (bytes[0] == '@') {
+                       /*
+                        * Allow email constraints of the form "@domain.com":
+                        * treat them as if they just said "domain.com".
+                        *
+                        * This is not in the RFC, but is allowed by both
+                        * OpenSSL upstream and Microsoft's X.509 bits.
+                        */
+                       if (!x509_constraints_valid_domain_constraint(bytes + 1,
+                           len - 1))
+                               goto err;
+                       name->local = NULL;
+                       name->name = strdup(bytes + 1);
+                       name->type = GEN_EMAIL;
+
+               } else if (memchr(bytes, '@', len) != NULL) {
+                       /* Specific email address (no suffix match) */
                        if (!x509_constraints_parse_mailbox(bytes, len, name))
                                goto err;
+                       name->type = GEN_EMAIL;
+
                } else {
+                       /* Just a domain name. */
                        if (!x509_constraints_valid_domain_constraint(bytes,
                            len))
                                goto err;
-                       if ((name->name = strdup(bytes)) == NULL) {
-                               *error = X509_V_ERR_OUT_OF_MEM;
-                               return 0;
-                       }
+                       name->local = NULL;
+                       name->name = strdup(bytes);
+                       name->type = GEN_EMAIL;
+               }
+               if (name->name == NULL) {
+                       *error = X509_V_ERR_OUT_OF_MEM;
+                       return 0;
                }
-               name->type = GEN_EMAIL;
                break;
        case GEN_IPADD:
                /* Constraints are ip then mask */
@@ -949,7 +974,10 @@ x509_constraints_validate(GENERAL_NAME *
        case GEN_URI:
                if (!x509_constraints_valid_domain_constraint(bytes, len))
                        goto err;
-               name->name = strdup(bytes);
+               if ((name->name = strdup(bytes)) == NULL) {
+                       *error = X509_V_ERR_OUT_OF_MEM;
+                       return 0;
+               }
                name->type = GEN_URI;
                break;
        default:
Index: lib/libcrypto/x509/x509_internal.h
===================================================================
RCS file: /cvs/src/lib/libcrypto/x509/x509_internal.h,v
retrieving revision 1.16
diff -u -p -r1.16 x509_internal.h
--- lib/libcrypto/x509/x509_internal.h  24 Nov 2021 05:38:12 -0000      1.16
+++ lib/libcrypto/x509/x509_internal.h  3 Mar 2022 01:57:32 -0000
@@ -126,6 +126,8 @@ int x509_constraints_extract_names(struc
 int x509_constraints_extract_constraints(X509 *cert,
     struct x509_constraints_names *permitted,
     struct x509_constraints_names *excluded, int *error);
+int x509_constraints_validate(GENERAL_NAME *constraint,
+    struct x509_constraints_name *name, int *error);
 int x509_constraints_check(struct x509_constraints_names *names,
     struct x509_constraints_names *permitted,
     struct x509_constraints_names *excluded, int *error);
Index: regress/lib/libcrypto/CA/Makefile
===================================================================
RCS file: /cvs/src/regress/lib/libcrypto/CA/Makefile,v
retrieving revision 1.3
diff -u -p -r1.3 Makefile
--- regress/lib/libcrypto/CA/Makefile   26 Dec 2020 14:42:09 -0000      1.3
+++ regress/lib/libcrypto/CA/Makefile   3 Mar 2022 01:33:28 -0000
@@ -59,7 +59,7 @@ server.key.pem: stamp-clean
 server.csr.pem: intermediate.cnf server.key.pem
        # server req
        openssl req -batch -config ${.CURDIR}/intermediate.cnf -new -sha256 \
-           -subj '/CN=server/O=OpenBSD/OU=So and Sos/C=CA' \
+           -subj '/CN=server.openbsd.org/OU=So and Sos/O=OpenBSD/C=CA' \
            -key server.key.pem -out server.csr.pem
 
 # Sign server key
@@ -77,7 +77,7 @@ client.key.pem: stamp-clean
 client.csr.pem: intermediate.cnf intermediate.cert.pem client.key.pem
        # client req
        openssl req -batch -config ${.CURDIR}/intermediate.cnf -new -sha256 \
-           -subj '/CN=client/O=OpenBSD/OU=So and Sos/C=CA' \
+           -subj '/CN=client/OU=So and Sos/O=OpenBSD/C=CA' \
            -key client.key.pem -out client.csr.pem
 
 # Sign client key
Index: regress/lib/libcrypto/CA/intermediate.cnf
===================================================================
RCS file: /cvs/src/regress/lib/libcrypto/CA/intermediate.cnf,v
retrieving revision 1.3
diff -u -p -r1.3 intermediate.cnf
--- regress/lib/libcrypto/CA/intermediate.cnf   26 Dec 2020 00:48:56 -0000      
1.3
+++ regress/lib/libcrypto/CA/intermediate.cnf   3 Mar 2022 01:33:28 -0000
@@ -105,6 +105,10 @@ subjectKeyIdentifier = hash
 authorityKeyIdentifier = keyid,issuer
 keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
 extendedKeyUsage = clientAuth, emailProtection
+subjectAltName = critical, @usr_san
+
+[ usr_san ]
+email.0 = [email protected]
 
 [ server_cert ]
 # Extensions for server certificates (`man x509v3_config`).
Index: regress/lib/libcrypto/CA/root.cnf
===================================================================
RCS file: /cvs/src/regress/lib/libcrypto/CA/root.cnf,v
retrieving revision 1.3
diff -u -p -r1.3 root.cnf
--- regress/lib/libcrypto/CA/root.cnf   26 Dec 2020 00:48:56 -0000      1.3
+++ regress/lib/libcrypto/CA/root.cnf   3 Mar 2022 01:33:28 -0000
@@ -95,6 +95,22 @@ subjectKeyIdentifier = hash
 authorityKeyIdentifier = keyid:always,issuer
 basicConstraints = critical, CA:true, pathlen:0
 keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+nameConstraints = critical, @ca_name_constraints
+
+[ ca_name_constraints ]
+permitted;DNS.0 = .openbsd.org
+permitted;DNS.1 = client
+permitted;email.0 = openbsd.org
+permitted;email.1 = @test.openbsd.org
+permitted;URI.0 = .openbsd.org
+permitted;dirName.0 = openbsd_dn
+permitted;otherName.0 = 1.3.6.1.4.1.311.20.2.3;UTF8:@openbsd.org
+excluded;IP.0 = 0.0.0.0/0.0.0.0
+excluded;IP.1 = 0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0
+
+[ openbsd_dn ]
+C = CA
+O = OpenBSD
 
 [ usr_cert ]
 # Extensions for client certificates (`man x509v3_config`).

Reply via email to