Example #1
0
        internal static IIdentity GetIdentity(NTAuthentication context)
        {
            Debug.Assert(!context.IsServer, "GetIdentity: Server is not supported");

            string name = context.Spn;
            string protocol = context.ProtocolName;

            return new GenericIdentity(name, protocol);

        }
        internal static IIdentity GetIdentity(NTAuthentication context)
        {
            IIdentity result = null;
            string name = context.IsServer ? context.AssociatedName : context.Spn;
            string protocol = context.ProtocolName;

            if (context.IsServer)
            {
                SecurityContextTokenHandle token = null;
                try
                {
                    SecurityStatusPal status;
                    SafeDeleteContext securityContext = context.GetContext(out status);
                    if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
                    {
                        throw new Win32Exception((int)SecurityStatusAdapterPal.GetInteropFromSecurityStatusPal(status));
                    }

                    // This will return a client token when conducted authentication on server side.
                    // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app.
                    Interop.SecurityStatus winStatus = (Interop.SecurityStatus)SSPIWrapper.QuerySecurityContextToken(
                        GlobalSSPI.SSPIAuth,
                        securityContext,
                        out token);
                    if (winStatus != Interop.SecurityStatus.OK)
                    {
                        throw new Win32Exception((int)winStatus);
                    }
                    string authtype = context.ProtocolName;

                    // TODO #5241:
                    // The following call was also specifying WindowsAccountType.Normal, true.
                    // WindowsIdentity.IsAuthenticated is no longer supported in CoreFX.
                    result = new WindowsIdentity(token.DangerousGetHandle(), authtype);
                    return result;
                }
                catch (SecurityException)
                {
                    // Ignore and construct generic Identity if failed due to security problem.
                }
                finally
                {
                    if (token != null)
                    {
                        token.Dispose();
                    }
                }
            }

            // On the client we don't have access to the remote side identity.
            result = new GenericIdentity(name, protocol);
            return result;
        }
        public Authorization Authenticate(string challenge, NetworkCredential credential, object sessionCookie, string spn, ChannelBinding channelBindingToken)
        {
            if (NetEventSource.IsEnabled) NetEventSource.Enter(this, "Authenticate");
            try
            {
                lock (_sessions)
                {
                    NTAuthentication clientContext;
                    if (!_sessions.TryGetValue(sessionCookie, out clientContext))
                    {
                        if (credential == null)
                        {
                            return null;
                        }

                        _sessions[sessionCookie] =
                            clientContext =
                            new NTAuthentication(false, "Ntlm", credential, spn, ContextFlagsPal.Connection, channelBindingToken);

                    }

                    string resp = clientContext.GetOutgoingBlob(challenge);

                    if (!clientContext.IsCompleted)
                    {
                        return new Authorization(resp, false);
                    }
                    else
                    {
                        _sessions.Remove(sessionCookie);
                        return new Authorization(resp, true);
                    }
                }
            }
            // From reflected type NTAuthentication in System.Net.Security.
            catch (NullReferenceException)
            {
                return null;
            }
            finally
            {
                if (NetEventSource.IsEnabled) NetEventSource.Exit(this, "Authenticate");
            }
        }
        internal void ValidateCreateContext(
            string package,
            bool isServer,
            NetworkCredential credential,
            string servicePrincipalName,
            ChannelBinding channelBinding,
            ProtectionLevel protectionLevel,
            TokenImpersonationLevel impersonationLevel)
        {
            if (_exception != null && !_canRetryAuthentication)
            {
                throw _exception;
            }

            if (_context != null && _context.IsValidContext)
            {
                throw new InvalidOperationException(SR.net_auth_reauth);
            }

            if (credential == null)
            {
                throw new ArgumentNullException(nameof(credential));
            }

            if (servicePrincipalName == null)
            {
                throw new ArgumentNullException(nameof(servicePrincipalName));
            }

            if (impersonationLevel != TokenImpersonationLevel.Identification &&
                impersonationLevel != TokenImpersonationLevel.Impersonation &&
                impersonationLevel != TokenImpersonationLevel.Delegation)
            {
                throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_auth_supported_impl_levels);
            }

            if (_context != null && IsServer != isServer)
            {
                throw new InvalidOperationException(SR.net_auth_client_server);
            }

            _exception = null;
            _remoteOk = false;
            _framer = new StreamFramer(_innerStream);
            _framer.WriteHeader.MessageId = FrameHeader.HandshakeId;

            _expectedProtectionLevel = protectionLevel;
            _expectedImpersonationLevel = isServer ? impersonationLevel : TokenImpersonationLevel.None;
            _writeSequenceNumber = 0;
            _readSequenceNumber = 0;

            Interop.SspiCli.ContextFlags flags = Interop.SspiCli.ContextFlags.Connection;

            // A workaround for the client when talking to Win9x on the server side.
            if (protectionLevel == ProtectionLevel.None && !isServer)
            {
                package = NegotiationInfoClass.NTLM;
            }

            else if (protectionLevel == ProtectionLevel.EncryptAndSign)
            {
                flags |= Interop.SspiCli.ContextFlags.Confidentiality;
            }
            else if (protectionLevel == ProtectionLevel.Sign)
            {
                // Assuming user expects NT4 SP4 and above.
                flags |= Interop.SspiCli.ContextFlags.ReplayDetect | Interop.SspiCli.ContextFlags.SequenceDetect | Interop.SspiCli.ContextFlags.InitIntegrity;
            }

            if (isServer)
            {
                if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
                {
                    flags |= Interop.SspiCli.ContextFlags.AllowMissingBindings;
                }

                if (_extendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never &&
                    _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy)
                {
                    flags |= Interop.SspiCli.ContextFlags.ProxyBindings;
                }
            }
            else
            {
                // Server side should not request any of these flags.
                if (protectionLevel != ProtectionLevel.None)
                {
                    flags |= Interop.SspiCli.ContextFlags.MutualAuth;
                }

                if (impersonationLevel == TokenImpersonationLevel.Identification)
                {
                    flags |= Interop.SspiCli.ContextFlags.InitIdentify;
                }

                if (impersonationLevel == TokenImpersonationLevel.Delegation)
                {
                    flags |= Interop.SspiCli.ContextFlags.Delegate;
                }
            }

            _canRetryAuthentication = false;

            try
            {
                _context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding);
            }
            catch (Win32Exception e)
            {
                throw new AuthenticationException(SR.net_auth_SSPI, e);
            }
        }
        public Authorization Authenticate(string challenge, NetworkCredential credential, object sessionCookie, string spn, ChannelBinding channelBindingToken)
        {
            if (NetEventSource.IsEnabled) NetEventSource.Enter(this, "Authenticate");
            try
            {
                lock (_sessions)
                {
                    NTAuthentication clientContext;
                    if (!_sessions.TryGetValue(sessionCookie, out clientContext))
                    {
                        if (credential == null)
                        {
                            return null;
                        }

                        _sessions[sessionCookie] =
                            clientContext =
                            new NTAuthentication(false, "Negotiate", credential, spn,
                                                 ContextFlags.Connection | ContextFlags.InitIntegrity, channelBindingToken);
                    }

                    byte[] byteResp;
                    string resp = null;

                    if (!clientContext.IsCompleted)
                    {

                        // If auth is not yet completed keep producing
                        // challenge responses with GetOutgoingBlob

                        byte[] decodedChallenge = null;
                        if (challenge != null)
                        {
                            decodedChallenge =
                                Convert.FromBase64String(challenge);
                        }
                        byteResp = clientContext.GetOutgoingBlob(decodedChallenge, false);
                        if (clientContext.IsCompleted && byteResp == null)
                        {
                            resp = "\r\n";
                        }
                        if (byteResp != null)
                        {
                            resp = Convert.ToBase64String(byteResp);
                        }
                    }
                    else
                    {
                        // If auth completed and still have a challenge then
                        // server may be doing "correct" form of GSSAPI SASL.
                        // Validate incoming and produce outgoing SASL security 
                        // layer negotiate message.

                        resp = GetSecurityLayerOutgoingBlob(challenge, clientContext);
                    }

                    return new Authorization(resp, clientContext.IsCompleted);
                }
            }
            // From reflected type NTAuthentication in System.Net.Security.
            catch (NullReferenceException)
            {
                return null;
            }
            finally
            {
                if (NetEventSource.IsEnabled) NetEventSource.Exit(this, "Authenticate");
            }
        }
        // Function for SASL security layer negotiation after
        // authorization completes.   
        //
        // Returns null for failure, Base64 encoded string on
        // success.
        private string GetSecurityLayerOutgoingBlob(string challenge, NTAuthentication clientContext)
        {
            // must have a security layer challenge

            if (challenge == null)
                return null;

            // "unwrap" challenge

            byte[] input = Convert.FromBase64String(challenge);

            int len;

            try
            {
                len = clientContext.VerifySignature(input, 0, input.Length);
            }
            catch (Win32Exception)
            {
                // any decrypt failure is an auth failure
                return null;
            }

            // Per RFC 2222 Section 7.2.2:
            //   the client should then expect the server to issue a 
            //   token in a subsequent challenge.  The client passes
            //   this token to GSS_Unwrap and interprets the first 
            //   octet of cleartext as a bit-mask specifying the 
            //   security layers supported by the server and the 
            //   second through fourth octets as the maximum size 
            //   output_message to send to the server.   
            // Section 7.2.3
            //   The security layer and their corresponding bit-masks
            //   are as follows:
            //     1 No security layer
            //     2 Integrity protection
            //       Sender calls GSS_Wrap with conf_flag set to FALSE
            //     4 Privacy protection
            //       Sender calls GSS_Wrap with conf_flag set to TRUE
            //
            // Exchange 2007 and our client only support 
            // "No security layer". Therefore verify first byte is value 1
            // and the 2nd-4th bytes are value zero since token size is not
            // applicable when there is no security layer.

            if (len < 4 ||          // expect 4 bytes
                input[0] != 1 ||    // first value 1
                input[1] != 0 ||    // rest value 0
                input[2] != 0 ||
                input[3] != 0)
            {
                return null;
            }

            // Continuing with RFC 2222 section 7.2.2:
            //   The client then constructs data, with the first octet 
            //   containing the bit-mask specifying the selected security
            //   layer, the second through fourth octets containing in 
            //   network byte order the maximum size output_message the client
            //   is able to receive, and the remaining octets containing the
            //   authorization identity.  
            // 
            // So now this contructs the "wrapped" response.  The response is
            // payload is identical to the received server payload and the 
            // "authorization identity" is not supplied as it is unnecessary.

            // let MakeSignature figure out length of output
            byte[] output = null;
            try
            {
                len = clientContext.MakeSignature(input, 0, 4, ref output);
            }
            catch (Win32Exception)
            {
                // any encrypt failure is an auth failure
                return null;
            }

            // return Base64 encoded string of signed payload
            return Convert.ToBase64String(output, 0, len);
        }
