123456789_123456789_123456789_123456789_123456789_

Class: Gem::Security::Signer

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
Inherits: Object
Defined in: lib/rubygems/security/signer.rb

Constant Summary

Class Method Summary

Instance Attribute Summary

  • #cert_chain rw

    The chain of certificates for signing including the signing certificate.

  • #digest_algorithm readonly

    The digest algorithm used to create the signature.

  • #key rw

    The private key for the signing certificate.

  • #options readonly

    Signer options.

  • #digest_name readonly Internal use only

    The name of the digest algorithm, used to pull digests out of the hash by name.

::Gem::DefaultUserInteraction - Included

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(key, cert_chain, passphrase = nil, options = {}) ⇒ Signer

Creates a new signer with an RSA #key or path to a key, and a certificate chain containing X509 certificates, encoding certificates or paths to certificates.

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 67

def initialize(key, cert_chain, passphrase = nil, options = {})
  @cert_chain = cert_chain
  @key        = key
  @passphrase = passphrase
  @options = DEFAULT_OPTIONS.merge(options)

  unless @key
    default_key = File.join Gem.default_key_path
    @key = default_key if File.exist? default_key
  end

  unless @cert_chain
    default_cert = File.join Gem.default_cert_path
    @cert_chain = [default_cert] if File.exist? default_cert
  end

  @digest_name      = Gem::Security::DIGEST_NAME
  @digest_algorithm = Gem::Security.create_digest(@digest_name)

  if @key && !@key.is_a?(OpenSSL::PKey::PKey)
    @key = OpenSSL::PKey.read(File.read(@key), @passphrase)
  end

  if @cert_chain
    @cert_chain = @cert_chain.compact.map do |cert|
      next cert if OpenSSL::X509::Certificate === cert

      cert = File.read cert if File.exist? cert

      OpenSSL::X509::Certificate.new cert
    end

    load_cert_chain
  end
end

Class Method Details

.re_sign_cert(expired_cert, expired_cert_path, private_key) {|expired_cert_path, new_expired_cert_path| ... }

Attempts to re-sign an expired cert with a given private key

Yields:

  • (expired_cert_path, new_expired_cert_path)
[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 42

def self.re_sign_cert(expired_cert, expired_cert_path, private_key)
  return unless expired_cert.not_after < Time.now

  expiry = expired_cert.not_after.strftime('%Y%m%d%H%M%S')
  expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}"
  new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file)

  Gem::Security.write(expired_cert, new_expired_cert_path)

  re_signed_cert = Gem::Security.re_sign(
    expired_cert,
    private_key,
    (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days)
  )

  Gem::Security.write(re_signed_cert, expired_cert_path)

  yield(expired_cert_path, new_expired_cert_path) if block_given?
end

Instance Attribute Details

#cert_chain (rw)

The chain of certificates for signing including the signing certificate

[ GitHub ]

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

attr_accessor :cert_chain

#digest_algorithm (readonly)

The digest algorithm used to create the signature

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 23

attr_reader :digest_algorithm

#digest_name (readonly)

This method is for internal use only.

The name of the digest algorithm, used to pull digests out of the hash by name.

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 29

attr_reader :digest_name # :nodoc:

#key (rw)

The private key for the signing certificate

[ GitHub ]

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

attr_accessor :key

#options (readonly)

Signer options

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 34

attr_reader :options

Instance Method Details

#extract_name(cert)

This method is for internal use only.

Extracts the full name of cert. If the certificate has a subjectAltName this value is preferred, otherwise the subject is used.

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 107

def extract_name(cert) # :nodoc:
  subject_alt_name = cert.extensions.find {|e| 'subjectAltName' == e.oid }

  if subject_alt_name
    /\Aemail:/ =~ subject_alt_name.value # rubocop:disable Performance/StartWith

    $' || subject_alt_name.value
  else
    cert.subject
  end
end

#load_cert_chain

This method is for internal use only.

Loads any missing issuers in the cert chain from the trusted certificates.

If the issuer does not exist it is ignored as it will be checked later.

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 124

def load_cert_chain # :nodoc:
  return if @cert_chain.empty?

  while @cert_chain.first.issuer.to_s != @cert_chain.first.subject.to_s do
    issuer = Gem::Security.trust_dir.issuer_of @cert_chain.first

    break unless issuer # cert chain is verified later

    @cert_chain.unshift issuer
  end
end

#re_sign_key(expiration_length: Gem::Security::ONE_YEAR)

This method is for internal use only.

Attempts to re-sign the private key if the signing certificate is expired.

The key will be re-signed if:

  • The expired certificate is self-signed

  • The expired certificate is saved at ~/.gem/gem-public_cert.pem and the private key is saved at ~/.gem/gem-private_key.pem

  • There is no file matching the expiry date at ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S

If the signing certificate can be re-signed the expired certificate will be saved as ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S where the expiry time (not after) is used for the timestamp.

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 173

def re_sign_key(expiration_length: Gem::Security::ONE_YEAR) # :nodoc:
  old_cert = @cert_chain.last

  disk_cert_path = File.join(Gem.default_cert_path)
  disk_cert = File.read(disk_cert_path) rescue nil

  disk_key_path = File.join(Gem.default_key_path)
  disk_key = OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue nil

  return unless disk_key

  if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem
    expiry = old_cert.not_after.strftime('%Y%m%d%H%M%S')
    old_cert_file = "gem-public_cert.pem.expired.#{expiry}"
    old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file)

    unless File.exist?(old_cert_path)
      Gem::Security.write(old_cert, old_cert_path)

      cert = Gem::Security.re_sign(old_cert, @key, expiration_length)

      Gem::Security.write(cert, disk_cert_path)

      alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}")
      alert("Your expired cert will be located at: #{old_cert_path}")

      @cert_chain = [cert]
    end
  end
end

#sign(data)

Sign data with given digest algorithm

[ GitHub ]

  
# File 'lib/rubygems/security/signer.rb', line 139

def sign(data)
  return unless @key

  raise Gem::Security::Exception, 'no certs provided' if @cert_chain.empty?

  if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now
    alert("Your certificate has expired, trying to re-sign it...")

    re_sign_key(
      expiration_length: (Gem::Security::ONE_DAY * options[:expiration_length_days])
    )
  end

  full_name = extract_name @cert_chain.last

  Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name

  @key.sign @digest_algorithm.new, data
end