simple-gnupg-keyserver 1.3.0 → 2.0.0

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