Example #7
0
        internal void ValidateCreateContext(
            string package,
            bool isServer,
            NetworkCredential credential,
            string servicePrincipalName,
            ChannelBinding channelBinding,
            ProtectionLevel protectionLevel,
            TokenImpersonationLevel impersonationLevel)
        {
            if (_exception != null && !_canRetryAuthentication)
            {
                ExceptionDispatchInfo.Throw(_exception);
            }

            if (_context != null && _context.IsValidContext)
            {
                throw new InvalidOperationException(SR.net_auth_reauth);
            }

            if (credential == null)
            {
                throw new ArgumentNullException(nameof(credential));
            }

            if (servicePrincipalName == null)
            {
                throw new ArgumentNullException(nameof(servicePrincipalName));
            }

            NegotiateStreamPal.ValidateImpersonationLevel(impersonationLevel);
            if (_context != null && IsServer != isServer)
            {
                throw new InvalidOperationException(SR.net_auth_client_server);
            }

            _exception = null;
            _remoteOk  = false;
            _framer    = new StreamFramer(_innerStream);
            _framer.WriteHeader.MessageId = FrameHeader.HandshakeId;

            _expectedProtectionLevel    = protectionLevel;
            _expectedImpersonationLevel = isServer ? impersonationLevel : TokenImpersonationLevel.None;
            _writeSequenceNumber        = 0;
            _readSequenceNumber         = 0;

            ContextFlagsPal flags = ContextFlagsPal.Connection;

            // A workaround for the client when talking to Win9x on the server side.
            if (protectionLevel == ProtectionLevel.None && !isServer)
            {
                package = NegotiationInfoClass.NTLM;
            }
            else if (protectionLevel == ProtectionLevel.EncryptAndSign)
            {
                flags |= ContextFlagsPal.Confidentiality;
            }
            else if (protectionLevel == ProtectionLevel.Sign)
            {
                // Assuming user expects NT4 SP4 and above.
                flags |= (ContextFlagsPal.ReplayDetect | ContextFlagsPal.SequenceDetect | ContextFlagsPal.InitIntegrity);
            }

            if (isServer)
            {
                if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
                {
                    flags |= ContextFlagsPal.AllowMissingBindings;
                }

                if (_extendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never &&
                    _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy)
                {
                    flags |= ContextFlagsPal.ProxyBindings;
                }
            }
            else
            {
                // Server side should not request any of these flags.
                if (protectionLevel != ProtectionLevel.None)
                {
                    flags |= ContextFlagsPal.MutualAuth;
                }

                if (impersonationLevel == TokenImpersonationLevel.Identification)
                {
                    flags |= ContextFlagsPal.InitIdentify;
                }

                if (impersonationLevel == TokenImpersonationLevel.Delegation)
                {
                    flags |= ContextFlagsPal.Delegate;
                }
            }

            _canRetryAuthentication = false;

            try
            {
                _context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding);
            }
            catch (Win32Exception e)
            {
                throw new AuthenticationException(SR.net_auth_SSPI, e);
            }
        }
Example #8
0
        public Authorization Authenticate(string challenge, NetworkCredential credential, object sessionCookie, string spn, ChannelBinding channelBindingToken)
        {
            if (Logging.On)
            {
                Logging.Enter(Logging.Web, this, "Authenticate", null);
            }
            try {
                lock (this.sessions)
                {
                    NTAuthentication clientContext = this.sessions[sessionCookie] as NTAuthentication;

                    if (clientContext == null)
                    {
                        if (credential == null)
                        {
                            return(null);
                        }

                        this.sessions[sessionCookie] =
                            clientContext            =
                                new NTAuthentication(false, "Negotiate", credential, spn,
                                                     ContextFlags.Connection | ContextFlags.InitIntegrity, channelBindingToken);
                    }
                    byte[] byteResp;
                    string resp = null;

                    if (!clientContext.IsCompleted)
                    {
                        // If auth is not yet completed keep producing
                        // challenge responses with GetOutgoingBlob

                        SecurityStatus statusCode;
                        byte[]         decodedChallenge = null;
                        if (challenge != null)
                        {
                            decodedChallenge =
                                Convert.FromBase64String(challenge);
                        }
                        byteResp = clientContext.GetOutgoingBlob(
                            decodedChallenge,
                            false,
                            out statusCode);
                        // Note sure why this is here...keeping it.
                        if (clientContext.IsCompleted && byteResp == null)
                        {
                            resp = "\r\n";
                        }
                        if (byteResp != null)
                        {
                            resp = Convert.ToBase64String(byteResp);
                        }
                    }
                    else
                    {
                        // If auth completed and still have a challenge then
                        // server may be doing "correct" form of GSSAPI SASL.
                        // Validate incoming and produce outgoing SASL security
                        // layer negotiate message.

                        resp = GetSecurityLayerOutgoingBlob(
                            challenge,
                            clientContext);
                    }

                    return(new Authorization(resp, clientContext.IsCompleted));
                }
            }
            finally {
                if (Logging.On)
                {
                    Logging.Exit(Logging.Web, this, "Authenticate", null);
                }
            }
        }
