> > - * 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);