Index | Thread | Search

From:
Claudio Jeker <cjeker@diehard.n-r-g.com>
Subject:
Re: rpki-client: openssl 4 compat
To:
Theo Buehler <tb@theobuehler.org>
Cc:
tech@openbsd.org
Date:
Mon, 13 Apr 2026 13:34:50 +0200

Download raw body.

Thread
On Mon, Apr 13, 2026 at 12:11:38PM +0200, Theo Buehler wrote:
> 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
>  
 
Looks good to me.

> +	/* OpenSSL likes such checks. */

Hell yes. I would have skipped a fair bunch of those but it seems people
like to pass random NULLs everywhere.

OK claudio@

-- 
:wq Claudio