From e798ccbacf489a3af2201ae30058ff0ae7f79045 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 19 Jan 2011 00:08:49 +0000 Subject: Import rubygems 1.5.0 (release candidate) git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@30599 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/rubygems/security.rb | 257 ++++++++++++++++++++++++++++------------------- 1 file changed, 151 insertions(+), 106 deletions(-) (limited to 'lib/rubygems/security.rb') diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 5cd3fb756f..9f3daa9453 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -1,12 +1,19 @@ +###################################################################### +# This file is imported from the rubygems project. +# DO NOT make modifications in this repo. They _will_ be reverted! +# File a patch instead and assign it to Ryan Davis or Eric Hodel. +###################################################################### + #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ -require 'rubygems' +require 'rubygems/exceptions' require 'rubygems/gem_openssl' +# # = Signed Gems README # # == Table of Contents @@ -265,6 +272,34 @@ require 'rubygems/gem_openssl' # A more detailed description of each options is available in the walkthrough # above. # +# == 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 -pe 'sub(/^ +/, "")' > 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 # @@ -319,11 +354,14 @@ require 'rubygems/gem_openssl' module Gem::Security + ## + # Gem::Security default exception type + class Exception < Gem::Exception; end - # - # default options for most of the methods below - # + ## + # Default options for most of the methods below + OPT = { # private key options :key_algo => Gem::SSL::PKEY_RSA, @@ -338,38 +376,38 @@ module Gem::Security 'basicConstraints' => 'CA:FALSE', 'subjectKeyIdentifier' => 'hash', 'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature', - }, - - # save the key and cert to a file in build_self_signed_cert()? - :save_key => true, - :save_cert => true, - - # if you define either of these, then they'll be used instead of - # the output_fmt macro below - :save_key_path => nil, - :save_cert_path => nil, - - # output name format for self-signed certs - :output_fmt => 'gem-%s.pem', - :munge_re => Regexp.new(/[^a-z0-9_.-]+/), - - # output directory for trusted certificate checksums - :trust_dir => File::join(Gem.user_home, '.gem', 'trust'), - - # default permissions for trust directory and certs - :perms => { - :trust_dir => 0700, - :trusted_cert => 0600, - :signing_cert => 0600, - :signing_key => 0600, - }, + }, + + # save the key and cert to a file in build_self_signed_cert()? + :save_key => true, + :save_cert => true, + + # if you define either of these, then they'll be used instead of + # the output_fmt macro below + :save_key_path => nil, + :save_cert_path => nil, + + # output name format for self-signed certs + :output_fmt => 'gem-%s.pem', + :munge_re => Regexp.new(/[^a-z0-9_.-]+/), + + # output directory for trusted certificate checksums + :trust_dir => File::join(Gem.user_home, '.gem', 'trust'), + + # default permissions for trust directory and certs + :perms => { + :trust_dir => 0700, + :trusted_cert => 0600, + :signing_cert => 0600, + :signing_key => 0600, + }, } - # + ## # A Gem::Security::Policy object encapsulates the settings for verifying # signed gem files. This is the base class. You can either declare an # instance of this or use one of the preset security policies below. - # + class Policy attr_accessor :verify_data, :verify_signer, :verify_chain, :verify_root, :only_trusted, :only_signed @@ -509,9 +547,9 @@ module Gem::Security end end - # + ## # No security policy: all package signature checks are disabled. - # + NoSecurity = Policy.new( :verify_data => false, :verify_signer => false, @@ -521,14 +559,14 @@ module Gem::Security :only_signed => false ) - # + ## # AlmostNo security policy: only verify that the signing certificate is the # one that actually signed the data. Make no attempt to verify the signing # certificate chain. # # This policy is basically useless. better than nothing, but can still be # easily spoofed, and is not recommended. - # + AlmostNoSecurity = Policy.new( :verify_data => true, :verify_signer => false, @@ -538,13 +576,13 @@ module Gem::Security :only_signed => false ) - # + ## # Low security policy: only verify that the signing certificate is actually # the gem signer, and that the signing certificate is valid. # # This policy is better than nothing, but can still be easily spoofed, and # is not recommended. - # + LowSecurity = Policy.new( :verify_data => true, :verify_signer => true, @@ -554,7 +592,7 @@ module Gem::Security :only_signed => false ) - # + ## # Medium security policy: verify the signing certificate, verify the signing # certificate chain all the way to the root certificate, and only trust root # certificates that we have explicitly allowed trust for. @@ -562,7 +600,7 @@ module Gem::Security # This security policy is reasonable, but it allows unsigned packages, so a # malicious person could simply delete the package signature and pass the # gem off as unsigned. - # + MediumSecurity = Policy.new( :verify_data => true, :verify_signer => true, @@ -572,7 +610,7 @@ module Gem::Security :only_signed => false ) - # + ## # High security policy: only allow signed gems to be installed, verify the # signing certificate, verify the signing certificate chain all the way to # the root certificate, and only trust root certificates that we have @@ -580,7 +618,7 @@ module Gem::Security # # This security policy is significantly more difficult to bypass, and offers # a reasonable guarantee that the contents of the gem have not been altered. - # + HighSecurity = Policy.new( :verify_data => true, :verify_signer => true, @@ -590,9 +628,9 @@ module Gem::Security :only_signed => true ) - # + ## # Hash of configured security policies - # + Policies = { 'NoSecurity' => NoSecurity, 'AlmostNoSecurity' => AlmostNoSecurity, @@ -601,25 +639,24 @@ module Gem::Security 'HighSecurity' => HighSecurity, } - # + ## # Sign the cert cert with @signing_key and @signing_cert, using the digest # algorithm opt[:dgst_algo]. Returns the newly signed certificate. - # + def self.sign_cert(cert, signing_key, signing_cert, opt = {}) opt = OPT.merge(opt) - # set up issuer information cert.issuer = signing_cert.subject - cert.sign(signing_key, opt[:dgst_algo].new) + cert.sign signing_key, opt[:dgst_algo].new cert end - # + ## # Make sure the trust directory exists. If it does exist, make sure it's # actually a directory. If not, then create it with the appropriate # permissions. - # + def self.verify_trust_dir(path, perms) # if the directory exists, then make sure it is in fact a directory. if # it doesn't exist, then create it with the appropriate permissions @@ -636,94 +673,99 @@ module Gem::Security end end - # + ## # Build a certificate from the given DN and private key. - # + def self.build_cert(name, key, opt = {}) Gem.ensure_ssl_available - opt = OPT.merge(opt) + opt = OPT.merge opt + + cert = OpenSSL::X509::Certificate.new - # create new cert - ret = OpenSSL::X509::Certificate.new + cert.not_after = Time.now + opt[:cert_age] + cert.not_before = Time.now + cert.public_key = key.public_key + cert.serial = 0 + cert.subject = name + cert.version = 2 - # populate cert attributes - ret.version = 2 - ret.serial = 0 - ret.public_key = key.public_key - ret.not_before = Time.now - ret.not_after = Time.now + opt[:cert_age] - ret.subject = name + ef = OpenSSL::X509::ExtensionFactory.new nil, cert - # add certificate extensions - ef = OpenSSL::X509::ExtensionFactory.new(nil, ret) - ret.extensions = opt[:cert_exts].map { |k, v| ef.create_extension(k, v) } + cert.extensions = opt[:cert_exts].map do |ext_name, value| + ef.create_extension ext_name, value + end - # sign cert - i_key, i_cert = opt[:issuer_key] || key, opt[:issuer_cert] || ret - ret = sign_cert(ret, i_key, i_cert, opt) + i_key = opt[:issuer_key] || key + i_cert = opt[:issuer_cert] || cert - # return cert - ret + cert = sign_cert cert, i_key, i_cert, opt + + cert end - # + ## # Build a self-signed certificate for the given email address. - # + def self.build_self_signed_cert(email_addr, opt = {}) Gem.ensure_ssl_available opt = OPT.merge(opt) path = { :key => nil, :cert => nil } - # split email address up - cn, dcs = email_addr.split('@') - dcs = dcs.split('.') - - # munge email CN and DCs - cn = cn.gsub(opt[:munge_re], '_') - dcs = dcs.map { |dc| dc.gsub(opt[:munge_re], '_') } - - # create DN - name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/') - name = OpenSSL::X509::Name::parse(name) + name = email_to_name email_addr, opt[:munge_re] - # build private key - key = opt[:key_algo].new(opt[:key_size]) + key = opt[:key_algo].new opt[:key_size] - # method name pretty much says it all :) - verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir]) + verify_trust_dir opt[:trust_dir], opt[:perms][:trust_dir] - # if we're saving the key, then write it out - if opt[:save_key] + if opt[:save_key] then path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key') - File.open(path[:key], 'wb') do |file| - file.chmod(opt[:perms][:signing_key]) - file.write(key.to_pem) + + open path[:key], 'wb' do |io| + io.chmod opt[:perms][:signing_key] + io.write key.to_pem end end - # build self-signed public cert from key - cert = build_cert(name, key, opt) + cert = build_cert name, key, opt - # if we're saving the cert, then write it out - if opt[:save_cert] + if opt[:save_cert] then path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert') - File.open(path[:cert], 'wb') do |file| - file.chmod(opt[:perms][:signing_cert]) - file.write(cert.to_pem) + + open path[:cert], 'wb' do |file| + file.chmod opt[:perms][:signing_cert] + file.write cert.to_pem end end - # return key, cert, and paths (if applicable) { :key => key, :cert => cert, :key_path => path[:key], :cert_path => path[:cert] } end - # + ## + # Turns +email_address+ into an OpenSSL::X509::Name + + def self.email_to_name email_address, munge_re + cn, dcs = email_address.split '@' + + dcs = dcs.split '.' + + cn = cn.gsub munge_re, '_' + + dcs = dcs.map do |dc| + dc.gsub munge_re, '_' + end + + name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/') + + OpenSSL::X509::Name.parse name + end + + ## # Add certificate to trusted cert list. # # Note: At the moment these are stored in OPT[:trust_dir], although that # directory may change in the future. - # + def self.add_trusted_cert(cert, opt = {}) opt = OPT.merge(opt) @@ -743,11 +785,13 @@ module Gem::Security nil end - # + ## # Basic OpenSSL-based package signing class. - # + class Signer - attr_accessor :key, :cert_chain + + attr_accessor :cert_chain + attr_accessor :key def initialize(key, cert_chain) Gem.ensure_ssl_available @@ -774,13 +818,14 @@ module Gem::Security end end - # + ## # Sign data with given digest algorithm - # + def sign(data) @key.sign(@algo.new, data) end end + end -- cgit v1.2.3