2007-11-10 02:48:56 -05:00
|
|
|
#--
|
|
|
|
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
|
|
|
|
# All rights reserved.
|
|
|
|
# See LICENSE.txt for permissions.
|
|
|
|
#++
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
require 'rubygems/exceptions'
|
2011-03-01 04:41:32 -05:00
|
|
|
require 'fileutils'
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2013-03-05 17:40:53 -05:00
|
|
|
begin
|
|
|
|
require 'openssl'
|
|
|
|
rescue LoadError => e
|
|
|
|
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
|
|
|
|
e.message =~ / -- openssl$/
|
2013-09-13 15:58:57 -04:00
|
|
|
|
|
|
|
module OpenSSL # :nodoc:
|
|
|
|
class Digest # :nodoc:
|
|
|
|
class SHA1 # :nodoc:
|
|
|
|
def name
|
|
|
|
'SHA1'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
module PKey # :nodoc:
|
|
|
|
class RSA # :nodoc:
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2013-03-05 17:40:53 -05:00
|
|
|
end
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# = Signing gems
|
2011-01-18 19:08:49 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# The Gem::Security implements cryptographic signatures for gems. The section
|
2007-11-10 02:48:56 -05:00
|
|
|
# below is a step-by-step guide to using signed gems and generating your own.
|
|
|
|
#
|
|
|
|
# == Walkthrough
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# === Building your certificate
|
|
|
|
#
|
2007-11-10 02:48:56 -05:00
|
|
|
# In order to start signing your gems, you'll need to build a private key and
|
|
|
|
# a self-signed certificate. Here's how:
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# # build a private key and certificate for yourself:
|
|
|
|
# $ gem cert --build you@example.com
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# This could take anywhere from a few seconds to a minute or two, depending on
|
|
|
|
# the speed of your computer (public key algorithms aren't exactly the
|
|
|
|
# speediest crypto algorithms in the world). When it's finished, you'll see
|
|
|
|
# the files "gem-private_key.pem" and "gem-public_cert.pem" in the current
|
|
|
|
# directory.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# First things first: Move both files to ~/.gem if you don't already have a
|
|
|
|
# key and certificate in that directory. Ensure the file permissions make the
|
|
|
|
# key unreadable by others (by default the file is saved securely).
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# Keep your private key hidden; if it's compromised, someone can sign packages
|
|
|
|
# as you (note: PKI has ways of mitigating the risk of stolen keys; more on
|
|
|
|
# that later).
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# === Signing Gems
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# In RubyGems 2 and newer there is no extra work to sign a gem. RubyGems will
|
|
|
|
# automatically find your key and certificate in your home directory and use
|
|
|
|
# them to sign newly packaged gems.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# If your certificate is not self-signed (signed by a third party) RubyGems
|
|
|
|
# will attempt to load the certificate chain from the trusted certificates.
|
|
|
|
# Use <code>gem cert --add signing_cert.pem</code> to add your signers as
|
|
|
|
# trusted certificates. See below for further information on certificate
|
|
|
|
# chains.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# If you build your gem it will automatically be signed. If you peek inside
|
|
|
|
# your gem file, you'll see a couple of new files have been added:
|
|
|
|
#
|
|
|
|
# $ tar tf your-gem-1.0.gem
|
2007-11-10 02:48:56 -05:00
|
|
|
# metadata.gz
|
2012-11-29 01:52:18 -05:00
|
|
|
# metadata.gz.sum
|
|
|
|
# metadata.gz.sig # metadata signature
|
|
|
|
# data.tar.gz
|
|
|
|
# data.tar.gz.sum
|
|
|
|
# data.tar.gz.sig # data signature
|
|
|
|
#
|
|
|
|
# === Manually signing gems
|
|
|
|
#
|
|
|
|
# If you wish to store your key in a separate secure location you'll need to
|
|
|
|
# set your gems up for signing by hand. To do this, set the
|
|
|
|
# <code>signing_key</code> and <code>cert_chain</code> in the gemspec before
|
|
|
|
# packaging your gem:
|
|
|
|
#
|
|
|
|
# s.signing_key = '/secure/path/to/gem-private_key.pem'
|
|
|
|
# s.cert_chain = %w[/secure/path/to/gem-public_cert.pem]
|
|
|
|
#
|
|
|
|
# When you package your gem with these options set RubyGems will automatically
|
|
|
|
# load your key and certificate from the secure paths.
|
|
|
|
#
|
|
|
|
# === Signed gems and security policies
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# Now let's verify the signature. Go ahead and install the gem, but add the
|
2012-11-29 01:52:18 -05:00
|
|
|
# following options: <code>-P HighSecurity</code>, like this:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# # install the gem with using the security policy "HighSecurity"
|
2012-11-29 01:52:18 -05:00
|
|
|
# $ sudo gem install your.gem -P HighSecurity
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# The <code>-P</code> option sets your security policy -- we'll talk about
|
|
|
|
# that in just a minute. Eh, what's this?
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# $ gem install -P HighSecurity your-gem-1.0.gem
|
|
|
|
# ERROR: While executing gem ... (Gem::Security::Exception)
|
|
|
|
# root cert /CN=you/DC=example is not trusted
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# The culprit here is the security policy. RubyGems has several different
|
|
|
|
# security policies. Let's take a short break and go over the security
|
|
|
|
# policies. Here's a list of the available security policies, and a brief
|
|
|
|
# description of each one:
|
|
|
|
#
|
|
|
|
# * NoSecurity - Well, no security at all. Signed packages are treated like
|
|
|
|
# unsigned packages.
|
|
|
|
# * LowSecurity - Pretty much no security. If a package is signed then
|
|
|
|
# RubyGems will make sure the signature matches the signing
|
|
|
|
# certificate, and that the signing certificate hasn't expired, but
|
|
|
|
# that's it. A malicious user could easily circumvent this kind of
|
|
|
|
# security.
|
|
|
|
# * MediumSecurity - Better than LowSecurity and NoSecurity, but still
|
|
|
|
# fallible. Package contents are verified against the signing
|
|
|
|
# certificate, and the signing certificate is checked for validity,
|
|
|
|
# and checked against the rest of the certificate chain (if you don't
|
|
|
|
# know what a certificate chain is, stay tuned, we'll get to that).
|
|
|
|
# The biggest improvement over LowSecurity is that MediumSecurity
|
|
|
|
# won't install packages that are signed by untrusted sources.
|
|
|
|
# Unfortunately, MediumSecurity still isn't totally secure -- a
|
|
|
|
# malicious user can still unpack the gem, strip the signatures, and
|
|
|
|
# distribute the gem unsigned.
|
|
|
|
# * HighSecurity - Here's the bugger that got us into this mess.
|
|
|
|
# The HighSecurity policy is identical to the MediumSecurity policy,
|
|
|
|
# except that it does not allow unsigned gems. A malicious user
|
|
|
|
# doesn't have a whole lot of options here; he can't modify the
|
|
|
|
# package contents without invalidating the signature, and he can't
|
|
|
|
# modify or remove signature or the signing certificate chain, or
|
|
|
|
# RubyGems will simply refuse to install the package. Oh well, maybe
|
|
|
|
# he'll have better luck causing problems for CPAN users instead :).
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# The reason RubyGems refused to install your shiny new signed gem was because
|
|
|
|
# it was from an untrusted source. Well, your code is infallible (naturally),
|
|
|
|
# so you need to add yourself as a trusted source:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# # add trusted certificate
|
|
|
|
# gem cert --add ~/.gem/gem-public_cert.pem
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# You've now added your public certificate as a trusted source. Now you can
|
|
|
|
# install packages signed by your private key without any hassle. Let's try
|
|
|
|
# the install command above again:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# # install the gem with using the HighSecurity policy (and this time
|
|
|
|
# # without any shenanigans)
|
2012-11-29 01:52:18 -05:00
|
|
|
# $ gem install -P HighSecurity your-gem-1.0.gem
|
|
|
|
# Successfully installed your-gem-1.0
|
|
|
|
# 1 gem installed
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# This time RubyGems will accept your signed package and begin installing.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# While you're waiting for RubyGems to work it's magic, have a look at some of
|
|
|
|
# the other security commands by running <code>gem help cert</code>:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# Options:
|
2012-11-29 01:52:18 -05:00
|
|
|
# -a, --add CERT Add a trusted certificate.
|
|
|
|
# -l, --list [FILTER] List trusted certificates where the
|
|
|
|
# subject contains FILTER
|
|
|
|
# -r, --remove FILTER Remove trusted certificates where the
|
|
|
|
# subject contains FILTER
|
|
|
|
# -b, --build EMAIL_ADDR Build private key and self-signed
|
|
|
|
# certificate for EMAIL_ADDR
|
|
|
|
# -C, --certificate CERT Signing certificate for --sign
|
|
|
|
# -K, --private-key KEY Key for --sign or --build
|
|
|
|
# -s, --sign CERT Signs CERT with the key from -K
|
|
|
|
# and the certificate from -C
|
|
|
|
#
|
|
|
|
# We've already covered the <code>--build</code> option, and the
|
|
|
|
# <code>--add</code>, <code>--list</code>, and <code>--remove</code> commands
|
|
|
|
# seem fairly straightforward; they allow you to add, list, and remove the
|
|
|
|
# certificates in your trusted certificate list. But what's with this
|
|
|
|
# <code>--sign</code> option?
|
|
|
|
#
|
|
|
|
# === Certificate chains
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# To answer that question, let's take a look at "certificate chains", a
|
|
|
|
# concept I mentioned earlier. There are a couple of problems with
|
|
|
|
# self-signed certificates: first of all, self-signed certificates don't offer
|
|
|
|
# a whole lot of security. Sure, the certificate says Yukihiro Matsumoto, but
|
|
|
|
# how do I know it was actually generated and signed by matz himself unless he
|
|
|
|
# gave me the certificate in person?
|
|
|
|
#
|
|
|
|
# The second problem is scalability. Sure, if there are 50 gem authors, then
|
|
|
|
# I have 50 trusted certificates, no problem. What if there are 500 gem
|
|
|
|
# authors? 1000? Having to constantly add new trusted certificates is a
|
|
|
|
# pain, and it actually makes the trust system less secure by encouraging
|
|
|
|
# RubyGems users to blindly trust new certificates.
|
|
|
|
#
|
|
|
|
# Here's where certificate chains come in. A certificate chain establishes an
|
|
|
|
# arbitrarily long chain of trust between an issuing certificate and a child
|
|
|
|
# certificate. So instead of trusting certificates on a per-developer basis,
|
|
|
|
# we use the PKI concept of certificate chains to build a logical hierarchy of
|
|
|
|
# trust. Here's a hypothetical example of a trust hierarchy based (roughly)
|
|
|
|
# on geography:
|
|
|
|
#
|
|
|
|
# --------------------------
|
2012-11-29 01:52:18 -05:00
|
|
|
# | rubygems@rubygems.org |
|
2007-11-10 02:48:56 -05:00
|
|
|
# --------------------------
|
|
|
|
# |
|
|
|
|
# -----------------------------------
|
|
|
|
# | |
|
|
|
|
# ---------------------------- -----------------------------
|
2012-11-29 01:52:18 -05:00
|
|
|
# | seattlerb@seattlerb.org | | dcrubyists@richkilmer.com |
|
2007-11-10 02:48:56 -05:00
|
|
|
# ---------------------------- -----------------------------
|
|
|
|
# | | | |
|
|
|
|
# --------------- ---------------- ----------- --------------
|
2012-11-29 01:52:18 -05:00
|
|
|
# | drbrain | | zenspider | | pabs@dc | | tomcope@dc |
|
2007-11-10 02:48:56 -05:00
|
|
|
# --------------- ---------------- ----------- --------------
|
|
|
|
#
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# Now, rather than having 4 trusted certificates (one for drbrain, zenspider,
|
|
|
|
# pabs@dc, and tomecope@dc), a user could actually get by with one
|
|
|
|
# certificate, the "rubygems@rubygems.org" certificate.
|
|
|
|
#
|
|
|
|
# Here's how it works:
|
|
|
|
#
|
|
|
|
# I install "rdoc-3.12.gem", a package signed by "drbrain". I've never heard
|
|
|
|
# of "drbrain", but his certificate has a valid signature from the
|
|
|
|
# "seattle.rb@seattlerb.org" certificate, which in turn has a valid signature
|
|
|
|
# from the "rubygems@rubygems.org" certificate. Voila! At this point, it's
|
|
|
|
# much more reasonable for me to trust a package signed by "drbrain", because
|
|
|
|
# I can establish a chain to "rubygems@rubygems.org", which I do trust.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# === Signing certificates
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# The <code>--sign</code> option allows all this to happen. A developer
|
|
|
|
# creates their build certificate with the <code>--build</code> option, then
|
|
|
|
# has their certificate signed by taking it with them to their next regional
|
|
|
|
# Ruby meetup (in our hypothetical example), and it's signed there by the
|
|
|
|
# person holding the regional RubyGems signing certificate, which is signed at
|
|
|
|
# the next RubyConf by the holder of the top-level RubyGems certificate. At
|
|
|
|
# each point the issuer runs the same command:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# # sign a certificate with the specified key and certificate
|
|
|
|
# # (note that this modifies client_cert.pem!)
|
|
|
|
# $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem
|
|
|
|
# --sign client_cert.pem
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# Then the holder of issued certificate (in this case, your buddy "drbrain"),
|
|
|
|
# can start using this signed certificate to sign RubyGems. By the way, in
|
|
|
|
# order to let everyone else know about his new fancy signed certificate,
|
|
|
|
# "drbrain" would save his newly signed certificate as
|
|
|
|
# <code>~/.gem/gem-public_cert.pem</code>
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# Obviously this RubyGems trust infrastructure doesn't exist yet. Also, in
|
|
|
|
# the "real world", issuers actually generate the child certificate from a
|
2007-11-10 02:48:56 -05:00
|
|
|
# certificate request, rather than sign an existing certificate. And our
|
|
|
|
# hypothetical infrastructure is missing a certificate revocation system.
|
|
|
|
# These are that can be fixed in the future...
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# At this point you should know how to do all of these new and interesting
|
|
|
|
# things:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# * build a gem signing key and certificate
|
|
|
|
# * adjust your security policy
|
|
|
|
# * modify your trusted certificate list
|
|
|
|
# * sign a certificate
|
|
|
|
#
|
2011-01-18 19:08:49 -05:00
|
|
|
# == Manually verifying signatures
|
|
|
|
#
|
|
|
|
# In case you don't trust RubyGems you can verify gem signatures manually:
|
|
|
|
#
|
|
|
|
# 1. Fetch and unpack the gem
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# gem fetch some_signed_gem
|
|
|
|
# tar -xf some_signed_gem-1.0.gem
|
2011-01-18 19:08:49 -05:00
|
|
|
#
|
|
|
|
# 2. Grab the public key from the gemspec
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# gem spec some_signed_gem-1.0.gem cert_chain | \
|
|
|
|
# ruby -ryaml -e 'puts YAML.load_documents($stdin)' > public_key.crt
|
2011-01-18 19:08:49 -05:00
|
|
|
#
|
|
|
|
# 3. Generate a SHA1 hash of the data.tar.gz
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# openssl dgst -sha1 < data.tar.gz > my.hash
|
2011-01-18 19:08:49 -05:00
|
|
|
#
|
|
|
|
# 4. Verify the signature
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# openssl rsautl -verify -inkey public_key.crt -certin \
|
|
|
|
# -in data.tar.gz.sig > verified.hash
|
2011-01-18 19:08:49 -05:00
|
|
|
#
|
|
|
|
# 5. Compare your hash to the verified hash
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# diff -s verified.hash my.hash
|
2011-01-18 19:08:49 -05:00
|
|
|
#
|
|
|
|
# 6. Repeat 5 and 6 with metadata.gz
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# == OpenSSL Reference
|
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# The .pem files generated by --build and --sign are PEM files. Here's a
|
|
|
|
# couple of useful OpenSSL commands for manipulating them:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# # convert a PEM format X509 certificate into DER format:
|
|
|
|
# # (note: Windows .cer files are X509 certificates in DER format)
|
|
|
|
# $ openssl x509 -in input.pem -outform der -out output.der
|
2009-12-08 02:19:09 -05:00
|
|
|
#
|
2007-11-10 02:48:56 -05:00
|
|
|
# # print out the certificate in a human-readable format:
|
|
|
|
# $ openssl x509 -in input.pem -noout -text
|
|
|
|
#
|
|
|
|
# And you can do the same thing with the private key file as well:
|
|
|
|
#
|
|
|
|
# # convert a PEM format RSA key into DER format:
|
|
|
|
# $ openssl rsa -in input_key.pem -outform der -out output_key.der
|
2009-12-08 02:19:09 -05:00
|
|
|
#
|
2007-11-10 02:48:56 -05:00
|
|
|
# # print out the key in a human readable format:
|
|
|
|
# $ openssl rsa -in input_key.pem -noout -text
|
|
|
|
#
|
|
|
|
# == Bugs/TODO
|
|
|
|
#
|
|
|
|
# * There's no way to define a system-wide trust list.
|
|
|
|
# * custom security policies (from a YAML file, etc)
|
|
|
|
# * Simple method to generate a signed certificate request
|
2012-11-29 01:52:18 -05:00
|
|
|
# * Support for OCSP, SCVP, CRLs, or some other form of cert status check
|
|
|
|
# (list is in order of preference)
|
2007-11-10 02:48:56 -05:00
|
|
|
# * Support for encrypted private keys
|
|
|
|
# * Some sort of semi-formal trust hierarchy (see long-winded explanation
|
|
|
|
# above)
|
|
|
|
# * Path discovery (for gem certificate chains that don't have a self-signed
|
|
|
|
# root) -- by the way, since we don't have this, THE ROOT OF THE CERTIFICATE
|
|
|
|
# CHAIN MUST BE SELF SIGNED if Policy#verify_root is true (and it is for the
|
|
|
|
# MediumSecurity and HighSecurity policies)
|
|
|
|
# * Better explanation of X509 naming (ie, we don't have to use email
|
|
|
|
# addresses)
|
|
|
|
# * Honor AIA field (see note about OCSP above)
|
2012-11-29 01:52:18 -05:00
|
|
|
# * Honor extension restrictions
|
2007-11-10 02:48:56 -05:00
|
|
|
# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
|
2012-11-29 01:52:18 -05:00
|
|
|
# file, instead of an array embedded in the metadata.
|
|
|
|
# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# == Original author
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
|
|
|
# Paul Duncan <pabs@pablotron.org>
|
|
|
|
# http://pablotron.org/
|
|
|
|
|
|
|
|
module Gem::Security
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
|
|
|
# Gem::Security default exception type
|
|
|
|
|
2007-12-20 03:39:12 -05:00
|
|
|
class Exception < Gem::Exception; end
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Digest algorithm used to sign gems
|
|
|
|
|
2013-09-13 15:58:57 -04:00
|
|
|
DIGEST_ALGORITHM = OpenSSL::Digest::SHA1
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Used internally to select the signing digest from all computed digests
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2013-09-13 15:58:57 -04:00
|
|
|
DIGEST_NAME = DIGEST_ALGORITHM.new.name # :nodoc:
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Algorithm for creating the key pair used to sign gems
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2013-09-13 15:58:57 -04:00
|
|
|
KEY_ALGORITHM = OpenSSL::PKey::RSA
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Length of keys created by KEY_ALGORITHM
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
KEY_LENGTH = 2048
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# One year in seconds
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
ONE_YEAR = 86400 * 365
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# The default set of extensions are:
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# * The certificate is not a certificate authority
|
|
|
|
# * The key for the certificate may be used for key and data encipherment
|
|
|
|
# and digital signatures
|
|
|
|
# * The certificate contains a subject key identifier
|
|
|
|
|
|
|
|
EXTENSIONS = {
|
|
|
|
'basicConstraints' => 'CA:FALSE',
|
|
|
|
'keyUsage' =>
|
|
|
|
'keyEncipherment,dataEncipherment,digitalSignature',
|
|
|
|
'subjectKeyIdentifier' => 'hash',
|
|
|
|
}
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.alt_name_or_x509_entry certificate, x509_entry
|
|
|
|
alt_name = certificate.extensions.find do |extension|
|
|
|
|
extension.oid == "#{x509_entry}AltName"
|
|
|
|
end
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
return alt_name.value if alt_name
|
|
|
|
|
|
|
|
certificate.send x509_entry
|
|
|
|
end
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Creates an unsigned certificate for +subject+ and +key+. The lifetime of
|
|
|
|
# the key is from the current time to +age+ which defaults to one year.
|
2007-11-10 02:48:56 -05:00
|
|
|
#
|
2012-11-29 01:52:18 -05:00
|
|
|
# The +extensions+ restrict the key to the indicated uses.
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.create_cert subject, key, age = ONE_YEAR, extensions = EXTENSIONS,
|
|
|
|
serial = 1
|
|
|
|
cert = OpenSSL::X509::Certificate.new
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
cert.public_key = key.public_key
|
|
|
|
cert.version = 2
|
|
|
|
cert.serial = serial
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
cert.not_before = Time.now
|
|
|
|
cert.not_after = Time.now + age
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
cert.subject = subject
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
ef = OpenSSL::X509::ExtensionFactory.new nil, cert
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
cert.extensions = extensions.map do |ext_name, value|
|
|
|
|
ef.create_extension ext_name, value
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
2012-11-29 01:52:18 -05:00
|
|
|
|
|
|
|
cert
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Creates a self-signed certificate with an issuer and subject from +email+,
|
|
|
|
# a subject alternative name of +email+ and the given +extensions+ for the
|
|
|
|
# +key+.
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.create_cert_email email, key, age = ONE_YEAR, extensions = EXTENSIONS
|
|
|
|
subject = email_to_name email
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
extensions = extensions.merge "subjectAltName" => "email:#{email}"
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
create_cert_self_signed subject, key, age, extensions
|
|
|
|
end
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Creates a self-signed certificate with an issuer and subject of +subject+
|
|
|
|
# and the given +extensions+ for the +key+.
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.create_cert_self_signed subject, key, age = ONE_YEAR,
|
|
|
|
extensions = EXTENSIONS, serial = 1
|
|
|
|
certificate = create_cert subject, key, age, extensions
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
sign certificate, key, certificate, age, extensions, serial
|
|
|
|
end
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Creates a new key pair of the specified +length+ and +algorithm+. The
|
|
|
|
# default is a 2048 bit RSA key.
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.create_key length = KEY_LENGTH, algorithm = KEY_ALGORITHM
|
|
|
|
algorithm.new length
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Turns +email_address+ into an OpenSSL::X509::Name
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.email_to_name email_address
|
|
|
|
email_address = email_address.gsub(/[^\w@.-]+/i, '_')
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
cn, dcs = email_address.split '@'
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
dcs = dcs.split '.'
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
name = "CN=#{cn}/#{dcs.map { |dc| "DC=#{dc}" }.join '/'}"
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
OpenSSL::X509::Name.parse name
|
|
|
|
end
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Signs +expired_certificate+ with +private_key+ if the keys match and the
|
|
|
|
# expired certificate was self-signed.
|
|
|
|
#--
|
|
|
|
# TODO increment serial
|
|
|
|
|
|
|
|
def self.re_sign expired_certificate, private_key, age = ONE_YEAR,
|
|
|
|
extensions = EXTENSIONS
|
|
|
|
raise Gem::Security::Exception,
|
2012-12-06 00:26:46 -05:00
|
|
|
"incorrect signing key for re-signing " +
|
2012-11-29 01:52:18 -05:00
|
|
|
"#{expired_certificate.subject}" unless
|
|
|
|
expired_certificate.public_key.to_pem == private_key.public_key.to_pem
|
|
|
|
|
|
|
|
unless expired_certificate.subject.to_s ==
|
|
|
|
expired_certificate.issuer.to_s then
|
|
|
|
subject = alt_name_or_x509_entry expired_certificate, :subject
|
|
|
|
issuer = alt_name_or_x509_entry expired_certificate, :issuer
|
|
|
|
|
|
|
|
raise Gem::Security::Exception,
|
2012-12-06 00:26:46 -05:00
|
|
|
"#{subject} is not self-signed, contact #{issuer} " +
|
2012-11-29 01:52:18 -05:00
|
|
|
"to obtain a valid certificate"
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
serial = expired_certificate.serial + 1
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
create_cert_self_signed(expired_certificate.subject, private_key, age,
|
|
|
|
extensions, serial)
|
|
|
|
end
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Resets the trust directory for verifying gems.
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.reset
|
|
|
|
@trust_dir = nil
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Sign the public key from +certificate+ with the +signing_key+ and
|
|
|
|
# +signing_cert+, using the Gem::Security::DIGEST_ALGORITHM. Uses the
|
|
|
|
# default certificate validity range and extensions.
|
|
|
|
#
|
|
|
|
# Returns the newly signed certificate.
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.sign certificate, signing_key, signing_cert,
|
|
|
|
age = ONE_YEAR, extensions = EXTENSIONS, serial = 1
|
|
|
|
signee_subject = certificate.subject
|
|
|
|
signee_key = certificate.public_key
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
alt_name = certificate.extensions.find do |extension|
|
|
|
|
extension.oid == 'subjectAltName'
|
|
|
|
end
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
extensions = extensions.merge 'subjectAltName' => alt_name.value if
|
|
|
|
alt_name
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
issuer_alt_name = signing_cert.extensions.find do |extension|
|
|
|
|
extension.oid == 'subjectAltName'
|
2011-01-18 19:08:49 -05:00
|
|
|
end
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
extensions = extensions.merge 'issuerAltName' => issuer_alt_name.value if
|
|
|
|
issuer_alt_name
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
signed = create_cert signee_subject, signee_key, age, extensions, serial
|
|
|
|
signed.issuer = signing_cert.subject
|
|
|
|
|
|
|
|
signed.sign signing_key, Gem::Security::DIGEST_ALGORITHM.new
|
2011-01-18 19:08:49 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Returns a Gem::Security::TrustDir which wraps the directory where trusted
|
|
|
|
# certificates live.
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.trust_dir
|
|
|
|
return @trust_dir if @trust_dir
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
dir = File.join Gem.user_home, '.gem', 'trust'
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
@trust_dir ||= Gem::Security::TrustDir.new dir
|
|
|
|
end
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
##
|
|
|
|
# Enumerates the trusted certificates via Gem::Security::TrustDir.
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def self.trusted_certificates &block
|
|
|
|
trust_dir.each_certificate(&block)
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
##
|
2012-11-29 01:52:18 -05:00
|
|
|
# Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given
|
2013-09-13 15:58:57 -04:00
|
|
|
# +permissions+.
|
2007-11-10 02:48:56 -05:00
|
|
|
|
2013-09-13 15:58:57 -04:00
|
|
|
def self.write pemmable, path, permissions = 0600
|
2012-11-29 01:52:18 -05:00
|
|
|
path = File.expand_path path
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
open path, 'wb', permissions do |io|
|
2013-09-13 15:58:57 -04:00
|
|
|
io.write pemmable.to_pem
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
path
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
2011-01-18 19:08:49 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
reset
|
|
|
|
|
2007-11-10 02:48:56 -05:00
|
|
|
end
|
|
|
|
|
2013-09-13 15:58:57 -04:00
|
|
|
require 'rubygems/security/policy'
|
|
|
|
require 'rubygems/security/policies'
|
2012-11-29 01:52:18 -05:00
|
|
|
require 'rubygems/security/signer'
|
2013-09-13 15:58:57 -04:00
|
|
|
require 'rubygems/security/trust_dir'
|
2012-11-29 01:52:18 -05:00
|
|
|
|