rubygems-update 1.8.30 → 2.0.0.preview2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rubygems-update might be problematic. Click here for more details.

Files changed (241) hide show
  1. checksums.yaml +6 -6
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +3 -0
  4. data/.autotest +6 -3
  5. data/History.txt +137 -63
  6. data/LICENSE.txt +1 -5
  7. data/Manifest.txt +69 -32
  8. data/README.rdoc +11 -9
  9. data/Rakefile +24 -38
  10. data/bin/gem +0 -9
  11. data/bin/update_rubygems +1 -0
  12. data/lib/rubygems.rb +193 -405
  13. data/lib/rubygems/available_set.rb +95 -0
  14. data/lib/rubygems/command.rb +88 -45
  15. data/lib/rubygems/command_manager.rb +67 -40
  16. data/lib/rubygems/commands/build_command.rb +5 -23
  17. data/lib/rubygems/commands/cert_command.rb +199 -57
  18. data/lib/rubygems/commands/check_command.rb +14 -39
  19. data/lib/rubygems/commands/cleanup_command.rb +9 -1
  20. data/lib/rubygems/commands/contents_command.rb +30 -12
  21. data/lib/rubygems/commands/dependency_command.rb +3 -8
  22. data/lib/rubygems/commands/environment_command.rb +13 -8
  23. data/lib/rubygems/commands/fetch_command.rb +3 -16
  24. data/lib/rubygems/commands/generate_index_command.rb +7 -47
  25. data/lib/rubygems/commands/help_command.rb +1 -1
  26. data/lib/rubygems/commands/install_command.rb +69 -36
  27. data/lib/rubygems/commands/list_command.rb +6 -4
  28. data/lib/rubygems/commands/lock_command.rb +1 -1
  29. data/lib/rubygems/commands/mirror_command.rb +17 -0
  30. data/lib/rubygems/commands/outdated_command.rb +6 -3
  31. data/lib/rubygems/commands/owner_command.rb +13 -5
  32. data/lib/rubygems/commands/pristine_command.rb +19 -4
  33. data/lib/rubygems/commands/push_command.rb +12 -1
  34. data/lib/rubygems/commands/query_command.rb +43 -27
  35. data/lib/rubygems/commands/rdoc_command.rb +23 -28
  36. data/lib/rubygems/commands/search_command.rb +4 -18
  37. data/lib/rubygems/commands/server_command.rb +1 -1
  38. data/lib/rubygems/commands/setup_command.rb +124 -38
  39. data/lib/rubygems/commands/sources_command.rb +16 -16
  40. data/lib/rubygems/commands/specification_command.rb +11 -13
  41. data/lib/rubygems/commands/uninstall_command.rb +24 -7
  42. data/lib/rubygems/commands/unpack_command.rb +7 -3
  43. data/lib/rubygems/commands/update_command.rb +22 -36
  44. data/lib/rubygems/commands/yank_command.rb +98 -0
  45. data/lib/rubygems/compatibility.rb +51 -0
  46. data/lib/rubygems/config_file.rb +82 -54
  47. data/lib/rubygems/core_ext/kernel_gem.rb +53 -0
  48. data/lib/rubygems/core_ext/kernel_require.rb +119 -0
  49. data/lib/rubygems/defaults.rb +10 -21
  50. data/lib/rubygems/dependency.rb +61 -10
  51. data/lib/rubygems/dependency_installer.rb +157 -69
  52. data/lib/rubygems/dependency_list.rb +11 -19
  53. data/lib/rubygems/dependency_resolver.rb +562 -0
  54. data/lib/rubygems/deprecate.rb +40 -40
  55. data/lib/rubygems/errors.rb +77 -24
  56. data/lib/rubygems/exceptions.rb +25 -7
  57. data/lib/rubygems/ext/builder.rb +20 -23
  58. data/lib/rubygems/ext/configure_builder.rb +2 -2
  59. data/lib/rubygems/ext/ext_conf_builder.rb +5 -45
  60. data/lib/rubygems/ext/rake_builder.rb +2 -2
  61. data/lib/rubygems/gem_runner.rb +3 -16
  62. data/lib/rubygems/gemcutter_utilities.rb +22 -7
  63. data/lib/rubygems/indexer.rb +6 -159
  64. data/lib/rubygems/install_message.rb +12 -0
  65. data/lib/rubygems/install_update_options.rb +56 -18
  66. data/lib/rubygems/installer.rb +244 -134
  67. data/lib/rubygems/installer_test_case.rb +71 -19
  68. data/lib/rubygems/mock_gem_ui.rb +17 -0
  69. data/lib/rubygems/name_tuple.rb +110 -0
  70. data/lib/rubygems/package.rb +514 -43
  71. data/lib/rubygems/package/digest_io.rb +64 -0
  72. data/lib/rubygems/package/old.rb +147 -0
  73. data/lib/rubygems/package/tar_header.rb +18 -55
  74. data/lib/rubygems/package/tar_reader.rb +20 -3
  75. data/lib/rubygems/package/tar_writer.rb +63 -7
  76. data/lib/rubygems/package_task.rb +3 -4
  77. data/lib/rubygems/path_support.rb +14 -7
  78. data/lib/rubygems/platform.rb +19 -26
  79. data/lib/rubygems/rdoc.rb +316 -0
  80. data/lib/rubygems/remote_fetcher.rb +117 -54
  81. data/lib/rubygems/request_set.rb +182 -0
  82. data/lib/rubygems/requirement.rb +63 -26
  83. data/lib/rubygems/security.rb +295 -555
  84. data/lib/rubygems/security/policies.rb +115 -0
  85. data/lib/rubygems/security/policy.rb +227 -0
  86. data/lib/rubygems/security/signer.rb +136 -0
  87. data/lib/rubygems/security/trust_dir.rb +104 -0
  88. data/lib/rubygems/server.rb +45 -55
  89. data/lib/rubygems/source.rb +144 -0
  90. data/lib/rubygems/source_list.rb +87 -0
  91. data/lib/rubygems/source_local.rb +92 -0
  92. data/lib/rubygems/source_specific_file.rb +28 -0
  93. data/lib/rubygems/spec_fetcher.rb +116 -184
  94. data/lib/rubygems/specification.rb +731 -335
  95. data/lib/rubygems/ssl_certs/AddTrustExternalCARoot.pem +88 -30
  96. data/lib/rubygems/ssl_certs/Entrust_net-Secure-Server-Certification-Authority.pem +90 -0
  97. data/lib/rubygems/ssl_certs/VerisignClass3PublicPrimaryCertificationAuthority-G2.pem +57 -0
  98. data/lib/rubygems/syck_hack.rb +2 -0
  99. data/lib/rubygems/test_case.rb +199 -109
  100. data/lib/rubygems/test_utilities.rb +25 -5
  101. data/lib/rubygems/uninstaller.rb +62 -20
  102. data/lib/rubygems/user_interaction.rb +10 -0
  103. data/lib/rubygems/validator.rb +33 -40
  104. data/lib/rubygems/version.rb +19 -8
  105. data/setup.rb +8 -1
  106. data/test/rubygems/alternate_cert.pem +9 -0
  107. data/test/rubygems/alternate_cert_32.pem +9 -0
  108. data/test/rubygems/alternate_key.pem +9 -0
  109. data/test/rubygems/bad_rake.rb +1 -0
  110. data/test/rubygems/child_cert.pem +9 -0
  111. data/test/rubygems/child_cert_32.pem +9 -0
  112. data/test/rubygems/child_key.pem +9 -0
  113. data/test/rubygems/data/null-type.gemspec.rz +0 -0
  114. data/test/rubygems/expired_cert.pem +9 -0
  115. data/test/rubygems/future_cert.pem +9 -0
  116. data/test/rubygems/future_cert_32.pem +9 -0
  117. data/test/rubygems/good_rake.rb +1 -0
  118. data/test/rubygems/grandchild_cert.pem +9 -0
  119. data/test/rubygems/grandchild_cert_32.pem +9 -0
  120. data/test/rubygems/grandchild_key.pem +9 -0
  121. data/test/rubygems/invalid_issuer_cert.pem +9 -0
  122. data/test/rubygems/invalid_issuer_cert_32.pem +9 -0
  123. data/test/rubygems/invalid_key.pem +9 -0
  124. data/test/rubygems/invalid_signer_cert.pem +9 -0
  125. data/test/rubygems/invalid_signer_cert_32.pem +9 -0
  126. data/test/rubygems/invalidchild_cert.pem +9 -0
  127. data/test/rubygems/invalidchild_cert_32.pem +9 -0
  128. data/test/rubygems/invalidchild_key.pem +9 -0
  129. data/test/rubygems/plugin/exception/rubygems_plugin.rb +1 -1
  130. data/test/rubygems/plugin/standarderror/rubygems_plugin.rb +1 -1
  131. data/test/rubygems/private_key.pem +7 -25
  132. data/test/rubygems/public_cert.pem +8 -18
  133. data/test/rubygems/public_cert_32.pem +10 -0
  134. data/test/rubygems/public_key.pem +4 -0
  135. data/test/rubygems/rubygems/commands/crash_command.rb +1 -1
  136. data/test/rubygems/test_config.rb +4 -6
  137. data/test/rubygems/test_deprecate.rb +76 -0
  138. data/test/rubygems/test_gem.rb +318 -83
  139. data/test/rubygems/test_gem_available_set.rb +106 -0
  140. data/test/rubygems/test_gem_command.rb +10 -0
  141. data/test/rubygems/test_gem_command_manager.rb +55 -9
  142. data/test/rubygems/test_gem_commands_build_command.rb +11 -19
  143. data/test/rubygems/test_gem_commands_cert_command.rb +441 -42
  144. data/test/rubygems/test_gem_commands_cleanup_command.rb +29 -1
  145. data/test/rubygems/test_gem_commands_contents_command.rb +23 -0
  146. data/test/rubygems/test_gem_commands_dependency_command.rb +5 -0
  147. data/test/rubygems/test_gem_commands_fetch_command.rb +19 -20
  148. data/test/rubygems/test_gem_commands_generate_index_command.rb +2 -83
  149. data/test/rubygems/test_gem_commands_help_command.rb +2 -1
  150. data/test/rubygems/test_gem_commands_install_command.rb +647 -48
  151. data/test/rubygems/test_gem_commands_mirror.rb +32 -0
  152. data/test/rubygems/test_gem_commands_owner_command.rb +4 -8
  153. data/test/rubygems/test_gem_commands_pristine_command.rb +99 -4
  154. data/test/rubygems/test_gem_commands_push_command.rb +62 -8
  155. data/test/rubygems/test_gem_commands_query_command.rb +51 -0
  156. data/test/rubygems/test_gem_commands_search_command.rb +25 -0
  157. data/test/rubygems/test_gem_commands_setup_command.rb +45 -0
  158. data/test/rubygems/test_gem_commands_sources_command.rb +21 -6
  159. data/test/rubygems/test_gem_commands_specification_command.rb +33 -1
  160. data/test/rubygems/test_gem_commands_uninstall_command.rb +91 -31
  161. data/test/rubygems/test_gem_commands_unpack_command.rb +3 -3
  162. data/test/rubygems/test_gem_commands_update_command.rb +56 -38
  163. data/test/rubygems/test_gem_commands_which_command.rb +4 -4
  164. data/test/rubygems/test_gem_commands_yank_command.rb +97 -0
  165. data/test/rubygems/test_gem_config_file.rb +66 -21
  166. data/test/rubygems/test_gem_dependency.rb +46 -0
  167. data/test/rubygems/test_gem_dependency_installer.rb +228 -18
  168. data/test/rubygems/test_gem_dependency_list.rb +0 -9
  169. data/test/rubygems/test_gem_dependency_resolver.rb +327 -0
  170. data/test/rubygems/test_gem_ext_configure_builder.rb +4 -4
  171. data/test/rubygems/test_gem_ext_ext_conf_builder.rb +21 -49
  172. data/test/rubygems/test_gem_ext_rake_builder.rb +13 -13
  173. data/test/rubygems/test_gem_gem_runner.rb +27 -5
  174. data/test/rubygems/test_gem_gemcutter_utilities.rb +19 -0
  175. data/test/rubygems/test_gem_indexer.rb +14 -227
  176. data/test/rubygems/test_gem_install_update_options.rb +83 -3
  177. data/test/rubygems/test_gem_installer.rb +211 -236
  178. data/test/rubygems/test_gem_local_remote_options.rb +8 -2
  179. data/test/rubygems/test_gem_name_tuple.rb +15 -0
  180. data/test/rubygems/test_gem_package.rb +547 -0
  181. data/test/rubygems/test_gem_package_old.rb +37 -0
  182. data/test/rubygems/test_gem_package_tar_reader.rb +32 -0
  183. data/test/rubygems/test_gem_package_tar_writer.rb +84 -1
  184. data/test/rubygems/test_gem_path_support.rb +4 -30
  185. data/test/rubygems/test_gem_platform.rb +3 -6
  186. data/test/rubygems/test_gem_rdoc.rb +245 -0
  187. data/test/rubygems/test_gem_remote_fetcher.rb +51 -5
  188. data/test/rubygems/test_gem_request_set.rb +70 -0
  189. data/test/rubygems/test_gem_requirement.rb +53 -24
  190. data/test/rubygems/test_gem_security.rb +189 -43
  191. data/test/rubygems/test_gem_security_policy.rb +376 -0
  192. data/test/rubygems/test_gem_security_signer.rb +184 -0
  193. data/test/rubygems/test_gem_security_trust_dir.rb +94 -0
  194. data/test/rubygems/test_gem_server.rb +31 -36
  195. data/test/rubygems/test_gem_silent_ui.rb +2 -2
  196. data/test/rubygems/test_gem_source.rb +188 -0
  197. data/test/rubygems/test_gem_source_list.rb +87 -0
  198. data/test/rubygems/test_gem_source_local.rb +83 -0
  199. data/test/rubygems/test_gem_source_specific_file.rb +33 -0
  200. data/test/rubygems/test_gem_spec_fetcher.rb +91 -255
  201. data/test/rubygems/test_gem_specification.rb +293 -39
  202. data/test/rubygems/test_gem_uninstaller.rb +136 -13
  203. data/test/rubygems/test_gem_validator.rb +14 -41
  204. data/test/rubygems/test_gem_version.rb +15 -21
  205. data/test/rubygems/test_require.rb +193 -0
  206. data/test/rubygems/wrong_key_cert.pem +9 -0
  207. data/test/rubygems/wrong_key_cert_32.pem +9 -0
  208. metadata +171 -83
  209. metadata.gz.sig +1 -0
  210. data/CVE-2013-4287.txt +0 -36
  211. data/CVE-2013-4363.txt +0 -45
  212. data/ci_build.sh +0 -27
  213. data/cruise_config.rb +0 -32
  214. data/lib/rbconfig/datadir.rb +0 -13
  215. data/lib/rubygems/builder.rb +0 -99
  216. data/lib/rubygems/custom_require.rb +0 -69
  217. data/lib/rubygems/doc_manager.rb +0 -243
  218. data/lib/rubygems/format.rb +0 -82
  219. data/lib/rubygems/gem_openssl.rb +0 -90
  220. data/lib/rubygems/gem_path_searcher.rb +0 -172
  221. data/lib/rubygems/old_format.rb +0 -153
  222. data/lib/rubygems/package/f_sync_dir.rb +0 -23
  223. data/lib/rubygems/package/tar_input.rb +0 -234
  224. data/lib/rubygems/package/tar_output.rb +0 -146
  225. data/lib/rubygems/require_paths_builder.rb +0 -18
  226. data/lib/rubygems/source_index.rb +0 -406
  227. data/lib/rubygems/ssl_certs/AddTrustExternalCARoot-2048.pem +0 -25
  228. data/lib/rubygems/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +0 -14
  229. data/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem +0 -23
  230. data/lib/rubygems/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +0 -28
  231. data/lib/rubygems/ssl_certs/GeoTrustGlobalCA.pem +0 -20
  232. data/test/rubygems/test_bundled_ca.rb +0 -59
  233. data/test/rubygems/test_gem_builder.rb +0 -44
  234. data/test/rubygems/test_gem_doc_manager.rb +0 -32
  235. data/test/rubygems/test_gem_ext_builder.rb +0 -58
  236. data/test/rubygems/test_gem_format.rb +0 -88
  237. data/test/rubygems/test_gem_gem_path_searcher.rb +0 -94
  238. data/test/rubygems/test_gem_package_tar_input.rb +0 -129
  239. data/test/rubygems/test_gem_package_tar_output.rb +0 -101
  240. data/test/rubygems/test_gem_source_index.rb +0 -250
  241. data/util/update_bundled_ca_certificates.rb +0 -103
