]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Provide ASN1_STRING_new_not_owned()
authorBob Beck <beck@openssl.org>
Fri, 24 Apr 2026 20:28:54 +0000 (14:28 -0600)
committerNorbert Pocs <norbertp@openssl.org>
Wed, 3 Jun 2026 11:41:32 +0000 (13:41 +0200)
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)

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

index 03c6d760333b761b647fefaa1796a2e54e2d0aa3..08bf6045bb763250d59cd15786f0758bf99cb135 100644 (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*
index 9976cc478e6f3e99087c1c3e5c7c5d1152049615..914ff98400617c979cccdcc2a911736a56a20bfb 100644 (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;
index 28898b49fff20b9a89a1371d37e2b064365fd5f7..99903564e25b213f6759ea4371226548f6b2c56e 100644 (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)
index 3f230683d72f20bf348633c328aa0228c78612d3..46d7aca7a20e69029695e548c1b894e80445a102 100644 (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,
index f8068832ab674f7239c1494ccdf02b5607b63aa6..df2411157802c264bec0e57d942ed08372b61870 100644 (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;
index 642b6f47777b675ebc72b7951ee633bd2b7c53a4..7698f7411e38055aeba611ceaa7efbd46bf539d6 100644 (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.
index f7dc7852f62381dc9a18fd378b3b72d3679c8e29..bc556588d0c9fcc4f09549135111589d4af75ff7 100644 (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,
index cce56701c93e4a65a601ff914edd1b9b271ed493..21d9e772c6761d08b76f059eabef41cf32424953 100644 (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,
index d64f54da5ef44c405abdcc199bb2dbb065bffc05..baf5525b38623d8d564661630b02021a3893cd2f 100644 (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;
 }
index 659eb5cbf45c4504a84bc59dfb4f9c451204eaa4..777dfbf70e7b9d5dbd0e952dd1ebc4fe018ed6bd 100644 (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: