Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
rpki-client: disallow certs with zero serialNumber
To:
tech@openbsd.org
Date:
Thu, 29 Oct 2026 17:03:50 +0100

Download raw body.

Thread
  • Theo Buehler:

    rpki-client: disallow certs with zero serialNumber

Consistency is hard.

RFC 5280 (eid3200 clarifies) and RFC 6487 disallow a zero serialNumber.

For extra fun, CRLNumber and manifestNumber are defined via ASN.1
explicitly allowing zero. So we need some extra dances.

Arguably eid3200 was resolved the wrong way.

Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
diff -u -p -r1.248 cert.c
--- cert.c	14 Oct 2026 06:41:29 -0000	1.248
+++ cert.c	29 Oct 2026 15:49:14 -0000
@@ -1504,7 +1504,7 @@ cert_parse_internal(const char *fn, X509
 		warnx("%s: RFC 6487 4.2: missing serialNumber", fn);
 		goto out;
 	}
-	if (!x509_valid_seqnum(fn, "RFC 6487 4.2: serialNumber", serial))
+	if (!x509_valid_serial(fn, "RFC 6487 4.2: serialNumber", serial))
 		goto out;
 
 	if (!cert_check_sigalg(fn, cert))
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
diff -u -p -r1.317 extern.h
--- extern.h	26 Oct 2026 08:19:00 -0000	1.317
+++ extern.h	29 Oct 2026 15:49:27 -0000
@@ -1011,6 +1011,8 @@ char		*x509_convert_seqnum(const char *,
 		    const ASN1_INTEGER *);
 int		 x509_valid_seqnum(const char *, const char *,
 		    const ASN1_INTEGER *);
+int		 x509_valid_serial(const char *, const char *,
+		    const ASN1_INTEGER *);
 int		 x509_check_tbs_sigalg(const char *, const X509_ALGOR *);
 int		 x509_location(const char *, const char *, GENERAL_NAME *,
 		    char **);
Index: x509.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v
diff -u -p -r1.136 x509.c
--- x509.c	9 Oct 2026 08:34:39 -0000	1.136
+++ x509.c	29 Oct 2026 15:58:22 -0000
@@ -479,11 +479,12 @@ x509_valid_issuer_name(const char *fn, c
 }
 
 /*
- * Check ASN1_INTEGER is non-negative and fits in 20 octets.
+ * Check ASN1_INTEGER is non-negative (or positive) and fits in 20 octets.
  * Returns allocated BIGNUM if true, NULL otherwise.
  */
 static BIGNUM *
-x509_seqnum_to_bn(const char *fn, const char *descr, const ASN1_INTEGER *i)
+x509_seqnum_to_bn(const char *fn, const char *descr, const ASN1_INTEGER *i,
+    int exclude_zero)
 {
 	BIGNUM *bn = NULL;
 
@@ -492,8 +493,9 @@ x509_seqnum_to_bn(const char *fn, const 
 		goto out;
 	}
 
-	if (BN_is_negative(bn)) {
-		warnx("%s: %s should be non-negative", fn, descr);
+	if (BN_is_negative(bn) || (exclude_zero && BN_is_zero(bn))) {
+		warnx("%s: %s should be %s", fn, descr,
+		    exclude_zero ? "positive" : "non-negative");
 		goto out;
 	}
 
@@ -524,7 +526,7 @@ x509_convert_seqnum(const char *fn, cons
 	if (i == NULL)
 		goto out;
 
-	if ((bn = x509_seqnum_to_bn(fn, descr, i)) == NULL)
+	if ((bn = x509_seqnum_to_bn(fn, descr, i, 0)) == NULL)
 		goto out;
 
 	if ((s = BN_bn2hex(bn)) == NULL)
@@ -535,12 +537,32 @@ x509_convert_seqnum(const char *fn, cons
 	return s;
 }
 
+/*
+ * Check seqnum is non-negative and fits into 20 octets:
+ *   RFC 5280: CRLNumber ::= INTEGER (0..MAX)
+ *   RFC 9286: manifestNumber  INTEGER (0..MAX)
+ */
 int
 x509_valid_seqnum(const char *fn, const char *descr, const ASN1_INTEGER *i)
 {
 	BIGNUM *bn;
 
-	if ((bn = x509_seqnum_to_bn(fn, descr, i)) == NULL)
+	if ((bn = x509_seqnum_to_bn(fn, descr, i, 0)) == NULL)
+		return 0;
+
+	BN_free(bn);
+	return 1;
+}
+
+/*
+ * RFC 5280, 4.1.2.2, RFC 6487, 4.2: serialNumber is positive (also eid3200).
+ */
+int
+x509_valid_serial(const char *fn, const char *descr, const ASN1_INTEGER *i)
+{
+	BIGNUM *bn;
+
+	if ((bn = x509_seqnum_to_bn(fn, descr, i, 1)) == NULL)
 		return 0;
 
 	BN_free(bn);