Class: Gem::Security::Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/rubygems/security/signer.rb

Overview

Basic OpenSSL-based package signing class.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key, cert_chain, passphrase = nil) ⇒ 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.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rubygems/security/signer.rb', line 32

def initialize key, cert_chain, passphrase = nil
  @cert_chain = cert_chain
  @key        = key

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

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

  @digest_algorithm = Gem::Security::DIGEST_ALGORITHM
  @digest_name      = Gem::Security::DIGEST_NAME

  @key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if
    @key and not OpenSSL::PKey::RSA === @key

  if @cert_chain then
    @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

Instance Attribute Details

#cert_chainObject

The chain of certificates for signing including the signing certificate



9
10
11
# File 'lib/rubygems/security/signer.rb', line 9

def cert_chain
  @cert_chain
end

#digest_algorithmObject (readonly)

The digest algorithm used to create the signature



19
20
21
# File 'lib/rubygems/security/signer.rb', line 19

def digest_algorithm
  @digest_algorithm
end

#digest_nameObject (readonly)

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



25
26
27
# File 'lib/rubygems/security/signer.rb', line 25

def digest_name
  @digest_name
end

#keyObject

The private key for the signing certificate



14
15
16
# File 'lib/rubygems/security/signer.rb', line 14

def key
  @key
end

Instance Method Details

#extract_name(cert) ⇒ Object

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



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/rubygems/security/signer.rb', line 69

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

  if subject_alt_name then
    /\Aemail:/ =~ subject_alt_name.value

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

#load_cert_chainObject

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.



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rubygems/security/signer.rb', line 86

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_keyObject

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

  • 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-pubilc_cert.pem.expired.%Y%m%d%H%M%S where the expiry time (not after) is used for the timestamp.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/rubygems/security/signer.rb', line 128

def re_sign_key # :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  =
    File.read File.join(Gem.default_key_path) rescue nil

  if disk_key == @key.to_pem and disk_cert == old_cert.to_pem then
    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 then
      Gem::Security.write old_cert, old_cert_path

      cert = Gem::Security.re_sign old_cert, @key

      Gem::Security.write cert, disk_cert_path

      @cert_chain = [cert]
    end
  end
end

#sign(data) ⇒ Object

Sign data with given digest algorithm



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/rubygems/security/signer.rb', line 101

def sign data
  return unless @key

  if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now then
    re_sign_key
  end

  full_name = extract_name @cert_chain.last

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

  @key.sign @digest_algorithm.new, data
end