Securing RubyGems with TUF, Part 3

Applying The Update Framework (TUF) to RubyGems to secure it against nefarious activity.

Written by Xavier Shay.

This is the third and final part of the Securing RubyGems with TUF series. Start with part 1 and part 2 if you haven’t read it yet.

In the last post, we covered how The Update Framework (TUF) enables developers to securely sign for their code, protecting clients from installing maliciously modified gems. In this post we cover a grab bag of other features provided by TUF. It defends against many more attacks than I would have thought of by myself!

Timeliness

If an attacker gains access to the RubyGems server, it is trivial for them to prevent further updates from reaching clients by simply shutting down or otherwise crippling the server. This attack is highly visible though, since clients will start receiving errors.

A more subtle attack is to serve a version of a targets file (such as targets.txt or verified.txt) that contains references to gems with known security vulnerabilities. Note that the attacker cannot serve up arbitrary content since the targets file is signed by an offline key, but they can reuse an old one. There is no way for a client to know it is receiving stale information.

TUF defends against this attack by requiring that all metadata files contain an expiresheader, after which the client must not trust that file.

# targets/verified.txt
{
"signature": { "keyid": "offline", "sig": "301db8518827759236ca4" },
"signed": {
"expires": "2014-12-06 11:59:59 UTC",
"files": {
"my-super-file.txt": "cb18ca7e4084820d53dc444b97c253b3"
}
}
}

Files like verified.txt that are signed by the offline key need to have expiration dates rather far in the future. To enable updates to these files before they expire, TUF also provides a timestamp.txt metadata file that expires every minute, and points to the latest versions of files. If clients always start with timestamp.txt, they know they are getting fresh information. Note that timestamp.txt is still vulnerable to spoofing if an attacker gains access to the server, but it protects against malicious mirrors and enables legitimate updates to long-expiring files.

Consistency

Updating content on a file system, especially one distributed geographically by CDNs, is not atomic. This is a problem: if a client downloads a new version of a targets.txt with an old version of a file it refers to, it will fail signature verification and look like an attack.

TUF solves this by storing all mutable files with their digest in their file name. A new metadata file release.txt contains the mapping of filenames (without digest) to the current version.

# release.txt
{
"signatures": { "keyid": "online", "sig": "58fed8dac1ba8d755dbb6b" },
"signed": {
"expires": "2013-12-01 11:59:59 UTC",
"meta": {
"targets.txt": "6b02ee4c1a2dad2bf8bb08f8eba58f19",
"targets/verified.txt": "0d0de21a2c5f3f4151fc66d60667e564",
"targets/recent.txt": "bd7cd4f115806a40f2e47f6d40aee21c",
"targets/verified/cane.txt": "56e07ea730ed44f74f4c1e37d5572d52"
}
}
}
}

For RubyGems, most files (such as the gem files themselves) are immutable so we do not need to include a digest in their file name. This is handy because it means everything is backwards compatible. The gem files are still available in the same location, so a non-TUF aware client can simply grab the file directly without going via the metadata. For the mutable index files, we write two copies of the file: one with a digest in the filename to support TUF, one without to support legacy clients.

With this in place, timestamp.txt only needs to point to the most recent release.txt. The final system looks like:

And more!

This series has only scratched the surface of TUF. It also contains features for updating the offline key (in case of compromise), threshold signing (require that two developers out of three sign for a gem), further timeliness guarantees, denial of service defense, and optimizations for large numbers of files.

Our implementation of TUF is heavily inspired (if not outright copied) from PEP 458: Surviving a Compromise of PyPI. That and the TUF specification are the best places to learn more. There is also a proof-of-concept TUF implementation for RubyGemswhere you can actually experiment with the concepts in this series.

There is still a lot of work remaining before we can roll this change out to the community, but the Rubgems and Bundler maintainers are onboard. Get in touch if you would like to help out. Otherwise, stay tuned for more soon!