Просмотр исходного кода

Issue 6951 - Dynamic Certificate refresh phase 1 - Search support (#7117)

First phase of Dynamic Certificate Refresh

Implement a new backend handling cn=dynamiccertificates suffix
and the code supporting search request to list and show nss db certificates

issue: #6951

Reviewed by: @vashirov (Thanks!)

* Dynamic Certificate - Phase1- Search

* Fix sourcery-ai remarks

* Minor changes

* Fix broken search and memory leaks

Co-authored-by: Viktor Ashirov <[email protected]>
progier389 4 месяцев назад
Родитель
Сommit
337e2c905a

+ 1 - 0
Makefile.am

@@ -1106,6 +1106,7 @@ libslapd_la_SOURCES = ldap/servers/slapd/add.c \
 	ldap/servers/slapd/dn.c \
 	ldap/servers/slapd/dse.c \
 	ldap/servers/slapd/dynalib.c \
+	ldap/servers/slapd/dyncerts.c \
 	ldap/servers/slapd/entry.c \
 	ldap/servers/slapd/entrywsi.c \
 	ldap/servers/slapd/errormap.c \

+ 918 - 0
ldap/servers/slapd/dyncerts.c

@@ -0,0 +1,918 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright (C) 2025 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * License: GPL (version 3 or any later version).
+ * See LICENSE for details.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*
+ * dyncerts.c - Dynamic Certificates Handler
+ *
+ */
+
+#include "slap.h"
+#include "fe.h"
+#include <nss.h>
+#include <nss3/pk11pub.h>
+#include <nss3/certdb.h>
+#include "svrcore.h"
+#include "dyncerts.h"
+
+#ifdef DEBUG
+#define SLAPI_LOG_DYC SLAPI_LOG_INFO
+#else
+#define SLAPI_LOG_DYC SLAPI_LOG_TRACE
+#endif
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct slapdplugin dyncerts_plugin = {0};
+static struct dyncerts pdyncerts;
+
+
+/*
+ * dsDynamicCertificateSwitchInProgress is always seen as FALSE
+ * Because accept and listening thread are blocked
+ * when it is switched to TRUE (so it cannot be queried)
+ */ 
+static const char *dyncerts_baseentry_str =
+    "dn: " DYNCERTS_SUFFIX "\n"
+    "objectclass: top\n"
+    "objectclass: nsContainer\n"
+    "objectclass: extensibleObject\n"
+    "dsDynamicCertificateSwitchInProgress: FALSE\n"
+    "cn: " DYNCERTS_CN "\n";
+
+static const struct trust_flags_mask trust_flags[] = {
+    { CERTDB_VALID_CA, CERTDB_TRUSTED_CA|CERTDB_TRUSTED_CLIENT_CA, 'c' },
+    { CERTDB_TERMINAL_RECORD, CERTDB_TRUSTED, 'p' },
+    { CERTDB_TRUSTED_CA, 0, 'C' },
+    { CERTDB_TRUSTED_CLIENT_CA, 0, 'T' },
+    { CERTDB_TRUSTED, 0, 'P' },
+    { CERTDB_USER, 0, 'u' },
+    { CERTDB_SEND_WARN, 0, 'w' },
+    { CERTDB_INVISIBLE_CA, 0, 'I' },
+    { CERTDB_GOVT_APPROVED_CA, 0, 'G' },
+    { 0 }
+};
+
+/* Some forward definitions */
+static DCSS *get_entry_list(const Slapi_DN *basesn, int scope, char **attrs);
+int dyncerts_apply_cb(const char *nickname, dyc_action_cb_t cb, void *arg, char *errmsg);
+
+/* Alloc a search set */
+static DCSS *
+ss_new()
+{
+    return (DCSS *)slapi_ch_calloc(1, sizeof (DCSS));
+}
+
+/* Free a parent search set */
+static void
+ss_destroy(DCSS **pss)
+{
+    DCSS *ss = *pss;
+    if (ss) {
+        for (size_t idx=0; idx<ss->nb_entries; idx++) {
+            slapi_entry_free(ss->entries[idx]);
+        }
+        slapi_ch_free((void**)&ss->entries);
+    }
+    slapi_ch_free((void**)pss);
+}
+
+/* Add an entry into search set */
+static void
+ss_add_entry(DCSS *ss, Slapi_Entry *e)
+{
+    if (e == NULL) {
+        return;
+    }
+    if (ss->nb_entries >= ss->max_entries) {
+        if (ss->max_entries == 0) {
+            ss->max_entries = 4;
+        } else {
+            ss->max_entries *= 2;
+        }
+        ss->entries = (Slapi_Entry **)slapi_ch_realloc((void*)(ss->entries), (sizeof (Slapi_Entry*)) * ss->max_entries);
+    }
+    ss->entries[ss->nb_entries++] = e;
+}
+
+/* Backend callback (operation not allowed) */
+static int
+be_unwillingtoperform(Slapi_PBlock *pb)
+{
+    send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Operation not allowed on Dynamic Certificate Entry", 0, NULL);
+    return -1;
+}
+
+/* Get instance config data and store them in private data */
+static void
+read_config_info()
+{
+    if (pdyncerts.config == NULL) {
+        DCSS *ss = ss_new();
+        Slapi_PBlock *pb = NULL;
+        Slapi_DN sdn = {0};
+        Slapi_Entry *e = NULL;
+        Slapi_Entry **e2 = NULL;
+
+        /* Store cn=config entry */
+        slapi_sdn_init_dn_byref(&sdn, CONFIG_DN1);
+        pthread_mutex_lock(&mutex);
+        slapi_search_internal_get_entry(&sdn, NULL, &e, plugin_get_default_component_id());
+        ss_add_entry(ss, e);
+        slapi_sdn_done(&sdn);
+        /* Store cn=encryption,cn=config entry */
+        slapi_sdn_init_dn_byref(&sdn, CONFIG_DN2);
+        slapi_search_internal_get_entry(&sdn, NULL, &e, plugin_get_default_component_id());
+        ss_add_entry(ss, e);
+        /* Store cn=*,cn=encryption,cn=config active entries */
+        pb = slapi_search_internal(CONFIG_DN2, LDAP_SCOPE_ONELEVEL, CONFIG_DN2_FILTER,
+                                   NULL, NULL, 0);
+        slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &e2);
+        if (e2 != NULL) {
+            while (*e2) {
+                ss_add_entry(ss, slapi_entry_dup(*e2++));
+            }
+        }
+        slapi_free_search_results_internal(pb);
+        slapi_pblock_destroy(pb);
+        slapi_sdn_done(&sdn);
+        pdyncerts.config = ss;
+    }
+}
+
+/* Free private data instance config data */
+static void
+free_config_info()
+{
+    if (pdyncerts.config != NULL) {
+        ss_destroy(&pdyncerts.config);
+        pthread_mutex_unlock(&mutex);
+    }
+}
+
+/* Get a specific config entry by index */
+static Slapi_Entry *
+get_config_entry(int idx)
+{
+    if (pdyncerts.config && idx < pdyncerts.config->nb_entries) {
+        return pdyncerts.config->entries[idx];
+    }
+    return NULL;
+}
+
+/* Backend callback (freeing search set pair) */
+void
+dyncerts_search_set_release(void **pss)
+{
+    DCSS *ss = *pss;
+    if (ss) {
+        ss_destroy(&ss->pdscc);
+        slapi_ch_free((void**)&ss->entries);
+        slapi_ch_free(pss);
+    }
+}
+
+static Slapi_Entry *
+dyncerts_find_entry(const Slapi_DN *basedn, int scope, char **attrs, DCSS **be_ss)
+{
+    Slapi_DN suffix = {0};
+    Slapi_Entry *e = NULL;
+    DCSS *ss = NULL;
+
+    slapi_sdn_init_dn_byref(&suffix, DYNCERTS_SUFFIX);
+    read_config_info();
+    if (slapi_sdn_compare(basedn, &suffix) != 0) {
+        scope = LDAP_SCOPE_SUBTREE;
+    }
+    ss =  get_entry_list(&suffix, scope, attrs);
+    for(size_t idx = 0; idx<ss->nb_entries; idx++) {
+        e = ss->entries[idx];
+        if (slapi_sdn_compare(basedn, slapi_entry_get_sdn_const(e)) == 0) {
+            break;
+        }
+        e = NULL;
+    }
+    *be_ss = ss;
+    slapi_sdn_done(&suffix);
+    return e;
+}
+
+
+/* Backend callback (search operation: generate search set) */
+int
+dyncerts_search(Slapi_PBlock *pb)
+{
+    Slapi_Operation *operation = NULL;
+    Slapi_Filter *filter = NULL;
+    Slapi_DN *basesdn = NULL;
+    struct pdyncerts *pdata;
+    char *strfilter = NULL;
+    char **attrs = NULL;
+    int attrsonly = 0;
+    int scope = 0;
+    DCSS *be_ss = NULL;
+    DCSS *ss = NULL;
+    int estimate = 0;
+    Slapi_Entry *e = NULL;
+    int rc = -1;
+
+    /*
+     * Get private information created in the init routine.
+     * Also get the parameters of the search operation. These come
+     * more or less directly from the client.
+     */
+    if (slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &pdata) < 0 ||
+        slapi_pblock_get(pb, SLAPI_OPERATION, &operation) < 0 ||
+        slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &basesdn) < 0 ||
+        slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &scope) < 0 ||
+        slapi_pblock_get(pb, SLAPI_SEARCH_FILTER, &filter) < 0 ||
+        slapi_pblock_get(pb, SLAPI_SEARCH_STRFILTER, &strfilter) < 0 ||
+        slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &attrs) < 0 ||
+        slapi_pblock_get(pb, SLAPI_SEARCH_ATTRSONLY, &attrsonly) < 0) {
+        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
+        return (-1);
+    }
+    if (slapi_operation_is_flag_set(operation, SLAPI_OP_FLAG_INTERNAL)) {
+        /*
+         *  - acl discovery
+         */
+        if (attrs && strcasecmp(attrs[0], "aci")==0) {
+            /*
+             * aci discovery internal search ==>
+             * lets compute contatiner entry
+             */
+            scope = LDAP_SCOPE_BASE;
+        } else {
+             /*
+              * Bypass internal operations targetting all backends.
+              *  Like the roles & cos discovery
+              */
+            slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, ss);
+            slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET_SIZE_ESTIMATE, &estimate);
+            return 0;
+        }
+    }
+
+    /* Let first build the list of all entries */
+    e = dyncerts_find_entry(basesdn, scope, attrs, &be_ss);
+    ss = ss_new();
+    ss->pdscc = be_ss;
+
+    /* Make sure that the base entry exists */
+    if (!e) {
+        slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
+        slapi_log_err(SLAPI_LOG_PLUGIN, "dyncerts_search", "node %s was not found\n",
+                      slapi_sdn_get_dn(basesdn));
+        goto fail;
+    }
+    /* Then filter the matching entries */
+    for(size_t idx = 0; idx<be_ss->nb_entries; idx++) {
+        e = be_ss->entries[idx];
+        if (slapi_sdn_scope_test(slapi_entry_get_sdn_const(e), basesdn, scope) &&
+            slapi_vattr_filter_test(pb, e, filter, PR_TRUE) == 0) {
+            ss_add_entry(ss, e);
+        }
+    }
+    estimate = ss->nb_entries;
+    slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, ss);
+    slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET_SIZE_ESTIMATE, &estimate);
+    rc = 0;
+fail:
+    if (rc != 0) {
+        dyncerts_search_set_release((void**)&ss);
+    }
+    free_config_info();
+    return rc;
+}
+
+/* Backend callback (get next search entryt from search set) */
+int
+dyncerts_next_search_entry(Slapi_PBlock *pb)
+{
+    DCSS *ss = NULL;
+    Slapi_Entry *e = NULL;
+    slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &ss);
+
+    /* no entries to return */
+    if (ss == NULL) {
+        slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_ENTRY, NULL);
+        return 0;
+    }
+
+    if (ss->cur_entry < ss->nb_entries) {
+        e = ss->entries[ss->cur_entry++];
+    }
+    slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_ENTRY, e);
+
+    if (e == NULL) {
+        /* we reached the end of the list */
+        pagedresults_set_search_result_pb(pb, NULL, 0);
+        dyncerts_search_set_release((void**)&ss);
+        slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
+    }
+    return 0;
+}
+
+/* Backend callback (get previous search entry from search set) */
+void
+dyncerts_prev_search_results(void *vp)
+{
+    Slapi_PBlock *pb = (Slapi_PBlock *)vp;
+    DCSS *ss = NULL;
+    slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &ss);
+    if (ss->cur_entry > 0) {
+        ss->cur_entry--;
+    }
+}
+
+/* Backend callback (release private data context) */
+int
+dyncerts_cleanup(Slapi_PBlock *pb)
+{
+    struct dyncerts *pdata = NULL;
+
+    pthread_mutex_lock(&mutex);
+    slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &pdata);
+    if (pdata) {
+        while (pdata->sockets) {
+            struct sock_elem *se = pdata->sockets->next;
+            slapi_ch_free((void **)&pdata->sockets);
+            pdata->sockets = se;
+        }
+        memset(pdata, 0, sizeof *pdata);
+        pdata = NULL;
+    }
+    slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, pdata);
+    pthread_mutex_unlock(&mutex);
+    return 0;
+}
+
+/* Convert PRBool to string */
+static char *
+boolv(PRBool v)
+{
+    return (v == PR_TRUE) ? "TRUE" : "FALSE";
+}
+
+/* Convert PRTime to Zulu timestamp string */
+static void
+timev(PRTime t, char *str, size_t strsize)
+{
+    PRExplodedTime expt = {0};
+    PR_ExplodeTime(t, PR_GMTParameters, &expt);
+    PR_snprintf(str, strsize, "%04d%02d%02d%02d%02d%02dZ",
+                expt.tm_year,
+                expt.tm_month,
+                expt.tm_mday,
+                expt.tm_hour,
+                expt.tm_min,
+                expt.tm_sec);
+}
+
+/* Convert trust flags to string */
+static char *
+add_trust_flags(char *str, char *endstr, unsigned int flags)
+{
+    for (size_t i=0; trust_flags[i].flags && str<endstr; i++) {
+        if ((flags & (trust_flags[i].flags | trust_flags[i].mask)) == trust_flags[i].flags) {
+            *str++ = trust_flags[i].c;
+        }
+    }
+    *str = 0;
+    return str;
+}
+
+/* Convert certificate trust to string */
+static void
+trustv(CERTCertificate *cert, char *str, size_t strsize)
+{
+    char *endstr = str + strsize -1; /* -1 to keep space for \0 */
+    CERTCertTrust trust = {0};
+    CERT_GetCertTrust(cert, &trust);
+
+    str = add_trust_flags(str, endstr, trust.sslFlags);
+    if (str < endstr) {
+        *str++ = ',';
+        str = add_trust_flags(str, endstr, trust.emailFlags);
+    }
+    if (str < endstr) {
+        *str++ = ',';
+        str = add_trust_flags(str, endstr, trust.objectSigningFlags);
+    }
+}
+
+/* Convert a SECItem to a value */
+static void
+secitemv(SECItem *si, Slapi_Value *v)
+{
+    struct berval bv = {0};
+    if (si) {
+        bv.bv_val = (char*)(si->data);
+        bv.bv_len = si->len;
+    }
+    slapi_value_init_berval(v, &bv);
+}
+
+/* Determine if a certificate has a private key */
+static PRBool
+has_privkey(CERTCertificate *cert)
+{
+    SECKEYPrivateKey *privkey = PK11_FindKeyByDERCert(cert->slot, cert, NULL);
+    PRBool res = privkey ? PR_TRUE : PR_FALSE;
+    SECKEY_DestroyPrivateKey(privkey);
+    return res;
+}
+
+/* Determine the key algorythm */
+static char *
+key_algo(CERTCertificate *cert)
+{
+    SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert);\
+    CK_KEY_TYPE keyType = 0;
+    if (pubkey == NULL) {
+        return "None";
+    }
+    keyType = pubkey->keyType;
+    SECKEY_DestroyPublicKey(pubkey);
+	switch (keyType) {
+        case rsaKey: return "RSA";
+        case ecKey: return "EC";
+        default: return "UNKNOWN";
+    }
+}
+
+static char *
+secitem2hex(SECItem *si)
+{
+    size_t len = 0;
+    unsigned char *data = NULL;
+    if (si) {
+        len = si->len;
+        data = (unsigned char *)(si->data);
+    }
+    char *pt = slapi_ch_malloc(len*2+1);
+    pt[0] = 0;
+    for (size_t i=0; i<len; i++) {
+        sprintf(&pt[2*i], "%02x", data[i]);
+    }
+    return pt;
+}
+
+/* Helper function to determine if slotname is the internal slot */
+static bool
+is_internal_slot(const char *slotname)
+{
+    return ( strcasecmp(slotname, INTERNAL_SLOTNAME1) == 0 ||
+             strcasecmp(slotname, INTERNAL_SLOTNAME2) == 0);
+}
+
+/* Determine if a certificate is the server certificate */
+static PRBool
+is_servercert_int(const char *slotname, const char *nickname)
+{
+    Slapi_Entry *e = NULL;
+    if (slotname == NULL) {
+        slotname = INTERNAL_SLOTNAME1;
+    }
+    /* Check iof certificate match one of the familly definition */
+    for (size_t i=FIRST_FAMILY_CONFIG_ENTRY_IDX; (e=get_config_entry(i)); i++) {
+        const char *ename = slapi_entry_attr_get_ref(e, "nsSSLPersonalitySSL");
+        const char *eslot = slapi_entry_attr_get_ref(e, "nsSSLToken");
+        if (!ename || !eslot) {
+            continue;
+        }
+        if (strcasecmp(ename, nickname) != 0) {
+            continue;
+        }
+        if (is_internal_slot(eslot) && is_internal_slot(slotname)) {
+            return PR_TRUE;
+        }
+        if (strcasecmp(eslot, slotname) == 0) {
+            return PR_TRUE;
+        }
+    }
+    return PR_FALSE;
+}
+
+/* Determine if a certificate is the server certificate */
+static PRBool
+is_servercert(CERTCertificate *cert)
+{
+    const char *slotname = PK11_GetTokenName(cert->slot);
+    const char *nickname = cert->nickname;
+    return is_servercert_int(slotname, nickname);
+}
+
+static const char *
+cert_token(CERTCertificate *cert)
+{
+    return PK11_GetTokenName(cert->slot);
+}
+
+/* Get certificate db pin */
+static inline void * __attribute__((always_inline))
+_get_pw(const char *token)
+{
+    /* mutex is held in all backend operation callbacks */
+    struct sock_elem *se = pdyncerts.sockets;
+    char *pw = NULL;
+    SVRCOREStdPinObj *StdPinObj = (SVRCOREStdPinObj *)SVRCORE_GetRegisteredPinObj();
+    SVRCOREError err = SVRCORE_StdPinGetPin(&pw, StdPinObj, token);
+
+    if (err != SVRCORE_Success || pw == NULL) {
+        for (;pw == NULL && se; se=se->next) {
+            pw = SSL_RevealPinArg(se->pr_sock);
+        }
+    }
+    return pw;
+}
+
+/* Add certificate verification status to the entry */
+static void
+verify_cert(Slapi_Entry *e, CERTCertificate *cert, const char *attrname)
+{
+    SECCertificateUsage returnedUsages;
+    SECCertificateUsage usage = 0;
+    char *val = NULL;
+    char *pw = _get_pw(cert_token(cert));
+
+    if (strcmp(slapi_entry_attr_get_ref(e, DYCATTR_ISSRVCERT), "TRUE") == 0) {
+        usage = certificateUsageSSLServer;
+    } else if (strcmp(slapi_entry_attr_get_ref(e, DYCATTR_ISCA), "TRUE") == 0) {
+        usage = certificateUsageAnyCA;
+    } else {
+        usage = certificateUsageSSLClient;
+    }
+
+    int rv = CERT_VerifyCertificateNow(cert->dbhandle, cert, PR_TRUE,
+                                       usage, pw, &returnedUsages);
+    slapi_ch_free_string(&pw);
+
+    if (rv != SECSuccess) {
+        int errorCode = PR_GetError();
+        val = slapi_ch_smprintf("FAILURE: error %d - %s", errorCode,
+                                slapd_pr_strerror(errorCode));
+    } else {
+        val = slapi_ch_strdup("SUCCESS");
+	}
+    slapi_entry_add_string(e, attrname, val);
+    slapi_ch_free_string(&val);
+}
+
+/*
+ * A callback for the Subject Alternate Name supported encoding table. Used for:
+ *   - OtherName:
+ *     EDIPartyName ::= SEQUENCE { nameAssigner [0] OPTIONAL, partyName [1] }
+ *   or
+ *   - EDIPartyName with optional nameAssigner
+ *     OtherName ::= SEQUENCE { type-id OID, value [0] EXPLICIT ANY }
+ */
+static ber_tag_t
+sanse_bv2 (BerElement *ber, general_name_value_t *val)
+{
+    val->nbvals = 2;
+    return ber_scanf(ber, "{oo}", &val->vals[0], &val->vals[1]);
+}
+
+/*
+ * A callback for the Subject Alternate Name supported encoding table. Used for:
+ *   - EDIPartyName without optional nameAssigner
+ *     OtherName ::= SEQUENCE { type-id OID, value [0] EXPLICIT ANY }
+ */
+static ber_tag_t
+sanse_edi (BerElement *ber, general_name_value_t *val)
+{
+    val->nbvals = 1;
+    return ber_scanf(ber, "{o}", &val->vals[0]);
+}
+
+/*
+ * A callback for the Subject Alternate Name supported encoding table. Used for:
+ *   - SomeName ::= OCTETSTRING
+ */
+static ber_tag_t
+sanse_bv1 (BerElement *ber, general_name_value_t *val)
+{
+    val->nbvals = 1;
+    return ber_scanf(ber, "o", &val->vals[0]);
+}
+
+/*
+ * A callback for the Subject Alternate Name supported encoding table. Used for:
+ *   - Skipping unknown format
+ */
+static ber_tag_t
+sanse_bv0 (BerElement *ber, general_name_value_t *val)
+{
+    ber_tag_t tag = 0;
+    val->nbvals = 0;
+    return ber_scanf(ber, "T", &tag);
+}
+
+/* Free data within general_name_value_t */
+static void
+general_name_value_free(general_name_value_t *val)
+{
+    for (size_t i=0; i<val->nbvals; i++) {
+        if (val->vals[i].bv_val) {
+            ber_memfree(val->vals[i].bv_val);
+            val->vals[i].bv_val = NULL;
+        }
+        val->vals[i].bv_len = 0;
+    }
+    val->nbvals = 0;
+}
+
+/*
+ * Decode Alternate Subject Names (rfc 5280)
+ * and call callback for each names
+ */
+static void
+walk_subject_alt_names(BerValue *bv, gnw_cb_t cb, void *arg)
+{
+    BerElement *ber = ber_init(bv);
+    char *cookie = NULL;
+    size_t len = 0;
+    ber_tag_t tag = 0;
+    /* The subject alternate name supported encoding table */
+    ber_tag_t (*sanset[])(BerElement*, general_name_value_t*) = {
+        sanse_bv2,
+        sanse_edi,
+        sanse_bv1,
+        sanse_bv0,
+        NULL
+    };
+
+    /* Iterate through GeneralName entries */
+    for (tag=ber_first_element(ber, &len, &cookie);
+         tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+         tag=ber_next_element(ber, &len, cookie)) {
+        general_name_value_t val = {0};
+        ber_tag_t rc = LBER_ERROR;
+        /*
+         * Determine how the GeneralName is encoded
+         *  (by walking supported encoding table)
+         */
+        for(size_t i=0; sanset[i] && rc == LBER_ERROR; i++) {
+            /* ber state is modified by failed decoding attempt
+             * ==> lets duplicate it
+             */
+            BerElement *b = ber_dup(ber);
+            rc = sanset[i](b, &val);
+            general_name_value_free(&val);
+            if (rc != LBER_ERROR) {
+                /*
+                 * The right encoding has been found
+                 * ==> Now we can decode the main buffer
+                 *     and call the callback
+                 */
+                rc = sanset[i](ber, &val);
+                cb(tag & 0x1F, &val, arg);
+            }
+            general_name_value_free(&val);
+            ber_free(b, 0);
+        }
+    }
+    ber_free(ber, 1);
+}
+
+/* Callback used to store subject alternate name in the entry */
+static void
+gnw_cb(general_name_type_t gnt, const general_name_value_t *val, void *arg)
+{
+    struct altname_ctx *ctx = arg;
+    char *str = NULL;
+
+    switch (val->nbvals) {
+        case 1:
+            if (gnt == gnt_ipaddress) {
+                /* Should convert the address to string */
+            } else {
+                size_t len = val->vals[0].bv_len;
+                str = slapi_ch_malloc(len+1);
+                memcpy(str, val->vals[0].bv_val, len);
+                str[len] = 0;
+            }
+            break;
+        case 2:
+            {
+                size_t len0 = val->vals[0].bv_len;
+                size_t len1 = val->vals[1].bv_len;
+                str = slapi_ch_malloc(len0+len1+2);
+                memcpy(str, val->vals[0].bv_val, len0);
+                str[len0] = '=';
+                memcpy(str+len0+1, val->vals[1].bv_val, len1);
+                str[len0+1+len1] = 0;
+            }
+            break;
+    }
+    if (str) {
+        slapi_entry_add_string(ctx->e, ctx->attrname, str);
+        slapi_ch_free_string(&str);
+    }
+}
+
+/* Store subject alternate name in the entry */
+static void
+store_alt_name(Slapi_Entry *e, CERTCertificate *cert, const char *attrname)
+{
+    SECItem si = {0};
+    int rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, &si);
+    struct altname_ctx ctx = { attrname, e };
+    BerValue bv = {0};
+    if (rv == 0) {
+        bv.bv_len = si.len;
+        bv.bv_val = (char*) si.data;
+        walk_subject_alt_names(&bv, gnw_cb, &ctx);
+    }
+    SECITEM_FreeItem(&si, PR_FALSE);
+}
+
+/* Get certificate nickname (including token name). Must be freed by caller */
+static char *
+get_cert_nickname(CERTCertificate *cert)
+{
+    char *token = PK11_GetTokenName(cert->slot);
+    if (is_internal_slot(token)) {
+        return slapi_ch_strdup(cert->nickname);
+    }
+    return slapi_ch_smprintf("%s:%s", token, cert->nickname);
+}
+
+/* Generate the certificate entry */
+static Slapi_Entry *
+dyncerts_cert2entry(CERTCertificate *cert)
+{
+    char *nickname = get_cert_nickname(cert);
+    char *dn = slapi_ch_smprintf("cn=%s,%s", nickname, DYNCERTS_SUFFIX);
+    Slapi_Entry *e = slapi_entry_alloc();
+    char tmpbuff[32] = "";
+    PRTime notBefore = 0;
+    PRTime notAfter = 0;
+    Slapi_Value tmpv = {0};
+    char *tmpstr = NULL;
+
+    slapi_entry_init(e, dn, NULL);
+    slapi_entry_add_string(e, "objectclass", "top");
+    slapi_entry_add_string(e, "objectclass", "extensibleobject");
+    slapi_entry_add_string(e, DYCATTR_NICKNAME, nickname);
+    slapi_entry_add_string(e, DYCATTR_SUBJECT, cert->subjectName);
+    slapi_entry_add_string(e, DYCATTR_ISSUER, cert->issuerName);
+    slapi_entry_add_string(e, DYCATTR_ISCA, boolv(CERT_IsCACert(cert, NULL)));
+    slapi_entry_add_string(e, DYCATTR_ISROOTCA, boolv(cert->isRoot));
+    slapi_entry_add_string(e, DYCATTR_HASPKEY, boolv(has_privkey(cert)));
+    slapi_entry_add_string(e, DYCATTR_ISSRVCERT, boolv(is_servercert(cert)));
+    slapi_entry_add_string(e, DYCATTR_KALGO, key_algo(cert));
+    CERT_GetCertTimes(cert, &notBefore, &notAfter);
+    timev(notBefore, tmpbuff, sizeof tmpbuff);
+    slapi_entry_add_string(e, DYCATTR_NBEFORE, tmpbuff);
+    timev(notAfter, tmpbuff, sizeof tmpbuff);
+    slapi_entry_add_string(e, DYCATTR_NAFTER, tmpbuff);
+    trustv(cert, tmpbuff, sizeof tmpbuff);
+    slapi_entry_add_string(e, DYCATTR_TRUST, tmpbuff);
+    COND_STR(e, DYCATTR_TYPE, "SSL CLIENT", cert->nsCertType & NS_CERT_TYPE_SSL_CLIENT);
+    COND_STR(e, DYCATTR_TYPE, "SSL SERVER", cert->nsCertType & NS_CERT_TYPE_SSL_SERVER);
+    COND_STR(e, DYCATTR_TYPE, "EMAIL", cert->nsCertType & NS_CERT_TYPE_EMAIL);
+    COND_STR(e, DYCATTR_TYPE, "OBJECT SIGNING", cert->nsCertType & NS_CERT_TYPE_OBJECT_SIGNING);
+    COND_STR(e, DYCATTR_TYPE, "SSL CA", cert->nsCertType & NS_CERT_TYPE_SSL_CA);
+    COND_STR(e, DYCATTR_TYPE, "EMAIL CA", cert->nsCertType & NS_CERT_TYPE_EMAIL_CA);
+    COND_STR(e, DYCATTR_TYPE, "OBJECT SIGNING CA", cert->nsCertType & NS_CERT_TYPE_OBJECT_SIGNING_CA);
+    slapi_entry_add_string(e, DYCATTR_TOKEN, PK11_GetTokenName(cert->slot));
+    secitemv(&cert->derCert, &tmpv);
+    value_done(&tmpv);
+    slapi_entry_add_value(e, DYCATTR_CERTDER, &tmpv);
+    tmpstr = secitem2hex(&cert->serialNumber);
+    slapi_entry_add_string(e, DYCATTR_SN, tmpstr);
+    slapi_ch_free_string(&tmpstr);
+    verify_cert(e, cert, DYCATTR_VERIF);
+    store_alt_name(e, cert, DYCATTR_ALTNAME);
+    slapi_ch_free_string(&nickname);
+    return e;
+}
+
+/* Backend callback (unbind operation) */
+int
+dyncerts_unbind(Slapi_PBlock *pb __attribute__((unused)))
+{
+    return 0;
+}
+
+/* PK11_TraverseSlotCerts callback that adds a certificate entry in the parent search set */
+static SECStatus
+dyncerts_list_cert_cb(CERTCertificate *cert, SECItem *sitem, void *arg)
+{
+    /* slapi_log_err(SLAPI_LOG_INFO, "dyncerts_list_cert_cb", "See certificate %s\n", cert->nickname); */
+    ss_add_entry(arg, dyncerts_cert2entry(cert));
+    return 0;
+}
+
+/* Generate the parent search set */
+static DCSS *
+get_entry_list(const Slapi_DN *basesn, int scope, char **attrs)
+{
+    DCSS *ss = ss_new();
+    int str2entry_flags = SLAPI_STR2ENTRY_EXPAND_OBJECTCLASSES |
+                          SLAPI_STR2ENTRY_NOT_WELL_FORMED_LDIF;
+    Slapi_Entry *e = slapi_str2entry((char*)dyncerts_baseentry_str, str2entry_flags);
+
+    /* Handle aci if requested */
+    if (charray_inlist(attrs, "aci") || charray_inlist(attrs, "+") ||
+        charray_inlist(attrs, "*")) {
+        const struct slapi_value **va = NULL;
+        Slapi_Entry *ce = get_config_entry(ENCRYPTION_CONFIG_ENTRY_IDX);
+        if (ce) {
+            va = slapi_entry_attr_get_valuearray(ce, "aci");
+        }
+        if (va) {
+            slapi_entry_attr_replace_sv(e, "aci", (struct slapi_value **)va);
+        }
+    }
+    ss_add_entry(ss, e);
+    if (LDAP_SCOPE_BASE == scope) {
+        /* Bypass getting cert list if only looking for the container */
+        Slapi_DN sdn = {0};
+        slapi_sdn_init_dn_byref(&sdn, DYNCERTS_SUFFIX);
+        if (slapi_sdn_compare(&sdn, slapi_entry_get_sdn_const(e)) == 0) {
+            slapi_sdn_done(&sdn);
+            return ss;
+        }
+        slapi_sdn_done(&sdn);
+    }
+
+    if (slapd_nss_is_initialized()) {
+        (void) PK11_TraverseSlotCerts(dyncerts_list_cert_cb, ss, NULL);
+    }
+    return ss;
+}
+
+/* Create the dynamic certificate backend */
+Slapi_Backend *
+dyncert_init_be()
+{
+    pthread_mutex_lock(&mutex);
+    if (!pdyncerts.be) {
+        Slapi_Backend *be = slapi_be_new(DYNCERTS_BETYPE, DYNCERTS_BENAME, 1 /* Private */, 0 /* Do Not Log Changes */);
+        Slapi_DN dn = {0};
+        pdyncerts.be = be;
+
+        be->be_database = &dyncerts_plugin;
+        be->be_database->plg_private = &pdyncerts;
+        be->be_database->plg_bind = &be_unwillingtoperform;
+        be->be_database->plg_unbind = &dyncerts_unbind;
+        be->be_database->plg_search = &dyncerts_search;
+        be->be_database->plg_next_search_entry = &dyncerts_next_search_entry;
+        be->be_database->plg_search_results_release = &dyncerts_search_set_release;
+        be->be_database->plg_prev_search_results = &dyncerts_prev_search_results;
+        be->be_database->plg_compare = &be_unwillingtoperform;
+/*
+        be->be_database->plg_modify = &dyncerts_modify;
+        be->be_database->plg_modrdn = &be_unwillingtoperform;
+        be->be_database->plg_add = &dyncerts_add;
+        be->be_database->plg_delete = &dyncerts_delete;
+*/
+        be->be_database->plg_abandon = &be_unwillingtoperform;
+        be->be_database->plg_cleanup = &dyncerts_cleanup;
+        /* All the other function pointers default to NULL */
+
+        slapi_sdn_init_ndn_byref(&dn, DYNCERTS_SUFFIX);
+        be_addsuffix(be, &dn);
+        slapi_sdn_done(&dn);
+    }
+    pthread_mutex_unlock(&mutex);
+    return pdyncerts.be;
+}
+
+/*
+ * Store socket fd and associated PRFileDesc in private data
+ * to fetch the password
+ */
+void
+dyncerts_register_socket(int sock, PRFileDesc *pr_sock)
+{
+    struct sock_elem *se;
+    pthread_mutex_lock(&mutex);
+    se = pdyncerts.sockets;
+    /* If port already exist, lets reuse its slot */
+    for (;se; se=se->next) {
+        if (se->sock == sock) {
+            break;
+        }
+    }
+    if (!se) {
+        /* Else alloc a new one */
+        se = (struct sock_elem *) slapi_ch_calloc(1, sizeof(struct sock_elem));
+        se->next = pdyncerts.sockets;
+        pdyncerts.sockets = se;
+    }
+    se->sock = sock;
+    se->pr_sock = pr_sock;
+    pthread_mutex_unlock(&mutex);
+}

+ 158 - 0
ldap/servers/slapd/dyncerts.h

@@ -0,0 +1,158 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright (C) 2025 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * License: GPL (version 3 or any later version).
+ * See LICENSE for details.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef DYNCERT_H_
+#define DYNCERT_H_
+/*
+ * dyncerts.h - Dynamic Certificates Handler
+ */
+
+#define DYNCERTS_BENAME "DYNCERTS"
+#define DYNCERTS_BETYPE "DSE"
+
+#define CONFIG_DN1 "cn=config"
+#define CONFIG_DN2 "cn=encryption,cn=config"
+#define CONFIG_DN2_FILTER "(&(objectclass=nsEncryptionModule)(nsSSLActivation=on))"
+
+/* Indexes in config search set */
+#define CONFIG_ENTRY_IDX 	            0
+#define ENCRYPTION_CONFIG_ENTRY_IDX 	1
+#define FIRST_FAMILY_CONFIG_ENTRY_IDX 	2
+
+/* Conditionnaly add an attribute value */
+#define COND_STR(e, a, v, c) do { if (c) { slapi_entry_add_string((e), (a), (v)); } } while (false)
+
+/* The Dynamic certificate attribute names */
+#define DYCATTR_NICKNAME		 "cn"
+#define DYCATTR_PREFIX           "dsDynamicCertificate"
+#define DYCATTR_ISSRVCERT        DYCATTR_PREFIX "IsServerCert"
+#define DYCATTR_ISROOTCA	     DYCATTR_PREFIX "IsRootCA"
+#define DYCATTR_ISCA	         DYCATTR_PREFIX "IsCA"
+#define DYCATTR_SUBJECT		     DYCATTR_PREFIX "Subject"
+#define DYCATTR_ISSUER		     DYCATTR_PREFIX "Issuer"
+#define DYCATTR_HASPKEY		     DYCATTR_PREFIX "HasPrivateKey"
+#define DYCATTR_KALGO		     DYCATTR_PREFIX "KeyAlgorithm"
+#define DYCATTR_NBEFORE		     DYCATTR_PREFIX "NotBefore"
+#define DYCATTR_NAFTER		     DYCATTR_PREFIX "NotAfter"
+#define DYCATTR_TRUST		     DYCATTR_PREFIX "TrustFlags"
+#define DYCATTR_TYPE		     DYCATTR_PREFIX "Type"
+#define DYCATTR_TOKEN		     DYCATTR_PREFIX "TokenName"
+#define DYCATTR_CERTDER		     DYCATTR_PREFIX "Der"
+#define DYCATTR_PKEYDER		     DYCATTR_PREFIX "PrivateKeyDer"
+#define DYCATTR_SN		         DYCATTR_PREFIX "SerialNumber"
+#define DYCATTR_VERIF	         DYCATTR_PREFIX "VerificationStatus"
+#define DYCATTR_ALTNAME	         DYCATTR_PREFIX "SubjectAltName"
+#define DYCATTR_FORCE	         DYCATTR_PREFIX "Force"
+#define DYCATTR_SWITCH	         DYCATTR_PREFIX "SwitchInProgress"
+
+#define INTERNAL_SLOTNAME1    "Internal (Software)"
+#define INTERNAL_SLOTNAME2    "Internal (Software) Token"
+
+#define ERRMSG(ctx, rc, ...) { \
+            (ctx)->ldaprc = (rc); \
+            if ((ctx)->errmsg) { \
+                PR_snprintf((ctx)->errmsg, SLAPI_DSE_RETURNTEXT_SIZE, __VA_ARGS__); \
+            } \
+            return (ctx)->ldaprc; \
+        }
+
+/* Entry type (container or certificate) used to check dn validity */
+typedef enum {
+    DYC_ERR,
+    DYC_CONT,
+    DYC_CERT,
+    DYC_BOTH
+} dyc_et_t;
+
+struct certctx_str;
+typedef void (*dyc_action_cb_t)(struct certctx_str *ctx);
+
+/* Context used to store certificate and private key in NSS db */
+typedef struct certctx_str {
+    SECItem dercert;
+    SECItem derpkey;
+    char *fullnickname;
+    char *nickname;
+    char *token;
+    char *trust;
+    bool force;
+    bool primary;
+    bool internal_token;
+    Slapi_DN *sdn;
+    PK11SlotInfo *slot;
+    CERTCertificate *cert;
+    SECKEYPrivateKey *privkey;
+    char *errmsg; /* SLAPI_DSE_RETURNTEXT_SIZE */
+    int ldaprc;
+    int verifyrc;
+    dyc_action_cb_t action_cb;
+    void *arg;
+} CertCtx_t;
+
+/*
+ * Helper structs used to decode Subject Alternate Name
+ */
+typedef enum general_name_type {
+    gnt_othername, gnt_rfc822name, gnt_dnsname, gnt_x400address, gnt_dirname,
+    gnt_edipartyname, gnt_uri, gnt_ipaddress, gnt_registeredid,
+} general_name_type_t;
+
+typedef struct {
+    BerValue vals[2];
+    int nbvals;
+} general_name_value_t;
+
+struct altname_ctx {
+    const char *attrname;
+    Slapi_Entry *e;
+};
+
+/* Helper structs used to decode trust flags */
+struct trust_flags_mask {
+    unsigned int flags;
+    unsigned int mask;
+    char c;
+};
+
+/*
+ * Search set struct is used to store a list of entries
+ * The different search set are:
+ * The whole backend search set
+ *      containing the backend container and the certificates entries
+ *      It has a NULL pdscc
+ * The search search set containing the entrties that matches the search request
+ *      The entries are links to the backend entries (so they should not be freed)
+ *      Its pdscc point to the backend search set
+ * The config search set containing cn=config and nsEncryptionModule entries
+ */
+typedef struct dyncerts_search_set {
+    size_t max_entries;
+    size_t nb_entries;
+    Slapi_Entry **entries;
+    size_t cur_entry;
+    struct dyncerts_search_set *pdscc;
+} DCSS;
+
+/* List of ldaps sockets and associated PRFileDesc */
+struct sock_elem {
+    struct sock_elem *next;
+    int sock;
+    PRFileDesc *pr_sock;
+};
+
+/* The backend private data */
+struct dyncerts {
+    Slapi_Backend *be;
+    DCSS *config;
+    struct sock_elem *sockets;  /* Secure sockets (needed to get the pin) */
+};
+
+/* Alternate Name Decoder Callback */
+typedef void (*gnw_cb_t)(general_name_type_t gnt, const general_name_value_t *val, void *arg);
+
+#endif  /* DYNCERT_H_ */

+ 2 - 0
ldap/servers/slapd/mapping_tree.c

@@ -1690,6 +1690,8 @@ mapping_tree_init()
     be = slapi_be_select_by_instance_name(DSE_SCHEMA);
     node = add_internal_mapping_tree_node("cn=schema", be, mapping_tree_root);
     mapping_tree_node_add_child(mapping_tree_root, node);
+    node = add_internal_mapping_tree_node(DYNCERTS_SUFFIX, dyncert_init_be(), mapping_tree_root);
+    mapping_tree_node_add_child(mapping_tree_root, node);
 
     slapi_rwlock_unlock(myLock);
 

+ 7 - 0
ldap/servers/slapd/proto-slap.h

@@ -140,6 +140,7 @@ int valuearray_first_value(Slapi_Value **va, Slapi_Value **v);
 
 void valuearrayfast_init(struct valuearrayfast *vaf, Slapi_Value **va);
 void valuearrayfast_done(struct valuearrayfast *vaf);
+const Slapi_Value **slapi_entry_attr_get_valuearray(const Slapi_Entry *e, const char *attrname);
 
 /* Valueset functions */
 
@@ -1655,4 +1656,10 @@ int sort_make_sort_response_control(Slapi_PBlock *pb, int code, char *error_type
  */
 int subentries_parse_request_control(struct berval *subentries_spec_ber);
 
+/*
+ * dyncerts.c
+ */
+Slapi_Backend *dyncert_init_be(void);
+void dyncerts_register_socket(int sock, PRFileDesc *pr_sock);
+
 #endif /* _PROTO_SLAP */

+ 3 - 0
ldap/servers/slapd/slap.h

@@ -2956,4 +2956,7 @@ int32_t slapd_identify_local_user(Connection *conn);
 int32_t slapd_bind_local_user(Connection *conn);
 #endif
 
+#define DYNCERTS_CN	"dynamiccertificates"
+#define DYNCERTS_SUFFIX	"cn=" DYNCERTS_CN
+
 #endif /* _slap_h_ */

+ 4 - 1
ldap/servers/slapd/ssl.c

@@ -23,6 +23,8 @@
 #include "secmod.h"
 #include <string.h>
 #include <errno.h>
+#include <private/pprio.h>
+
 
 #define NEED_TOK_PBE /* defines tokPBE and ptokPBE - see slap.h */
 #include "slap.h"
@@ -1087,7 +1089,7 @@ slapd_nss_init(int init_ssl __attribute__((unused)), int config_available __attr
 
     nssFlags &= (~NSS_INIT_READONLY);
     slapd_pk11_configurePKCS11(NULL, NULL, tokPBE, ptokPBE, NULL, NULL, NULL, NULL, 0, 0);
-    secStatus = NSS_Initialize(certdir, NULL, NULL, "secmod.db", nssFlags);
+   secStatus = NSS_InitReadWrite(certdir);
 
     dongle_file_name = slapi_ch_smprintf("%s/pin.txt", certdir);
 
@@ -1607,6 +1609,7 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
     }
 
     (*fd) = pr_sock;
+    dyncerts_register_socket(PR_FileDesc2NativeHandle(sock), pr_sock);
 
     /* Step / Three.6 /
      *  - If in FIPS mode, authenticate to the token before

+ 4 - 1
ldap/servers/slapd/vattr.c

@@ -148,7 +148,10 @@ vattr_check_thread(void *arg)
     int32_t rc;
     int32_t check_suffix; /* used to skip suffixes in ignored_backend */
     PRBool exist_vattr_definition = PR_FALSE;
-    char *ignored_backend[5] = {"cn=config", "cn=schema", "cn=monitor", "cn=changelog", NULL}; /* suffixes to ignore */
+    const char *ignored_backend[] = {  /* suffixes to ignore */ \
+        "cn=config", "cn=schema", "cn=monitor", "cn=changelog",
+        DYNCERTS_SUFFIX, NULL
+    };
     char *suffix;
     int ignore_vattrs;