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.

181 lines
6.9 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright © 2004, 2017 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 Externals.MySql.Data.MySqlClient;
  24. using System;
  25. using System.Collections.Generic;
  26. using System.Diagnostics;
  27. using System.IO;
  28. using System.Net.Security;
  29. using System.Security.Authentication;
  30. using System.Security.Cryptography.X509Certificates;
  31. using System.Text;
  32. namespace Externals.MySql.Data.Common
  33. {
  34. internal class Ssl
  35. {
  36. private MySqlConnectionStringBuilder settings;
  37. private static Dictionary<string, SslProtocols> tlsConnectionRef = new Dictionary<string, SslProtocols>();
  38. private static Dictionary<string, int> tlsRetry = new Dictionary<string, int>();
  39. private static SslProtocols[] tlsProtocols = new SslProtocols[] { SslProtocols.Tls12, SslProtocols.Tls11 };
  40. private static Object thisLock = new Object();
  41. public Ssl(MySqlConnectionStringBuilder settings)
  42. {
  43. this.settings = settings;
  44. }
  45. /// <summary>
  46. /// Retrieve client SSL certificates. Dependent on connection string
  47. /// settings we use either file or store based certificates.
  48. /// </summary>
  49. private X509CertificateCollection GetClientCertificates()
  50. {
  51. X509CertificateCollection certs = new X509CertificateCollection();
  52. // Check for file-based certificate
  53. if (settings.CertificateFile != null)
  54. {
  55. X509Certificate2 clientCert = new X509Certificate2(settings.CertificateFile,
  56. settings.CertificatePassword);
  57. certs.Add(clientCert);
  58. return certs;
  59. }
  60. if (settings.CertificateStoreLocation == MySqlCertificateStoreLocation.None)
  61. return certs;
  62. StoreLocation location =
  63. (settings.CertificateStoreLocation == MySqlCertificateStoreLocation.CurrentUser) ?
  64. StoreLocation.CurrentUser : StoreLocation.LocalMachine;
  65. // Check for store-based certificate
  66. X509Store store = new X509Store(StoreName.My, location);
  67. store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
  68. if (settings.CertificateThumbprint == null)
  69. {
  70. // Return all certificates from the store.
  71. certs.AddRange(store.Certificates);
  72. return certs;
  73. }
  74. // Find certificate with given thumbprint
  75. certs.AddRange(store.Certificates.Find(X509FindType.FindByThumbprint,
  76. settings.CertificateThumbprint, true));
  77. if (certs.Count == 0)
  78. {
  79. throw new MySqlException("Certificate with Thumbprint " +
  80. settings.CertificateThumbprint + " not found");
  81. }
  82. return certs;
  83. }
  84. public MySqlStream StartSSL(ref Stream baseStream, Encoding encoding, string connectionString)
  85. {
  86. RemoteCertificateValidationCallback sslValidateCallback =
  87. new RemoteCertificateValidationCallback(ServerCheckValidation);
  88. SslStream ss = new SslStream(baseStream, false, sslValidateCallback, null);
  89. X509CertificateCollection certs = GetClientCertificates();
  90. string connectionId = connectionString.GetHashCode().ToString();
  91. SslProtocols tlsProtocol = SslProtocols.Tls;
  92. lock (thisLock)
  93. {
  94. if (tlsConnectionRef.ContainsKey(connectionId))
  95. {
  96. tlsProtocol = tlsConnectionRef[connectionId];
  97. }
  98. else
  99. {
  100. if (!tlsRetry.ContainsKey(connectionId))
  101. {
  102. tlsRetry[connectionId] = 0;
  103. }
  104. for (int i = tlsRetry[connectionId]; i < tlsProtocols.Length; i++)
  105. {
  106. tlsProtocol |= tlsProtocols[i];
  107. }
  108. }
  109. try
  110. {
  111. ss.AuthenticateAsClientAsync(settings.Server, certs, tlsProtocol, false).Wait();
  112. tlsConnectionRef[connectionId] = tlsProtocol;
  113. tlsRetry.Remove(connectionId);
  114. }
  115. catch (AggregateException ex)
  116. {
  117. if (ex.GetBaseException() is IOException)
  118. {
  119. tlsConnectionRef.Remove(connectionId);
  120. if (tlsRetry.ContainsKey(connectionId))
  121. {
  122. if (tlsRetry[connectionId] > tlsProtocols.Length)
  123. throw new MySqlException(Resources.SslConnectionError, ex);
  124. tlsRetry[connectionId] += 1;
  125. }
  126. }
  127. throw ex.GetBaseException();
  128. }
  129. }
  130. baseStream = ss;
  131. MySqlStream stream = new MySqlStream(ss, encoding, false);
  132. stream.SequenceByte = 2;
  133. return stream;
  134. }
  135. private bool ServerCheckValidation(object sender, X509Certificate certificate,
  136. X509Chain chain, SslPolicyErrors sslPolicyErrors)
  137. {
  138. if (sslPolicyErrors == SslPolicyErrors.None)
  139. return true;
  140. if (settings.SslMode == MySqlSslMode.Preferred ||
  141. settings.SslMode == MySqlSslMode.Required)
  142. {
  143. //Tolerate all certificate errors.
  144. return true;
  145. }
  146. if (settings.SslMode == MySqlSslMode.VerifyCA &&
  147. sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
  148. {
  149. // Tolerate name mismatch in certificate, if full validation is not requested.
  150. return true;
  151. }
  152. return false;
  153. }
  154. }
  155. }
  156. #endif