diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml
index 229f5eee58..c093bbd505 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml
@@ -37,5 +37,11 @@
Logs information through a specified method of the current instance type.
To be added.
+
+ The type to be logged.
+ The logging method.
+ The message to be logged.
+ Logs warning through a specified method of the current instance type.
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
index 1296e4afa3..a4eed7751b 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
@@ -225,7 +225,7 @@ internal struct SNI_Error
internal static extern uint SNITerminate();
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")]
- internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds);
+ internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
index 6c5bda96b2..bd3facd5fc 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
@@ -91,6 +91,10 @@ internal abstract class SNIHandle
public abstract void ReturnPacket(SNIPacket packet);
+ ///
+ /// Gets a value that indicates the security protocol used to authenticate this connection.
+ ///
+ public virtual int ProtocolVersion { get; } = 0;
#if DEBUG
///
/// Test handle for killing underlying connection
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs
index f3ed0c1be0..5877508942 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs
@@ -35,6 +35,8 @@ public Guid ConnectionId
}
}
+ public int ProtocolVersion => _lowerHandle.ProtocolVersion;
+
///
/// Constructor
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs
index 54572634cd..8c35907a8f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs
@@ -47,6 +47,8 @@ internal sealed class SNIMarsHandle : SNIHandle
public override int ReserveHeaderSize => SNISMUXHeader.HEADER_LENGTH;
+ public override int ProtocolVersion => _connection.ProtocolVersion;
+
///
/// Dispose object
///
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 ffda6c6248..362d4397d2 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
@@ -120,6 +120,21 @@ public override uint Status
}
}
+ public override int ProtocolVersion
+ {
+ get
+ {
+ try
+ {
+ return (int)_sslStream.SslProtocol;
+ }
+ catch
+ {
+ return base.ProtocolVersion;
+ }
+ }
+ }
+
public override uint CheckConnection()
{
long scopeID = SqlClientEventSource.Log.SNIScopeEnterEvent("");
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 158c13949a..e83e63882a 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
@@ -93,6 +93,21 @@ public override uint Status
}
}
+ public override int ProtocolVersion
+ {
+ get
+ {
+ try
+ {
+ return (int)_sslStream.SslProtocol;
+ }
+ catch
+ {
+ return base.ProtocolVersion;
+ }
+ }
+ }
+
///
/// Constructor
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
index 73eb6fd93f..1c875f5813 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
@@ -18,10 +18,9 @@ private void LoadSSPILibrary()
// No - Op
}
- private void WaitForSSLHandShakeToComplete(ref uint error)
+ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersion)
{
// No - Op
-
}
private SNIErrorDetails GetSniErrorDetails()
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
index 4e60f4e7f6..d5ddb5d47d 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
@@ -75,15 +75,13 @@ private void LoadSSPILibrary()
}
}
- private void WaitForSSLHandShakeToComplete(ref uint error)
+ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersion)
{
- if (TdsParserStateObjectFactory.UseManagedSNI)
- return;
// in the case where an async connection is made, encryption is used and Windows Authentication is used,
// wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
- error = _physicalStateObj.WaitForSSLHandShakeToComplete();
+ error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocolVersion);
if (error != TdsEnums.SNI_SUCCESS)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index eedcf86f8f..da350df284 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -10,6 +10,8 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Reflection;
+using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -19,6 +21,7 @@
using Microsoft.Data.SqlClient.DataClassification;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Data.SqlTypes;
+using Newtonsoft.Json.Serialization;
namespace Microsoft.Data.SqlClient
{
@@ -39,6 +42,9 @@ internal struct SNIErrorDetails
internal sealed partial class TdsParser
{
private static int _objectTypeCount; // EventSource counter
+ private readonly SqlClientLogger _logger = new SqlClientLogger();
+ private readonly string _typeName;
+
internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount);
internal int ObjectID => _objectID;
@@ -174,6 +180,7 @@ internal TdsParser(bool MARS, bool fAsynchronous)
_physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this);
DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
+ _typeName = GetType().Name;
}
internal SqlInternalConnectionTds Connection
@@ -906,7 +913,15 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus
ThrowExceptionAndWarning(_physicalStateObj);
}
- WaitForSSLHandShakeToComplete(ref error);
+ int protocolVersion = 0;
+ WaitForSSLHandShakeToComplete(ref error, ref protocolVersion);
+
+ SslProtocols protocol = (SslProtocols)protocolVersion;
+ string warningMessage = protocol.GetProtocolWarning();
+ if(!string.IsNullOrEmpty(warningMessage))
+ {
+ _logger.LogWarning(_typeName, MethodBase.GetCurrentMethod().Name, warningMessage);
+ }
// create a new packet encryption changes the internal packet size
_physicalStateObj.ClearAllWritePackets();
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index 1e24817b85..66c0966e7d 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -12,6 +12,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Security;
+using System.Security.Authentication;
using System.Text;
using Microsoft.Data.Common;
using Microsoft.Data.SqlTypes;
@@ -917,4 +918,64 @@ private void ParseMultipartName()
internal static readonly MultiPartTableName Null = new MultiPartTableName(new string[] { null, null, null, null });
}
+
+ internal static class SslProtocolsHelper
+ {
+ private static string ToFriendlyName(this SslProtocols protocol)
+ {
+ string name;
+
+ /* The SslProtocols.Tls13 is supported by netcoreapp3.1 and later
+ * This driver does not support this version yet!
+ if ((protocol & SslProtocols.Tls13) == SslProtocols.Tls13)
+ {
+ name = "TLS 1.3";
+ }*/
+ if((protocol & SslProtocols.Tls12) == SslProtocols.Tls12)
+ {
+ name = "TLS 1.2";
+ }
+ else if ((protocol & SslProtocols.Tls11) == SslProtocols.Tls11)
+ {
+ name = "TLS 1.1";
+ }
+ else if ((protocol & SslProtocols.Tls) == SslProtocols.Tls)
+ {
+ name = "TLS 1.0";
+ }
+#pragma warning disable CS0618 // Type or member is obsolete: SSL is depricated
+ else if ((protocol & SslProtocols.Ssl3) == SslProtocols.Ssl3)
+ {
+ name = "SSL 3.0";
+ }
+ else if ((protocol & SslProtocols.Ssl2) == SslProtocols.Ssl2)
+#pragma warning restore CS0618 // Type or member is obsolete: SSL is depricated
+ {
+ name = "SSL 2.0";
+ }
+ else
+ {
+ name = protocol.ToString();
+ }
+
+ return name;
+ }
+
+ ///
+ /// check the negotiated secure protocol if it's under TLS 1.2
+ ///
+ ///
+ /// Localized warning message
+ public static string GetProtocolWarning(this SslProtocols protocol)
+ {
+ string message = string.Empty;
+#pragma warning disable CS0618 // Type or member is obsolete : SSL is depricated
+ if ((protocol & (SslProtocols.Ssl2 | SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11)) != SslProtocols.None)
+#pragma warning restore CS0618 // Type or member is obsolete : SSL is depricated
+ {
+ message = SRHelper.Format(SR.SEC_ProtocolWarning, protocol.ToFriendlyName());
+ }
+ return message;
+ }
+ }
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index 9c197b9c37..6888ad0453 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -774,7 +774,7 @@ private void ResetCancelAndProcessAttention()
internal abstract uint EnableSsl(ref uint info);
- internal abstract uint WaitForSSLHandShakeToComplete();
+ internal abstract uint WaitForSSLHandShakeToComplete(out int protocolVersion);
internal abstract void Dispose();
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
index 46ae4c4dfb..6e25589986 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
@@ -220,6 +220,10 @@ internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint recei
return 0;
}
- internal override uint WaitForSSLHandShakeToComplete() => 0;
+ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion)
+ {
+ protocolVersion = Handle.ProtocolVersion;
+ return 0;
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
index 97825278ec..a38b5524df 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Security.Authentication;
using System.Threading.Tasks;
using Microsoft.Data.Common;
@@ -13,6 +14,25 @@ namespace Microsoft.Data.SqlClient
{
internal class TdsParserStateObjectNative : TdsParserStateObject
{
+ // protocol versions from native sni
+ [Flags]
+ private enum NativeProtocols
+ {
+ SP_PROT_SSL2_SERVER = 0x00000004,
+ SP_PROT_SSL2_CLIENT = 0x00000008,
+ SP_PROT_SSL3_SERVER = 0x00000010,
+ SP_PROT_SSL3_CLIENT = 0x00000020,
+ SP_PROT_TLS1_0_SERVER = 0x00000040,
+ SP_PROT_TLS1_0_CLIENT = 0x00000080,
+ SP_PROT_TLS1_1_SERVER = 0x00000100,
+ SP_PROT_TLS1_1_CLIENT = 0x00000200,
+ SP_PROT_TLS1_2_SERVER = 0x00000400,
+ SP_PROT_TLS1_2_CLIENT = 0x00000800,
+ SP_PROT_TLS1_3_SERVER = 0x00001000,
+ SP_PROT_TLS1_3_CLIENT = 0x00002000,
+ SP_PROT_NONE = 0x0
+ }
+
private SNIHandle _sessionHandle = null; // the SNI handle we're to work on
private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS
@@ -309,8 +329,49 @@ internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize)
internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[] _sniSpnBuffer)
=> SNINativeMethodWrapper.SNISecGenClientContext(Handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer);
- internal override uint WaitForSSLHandShakeToComplete()
- => SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(Handle, GetTimeoutRemaining());
+ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion)
+ {
+ uint returnValue = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(Handle, GetTimeoutRemaining(), out uint nativeProtocolVersion);
+ var nativeProtocol = (NativeProtocols)nativeProtocolVersion;
+
+ /* The SslProtocols.Tls13 is supported by netcoreapp3.1 and later
+ * This driver does not support this version yet!
+ if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls13;
+ }*/
+ if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls12;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls11;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER))
+ {
+#pragma warning disable CS0618 // Type or member is obsolete : SSL is depricated
+ protocolVersion = (int)SslProtocols.Ssl3;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL2_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL2_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Ssl2;
+#pragma warning restore CS0618 // Type or member is obsolete : SSL is depricated
+ }
+ else if(nativeProtocol.HasFlag(NativeProtocols.SP_PROT_NONE))
+ {
+ protocolVersion = (int)SslProtocols.None;
+ }
+ else
+ {
+ throw new ArgumentException(SRHelper.Format(SRHelper.net_invalid_enum, nameof(NativeProtocols)), nameof(NativeProtocols));
+ }
+ return returnValue;
+ }
internal override void DisposePacketCache()
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
index fd5175504c..23cbaa4efe 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
@@ -1428,6 +1428,15 @@ internal static string PlatformNotSupported_DataSqlClient {
}
}
+ ///
+ /// Looks up a localized string similar to Security Warning: The negotiated '{0}' is an insecured protocol and is supported for backward compatibility only. The recommended protocol is TLS 1.2 and later..
+ ///
+ internal static string SEC_ProtocolWarning {
+ get {
+ return ResourceManager.GetString("SEC_ProtocolWarning", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to I/O Error detected in read/write operation.
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
index bab71b9600..47d203f9ff 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
@@ -1860,6 +1860,9 @@
The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
A column order hint cannot have an unspecified sort order.
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
index dd585ededa..edfb5e960f 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
@@ -61,7 +61,7 @@ internal static class SNINativeManagedWrapperX64
internal static extern uint SNITerminate();
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")]
- internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds);
+ internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted);
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
index 339760c1a2..89c9af997b 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
@@ -61,7 +61,7 @@ internal static class SNINativeManagedWrapperX86
internal static extern uint SNITerminate();
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")]
- internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds);
+ internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted);
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
index 499b29b4d3..fefdeea4b7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
@@ -505,11 +505,11 @@ internal static uint SNITerminate()
SNINativeManagedWrapperX86.SNITerminate();
}
- internal static uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds)
+ internal static uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion)
{
return s_is64bitProcess ?
- SNINativeManagedWrapperX64.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds) :
- SNINativeManagedWrapperX86.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds);
+ SNINativeManagedWrapperX64.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion) :
+ SNINativeManagedWrapperX86.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion);
}
internal static uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
index 692d3ef85c..f847848eb5 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -9,6 +9,7 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
@@ -30,6 +31,9 @@ namespace Microsoft.Data.SqlClient
sealed internal class TdsParser
{
private static int _objectTypeCount; // EventSource Counter
+ private readonly SqlClientLogger _logger = new SqlClientLogger();
+ private readonly string _typeName;
+
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
static Task completedTask;
@@ -300,6 +304,7 @@ internal TdsParser(bool MARS, bool fAsynchronous)
_fMARS = MARS; // may change during Connect to pre Yukon servers
_physicalStateObj = new TdsParserStateObject(this);
DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
+ _typeName = GetType().Name;
}
internal SqlInternalConnectionTds Connection
@@ -1204,15 +1209,21 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod
// wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
- error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining());
+ error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining(), out uint protocolVersion);
+
if (error != TdsEnums.SNI_SUCCESS)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
ThrowExceptionAndWarning(_physicalStateObj);
}
+ string warningMessage = SslProtocolsHelper.GetProtocolWarning(protocolVersion);
+ if (!string.IsNullOrEmpty(warningMessage))
+ {
+ _logger.LogWarning(_typeName, MethodBase.GetCurrentMethod().Name, warningMessage);
+ }
+
// Validate server certificate
- //
if (serverCallback != null)
{
X509Certificate2 serverCert = null;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index 96eb11c0d2..873aeff914 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -1421,4 +1421,87 @@ private void ParseMultipartName()
internal static readonly MultiPartTableName Null = new MultiPartTableName(new string[] { null, null, null, null });
}
+
+ internal static class SslProtocolsHelper
+ {
+ // protocol versions from native sni
+ [Flags]
+ private enum NativeProtocols
+ {
+ SP_PROT_SSL2_SERVER = 0x00000004,
+ SP_PROT_SSL2_CLIENT = 0x00000008,
+ SP_PROT_SSL3_SERVER = 0x00000010,
+ SP_PROT_SSL3_CLIENT = 0x00000020,
+ SP_PROT_TLS1_0_SERVER = 0x00000040,
+ SP_PROT_TLS1_0_CLIENT = 0x00000080,
+ SP_PROT_TLS1_1_SERVER = 0x00000100,
+ SP_PROT_TLS1_1_CLIENT = 0x00000200,
+ SP_PROT_TLS1_2_SERVER = 0x00000400,
+ SP_PROT_TLS1_2_CLIENT = 0x00000800,
+ SP_PROT_TLS1_3_SERVER = 0x00001000,
+ SP_PROT_TLS1_3_CLIENT = 0x00002000,
+ SP_PROT_SSL2 = SP_PROT_SSL2_SERVER | SP_PROT_SSL2_CLIENT,
+ SP_PROT_SSL3 = SP_PROT_SSL3_SERVER | SP_PROT_SSL3_CLIENT,
+ SP_PROT_TLS1_0 = SP_PROT_TLS1_0_SERVER | SP_PROT_TLS1_0_CLIENT,
+ SP_PROT_TLS1_1 = SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_1_CLIENT,
+ SP_PROT_TLS1_2 = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_2_CLIENT,
+ SP_PROT_TLS1_3 = SP_PROT_TLS1_3_SERVER | SP_PROT_TLS1_3_CLIENT,
+ SP_PROT_NONE = 0x0
+ }
+
+ private static string ToFriendlyName(this NativeProtocols protocol)
+ {
+ string name;
+
+ if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_SERVER))
+ {
+ name = "TLS 1.3";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_SERVER))
+ {
+ name = "TLS 1.2";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_SERVER))
+ {
+ name = "TLS 1.1";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_SERVER))
+ {
+ name = "TLS 1.0";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER))
+ {
+ name = "SSL 3.0";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_SSL2_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_SSL2_SERVER))
+ {
+ name = "SSL 2.0";
+ }
+ else if(protocol.HasFlag(NativeProtocols.SP_PROT_NONE))
+ {
+ name = "None";
+ }
+ else
+ {
+ throw new ArgumentException(StringsHelper.GetString(StringsHelper.net_invalid_enum, nameof(NativeProtocols)), nameof(NativeProtocols));
+ }
+ return name;
+ }
+
+ ///
+ /// check the negotiated secure protocol if it's under TLS 1.2
+ ///
+ ///
+ /// Localized warning message
+ public static string GetProtocolWarning(uint protocol)
+ {
+ var nativeProtocol = (NativeProtocols)protocol;
+ string message = string.Empty;
+ if ((nativeProtocol & (NativeProtocols.SP_PROT_SSL2 | NativeProtocols.SP_PROT_SSL3 | NativeProtocols.SP_PROT_TLS1_1)) != NativeProtocols.SP_PROT_NONE)
+ {
+ message = StringsHelper.GetString(Strings.SEC_ProtocolWarning, nativeProtocol.ToFriendlyName());
+ }
+ return message;
+ }
+ }
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
index 9f40b57bdd..a4ca42aeb4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -7062,6 +7062,15 @@ internal static string NamedSimpleType_InvalidDuplicateNamedSimpleTypeDelaration
}
}
+ ///
+ /// Looks up a localized string similar to The specified value is not valid in the '{0}' enumeration..
+ ///
+ internal static string net_invalid_enum {
+ get {
+ return ResourceManager.GetString("net_invalid_enum", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to DateType column for field '{0}' in schema table is null. DataType must be non-null..
///
@@ -8082,6 +8091,15 @@ internal static string RecordManager_MinimumCapacity {
}
}
+ ///
+ /// Looks up a localized string similar to Security Warning: The negotiated '{0}' is an insecured protocol and is supported for backward compatibility only. The recommended protocol is TLS 1.2 and later..
+ ///
+ internal static string SEC_ProtocolWarning {
+ get {
+ return ResourceManager.GetString("SEC_ProtocolWarning", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to I/O Error detected in read/write operation.
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index 0bca401c21..3cc34302a4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4530,6 +4530,12 @@
UDT size must be less than {1}, size: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
The given column order hint is not valid.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs
index 5ff2f42e75..68afb07e49 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs
@@ -2,6 +2,8 @@
// 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;
+
namespace Microsoft.Data.SqlClient
{
///
@@ -10,7 +12,8 @@ public class SqlClientLogger
internal enum LogLevel
{
Info = 0,
- Error,
+ Warning,
+ Error
}
///
@@ -19,10 +22,18 @@ public void LogInfo(string type, string method, string message)
SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Info, message);
}
+ ///
+ public void LogWarning(string type, string method, string message)
+ {
+ Console.Out.WriteLine(message);
+ SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Warning, message);
+ }
+
///
public void LogError(string type, string method, string message)
{
- SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Info, message);
+ Console.Out.WriteLine(message);
+ SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Error, message);
}
///