[release/6.0] fix SendAsync from impersonificated context with default credentials...
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Wed, 15 Sep 2021 17:45:55 +0000 (10:45 -0700)
committerGitHub <noreply@github.com>
Wed, 15 Sep 2021 17:45:55 +0000 (10:45 -0700)
* fix SendAsync from inpersonificated context and default credentials

* add missing file

* remove dead code

* feedback from review

* name cleanup

Co-authored-by: wfurt <tweinfurt@yahoo.com>
src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
src/libraries/Common/tests/TestUtilities/System/WindowsIdentityFixture.cs [new file with mode: 0644]
src/libraries/Common/tests/TestUtilities/TestUtilities.csproj
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs
src/libraries/System.Net.Http/tests/FunctionalTests/ImpersonatedAuthTests.cs [new file with mode: 0644]
src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs [new file with mode: 0644]
src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.cs
src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj
src/libraries/System.Security.Principal.Windows/tests/WindowsIdentityImpersonatedTests.netcoreapp.cs

index 7594b49..ff4b346 100644 (file)
@@ -866,6 +866,12 @@ namespace System.Net.Test.Common
                 return buffer;
             }
 
+            public void CompleteRequestProcessing()
+            {
+                _contentLength = 0;
+                _bodyRead = false;
+            }
+
             public override async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
             {
                 MemoryStream headerBytes = new MemoryStream();
diff --git a/src/libraries/Common/tests/TestUtilities/System/WindowsIdentityFixture.cs b/src/libraries/Common/tests/TestUtilities/System/WindowsIdentityFixture.cs
new file mode 100644 (file)
index 0000000..360fbe6
--- /dev/null
@@ -0,0 +1,141 @@
+// 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.ComponentModel;
+using System.Linq;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Principal;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+
+namespace System
+{
+    public class WindowsIdentityFixture : IDisposable
+    {
+        public WindowsTestAccount TestAccount { get; private set; }
+
+        public WindowsIdentityFixture()
+        {
+            TestAccount = new WindowsTestAccount("CorFxTstWiIde01kiu");
+        }
+
+        public void Dispose()
+        {
+            TestAccount.Dispose();
+        }
+    }
+
+    public sealed class WindowsTestAccount : IDisposable
+    {
+        private readonly string _userName;
+        private SafeAccessTokenHandle _accountTokenHandle;
+        public SafeAccessTokenHandle AccountTokenHandle => _accountTokenHandle;
+        public string AccountName { get; private set; }
+
+        public WindowsTestAccount(string userName)
+        {
+            _userName = userName;
+            CreateUser();
+        }
+
+        private void CreateUser()
+        {
+            string testAccountPassword;
+            using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
+            {
+                byte[] randomBytes = new byte[33];
+                rng.GetBytes(randomBytes);
+
+                // Add special chars to ensure it satisfies password requirements.
+                testAccountPassword = Convert.ToBase64String(randomBytes) + "_-As@!%*(1)4#2";
+
+                USER_INFO_1 userInfo = new USER_INFO_1
+                {
+                    usri1_name = _userName,
+                    usri1_password = testAccountPassword,
+                    usri1_priv = 1
+                };
+
+                // Create user and remove/create if already exists
+                uint result = NetUserAdd(null, 1, ref userInfo, out uint param_err);
+
+                // error codes https://docs.microsoft.com/en-us/windows/desktop/netmgmt/network-management-error-codes
+                // 0 == NERR_Success
+                if (result == 2224) // NERR_UserExists
+                {
+                    result = NetUserDel(null, userInfo.usri1_name);
+                    if (result != 0)
+                    {
+                        throw new Win32Exception((int)result);
+                    }
+                    result = NetUserAdd(null, 1, ref userInfo, out param_err);
+                    if (result != 0)
+                    {
+                        throw new Win32Exception((int)result);
+                    }
+                }
+
+                const int LOGON32_PROVIDER_DEFAULT = 0;
+                const int LOGON32_LOGON_INTERACTIVE = 2;
+
+                if (!LogonUser(_userName, ".", testAccountPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out _accountTokenHandle))
+                {
+                    _accountTokenHandle = null;
+                    throw new Exception($"Failed to get SafeAccessTokenHandle for test account {_userName}", new Win32Exception());
+                }
+
+                bool gotRef = false;
+                try
+                {
+                    _accountTokenHandle.DangerousAddRef(ref gotRef);
+                    IntPtr logonToken = _accountTokenHandle.DangerousGetHandle();
+                    AccountName = new WindowsIdentity(logonToken).Name;
+                }
+                finally
+                {
+                    if (gotRef)
+                        _accountTokenHandle.DangerousRelease();
+                }
+            }
+        }
+
+        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        private static extern bool LogonUser(string userName, string domain, string password, int logonType, int logonProvider, out SafeAccessTokenHandle safeAccessTokenHandle);
+
+        [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+        internal static extern uint NetUserAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, uint level, ref USER_INFO_1 buf, out uint parm_err);
+
+        [DllImport("netapi32.dll")]
+        internal static extern uint NetUserDel([MarshalAs(UnmanagedType.LPWStr)]string servername, [MarshalAs(UnmanagedType.LPWStr)]string username);
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        internal struct USER_INFO_1
+        {
+            public string usri1_name;
+            public string usri1_password;
+            public uint usri1_password_age;
+            public uint usri1_priv;
+            public string usri1_home_dir;
+            public string usri1_comment;
+            public uint usri1_flags;
+            public string usri1_script_path;
+        }
+
+        public void Dispose()
+        {
+            _accountTokenHandle?.Dispose();
+
+            uint result = NetUserDel(null, _userName);
+
+            // 2221= NERR_UserNotFound
+            if (result != 0 && result != 2221)
+            {
+                throw new Win32Exception((int)result);
+            }
+        }
+    }
+ }
+
index 375fca4..adf4da7 100644 (file)
@@ -33,6 +33,7 @@
     <Compile Include="System\PlatformDetection.cs" />
     <Compile Include="System\PlatformDetection.Unix.cs" />
     <Compile Include="System\PlatformDetection.Windows.cs" />
+    <Compile Include="System\WindowsIdentityFixture.cs" />
     <!--
       Interop.Library is not designed to support runtime checks therefore we are picking the Windows
       variant from the Common folder and adding the missing members manually.
index 410acc1..64476aa 100644 (file)
@@ -75,8 +75,6 @@ namespace System.Net.Http
                 allowHttp3 && allowHttp2 ? HttpVersion.Version30 :
                 allowHttp2 ? HttpVersion.Version20 :
                 HttpVersion.Version11;
-            _defaultCredentialsUsedForProxy = _proxy != null && (_proxy.Credentials == CredentialCache.DefaultCredentials || _defaultProxyCredentials == CredentialCache.DefaultCredentials);
-            _defaultCredentialsUsedForServer = _credentials == CredentialCache.DefaultCredentials;
         }
 
         /// <summary>Creates a copy of the settings but with some values normalized to suit the implementation.</summary>
@@ -96,8 +94,6 @@ namespace System.Net.Http
                 _connectTimeout = _connectTimeout,
                 _credentials = _credentials,
                 _defaultProxyCredentials = _defaultProxyCredentials,
-                _defaultCredentialsUsedForProxy = _defaultCredentialsUsedForProxy,
-                _defaultCredentialsUsedForServer = _defaultCredentialsUsedForServer,
                 _expect100ContinueTimeout = _expect100ContinueTimeout,
                 _maxAutomaticRedirections = _maxAutomaticRedirections,
                 _maxConnectionsPerServer = _maxConnectionsPerServer,
@@ -123,6 +119,8 @@ namespace System.Net.Http
                 _plaintextStreamFilter = _plaintextStreamFilter,
                 _initialHttp2StreamWindowSize = _initialHttp2StreamWindowSize,
                 _activityHeadersPropagator = _activityHeadersPropagator,
+                _defaultCredentialsUsedForProxy = _proxy != null && (_proxy.Credentials == CredentialCache.DefaultCredentials || _defaultProxyCredentials == CredentialCache.DefaultCredentials),
+                _defaultCredentialsUsedForServer = _credentials == CredentialCache.DefaultCredentials,
             };
 
             // TODO: Remove if/when QuicImplementationProvider is removed from System.Net.Quic.
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/ImpersonatedAuthTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/ImpersonatedAuthTests.cs
new file mode 100644 (file)
index 0000000..6b56d35
--- /dev/null
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using System.Net.Security;
+using System.Net.Test.Common;
+using System.Security.Principal;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+    public class ImpersonatedAuthTests: IClassFixture<WindowsIdentityFixture>
+    {
+        public static bool CanRunImpersonatedTests = PlatformDetection.IsWindows && PlatformDetection.IsNotWindowsNanoServer;
+        private readonly WindowsIdentityFixture _fixture;
+        private readonly ITestOutputHelper _output;
+
+        public  ImpersonatedAuthTests(WindowsIdentityFixture windowsIdentityFixture, ITestOutputHelper output)
+        {
+            _output = output;
+            _fixture = windowsIdentityFixture;
+
+            Assert.False(_fixture.TestAccount.AccountTokenHandle.IsInvalid);
+            Assert.False(string.IsNullOrEmpty(_fixture.TestAccount.AccountName));
+        }
+
+        [OuterLoop]
+        [ConditionalTheory(nameof(CanRunImpersonatedTests))]
+        [InlineData(true)]
+        [InlineData(false)]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public async Task DefaultHandler_ImpersonificatedUser_Success(bool useNtlm)
+        {
+            await LoopbackServer.CreateClientAndServerAsync(
+                async uri =>
+                {
+                    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
+                    requestMessage.Version = new Version(1, 1);
+
+                    var handler = new HttpClientHandler();
+                    handler.UseDefaultCredentials = true;
+
+                    using (var client = new HttpClient(handler))
+                    {
+                        HttpResponseMessage response = await client.SendAsync(requestMessage);
+                        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+                        Assert.Equal("foo", await response.Content.ReadAsStringAsync());
+
+                        string initialUser = response.Headers.GetValues(NtAuthTests.UserHeaderName).First();
+
+                        _output.WriteLine($"Starting test as {WindowsIdentity.GetCurrent().Name}");
+
+                        // get token and run another request as different user.
+                        WindowsIdentity.RunImpersonated(_fixture.TestAccount.AccountTokenHandle, () =>
+                        {
+                            _output.WriteLine($"Running test as {WindowsIdentity.GetCurrent().Name}");
+                            Assert.Equal(_fixture.TestAccount.AccountName, WindowsIdentity.GetCurrent().Name);
+
+                            requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
+                            requestMessage.Version = new Version(1, 1);
+
+                            HttpResponseMessage response = client.SendAsync(requestMessage).GetAwaiter().GetResult();
+                            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+                            Assert.Equal("foo", response.Content.ReadAsStringAsync().GetAwaiter().GetResult());
+
+                            string newUser = response.Headers.GetValues(NtAuthTests.UserHeaderName).First();
+                            Assert.Equal(_fixture.TestAccount.AccountName, newUser);
+                        });
+                    }
+                },
+                async server =>
+                {
+                    await server.AcceptConnectionAsync(async connection =>
+                    {
+                        Task t = useNtlm ? NtAuthTests.HandleNtlmAuthenticationRequest(connection, closeConnection: false) : NtAuthTests.HandleNegotiateAuthenticationRequest(connection, closeConnection: false);
+                        await t;
+                        _output.WriteLine("Finished first request");
+
+                        // Second request should use new connection as it runs as different user.
+                        // We keep first connection open so HttpClient may be tempted top use it.
+                        await server.AcceptConnectionAsync(async connection =>
+                        {
+                            Task t = useNtlm ? NtAuthTests.HandleNtlmAuthenticationRequest(connection, closeConnection: false) : NtAuthTests.HandleNegotiateAuthenticationRequest(connection, closeConnection: false);
+                            await t;
+                        }).ConfigureAwait(false);
+                    }).ConfigureAwait(false);
+                });
+
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs
new file mode 100644 (file)
index 0000000..8bb4df5
--- /dev/null
@@ -0,0 +1,153 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using System.Net.Security;
+using System.Net.Test.Common;
+using System.Security.Principal;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+    public partial class NtAuthTests : IClassFixture<NtAuthServers>
+    {
+        internal const string NtlmAuthHeader = "WWW-Authenticate: NTLM";
+        internal const string NegotiateAuthHeader = "WWW-Authenticate: Negotiate";
+        internal const string UserHeaderName = "X-User";
+
+        internal static Task HandleNtlmAuthenticationRequest(LoopbackServer.Connection connection, bool closeConnection = true)
+        {
+            return HandleAuthenticationRequest(connection, useNtlm: true, useNegotiate: false, closeConnection);
+        }
+
+        internal static Task HandleNegotiateAuthenticationRequest(LoopbackServer.Connection connection, bool closeConnection = true)
+        {
+            return HandleAuthenticationRequest(connection, useNtlm: false, useNegotiate: true, closeConnection);
+        }
+
+        internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection connection, bool useNtlm, bool useNegotiate, bool closeConnection)
+        {
+            HttpRequestData request = await connection.ReadRequestDataAsync();
+            NTAuthentication authContext = null;
+            string authHeader = null;
+
+            foreach (HttpHeaderData header in request.Headers)
+            {
+                if (header.Name == "Authorization")
+                {
+                    authHeader = header.Value;
+                    break;
+                }
+            }
+
+            if (string.IsNullOrEmpty(authHeader))
+            {
+                // This is initial request, we reject with showing supported mechanisms.
+                authHeader = string.Empty;
+                if (useNtlm)
+                {
+                    authHeader += NtlmAuthHeader + "\r\n";
+                }
+
+                if (useNegotiate)
+                {
+                    authHeader += NegotiateAuthHeader + "\r\n";
+                }
+
+                await connection.SendResponseAsync(HttpStatusCode.Unauthorized, authHeader).ConfigureAwait(false);
+                connection.CompleteRequestProcessing();
+
+                // Read next requests and fall-back to loop bellow to process it.
+                request = await connection.ReadRequestDataAsync();
+            }
+
+            SecurityStatusPal statusCode;
+            do
+            {
+                foreach (HttpHeaderData header in request.Headers)
+                {
+                    if (header.Name == "Authorization")
+                    {
+                        authHeader = header.Value;
+                        break;
+                    }
+                }
+
+                Assert.NotNull(authHeader);
+                var tokens = authHeader.Split(' ', 2, StringSplitOptions.TrimEntries);
+                // Should be type and base64 encoded blob
+                Assert.Equal(2, tokens.Length);
+
+                authContext ??= new NTAuthentication(isServer: true, tokens[0], CredentialCache.DefaultNetworkCredentials, null, ContextFlagsPal.Connection, null);
+
+                byte[]? outBlob = authContext.GetOutgoingBlob(Convert.FromBase64String(tokens[1]), throwOnError: false, out statusCode);
+
+                if (outBlob != null && statusCode.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded)
+                {
+                    authHeader = $"WWW-Authenticate: {tokens[0]} {Convert.ToBase64String(outBlob)}\r\n";
+                    await connection.SendResponseAsync(HttpStatusCode.Unauthorized, authHeader);
+                    connection.CompleteRequestProcessing();
+
+                    request = await connection.ReadRequestDataAsync();
+                }
+            }
+            while (statusCode.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded);
+
+            if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK)
+            {
+                // If authentication succeeded ask Windows about the identity and send it back as custom header.
+                SecurityContextTokenHandle? userContext = null;
+                using SafeDeleteContext securityContext = authContext.GetContext(out SecurityStatusPal statusCodeNew)!;
+                SSPIWrapper.QuerySecurityContextToken(GlobalSSPI.SSPIAuth, securityContext, out userContext);
+                using WindowsIdentity identity = new WindowsIdentity(userContext.DangerousGetHandle(), authContext.ProtocolName);
+
+                authHeader = $"{UserHeaderName}: {identity.Name}\r\n";
+                if (closeConnection)
+                {
+                    authHeader += "Connection: close\r\n";
+                }
+
+                await connection.SendResponseAsync(HttpStatusCode.OK, authHeader, "foo");
+                userContext.Dispose();
+            }
+            else
+            {
+                await connection.SendResponseAsync(HttpStatusCode.Forbidden, "Connection: close\r\n", "boo");
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+        [InlineData(true)]
+        [InlineData(false)]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public async Task DefaultHandler_DefaultCredentials_Success(bool useNtlm)
+        {
+            await LoopbackServer.CreateClientAndServerAsync(
+                async uri =>
+                {
+                    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
+                    requestMessage.Version = new Version(1, 1);
+
+                    var handler = new HttpClientHandler() { UseDefaultCredentials = true };
+                    using (var client = new HttpClient(handler))
+                    {
+                        HttpResponseMessage response = await client.SendAsync(requestMessage);
+                        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+                        _output.WriteLine($"Authenticated as {response.Headers.GetValues(NtAuthTests.UserHeaderName).First()}");
+                        Assert.Equal("foo", await response.Content.ReadAsStringAsync());
+                    }
+                },
+                async server =>
+                {
+                    await server.AcceptConnectionAsync(async connection =>
+                    {
+                        Task t = useNtlm ? HandleNtlmAuthenticationRequest(connection) : HandleNegotiateAuthenticationRequest(connection);
+                        await t;
+                    }).ConfigureAwait(false);
+                });
+        }
+    }
+}
index 096c5af..5c5954f 100644 (file)
@@ -104,7 +104,7 @@ namespace System.Net.Http.Functional.Tests
         }
     }
 
-    public class NtAuthTests : IClassFixture<NtAuthServers>
+    public partial class NtAuthTests : IClassFixture<NtAuthServers>
     {
         private readonly NtAuthServers _servers;
         private readonly ITestOutputHelper _output;
index 5c17759..718551f 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <StringResourcesPath>../../src/Resources/Strings.resx</StringResourcesPath>
     <DefineConstants Condition="'$(TargetsWindows)'=='true'">$(DefineConstants);TargetsWindows</DefineConstants>
@@ -33,8 +33,8 @@
   <ItemGroup>
     <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true'"
              Link="Common\Interop\Unix\Interop.Libraries.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\NetEventSource.Common.cs"
-             Link="Common\System\Net\Http\aspnetcore\NetEventSource.Common.cs" />
+    <Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs"
+             Link="Common\System\Net\Logging\NetEventSource.Common.cs" />
     <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
              Link="System\System\Threading\Tasks\TaskToApm.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true'"
   <ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
     <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"
              Link="Common\Interop\Windows\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
+             Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs"
+             Link="Common\Interop\Windows\Interop.BOOL.cs" />
+    <Compile Include="$(CommonPath)System\Net\DebugSafeHandle.cs"
+             Link="Common\System\Net\DebugSafeHandle.cs" />
+    <Compile Include="$(CommonPath)System\Net\InternalException.cs"
+             Link="Common\System\Net\InternalException.cs" />
+    <Compile Include="$(CommonPath)System\Net\ExceptionCheck.cs"
+             Link="Common\System\Net\ExceptionCheck.cs" />
+    <!-- Add NTAuthentication -->
+    <Compile Include="$(CommonPath)System\Net\DebugCriticalHandleMinusOneIsInvalid.cs"
+             Link="Common\System\Net\DebugCriticalHandleMinusOneIsInvalid.cs" />
+    <Compile Include="$(CommonPath)System\Net\DebugCriticalHandleZeroOrMinusOneIsInvalid.cs"
+             Link="Common\System\Net\DebugCriticalHandleZeroOrMinusOneIsInvalid.cs" />
+    <Compile Include="$(CommonPath)System\Collections\Generic\BidirectionalDictionary.cs"
+             Link="Common\System\Collections\Generic\BidirectionalDictionary.cs" />
+    <Compile Include="$(CommonPath)System\NotImplemented.cs"
+             Link="Common\System\NotImplemented.cs" />
+    <Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs"
+             Link="Common\System\Net\ContextFlagsPal.cs" />
+    <Compile Include="$(CommonPath)System\Net\NegotiationInfoClass.cs"
+             Link="Common\System\Net\NegotiationInfoClass.cs" />
+    <Compile Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
+             Link="Common\System\Net\NTAuthentication.Common.cs" />
+    <Compile Include="$(CommonPath)System\Net\SecurityStatusPal.cs"
+             Link="Common\System\Net\SecurityStatusPal.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\SecurityBuffer.Windows.cs"
+             Link="Common\System\Net\Security\SecurityBuffer.Windows.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\SecurityBufferType.Windows.cs"
+             Link="Common\System\Net\Security\SecurityBufferType.Windows.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\SafeCredentialReference.cs"
+             Link="Common\System\Net\Security\SafeCredentialReference.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\SSPIHandleCache.cs"
+             Link="Common\System\Net\Security\SSPIHandleCache.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\NetEventSource.Security.cs"
+             Link="Common\System\Net\Security\NetEventSource.Security.cs" />
+    <!-- Windows specific NTAuthentication -->
+    <Compile Include="$(CommonPath)System\Net\Security\SecurityContextTokenHandle.cs"
+             Link="Common\System\Net\Security\SecurityContextTokenHandle.cs" />
+    <Compile Include="$(CommonPath)System\Net\SecurityStatusAdapterPal.Windows.cs"
+             Link="Common\System\Net\SecurityStatusAdapterPal.Windows.cs" />
+    <Compile Include="$(CommonPath)System\Net\ContextFlagsAdapterPal.Windows.cs"
+             Link="Common\System\Net\ContextFlagsAdapterPal.Windows.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Windows.cs"
+             Link="Common\System\Net\Security\NegotiateStreamPal.Windows.cs" />
+    <Compile Include="$(CommonPath)System\Net\Security\NetEventSource.Security.Windows.cs"
+             Link="Common\System\Net\Security\NetEventSource.Security.Windows.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.Interop.CertFreeCertificateContext.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_INFO.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.CERT_INFO.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_BIT_BLOB.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs"
+             Link="Common\Interop\Windows\Crypt32\Interop.Interop.MsgEncodingType.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_Bindings.cs"
+             Link="Common\Interop\Windows\SspiCli\SecPkgContext_Bindings.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs"
+             Link="Common\Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CloseHandle.cs"
+             Link="Common\Interop\Windows\Kernel32\Interop.CloseHandle.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_StreamSizes.cs"
+             Link="Common\Interop\Windows\SspiCli\SecPkgContext_StreamSizes.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_NegotiationInfoW.cs"
+             Link="Common\Interop\Windows\SspiCli\SecPkgContext_NegotiationInfoW.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\NegotiationInfoClass.cs"
+             Link="Common\Interop\Windows\SspiCli\NegotiationInfoClass.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SChannel\SecPkgContext_ConnectionInfo.cs"
+             Link="Common\Interop\Windows\SChannel\SecPkgContext_ConnectionInfo.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SChannel\SecPkgContext_CipherInfo.cs"
+             Link="Common\Interop\Windows\SChannel\SecPkgContext_CipherInfo.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPISecureChannelType.cs"
+             Link="Common\Interop\Windows\SspiCli\SSPISecureChannelType.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\ISSPIInterface.cs"
+             Link="Common\Interop\Windows\SspiCli\ISSPIInterface.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPIAuthType.cs"
+             Link="Common\Interop\Windows\SspiCli\SSPIAuthType.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecurityPackageInfoClass.cs"
+             Link="Common\Interop\Windows\SspiCli\SecurityPackageInfoClass.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecurityPackageInfo.cs"
+             Link="Common\Interop\Windows\SspiCli\SecurityPackageInfo.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_Sizes.cs"
+             Link="Common\Interop\Windows\SspiCli\SecPkgContext_Sizes.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SafeDeleteContext.cs"
+         Link="Common\Interop\Windows\SspiCli\SafeDeleteContext.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\GlobalSSPI.cs"
+             Link="Common\Interop\Windows\SspiCli\GlobalSSPI.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\Interop.SSPI.cs"
+             Link="Common\Interop\Windows\SspiCli\Interop.SSPI.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecuritySafeHandles.cs"
+             Link="Common\Interop\Windows\SspiCli\SecuritySafeHandles.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPIWrapper.cs"
+             Link="Common\Interop\Windows\SspiCli\SSPIWrapper.cs" />
+    <Compile Include="NtAuthTests.Windows.cs" />
+    <Compile Include="ImpersonatedAuthTests.cs" />
   </ItemGroup>
   <!-- Linux specific files -->
   <ItemGroup Condition="'$(TargetsLinux)' == 'true' or '$(TargetsBrowser)' == 'true'">
index 868afcc..4bfb059 100644 (file)
@@ -84,6 +84,9 @@ public class WindowsIdentityImpersonatedTests : IClassFixture<WindowsIdentityFix
     {
         WindowsIdentity currentWindowsIdentity = WindowsIdentity.GetCurrent();
 
+        // make sure the assembly is loaded.
+        _ = Dns.GetHostAddresses("");
+
         await WindowsIdentity.RunImpersonatedAsync(_fixture.TestAccount.AccountTokenHandle, async () =>
         {
             Assert.Equal(_fixture.TestAccount.AccountName, WindowsIdentity.GetCurrent().Name);
@@ -96,129 +99,3 @@ public class WindowsIdentityImpersonatedTests : IClassFixture<WindowsIdentityFix
         });
     }
 }
