/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
- * Copyright 2008-2009 The OpenLDAP Foundation.
+ * Copyright 2008-2012 The OpenLDAP Foundation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS: GNUTLS support written by Howard Chu and
- * Matt Backes; sponsored by The Written Word (thewrittenword.com)
+ * Emily Backes; sponsored by The Written Word (thewrittenword.com)
* and Stanford University (stanford.edu).
*/
#include <ac/unistd.h>
#include <ac/param.h>
#include <ac/dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include "ldap-int.h"
#include "ldap-tls.h"
-#ifdef LDAP_R_COMPILE
-#include <ldap_pvt_thread.h>
-#endif
-
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gcrypt.h>
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
+ LDAP_MUTEX_LOCK( &c->ref_mutex );
c->refcount++;
-#ifdef LDAP_R_COMPILE
- ldap_pvt_thread_mutex_unlock( &c->ref_mutex );
-#endif
+ LDAP_MUTEX_UNLOCK( &c->ref_mutex );
}
static void
if ( !c ) return;
-#ifdef LDAP_R_COMPILE
- ldap_pvt_thread_mutex_lock( &c->ref_mutex );
-#endif
+ LDAP_MUTEX_LOCK( &c->ref_mutex );
refcount = --c->refcount;
-#ifdef LDAP_R_COMPILE
- ldap_pvt_thread_mutex_unlock( &c->ref_mutex );
-#endif
+ LDAP_MUTEX_UNLOCK( &c->ref_mutex );
if ( refcount )
return;
#ifdef HAVE_CIPHERSUITES
ber_memfree ( c );
}
+static int
+tlsg_getfile( const char *path, gnutls_datum_t *buf )
+{
+ int rc = -1, fd;
+ struct stat st;
+
+ fd = open( path, O_RDONLY );
+ if ( fd >= 0 && fstat( fd, &st ) == 0 ) {
+ buf->size = st.st_size;
+ buf->data = LDAP_MALLOC( st.st_size + 1 );
+ if ( buf->data ) {
+ rc = read( fd, buf->data, st.st_size );
+ close( fd );
+ if ( rc < st.st_size )
+ rc = -1;
+ else
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+/* This is the GnuTLS default */
+#define VERIFY_DEPTH 6
+
/*
* initialize a new TLS context
*/
}
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_privkey_t key;
+ gnutls_datum_t buf;
+ gnutls_x509_crt_t certs[VERIFY_DEPTH];
+ unsigned int max = VERIFY_DEPTH;
+
+ rc = gnutls_x509_privkey_init( &key );
+ if ( rc ) return -1;
+
+ /* OpenSSL builds the cert chain for us, but GnuTLS
+ * expects it to be present in the certfile. If it's
+ * not, we have to build it ourselves. So we have to
+ * do some special checks here...
+ */
+ rc = tlsg_getfile( lt->lt_keyfile, &buf );
+ if ( rc ) return -1;
+ rc = gnutls_x509_privkey_import( key, &buf,
GNUTLS_X509_FMT_PEM );
+ LDAP_FREE( buf.data );
+ if ( rc < 0 ) return rc;
+
+ rc = tlsg_getfile( lt->lt_certfile, &buf );
+ if ( rc ) return -1;
+ rc = gnutls_x509_crt_list_import( certs, &max, &buf,
+ GNUTLS_X509_FMT_PEM, 0 );
+ LDAP_FREE( buf.data );
+ if ( rc < 0 ) return rc;
+
+ /* If there's only one cert and it's not self-signed,
+ * then we have to build the cert chain.
+ */
+ if ( max == 1 && !gnutls_x509_crt_check_issuer( certs[0], certs[0] )) {
+ gnutls_x509_crt_t *cas;
+ unsigned int i, j, ncas;
+
+ gnutls_certificate_get_x509_cas( ctx->cred, &cas, &ncas );
+ for ( i = 1; i<VERIFY_DEPTH; i++ ) {
+ for ( j = 0; j<ncas; j++ ) {
+ if ( gnutls_x509_crt_check_issuer( certs[i-1], cas[j] )) {
+ certs[i] = cas[j];
+ max++;
+ /* If this CA is self-signed, we're done */
+ if ( gnutls_x509_crt_check_issuer( cas[j], cas[j] ))
+ j = ncas;
+ break;
+ }
+ }
+ /* only continue if we found a CA and it was not self-signed */
+ if ( j == ncas )
+ break;
+ }
+ }
+ rc = gnutls_certificate_set_x509_key( ctx->cred, certs, max, key );
if ( rc ) return -1;
} else if ( lo->ldo_tls_certfile || lo->ldo_tls_keyfile ) {
Debug( LDAP_DEBUG_ANY,
if ( rc < 0 ) return -1;
rc = 0;
}
+
+ /* FIXME: ITS#5992 - this should go be configurable,
+ * and V1 CA certs should be phased out ASAP.
+ */
+ gnutls_certificate_set_verify_flags( ctx->cred,
+ GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT );
+
if ( is_server ) {
gnutls_dh_params_init(&ctx->dh_params);
gnutls_dh_params_generate2(ctx->dh_params, DH_BITS);
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 )
+ const gnutls_datum_t *peer_cert_list;
+ unsigned int list_size;
+
+ peer_cert_list = gnutls_certificate_get_peers( s->session,
+ &list_size );
+ if ( !peer_cert_list && s->ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_TRY )
rc = 0;
+ else {
+ rc = tlsg_cert_verify( s );
+ if ( rc && s->ctx->lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW )
+ rc = 0;
+ }
}
return rc;
}
}
static char *
-tlsg_session_errmsg( int rc, char *buf, size_t len )
+tlsg_session_errmsg( tls_session *sess, int rc, char *buf, size_t len )
{
return (char *)gnutls_strerror( rc );
}
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_peek_tag( ber, &len ); /* Context + Constructed (version) */
+ if ( tag == 0xa0 ) { /* Version is optional */
+ tag = ber_skip_tag( ber, &len );
tag = ber_get_int( ber, &i ); /* Int: Version */
- tag = ber_get_int( ber, &i ); /* Int: Serial */
+ }
+ tag = ber_skip_tag( ber, &len ); /* Int: Serial (can be longer than ber_int_t) */
+ ber_skip_data( ber, len );
tag = ber_skip_tag( ber, &len ); /* Sequence: Signature */
ber_skip_data( ber, len );
if ( !get_subject ) {
if (!x) return LDAP_INVALID_CREDENTIALS;
- bv.bv_val = x->data;
+ bv.bv_val = (char *) x->data;
bv.bv_len = x->size;
tlsg_x509_cert_dn( &bv, der_dn, 1 );
tlsg_session *s = (tlsg_session *)session;
if ( !s->peer_der_dn.bv_val ) {
const gnutls_datum_t *peer_cert_list;
- int list_size;
+ unsigned int list_size;
struct berval bv;
peer_cert_list = gnutls_certificate_get_peers( s->session,
if ( !peer_cert_list ) return LDAP_INVALID_CREDENTIALS;
bv.bv_len = peer_cert_list->size;
- bv.bv_val = peer_cert_list->data;
+ bv.bv_val = (char *) peer_cert_list->data;
tlsg_x509_cert_dn( &bv, &s->peer_der_dn, 1 );
}
tlsg_session *s = (tlsg_session *)session;
int i, ret;
const gnutls_datum_t *peer_cert_list;
- int list_size;
- struct berval bv;
+ unsigned int list_size;
char altname[NI_MAXHOST];
size_t altnamesize;
gnutls_x509_crt_t cert;
- gnutls_datum_t *x;
const char *name;
char *ptr;
char *domain = NULL;
#else
struct in_addr addr;
#endif
- int n, len1 = 0, len2 = 0;
+ int len1 = 0, len2 = 0;
int ntype = IS_DNS;
- time_t now = time(0);
if( ldap_int_hostname &&
( !name_in || !strcasecmp( name_in, "localhost" ) ) )
}
#ifdef LDAP_PF_INET6
- if (name[0] == '[' && strchr(name, ']')) {
- char *n2 = ldap_strdup(name+1);
- *strchr(n2, ']') = 0;
- if (inet_pton(AF_INET6, n2, &addr))
- ntype = IS_IP6;
- LDAP_FREE(n2);
+ if (inet_pton(AF_INET6, name, &addr)) {
+ ntype = IS_IP6;
} else
#endif
if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) {
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 );
+ /* find the last CN */
+ i=0;
+ do {
+ altnamesize = 0;
+ ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID,
+ i, 1, altname, &altnamesize );
+ if ( ret == GNUTLS_E_SHORT_MEMORY_BUFFER )
+ i++;
+ else
+ break;
+ } while ( 1 );
+
+ if ( i ) {
+ altnamesize = sizeof(altname);
+ ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID,
+ i-1, 0, altname, &altnamesize );
+ }
+
if ( ret < 0 ) {
Debug( LDAP_DEBUG_ANY,
"TLS: unable to get common name from peer certificate.\n",
assert( sbiod->sbiod_pvt != NULL );
p = (struct tls_data *)sbiod->sbiod_pvt;
- gnutls_bye ( p->session->session, GNUTLS_SHUT_RDWR );
+ gnutls_bye ( p->session->session, GNUTLS_SHUT_WR );
return 0;
}
{
struct tls_data *p;
ber_slen_t ret;
- int err;
assert( sbiod != NULL );
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
{
struct tls_data *p;
ber_slen_t ret;
- int err;
assert( sbiod != NULL );
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
unsigned int status = 0;
int err;
time_t now = time(0);
+ time_t peertime;
err = gnutls_certificate_verify_peers2( ssl->session, &status );
if ( err < 0 ) {
status, 0,0 );
return -1;
}
- if ( gnutls_certificate_expiration_time_peers( ssl->session ) < now ) {
+ peertime = gnutls_certificate_expiration_time_peers( ssl->session );
+ if ( peertime == (time_t) -1 ) {
+ Debug( LDAP_DEBUG_ANY, "TLS: gnutls_certificate_expiration_time_peers failed\n",
+ 0, 0, 0 );
+ return -1;
+ }
+ if ( peertime < 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 ) {
+ peertime = gnutls_certificate_activation_time_peers( ssl->session );
+ if ( peertime == (time_t) -1 ) {
+ Debug( LDAP_DEBUG_ANY, "TLS: gnutls_certificate_activation_time_peers failed\n",
+ 0, 0, 0 );
+ return -1;
+ }
+ if ( peertime > now ) {
Debug( LDAP_DEBUG_ANY, "TLS: peer certificate not yet active\n",
0, 0, 0 );
return -1;