public async Task Credentials_BrokenNtlmFromServer()
        {
            if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
            {
                return;
            }

            await LoopbackServer.CreateClientAndServerAsync(
                async uri =>
            {
                using (HttpClientHandler handler = CreateHttpClientHandler())
                    using (HttpClient client = CreateHttpClient(handler))
                    {
                        handler.Credentials = new NetworkCredential("username", "password");
                        var response        = await client.GetAsync(uri);
                        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
                    }
            },
                async server =>
            {
                var responseHeader          = new HttpHeaderData[] { new HttpHeaderData("WWW-Authenticate", "NTLM") };
                HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.Unauthorized, responseHeader);
                Assert.Equal(0, requestData.GetHeaderValueCount("Authorization"));

                // Establish a session connection
                await using LoopbackServer.Connection connection = await server.EstablishConnectionAsync();
                requestData            = await connection.ReadRequestDataAsync();
                string authHeaderValue = requestData.GetSingleHeaderValue("Authorization");
                Assert.Contains("NTLM", authHeaderValue);
                _output.WriteLine(authHeaderValue);

                // Incorrect NTLMv1 challenge from server (generated by Cyrus HTTP)
                responseHeader = new HttpHeaderData[] {
                    new HttpHeaderData("WWW-Authenticate", "NTLM TlRMTVNTUAACAAAAHAAcADAAAACV/wIAUwCrhitz1vsAAAAAAAAAAAAAAAAAAAAASgAuAEUATQBDAEwASQBFAE4AVAAuAEMATwBNAA=="),
                    new HttpHeaderData("Connection", "keep-alive")
                };
                await connection.SendResponseAsync(HttpStatusCode.Unauthorized, responseHeader);
                connection.CompleteRequestProcessing();

                // Wait for the client to close the connection
                try
                {
                    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(1000);
                    await connection.WaitForCloseAsync(cancellationTokenSource.Token);
                }
                catch (OperationCanceledException)
                {
                    // On Linux the GSSAPI NTLM provider may try to continue with the authentication, so go along with it
                    requestData     = await connection.ReadRequestDataAsync();
                    authHeaderValue = requestData.GetSingleHeaderValue("Authorization");
                    Assert.Contains("NTLM", authHeaderValue);
                    _output.WriteLine(authHeaderValue);
                    await connection.SendResponseAsync(HttpStatusCode.Unauthorized);
                    connection.CompleteRequestProcessing();
                }
            });
        }
Example #2
0
        internal static async Task HandleAuthenticationRequestWithFakeServer(LoopbackServer.Connection connection, bool useNtlm)
        {
            HttpRequestData request = await connection.ReadRequestDataAsync();

            FakeNtlmServer?     fakeNtlmServer      = null;
            FakeNegotiateServer?fakeNegotiateServer = 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.
                if (useNtlm)
                {
                    authHeader = "WWW-Authenticate: NTLM\r\n";
                }
                else
                {
                    authHeader = "WWW-Authenticate: Negotiate\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();
            }

            bool isAuthenticated = false;

            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);

                if (fakeNtlmServer == null)
                {
                    fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight)
                    {
                        ForceNegotiateVersion = true
                    };
                    if (!useNtlm)
                    {
                        fakeNegotiateServer = new FakeNegotiateServer(fakeNtlmServer);
                    }
                }

                byte[]? outBlob;

                if (fakeNegotiateServer != null)
                {
                    outBlob         = fakeNegotiateServer.GetOutgoingBlob(Convert.FromBase64String(tokens[1]));
                    isAuthenticated = fakeNegotiateServer.IsAuthenticated;
                }
                else
                {
                    outBlob         = fakeNtlmServer.GetOutgoingBlob(Convert.FromBase64String(tokens[1]));
                    isAuthenticated = fakeNtlmServer.IsAuthenticated;
                }

                if (outBlob != null)
                {
                    authHeader = $"WWW-Authenticate: {tokens[0]} {Convert.ToBase64String(outBlob)}\r\n";
                    await connection.SendResponseAsync(isAuthenticated?HttpStatusCode.OK : HttpStatusCode.Unauthorized, authHeader);

                    connection.CompleteRequestProcessing();

                    if (!isAuthenticated)
                    {
                        request = await connection.ReadRequestDataAsync();
                    }
                }
            }while (!isAuthenticated);

            await connection.SendResponseAsync(HttpStatusCode.OK);
        }
Example #3
0
        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");
            }
        }
Example #4
0
        internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection connection, bool useNtlm, bool useNegotiate, bool closeConnection)
        {
            HttpRequestData request = await connection.ReadRequestDataAsync();

            NegotiateAuthentication 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();
            }

            NegotiateAuthenticationStatusCode 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 NegotiateAuthentication(new NegotiateAuthenticationServerOptions {
                    Package = tokens[0]
                });

                byte[]? outBlob = authContext.GetOutgoingBlob(Convert.FromBase64String(tokens[1]), out statusCode);

                if (outBlob != null && statusCode == NegotiateAuthenticationStatusCode.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 == NegotiateAuthenticationStatusCode.ContinueNeeded);

            if (statusCode == NegotiateAuthenticationStatusCode.Completed)
            {
                // If authentication succeeded ask Windows about the identity and send it back as custom header.
                IIdentity identity = authContext.RemoteIdentity;

                authHeader = $"{UserHeaderName}: {identity.Name}\r\n";
                if (closeConnection)
                {
                    authHeader += "Connection: close\r\n";
                }

                await connection.SendResponseAsync(HttpStatusCode.OK, authHeader, "foo");

                authContext.Dispose();
            }
            else
            {
                await connection.SendResponseAsync(HttpStatusCode.Forbidden, "Connection: close\r\n", "boo");
            }
        }