diff --git a/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs b/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs index 997ca0b06ebede7fbebfb18833059637c3b8813c..1907e9098c67e4b2023112307eba3c1f167f012b 100644 --- a/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs +++ b/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs @@ -1,13 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; - namespace Microsoft.AspNetCore.Authentication.Negotiate; // For testing internal interface INegotiateStateFactory { - [RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] INegotiateState CreateInstance(); } diff --git a/src/Security/Authentication/Negotiate/src/Internal/NegotiateState.cs b/src/Security/Authentication/Negotiate/src/Internal/NegotiateState.cs new file mode 100644 index 0000000000000000000000000000000000000000..4d3e74e160a081f3d9a511137734a49dfb799a75 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/NegotiateState.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Claims; +using System.Security.Principal; + +namespace Microsoft.AspNetCore.Authentication.Negotiate; + +internal sealed class NegotiateState : INegotiateState +{ + private static readonly NegotiateAuthenticationServerOptions _serverOptions = new(); + private readonly NegotiateAuthentication _instance; + + public NegotiateState() + { + _instance = new NegotiateAuthentication(_serverOptions); + } + + public string? GetOutgoingBlob(string incomingBlob, out BlobErrorType status, out Exception? error) + { + var outgoingBlob = _instance.GetOutgoingBlob(incomingBlob, out var authStatus); + + if (authStatus == NegotiateAuthenticationStatusCode.Completed || + authStatus == NegotiateAuthenticationStatusCode.ContinueNeeded) + { + status = BlobErrorType.None; + error = null; + } + else + { + error = new AuthenticationException(authStatus.ToString()); + if (IsCredentialError(authStatus)) + { + status = BlobErrorType.CredentialError; + } + else if (IsClientError(authStatus)) + { + status = BlobErrorType.ClientError; + } + else + { + status = BlobErrorType.Other; + } + } + + return outgoingBlob; + } + + public bool IsCompleted + { + get => _instance.IsAuthenticated; + } + + public string Protocol + { + get => _instance.Package; + } + + public IIdentity GetIdentity() + { + var remoteIdentity = _instance.RemoteIdentity; + return remoteIdentity is ClaimsIdentity claimsIdentity ? claimsIdentity.Clone() : remoteIdentity; + } + + public void Dispose() + { + _instance.Dispose(); + } + + private static bool IsCredentialError(NegotiateAuthenticationStatusCode error) + { + return error == NegotiateAuthenticationStatusCode.UnknownCredentials || + error == NegotiateAuthenticationStatusCode.CredentialsExpired || + error == NegotiateAuthenticationStatusCode.BadBinding; + } + + private static bool IsClientError(NegotiateAuthenticationStatusCode error) + { + return error == NegotiateAuthenticationStatusCode.InvalidToken || + error == NegotiateAuthenticationStatusCode.QopNotSupported || + error == NegotiateAuthenticationStatusCode.UnknownCredentials || + error == NegotiateAuthenticationStatusCode.MessageAltered || + error == NegotiateAuthenticationStatusCode.OutOfSequence || + error == NegotiateAuthenticationStatusCode.InvalidCredentials; + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/NegotiateStateFactory.cs b/src/Security/Authentication/Negotiate/src/Internal/NegotiateStateFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..07bb5ff79610bdf8240fec24224fe26af636a488 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/NegotiateStateFactory.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Authentication.Negotiate; + +internal sealed class NegotiateStateFactory : INegotiateStateFactory +{ + public INegotiateState CreateInstance() + { + return new NegotiateState(); + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs deleted file mode 100644 index b65c692772164cea40b8a266ebc7eb74ed2e4e6f..0000000000000000000000000000000000000000 --- a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs +++ /dev/null @@ -1,210 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma warning disable CA1810 // Initialize all static fields inline. - -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Runtime.ExceptionServices; -using System.Security.Authentication; -using System.Security.Principal; - -namespace Microsoft.AspNetCore.Authentication.Negotiate; - -[RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] -internal sealed class ReflectedNegotiateState : INegotiateState -{ - // https://www.gnu.org/software/gss/reference/gss.pdf - private const uint GSS_S_NO_CRED = 7 << 16; - - private static readonly ConstructorInfo _constructor; - private static readonly MethodInfo _getOutgoingBlob; - private static readonly MethodInfo _isCompleted; - private static readonly MethodInfo _protocol; - private static readonly MethodInfo _getIdentity; - private static readonly MethodInfo _closeContext; - private static readonly FieldInfo _statusCode; - private static readonly FieldInfo _statusException; - private static readonly MethodInfo _getException; - private static readonly FieldInfo? _gssMinorStatus; - private static readonly Type? _gssExceptionType; - - private readonly object _instance; - - static ReflectedNegotiateState() - { - var secAssembly = typeof(AuthenticationException).Assembly; - var ntAuthType = secAssembly.GetType("System.Net.NTAuthentication", throwOnError: true)!; - _constructor = ntAuthType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First(); - _getOutgoingBlob = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => - info.Name.Equals("GetOutgoingBlob") && info.GetParameters().Length == 3 && info.GetParameters()[0].ParameterType == typeof(byte[])).Single(); - _isCompleted = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => - info.Name.Equals("get_IsCompleted")).Single(); - _protocol = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => - info.Name.Equals("get_ProtocolName")).Single(); - _closeContext = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => - info.Name.Equals("CloseContext")).Single(); - - var securityStatusType = secAssembly.GetType("System.Net.SecurityStatusPal", throwOnError: true)!; - _statusCode = securityStatusType.GetField("ErrorCode")!; - _statusException = securityStatusType.GetField("Exception")!; - - if (!OperatingSystem.IsWindows()) - { - var interopType = secAssembly.GetType("Interop", throwOnError: true)!; - var netNativeType = interopType.GetNestedType("NetSecurityNative", BindingFlags.NonPublic | BindingFlags.Static)!; - _gssExceptionType = netNativeType.GetNestedType("GssApiException", BindingFlags.NonPublic)!; - _gssMinorStatus = _gssExceptionType.GetField("_minorStatus", BindingFlags.Instance | BindingFlags.NonPublic)!; - } - - var negoStreamPalType = secAssembly.GetType("System.Net.Security.NegotiateStreamPal", throwOnError: true)!; - _getIdentity = negoStreamPalType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(info => - info.Name.Equals("GetIdentity")).Single(); - _getException = negoStreamPalType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(info => - info.Name.Equals("CreateExceptionFromError")).Single(); - } - - public ReflectedNegotiateState() - { - // internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding) - var credential = CredentialCache.DefaultCredentials; - _instance = _constructor.Invoke(new object?[] { true, "Negotiate", credential, null, 0, null }); - } - - // Copied rather than reflected to remove the IsCompleted -> CloseContext check. - // The client doesn't need the context once auth is complete, but the server does. - // I'm not sure why it auto-closes for the client given that the client closes it just a few lines later. - // https://github.com/dotnet/corefx/blob/a3ab91e10045bb298f48c1d1f9bd5b0782a8ac46/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs#L134 - public string? GetOutgoingBlob(string incomingBlob, out BlobErrorType status, out Exception? error) - { - byte[]? decodedIncomingBlob = null; - if (incomingBlob != null && incomingBlob.Length > 0) - { - decodedIncomingBlob = Convert.FromBase64String(incomingBlob); - } - - byte[] decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, out status, out error); - - string? outgoingBlob = null; - if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) - { - outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob); - } - - return outgoingBlob; - } - - private byte[] GetOutgoingBlob(byte[]? incomingBlob, out BlobErrorType status, out Exception? error) - { - try - { - // byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) - var parameters = new object?[] { incomingBlob, false, null }; - var blob = (byte[])_getOutgoingBlob.Invoke(_instance, parameters)!; - - var securityStatus = parameters[2]; - // TODO: Update after corefx changes - error = (Exception?)(_statusException.GetValue(securityStatus) - ?? _getException.Invoke(null, new[] { securityStatus })); - var errorCode = (SecurityStatusPalErrorCode)_statusCode.GetValue(securityStatus)!; - - // TODO: Remove after corefx changes - // The linux implementation always uses InternalError; - if (errorCode == SecurityStatusPalErrorCode.InternalError - && !OperatingSystem.IsWindows() - && _gssExceptionType!.IsInstanceOfType(error)) - { - var majorStatus = (uint)error.HResult; - var minorStatus = (uint)_gssMinorStatus!.GetValue(error)!; - - // Remap specific errors - if (majorStatus == GSS_S_NO_CRED && minorStatus == 0) - { - errorCode = SecurityStatusPalErrorCode.UnknownCredentials; - } - - error = new Exception($"An authentication exception occurred (0x{majorStatus:X}/0x{minorStatus:X}).", error); - } - - if (errorCode == SecurityStatusPalErrorCode.OK - || errorCode == SecurityStatusPalErrorCode.ContinueNeeded - || errorCode == SecurityStatusPalErrorCode.CompleteNeeded) - { - status = BlobErrorType.None; - } - else if (IsCredentialError(errorCode)) - { - status = BlobErrorType.CredentialError; - } - else if (IsClientError(errorCode)) - { - status = BlobErrorType.ClientError; - } - else - { - status = BlobErrorType.Other; - } - - return blob; - } - catch (TargetInvocationException tex) - { - // Unwrap - ExceptionDispatchInfo.Capture(tex.InnerException!).Throw(); - throw; - } - } - - public bool IsCompleted - { - get => (bool)_isCompleted.Invoke(_instance, Array.Empty<object>())!; - } - - public string Protocol - { - get => (string)_protocol.Invoke(_instance, Array.Empty<object>())!; - } - - public IIdentity GetIdentity() - { - return (IIdentity)_getIdentity.Invoke(obj: null, parameters: new object[] { _instance })!; - } - - public void Dispose() - { - _closeContext.Invoke(_instance, Array.Empty<object>()); - } - - private static bool IsCredentialError(SecurityStatusPalErrorCode error) - { - return error == SecurityStatusPalErrorCode.LogonDenied || - error == SecurityStatusPalErrorCode.UnknownCredentials || - error == SecurityStatusPalErrorCode.NoImpersonation || - error == SecurityStatusPalErrorCode.NoAuthenticatingAuthority || - error == SecurityStatusPalErrorCode.UntrustedRoot || - error == SecurityStatusPalErrorCode.CertExpired || - error == SecurityStatusPalErrorCode.SmartcardLogonRequired || - error == SecurityStatusPalErrorCode.BadBinding; - } - - private static bool IsClientError(SecurityStatusPalErrorCode error) - { - return error == SecurityStatusPalErrorCode.InvalidToken || - error == SecurityStatusPalErrorCode.CannotPack || - error == SecurityStatusPalErrorCode.QopNotSupported || - error == SecurityStatusPalErrorCode.NoCredentials || - error == SecurityStatusPalErrorCode.MessageAltered || - error == SecurityStatusPalErrorCode.OutOfSequence || - error == SecurityStatusPalErrorCode.IncompleteMessage || - error == SecurityStatusPalErrorCode.IncompleteCredentials || - error == SecurityStatusPalErrorCode.WrongPrincipal || - error == SecurityStatusPalErrorCode.TimeSkew || - error == SecurityStatusPalErrorCode.IllegalMessage || - error == SecurityStatusPalErrorCode.CertUnknown || - error == SecurityStatusPalErrorCode.AlgorithmMismatch || - error == SecurityStatusPalErrorCode.SecurityQosFailed || - error == SecurityStatusPalErrorCode.UnsupportedPreauth; - } -} diff --git a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs deleted file mode 100644 index 4a418b07f91ba6de9af66422161289e6a11fc635..0000000000000000000000000000000000000000 --- a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.AspNetCore.Authentication.Negotiate; - -internal sealed class ReflectedNegotiateStateFactory : INegotiateStateFactory -{ - [RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] - public INegotiateState CreateInstance() - { - return new ReflectedNegotiateState(); - } -} diff --git a/src/Security/Authentication/Negotiate/src/Internal/SecurityStatusPalErrorCode.cs b/src/Security/Authentication/Negotiate/src/Internal/SecurityStatusPalErrorCode.cs deleted file mode 100644 index 4abac7f4e1bc912017acba785727ee17856d6214..0000000000000000000000000000000000000000 --- a/src/Security/Authentication/Negotiate/src/Internal/SecurityStatusPalErrorCode.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Authentication.Negotiate; - -internal enum SecurityStatusPalErrorCode -{ - NotSet = 0, - OK, - ContinueNeeded, - CompleteNeeded, - CompAndContinue, - ContextExpired, - CredentialsNeeded, - Renegotiate, - - // Errors - OutOfMemory, - InvalidHandle, - Unsupported, - TargetUnknown, - InternalError, - PackageNotFound, - NotOwner, - CannotInstall, - InvalidToken, - CannotPack, - QopNotSupported, - NoImpersonation, - LogonDenied, - UnknownCredentials, - NoCredentials, - MessageAltered, - OutOfSequence, - NoAuthenticatingAuthority, - IncompleteMessage, - IncompleteCredentials, - BufferNotEnough, - WrongPrincipal, - TimeSkew, - UntrustedRoot, - IllegalMessage, - CertUnknown, - CertExpired, - DecryptFailure, - AlgorithmMismatch, - SecurityQosFailed, - SmartcardLogonRequired, - UnsupportedPreauth, - BadBinding, - DowngradeDetected, - ApplicationProtocolMismatch -} diff --git a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs index a2d03767ec5bffd92ce1770d8396ceee91f04929..c71824f54f8ca3fea2991d31e37e17a8f2d06906 100644 --- a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs +++ b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Negotiate; using Microsoft.AspNetCore.Authentication.Negotiate.Internal; @@ -25,7 +24,6 @@ public static class NegotiateExtensions /// </summary> /// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param> /// <returns>The original builder.</returns> - [RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder) => builder.AddNegotiate(NegotiateDefaults.AuthenticationScheme, _ => { }); @@ -39,7 +37,6 @@ public static class NegotiateExtensions /// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param> /// <param name="configureOptions">Allows for configuring the authentication handler.</param> /// <returns>The original builder.</returns> - [RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, Action<NegotiateOptions> configureOptions) => builder.AddNegotiate(NegotiateDefaults.AuthenticationScheme, configureOptions); @@ -54,7 +51,6 @@ public static class NegotiateExtensions /// <param name="authenticationScheme">The scheme name used to identify the authentication handler internally.</param> /// <param name="configureOptions">Allows for configuring the authentication handler.</param> /// <returns>The original builder.</returns> - [RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, Action<NegotiateOptions> configureOptions) => builder.AddNegotiate(authenticationScheme, displayName: null, configureOptions: configureOptions); @@ -70,7 +66,6 @@ public static class NegotiateExtensions /// <param name="displayName">The name displayed to users when selecting an authentication handler. The default is null to prevent this from displaying.</param> /// <param name="configureOptions">Allows for configuring the authentication handler.</param> /// <returns>The original builder.</returns> - [RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<NegotiateOptions> configureOptions) { builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<NegotiateOptions>, PostConfigureNegotiateOptions>()); diff --git a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs index f12b1ae7b1ea35fa5d10c143baa2550809498a0d..12c7395f6663084814cb21744f7554761f849b50 100644 --- a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs +++ b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security.Claims; using System.Security.Principal; @@ -19,7 +18,6 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate; /// <summary> /// Authenticates requests using Negotiate, Kerberos, or NTLM. /// </summary> -[RequiresUnreferencedCode("Negotiate authentication uses types that cannot be statically analyzed.")] public class NegotiateHandler : AuthenticationHandler<NegotiateOptions>, IAuthenticationRequestHandler { private const string AuthPersistenceKey = nameof(AuthPersistence); diff --git a/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs b/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs index bcf86af754a1bce8e3c143a9af9d9e85c679d214..4a92b6b41799a4690b0d4ac8a82bcf18c47d2a97 100644 --- a/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs +++ b/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs @@ -76,5 +76,5 @@ public class NegotiateOptions : AuthenticationSchemeOptions internal bool DeferToServer { get; set; } // For testing - internal INegotiateStateFactory StateFactory { get; set; } = new ReflectedNegotiateStateFactory(); + internal INegotiateStateFactory StateFactory { get; set; } = new NegotiateStateFactory(); }