private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken) { X509CertificateCollection clientCertificates = null; if (cs.CertificateFile != null) { try { var certificate = new X509Certificate2(cs.CertificateFile, cs.CertificatePassword); #if !NET45 m_clientCertificate = certificate; #endif clientCertificates = new X509CertificateCollection { certificate }; } catch (CryptographicException ex) { if (!File.Exists(cs.CertificateFile)) { throw new MySqlException("Cannot find Certificate File", ex); } throw new MySqlException("Either the Certificate Password is incorrect or the Certificate File is invalid", ex); } } X509Chain caCertificateChain = null; if (cs.CACertificateFile != null) { try { var caCertificate = new X509Certificate2(cs.CACertificateFile); #if !NET45 m_serverCertificate = caCertificate; #endif caCertificateChain = new X509Chain { ChainPolicy = { RevocationMode = X509RevocationMode.NoCheck, VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority } }; caCertificateChain.ChainPolicy.ExtraStore.Add(caCertificate); } catch (CryptographicException ex) { if (!File.Exists(cs.CACertificateFile)) { throw new MySqlException("Cannot find CA Certificate File", ex); } throw new MySqlException("The CA Certificate File is invalid", ex); } } X509Certificate ValidateLocalCertificate(object lcbSender, string lcbTargetHost, X509CertificateCollection lcbLocalCertificates, X509Certificate lcbRemoteCertificate, string[] lcbAcceptableIssuers) => lcbLocalCertificates[0]; bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate, X509Chain rcbChain, SslPolicyErrors rcbPolicyErrors) { if (cs.SslMode == MySqlSslMode.Preferred || cs.SslMode == MySqlSslMode.Required) { return(true); } if ((rcbPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0 && caCertificateChain != null) { if (caCertificateChain.Build((X509Certificate2)rcbCertificate)) { var chainStatus = caCertificateChain.ChainStatus[0].Status & ~X509ChainStatusFlags.UntrustedRoot; if (chainStatus == X509ChainStatusFlags.NoError) { rcbPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors; } } } if (cs.SslMode == MySqlSslMode.VerifyCA) { rcbPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch; } return(rcbPolicyErrors == SslPolicyErrors.None); } SslStream sslStream; if (clientCertificates == null) { sslStream = new SslStream(m_networkStream, false, ValidateRemoteCertificate); } else { sslStream = new SslStream(m_networkStream, false, ValidateRemoteCertificate, ValidateLocalCertificate); } // SslProtocols.Tls1.2 throws an exception in Windows, see https://github.com/mysql-net/MySqlConnector/pull/101 var sslProtocols = SslProtocols.Tls | SslProtocols.Tls11; if (!Utility.IsWindows()) { sslProtocols |= SslProtocols.Tls12; } var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull; var initSsl = HandshakeResponse41Payload.CreateWithSsl(serverCapabilities, cs, m_useCompression); await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false); try { if (ioBehavior == IOBehavior.Asynchronous) { await sslStream.AuthenticateAsClientAsync(HostName, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false); } else { #if NETSTANDARD1_3 await sslStream.AuthenticateAsClientAsync(HostName, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false); #else sslStream.AuthenticateAsClient(HostName, clientCertificates, sslProtocols, checkCertificateRevocation); #endif } var sslByteHandler = new StreamByteHandler(sslStream); m_payloadHandler.ByteHandler = sslByteHandler; m_isSecureConnection = true; } catch (Exception ex) { sslStream.Dispose(); ShutdownSocket(); HostName = ""; lock (m_lock) m_state = State.Failed; if (ex is AuthenticationException) { throw new MySqlException("SSL Authentication Error", ex); } if (ex is IOException && clientCertificates != null) { throw new MySqlException("MySQL Server rejected client certificate", ex); } throw; } }
public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer, IOBehavior ioBehavior, CancellationToken cancellationToken) { try { lock (m_lock) { VerifyState(State.Created); m_state = State.Connecting; } var connected = false; if (cs.ConnectionType == ConnectionType.Tcp) { connected = await OpenTcpSocketAsync(cs, loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false); } else if (cs.ConnectionType == ConnectionType.Unix) { connected = await OpenUnixSocketAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false); } if (!connected) { lock (m_lock) m_state = State.Failed; throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var byteHandler = new SocketByteHandler(m_socket); m_payloadHandler = new StandardPayloadHandler(byteHandler); var payload = await ReceiveAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var initialHandshake = InitialHandshakePayload.Create(payload); // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use string authPluginName; if ((initialHandshake.ProtocolCapabilities & ProtocolCapabilities.PluginAuth) != 0) { authPluginName = initialHandshake.AuthPluginName; } else { authPluginName = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.SecureConnection) == 0 ? "mysql_old_password" : "mysql_native_password"; } if (authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password") { throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(initialHandshake.AuthPluginName)); } ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); ConnectionId = initialHandshake.ConnectionId; AuthPluginData = initialHandshake.AuthPluginData; m_useCompression = cs.UseCompression && (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Compress) != 0; var serverSupportsSsl = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Ssl) != 0; if (cs.SslMode != MySqlSslMode.None && (cs.SslMode != MySqlSslMode.Preferred || serverSupportsSsl)) { if (!serverSupportsSsl) { throw new MySqlException("Server does not support SSL"); } await InitSslAsync(initialHandshake.ProtocolCapabilities, cs, ioBehavior, cancellationToken).ConfigureAwait(false); } m_supportsConnectionAttributes = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.ConnectionAttributes) != 0; if (m_supportsConnectionAttributes && s_connectionAttributes == null) { s_connectionAttributes = CreateConnectionAttributes(); } m_supportsDeprecateEof = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.DeprecateEof) != 0; payload = HandshakeResponse41Payload.Create(initialHandshake, cs, m_useCompression, m_supportsConnectionAttributes ? s_connectionAttributes : null); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); // if server doesn't support the authentication fast path, it will send a new challenge if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature) { payload = await SwitchAuthenticationAsync(cs, payload, ioBehavior, cancellationToken).ConfigureAwait(false); } OkPayload.Create(payload); if (m_useCompression) { m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler); } } catch (IOException ex) { throw new MySqlException("Couldn't connect to server", ex); } }