simple-gnupg-keyserver 1.3.0 → 2.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.
data/lib/simpleHKP.rb CHANGED
@@ -1,8 +1,3 @@
1
- require 'uri'
2
- require 'open3'
3
- require 'pp'
4
- require 'fileutils'
5
-
6
1
  # This code was inspired by:
7
2
  # Sebi2020's Informatikonline Easy-HKP
8
3
  # https://github.com/Sebi2020/easy-hkp
@@ -39,492 +34,18 @@ require 'fileutils'
39
34
  # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
40
35
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
36
 
42
- class SimpleHKP
43
-
44
- VERSION = "1.3.0"
45
-
46
- def saveLastKey(lastKey)
47
- lastKey['colonData'].gsub!(/\\x3a/,':') if
48
- lastKey.has_key?('colonData')
49
- pp lastKey if @debug
50
- @keyLookupData[lastKey['keyID']] = lastKey unless
51
- lastKey.empty? || lastKey['keyID'].empty?
52
- end
53
-
54
- def loadKeys
55
- @keyLookupData = Hash.new
56
- puts "SimpleHKP: loading keys" if @debug
57
- gpg2cmd = "gpg2 --homedir #{@keyDir} --with-fingerprint --with-colons --list-sigs"
58
- puts gpg2cmd if @debug
59
- lastKey = Hash.new
60
- IO.popen(gpg2cmd, 'r').readlines.each do | aLine |
61
- next if aLine =~ /^tru/
62
- if aLine =~ /^pub/ then
63
- saveLastKey(lastKey)
64
- lastKey = Hash.new
65
- keyData = aLine.split(/:/)
66
- lastKey['userID'] = keyData[9].gsub(/\\x3a/,':') # user ID
67
- lastKey['keyID'] = keyData[4] # key ID
68
- lastKey['algorithm'] = keyData[3] # key type (algorithm)
69
- lastKey['len'] = keyData[2] # key length
70
- lastKey['created'] = keyData[5] # creation date
71
- lastKey['expires'] = keyData[6] # expiration date
72
- lastKey['flags'] = keyData[1] # flags
73
- lastKey['colonData'] = aLine
74
- next
75
- end
76
- lastKey['userID'] = aLine.split(/:/)[9].gsub(/\\x3a/,':') if
77
- aLine =~ /^uid/ && lastKey['userID'].empty?
78
- lastKey['colonData'] << aLine
79
- end
80
- saveLastKey(lastKey)
81
- @keyLookupData.each_pair do | key, value |
82
- value['humanData'] = `gpg2 --list-sig #{key}`
83
- end
84
- pp @keyLookupData if @debug
85
- puts "SimpleHKP: finished loading keys" if @debug
86
- end
87
-
88
- def loadHtml
89
- puts "SimpleHKP: loading html partials" if @debug
90
- Dir.chdir(@htmlDir) do
91
- @headerHtml = "<html><head>"
92
- @headerHtml << " <title>#{@title}</title>\n"
93
- @headerHtml << "</head><body class=\"simpleHKP-body\">\n"
94
- @headerHtml << "<p><a href=\"/\">#{@title}</a></p>"
95
- @headerHtml = File.open('header.html','r').read if
96
- File.exists?('header.html')
97
- puts @headerHtml if @debug
98
-
99
- @defaultBody = ""
100
- @defaultBody << "<h1 class=\"simpleHKP-welcome\">Welcome to SimpleHKP</h1>\n"
101
- @defaultBody << "<ul class=\"simpleHKP-tasksList\">\n"
102
- @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"lookup?op=form\">Search</a></li>\n"
103
- @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"add\">Upload new key</a></li>\n"
104
- @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"reload\">Reload existing keys</a></li>\n"
105
- @defaultBody << "</ul>\n"
106
- @defaultBody = File.open('defaultBody.html','r').read if
107
- File.exists?('defaultBody.html')
108
- puts @defaultBody if @debug
109
-
110
- @lookupForm = ""
111
- @lookupForm << "<div class=\"simpleHKP-lookupFormDiv\">\n"
112
- @lookupForm << " <h1 class=\"simpleHKP-lookupFormTitle\">Search GnuPG keys</h1>\n"
113
- @lookupForm << " <form action=\"lookup\" method=\"get\" id=\"simpleHKP-lookupForm\">\n"
114
- @lookupForm << " <input type=\"text\" name=\"search\" size=\"80\" />\n"
115
- @lookupForm << " <input type=\"hidden\" name=\"op\" value=\"index\" />\n"
116
- @lookupForm << " <input type=\"submit\" value=\"Search\" />\n"
117
- @lookupForm << " </form>\n"
118
- @lookupForm << "</div>\n"
119
- @lookupForm = File.open('lookupForm.html','r').read if
120
- File.exists?('lookupForm.html')
121
- puts @lookupForm if @debug
122
-
123
- @uploadForm = ""
124
- @uploadForm << "<div class=\"simpleHKP-uploadFormDiv\">\n"
125
- @uploadForm << " <h1 class=\"simpleHKP-uploadFormTitle\">Paste GnuPG key to be uploaded below</h1>\n"
126
- @uploadForm << " <form action=\"add\" method=\"post\" id=\"simpleHKP-uploadForm\">\n"
127
- @uploadForm << " <textarea name=\"keytext\" form=\"simpleHKP-uploadForm\" rows = \"30\" cols=\"70\" ></textarea>\n"
128
- @uploadForm << " <input type=\"submit\" value=\"Upload\" />\n"
129
- @uploadForm << " </form>\n"
130
- @uploadForm << "</div>\n"
131
- @uploadForm = File.open('uploadForm.html','r').read if
132
- File.exists?('uploadForm.html')
133
- puts @uploadForm if @debug
134
-
135
- @footer = "</body></html>\n"
136
- @footer = File.open('footer.html','r').read if
137
- File.exists?('footer.html')
138
- puts @footer if @debug
139
- end
140
- puts "SimpleHKP: finished loading html partials" if @debug
141
- end
142
-
143
- def initialize(options = {})
144
- #
145
- # setup the default options
146
- #
147
- defaultOptions = {
148
- 'debug' => false,
149
- 'title' => 'SimpleHKP',
150
- 'simpleHKPdir' => 'simpleHKP',
151
- 'keyDir' => 'keys',
152
- 'mediaDir' => 'media',
153
- 'htmlDir' => 'html',
154
- 'mimeMap' => {
155
- 'css' => 'text/css',
156
- 'html' => 'text/html',
157
- 'js' => 'text/javascript'
158
- }
159
- }
160
- #
161
- # merge the options mimeMap into the default options mimeMap
162
- #
163
- defaultOptions['mimeMap'].merge!(delete(options['mimeMap'])) if
164
- options.has_key?('mimeMap')
165
- #
166
- # merge in the rest of the options into the default options
167
- #
168
- @options = defaultOptions.merge(options)
169
- #
170
- # setup the required variables
171
- #
172
- @debug = @options['debug']
173
- @title = @options['title']
174
- @baseDir = @options['simpleHKPdir']
175
- @keyDir = @baseDir+'/'+@options['keyDir']
176
- @mediaDir = @baseDir+'/'+@options['mediaDir']
177
- @htmlDir = @baseDir+'/'+@options['htmlDir']
178
- @mimeMap = @options['mimeMap']
179
- if @debug then
180
- puts "SimpleHKP options:"
181
- pp @options
182
- end
183
- #
184
- # ensure the required directories all exist
185
- #
186
- FileUtils.mkdir_p(@keyDir)
187
- FileUtils.chmod(0700, @keyDir)
188
- FileUtils.mkdir_p(@mediaDir)
189
- FileUtils.mkdir_p(@htmlDir)
190
- #
191
- # load the existing keys and the html partials
192
- loadKeys
193
- loadHtml
194
- end
195
-
196
- def ppEnv(env)
197
- Dir.pwd+' '+
198
- # '['+URI.decode_www_form(env["rack.input"].rewind.read).pretty_inspect+']'+
199
- env.pretty_inspect
200
- end
201
-
202
- def replyInternalError(env, exception)
203
- @statusCode = 500
204
- @body << @headerHtml
205
- @body << "<p>Internal server error!</p>\n"
206
- @body << "<pre>#{exception}</pre>\n" if @debug
207
- @body << @footer
208
- if @debug then
209
- puts "\n\n-------------------------------------------------------------"
210
- puts exception
211
- puts "-------------------------------------------------------------"
212
- puts ppEnv(env)
213
- puts "-------------------------------------------------------------\n\n"
214
- end
215
- end
216
-
217
- def replyNotImplemented
218
- @statusCode = 501
219
- @body << @headerHtml
220
- @body << '<p>Not implemented!<p>'
221
- @body << @footer
222
- end
223
-
224
- def replyBadRequest(message)
225
- @statusCode = 400
226
- @body << @headerHtml
227
- @body << '<p>Bad request!</p>'
228
- @body << "<p>#{message}</p>"
229
- @body << @footer
230
- end
231
-
232
- def replyNotFound
233
- @statusCode = 404
234
- @body << @headerHtml
235
- @body << '<p>Not found!</p>'
236
- @body << @footer
237
- end
238
-
239
- def replyDefaultBody
240
- @body << @headerHtml
241
- @body << @defaultBody
242
- @body << @footer
243
- end
244
-
245
- def replyLookupForm
246
- @body << @headerHtml
247
- @body << @lookupForm
248
- @body << @footer
249
- end
250
-
251
- def replyUploadForm
252
- @body << @headerHtml
253
- @body << @uploadForm
254
- @body << @footer
255
- end
256
-
257
- def replyFile(env)
258
- fileName = env['REQUEST_PATH'].sub(/^.*\/media\//,'')
259
- if File.exists?(fileName) then
260
- fileExt = File.extname(fileName).sub(/^\./,'')
261
- @header['Content-Type'] = @mimeMap[fileExt] if
262
- @mimeMap.has_key?(fileExt)
263
- @body << File.open(fileName,'r').read
264
- end
265
- end
266
-
267
- def storeKey(env)
268
- #
269
- # decode the post data
270
- #
271
- keys = URI.decode_www_form(env['rack.input'].read)
272
- #
273
- # ensure we are being sent a key
274
- #
275
- return replyBadRequest("No keytext field in post data") unless
276
- keys[0][0] =~ /keytext/
277
- pp keys[0][1] if @debug
278
- #
279
- # send the key data through gpg2 to import it
280
- #
281
- gpgStdErr = ""
282
- Open3.popen3("gpg2 --homedir #{@keyDir} --import") do | stdin, stdout, stderr, wait_thread |
283
- stdin.write(keys[0][1])
284
- stdin.close
285
- gpgStdErr = stderr.read
286
- end
287
- #
288
- # check to see if the key has already been imported and/or changed
289
- #
290
- puts "[[#{gpgStdErr}]]" if @debug
291
- keyChanged = "changed"
292
- if gpgStdErr =~ /not\s+changed/ then
293
- keyChanged = "unchanged"
294
- else
295
- # something has changed to re-load the keys
296
- loadKeys
297
- end
298
- #
299
- # send the key data through gpg2 (again ;-( to extract the keyID
300
- #
301
- gpgKeyData = ""
302
- IO.popen('gpg2 --with-fingerprint --with-colons -', 'r+') do | pipe |
303
- puts 'gpg2 --with-fingerprint --with-colons -' if @debug
304
- pipe.write(keys[0][1])
305
- pipe.close_write
306
- gpgKeyData = pipe.read
307
- end
308
- puts gpgKeyData if @debug
309
- #
310
- # look for the keyID of the public key
311
- #
312
- keyID = nil
313
- gpgKeyData.each_line do | aLine |
314
- keyData = aLine.split(/:/)
315
- next unless keyData[0] =~ /pub/
316
- keyID = keyData[4]
317
- break
318
- end
319
- return replyBadRequest("No keyID found in gpg2 result using uploaded keytext data") if keyID.nil?
320
- #
321
- # return OK
322
- #
323
- @statusCode = 200
324
- @body << @headerHtml
325
- @body << "<h1 class=\"simpleHKP-storedKey\">Stored key: [#{keyID}]</h1>"
326
- @body << "<p>key #{keyChanged}</p>"
327
- @body << "<h2 class=\"simpleHKP-keyDataKeyID\">Key data for key: [#{keyID}]</h2>"
328
- @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['colonData']+"</pre>\n"
329
- @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['humanData']+"</pre>\n"
330
- @body << @footer
331
- end
332
-
333
- def lookUpKey(queryString)
334
- return replyBadRequest("No search field in queryString") unless
335
- queryString.has_key?('search')
336
- #
337
- # normalize the search string
338
- #
339
- searchString = queryString['search']
340
- searchString.gsub!(/0x/,'')
341
- searchRegexp = Regexp.new(searchString,
342
- Regexp::IGNORECASE | Regexp::MULTILINE)
343
- puts searchRegexp if @debug
344
- #
345
- # (linearly) look through the hash of known keys
346
- # looking for the FIRST match
347
- #
348
- keyID = nil
349
- @keyLookupData.each_pair do | key, keyData |
350
- next unless keyData['colonData'] =~ searchRegexp
351
- puts "FOUND #{key} (#{keyData})" if @debug
352
- keyID = key
353
- break
354
- end
355
- return replyNotFound if keyID.nil?
356
-
357
- if queryString.has_key?('options') &&
358
- queryString['options'] == 'mr' then
359
- #
360
- # return the key data in machine readable format
361
- #
362
- @header = {
363
- 'Content-Type' => 'application/pgp-keys; charset=utf-8',
364
- 'Content-Disposition' =>
365
- 'attachment; filename=' + keyID
366
- }
367
- puts @header if @debug
368
- @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
369
- else
370
- #
371
- # return the key data for a human to read
372
- #
373
- @body << @headerHtml
374
- @body << @lookupForm
375
- @body << '<h1 class="simpleHKP-keyAsckeyId">Key: '+keyID+'</h1>'
376
- @body << '<h2 class="simpleHKP-keyDataTitle">Key data:</h2>'
377
- @body << '<pre class="simpleHKP-keyData">'
378
- @body << @keyLookupData[keyID]['humanData']
379
- @body << '</pre>'
380
- @body << '<h2 class="simpleHKP-keyAscTitle">Key contents:</h2>'
381
- @body << '<pre class="simpleHKP-keyAsc">'
382
- @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
383
- @body << '</pre>'
384
- @body << @footer
385
- end
386
- end
387
-
388
- def indexKeys(queryString)
389
- return replyBadRequest("No search field in queryString") unless
390
- queryString.has_key?('search')
391
- #
392
- # normalize the search string
393
- #
394
- searchString = queryString['search']
395
- searchString.gsub!(/0x/,'')
396
- searchRegexp = Regexp.new(searchString,
397
- Regexp::IGNORECASE | Regexp::MULTILINE)
398
- puts searchRegexp if @debug
399
- #
400
- # accumulate the keyIDs of any keys that match the serach
401
- #
402
- keys = Array.new
403
- @keyLookupData.each_pair do | key, keyData |
404
- next unless keyData['colonData'] =~ searchRegexp
405
- puts "FOUND #{key} (#{keyData})" if @debug
406
- keys.push(key)
407
- end
37
+ require 'uri'
38
+ require 'open3'
39
+ require 'pp'
40
+ require 'fileutils'
41
+ require 'safe_yaml'
408
42
 
409
- if queryString.has_key?('options') &&
410
- queryString['options'] == 'mr' then
411
- #
412
- # return a machine readable list of the keys found
413
- #
414
- @header = { 'Content-Type' => 'text/plain' }
415
- @body << "info:1:#{keys.size}\n"
416
- keys.each do | aKey |
417
- next unless @keyLookupData.has_key?(aKey)
418
- keyData = @keyLookupData[aKey]
419
- pubData = [ "pub" ]
420
- pubData << aKey
421
- pubData << keyData['algorithm']
422
- pubData << keyData['len']
423
- pubData << keyData['created']
424
- pubData << keyData['expires']
425
- pubData << keyData['flags']
426
- @body << pubData.join(':')
427
- @body << "\n"
428
- uidData = [ "uid" ]
429
- uidData << keyData['userID'].gsub(/:/, '\x3a')
430
- uidData << keyData['created']
431
- uidData << keyData['expires']
432
- uidData << keyData['flags']
433
- @body << uidData.join(':')
434
- @body << "\n"
435
- end
436
- else
437
- #
438
- # return a (simple) human readable list of the keys found
439
- #
440
- @body << @headerHtml
441
- @body << @lookupForm
442
- @body << '<h1 class="simpleHKP-searchString">Keys matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
443
- keys.each do | aKey |
444
- next unless @keyLookupData.has_key?(aKey)
445
- keyData = @keyLookupData[aKey]
446
- keyStr = " <li class=\"simpleHKP-searchItem\"><a href=\"lookup?op=get&search=#{aKey}\">#{aKey}:&nbsp;"
447
- keyStr << keyData['userID']
448
- keyStr << "</a></li>\n"
449
- @body << keyStr
450
- end
451
- @body << '</ul>'
452
- @body << @footer
453
- end
454
- pp @body if @debug
455
- end
43
+ require 'simpleHKP/keys'
44
+ require 'simpleHKP/identities'
45
+ require 'simpleHKP/server'
456
46
 
457
- def decodeQueryString(env)
458
- queryHash = Hash.new
459
- URI.decode_www_form(env['QUERY_STRING']).each do | aKeyValue |
460
- queryHash[aKeyValue[0]] = aKeyValue[1]
461
- end
462
- queryHash
463
- end
47
+ module SimpleHKP
464
48
 
465
- def call(env)
466
- #
467
- # initialize the response parts
468
- #
469
- @statusCode = 200
470
- @header = {"Content-Type" => "text/html; charset=utf-8"}
471
- @body = Array.new
472
- #
473
- # decode the request
474
- #
475
- begin
476
- puts ppEnv(env) if @debug
477
- case env['REQUEST_METHOD']
478
- when 'POST'
479
- case env['REQUEST_PATH']
480
- when /add$/i
481
- storeKey(env)
482
- else
483
- replyNotImplemented
484
- end
485
- when 'GET'
486
- case env['REQUEST_PATH']
487
- when /add$/i
488
- replyUploadForm
489
- when /lookup$/i
490
- queryString = decodeQueryString(env)
491
- if queryString.has_key?('op') then
492
- case queryString['op']
493
- when /get/i
494
- lookUpKey(queryString)
495
- when /index/i
496
- indexKeys(queryString)
497
- else
498
- replyLookupForm
499
- end
500
- else
501
- replyBadRequest("No op field in queryString")
502
- end
503
- when /media\//i
504
- replyFile(env)
505
- when /reload$/i
506
- loadKeys
507
- loadHtml
508
- replyDefaultBody
509
- else
510
- replyDefaultBody
511
- end
512
- else
513
- replyBadRequest("Unknown request method")
514
- end
515
- rescue Exception => exception
516
- replyInternalError(env, exception)
517
- end
518
- #
519
- # send the response
520
- #
521
- if @debug then
522
- puts "SimpleHKP reply:"
523
- pp @statusCode
524
- pp @header
525
- puts @body.join("\n")
526
- end
527
- [ @statusCode, @header, @body.flatten ]
528
- end
49
+ VERSION = "2.0.0"
529
50
 
530
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-gnupg-keyserver
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-12 00:00:00.000000000 Z
12
+ date: 2015-03-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rdoc
@@ -43,12 +43,12 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
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]."
46
+ description: ! "SimpleHKP is a simple Ruby/rack based GnuPG extendedHKP key and \nidentity
47
+ server packaged as a standard Ruby Gem. \n\nAs such it conforms to an {extended
48
+ \nversion}[http://stephengaito.github.io/rGem-simple-gnupg-keyserver/] of \n{the
49
+ OpenPGP HTTP Keyserver Protocol (HKP) \n(draft-shaw-openpgp-hkp-00.txt)}[http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00].\n\nThe
50
+ {associated \ndocumentation}[https://stephengaito.github.io/rGem-simple-gnupg-keyserver]
51
+ \nprovides more detail."
52
52
  email:
53
53
  - stephen@perceptisys.co.uk
54
54
  executables: []
@@ -65,6 +65,9 @@ files:
65
65
  - Rakefile
66
66
  - lib/simpleHKP.rb
67
67
  - lib/simpleHKP/echo.rb
68
+ - lib/simpleHKP/identities.rb
69
+ - lib/simpleHKP/keys.rb
70
+ - lib/simpleHKP/server.rb
68
71
  homepage: https://github.com/stephengaito/rGem-simple-gnupg-keyserver
69
72
  licenses:
70
73
  - MIT
@@ -91,5 +94,6 @@ rubyforge_project:
91
94
  rubygems_version: 1.8.23
92
95
  signing_key:
93
96
  specification_version: 3
94
- summary: SimpleHKP is a simple Ruby/rack based GnuPG HKP key server
97
+ summary: SimpleHKP is a simple Ruby/rack based GnuPG extendedHKP key and identity
98
+ server packaged as a standard Ruby Gem
95
99
  test_files: []