Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
rpki-client: openssl 4 compat
To:
tech@openbsd.org
Date:
Mon, 13 Apr 2026 12:11:38 +0200

Download raw body.

Thread
This adds two compat implementations for the OpenSSL 4 API that is
required to deal with opaque strings. This API will eventually land in
libcrypto but I did not manage to do that in this cycle for various
reasons.

Once LibreSSL provides this, the file will move to portable, but for
now this way is simplest to ensure that rpki-client as expected with all
libcrypto versions we care about.

I did not guard the prototypes in extern.h because there's no need.

The openssl bit in regress isn't for commit. I did it this way to keep
the diff small if anyone wants to test that. You'll need to build and
install the security/openssl/4.0 and security/openssl/libretls4 ports.

Portable should just work after linking asn1_bit_string.c to the build,
but OpenSSL 4 support needs a little logic to set HAVE_ASN1_BIT_STRING_* 

Index: usr.sbin/rpki-client/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v
diff -u -p -r1.39 Makefile
--- usr.sbin/rpki-client/Makefile	13 Jan 2026 21:36:17 -0000	1.39
+++ usr.sbin/rpki-client/Makefile	13 Apr 2026 09:46:02 -0000
@@ -3,6 +3,7 @@
 PROG=	rpki-client
 
 SRCS+=	as.c
+SRCS+=	asn1_bit_string.c
 SRCS+=	aspa.c
 SRCS+=	ccr.c
 SRCS+=	cert.c
