From a225b02f17fe79f6680d5d31db37320981e24774 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Wed, 13 Aug 2008 16:18:51 +0000 Subject: [PATCH] Modular TLS support, proof of concept. tls2.c would replace tls.c, but I'm leaving tls.c intact for now. --- libraries/libldap/ldap-int.h | 5 +- libraries/libldap/ldap-tls.h | 78 +++ libraries/libldap/tls2.c | 1180 +++++++++++++++++++++++++++++++ libraries/libldap/tls_g.c | 975 ++++++++++++++++++++++++++ libraries/libldap/tls_m.c | 865 +++++++++++++++++++++++ libraries/libldap/tls_o.c | 1286 ++++++++++++++++++++++++++++++++++ 6 files changed, 4387 insertions(+), 2 deletions(-) create mode 100644 libraries/libldap/ldap-tls.h create mode 100644 libraries/libldap/tls2.c create mode 100644 libraries/libldap/tls_g.c create mode 100644 libraries/libldap/tls_m.c create mode 100644 libraries/libldap/tls_o.c diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h index be8e1c015c..f13de68fd6 100644 --- a/libraries/libldap/ldap-int.h +++ b/libraries/libldap/ldap-int.h @@ -154,9 +154,8 @@ struct ldaptls { char *lt_cacertfile; char *lt_cacertdir; char *lt_ciphersuite; -#ifdef HAVE_GNUTLS char *lt_crlfile; -#endif + char *lt_randfile; /* OpenSSL only */ }; #endif @@ -200,8 +199,10 @@ struct ldapoptions { #define ldo_tls_cacertdir ldo_tls_info.lt_cacertdir #define ldo_tls_ciphersuite ldo_tls_info.lt_ciphersuite #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 0000000000..b24fd3634c --- /dev/null +++ b/libraries/libldap/ldap-tls.h @@ -0,0 +1,78 @@ +/* ldap-tls.h - TLS defines & prototypes internal to the LDAP library */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ + +#ifndef _LDAP_TLS_H +#define _LDAP_TLS_H 1 + +struct tls_impl; + +typedef struct tls_ctx { + struct tls_impl *tc_impl; +} tls_ctx; + +typedef struct tls_session { + struct tls_impl *ts_impl; + void *ts_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; + +#endif /* _LDAP_TLS_H */ diff --git a/libraries/libldap/tls2.c b/libraries/libldap/tls2.c new file mode 100644 index 0000000000..86e69da1c9 --- /dev/null +++ b/libraries/libldap/tls2.c @@ -0,0 +1,1180 @@ +/* tls.c - Handle tls/ssl. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: restructured by Howard Chu. + */ + +#include "portable.h" +#include "ldap_config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ldap-int.h" + +#ifdef HAVE_TLS + +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include +#endif + +#ifdef HAVE_GNUTLS +extern tls_impl ldap_int_gnutls_impl; +#endif + +#ifdef HAVE_OPENSSL +extern tls_impl ldap_int_openssl_impl; +#endif + +#ifdef HAVE_MOZNSS +extern tls_impl ldap_int_moznss_impl; +#endif + +static tls_impl *tls_impls[] = { +#ifdef HAVE_OPENSSL + &ldap_int_openssl_impl, +#endif +#ifdef HAVE_GNUTLS + &ldap_int_gnutls_impl, +#endif +#ifdef HAVE_MOZNSS + &ldap_int_moznss_impl, +#endif + NULL +}; + +#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 ) +{ + tls_ctx *ctx = c; + + if ( !ctx ) return; + + ctx->tc_impl->ti_ctx_free( ctx ); +} + +static void +tls_ctx_ref( tls_ctx *ctx ) +{ + if ( !ctx ) return; + + ctx->tc_impl->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 ); + + for ( i=0; tls_impls[i]; i++ ) { + if ( tls_impls[i]->ti_inited ) { + tls_impls[i]->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_impls[lo->ldo_tls_impl] ); +} + +/* + * 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_impls[lo->ldo_tls_impl]; + 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 = ctx->tc_impl->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 ssl->ts_impl->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, ssl->ts_impl->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 = ssl->ts_impl->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 = ssl->ts_impl->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, ssl->ts_impl->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, ssl->ts_impl->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); + } + + err = ssl->ts_impl->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", + ssl->ts_impl->ti_session_errmsg( err, buf, sizeof(buf) ),0,0 ); + + ber_sockbuf_remove_io( sb, ssl->ts_impl->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 = session->ts_impl->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 session->ts_impl->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_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_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_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_impls[ld->ld_options.ldo_tls_impl] ); + + /* + * 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 session->ts_impl->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; + + session->ts_impl->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 0000000000..081047a3f7 --- /dev/null +++ b/libraries/libldap/tls_g.c @@ -0,0 +1,975 @@ +/* tls_g.c - Handle tls/ssl using GNUTLS. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include +#endif + +#include +#include +#include + +#define DH_BITS (1024) + +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; + +typedef struct tlsg_ctx { + tls_impl *tc_impl; + 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 +} tlsg_ctx; + +typedef struct tlsg_session { + tls_impl *ts_impl; + gnutls_session_t session; + tlsg_ctx *ctx; + struct berval peer_der_dn; +} tlsg_session; + +static tls_cipher_suite *tlsg_ciphers; +static int tlsg_n_ciphers; + +static int tlsg_parse_ciphers( tlsg_ctx *ctx, char *suites ); +static int tlsg_cert_verify( tlsg_session *s ); +extern tls_impl ldap_int_gnutls_impl; + +#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 ) +{ + 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++; + 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; ilo = lo; + if ( gnutls_certificate_allocate_credentials( &ctx->cred )) { + ber_memfree( ctx ); + return NULL; + } + ctx->tc_impl = &ldap_int_gnutls_impl; + ctx->refcount = 1; +#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; + LDAP_FREE( c->kx_list ); + 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; + session->ts_impl = &ldap_int_gnutls_impl; + gnutls_init( &session->session, is_server ? GNUTLS_SERVER : GNUTLS_CLIENT ); + 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 ); + } + 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 == 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 ) +{ + 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; ikx_list = kx; + ctx->cipher_list = cipher; + ctx->mac_list = mac; + return 0; +} + +/* + * 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_gnutls_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 0000000000..85c74b9e1a --- /dev/null +++ b/libraries/libldap/tls_m.c @@ -0,0 +1,865 @@ +/* tls_m.c - Handle tls/ssl using Mozilla NSS. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: written by Howard Chu. + */ + +#include "portable.h" + +#ifdef HAVE_MOZNSS + +#include "ldap_config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include +#endif + +#include +#include +#include + +typedef struct tlsm_ctx { + tls_impl *tc_impl; + PRFileDesc *tc_model; + int tc_refcnt; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t tc_refmutex; +#endif +} tlsm_ctx; + +typedef struct tlsm_session { + tls_impl *ts_impl; + PRFileDesc *ts_session; +} tlsm_session; + +static PRDescIdentity tlsm_layer_id; + +static const PRIOMethods tlsm_PR_methods; + +extern tls_impl ldap_int_moznss_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_impl = &ldap_int_moznss_impl; + 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; + + session = LDAP_MALLOC( sizeof (*session) ); + if ( !session ) + return NULL; + + fd = PR_CreateIOLayerStub(tlsm_layer_id, &tlsm_PR_methods); + if ( !fd ) { + LDAP_FREE( session ); + return NULL; + } + + session->ts_session = SSL_ImportFD( c->tc_model, fd ); + if ( !session->ts_session ) { + PR_DELETE( fd ); + LDAP_FREE( session ); + return NULL; + } + + session->ts_impl = &ldap_int_moznss_impl; + + SSL_ResetHandshake( session->ts_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->ts_session ); +} + +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->ts_session, ld->ld_options.ldo_defludp->lud_host ); + return SSL_ForceHandshake( s->ts_session ); +} + +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->ts_session ); + 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->ts_session ); + 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->ts_session, 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->ts_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->ts_session ); + LBER_FREE( 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->ts_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->ts_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->ts_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->ts_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 0000000000..62c9638f07 --- /dev/null +++ b/libraries/libldap/tls_o.c @@ -0,0 +1,1286 @@ +/* tls_o.c - Handle tls/ssl using SSLeay or OpenSSL */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: Rewritten by Howard Chu + */ + +#include "portable.h" + +#ifdef HAVE_OPENSSL + +#include "ldap_config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef LDAP_R_COMPILE +#include +#endif + +#ifdef HAVE_OPENSSL_SSL_H +#include +#include +#include +#include +#include +#elif defined( HAVE_SSL_H ) +#include +#endif + +typedef struct tlso_ctx { + tls_impl *tc_impl; + SSL_CTX *tc_ctx; + int tc_refcnt; +} tlso_ctx; + +typedef struct tlso_session { + tls_impl *ts_impl; + SSL *ts_session; +} tlso_session; + +extern tls_impl ldap_int_openssl_impl; + +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 ) +{ + tlso_ctx *ctx = LDAP_MALLOC( sizeof(tlso_ctx) ); + if ( ctx ) { + ctx->tc_ctx = SSL_CTX_new( SSLv23_method() ); + if ( ctx->tc_ctx ) { + ctx->tc_impl = &ldap_int_openssl_impl; + ctx->tc_refcnt = 1; + } else { + LDAP_FREE( ctx ); + ctx = NULL; + } + } + return (tls_ctx *)ctx; +} + +static void +tlso_ctx_ref( tls_ctx *ctx ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + c->tc_refcnt++; +} + +static void +tlso_ctx_free ( tls_ctx *ctx ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + c->tc_refcnt--; + if ( c->tc_refcnt < 1 ) { + SSL_CTX_free( c->tc_ctx ); + LDAP_FREE( c ); + } +} + +/* + * initialize a new TLS context + */ +static int +tlso_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlso_ctx *tc = (tlso_ctx *)lo->ldo_tls_ctx; + SSL_CTX *ctx = tc->tc_ctx; + int i; + + if ( is_server ) { + SSL_CTX_set_session_id_context( ctx, + (const unsigned char *) "OpenLDAP", sizeof("OpenLDAP")-1 ); + } + + 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; + tlso_session *s = LDAP_MALLOC( sizeof(tlso_session)); + if ( s ) { + s->ts_session = SSL_new( c->tc_ctx ); + if ( s->ts_session ) { + s->ts_impl = &ldap_int_openssl_impl; + } else { + LDAP_FREE( s ); + s = NULL; + } + } + return (tls_session *)s; +} + +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->ts_session ) - 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->ts_session ) - 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->ts_session, 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->ts_session ); + + 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; + 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->ts_session ); + 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; + 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->ts_session); + 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; itype == 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->ts_session); + 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->ts_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->ts_session ); + LDAP_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->ts_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->ts_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->ts_session, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->session->ts_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->ts_session, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->session->ts_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_openssl_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 */ -- 2.39.5