|
|
@@ -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, ¬Before, ¬After);
|
|
|
+ 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);
|
|
|
+}
|