Truly Paranoid Software Updates

What does it mean to support “secure” updates? Nowadays, supply chain attacks are at the forefront of everyone’s mind, so I think it is worth delving into what it really means to make software updates secure.

The simplest version of software updates is just downloading the updated software version from the vendor, and executing it. For example, one can just download a binary from http://vendor.example/updated-binary.

Warning

But this update is happening over http://?

Exactly! This means that any attacker with the ability to intercept/modify your network traffic, can MITM the download of the update, and compromise your machine (there are even off the shelf tools to accomplish this!). So let’s just download it over https://–problem solved, right?

At this point, you can be confident that the download is truly coming from https://vendor.example.

Warning

What if an attacker compromises the web server hosting that endpoint?

In that case, an attacker could update the web server to serve a compromised binary (over that same TLS connection) and compromise your machine. So TLS alone isn’t a panacea.

At this point, most software vendors will reach for code signing. The idea is that the vendor builds the software on a secure machine, signs it with a cryptographic key that they store securely, and then they serve the software update and the signature from their web server. Clients can download the update and verify the signature before installing it. This way, even if an attacker compromises the web server, they can’t forge a signature, so they can’t forge an update.

Warning

What if the signing key gets stolen? Or the build environment gets compromised? Isn’t that just as likely as an attacker compromising a web server?

In theory, vendors can ensure that signing keys and build environments are extremely well protected to help defend against this. But it is true that if an attacker steals the code signing key, they can once again compromise an update. So how can we do better? By default people might reach for key rotation or TPMs. But key rotation and TPMs are only mitigating factors, they don’t change the fundamental risk of a signing key getting stolen. Can we do better?

For source-available software, reproducible builds can offer an improved solution. The idea is that if the Source Code → Binary conversion is done in a reproducible way, then people can verify that a binary they receive from an update was actually built from the source code they can access. This way if an attacker swaps out an update, people can re-compile the software, and notice this themselves.

Warning

If clients have to compile the software to validate the build, why are they downloading a pre-built binary anyways?

This is a fair point! If everyone is already expected to build from source, why even bother with offering pre-built binaries? One possible answer to this is that reproducible builds allow some people to verify updates, and that this provides security for everyone else. But couldn’t an attacker just serve different binaries to different users? Ultimately, yes. Reproducible builds only solve the problem for users that actually verify them.

One solution to this is binary transparency where the idea is that binaries can be logged to a tamper-evident log so as to ensure that all users are given the same binary. This way, every user is guaranteed to get the same binary. So as long as at least one user verifies the reproducible build, the update is secure!

Another really exciting solution is SLSA. SLSA aims to be a much more comprehensive solution to this. SLSA defines a number of different provenance levels:

SLSA levels

Ultimately, SLSA L3:

  • “Prevents tampering during the build—by insider threats, compromised credentials, or other tenants.”
  • “Provides strong confidence that the package was built from the official source and build process.”

It is able to do this by relying on a trusted party (e.g. Github Actions) to certify that a build was done from code commited to main, and was done in a reproducible way. As far as I know, SLSA L3 currently provides the strongest guarantees that can be done for secure software updates. But of course, it still relies on Github as a trusted party (for hosting both the code and the builder)…

Note

Personally, I’ve integrated SLSA into hiSHtory so that people can easily run hishtory update and be guaranteed that they get the latest version built from the latest commit on github. This is especially critical since hiSHtory provides end-to-end encrypted syncing, so it is imperative that the client-side code is securely updated. If you’re curious, check out hiSHtory to see how this can be done to provide secure updates as a first-class feature!