]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9318 add TLS_REQSAN option
authorHoward Chu <hyc@openldap.org>
Fri, 21 Aug 2020 08:15:15 +0000 (09:15 +0100)
committerQuanah Gibson-Mount <quanah@openldap.org>
Fri, 21 Aug 2020 22:43:47 +0000 (22:43 +0000)
Add an option to specify how subjectAlternativeNames should be
handled when validating the names in a server certificate.

doc/man/man3/ldap_get_option.3
doc/man/man5/ldap.conf.5
include/ldap.h
libraries/libldap/init.c
libraries/libldap/ldap-int.h
libraries/libldap/tls2.c
libraries/libldap/tls_g.c
libraries/libldap/tls_o.c

index 253fe57c7652332a3383db82132f2359d34b8651..c226056d0dc1d97c2b68cafa55d05f18cd4a0919 100644 (file)
@@ -778,6 +778,15 @@ one of
 .BR LDAP_OPT_X_TLS_ALLOW ,
 .BR LDAP_OPT_X_TLS_TRY .
 .TP
+.B LDAP_OPT_X_TLS_REQUIRE_SAN
+Sets/gets the peer certificate subjectAlternativeName checking strategy,
+one of
+.BR LDAP_OPT_X_TLS_NEVER ,
+.BR LDAP_OPT_X_TLS_HARD ,
+.BR LDAP_OPT_X_TLS_DEMAND ,
+.BR LDAP_OPT_X_TLS_ALLOW ,
+.BR LDAP_OPT_X_TLS_TRY .
+.TP
 .B LDAP_OPT_X_TLS_SSL_CTX
 Gets the TLS session context associated with this handle.
 .BR outvalue
index 1cf78cd4caab5f6b033c41ff0cc300eaf0cf8b75..e6ac5cf3b94e698f86132926be17c4499f67077d 100644 (file)
@@ -464,6 +464,37 @@ certificate is provided, or a bad certificate is provided, the session
 is immediately terminated. This is the default setting.
 .RE
 .TP
+.B TLS_REQSAN <level>
+Specifies what checks to perform on the subjectAlternativeName
+(SAN) extensions in a server certificate when validating the certificate
+name against the specified hostname of the server. The
+.B <level>
+can be specified as one of the following keywords:
+.RS
+.TP
+.B never
+The client will not check any SAN in the certificate.
+.TP
+.B allow
+The SAN is checked against the specified hostname. If a SAN is
+present but none match the specified hostname, the SANs are ignored
+and the usual check against the certificate DN is used.
+This is the default setting.
+.TP
+.B try
+The SAN is checked against the specified hostname. If no SAN is present
+in the server certificate, the usual check against the certificate DN
+is used. If a SAN is present but doesn't match the specified hostname,
+the session is immediately terminated. This setting may be preferred
+when a mix of certs with and without SANs are in use.
+.TP
+.B demand | hard
+These keywords are equivalent. The SAN is checked against the specified
+hostname. If no SAN is present in the server certificate, or no SANs
+match, the session is immediately terminated. This setting should be
+used when only certificates with SANs are in use.
+.RE
+.TP
 .B TLS_CRLCHECK <level>
 Specifies if the Certificate Revocation List (CRL) of the CA should be 
 used to verify if the server certificates have not been revoked. This
index c8f9f189b20b4933515e383afc0231e1ae483093..8523359cc4fbec16077bd61e13b9b102f9865a72 100644 (file)
@@ -159,6 +159,7 @@ LDAP_BEGIN_DECL
 #define LDAP_OPT_X_TLS_CRLFILE         0x6010  /* GNUtls only */
 #define LDAP_OPT_X_TLS_PACKAGE         0x6011
 #define LDAP_OPT_X_TLS_ECNAME          0x6012
+#define LDAP_OPT_X_TLS_REQUIRE_SAN     0x601a
 
 #define LDAP_OPT_X_TLS_NEVER   0
 #define LDAP_OPT_X_TLS_HARD            1
index 0ebfff5e024cb96967615fa4b2a1d5f1e46508f5..b9cdf8cc9e2efef33b0488e9bea32c6b83afc7ba 100644 (file)
@@ -127,6 +127,7 @@ static const struct ol_attribute {
        {0, ATTR_TLS,   "TLS_CACERT",           NULL,   LDAP_OPT_X_TLS_CACERTFILE},
        {0, ATTR_TLS,   "TLS_CACERTDIR",        NULL,   LDAP_OPT_X_TLS_CACERTDIR},
        {0, ATTR_TLS,   "TLS_REQCERT",          NULL,   LDAP_OPT_X_TLS_REQUIRE_CERT},
+       {0, ATTR_TLS,   "TLS_REQSAN",           NULL,   LDAP_OPT_X_TLS_REQUIRE_SAN},
        {0, ATTR_TLS,   "TLS_RANDFILE",         NULL,   LDAP_OPT_X_TLS_RANDOM_FILE},
        {0, ATTR_TLS,   "TLS_CIPHER_SUITE",     NULL,   LDAP_OPT_X_TLS_CIPHER_SUITE},
        {0, ATTR_TLS,   "TLS_PROTOCOL_MIN",     NULL,   LDAP_OPT_X_TLS_PROTOCOL_MIN},
@@ -574,6 +575,7 @@ void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl
        gopts->ldo_tls_connect_cb = NULL;
        gopts->ldo_tls_connect_arg = NULL;
        gopts->ldo_tls_require_cert = LDAP_OPT_X_TLS_DEMAND;
+       gopts->ldo_tls_require_san = LDAP_OPT_X_TLS_ALLOW;
 #endif
        gopts->ldo_keepalive_probes = 0;
        gopts->ldo_keepalive_interval = 0;
index e1f97aef83a351679f53b70700a98ad6bd6b9417..0ddc2a04eb93f9494349e242bb9275e8ebb5a804 100644 (file)
@@ -262,6 +262,7 @@ struct ldapoptions {
        int                     ldo_tls_require_cert;
        int                     ldo_tls_impl;
        int                     ldo_tls_crlcheck;
+       int                     ldo_tls_require_san;
 #define LDAP_LDO_TLS_NULLARG ,0,0,0,{0,0,0,0,0,0,0,0,0},0,0,0,0
 #else
 #define LDAP_LDO_TLS_NULLARG
index dc5c7f124aee23a30d1a4308dc83c1421d5d52f2..ca5a44ab0caeb837df4d8b482db6732515f34046 100644 (file)
@@ -537,6 +537,7 @@ ldap_int_tls_config( LDAP *ld, int option, const char *arg )
                return ldap_pvt_tls_set_option( ld, option, (void *) arg );
 
        case LDAP_OPT_X_TLS_REQUIRE_CERT:
+       case LDAP_OPT_X_TLS_REQUIRE_SAN:
        case LDAP_OPT_X_TLS:
                i = -1;
                if ( strcasecmp( arg, "never" ) == 0 ) {
@@ -667,6 +668,9 @@ ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg )
        case LDAP_OPT_X_TLS_REQUIRE_CERT:
                *(int *)arg = lo->ldo_tls_require_cert;
                break;
+       case LDAP_OPT_X_TLS_REQUIRE_SAN:
+               *(int *)arg = lo->ldo_tls_require_san;
+               break;
 #ifdef HAVE_OPENSSL_CRL
        case LDAP_OPT_X_TLS_CRLCHECK:   /* OpenSSL only */
                *(int *)arg = lo->ldo_tls_crlcheck;
@@ -799,6 +803,18 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg )
                        return 0;
                }
                return -1;
+       case LDAP_OPT_X_TLS_REQUIRE_SAN:
+               if ( !arg ) return -1;
+               switch( *(int *) arg ) {
+               case LDAP_OPT_X_TLS_NEVER:
+               case LDAP_OPT_X_TLS_DEMAND:
+               case LDAP_OPT_X_TLS_ALLOW:
+               case LDAP_OPT_X_TLS_TRY:
+               case LDAP_OPT_X_TLS_HARD:
+                       lo->ldo_tls_require_san = * (int *) arg;
+                       return 0;
+               }
+               return -1;
 #ifdef HAVE_OPENSSL_CRL
        case LDAP_OPT_X_TLS_CRLCHECK:   /* OpenSSL only */
                if ( !arg ) return -1;
index f6711b8f43563f9a3fbac9e7e345ccab2515fd1c..fb98d0042a07b3f8c4c3348424b187c3f75f6111 100644 (file)
@@ -453,6 +453,7 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
 {
        tlsg_session *s = (tlsg_session *)session;
        int i, ret;
+       int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
        const gnutls_datum_t *peer_cert_list;
        unsigned int list_size;
        char altname[NI_MAXHOST];
@@ -515,12 +516,14 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
                }
        }
 
