simple-gnupg-keyserver 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []