vtls: pass on the right SNI name

The TLS backends convert the host name to SNI name and need to use that.
This involves cutting off any trailing dot and lowercasing.

Co-authored-by: Jay Satiro
Closes #8320
This commit is contained in:
Daniel Stenberg 2022-01-22 23:44:00 +01:00
parent b27ad8e1d3
commit 2218c3a57e
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
13 changed files with 125 additions and 78 deletions

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2019 - 2021, Michael Forney, <mforney@mforney.org>
* Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -465,6 +465,14 @@ static CURLcode bearssl_connect_step1(struct Curl_easy *data,
}
hostname = NULL;
}
else {
char *snihost = Curl_ssl_snihost(data, hostname, NULL);
if(!snihost) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
hostname = snihost;
}
if(!br_ssl_client_reset(&backend->ctx, hostname, 0))
return CURLE_FAILED_INIT;

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -830,8 +830,13 @@ static CURLcode gskit_connect_step1(struct Curl_easy *data,
/* Process SNI. Ignore if not supported (on OS400 < V7R1). */
if(sni) {
char *snihost = Curl_ssl_snihost(data, sni, NULL);
if(!snihost) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
result = set_buffer(data, BACKEND->handle,
GSK_SSL_EXTN_SERVERNAME_REQUEST, sni, TRUE);
GSK_SSL_EXTN_SERVERNAME_REQUEST, snihost, TRUE);
if(result == CURLE_UNSUPPORTED_PROTOCOL)
result = CURLE_OK;
}

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -542,11 +542,15 @@ gtls_connect_step1(struct Curl_easy *data,
#ifdef ENABLE_IPV6
(0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
#endif
sni &&
(gnutls_server_name_set(session, GNUTLS_NAME_DNS, hostname,
strlen(hostname)) < 0))
infof(data, "WARNING: failed to configure server name indication (SNI) "
"TLS extension");
sni) {
size_t snilen;
char *snihost = Curl_ssl_snihost(data, hostname, &snilen);
if(!snihost || gnutls_server_name_set(session, GNUTLS_NAME_DNS, snihost,
snilen) < 0) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
}
/* Use default priorities */
rc = gnutls_set_default_priority(session);

View File

@ -561,12 +561,15 @@ mbed_connect_step1(struct Curl_easy *data, struct connectdata *conn,
mbedtls_ssl_conf_own_cert(&backend->config,
&backend->clicert, &backend->pk);
}
if(mbedtls_ssl_set_hostname(&backend->ssl, hostname)) {
/* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks *and*
the name to set in the SNI extension. So even if curl connects to a
host specified as an IP address, this function must be used. */
failf(data, "couldn't set hostname in mbedTLS");
return CURLE_SSL_CONNECT_ERROR;
{
char *snihost = Curl_ssl_snihost(data, hostname, NULL);
if(!snihost || mbedtls_ssl_set_hostname(&backend->ssl, snihost)) {
/* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and
the name to set in the SNI extension. So even if curl connects to a
host specified as an IP address, this function must be used. */
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
}
#ifdef HAS_ALPN

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -1865,7 +1865,6 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
CURLcode result;
bool second_layer = FALSE;
SSLVersionRange sslver_supported;
SSLVersionRange sslver = {
SSL_LIBRARY_VERSION_TLS_1_0, /* min */
#ifdef SSL_LIBRARY_VERSION_TLS_1_3
@ -1878,6 +1877,11 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
SSL_LIBRARY_VERSION_TLS_1_0
#endif
};
char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL);
if(!snihost) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
backend->data = data;
@ -2140,11 +2144,11 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
goto error;
/* propagate hostname to the TLS layer */
if(SSL_SetURL(backend->handle, SSL_HOST_NAME()) != SECSuccess)
if(SSL_SetURL(backend->handle, snihost) != SECSuccess)
goto error;
/* prevent NSS from re-using the session for a different hostname */
if(SSL_SetSockPeerID(backend->handle, SSL_HOST_NAME()) != SECSuccess)
if(SSL_SetSockPeerID(backend->handle, snihost) != SECSuccess)
goto error;
return CURLE_OK;

View File

@ -3244,21 +3244,11 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
(0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
#endif
sni) {
size_t nlen = strlen(hostname);
if((long)nlen >= data->set.buffer_size)
/* this is seriously messed up */
char *snihost = Curl_ssl_snihost(data, hostname, NULL);
if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) {
failf(data, "Failed set SNI");
return CURLE_SSL_CONNECT_ERROR;
/* RFC 6066 section 3 says the SNI field is case insensitive, but browsers
send the data lowercase and subsequently there are now numerous servers
out there that don't work unless the name is lowercased */
Curl_strntolower(data->state.buffer, hostname, nlen);
data->state.buffer[nlen] = 0;
DEBUGASSERT(nlen);
DEBUGASSERT(data->state.buffer[nlen-1] != '.');
if(!SSL_set_tlsext_host_name(backend->handle, data->state.buffer))
infof(data, "WARNING: failed to configure server name indication (SNI) "
"TLS extension");
}
}
#endif

