# class Gem::Security::Policy

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 in Gem::Security::Policies.

### Attributes

name[R]
only_signed[RW]
only_trusted[RW]
to_s[R]
verify_chain[RW]
verify_data[RW]
verify_root[RW]
verify_signer[RW]

### Public Class Methods

new(name, policy = {}, opt = {}) click to toggle source

Create a new `Gem::Security::Policy` object with the given mode and options.

    # 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

### Public Instance Methods

check_cert(signer, issuer, time) click to toggle source

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

    # 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) && not_before > time
        raise Gem::Security::Exception,
              "#{message} not valid before #{not_before}"
      end

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

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

      true
    end

check_chain(chain, time) click to toggle source

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

    # 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) click to toggle source

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

    # 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) click to toggle source

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

    # File lib/rubygems/security/policy.rb, line 111
    def check_key(signer, key)
      unless signer && key
        return true unless @only_signed

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

      raise Gem::Security::Exception,
        "certificate #{signer.subject} does not match the signing key" unless
          signer.check_private_key(key)

      true
    end

check_root(chain, time) click to toggle source

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

    # File lib/rubygems/security/policy.rb, line 129
    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) click to toggle source

Ensures the root of `chain` has a trusted certificate in `trust_dir` and the digests of the two certificates match according to `digester`

    # File lib/rubygems/security/policy.rb, line 148
    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

verify(chain, key = nil, digests = {}, signatures = {}, full_name = "(unknown)") click to toggle source

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.

    # File lib/rubygems/security/policy.rb, line 208
    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? || 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? && 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) click to toggle source

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

    # File lib/rubygems/security/policy.rb, line 280
    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