This adds another compliance check for the X.509 subject name.

Only commonName, and optionally serialNumber, are permitted in the
certificate subject name. See RFC 6487 section 4.4 and 4.5.

It seems the one CA who was not compliant with this requirement got
their act together, so now is an opportune time to get this in.

OK?

Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
retrieving revision 1.114
diff -u -p -r1.114 cert.c
--- cert.c      29 Jun 2023 10:28:25 -0000      1.114
+++ cert.c      9 Jul 2023 13:38:02 -0000
@@ -595,14 +595,13 @@ certificate_policies(struct parse *p, X5
 
 /*
  * Lightweight version of cert_parse_pre() for ASPA, ROA, and RSC EE certs.
- * This only parses the RFC 3779 extensions since these are necessary for
- * validation.
  * Returns cert on success and NULL on failure.
  */
 struct cert *
 cert_parse_ee_cert(const char *fn, X509 *x)
 {
        struct parse             p;
+       X509_NAME               *xn;
        X509_EXTENSION          *ext;
        int                      index;
 
@@ -616,6 +615,13 @@ cert_parse_ee_cert(const char *fn, X509 
                goto out;
        }
 
+       if ((xn = X509_get_subject_name(x)) == NULL) {
+               warnx("%s: X509_get_subject_name", fn);
+               goto out;
+       }
+       if (!x509_valid_subject(fn, xn))
+               goto out;
+
        if (X509_get_key_usage(x) != KU_DIGITAL_SIGNATURE) {
                warnx("%s: RFC 6487 section 4.8.4: KU must be digitalSignature",
                    fn);
@@ -669,6 +675,7 @@ cert_parse_pre(const char *fn, const uns
        const X509_ALGOR        *palg;
        const ASN1_BIT_STRING   *piuid = NULL, *psuid = NULL;
        const ASN1_OBJECT       *cobj;
+       const X509_NAME         *xn;
        ASN1_OBJECT             *obj;
        EVP_PKEY                *pkey;
        struct parse             p;
@@ -726,6 +733,13 @@ cert_parse_pre(const char *fn, const uns
                    fn);
                goto out;
        }
+
+       if ((xn = X509_get_subject_name(x)) == NULL) {
+               warnx("%s: X509_get_subject_name", p.fn);
+               goto out;
+       }
+       if (!x509_valid_subject(p.fn, xn))
+               goto out;
 
        /* Look for X509v3 extensions. */
 
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.188
diff -u -p -r1.188 extern.h
--- extern.h    29 Jun 2023 14:33:35 -0000      1.188
+++ extern.h    9 Jul 2023 13:38:02 -0000
@@ -839,6 +839,7 @@ int          x509_location(const char *, const 
                    GENERAL_NAME *, char **);
 int             x509_inherits(X509 *);
 int             x509_any_inherits(X509 *);
+int             x509_valid_subject(const char *, const X509_NAME *);
 time_t          x509_find_expires(time_t, struct auth *, struct crl_tree *);
 
 /* printers */
Index: x509.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v
retrieving revision 1.73
diff -u -p -r1.73 x509.c
--- x509.c      23 Jun 2023 15:32:15 -0000      1.73
+++ x509.c      9 Jul 2023 13:38:02 -0000
@@ -861,6 +861,80 @@ x509_location(const char *fn, const char
 }
 
 /*
+ * Check that the subject only contains commonName and serialNumber.
+ * Return 0 on failure.
+ */
+int
+x509_valid_subject(const char *fn, const X509_NAME *xn)
+{
+       X509_NAME_ENTRY *ne;
+       ASN1_OBJECT *ao;
+       ASN1_STRING *as;
+       int cn = 0, sn = 0;
+       int i, nid;
+
+       for (i = 0; i < X509_NAME_entry_count(xn); i++) {
+               if ((ne = X509_NAME_get_entry(xn, i)) == NULL) {
+                       warnx("%s: X509_NAME_get_entry", fn);
+                       return 0;
+               }
+               if ((ao = X509_NAME_ENTRY_get_object(ne)) == NULL) {
+                       warnx("%s: X509_NAME_ENTRY_get_object", fn);
+                       return 0;
+               }
+
+               nid = OBJ_obj2nid(ao);
+               switch (nid) {
+               case NID_commonName:
+                       if (cn++ > 0) {
+                               warnx("%s: duplicate commonName in subject",
+                                   fn);
+                               return 0;
+                       }
+                       if ((as = X509_NAME_ENTRY_get_data(ne)) == NULL) {
+                               warnx("%s: X509_NAME_ENTRY_get_data failed",
+                                   fn);
+                               return 0;
+                       }
+/*
+ * The following check can be enabled after AFRINIC re-issues CA certs.
+ * https://lists.afrinic.net/pipermail/dbwg/2023-March/000436.html
+ */
+#if 0
+                       if (as->type != V_ASN1_PRINTABLESTRING) {
+                               warnx("%s: RFC 6487 section 4.5: commonName is"
+                                   " not PrintableString", fn);
+                               return 0;
+                       }
+#endif
+                       break;
+               case NID_serialNumber:
+                       if (sn++ > 0) {
+                               warnx("%s: duplicate serialNumber in subject",
+                                   fn);
+                               return 0;
+                       }
+                       break;
+               case NID_undef:
+                       warnx("%s: OBJ_obj2nid failed", fn);
+                       return 0;
+               default:
+                       warnx("%s: RFC 6487 section 4.5: invalid attribute "
+                           "(NID: %d)", fn, nid);
+                       return 0;
+               }
+       }
+
+       if (!cn) {
+               warnx("%s: RFC 6487 section 4.5: subject missing commonName",
+                   fn);
+               return 0;
+       }
+
+       return 1;
+}
+
+/*
  * Convert an ASN1_INTEGER into a hexstring.
  * Returned string needs to be freed by the caller.
  */

Reply via email to