Commit bde5f49e9a for openssl.org
commit bde5f49e9a9a7bc0f825df42f5e3796bbecd48d0
Author: Viktor Dukhovni <openssl-users@dukhovni.org>
Date: Wed Mar 4 01:35:36 2026 +1100
Some more X509 extension add/del polish
- In various structures with optional X.509 extensions, deallocate and
NULL out the extensions pointer when the extensions become empty after
an extension is deleted. This uses a new X509v3_delete_extension()
helper function. Added corresponding docs.
- Do the same in X509V3_EXT_add_nconf_sk() if after processing all
the pending updates the stack becomes empty.
- Handle resulting NULL stack in X509V3_EXT_REQ_add_nconf() and
update_req_extensions().
- Improved testing of certificate SKID/AKID addition and implicit
removal via "none" value.
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
MergeDate: Thu Mar 5 17:40:10 2026
(Merged from https://github.com/openssl/openssl/pull/30252)
diff --git a/crypto/ocsp/ocsp_ext.c b/crypto/ocsp/ocsp_ext.c
index 81cc51fdc9..4142d0b029 100644
--- a/crypto/ocsp/ocsp_ext.c
+++ b/crypto/ocsp/ocsp_ext.c
@@ -48,7 +48,7 @@ const X509_EXTENSION *OCSP_REQUEST_get_ext(OCSP_REQUEST *x, int loc)
X509_EXTENSION *OCSP_REQUEST_delete_ext(OCSP_REQUEST *x, int loc)
{
- return X509v3_delete_ext(x->tbsRequest.requestExtensions, loc);
+ return X509v3_delete_extension(&x->tbsRequest.requestExtensions, loc);
}
void *OCSP_REQUEST_get1_ext_d2i(OCSP_REQUEST *x, int nid, int *crit, int *idx)
@@ -98,7 +98,7 @@ const X509_EXTENSION *OCSP_ONEREQ_get_ext(OCSP_ONEREQ *x, int loc)
X509_EXTENSION *OCSP_ONEREQ_delete_ext(OCSP_ONEREQ *x, int loc)
{
- return X509v3_delete_ext(x->singleRequestExtensions, loc);
+ return X509v3_delete_extension(&x->singleRequestExtensions, loc);
}
void *OCSP_ONEREQ_get1_ext_d2i(OCSP_ONEREQ *x, int nid, int *crit, int *idx)
@@ -149,7 +149,7 @@ const X509_EXTENSION *OCSP_BASICRESP_get_ext(OCSP_BASICRESP *x, int loc)
X509_EXTENSION *OCSP_BASICRESP_delete_ext(OCSP_BASICRESP *x, int loc)
{
- return X509v3_delete_ext(x->tbsResponseData.responseExtensions, loc);
+ return X509v3_delete_extension(&x->tbsResponseData.responseExtensions, loc);
}
void *OCSP_BASICRESP_get1_ext_d2i(OCSP_BASICRESP *x, int nid, int *crit,
@@ -203,7 +203,7 @@ const X509_EXTENSION *OCSP_SINGLERESP_get_ext(OCSP_SINGLERESP *x, int loc)
X509_EXTENSION *OCSP_SINGLERESP_delete_ext(OCSP_SINGLERESP *x, int loc)
{
- return X509v3_delete_ext(x->singleExtensions, loc);
+ return X509v3_delete_extension(&x->singleExtensions, loc);
}
void *OCSP_SINGLERESP_get1_ext_d2i(OCSP_SINGLERESP *x, int nid, int *crit,
diff --git a/crypto/ts/ts_req_utils.c b/crypto/ts/ts_req_utils.c
index a8818d56d6..00a1a681af 100644
--- a/crypto/ts/ts_req_utils.c
+++ b/crypto/ts/ts_req_utils.c
@@ -169,7 +169,7 @@ const X509_EXTENSION *TS_REQ_get_ext(TS_REQ *a, int loc)
X509_EXTENSION *TS_REQ_delete_ext(TS_REQ *a, int loc)
{
- return X509v3_delete_ext(a->extensions, loc);
+ return X509v3_delete_extension(&a->extensions, loc);
}
int TS_REQ_add_ext(TS_REQ *a, X509_EXTENSION *ex, int loc)
diff --git a/crypto/ts/ts_rsp_utils.c b/crypto/ts/ts_rsp_utils.c
index c83bedf79f..81b5e899c7 100644
--- a/crypto/ts/ts_rsp_utils.c
+++ b/crypto/ts/ts_rsp_utils.c
@@ -330,7 +330,7 @@ const X509_EXTENSION *TS_TST_INFO_get_ext(TS_TST_INFO *a, int loc)
X509_EXTENSION *TS_TST_INFO_delete_ext(TS_TST_INFO *a, int loc)
{
- return X509v3_delete_ext(a->extensions, loc);
+ return X509v3_delete_extension(&a->extensions, loc);
}
int TS_TST_INFO_add_ext(TS_TST_INFO *a, X509_EXTENSION *ex, int loc)
diff --git a/crypto/x509/v3_conf.c b/crypto/x509/v3_conf.c
index 6db7f37d68..848a4895c3 100644
--- a/crypto/x509/v3_conf.c
+++ b/crypto/x509/v3_conf.c
@@ -346,6 +346,10 @@ int X509V3_EXT_add_nconf_sk(CONF *conf, X509V3_CTX *ctx, const char *section,
}
X509_EXTENSION_free(ext);
}
+ if (sk != NULL && sk_X509_EXTENSION_num(*sk) == 0) {
+ sk_X509_EXTENSION_free(*sk);
+ *sk = NULL;
+ }
return 1;
}
@@ -357,6 +361,7 @@ int X509V3_EXT_add_nconf(CONF *conf, X509V3_CTX *ctx, const char *section,
X509 *cert)
{
STACK_OF(X509_EXTENSION) **sk = NULL;
+
if (cert != NULL)
sk = &cert->cert_info.extensions;
return X509V3_EXT_add_nconf_sk(conf, ctx, section, sk);
@@ -379,17 +384,19 @@ static int
update_req_extensions(X509_REQ *req, int *pnid, STACK_OF(X509_EXTENSION) *exts)
{
unsigned char *ext = NULL;
- int ret = 0, loc = -1, extlen;
+ int ret = 0, loc = -1, extlen = 0;
if (pnid == NULL || *pnid == NID_undef)
if ((pnid = X509_REQ_get_extension_nids()) == NULL)
return 0;
loc = X509at_get_attr_by_NID(req->req_info.attributes, *pnid, -1);
- extlen = ASN1_item_i2d((const ASN1_VALUE *)exts,
- &ext, ASN1_ITEM_rptr(X509_EXTENSIONS));
- if (extlen <= 0)
- return ret;
+ if (exts != NULL) {
+ extlen = ASN1_item_i2d((const ASN1_VALUE *)exts,
+ &ext, ASN1_ITEM_rptr(X509_EXTENSIONS));
+ if (extlen <= 0)
+ return ret;
+ }
if (loc != -1) {
X509_ATTRIBUTE *att = X509at_delete_attr(req->req_info.attributes, loc);
@@ -433,7 +440,7 @@ int X509V3_EXT_REQ_add_nconf(CONF *conf, X509V3_CTX *ctx, const char *section,
ret = X509V3_EXT_add_nconf_sk(conf, ctx, section, &exts);
/* Replace original extension list (stack) with updated stack */
- if (ret && req != NULL && exts != NULL)
+ if (ret && req != NULL)
ret = update_req_extensions(req, pnid, exts);
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
return ret;
diff --git a/crypto/x509/x509_ext.c b/crypto/x509/x509_ext.c
index 2b73e14a60..7f23f3a666 100644
--- a/crypto/x509/x509_ext.c
+++ b/crypto/x509/x509_ext.c
@@ -104,32 +104,28 @@ X509_EXTENSION *X509_delete_ext(X509 *x, int loc)
{
X509_EXTENSION *ret;
- if (x->cert_info.extensions == NULL)
- return NULL;
- if ((ret = delete_ext(&x->cert_info.extensions, loc)) != NULL)
+ ret = X509v3_delete_extension(&x->cert_info.extensions, loc);
+ if (ret != NULL)
x->cert_info.enc.modified = 1;
return ret;
}
int X509_add_ext(X509 *x, const X509_EXTENSION *ex, int loc)
{
- STACK_OF(X509_EXTENSION) *exts = x->cert_info.extensions;
+ STACK_OF(X509_EXTENSION) **exts = &x->cert_info.extensions;
- if (X509v3_add_ext(&exts, ex, loc) == NULL)
+ /* x->cert_info.extensions might initially be NULL */
+ if (X509v3_add_ext(exts, ex, loc) == NULL)
return 0;
/*
- * A duplicate empty SKID/AKID extension can displace a prior non-empty
- * one, but is then not itself added, so, somewhat counter-intutively, the
- * the extension list can become empty after an "add", in which case we must
- * drop the extension stack entirely, setting it to NULL. The extensions
- * list is either non-empty or absent.
+ * An ignored "empty" SKID or AKID extension will appear to be successfully
+ * added, even though nothing is pushed onto the resulting stack. However,
+ * if the stack was initially NULL or empty, it will now be non-NULL, but
+ * empty, deallocate and make it NULL in that case.
*/
- if (sk_X509_EXTENSION_num(exts) != 0) {
- x->cert_info.extensions = exts;
- } else {
- sk_X509_EXTENSION_free(exts);
- sk_X509_EXTENSION_pop_free(x->cert_info.extensions, X509_EXTENSION_free);
- x->cert_info.extensions = NULL;
+ if (sk_X509_EXTENSION_num(*exts) == 0) {
+ sk_X509_EXTENSION_free(*exts);
+ *exts = NULL;
}
x->cert_info.enc.modified = 1;
return 1;
diff --git a/crypto/x509/x509_v3.c b/crypto/x509/x509_v3.c
index f9ea5f75fe..60b18d2d13 100644
--- a/crypto/x509/x509_v3.c
+++ b/crypto/x509/x509_v3.c
@@ -100,6 +100,22 @@ X509_EXTENSION *X509v3_delete_ext(STACK_OF(X509_EXTENSION) *x, int loc)
return ret;
}
+X509_EXTENSION *X509v3_delete_extension(STACK_OF(X509_EXTENSION) **x, int loc)
+{
+ X509_EXTENSION *ext;
+
+ if (x == NULL)
+ return NULL;
+
+ /* Set extensions to NULL when last element dropped */
+ if ((ext = X509v3_delete_ext(*x, loc)) != NULL
+ && sk_X509_EXTENSION_num(*x) == 0) {
+ sk_X509_EXTENSION_free(*x);
+ *x = NULL;
+ }
+ return ext;
+}
+
STACK_OF(X509_EXTENSION) *X509v3_add_ext(STACK_OF(X509_EXTENSION) **x,
const X509_EXTENSION *ex, int loc)
{
diff --git a/doc/man3/X509v3_get_ext_by_NID.pod b/doc/man3/X509v3_get_ext_by_NID.pod
index d9e4257940..298afdbe57 100644
--- a/doc/man3/X509v3_get_ext_by_NID.pod
+++ b/doc/man3/X509v3_get_ext_by_NID.pod
@@ -4,14 +4,15 @@
X509v3_get_ext_count, X509v3_get_ext, X509v3_get_ext_by_NID,
X509v3_get_ext_by_OBJ, X509v3_get_ext_by_critical, X509v3_delete_ext,
-X509v3_add_ext, X509v3_add_extensions, X509_get_ext_count, X509_get_ext,
-X509_get_ext_by_NID, X509_get_ext_by_OBJ, X509_get_ext_by_critical,
-X509_delete_ext, X509_add_ext, X509_CRL_get_ext_count, X509_CRL_get_ext,
-X509_CRL_get_ext_by_NID, X509_CRL_get_ext_by_OBJ, X509_CRL_get_ext_by_critical,
-X509_CRL_delete_ext, X509_CRL_add_ext, X509_REVOKED_get_ext_count,
-X509_REVOKED_get_ext, X509_REVOKED_get_ext_by_NID, X509_REVOKED_get_ext_by_OBJ,
-X509_REVOKED_get_ext_by_critical, X509_REVOKED_delete_ext,
-X509_REVOKED_add_ext - extension stack utility functions
+X509v3_delete_extension, X509v3_add_ext, X509v3_add_extensions,
+X509_get_ext_count, X509_get_ext, X509_get_ext_by_NID, X509_get_ext_by_OBJ,
+X509_get_ext_by_critical, X509_delete_ext, X509_add_ext,
+X509_CRL_get_ext_count, X509_CRL_get_ext, X509_CRL_get_ext_by_NID,
+X509_CRL_get_ext_by_OBJ, X509_CRL_get_ext_by_critical, X509_CRL_delete_ext,
+X509_CRL_add_ext, X509_REVOKED_get_ext_count, X509_REVOKED_get_ext,
+X509_REVOKED_get_ext_by_NID, X509_REVOKED_get_ext_by_OBJ,
+X509_REVOKED_get_ext_by_critical, X509_REVOKED_delete_ext, X509_REVOKED_add_ext
+- extension stack utility functions
=head1 SYNOPSIS
@@ -27,6 +28,7 @@ X509_REVOKED_add_ext - extension stack utility functions
int X509v3_get_ext_by_critical(const STACK_OF(X509_EXTENSION) *x,
int crit, int lastpos);
X509_EXTENSION *X509v3_delete_ext(STACK_OF(X509_EXTENSION) *x, int loc);
+ X509_EXTENSION *X509v3_delete_extension(STACK_OF(X509_EXTENSION) **x, int loc);
STACK_OF(X509_EXTENSION) *X509v3_add_ext(STACK_OF(X509_EXTENSION) **x,
X509_EXTENSION *ex, int loc);
STACK_OF(X509_EXTENSION)
@@ -82,6 +84,12 @@ X509v3_delete_ext() deletes the extension with index I<loc> from I<x>.
The deleted extension is returned and must be freed by the caller.
If I<loc> is an invalid index value, NULL is returned.
+X509v3_delete_extension() extends X509v3_delete_ext() by deallocating the
+extension stack I<*x> if it becomes empty, and in that case also setting I<*x>
+to NULL.
+This is a convenience wrapper for cases in which extensions are optional and
+should be omitted if the stack becomes empty.
+
X509v3_add_ext() inserts extension I<ex> to STACK I<*x> at position I<loc>.
If I<loc> is -1, the new extension is added to the end.
A new STACK is allocated if I<*x> is NULL.
@@ -140,8 +148,9 @@ that case.
X509v3_get_ext_count() returns the extension count or 0 for failure.
-X509v3_get_ext(), X509v3_delete_ext() and X509_delete_ext() return an
-B<X509_EXTENSION> structure or NULL if an error occurs.
+X509v3_get_ext(), X509v3_delete_ext(), X509v3_delete_extension() and
+X509_delete_ext() return an B<X509_EXTENSION> structure or NULL if an error
+occurs.
X509v3_get_ext_by_OBJ() and X509v3_get_ext_by_critical() return
the extension index or -1 if an error occurs.
@@ -164,6 +173,8 @@ L<X509V3_get_d2i(3)>
X509v3_add_extensions() was added in OpenSSL 3.4.
+X509v3_delete_extension() was added in OpenSSL 4.0.
+
=head1 COPYRIGHT
Copyright 2015-2024 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/openssl/x509.h.in b/include/openssl/x509.h.in
index 84d65269cc..578df3fa6c 100644
--- a/include/openssl/x509.h.in
+++ b/include/openssl/x509.h.in
@@ -905,6 +905,7 @@ int X509v3_get_ext_by_critical(const STACK_OF(X509_EXTENSION) *x,
int crit, int lastpos);
const X509_EXTENSION *X509v3_get_ext(const STACK_OF(X509_EXTENSION) *x, int loc);
X509_EXTENSION *X509v3_delete_ext(STACK_OF(X509_EXTENSION) *x, int loc);
+X509_EXTENSION *X509v3_delete_extension(STACK_OF(X509_EXTENSION) **x, int loc);
STACK_OF(X509_EXTENSION) *X509v3_add_ext(STACK_OF(X509_EXTENSION) **x,
const X509_EXTENSION *ex, int loc);
STACK_OF(X509_EXTENSION) *X509v3_add_extensions(STACK_OF(X509_EXTENSION) **target,
diff --git a/test/x509_test.c b/test/x509_test.c
index 1381a1d33d..30f4770650 100644
--- a/test/x509_test.c
+++ b/test/x509_test.c
@@ -288,10 +288,13 @@ err:
static int test_drop_empty_cert_keyids(void)
{
static const unsigned char commonName[] = "test";
+ BIO *bio = NULL;
+ CONF *conf = NULL;
X509 *x = NULL;
X509_NAME *subject = NULL;
X509_NAME_ENTRY *name_entry = NULL;
X509_EXTENSION *ext = NULL;
+ const STACK_OF(X509_EXTENSION) *exts;
X509V3_CTX ctx;
int ret = 0;
@@ -312,23 +315,59 @@ static int test_drop_empty_cert_keyids(void)
|| !TEST_int_eq(X509_set_pubkey(x, pubkey), 1))
goto err;
- X509V3_set_ctx(&ctx, x, x, NULL, NULL, 0);
- if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "subjectKeyIdentifier",
- "none"))
+ /*
+ * Check that X509_add_ext() does not create non-NULL empty stack when
+ * adding an ignored extension (from initial NULL state).
+ */
+ X509V3_set_ctx(&ctx, x, x, NULL, NULL, X509V3_CTX_REPLACE);
+ if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "subjectKeyIdentifier", "none"))
|| !TEST_int_eq(X509_add_ext(x, ext, -1), 1)
|| !TEST_ptr_null(X509_get0_extensions(x)))
goto err;
+ /* Add non-empty SKID */
+ if (!TEST_ptr(bio = BIO_new(BIO_s_mem()))
+ || !TEST_int_ge(BIO_printf(bio, "subjectKeyIdentifier = hash\n"), 0)
+ || !TEST_ptr(conf = NCONF_new(NULL))
+ || !TEST_int_gt(NCONF_load_bio(conf, bio, NULL), 0))
+ goto err;
+ (void)BIO_reset(bio);
+
+ X509V3_set_nconf(&ctx, conf);
+ if (!TEST_true(X509V3_EXT_add_nconf(conf, &ctx, "default", x))
+ || !TEST_ptr(exts = X509_get0_extensions(x))
+ || !TEST_int_eq(sk_X509_EXTENSION_num(exts), 1))
+ goto err;
+
+ /* Request "empty" SKID and AKID in order to drop any previous values */
+ NCONF_free(conf);
+ if (!TEST_ptr(conf = NCONF_new(NULL))
+ || !TEST_int_ge(BIO_printf(bio, "subjectKeyIdentifier = none\n"), 0)
+ || !TEST_int_gt(NCONF_load_bio(conf, bio, NULL), 0))
+ goto err;
+
+ X509V3_set_nconf(&ctx, conf);
+ if (!TEST_true(X509V3_EXT_add_nconf(conf, &ctx, "default", x))
+ || !TEST_int_gt(X509_sign(x, privkey, signmd), 0)
+ || !TEST_ptr_null(X509_get0_extensions(x)))
+ goto err;
+
+ /*
+ * Now check that a non-empty extension is actually added via
+ * X509_add_ext().
+ */
X509_EXTENSION_free(ext);
- if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "authorityKeyIdentifier",
- "none"))
+ if (!TEST_ptr(ext = X509V3_EXT_conf(NULL, &ctx, "subjectKeyIdentifier", "hash"))
|| !TEST_int_eq(X509_add_ext(x, ext, -1), 1)
- || !TEST_ptr_null(X509_get0_extensions(x))
- || !TEST_int_gt(X509_sign(x, privkey, signmd), 0))
+ || !TEST_int_gt(X509_sign(x, privkey, signmd), 0)
+ || !TEST_ptr(exts = X509_get0_extensions(x))
+ || !TEST_int_eq(sk_X509_EXTENSION_num(exts), 1))
goto err;
ret = 1;
err:
+ BIO_free(bio);
+ NCONF_free(conf);
X509_NAME_ENTRY_free(name_entry);
X509_NAME_free(subject);
X509_EXTENSION_free(ext);
diff --git a/util/libcrypto.num b/util/libcrypto.num
index b694a335c5..b8dcd55a86 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5709,6 +5709,7 @@ 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:
OSSL_ESS_check_signing_certs_ex ? 4_0_0 EXIST::FUNCTION:
+X509v3_delete_extension ? 4_0_0 EXIST::FUNCTION:
X509_new ? 4_0_0 EXIST::FUNCTION:
X509_free ? 4_0_0 EXIST::FUNCTION:
d2i_X509 ? 4_0_0 EXIST::FUNCTION: