> > -    * Parse the SAN line.
> > -    * Make sure that all of the domains are represented only once.
> > +    * Ensure the certificate's SAN entries fully cover those passed on
> > +    * the command line and that all domains are represented only once.
> >      */
> 
> The SAN entries do not come from the command line, they come from the
> config file.

Ah yes, it raised an eyebrow but then I forgot.  Changed to

         * Ensure the certificate's SAN entries fully cover those from the
         * configuration file and that all domains are represented only once.

> > +           name_buf = ASN1_STRING_get0_data(name);
> > +           name_len = ASN1_STRING_length(name);
> > +
> > +           for (j = 0; j < altsz; j++) {
> > +                   if ((size_t)name_len != strlen(alts[j]))
> > +                           continue;
> > +                   if (strncmp(name_buf, alts[j], name_len) == 0)
> 
> This one tripped me up. Would it make sense to turn name_buf into a
> proper C-string? Maybe this is normal for people who deal with this all
> the time. I'm sure I will get this wrong...

I added a comment

                /* name_buf is not a string. There could be embedded NULs. */
                name_buf = ASN1_STRING_get0_data(name);

> Anyway, I checked the documentation, and it gave me this:
> 
> RETURN VALUES
>      ASN1_STRING_data() and ASN1_STRING_get0_data() return an internal pointer
>      to the data of x.
> 
> So that's cool. (Yes, yes, it documents in a different place that it's
> not a C-string.)

Yes, I tried to hint at that by calling it _buf. Arguably it's wrong to
call strncmp(). Using memcmp() makes it more explicit that name_buf
is not a string. I'll switch to that.

I really, really want to avoid any additional complications from
allocations as this function is already more than hairy enough.

> I played around with SANs, adding and removing them, and I couldn't
> break it.

Thanks. I had several positive test reports (thanks all), so I think I
didn't mess anything obvious up.

This is the diff I have in my tree now. I'll give jsing and beck a
few days for speaking.

Index: revokeproc.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/revokeproc.c,v
retrieving revision 1.23
diff -u -p -r1.23 revokeproc.c
--- revokeproc.c        15 Dec 2022 17:36:56 -0000      1.23
+++ revokeproc.c        16 Dec 2022 13:28:42 -0000
@@ -61,19 +61,15 @@ int
 revokeproc(int fd, const char *certfile, int force,
     int revocate, const char *const *alts, size_t altsz)
 {
+       GENERAL_NAMES                   *sans = NULL;
        char                            *der = NULL, *dercp, *der64 = NULL;
-       char                            *san = NULL, *str, *tok;
-       int                              rc = 0, cc, i, ssz, len;
+       int                              rc = 0, cc, i, len;
        size_t                          *found = NULL;
-       BIO                             *bio = NULL;
        FILE                            *f = NULL;
        X509                            *x = NULL;
        long                             lval;
        enum revokeop                    op, rop;
        time_t                           t;
-       const STACK_OF(X509_EXTENSION)  *exts;
-       X509_EXTENSION                  *ex;
-       ASN1_OBJECT                     *obj;
        size_t                           j;
 
        /*
@@ -119,6 +115,13 @@ revokeproc(int fd, const char *certfile,
                goto out;
        }
 
+       /* Cache and sanity check X509v3 extensions. */
+
+       if (X509_check_purpose(x, -1, -1) <= 0) {
+               warnx("%s: invalid X509v3 extensions", certfile);
+               goto out;
+       }
+
        /* Read out the expiration date. */
 
        if ((t = X509expires(x)) == -1) {
@@ -126,50 +129,10 @@ revokeproc(int fd, const char *certfile,
                goto out;
        }
 
-       /*
-        * Next, the long process to make sure that the SAN entries
-        * listed with the certificate fully cover those passed on the
-        * command line.
-        */
-
-       exts = X509_get0_extensions(x);
-
-       /* Scan til we find the SAN NID. */
-
-       for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) {
-               ex = sk_X509_EXTENSION_value(exts, i);
-               assert(ex != NULL);
-               obj = X509_EXTENSION_get_object(ex);
-               assert(obj != NULL);
-               if (NID_subject_alt_name != OBJ_obj2nid(obj))
-                       continue;
-
-               if (san != NULL) {
-                       warnx("%s: two SAN entries", certfile);
-                       goto out;
-               }
-
-               bio = BIO_new(BIO_s_mem());
-               if (bio == NULL) {
-                       warnx("BIO_new");
-                       goto out;
-               }
-               if (!X509V3_EXT_print(bio, ex, 0, 0)) {
-                       warnx("X509V3_EXT_print");
-                       goto out;
-               }
-               if ((san = calloc(1, BIO_number_written(bio) + 1)) == NULL) {
-                       warn("calloc");
-                       goto out;
-               }
-               ssz = BIO_read(bio, san, BIO_number_written(bio));
-               if (ssz < 0 || (unsigned)ssz != BIO_number_written(bio)) {
-                       warnx("BIO_read");
-                       goto out;
-               }
-       }
+       /* Extract list of SAN entries from the certificate. */
 
-       if (san == NULL) {
+       sans = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
+       if (sans == NULL) {
                warnx("%s: does not have a SAN entry", certfile);
                if (revocate)
                        goto out;
@@ -184,25 +147,39 @@ revokeproc(int fd, const char *certfile,
        }
 
        /*
-        * Parse the SAN line.
-        * Make sure that all of the domains are represented only once.
+        * Ensure the certificate's SAN entries fully cover those from the
+        * configuration file and that all domains are represented only once.
         */
 
-       str = san;
-       while ((tok = strsep(&str, ",")) != NULL) {
-               if (*tok == '\0')
-                       continue;
-               while (isspace((unsigned char)*tok))
-                       tok++;
-               if (strncmp(tok, "DNS:", 4))
+       for (i = 0; i < sk_GENERAL_NAME_num(sans); i++) {
+               GENERAL_NAME            *gen_name;
+               ASN1_IA5STRING          *name;
+               const unsigned char     *name_buf;
+               int                      name_len;
+               int                      name_type;
+
+               gen_name = sk_GENERAL_NAME_value(sans, i);
+               assert(gen_name != NULL);
+
+               name = GENERAL_NAME_get0_value(gen_name, &name_type);
+               if (name_type != GEN_DNS)
                        continue;
-               tok += 4;
-               for (j = 0; j < altsz; j++)
-                       if (strcmp(tok, alts[j]) == 0)
+
+               /* name_buf is not a string. There could be embedded NULs. */
+               name_buf = ASN1_STRING_get0_data(name);
+               name_len = ASN1_STRING_length(name);
+
+               for (j = 0; j < altsz; j++) {
+                       if ((size_t)name_len != strlen(alts[j]))
+                               continue;
+                       if (memcmp(name_buf, alts[j], name_len) == 0)
                                break;
+               }
                if (j == altsz) {
                        if (revocate) {
-                               warnx("%s: unknown SAN entry: %s", certfile, 
tok);
+                               /* XXX strnvis? */
+                               warnx("%s: unknown SAN entry: %.*s",
+                                   certfile, name_len, name_buf);
                                goto out;
                        }
                        force = 2;
@@ -210,7 +187,9 @@ revokeproc(int fd, const char *certfile,
                }
                if (found[j]++) {
                        if (revocate) {
-                               warnx("%s: duplicate SAN entry: %s", certfile, 
tok);
+                               /* XXX strnvis? */
+                               warnx("%s: duplicate SAN entry: %.*s",
+                                   certfile, name_len, name_buf);
                                goto out;
                        }
                        force = 2;
@@ -310,8 +289,7 @@ out:
        if (f != NULL)
                fclose(f);
        X509_free(x);
-       BIO_free(bio);
-       free(san);
+       GENERAL_NAMES_free(sans);
        free(der);
        free(found);
        free(der64);

Reply via email to