On Mon, Sep 11, 2023 at 01:42:03AM +0000, Job Snijders wrote:
> 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.

I wish there was a better place for this, but due to the unfortunate
parse/validate split, there doesn't seem to be.

> OK?

Some comments below.

> 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.

"ASPA, ROA, and RSC" is a lie, isn't it.

> - * This only parses the RFC 3779 extensions since these are necessary for
> - * validation.

Isn't this still true? You don't really parse the subject name.

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

I would pass in the X509 itself rather than extracting the name in the
caller. This adds

> +{
> +     X509_NAME_ENTRY *ne;
> +     ASN1_OBJECT *ao;
> +     ASN1_STRING *as;
> +     int cn = 0, sn = 0;
> +     int i, nid;

And get the subject name here. Then you only have to do this once and
the caller only grows by three lines and without additional variable.

> +
> +     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 "

s/invalid/unexpected ?

> +                         "(NID: %d)", fn, nid);

Since it's not NID_undef, it should have a short name, so I think this
would be more user friendly:

                            "%s", fn, OBJ_nid2sn(nid));

> +                     return 0;
> +             }
> +     }
> +
> +     if (!cn) {

        if (cn == 0)

> +             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