Geminstaller C0 Coverage Information - RCov

spec/fixture/rubygems_dist/rubygems-trunk/lib/rubygems/security.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
spec/fixture/rubygems_dist/rubygems-trunk/lib/rubygems/security.rb 786 254
69.21%
40.16%

Key

Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.

Coverage Details

1 #--
2 # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
3 # All rights reserved.
4 # See LICENSE.txt for permissions.
5 #++
6 
7 require 'rubygems'
8 require 'rubygems/gem_openssl'
9 
10 # = Signed Gems README
11 #
12 # == Table of Contents
13 # * Overview
14 # * Walkthrough
15 # * Command-Line Options
16 # * OpenSSL Reference
17 # * Bugs/TODO
18 # * About the Author
19 #
20 # == Overview
21 #
22 # Gem::Security implements cryptographic signatures in RubyGems.  The section
23 # below is a step-by-step guide to using signed gems and generating your own.
24 #
25 # == Walkthrough
26 #
27 # In order to start signing your gems, you'll need to build a private key and
28 # a self-signed certificate.  Here's how:
29 #
30 #   # build a private key and certificate for gemmaster@example.com
31 #   $ gem cert --build gemmaster@example.com
32 #
33 # This could take anywhere from 5 seconds to 10 minutes, depending on the
34 # speed of your computer (public key algorithms aren't exactly the speediest
35 # crypto algorithms in the world).  When it's finished, you'll see the files
36 # "gem-private_key.pem" and "gem-public_cert.pem" in the current directory.
37 #
38 # First things first: take the "gem-private_key.pem" file and move it
39 # somewhere private, preferably a directory only you have access to, a floppy
40 # (yuck!), a CD-ROM, or something comparably secure.  Keep your private key
41 # hidden; if it's compromised, someone can sign packages as you (note: PKI has
42 # ways of mitigating the risk of stolen keys; more on that later).
43 #
44 # Now, let's sign an existing gem.  I'll be using my Imlib2-Ruby bindings, but
45 # you can use whatever gem you'd like.  Open up your existing gemspec file and
46 # add the following lines:
47 #
48 #   # signing key and certificate chain
49 #   s.signing_key = '/mnt/floppy/gem-private_key.pem'
50 #   s.cert_chain  = ['gem-public_cert.pem']
51 #
52 # (Be sure to replace "/mnt/floppy" with the ultra-secret path to your private
53 # key).
54 #
55 # After that, go ahead and build your gem as usual.  Congratulations, you've
56 # just built your first signed gem!  If you peek inside your gem file, you'll
57 # see a couple of new files have been added:
58 #
59 #   $ tar tf tar tf Imlib2-Ruby-0.5.0.gem
60 #   data.tar.gz
61 #   data.tar.gz.sig
62 #   metadata.gz
63 #   metadata.gz.sig
64 #
65 # Now let's verify the signature.  Go ahead and install the gem, but add the
66 # following options: "-P HighSecurity", like this:
67 #
68 #   # install the gem with using the security policy "HighSecurity"
69 #   $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
70 #
71 # The -P option sets your security policy -- we'll talk about that in just a
72 # minute.  Eh, what's this?
73 #
74 #   Attempting local installation of 'Imlib2-Ruby-0.5.0.gem'
75 #   ERROR:  Error installing gem Imlib2-Ruby-0.5.0.gem[.gem]: Couldn't
76 #   verify data signature: Untrusted Signing Chain Root: cert =
77 #   '/CN=gemmaster/DC=example/DC=com', error = 'path
78 #   "/root/.rubygems/trust/cert-15dbb43a6edf6a70a85d4e784e2e45312cff7030.pem"
79 #   does not exist'
80 #
81 # The culprit here is the security policy.  RubyGems has several different
82 # security policies.  Let's take a short break and go over the security
83 # policies.  Here's a list of the available security policies, and a brief
84 # description of each one:
85 #
86 # * NoSecurity - Well, no security at all.  Signed packages are treated like
87 #   unsigned packages.
88 # * LowSecurity - Pretty much no security.  If a package is signed then
89 #   RubyGems will make sure the signature matches the signing
90 #   certificate, and that the signing certificate hasn't expired, but
91 #   that's it.  A malicious user could easily circumvent this kind of
92 #   security.
93 # * MediumSecurity - Better than LowSecurity and NoSecurity, but still
94 #   fallible.  Package contents are verified against the signing
95 #   certificate, and the signing certificate is checked for validity,
96 #   and checked against the rest of the certificate chain (if you don't
97 #   know what a certificate chain is, stay tuned, we'll get to that).
98 #   The biggest improvement over LowSecurity is that MediumSecurity
99 #   won't install packages that are signed by untrusted sources.
100 #   Unfortunately, MediumSecurity still isn't totally secure -- a
101 #   malicious user can still unpack the gem, strip the signatures, and
102 #   distribute the gem unsigned.
103 # * HighSecurity - Here's the bugger that got us into this mess.
104 #   The HighSecurity policy is identical to the MediumSecurity policy,
105 #   except that it does not allow unsigned gems.  A malicious user
106 #   doesn't have a whole lot of options here; he can't modify the
107 #   package contents without invalidating the signature, and he can't
108 #   modify or remove signature or the signing certificate chain, or
109 #   RubyGems will simply refuse to install the package.  Oh well, maybe
110 #   he'll have better luck causing problems for CPAN users instead :).
111 #
112 # So, the reason RubyGems refused to install our shiny new signed gem was
113 # because it was from an untrusted source.  Well, my code is infallible
114 # (hah!), so I'm going to add myself as a trusted source.
115 #
116 # Here's how:
117 #
118 #     # add trusted certificate
119 #     gem cert --add gem-public_cert.pem
120 #
121 # I've added my public certificate as a trusted source.  Now I can install
122 # packages signed my private key without any hassle.  Let's try the install
123 # command above again:
124 #
125 #   # install the gem with using the HighSecurity policy (and this time
126 #   # without any shenanigans)
127 #   $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
128 #
129 # This time RubyGems should accept your signed package and begin installing.
130 # While you're waiting for RubyGems to work it's magic, have a look at some of
131 # the other security commands:
132 #
133 #   Usage: gem cert [options]
134 #
135 #   Options:
136 #     -a, --add CERT          Add a trusted certificate.
137 #     -l, --list              List trusted certificates.
138 #     -r, --remove STRING     Remove trusted certificates containing STRING.
139 #     -b, --build EMAIL_ADDR  Build private key and self-signed certificate
140 #                             for EMAIL_ADDR.
141 #     -C, --certificate CERT  Certificate for --sign command.
142 #     -K, --private-key KEY   Private key for --sign command.
143 #     -s, --sign NEWCERT      Sign a certificate with my key and certificate.
144 #
145 # (By the way, you can pull up this list any time you'd like by typing "gem
146 # cert --help")
147 #
148 # Hmm.  We've already covered the "--build" option, and the "--add", "--list",
149 # and "--remove" commands seem fairly straightforward; they allow you to add,
150 # list, and remove the certificates in your trusted certificate list.  But
151 # what's with this "--sign" option?
152 #
153 # To answer that question, let's take a look at "certificate chains", a
154 # concept I mentioned earlier.  There are a couple of problems with
155 # self-signed certificates: first of all, self-signed certificates don't offer
156 # a whole lot of security.  Sure, the certificate says Yukihiro Matsumoto, but
157 # how do I know it was actually generated and signed by matz himself unless he
158 # gave me the certificate in person?
159 #
160 # The second problem is scalability.  Sure, if there are 50 gem authors, then
161 # I have 50 trusted certificates, no problem.  What if there are 500 gem
162 # authors?  1000?  Having to constantly add new trusted certificates is a
163 # pain, and it actually makes the trust system less secure by encouraging
164 # RubyGems users to blindly trust new certificates.
165 #
166 # Here's where certificate chains come in.  A certificate chain establishes an
167 # arbitrarily long chain of trust between an issuing certificate and a child
168 # certificate.  So instead of trusting certificates on a per-developer basis,
169 # we use the PKI concept of certificate chains to build a logical hierarchy of
170 # trust.  Here's a hypothetical example of a trust hierarchy based (roughly)
171 # on geography:
172 #
173 #
174 #                         --------------------------
175 #                         | rubygems@rubyforge.org |
176 #                         --------------------------
177 #                                     |
178 #                   -----------------------------------
179 #                   |                                 |
180 #       ----------------------------    -----------------------------
181 #       | seattle.rb@zenspider.com |    | dcrubyists@richkilmer.com |
182 #       ----------------------------    -----------------------------
183 #            |                |                 |             |
184 #     ---------------   ----------------   -----------   --------------
185 #     | alf@seattle |   | bob@portland |   | pabs@dc |   | tomcope@dc |
186 #     ---------------   ----------------   -----------   --------------
187 #
188 #
189 # Now, rather than having 4 trusted certificates (one for alf@seattle,
190 # bob@portland, pabs@dc, and tomecope@dc), a user could actually get by with 1
191 # certificate: the "rubygems@rubyforge.org" certificate.  Here's how it works:
192 #
193 # I install "Alf2000-Ruby-0.1.0.gem", a package signed by "alf@seattle".  I've
194 # never heard of "alf@seattle", but his certificate has a valid signature from
195 # the "seattle.rb@zenspider.com" certificate, which in turn has a valid
196 # signature from the "rubygems@rubyforge.org" certificate.  Voila!  At this
197 # point, it's much more reasonable for me to trust a package signed by
198 # "alf@seattle", because I can establish a chain to "rubygems@rubyforge.org",
199 # which I do trust.
200 #
201 # And the "--sign" option allows all this to happen.  A developer creates
202 # their build certificate with the "--build" option, then has their
203 # certificate signed by taking it with them to their next regional Ruby meetup
204 # (in our hypothetical example), and it's signed there by the person holding
205 # the regional RubyGems signing certificate, which is signed at the next
206 # RubyConf by the holder of the top-level RubyGems certificate.  At each point
207 # the issuer runs the same command:
208 #
209 #   # sign a certificate with the specified key and certificate
210 #   # (note that this modifies client_cert.pem!)
211 #   $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem
212 #      --sign client_cert.pem
213 #
214 # Then the holder of issued certificate (in this case, our buddy
215 # "alf@seattle"), can start using this signed certificate to sign RubyGems.
216 # By the way, in order to let everyone else know about his new fancy signed
217 # certificate, "alf@seattle" would change his gemspec file to look like this:
218 #
219 #   # signing key (still kept in an undisclosed location!)
220 #   s.signing_key = '/mnt/floppy/alf-private_key.pem'
221 #
222 #   # certificate chain (includes the issuer certificate now too)
223 #   s.cert_chain  = ['/home/alf/doc/seattlerb-public_cert.pem',
224 #                    '/home/alf/doc/alf_at_seattle-public_cert.pem']
225 #
226 # Obviously, this RubyGems trust infrastructure doesn't exist yet.  Also, in
227 # the "real world" issuers actually generate the child certificate from a
228 # certificate request, rather than sign an existing certificate.  And our
229 # hypothetical infrastructure is missing a certificate revocation system.
230 # These are that can be fixed in the future...
231 #
232 # I'm sure your new signed gem has finished installing by now (unless you're
233 # installing rails and all it's dependencies, that is ;D).  At this point you
234 # should know how to do all of these new and interesting things:
235 #
236 # * build a gem signing key and certificate
237 # * modify your existing gems to support signing
238 # * adjust your security policy
239 # * modify your trusted certificate list
240 # * sign a certificate
241 #
242 # If you've got any questions, feel free to contact me at the email address
243 # below.  The next couple of sections
244 #
245 #
246 # == Command-Line Options
247 #
248 # Here's a brief summary of the certificate-related command line options:
249 #
250 #   gem install
251 #     -P, --trust-policy POLICY        Specify gem trust policy.
252 #
253 #   gem cert
254 #     -a, --add CERT                   Add a trusted certificate.
255 #     -l, --list                       List trusted certificates.
256 #     -r, --remove STRING              Remove trusted certificates containing
257 #                                      STRING.
258 #     -b, --build EMAIL_ADDR           Build private key and self-signed
259 #                                      certificate for EMAIL_ADDR.
260 #     -C, --certificate CERT           Certificate for --sign command.
261 #     -K, --private-key KEY            Private key for --sign command.
262 #     -s, --sign NEWCERT               Sign a certificate with my key and
263 #                                      certificate.
264 #
265 # A more detailed description of each options is available in the walkthrough
266 # above.
267 #
268 #
269 # == OpenSSL Reference
270 #
271 # The .pem files generated by --build and --sign are just basic OpenSSL PEM
272 # files.  Here's a couple of useful commands for manipulating them:
273 #
274 #   # convert a PEM format X509 certificate into DER format:
275 #   # (note: Windows .cer files are X509 certificates in DER format)
276 #   $ openssl x509 -in input.pem -outform der -out output.der
277 #
278 #   # print out the certificate in a human-readable format:
279 #   $ openssl x509 -in input.pem -noout -text
280 #
281 # And you can do the same thing with the private key file as well:
282 #
283 #   # convert a PEM format RSA key into DER format:
284 #   $ openssl rsa -in input_key.pem -outform der -out output_key.der
285 #
286 #   # print out the key in a human readable format:
287 #   $ openssl rsa -in input_key.pem -noout -text
288 #
289 # == Bugs/TODO
290 #
291 # * There's no way to define a system-wide trust list.
292 # * custom security policies (from a YAML file, etc)
293 # * Simple method to generate a signed certificate request
294 # * Support for OCSP, SCVP, CRLs, or some other form of cert
295 #   status check (list is in order of preference)
296 # * Support for encrypted private keys
297 # * Some sort of semi-formal trust hierarchy (see long-winded explanation
298 #   above)
299 # * Path discovery (for gem certificate chains that don't have a self-signed
300 #   root) -- by the way, since we don't have this, THE ROOT OF THE CERTIFICATE
301 #   CHAIN MUST BE SELF SIGNED if Policy#verify_root is true (and it is for the
302 #   MediumSecurity and HighSecurity policies)
303 # * Better explanation of X509 naming (ie, we don't have to use email
304 #   addresses)
305 # * Possible alternate signing mechanisms (eg, via PGP).  this could be done
306 #   pretty easily by adding a :signing_type attribute to the gemspec, then add
307 #   the necessary support in other places
308 # * Honor AIA field (see note about OCSP above)
309 # * Maybe honor restriction extensions?
310 # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
311 #   file, instead of an array embedded in the metadata.  ideas?
312 # * Possibly embed signature and key algorithms into metadata (right now
313 #   they're assumed to be the same as what's set in Gem::Security::OPT)
314 #
315 # == About the Author
316 #
317 # Paul Duncan <pabs@pablotron.org>
318 # http://pablotron.org/
319 
320 module Gem::Security
321 
322   class Exception < Gem::Exception; end
323 
324   #
325   # default options for most of the methods below
326   #
327   OPT = {
328     # private key options
329     :key_algo   => Gem::SSL::PKEY_RSA,
330     :key_size   => 2048,
331 
332     # public cert options
333     :cert_age   => 365 * 24 * 3600, # 1 year
334     :dgst_algo  => Gem::SSL::DIGEST_SHA1,
335 
336     # x509 certificate extensions
337     :cert_exts  => {
338       'basicConstraints'      => 'CA:FALSE',
339       'subjectKeyIdentifier'  => 'hash',
340       'keyUsage'              => 'keyEncipherment,dataEncipherment,digitalSignature',
341   },
342 
343   # save the key and cert to a file in build_self_signed_cert()?
344   :save_key   => true,
345   :save_cert  => true,
346 
347   # if you define either of these, then they'll be used instead of
348   # the output_fmt macro below
349   :save_key_path => nil,
350   :save_cert_path => nil,
351 
352   # output name format for self-signed certs
353   :output_fmt => 'gem-%s.pem',
354   :munge_re   => Regexp.new(/[^a-z0-9_.-]+/),
355 
356   # output directory for trusted certificate checksums
357   :trust_dir => File::join(Gem.user_home, '.gem', 'trust'),
358 
359   # default permissions for trust directory and certs
360   :perms => {
361     :trust_dir      => 0700,
362     :trusted_cert   => 0600,
363     :signing_cert   => 0600,
364     :signing_key    => 0600,
365   },
366   }
367 
368   #
369   # A Gem::Security::Policy object encapsulates the settings for verifying
370   # signed gem files.  This is the base class.  You can either declare an
371   # instance of this or use one of the preset security policies below.
372   #
373   class Policy
374     attr_accessor :verify_data, :verify_signer, :verify_chain,
375       :verify_root, :only_trusted, :only_signed
376 
377     #
378     # Create a new Gem::Security::Policy object with the given mode and
379     # options.
380     #
381     def initialize(policy = {}, opt = {})
382       # set options
383       @opt = Gem::Security::OPT.merge(opt)
384 
385       # build policy
386       policy.each_pair do |key, val|
387         case key
388         when :verify_data   then @verify_data   = val
389         when :verify_signer then @verify_signer = val
390         when :verify_chain  then @verify_chain  = val
391         when :verify_root   then @verify_root   = val
392         when :only_trusted  then @only_trusted  = val
393         when :only_signed   then @only_signed   = val
394         end
395       end
396     end
397 
398     #
399     # Get the path to the file for this cert.
400     #
401     def self.trusted_cert_path(cert, opt = {})
402       opt = Gem::Security::OPT.merge(opt)
403 
404       # get digest algorithm, calculate checksum of root.subject
405       algo = opt[:dgst_algo]
406       dgst = algo.hexdigest(cert.subject.to_s)
407 
408       # build path to trusted cert file
409       name = "cert-#{dgst}.pem"
410 
411       # join and return path components
412       File::join(opt[:trust_dir], name)
413     end
414 
415     #
416     # Verify that the gem data with the given signature and signing chain
417     # matched this security policy at the specified time.
418     #
419     def verify_gem(signature, data, chain, time = Time.now)
420       Gem.ensure_ssl_available
421       cert_class = OpenSSL::X509::Certificate
422       exc = Gem::Security::Exception
423       chain ||= []
424 
425       chain = chain.map{ |str| cert_class.new(str) }
426       signer, ch_len = chain[-1], chain.size
427 
428       # make sure signature is valid
429       if @verify_data
430         # get digest algorithm (TODO: this should be configurable)
431         dgst = @opt[:dgst_algo]
432 
433         # verify the data signature (this is the most important part, so don't
434         # screw it up :D)
435         v = signer.public_key.verify(dgst.new, signature, data)
436         raise exc, "Invalid Gem Signature" unless v
437 
438         # make sure the signer is valid
439         if @verify_signer
440           # make sure the signing cert is valid right now
441           v = signer.check_validity(nil, time)
442           raise exc, "Invalid Signature: #{v[:desc]}" unless v[:is_valid]
443         end
444       end
445 
446       # make sure the certificate chain is valid
447       if @verify_chain
448         # iterate down over the chain and verify each certificate against it's
449         # issuer
450         (ch_len - 1).downto(1) do |i|
451           issuer, cert = chain[i - 1, 2]
452           v = cert.check_validity(issuer, time)
453           raise exc, "%s: cert = '%s', error = '%s'" % [
454               'Invalid Signing Chain', cert.subject, v[:desc]
455           ] unless v[:is_valid]
456         end
457 
458         # verify root of chain
459         if @verify_root
460           # make sure root is self-signed
461           root = chain[0]
462           raise exc, "%s: %s (subject = '%s', issuer = '%s')" % [
463               'Invalid Signing Chain Root',
464               'Subject does not match Issuer for Gem Signing Chain',
465               root.subject.to_s,
466               root.issuer.to_s,
467           ] unless root.issuer.to_s == root.subject.to_s
468 
469           # make sure root is valid
470           v = root.check_validity(root, time)
471           raise exc, "%s: cert = '%s', error = '%s'" % [
472               'Invalid Signing Chain Root', root.subject, v[:desc]
473           ] unless v[:is_valid]
474 
475           # verify that the chain root is trusted
476           if @only_trusted
477             # get digest algorithm, calculate checksum of root.subject
478             algo = @opt[:dgst_algo]
479             path = Gem::Security::Policy.trusted_cert_path(root, @opt)
480 
481             # check to make sure trusted path exists
482             raise exc, "%s: cert = '%s', error = '%s'" % [
483                 'Untrusted Signing Chain Root',
484                 root.subject.to_s,
485                 "path \"#{path}\" does not exist",
486             ] unless File.exist?(path)
487 
488             # load calculate digest from saved cert file
489             save_cert = OpenSSL::X509::Certificate.new(File.read(path))
490             save_dgst = algo.digest(save_cert.public_key.to_s)
491 
492             # create digest of public key
493             pkey_str = root.public_key.to_s
494             cert_dgst = algo.digest(pkey_str)
495 
496             # now compare the two digests, raise exception
497             # if they don't match
498             raise exc, "%s: %s (saved = '%s', root = '%s')" % [
499                 'Invalid Signing Chain Root',
500                 "Saved checksum doesn't match root checksum",
501                 save_dgst, cert_dgst,
502             ] unless save_dgst == cert_dgst
503           end
504         end
505 
506         # return the signing chain
507         chain.map { |cert| cert.subject }
508       end
509     end
510   end
511 
512   #
513   # No security policy: all package signature checks are disabled.
514   #
515   NoSecurity = Policy.new(
516     :verify_data      => false,
517     :verify_signer    => false,
518     :verify_chain     => false,
519     :verify_root      => false,
520     :only_trusted     => false,
521     :only_signed      => false
522   )
523 
524   #
525   # AlmostNo security policy: only verify that the signing certificate is the
526   # one that actually signed the data.  Make no attempt to verify the signing
527   # certificate chain.
528   #
529   # This policy is basically useless. better than nothing, but can still be
530   # easily spoofed, and is not recommended.
531   #
532   AlmostNoSecurity = Policy.new(
533     :verify_data      => true,
534     :verify_signer    => false,
535     :verify_chain     => false,
536     :verify_root      => false,
537     :only_trusted     => false,
538     :only_signed      => false
539   )
540 
541   #
542   # Low security policy: only verify that the signing certificate is actually
543   # the gem signer, and that the signing certificate is valid.
544   #
545   # This policy is better than nothing, but can still be easily spoofed, and
546   # is not recommended.
547   #
548   LowSecurity = Policy.new(
549     :verify_data      => true,
550     :verify_signer    => true,
551     :verify_chain     => false,
552     :verify_root      => false,
553     :only_trusted     => false,
554     :only_signed      => false
555   )
556 
557   #
558   # Medium security policy: verify the signing certificate, verify the signing
559   # certificate chain all the way to the root certificate, and only trust root
560   # certificates that we have explicitly allowed trust for.
561   #
562   # This security policy is reasonable, but it allows unsigned packages, so a
563   # malicious person could simply delete the package signature and pass the
564   # gem off as unsigned.
565   #
566   MediumSecurity = Policy.new(
567     :verify_data      => true,
568     :verify_signer    => true,
569     :verify_chain     => true,
570     :verify_root      => true,
571     :only_trusted     => true,
572     :only_signed      => false
573   )
574 
575   #
576   # High security policy: only allow signed gems to be installed, verify the
577   # signing certificate, verify the signing certificate chain all the way to
578   # the root certificate, and only trust root certificates that we have
579   # explicitly allowed trust for.
580   #
581   # This security policy is significantly more difficult to bypass, and offers
582   # a reasonable guarantee that the contents of the gem have not been altered.
583   #
584   HighSecurity = Policy.new(
585     :verify_data      => true,
586     :verify_signer    => true,
587     :verify_chain     => true,
588     :verify_root      => true,
589     :only_trusted     => true,
590     :only_signed      => true
591   )
592 
593   #
594   # Hash of configured security policies
595   #
596   Policies = {
597     'NoSecurity'       => NoSecurity,
598     'AlmostNoSecurity' => AlmostNoSecurity,
599     'LowSecurity'      => LowSecurity,
600     'MediumSecurity'   => MediumSecurity,
601     'HighSecurity'     => HighSecurity,
602   }
603 
604   #
605   # Sign the cert cert with @signing_key and @signing_cert, using the digest
606   # algorithm opt[:dgst_algo]. Returns the newly signed certificate.
607   #
608   def self.sign_cert(cert, signing_key, signing_cert, opt = {})
609     opt = OPT.merge(opt)
610 
611     # set up issuer information
612     cert.issuer = signing_cert.subject
613     cert.sign(signing_key, opt[:dgst_algo].new)
614 
615     cert
616   end
617 
618   #
619   # Make sure the trust directory exists.  If it does exist, make sure it's
620   # actually a directory.  If not, then create it with the appropriate
621   # permissions.
622   #
623   def self.verify_trust_dir(path, perms)
624     # if the directory exists, then make sure it is in fact a directory.  if
625     # it doesn't exist, then create it with the appropriate permissions
626     if File.exist?(path)
627       # verify that the trust directory is actually a directory
628       unless File.directory?(path)
629         err = "trust directory #{path} isn't a directory"
630         raise Gem::Security::Exception, err
631       end
632     else
633       # trust directory doesn't exist, so create it with permissions
634       FileUtils.mkdir_p(path)
635       FileUtils.chmod(perms, path)
636     end
637   end
638 
639   #
640   # Build a certificate from the given DN and private key.
641   #
642   def self.build_cert(name, key, opt = {})
643     Gem.ensure_ssl_available
644     opt = OPT.merge(opt)
645 
646     # create new cert
647     ret = OpenSSL::X509::Certificate.new
648 
649     # populate cert attributes
650     ret.version = 2
651     ret.serial = 0
652     ret.public_key = key.public_key
653     ret.not_before = Time.now
654     ret.not_after = Time.now + opt[:cert_age]
655     ret.subject = name
656 
657     # add certificate extensions
658     ef = OpenSSL::X509::ExtensionFactory.new(nil, ret)
659     ret.extensions = opt[:cert_exts].map { |k, v| ef.create_extension(k, v) }
660 
661     # sign cert
662     i_key, i_cert = opt[:issuer_key] || key, opt[:issuer_cert] || ret
663     ret = sign_cert(ret, i_key, i_cert, opt)
664 
665     # return cert
666     ret
667   end
668 
669   #
670   # Build a self-signed certificate for the given email address.
671   #
672   def self.build_self_signed_cert(email_addr, opt = {})
673     Gem.ensure_ssl_available
674     opt = OPT.merge(opt)
675     path = { :key => nil, :cert => nil }
676 
677     # split email address up
678     cn, dcs = email_addr.split('@')
679     dcs = dcs.split('.')
680 
681     # munge email CN and DCs
682     cn = cn.gsub(opt[:munge_re], '_')
683     dcs = dcs.map { |dc| dc.gsub(opt[:munge_re], '_') }
684 
685     # create DN
686     name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/')
687     name = OpenSSL::X509::Name::parse(name)
688 
689     # build private key
690     key = opt[:key_algo].new(opt[:key_size])
691 
692     # method name pretty much says it all :)
693     verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
694 
695     # if we're saving the key, then write it out
696     if opt[:save_key]
697       path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key')
698       File.open(path[:key], 'wb') do |file|
699         file.chmod(opt[:perms][:signing_key])
700         file.write(key.to_pem)
701       end
702     end
703 
704     # build self-signed public cert from key
705     cert = build_cert(name, key, opt)
706 
707     # if we're saving the cert, then write it out
708     if opt[:save_cert]
709       path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert')
710       File.open(path[:cert], 'wb') do |file|
711         file.chmod(opt[:perms][:signing_cert])
712         file.write(cert.to_pem)
713       end
714     end
715 
716     # return key, cert, and paths (if applicable)
717     { :key => key, :cert => cert,
718       :key_path => path[:key], :cert_path => path[:cert] }
719   end
720 
721   #
722   # Add certificate to trusted cert list.
723   #
724   # Note: At the moment these are stored in OPT[:trust_dir], although that
725   # directory may change in the future.
726   #
727   def self.add_trusted_cert(cert, opt = {})
728     opt = OPT.merge(opt)
729 
730     # get destination path
731     path = Gem::Security::Policy.trusted_cert_path(cert, opt)
732 
733     # verify trust directory (can't write to nowhere, you know)
734     verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
735 
736     # write cert to output file
737     File.open(path, 'wb') do |file|
738       file.chmod(opt[:perms][:trusted_cert])
739       file.write(cert.to_pem)
740     end
741 
742     # return nil
743     nil
744   end
745 
746   #
747   # Basic OpenSSL-based package signing class.
748   #
749   class Signer
750     attr_accessor :key, :cert_chain
751 
752     def initialize(key, cert_chain)
753       Gem.ensure_ssl_available
754       @algo = Gem::Security::OPT[:dgst_algo]
755       @key, @cert_chain = key, cert_chain
756 
757       # check key, if it's a file, and if it's key, leave it alone
758       if @key && !@key.kind_of?(OpenSSL::PKey::PKey)
759         @key = OpenSSL::PKey::RSA.new(File.read(@key))
760       end
761 
762       # check cert chain, if it's a file, load it, if it's cert data, convert
763       # it into a cert object, and if it's a cert object, leave it alone
764       if @cert_chain
765         @cert_chain = @cert_chain.map do |cert|
766           # check cert, if it's a file, load it, if it's cert data, convert it
767           # into a cert object, and if it's a cert object, leave it alone
768           if cert && !cert.kind_of?(OpenSSL::X509::Certificate)
769             cert = File.read(cert) if File::exist?(cert)
770             cert = OpenSSL::X509::Certificate.new(cert)
771           end
772           cert
773         end
774       end
775     end
776 
777     #
778     # Sign data with given digest algorithm
779     #
780     def sign(data)
781       @key.sign(@algo.new, data)
782     end
783 
784   end
785 end
786 

Generated on Mon May 10 23:40:28 -0700 2010 with rcov 0.9.8