]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Some more X509 extension add/del polish
authorViktor Dukhovni <openssl-users@dukhovni.org>
Tue, 3 Mar 2026 14:35:36 +0000 (01:35 +1100)
committerTomas Mraz <tomas@openssl.org>
Thu, 5 Mar 2026 17:39:54 +0000 (18:39 +0100)
- 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)

crypto/ocsp/ocsp_ext.c
crypto/ts/ts_req_utils.c
crypto/ts/ts_rsp_utils.c
crypto/x509/v3_conf.c
crypto/x509/x509_ext.c
crypto/x509/x509_v3.c
doc/man3/X509v3_get_ext_by_NID.pod
include/openssl/x509.h.in
test/x509_test.c
util/libcrypto.num

index 81cc51fdc95fc63ba63156aa63553cab25e34cf2..4142d0b029160b913ded246f32ce4363b9109b07 100644 (file)
@@ -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,
index a8818d56d6a2c3d07dd45c287ae6a8c052fc7fce..00a1a681afd80d2c384337d8b91035b5ab7a5841 100644 (file)
@@ -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)
index c83bedf79f4dc4ad99f149360d12f844acad830f..81b5e899c745b4451d789ab10bd52de3d4c6e035 100644 (file)
@@ -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)
index 6db7f37d6852b9acfb9c432e434d8607de2c006e..848a4895c33a2bd44d9cb68b06ffe0f50ddc2f72 100644 (file)
@@ -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;
index 2b73e14a60d82dd579fadf54008ce5035c3508f1..7f23f3a666b357fb5c84ebc2a08370b15ed3a008 100644 (file)
@@ -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;
index f9ea5f75fe92dcc8a871f8d815ee29cd2e9ba8de..60b18d2d139726d88dc78a2a4e7d340404081fb6 100644 (file)
@@ -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)
 {
index d9e4257940ad6ae631e6cd9b631cd4c5943045de..298afdbe57123102c84b1a5878f10213dcd1ce54 100644 (file)
@@ -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.
index 84d65269cc847ab9a07ef67827e842228943e8f3..578df3fa6cd71e51ec16ddaf60b479cf3e30c18e 100644 (file)
@@ -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,
index 1381a1d33d5059d5d0c9f5d9bb9cdc0b763bab27..30f477065085fe301bb5b85f10b7c060ba07f672 100644 (file)
@@ -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);
index b694a335c5b7a5e004b669a2cbe4afa1ed115129..b8dcd55a86220eb602dc4333de0ddefc9aad7cbd 100644 (file)
@@ -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: