X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=libraries%2Flibldap%2Ftls.c;h=a40601b784128c3f2f9db48e3a52ad20bf0048ec;hb=1d841e47c877842433dc11aaec9da36191fed960;hp=6739599db6f123f2ecaf5f9cfac0f5880baeebbf;hpb=acbb5cf689a4336af05c9f259d909d8141055bac;p=openldap diff --git a/libraries/libldap/tls.c b/libraries/libldap/tls.c index 6739599db6..a40601b784 100644 --- a/libraries/libldap/tls.c +++ b/libraries/libldap/tls.c @@ -1,8 +1,8 @@ -/* tls.c - Handle tls/ssl using SSLeay or OpenSSL. */ +/* tls.c - Handle tls/ssl using SSLeay, OpenSSL or GNUTLS. */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * - * Copyright 1998-2006 The OpenLDAP Foundation. + * Copyright 1998-2008 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -13,6 +13,10 @@ * 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" #include "ldap_config.h" @@ -37,6 +41,14 @@ #include #endif +#ifdef HAVE_GNUTLS +#include +#include +#include + +#define DH_BITS (1024) + +#else #ifdef HAVE_OPENSSL_SSL_H #include #include @@ -46,35 +58,325 @@ #elif defined( HAVE_SSL_H ) #include #endif - -static int tls_opt_trace = 1; -static char *tls_opt_certfile = NULL; -static char *tls_opt_keyfile = NULL; -static char *tls_opt_dhfile = NULL; -static char *tls_opt_cacertfile = NULL; -static char *tls_opt_cacertdir = NULL; -static int tls_opt_require_cert = LDAP_OPT_X_TLS_DEMAND; -#ifdef HAVE_OPENSSL_CRL -static int tls_opt_crlcheck = LDAP_OPT_X_TLS_CRL_NONE; #endif -static char *tls_opt_ciphersuite = NULL; -static char *tls_opt_randfile = NULL; #define HAS_TLS( sb ) ber_sockbuf_ctrl( sb, LBER_SB_OPT_HAS_IO, \ (void *)&sb_tls_sbio ) +#endif /* HAVE_TLS */ + +/* RFC2459 minimum required set of supported attribute types + * in a certificate DN + */ +typedef struct oid_name { + struct berval oid; + struct berval name; +} oid_name; + +#define CN_OID oids[0].oid.bv_val + +static oid_name oids[] = { + { BER_BVC("2.5.4.3"), BER_BVC("cn") }, + { BER_BVC("2.5.4.4"), BER_BVC("sn") }, + { BER_BVC("2.5.4.6"), BER_BVC("c") }, + { BER_BVC("2.5.4.7"), BER_BVC("l") }, + { BER_BVC("2.5.4.8"), BER_BVC("st") }, + { BER_BVC("2.5.4.10"), BER_BVC("o") }, + { BER_BVC("2.5.4.11"), BER_BVC("ou") }, + { BER_BVC("2.5.4.12"), BER_BVC("title") }, + { BER_BVC("2.5.4.41"), BER_BVC("name") }, + { BER_BVC("2.5.4.42"), BER_BVC("givenName") }, + { BER_BVC("2.5.4.43"), BER_BVC("initials") }, + { BER_BVC("2.5.4.44"), BER_BVC("generationQualifier") }, + { BER_BVC("2.5.4.46"), BER_BVC("dnQualifier") }, + { BER_BVC("1.2.840.113549.1.9.1"), BER_BVC("email") }, + { BER_BVC("0.9.2342.19200300.100.1.25"), BER_BVC("dc") }, + { BER_BVNULL, BER_BVNULL } +}; + +#ifdef HAVE_TLS +#ifdef HAVE_GNUTLS + +typedef struct tls_cipher_suite { + const char *name; + gnutls_kx_algorithm_t kx; + gnutls_cipher_algorithm_t cipher; + gnutls_mac_algorithm_t mac; + gnutls_protocol_t version; +} tls_cipher_suite; + +static tls_cipher_suite *ciphers; +static int n_ciphers; + +/* sorta replacing SSL_CTX */ +typedef struct tls_ctx { + struct ldapoptions *lo; + gnutls_certificate_credentials_t cred; + gnutls_dh_params_t dh_params; + unsigned long verify_depth; + int refcount; + int *kx_list; + int *cipher_list; + int *mac_list; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t ref_mutex; +#endif +} tls_ctx; + +/* sorta replacing SSL */ +typedef struct tls_session { + tls_ctx *ctx; + gnutls_session_t session; + struct berval peer_der_dn; +} tls_session; + +#ifdef LDAP_R_COMPILE + +static int +ldap_pvt_gcry_mutex_init( void **priv ) +{ + int err = 0; + ldap_pvt_thread_mutex_t *lock = LDAP_MALLOC( sizeof( ldap_pvt_thread_mutex_t )); + + if ( !lock ) + err = ENOMEM; + if ( !err ) { + err = ldap_pvt_thread_mutex_init( lock ); + if ( err ) + LDAP_FREE( lock ); + else + *priv = lock; + } + return err; +} +static int +ldap_pvt_gcry_mutex_destroy( void **lock ) +{ + int err = ldap_pvt_thread_mutex_destroy( *lock ); + LDAP_FREE( *lock ); + return err; +} +static int +ldap_pvt_gcry_mutex_lock( void **lock ) +{ + return ldap_pvt_thread_mutex_lock( *lock ); +} +static int +ldap_pvt_gcry_mutex_unlock( void **lock ) +{ + return ldap_pvt_thread_mutex_unlock( *lock ); +} + +static struct gcry_thread_cbs ldap_generic_thread_cbs = { + GCRY_THREAD_OPTION_USER, + NULL, + ldap_pvt_gcry_mutex_init, + ldap_pvt_gcry_mutex_destroy, + ldap_pvt_gcry_mutex_lock, + ldap_pvt_gcry_mutex_unlock, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static void +tls_init_threads( void ) +{ + gcry_control (GCRYCTL_SET_THREAD_CBS, &ldap_generic_thread_cbs); +} +#endif /* LDAP_R_COMPILE */ + +void +ldap_pvt_tls_ctx_free ( void *c ) +{ + int refcount; + tls_ctx *ctx = c; + + if ( !ctx ) return; + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &ctx->ref_mutex ); +#endif + refcount = --ctx->refcount; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &ctx->ref_mutex ); +#endif + if ( refcount ) + return; + LDAP_FREE( ctx->kx_list ); + gnutls_certificate_free_credentials( ctx->cred ); + ber_memfree ( ctx ); +} + +static void * +tls_ctx_new ( struct ldapoptions *lo ) +{ + tls_ctx *ctx; + + ctx = ber_memcalloc ( 1, sizeof (*ctx) ); + if ( ctx ) { + ctx->lo = lo; + if ( gnutls_certificate_allocate_credentials( &ctx->cred )) { + ber_memfree( ctx ); + return NULL; + } + ctx->refcount = 1; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &ctx->ref_mutex ); +#endif + } + return ctx; +} + +static void +tls_ctx_ref( tls_ctx *ctx ) +{ +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &ctx->ref_mutex ); +#endif + ctx->refcount++; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_unlock( &ctx->ref_mutex ); +#endif +} + +tls_session * +tls_session_new ( tls_ctx * ctx, int is_server ) +{ + tls_session *session; + + session = ber_memcalloc ( 1, sizeof (*session) ); + if ( !session ) + return NULL; + + session->ctx = ctx; + gnutls_init( &session->session, is_server ? GNUTLS_SERVER : GNUTLS_CLIENT ); + gnutls_set_default_priority( session->session ); + if ( ctx->kx_list ) { + gnutls_kx_set_priority( session->session, ctx->kx_list ); + gnutls_cipher_set_priority( session->session, ctx->cipher_list ); + gnutls_mac_set_priority( session->session, ctx->mac_list ); + } + if ( ctx->cred ) + gnutls_credentials_set( session->session, GNUTLS_CRD_CERTIFICATE, ctx->cred ); + + if ( is_server ) { + int flag = 0; + if ( ctx->lo->ldo_tls_require_cert ) { + flag = GNUTLS_CERT_REQUEST; + if ( ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || + ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) + flag = GNUTLS_CERT_REQUIRE; + gnutls_certificate_server_set_request( session->session, flag ); + } + } + return session; +} + +void +tls_session_free ( tls_session * session ) +{ + ber_memfree ( session ); +} + +#define tls_session_connect( ssl ) gnutls_handshake( ssl->session ) +#define tls_session_accept( ssl ) gnutls_handshake( ssl->session ) + +/* suites is a string of colon-separated cipher suite names. */ +static int +tls_parse_ciphers( tls_ctx *ctx, char *suites ) +{ + char *ptr, *end; + int i, j, len, num; + int *list, nkx = 0, ncipher = 0, nmac = 0; + int *kx, *cipher, *mac; + + num = 0; + ptr = suites; + do { + end = strchr(ptr, ':'); + if ( end ) + len = end - ptr; + else + len = strlen(ptr); + for (i=0; ikx_list = kx; + ctx->cipher_list = cipher; + ctx->mac_list = mac; + return 0; +} + +#else /* OpenSSL */ + +typedef SSL_CTX tls_ctx; +typedef SSL tls_session; + +static int tls_opt_trace = 1; +static char *tls_opt_randfile = NULL; + static void tls_report_error( void ); static void tls_info_cb( const SSL *ssl, int where, int ret ); static int tls_verify_cb( int ok, X509_STORE_CTX *ctx ); static int tls_verify_ok( int ok, X509_STORE_CTX *ctx ); static RSA * tls_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ); -static STACK_OF(X509_NAME) * get_ca_list( char * bundle, char * dir ); static DH * tls_tmp_dh_cb( SSL *ssl, int is_export, int key_length ); -static SSL_CTX *tls_def_ctx = NULL; - typedef struct dhplist { struct dhplist *next; int keylength; @@ -100,11 +402,18 @@ static void tls_locking_cb( int mode, int type, const char *file, int line ) } } -/* - * an extra mutex for the default ctx. - */ +static unsigned long tls_thread_self( void ) +{ + /* FIXME: CRYPTO_set_id_callback only works when ldap_pvt_thread_t + * is an integral type that fits in an unsigned long + */ -static ldap_pvt_thread_mutex_t tls_def_ctx_mutex; + /* force an error if the ldap_pvt_thread_t type is too large */ + enum { ok = sizeof( ldap_pvt_thread_t ) <= sizeof( unsigned long ) }; + typedef struct { int dummy: ok ? 1 : -1; } Check[ok ? 1 : -1]; + + return (unsigned long) ldap_pvt_thread_self(); +} static void tls_init_threads( void ) { @@ -114,53 +423,139 @@ static void tls_init_threads( void ) ldap_pvt_thread_mutex_init( &tls_mutexes[i] ); } CRYPTO_set_locking_callback( tls_locking_cb ); - /* FIXME: the thread id should be added somehow... */ - - ldap_pvt_thread_mutex_init( &tls_def_ctx_mutex ); + CRYPTO_set_id_callback( tls_thread_self ); } #endif /* LDAP_R_COMPILE */ +void +ldap_pvt_tls_ctx_free ( void *c ) +{ + + SSL_CTX_free( c ); +} + +static void * +tls_ctx_new( struct ldapoptions *lo ) +{ + return SSL_CTX_new( SSLv23_method() ); +} + +static void +tls_ctx_ref( void *c ) +{ + SSL_CTX *ctx = c; + CRYPTO_add( &ctx->references, 1, CRYPTO_LOCK_SSL_CTX ); +} + +static tls_session * +tls_session_new( tls_ctx *ctx, int is_server ) +{ + return SSL_new( ctx ); +} + +#define tls_session_connect( ssl ) SSL_connect( ssl ) +#define tls_session_accept( ssl ) SSL_accept( ssl ) + +static STACK_OF(X509_NAME) * +get_ca_list( char * bundle, char * dir ) +{ + STACK_OF(X509_NAME) *ca_list = NULL; + + if ( bundle ) { + ca_list = SSL_load_client_CA_file( bundle ); + } +#if defined(HAVE_DIRENT_H) || defined(dirent) + if ( dir ) { + int freeit = 0; + + if ( !ca_list ) { + ca_list = sk_X509_NAME_new_null(); + freeit = 1; + } + if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && + freeit ) { + sk_X509_NAME_free( ca_list ); + ca_list = NULL; + } + } +#endif + return ca_list; +} + +#endif /* HAVE_GNUTLS */ + +#ifdef LDAP_R_COMPILE /* - * Tear down the TLS subsystem. Should only be called once. + * an extra mutex for the default ctx. */ +static ldap_pvt_thread_mutex_t tls_def_ctx_mutex; +#endif + void -ldap_pvt_tls_destroy( void ) +ldap_int_tls_destroy( struct ldapoptions *lo ) { - SSL_CTX_free(tls_def_ctx); - tls_def_ctx = NULL; - - EVP_cleanup(); - ERR_remove_state(0); - ERR_free_strings(); + if ( lo->ldo_tls_ctx ) { + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + } - if ( tls_opt_certfile ) { - LDAP_FREE( tls_opt_certfile ); - tls_opt_certfile = 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 ( tls_opt_keyfile ) { - LDAP_FREE( tls_opt_keyfile ); - tls_opt_keyfile = NULL; + if ( lo->ldo_tls_dhfile ) { + LDAP_FREE( lo->ldo_tls_dhfile ); + lo->ldo_tls_dhfile = NULL; } - if ( tls_opt_dhfile ) { - LDAP_FREE( tls_opt_dhfile ); - tls_opt_dhfile = NULL; + if ( lo->ldo_tls_cacertfile ) { + LDAP_FREE( lo->ldo_tls_cacertfile ); + lo->ldo_tls_cacertfile = NULL; } - if ( tls_opt_cacertfile ) { - LDAP_FREE( tls_opt_cacertfile ); - tls_opt_cacertfile = NULL; + if ( lo->ldo_tls_cacertdir ) { + LDAP_FREE( lo->ldo_tls_cacertdir ); + lo->ldo_tls_cacertdir = NULL; } - if ( tls_opt_cacertdir ) { - LDAP_FREE( tls_opt_cacertdir ); - tls_opt_cacertdir = NULL; + if ( lo->ldo_tls_ciphersuite ) { + LDAP_FREE( lo->ldo_tls_ciphersuite ); + lo->ldo_tls_ciphersuite = NULL; } - if ( tls_opt_ciphersuite ) { - LDAP_FREE( tls_opt_ciphersuite ); - tls_opt_ciphersuite = NULL; +#ifdef HAVE_GNUTLS + if ( lo->ldo_tls_crlfile ) { + LDAP_FREE( lo->ldo_tls_crlfile ); + lo->ldo_tls_crlfile = NULL; } +#endif +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +void +ldap_pvt_tls_destroy( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + + ldap_int_tls_destroy( lo ); + +#ifdef HAVE_GNUTLS + LDAP_FREE( ciphers ); + ciphers = NULL; + + gnutls_global_deinit(); +#else + EVP_cleanup(); + ERR_remove_state(0); + ERR_free_strings(); + if ( tls_opt_randfile ) { LDAP_FREE( tls_opt_randfile ); tls_opt_randfile = NULL; } +#endif } /* @@ -173,6 +568,43 @@ ldap_pvt_tls_init( void ) if ( tls_initialized++ ) return 0; +#ifdef LDAP_R_COMPILE + tls_init_threads(); + ldap_pvt_thread_mutex_init( &tls_def_ctx_mutex ); +#endif + +#ifdef HAVE_GNUTLS + gnutls_global_init (); + + /* GNUtls cipher suite handling: The library ought to parse suite + * names for us, but it doesn't. It will return a list of suite names + * that it supports, so we can do parsing ourselves. It ought to tell + * us how long the list is, but it doesn't do that either, so we just + * have to count it manually... + */ + { + int i = 0; + tls_cipher_suite *ptr, tmp; + char cs_id[2]; + + while ( gnutls_cipher_suite_info( i, cs_id, &tmp.kx, &tmp.cipher, + &tmp.mac, &tmp.version )) + i++; + n_ciphers = i; + + /* Store a copy */ + ciphers = LDAP_MALLOC(n_ciphers * sizeof(tls_cipher_suite)); + if ( !ciphers ) + return -1; + for ( i=0; ildo_tls_ciphersuite; + char *cacertfile = lo->ldo_tls_cacertfile; + char *cacertdir = lo->ldo_tls_cacertdir; + char *certfile = lo->ldo_tls_certfile; + char *keyfile = lo->ldo_tls_keyfile; +#ifdef HAVE_GNUTLS + char *crlfile = lo->ldo_tls_crlfile; +#else + char *dhfile = lo->ldo_tls_dhfile; #endif + if ( lo->ldo_tls_ctx ) + return 0; + + ldap_pvt_tls_init(); + if ( is_server && !certfile && !keyfile && !cacertfile && !cacertdir ) { /* minimum configuration not provided */ -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &tls_def_ctx_mutex ); -#endif return LDAP_NOT_SUPPORTED; } @@ -233,10 +664,6 @@ ldap_pvt_tls_init_def_ctx( int is_server ) cacertfile = LDAP_STRDUP( cacertfile ); __atoe( cacertfile ); } - if ( cacertdir ) { - cacertdir = LDAP_STRDUP( cacertdir ); - __atoe( cacertdir ); - } if ( certfile ) { certfile = LDAP_STRDUP( certfile ); __atoe( certfile ); @@ -245,215 +672,287 @@ ldap_pvt_tls_init_def_ctx( int is_server ) keyfile = LDAP_STRDUP( keyfile ); __atoe( keyfile ); } +#ifdef HAVE_GNUTLS + if ( crlfile ) { + crlfile = LDAP_STRDUP( crlfile ); + __atoe( crlfile ); + } +#else + if ( cacertdir ) { + cacertdir = LDAP_STRDUP( cacertdir ); + __atoe( cacertdir ); + } if ( dhfile ) { dhfile = LDAP_STRDUP( dhfile ); __atoe( dhfile ); } #endif - if ( tls_def_ctx == NULL ) { - int i; - tls_def_ctx = SSL_CTX_new( SSLv23_method() ); - if ( tls_def_ctx == NULL ) { - Debug( LDAP_DEBUG_ANY, - "TLS: could not allocate default ctx (%lu).\n", - ERR_peek_error(),0,0); - rc = -1; - goto error_exit; - } - - SSL_CTX_set_session_id_context( tls_def_ctx, +#endif + lo->ldo_tls_ctx = tls_ctx_new( lo ); + if ( lo->ldo_tls_ctx == NULL ) { +#ifdef HAVE_GNUTLS + Debug( LDAP_DEBUG_ANY, + "TLS: could not allocate default ctx.\n", + 0,0,0); +#else + Debug( LDAP_DEBUG_ANY, + "TLS: could not allocate default ctx (%lu).\n", + ERR_peek_error(),0,0); +#endif + rc = -1; + goto error_exit; + } + +#ifdef HAVE_GNUTLS + if ( lo->ldo_tls_ciphersuite && + tls_parse_ciphers( lo->ldo_tls_ctx, + ciphersuite )) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + rc = -1; + goto error_exit; + } + + if (lo->ldo_tls_cacertdir != NULL) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: cacertdir not implemented for gnutls\n", + NULL, NULL, NULL ); + } + + if (lo->ldo_tls_cacertfile != NULL) { + rc = gnutls_certificate_set_x509_trust_file( + ((tls_ctx*) lo->ldo_tls_ctx)->cred, + cacertfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) goto error_exit; + } + + if ( lo->ldo_tls_certfile && lo->ldo_tls_keyfile ) { + rc = gnutls_certificate_set_x509_key_file( + ((tls_ctx*) lo->ldo_tls_ctx)->cred, + certfile, + keyfile, + GNUTLS_X509_FMT_PEM ); + if ( rc ) goto error_exit; + } else if ( lo->ldo_tls_certfile || lo->ldo_tls_keyfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: only one of certfile and keyfile specified\n", + NULL, NULL, NULL ); + rc = 1; + goto error_exit; + } + + if ( lo->ldo_tls_dhfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: ignoring dhfile\n", + NULL, NULL, NULL ); + } + + if ( lo->ldo_tls_crlfile ) { + rc = gnutls_certificate_set_x509_crl_file( + ((tls_ctx*) lo->ldo_tls_ctx)->cred, + crlfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) goto error_exit; + } + if ( is_server ) { + gnutls_dh_params_init (&((tls_ctx*) + lo->ldo_tls_ctx)->dh_params); + gnutls_dh_params_generate2 (((tls_ctx*) + lo->ldo_tls_ctx)->dh_params, + DH_BITS); + } + +#else /* !HAVE_GNUTLS */ + + if ( is_server ) { + SSL_CTX_set_session_id_context( lo->ldo_tls_ctx, (const unsigned char *) "OpenLDAP", sizeof("OpenLDAP")-1 ); + } + + if ( lo->ldo_tls_ciphersuite && + !SSL_CTX_set_cipher_list( lo->ldo_tls_ctx, ciphersuite ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + tls_report_error(); + rc = -1; + goto error_exit; + } - if ( tls_opt_ciphersuite && - !SSL_CTX_set_cipher_list( tls_def_ctx, ciphersuite ) ) + if (lo->ldo_tls_cacertfile != NULL || lo->ldo_tls_cacertdir != NULL) { + if ( !SSL_CTX_load_verify_locations( lo->ldo_tls_ctx, + cacertfile, cacertdir ) || + !SSL_CTX_set_default_verify_paths( lo->ldo_tls_ctx ) ) { - Debug( LDAP_DEBUG_ANY, - "TLS: could not set cipher list %s.\n", - tls_opt_ciphersuite, 0, 0 ); + Debug( LDAP_DEBUG_ANY, "TLS: " + "could not load verify locations (file:`%s',dir:`%s').\n", + lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", + lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", + 0 ); tls_report_error(); rc = -1; goto error_exit; } - if (tls_opt_cacertfile != NULL || tls_opt_cacertdir != NULL) { - if ( !SSL_CTX_load_verify_locations( tls_def_ctx, - cacertfile, cacertdir ) || - !SSL_CTX_set_default_verify_paths( tls_def_ctx ) ) - { - Debug( LDAP_DEBUG_ANY, "TLS: " - "could not load verify locations (file:`%s',dir:`%s').\n", - tls_opt_cacertfile ? tls_opt_cacertfile : "", - tls_opt_cacertdir ? tls_opt_cacertdir : "", - 0 ); - tls_report_error(); - rc = -1; - goto error_exit; - } - + if ( is_server ) { + STACK_OF(X509_NAME) *calist; + /* List of CA names to send to a client */ calist = get_ca_list( cacertfile, cacertdir ); if ( !calist ) { Debug( LDAP_DEBUG_ANY, "TLS: " "could not load client CA list (file:`%s',dir:`%s').\n", - tls_opt_cacertfile ? tls_opt_cacertfile : "", - tls_opt_cacertdir ? tls_opt_cacertdir : "", + lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", + lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", 0 ); tls_report_error(); rc = -1; goto error_exit; } - SSL_CTX_set_client_CA_list( tls_def_ctx, calist ); + SSL_CTX_set_client_CA_list( lo->ldo_tls_ctx, calist ); } + } - if ( tls_opt_keyfile && - !SSL_CTX_use_PrivateKey_file( tls_def_ctx, - keyfile, SSL_FILETYPE_PEM ) ) - { - Debug( LDAP_DEBUG_ANY, - "TLS: could not use key file `%s'.\n", - tls_opt_keyfile,0,0); - tls_report_error(); - rc = -1; - goto error_exit; - } + if ( lo->ldo_tls_certfile && + !SSL_CTX_use_certificate_file( lo->ldo_tls_ctx, + certfile, SSL_FILETYPE_PEM ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use certificate `%s'.\n", + lo->ldo_tls_certfile,0,0); + tls_report_error(); + rc = -1; + goto error_exit; + } - if ( tls_opt_certfile && - !SSL_CTX_use_certificate_file( tls_def_ctx, - certfile, SSL_FILETYPE_PEM ) ) - { - Debug( LDAP_DEBUG_ANY, - "TLS: could not use certificate `%s'.\n", - tls_opt_certfile,0,0); - tls_report_error(); - rc = -1; - goto error_exit; - } + /* Key validity is checked automatically if cert has already been set */ + if ( lo->ldo_tls_keyfile && + !SSL_CTX_use_PrivateKey_file( lo->ldo_tls_ctx, + keyfile, SSL_FILETYPE_PEM ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use key file `%s'.\n", + lo->ldo_tls_keyfile,0,0); + tls_report_error(); + rc = -1; + goto error_exit; + } - if ( ( tls_opt_certfile || tls_opt_keyfile ) && - !SSL_CTX_check_private_key( tls_def_ctx ) ) - { + if ( lo->ldo_tls_dhfile ) { + DH *dh = NULL; + BIO *bio; + dhplist *p; + + if (( bio=BIO_new_file( dhfile,"r" )) == NULL ) { Debug( LDAP_DEBUG_ANY, - "TLS: private key mismatch.\n", - 0,0,0); + "TLS: could not use DH parameters file `%s'.\n", + lo->ldo_tls_dhfile,0,0); tls_report_error(); rc = -1; goto error_exit; } - - if ( tls_opt_dhfile ) { - DH *dh = NULL; - BIO *bio; - dhplist *p; - - if (( bio=BIO_new_file( dhfile,"r" )) == NULL ) { - Debug( LDAP_DEBUG_ANY, - "TLS: could not use DH parameters file `%s'.\n", - tls_opt_dhfile,0,0); - tls_report_error(); - rc = -1; - goto error_exit; + while (( dh=PEM_read_bio_DHparams( bio, NULL, NULL, NULL ))) { + p = LDAP_MALLOC( sizeof(dhplist) ); + if ( p != NULL ) { + p->keylength = DH_size( dh ) * 8; + p->param = dh; + p->next = dhparams; + dhparams = p; } - while (( dh=PEM_read_bio_DHparams( bio, NULL, NULL, NULL ))) { - p = LDAP_MALLOC( sizeof(dhplist) ); - if ( p != NULL ) { - p->keylength = DH_size( dh ) * 8; - p->param = dh; - p->next = dhparams; - dhparams = p; - } - } - BIO_free( bio ); } + BIO_free( bio ); + } - if ( tls_opt_trace ) { - SSL_CTX_set_info_callback( tls_def_ctx, tls_info_cb ); - } + if ( tls_opt_trace ) { + SSL_CTX_set_info_callback( (SSL_CTX *)lo->ldo_tls_ctx, tls_info_cb ); + } - i = SSL_VERIFY_NONE; - if ( tls_opt_require_cert ) { - i = SSL_VERIFY_PEER; - if ( tls_opt_require_cert == LDAP_OPT_X_TLS_DEMAND || - tls_opt_require_cert == LDAP_OPT_X_TLS_HARD ) { - i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - } + 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( tls_def_ctx, i, - tls_opt_require_cert == LDAP_OPT_X_TLS_ALLOW ? - tls_verify_ok : tls_verify_cb ); - SSL_CTX_set_tmp_rsa_callback( tls_def_ctx, tls_tmp_rsa_cb ); - SSL_CTX_set_tmp_dh_callback( tls_def_ctx, tls_tmp_dh_cb ); + SSL_CTX_set_verify( lo->ldo_tls_ctx, i, + lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW ? + tls_verify_ok : tls_verify_cb ); + SSL_CTX_set_tmp_rsa_callback( lo->ldo_tls_ctx, tls_tmp_rsa_cb ); + if ( lo->ldo_tls_dhfile ) { + SSL_CTX_set_tmp_dh_callback( lo->ldo_tls_ctx, tls_tmp_dh_cb ); + } #ifdef HAVE_OPENSSL_CRL - if ( tls_opt_crlcheck ) { - X509_STORE *x509_s = SSL_CTX_get_cert_store( tls_def_ctx ); - if ( tls_opt_crlcheck == LDAP_OPT_X_TLS_CRL_PEER ) { - X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK ); - } else if ( tls_opt_crlcheck == LDAP_OPT_X_TLS_CRL_ALL ) { - X509_STORE_set_flags( x509_s, - X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); - } + if ( lo->ldo_tls_crlcheck ) { + X509_STORE *x509_s = SSL_CTX_get_cert_store( lo->ldo_tls_ctx ); + if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_PEER ) { + X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK ); + } else if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_ALL ) { + X509_STORE_set_flags( x509_s, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); } -#endif } +#endif + +#endif /* HAVE_GNUTLS */ + error_exit: - if ( rc == -1 && tls_def_ctx != NULL ) { - SSL_CTX_free( tls_def_ctx ); - tls_def_ctx = NULL; + if ( rc == -1 && lo->ldo_tls_ctx != NULL ) { + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; } #ifdef HAVE_EBCDIC LDAP_FREE( ciphersuite ); LDAP_FREE( cacertfile ); - LDAP_FREE( cacertdir ); LDAP_FREE( certfile ); LDAP_FREE( keyfile ); +#ifdef HAVE_GNUTLS + LDAP_FREE( crlfile ); +#else + LDAP_FREE( cacertdir ); LDAP_FREE( dhfile ); #endif +#endif + return rc; +} + +/* + * initialize the default context + */ +int +ldap_pvt_tls_init_def_ctx( int is_server ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + int rc; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_lock( &tls_def_ctx_mutex ); +#endif + rc = ldap_int_tls_init_ctx( lo, is_server ); #ifdef LDAP_R_COMPILE ldap_pvt_thread_mutex_unlock( &tls_def_ctx_mutex ); #endif return rc; } -static STACK_OF(X509_NAME) * -get_ca_list( char * bundle, char * dir ) -{ - STACK_OF(X509_NAME) *ca_list = NULL; - - if ( bundle ) { - ca_list = SSL_load_client_CA_file( bundle ); - } -#if defined(HAVE_DIRENT_H) || defined(dirent) - if ( dir ) { - int freeit = 0; - - if ( !ca_list ) { - ca_list = sk_X509_NAME_new_null(); - freeit = 1; - } - if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && - freeit ) { - sk_X509_NAME_free( ca_list ); - ca_list = NULL; - } - } -#endif - return ca_list; -} - -static SSL * +static tls_session * alloc_handle( void *ctx_arg, int is_server ) { - SSL_CTX *ctx; - SSL *ssl; + tls_ctx *ctx; + tls_session *ssl; if ( ctx_arg ) { - ctx = (SSL_CTX *) 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 = tls_def_ctx; + ctx = lo->ldo_tls_ctx; } - ssl = SSL_new( ctx ); + ssl = tls_session_new( ctx, is_server ); if ( ssl == NULL ) { Debug( LDAP_DEBUG_ANY,"TLS: can't create ssl handle.\n",0,0,0); return NULL; @@ -462,24 +961,37 @@ alloc_handle( void *ctx_arg, int is_server ) } static int -update_flags( Sockbuf *sb, SSL * ssl, int rc ) +update_flags( Sockbuf *sb, tls_session * ssl, int rc ) { - int err = SSL_get_error(ssl, rc); - sb->sb_trans_needs_read = 0; sb->sb_trans_needs_write = 0; - if (err == SSL_ERROR_WANT_READ) { +#ifdef HAVE_GNUTLS + if ( rc != GNUTLS_E_INTERRUPTED && rc != GNUTLS_E_AGAIN ) + return 0; + + switch (gnutls_record_get_direction (ssl->session)) { + case 0: + sb->sb_trans_needs_read = 1; + return 1; + case 1: + sb->sb_trans_needs_write = 1; + return 1; + } +#else /* !HAVE_GNUTLS */ + rc = SSL_get_error(ssl, rc); + if (rc == SSL_ERROR_WANT_READ) { sb->sb_trans_needs_read = 1; return 1; - } else if (err == SSL_ERROR_WANT_WRITE) { + } else if (rc == SSL_ERROR_WANT_WRITE) { sb->sb_trans_needs_write = 1; return 1; - } else if (err == SSL_ERROR_WANT_CONNECT) { + } else if (rc == SSL_ERROR_WANT_CONNECT) { return 1; } +#endif /* HAVE_GNUTLS */ return 0; } @@ -488,17 +1000,49 @@ update_flags( Sockbuf *sb, SSL * ssl, int rc ) */ struct tls_data { - SSL *ssl; + tls_session *ssl; Sockbuf_IO_Desc *sbiod; }; -static BIO_METHOD sb_tls_bio_method; +#ifdef HAVE_GNUTLS + +static ssize_t +sb_gtls_recv( gnutls_transport_ptr_t ptr, void *buf, size_t len ) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); +} + +static ssize_t +sb_gtls_send( gnutls_transport_ptr_t ptr, const void *buf, size_t len ) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); +} static int sb_tls_setup( Sockbuf_IO_Desc *sbiod, void *arg ) { struct tls_data *p; - BIO *bio; + tls_session *session = arg; assert( sbiod != NULL ); @@ -507,11 +1051,11 @@ sb_tls_setup( Sockbuf_IO_Desc *sbiod, void *arg ) return -1; } - p->ssl = (SSL *)arg; + gnutls_transport_set_ptr( session->session, (gnutls_transport_ptr)p ); + gnutls_transport_set_pull_function( session->session, sb_gtls_recv ); + gnutls_transport_set_push_function( session->session, sb_gtls_send ); + p->ssl = arg; p->sbiod = sbiod; - bio = BIO_new( &sb_tls_bio_method ); - bio->ptr = (void *)p; - SSL_set_bio( p->ssl, bio, bio ); sbiod->sbiod_pvt = p; return 0; } @@ -525,7 +1069,8 @@ sb_tls_remove( Sockbuf_IO_Desc *sbiod ) assert( sbiod->sbiod_pvt != NULL ); p = (struct tls_data *)sbiod->sbiod_pvt; - SSL_free( p->ssl ); + gnutls_deinit ( p->ssl->session ); + LBER_FREE( p->ssl ); LBER_FREE( sbiod->sbiod_pvt ); sbiod->sbiod_pvt = NULL; return 0; @@ -540,7 +1085,7 @@ sb_tls_close( Sockbuf_IO_Desc *sbiod ) assert( sbiod->sbiod_pvt != NULL ); p = (struct tls_data *)sbiod->sbiod_pvt; - SSL_shutdown( p->ssl ); + gnutls_bye ( p->ssl->session, GNUTLS_SHUT_RDWR ); return 0; } @@ -555,11 +1100,11 @@ sb_tls_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) p = (struct tls_data *)sbiod->sbiod_pvt; if ( opt == LBER_SB_OPT_GET_SSL ) { - *((SSL **)arg) = p->ssl; + *((tls_session **)arg) = p->ssl; return 1; - + } else if ( opt == LBER_SB_OPT_DATA_READY ) { - if( SSL_pending( p->ssl ) > 0 ) { + if( gnutls_record_check_pending( p->ssl->session ) > 0 ) { return 1; } } @@ -579,17 +1124,24 @@ sb_tls_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) p = (struct tls_data *)sbiod->sbiod_pvt; - ret = SSL_read( p->ssl, (char *)buf, len ); -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); -#endif - err = SSL_get_error( p->ssl, ret ); - if (err == SSL_ERROR_WANT_READ ) { + ret = gnutls_record_recv ( p->ssl->session, buf, len ); + switch (ret) { + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_AGAIN: sbiod->sbiod_sb->sb_trans_needs_read = 1; - errno = EWOULDBLOCK; - } - else + sock_errset(EWOULDBLOCK); + ret = 0; + break; + case GNUTLS_E_REHANDSHAKE: + for ( ret = gnutls_handshake ( p->ssl->session ); + ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN; + ret = gnutls_handshake ( p->ssl->session ) ); + sbiod->sbiod_sb->sb_trans_needs_read = 1; + ret = 0; + break; + default: sbiod->sbiod_sb->sb_trans_needs_read = 0; + } return ret; } @@ -605,30 +1157,19 @@ sb_tls_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) p = (struct tls_data *)sbiod->sbiod_pvt; - ret = SSL_write( p->ssl, (char *)buf, len ); -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); -#endif - err = SSL_get_error( p->ssl, ret ); - if (err == SSL_ERROR_WANT_WRITE ) { - sbiod->sbiod_sb->sb_trans_needs_write = 1; - errno = EWOULDBLOCK; + ret = gnutls_record_send ( p->ssl->session, (char *)buf, len ); + if ( ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + ret = 0; } else { sbiod->sbiod_sb->sb_trans_needs_write = 0; } return ret; } -static Sockbuf_IO sb_tls_sbio = -{ - sb_tls_setup, /* sbi_setup */ - sb_tls_remove, /* sbi_remove */ - sb_tls_ctrl, /* sbi_ctrl */ - sb_tls_read, /* sbi_read */ - sb_tls_write, /* sbi_write */ - sb_tls_close /* sbi_close */ -}; +#else /* !HAVE_GNUTLS */ static int sb_tls_bio_create( BIO *b ) { @@ -668,7 +1209,7 @@ sb_tls_bio_read( BIO *b, char *buf, int len ) BIO_clear_retry_flags( b ); if ( ret < 0 ) { - int err = errno; + int err = sock_errno(); if ( err == EAGAIN || err == EWOULDBLOCK ) { BIO_set_retry_read( b ); } @@ -695,7 +1236,7 @@ sb_tls_bio_write( BIO *b, const char *buf, int len ) BIO_clear_retry_flags( b ); if ( ret < 0 ) { - int err = errno; + int err = sock_errno(); if ( err == EAGAIN || err == EWOULDBLOCK ) { BIO_set_retry_write( b ); } @@ -739,6 +1280,178 @@ static BIO_METHOD sb_tls_bio_method = sb_tls_bio_destroy }; +static int +sb_tls_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + BIO *bio; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + p->ssl = (SSL *)arg; + p->sbiod = sbiod; + bio = BIO_new( &sb_tls_bio_method ); + bio->ptr = (void *)p; + SSL_set_bio( p->ssl, bio, bio ); + sbiod->sbiod_pvt = p; + return 0; +} + +static int +sb_tls_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + SSL_free( p->ssl ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +sb_tls_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + SSL_shutdown( p->ssl ); + return 0; +} + +static int +sb_tls_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((SSL **)arg) = p->ssl; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + if( SSL_pending( p->ssl ) > 0 ) { + return 1; + } + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +sb_tls_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = SSL_read( p->ssl, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->ssl, ret ); + if (err == SSL_ERROR_WANT_READ ) { + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + } + else + sbiod->sbiod_sb->sb_trans_needs_read = 0; + return ret; +} + +static ber_slen_t +sb_tls_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = SSL_write( p->ssl, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->ssl, ret ); + if (err == SSL_ERROR_WANT_WRITE ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +#endif + +static Sockbuf_IO sb_tls_sbio = +{ + sb_tls_setup, /* sbi_setup */ + sb_tls_remove, /* sbi_remove */ + sb_tls_ctrl, /* sbi_ctrl */ + sb_tls_read, /* sbi_read */ + sb_tls_write, /* sbi_write */ + sb_tls_close /* sbi_close */ +}; + +#ifdef HAVE_GNUTLS +/* Certs are not automatically varified during the handshake */ +static int +tls_cert_verify( tls_session *ssl ) +{ + unsigned int status = 0; + int err; + time_t now = time(0); + + err = gnutls_certificate_verify_peers2( ssl->session, &status ); + if ( err < 0 ) { + Debug( LDAP_DEBUG_ANY,"TLS: gnutls_certificate_verify_peers2 failed %d\n", + err,0,0 ); + return -1; + } + if ( status ) { + Debug( LDAP_DEBUG_TRACE,"TLS: peer cert untrusted or revoked (0x%x)\n", + status, 0,0 ); + return -1; + } + if ( gnutls_certificate_expiration_time_peers( ssl->session ) < now ) { + Debug( LDAP_DEBUG_ANY, "TLS: peer certificate is expired\n", + 0, 0, 0 ); + return -1; + } + if ( gnutls_certificate_activation_time_peers( ssl->session ) > now ) { + Debug( LDAP_DEBUG_ANY, "TLS: peer certificate not yet active\n", + 0, 0, 0 ); + return -1; + } + return 0; +} +#endif /* HAVE_GNUTLS */ + /* * Call this to do a TLS connect on a sockbuf. ctx_arg can be * a SSL_CTX * or NULL, in which case the default ctx is used. @@ -757,17 +1470,16 @@ ldap_int_tls_connect( LDAP *ld, LDAPConn *conn ) { Sockbuf *sb = conn->lconn_sb; int err; - SSL *ssl; + tls_session *ssl; if ( HAS_TLS( sb ) ) { ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); } else { struct ldapoptions *lo; - void *ctx; + tls_ctx *ctx; - lo = &ld->ld_options; - ctx = lo->ldo_tls_ctx; + ctx = ld->ld_options.ldo_tls_ctx; ssl = alloc_handle( ctx, 0 ); @@ -780,41 +1492,58 @@ ldap_int_tls_connect( LDAP *ld, LDAPConn *conn ) ber_sockbuf_add_io( sb, &sb_tls_sbio, LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); + lo = LDAP_INT_GLOBAL_OPT(); if( ctx == NULL ) { - ctx = tls_def_ctx; - lo->ldo_tls_ctx = ctx; + ctx = lo->ldo_tls_ctx; + ld->ld_options.ldo_tls_ctx = ctx; + tls_ctx_ref( ctx ); } - if ( lo->ldo_tls_connect_cb ) - lo->ldo_tls_connect_cb( ld, ssl, ctx, lo->ldo_tls_connect_arg ); - lo = LDAP_INT_GLOBAL_OPT(); - if ( lo && lo->ldo_tls_connect_cb ) + 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_connect( ssl ); + err = tls_session_connect( ssl ); #ifdef HAVE_WINSOCK errno = WSAGetLastError(); #endif - if ( err <= 0 ) { +#ifdef HAVE_GNUTLS + if ( err < 0 ) +#else + if ( err <= 0 ) +#endif + { if ( update_flags( sb, ssl, err )) { return 1; } - if ((err = ERR_peek_error())) { - char buf[256]; - +#ifndef HAVE_GNUTLS + if ((err = ERR_peek_error())) +#endif + { if ( ld->ld_error ) { LDAP_FREE( ld->ld_error ); } - ld->ld_error = LDAP_STRDUP(ERR_error_string(err, buf)); +#ifdef HAVE_GNUTLS + ld->ld_error = LDAP_STRDUP(gnutls_strerror( err )); +#else + { + char buf[256]; + ld->ld_error = LDAP_STRDUP(ERR_error_string(err, buf)); + } +#endif #ifdef HAVE_EBCDIC if ( ld->ld_error ) __etoa(ld->ld_error); #endif } - Debug( LDAP_DEBUG_ANY,"TLS: can't connect.\n",0,0,0); + Debug( LDAP_DEBUG_ANY,"TLS: can't connect: %s.\n", + ld->ld_error ? ld->ld_error : "" ,0,0); ber_sockbuf_remove_io( sb, &sb_tls_sbio, LBER_SBIOD_LEVEL_TRANSPORT ); @@ -825,6 +1554,14 @@ ldap_int_tls_connect( LDAP *ld, LDAPConn *conn ) return -1; } +#ifdef HAVE_GNUTLS + if ( ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER ) { + err = tls_cert_verify( ssl ); + if ( err && ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_ALLOW ) + return err; + } +#endif + return 0; } @@ -836,7 +1573,7 @@ int ldap_pvt_tls_accept( Sockbuf *sb, void *ctx_arg ) { int err; - SSL *ssl; + tls_session *ssl; if ( HAS_TLS( sb ) ) { ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); @@ -853,17 +1590,27 @@ ldap_pvt_tls_accept( Sockbuf *sb, void *ctx_arg ) LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); } - err = SSL_accept( ssl ); + err = tls_session_accept( ssl ); #ifdef HAVE_WINSOCK errno = WSAGetLastError(); #endif - if ( err <= 0 ) { + +#ifdef HAVE_GNUTLS + if ( err < 0 ) +#else + if ( err <= 0 ) +#endif + { if ( update_flags( sb, ssl, err )) return 1; +#ifdef HAVE_GNUTLS + Debug( LDAP_DEBUG_ANY,"TLS: can't accept: %s.\n", + gnutls_strerror( err ),0,0 ); +#else Debug( LDAP_DEBUG_ANY,"TLS: can't accept.\n",0,0,0 ); - tls_report_error(); +#endif ber_sockbuf_remove_io( sb, &sb_tls_sbio, LBER_SBIOD_LEVEL_TRANSPORT ); #ifdef LDAP_DEBUG @@ -873,6 +1620,13 @@ ldap_pvt_tls_accept( Sockbuf *sb, void *ctx_arg ) return -1; } +#ifdef HAVE_GNUTLS + if ( ssl->ctx->lo->ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER ) { + err = tls_cert_verify( ssl ); + if ( err && ssl->ctx->lo->ldo_tls_require_cert != LDAP_OPT_X_TLS_ALLOW ) + return err; + } +#endif return 0; } @@ -900,6 +1654,60 @@ ldap_tls_inplace( LDAP *ld ) return ldap_pvt_tls_inplace( sb ); } +#ifdef HAVE_GNUTLS +static void +x509_cert_get_dn( struct berval *cert, struct berval *dn, int get_subject ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t i; + + ber_init2( ber, cert, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_skip_tag( ber, &len ); /* Context + Constructed (version) */ + if ( tag == 0xa0 ) /* Version is optional */ + tag = ber_get_int( ber, &i ); /* Int: Version */ + tag = ber_get_int( ber, &i ); /* Int: Serial */ + tag = ber_skip_tag( ber, &len ); /* Sequence: Signature */ + ber_skip_data( ber, len ); + if ( !get_subject ) { + tag = ber_peek_tag( ber, &len ); /* Sequence: Issuer DN */ + } else { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Sequence: Validity */ + ber_skip_data( ber, len ); + tag = ber_peek_tag( ber, &len ); /* Sequence: Subject DN */ + } + len = ber_ptrlen( ber ); + dn->bv_val = cert->bv_val + len; + dn->bv_len = cert->bv_len - len; +} + +static int +tls_get_cert_dn( tls_session *session, struct berval *dnbv ) +{ + if ( !session->peer_der_dn.bv_val ) { + const gnutls_datum_t *peer_cert_list; + int list_size; + struct berval bv; + + peer_cert_list = gnutls_certificate_get_peers( session->session, + &list_size ); + if ( !peer_cert_list ) return LDAP_INVALID_CREDENTIALS; + + bv.bv_len = peer_cert_list->size; + bv.bv_val = peer_cert_list->data; + + x509_cert_get_dn( &bv, &session->peer_der_dn, 1 ); + *dnbv = session->peer_der_dn; + } + return 0; +} +#else /* !HAVE_GNUTLS */ static X509 * tls_get_cert( SSL *s ) { @@ -914,52 +1722,207 @@ tls_get_cert( SSL *s ) return SSL_get_peer_certificate(s); } +static int +tls_get_cert_dn( tls_session *session, struct berval *dnbv ) +{ + X509_NAME *xn; + X509 *x = tls_get_cert( session ); + + if ( !x ) + return LDAP_INVALID_CREDENTIALS; + + xn = X509_get_subject_name(x); + dnbv->bv_len = i2d_X509_NAME( xn, NULL ); + dnbv->bv_val = xn->bytes->data; + return 0; +} +#endif /* HAVE_GNUTLS */ + int ldap_pvt_tls_get_peer_dn( void *s, struct berval *dn, LDAPDN_rewrite_dummy *func, unsigned flags ) { - X509 *x; - X509_NAME *xn; + tls_session *session = s; + struct berval bvdn; int rc; - x = tls_get_cert((SSL *)s); + rc = tls_get_cert_dn( session, &bvdn ); + if ( rc ) return rc; - if (!x) return LDAP_INVALID_CREDENTIALS; - - xn = X509_get_subject_name(x); - rc = ldap_X509dn2bv(xn, dn, (LDAPDN_rewrite_func *)func, flags); - X509_free(x); + rc = ldap_X509dn2bv( &bvdn, dn, + (LDAPDN_rewrite_func *)func, flags); return rc; } -char * -ldap_pvt_tls_get_peer_hostname( void *s ) +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +#ifdef HAVE_GNUTLS + +int +ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) { - X509 *x; - X509_NAME *xn; - char buf[2048], *p; - int ret; + tls_session *session = s; + int i, ret; + const gnutls_datum_t *peer_cert_list; + int list_size; + struct berval bv; + char altname[NI_MAXHOST]; + size_t altnamesize; + + gnutls_x509_crt_t cert; + gnutls_datum_t *x; + const char *name; + char *ptr; + char *domain = NULL; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + int n, len1 = 0, len2 = 0; + int ntype = IS_DNS; + time_t now = time(0); - x = tls_get_cert((SSL *)s); - if (!x) return NULL; + 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; + } - xn = X509_get_subject_name(x); + if (ntype == IS_DNS) { + len1 = strlen(name); + domain = strchr(name, '.'); + if (domain) { + len2 = len1 - (domain-name); + } + } - ret = X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf)); - if( ret == -1 ) { - X509_free(x); - return NULL; + 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")); - p = LDAP_STRDUP(buf); - X509_free(x); - return p; + } 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; } -/* what kind of hostname were we given? */ -#define IS_DNS 0 -#define IS_IP4 1 -#define IS_IP6 2 +#else /* !HAVE_GNUTLS */ int ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) @@ -1133,27 +2096,7 @@ ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) X509_free(x); return ret; } - -const char * -ldap_pvt_tls_get_peer_issuer( void *s ) -{ -#if 0 /* currently unused; see ldap_pvt_tls_get_peer_dn() if needed */ - X509 *x; - X509_NAME *xn; - char buf[2048], *p; - - x = SSL_get_peer_certificate((SSL *)s); - - if (!x) return NULL; - - xn = X509_get_issuer_name(x); - p = LDAP_STRDUP(X509_NAME_oneline(xn, buf, sizeof(buf))); - X509_free(x); - return p; -#else - return NULL; #endif -} int ldap_int_tls_config( LDAP *ld, int option, const char *arg ) @@ -1168,6 +2111,9 @@ ldap_int_tls_config( LDAP *ld, int option, const char *arg ) case LDAP_OPT_X_TLS_RANDOM_FILE: case LDAP_OPT_X_TLS_CIPHER_SUITE: case LDAP_OPT_X_TLS_DHFILE: +#ifdef HAVE_GNUTLS + case LDAP_OPT_X_TLS_CRLFILE: +#endif return ldap_pvt_tls_set_option( ld, option, (void *) arg ); case LDAP_OPT_X_TLS_REQUIRE_CERT: @@ -1243,43 +2189,56 @@ ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg ) *(int *)arg = lo->ldo_tls_mode; break; case LDAP_OPT_X_TLS_CTX: - if ( ld == NULL ) { - *(void **)arg = (void *) tls_def_ctx; - } else { - *(void **)arg = lo->ldo_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 = tls_opt_cacertfile ? - LDAP_STRDUP( tls_opt_cacertfile ) : NULL; + *(char **)arg = lo->ldo_tls_cacertfile ? + LDAP_STRDUP( lo->ldo_tls_cacertfile ) : NULL; break; case LDAP_OPT_X_TLS_CACERTDIR: - *(char **)arg = tls_opt_cacertdir ? - LDAP_STRDUP( tls_opt_cacertdir ) : NULL; + *(char **)arg = lo->ldo_tls_cacertdir ? + LDAP_STRDUP( lo->ldo_tls_cacertdir ) : NULL; break; case LDAP_OPT_X_TLS_CERTFILE: - *(char **)arg = tls_opt_certfile ? - LDAP_STRDUP( tls_opt_certfile ) : NULL; + *(char **)arg = lo->ldo_tls_certfile ? + LDAP_STRDUP( lo->ldo_tls_certfile ) : NULL; break; case LDAP_OPT_X_TLS_KEYFILE: - *(char **)arg = tls_opt_keyfile ? - LDAP_STRDUP( tls_opt_keyfile ) : NULL; + *(char **)arg = lo->ldo_tls_keyfile ? + LDAP_STRDUP( lo->ldo_tls_keyfile ) : NULL; break; case LDAP_OPT_X_TLS_DHFILE: - *(char **)arg = tls_opt_dhfile ? - LDAP_STRDUP( tls_opt_dhfile ) : NULL; + *(char **)arg = lo->ldo_tls_dhfile ? + LDAP_STRDUP( lo->ldo_tls_dhfile ) : NULL; break; +#ifdef HAVE_GNUTLS + case LDAP_OPT_X_TLS_CRLFILE: + *(char **)arg = lo->ldo_tls_crlfile ? + LDAP_STRDUP( lo->ldo_tls_crlfile ) : NULL; + break; +#endif case LDAP_OPT_X_TLS_REQUIRE_CERT: - *(int *)arg = tls_opt_require_cert; + *(int *)arg = lo->ldo_tls_require_cert; break; #ifdef HAVE_OPENSSL_CRL case LDAP_OPT_X_TLS_CRLCHECK: - *(int *)arg = tls_opt_crlcheck; + *(int *)arg = lo->ldo_tls_crlcheck; break; #endif + case LDAP_OPT_X_TLS_CIPHER_SUITE: + *(char **)arg = lo->ldo_tls_ciphersuite ? + LDAP_STRDUP( lo->ldo_tls_ciphersuite ) : NULL; + break; case LDAP_OPT_X_TLS_RANDOM_FILE: +#ifdef HAVE_OPENSSL *(char **)arg = tls_opt_randfile ? LDAP_STRDUP( tls_opt_randfile ) : NULL; +#else + *(char **)arg = NULL; +#endif break; case LDAP_OPT_X_TLS_SSL_CTX: { void *retval = 0; @@ -1329,6 +2288,8 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) 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: @@ -1344,12 +2305,10 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) return -1; case LDAP_OPT_X_TLS_CTX: - if ( ld == NULL ) { - tls_def_ctx = (SSL_CTX *) arg; - - } else { - lo->ldo_tls_ctx = arg; - } + 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; @@ -1357,63 +2316,76 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) case LDAP_OPT_X_TLS_CONNECT_ARG: lo->ldo_tls_connect_arg = arg; return 0; - } - - if ( ld != NULL ) { - return -1; - } - - switch( option ) { case LDAP_OPT_X_TLS_CACERTFILE: - if ( tls_opt_cacertfile ) LDAP_FREE( tls_opt_cacertfile ); - tls_opt_cacertfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - break; + 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 ( tls_opt_cacertdir ) LDAP_FREE( tls_opt_cacertdir ); - tls_opt_cacertdir = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - break; + 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 ( tls_opt_certfile ) LDAP_FREE( tls_opt_certfile ); - tls_opt_certfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - break; + 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 ( tls_opt_keyfile ) LDAP_FREE( tls_opt_keyfile ); - tls_opt_keyfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - break; + 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 ( tls_opt_dhfile ) LDAP_FREE( tls_opt_dhfile ); - tls_opt_dhfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - break; + if ( lo->ldo_tls_dhfile ) LDAP_FREE( lo->ldo_tls_dhfile ); + lo->ldo_tls_dhfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; +#ifdef HAVE_GNUTLS + case LDAP_OPT_X_TLS_CRLFILE: + if ( lo->ldo_tls_crlfile ) LDAP_FREE( lo->ldo_tls_crlfile ); + lo->ldo_tls_crlfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; +#endif case LDAP_OPT_X_TLS_REQUIRE_CERT: + if ( !arg ) return -1; switch( *(int *) arg ) { case LDAP_OPT_X_TLS_NEVER: case LDAP_OPT_X_TLS_DEMAND: case LDAP_OPT_X_TLS_ALLOW: case LDAP_OPT_X_TLS_TRY: case LDAP_OPT_X_TLS_HARD: - tls_opt_require_cert = * (int *) arg; + lo->ldo_tls_require_cert = * (int *) arg; return 0; } return -1; #ifdef HAVE_OPENSSL_CRL case LDAP_OPT_X_TLS_CRLCHECK: + if ( !arg ) return -1; switch( *(int *) arg ) { case LDAP_OPT_X_TLS_CRL_NONE: case LDAP_OPT_X_TLS_CRL_PEER: case LDAP_OPT_X_TLS_CRL_ALL: - tls_opt_crlcheck = * (int *) arg; + lo->ldo_tls_crlcheck = * (int *) arg; return 0; } return -1; #endif case LDAP_OPT_X_TLS_CIPHER_SUITE: - if ( tls_opt_ciphersuite ) LDAP_FREE( tls_opt_ciphersuite ); - tls_opt_ciphersuite = arg ? LDAP_STRDUP( (char *) arg ) : NULL; - break; + if ( lo->ldo_tls_ciphersuite ) LDAP_FREE( lo->ldo_tls_ciphersuite ); + lo->ldo_tls_ciphersuite = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_RANDOM_FILE: + if ( ld != NULL ) + return -1; +#ifdef HAVE_OPENSSL if (tls_opt_randfile ) LDAP_FREE (tls_opt_randfile ); tls_opt_randfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; +#endif break; + + case LDAP_OPT_X_TLS_NEWCTX: + if ( !arg ) return -1; + if ( lo->ldo_tls_ctx ) + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + return ldap_int_tls_init_ctx( lo, *(int *)arg ); default: return -1; } @@ -1454,7 +2426,7 @@ ldap_int_tls_start ( LDAP *ld, LDAPConn *conn, LDAPURLDesc *srv ) /* * compare host with name(s) in certificate */ - if (tls_opt_require_cert != LDAP_OPT_X_TLS_NEVER) { + 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; @@ -1464,6 +2436,7 @@ ldap_int_tls_start ( LDAP *ld, LDAPConn *conn, LDAPURLDesc *srv ) return LDAP_SUCCESS; } +#ifdef HAVE_OPENSSL /* Derived from openssl/apps/s_cb.c */ static void tls_info_cb( const SSL *ssl, int where, int ret ) @@ -1791,6 +2764,8 @@ tls_tmp_dh_cb( SSL *ssl, int is_export, int key_length ) } #endif +#endif /* HAVE_OPENSSL */ + void * ldap_pvt_tls_sb_ctx( Sockbuf *sb ) { @@ -1809,11 +2784,17 @@ ldap_pvt_tls_sb_ctx( Sockbuf *sb ) int ldap_pvt_tls_get_strength( void *s ) { -#ifdef HAVE_TLS +#ifdef HAVE_OPENSSL SSL_CIPHER *c; c = SSL_get_current_cipher((SSL *)s); return SSL_CIPHER_get_bits(c, NULL); +#elif defined(HAVE_GNUTLS) + tls_session *session = s; + gnutls_cipher_algorithm_t c; + + c = gnutls_cipher_get( session->session ); + return gnutls_cipher_get_key_size( c ); #else return 0; #endif @@ -1824,18 +2805,36 @@ int ldap_pvt_tls_get_my_dn( void *s, struct berval *dn, LDAPDN_rewrite_dummy *func, unsigned flags ) { #ifdef HAVE_TLS + struct berval der_dn; + int rc; +#ifdef HAVE_OPENSSL X509 *x; X509_NAME *xn; - int rc; x = SSL_get_certificate((SSL *)s); if (!x) return LDAP_INVALID_CREDENTIALS; xn = X509_get_subject_name(x); - rc = ldap_X509dn2bv(xn, dn, (LDAPDN_rewrite_func *)func, flags ); + der_dn.bv_len = i2d_X509_NAME( xn, NULL ); + der_dn.bv_val = xn->bytes->data; +#elif defined(HAVE_GNUTLS) + tls_session *session = s; + const gnutls_datum_t *x; + struct berval bv; + + x = gnutls_certificate_get_ours( session->session ); + + if (!x) return LDAP_INVALID_CREDENTIALS; + + bv.bv_val = x->data; + bv.bv_len = x->size; + + x509_cert_get_dn( &bv, &der_dn, 1 ); +#endif + rc = ldap_X509dn2bv(&der_dn, dn, (LDAPDN_rewrite_func *)func, flags ); return rc; -#else +#else /* !HAVE_TLS */ return LDAP_NOT_SUPPORTED; #endif } @@ -1901,3 +2900,234 @@ ldap_start_tls_s ( LDAP *ld, #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; +} +