未验证 提交 f7ad7265 编写于 作者: F Filip Navara 提交者: GitHub

Restructure NegotiateAuthentication implementation (#87930)

* Change NegotiateAuthentication implementation to use indirection through NegotiateAuthenticationPal to the actual implementation.

The PAL implementation are merged from different sources to follow the same structure:
- NTAuthentication.Managed.cs -> NegotiateAuthenticationPal.Managed.cs
- NTAuthentication.Common.cs + NegotiateStreamPal.Windows.cs -> NegotiateAuthenticationPal.Windows.cs
- NTAuthentication.Common.cs + NegotiateStreamPal.Unix.cs -> NegotiateAuthenticationPal.Unix.cs

This split allows to delete ContextFlagsPal, SafeDeleteNegoContext, and SafeFreeNegoCredentials abstractions that were used in NegotiateStreamPal.

* Unify impersonation level validation between Windows and Unix pllatforms

* Split managed NTLM and managed SPNEGO implementations; add UseManagedNtlm switch on Unix platforms

* Remove debug cruft

* Fix couple of errors in managed SPNEGO

* Remove debug print

* Fix message sequence in managed NTLM; remove unused method

* Fix fallbacks on macOS GSSAPI

* Cleanup and fallbacks for missing NTLM, GSSAPI

* Adjust tests to assume that NTLM is always available on Unix

* Don't claim NTLM support on Browser

* Revert "Don't claim NTLM support on Browser"

This reverts commit 87d0c56f67a5269d9b334ab17887338ac4cfe49b.

* Attempt to fix the browser tests

* Revert "Attempt to fix the browser tests"

This reverts commit 91d7ce289a7274b682803b1d5dfdf418a5c2120c.

* Browser test suppression

* Respect UseManagedNtlm=false on platforms without NTLM GSSAPI provider

* Update src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs

* Revert all the fallback code paths, System.Net.Security.UseManagedNtlm has to be enabled explicitly; NativeAOT on Linux Bionic does that by default because it doesn't have GSSAPI and native shim
上级 f1d60990
......@@ -279,6 +279,9 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Include="--feature:System.Linq.Expressions.CanEmitObjectArrayDelegate=false" />
<IlcArg Include="--feature:System.Linq.Expressions.CanCreateArbitraryDelegates=false" />
<!-- Linux Bionic doesn't ship GSSAPI, so enable managed implementation -->
<IlcArg Condition="'$(_linuxLibcFlavor)' == 'bionic'" Include="--feature:System.Net.Security.UseManagedNtlm=true" />
<!-- The managed debugging support in libraries is unused - trim it -->
<IlcArg Condition="'$(IlcKeepManagedDebuggerSupport)' != 'true'" Include="--feature:System.Diagnostics.Debugger.IsSupported=false" />
<IlcArg Condition="'$(UseWindowsThreadPool)' != '' and '$(_targetOS)' == 'win'" Include="--feature:System.Threading.ThreadPool.UseWindowsThreadPool=$(UseWindowsThreadPool)" />
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace System.Net.Security
{
internal sealed class SafeDeleteNegoContext : SafeDeleteContext
{
private SafeGssCredHandle? _acceptorCredential;
private SafeGssNameHandle? _targetName;
private SafeGssContextHandle _context;
private bool _isNtlmUsed;
private readonly SafeFreeNegoCredentials? _credential;
public SafeGssCredHandle AcceptorCredential
{
get
{
_acceptorCredential ??= SafeGssCredHandle.CreateAcceptor();
return _acceptorCredential;
}
}
public SafeGssNameHandle? TargetName
{
get { return _targetName; }
}
// Property represents if final protocol negotiated is Ntlm or not.
public bool IsNtlmUsed
{
get { return _isNtlmUsed; }
}
public SafeGssContextHandle GssContext
{
get { return _context; }
}
public SafeDeleteNegoContext(SafeFreeNegoCredentials credential)
: base(IntPtr.Zero)
{
Debug.Assert((null != credential), "Null credential in SafeDeleteNegoContext");
bool added = false;
credential.DangerousAddRef(ref added);
_credential = credential;
_context = new SafeGssContextHandle();
}
public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetName)
: base(IntPtr.Zero)
{
try
{
_targetName = SafeGssNameHandle.CreateTarget(targetName);
_context = new SafeGssContextHandle();
}
catch
{
Dispose();
throw;
}
_credential = credential;
bool ignore = false;
_credential.DangerousAddRef(ref ignore);
}
public void SetGssContext(SafeGssContextHandle context)
{
_context = context;
}
public void SetAuthenticationPackage(bool isNtlmUsed)
{
_isNtlmUsed = isNtlmUsed;
}
public override bool IsInvalid
{
get { return (null == _credential); }
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
if (_targetName != null)
{
_targetName.Dispose();
_targetName = null;
}
if (_acceptorCredential != null)
{
_acceptorCredential.Dispose();
_acceptorCredential = null;
}
}
base.Dispose(disposing);
}
protected override bool ReleaseHandle()
{
_credential?.DangerousRelease();
return true;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace System.Net.Security
{
internal sealed class SafeFreeNegoCredentials : SafeFreeCredentials
{
private SafeGssCredHandle _credential;
private readonly Interop.NetSecurityNative.PackageType _packageType;
private readonly string _userName;
private readonly bool _isDefault;
public SafeGssCredHandle GssCredential
{
get { return _credential; }
}
// Property represents which protocol is specified (Negotiate, Ntlm or Kerberos).
public Interop.NetSecurityNative.PackageType PackageType
{
get { return _packageType; }
}
public string UserName
{
get { return _userName; }
}
public bool IsDefault
{
get { return _isDefault; }
}
public SafeFreeNegoCredentials(Interop.NetSecurityNative.PackageType packageType, string username, string password, ReadOnlySpan<char> domain)
: base(IntPtr.Zero, true)
{
Debug.Assert(username != null && password != null, "Username and Password can not be null");
// any invalid user format will not be manipulated and passed as it is.
int index = username.IndexOf('\\');
if (index > 0 && username.IndexOf('\\', index + 1) < 0 && domain.IsEmpty)
{
domain = username.AsSpan(0, index);
username = username.Substring(index + 1);
}
// remove any leading and trailing whitespace
username = username.Trim();
domain = domain.Trim();
if (!username.Contains('@') && !domain.IsEmpty)
{
username = string.Concat(username, "@", domain);
}
bool ignore = false;
_packageType = packageType;
_userName = username;
_isDefault = string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password);
_credential = SafeGssCredHandle.Create(username, password, packageType);
_credential.DangerousAddRef(ref ignore);
}
public override bool IsInvalid
{
get { return (null == _credential); }
}
protected override bool ReleaseHandle()
{
_credential.DangerousRelease();
_credential = null!;
return true;
}
}
}
......@@ -115,6 +115,7 @@ internal static async Task HandleAuthenticationRequestWithFakeServer(LoopbackSer
[ConditionalTheory(nameof(IsNtlmAvailable))]
[InlineData(true)]
[InlineData(false)]
[SkipOnPlatform(TestPlatforms.Browser, "Credentials and HttpListener is not supported on Browser")]
public async Task DefaultHandler_FakeServer_Success(bool useNtlm)
{
await LoopbackServer.CreateClientAndServerAsync(
......
<linker>
<assembly fullname="System.Net.Security">
<type fullname="System.Net.NegotiateAuthenticationPal">
<method signature="System.Boolean get_UseManagedNtlm()" feature="System.Net.Security.UseManagedNtlm" featurevalue="false" body="stub" value="false" />
</type>
</assembly>
</linker>
......@@ -18,7 +18,7 @@
<DefineConstants Condition="'$(UseAndroidCrypto)' == 'true' or '$(UseAppleCrypto)' == 'true'">$(DefineConstants);SYSNETSECURITY_NO_OPENSSL</DefineConstants>
<GenAPIExcludeApiList>ReferenceAssemblyExclusions.txt</GenAPIExcludeApiList>
</PropertyGroup>
<Import Project="$(CommonPath)System\Security\Cryptography\Asn1Reader\System.Security.Cryptography.Asn1Reader.Shared.projitems" Condition="'$(UseManagedNtlm)' == 'true'" />
<Import Project="$(CommonPath)System\Security\Cryptography\Asn1Reader\System.Security.Cryptography.Asn1Reader.Shared.projitems" Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows'" />
<ItemGroup>
<Compile Include="System\Security\Authentication\ExtendedProtection\ExtendedProtectionPolicy.cs" />
<Compile Include="System\Security\Authentication\ExtendedProtection\ServiceNameCollection.cs" />
......@@ -29,6 +29,8 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != ''">
<Compile Include="System\Net\CertificateValidationPal.cs" />
<Compile Include="System\Net\NegotiateAuthenticationPal.cs" />
<Compile Include="System\Net\NegotiateAuthenticationPal.Unsupported.cs" />
<Compile Include="System\Net\SslStreamContext.cs" />
<Compile Include="System\Net\Security\AuthenticatedStream.cs" />
<Compile Include="System\Security\Authentication\AuthenticationException.cs" />
......@@ -57,9 +59,7 @@
<Compile Include="System\Net\Security\TlsAlertType.cs" />
<Compile Include="System\Net\Security\TlsFrameHelper.cs" />
<!-- NegotiateStream -->
<Compile Include="System\Net\ContextFlagsPal.cs" />
<Compile Include="System\Net\SecurityStatusPal.cs" />
<Compile Include="System\Net\NTAuthentication.cs" />
<Compile Include="System\Net\StreamFramer.cs" />
<Compile Include="System\Net\Security\NegotiateStream.cs" />
<Compile Include="System\Security\Authentication\ExtendedProtection\PolicyEnforcement.cs" />
......@@ -107,8 +107,6 @@
Link="Common\System\Net\Security\SSPIHandleCache.cs" />
<Compile Include="$(CommonPath)System\Net\NegotiationInfoClass.cs"
Link="Common\System\Net\NegotiationInfoClass.cs" />
<Compile Include="System\Net\NTAuthentication.Common.cs"
Condition="'$(UseManagedNtlm)' != 'true'" />
<Compile Include="$(CommonPath)System\HexConverter.cs"
Link="Common\System\HexConverter.cs" />
<Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs"
......@@ -155,8 +153,8 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
<Compile Include="System\Net\CertificateValidationPal.Windows.cs" />
<Compile Include="System\Net\NegotiateAuthenticationPal.Windows.cs" />
<Compile Include="System\Net\Security\CipherSuitesPolicyPal.Windows.cs" />
<Compile Include="System\Net\Security\NegotiateStreamPal.Windows.cs" />
<Compile Include="System\Net\Security\SslStreamCertificateContext.Windows.cs" />
<Compile Include="System\Net\Security\SslStreamPal.Windows.cs" />
<Compile Include="System\Net\Security\SslConnectionInfo.Windows.cs" />
......@@ -171,7 +169,6 @@
Link="Common\System\Net\Security\SecurityBufferType.Windows.cs" />
<!-- NegotiateStream -->
<Compile Include="System\Net\SecurityStatusAdapterPal.Windows.cs" />
<Compile Include="System\Net\ContextFlagsAdapterPal.Windows.cs" />
<Compile Include="$(CommonPath)System\Net\Security\SecurityContextTokenHandle.cs"
Link="Common\System\Net\Security\SecurityContextTokenHandle.cs" />
<!-- Interop -->
......@@ -287,13 +284,10 @@
<Compile Include="System\Net\Security\Pal.Managed\SafeChannelBindingHandle.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(UseManagedNtlm)' != 'true'">
<ILLinkSubstitutionsXmls Include="$(ILLinkDirectory)ILLink.Substitutions.xml" />
<Compile Include="System\Net\NegotiateAuthenticationPal.Unix.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\GssSafeHandles.cs"
Link="Common\Microsoft\Win32\SafeHandles\GssSafeHandles.cs" />
<Compile Include="$(CommonPath)System\Net\Security\Unix\SafeDeleteNegoContext.cs"
Link="Common\System\Net\Security\Unix\SafeDeleteNegoContext.cs" />
<Compile Include="$(CommonPath)System\Net\Security\Unix\SafeFreeNegoCredentials.cs"
Link="Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs" />
<Compile Include="System\Net\ContextFlagsAdapterPal.Unix.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs"
Link="Common\Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs"
......@@ -304,11 +298,13 @@
Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs"
Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" />
<Compile Include="System\Net\Security\NegotiateStreamPal.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(UseManagedNtlm)' == 'true'">
<Compile Include="System\Net\Security\NegotiateStreamPal.Managed.cs" />
<Compile Include="System\Net\NTAuthentication.Managed.cs" />
<Compile Include="System\Net\NegotiateAuthenticationPal.Managed.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows'">
<Compile Include="System\Net\NegotiateAuthenticationPal.ManagedNtlm.cs" />
<Compile Include="System\Net\NegotiateAuthenticationPal.ManagedSpnego.cs" />
<Compile Include="$(CommonPath)System\Net\Security\MD4.cs"
Link="Common\System\Net\Security\MD4.cs" />
<Compile Include="$(CommonPath)System\Net\Security\RC4.cs"
......@@ -455,9 +451,7 @@
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows'">
<Reference Include="System.Diagnostics.StackTrace" />
<Reference Include="System.Security.Cryptography" />
</ItemGroup>
<ItemGroup Condition="'$(UseManagedNtlm)' == 'true'">
<ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" />
<Reference Include="System.Runtime.Numerics" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" />
</ItemGroup>
</Project>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace System.Net
{
internal static class ContextFlagsAdapterPal
{
private readonly struct ContextFlagMapping
{
public readonly Interop.NetSecurityNative.GssFlags GssFlags;
public readonly ContextFlagsPal ContextFlag;
public ContextFlagMapping(Interop.NetSecurityNative.GssFlags gssFlag, ContextFlagsPal contextFlag)
{
GssFlags = gssFlag;
ContextFlag = contextFlag;
}
}
private static readonly ContextFlagMapping[] s_contextFlagMapping = new[]
{
new ContextFlagMapping(Interop.NetSecurityNative.GssFlags.GSS_C_CONF_FLAG, ContextFlagsPal.Confidentiality),
new ContextFlagMapping(Interop.NetSecurityNative.GssFlags.GSS_C_MUTUAL_FLAG, ContextFlagsPal.MutualAuth),
new ContextFlagMapping(Interop.NetSecurityNative.GssFlags.GSS_C_REPLAY_FLAG, ContextFlagsPal.ReplayDetect),
new ContextFlagMapping(Interop.NetSecurityNative.GssFlags.GSS_C_SEQUENCE_FLAG, ContextFlagsPal.SequenceDetect),
new ContextFlagMapping(Interop.NetSecurityNative.GssFlags.GSS_C_DELEG_FLAG, ContextFlagsPal.Delegate)
};
internal static ContextFlagsPal GetContextFlagsPalFromInterop(Interop.NetSecurityNative.GssFlags gssFlags, bool isServer)
{
ContextFlagsPal flags = ContextFlagsPal.None;
// GSS_C_IDENTIFY_FLAG is handled separately as its value can either be AcceptIdentify (used by server) or InitIdentify (used by client)
if ((gssFlags & Interop.NetSecurityNative.GssFlags.GSS_C_IDENTIFY_FLAG) != 0)
{
flags |= isServer ? ContextFlagsPal.AcceptIdentify : ContextFlagsPal.InitIdentify;
}
foreach (ContextFlagMapping mapping in s_contextFlagMapping)
{
if ((gssFlags & mapping.GssFlags) == mapping.GssFlags)
{
flags |= mapping.ContextFlag;
}
}
// GSS_C_INTEG_FLAG is handled separately as its value can either be AcceptIntegrity (used by server) or InitIntegrity (used by client)
if ((gssFlags & Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG) != 0)
{
flags |= isServer ? ContextFlagsPal.AcceptIntegrity : ContextFlagsPal.InitIntegrity;
}
foreach (ContextFlagMapping mapping in s_contextFlagMapping)
{
if ((gssFlags & mapping.GssFlags) == mapping.GssFlags)
{
flags |= mapping.ContextFlag;
}
}
return flags;
}
internal static Interop.NetSecurityNative.GssFlags GetInteropFromContextFlagsPal(ContextFlagsPal flags, bool isServer)
{
Interop.NetSecurityNative.GssFlags gssFlags = 0;
// GSS_C_IDENTIFY_FLAG is set if either AcceptIdentify (used by server) or InitIdentify (used by client) is set
if (isServer)
{
if ((flags & ContextFlagsPal.AcceptIdentify) != 0)
{
gssFlags |= Interop.NetSecurityNative.GssFlags.GSS_C_IDENTIFY_FLAG;
}
}
else
{
if ((flags & ContextFlagsPal.InitIdentify) != 0)
{
gssFlags |= Interop.NetSecurityNative.GssFlags.GSS_C_IDENTIFY_FLAG;
}
}
// GSS_C_INTEG_FLAG is set if either AcceptIntegrity (used by server) or InitIntegrity (used by client) is set
if (isServer)
{
if ((flags & ContextFlagsPal.AcceptIntegrity) != 0)
{
gssFlags |= Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG;
}
}
else
{
if ((flags & ContextFlagsPal.InitIntegrity) != 0)
{
gssFlags |= Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG;
}
}
foreach (ContextFlagMapping mapping in s_contextFlagMapping)
{
if ((flags & mapping.ContextFlag) == mapping.ContextFlag)
{
gssFlags |= mapping.GssFlags;
}
}
return gssFlags;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace System.Net
{
internal static class ContextFlagsAdapterPal
{
private readonly struct ContextFlagMapping
{
public readonly Interop.SspiCli.ContextFlags Win32Flag;
public readonly ContextFlagsPal ContextFlag;
public ContextFlagMapping(Interop.SspiCli.ContextFlags win32Flag, ContextFlagsPal contextFlag)
{
Win32Flag = win32Flag;
ContextFlag = contextFlag;
}
}
private static readonly ContextFlagMapping[] s_contextFlagMapping = new[]
{
new ContextFlagMapping(Interop.SspiCli.ContextFlags.AcceptExtendedError, ContextFlagsPal.AcceptExtendedError),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.AcceptIdentify, ContextFlagsPal.AcceptIdentify),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.AcceptIntegrity, ContextFlagsPal.AcceptIntegrity),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.AcceptStream, ContextFlagsPal.AcceptStream),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.AllocateMemory, ContextFlagsPal.AllocateMemory),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.AllowMissingBindings, ContextFlagsPal.AllowMissingBindings),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.Confidentiality, ContextFlagsPal.Confidentiality),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.Connection, ContextFlagsPal.Connection),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.Delegate, ContextFlagsPal.Delegate),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.InitExtendedError, ContextFlagsPal.InitExtendedError),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.InitIdentify, ContextFlagsPal.InitIdentify),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.InitManualCredValidation, ContextFlagsPal.InitManualCredValidation),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.InitIntegrity, ContextFlagsPal.InitIntegrity),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.InitStream, ContextFlagsPal.InitStream),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.InitUseSuppliedCreds, ContextFlagsPal.InitUseSuppliedCreds),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.MutualAuth, ContextFlagsPal.MutualAuth),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.ProxyBindings, ContextFlagsPal.ProxyBindings),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.ReplayDetect, ContextFlagsPal.ReplayDetect),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.SequenceDetect, ContextFlagsPal.SequenceDetect),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.UnverifiedTargetName, ContextFlagsPal.UnverifiedTargetName),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.UseSessionKey, ContextFlagsPal.UseSessionKey),
new ContextFlagMapping(Interop.SspiCli.ContextFlags.Zero, ContextFlagsPal.None),
};
internal static ContextFlagsPal GetContextFlagsPalFromInterop(Interop.SspiCli.ContextFlags win32Flags)
{
ContextFlagsPal flags = ContextFlagsPal.None;
foreach (ContextFlagMapping mapping in s_contextFlagMapping)
{
if ((win32Flags & mapping.Win32Flag) == mapping.Win32Flag)
{
flags |= mapping.ContextFlag;
}
}
return flags;
}
internal static Interop.SspiCli.ContextFlags GetInteropFromContextFlagsPal(ContextFlagsPal flags)
{
Interop.SspiCli.ContextFlags win32Flags = Interop.SspiCli.ContextFlags.Zero;
foreach (ContextFlagMapping mapping in s_contextFlagMapping)
{
if ((flags & mapping.ContextFlag) == mapping.ContextFlag)
{
win32Flags |= mapping.Win32Flag;
}
}
return win32Flags;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace System.Net
{
[Flags]
internal enum ContextFlagsPal
{
None = 0,
Delegate = 0x00000001,
MutualAuth = 0x00000002,
ReplayDetect = 0x00000004,
SequenceDetect = 0x00000008,
Confidentiality = 0x00000010,
UseSessionKey = 0x00000020,
AllocateMemory = 0x00000100,
Connection = 0x00000800,
InitExtendedError = 0x00004000,
AcceptExtendedError = 0x00008000,
InitStream = 0x00008000,
AcceptStream = 0x00010000,
InitIntegrity = 0x00010000,
AcceptIntegrity = 0x00020000,
InitManualCredValidation = 0x00080000,
InitUseSuppliedCreds = 0x00000080,
InitIdentify = 0x00020000,
AcceptIdentify = 0x00080000,
ProxyBindings = 0x04000000,
AllowMissingBindings = 0x10000000,
UnverifiedTargetName = 0x20000000,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Authentication.ExtendedProtection;
namespace System.Net
{
internal sealed partial class NTAuthentication
{
private bool _isServer;
private SafeFreeCredentials? _credentialsHandle;
private SafeDeleteContext? _securityContext;
private string? _spn;
private int _tokenSize;
private byte[]? _tokenBuffer;
private ContextFlagsPal _requestedContextFlags;
private ContextFlagsPal _contextFlags;
private bool _isCompleted;
private string _package;
private string? _lastProtocolName;
private string? _protocolName;
private string? _clientSpecifiedSpn;
private ChannelBinding? _channelBinding;
// If set, no more calls should be made.
internal bool IsCompleted => _isCompleted;
internal bool IsValidContext => !(_securityContext == null || _securityContext.IsInvalid);
internal string Package => _package;
// True indicates this instance is for Server and will use AcceptSecurityContext SSPI API.
internal bool IsServer => _isServer;
internal string? ClientSpecifiedSpn => _clientSpecifiedSpn ??= GetClientSpecifiedSpn();
internal string ProtocolName
{
get
{
// Note: May return string.Empty if the auth is not done yet or failed.
if (_protocolName == null)
{
string? negotiationAuthenticationPackage = null;
if (IsValidContext)
{
negotiationAuthenticationPackage = NegotiateStreamPal.QueryContextAuthenticationPackage(_securityContext!);
if (IsCompleted)
{
_protocolName = negotiationAuthenticationPackage;
}
}
return negotiationAuthenticationPackage ?? string.Empty;
}
return _protocolName;
}
}
internal bool IsKerberos
{
get
{
_lastProtocolName ??= ProtocolName;
return _lastProtocolName == NegotiationInfoClass.Kerberos;
}
}
internal bool IsNTLM
{
get
{
_lastProtocolName ??= ProtocolName;
return _lastProtocolName == NegotiationInfoClass.NTLM;
}
}
//
// This overload does not attempt to impersonate because the caller either did it already or the original thread context is still preserved.
//
internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string? spn, ContextFlagsPal requestedContextFlags, ChannelBinding? channelBinding)
{
Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding);
}
[MemberNotNull(nameof(_package))]
private void Initialize(bool isServer, string package, NetworkCredential credential, string? spn, ContextFlagsPal requestedContextFlags, ChannelBinding? channelBinding)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"package={package}, spn={spn}, requestedContextFlags={requestedContextFlags}");
_tokenSize = NegotiateStreamPal.QueryMaxTokenSize(package);
_isServer = isServer;
_spn = spn;
_securityContext = null;
_requestedContextFlags = requestedContextFlags;
_package = package;
_channelBinding = channelBinding;
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
//
// Check if we're using DefaultCredentials.
//
Debug.Assert(CredentialCache.DefaultCredentials == CredentialCache.DefaultNetworkCredentials);
if (credential == CredentialCache.DefaultCredentials)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "using DefaultCredentials");
_credentialsHandle = NegotiateStreamPal.AcquireDefaultCredential(package, _isServer);
}
else
{
_credentialsHandle = NegotiateStreamPal.AcquireCredentialsHandle(package, _isServer, credential);
}
}
internal SafeDeleteContext? GetContext(out SecurityStatusPal status)
{
status = new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
Debug.Assert(IsCompleted && IsValidContext, "Should be called only when completed with success, currently is not!");
Debug.Assert(IsServer, "The method must not be called by the client side!");
if (!IsValidContext)
{
status = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidHandle);
return null;
}
return _securityContext;
}
internal void CloseContext()
{
if (_securityContext != null && !_securityContext.IsClosed)
{
_securityContext.Dispose();
}
_isCompleted = false;
}
internal NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted)
{
return NegotiateStreamPal.Wrap(_securityContext!, input, outputWriter, requestEncryption, out isEncrypted);
}
internal NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out bool wasEncrypted)
{
return NegotiateStreamPal.Unwrap(_securityContext!, input, outputWriter, out wasEncrypted);
}
internal NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
{
return NegotiateStreamPal.UnwrapInPlace(_securityContext!, input, out unwrappedOffset, out unwrappedLength, out wasEncrypted);
}
internal bool VerifyMIC(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature)
{
return NegotiateStreamPal.VerifyMIC(_securityContext!, (_contextFlags & ContextFlagsPal.Confidentiality) != 0, message, signature);
}
internal void GetMIC(ReadOnlySpan<byte> message, IBufferWriter<byte> signature)
{
NegotiateStreamPal.GetMIC(_securityContext!, (_contextFlags & ContextFlagsPal.Confidentiality) != 0, message, signature);
}
internal string? GetOutgoingBlob(string? incomingBlob)
{
return GetOutgoingBlob(incomingBlob, throwOnError: true, out _);
}
internal string? GetOutgoingBlob(string? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
byte[]? decodedIncomingBlob = null;
if (incomingBlob != null && incomingBlob.Length > 0)
{
decodedIncomingBlob = Convert.FromBase64String(incomingBlob);
}
byte[]? decodedOutgoingBlob = null;
if ((IsValidContext || IsCompleted) && decodedIncomingBlob == null)
{
// we tried auth previously, now we got a null blob, we're done. this happens
// with Kerberos & valid credentials on the domain but no ACLs on the resource
_isCompleted = true;
statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
}
else
{
decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, throwOnError, out statusCode);
}
string? outgoingBlob = null;
if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0)
{
outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob);
}
if (IsCompleted)
{
CloseContext();
}
return outgoingBlob;
}
internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError)
{
return GetOutgoingBlob(incomingBlob.AsSpan(), throwOnError, out _);
}
// Accepts an incoming binary security blob and returns an outgoing binary security blob.
internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
return GetOutgoingBlob(incomingBlob.AsSpan(), throwOnError, out statusCode);
}
internal byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
_tokenBuffer ??= _tokenSize == 0 ? Array.Empty<byte>() : new byte[_tokenSize];
bool firstTime = _securityContext == null;
int resultBlobLength;
try
{
if (!_isServer)
{
// client session
statusCode = NegotiateStreamPal.InitializeSecurityContext(
ref _credentialsHandle!,
ref _securityContext,
_spn,
_requestedContextFlags,
incomingBlob,
_channelBinding,
ref _tokenBuffer,
out resultBlobLength,
ref _contextFlags);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
if (statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded)
{
statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, _tokenBuffer.AsSpan(0, resultBlobLength));
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
resultBlobLength = 0;
}
}
else
{
// Server session.
statusCode = NegotiateStreamPal.AcceptSecurityContext(
_credentialsHandle,
ref _securityContext,
_requestedContextFlags,
incomingBlob,
_channelBinding,
ref _tokenBuffer,
out resultBlobLength,
ref _contextFlags);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
}
}
finally
{
//
// Assuming the ISC or ASC has referenced the credential on the first successful call,
// we want to decrement the effective ref count by "disposing" it.
// The real dispose will happen when the security context is closed.
// Note if the first call was not successful the handle is physically destroyed here.
//
if (firstTime)
{
_credentialsHandle?.Dispose();
}
}
if (((int)statusCode.ErrorCode >= (int)SecurityStatusPalErrorCode.OutOfMemory))
{
CloseContext();
_isCompleted = true;
_tokenBuffer = null;
if (throwOnError)
{
throw NegotiateStreamPal.CreateExceptionFromError(statusCode);
}
return null;
}
else if (firstTime && _credentialsHandle != null)
{
// Cache until it is pushed out by newly incoming handles.
SSPIHandleCache.CacheCredential(_credentialsHandle);
}
byte[]? result =
resultBlobLength == 0 || _tokenBuffer == null ? null :
_tokenBuffer.Length == resultBlobLength ? _tokenBuffer :
_tokenBuffer[0..resultBlobLength];
// The return value will tell us correctly if the handshake is over or not
if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK
|| (_isServer && statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded))
{
// Success.
_isCompleted = true;
_tokenBuffer = null;
}
else
{
// We need to continue.
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"need continue statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode}) _securityContext:{_securityContext}");
}
return result;
}
private string? GetClientSpecifiedSpn()
{
Debug.Assert(IsValidContext && IsCompleted, "Trying to get the client SPN before handshaking is done!");
string? spn = NegotiateStreamPal.QueryContextClientSpecifiedSpn(_securityContext!);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"The client specified SPN is [{spn}]");
return spn;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Net.Security;
using System.Runtime.Versioning;
using System.Security.Authentication.ExtendedProtection;
namespace System.Net
{
internal sealed partial class NTAuthentication
{
internal bool IsConfidentialityFlag => (_contextFlags & ContextFlagsPal.Confidentiality) != 0;
internal bool IsIntegrityFlag => (_contextFlags & (IsServer ? ContextFlagsPal.AcceptIntegrity : ContextFlagsPal.InitIntegrity)) != 0;
internal bool IsMutualAuthFlag => (_contextFlags & ContextFlagsPal.MutualAuth) != 0;
internal bool IsDelegationFlag => (_contextFlags & ContextFlagsPal.Delegate) != 0;
internal bool IsIdentifyFlag => (_contextFlags & (IsServer ? ContextFlagsPal.AcceptIdentify : ContextFlagsPal.InitIdentify)) != 0;
internal string? Spn => _spn;
}
}
// 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;
namespace System.Net
{
internal abstract partial class NegotiateAuthenticationPal
{
public static NegotiateAuthenticationPal Create(NegotiateAuthenticationClientOptions clientOptions)
{
switch (clientOptions.Package)
{
case NegotiationInfoClass.NTLM:
return new ManagedNtlmNegotiateAuthenticationPal(clientOptions);
case NegotiationInfoClass.Negotiate:
return new ManagedSpnegoNegotiateAuthenticationPal(clientOptions);
default:
return new UnsupportedNegotiateAuthenticationPal(clientOptions);
}
}
public static NegotiateAuthenticationPal Create(NegotiateAuthenticationServerOptions serverOptions)
{
return new UnsupportedNegotiateAuthenticationPal(serverOptions);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Security.Principal;
using System.Net.Security;
namespace System.Net
{
internal abstract partial class NegotiateAuthenticationPal
{
internal sealed class UnsupportedNegotiateAuthenticationPal : NegotiateAuthenticationPal
{
private string _package;
private string? _targetName;
public override bool IsAuthenticated => false;
public override bool IsSigned => false;
public override bool IsEncrypted => false;
public override bool IsMutuallyAuthenticated => false;
public override string Package => _package;
public override string? TargetName => _targetName;
public override IIdentity RemoteIdentity => throw new InvalidOperationException();
public override System.Security.Principal.TokenImpersonationLevel ImpersonationLevel => System.Security.Principal.TokenImpersonationLevel.Impersonation;
public UnsupportedNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions)
{
_package = clientOptions.Package;
_targetName = clientOptions.TargetName;
}
public UnsupportedNegotiateAuthenticationPal(NegotiateAuthenticationServerOptions serverOptions)
{
_package = serverOptions.Package;
}
public override void Dispose()
{
}
public override byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
{
statusCode = NegotiateAuthenticationStatusCode.Unsupported;
return null;
}
public override NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted) => throw new InvalidOperationException();
public override NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out bool wasEncrypted) => throw new InvalidOperationException();
public override NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) => throw new InvalidOperationException();
public override void GetMIC(ReadOnlySpan<byte> message, IBufferWriter<byte> signature) => throw new InvalidOperationException();
public override bool VerifyMIC(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature) => throw new InvalidOperationException();
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Security.Principal;
using System.Net.Security;
namespace System.Net
{
internal abstract partial class NegotiateAuthenticationPal : IDisposable
{
public abstract bool IsAuthenticated { get; }
public abstract bool IsSigned { get; }
public abstract bool IsEncrypted { get; }
public abstract bool IsMutuallyAuthenticated { get; }
public abstract string Package { get; }
public abstract string? TargetName { get; }
public abstract IIdentity RemoteIdentity { get; }
public abstract System.Security.Principal.TokenImpersonationLevel ImpersonationLevel { get; }
public abstract void Dispose();
public abstract byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, out NegotiateAuthenticationStatusCode statusCode);
public abstract NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted);
public abstract NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out bool wasEncrypted);
public abstract NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted);
public abstract void GetMIC(ReadOnlySpan<byte> message, IBufferWriter<byte> signature);
public abstract bool VerifyMIC(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature);
}
}
......@@ -25,7 +25,7 @@ public enum NegotiateAuthenticationStatusCode
BadBinding,
/// <summary>Unsupported authentication package was requested.</summary>
/// <remarks>Maps to GSS_S_BAD_MECH status in GSSAPI.</remarks>
/// <remarks>Maps to GSS_S_BAD_MECH or GSS_S_UNAVAILABLE status in GSSAPI.</remarks>
Unsupported,
/// <summary>Message was altered and failed an integrity check validation.</summary>
......
......@@ -640,7 +640,13 @@ void ThrowExceptional(ExceptionDispatchInfo e)
ArgumentNullException.ThrowIfNull(credential);
ArgumentNullException.ThrowIfNull(servicePrincipalName);
NegotiateStreamPal.ValidateImpersonationLevel(impersonationLevel);
if (impersonationLevel != TokenImpersonationLevel.Identification &&
impersonationLevel != TokenImpersonationLevel.Impersonation &&
impersonationLevel != TokenImpersonationLevel.Delegation)
{
throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_auth_supported_impl_levels);
}
if (_context is not null && IsServer != isServer)
{
throw new InvalidOperationException(SR.net_auth_client_server);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册