You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
7.5 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright © 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.Collections.Generic;
  25. using System.IO;
  26. using System.Text;
  27. using System.Security.Cryptography;
  28. #if NETSTANDARD1_3
  29. using AliasText = Externals.MySql.Data.MySqlClient.Framework.NetStandard1_3;
  30. #else
  31. using AliasText = System.Text;
  32. #endif
  33. namespace Externals.MySql.Data.MySqlClient.Authentication
  34. {
  35. /// <summary>
  36. /// The implementation of the caching_sha2_password authentication plugin.
  37. /// </summary>
  38. internal class CachingSha2AuthenticationPlugin : Sha256AuthenticationPlugin
  39. {
  40. internal static AuthStage _authStage;
  41. public override string PluginName => "caching_sha2_password";
  42. protected override void SetAuthData(byte[] data)
  43. {
  44. // If the data given to us is a null terminated string, we need to trim off the trailing zero.
  45. if (data[data.Length - 1] == 0)
  46. {
  47. byte[] b = new byte[data.Length - 1];
  48. Buffer.BlockCopy(data, 0, b, 0, data.Length - 1);
  49. base.SetAuthData(b);
  50. }
  51. else base.SetAuthData(data);
  52. }
  53. protected override byte[] MoreData(byte[] data)
  54. {
  55. rawPubkey = data;
  56. // Generate scramble.
  57. if (data == null)
  58. {
  59. byte[] scramble = GetPassword() as byte[];
  60. byte[] buffer = new byte[scramble.Length - 1];
  61. Array.Copy(scramble, 1, buffer, 0, scramble.Length - 1);
  62. return buffer;
  63. }
  64. // Fast authentication.
  65. else if (data[0] == 3)
  66. {
  67. _authStage = AuthStage.FAST_AUTH;
  68. return null;
  69. }
  70. else
  71. return GeneratePassword() as byte[];
  72. }
  73. /// <summary>
  74. /// Generates a byte array set with the password of the user in the expected format based on the
  75. /// SSL settings of the current connection.
  76. /// </summary>
  77. /// <returns>A byte array that contains the password of the user in the expected format.</returns>
  78. protected byte[] GeneratePassword()
  79. {
  80. // If connection is secure perform full authentication.
  81. if (Settings.SslMode != MySqlSslMode.None)
  82. {
  83. _authStage = AuthStage.FULL_AUTH;
  84. // Send as clear text since the channel is already encrypted.
  85. byte[] passBytes = Encoding.GetBytes(Settings.Password);
  86. byte[] buffer = new byte[passBytes.Length + 1];
  87. Array.Copy(passBytes, 0, buffer, 0, passBytes.Length);
  88. buffer[passBytes.Length] = 0;
  89. return buffer;
  90. }
  91. else
  92. {
  93. // Request RSA key from server.
  94. if (rawPubkey != null && rawPubkey[0] == 4)
  95. {
  96. _authStage = AuthStage.REQUEST_RSA_KEY;
  97. return new byte[] { 0x02 };
  98. }
  99. else if (!Settings.AllowPublicKeyRetrieval)
  100. throw new MySqlException(Resources.RSAPublicKeyRetrievalNotEnabled);
  101. // Full authentication.
  102. else
  103. {
  104. _authStage = AuthStage.FULL_AUTH;
  105. byte[] bytes = GetRsaPassword(Settings.Password, AuthenticationData, rawPubkey);
  106. if (bytes != null && bytes.Length == 1 && bytes[0] == 0) return null;
  107. return bytes;
  108. }
  109. }
  110. }
  111. private byte[] GetRsaPassword(string password, byte[] seedBytes, byte[] rawPublicKey)
  112. {
  113. if (password.Length == 0) return new byte[1];
  114. if (rawPubkey == null) return null;
  115. // Obfuscate the plain text password with the session scramble.
  116. byte[] obfuscated = GetXor(AliasText.Encoding.Default.GetBytes(password), seedBytes);
  117. // Encrypt the password and send it to the server.
  118. if (this.ServerVersion >= new Version("8.0.5"))
  119. {
  120. #if NETSTANDARD1_3
  121. RSA rsa = MySqlPemReader.ConvertPemToRSAProvider(rawPublicKey);
  122. if (rsa == null) throw new MySqlException(Resources.UnableToReadRSAKey);
  123. return rsa.Encrypt(obfuscated, RSAEncryptionPadding.OaepSHA1);
  124. #else
  125. RSACryptoServiceProvider rsa = MySqlPemReader.ConvertPemToRSAProvider(rawPublicKey);
  126. if (rsa == null) throw new MySqlException(Resources.UnableToReadRSAKey);
  127. return rsa.Encrypt(obfuscated, true);
  128. #endif
  129. }
  130. else
  131. {
  132. #if NETSTANDARD1_3
  133. RSA rsa = MySqlPemReader.ConvertPemToRSAProvider(rawPublicKey);
  134. if (rsa == null) throw new MySqlException(Resources.UnableToReadRSAKey);
  135. return rsa.Encrypt(obfuscated, RSAEncryptionPadding.Pkcs1);
  136. #else
  137. RSACryptoServiceProvider rsa = MySqlPemReader.ConvertPemToRSAProvider(rawPublicKey);
  138. if (rsa == null) throw new MySqlException(Resources.UnableToReadRSAKey);
  139. return rsa.Encrypt(obfuscated, false);
  140. #endif
  141. }
  142. }
  143. public override object GetPassword()
  144. {
  145. _authStage = AuthStage.GENERATE_SCRAMBLE;
  146. // If we have no password then we just return 1 zero byte.
  147. if (Settings.Password.Length == 0) return new byte[1];
  148. SHA256 sha = SHA256.Create();
  149. byte[] firstHash = sha.ComputeHash(AliasText.Encoding.Default.GetBytes(Settings.Password));
  150. byte[] secondHash = sha.ComputeHash(firstHash);
  151. byte[] input = new byte[AuthenticationData.Length + secondHash.Length];
  152. Array.Copy(secondHash, 0, input, 0, secondHash.Length);
  153. Array.Copy(AuthenticationData, 0, input, secondHash.Length, AuthenticationData.Length);
  154. byte[] thirdHash = sha.ComputeHash(input);
  155. byte[] finalHash = new byte[thirdHash.Length];
  156. for (int i = 0; i < firstHash.Length; i++)
  157. finalHash[i] = (byte)(firstHash[i] ^ thirdHash[i]);
  158. byte[] buffer = new byte[finalHash.Length + 1];
  159. Array.Copy(finalHash, 0, buffer, 1, finalHash.Length);
  160. buffer[0] = 0x20;
  161. return buffer;
  162. }
  163. }
  164. /// <summary>
  165. /// Defines the stage of the authentication.
  166. /// </summary>
  167. internal enum AuthStage
  168. {
  169. GENERATE_SCRAMBLE = 0,
  170. REQUEST_RSA_KEY = 1,
  171. FAST_AUTH = 2,
  172. FULL_AUTH = 3
  173. }
  174. }
  175. #endif