Add autoca overlay

Automated certificate authority
This commit is contained in:
Howard Chu 2017-04-07 15:25:37 +01:00
parent ec5af7b5e7
commit 2b920ecaec
4 changed files with 1091 additions and 0 deletions

View 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

103
doc/man/man5/slapo-autoca.5 Normal file
View file

@ -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

View 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)

View file

@ -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) */