public async Task UseClientCertOnHttp2_DowngradedToHttp1MutualAuth_Success() { using X509Certificate2 clientCert = Test.Common.Configuration.Certificates.GetClientCertificate(); await LoopbackServer.CreateClientAndServerAsync( async address => { var handler = new WinHttpHandler(); handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; handler.ClientCertificates.Add(clientCert); handler.ClientCertificateOption = ClientCertificateOption.Manual; using (var client = new HttpClient(handler)) using (HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, address) { Version = HttpVersion20.Value })) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(_validationCallbackHistory.WasCalled); Assert.NotEmpty(_validationCallbackHistory.CertificateChain); Assert.Equal(Test.Common.Configuration.Certificates.GetServerCertificate(), _validationCallbackHistory.CertificateChain[0]); } }, async s => { await using (LoopbackServer.Connection connection = await s.EstablishConnectionAsync().ConfigureAwait(false)) { SslStream sslStream = connection.Stream as SslStream; Assert.NotNull(sslStream); Assert.True(sslStream.IsMutuallyAuthenticated); Assert.Equal(clientCert, sslStream.RemoteCertificate); await connection.ReadRequestHeaderAndSendResponseAsync(HttpStatusCode.OK); } }, new LoopbackServer.Options { UseSsl = true }); }
public static async Task <bool> WebSocketHandshakeAsync(LoopbackServer.Connection connection) { string serverResponse = null; string currentRequestLine; while (!string.IsNullOrEmpty(currentRequestLine = await connection.Reader.ReadLineAsync().ConfigureAwait(false))) { string[] tokens = currentRequestLine.Split(new char[] { ':' }, 2); if (tokens.Length == 2) { string headerName = tokens[0]; if (headerName == "Sec-WebSocket-Key") { string headerValue = tokens[1].Trim(); string responseSecurityAcceptValue = ComputeWebSocketHandshakeSecurityAcceptValue(headerValue); serverResponse = "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + responseSecurityAcceptValue + "\r\n\r\n"; } } } if (serverResponse != null) { // We received a valid WebSocket opening handshake. Send the appropriate response. await connection.Writer.WriteAsync(serverResponse).ConfigureAwait(false); return(true); } return(false); }
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 Task HandleNegotiateAuthenticationRequest(LoopbackServer.Connection connection, bool closeConnection = true) { return(HandleAuthenticationRequest(connection, useNtlm: false, useNegotiate: true, closeConnection)); }
public static async Task <Dictionary <string, string> > WebSocketHandshakeAsync(LoopbackServer.Connection connection) { string serverResponse = null; List <string> headers = await connection.ReadRequestHeaderAsync().ConfigureAwait(false); var results = new Dictionary <string, string>(); foreach (string header in headers) { string[] tokens = header.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length == 2) { results.Add(tokens[0].Trim(), tokens[1].Trim()); string headerName = tokens[0]; if (headerName == "Sec-WebSocket-Key") { string headerValue = tokens[1].Trim(); string responseSecurityAcceptValue = ComputeWebSocketHandshakeSecurityAcceptValue(headerValue); serverResponse = "HTTP/1.1 101 Switching Protocols\r\n" + "Content-Length: 0\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + responseSecurityAcceptValue + "\r\n\r\n"; } } } if (serverResponse != null) { // We received a valid WebSocket opening handshake. Send the appropriate response. await connection.Writer.WriteAsync(serverResponse).ConfigureAwait(false); return(results); } return(null); }
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"); } }