From: Theo Buehler Date: Mon, 2 Feb 2026 22:55:32 +0000 (-0700) Subject: Provide ASN1_BIT_STRING_set1() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=74d47c8e66e02c1b6d97323ad4507292b02e9e5b;p=thirdparty%2Fopenssl.git 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 Reviewed-by: Tomas Mraz 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 f88d6295338..48a378d6185 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 1d58c9cbbf7..29b0a03b005 100644 --- a/crypto/asn1/a_bitstr.c +++ b/crypto/asn1/a_bitstr.c @@ -13,6 +13,27 @@ #include #include "asn1_local.h" +#include + +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 79497f41ec1..7c4f4c6758c 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 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 containing bit values in I and the number of unused bits in the last octet in I. The value returned in I is guaranteed to be between 0 and 7, inclusive. +ASN1_BIT_STRING_set1() sets the type of I to +I and its octets to the bits in the byte string +I of length I octets, making sure that the last +I 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 is internally inconsistent, or if one of I, I, or I is NULL. +ASN1_BIT_STRING_set1() returns 1 on success or 0 if memory allocation +fails or if I is NULL , I is too large, Iis +zero and I is nonzero, I is less than 0 or +greater than 7, or any unused bit in the last octet of I 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 08d1c278174..7f792db610f 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 74285976218..122db56de82 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 1213bfcbe4a..fb091d360c8 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: