123456789_123456789_123456789_123456789_123456789_

Class: Gem::Security::Policy

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
Inherits: Object
Defined in: lib/rubygems/security/policy.rb,
lib/rubygems/security_option.rb,
lib/rubygems/commands/unpack_command.rb

Overview

A 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 in Policies.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

::Gem::UserInteraction - Included

#alert

Displays an alert statement.

#alert_error

Displays an error statement to the error output location.

#alert_warning

Displays a warning statement to the warning output location.

#ask

Asks a question and returns the answer.

#ask_for_password

Asks for a password with a prompt

#ask_yes_no

Asks a yes or no question.

#choose_from_list

Asks the user to answer question with an answer from the given list.

#say

Displays the given statement on the standard output (or equivalent).

#terminate_interaction

Terminates the RubyGems process with the given exit_code

#verbose

Calls say with msg or the results of the block if really_verbose is true.

::Gem::DefaultUserInteraction - Included

::Gem::Text - Included

#clean_text

Remove any non-printable characters and make the text suitable for printing.

#format_text

Wraps text to wrap characters and optionally indents by indent characters.

#levenshtein_distance

This code is based directly on the ::Gem::Text gem implementation Returns a value representing the “cost” of transforming str1 into str2.

#truncate_text, #min3

Constructor Details

.new(name, policy = {}, opt = {}) ⇒ Policy

Create a new Policy object with the given mode and options.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 26

def initialize(name, policy = {}, opt = {})
  @name = name

  @opt = opt

  # Default to security
  @only_signed   = true
  @only_trusted  = true
  @verify_chain  = true
  @verify_data   = true
  @verify_root   = true
  @verify_signer = true

  policy.each_pair do |key, val|
    case key
    when :verify_data   then @verify_data   = val
    when :verify_signer then @verify_signer = val
    when :verify_chain  then @verify_chain  = val
    when :verify_root   then @verify_root   = val
    when :only_trusted  then @only_trusted  = val
    when :only_signed   then @only_signed   = val
    end
  end
end

Instance Attribute Details

#name (readonly) Also known as: #to_s

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 13

attr_reader :name

#only_signed (rw)

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 15

attr_accessor :only_signed

#only_trusted (rw)

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 16

attr_accessor :only_trusted

#to_s (readonly)

This method is for internal use only.

Alias for #name.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 292

alias to_s name # :nodoc:

#verify_chain (rw)

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 17

attr_accessor :verify_chain

#verify_data (rw)

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 18

attr_accessor :verify_data

#verify_root (rw)

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 19

attr_accessor :verify_root

#verify_signer (rw)

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 20

attr_accessor :verify_signer

Instance Method Details

#check_cert(signer, issuer, time)

Ensures that signer is valid for Gem.time and was signed by the issuer. If the issuer is nil no verification is performed.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 85

def check_cert(signer, issuer, time)
  raise Gem::Security::Exception, 'missing signing certificate' unless
    signer

  message = "certificate #{signer.subject}"

  if not_before = signer.not_before and not_before > time
    raise Gem::Security::Exception,
          "#{message} not valid before #{not_before}"
  end

  if not_after = signer.not_after and not_after < time
    raise Gem::Security::Exception, "#{message} not valid after #{not_after}"
  end

  if issuer and not signer.verify issuer.public_key
    raise Gem::Security::Exception,
          "#{message} was not issued by #{issuer.subject}"
  end

  true
end

#check_chain(chain, time)

Verifies each certificate in chain has signed the following certificate and is valid for the given Gem.time.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 55

def check_chain(chain, time)
  raise Gem::Security::Exception, 'missing signing chain' unless chain
  raise Gem::Security::Exception, 'empty signing chain' if chain.empty?

  begin
    chain.each_cons 2 do |issuer, cert|
      check_cert cert, issuer, time
    end

    true
  rescue Gem::Security::Exception => e
    raise Gem::Security::Exception, "invalid signing chain: #{e.message}"
  end
end

#check_data(public_key, digest, signature, data)

Verifies that data matches the signature created by public_key and the digest algorithm.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 74

def check_data(public_key, digest, signature, data)
  raise Gem::Security::Exception, "invalid signature" unless
    public_key.verify digest, signature, data.digest

  true
end

#check_key(signer, key)

Ensures the public key of key matches the public key in signer

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 111

def check_key(signer, key)
  unless signer and key
    return true unless @only_signed

    raise Gem::Security::Exception, 'missing key or signature'
  end

  public_key = Gem::Security.get_public_key(key)

  raise Gem::Security::Exception,
    "certificate #{signer.subject} does not match the signing key" unless
      signer.public_key.to_pem == public_key.to_pem

  true
end

#check_root(chain, time)

Ensures the root certificate in chain is self-signed and valid for Gem.time.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 131

def check_root(chain, time)
  raise Gem::Security::Exception, 'missing signing chain' unless chain

  root = chain.first

  raise Gem::Security::Exception, 'missing root certificate' unless root

  raise Gem::Security::Exception,
        "root certificate #{root.subject} is not self-signed " +
        "(issuer #{root.issuer})" if
    root.issuer != root.subject

  check_cert root, root, time
end

#check_trust(chain, digester, trust_dir)

Ensures the root of chain has a trusted certificate in Gem::Security.trust_dir and the digests of the two certificates match according to digester

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 150

def check_trust(chain, digester, trust_dir)
  raise Gem::Security::Exception, 'missing signing chain' unless chain

  root = chain.first

  raise Gem::Security::Exception, 'missing root certificate' unless root

  path = Gem::Security.trust_dir.cert_path root

  unless File.exist? path
    message = "root cert #{root.subject} is not trusted".dup

    message << " (root of signing cert #{chain.last.subject})" if
      chain.length > 1

    raise Gem::Security::Exception, message
  end

  save_cert = OpenSSL::X509::Certificate.new File.read path
  save_dgst = digester.digest save_cert.public_key.to_pem

  pkey_str = root.public_key.to_pem
  cert_dgst = digester.digest pkey_str

  raise Gem::Security::Exception,
        "trusted root certificate #{root.subject} checksum " +
        "does not match signing root certificate checksum" unless
    save_dgst == cert_dgst

  true
end

#inspect

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 195

def inspect # :nodoc:
  ("[Policy: %s - data: %p signer: %p chain: %p root: %p " +
   "signed-only: %p trusted-only: %p]") % [
     @name, @verify_chain, @verify_data, @verify_root, @verify_signer,
     @only_signed, @only_trusted
   ]
end

#subject(certificate)

This method is for internal use only.

Extracts the email or subject from certificate

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 185

def subject(certificate) # :nodoc:
  certificate.extensions.each do |extension|
    next unless extension.oid == 'subjectAltName'

    return extension.value
  end

  certificate.subject.to_s
end

#verify(chain, key = nil, digests = {}, signatures = {}, full_name = '(unknown)')

For full_name, verifies the certificate chain is valid, the digests match the signatures signatures created by the signer depending on the policy settings.

If key is given it is used to validate the signing certificate.

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 210

def verify(chain, key = nil, digests = {}, signatures = {},
           full_name = '(unknown)')
  if signatures.empty?
    if @only_signed
      raise Gem::Security::Exception,
        "unsigned gems are not allowed by the #{name} policy"
    elsif digests.empty?
      # lack of signatures is irrelevant if there is nothing to check
      # against
    else
      alert_warning "#{full_name} is not signed"
      return
    end
  end

  opt       = @opt
  digester  = Gem::Security.create_digest
  trust_dir = opt[:trust_dir]
  time      = Time.now

  _, signer_digests = digests.find do |algorithm, file_digests|
    file_digests.values.first.name == Gem::Security::DIGEST_NAME
  end

  if @verify_data
    raise Gem::Security::Exception, 'no digests provided (probable bug)' if
      signer_digests.nil? or signer_digests.empty?
  else
    signer_digests = {}
  end

  signer = chain.last

  check_key signer, key if key

  check_cert signer, nil, time if @verify_signer

  check_chain chain, time if @verify_chain

  check_root chain, time if @verify_root

  if @only_trusted
    check_trust chain, digester, trust_dir
  elsif signatures.empty? and digests.empty?
    # trust is irrelevant if there's no signatures to verify
  else
    alert_warning "#{subject signer} is not trusted for #{full_name}"
  end

  signatures.each do |file, _|
    digest = signer_digests[file]

    raise Gem::Security::Exception, "missing digest for #{file}" unless
      digest
  end

  signer_digests.each do |file, digest|
    signature = signatures[file]

    raise Gem::Security::Exception, "missing signature for #{file}" unless
      signature

    check_data signer.public_key, digester, signature, digest if @verify_data
  end

  true
end

#verify_signatures(spec, digests, signatures)

Extracts the certificate chain from the spec and calls #verify to ensure the signatures and certificate chain is valid according to the policy..

[ GitHub ]

  
# File 'lib/rubygems/security/policy.rb', line 282

def verify_signatures(spec, digests, signatures)
  chain = spec.cert_chain.map do |cert_pem|
    OpenSSL::X509::Certificate.new cert_pem
  end

  verify chain, nil, digests, signatures, spec.full_name

  true
end