Commit 68c0321e90 for openssl.org
commit 68c0321e90d015ebe851b33761c0acdbaebc12bd
Author: Bob Beck <beck@openssl.org>
Date: Fri Apr 24 14:28:54 2026 -0600
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)
diff --git a/CHANGES.md b/CHANGES.md
index 03c6d76033..08bf6045bb 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -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*
diff --git a/crypto/asn1/a_bitstr.c b/crypto/asn1/a_bitstr.c
index 9976cc478e..914ff98400 100644
--- a/crypto/asn1/a_bitstr.c
+++ b/crypto/asn1/a_bitstr.c
@@ -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;
diff --git a/crypto/asn1/asn1_lib.c b/crypto/asn1/asn1_lib.c
index 28898b49ff..99903564e2 100644
--- a/crypto/asn1/asn1_lib.c
+++ b/crypto/asn1/asn1_lib.c
@@ -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)
diff --git a/crypto/asn1/asn1_local.h b/crypto/asn1/asn1_local.h
index 3f230683d7..46d7aca7a2 100644
--- a/crypto/asn1/asn1_local.h
+++ b/crypto/asn1/asn1_local.h
@@ -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,
diff --git a/crypto/asn1/tasn_fre.c b/crypto/asn1/tasn_fre.c
index f8068832ab..df24111578 100644
--- a/crypto/asn1/tasn_fre.c
+++ b/crypto/asn1/tasn_fre.c
@@ -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;
diff --git a/doc/man3/ASN1_STRING_new.pod b/doc/man3/ASN1_STRING_new.pod
index 642b6f4777..7698f7411e 100644
--- a/doc/man3/ASN1_STRING_new.pod
+++ b/doc/man3/ASN1_STRING_new.pod
@@ -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.
diff --git a/include/crypto/asn1.h b/include/crypto/asn1.h
index f7dc7852f6..bc556588d0 100644
--- a/include/crypto/asn1.h
+++ b/include/crypto/asn1.h
@@ -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,
diff --git a/include/openssl/asn1.h.in b/include/openssl/asn1.h.in
index cce56701c9..21d9e772c6 100644
--- a/include/openssl/asn1.h.in
+++ b/include/openssl/asn1.h.in
@@ -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,
diff --git a/test/asn1_string_test.c b/test/asn1_string_test.c
index d64f54da5e..baf5525b38 100644
--- a/test/asn1_string_test.c
+++ b/test/asn1_string_test.c
@@ -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;
}
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 659eb5cbf4..777dfbf70e 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -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: