Skip to content

Commit d7a6d8d

Browse files
committed
add CA Certificate validation
1 parent 989a8ec commit d7a6d8d

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

src/MySqlConnector/MySqlClient/MySqlConnectionStringBuilder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ public string CertificatePassword
6666
set => MySqlConnectionStringOption.CertificatePassword.SetValue(this, value);
6767
}
6868

69+
public string CACertificateFile
70+
{
71+
get => MySqlConnectionStringOption.CACertificateFile.GetValue(this);
72+
set => MySqlConnectionStringOption.CACertificateFile.SetValue(this, value);
73+
}
74+
6975
// Connection Pooling Options
7076
public bool Pooling
7177
{
@@ -238,6 +244,7 @@ internal abstract class MySqlConnectionStringOption
238244
public static readonly MySqlConnectionStringOption<MySqlSslMode> SslMode;
239245
public static readonly MySqlConnectionStringOption<string> CertificateFile;
240246
public static readonly MySqlConnectionStringOption<string> CertificatePassword;
247+
public static readonly MySqlConnectionStringOption<string> CACertificateFile;
241248

242249
// Connection Pooling Options
243250
public static readonly MySqlConnectionStringOption<bool> Pooling;
@@ -322,6 +329,10 @@ static MySqlConnectionStringOption()
322329
keys: new[] { "CertificatePassword", "Certificate Password" },
323330
defaultValue: null));
324331

332+
AddOption(CACertificateFile = new MySqlConnectionStringOption<string>(
333+
keys: new[] { "CACertificateFile", "CA Certificate File" },
334+
defaultValue: null));
335+
325336
// Connection Pooling Options
326337
AddOption(Pooling = new MySqlConnectionStringOption<bool>(
327338
keys: new[] { "Pooling" },

src/MySqlConnector/Serialization/ConnectionSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
3333
SslMode = csb.SslMode;
3434
CertificateFile = csb.CertificateFile;
3535
CertificatePassword = csb.CertificatePassword;
36+
CACertificateFile = csb.CACertificateFile;
3637

3738
// Connection Pooling Options
3839
Pooling = csb.Pooling;
@@ -73,6 +74,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
7374
public MySqlSslMode SslMode { get; }
7475
public string CertificateFile { get; }
7576
public string CertificatePassword { get; }
77+
public string CACertificateFile { get; }
7678

7779
// Connection Pooling Options
7880
public bool Pooling { get; }

src/MySqlConnector/Serialization/MySqlSession.cs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -537,36 +537,66 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect
537537
catch (CryptographicException ex)
538538
{
539539
if (!File.Exists(cs.CertificateFile))
540-
throw new MySqlException("Cannot find SSL Certificate File", ex);
541-
throw new MySqlException("Either the SSL Certificate Password is incorrect or the SSL Certificate File is invalid", ex);
540+
throw new MySqlException("Cannot find Certificate File", ex);
541+
throw new MySqlException("Either the Certificate Password is incorrect or the Certificate File is invalid", ex);
542542
}
543543
}
544544

545-
Func<object, string, X509CertificateCollection, X509Certificate, string[], X509Certificate> localCertificateCb =
546-
(lcbSender, lcbTargetHost, lcbLocalCertificates, lcbRemoteCertificate, lcbAcceptableIssuers) => lcbLocalCertificates[0];
545+
X509Chain caCertificateChain = null;
546+
if (cs.CACertificateFile != null)
547+
{
548+
try
549+
{
550+
var caCertificate = new X509Certificate2(cs.CACertificateFile);
551+
caCertificateChain = new X509Chain
552+
{
553+
ChainPolicy =
554+
{
555+
RevocationMode = X509RevocationMode.NoCheck,
556+
VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority
557+
}
558+
};
559+
caCertificateChain.ChainPolicy.ExtraStore.Add(caCertificate);
560+
}
561+
catch (CryptographicException ex)
562+
{
563+
if (!File.Exists(cs.CACertificateFile))
564+
throw new MySqlException("Cannot find CA Certificate File", ex);
565+
throw new MySqlException("The CA Certificate File is invalid", ex);
566+
}
567+
}
568+
569+
X509Certificate LocalCertificateCb(object lcbSender, string lcbTargetHost, X509CertificateCollection lcbLocalCertificates, X509Certificate lcbRemoteCertificate, string[] lcbAcceptableIssuers) => lcbLocalCertificates[0];
570+
571+
bool RemoteCertificateCb(object rcbSender, X509Certificate rcbCertificate, X509Chain rcbChain, SslPolicyErrors rcbPolicyErrors)
572+
{
573+
if (cs.SslMode == MySqlSslMode.Preferred || cs.SslMode == MySqlSslMode.Required)
574+
return true;
547575

548-
Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> remoteCertificateCb =
549-
(rcbSender, rcbCertificate, rcbChain, rcbPolicyErrors) =>
576+
if ((rcbPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0 && caCertificateChain != null)
550577
{
551-
switch (rcbPolicyErrors)
578+
if (caCertificateChain.Build((X509Certificate2) rcbCertificate))
552579
{
553-
case SslPolicyErrors.None:
554-
return true;
555-
case SslPolicyErrors.RemoteCertificateNameMismatch:
556-
return cs.SslMode != MySqlSslMode.VerifyFull;
557-
default:
558-
return cs.SslMode == MySqlSslMode.Preferred || cs.SslMode == MySqlSslMode.Required;
580+
var chainStatus = caCertificateChain.ChainStatus[0].Status & ~X509ChainStatusFlags.UntrustedRoot;
581+
if (chainStatus == X509ChainStatusFlags.NoError)
582+
rcbPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
559583
}
560-
};
584+
}
585+
586+
if (cs.SslMode == MySqlSslMode.VerifyCA)
587+
rcbPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch;
588+
589+
return rcbPolicyErrors == SslPolicyErrors.None;
590+
}
561591

562592
SslStream sslStream;
563593
if (clientCertificates == null)
564594
sslStream = new SslStream(m_networkStream, false,
565-
new RemoteCertificateValidationCallback(remoteCertificateCb));
595+
new RemoteCertificateValidationCallback((Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool>) RemoteCertificateCb));
566596
else
567597
sslStream = new SslStream(m_networkStream, false,
568-
new RemoteCertificateValidationCallback(remoteCertificateCb),
569-
new LocalCertificateSelectionCallback(localCertificateCb));
598+
new RemoteCertificateValidationCallback((Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool>) RemoteCertificateCb),
599+
new LocalCertificateSelectionCallback((Func<object, string, X509CertificateCollection, X509Certificate, string[], X509Certificate>) LocalCertificateCb));
570600

571601
// SslProtocols.Tls1.2 throws an exception in Windows, see https://github.com/mysql-net/MySqlConnector/pull/101
572602
var sslProtocols = SslProtocols.Tls | SslProtocols.Tls11;

0 commit comments

Comments
 (0)