From 2218c3a57e86c4ef68c5fa1e2f29e4a9a915d667 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 22 Jan 2022 23:44:00 +0100 Subject: [PATCH] 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 --- lib/vtls/bearssl.c | 10 ++++++++- lib/vtls/gskit.c | 9 +++++++-- lib/vtls/gtls.c | 16 +++++++++------ lib/vtls/mbedtls.c | 15 ++++++++------ lib/vtls/nss.c | 12 +++++++---- lib/vtls/openssl.c | 18 ++++------------- lib/vtls/rustls.c | 9 ++++++++- lib/vtls/schannel.c | 48 +++++++++++++++++--------------------------- lib/vtls/schannel.h | 3 ++- lib/vtls/sectransp.c | 11 +++++++--- lib/vtls/vtls.c | 26 ++++++++++++++++++++++++ lib/vtls/vtls.h | 1 + lib/vtls/wolfssl.c | 25 ++++++++++++++--------- 13 files changed, 125 insertions(+), 78 deletions(-) diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c index 9b772d064d..bac6b39314 100644 --- a/lib/vtls/bearssl.c +++ b/lib/vtls/bearssl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2021, Michael Forney, + * Copyright (C) 2019 - 2022, Michael Forney, * * 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; diff --git a/lib/vtls/gskit.c b/lib/vtls/gskit.c index e451f6aebe..223ca61101 100644 --- a/lib/vtls/gskit.c +++ b/lib/vtls/gskit.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2022, Daniel Stenberg, , 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; } diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 18864aa4b2..f9ef4d12b4 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2022, Daniel Stenberg, , 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); diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 4dd7efa2b2..dc08032dd4 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -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 diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index 2b44f05126..9e301437b6 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2022, Daniel Stenberg, , 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; diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 5e8a657165..d6f814bc80 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -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 diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 51230b1967..1c4cb19104 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -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); diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 0a8e60610d..983ed540d4 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 2012 - 2022, Daniel Stenberg, , et al. * Copyright (C) 2012 - 2016, Marc Hoersken, * Copyright (C) 2012, Mark Salisbury, * @@ -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; diff --git a/lib/vtls/schannel.h b/lib/vtls/schannel.h index 77853aa30f..c412ea4d02 100644 --- a/lib/vtls/schannel.h +++ b/lib/vtls/schannel.h @@ -8,7 +8,7 @@ * \___|\___/|_| \_\_____| * * Copyright (C) 2012, Marc Hoersken, , et al. - * Copyright (C) 2012 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 2012 - 2022, Daniel Stenberg, , 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; }; diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c index f7a20b20b1..059756eb69 100644 --- a/lib/vtls/sectransp.c +++ b/lib/vtls/sectransp.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 2012 - 2022, Daniel Stenberg, , et al. * Copyright (C) 2012 - 2017, Nick Zitzmann, . * * 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", diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index b606c622ba..b3bd87303f 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -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 */ diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index fe5fc20e3c..af3b8d3c94 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -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, diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 8c5b9157b8..314279dd13 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2022, Daniel Stenberg, , 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); -- 2.47.3