@@ -225,60 +225,93 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
225225 VerifyState ( State . Created ) ;
226226 m_state = State . Connecting ;
227227 }
228- var connected = false ;
229- if ( cs . ConnectionType == ConnectionType . Tcp )
230- connected = await OpenTcpSocketAsync ( cs , loadBalancer , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
231- else if ( cs . ConnectionType == ConnectionType . Unix )
232- connected = await OpenUnixSocketAsync ( cs , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
233- if ( ! connected )
228+
229+ // TLS negotiation should automatically fall back to the best version supported by client and server. However,
230+ // Windows Schannel clients will fail to connect to a yaSSL-based MySQL Server if TLS 1.2 is requested and
231+ // have to use only TLS 1.1: https://github.com/mysql-net/MySqlConnector/pull/101
232+ // In order to use the best protocol possible (i.e., not always default to TLS 1.1), we try the OS-default protocol
233+ // (which is SslProtocols.None; see https://docs.microsoft.com/en-us/dotnet/framework/network-programming/tls),
234+ // then fall back to SslProtocols.Tls11 if that fails and it's possible that the cause is a yaSSL server.
235+ bool shouldRetrySsl ;
236+ var sslProtocols = Pool ? . SslProtocols ?? Utility . GetDefaultSslProtocols ( ) ;
237+ PayloadData payload ;
238+ InitialHandshakePayload initialHandshake ;
239+ do
234240 {
235- lock ( m_lock )
236- m_state = State . Failed ;
237- Log . Error ( "{0} connecting failed" , m_logArguments ) ;
238- throw new MySqlException ( "Unable to connect to any of the specified MySQL hosts." ) ;
239- }
241+ shouldRetrySsl = ( sslProtocols == SslProtocols . None || ( sslProtocols & SslProtocols . Tls12 ) == SslProtocols . Tls12 ) && Utility . IsWindows ( ) ;
242+
243+ var connected = false ;
244+ if ( cs . ConnectionType == ConnectionType . Tcp )
245+ connected = await OpenTcpSocketAsync ( cs , loadBalancer , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
246+ else if ( cs . ConnectionType == ConnectionType . Unix )
247+ connected = await OpenUnixSocketAsync ( cs , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
248+ if ( ! connected )
249+ {
250+ lock ( m_lock )
251+ m_state = State . Failed ;
252+ Log . Error ( "{0} connecting failed" , m_logArguments ) ;
253+ throw new MySqlException ( "Unable to connect to any of the specified MySQL hosts." ) ;
254+ }
240255
241- var byteHandler = new SocketByteHandler ( m_socket ) ;
242- m_payloadHandler = new StandardPayloadHandler ( byteHandler ) ;
256+ var byteHandler = new SocketByteHandler ( m_socket ) ;
257+ m_payloadHandler = new StandardPayloadHandler ( byteHandler ) ;
243258
244- var payload = await ReceiveAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
245- var initialHandshake = InitialHandshakePayload . Create ( payload ) ;
259+ payload = await ReceiveAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
260+ initialHandshake = InitialHandshakePayload . Create ( payload ) ;
246261
247- // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use
248- string authPluginName ;
249- if ( ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . PluginAuth ) != 0 )
250- authPluginName = initialHandshake . AuthPluginName ;
251- else
252- authPluginName = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SecureConnection ) == 0 ? "mysql_old_password" : "mysql_native_password" ;
253- m_logArguments [ 1 ] = authPluginName ;
254- Log . Debug ( "{0} server sent auth_plugin_name '{1}'" , m_logArguments ) ;
255- if ( authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password" )
256- {
257- Log . Error ( "{0} unsupported authentication method '{1}'" , m_logArguments ) ;
258- throw new NotSupportedException ( "Authentication method '{0}' is not supported." . FormatInvariant ( initialHandshake . AuthPluginName ) ) ;
259- }
262+ // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use
263+ string authPluginName ;
264+ if ( ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . PluginAuth ) != 0 )
265+ authPluginName = initialHandshake . AuthPluginName ;
266+ else
267+ authPluginName = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SecureConnection ) == 0 ? "mysql_old_password" : "mysql_native_password" ;
268+ m_logArguments [ 1 ] = authPluginName ;
269+ Log . Debug ( "{0} server sent auth_plugin_name '{1}'" , m_logArguments ) ;
270+ if ( authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password" )
271+ {
272+ Log . Error ( "{0} unsupported authentication method '{1}'" , m_logArguments ) ;
273+ throw new NotSupportedException ( "Authentication method '{0}' is not supported." . FormatInvariant ( initialHandshake . AuthPluginName ) ) ;
274+ }
260275
261- ServerVersion = new ServerVersion ( Encoding . ASCII . GetString ( initialHandshake . ServerVersion ) ) ;
262- ConnectionId = initialHandshake . ConnectionId ;
263- AuthPluginData = initialHandshake . AuthPluginData ;
264- m_useCompression = cs . UseCompression && ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Compress ) != 0 ;
276+ ServerVersion = new ServerVersion ( Encoding . ASCII . GetString ( initialHandshake . ServerVersion ) ) ;
277+ ConnectionId = initialHandshake . ConnectionId ;
278+ AuthPluginData = initialHandshake . AuthPluginData ;
279+ m_useCompression = cs . UseCompression && ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Compress ) != 0 ;
265280
266- m_supportsConnectionAttributes = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . ConnectionAttributes ) != 0 ;
267- m_supportsDeprecateEof = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . DeprecateEof ) != 0 ;
268- var serverSupportsSsl = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Ssl ) != 0 ;
281+ m_supportsConnectionAttributes = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . ConnectionAttributes ) != 0 ;
282+ m_supportsDeprecateEof = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . DeprecateEof ) != 0 ;
283+ var serverSupportsSsl = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Ssl ) != 0 ;
269284
270- Log . Info ( "{0} made connection; ServerVersion={1}; ConnectionId={2}; Flags: {3}{4}{5}{6}" , m_logArguments [ 0 ] , ServerVersion . OriginalString , ConnectionId ,
271- m_useCompression ? "Cmp " : "" , m_supportsConnectionAttributes ? "Attr " : "" , m_supportsDeprecateEof ? "" : "Eof " , serverSupportsSsl ? "Ssl " : "" ) ;
285+ Log . Info ( "{0} made connection; ServerVersion={1}; ConnectionId={2}; Flags: {3}{4}{5}{6}" , m_logArguments [ 0 ] , ServerVersion . OriginalString , ConnectionId ,
286+ m_useCompression ? "Cmp " : "" , m_supportsConnectionAttributes ? "Attr " : "" , m_supportsDeprecateEof ? "" : "Eof " , serverSupportsSsl ? "Ssl " : "" ) ;
272287
273- if ( cs . SslMode != MySqlSslMode . None && ( cs . SslMode != MySqlSslMode . Preferred || serverSupportsSsl ) )
274- {
275- if ( ! serverSupportsSsl )
288+ if ( cs . SslMode != MySqlSslMode . None && ( cs . SslMode != MySqlSslMode . Preferred || serverSupportsSsl ) )
276289 {
277- Log . Error ( "{0} requires SSL but server doesn't support it" , m_logArguments ) ;
278- throw new MySqlException ( "Server does not support SSL" ) ;
290+ if ( ! serverSupportsSsl )
291+ {
292+ Log . Error ( "{0} requires SSL but server doesn't support it" , m_logArguments ) ;
293+ throw new MySqlException ( "Server does not support SSL" ) ;
294+ }
295+
296+ try
297+ {
298+ await InitSslAsync ( initialHandshake . ProtocolCapabilities , cs , sslProtocols , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
299+ shouldRetrySsl = false ;
300+ }
301+ catch ( Exception ex ) when ( shouldRetrySsl && ( ( ex is MySqlException && ex . InnerException is IOException ) || ex is IOException ) )
302+ {
303+ // negotiating TLS 1.2 with a yaSSL-based server throws an exception on Windows, see comment at top of method
304+ Log . Warn ( ex , "{0} failed negotiating TLS; falling back to TLS 1.1" , m_logArguments ) ;
305+ sslProtocols = SslProtocols . Tls | SslProtocols . Tls11 ;
306+ if ( Pool != null )
307+ Pool . SslProtocols = sslProtocols ;
308+ }
279309 }
280- await InitSslAsync ( initialHandshake . ProtocolCapabilities , cs , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
281- }
310+ else
311+ {
312+ shouldRetrySsl = false ;
313+ }
314+ } while ( shouldRetrySsl ) ;
282315
283316 if ( m_supportsConnectionAttributes && s_connectionAttributes == null )
284317 s_connectionAttributes = CreateConnectionAttributes ( ) ;
@@ -772,7 +805,7 @@ private async Task<bool> OpenUnixSocketAsync(ConnectionSettings cs, IOBehavior i
772805 return false ;
773806 }
774807
775- private async Task InitSslAsync ( ProtocolCapabilities serverCapabilities , ConnectionSettings cs , IOBehavior ioBehavior , CancellationToken cancellationToken )
808+ private async Task InitSslAsync ( ProtocolCapabilities serverCapabilities , ConnectionSettings cs , SslProtocols sslProtocols , IOBehavior ioBehavior , CancellationToken cancellationToken )
776809 {
777810 Log . Info ( "{0} initializing TLS connection" , m_logArguments ) ;
778811 X509CertificateCollection clientCertificates = null ;
@@ -861,11 +894,6 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
861894 else
862895 sslStream = new SslStream ( m_networkStream , false , ValidateRemoteCertificate , ValidateLocalCertificate ) ;
863896
864- // SslProtocols.Tls1.2 throws an exception in Windows, see https://github.com/mysql-net/MySqlConnector/pull/101
865- var sslProtocols = SslProtocols . Tls | SslProtocols . Tls11 ;
866- if ( ! Utility . IsWindows ( ) )
867- sslProtocols |= SslProtocols . Tls12 ;
868-
869897 var checkCertificateRevocation = cs . SslMode == MySqlSslMode . VerifyFull ;
870898
871899 var initSsl = HandshakeResponse41Payload . CreateWithSsl ( serverCapabilities , cs , m_useCompression ) ;
@@ -889,6 +917,8 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
889917 m_payloadHandler . ByteHandler = sslByteHandler ;
890918 m_isSecureConnection = true ;
891919 m_sslStream = sslStream ;
920+ m_logArguments [ 1 ] = sslStream . SslProtocol ;
921+ Log . Info ( "{0} connected TLS with protocol {1}" , m_logArguments ) ;
892922 }
893923 catch ( Exception ex )
894924 {
@@ -1082,6 +1112,8 @@ private void VerifyState(State state1, State state2, State state3)
10821112
10831113 internal bool SslIsMutuallyAuthenticated => m_sslStream ? . IsMutuallyAuthenticated ?? false ;
10841114
1115+ internal SslProtocols SslProtocol => m_sslStream ? . SslProtocol ?? SslProtocols . None ;
1116+
10851117 private byte [ ] CreateConnectionAttributes ( )
10861118 {
10871119 Log . Debug ( "{0} creating connection attributes" , m_logArguments ) ;
0 commit comments