]> git.sur5r.net Git - openldap/commitdiff
Add autoca overlay
authorHoward Chu <hyc@openldap.org>
Fri, 7 Apr 2017 14:25:37 +0000 (15:25 +0100)
committerHoward Chu <hyc@openldap.org>
Sat, 8 Apr 2017 01:51:08 +0000 (02:51 +0100)
Automated certificate authority

configure.in
doc/man/man5/slapo-autoca.5 [new file with mode: 0644]
servers/slapd/overlays/Makefile.in
servers/slapd/overlays/autoca.c [new file with mode: 0644]

index ab6751e87bbbbebcc864ae414cff2be980190b56..41cd0a5df892091d70e4deae4b86a9d022f4302b 100644 (file)
@@ -344,6 +344,7 @@ dnl ----------------------------------------------------------------
 dnl SLAPD Overlay Options
 Overlays="accesslog \
        auditlog \
+       autoca \
        collect \
        constraint \
        dds \
@@ -372,6 +373,8 @@ OL_ARG_ENABLE(accesslog,[    --enable-accesslog       In-Directory Access Logging ov
        no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(auditlog,[    --enable-auditlog    Audit Logging overlay],
        no, [no yes mod], ol_enable_overlays)
+OL_ARG_ENABLE(autoca,[    --enable-autoca        Automatic Certificate Authority overlay],
+       no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(collect,[    --enable-collect      Collect overlay],
        no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(constraint,[    --enable-constraint        Attribute Constraint overlay],
@@ -2903,6 +2906,18 @@ if test "$ol_enable_auditlog" != no ; then
        AC_DEFINE_UNQUOTED(SLAPD_OVER_AUDITLOG,$MFLAG,[define for Audit Logging overlay])
 fi
 
+if test "$ol_enable_autoca" != no ; then
+       BUILD_AUDITLOG=$ol_enable_autoca
+       if test "$ol_enable_autoca" = mod ; then
+               MFLAG=SLAPD_MOD_DYNAMIC
+               SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS autoca.la"
+       else
+               MFLAG=SLAPD_MOD_STATIC
+               SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS autoca.o"
+       fi
+       AC_DEFINE_UNQUOTED(SLAPD_OVER_AUTOCA,$MFLAG,[define for Automatic Certificate Authority overlay])
+fi
+
 if test "$ol_enable_collect" != no ; then
         BUILD_COLLECT=$ol_enable_collect
         if test "$ol_enable_collect" = mod ; then
diff --git a/doc/man/man5/slapo-autoca.5 b/doc/man/man5/slapo-autoca.5
new file mode 100644 (file)
index 0000000..6bfb71f
--- /dev/null
@@ -0,0 +1,103 @@
+.TH SLAPO-AUTOCA 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2009-2017 The OpenLDAP Foundation All Rights Reserved.
+.\" Copyright 2009-2017 Howard Chu All Rights Reserved.
+.\" Copying restrictions apply.  See COPYRIGHT/LICENSE.
+.\" $OpenLDAP$
+.SH NAME
+slapo\-autoca \- Automatic Certificate Authority overlay to slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The Automatic CA overlay generates X.509 certificate/key pairs for
+entries in the directory. The DN of a generated certificate is
+identical to the DN of the entry containing it. On startup it
+checks for a CA certificate in the suffix entry of the database
+and generates and stores one if not found. This CA certificate
+is used to sign all subsequently generated certificates.
+.LP
+Certificates for users and servers are generated on demand using
+a Search request returning only the userCertificate;binary and
+userPrivateKey;binary attributes. Any Search for anything besides
+exactly these two attributes is ignored by the overlay. Note that
+these values are stored in ASN.1 DER form in the directory so the
+";binary" attribute option is mandatory.
+.LP
+Entries that do not belong to selected objectClasses will be
+ignored by the overlay. By default, entries of objectClass
+.B person
+will be treated as users, and entries of objectClass
+.B ipHost
+will be treated as servers. There are slight differences in the
+set of X.509V3 certificate extensions added to the certificate
+between users and servers.
+.LP
+The CA's private key is stored in a
+.B cAPrivateKey
+attribute, and user and server private keys are stored in the
+.B userPrivateKey
+attribute. It is essential that access to these attributes be
+properly secured with ACLs. Both of these attributes inherit
+from the
+.B x509PrivateKey
+attribute, so it is sufficient to use a single ACL rule like
+
+.nf
+        access to attrs=x509PrivateKey by self ssf=128 write
+.fi
+
+at the beginning of the rules.
+
+.SH CONFIGURATION
+These
+.B slapd.conf
+options apply to the Automatic CA overlay.
+They should appear after the
+.B overlay
+directive.
+.TP
+.B userClass <objectClass>
+Specify the objectClass to be treated as user entries.
+.TP
+.B serverClass <objectClass>
+Specify the objectClass to be treated as server entries.
+.TP
+.B userKeybits <integer>
+Specify the size of the private key to use for user certificates.
+The default is 2048 and the minimum is 512.
+.TP
+.B serverKeybits <integer>
+Specify the size of the private key to use for server certificates.
+The default is 2048 and the minimum is 512.
+.TP
+.B caKeybits <integer>
+Specify the size of the private key to use for the CA certificate.
+The default is 2048 and the minimum is 512.
+.TP
+.B userDays <integer>
+Specify the duration for a user certificate's validity.
+The default is 365, 1 year.
+.TP
+.B serverDays <integer>
+Specify the duration for a server certificate's validity.
+The default is 1826, 5 years.
+.TP
+.B caDays <integer>
+Specify the duration for the CA certificate's validity.
+The default is 3652, 10 years.
+
+.SH EXAMPLES
+.nf
+  database mdb
+  ...
+  overlay autoca
+  caKeybits 4096
+.fi
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd\-config (5).
+.SH AUTHOR
+Howard Chu
index 40c71f6946e7965b62fc155282ea96ffb155d830..49d5c1af4c634c4ca439285f67882d939db7f49f 100644 (file)
@@ -16,6 +16,7 @@
 SRCS = overlays.c \
        accesslog.c \
        auditlog.c \
+       autoca.c \
        constraint.c \
        dds.c \
        deref.c \
@@ -68,6 +69,9 @@ accesslog.la : accesslog.lo
 auditlog.la : auditlog.lo
        $(LTLINK_MOD) -module -o $@ auditlog.lo version.lo $(LINK_LIBS)
 
+autoca.la : autoca.lo
+       $(LTLINK_MOD) -module -o $@ autoca.lo version.lo $(LINK_LIBS)
+
 constraint.la : constraint.lo
        $(LTLINK_MOD) -module -o $@ constraint.lo version.lo $(LINK_LIBS)
 
diff --git a/servers/slapd/overlays/autoca.c b/servers/slapd/overlays/autoca.c
new file mode 100644 (file)
index 0000000..717fad8
--- /dev/null
@@ -0,0 +1,969 @@
+/* autoca.c - Automatic Certificate Authority */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2017 The OpenLDAP Foundation.
+ * Copyright 2009-2017 by Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#define SLAPD_OVER_AUTOCA      SLAPD_MOD_DYNAMIC
+
+#ifdef SLAPD_OVER_AUTOCA
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "config.h"
+
+#include <openssl/x509v3.h>
+#include <openssl/evp.h>
+
+/* This overlay implements a certificate authority that can generate
+ * certificates automatically for any entry in the directory.
+ * On startup it generates a self-signed CA cert for the directory's
+ * suffix entry and uses this to sign all other certs that it generates.
+ * User and server certs are generated on demand, using a Search request.
+ */
+
+#define LBER_TAG_OID        ((ber_tag_t) 0x06UL)
+#define LBER_TAG_UTF8       ((ber_tag_t) 0x0cUL)
+
+#define KEYBITS        2048
+#define MIN_KEYBITS    512
+
+#define ACA_SCHEMA_ROOT        "1.3.6.1.4.1.4203.666.11.11"
+
+#define ACA_SCHEMA_AT ACA_SCHEMA_ROOT ".1"
+#define ACA_SCHEMA_OC ACA_SCHEMA_ROOT ".2"
+#define ACA_SCHEMA_SYN ACA_SCHEMA_ROOT ".3"
+#define ACA_SCHEMA_MR ACA_SCHEMA_ROOT ".4"
+
+static AttributeDescription *ad_caCert, *ad_caPkey, *ad_usrCert, *ad_usrPkey;
+static AttributeDescription *ad_mail, *ad_ipaddr;
+static ObjectClass *oc_caObj, *oc_usrObj;
+
+/* OpenSSL privatekeys have no single specific format */
+static int
+privateKeyValidate(
+       Syntax          *syntax,
+       struct berval   *val )
+{
+       BerElementBuffer berbuf;
+       BerElement *ber = (BerElement *)&berbuf;
+       ber_tag_t tag;
+       ber_len_t len;
+       ber_int_t version;
+
+       ber_init2( ber, val, LBER_USE_DER );
+       tag = ber_skip_tag( ber, &len );        /* Sequence */
+       if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+       tag = ber_peek_tag( ber, &len );
+       if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX;
+       tag = ber_get_int( ber, &version );
+       /* the rest varies for RSA, DSA, EC, PKCS#8 */
+       return LDAP_SUCCESS;
+}
+
+static slap_syntax_defs_rec aca_syntax = {
+       "( " ACA_SCHEMA_SYN ".1 DESC 'X.509 Private Key' "
+                       "X-BINARY-TRANSFER-REQUIRED 'TRUE' "
+                       "X-NOT-HUMAN-READABLE 'TRUE' )",
+                       SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER,
+                       NULL,
+                       privateKeyValidate,
+                       NULL };
+
+static slap_mrule_defs_rec aca_mrule = {
+       "( " ACA_SCHEMA_MR ".1 NAME 'privateKeyMatch' "
+               "SYNTAX " ACA_SCHEMA_SYN ".1 )",
+               SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
+               NULL, NULL, octetStringMatch, octetStringIndexer,
+               octetStringFilter, NULL };
+
+static char *aca_attrs[] = {
+       "( " ACA_SCHEMA_AT ".0 NAME 'x509PrivateKey' "
+               "DESC 'X.509 private key, use ;binary' "
+               "EQUALITY privateKeyMatch "
+               "SYNTAX " ACA_SCHEMA_SYN ".1 )",
+       "( " ACA_SCHEMA_AT ".1 NAME 'cAPrivateKey' "
+               "DESC 'X.509 CA private key, use ;binary' "
+               "SUP x509PrivateKey )",
+       "( " ACA_SCHEMA_AT ".2 NAME 'userPrivateKey' "
+               "DESC 'X.509 user private key, use ;binary' "
+               "SUP x509PrivateKey )",
+       NULL
+};
+
+static struct {
+       char *at;
+       AttributeDescription **ad;
+} aca_attr2[] = {
+       { "cACertificate;binary", &ad_caCert },
+       { "cAPrivateKey;binary", &ad_caPkey },
+       { "userCertificate;binary", &ad_usrCert },
+       { "userPrivateKey;binary", &ad_usrPkey },
+       { "mail", &ad_mail },
+       { NULL }
+};
+
+static struct {
+       char *ot;
+       ObjectClass **oc;
+} aca_ocs[] = {
+       { "( " ACA_SCHEMA_OC ".1 NAME 'autoCA' "
+               "DESC 'Automated PKI certificate authority' "
+               "SUP pkiCA AUXILIARY "
+               "MAY cAPrivateKey )", &oc_caObj },
+       { "( " ACA_SCHEMA_OC ".2 NAME 'autoCAuser' "
+               "DESC 'Automated PKI CA user' "
+               "SUP pkiUser AUXILIARY "
+               "MAY userPrivateKey )", &oc_usrObj },
+       { NULL }
+};
+
+typedef struct autoca_info {
+       X509 *ai_cert;
+       EVP_PKEY *ai_pkey;
+       ObjectClass *ai_usrclass;
+       ObjectClass *ai_srvclass;
+       int ai_usrkeybits;
+       int ai_srvkeybits;
+       int ai_cakeybits;
+       int ai_usrdays;
+       int ai_srvdays;
+       int ai_cadays;
+} autoca_info;
+
+/* Rewrite an LDAP DN in DER form
+ * Input must be valid DN, therefore no error checking is done here.
+ */
+static int autoca_dnbv2der( Operation *op, struct berval *bv, struct berval *der )
+{
+       BerElementBuffer berbuf;
+       BerElement *ber = (BerElement *)&berbuf;
+       LDAPDN dn;
+       LDAPRDN rdn;
+       LDAPAVA *ava;
+       AttributeDescription *ad;
+       int irdn, iava;
+
+       ldap_bv2dn_x( bv, &dn, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx );
+
+       ber_init2( ber, NULL, LBER_USE_DER );
+       ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+       /* count RDNs, we need them in reverse order */
+       for (irdn = 0; dn[irdn]; irdn++);
+       irdn--;
+
+       /* DN is a SEQuence of RDNs */
+       ber_start_seq( ber, LBER_SEQUENCE );
+       for (; irdn >=0; irdn--)
+       {
+               /* RDN is a SET of AVAs */
+               ber_start_set( ber, LBER_SET );
+               rdn = dn[irdn];
+               for (iava = 0; rdn[iava]; iava++)
+               {
+                       const char *text;
+                       char oid[1024];
+                       struct berval bvo = { sizeof(oid), oid };
+                       struct berval bva;
+
+                       /* AVA is a SEQuence of attr and value */
+                       ber_start_seq( ber, LBER_SEQUENCE );
+                       ava = rdn[iava];
+                       ad = NULL;
+                       slap_bv2ad( &ava->la_attr, &ad, &text );
+                       ber_str2bv( ad->ad_type->sat_oid, 0, 0, &bva );
+                       ber_encode_oid( &bva, &bvo );
+                       ber_put_berval( ber, &bvo, LBER_TAG_OID );
+                       ber_put_berval( ber, &ava->la_value, LBER_TAG_UTF8 );
+                       ber_put_seq( ber );
+               }
+               ber_put_set( ber );
+       }
+       ber_put_seq( ber );
+       ber_flatten2( ber, der, 0 );
+       ldap_dnfree_x( dn, op->o_tmpmemctx );
+       return 0;
+}
+
+static int autoca_genpkey(int bits, EVP_PKEY **pkey)
+{
+       EVP_PKEY_CTX *kctx;
+       int rc;
+
+       kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+       if (kctx == NULL)
+               return -1;
+       if (EVP_PKEY_keygen_init(kctx) <= 0)
+       {
+               EVP_PKEY_CTX_free(kctx);
+               return -1;
+       }
+       if (EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, bits) <= 0)
+       {
+               EVP_PKEY_CTX_free(kctx);
+               return -1;
+       }
+       rc = EVP_PKEY_keygen(kctx, pkey);
+       EVP_PKEY_CTX_free(kctx);
+       return rc;
+}
+
+static int autoca_signcert(X509 *cert, EVP_PKEY *pkey)
+{
+       EVP_MD_CTX *ctx = EVP_MD_CTX_create();
+       EVP_PKEY_CTX *pkctx = NULL;
+       int rc = -1;
+
+       if ( ctx == NULL )
+               return -1;
+       if (EVP_DigestSignInit(ctx, &pkctx, NULL, NULL, pkey))
+       {
+               rc = X509_sign_ctx(cert, ctx);
+       }
+       EVP_MD_CTX_destroy(ctx);
+       return rc;
+}
+
+#define SERIAL_BITS    64      /* should be less than 160 */
+
+typedef struct myext {
+       char *name;
+       char *value;
+} myext;
+
+static myext CAexts[] = {
+       { "subjectKeyIdentifier", "hash" },
+       { "authorityKeyIdentifier", "keyid:always,issuer" },
+       { "basicConstraints", "critical,CA:true" },
+       { "keyUsage", "digitalSignature,cRLSign,keyCertSign" },
+       { "nsComment", "OpenLDAP automatic certificate" },
+       { NULL }
+};
+
+static myext usrExts[] = {
+       { "subjectKeyIdentifier", "hash" },
+       { "authorityKeyIdentifier", "keyid:always,issuer" },
+       { "basicConstraints", "CA:false" },
+       { "keyUsage", "digitalSignature,nonRepudiation,keyEncipherment" },
+       { "extendedKeyUsage", "clientAuth,emailProtection,codeSigning" },
+       { "nsComment", "OpenLDAP automatic certificate" },
+       { NULL }
+};
+
+static myext srvExts[] = {
+       { "subjectKeyIdentifier", "hash" },
+       { "authorityKeyIdentifier", "keyid:always,issuer" },
+       { "basicConstraints", "CA:false" },
+       { "keyUsage", "digitalSignature,keyEncipherment" },
+       { "extendedKeyUsage", "serverAuth,clientAuth" },
+       { "nsComment", "OpenLDAP automatic certificate" },
+       { NULL }
+};
+
+typedef struct genargs {
+       X509 *issuer_cert;
+       EVP_PKEY *issuer_pkey;
+       struct berval *subjectDN;
+       myext *cert_exts;
+       myext *more_exts;
+       X509 *newcert;
+       EVP_PKEY *newpkey;
+       struct berval dercert;
+       struct berval derpkey;
+       int keybits;
+       int days;
+} genargs;
+
+static int autoca_gencert( Operation *op, genargs *args )
+{
+       X509_NAME *subj_name, *issuer_name;
+       X509 *subj_cert;
+       struct berval derdn;
+       const unsigned char *p;
+       EVP_PKEY *evpk = NULL;
+       int rc;
+       unsigned char *pp;
+
+       if ((subj_cert = X509_new()) == NULL)
+               return -1;
+
+       autoca_dnbv2der( op, args->subjectDN, &derdn );
+       p = (const unsigned char *)derdn.bv_val;
+       subj_name = d2i_X509_NAME( NULL, &p, derdn.bv_len );
+       op->o_tmpfree( derdn.bv_val, op->o_tmpmemctx );
+       if ( subj_name == NULL )
+       {
+fail1:
+               X509_free( subj_cert );
+               return -1;
+       }
+
+       rc = autoca_genpkey( args->keybits, &evpk );
+       if ( rc <= 0 )
+       {
+fail2:
+               if ( subj_name ) X509_NAME_free( subj_name );
+               goto fail1;
+       }
+       args->derpkey.bv_len = i2d_PrivateKey( evpk, NULL );
+       args->derpkey.bv_val = op->o_tmpalloc( args->derpkey.bv_len, op->o_tmpmemctx );
+       pp = args->derpkey.bv_val;
+       i2d_PrivateKey( evpk, &pp );
+       args->newpkey = evpk;
+
+       /* set random serial */
+       {
+               BIGNUM *bn = BN_new();
+               if ( bn == NULL )
+               {
+fail3:
+                       EVP_PKEY_free( evpk );
+                       goto fail2;
+               }
+               if (!BN_pseudo_rand(bn, SERIAL_BITS, 0, 0))
+               {
+                       BN_free( bn );
+                       goto fail3;
+               }
+               if (!BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(subj_cert)))
+               {
+                       BN_free( bn );
+                       goto fail3;
+               }
+               BN_free(bn);
+       }
+       if (args->issuer_cert) {
+               issuer_name = X509_get_subject_name(args->issuer_cert);
+       } else {
+               issuer_name = subj_name;
+               args->issuer_cert = subj_cert;
+               args->issuer_pkey = evpk;
+       }
+       if (!X509_set_version(subj_cert, 2) ||  /* set version to V3 */
+               !X509_set_issuer_name(subj_cert, issuer_name) ||
+               !X509_set_subject_name(subj_cert, subj_name) ||
+               !X509_gmtime_adj(X509_get_notBefore(subj_cert), 0) ||
+               !X509_time_adj_ex(X509_get_notAfter(subj_cert), args->days, 0, NULL) ||
+               !X509_set_pubkey(subj_cert, evpk))
+       {
+               goto fail3;
+       }
+       X509_NAME_free(subj_name);
+       subj_name = NULL;
+
+       /* set cert extensions */
+       {
+               X509V3_CTX ctx;
+               X509_EXTENSION *ext;
+               int i;
+
+               X509V3_set_ctx(&ctx, args->issuer_cert, subj_cert, NULL, NULL, 0);
+               for (i=0; args->cert_exts[i].name; i++) {
+                       ext = X509V3_EXT_nconf(NULL, &ctx, args->cert_exts[i].name, args->cert_exts[i].value);
+                       if ( ext == NULL )
+                               goto fail3;
+                       rc = X509_add_ext(subj_cert, ext, -1);
+                       X509_EXTENSION_free(ext);
+                       if ( !rc )
+                               goto fail3;
+               }
+               if (args->more_exts) {
+                       for (i=0; args->more_exts[i].name; i++) {
+                               ext = X509V3_EXT_nconf(NULL, &ctx, args->more_exts[i].name, args->more_exts[i].value);
+                               if ( ext == NULL )
+                                       goto fail3;
+                               rc = X509_add_ext(subj_cert, ext, -1);
+                               X509_EXTENSION_free(ext);
+                               if ( !rc )
+                                       goto fail3;
+                       }
+               }
+       }
+       rc = autoca_signcert( subj_cert, args->issuer_pkey );
+       if ( rc < 0 )
+               goto fail3;
+       args->dercert.bv_len = i2d_X509( subj_cert, NULL );
+       args->dercert.bv_val = op->o_tmpalloc( args->dercert.bv_len, op->o_tmpmemctx );
+       pp = args->dercert.bv_val;
+       i2d_X509( subj_cert, &pp );
+       args->newcert = subj_cert;
+       return 0;
+}
+
+typedef struct saveargs {
+       ObjectClass *oc;
+       struct berval *dercert;
+       struct berval *derpkey;
+       slap_overinst *on;
+       struct berval *dn;
+       struct berval *ndn;
+       int isca;
+} saveargs;
+
+static int autoca_savecert( Operation *op, saveargs *args )
+{
+       Modifications mod[3], *mp = mod;
+       struct berval bvs[6], *bp = bvs;
+       BackendInfo *bi;
+       slap_callback cb = {0};
+       SlapReply rs = {REP_RESULT};
+
+       if ( args->oc ) {
+               mp->sml_numvals = 1;
+               mp->sml_values = bp;
+               mp->sml_nvalues = NULL;
+               mp->sml_desc = slap_schema.si_ad_objectClass;
+               mp->sml_op = LDAP_MOD_ADD;
+               mp->sml_flags = SLAP_MOD_INTERNAL;
+               *bp++ = args->oc->soc_cname;
+               BER_BVZERO( bp );
+               bp++;
+               mp->sml_next = mp+1;
+               mp++;
+       }
+       mp->sml_numvals = 1;
+       mp->sml_values = bp;
+       mp->sml_nvalues = NULL;
+       mp->sml_desc = args->isca ? ad_caCert : ad_usrCert;
+       mp->sml_op = LDAP_MOD_REPLACE;
+       mp->sml_flags = SLAP_MOD_INTERNAL;
+       *bp++ = *args->dercert;
+       BER_BVZERO( bp );
+       bp++;
+       mp->sml_next = mp+1;
+       mp++;
+
+       mp->sml_numvals = 1;
+       mp->sml_values = bp;
+       mp->sml_nvalues = NULL;
+       mp->sml_desc = args->isca ? ad_caPkey : ad_usrPkey;
+       mp->sml_op = LDAP_MOD_ADD;
+       mp->sml_flags = SLAP_MOD_INTERNAL;
+       *bp++ = *args->derpkey;
+       BER_BVZERO( bp );
+       mp->sml_next = NULL;
+
+       cb.sc_response = slap_null_cb;
+       bi = op->o_bd->bd_info;
+       op->o_bd->bd_info = args->on->on_info->oi_orig;
+       op->o_tag = LDAP_REQ_MODIFY;
+       op->o_callback = &cb;
+       op->orm_modlist = mod;
+       op->orm_no_opattrs = 1;
+       op->o_req_dn = *args->dn;
+       op->o_req_ndn = *args->ndn;
+       op->o_bd->be_modify( op, &rs );
+       op->o_bd->bd_info = bi;
+       return rs.sr_err;
+}
+
+enum {
+       ACA_USRCLASS = 1,
+       ACA_SRVCLASS,
+       ACA_USRKEYBITS,
+       ACA_SRVKEYBITS,
+       ACA_CAKEYBITS,
+       ACA_USRDAYS,
+       ACA_SRVDAYS,
+       ACA_CADAYS
+};
+
+static int autoca_cf( ConfigArgs *c )
+{
+       slap_overinst *on = (slap_overinst *)c->bi;
+       autoca_info *ai = on->on_bi.bi_private;
+       int rc = 0;
+
+       switch( c->op ) {
+       case SLAP_CONFIG_EMIT:
+               switch( c->type ) {
+               case ACA_USRCLASS:
+                       if ( ai->ai_usrclass ) {
+                               c->value_string = ch_strdup( ai->ai_usrclass->soc_cname.bv_val );
+                       } else {
+                               rc = 1;
+                       }
+                       break;
+               case ACA_SRVCLASS:
+                       if ( ai->ai_srvclass ) {
+                               c->value_string = ch_strdup( ai->ai_srvclass->soc_cname.bv_val );
+                       } else {
+                               rc = 1;
+                       }
+                       break;
+               case ACA_USRKEYBITS:
+                       c->value_int = ai->ai_usrkeybits;
+                       break;
+               case ACA_SRVKEYBITS:
+                       c->value_int = ai->ai_srvkeybits;
+                       break;
+               case ACA_CAKEYBITS:
+                       c->value_int = ai->ai_cakeybits;
+                       break;
+               case ACA_USRDAYS:
+                       c->value_int = ai->ai_usrdays;
+                       break;
+               case ACA_SRVDAYS:
+                       c->value_int = ai->ai_srvdays;
+                       break;
+               case ACA_CADAYS:
+                       c->value_int = ai->ai_cadays;
+                       break;
+               }
+               break;
+       case LDAP_MOD_DELETE:
+               switch( c->type ) {
+               case ACA_USRCLASS:
+                       ai->ai_usrclass = NULL;
+                       break;
+               case ACA_SRVCLASS:
+                       ai->ai_srvclass = NULL;
+                       break;
+               /* single-valued attrs, all no-ops */
+               }
+               break;
+       case SLAP_CONFIG_ADD:
+       case LDAP_MOD_ADD:
+               switch( c->type ) {
+               case ACA_USRCLASS:
+                       {
+                               ObjectClass *oc = oc_find( c->value_string );
+                               if ( oc )
+                                       ai->ai_usrclass = oc;
+                               else
+                                       rc = 1;
+                       }
+                       break;
+               case ACA_SRVCLASS:
+                       {
+                               ObjectClass *oc = oc_find( c->value_string );
+                               if ( oc )
+                                       ai->ai_srvclass = oc;
+                               else
+                                       rc = 1;
+                       }
+               case ACA_USRKEYBITS:
+                       if ( c->value_int < MIN_KEYBITS )
+                               rc = 1;
+                       else
+                               ai->ai_usrkeybits = c->value_int;
+                       break;
+               case ACA_SRVKEYBITS:
+                       if ( c->value_int < MIN_KEYBITS )
+                               rc = 1;
+                       else
+                               ai->ai_srvkeybits = c->value_int;
+                       break;
+               case ACA_CAKEYBITS:
+                       if ( c->value_int < MIN_KEYBITS )
+                               rc = 1;
+                       else
+                               ai->ai_cakeybits = c->value_int;
+                       break;
+               case ACA_USRDAYS:
+                       ai->ai_usrdays = c->value_int;
+                       break;
+               case ACA_SRVDAYS:
+                       ai->ai_srvdays = c->value_int;
+                       break;
+               case ACA_CADAYS:
+                       ai->ai_cadays = c->value_int;
+                       break;
+               }
+       }
+       return rc;
+}
+
+static ConfigTable autoca_cfg[] = {
+       { "userClass", "objectclass", 2, 2, 0,
+         ARG_STRING|ARG_MAGIC|ACA_USRCLASS, autoca_cf,
+         "( OLcfgOvAt:22.1 NAME 'olcACAuserClass' "
+         "DESC 'ObjectClass of user entries' "
+         "SYNTAX OMsDirectoryString )", NULL, NULL },
+       { "servererClass", "objectclass", 2, 2, 0,
+         ARG_STRING|ARG_MAGIC|ACA_SRVCLASS, autoca_cf,
+         "( OLcfgOvAt:22.2 NAME 'olcACAserverClass' "
+         "DESC 'ObjectClass of server entries' "
+         "SYNTAX OMsDirectoryString )", NULL, NULL },
+       { "userKeybits", "integer", 2, 2, 0,
+         ARG_INT|ARG_MAGIC|ACA_USRKEYBITS, autoca_cf,
+         "( OLcfgOvAt:22.3 NAME 'olcACAuserKeybits' "
+         "DESC 'Size of PrivateKey for user entries' "
+         "SYNTAX OMsInteger )", NULL, NULL },
+       { "serverKeybits", "integer", 2, 2, 0,
+         ARG_INT|ARG_MAGIC|ACA_SRVKEYBITS, autoca_cf,
+         "( OLcfgOvAt:22.4 NAME 'olcACAserverKeybits' "
+         "DESC 'Size of PrivateKey for server entries' "
+         "SYNTAX OMsInteger )", NULL, NULL },
+       { "caKeybits", "integer", 2, 2, 0,
+         ARG_INT|ARG_MAGIC|ACA_CAKEYBITS, autoca_cf,
+         "( OLcfgOvAt:22.5 NAME 'olcACAKeybits' "
+         "DESC 'Size of PrivateKey for CA certificate' "
+         "SYNTAX OMsInteger )", NULL, NULL },
+       { "userDays", "integer", 2, 2, 0,
+         ARG_INT|ARG_MAGIC|ACA_USRDAYS, autoca_cf,
+         "( OLcfgOvAt:22.6 NAME 'olcACAuserDays' "
+         "DESC 'Lifetime of user certificates in days' "
+         "SYNTAX OMsInteger )", NULL, NULL },
+       { "serverDays", "integer", 2, 2, 0,
+         ARG_INT|ARG_MAGIC|ACA_SRVDAYS, autoca_cf,
+         "( OLcfgOvAt:22.7 NAME 'olcACAserverDays' "
+         "DESC 'Lifetime of server certificates in days' "
+         "SYNTAX OMsInteger )", NULL, NULL },
+       { "caDays", "integer", 2, 2, 0,
+         ARG_INT|ARG_MAGIC|ACA_CADAYS, autoca_cf,
+         "( OLcfgOvAt:22.8 NAME 'olcACADays' "
+         "DESC 'Lifetime of CA certificate in days' "
+         "SYNTAX OMsInteger )", NULL, NULL },
+       { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs autoca_ocs[] = {
+       { "( OLcfgOvOc:22.1 "
+         "NAME 'olcACAConfig' "
+         "DESC 'AutoCA configuration' "
+         "SUP olcOverlayConfig "
+         "MAY ( olcACAuserClass $ olcACAserverClass $ "
+          "olcACAuserKeybits $ olcACAserverKeybits $ olcACAKeyBits $ "
+          "olcACAuserDays $ olcACAserverDays $ olcACADays ) )",
+         Cft_Overlay, autoca_cfg },
+       { NULL, 0, NULL }
+};
+
+static int
+autoca_op_response(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       slap_overinst *on = op->o_callback->sc_private;
+       autoca_info *ai = on->on_bi.bi_private;
+       Attribute *a;
+       int isusr = 0;
+
+       if (rs->sr_type != REP_SEARCH)
+               return SLAP_CB_CONTINUE;
+
+       /* If root or self */
+       if ( !be_isroot( op ) &&
+               !dn_match( &rs->sr_entry->e_nname, &op->o_ndn ))
+               return SLAP_CB_CONTINUE;
+
+       isusr = is_entry_objectclass( rs->sr_entry, ai->ai_usrclass, SLAP_OCF_CHECK_SUP );
+       if ( !isusr )
+       {
+               if (!is_entry_objectclass( rs->sr_entry, ai->ai_srvclass, SLAP_OCF_CHECK_SUP ))
+                       return SLAP_CB_CONTINUE;
+       }
+       a = attr_find( rs->sr_entry->e_attrs, ad_usrPkey );
+       if ( !a )
+       {
+               Operation op2;
+               genargs args;
+               saveargs arg2;
+               myext extras[2];
+               int rc;
+
+               args.issuer_cert = ai->ai_cert;
+               args.issuer_pkey = ai->ai_pkey;
+               args.subjectDN = &rs->sr_entry->e_name;
+               args.more_exts = NULL;
+               if ( isusr )
+               {
+                       args.cert_exts = usrExts;
+                       args.keybits = ai->ai_usrkeybits;
+                       args.days = ai->ai_usrdays;
+                       a = attr_find( rs->sr_entry->e_attrs, ad_mail );
+                       if ( a )
+                       {
+                               extras[0].name = "subjectAltName";
+                               extras[1].name = NULL;
+                               extras[0].value = op->o_tmpalloc( sizeof("email:") + a->a_vals[0].bv_len, op->o_tmpmemctx );
+                               sprintf(extras[0].value, "email:%s", a->a_vals[0].bv_val);
+                               args.more_exts = extras;
+                       }
+               } else
+               {
+                       args.cert_exts = srvExts;
+                       args.keybits = ai->ai_srvkeybits;
+                       args.days = ai->ai_srvdays;
+                       if ( ad_ipaddr && (a = attr_find( rs->sr_entry->e_attrs, ad_ipaddr )))
+                       {
+                               extras[0].name = "subjectAltName";
+                               extras[1].name = NULL;
+                               extras[0].value = op->o_tmpalloc( sizeof("IP:") + a->a_vals[0].bv_len, op->o_tmpmemctx );
+                               sprintf(extras[0].value, "IP:%s", a->a_vals[0].bv_val);
+                               args.more_exts = extras;
+                       }
+               }
+               rc = autoca_gencert( op, &args );
+               if ( rc )
+                       return SLAP_CB_CONTINUE;
+               X509_free( args.newcert );
+               EVP_PKEY_free( args.newpkey );
+
+               if ( is_entry_objectclass( rs->sr_entry, oc_usrObj, 0 ))
+                       arg2.oc = NULL;
+               else
+                       arg2.oc = oc_usrObj;
+               arg2.dercert = &args.dercert;
+               arg2.derpkey = &args.derpkey;
+               arg2.on = on;
+               arg2.dn = &rs->sr_entry->e_name;
+               arg2.ndn = &rs->sr_entry->e_nname;
+               arg2.isca = 0;
+               op2 = *op;
+               rc = autoca_savecert( &op2, &arg2 );
+               if ( !rc )
+               {
+                       if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ))
+                       {
+                               Entry *e = entry_dup( rs->sr_entry );
+                               rs_replace_entry( op, rs, on, e );
+                               rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+                       }
+                       attr_merge_one( rs->sr_entry, ad_usrCert, &args.dercert, NULL );
+                       attr_merge_one( rs->sr_entry, ad_usrPkey, &args.derpkey, NULL );
+               }
+               op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx );
+               op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx );
+       }
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+autoca_op_search(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       /* we only act on a search that returns just our cert/key attrs */
+       if ( op->ors_attrs[0].an_desc == ad_usrCert &&
+               op->ors_attrs[1].an_desc == ad_usrPkey &&
+               op->ors_attrs[2].an_name.bv_val == NULL )
+       {
+               slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+               slap_callback *sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+               sc->sc_response = autoca_op_response;
+               sc->sc_private = on;
+               sc->sc_next = op->o_callback;
+               op->o_callback = sc;
+       }
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+autoca_db_init(
+       BackendDB *be,
+       ConfigReply *cr
+)
+{
+       slap_overinst *on = (slap_overinst *) be->bd_info;
+       autoca_info *ai;
+
+       ai = ch_calloc(1, sizeof(autoca_info));
+       on->on_bi.bi_private = ai;
+
+       /* set defaults */
+       ai->ai_usrclass = oc_find( "person" );
+       ai->ai_srvclass = oc_find( "ipHost" );
+       ai->ai_usrkeybits = KEYBITS;
+       ai->ai_srvkeybits = KEYBITS;
+       ai->ai_cakeybits = KEYBITS;
+       ai->ai_usrdays = 365;   /* 1 year */
+       ai->ai_srvdays = 1826;  /* 5 years */
+       ai->ai_cadays = 3652;   /* 10 years */
+       return 0;
+}
+
+static int
+autoca_db_destroy(
+       BackendDB *be,
+       ConfigReply *cr
+)
+{
+       slap_overinst *on = (slap_overinst *) be->bd_info;
+       autoca_info *ai = on->on_bi.bi_private;
+
+       if ( ai->ai_cert )
+               X509_free( ai->ai_cert );
+       if ( ai->ai_pkey )
+               EVP_PKEY_free( ai->ai_pkey );
+       ch_free( ai );
+
+       return 0;
+}
+
+static int
+autoca_db_open(
+       BackendDB *be,
+       ConfigReply *cr
+)
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       autoca_info *ai = on->on_bi.bi_private;
+
+       Connection conn = { 0 };
+       OperationBuffer opbuf;
+       Operation *op;
+       void *thrctx;
+       Entry *e;
+       Attribute *a;
+       int rc;
+
+       if (slapMode & SLAP_TOOL_MODE)
+               return 0;
+
+       thrctx = ldap_pvt_thread_pool_context();
+       connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+       op = &opbuf.ob_op;
+       op->o_bd = be;
+       op->o_dn = be->be_rootdn;
+       op->o_ndn = be->be_rootndn;
+       rc = overlay_entry_get_ov( op, be->be_nsuffix, NULL, 
+               NULL, 0, &e, on );
+
+       if ( e ) {
+               int gotoc = 0, gotat = 0;
+               if ( is_entry_objectclass( e, oc_caObj, 0 )) {
+                       gotoc = 1;
+                       a = attr_find( e->e_attrs, ad_caPkey );
+                       if ( a ) {
+                               const unsigned char *pp;
+                               pp = a->a_vals[0].bv_val;
+                               ai->ai_pkey = d2i_AutoPrivateKey( NULL, &pp, a->a_vals[0].bv_len );
+                               if ( ai->ai_pkey )
+                               {
+                                       a = attr_find( e->e_attrs, ad_caCert );
+                                       if ( a )
+                                       {
+                                               pp = a->a_vals[0].bv_val;
+                                               ai->ai_cert = d2i_X509( NULL, &pp, a->a_vals[0].bv_len );
+                                       }
+                               }
+                               gotat = 1;
+                       }
+               }
+               overlay_entry_release_ov( op, e, 0, on );
+               /* generate attrs, store... */
+               if ( !gotat ) {
+                       genargs args;
+                       saveargs arg2;
+
+                       args.issuer_cert = NULL;
+                       args.issuer_pkey = NULL;
+                       args.subjectDN = &be->be_suffix[0];
+                       args.cert_exts = CAexts;
+                       args.more_exts = NULL;
+                       args.keybits = ai->ai_cakeybits;
+                       args.days = ai->ai_cadays;
+
+                       rc = autoca_gencert( op, &args );
+                       if ( rc )
+                               return -1;
+
+                       ai->ai_cert = args.newcert;
+                       ai->ai_pkey = args.newpkey;
+
+                       arg2.dn = be->be_suffix;
+                       arg2.ndn = be->be_nsuffix;
+                       arg2.isca = 1;
+                       if ( !gotoc )
+                               arg2.oc = oc_caObj;
+                       else
+                               arg2.oc = NULL;
+                       arg2.on = on;
+                       arg2.dercert = &args.dercert;
+                       arg2.derpkey = &args.derpkey;
+
+                       autoca_savecert( op, &arg2 );
+                       op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx );
+                       op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx );
+               }
+       }
+
+       return 0;
+}
+
+static slap_overinst autoca;
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+int autoca_initialize() {
+       int i, code;
+       const char *text;
+
+       autoca.on_bi.bi_type = "autoca";
+       autoca.on_bi.bi_db_init = autoca_db_init;
+       autoca.on_bi.bi_db_destroy = autoca_db_destroy;
+       autoca.on_bi.bi_db_open = autoca_db_open;
+       autoca.on_bi.bi_op_search = autoca_op_search;
+
+       autoca.on_bi.bi_cf_ocs = autoca_ocs;
+       code = config_register_schema( autoca_cfg, autoca_ocs );
+       if ( code ) return code;
+
+       code = register_syntax( &aca_syntax );
+       if ( code ) return code;
+
+       code = register_matching_rule( &aca_mrule );
+       if ( code ) return code;
+
+       for ( i=0; aca_attrs[i]; i++ ) {
+               code = register_at( aca_attrs[i], NULL, 0 );
+               if ( code ) return code;
+       }
+
+       for ( i=0; aca_attr2[i].at; i++ ) {
+               code = slap_str2ad( aca_attr2[i].at, aca_attr2[i].ad, &text );
+               if ( code ) return code;
+       }
+
+       /* Schema may not be loaded, ignore if missing */
+       slap_str2ad( "ipHostNumber", &ad_ipaddr, &text );
+
+       for ( i=0; aca_ocs[i].ot; i++ ) {
+               code = register_oc( aca_ocs[i].ot, aca_ocs[i].oc, 0 );
+               if ( code ) return code;
+       }
+
+
+       return overlay_register( &autoca );
+}
+
+#if SLAPD_OVER_AUTOCA == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+       return autoca_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_AUTOCA) */