From: Howard Chu Date: Fri, 31 Jul 2009 02:39:20 +0000 (+0000) Subject: ITS#6239 from HEAD X-Git-Tag: OPENLDAP_REL_ENG_2_4_18~89 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=1cee5dcd12701a972feb1dd974f3f393a97c6dca;p=openldap ITS#6239 from HEAD --- diff --git a/libraries/libldap/tls_m.c b/libraries/libldap/tls_m.c index ef2ccf5b44..05e8648739 100644 --- a/libraries/libldap/tls_m.c +++ b/libraries/libldap/tls_m.c @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -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 diff --git a/libraries/libldap/tls_o.c b/libraries/libldap/tls_o.c index a5855b4a16..5b5bfb94a9 100644 --- a/libraries/libldap/tls_o.c +++ b/libraries/libldap/tls_o.c @@ -1,4 +1,4 @@ -/* 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 . * @@ -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; } }