From: "Hiroshi Miura(@osmf)" Subject: [PATCH(1/1)] wininet: TLS fallback mechanism Message-Id: <508803BB.6020209@osmf.jp> Date: Thu, 25 Oct 2012 00:05:31 +0900 OpenSSL 1.0.x now support TLSv1.1 and TLSv1.2. Sometimes TLSv1.1/1.2 negotiation fails because of combination of server/client ssl library versions. It fixes the error connecting Evernote server with evernote client. http://bugs.winehq.org/show_bug.cgi?id=30598 It has a mechanism to fallback to SSLv3/TLSv1 when fails with TLSv1.1/1.2. It has also mechanism enable/disable TLSv1.1/1.2 by registry entry under SYSTEM/CurrentControlSet/Control/SecurityProvider/SCHANNEL/Protocols/ where is as same place as Windows7. TLSv1.1/1.2 is enabled when the registry entry does not exist. Signed-off-by: Hiroshi Miura --- dlls/wininet/netconnection.c | 163 ++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 39 deletions(-) diff --git a/dlls/wininet/netconnection.c b/dlls/wininet/netconnection.c index c944ff3..dd3248a 100644 --- a/dlls/wininet/netconnection.c +++ b/dlls/wininet/netconnection.c @@ -124,8 +124,10 @@ MAKE_FUNCPTR(SSL_load_error_strings); MAKE_FUNCPTR(SSLv23_method); MAKE_FUNCPTR(SSL_CTX_free); MAKE_FUNCPTR(SSL_CTX_new); +MAKE_FUNCPTR(SSL_CTX_ctrl); MAKE_FUNCPTR(SSL_new); MAKE_FUNCPTR(SSL_free); +MAKE_FUNCPTR(SSL_ctrl); MAKE_FUNCPTR(SSL_set_fd); MAKE_FUNCPTR(SSL_connect); MAKE_FUNCPTR(SSL_shutdown); @@ -446,6 +448,48 @@ static int netconn_secure_verify(int preverify_ok, X509_STORE_CTX *ctx) return ret; } +static long get_tls_option(void) { + long tls_option; + DWORD type, val, size; + HKEY hkey,tls12_client,tls11_client; + LONG res; + const WCHAR Schannel_Prot[] = { /* SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\SCANNEL\\Protocols */ + 'S','Y','S','T','E','M','\\', + 'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\', + 'C','o','n','t','r','o','l','\\', + 'S','e','c','u','r','i','t','y','P','r','o','v','i','d','e','r','s','\\', + 'S','C','H','A','N','N','E','L','\\', + 'P','r','o','t','o','c','o','l','s',0 }; + const WCHAR TLS12_Client[] = {'T','L','S',' ','1','.','2','\\','C','l','i','e','n','t',0}; + const WCHAR TLS11_Client[] = {'T','L','S',' ','1','.','1','\\','C','l','i','e','n','t',0}; + const WCHAR DisabledByDefault[] = {'D','i','s','a','b','l','e','d','B','y','D','e','f','a','u','l','t',0}; + + tls_option = SSL_OP_NO_SSLv2; /* disable SSLv2 for security reason, secur32/Schannel(GnuTLS) don't support it */ + res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + Schannel_Prot, + 0, KEY_READ, &hkey); + if (res != ERROR_SUCCESS) { /* enabled TLSv1.1/1.2 when no registry entry */ + return tls_option; + } + if (RegOpenKeyExW(hkey, TLS12_Client, 0, KEY_READ, &tls12_client) == ERROR_SUCCESS) { + size = sizeof(DWORD); + if (RegQueryValueExW(tls12_client, DisabledByDefault, NULL, &type, (LPBYTE) &val, &size) == ERROR_SUCCESS + && type == REG_DWORD) { + tls_option |= val?SSL_OP_NO_TLSv1_2:0; + } + RegCloseKey(tls12_client); + } + if (RegOpenKeyExW(hkey, TLS11_Client, 0, KEY_READ, &tls11_client) == ERROR_SUCCESS) { + size = sizeof(DWORD); + if (RegQueryValueExW(tls11_client, DisabledByDefault, NULL, &type, (LPBYTE) &val, &size) == ERROR_SUCCESS + && type == REG_DWORD) { + tls_option |= val?SSL_OP_NO_TLSv1_1:0; + } + RegCloseKey(tls11_client); + } + RegCloseKey(hkey); + return tls_option; +} #endif static CRITICAL_SECTION init_ssl_cs; @@ -491,8 +535,10 @@ static DWORD init_openssl(void) DYNSSL(SSLv23_method); DYNSSL(SSL_CTX_free); DYNSSL(SSL_CTX_new); + DYNSSL(SSL_CTX_ctrl); DYNSSL(SSL_new); DYNSSL(SSL_free); + DYNSSL(SSL_ctrl); DYNSSL(SSL_set_fd); DYNSSL(SSL_connect); DYNSSL(SSL_shutdown); @@ -534,12 +580,18 @@ static DWORD init_openssl(void) DYNCRYPTO(sk_value); #undef DYNCRYPTO +#define pSSL_CTX_set_options(ctx,op) \ + pSSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL) +#define pSSL_set_options(ssl,op) \ + pSSL_ctrl((ssl),SSL_CTRL_OPTIONS,(op),NULL) + pSSL_library_init(); pSSL_load_error_strings(); pBIO_new_fp(stderr, BIO_NOCLOSE); /* FIXME: should use winedebug stuff */ meth = pSSLv23_method(); ctx = pSSL_CTX_new(meth); + pSSL_CTX_set_options(ctx, get_tls_option()); if(!pSSL_CTX_set_default_verify_paths(ctx)) { ERR("SSL_CTX_set_default_verify_paths failed: %s\n", pERR_error_string(pERR_get_error(), 0)); @@ -580,33 +632,10 @@ static DWORD init_openssl(void) #endif } -DWORD create_netconn(BOOL useSSL, server_t *server, DWORD security_flags, BOOL mask_errors, DWORD timeout, netconn_t **ret) +static DWORD create_netconn_socket(server_t *server, netconn_t *netconn, DWORD timeout) { - netconn_t *netconn; int result, flag; - if(useSSL) { - DWORD res; - - TRACE("using SSL connection\n"); - - EnterCriticalSection(&init_ssl_cs); - res = init_openssl(); - LeaveCriticalSection(&init_ssl_cs); - if(res != ERROR_SUCCESS) - return res; - } - - netconn = heap_alloc_zero(sizeof(*netconn)); - if(!netconn) - return ERROR_OUTOFMEMORY; - - netconn->useSSL = useSSL; - netconn->socketFD = -1; - netconn->security_flags = security_flags | server->security_flags; - netconn->mask_errors = mask_errors; - list_init(&netconn->pool_entry); - assert(server->addr_len); result = netconn->socketFD = socket(server->addr.ss_family, SOCK_STREAM, 0); if(result != -1) { @@ -656,11 +685,42 @@ DWORD create_netconn(BOOL useSSL, server_t *server, DWORD security_flags, BOOL m WARN("setsockopt(TCP_NODELAY) failed\n"); #endif + return ERROR_SUCCESS; +} + +DWORD create_netconn(BOOL useSSL, server_t *server, DWORD security_flags, BOOL mask_errors, DWORD timeout, netconn_t **ret) +{ + netconn_t *netconn; + int result; + + if(useSSL) { + DWORD res; + + TRACE("using SSL connection\n"); + + EnterCriticalSection(&init_ssl_cs); + res = init_openssl(); + LeaveCriticalSection(&init_ssl_cs); + if(res != ERROR_SUCCESS) + return res; + } + + netconn = heap_alloc_zero(sizeof(*netconn)); + if(!netconn) + return ERROR_OUTOFMEMORY; + + netconn->useSSL = useSSL; + netconn->socketFD = -1; + netconn->security_flags = security_flags | server->security_flags; + netconn->mask_errors = mask_errors; + list_init(&netconn->pool_entry); + + result = create_netconn_socket(server, netconn, timeout); server_addref(server); netconn->server = server; *ret = netconn; - return ERROR_SUCCESS; + return result; } void free_netconn(netconn_t *netconn) @@ -772,24 +832,13 @@ int sock_get_error( int err ) return err; } -/****************************************************************************** - * NETCON_secure_connect - * Initiates a secure connection over an existing plaintext connection. - */ -DWORD NETCON_secure_connect(netconn_t *connection) -{ - DWORD res = ERROR_NOT_SUPPORTED; #ifdef SONAME_LIBSSL +static DWORD netcon_secure_connect_setup(netconn_t *connection, long tls_option) +{ void *ssl_s; + DWORD res; int bits; - /* can't connect if we are already connected */ - if (connection->ssl_s) - { - ERR("already connected\n"); - return ERROR_INTERNET_CANNOT_CONNECT; - } - ssl_s = pSSL_new(ctx); if (!ssl_s) { @@ -798,6 +847,7 @@ DWORD NETCON_secure_connect(netconn_t *connection) return ERROR_OUTOFMEMORY; } + pSSL_set_options(ssl_s, tls_option); if (!pSSL_set_fd(ssl_s, connection->socketFD)) { ERR("SSL_set_fd failed: %s\n", @@ -843,6 +893,41 @@ fail: pSSL_shutdown(ssl_s); pSSL_free(ssl_s); } + return res; +} +#endif + +/****************************************************************************** + * NETCON_secure_connect + * Initiates a secure connection over an existing plaintext connection. + */ +DWORD NETCON_secure_connect(netconn_t *connection) +{ + DWORD res = ERROR_NOT_SUPPORTED; +#ifdef SONAME_LIBSSL + /* can't connect if we are already connected */ + if (connection->ssl_s) + { + ERR("already connected\n"); + return ERROR_INTERNET_CANNOT_CONNECT; + } + + /* connect with given TLS options */ + res = netcon_secure_connect_setup(connection, get_tls_option()); + if (res == ERROR_SUCCESS) + return res; + + /* FIXME: when got version alert and FIN from server */ + /* fallback to connect without TLSv1.1/TLSv1.2 */ + if (res == ERROR_INTERNET_SECURITY_CHANNEL_ERROR) + { + closesocket(connection->socketFD); + pSSL_CTX_set_options(ctx,SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2); + res = create_netconn_socket(connection->server, connection, 500); + if (res != ERROR_SUCCESS) + return res; + res = netcon_secure_connect_setup(connection, get_tls_option()|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2); + } #endif return res; } -- 1.7.9.5