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);