+       if (chkSAN) {
        for ( i=0, ret=0; ret >= 0; i++ ) {
                altnamesize = sizeof(altname);
                ret = gnutls_x509_crt_get_subject_alt_name( cert, i, 
                        altname, &altnamesize, NULL );
                if ( ret < 0 ) break;
 
+               gotSAN = 1;
                /* ignore empty */
                if ( altnamesize == 0 ) continue;
 
@@ -556,7 +559,44 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
        }
        if ( ret >= 0 ) {
                ret = LDAP_SUCCESS;
-       } else {
+       }
+       }
+       if (ret != LDAP_SUCCESS && chkSAN) {
+               switch(chkSAN) {
+               case LDAP_OPT_X_TLS_DEMAND:
+               case LDAP_OPT_X_TLS_HARD:
+                       if (!gotSAN) {
+                               Debug( LDAP_DEBUG_ANY,
+                                       "TLS: unable to get subjectAltName from peer certificate.\n", 0, 0, 0 );
+                               ret = LDAP_CONNECT_ERROR;
+                               if ( ld->ld_error ) {
+                                       LDAP_FREE( ld->ld_error );
+                               }
+                               ld->ld_error = LDAP_STRDUP(
+                                       _("TLS: unable to get subjectAltName from peer certificate"));
+                               goto done;
+                       }
+                       /* FALLTHRU */
+               case LDAP_OPT_X_TLS_TRY:
+                       if (gotSAN) {
+                               Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
+                                       "subjectAltName in certificate.\n",
+                                       name, 0, 0 );
+                               ret = LDAP_CONNECT_ERROR;
+                               if ( ld->ld_error ) {
+                                       LDAP_FREE( ld->ld_error );
+                               }
+                               ld->ld_error = LDAP_STRDUP(
+                                       _("TLS: hostname does not match subjectAltName in peer certificate"));
+                               goto done;
+                       }
+                       break;
+               case LDAP_OPT_X_TLS_ALLOW:
+                       break;
+               }
+       }
+
+       if ( ret != LDAP_SUCCESS ){
                /* find the last CN */
                i=0;
                do {
@@ -611,9 +651,10 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
                                LDAP_FREE( ld->ld_error );
                        }
                        ld->ld_error = LDAP_STRDUP(
-                               _("TLS: hostname does not match CN in peer certificate"));
+                               _("TLS: hostname does not match name in peer certificate"));
                }
        }
+done:
        gnutls_x509_crt_deinit( cert );
        return ret;
 }
index 3295ebf5cfbfb7b7be3abed9eeea7d84c8d184f2..c4d8caa71b54a3e204ac0684167e89494795dd62 100644 (file)
@@ -620,6 +620,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
 {
        tlso_session *s = (tlso_session *)sess;
        int i, ret = LDAP_LOCAL_ERROR;
+       int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
        X509 *x;
        const char *name;
        char *ptr;
@@ -658,7 +659,8 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
        if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) {
                if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4;
        }
-       
+
+       if (chkSAN) {
        i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
        if (i >= 0) {
                X509_EXTENSION *ex;
@@ -671,6 +673,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
                        char *domain = NULL;
                        GENERAL_NAME *gn;
 
+                       gotSAN = 1;
                        if (ntype == IS_DNS) {
                                domain = strchr(name, '.');
                                if (domain) {
@@ -729,6 +732,41 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
                        }
                }
        }
+       }
+       if (ret != LDAP_SUCCESS && chkSAN) {
+               switch(chkSAN) {
+               case LDAP_OPT_X_TLS_DEMAND:
+               case LDAP_OPT_X_TLS_HARD:
+                       if (!gotSAN) {
+                               Debug( LDAP_DEBUG_ANY,
+                                       "TLS: unable to get subjectAltName from peer certificate.\n", 0, 0, 0 );
+                               ret = LDAP_CONNECT_ERROR;
+                               if ( ld->ld_error ) {
+                                       LDAP_FREE( ld->ld_error );
+                               }
+                               ld->ld_error = LDAP_STRDUP(
+                                       _("TLS: unable to get subjectAltName from peer certificate"));
+                               goto done;
+                       }
+                       /* FALLTHRU */
+               case LDAP_OPT_X_TLS_TRY:
+                       if (gotSAN) {
+                               Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
+                                       "subjectAltName in certificate.\n",
+                                       name, 0, 0 );
+                               ret = LDAP_CONNECT_ERROR;
+                               if ( ld->ld_error ) {
+                                       LDAP_FREE( ld->ld_error );
+                               }
+                               ld->ld_error = LDAP_STRDUP(
+                                       _("TLS: hostname does not match subjectAltName in peer certificate"));
+                               goto done;
+                       }
+                       break;
+               case LDAP_OPT_X_TLS_ALLOW:
+                       break;
+               }
+       }
 
        if (ret != LDAP_SUCCESS) {
                X509_NAME *xn;
@@ -792,9 +830,10 @@ no_cn:
                                LDAP_FREE( ld->ld_error );
                        }
                        ld->ld_error = LDAP_STRDUP(
-                               _("TLS: hostname does not match CN in peer certificate"));
+                               _("TLS: hostname does not match name in peer certificate"));
                }
        }
+done:
        X509_free(x);
        return ret;
 }