diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 2035f24097..1f81fe403f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -656,11 +656,14 @@ Microsoft\Data\SqlClient\SqlUtil.cs - - Microsoft\Data\SqlClient\SSPI\NegotiateSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs - - Microsoft\Data\SqlClient\SSPI\SSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\SspiContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs Microsoft\Data\SqlClient\Utilities\ObjectPool.cs @@ -887,8 +890,8 @@ Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.Windows.cs - - Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.cs Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.Windows.cs 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 5fbfb51673..d66984d8b7 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 @@ -44,7 +44,7 @@ internal sealed partial class TdsParser private static int _objectTypeCount; // EventSource counter private readonly SqlClientLogger _logger = new SqlClientLogger(); - private SSPIContextProvider _authenticationProvider; + private SspiContextProvider _authenticationProvider; internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); internal int ObjectID => _objectID; @@ -413,7 +413,7 @@ internal void Connect(ServerInfo serverInfo, // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - _authenticationProvider = _physicalStateObj.CreateSSPIContextProvider(); + _authenticationProvider = _physicalStateObj.CreateSspiContextProvider(); SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } 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 3a709d03c9..e6dddc79f9 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 @@ -407,7 +407,7 @@ private SNIHandle GetSessionSNIHandleHandleOrThrow() [MethodImpl(MethodImplOptions.NoInlining)] // this forces the exception throwing code not to be inlined for performance private void ThrowClosedConnection() => throw ADP.ClosedConnectionError(); - internal override SSPIContextProvider CreateSSPIContextProvider() - => new NegotiateSSPIContextProvider(); + internal override SspiContextProvider CreateSspiContextProvider() + => new NegotiateSspiContextProvider(); } } 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 929056b306..b8d1b6cccb 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 @@ -449,7 +449,7 @@ internal override void DisposePacketCache() } } - internal override SSPIContextProvider CreateSSPIContextProvider() => new NativeSSPIContextProvider(); + internal override SspiContextProvider CreateSspiContextProvider() => new NativeSspiContextProvider(); internal sealed class WritePacketCache : IDisposable { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 9e3f8f87a9..50b250dea2 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -354,14 +354,17 @@ Resources\ResCategoryAttribute.cs - - Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.cs - - Microsoft\Data\SqlClient\SSPI\NegotiateSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs - - Microsoft\Data\SqlClient\SSPI\SSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\SspiContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs Microsoft\Data\SqlClient\TdsParser.cs 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 86e5ca7026..130094dc53 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 @@ -44,7 +44,7 @@ internal sealed partial class TdsParser private static int _objectTypeCount; // EventSource counter private readonly SqlClientLogger _logger = new SqlClientLogger(); - private SSPIContextProvider _authenticationProvider; + private SspiContextProvider _authenticationProvider; internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); internal int ObjectID => _objectID; @@ -411,7 +411,7 @@ internal void Connect(ServerInfo serverInfo, // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - _authenticationProvider = _physicalStateObj.CreateSSPIContextProvider(); + _authenticationProvider = _physicalStateObj.CreateSspiContextProvider(); if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index aeace285e4..f83e50cc22 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -31,6 +31,6 @@ internal override uint EnableMars(ref uint info) internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) => SniNativeWrapper.SniSetInfo(Handle, QueryType.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); - internal override SSPIContextProvider CreateSSPIContextProvider() => new NativeSSPIContextProvider(); + internal override SspiContextProvider CreateSspiContextProvider() => new NativeSspiContextProvider(); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs similarity index 88% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs index 621ec5b4cc..5935b149c8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs @@ -6,7 +6,7 @@ namespace Microsoft.Data.SqlClient { - internal sealed class NativeSSPIContextProvider : SSPIContextProvider + internal sealed class NativeSspiContextProvider : SspiContextProvider { private static readonly object s_tdsParserLock = new(); @@ -49,7 +49,7 @@ private void LoadSSPILibrary() } } - protected override void GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, ReadOnlySpan serverSpns) + protected override bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams) { #if NETFRAMEWORK SNIHandle handle = _physicalStateObj.Handle; @@ -62,9 +62,9 @@ protected override void GenerateSspiClientContext(ReadOnlySpan incomingBlo var sendLength = s_maxSSPILength; var outBuff = outgoingBlobWriter.GetSpan((int)sendLength); - if (0 != SniNativeWrapper.SniSecGenClientContext(handle, incomingBlob, outBuff, ref sendLength, serverSpns[0])) + if (0 != SniNativeWrapper.SniSecGenClientContext(handle, incomingBlob, outBuff, ref sendLength, authParams.Resource)) { - throw new InvalidOperationException(SQLMessage.SSPIGenerateError()); + return false; } if (sendLength > int.MaxValue) @@ -73,6 +73,8 @@ protected override void GenerateSspiClientContext(ReadOnlySpan incomingBlo } outgoingBlobWriter.Advance((int)sendLength); + + return true; } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs deleted file mode 100644 index 9a4eb457a4..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -#if NET - -using System; -using System.Net.Security; -using System.Buffers; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - internal sealed class NegotiateSSPIContextProvider : SSPIContextProvider - { - private NegotiateAuthentication? _negotiateAuth = null; - - protected override void GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, ReadOnlySpan serverSpns) - { - NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.UnknownCredentials; - - for (int i = 0; i < serverSpns.Length; i++) - { - _negotiateAuth ??= new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = serverSpns[i] }); - var sendBuff = _negotiateAuth.GetOutgoingBlob(incomingBlob, out statusCode)!; - - // Log session id, status code and the actual SPN used in the negotiation - SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Session Id {2}, StatusCode={3}, SPN={4}", nameof(NegotiateSSPIContextProvider), - nameof(GenerateSspiClientContext), _physicalStateObj.SessionId, statusCode, _negotiateAuth.TargetName); - if (statusCode == NegotiateAuthenticationStatusCode.Completed || statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded) - { - outgoingBlobWriter.Write(sendBuff); - break; // Successful case, exit the loop with current SPN. - } - else - { - _negotiateAuth = null; // Reset _negotiateAuth to be generated again for next SPN. - } - } - - if (statusCode is not NegotiateAuthenticationStatusCode.Completed and not NegotiateAuthenticationStatusCode.ContinueNeeded) - { - throw new InvalidOperationException(SQLMessage.SSPIGenerateError() + Environment.NewLine + statusCode); - } - } - } -} -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs new file mode 100644 index 0000000000..5dc52010b3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs @@ -0,0 +1,36 @@ +#if NET + +using System; +using System.Buffers; +using System.Net.Security; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class NegotiateSspiContextProvider : SspiContextProvider + { + private NegotiateAuthentication? _negotiateAuth = null; + + protected override bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams) + { + NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.UnknownCredentials; + + _negotiateAuth ??= new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = authParams.Resource }); + var sendBuff = _negotiateAuth.GetOutgoingBlob(incomingBlob, out statusCode)!; + + // Log session id, status code and the actual SPN used in the negotiation + SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Session Id {2}, StatusCode={3}, SPN={4}", nameof(NegotiateSspiContextProvider), + nameof(GenerateSspiClientContext), _physicalStateObj.SessionId, statusCode, _negotiateAuth.TargetName); + + if (statusCode == NegotiateAuthenticationStatusCode.Completed || statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded) + { + outgoingBlobWriter.Write(sendBuff); + return true; + } + + return false; + } + } +} +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs deleted file mode 100644 index 69de086a93..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Buffers; -using System.Diagnostics; -using Microsoft.Data.Common; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - internal abstract class SSPIContextProvider - { - private TdsParser _parser = null!; - private ServerInfo _serverInfo = null!; - private protected TdsParserStateObject _physicalStateObj = null!; - - internal void Initialize(ServerInfo serverInfo, TdsParserStateObject physicalStateObj, TdsParser parser) - { - _parser = parser; - _physicalStateObj = physicalStateObj; - _serverInfo = serverInfo; - - Initialize(); - } - - private protected virtual void Initialize() - { - } - - protected abstract void GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, ReadOnlySpan serverSpns); - - internal void SSPIData(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter, string serverSpn) - => SSPIData(receivedBuff, outgoingBlobWriter, new[] { serverSpn }); - - internal void SSPIData(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter, string[] serverSpns) - { - using (TrySNIEventScope.Create(nameof(SSPIContextProvider))) - { - try - { - GenerateSspiClientContext(receivedBuff, outgoingBlobWriter, serverSpns); - } - catch (Exception e) - { - SSPIError(e.Message + Environment.NewLine + e.StackTrace, TdsEnums.GEN_CLIENT_CONTEXT); - } - } - } - - protected void SSPIError(string error, string procedure) - { - Debug.Assert(!string.IsNullOrEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); - Debug.Assert(!string.IsNullOrEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); - - _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _serverInfo.ResolvedServerName, error, procedure, 0)); - _parser.ThrowExceptionAndWarning(_physicalStateObj); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs new file mode 100644 index 0000000000..dce0858360 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs @@ -0,0 +1,23 @@ +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class SspiAuthenticationParameters + { + public SspiAuthenticationParameters(string serverName, string resource) + { + ServerName = serverName; + Resource = resource; + } + + public string Resource { get; } + + public string ServerName { get; } + + public string? UserId { get; set; } + + public string? DatabaseName { get; set; } + + public string? Password { get; set; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs new file mode 100644 index 0000000000..ff83422f10 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs @@ -0,0 +1,89 @@ +using System; +using System.Buffers; +using System.Diagnostics; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal abstract class SspiContextProvider + { + private TdsParser _parser = null!; + private ServerInfo _serverInfo = null!; + private protected TdsParserStateObject _physicalStateObj = null!; + + internal void Initialize(ServerInfo serverInfo, TdsParserStateObject physicalStateObj, TdsParser parser) + { + _parser = parser; + _physicalStateObj = physicalStateObj; + _serverInfo = serverInfo; + + Initialize(); + } + + private protected virtual void Initialize() + { + } + + protected abstract bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams); + + internal void SSPIData(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter, string serverSpn) + { + using var _ = TrySNIEventScope.Create(nameof(SspiContextProvider)); + + if (!RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, serverSpn)) + { + // If we've hit here, the SSPI context provider implementation failed to generate the SSPI context. + SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT); + } + } + + internal void SSPIData(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter, ReadOnlySpan serverSpns) + { + using var _ = TrySNIEventScope.Create(nameof(SspiContextProvider)); + + foreach (var serverSpn in serverSpns) + { + if (RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, serverSpn)) + { + return; + } + } + + // If we've hit here, the SSPI context provider implementation failed to generate the SSPI context. + SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT); + } + + private bool RunGenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, string serverSpn) + { + var options = _parser.Connection.ConnectionOptions; + var authParams = new SspiAuthenticationParameters(options.DataSource, serverSpn) + { + DatabaseName = options.InitialCatalog, + UserId = options.UserID, + Password = options.Password, + }; + + try + { + SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | SPN={1}", GetType().FullName, nameof(GenerateSspiClientContext), serverSpn); + + return GenerateSspiClientContext(incomingBlob, outgoingBlobWriter, authParams); + } + catch (Exception e) + { + SSPIError(e.Message + Environment.NewLine + e.StackTrace, TdsEnums.GEN_CLIENT_CONTEXT); + return false; + } + } + + protected void SSPIError(string error, string procedure) + { + Debug.Assert(!string.IsNullOrEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); + Debug.Assert(!string.IsNullOrEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); + + _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _serverInfo.ResolvedServerName, error, procedure, 0)); + _parser.ThrowExceptionAndWarning(_physicalStateObj); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 55e5c51c05..2f377581ce 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -470,7 +470,7 @@ internal long TimeoutTime internal abstract uint DisableSsl(); - internal abstract SSPIContextProvider CreateSSPIContextProvider(); + internal abstract SspiContextProvider CreateSspiContextProvider(); internal abstract uint EnableMars(ref uint info);