The bad news, as determined by ILSpy, is that there is no way to get to a System.Net.SslStream
instance from anywhere inside ASP.NET. That class is used for direct programming against the network, for example by the WCF framework. The best you can do from ASP.NET (whether using System.Web or OWIN on top of IIS or HttpListener) is to get a server variable (see list of IIS server variables) for whether the connection is secured by whatever secure transport was negotiated with the client.
As far as deterministically reading data from the event log during a web request... that seems scary. But if you can make it work, please share the code. :)
Alternatively, you could try to implement your own Owin host (aka web server!) that uses SslStream
underneath. Maybe. :P See this article for a thorough introduction to SslStream
programming.
But since you're already able to turn off certain protocols on your server (as in this article, I assume)... You could set up your site on two different subdomains, e.g. www.example.com
and secure.example.com
, where the former is a vanilla web server and the latter is configured to only accept TLS 1.2 connections. Then you'd write some bootstrapping logic that gets served from www.example.com
and attempts to make an AJAX request to secure.example.com/securityUpgradeCheck
(possibly with a nicely styled spinner animation and "Please wait, attempting to secure this connection" text to impress your users :)). If that request succeeds, the user can be redirected to secure.example.com
(probably permanently, since that user agent is then known to support TLS 1.2 unless for some reason the user changes their browser settings).
For added impact, order an EV SSL certificate for the secure domain so your users will notice the upgrade in security. :)
UPDATE: I did some more digging, on the theoretical basis of writing a custom (native) ISAPI filter (or extension) to get at this information via the SChannel API. At first I was hopeful because I discovered a function HSE_REQ_GET_SSPI_INFO
that would return an SSPI CtxtHandle
structure, and which you could call from a custom ISAPI extension via the EXTENSION_CONTROL_BLOCK
ServerSupportFunction
function. That CtxtHandle
structure, it turns out, represents an SChannel context and can get you a reference to a SECPKG_ATTR_CONNECTION_INFO
attribute with which you can retrieve SSL connection-level information (the same information that's surfaced in the SslStream
class in .NET, as far as I could tell). However, sadly, Microsoft anticipated that possibility and decided that this information is only available if you are using client certificates. The behavior is "by design."
There was one (native) SSPI function, QueryContextAttributes (Schannel)
, that I discovered during a long hunt through MSDN which may work. I haven't tried it, and it could simply fail for the same "by design" reason as the ISAPI API limitation linked to above. However, it may be worth a try. If you want to explore this route, here is an example of an ISAPI extension. Actually, with this approach you might be able to write an IIS module instead, using the newer IIS 7.0+ SDK.
But, assuming you don't have the luxury of requiring client certificates and that long shot doesn't work, that absolutely leaves only two options.
- Use a different web server (Apache, etc.), running on the same physical/virtual machine but on a different port, etc. (as per our discussion in the comments, since you can't spin up another machine). If you only want to give the client an informational message, then this approach, coupled with an AJAX request, might be sufficient. Yes, a different port could well be blocked by a firewall somewhere, but hey - it's only an optional informational message anyways.
- Rely on the semi-brittle approach with the System event log. Enable Schannel event logging and then write some event log querying code to try to correlate the request with the last-logged Schannel event. Note that you'll need to find a way to reliably correlate whatever gets put in the event log with the current HTTP request, so you might also need to write an ISAPI filter/extension or IIS module in this case to find the Schannel context handle (which is what I'm assuming the correlation would be based on).
By the way - is your load balancer configured to do any SSL interception? Because then this whole thing is moot anyways... Just a thought to consider.
UPDATE: Enabling Schannel logging netted this gem:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Schannel" Guid="{1F678132-5938-4686-9FDC-C8FF68F15C85}" />
<EventID>36880</EventID>
<Version>0</Version>
<Level>4</Level>
<Task>0</Task>
<Opcode>0</Opcode>
<Keywords>0x8000000000000000</Keywords>
<TimeCreated SystemTime="2014-08-13T02:59:35.431187600Z" />
<EventRecordID>25943</EventRecordID>
<Correlation />
<Execution ProcessID="928" ThreadID="12912" />
<Channel>System</Channel>
<Computer>**********</Computer>
<Security UserID="S-1-5-18" />
</System>
<UserData>
<EventXML xmlns:auto-ns3="http://schemas.microsoft.com/win/2004/08/events" xmlns="LSA_NS">
<Type>client</Type>
<Protocol>TLS 1.2</Protocol>
<CipherSuite>0x3c</CipherSuite>
<ExchangeStrength>2048</ExchangeStrength>
</EventXML>
</UserData>
</Event>
This can be read out directly from managed code. I think the UserID only corresponds to the IIS worker process SID, unfortunately, but assuming you can come up with some kind of correlation heuristic, you could set up a background thread to continually poll the event log and give you a list of recently established client handshakes (use a ConcurrentDictionary
perhaps).
There. That's it. No more curious investigating for me. I'm done. :P