Commit 1cee5dcd authored by Howard Chu's avatar Howard Chu
Browse files

ITS#6239 from HEAD

parent 2aad7897
......@@ -57,6 +57,7 @@
#include <private/pprio.h>
#include <nss.h>
#include <ssl.h>
#include <sslerr.h>
#include <sslproto.h>
#include <pk11pub.h>
#include <secerr.h>
......@@ -639,6 +640,9 @@ tlsm_bad_cert_handler(void *arg, PRFileDesc *ssl)
success = SECFailure;
}
break;
/* we bypass NSS's hostname checks and do our own */
case SSL_ERROR_BAD_CERT_DOMAIN:
break;
default:
success = SECFailure;
break;
......@@ -685,10 +689,10 @@ tlsm_auth_cert_handler(void *arg, PRFileDesc *fd,
{
SECStatus ret = SSL_AuthCertificate(arg, fd, checksig, isServer);
tlsm_dump_security_status( fd );
Debug( LDAP_DEBUG_TRACE,
"TLS certificate verification: %s: %s,",
ret == SECSuccess ? "ok" : "bad",
tlsm_dump_security_status( fd ), 0 );
"TLS certificate verification: %s\n",
ret == SECSuccess ? "ok" : "bad", 0, 0 );
if ( ret != SECSuccess ) {
PRErrorCode errcode = PORT_GetError();
......@@ -1237,7 +1241,8 @@ tlsm_ctx_free ( tls_ctx *ctx )
#endif
if ( refcount )
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 */
PL_strfree( c->tc_certname );
c->tc_certname = NULL;
......@@ -1366,7 +1371,8 @@ tlsm_deferred_ctx_init( void *arg )
ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) {
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 {
ctx->tc_verify_cert = PR_FALSE;
}
......@@ -1663,7 +1669,6 @@ tlsm_session_connect( LDAP *ld, tls_session *session )
int rc;
PRErrorCode err;
/* By default, NSS checks the cert hostname for us */
rc = SSL_ResetHandshake( s, PR_FALSE /* server */ );
if (rc) {
err = PR_GetError();
......@@ -1673,15 +1678,6 @@ tlsm_session_connect( LDAP *ld, tls_session *session )
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 );
if (rc) {
err = PR_GetError();
......@@ -1754,11 +1750,179 @@ tlsm_session_peer_dn( tls_session *session, struct berval *der_dn )
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 */
return LDAP_SUCCESS;
tlsm_session *s = (tlsm_session *)session;
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
......
/* tls_o.c - Handle tls/ssl using SSLeay or OpenSSL */
/* tls_o.c - Handle tls/ssl using OpenSSL */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
......@@ -466,7 +466,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
X509 *x;
const char *name;
char *ptr;
int ntype = IS_DNS;
int ntype = IS_DNS, nlen;
#ifdef LDAP_PF_INET6
struct in6_addr addr;
#else
......@@ -480,6 +480,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
} else {
name = name_in;
}
nlen = strlen(name);
x = tlso_get_cert(s);
if (!x) {
......@@ -513,15 +514,14 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
ex = X509_get_ext(x, i);
alt = X509V3_EXT_d2i(ex);
if (alt) {
int n, len1 = 0, len2 = 0;
int n, len2 = 0;
char *domain = NULL;
GENERAL_NAME *gn;
if (ntype == IS_DNS) {
len1 = strlen(name);
domain = strchr(name, '.');
if (domain) {
len2 = len1 - (domain-name);
len2 = nlen - (domain-name);
}
}
n = sk_GENERAL_NAME_num(alt);
......@@ -539,7 +539,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
if (sl == 0) continue;
/* Is this an exact match? */
if ((len1 == sl) && !strncasecmp(name, sn, len1)) {
if ((nlen == sl) && !strncasecmp(name, sn, nlen)) {
break;
}
......@@ -580,11 +580,13 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
if (ret != LDAP_SUCCESS) {
X509_NAME *xn;
char buf[2048];
int clen;
buf[0] = '\0';
xn = X509_get_subject_name(x);
if( X509_NAME_get_text_by_NID( xn, NID_commonName,
buf, sizeof(buf)) == -1)
clen = X509_NAME_get_text_by_NID( xn, NID_commonName,
buf, sizeof(buf));
if( clen == -1 )
{
Debug( LDAP_DEBUG_ANY,
"TLS: unable to get common name from peer certificate.\n",
......@@ -596,21 +598,18 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
ld->ld_error = LDAP_STRDUP(
_("TLS: unable to get CN from peer certificate"));
} else if (strcasecmp(name, buf) == 0 ) {
} else if (clen == nlen && 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;
size_t dlen;
sl = strlen(name);
dlen = sl - (domain-name);
sl = strlen(buf);
dlen = nlen - (domain-name);
/* Is this a wildcard match? */
if ((dlen == sl-1) && !strncasecmp(domain, &buf[1], dlen)) {
if ((dlen == clen-1) && !strncasecmp(domain, &buf[1], dlen)) {
ret = LDAP_SUCCESS;
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment