Ejemplo n.º 1
0
        private async Task <PayloadData> SendEncryptedPasswordAsync(
            AuthenticationMethodSwitchRequestPayload switchRequest,
            string rsaPublicKey,
            ConnectionSettings cs,
            IOBehavior ioBehavior,
            CancellationToken cancellationToken)
        {
            // load the RSA public key
            RSA rsa;

            try
            {
                rsa = Utility.DecodeX509PublicKey(rsaPublicKey);
            }
            catch (Exception ex)
            {
                throw new MySqlException("Couldn't load server's RSA public key; try using a secure connection instead.", ex);
            }

            // add NUL terminator to password
            var passwordBytes = Encoding.UTF8.GetBytes(cs.Password);

            Array.Resize(ref passwordBytes, passwordBytes.Length + 1);

            using (rsa)
            {
                // XOR the password bytes with the challenge
                AuthPluginData = Utility.TrimZeroByte(switchRequest.Data);
                for (var i = 0; i < passwordBytes.Length; i++)
                {
                    passwordBytes[i] ^= AuthPluginData[i % AuthPluginData.Length];
                }

                // encrypt with RSA public key
                var padding           = switchRequest.Name == "caching_sha2_password" ? RSAEncryptionPadding.Pkcs1 : RSAEncryptionPadding.OaepSHA1;
                var encryptedPassword = rsa.Encrypt(passwordBytes, padding);
                var payload           = new PayloadData(encryptedPassword);
                await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false));
            }
        }
        private async Task ResetConnectionAsync(CancellationToken cancellationToken)
        {
            if (m_session.ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0)
            {
                await m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken).ConfigureAwait(false);

                var payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false);

                OkPayload.Create(payload);
            }
            else
            {
                // optimistically hash the password with the challenge from the initial handshake (supported by MariaDB; doesn't appear to be supported by MySQL)
                var hashedPassword = AuthenticationUtility.HashPassword(m_session.AuthPluginData, 0, m_connectionStringBuilder.Password);
                var payload        = ChangeUserPayload.Create(m_connectionStringBuilder.UserID, hashedPassword, m_database);
                await m_session.SendAsync(payload, cancellationToken).ConfigureAwait(false);

                payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false);

                if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature)
                {
                    // if the server didn't support the hashed password; rehash with the new challenge
                    var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload);
                    if (switchRequest.Name != "mysql_native_password")
                    {
                        throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported.");
                    }
                    hashedPassword = AuthenticationUtility.HashPassword(switchRequest.Data, 0, m_connectionStringBuilder.Password);
                    payload        = new PayloadData(new ArraySegment <byte>(hashedPassword));
                    await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false);

                    payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false);
                }
                OkPayload.Create(payload);
            }
        }
Ejemplo n.º 3
0
        private async Task <PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs, PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken)
        {
            // if the server didn't support the hashed password; rehash with the new challenge
            var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload);

            switch (switchRequest.Name)
            {
            case "mysql_native_password":
                AuthPluginData = switchRequest.Data;
                var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);
                payload = new PayloadData(hashedPassword);
                await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false));

            case "mysql_clear_password":
                if (!m_isSecureConnection)
                {
                    throw new MySqlException("Authentication method '{0}' requires a secure connection.".FormatInvariant(switchRequest.Name));
                }
                payload = new PayloadData(Encoding.UTF8.GetBytes(cs.Password));
                await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false));

            case "caching_sha2_password":
                var scrambleBytes = AuthenticationUtility.CreateScrambleResponse(Utility.TrimZeroByte(switchRequest.Data), cs.Password);
                payload = new PayloadData(scrambleBytes);
                await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);

                var cachingSha2ServerResponsePayload = CachingSha2ServerResponsePayload.Create(payload);
                if (cachingSha2ServerResponsePayload.Succeeded)
                {
                    return(await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false));
                }

                goto case "sha256_password";

            case "sha256_password":
                if (!m_isSecureConnection && cs.Password.Length > 1)
                {
#if NET45
                    throw new MySqlException("Authentication method '{0}' requires a secure connection (prior to .NET 4.6).".FormatInvariant(switchRequest.Name));
#else
                    var publicKey = await GetRsaPublicKeyAsync(switchRequest.Name, cs, ioBehavior, cancellationToken).ConfigureAwait(false);

                    return(await SendEncryptedPasswordAsync(switchRequest, publicKey, cs, ioBehavior, cancellationToken).ConfigureAwait(false));
#endif
                }
                else
                {
                    return(await SendClearPasswordAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false));
                }

            case "mysql_old_password":
                throw new NotSupportedException("'MySQL Server is requesting the insecure pre-4.1 auth mechanism (mysql_old_password). The user password must be upgraded; see https://dev.mysql.com/doc/refman/5.7/en/account-upgrades.html.");

            default:
                throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name));
            }
        }
