Deprecating TLSv1.0 and TLSv1.1 gracefully with Cloudflare Workers

A lot has changed in the TLS ecosystem in just the last few years and and a huge drive towards deploying TLS has seen a surge in the adoption of HTTPS across the web. As things move forwards though, we have to leave relics of the past behind.



The rise in adoption

Regular readers will know that I scan the Alexa Top 1 Million sites on a regular basis and publish my crawler data on crawler.ninja. Alongside that I also publish a report every 6 months with the most recent being in February 2019. In that latest report we saw the continued growth in the use HTTPS.



As TLS grows from strength to strength and we see performance and security advances in newer protocol versions like TLSv1.2 and TLSv1.3, we also see older protocol versions fall behind. The use of these older protocols is low across the web and it's time for them to start stepping aside.



Deprecating older protocols

Not only do these older protocol versions lack the security or performance of newer protocol versions, we've already seen a widespread push to deprecate them. In June 2018 the Payment Card Industry said that TLSv1.0 was no longer considered strong enough to encrypt payment card data sent over the Internet. If it's not good enough to encrypt payment card data, what is it good enough to encrypt?..

It wasn't just the PCI either. In 2018 GitHub also announced that they would be dropping support for early versions of TLS, so did Cloudflare and Microsoft amongst many other large companies. Don't think it's just services that are dropping support for early versions of TLS either, there was a joint effort from browser vendors to announce their deprecations in their browsers too. In October 2018 Chrome, Firefox, Edge and Safari all announced their plans to remove the protocols. Whilst these dates haven't all passed yet, the plan is set and the dates on the horizon aren't getting any further away.


Notifying clients

In some of the stories above you can see that organisations were worried about the impact of turning off support for these early protocol versions, and that's a perfectly valid concern. If you turn off support for a protocol version, and that was the highest version a particular client could support, that client can no longer connect to your site/service. Way back in 2015 I blogged about how you can figure out what % of clients depend on these old protocol versions using your server logs. Knowing what % of clients will fail is a great starting point, but then how do you tell them? Some service providers did a brown out where they'd disable support for a short period and then turn it back on. This would allow clients to fail and hopefully their owners would investigate why, resulting in them upgrading where necessary. This is a great approach but I wanted to try and go one step earlier and inform clients before any brown out. It turns out there was a pretty easy way for me to do that using Cloudflare Workers.


Cloudflare Workers to the rescue

I've talked extensively about how I use Cloudflare Workers on my blog and on Report URI so having a worker already in place meant I can simply add more functionality to the existing worker, with no additional cost.

Inside the worker the request.cf object gives you access to some nice attributes that we can use to detect the protocol version of the incoming connection. Here's a look at some of the values inside request.cf.


{
    "tlsVersion":"TLSv1.3",
    "httpProtocol":"HTTP/2",
    "tlsCipher":"AEAD-AES128-GCM-SHA256",
    "asn":45780,
    "requestPriority":"",
    "clientTrustScore":95,
    "country":"AU",
    "colo":"BNE"
}


With such easy access to this information it's simply a case of detecting a version we plan to deprecate support for and informing the client. To do that I modified a GitHub style 'Fork Me' banner to place in the top right of the screen for clients using TLSv1.0 or TLSv1.1 to connect.


#tlsWarning a{
    background:#fc913a;
    color:#fff;
    text-decoration:none;
    font-family:arial,sans-serif;
    text-align:center;
    font-weight:bold;
    padding:5px 40px;
    font-size:1rem;
    line-height:2rem;
    position:relative;
    transition:0.5s;
}
 #tlsWarning a:hover{
    background:#c11;
    color:#fff;
}
#tlsWarning a::before,#tlsWarning a::after{
    content:"";
    width:100%;
    display:block;
    position:absolute;
    top:1px;
    left:0;
    height:1px;
    background:#fff;
}
 #tlsWarning a::after{
    bottom:1px;
    top:auto;
}
 @media screen and (min-width:800px){
     #tlsWarning{
        position:fixed;
        display:block;
        top:0;
        right:0;
        width:200px;
        overflow:hidden;
        height:200px;
        z-index:9999;
    }
     #tlsWarning a{
        width:200px;
        position:absolute;
        top:60px;
        right:-60px;
        transform:rotate(45deg);
        -webkit-transform:rotate(45deg);
        -ms-transform:rotate(45deg);
        -moz-transform:rotate(45deg);
        -o-transform:rotate(45deg);
    }
}


The CSS was added to my style sheet and the following HTML needed to be injected on pages where the client connection was using a protocol version that was going to be deprecated.


<span id="tlsWarning"><a href="https://scotthelme.co.uk/tls-warning">Urgent Notice</a></span>


With that, all I need is for the Worker to inject the HTML into pages when the client connects with the offending protocol versions.


let tlsVersion = "NONE"
if (event.request && event.request.cf && event.request.cf.tlsVersion && typeof event.request.cf.tlsVersion === "string" && event.request.cf.tlsVersion !== "") {
	tlsVersion = event.request.cf.tlsVersion
	if (tlsVersion !== "NONE" && tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") {
		let text = await response.text()
		let modified = text.replace("</head>", "</head><span id=\"tlsWarning\"><a href=\"https://scotthelme.co.uk/tls-warning\">Urgent Notice</a></span>")
		return new Response(modified , {
			status: 200,
		})
	}
}


The above code will need to be worked into your existing worker but you can see the basic idea of what's going on. We declare tlsVersion, check that the Cloudflare provided TLS version is present and correct, then check the version to see if it's one we plan to deprecate. If it is, grab the response text, insert the HTML for the banner and return it to the client.


Testing it out

To see if everything is working as expected we of course need a browser to connect to the site with a TLS version that will fall under the deprecation warning. To do this I used Firefox where you can navigate to about:config, search for security.tls.version.max and set it to 2. Then, when you connect to sites, Firefox will be using TLSv1.1 for the connection. Here's what that looks like!



If I switch back over to Chrome, which will be using TLSv1.3 at this stage, there is of course no warning presented.



Moving forwards

The idea here is to start spreading awareness that very soon the use of these protocols will become more and more of a problem. Users that see this may not be able to do something about this directly, perhaps they're in a corporate environment, but they will be able to start asking the appropriate questions to the appropriate people.

For now I have no fixed date on which I will disable TLSv1.0 and TLSv1.1, but I will keep monitoring the use of these protocols and intend to leave the notice up there for a good period of time before turning off support for them.


Bonus round!

Because it's so easy to do things for fun with Cloudflare Workers, I decided to throw up a little test page on tls.scotthelme.co.uk that shows you some of the information about your connection to my site.



It's not perfect code, I wrote it in about 30s, but it was literally just for fun! Here it is:


if (domain === 'tls.scotthelme.co.uk') {
	let output = "NONE"
	if (req && req.cf && req.cf.tlsVersion && typeof req.cf.tlsVersion === "string" && req.cf.tlsVersion !== "") {
		output = "TLS version: " + req.cf.tlsVersion + "\r\n"
		output += "Cipher suite: " + req.cf.tlsCipher + "\r\n"
		output += "HTTP version: " + req.cf.httpProtocol + "\r\n"
		output += "Country code: " + req.cf.country + "\r\n"
		output += "Datacentre code: " + req.cf.colo + "\r\n"
	}
	return new Response(output, {
		status: 200
	})
}