Commit da8f09846b for openssl.org

commit da8f09846b98c5a518d5634c50f6835c5bc4be95
Author: Theo Buehler <tb@openbsd.org>
Date:   Thu Dec 11 08:38:53 2025 -0700

    Add ASN1_BIT_STRING_get_length()

    From tb@openbsd.org with tests adapted by beck for OpenSSL.

    Fixes: https://github.com/openssl/openssl/issues/29184

    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.org>
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/29387)

diff --git a/crypto/asn1/a_bitstr.c b/crypto/asn1/a_bitstr.c
index 223efc2bb1..1d58c9cbbf 100644
--- a/crypto/asn1/a_bitstr.c
+++ b/crypto/asn1/a_bitstr.c
@@ -220,3 +220,36 @@ int ASN1_BIT_STRING_check(const ASN1_BIT_STRING *a,
     }
     return ok;
 }
+
+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;
+}
diff --git a/doc/build.info b/doc/build.info
index fdaa35df76..d53aed8df8 100644
--- a/doc/build.info
+++ b/doc/build.info
@@ -479,6 +479,10 @@ DEPEND[html/man3/ADMISSIONS.html]=man3/ADMISSIONS.pod
 GENERATE[html/man3/ADMISSIONS.html]=man3/ADMISSIONS.pod
 DEPEND[man/man3/ADMISSIONS.3]=man3/ADMISSIONS.pod
 GENERATE[man/man3/ADMISSIONS.3]=man3/ADMISSIONS.pod
+DEPEND[html/man3/ASN1_BIT_STRING_get_length.html]=man3/ASN1_BIT_STRING_get_length.pod
+GENERATE[html/man3/ASN1_BIT_STRING_get_length.html]=man3/ASN1_BIT_STRING_get_length.pod
+DEPEND[man/man3/ASN1_BIT_STRING_get_length.3]=man3/ASN1_BIT_STRING_get_length.pod
+GENERATE[man/man3/ASN1_BIT_STRING_get_length.3]=man3/ASN1_BIT_STRING_get_length.pod
 DEPEND[html/man3/ASN1_EXTERN_FUNCS.html]=man3/ASN1_EXTERN_FUNCS.pod
 GENERATE[html/man3/ASN1_EXTERN_FUNCS.html]=man3/ASN1_EXTERN_FUNCS.pod
 DEPEND[man/man3/ASN1_EXTERN_FUNCS.3]=man3/ASN1_EXTERN_FUNCS.pod
@@ -3165,6 +3169,7 @@ DEPEND[man/man3/s2i_ASN1_IA5STRING.3]=man3/s2i_ASN1_IA5STRING.pod
 GENERATE[man/man3/s2i_ASN1_IA5STRING.3]=man3/s2i_ASN1_IA5STRING.pod
 IMAGEDOCS[man3]=
 HTMLDOCS[man3]=html/man3/ADMISSIONS.html \
+html/man3/ASN1_BIT_STRING_get_length.html \
 html/man3/ASN1_EXTERN_FUNCS.html \
 html/man3/ASN1_INTEGER_get_int64.html \
 html/man3/ASN1_INTEGER_new.html \
@@ -3837,6 +3842,7 @@ html/man3/i2d_re_X509_tbs.html \
 html/man3/o2i_SCT_LIST.html \
 html/man3/s2i_ASN1_IA5STRING.html
 MANDOCS[man3]=man/man3/ADMISSIONS.3 \
+man/man3/ASN1_BIT_STRING_get_length.3 \
 man/man3/ASN1_EXTERN_FUNCS.3 \
 man/man3/ASN1_INTEGER_get_int64.3 \
 man/man3/ASN1_INTEGER_new.3 \
diff --git a/doc/man3/ASN1_BIT_STRING_get_length.pod b/doc/man3/ASN1_BIT_STRING_get_length.pod
new file mode 100644
index 0000000000..79497f41ec
--- /dev/null
+++ b/doc/man3/ASN1_BIT_STRING_get_length.pod
@@ -0,0 +1,35 @@
+=pod
+
+=head1 NAME
+
+ASN1_BIT_STRING_get_length - ASN1_BIT_STRING accessors
+
+=head1 SYNOPSIS
+
+  #include <openssl/asn1.h>
+
+  int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length, int *unused_bits);
+
+=head1 DESCRIPTION
+
+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.
+
+=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.
+
+=head1 COPYRIGHT
+
+Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the Apache License 2.0 (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/include/openssl/asn1.h.in b/include/openssl/asn1.h.in
index 72c20f76d7..1b7dd66d68 100644
--- a/include/openssl/asn1.h.in
+++ b/include/openssl/asn1.h.in
@@ -586,6 +586,8 @@ int ASN1_BIT_STRING_name_print(BIO *out, ASN1_BIT_STRING *bs,
 int ASN1_BIT_STRING_num_asc(const char *name, BIT_STRING_BITNAME *tbl);
 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);

 /* clang-format off */
 {-
diff --git a/test/asn1_string_test.c b/test/asn1_string_test.c
new file mode 100644
index 0000000000..7428597621
--- /dev/null
+++ b/test/asn1_string_test.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/* ASN1_STRING tests */
+
+#include <stdio.h>
+
+#include <openssl/asn1.h>
+#include "testutil.h"
+
+struct abs_get_length_test {
+    const char *descr;
+    int valid;
+    const unsigned char der[20];
+    int der_len;
+    size_t length;
+    int unused_bits;
+};
+
+static const struct abs_get_length_test abs_get_length_tests[] = {
+    {
+        .descr = "zero bits",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x01,
+            0x00,
+        },
+        .der_len = 3,
+        .length = 0,
+        .unused_bits = 0,
+    },
+    {
+        .descr = "zero bits one unused",
+        .valid = 0,
+        .der = {
+            0x03,
+            0x01,
+            0x01,
+        },
+        .der_len = 3,
+    },
+    {
+        .descr = "single zero bit",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x02,
+            0x07,
+            0x00,
+        },
+        .der_len = 4,
+        .length = 1,
+        .unused_bits = 7,
+    },
+    {
+        .descr = "single one bit",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x02,
+            0x07,
+            0x80,
+        },
+        .der_len = 4,
+        .length = 1,
+        .unused_bits = 7,
+    },
+    {
+        /* XXX - the library pretends this is 03 02 07 80 */
+        .descr = "invalid: single one bit, seventh bit set",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x02,
+            0x07,
+            0xc0,
+        },
+        .der_len = 4,
+        .length = 1,
+        .unused_bits = 7,
+    },
+    {
+        .descr = "x.690, primitive encoding in example 8.6.4.2",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x07,
+            0x04,
+            0x0A,
+            0x3b,
+            0x5F,
+            0x29,
+            0x1c,
+            0xd0,
+        },
+        .der_len = 9,
+        .length = 6,
+        .unused_bits = 4,
+    },
+    {
+        /*
+         * XXX - the library thinks it "decodes" this but gets it
+         * quite wrong. Looks like it uses the unused bits of the
+         * first component, and the unused bits octet 04 of the
+         * second component somehow becomes part of the value.
+         */
+        .descr = "x.690, constructed encoding in example 8.6.4.2",
+        .valid = 1,
+        .der = {
+            0x23,
+            0x80,
+            0x03,
+            0x03,
+            0x00,
+            0x0A,
+            0x3b,
+            0x03,
+            0x05,
+            0x04,
+            0x5F,
+            0x29,
+            0x1c,
+            0xd0,
+            0x00,
+            0x00,
+        },
+        .der_len = 16,
+        .length = 7, /* XXX - should be 6. */
+        .unused_bits = 0, /* XXX - should be 4. */
+    },
+    {
+        .descr = "RFC 3779, 2.1.1, IPv4 address 10.5.0.4",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x05,
+            0x00,
+            0x0a,
+            0x05,
+            0x00,
+            0x04,
+        },
+        .der_len = 7,
+        .length = 4,
+        .unused_bits = 0,
+    },
+    {
+        .descr = "RFC 3779, 2.1.1, IPv4 prefix 10.5.0/23",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x04,
+            0x01,
+            0x0a,
+            0x05,
+            0x00,
+        },
+        .der_len = 6,
+        .length = 3,
+        .unused_bits = 1,
+    },
+    {
+        .descr = "RFC 3779, 2.1.1, IPv6 address 2001:0:200:3::1",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x11,
+            0x00,
+            0x20,
+            0x01,
+            0x00,
+            0x00,
+            0x02,
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            0x03,
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            0x01,
+        },
+        .der_len = 19,
+        .length = 16,
+        .unused_bits = 0,
+    },
+    {
+        .descr = "RFC 3779, 2.1.1, IPv6 prefix 2001:0:200/39",
+        .valid = 1,
+        .der = {
+            0x03,
+            0x06,
+            0x01,
+            0x20,
+            0x01,
+            0x00,
+            0x00,
+            0x02,
+        },
+        .der_len = 8,
+        .length = 5,
+        .unused_bits = 1,
+    },
+};
+
+static int
+abs_get_length_test(const struct abs_get_length_test *tbl, int idx)
+{
+    const struct abs_get_length_test *test = &tbl[idx];
+    ASN1_BIT_STRING *abs = NULL;
+    const unsigned char *p;
+    int unused_bits, ret;
+    size_t length;
+    int success = 0;
+
+    p = test->der;
+    if (!TEST_ptr(abs = d2i_ASN1_BIT_STRING(NULL, &p, test->der_len))) {
+        TEST_info("%s, (idx=%d) - d2i_ASN1_BIT_STRING faled", __func__, idx);
+        goto err;
+    }
+
+    ret = ASN1_BIT_STRING_get_length(abs, &length, &unused_bits);
+    if (!TEST_int_eq(test->valid, ret)) {
+        TEST_info("%s (idx=%d): %s ASN1_BIT_STRING_get_length want %d, got %d\n",
+            __func__, idx, test->descr, test->valid, ret);
+        goto err;
+    }
+    if (!test->valid)
+        goto done;
+
+    if (!TEST_size_t_eq(length, test->length)
+        || !TEST_int_eq(unused_bits, test->unused_bits)) {
+        TEST_info("%s: (idx=%d) %s: want (%zu, %d), got (%zu, %d)\n", __func__,
+            idx, test->descr, test->length, test->unused_bits, length,
+            unused_bits);
+        goto err;
+    }
+
+done:
+    success = 1;
+
+err:
+    ASN1_STRING_free(abs);
+
+    return success;
+}
+
+static int
+asn1_bit_string_get_length_test(int idx)
+{
+    return abs_get_length_test(abs_get_length_tests, idx);
+}
+
+int setup_tests(void)
+{
+    ADD_ALL_TESTS(asn1_bit_string_get_length_test, OSSL_NELEM(abs_get_length_tests));
+    return 1;
+}
diff --git a/test/build.info b/test/build.info
index e5990247d8..6890544f21 100644
--- a/test/build.info
+++ b/test/build.info
@@ -1096,6 +1096,11 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[asn1_time_test]=../include ../apps/include
   DEPEND[asn1_time_test]=../libcrypto libtestutil.a

+  PROGRAMS{noinst}=asn1_string_test
+  SOURCE[asn1_string_test]=asn1_string_test.c
+  INCLUDE[asn1_string_test]=../include ../apps/include
+  DEPEND[asn1_string_test]=../libcrypto libtestutil.a
+
   # We disable this test completely in a shared build because it deliberately
   # redefines some internal libssl symbols. This doesn't work in a non-shared
   # build
diff --git a/test/recipes/90-test_asn1_string.t b/test/recipes/90-test_asn1_string.t
new file mode 100644
index 0000000000..ce0db28a18
--- /dev/null
+++ b/test/recipes/90-test_asn1_string.t
@@ -0,0 +1,12 @@
+#! /usr/bin/env perl
+# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+
+use OpenSSL::Test::Simple;
+
+simple_test("test_asn1_string", "asn1_string_test");
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 16a60ba98c..c1b1367028 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -2614,6 +2614,7 @@ ASN1_BIT_STRING_set                     ?	4_0_0	EXIST::FUNCTION:
 ASN1_BIT_STRING_set_bit                 ?	4_0_0	EXIST::FUNCTION:
 ASN1_BIT_STRING_get_bit                 ?	4_0_0	EXIST::FUNCTION:
 ASN1_BIT_STRING_check                   ?	4_0_0	EXIST::FUNCTION:
+ASN1_BIT_STRING_get_length              ?	4_0_0	EXIST::FUNCTION:
 ASN1_BIT_STRING_name_print              ?	4_0_0	EXIST::FUNCTION:
 ASN1_BIT_STRING_num_asc                 ?	4_0_0	EXIST::FUNCTION:
 ASN1_BIT_STRING_set_asc                 ?	4_0_0	EXIST::FUNCTION: