Skip to content

Commit 9fbb2bf

Browse files
authored
Merge pull request #437 from caleblloyd/f_mutual_auth
Check for Private Key in CertificateFile.
2 parents 2749c5f + 7353ca6 commit 9fbb2bf

File tree

7 files changed

+55
-18
lines changed

7 files changed

+55
-18
lines changed

.ci/server/certs/non-ca-client.pfx

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

.ci/server/certs/ssl-client.pfx

0 Bytes
Binary file not shown.

docs/content/connection-options.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ These are the options that need to be used in order to configure a connection to
7777
<tr>
7878
<td>Certificate File, CertificateFile</td>
7979
<td></td>
80-
<td>Specifies the path to a certificate file in a PEM Encoded (.pem) or PKCS #12 (.pfx) format.</td>
80+
<td>Specifies the path to a certificate file in PKCS #12 (.pfx) format containing a bundled Certificate and Private Key used for Mutual Authentication. To create a PKCS #12 bundle from a PEM encoded Certificate and Key, use <code>openssl pkcs12 -in cert.pem -inkey key.pem -export -out bundle.pfx</code></td>
8181
</tr>
8282
<tr>
8383
<td>Certificate Password, CertificatePassword </td>

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,13 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect
772772
try
773773
{
774774
var certificate = new X509Certificate2(cs.CertificateFile, cs.CertificatePassword);
775+
if (!certificate.HasPrivateKey)
776+
{
777+
m_logArguments[1] = cs.CertificateFile;
778+
Log.Error("{0} no private key included with certificate '{1}'", m_logArguments);
779+
throw new MySqlException("CertificateFile does not contain a private key. "+
780+
"CertificateFile should be in PKCS #12 (.pfx) format and contain both a Certificate and Private Key");
781+
}
775782
#if !NET45
776783
m_clientCertificate = certificate;
777784
#endif
@@ -872,6 +879,7 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
872879
var sslByteHandler = new StreamByteHandler(sslStream);
873880
m_payloadHandler.ByteHandler = sslByteHandler;
874881
m_isSecureConnection = true;
882+
m_sslStream = sslStream;
875883
}
876884
catch (Exception ex)
877885
{
@@ -1057,6 +1065,14 @@ private void VerifyState(State state1, State state2, State state3)
10571065
}
10581066
}
10591067

1068+
internal bool SslIsEncrypted => m_sslStream != null && m_sslStream.IsEncrypted;
1069+
1070+
internal bool SslIsSigned => m_sslStream != null && m_sslStream.IsSigned;
1071+
1072+
internal bool SslIsAuthenticated => m_sslStream != null && m_sslStream.IsAuthenticated;
1073+
1074+
internal bool SslIsMutuallyAuthenticated => m_sslStream != null && m_sslStream.IsMutuallyAuthenticated;
1075+
10601076
private byte[] CreateConnectionAttributes()
10611077
{
10621078
Log.Debug("{0} creating connection attributes", m_logArguments);
@@ -1143,6 +1159,7 @@ private enum State
11431159
TcpClient m_tcpClient;
11441160
Socket m_socket;
11451161
NetworkStream m_networkStream;
1162+
SslStream m_sslStream;
11461163
#if !NET45
11471164
IDisposable m_clientCertificate;
11481165
IDisposable m_serverCertificate;

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnection.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,14 @@ private async Task<ServerSession> CreateSessionAsync(IOBehavior ioBehavior, Canc
405405
}
406406
}
407407

408+
internal bool SslIsEncrypted => m_session.SslIsEncrypted;
409+
410+
internal bool SslIsSigned => m_session.SslIsSigned;
411+
412+
internal bool SslIsAuthenticated => m_session.SslIsAuthenticated;
413+
414+
internal bool SslIsMutuallyAuthenticated => m_session.SslIsMutuallyAuthenticated;
415+
408416
internal void SetState(ConnectionState newState)
409417
{
410418
if (m_connectionState != newState)

tests/SideBySide/SslTests.cs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,33 @@ public SslTests(DatabaseFixture database)
1616
public async Task ConnectSslPreferred()
1717
{
1818
var csb = AppConfig.CreateConnectionStringBuilder();
19-
string requiredSslVersion;
20-
using (var connection = new MySqlConnection(csb.ConnectionString))
21-
{
22-
using (var cmd = connection.CreateCommand())
23-
{
24-
await connection.OpenAsync();
25-
cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'";
26-
requiredSslVersion = (string)await cmd.ExecuteScalarAsync();
27-
}
28-
}
29-
Assert.False(string.IsNullOrWhiteSpace(requiredSslVersion));
30-
3119
csb.SslMode = MySqlSslMode.Preferred;
20+
csb.CertificateFile = null;
21+
csb.CertificatePassword = null;
3222
using (var connection = new MySqlConnection(csb.ConnectionString))
3323
{
3424
using (var cmd = connection.CreateCommand())
3525
{
3626
await connection.OpenAsync();
27+
#if !BASELINE
28+
Assert.True(connection.SslIsEncrypted);
29+
Assert.True(connection.SslIsSigned);
30+
Assert.True(connection.SslIsAuthenticated);
31+
Assert.False(connection.SslIsMutuallyAuthenticated);
32+
#endif
3733
cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'";
38-
var preferredSslVersion = (string)await cmd.ExecuteScalarAsync();
39-
Assert.Equal(requiredSslVersion, preferredSslVersion);
34+
var sslVersion = (string)await cmd.ExecuteScalarAsync();
35+
Assert.False(string.IsNullOrWhiteSpace(sslVersion));
4036
}
4137
}
4238
}
4339

4440
[SkippableTheory(ConfigSettings.RequiresSsl | ConfigSettings.KnownClientCertificate)]
4541
[InlineData("ssl-client.pfx", null, null)]
4642
[InlineData("ssl-client-pw-test.pfx", "test", null)]
47-
[InlineData("ssl-client-cert.pem", null, null)]
4843
#if !BASELINE
4944
[InlineData("ssl-client.pfx", null, "ssl-ca-cert.pem")]
5045
[InlineData("ssl-client-pw-test.pfx", "test", "ssl-ca-cert.pem")]
51-
[InlineData("ssl-client-cert.pem", null, "ssl-ca-cert.pem")]
5246
#endif
5347
public async Task ConnectSslClientCertificate(string certFile, string certFilePassword, string caCertFile)
5448
{
@@ -67,13 +61,31 @@ public async Task ConnectSslClientCertificate(string certFile, string certFilePa
6761
using (var cmd = connection.CreateCommand())
6862
{
6963
await connection.OpenAsync();
64+
#if !BASELINE
65+
Assert.True(connection.SslIsEncrypted);
66+
Assert.True(connection.SslIsSigned);
67+
Assert.True(connection.SslIsAuthenticated);
68+
Assert.True(connection.SslIsMutuallyAuthenticated);
69+
#endif
7070
cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'";
7171
var sslVersion = (string)await cmd.ExecuteScalarAsync();
7272
Assert.False(string.IsNullOrWhiteSpace(sslVersion));
7373
}
7474
}
7575
}
7676

77+
[SkippableFact(ConfigSettings.RequiresSsl, Baseline = "MySql.Data does not check for a private key")]
78+
public async Task ConnectSslClientCertificateNoPrivateKey()
79+
{
80+
var csb = AppConfig.CreateConnectionStringBuilder();
81+
csb.CertificateFile = Path.Combine(AppConfig.CertsPath, "ssl-client-cert.pem");
82+
csb.SslMode = MySqlSslMode.Required;
83+
using (var connection = new MySqlConnection(csb.ConnectionString))
84+
{
85+
await Assert.ThrowsAsync<MySqlException>(async () => await connection.OpenAsync());
86+
}
87+
}
88+
7789
[SkippableFact(ServerFeatures.KnownCertificateAuthority, ConfigSettings.RequiresSsl)]
7890
public async Task ConnectSslBadClientCertificate()
7991
{
@@ -95,7 +107,7 @@ public async Task ConnectSslBadClientCertificate()
95107
public async Task ConnectSslBadCaCertificate()
96108
{
97109
var csb = AppConfig.CreateConnectionStringBuilder();
98-
csb.CertificateFile = Path.Combine(AppConfig.CertsPath, "ssl-client-cert.pem");
110+
csb.CertificateFile = Path.Combine(AppConfig.CertsPath, "ssl-client.pfx");
99111
csb.SslMode = MySqlSslMode.VerifyCA;
100112
#if !BASELINE
101113
csb.CACertificateFile = Path.Combine(AppConfig.CertsPath, "non-ca-client-cert.pem");

0 commit comments

Comments
 (0)