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()); }
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); }
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); }
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()); }
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)); } }
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)); } }