public async void Invalid_Token()
 {
     using var kerberosExecutor = new KerberosExecutor(_testOutputHelper, "LINUX.CONTOSO.COM");
     // Force a non-empty keytab to make macOS happy
     kerberosExecutor.AddService("HTTP/linux.contoso.com");
     await kerberosExecutor.Invoke(() =>
     {
         NegotiateAuthentication ntAuth = new NegotiateAuthentication(new NegotiateAuthenticationServerOptions {
         });
         // Ask for NegHints
         byte[] blob = ntAuth.GetOutgoingBlob((ReadOnlySpan <byte>) default, out NegotiateAuthenticationStatusCode statusCode);
Пример #2
0
        public Authorization?Authenticate(string?challenge, NetworkCredential?credential, object sessionCookie, string?spn, ChannelBinding?channelBindingToken)
        {
            lock (_sessions)
            {
                NegotiateAuthentication?clientContext;
                if (!_sessions.TryGetValue(sessionCookie, out clientContext))
                {
                    if (credential == null)
                    {
                        return(null);
                    }

                    _sessions[sessionCookie] = clientContext =
                        new NegotiateAuthentication(
                            new NegotiateAuthenticationClientOptions
                    {
                        Credential = credential,
                        TargetName = spn,
                        Binding    = channelBindingToken
                    });
                }

                NegotiateAuthenticationStatusCode statusCode;
                string?resp = clientContext.GetOutgoingBlob(challenge, out statusCode);

                if (statusCode != NegotiateAuthenticationStatusCode.Completed &&
                    statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded)
                {
                    return(null);
                }

                if (!clientContext.IsAuthenticated)
                {
                    return(new Authorization(resp, false));
                }
                else
                {
                    _sessions.Remove(sessionCookie);
                    clientContext.Dispose();
                    return(new Authorization(resp, true));
                }
            }
        }
Пример #3
0
        public Authorization?Authenticate(string?challenge, NetworkCredential?credential, object sessionCookie, string?spn, ChannelBinding?channelBindingToken)
        {
            lock (_sessions)
            {
                NegotiateAuthentication?clientContext;
                if (!_sessions.TryGetValue(sessionCookie, out clientContext))
                {
                    if (credential == null)
                    {
                        return(null);
                    }

                    ProtectionLevel protectionLevel = ProtectionLevel.Sign;
                    // Workaround for https://github.com/gssapi/gss-ntlmssp/issues/77
                    // GSSAPI NTLM SSP does not support gss_wrap/gss_unwrap unless confidentiality
                    // is negotiated.
                    if (OperatingSystem.IsLinux())
                    {
                        protectionLevel = ProtectionLevel.EncryptAndSign;
                    }

                    _sessions[sessionCookie] = clientContext =
                        new NegotiateAuthentication(
                            new NegotiateAuthenticationClientOptions
                    {
                        Credential = credential,
                        TargetName = spn,
                        RequiredProtectionLevel = protectionLevel,
                        Binding = channelBindingToken
                    });
                }

                string?resp = null;
                NegotiateAuthenticationStatusCode statusCode;

                if (!clientContext.IsAuthenticated)
                {
                    // If auth is not yet completed keep producing
                    // challenge responses with GetOutgoingBlob
                    resp = clientContext.GetOutgoingBlob(challenge, out statusCode);
                    if (statusCode != NegotiateAuthenticationStatusCode.Completed &&
                        statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded)
                    {
                        return(null);
                    }
                    if (clientContext.IsAuthenticated && resp == null)
                    {
                        resp = "\r\n";
                    }
                }
                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.IsAuthenticated));
            }
        }
Пример #4
0
        // Function for SASL security layer negotiation after
        // authorization completes.
        //
        // Returns null for failure, Base64 encoded string on
        // success.
        private static string?GetSecurityLayerOutgoingBlob(string?challenge, NegotiateAuthentication clientContext)
        {
            // must have a security layer challenge

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

            // "unwrap" challenge

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

            Span <byte> unwrappedChallenge;
            NegotiateAuthenticationStatusCode statusCode;

            statusCode = clientContext.UnwrapInPlace(input, out int newOffset, out int newLength, out _);
            if (statusCode != NegotiateAuthenticationStatusCode.Completed)
            {
                return(null);
            }
            unwrappedChallenge = input.AsSpan(newOffset, newLength);

            // 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". We verify that the server offers
            // option to use no security layer and negotiate that if
            // possible.

            if (unwrappedChallenge.Length != 4 || (unwrappedChallenge[0] & 1) != 1)
            {
                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 constructs the "wrapped" response.

            // let MakeSignature figure out length of output
            ArrayBufferWriter <byte> outputWriter = new ArrayBufferWriter <byte>();

            statusCode = clientContext.Wrap(_saslNoSecurtyLayerToken, outputWriter, false, out _);
            if (statusCode != NegotiateAuthenticationStatusCode.Completed)
            {
                return(null);
            }

            // return Base64 encoded string of signed payload
            return(Convert.ToBase64String(outputWriter.WrittenSpan));
        }
Пример #5
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}");
                        }

                        ProtectionLevel requiredProtectionLevel = ProtectionLevel.None;
                        // When connecting to proxy server don't enforce the integrity to avoid
                        // compatibility issues. The assumption is that the proxy server comes
                        // from a trusted source. On macOS we always need to enforce the integrity
                        // to avoid the GSSAPI implementation generating corrupted authentication
                        // tokens.
                        if (!isProxyAuth || OperatingSystem.IsMacOS())
                        {
                            requiredProtectionLevel = ProtectionLevel.Sign;
                        }

                        NegotiateAuthenticationClientOptions authClientOptions = new NegotiateAuthenticationClientOptions
                        {
                            Package    = challenge.SchemeName,
                            Credential = challenge.Credential,
                            TargetName = spn,
                            RequiredProtectionLevel = requiredProtectionLevel,
                            Binding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint)
                        };

                        using NegotiateAuthentication authContext = new NegotiateAuthentication(authClientOptions);
                        string?challengeData = challenge.ChallengeData;
                        NegotiateAuthenticationStatusCode statusCode;
                        while (true)
                        {
                            string?challengeResponse = authContext.GetOutgoingBlob(challengeData, out statusCode);
                            if (statusCode > NegotiateAuthenticationStatusCode.ContinueNeeded || 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.IsAuthenticated || !TryGetChallengeDataForScheme(challenge.SchemeName, GetResponseAuthenticationHeaderValues(response, isProxyAuth), out challengeData))
                            {
                                break;
                            }

                            if (!IsAuthenticationChallenge(response, isProxyAuth))
                            {
                                // Tail response for Negoatiate on successful authentication. Validate it before we proceed.
                                authContext.GetOutgoingBlob(challengeData, out statusCode);
                                if (statusCode > NegotiateAuthenticationStatusCode.ContinueNeeded)
                                {
                                    isNewConnection = false;
                                    connection.Dispose();
                                    throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode), null, HttpStatusCode.Unauthorized);
                                }
                                break;
                            }

                            needDrain = true;
                        }
                    }
                    finally
                    {
                        if (isNewConnection)
                        {
                            connection !.Release();
                        }
                    }
                }
            }

            return(response !);
        }
Пример #6
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");
            }
        }