Skip to content

Commit cdcc738

Browse files
committed
Load multiple certificates from CACertificateFile. Fixes #499
Use .Reset() to clear certificates on .NET 4.5 (where .Dispose() is not available); this may prevent temp files was being leaked.
1 parent 45df21e commit cdcc738

File tree

2 files changed

+107
-27
lines changed

2 files changed

+107
-27
lines changed

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -828,13 +828,11 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect
828828
{
829829
m_logArguments[1] = cs.CertificateFile;
830830
Log.Error("Session{0} no private key included with CertificateFile '{1}'", m_logArguments);
831-
throw new MySqlException("CertificateFile does not contain a private key. "+
832-
"CertificateFile should be in PKCS #12 (.pfx) format and contain both a Certificate and Private Key");
831+
throw new MySqlException("CertificateFile does not contain a private key. " +
832+
"CertificateFile should be in PKCS #12 (.pfx) format and contain both a Certificate and Private Key");
833833
}
834-
#if !NET45
835834
m_clientCertificate = certificate;
836-
#endif
837-
clientCertificates = new X509CertificateCollection {certificate};
835+
clientCertificates = new X509CertificateCollection { certificate };
838836
}
839837
catch (CryptographicException ex)
840838
{
@@ -849,29 +847,64 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect
849847
X509Chain caCertificateChain = null;
850848
if (cs.CACertificateFile != null)
851849
{
852-
try
850+
var certificateChain = new X509Chain
853851
{
854-
var caCertificate = new X509Certificate2(cs.CACertificateFile);
855-
#if !NET45
856-
m_serverCertificate = caCertificate;
857-
#endif
858-
caCertificateChain = new X509Chain
859-
{
860-
ChainPolicy =
852+
ChainPolicy =
861853
{
862854
RevocationMode = X509RevocationMode.NoCheck,
863855
VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority
864856
}
865-
};
866-
caCertificateChain.ChainPolicy.ExtraStore.Add(caCertificate);
867-
}
868-
catch (CryptographicException ex)
857+
};
858+
859+
try
869860
{
861+
// read the CA Certificate File
870862
m_logArguments[1] = cs.CACertificateFile;
871-
Log.Error(ex, "Session{0} couldn't load CA certificate from CertificateFile '{1}'", m_logArguments);
872-
if (!File.Exists(cs.CACertificateFile))
873-
throw new MySqlException("Cannot find CA Certificate File", ex);
874-
throw new MySqlException("The CA Certificate File is invalid", ex);
863+
Log.Debug("Session{0} loading CA certificate(s) from CertificateFile '{1}'", m_logArguments);
864+
byte[] certificateBytes;
865+
try
866+
{
867+
certificateBytes = File.ReadAllBytes(cs.CACertificateFile);
868+
}
869+
catch (Exception ex)
870+
{
871+
Log.Error(ex, "Session{0} couldn't load CA certificate from CertificateFile '{1}'", m_logArguments);
872+
throw new MySqlException("Could not load CA Certificate File: " + cs.CACertificateFile, ex);
873+
}
874+
875+
// find the index of each individual certificate in the file (assuming there may be multiple certificates concatenated together)
876+
for (var index = 0; index != -1; index = Utility.FindNextIndex(certificateBytes, index + 1, s_beginCertificateBytes))
877+
{
878+
try
879+
{
880+
// load the certificate at this index; note that 'new X509Certificate' stops at the end of the first certificate it loads
881+
m_logArguments[1] = index;
882+
Log.Debug("Session{0} loading certificate at Index {1} in the CA certificate file.", m_logArguments);
883+
var caCertificate = new X509Certificate2(Utility.ArraySlice(certificateBytes, index));
884+
certificateChain.ChainPolicy.ExtraStore.Add(caCertificate);
885+
}
886+
catch (CryptographicException ex)
887+
{
888+
m_logArguments[1] = cs.CACertificateFile;
889+
Log.Error(ex, "Session{0} couldn't load CA certificate from CertificateFile '{1}'", m_logArguments);
890+
if (!File.Exists(cs.CACertificateFile))
891+
throw new MySqlException("The CA Certificate File is invalid", ex);
892+
}
893+
}
894+
895+
// success
896+
if (Log.IsInfoEnabled())
897+
Log.Info("Session{0} loaded certificates from CertificateFile '{1}'; CertificateCount: {2}", m_logArguments[0], cs.CACertificateFile, certificateChain.ChainPolicy.ExtraStore.Count);
898+
caCertificateChain = certificateChain;
899+
certificateChain = null;
900+
}
901+
finally
902+
{
903+
#if NET45
904+
certificateChain?.Reset();
905+
#else
906+
certificateChain?.Dispose();
907+
#endif
875908
}
876909
}
877910

@@ -944,6 +977,14 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
944977
throw new MySqlException("MySQL Server rejected client certificate", ex);
945978
throw;
946979
}
980+
finally
981+
{
982+
#if NET45
983+
caCertificateChain?.Reset();
984+
#else
985+
caCertificateChain?.Dispose();
986+
#endif
987+
}
947988
}
948989

949990
// Some servers are exposed through a proxy, which handles the initial handshake and gives the proxy's
@@ -1015,9 +1056,11 @@ private void ShutdownSocket()
10151056
Utility.Dispose(ref m_networkStream);
10161057
SafeDispose(ref m_tcpClient);
10171058
SafeDispose(ref m_socket);
1018-
#if !NET45
1059+
#if NET45
1060+
m_clientCertificate?.Reset();
1061+
m_clientCertificate = null;
1062+
#else
10191063
Utility.Dispose(ref m_clientCertificate);
1020-
Utility.Dispose(ref m_serverCertificate);
10211064
#endif
10221065
}
10231066

@@ -1198,6 +1241,7 @@ private enum State
11981241
Failed,
11991242
}
12001243

1244+
static readonly byte[] s_beginCertificateBytes = new byte[] { 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45 }; // -----BEGIN CERTIFICATE-----
12011245
static int s_lastId;
12021246
static byte[] s_connectionAttributes;
12031247
static readonly IMySqlConnectorLogger Log = MySqlConnectorLogManager.CreateLogger(nameof(ServerSession));
@@ -1211,10 +1255,7 @@ private enum State
12111255
Socket m_socket;
12121256
NetworkStream m_networkStream;
12131257
SslStream m_sslStream;
1214-
#if !NET45
1215-
IDisposable m_clientCertificate;
1216-
IDisposable m_serverCertificate;
1217-
#endif
1258+
X509Certificate2 m_clientCertificate;
12181259
IPayloadHandler m_payloadHandler;
12191260
int m_activeCommandId;
12201261
bool m_useCompression;

src/MySqlConnector/Utilities/Utility.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,45 @@ public static ArraySegment<T> Slice<T>(this ArraySegment<T> arraySegment, int in
156156
public static ArraySegment<T> Slice<T>(this ArraySegment<T> arraySegment, int index, int length) =>
157157
new ArraySegment<T>(arraySegment.Array, arraySegment.Offset + index, length);
158158

159+
/// <summary>
160+
/// Returns a new <see cref="byte[]"/> that is a slice of <paramref name="input"/> starting at <paramref name="offset"/>.
161+
/// </summary>
162+
/// <param name="input">The array to slice.</param>
163+
/// <param name="offset">The offset at which to slice.</param>
164+
/// <returns>A new <see cref="byte[]"/> that is a slice of <paramref name="input"/> from <paramref name="offset"/> to the end.</returns>
165+
public static byte[] ArraySlice(byte[] input, int offset)
166+
{
167+
if (offset == 0)
168+
return input;
169+
var slice = new byte[input.Length - offset];
170+
Array.Copy(input, offset, slice, 0, slice.Length);
171+
return slice;
172+
}
173+
174+
/// <summary>
175+
/// Finds the next index of <paramref name="pattern"/> in <paramref name="array"/>, starting at index <paramref name="offset"/>.
176+
/// </summary>
177+
/// <param name="array">The array to search.</param>
178+
/// <param name="offset">The offset at which to start searching.</param>
179+
/// <param name="pattern">The pattern to find in <paramref name="array"/>.</param>
180+
/// <returns>The offset of <paramref name="pattern"/> within <paramref name="array"/>, or <c>-1</c> if <paramref name="pattern"/> was not found.</returns>
181+
public static int FindNextIndex(byte[] array, int offset, byte[] pattern)
182+
{
183+
var limit = array.Length - pattern.Length;
184+
for (var start = offset; start <= limit; start++)
185+
{
186+
var i = 0;
187+
for (; i < pattern.Length; i++)
188+
{
189+
if (array[start + i] != pattern[i])
190+
break;
191+
}
192+
if (i == pattern.Length)
193+
return start;
194+
}
195+
return -1;
196+
}
197+
159198
#if NET45
160199
public static Task<T> TaskFromException<T>(Exception exception)
161200
{

0 commit comments

Comments
 (0)