/* autoca.c - Automatic Certificate Authority */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2009-2020 The OpenLDAP Foundation. * Copyright 2009-2018 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 * . */ /* ACKNOWLEDGEMENTS: * This work was initially developed by Howard Chu for inclusion in * OpenLDAP Software. */ #include "portable.h" #ifdef SLAPD_OVER_AUTOCA #include #include #include #include "lutil.h" #include "slap.h" #include "config.h" #include #include #include #include /* Starting with OpenSSL 1.1.0, rsa.h is no longer included in * x509.h, so we need to explicitly include it for the * call to EVP_PKEY_CTX_set_rsa_keygen_bits */ #if OPENSSL_VERSION_NUMBER >= 0x10100000 #include #define X509_get_notBefore(x) X509_getm_notBefore(x) #define X509_get_notAfter(x) X509_getm_notAfter(x) #endif /* 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" static AttributeDescription *ad_caCert, *ad_caPkey, *ad_usrCert, *ad_usrPkey; static AttributeDescription *ad_mail, *ad_ipaddr; static ObjectClass *oc_caObj, *oc_usrObj; static char *aca_attrs[] = { "( " ACA_SCHEMA_AT ".1 NAME 'cAPrivateKey' " "DESC 'X.509 CA private key, use ;binary' " "SUP pKCS8PrivateKey )", "( " ACA_SCHEMA_AT ".2 NAME 'userPrivateKey' " "DESC 'X.509 user private key, use ;binary' " "SUP pKCS8PrivateKey )", 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; struct berval ai_localdn; struct berval ai_localndn; 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; unsigned char *pp; EVP_PKEY *evpk = NULL; int rc; if ((subj_cert = X509_new()) == NULL) return -1; autoca_dnbv2der( op, args->subjectDN, &derdn ); pp = (unsigned char *)derdn.bv_val; subj_name = d2i_X509_NAME( NULL, (const unsigned char **)&pp, 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; } /* encode DER in PKCS#8 */ { PKCS8_PRIV_KEY_INFO *p8inf; if (( p8inf = EVP_PKEY2PKCS8( evpk )) == NULL ) goto fail2; args->derpkey.bv_len = i2d_PKCS8_PRIV_KEY_INFO( p8inf, NULL ); args->derpkey.bv_val = op->o_tmpalloc( args->derpkey.bv_len, op->o_tmpmemctx ); pp = (unsigned char *)args->derpkey.bv_val; i2d_PKCS8_PRIV_KEY_INFO( p8inf, &pp ); PKCS8_PRIV_KEY_INFO_free( p8inf ); } 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 = (unsigned char *)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; } static const struct berval configDN = BER_BVC("cn=config"); /* must run as a pool thread to avoid cn=config deadlock */ static void * autoca_setca_task( void *ctx, void *arg ) { Connection conn = { 0 }; OperationBuffer opbuf; Operation *op; struct berval *cacert = arg; Modifications mod; struct berval bvs[2]; slap_callback cb = {0}; SlapReply rs = {REP_RESULT}; const char *text; connection_fake_init( &conn, &opbuf, ctx ); op = &opbuf.ob_op; mod.sml_numvals = 1; mod.sml_values = bvs; mod.sml_nvalues = NULL; mod.sml_desc = NULL; if ( slap_str2ad( "olcTLSCACertificate;binary", &mod.sml_desc, &text )) goto leave; mod.sml_op = LDAP_MOD_REPLACE; mod.sml_flags = SLAP_MOD_INTERNAL; bvs[0] = *cacert; BER_BVZERO( &bvs[1] ); mod.sml_next = NULL; cb.sc_response = slap_null_cb; op->o_bd = select_backend( (struct berval *)&configDN, 0 ); if ( !op->o_bd ) goto leave; op->o_tag = LDAP_REQ_MODIFY; op->o_callback = &cb; op->orm_modlist = &mod; op->orm_no_opattrs = 1; op->o_req_dn = configDN; op->o_req_ndn = configDN; op->o_dn = op->o_bd->be_rootdn; op->o_ndn = op->o_bd->be_rootndn; op->o_bd->be_modify( op, &rs ); leave: ch_free( arg ); return NULL; } static int autoca_setca( struct berval *cacert ) { struct berval *bv = ch_malloc( sizeof(struct berval) + cacert->bv_len ); bv->bv_len = cacert->bv_len; bv->bv_val = (char *)(bv+1); AC_MEMCPY( bv->bv_val, cacert->bv_val, bv->bv_len ); return ldap_pvt_thread_pool_submit( &connection_pool, autoca_setca_task, bv ); } static int autoca_setlocal( Operation *op, struct berval *cert, struct berval *pkey ) { Modifications mod[2]; struct berval bvs[4]; slap_callback cb = {0}; SlapReply rs = {REP_RESULT}; const char *text; mod[0].sml_numvals = 1; mod[0].sml_values = bvs; mod[0].sml_nvalues = NULL; mod[0].sml_desc = NULL; if ( slap_str2ad( "olcTLSCertificate;binary", &mod[0].sml_desc, &text )) return -1; mod[0].sml_op = LDAP_MOD_REPLACE; mod[0].sml_flags = SLAP_MOD_INTERNAL; bvs[0] = *cert; BER_BVZERO( &bvs[1] ); mod[0].sml_next = &mod[1]; mod[1].sml_numvals = 1; mod[1].sml_values = &bvs[2]; mod[1].sml_nvalues = NULL; mod[1].sml_desc = NULL; if ( slap_str2ad( "olcTLSCertificateKey;binary", &mod[1].sml_desc, &text )) return -1; mod[1].sml_op = LDAP_MOD_REPLACE; mod[1].sml_flags = SLAP_MOD_INTERNAL; bvs[2] = *pkey; BER_BVZERO( &bvs[3] ); mod[1].sml_next = NULL; cb.sc_response = slap_null_cb; op->o_bd = select_backend( (struct berval *)&configDN, 0 ); if ( !op->o_bd ) return -1; op->o_tag = LDAP_REQ_MODIFY; op->o_callback = &cb; op->orm_modlist = mod; op->orm_no_opattrs = 1; op->o_req_dn = configDN; op->o_req_ndn = configDN; op->o_dn = op->o_bd->be_rootdn; op->o_ndn = op->o_bd->be_rootndn; op->o_bd->be_modify( op, &rs ); return rs.sr_err; } enum { ACA_USRCLASS = 1, ACA_SRVCLASS, ACA_USRKEYBITS, ACA_SRVKEYBITS, ACA_CAKEYBITS, ACA_USRDAYS, ACA_SRVDAYS, ACA_CADAYS, ACA_LOCALDN }; 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; case ACA_LOCALDN: if ( !BER_BVISNULL( &ai->ai_localdn )) { rc = value_add_one( &c->rvalue_vals, &ai->ai_localdn ); } else { rc = 1; } 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; case ACA_LOCALDN: if ( ai->ai_localdn.bv_val ) { ch_free( ai->ai_localdn.bv_val ); ch_free( ai->ai_localndn.bv_val ); BER_BVZERO( &ai->ai_localdn ); BER_BVZERO( &ai->ai_localndn ); } 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; case ACA_LOCALDN: if ( c->be->be_nsuffix == NULL ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "suffix must be set" ); Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n", c->cr_msg ); rc = ARG_BAD_CONF; break; } if ( !dnIsSuffix( &c->value_ndn, c->be->be_nsuffix )) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "DN is not a subordinate of backend" ); Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n", c->cr_msg ); rc = ARG_BAD_CONF; break; } if ( ai->ai_localdn.bv_val ) { ch_free( ai->ai_localdn.bv_val ); ch_free( ai->ai_localndn.bv_val ); } ai->ai_localdn = c->value_dn; ai->ai_localndn = c->value_ndn; } } 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' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, { "serverClass", "objectclass", 2, 2, 0, ARG_STRING|ARG_MAGIC|ACA_SRVCLASS, autoca_cf, "( OLcfgOvAt:22.2 NAME 'olcACAserverClass' " "DESC 'ObjectClass of server entries' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString SINGLE-VALUE )", 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' " "EQUALITY integerMatch " "SYNTAX OMsInteger SINGLE-VALUE )", 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' " "EQUALITY integerMatch " "SYNTAX OMsInteger SINGLE-VALUE )", 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' " "EQUALITY integerMatch " "SYNTAX OMsInteger SINGLE-VALUE )", 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' " "EQUALITY integerMatch " "SYNTAX OMsInteger SINGLE-VALUE )", 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' " "EQUALITY integerMatch " "SYNTAX OMsInteger SINGLE-VALUE )", 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' " "EQUALITY integerMatch " "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, { "localdn", "dn", 2, 2, 0, ARG_DN|ARG_MAGIC|ACA_LOCALDN, autoca_cf, "( OLcfgOvAt:22.9 NAME 'olcACAlocalDN' " "DESC 'DN of local server cert' " "EQUALITY distinguishedNameMatch " "SYNTAX OMsDN SINGLE-VALUE )", 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 $ " "olcACAlocalDN ) )", 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; 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; } 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 this is our cert DN, configure it */ if ( dn_match( &rs->sr_entry->e_nname, &ai->ai_localndn )) autoca_setlocal( &op2, &args.dercert, &args.derpkey ); 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 && 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; if ( ! *aca_attr2[0].ad ) { int i, code; const char *text; 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; } } 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 = (unsigned char *)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 = (unsigned char *)a->a_vals[0].bv_val; ai->ai_cert = d2i_X509( NULL, &pp, a->a_vals[0].bv_len ); /* If TLS wasn't configured yet, set this as our CA */ if ( !slap_tls_ctx ) autoca_setca( a->a_vals ); } } 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 ); /* If TLS wasn't configured yet, set this as our CA */ if ( !slap_tls_ctx ) autoca_setca( &args.dercert ); 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; 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; for ( i=0; aca_attrs[i]; i++ ) { code = register_at( aca_attrs[i], NULL, 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) */