@@ -0,0 +1,182 @@
1
+ require 'rubygems'
2
+ require 'rubygems/dependency'
3
+ require 'rubygems/dependency_resolver'
4
+ require 'rubygems/dependency_list'
5
+ require 'rubygems/installer'
6
+ require 'tsort'
7
+
8
+ module Gem
9
+ class RequestSet
10
+
11
+ include TSort
12
+
13
+ def initialize(*deps)
14
+ @dependencies = deps
15
+
16
+ yield self if block_given?
17
+ end
18
+
19
+ attr_reader :dependencies
20
+
21
+ # Declare that a gem of name +name+ with +reqs+ requirements
22
+ # is needed.
23
+ #
24
+ def gem(name, *reqs)
25
+ @dependencies << Gem::Dependency.new(name, reqs)
26
+ end
27
+
28
+ # Add +deps+ Gem::Depedency objects to the set.
29
+ #
30
+ def import(deps)
31
+ @dependencies += deps
32
+ end
33
+
34
+ # Resolve the requested dependencies and return an Array of
35
+ # Specification objects to be activated.
36
+ #
37
+ def resolve(set=nil)
38
+ r = Gem::DependencyResolver.new(@dependencies, set)
39
+ @requests = r.resolve
40
+ end
41
+
42
+ # Resolve the requested dependencies against the gems
43
+ # available via Gem.path and return an Array of Specification
44
+ # objects to be activated.
45
+ #
46
+ def resolve_current
47
+ resolve DependencyResolver::CurrentSet.new
48
+ end
49
+
50
+ # Load a dependency management file.
51
+ #
52
+ def load_gemdeps(path)
53
+ gf = GemDepedencyAPI.new(self, path)
54
+ gf.load
55
+ end
56
+
57
+ def specs
58
+ @specs ||= @requests.map { |r| r.full_spec }
59
+ end
60
+
61
+ def tsort_each_node(&block)
62
+ @requests.each(&block)
63
+ end
64
+
65
+ def tsort_each_child(node)
66
+ node.spec.dependencies.each do |dep|
67
+ next if dep.type == :development
68
+
69
+ match = @requests.find { |r| dep.match? r.spec.name, r.spec.version }
70
+ if match
71
+ begin
72
+ yield match
73
+ rescue TSort::Cyclic
74
+ end
75
+ else
76
+ raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}"
77
+ end
78
+ end
79
+ end
80
+
81
+ def sorted_requests
82
+ @sorted ||= strongly_connected_components.flatten
83
+ end
84
+
85
+ def specs_in(dir)
86
+ Dir["#{dir}/specifications/*.gemspec"].map do |g|
87
+ Gem::Specification.load g
88
+ end
89
+ end
90
+
91
+ def install_into(dir, force=true, &b)
92
+ existing = force ? [] : specs_in(dir)
93
+
94
+ dir = File.expand_path dir
95
+
96
+ installed = []
97
+
98
+ sorted_requests.each do |req|
99
+ if existing.find { |s| s.full_name == req.spec.full_name }
100
+ b.call req, nil if b
101
+ next
102
+ end
103
+
104
+ path = req.download(dir)
105
+
106
+ inst = Gem::Installer.new path, :install_dir => dir,
107
+ :only_install_dir => true
108
+
109
+ b.call req, inst if b
110
+
111
+ inst.install
112
+
113
+ installed << req
114
+ end
115
+
116
+ installed
117
+ end
118
+
119
+ def install(options, &b)
120
+ if dir = options[:install_dir]
121
+ return install_into(dir, false, &b)
122
+ end
123
+
124
+ cache_dir = options[:cache_dir] || Gem.dir
125
+
126
+ specs = []
127
+
128
+ sorted_requests.each do |req|
129
+ if req.installed?
130
+ b.call req, nil if b
131
+ next
132
+ end
133
+
134
+ path = req.download cache_dir
135
+
136
+ inst = Gem::Installer.new path, options
137
+
138
+ b.call req, inst if b
139
+
140
+ specs << inst.install
141
+ end
142
+
143
+ specs
144
+ end
145
+
146
+ # A semi-compatible DSL for Bundler's Gemfile format
147
+ #
148
+ class GemDepedencyAPI
149
+ def initialize(set, path)
150
+ @set = set
151
+ @path = path
152
+ end
153
+
154
+ def load
155
+ instance_eval File.read(@path).untaint, @path, 1
156
+ end
157
+
158
+ # DSL
159
+
160
+ def source(url)
161
+ end
162
+
163
+ def gem(name, *reqs)
164
+ # Ignore the opts for now.
165
+ reqs.pop if reqs.last.kind_of?(Hash)
166
+
167
+ @set.gem name, *reqs
168
+ end
169
+
170
+ def platform(what)
171
+ if what == :ruby
172
+ yield
173
+ end
174
+ end
175
+
176
+ alias_method :platforms, :platform
177
+
178
+ def group(*what)
179
+ end
180
+ end
181
+ end
182
+ end
@@ -1,5 +1,3 @@
1
- require "rubygems/version"
2
-
3
1
  ##
