dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.96k stars 4.65k forks source link

[API Proposal]: Expose negotiated cipher suite and TLS protocols via HttpResponseMessage #91460

Closed Sayan751 closed 1 year ago

Sayan751 commented 1 year ago

Background and motivation

I have a use case, where when I am sending messages via an HTTP endpoint, I need to know the following information:

At this point, it is extremely cumbersome to procure this information. For example, in .NET 7.0 I am using a wrapper over an HTTP client, the holds a reference to my SslClientAuthenticationOptions, which uses reflection to extract the InnerStream from the SslStream (sender in the RemoteCertificateValidationCallback) sets the reference of the SslStream in the InnerStream which happens to be my custom NetworkStream. The use of reflection also means that my code won't be AOT compatible.

From my point of view, it would have been a lot simpler if this information were available via the HttpResponseMessage.

API Proposal

var responseMessage = await httpClient.SendAsync(whatever);

var connection = responseMessage.Connection;
var cipherSuite = connection.TlsCipherSuite;
var tlsProtocol = connection.SslProtocols;
var remoteCertificateChain = connection.RemoteCertificateChain;
var ipEndpoint = connection.Endpoint as IPEndpoint;

API Usage

See above.

Alternative Designs

No response

Risks

No response

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation I have a use case, where when I am sending messages via an HTTP endpoint, I need to know the following information: - the negotiated TLS cipher suite, - the negotiated TLS protocols, - the remote certificate, and possibly the chain, and - the remote IP address and port. At this point, it is extremely cumbersome to procure this information. For example, in .NET 7.0 I am using a wrapper over an HTTP client, the holds a reference to my `SslClientAuthenticationOptions`, which uses reflection to extract the `InnerStream` from the `SslStream` (`sender` in the `RemoteCertificateValidationCallback`) sets the reference of the `SslStream` in the `InnerStream` which happens to be my custom `NetworkStream`. The use of reflection also means that my code won't be AOT compatible. From y point of view, it would have been a lot simpler if this information were available via the `HttpResponseMessage`. ### API Proposal ```csharp var responseMessage = await httpClient.SendAsync(whatever); var connection = responseMessage.Connection; var cipherSuite = connection.TlsCipherSuite; var tlsProtocol = connection.SslProtocols; var remoteCertificateChain = connection.RemoteCertificateChain; var ipEndpoint = connection.Endpoint as IPEndpoint; ``` ### API Usage See above. ### Alternative Designs _No response_ ### Risks _No response_
Author: Sayan751
Assignees: -
Labels: `api-suggestion`, `area-System.Net.Security`, `untriaged`
Milestone: -
MihaZupan commented 1 year ago

Related: https://github.com/dotnet/runtime/issues/63159

Sayan751 commented 1 year ago

Thanks @MihaZupan for the related issue! It is great to know that such telemetry for HTTP request as well as for Kestrel exist. I am referring to this https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/networking-telemetry.

However, I am a bit lost in the sea of telemetry metrics. Also, I could not unfortunately locate the information I need in those metrics, and those seem to be purely statistical data. If I am missing anything, can you please kindly point out to a fuller example that I can leverage for my use case?

henning-krause commented 1 year ago

A little more information on why we need this information. We're using the HttpClient to send documents to partners in the energy sector. This area is highly regulated and we need to make sure (and document) that we only use the permitted security settings. This includes the TLS version used and the negotiated cipher suite and the server certificates used.

For inbound messages we're able to extract this information from Kestrel in a safe way, but with the HttpClient we currently need to use Reflection. As @Sayan751 pointed out, this removes the option to use AOT and further we're now dependent on the .NET team not to change the internals we rely on.

MihaZupan commented 1 year ago

Starting with .NET 7.0, you can set the SocketsHttpHandler.ConnectCallback to a custom implementation that logs information about what parameters were used/negotiated for a specific connection/host.

using var handler = new SocketsHttpHandler();
using HttpClient client = new(handler);

handler.ConnectCallback = async (context, ct) =>
{
    var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
    try
    {
        await socket.ConnectAsync(context.DnsEndPoint, ct);

        var sslStream = new SslStream(new NetworkStream(socket, ownsSocket: true));

        // Use whatever options you want (e.g. handle client certs)
        // When using HTTP/2, you must also keep in mind to set things like ApplicationProtocols
        var options = new SslClientAuthenticationOptions
        {
            TargetHost = context.DnsEndPoint.Host
        };

        await sslStream.AuthenticateAsClientAsync(options, ct);

        // Log all the things you care about
        Console.WriteLine($"Connection to {socket.RemoteEndPoint} for {context.DnsEndPoint.Host} is using {sslStream.NegotiatedCipherSuite}");

        return sslStream;
    }
    catch
    {
        socket.Dispose();
        throw;
    }
};
Connection to [::ffff:54.175.87.239]:443 for httpbin.org is using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

If you also want to tie that information to each individual request, it gets a bit more complicated.

63159 was asking for similar information - what connection a specific request used.

As of .NET 8.0, the only real option available is to use the newly added EventSource events that allow you to correlate requests & connections to some degree.

Example.cs is a modified example from https://github.com/dotnet/runtime/issues/63159#issuecomment-1515432131.

Request to https://httpbin.org/get used a connection with TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
karelz commented 1 year ago

If you don't want to hook up to the EventListener, you also have an option to create HttpClient with just 1 connection and then store the associated SslOptions from ConnectCallback for it. Whenever you use the HttpClient, you will have the info handy. You will be in charge of load balancing requests among the connections (each represented with HttpClient), so there will be more work there.

Overall, looks like it is answered, so closing.