bdawg
bdawg

Reputation: 363

Detecting TLS Version used for HttpClient POST or GET calls

I am trying to retrieve the TLS Version information. The code I have below makes a successful HTTP GET call using HttpClient. What am I missing? Where do I get the TLS Version information from HttpClient?

I am kind of doing the same thing as was suggested in Which TLS version was negotiated? but that is specific to WebRequest which is not the same as HttpClient.

static async Task MainAsync()
{
    Uri baseURI = new Uri("https://jsonplaceholder.typicode.com/posts/1");
    string apiPath = "";
    using (var client = new HttpClient())
    {
        client.BaseAddress = baseURI;
        HttpResponseMessage response = await client.GetAsync(apiPath);
        Console.WriteLine("HTTP status code: " + response.StatusCode.ToString());
        GetSSLConnectionInfo(response, client.BaseAddress.ToString(), apiPath);
    }
    Console.ReadKey();
}

static async Task GetSSLConnectionInfo(HttpResponseMessage response, string baseURI, string apiPath)
{
    using (Stream stream = await response.RequestMessage.Content.ReadAsStreamAsync())
    {
        BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
        Stream CompressedStream = null;
        if (stream.GetType().BaseType == typeof(GZipStream))
        {
            CompressedStream = (GZipStream)stream;
        }
        else if (stream.GetType().BaseType == typeof(DeflateStream))
        {
            CompressedStream = (DeflateStream)stream;
        }

        var objbaseStream = CompressedStream?.GetType().GetProperty("BaseStream").GetValue(stream);
        if (objbaseStream == null)
        {
            objbaseStream = stream;
        }

        var objConnection = objbaseStream.GetType().GetField("m_Connection", bindingFlags).GetValue(objbaseStream);
        var objTlsStream = objConnection.GetType().GetProperty("NetworkStream", bindingFlags).GetValue(objConnection);
        var objSslState = objTlsStream.GetType().GetField("m_Worker", bindingFlags).GetValue(objTlsStream);
        SslProtocols b = (SslProtocols)objSslState.GetType().GetProperty("SslProtocol", bindingFlags).GetValue(objSslState);
        Console.WriteLine("SSL Protocol Used for " + baseURI + apiPath + System.Environment.NewLine + "The TLS version used is " + b);
    }
}

I am expecting TLS connection Info but I get an exception.

Upvotes: 12

Views: 7092

Answers (5)

GregC
GregC

Reputation: 8007

I have re-worked @Zergatul's answer, setting aside some of the code duplication, and enabling IntelliSense exploration of the SSL Stream object. This uses HttpClient.

using System.Net.Security;
using System.Reflection;

public static class Program
{
    public static void Main()
    {
        var (protocol, negotiatedCipherSuite) = Task.Run(async () =>
        {
            using var client = new HttpClient();
            using var httpStream = await client.GetStreamAsync("https://www.google.com");

            var connection = GetField(httpStream, "_connection");
            var transportContext = GetProperty(connection, "TransportContext");
            var sslStream = (SslStream)GetField(transportContext, "_sslStream");
            return (sslStream.SslProtocol, sslStream.NegotiatedCipherSuite);

        }).Result;

        Console.WriteLine(protocol);
        Console.WriteLine(negotiatedCipherSuite);
    }

    static object GetProperty(object obj, string propertyName)
        => GetMemberInfo(obj,
            type => type.GetProperty(propertyName, bindingFlags)!)
        .GetValue(obj)!;

    static object GetField(object obj, string fieldName)
        => GetMemberInfo(obj, 
            type => type.GetField(fieldName, bindingFlags)!)
        .GetValue(obj)!;

    static TMember GetMemberInfo<TMember>(object obj, Func<Type, TMember> getMemberInfo)
        where TMember : MemberInfo
        => getMemberInfo(obj.GetType());

    static BindingFlags bindingFlags = 
        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
}

When I run it, I get the following output:

Tls13
TLS_AES_256_GCM_SHA384

Upvotes: 1

Clint
Clint

Reputation: 1

...or as a nifty PowerShell script;

$sourceCode = @"
using System.Net;
using System.Reflection;

public class TlsChecker
{

    private static object GetProperty(object obj, string property)
    {
        var t = obj != null ? obj.GetType() : null;

        var p = t != null ? t.GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) : null;

        var v = p != null ? p.GetValue(obj, null) : null;

        return v;
    }

    private static object GetField(object obj, string field)
    {
        var t = obj != null ? obj.GetType() : null;

        var f = t != null ? t.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) : null;

        var v = f != null ? f.GetValue(obj) : null;

        return v;
    }


    private static object GetFieldOrProperty(object obj, string fieldOrProperty)
    {
        if (obj == null) return null;
        var field = GetField(obj, fieldOrProperty);
        var property = GetProperty(obj, fieldOrProperty);
        return field != null ? field : property;
    }




    private static object GetPropValue(object o, string property)
    {
        var a = property.Split('.');
        object obj = o;
        object value = null;
        foreach (var prop in a)
        {
            value = GetFieldOrProperty(obj, prop);
            obj = value;
        }
        return value;
    }


    public static string GetProtocolInfo(string url)
    {
        try
        {
            var output = string.Empty;

            using (var client = new WebClient())
            {
                using (var stm = client.OpenRead(url))
                {

                    // this works in .Net Framework
                    var oneWay = GetPropValue(stm, "Connection.NetworkStream.m_Worker.SslProtocol");
                    // this works in .net 7.0
                    var orTheOther = GetPropValue(stm, "_connection.TransportContext._sslStream.SslProtocol");

                    var protocol = oneWay != null ? oneWay : orTheOther != null ? orTheOther : "[NO_VALUE]";
                    output += "SSL PROTOCOL: " + protocol;

                }
            }
            return output;
        }
        catch (System.Exception ex)
        {
            return "GetProtocolInfo(): ERROR: " + ex.Message;
        }
    }

}
"@

Add-Type -TypeDefinition $sourceCode -Language CSharp;

[TlsChecker]::GetProtocolInfo("https://www.google.com")

Upvotes: 0

Clint
Clint

Reputation: 1

This works for both .Net Framework 4.8 and .net 7.0. Good luck.

    private static object GetProperty(object obj, string property)
    {
        return obj != null ? (obj?.GetType()?.GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(obj) ?? null) : null;
    }

    private static object GetField(object obj, string field)
    {
        return obj != null ? (obj?.GetType()?.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(obj) ?? null) : null;
    }

    private static object GetFieldOrProperty(object obj, string fieldOrProperty)
    {
        if (obj == null) return null;
        var field = GetField(obj, fieldOrProperty);
        var property = GetProperty(obj, fieldOrProperty);
        return field ?? property;
    }

    private object GetPropValue(object o, string property)
    {
        var a = property.Split('.');
        object obj = o;
        object value = null;
        foreach (var prop in a)
        {
            value = GetFieldOrProperty(obj, prop);
            obj = value;
        }
        return value;
    }

    private string GetProtocolInfo(string url)
    {
        try
        {
            var output = string.Empty;

            using (var client = new WebClient())
            {
                using (var stm = client.OpenRead(url))
                {

                    // this works in .Net Framework
                    var oneWay = GetPropValue(stm, "Connection.NetworkStream.m_Worker.SslProtocol");
                    // this works in .net 7.0
                    var orTheOther = GetPropValue(stm, "_connection.TransportContext._sslStream.SslProtocol");

                    var protocol = oneWay ?? orTheOther ?? "[NO_VALUE]";
                    output += $"{Environment.NewLine}{Environment.NewLine}SSL PROTOCOL: {protocol}";

                }
            }
            return output;
        }
        catch (Exception ex)
        {
            return $"GetProtocolInfo(): ERROR: {ex.Message}";
        }
    }

Upvotes: 0

Wagner Bertolini Junior
Wagner Bertolini Junior

Reputation: 1200

Just adjusting the old code to make it work on current dotnet 6.0 version.

using System.Net;
using System.Reflection;
using System.Security.Authentication;

public static class Program
{
    static void Main(string[] args)
    {
        using (var client = new WebClient())
        {
            var stm = client.OpenRead("https://www.google.com");
            var connection = GetField(stm, "_connection");
            var transportContext = GetProperty(connection, "TransportContext");
            var sslStream = GetField(transportContext, "_sslStream");
            var protocol = (SslProtocols)GetProperty(sslStream, "SslProtocol");
            Console.WriteLine(protocol);
            stm.Close();
        }
    }

    private static object GetProperty(object obj, string property)
    {
        return obj.GetType().GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(obj);
    }

    private static object GetField(object obj, string field)
    {
        return obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(obj);
    }
}

Upvotes: 0

Zergatul
Zergatul

Reputation: 2010

Under the hood HttpClient uses internal TlsStream class (as in your example for WebRequest). We just need to find it in another location. Here is an example:

static void Main(string[] args)
{
    using (var client = new HttpClient())
    {
        using (var response = client.GetAsync("https://example.com/").Result)
        {
            if (response.Content is StreamContent)
            {
                var webExceptionWrapperStream = GetPrivateField(response.Content, "content");
                var connectStream = GetBasePrivateField(webExceptionWrapperStream, "innerStream");
                var connection = GetPrivateProperty(connectStream, "Connection");
                var tlsStream = GetPrivateProperty(connection, "NetworkStream");
                var state = GetPrivateField(tlsStream, "m_Worker");
                var protocol = (SslProtocols)GetPrivateProperty(state, "SslProtocol");
                Console.WriteLine(protocol);
            }
            else
            {
                // not sure if this is possible
            }
        }
    }
}

private static object GetPrivateProperty(object obj, string property)
{
    return obj.GetType().GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}

private static object GetPrivateField(object obj, string field)
{
    return obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}

private static object GetBasePrivateField(object obj, string field)
{
    return obj.GetType().BaseType.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}

Upvotes: 10

Related Questions