libgems 0.0.1

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.
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
+