Example #9
0
        // Function for SASL security layer negotiation after
        // authorization completes.
        //
        // Returns null for failure, Base64 encoded string on
        // success.
        private string GetSecurityLayerOutgoingBlob(
            string challenge,
            NTAuthentication clientContext)
        {
            // must have a security layer challenge

            if (challenge == null)
            {
                return(null);
            }

            // "unwrap" challenge

            byte[] input = Convert.FromBase64String(challenge);

            int len;

            try {
                len = clientContext.VerifySignature(input, 0, input.Length);
            }
            catch (Win32Exception) {
                // any decrypt failure is an auth failure
                return(null);
            }

            // Per RFC 2222 Section 7.2.2:
            //   the client should then expect the server to issue a
            //   token in a subsequent challenge.  The client passes
            //   this token to GSS_Unwrap and interprets the first
            //   octet of cleartext as a bit-mask specifying the
            //   security layers supported by the server and the
            //   second through fourth octets as the maximum size
            //   output_message to send to the server.
            // Section 7.2.3
            //   The security layer and their corresponding bit-masks
            //   are as follows:
            //     1 No security layer
            //     2 Integrity protection
            //       Sender calls GSS_Wrap with conf_flag set to FALSE
            //     4 Privacy protection
            //       Sender calls GSS_Wrap with conf_flag set to TRUE
            //
            // Exchange 2007 and our client only support
            // "No security layer". Therefore verify first byte is value 1
            // and the 2nd-4th bytes are value zero since token size is not
            // applicable when there is no security layer.

            if (len < 4 ||          // expect 4 bytes
                input[0] != 1 ||    // first value 1
                input[1] != 0 ||    // rest value 0
                input[2] != 0 ||
                input[3] != 0)
            {
                return(null);
            }

            // Continuing with RFC 2222 section 7.2.2:
            //   The client then constructs data, with the first octet
            //   containing the bit-mask specifying the selected security
            //   layer, the second through fourth octets containing in
            //   network byte order the maximum size output_message the client
            //   is able to receive, and the remaining octets containing the
            //   authorization identity.
            //
            // So now this contructs the "wrapped" response.  The response is
            // payload is identical to the received server payload and the
            // "authorization identity" is not supplied as it is unnecessary.

            // let MakeSignature figure out length of output
            byte[] output = null;
            try {
                len = clientContext.MakeSignature(input, 0, 4, ref output);
            }
            catch (Win32Exception) {
                // any decrypt failure is an auth failure
                return(null);
            }

            // return Base64 encoded string of signed payload
            return(Convert.ToBase64String(output, 0, len));
        }
Example #10
0
        //
        internal void ValidateCreateContext(
            string package,
            bool isServer,
            NetworkCredential credential,
            string servicePrincipalName,
            ChannelBinding channelBinding,
            ProtectionLevel protectionLevel,
            TokenImpersonationLevel impersonationLevel
            )
        {
            if (_Exception != null && !_CanRetryAuthentication)
            {
                throw _Exception;
            }

            if (_Context != null && _Context.IsValidContext)
            {
                throw new InvalidOperationException(SR.GetString(SR.net_auth_reauth));
            }

            if (credential == null)
            {
                throw new ArgumentNullException("credential");
            }

            if (servicePrincipalName == null)
            {
                throw new ArgumentNullException("servicePrincipalName");
            }

            if (impersonationLevel != TokenImpersonationLevel.Identification &&
                impersonationLevel != TokenImpersonationLevel.Impersonation &&
                impersonationLevel != TokenImpersonationLevel.Delegation)
            {
                throw new ArgumentOutOfRangeException("impersonationLevel", impersonationLevel.ToString(), SR.GetString(SR.net_auth_supported_impl_levels));
            }

            if (_Context != null && IsServer != isServer)
            {
                throw new InvalidOperationException(SR.GetString(SR.net_auth_client_server));
            }

            _Exception = null;
            _RemoteOk  = false;
            _Framer    = new StreamFramer(_InnerStream);
            _Framer.WriteHeader.MessageId = FrameHeader.HandshakeId;

            _ExpectedProtectionLevel    = protectionLevel;
            _ExpectedImpersonationLevel = isServer? impersonationLevel: TokenImpersonationLevel.None;
            _WriteSequenceNumber        = 0;
            _ReadSequenceNumber         = 0;

            ContextFlags flags = ContextFlags.Connection;

            // A workaround for the client when talking to Win9x on the server side
            if (protectionLevel == ProtectionLevel.None && !isServer)
            {
                package = NegotiationInfoClass.NTLM;
            }

            else if (protectionLevel == ProtectionLevel.EncryptAndSign)
            {
                flags |= ContextFlags.Confidentiality;
            }
            else if (protectionLevel == ProtectionLevel.Sign)
            {
                // Assuming user expects NT4 SP4 and above
                flags |= ContextFlags.ReplayDetect | ContextFlags.SequenceDetect | ContextFlags.InitIntegrity;
            }

            if (isServer)
            {
                if (_ExtendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
                {
                    flags |= ContextFlags.AllowMissingBindings;
                }
                if (_ExtendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never &&
                    _ExtendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy)
                {
                    flags |= ContextFlags.ProxyBindings;
                }
            }
            else
            {
                // According to lzhu server side should not request any of these flags
                if (protectionLevel != ProtectionLevel.None)
                {
                    flags |= ContextFlags.MutualAuth;
                }
                if (impersonationLevel == TokenImpersonationLevel.Identification)
                {
                    flags |= ContextFlags.InitIdentify;
                }
                if (impersonationLevel == TokenImpersonationLevel.Delegation)
                {
                    flags |= ContextFlags.Delegate;
                }
            }


            _CanRetryAuthentication = false;

            //
            // Security: We used to rely on NetworkCredential class to demand permission
            //           Switched over to explicit ControlPrincipalPermission demand (except for DefaultCredential case)
            //           The mitigated attack is brute-force pasword guessing through SSPI.
            if (!(credential is SystemNetworkCredential))
            {
                ExceptionHelper.ControlPrincipalPermission.Demand();
            }

            try {
                //
                _Context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding);
            }
            catch (Win32Exception e)
            {
                throw new AuthenticationException(SR.GetString(SR.net_auth_SSPI), e);
            }
        }
Example #11
0
        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 !);
        }