From: Theo Buehler Subject: rpki-client: fix handling of inherited resources in leaves To: tech@openbsd.org Date: Thu, 10 Sep 2026 12:07:52 +0200 It's been publicly known at least since Frank Denis's "security audit" dumps back in May that the RFC 3779 inheritance handling in libcrypto's path validation is broken. Namely, if the certificate to be validated inherits resources, X509v3_asid_validate_path(3) and friends can succeed even if one of the certs on the path does not have the corresponding resource delegation extension at all. RFC 3779's sections 2.3 and 3.3 clearly require that every cert on the entire validating path have the relevant INR delegation extensions. Frank Denis flagged this ASIDs extension, but it is obvious that IP addresses have the same problem. For rpki-client, one of the very few (if not the only) consumers of this code in libcrypto, this doesn't matter all that much because it duplicates much of the work on INR validation. Per the specification, EE certs with inherit elements are only in MFTs, TAKs (of which there are none) and the no longer supported GBRs (of which there is one - malformed). Correct nesting and presence of RFC 3779 extensions is ensured by rpki-client except for the leaf. RFC 9286, section 5.1, says this for manifest EE certs: This EE certificate MUST describe its Internet Number Resources (INRs) using the "inherit" attribute, rather than an explicit description of a resource set (see [RFC3779]). (RPs are required to verify this.) which is the exact wording of RFC 6486 up to the last parenthesis. This does not explicitly say both resource extensions must be present. It could probably have been a bit more explicit that it does not do so and perhaps the following mistake would have been avoided. About 1/3 (~16k) of all manifest EE certs have both RFC 3779 extensions (with inherit element) despite the fact that their issuing certificate lacks one of the two. Blindly fixing this to be strictly RFC 3779 compliant is therefore quite devastating for the RPKI: about 130,000 validated ROA payloads (out of roughly one million) would disappear as well as 500 validated ASPA payloads (out of ~3,000). It turns out that rpki-client's valid_cert() has a similar bug as libcrypto. Given the above, we cannot be strictly RFC 3779 compliant, but we can get close: allow EE certs to inherit even if the parent does not have the required resource extension. There is no real downside to this. Subordinate objects (mostly ASPAs and ROAs will still need to have the subordinate resources covered with explicit extensions). valid_cert() is currently only called for CA and BGPsec certs, but I want to change this soon. Index: validate.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/validate.c,v diff -u -p -r1.84 validate.c --- validate.c 15 Jun 2026 14:30:53 -0000 1.84 +++ validate.c 9 Sep 2026 21:58:57 -0000 @@ -90,8 +90,22 @@ valid_cert(const char *fn, struct auth * uint32_t min, max; for (i = 0; i < cert->num_ases; i++) { - if (cert->ases[i].type == CERT_AS_INHERIT) - continue; + if (cert->ases[i].type == CERT_AS_INHERIT) { + /* + * Many MFTs are issued by a CA without AS resources. + * Accept this non-compliance with RFC 3779, 3.3. + * Apart from MFTs, this affects only TAKs and GBRs. + */ + if (cert->purpose == CERT_PURPOSE_EE) + continue; + + if (a->cert->num_ases > 0) + continue; + + as_warn(fn, "parent without AS resources", + &cert->ases[i]); + return 0; + } if (cert->ases[i].type == CERT_AS_ID) { min = cert->ases[i].id; @@ -109,8 +123,22 @@ valid_cert(const char *fn, struct auth * } for (i = 0; i < cert->num_ips; i++) { - if (cert->ips[i].type == CERT_IP_INHERIT) - continue; + if (cert->ips[i].type == CERT_IP_INHERIT) { + /* + * Many MFTs are issued by a CA without IP resources. + * Accept this non-compliance with RFC 3779, 2.3. + * Apart from MFTs, this affects only TAKs and GBRs. + */ + if (cert->purpose == CERT_PURPOSE_EE) + continue; + + if (a->cert->num_ips > 0) + continue; + + ip_warn(fn, "parent without IP resources", + &cert->ips[i]); + return 0; + } if (valid_ip(a, cert->ips[i].afi, cert->ips[i].min, cert->ips[i].max))