Provide ASN1_STRING_new_not_owned()

This function provides the ability to construct an ASN1_STRING
containing data that is not owned by the constructed ASN1_STRING. The
resulting ASN1_STRING, when freed, will not free the data, and it is
the caller's resposibility to ensure that the data lives past the
lifetime of any returned ASN1_STRING.

Why? you may ask? Many places where ->data and ->length were used
directly in the past before the opaquification of ASN1_STRING were
for this purpose, whether used for actual static data, or to turn
bytes created by and in control of the caller into an ASN1_STRING
for temporary use as an input. This function makes this easier
to do without making copies.

The function deliberately does not allow the creation of a BIT_STRING
as this would require also always providing unused bits, which is
annoying and unnecessary for almost all potential use cases.

For: https://github.com/openssl/openssl/issues/29861
For: https://github.com/openssl/openssl/issues/30162

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
MergeDate: Wed Jun  3 11:42:49 2026
(Merged from https://github.com/openssl/openssl/pull/30964)
This commit is contained in:
Bob Beck 2026-04-24 14:28:54 -06:00 committed by Norbert Pocs
parent 46b5165d44
commit 68c0321e90
10 changed files with 200 additions and 22 deletions

View file

@ -109,6 +109,12 @@ OpenSSL Releases
*Bob Beck*
* The API function `ASN1_STRING_new_not_owned` has been added to the
libcrypto. It provides the ability to construct an ASN1_STRING with data
for which ownership is not taken by the created ASN1_STRING object.
*Bob Beck*
* Added AVX2 optimized ML-DSA NTT operations on `x86_64`.
*Marcel Cornu and Tomasz Kantecki*

View file

@ -263,12 +263,6 @@ int ASN1_BIT_STRING_set1(ASN1_BIT_STRING *abs, const uint8_t *data, size_t lengt
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;

View file

@ -248,9 +248,15 @@ int ASN1_object_size(int constructed, int length, int tag)
return ret + length;
}
void ossl_asn1_bit_string_set_unused_bits(ASN1_STRING *str, unsigned int num)
void ossl_asn1_bit_string_clear_unused_bits(ASN1_STRING *str)
{
str->flags &= ~0x07;
str->flags &= ~ASN1_STRING_FLAG_BITS_LEFT;
}
void ossl_asn1_bit_string_set_unused_bits(ASN1_STRING *str, unsigned int num)
{
ossl_asn1_bit_string_clear_unused_bits(str);
str->flags |= ASN1_STRING_FLAG_BITS_LEFT | (num & 0x07);
}
@ -309,6 +315,13 @@ int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LARGE);
return 0;
}
if ((str->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
str->data = NULL;
str->length = 0;
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
@ -337,12 +350,17 @@ int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
str->data[len] = '\0';
#endif
}
ossl_asn1_bit_string_clear_unused_bits(str);
return 1;
}
void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len)
{
OPENSSL_free(str->data);
if (!(str->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
OPENSSL_clear_free(str->data, str->length);
}
str->flags &= ~ASN1_STRING_FLAG_DATA_NOT_OWNED;
str->data = data;
str->length = len;
}
@ -363,30 +381,75 @@ ASN1_STRING *ASN1_STRING_type_new(int type)
return ret;
}
void ossl_asn1_string_embed_free(ASN1_STRING *a, int embed)
ASN1_STRING *ASN1_STRING_new_not_owned(int type, const uint8_t *data,
size_t length)
{
ASN1_STRING *ret;
if (type == V_ASN1_BIT_STRING)
return NULL;
if (data == NULL || length == 0)
return NULL;
if (length > INT_MAX)
return NULL;
ret = OPENSSL_zalloc(sizeof(*ret));
if (ret == NULL)
return NULL;
ret->type = type;
ret->data = (unsigned char *)data;
ret->length = (int)length;
ret->flags |= ASN1_STRING_FLAG_DATA_NOT_OWNED;
return ret;
}
void ossl_asn1_string_free_internal(ASN1_STRING *a, int clear, int embed)
{
if (a == NULL)
return;
if (!(a->flags & ASN1_STRING_FLAG_NDEF))
OPENSSL_free(a->data);
if (embed == 0)
OPENSSL_free(a);
if ((a->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
a->data = NULL;
a->length = 0;
a->flags &= ~ASN1_STRING_FLAG_DATA_NOT_OWNED;
}
if (!(a->flags & ASN1_STRING_FLAG_NDEF)) {
if (clear)
OPENSSL_clear_free(a->data, a->length);
else
OPENSSL_free(a->data);
}
/*
* TODO(beck): Add an assert here to verify that the embed arg is
* always set to match the flag, and then get rid of the arg.
*/
if (!embed && !(a->flags & ASN1_STRING_FLAG_EMBED)) {
if (clear)
OPENSSL_clear_free(a, sizeof(*a));
else
OPENSSL_free(a);
}
}
void ASN1_STRING_free(ASN1_STRING *a)
{
if (a == NULL)
return;
ossl_asn1_string_embed_free(a, a->flags & ASN1_STRING_FLAG_EMBED);
ossl_asn1_string_free_internal(a, 0, a->flags & ASN1_STRING_FLAG_EMBED);
}
void ASN1_STRING_clear_free(ASN1_STRING *a)
{
if (a == NULL)
return;
if (a->data && !(a->flags & ASN1_STRING_FLAG_NDEF))
OPENSSL_cleanse(a->data, a->length);
ASN1_STRING_free(a);
ossl_asn1_string_free_internal(a, 1, a->flags & ASN1_STRING_FLAG_EMBED);
}
int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b)

