Class: Gem::Security::Policy

Inherits:
Object
  • Object
show all
Includes:
UserInteraction
Defined in:
lib/rubygems/security/policy.rb,
lib/rubygems/security_option.rb,
lib/rubygems/commands/unpack_command.rb

Overview

:nodoc:

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from UserInteraction

#alert, #alert_error, #alert_warning, #ask, #ask_for_password, #ask_yes_no, #choose_from_list, #say, #terminate_interaction, #verbose

Methods included from DefaultUserInteraction

ui, #ui, ui=, #ui=, use_ui, #use_ui

Methods included from Text

#clean_text, #format_text, #levenshtein_distance, #min3, #truncate_text

Constructor Details

#initialize(name, policy = {}, opt = {}) ⇒ Policy

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



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/rubygems/security/policy.rb', line 27

def initialize(name, policy = {}, opt = {})
  require 'openssl'

  @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

#nameObject (readonly) Also known as: to_s

Returns the value of attribute name



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

def name
  @name
end

#only_signedObject

Returns the value of attribute only_signed



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

def only_signed
  @only_signed
end

#only_trustedObject

Returns the value of attribute only_trusted



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

def only_trusted
  @only_trusted
end

#verify_chainObject

Returns the value of attribute verify_chain



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

def verify_chain
  @verify_chain
end

#verify_dataObject

Returns the value of attribute verify_data



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

def verify_data
  @verify_data
end

#verify_rootObject

Returns the value of attribute verify_root



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

def verify_root
  @verify_root
end

#verify_signerObject

Returns the value of attribute verify_signer



21
22
23
# File 'lib/rubygems/security/policy.rb', line 21

def verify_signer
  @verify_signer
end

Instance Method Details

#check_cert(signer, issuer, time) ⇒ Object

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



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rubygems/security/policy.rb', line 88

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) ⇒ Object

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



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rubygems/security/policy.rb', line 58

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) ⇒ Object

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



77
78
79
80
81
82
# File 'lib/rubygems/security/policy.rb', line 77

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

  true
end

#check_key(signer, key) ⇒ Object

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



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/rubygems/security/policy.rb', line 114

def check_key(signer, key)
  unless signer and 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.public_key.to_pem == key.public_key.to_pem

  true
end

#check_root(chain, time) ⇒ Object

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



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/rubygems/security/policy.rb', line 132

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.to_s != root.subject.to_s # HACK to_s is for ruby 1.8

  check_cert root, root, time
end

#check_trust(chain, digester, trust_dir) ⇒ Object

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



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/rubygems/security/policy.rb', line 151

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_s

  pkey_str = root.public_key.to_s
  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

#inspectObject

:nodoc:



196
197
198
199
200
201
202
# File 'lib/rubygems/security/policy.rb', line 196

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) ⇒ Object

Extracts the email or subject from certificate



186
187
188
189
190
191
192
193
194
# File 'lib/rubygems/security/policy.rb', line 186

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)') ⇒ Object

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.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/rubygems/security/policy.rb', line 211

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::DIGEST_ALGORITHM
  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) ⇒ Object

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



283
284
285
286
287
288
289
290
291
# File 'lib/rubygems/security/policy.rb', line 283

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