Commit 74d47c8e66 for openssl.org
commit 74d47c8e66e02c1b6d97323ad4507292b02e9e5b
Author: Theo Buehler <tb@openbsd.org>
Date: Mon Feb 2 15:55:32 2026 -0700
Provide ASN1_BIT_STRING_set1()
Mostly work by @botovq with tests adapted to openssl by
@bob-beck
Fixes: https://github.com/openssl/openssl/issues/29185
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
MergeDate: Thu Feb 12 20:41:13 2026
(Merged from https://github.com/openssl/openssl/pull/29926)
diff --git a/CHANGES.md b/CHANGES.md
index f88d629533..48a378d618 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -125,6 +125,11 @@ OpenSSL 4.0
*Beat Bolli*
+ * Added ASN1_BIT_STRING_set1() to set a bit string to a value including
+ the length in bytes and the number of unused bits.
+
+ * Bob Beck *
+
* The deprecated function `ASN1_STRING_data` has been removed.
*Bob Beck*
diff --git a/crypto/asn1/a_bitstr.c b/crypto/asn1/a_bitstr.c
index 1d58c9cbbf..29b0a03b00 100644
--- a/crypto/asn1/a_bitstr.c
+++ b/crypto/asn1/a_bitstr.c
@@ -13,6 +13,27 @@
#include <openssl/asn1.h>
#include "asn1_local.h"
+#include <crypto/asn1.h>
+
+static void
+asn1_bit_string_clear_unused_bits(ASN1_BIT_STRING *abs)
+{
+ abs->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
+}
+
+static int asn1_bit_string_set_unused_bits(ASN1_BIT_STRING *abs,
+ uint8_t unused_bits)
+{
+ if (unused_bits > 7)
+ return 0;
+
+ asn1_bit_string_clear_unused_bits(abs);
+
+ abs->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits;
+
+ return 1;
+}
+
int ASN1_BIT_STRING_set(ASN1_BIT_STRING *x, unsigned char *d, int len)
{
return ASN1_STRING_set(x, d, len);
@@ -253,3 +274,31 @@ int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *abs, size_t *out_length,
return 1;
}
+
+int ASN1_BIT_STRING_set1(ASN1_BIT_STRING *abs, const uint8_t *data, size_t length,
+ int unused_bits)
+{
+ 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;
+
+ /*
+ * XXX - ASN1_STRING_set() and asn1_bit_string_set_unused_bits() preserve the
+ * state of flags irrelevant to ASN1_BIT_STRING. Should we explicitly
+ * clear them?
+ */
+
+ if (!ASN1_STRING_set(abs, data, (int)length))
+ return 0;
+ abs->type = V_ASN1_BIT_STRING;
+
+ return asn1_bit_string_set_unused_bits(abs, unused_bits);
+}
diff --git a/doc/man3/ASN1_BIT_STRING_get_length.pod b/doc/man3/ASN1_BIT_STRING_get_length.pod
index 79497f41ec..7c4f4c6758 100644
--- a/doc/man3/ASN1_BIT_STRING_get_length.pod
+++ b/doc/man3/ASN1_BIT_STRING_get_length.pod
@@ -2,6 +2,7 @@
=head1 NAME
+ASN1_BIT_STRING_set1,
ASN1_BIT_STRING_get_length - ASN1_BIT_STRING accessors
=head1 SYNOPSIS
@@ -9,20 +10,37 @@ ASN1_BIT_STRING_get_length - ASN1_BIT_STRING accessors
#include <openssl/asn1.h>
int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length, int *unused_bits);
+ int ASN1_BIT_STRING_set1(ASN1_BIT_STRING *bitstr, const uint8_t *data, size_t length, int unused_bits);
=head1 DESCRIPTION
+The ASN.1 BIT STRING type holds a bit string of arbitrary bit length.
+In the distinguished encoding rules DER, its bits are encoded in
+groups of eight, leaving between zero and seven bits of the
+last octet unused. If there are unused bits, they must all be set to zero.
+
ASN1_BIT_STRING_get_length() returns the number of octets in I<bitstr>
containing bit values in I<length> and the number of unused bits in
the last octet in I<unused_bits>. The value returned in
I<unused_bits> is guaranteed to be between 0 and 7, inclusive.
+ASN1_BIT_STRING_set1() sets the type of I<bitstr> to
+I<V_ASN1_BIT_STRING> and its octets to the bits in the byte string
+I<data> of length I<length> octets, making sure that the last
+I<unused_bits> bits in the last byte are zero.
+
=head1 RETURN VALUES
ASN1_BIT_STRING_get_length() returns 1 on success or 0 if the encoding
of I<bitstr> is internally inconsistent, or if one of I<bitstr>,
I<length>, or I<unused_bits> is NULL.
+ASN1_BIT_STRING_set1() returns 1 on success or 0 if memory allocation
+fails or if I<bitstr> is NULL , I<length> is too large, I<length>is
+zero and I<unused_bits> is nonzero, I<unused_bits> is less than 0 or
+greater than 7, or any unused bit in the last octet of I<data> is
+nonzero.
+
=head1 COPYRIGHT
Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/openssl/asn1.h.in b/include/openssl/asn1.h.in
index 08d1c27817..7f792db610 100644
--- a/include/openssl/asn1.h.in
+++ b/include/openssl/asn1.h.in
@@ -586,6 +586,8 @@ int ASN1_BIT_STRING_set_asc(ASN1_BIT_STRING *bs, const char *name, int value,
BIT_STRING_BITNAME *tbl);
int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *abs, size_t *length,
int *unused_bits);
+int ASN1_BIT_STRING_set1(ASN1_BIT_STRING *abs, const uint8_t *data,
+ size_t length, int unused_bits);
/* clang-format off */
{-
diff --git a/test/asn1_string_test.c b/test/asn1_string_test.c
index 7428597621..122db56de8 100644
--- a/test/asn1_string_test.c
+++ b/test/asn1_string_test.c
@@ -261,8 +261,407 @@ asn1_bit_string_get_length_test(int idx)
return abs_get_length_test(abs_get_length_tests, idx);
}
+struct abs_set1_test {
+ const char *descr;
+ int valid;
+ const uint8_t data[20];
+ size_t length;
+ int unused_bits;
+ const unsigned char der[20];
+ int der_len;
+};
+
+static const struct abs_set1_test abs_set1_tests[] = {
+ {
+ .descr = "length too large",
+ .valid = 0,
+ .length = (size_t)INT_MAX + 1,
+ },
+ {
+ .descr = "negative unused bits",
+ .valid = 0,
+ .unused_bits = -1,
+ },
+ {
+ .descr = "8 unused bits",
+ .valid = 0,
+ .unused_bits = 8,
+ },
+ {
+ .descr = "empty with unused bits",
+ .valid = 0,
+ .data = {
+ 0x00,
+ },
+ .length = 0,
+ .unused_bits = 1,
+ },
+ {
+ .descr = "empty",
+ .valid = 1,
+ .data = {
+ 0x00,
+ },
+ .length = 0,
+ .unused_bits = 0,
+ .der = {
+ 0x03,
+ 0x01,
+ 0x00,
+ },
+ .der_len = 3,
+ },
+ {
+ .descr = "single zero bit",
+ .valid = 1,
+ .data = {
+ 0x00,
+ },
+ .length = 1,
+ .unused_bits = 7,
+ .der = {
+ 0x03,
+ 0x02,
+ 0x07,
+ 0x00,
+ },
+ .der_len = 4,
+ },
+ {
+ .descr = "single zero bit, with non-zero unused bit 6",
+ .valid = 0,
+ .data = {
+ 0x40,
+ },
+ .length = 1,
+ .unused_bits = 7,
+ },
+ {
+ .descr = "single zero bit, with non-zero unused bit 0",
+ .valid = 0,
+ .data = {
+ 0x01,
+ },
+ .length = 1,
+ .unused_bits = 7,
+ },
+ {
+ .descr = "single one bit",
+ .valid = 1,
+ .data = {
+ 0x80,
+ },
+ .length = 1,
+ .unused_bits = 7,
+ .der = {
+ 0x03,
+ 0x02,
+ 0x07,
+ 0x80,
+ },
+ .der_len = 4,
+ },
+ {
+ .descr = "single one bit, with non-zero unused-bit 6",
+ .valid = 0,
+ .data = {
+ 0xc0,
+ },
+ .length = 1,
+ .unused_bits = 7,
+ },
+ {
+ .descr = "single one bit, with non-zero unused-bit 0",
+ .valid = 0,
+ .data = {
+ 0x81,
+ },
+ .length = 1,
+ .unused_bits = 7,
+ },
+ {
+ .descr = "RFC 3779, 2.1.1, IPv4 address 10.5.0.4",
+ .valid = 1,
+ .data = {
+ 0x0a,
+ 0x05,
+ 0x00,
+ 0x04,
+ },
+ .length = 4,
+ .unused_bits = 0,
+ .der = {
+ 0x03,
+ 0x05,
+ 0x00,
+ 0x0a,
+ 0x05,
+ 0x00,
+ 0x04,
+ },
+ .der_len = 7,
+ },
+ {
+ .descr = "RFC 3779, 2.1.1, IPv4 address 10.5.0/23",
+ .valid = 1,
+ .data = {
+ 0x0a,
+ 0x05,
+ 0x00,
+ },
+ .length = 3,
+ .unused_bits = 1,
+ .der = {
+ 0x03,
+ 0x04,
+ 0x01,
+ 0x0a,
+ 0x05,
+ 0x00,
+ },
+ .der_len = 6,
+ },
+ {
+ .descr = "RFC 3779, 2.1.1, IPv4 address 10.5.0/23, unused bit",
+ .valid = 0,
+ .data = {
+ 0x0a,
+ 0x05,
+ 0x01,
+ },
+ .length = 3,
+ .unused_bits = 1,
+ },
+ {
+ .descr = "RFC 3779, IPv4 address 10.5.0/17",
+ .valid = 1,
+ .data = {
+ 0x0a,
+ 0x05,
+ 0x00,
+ },
+ .length = 3,
+ .unused_bits = 7,
+ .der = {
+ 0x03,
+ 0x04,
+ 0x07,
+ 0x0a,
+ 0x05,
+ 0x00,
+ },
+ .der_len = 6,
+ },
+ {
+ .descr = "RFC 3779, IPv4 address 10.5.0/18, unused bit set",
+ .valid = 0,
+ .data = {
+ 0x0a,
+ 0x05,
+ 0x20,
+ },
+ .length = 3,
+ .unused_bits = 6,
+ },
+ {
+ .descr = "RFC 3779, 2.1.1, IPv6 address 2001:0:200:3::1",
+ .valid = 1,
+ .data = {
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01,
+ },
+ .length = 16,
+ .unused_bits = 0,
+ .der = {
+ 0x03,
+ 0x11,
+ 0x00,
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01,
+ },
+ .der_len = 19,
+ },
+ {
+ .descr = "RFC 3779, IPv6 address 2001:0:200:3::/127",
+ .valid = 1,
+ .data = {
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ },
+ .length = 16,
+ .unused_bits = 1,
+ .der = {
+ 0x03,
+ 0x11,
+ 0x01,
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ },
+ .der_len = 19,
+ },
+ {
+ .descr = "RFC 3779, IPv6 address 2001:0:200:3::/127, unused bit",
+ .valid = 0,
+ .data = {
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01,
+ },
+ .length = 16,
+ .unused_bits = 1,
+ },
+ {
+ .descr = "RFC 3779, 2.1.1, IPv6 address 2001:0:200:3::/39",
+ .valid = 1,
+ .data = {
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ },
+ .length = 5,
+ .unused_bits = 1,
+ .der = {
+ 0x03,
+ 0x06,
+ 0x01,
+ 0x20,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x02,
+ },
+ .der_len = 8,
+ },
+};
+
+static int
+abs_set1_test(const struct abs_set1_test *tbl, int idx)
+{
+ const struct abs_set1_test *test = &tbl[idx];
+ ASN1_BIT_STRING *abs = NULL;
+ unsigned char *der = NULL;
+ int ret, der_len = 0;
+ int success = 0;
+
+ if (!TEST_ptr(abs = ASN1_BIT_STRING_new())) {
+ TEST_info("%s: (idx = %d) %s ASN1_BIT_STRING_new()", __func__, idx, test->descr);
+ goto err;
+ }
+
+ ret = ASN1_BIT_STRING_set1(abs, test->data, test->length, test->unused_bits);
+ if (!TEST_int_eq(ret, test->valid)) {
+ TEST_info("%s: (idx = %d) %s ASN1_BIT_STRING_set1(): want %d, got %d",
+ __func__, idx, test->descr, test->valid, ret);
+ goto err;
+ }
+
+ if (!test->valid)
+ goto done;
+
+ der = NULL;
+ if (!TEST_int_eq((der_len = i2d_ASN1_BIT_STRING(abs, &der)), test->der_len)) {
+ TEST_info("%s: (idx=%d), %s i2d_ASN1_BIT_STRING(): want %d, got %d",
+ __func__, idx, test->descr, test->der_len, der_len);
+ if (der_len < 0)
+ der_len = 0;
+ goto err;
+ }
+
+ if (!TEST_mem_eq(der, der_len, test->der, test->der_len)) {
+ TEST_info("%s: (idx = %d) %s DER mismatch", __func__, idx, test->descr);
+ goto err;
+ }
+
+done:
+ success = 1;
+
+err:
+ ASN1_BIT_STRING_free(abs);
+ OPENSSL_clear_free(der, der_len);
+
+ return success;
+}
+
+static int
+asn1_bit_string_set1_test(int idx)
+{
+ return abs_set1_test(abs_set1_tests, idx);
+}
+
int setup_tests(void)
{
ADD_ALL_TESTS(asn1_bit_string_get_length_test, OSSL_NELEM(abs_get_length_tests));
+ ADD_ALL_TESTS(asn1_bit_string_set1_test, OSSL_NELEM(abs_set1_tests));
return 1;
}
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 1213bfcbe4..fb091d360c 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5703,3 +5703,4 @@ EVP_MD_CTX_serialize ? 4_0_0 EXIST::FUNCTION:
EVP_MD_CTX_deserialize ? 4_0_0 EXIST::FUNCTION:
OSSL_ENCODER_CTX_ctrl_string ? 4_0_0 EXIST::FUNCTION:
OPENSSL_sk_set_cmp_thunks ? 4_0_0 EXIST::FUNCTION:
+ASN1_BIT_STRING_set1 ? 4_0_0 EXIST::FUNCTION: