private void DoNtlmExchange(FakeNtlmServer fakeNtlmServer, NTAuthentication ntAuth) { byte[]? negotiateBlob = ntAuth.GetOutgoingBlob(null, throwOnError: false); Assert.NotNull(negotiateBlob); byte[]? challengeBlob = fakeNtlmServer.GetOutgoingBlob(negotiateBlob); Assert.NotNull(challengeBlob); byte[]? authenticateBlob = ntAuth.GetOutgoingBlob(challengeBlob, throwOnError: false); Assert.NotNull(authenticateBlob); byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob); Assert.Null(empty); }
private unsafe byte[] GetOutgoingBlob(byte[] incomingBlob, ref Exception e) { SecurityStatusPal statusCode; byte[] message = _context.GetOutgoingBlob(incomingBlob, false, out statusCode); if (IsError(statusCode)) { e = NegotiateStreamPal.CreateExceptionFromError(statusCode); uint error = (uint)e.HResult; message = new byte[sizeof(long)]; for (int i = message.Length - 1; i >= 0; --i) { message[i] = (byte)(error & 0xFF); error = (error >> 8); } } if (message != null && message.Length == 0) { message = s_emptyMessage; } return(message); }
private static async Task <HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken) { HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false); if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response)) { // Proxy didn't indicate that it supports connection-based auth, so we can't proceed. if (NetEventSource.IsEnabled) { NetEventSource.Error(connection, $"Proxy doesn't support connection-based auth, uri={authUri}"); } return(response); } if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { string challengeData = challenge.ChallengeData; string spn = "HTTP/" + authUri.IdnHost; ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer: false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); try { while (true) { string challengeResponse = authContext.GetOutgoingBlob(challengeData); if (challengeResponse == null) { // Response indicated denial even after login, so stop processing and return current response. break; } await connection.DrainResponseAsync(response).ConfigureAwait(false); SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false); if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) { break; } } } finally { authContext.CloseContext(); } } } return(response); }
private static async Task <HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken) { HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false); if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge) && (!isProxyAuth || CheckIfProxySupportsConnectionAuth(response))) { if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { string challengeData = challenge.ChallengeData; string spn = "HTTP/" + authUri.IdnHost; ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); try { while (true) { string challengeResponse = authContext.GetOutgoingBlob(challengeData); if (authContext.IsCompleted) { break; } await connection.DrainResponseAsync(response).ConfigureAwait(false); SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false); if (!TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) { break; } } } finally { authContext.CloseContext(); } } } return(response); }
private unsafe byte[] GetOutgoingBlob(byte[] incomingBlob, ref Win32Exception e) { Interop.SecurityStatus statusCode; byte[] message = _context.GetOutgoingBlob(incomingBlob, false, out statusCode); if (((int)statusCode & unchecked ((int)0x80000000)) != 0) { e = new System.ComponentModel.Win32Exception((int)statusCode); message = new byte[8]; //sizeof(long) for (int i = message.Length - 1; i >= 0; --i) { message[i] = (byte)((uint)statusCode & 0xFF); statusCode = (Interop.SecurityStatus)((uint)statusCode >> 8); } } if (message != null && message.Length == 0) { message = s_emptyMessage; } return(message); }
private static async Task <HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, bool async, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { HttpResponseMessage response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response)) { // Proxy didn't indicate that it supports connection-based auth, so we can't proceed. if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(connection, $"Proxy doesn't support connection-based auth, uri={authUri}"); } return(response); } if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { bool isNewConnection = false; bool needDrain = true; try { if (response.Headers.ConnectionClose.GetValueOrDefault()) { // Server is closing the connection and asking us to authenticate on a new connection. // First, detach the current connection from the pool. This means it will no longer count against the connection limit. // Instead, it will be replaced by the new connection below. connection.DetachFromPool(); connection = await connectionPool.CreateHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); connection !.Acquire(); isNewConnection = true; needDrain = false; } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Uri: {authUri.AbsoluteUri}"); } // Calculate SPN (Service Principal Name) using the host name of the request. // Use the request's 'Host' header if available. Otherwise, use the request uri. // Ignore the 'Host' header if this is proxy authentication since we need to use // the host name of the proxy itself for SPN calculation. string hostName; if (!isProxyAuth && request.HasHeaders && request.Headers.Host != null) { // Use the host name without any normalization. hostName = request.Headers.Host; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {hostName}"); } } else { // Need to use FQDN normalized host so that CNAME's are traversed. // Use DNS to do the forward lookup to an A (host) record. // But skip DNS lookup on IP literals. Otherwise, we would end up // doing an unintended reverse DNS lookup. UriHostNameType hnt = authUri.HostNameType; if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) { hostName = authUri.IdnHost; } else { IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost, cancellationToken).ConfigureAwait(false); hostName = result.HostName; } if (!isProxyAuth && !authUri.IsDefaultPort && UsePortInSpn) { hostName = string.Create(null, stackalloc char[128], $"{hostName}:{authUri.Port}"); } } string spn = "HTTP/" + hostName; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, SPN: {spn}"); } ChannelBinding? channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer: false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity, channelBinding); string? challengeData = challenge.ChallengeData; try { while (true) { string?challengeResponse = authContext.GetOutgoingBlob(challengeData); if (challengeResponse == null) { // Response indicated denial even after login, so stop processing and return current response. break; } if (needDrain) { await connection.DrainResponseAsync(response !, cancellationToken).ConfigureAwait(false); } SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) { break; } needDrain = true; } } finally { authContext.CloseContext(); } } finally { if (isNewConnection) { connection !.Release(); } } } } return(response !); }
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"); } }
private static async Task <HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response)) { // Proxy didn't indicate that it supports connection-based auth, so we can't proceed. if (NetEventSource.IsEnabled) { NetEventSource.Error(connection, $"Proxy doesn't support connection-based auth, uri={authUri}"); } return(response); } if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { bool isNewConnection = false; bool needDrain = true; try { if (response.Headers.ConnectionClose.GetValueOrDefault()) { // Server is closing the connection and asking us to authenticate on a new connection. (connection, response) = await connectionPool.CreateHttp11ConnectionAsync(request, cancellationToken).ConfigureAwait(false); if (response != null) { return(response); } connectionPool.IncrementConnectionCount(); connection.Acquire(); isNewConnection = true; needDrain = false; } string challengeData = challenge.ChallengeData; // Need to use FQDN normalized host so that CNAME's are traversed. // Use DNS to do the forward lookup to an A (host) record. // But skip DNS lookup on IP literals. Otherwise, we would end up // doing an unintended reverse DNS lookup. string spn; UriHostNameType hnt = authUri.HostNameType; if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) { spn = authUri.IdnHost; } else { IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost).ConfigureAwait(false); spn = result.HostName; } spn = "HTTP/" + spn; if (NetEventSource.IsEnabled) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {authUri.IdnHost}, SPN: {spn}"); } ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer: false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); try { while (true) { string challengeResponse = authContext.GetOutgoingBlob(challengeData); if (challengeResponse == null) { // Response indicated denial even after login, so stop processing and return current response. break; } if (needDrain) { await connection.DrainResponseAsync(response).ConfigureAwait(false); } SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) { break; } needDrain = true; } } finally { authContext.CloseContext(); } } finally { if (isNewConnection) { connection.Release(); } } } } return(response); }