public static byte[] Create(InitialHandshakePacket handshake, string userName, string password, string database)
        {
            // TODO: verify server capabilities

            var writer = new PayloadWriter();

            writer.WriteInt32((int)(
                                  ProtocolCapabilities.Protocol41 |
                                  ProtocolCapabilities.LongPassword |
                                  ProtocolCapabilities.SecureConnection |
                                  ProtocolCapabilities.PluginAuth |
                                  ProtocolCapabilities.PluginAuthLengthEncodedClientData |
                                  ProtocolCapabilities.MultiStatements |
                                  ProtocolCapabilities.MultiResults |
                                  ProtocolCapabilities.PreparedStatementMultiResults |
                                  (string.IsNullOrWhiteSpace(database) ? 0 : ProtocolCapabilities.ConnectWithDatabase)));
            writer.WriteInt32(0x40000000);
            writer.WriteByte((byte)CharacterSet.Utf8Mb4Binary);
            writer.Write(new byte[23]);
            writer.WriteNullTerminatedString(userName);

            var authenticationResponse = AuthenticationUtility.CreateAuthenticationResponse(handshake.AuthPluginData, 0, password);

            writer.WriteByte((byte)authenticationResponse.Length);
            writer.Write(authenticationResponse);

            if (!string.IsNullOrWhiteSpace(database))
            {
                writer.WriteNullTerminatedString(database);
            }

            writer.WriteNullTerminatedString("mysql_native_password");

            return(writer.ToBytes());
        }
Beispiel #2
0
        public async Task <bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
        {
            VerifyState(State.Connected);

            try
            {
                if (ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0)
                {
                    await SendAsync(ResetConnectionPayload.Create(), ioBehavior, cancellationToken).ConfigureAwait(false);

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

                    OkPayload.Create(payload);

                    // the "reset connection" packet also resets the connection charset, so we need to change that back to our default
                    payload = new PayloadData(new ArraySegment <byte>(PayloadUtilities.CreateEofStringPayload(CommandKind.Query, "SET NAMES utf8mb4 COLLATE utf8mb4_bin;")));
                    await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                    payload = await ReceiveReplyAsync(ioBehavior, 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.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);
                    var payload        = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_supportsConnectionAttributes ? s_connectionAttributes : null);
                    await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

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

                    if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature)
                    {
                        await SwitchAuthenticationAsync(cs, payload, ioBehavior, cancellationToken).ConfigureAwait(false);

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

                return(true);
            }
            catch (EndOfStreamException)
            {
            }
            catch (IOException)
            {
            }
            catch (SocketException)
            {
            }

            return(false);
        }
Beispiel #3
0
        private async Task SwitchAuthenticationAsync(PayloadData payload, ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
        {
            // 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("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name));
            }
            AuthPluginData = switchRequest.Data;
            var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);

            payload = new PayloadData(new ArraySegment <byte>(hashedPassword));
            await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
        }
Beispiel #4
0
        public async Task ResetConnectionAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
        {
            if (ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0)
            {
                await SendAsync(ResetConnectionPayload.Create(), ioBehavior, cancellationToken).ConfigureAwait(false);

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

                OkPayload.Create(payload);

                // the "reset connection" packet also resets the connection charset, so we need to change that back to our default
                payload = new PayloadData(new ArraySegment <byte>(PayloadUtilities.CreateEofStringPayload(CommandKind.Query, "SET NAMES utf8mb4;")));
                await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                payload = await ReceiveReplyAsync(ioBehavior, 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.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);
                var payload        = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database);
                await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                payload = await ReceiveReplyAsync(ioBehavior, 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("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name));
                    }
                    hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(switchRequest.Data, 0, cs.Password);
                    payload        = new PayloadData(new ArraySegment <byte>(hashedPassword));
                    await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                    payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
                }
                OkPayload.Create(payload);
            }
        }
        public static byte[] Create(InitialHandshakePacket handshake, ConnectionSettings cs)
        {
            // TODO: verify server capabilities

            var writer = CapabilitiesPayload(cs);

            writer.WriteNullTerminatedString(cs.UserID);
            var authenticationResponse = AuthenticationUtility.CreateAuthenticationResponse(handshake.AuthPluginData, 0, cs.Password);

            writer.WriteByte((byte)authenticationResponse.Length);
            writer.Write(authenticationResponse);

            if (!string.IsNullOrWhiteSpace(cs.Database))
            {
                writer.WriteNullTerminatedString(cs.Database);
            }

            writer.WriteNullTerminatedString("mysql_native_password");

            return(writer.ToBytes());
        }
Beispiel #6
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(new ArraySegment <byte>(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(new ArraySegment <byte>(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(new ArraySegment <byte>(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));
            }
        }
Beispiel #7
0
        private async Task 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(new ArraySegment <byte>(hashedPassword));
                await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);

                break;

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

                break;

            case "sha256_password":
                // add NUL terminator to password
                var passwordBytes = Encoding.UTF8.GetBytes(cs.Password);
                Array.Resize(ref passwordBytes, passwordBytes.Length + 1);

                if (!m_isSecureConnection && passwordBytes.Length > 1)
                {
#if NET45
                    throw new MySqlException("Authentication method '{0}' requires a secure connection (prior to .NET 4.6).".FormatInvariant(switchRequest.Name));
#else
                    string publicKey;
                    if (!string.IsNullOrEmpty(cs.ServerRsaPublicKeyFile))
                    {
                        try
                        {
                            publicKey = File.ReadAllText(cs.ServerRsaPublicKeyFile);
                        }
                        catch (IOException ex)
                        {
                            throw new MySqlException("Couldn't load server's RSA public key from '{0}'".FormatInvariant(cs.ServerRsaPublicKeyFile), ex);
                        }
                    }
                    else if (cs.AllowPublicKeyRetrieval)
                    {
                        // request the RSA public key
                        await SendReplyAsync(new PayloadData(new ArraySegment <byte>(new byte[] { 0x01 }, 0, 1)), ioBehavior, cancellationToken).ConfigureAwait(false);

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

                        var publicKeyPayload = AuthenticationMoreDataPayload.Create(payload);
                        publicKey = Encoding.ASCII.GetString(publicKeyPayload.Data);
                    }
                    else
                    {
                        throw new MySqlException("Authentication method '{0}' failed. Either use a secure connection, specify the server's RSA public key with ServerRSAPublicKeyFile, or set AllowPublicKeyRetrieval=True.".FormatInvariant(switchRequest.Name));
                    }

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

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

                        // encrypt with RSA public key
                        var encryptedPassword = rsa.Encrypt(passwordBytes, RSAEncryptionPadding.OaepSHA1);
                        payload = new PayloadData(new ArraySegment <byte>(encryptedPassword));
                        await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
                    }
#endif
                }
                else
                {
                    // send plaintext password
                    payload = new PayloadData(new ArraySegment <byte>(passwordBytes));
                    await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
                }
                break;

            default:
                throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name));
            }
        }