A Practical Guide to Using Signed Ruby Gems - Part 3: Signing your Own

In Part 3, I'll show you how to start signing your own gems. There are actually two different ways of signing gems - I'll show you how to use each method, and then we'll look at the relative merits of each. Note that choosing a gem signing scheme going forward is an active area of discussion in the Ruby community - I hope that you'll join the debate.

The gem signatures we've seen so far have been using "old school" X.509 certificates. This is similar to the model that your web browser uses for SSL certificates: certificates are built up into a multi-rooted hierarchy, with a few certificate authorities (CAs) that everyone trusts. The CAs sign intermediate certificates, and those intermediate certificates sign the leaf nodes of the hierarchy. Certificates can also be "self-signed", which means there's no hierarchy present to verify them, so each certificate must be trusted on its own.

Let's make a new gem and sign it:

$ bundle gem example
$ cd example
$ $EDITOR example.gemspec

In example.gemspec, add a decription and summary:

spec.description   = %q{This gem is sweet!}
spec.summary       = %q{A summary is not the same as a description}

And then add these two lines (replacing yourname) inside the block:

spec.cert_chain  = ['certs/yourname-public.pem']
spec.signing_key = File.expand_path("~/.gem/yourname-private.pem")

The first line says that there is a "certificate chain" that describes how users can verify this gem - right now the chain consists of only one certificate (more on this later). The second line tells rubygems where to find your private key during the gem build process. You can see an example in my gemspec for bundler_signature_check.

So how do we get that key pair? We have rubygems generate it for us (be sure to store your private key somewhere that only you have access to):

$ gem cert --build me@example.com
Public Cert: gem-public_cert.pem
Private Key: gem-private_key.pem
Don't forget to move the key file to somewhere private...
$ mkdir certs
$ mv gem-public_cert.pem certs/yourname-public.pem
$ mv gem-private_key.pem ~/.gem/yourname-private.pem
$ chmod 400 ~/.gem/yourname-private.pem
$ gem build example.gemspec
Successfully built RubyGem
Name: example
Version: 0.0.1
File: example-0.0.1.gem

You've built a signed gem! You can verify that a signature is present by looking at the contents of the .gem file:

$ tar tf example-0.0.1.gem
data.tar.gz
metadata.gz
data.tar.gz.sig
metadata.gz.sig

Those .sig files are the actual signatures. Let's try to install the gem and see if rubygems can verify them:

$ gem install example-0.0.1.gem -P HighSecurity
ERROR:  While executing gem ... (RuntimeError)
    Couldn't verify data signature: Untrusted Signing Chain Root: cert = '/CN=me/DC=example/DC=com', error = 'path "/Users/brad/.gem/trust/cert-72026d198bb9b3134425f5914b9996c943a1cf35.pem" does not exist'

No luck. Rubygems couldn't verify the signing certificate, because there's no chain to a trusted root certificate. We ran in to this in part 1. The solution is to trust the public key we just created:

$ gem cert --add certs/yourname-public.pem
Added '/CN=me/DC=example/DC=com'
$ gem install example-0.0.1.gem -P HighSecurity
Successfully installed example-0.0.1
1 gem installed

The rubygems guide on gem signing has lot more information on these PKI signatures - I highly recommend it for further reading about how this model works, and some details about how to construct certificate chains. The problem with this model - in fact, much of the problem with real-world cryptography - is certificate distribution. How do I get my new public certificate out to the world in a trustworthy manner? The Ruby community hasn't found a way to do this yet - there's very little actual use of signed gems, and no good way to find the public keys of signers. That's why I'd like to take a look at an alternate gem signing proposal - rubygems-openpgp. Check out this short talk by Grant Olson (who is building rubygems-openpgp) about why it's a better model for gem signing:

It's not a silver bullet, but I'm inclined to agree with Grant that an OpenPGP-based system is easier to bootstrap then a full X.509 / CA system. But I'll leave the theory to the experts - let's look at how to actually sign our gem with gpg. First you'll need GPG, and your own gpg key:

# If you're on OSX:
$ brew install gpg
# You can use answer all the questions with the defaults here:
$ gpg --gen-key

Then install the rubygems-openpgp plugin (the paranoid will want to verify its signature - see the author's guide for instructions):

$ gem install rubygems-openpgp

And signing is just an extra switch to gem build:

$ gem build example.gemspec --sign
WARNING:  no homepage specified
  Successfully built RubyGem
  Name: example
  Version: 0.0.1
  File: example-0.0.1.gem
Signing "data.tar.gz"...

You need a passphrase to unlock the secret key for
user: "Bradley Buda <bradleybuda@gmail.com>"
2048-bit RSA key, ID 4A64FE1D, created 2013-01-22

Signing "metadata.gz"...

You need a passphrase to unlock the secret key for
user: "Bradley Buda <bradleybuda@gmail.com>"
2048-bit RSA key, ID 4A64FE1D, created 2013-01-22

Congratulations - you are now in posession of a gem that has not one, but two digital signatures. You can see them peacefully coexisting insde the gem tarball:

$ tar tf example-0.0.1.gem
data.tar.gz
data.tar.gz.asc
data.tar.gz.sig
metadata.gz
metadata.gz.asc
metadata.gz.sig

And you can request that both signatures be verified on installation:

$ gem install example-0.0.1.gem -P HighSecurity --verify

There's no support yet for the OpenPGP --verify flag in bundler (like there is for -P). If you want to enable these flags by default in your environment, you can add them you your .gemrc file like this:

$ cat ~/.gemrc
gem: -P HighSecurity --verify

Unfortunately, you probably won't be able to install many gems this way - both of these flags will abort install if their respective signature type isn't present, and I'm willing to bet there are fewer than five gems on rubygems.org that are signed with both X.509 and OpenPGP certs. However, what I do recommend you do is start signing all of the gems you and your team publish - getting to an ecosystem of signed gems will take time, and we'll only get there through slow and steady growth.

The best thing we can do is encourage signing by default and also make signature verification the default, something you have to opt out of (or "whitelist" gems that are allowed to bypass signature checks). With the recent rubygems.org breach, verification of gems is very much on the mind of the volunteers who run the gem distribution infrastucture, and now is a great time for the community to get involved.

What do you think? Let us know over at Hacker News.