View File

@ -368,7 +368,14 @@ cr_init_backend(struct Curl_easy *data, struct connectdata *conn,
backend->config = rustls_client_config_builder_build(config_builder);
DEBUGASSERT(rconn == NULL);
result = rustls_client_connection_new(backend->config, hostname, &rconn);
{
char *snihost = Curl_ssl_snihost(data, hostname, NULL);
if(!snihost) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
result = rustls_client_connection_new(backend->config, snihost, &rconn);
}
if(result != RUSTLS_RESULT_OK) {
rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen);
failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf);

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
* Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
*
@ -765,7 +765,6 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
#ifdef ENABLE_IPV6
struct in6_addr addr6;
#endif
TCHAR *host_name;
CURLcode result;
char * const hostname = SSL_HOST_NAME();
struct ssl_backend_data *backend = connssl->backend;
@ -846,10 +845,21 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
}
if(!backend->cred) {
char *snihost;
result = schannel_acquire_credential_handle(data, conn, sockindex);
if(result != CURLE_OK) {
return result;
}
/* A hostname associated with the credential is needed by
InitializeSecurityContext for SNI and other reasons. */
snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL);
if(!snihost) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost);
if(!backend->cred->sni_hostname)
return CURLE_OUT_OF_MEMORY;
}
/* Warn if SNI is disabled due to use of an IP address */
@ -936,10 +946,6 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
return CURLE_OUT_OF_MEMORY;
}
host_name = curlx_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
/* Schannel InitializeSecurityContext:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx
@ -948,13 +954,12 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
us problems with inbuf regardless. https://github.com/curl/curl/issues/983
*/
sspi_status = s_pSecFn->InitializeSecurityContext(
&backend->cred->cred_handle, NULL, host_name, backend->req_flags, 0, 0,
&backend->cred->cred_handle, NULL, backend->cred->sni_hostname,
backend->req_flags, 0, 0,
(backend->use_alpn ? &inbuf_desc : NULL),
0, &backend->ctxt->ctxt_handle,
&outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp);
curlx_unicodefree(host_name);
if(sspi_status != SEC_I_CONTINUE_NEEDED) {
char buffer[STRERROR_LEN];
Curl_safefree(backend->ctxt);
@ -1027,16 +1032,11 @@ schannel_connect_step2(struct Curl_easy *data, struct connectdata *conn,
SECURITY_STATUS sspi_status = SEC_E_OK;
CURLcode result;
bool doread;
char * const hostname = SSL_HOST_NAME();
const char *pubkey_ptr;
struct ssl_backend_data *backend = connssl->backend;
doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
DEBUGF(infof(data,
"schannel: SSL/TLS connection with %s port %hu (step 2/3)",
hostname, conn->remote_port));
if(!backend->cred || !backend->ctxt)
return CURLE_SSL_CONNECT_ERROR;
@ -1083,7 +1083,6 @@ schannel_connect_step2(struct Curl_easy *data, struct connectdata *conn,
}
for(;;) {
TCHAR *host_name;
if(doread) {
/* read encrypted handshake data from socket */
result = Curl_read_plain(conn->sock[sockindex],
@ -1136,17 +1135,12 @@ schannel_connect_step2(struct Curl_easy *data, struct connectdata *conn,
memcpy(inbuf[0].pvBuffer, backend->encdata_buffer,
backend->encdata_offset);
host_name = curlx_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
sspi_status = s_pSecFn->InitializeSecurityContext(
&backend->cred->cred_handle, &backend->ctxt->ctxt_handle,
host_name, backend->req_flags, 0, 0, &inbuf_desc, 0, NULL,
backend->cred->sni_hostname, backend->req_flags,
0, 0, &inbuf_desc, 0, NULL,
&outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp);
curlx_unicodefree(host_name);
/* free buffer for received handshake data */
Curl_safefree(inbuf[0].pvBuffer);
@ -2138,6 +2132,7 @@ static void schannel_session_free(void *ptr)
cred->refcount--;
if(cred->refcount == 0) {
s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
curlx_unicodefree(cred->sni_hostname);
Curl_safefree(cred);
}
}
@ -2170,7 +2165,6 @@ static int schannel_shutdown(struct Curl_easy *data, struct connectdata *conn,
SecBuffer outbuf;
SecBufferDesc outbuf_desc;
CURLcode result;
TCHAR *host_name;
DWORD dwshut = SCHANNEL_SHUTDOWN;
InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
@ -2185,10 +2179,6 @@ static int schannel_shutdown(struct Curl_easy *data, struct connectdata *conn,
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
}
host_name = curlx_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
/* setup output buffer */
InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
@ -2196,7 +2186,7 @@ static int schannel_shutdown(struct Curl_easy *data, struct connectdata *conn,
sspi_status = s_pSecFn->InitializeSecurityContext(
&backend->cred->cred_handle,
&backend->ctxt->ctxt_handle,
host_name,
backend->cred->sni_hostname,
backend->req_flags,
0,
0,
@ -2207,8 +2197,6 @@ static int schannel_shutdown(struct Curl_easy *data, struct connectdata *conn,
&backend->ret_flags,
&backend->ctxt->time_stamp);
curlx_unicodefree(host_name);
if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
/* send close message which is in output buffer */
ssize_t written;

View File

@ -8,7 +8,7 @@
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al.
* Copyright (C) 2012 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -76,6 +76,7 @@ CURLcode Curl_verify_certificate(struct Curl_easy *data,
struct Curl_schannel_cred {
CredHandle cred_handle;
TimeStamp time_stamp;
TCHAR *sni_hostname;
int refcount;
};

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2012 - 2017, Nick Zitzmann, <nickzman@gmail.com>.
*
* This software is licensed as described in the file COPYING, which
@ -2028,8 +2028,13 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
* Both hostname check and SNI require SSLSetPeerDomainName().
* Also: the verifyhost setting influences SNI usage */
if(conn->ssl_config.verifyhost) {
err = SSLSetPeerDomainName(backend->ssl_ctx, hostname,
strlen(hostname));
size_t snilen;
char *snihost = Curl_ssl_snihost(data, hostname, &snilen);
if(!snihost) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
err = SSLSetPeerDomainName(backend->ssl_ctx, snihost, snilen);
if(err != noErr) {
infof(data, "WARNING: SSL: SSLSetPeerDomainName() failed: OSStatus %d",

View File

@ -871,6 +871,32 @@ CURLcode Curl_ssl_random(struct Curl_easy *data,
return Curl_ssl->random(data, entropy, length);
}
/*
* Curl_ssl_snihost() converts the input host name to a suitable SNI name put
* in data->state.buffer. Returns a pointer to the name (or NULL if a problem)
* and stores the new length in 'olen'.
*
* SNI fields must not have any trailing dot and while RFC 6066 section 3 says
* the SNI field is case insensitive, browsers always send the data lowercase
* and subsequently there are numerous servers out there that don't work
* unless the name is lowercased.
*/
char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen)
{
size_t len = strlen(host);
if(len && (host[len-1] == '.'))
len--;
if((long)len >= data->set.buffer_size)
return NULL;
Curl_strntolower(data->state.buffer, host, len);
data->state.buffer[len] = 0;
if(olen)
*olen = len;
return data->state.buffer;
}
/*
* Public key pem to der conversion
*/

View File

@ -172,6 +172,7 @@ bool Curl_ssl_tls13_ciphersuites(void);
data->set.str[STRING_SSL_PINNEDPUBLICKEY]
#endif
char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen);
bool Curl_ssl_config_matches(struct ssl_primary_config *data,
struct ssl_primary_config *needle);
bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source,

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -462,12 +462,17 @@ wolfssl_connect_step1(struct Curl_easy *data, struct connectdata *conn,
if((hostname_len < USHRT_MAX) &&
(0 == Curl_inet_pton(AF_INET, hostname, &addr4)) &&
#ifdef ENABLE_IPV6
(0 == Curl_inet_pton(AF_INET6, hostname, &addr6)) &&
(0 == Curl_inet_pton(AF_INET6, hostname, &addr6))
#endif
(wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, hostname,
(unsigned short)hostname_len) != 1)) {
infof(data, "WARNING: failed to configure server name indication (SNI) "
"TLS extension");
) {
size_t snilen;
char *snihost = Curl_ssl_snihost(data, hostname, &snilen);
if(!snihost ||
wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, snihost,
(unsigned short)snilen) != 1) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
}
}
#endif
@ -590,7 +595,6 @@ wolfssl_connect_step2(struct Curl_easy *data, struct connectdata *conn,
int ret = -1;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
struct ssl_backend_data *backend = connssl->backend;
const char * const hostname = SSL_HOST_NAME();
const char * const dispname = SSL_HOST_DISPNAME();
const char * const pinnedpubkey = SSL_PINNED_PUB_KEY();
@ -601,9 +605,10 @@ wolfssl_connect_step2(struct Curl_easy *data, struct connectdata *conn,
/* Enable RFC2818 checks */
if(SSL_CONN_CONFIG(verifyhost)) {
ret = wolfSSL_check_domain_name(backend->handle, hostname);
if(ret == SSL_FAILURE)
return CURLE_OUT_OF_MEMORY;
char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL);
if(!snihost ||
(wolfSSL_check_domain_name(backend->handle, snihost) == SSL_FAILURE))
return CURLE_SSL_CONNECT_ERROR;
}
ret = SSL_connect(backend->handle);