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); } ///