Download raw body.
rpki-client: check required presence/absence of extensions
cert_parse_extensions() keeps track of all the extensions expected in a
resource certificate. It is the natural place to check completeness
against RFC 6487, section 4.8 and RFC 8209, section 3.1.3. Some of this
is currently in cert_parse_pre(). Some of it in cert_parse(), ta_parse(),
or sprinkled throughout the signed object handlers.
As mentioned in the comment, I deliberately kept the checks here
simple-minded and systematic. If an extension is present, the extension
handler is assumed to have checked that the extension is valid and the
required info is present, so we don't much more than presence/absence
except a few things where more than one extension is involved (ski vs
aki, presence of INRs).
Some of this wasn't checked at all (e.g., presence of a CRLDP for most
EE certs) or we left that to the X.509 validator (e.g., presence of the
certificate policy).
The last check on INRs in BGPsec router certs can be extended a bit. For
example, we can add checks there that TA and BGPsec router certs don't
inherit and perhaps add some other things like checking we have the
BRK. That's for later.
This essentially completes the reorganization and unification of the
cert parsing proper. We still have a few loose ends like issuer/subjects
having incomplete checks, but first I want to simplify a few things and
then finally remove some of the duplication (the bulk of the x509_* API
can soon be deleted).
Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
diff -u -p -r1.190 cert.c
--- cert.c 10 Jul 2025 19:22:48 -0000 1.190
+++ cert.c 11 Jul 2025 05:35:06 -0000
@@ -1583,7 +1583,156 @@ cert_parse_extensions(const char *fn, st
}
}
- /* XXX - validate required fields. */
+ /*
+ * Check specifics on presence and absence of extensions depending
+ * on the certificate purpose. Some of the checks are redundant with
+ * cert_check_purpose(), some of the checks are also impossible to
+ * hit with the checks in the extension parsers, but it is easier to
+ * check for completeness against RFC 6487 and RFC 8209 if we're not
+ * trying to be smart here.
+ */
+
+ if (bc == 0) {
+ if (cert->purpose == CERT_PURPOSE_TA ||
+ cert->purpose == CERT_PURPOSE_CA) {
+ warnx("%s: RFC 6487, 4.8.1: CA cert without "
+ "basic constraints", fn);
+ goto out;
+ }
+ } else {
+ if (cert->purpose != CERT_PURPOSE_TA &&
+ cert->purpose != CERT_PURPOSE_CA) {
+ /* This also covers RFC 8209, 3.1.3.1. */
+ warnx("%s: RFC 6487, 4.8.1: non-CA cert with "
+ "basic constraints", fn);
+ goto out;
+ }
+ }
+
+ if (ski == 0) {
+ warnx("%s: RFC 6487, 4.8.2: cert without SKI", fn);
+ goto out;
+ }
+
+ if (aki == 0) {
+ if (cert->purpose != CERT_PURPOSE_TA) {
+ warnx("%s: RFC 6487, 4.8.3: non-TA cert without "
+ "AKI", fn);
+ goto out;
+ }
+ } else {
+ if (cert->purpose == CERT_PURPOSE_TA) {
+ if (strcmp(cert->ski, cert->aki) != 0) {
+ warnx("%s: RFC 6487, 4.8.3: TA cert with "
+ "mismatch between AKI and SKI", fn);
+ goto out;
+ }
+ } else {
+ if (strcmp(cert->ski, cert->aki) == 0) {
+ warnx("%s: RFC 6487, 4.8.3: non-TA cert "
+ "must not have matching AKI and SKI", fn);
+ goto out;
+ }
+ }
+ }
+
+ if (ku == 0) {
+ warnx("%s: RFC 6487, 4.8.4: cert without key usage", fn);
+ goto out;
+ }
+
+ if (eku == 0) {
+ if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+ warnx("%s: RFC 8209, 3.1.3.2: BGPsec Router cert "
+ "without extended key usage", fn);
+ goto out;
+ }
+ } else {
+ if (cert->purpose != CERT_PURPOSE_BGPSEC_ROUTER) {
+ warnx("%s: RFC 6487, 4.8.5: non-BGPsec cert with "
+ "extended key usage", fn);
+ goto out;
+ }
+ }
+
+ if (crldp == 0) {
+ if (cert->purpose != CERT_PURPOSE_TA) {
+ warnx("%s: RFC 6487, 4.8.6: non-TA cert without "
+ "CRL Distribution Point", fn);
+ goto out;
+ }
+ } else {
+ if (cert->purpose == CERT_PURPOSE_TA) {
+ warnx("%s: RFC 6487, 4.8.6: TA cert with "
+ "CRL Distribution Point", fn);
+ goto out;
+ }
+ }
+
+ if (aia == 0) {
+ if (cert->purpose != CERT_PURPOSE_TA) {
+ warnx("%s: RFC 6487, 4.8.7: non-TA cert without "
+ "AIA", fn);
+ goto out;
+ }
+ } else {
+ if (cert->purpose == CERT_PURPOSE_TA) {
+ warnx("%s: RFC 6487, 4.8.7: TA cert with AIA", fn);
+ goto out;
+ }
+ }
+
+ if (sia == 0) {
+ /*
+ * Allow two special snowflakes to omit the SIA in EE certs
+ * even though this extension is mandated by RFC 6487, 4.8.8.2.
+ * RFC 9323, 2 clarifies: it is because RSCs are not distributed
+ * through the RPKI repository system. Same goes for Geofeed.
+ * RFC 9092 had an EE cert sporting an rpkiNotify SIA (!).
+ * RFC 9632 fixed this and pleads the Fifth on SIAs...
+ */
+ if (filemode && cert->purpose == CERT_PURPOSE_EE) {
+ if (rtype_from_file_extension(fn) != RTYPE_GEOFEED &&
+ rtype_from_file_extension(fn) != RTYPE_RSC) {
+ warnx("%s: RFC 6487, 4.8.8: cert without SIA",
+ fn);
+ goto out;
+ }
+ } else if (cert->purpose != CERT_PURPOSE_BGPSEC_ROUTER) {
+ warnx("%s: RFC 6487, 4.8.8: cert without SIA", fn);
+ goto out;
+ }
+ } else {
+ if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+ warnx("%s: RFC 8209, 3.1.3.3: BGPsec Router cert "
+ "with SIA", fn);
+ goto out;
+ }
+ }
+
+ if (cp == 0) {
+ warnx("%s: RFC 6487, 4.8.9: missing certificate policy", fn);
+ goto out;
+ }
+
+ if (ip == 0 && as == 0) {
+ warnx("%s: RFC 6487, 4.8.10 and 4.8.11: cert without "
+ "IP or AS resources", fn);
+ goto out;
+ }
+
+ if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+ if (ip != 0) {
+ warnx("%s: RFC 8209, 3.1.3.4: BGPsec Router cert "
+ "with IP resources", fn);
+ goto out;
+ }
+ if (as == 0) {
+ warnx("%s: RFC 8209, 3.1.3.5: BGPsec Router cert "
+ "without AS resources", fn);
+ goto out;
+ }
+ }
return 1;
rpki-client: check required presence/absence of extensions