diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index d417e7b91ec1d..5e4c002898f64 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -279,6 +279,9 @@ The .NET Foundation licenses this file to you under the MIT license.
+
+
+
diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs
deleted file mode 100644
index 45be4535ec73b..0000000000000
--- a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs
+++ /dev/null
@@ -1,115 +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;
-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;
- }
- }
-}
diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs
deleted file mode 100644
index e29e9b48cfdef..0000000000000
--- a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs
+++ /dev/null
@@ -1,81 +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;
-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 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;
- }
- }
-}
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs
index 5ec6a53015cb2..780db637ba335 100644
--- a/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs
+++ b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs
@@ -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(
diff --git a/src/libraries/System.Net.Security/src/ILLink/ILLink.Substitutions.xml b/src/libraries/System.Net.Security/src/ILLink/ILLink.Substitutions.xml
new file mode 100644
index 0000000000000..8c56e81a9bb76
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/ILLink/ILLink.Substitutions.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
index 9cce5ff1e045b..a06dbd79b8f67 100644
--- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj
+++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
@@ -18,7 +18,7 @@
$(DefineConstants);SYSNETSECURITY_NO_OPENSSL
ReferenceAssemblyExclusions.txt
-
+
@@ -29,6 +29,8 @@
+
+
@@ -57,9 +59,7 @@
-
-
@@ -107,8 +107,6 @@
Link="Common\System\Net\Security\SSPIHandleCache.cs" />
-
+
-
@@ -171,7 +169,6 @@
Link="Common\System\Net\Security\SecurityBufferType.Windows.cs" />
-
@@ -287,13 +284,10 @@
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
diff --git a/src/libraries/System.Net.Security/src/System/Net/ContextFlagsAdapterPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/ContextFlagsAdapterPal.Unix.cs
deleted file mode 100644
index 6ded2dcc321f4..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/ContextFlagsAdapterPal.Unix.cs
+++ /dev/null
@@ -1,114 +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;
-
-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;
- }
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/ContextFlagsAdapterPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/ContextFlagsAdapterPal.Windows.cs
deleted file mode 100644
index 5ce5bf0410d73..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/ContextFlagsAdapterPal.Windows.cs
+++ /dev/null
@@ -1,76 +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;
-
-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;
- }
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/ContextFlagsPal.cs b/src/libraries/System.Net.Security/src/System/Net/ContextFlagsPal.cs
deleted file mode 100644
index 6dad121c15352..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/ContextFlagsPal.cs
+++ /dev/null
@@ -1,34 +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;
-
-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,
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.Common.cs b/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.Common.cs
deleted file mode 100644
index 26351a41f1731..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.Common.cs
+++ /dev/null
@@ -1,343 +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.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 input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted)
- {
- return NegotiateStreamPal.Wrap(_securityContext!, input, outputWriter, requestEncryption, out isEncrypted);
- }
-
- internal NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
- {
- return NegotiateStreamPal.Unwrap(_securityContext!, input, outputWriter, out wasEncrypted);
- }
-
- internal NegotiateAuthenticationStatusCode UnwrapInPlace(Span 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 message, ReadOnlySpan signature)
- {
- return NegotiateStreamPal.VerifyMIC(_securityContext!, (_contextFlags & ContextFlagsPal.Confidentiality) != 0, message, signature);
- }
-
- internal void GetMIC(ReadOnlySpan message, IBufferWriter 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 incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
- {
- _tokenBuffer ??= _tokenSize == 0 ? Array.Empty() : 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;
- }
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.Managed.cs
deleted file mode 100644
index 50bc657319097..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.Managed.cs
+++ /dev/null
@@ -1,1116 +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;
-using System.Buffers;
-using System.Buffers.Binary;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Formats.Asn1;
-using System.Net.Security;
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
-using System.Security.Authentication.ExtendedProtection;
-using System.Security.Cryptography;
-using System.Text;
-
-// NTLM uses all sorts of broken cryptographic primitives (HMAC-MD5, MD4, RC4)
-#pragma warning disable CA5351
-
-namespace System.Net
-{
- internal sealed partial class NTAuthentication
- {
- // Input parameters
- private readonly bool _isSpNego;
- private readonly NetworkCredential _credential;
- private readonly string? _spn;
- private readonly ChannelBinding? _channelBinding;
- private readonly ContextFlagsPal _contextFlags;
-
- // State parameters
- private byte[]? _spnegoMechList;
- private byte[]? _negotiateMessage;
- private byte[]? _clientSigningKey;
- private byte[]? _serverSigningKey;
- private byte[]? _clientSealingKey;
- private byte[]? _serverSealingKey;
- private RC4? _clientSeal;
- private RC4? _serverSeal;
- private uint _clientSequenceNumber;
- private uint _serverSequenceNumber;
- public bool IsCompleted { get; set; }
-
- // value should match the Windows sspicli NTE_FAIL value
- // defined in winerror.h
- private const int NTE_FAIL = unchecked((int)0x80090020);
-
- private static ReadOnlySpan NtlmHeader => "NTLMSSP\0"u8;
-
- private static ReadOnlySpan ClientSigningKeyMagic => "session key to client-to-server signing key magic constant\0"u8;
- private static ReadOnlySpan ServerSigningKeyMagic => "session key to server-to-client signing key magic constant\0"u8;
- private static ReadOnlySpan ClientSealingKeyMagic => "session key to client-to-server sealing key magic constant\0"u8;
- private static ReadOnlySpan ServerSealingKeyMagic => "session key to server-to-client sealing key magic constant\0"u8;
-
- private static readonly byte[] s_workstation = Encoding.Unicode.GetBytes(Environment.MachineName);
-
- private static SecurityStatusPal SecurityStatusPalOk = new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
- private static SecurityStatusPal SecurityStatusPalContinueNeeded = new SecurityStatusPal(SecurityStatusPalErrorCode.ContinueNeeded);
- private static SecurityStatusPal SecurityStatusPalInvalidToken = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken);
- private static SecurityStatusPal SecurityStatusPalInternalError = new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError);
- private static SecurityStatusPal SecurityStatusPalPackageNotFound = new SecurityStatusPal(SecurityStatusPalErrorCode.PackageNotFound);
- private static SecurityStatusPal SecurityStatusPalMessageAltered = new SecurityStatusPal(SecurityStatusPalErrorCode.MessageAltered);
- private static SecurityStatusPal SecurityStatusPalLogonDenied = new SecurityStatusPal(SecurityStatusPalErrorCode.LogonDenied);
-
- private const Flags s_requiredFlags =
- Flags.NegotiateNtlm2 | Flags.NegotiateNtlm | Flags.NegotiateUnicode | Flags.TargetName |
- Flags.NegotiateVersion | Flags.NegotiateKeyExchange | Flags.Negotiate128 |
- Flags.NegotiateTargetInfo | Flags.NegotiateAlwaysSign | Flags.NegotiateSign;
-
- private static readonly Version s_version = new Version { VersionMajor = 6, VersionMinor = 1, ProductBuild = 7600, CurrentRevision = 15 };
-
- private const int ChallengeResponseLength = 24;
-
- private const int HeaderLength = 8;
-
- private const int ChallengeLength = 8;
-
- private const int DigestLength = 16;
-
- private const int SessionKeyLength = 16;
- private const int SignatureLength = 16;
-
- private const string SpnegoOid = "1.3.6.1.5.5.2";
-
- private const string NtlmOid = "1.3.6.1.4.1.311.2.2.10";
-
- private enum MessageType : byte
- {
- Negotiate = 1,
- Challenge = 2,
- Authenticate = 3,
- }
-
- // 2.2.2.5 NEGOTIATE
- [Flags]
- private enum Flags : uint
- {
- NegotiateUnicode = 0x00000001,
- NegotiateOEM = 0x00000002,
- TargetName = 0x00000004,
- NegotiateSign = 0x00000010,
- NegotiateSeal = 0x00000020,
- NegotiateDatagram = 0x00000040,
- NegotiateLMKey = 0x00000080,
- NegotiateNtlm = 0x00000200,
- NegotiateAnonymous = 0x00000800,
- NegotiateDomainSupplied = 0x00001000,
- NegotiateWorkstationSupplied = 0x00002000,
- NegotiateAlwaysSign = 0x00008000,
- TargetTypeDomain = 0x00010000,
- TargetTypeServer = 0x00020000,
- NegotiateNtlm2 = 0x00080000,
- RequestIdenityToken = 0x00100000,
- RequestNonNtSessionKey = 0x00400000,
- NegotiateTargetInfo = 0x00800000,
- NegotiateVersion = 0x02000000,
- Negotiate128 = 0x20000000,
- NegotiateKeyExchange = 0x40000000,
- Negotiate56 = 0x80000000,
- }
-
- private enum AvId
- {
- EOL,
- NbComputerName,
- NbDomainName,
- DnsComputerName,
- DnsDomainName,
- DnsTreeName,
- Flags,
- Timestamp,
- SingleHost,
- TargetName,
- ChannelBindings,
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct MessageField
- {
- public ushort Length;
- public ushort MaximumLength;
- public int PayloadOffset;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct MessageHeader
- {
- public fixed byte Header[HeaderLength];
- public MessageType MessageType;
- private byte _unused1;
- private byte _unused2;
- private byte _unused3;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct Version
- {
- public byte VersionMajor;
- public byte VersionMinor;
- public ushort ProductBuild;
- private byte _unused4;
- private byte _unused5;
- private byte _unused6;
- public byte CurrentRevision;
- }
-
- // Type 1 message
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct NegotiateMessage
- {
- public MessageHeader Header;
- public Flags Flags;
- public MessageField DomainName;
- public MessageField WorkStation;
- public Version Version;
- }
-
- // TYPE 2 message
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct ChallengeMessage
- {
- public MessageHeader Header;
- public MessageField TargetName;
- public Flags Flags;
- public fixed byte ServerChallenge[ChallengeLength];
- private ulong _unused;
- public MessageField TargetInfo;
- public Version Version;
- }
-
- // TYPE 3 message
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct AuthenticateMessage
- {
- public MessageHeader Header;
- public MessageField LmChallengeResponse;
- public MessageField NtChallengeResponse;
- public MessageField DomainName;
- public MessageField UserName;
- public MessageField Workstation;
- public MessageField EncryptedRandomSessionKey;
- public Flags Flags;
- public Version Version;
- public fixed byte Mic[16];
- }
-
- // Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4))
- [StructLayout(LayoutKind.Sequential)]
- private unsafe struct NtChallengeResponse
- {
- public fixed byte Hmac[DigestLength];
- public byte Responserversion;
- public byte HiResponserversion;
- private byte _reserved1;
- private byte _reserved2;
- private int _reserved3;
- public long Time;
- public fixed byte ClientChallenge[ChallengeLength];
- private int _reserved4;
- public fixed byte ServerInfo[4]; // Has to be non-zero size, so set it to the Z(4) padding
- }
-
- // rfc4178
- private enum NegotiationToken
- {
- NegTokenInit = 0,
- NegTokenResp = 1
- }
-
- private enum NegTokenInit
- {
- MechTypes = 0,
- ReqFlags = 1,
- MechToken = 2,
- MechListMIC = 3
- }
-
- private enum NegTokenResp
- {
- NegState = 0,
- SupportedMech = 1,
- ResponseToken = 2,
- MechListMIC = 3
- }
-
- private enum NegState
- {
- Unknown = -1, // Internal. Not in RFC.
- AcceptCompleted = 0,
- AcceptIncomplete = 1,
- Reject = 2,
- RequestMic = 3
- }
-
- internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string? spn, ContextFlagsPal requestedContextFlags, ChannelBinding? channelBinding)
- {
- if (isServer)
- {
- throw new PlatformNotSupportedException(SR.net_nego_server_not_supported);
- }
-
- if (package.Equals("NTLM", StringComparison.OrdinalIgnoreCase))
- {
- _isSpNego = false;
- }
- else if (package.Equals("Negotiate", StringComparison.OrdinalIgnoreCase))
- {
- _isSpNego = true;
- }
- else
- {
- throw new PlatformNotSupportedException(SR.net_securitypackagesupport);
- }
-
- if (string.IsNullOrWhiteSpace(credential.UserName) || string.IsNullOrWhiteSpace(credential.Password))
- {
- // NTLM authentication is not possible with default credentials which are no-op
- throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred);
- }
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"package={package}, spn={spn}, requestedContextFlags={requestedContextFlags}");
-
- _credential = credential;
- _spn = spn;
- _channelBinding = channelBinding;
- _contextFlags = requestedContextFlags;
- IsServer = isServer;
- }
-
- internal void CloseContext()
- {
- // Dispose of the state
- _negotiateMessage = null;
- _clientSigningKey = null;
- _serverSigningKey = null;
- _clientSealingKey = null;
- _serverSealingKey = null;
- _clientSeal?.Dispose();
- _serverSeal?.Dispose();
- _clientSeal = null;
- _serverSeal = null;
- _clientSequenceNumber = 0;
- _serverSequenceNumber = 0;
- IsCompleted = false;
- }
-
- internal string? GetOutgoingBlob(string? incomingBlob)
- {
- return GetOutgoingBlob(incomingBlob, throwOnError: true, out _);
- }
-
- internal unsafe string? GetOutgoingBlob(string? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
- {
- Debug.Assert(!IsCompleted);
-
- byte[]? decodedIncomingBlob = null;
- if (incomingBlob != null && incomingBlob.Length > 0)
- {
- decodedIncomingBlob = Convert.FromBase64String(incomingBlob);
- }
- byte[]? 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 unsafe byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
- {
- byte[]? outgoingBlob;
-
- // TODO: Logging, validation
- if (_negotiateMessage == null)
- {
- Debug.Assert(incomingBlob.IsEmpty);
-
- _negotiateMessage = new byte[sizeof(NegotiateMessage)];
- CreateNtlmNegotiateMessage(_negotiateMessage);
-
- outgoingBlob = _isSpNego ? CreateSpNegoNegotiateMessage(_negotiateMessage) : _negotiateMessage;
- statusCode = SecurityStatusPalContinueNeeded;
- }
- else
- {
- Debug.Assert(!incomingBlob.IsEmpty);
-
- if (!_isSpNego)
- {
- IsCompleted = true;
- outgoingBlob = ProcessChallenge(incomingBlob, out statusCode);
- }
- else
- {
- outgoingBlob = ProcessSpNegoChallenge(incomingBlob, out statusCode);
- }
- }
-
- if (statusCode.ErrorCode >= SecurityStatusPalErrorCode.OutOfMemory && throwOnError)
- {
- throw new Win32Exception(NTE_FAIL, statusCode.ErrorCode.ToString());
- }
-
- return outgoingBlob;
- }
-
- private static unsafe void CreateNtlmNegotiateMessage(Span asBytes)
- {
- Debug.Assert(HeaderLength == NtlmHeader.Length);
- Debug.Assert(asBytes.Length == sizeof(NegotiateMessage));
-
- ref NegotiateMessage message = ref MemoryMarshal.AsRef(asBytes);
-
- asBytes.Clear();
- NtlmHeader.CopyTo(asBytes);
- message.Header.MessageType = MessageType.Negotiate;
- message.Flags = s_requiredFlags;
- message.Version = s_version;
- }
-
- private static unsafe int GetFieldLength(MessageField field)
- {
- ReadOnlySpan span = new ReadOnlySpan(&field, sizeof(MessageField));
- return BinaryPrimitives.ReadInt16LittleEndian(span);
- }
-
- private static unsafe int GetFieldOffset(MessageField field)
- {
- ReadOnlySpan span = new ReadOnlySpan(&field, sizeof(MessageField));
- return BinaryPrimitives.ReadInt16LittleEndian(span.Slice(4));
- }
-
- private static ReadOnlySpan GetField(MessageField field, ReadOnlySpan payload)
- {
- int offset = GetFieldOffset(field);
- int length = GetFieldLength(field);
-
- if (length == 0 || offset + length > payload.Length)
- {
- return ReadOnlySpan.Empty;
- }
-
- return payload.Slice(GetFieldOffset(field), GetFieldLength(field));
- }
-
- private static unsafe void SetField(ref MessageField field, int length, int offset)
- {
- if (length > short.MaxValue)
- {
- throw new Win32Exception(NTE_FAIL);
- }
-
- Span span = MemoryMarshal.AsBytes(new Span(ref field));
- BinaryPrimitives.WriteInt16LittleEndian(span, (short)length);
- BinaryPrimitives.WriteInt16LittleEndian(span.Slice(2), (short)length);
- BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), offset);
- }
-
- private static void AddToPayload(ref MessageField field, ReadOnlySpan data, Span payload, ref int offset)
- {
- SetField(ref field, data.Length, offset);
- data.CopyTo(payload.Slice(offset));
- offset += data.Length;
- }
-
- private static void AddToPayload(ref MessageField field, ReadOnlySpan data, Span payload, ref int offset)
- {
- int dataLength = Encoding.Unicode.GetBytes(data, payload.Slice(offset));
- SetField(ref field, dataLength, offset);
- offset += dataLength;
- }
-
- // Section 3.3.2
- // Define NTOWFv2(Passwd, User, UserDom) as HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(ConcatenationOf(Uppercase(User),
- // UserDom ) ) )
- // EndDefine
- private static void makeNtlm2Hash(string domain, string userName, ReadOnlySpan password, Span hash)
- {
- // Maximum password length for Windows authentication is 128 characters, we enforce
- // the limit early to prevent allocating large buffers on stack.
- if (password.Length > 128)
- {
- throw new Win32Exception(NTE_FAIL);
- }
-
- Span pwHash = stackalloc byte[DigestLength];
- Span pwBytes = stackalloc byte[Encoding.Unicode.GetByteCount(password)];
-
- try
- {
- Encoding.Unicode.GetBytes(password, pwBytes);
- MD4.HashData(pwBytes, pwHash);
- // strangely, user is upper case, domain is not.
- byte[] blob = Encoding.Unicode.GetBytes(string.Concat(userName.ToUpperInvariant(), domain));
- int written = HMACMD5.HashData(pwHash, blob, hash);
- Debug.Assert(written == HMACMD5.HashSizeInBytes);
- }
- finally
- {
- CryptographicOperations.ZeroMemory(pwBytes);
- CryptographicOperations.ZeroMemory(pwHash);
- }
- }
-
- // Section 3.3.2
- //
- // Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4))
- // Set NTProofStr to HMAC_MD5(ResponseKeyNT, ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge, temp))
- // Set NtChallengeResponse to ConcatenationOf(NTProofStr, temp)
- private unsafe void makeNtlm2ChallengeResponse(DateTime time, ReadOnlySpan ntlm2hash, ReadOnlySpan serverChallenge, Span clientChallenge, ReadOnlySpan serverInfo, ref MessageField field, Span payload, ref int payloadOffset)
- {
- Debug.Assert(serverChallenge.Length == ChallengeLength);
- Debug.Assert(clientChallenge.Length == ChallengeLength);
- Debug.Assert(ntlm2hash.Length == DigestLength);
-
- Span blob = payload.Slice(payloadOffset, sizeof(NtChallengeResponse) + serverInfo.Length);
- ref NtChallengeResponse temp = ref MemoryMarshal.AsRef(blob.Slice(0, sizeof(NtChallengeResponse)));
-
- temp.HiResponserversion = 1;
- temp.Responserversion = 1;
- temp.Time = time.ToFileTimeUtc();
-
- clientChallenge.CopyTo(MemoryMarshal.CreateSpan(ref temp.ClientChallenge[0], ChallengeLength));
- serverInfo.CopyTo(MemoryMarshal.CreateSpan(ref temp.ServerInfo[0], serverInfo.Length));
-
- // Calculate NTProofStr
- // we created temp part in place where it needs to be.
- // now we need to prepend it with calculated hmac.
- using (var hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, ntlm2hash))
- {
- hmac.AppendData(serverChallenge);
- hmac.AppendData(blob.Slice(DigestLength));
- hmac.GetHashAndReset(blob);
- }
-
- SetField(ref field, blob.Length, payloadOffset);
-
- payloadOffset += blob.Length;
- }
-
- private unsafe void WriteChannelBindingHash(Span hashBuffer)
- {
- if (_channelBinding != null)
- {
- IntPtr cbtData = _channelBinding.DangerousGetHandle();
- int cbtDataSize = _channelBinding.Size;
- int written = MD5.HashData(new Span((void*)cbtData, cbtDataSize), hashBuffer);
- Debug.Assert(written == MD5.HashSizeInBytes);
- }
- else
- {
- hashBuffer.Clear();
- }
- }
-
- private byte[] ProcessTargetInfo(ReadOnlySpan targetInfo, out DateTime time, out bool hasNbNames)
- {
- int spnSize = _spn != null ? Encoding.Unicode.GetByteCount(_spn) : 0;
-
- if (spnSize > short.MaxValue)
- {
- throw new Win32Exception(NTE_FAIL);
- }
-
- bool hasNbComputerName = false, hasNbDomainName = false;
- byte[] targetInfoBuffer = new byte[targetInfo.Length + 20 /* channel binding */ + 4 + spnSize /* SPN */ + 8 /* flags */];
- int targetInfoOffset = 0;
-
- time = DateTime.UtcNow;
-
- if (targetInfo.Length > 0)
- {
- ReadOnlySpan info = targetInfo;
- while (info.Length >= 4)
- {
- AvId ID = (AvId)info[0];
- byte l1 = info[2];
- byte l2 = info[3];
- int length = (l2 << 8) + l1;
-
- if (ID == AvId.EOL)
- {
- break;
- }
-
- if (ID == AvId.Timestamp)
- {
- time = DateTime.FromFileTimeUtc(BitConverter.ToInt64(info.Slice(4, 8)));
- }
- else if (ID == AvId.TargetName || ID == AvId.ChannelBindings)
- {
- // Skip these, we insert them ourselves
- info = info.Slice(length + 4);
- continue;
- }
- else if (ID == AvId.NbComputerName)
- {
- hasNbComputerName = true;
- }
- else if (ID == AvId.NbDomainName)
- {
- hasNbDomainName = true;
- }
-
- // Copy attribute-value pair into destination target info
- info.Slice(0, length + 4).CopyTo(targetInfoBuffer.AsSpan(targetInfoOffset, length + 4));
- targetInfoOffset += length + 4;
-
- info = info.Slice(length + 4);
- }
- }
-
- hasNbNames = hasNbComputerName && hasNbDomainName;
-
- // Add new entries into destination target info
-
- // Target name (eg. HTTP/example.org)
- targetInfoBuffer[targetInfoOffset] = (byte)AvId.TargetName;
- BinaryPrimitives.WriteUInt16LittleEndian(targetInfoBuffer.AsSpan(2 + targetInfoOffset), (ushort)spnSize);
- if (_spn != null)
- {
- int bytesWritten = Encoding.Unicode.GetBytes(_spn, targetInfoBuffer.AsSpan(4 + targetInfoOffset));
- Debug.Assert(bytesWritten == spnSize);
- }
- targetInfoOffset += spnSize + 4;
-
- // Channel binding
- targetInfoBuffer[targetInfoOffset] = (byte)AvId.ChannelBindings;
- targetInfoBuffer[targetInfoOffset + 2] = 16;
- WriteChannelBindingHash(targetInfoBuffer.AsSpan(targetInfoOffset + 4, 16));
- targetInfoOffset += 16 + 4;
-
- // Flags
- targetInfoBuffer[targetInfoOffset] = (byte)AvId.Flags;
- targetInfoBuffer[targetInfoOffset + 2] = 4; // Length of flags
- targetInfoBuffer[targetInfoOffset + 4] = 2; // MIC flag
- targetInfoOffset += 8;
-
- // EOL
- targetInfoOffset += 4;
-
- if (targetInfoOffset == targetInfoBuffer.Length)
- {
- return targetInfoBuffer;
- }
-
- return targetInfoBuffer.AsSpan(targetInfoOffset).ToArray();
- }
-
- // Section 3.4.5.2 SIGNKEY, 3.4.5.3 SEALKEY
- private static byte[] DeriveKey(ReadOnlySpan exportedSessionKey, ReadOnlySpan magic)
- {
- using (var md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5))
- {
- md5.AppendData(exportedSessionKey);
- md5.AppendData(magic);
- return md5.GetHashAndReset();
- }
- }
-
- // This gets decoded byte blob and returns response in binary form.
- private unsafe byte[]? ProcessChallenge(ReadOnlySpan blob, out SecurityStatusPal statusCode)
- {
- // TODO: Validate size and offsets
-
- ref readonly ChallengeMessage challengeMessage = ref MemoryMarshal.AsRef(blob.Slice(0, sizeof(ChallengeMessage)));
-
- // Verify message type and signature
- if (challengeMessage.Header.MessageType != MessageType.Challenge ||
- !NtlmHeader.SequenceEqual(blob.Slice(0, NtlmHeader.Length)))
- {
- statusCode = SecurityStatusPalInvalidToken;
- return null;
- }
-
- Flags flags = BitConverter.IsLittleEndian ? challengeMessage.Flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)challengeMessage.Flags);
- ReadOnlySpan targetName = GetField(challengeMessage.TargetName, blob);
-
- // Only NTLMv2 with MIC is supported
- //
- // NegotiateSign and NegotiateKeyExchange are necessary to calculate the key
- // that is used for MIC.
- if ((flags & s_requiredFlags) != s_requiredFlags)
- {
- statusCode = SecurityStatusPalInvalidToken;
- return null;
- }
-
- ReadOnlySpan targetInfo = GetField(challengeMessage.TargetInfo, blob);
- byte[] targetInfoBuffer = ProcessTargetInfo(targetInfo, out DateTime time, out bool hasNbNames);
-
- // If NTLM v2 authentication is used and the CHALLENGE_MESSAGE does not contain both
- // MsvAvNbComputerName and MsvAvNbDomainName AVPairs and either Integrity is TRUE or
- // Confidentiality is TRUE, then return STATUS_LOGON_FAILURE ([MS-ERREF] section 2.3.1).
- if (!hasNbNames && (flags & (Flags.NegotiateSign | Flags.NegotiateSeal)) != 0)
- {
- statusCode = SecurityStatusPalInvalidToken;
- return null;
- }
-
- int responseLength =
- sizeof(AuthenticateMessage) +
- ChallengeResponseLength +
- sizeof(NtChallengeResponse) +
- targetInfoBuffer.Length +
- Encoding.Unicode.GetByteCount(_credential.UserName) +
- Encoding.Unicode.GetByteCount(_credential.Domain) +
- s_workstation.Length +
- SessionKeyLength;
-
- byte[] responseBytes = new byte[responseLength];
- Span responseAsSpan = new Span(responseBytes);
- ref AuthenticateMessage response = ref MemoryMarshal.AsRef(responseAsSpan.Slice(0, sizeof(AuthenticateMessage)));
-
- // variable fields
- Span payload = responseAsSpan;
- int payloadOffset = sizeof(AuthenticateMessage);
-
- responseAsSpan.Clear();
- NtlmHeader.CopyTo(responseAsSpan);
-
- response.Header.MessageType = MessageType.Authenticate;
- response.Flags = s_requiredFlags;
- response.Version = s_version;
-
- // Calculate hash for hmac - same for lm2 and ntlm2
- Span ntlm2hash = stackalloc byte[DigestLength];
- makeNtlm2Hash(_credential.Domain, _credential.UserName, _credential.Password, ntlm2hash);
-
- // Get random bytes for client challenge
- Span clientChallenge = stackalloc byte[ChallengeLength];
- RandomNumberGenerator.Fill(clientChallenge);
-
- // Create empty LM2 response.
- SetField(ref response.LmChallengeResponse, ChallengeResponseLength, payloadOffset);
- payload.Slice(payloadOffset, ChallengeResponseLength).Clear();
- payloadOffset += ChallengeResponseLength;
-
- // Create NTLM2 response
- ReadOnlySpan serverChallenge = blob.Slice(24, 8);
- makeNtlm2ChallengeResponse(time, ntlm2hash, serverChallenge, clientChallenge, targetInfoBuffer, ref response.NtChallengeResponse, payload, ref payloadOffset);
- Debug.Assert(payloadOffset == sizeof(AuthenticateMessage) + ChallengeResponseLength + sizeof(NtChallengeResponse) + targetInfoBuffer.Length);
-
- AddToPayload(ref response.UserName, _credential.UserName, payload, ref payloadOffset);
- AddToPayload(ref response.DomainName, _credential.Domain, payload, ref payloadOffset);
- AddToPayload(ref response.Workstation, s_workstation, payload, ref payloadOffset);
-
- // Generate random session key that will be used for signing the messages
- Span exportedSessionKey = stackalloc byte[16];
- RandomNumberGenerator.Fill(exportedSessionKey);
-
- // Both flags are necessary to exchange keys needed for MIC (!)
- Debug.Assert(flags.HasFlag(Flags.NegotiateSign) && flags.HasFlag(Flags.NegotiateKeyExchange));
-
- // Derive session base key
- Span sessionBaseKey = stackalloc byte[HMACMD5.HashSizeInBytes];
- int sessionKeyWritten = HMACMD5.HashData(ntlm2hash, responseAsSpan.Slice(response.NtChallengeResponse.PayloadOffset, 16), sessionBaseKey);
- Debug.Assert(sessionKeyWritten == HMACMD5.HashSizeInBytes);
-
- // Encrypt exportedSessionKey with sessionBaseKey
- using (RC4 rc4 = new RC4(sessionBaseKey))
- {
- Span encryptedRandomSessionKey = payload.Slice(payloadOffset, 16);
- rc4.Transform(exportedSessionKey, encryptedRandomSessionKey);
- SetField(ref response.EncryptedRandomSessionKey, 16, payloadOffset);
- payloadOffset += 16;
- }
-
- // Calculate MIC
- Debug.Assert(_negotiateMessage != null);
- using (var hmacMic = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, exportedSessionKey))
- {
- hmacMic.AppendData(_negotiateMessage);
- hmacMic.AppendData(blob);
- hmacMic.AppendData(responseBytes.AsSpan(0, payloadOffset));
- hmacMic.GetHashAndReset(MemoryMarshal.CreateSpan(ref response.Mic[0], hmacMic.HashLengthInBytes));
- }
-
- // Derive signing keys
- _clientSigningKey = DeriveKey(exportedSessionKey, ClientSigningKeyMagic);
- _serverSigningKey = DeriveKey(exportedSessionKey, ServerSigningKeyMagic);
- _clientSealingKey = DeriveKey(exportedSessionKey, ClientSealingKeyMagic);
- _serverSealingKey = DeriveKey(exportedSessionKey, ServerSealingKeyMagic);
- ResetKeys();
- _clientSequenceNumber = 0;
- _serverSequenceNumber = 0;
- CryptographicOperations.ZeroMemory(exportedSessionKey);
-
- Debug.Assert(payloadOffset == responseBytes.Length);
-
- statusCode = SecurityStatusPalOk;
- return responseBytes;
- }
-
- private void ResetKeys()
- {
- // Release buffers to pool
- _clientSeal?.Dispose();
- _serverSeal?.Dispose();
-
- _clientSeal = new RC4(_clientSealingKey);
- _serverSeal = new RC4(_serverSealingKey);
- }
-
- private void CalculateSignature(
- ReadOnlySpan message,
- uint sequenceNumber,
- ReadOnlySpan signingKey,
- RC4 seal,
- Span signature)
- {
- BinaryPrimitives.WriteInt32LittleEndian(signature, 1);
- BinaryPrimitives.WriteUInt32LittleEndian(signature.Slice(12), sequenceNumber);
- using (var hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, signingKey))
- {
- hmac.AppendData(signature.Slice(12, 4));
- hmac.AppendData(message);
- Span hmacResult = stackalloc byte[hmac.HashLengthInBytes];
- hmac.GetHashAndReset(hmacResult);
- seal.Transform(hmacResult.Slice(0, 8), signature.Slice(4, 8));
- }
- }
-
- internal bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature)
- {
- // Check length and version
- if (signature.Length != SignatureLength ||
- BinaryPrimitives.ReadInt32LittleEndian(signature) != 1 ||
- _serverSeal == null ||
- _serverSigningKey == null)
- {
- return false;
- }
-
- Span expectedSignature = stackalloc byte[SignatureLength];
- CalculateSignature(message, _serverSequenceNumber, _serverSigningKey, _serverSeal, expectedSignature);
-
- _serverSequenceNumber++;
-
- return signature.SequenceEqual(expectedSignature);
- }
-
- internal void GetMIC(ReadOnlySpan message, IBufferWriter signature)
- {
- Debug.Assert(_clientSeal is not null);
- Debug.Assert(_clientSigningKey is not null);
-
- Span signatureBuffer = signature.GetSpan(SignatureLength);
- CalculateSignature(message, _clientSequenceNumber, _clientSigningKey, _clientSeal, signatureBuffer);
- _clientSequenceNumber++;
- signature.Advance(SignatureLength);
- }
-
- private byte[] GetMIC(ReadOnlySpan message)
- {
- Debug.Assert(_clientSeal is not null);
- Debug.Assert(_clientSigningKey is not null);
-
- byte[] signature = new byte[SignatureLength];
- CalculateSignature(message, _clientSequenceNumber, _clientSigningKey, _clientSeal, signature);
- _clientSequenceNumber++;
- return signature;
- }
-
- private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegotiateMessage)
- {
- AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
- using (writer.PushSequence(new Asn1Tag(TagClass.Application, 0)))
- {
- writer.WriteObjectIdentifier(SpnegoOid);
-
- // NegTokenInit::= SEQUENCE {
- // mechTypes[0] MechTypeList,
- // reqFlags[1] ContextFlags OPTIONAL,
- // --inherited from RFC 2478 for backward compatibility,
- // --RECOMMENDED to be left out
- // mechToken[2] OCTET STRING OPTIONAL,
- // mechListMIC[3] OCTET STRING OPTIONAL,
- // ...
- // }
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenInit)))
- {
- using (writer.PushSequence())
- {
- // MechType::= OBJECT IDENTIFIER
- // -- OID represents each security mechanism as suggested by
- // --[RFC2743]
- //
- // MechTypeList::= SEQUENCE OF MechType
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenInit.MechTypes)))
- {
- AsnWriter mechListWriter = new AsnWriter(AsnEncodingRules.DER);
-
- using (mechListWriter.PushSequence())
- {
- mechListWriter.WriteObjectIdentifier(NtlmOid);
- }
-
- _spnegoMechList = mechListWriter.Encode();
- mechListWriter.CopyTo(writer);
- }
-
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenInit.MechToken)))
- {
- writer.WriteOctetString(ntlmNegotiateMessage);
- }
- }
- }
- }
-
- return writer.Encode();
- }
-
- private unsafe byte[]? ProcessSpNegoChallenge(ReadOnlySpan challenge, out SecurityStatusPal statusCode)
- {
- NegState state = NegState.Unknown;
- string? mech = null;
- byte[]? blob = null;
- byte[]? mechListMIC = null;
-
- try
- {
- AsnValueReader reader = new AsnValueReader(challenge, AsnEncodingRules.DER);
- AsnValueReader challengeReader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp));
- reader.ThrowIfNotEmpty();
-
- // NegTokenResp ::= SEQUENCE {
- // negState[0] ENUMERATED {
- // accept - completed(0),
- // accept - incomplete(1),
- // reject(2),
- // request - mic(3)
- // } OPTIONAL,
- // --REQUIRED in the first reply from the target
- // supportedMech[1] MechType OPTIONAL,
- // --present only in the first reply from the target
- // responseToken[2] OCTET STRING OPTIONAL,
- // mechListMIC[3] OCTET STRING OPTIONAL,
- // ...
- // }
-
- challengeReader = challengeReader.ReadSequence();
-
- if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState)))
- {
- AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState));
- state = valueReader.ReadEnumeratedValue();
- valueReader.ThrowIfNotEmpty();
- }
-
- if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech)))
- {
- AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech));
- mech = valueReader.ReadObjectIdentifier();
- valueReader.ThrowIfNotEmpty();
- }
-
- if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)))
- {
- AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken));
- blob = valueReader.ReadOctetString();
- valueReader.ThrowIfNotEmpty();
- }
-
- if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)))
- {
- AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC));
- mechListMIC = valueReader.ReadOctetString();
- valueReader.ThrowIfNotEmpty();
- }
-
- challengeReader.ThrowIfNotEmpty();
- }
- catch (AsnContentException)
- {
- statusCode = SecurityStatusPalInvalidToken;
- return null;
- }
-
- if (blob?.Length > 0)
- {
- // Mechanism should be set on first message. In case of NTLM that's the only
- // message with the challenge blob.
- if (!NtlmOid.Equals(mech))
- {
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Server requested unknown mechanism {mech}");
- statusCode = SecurityStatusPalPackageNotFound;
- return null;
- }
-
- // Process decoded NTLM blob.
- byte[]? response = ProcessChallenge(blob, out statusCode);
-
- if (statusCode.ErrorCode != SecurityStatusPalErrorCode.OK)
- {
- return null;
- }
-
- if (response?.Length > 0)
- {
- AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
-
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp)))
- {
- using (writer.PushSequence())
- {
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)))
- {
- writer.WriteOctetString(response);
- }
-
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)))
- {
- writer.WriteOctetString(GetMIC(_spnegoMechList));
- }
- }
- }
-
- statusCode = state == NegState.RequestMic ? SecurityStatusPalContinueNeeded : SecurityStatusPalOk;
- return writer.Encode();
- }
- }
-
- if (mechListMIC != null)
- {
- if (_spnegoMechList == null || state != NegState.AcceptCompleted)
- {
- statusCode = SecurityStatusPalInternalError;
- return null;
- }
-
- if (!VerifyMIC(_spnegoMechList, mechListMIC))
- {
- statusCode = SecurityStatusPalMessageAltered;
- return null;
- }
-
- ResetKeys();
- }
-
- IsCompleted = state == NegState.AcceptCompleted || state == NegState.Reject;
- statusCode = state switch {
- NegState.AcceptCompleted => SecurityStatusPalOk,
- NegState.AcceptIncomplete => SecurityStatusPalContinueNeeded,
- NegState.Reject => SecurityStatusPalLogonDenied,
- _ => SecurityStatusPalInternalError
- };
-
- return null;
- }
-
- internal NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool _/*requestEncryption*/, out bool isEncrypted)
- {
- if (_clientSeal == null)
- {
- throw new InvalidOperationException(SR.net_auth_noauth);
- }
-
- Span output = outputWriter.GetSpan(input.Length + SignatureLength);
- _clientSeal.Transform(input, output.Slice(SignatureLength, input.Length));
- CalculateSignature(input, _clientSequenceNumber, _clientSigningKey, _clientSeal, output.Slice(0, SignatureLength));
- _clientSequenceNumber++;
-
- isEncrypted = true;
- outputWriter.Advance(input.Length + SignatureLength);
-
- return NegotiateAuthenticationStatusCode.Completed;
- }
-
- internal NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
- {
- wasEncrypted = true;
-
- if (_serverSeal == null)
- {
- throw new InvalidOperationException(SR.net_auth_noauth);
- }
-
- if (input.Length < SignatureLength)
- {
- return NegotiateAuthenticationStatusCode.InvalidToken;
- }
-
- Span output = outputWriter.GetSpan(input.Length - SignatureLength);
- _serverSeal.Transform(input.Slice(SignatureLength), output.Slice(0, input.Length - SignatureLength));
- if (!VerifyMIC(output.Slice(0, input.Length - SignatureLength), input.Slice(0, SignatureLength)))
- {
- CryptographicOperations.ZeroMemory(output);
- return NegotiateAuthenticationStatusCode.MessageAltered;
- }
-
- outputWriter.Advance(input.Length - SignatureLength);
-
- return NegotiateAuthenticationStatusCode.Completed;
- }
-
- internal NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
- {
- wasEncrypted = true;
- unwrappedOffset = SignatureLength;
- unwrappedLength = input.Length - SignatureLength;
-
- if (_serverSeal == null)
- {
- throw new InvalidOperationException(SR.net_auth_noauth);
- }
-
- if (input.Length < SignatureLength)
- {
- return NegotiateAuthenticationStatusCode.InvalidToken;
- }
-
- _serverSeal.Transform(input.Slice(SignatureLength), input.Slice(SignatureLength));
- if (!VerifyMIC(input.Slice(SignatureLength), input.Slice(0, SignatureLength)))
- {
- CryptographicOperations.ZeroMemory(input.Slice(SignatureLength));
- return NegotiateAuthenticationStatusCode.MessageAltered;
- }
-
- return NegotiateAuthenticationStatusCode.Completed;
- }
-
- internal string ProtocolName => _isSpNego ? NegotiationInfoClass.Negotiate : NegotiationInfoClass.NTLM;
-
-#pragma warning disable CA1822, IDE0060
- internal bool IsNTLM => true;
-
- internal bool IsKerberos => false;
-
- internal bool IsServer { get; set; }
-
- internal bool IsValidContext => true;
-#pragma warning restore CA1822, IDE0060
-
- internal string? ClientSpecifiedSpn => _spn;
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs
deleted file mode 100644
index f67274fe139bd..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs
+++ /dev/null
@@ -1,26 +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.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;
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Managed.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Managed.cs
new file mode 100644
index 0000000000000..4e5e8906b795c
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Managed.cs
@@ -0,0 +1,30 @@
+// 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);
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs
new file mode 100644
index 0000000000000..866a754af7292
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs
@@ -0,0 +1,800 @@
+// 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.Buffers.Binary;
+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;
+using System.Security.Cryptography;
+using System.Security.Principal;
+using System.Text;
+
+// NTLM uses all sorts of broken cryptographic primitives (HMAC-MD5, MD4, RC4)
+#pragma warning disable CA5351
+
+namespace System.Net
+{
+ internal abstract partial class NegotiateAuthenticationPal
+ {
+ internal sealed class ManagedNtlmNegotiateAuthenticationPal : NegotiateAuthenticationPal
+ {
+ // Input parameters
+ private readonly NetworkCredential _credential;
+ private readonly string? _spn;
+ private readonly ChannelBinding? _channelBinding;
+ private readonly ProtectionLevel _protectionLevel;
+
+ // State parameters
+ private byte[]? _negotiateMessage;
+ private byte[]? _clientSigningKey;
+ private byte[]? _serverSigningKey;
+ private byte[]? _clientSealingKey;
+ private byte[]? _serverSealingKey;
+ private RC4? _clientSeal;
+ private RC4? _serverSeal;
+ private uint _clientSequenceNumber;
+ private uint _serverSequenceNumber;
+ private bool _isAuthenticated;
+
+ // value should match the Windows sspicli NTE_FAIL value
+ // defined in winerror.h
+ private const int NTE_FAIL = unchecked((int)0x80090020);
+
+ private static ReadOnlySpan NtlmHeader => "NTLMSSP\0"u8;
+
+ private static ReadOnlySpan ClientSigningKeyMagic => "session key to client-to-server signing key magic constant\0"u8;
+ private static ReadOnlySpan ServerSigningKeyMagic => "session key to server-to-client signing key magic constant\0"u8;
+ private static ReadOnlySpan ClientSealingKeyMagic => "session key to client-to-server sealing key magic constant\0"u8;
+ private static ReadOnlySpan ServerSealingKeyMagic => "session key to server-to-client sealing key magic constant\0"u8;
+
+ private static readonly byte[] s_workstation = Encoding.Unicode.GetBytes(Environment.MachineName);
+
+ private const Flags s_requiredFlags =
+ Flags.NegotiateNtlm2 | Flags.NegotiateNtlm | Flags.NegotiateUnicode | Flags.TargetName |
+ Flags.NegotiateVersion | Flags.NegotiateKeyExchange | Flags.Negotiate128 |
+ Flags.NegotiateTargetInfo | Flags.NegotiateAlwaysSign | Flags.NegotiateSign;
+
+ private static readonly Version s_version = new Version { VersionMajor = 6, VersionMinor = 1, ProductBuild = 7600, CurrentRevision = 15 };
+
+ private const int ChallengeResponseLength = 24;
+
+ private const int HeaderLength = 8;
+
+ private const int ChallengeLength = 8;
+
+ private const int DigestLength = 16;
+
+ private const int SessionKeyLength = 16;
+ private const int SignatureLength = 16;
+
+ private enum MessageType : byte
+ {
+ Negotiate = 1,
+ Challenge = 2,
+ Authenticate = 3,
+ }
+
+ // 2.2.2.5 NEGOTIATE
+ [Flags]
+ private enum Flags : uint
+ {
+ NegotiateUnicode = 0x00000001,
+ NegotiateOEM = 0x00000002,
+ TargetName = 0x00000004,
+ NegotiateSign = 0x00000010,
+ NegotiateSeal = 0x00000020,
+ NegotiateDatagram = 0x00000040,
+ NegotiateLMKey = 0x00000080,
+ NegotiateNtlm = 0x00000200,
+ NegotiateAnonymous = 0x00000800,
+ NegotiateDomainSupplied = 0x00001000,
+ NegotiateWorkstationSupplied = 0x00002000,
+ NegotiateAlwaysSign = 0x00008000,
+ TargetTypeDomain = 0x00010000,
+ TargetTypeServer = 0x00020000,
+ NegotiateNtlm2 = 0x00080000,
+ RequestIdenityToken = 0x00100000,
+ RequestNonNtSessionKey = 0x00400000,
+ NegotiateTargetInfo = 0x00800000,
+ NegotiateVersion = 0x02000000,
+ Negotiate128 = 0x20000000,
+ NegotiateKeyExchange = 0x40000000,
+ Negotiate56 = 0x80000000,
+ }
+
+ private enum AvId
+ {
+ EOL,
+ NbComputerName,
+ NbDomainName,
+ DnsComputerName,
+ DnsDomainName,
+ DnsTreeName,
+ Flags,
+ Timestamp,
+ SingleHost,
+ TargetName,
+ ChannelBindings,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct MessageField
+ {
+ public ushort Length;
+ public ushort MaximumLength;
+ public int PayloadOffset;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct MessageHeader
+ {
+ public fixed byte Header[HeaderLength];
+ public MessageType MessageType;
+ private byte _unused1;
+ private byte _unused2;
+ private byte _unused3;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct Version
+ {
+ public byte VersionMajor;
+ public byte VersionMinor;
+ public ushort ProductBuild;
+ private byte _unused4;
+ private byte _unused5;
+ private byte _unused6;
+ public byte CurrentRevision;
+ }
+
+ // Type 1 message
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct NegotiateMessage
+ {
+ public MessageHeader Header;
+ public Flags Flags;
+ public MessageField DomainName;
+ public MessageField WorkStation;
+ public Version Version;
+ }
+
+ // TYPE 2 message
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct ChallengeMessage
+ {
+ public MessageHeader Header;
+ public MessageField TargetName;
+ public Flags Flags;
+ public fixed byte ServerChallenge[ChallengeLength];
+ private ulong _unused;
+ public MessageField TargetInfo;
+ public Version Version;
+ }
+
+ // TYPE 3 message
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct AuthenticateMessage
+ {
+ public MessageHeader Header;
+ public MessageField LmChallengeResponse;
+ public MessageField NtChallengeResponse;
+ public MessageField DomainName;
+ public MessageField UserName;
+ public MessageField Workstation;
+ public MessageField EncryptedRandomSessionKey;
+ public Flags Flags;
+ public Version Version;
+ public fixed byte Mic[16];
+ }
+
+ // Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4))
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct NtChallengeResponse
+ {
+ public fixed byte Hmac[DigestLength];
+ public byte Responserversion;
+ public byte HiResponserversion;
+ private byte _reserved1;
+ private byte _reserved2;
+ private int _reserved3;
+ public long Time;
+ public fixed byte ClientChallenge[ChallengeLength];
+ private int _reserved4;
+ public fixed byte ServerInfo[4]; // Has to be non-zero size, so set it to the Z(4) padding
+ }
+
+ public override bool IsAuthenticated => _isAuthenticated;
+ public override bool IsSigned => _protectionLevel != ProtectionLevel.None;
+ public override bool IsEncrypted => _protectionLevel == ProtectionLevel.EncryptAndSign;
+ public override bool IsMutuallyAuthenticated => false;
+ public override string Package => NegotiationInfoClass.NTLM;
+ public override string? TargetName => _spn;
+ public override IIdentity RemoteIdentity => throw new InvalidOperationException();
+ public override System.Security.Principal.TokenImpersonationLevel ImpersonationLevel => System.Security.Principal.TokenImpersonationLevel.Impersonation;
+
+ public ManagedNtlmNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions)
+ {
+ Debug.Assert(clientOptions.Package == NegotiationInfoClass.NTLM);
+
+ _credential = clientOptions.Credential;
+ if (string.IsNullOrWhiteSpace(_credential.UserName) || string.IsNullOrWhiteSpace(_credential.Password))
+ {
+ // NTLM authentication is not possible with default credentials which are no-op
+ throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred);
+ }
+
+ _spn = clientOptions.TargetName;
+ _channelBinding = clientOptions.Binding;
+ _protectionLevel = clientOptions.RequiredProtectionLevel;
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"package={clientOptions.Package}, spn={_spn}, requiredProtectionLevel={_protectionLevel}");
+ }
+
+ public override void Dispose()
+ {
+ // Dispose of the state
+ _negotiateMessage = null;
+ _clientSigningKey = null;
+ _serverSigningKey = null;
+ _clientSealingKey = null;
+ _serverSealingKey = null;
+ _clientSeal?.Dispose();
+ _serverSeal?.Dispose();
+ _clientSeal = null;
+ _serverSeal = null;
+ _clientSequenceNumber = 0;
+ _serverSequenceNumber = 0;
+ _isAuthenticated = false;
+ }
+
+ public override unsafe byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ byte[]? outgoingBlob;
+
+ // TODO: Logging, validation
+ if (_negotiateMessage == null)
+ {
+ Debug.Assert(incomingBlob.IsEmpty);
+
+ _negotiateMessage = new byte[sizeof(NegotiateMessage)];
+ CreateNtlmNegotiateMessage(_negotiateMessage);
+
+ outgoingBlob = _negotiateMessage;
+ statusCode = NegotiateAuthenticationStatusCode.ContinueNeeded;
+ }
+ else
+ {
+ Debug.Assert(!incomingBlob.IsEmpty);
+ _isAuthenticated = true;
+ outgoingBlob = ProcessChallenge(incomingBlob, out statusCode);
+ }
+
+ return outgoingBlob;
+ }
+
+ private static unsafe void CreateNtlmNegotiateMessage(Span asBytes)
+ {
+ Debug.Assert(HeaderLength == NtlmHeader.Length);
+ Debug.Assert(asBytes.Length == sizeof(NegotiateMessage));
+
+ ref NegotiateMessage message = ref MemoryMarshal.AsRef(asBytes);
+
+ asBytes.Clear();
+ NtlmHeader.CopyTo(asBytes);
+ message.Header.MessageType = MessageType.Negotiate;
+ message.Flags = s_requiredFlags;
+ message.Version = s_version;
+ }
+
+ private static unsafe int GetFieldLength(MessageField field)
+ {
+ ReadOnlySpan span = new ReadOnlySpan(&field, sizeof(MessageField));
+ return BinaryPrimitives.ReadInt16LittleEndian(span);
+ }
+
+ private static unsafe int GetFieldOffset(MessageField field)
+ {
+ ReadOnlySpan span = new ReadOnlySpan(&field, sizeof(MessageField));
+ return BinaryPrimitives.ReadInt16LittleEndian(span.Slice(4));
+ }
+
+ private static ReadOnlySpan GetField(MessageField field, ReadOnlySpan payload)
+ {
+ int offset = GetFieldOffset(field);
+ int length = GetFieldLength(field);
+
+ if (length == 0 || offset + length > payload.Length)
+ {
+ return ReadOnlySpan.Empty;
+ }
+
+ return payload.Slice(GetFieldOffset(field), GetFieldLength(field));
+ }
+
+ private static unsafe void SetField(ref MessageField field, int length, int offset)
+ {
+ if (length > short.MaxValue)
+ {
+ throw new Win32Exception(NTE_FAIL);
+ }
+
+ Span span = MemoryMarshal.AsBytes(new Span(ref field));
+ BinaryPrimitives.WriteInt16LittleEndian(span, (short)length);
+ BinaryPrimitives.WriteInt16LittleEndian(span.Slice(2), (short)length);
+ BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), offset);
+ }
+
+ private static void AddToPayload(ref MessageField field, ReadOnlySpan data, Span payload, ref int offset)
+ {
+ SetField(ref field, data.Length, offset);
+ data.CopyTo(payload.Slice(offset));
+ offset += data.Length;
+ }
+
+ private static void AddToPayload(ref MessageField field, ReadOnlySpan data, Span payload, ref int offset)
+ {
+ int dataLength = Encoding.Unicode.GetBytes(data, payload.Slice(offset));
+ SetField(ref field, dataLength, offset);
+ offset += dataLength;
+ }
+
+ // Section 3.3.2
+ // Define NTOWFv2(Passwd, User, UserDom) as HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(ConcatenationOf(Uppercase(User),
+ // UserDom ) ) )
+ // EndDefine
+ private static void makeNtlm2Hash(string domain, string userName, ReadOnlySpan password, Span hash)
+ {
+ // Maximum password length for Windows authentication is 128 characters, we enforce
+ // the limit early to prevent allocating large buffers on stack.
+ if (password.Length > 128)
+ {
+ throw new Win32Exception(NTE_FAIL);
+ }
+
+ Span pwHash = stackalloc byte[DigestLength];
+ Span pwBytes = stackalloc byte[Encoding.Unicode.GetByteCount(password)];
+
+ try
+ {
+ Encoding.Unicode.GetBytes(password, pwBytes);
+ MD4.HashData(pwBytes, pwHash);
+ // strangely, user is upper case, domain is not.
+ byte[] blob = Encoding.Unicode.GetBytes(string.Concat(userName.ToUpperInvariant(), domain));
+ int written = HMACMD5.HashData(pwHash, blob, hash);
+ Debug.Assert(written == HMACMD5.HashSizeInBytes);
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(pwBytes);
+ CryptographicOperations.ZeroMemory(pwHash);
+ }
+ }
+
+ // Section 3.3.2
+ //
+ // Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4))
+ // Set NTProofStr to HMAC_MD5(ResponseKeyNT, ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge, temp))
+ // Set NtChallengeResponse to ConcatenationOf(NTProofStr, temp)
+ private unsafe void makeNtlm2ChallengeResponse(DateTime time, ReadOnlySpan ntlm2hash, ReadOnlySpan serverChallenge, Span clientChallenge, ReadOnlySpan serverInfo, ref MessageField field, Span payload, ref int payloadOffset)
+ {
+ Debug.Assert(serverChallenge.Length == ChallengeLength);
+ Debug.Assert(clientChallenge.Length == ChallengeLength);
+ Debug.Assert(ntlm2hash.Length == DigestLength);
+
+ Span blob = payload.Slice(payloadOffset, sizeof(NtChallengeResponse) + serverInfo.Length);
+ ref NtChallengeResponse temp = ref MemoryMarshal.AsRef(blob.Slice(0, sizeof(NtChallengeResponse)));
+
+ temp.HiResponserversion = 1;
+ temp.Responserversion = 1;
+ temp.Time = time.ToFileTimeUtc();
+
+ clientChallenge.CopyTo(MemoryMarshal.CreateSpan(ref temp.ClientChallenge[0], ChallengeLength));
+ serverInfo.CopyTo(MemoryMarshal.CreateSpan(ref temp.ServerInfo[0], serverInfo.Length));
+
+ // Calculate NTProofStr
+ // we created temp part in place where it needs to be.
+ // now we need to prepend it with calculated hmac.
+ using (var hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, ntlm2hash))
+ {
+ hmac.AppendData(serverChallenge);
+ hmac.AppendData(blob.Slice(DigestLength));
+ hmac.GetHashAndReset(blob);
+ }
+
+ SetField(ref field, blob.Length, payloadOffset);
+
+ payloadOffset += blob.Length;
+ }
+
+ private unsafe void WriteChannelBindingHash(Span hashBuffer)
+ {
+ if (_channelBinding != null)
+ {
+ IntPtr cbtData = _channelBinding.DangerousGetHandle();
+ int cbtDataSize = _channelBinding.Size;
+ int written = MD5.HashData(new Span((void*)cbtData, cbtDataSize), hashBuffer);
+ Debug.Assert(written == MD5.HashSizeInBytes);
+ }
+ else
+ {
+ hashBuffer.Clear();
+ }
+ }
+
+ private byte[] ProcessTargetInfo(ReadOnlySpan targetInfo, out DateTime time, out bool hasNbNames)
+ {
+ int spnSize = _spn != null ? Encoding.Unicode.GetByteCount(_spn) : 0;
+
+ if (spnSize > short.MaxValue)
+ {
+ throw new Win32Exception(NTE_FAIL);
+ }
+
+ bool hasNbComputerName = false, hasNbDomainName = false;
+ byte[] targetInfoBuffer = new byte[targetInfo.Length + 20 /* channel binding */ + 4 + spnSize /* SPN */ + 8 /* flags */];
+ int targetInfoOffset = 0;
+
+ time = DateTime.UtcNow;
+
+ if (targetInfo.Length > 0)
+ {
+ ReadOnlySpan info = targetInfo;
+ while (info.Length >= 4)
+ {
+ AvId ID = (AvId)info[0];
+ byte l1 = info[2];
+ byte l2 = info[3];
+ int length = (l2 << 8) + l1;
+
+ if (ID == AvId.EOL)
+ {
+ break;
+ }
+
+ if (ID == AvId.Timestamp)
+ {
+ time = DateTime.FromFileTimeUtc(BitConverter.ToInt64(info.Slice(4, 8)));
+ }
+ else if (ID == AvId.TargetName || ID == AvId.ChannelBindings)
+ {
+ // Skip these, we insert them ourselves
+ info = info.Slice(length + 4);
+ continue;
+ }
+ else if (ID == AvId.NbComputerName)
+ {
+ hasNbComputerName = true;
+ }
+ else if (ID == AvId.NbDomainName)
+ {
+ hasNbDomainName = true;
+ }
+
+ // Copy attribute-value pair into destination target info
+ info.Slice(0, length + 4).CopyTo(targetInfoBuffer.AsSpan(targetInfoOffset, length + 4));
+ targetInfoOffset += length + 4;
+
+ info = info.Slice(length + 4);
+ }
+ }
+
+ hasNbNames = hasNbComputerName && hasNbDomainName;
+
+ // Add new entries into destination target info
+
+ // Target name (eg. HTTP/example.org)
+ targetInfoBuffer[targetInfoOffset] = (byte)AvId.TargetName;
+ BinaryPrimitives.WriteUInt16LittleEndian(targetInfoBuffer.AsSpan(2 + targetInfoOffset), (ushort)spnSize);
+ if (_spn != null)
+ {
+ int bytesWritten = Encoding.Unicode.GetBytes(_spn, targetInfoBuffer.AsSpan(4 + targetInfoOffset));
+ Debug.Assert(bytesWritten == spnSize);
+ }
+ targetInfoOffset += spnSize + 4;
+
+ // Channel binding
+ targetInfoBuffer[targetInfoOffset] = (byte)AvId.ChannelBindings;
+ targetInfoBuffer[targetInfoOffset + 2] = 16;
+ WriteChannelBindingHash(targetInfoBuffer.AsSpan(targetInfoOffset + 4, 16));
+ targetInfoOffset += 16 + 4;
+
+ // Flags
+ targetInfoBuffer[targetInfoOffset] = (byte)AvId.Flags;
+ targetInfoBuffer[targetInfoOffset + 2] = 4; // Length of flags
+ targetInfoBuffer[targetInfoOffset + 4] = 2; // MIC flag
+ targetInfoOffset += 8;
+
+ // EOL
+ targetInfoOffset += 4;
+
+ if (targetInfoOffset == targetInfoBuffer.Length)
+ {
+ return targetInfoBuffer;
+ }
+
+ return targetInfoBuffer.AsSpan(targetInfoOffset).ToArray();
+ }
+
+ // Section 3.4.5.2 SIGNKEY, 3.4.5.3 SEALKEY
+ private static byte[] DeriveKey(ReadOnlySpan exportedSessionKey, ReadOnlySpan magic)
+ {
+ using (var md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5))
+ {
+ md5.AppendData(exportedSessionKey);
+ md5.AppendData(magic);
+ return md5.GetHashAndReset();
+ }
+ }
+
+ // This gets decoded byte blob and returns response in binary form.
+ private unsafe byte[]? ProcessChallenge(ReadOnlySpan blob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ // TODO: Validate size and offsets
+
+ ref readonly ChallengeMessage challengeMessage = ref MemoryMarshal.AsRef(blob.Slice(0, sizeof(ChallengeMessage)));
+
+ // Verify message type and signature
+ if (challengeMessage.Header.MessageType != MessageType.Challenge ||
+ !NtlmHeader.SequenceEqual(blob.Slice(0, NtlmHeader.Length)))
+ {
+ statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
+ return null;
+ }
+
+ Flags flags = BitConverter.IsLittleEndian ? challengeMessage.Flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)challengeMessage.Flags);
+ ReadOnlySpan targetName = GetField(challengeMessage.TargetName, blob);
+
+ // Only NTLMv2 with MIC is supported
+ //
+ // NegotiateSign and NegotiateKeyExchange are necessary to calculate the key
+ // that is used for MIC.
+ if ((flags & s_requiredFlags) != s_requiredFlags)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
+ return null;
+ }
+
+ ReadOnlySpan targetInfo = GetField(challengeMessage.TargetInfo, blob);
+ byte[] targetInfoBuffer = ProcessTargetInfo(targetInfo, out DateTime time, out bool hasNbNames);
+
+ // If NTLM v2 authentication is used and the CHALLENGE_MESSAGE does not contain both
+ // MsvAvNbComputerName and MsvAvNbDomainName AVPairs and either Integrity is TRUE or
+ // Confidentiality is TRUE, then return STATUS_LOGON_FAILURE ([MS-ERREF] section 2.3.1).
+ if (!hasNbNames && (flags & (Flags.NegotiateSign | Flags.NegotiateSeal)) != 0)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
+ return null;
+ }
+
+ int responseLength =
+ sizeof(AuthenticateMessage) +
+ ChallengeResponseLength +
+ sizeof(NtChallengeResponse) +
+ targetInfoBuffer.Length +
+ Encoding.Unicode.GetByteCount(_credential.UserName) +
+ Encoding.Unicode.GetByteCount(_credential.Domain) +
+ s_workstation.Length +
+ SessionKeyLength;
+
+ byte[] responseBytes = new byte[responseLength];
+ Span responseAsSpan = new Span(responseBytes);
+ ref AuthenticateMessage response = ref MemoryMarshal.AsRef(responseAsSpan.Slice(0, sizeof(AuthenticateMessage)));
+
+ // variable fields
+ Span payload = responseAsSpan;
+ int payloadOffset = sizeof(AuthenticateMessage);
+
+ responseAsSpan.Clear();
+ NtlmHeader.CopyTo(responseAsSpan);
+
+ response.Header.MessageType = MessageType.Authenticate;
+ response.Flags = s_requiredFlags;
+ response.Version = s_version;
+
+ // Calculate hash for hmac - same for lm2 and ntlm2
+ Span ntlm2hash = stackalloc byte[DigestLength];
+ makeNtlm2Hash(_credential.Domain, _credential.UserName, _credential.Password, ntlm2hash);
+
+ // Get random bytes for client challenge
+ Span clientChallenge = stackalloc byte[ChallengeLength];
+ RandomNumberGenerator.Fill(clientChallenge);
+
+ // Create empty LM2 response.
+ SetField(ref response.LmChallengeResponse, ChallengeResponseLength, payloadOffset);
+ payload.Slice(payloadOffset, ChallengeResponseLength).Clear();
+ payloadOffset += ChallengeResponseLength;
+
+ // Create NTLM2 response
+ ReadOnlySpan serverChallenge = blob.Slice(24, 8);
+ makeNtlm2ChallengeResponse(time, ntlm2hash, serverChallenge, clientChallenge, targetInfoBuffer, ref response.NtChallengeResponse, payload, ref payloadOffset);
+ Debug.Assert(payloadOffset == sizeof(AuthenticateMessage) + ChallengeResponseLength + sizeof(NtChallengeResponse) + targetInfoBuffer.Length);
+
+ AddToPayload(ref response.UserName, _credential.UserName, payload, ref payloadOffset);
+ AddToPayload(ref response.DomainName, _credential.Domain, payload, ref payloadOffset);
+ AddToPayload(ref response.Workstation, s_workstation, payload, ref payloadOffset);
+
+ // Generate random session key that will be used for signing the messages
+ Span exportedSessionKey = stackalloc byte[16];
+ RandomNumberGenerator.Fill(exportedSessionKey);
+
+ // Both flags are necessary to exchange keys needed for MIC (!)
+ Debug.Assert(flags.HasFlag(Flags.NegotiateSign) && flags.HasFlag(Flags.NegotiateKeyExchange));
+
+ // Derive session base key
+ Span sessionBaseKey = stackalloc byte[HMACMD5.HashSizeInBytes];
+ int sessionKeyWritten = HMACMD5.HashData(ntlm2hash, responseAsSpan.Slice(response.NtChallengeResponse.PayloadOffset, 16), sessionBaseKey);
+ Debug.Assert(sessionKeyWritten == HMACMD5.HashSizeInBytes);
+
+ // Encrypt exportedSessionKey with sessionBaseKey
+ using (RC4 rc4 = new RC4(sessionBaseKey))
+ {
+ Span encryptedRandomSessionKey = payload.Slice(payloadOffset, 16);
+ rc4.Transform(exportedSessionKey, encryptedRandomSessionKey);
+ SetField(ref response.EncryptedRandomSessionKey, 16, payloadOffset);
+ payloadOffset += 16;
+ }
+
+ // Calculate MIC
+ Debug.Assert(_negotiateMessage != null);
+ using (var hmacMic = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, exportedSessionKey))
+ {
+ hmacMic.AppendData(_negotiateMessage);
+ hmacMic.AppendData(blob);
+ hmacMic.AppendData(responseBytes.AsSpan(0, payloadOffset));
+ hmacMic.GetHashAndReset(MemoryMarshal.CreateSpan(ref response.Mic[0], hmacMic.HashLengthInBytes));
+ }
+
+ // Derive signing keys
+ _clientSigningKey = DeriveKey(exportedSessionKey, ClientSigningKeyMagic);
+ _serverSigningKey = DeriveKey(exportedSessionKey, ServerSigningKeyMagic);
+ _clientSealingKey = DeriveKey(exportedSessionKey, ClientSealingKeyMagic);
+ _serverSealingKey = DeriveKey(exportedSessionKey, ServerSealingKeyMagic);
+ ResetKeys();
+ _clientSequenceNumber = 0;
+ _serverSequenceNumber = 0;
+ CryptographicOperations.ZeroMemory(exportedSessionKey);
+
+ Debug.Assert(payloadOffset == responseBytes.Length);
+
+ statusCode = NegotiateAuthenticationStatusCode.Completed;
+ return responseBytes;
+ }
+
+ internal void ResetKeys()
+ {
+ // Release buffers to pool
+ _clientSeal?.Dispose();
+ _serverSeal?.Dispose();
+
+ _clientSeal = new RC4(_clientSealingKey);
+ _serverSeal = new RC4(_serverSealingKey);
+ }
+
+ private void CalculateSignature(
+ ReadOnlySpan message,
+ uint sequenceNumber,
+ ReadOnlySpan signingKey,
+ RC4 seal,
+ Span signature)
+ {
+ BinaryPrimitives.WriteInt32LittleEndian(signature, 1);
+ BinaryPrimitives.WriteUInt32LittleEndian(signature.Slice(12), sequenceNumber);
+ using (var hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, signingKey))
+ {
+ hmac.AppendData(signature.Slice(12, 4));
+ hmac.AppendData(message);
+ Span hmacResult = stackalloc byte[hmac.HashLengthInBytes];
+ hmac.GetHashAndReset(hmacResult);
+ seal.Transform(hmacResult.Slice(0, 8), signature.Slice(4, 8));
+ }
+ }
+
+ public override bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature)
+ {
+ // Check length and version
+ if (signature.Length != SignatureLength ||
+ BinaryPrimitives.ReadInt32LittleEndian(signature) != 1 ||
+ _serverSeal == null ||
+ _serverSigningKey == null)
+ {
+ return false;
+ }
+
+ Span expectedSignature = stackalloc byte[SignatureLength];
+ CalculateSignature(message, _serverSequenceNumber, _serverSigningKey, _serverSeal, expectedSignature);
+
+ _serverSequenceNumber++;
+
+ return signature.SequenceEqual(expectedSignature);
+ }
+
+ public override void GetMIC(ReadOnlySpan message, IBufferWriter signature)
+ {
+ Debug.Assert(_clientSeal is not null);
+ Debug.Assert(_clientSigningKey is not null);
+
+ Span signatureBuffer = signature.GetSpan(SignatureLength);
+ CalculateSignature(message, _clientSequenceNumber, _clientSigningKey, _clientSeal, signatureBuffer);
+ _clientSequenceNumber++;
+ signature.Advance(SignatureLength);
+ }
+
+ public override NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool _/*requestEncryption*/, out bool isEncrypted)
+ {
+ if (_clientSeal == null)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ Span output = outputWriter.GetSpan(input.Length + SignatureLength);
+ _clientSeal.Transform(input, output.Slice(SignatureLength, input.Length));
+ CalculateSignature(input, _clientSequenceNumber, _clientSigningKey, _clientSeal, output.Slice(0, SignatureLength));
+ _clientSequenceNumber++;
+
+ isEncrypted = true;
+ outputWriter.Advance(input.Length + SignatureLength);
+
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+
+ public override NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
+ {
+ wasEncrypted = true;
+
+ if (_serverSeal == null)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ if (input.Length < SignatureLength)
+ {
+ return NegotiateAuthenticationStatusCode.InvalidToken;
+ }
+
+ Span output = outputWriter.GetSpan(input.Length - SignatureLength);
+ _serverSeal.Transform(input.Slice(SignatureLength), output.Slice(0, input.Length - SignatureLength));
+ if (!VerifyMIC(output.Slice(0, input.Length - SignatureLength), input.Slice(0, SignatureLength)))
+ {
+ CryptographicOperations.ZeroMemory(output);
+ return NegotiateAuthenticationStatusCode.MessageAltered;
+ }
+
+ outputWriter.Advance(input.Length - SignatureLength);
+
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+
+ public override NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
+ {
+ wasEncrypted = true;
+ unwrappedOffset = SignatureLength;
+ unwrappedLength = input.Length - SignatureLength;
+
+ if (_serverSeal == null)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ if (input.Length < SignatureLength)
+ {
+ return NegotiateAuthenticationStatusCode.InvalidToken;
+ }
+
+ _serverSeal.Transform(input.Slice(SignatureLength), input.Slice(SignatureLength));
+ if (!VerifyMIC(input.Slice(SignatureLength), input.Slice(0, SignatureLength)))
+ {
+ CryptographicOperations.ZeroMemory(input.Slice(SignatureLength));
+ return NegotiateAuthenticationStatusCode.MessageAltered;
+ }
+
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedSpnego.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedSpnego.cs
new file mode 100644
index 0000000000000..dbf9fb8aee799
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedSpnego.cs
@@ -0,0 +1,451 @@
+// 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.Buffers.Binary;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Formats.Asn1;
+using System.Net.Security;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Security.Authentication.ExtendedProtection;
+using System.Security.Cryptography;
+using System.Security.Principal;
+using System.Text;
+
+namespace System.Net
+{
+ internal abstract partial class NegotiateAuthenticationPal
+ {
+ internal sealed class ManagedSpnegoNegotiateAuthenticationPal : NegotiateAuthenticationPal
+ {
+ // Input parameters
+ private readonly NegotiateAuthenticationClientOptions _clientOptions;
+
+ // State parameters
+ private byte[]? _spnegoMechList;
+ private bool _isAuthenticated;
+ private bool _supportKerberos;
+ private NegotiateAuthenticationPal? _optimisticMechanism;
+ private NegotiateAuthenticationPal? _mechanism;
+
+ private const string SpnegoOid = "1.3.6.1.5.5.2";
+ private const string NtlmOid = "1.3.6.1.4.1.311.2.2.10";
+ private const string KerberosOid = "1.2.840.113554.1.2.2";
+
+ // rfc4178
+ private enum NegotiationToken
+ {
+ NegTokenInit = 0,
+ NegTokenResp = 1
+ }
+
+ private enum NegTokenInit
+ {
+ MechTypes = 0,
+ ReqFlags = 1,
+ MechToken = 2,
+ MechListMIC = 3
+ }
+
+ private enum NegTokenResp
+ {
+ NegState = 0,
+ SupportedMech = 1,
+ ResponseToken = 2,
+ MechListMIC = 3
+ }
+
+ private enum NegState
+ {
+ Unknown = -1, // Internal. Not in RFC.
+ AcceptCompleted = 0,
+ AcceptIncomplete = 1,
+ Reject = 2,
+ RequestMic = 3
+ }
+
+ public override bool IsAuthenticated => _isAuthenticated && _mechanism?.IsAuthenticated == true;
+ public override bool IsSigned => _mechanism?.IsSigned ?? false;
+ public override bool IsEncrypted => _mechanism?.IsEncrypted ?? false;
+ public override bool IsMutuallyAuthenticated => _mechanism?.IsMutuallyAuthenticated ?? false;
+ public override string Package => _mechanism?.Package ?? NegotiationInfoClass.Negotiate;
+ public override string? TargetName => _clientOptions.TargetName;
+ public override IIdentity RemoteIdentity => _mechanism?.RemoteIdentity ?? throw new InvalidOperationException();
+ public override System.Security.Principal.TokenImpersonationLevel ImpersonationLevel => _mechanism?.ImpersonationLevel ?? System.Security.Principal.TokenImpersonationLevel.Impersonation;
+
+ public ManagedSpnegoNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions, bool supportKerberos = false)
+ {
+ Debug.Assert(clientOptions.Package == NegotiationInfoClass.Negotiate);
+ _clientOptions = clientOptions;
+ _supportKerberos = supportKerberos;
+ }
+
+ public override void Dispose()
+ {
+ _optimisticMechanism?.Dispose();
+ _optimisticMechanism = null;
+ _mechanism?.Dispose();
+ _mechanism = null;
+ _isAuthenticated = false;
+ }
+
+ public override unsafe byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ //Console.WriteLine($"ManagedSpnegoNegotiateAuthenticationPal.GetOutgoingBlob > {Convert.ToBase64String(incomingBlob)}");
+
+ byte[]? outgoingBlob;
+ if (_spnegoMechList == null)
+ {
+ outgoingBlob = CreateSpNegoNegotiateMessage(incomingBlob, out statusCode);
+ }
+ else
+ {
+ outgoingBlob = ProcessSpNegoChallenge(incomingBlob, out statusCode);
+ }
+
+ //Console.WriteLine($"ManagedSpnegoNegotiateAuthenticationPal.GetOutgoingBlob < {(outgoingBlob == null ? "null" : Convert.ToBase64String(outgoingBlob))} {statusCode}");
+
+ return outgoingBlob;
+ }
+
+ private NegotiateAuthenticationPal CreateMechanismForPackage(string packageName)
+ {
+ return NegotiateAuthenticationPal.Create(new NegotiateAuthenticationClientOptions
+ {
+ Package = packageName,
+ Credential = _clientOptions.Credential,
+ TargetName = _clientOptions.TargetName,
+ Binding = _clientOptions.Binding,
+ RequiredProtectionLevel = _clientOptions.RequiredProtectionLevel,
+ RequireMutualAuthentication = _clientOptions.RequireMutualAuthentication,
+ AllowedImpersonationLevel = _clientOptions.AllowedImpersonationLevel,
+ });
+ }
+
+ private IEnumerable> EnumerateMechanisms()
+ {
+ if (_supportKerberos)
+ {
+ yield return new KeyValuePair(NegotiationInfoClass.Kerberos, KerberosOid);
+ }
+
+ yield return new KeyValuePair(NegotiationInfoClass.NTLM, NtlmOid);
+ }
+
+ private byte[]? CreateSpNegoNegotiateMessage(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ using (writer.PushSequence(new Asn1Tag(TagClass.Application, 0)))
+ {
+ writer.WriteObjectIdentifier(SpnegoOid);
+
+ // NegTokenInit::= SEQUENCE {
+ // mechTypes[0] MechTypeList,
+ // reqFlags[1] ContextFlags OPTIONAL,
+ // --inherited from RFC 2478 for backward compatibility,
+ // --RECOMMENDED to be left out
+ // mechToken[2] OCTET STRING OPTIONAL,
+ // mechListMIC[3] OCTET STRING OPTIONAL,
+ // ...
+ // }
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenInit)))
+ {
+ using (writer.PushSequence())
+ {
+ byte[]? mechBlob = null;
+
+ // MechType::= OBJECT IDENTIFIER
+ // -- OID represents each security mechanism as suggested by
+ // --[RFC2743]
+ //
+ // MechTypeList::= SEQUENCE OF MechType
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenInit.MechTypes)))
+ {
+ AsnWriter mechListWriter = new AsnWriter(AsnEncodingRules.DER);
+
+ using (mechListWriter.PushSequence())
+ {
+ foreach (KeyValuePair packageAndOid in EnumerateMechanisms())
+ {
+ if (_optimisticMechanism == null)
+ {
+ _optimisticMechanism = CreateMechanismForPackage(packageAndOid.Key);
+ mechBlob = _optimisticMechanism.GetOutgoingBlob(incomingBlob, out statusCode);
+ if (statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded &&
+ statusCode != NegotiateAuthenticationStatusCode.Completed)
+ {
+ mechBlob = null;
+ _optimisticMechanism?.Dispose();
+ _optimisticMechanism = null;
+ if (statusCode != NegotiateAuthenticationStatusCode.Unsupported)
+ {
+ return null;
+ }
+ continue;
+ }
+ }
+
+ mechListWriter.WriteObjectIdentifier(packageAndOid.Value);
+ }
+ }
+
+ _spnegoMechList = mechListWriter.Encode();
+ mechListWriter.CopyTo(writer);
+ }
+
+ if (mechBlob != null)
+ {
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenInit.MechToken)))
+ {
+ writer.WriteOctetString(mechBlob);
+ }
+ }
+ }
+ }
+ }
+
+ statusCode = NegotiateAuthenticationStatusCode.ContinueNeeded;
+ return writer.Encode();
+ }
+
+ private byte[]? ProcessSpNegoChallenge(ReadOnlySpan challenge, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ NegState state = NegState.Unknown;
+ string? mech = null;
+ byte[]? blob = null;
+ byte[]? mechListMIC = null;
+
+ try
+ {
+ AsnValueReader reader = new AsnValueReader(challenge, AsnEncodingRules.DER);
+ AsnValueReader challengeReader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp));
+ reader.ThrowIfNotEmpty();
+
+ // NegTokenResp ::= SEQUENCE {
+ // negState[0] ENUMERATED {
+ // accept - completed(0),
+ // accept - incomplete(1),
+ // reject(2),
+ // request - mic(3)
+ // } OPTIONAL,
+ // --REQUIRED in the first reply from the target
+ // supportedMech[1] MechType OPTIONAL,
+ // --present only in the first reply from the target
+ // responseToken[2] OCTET STRING OPTIONAL,
+ // mechListMIC[3] OCTET STRING OPTIONAL,
+ // ...
+ // }
+
+ challengeReader = challengeReader.ReadSequence();
+
+ if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState)))
+ {
+ AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState));
+ state = valueReader.ReadEnumeratedValue();
+ valueReader.ThrowIfNotEmpty();
+ }
+
+ if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech)))
+ {
+ AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech));
+ mech = valueReader.ReadObjectIdentifier();
+ valueReader.ThrowIfNotEmpty();
+ }
+
+ if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)))
+ {
+ AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken));
+ blob = valueReader.ReadOctetString();
+ valueReader.ThrowIfNotEmpty();
+ }
+
+ if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)))
+ {
+ AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC));
+ mechListMIC = valueReader.ReadOctetString();
+ valueReader.ThrowIfNotEmpty();
+ }
+
+ challengeReader.ThrowIfNotEmpty();
+ }
+ catch (AsnContentException)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
+ return null;
+ }
+
+ // Validate and choose the mechanism if necessary
+ string? requestedPackage = mech switch
+ {
+ NtlmOid => NegotiationInfoClass.NTLM,
+ KerberosOid => NegotiationInfoClass.Kerberos,
+ _ => null
+ };
+
+ if (_mechanism is null)
+ {
+ if (requestedPackage is null)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.Unsupported;
+ return null;
+ }
+
+ if (requestedPackage == _optimisticMechanism?.Package)
+ {
+ _mechanism = _optimisticMechanism;
+ }
+ else
+ {
+ // Abandon the optimistic path and restart with a new mechanism
+ _optimisticMechanism?.Dispose();
+ _mechanism = NegotiateAuthenticationPal.Create(new NegotiateAuthenticationClientOptions
+ {
+ Package = requestedPackage,
+ Credential = _clientOptions.Credential,
+ TargetName = _clientOptions.TargetName,
+ Binding = _clientOptions.Binding,
+ RequiredProtectionLevel = _clientOptions.RequiredProtectionLevel,
+ RequireMutualAuthentication = _clientOptions.RequireMutualAuthentication,
+ AllowedImpersonationLevel = _clientOptions.AllowedImpersonationLevel,
+ });
+ }
+
+ _optimisticMechanism = null;
+ }
+ else
+ {
+ if (requestedPackage != null &&
+ _mechanism.Package != requestedPackage)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
+ return null;
+ }
+ }
+
+ if (blob?.Length > 0)
+ {
+ // Process decoded blob.
+ byte[]? response = _mechanism.GetOutgoingBlob(blob, out statusCode);
+
+ if (statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded &&
+ statusCode != NegotiateAuthenticationStatusCode.Completed)
+ {
+ return null;
+ }
+
+ if (response?.Length > 0)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp)))
+ {
+ using (writer.PushSequence())
+ {
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)))
+ {
+ writer.WriteOctetString(response);
+ }
+
+ if (statusCode == NegotiateAuthenticationStatusCode.Completed)
+ {
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)))
+ {
+ ArrayBufferWriter micBuffer = new ArrayBufferWriter();
+ _mechanism.GetMIC(_spnegoMechList, micBuffer);
+ writer.WriteOctetString(micBuffer.WrittenSpan);
+ }
+ }
+ }
+ }
+
+ statusCode = state == NegState.RequestMic ? NegotiateAuthenticationStatusCode.ContinueNeeded : NegotiateAuthenticationStatusCode.Completed;
+ _isAuthenticated = statusCode == NegotiateAuthenticationStatusCode.Completed;
+ return writer.Encode();
+ }
+ }
+
+ if (mechListMIC != null)
+ {
+ if (_spnegoMechList == null || state != NegState.AcceptCompleted)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.GenericFailure;
+ return null;
+ }
+
+ if (!_mechanism.VerifyMIC(_spnegoMechList, mechListMIC))
+ {
+ statusCode = NegotiateAuthenticationStatusCode.MessageAltered;
+ return null;
+ }
+
+ (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys();
+ }
+
+ _isAuthenticated = state == NegState.AcceptCompleted || state == NegState.Reject;
+ statusCode = state switch {
+ NegState.AcceptCompleted => NegotiateAuthenticationStatusCode.Completed,
+ NegState.AcceptIncomplete => NegotiateAuthenticationStatusCode.ContinueNeeded,
+ NegState.Reject => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ _ => NegotiateAuthenticationStatusCode.GenericFailure
+ };
+
+ return null;
+ }
+
+ public override NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted)
+ {
+ if (_mechanism is null || !_isAuthenticated)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ return _mechanism.Wrap(input, outputWriter, requestEncryption, out isEncrypted);
+ }
+
+ public override NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
+ {
+ if (_mechanism is null || !_isAuthenticated)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ return _mechanism.Unwrap(input, outputWriter, out wasEncrypted);
+ }
+
+ public override NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
+ {
+ if (_mechanism is null || !_isAuthenticated)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ return _mechanism.UnwrapInPlace(input, out unwrappedOffset, out unwrappedLength, out wasEncrypted);
+ }
+
+ public override bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature)
+ {
+ if (_mechanism is null || !_isAuthenticated)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ return _mechanism.VerifyMIC(message, signature);
+ }
+
+ public override void GetMIC(ReadOnlySpan message, IBufferWriter signature)
+ {
+ if (_mechanism is null || !_isAuthenticated)
+ {
+ throw new InvalidOperationException(SR.net_auth_noauth);
+ }
+
+ _mechanism.GetMIC(message, signature);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs
new file mode 100644
index 0000000000000..900d66c05bfc7
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs
@@ -0,0 +1,779 @@
+// 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.Buffers.Binary;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Principal;
+using System.Security.Authentication.ExtendedProtection;
+using System.Text;
+using System.Net.Security;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Net
+{
+ internal partial class NegotiateAuthenticationPal
+ {
+ private static bool UseManagedNtlm { get; } = AppContext.TryGetSwitch("System.Net.Security.UseManagedNtlm", out bool useManagedNtlm) && useManagedNtlm;
+
+ public static NegotiateAuthenticationPal Create(NegotiateAuthenticationClientOptions clientOptions)
+ {
+ if (UseManagedNtlm)
+ {
+ switch (clientOptions.Package)
+ {
+ case NegotiationInfoClass.NTLM:
+ return new ManagedNtlmNegotiateAuthenticationPal(clientOptions);
+
+ case NegotiationInfoClass.Negotiate:
+ return new ManagedSpnegoNegotiateAuthenticationPal(clientOptions, supportKerberos: true);
+ }
+ }
+
+ try
+ {
+ return new UnixNegotiateAuthenticationPal(clientOptions);
+ }
+ catch (Win32Exception)
+ {
+ return new UnsupportedNegotiateAuthenticationPal(clientOptions);
+ }
+ catch (PlatformNotSupportedException)
+ {
+ return new UnsupportedNegotiateAuthenticationPal(clientOptions);
+ }
+ catch (EntryPointNotFoundException)
+ {
+ // GSSAPI shim may not be available on some platforms (Linux Bionic)
+ return new UnsupportedNegotiateAuthenticationPal(clientOptions);
+ }
+ }
+
+ public static NegotiateAuthenticationPal Create(NegotiateAuthenticationServerOptions serverOptions)
+ {
+ try
+ {
+ return new UnixNegotiateAuthenticationPal(serverOptions);
+ }
+ catch (Win32Exception)
+ {
+ return new UnsupportedNegotiateAuthenticationPal(serverOptions);
+ }
+ catch (PlatformNotSupportedException)
+ {
+ return new UnsupportedNegotiateAuthenticationPal(serverOptions);
+ }
+ catch (EntryPointNotFoundException)
+ {
+ // GSSAPI shim may not be available on some platforms (Linux Bionic)
+ return new UnsupportedNegotiateAuthenticationPal(serverOptions);
+ }
+ }
+
+ internal sealed class UnixNegotiateAuthenticationPal : NegotiateAuthenticationPal
+ {
+ private bool _isServer;
+ private bool _isAuthenticated;
+ private byte[]? _tokenBuffer;
+ private SafeGssCredHandle _credentialsHandle;
+ private SafeGssContextHandle? _securityContext;
+ private SafeGssNameHandle? _targetNameHandle;
+ private Interop.NetSecurityNative.GssFlags _requestedContextFlags;
+ private Interop.NetSecurityNative.GssFlags _contextFlags;
+ private string _package;
+ private string? _spn;
+ private ChannelBinding? _channelBinding;
+ private readonly Interop.NetSecurityNative.PackageType _packageType;
+
+ public override bool IsAuthenticated => _isAuthenticated;
+
+ public override bool IsSigned => (_contextFlags & Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG) != 0;
+
+ public override bool IsEncrypted => (_contextFlags & Interop.NetSecurityNative.GssFlags.GSS_C_CONF_FLAG) != 0;
+
+ public override bool IsMutuallyAuthenticated => (_contextFlags & Interop.NetSecurityNative.GssFlags.GSS_C_MUTUAL_FLAG) != 0;
+
+ public override string Package => _package;
+
+ public override string? TargetName
+ {
+ get
+ {
+ if (_isServer && _spn == null)
+ {
+ Debug.Assert(_securityContext is not null && _isAuthenticated, "Trying to get the client SPN before handshaking is done!");
+ throw new PlatformNotSupportedException(SR.net_nego_server_not_supported);
+ }
+ return _spn;
+ }
+ }
+
+ public override IIdentity RemoteIdentity
+ {
+ get
+ {
+ IIdentity? result;
+ string? name = _isServer ? null : TargetName;
+ string protocol = Package;
+
+ Debug.Assert(_securityContext is not null);
+
+ if (_isServer)
+ {
+ try
+ {
+ name = GssGetUser(_securityContext);
+ }
+ catch (Exception ex)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
+ throw;
+ }
+ }
+
+ // On the client we don't have access to the remote side identity.
+ result = new GenericIdentity(name ?? string.Empty, protocol);
+ return result;
+ }
+ }
+
+ public override System.Security.Principal.TokenImpersonationLevel ImpersonationLevel
+ {
+ get
+ {
+ return
+ (_contextFlags & Interop.NetSecurityNative.GssFlags.GSS_C_DELEG_FLAG) != 0 && Package != NegotiationInfoClass.NTLM ? TokenImpersonationLevel.Delegation :
+ (_contextFlags & Interop.NetSecurityNative.GssFlags.GSS_C_IDENTIFY_FLAG) != 0 ? TokenImpersonationLevel.Identification :
+ TokenImpersonationLevel.Impersonation;
+ }
+ }
+
+ public UnixNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions)
+ {
+ Interop.NetSecurityNative.GssFlags contextFlags = clientOptions.RequiredProtectionLevel switch
+ {
+ ProtectionLevel.Sign => Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG,
+ ProtectionLevel.EncryptAndSign => Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG | Interop.NetSecurityNative.GssFlags.GSS_C_CONF_FLAG,
+ _ => 0
+ };
+
+ contextFlags |= clientOptions.RequireMutualAuthentication ? Interop.NetSecurityNative.GssFlags.GSS_C_MUTUAL_FLAG : 0;
+
+ contextFlags |= clientOptions.AllowedImpersonationLevel switch
+ {
+ TokenImpersonationLevel.Identification => Interop.NetSecurityNative.GssFlags.GSS_C_IDENTIFY_FLAG,
+ TokenImpersonationLevel.Delegation => Interop.NetSecurityNative.GssFlags.GSS_C_DELEG_FLAG,
+ _ => 0
+ };
+
+ _isServer = false;
+ _spn = clientOptions.TargetName;
+ _securityContext = null;
+ _requestedContextFlags = contextFlags;
+ _package = clientOptions.Package;
+ _channelBinding = clientOptions.Binding;
+ _packageType = GetPackageType(_package);
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
+
+ if (clientOptions.Credential == CredentialCache.DefaultCredentials ||
+ string.IsNullOrWhiteSpace(clientOptions.Credential.UserName) ||
+ string.IsNullOrWhiteSpace(clientOptions.Credential.Password))
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "using DefaultCredentials");
+ _credentialsHandle = AcquireDefaultCredential();
+
+ if (_packageType == Interop.NetSecurityNative.PackageType.NTLM)
+ {
+ // NTLM authentication is not possible with default credentials which are no-op
+ throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred);
+ }
+ if (string.IsNullOrEmpty(_spn))
+ {
+ throw new PlatformNotSupportedException(SR.net_nego_not_supported_empty_target_with_defaultcreds);
+ }
+ }
+ else
+ {
+ _credentialsHandle = AcquireCredentialsHandle(clientOptions.Credential);
+ }
+ }
+
+ public UnixNegotiateAuthenticationPal(NegotiateAuthenticationServerOptions serverOptions)
+ {
+ Interop.NetSecurityNative.GssFlags contextFlags = serverOptions.RequiredProtectionLevel switch
+ {
+ ProtectionLevel.Sign => Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG,
+ ProtectionLevel.EncryptAndSign => Interop.NetSecurityNative.GssFlags.GSS_C_INTEG_FLAG | Interop.NetSecurityNative.GssFlags.GSS_C_CONF_FLAG,
+ _ => 0
+ };
+
+ // NOTE: Historically serverOptions.Policy was ignored on Unix without an exception
+ // or error message. We continue to do so for compatibility reasons and because there
+ // are no direct equivalents in GSSAPI.
+
+ _isServer = true;
+ _securityContext = null;
+ _requestedContextFlags = contextFlags;
+ _package = serverOptions.Package;
+ _channelBinding = serverOptions.Binding;
+ _packageType = GetPackageType(_package);
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
+
+ if (serverOptions.Credential == CredentialCache.DefaultCredentials ||
+ string.IsNullOrWhiteSpace(serverOptions.Credential.UserName) ||
+ string.IsNullOrWhiteSpace(serverOptions.Credential.Password))
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "using DefaultCredentials");
+ _credentialsHandle = SafeGssCredHandle.CreateAcceptor();
+ }
+ else
+ {
+ // NOTE: The input parameter was previously ignored and SafeGssCredHandle.CreateAcceptor
+ // was always used. We don't know of any uses with non-default credentials so this code
+ // path is essentially untested.
+ _credentialsHandle = AcquireCredentialsHandle(serverOptions.Credential);
+ }
+ }
+
+ public override void Dispose()
+ {
+ _credentialsHandle?.Dispose();
+ _targetNameHandle?.Dispose();
+ _securityContext?.Dispose();
+ }
+
+ public override byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ int resultBlobLength;
+ if (!_isServer)
+ {
+ // client session
+ statusCode = InitializeSecurityContext(
+ ref _credentialsHandle!,
+ ref _securityContext,
+ ref _targetNameHandle,
+ _spn,
+ _requestedContextFlags,
+ incomingBlob,
+ _channelBinding,
+ ref _tokenBuffer,
+ out resultBlobLength,
+ ref _contextFlags);
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:{statusCode}");
+ }
+ else
+ {
+ // TODO: We don't currently check channel bindings.
+
+ // Server session.
+ statusCode = AcceptSecurityContext(
+ _credentialsHandle,
+ ref _securityContext,
+ incomingBlob,
+ ref _tokenBuffer,
+ out resultBlobLength,
+ ref _contextFlags);
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:{statusCode}");
+ }
+
+ if (statusCode >= NegotiateAuthenticationStatusCode.GenericFailure)
+ {
+ Dispose();
+ _isAuthenticated = true;
+ _tokenBuffer = null;
+ return null;
+ }
+
+ 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 == NegotiateAuthenticationStatusCode.Completed)
+ {
+ // Success.
+ _isAuthenticated = true;
+ _tokenBuffer = null;
+ }
+ else
+ {
+ // We need to continue.
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"need continue _securityContext:{_securityContext}");
+ }
+
+ return result;
+ }
+
+ public override NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ Interop.NetSecurityNative.GssBuffer encryptedBuffer = default;
+ try
+ {
+ Interop.NetSecurityNative.Status minorStatus;
+ bool encrypt = requestEncryption;
+ Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.WrapBuffer(
+ out minorStatus,
+ _securityContext,
+ ref encrypt,
+ input,
+ ref encryptedBuffer);
+ isEncrypted = encrypt;
+ if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ return NegotiateAuthenticationStatusCode.GenericFailure;
+ }
+
+ encryptedBuffer.Span.CopyTo(outputWriter.GetSpan(encryptedBuffer.Span.Length));
+ outputWriter.Advance(encryptedBuffer.Span.Length);
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+ finally
+ {
+ encryptedBuffer.Dispose();
+ }
+ }
+
+ public override NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer);
+ try
+ {
+ Interop.NetSecurityNative.Status minorStatus;
+ Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, _securityContext, out wasEncrypted, input, ref decryptedBuffer);
+ if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ return status switch
+ {
+ Interop.NetSecurityNative.Status.GSS_S_BAD_SIG => NegotiateAuthenticationStatusCode.MessageAltered,
+ _ => NegotiateAuthenticationStatusCode.InvalidToken
+ };
+ }
+
+ decryptedBuffer.Span.CopyTo(outputWriter.GetSpan(decryptedBuffer.Span.Length));
+ outputWriter.Advance(decryptedBuffer.Span.Length);
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+ finally
+ {
+ decryptedBuffer.Dispose();
+ }
+ }
+
+ public override unsafe NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer);
+ try
+ {
+ Interop.NetSecurityNative.Status minorStatus;
+ Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, _securityContext, out wasEncrypted, input, ref decryptedBuffer);
+ if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ unwrappedOffset = 0;
+ unwrappedLength = 0;
+ return status switch
+ {
+ Interop.NetSecurityNative.Status.GSS_S_BAD_SIG => NegotiateAuthenticationStatusCode.MessageAltered,
+ _ => NegotiateAuthenticationStatusCode.InvalidToken
+ };
+ }
+
+ decryptedBuffer.Span.CopyTo(input);
+ unwrappedOffset = 0;
+ unwrappedLength = decryptedBuffer.Span.Length;
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+ finally
+ {
+ decryptedBuffer.Dispose();
+ }
+ }
+
+ public override unsafe void GetMIC(ReadOnlySpan message, IBufferWriter signature)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ Interop.NetSecurityNative.GssBuffer micBuffer = default;
+ try
+ {
+ Interop.NetSecurityNative.Status minorStatus;
+ Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.GetMic(
+ out minorStatus,
+ _securityContext,
+ message,
+ ref micBuffer);
+ if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
+ }
+
+ signature.Write(micBuffer.Span);
+ }
+ finally
+ {
+ micBuffer.Dispose();
+ }
+ }
+
+ public override unsafe bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.VerifyMic(
+ out _,
+ _securityContext,
+ message,
+ signature);
+ return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE;
+ }
+
+ private static Interop.NetSecurityNative.PackageType GetPackageType(string package)
+ {
+ if (string.Equals(package, NegotiationInfoClass.Negotiate, StringComparison.OrdinalIgnoreCase))
+ {
+ return Interop.NetSecurityNative.PackageType.Negotiate;
+ }
+ else if (string.Equals(package, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase))
+ {
+ return Interop.NetSecurityNative.PackageType.NTLM;
+ }
+ else if (string.Equals(package, NegotiationInfoClass.Kerberos, StringComparison.OrdinalIgnoreCase))
+ {
+ return Interop.NetSecurityNative.PackageType.Kerberos;
+ }
+ else
+ {
+ // Native shim currently supports only NTLM, Negotiate and Kerberos
+ throw new PlatformNotSupportedException(SR.net_securitypackagesupport);
+ }
+ }
+
+ private SafeGssCredHandle AcquireDefaultCredential()
+ {
+ try
+ {
+ return SafeGssCredHandle.Create(string.Empty, string.Empty, _packageType);
+ }
+ catch (Exception ex)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
+
+ // NOTE: We throw PlatformNotSupportedException which is caught in
+ // NegotiateAuthenticationPal.Create and transformed into instantiation of
+ // UnsupportedNegotiateAuthenticationPal.
+ throw new PlatformNotSupportedException(ex.Message, ex);
+ }
+ }
+
+ private SafeGssCredHandle AcquireCredentialsHandle(NetworkCredential credential)
+ {
+ try
+ {
+ string username = credential.UserName;
+ string password = credential.Password;
+ ReadOnlySpan domain = credential.Domain;
+
+ 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);
+ }
+
+ return SafeGssCredHandle.Create(username, password, _packageType);
+ }
+ catch (Exception ex)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
+
+ // NOTE: We throw PlatformNotSupportedException which is caught in
+ // NegotiateAuthenticationPal.Create and transformed into instantiation of
+ // UnsupportedNegotiateAuthenticationPal.
+ throw new PlatformNotSupportedException(ex.Message, ex);
+ }
+ }
+
+ private static string GssGetUser(SafeGssContextHandle context)
+ {
+ Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
+
+ try
+ {
+ Interop.NetSecurityNative.Status status
+ = Interop.NetSecurityNative.GetUser(out var minorStatus,
+ context,
+ ref token);
+
+ if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
+ }
+
+ ReadOnlySpan tokenBytes = token.Span;
+ int length = tokenBytes.Length;
+ if (length > 0 && tokenBytes[length - 1] == '\0')
+ {
+ // Some GSS-API providers (gss-ntlmssp) include the terminating null with strings, so skip that.
+ tokenBytes = tokenBytes.Slice(0, length - 1);
+ }
+
+ return Encoding.UTF8.GetString(tokenBytes);
+ }
+ finally
+ {
+ token.Dispose();
+ }
+ }
+
+ private NegotiateAuthenticationStatusCode InitializeSecurityContext(
+ ref SafeGssCredHandle credentialsHandle,
+ ref SafeGssContextHandle? contextHandle,
+ ref SafeGssNameHandle? targetNameHandle,
+ string? spn,
+ Interop.NetSecurityNative.GssFlags requestedContextFlags,
+ ReadOnlySpan incomingBlob,
+ ChannelBinding? channelBinding,
+ ref byte[]? resultBlob,
+ out int resultBlobLength,
+ ref Interop.NetSecurityNative.GssFlags contextFlags)
+ {
+ resultBlob = null;
+ resultBlobLength = 0;
+
+ if (contextHandle == null)
+ {
+ if (NetEventSource.Log.IsEnabled())
+ {
+ string protocol = _packageType switch {
+ Interop.NetSecurityNative.PackageType.NTLM => "NTLM",
+ Interop.NetSecurityNative.PackageType.Kerberos => "Kerberos",
+ _ => "SPNEGO"
+ };
+ NetEventSource.Info(this, $"requested protocol = {protocol}, target = {spn}");
+ }
+
+ targetNameHandle = SafeGssNameHandle.CreateTarget(spn!);
+ contextHandle = new SafeGssContextHandle();
+ }
+
+ Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
+ Interop.NetSecurityNative.Status status;
+ Interop.NetSecurityNative.Status minorStatus;
+ try
+ {
+ uint outputFlags;
+ bool isNtlmUsed;
+
+ if (channelBinding != null)
+ {
+ // If a TLS channel binding token (cbt) is available then get the pointer
+ // to the application specific data.
+ int appDataOffset = Marshal.SizeOf();
+ Debug.Assert(appDataOffset < channelBinding.Size);
+ IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
+ int cbtAppDataSize = channelBinding.Size - appDataOffset;
+ status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
+ credentialsHandle,
+ ref contextHandle,
+ _packageType,
+ cbtAppData,
+ cbtAppDataSize,
+ targetNameHandle,
+ (uint)requestedContextFlags,
+ incomingBlob,
+ ref token,
+ out outputFlags,
+ out isNtlmUsed);
+ }
+ else
+ {
+ status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
+ credentialsHandle,
+ ref contextHandle,
+ _packageType,
+ targetNameHandle,
+ (uint)requestedContextFlags,
+ incomingBlob,
+ ref token,
+ out outputFlags,
+ out isNtlmUsed);
+ }
+
+ if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
+ (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
+ {
+ if (contextHandle.IsInvalid)
+ {
+ targetNameHandle?.Dispose();
+ }
+
+ Interop.NetSecurityNative.GssApiException gex = new Interop.NetSecurityNative.GssApiException(status, minorStatus);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, gex);
+ resultBlob = Array.Empty();
+ return GetErrorCode(gex);
+ }
+
+ resultBlob = token.ToByteArray();
+ resultBlobLength = resultBlob?.Length ?? 0;
+
+ if (status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ if (NetEventSource.Log.IsEnabled())
+ {
+ string protocol = _packageType switch {
+ Interop.NetSecurityNative.PackageType.NTLM => "NTLM",
+ Interop.NetSecurityNative.PackageType.Kerberos => "Kerberos",
+ _ => isNtlmUsed ? "SPNEGO-NTLM" : "SPNEGO-Kerberos"
+ };
+ NetEventSource.Info(this, $"actual protocol = {protocol}");
+ }
+
+ // Populate protocol used for authentication
+ _package = isNtlmUsed ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Kerberos;
+ }
+
+ Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi");
+ contextFlags = (Interop.NetSecurityNative.GssFlags)outputFlags;
+
+ return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE ?
+ NegotiateAuthenticationStatusCode.Completed :
+ NegotiateAuthenticationStatusCode.ContinueNeeded;
+ }
+ catch (Exception ex)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
+ return NegotiateAuthenticationStatusCode.GenericFailure;
+ }
+ finally
+ {
+ token.Dispose();
+ }
+ }
+
+ private NegotiateAuthenticationStatusCode AcceptSecurityContext(
+ SafeGssCredHandle credentialsHandle,
+ ref SafeGssContextHandle? contextHandle,
+ //ContextFlagsPal requestedContextFlags,
+ ReadOnlySpan incomingBlob,
+ //ChannelBinding? channelBinding,
+ ref byte[]? resultBlob,
+ out int resultBlobLength,
+ ref Interop.NetSecurityNative.GssFlags contextFlags)
+ {
+ contextHandle ??= new SafeGssContextHandle();
+
+ Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
+ try
+ {
+ Interop.NetSecurityNative.Status status;
+ Interop.NetSecurityNative.Status minorStatus;
+ status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus,
+ credentialsHandle,
+ ref contextHandle,
+ incomingBlob,
+ ref token,
+ out uint outputFlags,
+ out bool isNtlmUsed);
+
+ if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
+ (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
+ {
+ Interop.NetSecurityNative.GssApiException gex = new Interop.NetSecurityNative.GssApiException(status, minorStatus);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, gex);
+ resultBlobLength = 0;
+ return GetErrorCode(gex);
+ }
+
+ resultBlob = token.ToByteArray();
+
+ Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi");
+
+ contextFlags = (Interop.NetSecurityNative.GssFlags)outputFlags;
+ resultBlobLength = resultBlob.Length;
+
+ NegotiateAuthenticationStatusCode errorCode;
+ if (status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
+ {
+ if (NetEventSource.Log.IsEnabled())
+ {
+ string protocol = isNtlmUsed ? "SPNEGO-NTLM" : "SPNEGO-Kerberos";
+ NetEventSource.Info(this, $"AcceptSecurityContext: actual protocol = {protocol}");
+ }
+
+ // Populate protocol used for authentication
+ _package = isNtlmUsed ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Kerberos;
+ errorCode = NegotiateAuthenticationStatusCode.Completed;
+ }
+ else
+ {
+ errorCode = NegotiateAuthenticationStatusCode.ContinueNeeded;
+ }
+
+ return errorCode;
+ }
+ catch (Exception ex)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
+ resultBlobLength = 0;
+ return NegotiateAuthenticationStatusCode.GenericFailure;
+ }
+ finally
+ {
+ token.Dispose();
+ }
+ }
+
+ // https://www.gnu.org/software/gss/reference/gss.pdf (page 25)
+ private static NegotiateAuthenticationStatusCode GetErrorCode(Interop.NetSecurityNative.GssApiException exception)
+ {
+ switch (exception.MajorStatus)
+ {
+ case Interop.NetSecurityNative.Status.GSS_S_NO_CRED:
+ return NegotiateAuthenticationStatusCode.UnknownCredentials;
+ case Interop.NetSecurityNative.Status.GSS_S_BAD_BINDINGS:
+ return NegotiateAuthenticationStatusCode.BadBinding;
+ case Interop.NetSecurityNative.Status.GSS_S_CREDENTIALS_EXPIRED:
+ return NegotiateAuthenticationStatusCode.CredentialsExpired;
+ case Interop.NetSecurityNative.Status.GSS_S_DEFECTIVE_TOKEN:
+ return NegotiateAuthenticationStatusCode.InvalidToken;
+ case Interop.NetSecurityNative.Status.GSS_S_DEFECTIVE_CREDENTIAL:
+ return NegotiateAuthenticationStatusCode.InvalidCredentials;
+ case Interop.NetSecurityNative.Status.GSS_S_BAD_SIG:
+ return NegotiateAuthenticationStatusCode.MessageAltered;
+ case Interop.NetSecurityNative.Status.GSS_S_BAD_MECH:
+ case Interop.NetSecurityNative.Status.GSS_S_UNAVAILABLE:
+ return NegotiateAuthenticationStatusCode.Unsupported;
+ case Interop.NetSecurityNative.Status.GSS_S_NO_CONTEXT:
+ default:
+ return NegotiateAuthenticationStatusCode.GenericFailure;
+ }
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unsupported.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unsupported.cs
new file mode 100644
index 0000000000000..7e6498832f738
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unsupported.cs
@@ -0,0 +1,56 @@
+// 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 incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ statusCode = NegotiateAuthenticationStatusCode.Unsupported;
+ return null;
+ }
+
+ public override NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted) => throw new InvalidOperationException();
+ public override NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted) => throw new InvalidOperationException();
+ public override NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) => throw new InvalidOperationException();
+ public override void GetMIC(ReadOnlySpan message, IBufferWriter signature) => throw new InvalidOperationException();
+ public override bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature) => throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Windows.cs
new file mode 100644
index 0000000000000..3dcb03bfd08f7
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Windows.cs
@@ -0,0 +1,795 @@
+// 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.Buffers.Binary;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Principal;
+using System.Security.Authentication.ExtendedProtection;
+using System.Net.Security;
+
+namespace System.Net
+{
+ internal partial class NegotiateAuthenticationPal
+ {
+ public static NegotiateAuthenticationPal Create(NegotiateAuthenticationClientOptions clientOptions)
+ {
+ try
+ {
+ return new WindowsNegotiateAuthenticationPal(clientOptions);
+ }
+ catch (NotSupportedException)
+ {
+ return new UnsupportedNegotiateAuthenticationPal(clientOptions);
+ }
+ }
+
+ public static NegotiateAuthenticationPal Create(NegotiateAuthenticationServerOptions serverOptions)
+ {
+ try
+ {
+ return new WindowsNegotiateAuthenticationPal(serverOptions);
+ }
+ catch (NotSupportedException)
+ {
+ return new UnsupportedNegotiateAuthenticationPal(serverOptions);
+ }
+ }
+
+ internal sealed class WindowsNegotiateAuthenticationPal : NegotiateAuthenticationPal
+ {
+ private bool _isServer;
+ private bool _isAuthenticated;
+ private int _tokenSize;
+ private byte[]? _tokenBuffer;
+ private SafeFreeCredentials? _credentialsHandle;
+ private SafeDeleteContext? _securityContext;
+ private Interop.SspiCli.ContextFlags _requestedContextFlags;
+ private Interop.SspiCli.ContextFlags _contextFlags;
+ private string _package;
+ private string? _protocolName;
+ private string? _spn;
+ private ChannelBinding? _channelBinding;
+
+ public override bool IsAuthenticated => _isAuthenticated;
+
+ public override bool IsSigned => (_contextFlags & (_isServer ? Interop.SspiCli.ContextFlags.AcceptIntegrity : Interop.SspiCli.ContextFlags.InitIntegrity)) != 0;
+
+ public override bool IsEncrypted => (_contextFlags & Interop.SspiCli.ContextFlags.Confidentiality) != 0;
+
+ public override bool IsMutuallyAuthenticated => (_contextFlags & Interop.SspiCli.ContextFlags.MutualAuth) != 0;
+
+ public override string Package
+ {
+ get
+ {
+ // Note: May return string.Empty if the auth is not done yet or failed.
+ if (_protocolName == null)
+ {
+ string? negotiationAuthenticationPackage = null;
+
+ if (_securityContext is not null)
+ {
+ SecPkgContext_NegotiationInfoW ctx = default;
+ bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_NEGOTIATION_INFO, typeof(SafeFreeContextBuffer), out SafeHandle? sspiHandle, ref ctx);
+ using (sspiHandle)
+ {
+ negotiationAuthenticationPackage = success ? NegotiationInfoClass.GetAuthenticationPackageName(sspiHandle!, (int)ctx.NegotiationState) : null;
+ }
+ if (_isAuthenticated)
+ {
+ _protocolName = negotiationAuthenticationPackage;
+ }
+ }
+
+ return negotiationAuthenticationPackage ?? string.Empty;
+ }
+
+ return _protocolName;
+ }
+ }
+
+ public override string? TargetName
+ {
+ get
+ {
+ if (_isServer && _spn == null)
+ {
+ Debug.Assert(_securityContext is not null && _isAuthenticated, "Trying to get the client SPN before handshaking is done!");
+ _spn = SSPIWrapper.QueryStringContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CLIENT_SPECIFIED_TARGET);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"The client specified SPN is [{_spn}]");
+ }
+ return _spn;
+ }
+ }
+
+ public override IIdentity RemoteIdentity
+ {
+ get
+ {
+ IIdentity? result;
+ string? name = _isServer ? null : TargetName;
+ string protocol = Package;
+
+ Debug.Assert(_securityContext is not null);
+
+ if (_isServer)
+ {
+ SecurityContextTokenHandle? token = null;
+ try
+ {
+ name = SSPIWrapper.QueryStringContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_NAMES);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"NTAuthentication: The context is associated with [{name}]");
+
+ // This will return a client token when conducted authentication on server side.
+ // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app.
+ Interop.SECURITY_STATUS winStatus = (Interop.SECURITY_STATUS)SSPIWrapper.QuerySecurityContextToken(
+ GlobalSSPI.SSPIAuth,
+ _securityContext,
+ out token);
+ if (winStatus != Interop.SECURITY_STATUS.OK)
+ {
+ throw new Win32Exception((int)winStatus);
+ }
+
+ // The following call was also specifying WindowsAccountType.Normal, true.
+ // WindowsIdentity.IsAuthenticated is no longer supported in .NET Core
+ result = new WindowsIdentity(token.DangerousGetHandle(), protocol);
+ return result;
+ }
+ catch (SecurityException)
+ {
+ // Ignore and construct generic Identity if failed due to security problem.
+ }
+ finally
+ {
+ token?.Dispose();
+ }
+ }
+
+ // On the client we don't have access to the remote side identity.
+ result = new GenericIdentity(name ?? string.Empty, protocol);
+ return result;
+ }
+ }
+
+ public override System.Security.Principal.TokenImpersonationLevel ImpersonationLevel
+ {
+ get
+ {
+ return
+ (_contextFlags & Interop.SspiCli.ContextFlags.Delegate) != 0 && Package != NegotiationInfoClass.NTLM ? TokenImpersonationLevel.Delegation :
+ (_contextFlags & (_isServer ? Interop.SspiCli.ContextFlags.AcceptIdentify : Interop.SspiCli.ContextFlags.InitIdentify)) != 0 ? TokenImpersonationLevel.Identification :
+ TokenImpersonationLevel.Impersonation;
+ }
+ }
+
+ public WindowsNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions)
+ {
+ Interop.SspiCli.ContextFlags contextFlags = Interop.SspiCli.ContextFlags.Connection;
+
+ contextFlags |= clientOptions.RequiredProtectionLevel switch
+ {
+ ProtectionLevel.Sign => Interop.SspiCli.ContextFlags.InitIntegrity,
+ ProtectionLevel.EncryptAndSign => Interop.SspiCli.ContextFlags.InitIntegrity | Interop.SspiCli.ContextFlags.Confidentiality,
+ _ => 0
+ };
+
+ contextFlags |= clientOptions.RequireMutualAuthentication ? Interop.SspiCli.ContextFlags.MutualAuth : 0;
+
+ contextFlags |= clientOptions.AllowedImpersonationLevel switch
+ {
+ TokenImpersonationLevel.Identification => Interop.SspiCli.ContextFlags.InitIdentify,
+ TokenImpersonationLevel.Delegation => Interop.SspiCli.ContextFlags.Delegate,
+ _ => 0
+ };
+
+ _isServer = false;
+ _tokenSize = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, clientOptions.Package, true)!.MaxToken;
+ _spn = clientOptions.TargetName;
+ _securityContext = null;
+ _requestedContextFlags = contextFlags;
+ _package = clientOptions.Package;
+ _channelBinding = clientOptions.Binding;
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
+
+ //
+ // Check if we're using DefaultCredentials.
+ //
+
+ Debug.Assert(CredentialCache.DefaultCredentials == CredentialCache.DefaultNetworkCredentials);
+ if (clientOptions.Credential == CredentialCache.DefaultCredentials)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "using DefaultCredentials");
+ _credentialsHandle = AcquireDefaultCredential(_package, _isServer);
+ }
+ else
+ {
+ _credentialsHandle = AcquireCredentialsHandle(_package, _isServer, clientOptions.Credential);
+ }
+ }
+
+ public WindowsNegotiateAuthenticationPal(NegotiateAuthenticationServerOptions serverOptions)
+ {
+ Interop.SspiCli.ContextFlags contextFlags = serverOptions.RequiredProtectionLevel switch
+ {
+ ProtectionLevel.Sign => Interop.SspiCli.ContextFlags.AcceptIntegrity,
+ ProtectionLevel.EncryptAndSign => Interop.SspiCli.ContextFlags.AcceptIntegrity | Interop.SspiCli.ContextFlags.Confidentiality,
+ _ => 0
+ } | Interop.SspiCli.ContextFlags.Connection;
+
+ if (serverOptions.Policy is not null)
+ {
+ if (serverOptions.Policy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
+ {
+ contextFlags |= Interop.SspiCli.ContextFlags.AllowMissingBindings;
+ }
+
+ if (serverOptions.Policy.PolicyEnforcement != PolicyEnforcement.Never &&
+ serverOptions.Policy.ProtectionScenario == ProtectionScenario.TrustedProxy)
+ {
+ contextFlags |= Interop.SspiCli.ContextFlags.ProxyBindings;
+ }
+ }
+
+ _isServer = true;
+ _tokenSize = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, serverOptions.Package, true)!.MaxToken;
+ _securityContext = null;
+ _requestedContextFlags = contextFlags;
+ _package = serverOptions.Package;
+ _channelBinding = serverOptions.Binding;
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
+
+ //
+ // Check if we're using DefaultCredentials.
+ //
+
+ Debug.Assert(CredentialCache.DefaultCredentials == CredentialCache.DefaultNetworkCredentials);
+ if (serverOptions.Credential == CredentialCache.DefaultCredentials)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "using DefaultCredentials");
+ _credentialsHandle = AcquireDefaultCredential(_package, _isServer);
+ }
+ else
+ {
+ _credentialsHandle = AcquireCredentialsHandle(_package, _isServer, serverOptions.Credential);
+ }
+ }
+
+ public override void Dispose()
+ {
+ _securityContext?.Dispose();
+ }
+
+ public override byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
+ {
+ _tokenBuffer ??= _tokenSize == 0 ? Array.Empty() : new byte[_tokenSize];
+
+ bool firstTime = _securityContext == null;
+ int resultBlobLength;
+ SecurityStatusPal platformStatusCode;
+ try
+ {
+ if (!_isServer)
+ {
+ // client session
+ platformStatusCode = 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)platformStatusCode.ErrorCode):x8} ({platformStatusCode})");
+
+ if (platformStatusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded)
+ {
+ platformStatusCode = CompleteAuthToken(ref _securityContext, _tokenBuffer.AsSpan(0, resultBlobLength));
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)platformStatusCode.ErrorCode):x8} ({platformStatusCode})");
+
+ resultBlobLength = 0;
+ }
+ }
+ else
+ {
+ // Server session.
+ platformStatusCode = 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)platformStatusCode.ErrorCode):x8} ({platformStatusCode})");
+ }
+ }
+ 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();
+ }
+ }
+
+ // Map error codes
+ // TODO: Remove double mapping from Win32 codes
+ statusCode = platformStatusCode.ErrorCode switch
+ {
+ SecurityStatusPalErrorCode.OK => NegotiateAuthenticationStatusCode.Completed,
+ SecurityStatusPalErrorCode.ContinueNeeded => NegotiateAuthenticationStatusCode.ContinueNeeded,
+
+ // These code should never be returned and they should be handled internally
+ SecurityStatusPalErrorCode.CompleteNeeded => NegotiateAuthenticationStatusCode.Completed,
+ SecurityStatusPalErrorCode.CompAndContinue => NegotiateAuthenticationStatusCode.ContinueNeeded,
+
+ SecurityStatusPalErrorCode.ContextExpired => NegotiateAuthenticationStatusCode.ContextExpired,
+ SecurityStatusPalErrorCode.Unsupported => NegotiateAuthenticationStatusCode.Unsupported,
+ SecurityStatusPalErrorCode.PackageNotFound => NegotiateAuthenticationStatusCode.Unsupported,
+ SecurityStatusPalErrorCode.CannotInstall => NegotiateAuthenticationStatusCode.Unsupported,
+ SecurityStatusPalErrorCode.InvalidToken => NegotiateAuthenticationStatusCode.InvalidToken,
+ SecurityStatusPalErrorCode.QopNotSupported => NegotiateAuthenticationStatusCode.QopNotSupported,
+ SecurityStatusPalErrorCode.NoImpersonation => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.LogonDenied => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.UnknownCredentials => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.NoCredentials => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.MessageAltered => NegotiateAuthenticationStatusCode.MessageAltered,
+ SecurityStatusPalErrorCode.OutOfSequence => NegotiateAuthenticationStatusCode.OutOfSequence,
+ SecurityStatusPalErrorCode.NoAuthenticatingAuthority => NegotiateAuthenticationStatusCode.InvalidCredentials,
+ SecurityStatusPalErrorCode.IncompleteCredentials => NegotiateAuthenticationStatusCode.InvalidCredentials,
+ SecurityStatusPalErrorCode.IllegalMessage => NegotiateAuthenticationStatusCode.InvalidToken,
+ SecurityStatusPalErrorCode.CertExpired => NegotiateAuthenticationStatusCode.CredentialsExpired,
+ SecurityStatusPalErrorCode.SecurityQosFailed => NegotiateAuthenticationStatusCode.QopNotSupported,
+ SecurityStatusPalErrorCode.UnsupportedPreauth => NegotiateAuthenticationStatusCode.InvalidToken,
+ SecurityStatusPalErrorCode.BadBinding => NegotiateAuthenticationStatusCode.BadBinding,
+ SecurityStatusPalErrorCode.UntrustedRoot => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.SmartcardLogonRequired => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.WrongPrincipal => NegotiateAuthenticationStatusCode.UnknownCredentials,
+ SecurityStatusPalErrorCode.CannotPack => NegotiateAuthenticationStatusCode.InvalidToken,
+ SecurityStatusPalErrorCode.TimeSkew => NegotiateAuthenticationStatusCode.InvalidToken,
+ SecurityStatusPalErrorCode.AlgorithmMismatch => NegotiateAuthenticationStatusCode.InvalidToken,
+ SecurityStatusPalErrorCode.CertUnknown => NegotiateAuthenticationStatusCode.UnknownCredentials,
+
+ // Processing partial inputs is not supported, so this is result of incorrect input
+ SecurityStatusPalErrorCode.IncompleteMessage => NegotiateAuthenticationStatusCode.InvalidToken,
+
+ _ => NegotiateAuthenticationStatusCode.GenericFailure,
+ };
+
+ if (((int)platformStatusCode.ErrorCode >= (int)SecurityStatusPalErrorCode.OutOfMemory))
+ {
+ //CloseContext();
+ _securityContext?.Dispose();
+ _isAuthenticated = true;
+ _tokenBuffer = null;
+ 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 (platformStatusCode.ErrorCode == SecurityStatusPalErrorCode.OK
+ || (_isServer && platformStatusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded))
+ {
+ // Success.
+ _isAuthenticated = true;
+ _tokenBuffer = null;
+ }
+ else
+ {
+ // We need to continue.
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"need continue statusCode:0x{((int)platformStatusCode.ErrorCode):x8} ({platformStatusCode}) _securityContext:{_securityContext}");
+ }
+
+ return result;
+ }
+
+ public override unsafe NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ SecPkgContext_Sizes sizes = default;
+ bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes);
+ Debug.Assert(success);
+
+ // alloc new output buffer if not supplied or too small
+ int resultSize = input.Length + sizes.cbMaxSignature;
+ Span outputBuffer = outputWriter.GetSpan(resultSize);
+
+ // make a copy of user data for in-place encryption
+ input.CopyTo(outputBuffer.Slice(sizes.cbMaxSignature, input.Length));
+
+ isEncrypted = requestEncryption;
+
+ fixed (byte* outputPtr = outputBuffer)
+ {
+ // Prepare buffers TOKEN(signature), DATA and Padding.
+ Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2];
+ Interop.SspiCli.SecBuffer* tokenBuffer = &unmanagedBuffer[0];
+ Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1];
+ tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN;
+ tokenBuffer->pvBuffer = (IntPtr)(outputPtr);
+ tokenBuffer->cbBuffer = sizes.cbMaxSignature;
+ dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA;
+ dataBuffer->pvBuffer = (IntPtr)(outputPtr + sizes.cbMaxSignature);
+ dataBuffer->cbBuffer = input.Length;
+
+ Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2)
+ {
+ pBuffers = unmanagedBuffer
+ };
+
+ uint qop = requestEncryption ? 0 : Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT;
+ int errorCode = GlobalSSPI.SSPIAuth.EncryptMessage(_securityContext, ref sdcInOut, qop);
+
+ if (errorCode != 0)
+ {
+ return errorCode switch
+ {
+ (int)Interop.SECURITY_STATUS.ContextExpired => NegotiateAuthenticationStatusCode.ContextExpired,
+ (int)Interop.SECURITY_STATUS.QopNotSupported => NegotiateAuthenticationStatusCode.QopNotSupported,
+ _ => NegotiateAuthenticationStatusCode.GenericFailure,
+ };
+ }
+
+ outputWriter.Advance(tokenBuffer->cbBuffer + dataBuffer->cbBuffer);
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+ }
+
+ public override NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
+ {
+ Span outputBuffer = outputWriter.GetSpan(input.Length).Slice(0, input.Length);
+ NegotiateAuthenticationStatusCode statusCode;
+
+ input.CopyTo(outputBuffer);
+ statusCode = UnwrapInPlace(outputBuffer, out int unwrappedOffset, out int unwrappedLength, out wasEncrypted);
+
+ if (statusCode == NegotiateAuthenticationStatusCode.Completed)
+ {
+ if (unwrappedOffset > 0)
+ {
+ outputBuffer.Slice(unwrappedOffset, unwrappedLength).CopyTo(outputBuffer);
+ }
+ outputWriter.Advance(unwrappedLength);
+ }
+
+ return statusCode;
+ }
+
+ public override unsafe NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
+ {
+ Debug.Assert(_securityContext is not null);
+
+ fixed (byte* inputPtr = input)
+ {
+ Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2];
+ Interop.SspiCli.SecBuffer* streamBuffer = &unmanagedBuffer[0];
+ Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1];
+ streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM;
+ streamBuffer->pvBuffer = (IntPtr)inputPtr;
+ streamBuffer->cbBuffer = input.Length;
+ dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA;
+ dataBuffer->pvBuffer = IntPtr.Zero;
+ dataBuffer->cbBuffer = 0;
+
+ Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2)
+ {
+ pBuffers = unmanagedBuffer
+ };
+
+ uint qop;
+ int errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(_securityContext, ref sdcInOut, out qop);
+ if (errorCode != 0)
+ {
+ unwrappedOffset = 0;
+ unwrappedLength = 0;
+ wasEncrypted = false;
+ return errorCode switch
+ {
+ (int)Interop.SECURITY_STATUS.MessageAltered => NegotiateAuthenticationStatusCode.MessageAltered,
+ _ => NegotiateAuthenticationStatusCode.InvalidToken
+ };
+ }
+
+ if (dataBuffer->BufferType != SecurityBufferType.SECBUFFER_DATA)
+ {
+ throw new InternalException(dataBuffer->BufferType);
+ }
+
+ wasEncrypted = qop != Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT;
+
+ Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)inputPtr);
+ Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)inputPtr + input.Length);
+ unwrappedOffset = (int)((byte*)dataBuffer->pvBuffer - inputPtr);
+ unwrappedLength = dataBuffer->cbBuffer;
+ return NegotiateAuthenticationStatusCode.Completed;
+ }
+ }
+
+ public override unsafe void GetMIC(ReadOnlySpan message, IBufferWriter signature)
+ {
+ bool refAdded = false;
+
+ Debug.Assert(_securityContext is not null);
+
+ try
+ {
+ _securityContext.DangerousAddRef(ref refAdded);
+
+ SecPkgContext_Sizes sizes = default;
+ bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes);
+ Debug.Assert(success);
+
+ Span signatureBuffer = signature.GetSpan(sizes.cbSecurityTrailer);
+
+ fixed (byte* messagePtr = message)
+ fixed (byte* signaturePtr = signatureBuffer)
+ {
+ // Prepare buffers TOKEN(signature), DATA.
+ Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2];
+ Interop.SspiCli.SecBuffer* tokenBuffer = &unmanagedBuffer[0];
+ Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1];
+ tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN;
+ tokenBuffer->pvBuffer = (IntPtr)signaturePtr;
+ tokenBuffer->cbBuffer = sizes.cbSecurityTrailer;
+ dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA;
+ dataBuffer->pvBuffer = (IntPtr)messagePtr;
+ dataBuffer->cbBuffer = message.Length;
+
+ Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2)
+ {
+ pBuffers = unmanagedBuffer
+ };
+
+ uint qop = IsEncrypted ? 0 : Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT;
+ int errorCode = Interop.SspiCli.MakeSignature(ref _securityContext._handle, qop, ref sdcInOut, 0);
+
+ if (errorCode != 0)
+ {
+ Exception e = new Win32Exception(errorCode);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e);
+ throw new Win32Exception(errorCode);
+ }
+
+ signature.Advance(signatureBuffer.Length);
+ }
+ }
+ finally
+ {
+ if (refAdded)
+ {
+ _securityContext.DangerousRelease();
+ }
+ }
+ }
+
+ public override unsafe bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature)
+ {
+ bool refAdded = false;
+
+ Debug.Assert(_securityContext is not null);
+
+ try
+ {
+ _securityContext.DangerousAddRef(ref refAdded);
+
+ fixed (byte* messagePtr = message)
+ fixed (byte* signaturePtr = signature)
+ {
+ Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2];
+ Interop.SspiCli.SecBuffer* tokenBuffer = &unmanagedBuffer[0];
+ Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1];
+ tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN;
+ tokenBuffer->pvBuffer = (IntPtr)signaturePtr;
+ tokenBuffer->cbBuffer = signature.Length;
+ dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA;
+ dataBuffer->pvBuffer = (IntPtr)messagePtr;
+ dataBuffer->cbBuffer = message.Length;
+
+ Interop.SspiCli.SecBufferDesc sdcIn = new Interop.SspiCli.SecBufferDesc(2)
+ {
+ pBuffers = unmanagedBuffer
+ };
+
+ uint qop;
+ int errorCode = Interop.SspiCli.VerifySignature(ref _securityContext._handle, in sdcIn, 0, &qop);
+
+ if (errorCode != 0)
+ {
+ Exception e = new Win32Exception(errorCode);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e);
+ throw new Win32Exception(errorCode);
+ }
+
+ if (IsEncrypted && qop == Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT)
+ {
+ Debug.Fail($"Expected qop = 0, returned value = {qop}");
+ throw new InvalidOperationException(SR.net_auth_message_not_encrypted);
+ }
+
+ return true;
+ }
+ }
+ finally
+ {
+ if (refAdded)
+ {
+ _securityContext.DangerousRelease();
+ }
+ }
+ }
+
+ private static SafeFreeCredentials AcquireDefaultCredential(string package, bool isServer)
+ {
+ return SSPIWrapper.AcquireDefaultCredential(
+ GlobalSSPI.SSPIAuth,
+ package,
+ (isServer ? Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND : Interop.SspiCli.CredentialUse.SECPKG_CRED_OUTBOUND));
+ }
+
+ private static SafeFreeCredentials AcquireCredentialsHandle(string package, bool isServer, NetworkCredential credential)
+ {
+ SafeSspiAuthDataHandle? authData = null;
+ try
+ {
+ Interop.SECURITY_STATUS result = Interop.SspiCli.SspiEncodeStringsAsAuthIdentity(
+ credential.UserName, credential.Domain,
+ credential.Password, out authData);
+
+ if (result != Interop.SECURITY_STATUS.OK)
+ {
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, SR.Format(SR.net_log_operation_failed_with_error, nameof(Interop.SspiCli.SspiEncodeStringsAsAuthIdentity), $"0x{(int)result:X}"));
+ throw new Win32Exception((int)result);
+ }
+
+ return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPIAuth,
+ package, (isServer ? Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND : Interop.SspiCli.CredentialUse.SECPKG_CRED_OUTBOUND), ref authData);
+ }
+ finally
+ {
+ authData?.Dispose();
+ }
+ }
+
+ private static SecurityStatusPal InitializeSecurityContext(
+ ref SafeFreeCredentials? credentialsHandle,
+ ref SafeDeleteContext? securityContext,
+ string? spn,
+ Interop.SspiCli.ContextFlags requestedContextFlags,
+ ReadOnlySpan incomingBlob,
+ ChannelBinding? channelBinding,
+ ref byte[]? resultBlob,
+ out int resultBlobLength,
+ ref Interop.SspiCli.ContextFlags contextFlags)
+ {
+
+ InputSecurityBuffers inputBuffers = default;
+ if (!incomingBlob.IsEmpty)
+ {
+ inputBuffers.SetNextBuffer(new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN));
+ }
+
+ if (channelBinding != null)
+ {
+ inputBuffers.SetNextBuffer(new InputSecurityBuffer(channelBinding));
+ }
+
+ var outSecurityBuffer = new SecurityBuffer(resultBlob, SecurityBufferType.SECBUFFER_TOKEN);
+
+ contextFlags = Interop.SspiCli.ContextFlags.Zero;
+ // There is only one SafeDeleteContext type on Windows which is SafeDeleteSslContext so this cast is safe.
+ SafeDeleteSslContext? sslContext = (SafeDeleteSslContext?)securityContext;
+ Interop.SECURITY_STATUS winStatus = (Interop.SECURITY_STATUS)SSPIWrapper.InitializeSecurityContext(
+ GlobalSSPI.SSPIAuth,
+ ref credentialsHandle,
+ ref sslContext,
+ spn,
+ requestedContextFlags,
+ Interop.SspiCli.Endianness.SECURITY_NETWORK_DREP,
+ inputBuffers,
+ ref outSecurityBuffer,
+ ref contextFlags);
+ securityContext = sslContext;
+ Debug.Assert(outSecurityBuffer.offset == 0);
+ resultBlob = outSecurityBuffer.token;
+ resultBlobLength = outSecurityBuffer.size;
+ return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(winStatus);
+ }
+
+ private static SecurityStatusPal CompleteAuthToken(
+ ref SafeDeleteContext? securityContext,
+ ReadOnlySpan incomingBlob)
+ {
+ // There is only one SafeDeleteContext type on Windows which is SafeDeleteSslContext so this cast is safe.
+ SafeDeleteSslContext? sslContext = (SafeDeleteSslContext?)securityContext;
+ var inSecurityBuffer = new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN);
+ Interop.SECURITY_STATUS winStatus = (Interop.SECURITY_STATUS)SSPIWrapper.CompleteAuthToken(
+ GlobalSSPI.SSPIAuth,
+ ref sslContext,
+ in inSecurityBuffer);
+ securityContext = sslContext;
+ return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(winStatus);
+ }
+
+ private static SecurityStatusPal AcceptSecurityContext(
+ SafeFreeCredentials? credentialsHandle,
+ ref SafeDeleteContext? securityContext,
+ Interop.SspiCli.ContextFlags requestedContextFlags,
+ ReadOnlySpan incomingBlob,
+ ChannelBinding? channelBinding,
+ ref byte[]? resultBlob,
+ out int resultBlobLength,
+ ref Interop.SspiCli.ContextFlags contextFlags)
+ {
+ InputSecurityBuffers inputBuffers = default;
+ if (!incomingBlob.IsEmpty)
+ {
+ inputBuffers.SetNextBuffer(new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN));
+ }
+
+ if (channelBinding != null)
+ {
+ inputBuffers.SetNextBuffer(new InputSecurityBuffer(channelBinding));
+ }
+
+ var outSecurityBuffer = new SecurityBuffer(resultBlob, SecurityBufferType.SECBUFFER_TOKEN);
+
+ contextFlags = Interop.SspiCli.ContextFlags.Zero;
+ // There is only one SafeDeleteContext type on Windows which is SafeDeleteSslContext so this cast is safe.
+ SafeDeleteSslContext? sslContext = (SafeDeleteSslContext?)securityContext;
+ Interop.SECURITY_STATUS winStatus = (Interop.SECURITY_STATUS)SSPIWrapper.AcceptSecurityContext(
+ GlobalSSPI.SSPIAuth,
+ credentialsHandle,
+ ref sslContext,
+ requestedContextFlags,
+ Interop.SspiCli.Endianness.SECURITY_NETWORK_DREP,
+ inputBuffers,
+ ref outSecurityBuffer,
+ ref contextFlags);
+
+ // SSPI Workaround
+ // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE
+ // when it should return SEC_E_INVALID_TOKEN.
+ if (winStatus == Interop.SECURITY_STATUS.InvalidHandle && securityContext == null && !incomingBlob.IsEmpty)
+ {
+ winStatus = Interop.SECURITY_STATUS.InvalidToken;
+ }
+
+ Debug.Assert(outSecurityBuffer.offset == 0);
+ resultBlob = outSecurityBuffer.token;
+ resultBlobLength = outSecurityBuffer.size;
+ securityContext = sslContext;
+ return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(winStatus);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.cs
new file mode 100644
index 0000000000000..78eed0d81f84c
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.cs
@@ -0,0 +1,29 @@
+// 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 incomingBlob, out NegotiateAuthenticationStatusCode statusCode);
+ public abstract NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted);
+ public abstract NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted);
+ public abstract NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted);
+ public abstract void GetMIC(ReadOnlySpan message, IBufferWriter signature);
+ public abstract bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature);
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs
index d3c5d368be6db..6b990651e8939 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs
@@ -16,13 +16,14 @@ namespace System.Net.Security
///
public sealed class NegotiateAuthentication : IDisposable
{
- private readonly NTAuthentication? _ntAuthentication;
+ private readonly NegotiateAuthenticationPal _pal;
private readonly string _requestedPackage;
private readonly bool _isServer;
private readonly TokenImpersonationLevel _requiredImpersonationLevel;
private readonly ProtectionLevel _requiredProtectionLevel;
private readonly ExtendedProtectionPolicy? _extendedProtectionPolicy;
private readonly bool _isSecureConnection;
+ private bool _isDisposed;
private IIdentity? _remoteIdentity;
///
@@ -34,47 +35,11 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption
{
ArgumentNullException.ThrowIfNull(clientOptions);
- ContextFlagsPal contextFlags = ContextFlagsPal.Connection;
-
- contextFlags |= clientOptions.RequiredProtectionLevel switch
- {
- ProtectionLevel.Sign => ContextFlagsPal.InitIntegrity,
- ProtectionLevel.EncryptAndSign => ContextFlagsPal.InitIntegrity | ContextFlagsPal.Confidentiality,
- _ => 0
- };
-
- contextFlags |= clientOptions.RequireMutualAuthentication ? ContextFlagsPal.MutualAuth : 0;
-
- contextFlags |= clientOptions.AllowedImpersonationLevel switch
- {
- TokenImpersonationLevel.Identification => ContextFlagsPal.InitIdentify,
- TokenImpersonationLevel.Delegation => ContextFlagsPal.Delegate,
- _ => 0
- };
-
_isServer = false;
_requestedPackage = clientOptions.Package;
_requiredImpersonationLevel = TokenImpersonationLevel.None;
_requiredProtectionLevel = clientOptions.RequiredProtectionLevel;
- try
- {
- _ntAuthentication = new NTAuthentication(
- isServer: false,
- clientOptions.Package,
- clientOptions.Credential,
- clientOptions.TargetName,
- contextFlags,
- clientOptions.Binding);
- }
- catch (PlatformNotSupportedException) // Managed implementation, Unix
- {
- }
- catch (NotSupportedException) // Windows implementation
- {
- }
- catch (Win32Exception) // Unix implementation in native layer
- {
- }
+ _pal = NegotiateAuthenticationPal.Create(clientOptions);
}
///
@@ -86,52 +51,13 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption
{
ArgumentNullException.ThrowIfNull(serverOptions);
- ContextFlagsPal contextFlags = serverOptions.RequiredProtectionLevel switch
- {
- ProtectionLevel.Sign => ContextFlagsPal.AcceptIntegrity,
- ProtectionLevel.EncryptAndSign => ContextFlagsPal.AcceptIntegrity | ContextFlagsPal.Confidentiality,
- _ => 0
- } | ContextFlagsPal.Connection;
-
- if (serverOptions.Policy is not null)
- {
- if (serverOptions.Policy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
- {
- contextFlags |= ContextFlagsPal.AllowMissingBindings;
- }
-
- if (serverOptions.Policy.PolicyEnforcement != PolicyEnforcement.Never &&
- serverOptions.Policy.ProtectionScenario == ProtectionScenario.TrustedProxy)
- {
- contextFlags |= ContextFlagsPal.ProxyBindings;
- }
- }
-
_isServer = true;
_requestedPackage = serverOptions.Package;
_requiredImpersonationLevel = serverOptions.RequiredImpersonationLevel;
_requiredProtectionLevel = serverOptions.RequiredProtectionLevel;
_extendedProtectionPolicy = serverOptions.Policy;
_isSecureConnection = serverOptions.Binding != null;
- try
- {
- _ntAuthentication = new NTAuthentication(
- isServer: true,
- serverOptions.Package,
- serverOptions.Credential,
- null,
- contextFlags,
- serverOptions.Binding);
- }
- catch (PlatformNotSupportedException) // Managed implementation, Unix
- {
- }
- catch (NotSupportedException) // Windows implementation
- {
- }
- catch (Win32Exception) // Unix implementation in native layer
- {
- }
+ _pal = NegotiateAuthenticationPal.Create(serverOptions);
}
///
@@ -140,10 +66,14 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption
///
public void Dispose()
{
- _ntAuthentication?.CloseContext();
- if (_remoteIdentity is IDisposable disposableRemoteIdentity)
+ if (!_isDisposed)
{
- disposableRemoteIdentity.Dispose();
+ _isDisposed = true;
+ _pal?.Dispose();
+ if (_remoteIdentity is IDisposable disposableRemoteIdentity)
+ {
+ disposableRemoteIdentity.Dispose();
+ }
}
}
@@ -151,7 +81,7 @@ public void Dispose()
/// Indicates whether authentication was successfully completed and the session
/// was established.
///
- public bool IsAuthenticated => _ntAuthentication?.IsCompleted ?? false;
+ public bool IsAuthenticated => _isDisposed ? false : _pal.IsAuthenticated;
///
/// Indicates the negotiated level of protection.
@@ -171,17 +101,17 @@ public void Dispose()
///
/// Indicates whether data signing was negotiated.
///
- public bool IsSigned => _ntAuthentication?.IsIntegrityFlag ?? false;
+ public bool IsSigned => _isDisposed ? false : _pal.IsSigned;
///
/// Indicates whether data encryption was negotiated.
///
- public bool IsEncrypted => _ntAuthentication?.IsConfidentialityFlag ?? false;
+ public bool IsEncrypted => _isDisposed ? false : _pal.IsEncrypted;
///
/// Indicates whether both server and client have been authenticated.
///
- public bool IsMutuallyAuthenticated => _ntAuthentication?.IsMutualAuthFlag ?? false;
+ public bool IsMutuallyAuthenticated => _isDisposed ? false : _pal.IsMutuallyAuthenticated;
///
/// Indicates whether the local side of the authentication is representing
@@ -204,7 +134,7 @@ public void Dispose()
/// property will be Kerberos, NTLM, or any other specific protocol that was
/// negotiated between both sides of the authentication.
///
- public string Package => _ntAuthentication?.ProtocolName ?? _requestedPackage;
+ public string Package => _pal.Package ?? _requestedPackage;
///
/// Gets target name (service principal name) of the server.
@@ -216,7 +146,7 @@ public void Dispose()
/// For client-side of the authentication the property returns the target name
/// specified in .
///
- public string? TargetName => IsServer ? _ntAuthentication?.ClientSpecifiedSpn : _ntAuthentication?.Spn;
+ public string? TargetName => _pal.TargetName;
///
/// Gets information about the identity of the remote party.
@@ -233,7 +163,7 @@ public IIdentity RemoteIdentity
IIdentity? identity = _remoteIdentity;
if (identity is null)
{
- if (!IsAuthenticated || _ntAuthentication == null)
+ if (!IsAuthenticated || _isDisposed)
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
@@ -241,7 +171,7 @@ public IIdentity RemoteIdentity
if (IsServer)
{
Debug.Assert(!OperatingSystem.IsTvOS(), "Server authentication is not supported on tvOS");
- _remoteIdentity = identity = NegotiateStreamPal.GetIdentity(_ntAuthentication);
+ _remoteIdentity = identity = _pal.RemoteIdentity;
}
else
{
@@ -256,17 +186,7 @@ public IIdentity RemoteIdentity
/// One of the values, indicating the negotiated
/// level of impresonation.
///
- public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel
- {
- get
- {
- // We should suppress the delegate flag in NTLM case.
- return
- _ntAuthentication!.IsDelegationFlag && _ntAuthentication.ProtocolName != NegotiationInfoClass.NTLM ? TokenImpersonationLevel.Delegation :
- _ntAuthentication.IsIdentifyFlag ? TokenImpersonationLevel.Identification :
- TokenImpersonationLevel.Impersonation;
- }
- }
+ public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel => _pal.ImpersonationLevel;
///
/// Evaluates an authentication token sent by the other party and returns a token in response.
@@ -287,57 +207,12 @@ public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel
///
public byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
{
- if (_ntAuthentication == null)
+ if (_isDisposed)
{
- // Unsupported protocol
- statusCode = NegotiateAuthenticationStatusCode.Unsupported;
- return null;
+ throw new InvalidOperationException(SR.net_auth_noauth);
}
- byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob, false, out SecurityStatusPal securityStatus);
-
- // Map error codes
- statusCode = securityStatus.ErrorCode switch
- {
- SecurityStatusPalErrorCode.OK => NegotiateAuthenticationStatusCode.Completed,
- SecurityStatusPalErrorCode.ContinueNeeded => NegotiateAuthenticationStatusCode.ContinueNeeded,
-
- // These code should never be returned and they should be handled internally
- SecurityStatusPalErrorCode.CompleteNeeded => NegotiateAuthenticationStatusCode.Completed,
- SecurityStatusPalErrorCode.CompAndContinue => NegotiateAuthenticationStatusCode.ContinueNeeded,
-
- SecurityStatusPalErrorCode.ContextExpired => NegotiateAuthenticationStatusCode.ContextExpired,
- SecurityStatusPalErrorCode.Unsupported => NegotiateAuthenticationStatusCode.Unsupported,
- SecurityStatusPalErrorCode.PackageNotFound => NegotiateAuthenticationStatusCode.Unsupported,
- SecurityStatusPalErrorCode.CannotInstall => NegotiateAuthenticationStatusCode.Unsupported,
- SecurityStatusPalErrorCode.InvalidToken => NegotiateAuthenticationStatusCode.InvalidToken,
- SecurityStatusPalErrorCode.QopNotSupported => NegotiateAuthenticationStatusCode.QopNotSupported,
- SecurityStatusPalErrorCode.NoImpersonation => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.LogonDenied => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.UnknownCredentials => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.NoCredentials => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.MessageAltered => NegotiateAuthenticationStatusCode.MessageAltered,
- SecurityStatusPalErrorCode.OutOfSequence => NegotiateAuthenticationStatusCode.OutOfSequence,
- SecurityStatusPalErrorCode.NoAuthenticatingAuthority => NegotiateAuthenticationStatusCode.InvalidCredentials,
- SecurityStatusPalErrorCode.IncompleteCredentials => NegotiateAuthenticationStatusCode.InvalidCredentials,
- SecurityStatusPalErrorCode.IllegalMessage => NegotiateAuthenticationStatusCode.InvalidToken,
- SecurityStatusPalErrorCode.CertExpired => NegotiateAuthenticationStatusCode.CredentialsExpired,
- SecurityStatusPalErrorCode.SecurityQosFailed => NegotiateAuthenticationStatusCode.QopNotSupported,
- SecurityStatusPalErrorCode.UnsupportedPreauth => NegotiateAuthenticationStatusCode.InvalidToken,
- SecurityStatusPalErrorCode.BadBinding => NegotiateAuthenticationStatusCode.BadBinding,
- SecurityStatusPalErrorCode.UntrustedRoot => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.SmartcardLogonRequired => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.WrongPrincipal => NegotiateAuthenticationStatusCode.UnknownCredentials,
- SecurityStatusPalErrorCode.CannotPack => NegotiateAuthenticationStatusCode.InvalidToken,
- SecurityStatusPalErrorCode.TimeSkew => NegotiateAuthenticationStatusCode.InvalidToken,
- SecurityStatusPalErrorCode.AlgorithmMismatch => NegotiateAuthenticationStatusCode.InvalidToken,
- SecurityStatusPalErrorCode.CertUnknown => NegotiateAuthenticationStatusCode.UnknownCredentials,
-
- // Processing partial inputs is not supported, so this is result of incorrect input
- SecurityStatusPalErrorCode.IncompleteMessage => NegotiateAuthenticationStatusCode.InvalidToken,
-
- _ => NegotiateAuthenticationStatusCode.GenericFailure,
- };
+ byte[]? blob = _pal.GetOutgoingBlob(incomingBlob, out statusCode);
// Additional policy validation
if (statusCode == NegotiateAuthenticationStatusCode.Completed)
@@ -414,12 +289,12 @@ public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel
/// Authentication failed or has not occurred.
public NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferWriter outputWriter, bool requestEncryption, out bool isEncrypted)
{
- if (!IsAuthenticated || _ntAuthentication == null)
+ if (!IsAuthenticated || _isDisposed)
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
- return _ntAuthentication.Wrap(input, outputWriter, requestEncryption, out isEncrypted);
+ return _pal.Wrap(input, outputWriter, requestEncryption, out isEncrypted);
}
///
@@ -441,12 +316,12 @@ public NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan input, IBufferW
/// Authentication failed or has not occurred.
public NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBufferWriter outputWriter, out bool wasEncrypted)
{
- if (!IsAuthenticated || _ntAuthentication == null)
+ if (!IsAuthenticated || _isDisposed)
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
- return _ntAuthentication.Unwrap(input, outputWriter, out wasEncrypted);
+ return _pal.Unwrap(input, outputWriter, out wasEncrypted);
}
///
@@ -469,12 +344,12 @@ public NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan input, IBuffe
/// Authentication failed or has not occurred.
public NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted)
{
- if (!IsAuthenticated || _ntAuthentication == null)
+ if (!IsAuthenticated || _isDisposed)
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
- return _ntAuthentication.UnwrapInPlace(input, out unwrappedOffset, out unwrappedLength, out wasEncrypted);
+ return _pal.UnwrapInPlace(input, out unwrappedOffset, out unwrappedLength, out wasEncrypted);
}
///
@@ -490,12 +365,12 @@ public NegotiateAuthenticationStatusCode UnwrapInPlace(Span input, out int
///
internal void GetMIC(ReadOnlySpan message, IBufferWriter signature)
{
- if (!IsAuthenticated || _ntAuthentication == null)
+ if (!IsAuthenticated || _isDisposed)
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
- _ntAuthentication.GetMIC(message, signature);
+ _pal.GetMIC(message, signature);
}
///
@@ -512,20 +387,19 @@ internal void GetMIC(ReadOnlySpan message, IBufferWriter signature)
///
internal bool VerifyMIC(ReadOnlySpan message, ReadOnlySpan signature)
{
- if (!IsAuthenticated || _ntAuthentication == null)
+ if (!IsAuthenticated || _isDisposed)
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
- return _ntAuthentication.VerifyMIC(message, signature);
+ return _pal.VerifyMIC(message, signature);
}
private bool CheckSpn()
{
- Debug.Assert(_ntAuthentication != null);
Debug.Assert(_extendedProtectionPolicy != null);
- if (_ntAuthentication.IsKerberos)
+ if (_pal.Package == NegotiationInfoClass.Kerberos)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_kerberos);
return true;
@@ -549,7 +423,7 @@ private bool CheckSpn()
return true;
}
- string? clientSpn = _ntAuthentication.ClientSpecifiedSpn;
+ string? clientSpn = _pal.TargetName;
if (string.IsNullOrEmpty(clientSpn))
{
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs
index 0704b390bd21f..b1c5c7dc7903c 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs
@@ -25,7 +25,7 @@ public enum NegotiateAuthenticationStatusCode
BadBinding,
/// Unsupported authentication package was requested.
- /// Maps to GSS_S_BAD_MECH status in GSSAPI.
+ /// Maps to GSS_S_BAD_MECH or GSS_S_UNAVAILABLE status in GSSAPI.
Unsupported,
/// Message was altered and failed an integrity check validation.
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs
index 19022bdfa33d9..f26a837e0c110 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs
@@ -640,7 +640,13 @@ private void ValidateCreateContext(
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);
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Managed.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Managed.cs
deleted file mode 100644
index 9734bdf441ed6..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Managed.cs
+++ /dev/null
@@ -1,33 +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.ComponentModel;
-using System.Runtime.Versioning;
-using System.Security.Principal;
-
-namespace System.Net.Security
-{
- //
- // The class maintains the state of the authentication process and the security context.
- // It encapsulates security context and does the real work in authentication and
- // user data encryption with NEGO SSPI package.
- //
- internal static partial class NegotiateStreamPal
- {
-#pragma warning disable IDE0060
- internal static IIdentity GetIdentity(NTAuthentication context)
- {
- throw new PlatformNotSupportedException();
- }
-
- internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel)
- {
- if (impersonationLevel != TokenImpersonationLevel.Identification)
- {
- throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(),
- SR.net_auth_supported_impl_levels);
- }
- }
-#pragma warning restore IDE0060
- }
-}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs
deleted file mode 100644
index 421e1a41902eb..0000000000000
--- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs
+++ /dev/null
@@ -1,676 +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.IO;
-using System.Buffers;
-using System.Buffers.Binary;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Security.Authentication;
-using System.Security.Authentication.ExtendedProtection;
-using System.Security.Principal;
-using System.Text;
-using System.Threading;
-using Microsoft.Win32.SafeHandles;
-
-namespace System.Net.Security
-{
- //
- // The class maintains the state of the authentication process and the security context.
- // It encapsulates security context and does the real work in authentication and
- // user data encryption with NEGO SSPI package.
- //
- internal static partial class NegotiateStreamPal
- {
- // value should match the Windows sspicli NTE_FAIL value
- // defined in winerror.h
- private const int NTE_FAIL = unchecked((int)0x80090020);
-
- internal static string QueryContextClientSpecifiedSpn(SafeDeleteContext _ /*securityContext*/)
- {
- throw new PlatformNotSupportedException(SR.net_nego_server_not_supported);
- }
-
- internal static string QueryContextAuthenticationPackage(SafeDeleteContext securityContext)
- {
- SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext;
- return negoContext.IsNtlmUsed ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Kerberos;
- }
-
- private static byte[] GssWrap(
- SafeGssContextHandle context,
- ref bool encrypt,
- ReadOnlySpan buffer)
- {
- Interop.NetSecurityNative.GssBuffer encryptedBuffer = default;
- try
- {
- Interop.NetSecurityNative.Status minorStatus;
- Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.WrapBuffer(out minorStatus, context, ref encrypt, buffer, ref encryptedBuffer);
- if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
- {
- throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
- }
-
- return encryptedBuffer.ToByteArray();
- }
- finally
- {
- encryptedBuffer.Dispose();
- }
- }
-
- private static int GssUnwrap(
- SafeGssContextHandle context,
- out bool encrypt,
- Span buffer)
- {
- Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer);
- try
- {
- Interop.NetSecurityNative.Status minorStatus;
- Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, context, out encrypt, buffer, ref decryptedBuffer);
- if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
- {
- throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
- }
-
- decryptedBuffer.Span.CopyTo(buffer);
- return decryptedBuffer.Span.Length;
- }
- finally
- {
- decryptedBuffer.Dispose();
- }
- }
-
- private static string GssGetUser(
- ref SafeGssContextHandle? context)
- {
- Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
-
- try
- {
- Interop.NetSecurityNative.Status status
- = Interop.NetSecurityNative.GetUser(out var minorStatus,
- context,
- ref token);
-
- if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
- {
- throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
- }
-
- ReadOnlySpan