Skip to content

Commit 8564c65

Browse files
committed
Add TlsCipherSuites connection string option. Fixes #904
Signed-off-by: Bradley Grainger <[email protected]>
1 parent c46df7f commit 8564c65

File tree

7 files changed

+116
-4
lines changed

7 files changed

+116
-4
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: TlsCipherSuites
3+
---
4+
5+
# MySqlConnectionStringBuilder.TlsCipherSuites property
6+
7+
```csharp
8+
public string TlsCipherSuites { get; set; }
9+
```
10+
11+
## See Also
12+
13+
* class [MySqlConnectionStringBuilder](../../MySqlConnectionStringBuilderType/)
14+
* namespace [MySqlConnector](../../MySqlConnectionStringBuilderType/)
15+
* assembly [MySqlConnector](../../../MySqlConnectorAssembly/)
16+
17+
<!-- DO NOT EDIT: generated by xmldocmd for MySqlConnector.dll -->

docs/content/api/MySqlConnector/MySqlConnectionStringBuilderType.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public sealed class MySqlConnectionStringBuilder : DbConnectionStringBuilder
6060
| [SslCert](../MySqlConnectionStringBuilder/SslCert/) { getset; } | |
6161
| [SslKey](../MySqlConnectionStringBuilder/SslKey/) { getset; } | |
6262
| [SslMode](../MySqlConnectionStringBuilder/SslMode/) { getset; } | |
63+
| [TlsCipherSuites](../MySqlConnectionStringBuilder/TlsCipherSuites/) { getset; } | |
6364
| [TlsVersion](../MySqlConnectionStringBuilder/TlsVersion/) { getset; } | |
6465
| [TreatTinyAsBoolean](../MySqlConnectionStringBuilder/TreatTinyAsBoolean/) { getset; } | |
6566
| [UseAffectedRows](../MySqlConnectionStringBuilder/UseAffectedRows/) { getset; } | |

docs/content/connection-options.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ These are the options that need to be used in order to configure a connection to
133133
<td></td>
134134
<td>Specifies which certificate should be used from the Certificate Store specified in the setting above. This option must be used to indicate which certificate in the store should be used for authentication.</td>
135135
</tr>
136+
<tr id="TlsCipherSuites">
137+
<td>Tls Cipher Suites,TlsCipherSuites</td>
138+
<td></td>
139+
<td>Specifies which TLS cipher suites may be used during TLS negotiation. The default value (the empty string) allows the OS to determine the TLS cipher suites to use; this is the recommended setting. Otherwise, specify a comma-delimited list of <a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.security.tlsciphersuite"><code>TlsCipherSuite</code> enum values</a> to allow just those cipher suites. (This option is only supported on Linux when using .NET Core 3.1 or .NET 5.0 or later.)</td>
140+
</tr>
136141
<tr id="TlsVersion">
137142
<td>Tls Version, TlsVersion, Tls-Version</td>
138143
<td></td>

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Net.Security;
45
using System.Security.Authentication;
56
using MySqlConnector.Utilities;
67

@@ -87,6 +88,28 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
8788
throw new NotSupportedException("All specified TLS versions are incompatible with this platform.");
8889
}
8990

91+
if (csb.TlsCipherSuites != "")
92+
{
93+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_1
94+
throw new PlatformNotSupportedException("The TlsCipherSuites connection string option is only supported on .NET Core 3.1 (or later) on Linux.");
95+
#else
96+
var tlsCipherSuites = new List<TlsCipherSuite>();
97+
foreach (var token in csb.TlsCipherSuites.Split(','))
98+
{
99+
var suiteName = token.Trim();
100+
if (Enum.TryParse<TlsCipherSuite>(suiteName, ignoreCase: true, out var cipherSuite))
101+
tlsCipherSuites.Add(cipherSuite);
102+
else if (int.TryParse(suiteName, out var value) && Enum.IsDefined(typeof(TlsCipherSuite), value))
103+
tlsCipherSuites.Add((TlsCipherSuite) value);
104+
else if (Enum.TryParse<TlsCipherSuite>("TLS_" + suiteName, ignoreCase: true, out cipherSuite))
105+
tlsCipherSuites.Add(cipherSuite);
106+
else
107+
throw new NotSupportedException("Unknown value '{0}' for TlsCipherSuites.".FormatInvariant(suiteName));
108+
}
109+
TlsCipherSuites = tlsCipherSuites;
110+
#endif
111+
}
112+
90113
// Connection Pooling Options
91114
Pooling = csb.Pooling;
92115
ConnectionLifeTime = Math.Min(csb.ConnectionLifeTime, uint.MaxValue / 1000) * 1000;
@@ -176,6 +199,9 @@ private static MySqlGuidFormat GetEffectiveGuidFormat(MySqlGuidFormat guidFormat
176199
public MySqlCertificateStoreLocation CertificateStoreLocation { get; }
177200
public string CertificateThumbprint { get; }
178201
public SslProtocols TlsVersions { get; }
202+
#if !NET45 && !NET461 && !NET471 && !NETSTANDARD1_3 && !NETSTANDARD2_0 && !NETSTANDARD2_1 && !NETCOREAPP2_1
203+
public IReadOnlyList<TlsCipherSuite>? TlsCipherSuites { get; }
204+
#endif
179205

180206
// Connection Pooling Options
181207
public bool Pooling { get; }

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,26 +1317,57 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate? rcbCertificate
13171317
using (var initSsl = HandshakeResponse41Payload.CreateWithSsl(serverCapabilities, cs, m_useCompression, m_characterSet))
13181318
await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false);
13191319

1320+
var clientAuthenticationOptions = new SslClientAuthenticationOptions
1321+
{
1322+
EnabledSslProtocols = sslProtocols,
1323+
ClientCertificates = clientCertificates,
1324+
TargetHost = HostName,
1325+
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck
1326+
};
1327+
1328+
#if !NET45 && !NET461 && !NET471 && !NETSTANDARD1_3 && !NETSTANDARD2_0 && !NETSTANDARD2_1 && !NETCOREAPP2_1
1329+
if (cs.TlsCipherSuites is { Count: > 0 })
1330+
clientAuthenticationOptions.CipherSuitesPolicy = new CipherSuitesPolicy(cs.TlsCipherSuites);
1331+
#endif
1332+
13201333
try
13211334
{
13221335
if (ioBehavior == IOBehavior.Asynchronous)
13231336
{
1324-
await sslStream.AuthenticateAsClientAsync(HostName, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false);
1337+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0
1338+
await sslStream.AuthenticateAsClientAsync(clientAuthenticationOptions.TargetHost,
1339+
clientAuthenticationOptions.ClientCertificates,
1340+
clientAuthenticationOptions.EnabledSslProtocols,
1341+
checkCertificateRevocation).ConfigureAwait(false);
1342+
#else
1343+
await sslStream.AuthenticateAsClientAsync(clientAuthenticationOptions, cancellationToken).ConfigureAwait(false);
1344+
#endif
13251345
}
13261346
else
13271347
{
13281348
#if NETSTANDARD1_3
13291349
await sslStream.AuthenticateAsClientAsync(HostName, clientCertificates, sslProtocols, checkCertificateRevocation).ConfigureAwait(false);
1350+
#elif NET45 || NET461 || NET471 || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP3_1
1351+
sslStream.AuthenticateAsClient(clientAuthenticationOptions.TargetHost,
1352+
clientAuthenticationOptions.ClientCertificates,
1353+
clientAuthenticationOptions.EnabledSslProtocols,
1354+
checkCertificateRevocation);
13301355
#else
1331-
sslStream.AuthenticateAsClient(HostName, clientCertificates, sslProtocols, checkCertificateRevocation);
1356+
sslStream.AuthenticateAsClient(clientAuthenticationOptions);
13321357
#endif
13331358
}
13341359
var sslByteHandler = new StreamByteHandler(sslStream);
13351360
m_payloadHandler!.ByteHandler = sslByteHandler;
13361361
m_isSecureConnection = true;
13371362
m_sslStream = sslStream;
1338-
m_logArguments[1] = sslStream.SslProtocol;
1339-
Log.Info("Session{0} connected TLS with Protocol {1}", m_logArguments);
1363+
if (Log.IsInfoEnabled())
1364+
{
1365+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_1
1366+
Log.Info("Session{0} connected TLS with SslProtocol={1}, CipherAlgorithm={2}, HashAlgorithm={3}, KeyExchangeAlgorithm={4}, KeyExchangeStrength={5}", m_logArguments[0], sslStream.SslProtocol, sslStream.CipherAlgorithm, sslStream.HashAlgorithm, sslStream.KeyExchangeAlgorithm, sslStream.KeyExchangeStrength);
1367+
#else
1368+
Log.Info("Session{0} connected TLS with SslProtocol={1}, NegotiatedCipherSuite={2}", m_logArguments[0], sslStream.SslProtocol, sslStream.NegotiatedCipherSuite);
1369+
#endif
1370+
}
13401371
}
13411372
catch (Exception ex)
13421373
{
@@ -1364,6 +1395,17 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate? rcbCertificate
13641395
}
13651396
}
13661397

1398+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0
1399+
// a stripped-down version of this POCO options class for TFMs that don't have it buit-in
1400+
internal sealed class SslClientAuthenticationOptions
1401+
{
1402+
public X509RevocationMode CertificateRevocationCheckMode { get; set; }
1403+
public X509CertificateCollection? ClientCertificates { get; set; }
1404+
public SslProtocols EnabledSslProtocols { get; set; }
1405+
public string? TargetHost { get; set; }
1406+
}
1407+
#endif
1408+
13671409
// Some servers are exposed through a proxy, which handles the initial handshake and gives the proxy's
13681410
// server version and thread ID. Detect this situation and return `true` if the real server's details should
13691411
// be requested after connecting (which takes an extra roundtrip).

src/MySqlConnector/MySqlConnectionStringBuilder.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ public string TlsVersion
143143
set => MySqlConnectionStringOption.TlsVersion.SetValue(this, value);
144144
}
145145

146+
[AllowNull]
147+
public string TlsCipherSuites
148+
{
149+
get => MySqlConnectionStringOption.TlsCipherSuites.GetValue(this);
150+
set => MySqlConnectionStringOption.TlsCipherSuites.SetValue(this, value);
151+
}
152+
146153
// Connection Pooling Options
147154
public bool Pooling
148155
{
@@ -432,6 +439,7 @@ internal abstract class MySqlConnectionStringOption
432439
public static readonly MySqlConnectionStringReferenceOption<string> SslCert;
433440
public static readonly MySqlConnectionStringReferenceOption<string> SslKey;
434441
public static readonly MySqlConnectionStringReferenceOption<string> TlsVersion;
442+
public static readonly MySqlConnectionStringReferenceOption<string> TlsCipherSuites;
435443

436444
// Connection Pooling Options
437445
public static readonly MySqlConnectionStringValueOption<bool> Pooling;
@@ -602,6 +610,10 @@ static MySqlConnectionStringOption()
602610
return coercedValue;
603611
}));
604612

613+
AddOption(TlsCipherSuites = new(
614+
keys: new[] { "TlsCipherSuites", "Tls Cipher Suites" },
615+
defaultValue: ""));
616+
605617
// Connection Pooling Options
606618
AddOption(Pooling = new(
607619
keys: new[] { "Pooling" },

tests/MySqlConnector.Tests/MySqlConnectionStringBuilderTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public void Defaults()
7474
Assert.Equal("", csb.SslCa);
7575
Assert.Equal("", csb.SslCert);
7676
Assert.Equal("", csb.SslKey);
77+
Assert.Equal("", csb.TlsCipherSuites);
7778
Assert.Equal("", csb.TlsVersion);
7879
#else
7980
Assert.Null(csb.SslCa);
@@ -130,6 +131,7 @@ public void ParseConnectionString()
130131
"nobackslashescapes=true;" +
131132
"server spn=mariadb/[email protected];" +
132133
"use xa transactions=false;" +
134+
"tls cipher suites=TLS_AES_128_CCM_8_SHA256,TLS_RSA_WITH_RC4_128_MD5;" +
133135
#endif
134136
"ignore prepare=false;" +
135137
"interactive=true;" +
@@ -148,6 +150,7 @@ public void ParseConnectionString()
148150
"ssl-cert=client-cert.pem;" +
149151
"ssl-key=client-key.pem;" +
150152
"ssl mode=verifyca;" +
153+
"tls version=Tls10, TLS v1.2;" +
151154
"Uid=username;" +
152155
"useaffectedrows=true"
153156
};
@@ -187,6 +190,7 @@ public void ParseConnectionString()
187190
Assert.True(csb.NoBackslashEscapes);
188191
Assert.Equal("mariadb/[email protected]", csb.ServerSPN);
189192
Assert.False(csb.UseXaTransactions);
193+
Assert.Equal("TLS_AES_128_CCM_8_SHA256,TLS_RSA_WITH_RC4_128_MD5", csb.TlsCipherSuites);
190194
#endif
191195
Assert.False(csb.IgnorePrepare);
192196
Assert.True(csb.InteractiveSession);
@@ -205,6 +209,11 @@ public void ParseConnectionString()
205209
Assert.Equal("client-cert.pem", csb.SslCert);
206210
Assert.Equal("client-key.pem", csb.SslKey);
207211
Assert.Equal(MySqlSslMode.VerifyCA, csb.SslMode);
212+
#if BASELINE
213+
Assert.Equal("Tls, Tls12", csb.TlsVersion);
214+
#else
215+
Assert.Equal("TLS 1.0, TLS 1.2", csb.TlsVersion);
216+
#endif
208217
Assert.True(csb.UseAffectedRows);
209218
Assert.True(csb.UseCompression);
210219
Assert.Equal("username", csb.UserID);

0 commit comments

Comments
 (0)