10

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.

bdawg
  • 343
  • 3
  • 13
  • My team tried to do the same but basically concluded that it's not possible in a meaningful way. – abatishchev Jan 07 '19 at 19:47
  • What exception you're getting? – abatishchev Jan 07 '19 at 19:48
  • And never use `async void` – abatishchev Jan 07 '19 at 19:48
  • @abatishchev sorry its just test code. The exception I get is System.NullReferenceException: 'Object reference not set to an instance of an object.' it happens when they start to read up the objbaseStream. – bdawg Jan 07 '19 at 19:54
  • Your implementation relies on reflection what is inherently and understandably flaky. Debug and figure out which line is causing NullReferenceException means what you're looking for doesn't exist/named differently. – abatishchev Jan 07 '19 at 19:55

4 Answers4

7

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);
}
Zergatul
  • 1,957
  • 1
  • 18
  • 28
  • I am getting exception "Cannot access a disposed object. Object name: 'SslStream'." at line var protocol = (SslProtocols)GetPrivateProperty(state, "SslProtocol"); in .net framework 4.6.1. @Zergatul – Hassan Qayyum Jun 26 '20 at 13:28
  • @HassanQayyum did you use the same code, or it is modified in some way? – Zergatul Jun 26 '20 at 14:37
  • My function type is not "static void main()" but it is a sub function with return type also I am calling some other stuff from this function too. But I have pasted you code as it is and tried. When I ran it in debug mode, I noticed that there are other properties that get disposed too in "state" variable. Also is there any other way I could try? @Zergatul – Hassan Qayyum Jun 26 '20 at 16:35
  • After doing some research I concluded that the some objects get disposed because they are unmanaged and .net by default dispose unmanaged objects. Is there anyway to stop disposing them? @Zergatul – Hassan Qayyum Jun 27 '20 at 13:19
  • @HassanQayyum does your server return empty response? I think this can be the problem. – Zergatul Jun 27 '20 at 18:32
  • I have confirmed that it returns response with http.599 error code. @Zergatul – Hassan Qayyum Jun 29 '20 at 07:39
  • @HassanQayyum I mean empty http response. HttpClient class reads http headers, see empty http response body and decides to close/dispose stream objects. There is no point to keep them open in this case. – Zergatul Jun 29 '20 at 10:54
  • I am also talking about httpresponse returned after httpclient request, it has a body. Also, I have found another way of reading Tls version through system.net tracing. So, i am no longer using this method. @Zergatul – Hassan Qayyum Jun 29 '20 at 23:29
0

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);
    }
}
0

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}";
        }
    }
Clint
  • 1
  • 2
  • Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. **Would you kindly [edit] your answer to include additional details for the benefit of the community?** – Jeremy Caney Aug 06 '23 at 01:04
0

...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")

Clint
  • 1
  • 2