Blame SOURCES/openldap-add-TLS_REQSAN-option.patch

47a6a9
From c8050d1e6eb0f4f3deb187224945ddcfc3baa4d6 Mon Sep 17 00:00:00 2001
47a6a9
From: Howard Chu <hyc@openldap.org>
47a6a9
Date: Fri, 21 Aug 2020 09:15:15 +0100
47a6a9
Subject: [PATCH] ITS#9318 add TLS_REQSAN option
47a6a9
47a6a9
Add an option to specify how subjectAlternativeNames should be
47a6a9
handled when validating the names in a server certificate.
47a6a9
---
47a6a9
 doc/man/man3/ldap_get_option.3 |  9 +++++++
47a6a9
 doc/man/man5/ldap.conf.5       | 31 +++++++++++++++++++++++
47a6a9
 include/ldap.h                 |  1 +
47a6a9
 libraries/libldap/init.c       |  2 ++
47a6a9
 libraries/libldap/ldap-int.h   |  1 +
47a6a9
 libraries/libldap/tls2.c       | 16 ++++++++++++
47a6a9
 libraries/libldap/tls_g.c      | 46 ++++++++++++++++++++++++++++++++--
47a6a9
 libraries/libldap/tls_o.c      | 44 ++++++++++++++++++++++++++++++--
47a6a9
 8 files changed, 146 insertions(+), 4 deletions(-)
47a6a9
47a6a9
diff --git a/doc/man/man3/ldap_get_option.3 b/doc/man/man3/ldap_get_option.3
47a6a9
index d229ce6e3..7d760136f 100644
47a6a9
--- a/doc/man/man3/ldap_get_option.3
47a6a9
+++ b/doc/man/man3/ldap_get_option.3
47a6a9
@@ -788,6 +788,15 @@ one of
47a6a9
 .BR LDAP_OPT_X_TLS_ALLOW ,
47a6a9
 .BR LDAP_OPT_X_TLS_TRY .
47a6a9
 .TP
47a6a9
+.B LDAP_OPT_X_TLS_REQUIRE_SAN
47a6a9
+Sets/gets the peer certificate subjectAlternativeName checking strategy,
47a6a9
+one of
47a6a9
+.BR LDAP_OPT_X_TLS_NEVER ,
47a6a9
+.BR LDAP_OPT_X_TLS_HARD ,
47a6a9
+.BR LDAP_OPT_X_TLS_DEMAND ,
47a6a9
+.BR LDAP_OPT_X_TLS_ALLOW ,
47a6a9
+.BR LDAP_OPT_X_TLS_TRY .
47a6a9
+.TP
47a6a9
 .B LDAP_OPT_X_TLS_SSL_CTX
47a6a9
 Gets the TLS session context associated with this handle.
47a6a9
 .BR outvalue
47a6a9
diff --git a/doc/man/man5/ldap.conf.5 b/doc/man/man5/ldap.conf.5
47a6a9
index 2f1ee886d..cde2c875f 100644
47a6a9
--- a/doc/man/man5/ldap.conf.5
47a6a9
+++ b/doc/man/man5/ldap.conf.5
47a6a9
@@ -464,6 +464,37 @@ certificate is provided, or a bad certificate is provided, the session
47a6a9
 is immediately terminated. This is the default setting.
47a6a9
 .RE
47a6a9
 .TP
47a6a9
+.B TLS_REQSAN <level>
47a6a9
+Specifies what checks to perform on the subjectAlternativeName
47a6a9
+(SAN) extensions in a server certificate when validating the certificate
47a6a9
+name against the specified hostname of the server. The
47a6a9
+.B <level>
47a6a9
+can be specified as one of the following keywords:
47a6a9
+.RS
47a6a9
+.TP
47a6a9
+.B never
47a6a9
+The client will not check any SAN in the certificate.
47a6a9
+.TP
47a6a9
+.B allow
47a6a9
+The SAN is checked against the specified hostname. If a SAN is
47a6a9
+present but none match the specified hostname, the SANs are ignored
47a6a9
+and the usual check against the certificate DN is used.
47a6a9
+This is the default setting.
47a6a9
+.TP
47a6a9
+.B try
47a6a9
+The SAN is checked against the specified hostname. If no SAN is present
47a6a9
+in the server certificate, the usual check against the certificate DN
47a6a9
+is used. If a SAN is present but doesn't match the specified hostname,
47a6a9
+the session is immediately terminated. This setting may be preferred
47a6a9
+when a mix of certs with and without SANs are in use.
47a6a9
+.TP
47a6a9
+.B demand | hard
47a6a9
+These keywords are equivalent. The SAN is checked against the specified
47a6a9
+hostname. If no SAN is present in the server certificate, or no SANs
47a6a9
+match, the session is immediately terminated. This setting should be
47a6a9
+used when only certificates with SANs are in use.
47a6a9
+.RE
47a6a9
+.TP
47a6a9
 .B TLS_CRLCHECK <level>
47a6a9
 Specifies if the Certificate Revocation List (CRL) of the CA should be 
47a6a9
 used to verify if the server certificates have not been revoked. This
47a6a9
diff --git a/include/ldap.h b/include/ldap.h
47a6a9
index 4b81a6841..4877de24a 100644
47a6a9
--- a/include/ldap.h
47a6a9
+++ b/include/ldap.h
47a6a9
@@ -160,6 +160,7 @@ LDAP_BEGIN_DECL
47a6a9
 #define LDAP_OPT_X_TLS_PACKAGE		0x6011
47a6a9
 #define LDAP_OPT_X_TLS_ECNAME		0x6012
47a6a9
 #define LDAP_OPT_X_TLS_PEERCERT		0x6015	/* read-only */
47a6a9
+#define LDAP_OPT_X_TLS_REQUIRE_SAN	0x601a
47a6a9
 
47a6a9
 #define LDAP_OPT_X_TLS_NEVER	0
47a6a9
 #define LDAP_OPT_X_TLS_HARD		1
