Prepare a now opaque ASN1_STRING for the size_t rapture.

Now that ASN1_STRING is opaque, we can finally move away
from an int for the length internally. The remaining problematic
piece for this is that ASN1_STRING_length() returns an int
and is public API.

Therefore, we deprecate ASN1_STRING_length() and provide a
replacement ASN1_STRING_length_ex() that returns a size_t length.

We also provide setting functions that take size_t lengths,
they are ASN1_STRING_set_data() which takes a uint8_t data
pointer and a size_t length, and ASN1_STRING_set_string() which
takes a argument that must be a c string and will use strlen
to determine the length. (This replaces th previous arcane
behaviour of calling "strlen" on a magical input length value
of -1, which leads to bugs.)  We then deprecate ASN1_STRING_set().

ASN1_STRING_set_string() requires a valid C string argument that
may not be NULL - refer to the documentation.

Both new functions do not magically add 0 bytes on the end of
values, as ASN1_STRING has already been documented for a long
time to not depend on this behaviour.

Both new functions do not allow the setting of values on an
ASN1_BIT_STRING, as ASN1_BIT_STRING_set1 must be used for that.

Note that this does *NOT* yet change ASN1_STRING to use size_t
internally, this must wait until the integer-returning ASN1_STRING_length()
has been deprecated, and then removed in future major. Once
ASN1_STRING_length() has been removed then ASN1_STRING internally
can change to using a size_t for the length of the data.
(And the setters will no longer return an error if the provided
size_t length exceeds INT_MAX)

Reviewed-by: Milan Broz <mbroz@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
MergeDate: Sat Jul 18 13:01:15 2026
(Merged from https://github.com/openssl/openssl/pull/31194)
This commit is contained in:
Bob Beck 2026-05-11 10:30:04 -06:00 committed by Norbert Pocs
parent d8bf6cdd48
commit 28179061bf
6 changed files with 224 additions and 51 deletions

View file

@ -25,5 +25,11 @@ int ASN1_OCTET_STRING_cmp(const ASN1_OCTET_STRING *a,
int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *x, const unsigned char *d,
int len)
{
return ASN1_STRING_set(x, d, len);
if (len < -1) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_SMALL);
return 0;
}
if (len == -1)
return ASN1_STRING_set_string(x, (const char *)d);
return ASN1_STRING_set_data(x, d, len);
}

View file