-
-public class WindowsIdentityFixture : IDisposable
-{
-    public WindowsTestAccount TestAccount { get; private set; }
-
-    public WindowsIdentityFixture()
-    {
-        TestAccount = new WindowsTestAccount("CorFxTstWiIde01kiu");
-    }
-
-    public void Dispose()
-    {
-        TestAccount.Dispose();
-    }
-}
-
-public sealed class WindowsTestAccount : IDisposable
-{
-    private readonly string _userName;
-    private SafeAccessTokenHandle _accountTokenHandle;
-    public SafeAccessTokenHandle AccountTokenHandle => _accountTokenHandle;
-    public string AccountName { get; private set; }
-
-    public WindowsTestAccount(string userName)
-    {
-        _userName = userName;
-        CreateUser();
-    }
-
-    private void CreateUser()
-    {
-        string testAccountPassword;
-        using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
-        {
-            byte[] randomBytes = new byte[33];
-            rng.GetBytes(randomBytes);
-
-            // Add special chars to ensure it satisfies password requirements.
-            testAccountPassword = Convert.ToBase64String(randomBytes) + "_-As@!%*(1)4#2";
-
-            USER_INFO_1 userInfo = new USER_INFO_1
-            {
-                usri1_name = _userName,
-                usri1_password = testAccountPassword,
-                usri1_priv = 1
-            };
-
-            // Create user and remove/create if already exists
-            uint result = NetUserAdd(null, 1, ref userInfo, out uint param_err);
-
-            // error codes https://docs.microsoft.com/en-us/windows/desktop/netmgmt/network-management-error-codes
-            // 0 == NERR_Success
-            if (result == 2224) // NERR_UserExists
-            {
-                result = NetUserDel(null, userInfo.usri1_name);
-                if (result != 0)
-                {
-                    throw new Win32Exception((int)result);
-                }
-                result = NetUserAdd(null, 1, ref userInfo, out param_err);
-                if (result != 0)
-                {
-                    throw new Win32Exception((int)result);
-                }
-            }
-
-            const int LOGON32_PROVIDER_DEFAULT = 0;
-            const int LOGON32_LOGON_INTERACTIVE = 2;
-
-            if (!LogonUser(_userName, ".", testAccountPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out _accountTokenHandle))
-            {
-                _accountTokenHandle = null;
-                throw new Exception($"Failed to get SafeAccessTokenHandle for test account {_userName}", new Win32Exception());
-            }
-
-            bool gotRef = false;
-            try
-            {
-                _accountTokenHandle.DangerousAddRef(ref gotRef);
-                IntPtr logonToken = _accountTokenHandle.DangerousGetHandle();
-                AccountName = new WindowsIdentity(logonToken).Name;
-            }
-            finally
-            {
-                if (gotRef)
-                    _accountTokenHandle.DangerousRelease();
-            }
-        }
-    }
-
-    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
-    private static extern bool LogonUser(string userName, string domain, string password, int logonType, int logonProvider, out SafeAccessTokenHandle safeAccessTokenHandle);
-
-    [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
-    internal static extern uint NetUserAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, uint level, ref USER_INFO_1 buf, out uint parm_err);
-
-    [DllImport("netapi32.dll")]
-    internal static extern uint NetUserDel([MarshalAs(UnmanagedType.LPWStr)]string servername, [MarshalAs(UnmanagedType.LPWStr)]string username);
-
-    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
-    internal struct USER_INFO_1
-    {
-        public string usri1_name;
-        public string usri1_password;
-        public uint usri1_password_age;
-        public uint usri1_priv;
-        public string usri1_home_dir;
-        public string usri1_comment;
-        public uint usri1_flags;
-        public string usri1_script_path;
-    }
-
-    public void Dispose()
-    {
-        _accountTokenHandle?.Dispose();
-
-        uint result = NetUserDel(null, _userName);
-
-        // 2221= NERR_UserNotFound
-        if (result != 0 && result != 2221)
-        {
-            throw new Win32Exception((int)result);
-        }
-    }
-}
-