Skip to content

Instantly share code, notes, and snippets.

@jzheaux
Created November 21, 2019 16:47
Show Gist options
  • Save jzheaux/0b4bb5a24c3092b43cc5626362f08173 to your computer and use it in GitHub Desktop.
Save jzheaux/0b4bb5a24c3092b43cc5626362f08173 to your computer and use it in GitHub Desktop.

Why Your App Shouldn't Use HTTP Anywhere

In October 2018, Google kept its HTTP “name and shame” promise when the Chrome team flipped a switch. That switch, was to officially mark any site with an http:// scheme as insecure.

For some sites, the “nohttp” movement had begun in earnest.

But, changing the channel the browser uses is really only the first step. It’s time to clean up your build process. It’s time to clean up your documentation. It’s even time to clean up your old-school eXtensible Markup Language (XML) namespaces.

It’s time to stop using http://. Everywhere.

Let’s take a quick trip down the HTTPS memory lane. After that, you’ll see where http:// trickles into your application and why that’s bad. Finally, you’ll learn how to use the latest addition to this movement, nohttp from the Spring team.

Going nohttp: A Brief History

In January 2015, Mark Nottingham published a W3C paper outlining the need to move the web over to HTTPS. Many of you will remember the days when HTTPS was essentially the realm of authentication and financial transactions.

In fact, at the time that paper was written, barely 40% of Internet traffic was using HTTPS. You can see the following percentages of pages loaded over HTTPS by Chrome by various countries in 2015:

But, the evidence was mounting against ISPs that tampered with each HTTP response. And, against websites that tracked users.

The simple fact was -- and still is -- that HTTP offered no guarantee that you were actually seeing the page you requested. Since that was about 80% of websites at the time, that meant the lion-share of the Internet.

But it’s haaaard!

At that time, much of the Internet wasn’t using HTTPS because it had been historically difficult and expensive to set up.

In April 2016, Let’s Encrypt, a non-profit security organization, was launched. Let’s Encrypt began making HTTPS certificates available free of charge.

They followed that up by developing an open-source protocol for setting up SSL certificates. This eventually resulted in two simple commands you could run on your server to upgrade to HTTPS.

Difficult and expensive? No longer.

But it’s slooooow!

Another barrier was a relic from the dark ages of the Internet: HTTPS was demonstrably slower than HTTP.

Actually, HTTPS performance hasn’t been a performance problem for a very long time, as Adam Langley from Google said about this back in 2010:

SSL/TLS accounts for less than 1% of [our] CPU load, less than 10KB of memory per connection and less than 2% of network overhead.

This Site is “Not Secure”

HTTPS was now fast, cheap, and easy to use. It was time to grease the wheels.

The Google Chrome Security team made a proposal that Chrome start changing their security indicators.

Their research showed that their current indicators did almost nothing to inform the user about insecure connections. One user innocently asked, “Why does the website have a purse icon next to it?”

If a user could mistake a padlock for a purse, how could the Chrome team inform the population of something as complex as browser communication channels?

In the end, the Chrome team progressively changed the way they warned users about using http:// websites:

  • In January 2017, Chrome would add the words “Not Secure” before any HTTP website where credentials or credit cards were being accepted
  • The following year in September, it would ramp that up to any HTTP website at all
  • In October 2018, any HTTP website, when credentials or cards were being entered, the “Not Secure” message would turn red

Okay, now what?

These days, browser HTTPS is at an all-time high, thanks to many factors. In the space of just four years -- 2015 to 2019 -- browser HTTPS traffic has moved from a tragic 40% to an admirable 80%.

Again, you can see the same chart here, just focused on 2019 now:

Let’s quickly pat ourselves on the back! Then, recognize the next problem to solve -- the non-browser HTTP traffic in software that you build.

Repositories

The fact is that the exact same principles apply when it comes to non-browser, HTTPS traffic.

Take, for example, the fact that most software depends on additional software, which an app likely downloads at build time from one or more artifact repositories.

In Maven, you indicate a dependency like so:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

You'd expect to get this dependency from a repository.

However, if you point to a repository using HTTP, you have the same *lack* of guarantee you have when using HTTP in the browser:

<repositories>
  <id>central</id>
  <url>http://repo1.maven.org/maven2</url> <!-- ouch! -->
</repositories>

By pointing to http://repo1.maven.org/maven2, you lack the guarantee that:

  • You are actually talking to Maven Central
  • You received the dependencies asked for without an intermediary having modified it

Which dependencies would you like to know for sure they are what they say they are? I’m pretty sure you’d say the same thing I do when my kids ask which of them I love the most: “All of them.”

Happily, the fix is easy: Simply point to [https://repo1.maven.org/maven2](http://repo1.maven.org/maven2) instead. Now, when you build your project, you'll download its dependencies from a known and trusted location.

Documentation

What about in your documentation?

When users read your documentation, certainly they will click on the links that you provide to other sites:

For more information about this feature of our product, check out [this article](https://www.troyhunt.com/heres-why-your-static-website-needs-https/).

While 90% of the top 100 sites use HTTPS by default and 96% of them work with HTTPS, we are still a long way from websites turning off port 80 altogether.

This means that there is still a small window where a visitor could be compromised by clicking an HTTP link that you provide them in your documentation.

You might be thinking, "but HSTS!" since, of course, HTTP Strict Transport Security (HSTS) is supposed to force browsers to talk HTTPS even if the user requests HTTP. It doesn’t help this scenario.

First, the site you are linking to may not be using HSTS. It’s certainly still an optional feature.

Second, if it’s the first time the visitor has visited that site, the HTTP payload can be intercepted and any redirection to HTTPS prevented. It’s common for developers to forget that the HSTS header isn’t actually respected by browsers unless it’s part of an HTTPS response.

The simple fix is to point to the https:// version of the site in which your documentation links.

Tests

You may have integration tests that reach out to external services.

I have a test, for example, that confirms correct integration with Okta which looks something like this:

this.client.post("https://dev-123456.oktapreview.com")
  .param("username", "user")
  .param("password", …)

Would you agree that it would be a good thing to know for sure that my integration test is really talking to Okta? Also, to know that the response I’m getting back is the unaltered response from Okta?

Consider the scenario where you don’t have confidence that your continuous delivery pipeline is telling you the truth, and you can see why simple maneuvers over to HTTPS are valuable.

Namespaces

XML namespaces are pretty curious since they are usually arcane-seeming things that we simply copy-paste from library documentation (ah look, documentation again). We likely don’t think about XML namespaces very much.

However, consider the following XML payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
  "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
  <!-- bean definitions here -->
</beans>

This is certainly an older version of Spring, but I did, for the sake of illustration, lift it from the results of googling for “spring beans xml”.

Now to be clear, Spring ships with the corresponding Document Type Definitions (DTDs) and so it resolves them locally, by default. All safe and sound.

But if Spring ended up calling out for some reason, there would be no guarantee that the code is actually getting the DTD it requested. This could lead to an XML External Entity (XXE) vulnerability.

The great thing is that modern Spring can resolve these locally with HTTP or HTTPS, so the following will work:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
  "https://www.springframework.org/dtd/spring-beans-2.0.dtd">

And with that very simple change, you get an extra layer of security. If Spring does actually make this call to download the DTD, it will be over HTTPS.

You Need HTTPS with XSDs, too

This is a smaller problem with more modern XML definitions that use XML Schema Definitions (XSDs):

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

XML resolves namespaces via a key-value pair. The http://www.springframework.org/schema/beans is the key, and the value is http://www.springframework.org/schema/beans/spring-beans.xsd.

The value is the one you’re concerned about.

For the same reasons already explained, this should instead be:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

Demo Time

Now that you’re ready to get rid of any reference to http:// in your projects, how should you proceed?

Doing a manual search is okay, but it’s cumbersome, and you’ll forget**.**

You need something that is going to run quickly, on every build of your code. This makes sure that your Continuous Integration pipeline doesn’t ship your artifact if it was built using http://.

To follow along, then, you’ll need a simple Java project that has references to http:// in it.

Second, you’ve hopefully at least seen Gradle or Maven before and are comfortable at least tweaking it.

And if you aren’t using Java, maybe consider applying these same principles to a plugin in your own language!

Compatibility

Note that while we’ll focus on Java, your project need only be Java Virtual Machine (JVM)-based and at Java 1.8 or higher (sorry no Android support just yet).

Scrubbing with nohttp

The simplest way to confirm zero references to http:// in your Java project is by introducing the following Gradle plugin:

plugins {
  id "io.spring.nohttp" version "0.0.3.RELEASE"
}

Then run gradle nohttp or gradle check and watch it go to work! The result will be a list of http:// links that should be changed.

nohttp with Maven

If you’re using Maven, then there’s a bit more boilerplate, but the concept is the same. Simply add the plugin to the <plugins> section of your pom:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-checkstyle-plugin</artifactId>
  <version>3.0.0</version>
  <dependencies>
    <dependency>
      <groupId>com.puppycrawl.tools</groupId>
      <artifactId>checkstyle</artifactId>
      <version>8.18</version>
    </dependency>
    <dependency>
      <groupId>io.spring.nohttp</groupId>
      <artifactId>nohttp-checkstyle</artifactId>
      <version>0.0.3.RELEASE</version>
    </dependency>
  </dependencies>
  <configuration>
    <configLocation>nohttp-checkstyle.xml</configLocation>
    <includes>**/*</includes>
    <excludes>.git/**/*,target/**/*</excludes>
    <sourceDirectories>./</sourceDirectories>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>check</goal>
      </goals>
    </execution>
  </executions>
</plugin>

And then add the following into a file you’ll name nohttp-checkstyle.xml:

<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
  "https://www.puppycrawl.com/dtds/configuration_1_3.dtd">

<module name="Checker">
  <property name="charset" value="UTF-8"/>
  <property name="fileExtensions" value=""/>
  <module name="io.spring.nohttp.checkstyle.check.NoHttpCheck">
  </module>
</module>

Finally, run mvn checkstyle:check or mvn verify to see it go to work.

Exclusions

Of course, there are always exceptions, even begrudging ones.

There will be a website here or there that doesn’t use HTTPS. There will be XML namespaces that aren’t served over HTTPS. There will be third-party software (or even parts of Java itself!) that hardcodes its usage of HTTP.

Or, quite simply, your project may be huge and you need to change things a bit more iteratively.

In these cases, you can create a whitelist of exclusions.

Exclusions with Gradle

nohttp currently has slicker support for Gradle than Maven. So, it’s quite easy to add exclusions by simply introducing a file called /etc/nohttp/whitelist.lines into your project.

The file can have regular expressions in it, like so:

http://some.exclusive.url/that/doesnt/support/http
http://[.*].specialdomain.com

Once you build again, you’ll see nohttp suppressing errors for URLs matching the given patterns.

Exclusions with Maven

Doing the same with Maven is still quite simple, but it requires one extra step.

Remember the nohttp-checkstyle.xml file you added? Just check the NoHttpCheck module definition like so:

<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck">
  <property name="whitelistFileName" value="etc/nohttp/whitelist.lines"/>
</module>

And, it will work the same as the Gradle plugin.

Of course, you can edit value to point to a different file location.

Exclusions with Checkstyle

nohttp is based on the Checkstyle project.

So, you can also use standard Checkstyle exclusion syntax to skip a block of code:

// CHECKSTYLE:OFF
if ("http://my.special.url".equals(url)) {
// CHECKSTYLE:ON

Because you already included Checkstyle when you added nohttp to your project, no further configuration is necessary for this to work.

Summary

Okay, so you’ve seen the immense efforts and benefits that have come from the browser HTTPS movement. The next step is non-browser.

You can start with simple things, like links to repositories, your documentation, your tests, and your namespaces.

For Java projects, the nohttp project makes this a cinch. Simply add to your project, configure, and enjoy the additional layer of security.

Further Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment