rubygems-openpgp 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0657ab763223c89affb7cfdbdfe7227ba093a0c0
4
+ data.tar.gz: 7b16826241b21a82815b8baa681697ad7e0f3983
5
+ SHA512:
6
+ metadata.gz: b3c5a89a986acbcd84aaee04aae27aa5a68966529c830232be62cb64a3f7c62c59d1a18e749007477516227bcdf5969a3c0b1507a62aaa55c6bd84c82f6a9244
7
+ data.tar.gz: adeb6e5b33bb455ff52f90405744417d566af3b721cc829576b8eba9eeed7c0cc7fa5072393bf0e4ce4c1c6020f0e8e85883c97d89ad5482894a26431f628bd6
checksums.yaml.gz.asc ADDED
@@ -0,0 +1,11 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+ Version: GnuPG v1.4.11 (GNU/Linux)
3
+
4
+ iQEcBAABAgAGBQJRPJV6AAoJEP5F5V2hilTW55oH/RVctpv/kKf8erckN0vvNZUa
5
+ KdtcULIoD0P8u26uiL8zTM4noWi2f3kkfv1fAd9026IsJarOpucfDmpZpTDb88Xw
6
+ gElUTXKhG7bnoPhuEfrz6/h5cwMpQREktIbVwtCF0ED7aRLbxcUChx+BfFLLeUW1
7
+ yLnwi8aN2EJlcL1w8fvDmPux6IppuZ/NPxo65dXVHIsjpyLvhrNhqaEOEHv2AP9b
8
+ xNWSV+sn8i9YfVUZhmZLLakWkmfoRGrZYXQYisTQbKfN5Kfeyg3uJRcQ9kk8Kmua
9
+ 5UBTLEk/T2meyaN7FQ8l23P5ZNxcRhqDWnGKJMxRZxaShVJk4P8hmhgICL/YX6w=
10
+ =CtND
11
+ -----END PGP SIGNATURE-----
data.tar.gz.asc CHANGED
@@ -1,11 +1,11 @@
1
1
  -----BEGIN PGP SIGNATURE-----
2
- Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
2
+ Version: GnuPG v1.4.11 (GNU/Linux)
3
3
 
4
- iQEcBAABCgAGBQJRKNObAAoJEP5F5V2hilTWjFYH/RPGxYJA41R43Yrgqu56vtAU
5
- iPY/pg041S42UnObIIFgA76fMeixzfFCdBkvFFnlDuqmbSLxtiJt0Q/ql5c/hFzA
6
- S/rKibb1I9nlzOJ3p/GO78HzaD/M2Ja/jdrJtt+w7LVqSVtfDOyS+lPe3wxzf6SS
7
- 7hgpycQv6GX3C5AEGY/nP0btkDxNtYMGGgcYz3WfrOEQFDIKS6IU4iZCj5c6Zw4n
8
- yD2pAvHWoqdkYy0y+eUsi83E1zCL9fHEK3R/I+c9Kc4TQF2OXFmGZdc7wbYwtdPb
9
- yDqlTNo8mbm+kxek1P4PobHJ7QADHRC7kOSBMOXVw80JALUThAt11Y+bHOHv3/Y=
10
- =58pK
4
+ iQEcBAABAgAGBQJRPJV3AAoJEP5F5V2hilTWJnAH/jyebr8gp9guDxLVuN3lodD9
5
+ OLjYgkI97P7e597lL7LDyMpadyo01yQjB1+xfKNQqgAaUtVUglfaeGjU+vnvpfAz
6
+ 5Hx/SdztlhDnzK+W71wzdBJ6/8Nco6wDhDe8dQcM3BgSK2ijvHgCfYMqy9SoKGgC
7
+ ZHNWq+a2cPd8nglgaR2iTlN+0kzQq/f4WdGFOuCliq5E5ItFlCzMiSnVUfAvyT2G
8
+ lOvnxYvzMOItxQMHU9lVvGvYhUxsUd4bvRMBvy3rdbErnp3zFFSu2tHTEvoJt2+v
9
+ LvzBaiQ+u00qirlusp1amEHurUK7bPSuTSWN98fnrh5DaFwzh/hd1Cjrr0FY1xY=
10
+ =rKlC
11
11
  -----END PGP SIGNATURE-----
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  rubygems-openpgp
2
2
  ================
3
3
 
4
+ Information for gem users and gem developers is slowly and surely migrating
5
+ to the [rubygems-openpgp Certificate
6
+ Authority](http://rubygems-openpgp-ca.org). You probably want to go
7
+ there unless you're interested in working on the plugin itself.
8
+
9
+
4
10
  Software Assurance
5
11
  ------------------
6
12
 
@@ -44,33 +50,28 @@ Signing example
44
50
  Verification Example
45
51
  --------------------
46
52
 
53
+ A detailed walkthrough of verifiction is available at
54
+ [The Complete Guide to Verifying Gems with
55
+ rubygems-openpgp](http://www.rubygems-openpgp-ca.org/blog/the-complete-guide-to-verifying-gems-with-rubygems-openpgp.html)
56
+
57
+ ### TLDR?
58
+
47
59
  A test gem **openpgp_signed_hola** is on rubygems.org. To try out
48
60
  this extension:
49
61
 
50
- gem install openpgp_signed_hola-0.0.0.gem --verify
62
+ gem install openpgp_signed_hola-0.0.0.gem --verify --trust --get-key
51
63
 
52
- But That Just Failed!
53
- ---------------------
54
64
 
55
- You probably don't have my public key yet. You need my public key to
56
- verify the digital signature. You also want to perform some
57
- authentication of that public key.
65
+ ### But That Just Failed!
58
66
 
59
- [Notes on retrieving and authenticating public keys.](./doc/retrieving-and-authenticating-keys.md)
67
+ You probably don't *trust* my public key. More information is
68
+ available at [The Complete Guide to Verifying Gems with
69
+ rubygems-openpgp](http://www.rubygems-openpgp-ca.org/blog/the-complete-guide-to-verifying-gems-with-rubygems-openpgp.html)
60
70
 
61
71
  Verifying your initial install
62
72
  ------------------------------
63
73
 
64
- All versions of this gem should be signed. But the first time you
65
- install the package you run into a bit of a chicken-and-the-egg
66
- problem. You can't verify the package until you've installed a copy.
67
- But if that copy isn't verified, if could already be compromised.
68
-
69
- But don't worry. You can use a stand-alone signature to verify your
70
- initial install. Since the stand-alone signature is on github, and
71
- the software package is on rubygems.org, a malicious user would need
72
- to compromise both sites to publish a compromised gem and
73
- compromised/forged digital signature.
74
-
75
- [Notes on verifying the initial install.](./doc/verifying-initial-install.md)
74
+ You can verify your initial install with a detached signature.
75
+ [Here's
76
+ how.](http://www.rubygems-openpgp-ca.org/blog/the-complete-guide-to-verifying-your-initial-install.html)
76
77
 
@@ -1,284 +1,2 @@
1
- require 'rubygems'
2
- require 'rubygems/package'
3
- require 'rubygems/user_interaction'
4
- require 'shellwords'
5
- require 'open3'
6
- require 'tempfile'
7
- require 'gpg_status_parser'
8
-
9
- # Exception for this class
10
- class Gem::OpenPGPException < RuntimeError; end
11
-
12
- # A wrapper that shells out the real OpenPGP crypto work
13
- # to gpg.
14
- module Gem::OpenPGP
15
- extend Shellwords
16
- extend Gem::UserInteraction
17
-
18
- # Given a string of data, generate and return a detached
19
- # signature. By defualt, this will use your primary secret key.
20
- # This can be overridden by specifying a key_id for another
21
- # private key.
22
- def self.detach_sign data, key_id=nil, homedir=nil
23
- is_gpg_available
24
- is_key_valid key_id if key_id
25
- is_homedir_valid homedir if homedir
26
-
27
- key_flag = ""
28
- key_flag = "-u #{shellescape(key_id)}" if key_id
29
-
30
- homedir_flag = ""
31
- homedir_flag = "--homedir #{shellescape(homedir)}" if homedir
32
-
33
- gpg_args = "#{key_flag} #{homedir_flag} --detach-sign --armor"
34
- gpg_results = run_gpg(gpg_args, data)
35
- did_gpg_error? gpg_results
36
-
37
- gpg_results[:stdout]
38
- end
39
-
40
- # Extract the info we care about, throw away the rest
41
- def self.verify_extract_status_info message, status_info
42
- case message.status
43
- when :GOODSIG, :BADSIG, :ERRSIG
44
- status_info[:good_or_bad] = message.status
45
- status_info[:uid] = (message.args[:username] || "").strip
46
-
47
- when :SIG_ID
48
- when :VALIDSIG, :EXPSIG, :BADSIG
49
- status_info[:sig_status] = message.status
50
- status_info[:primary_key] = "0x#{message.args[:primary_key_fpr][-9..-1]}"
51
- when :TRUST_UNDEFINED, :TRUST_NEVER, :TRUST_MARGINAL, :TRUST_FULLY, :TRUST_ULTIMATE
52
- status_info[:trust_status] = message.status
53
- when :NO_PUBKEY
54
- status_info[:failure] = "You don't have the public key. Use --get-key to automagically retrieve from keyservers"
55
- when :IMPORTED, :IMPORT_OK, :IMPORT_RES
56
- #silently_ignore
57
- when :KEYEXPIRED, :SIGEXPIRED
58
- # recalculating trust db, ignore.
59
- else
60
- puts "unexpected message: #{message.status} #{message.args.inspect}"
61
- end
62
- end
63
-
64
- # Print info about the sig, check that we like it, and possibly abort
65
- def self.verify_check_sig status_info
66
- sig_msg = "Signature for #{status_info[:file_name]} from user #{status_info[:uid]} key #{status_info[:primary_key]} is #{status_info[:good_or_bad]}, #{status_info[:sig_status]} and #{status_info[:trust_status]}"
67
- if status_info[:trust_status] == :TRUST_NEVER
68
- say add_color(sig_msg, :red)
69
- raise Gem::OpenPGPException, "Never Trusted. Won't install."
70
- elsif status_info[:trust_status] == :TRUST_UNDEFINED
71
- say add_color(sig_msg, :yellow)
72
- if options[:trust] && !options[:no_trust]
73
- raise Gem::OpenPGPException, "Trust Undefined and you've specified --trust. Won't install."
74
- end
75
- else
76
- say add_color(sig_msg , :green)
77
- end
78
- end
79
-
80
- # Given a string containing data, and a string containing
81
- # a detached signature, verify the data. If we can't verify
82
- # then raise an exception.
83
- #
84
- # Optionally tell gpg to retrive the key if it's not provided
85
- def self.verify file_name, data, sig, get_key=false, homedir=nil
86
- is_gpg_available
87
- is_homedir_valid homedir if homedir
88
-
89
- data_file = create_tempfile data
90
- sig_file = create_tempfile sig
91
-
92
- get_key_params = "--keyserver pool.sks-keyservers.net --keyserver-options auto-key-retrieve"
93
- get_key_params = "" if get_key != true
94
-
95
- homedir_flags = ""
96
- homedir_flags = "--homedir #{homedir}" if homedir
97
-
98
- gpg_args = "#{get_key_params} #{homedir_flags} --verify #{sig_file.path} #{data_file.path}"
99
-
100
- status_info = {:file_name => file_name}
101
- gpg_results = run_gpg(gpg_args) { |message| verify_extract_status_info(message, status_info) }
102
-
103
- if status_info[:failure]
104
- say add_color(status_info[:failure], :red)
105
- raise Gem::OpenPGPException, "Fail!"
106
- else
107
- verify_check_sig status_info
108
- end
109
-
110
- did_gpg_error? gpg_results
111
-
112
- [gpg_results[:stderr], gpg_results[:status]]
113
- end
114
-
115
- # Signs an existing gemfile by iterating the tar'ed up contents,
116
- # and signing any contents. creating a new file with original contents
117
- # and OpenPGP sigs. The OpenPGP sigs are saved as .asc files so they
118
- # won't conflict with X509 sigs.
119
- #
120
- # Optional param "key" allows you to use a different private
121
- # key than the GPG default.
122
- def self.sign_gem gem, key=nil, homedir=nil
123
- unsigned_gem = gem + ".unsigned"
124
-
125
- begin
126
- FileUtils.mv gem, unsigned_gem
127
- rescue Errno::ENOENT => ex
128
- raise Gem::CommandLineError, "The gem #{gem} does not seem to exist. (#{ex.message})"
129
- end
130
-
131
- unsigned_gem_file = File.open(unsigned_gem, "r")
132
- signed_gem_file = File.open(gem, "w")
133
-
134
- signed_gem = Gem::Package::TarWriter.new(signed_gem_file)
135
-
136
- Gem::Package::TarReader.new(unsigned_gem_file).each do |f|
137
- say(f.full_name.inspect)
138
-
139
- if f.full_name[-4..-1] == ".asc"
140
- say("Skipping old signature file #{f.full_name}")
141
- next
142
- end
143
-
144
- say("Signing #{f.full_name.inspect}...")
145
-
146
- file_contents = f.read()
147
-
148
- signed_gem.add_file(f.full_name, 0644) do |outfile|
149
- outfile.write(file_contents)
150
- end
151
-
152
- signed_gem.add_file(f.full_name + ".asc", 0644) do |outfile|
153
- outfile.write(Gem::OpenPGP.detach_sign(file_contents,key,homedir))
154
- end
155
-
156
- end
157
-
158
- signed_gem_file.close
159
- unsigned_gem_file.close
160
- File.delete unsigned_gem_file
161
-
162
- rescue Exception => ex
163
- if unsigned_gem_file
164
- FileUtils.mv unsigned_gem_file, gem
165
- end
166
-
167
- raise
168
- end
169
-
170
- def self.verify_gem gem, get_key=false, homedir=nil
171
-
172
- begin
173
- file = File.open(gem,"r")
174
- rescue Errno::ENOENT => ex
175
- raise Gem::CommandLineError, "Gem #{gem} not found. Note you can only verify local gems at this time, so you may need to run 'gem fetch #{gem}' before verifying."
176
- end
177
-
178
- tar_files = {}
179
-
180
- Gem::Package::TarReader.new(file).each do |f|
181
- tar_files[f.full_name] = f.read()
182
- end
183
-
184
- tar_files.keys.each do |file_name|
185
- next if file_name[-4..-1] == ".asc"
186
-
187
- sig_file_name = file_name + ".asc"
188
- if !tar_files.has_key? sig_file_name
189
- say add_color("WARNING!!! No sig found for #{file_name}", :red)
190
- raise Gem::OpenPGPException, "Can't verify without sig, aborting!!!"
191
- end
192
-
193
- begin
194
- err, res = Gem::OpenPGP.verify(file_name, tar_files[file_name], tar_files[sig_file_name], get_key, homedir)
195
-
196
- rescue Gem::OpenPGPException => ex
197
- color_code = "31"
198
- say add_color(ex.message, :red)
199
- raise
200
- end
201
- end
202
-
203
- ensure
204
- file.close unless file.nil?
205
- end
206
-
207
- private
208
-
209
- def self.did_gpg_error? gpg_results
210
- if gpg_results[:status] != 0
211
- say add_color("gpg returned unexpected status code", :red)
212
- say add_color(gpg_results[:stderr], :red)
213
- raise Gem::OpenPGPException, "gpg returned unexpected error code #{gpg_results[:status]}"
214
- end
215
- end
216
-
217
- # Tests to see if gpg is installed and available.
218
- def self.is_gpg_available
219
- err_msg = "Unable to find a working gnupg installation. Make sure gnupg is installed and you can call 'gpg --version' from a command prompt."
220
- `gpg --version`
221
- raise Gem::OpenPGPException, err_msg if $? != 0
222
- rescue Errno::ENOENT => ex
223
- raise Gem::OpenPGPException, err_msg if $? != 0
224
- end
225
-
226
- def self.is_key_valid key_id
227
- valid = /^0x[A-Za-z0-9]{8,8}/.match(key_id)
228
- if valid.nil?
229
- err_msg = "Invalid key id. Keys should be in form of 0xDEADBEEF"
230
- raise Gem::OpenPGPException, err_msg
231
- end
232
- end
233
-
234
- def self.is_homedir_valid homedir
235
- if !File.exists? homedir
236
- raise OpenPGPException, "Bad homedir #{homedir.inspect}"
237
- end
238
- end
239
-
240
- def self.run_gpg args, data=nil, &block
241
- exit_status = nil
242
- status_file = Tempfile.new("status")
243
-
244
- full_gpg_command = "gpg --status-file #{status_file.path} #{args}"
245
- gpg_results = Open3.popen3(full_gpg_command) do |stdin, stdout, stderr, wait_thr|
246
- stdin.write data if data
247
- stdin.close
248
- exit_status = wait_thr.value
249
- GPGStatusParser.parse(status_file, &block)
250
- out = stdout.read()
251
- err = stderr.read()
252
- {:status => exit_status, :stdout => out, :err => err}
253
- end
254
- gpg_results
255
- ensure
256
- status_file.close
257
- status_file.unlink
258
- end
259
-
260
- def self.create_tempfile data
261
- temp_file = Tempfile.new("rubygems_gpg")
262
- temp_file.binmode
263
- temp_file.write(data)
264
- temp_file.close
265
- temp_file
266
- end
267
-
268
- def self.add_color s, color=:green
269
- color_code = case color
270
- when :green then "32"
271
- when :red then "31"
272
- when :yellow then "33"
273
- else raise RuntimeError, "Invalid color #{color.inspect}"
274
- end
275
-
276
- #TODO - NO-OP on windows
277
- "\033[#{color_code}m#{s}\033[0m"
278
- end
279
-
280
- def self.options
281
- @options ||= {}
282
- @options
283
- end
284
- end
1
+ require 'rubygems/openpgp/signing'
2
+ require 'rubygems/openpgp/verification'
@@ -0,0 +1,24 @@
1
+ module Gem::OpenPGP
2
+ extend Gem::UserInteraction
3
+
4
+ private
5
+
6
+ def self.did_gpg_error? gpg_results
7
+ if gpg_results[:status] != 0
8
+ say add_color("gpg returned unexpected status code", :red)
9
+ say add_color(gpg_results[:stdout], :yellow)
10
+ say add_color(gpg_results[:stderr], :red)
11
+ raise Gem::OpenPGPException, "gpg returned unexpected error code #{gpg_results[:status]}"
12
+ end
13
+ end
14
+
15
+ # Tests to see if gpg is installed and available.
16
+ def self.is_gpg_available
17
+ err_msg = "Unable to find a working gnupg installation. Make sure gnupg is installed and you can call 'gpg --version' from a command prompt."
18
+ `gpg --version`
19
+ raise Gem::OpenPGPException, err_msg if $? != 0
20
+ rescue Errno::ENOENT => ex
21
+ raise Gem::OpenPGPException, err_msg if $? != 0
22
+ end
23
+
24
+ end
@@ -0,0 +1,74 @@
1
+ module Gem::OpenPGP
2
+ # Capture used signing keys so we can see if they changed.
3
+ module KeyMaster
4
+ SETTING_DIR = ".rubygems-openpgp"
5
+ KEYMASTER_FILE = "known_gems"
6
+
7
+ def self.full_setting_filename
8
+ home_dir = ENV["HOME"] || ENV["HOMEPATH"]
9
+ File.join(home_dir, SETTING_DIR, KEYMASTER_FILE)
10
+ end
11
+
12
+ def self.touch_keymaster_file
13
+ home_dir = ENV["HOME"] || ENV["HOMEPATH"]
14
+
15
+ setting_dir = File.join(home_dir, SETTING_DIR)
16
+ Dir.mkdir(setting_dir, 0700) if !File.directory? setting_dir
17
+
18
+ if !File.exists?(full_setting_filename)
19
+ File.open(full_setting_filename,"w").close
20
+ end
21
+ end
22
+
23
+ def self.load_fingerprints
24
+ touch_keymaster_file
25
+
26
+ home_dir = ENV["HOME"] || ENV["HOMEPATH"]
27
+
28
+ fingerprints = {}
29
+ File.open(full_setting_filename) do |f|
30
+ f.readlines.each do |line|
31
+ gem, fingerprint = line.strip.split("\t")
32
+ fingerprints[gem] = fingerprint
33
+ end
34
+ end
35
+
36
+ fingerprints
37
+ end
38
+
39
+ def self.save_fingerprints fingerprints
40
+ touch_keymaster_file
41
+
42
+ File.open(full_setting_filename,"w") do |f|
43
+ fingerprints.each_pair do |gem, fingerprint|
44
+ f.puts("#{gem}\t#{fingerprint}")
45
+ end
46
+ end
47
+ end
48
+
49
+ def self.get_fingerprint gem_name
50
+ load_fingerprints[gem_name]
51
+ end
52
+
53
+ def self.add_fingerprint gem_name, fingerprint
54
+ fingerprints = load_fingerprints
55
+ fingerprints[gem_name] = fingerprint
56
+ save_fingerprints fingerprints
57
+ end
58
+
59
+ # Check that an existing gem fingerprint matches a given fingerprint.
60
+ # In the case we haven't seen the gem before, we'll add the fingerprint
61
+ # and won't complain.
62
+ def self.check_fingerprint gem_name, fingerprint
63
+ fingerprints = load_fingerprints
64
+
65
+ if !fingerprints.has_key?(gem_name)
66
+ add_fingerprint gem_name, fingerprint
67
+ return true
68
+ end
69
+
70
+ fingerprints[gem_name] == fingerprint
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1 @@
1
+ class Gem::OpenPGPException < RuntimeError; end
@@ -0,0 +1,10 @@
1
+ module Gem::OpenPGP
2
+ private
3
+
4
+ # Store options we get from command line here so sign/verify code
5
+ # can get to it.
6
+ def self.options
7
+ @options ||= {}
8
+ @options
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems/command_manager'
2
+ require 'rubygems/gem_openpgp'
3
+
4
+ Gem::CommandManager.instance.register_command :sign
5
+
6
+ # gem build hooks
7
+ b = Gem::CommandManager.instance[:build]
8
+ b.add_option("--sign", "Sign gem with OpenPGP.") do |value, options|
9
+ Gem::OpenPGP.options[:sign] = true
10
+ end
11
+
12
+ b.add_option('--key KEY', "Specify key id if you don't want to use your default gpg key") do |key, options|
13
+ Gem::OpenPGP.options[:key] = key
14
+ end
15
+
16
+ class Gem::Commands::BuildCommand
17
+ alias_method :original_execute, :execute
18
+ def execute
19
+ original_execute
20
+
21
+ if Gem::OpenPGP.options[:sign]
22
+ gemspec = get_one_gem_name
23
+ if File.exist? gemspec then
24
+ spec = Gem::Specification.load gemspec
25
+ file_name = File.join(".", File.basename(spec.cache_file))
26
+ Gem::OpenPGP.sign_gem file_name, key=Gem::OpenPGP.options[:key]
27
+ end
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,109 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package'
3
+ require 'rubygems/user_interaction'
4
+ require 'gpg_status_parser'
5
+ require 'rubygems/openpgp/gpg_helpers'
6
+ require 'rubygems/openpgp/openpgpexception'
7
+ require 'shellwords'
8
+
9
+ module Gem::OpenPGP
10
+ extend Shellwords
11
+
12
+ # Given a string of data, generate and return a detached
13
+ # signature. By defualt, this will use your primary secret key.
14
+ # This can be overridden by specifying a key_id for another
15
+ # private key.
16
+ def self.detach_sign data, key_id=nil, homedir=nil
17
+ is_gpg_available
18
+ is_key_valid key_id if key_id
19
+ is_homedir_valid homedir if homedir
20
+
21
+ key_flag = ""
22
+ key_flag = "-u #{shellescape(key_id)}" if key_id
23
+
24
+ homedir_flag = ""
25
+ homedir_flag = "--homedir #{shellescape(homedir)}" if homedir
26
+
27
+ gpg_args = "#{key_flag} #{homedir_flag} --detach-sign --armor"
28
+ gpg_results = GPGStatusParser.run_gpg(gpg_args, data)
29
+ did_gpg_error? gpg_results
30
+
31
+ gpg_results[:stdout]
32
+ end
33
+
34
+ # Signs an existing gemfile by iterating the tar'ed up contents,
35
+ # and signing any contents. creating a new file with original contents
36
+ # and OpenPGP sigs. The OpenPGP sigs are saved as .asc files so they
37
+ # won't conflict with X509 sigs.
38
+ #
39
+ # Optional param "key" allows you to use a different private
40
+ # key than the GPG default.
41
+ def self.sign_gem gem, key=nil, homedir=nil
42
+ unsigned_gem = gem + ".unsigned"
43
+
44
+ begin
45
+ FileUtils.mv gem, unsigned_gem
46
+ rescue Errno::ENOENT => ex
47
+ raise Gem::CommandLineError, "The gem #{gem} does not seem to exist. (#{ex.message})"
48
+ end
49
+
50
+ unsigned_gem_file = File.open(unsigned_gem, "r")
51
+ signed_gem_file = File.open(gem, "w")
52
+
53
+ signed_gem = Gem::Package::TarWriter.new(signed_gem_file)
54
+
55
+ Gem::Package::TarReader.new(unsigned_gem_file).each do |f|
56
+
57
+ if f.full_name[-4..-1] == ".asc"
58
+ say("Skipping old OpenPGP signature file #{f.full_name}")
59
+ next
60
+ end
61
+
62
+ file_contents = f.read()
63
+
64
+ # Copy file no matter what
65
+ signed_gem.add_file(f.full_name, 0644) do |outfile|
66
+ outfile.write(file_contents)
67
+ end
68
+
69
+ # Only sign if it's really part of the gem and not
70
+ # a X.509 sig
71
+ if f.full_name[-3..-1] == ".gz"
72
+ say add_color("Signing #{f.full_name.inspect}...",:green)
73
+ signed_gem.add_file(f.full_name + ".asc", 0644) do |outfile|
74
+ outfile.write(Gem::OpenPGP.detach_sign(file_contents,key,homedir))
75
+ end
76
+ elsif f.full_name[-4..-1] != ".sig"
77
+ say add_color("Not signing #{f.full_name.inspect}. Didn't expect to see that...",:yellow)
78
+ end
79
+ end
80
+
81
+ signed_gem_file.close
82
+ unsigned_gem_file.close
83
+ File.delete unsigned_gem_file
84
+
85
+ rescue Exception => ex
86
+ if unsigned_gem_file
87
+ FileUtils.mv unsigned_gem_file, gem
88
+ end
89
+
90
+ raise
91
+ end
92
+
93
+ private
94
+
95
+ def self.is_key_valid key_id
96
+ valid = /^0x[A-Za-z0-9]{8,8}/.match(key_id)
97
+ if valid.nil?
98
+ err_msg = "Invalid key id. Keys should be in form of 0xDEADBEEF"
99
+ raise Gem::OpenPGPException, err_msg
100
+ end
101
+ end
102
+
103
+ def self.is_homedir_valid homedir
104
+ if !File.exists? homedir
105
+ raise OpenPGPException, "Bad homedir #{homedir.inspect}"
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,180 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package'
3
+ require 'rubygems/user_interaction'
4
+
5
+ require 'tempfile'
6
+ require 'rbconfig'
7
+
8
+ require 'gpg_status_parser'
9
+
10
+ require 'rubygems/openpgp/keymaster'
11
+ require 'rubygems/openpgp/options'
12
+ require 'rubygems/openpgp/gpg_helpers'
13
+ require 'rubygems/openpgp/openpgpexception'
14
+
15
+ module Gem::OpenPGP
16
+ extend Gem::UserInteraction
17
+
18
+ # Given a string containing data, and a string containing
19
+ # a detached signature, verify the data. If we can't verify
20
+ # then raise an exception.
21
+ #
22
+ # Optionally tell gpg to retrive the key if it's not provided
23
+ #
24
+ # returns the fingerprint used to sign the file
25
+ def self.verify file_name, data, sig, get_key=false, homedir=nil
26
+ is_gpg_available
27
+ is_homedir_valid homedir if homedir
28
+
29
+ data_file = create_tempfile data
30
+ sig_file = create_tempfile sig
31
+
32
+ get_key_params = "--keyserver pool.sks-keyservers.net --keyserver-options auto-key-retrieve"
33
+ get_key_params = "" if get_key != true
34
+
35
+ homedir_flags = ""
36
+ homedir_flags = "--homedir #{homedir}" if homedir
37
+
38
+ gpg_args = "#{get_key_params} #{homedir_flags} --verify #{sig_file.path} #{data_file.path}"
39
+
40
+ status_info = {:file_name => file_name}
41
+ gpg_results = GPGStatusParser.run_gpg(gpg_args) { |message| verify_extract_status_info(message, status_info) }
42
+
43
+ if status_info[:failure]
44
+ say add_color(status_info[:failure], :red)
45
+ raise Gem::OpenPGPException, "Fail!"
46
+ else
47
+ verify_check_sig status_info
48
+ end
49
+
50
+ did_gpg_error? gpg_results
51
+
52
+ status_info[:primary_key_fingerprint]
53
+ end
54
+
55
+ def self.verify_gem gem, get_key=false, homedir=nil
56
+ raise Gem::CommandLineError, "Gem #{gem} not found." if !File.exists?(gem)
57
+
58
+ gem_name = if Gem::VERSION[0..1] == "2." #gotta be a better way
59
+ Gem::Package.new(gem).spec.name
60
+ else
61
+ Gem::Format.from_file_by_path(gem).spec.name
62
+ end
63
+
64
+ say("Verifying #{gem_name}...")
65
+
66
+ file = File.open(gem,"r")
67
+
68
+ fingerprints = []
69
+ tar_files = {}
70
+
71
+ Gem::Package::TarReader.new(file).each do |f|
72
+ tar_files[f.full_name] = f.read()
73
+ end
74
+
75
+ tar_files.keys.each do |file_name|
76
+ next if [".asc",".sig"].include? file_name[-4..-1]
77
+
78
+ if file_name[-3..-1] != ".gz"
79
+ say add_color("Skipping #{file_name}. Only expected .gz files...", :yellow)
80
+ next
81
+ end
82
+
83
+ sig_file_name = file_name + ".asc"
84
+ if !tar_files.has_key? sig_file_name
85
+ say add_color("WARNING!!! No sig found for #{file_name}", :red)
86
+ raise Gem::OpenPGPException, "Can't verify without sig, aborting!!!"
87
+ end
88
+
89
+ begin
90
+ fingerprints << Gem::OpenPGP.verify(file_name, tar_files[file_name], tar_files[sig_file_name], get_key, homedir)
91
+ rescue Gem::OpenPGPException => ex
92
+ color_code = "31"
93
+ say add_color(ex.message, :red)
94
+ raise
95
+ end
96
+ end
97
+
98
+ # Verify fingerprint
99
+ fingerprints.uniq.each do |fp|
100
+ verify_gem_check_fingerprint gem_name, fp
101
+ end
102
+
103
+ ensure
104
+ file.close unless file.nil?
105
+ end
106
+
107
+ private
108
+
109
+ # Extract the info we care about, throw away the rest
110
+ def self.verify_extract_status_info message, status_info
111
+ case message.status
112
+ when :GOODSIG, :BADSIG, :ERRSIG
113
+ status_info[:good_or_bad] = message.status
114
+ status_info[:uid] = (message.args[:username] || "").strip
115
+
116
+ when :SIG_ID
117
+ when :VALIDSIG, :EXPSIG, :BADSIG
118
+ status_info[:sig_status] = message.status
119
+ status_info[:primary_key] = "0x#{message.args[:primary_key_fpr][-9..-1]}"
120
+ status_info[:primary_key_fingerprint] = message.args[:primary_key_fpr]
121
+ when :TRUST_UNDEFINED, :TRUST_NEVER, :TRUST_MARGINAL, :TRUST_FULLY, :TRUST_ULTIMATE
122
+ status_info[:trust_status] = message.status
123
+ when :NO_PUBKEY
124
+ status_info[:failure] = "You don't have the public key. Use --get-key to automagically retrieve from keyservers"
125
+ when :IMPORTED, :IMPORT_OK, :IMPORT_RES
126
+ #silently_ignore
127
+ when :KEYEXPIRED, :SIGEXPIRED
128
+ # recalculating trust db, ignore.
129
+ else
130
+ puts "unexpected message: #{message.status} #{message.args.inspect}"
131
+ end
132
+ end
133
+
134
+ # Print info about the sig, check that we like it, and possibly abort
135
+ def self.verify_check_sig status_info
136
+ sig_msg = "Signature for #{status_info[:file_name]} from user #{status_info[:uid]} key #{status_info[:primary_key]} is #{status_info[:good_or_bad]}, #{status_info[:sig_status]} and #{status_info[:trust_status]}"
137
+ if status_info[:trust_status] == :TRUST_NEVER
138
+ say add_color(sig_msg, :red)
139
+ raise Gem::OpenPGPException, "Never Trusted. Won't install."
140
+ elsif status_info[:trust_status] == :TRUST_UNDEFINED
141
+ say add_color(sig_msg, :yellow)
142
+ if options[:trust] && !options[:no_trust]
143
+ raise Gem::OpenPGPException, "Trust Undefined and you've specified --trust. Won't install."
144
+ end
145
+ else
146
+ say add_color(sig_msg , :green)
147
+ end
148
+ end
149
+
150
+ def self.verify_gem_check_fingerprint gem_name, fingerprint
151
+ if !Gem::OpenPGP::KeyMaster.check_fingerprint(gem_name, fingerprint)
152
+ raise Gem::OpenPGPException, "Gem #{gem_name} fingerprint #{fingerprint} didn't match fingerprint in #{Gem::OpenPGP::KeyMaster.full_setting_filename}. Won't install!"
153
+ end
154
+ end
155
+
156
+ def self.create_tempfile data
157
+ temp_file = Tempfile.new("rubygems_gpg")
158
+ temp_file.binmode
159
+ temp_file.write(data)
160
+ temp_file.close
161
+ temp_file
162
+ end
163
+
164
+ def self.add_color s, color=:green
165
+ color_code = case color
166
+ when :green then "32"
167
+ when :red then "31"
168
+ when :yellow then "33"
169
+ else raise RuntimeError, "Invalid color #{color.inspect}"
170
+ end
171
+
172
+ if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw/)
173
+ s # no colors on windows
174
+ else
175
+ "\033[#{color_code}m#{s}\033[0m"
176
+ end
177
+
178
+ end
179
+
180
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems/command_manager'
2
+ require 'rubygems/gem_openpgp'
3
+
4
+ Gem::CommandManager.instance.register_command :verify
5
+
6
+ # gem install hooks
7
+ i = Gem::CommandManager.instance[:install]
8
+ i.add_option("--verify",
9
+ 'Verifies a local gem that has been signed via OpenPGP.' +
10
+ 'This helps to ensure the gem has not been tampered with in transit.') do |value, options|
11
+ Gem::OpenPGP.options[:verify] = true
12
+ end
13
+
14
+ i.add_option("--no-verify",
15
+ "Don't verify a gem, even if --verify has previously been specified") do |value, options|
16
+ Gem::OpenPGP.options[:no_verify] = true
17
+ end
18
+
19
+ i = Gem::CommandManager.instance[:install]
20
+ i.add_option("--trust",
21
+ 'Enforce gnupg trust settings. Only install if trusted.') do |value, options|
22
+ Gem::OpenPGP.options[:trust] = true
23
+ end
24
+
25
+ i.add_option("--no-trust",
26
+ "Ignoure gnupg trust settings, even if --trust has previously been specified") do |value, options|
27
+ Gem::OpenPGP.options[:no_trust] = true
28
+ end
29
+
30
+ i.add_option('--get-key', "If the key is not available, download it from a keyserver") do |key, options|
31
+ Gem::OpenPGP.options[:get_key] = true
32
+ end
33
+
34
+ Gem.pre_install do |installer|
35
+ begin
36
+ # --no-verify overrides --verify
37
+ if Gem::OpenPGP.options[:verify] && !Gem::OpenPGP.options[:no_verify]
38
+ Gem::OpenPGP.verify_gem(installer.gem,
39
+ Gem::OpenPGP.options[:get_key])
40
+ end
41
+ rescue Gem::OpenPGPException => ex
42
+ installer.alert_error(ex.message)
43
+ installer.terminate_interaction(1)
44
+ end
45
+ end
@@ -1,72 +1,4 @@
1
- require 'rubygems/command_manager'
2
1
  require 'rubygems/gem_openpgp'
2
+ require 'rubygems/openpgp/sign_plugins'
3
+ require 'rubygems/openpgp/verify_plugins'
3
4
 
4
- Gem::CommandManager.instance.register_command :sign
5
- Gem::CommandManager.instance.register_command :verify
6
-
7
- # gem build hooks
8
- b = Gem::CommandManager.instance[:build]
9
- b.add_option("--sign", "Sign gem with OpenPGP.") do |value, options|
10
- Gem::OpenPGP.options[:sign] = true
11
- end
12
-
13
- b.add_option('--key KEY', "Specify key id if you don't want to use your default gpg key") do |key, options|
14
- Gem::OpenPGP.options[:key] = key
15
- end
16
-
17
- class Gem::Commands::BuildCommand
18
- alias_method :original_execute, :execute
19
- def execute
20
- original_execute
21
-
22
- if Gem::OpenPGP.options[:sign]
23
- gemspec = get_one_gem_name
24
- if File.exist? gemspec then
25
- spec = Gem::Specification.load gemspec
26
- file_name = File.join(".", File.basename(spec.cache_file))
27
- Gem::OpenPGP.sign_gem file_name, key=Gem::OpenPGP.options[:key]
28
- end
29
- end
30
- end
31
- end
32
-
33
- # gem install hooks
34
- i = Gem::CommandManager.instance[:install]
35
- i.add_option("--verify",
36
- 'Verifies a local gem that has been signed via OpenPGP.' +
37
- 'This helps to ensure the gem has not been tampered with in transit.') do |value, options|
38
- Gem::OpenPGP.options[:verify] = true
39
- end
40
-
41
- i.add_option("--no-verify",
42
- "Don't verify a gem, even if --verify has previously been specified") do |value, options|
43
- Gem::OpenPGP.options[:no_verify] = true
44
- end
45
-
46
- i = Gem::CommandManager.instance[:install]
47
- i.add_option("--trust",
48
- 'Enforce gnupg trust settings. Only install if trusted.') do |value, options|
49
- Gem::OpenPGP.options[:trust] = true
50
- end
51
-
52
- i.add_option("--no-trust",
53
- "Ignoure gnupg trust settings, even if --trust has previously been specified") do |value, options|
54
- Gem::OpenPGP.options[:no_trust] = true
55
- end
56
-
57
- i.add_option('--get-key', "If the key is not available, download it from a keyserver") do |key, options|
58
- Gem::OpenPGP.options[:get_key] = true
59
- end
60
-
61
- Gem.pre_install do |installer|
62
- begin
63
- # --no-verify overrides --verify
64
- if Gem::OpenPGP.options[:verify] && !Gem::OpenPGP.options[:no_verify]
65
- Gem::OpenPGP.verify_gem(installer.gem,
66
- Gem::OpenPGP.options[:get_key])
67
- end
68
- rescue Gem::OpenPGPException => ex
69
- installer.alert_error(ex.message)
70
- installer.terminate_interaction(1)
71
- end
72
- end
@@ -0,0 +1,31 @@
1
+ require 'test/unit'
2
+ require 'mocha/setup'
3
+
4
+ require 'rubygems/openpgp/keymaster'
5
+
6
+ class KeymasterTest < Test::Unit::TestCase
7
+ def setup
8
+ Gem::OpenPGP::KeyMaster.stubs(:load_fingerprints => {"foo" => "DEADBEEF"},
9
+ :save_fingerprints => nil)
10
+ end
11
+
12
+
13
+ def test_good_fingerprint
14
+ assert_block("Good fingerprint test") do
15
+ Gem::OpenPGP::KeyMaster.check_fingerprint("foo", "DEADBEEF") == true
16
+ end
17
+ end
18
+
19
+ def test_bad_fingerprint
20
+ assert_block("Bad fingerprint test") do
21
+ Gem::OpenPGP::KeyMaster.check_fingerprint("foo", "C00FFEE") != true
22
+ end
23
+ end
24
+
25
+ def test_new_gem
26
+ assert_block("New gem test") do
27
+ Gem::OpenPGP::KeyMaster.check_fingerprint("bar", "C00FFEE") == true
28
+ end
29
+ end
30
+
31
+ end
@@ -1,4 +1,6 @@
1
1
  require 'test/unit'
2
+ require 'mocha/setup'
3
+
2
4
  require 'rubygems_plugin'
3
5
  require 'rubygems/gem_openpgp'
4
6
  require 'tmpdir'
@@ -19,6 +21,8 @@ class RubygemsPluginTest < Test::Unit::TestCase
19
21
  end
20
22
 
21
23
  def test_gem_sign_and_verify
24
+ Gem::OpenPGP.stubs(:verify_gem_check_fingerprint => true)
25
+
22
26
  in_tmp_gpg_homedir do |gpg_home|
23
27
  assert_raise Gem::OpenPGPException do
24
28
  Gem::OpenPGP.verify_gem UNSIGNED_GEM, false, gpg_home
@@ -41,12 +45,12 @@ class RubygemsPluginTest < Test::Unit::TestCase
41
45
 
42
46
  sig = Gem::OpenPGP.detach_sign data, key_id=nil, homedir=gpg_home
43
47
  assert_nothing_raised do
44
- Gem::OpenPGP.verify data, sig, false, homedir=gpg_home
48
+ Gem::OpenPGP.verify "<file>", data, sig, false, homedir=gpg_home
45
49
  end
46
50
 
47
51
  # BAD SIG
48
52
  assert_raise(Gem::OpenPGPException) do
49
- Gem::OpenPGP.verify data + "\n", sig, false, homedir=gpg_home
53
+ Gem::OpenPGP.verify "<file>", data + "\n", sig, false, homedir=gpg_home
50
54
  end
51
55
  end
52
56
  end
metadata CHANGED
@@ -1,76 +1,96 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rubygems-openpgp
3
- version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.4.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
6
5
  platform: ruby
7
- authors:
6
+ authors:
8
7
  - Grant Olson
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
-
13
- date: 2013-02-23 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
11
+ date: 2013-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
16
14
  name: gpg_status_parser
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
19
- none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: 0.3.0
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.0
24
20
  type: :runtime
25
- version_requirements: *id001
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: mocha
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.13.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.13.2
26
41
  description: Digitally sign gems via OpenPGP.
27
42
  email: kgo@grant-olson.net
28
43
  executables: []
29
-
30
44
  extensions: []
31
-
32
- extra_rdoc_files:
45
+ extra_rdoc_files:
33
46
  - README.md
34
- files:
47
+ files:
35
48
  - LICENSE
36
49
  - Rakefile
37
50
  - lib/rubygems_plugin.rb
38
51
  - lib/rubygems/commands/verify_command.rb
39
52
  - lib/rubygems/commands/sign_command.rb
40
53
  - lib/rubygems/gem_openpgp.rb
54
+ - lib/rubygems/openpgp/gpg_helpers.rb
55
+ - lib/rubygems/openpgp/options.rb
56
+ - lib/rubygems/openpgp/signing.rb
57
+ - lib/rubygems/openpgp/verification.rb
58
+ - lib/rubygems/openpgp/keymaster.rb
59
+ - lib/rubygems/openpgp/verify_plugins.rb
60
+ - lib/rubygems/openpgp/sign_plugins.rb
61
+ - lib/rubygems/openpgp/openpgpexception.rb
41
62
  - README.md
63
+ - test/test_keymaster.rb
42
64
  - test/test_rubygems-openpgp.rb
43
65
  - test/pablo_escobar_seckey.asc
44
66
  - test/pablo_escobar_pubkey.asc
45
67
  - test/unsigned_hola-0.0.0.gem
46
- homepage: https://github.com/grant-olson/rubygems-openpgp
47
- licenses:
68
+ homepage: https://rubygems-openpgp-ca.org
69
+ licenses:
48
70
  - BSD 3 Clause
71
+ metadata: {}
49
72
  post_install_message:
50
73
  rdoc_options: []
51
-
52
- require_paths:
74
+ require_paths:
53
75
  - lib
54
- required_ruby_version: !ruby/object:Gem::Requirement
55
- none: false
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: "0"
60
- required_rubygems_version: !ruby/object:Gem::Requirement
61
- none: false
62
- requirements:
63
- - - ">="
64
- - !ruby/object:Gem::Version
65
- version: "0"
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
66
86
  requirements: []
67
-
68
87
  rubyforge_project:
69
- rubygems_version: 1.8.24
88
+ rubygems_version: 2.0.0.rc.2
70
89
  signing_key:
71
- specification_version: 3
90
+ specification_version: 4
72
91
  summary: Sign gems via OpenPGP
73
- test_files:
92
+ test_files:
93
+ - test/test_keymaster.rb
74
94
  - test/test_rubygems-openpgp.rb
75
95
  - test/pablo_escobar_seckey.asc
76
96
  - test/pablo_escobar_pubkey.asc
metadata.gz.asc CHANGED
@@ -1,11 +1,11 @@
1
1
  -----BEGIN PGP SIGNATURE-----
2
- Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
2
+ Version: GnuPG v1.4.11 (GNU/Linux)
3
3
 
4
- iQEcBAABCgAGBQJRKNOhAAoJEP5F5V2hilTWcBgIAIob5FD4SK6UxfnXQk0U+5nP
5
- huK6hsanhLbH3m3fqz2BkB/WBVonF0sqO2zNIw+qxnIl/r426iXmxTfNYF3F+0Ri
6
- mP7YF+HBNfuElraRBBKUXSCQoB0yxqLpx3mcDwjB//0Q9CWXdeCLzefbs7+4ca4q
7
- rBezyxSxRXun2qBMBs7FSZYkC6rsP58EgTi555WfxymiqEZ9fEi1r4rEM37F+z2w
8
- FBMlYjTJhATOIhHTi8nFQoGgjFcQG9a7cgABHGZwB9IR6k5O6IeTHVXCQvOlg4au
9
- cj6HKS8YKpJGZPlPIJVpLmW9UhUQukg4AwTpPAHIeIiwZYahitQtm0Ra5MnSJtc=
10
- =bh2W
4
+ iQEcBAABAgAGBQJRPJV0AAoJEP5F5V2hilTWUlgH/34nV018I/aZnDLYCQyFqJSJ
5
+ Ek96fj//cmCbbN55YG8ptgB4NyZBBsc0Nvhe7CEsKFbcZJoutqoPXKeE0A3Qe9lp
6
+ gnogg7mwcm5ndn2dxBH1fd2it0fS3vG09i5r7MMqsE5LzaLJ6OcmYWmNnV2HiPTr
7
+ LQeqyC4hFyweIYpgO8sgdCDjrAaWDTBfoHmvHZ8xHZRZypJP45EdQ/+brp+DZmKu
8
+ K5E7LpV9VOFFobb91a4RYuzqulSK5tQELSXkh2IcDPDyEu0FZl7iP3S6N6xuDUQc
9
+ Zvtg1UpyMWs31DlCeivJhmQbFBbaVgjcqyHrEXnMWAIRq4XQxA7sZcxgg+K9j7k=
10
+ =1hJH
11
11
  -----END PGP SIGNATURE-----