Testing .Net Core SSL or where is the certificate store (on Linux)

After mucking about with this for a good few hours I decided it's at least worth a quick write-up.

So first of all, most people are aware that OpenSSL is backing the SSL libraries in .Net Core on Linux. Which is great, because Mono's SSL support is horrible -- it supports about 4 ciphers from the 80's (I don't have the exact list to hand, but is dire) and while there are initiatives out there to get this up to scratch, it's not in the standard versions.

On the other hand, Mono has very extensive docs on how to do certificate management and how to add custom certificates. A quick Google turns up nothing of the sort for .Net Core. Probably not least, because .Net Core doesn't make for a great search term, but that's a different story.

Luckily, SSL has pretty decent (albeit 3rd party) docs on how to manage the certificate store. On the other hand, I managed to miss that completely and run off on a wild goose-chase through the corefx codebase trying to figure out what it's doing.

Digging through the corefx code

Unsurprisingly, the corefx repo is very well structured and it doesn't take much to figure out what's going on, though downloading the zip is definitely a must to explore what's going on as Github search isn't very great when it comes to exploring the codebase using a grep approach. A quick grep for 'SSL_CTX' is a good start. Also if you search for a specific OpenSSL way of doing things, you can then grep for that in corefx to see if it's wrapped and where it's used.

Testing SSL

Eventually, we do find an interesting way to write our unit tests and see that corefx respects the magic SSL_CERT_FILE environment variable (see StorePal.cs). Setting this allows us to quickly unit-test code without the need of adding test certificates into our trust-store. For example my project uses the following strip to run it's tests with a test ca:

SSL_CERT_FILE=../misc/certs/ca-cert /home/tomciaaa/dotnet/dotnet test -xml ../xunit_results.xml

Also it's fairly easy to spin up an ssl server using the fake certificates from inside the C# app making for a rather contained test.

private static void RunTest(int port, string extraOpenSslArgs)
{
    var testString = "Hello from a Xunit test!";

    // Process management gubbins to spin up an openssl server
    var process = new System.Diagnostics.Process();
    process.StartInfo.FileName = "/usr/bin/openssl";
    process.StartInfo.Arguments = "s_server -cert ../misc/certs/client-cert-signed -key ../misc/certs/client-key -CAfile ../misc/certs/ca-cert -accept " + port + " -pass pass:clientqwerty" + extraOpenSslArgs;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = false;
    process.StartInfo.RedirectStandardInput = true;
    process.Start();

    using (var secureStream = MySslStreamProvider.ConnectStream("localhost", port))
    {
        // Send something C# -> openssl
        secureStream.Write(Encoding.UTF8.GetBytes(testString));

        // Send something openssl -> C# and check that C# received it
        var testString2 = "The quick brow fox jumps ove the lazy dog.\n";
        var encoding = process.StandardInput.Encoding;
        var testBytes = encoding.GetBytes(testString2);
        process.StandardInput.Write(testString2);
        process.StandardInput.Flush();
        process.StandardInput.Dispose();
        // Uses a custom extension method, but easy to write using a simple Read() as well
        var responseBytes = secureStream.ReadExactlyNBytes(testBytes.Length);
        Assert.Equal(testString2, encoding.GetString(responseBytes));
    }

    process.Kill();
    var response = process.StandardOutput.ReadToEnd();
    // Check that C# -> opessl transmission was successful
    Assert.Contains(testString, response);
}

There's also the SSL_CERT_DIR environment variable that allows you to specify the full store override, however I found this option quicker.

blogroll