Module: Gem::Security

Defined in:
lib/rubygems/security.rb,
lib/rubygems/install_update_options.rb

Overview

forward-declare

Defined Under Namespace

Classes: Exception, Policy, Signer

Constant Summary collapse

OPT =

Default options for most of the methods below

{
  # private key options
  :key_algo   => Gem::SSL::PKEY_RSA,
  :key_size   => 2048,

  # public cert options
  :cert_age   => 365 * 24 * 3600, # 1 year
  :dgst_algo  => Gem::SSL::DIGEST_SHA1,

  # x509 certificate extensions
  :cert_exts  => {
    '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,
  },
}
NoSecurity =

No security policy: all package signature checks are disabled.

Policy.new(
  :verify_data      => false,
  :verify_signer    => false,
  :verify_chain     => false,
  :verify_root      => false,
  :only_trusted     => false,
  :only_signed      => false
)
AlmostNoSecurity =

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.

Policy.new(
  :verify_data      => true,
  :verify_signer    => false,
  :verify_chain     => false,
  :verify_root      => false,
  :only_trusted     => false,
  :only_signed      => false
)
LowSecurity =

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.

Policy.new(
  :verify_data      => true,
  :verify_signer    => true,
  :verify_chain     => false,
  :verify_root      => false,
  :only_trusted     => false,
  :only_signed      => false
)
MediumSecurity =

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.

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.

Policy.new(
  :verify_data      => true,
  :verify_signer    => true,
  :verify_chain     => true,
  :verify_root      => true,
  :only_trusted     => true,
  :only_signed      => false
)
HighSecurity =

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 explicitly allowed trust for.

This security policy is significantly more difficult to bypass, and offers a reasonable guarantee that the contents of the gem have not been altered.

Policy.new(
  :verify_data      => true,
  :verify_signer    => true,
  :verify_chain     => true,
  :verify_root      => true,
  :only_trusted     => true,
  :only_signed      => true
)
Policies =

Hash of configured security policies

{
  'NoSecurity'       => NoSecurity,
  'AlmostNoSecurity' => AlmostNoSecurity,
  'LowSecurity'      => LowSecurity,
  'MediumSecurity'   => MediumSecurity,
  'HighSecurity'     => HighSecurity,
}

Class Method Summary collapse

Class Method Details

.add_trusted_cert(cert, opt = {}) ⇒ Object

Add certificate to trusted cert list.

Note: At the moment these are stored in OPT, although that directory may change in the future.



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
# File 'lib/rubygems/security.rb', line 764

def self.add_trusted_cert(cert, opt = {})
  opt = OPT.merge(opt)

  # get destination path
  path = Gem::Security::Policy.trusted_cert_path(cert, opt)

  # verify trust directory (can't write to nowhere, you know)
  verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])

  # write cert to output file
  File.open(path, 'wb') do |file|
    file.chmod(opt[:perms][:trusted_cert])
    file.write(cert.to_pem)
  end

  # return nil
  nil
end

.build_cert(name, key, opt = {}) ⇒ Object

Build a certificate from the given DN and private key.



674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/rubygems/security.rb', line 674

def self.build_cert(name, key, opt = {})
  Gem.ensure_ssl_available
  opt = OPT.merge opt

  cert = 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

  ef = OpenSSL::X509::ExtensionFactory.new nil, cert

  cert.extensions = opt[:cert_exts].map do |ext_name, value|
    ef.create_extension ext_name, value
  end

  i_key  = opt[:issuer_key]  || key
  i_cert = opt[:issuer_cert] || cert

  cert = sign_cert cert, i_key, i_cert, opt

  cert
end

.build_self_signed_cert(email_addr, opt = {}) ⇒ Object

Build a self-signed certificate for the given email address.



704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
# File 'lib/rubygems/security.rb', line 704

def self.build_self_signed_cert(email_addr, opt = {})
  Gem.ensure_ssl_available
  opt = OPT.merge(opt)
  path = { :key => nil, :cert => nil }

  name = email_to_name email_addr, opt[:munge_re]

  key = opt[:key_algo].new opt[:key_size]

  verify_trust_dir opt[:trust_dir], opt[:perms][:trust_dir]

  if opt[:save_key] then
    path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key')

    open path[:key], 'wb' do |io|
      io.chmod opt[:perms][:signing_key]
      io.write key.to_pem
    end
  end

  cert = build_cert name, key, opt

  if opt[:save_cert] then
    path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert')

    open path[:cert], 'wb' do |file|
      file.chmod opt[:perms][:signing_cert]
      file.write cert.to_pem
    end
  end

  { :key => key, :cert => cert,
    :key_path => path[:key], :cert_path => path[:cert] }
end

.email_to_name(email_address, munge_re) ⇒ Object

Turns email_address into an OpenSSL::X509::Name



742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
# File 'lib/rubygems/security.rb', line 742

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

.sign_cert(cert, signing_key, signing_cert, opt = {}) ⇒ Object

Sign the cert cert with @signing_key and @signing_cert, using the digest algorithm opt. Returns the newly signed certificate.



641
642
643
644
645
646
647
648
# File 'lib/rubygems/security.rb', line 641

def self.sign_cert(cert, signing_key, signing_cert, opt = {})
  opt = OPT.merge(opt)

  cert.issuer = signing_cert.subject
  cert.sign signing_key, opt[:dgst_algo].new

  cert
end

.verify_trust_dir(path, perms) ⇒ Object

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.



655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
# File 'lib/rubygems/security.rb', line 655

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
  if File.exist?(path)
    # verify that the trust directory is actually a directory
    unless File.directory?(path)
      err = "trust directory #{path} isn't a directory"
      raise Gem::Security::Exception, err
    end
  else
    # trust directory doesn't exist, so create it with permissions
    FileUtils.mkdir_p(path)
    FileUtils.chmod(perms, path)
  end
end