]> git.sur5r.net Git - openldap/commitdiff
ITS#6239 from HEAD
authorHoward Chu <hyc@openldap.org>
Fri, 31 Jul 2009 02:39:20 +0000 (02:39 +0000)
committerHoward Chu <hyc@openldap.org>
Fri, 31 Jul 2009 02:39:20 +0000 (02:39 +0000)
libraries/libldap/tls_m.c
libraries/libldap/tls_o.c

index ef2ccf5b44504b7a986f6833f48c752401aa5d6b..05e8648739d55fc1c1bad74a828f02079bd2a7b0 100644 (file)
@@ -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
index a5855b4a1698edfc5d1746a0d0dcccb0897a4de2..5b5bfb94a9c16921a05ad14587cabeadaac93633 100644 (file)
@@ -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 <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;
                                }
                        }