]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Provide ASN1_BIT_STRING_set1()
authorTheo Buehler <tb@openbsd.org>
Mon, 2 Feb 2026 22:55:32 +0000 (15:55 -0700)
committerNeil Horman <nhorman@openssl.org>
Thu, 12 Feb 2026 20:41:09 +0000 (15:41 -0500)
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)

CHANGES.md
crypto/asn1/a_bitstr.c
doc/man3/ASN1_BIT_STRING_get_length.pod
include/openssl/asn1.h.in
test/asn1_string_test.c
util/libcrypto.num

index f88d629533813cdef189fa26bd2c889c111ca101..48a378d6185baaa9b96e656ded5cb8e48ee6a8d0 100644 (file)
@@ -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*
index 1d58c9cbbf72576cd2200afbe7d247120a1a5b2b..29b0a03b005150c87d8d135645ea990346fc4a4e 100644 (file)
 #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);
+}
index 79497f41ec13f15beb14e7a3d19c35245f97dfda..7c4f4c6758cb9e7e68b9d437560a6e5f783286f3 100644 (file)
@@ -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.
index 08d1c27817474c297f2737553ac101f8427519e0..7f792db610f6b2c190bee74dac2710f5680e77ed 100644 (file)
@@ -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 */
 {-
index 74285976218582b768135cb3be87900e916094bc..122db56de821f207e7669272d781d2a9f171f873 100644 (file)
@@ -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;
 }
index 1213bfcbe4a0a9c4326e5e0d8897da032851c163..fb091d360c867814ca15b0ab3d041576d4d80306 100644 (file)
@@ -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: