Commit 608a8223 authored by Howard Chu's avatar Howard Chu Committed by Quanah Gibson-Mount
Browse files

ITS#9318 add TLS_REQSAN option

Add an option to specify how subjectAlternativeNames should be
handled when validating the names in a server certificate.
parent 2386a116
Pipeline #731 passed with stage
in 31 minutes and 11 seconds
...@@ -815,6 +815,15 @@ one of ...@@ -815,6 +815,15 @@ one of
.BR LDAP_OPT_X_TLS_ALLOW , .BR LDAP_OPT_X_TLS_ALLOW ,
.BR LDAP_OPT_X_TLS_TRY . .BR LDAP_OPT_X_TLS_TRY .
.TP .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 .B LDAP_OPT_X_TLS_SSL_CTX
Gets the TLS session context associated with this handle. Gets the TLS session context associated with this handle.
.BR outvalue .BR outvalue
......
...@@ -425,6 +425,37 @@ certificate is provided, or a bad certificate is provided, the session ...@@ -425,6 +425,37 @@ certificate is provided, or a bad certificate is provided, the session
is immediately terminated. This is the default setting. is immediately terminated. This is the default setting.
.RE .RE
.TP .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> .B TLS_CRLCHECK <level>
Specifies if the Certificate Revocation List (CRL) of the CA should be Specifies if the Certificate Revocation List (CRL) of the CA should be
used to verify if the server certificates have not been revoked. This used to verify if the server certificates have not been revoked. This
......
...@@ -160,6 +160,7 @@ LDAP_BEGIN_DECL ...@@ -160,6 +160,7 @@ LDAP_BEGIN_DECL
#define LDAP_OPT_X_TLS_CERT 0x6017 #define LDAP_OPT_X_TLS_CERT 0x6017
#define LDAP_OPT_X_TLS_KEY 0x6018 #define LDAP_OPT_X_TLS_KEY 0x6018
#define LDAP_OPT_X_TLS_PEERKEY_HASH 0x6019 #define LDAP_OPT_X_TLS_PEERKEY_HASH 0x6019
#define LDAP_OPT_X_TLS_REQUIRE_SAN 0x601a
#define LDAP_OPT_X_TLS_NEVER 0 #define LDAP_OPT_X_TLS_NEVER 0
#define LDAP_OPT_X_TLS_HARD 1 #define LDAP_OPT_X_TLS_HARD 1
......
...@@ -119,6 +119,7 @@ static const struct ol_attribute { ...@@ -119,6 +119,7 @@ static const struct ol_attribute {
{0, ATTR_TLS, "TLS_CACERT", NULL, LDAP_OPT_X_TLS_CACERTFILE}, {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_CACERTDIR", NULL, LDAP_OPT_X_TLS_CACERTDIR},
{0, ATTR_TLS, "TLS_REQCERT", NULL, LDAP_OPT_X_TLS_REQUIRE_CERT}, {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_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_CIPHER_SUITE", NULL, LDAP_OPT_X_TLS_CIPHER_SUITE},
{0, ATTR_TLS, "TLS_PROTOCOL_MIN", NULL, LDAP_OPT_X_TLS_PROTOCOL_MIN}, {0, ATTR_TLS, "TLS_PROTOCOL_MIN", NULL, LDAP_OPT_X_TLS_PROTOCOL_MIN},
...@@ -597,6 +598,7 @@ void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl ...@@ -597,6 +598,7 @@ void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl
gopts->ldo_tls_connect_cb = NULL; gopts->ldo_tls_connect_cb = NULL;
gopts->ldo_tls_connect_arg = NULL; gopts->ldo_tls_connect_arg = NULL;
gopts->ldo_tls_require_cert = LDAP_OPT_X_TLS_DEMAND; gopts->ldo_tls_require_cert = LDAP_OPT_X_TLS_DEMAND;
gopts->ldo_tls_require_san = LDAP_OPT_X_TLS_ALLOW;
#endif #endif
gopts->ldo_keepalive_probes = 0; gopts->ldo_keepalive_probes = 0;
gopts->ldo_keepalive_interval = 0; gopts->ldo_keepalive_interval = 0;
......
...@@ -285,6 +285,7 @@ struct ldapoptions { ...@@ -285,6 +285,7 @@ struct ldapoptions {
int ldo_tls_require_cert; int ldo_tls_require_cert;
int ldo_tls_impl; int ldo_tls_impl;
int ldo_tls_crlcheck; int ldo_tls_crlcheck;
int ldo_tls_require_san;
char *ldo_tls_pin_hashalg; char *ldo_tls_pin_hashalg;
struct berval ldo_tls_pin; struct berval ldo_tls_pin;
#define LDAP_LDO_TLS_NULLARG ,0,0,0,{0,0,0,0,0,0,0,0,0},0,0,0,0,0,{0,0} #define LDAP_LDO_TLS_NULLARG ,0,0,0,{0,0,0,0,0,0,0,0,0},0,0,0,0,0,{0,0}
......
...@@ -585,6 +585,7 @@ ldap_pvt_tls_config( LDAP *ld, int option, const char *arg ) ...@@ -585,6 +585,7 @@ ldap_pvt_tls_config( LDAP *ld, int option, const char *arg )
return ldap_pvt_tls_set_option( ld, option, (void *) arg ); return ldap_pvt_tls_set_option( ld, option, (void *) arg );
case LDAP_OPT_X_TLS_REQUIRE_CERT: case LDAP_OPT_X_TLS_REQUIRE_CERT:
case LDAP_OPT_X_TLS_REQUIRE_SAN:
case LDAP_OPT_X_TLS: case LDAP_OPT_X_TLS:
i = -1; i = -1;
if ( strcasecmp( arg, "never" ) == 0 ) { if ( strcasecmp( arg, "never" ) == 0 ) {
...@@ -715,6 +716,9 @@ ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg ) ...@@ -715,6 +716,9 @@ ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg )
case LDAP_OPT_X_TLS_REQUIRE_CERT: case LDAP_OPT_X_TLS_REQUIRE_CERT:
*(int *)arg = lo->ldo_tls_require_cert; *(int *)arg = lo->ldo_tls_require_cert;
break; break;
case LDAP_OPT_X_TLS_REQUIRE_SAN:
*(int *)arg = lo->ldo_tls_require_san;
break;
#ifdef HAVE_OPENSSL_CRL #ifdef HAVE_OPENSSL_CRL
case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */
*(int *)arg = lo->ldo_tls_crlcheck; *(int *)arg = lo->ldo_tls_crlcheck;
...@@ -921,6 +925,18 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) ...@@ -921,6 +925,18 @@ ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg )
return 0; return 0;
} }
return -1; 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 #ifdef HAVE_OPENSSL_CRL
case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */
if ( !arg ) return -1; if ( !arg ) return -1;
......
...@@ -559,6 +559,7 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) ...@@ -559,6 +559,7 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
{ {
tlsg_session *s = (tlsg_session *)session; tlsg_session *s = (tlsg_session *)session;
int i, ret; int i, ret;
int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
const gnutls_datum_t *peer_cert_list; const gnutls_datum_t *peer_cert_list;
unsigned int list_size; unsigned int list_size;
char altname[NI_MAXHOST]; char altname[NI_MAXHOST];
...@@ -620,12 +621,14 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) ...@@ -620,12 +621,14 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
} }
} }
if (chkSAN) {
for ( i=0, ret=0; ret >= 0; i++ ) { for ( i=0, ret=0; ret >= 0; i++ ) {
altnamesize = sizeof(altname); altnamesize = sizeof(altname);
ret = gnutls_x509_crt_get_subject_alt_name( cert, i, ret = gnutls_x509_crt_get_subject_alt_name( cert, i,
altname, &altnamesize, NULL ); altname, &altnamesize, NULL );
if ( ret < 0 ) break; if ( ret < 0 ) break;
gotSAN = 1;
/* ignore empty */ /* ignore empty */
if ( altnamesize == 0 ) continue; if ( altnamesize == 0 ) continue;
...@@ -661,7 +664,44 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) ...@@ -661,7 +664,44 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
} }
if ( ret >= 0 ) { if ( ret >= 0 ) {
ret = LDAP_SUCCESS; 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) {
Debug0( LDAP_DEBUG_ANY,
"TLS: unable to get subjectAltName from peer certificate.\n" );
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) {
Debug1( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
"subjectAltName in certificate.\n",
name );
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 */ /* find the last CN */
i=0; i=0;
do { do {
...@@ -715,9 +755,10 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) ...@@ -715,9 +755,10 @@ tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in )
LDAP_FREE( ld->ld_error ); LDAP_FREE( ld->ld_error );
} }
ld->ld_error = LDAP_STRDUP( 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 ); gnutls_x509_crt_deinit( cert );
return ret; return ret;
} }
......
...@@ -662,6 +662,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) ...@@ -662,6 +662,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
{ {
tlso_session *s = (tlso_session *)sess; tlso_session *s = (tlso_session *)sess;
int i, ret = LDAP_LOCAL_ERROR; int i, ret = LDAP_LOCAL_ERROR;
int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
X509 *x; X509 *x;
const char *name; const char *name;
char *ptr; char *ptr;
...@@ -699,7 +700,8 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) ...@@ -699,7 +700,8 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) {
if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; 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); i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
if (i >= 0) { if (i >= 0) {
X509_EXTENSION *ex; X509_EXTENSION *ex;
...@@ -712,6 +714,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) ...@@ -712,6 +714,7 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
char *domain = NULL; char *domain = NULL;
GENERAL_NAME *gn; GENERAL_NAME *gn;
gotSAN = 1;
if (ntype == IS_DNS) { if (ntype == IS_DNS) {
domain = strchr(name, '.'); domain = strchr(name, '.');
if (domain) { if (domain) {
...@@ -770,6 +773,41 @@ tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) ...@@ -770,6 +773,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) {
Debug0( LDAP_DEBUG_ANY,
"TLS: unable to get subjectAltName from peer certificate.\n" );
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) {
Debug1( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
"subjectAltName in certificate.\n",
name );
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) { if (ret != LDAP_SUCCESS) {
X509_NAME *xn; X509_NAME *xn;
...@@ -832,9 +870,10 @@ no_cn: ...@@ -832,9 +870,10 @@ no_cn:
LDAP_FREE( ld->ld_error ); LDAP_FREE( ld->ld_error );
} }
ld->ld_error = LDAP_STRDUP( 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); X509_free(x);
return ret; return ret;
} }
......
Markdown is supported
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