On secure software updates
Software updates can be inconvenient or annoying for a lot of people. Sometimes the process is problematic or the update itself is a step backwards. Then there are the stories of updates installing malware or breaking things.
Developers who care about the people who use their software, should take an interest in making the process safer and less painful.
A short history of software updates
A long, long, time ago people updated software by going to their local shop, buying a box of diskettes or a disc, and launching a long installation process. As the internet burst to life and got faster and cheaper, updates became easier to get. Now you could just download the new version, run the installer, and poof, your software was up-to-date.
With the ability to ship software quickly over the internet, release cycles became shorter, and vendors started building automatic updaters into their programs. You no longer had to download updates manually. Instead, the app would connect to the vendor’s server and check if there was a new version. If there was one, it would show you release notes and ask if you’d like to upgrade.
The next revolution came with Google’s Chrome browser — it invisibly downloaded new versions and installed them in background each time you quit the app. The next time you started Chrome, it had already upgraded to the new version and was ready to use. You no longer had to do anything to get updates, they just happened. This was a huge usability improvement. With users no longer being annoyed by updaters, this also allowed for even more frequent releases.
Automatic updates, however, begged the question: how would the current version verify that the new update came from the original vendor? What if somebody sent a fake update with malware? The answer is clear to anyone familiar with cryptography: create a pair of signing keys – one public and one private, distribute the public key with the software and sign updates with the private key. When the program downloads the update, it uses the public key to verify the signature. If the signature is good, the update is installed. If not, it’s dropped, since it might be malicious or corrupted.
In the above update scheme, no certification authority, or public key infrastructure is used. Since the developer controls both the updater software and key distribution, there’s no need for a third-party to vouch for the keys. That’s why this scheme was implemented and used even before HTTPS (HTTP over Transport Layer Security) was widely deployed.
If you have used macOS apps from smaller software vendors, chances are you’ve updated them with the wonderful Sparkle updater framework: an open source component that is embedded in many macOS apps to download, verify, and install updates. Until a few years ago, when cheaper SSL certificates became available, many of these apps didn’t even use secure HTTPS connection to serve updates. The updates themselves, however, were protected with the update scheme described above to ensure that the original developer created them (or, more accurately, the holder of the private key).
Secure connection doesn’t guarantee secure app updates
Now that we have cheap and even free SSL certificates (thanks to Let’s Encrypt) and the ability to deploy servers that send updates via a secure connection, should we abandon previous efforts of signing updates? Should we just switch to sending them over HTTPS? No!
There is an important difference: HTTPS secures connections between the update server and the program, but the signatures secure the whole path from the developer to the program. This means if updates are not signed, the attacker has only to hack the server to send a fake update. This is why, for example, someone who hacks the WordPress update server can hack every single WordPress site out there—or 26% of the Web—by sending a malicious update.
Another problem with HTTPS is that it relies on trusting hundreds of certification authorities from many countries. Ideally, these authorities never get compromised and only issue certificates correctly. But from these many incidents we know that they sometimes fail to do so.
On the other hand, after implementing secure signing, some vendors claim they need not serve signed updates over HTTPS, and continue sending them through an unsecured connection. While it’s true that an attacker won’t be able to send a fake update in this way, they can prevent the app from updating by sending an out-of-date information (“there’s no new version”). This is not a good thing if the update contains important security fixes, or even exploit vulnerabilities in the updater itself.
At a minimum, a proper updater must receive updates over a secure channel, such as HTTPS, and it must verify that the vendor cryptographically signed the updates
* * *
How we deliver updates securely (and how you can too)
At Peerio, we use Electron for our desktop app, which allows us to create a cross-platform program that runs on Windows, macOS, and Linux from a single code base. Electron uses the Squirrel framework for updates on Windows and macOS but lacks an updater for Linux.
Unfortunately, although Squirrel is newer, it is a step back from Sparkle. It requires deploying complicated server software instead of publishing simple static files on the web server. And it doesn’t use the above mentioned signature scheme to ensure that updates come from the original developer.
We explored options, such as The Update Framework) (great design, but too complicated for our purposes), and electron-updater (what we used initially: simple, with static files, supports updating via GitHub Releases, but has no proper signature verification and, until a few months ago, no Linux support).
In the end we wrote our own updater with the following properties:
- Tiny code base for fewer bugs and ease of auditing.
- Proper signature verification.
- Update information via static files.
- Multiple sources of updates, including GitHub releases.
- Support for all our three platforms: Windows, macOS, Linux.
The Peerio updater works by:
- Periodically downloading the update manifest (a small text file) from one of the predefined locations (it only supports HTTPS protocol),
- Verifying the signature of this manifest,
- And then using the information from the manifest to download the new version of the program.
The manifest itself contains a list of file locations for each platform, SHA-512 hash and size of each file. The hash is trusted because the manifest contents is signed by our key.
For signing, we use Ed25519—the same algorithm we use for cryptographic signatures in Peerio itself—in OpenBSD’s signify format. Because the manifest is a simple plain text file, signed with signify, anyone can manually verify our files. We have even published instructions on how to do it on Linux.
We have open sourced the Peerio Updater and invite anyone interested to audit or use it in their own Electron projects.
* * *
The future of updates
With the update delivery and installation problems solved, how do we make sure that all users receive the same updates? For example, what if a powerful entity, such as government, orders a vendor to create a backdoored update for a specific targeted user? How do we make sure that every update delivered to every user is exactly the same? How do we audit which updates were delivered and when?
It turns out we can solve this problem by publishing hashes of updates into some immutable globally available registry (or blockchain). The updater then can check this registry to make sure it receives the same update as everyone else. This work remains a subject of research—notably, Paragon Initiative Enterprises is working on a practical solution—but we’re getting closer to making software updates even more secure.