Skip to content

Commit 2ab0342

Browse files
committed
Add RemoteCertificateValidationCallback.
Signed-off-by: Bradley Grainger <bgrainger@gmail.com>
1 parent 6abcff1 commit 2ab0342

File tree

6 files changed

+80
-3
lines changed

6 files changed

+80
-3
lines changed

docs/content/api/MySqlConnector/MySqlConnection/RemoteCertificateValidationCallback.md

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/content/api/MySqlConnector/MySqlConnectionType.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/content/connection-options.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ These are the options that need to be used in order to configure a connection to
130130
<tr id="SslCa">
131131
<td>SSL CA, CA Certificate File, CACertificateFile, SslCa, Ssl-Ca</td>
132132
<td></td>
133-
<td>The path to a CA certificate file in a PEM Encoded (.pem) format. This should be used with <code>SslMode=VerifyCA</code> or <code>SslMode=VerifyFull</code> to enable verification of a CA certificate that is not trusted by the operating system’s certificate store.</td>
133+
<td>
134+
<p>The path to a CA certificate file in a PEM Encoded (.pem) format. This should be used with <code>SslMode=VerifyCA</code> or <code>SslMode=VerifyFull</code> to enable verification of a CA certificate that is not trusted by the operating system’s certificate store.</p>
135+
<p>To provide a custom callback to validate the remote certificate, leave this option empty and set <code>SslMode</code> to <code>Required</code> (or <code>Preferred</code>), then set <a href="/api/mysqlconnector/mysqlconnection/remotecertificatevalidationcallback/"><code>MySqlConnection.RemoteCertificateValidationCallback</code></a> before calling <a href="/api/mysqlconnector/mysqlconnection/open/"><code>MySqlConnection.Open</code></a>. The property should be set to a delegate that will validate the remote certificate, as per <a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.security.remotecertificatevalidationcallback" title="RemoteCertificateValidationCallback Delegate (MSDN)">the documentation</a>.</p>
136+
</td>
134137
</tr>
135138
<tr id="CertificateStoreLocation">
136139
<td>Certificate Store Location, CertificateStoreLocation</td>

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,8 +1369,28 @@ caCertificateChain is not null &&
13691369
return rcbPolicyErrors == SslPolicyErrors.None;
13701370
}
13711371

1372-
var sslStream = clientCertificates is null ? new SslStream(m_stream!, false, ValidateRemoteCertificate) :
1373-
new SslStream(m_stream!, false, ValidateRemoteCertificate, ValidateLocalCertificate);
1372+
// use the client's callback (if any) for Preferred or Required mode
1373+
RemoteCertificateValidationCallback validateRemoteCertificate = ValidateRemoteCertificate;
1374+
if (connection.RemoteCertificateValidationCallback is not null)
1375+
{
1376+
if (caCertificateChain is not null)
1377+
{
1378+
Log.Warn("Session{0} not using client-provided RemoteCertificateValidationCallback because SslCA is specified", m_logArguments);
1379+
}
1380+
else if (cs.SslMode is not MySqlSslMode.Preferred and not MySqlSslMode.Required)
1381+
{
1382+
m_logArguments[1] = cs.SslMode;
1383+
Log.Warn("Session{0} not using client-provided RemoteCertificateValidationCallback because SslMode is {1}", m_logArguments);
1384+
}
1385+
else
1386+
{
1387+
Log.Debug("Session{0} using client-provided RemoteCertificateValidationCallback", m_logArguments);
1388+
validateRemoteCertificate = connection.RemoteCertificateValidationCallback;
1389+
}
1390+
}
1391+
1392+
var sslStream = clientCertificates is null ? new SslStream(m_stream!, false, validateRemoteCertificate) :
1393+
new SslStream(m_stream!, false, validateRemoteCertificate, ValidateLocalCertificate);
13741394

13751395
var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull;
13761396

src/MySqlConnector/MySqlConnection.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Diagnostics;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Net.Security;
45
using System.Net.Sockets;
56
using System.Security.Authentication;
67
using System.Security.Cryptography.X509Certificates;
@@ -522,6 +523,14 @@ public override string ConnectionString
522523
/// </remarks>
523524
public Func<MySqlProvidePasswordContext, string>? ProvidePasswordCallback { get; set;}
524525

526+
/// <summary>
527+
/// Gets or sets the delegate used to verify that the server's certificate is valid.
528+
/// </summary>
529+
/// <remarks><see cref="MySqlConnectionStringBuilder.SslMode"/> must be set to <see cref="MySqlSslMode.Preferred"/>
530+
/// or <see cref="MySqlSslMode.Required"/> in order for this delegate to be invoked. See the documentation for
531+
/// <see cref="RemoteCertificateValidationCallback"/> for more information on the values passed to this delegate.</remarks>
532+
public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; }
533+
525534
/// <summary>
526535
/// Clears the connection pool that <paramref name="connection"/> belongs to.
527536
/// </summary>

tests/SideBySide/SslTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,27 @@ public async Task ConnectSslBadCaCertificate()
192192
await Assert.ThrowsAsync<MySqlException>(async () => await connection.OpenAsync());
193193
}
194194

195+
#if !BASELINE
196+
[SkippableTheory(ServerFeatures.KnownCertificateAuthority, ConfigSettings.RequiresSsl)]
197+
[InlineData(MySqlSslMode.VerifyCA, false, false)]
198+
[InlineData(MySqlSslMode.Required, false, false)]
199+
[InlineData(MySqlSslMode.Required, true, true)]
200+
public async Task ConnectSslRemoteCertificateValidationCallback(MySqlSslMode sslMode, bool clearCA, bool expectedSuccess)
201+
{
202+
var csb = AppConfig.CreateConnectionStringBuilder();
203+
csb.CertificateFile = Path.Combine(AppConfig.CertsPath, "ssl-client.pfx");
204+
csb.SslMode = sslMode;
205+
csb.SslCa = clearCA ? Path.Combine(AppConfig.CertsPath, "non-ca-client-cert.pem") : "";
206+
using var connection = new MySqlConnection(csb.ConnectionString);
207+
connection.RemoteCertificateValidationCallback = (s, c, h, e) => true;
208+
209+
if (expectedSuccess)
210+
await connection.OpenAsync();
211+
else
212+
await Assert.ThrowsAsync<MySqlException>(async () => await connection.OpenAsync());
213+
}
214+
#endif
215+
195216
[SkippableFact(ConfigSettings.RequiresSsl)]
196217
public async Task ConnectSslTlsVersion()
197218
{

0 commit comments

Comments
 (0)