diff --git a/CHANGES b/CHANGES index 13439c53c9b8db3eff710b572f3c6d89d6fdaa44..918fd14b8db72d001dab991b0b103db4f75c0cbb 100644 --- a/CHANGES +++ b/CHANGES @@ -2,19 +2,23 @@ OpenLDAP 2.4 Change Log OpenLDAP 2.4.14 Engineering Added libldap option to disable SASL host canonicalization (ITS#5812) + Added libldap TLS_PROTOCOL_MIN (ITS#5655) + Added libldap GnuTLS support for TLS_CIPHER_SUITE (ITS#5887) + Added libldap GnuTLS setting random file (ITS#5462) Fixed libldap deref handling (ITS#5768) Fixed libldap peer cert memory leak (ITS#5849) + Fixed libldap interaction with GnuTLS CN IP-based matches (ITS#5789) Fixed libldap intermediate response behavior (ITS#5896) Fixed libldap_r deref building (ITS#5768) Fixed libldap_r slapd lockup when paused during shutdown (ITS#5841) Added slapd syncrepl default retry setting (ITS#5825) Added slapd val.regex expansion (ITS#5804) - Added slapo-rwm newRDN rewriting (ITS#5834) + Added slapd TLS_PROTOCOL_MIN (ITS#5655) + Added slapd slapi_pw_find (ITS#2615,ITS#4359) Fixed slapd bconfig to return error codes (ITS#5867) Fixed slapd bconfig encoding incorrectly (ITS#5897) Fixed slapd connection assert (ITS#5835) Fixed slapd epoll handling (ITS#5886) - Added slapd slapi_pw_find (ITS#2615,ITS#4359) Fixed slapd syncrepl rename handling (ITS#5809) Fixed slapd syncrepl MMR when adding new server (ITS#5850) Fixed slapd syncrepl MMR with deleted entries (ITS#5843) @@ -35,6 +39,7 @@ OpenLDAP 2.4.14 Engineering Fixed slapo-pcache filter sorting (ITS#5756) Fixed slapo-ppolicy to not be global (ITS#5858) Fixed slapo-rwm with back-config (ITS#5906) + Added slapo-rwm newRDN rewriting (ITS#5834) Updated contrib/addpartial module (ITS#5764) Added contrib/cloak module (ITS#5872) Added contrib/smbk5pwd gcrypt support (ITS#5410) diff --git a/include/ldap.h b/include/ldap.h index 3dff72ad683bf1ca9383de443612441a3ed12743..19e25fb7a7c2824396586efa23622248b071d5e2 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -144,7 +144,7 @@ LDAP_BEGIN_DECL #define LDAP_OPT_X_TLS_CERTFILE 0x6004 #define LDAP_OPT_X_TLS_KEYFILE 0x6005 #define LDAP_OPT_X_TLS_REQUIRE_CERT 0x6006 -/* #define LDAP_OPT_X_TLS_PROTOCOL 0x6007 */ +#define LDAP_OPT_X_TLS_PROTOCOL_MIN 0x6007 #define LDAP_OPT_X_TLS_CIPHER_SUITE 0x6008 #define LDAP_OPT_X_TLS_RANDOM_FILE 0x6009 #define LDAP_OPT_X_TLS_SSL_CTX 0x600a /* OpenSSL SSL* */ @@ -165,6 +165,14 @@ LDAP_BEGIN_DECL #define LDAP_OPT_X_TLS_CRL_PEER 1 #define LDAP_OPT_X_TLS_CRL_ALL 2 +/* for LDAP_OPT_X_TLS_PROTOCOL_MIN */ +#define LDAP_OPT_X_TLS_PROTOCOL(maj,min) (((maj) << 8) + (min)) +#define LDAP_OPT_X_TLS_PROTOCOL_SSL2 (2 << 8) +#define LDAP_OPT_X_TLS_PROTOCOL_SSL3 (3 << 8) +#define LDAP_OPT_X_TLS_PROTOCOL_TLS1_0 ((3 << 8) + 1) +#define LDAP_OPT_X_TLS_PROTOCOL_TLS1_1 ((3 << 8) + 2) +#define LDAP_OPT_X_TLS_PROTOCOL_TLS1_2 ((3 << 8) + 3) + /* OpenLDAP SASL options */ #define LDAP_OPT_X_SASL_MECH 0x6100 #define LDAP_OPT_X_SASL_REALM 0x6101 diff --git a/libraries/libldap/Makefile.in b/libraries/libldap/Makefile.in index 4e393cc5625a1f7598f95701ae0c8c72942a07f7..004bff7f3bb9bad23e37b91b823b5a4c9599395e 100644 --- a/libraries/libldap/Makefile.in +++ b/libraries/libldap/Makefile.in @@ -25,7 +25,8 @@ SRCS = bind.c open.c result.c error.c compare.c search.c \ getdn.c getentry.c getattr.c getvalues.c addentry.c \ request.c os-ip.c url.c pagectrl.c sortctrl.c vlvctrl.c \ init.c options.c print.c string.c util-int.c schema.c \ - charray.c tls.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \ + charray.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \ + tls2.c tls_o.c tls_g.c tls_m.c \ turn.c ppolicy.c dds.c txn.c ldap_sync.c stctrl.c \ assertion.c deref.c @@ -37,7 +38,8 @@ OBJS = bind.lo open.lo result.lo error.lo compare.lo search.lo \ getdn.lo getentry.lo getattr.lo getvalues.lo addentry.lo \ request.lo os-ip.lo url.lo pagectrl.lo sortctrl.lo vlvctrl.lo \ init.lo options.lo print.lo string.lo util-int.lo schema.lo \ - charray.lo tls.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \ + charray.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \ + tls2.lo tls_o.lo tls_g.lo tls_m.lo \ turn.lo ppolicy.lo dds.lo txn.lo ldap_sync.lo stctrl.lo \ assertion.lo deref.lo diff --git a/libraries/libldap/init.c b/libraries/libldap/init.c index eb5ff66f591008267a7d91d17a10e5514a15d8c8..8cf2530b399144872389dcbe80c40dd139c76421 100644 --- a/libraries/libldap/init.c +++ b/libraries/libldap/init.c @@ -123,6 +123,7 @@ static const struct ol_attribute { {0, ATTR_TLS, "TLS_REQCERT", NULL, LDAP_OPT_X_TLS_REQUIRE_CERT}, {0, ATTR_TLS, "TLS_RANDFILE", NULL, LDAP_OPT_X_TLS_RANDOM_FILE}, {0, ATTR_TLS, "TLS_CIPHER_SUITE", NULL, LDAP_OPT_X_TLS_CIPHER_SUITE}, + {0, ATTR_TLS, "TLS_PROTOCOL_MIN", NULL, LDAP_OPT_X_TLS_PROTOCOL_MIN}, #ifdef HAVE_OPENSSL_CRL {0, ATTR_TLS, "TLS_CRLCHECK", NULL, LDAP_OPT_X_TLS_CRLCHECK}, diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h index 01bac59765e6e74f292bde0171ef599499a0d229..6b6cf08af4f59e8024b8e686e038f88672ff8f48 100644 --- a/libraries/libldap/ldap-int.h +++ b/libraries/libldap/ldap-int.h @@ -155,9 +155,9 @@ struct ldaptls { char *lt_cacertfile; char *lt_cacertdir; char *lt_ciphersuite; -#ifdef HAVE_GNUTLS char *lt_crlfile; -#endif + char *lt_randfile; /* OpenSSL only */ + int lt_protocol_min; }; #endif @@ -205,9 +205,12 @@ struct ldapoptions { #define ldo_tls_cacertfile ldo_tls_info.lt_cacertfile #define ldo_tls_cacertdir ldo_tls_info.lt_cacertdir #define ldo_tls_ciphersuite ldo_tls_info.lt_ciphersuite +#define ldo_tls_protocol_min ldo_tls_info.lt_protocol_min #define ldo_tls_crlfile ldo_tls_info.lt_crlfile +#define ldo_tls_randfile ldo_tls_info.lt_randfile int ldo_tls_mode; int ldo_tls_require_cert; + int ldo_tls_impl; #ifdef HAVE_OPENSSL_CRL int ldo_tls_crlcheck; #endif diff --git a/libraries/libldap/ldap-tls.h b/libraries/libldap/ldap-tls.h new file mode 100644 index 0000000000000000000000000000000000000000..0200cc20b9e4d6f675714dcdde5eb6722813dd26 --- /dev/null +++ b/libraries/libldap/ldap-tls.h @@ -0,0 +1,77 @@ +/* ldap-tls.h - TLS defines & prototypes internal to the LDAP library */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2009 The OpenLDAP Foundation. + * 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>. + */ + +#ifndef _LDAP_TLS_H +#define _LDAP_TLS_H 1 + +struct tls_impl; + +struct tls_ctx; +struct tls_session; + +typedef struct tls_ctx tls_ctx; +typedef struct tls_session tls_session; + +typedef int (TI_tls_init)(void); +typedef void (TI_tls_destroy)(void); + +typedef tls_ctx *(TI_ctx_new)(struct ldapoptions *lo); +typedef void (TI_ctx_ref)(tls_ctx *ctx); +typedef void (TI_ctx_free)(tls_ctx *ctx); +typedef int (TI_ctx_init)(struct ldapoptions *lo, struct ldaptls *lt, int is_server); + +typedef tls_session *(TI_session_new)(tls_ctx *ctx, int is_server); +typedef int (TI_session_connect)(LDAP *ld, tls_session *s); +typedef int (TI_session_accept)(tls_session *s); +typedef int (TI_session_upflags)(Sockbuf *sb, tls_session *s, int rc); +typedef char *(TI_session_errmsg)(int rc, char *buf, size_t len ); +typedef int (TI_session_dn)(tls_session *sess, struct berval *dn); +typedef int (TI_session_chkhost)(LDAP *ld, tls_session *s, const char *name_in); +typedef int (TI_session_strength)(tls_session *sess); + +typedef void (TI_thr_init)(void); + +typedef struct tls_impl { + const char *ti_name; + + TI_tls_init *ti_tls_init; /* library initialization */ + TI_tls_destroy *ti_tls_destroy; + + TI_ctx_new *ti_ctx_new; + TI_ctx_ref *ti_ctx_ref; + TI_ctx_free *ti_ctx_free; + TI_ctx_init *ti_ctx_init; + + TI_session_new *ti_session_new; + TI_session_connect *ti_session_connect; + TI_session_accept *ti_session_accept; + TI_session_upflags *ti_session_upflags; + TI_session_errmsg *ti_session_errmsg; + TI_session_dn *ti_session_my_dn; + TI_session_dn *ti_session_peer_dn; + TI_session_chkhost *ti_session_chkhost; + TI_session_strength *ti_session_strength; + + Sockbuf_IO *ti_sbio; + + TI_thr_init *ti_thr_init; + + int ti_inited; +} tls_impl; + +extern tls_impl ldap_int_tls_impl; + +#endif /* _LDAP_TLS_H */ diff --git a/libraries/libldap/tls.c b/libraries/libldap/tls.c deleted file mode 100644 index 3579351bd1dd581c7e5bb303a09980027d19ff6e..0000000000000000000000000000000000000000 --- a/libraries/libldap/tls.c +++ /dev/null @@ -1,3136 +0,0 @@ -/* tls.c - Handle tls/ssl using SSLeay, OpenSSL or GNUTLS. */ -/* $OpenLDAP$ */ -/* This work is part of OpenLDAP Software <http://www.openldap.org/>. - * - * Copyright 1998-2009 The OpenLDAP Foundation. - * 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: GNUTLS support written by Howard Chu and - * Matt Backes; sponsored by The Written Word (thewrittenword.com) - * and Stanford University (stanford.edu). - */ - -#include "portable.h" -#include "ldap_config.h" - -#include <stdio.h> - -#include <ac/stdlib.h> -#include <ac/errno.h> -#include <ac/socket.h> -#include <ac/string.h> -#include <ac/ctype.h> -#include <ac/time.h> -#include <ac/unistd.h> -#include <ac/param.h> -#include <ac/dirent.h> - -#include "ldap-int.h" - -#ifdef HAVE_TLS - -#ifdef LDAP_R_COMPILE -#include <ldap_pvt_thread.h> -#endif - -#ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#include <gcrypt.h> - -#define DH_BITS (1024) - -#else -#ifdef HAVE_OPENSSL_SSL_H -#include <openssl/ssl.h> -#include <openssl/x509v3.h> -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/safestack.h> -#elif defined( HAVE_SSL_H ) -#include <ssl.h> -#endif -#endif - -#define HAS_TLS( sb ) ber_sockbuf_ctrl( sb, LBER_SB_OPT_HAS_IO, \ - (void *)&sb_tls_sbio ) - -#endif /* HAVE_TLS */ - -/* RFC2459 minimum required set of supported attribute types - * in a certificate DN - */ -typedef struct oid_name { - struct berval oid; - struct berval name; -} oid_name; - -#define CN_OID oids[0].oid.bv_val - -static oid_name oids[] = { - { BER_BVC("2.5.4.3"), BER_BVC("cn") }, - { BER_BVC("2.5.4.4"), BER_BVC("sn") }, - { BER_BVC("2.5.4.6"), BER_BVC("c") }, - { BER_BVC("2.5.4.7"), BER_BVC("l") }, - { BER_BVC("2.5.4.8"), BER_BVC("st") }, - { BER_BVC("2.5.4.10"), BER_BVC("o") }, - { BER_BVC("2.5.4.11"), BER_BVC("ou") }, - { BER_BVC("2.5.4.12"), BER_BVC("title") }, - { BER_BVC("2.5.4.41"), BER_BVC("name") }, - { BER_BVC("2.5.4.42"), BER_BVC("givenName") }, - { BER_BVC("2.5.4.43"), BER_BVC("initials") }, - { BER_BVC("2.5.4.44"), BER_BVC("generationQualifier") }, - { BER_BVC("2.5.4.46"), BER_BVC("dnQualifier") }, - { BER_BVC("1.2.840.113549.1.9.1"), BER_BVC("email") }, - { BER_BVC("0.9.2342.19200300.100.1.25"), BER_BVC("dc") }, - { BER_BVNULL, BER_BVNULL } -}; - -#ifdef HAVE_TLS -#ifdef HAVE_GNUTLS - -typedef struct tls_cipher_suite { - const char *name; - gnutls_kx_algorithm_t kx; - gnutls_cipher_algorithm_t cipher; - gnutls_mac_algorithm_t mac; - gnutls_protocol_t version; -} tls_cipher_suite; - -static tls_cipher_suite *ciphers; -static int n_ciphers; - -/* sorta replacing SSL_CTX */ -typedef struct tls_ctx { - struct ldapoptions *lo; - gnutls_certificate_credentials_t cred; - gnutls_dh_params_t dh_params; - unsigned long verify_depth; - int refcount; - int *kx_list; - int *cipher_list; - int *mac_list; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_t ref_mutex; -#endif -} tls_ctx; - -/* sorta replacing SSL */ -typedef struct tls_session { - tls_ctx *ctx; - gnutls_session_t session; - struct berval peer_der_dn; -} tls_session; - -#ifdef LDAP_R_COMPILE - -static int -ldap_pvt_gcry_mutex_init( void **priv ) -{ - int err = 0; - ldap_pvt_thread_mutex_t *lock = LDAP_MALLOC( sizeof( ldap_pvt_thread_mutex_t )); - - if ( !lock ) - err = ENOMEM; - if ( !err ) { - err = ldap_pvt_thread_mutex_init( lock ); - if ( err ) - LDAP_FREE( lock ); - else - *priv = lock; - } - return err; -} -static int -ldap_pvt_gcry_mutex_destroy( void **lock ) -{ - int err = ldap_pvt_thread_mutex_destroy( *lock ); - LDAP_FREE( *lock ); - return err; -} -static int -ldap_pvt_gcry_mutex_lock( void **lock ) -{ - return ldap_pvt_thread_mutex_lock( *lock ); -} -static int -ldap_pvt_gcry_mutex_unlock( void **lock ) -{ - return ldap_pvt_thread_mutex_unlock( *lock ); -} - -static struct gcry_thread_cbs ldap_generic_thread_cbs = { - GCRY_THREAD_OPTION_USER, - NULL, - ldap_pvt_gcry_mutex_init, - ldap_pvt_gcry_mutex_destroy, - ldap_pvt_gcry_mutex_lock, - ldap_pvt_gcry_mutex_unlock, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; - -static void -tls_init_threads( void ) -{ - gcry_control (GCRYCTL_SET_THREAD_CBS, &ldap_generic_thread_cbs); -} -#endif /* LDAP_R_COMPILE */ - -void -ldap_pvt_tls_ctx_free ( void *c ) -{ - int refcount; - tls_ctx *ctx = c; - - if ( !ctx ) return; - -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_lock( &ctx->ref_mutex ); -#endif - refcount = --ctx->refcount; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &ctx->ref_mutex ); -#endif - if ( refcount ) - return; - LDAP_FREE( ctx->kx_list ); - gnutls_certificate_free_credentials( ctx->cred ); - ber_memfree ( ctx ); -} - -static void * -tls_ctx_new ( struct ldapoptions *lo ) -{ - tls_ctx *ctx; - - ctx = ber_memcalloc ( 1, sizeof (*ctx) ); - if ( ctx ) { - ctx->lo = lo; - if ( gnutls_certificate_allocate_credentials( &ctx->cred )) { - ber_memfree( ctx ); - return NULL; - } - ctx->refcount = 1; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_init( &ctx->ref_mutex ); -#endif - } - return ctx; -} - -static void -tls_ctx_ref( tls_ctx *ctx ) -{ -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_lock( &ctx->ref_mutex ); -#endif - ctx->refcount++; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &ctx->ref_mutex ); -#endif -} - -tls_session * -tls_session_new ( tls_ctx * ctx, int is_server ) -{ - tls_session *session; - - session = ber_memcalloc ( 1, sizeof (*session) ); - if ( !session ) - return NULL; - - session->ctx = ctx; - gnutls_init( &session->session, is_server ? GNUTLS_SERVER : GNUTLS_CLIENT ); - gnutls_set_default_priority( session->session ); - if ( ctx->kx_list ) { - gnutls_kx_set_priority( session->session, ctx->kx_list ); - gnutls_cipher_set_priority( session->session, ctx->cipher_list ); - gnutls_mac_set_priority( session->session, ctx->mac_list ); - } - if ( ctx->cred ) - gnutls_credentials_set( session->session, GNUTLS_CRD_CERTIFICATE, ctx->cred ); - - if ( is_server ) { - int flag = 0; - if ( ctx->lo->ldo_tls_require_cert ) { - flag = GNUTLS_CERT_REQUEST; - if ( ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || - ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) - flag = GNUTLS_CERT_REQUIRE; - gnutls_certificate_server_set_request( session->session, flag ); - } - } - return session; -} - -void -tls_session_free ( tls_session * session ) -{ - ber_memfree ( session ); -} - -#define tls_session_connect( ssl ) gnutls_handshake( ssl->session ) -#define tls_session_accept( ssl ) gnutls_handshake( ssl->session ) - -/* suites is a string of colon-separated cipher suite names. */ -static int -tls_parse_ciphers( tls_ctx *ctx, char *suites ) -{ - char *ptr, *end; - int i, j, len, num; - int *list, nkx = 0, ncipher = 0, nmac = 0; - int *kx, *cipher, *mac; - - num = 0; - ptr = suites; - do { - end = strchr(ptr, ':'); - if ( end ) - len = end - ptr; - else - len = strlen(ptr); - for (i=0; i<n_ciphers; i++) { - if ( !strncasecmp( ciphers[i].name, ptr, len )) { - num++; - break; - } - } - if ( i == n_ciphers ) { - /* unrecognized cipher suite */ - return -1; - } - ptr += len + 1; - } while (end); - - /* Space for all 3 lists */ - list = LDAP_MALLOC( (num+1) * sizeof(int) * 3 ); - if ( !list ) - return -1; - kx = list; - cipher = kx+num+1; - mac = cipher+num+1; - - ptr = suites; - do { - end = strchr(ptr, ':'); - if ( end ) - len = end - ptr; - else - len = strlen(ptr); - for (i=0; i<n_ciphers; i++) { - /* For each cipher suite, insert its algorithms into - * their respective priority lists. Make sure they - * only appear once in each list. - */ - if ( !strncasecmp( ciphers[i].name, ptr, len )) { - for (j=0; j<nkx; j++) - if ( kx[j] == ciphers[i].kx ) - break; - if ( j == nkx ) - kx[nkx++] = ciphers[i].kx; - for (j=0; j<ncipher; j++) - if ( cipher[j] == ciphers[i].cipher ) - break; - if ( j == ncipher ) - cipher[ncipher++] = ciphers[i].cipher; - for (j=0; j<nmac; j++) - if ( mac[j] == ciphers[i].mac ) - break; - if ( j == nmac ) - mac[nmac++] = ciphers[i].mac; - break; - } - } - ptr += len + 1; - } while (end); - kx[nkx] = 0; - cipher[ncipher] = 0; - mac[nmac] = 0; - ctx->kx_list = kx; - ctx->cipher_list = cipher; - ctx->mac_list = mac; - return 0; -} - -#else /* OpenSSL */ - -typedef SSL_CTX tls_ctx; -typedef SSL tls_session; - -static int tls_opt_trace = 1; -static char *tls_opt_randfile = NULL; - -static void tls_report_error( void ); - -static void tls_info_cb( const SSL *ssl, int where, int ret ); -static int tls_verify_cb( int ok, X509_STORE_CTX *ctx ); -static int tls_verify_ok( int ok, X509_STORE_CTX *ctx ); -static RSA * tls_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ); - -static DH * tls_tmp_dh_cb( SSL *ssl, int is_export, int key_length ); - -typedef struct dhplist { - struct dhplist *next; - int keylength; - DH *param; -} dhplist; - -static dhplist *dhparams; - -static int tls_seed_PRNG( const char *randfile ); - -#ifdef LDAP_R_COMPILE -/* - * provide mutexes for the SSLeay library. - */ -static ldap_pvt_thread_mutex_t tls_mutexes[CRYPTO_NUM_LOCKS]; - -static void tls_locking_cb( int mode, int type, const char *file, int line ) -{ - if ( mode & CRYPTO_LOCK ) { - ldap_pvt_thread_mutex_lock( &tls_mutexes[type] ); - } else { - ldap_pvt_thread_mutex_unlock( &tls_mutexes[type] ); - } -} - -static unsigned long tls_thread_self( void ) -{ - /* FIXME: CRYPTO_set_id_callback only works when ldap_pvt_thread_t - * is an integral type that fits in an unsigned long - */ - - /* force an error if the ldap_pvt_thread_t type is too large */ - enum { ok = sizeof( ldap_pvt_thread_t ) <= sizeof( unsigned long ) }; - typedef struct { int dummy: ok ? 1 : -1; } Check[ok ? 1 : -1]; - - return (unsigned long) ldap_pvt_thread_self(); -} - -static void tls_init_threads( void ) -{ - int i; - - for( i=0; i< CRYPTO_NUM_LOCKS ; i++ ) { - ldap_pvt_thread_mutex_init( &tls_mutexes[i] ); - } - CRYPTO_set_locking_callback( tls_locking_cb ); - CRYPTO_set_id_callback( tls_thread_self ); -} -#endif /* LDAP_R_COMPILE */ - -void -ldap_pvt_tls_ctx_free ( void *c ) -{ - - SSL_CTX_free( c ); -} - -static void * -tls_ctx_new( struct ldapoptions *lo ) -{ - return SSL_CTX_new( SSLv23_method() ); -} - -static void -tls_ctx_ref( void *c ) -{ - SSL_CTX *ctx = c; - CRYPTO_add( &ctx->references, 1, CRYPTO_LOCK_SSL_CTX ); -} - -static tls_session * -tls_session_new( tls_ctx *ctx, int is_server ) -{ - return SSL_new( ctx ); -} - -#define tls_session_connect( ssl ) SSL_connect( ssl ) -#define tls_session_accept( ssl ) SSL_accept( ssl ) - -static STACK_OF(X509_NAME) * -get_ca_list( char * bundle, char * dir ) -{ - STACK_OF(X509_NAME) *ca_list = NULL; - - if ( bundle ) { - ca_list = SSL_load_client_CA_file( bundle ); - } -#if defined(HAVE_DIRENT_H) || defined(dirent) - if ( dir ) { - int freeit = 0; - - if ( !ca_list ) { - ca_list = sk_X509_NAME_new_null(); - freeit = 1; - } - if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && - freeit ) { - sk_X509_NAME_free( ca_list ); - ca_list = NULL; - } - } -#endif - return ca_list; -} - -#endif /* HAVE_GNUTLS */ - -#ifdef LDAP_R_COMPILE -/* - * an extra mutex for the default ctx. - */ -static ldap_pvt_thread_mutex_t tls_def_ctx_mutex; -#endif - -void -ldap_int_tls_destroy( struct ldapoptions *lo ) -{ - if ( lo->ldo_tls_ctx ) { - ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); - lo->ldo_tls_ctx = NULL; - } - - if ( lo->ldo_tls_certfile ) { - LDAP_FREE( lo->ldo_tls_certfile ); - lo->ldo_tls_certfile = NULL; - } - if ( lo->ldo_tls_keyfile ) { - LDAP_FREE( lo->ldo_tls_keyfile ); - lo->ldo_tls_keyfile = NULL; - } - if ( lo->ldo_tls_dhfile ) { - LDAP_FREE( lo->ldo_tls_dhfile ); - lo->ldo_tls_dhfile = NULL; - } - if ( lo->ldo_tls_cacertfile ) { - LDAP_FREE( lo->ldo_tls_cacertfile ); - lo->ldo_tls_cacertfile = NULL; - } - if ( lo->ldo_tls_cacertdir ) { - LDAP_FREE( lo->ldo_tls_cacertdir ); - lo->ldo_tls_cacertdir = NULL; - } - if ( lo->ldo_tls_ciphersuite ) { - LDAP_FREE( lo->ldo_tls_ciphersuite ); - lo->ldo_tls_ciphersuite = NULL; - } -#ifdef HAVE_GNUTLS - if ( lo->ldo_tls_crlfile ) { - LDAP_FREE( lo->ldo_tls_crlfile ); - lo->ldo_tls_crlfile = NULL; - } -#endif -} - -/* - * Tear down the TLS subsystem. Should only be called once. - */ -void -ldap_pvt_tls_destroy( void ) -{ - struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); - - ldap_int_tls_destroy( lo ); - -#ifdef HAVE_GNUTLS - LDAP_FREE( ciphers ); - ciphers = NULL; - - gnutls_global_deinit(); -#else - EVP_cleanup(); - ERR_remove_state(0); - ERR_free_strings(); - - if ( tls_opt_randfile ) { - LDAP_FREE( tls_opt_randfile ); - tls_opt_randfile = NULL; - } -#endif -} - -/* - * Initialize TLS subsystem. Should be called only once. - */ -int -ldap_pvt_tls_init( void ) -{ - static int tls_initialized = 0; - - if ( tls_initialized++ ) return 0; - -#ifdef LDAP_R_COMPILE - tls_init_threads(); - ldap_pvt_thread_mutex_init( &tls_def_ctx_mutex ); -#endif - -#ifdef HAVE_GNUTLS - gnutls_global_init (); - - /* GNUtls cipher suite handling: The library ought to parse suite - * names for us, but it doesn't. It will return a list of suite names - * that it supports, so we can do parsing ourselves. It ought to tell - * us how long the list is, but it doesn't do that either, so we just - * have to count it manually... - */ - { - int i = 0; - tls_cipher_suite *ptr, tmp; - char cs_id[2]; - - while ( gnutls_cipher_suite_info( i, cs_id, &tmp.kx, &tmp.cipher, - &tmp.mac, &tmp.version )) - i++; - n_ciphers = i; - - /* Store a copy */ - ciphers = LDAP_MALLOC(n_ciphers * sizeof(tls_cipher_suite)); - if ( !ciphers ) - return -1; - for ( i=0; i<n_ciphers; i++ ) { - ciphers[i].name = gnutls_cipher_suite_info( i, cs_id, - &ciphers[i].kx, &ciphers[i].cipher, &ciphers[i].mac, - &ciphers[i].version ); - } - } - -#else /* !HAVE_GNUTLS */ - -#ifdef HAVE_EBCDIC - { - char *file = LDAP_STRDUP( tls_opt_randfile ); - if ( file ) __atoe( file ); - (void) tls_seed_PRNG( file ); - LDAP_FREE( file ); - } -#else - (void) tls_seed_PRNG( tls_opt_randfile ); -#endif - - SSL_load_error_strings(); - SSLeay_add_ssl_algorithms(); - - /* FIXME: mod_ssl does this */ - X509V3_add_standard_extensions(); - -#endif /* HAVE_GNUTLS */ - return 0; -} - -/* - * initialize a new TLS context - */ -static int -ldap_int_tls_init_ctx( struct ldapoptions *lo, int is_server ) -{ - int i, rc = 0; - char *ciphersuite = lo->ldo_tls_ciphersuite; - char *cacertfile = lo->ldo_tls_cacertfile; - char *cacertdir = lo->ldo_tls_cacertdir; - char *certfile = lo->ldo_tls_certfile; - char *keyfile = lo->ldo_tls_keyfile; -#ifdef HAVE_GNUTLS - char *crlfile = lo->ldo_tls_crlfile; -#else - char *dhfile = lo->ldo_tls_dhfile; -#endif - - if ( lo->ldo_tls_ctx ) - return 0; - - ldap_pvt_tls_init(); - - if ( is_server && !certfile && !keyfile && !cacertfile && !cacertdir ) { - /* minimum configuration not provided */ - return LDAP_NOT_SUPPORTED; - } - -#ifdef HAVE_EBCDIC - /* This ASCII/EBCDIC handling is a real pain! */ - if ( ciphersuite ) { - ciphersuite = LDAP_STRDUP( ciphersuite ); - __atoe( ciphersuite ); - } - if ( cacertfile ) { - cacertfile = LDAP_STRDUP( cacertfile ); - __atoe( cacertfile ); - } - if ( certfile ) { - certfile = LDAP_STRDUP( certfile ); - __atoe( certfile ); - } - if ( keyfile ) { - keyfile = LDAP_STRDUP( keyfile ); - __atoe( keyfile ); - } -#ifdef HAVE_GNUTLS - if ( crlfile ) { - crlfile = LDAP_STRDUP( crlfile ); - __atoe( crlfile ); - } -#else - if ( cacertdir ) { - cacertdir = LDAP_STRDUP( cacertdir ); - __atoe( cacertdir ); - } - if ( dhfile ) { - dhfile = LDAP_STRDUP( dhfile ); - __atoe( dhfile ); - } -#endif -#endif - lo->ldo_tls_ctx = tls_ctx_new( lo ); - if ( lo->ldo_tls_ctx == NULL ) { -#ifdef HAVE_GNUTLS - Debug( LDAP_DEBUG_ANY, - "TLS: could not allocate default ctx.\n", - 0,0,0); -#else - Debug( LDAP_DEBUG_ANY, - "TLS: could not allocate default ctx (%lu).\n", - ERR_peek_error(),0,0); -#endif - rc = -1; - goto error_exit; - } - -#ifdef HAVE_GNUTLS - if ( lo->ldo_tls_ciphersuite && - tls_parse_ciphers( lo->ldo_tls_ctx, - ciphersuite )) { - Debug( LDAP_DEBUG_ANY, - "TLS: could not set cipher list %s.\n", - lo->ldo_tls_ciphersuite, 0, 0 ); - rc = -1; - goto error_exit; - } - - if (lo->ldo_tls_cacertdir != NULL) { - Debug( LDAP_DEBUG_ANY, - "TLS: warning: cacertdir not implemented for gnutls\n", - NULL, NULL, NULL ); - } - - if (lo->ldo_tls_cacertfile != NULL) { - rc = gnutls_certificate_set_x509_trust_file( - ((tls_ctx*) lo->ldo_tls_ctx)->cred, - cacertfile, - GNUTLS_X509_FMT_PEM ); - if ( rc < 0 ) goto error_exit; - } - - if ( lo->ldo_tls_certfile && lo->ldo_tls_keyfile ) { - rc = gnutls_certificate_set_x509_key_file( - ((tls_ctx*) lo->ldo_tls_ctx)->cred, - certfile, - keyfile, - GNUTLS_X509_FMT_PEM ); - if ( rc ) goto error_exit; - } else if ( lo->ldo_tls_certfile || lo->ldo_tls_keyfile ) { - Debug( LDAP_DEBUG_ANY, - "TLS: only one of certfile and keyfile specified\n", - NULL, NULL, NULL ); - rc = 1; - goto error_exit; - } - - if ( lo->ldo_tls_dhfile ) { - Debug( LDAP_DEBUG_ANY, - "TLS: warning: ignoring dhfile\n", - NULL, NULL, NULL ); - } - - if ( lo->ldo_tls_crlfile ) { - rc = gnutls_certificate_set_x509_crl_file( - ((tls_ctx*) lo->ldo_tls_ctx)->cred, - crlfile, - GNUTLS_X509_FMT_PEM ); - if ( rc < 0 ) goto error_exit; - rc = 0; - } - if ( is_server ) { - gnutls_dh_params_init (&((tls_ctx*) - lo->ldo_tls_ctx)->dh_params); - gnutls_dh_params_generate2 (((tls_ctx*) - lo->ldo_tls_ctx)->dh_params, - DH_BITS); - } - -#else /* !HAVE_GNUTLS */ - - if ( is_server ) { - SSL_CTX_set_session_id_context( lo->ldo_tls_ctx, - (const unsigned char *) "OpenLDAP", sizeof("OpenLDAP")-1 ); - } - - if ( lo->ldo_tls_ciphersuite && - !SSL_CTX_set_cipher_list( lo->ldo_tls_ctx, ciphersuite ) ) - { - Debug( LDAP_DEBUG_ANY, - "TLS: could not set cipher list %s.\n", - lo->ldo_tls_ciphersuite, 0, 0 ); - tls_report_error(); - rc = -1; - goto error_exit; - } - - if (lo->ldo_tls_cacertfile != NULL || lo->ldo_tls_cacertdir != NULL) { - if ( !SSL_CTX_load_verify_locations( lo->ldo_tls_ctx, - cacertfile, cacertdir ) || - !SSL_CTX_set_default_verify_paths( lo->ldo_tls_ctx ) ) - { - Debug( LDAP_DEBUG_ANY, "TLS: " - "could not load verify locations (file:`%s',dir:`%s').\n", - lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", - lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", - 0 ); - tls_report_error(); - rc = -1; - goto error_exit; - } - - if ( is_server ) { - STACK_OF(X509_NAME) *calist; - /* List of CA names to send to a client */ - calist = get_ca_list( cacertfile, cacertdir ); - if ( !calist ) { - Debug( LDAP_DEBUG_ANY, "TLS: " - "could not load client CA list (file:`%s',dir:`%s').\n", - lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", - lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", - 0 ); - tls_report_error(); - rc = -1; - goto error_exit; - } - - SSL_CTX_set_client_CA_list( lo->ldo_tls_ctx, calist ); - } - } - - if ( lo->ldo_tls_certfile && - !SSL_CTX_use_certificate_file( lo->ldo_tls_ctx, - certfile, SSL_FILETYPE_PEM ) ) - { - Debug( LDAP_DEBUG_ANY, - "TLS: could not use certificate `%s'.\n", - lo->ldo_tls_certfile,0,0); - tls_report_error(); - rc = -1; - goto error_exit; - } - - /* Key validity is checked automatically if cert has already been set */ - if ( lo->ldo_tls_keyfile && - !SSL_CTX_use_PrivateKey_file( lo->ldo_tls_ctx, - keyfile, SSL_FILETYPE_PEM ) ) - { - Debug( LDAP_DEBUG_ANY, - "TLS: could not use key file `%s'.\n", - lo->ldo_tls_keyfile,0,0); - tls_report_error(); - rc = -1; - goto error_exit; - } - - if ( lo->ldo_tls_dhfile ) { - DH *dh = NULL; - BIO *bio; - dhplist *p; - - if (( bio=BIO_new_file( dhfile,"r" )) == NULL ) { - Debug( LDAP_DEBUG_ANY, - "TLS: could not use DH parameters file `%s'.\n", - lo->ldo_tls_dhfile,0,0); - tls_report_error(); - rc = -1; - goto error_exit; - } - while (( dh=PEM_read_bio_DHparams( bio, NULL, NULL, NULL ))) { - p = LDAP_MALLOC( sizeof(dhplist) ); - if ( p != NULL ) { - p->keylength = DH_size( dh ) * 8; - p->param = dh; - p->next = dhparams; - dhparams = p; - } - } - BIO_free( bio ); - } - - if ( tls_opt_trace ) { - SSL_CTX_set_info_callback( (SSL_CTX *)lo->ldo_tls_ctx, tls_info_cb ); - } - - i = SSL_VERIFY_NONE; - if ( lo->ldo_tls_require_cert ) { - i = SSL_VERIFY_PEER; - if ( lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || - lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) { - i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - } - } - - SSL_CTX_set_verify( lo->ldo_tls_ctx, i, - lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW ? - tls_verify_ok : tls_verify_cb ); - SSL_CTX_set_tmp_rsa_callback( lo->ldo_tls_ctx, tls_tmp_rsa_cb ); - if ( lo->ldo_tls_dhfile ) { - SSL_CTX_set_tmp_dh_callback( lo->ldo_tls_ctx, tls_tmp_dh_cb ); - } -#ifdef HAVE_OPENSSL_CRL - if ( lo->ldo_tls_crlcheck ) { - X509_STORE *x509_s = SSL_CTX_get_cert_store( lo->ldo_tls_ctx ); - if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_PEER ) { - X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK ); - } else if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_ALL ) { - X509_STORE_set_flags( x509_s, - X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); - } - } -#endif - -#endif /* HAVE_GNUTLS */ - -error_exit: - if ( rc == -1 && lo->ldo_tls_ctx != NULL ) { - ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); - lo->ldo_tls_ctx = NULL; - } -#ifdef HAVE_EBCDIC - LDAP_FREE( ciphersuite ); - LDAP_FREE( cacertfile ); - LDAP_FREE( certfile ); - LDAP_FREE( keyfile ); -#ifdef HAVE_GNUTLS - LDAP_FREE( crlfile ); -#else - LDAP_FREE( cacertdir ); - LDAP_FREE( dhfile ); -#endif -#endif - return rc; -} - -/* - * initialize the default context - */ -int -ldap_pvt_tls_init_def_ctx( int is_server ) -{ - struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); - int rc; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_lock( &tls_def_ctx_mutex ); -#endif - rc = ldap_int_tls_init_ctx( lo, is_server ); -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &tls_def_ctx_mutex ); -#endif - return rc; -} - -static tls_session * -alloc_handle( void *ctx_arg, int is_server ) -{ - tls_ctx *ctx; - tls_session *ssl; - - if ( ctx_arg ) { - ctx = ctx_arg; - } else { - struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); - if ( ldap_pvt_tls_init_def_ctx( is_server ) < 0 ) return NULL; - ctx = lo->ldo_tls_ctx; - } - - ssl = tls_session_new( ctx, is_server ); - if ( ssl == NULL ) { - Debug( LDAP_DEBUG_ANY,"TLS: can't create ssl handle.\n",0,0,0); - return NULL; - } - return ssl; -} - -static int -update_flags( Sockbuf *sb, tls_session * ssl, int rc ) -{ - sb->sb_trans_needs_read = 0; - sb->sb_trans_needs_write = 0; - -#ifdef HAVE_GNUTLS - if ( rc != GNUTLS_E_INTERRUPTED && rc != GNUTLS_E_AGAIN ) - return 0; - - switch (gnutls_record_get_direction (ssl->session)) { - case 0: - sb->sb_trans_needs_read = 1; - return 1; - case 1: - sb->sb_trans_needs_write = 1; - return 1; - } -#else /* !HAVE_GNUTLS */ - rc = SSL_get_error(ssl, rc); - if (rc == SSL_ERROR_WANT_READ) { - sb->sb_trans_needs_read = 1; - return 1; - - } else if (rc == SSL_ERROR_WANT_WRITE) { - sb->sb_trans_needs_write = 1; - return 1; - - } else if (rc == SSL_ERROR_WANT_CONNECT) { - return 1; - } -#endif /* HAVE_GNUTLS */ - return 0; -} - -/* - * TLS support for LBER Sockbufs - */ - -struct tls_data { - tls_session *ssl; - Sockbuf_IO_Desc *sbiod; -}; - -#ifdef HAVE_GNUTLS - -static ssize_t -sb_gtls_recv( gnutls_transport_ptr_t ptr, void *buf, size_t len ) -{ - struct tls_data *p; - - if ( buf == NULL || len <= 0 ) return 0; - - p = (struct tls_data *)ptr; - - if ( p == NULL || p->sbiod == NULL ) { - return 0; - } - - return LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); -} - -static ssize_t -sb_gtls_send( gnutls_transport_ptr_t ptr, const void *buf, size_t len ) -{ - struct tls_data *p; - - if ( buf == NULL || len <= 0 ) return 0; - - p = (struct tls_data *)ptr; - - if ( p == NULL || p->sbiod == NULL ) { - return 0; - } - - return LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); -} - -static int -sb_tls_setup( Sockbuf_IO_Desc *sbiod, void *arg ) -{ - struct tls_data *p; - tls_session *session = arg; - - assert( sbiod != NULL ); - - p = LBER_MALLOC( sizeof( *p ) ); - if ( p == NULL ) { - return -1; - } - - gnutls_transport_set_ptr( session->session, (gnutls_transport_ptr)p ); - gnutls_transport_set_pull_function( session->session, sb_gtls_recv ); - gnutls_transport_set_push_function( session->session, sb_gtls_send ); - p->ssl = arg; - p->sbiod = sbiod; - sbiod->sbiod_pvt = p; - return 0; -} - -static int -sb_tls_remove( Sockbuf_IO_Desc *sbiod ) -{ - struct tls_data *p; - - assert( sbiod != NULL ); - assert( sbiod->sbiod_pvt != NULL ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - gnutls_deinit ( p->ssl->session ); - LBER_FREE( p->ssl ); - LBER_FREE( sbiod->sbiod_pvt ); - sbiod->sbiod_pvt = NULL; - return 0; -} - -static int -sb_tls_close( Sockbuf_IO_Desc *sbiod ) -{ - struct tls_data *p; - - assert( sbiod != NULL ); - assert( sbiod->sbiod_pvt != NULL ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - gnutls_bye ( p->ssl->session, GNUTLS_SHUT_RDWR ); - return 0; -} - -static int -sb_tls_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) -{ - struct tls_data *p; - - assert( sbiod != NULL ); - assert( sbiod->sbiod_pvt != NULL ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - - if ( opt == LBER_SB_OPT_GET_SSL ) { - *((tls_session **)arg) = p->ssl; - return 1; - - } else if ( opt == LBER_SB_OPT_DATA_READY ) { - if( gnutls_record_check_pending( p->ssl->session ) > 0 ) { - return 1; - } - } - - return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); -} - -static ber_slen_t -sb_tls_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) -{ - struct tls_data *p; - ber_slen_t ret; - int err; - - assert( sbiod != NULL ); - assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - - ret = gnutls_record_recv ( p->ssl->session, buf, len ); - switch (ret) { - case GNUTLS_E_INTERRUPTED: - case GNUTLS_E_AGAIN: - sbiod->sbiod_sb->sb_trans_needs_read = 1; - sock_errset(EWOULDBLOCK); - ret = 0; - break; - case GNUTLS_E_REHANDSHAKE: - for ( ret = gnutls_handshake ( p->ssl->session ); - ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN; - ret = gnutls_handshake ( p->ssl->session ) ); - sbiod->sbiod_sb->sb_trans_needs_read = 1; - ret = 0; - break; - default: - sbiod->sbiod_sb->sb_trans_needs_read = 0; - } - return ret; -} - -static ber_slen_t -sb_tls_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) -{ - struct tls_data *p; - ber_slen_t ret; - int err; - - assert( sbiod != NULL ); - assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - - ret = gnutls_record_send ( p->ssl->session, (char *)buf, len ); - - if ( ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN ) { - sbiod->sbiod_sb->sb_trans_needs_write = 1; - sock_errset(EWOULDBLOCK); - ret = 0; - } else { - sbiod->sbiod_sb->sb_trans_needs_write = 0; - } - return ret; -} - -#else /* !HAVE_GNUTLS */ - -static int -sb_tls_bio_create( BIO *b ) { - b->init = 1; - b->num = 0; - b->ptr = NULL; - b->flags = 0; - return 1; -} - -static int -sb_tls_bio_destroy( BIO *b ) -{ - if ( b == NULL ) return 0; - - b->ptr = NULL; /* sb_tls_remove() will free it */ - b->init = 0; - b->flags = 0; - return 1; -} - -static int -sb_tls_bio_read( BIO *b, char *buf, int len ) -{ - struct tls_data *p; - int ret; - - if ( buf == NULL || len <= 0 ) return 0; - - p = (struct tls_data *)b->ptr; - - if ( p == NULL || p->sbiod == NULL ) { - return 0; - } - - ret = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); - - BIO_clear_retry_flags( b ); - if ( ret < 0 ) { - int err = sock_errno(); - if ( err == EAGAIN || err == EWOULDBLOCK ) { - BIO_set_retry_read( b ); - } - } - - return ret; -} - -static int -sb_tls_bio_write( BIO *b, const char *buf, int len ) -{ - struct tls_data *p; - int ret; - - if ( buf == NULL || len <= 0 ) return 0; - - p = (struct tls_data *)b->ptr; - - if ( p == NULL || p->sbiod == NULL ) { - return 0; - } - - ret = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); - - BIO_clear_retry_flags( b ); - if ( ret < 0 ) { - int err = sock_errno(); - if ( err == EAGAIN || err == EWOULDBLOCK ) { - BIO_set_retry_write( b ); - } - } - - return ret; -} - -static long -sb_tls_bio_ctrl( BIO *b, int cmd, long num, void *ptr ) -{ - if ( cmd == BIO_CTRL_FLUSH ) { - /* The OpenSSL library needs this */ - return 1; - } - return 0; -} - -static int -sb_tls_bio_gets( BIO *b, char *buf, int len ) -{ - return -1; -} - -static int -sb_tls_bio_puts( BIO *b, const char *str ) -{ - return sb_tls_bio_write( b, str, strlen( str ) ); -} - -static BIO_METHOD sb_tls_bio_method = -{ - ( 100 | 0x400 ), /* it's a source/sink BIO */ - "sockbuf glue", - sb_tls_bio_write, - sb_tls_bio_read, - sb_tls_bio_puts, - sb_tls_bio_gets, - sb_tls_bio_ctrl, - sb_tls_bio_create, - sb_tls_bio_destroy -}; - -static int -sb_tls_setup( Sockbuf_IO_Desc *sbiod, void *arg ) -{ - struct tls_data *p; - BIO *bio; - - assert( sbiod != NULL ); - - p = LBER_MALLOC( sizeof( *p ) ); - if ( p == NULL ) { - return -1; - } - - p->ssl = (SSL *)arg; - p->sbiod = sbiod; - bio = BIO_new( &sb_tls_bio_method ); - bio->ptr = (void *)p; - SSL_set_bio( p->ssl, bio, bio ); - sbiod->sbiod_pvt = p; - return 0; -} - -static int -sb_tls_remove( Sockbuf_IO_Desc *sbiod ) -{ - struct tls_data *p; - - assert( sbiod != NULL ); - assert( sbiod->sbiod_pvt != NULL ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - SSL_free( p->ssl ); - LBER_FREE( sbiod->sbiod_pvt ); - sbiod->sbiod_pvt = NULL; - return 0; -} - -static int -sb_tls_close( Sockbuf_IO_Desc *sbiod ) -{ - struct tls_data *p; - - assert( sbiod != NULL ); - assert( sbiod->sbiod_pvt != NULL ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - SSL_shutdown( p->ssl ); - return 0; -} - -static int -sb_tls_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) -{ - struct tls_data *p; - - assert( sbiod != NULL ); - assert( sbiod->sbiod_pvt != NULL ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - - if ( opt == LBER_SB_OPT_GET_SSL ) { - *((SSL **)arg) = p->ssl; - return 1; - - } else if ( opt == LBER_SB_OPT_DATA_READY ) { - if( SSL_pending( p->ssl ) > 0 ) { - return 1; - } - } - - return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); -} - -static ber_slen_t -sb_tls_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) -{ - struct tls_data *p; - ber_slen_t ret; - int err; - - assert( sbiod != NULL ); - assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - - ret = SSL_read( p->ssl, (char *)buf, len ); -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); -#endif - err = SSL_get_error( p->ssl, ret ); - if (err == SSL_ERROR_WANT_READ ) { - sbiod->sbiod_sb->sb_trans_needs_read = 1; - sock_errset(EWOULDBLOCK); - } - else - sbiod->sbiod_sb->sb_trans_needs_read = 0; - return ret; -} - -static ber_slen_t -sb_tls_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) -{ - struct tls_data *p; - ber_slen_t ret; - int err; - - assert( sbiod != NULL ); - assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); - - p = (struct tls_data *)sbiod->sbiod_pvt; - - ret = SSL_write( p->ssl, (char *)buf, len ); -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); -#endif - err = SSL_get_error( p->ssl, ret ); - if (err == SSL_ERROR_WANT_WRITE ) { - sbiod->sbiod_sb->sb_trans_needs_write = 1; - sock_errset(EWOULDBLOCK); - - } else { - sbiod->sbiod_sb->sb_trans_needs_write = 0; - } - return ret; -} - -#endif - -static Sockbuf_IO sb_tls_sbio = -{ - sb_tls_setup, /* sbi_setup */ - sb_tls_remove, /* sbi_remove */ - sb_tls_ctrl, /* sbi_ctrl */ - sb_tls_read, /* sbi_read */ - sb_tls_write, /* sbi_write */ - sb_tls_close /* sbi_close */ -}; - -#ifdef HAVE_GNUTLS -/* Certs are not automatically varified during the handshake */ -static int -tls_cert_verify( tls_session *ssl ) -{ - unsigned int status = 0; - int err; - time_t now = time(0); - - err = gnutls_certificate_verify_peers2( ssl->session, &status ); - if ( err < 0 ) { - Debug( LDAP_DEBUG_ANY,"TLS: gnutls_certificate_verify_peers2 failed %d\n", - err,0,0 ); - return -1; - } - if ( status ) { - Debug( LDAP_DEBUG_TRACE,"TLS: peer cert untrusted or revoked (0x%x)\n", - status, 0,0 ); - return -1; - } - if ( gnutls_certificate_expiration_time_peers( ssl->session ) < now ) { - Debug( LDAP_DEBUG_ANY, "TLS: peer certificate is expired\n", - 0, 0, 0 ); - return -1; - } - if ( gnutls_certificate_activation_time_peers( ssl->session ) > now ) { - Debug( LDAP_DEBUG_ANY, "TLS: peer certificate not yet active\n", - 0, 0, 0 ); - return -1; - } - return 0; -} -#endif /* HAVE_GNUTLS */ - -/* - * Call this to do a TLS connect on a sockbuf. ctx_arg can be - * a SSL_CTX * or NULL, in which case the default ctx is used. - * - * Return value: - * - * 0 - Success. Connection is ready for communication. - * <0 - Error. Can't create a TLS stream. - * >0 - Partial success. - * Do a select (using information from lber_pvt_sb_needs_{read,write} - * and call again. - */ - -static int -ldap_int_tls_connect( LDAP *ld, LDAPConn *conn ) -{ - Sockbuf *sb = conn->lconn_sb; - int err; - tls_session *ssl; - - if ( HAS_TLS( sb ) ) { - ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); - - } else { - struct ldapoptions *lo; - tls_ctx *ctx; - - ctx = ld->ld_options.ldo_tls_ctx; - - ssl = alloc_handle( ctx, 0 ); - - if ( ssl == NULL ) return -1; - -#ifdef LDAP_DEBUG - ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, - LBER_SBIOD_LEVEL_TRANSPORT, (void *)"tls_" ); -#endif - ber_sockbuf_add_io( sb, &sb_tls_sbio, - LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); - - lo = LDAP_INT_GLOBAL_OPT(); - if( ctx == NULL ) { - ctx = lo->ldo_tls_ctx; - ld->ld_options.ldo_tls_ctx = ctx; - tls_ctx_ref( ctx ); - } - if ( ld->ld_options.ldo_tls_connect_cb ) - ld->ld_options.ldo_tls_connect_cb( ld, ssl, ctx, - ld->ld_options.ldo_tls_connect_arg ); - if ( lo && lo->ldo_tls_connect_cb && lo->ldo_tls_connect_cb != - ld->ld_options.ldo_tls_connect_cb ) - lo->ldo_tls_connect_cb( ld, ssl, ctx, lo->ldo_tls_connect_arg ); - } - - err = tls_session_connect( ssl ); - -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); -#endif - -#ifdef HAVE_GNUTLS - if ( err < 0 ) -#else - if ( err <= 0 ) -#endif - { - if ( update_flags( sb, ssl, err )) { - return 1; - } - -#ifndef HAVE_GNUTLS - if ((err = ERR_peek_error())) -#endif - { - if ( ld->ld_error ) { - LDAP_FREE( ld->ld_error ); - } -#ifdef HAVE_GNUTLS - ld->ld_error = LDAP_STRDUP(gnutls_strerror( err )); -#else - { - char buf[256]; - ld->ld_error = LDAP_STRDUP(ERR_error_string(err, buf)); - } -#endif -#ifdef HAVE_EBCDIC - if ( ld->ld_error ) __etoa(ld->ld_error); -#endif - } - - Debug( LDAP_DEBUG_ANY,"TLS: can't connect: %s.\n", - ld->ld_error ? ld->ld_error : "" ,0,0); - - ber_sockbuf_remove_io( sb, &sb_tls_sbio, - LBER_SBIOD_LEVEL_TRANSPORT ); -#ifdef LDAP_DEBUG - ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, - LBER_SBIOD_LEVEL_TRANSPORT ); -#endif - return -1; - } - -#ifdef HAVE_GNUTLS - if ( ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER ) { - err = tls_cert_verify( ssl ); - if ( err && ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_ALLOW ) - return err; - } -#endif - - return 0; -} - -/* - * Call this to do a TLS accept on a sockbuf. - * Everything else is the same as with tls_connect. - */ -int -ldap_pvt_tls_accept( Sockbuf *sb, void *ctx_arg ) -{ - int err; - tls_session *ssl; - - if ( HAS_TLS( sb ) ) { - ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); - - } else { - ssl = alloc_handle( ctx_arg, 1 ); - if ( ssl == NULL ) return -1; - -#ifdef LDAP_DEBUG - ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, - LBER_SBIOD_LEVEL_TRANSPORT, (void *)"tls_" ); -#endif - ber_sockbuf_add_io( sb, &sb_tls_sbio, - LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); - } - - err = tls_session_accept( ssl ); - -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); -#endif - -#ifdef HAVE_GNUTLS - if ( err < 0 ) -#else - if ( err <= 0 ) -#endif - { - if ( update_flags( sb, ssl, err )) return 1; - -#ifdef HAVE_GNUTLS - Debug( LDAP_DEBUG_ANY,"TLS: can't accept: %s.\n", - gnutls_strerror( err ),0,0 ); -#else - Debug( LDAP_DEBUG_ANY,"TLS: can't accept.\n",0,0,0 ); - tls_report_error(); -#endif - ber_sockbuf_remove_io( sb, &sb_tls_sbio, - LBER_SBIOD_LEVEL_TRANSPORT ); -#ifdef LDAP_DEBUG - ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, - LBER_SBIOD_LEVEL_TRANSPORT ); -#endif - return -1; - } - -#ifdef HAVE_GNUTLS - if ( ssl->ctx->lo->ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER ) { - err = tls_cert_verify( ssl ); - if ( err && ssl->ctx->lo->ldo_tls_require_cert != LDAP_OPT_X_TLS_ALLOW ) - return err; - } -#endif - return 0; -} - -int -ldap_pvt_tls_inplace ( Sockbuf *sb ) -{ - return HAS_TLS( sb ) ? 1 : 0; -} - -int -ldap_tls_inplace( LDAP *ld ) -{ - Sockbuf *sb = NULL; - - if ( ld->ld_defconn && ld->ld_defconn->lconn_sb ) { - sb = ld->ld_defconn->lconn_sb; - - } else if ( ld->ld_sb ) { - sb = ld->ld_sb; - - } else { - return 0; - } - - return ldap_pvt_tls_inplace( sb ); -} - -#ifdef HAVE_GNUTLS -static void -x509_cert_get_dn( struct berval *cert, struct berval *dn, int get_subject ) -{ - BerElementBuffer berbuf; - BerElement *ber = (BerElement *)&berbuf; - ber_tag_t tag; - ber_len_t len; - ber_int_t i; - - ber_init2( ber, cert, LBER_USE_DER ); - tag = ber_skip_tag( ber, &len ); /* Sequence */ - tag = ber_skip_tag( ber, &len ); /* Sequence */ - tag = ber_skip_tag( ber, &len ); /* Context + Constructed (version) */ - if ( tag == 0xa0 ) /* Version is optional */ - tag = ber_get_int( ber, &i ); /* Int: Version */ - tag = ber_get_int( ber, &i ); /* Int: Serial */ - tag = ber_skip_tag( ber, &len ); /* Sequence: Signature */ - ber_skip_data( ber, len ); - if ( !get_subject ) { - tag = ber_peek_tag( ber, &len ); /* Sequence: Issuer DN */ - } else { - tag = ber_skip_tag( ber, &len ); - ber_skip_data( ber, len ); - tag = ber_skip_tag( ber, &len ); /* Sequence: Validity */ - ber_skip_data( ber, len ); - tag = ber_peek_tag( ber, &len ); /* Sequence: Subject DN */ - } - len = ber_ptrlen( ber ); - dn->bv_val = cert->bv_val + len; - dn->bv_len = cert->bv_len - len; -} - -static int -tls_get_cert_dn( tls_session *session, struct berval *dnbv ) -{ - if ( !session->peer_der_dn.bv_val ) { - const gnutls_datum_t *peer_cert_list; - int list_size; - struct berval bv; - - peer_cert_list = gnutls_certificate_get_peers( session->session, - &list_size ); - if ( !peer_cert_list ) return LDAP_INVALID_CREDENTIALS; - - bv.bv_len = peer_cert_list->size; - bv.bv_val = peer_cert_list->data; - - x509_cert_get_dn( &bv, &session->peer_der_dn, 1 ); - *dnbv = session->peer_der_dn; - } - return 0; -} -#else /* !HAVE_GNUTLS */ -static X509 * -tls_get_cert( SSL *s ) -{ - /* If peer cert was bad, treat as if no cert was given */ - if (SSL_get_verify_result(s)) { - /* If we can send an alert, do so */ - if (SSL_version(s) != SSL2_VERSION) { - ssl3_send_alert(s,SSL3_AL_WARNING,SSL3_AD_BAD_CERTIFICATE); - } - return NULL; - } - return SSL_get_peer_certificate(s); -} - -static int -tls_get_cert_dn( tls_session *session, struct berval *dnbv ) -{ - X509_NAME *xn; - X509 *x = tls_get_cert( session ); - - if ( !x ) - return LDAP_INVALID_CREDENTIALS; - - xn = X509_get_subject_name(x); - dnbv->bv_len = i2d_X509_NAME( xn, NULL ); - dnbv->bv_val = xn->bytes->data; - X509_free(x); - return 0; -} -#endif /* HAVE_GNUTLS */ - -int -ldap_pvt_tls_get_peer_dn( void *s, struct berval *dn, - LDAPDN_rewrite_dummy *func, unsigned flags ) -{ - tls_session *session = s; - struct berval bvdn; - int rc; - - rc = tls_get_cert_dn( session, &bvdn ); - if ( rc ) return rc; - - rc = ldap_X509dn2bv( &bvdn, dn, - (LDAPDN_rewrite_func *)func, flags); - return rc; -} - -/* what kind of hostname were we given? */ -#define IS_DNS 0 -#define IS_IP4 1 -#define IS_IP6 2 - -#ifdef HAVE_GNUTLS - -int -ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) -{ - tls_session *session = s; - int i, ret; - const gnutls_datum_t *peer_cert_list; - int list_size; - struct berval bv; - char altname[NI_MAXHOST]; - size_t altnamesize; - - gnutls_x509_crt_t cert; - gnutls_datum_t *x; - const char *name; - char *ptr; - char *domain = NULL; -#ifdef LDAP_PF_INET6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - int n, len1 = 0, len2 = 0; - int ntype = IS_DNS; - time_t now = time(0); - - if( ldap_int_hostname && - ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) - { - name = ldap_int_hostname; - } else { - name = name_in; - } - - peer_cert_list = gnutls_certificate_get_peers( session->session, - &list_size ); - if ( !peer_cert_list ) { - Debug( LDAP_DEBUG_ANY, - "TLS: unable to get peer certificate.\n", - 0, 0, 0 ); - /* If this was a fatal condition, things would have - * aborted long before now. - */ - return LDAP_SUCCESS; - } - ret = gnutls_x509_crt_init( &cert ); - if ( ret < 0 ) - return LDAP_LOCAL_ERROR; - ret = gnutls_x509_crt_import( cert, peer_cert_list, GNUTLS_X509_FMT_DER ); - if ( ret ) { - gnutls_x509_crt_deinit( cert ); - return LDAP_LOCAL_ERROR; - } - -#ifdef LDAP_PF_INET6 - if (name[0] == '[' && strchr(name, ']')) { - char *n2 = ldap_strdup(name+1); - *strchr(n2, ']') = 2; - if (inet_pton(AF_INET6, n2, &addr)) - ntype = IS_IP6; - LDAP_FREE(n2); - } else -#endif - if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { - if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; - } - - if (ntype == IS_DNS) { - len1 = strlen(name); - domain = strchr(name, '.'); - if (domain) { - len2 = len1 - (domain-name); - } - } - - for ( i=0, ret=0; ret >= 0; i++ ) { - altnamesize = sizeof(altname); - ret = gnutls_x509_crt_get_subject_alt_name( cert, i, - altname, &altnamesize, NULL ); - if ( ret < 0 ) break; - - /* ignore empty */ - if ( altnamesize == 0 ) continue; - - if ( ret == GNUTLS_SAN_DNSNAME ) { - if (ntype != IS_DNS) continue; - - /* Is this an exact match? */ - if ((len1 == altnamesize) && !strncasecmp(name, altname, len1)) { - break; - } - - /* Is this a wildcard match? */ - if (domain && (altname[0] == '*') && (altname[1] == '.') && - (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) - { - break; - } - } else if ( ret == GNUTLS_SAN_IPADDRESS ) { - if (ntype == IS_DNS) continue; - -#ifdef LDAP_PF_INET6 - if (ntype == IS_IP6 && altnamesize != sizeof(struct in6_addr)) { - continue; - } else -#endif - if (ntype == IS_IP4 && altnamesize != sizeof(struct in_addr)) { - continue; - } - if (!memcmp(altname, &addr, altnamesize)) { - break; - } - } - } - if ( ret >= 0 ) { - ret = LDAP_SUCCESS; - } else { - altnamesize = sizeof(altname); - ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID, - 0, 0, altname, &altnamesize ); - if ( ret < 0 ) { - Debug( LDAP_DEBUG_ANY, - "TLS: unable to get common name from peer certificate.\n", - 0, 0, 0 ); - ret = LDAP_CONNECT_ERROR; - if ( ld->ld_error ) { - LDAP_FREE( ld->ld_error ); - } - ld->ld_error = LDAP_STRDUP( - _("TLS: unable to get CN from peer certificate")); - - } else { - ret = LDAP_LOCAL_ERROR; - if ( !len1 ) len1 = strlen( name ); - if ( len1 == altnamesize && strncasecmp(name, altname, altnamesize) == 0 ) { - ret = LDAP_SUCCESS; - - } else if (( altname[0] == '*' ) && ( altname[1] == '.' )) { - /* Is this a wildcard match? */ - if( domain && - (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) { - ret = LDAP_SUCCESS; - } - } - } - - if( ret == LDAP_LOCAL_ERROR ) { - altname[altnamesize] = '\0'; - Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " - "common name in certificate (%s).\n", - name, altname, 0 ); - ret = LDAP_CONNECT_ERROR; - if ( ld->ld_error ) { - LDAP_FREE( ld->ld_error ); - } - ld->ld_error = LDAP_STRDUP( - _("TLS: hostname does not match CN in peer certificate")); - } - } - gnutls_x509_crt_deinit( cert ); - return ret; -} - -#else /* !HAVE_GNUTLS */ - -int -ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) -{ - int i, ret = LDAP_LOCAL_ERROR; - X509 *x; - const char *name; - char *ptr; - int ntype = IS_DNS; -#ifdef LDAP_PF_INET6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - if( ldap_int_hostname && - ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) - { - name = ldap_int_hostname; - } else { - name = name_in; - } - - x = tls_get_cert((SSL *)s); - if (!x) { - Debug( LDAP_DEBUG_ANY, - "TLS: unable to get peer certificate.\n", - 0, 0, 0 ); - /* If this was a fatal condition, things would have - * aborted long before now. - */ - return LDAP_SUCCESS; - } - -#ifdef LDAP_PF_INET6 - if (name[0] == '[' && strchr(name, ']')) { - char *n2 = ldap_strdup(name+1); - *strchr(n2, ']') = 2; - if (inet_pton(AF_INET6, n2, &addr)) - ntype = IS_IP6; - LDAP_FREE(n2); - } else -#endif - if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { - if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; - } - - i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); - if (i >= 0) { - X509_EXTENSION *ex; - STACK_OF(GENERAL_NAME) *alt; - - ex = X509_get_ext(x, i); - alt = X509V3_EXT_d2i(ex); - if (alt) { - int n, len1 = 0, len2 = 0; - char *domain = NULL; - GENERAL_NAME *gn; - - if (ntype == IS_DNS) { - len1 = strlen(name); - domain = strchr(name, '.'); - if (domain) { - len2 = len1 - (domain-name); - } - } - n = sk_GENERAL_NAME_num(alt); - for (i=0; i<n; i++) { - char *sn; - int sl; - gn = sk_GENERAL_NAME_value(alt, i); - if (gn->type == GEN_DNS) { - if (ntype != IS_DNS) continue; - - sn = (char *) ASN1_STRING_data(gn->d.ia5); - sl = ASN1_STRING_length(gn->d.ia5); - - /* ignore empty */ - if (sl == 0) continue; - - /* Is this an exact match? */ - if ((len1 == sl) && !strncasecmp(name, sn, len1)) { - break; - } - - /* Is this a wildcard match? */ - if (domain && (sn[0] == '*') && (sn[1] == '.') && - (len2 == sl-1) && !strncasecmp(domain, &sn[1], len2)) - { - break; - } - - } else if (gn->type == GEN_IPADD) { - if (ntype == IS_DNS) continue; - - sn = (char *) ASN1_STRING_data(gn->d.ia5); - sl = ASN1_STRING_length(gn->d.ia5); - -#ifdef LDAP_PF_INET6 - if (ntype == IS_IP6 && sl != sizeof(struct in6_addr)) { - continue; - } else -#endif - if (ntype == IS_IP4 && sl != sizeof(struct in_addr)) { - continue; - } - if (!memcmp(sn, &addr, sl)) { - break; - } - } - } - - GENERAL_NAMES_free(alt); - if (i < n) { /* Found a match */ - ret = LDAP_SUCCESS; - } - } - } - - if (ret != LDAP_SUCCESS) { - X509_NAME *xn; - char buf[2048]; - buf[0] = '\0'; - - xn = X509_get_subject_name(x); - if( X509_NAME_get_text_by_NID( xn, NID_commonName, - buf, sizeof(buf)) == -1) - { - Debug( LDAP_DEBUG_ANY, - "TLS: unable to get common name from peer certificate.\n", - 0, 0, 0 ); - ret = LDAP_CONNECT_ERROR; - if ( ld->ld_error ) { - LDAP_FREE( ld->ld_error ); - } - ld->ld_error = LDAP_STRDUP( - _("TLS: unable to get CN from peer certificate")); - - } else if (strcasecmp(name, buf) == 0 ) { - ret = LDAP_SUCCESS; - - } else if (( buf[0] == '*' ) && ( buf[1] == '.' )) { - char *domain = strchr(name, '.'); - if( domain ) { - size_t dlen = 0; - size_t sl; - - sl = strlen(name); - dlen = sl - (domain-name); - sl = strlen(buf); - - /* Is this a wildcard match? */ - if ((dlen == sl-1) && !strncasecmp(domain, &buf[1], dlen)) { - ret = LDAP_SUCCESS; - } - } - } - - if( ret == LDAP_LOCAL_ERROR ) { - Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " - "common name in certificate (%s).\n", - name, buf, 0 ); - ret = LDAP_CONNECT_ERROR; - if ( ld->ld_error ) { - LDAP_FREE( ld->ld_error ); - } - ld->ld_error = LDAP_STRDUP( - _("TLS: hostname does not match CN in peer certificate")); - } - } - X509_free(x); - return ret; -} -#endif - -int -ldap_int_tls_config( LDAP *ld, int option, const char *arg ) -{ - int i; - - switch( option ) { - case LDAP_OPT_X_TLS_CACERTFILE: - case LDAP_OPT_X_TLS_CACERTDIR: - case LDAP_OPT_X_TLS_CERTFILE: - case LDAP_OPT_X_TLS_KEYFILE: - case LDAP_OPT_X_TLS_RANDOM_FILE: - case LDAP_OPT_X_TLS_CIPHER_SUITE: - case LDAP_OPT_X_TLS_DHFILE: -#ifdef HAVE_GNUTLS - case LDAP_OPT_X_TLS_CRLFILE: -#endif - return ldap_pvt_tls_set_option( ld, option, (void *) arg ); - - case LDAP_OPT_X_TLS_REQUIRE_CERT: - case LDAP_OPT_X_TLS: - i = -1; - if ( strcasecmp( arg, "never" ) == 0 ) { - i = LDAP_OPT_X_TLS_NEVER ; - - } else if ( strcasecmp( arg, "demand" ) == 0 ) { - i = LDAP_OPT_X_TLS_DEMAND ; - - } else if ( strcasecmp( arg, "allow" ) == 0 ) { - i = LDAP_OPT_X_TLS_ALLOW ; - - } else if ( strcasecmp( arg, "try" ) == 0 ) { - i = LDAP_OPT_X_TLS_TRY ; - - } else if ( ( strcasecmp( arg, "hard" ) == 0 ) || - ( strcasecmp( arg, "on" ) == 0 ) || - ( strcasecmp( arg, "yes" ) == 0) || - ( strcasecmp( arg, "true" ) == 0 ) ) - { - i = LDAP_OPT_X_TLS_HARD ; - } - - if (i >= 0) { - return ldap_pvt_tls_set_option( ld, option, &i ); - } - return -1; -#ifdef HAVE_OPENSSL_CRL - case LDAP_OPT_X_TLS_CRLCHECK: - i = -1; - if ( strcasecmp( arg, "none" ) == 0 ) { - i = LDAP_OPT_X_TLS_CRL_NONE ; - } else if ( strcasecmp( arg, "peer" ) == 0 ) { - i = LDAP_OPT_X_TLS_CRL_PEER ; - } else if ( strcasecmp( arg, "all" ) == 0 ) { - i = LDAP_OPT_X_TLS_CRL_ALL ; - } - if (i >= 0) { - return ldap_pvt_tls_set_option( ld, option, &i ); - } - return -1; -#endif - } - return -1; -} - -int -ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg ) -{ - struct ldapoptions *lo; - - if( ld != NULL ) { - assert( LDAP_VALID( ld ) ); - - if( !LDAP_VALID( ld ) ) { - return LDAP_OPT_ERROR; - } - - lo = &ld->ld_options; - - } else { - /* Get pointer to global option structure */ - lo = LDAP_INT_GLOBAL_OPT(); - if ( lo == NULL ) { - return LDAP_NO_MEMORY; - } - } - - switch( option ) { - case LDAP_OPT_X_TLS: - *(int *)arg = lo->ldo_tls_mode; - break; - case LDAP_OPT_X_TLS_CTX: - *(void **)arg = lo->ldo_tls_ctx; - if ( lo->ldo_tls_ctx ) { - tls_ctx_ref( lo->ldo_tls_ctx ); - } - break; - case LDAP_OPT_X_TLS_CACERTFILE: - *(char **)arg = lo->ldo_tls_cacertfile ? - LDAP_STRDUP( lo->ldo_tls_cacertfile ) : NULL; - break; - case LDAP_OPT_X_TLS_CACERTDIR: - *(char **)arg = lo->ldo_tls_cacertdir ? - LDAP_STRDUP( lo->ldo_tls_cacertdir ) : NULL; - break; - case LDAP_OPT_X_TLS_CERTFILE: - *(char **)arg = lo->ldo_tls_certfile ? - LDAP_STRDUP( lo->ldo_tls_certfile ) : NULL; - break; - case LDAP_OPT_X_TLS_KEYFILE: - *(char **)arg = lo->ldo_tls_keyfile ? - LDAP_STRDUP( lo->ldo_tls_keyfile ) : NULL; - break; - case LDAP_OPT_X_TLS_DHFILE: - *(char **)arg = lo->ldo_tls_dhfile ? - LDAP_STRDUP( lo->ldo_tls_dhfile ) : NULL; - break; -#ifdef HAVE_GNUTLS - case LDAP_OPT_X_TLS_CRLFILE: - *(char **)arg = lo->ldo_tls_crlfile ? - LDAP_STRDUP( lo->ldo_tls_crlfile ) : NULL; - break; -#endif - case LDAP_OPT_X_TLS_REQUIRE_CERT: - *(int *)arg = lo->ldo_tls_require_cert; - break; -#ifdef HAVE_OPENSSL_CRL - case LDAP_OPT_X_TLS_CRLCHECK: - *(int *)arg = lo->ldo_tls_crlcheck; - break; -#endif - case LDAP_OPT_X_TLS_CIPHER_SUITE: - *(char **)arg = lo->ldo_tls_ciphersuite ? - LDAP_STRDUP( lo->ldo_tls_ciphersuite ) : NULL; - break; - case LDAP_OPT_X_TLS_RANDOM_FILE: -#ifdef HAVE_OPENSSL - *(char **)arg = tls_opt_randfile ? - LDAP_STRDUP( tls_opt_randfile ) : NULL; -#else - *(char **)arg = NULL; -#endif - break; - case LDAP_OPT_X_TLS_SSL_CTX: { - void *retval = 0; - if ( ld != NULL ) { - LDAPConn *conn = ld->ld_defconn; - if ( conn != NULL ) { - Sockbuf *sb = conn->lconn_sb; - retval = ldap_pvt_tls_sb_ctx( sb ); - } - } - *(void **)arg = retval; - break; - } - case LDAP_OPT_X_TLS_CONNECT_CB: - *(LDAP_TLS_CONNECT_CB **)arg = lo->ldo_tls_connect_cb; - break; - case LDAP_OPT_X_TLS_CONNECT_ARG: - *(void **)arg = lo->ldo_tls_connect_arg; - break; - default: - return -1; - } - return 0; -} - -int -ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) -{ - struct ldapoptions *lo; - - if( ld != NULL ) { - assert( LDAP_VALID( ld ) ); - - if( !LDAP_VALID( ld ) ) { - return LDAP_OPT_ERROR; - } - - lo = &ld->ld_options; - - } else { - /* Get pointer to global option structure */ - lo = LDAP_INT_GLOBAL_OPT(); - if ( lo == NULL ) { - return LDAP_NO_MEMORY; - } - } - - switch( option ) { - case LDAP_OPT_X_TLS: - if ( !arg ) return -1; - - switch( *(int *) arg ) { - case LDAP_OPT_X_TLS_NEVER: - case LDAP_OPT_X_TLS_DEMAND: - case LDAP_OPT_X_TLS_ALLOW: - case LDAP_OPT_X_TLS_TRY: - case LDAP_OPT_X_TLS_HARD: - if (lo != NULL) { - lo->ldo_tls_mode = *(int *)arg; - } - - return 0; - } - return -1; - - case LDAP_OPT_X_TLS_CTX: - if ( lo->ldo_tls_ctx ) - ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); - lo->ldo_tls_ctx = arg; - tls_ctx_ref( lo->ldo_tls_ctx ); - return 0; - case LDAP_OPT_X_TLS_CONNECT_CB: - lo->ldo_tls_connect_cb = (LDAP_TLS_CONNECT_CB *)arg; - return 0; - case LDAP_OPT_X_TLS_CONNECT_ARG: - lo->ldo_tls_connect_arg = arg; - return 0; - case LDAP_OPT_X_TLS_CACERTFILE: - if ( lo->ldo_tls_cacertfile ) LDAP_FREE( lo->ldo_tls_cacertfile ); - lo->ldo_tls_cacertfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; - case LDAP_OPT_X_TLS_CACERTDIR: - if ( lo->ldo_tls_cacertdir ) LDAP_FREE( lo->ldo_tls_cacertdir ); - lo->ldo_tls_cacertdir = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; - case LDAP_OPT_X_TLS_CERTFILE: - if ( lo->ldo_tls_certfile ) LDAP_FREE( lo->ldo_tls_certfile ); - lo->ldo_tls_certfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; - case LDAP_OPT_X_TLS_KEYFILE: - if ( lo->ldo_tls_keyfile ) LDAP_FREE( lo->ldo_tls_keyfile ); - lo->ldo_tls_keyfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; - case LDAP_OPT_X_TLS_DHFILE: - if ( lo->ldo_tls_dhfile ) LDAP_FREE( lo->ldo_tls_dhfile ); - lo->ldo_tls_dhfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; -#ifdef HAVE_GNUTLS - case LDAP_OPT_X_TLS_CRLFILE: - if ( lo->ldo_tls_crlfile ) LDAP_FREE( lo->ldo_tls_crlfile ); - lo->ldo_tls_crlfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; -#endif - case LDAP_OPT_X_TLS_REQUIRE_CERT: - if ( !arg ) return -1; - switch( *(int *) arg ) { - case LDAP_OPT_X_TLS_NEVER: - case LDAP_OPT_X_TLS_DEMAND: - case LDAP_OPT_X_TLS_ALLOW: - case LDAP_OPT_X_TLS_TRY: - case LDAP_OPT_X_TLS_HARD: - lo->ldo_tls_require_cert = * (int *) arg; - return 0; - } - return -1; -#ifdef HAVE_OPENSSL_CRL - case LDAP_OPT_X_TLS_CRLCHECK: - if ( !arg ) return -1; - switch( *(int *) arg ) { - case LDAP_OPT_X_TLS_CRL_NONE: - case LDAP_OPT_X_TLS_CRL_PEER: - case LDAP_OPT_X_TLS_CRL_ALL: - lo->ldo_tls_crlcheck = * (int *) arg; - return 0; - } - return -1; -#endif - case LDAP_OPT_X_TLS_CIPHER_SUITE: - if ( lo->ldo_tls_ciphersuite ) LDAP_FREE( lo->ldo_tls_ciphersuite ); - lo->ldo_tls_ciphersuite = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - return 0; - - case LDAP_OPT_X_TLS_RANDOM_FILE: - if ( ld != NULL ) - return -1; -#ifdef HAVE_OPENSSL - if (tls_opt_randfile ) LDAP_FREE (tls_opt_randfile ); - tls_opt_randfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; -#endif - break; - - case LDAP_OPT_X_TLS_NEWCTX: - if ( !arg ) return -1; - if ( lo->ldo_tls_ctx ) - ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); - lo->ldo_tls_ctx = NULL; - return ldap_int_tls_init_ctx( lo, *(int *)arg ); - default: - return -1; - } - return 0; -} - -int -ldap_int_tls_start ( LDAP *ld, LDAPConn *conn, LDAPURLDesc *srv ) -{ - Sockbuf *sb = conn->lconn_sb; - char *host; - void *ssl; - - if( srv ) { - host = srv->lud_host; - } else { - host = conn->lconn_server->lud_host; - } - - /* avoid NULL host */ - if( host == NULL ) { - host = "localhost"; - } - - (void) ldap_pvt_tls_init(); - - /* - * Fortunately, the lib uses blocking io... - */ - if ( ldap_int_tls_connect( ld, conn ) < 0 ) { - ld->ld_errno = LDAP_CONNECT_ERROR; - return (ld->ld_errno); - } - - ssl = ldap_pvt_tls_sb_ctx( sb ); - assert( ssl != NULL ); - - /* - * compare host with name(s) in certificate - */ - if (ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER) { - ld->ld_errno = ldap_pvt_tls_check_hostname( ld, ssl, host ); - if (ld->ld_errno != LDAP_SUCCESS) { - return ld->ld_errno; - } - } - - return LDAP_SUCCESS; -} - -#ifdef HAVE_OPENSSL -/* Derived from openssl/apps/s_cb.c */ -static void -tls_info_cb( const SSL *ssl, int where, int ret ) -{ - int w; - char *op; - char *state = (char *) SSL_state_string_long( (SSL *)ssl ); - - w = where & ~SSL_ST_MASK; - if ( w & SSL_ST_CONNECT ) { - op = "SSL_connect"; - } else if ( w & SSL_ST_ACCEPT ) { - op = "SSL_accept"; - } else { - op = "undefined"; - } - -#ifdef HAVE_EBCDIC - if ( state ) { - state = LDAP_STRDUP( state ); - __etoa( state ); - } -#endif - if ( where & SSL_CB_LOOP ) { - Debug( LDAP_DEBUG_TRACE, - "TLS trace: %s:%s\n", - op, state, 0 ); - - } else if ( where & SSL_CB_ALERT ) { - char *atype = (char *) SSL_alert_type_string_long( ret ); - char *adesc = (char *) SSL_alert_desc_string_long( ret ); - op = ( where & SSL_CB_READ ) ? "read" : "write"; -#ifdef HAVE_EBCDIC - if ( atype ) { - atype = LDAP_STRDUP( atype ); - __etoa( atype ); - } - if ( adesc ) { - adesc = LDAP_STRDUP( adesc ); - __etoa( adesc ); - } -#endif - Debug( LDAP_DEBUG_TRACE, - "TLS trace: SSL3 alert %s:%s:%s\n", - op, atype, adesc ); -#ifdef HAVE_EBCDIC - if ( atype ) LDAP_FREE( atype ); - if ( adesc ) LDAP_FREE( adesc ); -#endif - } else if ( where & SSL_CB_EXIT ) { - if ( ret == 0 ) { - Debug( LDAP_DEBUG_TRACE, - "TLS trace: %s:failed in %s\n", - op, state, 0 ); - } else if ( ret < 0 ) { - Debug( LDAP_DEBUG_TRACE, - "TLS trace: %s:error in %s\n", - op, state, 0 ); - } - } -#ifdef HAVE_EBCDIC - if ( state ) LDAP_FREE( state ); -#endif -} - -static int -tls_verify_cb( int ok, X509_STORE_CTX *ctx ) -{ - X509 *cert; - int errnum; - int errdepth; - X509_NAME *subject; - X509_NAME *issuer; - char *sname; - char *iname; - char *certerr = NULL; - - cert = X509_STORE_CTX_get_current_cert( ctx ); - errnum = X509_STORE_CTX_get_error( ctx ); - errdepth = X509_STORE_CTX_get_error_depth( ctx ); - - /* - * X509_get_*_name return pointers to the internal copies of - * those things requested. So do not free them. - */ - subject = X509_get_subject_name( cert ); - issuer = X509_get_issuer_name( cert ); - /* X509_NAME_oneline, if passed a NULL buf, allocate memomry */ - sname = X509_NAME_oneline( subject, NULL, 0 ); - iname = X509_NAME_oneline( issuer, NULL, 0 ); - if ( !ok ) certerr = (char *)X509_verify_cert_error_string( errnum ); -#ifdef HAVE_EBCDIC - if ( sname ) __etoa( sname ); - if ( iname ) __etoa( iname ); - if ( certerr ) { - certerr = LDAP_STRDUP( certerr ); - __etoa( certerr ); - } -#endif - Debug( LDAP_DEBUG_TRACE, - "TLS certificate verification: depth: %d, err: %d, subject: %s,", - errdepth, errnum, - sname ? sname : "-unknown-" ); - Debug( LDAP_DEBUG_TRACE, " issuer: %s\n", iname ? iname : "-unknown-", 0, 0 ); - if ( !ok ) { - Debug( LDAP_DEBUG_ANY, - "TLS certificate verification: Error, %s\n", - certerr, 0, 0 ); - } - if ( sname ) - CRYPTO_free ( sname ); - if ( iname ) - CRYPTO_free ( iname ); -#ifdef HAVE_EBCDIC - if ( certerr ) LDAP_FREE( certerr ); -#endif - return ok; -} - -static int -tls_verify_ok( int ok, X509_STORE_CTX *ctx ) -{ - (void) tls_verify_cb( ok, ctx ); - return 1; -} - -/* Inspired by ERR_print_errors in OpenSSL */ -static void -tls_report_error( void ) -{ - unsigned long l; - char buf[200]; - const char *file; - int line; - - while ( ( l = ERR_get_error_line( &file, &line ) ) != 0 ) { - ERR_error_string_n( l, buf, sizeof( buf ) ); -#ifdef HAVE_EBCDIC - if ( file ) { - file = LDAP_STRDUP( file ); - __etoa( (char *)file ); - } - __etoa( buf ); -#endif - Debug( LDAP_DEBUG_ANY, "TLS: %s %s:%d\n", - buf, file, line ); -#ifdef HAVE_EBCDIC - if ( file ) LDAP_FREE( (void *)file ); -#endif - } -} - -static RSA * -tls_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ) -{ - RSA *tmp_rsa; - - /* FIXME: Pregenerate the key on startup */ - /* FIXME: Who frees the key? */ - tmp_rsa = RSA_generate_key( key_length, RSA_F4, NULL, NULL ); - - if ( !tmp_rsa ) { - Debug( LDAP_DEBUG_ANY, - "TLS: Failed to generate temporary %d-bit %s RSA key\n", - key_length, is_export ? "export" : "domestic", 0 ); - return NULL; - } - return tmp_rsa; -} - -static int -tls_seed_PRNG( const char *randfile ) -{ -#ifndef URANDOM_DEVICE - /* no /dev/urandom (or equiv) */ - long total=0; - char buffer[MAXPATHLEN]; - - if (randfile == NULL) { - /* The seed file is $RANDFILE if defined, otherwise $HOME/.rnd. - * If $HOME is not set or buffer too small to hold the pathname, - * an error occurs. - From RAND_file_name() man page. - * The fact is that when $HOME is NULL, .rnd is used. - */ - randfile = RAND_file_name( buffer, sizeof( buffer ) ); - - } else if (RAND_egd(randfile) > 0) { - /* EGD socket */ - return 0; - } - - if (randfile == NULL) { - Debug( LDAP_DEBUG_ANY, - "TLS: Use configuration file or $RANDFILE to define seed PRNG\n", - 0, 0, 0); - return -1; - } - - total = RAND_load_file(randfile, -1); - - if (RAND_status() == 0) { - Debug( LDAP_DEBUG_ANY, - "TLS: PRNG not been seeded with enough data\n", - 0, 0, 0); - return -1; - } - - /* assume if there was enough bits to seed that it's okay - * to write derived bits to the file - */ - RAND_write_file(randfile); - -#endif - - return 0; -} - -struct dhinfo { - int keylength; - const char *pem; - size_t size; -}; - - -/* From the OpenSSL 0.9.7 distro */ -static const char dhpem512[] = -"-----BEGIN DH PARAMETERS-----\n\ -MEYCQQDaWDwW2YUiidDkr3VvTMqS3UvlM7gE+w/tlO+cikQD7VdGUNNpmdsp13Yn\n\ -a6LT1BLiGPTdHghM9tgAPnxHdOgzAgEC\n\ ------END DH PARAMETERS-----\n"; - -static const char dhpem1024[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq\n\ -/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx\n\ -/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC\n\ ------END DH PARAMETERS-----\n"; - -static const char dhpem2048[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIIBCAKCAQEA7ZKJNYJFVcs7+6J2WmkEYb8h86tT0s0h2v94GRFS8Q7B4lW9aG9o\n\ -AFO5Imov5Jo0H2XMWTKKvbHbSe3fpxJmw/0hBHAY8H/W91hRGXKCeyKpNBgdL8sh\n\ -z22SrkO2qCnHJ6PLAMXy5fsKpFmFor2tRfCzrfnggTXu2YOzzK7q62bmqVdmufEo\n\ -pT8igNcLpvZxk5uBDvhakObMym9mX3rAEBoe8PwttggMYiiw7NuJKO4MqD1llGkW\n\ -aVM8U2ATsCun1IKHrRxynkE1/MJ86VHeYYX8GZt2YA8z+GuzylIOKcMH6JAWzMwA\n\ -Gbatw6QwizOhr9iMjZ0B26TE3X8LvW84wwIBAg==\n\ ------END DH PARAMETERS-----\n"; - -static const char dhpem4096[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIICCAKCAgEA/urRnb6vkPYc/KEGXWnbCIOaKitq7ySIq9dTH7s+Ri59zs77zty7\n\ -vfVlSe6VFTBWgYjD2XKUFmtqq6CqXMhVX5ElUDoYDpAyTH85xqNFLzFC7nKrff/H\n\ -TFKNttp22cZE9V0IPpzedPfnQkE7aUdmF9JnDyv21Z/818O93u1B4r0szdnmEvEF\n\ -bKuIxEHX+bp0ZR7RqE1AeifXGJX3d6tsd2PMAObxwwsv55RGkn50vHO4QxtTARr1\n\ -rRUV5j3B3oPMgC7Offxx+98Xn45B1/G0Prp11anDsR1PGwtaCYipqsvMwQUSJtyE\n\ -EOQWk+yFkeMe4vWv367eEi0Sd/wnC+TSXBE3pYvpYerJ8n1MceI5GQTdarJ77OW9\n\ -bGTHmxRsLSCM1jpLdPja5jjb4siAa6EHc4qN9c/iFKS3PQPJEnX7pXKBRs5f7AF3\n\ -W3RIGt+G9IVNZfXaS7Z/iCpgzgvKCs0VeqN38QsJGtC1aIkwOeyjPNy2G6jJ4yqH\n\ -ovXYt/0mc00vCWeSNS1wren0pR2EiLxX0ypjjgsU1mk/Z3b/+zVf7fZSIB+nDLjb\n\ -NPtUlJCVGnAeBK1J1nG3TQicqowOXoM6ISkdaXj5GPJdXHab2+S7cqhKGv5qC7rR\n\ -jT6sx7RUr0CNTxzLI7muV2/a4tGmj0PSdXQdsZ7tw7gbXlaWT1+MM2MCAQI=\n\ ------END DH PARAMETERS-----\n"; - -static const struct dhinfo dhpem[] = { - { 512, dhpem512, sizeof(dhpem512) }, - { 1024, dhpem1024, sizeof(dhpem1024) }, - { 2048, dhpem2048, sizeof(dhpem2048) }, - { 4096, dhpem4096, sizeof(dhpem4096) }, - { 0, NULL, 0 } -}; - -static DH * -tls_tmp_dh_cb( SSL *ssl, int is_export, int key_length ) -{ - struct dhplist *p = NULL; - BIO *b = NULL; - DH *dh = NULL; - int i; - - /* Do we have params of this length already? */ -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_lock( &tls_def_ctx_mutex ); -#endif - for ( p = dhparams; p; p=p->next ) { - if ( p->keylength == key_length ) { -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &tls_def_ctx_mutex ); -#endif - return p->param; - } - } - - /* No - check for hardcoded params */ - - for (i=0; dhpem[i].keylength; i++) { - if ( dhpem[i].keylength == key_length ) { - b = BIO_new_mem_buf( (char *)dhpem[i].pem, dhpem[i].size ); - break; - } - } - - if ( b ) { - dh = PEM_read_bio_DHparams( b, NULL, NULL, NULL ); - BIO_free( b ); - } - - /* Generating on the fly is expensive/slow... */ - if ( !dh ) { - dh = DH_generate_parameters( key_length, DH_GENERATOR_2, NULL, NULL ); - } - if ( dh ) { - p = LDAP_MALLOC( sizeof(struct dhplist) ); - if ( p != NULL ) { - p->keylength = key_length; - p->param = dh; - p->next = dhparams; - dhparams = p; - } - } - -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &tls_def_ctx_mutex ); -#endif - return dh; -} -#endif - -#endif /* HAVE_OPENSSL */ - -void * -ldap_pvt_tls_sb_ctx( Sockbuf *sb ) -{ -#ifdef HAVE_TLS - void *p; - - if (HAS_TLS( sb )) { - ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&p ); - return p; - } -#endif - - return NULL; -} - -int -ldap_pvt_tls_get_strength( void *s ) -{ -#ifdef HAVE_OPENSSL - SSL_CIPHER *c; - - c = SSL_get_current_cipher((SSL *)s); - return SSL_CIPHER_get_bits(c, NULL); -#elif defined(HAVE_GNUTLS) - tls_session *session = s; - gnutls_cipher_algorithm_t c; - - c = gnutls_cipher_get( session->session ); - return gnutls_cipher_get_key_size( c ) * 8; -#else - return 0; -#endif -} - - -int -ldap_pvt_tls_get_my_dn( void *s, struct berval *dn, LDAPDN_rewrite_dummy *func, unsigned flags ) -{ -#ifdef HAVE_TLS - struct berval der_dn; - int rc; -#ifdef HAVE_OPENSSL - X509 *x; - X509_NAME *xn; - - x = SSL_get_certificate((SSL *)s); - - if (!x) return LDAP_INVALID_CREDENTIALS; - - xn = X509_get_subject_name(x); - der_dn.bv_len = i2d_X509_NAME( xn, NULL ); - der_dn.bv_val = xn->bytes->data; -#elif defined(HAVE_GNUTLS) - tls_session *session = s; - const gnutls_datum_t *x; - struct berval bv; - - x = gnutls_certificate_get_ours( session->session ); - - if (!x) return LDAP_INVALID_CREDENTIALS; - - bv.bv_val = x->data; - bv.bv_len = x->size; - - x509_cert_get_dn( &bv, &der_dn, 1 ); -#endif - rc = ldap_X509dn2bv(&der_dn, dn, (LDAPDN_rewrite_func *)func, flags ); - return rc; -#else /* !HAVE_TLS */ - return LDAP_NOT_SUPPORTED; -#endif -} - -int -ldap_start_tls( LDAP *ld, - LDAPControl **serverctrls, - LDAPControl **clientctrls, - int *msgidp ) -{ - return ldap_extended_operation( ld, LDAP_EXOP_START_TLS, - NULL, serverctrls, clientctrls, msgidp ); -} - -int -ldap_install_tls( LDAP *ld ) -{ -#ifndef HAVE_TLS - return LDAP_NOT_SUPPORTED; -#else - if ( ldap_tls_inplace( ld ) ) { - return LDAP_LOCAL_ERROR; - } - - return ldap_int_tls_start( ld, ld->ld_defconn, NULL ); -#endif -} - -int -ldap_start_tls_s ( LDAP *ld, - LDAPControl **serverctrls, - LDAPControl **clientctrls ) -{ -#ifndef HAVE_TLS - return LDAP_NOT_SUPPORTED; -#else - int rc; - char *rspoid = NULL; - struct berval *rspdata = NULL; - - /* XXYYZ: this initiates operation only on default connection! */ - - if ( ldap_tls_inplace( ld ) ) { - return LDAP_LOCAL_ERROR; - } - - rc = ldap_extended_operation_s( ld, LDAP_EXOP_START_TLS, - NULL, serverctrls, clientctrls, &rspoid, &rspdata ); - - if ( rspoid != NULL ) { - LDAP_FREE(rspoid); - } - - if ( rspdata != NULL ) { - ber_bvfree( rspdata ); - } - - if ( rc == LDAP_SUCCESS ) { - rc = ldap_int_tls_start( ld, ld->ld_defconn, NULL ); - } - - return rc; -#endif -} - -/* These tags probably all belong in lber.h, but they're - * not normally encountered when processing LDAP, so maybe - * they belong somewhere else instead. - */ - -#define LBER_TAG_OID ((ber_tag_t) 0x06UL) - -/* Tags for string types used in a DirectoryString. - * - * Note that IA5string is not one of the defined choices for - * DirectoryString in X.520, but it gets used for email AVAs. - */ -#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL) -#define LBER_TAG_PRINTABLE ((ber_tag_t) 0x13UL) -#define LBER_TAG_TELETEX ((ber_tag_t) 0x14UL) -#define LBER_TAG_IA5 ((ber_tag_t) 0x16UL) -#define LBER_TAG_UNIVERSAL ((ber_tag_t) 0x1cUL) -#define LBER_TAG_BMP ((ber_tag_t) 0x1eUL) - -static oid_name * -find_oid( struct berval *oid ) -{ - int i; - - for ( i=0; !BER_BVISNULL( &oids[i].oid ); i++ ) { - if ( oids[i].oid.bv_len != oid->bv_len ) continue; - if ( !strcmp( oids[i].oid.bv_val, oid->bv_val )) - return &oids[i]; - } - return NULL; -} - -/* Convert a structured DN from an X.509 certificate into an LDAPV3 DN. - * x509_name must be raw DER. If func is non-NULL, the - * constructed DN will use numeric OIDs to identify attributeTypes, - * and the func() will be invoked to rewrite the DN with the given - * flags. - * - * Otherwise the DN will use shortNames from a hardcoded table. - */ -int -ldap_X509dn2bv( void *x509_name, struct berval *bv, LDAPDN_rewrite_func *func, - unsigned flags ) -{ - LDAPDN newDN; - LDAPRDN newRDN; - LDAPAVA *newAVA, *baseAVA; - BerElementBuffer berbuf; - BerElement *ber = (BerElement *)&berbuf; - char oids[8192], *oidptr = oids, *oidbuf = NULL; - void *ptrs[2048]; - char *dn_end, *rdn_end; - int i, navas, nrdns, rc = LDAP_SUCCESS; - size_t dnsize, oidrem = sizeof(oids), oidsize = 0; - int csize; - ber_tag_t tag; - ber_len_t len; - oid_name *oidname; - - struct berval Oid, Val, oid2, *in = x509_name; - - assert( bv != NULL ); - - bv->bv_len = 0; - bv->bv_val = NULL; - - navas = 0; - nrdns = 0; - - /* A DN is a SEQUENCE of RDNs. An RDN is a SET of AVAs. - * An AVA is a SEQUENCE of attr and value. - * Count the number of AVAs and RDNs - */ - ber_init2( ber, in, LBER_USE_DER ); - tag = ber_peek_tag( ber, &len ); - if ( tag != LBER_SEQUENCE ) - return LDAP_DECODING_ERROR; - - for ( tag = ber_first_element( ber, &len, &dn_end ); - tag == LBER_SET; - tag = ber_next_element( ber, &len, dn_end )) { - nrdns++; - for ( tag = ber_first_element( ber, &len, &rdn_end ); - tag == LBER_SEQUENCE; - tag = ber_next_element( ber, &len, rdn_end )) { - tag = ber_skip_tag( ber, &len ); - ber_skip_data( ber, len ); - navas++; - } - } - - /* Allocate the DN/RDN/AVA stuff as a single block */ - dnsize = sizeof(LDAPRDN) * (nrdns+1); - dnsize += sizeof(LDAPAVA *) * (navas+nrdns); - dnsize += sizeof(LDAPAVA) * navas; - if (dnsize > sizeof(ptrs)) { - newDN = (LDAPDN)LDAP_MALLOC( dnsize ); - if ( newDN == NULL ) - return LDAP_NO_MEMORY; - } else { - newDN = (LDAPDN)(char *)ptrs; - } - - newDN[nrdns] = NULL; - newRDN = (LDAPRDN)(newDN + nrdns+1); - newAVA = (LDAPAVA *)(newRDN + navas + nrdns); - baseAVA = newAVA; - - /* Rewind and start extracting */ - ber_rewind( ber ); - - tag = ber_first_element( ber, &len, &dn_end ); - for ( i = nrdns - 1; i >= 0; i-- ) { - newDN[i] = newRDN; - - for ( tag = ber_first_element( ber, &len, &rdn_end ); - tag == LBER_SEQUENCE; - tag = ber_next_element( ber, &len, rdn_end )) { - - *newRDN++ = newAVA; - tag = ber_skip_tag( ber, &len ); - tag = ber_get_stringbv( ber, &Oid, LBER_BV_NOTERM ); - if ( tag != LBER_TAG_OID ) { - rc = LDAP_DECODING_ERROR; - goto nomem; - } - - oid2.bv_val = oidptr; - oid2.bv_len = oidrem; - if ( ber_decode_oid( &Oid, &oid2 ) < 0 ) { - rc = LDAP_DECODING_ERROR; - goto nomem; - } - oidname = find_oid( &oid2 ); - if ( !oidname ) { - newAVA->la_attr = oid2; - oidptr += oid2.bv_len + 1; - oidrem -= oid2.bv_len + 1; - - /* Running out of OID buffer space? */ - if (oidrem < 128) { - if ( oidsize == 0 ) { - oidsize = sizeof(oids) * 2; - oidrem = oidsize; - oidbuf = LDAP_MALLOC( oidsize ); - if ( oidbuf == NULL ) goto nomem; - oidptr = oidbuf; - } else { - char *old = oidbuf; - oidbuf = LDAP_REALLOC( oidbuf, oidsize*2 ); - if ( oidbuf == NULL ) goto nomem; - /* Buffer moved! Fix AVA pointers */ - if ( old != oidbuf ) { - LDAPAVA *a; - long dif = oidbuf - old; - - for (a=baseAVA; a<=newAVA; a++){ - if (a->la_attr.bv_val >= old && - a->la_attr.bv_val <= (old + oidsize)) - a->la_attr.bv_val += dif; - } - } - oidptr = oidbuf + oidsize - oidrem; - oidrem += oidsize; - oidsize *= 2; - } - } - } else { - if ( func ) { - newAVA->la_attr = oidname->oid; - } else { - newAVA->la_attr = oidname->name; - } - } - tag = ber_get_stringbv( ber, &Val, LBER_BV_NOTERM ); - switch(tag) { - case LBER_TAG_UNIVERSAL: - /* This uses 32-bit ISO 10646-1 */ - csize = 4; goto to_utf8; - case LBER_TAG_BMP: - /* This uses 16-bit ISO 10646-1 */ - csize = 2; goto to_utf8; - case LBER_TAG_TELETEX: - /* This uses 8-bit, assume ISO 8859-1 */ - csize = 1; -to_utf8: rc = ldap_ucs_to_utf8s( &Val, csize, &newAVA->la_value ); - newAVA->la_flags |= LDAP_AVA_FREE_VALUE; - if (rc != LDAP_SUCCESS) goto nomem; - newAVA->la_flags = LDAP_AVA_NONPRINTABLE; - break; - case LBER_TAG_UTF8: - newAVA->la_flags = LDAP_AVA_NONPRINTABLE; - /* This is already in UTF-8 encoding */ - case LBER_TAG_IA5: - case LBER_TAG_PRINTABLE: - /* These are always 7-bit strings */ - newAVA->la_value = Val; - default: - ; - } - newAVA->la_private = NULL; - newAVA->la_flags = LDAP_AVA_STRING; - newAVA++; - } - *newRDN++ = NULL; - tag = ber_next_element( ber, &len, dn_end ); - } - - if ( func ) { - rc = func( newDN, flags, NULL ); - if ( rc != LDAP_SUCCESS ) - goto nomem; - } - - rc = ldap_dn2bv_x( newDN, bv, LDAP_DN_FORMAT_LDAPV3, NULL ); - -nomem: - for (;baseAVA < newAVA; baseAVA++) { - if (baseAVA->la_flags & LDAP_AVA_FREE_ATTR) - LDAP_FREE( baseAVA->la_attr.bv_val ); - if (baseAVA->la_flags & LDAP_AVA_FREE_VALUE) - LDAP_FREE( baseAVA->la_value.bv_val ); - } - - if ( oidsize != 0 ) - LDAP_FREE( oidbuf ); - if ( newDN != (LDAPDN)(char *) ptrs ) - LDAP_FREE( newDN ); - return rc; -} - diff --git a/libraries/libldap/tls2.c b/libraries/libldap/tls2.c new file mode 100644 index 0000000000000000000000000000000000000000..37e012aed7ded3ee7e997511a52e73b341b3d561 --- /dev/null +++ b/libraries/libldap/tls2.c @@ -0,0 +1,1175 @@ +/* tls.c - Handle tls/ssl. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2009 The OpenLDAP Foundation. + * 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: restructured by Howard Chu. + */ + +#include "portable.h" +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> + +#include "ldap-int.h" + +#ifdef HAVE_TLS + +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include <ldap_pvt_thread.h> +#endif + +static tls_impl *tls_imp = &ldap_int_tls_impl; + +#endif /* HAVE_TLS */ + +/* RFC2459 minimum required set of supported attribute types + * in a certificate DN + */ +typedef struct oid_name { + struct berval oid; + struct berval name; +} oid_name; + +static oid_name oids[] = { + { BER_BVC("2.5.4.3"), BER_BVC("cn") }, + { BER_BVC("2.5.4.4"), BER_BVC("sn") }, + { BER_BVC("2.5.4.6"), BER_BVC("c") }, + { BER_BVC("2.5.4.7"), BER_BVC("l") }, + { BER_BVC("2.5.4.8"), BER_BVC("st") }, + { BER_BVC("2.5.4.10"), BER_BVC("o") }, + { BER_BVC("2.5.4.11"), BER_BVC("ou") }, + { BER_BVC("2.5.4.12"), BER_BVC("title") }, + { BER_BVC("2.5.4.41"), BER_BVC("name") }, + { BER_BVC("2.5.4.42"), BER_BVC("givenName") }, + { BER_BVC("2.5.4.43"), BER_BVC("initials") }, + { BER_BVC("2.5.4.44"), BER_BVC("generationQualifier") }, + { BER_BVC("2.5.4.46"), BER_BVC("dnQualifier") }, + { BER_BVC("1.2.840.113549.1.9.1"), BER_BVC("email") }, + { BER_BVC("0.9.2342.19200300.100.1.25"), BER_BVC("dc") }, + { BER_BVNULL, BER_BVNULL } +}; + +void +ldap_pvt_tls_ctx_free ( void *c ) +{ + if ( !c ) return; + tls_imp->ti_ctx_free( c ); +} + +static void +tls_ctx_ref( tls_ctx *ctx ) +{ + if ( !ctx ) return; + + tls_imp->ti_ctx_ref( ctx ); +} + +#ifdef LDAP_R_COMPILE +/* + * an extra mutex for the default ctx. + */ +static ldap_pvt_thread_mutex_t tls_def_ctx_mutex; +#endif + +void +ldap_int_tls_destroy( struct ldapoptions *lo ) +{ + if ( lo->ldo_tls_ctx ) { + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + } + + if ( lo->ldo_tls_certfile ) { + LDAP_FREE( lo->ldo_tls_certfile ); + lo->ldo_tls_certfile = NULL; + } + if ( lo->ldo_tls_keyfile ) { + LDAP_FREE( lo->ldo_tls_keyfile ); + lo->ldo_tls_keyfile = NULL; + } + if ( lo->ldo_tls_dhfile ) { + LDAP_FREE( lo->ldo_tls_dhfile ); + lo->ldo_tls_dhfile = NULL; + } + if ( lo->ldo_tls_cacertfile ) { + LDAP_FREE( lo->ldo_tls_cacertfile ); + lo->ldo_tls_cacertfile = NULL; + } + if ( lo->ldo_tls_cacertdir ) { + LDAP_FREE( lo->ldo_tls_cacertdir ); + lo->ldo_tls_cacertdir = NULL; + } + if ( lo->ldo_tls_ciphersuite ) { + LDAP_FREE( lo->ldo_tls_ciphersuite ); + lo->ldo_tls_ciphersuite = NULL; + } + if ( lo->ldo_tls_crlfile ) { + LDAP_FREE( lo->ldo_tls_crlfile ); + lo->ldo_tls_crlfile = NULL; + } +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +void +ldap_pvt_tls_destroy( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + int i; + + ldap_int_tls_destroy( lo ); + + tls_imp->ti_tls_destroy(); +} + +/* + * Initialize a particular TLS implementation. + * Called once per implementation. + */ +static int +tls_init(tls_impl *impl ) +{ + static int tls_initialized = 0; + + if ( !tls_initialized++ ) { +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &tls_def_ctx_mutex ); +#endif + } + + if ( impl->ti_inited++ ) return 0; + +#ifdef LDAP_R_COMPILE + impl->ti_thr_init(); +#endif + return impl->ti_tls_init(); +} + +/* + * Initialize TLS subsystem. Called once per implementation. + */ +int +ldap_pvt_tls_init( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + + return tls_init( tls_imp ); +} + +/* + * initialize a new TLS context + */ +static int +ldap_int_tls_init_ctx( struct ldapoptions *lo, int is_server ) +{ + int i, rc = 0; + tls_impl *ti = tls_imp; + struct ldaptls lts = lo->ldo_tls_info; + + if ( lo->ldo_tls_ctx ) + return 0; + + tls_init( ti ); + + if ( is_server && !lts.lt_certfile && !lts.lt_keyfile && + !lts.lt_cacertfile && !lts.lt_cacertdir ) { + /* minimum configuration not provided */ + return LDAP_NOT_SUPPORTED; + } + +#ifdef HAVE_EBCDIC + /* This ASCII/EBCDIC handling is a real pain! */ + if ( lts.lt_ciphersuite ) { + lts.lt_ciphersuite = LDAP_STRDUP( lts.lt_ciphersuite ); + __atoe( lts.lt_ciphersuite ); + } + if ( lts.lt_cacertfile ) { + lts.lt_cacertfile = LDAP_STRDUP( lts.lt_cacertfile ); + __atoe( lts.lt_cacertfile ); + } + if ( lts.lt_certfile ) { + lts.lt_certfile = LDAP_STRDUP( lts.lt_certfile ); + __atoe( lts.lt_certfile ); + } + if ( lts.lt_keyfile ) { + lts.lt_keyfile = LDAP_STRDUP( lts.lt_keyfile ); + __atoe( lts.lt_keyfile ); + } + if ( lts.lt_crlfile ) { + lts.lt_crlfile = LDAP_STRDUP( lts.lt_crlfile ); + __atoe( lts.lt_crlfile ); + } + if ( lts.lt_cacertdir ) { + lts.lt_cacertdir = LDAP_STRDUP( lts.lt_cacertdir ); + __atoe( lts.lt_cacertdir ); + } + if ( lts.lt_dhfile ) { + lts.lt_dhfile = LDAP_STRDUP( lts.lt_dhfile ); + __atoe( lts.lt_dhfile ); + } +#endif + lo->ldo_tls_ctx = ti->ti_ctx_new( lo ); + if ( lo->ldo_tls_ctx == NULL ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not allocate default ctx.\n", + 0,0,0); + rc = -1; + goto error_exit; + } + + rc = ti->ti_ctx_init( lo, <s, is_server ); + +error_exit: + if ( rc < 0 && lo->ldo_tls_ctx != NULL ) { + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + } +#ifdef HAVE_EBCDIC + LDAP_FREE( lts.lt_ciphersuite ); + LDAP_FREE( lts.lt_cacertfile ); + LDAP_FREE( lts.lt_certfile ); + LDAP_FREE( lts.lt_keyfile ); + LDAP_FREE( lts.lt_crlfile ); + LDAP_FREE( lts.lt_cacertdir ); + LDAP_FREE( lts.lt_dhfile ); +#endif + return rc; +} + +/* + * initialize the default context + */ +int +ldap_pvt_tls_init_def_ctx( int is_server ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + int rc; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &tls_def_ctx_mutex ); +#endif + rc = ldap_int_tls_init_ctx( lo, is_server ); +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &tls_def_ctx_mutex ); +#endif + return rc; +} + +static tls_session * +alloc_handle( void *ctx_arg, int is_server ) +{ + tls_ctx *ctx; + tls_session *ssl; + + if ( ctx_arg ) { + ctx = ctx_arg; + } else { + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + if ( ldap_pvt_tls_init_def_ctx( is_server ) < 0 ) return NULL; + ctx = lo->ldo_tls_ctx; + } + + ssl = tls_imp->ti_session_new( ctx, is_server ); + if ( ssl == NULL ) { + Debug( LDAP_DEBUG_ANY,"TLS: can't create ssl handle.\n",0,0,0); + return NULL; + } + return ssl; +} + +static int +update_flags( Sockbuf *sb, tls_session * ssl, int rc ) +{ + sb->sb_trans_needs_read = 0; + sb->sb_trans_needs_write = 0; + + return tls_imp->ti_session_upflags( sb, ssl, rc ); +} + +/* + * Call this to do a TLS connect on a sockbuf. ctx_arg can be + * a SSL_CTX * or NULL, in which case the default ctx is used. + * + * Return value: + * + * 0 - Success. Connection is ready for communication. + * <0 - Error. Can't create a TLS stream. + * >0 - Partial success. + * Do a select (using information from lber_pvt_sb_needs_{read,write} + * and call again. + */ + +static int +ldap_int_tls_connect( LDAP *ld, LDAPConn *conn ) +{ + Sockbuf *sb = conn->lconn_sb; + int err; + tls_session *ssl = NULL; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); + if ( !ssl ) { + struct ldapoptions *lo; + tls_ctx *ctx; + + ctx = ld->ld_options.ldo_tls_ctx; + + ssl = alloc_handle( ctx, 0 ); + + if ( ssl == NULL ) return -1; + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)"tls_" ); +#endif + ber_sockbuf_add_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); + + lo = LDAP_INT_GLOBAL_OPT(); + if( ctx == NULL ) { + ctx = lo->ldo_tls_ctx; + ld->ld_options.ldo_tls_ctx = ctx; + tls_ctx_ref( ctx ); + } + if ( ld->ld_options.ldo_tls_connect_cb ) + ld->ld_options.ldo_tls_connect_cb( ld, ssl, ctx, + ld->ld_options.ldo_tls_connect_arg ); + if ( lo && lo->ldo_tls_connect_cb && lo->ldo_tls_connect_cb != + ld->ld_options.ldo_tls_connect_cb ) + lo->ldo_tls_connect_cb( ld, ssl, ctx, lo->ldo_tls_connect_arg ); + } + + err = tls_imp->ti_session_connect( ld, ssl ); + +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + + if ( err < 0 ) + { + char buf[256], *msg; + if ( update_flags( sb, ssl, err )) { + return 1; + } + + msg = tls_imp->ti_session_errmsg( err, buf, sizeof(buf) ); + if ( msg ) { + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( msg ); +#ifdef HAVE_EBCDIC + if ( ld->ld_error ) __etoa(ld->ld_error); +#endif + } + + Debug( LDAP_DEBUG_ANY,"TLS: can't connect: %s.\n", + ld->ld_error ? ld->ld_error : "" ,0,0); + + ber_sockbuf_remove_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT ); +#ifdef LDAP_DEBUG + ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT ); +#endif + return -1; + } + + return 0; +} + +/* + * Call this to do a TLS accept on a sockbuf. + * Everything else is the same as with tls_connect. + */ +int +ldap_pvt_tls_accept( Sockbuf *sb, void *ctx_arg ) +{ + int err; + tls_session *ssl = NULL; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); + if ( !ssl ) { + ssl = alloc_handle( ctx_arg, 1 ); + if ( ssl == NULL ) return -1; + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)"tls_" ); +#endif + ber_sockbuf_add_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); + } + + err = tls_imp->ti_session_accept( ssl ); + +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + + if ( err < 0 ) + { + char buf[256]; + if ( update_flags( sb, ssl, err )) return 1; + + Debug( LDAP_DEBUG_ANY,"TLS: can't accept: %s.\n", + tls_imp->ti_session_errmsg( err, buf, sizeof(buf) ),0,0 ); + + ber_sockbuf_remove_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT ); +#ifdef LDAP_DEBUG + ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT ); +#endif + return -1; + } + return 0; +} + +int +ldap_pvt_tls_inplace ( Sockbuf *sb ) +{ + tls_session *ssl = NULL; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); + return ssl != NULL; +} + +int +ldap_tls_inplace( LDAP *ld ) +{ + Sockbuf *sb = NULL; + + if ( ld->ld_defconn && ld->ld_defconn->lconn_sb ) { + sb = ld->ld_defconn->lconn_sb; + + } else if ( ld->ld_sb ) { + sb = ld->ld_sb; + + } else { + return 0; + } + + return ldap_pvt_tls_inplace( sb ); +} + +int +ldap_pvt_tls_get_peer_dn( void *s, struct berval *dn, + LDAPDN_rewrite_dummy *func, unsigned flags ) +{ + tls_session *session = s; + struct berval bvdn; + int rc; + + rc = tls_imp->ti_session_peer_dn( session, &bvdn ); + if ( rc ) return rc; + + rc = ldap_X509dn2bv( &bvdn, dn, + (LDAPDN_rewrite_func *)func, flags); + return rc; +} + +int +ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) +{ + tls_session *session = s; + + return tls_imp->ti_session_chkhost( ld, session, name_in ); +} + +int +ldap_int_tls_config( LDAP *ld, int option, const char *arg ) +{ + int i; + + switch( option ) { + case LDAP_OPT_X_TLS_CACERTFILE: + case LDAP_OPT_X_TLS_CACERTDIR: + case LDAP_OPT_X_TLS_CERTFILE: + case LDAP_OPT_X_TLS_KEYFILE: + case LDAP_OPT_X_TLS_RANDOM_FILE: + case LDAP_OPT_X_TLS_CIPHER_SUITE: + case LDAP_OPT_X_TLS_DHFILE: + case LDAP_OPT_X_TLS_CRLFILE: /* GnuTLS only */ + return ldap_pvt_tls_set_option( ld, option, (void *) arg ); + + case LDAP_OPT_X_TLS_REQUIRE_CERT: + case LDAP_OPT_X_TLS: + i = -1; + if ( strcasecmp( arg, "never" ) == 0 ) { + i = LDAP_OPT_X_TLS_NEVER ; + + } else if ( strcasecmp( arg, "demand" ) == 0 ) { + i = LDAP_OPT_X_TLS_DEMAND ; + + } else if ( strcasecmp( arg, "allow" ) == 0 ) { + i = LDAP_OPT_X_TLS_ALLOW ; + + } else if ( strcasecmp( arg, "try" ) == 0 ) { + i = LDAP_OPT_X_TLS_TRY ; + + } else if ( ( strcasecmp( arg, "hard" ) == 0 ) || + ( strcasecmp( arg, "on" ) == 0 ) || + ( strcasecmp( arg, "yes" ) == 0) || + ( strcasecmp( arg, "true" ) == 0 ) ) + { + i = LDAP_OPT_X_TLS_HARD ; + } + + if (i >= 0) { + return ldap_pvt_tls_set_option( ld, option, &i ); + } + return -1; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: { + char *next; + long l; + l = strtol( arg, &next, 10 ); + if ( l < 0 || l > 0xff || next == arg || + ( *next != '\0' && *next != '.' ) ) + return -1; + i = l << 8; + if (*next == '.') { + arg = next + 1; + l = strtol( arg, &next, 10 ); + if ( l < 0 || l > 0xff || next == arg || *next != '\0' ) + return -1; + i += l; + } + return ldap_pvt_tls_set_option( ld, option, &i ); + } + case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ + i = -1; + if ( strcasecmp( arg, "none" ) == 0 ) { + i = LDAP_OPT_X_TLS_CRL_NONE ; + } else if ( strcasecmp( arg, "peer" ) == 0 ) { + i = LDAP_OPT_X_TLS_CRL_PEER ; + } else if ( strcasecmp( arg, "all" ) == 0 ) { + i = LDAP_OPT_X_TLS_CRL_ALL ; + } + if (i >= 0) { + return ldap_pvt_tls_set_option( ld, option, &i ); + } + return -1; + } + return -1; +} + +int +ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg ) +{ + struct ldapoptions *lo; + + if( ld != NULL ) { + assert( LDAP_VALID( ld ) ); + + if( !LDAP_VALID( ld ) ) { + return LDAP_OPT_ERROR; + } + + lo = &ld->ld_options; + + } else { + /* Get pointer to global option structure */ + lo = LDAP_INT_GLOBAL_OPT(); + if ( lo == NULL ) { + return LDAP_NO_MEMORY; + } + } + + switch( option ) { + case LDAP_OPT_X_TLS: + *(int *)arg = lo->ldo_tls_mode; + break; + case LDAP_OPT_X_TLS_CTX: + *(void **)arg = lo->ldo_tls_ctx; + if ( lo->ldo_tls_ctx ) { + tls_ctx_ref( lo->ldo_tls_ctx ); + } + break; + case LDAP_OPT_X_TLS_CACERTFILE: + *(char **)arg = lo->ldo_tls_cacertfile ? + LDAP_STRDUP( lo->ldo_tls_cacertfile ) : NULL; + break; + case LDAP_OPT_X_TLS_CACERTDIR: + *(char **)arg = lo->ldo_tls_cacertdir ? + LDAP_STRDUP( lo->ldo_tls_cacertdir ) : NULL; + break; + case LDAP_OPT_X_TLS_CERTFILE: + *(char **)arg = lo->ldo_tls_certfile ? + LDAP_STRDUP( lo->ldo_tls_certfile ) : NULL; + break; + case LDAP_OPT_X_TLS_KEYFILE: + *(char **)arg = lo->ldo_tls_keyfile ? + LDAP_STRDUP( lo->ldo_tls_keyfile ) : NULL; + break; + case LDAP_OPT_X_TLS_DHFILE: + *(char **)arg = lo->ldo_tls_dhfile ? + LDAP_STRDUP( lo->ldo_tls_dhfile ) : NULL; + break; + case LDAP_OPT_X_TLS_CRLFILE: /* GnuTLS only */ + *(char **)arg = lo->ldo_tls_crlfile ? + LDAP_STRDUP( lo->ldo_tls_crlfile ) : NULL; + break; + case LDAP_OPT_X_TLS_REQUIRE_CERT: + *(int *)arg = lo->ldo_tls_require_cert; + break; + case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ + *(int *)arg = lo->ldo_tls_crlcheck; + break; + case LDAP_OPT_X_TLS_CIPHER_SUITE: + *(char **)arg = lo->ldo_tls_ciphersuite ? + LDAP_STRDUP( lo->ldo_tls_ciphersuite ) : NULL; + break; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: + *(int *)arg = lo->ldo_tls_protocol_min; + break; + case LDAP_OPT_X_TLS_RANDOM_FILE: /* OpenSSL only */ + *(char **)arg = lo->ldo_tls_randfile ? + LDAP_STRDUP( lo->ldo_tls_randfile ) : NULL; + break; + case LDAP_OPT_X_TLS_SSL_CTX: { + void *retval = 0; + if ( ld != NULL ) { + LDAPConn *conn = ld->ld_defconn; + if ( conn != NULL ) { + Sockbuf *sb = conn->lconn_sb; + retval = ldap_pvt_tls_sb_ctx( sb ); + } + } + *(void **)arg = retval; + break; + } + case LDAP_OPT_X_TLS_CONNECT_CB: + *(LDAP_TLS_CONNECT_CB **)arg = lo->ldo_tls_connect_cb; + break; + case LDAP_OPT_X_TLS_CONNECT_ARG: + *(void **)arg = lo->ldo_tls_connect_arg; + break; + default: + return -1; + } + return 0; +} + +int +ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) +{ + struct ldapoptions *lo; + + if( ld != NULL ) { + assert( LDAP_VALID( ld ) ); + + if( !LDAP_VALID( ld ) ) { + return LDAP_OPT_ERROR; + } + + lo = &ld->ld_options; + + } else { + /* Get pointer to global option structure */ + lo = LDAP_INT_GLOBAL_OPT(); + if ( lo == NULL ) { + return LDAP_NO_MEMORY; + } + } + + switch( option ) { + case LDAP_OPT_X_TLS: + if ( !arg ) return -1; + + switch( *(int *) arg ) { + case LDAP_OPT_X_TLS_NEVER: + case LDAP_OPT_X_TLS_DEMAND: + case LDAP_OPT_X_TLS_ALLOW: + case LDAP_OPT_X_TLS_TRY: + case LDAP_OPT_X_TLS_HARD: + if (lo != NULL) { + lo->ldo_tls_mode = *(int *)arg; + } + + return 0; + } + return -1; + + case LDAP_OPT_X_TLS_CTX: + if ( lo->ldo_tls_ctx ) + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = arg; + tls_ctx_ref( lo->ldo_tls_ctx ); + return 0; + case LDAP_OPT_X_TLS_CONNECT_CB: + lo->ldo_tls_connect_cb = (LDAP_TLS_CONNECT_CB *)arg; + return 0; + case LDAP_OPT_X_TLS_CONNECT_ARG: + lo->ldo_tls_connect_arg = arg; + return 0; + case LDAP_OPT_X_TLS_CACERTFILE: + if ( lo->ldo_tls_cacertfile ) LDAP_FREE( lo->ldo_tls_cacertfile ); + lo->ldo_tls_cacertfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_CACERTDIR: + if ( lo->ldo_tls_cacertdir ) LDAP_FREE( lo->ldo_tls_cacertdir ); + lo->ldo_tls_cacertdir = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_CERTFILE: + if ( lo->ldo_tls_certfile ) LDAP_FREE( lo->ldo_tls_certfile ); + lo->ldo_tls_certfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_KEYFILE: + if ( lo->ldo_tls_keyfile ) LDAP_FREE( lo->ldo_tls_keyfile ); + lo->ldo_tls_keyfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_DHFILE: + if ( lo->ldo_tls_dhfile ) LDAP_FREE( lo->ldo_tls_dhfile ); + lo->ldo_tls_dhfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_CRLFILE: /* GnuTLS only */ + if ( lo->ldo_tls_crlfile ) LDAP_FREE( lo->ldo_tls_crlfile ); + lo->ldo_tls_crlfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_REQUIRE_CERT: + if ( !arg ) return -1; + switch( *(int *) arg ) { + case LDAP_OPT_X_TLS_NEVER: + case LDAP_OPT_X_TLS_DEMAND: + case LDAP_OPT_X_TLS_ALLOW: + case LDAP_OPT_X_TLS_TRY: + case LDAP_OPT_X_TLS_HARD: + lo->ldo_tls_require_cert = * (int *) arg; + return 0; + } + return -1; + case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ + if ( !arg ) return -1; + switch( *(int *) arg ) { + case LDAP_OPT_X_TLS_CRL_NONE: + case LDAP_OPT_X_TLS_CRL_PEER: + case LDAP_OPT_X_TLS_CRL_ALL: + lo->ldo_tls_crlcheck = * (int *) arg; + return 0; + } + return -1; + case LDAP_OPT_X_TLS_CIPHER_SUITE: + if ( lo->ldo_tls_ciphersuite ) LDAP_FREE( lo->ldo_tls_ciphersuite ); + lo->ldo_tls_ciphersuite = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + + case LDAP_OPT_X_TLS_PROTOCOL_MIN: + if ( !arg ) return -1; + lo->ldo_tls_protocol_min = *(int *)arg; + return 0; + + case LDAP_OPT_X_TLS_RANDOM_FILE: /* OpenSSL only */ + if ( ld != NULL ) + return -1; + if ( lo->ldo_tls_randfile ) LDAP_FREE (lo->ldo_tls_randfile ); + lo->ldo_tls_randfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + break; + + case LDAP_OPT_X_TLS_NEWCTX: + if ( !arg ) return -1; + if ( lo->ldo_tls_ctx ) + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + return ldap_int_tls_init_ctx( lo, *(int *)arg ); + default: + return -1; + } + return 0; +} + +int +ldap_int_tls_start ( LDAP *ld, LDAPConn *conn, LDAPURLDesc *srv ) +{ + Sockbuf *sb = conn->lconn_sb; + char *host; + void *ssl; + + if( srv ) { + host = srv->lud_host; + } else { + host = conn->lconn_server->lud_host; + } + + /* avoid NULL host */ + if( host == NULL ) { + host = "localhost"; + } + + (void) tls_init( tls_imp ); + + /* + * Fortunately, the lib uses blocking io... + */ + if ( ldap_int_tls_connect( ld, conn ) < 0 ) { + ld->ld_errno = LDAP_CONNECT_ERROR; + return (ld->ld_errno); + } + + ssl = ldap_pvt_tls_sb_ctx( sb ); + assert( ssl != NULL ); + + /* + * compare host with name(s) in certificate + */ + if (ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER) { + ld->ld_errno = ldap_pvt_tls_check_hostname( ld, ssl, host ); + if (ld->ld_errno != LDAP_SUCCESS) { + return ld->ld_errno; + } + } + + return LDAP_SUCCESS; +} + +void * +ldap_pvt_tls_sb_ctx( Sockbuf *sb ) +{ +#ifdef HAVE_TLS + void *p = NULL; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&p ); + return p; +#endif + return NULL; +} + +int +ldap_pvt_tls_get_strength( void *s ) +{ + tls_session *session = s; + + return tls_imp->ti_session_strength( session ); +} + + +int +ldap_pvt_tls_get_my_dn( void *s, struct berval *dn, LDAPDN_rewrite_dummy *func, unsigned flags ) +{ +#ifdef HAVE_TLS + tls_session *session = s; + struct berval der_dn; + int rc; + + tls_imp->ti_session_my_dn( session, &der_dn ); + rc = ldap_X509dn2bv(&der_dn, dn, (LDAPDN_rewrite_func *)func, flags ); + return rc; +#else /* !HAVE_TLS */ + return LDAP_NOT_SUPPORTED; +#endif +} + +int +ldap_start_tls( LDAP *ld, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int *msgidp ) +{ + return ldap_extended_operation( ld, LDAP_EXOP_START_TLS, + NULL, serverctrls, clientctrls, msgidp ); +} + +int +ldap_install_tls( LDAP *ld ) +{ +#ifndef HAVE_TLS + return LDAP_NOT_SUPPORTED; +#else + if ( ldap_tls_inplace( ld ) ) { + return LDAP_LOCAL_ERROR; + } + + return ldap_int_tls_start( ld, ld->ld_defconn, NULL ); +#endif +} + +int +ldap_start_tls_s ( LDAP *ld, + LDAPControl **serverctrls, + LDAPControl **clientctrls ) +{ +#ifndef HAVE_TLS + return LDAP_NOT_SUPPORTED; +#else + int rc; + char *rspoid = NULL; + struct berval *rspdata = NULL; + + /* XXYYZ: this initiates operation only on default connection! */ + + if ( ldap_tls_inplace( ld ) ) { + return LDAP_LOCAL_ERROR; + } + + rc = ldap_extended_operation_s( ld, LDAP_EXOP_START_TLS, + NULL, serverctrls, clientctrls, &rspoid, &rspdata ); + + if ( rspoid != NULL ) { + LDAP_FREE(rspoid); + } + + if ( rspdata != NULL ) { + ber_bvfree( rspdata ); + } + + if ( rc == LDAP_SUCCESS ) { + rc = ldap_int_tls_start( ld, ld->ld_defconn, NULL ); + } + + return rc; +#endif +} + +/* These tags probably all belong in lber.h, but they're + * not normally encountered when processing LDAP, so maybe + * they belong somewhere else instead. + */ + +#define LBER_TAG_OID ((ber_tag_t) 0x06UL) + +/* Tags for string types used in a DirectoryString. + * + * Note that IA5string is not one of the defined choices for + * DirectoryString in X.520, but it gets used for email AVAs. + */ +#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL) +#define LBER_TAG_PRINTABLE ((ber_tag_t) 0x13UL) +#define LBER_TAG_TELETEX ((ber_tag_t) 0x14UL) +#define LBER_TAG_IA5 ((ber_tag_t) 0x16UL) +#define LBER_TAG_UNIVERSAL ((ber_tag_t) 0x1cUL) +#define LBER_TAG_BMP ((ber_tag_t) 0x1eUL) + +static oid_name * +find_oid( struct berval *oid ) +{ + int i; + + for ( i=0; !BER_BVISNULL( &oids[i].oid ); i++ ) { + if ( oids[i].oid.bv_len != oid->bv_len ) continue; + if ( !strcmp( oids[i].oid.bv_val, oid->bv_val )) + return &oids[i]; + } + return NULL; +} + +/* Convert a structured DN from an X.509 certificate into an LDAPV3 DN. + * x509_name must be raw DER. If func is non-NULL, the + * constructed DN will use numeric OIDs to identify attributeTypes, + * and the func() will be invoked to rewrite the DN with the given + * flags. + * + * Otherwise the DN will use shortNames from a hardcoded table. + */ +int +ldap_X509dn2bv( void *x509_name, struct berval *bv, LDAPDN_rewrite_func *func, + unsigned flags ) +{ + LDAPDN newDN; + LDAPRDN newRDN; + LDAPAVA *newAVA, *baseAVA; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + char oids[8192], *oidptr = oids, *oidbuf = NULL; + void *ptrs[2048]; + char *dn_end, *rdn_end; + int i, navas, nrdns, rc = LDAP_SUCCESS; + size_t dnsize, oidrem = sizeof(oids), oidsize = 0; + int csize; + ber_tag_t tag; + ber_len_t len; + oid_name *oidname; + + struct berval Oid, Val, oid2, *in = x509_name; + + assert( bv != NULL ); + + bv->bv_len = 0; + bv->bv_val = NULL; + + navas = 0; + nrdns = 0; + + /* A DN is a SEQUENCE of RDNs. An RDN is a SET of AVAs. + * An AVA is a SEQUENCE of attr and value. + * Count the number of AVAs and RDNs + */ + ber_init2( ber, in, LBER_USE_DER ); + tag = ber_peek_tag( ber, &len ); + if ( tag != LBER_SEQUENCE ) + return LDAP_DECODING_ERROR; + + for ( tag = ber_first_element( ber, &len, &dn_end ); + tag == LBER_SET; + tag = ber_next_element( ber, &len, dn_end )) { + nrdns++; + for ( tag = ber_first_element( ber, &len, &rdn_end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, rdn_end )) { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + navas++; + } + } + + /* Allocate the DN/RDN/AVA stuff as a single block */ + dnsize = sizeof(LDAPRDN) * (nrdns+1); + dnsize += sizeof(LDAPAVA *) * (navas+nrdns); + dnsize += sizeof(LDAPAVA) * navas; + if (dnsize > sizeof(ptrs)) { + newDN = (LDAPDN)LDAP_MALLOC( dnsize ); + if ( newDN == NULL ) + return LDAP_NO_MEMORY; + } else { + newDN = (LDAPDN)(char *)ptrs; + } + + newDN[nrdns] = NULL; + newRDN = (LDAPRDN)(newDN + nrdns+1); + newAVA = (LDAPAVA *)(newRDN + navas + nrdns); + baseAVA = newAVA; + + /* Rewind and start extracting */ + ber_rewind( ber ); + + tag = ber_first_element( ber, &len, &dn_end ); + for ( i = nrdns - 1; i >= 0; i-- ) { + newDN[i] = newRDN; + + for ( tag = ber_first_element( ber, &len, &rdn_end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, rdn_end )) { + + *newRDN++ = newAVA; + tag = ber_skip_tag( ber, &len ); + tag = ber_get_stringbv( ber, &Oid, LBER_BV_NOTERM ); + if ( tag != LBER_TAG_OID ) { + rc = LDAP_DECODING_ERROR; + goto nomem; + } + + oid2.bv_val = oidptr; + oid2.bv_len = oidrem; + if ( ber_decode_oid( &Oid, &oid2 ) < 0 ) { + rc = LDAP_DECODING_ERROR; + goto nomem; + } + oidname = find_oid( &oid2 ); + if ( !oidname ) { + newAVA->la_attr = oid2; + oidptr += oid2.bv_len + 1; + oidrem -= oid2.bv_len + 1; + + /* Running out of OID buffer space? */ + if (oidrem < 128) { + if ( oidsize == 0 ) { + oidsize = sizeof(oids) * 2; + oidrem = oidsize; + oidbuf = LDAP_MALLOC( oidsize ); + if ( oidbuf == NULL ) goto nomem; + oidptr = oidbuf; + } else { + char *old = oidbuf; + oidbuf = LDAP_REALLOC( oidbuf, oidsize*2 ); + if ( oidbuf == NULL ) goto nomem; + /* Buffer moved! Fix AVA pointers */ + if ( old != oidbuf ) { + LDAPAVA *a; + long dif = oidbuf - old; + + for (a=baseAVA; a<=newAVA; a++){ + if (a->la_attr.bv_val >= old && + a->la_attr.bv_val <= (old + oidsize)) + a->la_attr.bv_val += dif; + } + } + oidptr = oidbuf + oidsize - oidrem; + oidrem += oidsize; + oidsize *= 2; + } + } + } else { + if ( func ) { + newAVA->la_attr = oidname->oid; + } else { + newAVA->la_attr = oidname->name; + } + } + tag = ber_get_stringbv( ber, &Val, LBER_BV_NOTERM ); + switch(tag) { + case LBER_TAG_UNIVERSAL: + /* This uses 32-bit ISO 10646-1 */ + csize = 4; goto to_utf8; + case LBER_TAG_BMP: + /* This uses 16-bit ISO 10646-1 */ + csize = 2; goto to_utf8; + case LBER_TAG_TELETEX: + /* This uses 8-bit, assume ISO 8859-1 */ + csize = 1; +to_utf8: rc = ldap_ucs_to_utf8s( &Val, csize, &newAVA->la_value ); + newAVA->la_flags |= LDAP_AVA_FREE_VALUE; + if (rc != LDAP_SUCCESS) goto nomem; + newAVA->la_flags = LDAP_AVA_NONPRINTABLE; + break; + case LBER_TAG_UTF8: + newAVA->la_flags = LDAP_AVA_NONPRINTABLE; + /* This is already in UTF-8 encoding */ + case LBER_TAG_IA5: + case LBER_TAG_PRINTABLE: + /* These are always 7-bit strings */ + newAVA->la_value = Val; + default: + ; + } + newAVA->la_private = NULL; + newAVA->la_flags = LDAP_AVA_STRING; + newAVA++; + } + *newRDN++ = NULL; + tag = ber_next_element( ber, &len, dn_end ); + } + + if ( func ) { + rc = func( newDN, flags, NULL ); + if ( rc != LDAP_SUCCESS ) + goto nomem; + } + + rc = ldap_dn2bv_x( newDN, bv, LDAP_DN_FORMAT_LDAPV3, NULL ); + +nomem: + for (;baseAVA < newAVA; baseAVA++) { + if (baseAVA->la_flags & LDAP_AVA_FREE_ATTR) + LDAP_FREE( baseAVA->la_attr.bv_val ); + if (baseAVA->la_flags & LDAP_AVA_FREE_VALUE) + LDAP_FREE( baseAVA->la_value.bv_val ); + } + + if ( oidsize != 0 ) + LDAP_FREE( oidbuf ); + if ( newDN != (LDAPDN)(char *) ptrs ) + LDAP_FREE( newDN ); + return rc; +} + diff --git a/libraries/libldap/tls_g.c b/libraries/libldap/tls_g.c new file mode 100644 index 0000000000000000000000000000000000000000..a59e33a9acf84799a6a7af396c5727ff31737925 --- /dev/null +++ b/libraries/libldap/tls_g.c @@ -0,0 +1,1025 @@ +/* tls_g.c - Handle tls/ssl using GNUTLS. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2009 The OpenLDAP Foundation. + * 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: GNUTLS support written by Howard Chu and + * Matt Backes; sponsored by The Written Word (thewrittenword.com) + * and Stanford University (stanford.edu). + */ + +#include "portable.h" + +#ifdef HAVE_GNUTLS + +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include <ldap_pvt_thread.h> +#endif + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gcrypt.h> + +#define DH_BITS (1024) + +#if LIBGNUTLS_VERSION_NUMBER >= 0x020200 +#define HAVE_CIPHERSUITES 1 +/* This is a kludge. gcrypt 1.4.x has support. Recent GnuTLS requires gcrypt 1.4.x + * but that dependency isn't reflected in their configure script, resulting in + * build errors on older gcrypt. So, if they have a working build environment, + * assume gcrypt is new enough. + */ +#define HAVE_GCRYPT_RAND 1 +#else +#undef HAVE_CIPHERSUITES +#undef HAVE_GCRYPT_RAND +#endif + +#ifndef HAVE_CIPHERSUITES +/* Versions prior to 2.2.0 didn't handle cipher suites, so we had to + * kludge them ourselves. + */ +typedef struct tls_cipher_suite { + const char *name; + gnutls_kx_algorithm_t kx; + gnutls_cipher_algorithm_t cipher; + gnutls_mac_algorithm_t mac; + gnutls_protocol_t version; +} tls_cipher_suite; +#endif + +typedef struct tlsg_ctx { + struct ldapoptions *lo; + gnutls_certificate_credentials_t cred; + gnutls_dh_params_t dh_params; + unsigned long verify_depth; + int refcount; +#ifdef HAVE_CIPHERSUITES + gnutls_priority_t prios; +#else + int *kx_list; + int *cipher_list; + int *mac_list; +#endif +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t ref_mutex; +#endif +} tlsg_ctx; + +typedef struct tlsg_session { + gnutls_session_t session; + tlsg_ctx *ctx; + struct berval peer_der_dn; +} tlsg_session; + +#ifndef HAVE_CIPHERSUITES +static tls_cipher_suite *tlsg_ciphers; +static int tlsg_n_ciphers; +#endif + +static int tlsg_parse_ciphers( tlsg_ctx *ctx, char *suites ); +static int tlsg_cert_verify( tlsg_session *s ); + +#ifdef LDAP_R_COMPILE + +static int +tlsg_mutex_init( void **priv ) +{ + int err = 0; + ldap_pvt_thread_mutex_t *lock = LDAP_MALLOC( sizeof( ldap_pvt_thread_mutex_t )); + + if ( !lock ) + err = ENOMEM; + if ( !err ) { + err = ldap_pvt_thread_mutex_init( lock ); + if ( err ) + LDAP_FREE( lock ); + else + *priv = lock; + } + return err; +} + +static int +tlsg_mutex_destroy( void **lock ) +{ + int err = ldap_pvt_thread_mutex_destroy( *lock ); + LDAP_FREE( *lock ); + return err; +} + +static int +tlsg_mutex_lock( void **lock ) +{ + return ldap_pvt_thread_mutex_lock( *lock ); +} + +static int +tlsg_mutex_unlock( void **lock ) +{ + return ldap_pvt_thread_mutex_unlock( *lock ); +} + +static struct gcry_thread_cbs tlsg_thread_cbs = { + GCRY_THREAD_OPTION_USER, + NULL, + tlsg_mutex_init, + tlsg_mutex_destroy, + tlsg_mutex_lock, + tlsg_mutex_unlock, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static void +tlsg_thr_init( void ) +{ + gcry_control (GCRYCTL_SET_THREAD_CBS, &tlsg_thread_cbs); +} +#endif /* LDAP_R_COMPILE */ + +/* + * Initialize TLS subsystem. Should be called only once. + */ +static int +tlsg_init( void ) +{ +#ifdef HAVE_GCRYPT_RAND + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + if ( lo->ldo_tls_randfile && + gcry_control( GCRYCTL_SET_RNDEGD_SOCKET, lo->ldo_tls_randfile )) { + Debug( LDAP_DEBUG_ANY, + "TLS: gcry_control GCRYCTL_SET_RNDEGD_SOCKET failed\n", + 0, 0, 0); + return -1; + } +#endif + + gnutls_global_init(); + +#ifndef HAVE_CIPHERSUITES + /* GNUtls cipher suite handling: The library ought to parse suite + * names for us, but it doesn't. It will return a list of suite names + * that it supports, so we can do parsing ourselves. It ought to tell + * us how long the list is, but it doesn't do that either, so we just + * have to count it manually... + */ + { + int i = 0; + tls_cipher_suite *ptr, tmp; + char cs_id[2]; + + while ( gnutls_cipher_suite_info( i, cs_id, &tmp.kx, &tmp.cipher, + &tmp.mac, &tmp.version )) + i++; + tlsg_n_ciphers = i; + + /* Store a copy */ + tlsg_ciphers = LDAP_MALLOC(tlsg_n_ciphers * sizeof(tls_cipher_suite)); + if ( !tlsg_ciphers ) + return -1; + for ( i=0; i<tlsg_n_ciphers; i++ ) { + tlsg_ciphers[i].name = gnutls_cipher_suite_info( i, cs_id, + &tlsg_ciphers[i].kx, &tlsg_ciphers[i].cipher, &tlsg_ciphers[i].mac, + &tlsg_ciphers[i].version ); + } + } +#endif + return 0; +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +static void +tlsg_destroy( void ) +{ +#ifndef HAVE_CIPHERSUITES + LDAP_FREE( tlsg_ciphers ); + tlsg_ciphers = NULL; + tlsg_n_ciphers = 0; +#endif + gnutls_global_deinit(); +} + +static tls_ctx * +tlsg_ctx_new ( struct ldapoptions *lo ) +{ + tlsg_ctx *ctx; + + ctx = ber_memcalloc ( 1, sizeof (*ctx) ); + if ( ctx ) { + ctx->lo = lo; + if ( gnutls_certificate_allocate_credentials( &ctx->cred )) { + ber_memfree( ctx ); + return NULL; + } + ctx->refcount = 1; +#ifdef HAVE_CIPHERSUITES + gnutls_priority_init( &ctx->prios, "NORMAL", NULL ); +#endif +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &ctx->ref_mutex ); +#endif + } + return (tls_ctx *)ctx; +} + +static void +tlsg_ctx_ref( tls_ctx *ctx ) +{ + tlsg_ctx *c = (tlsg_ctx *)ctx; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &c->ref_mutex ); +#endif + c->refcount++; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &c->ref_mutex ); +#endif +} + +static void +tlsg_ctx_free ( tls_ctx *ctx ) +{ + tlsg_ctx *c = (tlsg_ctx *)ctx; + int refcount; + + if ( !c ) return; + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &c->ref_mutex ); +#endif + refcount = --c->refcount; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &c->ref_mutex ); +#endif + if ( refcount ) + return; +#ifdef HAVE_CIPHERSUITES + gnutls_priority_deinit( c->prios ); +#else + LDAP_FREE( c->kx_list ); +#endif + gnutls_certificate_free_credentials( c->cred ); + ber_memfree ( c ); +} + +/* + * initialize a new TLS context + */ +static int +tlsg_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlsg_ctx *ctx = lo->ldo_tls_ctx; + int rc; + + if ( lo->ldo_tls_ciphersuite && + tlsg_parse_ciphers( ctx, lt->lt_ciphersuite )) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + return -1; + } + + if (lo->ldo_tls_cacertdir != NULL) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: cacertdir not implemented for gnutls\n", + NULL, NULL, NULL ); + } + + if (lo->ldo_tls_cacertfile != NULL) { + rc = gnutls_certificate_set_x509_trust_file( + ctx->cred, + lt->lt_cacertfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) return -1; + } + + if ( lo->ldo_tls_certfile && lo->ldo_tls_keyfile ) { + rc = gnutls_certificate_set_x509_key_file( + ctx->cred, + lt->lt_certfile, + lt->lt_keyfile, + GNUTLS_X509_FMT_PEM ); + if ( rc ) return -1; + } else if ( lo->ldo_tls_certfile || lo->ldo_tls_keyfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: only one of certfile and keyfile specified\n", + NULL, NULL, NULL ); + return -1; + } + + if ( lo->ldo_tls_dhfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: ignoring dhfile\n", + NULL, NULL, NULL ); + } + + if ( lo->ldo_tls_crlfile ) { + rc = gnutls_certificate_set_x509_crl_file( + ctx->cred, + lt->lt_crlfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) return -1; + rc = 0; + } + if ( is_server ) { + gnutls_dh_params_init(&ctx->dh_params); + gnutls_dh_params_generate2(ctx->dh_params, DH_BITS); + } + return 0; +} + +static tls_session * +tlsg_session_new ( tls_ctx * ctx, int is_server ) +{ + tlsg_ctx *c = (tlsg_ctx *)ctx; + tlsg_session *session; + + session = ber_memcalloc ( 1, sizeof (*session) ); + if ( !session ) + return NULL; + + session->ctx = c; + gnutls_init( &session->session, is_server ? GNUTLS_SERVER : GNUTLS_CLIENT ); +#ifdef HAVE_CIPHERSUITES + gnutls_priority_set( session->session, c->prios ); +#else + gnutls_set_default_priority( session->session ); + if ( c->kx_list ) { + gnutls_kx_set_priority( session->session, c->kx_list ); + gnutls_cipher_set_priority( session->session, c->cipher_list ); + gnutls_mac_set_priority( session->session, c->mac_list ); + } +#endif + if ( c->cred ) + gnutls_credentials_set( session->session, GNUTLS_CRD_CERTIFICATE, c->cred ); + + if ( is_server ) { + int flag = 0; + if ( c->lo->ldo_tls_require_cert ) { + flag = GNUTLS_CERT_REQUEST; + if ( c->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || + c->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) + flag = GNUTLS_CERT_REQUIRE; + gnutls_certificate_server_set_request( session->session, flag ); + } + } + return (tls_session *)session; +} + +static int +tlsg_session_accept( tls_session *session ) +{ + tlsg_session *s = (tlsg_session *)session; + int rc; + + rc = gnutls_handshake( s->session ); + if ( rc == 0 && s->ctx->lo->ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER ) { + rc = tlsg_cert_verify( s ); + if ( rc && s->ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW ) + rc = 0; + } + return rc; +} + +static int +tlsg_session_connect( LDAP *ld, tls_session *session ) +{ + return tlsg_session_accept( session); +} + +static int +tlsg_session_upflags( Sockbuf *sb, tls_session *session, int rc ) +{ + tlsg_session *s = (tlsg_session *)session; + + if ( rc != GNUTLS_E_INTERRUPTED && rc != GNUTLS_E_AGAIN ) + return 0; + + switch (gnutls_record_get_direction (s->session)) { + case 0: + sb->sb_trans_needs_read = 1; + return 1; + case 1: + sb->sb_trans_needs_write = 1; + return 1; + } + return 0; +} + +static char * +tlsg_session_errmsg( int rc, char *buf, size_t len ) +{ + return (char *)gnutls_strerror( rc ); +} + +static void +tlsg_x509_cert_dn( struct berval *cert, struct berval *dn, int get_subject ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t i; + + ber_init2( ber, cert, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_skip_tag( ber, &len ); /* Context + Constructed (version) */ + if ( tag == 0xa0 ) /* Version is optional */ + tag = ber_get_int( ber, &i ); /* Int: Version */ + tag = ber_get_int( ber, &i ); /* Int: Serial */ + tag = ber_skip_tag( ber, &len ); /* Sequence: Signature */ + ber_skip_data( ber, len ); + if ( !get_subject ) { + tag = ber_peek_tag( ber, &len ); /* Sequence: Issuer DN */ + } else { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Sequence: Validity */ + ber_skip_data( ber, len ); + tag = ber_peek_tag( ber, &len ); /* Sequence: Subject DN */ + } + len = ber_ptrlen( ber ); + dn->bv_val = cert->bv_val + len; + dn->bv_len = cert->bv_len - len; +} + +static int +tlsg_session_my_dn( tls_session *session, struct berval *der_dn ) +{ + tlsg_session *s = (tlsg_session *)session; + const gnutls_datum_t *x; + struct berval bv; + + x = gnutls_certificate_get_ours( s->session ); + + if (!x) return LDAP_INVALID_CREDENTIALS; + + bv.bv_val = x->data; + bv.bv_len = x->size; + + tlsg_x509_cert_dn( &bv, der_dn, 1 ); + return 0; +} + +static int +tlsg_session_peer_dn( tls_session *session, struct berval *der_dn ) +{ + tlsg_session *s = (tlsg_session *)session; + if ( !s->peer_der_dn.bv_val ) { + const gnutls_datum_t *peer_cert_list; + int list_size; + struct berval bv; + + peer_cert_list = gnutls_certificate_get_peers( s->session, + &list_size ); + if ( !peer_cert_list ) return LDAP_INVALID_CREDENTIALS; + + bv.bv_len = peer_cert_list->size; + bv.bv_val = peer_cert_list->data; + + tlsg_x509_cert_dn( &bv, &s->peer_der_dn, 1 ); + } + *der_dn = s->peer_der_dn; + return 0; +} + +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +#define CN_OID "2.5.4.3" + +static int +tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) +{ + tlsg_session *s = (tlsg_session *)session; + int i, ret; + const gnutls_datum_t *peer_cert_list; + int list_size; + struct berval bv; + char altname[NI_MAXHOST]; + size_t altnamesize; + + gnutls_x509_crt_t cert; + gnutls_datum_t *x; + const char *name; + char *ptr; + char *domain = NULL; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + int n, len1 = 0, len2 = 0; + int ntype = IS_DNS; + time_t now = time(0); + + if( ldap_int_hostname && + ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) + { + name = ldap_int_hostname; + } else { + name = name_in; + } + + peer_cert_list = gnutls_certificate_get_peers( s->session, + &list_size ); + if ( !peer_cert_list ) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get peer certificate.\n", + 0, 0, 0 ); + /* If this was a fatal condition, things would have + * aborted long before now. + */ + return LDAP_SUCCESS; + } + ret = gnutls_x509_crt_init( &cert ); + if ( ret < 0 ) + return LDAP_LOCAL_ERROR; + ret = gnutls_x509_crt_import( cert, peer_cert_list, GNUTLS_X509_FMT_DER ); + if ( ret ) { + gnutls_x509_crt_deinit( cert ); + return LDAP_LOCAL_ERROR; + } + +#ifdef LDAP_PF_INET6 + if (name[0] == '[' && strchr(name, ']')) { + char *n2 = ldap_strdup(name+1); + *strchr(n2, ']') = 2; + if (inet_pton(AF_INET6, n2, &addr)) + ntype = IS_IP6; + LDAP_FREE(n2); + } else +#endif + if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { + if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; + } + + if (ntype == IS_DNS) { + len1 = strlen(name); + domain = strchr(name, '.'); + if (domain) { + len2 = len1 - (domain-name); + } + } + + for ( i=0, ret=0; ret >= 0; i++ ) { + altnamesize = sizeof(altname); + ret = gnutls_x509_crt_get_subject_alt_name( cert, i, + altname, &altnamesize, NULL ); + if ( ret < 0 ) break; + + /* ignore empty */ + if ( altnamesize == 0 ) continue; + + if ( ret == GNUTLS_SAN_DNSNAME ) { + if (ntype != IS_DNS) continue; + + /* Is this an exact match? */ + if ((len1 == altnamesize) && !strncasecmp(name, altname, len1)) { + break; + } + + /* Is this a wildcard match? */ + if (domain && (altname[0] == '*') && (altname[1] == '.') && + (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) + { + break; + } + } else if ( ret == GNUTLS_SAN_IPADDRESS ) { + if (ntype == IS_DNS) continue; + +#ifdef LDAP_PF_INET6 + if (ntype == IS_IP6 && altnamesize != sizeof(struct in6_addr)) { + continue; + } else +#endif + if (ntype == IS_IP4 && altnamesize != sizeof(struct in_addr)) { + continue; + } + if (!memcmp(altname, &addr, altnamesize)) { + break; + } + } + } + if ( ret >= 0 ) { + ret = LDAP_SUCCESS; + } else { + altnamesize = sizeof(altname); + ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID, + 0, 0, altname, &altnamesize ); + if ( ret < 0 ) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get common name from peer certificate.\n", + 0, 0, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: unable to get CN from peer certificate")); + + } else { + ret = LDAP_LOCAL_ERROR; + if ( !len1 ) len1 = strlen( name ); + if ( len1 == altnamesize && strncasecmp(name, altname, altnamesize) == 0 ) { + ret = LDAP_SUCCESS; + + } else if (( altname[0] == '*' ) && ( altname[1] == '.' )) { + /* Is this a wildcard match? */ + if( domain && + (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) { + ret = LDAP_SUCCESS; + } + } + } + + if( ret == LDAP_LOCAL_ERROR ) { + altname[altnamesize] = '\0'; + Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " + "common name in certificate (%s).\n", + name, altname, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: hostname does not match CN in peer certificate")); + } + } + gnutls_x509_crt_deinit( cert ); + return ret; +} + +static int +tlsg_session_strength( tls_session *session ) +{ + tlsg_session *s = (tlsg_session *)session; + gnutls_cipher_algorithm_t c; + + c = gnutls_cipher_get( s->session ); + return gnutls_cipher_get_key_size( c ) * 8; +} + +/* suites is a string of colon-separated cipher suite names. */ +static int +tlsg_parse_ciphers( tlsg_ctx *ctx, char *suites ) +{ +#ifdef HAVE_CIPHERSUITES + const char *err; + return gnutls_priority_init( &ctx->prios, suites, &err ); +#else + char *ptr, *end; + int i, j, len, num; + int *list, nkx = 0, ncipher = 0, nmac = 0; + int *kx, *cipher, *mac; + + num = 0; + ptr = suites; + do { + end = strchr(ptr, ':'); + if ( end ) + len = end - ptr; + else + len = strlen(ptr); + for (i=0; i<tlsg_n_ciphers; i++) { + if ( !strncasecmp( tlsg_ciphers[i].name, ptr, len )) { + num++; + break; + } + } + if ( i == tlsg_n_ciphers ) { + /* unrecognized cipher suite */ + return -1; + } + ptr += len + 1; + } while (end); + + /* Space for all 3 lists */ + list = LDAP_MALLOC( (num+1) * sizeof(int) * 3 ); + if ( !list ) + return -1; + kx = list; + cipher = kx+num+1; + mac = cipher+num+1; + + ptr = suites; + do { + end = strchr(ptr, ':'); + if ( end ) + len = end - ptr; + else + len = strlen(ptr); + for (i=0; i<tlsg_n_ciphers; i++) { + /* For each cipher suite, insert its algorithms into + * their respective priority lists. Make sure they + * only appear once in each list. + */ + if ( !strncasecmp( tlsg_ciphers[i].name, ptr, len )) { + for (j=0; j<nkx; j++) + if ( kx[j] == tlsg_ciphers[i].kx ) + break; + if ( j == nkx ) + kx[nkx++] = tlsg_ciphers[i].kx; + for (j=0; j<ncipher; j++) + if ( cipher[j] == tlsg_ciphers[i].cipher ) + break; + if ( j == ncipher ) + cipher[ncipher++] = tlsg_ciphers[i].cipher; + for (j=0; j<nmac; j++) + if ( mac[j] == tlsg_ciphers[i].mac ) + break; + if ( j == nmac ) + mac[nmac++] = tlsg_ciphers[i].mac; + break; + } + } + ptr += len + 1; + } while (end); + kx[nkx] = 0; + cipher[ncipher] = 0; + mac[nmac] = 0; + ctx->kx_list = kx; + ctx->cipher_list = cipher; + ctx->mac_list = mac; + return 0; +#endif +} + +/* + * TLS support for LBER Sockbufs + */ + +struct tls_data { + tlsg_session *session; + Sockbuf_IO_Desc *sbiod; +}; + +static ssize_t +tlsg_recv( gnutls_transport_ptr_t ptr, void *buf, size_t len ) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); +} + +static ssize_t +tlsg_send( gnutls_transport_ptr_t ptr, const void *buf, size_t len ) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); +} + +static int +tlsg_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + tlsg_session *session = arg; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + gnutls_transport_set_ptr( session->session, (gnutls_transport_ptr)p ); + gnutls_transport_set_pull_function( session->session, tlsg_recv ); + gnutls_transport_set_push_function( session->session, tlsg_send ); + p->session = session; + p->sbiod = sbiod; + sbiod->sbiod_pvt = p; + return 0; +} + +static int +tlsg_sb_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + gnutls_deinit ( p->session->session ); + LBER_FREE( p->session ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +tlsg_sb_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + gnutls_bye ( p->session->session, GNUTLS_SHUT_RDWR ); + return 0; +} + +static int +tlsg_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((tlsg_session **)arg) = p->session; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + if( gnutls_record_check_pending( p->session->session ) > 0 ) { + return 1; + } + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +tlsg_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = gnutls_record_recv ( p->session->session, buf, len ); + switch (ret) { + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_AGAIN: + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + ret = 0; + break; + case GNUTLS_E_REHANDSHAKE: + for ( ret = gnutls_handshake ( p->session->session ); + ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN; + ret = gnutls_handshake ( p->session->session ) ); + sbiod->sbiod_sb->sb_trans_needs_read = 1; + ret = 0; + break; + default: + sbiod->sbiod_sb->sb_trans_needs_read = 0; + } + return ret; +} + +static ber_slen_t +tlsg_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = gnutls_record_send ( p->session->session, (char *)buf, len ); + + if ( ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + ret = 0; + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +static Sockbuf_IO tlsg_sbio = +{ + tlsg_sb_setup, /* sbi_setup */ + tlsg_sb_remove, /* sbi_remove */ + tlsg_sb_ctrl, /* sbi_ctrl */ + tlsg_sb_read, /* sbi_read */ + tlsg_sb_write, /* sbi_write */ + tlsg_sb_close /* sbi_close */ +}; + +/* Certs are not automatically varified during the handshake */ +static int +tlsg_cert_verify( tlsg_session *ssl ) +{ + unsigned int status = 0; + int err; + time_t now = time(0); + + err = gnutls_certificate_verify_peers2( ssl->session, &status ); + if ( err < 0 ) { + Debug( LDAP_DEBUG_ANY,"TLS: gnutls_certificate_verify_peers2 failed %d\n", + err,0,0 ); + return -1; + } + if ( status ) { + Debug( LDAP_DEBUG_TRACE,"TLS: peer cert untrusted or revoked (0x%x)\n", + status, 0,0 ); + return -1; + } + if ( gnutls_certificate_expiration_time_peers( ssl->session ) < now ) { + Debug( LDAP_DEBUG_ANY, "TLS: peer certificate is expired\n", + 0, 0, 0 ); + return -1; + } + if ( gnutls_certificate_activation_time_peers( ssl->session ) > now ) { + Debug( LDAP_DEBUG_ANY, "TLS: peer certificate not yet active\n", + 0, 0, 0 ); + return -1; + } + return 0; +} + +tls_impl ldap_int_tls_impl = { + "GnuTLS", + + tlsg_init, + tlsg_destroy, + + tlsg_ctx_new, + tlsg_ctx_ref, + tlsg_ctx_free, + tlsg_ctx_init, + + tlsg_session_new, + tlsg_session_connect, + tlsg_session_accept, + tlsg_session_upflags, + tlsg_session_errmsg, + tlsg_session_my_dn, + tlsg_session_peer_dn, + tlsg_session_chkhost, + tlsg_session_strength, + + &tlsg_sbio, + +#ifdef LDAP_R_COMPILE + tlsg_thr_init, +#else + NULL, +#endif + + 0 +}; + +#endif /* HAVE_GNUTLS */ diff --git a/libraries/libldap/tls_m.c b/libraries/libldap/tls_m.c new file mode 100644 index 0000000000000000000000000000000000000000..9735703054fe30310476a7b2ca4f2bd4698075ec --- /dev/null +++ b/libraries/libldap/tls_m.c @@ -0,0 +1,851 @@ +/* tls_m.c - Handle tls/ssl using Mozilla NSS. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2009 The OpenLDAP Foundation. + * 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: written by Howard Chu. + */ + +#include "portable.h" + +#ifdef HAVE_MOZNSS + +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include <ldap_pvt_thread.h> +#endif + +#include <nspr.h> +#include <nss.h> +#include <ssl.h> + +typedef struct tlsm_ctx { + PRFileDesc *tc_model; + int tc_refcnt; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t tc_refmutex; +#endif +} tlsm_ctx; + +typedef PRFileDesc tlsm_session; + +static PRDescIdentity tlsm_layer_id; + +static const PRIOMethods tlsm_PR_methods; + +extern tls_impl ldap_int_tls_impl; + +#ifdef LDAP_R_COMPILE + +static void +tlsm_thr_init( void ) +{ +} + +#endif /* LDAP_R_COMPILE */ + +/* + * Initialize TLS subsystem. Should be called only once. + */ +static int +tlsm_init( void ) +{ + PR_Init(0, 0, 0); + + tlsm_layer_id = PR_GetUniqueIdentity("OpenLDAP"); + + if ( !NSS_IsInitialized() ) { + NSS_NoDB_Init(""); + + NSS_SetDomesticPolicy(); + } + + /* No cipher suite handling for now */ + + return 0; +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +static void +tlsm_destroy( void ) +{ + NSS_Shutdown(); + + PR_Cleanup(); +} + +static tls_ctx * +tlsm_ctx_new ( struct ldapoptions *lo ) +{ + tlsm_ctx *ctx; + + ctx = LDAP_MALLOC( sizeof (*ctx) ); + if ( ctx ) { + PRFileDesc *fd = PR_CreateIOLayerStub(tlsm_layer_id, &tlsm_PR_methods); + if ( fd ) { + ctx->tc_model = SSL_ImportFD( NULL, fd ); + if ( ctx->tc_model ) { + ctx->tc_refcnt = 1; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &ctx->tc_refmutex ); +#endif + } else { + PR_DELETE( fd ); + LDAP_FREE( ctx ); + ctx = NULL; + } + } else { + LDAP_FREE( ctx ); + ctx = NULL; + } + } + return (tls_ctx *)ctx; +} + +static void +tlsm_ctx_ref( tls_ctx *ctx ) +{ + tlsm_ctx *c = (tlsm_ctx *)ctx; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &c->tc_refmutex ); +#endif + c->tc_refcnt++; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &c->tc_refmutex ); +#endif +} + +static void +tlsm_ctx_free ( tls_ctx *ctx ) +{ + tlsm_ctx *c = (tlsm_ctx *)ctx; + int refcount; + + if ( !c ) return; + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &c->tc_refmutex ); +#endif + refcount = --c->tc_refcnt; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &c->tc_refmutex ); +#endif + if ( refcount ) + return; + PR_Close( c->tc_model ); +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_destroy( &c->tc_refmutex ); +#endif + LDAP_FREE( c ); +} + +/* + * initialize a new TLS context + */ +static int +tlsm_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlsm_ctx *ctx = lo->ldo_tls_ctx; + int rc; + + SSL_OptionSet( ctx->tc_model, SSL_SECURITY, PR_TRUE ); + SSL_OptionSet( ctx->tc_model, SSL_HANDSHAKE_AS_CLIENT, !is_server ); + SSL_OptionSet( ctx->tc_model, SSL_HANDSHAKE_AS_SERVER, is_server ); + + /* See SECMOD_OpenUserDB() */ +#if 0 + if ( lo->ldo_tls_ciphersuite && + tlsm_parse_ciphers( ctx, lt->lt_ciphersuite )) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + return -1; + } + + if (lo->ldo_tls_cacertdir != NULL) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: cacertdir not implemented for gnutls\n", + NULL, NULL, NULL ); + } + + if (lo->ldo_tls_cacertfile != NULL) { + rc = gnutls_certificate_set_x509_trust_file( + ctx->cred, + lt->lt_cacertfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) return -1; + } + + if ( lo->ldo_tls_certfile && lo->ldo_tls_keyfile ) { + rc = gnutls_certificate_set_x509_key_file( + ctx->cred, + lt->lt_certfile, + lt->lt_keyfile, + GNUTLS_X509_FMT_PEM ); + if ( rc ) return -1; + } else if ( lo->ldo_tls_certfile || lo->ldo_tls_keyfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: only one of certfile and keyfile specified\n", + NULL, NULL, NULL ); + return -1; + } + + if ( lo->ldo_tls_dhfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: ignoring dhfile\n", + NULL, NULL, NULL ); + } + + if ( lo->ldo_tls_crlfile ) { + rc = gnutls_certificate_set_x509_crl_file( + ctx->cred, + lt->lt_crlfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) return -1; + rc = 0; + } + if ( is_server ) { + gnutls_dh_params_init(&ctx->dh_params); + gnutls_dh_params_generate2(ctx->dh_params, DH_BITS); + } +#endif + return 0; +} + +static tls_session * +tlsm_session_new ( tls_ctx * ctx, int is_server ) +{ + tlsm_ctx *c = (tlsm_ctx *)ctx; + tlsm_session *session; + PRFileDesc *fd; + + fd = PR_CreateIOLayerStub(tlsm_layer_id, &tlsm_PR_methods); + if ( !fd ) { + return NULL; + } + + session = SSL_ImportFD( c->tc_model, fd ); + if ( !session ) { + PR_DELETE( fd ); + return NULL; + } + + SSL_ResetHandshake( session, is_server ); + + return (tls_session *)session; +} + +static int +tlsm_session_accept( tls_session *session ) +{ + tlsm_session *s = (tlsm_session *)session; + + return SSL_ForceHandshake( s ); +} + +static int +tlsm_session_connect( LDAP *ld, tls_session *session ) +{ + tlsm_session *s = (tlsm_session *)session; + int rc; + + /* By default, NSS checks the cert hostname for us */ + rc = SSL_SetURL( s, ld->ld_options.ldo_defludp->lud_host ); + return SSL_ForceHandshake( s ); +} + +static int +tlsm_session_upflags( Sockbuf *sb, tls_session *session, int rc ) +{ + /* Should never happen */ + rc = PR_GetError(); + + if ( rc != PR_PENDING_INTERRUPT_ERROR && rc != PR_WOULD_BLOCK_ERROR ) + return 0; + return 0; +} + +static char * +tlsm_session_errmsg( int rc, char *buf, size_t len ) +{ + int i; + + rc = PR_GetError(); + i = PR_GetErrorTextLength(); + if ( i > len ) { + char *msg = LDAP_MALLOC( i+1 ); + PR_GetErrorText( msg ); + memcpy( buf, msg, len ); + LDAP_FREE( msg ); + } else if ( i ) { + PR_GetErrorText( buf ); + } + + return i ? buf : NULL; +} + +static int +tlsm_session_my_dn( tls_session *session, struct berval *der_dn ) +{ + tlsm_session *s = (tlsm_session *)session; + CERTCertificate *cert; + + cert = SSL_LocalCertificate( s ); + if (!cert) return LDAP_INVALID_CREDENTIALS; + + der_dn->bv_val = cert->derSubject.data; + der_dn->bv_len = cert->derSubject.len; + CERT_DestroyCertificate( cert ); + return 0; +} + +static int +tlsm_session_peer_dn( tls_session *session, struct berval *der_dn ) +{ + tlsm_session *s = (tlsm_session *)session; + CERTCertificate *cert; + + cert = SSL_PeerCertificate( s ); + if (!cert) return LDAP_INVALID_CREDENTIALS; + + der_dn->bv_val = cert->derSubject.data; + der_dn->bv_len = cert->derSubject.len; + CERT_DestroyCertificate( cert ); + return 0; +} + +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +static int +tlsm_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) +{ +/* NSS already does a hostname check */ +#if 0 + int i, ret; + const gnutls_datum_t *peer_cert_list; + int list_size; + struct berval bv; + char altname[NI_MAXHOST]; + size_t altnamesize; + + gnutls_x509_crt_t cert; + gnutls_datum_t *x; + const char *name; + char *ptr; + char *domain = NULL; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + int n, len1 = 0, len2 = 0; + int ntype = IS_DNS; + time_t now = time(0); + + if( ldap_int_hostname && + ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) + { + name = ldap_int_hostname; + } else { + name = name_in; + } + + peer_cert_list = gnutls_certificate_get_peers( session->session, + &list_size ); + if ( !peer_cert_list ) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get peer certificate.\n", + 0, 0, 0 ); + /* If this was a fatal condition, things would have + * aborted long before now. + */ + return LDAP_SUCCESS; + } + ret = gnutls_x509_crt_init( &cert ); + if ( ret < 0 ) + return LDAP_LOCAL_ERROR; + ret = gnutls_x509_crt_import( cert, peer_cert_list, GNUTLS_X509_FMT_DER ); + if ( ret ) { + gnutls_x509_crt_deinit( cert ); + return LDAP_LOCAL_ERROR; + } + +#ifdef LDAP_PF_INET6 + if (name[0] == '[' && strchr(name, ']')) { + char *n2 = ldap_strdup(name+1); + *strchr(n2, ']') = 2; + if (inet_pton(AF_INET6, n2, &addr)) + ntype = IS_IP6; + LDAP_FREE(n2); + } else +#endif + if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { + if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; + } + + if (ntype == IS_DNS) { + len1 = strlen(name); + domain = strchr(name, '.'); + if (domain) { + len2 = len1 - (domain-name); + } + } + + for ( i=0, ret=0; ret >= 0; i++ ) { + altnamesize = sizeof(altname); + ret = gnutls_x509_crt_get_subject_alt_name( cert, i, + altname, &altnamesize, NULL ); + if ( ret < 0 ) break; + + /* ignore empty */ + if ( altnamesize == 0 ) continue; + + if ( ret == GNUTLS_SAN_DNSNAME ) { + if (ntype != IS_DNS) continue; + + /* Is this an exact match? */ + if ((len1 == altnamesize) && !strncasecmp(name, altname, len1)) { + break; + } + + /* Is this a wildcard match? */ + if (domain && (altname[0] == '*') && (altname[1] == '.') && + (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) + { + break; + } + } else if ( ret == GNUTLS_SAN_IPADDRESS ) { + if (ntype == IS_DNS) continue; + +#ifdef LDAP_PF_INET6 + if (ntype == IS_IP6 && altnamesize != sizeof(struct in6_addr)) { + continue; + } else +#endif + if (ntype == IS_IP4 && altnamesize != sizeof(struct in_addr)) { + continue; + } + if (!memcmp(altname, &addr, altnamesize)) { + break; + } + } + } + if ( ret >= 0 ) { + ret = LDAP_SUCCESS; + } else { + altnamesize = sizeof(altname); + ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID, + 0, 0, altname, &altnamesize ); + if ( ret < 0 ) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get common name from peer certificate.\n", + 0, 0, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: unable to get CN from peer certificate")); + + } else { + ret = LDAP_LOCAL_ERROR; + if ( len1 == altnamesize && strncasecmp(name, altname, altnamesize) == 0 ) { + ret = LDAP_SUCCESS; + + } else if (( altname[0] == '*' ) && ( altname[1] == '.' )) { + /* Is this a wildcard match? */ + if( domain && + (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) { + ret = LDAP_SUCCESS; + } + } + } + + if( ret == LDAP_LOCAL_ERROR ) { + altname[altnamesize] = '\0'; + Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " + "common name in certificate (%s).\n", + name, altname, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: hostname does not match CN in peer certificate")); + } + } + gnutls_x509_crt_deinit( cert ); + return ret; +#endif +} + +static int +tlsm_session_strength( tls_session *session ) +{ + tlsm_session *s = (tlsm_session *)session; + int rc, keySize; + + rc = SSL_SecurityStatus( s, NULL, NULL, NULL, &keySize, + NULL, NULL ); + return rc ? 0 : keySize; +} + +/* + * TLS support for LBER Sockbufs + */ + +struct tls_data { + tlsm_session *session; + Sockbuf_IO_Desc *sbiod; +}; + + +static PRStatus PR_CALLBACK +tlsm_PR_Close(PRFileDesc *fd) +{ + return PR_SUCCESS; +} + +static int PR_CALLBACK +tlsm_PR_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags, + PRIntervalTime timeout) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)fd->secret; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); +} + +static int PR_CALLBACK +tlsm_PR_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags, + PRIntervalTime timeout) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)fd->secret; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); +} + +static int PR_CALLBACK +tlsm_PR_Read(PRFileDesc *fd, void *buf, PRInt32 len) +{ + return tlsm_PR_Recv( fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); +} + +static int PR_CALLBACK +tlsm_PR_Write(PRFileDesc *fd, const void *buf, PRInt32 len) +{ + return tlsm_PR_Send( fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); +} + +static PRStatus PR_CALLBACK +tlsm_PR_GetPeerName(PRFileDesc *fd, PRNetAddr *addr) +{ + struct tls_data *p; + int rc; + ber_socklen_t len; + + p = (struct tls_data *)fd->secret; + + if ( p == NULL || p->sbiod == NULL ) { + return PR_FAILURE; + } + len = sizeof(PRNetAddr); + return getpeername( p->sbiod->sbiod_sb->sb_fd, (struct sockaddr *)addr, &len ); +} + +static PRStatus PR_CALLBACK +tlsm_PR_prs_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return PR_FAILURE; +} + +static PRFileDesc * PR_CALLBACK +tlsm_PR_pfd_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return NULL; +} + +static PRInt16 PR_CALLBACK +tlsm_PR_i16_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return SECFailure; +} + +static PRInt32 PR_CALLBACK +tlsm_PR_i32_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return SECFailure; +} + +static PRInt64 PR_CALLBACK +tlsm_PR_i64_unimp() +{ + PRInt64 res; + + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + LL_I2L(res, -1L); + return res; +} + +static const PRIOMethods tlsm_PR_methods = { + PR_DESC_LAYERED, + tlsm_PR_Close, /* close */ + tlsm_PR_Read, /* read */ + tlsm_PR_Write, /* write */ + tlsm_PR_i32_unimp, /* available */ + tlsm_PR_i64_unimp, /* available64 */ + tlsm_PR_prs_unimp, /* fsync */ + tlsm_PR_i32_unimp, /* seek */ + tlsm_PR_i64_unimp, /* seek64 */ + tlsm_PR_prs_unimp, /* fileInfo */ + tlsm_PR_prs_unimp, /* fileInfo64 */ + tlsm_PR_i32_unimp, /* writev */ + tlsm_PR_prs_unimp, /* connect */ + tlsm_PR_pfd_unimp, /* accept */ + tlsm_PR_prs_unimp, /* bind */ + tlsm_PR_prs_unimp, /* listen */ + (PRShutdownFN)tlsm_PR_Close, /* shutdown */ + tlsm_PR_Recv, /* recv */ + tlsm_PR_Send, /* send */ + tlsm_PR_i32_unimp, /* recvfrom */ + tlsm_PR_i32_unimp, /* sendto */ + (PRPollFN)tlsm_PR_i16_unimp, /* poll */ + tlsm_PR_i32_unimp, /* acceptread */ + tlsm_PR_i32_unimp, /* transmitfile */ + tlsm_PR_prs_unimp, /* getsockname */ + tlsm_PR_GetPeerName, /* getpeername */ + tlsm_PR_i32_unimp, /* getsockopt OBSOLETE */ + tlsm_PR_i32_unimp, /* setsockopt OBSOLETE */ + tlsm_PR_i32_unimp, /* getsocketoption */ + tlsm_PR_i32_unimp, /* setsocketoption */ + tlsm_PR_i32_unimp, /* Send a (partial) file with header/trailer*/ + (PRConnectcontinueFN)tlsm_PR_prs_unimp, /* connectcontinue */ + tlsm_PR_i32_unimp, /* reserved for future use */ + tlsm_PR_i32_unimp, /* reserved for future use */ + tlsm_PR_i32_unimp, /* reserved for future use */ + tlsm_PR_i32_unimp /* reserved for future use */ +}; + +static int +tlsm_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + tlsm_session *session = arg; + PRFileDesc *fd; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + fd = PR_GetIdentitiesLayer( session, tlsm_layer_id ); + if ( !fd ) { + LBER_FREE( p ); + return -1; + } + + fd->secret = (PRFilePrivate *)p; + p->session = session; + p->sbiod = sbiod; + sbiod->sbiod_pvt = p; + return 0; +} + +static int +tlsm_sb_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + PR_Close( p->session ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +tlsm_sb_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + PR_Shutdown( p->session, PR_SHUTDOWN_BOTH ); + return 0; +} + +static int +tlsm_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((tlsm_session **)arg) = p->session; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + PRPollDesc pd = { p->session, PR_POLL_READ, 0 }; + if( PR_Poll( &pd, 1, 1 ) > 0 ) { + return 1; + } + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +tlsm_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = PR_Recv( p->session, buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); + if ( ret < 0 ) { + err = PR_GetError(); + if ( err == PR_PENDING_INTERRUPT_ERROR || err == PR_WOULD_BLOCK_ERROR ) { + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + } + } else { + sbiod->sbiod_sb->sb_trans_needs_read = 0; + } + return ret; +} + +static ber_slen_t +tlsm_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = PR_Send( p->session, (char *)buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); + if ( ret < 0 ) { + err = PR_GetError(); + if ( err == PR_PENDING_INTERRUPT_ERROR || err == PR_WOULD_BLOCK_ERROR ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + ret = 0; + } + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +static Sockbuf_IO tlsm_sbio = +{ + tlsm_sb_setup, /* sbi_setup */ + tlsm_sb_remove, /* sbi_remove */ + tlsm_sb_ctrl, /* sbi_ctrl */ + tlsm_sb_read, /* sbi_read */ + tlsm_sb_write, /* sbi_write */ + tlsm_sb_close /* sbi_close */ +}; + +tls_impl ldap_int_moznss_impl = { + "MozNSS", + + tlsm_init, + tlsm_destroy, + + tlsm_ctx_new, + tlsm_ctx_ref, + tlsm_ctx_free, + tlsm_ctx_init, + + tlsm_session_new, + tlsm_session_connect, + tlsm_session_accept, + tlsm_session_upflags, + tlsm_session_errmsg, + tlsm_session_my_dn, + tlsm_session_peer_dn, + tlsm_session_chkhost, + tlsm_session_strength, + + &tlsm_sbio, + +#ifdef LDAP_R_COMPILE + tlsm_thr_init, +#else + NULL, +#endif + + 0 +}; + +#endif /* HAVE_MOZNSS */ diff --git a/libraries/libldap/tls_o.c b/libraries/libldap/tls_o.c new file mode 100644 index 0000000000000000000000000000000000000000..fc4f9bc3d6d44113aab80b831b2ae2a53ae28fdb --- /dev/null +++ b/libraries/libldap/tls_o.c @@ -0,0 +1,1256 @@ +/* tls_o.c - Handle tls/ssl using SSLeay or OpenSSL */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2009 The OpenLDAP Foundation. + * 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: Rewritten by Howard Chu + */ + +#include "portable.h" + +#ifdef HAVE_OPENSSL + +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include <ldap_pvt_thread.h> +#endif + +#ifdef HAVE_OPENSSL_SSL_H +#include <openssl/ssl.h> +#include <openssl/x509v3.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/safestack.h> +#elif defined( HAVE_SSL_H ) +#include <ssl.h> +#endif + +typedef SSL_CTX tlso_ctx; +typedef SSL tlso_session; + +static int tlso_opt_trace = 1; + +static void tlso_report_error( void ); + +static void tlso_info_cb( const SSL *ssl, int where, int ret ); +static int tlso_verify_cb( int ok, X509_STORE_CTX *ctx ); +static int tlso_verify_ok( int ok, X509_STORE_CTX *ctx ); +static RSA * tlso_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ); + +static DH * tlso_tmp_dh_cb( SSL *ssl, int is_export, int key_length ); + +typedef struct dhplist { + struct dhplist *next; + int keylength; + DH *param; +} dhplist; + +static dhplist *tlso_dhparams; + +static int tlso_seed_PRNG( const char *randfile ); + +#ifdef LDAP_R_COMPILE +/* + * provide mutexes for the SSLeay library. + */ +static ldap_pvt_thread_mutex_t tlso_mutexes[CRYPTO_NUM_LOCKS]; +static ldap_pvt_thread_mutex_t tlso_dh_mutex; + +static void tlso_locking_cb( int mode, int type, const char *file, int line ) +{ + if ( mode & CRYPTO_LOCK ) { + ldap_pvt_thread_mutex_lock( &tlso_mutexes[type] ); + } else { + ldap_pvt_thread_mutex_unlock( &tlso_mutexes[type] ); + } +} + +static unsigned long tlso_thread_self( void ) +{ + /* FIXME: CRYPTO_set_id_callback only works when ldap_pvt_thread_t + * is an integral type that fits in an unsigned long + */ + + /* force an error if the ldap_pvt_thread_t type is too large */ + enum { ok = sizeof( ldap_pvt_thread_t ) <= sizeof( unsigned long ) }; + typedef struct { int dummy: ok ? 1 : -1; } Check[ok ? 1 : -1]; + + return (unsigned long) ldap_pvt_thread_self(); +} + +static void tlso_thr_init( void ) +{ + int i; + + for( i=0; i< CRYPTO_NUM_LOCKS ; i++ ) { + ldap_pvt_thread_mutex_init( &tlso_mutexes[i] ); + } + ldap_pvt_thread_mutex_init( &tlso_dh_mutex ); + CRYPTO_set_locking_callback( tlso_locking_cb ); + CRYPTO_set_id_callback( tlso_thread_self ); +} +#endif /* LDAP_R_COMPILE */ + +static STACK_OF(X509_NAME) * +tlso_ca_list( char * bundle, char * dir ) +{ + STACK_OF(X509_NAME) *ca_list = NULL; + + if ( bundle ) { + ca_list = SSL_load_client_CA_file( bundle ); + } +#if defined(HAVE_DIRENT_H) || defined(dirent) + if ( dir ) { + int freeit = 0; + + if ( !ca_list ) { + ca_list = sk_X509_NAME_new_null(); + freeit = 1; + } + if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && + freeit ) { + sk_X509_NAME_free( ca_list ); + ca_list = NULL; + } + } +#endif + return ca_list; +} + +/* + * Initialize TLS subsystem. Should be called only once. + */ +static int +tlso_init( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); +#ifdef HAVE_EBCDIC + { + char *file = LDAP_STRDUP( lo->ldo_tls_randfile ); + if ( file ) __atoe( file ); + (void) tlso_seed_PRNG( file ); + LDAP_FREE( file ); + } +#else + (void) tlso_seed_PRNG( lo->ldo_tls_randfile ); +#endif + + SSL_load_error_strings(); + SSLeay_add_ssl_algorithms(); + + /* FIXME: mod_ssl does this */ + X509V3_add_standard_extensions(); + + return 0; +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +static void +tlso_destroy( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + + EVP_cleanup(); + ERR_remove_state(0); + ERR_free_strings(); + + if ( lo->ldo_tls_randfile ) { + LDAP_FREE( lo->ldo_tls_randfile ); + lo->ldo_tls_randfile = NULL; + } +} + +static tls_ctx * +tlso_ctx_new( struct ldapoptions *lo ) +{ + return (tls_ctx *) SSL_CTX_new( SSLv23_method() ); +} + +static void +tlso_ctx_ref( tls_ctx *ctx ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + CRYPTO_add( &c->references, 1, CRYPTO_LOCK_SSL_CTX ); +} + +static void +tlso_ctx_free ( tls_ctx *ctx ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + SSL_CTX_free( c ); +} + +/* + * initialize a new TLS context + */ +static int +tlso_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlso_ctx *ctx = (tlso_ctx *)lo->ldo_tls_ctx; + int i; + + if ( is_server ) { + SSL_CTX_set_session_id_context( ctx, + (const unsigned char *) "OpenLDAP", sizeof("OpenLDAP")-1 ); + } + + if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL3 ) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 ); + else if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 ); + + if ( lo->ldo_tls_ciphersuite && + !SSL_CTX_set_cipher_list( ctx, lt->lt_ciphersuite ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + tlso_report_error(); + return -1; + } + + if (lo->ldo_tls_cacertfile != NULL || lo->ldo_tls_cacertdir != NULL) { + if ( !SSL_CTX_load_verify_locations( ctx, + lt->lt_cacertfile, lt->lt_cacertdir ) || + !SSL_CTX_set_default_verify_paths( ctx ) ) + { + Debug( LDAP_DEBUG_ANY, "TLS: " + "could not load verify locations (file:`%s',dir:`%s').\n", + lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", + lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", + 0 ); + tlso_report_error(); + return -1; + } + + if ( is_server ) { + STACK_OF(X509_NAME) *calist; + /* List of CA names to send to a client */ + calist = tlso_ca_list( lt->lt_cacertfile, lt->lt_cacertdir ); + if ( !calist ) { + Debug( LDAP_DEBUG_ANY, "TLS: " + "could not load client CA list (file:`%s',dir:`%s').\n", + lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", + lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", + 0 ); + tlso_report_error(); + return -1; + } + + SSL_CTX_set_client_CA_list( ctx, calist ); + } + } + + if ( lo->ldo_tls_certfile && + !SSL_CTX_use_certificate_file( ctx, + lt->lt_certfile, SSL_FILETYPE_PEM ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use certificate `%s'.\n", + lo->ldo_tls_certfile,0,0); + tlso_report_error(); + return -1; + } + + /* Key validity is checked automatically if cert has already been set */ + if ( lo->ldo_tls_keyfile && + !SSL_CTX_use_PrivateKey_file( ctx, + lt->lt_keyfile, SSL_FILETYPE_PEM ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use key file `%s'.\n", + lo->ldo_tls_keyfile,0,0); + tlso_report_error(); + return -1; + } + + if ( lo->ldo_tls_dhfile ) { + DH *dh = NULL; + BIO *bio; + dhplist *p; + + if (( bio=BIO_new_file( lt->lt_dhfile,"r" )) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use DH parameters file `%s'.\n", + lo->ldo_tls_dhfile,0,0); + tlso_report_error(); + return -1; + } + while (( dh=PEM_read_bio_DHparams( bio, NULL, NULL, NULL ))) { + p = LDAP_MALLOC( sizeof(dhplist) ); + if ( p != NULL ) { + p->keylength = DH_size( dh ) * 8; + p->param = dh; + p->next = tlso_dhparams; + tlso_dhparams = p; + } + } + BIO_free( bio ); + } + + if ( tlso_opt_trace ) { + SSL_CTX_set_info_callback( ctx, tlso_info_cb ); + } + + i = SSL_VERIFY_NONE; + if ( lo->ldo_tls_require_cert ) { + i = SSL_VERIFY_PEER; + if ( lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || + lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) { + i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } + } + + SSL_CTX_set_verify( ctx, i, + lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW ? + tlso_verify_ok : tlso_verify_cb ); + SSL_CTX_set_tmp_rsa_callback( ctx, tlso_tmp_rsa_cb ); + if ( lo->ldo_tls_dhfile ) { + SSL_CTX_set_tmp_dh_callback( ctx, tlso_tmp_dh_cb ); + } +#ifdef HAVE_OPENSSL_CRL + if ( lo->ldo_tls_crlcheck ) { + X509_STORE *x509_s = SSL_CTX_get_cert_store( ctx ); + if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_PEER ) { + X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK ); + } else if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_ALL ) { + X509_STORE_set_flags( x509_s, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); + } + } +#endif + return 0; +} + +static tls_session * +tlso_session_new( tls_ctx *ctx, int is_server ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + return (tls_session *)SSL_new( c ); +} + +static int +tlso_session_connect( LDAP *ld, tls_session *sess ) +{ + tlso_session *s = (tlso_session *)sess; + + /* Caller expects 0 = success, OpenSSL returns 1 = success */ + return SSL_connect( s ) - 1; +} + +static int +tlso_session_accept( tls_session *sess ) +{ + tlso_session *s = (tlso_session *)sess; + + /* Caller expects 0 = success, OpenSSL returns 1 = success */ + return SSL_accept( s ) - 1; +} + +static int +tlso_session_upflags( Sockbuf *sb, tls_session *sess, int rc ) +{ + tlso_session *s = (tlso_session *)sess; + + /* 1 was subtracted above, offset it back now */ + rc = SSL_get_error(s, rc+1); + if (rc == SSL_ERROR_WANT_READ) { + sb->sb_trans_needs_read = 1; + return 1; + + } else if (rc == SSL_ERROR_WANT_WRITE) { + sb->sb_trans_needs_write = 1; + return 1; + + } else if (rc == SSL_ERROR_WANT_CONNECT) { + return 1; + } + return 0; +} + +static char * +tlso_session_errmsg( int rc, char *buf, size_t len ) +{ + rc = ERR_peek_error(); + if ( rc ) { + ERR_error_string_n( rc, buf, len ); + return buf; + } + return NULL; +} + +static int +tlso_session_my_dn( tls_session *sess, struct berval *der_dn ) +{ + tlso_session *s = (tlso_session *)sess; + X509 *x; + X509_NAME *xn; + + x = SSL_get_certificate( s ); + + if (!x) return LDAP_INVALID_CREDENTIALS; + + xn = X509_get_subject_name(x); + der_dn->bv_len = i2d_X509_NAME( xn, NULL ); + der_dn->bv_val = xn->bytes->data; + X509_free(x); + return 0; +} + +static X509 * +tlso_get_cert( SSL *s ) +{ + /* If peer cert was bad, treat as if no cert was given */ + if (SSL_get_verify_result(s)) { + return NULL; + } + return SSL_get_peer_certificate(s); +} + +static int +tlso_session_peer_dn( tls_session *sess, struct berval *der_dn ) +{ + tlso_session *s = (tlso_session *)sess; + X509 *x = tlso_get_cert( s ); + X509_NAME *xn; + + if ( !x ) + return LDAP_INVALID_CREDENTIALS; + + xn = X509_get_subject_name(x); + der_dn->bv_len = i2d_X509_NAME( xn, NULL ); + der_dn->bv_val = xn->bytes->data; + X509_free(x); + return 0; +} + +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +static int +tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) +{ + tlso_session *s = (tlso_session *)sess; + int i, ret = LDAP_LOCAL_ERROR; + X509 *x; + const char *name; + char *ptr; + int ntype = IS_DNS; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + + if( ldap_int_hostname && + ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) + { + name = ldap_int_hostname; + } else { + name = name_in; + } + + x = tlso_get_cert(s); + if (!x) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get peer certificate.\n", + 0, 0, 0 ); + /* If this was a fatal condition, things would have + * aborted long before now. + */ + return LDAP_SUCCESS; + } + +#ifdef LDAP_PF_INET6 + if (name[0] == '[' && strchr(name, ']')) { + char *n2 = ldap_strdup(name+1); + *strchr(n2, ']') = 2; + if (inet_pton(AF_INET6, n2, &addr)) + ntype = IS_IP6; + LDAP_FREE(n2); + } else +#endif + if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { + if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; + } + + i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); + if (i >= 0) { + X509_EXTENSION *ex; + STACK_OF(GENERAL_NAME) *alt; + + ex = X509_get_ext(x, i); + alt = X509V3_EXT_d2i(ex); + if (alt) { + int n, len1 = 0, len2 = 0; + char *domain = NULL; + GENERAL_NAME *gn; + + if (ntype == IS_DNS) { + len1 = strlen(name); + domain = strchr(name, '.'); + if (domain) { + len2 = len1 - (domain-name); + } + } + n = sk_GENERAL_NAME_num(alt); + for (i=0; i<n; i++) { + char *sn; + int sl; + gn = sk_GENERAL_NAME_value(alt, i); + if (gn->type == GEN_DNS) { + if (ntype != IS_DNS) continue; + + sn = (char *) ASN1_STRING_data(gn->d.ia5); + sl = ASN1_STRING_length(gn->d.ia5); + + /* ignore empty */ + if (sl == 0) continue; + + /* Is this an exact match? */ + if ((len1 == sl) && !strncasecmp(name, sn, len1)) { + break; + } + + /* Is this a wildcard match? */ + if (domain && (sn[0] == '*') && (sn[1] == '.') && + (len2 == sl-1) && !strncasecmp(domain, &sn[1], len2)) + { + break; + } + + } else if (gn->type == GEN_IPADD) { + if (ntype == IS_DNS) continue; + + sn = (char *) ASN1_STRING_data(gn->d.ia5); + sl = ASN1_STRING_length(gn->d.ia5); + +#ifdef LDAP_PF_INET6 + if (ntype == IS_IP6 && sl != sizeof(struct in6_addr)) { + continue; + } else +#endif + if (ntype == IS_IP4 && sl != sizeof(struct in_addr)) { + continue; + } + if (!memcmp(sn, &addr, sl)) { + break; + } + } + } + + GENERAL_NAMES_free(alt); + if (i < n) { /* Found a match */ + ret = LDAP_SUCCESS; + } + } + } + + if (ret != LDAP_SUCCESS) { + X509_NAME *xn; + char buf[2048]; + buf[0] = '\0'; + + xn = X509_get_subject_name(x); + if( X509_NAME_get_text_by_NID( xn, NID_commonName, + buf, sizeof(buf)) == -1) + { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get common name from peer certificate.\n", + 0, 0, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: unable to get CN from peer certificate")); + + } else if (strcasecmp(name, buf) == 0 ) { + ret = LDAP_SUCCESS; + + } else if (( buf[0] == '*' ) && ( buf[1] == '.' )) { + char *domain = strchr(name, '.'); + if( domain ) { + size_t dlen = 0; + size_t sl; + + sl = strlen(name); + dlen = sl - (domain-name); + sl = strlen(buf); + + /* Is this a wildcard match? */ + if ((dlen == sl-1) && !strncasecmp(domain, &buf[1], dlen)) { + ret = LDAP_SUCCESS; + } + } + } + + if( ret == LDAP_LOCAL_ERROR ) { + Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " + "common name in certificate (%s).\n", + name, buf, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: hostname does not match CN in peer certificate")); + } + } + X509_free(x); + return ret; +} + +static int +tlso_session_strength( tls_session *sess ) +{ + tlso_session *s = (tlso_session *)sess; + SSL_CIPHER *c; + + c = SSL_get_current_cipher(s); + return SSL_CIPHER_get_bits(c, NULL); +} + +/* + * TLS support for LBER Sockbufs + */ + +struct tls_data { + tlso_session *session; + Sockbuf_IO_Desc *sbiod; +}; + +static int +tlso_bio_create( BIO *b ) { + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + return 1; +} + +static int +tlso_bio_destroy( BIO *b ) +{ + if ( b == NULL ) return 0; + + b->ptr = NULL; /* sb_tls_remove() will free it */ + b->init = 0; + b->flags = 0; + return 1; +} + +static int +tlso_bio_read( BIO *b, char *buf, int len ) +{ + struct tls_data *p; + int ret; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)b->ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + ret = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); + + BIO_clear_retry_flags( b ); + if ( ret < 0 ) { + int err = sock_errno(); + if ( err == EAGAIN || err == EWOULDBLOCK ) { + BIO_set_retry_read( b ); + } + } + + return ret; +} + +static int +tlso_bio_write( BIO *b, const char *buf, int len ) +{ + struct tls_data *p; + int ret; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)b->ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + ret = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); + + BIO_clear_retry_flags( b ); + if ( ret < 0 ) { + int err = sock_errno(); + if ( err == EAGAIN || err == EWOULDBLOCK ) { + BIO_set_retry_write( b ); + } + } + + return ret; +} + +static long +tlso_bio_ctrl( BIO *b, int cmd, long num, void *ptr ) +{ + if ( cmd == BIO_CTRL_FLUSH ) { + /* The OpenSSL library needs this */ + return 1; + } + return 0; +} + +static int +tlso_bio_gets( BIO *b, char *buf, int len ) +{ + return -1; +} + +static int +tlso_bio_puts( BIO *b, const char *str ) +{ + return tlso_bio_write( b, str, strlen( str ) ); +} + +static BIO_METHOD tlso_bio_method = +{ + ( 100 | 0x400 ), /* it's a source/sink BIO */ + "sockbuf glue", + tlso_bio_write, + tlso_bio_read, + tlso_bio_puts, + tlso_bio_gets, + tlso_bio_ctrl, + tlso_bio_create, + tlso_bio_destroy +}; + +static int +tlso_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + BIO *bio; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + p->session = arg; + p->sbiod = sbiod; + bio = BIO_new( &tlso_bio_method ); + bio->ptr = (void *)p; + SSL_set_bio( p->session, bio, bio ); + sbiod->sbiod_pvt = p; + return 0; +} + +static int +tlso_sb_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + SSL_free( p->session ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +tlso_sb_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + SSL_shutdown( p->session ); + return 0; +} + +static int +tlso_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((tlso_session **)arg) = p->session; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + if( SSL_pending( p->session ) > 0 ) { + return 1; + } + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +tlso_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = SSL_read( p->session, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->session, ret ); + if (err == SSL_ERROR_WANT_READ ) { + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + } + else + sbiod->sbiod_sb->sb_trans_needs_read = 0; + return ret; +} + +static ber_slen_t +tlso_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = SSL_write( p->session, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->session, ret ); + if (err == SSL_ERROR_WANT_WRITE ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +static Sockbuf_IO tlso_sbio = +{ + tlso_sb_setup, /* sbi_setup */ + tlso_sb_remove, /* sbi_remove */ + tlso_sb_ctrl, /* sbi_ctrl */ + tlso_sb_read, /* sbi_read */ + tlso_sb_write, /* sbi_write */ + tlso_sb_close /* sbi_close */ +}; + +/* Derived from openssl/apps/s_cb.c */ +static void +tlso_info_cb( const SSL *ssl, int where, int ret ) +{ + int w; + char *op; + char *state = (char *) SSL_state_string_long( (SSL *)ssl ); + + w = where & ~SSL_ST_MASK; + if ( w & SSL_ST_CONNECT ) { + op = "SSL_connect"; + } else if ( w & SSL_ST_ACCEPT ) { + op = "SSL_accept"; + } else { + op = "undefined"; + } + +#ifdef HAVE_EBCDIC + if ( state ) { + state = LDAP_STRDUP( state ); + __etoa( state ); + } +#endif + if ( where & SSL_CB_LOOP ) { + Debug( LDAP_DEBUG_TRACE, + "TLS trace: %s:%s\n", + op, state, 0 ); + + } else if ( where & SSL_CB_ALERT ) { + char *atype = (char *) SSL_alert_type_string_long( ret ); + char *adesc = (char *) SSL_alert_desc_string_long( ret ); + op = ( where & SSL_CB_READ ) ? "read" : "write"; +#ifdef HAVE_EBCDIC + if ( atype ) { + atype = LDAP_STRDUP( atype ); + __etoa( atype ); + } + if ( adesc ) { + adesc = LDAP_STRDUP( adesc ); + __etoa( adesc ); + } +#endif + Debug( LDAP_DEBUG_TRACE, + "TLS trace: SSL3 alert %s:%s:%s\n", + op, atype, adesc ); +#ifdef HAVE_EBCDIC + if ( atype ) LDAP_FREE( atype ); + if ( adesc ) LDAP_FREE( adesc ); +#endif + } else if ( where & SSL_CB_EXIT ) { + if ( ret == 0 ) { + Debug( LDAP_DEBUG_TRACE, + "TLS trace: %s:failed in %s\n", + op, state, 0 ); + } else if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "TLS trace: %s:error in %s\n", + op, state, 0 ); + } + } +#ifdef HAVE_EBCDIC + if ( state ) LDAP_FREE( state ); +#endif +} + +static int +tlso_verify_cb( int ok, X509_STORE_CTX *ctx ) +{ + X509 *cert; + int errnum; + int errdepth; + X509_NAME *subject; + X509_NAME *issuer; + char *sname; + char *iname; + char *certerr = NULL; + + cert = X509_STORE_CTX_get_current_cert( ctx ); + errnum = X509_STORE_CTX_get_error( ctx ); + errdepth = X509_STORE_CTX_get_error_depth( ctx ); + + /* + * X509_get_*_name return pointers to the internal copies of + * those things requested. So do not free them. + */ + subject = X509_get_subject_name( cert ); + issuer = X509_get_issuer_name( cert ); + /* X509_NAME_oneline, if passed a NULL buf, allocate memomry */ + sname = X509_NAME_oneline( subject, NULL, 0 ); + iname = X509_NAME_oneline( issuer, NULL, 0 ); + if ( !ok ) certerr = (char *)X509_verify_cert_error_string( errnum ); +#ifdef HAVE_EBCDIC + if ( sname ) __etoa( sname ); + if ( iname ) __etoa( iname ); + if ( certerr ) { + certerr = LDAP_STRDUP( certerr ); + __etoa( certerr ); + } +#endif + Debug( LDAP_DEBUG_TRACE, + "TLS certificate verification: depth: %d, err: %d, subject: %s,", + errdepth, errnum, + sname ? sname : "-unknown-" ); + Debug( LDAP_DEBUG_TRACE, " issuer: %s\n", iname ? iname : "-unknown-", 0, 0 ); + if ( !ok ) { + Debug( LDAP_DEBUG_ANY, + "TLS certificate verification: Error, %s\n", + certerr, 0, 0 ); + } + if ( sname ) + CRYPTO_free ( sname ); + if ( iname ) + CRYPTO_free ( iname ); +#ifdef HAVE_EBCDIC + if ( certerr ) LDAP_FREE( certerr ); +#endif + return ok; +} + +static int +tlso_verify_ok( int ok, X509_STORE_CTX *ctx ) +{ + (void) tlso_verify_cb( ok, ctx ); + return 1; +} + +/* Inspired by ERR_print_errors in OpenSSL */ +static void +tlso_report_error( void ) +{ + unsigned long l; + char buf[200]; + const char *file; + int line; + + while ( ( l = ERR_get_error_line( &file, &line ) ) != 0 ) { + ERR_error_string_n( l, buf, sizeof( buf ) ); +#ifdef HAVE_EBCDIC + if ( file ) { + file = LDAP_STRDUP( file ); + __etoa( (char *)file ); + } + __etoa( buf ); +#endif + Debug( LDAP_DEBUG_ANY, "TLS: %s %s:%d\n", + buf, file, line ); +#ifdef HAVE_EBCDIC + if ( file ) LDAP_FREE( (void *)file ); +#endif + } +} + +static RSA * +tlso_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ) +{ + RSA *tmp_rsa; + + /* FIXME: Pregenerate the key on startup */ + /* FIXME: Who frees the key? */ + tmp_rsa = RSA_generate_key( key_length, RSA_F4, NULL, NULL ); + + if ( !tmp_rsa ) { + Debug( LDAP_DEBUG_ANY, + "TLS: Failed to generate temporary %d-bit %s RSA key\n", + key_length, is_export ? "export" : "domestic", 0 ); + return NULL; + } + return tmp_rsa; +} + +static int +tlso_seed_PRNG( const char *randfile ) +{ +#ifndef URANDOM_DEVICE + /* no /dev/urandom (or equiv) */ + long total=0; + char buffer[MAXPATHLEN]; + + if (randfile == NULL) { + /* The seed file is $RANDFILE if defined, otherwise $HOME/.rnd. + * If $HOME is not set or buffer too small to hold the pathname, + * an error occurs. - From RAND_file_name() man page. + * The fact is that when $HOME is NULL, .rnd is used. + */ + randfile = RAND_file_name( buffer, sizeof( buffer ) ); + + } else if (RAND_egd(randfile) > 0) { + /* EGD socket */ + return 0; + } + + if (randfile == NULL) { + Debug( LDAP_DEBUG_ANY, + "TLS: Use configuration file or $RANDFILE to define seed PRNG\n", + 0, 0, 0); + return -1; + } + + total = RAND_load_file(randfile, -1); + + if (RAND_status() == 0) { + Debug( LDAP_DEBUG_ANY, + "TLS: PRNG not been seeded with enough data\n", + 0, 0, 0); + return -1; + } + + /* assume if there was enough bits to seed that it's okay + * to write derived bits to the file + */ + RAND_write_file(randfile); + +#endif + + return 0; +} + +struct dhinfo { + int keylength; + const char *pem; + size_t size; +}; + + +/* From the OpenSSL 0.9.7 distro */ +static const char tlso_dhpem512[] = +"-----BEGIN DH PARAMETERS-----\n\ +MEYCQQDaWDwW2YUiidDkr3VvTMqS3UvlM7gE+w/tlO+cikQD7VdGUNNpmdsp13Yn\n\ +a6LT1BLiGPTdHghM9tgAPnxHdOgzAgEC\n\ +-----END DH PARAMETERS-----\n"; + +static const char tlso_dhpem1024[] = +"-----BEGIN DH PARAMETERS-----\n\ +MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq\n\ +/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx\n\ +/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC\n\ +-----END DH PARAMETERS-----\n"; + +static const char tlso_dhpem2048[] = +"-----BEGIN DH PARAMETERS-----\n\ +MIIBCAKCAQEA7ZKJNYJFVcs7+6J2WmkEYb8h86tT0s0h2v94GRFS8Q7B4lW9aG9o\n\ +AFO5Imov5Jo0H2XMWTKKvbHbSe3fpxJmw/0hBHAY8H/W91hRGXKCeyKpNBgdL8sh\n\ +z22SrkO2qCnHJ6PLAMXy5fsKpFmFor2tRfCzrfnggTXu2YOzzK7q62bmqVdmufEo\n\ +pT8igNcLpvZxk5uBDvhakObMym9mX3rAEBoe8PwttggMYiiw7NuJKO4MqD1llGkW\n\ +aVM8U2ATsCun1IKHrRxynkE1/MJ86VHeYYX8GZt2YA8z+GuzylIOKcMH6JAWzMwA\n\ +Gbatw6QwizOhr9iMjZ0B26TE3X8LvW84wwIBAg==\n\ +-----END DH PARAMETERS-----\n"; + +static const char tlso_dhpem4096[] = +"-----BEGIN DH PARAMETERS-----\n\ +MIICCAKCAgEA/urRnb6vkPYc/KEGXWnbCIOaKitq7ySIq9dTH7s+Ri59zs77zty7\n\ +vfVlSe6VFTBWgYjD2XKUFmtqq6CqXMhVX5ElUDoYDpAyTH85xqNFLzFC7nKrff/H\n\ +TFKNttp22cZE9V0IPpzedPfnQkE7aUdmF9JnDyv21Z/818O93u1B4r0szdnmEvEF\n\ +bKuIxEHX+bp0ZR7RqE1AeifXGJX3d6tsd2PMAObxwwsv55RGkn50vHO4QxtTARr1\n\ +rRUV5j3B3oPMgC7Offxx+98Xn45B1/G0Prp11anDsR1PGwtaCYipqsvMwQUSJtyE\n\ +EOQWk+yFkeMe4vWv367eEi0Sd/wnC+TSXBE3pYvpYerJ8n1MceI5GQTdarJ77OW9\n\ +bGTHmxRsLSCM1jpLdPja5jjb4siAa6EHc4qN9c/iFKS3PQPJEnX7pXKBRs5f7AF3\n\ +W3RIGt+G9IVNZfXaS7Z/iCpgzgvKCs0VeqN38QsJGtC1aIkwOeyjPNy2G6jJ4yqH\n\ +ovXYt/0mc00vCWeSNS1wren0pR2EiLxX0ypjjgsU1mk/Z3b/+zVf7fZSIB+nDLjb\n\ +NPtUlJCVGnAeBK1J1nG3TQicqowOXoM6ISkdaXj5GPJdXHab2+S7cqhKGv5qC7rR\n\ +jT6sx7RUr0CNTxzLI7muV2/a4tGmj0PSdXQdsZ7tw7gbXlaWT1+MM2MCAQI=\n\ +-----END DH PARAMETERS-----\n"; + +static const struct dhinfo tlso_dhpem[] = { + { 512, tlso_dhpem512, sizeof(tlso_dhpem512) }, + { 1024, tlso_dhpem1024, sizeof(tlso_dhpem1024) }, + { 2048, tlso_dhpem2048, sizeof(tlso_dhpem2048) }, + { 4096, tlso_dhpem4096, sizeof(tlso_dhpem4096) }, + { 0, NULL, 0 } +}; + +static DH * +tlso_tmp_dh_cb( SSL *ssl, int is_export, int key_length ) +{ + struct dhplist *p = NULL; + BIO *b = NULL; + DH *dh = NULL; + int i; + + /* Do we have params of this length already? */ +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &tlso_dh_mutex ); +#endif + for ( p = tlso_dhparams; p; p=p->next ) { + if ( p->keylength == key_length ) { +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &tlso_dh_mutex ); +#endif + return p->param; + } + } + + /* No - check for hardcoded params */ + + for (i=0; tlso_dhpem[i].keylength; i++) { + if ( tlso_dhpem[i].keylength == key_length ) { + b = BIO_new_mem_buf( (char *)tlso_dhpem[i].pem, tlso_dhpem[i].size ); + break; + } + } + + if ( b ) { + dh = PEM_read_bio_DHparams( b, NULL, NULL, NULL ); + BIO_free( b ); + } + + /* Generating on the fly is expensive/slow... */ + if ( !dh ) { + dh = DH_generate_parameters( key_length, DH_GENERATOR_2, NULL, NULL ); + } + if ( dh ) { + p = LDAP_MALLOC( sizeof(struct dhplist) ); + if ( p != NULL ) { + p->keylength = key_length; + p->param = dh; + p->next = tlso_dhparams; + tlso_dhparams = p; + } + } + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &tlso_dh_mutex ); +#endif + return dh; +} + +tls_impl ldap_int_tls_impl = { + "OpenSSL", + + tlso_init, + tlso_destroy, + + tlso_ctx_new, + tlso_ctx_ref, + tlso_ctx_free, + tlso_ctx_init, + + tlso_session_new, + tlso_session_connect, + tlso_session_accept, + tlso_session_upflags, + tlso_session_errmsg, + tlso_session_my_dn, + tlso_session_peer_dn, + tlso_session_chkhost, + tlso_session_strength, + + &tlso_sbio, + +#ifdef LDAP_R_COMPILE + tlso_thr_init, +#else + NULL, +#endif + + 0 +}; + +#endif /* HAVE_OPENSSL */ diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index b6be658ad3dfd8d6ee32d80227936e44dd4fa7fd..545ce4a3b2c482fecc502fdaa2dc9f43fe3c6772 100644 --- a/servers/slapd/bconfig.c +++ b/servers/slapd/bconfig.c @@ -145,6 +145,7 @@ enum { CFG_DATABASE, CFG_TLS_RAND, CFG_TLS_CIPHER, + CFG_TLS_PROTOCOL_MIN, CFG_TLS_CERT_FILE, CFG_TLS_CERT_KEY, CFG_TLS_CA_PATH, @@ -685,6 +686,14 @@ static ConfigTable config_back_cf_table[] = { #endif "( OLcfgGlAt:77 NAME 'olcTLSDHParamFile' " "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSProtocolMin", NULL, 0, 0, 0, +#ifdef HAVE_TLS + CFG_TLS_PROTOCOL_MIN|ARG_STRING|ARG_MAGIC, &config_tls_config, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:87 NAME 'olcTLSProtocolMin' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, { "tool-threads", "count", 2, 2, 0, ARG_INT|ARG_MAGIC|CFG_TTHREADS, &config_generic, "( OLcfgGlAt:80 NAME 'olcToolThreads' " "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, @@ -3207,6 +3216,7 @@ config_tls_config(ConfigArgs *c) { switch(c->type) { case CFG_TLS_CRLCHECK: flag = LDAP_OPT_X_TLS_CRLCHECK; break; case CFG_TLS_VERIFY: flag = LDAP_OPT_X_TLS_REQUIRE_CERT; break; + case CFG_TLS_PROTOCOL_MIN: flag = LDAP_OPT_X_TLS_PROTOCOL_MIN; break; default: Debug(LDAP_DEBUG_ANY, "%s: " "unknown tls_option <0x%x>\n", diff --git a/servers/slapd/config.c b/servers/slapd/config.c index de0e5badc24773eaa7b86085912f640f5613f76d..16e335e4047fccdc5afc67eb39daff59e438428c 100644 --- a/servers/slapd/config.c +++ b/servers/slapd/config.c @@ -1200,6 +1200,7 @@ static slap_cf_aux_table bindkey[] = { { BER_BVC("tls_cacertdir="), offsetof(slap_bindconf, sb_tls_cacertdir), 's', 1, NULL }, { BER_BVC("tls_reqcert="), offsetof(slap_bindconf, sb_tls_reqcert), 's', 1, NULL }, { BER_BVC("tls_cipher_suite="), offsetof(slap_bindconf, sb_tls_cipher_suite), 's', 1, NULL }, + { BER_BVC("tls_protocol_min="), offsetof(slap_bindconf, sb_tls_protocol_min), 's', 1, NULL }, #ifdef HAVE_OPENSSL_CRL { BER_BVC("tls_crlcheck="), offsetof(slap_bindconf, sb_tls_crlcheck), 's', 1, NULL }, #endif @@ -1408,6 +1409,14 @@ slap_tls_get_config( LDAP *ld, int opt, char **val ) case LDAP_OPT_X_TLS_REQUIRE_CERT: keys = vfykeys; break; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: { + char buf[8]; + ldap_pvt_tls_get_option( ld, opt, &ival ); + snprintf( buf, sizeof( buf ), "%d.%d", + ( ival >> 8 ) & 0xff, ival & 0xff ); + *val = ch_strdup( buf ); + return 0; + } default: return -1; } @@ -1519,6 +1528,10 @@ void bindconf_free( slap_bindconf *bc ) { ch_free( bc->sb_tls_cipher_suite ); bc->sb_tls_cipher_suite = NULL; } + if ( bc->sb_tls_protocol_min ) { + ch_free( bc->sb_tls_protocol_min ); + bc->sb_tls_protocol_min = NULL; + } #ifdef HAVE_OPENSSL_CRL if ( bc->sb_tls_crlcheck ) { ch_free( bc->sb_tls_crlcheck ); @@ -1570,6 +1583,7 @@ static struct { { "tls_cacert", offsetof(slap_bindconf, sb_tls_cacert), LDAP_OPT_X_TLS_CACERTFILE }, { "tls_cacertdir", offsetof(slap_bindconf, sb_tls_cacertdir), LDAP_OPT_X_TLS_CACERTDIR }, { "tls_cipher_suite", offsetof(slap_bindconf, sb_tls_cipher_suite), LDAP_OPT_X_TLS_CIPHER_SUITE }, + { "tls_protocol_min", offsetof(slap_bindconf, sb_tls_protocol_min), LDAP_OPT_X_TLS_PROTOCOL_MIN }, {0, 0} }; @@ -1604,6 +1618,17 @@ int bindconf_tls_set( slap_bindconf *bc, LDAP *ld ) } else newctx = 1; } + if ( bc->sb_tls_protocol_min ) { + rc = ldap_int_tls_config( ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, + bc->sb_tls_protocol_min ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bindconf_tls_set: failed to set tls_protocol_min to %s\n", + bc->sb_tls_protocol_min, 0, 0 ); + res = -1; + } else + newctx = 1; + } #ifdef HAVE_OPENSSL_CRL if ( bc->sb_tls_crlcheck ) { rc = ldap_int_tls_config( ld, LDAP_OPT_X_TLS_CRLCHECK, diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index 6bd3af7c48e2e428943186df2aa6be7dd49a43cf..2439156a48f86fbc4143a3529ad3d427b0033145 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -1607,6 +1607,7 @@ typedef struct slap_bindconf { char *sb_tls_cacertdir; char *sb_tls_reqcert; char *sb_tls_cipher_suite; + char *sb_tls_protocol_min; #ifdef HAVE_OPENSSL_CRL char *sb_tls_crlcheck; #endif