summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/rubygems/security.rb
diff options
context:
space:
mode:
Diffstat (limited to 'jni/ruby/lib/rubygems/security.rb')
-rw-r--r--jni/ruby/lib/rubygems/security.rb595
1 files changed, 595 insertions, 0 deletions
diff --git a/jni/ruby/lib/rubygems/security.rb b/jni/ruby/lib/rubygems/security.rb
new file mode 100644
index 0000000..8c5fb7d
--- /dev/null
+++ b/jni/ruby/lib/rubygems/security.rb
@@ -0,0 +1,595 @@
+#--
+# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
+# All rights reserved.
+# See LICENSE.txt for permissions.
+#++
+
+require 'rubygems/exceptions'
+require 'fileutils'
+
+begin
+ require 'openssl'
+rescue LoadError => e
+ raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
+ e.message =~ / -- openssl$/
+end
+
+##
+# = Signing gems
+#
+# The Gem::Security implements cryptographic signatures for gems. The section
+# below is a step-by-step guide to using signed gems and generating your own.
+#
+# == Walkthrough
+#
+# === Building your certificate
+#
+# In order to start signing your gems, you'll need to build a private key and
+# a self-signed certificate. Here's how:
+#
+# # build a private key and certificate for yourself:
+# $ gem cert --build you@example.com
+#
+# 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.
+#
+# 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).
+#
+# 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).
+#
+# === Signing Gems
+#
+# 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.
+#
+# 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.
+#
+# 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
+# metadata.gz
+# 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
+#
+# Now let's verify the signature. Go ahead and install the gem, but add the
+# following options: <code>-P HighSecurity</code>, like this:
+#
+# # install the gem with using the security policy "HighSecurity"
+# $ sudo gem install your.gem -P HighSecurity
+#
+# The <code>-P</code> option sets your security policy -- we'll talk about
+# that in just a minute. Eh, what's this?
+#
+# $ 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
+#
+# 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; they can't modify the
+# package contents without invalidating the signature, and they can't
+# modify or remove signature or the signing certificate chain, or
+# RubyGems will simply refuse to install the package. Oh well, maybe
+# they'll have better luck causing problems for CPAN users instead :).
+#
+# 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:
+#
+# # add trusted certificate
+# gem cert --add ~/.gem/gem-public_cert.pem
+#
+# 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:
+#
+# # install the gem with using the HighSecurity policy (and this time
+# # without any shenanigans)
+# $ gem install -P HighSecurity your-gem-1.0.gem
+# Successfully installed your-gem-1.0
+# 1 gem installed
+#
+# This time RubyGems will accept your signed package and begin installing.
+#
+# 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>:
+#
+# Options:
+# -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
+#
+# 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:
+#
+# --------------------------
+# | rubygems@rubygems.org |
+# --------------------------
+# |
+# -----------------------------------
+# | |
+# ---------------------------- -----------------------------
+# | seattlerb@seattlerb.org | | dcrubyists@richkilmer.com |
+# ---------------------------- -----------------------------
+# | | | |
+# --------------- ---------------- ----------- --------------
+# | drbrain | | zenspider | | pabs@dc | | tomcope@dc |
+# --------------- ---------------- ----------- --------------
+#
+#
+# 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.
+#
+# === Signing certificates
+#
+# 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:
+#
+# # 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
+#
+# 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>
+#
+# Obviously this RubyGems trust infrastructure doesn't exist yet. Also, in
+# the "real world", issuers actually generate the child certificate from a
+# 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...
+#
+# At this point you should know how to do all of these new and interesting
+# things:
+#
+# * build a gem signing key and certificate
+# * adjust your security policy
+# * modify your trusted certificate list
+# * sign a certificate
+#
+# == Manually verifying signatures
+#
+# In case you don't trust RubyGems you can verify gem signatures manually:
+#
+# 1. Fetch and unpack the gem
+#
+# gem fetch some_signed_gem
+# tar -xf some_signed_gem-1.0.gem
+#
+# 2. Grab the public key from the gemspec
+#
+# gem spec some_signed_gem-1.0.gem cert_chain | \
+# ruby -ryaml -e 'puts YAML.load_documents($stdin)' > public_key.crt
+#
+# 3. Generate a SHA1 hash of the data.tar.gz
+#
+# openssl dgst -sha1 < data.tar.gz > my.hash
+#
+# 4. Verify the signature
+#
+# openssl rsautl -verify -inkey public_key.crt -certin \
+# -in data.tar.gz.sig > verified.hash
+#
+# 5. Compare your hash to the verified hash
+#
+# diff -s verified.hash my.hash
+#
+# 6. Repeat 5 and 6 with metadata.gz
+#
+# == OpenSSL Reference
+#
+# The .pem files generated by --build and --sign are PEM files. Here's a
+# couple of useful OpenSSL commands for manipulating them:
+#
+# # 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
+#
+# # 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
+#
+# # 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
+# * Support for OCSP, SCVP, CRLs, or some other form of cert status check
+# (list is in order of preference)
+# * 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)
+# * Honor extension restrictions
+# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
+# file, instead of an array embedded in the metadata.
+# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
+#
+# == Original author
+#
+# Paul Duncan <pabs@pablotron.org>
+# http://pablotron.org/
+
+module Gem::Security
+
+ ##
+ # Gem::Security default exception type
+
+ class Exception < Gem::Exception; end
+
+ ##
+ # Digest algorithm used to sign gems
+
+ DIGEST_ALGORITHM =
+ if defined?(OpenSSL::Digest) then
+ OpenSSL::Digest::SHA1
+ end
+
+ ##
+ # Used internally to select the signing digest from all computed digests
+
+ DIGEST_NAME = # :nodoc:
+ if DIGEST_ALGORITHM then
+ DIGEST_ALGORITHM.new.name
+ end
+
+ ##
+ # Algorithm for creating the key pair used to sign gems
+
+ KEY_ALGORITHM =
+ if defined?(OpenSSL::PKey) then
+ OpenSSL::PKey::RSA
+ end
+
+ ##
+ # Length of keys created by KEY_ALGORITHM
+
+ KEY_LENGTH = 2048
+
+ ##
+ # Cipher used to encrypt the key pair used to sign gems.
+ # Must be in the list returned by OpenSSL::Cipher.ciphers
+
+ KEY_CIPHER = OpenSSL::Cipher.new('AES-256-CBC') if defined?(OpenSSL::Cipher)
+
+ ##
+ # One year in seconds
+
+ ONE_YEAR = 86400 * 365
+
+ ##
+ # The default set of extensions are:
+ #
+ # * 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',
+ }
+
+ def self.alt_name_or_x509_entry certificate, x509_entry
+ alt_name = certificate.extensions.find do |extension|
+ extension.oid == "#{x509_entry}AltName"
+ end
+
+ return alt_name.value if alt_name
+
+ certificate.send x509_entry
+ end
+
+ ##
+ # 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.
+ #
+ # The +extensions+ restrict the key to the indicated uses.
+
+ def self.create_cert subject, key, age = ONE_YEAR, extensions = EXTENSIONS,
+ serial = 1
+ cert = OpenSSL::X509::Certificate.new
+
+ cert.public_key = key.public_key
+ cert.version = 2
+ cert.serial = serial
+
+ cert.not_before = Time.now
+ cert.not_after = Time.now + age
+
+ cert.subject = subject
+
+ ef = OpenSSL::X509::ExtensionFactory.new nil, cert
+
+ cert.extensions = extensions.map do |ext_name, value|
+ ef.create_extension ext_name, value
+ end
+
+ cert
+ end
+
+ ##
+ # 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+.
+
+ def self.create_cert_email email, key, age = ONE_YEAR, extensions = EXTENSIONS
+ subject = email_to_name email
+
+ extensions = extensions.merge "subjectAltName" => "email:#{email}"
+
+ create_cert_self_signed subject, key, age, extensions
+ end
+
+ ##
+ # Creates a self-signed certificate with an issuer and subject of +subject+
+ # and the given +extensions+ for the +key+.
+
+ def self.create_cert_self_signed subject, key, age = ONE_YEAR,
+ extensions = EXTENSIONS, serial = 1
+ certificate = create_cert subject, key, age, extensions
+
+ sign certificate, key, certificate, age, extensions, serial
+ end
+
+ ##
+ # Creates a new key pair of the specified +length+ and +algorithm+. The
+ # default is a 2048 bit RSA key.
+
+ def self.create_key length = KEY_LENGTH, algorithm = KEY_ALGORITHM
+ algorithm.new length
+ end
+
+ ##
+ # Turns +email_address+ into an OpenSSL::X509::Name
+
+ def self.email_to_name email_address
+ email_address = email_address.gsub(/[^\w@.-]+/i, '_')
+
+ cn, dcs = email_address.split '@'
+
+ dcs = dcs.split '.'
+
+ name = "CN=#{cn}/#{dcs.map { |dc| "DC=#{dc}" }.join '/'}"
+
+ OpenSSL::X509::Name.parse name
+ end
+
+ ##
+ # 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,
+ "incorrect signing key for re-signing " +
+ "#{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,
+ "#{subject} is not self-signed, contact #{issuer} " +
+ "to obtain a valid certificate"
+ end
+
+ serial = expired_certificate.serial + 1
+
+ create_cert_self_signed(expired_certificate.subject, private_key, age,
+ extensions, serial)
+ end
+
+ ##
+ # Resets the trust directory for verifying gems.
+
+ def self.reset
+ @trust_dir = nil
+ end
+
+ ##
+ # 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.
+
+ def self.sign certificate, signing_key, signing_cert,
+ age = ONE_YEAR, extensions = EXTENSIONS, serial = 1
+ signee_subject = certificate.subject
+ signee_key = certificate.public_key
+
+ alt_name = certificate.extensions.find do |extension|
+ extension.oid == 'subjectAltName'
+ end
+
+ extensions = extensions.merge 'subjectAltName' => alt_name.value if
+ alt_name
+
+ issuer_alt_name = signing_cert.extensions.find do |extension|
+ extension.oid == 'subjectAltName'
+ end
+
+ extensions = extensions.merge 'issuerAltName' => issuer_alt_name.value if
+ issuer_alt_name
+
+ signed = create_cert signee_subject, signee_key, age, extensions, serial
+ signed.issuer = signing_cert.subject
+
+ signed.sign signing_key, Gem::Security::DIGEST_ALGORITHM.new
+ end
+
+ ##
+ # Returns a Gem::Security::TrustDir which wraps the directory where trusted
+ # certificates live.
+
+ def self.trust_dir
+ return @trust_dir if @trust_dir
+
+ dir = File.join Gem.user_home, '.gem', 'trust'
+
+ @trust_dir ||= Gem::Security::TrustDir.new dir
+ end
+
+ ##
+ # Enumerates the trusted certificates via Gem::Security::TrustDir.
+
+ def self.trusted_certificates &block
+ trust_dir.each_certificate(&block)
+ end
+
+ ##
+ # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given
+ # +permissions+. If passed +cipher+ and +passphrase+ those arguments will be
+ # passed to +to_pem+.
+
+ def self.write pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER
+ path = File.expand_path path
+
+ open path, 'wb', permissions do |io|
+ if passphrase and cipher
+ io.write pemmable.to_pem cipher, passphrase
+ else
+ io.write pemmable.to_pem
+ end
+ end
+
+ path
+ end
+
+ reset
+
+end
+
+if defined?(OpenSSL::SSL) then
+ require 'rubygems/security/policy'
+ require 'rubygems/security/policies'
+ require 'rubygems/security/trust_dir'
+end
+
+require 'rubygems/security/signer'
+