Download raw body.
rpki-client: more complete signature algorithm checks
So far we relied on the X.509 verifier to check that the two
signature algorithm identifiers in a certificate are identical.
Let's do it ourselves since this is very cheap (in well-formed
certificates this is a few NULL checks plus comparing nid and
type).
An algorithm identifier contains an OID and optional parameters.
We've ignored the parameters so far, so let's check them. There is
some fun ASN.1 history here so there are two possible encodings
we need to accept for RSA, which is annoying. In fact, roughly
1600 ROAs issued by ARIN before end of 2020 have an EE cert with
incorrect encoding.
The bulk of this code can be reused for CRLs, I'll factor this
into a helper and do that as a follow-up.
Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
diff -u -p -r1.184 cert.c
--- cert.c 5 Jul 2025 15:05:12 -0000 1.184
+++ cert.c 7 Jul 2025 04:40:36 -0000
@@ -1104,24 +1104,60 @@ certificate_policies(const char *fn, str
static int
cert_check_sigalg(const char *fn, const struct cert *cert)
{
+ const X509 *x = cert->x509;
+ const X509_ALGOR *alg = NULL, *tbsalg;
+ const ASN1_OBJECT *aobj = NULL;
+ int ptype = 0;
int nid;
- /*
- * XXX - This is the NID of the AlgorithmIdentifier of the Certificate.
- * Consider using X509_get0_tbs_sigalg() instead to inspect the one from
- * the TBSCertificate. We currently rely on X509_verify() (also called
- * from X509_verify_cert()) to ascertain that the two are identical
- * (RFC 5280, 4.1.1.2 and 4.1.2.3) - this check isn't documented.
- * Also, we ignore the parameters. Can/should we check these as well?
- */
- if ((nid = X509_get_signature_nid(cert->x509)) == NID_undef) {
+ /* Retrieve AlgorithmIdentifiers from Certificate and TBSCertificate. */
+ X509_get0_signature(NULL, &alg, x);
+ if (alg == NULL || (tbsalg = X509_get0_tbs_sigalg(x)) == NULL) {
+ warnx("%s: missing signatureAlgorithm in certificate", fn);
+ return 0;
+ }
+
+ /* This cheap comparison is an undocumented part of X509_verify(). */
+ if (X509_ALGOR_cmp(alg, tbsalg) != 0) {
+ warnx("%s: RFC 5280, 4.1.1.2: signatureAlgorithm and signature "
+ "AlgorithmIdentifier mismatch", fn);
+ return 0;
+ }
+
+ X509_ALGOR_get0(&aobj, &ptype, NULL, tbsalg);
+ if ((nid = OBJ_obj2nid(aobj)) == NID_undef) {
warnx("%s: unknown signature type", fn);
return 0;
}
- if (nid == NID_sha256WithRSAEncryption)
+ if (nid == NID_sha256WithRSAEncryption) {
+ /*
+ * Correct encoding of parameters is explicit ASN.1 NULL
+ * (V_ASN1_NULL), but implementations MUST accept absent
+ * parameters due to an ASN.1 syntax translation mishap,
+ * see, e.g., RFC 4055, 2.1.
+ */
+ if (ptype != V_ASN1_NULL && ptype != V_ASN1_UNDEF) {
+ warnx("%s: RFC 4055, 5: wrong ASN.1 parameters for %s",
+ fn, LN_sha256WithRSAEncryption);
+ return 0;
+ }
+ /*
+ * In July 2025, there were ~1600 EE certs in ROAs with this
+ * faulty encoding, all issued by ARIN before September 2020.
+ */
+ if (verbose > 1 && ptype == V_ASN1_UNDEF)
+ warnx("%s: RFC 4055, 5: %s without ASN.1 parameters",
+ fn, LN_sha256WithRSAEncryption);
return 1;
+ }
+
if (experimental && nid == NID_ecdsa_with_SHA256) {
+ if (ptype != V_ASN1_UNDEF) {
+ warnx("%s: RFC 5758, 3.2: %s encoding MUST omit "
+ "the parameters", fn, SN_ecdsa_with_SHA256);
+ return 0;
+ }
if (verbose)
warnx("%s: P-256 support is experimental", fn);
return 1;
rpki-client: more complete signature algorithm checks