View file

@ -50,7 +50,7 @@ DEFINE_STACK_OF(MIME_PARAM)
typedef struct mime_header_st MIME_HEADER;
DEFINE_STACK_OF(MIME_HEADER)
void ossl_asn1_string_embed_free(ASN1_STRING *a, int embed);
void ossl_asn1_string_free_internal(ASN1_STRING *a, int clear, int embed);
int ossl_asn1_get_choice_selector(ASN1_VALUE **pval, const ASN1_ITEM *it);
int ossl_asn1_get_choice_selector_const(const ASN1_VALUE **pval,

View file

@ -205,7 +205,7 @@ void ossl_asn1_primitive_free(ASN1_VALUE **pval, const ASN1_ITEM *it, int embed)
break;
default:
ossl_asn1_string_embed_free((ASN1_STRING *)*pval, embed);
ossl_asn1_string_free_internal((ASN1_STRING *)*pval, 0, embed);
break;
}
*pval = NULL;

View file

@ -2,7 +2,8 @@
=head1 NAME
ASN1_STRING_new, ASN1_STRING_type_new, ASN1_STRING_free -
ASN1_STRING_new, ASN1_STRING_type_new, ASN1_STRING_free,
ASN1_STRING_new_not_owned -
ASN1_STRING allocation functions
=head1 SYNOPSIS
@ -11,6 +12,7 @@ ASN1_STRING allocation functions
ASN1_STRING *ASN1_STRING_new(void);
ASN1_STRING *ASN1_STRING_type_new(int type);
ASN1_STRING *ASN1_STRING_new_not_owned(int type, const uint8_t *data, size_t length);
void ASN1_STRING_free(ASN1_STRING *a);
=head1 DESCRIPTION
@ -21,6 +23,15 @@ is undefined.
ASN1_STRING_type_new() returns an allocated B<ASN1_STRING> structure of
type I<type>.
ASN1_STRING_new_not_owned() returns an allocated B<ASN1_STRING>
structure of type I<type>, and sets the data returned string to the
bytes at I<data> of length I<len>. Ownership of I<data> is not
transferred, and it is the caller's responsibility to ensure that
I<data> outlives any successfully returned result. The provided I<type>
must not be V_ASN1_BIT_STRING, the provided I<length> must be greater
than 0, and I<data> must not be NULL. It is an error if the provided
data size exceeds the internal limit of the ASN1_STRING implementation.
ASN1_STRING_free() frees up I<a>.
If I<a> is NULL nothing is done.
@ -29,10 +40,16 @@ If I<a> is NULL nothing is done.
Other string types call the B<ASN1_STRING> functions. For example
ASN1_OCTET_STRING_new() calls ASN1_STRING_type_new(V_ASN1_OCTET_STRING).
ASN1_STRING_new_not_owned() does not automatically call strlen()
to determine a length, or guarantee that the data is a C string. The
data contained in the string and the lifetime of it are the
responsibility of the caller.
=head1 RETURN VALUES
ASN1_STRING_new() and ASN1_STRING_type_new() return a valid
B<ASN1_STRING> structure or NULL if an error occurred.
ASN1_STRING_new(), ASN1_STRING_type_new(), and
ASN1_STRING_new_not_owned() return a valid B<ASN1_STRING>
structure or NULL if an error occurred.
ASN1_STRING_free() does not return a value.
@ -40,6 +57,10 @@ ASN1_STRING_free() does not return a value.
L<ERR_get_error(3)>
=head1 HISTORY
ASN1_STRING_new_not_owned() was added in OpenSSL 4.1.
=head1 COPYRIGHT
Copyright 2002-2023 The OpenSSL Project Authors. All Rights Reserved.

View file

@ -43,6 +43,9 @@
/* String is embedded and only content should be freed */
#define ASN1_STRING_FLAG_EMBED 0x080
/* Data is static and should not be freed. */
#define ASN1_STRING_FLAG_DATA_NOT_OWNED 0x100
/* This is the base type that holds just about everything :-) */
struct asn1_string_st {
int length;
@ -183,6 +186,7 @@ EVP_PKEY *ossl_d2i_PrivateKey_legacy(int keytype, EVP_PKEY **a,
OSSL_LIB_CTX *libctx, const char *propq);
X509_ALGOR *ossl_X509_ALGOR_from_nid(int nid, int ptype, void *pval);
void ossl_asn1_bit_string_clear_unused_bits(ASN1_STRING *str);
void ossl_asn1_bit_string_set_unused_bits(ASN1_STRING *str, unsigned int num);
int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in,

View file

@ -530,6 +530,8 @@ void ASN1_STRING_clear_free(ASN1_STRING *a);
int ASN1_STRING_copy(ASN1_STRING *dst, const ASN1_STRING *str);
DECLARE_ASN1_DUP_FUNCTION(ASN1_STRING)
ASN1_STRING *ASN1_STRING_type_new(int type);
ASN1_STRING *ASN1_STRING_new_not_owned(int type, const uint8_t *data,
size_t length);
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,

View file

@ -389,9 +389,96 @@ asn1_bit_string_set1_test(int idx)
return abs_set1_test(abs_set1_tests, idx);
}
static int
asn1_string_new_not_owned_test(void)
{
int success = 0;
ASN1_STRING *tmp = NULL;
char *tmpstring = NULL;
static const uint8_t data[] = { 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b };
static const uint8_t data2[] = { 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b };
if (!TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, sizeof(data))))
goto err;
ASN1_STRING_clear_free(tmp);
tmp = NULL;
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
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"))))
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")))
goto err;
if (!TEST_mem_eq(ASN1_STRING_get0_data(tmp), strlen("muppet"), "muppet", strlen("muppet")))
goto err;
ASN1_STRING_clear_free(tmp);
tmp = NULL;
if (!TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, sizeof(data))))
goto err;
if (!TEST_ptr(tmpstring = strdup("puppet")))
goto err;
ASN1_STRING_set0(tmp, tmpstring, 4);
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
if (!TEST_int_eq(ASN1_STRING_length(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));
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
if (!TEST_mem_eq(tmpstring, strlen("puppet"), "zzzzet", strlen("puppet")))
goto err;
ASN1_STRING_clear_free(tmp);
tmpstring = NULL;
tmp = NULL;
if (TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_BIT_STRING, data, sizeof(data))))
goto err;
if (TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, NULL, sizeof(data))))
goto err;
if (TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, 0)))
goto err;
if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
goto err;
success = 1;
err:
ASN1_STRING_clear_free(tmp);
free(tmpstring);
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);
return 1;
}

View file

@ -5721,3 +5721,4 @@ CRYPTO_atomic_store_ptr ? 4_1_0 EXIST::FUNCTION:
CRYPTO_atomic_cmp_exch_ptr ? 4_1_0 EXIST::FUNCTION:
EVP_EC_affine2oct ? 4_1_0 EXIST::FUNCTION:
OPENSSL_sk_set_copy_thunks ? 4_1_0 EXIST::FUNCTION:
ASN1_STRING_new_not_owned ? 4_1_0 EXIST::FUNCTION: