From: Claudio Jeker Subject: Re: rpki-client: openssl 4 compat To: Theo Buehler Cc: tech@openbsd.org Date: Mon, 13 Apr 2026 13:34:50 +0200 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 > + * > + * 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 > +#include > +#include > + > +#include > + > +#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