4
2
  # A Requirement is a set of one or more version restrictions. It supports a
5
3
  # few (<tt>=, !=, >, <, >=, <=, ~></tt>) different restriction operators.
@@ -13,21 +11,28 @@ require "rubygems/version"
13
11
  require "rubygems/version"
14
12
  require "rubygems/deprecate"
15
13
 
16
- class Gem::Requirement
17
- include Comparable
14
+ # If we're being loaded after yaml was already required, then
15
+ # load our yaml + workarounds now.
16
+ Gem.load_yaml if defined? ::YAML
18
17
 
18
+ class Gem::Requirement
19
19
  OPS = { #:nodoc:
20
20
  "=" => lambda { |v, r| v == r },
21
21
  "!=" => lambda { |v, r| v != r },
22
- ">" => lambda { |v, r| v > r },
23
- "<" => lambda { |v, r| v < r },
22
+ ">" => lambda { |v, r| v > r },
23
+ "<" => lambda { |v, r| v < r },
24
24
  ">=" => lambda { |v, r| v >= r },
25
25
  "<=" => lambda { |v, r| v <= r },
26
26
  "~>" => lambda { |v, r| v >= r && v.release < r.bump }
27
27
  }
28
28
 
29
29
  quoted = OPS.keys.map { |k| Regexp.quote k }.join "|"
30
- PATTERN = /\A\s*(#{quoted})?\s*(#{Gem::Version::VERSION_PATTERN})\s*\z/
30
+ PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*"
31
+ PATTERN = /\A#{PATTERN_RAW}\z/
32
+
33
+ DefaultRequirement = [">=", Gem::Version.new(0)]
34
+
35
+ class BadRequirementError < ArgumentError; end
31
36
 
32
37
  ##
33
38
  # Factory method to create a Gem::Requirement object. Input may be
@@ -36,6 +41,9 @@ class Gem::Requirement
36
41
  # If the input is "weird", the default version requirement is
37
42
  # returned.
38
43
 
44
+ # REFACTOR: There's no reason that this can't be unified with .new.
45
+ # .new is the standard Ruby factory method.
46
+
39
47
  def self.create input
40
48
  case input
41
49
  when Gem::Requirement then
@@ -53,10 +61,6 @@ class Gem::Requirement
53
61
 
54
62
  ##
55
63
  # A default "version requirement" can surely _only_ be '>= 0'.
56
- #--
57
- # This comment once said:
58
- #
59
- # "A default "version requirement" can surely _only_ be '> 0'."
60
64
 
61
65
  def self.default
62
66
  new '>= 0'
@@ -74,14 +78,23 @@ class Gem::Requirement
74
78
  # parse("1.0") # => ["=", "1.0"]
75
79
  # parse(Gem::Version.new("1.0")) # => ["=, "1.0"]
76
80
 
81
+ # REFACTOR: Little two element arrays like this have no real semantic
82
+ # value. I'd love to see something like this:
83
+ # Constraint = Struct.new(:operator, :version); (or similar)
84
+ # and have a Requirement be a list of Constraints.
85
+
77
86
  def self.parse obj
78
87
  return ["=", obj] if Gem::Version === obj
79
88
 
80
89
  unless PATTERN =~ obj.to_s
81
- raise ArgumentError, "Illformed requirement [#{obj.inspect}]"
90
+ raise BadRequirementError, "Illformed requirement [#{obj.inspect}]"
82
91
  end
83
92
 
84
- [$1 || "=", Gem::Version.new($2)]
93
+ if $1 == ">=" && $2 == "0"
94
+ DefaultRequirement
95
+ else
96
+ [$1 || "=", Gem::Version.new($2)]
97
+ end
85
98
  end
86
99
 
87
100
  ##
@@ -101,13 +114,23 @@ class Gem::Requirement
101
114
  requirements.compact!
102
115
  requirements.uniq!
103
116
 
104
- requirements << ">= 0" if requirements.empty?
105
- @none = (requirements == ">= 0")
106
- @requirements = requirements.map! { |r| self.class.parse r }
117
+ if requirements.empty?
118
+ @requirements = [DefaultRequirement]
119
+ else
120
+ @requirements = requirements.map! { |r| self.class.parse r }
121
+ end
107
122
  end
108
123
 
124
+ ##
125
+ # true if this gem has no requirements.
126
+
127
+ # FIX: maybe this should be using #default ?
109
128
  def none?
110
- @none ||= (to_s == ">= 0")
129
+ if @requirements.size == 1
130
+ @requirements[0] == DefaultRequirement
131
+ else
132
+ false
133
+ end
111
134
  end
112
135
 
113
136
  def as_list # :nodoc:
@@ -143,6 +166,18 @@ class Gem::Requirement
143
166
  yaml_initialize coder.tag, coder.map
144
167
  end
145
168
 
169
+ def to_yaml_properties
170
+ ["@requirements"]
171
+ end
172
+
173
+ def encode_with(coder)
174
+ coder.add 'requirements', @requirements
175
+ end
176
+
177
+ ##
178
+ # A requirement is a prerelease if any of the versions inside of it
179
+ # are prereleases
180
+
146
181
  def prerelease?
147
182
  requirements.any? { |r| r.last.prerelease? }
148
183
  end
@@ -157,6 +192,8 @@ class Gem::Requirement
157
192
  # True if +version+ satisfies this Requirement.
158
193
 
159
194
  def satisfied_by? version
195
+ raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless
196
+ Gem::Version === version
160
197
  # #28965: syck has a bug with unquoted '=' YAML.loading as YAML::DefaultKey
161
198
  requirements.all? { |op, rv| (OPS[op] || OPS["="]).call version, rv }
162
199
  end
@@ -177,12 +214,14 @@ class Gem::Requirement
177
214
  as_list.join ", "
178
215
  end
179
216
 
180
- def <=> other # :nodoc:
181
- to_s <=> other.to_s
217
+ # DOC: this should probably be :nodoc'd
218
+ def == other
219
+ Gem::Requirement === other and to_s == other.to_s
182
220
  end
183
221
 
184
222
  private
185
223
 
224
+ # DOC: this should probably be :nodoc'd
186
225
  def fix_syck_default_key_in_requirements
187
226
  Gem.load_yaml
188
227
 
@@ -195,11 +234,9 @@ class Gem::Requirement
195
234
  end
196
235
  end
197
236
 
198
- # :stopdoc:
199
- # Gem::Version::Requirement is used in a lot of old YAML specs. It's aliased
200
- # here for backwards compatibility. I'd like to remove this, maybe in RubyGems
201
- # 2.0.
202
-
203
- ::Gem::Version::Requirement = ::Gem::Requirement
204
- # :startdoc:
237
+ # This is needed for compatibility with older yaml
238
+ # gemspecs.
205
239
 
240
+ class Gem::Version
241
+ Requirement = Gem::Requirement
242
+ end
@@ -5,80 +5,89 @@
5
5
  #++
6
6
 
7
7
  require 'rubygems/exceptions'
8
- require 'rubygems/gem_openssl'
8
+ require 'openssl'
9
9
  require 'fileutils'
10
10
 
11
+ ##
12
+ # = Signing gems
11
13
  #
12
- # = Signed Gems README
13
- #
14
- # == Table of Contents
15
- # * Overview
16
- # * Walkthrough
17
- # * Command-Line Options
18
- # * OpenSSL Reference
19
- # * Bugs/TODO
20
- # * About the Author
21
- #
22
- # == Overview
23
- #
24
- # Gem::Security implements cryptographic signatures in RubyGems. The section
14
+ # The Gem::Security implements cryptographic signatures for gems. The section
25
15
  # below is a step-by-step guide to using signed gems and generating your own.
26
16
  #
27
17
  # == Walkthrough
28
18
  #
19
+ # === Building your certificate
20
+ #
29
21
  # In order to start signing your gems, you'll need to build a private key and
30
22
  # a self-signed certificate. Here's how:
31
23
  #
32
- # # build a private key and certificate for gemmaster@example.com
33
- # $ gem cert --build gemmaster@example.com
24
+ # # build a private key and certificate for yourself:
25
+ # $ gem cert --build you@example.com
34
26
  #
35
- # This could take anywhere from 5 seconds to 10 minutes, depending on the
36
- # speed of your computer (public key algorithms aren't exactly the speediest
37
- # crypto algorithms in the world). When it's finished, you'll see the files
38
- # "gem-private_key.pem" and "gem-public_cert.pem" in the current directory.
27
+ # This could take anywhere from a few seconds to a minute or two, depending on
28
+ # the speed of your computer (public key algorithms aren't exactly the
29
+ # speediest crypto algorithms in the world). When it's finished, you'll see
30
+ # the files "gem-private_key.pem" and "gem-public_cert.pem" in the current
31
+ # directory.
39
32
  #
40
- # First things first: take the "gem-private_key.pem" file and move it
41
- # somewhere private, preferably a directory only you have access to, a floppy
42
- # (yuck!), a CD-ROM, or something comparably secure. Keep your private key
43
- # hidden; if it's compromised, someone can sign packages as you (note: PKI has
44
- # ways of mitigating the risk of stolen keys; more on that later).
33
+ # First things first: Move both files to ~/.gem if you don't already have a
34
+ # key and certificate in that directory. Ensure the file permissions make the
35
+ # key unreadable by others (by default the file is saved securely).
45
36
  #
46
- # Now, let's sign an existing gem. I'll be using my Imlib2-Ruby bindings, but
47
- # you can use whatever gem you'd like. Open up your existing gemspec file and
48
- # add the following lines:
37
+ # Keep your private key hidden; if it's compromised, someone can sign packages
38
+ # as you (note: PKI has ways of mitigating the risk of stolen keys; more on
39
+ # that later).
49
40
  #
50
- # # signing key and certificate chain
51
- # s.signing_key = '/mnt/floppy/gem-private_key.pem'
52
- # s.cert_chain = ['gem-public_cert.pem']
41
+ # === Signing Gems
53
42
  #
54
- # (Be sure to replace "/mnt/floppy" with the ultra-secret path to your private
55
- # key).
43
+ # In RubyGems 2 and newer there is no extra work to sign a gem. RubyGems will
44
+ # automatically find your key and certificate in your home directory and use
45
+ # them to sign newly packaged gems.
56
46
  #
57
- # After that, go ahead and build your gem as usual. Congratulations, you've
58
- # just built your first signed gem! If you peek inside your gem file, you'll
59
- # see a couple of new files have been added:
47
+ # If your certificate is not self-signed (signed by a third party) RubyGems
48
+ # will attempt to load the certificate chain from the trusted certificates.
49
+ # Use <code>gem cert --add signing_cert.pem</code> to add your signers as
50
+ # trusted certificates. See below for further information on certificate
51
+ # chains.
60
52
  #
61
- # $ tar tf tar tf Imlib2-Ruby-0.5.0.gem
62
- # data.tar.gz
63
- # data.tar.gz.sig
53
+ # If you build your gem it will automatically be signed. If you peek inside
54
+ # your gem file, you'll see a couple of new files have been added:
55
+ #
56
+ # $ tar tf your-gem-1.0.gem
64
57
  # metadata.gz
65
- # metadata.gz.sig
58
+ # metadata.gz.sum
59
+ # metadata.gz.sig # metadata signature
60
+ # data.tar.gz
61
+ # data.tar.gz.sum
62
+ # data.tar.gz.sig # data signature
63
+ #
64
+ # === Manually signing gems
65
+ #
66
+ # If you wish to store your key in a separate secure location you'll need to
67
+ # set your gems up for signing by hand. To do this, set the
68
+ # <code>signing_key</code> and <code>cert_chain</code> in the gemspec before
69
+ # packaging your gem:
70
+ #
71
+ # s.signing_key = '/secure/path/to/gem-private_key.pem'
72
+ # s.cert_chain = %w[/secure/path/to/gem-public_cert.pem]
73
+ #
74
+ # When you package your gem with these options set RubyGems will automatically
75
+ # load your key and certificate from the secure paths.
76
+ #
77
+ # === Signed gems and security policies
66
78
  #
67
79
  # Now let's verify the signature. Go ahead and install the gem, but add the
68
- # following options: "-P HighSecurity", like this:
80
+ # following options: <code>-P HighSecurity</code>, like this:
69
81
  #
70
82
  # # install the gem with using the security policy "HighSecurity"
71
- # $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
83
+ # $ sudo gem install your.gem -P HighSecurity
72
84
  #
73
- # The -P option sets your security policy -- we'll talk about that in just a
74
- # minute. Eh, what's this?
85
+ # The <code>-P</code> option sets your security policy -- we'll talk about
86
+ # that in just a minute. Eh, what's this?
75
87
  #
76
- # Attempting local installation of 'Imlib2-Ruby-0.5.0.gem'
77
- # ERROR: Error installing gem Imlib2-Ruby-0.5.0.gem[.gem]: Couldn't
78
- # verify data signature: Untrusted Signing Chain Root: cert =
79
- # '/CN=gemmaster/DC=example/DC=com', error = 'path
80
- # "/root/.rubygems/trust/cert-15dbb43a6edf6a70a85d4e784e2e45312cff7030.pem"
81
- # does not exist'
88
+ # $ gem install -P HighSecurity your-gem-1.0.gem
89
+ # ERROR: While executing gem ... (Gem::Security::Exception)
90
+ # root cert /CN=you/DC=example is not trusted
82
91
  #
83
92
  # The culprit here is the security policy. RubyGems has several different
84
93
  # security policies. Let's take a short break and go over the security
@@ -111,46 +120,48 @@ require 'fileutils'
111
120
  # RubyGems will simply refuse to install the package. Oh well, maybe
112
121
  # he'll have better luck causing problems for CPAN users instead :).
113
122
  #
114
- # So, the reason RubyGems refused to install our shiny new signed gem was
115
- # because it was from an untrusted source. Well, my code is infallible
116
- # (hah!), so I'm going to add myself as a trusted source.
117
- #
118
- # Here's how:
123
+ # The reason RubyGems refused to install your shiny new signed gem was because
124
+ # it was from an untrusted source. Well, your code is infallible (naturally),
125
+ # so you need to add yourself as a trusted source:
119
126
  #
120
- # # add trusted certificate
121
- # gem cert --add gem-public_cert.pem
127
+ # # add trusted certificate
128
+ # gem cert --add ~/.gem/gem-public_cert.pem
122
129
  #
123
- # I've added my public certificate as a trusted source. Now I can install
124
- # packages signed my private key without any hassle. Let's try the install
125
- # command above again:
130
+ # You've now added your public certificate as a trusted source. Now you can
131
+ # install packages signed by your private key without any hassle. Let's try
132
+ # the install command above again:
126
133
  #
127
134
  # # install the gem with using the HighSecurity policy (and this time
128
135
  # # without any shenanigans)
129
- # $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
136
+ # $ gem install -P HighSecurity your-gem-1.0.gem
137
+ # Successfully installed your-gem-1.0
138
+ # 1 gem installed
130
139
  #
131
- # This time RubyGems should accept your signed package and begin installing.
132
- # While you're waiting for RubyGems to work it's magic, have a look at some of
133
- # the other security commands:
140
+ # This time RubyGems will accept your signed package and begin installing.
134
141
  #
135
- # Usage: gem cert [options]
142
+ # While you're waiting for RubyGems to work it's magic, have a look at some of
143
+ # the other security commands by running <code>gem help cert</code>:
136
144
  #
137
145
  # Options:
138
- # -a, --add CERT Add a trusted certificate.
139
- # -l, --list List trusted certificates.
140
- # -r, --remove STRING Remove trusted certificates containing STRING.
141
- # -b, --build EMAIL_ADDR Build private key and self-signed certificate
142
- # for EMAIL_ADDR.
143
- # -C, --certificate CERT Certificate for --sign command.
144
- # -K, --private-key KEY Private key for --sign command.
145
- # -s, --sign NEWCERT Sign a certificate with my key and certificate.
146
- #
147
- # (By the way, you can pull up this list any time you'd like by typing "gem
148
- # cert --help")
149
- #
150
- # Hmm. We've already covered the "--build" option, and the "--add", "--list",
151
- # and "--remove" commands seem fairly straightforward; they allow you to add,
152
- # list, and remove the certificates in your trusted certificate list. But
153
- # what's with this "--sign" option?
146
+ # -a, --add CERT Add a trusted certificate.
147
+ # -l, --list [FILTER] List trusted certificates where the
148
+ # subject contains FILTER
149
+ # -r, --remove FILTER Remove trusted certificates where the
150
+ # subject contains FILTER
151
+ # -b, --build EMAIL_ADDR Build private key and self-signed
152
+ # certificate for EMAIL_ADDR
153
+ # -C, --certificate CERT Signing certificate for --sign
154
+ # -K, --private-key KEY Key for --sign or --build
155
+ # -s, --sign CERT Signs CERT with the key from -K
156
+ # and the certificate from -C
157
+ #
158
+ # We've already covered the <code>--build</code> option, and the
159
+ # <code>--add</code>, <code>--list</code>, and <code>--remove</code> commands
160
+ # seem fairly straightforward; they allow you to add, list, and remove the
161
+ # certificates in your trusted certificate list. But what's with this
162
+ # <code>--sign</code> option?
163
+ #
164
+ # === Certificate chains
154
165
  #
155
166
  # To answer that question, let's take a look at "certificate chains", a
156
167
  # concept I mentioned earlier. There are a couple of problems with
@@ -172,134 +183,102 @@ require 'fileutils'
172
183
  # trust. Here's a hypothetical example of a trust hierarchy based (roughly)
173
184
  # on geography:
174
185
  #
175
- #
176
186
  # --------------------------
177
- # | rubygems@rubyforge.org |
187
+ # | rubygems@rubygems.org |
178
188
  # --------------------------
179
189
  # |
180
190
  # -----------------------------------
181
191
  # | |
182
192
  # ---------------------------- -----------------------------
183
- # | seattle.rb@zenspider.com | | dcrubyists@richkilmer.com |
193
+ # | seattlerb@seattlerb.org | | dcrubyists@richkilmer.com |
184
194
  # ---------------------------- -----------------------------
185
195
  # | | | |
186
196
  # --------------- ---------------- ----------- --------------
187
- # | alf@seattle | | bob@portland | | pabs@dc | | tomcope@dc |
197
+ # | drbrain | | zenspider | | pabs@dc | | tomcope@dc |
188
198
  # --------------- ---------------- ----------- --------------
189
199
  #
190
200
  #
191
- # Now, rather than having 4 trusted certificates (one for alf@seattle,
192
- # bob@portland, pabs@dc, and tomecope@dc), a user could actually get by with 1
193
- # certificate: the "rubygems@rubyforge.org" certificate. Here's how it works:
201
+ # Now, rather than having 4 trusted certificates (one for drbrain, zenspider,
202
+ # pabs@dc, and tomecope@dc), a user could actually get by with one
203
+ # certificate, the "rubygems@rubygems.org" certificate.
204
+ #
205
+ # Here's how it works:
206
+ #
207
+ # I install "rdoc-3.12.gem", a package signed by "drbrain". I've never heard
208
+ # of "drbrain", but his certificate has a valid signature from the
209
+ # "seattle.rb@seattlerb.org" certificate, which in turn has a valid signature
210
+ # from the "rubygems@rubygems.org" certificate. Voila! At this point, it's
211
+ # much more reasonable for me to trust a package signed by "drbrain", because
212
+ # I can establish a chain to "rubygems@rubygems.org", which I do trust.
194
213
  #
195
- # I install "Alf2000-Ruby-0.1.0.gem", a package signed by "alf@seattle". I've
196
- # never heard of "alf@seattle", but his certificate has a valid signature from
197
- # the "seattle.rb@zenspider.com" certificate, which in turn has a valid
198
- # signature from the "rubygems@rubyforge.org" certificate. Voila! At this
199
- # point, it's much more reasonable for me to trust a package signed by
200
- # "alf@seattle", because I can establish a chain to "rubygems@rubyforge.org",
201
- # which I do trust.
214
+ # === Signing certificates
202
215
  #
203
- # And the "--sign" option allows all this to happen. A developer creates
204
- # their build certificate with the "--build" option, then has their
205
- # certificate signed by taking it with them to their next regional Ruby meetup
206
- # (in our hypothetical example), and it's signed there by the person holding
207
- # the regional RubyGems signing certificate, which is signed at the next
208
- # RubyConf by the holder of the top-level RubyGems certificate. At each point
209
- # the issuer runs the same command:
216
+ # The <code>--sign</code> option allows all this to happen. A developer
217
+ # creates their build certificate with the <code>--build</code> option, then
218
+ # has their certificate signed by taking it with them to their next regional
219
+ # Ruby meetup (in our hypothetical example), and it's signed there by the
220
+ # person holding the regional RubyGems signing certificate, which is signed at
221
+ # the next RubyConf by the holder of the top-level RubyGems certificate. At
222
+ # each point the issuer runs the same command:
210
223
  #
211
224
  # # sign a certificate with the specified key and certificate
212
225
  # # (note that this modifies client_cert.pem!)
213
226
  # $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem
214
227
  # --sign client_cert.pem
215
228
  #
216
- # Then the holder of issued certificate (in this case, our buddy
217
- # "alf@seattle"), can start using this signed certificate to sign RubyGems.
218
- # By the way, in order to let everyone else know about his new fancy signed
219
- # certificate, "alf@seattle" would change his gemspec file to look like this:
229
+ # Then the holder of issued certificate (in this case, your buddy "drbrain"),
230
+ # can start using this signed certificate to sign RubyGems. By the way, in
231
+ # order to let everyone else know about his new fancy signed certificate,
232
+ # "drbrain" would save his newly signed certificate as
233
+ # <code>~/.gem/gem-public_cert.pem</code>
220
234
  #
221
- # # signing key (still kept in an undisclosed location!)
222
- # s.signing_key = '/mnt/floppy/alf-private_key.pem'
223
- #
224
- # # certificate chain (includes the issuer certificate now too)
225
- # s.cert_chain = ['/home/alf/doc/seattlerb-public_cert.pem',
226
- # '/home/alf/doc/alf_at_seattle-public_cert.pem']
227
- #
228
- # Obviously, this RubyGems trust infrastructure doesn't exist yet. Also, in
229
- # the "real world" issuers actually generate the child certificate from a
235
+ # Obviously this RubyGems trust infrastructure doesn't exist yet. Also, in
236
+ # the "real world", issuers actually generate the child certificate from a
230
237
  # certificate request, rather than sign an existing certificate. And our
231
238
  # hypothetical infrastructure is missing a certificate revocation system.
232
239
  # These are that can be fixed in the future...
233
240
  #
234
- # I'm sure your new signed gem has finished installing by now (unless you're
235
- # installing rails and all it's dependencies, that is ;D). At this point you
236
- # should know how to do all of these new and interesting things:
241
+ # At this point you should know how to do all of these new and interesting
242
+ # things:
237
243
  #
238
244
  # * build a gem signing key and certificate
239
- # * modify your existing gems to support signing
240
245
  # * adjust your security policy
241
246
  # * modify your trusted certificate list
242
247
  # * sign a certificate
243
248
  #
244
- # If you've got any questions, feel free to contact me at the email address
245
- # below. The next couple of sections
246
- #
247
- #
248
- # == Command-Line Options
249
- #
250
- # Here's a brief summary of the certificate-related command line options:
251
- #
252
- # gem install
253
- # -P, --trust-policy POLICY Specify gem trust policy.
254
- #
255
- # gem cert
256
- # -a, --add CERT Add a trusted certificate.
257
- # -l, --list List trusted certificates.
258
- # -r, --remove STRING Remove trusted certificates containing
259
- # STRING.
260
- # -b, --build EMAIL_ADDR Build private key and self-signed
261
- # certificate for EMAIL_ADDR.
262
- # -C, --certificate CERT Certificate for --sign command.
263
- # -K, --private-key KEY Private key for --sign command.
264
- # -s, --sign NEWCERT Sign a certificate with my key and
265
- # certificate.
266
- #
267
- # A more detailed description of each options is available in the walkthrough
268
- # above.
269
- #
270
249
  # == Manually verifying signatures
271
250
  #
272
251
  # In case you don't trust RubyGems you can verify gem signatures manually:
273
252
  #
274
253
  # 1. Fetch and unpack the gem
275
254
  #
276
- # gem fetch some_signed_gem
277
- # tar -xf some_signed_gem-1.0.gem
255
+ # gem fetch some_signed_gem
256
+ # tar -xf some_signed_gem-1.0.gem
278
257
  #
279
258
  # 2. Grab the public key from the gemspec
280
259
  #
281
- # gem spec some_signed_gem-1.0.gem cert_chain | \
282
- # ruby -pe 'sub(/^ +/, "")' > public_key.crt
260
+ # gem spec some_signed_gem-1.0.gem cert_chain | \
261
+ # ruby -ryaml -e 'puts YAML.load_documents($stdin)' > public_key.crt
283
262
  #
284
263
  # 3. Generate a SHA1 hash of the data.tar.gz
285
264
  #
286
- # openssl dgst -sha1 < data.tar.gz > my.hash
265
+ # openssl dgst -sha1 < data.tar.gz > my.hash
287
266
  #
288
267
  # 4. Verify the signature
289
268
  #
290
- # openssl rsautl -verify -inkey public_key.crt -certin \
291
- # -in data.tar.gz.sig > verified.hash
269
+ # openssl rsautl -verify -inkey public_key.crt -certin \
270
+ # -in data.tar.gz.sig > verified.hash
292
271
  #
293
272
  # 5. Compare your hash to the verified hash
294
273
  #
295
- # diff -s verified.hash my.hash
274
+ # diff -s verified.hash my.hash
296
275
  #
297
276
  # 6. Repeat 5 and 6 with metadata.gz
298
277
  #
299
278
  # == OpenSSL Reference
300
279
  #
301
- # The .pem files generated by --build and --sign are just basic OpenSSL PEM
302
- # files. Here's a couple of useful commands for manipulating them:
280
+ # The .pem files generated by --build and --sign are PEM files. Here's a
281
+ # couple of useful OpenSSL commands for manipulating them:
303
282
  #
304
283
  # # convert a PEM format X509 certificate into DER format:
305
284
  # # (note: Windows .cer files are X509 certificates in DER format)
@@ -321,8 +300,8 @@ require 'fileutils'
321
300
  # * There's no way to define a system-wide trust list.
322
301
  # * custom security policies (from a YAML file, etc)
323
302
  # * Simple method to generate a signed certificate request
324
- # * Support for OCSP, SCVP, CRLs, or some other form of cert
325
- # status check (list is in order of preference)
303
+ # * Support for OCSP, SCVP, CRLs, or some other form of cert status check
304
+ # (list is in order of preference)
326
305
  # * Support for encrypted private keys
327
306
  # * Some sort of semi-formal trust hierarchy (see long-winded explanation
328
307
  # above)
@@ -332,17 +311,13 @@ require 'fileutils'
332
311
  # MediumSecurity and HighSecurity policies)
333
312
  # * Better explanation of X509 naming (ie, we don't have to use email
334
313
  # addresses)
335
- # * Possible alternate signing mechanisms (eg, via PGP). this could be done
336
- # pretty easily by adding a :signing_type attribute to the gemspec, then add
337
- # the necessary support in other places
338
314
  # * Honor AIA field (see note about OCSP above)
339
- # * Maybe honor restriction extensions?
315
+ # * Honor extension restrictions
340
316
  # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
341
- # file, instead of an array embedded in the metadata. ideas?
342
- # * Possibly embed signature and key algorithms into metadata (right now
343
- # they're assumed to be the same as what's set in Gem::Security::OPT)
317
+ # file, instead of an array embedded in the metadata.
318
+ # * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
344
319
  #
345
- # == About the Author
320
+ # == Original author
346
321
  #
347
322
  # Paul Duncan <pabs@pablotron.org>
348
323
  # http://pablotron.org/
@@ -355,472 +330,237 @@ module Gem::Security
355
330
  class Exception < Gem::Exception; end
356
331
 
357
332
  ##
358
- # Default options for most of the methods below
359
-
360
- OPT = {
361
- # private key options
362
- :key_algo => Gem::SSL::PKEY_RSA,
363
- :key_size => 2048,
364
-
365
- # public cert options
366
- :cert_age => 365 * 24 * 3600, # 1 year
367
- :dgst_algo => Gem::SSL::DIGEST_SHA1,
368
-
369
- # x509 certificate extensions
370
- :cert_exts => {
371
- 'basicConstraints' => 'CA:FALSE',
372
- 'subjectKeyIdentifier' => 'hash',
373
- 'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature',
374
- },
375
-
376
- # save the key and cert to a file in build_self_signed_cert()?
377
- :save_key => true,
378
- :save_cert => true,
379
-
380
- # if you define either of these, then they'll be used instead of
381
- # the output_fmt macro below
382
- :save_key_path => nil,
383
- :save_cert_path => nil,
384
-
385
- # output name format for self-signed certs
386
- :output_fmt => 'gem-%s.pem',
387
- :munge_re => Regexp.new(/[^a-z0-9_.-]+/),
388
-
389
- # output directory for trusted certificate checksums
390
- :trust_dir => File.join(Gem.user_home, '.gem', 'trust'),
391
-
392
- # default permissions for trust directory and certs
393
- :perms => {
394
- :trust_dir => 0700,
395
- :trusted_cert => 0600,
396
- :signing_cert => 0600,
397
- :signing_key => 0600,
398
- },
399
- }
333
+ # Digest algorithm used to sign gems
334
+
335
+ DIGEST_ALGORITHM = OpenSSL::Digest::SHA1
400
336
 
401
337
  ##
402
- # A Gem::Security::Policy object encapsulates the settings for verifying
403
- # signed gem files. This is the base class. You can either declare an
404
- # instance of this or use one of the preset security policies below.
405
-
406
- class Policy
407
- attr_accessor :verify_data, :verify_signer, :verify_chain,
408
- :verify_root, :only_trusted, :only_signed
409
-
410
- #
411
- # Create a new Gem::Security::Policy object with the given mode and
412
- # options.
413
- #
414
- def initialize(policy = {}, opt = {})
415
- # set options
416
- @opt = Gem::Security::OPT.merge(opt)
417
-
418
- # build policy
419
- policy.each_pair do |key, val|
420
- case key
421
- when :verify_data then @verify_data = val
422
- when :verify_signer then @verify_signer = val
423
- when :verify_chain then @verify_chain = val
424
- when :verify_root then @verify_root = val
425
- when :only_trusted then @only_trusted = val
426
- when :only_signed then @only_signed = val
427
- end
428
- end
429
- end
338
+ # Used internally to select the signing digest from all computed digests
430
339
 
431
- #
432
- # Get the path to the file for this cert.
433
- #
434
- def self.trusted_cert_path(cert, opt = {})
435
- opt = Gem::Security::OPT.merge(opt)
340
+ DIGEST_NAME = DIGEST_ALGORITHM.new.name # :nodoc:
436
341
 
437
- # get digest algorithm, calculate checksum of root.subject
438
- algo = opt[:dgst_algo]
439
- dgst = algo.hexdigest(cert.subject.to_s)
342
+ ##
343
+ # Algorithm for creating the key pair used to sign gems
440
344
 
441
- # build path to trusted cert file
442
- name = "cert-#{dgst}.pem"
345
+ KEY_ALGORITHM = OpenSSL::PKey::RSA
443
346
 
444
- # join and return path components
445
- File::join(opt[:trust_dir], name)
446
- end
347
+ ##
348
+ # Length of keys created by KEY_ALGORITHM
447
349
 
448
- #
449
- # Verify that the gem data with the given signature and signing chain
450
- # matched this security policy at the specified time.
451
- #
452
- def verify_gem(signature, data, chain, time = Time.now)
453
- Gem.ensure_ssl_available
454
- cert_class = OpenSSL::X509::Certificate
455
- exc = Gem::Security::Exception
456
- chain ||= []
457
-
458
- chain = chain.map{ |str| cert_class.new(str) }
459
- signer, ch_len = chain[-1], chain.size
460
-
461
- # make sure signature is valid
462
- if @verify_data
463
- # get digest algorithm (TODO: this should be configurable)
464
- dgst = @opt[:dgst_algo]
465
-
466
- # verify the data signature (this is the most important part, so don't
467
- # screw it up :D)
468
- v = signer.public_key.verify(dgst.new, signature, data)
469
- raise exc, "Invalid Gem Signature" unless v
470
-
471
- # make sure the signer is valid
472
- if @verify_signer
473
- # make sure the signing cert is valid right now
474
- v = signer.check_validity(nil, time)
475
- raise exc, "Invalid Signature: #{v[:desc]}" unless v[:is_valid]
476
- end
477
- end
478
-
479
- # make sure the certificate chain is valid
480
- if @verify_chain
481
- # iterate down over the chain and verify each certificate against it's
482
- # issuer
483
- (ch_len - 1).downto(1) do |i|
484
- issuer, cert = chain[i - 1, 2]
485
- v = cert.check_validity(issuer, time)
486
- raise exc, "%s: cert = '%s', error = '%s'" % [
487
- 'Invalid Signing Chain', cert.subject, v[:desc]
488
- ] unless v[:is_valid]
489
- end
490
-
491
- # verify root of chain
492
- if @verify_root
493
- # make sure root is self-signed
494
- root = chain[0]
495
- raise exc, "%s: %s (subject = '%s', issuer = '%s')" % [
496
- 'Invalid Signing Chain Root',
497
- 'Subject does not match Issuer for Gem Signing Chain',
498
- root.subject.to_s,
499
- root.issuer.to_s,
500
- ] unless root.issuer.to_s == root.subject.to_s
501
-
502
- # make sure root is valid
503
- v = root.check_validity(root, time)
504
- raise exc, "%s: cert = '%s', error = '%s'" % [
505
- 'Invalid Signing Chain Root', root.subject, v[:desc]
506
- ] unless v[:is_valid]
507
-
508
- # verify that the chain root is trusted
509
- if @only_trusted
510
- # get digest algorithm, calculate checksum of root.subject
511
- algo = @opt[:dgst_algo]
512
- path = Gem::Security::Policy.trusted_cert_path(root, @opt)
513
-
514
- # check to make sure trusted path exists
515
- raise exc, "%s: cert = '%s', error = '%s'" % [
516
- 'Untrusted Signing Chain Root',
517
- root.subject.to_s,
518
- "path \"#{path}\" does not exist",
519
- ] unless File.exist?(path)
520
-
521
- # load calculate digest from saved cert file
522
- save_cert = OpenSSL::X509::Certificate.new(File.read(path))
523
- save_dgst = algo.digest(save_cert.public_key.to_s)
524
-
525
- # create digest of public key
526
- pkey_str = root.public_key.to_s
527
- cert_dgst = algo.digest(pkey_str)
528
-
529
- # now compare the two digests, raise exception
530
- # if they don't match
531
- raise exc, "%s: %s (saved = '%s', root = '%s')" % [
532
- 'Invalid Signing Chain Root',
533
- "Saved checksum doesn't match root checksum",
534
- save_dgst, cert_dgst,
535
- ] unless save_dgst == cert_dgst
536
- end
537
- end
538
-
539
- # return the signing chain
540
- chain.map { |cert| cert.subject }
541
- end
542
- end
543
- end
350
+ KEY_LENGTH = 2048
544
351
 
545
352
  ##
546
- # No security policy: all package signature checks are disabled.
353
+ # One year in seconds
547
354
 
548
- NoSecurity = Policy.new(
549
- :verify_data => false,
550
- :verify_signer => false,
551
- :verify_chain => false,
552
- :verify_root => false,
553
- :only_trusted => false,
554
- :only_signed => false
555
- )
355
+ ONE_YEAR = 86400 * 365
556
356
 
557
357
  ##
558
- # AlmostNo security policy: only verify that the signing certificate is the
559
- # one that actually signed the data. Make no attempt to verify the signing
560
- # certificate chain.
358
+ # The default set of extensions are:
561
359
  #
562
- # This policy is basically useless. better than nothing, but can still be
563
- # easily spoofed, and is not recommended.
564
-
565
- AlmostNoSecurity = Policy.new(
566
- :verify_data => true,
567
- :verify_signer => false,
568
- :verify_chain => false,
569
- :verify_root => false,
570
- :only_trusted => false,
571
- :only_signed => false
572
- )
360
+ # * The certificate is not a certificate authority
361
+ # * The key for the certificate may be used for key and data encipherment
362
+ # and digital signatures
363
+ # * The certificate contains a subject key identifier
364
+
365
+ EXTENSIONS = {
366
+ 'basicConstraints' => 'CA:FALSE',
367
+ 'keyUsage' =>
368
+ 'keyEncipherment,dataEncipherment,digitalSignature',
369
+ 'subjectKeyIdentifier' => 'hash',
370
+ }
573
371
 
574
- ##
575
- # Low security policy: only verify that the signing certificate is actually
576
- # the gem signer, and that the signing certificate is valid.
577
- #
578
- # This policy is better than nothing, but can still be easily spoofed, and
579
- # is not recommended.
580
-
581
- LowSecurity = Policy.new(
582
- :verify_data => true,
583
- :verify_signer => true,
584
- :verify_chain => false,
585
- :verify_root => false,
586
- :only_trusted => false,
587
- :only_signed => false
588
- )
372
+ def self.alt_name_or_x509_entry certificate, x509_entry
373
+ alt_name = certificate.extensions.find do |extension|
374
+ extension.oid == "#{x509_entry}AltName"
375
+ end
589
376
 
590
- ##
591
- # Medium security policy: verify the signing certificate, verify the signing
592
- # certificate chain all the way to the root certificate, and only trust root
593
- # certificates that we have explicitly allowed trust for.
594
- #
595
- # This security policy is reasonable, but it allows unsigned packages, so a
596
- # malicious person could simply delete the package signature and pass the
597
- # gem off as unsigned.
598
-
599
- MediumSecurity = Policy.new(
600
- :verify_data => true,
601
- :verify_signer => true,
602
- :verify_chain => true,
603
- :verify_root => true,
604
- :only_trusted => true,
605
- :only_signed => false
606
- )
377
+ return alt_name.value if alt_name
378
+
379
+ certificate.send x509_entry
380
+ end
607
381
 
608
382
  ##
609
- # High security policy: only allow signed gems to be installed, verify the
610
- # signing certificate, verify the signing certificate chain all the way to
611
- # the root certificate, and only trust root certificates that we have
612
- # explicitly allowed trust for.
383
+ # Creates an unsigned certificate for +subject+ and +key+. The lifetime of
384
+ # the key is from the current time to +age+ which defaults to one year.
613
385
  #
614
- # This security policy is significantly more difficult to bypass, and offers
615
- # a reasonable guarantee that the contents of the gem have not been altered.
616
-
617
- HighSecurity = Policy.new(
618
- :verify_data => true,
619
- :verify_signer => true,
620
- :verify_chain => true,
621
- :verify_root => true,
622
- :only_trusted => true,
623
- :only_signed => true
624
- )
386
+ # The +extensions+ restrict the key to the indicated uses.
625
387
 
626
- ##
627
- # Hash of configured security policies
628
-
629
- Policies = {
630
- 'NoSecurity' => NoSecurity,
631
- 'AlmostNoSecurity' => AlmostNoSecurity,
632
- 'LowSecurity' => LowSecurity,
633
- 'MediumSecurity' => MediumSecurity,
634
- 'HighSecurity' => HighSecurity,
635
- }
388
+ def self.create_cert subject, key, age = ONE_YEAR, extensions = EXTENSIONS,
389
+ serial = 1
390
+ cert = OpenSSL::X509::Certificate.new
636
391
 
637
- ##
638
- # Sign the cert cert with @signing_key and @signing_cert, using the digest
639
- # algorithm opt[:dgst_algo]. Returns the newly signed certificate.
392
+ cert.public_key = key.public_key
393
+ cert.version = 2
394
+ cert.serial = serial
640
395
 
641
- def self.sign_cert(cert, signing_key, signing_cert, opt = {})
642
- opt = OPT.merge(opt)
396
+ cert.not_before = Time.now
397
+ cert.not_after = Time.now + age
643
398
 
644
- cert.issuer = signing_cert.subject
645
- cert.sign signing_key, opt[:dgst_algo].new
399
+ cert.subject = subject
646
400
 
647
- cert
648
- end
401
+ ef = OpenSSL::X509::ExtensionFactory.new nil, cert
649
402
 
650
- ##
651
- # Make sure the trust directory exists. If it does exist, make sure it's
652
- # actually a directory. If not, then create it with the appropriate
653
- # permissions.
654
-
655
- def self.verify_trust_dir(path, perms)
656
- # if the directory exists, then make sure it is in fact a directory. if
657
- # it doesn't exist, then create it with the appropriate permissions
658
- if File.exist?(path)
659
- # verify that the trust directory is actually a directory
660
- unless File.directory?(path)
661
- err = "trust directory #{path} isn't a directory"
662
- raise Gem::Security::Exception, err
663
- end
664
- else
665
- # trust directory doesn't exist, so create it with permissions
666
- FileUtils.mkdir_p(path)
667
- FileUtils.chmod(perms, path)
403
+ cert.extensions = extensions.map do |ext_name, value|
404
+ ef.create_extension ext_name, value
668
405
  end
406
+
407
+ cert
669
408
  end
670
409
 
671
410
  ##
672
- # Build a certificate from the given DN and private key.
411
+ # Creates a self-signed certificate with an issuer and subject from +email+,
412
+ # a subject alternative name of +email+ and the given +extensions+ for the
413
+ # +key+.
673
414
 
674
- def self.build_cert(name, key, opt = {})
675
- Gem.ensure_ssl_available
676
- opt = OPT.merge opt
415
+ def self.create_cert_email email, key, age = ONE_YEAR, extensions = EXTENSIONS
416
+ subject = email_to_name email
677
417
 
678
- cert = OpenSSL::X509::Certificate.new
418
+ extensions = extensions.merge "subjectAltName" => "email:#{email}"
679
419
 
680
- cert.not_after = Time.now + opt[:cert_age]
681
- cert.not_before = Time.now
682
- cert.public_key = key.public_key
683
- cert.serial = 0
684
- cert.subject = name
685
- cert.version = 2
420
+ create_cert_self_signed subject, key, age, extensions
421
+ end
686
422
 
687
- ef = OpenSSL::X509::ExtensionFactory.new nil, cert
423
+ ##
424
+ # Creates a self-signed certificate with an issuer and subject of +subject+
425
+ # and the given +extensions+ for the +key+.
688
426
 
689
- cert.extensions = opt[:cert_exts].map do |ext_name, value|
690
- ef.create_extension ext_name, value
691
- end
427
+ def self.create_cert_self_signed subject, key, age = ONE_YEAR,
428
+ extensions = EXTENSIONS, serial = 1
429
+ certificate = create_cert subject, key, age, extensions
692
430
 
693
- i_key = opt[:issuer_key] || key
694
- i_cert = opt[:issuer_cert] || cert
431
+ sign certificate, key, certificate, age, extensions, serial
432
+ end
695
433
 
696
- cert = sign_cert cert, i_key, i_cert, opt
434
+ ##
435
+ # Creates a new key pair of the specified +length+ and +algorithm+. The
436
+ # default is a 2048 bit RSA key.
697
437
 
698
- cert
438
+ def self.create_key length = KEY_LENGTH, algorithm = KEY_ALGORITHM
439
+ algorithm.new length
699
440
  end
700
441
 
701
442
  ##
702
- # Build a self-signed certificate for the given email address.
443
+ # Turns +email_address+ into an OpenSSL::X509::Name
703
444
 
704
- def self.build_self_signed_cert(email_addr, opt = {})
705
- Gem.ensure_ssl_available
706
- opt = OPT.merge(opt)
707
- path = { :key => nil, :cert => nil }
445
+ def self.email_to_name email_address
446
+ email_address = email_address.gsub(/[^\w@.-]+/i, '_')
708
447
 
709
- name = email_to_name email_addr, opt[:munge_re]
448
+ cn, dcs = email_address.split '@'
710
449
 
711
- key = opt[:key_algo].new opt[:key_size]
450
+ dcs = dcs.split '.'
712
451
 
713
- verify_trust_dir opt[:trust_dir], opt[:perms][:trust_dir]
452
+ name = "CN=#{cn}/#{dcs.map { |dc| "DC=#{dc}" }.join '/'}"
714
453
 
715
- if opt[:save_key] then
716
- path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key')
454
+ OpenSSL::X509::Name.parse name
455
+ end
717
456
 
718
- open path[:key], 'wb' do |io|
719
- io.chmod opt[:perms][:signing_key]
720
- io.write key.to_pem
721
- end
457
+ ##
458
+ # Signs +expired_certificate+ with +private_key+ if the keys match and the
459
+ # expired certificate was self-signed.
460
+ #--
461
+ # TODO increment serial
462
+
463
+ def self.re_sign expired_certificate, private_key, age = ONE_YEAR,
464
+ extensions = EXTENSIONS
465
+ raise Gem::Security::Exception,
466
+ "incorrect signing key for re-signing " \
467
+ "#{expired_certificate.subject}" unless
468
+ expired_certificate.public_key.to_pem == private_key.public_key.to_pem
469
+
470
+ unless expired_certificate.subject.to_s ==
471
+ expired_certificate.issuer.to_s then
472
+ subject = alt_name_or_x509_entry expired_certificate, :subject
473
+ issuer = alt_name_or_x509_entry expired_certificate, :issuer
474
+
475
+ raise Gem::Security::Exception,
476
+ "#{subject} is not self-signed, contact #{issuer} " \
477
+ "to obtain a valid certificate"
722
478
  end
723
479
 
724
- cert = build_cert name, key, opt
480
+ serial = expired_certificate.serial + 1
725
481
 
726
- if opt[:save_cert] then
727
- path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert')
482
+ create_cert_self_signed(expired_certificate.subject, private_key, age,
483
+ extensions, serial)
484
+ end
728
485
 
729
- open path[:cert], 'wb' do |file|
730
- file.chmod opt[:perms][:signing_cert]
731
- file.write cert.to_pem
732
- end
733
- end
486
+ ##
487
+ # Resets the trust directory for verifying gems.
734
488
 
735
- { :key => key, :cert => cert,
736
- :key_path => path[:key], :cert_path => path[:cert] }
489
+ def self.reset
490
+ @trust_dir = nil
737
491
  end
738
492
 
739
493
  ##
740
- # Turns +email_address+ into an OpenSSL::X509::Name
494
+ # Sign the public key from +certificate+ with the +signing_key+ and
495
+ # +signing_cert+, using the Gem::Security::DIGEST_ALGORITHM. Uses the
496
+ # default certificate validity range and extensions.
497
+ #
498
+ # Returns the newly signed certificate.
741
499
 
742
- def self.email_to_name email_address, munge_re
743
- cn, dcs = email_address.split '@'
500
+ def self.sign certificate, signing_key, signing_cert,
501
+ age = ONE_YEAR, extensions = EXTENSIONS, serial = 1
502
+ signee_subject = certificate.subject
503
+ signee_key = certificate.public_key
744
504
 
745
- dcs = dcs.split '.'
505
+ alt_name = certificate.extensions.find do |extension|
506
+ extension.oid == 'subjectAltName'
507
+ end
746
508
 
747
- cn = cn.gsub munge_re, '_'
509
+ extensions = extensions.merge 'subjectAltName' => alt_name.value if
510
+ alt_name
748
511
 
749
- dcs = dcs.map do |dc|
750
- dc.gsub munge_re, '_'
512
+ issuer_alt_name = signing_cert.extensions.find do |extension|
513
+ extension.oid == 'subjectAltName'
751
514
  end
752
515
 
753
- name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/')
516
+ extensions = extensions.merge 'issuerAltName' => issuer_alt_name.value if
517
+ issuer_alt_name
754
518
 
755
- OpenSSL::X509::Name.parse name
519
+ signed = create_cert signee_subject, signee_key, age, extensions, serial
520
+ signed.issuer = signing_cert.subject
521
+
522
+ signed.sign signing_key, Gem::Security::DIGEST_ALGORITHM.new
756
523
  end
757
524
 
758
525
  ##
759
- # Add certificate to trusted cert list.
760
- #
761
- # Note: At the moment these are stored in OPT[:trust_dir], although that
762
- # directory may change in the future.
526
+ # Returns a Gem::Security::TrustDir which wraps the directory where trusted
527
+ # certificates live.
763
528
 
764
- def self.add_trusted_cert(cert, opt = {})
765
- opt = OPT.merge(opt)
529
+ def self.trust_dir
530
+ return @trust_dir if @trust_dir
766
531
 
767
- # get destination path
768
- path = Gem::Security::Policy.trusted_cert_path(cert, opt)
532
+ dir = File.join Gem.user_home, '.gem', 'trust'
769
533
 
770
- # verify trust directory (can't write to nowhere, you know)
771
- verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
534
+ @trust_dir ||= Gem::Security::TrustDir.new dir
535
+ end
772
536
 
773
- # write cert to output file
774
- File.open(path, 'wb') do |file|
775
- file.chmod(opt[:perms][:trusted_cert])
776
- file.write(cert.to_pem)
777
- end
537
+ ##
538
+ # Enumerates the trusted certificates via Gem::Security::TrustDir.
778
539
 
779
- # return nil
780
- nil
540
+ def self.trusted_certificates &block
541
+ trust_dir.each_certificate(&block)
781
542
  end
782
543
 
783
544
  ##
784
- # Basic OpenSSL-based package signing class.
785
-
786
- class Signer
787
-
788
- attr_accessor :cert_chain
789
- attr_accessor :key
790
-
791
- def initialize(key, cert_chain)
792
- Gem.ensure_ssl_available
793
- @algo = Gem::Security::OPT[:dgst_algo]
794
- @key, @cert_chain = key, cert_chain
795
-
796
- # check key, if it's a file, and if it's key, leave it alone
797
- if @key && !@key.kind_of?(OpenSSL::PKey::PKey)
798
- @key = OpenSSL::PKey::RSA.new(File.read(@key))
799
- end
800
-
801
- # check cert chain, if it's a file, load it, if it's cert data, convert
802
- # it into a cert object, and if it's a cert object, leave it alone
803
- if @cert_chain
804
- @cert_chain = @cert_chain.map do |cert|
805
- # check cert, if it's a file, load it, if it's cert data, convert it
806
- # into a cert object, and if it's a cert object, leave it alone
807
- if cert && !cert.kind_of?(OpenSSL::X509::Certificate)
808
- cert = File.read(cert) if File::exist?(cert)
809
- cert = OpenSSL::X509::Certificate.new(cert)
810
- end
811
- cert
812
- end
813
- end
814
- end
545
+ # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given
546
+ # +permissions+.
815
547
 
816
- ##
817
- # Sign data with given digest algorithm
548
+ def self.write pemmable, path, permissions = 0600
549
+ path = File.expand_path path
818
550
 
819
- def sign(data)
820
- @key.sign(@algo.new, data)
551
+ open path, 'wb', permissions do |io|
552
+ io.write pemmable.to_pem
821
553
  end
822
554
 
555
+ path
823
556
  end
824
557
 
558
+ reset
559
+
825
560
  end
826
561
 
562
+ require 'rubygems/security/policy'
563
+ require 'rubygems/security/policies'
564
+ require 'rubygems/security/signer'
565
+ require 'rubygems/security/trust_dir'
566
+