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: