libgems 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. data/ChangeLog +5811 -0
  2. data/History.txt +887 -0
  3. data/LICENSE.txt +51 -0
  4. data/README.md +87 -0
  5. data/Rakefile +113 -0
  6. data/lib/gauntlet_libgems.rb +50 -0
  7. data/lib/libgems.rb +1246 -0
  8. data/lib/libgems/builder.rb +102 -0
  9. data/lib/libgems/command.rb +534 -0
  10. data/lib/libgems/command_manager.rb +182 -0
  11. data/lib/libgems/commands/build_command.rb +53 -0
  12. data/lib/libgems/commands/cert_command.rb +86 -0
  13. data/lib/libgems/commands/check_command.rb +80 -0
  14. data/lib/libgems/commands/cleanup_command.rb +106 -0
  15. data/lib/libgems/commands/contents_command.rb +98 -0
  16. data/lib/libgems/commands/dependency_command.rb +195 -0
  17. data/lib/libgems/commands/environment_command.rb +133 -0
  18. data/lib/libgems/commands/fetch_command.rb +67 -0
  19. data/lib/libgems/commands/generate_index_command.rb +133 -0
  20. data/lib/libgems/commands/help_command.rb +172 -0
  21. data/lib/libgems/commands/install_command.rb +178 -0
  22. data/lib/libgems/commands/list_command.rb +35 -0
  23. data/lib/libgems/commands/lock_command.rb +110 -0
  24. data/lib/libgems/commands/mirror_command.rb +111 -0
  25. data/lib/libgems/commands/outdated_command.rb +33 -0
  26. data/lib/libgems/commands/owner_command.rb +75 -0
  27. data/lib/libgems/commands/pristine_command.rb +93 -0
  28. data/lib/libgems/commands/push_command.rb +56 -0
  29. data/lib/libgems/commands/query_command.rb +280 -0
  30. data/lib/libgems/commands/rdoc_command.rb +91 -0
  31. data/lib/libgems/commands/search_command.rb +31 -0
  32. data/lib/libgems/commands/server_command.rb +86 -0
  33. data/lib/libgems/commands/sources_command.rb +157 -0
  34. data/lib/libgems/commands/specification_command.rb +125 -0
  35. data/lib/libgems/commands/stale_command.rb +27 -0
  36. data/lib/libgems/commands/uninstall_command.rb +83 -0
  37. data/lib/libgems/commands/unpack_command.rb +121 -0
  38. data/lib/libgems/commands/update_command.rb +160 -0
  39. data/lib/libgems/commands/which_command.rb +86 -0
  40. data/lib/libgems/config_file.rb +345 -0
  41. data/lib/libgems/custom_require.rb +44 -0
  42. data/lib/libgems/defaults.rb +101 -0
  43. data/lib/libgems/dependency.rb +227 -0
  44. data/lib/libgems/dependency_installer.rb +286 -0
  45. data/lib/libgems/dependency_list.rb +208 -0
  46. data/lib/libgems/doc_manager.rb +242 -0
  47. data/lib/libgems/errors.rb +35 -0
  48. data/lib/libgems/exceptions.rb +91 -0
  49. data/lib/libgems/ext.rb +18 -0
  50. data/lib/libgems/ext/builder.rb +56 -0
  51. data/lib/libgems/ext/configure_builder.rb +25 -0
  52. data/lib/libgems/ext/ext_conf_builder.rb +24 -0
  53. data/lib/libgems/ext/rake_builder.rb +39 -0
  54. data/lib/libgems/format.rb +81 -0
  55. data/lib/libgems/gem_openssl.rb +92 -0
  56. data/lib/libgems/gem_path_searcher.rb +100 -0
  57. data/lib/libgems/gem_runner.rb +79 -0
  58. data/lib/libgems/gemcutter_utilities.rb +49 -0
  59. data/lib/libgems/indexer.rb +720 -0
  60. data/lib/libgems/install_update_options.rb +125 -0
  61. data/lib/libgems/installer.rb +604 -0
  62. data/lib/libgems/local_remote_options.rb +135 -0
  63. data/lib/libgems/old_format.rb +153 -0
  64. data/lib/libgems/package.rb +97 -0
  65. data/lib/libgems/package/f_sync_dir.rb +23 -0
  66. data/lib/libgems/package/tar_header.rb +266 -0
  67. data/lib/libgems/package/tar_input.rb +222 -0
  68. data/lib/libgems/package/tar_output.rb +144 -0
  69. data/lib/libgems/package/tar_reader.rb +106 -0
  70. data/lib/libgems/package/tar_reader/entry.rb +141 -0
  71. data/lib/libgems/package/tar_writer.rb +241 -0
  72. data/lib/libgems/package_task.rb +126 -0
  73. data/lib/libgems/platform.rb +183 -0
  74. data/lib/libgems/remote_fetcher.rb +414 -0
  75. data/lib/libgems/require_paths_builder.rb +18 -0
  76. data/lib/libgems/requirement.rb +153 -0
  77. data/lib/libgems/security.rb +814 -0
  78. data/lib/libgems/server.rb +872 -0
  79. data/lib/libgems/source_index.rb +597 -0
  80. data/lib/libgems/source_info_cache.rb +395 -0
  81. data/lib/libgems/source_info_cache_entry.rb +56 -0
  82. data/lib/libgems/spec_fetcher.rb +337 -0
  83. data/lib/libgems/specification.rb +1487 -0
  84. data/lib/libgems/test_utilities.rb +147 -0
  85. data/lib/libgems/text.rb +65 -0
  86. data/lib/libgems/uninstaller.rb +278 -0
  87. data/lib/libgems/user_interaction.rb +527 -0
  88. data/lib/libgems/validator.rb +240 -0
  89. data/lib/libgems/version.rb +316 -0
  90. data/lib/libgems/version_option.rb +65 -0
  91. data/lib/rbconfig/datadir.rb +20 -0
  92. data/test/bogussources.rb +8 -0
  93. data/test/data/gem-private_key.pem +27 -0
  94. data/test/data/gem-public_cert.pem +20 -0
  95. data/test/fake_certlib/openssl.rb +7 -0
  96. data/test/foo/discover.rb +0 -0
  97. data/test/gem_installer_test_case.rb +97 -0
  98. data/test/gem_package_tar_test_case.rb +132 -0
  99. data/test/gemutilities.rb +605 -0
  100. data/test/insure_session.rb +43 -0
  101. data/test/mockgemui.rb +56 -0
  102. data/test/plugin/exception/libgems_plugin.rb +2 -0
  103. data/test/plugin/load/libgems_plugin.rb +1 -0
  104. data/test/plugin/standarderror/libgems_plugin.rb +2 -0
  105. data/test/private_key.pem +27 -0
  106. data/test/public_cert.pem +20 -0
  107. data/test/rubygems_plugin.rb +21 -0
  108. data/test/simple_gem.rb +66 -0
  109. data/test/test_config.rb +12 -0
  110. data/test/test_gem.rb +780 -0
  111. data/test/test_gem_builder.rb +27 -0
  112. data/test/test_gem_command.rb +178 -0
  113. data/test/test_gem_command_manager.rb +207 -0
  114. data/test/test_gem_commands_build_command.rb +74 -0
  115. data/test/test_gem_commands_cert_command.rb +124 -0
  116. data/test/test_gem_commands_check_command.rb +18 -0
  117. data/test/test_gem_commands_contents_command.rb +156 -0
  118. data/test/test_gem_commands_dependency_command.rb +216 -0
  119. data/test/test_gem_commands_environment_command.rb +144 -0
  120. data/test/test_gem_commands_fetch_command.rb +76 -0
  121. data/test/test_gem_commands_generate_index_command.rb +135 -0
  122. data/test/test_gem_commands_install_command.rb +315 -0
  123. data/test/test_gem_commands_list_command.rb +36 -0
  124. data/test/test_gem_commands_lock_command.rb +68 -0
  125. data/test/test_gem_commands_mirror_command.rb +60 -0
  126. data/test/test_gem_commands_outdated_command.rb +40 -0
  127. data/test/test_gem_commands_owner_command.rb +105 -0
  128. data/test/test_gem_commands_pristine_command.rb +108 -0
  129. data/test/test_gem_commands_push_command.rb +81 -0
  130. data/test/test_gem_commands_query_command.rb +426 -0
  131. data/test/test_gem_commands_server_command.rb +59 -0
  132. data/test/test_gem_commands_sources_command.rb +209 -0
  133. data/test/test_gem_commands_specification_command.rb +139 -0
  134. data/test/test_gem_commands_stale_command.rb +38 -0
  135. data/test/test_gem_commands_uninstall_command.rb +83 -0
  136. data/test/test_gem_commands_unpack_command.rb +199 -0
  137. data/test/test_gem_commands_update_command.rb +207 -0
  138. data/test/test_gem_commands_which_command.rb +66 -0
  139. data/test/test_gem_config_file.rb +287 -0
  140. data/test/test_gem_dependency.rb +149 -0
  141. data/test/test_gem_dependency_installer.rb +661 -0
  142. data/test/test_gem_dependency_list.rb +230 -0
  143. data/test/test_gem_doc_manager.rb +31 -0
  144. data/test/test_gem_ext_configure_builder.rb +84 -0
  145. data/test/test_gem_ext_ext_conf_builder.rb +173 -0
  146. data/test/test_gem_ext_rake_builder.rb +81 -0
  147. data/test/test_gem_format.rb +70 -0
  148. data/test/test_gem_gem_path_searcher.rb +78 -0
  149. data/test/test_gem_gem_runner.rb +45 -0
  150. data/test/test_gem_gemcutter_utilities.rb +103 -0
  151. data/test/test_gem_indexer.rb +673 -0
  152. data/test/test_gem_install_update_options.rb +68 -0
  153. data/test/test_gem_installer.rb +857 -0
  154. data/test/test_gem_local_remote_options.rb +97 -0
  155. data/test/test_gem_package_tar_header.rb +130 -0
  156. data/test/test_gem_package_tar_input.rb +112 -0
  157. data/test/test_gem_package_tar_output.rb +97 -0
  158. data/test/test_gem_package_tar_reader.rb +46 -0
  159. data/test/test_gem_package_tar_reader_entry.rb +109 -0
  160. data/test/test_gem_package_tar_writer.rb +144 -0
  161. data/test/test_gem_package_task.rb +59 -0
  162. data/test/test_gem_platform.rb +264 -0
  163. data/test/test_gem_remote_fetcher.rb +740 -0
  164. data/test/test_gem_requirement.rb +292 -0
  165. data/test/test_gem_server.rb +356 -0
  166. data/test/test_gem_silent_ui.rb +113 -0
  167. data/test/test_gem_source_index.rb +461 -0
  168. data/test/test_gem_spec_fetcher.rb +410 -0
  169. data/test/test_gem_specification.rb +1334 -0
  170. data/test/test_gem_stream_ui.rb +218 -0
  171. data/test/test_gem_text.rb +43 -0
  172. data/test/test_gem_uninstaller.rb +146 -0
  173. data/test/test_gem_validator.rb +63 -0
  174. data/test/test_gem_version.rb +181 -0
  175. data/test/test_gem_version_option.rb +89 -0
  176. data/test/test_kernel.rb +59 -0
  177. metadata +402 -0
@@ -0,0 +1,18 @@
1
+ require 'libgems'
2
+
3
+ # TODO: remove after 1.9.1 dropped
4
+ module LibGems::RequirePathsBuilder
5
+ def write_require_paths_file_if_needed(spec = @spec, gem_home = @gem_home)
6
+ return if spec.require_paths == ["lib"] &&
7
+ (spec.bindir.nil? || spec.bindir == "bin")
8
+ file_name = File.join(gem_home, 'gems', "#{@spec.full_name}", ".require_paths")
9
+ file_name.untaint
10
+ File.open(file_name, "w") do |file|
11
+ spec.require_paths.each do |path|
12
+ file.puts path
13
+ end
14
+ file.puts spec.bindir if spec.bindir
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,153 @@
1
+ require "libgems/version"
2
+
3
+ ##
4
+ # A Requirement is a set of one or more version restrictions. It supports a
5
+ # few (<tt>=, !=, >, <, >=, <=, ~></tt>) different restriction operators.
6
+
7
+ class LibGems::Requirement
8
+ include Comparable
9
+
10
+ OPS = { #:nodoc:
11
+ "=" => lambda { |v, r| v == r },
12
+ "!=" => lambda { |v, r| v != r },
13
+ ">" => lambda { |v, r| v > r },
14
+ "<" => lambda { |v, r| v < r },
15
+ ">=" => lambda { |v, r| v >= r },
16
+ "<=" => lambda { |v, r| v <= r },
17
+ "~>" => lambda { |v, r| v >= r && v.release < r.bump }
18
+ }
19
+
20
+ quoted = OPS.keys.map { |k| Regexp.quote k }.join "|"
21
+ PATTERN = /\A\s*(#{quoted})?\s*(#{LibGems::Version::VERSION_PATTERN})\s*\z/
22
+
23
+ ##
24
+ # Factory method to create a LibGems::Requirement object. Input may be
25
+ # a Version, a String, or nil. Intended to simplify client code.
26
+ #
27
+ # If the input is "weird", the default version requirement is
28
+ # returned.
29
+
30
+ def self.create input
31
+ case input
32
+ when LibGems::Requirement then
33
+ input
34
+ when LibGems::Version, Array then
35
+ new input
36
+ else
37
+ if input.respond_to? :to_str then
38
+ new [input.to_str]
39
+ else
40
+ default
41
+ end
42
+ end
43
+ end
44
+
45
+ ##
46
+ # A default "version requirement" can surely _only_ be '>= 0'.
47
+ #--
48
+ # This comment once said:
49
+ #
50
+ # "A default "version requirement" can surely _only_ be '> 0'."
51
+
52
+ def self.default
53
+ new '>= 0'
54
+ end
55
+
56
+ ##
57
+ # Parse +obj+, returning an <tt>[op, version]</tt> pair. +obj+ can
58
+ # be a String or a LibGems::Version.
59
+ #
60
+ # If +obj+ is a String, it can be either a full requirement
61
+ # specification, like <tt>">= 1.2"</tt>, or a simple version number,
62
+ # like <tt>"1.2"</tt>.
63
+ #
64
+ # parse("> 1.0") # => [">", "1.0"]
65
+ # parse("1.0") # => ["=", "1.0"]
66
+ # parse(LibGems::Version.new("1.0")) # => ["=, "1.0"]
67
+
68
+ def self.parse obj
69
+ return ["=", obj] if LibGems::Version === obj
70
+
71
+ unless PATTERN =~ obj.to_s
72
+ raise ArgumentError, "Illformed requirement [#{obj.inspect}]"
73
+ end
74
+
75
+ [$1 || "=", LibGems::Version.new($2)]
76
+ end
77
+
78
+ ##
79
+ # An array of requirement pairs. The first element of the pair is
80
+ # the op, and the second is the LibGems::Version.
81
+
82
+ attr_reader :requirements #:nodoc:
83
+
84
+ ##
85
+ # Constructs a requirement from +requirements+. Requirements can be
86
+ # Strings, LibGems::Versions, or Arrays of those. +nil+ and duplicate
87
+ # requirements are ignored. An empty set of +requirements+ is the
88
+ # same as <tt>">= 0"</tt>.
89
+
90
+ def initialize *requirements
91
+ requirements = requirements.flatten
92
+ requirements.compact!
93
+ requirements.uniq!
94
+
95
+ requirements << ">= 0" if requirements.empty?
96
+ @none = (requirements == ">= 0")
97
+ @requirements = requirements.map! { |r| self.class.parse r }
98
+ end
99
+
100
+ def none?
101
+ @none ||= (to_s == ">= 0")
102
+ end
103
+
104
+ def as_list # :nodoc:
105
+ requirements.map { |op, version| "#{op} #{version}" }
106
+ end
107
+
108
+ def hash # :nodoc:
109
+ requirements.hash
110
+ end
111
+
112
+ def marshal_dump # :nodoc:
113
+ [@requirements]
114
+ end
115
+
116
+ def marshal_load array # :nodoc:
117
+ @requirements = array[0]
118
+ end
119
+
120
+ def prerelease?
121
+ requirements.any? { |r| r.last.prerelease? }
122
+ end
123
+
124
+ def pretty_print q # :nodoc:
125
+ q.group 1, 'LibGems::Requirement.new(', ')' do
126
+ q.pp as_list
127
+ end
128
+ end
129
+
130
+ ##
131
+ # True if +version+ satisfies this Requirement.
132
+
133
+ def satisfied_by? version
134
+ requirements.all? { |op, rv| OPS[op].call version, rv }
135
+ end
136
+
137
+ def to_s # :nodoc:
138
+ as_list.join ", "
139
+ end
140
+
141
+ def <=> other # :nodoc:
142
+ to_s <=> other.to_s
143
+ end
144
+ end
145
+
146
+ # :stopdoc:
147
+ # LibGems::Version::Requirement is used in a lot of old YAML specs. It's aliased
148
+ # here for backwards compatibility. I'd like to remove this, maybe in SlimGems
149
+ # 2.0.
150
+
151
+ ::LibGems::Version::Requirement = ::LibGems::Requirement
152
+ # :startdoc:
153
+
@@ -0,0 +1,814 @@
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 'libgems'
8
+ require 'libgems/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
+ # LibGems::Security implements cryptographic signatures in SlimGems. 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. SlimGems 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
+ # SlimGems 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
+ # SlimGems 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 SlimGems 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 SlimGems should accept your signed package and begin installing.
130
+ # While you're waiting for SlimGems 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
+ # SlimGems 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 SlimGems signing certificate, which is signed at the next
206
+ # RubyConf by the holder of the top-level SlimGems 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 SlimGems.
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 SlimGems 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
+ # == Manually verifying signatures
269
+ #
270
+ # In case you don't trust SlimGems you can verify gem signatures manually:
271
+ #
272
+ # 1. Fetch and unpack the gem
273
+ #
274
+ # gem fetch some_signed_gem
275
+ # tar -xf some_signed_gem-1.0.gem
276
+ #
277
+ # 2. Grab the public key from the gemspec
278
+ #
279
+ # gem spec some_signed_gem-1.0.gem cert_chain | \
280
+ # ruby -pe 'sub(/^ +/, "")' > public_key.crt
281
+ #
282
+ # 3. Generate a SHA1 hash of the data.tar.gz
283
+ #
284
+ # openssl dgst -sha1 < data.tar.gz > my.hash
285
+ #
286
+ # 4. Verify the signature
287
+ #
288
+ # openssl rsautl -verify -inkey public_key.crt -certin \
289
+ # -in data.tar.gz.sig > verified.hash
290
+ #
291
+ # 5. Compare your hash to the verified hash
292
+ #
293
+ # diff -s verified.hash my.hash
294
+ #
295
+ # 6. Repeat 5 and 6 with metadata.gz
296
+ #
297
+ # == OpenSSL Reference
298
+ #
299
+ # The .pem files generated by --build and --sign are just basic OpenSSL PEM
300
+ # files. Here's a couple of useful commands for manipulating them:
301
+ #
302
+ # # convert a PEM format X509 certificate into DER format:
303
+ # # (note: Windows .cer files are X509 certificates in DER format)
304
+ # $ openssl x509 -in input.pem -outform der -out output.der
305
+ #
306
+ # # print out the certificate in a human-readable format:
307
+ # $ openssl x509 -in input.pem -noout -text
308
+ #
309
+ # And you can do the same thing with the private key file as well:
310
+ #
311
+ # # convert a PEM format RSA key into DER format:
312
+ # $ openssl rsa -in input_key.pem -outform der -out output_key.der
313
+ #
314
+ # # print out the key in a human readable format:
315
+ # $ openssl rsa -in input_key.pem -noout -text
316
+ #
317
+ # == Bugs/TODO
318
+ #
319
+ # * There's no way to define a system-wide trust list.
320
+ # * custom security policies (from a YAML file, etc)
321
+ # * Simple method to generate a signed certificate request
322
+ # * Support for OCSP, SCVP, CRLs, or some other form of cert
323
+ # status check (list is in order of preference)
324
+ # * Support for encrypted private keys
325
+ # * Some sort of semi-formal trust hierarchy (see long-winded explanation
326
+ # above)
327
+ # * Path discovery (for gem certificate chains that don't have a self-signed
328
+ # root) -- by the way, since we don't have this, THE ROOT OF THE CERTIFICATE
329
+ # CHAIN MUST BE SELF SIGNED if Policy#verify_root is true (and it is for the
330
+ # MediumSecurity and HighSecurity policies)
331
+ # * Better explanation of X509 naming (ie, we don't have to use email
332
+ # addresses)
333
+ # * Possible alternate signing mechanisms (eg, via PGP). this could be done
334
+ # pretty easily by adding a :signing_type attribute to the gemspec, then add
335
+ # the necessary support in other places
336
+ # * Honor AIA field (see note about OCSP above)
337
+ # * Maybe honor restriction extensions?
338
+ # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
339
+ # file, instead of an array embedded in the metadata. ideas?
340
+ # * Possibly embed signature and key algorithms into metadata (right now
341
+ # they're assumed to be the same as what's set in LibGems::Security::OPT)
342
+ #
343
+ # == About the Author
344
+ #
345
+ # Paul Duncan <pabs@pablotron.org>
346
+ # http://pablotron.org/
347
+
348
+ module LibGems::Security
349
+
350
+ class Exception < LibGems::Exception; end
351
+
352
+ #
353
+ # default options for most of the methods below
354
+ #
355
+ OPT = {
356
+ # private key options
357
+ :key_algo => LibGems::SSL::PKEY_RSA,
358
+ :key_size => 2048,
359
+
360
+ # public cert options
361
+ :cert_age => 365 * 24 * 3600, # 1 year
362
+ :dgst_algo => LibGems::SSL::DIGEST_SHA1,
363
+
364
+ # x509 certificate extensions
365
+ :cert_exts => {
366
+ 'basicConstraints' => 'CA:FALSE',
367
+ 'subjectKeyIdentifier' => 'hash',
368
+ 'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature',
369
+ },
370
+
371
+ # save the key and cert to a file in build_self_signed_cert()?
372
+ :save_key => true,
373
+ :save_cert => true,
374
+
375
+ # if you define either of these, then they'll be used instead of
376
+ # the output_fmt macro below
377
+ :save_key_path => nil,
378
+ :save_cert_path => nil,
379
+
380
+ # output name format for self-signed certs
381
+ :output_fmt => 'gem-%s.pem',
382
+ :munge_re => Regexp.new(/[^a-z0-9_.-]+/),
383
+
384
+ # output directory for trusted certificate checksums
385
+ :trust_dir => File::join(LibGems.user_home, '.gem', 'trust'),
386
+
387
+ # default permissions for trust directory and certs
388
+ :perms => {
389
+ :trust_dir => 0700,
390
+ :trusted_cert => 0600,
391
+ :signing_cert => 0600,
392
+ :signing_key => 0600,
393
+ },
394
+ }
395
+
396
+ #
397
+ # A LibGems::Security::Policy object encapsulates the settings for verifying
398
+ # signed gem files. This is the base class. You can either declare an
399
+ # instance of this or use one of the preset security policies below.
400
+ #
401
+ class Policy
402
+ attr_accessor :verify_data, :verify_signer, :verify_chain,
403
+ :verify_root, :only_trusted, :only_signed
404
+
405
+ #
406
+ # Create a new LibGems::Security::Policy object with the given mode and
407
+ # options.
408
+ #
409
+ def initialize(policy = {}, opt = {})
410
+ # set options
411
+ @opt = LibGems::Security::OPT.merge(opt)
412
+
413
+ # build policy
414
+ policy.each_pair do |key, val|
415
+ case key
416
+ when :verify_data then @verify_data = val
417
+ when :verify_signer then @verify_signer = val
418
+ when :verify_chain then @verify_chain = val
419
+ when :verify_root then @verify_root = val
420
+ when :only_trusted then @only_trusted = val
421
+ when :only_signed then @only_signed = val
422
+ end
423
+ end
424
+ end
425
+
426
+ #
427
+ # Get the path to the file for this cert.
428
+ #
429
+ def self.trusted_cert_path(cert, opt = {})
430
+ opt = LibGems::Security::OPT.merge(opt)
431
+
432
+ # get digest algorithm, calculate checksum of root.subject
433
+ algo = opt[:dgst_algo]
434
+ dgst = algo.hexdigest(cert.subject.to_s)
435
+
436
+ # build path to trusted cert file
437
+ name = "cert-#{dgst}.pem"
438
+
439
+ # join and return path components
440
+ File::join(opt[:trust_dir], name)
441
+ end
442
+
443
+ #
444
+ # Verify that the gem data with the given signature and signing chain
445
+ # matched this security policy at the specified time.
446
+ #
447
+ def verify_gem(signature, data, chain, time = Time.now)
448
+ LibGems.ensure_ssl_available
449
+ cert_class = OpenSSL::X509::Certificate
450
+ exc = LibGems::Security::Exception
451
+ chain ||= []
452
+
453
+ chain = chain.map{ |str| cert_class.new(str) }
454
+ signer, ch_len = chain[-1], chain.size
455
+
456
+ # make sure signature is valid
457
+ if @verify_data
458
+ # get digest algorithm (TODO: this should be configurable)
459
+ dgst = @opt[:dgst_algo]
460
+
461
+ # verify the data signature (this is the most important part, so don't
462
+ # screw it up :D)
463
+ v = signer.public_key.verify(dgst.new, signature, data)
464
+ raise exc, "Invalid LibGems Signature" unless v
465
+
466
+ # make sure the signer is valid
467
+ if @verify_signer
468
+ # make sure the signing cert is valid right now
469
+ v = signer.check_validity(nil, time)
470
+ raise exc, "Invalid Signature: #{v[:desc]}" unless v[:is_valid]
471
+ end
472
+ end
473
+
474
+ # make sure the certificate chain is valid
475
+ if @verify_chain
476
+ # iterate down over the chain and verify each certificate against it's
477
+ # issuer
478
+ (ch_len - 1).downto(1) do |i|
479
+ issuer, cert = chain[i - 1, 2]
480
+ v = cert.check_validity(issuer, time)
481
+ raise exc, "%s: cert = '%s', error = '%s'" % [
482
+ 'Invalid Signing Chain', cert.subject, v[:desc]
483
+ ] unless v[:is_valid]
484
+ end
485
+
486
+ # verify root of chain
487
+ if @verify_root
488
+ # make sure root is self-signed
489
+ root = chain[0]
490
+ raise exc, "%s: %s (subject = '%s', issuer = '%s')" % [
491
+ 'Invalid Signing Chain Root',
492
+ 'Subject does not match Issuer for LibGems Signing Chain',
493
+ root.subject.to_s,
494
+ root.issuer.to_s,
495
+ ] unless root.issuer.to_s == root.subject.to_s
496
+
497
+ # make sure root is valid
498
+ v = root.check_validity(root, time)
499
+ raise exc, "%s: cert = '%s', error = '%s'" % [
500
+ 'Invalid Signing Chain Root', root.subject, v[:desc]
501
+ ] unless v[:is_valid]
502
+
503
+ # verify that the chain root is trusted
504
+ if @only_trusted
505
+ # get digest algorithm, calculate checksum of root.subject
506
+ algo = @opt[:dgst_algo]
507
+ path = LibGems::Security::Policy.trusted_cert_path(root, @opt)
508
+
509
+ # check to make sure trusted path exists
510
+ raise exc, "%s: cert = '%s', error = '%s'" % [
511
+ 'Untrusted Signing Chain Root',
512
+ root.subject.to_s,
513
+ "path \"#{path}\" does not exist",
514
+ ] unless File.exist?(path)
515
+
516
+ # load calculate digest from saved cert file
517
+ save_cert = OpenSSL::X509::Certificate.new(File.read(path))
518
+ save_dgst = algo.digest(save_cert.public_key.to_s)
519
+
520
+ # create digest of public key
521
+ pkey_str = root.public_key.to_s
522
+ cert_dgst = algo.digest(pkey_str)
523
+
524
+ # now compare the two digests, raise exception
525
+ # if they don't match
526
+ raise exc, "%s: %s (saved = '%s', root = '%s')" % [
527
+ 'Invalid Signing Chain Root',
528
+ "Saved checksum doesn't match root checksum",
529
+ save_dgst, cert_dgst,
530
+ ] unless save_dgst == cert_dgst
531
+ end
532
+ end
533
+
534
+ # return the signing chain
535
+ chain.map { |cert| cert.subject }
536
+ end
537
+ end
538
+ end
539
+
540
+ #
541
+ # No security policy: all package signature checks are disabled.
542
+ #
543
+ NoSecurity = Policy.new(
544
+ :verify_data => false,
545
+ :verify_signer => false,
546
+ :verify_chain => false,
547
+ :verify_root => false,
548
+ :only_trusted => false,
549
+ :only_signed => false
550
+ )
551
+
552
+ #
553
+ # AlmostNo security policy: only verify that the signing certificate is the
554
+ # one that actually signed the data. Make no attempt to verify the signing
555
+ # certificate chain.
556
+ #
557
+ # This policy is basically useless. better than nothing, but can still be
558
+ # easily spoofed, and is not recommended.
559
+ #
560
+ AlmostNoSecurity = Policy.new(
561
+ :verify_data => true,
562
+ :verify_signer => false,
563
+ :verify_chain => false,
564
+ :verify_root => false,
565
+ :only_trusted => false,
566
+ :only_signed => false
567
+ )
568
+
569
+ #
570
+ # Low security policy: only verify that the signing certificate is actually
571
+ # the gem signer, and that the signing certificate is valid.
572
+ #
573
+ # This policy is better than nothing, but can still be easily spoofed, and
574
+ # is not recommended.
575
+ #
576
+ LowSecurity = Policy.new(
577
+ :verify_data => true,
578
+ :verify_signer => true,
579
+ :verify_chain => false,
580
+ :verify_root => false,
581
+ :only_trusted => false,
582
+ :only_signed => false
583
+ )
584
+
585
+ #
586
+ # Medium security policy: verify the signing certificate, verify the signing
587
+ # certificate chain all the way to the root certificate, and only trust root
588
+ # certificates that we have explicitly allowed trust for.
589
+ #
590
+ # This security policy is reasonable, but it allows unsigned packages, so a
591
+ # malicious person could simply delete the package signature and pass the
592
+ # gem off as unsigned.
593
+ #
594
+ MediumSecurity = Policy.new(
595
+ :verify_data => true,
596
+ :verify_signer => true,
597
+ :verify_chain => true,
598
+ :verify_root => true,
599
+ :only_trusted => true,
600
+ :only_signed => false
601
+ )
602
+
603
+ #
604
+ # High security policy: only allow signed gems to be installed, verify the
605
+ # signing certificate, verify the signing certificate chain all the way to
606
+ # the root certificate, and only trust root certificates that we have
607
+ # explicitly allowed trust for.
608
+ #
609
+ # This security policy is significantly more difficult to bypass, and offers
610
+ # a reasonable guarantee that the contents of the gem have not been altered.
611
+ #
612
+ HighSecurity = Policy.new(
613
+ :verify_data => true,
614
+ :verify_signer => true,
615
+ :verify_chain => true,
616
+ :verify_root => true,
617
+ :only_trusted => true,
618
+ :only_signed => true
619
+ )
620
+
621
+ #
622
+ # Hash of configured security policies
623
+ #
624
+ Policies = {
625
+ 'NoSecurity' => NoSecurity,
626
+ 'AlmostNoSecurity' => AlmostNoSecurity,
627
+ 'LowSecurity' => LowSecurity,
628
+ 'MediumSecurity' => MediumSecurity,
629
+ 'HighSecurity' => HighSecurity,
630
+ }
631
+
632
+ #
633
+ # Sign the cert cert with @signing_key and @signing_cert, using the digest
634
+ # algorithm opt[:dgst_algo]. Returns the newly signed certificate.
635
+ #
636
+ def self.sign_cert(cert, signing_key, signing_cert, opt = {})
637
+ opt = OPT.merge(opt)
638
+
639
+ # set up issuer information
640
+ cert.issuer = signing_cert.subject
641
+ cert.sign(signing_key, opt[:dgst_algo].new)
642
+
643
+ cert
644
+ end
645
+
646
+ #
647
+ # Make sure the trust directory exists. If it does exist, make sure it's
648
+ # actually a directory. If not, then create it with the appropriate
649
+ # permissions.
650
+ #
651
+ def self.verify_trust_dir(path, perms)
652
+ # if the directory exists, then make sure it is in fact a directory. if
653
+ # it doesn't exist, then create it with the appropriate permissions
654
+ if File.exist?(path)
655
+ # verify that the trust directory is actually a directory
656
+ unless File.directory?(path)
657
+ err = "trust directory #{path} isn't a directory"
658
+ raise LibGems::Security::Exception, err
659
+ end
660
+ else
661
+ # trust directory doesn't exist, so create it with permissions
662
+ FileUtils.mkdir_p(path)
663
+ FileUtils.chmod(perms, path)
664
+ end
665
+ end
666
+
667
+ #
668
+ # Build a certificate from the given DN and private key.
669
+ #
670
+ def self.build_cert(name, key, opt = {})
671
+ LibGems.ensure_ssl_available
672
+ opt = OPT.merge(opt)
673
+
674
+ # create new cert
675
+ ret = OpenSSL::X509::Certificate.new
676
+
677
+ # populate cert attributes
678
+ ret.version = 2
679
+ ret.serial = 0
680
+ ret.public_key = key.public_key
681
+ ret.not_before = Time.now
682
+ ret.not_after = Time.now + opt[:cert_age]
683
+ ret.subject = name
684
+
685
+ # add certificate extensions
686
+ ef = OpenSSL::X509::ExtensionFactory.new(nil, ret)
687
+ ret.extensions = opt[:cert_exts].map { |k, v| ef.create_extension(k, v) }
688
+
689
+ # sign cert
690
+ i_key, i_cert = opt[:issuer_key] || key, opt[:issuer_cert] || ret
691
+ ret = sign_cert(ret, i_key, i_cert, opt)
692
+
693
+ # return cert
694
+ ret
695
+ end
696
+
697
+ #
698
+ # Build a self-signed certificate for the given email address.
699
+ #
700
+ def self.build_self_signed_cert(email_addr, opt = {})
701
+ LibGems.ensure_ssl_available
702
+ opt = OPT.merge(opt)
703
+ path = { :key => nil, :cert => nil }
704
+
705
+ # split email address up
706
+ cn, dcs = email_addr.split('@')
707
+ dcs = dcs.split('.')
708
+
709
+ # munge email CN and DCs
710
+ cn = cn.gsub(opt[:munge_re], '_')
711
+ dcs = dcs.map { |dc| dc.gsub(opt[:munge_re], '_') }
712
+
713
+ # create DN
714
+ name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/')
715
+ name = OpenSSL::X509::Name::parse(name)
716
+
717
+ # build private key
718
+ key = opt[:key_algo].new(opt[:key_size])
719
+
720
+ # method name pretty much says it all :)
721
+ verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
722
+
723
+ # if we're saving the key, then write it out
724
+ if opt[:save_key]
725
+ path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key')
726
+ File.open(path[:key], 'wb') do |file|
727
+ file.chmod(opt[:perms][:signing_key])
728
+ file.write(key.to_pem)
729
+ end
730
+ end
731
+
732
+ # build self-signed public cert from key
733
+ cert = build_cert(name, key, opt)
734
+
735
+ # if we're saving the cert, then write it out
736
+ if opt[:save_cert]
737
+ path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert')
738
+ File.open(path[:cert], 'wb') do |file|
739
+ file.chmod(opt[:perms][:signing_cert])
740
+ file.write(cert.to_pem)
741
+ end
742
+ end
743
+
744
+ # return key, cert, and paths (if applicable)
745
+ { :key => key, :cert => cert,
746
+ :key_path => path[:key], :cert_path => path[:cert] }
747
+ end
748
+
749
+ #
750
+ # Add certificate to trusted cert list.
751
+ #
752
+ # Note: At the moment these are stored in OPT[:trust_dir], although that
753
+ # directory may change in the future.
754
+ #
755
+ def self.add_trusted_cert(cert, opt = {})
756
+ opt = OPT.merge(opt)
757
+
758
+ # get destination path
759
+ path = LibGems::Security::Policy.trusted_cert_path(cert, opt)
760
+
761
+ # verify trust directory (can't write to nowhere, you know)
762
+ verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
763
+
764
+ # write cert to output file
765
+ File.open(path, 'wb') do |file|
766
+ file.chmod(opt[:perms][:trusted_cert])
767
+ file.write(cert.to_pem)
768
+ end
769
+
770
+ # return nil
771
+ nil
772
+ end
773
+
774
+ #
775
+ # Basic OpenSSL-based package signing class.
776
+ #
777
+ class Signer
778
+ attr_accessor :key, :cert_chain
779
+
780
+ def initialize(key, cert_chain)
781
+ LibGems.ensure_ssl_available
782
+ @algo = LibGems::Security::OPT[:dgst_algo]
783
+ @key, @cert_chain = key, cert_chain
784
+
785
+ # check key, if it's a file, and if it's key, leave it alone
786
+ if @key && !@key.kind_of?(OpenSSL::PKey::PKey)
787
+ @key = OpenSSL::PKey::RSA.new(File.read(@key))
788
+ end
789
+
790
+ # check cert chain, if it's a file, load it, if it's cert data, convert
791
+ # it into a cert object, and if it's a cert object, leave it alone
792
+ if @cert_chain
793
+ @cert_chain = @cert_chain.map do |cert|
794
+ # check cert, if it's a file, load it, if it's cert data, convert it
795
+ # into a cert object, and if it's a cert object, leave it alone
796
+ if cert && !cert.kind_of?(OpenSSL::X509::Certificate)
797
+ cert = File.read(cert) if File::exist?(cert)
798
+ cert = OpenSSL::X509::Certificate.new(cert)
799
+ end
800
+ cert
801
+ end
802
+ end
803
+ end
804
+
805
+ #
806
+ # Sign data with given digest algorithm
807
+ #
808
+ def sign(data)
809
+ @key.sign(@algo.new, data)
810
+ end
811
+
812
+ end
813
+ end
814
+