ITS#9318 add TLS_REQSAN option

Add an option to specify how subjectAlternativeNames should be
handled when validating the names in a server certificate.
This commit is contained in:
Howard Chu 2020-08-21 09:15:15 +01:00 committed by Quanah Gibson-Mount
parent 2386a11649
commit 608a822349
8 changed files with 144 additions and 4 deletions

View File

@ -815,6 +815,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

View File

@ -425,6 +425,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

View File

@ -160,6 +160,7 @@ LDAP_BEGIN_DECL
#define LDAP_OPT_X_TLS_CERT 0x6017
#define LDAP_OPT_X_TLS_KEY 0x6018
#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_HARD 1

View File

@ -119,6 +119,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},
@ -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_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;

View File

@ -285,6 +285,7 @@ struct ldapoptions {
int ldo_tls_require_cert;
int ldo_tls_impl;
int ldo_tls_crlcheck;
int ldo_tls_require_san;
char *ldo_tls_pin_hashalg;
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}

View File

@ -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 );
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 ) {
@ -715,6 +716,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;
@ -921,6 +925,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;

View File

@ -559,6 +559,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];
@ -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++ ) {
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;
@ -661,7 +664,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) {
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 */
i=0;
do {
@ -715,9 +755,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;
}

View File

@ -662,6 +662,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;
@ -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 (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;
@ -712,6 +714,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) {
@ -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) {
X509_NAME *xn;
@ -832,9 +870,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;
}