The Chrome Security Team have just announced that they're removing the yellow warning triangle from pages with mixed content. From now on, these pages will show with the same neutral, grey icon as HTTP pages. They claim that this will help with the migration from HTTP to HTTPS and here's how you can use HSTS and CSP to make the migration a little easier.
You can read the announcement from the Chrome Security Team here, but the basic gist of it is this.
From Chrome 46 onwards, instead of getting a yellow warning for pages served over HTTPS that contain resources loaded over HTTP, you will just get a plain grey indicator as if the whole page was loaded over HTTP. This is good because they're reducing the number of states the user is exposed to from 4 to 3 but I still think this should go one step further and be reduced to a binary state. Everything is ok or it isn't. It doesn't matter what the reason for HTTPS being broken is, mixed mode, expired cert, SHA1 cert in the chain, they all result in the same outcome. We can't be sure the session is secure. Fortunately, the Chrome team do state that they're planning to introduce this change but no timeline is provided.
In the long term, we hope that most sites on the internet will become secure, and we plan to reduce the icon to just two states: secure and not secure. The change announced in this post is a small step in that direction.
Another thing that is mentioned in the blog is that this will help sites transition from HTTP to HTTPS without fear of mixed content warnings in the browser. Previously, if you were to migrate from HTTP to HTTPS and wind up with some mixed content along the way, you could end up with nasty yellow warnings between the current grey icon and the desired green icon at the end. This isn't so good. During the roll out of HTTPS with the new warning indicators you won't have that yellow warning in the middle, the mixed content will be shown an insecure with the same grey icon already shown for HTTP pages. Whilst I agree with the point here, I think that there are ways to test your HTTPS deployment before pushing changes like this into production and causing mixed content warnings! For some additional precautions we can look to CSP and HSTS to help us out.
Content Security Policy
CSP is a HTTP response header that allows you to define a whitelist of sources that the browser is allowed to load content from. This can include preventing the browser from loading assets over an insecure scheme, or, to upgrade any insecure requests to a secure scheme before making the request. You can read my blog, Content Security Policy - An Introduction, if you'd like more details on CSP, but here are the bits you need to know.
Using the CSP directive
default-src, you can instruct the browser where it is permitted to load a wide variety of assets from, including images, scripts, fonts, style sheets and more. We can also require that any form submissions take place over a secure connection.
Content-Security-Policy: default-src https:; form-action https:
With this policy in place, the browser will refuse to load an asset that is not loaded using the HTTPS scheme and will not submit a form that does not use the HTTPS scheme either. Rather than enforce this policy right off the bat and risk breaking anything, you can issue it as a report-only header. This means that the browser will be on the lookout for any HTTP resources, it will send you reports of any that it finds and it won't block them which could possibly cause problems! You can harness this reporting with a free account over at https://report-uri.io and it's a great way to identify all of those HTTP resources that you need to address before you can flip the big HTTPS switch. That's a great start and you can identify where all of your HTTP assets are hiding, but how do you go about fixing them all? You should dig through your source and re-write all of the HTTP references to HTTPS, but there is another CSP directive that could help. The
This directive forces the browser to upgrade any insecure request to a secure request before issuing that request. This means that any reference to an asset loaded over HTTP will be re-written to use HTTPS before the request is issued.
This means that this:
Before the request is ever issued.
Now, all of the assets on our site, including anything loaded from a 3rd party, will be loaded over a secure scheme no matter how they are referenced in the page. This could of course result in the asset not being loaded if it isn't actually served over HTTPS, but this directive should be used as an additional layer of protection against loading mixed content and not as a solution to legacy code referencing insecure resources. Support for this directive is currently limited which further indicates that it shouldn't be solely relied upon.
Closely related to the upgrade-insecure-requests directive is the block-all-mixed-content directive. As the name suggests, instead of trying to upgrade the connection this directive simply instructs the browser to not load any resource over HTTP.
Whilst these 2 directives may seem very similar at first glance, there are some subtle differences between them that could make either of them suitable to your particular requirements.
HTTP Strict Transport Security
HSTS is another HTTP response header that allows us to improve the security stance of our site. This header allows the host to force the browser to use HTTPS for all subsequent requests. It doesn't matter how that requests comes about, it could be a link form a 3rd party site, the user themselves could type HTTP://scotthelme.co.uk into the address bar or they could be using a saved bookmark that has the HTTP scheme, the browser will not make the request using HTTP. The request will upgraded to HTTPS before it is made. I've written about HSTS in a lot more detail in my blog, HSTS - The Missing Link In Transport Layer Security, so you can do some further reading there if you like, but I've covered the basic purpose of the HSTS header.
Strict Transport Security
The minimum information required to issue a valid HSTS policy for your site is how long you would like the browser to enforce the policy for. This is done using the
max-age directive specifies the time, in seconds, that the browser must now use only HTTPS to access the site that issued the policy.
You can further extend this protection using the
includeSubDomains directive to, you guessed it, include subdomains!
Strict-Transport-Security: max-age=31536000; includeSubDomains
includeSubDomains directive in the policy, if I were to issue this policy on scotthelme.co.uk and the user were to visit any subdomain of my site, they would be forced to make the request using HTTPS. This offers protection on those subdomains even if the user has never visited them and is handy if you have a domain like cdn.scotthelme.co.uk that the user may not visit. Any assets requested from that domain would also be forced to use HTTPS.
The final addition to our HSTS policy is the
preload directive. This directive allows us to indicate that we want our site to be included into browser preload lists. These lists are hard coded into a browser and means that the browser will know your site wants to use HTTPS exclusively even if the browser has never visited your site.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Once you have added the preload token to your policy you can submit your site to be included in the preload list here: https://hstspreload.appspot.com/ This will get your site included in the Chromium list which is then used by Chrome, Firefox, Safari and Internet Explorer.
Things to note
CSP is issued on a per page basis and the browser does not cache the policy. You need to issue your CSP on every page that you want the browser to enforce it. This differs from HSTS which the browser will cache and continue to enforce, but it is still advised to issue your HSTS policy across your entire site to catch users at any entry point.
You need to be confident that you can serve your entire domain (and subdomains if you included them) over HTTPS before you issue the HSTS policy as it does have the ability to completely break your site if you don't. I'd advise starting with a very short policy expiration so that if things do go wrong, it will expire quickly. You can set a policy to be valid for only 60 seconds using
max-age=60 to start with. From here, you can gradually increase the policy expiration to something more acceptable with the minimum being 3 months for an effective policy.
The CSP can be issued in Report Only mode using the
Content-Security-Policy-Report-Only header instead. This means the browser will not enforce the policy, preventing it form breaking your site during testing, but you can see any violations or errors the policy would have created in the console. You can also use the
report-uri directive to have the browser send you reports when violations occur. This is a great way to get feedback from your policy once it's out in the wild and you can read more details on how to implement that here: CSP and HPKP violation reporting with report-uri.io
Despite having a CSP and HSTS policy, you will always need to have a 301 redirect in place to take users from HTTP to HTTPS. Not all browsers are CSP and HSTS compliant so the 301 will catch those that aren't.
Even if you do decide to go through your entire database and replace all references to http:// with https://, or go protocol relative with //, you should still consider issuing a CSP and HSTS policy. These policies will ensure that even if some assets did slip through the net and you're referencing http:// somewhere, you can avoid getting nasty mixed content warnings in the browser and breaking the security of the session by loading content insecurely. Issuing an additional HTTP response header has an almost insignificant impact, but the improvement in security gained by issuing these headers is incredibly significant. If you couple that with violation reporting for your CSP with https://report-uri.io, you can know about violations as they happen in real-time.