47a6a9
diff --git a/libraries/libldap/init.c b/libraries/libldap/init.c
47a6a9
index d503019aa..0d91808ec 100644
47a6a9
--- a/libraries/libldap/init.c
47a6a9
+++ b/libraries/libldap/init.c
47a6a9
@@ -128,6 +128,7 @@ static const struct ol_attribute {
47a6a9
   	{0, ATTR_TLS,	"TLS_CACERT",		NULL,	LDAP_OPT_X_TLS_CACERTFILE},
47a6a9
   	{0, ATTR_TLS,	"TLS_CACERTDIR",	NULL,	LDAP_OPT_X_TLS_CACERTDIR},
47a6a9
   	{0, ATTR_TLS,	"TLS_REQCERT",		NULL,	LDAP_OPT_X_TLS_REQUIRE_CERT},
47a6a9
+	{0, ATTR_TLS,	"TLS_REQSAN",		NULL,	LDAP_OPT_X_TLS_REQUIRE_SAN},
47a6a9
 	{0, ATTR_TLS,	"TLS_RANDFILE",		NULL,	LDAP_OPT_X_TLS_RANDOM_FILE},
47a6a9
 	{0, ATTR_TLS,	"TLS_CIPHER_SUITE",	NULL,	LDAP_OPT_X_TLS_CIPHER_SUITE},
47a6a9
 	{0, ATTR_TLS,	"TLS_PROTOCOL_MIN",	NULL,	LDAP_OPT_X_TLS_PROTOCOL_MIN},
47a6a9
@@ -624,6 +625,7 @@ void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl
47a6a9
 	gopts->ldo_tls_connect_cb = NULL;
47a6a9
 	gopts->ldo_tls_connect_arg = NULL;
47a6a9
 	gopts->ldo_tls_require_cert = LDAP_OPT_X_TLS_DEMAND;
47a6a9
+	gopts->ldo_tls_require_san = LDAP_OPT_X_TLS_ALLOW;
47a6a9
 #endif
47a6a9
 	gopts->ldo_keepalive_probes = 0;
47a6a9
 	gopts->ldo_keepalive_interval = 0;
47a6a9
diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h
47a6a9
index 753014ad0..2bf5d4ff6 100644
47a6a9
--- a/libraries/libldap/ldap-int.h
47a6a9
+++ b/libraries/libldap/ldap-int.h
47a6a9
@@ -262,6 +262,7 @@ struct ldapoptions {
47a6a9
    	int			ldo_tls_require_cert;
47a6a9
 	int			ldo_tls_impl;
47a6a9
    	int			ldo_tls_crlcheck;
47a6a9
+	int			ldo_tls_require_san;
47a6a9
 #define LDAP_LDO_TLS_NULLARG ,0,0,0,{0,0,0,0,0,0,0,0,0},0,0,0,0
47a6a9
 #else
47a6a9
 #define LDAP_LDO_TLS_NULLARG
47a6a9
diff --git a/libraries/libldap/tls2.c b/libraries/libldap/tls2.c
47a6a9
index 6a2113255..670292c22 100644
47a6a9
--- a/libraries/libldap/tls2.c
47a6a9
+++ b/libraries/libldap/tls2.c
47a6a9
@@ -539,6 +539,7 @@ ldap_int_tls_config( LDAP *ld, int option, const char *arg )
47a6a9
 		return ldap_pvt_tls_set_option( ld, option, (void *) arg );
47a6a9
 
47a6a9
 	case LDAP_OPT_X_TLS_REQUIRE_CERT:
47a6a9
+	case LDAP_OPT_X_TLS_REQUIRE_SAN:
47a6a9
 	case LDAP_OPT_X_TLS:
47a6a9
 		i = -1;
47a6a9
 		if ( strcasecmp( arg, "never" ) == 0 ) {
47a6a9
@@ -669,6 +670,9 @@ ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg )
47a6a9
 	case LDAP_OPT_X_TLS_REQUIRE_CERT:
47a6a9
 		*(int *)arg = lo->ldo_tls_require_cert;
47a6a9
 		break;
47a6a9
+	case LDAP_OPT_X_TLS_REQUIRE_SAN:
47a6a9
+		*(int *)arg = lo->ldo_tls_require_san;
47a6a9
+		break;
47a6a9
 #ifdef HAVE_OPENSSL_CRL
47a6a9
 	case LDAP_OPT_X_TLS_CRLCHECK:	/* OpenSSL only */
47a6a9
 		*(int *)arg = lo->ldo_tls_crlcheck;
47a6a9
@@ -818,6 +822,18 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg )
47a6a9
 			return 0;
47a6a9
 		}
47a6a9
 		return -1;
47a6a9
+	case LDAP_OPT_X_TLS_REQUIRE_SAN:
47a6a9
+		if ( !arg ) return -1;
47a6a9
+		switch( *(int *) arg ) {
47a6a9
+		case LDAP_OPT_X_TLS_NEVER:
47a6a9
+		case LDAP_OPT_X_TLS_DEMAND:
47a6a9
+		case LDAP_OPT_X_TLS_ALLOW:
47a6a9
+		case LDAP_OPT_X_TLS_TRY:
47a6a9
+		case LDAP_OPT_X_TLS_HARD:
47a6a9
+			lo->ldo_tls_require_san = * (int *) arg;
47a6a9
+			return 0;
47a6a9
+		}
47a6a9
+		return -1;
47a6a9
 #ifdef HAVE_OPENSSL_CRL
47a6a9
 	case LDAP_OPT_X_TLS_CRLCHECK:	/* OpenSSL only */
47a6a9
 		if ( !arg ) return -1;
47a6a9
diff --git a/libraries/libldap/tls_g.c b/libraries/libldap/tls_g.c
47a6a9
index 15ce0bbb8..e3486c9b4 100644
47a6a9
--- a/libraries/libldap/tls_g.c
47a6a9
+++ b/libraries/libldap/tls_g.c
47a6a9
@@ -496,6 +496,7 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
47a6a9
 {
47a6a9
 	tlsg_session *s = (tlsg_session *)session;
47a6a9
 	int i, ret;
47a6a9
+	int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
47a6a9
 	const gnutls_datum_t *peer_cert_list;
47a6a9
 	unsigned int list_size;
47a6a9
 	char altname[NI_MAXHOST];
47a6a9
@@ -558,12 +559,14 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
47a6a9
 		}
47a6a9
 	}
47a6a9
 
47a6a9
+	if (chkSAN) {
47a6a9
 	for ( i=0, ret=0; ret >= 0; i++ ) {
47a6a9
 		altnamesize = sizeof(altname);
47a6a9
 		ret = gnutls_x509_crt_get_subject_alt_name( cert, i, 
47a6a9
 			altname, &altnamesize, NULL );
47a6a9
 		if ( ret < 0 ) break;
47a6a9
 
47a6a9
+		gotSAN = 1;
47a6a9
 		/* ignore empty */
47a6a9
 		if ( altnamesize == 0 ) continue;
47a6a9
 
47a6a9
@@ -599,7 +602,45 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
47a6a9
 	}
47a6a9
 	if ( ret >= 0 ) {
47a6a9
 		ret = LDAP_SUCCESS;
47a6a9
-	} else {
47a6a9
+	}
47a6a9
+	}
47a6a9
+	if (ret != LDAP_SUCCESS && chkSAN) {
47a6a9
+		switch(chkSAN) {
47a6a9
+		case LDAP_OPT_X_TLS_DEMAND:
47a6a9
+		case LDAP_OPT_X_TLS_HARD:
47a6a9
+			if (!gotSAN) {
47a6a9
+				Debug( LDAP_DEBUG_ANY,
47a6a9
+					"TLS: unable to get subjectAltName from peer certificate.\n",
47a6a9
+					0, 0, 0 );
47a6a9
+				ret = LDAP_CONNECT_ERROR;
47a6a9
+				if ( ld->ld_error ) {
47a6a9
+					LDAP_FREE( ld->ld_error );
47a6a9
+				}
47a6a9
+				ld->ld_error = LDAP_STRDUP(
47a6a9
+					_("TLS: unable to get subjectAltName from peer certificate"));
47a6a9
+				goto done;
47a6a9
+			}
47a6a9
+			/* FALLTHRU */
47a6a9
+		case LDAP_OPT_X_TLS_TRY:
47a6a9
+			if (gotSAN) {
47a6a9
+				Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
47a6a9
+					"subjectAltName in certificate.\n",
47a6a9
+					name, 0, 0 );
47a6a9
+				ret = LDAP_CONNECT_ERROR;
47a6a9
+				if ( ld->ld_error ) {
47a6a9
+					LDAP_FREE( ld->ld_error );
47a6a9
+				}
47a6a9
+				ld->ld_error = LDAP_STRDUP(
47a6a9
+					_("TLS: hostname does not match subjectAltName in peer certificate"));
47a6a9
+				goto done;
47a6a9
+			}
47a6a9
+			break;
47a6a9
+		case LDAP_OPT_X_TLS_ALLOW:
47a6a9
+			break;
47a6a9
+		}
47a6a9
+	}
47a6a9
+
47a6a9
+	if ( ret != LDAP_SUCCESS ){
47a6a9
 		/* find the last CN */
47a6a9
 		i=0;
47a6a9
 		do {
47a6a9
@@ -654,9 +695,10 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
47a6a9
 				LDAP_FREE( ld->ld_error );
47a6a9
 			}
47a6a9
 			ld->ld_error = LDAP_STRDUP(
47a6a9
-				_("TLS: hostname does not match CN in peer certificate"));
47a6a9
+				_("TLS: hostname does not match name in peer certificate"));
47a6a9
 		}
47a6a9
 	}
47a6a9
+done:
47a6a9
 	gnutls_x509_crt_deinit( cert );
47a6a9
 	return ret;
47a6a9
 }
47a6a9
diff --git a/libraries/libldap/tls_o.c b/libraries/libldap/tls_o.c
47a6a9
index 4006f7a4f..6f27168e9 100644
47a6a9
--- a/libraries/libldap/tls_o.c
47a6a9
+++ b/libraries/libldap/tls_o.c
47a6a9
@@ -600,6 +600,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
47a6a9
 {
47a6a9
 	tlso_session *s = (tlso_session *)sess;
47a6a9
 	int i, ret = LDAP_LOCAL_ERROR;
47a6a9
+	int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
47a6a9
 	X509 *x;
47a6a9
 	const char *name;
47a6a9
 	char *ptr;
47a6a9
@@ -638,7 +639,8 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
47a6a9
 	if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) {
47a6a9
 		if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4;
47a6a9
 	}
47a6a9
-	
47a6a9
+
47a6a9
+	if (chkSAN) {
47a6a9
 	i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
47a6a9
 	if (i >= 0) {
47a6a9
 		X509_EXTENSION *ex;
47a6a9
@@ -651,6 +653,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
47a6a9
 			char *domain = NULL;
47a6a9
 			GENERAL_NAME *gn;
47a6a9
 
47a6a9
+			gotSAN = 1;
47a6a9
 			if (ntype == IS_DNS) {
47a6a9
 				domain = strchr(name, '.');
47a6a9
 				if (domain) {
47a6a9
@@ -709,6 +712,42 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
47a6a9
 			}
47a6a9
 		}
47a6a9
 	}
47a6a9
+	}
47a6a9
+	if (ret != LDAP_SUCCESS && chkSAN) {
47a6a9
+		switch(chkSAN) {
47a6a9
+		case LDAP_OPT_X_TLS_DEMAND:
47a6a9
+		case LDAP_OPT_X_TLS_HARD:
47a6a9
+			if (!gotSAN) {
47a6a9
+				Debug( LDAP_DEBUG_ANY,
47a6a9
+					"TLS: unable to get subjectAltName from peer certificate.\n",
47a6a9
+					0, 0, 0 );
47a6a9
+				ret = LDAP_CONNECT_ERROR;
47a6a9
+				if ( ld->ld_error ) {
47a6a9
+					LDAP_FREE( ld->ld_error );
47a6a9
+				}
47a6a9
+				ld->ld_error = LDAP_STRDUP(
47a6a9
+					_("TLS: unable to get subjectAltName from peer certificate"));
47a6a9
+				goto done;
47a6a9
+			}
47a6a9
+			/* FALLTHRU */
47a6a9
+		case LDAP_OPT_X_TLS_TRY:
47a6a9
+			if (gotSAN) {
47a6a9
+				Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
47a6a9
+					"subjectAltName in certificate.\n",
47a6a9
+					name, 0, 0 );
47a6a9
+				ret = LDAP_CONNECT_ERROR;
47a6a9
+				if ( ld->ld_error ) {
47a6a9
+					LDAP_FREE( ld->ld_error );
47a6a9
+				}
47a6a9
+				ld->ld_error = LDAP_STRDUP(
47a6a9
+					_("TLS: hostname does not match subjectAltName in peer certificate"));
47a6a9
+				goto done;
47a6a9
+			}
47a6a9
+			break;
47a6a9
+		case LDAP_OPT_X_TLS_ALLOW:
47a6a9
+			break;
47a6a9
+		}
47a6a9
+	}
47a6a9
 
47a6a9
 	if (ret != LDAP_SUCCESS) {
47a6a9
 		X509_NAME *xn;
47a6a9
@@ -772,9 +811,10 @@ no_cn:
47a6a9
 				LDAP_FREE( ld->ld_error );
47a6a9
 			}
47a6a9
 			ld->ld_error = LDAP_STRDUP(
47a6a9
-				_("TLS: hostname does not match CN in peer certificate"));
47a6a9
+				_("TLS: hostname does not match name in peer certificate"));
47a6a9
 		}
47a6a9
 	}
47a6a9
+done:
47a6a9
 	X509_free(x);
47a6a9
 	return ret;
47a6a9
 }
47a6a9
-- 
47a6a9
2.31.1
47a6a9