simple-gnupg-keyserver 1.0.0

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 (6) hide show
  1. data/.gitignore +6 -0
  2. data/History.txt +7 -0
  3. data/README.rdoc +129 -0
  4. data/Rakefile +39 -0
  5. data/lib/simpleHKP.rb +504 -0
  6. metadata +94 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ Manifest.txt
2
+ *.gem
3
+ /pkg/
4
+ /tmp/
5
+ /rdoc/
6
+
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ === 1.0.0 / 2015-03-08
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
7
+
data/README.rdoc ADDED
@@ -0,0 +1,129 @@
1
+ = rGem-simple-gnupg-keyserver
2
+
3
+ home :: https://github.com/stephengaito/rGem-simple-gnupg-keyserver
4
+
5
+ == DESCRIPTION:
6
+
7
+ SimpleHKP is a simple Ruby/rack based GnuPG HKP key server.
8
+
9
+ Its sole task is to supply a limited number of GnuPG _public_ keys
10
+ to/from MonkeySphere on a limited number of machines/servers. It is
11
+ _not_ meant to server hundreds of keys, so it is not meant as a truely
12
+ public key server.
13
+
14
+ If you need to run your own _large_ scale production Key Sever then use
15
+ {SKS}[https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home]. You
16
+ might want to read Paul Bauer's {How To Setup A Free PGP Key Server in
17
+ Ubuntu}[http://www.bauer-power.net/2010/05/how-to-setup-free-pgp-key-server-in.html].
18
+
19
+ === Key storage
20
+
21
+ Since this is a _simple_ key server, all keys are stored as flat
22
+ ASCII armored files in the "keys" directory.
23
+
24
+ === Key search
25
+
26
+ For each uploaded key, the key's "gpg2 --with-fingerprint --with-colon"
27
+ output (key data) is stored in an internal ruby Hash using the key's
28
+ file name as hash key. All searches become Ruby regular expressions
29
+ which are matched against each key's key data.
30
+
31
+ === Security:
32
+
33
+ _ALL_ uploaded key data is passed through the external gpg2 command.
34
+ Any security weaknesses of gpg2 are weaknesses of this SimpleHKP rack
35
+ application.
36
+
37
+ This rack application is written in Ruby, so any security weaknesses of
38
+ Ruby and/or the Ruby web-server you are using are also weaknesses of
39
+ this SimpleHKP rack application.
40
+
41
+ By default all files associated with the normal running of the
42
+ SimpleHKP rack application are stored in the "simpleHKP" directory of
43
+ the current working directory. This "simpleHKP" directory _SHOULD_
44
+ _NOT_ be accessible from any other web-server (such as NGinx).
45
+
46
+ This rack application is intentionally as self contained and simple as
47
+ possible. If you have any security concerns, this rack application is
48
+ easily readable.
49
+
50
+ == SYNOPSIS:
51
+
52
+ A typical rackup.ru file might be:
53
+
54
+ require 'simpleHKP'
55
+
56
+ simpleHKPoptions = {
57
+ ... some options ...
58
+ }
59
+
60
+ run SimpleHKP.new(simpleHKPoptions)
61
+
62
+ Your webserver should be configured to bind to ports 11371 (for use as
63
+ an HKP keyserver) and 80 (for use by standard browsers).
64
+
65
+ Options can be set by creating a SimpleHKP instance with a hash of
66
+ key/value pairs.
67
+
68
+ The current default options are:
69
+
70
+ defaultOptions = {
71
+ 'debug' => false, # should debug output to logged?
72
+ 'simpleHKPdir' => 'simpleHKP', # base disk path to simpleHKP disk space
73
+ 'keyDir' => 'keys', # subdir to key storage directory
74
+ 'mediaDir' => 'media', # subdir to any css, js, images etc
75
+ 'htmlDir' => 'html', # subdir to html partials
76
+ 'mimeMap' => { # a file ext to mime mapping
77
+ 'css' => 'text/css',
78
+ 'html' => 'text/html',
79
+ 'js' => 'text/javascript'
80
+ }
81
+ }
82
+
83
+ The following HTML partials can be used to over-ride the web pages for
84
+ the use of humans:
85
+
86
+ * header.html
87
+ * defaultBody.html
88
+ * lookupForm.html
89
+ * uploadForm.html
90
+ * footer.html
91
+
92
+ == REQUIREMENTS:
93
+
94
+ There are explicitly no external Ruby requirements other than Ruby and
95
+ a Ruby webserver (such as puma)
96
+
97
+ The webserver *requires* GnuPG2 installed.
98
+
99
+ == INSTALL:
100
+
101
+ To install the simple-gnupg-keyserver gem:
102
+
103
+ $ gem install simple-gnupg-keyserver
104
+
105
+ == LICENSE:
106
+
107
+ (The MIT License)
108
+
109
+ Copyright (c) 2015 Stephen Gaito
110
+
111
+ Permission is hereby granted, free of charge, to any person obtaining a
112
+ copy of this software and associated documentation files (the
113
+ 'Software'), to deal in the Software without restriction, including
114
+ without limitation the rights to use, copy, modify, merge, publish,
115
+ distribute, sublicense, and/or sell copies of the Software, and to
116
+ permit persons to whom the Software is furnished to do so, subject to
117
+ the following conditions:
118
+
119
+ The above copyright notice and this permission notice shall be included
120
+ in all copies or substantial portions of the Software.
121
+
122
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
123
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
124
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
125
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
126
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
127
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
128
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
129
+
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # -*- ruby -*-
2
+
3
+ # To release this ruby gem type:
4
+ # rake release VERSION=x.y.z
5
+ # where x.y.z is the appropriate version number of this gem.
6
+
7
+ require 'rubygems'
8
+ require 'hoe'
9
+
10
+ # Hoe.plugin :compiler
11
+ # Hoe.plugin :gem_prelude_sucks
12
+ # Hoe.plugin :inline
13
+ # Hoe.plugin :minitest
14
+ # Hoe.plugin :racc
15
+ # Hoe.plugin :rubyforge
16
+
17
+ # generate the Manifest.txt file (before we invoke Hoe.spec)
18
+ manifest = FileList[
19
+ '.gitignore',
20
+ 'History.*',
21
+ 'README.*',
22
+ 'Rakefile',
23
+ 'lib/**/*.rb',
24
+ 'test/**/*.rb',
25
+ ];
26
+ File.open('Manifest.txt', 'w') do | manifestFile |
27
+ manifestFile.puts("Manifest.txt");
28
+ manifestFile.write(manifest.to_a.join("\n"));
29
+ end
30
+
31
+ # For dependency documentation see:
32
+ # http://guides.rubygems.org/patterns/
33
+
34
+ Hoe.spec 'simple-gnupg-keyserver' do
35
+ developer('Stephen Gaito', 'stephen@perceptisys.co.uk')
36
+ license 'MIT'
37
+ end
38
+
39
+ # vim: syntax=ruby
data/lib/simpleHKP.rb ADDED
@@ -0,0 +1,504 @@
1
+ require 'uri'
2
+ require 'pp'
3
+ #require 'yaml'
4
+ require 'fileutils'
5
+
6
+ # This code was inspired by:
7
+ # Sebi2020's Informatikonline Easy-HKP
8
+ # https://github.com/Sebi2020/easy-hkp
9
+
10
+ # It conforms to:
11
+ # The OpenPGP HTTP Keyserver Protocol (HKP)
12
+ # draft-shaw-openpgp-hkp-00.txt
13
+ # http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
14
+
15
+ # See also the doc/DETAILS file in the gnupg2 source code
16
+
17
+ # Copyright (C) 2015 Stephen Gaito
18
+ #
19
+ # (The MIT License)
20
+ #
21
+ # Copyright (c) 2015 Stephen Gaito
22
+ #
23
+ # Permission is hereby granted, free of charge, to any person obtaining a
24
+ # copy of this software and associated documentation files (the
25
+ # 'Software'), to deal in the Software without restriction, including
26
+ # without limitation the rights to use, copy, modify, merge, publish,
27
+ # distribute, sublicense, and/or sell copies of the Software, and to
28
+ # permit persons to whom the Software is furnished to do so, subject to
29
+ # the following conditions:
30
+ #
31
+ # The above copyright notice and this permission notice shall be included
32
+ # in all copies or substantial portions of the Software.
33
+ #
34
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
35
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
37
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
38
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
39
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
40
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
+
42
+ class SimpleHKP
43
+
44
+ VERSION = "1.0.0"
45
+
46
+ def loadKeys
47
+ @keyLookupData = Hash.new
48
+ puts "SimpleHKP: loading keys" if @debug
49
+ Dir.glob(@keyDir+'/**/*.asc') do | aFile |
50
+ puts aFile if @debug
51
+ @keyLookupData[aFile] = `gpg2 --with-fingerprint --with-colons #{aFile}`
52
+ puts @keyLookupData[aFile] if @debug
53
+ end
54
+ puts "SimpleHKP: finished loading keys" if @debug
55
+ end
56
+
57
+ def loadHtml
58
+ puts "SimpleHKP: loading html partials" if @debug
59
+ Dir.chdir(@htmlDir) do
60
+ @headerHtml = "<html><head></head><body class=\"simpleHKP-body\">\n"
61
+ @headerHtml << "<p><a href=\"/\">SimpleHKP</a></p>"
62
+ @headerHtml = File.open('header.html','r').read if
63
+ File.exists?('header.html')
64
+ puts @headerHtml if @debug
65
+
66
+ @defaultBody = ""
67
+ @defaultBody << "<h1 class=\"simpleHKP-welcome\">Welcome to SimpleHKP</h1>\n"
68
+ @defaultBody << "<ul class=\"simpleHKP-tasksList\">\n"
69
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"lookup?op=form\">Search</a></li>\n"
70
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"add\">Upload new key</a></li>\n"
71
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"reload\">Reload existing keys</a></li>\n"
72
+ @defaultBody << "</ul>\n"
73
+ @defaultBody = File.open('defaultBody.html','r').read if
74
+ File.exists?('defaultBody.html')
75
+ puts @defaultBody if @debug
76
+
77
+ @lookupForm = ""
78
+ @lookupForm << "<div class=\"simpleHKP-lookupFormDiv\">\n"
79
+ @lookupForm << " <h1 class=\"simpleHKP-lookupFormTitle\">Search GnuPG keys</h1>\n"
80
+ @lookupForm << " <form action=\"lookup\" method=\"get\" id=\"simpleHKP-lookupForm\">\n"
81
+ @lookupForm << " <input type=\"text\" name=\"search\" size=\"80\" />\n"
82
+ @lookupForm << " <input type=\"hidden\" name=\"op\" value=\"index\" />\n"
83
+ @lookupForm << " <input type=\"submit\" value=\"Search\" />\n"
84
+ @lookupForm << " </form>\n"
85
+ @lookupForm << "</div>\n"
86
+ @lookupForm = File.open('lookupForm.html','r').read if
87
+ File.exists?('lookupForm.html')
88
+ puts @lookupForm if @debug
89
+
90
+ @uploadForm = ""
91
+ @uploadForm << "<div class=\"simpleHKP-uploadFormDiv\">\n"
92
+ @uploadForm << " <h1 class=\"simpleHKP-uploadFormTitle\">Paste GnuPG key to be uploaded below</h1>\n"
93
+ @uploadForm << " <form action=\"add\" method=\"post\" id=\"simpleHKP-uploadForm\">\n"
94
+ @uploadForm << " <textarea name=\"keytext\" form=\"simpleHKP-uploadForm\" rows = \"30\" cols=\"70\" ></textarea>\n"
95
+ @uploadForm << " <input type=\"submit\" value=\"Upload\" />\n"
96
+ @uploadForm << " </form>\n"
97
+ @uploadForm << "</div>\n"
98
+ @uploadForm = File.open('uploadForm.html','r').read if
99
+ File.exists?('uploadForm.html')
100
+ puts @uploadForm if @debug
101
+
102
+ @footer = "</body></html>\n"
103
+ @footer = File.open('footer.html','r').read if
104
+ File.exists?('footer.html')
105
+ puts @footer if @debug
106
+ end
107
+ puts "SimpleHKP: finished loading html partials" if @debug
108
+ end
109
+
110
+ def initialize(options = {})
111
+ #
112
+ # setup the default options
113
+ #
114
+ defaultOptions = {
115
+ 'debug' => false,
116
+ 'simpleHKPdir' => 'simpleHKP',
117
+ 'keyDir' => 'keys',
118
+ 'mediaDir' => 'media',
119
+ 'htmlDir' => 'html',
120
+ 'mimeMap' => {
121
+ 'css' => 'text/css',
122
+ 'html' => 'text/html',
123
+ 'js' => 'text/javascript'
124
+ }
125
+ }
126
+ #
127
+ # merge the options mimeMap into the default options mimeMap
128
+ #
129
+ defaultOptions['mimeMap'].merge!(delete(options['mimeMap'])) if
130
+ options.has_key?('mimeMap')
131
+ #
132
+ # merge in the rest of the options into the default options
133
+ #
134
+ @options = defaultOptions.merge(options)
135
+ #
136
+ # setup the required variables
137
+ #
138
+ @debug = @options['debug']
139
+ @baseDir = @options['simpleHKPdir']
140
+ @keyDir = @baseDir+'/'+@options['keyDir']
141
+ @mediaDir = @baseDir+'/'+@options['mediaDir']
142
+ @htmlDir = @baseDir+'/'+@options['htmlDir']
143
+ @mimeMap = @options['mimeMap']
144
+ if @debug then
145
+ puts "SimpleHKP options:"
146
+ pp @options
147
+ end
148
+ #
149
+ # ensure the required directories all exist
150
+ #
151
+ FileUtils.mkdir_p(@keyDir)
152
+ FileUtils.mkdir_p(@mediaDir)
153
+ FileUtils.mkdir_p(@htmlDir)
154
+ #
155
+ # load the existing keys and the html partials
156
+ loadKeys
157
+ loadHtml
158
+ end
159
+
160
+ def ppEnv(env)
161
+ Dir.pwd+' '+
162
+ # '['+URI.decode_www_form(env["rack.input"].rewind.read).pretty_inspect+']'+
163
+ env.pretty_inspect
164
+ end
165
+
166
+ def replyInternalError(env, exception)
167
+ @statusCode = 500
168
+ @body << @headerHtml
169
+ @body << "<p>Internal server error!</p>\n"
170
+ @body << "<pre>#{exception}</pre>\n" if @debug
171
+ @body << @footer
172
+ if @debug then
173
+ puts "\n\n-------------------------------------------------------------"
174
+ puts exception
175
+ puts "-------------------------------------------------------------"
176
+ puts ppEnv(env)
177
+ puts "-------------------------------------------------------------\n\n"
178
+ end
179
+ end
180
+
181
+ def replyNotImplemented
182
+ @statusCode = 501
183
+ @body << @headerHtml
184
+ @body << '<p>Not implemented!<p>'
185
+ @body << @footer
186
+ end
187
+
188
+ def replyBadRequest(message)
189
+ @statusCode = 400
190
+ @body << @headerHtml
191
+ @body << '<p>Bad request!</p>'
192
+ @body << "<p>#{message}</p>"
193
+ @body << @footer
194
+ end
195
+
196
+ def replyNotFound
197
+ @statusCode = 404
198
+ @body << @headerHtml
199
+ @body << '<p>Not found!</p>'
200
+ @body << @footer
201
+ end
202
+
203
+ def replyDefaultBody
204
+ @body << @headerHtml
205
+ @body << @defaultBody
206
+ @body << @footer
207
+ end
208
+
209
+ def replyLookupForm
210
+ @body << @headerHtml
211
+ @body << @lookupForm
212
+ @body << @footer
213
+ end
214
+
215
+ def replyUploadForm
216
+ @body << @headerHtml
217
+ @body << @uploadForm
218
+ @body << @footer
219
+ end
220
+
221
+ def replyFile(env)
222
+ fileName = env['REQUEST_PATH'].sub(/^.*\/media\//,'')
223
+ if File.exists?(fileName) then
224
+ fileExt = File.extname(fileName).sub(/^\./,'')
225
+ @header['Content-Type'] = @mimeMap[fileExt] if
226
+ @mimeMap.has_key?(fileExt)
227
+ @body << File.open(fileName,'r').read
228
+ end
229
+ end
230
+
231
+ def storeKey(env)
232
+ #
233
+ # decode the post data
234
+ #
235
+ keys = URI.decode_www_form(env['rack.input'].read)
236
+ #
237
+ # ensure we are being sent a key
238
+ #
239
+ return replyBadRequest("No keytext field in post data") unless
240
+ keys[0][0] =~ /keytext/
241
+ pp keys[0][1] if @debug
242
+ #
243
+ # send the key data through gpg2 to extract the keyID
244
+ #
245
+ gpgKeyData = ""
246
+ IO.popen('gpg2 --with-fingerprint --with-colons -', 'r+') do | pipe |
247
+ puts 'gpg2 --with-fingerprint --with-colons -' if @debug
248
+ pipe.write(keys[0][1])
249
+ pipe.close_write
250
+ gpgKeyData = pipe.read
251
+ end
252
+ puts gpgKeyData if @debug
253
+ #
254
+ # look for the keyID of the public key
255
+ #
256
+ keyID = nil
257
+ gpgKeyData.each_line do | aLine |
258
+ keyData = aLine.split(/:/)
259
+ next unless keyData[0] =~ /pub/
260
+ keyID = keyData[4]
261
+ break
262
+ end
263
+ return replyBadRequest("No keyID found in gpg2 result using uploaded keytext data") if keyID.nil?
264
+ #
265
+ # record the fact that we have stored this key for later lookup
266
+ #
267
+ keyFile = "#{@keyDir}/#{keyID}.asc"
268
+ @keyLookupData[keyFile] = gpgKeyData
269
+ pp @keyLookupData if @debug
270
+ #
271
+ # store this key as a flat file with the name of the keyID.asc
272
+ #
273
+ File.open(keyFile,'w') do | keyFile |
274
+ keyFile.write(keys[0][1])
275
+ end
276
+ #
277
+ # return OK
278
+ #
279
+ @statusCode = 200
280
+ @body << @headerHtml
281
+ @body << "<h1 class=\"simpleHKP-storedKey\">Stored key: [#{keyID}]</h1>"
282
+ @body << "<h2 class=\"simpleHKP-keyDataKeyID\">Key data for key: [#{keyID}]</h2>"
283
+ @body << '<pre class="simpleHKP-keyData">'+gpgKeyData+"</pre>\n"
284
+ @body << @footer
285
+ end
286
+
287
+ def lookUpKey(queryString)
288
+ return replyBadRequest("No search field in queryString") unless
289
+ queryString.has_key?('search')
290
+ #
291
+ # normalize the search string
292
+ #
293
+ searchString = queryString['search']
294
+ searchString.gsub!(/0x/,'')
295
+ searchRegexp = Regexp.new(searchString,
296
+ Regexp::IGNORECASE | Regexp::MULTILINE)
297
+ puts searchRegexp if @debug
298
+ #
299
+ # (linearly) look through the hash of known keys
300
+ # looking for the FIRST match
301
+ #
302
+ keyFile = nil
303
+ @keyLookupData.each_pair do | aKeyFile, keyData |
304
+ next unless keyData =~ searchRegexp
305
+ puts "FOUND #{aKeyFile} (#{keyData})" if @debug
306
+ keyFile = aKeyFile
307
+ break
308
+ end
309
+ return replyNotFound if keyFile.nil?
310
+
311
+ if queryString.has_key?('options') &&
312
+ queryString['options'] == 'mr' then
313
+ #
314
+ # return the key data in machine readable format
315
+ #
316
+ @header = {
317
+ 'Content-Type' => 'application/pgp-keys; charset=utf-8',
318
+ 'Content-Disposition' =>
319
+ 'attachment; filename=' + keyFile
320
+ }
321
+ puts @header if @debug
322
+ @body << File.open(keyFile,'r').read
323
+ else
324
+ #
325
+ # return the key data for a human to read
326
+ #
327
+ keyID = File.basename(keyFile, '.*')
328
+ @body << @headerHtml
329
+ @body << @lookupForm
330
+ @body << '<h1 class="simpleHKP-keyAsckeyId">Key: '+keyID+'</h1>'
331
+ @body << '<h2 class="simpleHKP-keyDataTitle">Key data:</h2>'
332
+ @body << '<pre class="simpleHKP-keyData">'
333
+ @body << @keyLookupData[keyFile]
334
+ @body << '</pre>'
335
+ @body << '<h2 class="simpleHKP-keyAscTitle">Key contents:</h2>'
336
+ @body << '<pre class="simpleHKP-keyAsc">'
337
+ @body << File.open(keyFile,'r').read
338
+ @body << '</pre>'
339
+ @body << @footer
340
+ end
341
+ end
342
+
343
+ def extractKeyInfo(keyFile)
344
+ keyInfo = Array.new
345
+ return keyInfo unless @keyLookupData.has_key?(keyFile)
346
+ #
347
+ # extract the detailed key information from the PUBLIC key
348
+ # see the doc/DETAILS file in the gnupg2 source code
349
+ #
350
+ @keyLookupData[keyFile].each_line do | aLine |
351
+ next unless aLine =~ /^pub/
352
+ keyData = aLine.split(/:/)
353
+ keyInfo.push(keyData[9]) # -1 = user ID
354
+ keyInfo.push(keyData[4]) # 0 = key ID
355
+ keyInfo.push(keyData[3]) # 1 = key type (algorithm)
356
+ keyInfo.push(keyData[2]) # 2 = key length
357
+ keyInfo.push(keyData[5]) # 3 = creation date
358
+ keyInfo.push(keyData[6]) # 4 = expiration date
359
+ keyInfo.push(keyData[1]) # 5 = flags
360
+ end
361
+
362
+ keyInfo
363
+ end
364
+
365
+ def indexKeys(queryString)
366
+ return replyBadRequest("No search field in queryString") unless
367
+ queryString.has_key?('search')
368
+ #
369
+ # normalize the search string
370
+ #
371
+ searchString = queryString['search']
372
+ searchString.gsub!(/0x/,'')
373
+ searchRegexp = Regexp.new(searchString,
374
+ Regexp::IGNORECASE | Regexp::MULTILINE)
375
+ puts searchRegexp if @debug
376
+ #
377
+ # accumulate the keyFiles of any keys that match the serach
378
+ #
379
+ keys = Array.new
380
+ @keyLookupData.each_pair do | aKeyFile, keyData |
381
+ next unless keyData =~ searchRegexp
382
+ puts "FOUND #{aKeyFile} (#{keyData})" if @debug
383
+ keys.push(aKeyFile)
384
+ end
385
+
386
+ if queryString.has_key?('options') &&
387
+ queryString['options'] == 'mr' then
388
+ #
389
+ # return a machine readable list of the keys found
390
+ #
391
+ @header = { 'Content-Type' => 'text/plain' }
392
+ @body << "info:1:#{keys.size}\n"
393
+ keys.each do | aKeyFile |
394
+ keyInfo = extractKeyInfo(aKeyFile)
395
+ next if keyInfo.empty?
396
+ userID = keyInfo.shift
397
+ @body << "pub:#{keyInfo.join(':')}\n"
398
+ keyID = keyInfo.shift
399
+ keyType = keyInfo.shift
400
+ keyLength = keyInfo.shift
401
+ @body << "uid:#{userID}:#{keyInfo.join(':')}\n"
402
+ @body << "\n"
403
+ end
404
+ else
405
+ #
406
+ # return a (simple) human readable list of the keys found
407
+ #
408
+ @body << @headerHtml
409
+ @body << @lookupForm
410
+ @body << '<h1 class="simpleHKP-searchString">Keys matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
411
+ keys.each do | aKeyFile |
412
+ keyInfo = extractKeyInfo(aKeyFile)
413
+ userID = keyInfo.shift
414
+ keyID = keyInfo.shift
415
+ keyType = keyInfo.shift
416
+ keyLength = keyInfo.shift
417
+ created = keyInfo.shift
418
+ expires = keyInfo.shift
419
+ flags = keyInfo.shift
420
+ keyStr = " <li class=\"simpleHKP-searchItem\"><a href=\"lookup?op=get&search=#{keyID}\">#{keyID}:&nbsp;"
421
+ keyStr << userID
422
+ keyStr << "</a></li>\n"
423
+ @body << keyStr
424
+ end
425
+ @body << '</ul>'
426
+ @body << @footer
427
+ end
428
+ pp @body if @debug
429
+ end
430
+
431
+ def decodeQueryString(env)
432
+ queryHash = Hash.new
433
+ URI.decode_www_form(env['QUERY_STRING']).each do | aKeyValue |
434
+ queryHash[aKeyValue[0]] = aKeyValue[1]
435
+ end
436
+ queryHash
437
+ end
438
+
439
+ def call(env)
440
+ #
441
+ # initialize the response parts
442
+ #
443
+ @statusCode = 200
444
+ @header = {"Content-Type" => "text/html; charset=utf-8"}
445
+ @body = Array.new
446
+ #
447
+ # decode the request
448
+ #
449
+ begin
450
+ puts ppEnv(env) if @debug
451
+ case env['REQUEST_METHOD']
452
+ when 'POST'
453
+ case env['REQUEST_PATH']
454
+ when /add$/i
455
+ storeKey(env)
456
+ else
457
+ replyNotImplemented
458
+ end
459
+ when 'GET'
460
+ case env['REQUEST_PATH']
461
+ when /add$/i
462
+ replyUploadForm
463
+ when /lookup$/i
464
+ queryString = decodeQueryString(env)
465
+ if queryString.has_key?('op') then
466
+ case queryString['op']
467
+ when /get/i
468
+ lookUpKey(queryString)
469
+ when /index/i
470
+ indexKeys(queryString)
471
+ else
472
+ replyLookupForm
473
+ end
474
+ else
475
+ replyBadRequest("No op field in queryString")
476
+ end
477
+ when /media\//i
478
+ replyFile(env)
479
+ when /reload$/i
480
+ loadKeys
481
+ loadHtml
482
+ replyDefaultBody
483
+ else
484
+ replyDefaultBody
485
+ end
486
+ else
487
+ replyBadRequest("Unknown request method")
488
+ end
489
+ rescue Exception => exception
490
+ replyInternalError(env, exception)
491
+ end
492
+ #
493
+ # send the response
494
+ #
495
+ if @debug then
496
+ puts "SimpleHKP reply:"
497
+ pp @statusCode
498
+ pp @header
499
+ puts @body.join("\n")
500
+ end
501
+ [ @statusCode, @header, @body.flatten ]
502
+ end
503
+
504
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple-gnupg-keyserver
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Gaito
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-03-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rdoc
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '4.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: hoe
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.13'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.13'
46
+ description: ! "SimpleHKP is a simple Ruby/rack based GnuPG HKP key server.\n\nIts
47
+ sole task is to supply a limited number of GnuPG _public_ keys \nto/from MonkeySphere
48
+ on a limited number of machines/servers. It is \n_not_ meant to server hundreds
49
+ of keys, so it is not meant as a truely \npublic key server.\n\nIf you need to run
50
+ your own _large_ scale production Key Sever then use \n{SKS}[https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home].
51
+ You \nmight want to read Paul Bauer's {How To Setup A Free PGP Key Server in \nUbuntu}[http://www.bauer-power.net/2010/05/how-to-setup-free-pgp-key-server-in.html]."
52
+ email:
53
+ - stephen@perceptisys.co.uk
54
+ executables: []
55
+ extensions: []
56
+ extra_rdoc_files:
57
+ - Manifest.txt
58
+ - History.txt
59
+ - README.rdoc
60
+ files:
61
+ - Manifest.txt
62
+ - .gitignore
63
+ - History.txt
64
+ - README.rdoc
65
+ - Rakefile
66
+ - lib/simpleHKP.rb
67
+ homepage: https://github.com/stephengaito/rGem-simple-gnupg-keyserver
68
+ licenses:
69
+ - MIT
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --main
73
+ - README.rdoc
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.23
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: SimpleHKP is a simple Ruby/rack based GnuPG HKP key server
94
+ test_files: []