@ -265,7 +265,8 @@ int ASN1_STRING_copy(ASN1_STRING *dst, const ASN1_STRING *str)
if (str == NULL)
return 0;
dst->type = str->type;
if (!ASN1_STRING_set(dst, str->data, str->length))
if (!ossl_asn1_string_set_internal(dst, str->data, str->length,
/*add_nul_byte=*/0))
return 0;
/* Copy flags but preserve embed value */
dst->flags &= ASN1_STRING_FLAG_EMBED;
@ -289,12 +290,18 @@ ASN1_STRING *ASN1_STRING_dup(const ASN1_STRING *str)
return ret;
}
int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
int ossl_asn1_string_set_internal(ASN1_STRING *str, const uint8_t *data,
int len_in, int add_nul_byte)
{
unsigned char *c;
const char *data = _data;
size_t len;
size_t len, alloc_len;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/*
* Force no NUL byte for callers that are requesting it
* 0 length object data will be NULL
*/
add_nul_byte = 0;
#endif
if (len_in < -1) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_SMALL);
return 0;
@ -302,16 +309,17 @@ int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
if (len_in == -1) {
if (data == NULL)
return 0;
len = strlen(data);
len = strlen((const char *)data);
} else {
len = (size_t)len_in;
}
/*
* Verify that the length fits within an integer for assignment to
* str->length below. The additional 1 is subtracted to allow for the
* '\0' terminator even though this isn't strictly necessary.
* Add one to the length to allow for adding an a '\0' terminator
* "even though this isn't strictly necessary".
*/
if (len > INT_MAX - 1) {
alloc_len = add_nul_byte ? len + 1 : len;
if (alloc_len > INT_MAX) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LARGE);
return 0;
}
@ -322,39 +330,47 @@ int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
str->flags &= ~ASN1_STRING_FLAG_DATA_NOT_OWNED;
}
if ((size_t)str->length <= len || str->data == NULL) {
c = str->data;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/* No NUL terminator in fuzzing builds */
str->data = OPENSSL_realloc(c, len != 0 ? len : 1);
#else
str->data = OPENSSL_realloc(c, len + 1);
#endif
if (str->data == NULL) {
str->data = c;
return 0;
}
/* Ensure copying a 0 length data field is defined. */
if (alloc_len == 0) {
OPENSSL_free(str->data);
str->data = NULL;
str->length = 0;
return 1;
}
if ((size_t)str->length != alloc_len) {
uint8_t *c;
c = OPENSSL_realloc(str->length == 0 ? NULL : str->data, alloc_len);
if (c == NULL)
return 0;
str->data = c;
}
/* length never includes the added \0 byte */
str->length = (int)len;
if (data != NULL) {
if (data != NULL && str->data != NULL) {
memcpy(str->data, data, len);
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/* Set the unused byte to something non NUL and printable. */
if (len == 0)
str->data[len] = '~';
#else
/*
* Add a NUL terminator. This should not be necessary - but we add it as
* a safety precaution
*/
str->data[len] = '\0';
#endif
if (add_nul_byte) {
/*
* Add a '\0' terminator. This should not be necessary - but we add it as
* a safety precaution
*/
str->data[len] = '\0';
}
}
ossl_asn1_bit_string_clear_unused_bits(str);
return 1;
}
#ifndef OPENSSL_NO_DEPRECATED_4_1
int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
{
return ossl_asn1_string_set_internal(str, (const uint8_t *)_data, len_in,
/*add_nul_byte=*/1);
}
#endif
void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len)
{
if (!(str->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
@ -365,6 +381,26 @@ void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len)
str->length = len;
}
int ASN1_STRING_set_data(ASN1_STRING *str, const uint8_t *data, size_t len_in)
{
if (str->type == V_ASN1_BIT_STRING) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_ILLEGAL_BITSTRING_FORMAT);
return 0;
}
/* This will go away once ASN1_STRING can size_t internally */
if (len_in > INT_MAX) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LARGE);
return 0;
}
return ossl_asn1_string_set_internal(str, data, (int)len_in, /*add_nul_byte=*/0);
}
int ASN1_STRING_set_string(ASN1_STRING *str, const char *c_string)
{
return ASN1_STRING_set_data(str, (const uint8_t *)c_string,
strlen(c_string));
}
ASN1_STRING *ASN1_STRING_new(void)
{
return ASN1_STRING_type_new(V_ASN1_OCTET_STRING);
@ -469,10 +505,17 @@ int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b)
}
}
#ifndef OPENSSL_NO_DEPRECATED_4_1
int ASN1_STRING_length(const ASN1_STRING *x)
{
return x->length;
}
#endif
size_t ASN1_STRING_length_ex(const ASN1_STRING *x)
{
return (size_t)x->length;
}
#ifndef OPENSSL_NO_DEPRECATED_3_0
void ASN1_STRING_length_set(ASN1_STRING *x, int len)

View file

