diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs
index 2e5d3fd815..f8f2facb59 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs
@@ -138,196 +138,154 @@ internal class SNICommon
internal const int LocalDBBadRuntime = 57;
///
- /// We only validate Server name in Certificate to match with "targetServerName".
+ /// We either validate that the provided 'validationCert' matches the 'serverCert', or we validate that the server name in the 'serverCert' matches 'targetServerName'.
/// Certificate validation and chain trust validations are done by SSLStream class [System.Net.Security.SecureChannel.VerifyRemoteCertificate method]
/// This method is called as a result of callback for SSL Stream Certificate validation.
///
+ /// Connection ID/GUID for tracing
/// Server that client is expecting to connect to
- /// X.509 certificate
+ /// Optional hostname to use for server certificate validation
+ /// X.509 certificate from the server
+ /// Path to an X.509 certificate file from the application to compare with the serverCert
/// Policy errors
/// True if certificate is valid
- internal static bool ValidateSslServerCertificate(string targetServerName, X509Certificate cert, SslPolicyErrors policyErrors)
+ internal static bool ValidateSslServerCertificate(Guid connectionId, string targetServerName, string hostNameInCertificate, X509Certificate serverCert, string validationCertFileName, SslPolicyErrors policyErrors)
{
using (TrySNIEventScope.Create("SNICommon.ValidateSslServerCertificate | SNI | SCOPE | INFO | Entering Scope {0} "))
{
if (policyErrors == SslPolicyErrors.None)
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "targetServerName {0}, SSL Server certificate not validated as PolicyErrors set to None.", args0: targetServerName);
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, targetServerName {1}, SSL Server certificate not validated as PolicyErrors set to None.", args0: connectionId, args1: targetServerName);
return true;
}
- // If we get to this point then there is a ssl policy flag.
- StringBuilder messageBuilder = new();
- if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
+ string serverNameToValidate;
+ X509Certificate validationCertificate = null;
+ if (!string.IsNullOrEmpty(hostNameInCertificate))
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, SslPolicyError {1}, SSL Policy certificate chain has errors.", args0: targetServerName, args1: policyErrors);
+ serverNameToValidate = hostNameInCertificate;
+ }
+ else
+ {
+ serverNameToValidate = targetServerName;
+ }
- // get the chain status from the certificate
- X509Certificate2 cert2 = cert as X509Certificate2;
- X509Chain chain = new();
- chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;
- StringBuilder chainStatusInformation = new();
- bool chainIsValid = chain.Build(cert2);
- Debug.Assert(!chainIsValid, "RemoteCertificateChainError flag is detected, but certificate chain is valid.");
- if (!chainIsValid)
+ if (!string.IsNullOrEmpty(validationCertFileName))
+ {
+ try
{
- foreach (X509ChainStatus chainStatus in chain.ChainStatus)
- {
- chainStatusInformation.Append($"{chainStatus.StatusInformation}, [Status: {chainStatus.Status}]");
- chainStatusInformation.AppendLine();
- }
+ validationCertificate = new X509Certificate(validationCertFileName);
+ }
+ catch (Exception e)
+ {
+ // if this fails, then fall back to the HostNameInCertificate or TargetServer validation.
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Exception occurred loading specified ServerCertificate: {1}, treating it as if ServerCertificate has not been specified.", args0: connectionId, args1: e.Message);
}
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, SslPolicyError {1}, SSL Policy certificate chain has errors. ChainStatus {2}", args0: targetServerName, args1: policyErrors, args2: chainStatusInformation);
- messageBuilder.AppendFormat(Strings.SQL_RemoteCertificateChainErrors, chainStatusInformation);
- messageBuilder.AppendLine();
}
- if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
+ if (validationCertificate != null)
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, SSL Policy invalidated certificate.", args0: targetServerName);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNotAvailable);
+ if (serverCert.GetRawCertData().AsSpan().SequenceEqual(validationCertificate.GetRawCertData().AsSpan()))
+ {
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, ServerCertificate matches the certificate provided by the server. Certificate validation passed.", args0: connectionId);
+ return true;
+ }
+ else
+ {
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, ServerCertificate doesn't match the certificate provided by the server. Certificate validation failed.", args0: connectionId);
+ throw ADP.SSLCertificateAuthenticationException(Strings.SQL_RemoteCertificateDoesNotMatchServerCertificate);
+ }
}
-
- if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
+ else
{
-#if NET8_0_OR_GREATER
- X509Certificate2 cert2 = cert as X509Certificate2;
- if (!cert2.MatchesHostname(targetServerName))
+ // If we get to this point then there is a ssl policy flag.
+ StringBuilder messageBuilder = new();
+ if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, Target Server name or HNIC does not match the Subject/SAN in Certificate.", args0: targetServerName);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, targetServerName {1}, SSL Server certificate not validated as PolicyErrors set to RemoteCertificateNotAvailable.", args0: connectionId, args1: targetServerName);
+ messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNotAvailable);
}
-#else
- // To Do: include certificate SAN (Subject Alternative Name) check.
- string certServerName = cert.Subject.Substring(cert.Subject.IndexOf('=') + 1);
- // Verify that target server name matches subject in the certificate
- if (targetServerName.Length > certServerName.Length)
- {
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, Target Server name is of greater length than Subject in Certificate.", args0: targetServerName);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
- }
- else if (targetServerName.Length == certServerName.Length)
+ if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
{
- // Both strings have the same length, so targetServerName must be a FQDN
- if (!targetServerName.Equals(certServerName, StringComparison.OrdinalIgnoreCase))
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, targetServerName {0}, SslPolicyError {1}, SSL Policy certificate chain has errors.", args0: connectionId, args1: targetServerName, args2: policyErrors);
+
+ // get the chain status from the certificate
+ X509Certificate2 cert2 = serverCert as X509Certificate2;
+ X509Chain chain = new();
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;
+ StringBuilder chainStatusInformation = new();
+ bool chainIsValid = chain.Build(cert2);
+ Debug.Assert(!chainIsValid, "RemoteCertificateChainError flag is detected, but certificate chain is valid.");
+ if (!chainIsValid)
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, Target Server name does not match Subject in Certificate.", args0: targetServerName);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ foreach (X509ChainStatus chainStatus in chain.ChainStatus)
+ {
+ chainStatusInformation.Append($"{chainStatus.StatusInformation}, [Status: {chainStatus.Status}]");
+ chainStatusInformation.AppendLine();
+ }
}
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, targetServerName {1}, SslPolicyError {2}, SSL Policy certificate chain has errors. ChainStatus {3}", args0: connectionId, args1: targetServerName, args2: policyErrors, args3: chainStatusInformation);
+ messageBuilder.AppendFormat(Strings.SQL_RemoteCertificateChainErrors, chainStatusInformation);
+ messageBuilder.AppendLine();
}
- else
+
+ if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
- if (string.Compare(targetServerName, 0, certServerName, 0, targetServerName.Length, StringComparison.OrdinalIgnoreCase) != 0)
+#if NET8_0_OR_GREATER
+ X509Certificate2 cert2 = serverCert as X509Certificate2;
+ if (!cert2.MatchesHostname(serverNameToValidate))
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, Target Server name does not match Subject in Certificate.", args0: targetServerName);
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name or HNIC does not match the Subject/SAN in Certificate.", args0: connectionId, args1: serverNameToValidate);
messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
}
+#else
+ // To Do: include certificate SAN (Subject Alternative Name) check.
+ string certServerName = serverCert.Subject.Substring(serverCert.Subject.IndexOf('=') + 1);
- // Server name matches cert name for its whole length, so ensure that the
- // character following the server name is a '.'. This will avoid
- // having server name "ab" match "abc.corp.company.com"
- // (Names have different lengths, so the target server can't be a FQDN.)
- if (certServerName[targetServerName.Length] != '.')
+ // Verify that target server name matches subject in the certificate
+ if (serverNameToValidate.Length > certServerName.Length)
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "targetServerName {0}, Target Server name does not match Subject in Certificate.", args0: targetServerName);
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name is of greater length than Subject in Certificate.", args0: connectionId, args1: serverNameToValidate);
messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
}
- }
-#endif
- }
-
- if (messageBuilder.Length > 0)
- {
- throw ADP.SSLCertificateAuthenticationException(messageBuilder.ToString());
- }
-
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, " Remote certificate with subject: {0}, validated successfully.", args0: cert.Subject);
- return true;
- }
- }
-
- ///
- /// We validate the provided certificate provided by the client with the one from the server to see if it matches.
- /// Certificate validation and chain trust validations are done by SSLStream class [System.Net.Security.SecureChannel.VerifyRemoteCertificate method]
- /// This method is called as a result of callback for SSL Stream Certificate validation.
- ///
- /// X.509 certificate provided by the client
- /// X.509 certificate provided by the server
- /// Policy errors
- /// True if certificate is valid
- internal static bool ValidateSslServerCertificate(X509Certificate clientCert, X509Certificate serverCert, SslPolicyErrors policyErrors)
- {
- using (TrySNIEventScope.Create("SNICommon.ValidateSslServerCertificate | SNI | SCOPE | INFO | Entering Scope {0} "))
- {
- if (policyErrors == SslPolicyErrors.None)
- {
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "serverCert {0}, SSL Server certificate not validated as PolicyErrors set to None.", args0: clientCert.Subject);
- return true;
- }
-
- StringBuilder messageBuilder = new();
- if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
- {
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "serverCert {0}, SSL Server certificate not validated as PolicyErrors set to RemoteCertificateNotAvailable.", args0: clientCert.Subject);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNotAvailable);
- }
-
- if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
- {
- // get the chain status from the server certificate
- X509Certificate2 cert2 = serverCert as X509Certificate2;
- X509Chain chain = new();
- chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;
- StringBuilder chainStatusInformation = new();
- bool chainIsValid = chain.Build(cert2);
- Debug.Assert(!chainIsValid, "RemoteCertificateChainError flag is detected, but certificate chain is valid.");
- if (!chainIsValid)
- {
- foreach (X509ChainStatus chainStatus in chain.ChainStatus)
+ else if (serverNameToValidate.Length == certServerName.Length)
{
- chainStatusInformation.Append($"{chainStatus.StatusInformation}, [Status: {chainStatus.Status}]");
- chainStatusInformation.AppendLine();
+ // Both strings have the same length, so serverNameToValidate must be a FQDN
+ if (!serverNameToValidate.Equals(certServerName, StringComparison.OrdinalIgnoreCase))
+ {
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name does not match Subject in Certificate.", args0: connectionId, args1: serverNameToValidate);
+ messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ }
}
- }
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "certificate subject from server is {0}, and does not match with the certificate provided client.", args0: cert2.SubjectName.Name);
- messageBuilder.AppendFormat(Strings.SQL_RemoteCertificateChainErrors, chainStatusInformation);
- messageBuilder.AppendLine();
- }
-
- if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
- {
-#if NET8_0_OR_GREATER
- X509Certificate2 s_cert = serverCert as X509Certificate2;
- X509Certificate2 c_cert = clientCert as X509Certificate2;
-
- if (!s_cert.MatchesHostname(c_cert.SubjectName.Name))
- {
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "certificate from server does not match with the certificate provided client.", args0: s_cert.Subject);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
- }
-#else
- // Verify that subject name matches
- if (serverCert.Subject != clientCert.Subject)
- {
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "certificate subject from server is {0}, and does not match with the certificate provided client.", args0: serverCert.Subject);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ else
+ {
+ if (string.Compare(serverNameToValidate, 0, certServerName, 0, serverNameToValidate.Length, StringComparison.OrdinalIgnoreCase) != 0)
+ {
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name does not match Subject in Certificate.", args0: connectionId, args1: serverNameToValidate);
+ messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ }
+
+ // Server name matches cert name for its whole length, so ensure that the
+ // character following the server name is a '.'. This will avoid
+ // having server name "ab" match "abc.corp.company.com"
+ // (Names have different lengths, so the target server can't be a FQDN.)
+ if (certServerName[serverNameToValidate.Length] != '.')
+ {
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name does not match Subject in Certificate.", args0: connectionId, args1: serverNameToValidate);
+ messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ }
+ }
+#endif
}
- if (!serverCert.Equals(clientCert))
+ if (messageBuilder.Length > 0)
{
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "certificate from server does not match with the certificate provided client.", args0: serverCert.Subject);
- messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch);
+ throw ADP.SSLCertificateAuthenticationException(messageBuilder.ToString());
}
-#endif
}
- if (messageBuilder.Length > 0)
- {
- throw ADP.SSLCertificateAuthenticationException(messageBuilder.ToString());
- }
-
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "certificate subject {0}, Client certificate validated successfully.", args0: clientCert.Subject);
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, certificate with subject: {1}, validated successfully.", args0: connectionId, args1: serverCert.Subject);
return true;
}
}
@@ -342,11 +300,11 @@ internal static IPAddress[] GetDnsIpAddresses(string serverName, TimeoutTimer ti
args0: serverName,
args1: remainingTimeout);
using CancellationTokenSource cts = new CancellationTokenSource(remainingTimeout);
-
- return Dns.GetHostAddressesAsync(serverName, cts.Token)
- .ConfigureAwait(false)
- .GetAwaiter()
- .GetResult();
+ // using this overload to support netstandard
+ Task task = Dns.GetHostAddressesAsync(serverName);
+ task.ConfigureAwait(false);
+ task.Wait(cts.Token);
+ return task.Result;
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
index 2889ce6bb4..8f8af57f58 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
@@ -24,6 +24,8 @@ internal sealed class SNINpHandle : SNIPhysicalHandle
private readonly string _targetServer;
private readonly object _sendSync;
+ private readonly string _hostNameInCertificate;
+ private readonly string _serverCertificateFilename;
private readonly bool _tlsFirst;
private Stream _stream;
private NamedPipeClientStream _pipeStream;
@@ -38,7 +40,7 @@ internal sealed class SNINpHandle : SNIPhysicalHandle
private int _bufferSize = TdsEnums.DEFAULT_LOGIN_PACKET_SIZE;
private readonly Guid _connectionId = Guid.NewGuid();
- public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, bool tlsFirst)
+ public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, bool tlsFirst, string hostNameInCertificate, string serverCertificateFilename)
{
using (TrySNIEventScope.Create(nameof(SNINpHandle)))
{
@@ -47,6 +49,8 @@ public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, boo
_sendSync = new object();
_targetServer = serverName;
_tlsFirst = tlsFirst;
+ _hostNameInCertificate = hostNameInCertificate;
+ _serverCertificateFilename = serverCertificateFilename;
try
{
_pipeStream = new NamedPipeClientStream(
@@ -369,14 +373,14 @@ public override void DisableSsl()
/// Validate server certificate
///
/// Sender object
- /// X.509 certificate
+ /// X.509 certificate
/// X.509 chain
/// Policy errors
/// true if valid
- private bool ValidateServerCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
+ private bool ValidateServerCertificate(object sender, X509Certificate serverCertificate, X509Chain chain, SslPolicyErrors policyErrors)
{
using (TrySNIEventScope.Create(nameof(SNINpHandle)))
- {
+ {
if (!_validateCert)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Certificate validation not requested.", args0: ConnectionId);
@@ -384,8 +388,8 @@ private bool ValidateServerCertificate(object sender, X509Certificate cert, X509
}
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Proceeding to SSL certificate validation.", args0: ConnectionId);
- return SNICommon.ValidateSslServerCertificate(_targetServer, cert, policyErrors);
- }
+ return SNICommon.ValidateSslServerCertificate(_connectionId, _targetServer, _hostNameInCertificate, serverCertificate, _serverCertificateFilename, policyErrors);
+ }
}
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
index 77b44a1cf5..0ee31e5f46 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
@@ -206,7 +206,7 @@ internal static SNIHandle CreateConnectionHandle(
tlsFirst, hostNameInCertificate, serverCertificateFilename);
break;
case DataSource.Protocol.NP:
- sniHandle = CreateNpHandle(details, timeout, parallel, tlsFirst);
+ sniHandle = CreateNpHandle(details, timeout, parallel, tlsFirst, hostNameInCertificate, serverCertificateFilename);
break;
default:
Debug.Fail($"Unexpected connection protocol: {details._connectionProtocol}");
@@ -362,8 +362,10 @@ private static SNITCPHandle CreateTcpHandle(
/// Timer expiration
/// Should MultiSubnetFailover be used. Only returns an error for named pipes.
///
+ /// Host name in certificate
+ /// Used for the path to the Server Certificate
/// SNINpHandle
- private static SNINpHandle CreateNpHandle(DataSource details, TimeoutTimer timeout, bool parallel, bool tlsFirst)
+ private static SNINpHandle CreateNpHandle(DataSource details, TimeoutTimer timeout, bool parallel, bool tlsFirst, string hostNameInCertificate, string serverCertificateFilename)
{
if (parallel)
{
@@ -371,7 +373,7 @@ private static SNINpHandle CreateNpHandle(DataSource details, TimeoutTimer timeo
SNICommon.ReportSNIError(SNIProviders.NP_PROV, 0, SNICommon.MultiSubnetFailoverWithNonTcpProtocol, Strings.SNI_ERROR_49);
return null;
}
- return new SNINpHandle(details.PipeHostName, details.PipeName, timeout, tlsFirst);
+ return new SNINpHandle(details.PipeHostName, details.PipeName, timeout, tlsFirst, hostNameInCertificate, serverCertificateFilename);
}
///
@@ -539,8 +541,10 @@ private void PopulateProtocol()
internal static string GetLocalDBInstance(string dataSource, out bool error)
{
string instanceName = null;
+ // ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
ReadOnlySpan input = dataSource.AsSpan().TrimStart();
error = false;
+ // NetStandard 2.0 does not support passing a string to ReadOnlySpan
int index = input.IndexOf(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase);
if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
index 9d415bcfc8..2791de17a4 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
@@ -644,7 +644,6 @@ public override uint EnableSsl(uint options)
}
else
{
- // TODO: Resolve whether to send _serverNameIndication or _targetServer. _serverNameIndication currently results in error. Why?
_sslStream.AuthenticateAsClient(_targetServer, null, s_supportedProtocols, false);
}
if (_sslOverTdsStream is not null)
@@ -698,33 +697,8 @@ private bool ValidateServerCertificate(object sender, X509Certificate serverCert
return true;
}
- string serverNameToValidate;
- if (!string.IsNullOrEmpty(_hostNameInCertificate))
- {
- serverNameToValidate = _hostNameInCertificate;
- }
- else
- {
- serverNameToValidate = _targetServer;
- }
-
- if (!string.IsNullOrEmpty(_serverCertificateFilename))
- {
- X509Certificate clientCertificate = null;
- try
- {
- clientCertificate = new X509Certificate(_serverCertificateFilename);
- return SNICommon.ValidateSslServerCertificate(clientCertificate, serverCertificate, policyErrors);
- }
- catch (Exception e)
- {
- // if this fails, then fall back to the HostNameInCertificate or TargetServer validation.
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, IOException occurred: {1}", args0: _connectionId, args1: e.Message);
- }
- }
-
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Certificate will be validated for Target Server name", args0: _connectionId);
- return SNICommon.ValidateSslServerCertificate(serverNameToValidate, serverCertificate, policyErrors);
+ return SNICommon.ValidateSslServerCertificate(_connectionId, _targetServer, _hostNameInCertificate, serverCertificate, _serverCertificateFilename, policyErrors);
}
///
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
index 533ffa86ad..a685787e9e 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
@@ -10215,6 +10215,15 @@ internal static string SQL_RemoteCertificateChainErrors {
}
}
+ ///
+ /// Looks up a localized string similar to The certificate provided by the server does not match the certificate provided by the ServerCertificate option..
+ ///
+ internal static string SQL_RemoteCertificateDoesNotMatchServerCertificate {
+ get {
+ return ResourceManager.GetString("SQL_RemoteCertificateDoesNotMatchServerCertificate", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Certificate name mismatch. The provided 'DataSource' or 'HostNameInCertificate' does not match the name in the certificate..
///
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
index 3fb2e0a74a..c2dd68b867 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
@@ -4737,4 +4737,7 @@
Certificate not available while validating the certificate.
+
+ The certificate provided by the server does not match the certificate provided by the ServerCertificate option.
+
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
index 8f16c09aa3..a164149a60 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
@@ -12,6 +12,7 @@
using System.Security;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.SqlServer.TDS.PreLogin;
using Microsoft.SqlServer.TDS.Servers;
using Xunit;
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs
index 1ead74f58d..ef45bdbc7a 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs
@@ -65,6 +65,5 @@ public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool ena
public void Dispose() => _endpoint?.Stop();
public string ConnectionString { get; private set; }
-
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs
new file mode 100644
index 0000000000..4b6e7b087b
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.SqlServer.TDS.PreLogin;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon
+{
+ public class ConnectionTestParameters
+ {
+ private SqlConnectionEncryptOption _encryptionOption;
+ private TDSPreLoginTokenEncryptionType _encryptionType;
+ private string _hnic;
+ private string _cert;
+ private bool _result;
+ private bool _trustServerCert;
+
+ public SqlConnectionEncryptOption Encrypt => _encryptionOption;
+ public bool TrustServerCertificate => _trustServerCert;
+ public string Certificate => _cert;
+ public string HostNameInCertificate => _hnic;
+ public bool TestResult => _result;
+ public TDSPreLoginTokenEncryptionType TdsEncryptionType => _encryptionType;
+
+ public ConnectionTestParameters(TDSPreLoginTokenEncryptionType tdsEncryptionType, SqlConnectionEncryptOption encryptOption, bool trustServerCert, string cert, string hnic, bool result)
+ {
+ _encryptionOption = encryptOption;
+ _trustServerCert = trustServerCert;
+ _cert = cert;
+ _hnic = hnic;
+ _result = result;
+ _encryptionType = tdsEncryptionType;
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs
new file mode 100644
index 0000000000..5a2e01a77c
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs
@@ -0,0 +1,85 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.SqlServer.TDS.PreLogin;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon
+{
+ public class ConnectionTestParametersData
+ {
+ private const int CASES = 30;
+ private string _empty = string.Empty;
+ // It was advised to store the client certificate in its own folder.
+ private static readonly string s_fullPathToCer = Path.Combine(Directory.GetCurrentDirectory(), "clientcert", "localhostcert.cer");
+ private static readonly string s_mismatchedcert = Path.Combine(Directory.GetCurrentDirectory(), "clientcert", "mismatchedcert.cer");
+
+ private static readonly string s_hostName = System.Net.Dns.GetHostName();
+ public static ConnectionTestParametersData Data { get; } = new ConnectionTestParametersData();
+ public List ConnectionTestParametersList { get; set; }
+
+ public static IEnumerable