Index: usr.sbin/rpki-client/asn1_bit_string.c
===================================================================
RCS file: usr.sbin/rpki-client/asn1_bit_string.c
diff -N usr.sbin/rpki-client/asn1_bit_string.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ usr.sbin/rpki-client/asn1_bit_string.c	13 Apr 2026 09:46:02 -0000
@@ -0,0 +1,96 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2025 Theo Buehler <tb@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <openssl/asn1.h>
+
+#include "extern.h"
+
+/*
+ * My implementation of the OpenSSL 4 API that beck upstreamed in:
+ * https://github.com/openssl/openssl/pull/29926
+ * https://github.com/openssl/openssl/pull/29387
+ */
+
+#ifndef HAVE_ASN1_BIT_STRING_GET_LENGTH
+int
+ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *abs, size_t *out_length,
+    int *out_unused_bits)
+{
+	size_t length;
+	int unused_bits;
+
+	if (abs == NULL || abs->type != V_ASN1_BIT_STRING)
+		return 0;
+
+	if (out_length == NULL || out_unused_bits == NULL)
+		return 0;
+
+	length = abs->length;
+	unused_bits = 0;
+
+	if ((abs->flags & ASN1_STRING_FLAG_BITS_LEFT) != 0)
+		unused_bits = abs->flags & 0x07;
+
+	if (length == 0 && unused_bits != 0)
+		return 0;
+
+	if (unused_bits != 0) {
+		unsigned char mask = (1 << unused_bits) - 1;
+		if ((abs->data[length - 1] & mask) != 0)
+			return 0;
+	}
+
+	*out_length = length;
+	*out_unused_bits = unused_bits;
+
+	return 1;
+}
+#endif /* !HAVE_ASN1_BIT_STRING_GET_LENGTH */
+
+#ifndef HAVE_ASN1_BIT_STRING_SET1
+int
+ASN1_BIT_STRING_set1(ASN1_BIT_STRING *abs, const uint8_t *data, size_t length,
+    int unused_bits)
+{
+	/* OpenSSL likes such checks. */
+	if (abs == NULL)
+		return 0;
+
+	if (length > INT_MAX || unused_bits < 0 || unused_bits > 7)
+		return 0;
+
+	if (length == 0 && unused_bits != 0)
+		return 0;
+
+	if (length > 0 && (data[length - 1] & ((1 << unused_bits) - 1)) != 0)
+		return 0;
+
+	if (!ASN1_STRING_set(abs, data, length))
+		return 0;
+
+	abs->type = V_ASN1_BIT_STRING;
+	abs->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
+	abs->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits;
+
+	return 1;
+}
+#endif /* HAVE_ASN1_BIT_STRING_SET1 */
Index: usr.sbin/rpki-client/ccr.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/ccr.c,v
diff -u -p -r1.34 ccr.c
--- usr.sbin/rpki-client/ccr.c	7 Apr 2026 14:38:04 -0000	1.34
+++ usr.sbin/rpki-client/ccr.c	13 Apr 2026 09:46:02 -0000
@@ -395,13 +395,9 @@ append_cached_vrp(STACK_OF(ROAIPAddress)
 	if (num_bits > 0)
 		unused_bits = 8 - num_bits;
 
-	if (!ASN1_BIT_STRING_set(ripa->address, vrp->addr.addr, num_bytes))
+	if (!ASN1_BIT_STRING_set1(ripa->address, vrp->addr.addr, num_bytes,
+	    unused_bits))
 		errx(1, "ASN1_BIT_STRING_set");
-
-	/* ip_addr_parse() handles unused bits, no need to clear them here. */
-	ripa->address->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits;
-
-	/* XXX - assert that unused bits are zero */
 
 	if (vrp->maxlength > vrp->addr.prefixlen) {
 		if ((ripa->maxLength = ASN1_INTEGER_new()) == NULL)
Index: usr.sbin/rpki-client/extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
diff -u -p -r1.278 extern.h
--- usr.sbin/rpki-client/extern.h	7 Apr 2026 10:59:19 -0000	1.278
+++ usr.sbin/rpki-client/extern.h	13 Apr 2026 09:46:02 -0000
@@ -1024,6 +1024,10 @@ time_t		 get_current_time(void);
 int	mkpath(const char *);
 int	mkpathat(int, const char *);
 
+/* Compat helpers for OpenSSL < 4 and LibreSSL. */
+int	ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *, size_t *, int *);
+int	ASN1_BIT_STRING_set1(ASN1_BIT_STRING *, const uint8_t *, size_t, int);
+
 #define RPKI_PATH_OUT_DIR	"/var/db/rpki-client"
 #define RPKI_PATH_BASE_DIR	"/var/cache/rpki-client"
 
Index: usr.sbin/rpki-client/ip.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/ip.c,v
diff -u -p -r1.37 ip.c
--- usr.sbin/rpki-client/ip.c	4 Dec 2025 06:11:44 -0000	1.37
+++ usr.sbin/rpki-client/ip.c	13 Apr 2026 09:46:02 -0000
@@ -173,15 +173,15 @@ ip_addr_parse(const ASN1_BIT_STRING *abs
     enum afi afi, const char *fn, struct ip_addr *addr)
 {
 	const unsigned char *data;
-	int length, unused = 0;
+	size_t length;
+	int unused = 0;
 
 	data = ASN1_STRING_get0_data(abs);
-	length = ASN1_STRING_length(abs);
 
-	/* Weird OpenSSL-ism to get unused bit count. */
-
-	if ((abs->flags & ASN1_STRING_FLAG_BITS_LEFT))
-		unused = abs->flags & 0x07;
+	if (!ASN1_BIT_STRING_get_length(abs, &length, &unused)) {
+		warnx("%s: invalid bit string in IP address", fn);
+		return 0;
+	}
 
 	if (length == 0 && unused != 0) {
 		warnx("%s: RFC 3779 section 2.2.3.8: "
Index: usr.sbin/rpki-client/mft.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v
diff -u -p -r1.136 mft.c
--- usr.sbin/rpki-client/mft.c	16 Jan 2026 11:25:27 -0000	1.136
+++ usr.sbin/rpki-client/mft.c	13 Apr 2026 09:46:02 -0000
@@ -151,7 +151,8 @@ mft_parse_filehash(const char *fn, struc
 {
 	const unsigned char	*data;
 	char			*file = NULL;
-	int			 length, rc = 0;
+	size_t			 len;
+	int			 length, unused_bits, rc = 0;
 	struct mftfile		*fent;
 	enum rtype		 type;
 	size_t			 new_idx = 0;
@@ -167,15 +168,19 @@ mft_parse_filehash(const char *fn, struc
 	if (file == NULL)
 		err(1, NULL);
 
-	/* XXX - malleability: ensure unused bits are 0. */
 	data = ASN1_STRING_get0_data(fh->hash);
-	length = ASN1_STRING_length(fh->hash);
-
-	if (length != SHA256_DIGEST_LENGTH) {
+	if (!ASN1_BIT_STRING_get_length(fh->hash, &len, &unused_bits)) {
 		warnx("%s: RFC 9286 section 4.2.1: hash: "
 		    "invalid SHA256 length, have %d", fn, length);
 		goto out;
 	}
+	if (len != SHA256_DIGEST_LENGTH || unused_bits != 0) {
+		warnx("%s: RFC 9286 section 4.2.1: hash: "
+		    "invalid SHA256 length, have %zu (%d unused bits)",
+		    fn, len, unused_bits);
+		goto out;
+	}
+	length = len;
 
 	type = rtype_from_mftfile(file);
 	if (type == RTYPE_CRL) {
Index: regress/usr.sbin/rpki-client/Makefile.inc
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/Makefile.inc,v
diff -u -p -r1.46 Makefile.inc
--- regress/usr.sbin/rpki-client/Makefile.inc	16 Jan 2026 11:25:27 -0000	1.46
+++ regress/usr.sbin/rpki-client/Makefile.inc	13 Apr 2026 09:46:02 -0000
@@ -29,7 +29,7 @@ CLEANFILES+=	*.out *.err *.txt
 
 TEST_COMMON= as.c aspa.c ccr.c cert.c cms.c constraints-dummy.c crl.c \
 	     encoding.c io.c ip.c json.c mft.c print.c repo-dummy.c \
-	     rfc3779.c roa.c validate.c x509.c
+	     rfc3779.c roa.c validate.c x509.c asn1_bit_string.c
 
 SRCS_test-ip +=		test-ip.c ${TEST_COMMON}
 run-regress-test-ip: test-ip
Index: regress/usr.sbin/rpki-client/openssl/Makefile
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/openssl/Makefile,v
diff -u -p -r1.10 Makefile
--- regress/usr.sbin/rpki-client/openssl/Makefile	31 Mar 2026 06:25:39 -0000	1.10
+++ regress/usr.sbin/rpki-client/openssl/Makefile	13 Apr 2026 09:46:02 -0000
@@ -1,8 +1,10 @@
 # $OpenBSD: Makefile,v 1.10 2026/03/31 06:25:39 tb Exp $
 
-EOPENSSL =	eopenssl35
+EOPENSSL =	eopenssl40
 
 .if ${EOPENSSL} != "eopenssl35"
+CFLAGS += -DHAVE_ASN1_BIT_STRING_GET_LENGTH
+CFLAGS += -DHAVE_ASN1_BIT_STRING_SET1
 CFLAGS += -DHAVE_X509_CRL_GET0_TBS_SIGALG
 .endif
 
Index: regress/usr.sbin/rpki-client/openssl/build/Makefile
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/openssl/build/Makefile,v
diff -u -p -r1.7 Makefile
--- regress/usr.sbin/rpki-client/openssl/build/Makefile	31 Mar 2026 06:25:39 -0000	1.7
+++ regress/usr.sbin/rpki-client/openssl/build/Makefile	13 Apr 2026 09:46:02 -0000
@@ -1,8 +1,10 @@
 # $OpenBSD: Makefile,v 1.7 2026/03/31 06:25:39 tb Exp $
 
-EOPENSSL =	eopenssl35
+EOPENSSL =	eopenssl40
 
 .if ${EOPENSSL} != "eopenssl35"
+CFLAGS += -DHAVE_ASN1_BIT_STRING_GET_LENGTH
+CFLAGS += -DHAVE_ASN1_BIT_STRING_SET1
 CFLAGS += -DHAVE_X509_CRL_GET0_TBS_SIGALG
 .endif