diff options
Diffstat (limited to 'Utilities/cmcurl/lib/vtls/schannel.c')
-rw-r--r-- | Utilities/cmcurl/lib/vtls/schannel.c | 515 |
1 files changed, 421 insertions, 94 deletions
diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index dfec66d..454eb79 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -51,6 +53,7 @@ #include "curl_printf.h" #include "multiif.h" #include "version_win32.h" +#include "rand.h" /* The last #include file should be: */ #include "curl_memory.h" @@ -81,8 +84,35 @@ #endif #endif -#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) -#define HAS_CLIENT_CERT_PATH +#ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM +#define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305" +#endif + +#ifndef BCRYPT_CHAIN_MODE_CCM +#define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM" +#endif + +#ifndef BCRYPT_CHAIN_MODE_GCM +#define BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM" +#endif + +#ifndef BCRYPT_AES_ALGORITHM +#define BCRYPT_AES_ALGORITHM L"AES" +#endif + +#ifndef BCRYPT_SHA256_ALGORITHM +#define BCRYPT_SHA256_ALGORITHM L"SHA256" +#endif + +#ifndef BCRYPT_SHA384_ALGORITHM +#define BCRYPT_SHA384_ALGORITHM L"SHA384" +#endif + +/* Workaround broken compilers like MinGW. + Return the number of elements in a statically sized array. +*/ +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif #ifdef HAS_CLIENT_CERT_PATH @@ -117,6 +147,10 @@ #define SP_PROT_TLS1_2_CLIENT 0x00000800 #endif +#ifndef SP_PROT_TLS1_3_CLIENT +#define SP_PROT_TLS1_3_CLIENT 0x00002000 +#endif + #ifndef SCH_USE_STRONG_CRYPTO #define SCH_USE_STRONG_CRYPTO 0x00400000 #endif @@ -147,6 +181,10 @@ #define ALG_CLASS_DHASH ALG_CLASS_HASH #endif +#ifndef PKCS12_NO_PERSIST_KEY +#define PKCS12_NO_PERSIST_KEY 0x00008000 +#endif + static Curl_recv schannel_recv; static Curl_send schannel_send; @@ -171,7 +209,7 @@ static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr, } static CURLcode -set_ssl_version_min_max(SCHANNEL_CRED *schannel_cred, struct Curl_easy *data, +set_ssl_version_min_max(DWORD *enabled_protocols, struct Curl_easy *data, struct connectdata *conn) { long ssl_version = SSL_CONN_CONFIG(version); @@ -181,23 +219,44 @@ set_ssl_version_min_max(SCHANNEL_CRED *schannel_cred, struct Curl_easy *data, switch(ssl_version_max) { case CURL_SSLVERSION_MAX_NONE: case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; + + /* Windows Server 2022 and newer (including Windows 11) support TLS 1.3 + built-in. Previous builds of Windows 10 had broken TLS 1.3 + implementations that could be enabled via registry. + */ + if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; + } + else /* Windows 10 and older */ + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; + break; } + for(; i <= (ssl_version_max >> 16); ++i) { switch(i) { case CURL_SSLVERSION_TLSv1_0: - schannel_cred->grbitEnabledProtocols |= SP_PROT_TLS1_0_CLIENT; + (*enabled_protocols) |= SP_PROT_TLS1_0_CLIENT; break; case CURL_SSLVERSION_TLSv1_1: - schannel_cred->grbitEnabledProtocols |= SP_PROT_TLS1_1_CLIENT; + (*enabled_protocols) |= SP_PROT_TLS1_1_CLIENT; break; case CURL_SSLVERSION_TLSv1_2: - schannel_cred->grbitEnabledProtocols |= SP_PROT_TLS1_2_CLIENT; + (*enabled_protocols) |= SP_PROT_TLS1_2_CLIENT; break; case CURL_SSLVERSION_TLSv1_3: - failf(data, "schannel: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; + + /* Windows Server 2022 and newer */ + if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + (*enabled_protocols) |= SP_PROT_TLS1_3_CLIENT; + break; + } + else { /* Windows 10 and older */ + failf(data, "schannel: TLS 1.3 not supported on Windows prior to 11"); + return CURLE_SSL_CONNECT_ERROR; + } } } return CURLE_OK; @@ -214,8 +273,12 @@ get_alg_id_by_name(char *name) { char tmp[LONGEST_ALG_ID] = { 0 }; char *nameEnd = strchr(name, ':'); - size_t n = nameEnd ? min((size_t)(nameEnd - name), LONGEST_ALG_ID - 1) : \ - min(strlen(name), LONGEST_ALG_ID - 1); + size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name); + + /* reject too-long alg names */ + if(n > (LONGEST_ALG_ID - 1)) + return 0; + strncpy(tmp, name, n); tmp[n] = 0; CIPHEROPTION(CALG_MD2); @@ -383,13 +446,13 @@ get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path, else if(_tcsncmp(path, TEXT("Users"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_USERS; else if(_tcsncmp(path, TEXT("CurrentUserGroupPolicy"), - store_name_len) == 0) + store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY; else if(_tcsncmp(path, TEXT("LocalMachineGroupPolicy"), - store_name_len) == 0) + store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY; else if(_tcsncmp(path, TEXT("LocalMachineEnterprise"), - store_name_len) == 0) + store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE; else return CURLE_SSL_CERTPROBLEM; @@ -419,49 +482,52 @@ schannel_acquire_credential_handle(struct Curl_easy *data, int sockindex) { struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - SCHANNEL_CRED schannel_cred; - ALG_ID algIds[NUM_CIPHERS]; + +#ifdef HAS_CLIENT_CERT_PATH PCCERT_CONTEXT client_certs[1] = { NULL }; + HCERTSTORE client_cert_store = NULL; +#endif SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; + + /* setup Schannel API options */ + DWORD flags = 0; + DWORD enabled_protocols = 0; + struct ssl_backend_data *backend = connssl->backend; DEBUGASSERT(backend); - /* setup Schannel API options */ - memset(&schannel_cred, 0, sizeof(schannel_cred)); - schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; - if(conn->ssl_config.verifypeer) { #ifdef HAS_MANUAL_VERIFY_API if(backend->use_manual_cred_validation) - schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION; + flags = SCH_CRED_MANUAL_CRED_VALIDATION; else #endif - schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION; + flags = SCH_CRED_AUTO_CRED_VALIDATION; if(SSL_SET_OPTION(no_revoke)) { - schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE; DEBUGF(infof(data, "schannel: disabled server certificate revocation " "checks")); } else if(SSL_SET_OPTION(revoke_best_effort)) { - schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN; DEBUGF(infof(data, "schannel: ignore revocation offline errors")); } else { - schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN; + flags |= SCH_CRED_REVOCATION_CHECK_CHAIN; DEBUGF(infof(data, "schannel: checking server certificate revocation")); } } else { - schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | + flags = SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE; DEBUGF(infof(data, @@ -469,15 +535,15 @@ schannel_acquire_credential_handle(struct Curl_easy *data, } if(!conn->ssl_config.verifyhost) { - schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK; + flags |= SCH_CRED_NO_SERVERNAME_CHECK; DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from " "comparing the supplied target name with the subject " "names in server certificates.")); } if(!SSL_SET_OPTION(auto_client_cert)) { - schannel_cred.dwFlags &= ~SCH_CRED_USE_DEFAULT_CREDS; - schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; + flags &= ~SCH_CRED_USE_DEFAULT_CREDS; + flags |= SCH_CRED_NO_DEFAULT_CREDS; infof(data, "schannel: disabled automatic use of client certificate"); } else @@ -491,7 +557,7 @@ schannel_acquire_credential_handle(struct Curl_easy *data, case CURL_SSLVERSION_TLSv1_2: case CURL_SSLVERSION_TLSv1_3: { - result = set_ssl_version_min_max(&schannel_cred, data, conn); + result = set_ssl_version_min_max(&enabled_protocols, data, conn); if(result != CURLE_OK) return result; break; @@ -505,16 +571,6 @@ schannel_acquire_credential_handle(struct Curl_easy *data, return CURLE_SSL_CONNECT_ERROR; } - if(SSL_CONN_CONFIG(cipher_list)) { - result = set_ssl_ciphers(&schannel_cred, SSL_CONN_CONFIG(cipher_list), - algIds); - if(CURLE_OK != result) { - failf(data, "Unable to set ciphers to passed via SSL_CONN_CONFIG"); - return result; - } - } - - #ifdef HAS_CLIENT_CERT_PATH /* client certificate */ if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { @@ -540,7 +596,7 @@ schannel_acquire_credential_handle(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; result = get_cert_location(cert_path, &cert_store_name, - &cert_store_path, &cert_thumbprint_str); + &cert_store_path, &cert_thumbprint_str); if(result && (data->set.ssl.primary.clientcert[0]!='\0')) fInCert = fopen(data->set.ssl.primary.clientcert, "rb"); @@ -555,18 +611,18 @@ schannel_acquire_credential_handle(struct Curl_easy *data, } if((fInCert || blob) && (data->set.ssl.cert_type) && - (!strcasecompare(data->set.ssl.cert_type, "P12"))) { + (!strcasecompare(data->set.ssl.cert_type, "P12"))) { failf(data, "schannel: certificate format compatibility error " - " for %s", - blob ? "(memory blob)" : data->set.ssl.primary.clientcert); + " for %s", + blob ? "(memory blob)" : data->set.ssl.primary.clientcert); curlx_unicodefree(cert_path); return CURLE_SSL_CERTPROBLEM; } if(fInCert || blob) { /* Reading a .P12 or .pfx file, like the example at bottom of - https://social.msdn.microsoft.com/Forums/windowsdesktop/ - en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 + https://social.msdn.microsoft.com/Forums/windowsdesktop/ + en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 */ CRYPT_DATA_BLOB datablob; WCHAR* pszPassword; @@ -594,7 +650,7 @@ schannel_acquire_credential_handle(struct Curl_easy *data, fclose(fInCert); if(!continue_reading) { failf(data, "schannel: Failed to read cert file %s", - data->set.ssl.primary.clientcert); + data->set.ssl.primary.clientcert); free(certdata); return CURLE_SSL_CERTPROBLEM; } @@ -610,16 +666,23 @@ schannel_acquire_credential_handle(struct Curl_easy *data, if(pszPassword) { if(pwd_len > 0) str_w_len = MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - data->set.ssl.key_passwd, (int)pwd_len, - pszPassword, (int)(pwd_len + 1)); + MB_ERR_INVALID_CHARS, + data->set.ssl.key_passwd, + (int)pwd_len, + pszPassword, (int)(pwd_len + 1)); if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) pszPassword[str_w_len] = 0; else pszPassword[0] = 0; - cert_store = PFXImportCertStore(&datablob, pszPassword, 0); + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + cert_store = PFXImportCertStore(&datablob, pszPassword, + PKCS12_NO_PERSIST_KEY); + else + cert_store = PFXImportCertStore(&datablob, pszPassword, 0); + free(pszPassword); } if(!blob) @@ -648,9 +711,6 @@ schannel_acquire_credential_handle(struct Curl_easy *data, CertCloseStore(cert_store, 0); return CURLE_SSL_CERTPROBLEM; } - - schannel_cred.cCreds = 1; - schannel_cred.paCred = client_certs; } else { cert_store = @@ -688,17 +748,13 @@ schannel_acquire_credential_handle(struct Curl_easy *data, curlx_unicodefree(cert_path); - if(client_certs[0]) { - schannel_cred.cCreds = 1; - schannel_cred.paCred = client_certs; - } - else { + if(!client_certs[0]) { /* CRYPT_E_NOT_FOUND / E_INVALIDARG */ CertCloseStore(cert_store, 0); return CURLE_SSL_CERTPROBLEM; } } - CertCloseStore(cert_store, 0); + client_cert_store = cert_store; } #else if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { @@ -713,22 +769,279 @@ schannel_acquire_credential_handle(struct Curl_easy *data, if(!backend->cred) { failf(data, "schannel: unable to allocate memory"); +#ifdef HAS_CLIENT_CERT_PATH if(client_certs[0]) CertFreeCertificateContext(client_certs[0]); + if(client_cert_store) + CertCloseStore(client_cert_store, 0); +#endif return CURLE_OUT_OF_MEMORY; } backend->cred->refcount = 1; - sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &schannel_cred, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); +#ifdef HAS_CLIENT_CERT_PATH + /* Since we did not persist the key, we need to extend the store's + * lifetime until the end of the connection + */ + backend->cred->client_cert_store = client_cert_store; +#endif + /* Windows 10, 1809 (a.k.a. Windows 10 build 17763) */ + if(curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + + char *ciphers13 = 0; + + bool disable_aes_gcm_sha384 = FALSE; + bool disable_aes_gcm_sha256 = FALSE; + bool disable_chacha_poly = FALSE; + bool disable_aes_ccm_8_sha256 = FALSE; + bool disable_aes_ccm_sha256 = FALSE; + + SCH_CREDENTIALS credentials = { 0 }; + TLS_PARAMETERS tls_parameters = { 0 }; + CRYPTO_SETTINGS crypto_settings[4] = { 0 }; + UNICODE_STRING blocked_ccm_modes[1] = { 0 }; + UNICODE_STRING blocked_gcm_modes[1] = { 0 }; + + int crypto_settings_idx = 0; + + + /* If TLS 1.3 ciphers are explicitly listed, then + * disable all the ciphers and re-enable which + * ciphers the user has provided. + */ + ciphers13 = SSL_CONN_CONFIG(cipher_list13); + if(ciphers13) { + const int remaining_ciphers = 5; + + /* detect which remaining ciphers to enable + and then disable everything else. + */ + + char *startCur = ciphers13; + int algCount = 0; + char tmp[LONGEST_ALG_ID] = { 0 }; + char *nameEnd; + size_t n; + + disable_aes_gcm_sha384 = TRUE; + disable_aes_gcm_sha256 = TRUE; + disable_chacha_poly = TRUE; + disable_aes_ccm_8_sha256 = TRUE; + disable_aes_ccm_sha256 = TRUE; + + while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) { + nameEnd = strchr(startCur, ':'); + n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); + + /* reject too-long cipher names */ + if(n > (LONGEST_ALG_ID - 1)) { + failf(data, "Cipher name too long, not checked."); + return CURLE_SSL_CIPHER; + } + + strncpy(tmp, startCur, n); + tmp[n] = 0; + + if(disable_aes_gcm_sha384 + && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) { + disable_aes_gcm_sha384 = FALSE; + } + else if(disable_aes_gcm_sha256 + && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) { + disable_aes_gcm_sha256 = FALSE; + } + else if(disable_chacha_poly + && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) { + disable_chacha_poly = FALSE; + } + else if(disable_aes_ccm_8_sha256 + && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) { + disable_aes_ccm_8_sha256 = FALSE; + } + else if(disable_aes_ccm_sha256 + && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) { + disable_aes_ccm_sha256 = FALSE; + } + else { + failf(data, "Passed in an unknown TLS 1.3 cipher."); + return CURLE_SSL_CIPHER; + } + + startCur = nameEnd; + if(startCur) + startCur++; + + algCount++; + } + } + + if(disable_aes_gcm_sha384 && disable_aes_gcm_sha256 + && disable_chacha_poly && disable_aes_ccm_8_sha256 + && disable_aes_ccm_sha256) { + failf(data, "All available TLS 1.3 ciphers were disabled."); + return CURLE_SSL_CIPHER; + } + + /* Disable TLS_AES_128_CCM_8_SHA256 and/or TLS_AES_128_CCM_SHA256 */ + if(disable_aes_ccm_8_sha256 || disable_aes_ccm_sha256) { + /* + Disallow AES_CCM algorithm. + */ + blocked_ccm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_CCM); + blocked_ccm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_CCM); + blocked_ccm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_CCM; + + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageCipher; + crypto_settings[crypto_settings_idx].rgstrChainingModes = + blocked_ccm_modes; + crypto_settings[crypto_settings_idx].cChainingModes = + ARRAYSIZE(blocked_ccm_modes); + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)BCRYPT_AES_ALGORITHM; + + /* only disabling one of the CCM modes */ + if(disable_aes_ccm_8_sha256 != disable_aes_ccm_sha256) { + if(disable_aes_ccm_8_sha256) + crypto_settings[crypto_settings_idx].dwMinBitLength = 128; + else /* disable_aes_ccm_sha256 */ + crypto_settings[crypto_settings_idx].dwMaxBitLength = 64; + } + + crypto_settings_idx++; + } + + /* Disable TLS_AES_256_GCM_SHA384 and/or TLS_AES_128_GCM_SHA256 */ + if(disable_aes_gcm_sha384 || disable_aes_gcm_sha256) { + + /* + Disallow AES_GCM algorithm + */ + blocked_gcm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_GCM); + blocked_gcm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_GCM); + blocked_gcm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_GCM; + + /* if only one is disabled, then explicitly disable the + digest cipher suite (sha384 or sha256) */ + if(disable_aes_gcm_sha384 != disable_aes_gcm_sha256) { + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageDigest; + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(disable_aes_gcm_sha384 ? + BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(disable_aes_gcm_sha384 ? + BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)(disable_aes_gcm_sha384 ? + BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); + } + else { /* Disable both AES_GCM ciphers */ + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageCipher; + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)BCRYPT_AES_ALGORITHM; + } + + crypto_settings[crypto_settings_idx].rgstrChainingModes = + blocked_gcm_modes; + crypto_settings[crypto_settings_idx].cChainingModes = 1; + + crypto_settings_idx++; + } + + /* + Disable ChaCha20-Poly1305. + */ + if(disable_chacha_poly) { + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageCipher; + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)BCRYPT_CHACHA20_POLY1305_ALGORITHM; + crypto_settings_idx++; + } + + tls_parameters.pDisabledCrypto = crypto_settings; + + /* The number of blocked suites */ + tls_parameters.cDisabledCrypto = crypto_settings_idx; + credentials.pTlsParameters = &tls_parameters; + credentials.cTlsParameters = 1; + + credentials.dwVersion = SCH_CREDENTIALS_VERSION; + credentials.dwFlags = flags | SCH_USE_STRONG_CRYPTO; + + credentials.pTlsParameters->grbitDisabledProtocols = + (DWORD)~enabled_protocols; + +#ifdef HAS_CLIENT_CERT_PATH + if(client_certs[0]) { + credentials.cCreds = 1; + credentials.paCred = client_certs; + } +#endif + + sspi_status = + s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, + SECPKG_CRED_OUTBOUND, NULL, + &credentials, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); + } + else { + /* Pre-Windows 10 1809 */ + ALG_ID algIds[NUM_CIPHERS]; + char *ciphers = SSL_CONN_CONFIG(cipher_list); + SCHANNEL_CRED schannel_cred = { 0 }; + schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; + schannel_cred.dwFlags = flags; + schannel_cred.grbitEnabledProtocols = enabled_protocols; + + if(ciphers) { + result = set_ssl_ciphers(&schannel_cred, ciphers, algIds); + if(CURLE_OK != result) { + failf(data, "Unable to set ciphers to passed via SSL_CONN_CONFIG"); + return result; + } + } + else { + schannel_cred.dwFlags = flags | SCH_USE_STRONG_CRYPTO; + } + +#ifdef HAS_CLIENT_CERT_PATH + if(client_certs[0]) { + schannel_cred.cCreds = 1; + schannel_cred.paCred = client_certs; + } +#endif + + sspi_status = + s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, + SECPKG_CRED_OUTBOUND, NULL, + &schannel_cred, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); + } + +#ifdef HAS_CLIENT_CERT_PATH if(client_certs[0]) CertFreeCertificateContext(client_certs[0]); +#endif if(sspi_status != SEC_E_OK) { char buffer[STRERROR_LEN]; @@ -1016,6 +1329,7 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn, backend->recv_unrecoverable_err = CURLE_OK; backend->recv_sspi_close_notify = false; backend->recv_connection_closed = false; + backend->recv_renegotiating = false; backend->encdata_is_incomplete = false; /* continue to second handshake step */ @@ -1415,6 +1729,7 @@ schannel_connect_step3(struct Curl_easy *data, struct connectdata *conn, if(alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { + unsigned char alpn = 0; infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, alpn_result.ProtocolIdSize, alpn_result.ProtocolId); @@ -1422,20 +1737,33 @@ schannel_connect_step3(struct Curl_easy *data, struct connectdata *conn, #ifdef USE_HTTP2 if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH && !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_2; + alpn = CURL_HTTP_VERSION_2; } else #endif if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH && !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId, ALPN_HTTP_1_1_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_1_1; + alpn = CURL_HTTP_VERSION_1_1; } + if(backend->recv_renegotiating) { + if(alpn != conn->alpn) { + failf(data, "schannel: server selected an ALPN protocol too late"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else + conn->alpn = alpn; + } + else { + if(!backend->recv_renegotiating) + infof(data, VTLS_INFOF_NO_ALPN); + } + + if(!backend->recv_renegotiating) { + Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? + BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); } - else - infof(data, VTLS_INFOF_NO_ALPN); - Curl_multiuse_state(data, conn->negnpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); } #endif @@ -1607,8 +1935,15 @@ schannel_connect_common(struct Curl_easy *data, struct connectdata *conn, if(ssl_connect_done == connssl->connecting_state) { connssl->state = ssl_connection_complete; - conn->recv[sockindex] = schannel_recv; - conn->send[sockindex] = schannel_send; + if(!connssl->backend->recv_renegotiating) { + /* On renegotiation, we don't want to reset the existing recv/send + * function pointers. They will have been set after the initial TLS + * handshake was completed. If they were subsequently modified, as + * is the case with HTTP/2, we don't want to override that change. + */ + conn->recv[sockindex] = schannel_recv; + conn->send[sockindex] = schannel_send; + } #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS /* When SSPI is used in combination with Schannel @@ -1990,17 +2325,14 @@ schannel_recv(struct Curl_easy *data, int sockindex, infof(data, "schannel: can't renegotiate, an error is pending"); goto cleanup; } - if(backend->encdata_offset) { - *err = CURLE_RECV_ERROR; - infof(data, "schannel: can't renegotiate, " - "encrypted data available"); - goto cleanup; - } + /* begin renegotiation */ infof(data, "schannel: renegotiating SSL/TLS connection"); connssl->state = ssl_connection_negotiating; connssl->connecting_state = ssl_connect_2_writing; + backend->recv_renegotiating = true; *err = schannel_connect_common(data, conn, sockindex, FALSE, &done); + backend->recv_renegotiating = false; if(*err) { infof(data, "schannel: renegotiation failed"); goto cleanup; @@ -2154,6 +2486,12 @@ static void schannel_session_free(void *ptr) if(cred->refcount == 0) { s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); curlx_unicodefree(cred->sni_hostname); +#ifdef HAS_CLIENT_CERT_PATH + if(cred->client_cert_store) { + CertCloseStore(cred->client_cert_store, 0); + cred->client_cert_store = NULL; + } +#endif Curl_safefree(cred); } } @@ -2296,21 +2634,9 @@ static size_t schannel_version(char *buffer, size_t size) static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, unsigned char *entropy, size_t length) { - HCRYPTPROV hCryptProv = 0; - (void)data; - if(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) - return CURLE_FAILED_INIT; - - if(!CryptGenRandom(hCryptProv, (DWORD)length, entropy)) { - CryptReleaseContext(hCryptProv, 0UL); - return CURLE_FAILED_INIT; - } - - CryptReleaseContext(hCryptProv, 0UL); - return CURLE_OK; + return Curl_win32_random(entropy, length); } static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, @@ -2457,7 +2783,8 @@ const struct Curl_ssl Curl_ssl_schannel = { #ifdef HAS_MANUAL_VERIFY_API SSLSUPP_CAINFO_BLOB | #endif - SSLSUPP_PINNEDPUBKEY, + SSLSUPP_PINNEDPUBKEY | + SSLSUPP_TLS13_CIPHERSUITES, sizeof(struct ssl_backend_data), |