Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement TLS-ALPN in .NET C# for a HTTP/2 server

Tags:

c#

.net

ssl

http2

alpn

does anyone know, how I can implement the TLS-ALPN in .NET?

I've implemented a basic HTTP/2 server, but without TLS encryption. I searched in google, but I only found resources for C, Java or other languages, but nothing for .NET (C#)

like image 814
Ringo Leese Avatar asked Aug 15 '15 06:08

Ringo Leese


3 Answers

According to HttpTwo project on Github it is not possible currently because of a bug.

Update: It's not supported in .NET. You can vote for it here: https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/6264363-add-support-for-alpn-to-system-net-security-sslstr

quote:

The HTTP/2 RFC states that secure connections must use ALPN to negotiate the protocol. Unfortunately, .NET's SslStream has no ability to specify application protocols as part of the TLS authentication, so it can't support ALPN. There's an issue tracking this on dotnetfix however it seems like this isn't going to happen very soon (especially on mono and .NET 4.x).

like image 139
Max Novich Avatar answered Nov 12 '22 01:11

Max Novich


It actually is possible. With some reflection you can inject any extension in client or server hello.

Here's some code to give you an idea:

// Refer IANA on ApplicationProtocols: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
public static void FixALPN(params string[] protocols) {
    if (Interlocked.Increment(ref cntFixALPN) > 1)
    {
        throw new Exception("FixALPN should be called only ONCE, put it in your Main or use a static constructor.");
        return;
    }

    // get the needed (internal) System types
    string tpname = typeof(System.Net.HttpListener).AssemblyQualifiedName;
    Type tpiface = Type.GetType(tpname.Replace("HttpListener", "SSPIInterface"));
    Type tpgsspi = Type.GetType(tpname.Replace("HttpListener", "GlobalSSPI"));
    Type tpsdc = Type.GetType(tpname.Replace("HttpListener", "SafeDeleteContext"));
    Type tpsecbuf = Type.GetType(tpname.Replace("HttpListener", "SecurityBuffer"));

    // create ALPN buffer
    ConstructorInfo ci = (from x in tpsecbuf.GetConstructors() where x.GetParameters().Length == 4 select x).First();
    var secbufempty = ci.Invoke(new object[] { new byte[0], 0, 0, 0 });
    byte[] btsalpn = GetALPNBuffer(protocols);
    var secbufalpn = ci.Invoke(new object[] { btsalpn, 0, btsalpn.Length, 18 });

    // grab the object to replace...
    FieldInfo fi = tpgsspi.GetField("SSPISecureChannel", BindingFlags.NonPublic | BindingFlags.Static);
    var secchan = fi.GetValue(null);

    // ...and the method(s) we'll use in our intercepted call(s)
    MethodInfo miSDC_ISC = tpsdc.GetMethod("InitializeSecurityContext", BindingFlags.NonPublic | BindingFlags.Static);
    MethodInfo miSDC_ASC = tpsdc.GetMethod("AcceptSecurityContext", BindingFlags.NonPublic | BindingFlags.Static);

    // fake the internal interface
    var result = new InterfaceImplementer(tpiface, (mcm) => {
        MethodInfo mi = (MethodInfo)mcm.MethodBase;
        object[] args = mcm.Args;
        object ret = null;
        if (mi.Name == "InitializeSecurityContext") // For Client Mode
        {
            if (args[5] == null) // empty input, new connection
            {
                dynamic[] secbufs = (dynamic[])Activator.CreateInstance(miSDC_ASC.GetParameters()[6].ParameterType, new object[] { 1 });
                secbufs[0] = secbufalpn;
                object[] sdcargs = new object[] { 0, args[0], args[1], args[2], args[3], args[4],
                    null,
                    secbufs,
                    args[6], args[7]
                };
                ret = miSDC_ISC.Invoke(null, sdcargs);
                args[0] = sdcargs[1];
                args[1] = sdcargs[2];
                args[7] = sdcargs[9];
            }
            else
            {
                ret = mi.Invoke(secchan, args);
            }
        }
        else if (mi.Name == "AcceptSecurityContext") // For Server Mode
        {
            dynamic[] secbufs = (dynamic[])Activator.CreateInstance(miSDC_ASC.GetParameters()[6].ParameterType, new object[] { 3 });
            secbufs[0] = args[2];
            secbufs[1] = secbufempty;
            secbufs[2] = secbufalpn;
            object[] sdcargs = new object[] { 0, args[0], args[1], args[3], args[4],
                null,
                secbufs,
                args[5], args[6]
            };
            ret = miSDC_ASC.Invoke(null, sdcargs);
            args[0] = sdcargs[1];
            args[1] = sdcargs[2];
            args[6] = sdcargs[8];
        }
        else
            ret = mi.Invoke(secchan, args);

        return new ReturnMessage(ret, args, args.Length, mcm.LogicalCallContext, mcm);
    }).GetTransparentProxy();
    
    // and set it, done
    fi.SetValue(null, result);
}
like image 45
Kris Avatar answered Nov 12 '22 00:11

Kris


.NET Core 2.1.2 includes the necessary changes to SslStream required to support ALPN. It isn't documented yet, but the pull request that adds it is here

like image 1
Dylan P Avatar answered Nov 12 '22 01:11

Dylan P