Ejemplo n.º 4
0
        public override async Task OpenAsync(CancellationToken cancellationToken)
        {
            VerifyNotDisposed();
            if (State != ConnectionState.Closed)
            {
                throw new InvalidOperationException("Cannot Open when State is {0}.".FormatInvariant(State));
            }
#if !NETSTANDARD1_3
            if (System.Transactions.Transaction.Current != null)
            {
                throw new NotSupportedException("Ambient transactions are not supported. Use BeginTransaction instead.");
            }
#endif

            if (m_connectionStringBuilder.UseCompression)
            {
                throw new NotSupportedException("Compression not supported.");
            }

            SetState(ConnectionState.Connecting);

            bool success = false;
            try
            {
                var pool = ConnectionPool.GetPool(m_connectionStringBuilder);
                m_session = pool?.TryGetSession();
                if (m_session == null)
                {
                    m_session = new MySqlSession(pool);
                    var connected = await m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int)m_connectionStringBuilder.Port).ConfigureAwait(false);

                    if (!connected)
                    {
                        SetState(ConnectionState.Closed);
                        throw new MySqlException("Unable to connect to any of the specified MySQL hosts.");
                    }

                    var payload = await m_session.ReceiveAsync(cancellationToken).ConfigureAwait(false);

                    var reader           = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count);
                    var initialHandshake = new InitialHandshakePacket(reader);
                    if (initialHandshake.AuthPluginName != "mysql_native_password")
                    {
                        throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported.");
                    }
                    m_session.ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion));

                    var response = HandshakeResponse41Packet.Create(initialHandshake, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_database);
                    payload = new PayloadData(new ArraySegment <byte>(response));
                    await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false);

                    await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false);

                    // TODO: Check success
                }
                else if (m_connectionStringBuilder.ConnectionReset)
                {
                    if (m_session.ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0)
                    {
                        await m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken).ConfigureAwait(false);

                        var payload = await m_session.ReceiveReplyAsync(cancellationToken);

                        OkPayload.Create(payload);
                    }
                    else
                    {
                        // MySQL doesn't appear to accept a replayed hashed password (using the challenge from the initial handshake), so just send zeroes
                        // and expect to get a new challenge
                        var payload = ChangeUserPayload.Create(m_connectionStringBuilder.UserID, new byte[20], m_database);
                        await m_session.SendAsync(payload, cancellationToken).ConfigureAwait(false);

                        payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false);

                        var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload);
                        if (switchRequest.Name != "mysql_native_password")
                        {
                            throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported.");
                        }
                        var hashedPassword = AuthenticationUtility.HashPassword(switchRequest.Data, 0, m_connectionStringBuilder.Password);
                        payload = new PayloadData(new ArraySegment <byte>(hashedPassword));
                        await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false);

                        payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false);

                        OkPayload.Create(payload);
                    }
                }

                m_hasBeenOpened = true;
                SetState(ConnectionState.Open);
                success = true;
            }
            catch (MySqlException)
            {
                SetState(ConnectionState.Closed);
                throw;
            }
            catch (SocketException ex)
            {
                SetState(ConnectionState.Closed);
                throw new MySqlException("Unable to connect to any of the specified MySQL hosts.", ex);
            }
            finally
            {
                if (!success)
                {
                    Utility.Dispose(ref m_session);
                }
            }
        }