#include "portable.h"
-#ifdef HAVE_TLS
-
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/time.h>
#include <ac/unistd.h>
+#include <ac/param.h>
#include "ldap-int.h"
+#ifdef HAVE_TLS
+
#ifdef LDAP_R_COMPILE
#include <ldap_pvt_thread.h>
#endif
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
+#include <openssl/rand.h>
#elif defined( HAVE_SSL_H )
#include <ssl.h>
#endif
static char *tls_opt_cacertdir = NULL;
static int tls_opt_require_cert = 0;
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 *)&ldap_pvt_sockbuf_io_tls )
static SSL_CTX *tls_def_ctx = NULL;
+static int tls_seed_PRNG( const char *randfile );
+
#ifdef LDAP_R_COMPILE
/*
* provide mutexes for the SSLeay library.
static void tls_locking_cb( int mode, int type, const char *file, int line )
{
if ( mode & CRYPTO_LOCK ) {
- ldap_pvt_thread_mutex_lock( tls_mutexes+type );
+ ldap_pvt_thread_mutex_lock( &tls_mutexes[type] );
} else {
- ldap_pvt_thread_mutex_unlock( tls_mutexes+type );
+ ldap_pvt_thread_mutex_unlock( &tls_mutexes[type] );
}
}
int i;
for( i=0; i< CRYPTO_NUM_LOCKS ; i++ ) {
- ldap_pvt_thread_mutex_init( tls_mutexes+i );
+ ldap_pvt_thread_mutex_init( &tls_mutexes[i] );
}
CRYPTO_set_locking_callback( tls_locking_cb );
/* FIXME: the thread id should be added somehow... */
#endif /* LDAP_R_COMPILE */
/*
- * Initialize tls system. Should be called only once.
+ * Initialize TLS subsystem. Should be called only once.
*/
int
ldap_pvt_tls_init( void )
{
static int tls_initialized = 0;
- if ( tls_initialized )
- return 0;
+ if ( tls_initialized ) return 0;
tls_initialized = 1;
+
+ (void) tls_seed_PRNG( tls_opt_randfile );
+
#ifdef LDAP_R_COMPILE
tls_init_threads();
#endif
+
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
+
/* FIXME: mod_ssl does this */
X509V3_add_standard_extensions();
return 0;
if ( tls_opt_trace ) {
SSL_CTX_set_info_callback( tls_def_ctx, tls_info_cb );
}
- SSL_CTX_set_verify( tls_def_ctx, (tls_opt_require_cert) ?
+ SSL_CTX_set_verify( tls_def_ctx,
+ tls_opt_require_cert ?
(SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT) :
- SSL_VERIFY_PEER, tls_verify_cb );
+ SSL_VERIFY_NONE,
+ 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 ); */
}
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 );
#endif
return -1;
}
+
return 0;
}
ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&p );
return p;
}
- return NULL;
+
+ return NULL;
}
void *
return ldap_pvt_tls_sb_handle( ld->ld_sb );
}
+int
+ldap_pvt_tls_get_strength( void *s )
+{
+ SSL_CIPHER *c;
+
+ c = SSL_get_current_cipher((SSL *)s);
+ return SSL_CIPHER_get_bits(c, NULL);
+}
+
+
const char *
-ldap_pvt_tls_get_peer( LDAP *ld )
+ldap_pvt_tls_get_peer( void *s )
{
- return NULL;
+ X509 *x;
+ X509_NAME *xn;
+ char buf[2048], *p;
+
+ x = SSL_get_peer_certificate((SSL *)s);
+
+ if (!x)
+ return NULL;
+
+ xn = X509_get_subject_name(x);
+ p = LDAP_STRDUP(X509_NAME_oneline(xn, buf, sizeof(buf)));
+ X509_free(x);
+ return p;
+}
+
+char *
+ldap_pvt_tls_get_peer_hostname( void *s )
+{
+ X509 *x;
+ X509_NAME *xn;
+ char buf[2048], *p;
+
+ x = SSL_get_peer_certificate((SSL *)s);
+
+ if (!x)
+ return NULL;
+
+ xn = X509_get_subject_name(x);
+
+ if ( X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf)) == -1 ) {
+ X509_free(x);
+ return NULL;
+ }
+
+ p = LDAP_STRDUP(buf);
+ X509_free(x);
+ return p;
}
const char *
-ldap_pvt_tls_get_peer_issuer( LDAP *ld )
+ldap_pvt_tls_get_peer_issuer( void *s )
{
+#if 0 /* currently unused; see ldap_pvt_tls_get_peer() 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_pvt_tls_config( struct ldapoptions *lo, int option, const char *arg )
+ldap_int_tls_config( struct ldapoptions *lo, int option, const char *arg )
{
int i;
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:
return ldap_pvt_tls_set_option( NULL, option, (void *) arg );
case LDAP_OPT_X_TLS_REQUIRE_CERT:
i = ( ( strcasecmp( arg, "on" ) == 0 ) ||
if (i >= 0)
return ldap_pvt_tls_set_option( lo, option, &i );
return -1;
- default:
- return -1;
}
+
+ return -1;
}
int
case LDAP_OPT_X_TLS_REQUIRE_CERT:
*(int *)arg = tls_opt_require_cert;
break;
+ case LDAP_OPT_X_TLS_RANDOM_FILE:
+ *(char **)arg = tls_opt_randfile;
+ break;
default:
return -1;
}
switch( option ) {
case LDAP_OPT_X_TLS_CACERTFILE:
- if ( tls_opt_cacertfile ) free( tls_opt_cacertfile );
+ if ( tls_opt_cacertfile ) LDAP_FREE( tls_opt_cacertfile );
tls_opt_cacertfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL;
break;
case LDAP_OPT_X_TLS_CACERTDIR:
- if ( tls_opt_cacertdir ) free( tls_opt_cacertdir );
+ if ( tls_opt_cacertdir ) LDAP_FREE( tls_opt_cacertdir );
tls_opt_cacertdir = arg ? LDAP_STRDUP( (char *) arg ) : NULL;
break;
case LDAP_OPT_X_TLS_CERTFILE:
- if ( tls_opt_certfile ) free( tls_opt_certfile );
+ if ( tls_opt_certfile ) LDAP_FREE( tls_opt_certfile );
tls_opt_certfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL;
break;
case LDAP_OPT_X_TLS_KEYFILE:
- if ( tls_opt_keyfile ) free( tls_opt_keyfile );
+ if ( tls_opt_keyfile ) LDAP_FREE( tls_opt_keyfile );
tls_opt_keyfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL;
break;
case LDAP_OPT_X_TLS_REQUIRE_CERT:
tls_opt_require_cert = * (int *) arg;
break;
case LDAP_OPT_X_TLS_CIPHER_SUITE:
- if ( tls_opt_ciphersuite ) free( tls_opt_ciphersuite );
+ if ( tls_opt_ciphersuite ) LDAP_FREE( tls_opt_ciphersuite );
tls_opt_ciphersuite = arg ? LDAP_STRDUP( (char *) arg ) : NULL;
break;
+ case LDAP_OPT_X_TLS_RANDOM_FILE:
+ if (tls_opt_randfile ) LDAP_FREE (tls_opt_randfile );
+ tls_opt_randfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL;
+ break;
default:
return -1;
}
int
ldap_pvt_tls_start ( LDAP *ld, Sockbuf *sb, void *ctx_arg )
{
+ char *peer_cert_cn, *peer_hostname;
+ void *ssl;
+
+ (void) ldap_pvt_tls_init();
+
/*
* Fortunately, the lib uses blocking io...
*/
return LDAP_CONNECT_ERROR;
}
- /* FIXME: hostname of server must be compared with name in
- * certificate....
+ ssl = (void *) ldap_pvt_tls_sb_handle( sb );
+ /*
+ * compare hostname of server with name in certificate
+ */
+ peer_cert_cn = ldap_pvt_tls_get_peer_hostname( ssl );
+ if ( !peer_cert_cn ) {
+ /* could not get hostname from peer certificate */
+ Debug( LDAP_DEBUG_ANY,
+ "TLS: unable to get common name from peer certificate.\n",
+ 0, 0, 0 );
+ return LDAP_LOCAL_ERROR;
+ }
+
+ peer_hostname = ldap_host_connected_to( sb );
+ if ( !peer_hostname ) {
+ /* could not lookup hostname */
+ Debug( LDAP_DEBUG_ANY,
+ "TLS: unable to reverse lookup peer hostname.\n",
+ 0, 0, 0 );
+ LDAP_FREE( peer_cert_cn );
+ return LDAP_LOCAL_ERROR;
+ }
+
+ if ( strcasecmp(peer_hostname, peer_cert_cn) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
+ "common name in certificate (%s).",
+ peer_hostname, peer_cert_cn, 0 );
+ LDAP_FREE( peer_cert_cn );
+ LDAP_FREE( peer_hostname );
+ return LDAP_CONNECT_ERROR;
+
+ } else {
+ LDAP_FREE( peer_cert_cn );
+ LDAP_FREE( peer_hostname );
+ }
+
+ /*
+ * set SASL properties to TLS ssf and authid
*/
+ {
+ const char *authid;
+ ber_len_t ssf;
+
+ /* we need to let SASL know */
+ ssf = ldap_pvt_tls_get_strength( ssl );
+ authid = ldap_pvt_tls_get_peer( ssl );
+
+ (void) ldap_int_sasl_external( ld, authid, ssf );
+ }
return LDAP_SUCCESS;
}
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 );
+ Debug( LDAP_DEBUG_ANY,
+ "TLS: Failed to generate temporary %d-bit %s RSA key\n",
+ key_length, is_export ? "export" : "domestic", 0 );
return NULL;
}
return tmp_rsa;
}
+static int
+tls_seed_PRNG( const char *randfile )
+{
+#ifndef URANDOM_DEVICE
+ /* no /dev/urandom (or equiv) */
+ 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;
+ }
+
+ 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;
+ }
+#endif
+
+ return 0;
+}
+
#if 0
static DH *
tls_tmp_dh_cb( SSL *ssl, int is_export, int key_length )
return NULL;
}
#endif
+#endif
+
+int
+ldap_start_tls_s ( LDAP *ld,
+ LDAPControl **serverctrls,
+ LDAPControl **clientctrls )
+{
+#ifdef HAVE_TLS
+ int rc;
+ char *rspoid = NULL;
+ struct berval *rspdata = NULL;
+ /* XXYYZ: this initiates operaton only on default connection! */
+
+ if ( ldap_pvt_tls_inplace( ld->ld_sb ) != 0 ) {
+ return LDAP_LOCAL_ERROR;
+ }
+
+ rc = ldap_extended_operation_s( ld, LDAP_EXOP_START_TLS,
+ NULL, serverctrls, clientctrls, &rspoid, &rspdata );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( rspoid != NULL ) {
+ LDAP_FREE(rspoid);
+ }
+
+ if ( rspdata != NULL ) {
+ ber_bvfree( rspdata );
+ }
+
+ rc = ldap_pvt_tls_start( ld, ld->ld_sb, ld->ld_options.ldo_tls_ctx );
+ return rc;
#else
-static int dummy;
+ return LDAP_NOT_SUPPORTED;
#endif
+}
+