Series: On why I hate using internet applications on Windows
Part 2: Mono
Occasionally, a really slick web application is built that is written in a language and/or uses libraries that proprietary to a specific platform, limiting it’s adoption or making it difficult to implement with other applications. Sonarr is one such app- written in C# and dependent on the .NET framework to run. Generally speaking, this limits Sonarr from being run in anything but a Windows environment.
The Mono project is an cross-platform, open-source .NET framework primarily supported by Xamarian, a subsidiary of Microsoft. Ideally, this opens up C#/.NET applications to a much wider set of platforms where it’s ubiquity would attract developers and in turn making C#/.NET a leading platform for developing web applications.
Realistically, the implementation is flawed and carries over many obtuse functional methods that have much simpler configurations on the platforms that Mono targets.
First, reaching or nearing 100% compatibility seems to be an ongoing issue.
Following along the same theme, the native .NET environment has many auxiliary utilities to set certain environment conditions or states. These have been ported over as a part of Mono, but are incomplete. Take the httpcfg utility which is a binary wrapper for the Windows HTTP configuration APIs for example. In Windows, there’s a full suite of options-
httpcfg {set | query | delete} {ssl | query | iplisten} [/i Ip:Port] [/h SSL Hash] [/g"{GUID}"] [/c StoreName] [/m CheckMode] [/r RevocationFreshness] [/x UrlRetrievalTimeout] [/t SslCtlIdentifier] [/n SslCtlStoreName] [/f Flags] [/u {http://URL:Port/ | https://URL:Port/}] [/a ACL]
whereas in Mono httpcfg is extraordinarily limited-
$ httpcfg
Usage is:
httpcfg -add -port NN [-cert CERT -pvk PVK] [-p12 P12 -pwd PASSWORD]
httpcfg -del -port NN
httpcfg -list
and a user is only able to add, delete, and list certificates bound to ports.
I’d like to pause here and point out how odd it is that TLS termination in a .NET environment exists as a separate process from the web application listening on that port. Also that I could have TLS termination enabled and occurring on a port with no app listening for incoming connections. I could terminate Sonarr and still be accepting TLS connections that ultimately go nowhere. By contrast, applications in a *nix environment that support TLS handle termination in-app. If the app is closed, TLS termination no longer occurs.
Further, Mono’s implementation of httpcfg doesn’t support intermediary certs so you’re out of luck if you need to use a publicly-trusted certificate to secure your connection as all certificate authorities sign against an intermediate certificate instead of their root certs. This seems like a HUGE oversight, unless they assumed that everyone who would develop on this platform would have a proxy in front of their app which handled TLS termination correctly. Still, not a great assumption.
Also annoyingly, it requires private keys to be in pvk format, which is a proprietary Microsoft file format that nobody else uses and is difficult to create outside of a Windows environment or in PKCS#12, which is more widely used, but still less-common than the near universal usage of PEM-encoded x509 (certificates) and PKCS#1 or SEC-1 (key) files.
Probably the most glaring and flagrant offense in in the Mono .NET enviromnent has to that, at some point, someone decided that it was a good idea to hard-code the requirement that the client must also present a certificate when establishing a TLS connection in addition to the server. That is, that all TLS connections must be mutually-authenticated. This requirement is still present today.
This is an absolutely crazy requirement to force upon the entire platform. Mutually-authenticated TLS is a great feature, but seldom used. Case in point- no browser on Windows supports this functionality by default, likely because SChannel doesn’t handle this but that’s a guess (somewhat hilariously, browsers on OS X 10.11 do). Given that why would this ever be a good idea to always enforce?
The only solution that exists to remove the always-mutually-authenticate TLS connections requirement is to compile mono from source, changing the file /mcs/class/System/System.Net/HttpConnection.cs
so that the line that reads:
SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, true, false);
becomes
SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false);
and allows clients to connect without presenting a certificate. Quite a lot of work just to solve one issue.
Looking back, the steps I took to enforce access to Sonarr via a TLS connection were:
- Obtain a publicly-trusted key/cert pair
- Convert the PEM-encoded key to PVK
- Bind the key/cert pair to a port that the .NET service was listening to
- Compile .NET from source to remove the mutual-authentication requiremen.
- Proxy Sonarr with Nginx so that clients are sent the full trust chain during the TLS handshake and don’t receive insecure connection warnings
Entirely way too much work to enable TLS for a service. I laugh when comparing this to configuring TLS for CouchPotato where all I needed to do was update the config file with the locations of the key and cert and simply restart the app.
I’d be a bit remiss if I didn’t at least acknowledge that this isn’t directly a problem with Windows and that all the difficulties mentioned above stem from using software for Windows on a non-Windows platform. I still feel like there’s some shame to be had in encouraging developers to build against one platform-specific run-time.