@ -2,6 +2,7 @@
=head1 NAME
ASN1_STRING_set_data, ASN1_STRING_set_string, ASN1_STRING_length_ex,
ASN1_STRING_dup, ASN1_STRING_cmp, ASN1_STRING_set, ASN1_STRING_length,
ASN1_STRING_type, ASN1_STRING_get0_data,
ASN1_STRING_to_UTF8 - ASN1_STRING utility functions
@ -10,19 +11,30 @@ ASN1_STRING_to_UTF8 - ASN1_STRING utility functions
#include <openssl/asn1.h>
int ASN1_STRING_length(ASN1_STRING *x);
const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *x);
ASN1_STRING *ASN1_STRING_dup(const ASN1_STRING *a);
int ASN1_STRING_cmp(ASN1_STRING *a, ASN1_STRING *b);
int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len);
int ASN1_STRING_type(const ASN1_STRING *x);
int ASN1_STRING_to_UTF8(unsigned char **out, const ASN1_STRING *in);
int ASN1_STRING_set_data(ASN1_STRING *str, const uint8_t *data, size_t len);
int ASN1_STRING_set_string(ASN1_STRING *str, const char *data);
size_t ASN1_STRING_length_ex(const ASN1_STRING *x);
The following functions have been deprecated since OpenSSL 4.1, and can be
hidden entirely by defining B<OPENSSL_API_COMPAT> with a suitable version value,
see L<openssl_user_macros(7)>:
int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len);
int ASN1_STRING_length(ASN1_STRING *x);
=head1 DESCRIPTION
These functions allow an B<ASN1_STRING> structure to be manipulated.
@ -38,9 +50,25 @@ ASN1_STRING_dup() returns a copy of the structure I<a>.
ASN1_STRING_cmp() compares I<a> and I<b> returning 0 if the two
are identical. The string types and content are compared.
ASN1_STRING_set() sets the data of string I<str> to the buffer
I<data> or length I<len>. The supplied data is copied. If I<len>
is -1 then the length is determined by strlen(data).
ASN1_STRING_set() allocates memory for string I<str> to hold I<len>
bytes of data. Any previously allocated memory owned by I<str> will be
freed or re-used. If I<data> is not NULL, I<len> bytes are copied
from the memory pointed to by I<data> to I<str>. If I<len> is -1 then
the length is determined by strlen(data).
ASN1_STRING_set_data() allocates memory for string I<str> to hold
I<len> bytes of data. Any previously allocated memory owned by I<str>
will be freed or re-used. If I<data> is not NULL, I<len> bytes are
copied from the memory pointed to by I<data> to I<str>. It is an error
to use this function on a string of type B<ASN1_BIT_STRING>.
ASN1_STRING_set_string() allocates memory for the string I<str> and makes
a copy of the characters from I<cstring>. Any previously
allocated memory owned by I<str> will be freed or re-used. I<cstring>
must point to a valid NUL-terminated C string, and must not be
NULL. The terminating NUL byte is not included in the data copied into
I<str>. It is an error to use this function on a string of type
B<ASN1_BIT_STRING>.
ASN1_STRING_type() returns the type of I<x>, using standard constants
such as B<V_ASN1_OCTET_STRING>.
@ -70,8 +98,9 @@ actual string type itself: for example for an IA5String the data will
be ASCII, for a BMPString two bytes per character in big endian
format, and for a UTF8String it will be in UTF8 format.
Similar care should be take to ensure the data is in the correct format
when calling ASN1_STRING_set().
Similar care should be taken to ensure the data is in the correct
format when calling ASN1_STRING_set(), ASN1_STRING_set_data(), or
ASN1_STRING_set_string().
=head1 RETURN VALUES
@ -86,7 +115,8 @@ error occurred.
ASN1_STRING_cmp() returns an integer greater than, equal to, or less than 0,
according to whether I<a> is greater than, equal to, or less than I<b>.
ASN1_STRING_set() returns 1 on success or 0 on error.
ASN1_STRING_set(), ASN1_STRING_set_data(), and
ASN1_STRING_set_string() return 1 on success or 0 on error or failure.
ASN1_STRING_type() returns the type of I<x>.
@ -97,6 +127,11 @@ negative value if an error occurred.
L<ERR_get_error(3)>
=head1 HISTORY
ASN1_STRING_set_data(), ASN1_STRING_set_string(), and ASN1_STRING_length_ex()
were added in OpenSSL 4.1.
=head1 COPYRIGHT
Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.

View file

@ -540,9 +540,16 @@ int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b);
* Since this is used to store all sorts of things, via macros, for now,
* make its data void *
*/
#if !defined(OPENSSL_NO_DEPRECATED_4_1)
OSSL_DEPRECATEDIN_4_1_FOR(" Use ASN1_STRING_set_data() or ASN1_STRING_set_string() instead.")
int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len);
void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len);
OSSL_DEPRECATEDIN_4_1_FOR(" Use ASN1_STRING_length_ex() instead.")
int ASN1_STRING_length(const ASN1_STRING *x);
#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */
void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len);
int ASN1_STRING_set_data(ASN1_STRING *str, const uint8_t *data, size_t len);
int ASN1_STRING_set_string(ASN1_STRING *str, const char *cstring);
size_t ASN1_STRING_length_ex(const ASN1_STRING *x);
#ifndef OPENSSL_NO_DEPRECATED_3_0
OSSL_DEPRECATEDIN_3_0 void ASN1_STRING_length_set(ASN1_STRING *x, int n);
#endif

View file

@ -390,6 +390,7 @@ asn1_bit_string_set1_test(int idx)
}
static int
asn1_string_new_not_owned_test(void)
{
int success = 0;
@ -410,13 +411,13 @@ asn1_string_new_not_owned_test(void)
if (!TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, sizeof(data))))
goto err;
if (!TEST_true(ASN1_STRING_set(tmp, "muppet", (int)strlen("muppet"))))
if (!TEST_true(ASN1_STRING_set_string(tmp, "muppet")))
goto err;
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
if (!TEST_int_eq(ASN1_STRING_length(tmp), (int)strlen("muppet")))
if (!TEST_size_t_eq(ASN1_STRING_length_ex(tmp), strlen("muppet")))
goto err;
if (!TEST_mem_eq(ASN1_STRING_get0_data(tmp), strlen("muppet"), "muppet", strlen("muppet")))
@ -436,13 +437,13 @@ asn1_string_new_not_owned_test(void)
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
if (!TEST_int_eq(ASN1_STRING_length(tmp), 4))
if (!TEST_size_t_eq(ASN1_STRING_length_ex(tmp), 4))
goto err;
if (!TEST_mem_eq(ASN1_STRING_get0_data(tmp), strlen("puppet"), "puppet", strlen("puppet")))
goto err;
memset((uint8_t *)ASN1_STRING_get0_data(tmp), 'z', ASN1_STRING_length(tmp));
memset((uint8_t *)ASN1_STRING_get0_data(tmp), 'z', ASN1_STRING_length_ex(tmp));
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
@ -474,10 +475,88 @@ err:
return success;
}
static int
asn1_string_set_data_test(void)
{
int success = 0;
ASN1_STRING *str = NULL;
const uint8_t *data;
if (!TEST_ptr(str = ASN1_STRING_new()))
goto err;
if (!TEST_false(ASN1_STRING_set_data(str, (uint8_t *)"hoobla", -1)))
goto err;
if (!TEST_false(ASN1_STRING_set_data(str, (uint8_t *)"hoobla", (size_t)INT_MAX + 1)))
goto err;
if (!TEST_true(ASN1_STRING_set_data(str, NULL, 10)))
goto err;
if (!TEST_true(ASN1_STRING_set_data(str, (uint8_t *)"hoobla", strlen("hoobla"))))
goto err;
data = ASN1_STRING_get0_data(str);
if (!TEST_size_t_eq(ASN1_STRING_length_ex(str), 6))
goto err;
if (!TEST_int_eq(memcmp("hoobla", data, strlen("hoobla")), 0))
goto err;
if (!TEST_true(ASN1_STRING_set_data(str, (uint8_t *)"hoobla", strlen("hoobla") + 1)))
goto err;
data = ASN1_STRING_get0_data(str);
if (!TEST_size_t_eq(ASN1_STRING_length_ex(str), 7))
goto err;
if (!TEST_int_eq(strcmp("hoobla", (char *)data), 0))
goto err;
success = 1;
err:
ASN1_STRING_free(str);
return success;
}
static int
asn1_string_set_string_test(void)
{
int success = 0;
ASN1_STRING *str = NULL;
if (!TEST_ptr(str = ASN1_STRING_new()))
goto err;
if (!TEST_true(ASN1_STRING_set_string(str, "foo")))
goto err;
if (!TEST_size_t_eq(ASN1_STRING_length_ex(str), 3))
goto err;
if (!TEST_true(ASN1_STRING_set_string(str, "hoob\0la")))
goto err;
if (!TEST_size_t_eq(ASN1_STRING_length_ex(str), 4))
goto err;
success = 1;
err:
ASN1_STRING_free(str);
return success;
}
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));
ADD_TEST(asn1_string_new_not_owned_test);
ADD_TEST(asn1_string_set_data_test);
ADD_TEST(asn1_string_set_string_test);
return 1;
}

View file

@ -2571,9 +2571,9 @@ ASN1_STRING_copy 2569 4_0_0 EXIST::FUNCTION:
ASN1_STRING_dup 2570 4_0_0 EXIST::FUNCTION:
ASN1_STRING_type_new 2571 4_0_0 EXIST::FUNCTION:
ASN1_STRING_cmp 2572 4_0_0 EXIST::FUNCTION:
ASN1_STRING_set 2573 4_0_0 EXIST::FUNCTION:
ASN1_STRING_set 2573 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_4_1
ASN1_STRING_set0 2574 4_0_0 EXIST::FUNCTION:
ASN1_STRING_length 2575 4_0_0 EXIST::FUNCTION:
ASN1_STRING_length 2575 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_4_1
ASN1_STRING_length_set 2576 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_3_0
ASN1_STRING_type 2577 4_0_0 EXIST::FUNCTION:
ASN1_STRING_get0_data 2578 4_0_0 EXIST::FUNCTION:
@ -5724,3 +5724,6 @@ OPENSSL_sk_set_copy_thunks ? 4_1_0 EXIST::FUNCTION:
ASN1_STRING_new_not_owned ? 4_1_0 EXIST::FUNCTION:
EVP_KDF_CTX_get0_kdf ? 4_1_0 EXIST::FUNCTION:
EVP_KDF_CTX_get1_kdf ? 4_1_0 EXIST::FUNCTION:
ASN1_STRING_set_data ? 4_1_0 EXIST::FUNCTION:
ASN1_STRING_set_string ? 4_1_0 EXIST::FUNCTION:
ASN1_STRING_length_ex ? 4_1_0 EXIST::FUNCTION: