@@ -225,60 +225,93 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
225
225
VerifyState ( State . Created ) ;
226
226
m_state = State . Connecting ;
227
227
}
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
234
240
{
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
+ }
240
255
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 ) ;
243
258
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 ) ;
246
261
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
+ }
260
275
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 ;
265
280
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 ;
269
284
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 " : "" ) ;
272
287
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 ) )
276
289
{
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
+ }
279
309
}
280
- await InitSslAsync ( initialHandshake . ProtocolCapabilities , cs , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
281
- }
310
+ else
311
+ {
312
+ shouldRetrySsl = false ;
313
+ }
314
+ } while ( shouldRetrySsl ) ;
282
315
283
316
if ( m_supportsConnectionAttributes && s_connectionAttributes == null )
284
317
s_connectionAttributes = CreateConnectionAttributes ( ) ;
@@ -772,7 +805,7 @@ private async Task<bool> OpenUnixSocketAsync(ConnectionSettings cs, IOBehavior i
772
805
return false ;
773
806
}
774
807
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 )
776
809
{
777
810
Log . Info ( "{0} initializing TLS connection" , m_logArguments ) ;
778
811
X509CertificateCollection clientCertificates = null ;
@@ -861,11 +894,6 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
861
894
else
862
895
sslStream = new SslStream ( m_networkStream , false , ValidateRemoteCertificate , ValidateLocalCertificate ) ;
863
896
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
-
869
897
var checkCertificateRevocation = cs . SslMode == MySqlSslMode . VerifyFull ;
870
898
871
899
var initSsl = HandshakeResponse41Payload . CreateWithSsl ( serverCapabilities , cs , m_useCompression ) ;
@@ -889,6 +917,8 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
889
917
m_payloadHandler . ByteHandler = sslByteHandler ;
890
918
m_isSecureConnection = true ;
891
919
m_sslStream = sslStream ;
920
+ m_logArguments [ 1 ] = sslStream . SslProtocol ;
921
+ Log . Info ( "{0} connected TLS with protocol {1}" , m_logArguments ) ;
892
922
}
893
923
catch ( Exception ex )
894
924
{
@@ -1082,6 +1112,8 @@ private void VerifyState(State state1, State state2, State state3)
1082
1112
1083
1113
internal bool SslIsMutuallyAuthenticated => m_sslStream ? . IsMutuallyAuthenticated ?? false ;
1084
1114
1115
+ internal SslProtocols SslProtocol => m_sslStream ? . SslProtocol ?? SslProtocols . None ;
1116
+
1085
1117
private byte [ ] CreateConnectionAttributes ( )
1086
1118
{
1087
1119
Log . Debug ( "{0} creating connection attributes" , m_logArguments ) ;
0 commit comments