version 1.8, 2009/07/03 00:06:24
|
version 1.11, 2009/07/31 01:26:03
|
Line 1
|
Line 1
|
/* tls_m.c - Handle tls/ssl using Mozilla NSS. */ |
/* tls_m.c - Handle tls/ssl using Mozilla NSS. */ |
/* $OpenLDAP: pkg/ldap/libraries/libldap/tls_m.c,v 1.7 2009/07/02 21:19:44 hyc Exp $ */ |
/* $OpenLDAP: pkg/ldap/libraries/libldap/tls_m.c,v 1.10 2009/07/30 23:51:34 hyc Exp $ */ |
/* This work is part of OpenLDAP Software <http://www.openldap.org/>. |
/* This work is part of OpenLDAP Software <http://www.openldap.org/>. |
* |
* |
* Copyright 2008-2009 The OpenLDAP Foundation. |
* Copyright 2008-2009 The OpenLDAP Foundation. |
Line 57
|
Line 57
|
#include <private/pprio.h> |
#include <private/pprio.h> |
#include <nss.h> |
#include <nss.h> |
#include <ssl.h> |
#include <ssl.h> |
|
#include <sslerr.h> |
#include <sslproto.h> |
#include <sslproto.h> |
#include <pk11pub.h> |
#include <pk11pub.h> |
#include <secerr.h> |
#include <secerr.h> |
Line 639 tlsm_bad_cert_handler(void *arg, PRFileD
|
Line 640 tlsm_bad_cert_handler(void *arg, PRFileD
|
success = SECFailure; |
success = SECFailure; |
} |
} |
break; |
break; |
|
/* we bypass NSS's hostname checks and do our own */ |
|
case SSL_ERROR_BAD_CERT_DOMAIN: |
|
break; |
default: |
default: |
success = SECFailure; |
success = SECFailure; |
break; |
break; |
Line 685 tlsm_auth_cert_handler(void *arg, PRFile
|
Line 689 tlsm_auth_cert_handler(void *arg, PRFile
|
{ |
{ |
SECStatus ret = SSL_AuthCertificate(arg, fd, checksig, isServer); |
SECStatus ret = SSL_AuthCertificate(arg, fd, checksig, isServer); |
|
|
|
tlsm_dump_security_status( fd ); |
Debug( LDAP_DEBUG_TRACE, |
Debug( LDAP_DEBUG_TRACE, |
"TLS certificate verification: %s: %s,", |
"TLS certificate verification: %s\n", |
ret == SECSuccess ? "ok" : "bad", |
ret == SECSuccess ? "ok" : "bad", 0, 0 ); |
tlsm_dump_security_status( fd ), 0 ); |
|
|
|
if ( ret != SECSuccess ) { |
if ( ret != SECSuccess ) { |
PRErrorCode errcode = PORT_GetError(); |
PRErrorCode errcode = PORT_GetError(); |
Line 1200 tlsm_ctx_new ( struct ldapoptions *lo )
|
Line 1204 tlsm_ctx_new ( struct ldapoptions *lo )
|
ctx->tc_model = NULL; |
ctx->tc_model = NULL; |
memset(&ctx->tc_callonce, 0, sizeof(ctx->tc_callonce)); |
memset(&ctx->tc_callonce, 0, sizeof(ctx->tc_callonce)); |
ctx->tc_require_cert = lo->ldo_tls_require_cert; |
ctx->tc_require_cert = lo->ldo_tls_require_cert; |
} else { |
ctx->tc_verify_cert = PR_FALSE; |
LDAP_FREE( ctx ); |
|
ctx = NULL; |
|
} |
} |
return (tls_ctx *)ctx; |
return (tls_ctx *)ctx; |
} |
} |
Line 1237 tlsm_ctx_free ( tls_ctx *ctx )
|
Line 1239 tlsm_ctx_free ( tls_ctx *ctx )
|
#endif |
#endif |
if ( refcount ) |
if ( refcount ) |
return; |
return; |
PR_Close( c->tc_model ); |
if ( c->tc_model ) |
|
PR_Close( c->tc_model ); |
c->tc_certdb = NULL; /* if not the default, may have to clean up */ |
c->tc_certdb = NULL; /* if not the default, may have to clean up */ |
PL_strfree( c->tc_certname ); |
PL_strfree( c->tc_certname ); |
c->tc_certname = NULL; |
c->tc_certname = NULL; |
Line 1366 tlsm_deferred_ctx_init( void *arg )
|
Line 1369 tlsm_deferred_ctx_init( void *arg )
|
ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) { |
ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) { |
require_cert = SSL_REQUIRE_ALWAYS; |
require_cert = SSL_REQUIRE_ALWAYS; |
} |
} |
ctx->tc_verify_cert = PR_TRUE; |
if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW ) |
|
ctx->tc_verify_cert = PR_TRUE; |
} else { |
} else { |
ctx->tc_verify_cert = PR_FALSE; |
ctx->tc_verify_cert = PR_FALSE; |
} |
} |
Line 1663 tlsm_session_connect( LDAP *ld, tls_sess
|
Line 1667 tlsm_session_connect( LDAP *ld, tls_sess
|
int rc; |
int rc; |
PRErrorCode err; |
PRErrorCode err; |
|
|
/* By default, NSS checks the cert hostname for us */ |
|
rc = SSL_ResetHandshake( s, PR_FALSE /* server */ ); |
rc = SSL_ResetHandshake( s, PR_FALSE /* server */ ); |
if (rc) { |
if (rc) { |
err = PR_GetError(); |
err = PR_GetError(); |
Line 1673 tlsm_session_connect( LDAP *ld, tls_sess
|
Line 1676 tlsm_session_connect( LDAP *ld, tls_sess
|
err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); |
err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); |
} |
} |
|
|
rc = SSL_SetURL( s, ld->ld_options.ldo_defludp->lud_host ); |
|
if (rc) { |
|
err = PR_GetError(); |
|
Debug( LDAP_DEBUG_TRACE, |
|
"TLS: error: connect - seturl failure %d - error %d:%s\n", |
|
rc, err, |
|
err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); |
|
} |
|
|
|
rc = SSL_ForceHandshake( s ); |
rc = SSL_ForceHandshake( s ); |
if (rc) { |
if (rc) { |
err = PR_GetError(); |
err = PR_GetError(); |
Line 1754 tlsm_session_peer_dn( tls_session *sessi
|
Line 1748 tlsm_session_peer_dn( tls_session *sessi
|
return 0; |
return 0; |
} |
} |
|
|
|
/* what kind of hostname were we given? */ |
|
#define IS_DNS 0 |
|
#define IS_IP4 1 |
|
#define IS_IP6 2 |
|
|
static int |
static int |
tlsm_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) |
tlsm_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) |
{ |
{ |
/* NSS already does a hostname check */ |
tlsm_session *s = (tlsm_session *)session; |
return LDAP_SUCCESS; |
CERTCertificate *cert; |
|
const char *name, *domain = NULL, *ptr; |
|
int i, ret, ntype = IS_DNS, nlen, dlen; |
|
#ifdef LDAP_PF_INET6 |
|
struct in6_addr addr; |
|
#else |
|
struct in_addr addr; |
|
#endif |
|
SECItem altname; |
|
SECStatus rv; |
|
|
|
if( ldap_int_hostname && |
|
( !name_in || !strcasecmp( name_in, "localhost" ) ) ) |
|
{ |
|
name = ldap_int_hostname; |
|
} else { |
|
name = name_in; |
|
} |
|
nlen = strlen( name ); |
|
|
|
cert = SSL_PeerCertificate( s ); |
|
if (!cert) { |
|
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, ']') = 0; |
|
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 ) { |
|
domain = strchr( name, '.' ); |
|
if ( domain ) |
|
dlen = nlen - ( domain - name ); |
|
} |
|
|
|
ret = LDAP_LOCAL_ERROR; |
|
|
|
rv = CERT_FindCertExtension( cert, SEC_OID_X509_SUBJECT_ALT_NAME, |
|
&altname ); |
|
if ( rv == SECSuccess && altname.data ) { |
|
PRArenaPool *arena; |
|
CERTGeneralName *names, *cur; |
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
|
if ( !arena ) { |
|
ret = LDAP_NO_MEMORY; |
|
goto fail; |
|
} |
|
|
|
names = cur = CERT_DecodeAltNameExtension(arena, &altname); |
|
if ( !cur ) |
|
goto altfail; |
|
|
|
do { |
|
char *host; |
|
int hlen; |
|
|
|
/* ignore empty */ |
|
if ( !cur->name.other.len ) continue; |
|
|
|
host = cur->name.other.data; |
|
hlen = cur->name.other.len; |
|
|
|
if ( cur->type == certDNSName ) { |
|
if ( ntype != IS_DNS ) continue; |
|
|
|
/* is this an exact match? */ |
|
if ( nlen == hlen && !strncasecmp( name, host, nlen )) { |
|
ret = LDAP_SUCCESS; |
|
break; |
|
} |
|
|
|
/* is this a wildcard match? */ |
|
if ( domain && host[0] == '*' && host[1] == '.' && |
|
dlen == hlen-1 && !strncasecmp( domain, host+1, dlen )) { |
|
ret = LDAP_SUCCESS; |
|
break; |
|
} |
|
} else if ( cur->type == certIPAddress ) { |
|
if ( ntype == IS_DNS ) continue; |
|
|
|
#ifdef LDAP_PF_INET6 |
|
if (ntype == IS_IP6 && hlen != sizeof(struct in6_addr)) { |
|
continue; |
|
} else |
|
#endif |
|
if (ntype == IS_IP4 && hlen != sizeof(struct in_addr)) { |
|
continue; |
|
} |
|
if (!memcmp(host, &addr, hlen)) { |
|
ret = LDAP_SUCCESS; |
|
break; |
|
} |
|
} |
|
} while (( cur = CERT_GetNextGeneralName( cur )) != names ); |
|
altfail: |
|
PORT_FreeArena( arena, PR_FALSE ); |
|
SECITEM_FreeItem( &altname, PR_FALSE ); |
|
} |
|
/* no altnames matched, try the CN */ |
|
if ( ret != LDAP_SUCCESS ) { |
|
/* find the last CN */ |
|
CERTRDN *rdn, **rdns; |
|
CERTAVA *lastava = NULL; |
|
char buf[2048]; |
|
|
|
buf[0] = '\0'; |
|
rdns = cert->subject.rdns; |
|
while ( rdns && ( rdn = *rdns++ )) { |
|
CERTAVA *ava, **avas = rdn->avas; |
|
while ( avas && ( ava = *avas++ )) { |
|
if ( CERT_GetAVATag( ava ) == SEC_OID_AVA_COMMON_NAME ) |
|
lastava = ava; |
|
} |
|
} |
|
if ( lastava ) { |
|
SECItem *av = CERT_DecodeAVAValue( &lastava->value ); |
|
if ( av ) { |
|
if ( av->len == nlen && !strncasecmp( name, av->data, nlen )) { |
|
ret = LDAP_SUCCESS; |
|
} else if ( av->data[0] == '*' && av->data[1] == '.' && |
|
domain && dlen == av->len - 1 && !strncasecmp( name, |
|
av->data+1, dlen )) { |
|
ret = LDAP_SUCCESS; |
|
} else { |
|
int len = av->len; |
|
if ( len >= sizeof(buf) ) |
|
len = sizeof(buf)-1; |
|
memcpy( buf, av->data, len ); |
|
buf[len] = '\0'; |
|
} |
|
SECITEM_FreeItem( av, PR_TRUE ); |
|
} |
|
} |
|
if ( ret != LDAP_SUCCESS ) { |
|
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")); |
|
} |
|
} |
|
|
|
fail: |
|
CERT_DestroyCertificate( cert ); |
|
return ret; |
} |
} |
|
|
static int |
static int |