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