Recently I started looking into the current state of package signing on RubyGems and considering what it might look like in the future.

To this end, I also ended up looking into how package signing is handled by Rust, Python, and Node.js.

The tl;dr is that nobody does package signing and that it’s a really hard problem to solve.

Ruby (RubyGems)

Let’s start with the current state of gem signing in the Ruby world.

RubyGems has had support for cryptographically signing packages since v0.8.11 (released in 2005). Gems are signed using X.509 keys/certificates (ie. the same kind used for HTTPS).

Gems are not signed by default when published and signatures are only checked if the user specifically chooses to check them.

What’s more, there are no established trusted certificate chains (this would be the CA system in the world of HTTPS) so every developer needs to make their own decisions about which certificates to trust.

The end result is that almost no library authors sign their gems and even fewer developers check signatures for the gems they use.

Python (PIP)

PIP has no mechanism for cryptographically signing packages. It has a hash checking mode that optionally verify packages against a given set of SHA256 (or stronger) hashes, but that’s it.

Rust (Cargo)

Cargo doesn’t implement any form of cryptographic package signatures. There’s discussion of implementing TUF support for Crates, but the issue remains open and I don’t believe that it’s particularly close to being resolved.

JavaScript/Node.js (NPM)

NPM takes an interesting approach: all packages are signed by the NPM registry themselves when published.

As far as I can tell, package authors cannot sign packages using their own keys.

Has anyone solved this?

I’d be extremely interested in learning if any package manager (specifically for code modules/libraries) has successfully solved the signature problem. As far as I can tell, nobody has.

The hard problem isn’t the actual signature part, that has been solved in several ways quite successfully (PGP/GPG signatures, X.509 signatures, or my favourite, minisign/signify). The really hard problem is what keys to trust and how to handle key rotation, lost keys, and changing authors/releasers.

Linux distribution maintainers solve this by having a very small pool of developers who are authorised to make releases and sign them. This is not a feasible approach for something like RubyGems which has thousands of independent developers.

Threat models

What are the possible threats are we trying to protect against with package signing?

As far as I can tell, these are the main threats facing packaging systems (in no particular order):

  1. Overall package data integrity (either from a bad download or some other corruption like cosmic rays flipping bits)
  2. Packages tampered with by a MITM attack
  3. Malicious updates from compromised RubyGems accounts? (See the strong_password compromise)

Data integrity

This class of problems is already well guarded against using the checksums included in each gem package. Additionally the RubyGems API returns a SHA1 checksum for each gem.

MITM attacks

MITM attacks are already quite effectively mitigated against by the use of HTTPS. For HTTPS we get to rely on the whole CA ecosystem and RubyGems doesn’t need to implement their own certificate/trust infrastructure.

It’s possible that there could be some sort of nation-state level attack that could use a compromised CA to intercept gem downloads, but this can be guarded against with the use of pinned certificates included with RubyGems or even Certificate Transparency for arbitrary gem hosts.

Compromised accounts

I’d argue that is the most pressing security issue with RubyGems. RubyGems.org has been around for a long time now and two factor authentication is a relatively recent addition. Many (probably most) gem authors don’t have TFA enabled on their accounts.

Moving towards a world in which most (if not all) RubyGems accounts have TFA enabled on their accounts would be the single biggest security gain I can envision in the near future. The key infrastructure problem is far harder…

Personally I’d like to see TFA become compulsory for publishing gems, but we need to ensure that we don’t make it overly onerous to publish gems in the process.

I’d also like to implement Pwned Passwords checking for RubyGems.org accounts using the new k-Anonymity based API. This is something I’ll be looking into in the near future if someone else doesn’t beat me to it.

The future of signed gems?

I’m somewhat pessimistic about the future of signed RubyGems, largely because of the difficulty of the trust model.

One possibility would be to adopt a NPM-like model where RubyGems.org signs all gem releases. While this wouldn’t solve the compromised accounts issue, it would allow for offline/out-of-band verification that a downloaded gem is the same as the one on RubyGems.org.

One possible advantage of this approach would be that it should be relatively simple to use the existing X.509 signature support for this. It could even use the existing HTTPS certificates for rubygems.org… (though it might be necessary to use a trusted time server like Roughtime for the signatures so that when the certificate expires, it doesn’t break existing releases).

Thoughts?

Do you have ideas about how we could spur the adoption of two factor authentication for RubyGems? Have you come up with a cool plan for gem signing? I’d love to hear from you!

I’m @matiaskorhonen on Twitter or you can email me at me@matiaskorhonen.fi

Further reading